From be666b55b6129edbc0cc652563e7b46eef067a88 Mon Sep 17 00:00:00 2001 From: Adrian-Stefan Mares Date: Sun, 20 Jun 2021 11:14:23 +0200 Subject: [PATCH] tor: Allow direct connections to clearnet targets --- sample-lnd.conf | 10 ++++++++++ tor/net.go | 13 ++++++++++--- tor/tor.go | 46 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/sample-lnd.conf b/sample-lnd.conf index 0e6c70a4b..235347047 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -827,6 +827,13 @@ litecoin.node=ltcd ; Allow outbound and inbound connections to be routed through Tor ; tor.active=true +; Allow the node to connect to non-onion services directly via clearnet. This +; allows the node operator to use direct connections to peers not running behind +; Tor, thus allowing lower latency and better connection stability. +; WARNING: This option will reveal the source IP address of the node, and should +; be used only if privacy is not a concern. +; tor.directconnections=true + ; The port that Tor's exposed SOCKS5 proxy is listening on. Using Tor allows ; outbound-only connections (listening will be disabled) -- NOTE port must be ; between 1024 and 65535 @@ -841,6 +848,9 @@ litecoin.node=ltcd ; connection. With this mode active, each connection will use a new circuit. ; This means that multiple applications (other than lnd) using Tor won't be mixed ; in with lnd's traffic. +; +; This option may not be used while direct connections are enabled, since direct +; connections compromise source IP privacy by default. ; tor.streamisolation=true ; The host:port that Tor is listening on for Tor control connections (default: diff --git a/tor/net.go b/tor/net.go index d389cc73a..26990edf0 100644 --- a/tor/net.go +++ b/tor/net.go @@ -72,7 +72,7 @@ func (r *ClearNet) ResolveTCPAddr(network, address string) (*net.TCPAddr, error) return net.ResolveTCPAddr(network, address) } -// ProxyNet is an implementation of the Net interface that defines behaviour +// ProxyNet is an implementation of the Net interface that defines behavior // for Tor network connections. type ProxyNet struct { // SOCKS is the host:port which Tor's exposed SOCKS5 proxy is listening @@ -88,6 +88,11 @@ type ProxyNet struct { // means that our traffic may be harder to correlate as each connection // will now use a distinct circuit. StreamIsolation bool + + // DirectConnections allows the proxy network to use direct connections + // to non-onion service targets. If enabled, the node IP address will be + // revealed while communicating with such targets. + DirectConnections bool } // Dial uses the Tor Dial function in order to establish connections through @@ -100,7 +105,9 @@ func (p *ProxyNet) Dial(network, address string, default: return nil, errors.New("cannot dial non-tcp network via Tor") } - return Dial(address, p.SOCKS, p.StreamIsolation, timeout) + return Dial( + address, p.SOCKS, p.StreamIsolation, p.DirectConnections, timeout, + ) } // LookupHost uses the Tor LookupHost function in order to resolve hosts over @@ -116,7 +123,7 @@ func (p *ProxyNet) LookupSRV(service, proto, return LookupSRV( service, proto, name, p.SOCKS, p.DNS, - p.StreamIsolation, timeout, + p.StreamIsolation, p.DirectConnections, timeout, ) } diff --git a/tor/tor.go b/tor/tor.go index 26e36a7e2..9db879195 100644 --- a/tor/tor.go +++ b/tor/tor.go @@ -66,9 +66,11 @@ func (c *proxyConn) RemoteAddr() net.Addr { // around net.Conn in order to expose the actual remote address we're dialing, // rather than the proxy's address. func Dial(address, socksAddr string, streamIsolation bool, - timeout time.Duration) (net.Conn, error) { + directConnections bool, timeout time.Duration) (net.Conn, error) { - conn, err := dial(address, socksAddr, streamIsolation, timeout) + conn, err := dial( + address, socksAddr, streamIsolation, directConnections, timeout, + ) if err != nil { return nil, err } @@ -87,13 +89,18 @@ func Dial(address, socksAddr string, streamIsolation bool, }, nil } -// dial establishes a connection to the address via Tor's SOCKS proxy. Only TCP -// is supported over Tor. The argument streamIsolation determines if we should -// force stream isolation for this new connection. If we do, then this means -// this new connection will use a fresh circuit, rather than possibly re-using -// an existing circuit. +// dial establishes a connection to the address via the provided TOR SOCKS +// proxy. Only TCP traffic may be routed via Tor. +// +// streamIsolation determines if we should force stream isolation for this new +// connection. If enabled, new connections will use a fresh circuit, rather than +// possibly re-using an existing circuit. +// +// directConnections argument allows the dialer to directly connect to the +// provided address if it does not represent an union service, skipping the +// SOCKS proxy. func dial(address, socksAddr string, streamIsolation bool, - timeout time.Duration) (net.Conn, error) { + directConnections bool, timeout time.Duration) (net.Conn, error) { // If we were requested to force stream isolation for this connection, // we'll populate the authentication credentials with random data as @@ -111,9 +118,22 @@ func dial(address, socksAddr string, streamIsolation bool, } } + clearDialer := &net.Dialer{Timeout: timeout} + if directConnections { + host, _, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + + // The SOCKS proxy is skipped if the target + // is not an union address. + if !IsOnionHost(host) { + return clearDialer.Dial("tcp", address) + } + } + // Establish the connection through Tor's SOCKS proxy. - proxyDialer := &net.Dialer{Timeout: timeout} - dialer, err := proxy.SOCKS5("tcp", socksAddr, auth, proxyDialer) + dialer, err := proxy.SOCKS5("tcp", socksAddr, auth, clearDialer) if err != nil { return nil, err } @@ -139,10 +159,12 @@ func LookupHost(host, socksAddr string) ([]string, error) { // must have TCP resolution enabled for the given port. func LookupSRV(service, proto, name, socksAddr, dnsServer string, streamIsolation bool, - timeout time.Duration) (string, []*net.SRV, error) { + directConnections bool, timeout time.Duration) (string, []*net.SRV, error) { // Connect to the DNS server we'll be using to query SRV records. - conn, err := dial(dnsServer, socksAddr, streamIsolation, timeout) + conn, err := dial( + dnsServer, socksAddr, streamIsolation, directConnections, timeout, + ) if err != nil { return "", nil, err }