github.com/castai/kvisor@v1.7.1-0.20240516114728-b3572a2607b5/pkg/net/packet/socks5.go (about)

     1  package packet
     2  
     3  import (
     4  	"encoding/binary"
     5  	"errors"
     6  )
     7  
     8  type SOCKS5MessagesType uint8
     9  
    10  const (
    11  	SOCKS5MessageUnknown SOCKS5MessagesType = iota
    12  	SOCKS5MessageInitialClientRequest
    13  	SOCKS5MessageInitialServerResponse
    14  
    15  	// they request and reply message look identical on the wire
    16  	// so hence we need to share a constant
    17  	SOCKS5MessageRequestOrReply
    18  )
    19  
    20  var socks5MessageTypeNames = map[SOCKS5MessagesType]string{
    21  	SOCKS5MessageInitialClientRequest:  "InitialClientRequest",
    22  	SOCKS5MessageInitialServerResponse: "InitialServerResponse",
    23  	SOCKS5MessageRequestOrReply:        "RequestOrReply",
    24  }
    25  
    26  func (m SOCKS5MessagesType) String() string {
    27  	if name, found := socks5MessageTypeNames[m]; found {
    28  		return name
    29  	}
    30  
    31  	return "Unknown"
    32  }
    33  
    34  type SOCKS5AddressType uint8
    35  
    36  const (
    37  	SOCKS5AddressTypeIPv4       SOCKS5AddressType = 0x01
    38  	SOCKS5AddressTypeDomainName SOCKS5AddressType = 0x03
    39  	SOCKS5AddressTypeIPv6       SOCKS5AddressType = 0x04
    40  )
    41  
    42  var socks5SAddressTypeNames = map[SOCKS5AddressType]string{
    43  	SOCKS5AddressTypeIPv4:       "IPv4",
    44  	SOCKS5AddressTypeDomainName: "DomainName",
    45  	SOCKS5AddressTypeIPv6:       "IPv6",
    46  }
    47  
    48  func (a SOCKS5AddressType) String() string {
    49  	if name, found := socks5SAddressTypeNames[a]; found {
    50  		return name
    51  	}
    52  
    53  	return "Unknown"
    54  }
    55  
    56  type SOCKS5Message interface {
    57  	internal()
    58  	MessageType() SOCKS5MessagesType
    59  }
    60  
    61  type SOCKS5InitialClientRequest struct{}
    62  
    63  func (SOCKS5InitialClientRequest) internal() {}
    64  func (SOCKS5InitialClientRequest) MessageType() SOCKS5MessagesType {
    65  	return SOCKS5MessageInitialClientRequest
    66  }
    67  
    68  type SOCKS5InitialServerResponse struct{}
    69  
    70  func (SOCKS5InitialServerResponse) internal() {}
    71  func (SOCKS5InitialServerResponse) MessageType() SOCKS5MessagesType {
    72  	return SOCKS5MessageInitialServerResponse
    73  }
    74  
    75  type SOCKS5RequestOrReply struct {
    76  	CmdOrReply  uint8
    77  	AddressType SOCKS5AddressType
    78  	Address     []byte // can either be a domain name, ipv4 or ipv6. check the address type to know how to parse it
    79  	Port        uint16
    80  }
    81  
    82  func (SOCKS5RequestOrReply) internal() {}
    83  func (SOCKS5RequestOrReply) MessageType() SOCKS5MessagesType {
    84  	return SOCKS5MessageRequestOrReply
    85  }
    86  
    87  var (
    88  	ErrSOCKS5InvalidMessage     = errors.New("invalid socks5 message")
    89  	ErrSOCKS5InvalidVersion     = errors.New("invalid version set in socks5 payload")
    90  	ErrSOCKS5InvalidAddressType = errors.New("invalid address type")
    91  )
    92  
    93  // ParseSOCKS5 tries to parses the given data based on https://datatracker.ietf.org/doc/html/rfc1928
    94  func ParseSOCKS5(data []byte) (SOCKS5Message, error) {
    95  	if len(data) < 2 {
    96  		return nil, ErrSOCKS5InvalidMessage
    97  	}
    98  
    99  	// socks5 messages always start with the number 5
   100  	if data[0] != 0x05 {
   101  		return nil, ErrSOCKS5InvalidVersion
   102  	}
   103  
   104  	// only the initial server response matches a len of 2
   105  	if len(data) == 2 {
   106  		return SOCKS5InitialServerResponse{}, nil
   107  	}
   108  
   109  	// the client sends a message with potential methods it can connect with. the second field
   110  	// gives the number of methods. if the message is the same length as the number of message + 2
   111  	// (since the first two bytes contain other info), we should have an initial client request.
   112  	// this could in theory clash with client request/server response , but is rather unlikely
   113  	if int(data[1]+2) == len(data) {
   114  		return SOCKS5InitialClientRequest{}, nil
   115  	}
   116  
   117  	// the request/response needs more than 4 bytes, but we use at least 4 for parsing
   118  	if len(data) < 4 {
   119  		return nil, ErrSOCKS5InvalidMessage
   120  	}
   121  
   122  	// the third byte is reserved to be 0x00 in both request and response
   123  	if data[2] != 0x00 {
   124  		return nil, ErrSOCKS5InvalidMessage
   125  	}
   126  	addressType := SOCKS5AddressType(data[3])
   127  
   128  	switch addressType {
   129  	case SOCKS5AddressTypeIPv4:
   130  		// 4 bytes protocol + 4 bytes ipv4 + 2 bytes port = 10 bytes
   131  		if len(data) != 10 {
   132  			return nil, ErrSOCKS5InvalidMessage
   133  		}
   134  
   135  		return SOCKS5RequestOrReply{
   136  			CmdOrReply:  data[1],
   137  			AddressType: addressType,
   138  			Address:     data[4:8],
   139  			Port:        binary.BigEndian.Uint16(data[8:]),
   140  		}, nil
   141  
   142  	case SOCKS5AddressTypeIPv6:
   143  		// 4 bytes protocol + 16 bytes ipv6 + 2 bytes port = 22 bytes
   144  		if len(data) != 22 {
   145  			return nil, ErrSOCKS5InvalidMessage
   146  		}
   147  
   148  		return SOCKS5RequestOrReply{
   149  			CmdOrReply:  data[1],
   150  			AddressType: addressType,
   151  			Address:     data[4:20],
   152  			Port:        binary.BigEndian.Uint16(data[20:]),
   153  		}, nil
   154  	case SOCKS5AddressTypeDomainName:
   155  		// first byte in addr contains domain name length
   156  		if len(data) < 5 {
   157  			return nil, ErrSOCKS5InvalidMessage
   158  		}
   159  		numChars := int(data[4])
   160  
   161  		// 4 bytes protocol + 1 byte len + num chars + 2 bytes port
   162  		if len(data) != 4+1+numChars+2 {
   163  			return nil, ErrSOCKS5InvalidMessage
   164  		}
   165  
   166  		return SOCKS5RequestOrReply{
   167  			CmdOrReply:  data[1],
   168  			AddressType: addressType,
   169  			Address:     data[5 : 5+numChars],
   170  			Port:        binary.BigEndian.Uint16(data[5+numChars:]),
   171  		}, nil
   172  	default:
   173  		return nil, ErrSOCKS5InvalidAddressType
   174  	}
   175  }