123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- // 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)))
- }
|