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.
This commit is contained in:
Oliver Gugger 2020-11-26 15:46:05 +01:00
parent 7e298f1434
commit 6f3c8611f4
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
2 changed files with 103 additions and 0 deletions

@ -1,6 +1,7 @@
package tor package tor
import ( import (
"bytes"
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
@ -39,6 +40,15 @@ var (
22: "bad truncation", 22: "bad truncation",
23: "bad/missing server cookie", 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 // 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 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())
}