github.com/metacubex/mihomo@v1.18.5/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/metacubex/mihomo/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, user string, 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  	user = string(userID)
    77  
    78  	if isReservedIP(dstIP) {
    79  		var target []byte
    80  		if target, err = readUntilNull(rw); err != nil {
    81  			return
    82  		}
    83  		host = string(target)
    84  	}
    85  
    86  	port = strconv.Itoa(int(binary.BigEndian.Uint16(dstPort)))
    87  	if host != "" {
    88  		addr = net.JoinHostPort(host, port)
    89  	} else {
    90  		addr = net.JoinHostPort(dstIP.String(), port)
    91  	}
    92  
    93  	// SOCKS4 only support USERID auth.
    94  	if authenticator == nil || authenticator.Verify(user, "") {
    95  		code = RequestGranted
    96  	} else {
    97  		code = RequestIdentdMismatched
    98  		err = ErrRequestIdentdMismatched
    99  	}
   100  
   101  	var reply [8]byte
   102  	reply[0] = 0x00 // reply code
   103  	reply[1] = code // result code
   104  	copy(reply[4:8], dstIP.AsSlice())
   105  	copy(reply[2:4], dstPort)
   106  
   107  	_, wErr := rw.Write(reply[:])
   108  	if err == nil {
   109  		err = wErr
   110  	}
   111  	return
   112  }
   113  
   114  func ClientHandshake(rw io.ReadWriter, addr string, command Command, userID string) (err error) {
   115  	host, portStr, err := net.SplitHostPort(addr)
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	port, err := strconv.ParseUint(portStr, 10, 16)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	dstIP, err := netip.ParseAddr(host)
   126  	if err != nil /* HOST */ {
   127  		dstIP = netip.AddrFrom4([4]byte{0, 0, 0, 1})
   128  	} else if dstIP.Is6() /* IPv6 */ {
   129  		return errIPv6NotSupported
   130  	}
   131  
   132  	req := &bytes.Buffer{}
   133  	req.WriteByte(Version)
   134  	req.WriteByte(command)
   135  	_ = binary.Write(req, binary.BigEndian, uint16(port))
   136  	req.Write(dstIP.AsSlice())
   137  	req.WriteString(userID)
   138  	req.WriteByte(0) /* NULL */
   139  
   140  	if isReservedIP(dstIP) /* SOCKS4A */ {
   141  		req.WriteString(host)
   142  		req.WriteByte(0) /* NULL */
   143  	}
   144  
   145  	if _, err = rw.Write(req.Bytes()); err != nil {
   146  		return err
   147  	}
   148  
   149  	var resp [8]byte
   150  	if _, err = io.ReadFull(rw, resp[:]); err != nil {
   151  		return err
   152  	}
   153  
   154  	if resp[0] != 0x00 {
   155  		return errVersionMismatched
   156  	}
   157  
   158  	switch resp[1] {
   159  	case RequestGranted:
   160  		return nil
   161  	case RequestRejected:
   162  		return ErrRequestRejected
   163  	case RequestIdentdFailed:
   164  		return ErrRequestIdentdFailed
   165  	case RequestIdentdMismatched:
   166  		return ErrRequestIdentdMismatched
   167  	default:
   168  		return ErrRequestUnknownCode
   169  	}
   170  }
   171  
   172  // For version 4A, if the client cannot resolve the destination host's
   173  // domain name to find its IP address, it should set the first three bytes
   174  // of DSTIP to NULL and the last byte to a non-zero value. (This corresponds
   175  // to IP address 0.0.0.x, with x nonzero. As decreed by IANA  -- The
   176  // Internet Assigned Numbers Authority -- such an address is inadmissible
   177  // as a destination IP address and thus should never occur if the client
   178  // can resolve the domain name.)
   179  func isReservedIP(ip netip.Addr) bool {
   180  	return !ip.IsUnspecified() && subnet.Contains(ip)
   181  }
   182  
   183  func readUntilNull(r io.Reader) ([]byte, error) {
   184  	buf := &bytes.Buffer{}
   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.WriteByte(data[0])
   195  	}
   196  }