From 6f3c8611f4b86ad5cdc6f9cc6aa2586b54d78e9c Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 26 Nov 2020 15:46:05 +0100 Subject: [PATCH 1/2] tor: convert onion v2 addrs into fake tcp6 If we use a chain backend that only understands IP addresses (like Neutrino for example), we need to turn any Onion v2 host addresses into a fake IPv6 representation, otherwise it would be resolved incorrectly. To do this, we use the same fake IPv6 address format that bitcoind and btcd use internally to represent Onion v2 hidden service addresses. --- tor/tor.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ tor/tor_test.go | 36 ++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 tor/tor_test.go diff --git a/tor/tor.go b/tor/tor.go index e5430f57e..26e36a7e2 100644 --- a/tor/tor.go +++ b/tor/tor.go @@ -1,6 +1,7 @@ package tor import ( + "bytes" "crypto/rand" "encoding/hex" "fmt" @@ -39,6 +40,15 @@ var ( 22: "bad truncation", 23: "bad/missing server cookie", } + + // onionPrefixBytes is a special purpose IPv6 prefix to encode Onion v2 + // addresses with. Because Neutrino uses the address manager of btcd + // which only understands net.IP addresses instead of net.Addr, we need + // to convert any .onion addresses into fake IPv6 addresses if we want + // to use a Tor hidden service as a Neutrino backend. This is the same + // range used by OnionCat, which is part part of the RFC4193 unique + // local IPv6 unicast address range. + onionPrefixBytes = []byte{0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43} ) // proxyConn is a wrapper around net.Conn that allows us to expose the actual @@ -248,3 +258,60 @@ func IsOnionHost(host string) bool { return true } + +// IsOnionFakeIP checks whether a given net.Addr is a fake IPv6 address that +// encodes an Onion v2 address. +func IsOnionFakeIP(addr net.Addr) bool { + _, err := FakeIPToOnionHost(addr) + return err == nil +} + +// OnionHostToFakeIP encodes an Onion v2 address into a fake IPv6 address that +// encodes the same information but can be used for libraries that operate on an +// IP address base only, like btcd's address manager. For example, this will +// turn the onion host ld47qlr6h2b7hrrf.onion into the ip6 address +// fd87:d87e:eb43:58f9:f82e:3e3e:83f3:c625. +func OnionHostToFakeIP(host string) (net.IP, error) { + if len(host) != V2Len { + return nil, fmt.Errorf("invalid onion v2 host: %v", host) + } + + data, err := Base32Encoding.DecodeString(host[:V2Len-OnionSuffixLen]) + if err != nil { + return nil, err + } + + ip := make([]byte, len(onionPrefixBytes)+len(data)) + copy(ip, onionPrefixBytes) + copy(ip[len(onionPrefixBytes):], data) + return ip, nil +} + +// FakeIPToOnionHost turns a fake IPv6 address that encodes an Onion v2 address +// back into its onion host address representation. For example, this will turn +// the fake tcp6 address [fd87:d87e:eb43:58f9:f82e:3e3e:83f3:c625]:8333 back +// into ld47qlr6h2b7hrrf.onion:8333. +func FakeIPToOnionHost(fakeIP net.Addr) (net.Addr, error) { + tcpAddr, ok := fakeIP.(*net.TCPAddr) + if !ok { + return nil, fmt.Errorf("invalid fake onion IP address: %v", + fakeIP) + } + + ip := tcpAddr.IP + if len(ip) != len(onionPrefixBytes)+V2DecodedLen { + return nil, fmt.Errorf("invalid fake onion IP address length: "+ + "%v", fakeIP) + } + + if !bytes.Equal(ip[:len(onionPrefixBytes)], onionPrefixBytes) { + return nil, fmt.Errorf("invalid fake onion IP address prefix: "+ + "%v", fakeIP) + } + + host := Base32Encoding.EncodeToString(ip[len(onionPrefixBytes):]) + return &OnionAddr{ + OnionService: host + ".onion", + Port: tcpAddr.Port, + }, nil +} diff --git a/tor/tor_test.go b/tor/tor_test.go new file mode 100644 index 000000000..594b77df1 --- /dev/null +++ b/tor/tor_test.go @@ -0,0 +1,36 @@ +package tor + +import ( + "fmt" + "net" + "testing" + + "github.com/stretchr/testify/require" +) + +const ( + testOnion = "ld47qlr6h2b7hrrf.onion" + testFakeIP = "fd87:d87e:eb43:58f9:f82e:3e3e:83f3:c625" +) + +// TestOnionHostToFakeIP tests that an onion host address can be converted into +// a fake tcp6 address successfully. +func TestOnionHostToFakeIP(t *testing.T) { + ip, err := OnionHostToFakeIP(testOnion) + require.NoError(t, err) + require.Equal(t, testFakeIP, ip.String()) +} + +// TestFakeIPToOnionHost tests that a fake tcp6 address can be converted back +// into its original .onion host address successfully. +func TestFakeIPToOnionHost(t *testing.T) { + tcpAddr, err := net.ResolveTCPAddr( + "tcp6", fmt.Sprintf("[%s]:8333", testFakeIP), + ) + require.NoError(t, err) + require.True(t, IsOnionFakeIP(tcpAddr)) + + onionHost, err := FakeIPToOnionHost(tcpAddr) + require.NoError(t, err) + require.Equal(t, fmt.Sprintf("%s:8333", testOnion), onionHost.String()) +} From 76d2c49a1750d9a5a5e7b22bdc46330a702b114e Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 26 Nov 2020 15:49:45 +0100 Subject: [PATCH 2/2] lnd: fix Onion v2 support for Neutrino backends With this commit we make it possible to use an Onion v2 hidden service address as the Neutrino backend. This failed before because an .onion address cannot be looked up and converted into an IP address through the normal DNS resolving process, even when using a Tor socks proxy. Instead, we turn any v2 .onion address into a fake IPv6 representation before giving it to Neutrino's address manager and turn it back into an Onion host address when actually dialing. --- lnd.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/lnd.go b/lnd.go index 11997d148..0643ba6f1 100644 --- a/lnd.go +++ b/lnd.go @@ -1508,12 +1508,43 @@ func initNeutrinoBackend(cfg *Config, chainDir string) (*neutrino.ChainService, AddPeers: cfg.NeutrinoMode.AddPeers, ConnectPeers: cfg.NeutrinoMode.ConnectPeers, Dialer: func(addr net.Addr) (net.Conn, error) { + dialAddr := addr + if tor.IsOnionFakeIP(addr) { + // Because the Neutrino address manager only + // knows IP addresses, we need to turn any fake + // tcp6 address that actually encodes an Onion + // v2 address back into the hostname + // representation before we can pass it to the + // dialer. + var err error + dialAddr, err = tor.FakeIPToOnionHost(addr) + if err != nil { + return nil, err + } + } + return cfg.net.Dial( - addr.Network(), addr.String(), + dialAddr.Network(), dialAddr.String(), cfg.ConnectionTimeout, ) }, NameResolver: func(host string) ([]net.IP, error) { + if tor.IsOnionHost(host) { + // Neutrino internally uses btcd's address + // manager which only operates on an IP level + // and does not understand onion hosts. We need + // to turn an onion host into a fake + // representation of an IP address to make it + // possible to connect to a block filter backend + // that serves on an Onion v2 hidden service. + fakeIP, err := tor.OnionHostToFakeIP(host) + if err != nil { + return nil, err + } + + return []net.IP{fakeIP}, nil + } + addrs, err := cfg.net.LookupHost(host) if err != nil { return nil, err