github.com/chwjbn/xclash@v0.2.0/transport/socks4/socks4.go (about)

     1  package socks4
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"errors"
     7  	"io"
     8  	"net"
     9  	"strconv"
    10  
    11  	"github.com/chwjbn/xclash/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  func ServerHandshake(rw io.ReadWriter, authenticator auth.Authenticator) (addr string, command Command, err error) {
    44  	var req [8]byte
    45  	if _, err = io.ReadFull(rw, req[:]); err != nil {
    46  		return
    47  	}
    48  
    49  	if req[0] != Version {
    50  		err = errVersionMismatched
    51  		return
    52  	}
    53  
    54  	if command = req[1]; command != CmdConnect {
    55  		err = errCommandNotSupported
    56  		return
    57  	}
    58  
    59  	var (
    60  		dstIP   = req[4:8] // [4]byte
    61  		dstPort = req[2:4] // [2]byte
    62  	)
    63  
    64  	var (
    65  		host   string
    66  		port   string
    67  		code   uint8
    68  		userID []byte
    69  	)
    70  	if userID, err = readUntilNull(rw); err != nil {
    71  		return
    72  	}
    73  
    74  	if isReservedIP(dstIP) {
    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(binary.BigEndian.Uint16(dstPort)))
    83  	if host != "" {
    84  		addr = net.JoinHostPort(host, port)
    85  	} else {
    86  		addr = net.JoinHostPort(net.IP(dstIP).String(), port)
    87  	}
    88  
    89  	// SOCKS4 only support USERID auth.
    90  	if authenticator == nil || authenticator.Verify(string(userID), "") {
    91  		code = RequestGranted
    92  	} else {
    93  		code = RequestIdentdMismatched
    94  		err = ErrRequestIdentdMismatched
    95  	}
    96  
    97  	var reply [8]byte
    98  	reply[0] = 0x00 // reply code
    99  	reply[1] = code // result code
   100  	copy(reply[4:8], dstIP)
   101  	copy(reply[2:4], dstPort)
   102  
   103  	_, wErr := rw.Write(reply[:])
   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  	ip := net.ParseIP(host)
   122  	if ip == nil /* HOST */ {
   123  		ip = net.IPv4(0, 0, 0, 1).To4()
   124  	} else if ip.To4() == nil /* IPv6 */ {
   125  		return errIPv6NotSupported
   126  	}
   127  
   128  	dstIP := ip.To4()
   129  
   130  	req := &bytes.Buffer{}
   131  	req.WriteByte(Version)
   132  	req.WriteByte(command)
   133  	binary.Write(req, binary.BigEndian, uint16(port))
   134  	req.Write(dstIP)
   135  	req.WriteString(userID)
   136  	req.WriteByte(0) /* NULL */
   137  
   138  	if isReservedIP(dstIP) /* SOCKS4A */ {
   139  		req.WriteString(host)
   140  		req.WriteByte(0) /* NULL */
   141  	}
   142  
   143  	if _, err = rw.Write(req.Bytes()); err != nil {
   144  		return err
   145  	}
   146  
   147  	var resp [8]byte
   148  	if _, err = io.ReadFull(rw, resp[:]); err != nil {
   149  		return err
   150  	}
   151  
   152  	if resp[0] != 0x00 {
   153  		return errVersionMismatched
   154  	}
   155  
   156  	switch resp[1] {
   157  	case RequestGranted:
   158  		return nil
   159  	case RequestRejected:
   160  		return ErrRequestRejected
   161  	case RequestIdentdFailed:
   162  		return ErrRequestIdentdFailed
   163  	case RequestIdentdMismatched:
   164  		return ErrRequestIdentdMismatched
   165  	default:
   166  		return ErrRequestUnknownCode
   167  	}
   168  }
   169  
   170  // For version 4A, if the client cannot resolve the destination host's
   171  // domain name to find its IP address, it should set the first three bytes
   172  // of DSTIP to NULL and the last byte to a non-zero value. (This corresponds
   173  // to IP address 0.0.0.x, with x nonzero. As decreed by IANA  -- The
   174  // Internet Assigned Numbers Authority -- such an address is inadmissible
   175  // as a destination IP address and thus should never occur if the client
   176  // can resolve the domain name.)
   177  func isReservedIP(ip net.IP) bool {
   178  	subnet := net.IPNet{
   179  		IP:   net.IPv4zero,
   180  		Mask: net.IPv4Mask(0xff, 0xff, 0xff, 0x00),
   181  	}
   182  
   183  	return !ip.IsUnspecified() && subnet.Contains(ip)
   184  }
   185  
   186  func readUntilNull(r io.Reader) ([]byte, error) {
   187  	buf := &bytes.Buffer{}
   188  	var data [1]byte
   189  
   190  	for {
   191  		if _, err := r.Read(data[:]); err != nil {
   192  			return nil, err
   193  		}
   194  		if data[0] == 0 {
   195  			return buf.Bytes(), nil
   196  		}
   197  		buf.WriteByte(data[0])
   198  	}
   199  }