github.com/igoogolx/clash@v1.19.8/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/igoogolx/clash/component/auth"
    11  
    12  	"github.com/Dreamacro/protobytes"
    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  func ServerHandshake(rw io.ReadWriter, authenticator auth.Authenticator) (addr string, command Command, err error) {
    45  	var req [8]byte
    46  	if _, err = io.ReadFull(rw, req[:]); err != nil {
    47  		return
    48  	}
    49  
    50  	r := protobytes.BytesReader(req[:])
    51  	if r.ReadUint8() != Version {
    52  		err = errVersionMismatched
    53  		return
    54  	}
    55  
    56  	if command = r.ReadUint8(); command != CmdConnect {
    57  		err = errCommandNotSupported
    58  		return
    59  	}
    60  
    61  	var (
    62  		host   string
    63  		port   string
    64  		code   uint8
    65  		userID []byte
    66  	)
    67  	if userID, err = readUntilNull(rw); err != nil {
    68  		return
    69  	}
    70  
    71  	dstPort := r.ReadUint16be()
    72  	dstAddr := r.ReadIPv4()
    73  	if isReservedIP(dstAddr) {
    74  		var target []byte
    75  		if target, err = readUntilNull(rw); err != nil {
    76  			return
    77  		}
    78  		host = string(target)
    79  	}
    80  
    81  	port = strconv.Itoa(int(dstPort))
    82  	if host != "" {
    83  		addr = net.JoinHostPort(host, port)
    84  	} else {
    85  		addr = net.JoinHostPort(dstAddr.String(), port)
    86  	}
    87  
    88  	// SOCKS4 only support USERID auth.
    89  	if authenticator == nil || authenticator.Verify(string(userID), "") {
    90  		code = RequestGranted
    91  	} else {
    92  		code = RequestIdentdMismatched
    93  		err = ErrRequestIdentdMismatched
    94  	}
    95  
    96  	reply := protobytes.BytesWriter(make([]byte, 0, 8))
    97  	reply.PutUint8(0)    // reply code
    98  	reply.PutUint8(code) // result code
    99  	reply.PutUint16be(dstPort)
   100  	reply.PutSlice(dstAddr.AsSlice())
   101  
   102  	_, wErr := rw.Write(reply.Bytes())
   103  	if err == nil {
   104  		err = wErr
   105  	}
   106  	return
   107  }
   108  
   109  func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID string) (err error) {
   110  	host, portStr, err := net.SplitHostPort(addr)
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	port, err := strconv.ParseUint(portStr, 10, 16)
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	ip, err := netip.ParseAddr(host)
   121  	if err != nil { // Host
   122  		ip = netip.AddrFrom4([4]byte{0, 0, 0, 1})
   123  	} else if ip.Is6() { // IPv6
   124  		return errIPv6NotSupported
   125  	}
   126  
   127  	req := protobytes.BytesWriter{}
   128  	req.PutUint8(Version)
   129  	req.PutUint8(command)
   130  	req.PutUint16be(uint16(port))
   131  	req.PutSlice(ip.AsSlice())
   132  	req.PutString(userID)
   133  	req.PutUint8(0) /* NULL */
   134  
   135  	if isReservedIP(ip) /* SOCKS4A */ {
   136  		req.PutString(host)
   137  		req.PutUint8(0) /* NULL */
   138  	}
   139  
   140  	if _, err = rw.Write(req.Bytes()); err != nil {
   141  		return err
   142  	}
   143  
   144  	var resp [8]byte
   145  	if _, err = io.ReadFull(rw, resp[:]); err != nil {
   146  		return err
   147  	}
   148  
   149  	if resp[0] != 0x00 {
   150  		return errVersionMismatched
   151  	}
   152  
   153  	switch resp[1] {
   154  	case RequestGranted:
   155  		return nil
   156  	case RequestRejected:
   157  		return ErrRequestRejected
   158  	case RequestIdentdFailed:
   159  		return ErrRequestIdentdFailed
   160  	case RequestIdentdMismatched:
   161  		return ErrRequestIdentdMismatched
   162  	default:
   163  		return ErrRequestUnknownCode
   164  	}
   165  }
   166  
   167  // For version 4A, if the client cannot resolve the destination host's
   168  // domain name to find its IP address, it should set the first three bytes
   169  // of DSTIP to NULL and the last byte to a non-zero value. (This corresponds
   170  // to IP address 0.0.0.x, with x nonzero. As decreed by IANA  -- The
   171  // Internet Assigned Numbers Authority -- such an address is inadmissible
   172  // as a destination IP address and thus should never occur if the client
   173  // can resolve the domain name.)
   174  func isReservedIP(ip netip.Addr) bool {
   175  	subnet := netip.PrefixFrom(
   176  		netip.AddrFrom4([4]byte{0, 0, 0, 0}),
   177  		24,
   178  	)
   179  
   180  	return !ip.IsUnspecified() && subnet.Contains(ip)
   181  }
   182  
   183  func readUntilNull(r io.Reader) ([]byte, error) {
   184  	buf := protobytes.BytesWriter{}
   185  	var data [1]byte
   186  
   187  	for {
   188  		if _, err := r.Read(data[:]); err != nil {
   189  			return nil, err
   190  		}
   191  		if data[0] == 0 {
   192  			return buf.Bytes(), nil
   193  		}
   194  		buf.PutUint8(data[0])
   195  	}
   196  }