|
@@ -0,0 +1,317 @@
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+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) == 0 || 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)))
|
|
|
+}
|