github.com/sagernet/sing@v0.4.0-beta.19.0.20240518125136-f67a0988a636/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  			if authenticator != nil && !authenticator.Verify(request.Username, "") {
   114  				err = socks4.WriteResponse(conn, socks4.Response{
   115  					ReplyCode:   socks4.ReplyCodeRejectedOrFailed,
   116  					Destination: request.Destination,
   117  				})
   118  				if err != nil {
   119  					return err
   120  				}
   121  				return E.New("socks4: authentication failed, username=", request.Username)
   122  			}
   123  			err = socks4.WriteResponse(conn, socks4.Response{
   124  				ReplyCode:   socks4.ReplyCodeGranted,
   125  				Destination: M.SocksaddrFromNet(conn.LocalAddr()),
   126  			})
   127  			if err != nil {
   128  				return err
   129  			}
   130  			metadata.Protocol = "socks4"
   131  			metadata.Destination = request.Destination
   132  			return handler.NewConnection(auth.ContextWithUser(ctx, request.Username), conn, metadata)
   133  		default:
   134  			err = socks4.WriteResponse(conn, socks4.Response{
   135  				ReplyCode:   socks4.ReplyCodeRejectedOrFailed,
   136  				Destination: request.Destination,
   137  			})
   138  			if err != nil {
   139  				return err
   140  			}
   141  			return E.New("socks4: unsupported command ", request.Command)
   142  		}
   143  	case socks5.Version:
   144  		authRequest, err := socks5.ReadAuthRequest0(conn)
   145  		if err != nil {
   146  			return err
   147  		}
   148  		var authMethod byte
   149  		if authenticator != nil && !common.Contains(authRequest.Methods, socks5.AuthTypeUsernamePassword) {
   150  			err = socks5.WriteAuthResponse(conn, socks5.AuthResponse{
   151  				Method: socks5.AuthTypeNoAcceptedMethods,
   152  			})
   153  			if err != nil {
   154  				return err
   155  			}
   156  		}
   157  		if authenticator != nil {
   158  			authMethod = socks5.AuthTypeUsernamePassword
   159  		} else {
   160  			authMethod = socks5.AuthTypeNotRequired
   161  		}
   162  		err = socks5.WriteAuthResponse(conn, socks5.AuthResponse{
   163  			Method: authMethod,
   164  		})
   165  		if err != nil {
   166  			return err
   167  		}
   168  		if authMethod == socks5.AuthTypeUsernamePassword {
   169  			usernamePasswordAuthRequest, err := socks5.ReadUsernamePasswordAuthRequest(conn)
   170  			if err != nil {
   171  				return err
   172  			}
   173  			ctx = auth.ContextWithUser(ctx, usernamePasswordAuthRequest.Username)
   174  			response := socks5.UsernamePasswordAuthResponse{}
   175  			if authenticator.Verify(usernamePasswordAuthRequest.Username, usernamePasswordAuthRequest.Password) {
   176  				response.Status = socks5.UsernamePasswordStatusSuccess
   177  			} else {
   178  				response.Status = socks5.UsernamePasswordStatusFailure
   179  			}
   180  			err = socks5.WriteUsernamePasswordAuthResponse(conn, response)
   181  			if err != nil {
   182  				return err
   183  			}
   184  			if response.Status != socks5.UsernamePasswordStatusSuccess {
   185  				return E.New("socks5: authentication failed, username=", usernamePasswordAuthRequest.Username, ", password=", usernamePasswordAuthRequest.Password)
   186  			}
   187  		}
   188  		request, err := socks5.ReadRequest(conn)
   189  		if err != nil {
   190  			return err
   191  		}
   192  		switch request.Command {
   193  		case socks5.CommandConnect:
   194  			err = socks5.WriteResponse(conn, socks5.Response{
   195  				ReplyCode: socks5.ReplyCodeSuccess,
   196  				Bind:      M.SocksaddrFromNet(conn.LocalAddr()),
   197  			})
   198  			if err != nil {
   199  				return err
   200  			}
   201  			metadata.Protocol = "socks5"
   202  			metadata.Destination = request.Destination
   203  			return handler.NewConnection(ctx, conn, metadata)
   204  		case socks5.CommandUDPAssociate:
   205  			var udpConn *net.UDPConn
   206  			udpConn, err = net.ListenUDP(M.NetworkFromNetAddr("udp", M.AddrFromNet(conn.LocalAddr())), net.UDPAddrFromAddrPort(netip.AddrPortFrom(M.AddrFromNet(conn.LocalAddr()), 0)))
   207  			if err != nil {
   208  				return err
   209  			}
   210  			defer udpConn.Close()
   211  			err = socks5.WriteResponse(conn, socks5.Response{
   212  				ReplyCode: socks5.ReplyCodeSuccess,
   213  				Bind:      M.SocksaddrFromNet(udpConn.LocalAddr()),
   214  			})
   215  			if err != nil {
   216  				return err
   217  			}
   218  			metadata.Protocol = "socks5"
   219  			metadata.Destination = request.Destination
   220  			var innerError error
   221  			done := make(chan struct{})
   222  			associatePacketConn := NewAssociatePacketConn(udpConn, request.Destination, conn)
   223  			go func() {
   224  				innerError = handler.NewPacketConnection(ctx, associatePacketConn, metadata)
   225  				close(done)
   226  			}()
   227  			err = common.Error(io.Copy(io.Discard, conn))
   228  			associatePacketConn.Close()
   229  			<-done
   230  			return E.Errors(innerError, err)
   231  		default:
   232  			err = socks5.WriteResponse(conn, socks5.Response{
   233  				ReplyCode: socks5.ReplyCodeUnsupported,
   234  			})
   235  			if err != nil {
   236  				return err
   237  			}
   238  			return E.New("socks5: unsupported command ", request.Command)
   239  		}
   240  	}
   241  	return os.ErrInvalid
   242  }