cubefs
1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package socks provides a SOCKS version 5 client implementation.
6//
7// SOCKS protocol version 5 is defined in RFC 1928.
8// Username/Password authentication for SOCKS version 5 is defined in
9// RFC 1929.
10package socks11
12import (13"context"14"errors"15"io"16"net"17"strconv"18)
19
20// A Command represents a SOCKS command.
21type Command int22
23func (cmd Command) String() string {24switch cmd {25case CmdConnect:26return "socks connect"27case cmdBind:28return "socks bind"29default:30return "socks " + strconv.Itoa(int(cmd))31}32}
33
34// An AuthMethod represents a SOCKS authentication method.
35type AuthMethod int36
37// A Reply represents a SOCKS command reply code.
38type Reply int39
40func (code Reply) String() string {41switch code {42case StatusSucceeded:43return "succeeded"44case 0x01:45return "general SOCKS server failure"46case 0x02:47return "connection not allowed by ruleset"48case 0x03:49return "network unreachable"50case 0x04:51return "host unreachable"52case 0x05:53return "connection refused"54case 0x06:55return "TTL expired"56case 0x07:57return "command not supported"58case 0x08:59return "address type not supported"60default:61return "unknown code: " + strconv.Itoa(int(code))62}63}
64
65// Wire protocol constants.
66const (67Version5 = 0x0568
69AddrTypeIPv4 = 0x0170AddrTypeFQDN = 0x0371AddrTypeIPv6 = 0x0472
73CmdConnect Command = 0x01 // establishes an active-open forward proxy connection74cmdBind Command = 0x02 // establishes a passive-open forward proxy connection75
76AuthMethodNotRequired AuthMethod = 0x00 // no authentication required77AuthMethodUsernamePassword AuthMethod = 0x02 // use username/password78AuthMethodNoAcceptableMethods AuthMethod = 0xff // no acceptable authentication methods79
80StatusSucceeded Reply = 0x0081)
82
83// An Addr represents a SOCKS-specific address.
84// Either Name or IP is used exclusively.
85type Addr struct {86Name string // fully-qualified domain name87IP net.IP88Port int89}
90
91func (a *Addr) Network() string { return "socks" }92
93func (a *Addr) String() string {94if a == nil {95return "<nil>"96}97port := strconv.Itoa(a.Port)98if a.IP == nil {99return net.JoinHostPort(a.Name, port)100}101return net.JoinHostPort(a.IP.String(), port)102}
103
104// A Conn represents a forward proxy connection.
105type Conn struct {106net.Conn107
108boundAddr net.Addr109}
110
111// BoundAddr returns the address assigned by the proxy server for
112// connecting to the command target address from the proxy server.
113func (c *Conn) BoundAddr() net.Addr {114if c == nil {115return nil116}117return c.boundAddr118}
119
120// A Dialer holds SOCKS-specific options.
121type Dialer struct {122cmd Command // either CmdConnect or cmdBind123proxyNetwork string // network between a proxy server and a client124proxyAddress string // proxy server address125
126// ProxyDial specifies the optional dial function for127// establishing the transport connection.128ProxyDial func(context.Context, string, string) (net.Conn, error)129
130// AuthMethods specifies the list of request authentication131// methods.132// If empty, SOCKS client requests only AuthMethodNotRequired.133AuthMethods []AuthMethod134
135// Authenticate specifies the optional authentication136// function. It must be non-nil when AuthMethods is not empty.137// It must return an error when the authentication is failed.138Authenticate func(context.Context, io.ReadWriter, AuthMethod) error139}
140
141// DialContext connects to the provided address on the provided
142// network.
143//
144// The returned error value may be a net.OpError. When the Op field of
145// net.OpError contains "socks", the Source field contains a proxy
146// server address and the Addr field contains a command target
147// address.
148//
149// See func Dial of the net package of standard library for a
150// description of the network and address parameters.
151func (d *Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {152if err := d.validateTarget(network, address); err != nil {153proxy, dst, _ := d.pathAddrs(address)154return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}155}156if ctx == nil {157proxy, dst, _ := d.pathAddrs(address)158return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")}159}160var err error161var c net.Conn162if d.ProxyDial != nil {163c, err = d.ProxyDial(ctx, d.proxyNetwork, d.proxyAddress)164} else {165var dd net.Dialer166c, err = dd.DialContext(ctx, d.proxyNetwork, d.proxyAddress)167}168if err != nil {169proxy, dst, _ := d.pathAddrs(address)170return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}171}172a, err := d.connect(ctx, c, address)173if err != nil {174c.Close()175proxy, dst, _ := d.pathAddrs(address)176return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}177}178return &Conn{Conn: c, boundAddr: a}, nil179}
180
181// DialWithConn initiates a connection from SOCKS server to the target
182// network and address using the connection c that is already
183// connected to the SOCKS server.
184//
185// It returns the connection's local address assigned by the SOCKS
186// server.
187func (d *Dialer) DialWithConn(ctx context.Context, c net.Conn, network, address string) (net.Addr, error) {188if err := d.validateTarget(network, address); err != nil {189proxy, dst, _ := d.pathAddrs(address)190return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}191}192if ctx == nil {193proxy, dst, _ := d.pathAddrs(address)194return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: errors.New("nil context")}195}196a, err := d.connect(ctx, c, address)197if err != nil {198proxy, dst, _ := d.pathAddrs(address)199return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}200}201return a, nil202}
203
204// Dial connects to the provided address on the provided network.
205//
206// Unlike DialContext, it returns a raw transport connection instead
207// of a forward proxy connection.
208//
209// Deprecated: Use DialContext or DialWithConn instead.
210func (d *Dialer) Dial(network, address string) (net.Conn, error) {211if err := d.validateTarget(network, address); err != nil {212proxy, dst, _ := d.pathAddrs(address)213return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}214}215var err error216var c net.Conn217if d.ProxyDial != nil {218c, err = d.ProxyDial(context.Background(), d.proxyNetwork, d.proxyAddress)219} else {220c, err = net.Dial(d.proxyNetwork, d.proxyAddress)221}222if err != nil {223proxy, dst, _ := d.pathAddrs(address)224return nil, &net.OpError{Op: d.cmd.String(), Net: network, Source: proxy, Addr: dst, Err: err}225}226if _, err := d.DialWithConn(context.Background(), c, network, address); err != nil {227c.Close()228return nil, err229}230return c, nil231}
232
233func (d *Dialer) validateTarget(network, address string) error {234switch network {235case "tcp", "tcp6", "tcp4":236default:237return errors.New("network not implemented")238}239switch d.cmd {240case CmdConnect, cmdBind:241default:242return errors.New("command not implemented")243}244return nil245}
246
247func (d *Dialer) pathAddrs(address string) (proxy, dst net.Addr, err error) {248for i, s := range []string{d.proxyAddress, address} {249host, port, err := splitHostPort(s)250if err != nil {251return nil, nil, err252}253a := &Addr{Port: port}254a.IP = net.ParseIP(host)255if a.IP == nil {256a.Name = host257}258if i == 0 {259proxy = a260} else {261dst = a262}263}264return265}
266
267// NewDialer returns a new Dialer that dials through the provided
268// proxy server's network and address.
269func NewDialer(network, address string) *Dialer {270return &Dialer{proxyNetwork: network, proxyAddress: address, cmd: CmdConnect}271}
272
273const (274authUsernamePasswordVersion = 0x01275authStatusSucceeded = 0x00276)
277
278// UsernamePassword are the credentials for the username/password
279// authentication method.
280type UsernamePassword struct {281Username string282Password string283}
284
285// Authenticate authenticates a pair of username and password with the
286// proxy server.
287func (up *UsernamePassword) Authenticate(ctx context.Context, rw io.ReadWriter, auth AuthMethod) error {288switch auth {289case AuthMethodNotRequired:290return nil291case AuthMethodUsernamePassword:292if len(up.Username) == 0 || len(up.Username) > 255 || len(up.Password) == 0 || len(up.Password) > 255 {293return errors.New("invalid username/password")294}295b := []byte{authUsernamePasswordVersion}296b = append(b, byte(len(up.Username)))297b = append(b, up.Username...)298b = append(b, byte(len(up.Password)))299b = append(b, up.Password...)300// TODO(mikio): handle IO deadlines and cancelation if301// necessary302if _, err := rw.Write(b); err != nil {303return err304}305if _, err := io.ReadFull(rw, b[:2]); err != nil {306return err307}308if b[0] != authUsernamePasswordVersion {309return errors.New("invalid username/password version")310}311if b[1] != authStatusSucceeded {312return errors.New("username/password authentication failed")313}314return nil315}316return errors.New("unsupported authentication method " + strconv.Itoa(int(auth)))317}
318