Browse Source

Add SOCKS4 support

V2Ray 10 years ago
parent
commit
8e8dbbcfd0
5 changed files with 158 additions and 67 deletions
  1. 53 2
      io/socks/socks.go
  2. 18 1
      io/socks/socks_test.go
  3. 3 1
      net/address.go
  4. 1 1
      net/freedom/freedom.go
  5. 83 62
      net/socks/socks.go

+ 53 - 2
io/socks/socks.go

@@ -3,19 +3,29 @@ package socks
 
 
 import (
 import (
 	"encoding/binary"
 	"encoding/binary"
+	"errors"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 
 
+	"github.com/v2ray/v2ray-core/log"
 	v2net "github.com/v2ray/v2ray-core/net"
 	v2net "github.com/v2ray/v2ray-core/net"
 )
 )
 
 
 const (
 const (
-	socksVersion = uint8(5)
+	socksVersion  = byte(0x05)
+	socks4Version = byte(0x04)
 
 
 	AuthNotRequired      = byte(0x00)
 	AuthNotRequired      = byte(0x00)
 	AuthGssApi           = byte(0x01)
 	AuthGssApi           = byte(0x01)
 	AuthUserPass         = byte(0x02)
 	AuthUserPass         = byte(0x02)
 	AuthNoMatchingMethod = byte(0xFF)
 	AuthNoMatchingMethod = byte(0xFF)
+
+	Socks4RequestGranted  = byte(90)
+	Socks4RequestRejected = byte(91)
+)
+
+var (
+	ErrorSocksVersion4 = errors.New("Using SOCKS version 4.")
 )
 )
 
 
 // Authentication request header of Socks5 protocol
 // Authentication request header of Socks5 protocol
@@ -25,6 +35,13 @@ type Socks5AuthenticationRequest struct {
 	authMethods [256]byte
 	authMethods [256]byte
 }
 }
 
 
+type Socks4AuthenticationRequest struct {
+	Version byte
+	Command byte
+	Port    uint16
+	IP      [4]byte
+}
+
 func (request *Socks5AuthenticationRequest) HasAuthMethod(method byte) bool {
 func (request *Socks5AuthenticationRequest) HasAuthMethod(method byte) bool {
 	for i := 0; i < int(request.nMethods); i++ {
 	for i := 0; i < int(request.nMethods); i++ {
 		if request.authMethods[i] == method {
 		if request.authMethods[i] == method {
@@ -34,10 +51,11 @@ func (request *Socks5AuthenticationRequest) HasAuthMethod(method byte) bool {
 	return false
 	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)
 	buffer := make([]byte, 256)
 	nBytes, err := reader.Read(buffer)
 	nBytes, err := reader.Read(buffer)
 	if err != nil {
 	if err != nil {
+		log.Error("Failed to read socks authentication: %v", err)
 		return
 		return
 	}
 	}
 	if nBytes < 2 {
 	if nBytes < 2 {
@@ -45,6 +63,15 @@ func ReadAuthentication(reader io.Reader) (auth Socks5AuthenticationRequest, err
 		return
 		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]
 	auth.version = buffer[0]
 	if auth.version != socksVersion {
 	if auth.version != socksVersion {
 		err = fmt.Errorf("Unknown SOCKS version %d", auth.version)
 		err = fmt.Errorf("Unknown SOCKS version %d", auth.version)
@@ -70,6 +97,12 @@ type Socks5AuthenticationResponse struct {
 	authMethod byte
 	authMethod byte
 }
 }
 
 
+type Socks4AuthenticationResponse struct {
+	result byte
+	port   uint16
+	ip     []byte
+}
+
 func NewAuthenticationResponse(authMethod byte) *Socks5AuthenticationResponse {
 func NewAuthenticationResponse(authMethod byte) *Socks5AuthenticationResponse {
 	return &Socks5AuthenticationResponse{
 	return &Socks5AuthenticationResponse{
 		version:    socksVersion,
 		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 {
 func WriteAuthentication(writer io.Writer, r *Socks5AuthenticationResponse) error {
 	_, err := writer.Write([]byte{r.version, r.authMethod})
 	_, err := writer.Write([]byte{r.version, r.authMethod})
 	return err
 	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 {
 type Socks5UserPassRequest struct {
 	version  byte
 	version  byte
 	username string
 	username string

+ 18 - 1
io/socks/socks_test.go

@@ -30,13 +30,30 @@ func TestAuthenticationRequestRead(t *testing.T) {
 		0x01, // nMethods
 		0x01, // nMethods
 		0x02, // methods
 		0x02, // methods
 	}
 	}
-	request, err := ReadAuthentication(bytes.NewReader(rawRequest))
+	request, _, err := ReadAuthentication(bytes.NewReader(rawRequest))
 	assert.Error(err).IsNil()
 	assert.Error(err).IsNil()
 	assert.Byte(request.version).Named("Version").Equals(0x05)
 	assert.Byte(request.version).Named("Version").Equals(0x05)
 	assert.Byte(request.nMethods).Named("#Methods").Equals(0x01)
 	assert.Byte(request.nMethods).Named("#Methods").Equals(0x01)
 	assert.Byte(request.authMethods[0]).Named("Auth Method").Equals(0x02)
 	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) {
 func TestAuthenticationResponseWrite(t *testing.T) {
 	assert := unit.Assert(t)
 	assert := unit.Assert(t)
 
 

+ 3 - 1
net/address.go

@@ -18,10 +18,12 @@ type Address struct {
 }
 }
 
 
 func IPAddress(ip []byte, port uint16) Address {
 func IPAddress(ip []byte, port uint16) Address {
+	ipCopy := make([]byte, 4)
+	copy(ipCopy, ip)
 	// TODO: check IP length
 	// TODO: check IP length
 	return Address{
 	return Address{
 		Type:   AddrTypeIP,
 		Type:   AddrTypeIP,
-		IP:     net.IP(ip),
+		IP:     net.IP(ipCopy),
 		Domain: "",
 		Domain: "",
 		Port:   port,
 		Port:   port,
 	}
 	}

+ 1 - 1
net/freedom/freedom.go

@@ -23,7 +23,7 @@ func (vconn *FreedomConnection) Start(ray core.OutboundRay) error {
 	output := ray.OutboundOutput()
 	output := ray.OutboundOutput()
 	conn, err := net.Dial("tcp", vconn.dest.String())
 	conn, err := net.Dial("tcp", vconn.dest.String())
 	if err != nil {
 	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())
 	log.Debug("Sending outbound tcp: %s", vconn.dest.String())
 
 

+ 83 - 62
net/socks/socks.go

@@ -64,92 +64,113 @@ func (server *SocksServer) HandleConnection(connection net.Conn) error {
 
 
 	reader := bufio.NewReader(connection)
 	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)
 		log.Error("Error on reading authentication: %v", err)
 		return 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)
-		err = socksio.WriteAuthentication(connection, authResponse)
-		if err != nil {
-			log.Error("Error on socksio write authentication: %v", err)
-			return err
+	// TODO refactor this part
+	if err == socksio.ErrorSocksVersion4 {
+		result := socksio.Socks4RequestGranted
+		if auth4.Command == socksio.CmdBind {
+			result = socksio.Socks4RequestRejected
 		}
 		}
-		log.Warning("Client doesn't support allowed any auth methods.")
-		return ErrorAuthenticationFailed
-	}
+		socks4Response := socksio.NewSocks4AuthenticationResponse(result, auth4.Port, auth4.IP[:])
+		socksio.WriteSocks4AuthenticationResponse(connection, socks4Response)
 
 
-	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)
+		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 {
 		if err != nil {
-			log.Error("Failed to read username and password: %v", err)
+			log.Error("Error on socksio write authentication: %v", err)
 			return err
 			return err
 		}
 		}
-		status := byte(0)
-		if !upRequest.IsValid(server.config.Username, server.config.Password) {
-			status = byte(0xFF)
+		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
+			}
 		}
 		}
-		upResponse := socksio.NewSocks5UserPassResponse(status)
-		err = socksio.WriteUserPassResponse(connection, upResponse)
+
+		request, err := socksio.ReadRequest(reader)
 		if err != nil {
 		if err != nil {
-			log.Error("Error on socksio write user pass response: %v", err)
+			log.Error("Error on reading socks request: %v", err)
 			return 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()
 
 
-	response := socksio.NewSocks5Response()
+		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
+		}
 
 
-	if request.Command == socksio.CmdBind || request.Command == socksio.CmdUdpAssociate {
-		response := socksio.NewSocks5Response()
-		response.Error = socksio.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)
 		err = socksio.WriteResponse(connection, response)
 		if err != nil {
 		if err != nil {
 			log.Error("Error on socksio write response: %v", err)
 			log.Error("Error on socksio write response: %v", err)
 			return 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
+		dest = request.Destination()
 	}
 	}
-	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()
 	input := ray.InboundInput()
 	output := ray.InboundOutput()
 	output := ray.InboundOutput()
 	readFinish := make(chan bool)
 	readFinish := make(chan bool)