From 8e8dbbcfd0b2c260567099854ad3eac7e61356fa Mon Sep 17 00:00:00 2001 From: V2Ray Date: Thu, 17 Sep 2015 17:37:04 +0200 Subject: [PATCH] Add SOCKS4 support --- io/socks/socks.go | 55 ++++++++++++++- io/socks/socks_test.go | 19 +++++- net/address.go | 4 +- net/freedom/freedom.go | 2 +- net/socks/socks.go | 147 +++++++++++++++++++++++------------------ 5 files changed, 159 insertions(+), 68 deletions(-) diff --git a/io/socks/socks.go b/io/socks/socks.go index 6287961f1..ea770b093 100644 --- a/io/socks/socks.go +++ b/io/socks/socks.go @@ -3,19 +3,29 @@ package socks import ( "encoding/binary" + "errors" "fmt" "io" + "github.com/v2ray/v2ray-core/log" v2net "github.com/v2ray/v2ray-core/net" ) const ( - socksVersion = uint8(5) + socksVersion = byte(0x05) + socks4Version = byte(0x04) AuthNotRequired = byte(0x00) AuthGssApi = byte(0x01) AuthUserPass = byte(0x02) AuthNoMatchingMethod = byte(0xFF) + + Socks4RequestGranted = byte(90) + Socks4RequestRejected = byte(91) +) + +var ( + ErrorSocksVersion4 = errors.New("Using SOCKS version 4.") ) // Authentication request header of Socks5 protocol @@ -25,6 +35,13 @@ type Socks5AuthenticationRequest struct { authMethods [256]byte } +type Socks4AuthenticationRequest struct { + Version byte + Command byte + Port uint16 + IP [4]byte +} + func (request *Socks5AuthenticationRequest) HasAuthMethod(method byte) bool { for i := 0; i < int(request.nMethods); i++ { if request.authMethods[i] == method { @@ -34,10 +51,11 @@ func (request *Socks5AuthenticationRequest) HasAuthMethod(method byte) bool { return false } -func ReadAuthentication(reader io.Reader) (auth Socks5AuthenticationRequest, err error) { +func ReadAuthentication(reader io.Reader) (auth Socks5AuthenticationRequest, auth4 Socks4AuthenticationRequest, err error) { buffer := make([]byte, 256) nBytes, err := reader.Read(buffer) if err != nil { + log.Error("Failed to read socks authentication: %v", err) return } if nBytes < 2 { @@ -45,6 +63,15 @@ func ReadAuthentication(reader io.Reader) (auth Socks5AuthenticationRequest, err return } + if buffer[0] == socks4Version { + auth4.Version = buffer[0] + auth4.Command = buffer[1] + auth4.Port = binary.BigEndian.Uint16(buffer[2:4]) + copy(auth4.IP[:], buffer[4:8]) + err = ErrorSocksVersion4 + return + } + auth.version = buffer[0] if auth.version != socksVersion { err = fmt.Errorf("Unknown SOCKS version %d", auth.version) @@ -70,6 +97,12 @@ type Socks5AuthenticationResponse struct { authMethod byte } +type Socks4AuthenticationResponse struct { + result byte + port uint16 + ip []byte +} + func NewAuthenticationResponse(authMethod byte) *Socks5AuthenticationResponse { return &Socks5AuthenticationResponse{ version: socksVersion, @@ -77,11 +110,29 @@ func NewAuthenticationResponse(authMethod byte) *Socks5AuthenticationResponse { } } +func NewSocks4AuthenticationResponse(result byte, port uint16, ip []byte) *Socks4AuthenticationResponse { + return &Socks4AuthenticationResponse{ + result: result, + port: port, + ip: ip, + } +} + func WriteAuthentication(writer io.Writer, r *Socks5AuthenticationResponse) error { _, err := writer.Write([]byte{r.version, r.authMethod}) return err } +func WriteSocks4AuthenticationResponse(writer io.Writer, r *Socks4AuthenticationResponse) error { + buffer := make([]byte, 8) + // buffer[0] is always 0 + buffer[1] = r.result + binary.BigEndian.PutUint16(buffer[2:4], r.port) + copy(buffer[4:], r.ip) + _, err := writer.Write(buffer) + return err +} + type Socks5UserPassRequest struct { version byte username string diff --git a/io/socks/socks_test.go b/io/socks/socks_test.go index 623671e23..357a8aecc 100644 --- a/io/socks/socks_test.go +++ b/io/socks/socks_test.go @@ -30,13 +30,30 @@ func TestAuthenticationRequestRead(t *testing.T) { 0x01, // nMethods 0x02, // methods } - request, err := ReadAuthentication(bytes.NewReader(rawRequest)) + request, _, err := ReadAuthentication(bytes.NewReader(rawRequest)) assert.Error(err).IsNil() assert.Byte(request.version).Named("Version").Equals(0x05) assert.Byte(request.nMethods).Named("#Methods").Equals(0x01) assert.Byte(request.authMethods[0]).Named("Auth Method").Equals(0x02) } +func TestAuthentication4RequestRead(t *testing.T) { + assert := unit.Assert(t) + + rawRequest := []byte{ + 0x04, // version + 0x01, // command + 0x00, 0x35, + 0x72, 0x72, 0x72, 0x72, + } + _, request4, err := ReadAuthentication(bytes.NewReader(rawRequest)) + assert.Error(err).Equals(ErrorSocksVersion4) + assert.Byte(request4.Version).Named("Version").Equals(0x04) + assert.Byte(request4.Command).Named("Command").Equals(0x01) + assert.Uint16(request4.Port).Named("Port").Equals(53) + assert.Bytes(request4.IP[:]).Named("IP").Equals([]byte{0x72, 0x72, 0x72, 0x72}) +} + func TestAuthenticationResponseWrite(t *testing.T) { assert := unit.Assert(t) diff --git a/net/address.go b/net/address.go index a5518c9dc..be605ba7f 100644 --- a/net/address.go +++ b/net/address.go @@ -18,10 +18,12 @@ type Address struct { } func IPAddress(ip []byte, port uint16) Address { + ipCopy := make([]byte, 4) + copy(ipCopy, ip) // TODO: check IP length return Address{ Type: AddrTypeIP, - IP: net.IP(ip), + IP: net.IP(ipCopy), Domain: "", Port: port, } diff --git a/net/freedom/freedom.go b/net/freedom/freedom.go index f70860d78..f721e6c4f 100644 --- a/net/freedom/freedom.go +++ b/net/freedom/freedom.go @@ -23,7 +23,7 @@ func (vconn *FreedomConnection) Start(ray core.OutboundRay) error { output := ray.OutboundOutput() conn, err := net.Dial("tcp", vconn.dest.String()) if err != nil { - return log.Error("Failed to open tcp: %s", vconn.dest.String()) + return log.Error("Failed to open tcp: %s : %v", vconn.dest.String(), err) } log.Debug("Sending outbound tcp: %s", vconn.dest.String()) diff --git a/net/socks/socks.go b/net/socks/socks.go index c82ba295d..46cc2f802 100644 --- a/net/socks/socks.go +++ b/net/socks/socks.go @@ -64,92 +64,113 @@ func (server *SocksServer) HandleConnection(connection net.Conn) error { reader := bufio.NewReader(connection) - auth, err := socksio.ReadAuthentication(reader) - if err != nil { + auth, auth4, err := socksio.ReadAuthentication(reader) + if err != nil && err != socksio.ErrorSocksVersion4 { log.Error("Error on reading authentication: %v", err) return err } - expectedAuthMethod := socksio.AuthNotRequired - if server.config.AuthMethod == JsonAuthMethodUserPass { - expectedAuthMethod = socksio.AuthUserPass - } + var dest v2net.Address - if !auth.HasAuthMethod(expectedAuthMethod) { - authResponse := socksio.NewAuthenticationResponse(socksio.AuthNoMatchingMethod) + // TODO refactor this part + if err == socksio.ErrorSocksVersion4 { + result := socksio.Socks4RequestGranted + if auth4.Command == socksio.CmdBind { + result = socksio.Socks4RequestRejected + } + socks4Response := socksio.NewSocks4AuthenticationResponse(result, auth4.Port, auth4.IP[:]) + socksio.WriteSocks4AuthenticationResponse(connection, socks4Response) + + if result == socksio.Socks4RequestRejected { + return ErrorCommandNotSupported + } + + dest = v2net.IPAddress(auth4.IP[:], auth4.Port) + } else { + expectedAuthMethod := socksio.AuthNotRequired + if server.config.AuthMethod == JsonAuthMethodUserPass { + expectedAuthMethod = socksio.AuthUserPass + } + + if !auth.HasAuthMethod(expectedAuthMethod) { + authResponse := socksio.NewAuthenticationResponse(socksio.AuthNoMatchingMethod) + err = socksio.WriteAuthentication(connection, authResponse) + if err != nil { + log.Error("Error on socksio write authentication: %v", err) + return err + } + log.Warning("Client doesn't support allowed any auth methods.") + return ErrorAuthenticationFailed + } + + authResponse := socksio.NewAuthenticationResponse(expectedAuthMethod) err = socksio.WriteAuthentication(connection, authResponse) if err != nil { log.Error("Error on socksio write authentication: %v", err) return err } - log.Warning("Client doesn't support allowed any auth methods.") - return ErrorAuthenticationFailed - } + if server.config.AuthMethod == JsonAuthMethodUserPass { + upRequest, err := socksio.ReadUserPassRequest(reader) + if err != nil { + log.Error("Failed to read username and password: %v", err) + return err + } + status := byte(0) + if !upRequest.IsValid(server.config.Username, server.config.Password) { + status = byte(0xFF) + } + upResponse := socksio.NewSocks5UserPassResponse(status) + err = socksio.WriteUserPassResponse(connection, upResponse) + if err != nil { + log.Error("Error on socksio write user pass response: %v", err) + return err + } + if status != byte(0) { + return ErrorInvalidUser + } + } - authResponse := socksio.NewAuthenticationResponse(expectedAuthMethod) - err = socksio.WriteAuthentication(connection, authResponse) - if err != nil { - log.Error("Error on socksio write authentication: %v", err) - return err - } - if server.config.AuthMethod == JsonAuthMethodUserPass { - upRequest, err := socksio.ReadUserPassRequest(reader) + request, err := socksio.ReadRequest(reader) if err != nil { - log.Error("Failed to read username and password: %v", err) + log.Error("Error on reading socks request: %v", err) return err } - status := byte(0) - if !upRequest.IsValid(server.config.Username, server.config.Password) { - status = byte(0xFF) - } - upResponse := socksio.NewSocks5UserPassResponse(status) - err = socksio.WriteUserPassResponse(connection, upResponse) - if err != nil { - log.Error("Error on socksio write user pass response: %v", err) - return err - } - if status != byte(0) { - return ErrorInvalidUser - } - } - request, err := socksio.ReadRequest(reader) - if err != nil { - log.Error("Error on reading socks request: %v", err) - return err - } - - response := socksio.NewSocks5Response() - - if request.Command == socksio.CmdBind || request.Command == socksio.CmdUdpAssociate { response := socksio.NewSocks5Response() - response.Error = socksio.ErrorCommandNotSupported + + if request.Command == socksio.CmdBind || request.Command == socksio.CmdUdpAssociate { + response := socksio.NewSocks5Response() + response.Error = socksio.ErrorCommandNotSupported + err = socksio.WriteResponse(connection, response) + if err != nil { + log.Error("Error on socksio write response: %v", err) + return err + } + log.Warning("Unsupported socks command %d", request.Command) + return ErrorCommandNotSupported + } + + response.Error = socksio.ErrorSuccess + response.Port = request.Port + response.AddrType = request.AddrType + switch response.AddrType { + case socksio.AddrTypeIPv4: + copy(response.IPv4[:], request.IPv4[:]) + case socksio.AddrTypeIPv6: + copy(response.IPv6[:], request.IPv6[:]) + case socksio.AddrTypeDomain: + response.Domain = request.Domain + } err = socksio.WriteResponse(connection, response) if err != nil { log.Error("Error on socksio write response: %v", err) return err } - log.Warning("Unsupported socks command %d", request.Command) - return ErrorCommandNotSupported + + dest = request.Destination() } - response.Error = socksio.ErrorSuccess - response.Port = request.Port - response.AddrType = request.AddrType - switch response.AddrType { - case socksio.AddrTypeIPv4: - copy(response.IPv4[:], request.IPv4[:]) - case socksio.AddrTypeIPv6: - copy(response.IPv6[:], request.IPv6[:]) - case socksio.AddrTypeDomain: - response.Domain = request.Domain - } - err = socksio.WriteResponse(connection, response) - if err != nil { - log.Error("Error on socksio write response: %v", err) - return err - } - ray := server.vPoint.NewInboundConnectionAccepted(request.Destination()) + ray := server.vPoint.NewInboundConnectionAccepted(dest) input := ray.InboundInput() output := ray.InboundOutput() readFinish := make(chan bool)