Merge pull request #4806 from guggero/neutrino-tor-fix

Fix Onion v2 support for Neutrino backends
This commit is contained in:
Johan T. Halseth 2020-12-01 11:30:39 +01:00 committed by GitHub
commit 42d7fcd2f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 135 additions and 1 deletions

33
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

@ -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
}

36
tor/tor_test.go Normal file

@ -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())
}