diff --git a/component/socks4/socks4.go b/component/socks4/socks4.go new file mode 100644 index 0000000..5edb39b --- /dev/null +++ b/component/socks4/socks4.go @@ -0,0 +1,97 @@ +// Package socks4 provides SOCKS4/SOCKS4A client functionalities. +package socks4 + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "net" + "strconv" +) + +const Version = 0x04 + +type Command = uint8 + +const ( + CmdConnect Command = 0x01 + CmdBind Command = 0x02 +) + +func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID string) (err error) { + var ( + host string + port uint16 + ) + if host, port, err = splitHostPort(addr); err != nil { + return err + } + + ip := net.ParseIP(host) + if ip == nil /* HOST */ { + ip = net.IPv4(0, 0, 0, 1).To4() + } else if ip.To4() == nil /* IPv6 */ { + return errors.New("IPv6 not supported") + } + + var ( + dstIP [4]byte + dstPort [2]byte + ) + copy(dstIP[:], ip.To4()) + binary.BigEndian.PutUint16(dstPort[:], port) + + req := &bytes.Buffer{} + req.WriteByte(Version) + req.WriteByte(command) + req.Write(dstPort[:]) + req.Write(dstIP[:]) + req.WriteString(userID) + req.WriteByte(0) /* NULL */ + + if bytes.Equal(dstIP[:3], []byte{0, 0, 0}) && dstIP[3] != 0 /* SOCKS4A */ { + req.WriteString(host) + req.WriteByte(0) /* NULL */ + } + + if _, err = rw.Write(req.Bytes()); err != nil { + return err + } + + var resp [8]byte + if _, err = rw.Read(resp[:]); err != nil { + return err + } + + if resp[0] != 0x00 { + return errors.New("reply version code mismatched") + } + + switch resp[1] { + case 90: + return nil // request granted + case 91: + return errors.New("request rejected or failed") + case 92: + return errors.New("request rejected because SOCKS server cannot connect to identd on the client") + case 93: + return errors.New("request rejected because the client program and identd report different user-ids") + default: + return errors.New("request failed with unknown reply code") + } +} + +func splitHostPort(addr string) (string, uint16, error) { + host, portStr, err := net.SplitHostPort(addr) + if err != nil { + return "", 0, err + } + + portInt, err := strconv.ParseUint(portStr, 10, 16) + if err != nil { + return "", 0, err + } + + return host, uint16(portInt), nil +} diff --git a/component/socks4/socks4.txt b/component/socks4/socks4.txt new file mode 100644 index 0000000..6bc0837 --- /dev/null +++ b/component/socks4/socks4.txt @@ -0,0 +1,152 @@ + SOCKS: A protocol for TCP proxy across firewalls + + Ying-Da Lee + Principal Member Technical Staff + NEC Systems Laboratory, CSTC + ylee@syl.dl.nec.com + +SOCKS was originally developed by David Koblas and subsequently modified +and extended by me to its current running version -- version 4. It is a +protocol that relays TCP sessions at a firewall host to allow application +users transparent access across the firewall. Because the protocol is +independent of application protocols, it can be (and has been) used for +many different services, such as telnet, ftp, finger, whois, gopher, WWW, +etc. Access control can be applied at the beginning of each TCP session; +thereafter the server simply relays the data between the client and the +application server, incurring minimum processing overhead. Since SOCKS +never has to know anything about the application protocol, it should also +be easy for it to accommodate applications which use encryption to protect +their traffic from nosey snoopers. + +Two operations are defined: CONNECT and BIND. + +1) CONNECT + +The client connects to the SOCKS server and sends a CONNECT request when +it wants to establish a connection to an application server. The client +includes in the request packet the IP address and the port number of the +destination host, and userid, in the following format. + + +----+----+----+----+----+----+----+----+----+----+....+----+ + | VN | CD | DSTPORT | DSTIP | USERID |NULL| + +----+----+----+----+----+----+----+----+----+----+....+----+ + # of bytes: 1 1 2 4 variable 1 + +VN is the SOCKS protocol version number and should be 4. CD is the +SOCKS command code and should be 1 for CONNECT request. NULL is a byte +of all zero bits. + +The SOCKS server checks to see whether such a request should be granted +based on any combination of source IP address, destination IP address, +destination port number, the userid, and information it may obtain by +consulting IDENT, cf. RFC 1413. If the request is granted, the SOCKS +server makes a connection to the specified port of the destination host. +A reply packet is sent to the client when this connection is established, +or when the request is rejected or the operation fails. + + +----+----+----+----+----+----+----+----+ + | VN | CD | DSTPORT | DSTIP | + +----+----+----+----+----+----+----+----+ + # of bytes: 1 1 2 4 + +VN is the version of the reply code and should be 0. CD is the result +code with one of the following values: + + 90: request granted + 91: request rejected or failed + 92: request rejected becasue SOCKS server cannot connect to + identd on the client + 93: request rejected because the client program and identd + report different user-ids + +The remaining fields are ignored. + +The SOCKS server closes its connection immediately after notifying +the client of a failed or rejected request. For a successful request, +the SOCKS server gets ready to relay traffic on both directions. This +enables the client to do I/O on its connection as if it were directly +connected to the application server. + + +2) BIND + +The client connects to the SOCKS server and sends a BIND request when +it wants to prepare for an inbound connection from an application server. +This should only happen after a primary connection to the application +server has been established with a CONNECT. Typically, this is part of +the sequence of actions: + +-bind(): obtain a socket +-getsockname(): get the IP address and port number of the socket +-listen(): ready to accept call from the application server +-use the primary connection to inform the application server of + the IP address and the port number that it should connect to. +-accept(): accept a connection from the application server + +The purpose of SOCKS BIND operation is to support such a sequence +but using a socket on the SOCKS server rather than on the client. + +The client includes in the request packet the IP address of the +application server, the destination port used in the primary connection, +and the userid. + + +----+----+----+----+----+----+----+----+----+----+....+----+ + | VN | CD | DSTPORT | DSTIP | USERID |NULL| + +----+----+----+----+----+----+----+----+----+----+....+----+ + # of bytes: 1 1 2 4 variable 1 + +VN is again 4 for the SOCKS protocol version number. CD must be 2 to +indicate BIND request. + +The SOCKS server uses the client information to decide whether the +request is to be granted. The reply it sends back to the client has +the same format as the reply for CONNECT request, i.e., + + +----+----+----+----+----+----+----+----+ + | VN | CD | DSTPORT | DSTIP | + +----+----+----+----+----+----+----+----+ + # of bytes: 1 1 2 4 + +VN is the version of the reply code and should be 0. CD is the result +code with one of the following values: + + 90: request granted + 91: request rejected or failed + 92: request rejected becasue SOCKS server cannot connect to + identd on the client + 93: request rejected because the client program and identd + report different user-ids. + +However, for a granted request (CD is 90), the DSTPORT and DSTIP fields +are meaningful. In that case, the SOCKS server obtains a socket to wait +for an incoming connection and sends the port number and the IP address +of that socket to the client in DSTPORT and DSTIP, respectively. If the +DSTIP in the reply is 0 (the value of constant INADDR_ANY), then the +client should replace it by the IP address of the SOCKS server to which +the cleint is connected. (This happens if the SOCKS server is not a +multi-homed host.) In the typical scenario, these two numbers are +made available to the application client prgram via the result of the +subsequent getsockname() call. The application protocol must provide a +way for these two pieces of information to be sent from the client to +the application server so that it can initiate the connection, which +connects it to the SOCKS server rather than directly to the application +client as it normally would. + +The SOCKS server sends a second reply packet to the client when the +anticipated connection from the application server is established. +The SOCKS server checks the IP address of the originating host against +the value of DSTIP specified in the client's BIND request. If a mismatch +is found, the CD field in the second reply is set to 91 and the SOCKS +server closes both connections. If the two match, CD in the second +reply is set to 90 and the SOCKS server gets ready to relay the traffic +on its two connections. From then on the client does I/O on its connection +to the SOCKS server as if it were directly connected to the application +server. + + + +For both CONNECT and BIND operations, the server sets a time limit +(2 minutes in current CSTC implementation) for the establishment of its +connection with the application server. If the connection is still not +establiched when the time limit expires, the server closes its connection +to the client and gives up. diff --git a/component/socks4/socks4a.txt b/component/socks4/socks4a.txt new file mode 100644 index 0000000..f944218 --- /dev/null +++ b/component/socks4/socks4a.txt @@ -0,0 +1,39 @@ + SOCKS 4A: A Simple Extension to SOCKS 4 Protocol + + Ying-Da Lee + yingda@best.com or yingda@esd.sgi.com + +Please read SOCKS4.protocol first for an description of the version 4 +protocol. This extension is intended to allow the use of SOCKS on hosts +which are not capable of resolving all domain names. + +In version 4, the client sends the following packet to the SOCKS server +to request a CONNECT or a BIND operation: + + +----+----+----+----+----+----+----+----+----+----+....+----+ + | VN | CD | DSTPORT | DSTIP | USERID |NULL| + +----+----+----+----+----+----+----+----+----+----+....+----+ + # of bytes: 1 1 2 4 variable 1 + +VN is the SOCKS protocol version number and should be 4. CD is the +SOCKS command code and should be 1 for CONNECT or 2 for BIND. NULL +is a byte of all zero bits. + +For version 4A, if the client cannot resolve the destination host's +domain name to find its IP address, it should set the first three bytes +of DSTIP to NULL and the last byte to a non-zero value. (This corresponds +to IP address 0.0.0.x, with x nonzero. As decreed by IANA -- The +Internet Assigned Numbers Authority -- such an address is inadmissible +as a destination IP address and thus should never occur if the client +can resolve the domain name.) Following the NULL byte terminating +USERID, the client must sends the destination domain name and termiantes +it with another NULL byte. This is used for both CONNECT and BIND requests. + +A server using protocol 4A must check the DSTIP in the request packet. +If it represent address 0.0.0.x with nonzero x, the server must read +in the domain name that the client sends in the packet. The server +should resolve the domain name and make connection to the destination +host if it can. + +SOCKSified sockd may pass domain names that it cannot resolve to +the next-hop SOCKS server. diff --git a/engine/parse.go b/engine/parse.go index cef14a6..0ae5999 100644 --- a/engine/parse.go +++ b/engine/parse.go @@ -51,9 +51,11 @@ func parseProxy(s string) (proxy.Proxy, error) { case proto.Reject.String(): return proxy.NewReject(), nil case proto.HTTP.String(): - return proxy.NewHTTP(parseAddrUser(u)) + return proxy.NewHTTP(parseAddrUserPass(u)) + case proto.Socks4.String(): + return proxy.NewSocks4(parseAddrUser(u)) case proto.Socks5.String(): - return proxy.NewSocks5(parseAddrUser(u)) + return proxy.NewSocks5(parseAddrUserPass(u)) case proto.Shadowsocks.String(): return proxy.NewShadowsocks(parseShadowsocks(u)) default: @@ -61,9 +63,13 @@ func parseProxy(s string) (proxy.Proxy, error) { } } -func parseAddrUser(u *url.URL) (address, username, password string) { - address = u.Host - username = u.User.Username() +func parseAddrUser(u *url.URL) (address, username string) { + address, username = u.Host, u.User.Username() + return +} + +func parseAddrUserPass(u *url.URL) (address, username, password string) { + address, username = parseAddrUser(u) password, _ = u.User.Password() return } diff --git a/proxy/proto/proto.go b/proxy/proto/proto.go index dad46e9..5378a5f 100644 --- a/proxy/proto/proto.go +++ b/proxy/proto/proto.go @@ -6,6 +6,7 @@ const ( Direct Proto = iota Reject HTTP + Socks4 Socks5 Shadowsocks ) @@ -20,6 +21,8 @@ func (proto Proto) String() string { return "reject" case HTTP: return "http" + case Socks4: + return "socks4" case Socks5: return "socks5" case Shadowsocks: diff --git a/proxy/socks4.go b/proxy/socks4.go new file mode 100644 index 0000000..0801033 --- /dev/null +++ b/proxy/socks4.go @@ -0,0 +1,43 @@ +package proxy + +import ( + "context" + "fmt" + "net" + + "github.com/xjasonlyu/tun2socks/component/dialer" + "github.com/xjasonlyu/tun2socks/component/socks4" + M "github.com/xjasonlyu/tun2socks/constant" + "github.com/xjasonlyu/tun2socks/proxy/proto" +) + +var _ Proxy = (*Socks4)(nil) + +type Socks4 struct { + *Base + + userID string +} + +func NewSocks4(addr, userID string) (*Socks4, error) { + return &Socks4{ + Base: &Base{ + addr: addr, + proto: proto.Socks4, + }, + userID: userID, + }, nil +} + +func (ss *Socks4) DialContext(ctx context.Context, metadata *M.Metadata) (c net.Conn, err error) { + c, err = dialer.DialContext(ctx, "tcp", ss.Addr()) + if err != nil { + return nil, fmt.Errorf("connect to %s: %w", ss.Addr(), err) + } + setKeepAlive(c) + + defer safeConnClose(c, err) + + err = socks4.ClientHandshake(c, metadata.DestinationAddress(), socks4.CmdConnect, ss.userID) + return +}