package socks
import (
"context"
"errors"
"io"
"net"
"strconv"
)
type Command int
func (cmd Command ) String () string {
switch cmd {
case CmdConnect :
return "socks connect"
case cmdBind :
return "socks bind"
default :
return "socks " + strconv .Itoa (int (cmd ))
}
}
type AuthMethod int
type Reply int
func (code Reply ) String () string {
switch code {
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 (code ))
}
}
const (
Version5 = 0x05
AddrTypeIPv4 = 0x01
AddrTypeFQDN = 0x03
AddrTypeIPv6 = 0x04
CmdConnect Command = 0x01
cmdBind Command = 0x02
AuthMethodNotRequired AuthMethod = 0x00
AuthMethodUsernamePassword AuthMethod = 0x02
AuthMethodNoAcceptableMethods AuthMethod = 0xff
StatusSucceeded Reply = 0x00
)
type Addr struct {
Name string
IP net .IP
Port int
}
func (a *Addr ) Network () string { return "socks" }
func (a *Addr ) 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 Conn struct {
net .Conn
boundAddr net .Addr
}
func (c *Conn ) BoundAddr () net .Addr {
if c == nil {
return nil
}
return c .boundAddr
}
type Dialer struct {
cmd Command
proxyNetwork string
proxyAddress string
ProxyDial func (context .Context , string , string ) (net .Conn , error )
AuthMethods []AuthMethod
Authenticate func (context .Context , io .ReadWriter , AuthMethod ) error
}
func (d *Dialer ) 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 &Conn {Conn : c , boundAddr : a }, nil
}
func (d *Dialer ) 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 *Dialer ) 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 *Dialer ) validateTarget (network , address string ) error {
switch network {
case "tcp" , "tcp6" , "tcp4" :
default :
return errors .New ("network not implemented" )
}
switch d .cmd {
case CmdConnect , cmdBind :
default :
return errors .New ("command not implemented" )
}
return nil
}
func (d *Dialer ) pathAddrs (address string ) (proxy , dst net .Addr , err error ) {
for i , s := range []string {d .proxyAddress , address } {
host , port , err := splitHostPort (s )
if err != nil {
return nil , nil , err
}
a := &Addr {Port : port }
a .IP = net .ParseIP (host )
if a .IP == nil {
a .Name = host
}
if i == 0 {
proxy = a
} else {
dst = a
}
}
return
}
func NewDialer (network , address string ) *Dialer {
return &Dialer {proxyNetwork : network , proxyAddress : address , cmd : CmdConnect }
}
const (
authUsernamePasswordVersion = 0x01
authStatusSucceeded = 0x00
)
type UsernamePassword struct {
Username string
Password string
}
func (up *UsernamePassword ) Authenticate (ctx context .Context , rw io .ReadWriter , auth AuthMethod ) error {
switch auth {
case AuthMethodNotRequired :
return nil
case AuthMethodUsernamePassword :
if len (up .Username ) == 0 || len (up .Username ) > 255 || len (up .Password ) > 255 {
return errors .New ("invalid username/password" )
}
b := []byte {authUsernamePasswordVersion }
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 ] != authUsernamePasswordVersion {
return errors .New ("invalid username/password version" )
}
if b [1 ] != authStatusSucceeded {
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.6.7 . (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 @Go100and1 (reachable from the left QR code) to get the latest news of Golds .