github.com/kelleygo/clashcore@v1.0.2/transport/socks4/socks4.go (about)

     1  package socks4
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"io"
     8  	"net"
     9  	"net/netip"
    10  	"strconv"
    11  
    12  	"github.com/kelleygo/clashcore/component/auth"
    13  )
    14  
    15  const Version = 0x04
    16  
    17  type Command = uint8
    18  
    19  const (
    20  	CmdConnect Command = 0x01
    21  	CmdBind    Command = 0x02
    22  )
    23  
    24  type Code = uint8
    25  
    26  const (
    27  	RequestGranted          Code = 90
    28  	RequestRejected         Code = 91
    29  	RequestIdentdFailed     Code = 92
    30  	RequestIdentdMismatched Code = 93
    31  )
    32  
    33  var (
    34  	errVersionMismatched   = errors.New("version code mismatched")
    35  	errCommandNotSupported = errors.New("command not supported")
    36  	errIPv6NotSupported    = errors.New("IPv6 not supported")
    37  
    38  	ErrRequestRejected         = errors.New("request rejected or failed")
    39  	ErrRequestIdentdFailed     = errors.New("request rejected because SOCKS server cannot connect to identd on the client")
    40  	ErrRequestIdentdMismatched = errors.New("request rejected because the client program and identd report different user-ids")
    41  	ErrRequestUnknownCode      = errors.New("request failed with unknown code")
    42  )
    43  
    44  var subnet = netip.PrefixFrom(netip.IPv4Unspecified(), 24)
    45  
    46  func ServerHandshake(rw io.ReadWriter, authenticator auth.Authenticator) (addr string, command Command, err error) {
    47  	var req [8]byte
    48  	if _, err = io.ReadFull(rw, req[:]); err != nil {
    49  		return
    50  	}
    51  
    52  	if req[0] != Version {
    53  		err = errVersionMismatched
    54  		return
    55  	}
    56  
    57  	if command = req[1]; command != CmdConnect {
    58  		err = errCommandNotSupported
    59  		return
    60  	}
    61  
    62  	var (
    63  		dstIP   = netip.AddrFrom4(*(*[4]byte)(req[4:8])) // [4]byte
    64  		dstPort = req[2:4]                               // [2]byte
    65  	)
    66  
    67  	var (
    68  		host   string
    69  		port   string
    70  		code   uint8
    71  		userID []byte
    72  	)
    73  	if userID, err = readUntilNull(rw); err != nil {
    74  		return
    75  	}
    76  
    77  	if isReservedIP(dstIP) {
    78  		var target []byte
    79  		if target, err = readUntilNull(rw); err != nil {
    80  			return
    81  		}
    82  		host = string(target)
    83  	}
    84  
    85  	port = strconv.Itoa(int(binary.BigEndian.Uint16(dstPort)))
    86  	if host != "" {
    87  		addr = net.JoinHostPort(host, port)
    88  	} else {
    89  		addr = net.JoinHostPort(dstIP.String(), port)
    90  	}
    91  
    92  	// SOCKS4 only support USERID auth.
    93  	if authenticator == nil || authenticator.Verify(string(userID), "") {
    94  		code = RequestGranted
    95  	} else {
    96  		code = RequestIdentdMismatched
    97  		err = ErrRequestIdentdMismatched
    98  	}
    99  
   100  	var reply [8]byte
   101  	reply[0] = 0x00 // reply code
   102  	reply[1] = code // result code
   103  	copy(reply[4:8], dstIP.AsSlice())
   104  	copy(reply[2:4], dstPort)
   105  
   106  	_, wErr := rw.Write(reply[:])
   107  	if err == nil {
   108  		err = wErr
   109  	}
   110  	return
   111  }
   112  
   113  func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID string) (err error) {
   114  	host, portStr, err := net.SplitHostPort(addr)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	port, err := strconv.ParseUint(portStr, 10, 16)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	dstIP, err := netip.ParseAddr(host)
   125  	if err != nil /* HOST */ {
   126  		dstIP = netip.AddrFrom4([4]byte{0, 0, 0, 1})
   127  	} else if dstIP.Is6() /* IPv6 */ {
   128  		return errIPv6NotSupported
   129  	}
   130  
   131  	req := &bytes.Buffer{}
   132  	req.WriteByte(Version)
   133  	req.WriteByte(command)
   134  	_ = binary.Write(req, binary.BigEndian, uint16(port))
   135  	req.Write(dstIP.AsSlice())
   136  	req.WriteString(userID)
   137  	req.WriteByte(0) /* NULL */
   138  
   139  	if isReservedIP(dstIP) /* SOCKS4A */ {
   140  		req.WriteString(host)
   141  		req.WriteByte(0) /* NULL */
   142  	}
   143  
   144  	if _, err = rw.Write(req.Bytes()); err != nil {
   145  		return err
   146  	}
   147  
   148  	var resp [8]byte
   149  	if _, err = io.ReadFull(rw, resp[:]); err != nil {
   150  		return err
   151  	}
   152  
   153  	if resp[0] != 0x00 {
   154  		return errVersionMismatched
   155  	}
   156  
   157  	switch resp[1] {
   158  	case RequestGranted:
   159  		return nil
   160  	case RequestRejected:
   161  		return ErrRequestRejected
   162  	case RequestIdentdFailed:
   163  		return ErrRequestIdentdFailed
   164  	case RequestIdentdMismatched:
   165  		return ErrRequestIdentdMismatched
   166  	default:
   167  		return ErrRequestUnknownCode
   168  	}
   169  }
   170  
   171  // For version 4A, if the client cannot resolve the destination host's
   172  // domain name to find its IP address, it should set the first three bytes
   173  // of DSTIP to NULL and the last byte to a non-zero value. (This corresponds
   174  // to IP address 0.0.0.x, with x nonzero. As decreed by IANA  -- The
   175  // Internet Assigned Numbers Authority -- such an address is inadmissible
   176  // as a destination IP address and thus should never occur if the client
   177  // can resolve the domain name.)
   178  func isReservedIP(ip netip.Addr) bool {
   179  	return !ip.IsUnspecified() && subnet.Contains(ip)
   180  }
   181  
   182  func readUntilNull(r io.Reader) ([]byte, error) {
   183  	buf := &bytes.Buffer{}
   184  	var data [1]byte
   185  
   186  	for {
   187  		if _, err := r.Read(data[:]); err != nil {
   188  			return nil, err
   189  		}
   190  		if data[0] == 0 {
   191  			return buf.Bytes(), nil
   192  		}
   193  		buf.WriteByte(data[0])
   194  	}
   195  }