From c4e2d998595b50f6259785e67e0e541e5a7abe3a Mon Sep 17 00:00:00 2001 From: Darien Raymond Date: Wed, 4 Jan 2017 00:43:13 +0100 Subject: [PATCH] refine socks tcp handling --- proxy/socks/protocol.go | 279 ++++++++++++++++++++++++++++++++++++++++ proxy/socks/server.go | 209 +++++------------------------- 2 files changed, 310 insertions(+), 178 deletions(-) create mode 100644 proxy/socks/protocol.go diff --git a/proxy/socks/protocol.go b/proxy/socks/protocol.go new file mode 100644 index 000000000..0b334a675 --- /dev/null +++ b/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 +} diff --git a/proxy/socks/server.go b/proxy/socks/server.go index e13b2d8ec..debf8ec0a 100644 --- a/proxy/socks/server.go +++ b/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) + + request, err := session.Handshake(reader, connection) + if err != nil { + log.Access(clientAddr, "", log.AccessRejected, err) + log.Info("Socks|Server: Failed to read request: ", err) + return + } + + if request.Command == proto.RequestCommandTCP { + dest := request.Destination() + session := &proxy.SessionInfo{ + Source: clientAddr, + Destination: dest, + Inbound: v.meta, + } + log.Info("Socks|Server: TCP Connect request to ", dest) + log.Access(clientAddr, dest, log.AccessAccepted, "") + + v.transport(reader, connection, session) + return + } + + if request.Command == proto.RequestCommandUDP { + v.handleUDP() + return } } -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) - 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) - } - - 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 - } - log.Warning("Socks: Unsupported socks command ", request.Command) - return ErrUnsupportedSocksCommand - } - - response := protocol.NewSocks5Response() - response.Error = protocol.ErrorSuccess - - // 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 - } - - reader.SetBuffered(false) - writer.SetBuffered(false) - - dest := request.Destination() - session := &proxy.SessionInfo{ - Source: clientAddr, - Destination: dest, - Inbound: v.meta, - } - 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()