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 ))
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
}
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 ))
}
}
type socksAuthMethod int
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 ))
}
}
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 (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 )
}
type socksConn struct {
net .Conn
boundAddr net .Addr
}
func (c *socksConn ) BoundAddr () net .Addr {
if c == nil {
return nil
}
return c .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 (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
}
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
}
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
}
func socksNewDialer(network , address string ) *socksDialer {
return &socksDialer {proxyNetwork : network , proxyAddress : address , cmd : socksCmdConnect }
}
const (
socksauthUsernamePasswordVersion = 0x01
socksauthStatusSucceeded = 0x00
)
type socksUsernamePassword struct {
Username string
Password string
}
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 ) > 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 ...)
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 )))
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .