mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
e95720cf3a
With this commit we avoid calling LookupHost if we already have an IPv4 or IPv6 address, as we can return that directly. This avoids asking Tor to resolve an IPv6 address, which it cannot do.
352 lines
10 KiB
Go
352 lines
10 KiB
Go
package tor
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/connmgr"
|
|
"github.com/miekg/dns"
|
|
"golang.org/x/net/proxy"
|
|
)
|
|
|
|
var (
|
|
// dnsCodes maps the DNS response codes to a friendly description. This
|
|
// does not include the BADVERS code because of duplicate keys and the
|
|
// underlying DNS (miekg/dns) package not using it. For more info, see
|
|
// https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml.
|
|
dnsCodes = map[int]string{
|
|
0: "no error",
|
|
1: "format error",
|
|
2: "server failure",
|
|
3: "non-existent domain",
|
|
4: "not implemented",
|
|
5: "query refused",
|
|
6: "name exists when it should not",
|
|
7: "RR set exists when it should not",
|
|
8: "RR set that should exist does not",
|
|
9: "server not authoritative for zone",
|
|
10: "name not contained in zone",
|
|
16: "TSIG signature failure",
|
|
17: "key not recognized",
|
|
18: "signature out of time window",
|
|
19: "bad TKEY mode",
|
|
20: "duplicate key name",
|
|
21: "algorithm not supported",
|
|
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
|
|
// remote address we're dialing, rather than the proxy's address.
|
|
type proxyConn struct {
|
|
net.Conn
|
|
remoteAddr net.Addr
|
|
}
|
|
|
|
func (c *proxyConn) RemoteAddr() net.Addr {
|
|
return c.remoteAddr
|
|
}
|
|
|
|
// Dial is a wrapper over the non-exported dial function that returns a wrapper
|
|
// around net.Conn in order to expose the actual remote address we're dialing,
|
|
// rather than the proxy's address.
|
|
func Dial(address, socksAddr string, streamIsolation bool,
|
|
skipProxyForClearNetTargets bool,
|
|
timeout time.Duration) (net.Conn, error) {
|
|
|
|
conn, err := dialProxy(
|
|
address, socksAddr, streamIsolation,
|
|
skipProxyForClearNetTargets, timeout,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("dial proxy failed: %w", err)
|
|
}
|
|
|
|
// Now that the connection is established, we'll create our internal
|
|
// proxyConn that will serve in populating the correct remote address
|
|
// of the connection, rather than using the proxy's address.
|
|
remoteAddr, err := ParseAddr(address, socksAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &proxyConn{
|
|
Conn: conn,
|
|
remoteAddr: remoteAddr,
|
|
}, nil
|
|
}
|
|
|
|
// dialProxy establishes a connection to the address via the provided TOR SOCKS
|
|
// proxy. Only TCP traffic may be routed via Tor.
|
|
//
|
|
// streamIsolation determines if we should force stream isolation for this new
|
|
// connection. If enabled, new connections will use a fresh circuit, rather than
|
|
// possibly re-using an existing circuit.
|
|
//
|
|
// skipProxyForClearNetTargets argument allows the dialer to directly connect
|
|
// to the provided address if it does not represent an union service, skipping
|
|
// the SOCKS proxy.
|
|
func dialProxy(address, socksAddr string, streamIsolation bool,
|
|
skipProxyForClearNetTargets bool,
|
|
timeout time.Duration) (net.Conn, error) {
|
|
|
|
// If we were requested to force stream isolation for this connection,
|
|
// we'll populate the authentication credentials with random data as
|
|
// Tor will create a new circuit for each set of credentials.
|
|
var auth *proxy.Auth
|
|
if streamIsolation {
|
|
var b [16]byte
|
|
if _, err := rand.Read(b[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
auth = &proxy.Auth{
|
|
User: hex.EncodeToString(b[:8]),
|
|
Password: hex.EncodeToString(b[8:]),
|
|
}
|
|
}
|
|
|
|
clearDialer := &net.Dialer{Timeout: timeout}
|
|
if skipProxyForClearNetTargets {
|
|
host, _, err := net.SplitHostPort(address)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// The SOCKS proxy is skipped if the target
|
|
// is not an union address.
|
|
if !IsOnionHost(host) {
|
|
return clearDialer.Dial("tcp", address)
|
|
}
|
|
}
|
|
|
|
// Establish the connection through Tor's SOCKS proxy.
|
|
dialer, err := proxy.SOCKS5("tcp", socksAddr, auth, clearDialer)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("establish sock proxy: %w", err)
|
|
}
|
|
|
|
return dialer.Dial("tcp", address)
|
|
}
|
|
|
|
// LookupHost performs DNS resolution on a given host via Tor's native resolver.
|
|
// Only IPv4 addresses are returned.
|
|
func LookupHost(host, socksAddr string) ([]string, error) {
|
|
ip, err := connmgr.TorLookupIP(host, socksAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Only one IPv4 address is returned by the TorLookupIP function.
|
|
return []string{ip[0].String()}, nil
|
|
}
|
|
|
|
// LookupSRV uses Tor's SOCKS proxy to route DNS SRV queries. Tor does not
|
|
// natively support SRV queries so we must route all SRV queries through the
|
|
// proxy by connecting directly to a DNS server and querying it. The DNS server
|
|
// must have TCP resolution enabled for the given port.
|
|
func LookupSRV(service, proto, name, socksAddr,
|
|
dnsServer string, streamIsolation bool, skipProxyForClearNetTargets bool,
|
|
timeout time.Duration) (string, []*net.SRV, error) {
|
|
|
|
// Connect to the DNS server we'll be using to query SRV records.
|
|
conn, err := dialProxy(
|
|
dnsServer, socksAddr, streamIsolation,
|
|
skipProxyForClearNetTargets, timeout,
|
|
)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
dnsConn := &dns.Conn{Conn: conn}
|
|
defer dnsConn.Close()
|
|
|
|
// Once connected, we'll construct the SRV request for the host
|
|
// following the format _service._proto.name. as described in RFC #2782.
|
|
host := fmt.Sprintf("_%s._%s.%s.", service, proto, name)
|
|
msg := new(dns.Msg).SetQuestion(host, dns.TypeSRV)
|
|
|
|
// Send the request to the DNS server and read its response.
|
|
if err := dnsConn.WriteMsg(msg); err != nil {
|
|
return "", nil, err
|
|
}
|
|
resp, err := dnsConn.ReadMsg()
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
// We'll fail if we were unable to query the DNS server for our record.
|
|
if resp.Rcode != dns.RcodeSuccess {
|
|
return "", nil, fmt.Errorf("unable to query for SRV records: "+
|
|
"%s", dnsCodes[resp.Rcode])
|
|
}
|
|
|
|
// Retrieve the RR(s) of the Answer section.
|
|
var rrs []*net.SRV
|
|
for _, rr := range resp.Answer {
|
|
srv := rr.(*dns.SRV)
|
|
rrs = append(rrs, &net.SRV{
|
|
Target: srv.Target,
|
|
Port: srv.Port,
|
|
Priority: srv.Priority,
|
|
Weight: srv.Weight,
|
|
})
|
|
}
|
|
|
|
return "", rrs, nil
|
|
}
|
|
|
|
// ResolveTCPAddr uses Tor's proxy to resolve TCP addresses instead of the
|
|
// standard system resolver provided in the `net` package.
|
|
func ResolveTCPAddr(address, socksAddr string) (*net.TCPAddr, error) {
|
|
// Split host:port since the lookup function does not take a port.
|
|
host, port, err := net.SplitHostPort(address)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
p, err := strconv.Atoi(port)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Do we already have an IP? Then we don't need to look up anything.
|
|
if ip := net.ParseIP(host); ip != nil {
|
|
return &net.TCPAddr{
|
|
IP: ip,
|
|
Port: p,
|
|
}, nil
|
|
}
|
|
|
|
ip, err := LookupHost(host, socksAddr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &net.TCPAddr{
|
|
IP: net.ParseIP(ip[0]),
|
|
Port: p,
|
|
}, nil
|
|
}
|
|
|
|
// ParseAddr parses an address from its string format to a net.Addr.
|
|
func ParseAddr(address, socksAddr string) (net.Addr, error) {
|
|
host, portStr, err := net.SplitHostPort(address)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
port, err := strconv.Atoi(portStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if IsOnionHost(host) {
|
|
return &OnionAddr{OnionService: host, Port: port}, nil
|
|
}
|
|
|
|
return ResolveTCPAddr(address, socksAddr)
|
|
}
|
|
|
|
// IsOnionHost determines whether a host is part of an onion address.
|
|
func IsOnionHost(host string) bool {
|
|
// Note the starting index of the onion suffix in the host depending
|
|
// on its length.
|
|
var suffixIndex int
|
|
switch len(host) {
|
|
case V2Len:
|
|
suffixIndex = V2Len - OnionSuffixLen
|
|
case V3Len:
|
|
suffixIndex = V3Len - OnionSuffixLen
|
|
default:
|
|
return false
|
|
}
|
|
|
|
// Make sure the host ends with the ".onion" suffix.
|
|
if host[suffixIndex:] != OnionSuffix {
|
|
return false
|
|
}
|
|
|
|
// We'll now attempt to decode the host without its suffix, as the
|
|
// suffix includes invalid characters. This will tell us if the host is
|
|
// actually valid if successful.
|
|
host = host[:suffixIndex]
|
|
if _, err := Base32Encoding.DecodeString(host); err != nil {
|
|
return false
|
|
}
|
|
|
|
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
|
|
}
|