github.com/sagernet/sing@v0.4.0-beta.19.0.20240518125136-f67a0988a636/protocol/socks/client.go (about)

     1  package socks
     2  
     3  import (
     4  	"context"
     5  	"net"
     6  	"net/url"
     7  	"os"
     8  	"strings"
     9  
    10  	E "github.com/sagernet/sing/common/exceptions"
    11  	M "github.com/sagernet/sing/common/metadata"
    12  	N "github.com/sagernet/sing/common/network"
    13  	"github.com/sagernet/sing/protocol/socks/socks4"
    14  	"github.com/sagernet/sing/protocol/socks/socks5"
    15  )
    16  
    17  type Version uint8
    18  
    19  const (
    20  	Version4 Version = iota
    21  	Version4A
    22  	Version5
    23  )
    24  
    25  func (v Version) String() string {
    26  	switch v {
    27  	case Version4:
    28  		return "4"
    29  	case Version4A:
    30  		return "4a"
    31  	case Version5:
    32  		return "5"
    33  	default:
    34  		return "unknown"
    35  	}
    36  }
    37  
    38  func ParseVersion(version string) (Version, error) {
    39  	switch version {
    40  	case "4":
    41  		return Version4, nil
    42  	case "4a":
    43  		return Version4A, nil
    44  	case "5":
    45  		return Version5, nil
    46  	}
    47  	return 0, E.New("unknown socks version: ", version)
    48  }
    49  
    50  var _ N.Dialer = (*Client)(nil)
    51  
    52  type Client struct {
    53  	version    Version
    54  	dialer     N.Dialer
    55  	serverAddr M.Socksaddr
    56  	username   string
    57  	password   string
    58  }
    59  
    60  func NewClient(dialer N.Dialer, serverAddr M.Socksaddr, version Version, username string, password string) *Client {
    61  	return &Client{
    62  		version:    version,
    63  		dialer:     dialer,
    64  		serverAddr: serverAddr,
    65  		username:   username,
    66  		password:   password,
    67  	}
    68  }
    69  
    70  func NewClientFromURL(dialer N.Dialer, rawURL string) (*Client, error) {
    71  	var client Client
    72  	if !strings.Contains(rawURL, "://") {
    73  		rawURL = "socks://" + rawURL
    74  	}
    75  	proxyURL, err := url.Parse(rawURL)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	client.dialer = dialer
    80  	client.serverAddr = M.ParseSocksaddr(proxyURL.Host)
    81  	switch proxyURL.Scheme {
    82  	case "socks4":
    83  		client.version = Version4
    84  	case "socks4a":
    85  		client.version = Version4A
    86  	case "socks", "socks5", "":
    87  		client.version = Version5
    88  	default:
    89  		return nil, E.New("socks: unknown scheme: ", proxyURL.Scheme)
    90  	}
    91  	if proxyURL.User != nil {
    92  		if client.version == Version5 {
    93  			client.username = proxyURL.User.Username()
    94  			client.password, _ = proxyURL.User.Password()
    95  		} else {
    96  			client.username = proxyURL.User.String()
    97  		}
    98  	}
    99  	return &client, nil
   100  }
   101  
   102  func (c *Client) DialContext(ctx context.Context, network string, address M.Socksaddr) (net.Conn, error) {
   103  	network = N.NetworkName(network)
   104  	var command byte
   105  	switch network {
   106  	case N.NetworkTCP:
   107  		command = socks4.CommandConnect
   108  	case N.NetworkUDP:
   109  		if c.version != Version5 {
   110  			return nil, E.New("socks4: udp unsupported")
   111  		}
   112  		command = socks5.CommandUDPAssociate
   113  	default:
   114  		return nil, E.Extend(N.ErrUnknownNetwork, network)
   115  	}
   116  	tcpConn, err := c.dialer.DialContext(ctx, N.NetworkTCP, c.serverAddr)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	if c.version == Version4 && address.IsFqdn() {
   121  		tcpAddr, err := net.ResolveTCPAddr(network, address.String())
   122  		if err != nil {
   123  			tcpConn.Close()
   124  			return nil, err
   125  		}
   126  		address = M.SocksaddrFromNet(tcpAddr)
   127  	}
   128  	switch c.version {
   129  	case Version4, Version4A:
   130  		_, err = ClientHandshake4(tcpConn, command, address, c.username)
   131  		if err != nil {
   132  			tcpConn.Close()
   133  			return nil, err
   134  		}
   135  		return tcpConn, nil
   136  	case Version5:
   137  		response, err := ClientHandshake5(tcpConn, command, address, c.username, c.password)
   138  		if err != nil {
   139  			tcpConn.Close()
   140  			return nil, err
   141  		}
   142  		if command == socks5.CommandConnect {
   143  			return tcpConn, nil
   144  		}
   145  		udpConn, err := c.dialer.DialContext(ctx, N.NetworkUDP, response.Bind)
   146  		if err != nil {
   147  			tcpConn.Close()
   148  			return nil, err
   149  		}
   150  		return NewAssociatePacketConn(udpConn, address, tcpConn), nil
   151  	}
   152  	return nil, os.ErrInvalid
   153  }
   154  
   155  func (c *Client) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
   156  	conn, err := c.DialContext(ctx, N.NetworkUDP, destination)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  	return conn.(*AssociatePacketConn), nil
   161  }
   162  
   163  func (c *Client) BindContext(ctx context.Context, address M.Socksaddr) (net.Conn, error) {
   164  	tcpConn, err := c.dialer.DialContext(ctx, N.NetworkTCP, c.serverAddr)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  	switch c.version {
   169  	case Version4, Version4A:
   170  		_, err = ClientHandshake4(tcpConn, socks4.CommandBind, address, c.username)
   171  		if err != nil {
   172  			tcpConn.Close()
   173  			return nil, err
   174  		}
   175  		return tcpConn, nil
   176  	case Version5:
   177  		_, err = ClientHandshake5(tcpConn, socks5.CommandBind, address, c.username, c.password)
   178  		if err != nil {
   179  			tcpConn.Close()
   180  			return nil, err
   181  		}
   182  		return tcpConn, nil
   183  	}
   184  	return nil, os.ErrInvalid
   185  }