From 3008c68d16f62a554b7c0e9549220422090357ac Mon Sep 17 00:00:00 2001 From: John Chi Date: Thu, 8 Nov 2018 16:27:10 +0800 Subject: [PATCH] Add SOCKS5 Protocol Support (#1455) * feat(p2p connection): add SOCKS5 protocol support * refactor(p2p connection): code refactoring --- cmd/bytomd/commands/run_node.go | 3 + config/config.go | 6 + p2p/netaddress.go | 10 + p2p/peer.go | 21 +- vendor/github.com/btcsuite/go-socks/LICENSE | 25 ++ vendor/github.com/btcsuite/go-socks/README.md | 9 + vendor/github.com/btcsuite/go-socks/socks/addr.go | 21 ++ vendor/github.com/btcsuite/go-socks/socks/conn.go | 54 +++++ vendor/github.com/btcsuite/go-socks/socks/dial.go | 269 ++++++++++++++++++++++ 9 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/btcsuite/go-socks/LICENSE create mode 100644 vendor/github.com/btcsuite/go-socks/README.md create mode 100644 vendor/github.com/btcsuite/go-socks/socks/addr.go create mode 100644 vendor/github.com/btcsuite/go-socks/socks/conn.go create mode 100644 vendor/github.com/btcsuite/go-socks/socks/dial.go diff --git a/cmd/bytomd/commands/run_node.go b/cmd/bytomd/commands/run_node.go index d73f6b1e..48c7dc63 100644 --- a/cmd/bytomd/commands/run_node.go +++ b/cmd/bytomd/commands/run_node.go @@ -40,6 +40,9 @@ func init() { runNodeCmd.Flags().Int("p2p.max_num_peers", config.P2P.MaxNumPeers, "Set max num peers") runNodeCmd.Flags().Int("p2p.handshake_timeout", config.P2P.HandshakeTimeout, "Set handshake timeout") runNodeCmd.Flags().Int("p2p.dial_timeout", config.P2P.DialTimeout, "Set dial timeout") + runNodeCmd.Flags().String("p2p.proxy_address", config.P2P.ProxyAddress, "Connect via SOCKS5 proxy (eg. 127.0.0.1:1086)") + runNodeCmd.Flags().String("p2p.proxy_username", config.P2P.ProxyUsername, "Username for proxy server") + runNodeCmd.Flags().String("p2p.proxy_password", config.P2P.ProxyPassword, "Password for proxy server") // log flags runNodeCmd.Flags().String("log_file", config.LogFile, "Log output file") diff --git a/config/config.go b/config/config.go index f5f478a7..4e8f29ff 100644 --- a/config/config.go +++ b/config/config.go @@ -111,6 +111,9 @@ type P2PConfig struct { MaxNumPeers int `mapstructure:"max_num_peers"` HandshakeTimeout int `mapstructure:"handshake_timeout"` DialTimeout int `mapstructure:"dial_timeout"` + ProxyAddress string `mapstructure:"proxy_address"` + ProxyUsername string `mapstructure:"proxy_username"` + ProxyPassword string `mapstructure:"proxy_password"` } // Default configurable p2p parameters. @@ -121,6 +124,9 @@ func DefaultP2PConfig() *P2PConfig { MaxNumPeers: 50, HandshakeTimeout: 30, DialTimeout: 3, + ProxyAddress: "", + ProxyUsername: "", + ProxyPassword: "", } } diff --git a/p2p/netaddress.go b/p2p/netaddress.go index ce3ebe6a..87a95356 100644 --- a/p2p/netaddress.go +++ b/p2p/netaddress.go @@ -12,6 +12,7 @@ import ( "time" cmn "github.com/tendermint/tmlibs/common" + "github.com/btcsuite/go-socks/socks" ) // NetAddress defines information about a peer on the network @@ -142,6 +143,15 @@ func (na *NetAddress) DialTimeout(timeout time.Duration) (net.Conn, error) { return conn, nil } +// DialTimeoutWithProxy calls socks.Proxy.DialTimeout on the address. +func (na *NetAddress) DialTimeoutWithProxy(proxy *socks.Proxy, timeout time.Duration) (net.Conn, error) { + conn, err := proxy.DialTimeout("tcp", na.DialString(), timeout) + if err != nil { + return nil, err + } + return conn, nil +} + // Routable returns true if the address is routable. func (na *NetAddress) Routable() bool { // TODO(oga) bitcoind doesn't include RFC3849 here, but should we? diff --git a/p2p/peer.go b/p2p/peer.go index 33beb414..963c56b0 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -16,6 +16,7 @@ import ( cfg "github.com/bytom/config" "github.com/bytom/consensus" "github.com/bytom/p2p/connection" + "github.com/btcsuite/go-socks/socks" ) // peerConn contains the raw connection and its config. @@ -29,6 +30,9 @@ type peerConn struct { type PeerConfig struct { HandshakeTimeout time.Duration `mapstructure:"handshake_timeout"` // times are in seconds DialTimeout time.Duration `mapstructure:"dial_timeout"` + ProxyAddress string `mapstructure:"proxy_address"` + ProxyUsername string `mapstructure:"proxy_username"` + ProxyPassword string `mapstructure:"proxy_password"` MConfig *connection.MConnConfig `mapstructure:"connection"` } @@ -37,6 +41,9 @@ func DefaultPeerConfig(config *cfg.P2PConfig) *PeerConfig { return &PeerConfig{ HandshakeTimeout: time.Duration(config.HandshakeTimeout) * time.Second, // * time.Second, DialTimeout: time.Duration(config.DialTimeout) * time.Second, // * time.Second, + ProxyAddress: config.ProxyAddress, + ProxyUsername: config.ProxyUsername, + ProxyPassword: config.ProxyPassword, MConfig: connection.DefaultMConnConfig(), } } @@ -240,7 +247,19 @@ func createMConnection(conn net.Conn, p *Peer, reactorsByCh map[byte]Reactor, ch } func dial(addr *NetAddress, config *PeerConfig) (net.Conn, error) { - conn, err := addr.DialTimeout(config.DialTimeout) + var conn net.Conn + var err error + if config.ProxyAddress == "" { + conn, err = addr.DialTimeout(config.DialTimeout) + } else { + proxy := &socks.Proxy{ + Addr: config.ProxyAddress, + Username: config.ProxyUsername, + Password: config.ProxyPassword, + TorIsolation: false, + } + conn, err = addr.DialTimeoutWithProxy(proxy, config.DialTimeout) + } if err != nil { return nil, err } diff --git a/vendor/github.com/btcsuite/go-socks/LICENSE b/vendor/github.com/btcsuite/go-socks/LICENSE new file mode 100644 index 00000000..3be2b5ea --- /dev/null +++ b/vendor/github.com/btcsuite/go-socks/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2012, Samuel Stauffer +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of the author nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/btcsuite/go-socks/README.md b/vendor/github.com/btcsuite/go-socks/README.md new file mode 100644 index 00000000..37820edc --- /dev/null +++ b/vendor/github.com/btcsuite/go-socks/README.md @@ -0,0 +1,9 @@ +SOCKS5 Proxy Package for Go +=========================== + +Documentation: + +License +------- + +3-clause BSD. See LICENSE file. diff --git a/vendor/github.com/btcsuite/go-socks/socks/addr.go b/vendor/github.com/btcsuite/go-socks/socks/addr.go new file mode 100644 index 00000000..d38e35ba --- /dev/null +++ b/vendor/github.com/btcsuite/go-socks/socks/addr.go @@ -0,0 +1,21 @@ +// Copyright 2012 Samuel Stauffer. All rights reserved. +// Use of this source code is governed by a 3-clause BSD +// license that can be found in the LICENSE file. + +package socks + +import "fmt" + +type ProxiedAddr struct { + Net string + Host string + Port int +} + +func (a *ProxiedAddr) Network() string { + return a.Net +} + +func (a *ProxiedAddr) String() string { + return fmt.Sprintf("%s:%d", a.Host, a.Port) +} diff --git a/vendor/github.com/btcsuite/go-socks/socks/conn.go b/vendor/github.com/btcsuite/go-socks/socks/conn.go new file mode 100644 index 00000000..9391682f --- /dev/null +++ b/vendor/github.com/btcsuite/go-socks/socks/conn.go @@ -0,0 +1,54 @@ +// Copyright 2012 Samuel Stauffer. All rights reserved. +// Use of this source code is governed by a 3-clause BSD +// license that can be found in the LICENSE file. + +package socks + +import ( + "net" + "time" +) + +type proxiedConn struct { + conn net.Conn + remoteAddr *ProxiedAddr + boundAddr *ProxiedAddr +} + +func (c *proxiedConn) Read(b []byte) (int, error) { + return c.conn.Read(b) +} + +func (c *proxiedConn) Write(b []byte) (int, error) { + return c.conn.Write(b) +} + +func (c *proxiedConn) Close() error { + return c.conn.Close() +} + +func (c *proxiedConn) LocalAddr() net.Addr { + if c.boundAddr != nil { + return c.boundAddr + } + return c.conn.LocalAddr() +} + +func (c *proxiedConn) RemoteAddr() net.Addr { + if c.remoteAddr != nil { + return c.remoteAddr + } + return c.conn.RemoteAddr() +} + +func (c *proxiedConn) SetDeadline(t time.Time) error { + return c.conn.SetDeadline(t) +} + +func (c *proxiedConn) SetReadDeadline(t time.Time) error { + return c.conn.SetReadDeadline(t) +} + +func (c *proxiedConn) SetWriteDeadline(t time.Time) error { + return c.conn.SetWriteDeadline(t) +} diff --git a/vendor/github.com/btcsuite/go-socks/socks/dial.go b/vendor/github.com/btcsuite/go-socks/socks/dial.go new file mode 100644 index 00000000..0aaed943 --- /dev/null +++ b/vendor/github.com/btcsuite/go-socks/socks/dial.go @@ -0,0 +1,269 @@ +// Copyright 2012 Samuel Stauffer. All rights reserved. +// Use of this source code is governed by a 3-clause BSD +// license that can be found in the LICENSE file. + +/* +Current limitations: + + - GSS-API authentication is not supported + - only SOCKS version 5 is supported + - TCP bind and UDP not yet supported + +Example http client over SOCKS5: + + proxy := &socks.Proxy{"127.0.0.1:1080"} + tr := &http.Transport{ + Dial: proxy.Dial, + } + client := &http.Client{Transport: tr} + resp, err := client.Get("https://example.com") +*/ +package socks + +import ( + "crypto/rand" + "encoding/hex" + "errors" + "io" + "net" + "strconv" + "time" +) + +const ( + protocolVersion = 5 + + defaultPort = 1080 + + authNone = 0 + authGssApi = 1 + authUsernamePassword = 2 + authUnavailable = 0xff + + commandTcpConnect = 1 + commandTcpBind = 2 + commandUdpAssociate = 3 + + addressTypeIPv4 = 1 + addressTypeDomain = 3 + addressTypeIPv6 = 4 + + statusRequestGranted = 0 + statusGeneralFailure = 1 + statusConnectionNotAllowed = 2 + statusNetworkUnreachable = 3 + statusHostUnreachable = 4 + statusConnectionRefused = 5 + statusTtlExpired = 6 + statusCommandNotSupport = 7 + statusAddressTypeNotSupported = 8 +) + +var ( + ErrAuthFailed = errors.New("authentication failed") + ErrInvalidProxyResponse = errors.New("invalid proxy response") + ErrNoAcceptableAuthMethod = errors.New("no acceptable authentication method") + + statusErrors = map[byte]error{ + statusGeneralFailure: errors.New("general failure"), + statusConnectionNotAllowed: errors.New("connection not allowed by ruleset"), + statusNetworkUnreachable: errors.New("network unreachable"), + statusHostUnreachable: errors.New("host unreachable"), + statusConnectionRefused: errors.New("connection refused by destination host"), + statusTtlExpired: errors.New("TTL expired"), + statusCommandNotSupport: errors.New("command not supported / protocol error"), + statusAddressTypeNotSupported: errors.New("address type not supported"), + } +) + +type Proxy struct { + Addr string + Username string + Password string + TorIsolation bool +} + +func (p *Proxy) Dial(network, addr string) (net.Conn, error) { + return p.dial(network, addr, 0) +} + +func (p *Proxy) DialTimeout(network, addr string, timeout time.Duration) (net.Conn, error) { + return p.dial(network, addr, timeout) +} + +func (p *Proxy) dial(network, addr string, timeout time.Duration) (net.Conn, error) { + host, strPort, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + port, err := strconv.Atoi(strPort) + if err != nil { + return nil, err + } + + conn, err := net.DialTimeout("tcp", p.Addr, timeout) + if err != nil { + return nil, err + } + + var user, pass string + if p.TorIsolation { + var b [16]byte + _, err := io.ReadFull(rand.Reader, b[:]) + if err != nil { + conn.Close() + return nil, err + } + user = hex.EncodeToString(b[0:8]) + pass = hex.EncodeToString(b[8:16]) + } else { + user = p.Username + pass = p.Password + } + buf := make([]byte, 32+len(host)+len(user)+len(pass)) + + // Initial greeting + buf[0] = protocolVersion + if user != "" { + buf = buf[:4] + buf[1] = 2 // num auth methods + buf[2] = authNone + buf[3] = authUsernamePassword + } else { + buf = buf[:3] + buf[1] = 1 // num auth methods + buf[2] = authNone + } + + _, err = conn.Write(buf) + if err != nil { + conn.Close() + return nil, err + } + + // Server's auth choice + + if _, err := io.ReadFull(conn, buf[:2]); err != nil { + conn.Close() + return nil, err + } + if buf[0] != protocolVersion { + conn.Close() + return nil, ErrInvalidProxyResponse + } + err = nil + switch buf[1] { + default: + err = ErrInvalidProxyResponse + case authUnavailable: + err = ErrNoAcceptableAuthMethod + case authGssApi: + err = ErrNoAcceptableAuthMethod + case authUsernamePassword: + buf = buf[:3+len(user)+len(pass)] + buf[0] = 1 // version + buf[1] = byte(len(user)) + copy(buf[2:], user) + buf[2+len(user)] = byte(len(pass)) + copy(buf[3+len(user):], pass) + if _, err = conn.Write(buf); err != nil { + conn.Close() + return nil, err + } + if _, err = io.ReadFull(conn, buf[:2]); err != nil { + conn.Close() + return nil, err + } + if buf[0] != 1 { // version + err = ErrInvalidProxyResponse + } else if buf[1] != 0 { // 0 = succes, else auth failed + err = ErrAuthFailed + } + case authNone: + // Do nothing + } + if err != nil { + conn.Close() + return nil, err + } + + // Command / connection request + + buf = buf[:7+len(host)] + buf[0] = protocolVersion + buf[1] = commandTcpConnect + buf[2] = 0 // reserved + buf[3] = addressTypeDomain + buf[4] = byte(len(host)) + copy(buf[5:], host) + buf[5+len(host)] = byte(port >> 8) + buf[6+len(host)] = byte(port & 0xff) + if _, err := conn.Write(buf); err != nil { + conn.Close() + return nil, err + } + + // Server response + + if _, err := io.ReadFull(conn, buf[:4]); err != nil { + conn.Close() + return nil, err + } + + if buf[0] != protocolVersion { + conn.Close() + return nil, ErrInvalidProxyResponse + } + + if buf[1] != statusRequestGranted { + conn.Close() + err := statusErrors[buf[1]] + if err == nil { + err = ErrInvalidProxyResponse + } + return nil, err + } + + paddr := &ProxiedAddr{Net: network} + + switch buf[3] { + default: + conn.Close() + return nil, ErrInvalidProxyResponse + case addressTypeIPv4: + if _, err := io.ReadFull(conn, buf[:4]); err != nil { + conn.Close() + return nil, err + } + paddr.Host = net.IP(buf).String() + case addressTypeIPv6: + if _, err := io.ReadFull(conn, buf[:16]); err != nil { + conn.Close() + return nil, err + } + paddr.Host = net.IP(buf).String() + case addressTypeDomain: + if _, err := io.ReadFull(conn, buf[:1]); err != nil { + conn.Close() + return nil, err + } + domainLen := buf[0] + if _, err := io.ReadFull(conn, buf[:domainLen]); err != nil { + conn.Close() + return nil, err + } + paddr.Host = string(buf[:domainLen]) + } + + if _, err := io.ReadFull(conn, buf[:2]); err != nil { + conn.Close() + return nil, err + } + paddr.Port = int(buf[0])<<8 | int(buf[1]) + + return &proxiedConn{ + conn: conn, + boundAddr: paddr, + remoteAddr: &ProxiedAddr{network, host, port}, + }, nil +} -- 2.11.0