Skip to content

Commit

Permalink
tor: convert onion v2 addrs into fake tcp6
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
guggero committed Nov 30, 2020
1 parent 7e298f1 commit 6f3c861
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 0 deletions.
67 changes: 67 additions & 0 deletions tor/tor.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tor

import (
"bytes"
"crypto/rand"
"encoding/hex"
"fmt"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 changes: 36 additions & 0 deletions tor/tor_test.go
Original file line number Diff line number Diff line change
@@ -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())
}

0 comments on commit 6f3c861

Please sign in to comment.