diff --git a/chainregistry.go b/chainregistry.go index 9bd09b558..7cbed86ee 100644 --- a/chainregistry.go +++ b/chainregistry.go @@ -174,6 +174,27 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB, ChainParams: *activeNetParams.Params, AddPeers: cfg.NeutrinoMode.AddPeers, ConnectPeers: cfg.NeutrinoMode.ConnectPeers, + Dialer: func(addr net.Addr) (net.Conn, error) { + return cfg.net.Dial(addr.Network(), addr.String()) + }, + NameResolver: func(host string) ([]net.IP, error) { + addrs, err := cfg.net.LookupHost(host) + if err != nil { + return nil, err + } + + ips := make([]net.IP, 0, len(addrs)) + for _, strIP := range addrs { + ip := net.ParseIP(strIP) + if ip == nil { + continue + } + + ips = append(ips, ip) + } + + return ips, nil + }, } neutrino.WaitForMoreCFHeaders = time.Second * 1 neutrino.MaxPeers = 8 diff --git a/config.go b/config.go index 27baa58eb..c890c5517 100644 --- a/config.go +++ b/config.go @@ -134,8 +134,9 @@ type autoPilotConfig struct { } type torConfig struct { - Socks string `long:"socks" description:"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"` - DNS string `long:"dns" description:"The DNS server as IP:PORT that Tor will use for SRV queries - NOTE must have TCP resolution enabled"` + Socks string `long:"socks" description:"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"` + DNS string `long:"dns" description:"The DNS server as IP:PORT that Tor will use for SRV queries - NOTE must have TCP resolution enabled"` + StreamIsolation bool `long:"streamisolation" description:"Enable Tor stream isolation by randomizing user credentials for each connection."` } // config defines the configuration options for lnd. @@ -325,8 +326,9 @@ func loadConfig() (*config, error) { } cfg.net = &torsvc.TorProxyNet{ - TorDNS: cfg.Tor.DNS, - TorSocks: cfg.Tor.Socks, + TorDNS: cfg.Tor.DNS, + TorSocks: cfg.Tor.Socks, + StreamIsolation: cfg.Tor.StreamIsolation, } // If we are using Tor, since we only want connections routed diff --git a/docs/configuring_tor.md b/docs/configuring_tor.md new file mode 100644 index 000000000..d4395fb1e --- /dev/null +++ b/docs/configuring_tor.md @@ -0,0 +1,120 @@ +# Table of Contents +1. [Overview](#Overview) +2. [Outbound Connections Only](#outbound-connections-only) +3. [Tor Stream Isolation](#tor-stream-isolation) + +## 1. Overview + +`lnd` currently has _partial_ support for using Lightning over +[Tor](https://www.torproject.org/). Usage of Lightning over Tor is valuable as +routing nodes no longer need to potentially expose their location via their +advertised IP address. Additionally, leaf nodes can also protect their location +by using Tor for anonymous networking to establish connections. + +At the time of the writing of this documentation, `lnd` only supports usage of +Tor for establishing _outbound_ connections. In the near future, support for +full [Onion Service](https://www.torproject.org/docs/onion-services.html.en) +usage will be added as well. Support for both `v2` and `v3` onion services are +planned. With widespread usage of Onion Services within the network, concerns +about the difficulty of proper NAT traversal are alleviated, as usage of Onion +Services allows nodes to accept inbound connections even if they're behind a +NAT. + +Before following the remainder of this documentation, you should ensure that +you already have Tor installed locally. Official instructions to install the +latest release of Tor can be found +[here](https://www.torproject.org/docs/tor-doc-unix.html.en). + +**NOTE**: This documentation covers how to ensure that `lnd`'s _Lightning +protocol traffic_ is tunnled over Tor. Users will need to take care that if +they're running using a Bitcoin full-node, then that is also configured to +proxy all trafic over Tor. If using the `neutrino` backend for `lnd`, then it +will automatically also default to Tor usage if active within `lnd`. + + +## 2. Outbound Connections Only + +Currenty, `lnd` only supports purely _outbound_ Tor usage. In this mode, `lnd` +_won't_ listen at all, and will only be able to establish outbound connections. +_All_ protocol traffic will be tunneled over Tor. Additionally, we'll also +force any DNS requests over Tor such that we don't leak our IP address to the +clear net. + +The remainder of this tutorial assumes one already has the `tor` daemon +installed locally. + +First, you'll want to run `tor` locally before starting up `lnd`. Depending on +how you installed Tor, you'll find the configuration file at +`/usr/local/etc/tor/torrc`. Here's an example configuration file that we'll be +using for the remainder of the tutorial: +``` +SOCKSPort 9050 # Default: Bind to localhost:9050 for local connections. +Log notice stdout +ControlPort 9051 +CookieAuthentication 1 +``` + +With the configuration file created, you'll then want to start the Tor daemon: +``` +⛰ tor +Feb 05 17:02:06.501 [notice] Tor 0.3.1.8 (git-ad5027f7dc790624) running on Darwin with Libevent 2.1.8-stable, OpenSSL 1.0.2l, Zlib 1.2.8, Liblzma N/A, and Libzstd N/A. +Feb 05 17:02:06.502 [notice] Tor can't help you if you use it wrong! Learn how to be safe at https://www.torproject.org/download/download#warning +Feb 05 17:02:06.502 [notice] Read configuration file "/usr/local/etc/tor/torrc". +Feb 05 17:02:06.506 [notice] Opening Socks listener on 127.0.0.1:9050 +Feb 05 17:02:06.506 [notice] Opening Control listener on 127.0.0.1:9051 +``` + +Once the `tor` daemon has started and it has finished bootstrapping, you'll see this in the logs: +``` +Feb 05 17:02:06.000 [notice] Bootstrapped 0%: Starting +Feb 05 17:02:07.000 [notice] Starting with guard context "default" +Feb 05 17:02:07.000 [notice] Bootstrapped 80%: Connecting to the Tor network +Feb 05 17:02:07.000 [notice] Bootstrapped 85%: Finishing handshake with first hop +Feb 05 17:02:08.000 [notice] Bootstrapped 90%: Establishing a Tor circuit +Feb 05 17:02:11.000 [notice] Tor has successfully opened a circuit. Looks like client functionality is working. +Feb 05 17:02:11.000 [notice] Bootstrapped 100%: Done +``` + +This indicates the daemon is fully bootstrapped and ready to proxy connections. +At this point, we can now start `lnd` with the relevant arguments: + +``` +⛰ ./lnd -h + + + +Tor: + --tor.socks= 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 + --tor.dns= The DNS server as IP:PORT that Tor will use for SRV queries - NOTE must have TCP resolution enabled +``` + +The `--tor.socks` argument should point to the interface that the `Tor` daemon +is listening on to proxy connections. The `--tor.dns` flag is required in order +to be able to properly automatically bootstrap a set of peer connections. The +`tor` daemon doesn't currently support proxying `SRV` queries over Tor. So +instead, we need to connect directly to the authoritative DNS server over TCP, +in order query for `SRV` records that we can use to bootstrap our connections. +As of the time this documentation was written, for Bitcoin's Testnet, clients +should point to `nodes.lightning.directory`. + +Finally, we'll start `lnd` with the proper arguments: +``` +⛰ ./lnd --tor.socks=9050 --tor.dns=nodes.lightning.directory +``` + +With the above arguments, `lnd` will proxy _all_ network traffic over Tor! + + +## 3. Tor Stream Isolation + +Our support for Tor also has an additional privacy enhancing modified: stream +isolation. Usage of this mode means that Tor will always use _new circuit_ for +each connection. This added features means that it's harder to correlate +connections. As otherwise, several applications using Tor might share the same +circuit. + +Activating stream isolation is very straightforward, we only require the +specification of an additional argument: +``` +⛰ ./lnd --tor.socks=9050 --tor.dns=nodes.lightning.directory --tor.streamisolation +``` diff --git a/glide.lock b/glide.lock index 9a091067f..9fc1c19b0 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: a69cfd6352bed3cc0d24a01f42609b849f9f6d5664be7470b281bf6b09c58d84 -updated: 2018-02-06T17:14:31.208312805-08:00 +hash: 63c0706e71ebdd4664e6a452bf58035571a8799b24e517ea0d8aa2fb8dd94155 +updated: 2018-02-08T15:26:31.423935935-08:00 imports: - name: github.com/aead/chacha20 version: d31a916ded42d1640b9d89a26f8abd53cc96790c @@ -72,7 +72,7 @@ imports: - name: github.com/lightninglabs/gozmq version: 0d266ba6d55ea65c18c7a02d8992c89355149e80 - name: github.com/lightninglabs/neutrino - version: 6c8f30a130bb170348ceb754ce4204156efe2741 + version: c933ad49936ac3e5bc1b42e855f2da69745e34cc subpackages: - filterdb - headerfs diff --git a/glide.yaml b/glide.yaml index c647ba3ee..9367b6de9 100644 --- a/glide.yaml +++ b/glide.yaml @@ -73,7 +73,7 @@ import: subpackages: - chaincfg - package: github.com/lightninglabs/neutrino - version: 6c8f30a130bb170348ceb754ce4204156efe2741 + version: c933ad49936ac3e5bc1b42e855f2da69745e34cc - package: gopkg.in/macaroon.v2 - package: gopkg.in/macaroon-bakery.v2 - package: github.com/juju/loggo diff --git a/lnd.go b/lnd.go index 09532fb6a..9f4e10f8f 100644 --- a/lnd.go +++ b/lnd.go @@ -251,6 +251,13 @@ func lndMain() error { } idPrivKey.Curve = btcec.S256() + if cfg.Tor.Socks != "" && cfg.Tor.DNS != "" { + srvrLog.Infof("Proxying all network traffic via Tor "+ + "(stream_isolation=%v)! NOTE: If running with a full-node "+ + "backend, ensure that is proxying over Tor as well", + cfg.Tor.StreamIsolation) + } + // Set up the core server which will listen for incoming peer // connections. server, err := newServer(cfg.Listeners, chanDB, activeChainControl, diff --git a/lnwallet/interface_test.go b/lnwallet/interface_test.go index 612be9c7a..08b834979 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -1700,7 +1700,6 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, neutrino.Config{ DataDir: tempTestDirAlice, Database: aliceDB, - Namespace: []byte("alice"), ChainParams: *netParams, ConnectPeers: []string{ miningNode.P2PAddress(), @@ -1726,7 +1725,6 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver, neutrino.Config{ DataDir: tempTestDirBob, Database: bobDB, - Namespace: []byte("bob"), ChainParams: *netParams, ConnectPeers: []string{ miningNode.P2PAddress(), diff --git a/sample-lnd.conf b/sample-lnd.conf index c21e54957..582b97605 100644 --- a/sample-lnd.conf +++ b/sample-lnd.conf @@ -261,3 +261,20 @@ litecoin.node=btcd ; within the wallet should be used to automatically establish channels. The total ; amount of attempted channels will still respect the maxchannels param. ; autopilot.allocation=0.6 + +[tor] +; 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 +; tor.socks=9050 + +; The DNS server as IP:PORT that Tor will use for SRV queries - NOTE must have +; TCP resolution enabled. The current active DNS sever for Testnet listening is +; nodes.lightning.directory +; tor.dns=nodes.lightning.directory + +; Enable Tor stream isolation by randomizing user credentials for each +; 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. +; tor.streamisolation=1 diff --git a/torsvc/net.go b/torsvc/net.go index 0a5576fd9..859189acd 100644 --- a/torsvc/net.go +++ b/torsvc/net.go @@ -39,6 +39,12 @@ type TorProxyNet struct { // This is used for an outbound-only mode, so the node will not listen for // incoming connections TorSocks string + + // StreamIsolation is a bool that determines if we should force the + // creation of a new circuit for this connection. If true, then this + // means that our traffic may be harder to correlate as each connection + // will now use a distinct circuit. + StreamIsolation bool } // Dial on the Tor network uses the torsvc TorDial() function, and requires @@ -47,7 +53,7 @@ func (t *TorProxyNet) Dial(network, address string) (net.Conn, error) { if network != "tcp" { return nil, fmt.Errorf("Cannot dial non-tcp network via Tor") } - return TorDial(address, t.TorSocks) + return TorDial(address, t.TorSocks, t.StreamIsolation) } // LookupHost on Tor network uses the torsvc TorLookupHost function. diff --git a/torsvc/torsvc.go b/torsvc/torsvc.go index 7325000cd..293693730 100644 --- a/torsvc/torsvc.go +++ b/torsvc/torsvc.go @@ -44,9 +44,16 @@ var ( ) // TorDial returns a connection to a remote peer via Tor's socks proxy. Only -// TCP is supported over Tor. -func TorDial(address, socksPort string) (net.Conn, error) { - p := &socks.Proxy{Addr: localhost + ":" + socksPort} +// TCP is supported over Tor. The final argument 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. +func TorDial(address, socksPort string, streamIsolation bool) (net.Conn, error) { + p := &socks.Proxy{ + Addr: localhost + ":" + socksPort, + TorIsolation: streamIsolation, + } + return p.Dial("tcp", address) }