// 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 ( ) // A Command represents a SOCKS command. type Command int func ( Command) () string { switch { case CmdConnect: return "socks connect" case cmdBind: return "socks bind" default: return "socks " + strconv.Itoa(int()) } } // An AuthMethod represents a SOCKS authentication method. type AuthMethod int // A Reply represents a SOCKS command reply code. type Reply int func ( Reply) () string { switch { 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()) } } // 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 ( *Addr) () string { return "socks" } func ( *Addr) () string { if == nil { return "<nil>" } := strconv.Itoa(.Port) if .IP == nil { return net.JoinHostPort(.Name, ) } return net.JoinHostPort(.IP.String(), ) } // 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 ( *Conn) () net.Addr { if == nil { return nil } return .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 ( *Dialer) ( context.Context, , string) (net.Conn, error) { if := .validateTarget(, ); != nil { , , := .pathAddrs() return nil, &net.OpError{Op: .cmd.String(), Net: , Source: , Addr: , Err: } } if == nil { , , := .pathAddrs() return nil, &net.OpError{Op: .cmd.String(), Net: , Source: , Addr: , Err: errors.New("nil context")} } var error var net.Conn if .ProxyDial != nil { , = .ProxyDial(, .proxyNetwork, .proxyAddress) } else { var net.Dialer , = .DialContext(, .proxyNetwork, .proxyAddress) } if != nil { , , := .pathAddrs() return nil, &net.OpError{Op: .cmd.String(), Net: , Source: , Addr: , Err: } } , := .connect(, , ) if != nil { .Close() , , := .pathAddrs() return nil, &net.OpError{Op: .cmd.String(), Net: , Source: , Addr: , Err: } } return &Conn{Conn: , boundAddr: }, 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 ( *Dialer) ( context.Context, net.Conn, , string) (net.Addr, error) { if := .validateTarget(, ); != nil { , , := .pathAddrs() return nil, &net.OpError{Op: .cmd.String(), Net: , Source: , Addr: , Err: } } if == nil { , , := .pathAddrs() return nil, &net.OpError{Op: .cmd.String(), Net: , Source: , Addr: , Err: errors.New("nil context")} } , := .connect(, , ) if != nil { , , := .pathAddrs() return nil, &net.OpError{Op: .cmd.String(), Net: , Source: , Addr: , Err: } } return , 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 ( *Dialer) (, string) (net.Conn, error) { if := .validateTarget(, ); != nil { , , := .pathAddrs() return nil, &net.OpError{Op: .cmd.String(), Net: , Source: , Addr: , Err: } } var error var net.Conn if .ProxyDial != nil { , = .ProxyDial(context.Background(), .proxyNetwork, .proxyAddress) } else { , = net.Dial(.proxyNetwork, .proxyAddress) } if != nil { , , := .pathAddrs() return nil, &net.OpError{Op: .cmd.String(), Net: , Source: , Addr: , Err: } } if , := .DialWithConn(context.Background(), , , ); != nil { .Close() return nil, } return , nil } func ( *Dialer) (, string) error { switch { case "tcp", "tcp6", "tcp4": default: return errors.New("network not implemented") } switch .cmd { case CmdConnect, cmdBind: default: return errors.New("command not implemented") } return nil } func ( *Dialer) ( string) (, net.Addr, error) { for , := range []string{.proxyAddress, } { , , := splitHostPort() if != nil { return nil, nil, } := &Addr{Port: } .IP = net.ParseIP() if .IP == nil { .Name = } if == 0 { = } else { = } } return } // NewDialer returns a new Dialer that dials through the provided // proxy server's network and address. func (, string) *Dialer { return &Dialer{proxyNetwork: , proxyAddress: , 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 ( *UsernamePassword) ( context.Context, io.ReadWriter, AuthMethod) error { switch { case AuthMethodNotRequired: return nil case AuthMethodUsernamePassword: if len(.Username) == 0 || len(.Username) > 255 || len(.Password) > 255 { return errors.New("invalid username/password") } := []byte{authUsernamePasswordVersion} = append(, byte(len(.Username))) = append(, .Username...) = append(, byte(len(.Password))) = append(, .Password...) // TODO(mikio): handle IO deadlines and cancelation if // necessary if , := .Write(); != nil { return } if , := io.ReadFull(, [:2]); != nil { return } if [0] != authUsernamePasswordVersion { return errors.New("invalid username/password version") } if [1] != authStatusSucceeded { return errors.New("username/password authentication failed") } return nil } return errors.New("unsupported authentication method " + strconv.Itoa(int())) }