socks.go raw

   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) > 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 cancellation 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  }
 318