github.com/yaling888/clash@v1.53.0/transport/socks4/socks4.go (about)

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