github.com/database64128/shadowsocks-go@v1.10.2-0.20240315062903-143a773533f1/socks5/stream.go (about)

     1  package socks5
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  
    10  	"github.com/database64128/shadowsocks-go/conn"
    11  	"github.com/database64128/shadowsocks-go/zerocopy"
    12  )
    13  
    14  // SOCKS version 5.
    15  const Version = 5
    16  
    17  // SOCKS5 authentication methods as defined in RFC 1928 section 3.
    18  const (
    19  	MethodNoAuthenticationRequired = 0
    20  	MethodGSSAPI                   = 1
    21  	MethodUsernamePassword         = 2
    22  	MethodNoAcceptable             = 0xFF
    23  )
    24  
    25  // SOCKS request commands as defined in RFC 1928 section 4.
    26  const (
    27  	CmdConnect      = 1
    28  	CmdBind         = 2
    29  	CmdUDPAssociate = 3
    30  )
    31  
    32  // SOCKS errors as defined in RFC 1928 section 6.
    33  const (
    34  	Succeeded               = 0
    35  	ErrGeneralFailure       = 1
    36  	ErrConnectionNotAllowed = 2
    37  	ErrNetworkUnreachable   = 3
    38  	ErrHostUnreachable      = 4
    39  	ErrConnectionRefused    = 5
    40  	ErrTTLExpired           = 6
    41  	ErrCommandNotSupported  = 7
    42  	ErrAddressNotSupported  = 8
    43  )
    44  
    45  var (
    46  	ErrUnsupportedSocksVersion         = errors.New("unsupported SOCKS version")
    47  	ErrUnsupportedAuthenticationMethod = errors.New("unsupported authentication method")
    48  	ErrUnsupportedCommand              = errors.New("unsupported command")
    49  	ErrUDPAssociateDone                = errors.New("UDP ASSOCIATE done")
    50  )
    51  
    52  // replyWithStatus writes a reply to w with the REP field set to status.
    53  func replyWithStatus(w io.Writer, b []byte, status byte) error {
    54  	const replyLen = 3 + IPv4AddrLen
    55  	reply := b[:replyLen]
    56  	reply[0] = Version
    57  	reply[1] = status
    58  	reply[2] = 0
    59  	*(*[IPv4AddrLen]byte)(reply[3:]) = IPv4UnspecifiedAddr
    60  	_, err := w.Write(reply)
    61  	return err
    62  }
    63  
    64  // ClientRequest writes a request to targetAddr and returns the bound address in reply.
    65  func ClientRequest(rw io.ReadWriter, command byte, targetAddr conn.Addr) (addr conn.Addr, err error) {
    66  	b := make([]byte, 3+MaxAddrLen)
    67  	b[0] = Version
    68  	b[1] = 1
    69  	b[2] = MethodNoAuthenticationRequired
    70  
    71  	// Write VER NMETHDOS METHODS.
    72  	_, err = rw.Write(b[:3])
    73  	if err != nil {
    74  		return
    75  	}
    76  
    77  	// Read version selection message.
    78  	_, err = io.ReadFull(rw, b[:2])
    79  	if err != nil {
    80  		return
    81  	}
    82  
    83  	// Check VER.
    84  	if b[0] != Version {
    85  		err = fmt.Errorf("%w: %d", ErrUnsupportedSocksVersion, b[0])
    86  		return
    87  	}
    88  
    89  	// Check METHOD.
    90  	if b[1] != MethodNoAuthenticationRequired {
    91  		err = fmt.Errorf("%w: %d", ErrUnsupportedAuthenticationMethod, b[1])
    92  		return
    93  	}
    94  
    95  	// Write VER, CMD, RSV, SOCKS address.
    96  	b[1] = command
    97  	n := WriteAddrFromConnAddr(b[3:], targetAddr)
    98  	_, err = rw.Write(b[:3+n])
    99  	if err != nil {
   100  		return
   101  	}
   102  
   103  	// Read VER, REP, RSV.
   104  	_, err = io.ReadFull(rw, b[:3])
   105  	if err != nil {
   106  		return
   107  	}
   108  
   109  	// Check VER.
   110  	if b[0] != Version {
   111  		err = fmt.Errorf("%w: %d", ErrUnsupportedSocksVersion, b[0])
   112  		return
   113  	}
   114  
   115  	// Check REP.
   116  	if b[1] != Succeeded {
   117  		err = fmt.Errorf("SOCKS error: %d", b[1])
   118  		return
   119  	}
   120  
   121  	// Read SOCKS address.
   122  	sa, err := AppendFromReader(b[3:3], rw)
   123  	if err != nil {
   124  		return
   125  	}
   126  	addr, _, err = ConnAddrFromSlice(sa)
   127  	return
   128  }
   129  
   130  // ClientConnect writes a CONNECT request to targetAddr.
   131  func ClientConnect(rw io.ReadWriter, targetAddr conn.Addr) error {
   132  	_, err := ClientRequest(rw, CmdConnect, targetAddr)
   133  	return err
   134  }
   135  
   136  // ClientUDPAssociate writes a UDP ASSOCIATE request to targetAddr.
   137  func ClientUDPAssociate(rw io.ReadWriter, targetAddr conn.Addr) (conn.Addr, error) {
   138  	return ClientRequest(rw, CmdUDPAssociate, targetAddr)
   139  }
   140  
   141  // ServerAccept processes an incoming request from rw.
   142  //
   143  // enableTCP enables the CONNECT command.
   144  // enableUDP enables the UDP ASSOCIATE command.
   145  //
   146  // When UDP is enabled, rw must be a [*net.TCPConn].
   147  func ServerAccept(rw io.ReadWriter, enableTCP, enableUDP bool) (addr conn.Addr, err error) {
   148  	b := make([]byte, 3+MaxAddrLen)
   149  
   150  	// Read VER, NMETHODS.
   151  	_, err = io.ReadFull(rw, b[:2])
   152  	if err != nil {
   153  		return
   154  	}
   155  
   156  	// Check VER.
   157  	if b[0] != Version {
   158  		err = fmt.Errorf("%w: %d", ErrUnsupportedSocksVersion, b[0])
   159  		return
   160  	}
   161  
   162  	// Check NMETHODS.
   163  	if b[1] == 0 {
   164  		err = fmt.Errorf("NMETHODS is %d", b[1])
   165  		return
   166  	}
   167  
   168  	// Read METHODS.
   169  	_, err = io.ReadFull(rw, b[:b[1]])
   170  	if err != nil {
   171  		return
   172  	}
   173  
   174  	// Check METHODS.
   175  	if bytes.IndexByte(b[:b[1]], MethodNoAuthenticationRequired) == -1 {
   176  		b[0] = Version
   177  		b[1] = MethodNoAcceptable
   178  		_, err = rw.Write(b[:2])
   179  		if err == nil {
   180  			err = ErrUnsupportedAuthenticationMethod
   181  		}
   182  		return
   183  	}
   184  
   185  	// Write method selection message.
   186  	//
   187  	// 	+-----+--------+
   188  	// 	| VER | METHOD |
   189  	// 	+-----+--------+
   190  	// 	|  1  |   1    |
   191  	// 	+-----+--------+
   192  	b[0] = Version
   193  	b[1] = MethodNoAuthenticationRequired
   194  	_, err = rw.Write(b[:2])
   195  	if err != nil {
   196  		return
   197  	}
   198  
   199  	// Read VER, CMD, RSV.
   200  	_, err = io.ReadFull(rw, b[:3])
   201  	if err != nil {
   202  		return
   203  	}
   204  
   205  	// Check VER.
   206  	if b[0] != Version {
   207  		err = fmt.Errorf("%w: %d", ErrUnsupportedSocksVersion, b[0])
   208  		return
   209  	}
   210  
   211  	// Read SOCKS address.
   212  	sa, err := AppendFromReader(b[3:3], rw)
   213  	if err != nil {
   214  		return
   215  	}
   216  	addr, _, err = ConnAddrFromSlice(sa)
   217  	if err != nil {
   218  		return
   219  	}
   220  
   221  	switch {
   222  	case b[1] == CmdConnect && enableTCP:
   223  		err = replyWithStatus(rw, b, Succeeded)
   224  
   225  	case b[1] == CmdUDPAssociate && enableUDP:
   226  		// Use the connection's local address as the returned UDP bound address.
   227  		tc, ok := rw.(*net.TCPConn)
   228  		if !ok {
   229  			err = zerocopy.ErrAcceptRequiresTCPConn
   230  			return
   231  		}
   232  		localAddrPort := tc.LocalAddr().(*net.TCPAddr).AddrPort()
   233  
   234  		// Construct reply.
   235  		b[1] = Succeeded
   236  		reply := AppendAddrFromAddrPort(b[:3], localAddrPort)
   237  
   238  		// Write reply.
   239  		_, err = rw.Write(reply)
   240  		if err != nil {
   241  			return
   242  		}
   243  
   244  		// Hold the connection open.
   245  		_, err = rw.Read(b[:1])
   246  		if err == nil || err == io.EOF {
   247  			err = ErrUDPAssociateDone
   248  		}
   249  
   250  	default:
   251  		err = replyWithStatus(rw, b, ErrCommandNotSupported)
   252  		if err == nil {
   253  			err = fmt.Errorf("%w: %d", ErrUnsupportedCommand, b[1])
   254  		}
   255  	}
   256  
   257  	return
   258  }