diff --git a/client.go b/client.go new file mode 100644 index 0000000..6e355b7 --- /dev/null +++ b/client.go @@ -0,0 +1,44 @@ +package gosocks5 + +import ( + "io" + "net" +) + +type Client struct { + Methods []uint8 + Conn net.Conn +} + +func (c *Client) Handshake() (method uint8, err error) { + nm := len(c.Methods) + if nm == 0 { + nm = 1 + } + b := make([]byte, 2+nm) + + if _, err = c.Conn.Write([]byte{Ver5, 1, 0}); err != nil { + return + } + + if _, err = io.ReadFull(c.Conn, b[:2]); err != nil { + return + } + + method = b[1] + + if b[0] != Ver5 { + err = ErrBadVersion + return + } + + return +} + +func (c *Client) Request(r *Request) (*Reply, error) { + if err := r.Write(c.Conn); err != nil { + return nil, err + } + + return ReadReply(c.Conn) +} diff --git a/rfc1928.txt b/rfc1928.txt new file mode 100644 index 0000000..46bf46e --- /dev/null +++ b/rfc1928.txt @@ -0,0 +1,507 @@ + + + + + + +Network Working Group M. Leech +Request for Comments: 1928 Bell-Northern Research Ltd +Category: Standards Track M. Ganis + International Business Machines + Y. Lee + NEC Systems Laboratory + R. Kuris + Unify Corporation + D. Koblas + Independent Consultant + L. Jones + Hewlett-Packard Company + March 1996 + + + SOCKS Protocol Version 5 + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Acknowledgments + + This memo describes a protocol that is an evolution of the previous + version of the protocol, version 4 [1]. This new protocol stems from + active discussions and prototype implementations. The key + contributors are: Marcus Leech: Bell-Northern Research, David Koblas: + Independent Consultant, Ying-Da Lee: NEC Systems Laboratory, LaMont + Jones: Hewlett-Packard Company, Ron Kuris: Unify Corporation, Matt + Ganis: International Business Machines. + +1. Introduction + + The use of network firewalls, systems that effectively isolate an + organizations internal network structure from an exterior network, + such as the INTERNET is becoming increasingly popular. These + firewall systems typically act as application-layer gateways between + networks, usually offering controlled TELNET, FTP, and SMTP access. + With the emergence of more sophisticated application layer protocols + designed to facilitate global information discovery, there exists a + need to provide a general framework for these protocols to + transparently and securely traverse a firewall. + + + + + +Leech, et al Standards Track [Page 1] + +RFC 1928 SOCKS Protocol Version 5 March 1996 + + + There exists, also, a need for strong authentication of such + traversal in as fine-grained a manner as is practical. This + requirement stems from the realization that client-server + relationships emerge between the networks of various organizations, + and that such relationships need to be controlled and often strongly + authenticated. + + The protocol described here is designed to provide a framework for + client-server applications in both the TCP and UDP domains to + conveniently and securely use the services of a network firewall. + The protocol is conceptually a "shim-layer" between the application + layer and the transport layer, and as such does not provide network- + layer gateway services, such as forwarding of ICMP messages. + +2. Existing practice + + There currently exists a protocol, SOCKS Version 4, that provides for + unsecured firewall traversal for TCP-based client-server + applications, including TELNET, FTP and the popular information- + discovery protocols such as HTTP, WAIS and GOPHER. + + This new protocol extends the SOCKS Version 4 model to include UDP, + and extends the framework to include provisions for generalized + strong authentication schemes, and extends the addressing scheme to + encompass domain-name and V6 IP addresses. + + The implementation of the SOCKS protocol typically involves the + recompilation or relinking of TCP-based client applications to use + the appropriate encapsulation routines in the SOCKS library. + +Note: + + Unless otherwise noted, the decimal numbers appearing in packet- + format diagrams represent the length of the corresponding field, in + octets. Where a given octet must take on a specific value, the + syntax X'hh' is used to denote the value of the single octet in that + field. When the word 'Variable' is used, it indicates that the + corresponding field has a variable length defined either by an + associated (one or two octet) length field, or by a data type field. + +3. Procedure for TCP-based clients + + When a TCP-based client wishes to establish a connection to an object + that is reachable only via a firewall (such determination is left up + to the implementation), it must open a TCP connection to the + appropriate SOCKS port on the SOCKS server system. The SOCKS service + is conventionally located on TCP port 1080. If the connection + request succeeds, the client enters a negotiation for the + + + +Leech, et al Standards Track [Page 2] + +RFC 1928 SOCKS Protocol Version 5 March 1996 + + + authentication method to be used, authenticates with the chosen + method, then sends a relay request. The SOCKS server evaluates the + request, and either establishes the appropriate connection or denies + it. + + Unless otherwise noted, the decimal numbers appearing in packet- + format diagrams represent the length of the corresponding field, in + octets. Where a given octet must take on a specific value, the + syntax X'hh' is used to denote the value of the single octet in that + field. When the word 'Variable' is used, it indicates that the + corresponding field has a variable length defined either by an + associated (one or two octet) length field, or by a data type field. + + The client connects to the server, and sends a version + identifier/method selection message: + + +----+----------+----------+ + |VER | NMETHODS | METHODS | + +----+----------+----------+ + | 1 | 1 | 1 to 255 | + +----+----------+----------+ + + The VER field is set to X'05' for this version of the protocol. The + NMETHODS field contains the number of method identifier octets that + appear in the METHODS field. + + The server selects from one of the methods given in METHODS, and + sends a METHOD selection message: + + +----+--------+ + |VER | METHOD | + +----+--------+ + | 1 | 1 | + +----+--------+ + + If the selected METHOD is X'FF', none of the methods listed by the + client are acceptable, and the client MUST close the connection. + + The values currently defined for METHOD are: + + o X'00' NO AUTHENTICATION REQUIRED + o X'01' GSSAPI + o X'02' USERNAME/PASSWORD + o X'03' to X'7F' IANA ASSIGNED + o X'80' to X'FE' RESERVED FOR PRIVATE METHODS + o X'FF' NO ACCEPTABLE METHODS + + The client and server then enter a method-specific sub-negotiation. + + + +Leech, et al Standards Track [Page 3] + +RFC 1928 SOCKS Protocol Version 5 March 1996 + + + Descriptions of the method-dependent sub-negotiations appear in + separate memos. + + Developers of new METHOD support for this protocol should contact + IANA for a METHOD number. The ASSIGNED NUMBERS document should be + referred to for a current list of METHOD numbers and their + corresponding protocols. + + Compliant implementations MUST support GSSAPI and SHOULD support + USERNAME/PASSWORD authentication methods. + +4. Requests + + Once the method-dependent subnegotiation has completed, the client + sends the request details. If the negotiated method includes + encapsulation for purposes of integrity checking and/or + confidentiality, these requests MUST be encapsulated in the method- + dependent encapsulation. + + The SOCKS request is formed as follows: + + +----+-----+-------+------+----------+----------+ + |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | + +----+-----+-------+------+----------+----------+ + | 1 | 1 | X'00' | 1 | Variable | 2 | + +----+-----+-------+------+----------+----------+ + + Where: + + o VER protocol version: X'05' + o CMD + o CONNECT X'01' + o BIND X'02' + o UDP ASSOCIATE X'03' + o RSV RESERVED + o ATYP address type of following address + o IP V4 address: X'01' + o DOMAINNAME: X'03' + o IP V6 address: X'04' + o DST.ADDR desired destination address + o DST.PORT desired destination port in network octet + order + + The SOCKS server will typically evaluate the request based on source + and destination addresses, and return one or more reply messages, as + appropriate for the request type. + + + + + +Leech, et al Standards Track [Page 4] + +RFC 1928 SOCKS Protocol Version 5 March 1996 + + +5. Addressing + + In an address field (DST.ADDR, BND.ADDR), the ATYP field specifies + the type of address contained within the field: + + o X'01' + + the address is a version-4 IP address, with a length of 4 octets + + o X'03' + + the address field contains a fully-qualified domain name. The first + octet of the address field contains the number of octets of name that + follow, there is no terminating NUL octet. + + o X'04' + + the address is a version-6 IP address, with a length of 16 octets. + +6. Replies + + The SOCKS request information is sent by the client as soon as it has + established a connection to the SOCKS server, and completed the + authentication negotiations. The server evaluates the request, and + returns a reply formed as follows: + + +----+-----+-------+------+----------+----------+ + |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | + +----+-----+-------+------+----------+----------+ + | 1 | 1 | X'00' | 1 | Variable | 2 | + +----+-----+-------+------+----------+----------+ + + Where: + + o VER protocol version: X'05' + o REP Reply field: + o X'00' succeeded + o X'01' general SOCKS server failure + o X'02' connection not allowed by ruleset + o X'03' Network unreachable + o X'04' Host unreachable + o X'05' Connection refused + o X'06' TTL expired + o X'07' Command not supported + o X'08' Address type not supported + o X'09' to X'FF' unassigned + o RSV RESERVED + o ATYP address type of following address + + + +Leech, et al Standards Track [Page 5] + +RFC 1928 SOCKS Protocol Version 5 March 1996 + + + o IP V4 address: X'01' + o DOMAINNAME: X'03' + o IP V6 address: X'04' + o BND.ADDR server bound address + o BND.PORT server bound port in network octet order + + Fields marked RESERVED (RSV) must be set to X'00'. + + If the chosen method includes encapsulation for purposes of + authentication, integrity and/or confidentiality, the replies are + encapsulated in the method-dependent encapsulation. + +CONNECT + + In the reply to a CONNECT, BND.PORT contains the port number that the + server assigned to connect to the target host, while BND.ADDR + contains the associated IP address. The supplied BND.ADDR is often + different from the IP address that the client uses to reach the SOCKS + server, since such servers are often multi-homed. It is expected + that the SOCKS server will use DST.ADDR and DST.PORT, and the + client-side source address and port in evaluating the CONNECT + request. + +BIND + + The BIND request is used in protocols which require the client to + accept connections from the server. FTP is a well-known example, + which uses the primary client-to-server connection for commands and + status reports, but may use a server-to-client connection for + transferring data on demand (e.g. LS, GET, PUT). + + It is expected that the client side of an application protocol will + use the BIND request only to establish secondary connections after a + primary connection is established using CONNECT. In is expected that + a SOCKS server will use DST.ADDR and DST.PORT in evaluating the BIND + request. + + Two replies are sent from the SOCKS server to the client during a + BIND operation. The first is sent after the server creates and binds + a new socket. The BND.PORT field contains the port number that the + SOCKS server assigned to listen for an incoming connection. The + BND.ADDR field contains the associated IP address. The client will + typically use these pieces of information to notify (via the primary + or control connection) the application server of the rendezvous + address. The second reply occurs only after the anticipated incoming + connection succeeds or fails. + + + + + +Leech, et al Standards Track [Page 6] + +RFC 1928 SOCKS Protocol Version 5 March 1996 + + + In the second reply, the BND.PORT and BND.ADDR fields contain the + address and port number of the connecting host. + +UDP ASSOCIATE + + The UDP ASSOCIATE request is used to establish an association within + the UDP relay process to handle UDP datagrams. The DST.ADDR and + DST.PORT fields contain the address and port that the client expects + to use to send UDP datagrams on for the association. The server MAY + use this information to limit access to the association. If the + client is not in possesion of the information at the time of the UDP + ASSOCIATE, the client MUST use a port number and address of all + zeros. + + A UDP association terminates when the TCP connection that the UDP + ASSOCIATE request arrived on terminates. + + In the reply to a UDP ASSOCIATE request, the BND.PORT and BND.ADDR + fields indicate the port number/address where the client MUST send + UDP request messages to be relayed. + +Reply Processing + + When a reply (REP value other than X'00') indicates a failure, the + SOCKS server MUST terminate the TCP connection shortly after sending + the reply. This must be no more than 10 seconds after detecting the + condition that caused a failure. + + If the reply code (REP value of X'00') indicates a success, and the + request was either a BIND or a CONNECT, the client may now start + passing data. If the selected authentication method supports + encapsulation for the purposes of integrity, authentication and/or + confidentiality, the data are encapsulated using the method-dependent + encapsulation. Similarly, when data arrives at the SOCKS server for + the client, the server MUST encapsulate the data as appropriate for + the authentication method in use. + +7. Procedure for UDP-based clients + + A UDP-based client MUST send its datagrams to the UDP relay server at + the UDP port indicated by BND.PORT in the reply to the UDP ASSOCIATE + request. If the selected authentication method provides + encapsulation for the purposes of authenticity, integrity, and/or + confidentiality, the datagram MUST be encapsulated using the + appropriate encapsulation. Each UDP datagram carries a UDP request + header with it: + + + + + +Leech, et al Standards Track [Page 7] + +RFC 1928 SOCKS Protocol Version 5 March 1996 + + + +----+------+------+----------+----------+----------+ + |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | + +----+------+------+----------+----------+----------+ + | 2 | 1 | 1 | Variable | 2 | Variable | + +----+------+------+----------+----------+----------+ + + The fields in the UDP request header are: + + o RSV Reserved X'0000' + o FRAG Current fragment number + o ATYP address type of following addresses: + o IP V4 address: X'01' + o DOMAINNAME: X'03' + o IP V6 address: X'04' + o DST.ADDR desired destination address + o DST.PORT desired destination port + o DATA user data + + When a UDP relay server decides to relay a UDP datagram, it does so + silently, without any notification to the requesting client. + Similarly, it will drop datagrams it cannot or will not relay. When + a UDP relay server receives a reply datagram from a remote host, it + MUST encapsulate that datagram using the above UDP request header, + and any authentication-method-dependent encapsulation. + + The UDP relay server MUST acquire from the SOCKS server the expected + IP address of the client that will send datagrams to the BND.PORT + given in the reply to UDP ASSOCIATE. It MUST drop any datagrams + arriving from any source IP address other than the one recorded for + the particular association. + + The FRAG field indicates whether or not this datagram is one of a + number of fragments. If implemented, the high-order bit indicates + end-of-fragment sequence, while a value of X'00' indicates that this + datagram is standalone. Values between 1 and 127 indicate the + fragment position within a fragment sequence. Each receiver will + have a REASSEMBLY QUEUE and a REASSEMBLY TIMER associated with these + fragments. The reassembly queue must be reinitialized and the + associated fragments abandoned whenever the REASSEMBLY TIMER expires, + or a new datagram arrives carrying a FRAG field whose value is less + than the highest FRAG value processed for this fragment sequence. + The reassembly timer MUST be no less than 5 seconds. It is + recommended that fragmentation be avoided by applications wherever + possible. + + Implementation of fragmentation is optional; an implementation that + does not support fragmentation MUST drop any datagram whose FRAG + field is other than X'00'. + + + +Leech, et al Standards Track [Page 8] + +RFC 1928 SOCKS Protocol Version 5 March 1996 + + + The programming interface for a SOCKS-aware UDP MUST report an + available buffer space for UDP datagrams that is smaller than the + actual space provided by the operating system: + + o if ATYP is X'01' - 10+method_dependent octets smaller + o if ATYP is X'03' - 262+method_dependent octets smaller + o if ATYP is X'04' - 20+method_dependent octets smaller + +8. Security Considerations + + This document describes a protocol for the application-layer + traversal of IP network firewalls. The security of such traversal is + highly dependent on the particular authentication and encapsulation + methods provided in a particular implementation, and selected during + negotiation between SOCKS client and SOCKS server. + + Careful consideration should be given by the administrator to the + selection of authentication methods. + +9. References + + [1] Koblas, D., "SOCKS", Proceedings: 1992 Usenix Security Symposium. + +Author's Address + + Marcus Leech + Bell-Northern Research Ltd + P.O. Box 3511, Stn. C, + Ottawa, ON + CANADA K1Y 4H7 + + Phone: (613) 763-9145 + EMail: mleech@bnr.ca + + + + + + + + + + + + + + + + + + +Leech, et al Standards Track [Page 9] + diff --git a/rfc1929.txt b/rfc1929.txt new file mode 100644 index 0000000..64fa02c --- /dev/null +++ b/rfc1929.txt @@ -0,0 +1,115 @@ + + + + + + +Network Working Group M. Leech +Request for Comments: 1929 Bell-Northern Research Ltd +Category: Standards Track March 1996 + + + Username/Password Authentication for SOCKS V5 + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +1. Introduction + + The protocol specification for SOCKS Version 5 specifies a + generalized framework for the use of arbitrary authentication + protocols in the initial socks connection setup. This document + describes one of those protocols, as it fits into the SOCKS Version 5 + authentication "subnegotiation". + +Note: + + Unless otherwise noted, the decimal numbers appearing in packet- + format diagrams represent the length of the corresponding field, in + octets. Where a given octet must take on a specific value, the + syntax X'hh' is used to denote the value of the single octet in that + field. When the word 'Variable' is used, it indicates that the + corresponding field has a variable length defined either by an + associated (one or two octet) length field, or by a data type field. + +2. Initial negotiation + + Once the SOCKS V5 server has started, and the client has selected the + Username/Password Authentication protocol, the Username/Password + subnegotiation begins. This begins with the client producing a + Username/Password request: + + +----+------+----------+------+----------+ + |VER | ULEN | UNAME | PLEN | PASSWD | + +----+------+----------+------+----------+ + | 1 | 1 | 1 to 255 | 1 | 1 to 255 | + +----+------+----------+------+----------+ + + + + + + +Leech Standards Track [Page 1] + +RFC 1929 Username Authentication for SOCKS V5 March 1996 + + + The VER field contains the current version of the subnegotiation, + which is X'01'. The ULEN field contains the length of the UNAME field + that follows. The UNAME field contains the username as known to the + source operating system. The PLEN field contains the length of the + PASSWD field that follows. The PASSWD field contains the password + association with the given UNAME. + + The server verifies the supplied UNAME and PASSWD, and sends the + following response: + + +----+--------+ + |VER | STATUS | + +----+--------+ + | 1 | 1 | + +----+--------+ + + A STATUS field of X'00' indicates success. If the server returns a + `failure' (STATUS value other than X'00') status, it MUST close the + connection. + +3. Security Considerations + + This document describes a subnegotiation that provides authentication + services to the SOCKS protocol. Since the request carries the + password in cleartext, this subnegotiation is not recommended for + environments where "sniffing" is possible and practical. + +4. Author's Address + + Marcus Leech + Bell-Northern Research Ltd + P.O. Box 3511, Station C + Ottawa, ON + CANADA K1Y 4H7 + + Phone: +1 613 763 9145 + EMail: mleech@bnr.ca + + + + + + + + + + + + + + +Leech Standards Track [Page 2] + diff --git a/server.go b/server.go new file mode 100644 index 0000000..e0654de --- /dev/null +++ b/server.go @@ -0,0 +1,60 @@ +package gosocks5 + +import ( + "log" + "net" +) + +type Server struct { + Addr string // TCP address to listen on + + SelectMethod func(methods ...uint8) uint8 + Handle func(conn net.Conn, method uint8) error +} + +func (s *Server) Serve() error { + addr, err := net.ResolveTCPAddr("tcp", s.Addr) + if err != nil { + return err + } + + ln, err := net.ListenTCP("tcp", addr) + if err != nil { + return err + } + defer ln.Close() + + for { + conn, err := ln.AcceptTCP() + if err != nil { + log.Println("accept:", err) + continue + } + //log.Println("accept", conn.RemoteAddr().String()) + go s.handle(conn) + } +} + +func (s *Server) handle(conn net.Conn) { + methods, err := ReadMethods(conn) + if err != nil { + log.Println(err) + return + } + + var method uint8 + if s.SelectMethod != nil { + method = s.SelectMethod(methods...) + } + + if _, err := conn.Write([]byte{Ver5, method}); err != nil { + log.Println(err) + return + } + + if s.Handle != nil { + s.Handle(conn, method) + } else { + conn.Close() + } +} diff --git a/socks5.go b/socks5.go index a5c52a8..2851db7 100644 --- a/socks5.go +++ b/socks5.go @@ -1,20 +1,25 @@ // SOCKS Protocol Version 5 // http://tools.ietf.org/html/rfc1928 +// http://tools.ietf.org/html/rfc1929 package gosocks5 import ( "bytes" "encoding/binary" - "fmt" + "errors" + //"fmt" + "io" + //"log" "net" + "strconv" ) -const Version5 uint8 = 5 - -type MethodType uint8 +const ( + Ver5 = 5 +) const ( - MethodNoAuth MethodType = iota + MethodNoAuth uint8 = iota MethodGSSAPI MethodUserPass // X'03' to X'7F' IANA ASSIGNED @@ -22,26 +27,20 @@ const ( MethodNoAcceptable = 0xFF ) -type CmdType uint8 - const ( - CmdConnect CmdType = 1 - CmdBind = 2 - CmdUdp = 3 + CmdConnect uint8 = 1 + CmdBind = 2 + CmdUdp = 3 ) -type AddrType uint8 - const ( - AddrIPv4 AddrType = 1 - AddrDomainName = 3 - AddrIPv6 = 4 + AddrIPv4 uint8 = 1 + AddrDomain = 3 + AddrIPv6 = 4 ) -type ReplyType uint8 - const ( - Succeeded ReplyType = iota + Succeeded uint8 = iota Failure NotAllowed NetUnreachable @@ -52,129 +51,367 @@ const ( AddrUnsupported ) -type Socks5 struct { - conn net.Conn - methods Methods -} - -func NewSocks5(conn net.Conn, methods ...MethodType) *Socks5 { - s := &Socks5{ - conn: conn, - methods: Methods(methods), - } - if len(s.methods) == 0 { - s.methods = append(s.methods, MethodNoAuth) - } - return s -} - -func (s *Socks5) Init() error { - b := make([]byte, 2) - if _, err := s.conn.Write(s.methods.Encode()); err != nil { - return err - } - if _, err := s.conn.Read(b); err != nil { - return err - } - return nil -} +var ( + ErrBadVersion = errors.New("Bad version") + ErrBadFormat = errors.New("Bad format") + ErrBadAddrType = errors.New("Bad address type") + ErrShortBuffer = errors.New("Short buffer") + ErrBadMethod = errors.New("Bad method") +) /* +Method selection +----+----------+----------+ |VER | NMETHODS | METHODS | +----+----------+----------+ | 1 | 1 | 1 to 255 | +----+----------+----------+ */ -type Methods []MethodType - -func (methods Methods) Encode() []byte { +func ReadMethods(r io.Reader) ([]uint8, error) { b := make([]byte, 257) - pos := 0 - b[pos] = Version5 - pos++ - b[pos] = byte(len(methods)) - pos++ - for _, m := range methods { - b[pos] = byte(m) - pos++ + n, err := io.ReadAtLeast(r, b, 2) + if err != nil { + return nil, err } - return b[:pos] + if b[0] != Ver5 { + return nil, ErrBadVersion + } + + if b[1] == 0 { + return nil, ErrBadMethod + } + + length := 2 + int(b[1]) + if n < length { + if _, err := io.ReadFull(r, b[n:length]); err != nil { + return nil, err + } + } + + return b[2:length], nil +} + +type Address struct { + Type uint8 + Host string + Port uint16 +} + +func (addr *Address) Decode(b []byte) error { + addr.Type = b[0] + pos := 1 + switch addr.Type { + case AddrIPv4: + addr.Host = net.IP(b[pos : pos+net.IPv4len]).String() + pos += net.IPv4len + case AddrIPv6: + addr.Host = net.IP(b[pos : pos+net.IPv6len]).String() + pos += net.IPv6len + case AddrDomain: + addrlen := int(b[pos]) + pos++ + addr.Host = string(b[pos : pos+addrlen]) + pos += addrlen + default: + return ErrBadAddrType + } + + addr.Port = binary.BigEndian.Uint16(b[pos:]) + + return nil +} + +func (addr *Address) Encode(b []byte) (int, error) { + b[0] = addr.Type + pos := 1 + switch addr.Type { + case AddrIPv4: + pos += copy(b[pos:], net.ParseIP(addr.Host).To4()) + case AddrDomain: + b[pos] = byte(len(addr.Host)) + pos++ + pos += copy(b[pos:], []byte(addr.Host)) + case AddrIPv6: + pos += copy(b[pos:], net.ParseIP(addr.Host).To16()) + default: + b[0] = AddrIPv4 + pos += 4 + } + binary.BigEndian.PutUint16(b[pos:], addr.Port) + pos += 2 + + return pos, nil +} + +func (addr *Address) String() string { + return net.JoinHostPort(addr.Host, strconv.Itoa(int(addr.Port))) } /* +The SOCKSv5 request +----+-----+-------+------+----------+----------+ |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00' | 1 | Variable | 2 | +----+-----+-------+------+----------+----------+ */ -type CMD struct { - Cmd CmdType - AType AddrType - Addr string - Port uint16 +type Request struct { + Cmd uint8 + Addr *Address } -func NewCMD(cmdType CmdType, aType AddrType, addr string, port uint16) *CMD { - return &CMD{ - Cmd: cmdType, - AType: aType, - Addr: addr, - Port: port, +func NewRequest(cmd uint8, addr *Address) *Request { + return &Request{ + Cmd: cmd, + Addr: addr, } } -func (cmd *CMD) Encode() []byte { - b := make([]byte, 128) - b[0] = Version5 - b[1] = byte(cmd.Cmd) - b[3] = byte(cmd.AType) - pos := 4 +func ReadRequest(r io.Reader) (*Request, error) { + b := make([]byte, 262) + n, err := io.ReadAtLeast(r, b, 5) + if err != nil { + return nil, err + } - switch cmd.AType { + if b[0] != Ver5 { + return nil, ErrBadVersion + } + + request := &Request{ + Cmd: b[1], + } + + atype := b[3] + length := 0 + switch atype { case AddrIPv4: - pos += copy(b[pos:], net.ParseIP(cmd.Addr).To4()) - case AddrDomainName: - b[pos] = byte(len(cmd.Addr)) - pos++ - pos += copy(b[pos:], []byte(cmd.Addr)) + length = 10 case AddrIPv6: - pos += copy(b[pos:], net.ParseIP(cmd.Addr).To16()) + length = 22 + case AddrDomain: + length = 7 + int(b[4]) + default: + return nil, ErrBadAddrType } - binary.BigEndian.PutUint16(b[pos:], cmd.Port) - return b[:pos+2] + if n < length { + if _, err := io.ReadFull(r, b[n:length]); err != nil { + return nil, err + } + } + addr := new(Address) + if err := addr.Decode(b[3:length]); err != nil { + return nil, err + } + request.Addr = addr + + return request, nil } -func (cmd *CMD) Decode(data []byte) { - cmd.Cmd = CmdType(data[1]) - cmd.AType = AddrType(data[3]) +func (r *Request) Write(w io.Writer) (err error) { + b := make([]byte, 262) - pos := 4 - switch cmd.AType { + b[0] = Ver5 + b[1] = r.Cmd + // b[2] = 0 //rsv + b[3] = AddrIPv4 // default + + length := 10 + if r.Addr != nil { + n, _ := r.Addr.Encode(b[3:]) + length = 3 + n + } + _, err = w.Write(b[:length]) + return +} + +/* +The SOCKSv5 reply ++----+-----+-------+------+----------+----------+ +|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | ++----+-----+-------+------+----------+----------+ +| 1 | 1 | X'00' | 1 | Variable | 2 | ++----+-----+-------+------+----------+----------+ +*/ +type Reply struct { + Rep uint8 + Addr *Address +} + +func NewReply(rep uint8, addr *Address) *Reply { + return &Reply{ + Rep: rep, + Addr: addr, + } +} + +func ReadReply(r io.Reader) (*Reply, error) { + b := make([]byte, 262) + n, err := io.ReadAtLeast(r, b, 5) + if err != nil { + return nil, err + } + + if b[0] != Ver5 { + return nil, ErrBadVersion + } + + reply := &Reply{ + Rep: b[1], + } + + atype := b[3] + length := 0 + switch atype { case AddrIPv4: - cmd.Addr = net.IP(data[pos : pos+4]).String() - pos += 4 - case AddrDomainName: - length := int(data[pos]) - pos++ - cmd.Addr = string(data[pos : pos+length]) - pos += length + length = 10 case AddrIPv6: - cmd.Addr = net.IP(data[pos : pos+16]).String() - pos += 16 + length = 22 + case AddrDomain: + length = 7 + int(b[4]) + default: + return nil, ErrBadAddrType } - cmd.Port = binary.BigEndian.Uint16(data[pos:]) + if n < length { + if _, err := io.ReadFull(r, b[n:length]); err != nil { + return nil, err + } + } + + addr := new(Address) + if err := addr.Decode(b[3:length]); err != nil { + return nil, err + } + reply.Addr = addr + + return reply, nil } -func (cmd *CMD) String() string { - b := bytes.Buffer{} - b.WriteString("cmd:" + fmt.Sprintf("%x", cmd.Cmd)) - b.WriteString("\natype:" + fmt.Sprintf("%x", cmd.AType)) - b.WriteString("\naddr:" + cmd.Addr) - b.WriteString("\nport:" + fmt.Sprintf("%x", cmd.Port)) - return b.String() +func (r *Reply) Write(w io.Writer) (err error) { + b := make([]byte, 262) + + b[0] = Ver5 + b[1] = r.Rep + // b[2] = 0 //rsv + b[3] = AddrIPv4 // default + + length := 10 + if r.Addr != nil { + n, _ := r.Addr.Encode(b[3:]) + length = 3 + n + } + _, err = w.Write(b[:length]) + + return +} + +/* +UDP request ++----+------+------+----------+----------+----------+ +|RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | ++----+------+------+----------+----------+----------+ +| 2 | 1 | 1 | Variable | 2 | Variable | ++----+------+------+----------+----------+----------+ +*/ +type UDPHeader struct { + Rsv uint16 + Frag uint8 + Addr *Address +} + +func NewUDPHeader(rsv uint16, frag uint8, addr *Address) *UDPHeader { + return &UDPHeader{ + Rsv: rsv, + Frag: frag, + Addr: addr, + } +} + +type UDPDatagram struct { + Header *UDPHeader + Data []byte +} + +func NewUDPDatagram(header *UDPHeader, data []byte) *UDPDatagram { + return &UDPDatagram{ + Header: header, + Data: data, + } +} + +func ReadUDPDatagram(r io.Reader) (*UDPDatagram, error) { + b := make([]byte, 65797) + n, err := io.ReadAtLeast(r, b, 5) + if err != nil { + return nil, err + } + + header := &UDPHeader{ + Rsv: binary.BigEndian.Uint16(b[:2]), + Frag: b[2], + } + + atype := b[3] + hlen := 0 + switch atype { + case AddrIPv4: + hlen = 10 + case AddrIPv6: + hlen = 22 + case AddrDomain: + hlen = 7 + int(b[4]) + default: + return nil, ErrBadAddrType + } + + dlen := int(header.Rsv) + if n < hlen+dlen { + if _, err := io.ReadFull(r, b[n:hlen+dlen]); err != nil { + return nil, err + } + n = hlen + dlen + } + + header.Addr = new(Address) + if err := header.Addr.Decode(b[3:hlen]); err != nil { + return nil, err + } + + d := &UDPDatagram{ + Header: header, + Data: b[hlen:n], + } + + return d, nil +} + +func (d *UDPDatagram) Write(w io.Writer) error { + buffer := &bytes.Buffer{} + + b := make([]byte, 259) + if d.Header != nil { + binary.BigEndian.PutUint16(b[:2], d.Header.Rsv) + buffer.Write(b[:2]) + buffer.WriteByte(d.Header.Frag) + + b[0] = AddrIPv4 + b[1] = 0 + length := 7 + + if d.Header.Addr != nil { + length, _ = d.Header.Addr.Encode(b) + } + buffer.Write(b[:length]) + } else { + b[3] = AddrIPv4 + buffer.Write(b[:10]) + } + + buffer.Write(d.Data) + _, err := w.Write(buffer.Bytes()) + + return err }