Przeglądaj źródła

refine socks tcp handling

Darien Raymond 8 lat temu
rodzic
commit
c4e2d99859
2 zmienionych plików z 302 dodań i 170 usunięć
  1. 279 0
      proxy/socks/protocol.go
  2. 23 170
      proxy/socks/server.go

+ 279 - 0
proxy/socks/protocol.go

@@ -0,0 +1,279 @@
+package socks
+
+import (
+	"io"
+
+	"v2ray.com/core/common/buf"
+	"v2ray.com/core/common/errors"
+	v2net "v2ray.com/core/common/net"
+	"v2ray.com/core/common/protocol"
+	"v2ray.com/core/common/serial"
+	"v2ray.com/core/proxy"
+)
+
+const (
+	socks5Version = 0x05
+	socks4Version = 0x04
+
+	cmdTCPConnect = 0x01
+	cmdTCPBind    = 0x02
+	cmdUDPPort    = 0x03
+
+	socks4RequestGranted  = 90
+	socks4RequestRejected = 91
+
+	authNotRequired      = 0x00
+	authGssAPI           = 0x01
+	authPassword         = 0x02
+	authNoMatchingMethod = 0xFF
+
+	addrTypeIPv4   = 0x01
+	addrTypeIPv6   = 0x04
+	addrTypeDomain = 0x03
+
+	statusSuccess       = 0x00
+	statusCmdNotSupport = 0x07
+)
+
+type ServerSession struct {
+	config *ServerConfig
+	meta   *proxy.InboundHandlerMeta
+}
+
+func (s *ServerSession) Handshake(reader io.Reader, writer io.Writer) (*protocol.RequestHeader, error) {
+	buffer := buf.NewLocal(512)
+	request := new(protocol.RequestHeader)
+
+	if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 2)); err != nil {
+		return nil, errors.Base(err).Message("Socks|Server: Insufficient header.")
+	}
+
+	version := buffer.Byte(0)
+	if version == socks4Version {
+		if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 6)); err != nil {
+			return nil, errors.Base(err).Message("Socks|Server: Insufficient header.")
+		}
+		port := v2net.PortFromBytes(buffer.BytesRange(2, 4))
+		address := v2net.IPAddress(buffer.BytesRange(4, 8))
+		_, err := readUntilNull(reader) // user id
+		if err != nil {
+			return nil, err
+		}
+		if address.IP()[0] == 0x00 {
+			domain, err := readUntilNull(reader)
+			if err != nil {
+				return nil, errors.Base(err).Message("Socks|Server: Failed to read domain for socks 4a.")
+			}
+			address = v2net.DomainAddress(domain)
+		}
+
+		switch buffer.Byte(1) {
+		case cmdTCPConnect:
+			request.Command = protocol.RequestCommandTCP
+			request.Address = address
+			request.Port = port
+			request.Version = socks4Version
+			if err := writeSocks4Response(writer, socks4RequestGranted, address, port); err != nil {
+				return nil, err
+			}
+			return request, nil
+		default:
+			writeSocks4Response(writer, socks4RequestRejected, address, port)
+			return nil, errors.New("Socks|Server: Unsupported command: ", buffer.Byte(1))
+		}
+	}
+
+	if version == socks5Version {
+		nMethod := int(buffer.Byte(1))
+		if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, nMethod)); err != nil {
+			return nil, err
+		}
+
+		var expectedAuth byte = authNotRequired
+		if len(s.config.Accounts) > 0 {
+			expectedAuth = authPassword
+		}
+
+		if !hasAuthMethod(expectedAuth, buffer.BytesRange(2, 2+nMethod)) {
+			writeSocks5AuthenticationResponse(writer, authNoMatchingMethod)
+			return nil, errors.New("Socks|Server: No matching auth method.")
+		}
+
+		if expectedAuth == authPassword {
+			username, password, err := readUsernamePassword(reader)
+			if err != nil {
+				return nil, errors.Base(err).Message("Socks|Server: Failed to read username or password.")
+			}
+			if !s.validate(username, password) {
+				writeSocks5AuthenticationResponse(writer, 0xFF)
+				return nil, errors.Base(err).Message("Socks|Server: Invalid username or password.")
+			}
+		}
+
+		if err := writeSocks5AuthenticationResponse(writer, 0x00); err != nil {
+			return nil, err
+		}
+
+		buffer.Clear()
+		if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 4)); err != nil {
+			return nil, err
+		}
+
+		cmd := buffer.Byte(1)
+		if cmd == cmdTCPBind || (cmd == cmdUDPPort && !s.config.UdpEnabled) {
+			writeSocks5Response(writer, statusCmdNotSupport, v2net.AnyIP, v2net.Port(0))
+			return nil, errors.New("Socks|Server: Unsupported command: ", cmd)
+		}
+
+		switch cmd {
+		case cmdTCPConnect:
+			request.Command = protocol.RequestCommandTCP
+		case cmdUDPPort:
+			request.Command = protocol.RequestCommandUDP
+		}
+
+		addrType := buffer.Byte(3)
+
+		buffer.Clear()
+
+		request.Version = socks5Version
+		switch addrType {
+		case addrTypeIPv4:
+			if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 4)); err != nil {
+				return nil, err
+			}
+			request.Address = v2net.IPAddress(buffer.Bytes())
+		case addrTypeIPv6:
+			if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 16)); err != nil {
+				return nil, err
+			}
+			request.Address = v2net.IPAddress(buffer.Bytes())
+		case addrTypeDomain:
+			if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 1)); err != nil {
+				return nil, err
+			}
+			domainLength := int(buffer.Byte(0))
+			if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, domainLength)); err != nil {
+				return nil, err
+			}
+			request.Address = v2net.DomainAddress(string(buffer.BytesFrom(-domainLength)))
+		default:
+			return nil, errors.New("Socks|Server: Unknown address type: ", addrType)
+		}
+
+		if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 2)); err != nil {
+			return nil, err
+		}
+		request.Port = v2net.PortFromBytes(buffer.BytesFrom(-2))
+
+		responseAddress := v2net.AnyIP
+		responsePort := v2net.Port(1717)
+		if request.Command == protocol.RequestCommandUDP {
+			addr := s.config.Address.AsAddress()
+			if addr == nil {
+				addr = v2net.LocalHostIP
+			}
+			responseAddress = addr
+			responsePort = s.meta.Port
+		}
+		if err := writeSocks5Response(writer, statusSuccess, responseAddress, responsePort); err != nil {
+			return nil, err
+		}
+
+		return request, nil
+	}
+
+	return nil, errors.New("Socks|Server: Unknown Socks version: ", version)
+}
+
+func (s *ServerSession) validate(username, password string) bool {
+	p, found := s.config.Accounts[username]
+	return found && p == password
+}
+
+func readUsernamePassword(reader io.Reader) (string, string, error) {
+	buffer := buf.NewLocal(512)
+	defer buffer.Release()
+
+	if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 2)); err != nil {
+		return "", "", err
+	}
+	nUsername := int(buffer.Byte(1))
+
+	buffer.Clear()
+	if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, nUsername)); err != nil {
+		return "", "", err
+	}
+	username := buffer.String()
+
+	if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, 1)); err != nil {
+		return "", "", err
+	}
+	nPassword := int(buffer.Byte(0))
+	if err := buffer.AppendSupplier(buf.ReadFullFrom(reader, nPassword)); err != nil {
+		return "", "", err
+	}
+	password := buffer.String()
+	return username, password, nil
+}
+
+func readUntilNull(reader io.Reader) (string, error) {
+	var b [256]byte
+	size := 0
+	for {
+		_, err := reader.Read(b[size : size+1])
+		if err != nil {
+			return "", err
+		}
+		if b[size] == 0x00 {
+			return string(b[:size]), nil
+		}
+		size++
+		if size == 256 {
+			return "", errors.New("Socks|Server: Buffer overrun.")
+		}
+	}
+}
+
+func hasAuthMethod(expectedAuth byte, authCandidates []byte) bool {
+	for _, a := range authCandidates {
+		if a == expectedAuth {
+			return true
+		}
+	}
+	return false
+}
+
+func writeSocks5AuthenticationResponse(writer io.Writer, auth byte) error {
+	_, err := writer.Write([]byte{socks5Version, auth})
+	return err
+}
+
+func writeSocks5Response(writer io.Writer, errCode byte, address v2net.Address, port v2net.Port) error {
+	buffer := buf.NewLocal(64)
+	buffer.AppendBytes(socks5Version, errCode, 0x00 /* reserved */)
+	switch address.Family() {
+	case v2net.AddressFamilyIPv4:
+		buffer.AppendBytes(0x01)
+		buffer.Append(address.IP())
+	case v2net.AddressFamilyIPv6:
+		buffer.AppendBytes(0x04)
+		buffer.Append(address.IP())
+	case v2net.AddressFamilyDomain:
+		buffer.AppendBytes(0x03, byte(len(address.Domain())))
+		buffer.AppendSupplier(serial.WriteString(address.Domain()))
+	}
+	buffer.AppendSupplier(serial.WriteUint16(port.Value()))
+
+	_, err := writer.Write(buffer.Bytes())
+	return err
+}
+
+func writeSocks4Response(writer io.Writer, errCode byte, address v2net.Address, port v2net.Port) error {
+	buffer := buf.NewLocal(32)
+	buffer.AppendBytes(0x00, errCode)
+	buffer.AppendSupplier(serial.WriteUint16(port.Value()))
+	buffer.Append(address.IP())
+	_, err := writer.Write(buffer.Bytes())
+	return err
+}

+ 23 - 170
proxy/socks/server.go

@@ -10,23 +10,17 @@ import (
 	"v2ray.com/core/common"
 	"v2ray.com/core/common/buf"
 	"v2ray.com/core/common/bufio"
-	"v2ray.com/core/common/crypto"
 	"v2ray.com/core/common/errors"
 	"v2ray.com/core/common/log"
 	v2net "v2ray.com/core/common/net"
+	proto "v2ray.com/core/common/protocol"
 	"v2ray.com/core/common/serial"
 	"v2ray.com/core/common/signal"
 	"v2ray.com/core/proxy"
-	"v2ray.com/core/proxy/socks/protocol"
 	"v2ray.com/core/transport/internet"
 	"v2ray.com/core/transport/internet/udp"
 )
 
-var (
-	ErrUnsupportedSocksCommand = errors.New("Unsupported socks command.")
-	ErrUnsupportedAuthMethod   = errors.New("Unsupported auth method.")
-)
-
 // Server is a SOCKS 5 proxy server
 type Server struct {
 	tcpMutex         sync.RWMutex
@@ -79,7 +73,7 @@ func (v *Server) Close() {
 	}
 }
 
-// Listen implements InboundHandler.Listen().
+// Start implements InboundHandler.Start().
 func (v *Server) Start() error {
 	if v.accepting {
 		return nil
@@ -111,153 +105,41 @@ func (v *Server) handleConnection(connection internet.Connection) {
 	reader := bufio.NewReader(timedReader)
 	defer reader.Release()
 
-	writer := bufio.NewWriter(connection)
-	defer writer.Release()
-
-	auth, auth4, err := protocol.ReadAuthentication(reader)
-	if err != nil && errors.Cause(err) != protocol.Socks4Downgrade {
-		if errors.Cause(err) != io.EOF {
-			log.Warning("Socks: failed to read authentication: ", err)
-		}
-		return
+	session := &ServerSession{
+		config: v.config,
+		meta:   v.meta,
 	}
 
 	clientAddr := v2net.DestinationFromAddr(connection.RemoteAddr())
-	if err != nil && err == protocol.Socks4Downgrade {
-		v.handleSocks4(clientAddr, reader, writer, auth4)
-	} else {
-		v.handleSocks5(clientAddr, reader, writer, auth)
-	}
-}
-
-func (v *Server) handleSocks5(clientAddr v2net.Destination, reader *bufio.BufferedReader, writer *bufio.BufferedWriter, auth protocol.Socks5AuthenticationRequest) error {
-	expectedAuthMethod := protocol.AuthNotRequired
-	if v.config.AuthType == AuthType_PASSWORD {
-		expectedAuthMethod = protocol.AuthUserPass
-	}
-
-	if !auth.HasAuthMethod(expectedAuthMethod) {
-		authResponse := protocol.NewAuthenticationResponse(protocol.AuthNoMatchingMethod)
-		err := protocol.WriteAuthentication(writer, authResponse)
-		writer.Flush()
-		if err != nil {
-			log.Warning("Socks: failed to write authentication: ", err)
-			return err
-		}
-		log.Warning("Socks: client doesn't support any allowed auth methods.")
-		return ErrUnsupportedAuthMethod
-	}
-
-	authResponse := protocol.NewAuthenticationResponse(expectedAuthMethod)
-	protocol.WriteAuthentication(writer, authResponse)
-	err := writer.Flush()
-	if err != nil {
-		log.Error("Socks: failed to write authentication: ", err)
-		return err
-	}
-	if v.config.AuthType == AuthType_PASSWORD {
-		upRequest, err := protocol.ReadUserPassRequest(reader)
-		if err != nil {
-			log.Warning("Socks: failed to read username and password: ", err)
-			return err
-		}
-		status := byte(0)
-		if !v.config.HasAccount(upRequest.Username(), upRequest.Password()) {
-			status = byte(0xFF)
-		}
-		upResponse := protocol.NewSocks5UserPassResponse(status)
-		err = protocol.WriteUserPassResponse(writer, upResponse)
-		writer.Flush()
-		if err != nil {
-			log.Error("Socks: failed to write user pass response: ", err)
-			return err
-		}
-		if status != byte(0) {
-			log.Warning("Socks: Invalid user account: ", upRequest.AuthDetail())
-			log.Access(clientAddr, "", log.AccessRejected, crypto.ErrAuthenticationFailed)
-			return crypto.ErrAuthenticationFailed
-		}
-	}
 
-	request, err := protocol.ReadRequest(reader)
+	request, err := session.Handshake(reader, connection)
 	if err != nil {
-		log.Warning("Socks: failed to read request: ", err)
-		return err
-	}
-
-	if request.Command == protocol.CmdUdpAssociate && v.config.UdpEnabled {
-		return v.handleUDP(reader, writer)
+		log.Access(clientAddr, "", log.AccessRejected, err)
+		log.Info("Socks|Server: Failed to read request: ", err)
+		return
 	}
 
-	if request.Command == protocol.CmdBind || request.Command == protocol.CmdUdpAssociate {
-		response := protocol.NewSocks5Response()
-		response.Error = protocol.ErrorCommandNotSupported
-		response.Port = v2net.Port(0)
-		response.SetIPv4([]byte{0, 0, 0, 0})
-
-		response.Write(writer)
-		writer.Flush()
-		if err != nil {
-			log.Error("Socks: failed to write response: ", err)
-			return err
+	if request.Command == proto.RequestCommandTCP {
+		dest := request.Destination()
+		session := &proxy.SessionInfo{
+			Source:      clientAddr,
+			Destination: dest,
+			Inbound:     v.meta,
 		}
-		log.Warning("Socks: Unsupported socks command ", request.Command)
-		return ErrUnsupportedSocksCommand
-	}
-
-	response := protocol.NewSocks5Response()
-	response.Error = protocol.ErrorSuccess
+		log.Info("Socks|Server: TCP Connect request to ", dest)
+		log.Access(clientAddr, dest, log.AccessAccepted, "")
 
-	// Some SOCKS software requires a value other than dest. Let's fake one:
-	response.Port = v2net.Port(1717)
-	response.SetIPv4([]byte{0, 0, 0, 0})
-
-	response.Write(writer)
-	if err != nil {
-		log.Error("Socks: failed to write response: ", err)
-		return err
+		v.transport(reader, connection, session)
+		return
 	}
 
-	reader.SetBuffered(false)
-	writer.SetBuffered(false)
-
-	dest := request.Destination()
-	session := &proxy.SessionInfo{
-		Source:      clientAddr,
-		Destination: dest,
-		Inbound:     v.meta,
+	if request.Command == proto.RequestCommandUDP {
+		v.handleUDP()
+		return
 	}
-	log.Info("Socks: TCP Connect request to ", dest)
-	log.Access(clientAddr, dest, log.AccessAccepted, "")
-
-	v.transport(reader, writer, session)
-	return nil
 }
 
-func (v *Server) handleUDP(reader io.Reader, writer *bufio.BufferedWriter) error {
-	response := protocol.NewSocks5Response()
-	response.Error = protocol.ErrorSuccess
-
-	udpAddr := v.udpAddress
-
-	response.Port = udpAddr.Port
-	switch udpAddr.Address.Family() {
-	case v2net.AddressFamilyIPv4:
-		response.SetIPv4(udpAddr.Address.IP())
-	case v2net.AddressFamilyIPv6:
-		response.SetIPv6(udpAddr.Address.IP())
-	case v2net.AddressFamilyDomain:
-		response.SetDomain(udpAddr.Address.Domain())
-	}
-
-	response.Write(writer)
-	err := writer.Flush()
-
-	if err != nil {
-		log.Error("Socks: failed to write response: ", err)
-		return err
-	}
-
+func (v *Server) handleUDP() error {
 	// The TCP connection closes after v method returns. We need to wait until
 	// the client closes it.
 	// TODO: get notified from UDP part
@@ -266,35 +148,6 @@ func (v *Server) handleUDP(reader io.Reader, writer *bufio.BufferedWriter) error
 	return nil
 }
 
-func (v *Server) handleSocks4(clientAddr v2net.Destination, reader *bufio.BufferedReader, writer *bufio.BufferedWriter, auth protocol.Socks4AuthenticationRequest) error {
-	result := protocol.Socks4RequestGranted
-	if auth.Command == protocol.CmdBind {
-		result = protocol.Socks4RequestRejected
-	}
-	socks4Response := protocol.NewSocks4AuthenticationResponse(result, auth.Port, auth.IP[:])
-
-	socks4Response.Write(writer)
-
-	if result == protocol.Socks4RequestRejected {
-		log.Warning("Socks: Unsupported socks 4 command ", auth.Command)
-		log.Access(clientAddr, "", log.AccessRejected, ErrUnsupportedSocksCommand)
-		return ErrUnsupportedSocksCommand
-	}
-
-	reader.SetBuffered(false)
-	writer.SetBuffered(false)
-
-	dest := v2net.TCPDestination(v2net.IPAddress(auth.IP[:]), auth.Port)
-	session := &proxy.SessionInfo{
-		Source:      clientAddr,
-		Destination: dest,
-		Inbound:     v.meta,
-	}
-	log.Access(clientAddr, dest, log.AccessAccepted, "")
-	v.transport(reader, writer, session)
-	return nil
-}
-
 func (v *Server) transport(reader io.Reader, writer io.Writer, session *proxy.SessionInfo) {
 	ray := v.packetDispatcher.DispatchToOutbound(session)
 	input := ray.InboundInput()