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 }