| // Copyright 2018 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package socks provides a SOCKS version 5 client implementation. |
| // |
| // SOCKS protocol version 5 is defined in RFC 1928. |
| // Username/Password authentication for SOCKS version 5 is defined in |
| // RFC 1929. |
| package socks |
| |
| import ( |
| "context" |
| "errors" |
| "io" |
| "net" |
| "strconv" |
| ) |
| |
| // A Command represents a SOCKS command. |
| type Command int |
| |
| func (cmd Command) String() string { |
| switch cmd { |
| case CmdConnect: |
| return "socks connect" |
| case cmdBind: |
| return "socks bind" |
| default: |
| return "socks " + strconv.Itoa(int(cmd)) |
| } |
| } |
| |
| // An AuthMethod represents a SOCKS authentication method. |
| type AuthMethod int |
| |
| // A Reply represents a SOCKS command reply code. |
| type Reply int |
| |
| func (code Reply) String() string { |
| switch code { |
| case StatusSucceeded: |
| return "succeeded" |
| case 0x01: |
| return "general SOCKS server failure" |
| case 0x02: |
| return "connection not allowed by ruleset" |
| case 0x03: |
| return "network unreachable" |
| case 0x04: |
| return "host unreachable" |
| case 0x05: |
| return "connection refused" |
| case 0x06: |
| return "TTL expired" |
| case 0x07: |
| return "command not supported" |
| case 0x08: |
| return "address type not supported" |
| default: |
| return "unknown code: " + strconv.Itoa(int(code)) |
| } |
| } |
| |
| // Wire protocol constants. |
| const ( |
| Version5 = 0x05 |
| |
| AddrTypeIPv4 = 0x01 |
| AddrTypeFQDN = 0x03 |
| AddrTypeIPv6 = 0x04 |
| |
| CmdConnect Command = 0x01 // establishes an active-open forward proxy connection |
| cmdBind Command = 0x02 // establishes a passive-open forward proxy connection |
| |
| AuthMethodNotRequired AuthMethod = 0x00 // no authentication required |
| AuthMethodUsernamePassword AuthMethod = 0x02 // use username/password |
| AuthMethodNoAcceptableMethods AuthMethod = 0xff // no acceptable authentication methods |
| |
| StatusSucceeded Reply = 0x00 |
| ) |
| |
| // An Addr represents a SOCKS-specific address. |
| // Either Name or IP is used exclusively. |
| type Addr struct { |
| Name string // fully-qualified domain name |
| IP net.IP |
| Port int |
| } |
| |
| func (a *Addr) Network() string { return "socks" } |
| |
| func (a *Addr) String() string { |
| if a == nil { |
| return "<nil>" |
| } |
| port := strconv.Itoa(a.Port) |
| if a.IP == nil { |
| return net.JoinHostPort(a.Name, port) |
| } |
| return net.JoinHostPort(a.IP.String(), port) |
| } |
| |
| // A Conn represents a forward proxy connection. |
| type Conn struct { |
| net.Conn |
| |
| boundAddr net.Addr |
| } |
| |
| // BoundAddr returns the address assigned by the proxy server for |
| // connecting to the command target address from the proxy server. |
| func (c *Conn) BoundAddr() net.Addr { |
| if c == nil { |
| return nil |
| } |
| return c.boundAddr |
| } |
| |
| // A Dialer holds SOCKS-specific options. |
| type Dialer struct { |
| cmd Command // either CmdConnect or cmdBind |
| proxyNetwork string // network between a proxy server and a client |
| proxyAddress string // proxy server address |
| |
| // ProxyDial specifies the optional dial function for |
| // establishing the transport connection. |
| ProxyDial func(context.Context, string, string) (net.Conn, error) |
| |
| // AuthMethods specifies the list of request authentication |
| // methods. |
| // If empty, SOCKS client requests only AuthMethodNotRequired. |
| AuthMethods []AuthMethod |
| |
| // Authenticate specifies the optional authentication |
| // function. It must be non-nil when AuthMethods is not empty. |
| // It must return an error when the authentication is failed. |
| Authenticate func(context.Context, io.ReadWriter, AuthMethod) error |
| } |
| |
| // DialContext connects to the provided address on the provided |
| // network. |
| // |
| // The returned error value may be a net.OpError. When the Op field of |
| // net.OpError contains "socks", the Source field contains a proxy |
| // server address and the Addr field contains a command target |
| // address. |
| // |
| // See func Dial of the net package of standard library for a |
| // description of the network and address parameters. |
| func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { |
| if err := d.validateTarget(network, address); err != nil { |
| proxy, dst, _ := d.pathAddrs(address) |
| return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} |
| } |
| if ctx == nil { |
| proxy, dst, _ := d.pathAddrs(address) |
| return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")} |
| } |
| var err error |
| var c net.Conn |
| if d.ProxyDial != nil { |
| c, err = d.ProxyDial(ctx, d.proxyNetwork, d.proxyAddress) |
| } else { |
| var dd net.Dialer |
| c, err = dd.DialContext(ctx, d.proxyNetwork, d.proxyAddress) |
| } |
| if err != nil { |
| proxy, dst, _ := d.pathAddrs(address) |
| return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} |
| } |
| a, err := d.connect(ctx, c, address) |
| if err != nil { |
| c.Close() |
| proxy, dst, _ := d.pathAddrs(address) |
| return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} |
| } |
| return &Conn{Conn: c, boundAddr: a}, nil |
| } |
| |
| // DialWithConn initiates a connection from SOCKS server to the target |
| // network and address using the connection c that is already |
| // connected to the SOCKS server. |
| // |
| // It returns the connection's local address assigned by the SOCKS |
| // server. |
| func (d *Dialer) DialWithConn(ctx context.Context, c net.Conn, network, address string) (net.Addr, error) { |
| if err := d.validateTarget(network, address); err != nil { |
| proxy, dst, _ := d.pathAddrs(address) |
| return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} |
| } |
| if ctx == nil { |
| proxy, dst, _ := d.pathAddrs(address) |
| return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")} |
| } |
| a, err := d.connect(ctx, c, address) |
| if err != nil { |
| proxy, dst, _ := d.pathAddrs(address) |
| return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} |
| } |
| return a, nil |
| } |
| |
| // Dial connects to the provided address on the provided network. |
| // |
| // Unlike DialContext, it returns a raw transport connection instead |
| // of a forward proxy connection. |
| // |
| // Deprecated: Use DialContext or DialWithConn instead. |
| func (d *Dialer) Dial(network, address string) (net.Conn, error) { |
| if err := d.validateTarget(network, address); err != nil { |
| proxy, dst, _ := d.pathAddrs(address) |
| return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} |
| } |
| var err error |
| var c net.Conn |
| if d.ProxyDial != nil { |
| c, err = d.ProxyDial(context.Background(), d.proxyNetwork, d.proxyAddress) |
| } else { |
| c, err = net.Dial(d.proxyNetwork, d.proxyAddress) |
| } |
| if err != nil { |
| proxy, dst, _ := d.pathAddrs(address) |
| return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err} |
| } |
| if _, err := d.DialWithConn(context.Background(), c, network, address); err != nil { |
| c.Close() |
| return nil, err |
| } |
| return c, nil |
| } |
| |
| func (d *Dialer) validateTarget(network, address string) error { |
| switch network { |
| case "tcp", "tcp6", "tcp4": |
| default: |
| return errors.New("network not implemented") |
| } |
| switch d.cmd { |
| case CmdConnect, cmdBind: |
| default: |
| return errors.New("command not implemented") |
| } |
| return nil |
| } |
| |
| func (d *Dialer) pathAddrs(address string) (proxy, dst net.Addr, err error) { |
| for i, s := range []string{d.proxyAddress, address} { |
| host, port, err := splitHostPort(s) |
| if err != nil { |
| return nil, nil, err |
| } |
| a := &Addr{Port: port} |
| a.IP = net.ParseIP(host) |
| if a.IP == nil { |
| a.Name = host |
| } |
| if i == 0 { |
| proxy = a |
| } else { |
| dst = a |
| } |
| } |
| return |
| } |
| |
| // NewDialer returns a new Dialer that dials through the provided |
| // proxy server's network and address. |
| func NewDialer(network, address string) *Dialer { |
| return &Dialer{proxyNetwork: network, proxyAddress: address, cmd: CmdConnect} |
| } |
| |
| const ( |
| authUsernamePasswordVersion = 0x01 |
| authStatusSucceeded = 0x00 |
| ) |
| |
| // UsernamePassword are the credentials for the username/password |
| // authentication method. |
| type UsernamePassword struct { |
| Username string |
| Password string |
| } |
| |
| // Authenticate authenticates a pair of username and password with the |
| // proxy server. |
| func (up *UsernamePassword) Authenticate(ctx context.Context, rw io.ReadWriter, auth AuthMethod) error { |
| switch auth { |
| case AuthMethodNotRequired: |
| return nil |
| case AuthMethodUsernamePassword: |
| if len(up.Username) == 0 || len(up.Username) > 255 || len(up.Password) == 0 || len(up.Password) > 255 { |
| return errors.New("invalid username/password") |
| } |
| b := []byte{authUsernamePasswordVersion} |
| b = append(b, byte(len(up.Username))) |
| b = append(b, up.Username...) |
| b = append(b, byte(len(up.Password))) |
| b = append(b, up.Password...) |
| // TODO(mikio): handle IO deadlines and cancelation if |
| // necessary |
| if _, err := rw.Write(b); err != nil { |
| return err |
| } |
| if _, err := io.ReadFull(rw, b[:2]); err != nil { |
| return err |
| } |
| if b[0] != authUsernamePasswordVersion { |
| return errors.New("invalid username/password version") |
| } |
| if b[1] != authStatusSucceeded { |
| return errors.New("username/password authentication failed") |
| } |
| return nil |
| } |
| return errors.New("unsupported authentication method " + strconv.Itoa(int(auth))) |
| } |