Dinesh Belwalkar | e63f7f9 | 2019-11-22 23:11:16 +0000 | [diff] [blame] | 1 | // Copyright 2018 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | // Package socks provides a SOCKS version 5 client implementation. |
| 6 | // |
| 7 | // SOCKS protocol version 5 is defined in RFC 1928. |
| 8 | // Username/Password authentication for SOCKS version 5 is defined in |
| 9 | // RFC 1929. |
| 10 | package socks |
| 11 | |
| 12 | import ( |
| 13 | "context" |
| 14 | "errors" |
| 15 | "io" |
| 16 | "net" |
| 17 | "strconv" |
| 18 | ) |
| 19 | |
| 20 | // A Command represents a SOCKS command. |
| 21 | type Command int |
| 22 | |
| 23 | func (cmd Command) String() string { |
| 24 | switch cmd { |
| 25 | case CmdConnect: |
| 26 | return "socks connect" |
| 27 | case cmdBind: |
| 28 | return "socks bind" |
| 29 | default: |
| 30 | return "socks " + strconv.Itoa(int(cmd)) |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | // An AuthMethod represents a SOCKS authentication method. |
| 35 | type AuthMethod int |
| 36 | |
| 37 | // A Reply represents a SOCKS command reply code. |
| 38 | type Reply int |
| 39 | |
| 40 | func (code Reply) String() string { |
| 41 | switch code { |
| 42 | case StatusSucceeded: |
| 43 | return "succeeded" |
| 44 | case 0x01: |
| 45 | return "general SOCKS server failure" |
| 46 | case 0x02: |
| 47 | return "connection not allowed by ruleset" |
| 48 | case 0x03: |
| 49 | return "network unreachable" |
| 50 | case 0x04: |
| 51 | return "host unreachable" |
| 52 | case 0x05: |
| 53 | return "connection refused" |
| 54 | case 0x06: |
| 55 | return "TTL expired" |
| 56 | case 0x07: |
| 57 | return "command not supported" |
| 58 | case 0x08: |
| 59 | return "address type not supported" |
| 60 | default: |
| 61 | return "unknown code: " + strconv.Itoa(int(code)) |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | // Wire protocol constants. |
| 66 | const ( |
| 67 | Version5 = 0x05 |
| 68 | |
| 69 | AddrTypeIPv4 = 0x01 |
| 70 | AddrTypeFQDN = 0x03 |
| 71 | AddrTypeIPv6 = 0x04 |
| 72 | |
| 73 | CmdConnect Command = 0x01 // establishes an active-open forward proxy connection |
| 74 | cmdBind Command = 0x02 // establishes a passive-open forward proxy connection |
| 75 | |
| 76 | AuthMethodNotRequired AuthMethod = 0x00 // no authentication required |
| 77 | AuthMethodUsernamePassword AuthMethod = 0x02 // use username/password |
| 78 | AuthMethodNoAcceptableMethods AuthMethod = 0xff // no acceptable authentication methods |
| 79 | |
| 80 | StatusSucceeded Reply = 0x00 |
| 81 | ) |
| 82 | |
| 83 | // An Addr represents a SOCKS-specific address. |
| 84 | // Either Name or IP is used exclusively. |
| 85 | type Addr struct { |
| 86 | Name string // fully-qualified domain name |
| 87 | IP net.IP |
| 88 | Port int |
| 89 | } |
| 90 | |
| 91 | func (a *Addr) Network() string { return "socks" } |
| 92 | |
| 93 | func (a *Addr) String() string { |
| 94 | if a == nil { |
| 95 | return "<nil>" |
| 96 | } |
| 97 | port := strconv.Itoa(a.Port) |
| 98 | if a.IP == nil { |
| 99 | return net.JoinHostPort(a.Name, port) |
| 100 | } |
| 101 | return net.JoinHostPort(a.IP.String(), port) |
| 102 | } |
| 103 | |
| 104 | // A Conn represents a forward proxy connection. |
| 105 | type Conn struct { |
| 106 | net.Conn |
| 107 | |
| 108 | boundAddr net.Addr |
| 109 | } |
| 110 | |
| 111 | // BoundAddr returns the address assigned by the proxy server for |
| 112 | // connecting to the command target address from the proxy server. |
| 113 | func (c *Conn) BoundAddr() net.Addr { |
| 114 | if c == nil { |
| 115 | return nil |
| 116 | } |
| 117 | return c.boundAddr |
| 118 | } |
| 119 | |
| 120 | // A Dialer holds SOCKS-specific options. |
| 121 | type Dialer struct { |
| 122 | cmd Command // either CmdConnect or cmdBind |
| 123 | proxyNetwork string // network between a proxy server and a client |
| 124 | proxyAddress string // proxy server address |
| 125 | |
| 126 | // ProxyDial specifies the optional dial function for |
| 127 | // establishing the transport connection. |
| 128 | ProxyDial func(context.Context, string, string) (net.Conn, error) |
| 129 | |
| 130 | // AuthMethods specifies the list of request authentication |
| 131 | // methods. |
| 132 | // If empty, SOCKS client requests only AuthMethodNotRequired. |
| 133 | AuthMethods []AuthMethod |
| 134 | |
| 135 | // Authenticate specifies the optional authentication |
| 136 | // function. It must be non-nil when AuthMethods is not empty. |
| 137 | // It must return an error when the authentication is failed. |
| 138 | Authenticate func(context.Context, io.ReadWriter, AuthMethod) error |
| 139 | } |
| 140 | |
| 141 | // DialContext connects to the provided address on the provided |
| 142 | // network. |
| 143 | // |
| 144 | // The returned error value may be a net.OpError. When the Op field of |
| 145 | // net.OpError contains "socks", the Source field contains a proxy |
| 146 | // server address and the Addr field contains a command target |
| 147 | // address. |
| 148 | // |
| 149 | // See func Dial of the net package of standard library for a |
| 150 | // description of the network and address parameters. |
| 151 | func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { |
| 152 | if err := d.validateTarget(network, address); err != nil { |
| 153 | proxy, dst, _ := d.pathAddrs(address) |
| 154 | return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} |
| 155 | } |
| 156 | if ctx == nil { |
| 157 | proxy, dst, _ := d.pathAddrs(address) |
| 158 | return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")} |
| 159 | } |
| 160 | var err error |
| 161 | var c net.Conn |
| 162 | if d.ProxyDial != nil { |
| 163 | c, err = d.ProxyDial(ctx, d.proxyNetwork, d.proxyAddress) |
| 164 | } else { |
| 165 | var dd net.Dialer |
| 166 | c, err = dd.DialContext(ctx, d.proxyNetwork, d.proxyAddress) |
| 167 | } |
| 168 | if err != nil { |
| 169 | proxy, dst, _ := d.pathAddrs(address) |
| 170 | return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} |
| 171 | } |
| 172 | a, err := d.connect(ctx, c, address) |
| 173 | if err != nil { |
| 174 | c.Close() |
| 175 | proxy, dst, _ := d.pathAddrs(address) |
| 176 | return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} |
| 177 | } |
| 178 | return &Conn{Conn: c, boundAddr: a}, nil |
| 179 | } |
| 180 | |
| 181 | // DialWithConn initiates a connection from SOCKS server to the target |
| 182 | // network and address using the connection c that is already |
| 183 | // connected to the SOCKS server. |
| 184 | // |
| 185 | // It returns the connection's local address assigned by the SOCKS |
| 186 | // server. |
| 187 | func (d *Dialer) DialWithConn(ctx context.Context, c net.Conn, network, address string) (net.Addr, error) { |
| 188 | if err := d.validateTarget(network, address); err != nil { |
| 189 | proxy, dst, _ := d.pathAddrs(address) |
| 190 | return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} |
| 191 | } |
| 192 | if ctx == nil { |
| 193 | proxy, dst, _ := d.pathAddrs(address) |
| 194 | return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")} |
| 195 | } |
| 196 | a, err := d.connect(ctx, c, address) |
| 197 | if err != nil { |
| 198 | proxy, dst, _ := d.pathAddrs(address) |
| 199 | return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} |
| 200 | } |
| 201 | return a, nil |
| 202 | } |
| 203 | |
| 204 | // Dial connects to the provided address on the provided network. |
| 205 | // |
| 206 | // Unlike DialContext, it returns a raw transport connection instead |
| 207 | // of a forward proxy connection. |
| 208 | // |
| 209 | // Deprecated: Use DialContext or DialWithConn instead. |
| 210 | func (d *Dialer) Dial(network, address string) (net.Conn, error) { |
| 211 | if err := d.validateTarget(network, address); err != nil { |
| 212 | proxy, dst, _ := d.pathAddrs(address) |
| 213 | return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} |
| 214 | } |
| 215 | var err error |
| 216 | var c net.Conn |
| 217 | if d.ProxyDial != nil { |
| 218 | c, err = d.ProxyDial(context.Background(), d.proxyNetwork, d.proxyAddress) |
| 219 | } else { |
| 220 | c, err = net.Dial(d.proxyNetwork, d.proxyAddress) |
| 221 | } |
| 222 | if err != nil { |
| 223 | proxy, dst, _ := d.pathAddrs(address) |
| 224 | return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} |
| 225 | } |
| 226 | if _, err := d.DialWithConn(context.Background(), c, network, address); err != nil { |
| 227 | c.Close() |
| 228 | return nil, err |
| 229 | } |
| 230 | return c, nil |
| 231 | } |
| 232 | |
| 233 | func (d *Dialer) validateTarget(network, address string) error { |
| 234 | switch network { |
| 235 | case "tcp", "tcp6", "tcp4": |
| 236 | default: |
| 237 | return errors.New("network not implemented") |
| 238 | } |
| 239 | switch d.cmd { |
| 240 | case CmdConnect, cmdBind: |
| 241 | default: |
| 242 | return errors.New("command not implemented") |
| 243 | } |
| 244 | return nil |
| 245 | } |
| 246 | |
| 247 | func (d *Dialer) pathAddrs(address string) (proxy, dst net.Addr, err error) { |
| 248 | for i, s := range []string{d.proxyAddress, address} { |
| 249 | host, port, err := splitHostPort(s) |
| 250 | if err != nil { |
| 251 | return nil, nil, err |
| 252 | } |
| 253 | a := &Addr{Port: port} |
| 254 | a.IP = net.ParseIP(host) |
| 255 | if a.IP == nil { |
| 256 | a.Name = host |
| 257 | } |
| 258 | if i == 0 { |
| 259 | proxy = a |
| 260 | } else { |
| 261 | dst = a |
| 262 | } |
| 263 | } |
| 264 | return |
| 265 | } |
| 266 | |
| 267 | // NewDialer returns a new Dialer that dials through the provided |
| 268 | // proxy server's network and address. |
| 269 | func NewDialer(network, address string) *Dialer { |
| 270 | return &Dialer{proxyNetwork: network, proxyAddress: address, cmd: CmdConnect} |
| 271 | } |
| 272 | |
| 273 | const ( |
| 274 | authUsernamePasswordVersion = 0x01 |
| 275 | authStatusSucceeded = 0x00 |
| 276 | ) |
| 277 | |
| 278 | // UsernamePassword are the credentials for the username/password |
| 279 | // authentication method. |
| 280 | type UsernamePassword struct { |
| 281 | Username string |
| 282 | Password string |
| 283 | } |
| 284 | |
| 285 | // Authenticate authenticates a pair of username and password with the |
| 286 | // proxy server. |
| 287 | func (up *UsernamePassword) Authenticate(ctx context.Context, rw io.ReadWriter, auth AuthMethod) error { |
| 288 | switch auth { |
| 289 | case AuthMethodNotRequired: |
| 290 | return nil |
| 291 | case AuthMethodUsernamePassword: |
| 292 | if len(up.Username) == 0 || len(up.Username) > 255 || len(up.Password) == 0 || len(up.Password) > 255 { |
| 293 | return errors.New("invalid username/password") |
| 294 | } |
| 295 | b := []byte{authUsernamePasswordVersion} |
| 296 | b = append(b, byte(len(up.Username))) |
| 297 | b = append(b, up.Username...) |
| 298 | b = append(b, byte(len(up.Password))) |
| 299 | b = append(b, up.Password...) |
| 300 | // TODO(mikio): handle IO deadlines and cancelation if |
| 301 | // necessary |
| 302 | if _, err := rw.Write(b); err != nil { |
| 303 | return err |
| 304 | } |
| 305 | if _, err := io.ReadFull(rw, b[:2]); err != nil { |
| 306 | return err |
| 307 | } |
| 308 | if b[0] != authUsernamePasswordVersion { |
| 309 | return errors.New("invalid username/password version") |
| 310 | } |
| 311 | if b[1] != authStatusSucceeded { |
| 312 | return errors.New("username/password authentication failed") |
| 313 | } |
| 314 | return nil |
| 315 | } |
| 316 | return errors.New("unsupported authentication method " + strconv.Itoa(int(auth))) |
| 317 | } |