github.com/sagernet/sing@v0.2.6/protocol/socks/handshake.go (about)

     1  package socks
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"net"
     7  	"net/netip"
     8  	"os"
     9  
    10  	"github.com/sagernet/sing/common"
    11  	"github.com/sagernet/sing/common/auth"
    12  	E "github.com/sagernet/sing/common/exceptions"
    13  	M "github.com/sagernet/sing/common/metadata"
    14  	N "github.com/sagernet/sing/common/network"
    15  	"github.com/sagernet/sing/common/rw"
    16  	"github.com/sagernet/sing/protocol/socks/socks4"
    17  	"github.com/sagernet/sing/protocol/socks/socks5"
    18  )
    19  
    20  type Handler interface {
    21  	N.TCPConnectionHandler
    22  	N.UDPConnectionHandler
    23  }
    24  
    25  func ClientHandshake4(conn io.ReadWriter, command byte, destination M.Socksaddr, username string) (socks4.Response, error) {
    26  	err := socks4.WriteRequest(conn, socks4.Request{
    27  		Command:     command,
    28  		Destination: destination,
    29  		Username:    username,
    30  	})
    31  	if err != nil {
    32  		return socks4.Response{}, err
    33  	}
    34  	response, err := socks4.ReadResponse(conn)
    35  	if err != nil {
    36  		return socks4.Response{}, err
    37  	}
    38  	if response.ReplyCode != socks4.ReplyCodeGranted {
    39  		err = E.New("socks4: request rejected, code= ", response.ReplyCode)
    40  	}
    41  	return response, err
    42  }
    43  
    44  func ClientHandshake5(conn io.ReadWriter, command byte, destination M.Socksaddr, username string, password string) (socks5.Response, error) {
    45  	var method byte
    46  	if username == "" {
    47  		method = socks5.AuthTypeNotRequired
    48  	} else {
    49  		method = socks5.AuthTypeUsernamePassword
    50  	}
    51  	err := socks5.WriteAuthRequest(conn, socks5.AuthRequest{
    52  		Methods: []byte{method},
    53  	})
    54  	if err != nil {
    55  		return socks5.Response{}, err
    56  	}
    57  	authResponse, err := socks5.ReadAuthResponse(conn)
    58  	if err != nil {
    59  		return socks5.Response{}, err
    60  	}
    61  	if authResponse.Method == socks5.AuthTypeUsernamePassword {
    62  		err = socks5.WriteUsernamePasswordAuthRequest(conn, socks5.UsernamePasswordAuthRequest{
    63  			Username: username,
    64  			Password: password,
    65  		})
    66  		if err != nil {
    67  			return socks5.Response{}, err
    68  		}
    69  		usernamePasswordResponse, err := socks5.ReadUsernamePasswordAuthResponse(conn)
    70  		if err != nil {
    71  			return socks5.Response{}, err
    72  		}
    73  		if usernamePasswordResponse.Status != socks5.UsernamePasswordStatusSuccess {
    74  			return socks5.Response{}, E.New("socks5: incorrect user name or password")
    75  		}
    76  	} else if authResponse.Method != socks5.AuthTypeNotRequired {
    77  		return socks5.Response{}, E.New("socks5: unsupported auth method: ", authResponse.Method)
    78  	}
    79  	err = socks5.WriteRequest(conn, socks5.Request{
    80  		Command:     command,
    81  		Destination: destination,
    82  	})
    83  	if err != nil {
    84  		return socks5.Response{}, err
    85  	}
    86  	response, err := socks5.ReadResponse(conn)
    87  	if err != nil {
    88  		return socks5.Response{}, err
    89  	}
    90  	if response.ReplyCode != socks5.ReplyCodeSuccess {
    91  		err = E.New("socks5: request rejected, code=", response.ReplyCode)
    92  	}
    93  	return response, err
    94  }
    95  
    96  func HandleConnection(ctx context.Context, conn net.Conn, authenticator auth.Authenticator, handler Handler, metadata M.Metadata) error {
    97  	version, err := rw.ReadByte(conn)
    98  	if err != nil {
    99  		return err
   100  	}
   101  	return HandleConnection0(ctx, conn, version, authenticator, handler, metadata)
   102  }
   103  
   104  func HandleConnection0(ctx context.Context, conn net.Conn, version byte, authenticator auth.Authenticator, handler Handler, metadata M.Metadata) error {
   105  	switch version {
   106  	case socks4.Version:
   107  		request, err := socks4.ReadRequest0(conn)
   108  		if err != nil {
   109  			return err
   110  		}
   111  		switch request.Command {
   112  		case socks4.CommandConnect:
   113  			err = socks4.WriteResponse(conn, socks4.Response{
   114  				ReplyCode:   socks4.ReplyCodeGranted,
   115  				Destination: M.SocksaddrFromNet(conn.LocalAddr()),
   116  			})
   117  			if err != nil {
   118  				return err
   119  			}
   120  			metadata.Protocol = "socks4"
   121  			metadata.Destination = request.Destination
   122  			return handler.NewConnection(auth.ContextWithUser(ctx, request.Username), conn, metadata)
   123  		default:
   124  			err = socks4.WriteResponse(conn, socks4.Response{
   125  				ReplyCode:   socks4.ReplyCodeRejectedOrFailed,
   126  				Destination: request.Destination,
   127  			})
   128  			if err != nil {
   129  				return err
   130  			}
   131  			return E.New("socks4: unsupported command ", request.Command)
   132  		}
   133  	case socks5.Version:
   134  		authRequest, err := socks5.ReadAuthRequest0(conn)
   135  		if err != nil {
   136  			return err
   137  		}
   138  		var authMethod byte
   139  		if authenticator != nil && !common.Contains(authRequest.Methods, socks5.AuthTypeUsernamePassword) {
   140  			err = socks5.WriteAuthResponse(conn, socks5.AuthResponse{
   141  				Method: socks5.AuthTypeNoAcceptedMethods,
   142  			})
   143  			if err != nil {
   144  				return err
   145  			}
   146  		}
   147  		if authenticator != nil {
   148  			authMethod = socks5.AuthTypeUsernamePassword
   149  		} else {
   150  			authMethod = socks5.AuthTypeNotRequired
   151  		}
   152  		err = socks5.WriteAuthResponse(conn, socks5.AuthResponse{
   153  			Method: authMethod,
   154  		})
   155  		if err != nil {
   156  			return err
   157  		}
   158  		if authMethod == socks5.AuthTypeUsernamePassword {
   159  			usernamePasswordAuthRequest, err := socks5.ReadUsernamePasswordAuthRequest(conn)
   160  			if err != nil {
   161  				return err
   162  			}
   163  			ctx = auth.ContextWithUser(ctx, usernamePasswordAuthRequest.Username)
   164  			response := socks5.UsernamePasswordAuthResponse{}
   165  			if authenticator.Verify(usernamePasswordAuthRequest.Username, usernamePasswordAuthRequest.Password) {
   166  				response.Status = socks5.UsernamePasswordStatusSuccess
   167  			} else {
   168  				response.Status = socks5.UsernamePasswordStatusFailure
   169  			}
   170  			err = socks5.WriteUsernamePasswordAuthResponse(conn, response)
   171  			if err != nil {
   172  				return err
   173  			}
   174  		}
   175  		request, err := socks5.ReadRequest(conn)
   176  		if err != nil {
   177  			return err
   178  		}
   179  		switch request.Command {
   180  		case socks5.CommandConnect:
   181  			err = socks5.WriteResponse(conn, socks5.Response{
   182  				ReplyCode: socks5.ReplyCodeSuccess,
   183  				Bind:      M.SocksaddrFromNet(conn.LocalAddr()),
   184  			})
   185  			if err != nil {
   186  				return err
   187  			}
   188  			metadata.Protocol = "socks5"
   189  			metadata.Destination = request.Destination
   190  			return handler.NewConnection(ctx, conn, metadata)
   191  		case socks5.CommandUDPAssociate:
   192  			var udpConn *net.UDPConn
   193  			udpConn, err = net.ListenUDP(M.NetworkFromNetAddr("udp", M.AddrFromNetAddr(conn.LocalAddr())), net.UDPAddrFromAddrPort(netip.AddrPortFrom(M.AddrFromNetAddr(conn.LocalAddr()), 0)))
   194  			if err != nil {
   195  				return err
   196  			}
   197  			defer udpConn.Close()
   198  			err = socks5.WriteResponse(conn, socks5.Response{
   199  				ReplyCode: socks5.ReplyCodeSuccess,
   200  				Bind:      M.SocksaddrFromNet(udpConn.LocalAddr()),
   201  			})
   202  			if err != nil {
   203  				return err
   204  			}
   205  			metadata.Protocol = "socks5"
   206  			metadata.Destination = request.Destination
   207  			var innerError error
   208  			done := make(chan struct{})
   209  			associatePacketConn := NewAssociatePacketConn(udpConn, request.Destination, conn)
   210  			go func() {
   211  				innerError = handler.NewPacketConnection(ctx, associatePacketConn, metadata)
   212  				close(done)
   213  			}()
   214  			err = common.Error(io.Copy(io.Discard, conn))
   215  			associatePacketConn.Close()
   216  			<-done
   217  			return E.Errors(innerError, err)
   218  		default:
   219  			err = socks5.WriteResponse(conn, socks5.Response{
   220  				ReplyCode: socks5.ReplyCodeUnsupported,
   221  			})
   222  			if err != nil {
   223  				return err
   224  			}
   225  			return E.New("socks5: unsupported command ", request.Command)
   226  		}
   227  	}
   228  	return os.ErrInvalid
   229  }