package http
import (
)
var (
socksnoDeadline = time.Time{}
socksaLongTimeAgo = time.Unix(1, 0)
)
func ( *socksDialer) ( context.Context, net.Conn, string) ( net.Addr, error) {
, , := sockssplitHostPort()
if != nil {
return nil,
}
if , := .Deadline(); && !.IsZero() {
.SetDeadline()
defer .SetDeadline(socksnoDeadline)
}
if != context.Background() {
:= make(chan error, 1)
:= make(chan struct{})
defer func() {
close()
if == nil {
= <-
}
}()
go func() {
select {
case <-.Done():
.SetDeadline(socksaLongTimeAgo)
<- .Err()
case <-:
<- nil
}
}()
}
:= make([]byte, 0, 6+len())
= append(, socksVersion5)
if len(.AuthMethods) == 0 || .Authenticate == nil {
= append(, 1, byte(socksAuthMethodNotRequired))
} else {
:= .AuthMethods
if len() > 255 {
return nil, errors.New("too many authentication methods")
}
= append(, byte(len()))
for , := range {
= append(, byte())
}
}
if _, = .Write(); != nil {
return
}
if _, = io.ReadFull(, [:2]); != nil {
return
}
if [0] != socksVersion5 {
return nil, errors.New("unexpected protocol version " + strconv.Itoa(int([0])))
}
:= socksAuthMethod([1])
if == socksAuthMethodNoAcceptableMethods {
return nil, errors.New("no acceptable authentication methods")
}
if .Authenticate != nil {
if = .Authenticate(, , ); != nil {
return
}
}
= [:0]
= append(, socksVersion5, byte(.cmd), 0)
if := net.ParseIP(); != nil {
if := .To4(); != nil {
= append(, socksAddrTypeIPv4)
= append(, ...)
} else if := .To16(); != nil {
= append(, socksAddrTypeIPv6)
= append(, ...)
} else {
return nil, errors.New("unknown address type")
}
} else {
if len() > 255 {
return nil, errors.New("FQDN too long")
}
= append(, socksAddrTypeFQDN)
= append(, byte(len()))
= append(, ...)
}
= append(, byte(>>8), byte())
if _, = .Write(); != nil {
return
}
if _, = io.ReadFull(, [:4]); != nil {
return
}
if [0] != socksVersion5 {
return nil, errors.New("unexpected protocol version " + strconv.Itoa(int([0])))
}
if := socksReply([1]); != socksStatusSucceeded {
return nil, errors.New("unknown error " + .String())
}
if [2] != 0 {
return nil, errors.New("non-zero reserved field")
}
:= 2
var socksAddr
switch [3] {
case socksAddrTypeIPv4:
+= net.IPv4len
.IP = make(net.IP, net.IPv4len)
case socksAddrTypeIPv6:
+= net.IPv6len
.IP = make(net.IP, net.IPv6len)
case socksAddrTypeFQDN:
if , := io.ReadFull(, [:1]); != nil {
return nil,
}
+= int([0])
default:
return nil, errors.New("unknown address type " + strconv.Itoa(int([3])))
}
if cap() < {
= make([]byte, )
} else {
= [:]
}
if _, = io.ReadFull(, ); != nil {
return
}
if .IP != nil {
copy(.IP, )
} else {
.Name = string([:len()-2])
}
.Port = int([len()-2])<<8 | int([len()-1])
return &, nil
}
func ( string) (string, int, error) {
, , := net.SplitHostPort()
if != nil {
return "", 0,
}
, := strconv.Atoi()
if != nil {
return "", 0,
}
if 1 > || > 0xffff {
return "", 0, errors.New("port number out of range " + )
}
return , , nil
}
type socksCommand int
func ( socksCommand) () string {
switch {
case socksCmdConnect:
return "socks connect"
case sockscmdBind:
return "socks bind"
default:
return "socks " + strconv.Itoa(int())
}
}
type socksAuthMethod int
type socksReply int
func ( socksReply) () string {
switch {
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())
}
}
const (
socksVersion5 = 0x05
socksAddrTypeIPv4 = 0x01
socksAddrTypeFQDN = 0x03
socksAddrTypeIPv6 = 0x04
socksCmdConnect socksCommand = 0x01
sockscmdBind socksCommand = 0x02
socksAuthMethodNotRequired socksAuthMethod = 0x00
socksAuthMethodUsernamePassword socksAuthMethod = 0x02
socksAuthMethodNoAcceptableMethods socksAuthMethod = 0xff
socksStatusSucceeded socksReply = 0x00
)
type socksAddr struct {
Name string
IP net.IP
Port int
}
func ( *socksAddr) () string { return "socks" }
func ( *socksAddr) () string {
if == nil {
return "<nil>"
}
:= strconv.Itoa(.Port)
if .IP == nil {
return net.JoinHostPort(.Name, )
}
return net.JoinHostPort(.IP.String(), )
}
type socksConn struct {
net.Conn
boundAddr net.Addr
}
func ( *socksConn) () net.Addr {
if == nil {
return nil
}
return .boundAddr
}
type socksDialer struct {
cmd socksCommand
proxyNetwork string
proxyAddress string
ProxyDial func(context.Context, string, string) (net.Conn, error)
AuthMethods []socksAuthMethod
Authenticate func(context.Context, io.ReadWriter, socksAuthMethod) error
}
func ( *socksDialer) ( 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 &socksConn{Conn: , boundAddr: }, nil
}
func ( *socksDialer) ( 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
}
func ( *socksDialer) (, 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 ( *socksDialer) (, string) error {
switch {
case "tcp", "tcp6", "tcp4":
default:
return errors.New("network not implemented")
}
switch .cmd {
case socksCmdConnect, sockscmdBind:
default:
return errors.New("command not implemented")
}
return nil
}
func ( *socksDialer) ( string) (, net.Addr, error) {
for , := range []string{.proxyAddress, } {
, , := sockssplitHostPort()
if != nil {
return nil, nil,
}
:= &socksAddr{Port: }
.IP = net.ParseIP()
if .IP == nil {
.Name =
}
if == 0 {
=
} else {
=
}
}
return
}
func (, string) *socksDialer {
return &socksDialer{proxyNetwork: , proxyAddress: , cmd: socksCmdConnect}
}
const (
socksauthUsernamePasswordVersion = 0x01
socksauthStatusSucceeded = 0x00
)
type socksUsernamePassword struct {
Username string
Password string
}
func ( *socksUsernamePassword) ( context.Context, io.ReadWriter, socksAuthMethod) error {
switch {
case socksAuthMethodNotRequired:
return nil
case socksAuthMethodUsernamePassword:
if len(.Username) == 0 || len(.Username) > 255 || len(.Password) == 0 || len(.Password) > 255 {
return errors.New("invalid username/password")
}
:= []byte{socksauthUsernamePasswordVersion}
= append(, byte(len(.Username)))
= append(, .Username...)
= append(, byte(len(.Password)))
= append(, .Password...)
if , := .Write(); != nil {
return
}
if , := io.ReadFull(, [:2]); != nil {
return
}
if [0] != socksauthUsernamePasswordVersion {
return errors.New("invalid username/password version")
}
if [1] != socksauthStatusSucceeded {
return errors.New("username/password authentication failed")
}
return nil
}
return errors.New("unsupported authentication method " + strconv.Itoa(int()))
}