golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/internal/socks/socks.go (about)

     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 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  }