mirror of
https://github.com/autc04/Retro68.git
synced 2024-11-03 07:07:20 +00:00
474 lines
13 KiB
Go
474 lines
13 KiB
Go
// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.
|
|
//go:generate bundle -o socks_bundle.go -dst net/http -prefix socks -underscore golang.org/x/net/internal/socks
|
|
|
|
// 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 http
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
socksnoDeadline = time.Time{}
|
|
socksaLongTimeAgo = time.Unix(1, 0)
|
|
)
|
|
|
|
func (d *socksDialer) connect(ctx context.Context, c net.Conn, address string) (_ net.Addr, ctxErr error) {
|
|
host, port, err := sockssplitHostPort(address)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if deadline, ok := ctx.Deadline(); ok && !deadline.IsZero() {
|
|
c.SetDeadline(deadline)
|
|
defer c.SetDeadline(socksnoDeadline)
|
|
}
|
|
if ctx != context.Background() {
|
|
errCh := make(chan error, 1)
|
|
done := make(chan struct{})
|
|
defer func() {
|
|
close(done)
|
|
if ctxErr == nil {
|
|
ctxErr = <-errCh
|
|
}
|
|
}()
|
|
go func() {
|
|
select {
|
|
case <-ctx.Done():
|
|
c.SetDeadline(socksaLongTimeAgo)
|
|
errCh <- ctx.Err()
|
|
case <-done:
|
|
errCh <- nil
|
|
}
|
|
}()
|
|
}
|
|
|
|
b := make([]byte, 0, 6+len(host)) // the size here is just an estimate
|
|
b = append(b, socksVersion5)
|
|
if len(d.AuthMethods) == 0 || d.Authenticate == nil {
|
|
b = append(b, 1, byte(socksAuthMethodNotRequired))
|
|
} else {
|
|
ams := d.AuthMethods
|
|
if len(ams) > 255 {
|
|
return nil, errors.New("too many authentication methods")
|
|
}
|
|
b = append(b, byte(len(ams)))
|
|
for _, am := range ams {
|
|
b = append(b, byte(am))
|
|
}
|
|
}
|
|
if _, ctxErr = c.Write(b); ctxErr != nil {
|
|
return
|
|
}
|
|
|
|
if _, ctxErr = io.ReadFull(c, b[:2]); ctxErr != nil {
|
|
return
|
|
}
|
|
if b[0] != socksVersion5 {
|
|
return nil, errors.New("unexpected protocol version " + strconv.Itoa(int(b[0])))
|
|
}
|
|
am := socksAuthMethod(b[1])
|
|
if am == socksAuthMethodNoAcceptableMethods {
|
|
return nil, errors.New("no acceptable authentication methods")
|
|
}
|
|
if d.Authenticate != nil {
|
|
if ctxErr = d.Authenticate(ctx, c, am); ctxErr != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
b = b[:0]
|
|
b = append(b, socksVersion5, byte(d.cmd), 0)
|
|
if ip := net.ParseIP(host); ip != nil {
|
|
if ip4 := ip.To4(); ip4 != nil {
|
|
b = append(b, socksAddrTypeIPv4)
|
|
b = append(b, ip4...)
|
|
} else if ip6 := ip.To16(); ip6 != nil {
|
|
b = append(b, socksAddrTypeIPv6)
|
|
b = append(b, ip6...)
|
|
} else {
|
|
return nil, errors.New("unknown address type")
|
|
}
|
|
} else {
|
|
if len(host) > 255 {
|
|
return nil, errors.New("FQDN too long")
|
|
}
|
|
b = append(b, socksAddrTypeFQDN)
|
|
b = append(b, byte(len(host)))
|
|
b = append(b, host...)
|
|
}
|
|
b = append(b, byte(port>>8), byte(port))
|
|
if _, ctxErr = c.Write(b); ctxErr != nil {
|
|
return
|
|
}
|
|
|
|
if _, ctxErr = io.ReadFull(c, b[:4]); ctxErr != nil {
|
|
return
|
|
}
|
|
if b[0] != socksVersion5 {
|
|
return nil, errors.New("unexpected protocol version " + strconv.Itoa(int(b[0])))
|
|
}
|
|
if cmdErr := socksReply(b[1]); cmdErr != socksStatusSucceeded {
|
|
return nil, errors.New("unknown error " + cmdErr.String())
|
|
}
|
|
if b[2] != 0 {
|
|
return nil, errors.New("non-zero reserved field")
|
|
}
|
|
l := 2
|
|
var a socksAddr
|
|
switch b[3] {
|
|
case socksAddrTypeIPv4:
|
|
l += net.IPv4len
|
|
a.IP = make(net.IP, net.IPv4len)
|
|
case socksAddrTypeIPv6:
|
|
l += net.IPv6len
|
|
a.IP = make(net.IP, net.IPv6len)
|
|
case socksAddrTypeFQDN:
|
|
if _, err := io.ReadFull(c, b[:1]); err != nil {
|
|
return nil, err
|
|
}
|
|
l += int(b[0])
|
|
default:
|
|
return nil, errors.New("unknown address type " + strconv.Itoa(int(b[3])))
|
|
}
|
|
if cap(b) < l {
|
|
b = make([]byte, l)
|
|
} else {
|
|
b = b[:l]
|
|
}
|
|
if _, ctxErr = io.ReadFull(c, b); ctxErr != nil {
|
|
return
|
|
}
|
|
if a.IP != nil {
|
|
copy(a.IP, b)
|
|
} else {
|
|
a.Name = string(b[:len(b)-2])
|
|
}
|
|
a.Port = int(b[len(b)-2])<<8 | int(b[len(b)-1])
|
|
return &a, nil
|
|
}
|
|
|
|
func sockssplitHostPort(address string) (string, int, error) {
|
|
host, port, err := net.SplitHostPort(address)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
portnum, err := strconv.Atoi(port)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
if 1 > portnum || portnum > 0xffff {
|
|
return "", 0, errors.New("port number out of range " + port)
|
|
}
|
|
return host, portnum, nil
|
|
}
|
|
|
|
// A Command represents a SOCKS command.
|
|
type socksCommand int
|
|
|
|
func (cmd socksCommand) String() string {
|
|
switch cmd {
|
|
case socksCmdConnect:
|
|
return "socks connect"
|
|
case sockscmdBind:
|
|
return "socks bind"
|
|
default:
|
|
return "socks " + strconv.Itoa(int(cmd))
|
|
}
|
|
}
|
|
|
|
// An AuthMethod represents a SOCKS authentication method.
|
|
type socksAuthMethod int
|
|
|
|
// A Reply represents a SOCKS command reply code.
|
|
type socksReply int
|
|
|
|
func (code socksReply) String() string {
|
|
switch code {
|
|
case socksStatusSucceeded:
|
|
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 (
|
|
socksVersion5 = 0x05
|
|
|
|
socksAddrTypeIPv4 = 0x01
|
|
socksAddrTypeFQDN = 0x03
|
|
socksAddrTypeIPv6 = 0x04
|
|
|
|
socksCmdConnect socksCommand = 0x01 // establishes an active-open forward proxy connection
|
|
sockscmdBind socksCommand = 0x02 // establishes a passive-open forward proxy connection
|
|
|
|
socksAuthMethodNotRequired socksAuthMethod = 0x00 // no authentication required
|
|
socksAuthMethodUsernamePassword socksAuthMethod = 0x02 // use username/password
|
|
socksAuthMethodNoAcceptableMethods socksAuthMethod = 0xff // no acceptable authentication methods
|
|
|
|
socksStatusSucceeded socksReply = 0x00
|
|
)
|
|
|
|
// An Addr represents a SOCKS-specific address.
|
|
// Either Name or IP is used exclusively.
|
|
type socksAddr struct {
|
|
Name string // fully-qualified domain name
|
|
IP net.IP
|
|
Port int
|
|
}
|
|
|
|
func (a *socksAddr) Network() string { return "socks" }
|
|
|
|
func (a *socksAddr) 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 socksConn 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 *socksConn) BoundAddr() net.Addr {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
return c.boundAddr
|
|
}
|
|
|
|
// A Dialer holds SOCKS-specific options.
|
|
type socksDialer struct {
|
|
cmd socksCommand // 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 authention
|
|
// methods.
|
|
// If empty, SOCKS client requests only AuthMethodNotRequired.
|
|
AuthMethods []socksAuthMethod
|
|
|
|
// 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, socksAuthMethod) 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 *socksDialer) 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 &socksConn{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 *socksDialer) 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 *socksDialer) 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 *socksDialer) validateTarget(network, address string) error {
|
|
switch network {
|
|
case "tcp", "tcp6", "tcp4":
|
|
default:
|
|
return errors.New("network not implemented")
|
|
}
|
|
switch d.cmd {
|
|
case socksCmdConnect, sockscmdBind:
|
|
default:
|
|
return errors.New("command not implemented")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *socksDialer) pathAddrs(address string) (proxy, dst net.Addr, err error) {
|
|
for i, s := range []string{d.proxyAddress, address} {
|
|
host, port, err := sockssplitHostPort(s)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
a := &socksAddr{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 socksNewDialer(network, address string) *socksDialer {
|
|
return &socksDialer{proxyNetwork: network, proxyAddress: address, cmd: socksCmdConnect}
|
|
}
|
|
|
|
const (
|
|
socksauthUsernamePasswordVersion = 0x01
|
|
socksauthStatusSucceeded = 0x00
|
|
)
|
|
|
|
// UsernamePassword are the credentials for the username/password
|
|
// authentication method.
|
|
type socksUsernamePassword struct {
|
|
Username string
|
|
Password string
|
|
}
|
|
|
|
// Authenticate authenticates a pair of username and password with the
|
|
// proxy server.
|
|
func (up *socksUsernamePassword) Authenticate(ctx context.Context, rw io.ReadWriter, auth socksAuthMethod) error {
|
|
switch auth {
|
|
case socksAuthMethodNotRequired:
|
|
return nil
|
|
case socksAuthMethodUsernamePassword:
|
|
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{socksauthUsernamePasswordVersion}
|
|
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] != socksauthUsernamePasswordVersion {
|
|
return errors.New("invalid username/password version")
|
|
}
|
|
if b[1] != socksauthStatusSucceeded {
|
|
return errors.New("username/password authentication failed")
|
|
}
|
|
return nil
|
|
}
|
|
return errors.New("unsupported authentication method " + strconv.Itoa(int(auth)))
|
|
}
|