lnd/torsvc/torsvc.go
Olaoluwa Osuntokun 2eb9059cf7
torsvc: add support for stream isolation
In this commit, we extend the TorDial function and add a new attribute
to the TorProxyNet struct to allow the caller to opt for stream
isolation or not. Using stream isolation, we ensure that each new
connection uses a distinct circuit.
2018-02-08 20:09:04 -08:00

169 lines
4.5 KiB
Go

package torsvc
import (
"fmt"
"net"
"strconv"
"github.com/btcsuite/go-socks/socks"
"github.com/miekg/dns"
"github.com/roasbeef/btcd/connmgr"
"golang.org/x/net/proxy"
)
const (
localhost = "127.0.0.1"
)
var (
// DNS Message Response Codes, 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",
// Left out 16: "Bad OPT Version" because of duplicate keys and
// because miekg/dns does not use this message response code.
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",
}
)
// TorDial returns a connection to a remote peer via Tor's socks proxy. Only
// TCP is supported over Tor. The final argument determines if we should force
// stream isolation for this new connection. If we do, then this means this new
// connection will use a fresh circuit, rather than possibly re-using an
// existing circuit.
func TorDial(address, socksPort string, streamIsolation bool) (net.Conn, error) {
p := &socks.Proxy{
Addr: localhost + ":" + socksPort,
TorIsolation: streamIsolation,
}
return p.Dial("tcp", address)
}
// TorLookupHost performs DNS resolution on a given hostname via Tor's
// native resolver. Only IPv4 addresses are returned.
func TorLookupHost(host, socksPort string) ([]string, error) {
ip, err := connmgr.TorLookupIP(host, localhost+":"+socksPort)
if err != nil {
return nil, err
}
var addrs []string
// Only one IPv4 address is returned by the TorLookupIP function.
addrs = append(addrs, ip[0].String())
return addrs, nil
}
// TorLookupSRV 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
// Tor proxy and connect directly to a DNS server and query it.
// NOTE: TorLookupSRV uses golang's proxy package since go-socks will cause
// the SRV request to hang.
func TorLookupSRV(service, proto, name, socksPort, dnsServer string) (string,
[]*net.SRV, error) {
// _service._proto.name as described in RFC#2782.
host := "_" + service + "._" + proto + "." + name + "."
// Set up golang's proxy dialer - Tor's socks proxy doesn't support
// authentication.
dialer, err := proxy.SOCKS5(
"tcp",
localhost+":"+socksPort,
nil,
proxy.Direct,
)
if err != nil {
return "", nil, err
}
// Dial dnsServer via Tor. dnsServer must have TCP resolution enabled
// for the port we are dialing.
conn, err := dialer.Dial("tcp", dnsServer)
if err != nil {
return "", nil, err
}
// Construct the actual SRV request.
msg := new(dns.Msg)
msg.SetQuestion(host, dns.TypeSRV)
msg.RecursionDesired = true
dnsConn := &dns.Conn{Conn: conn}
defer dnsConn.Close()
// Write the SRV request.
dnsConn.WriteMsg(msg)
// Read the response.
resp, err := dnsConn.ReadMsg()
if err != nil {
return "", nil, err
}
// If the message response code was not the success code, fail.
if resp.Rcode != dns.RcodeSuccess {
return "", nil, fmt.Errorf("Unsuccessful SRV request, "+
"received: %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
}
// TorResolveTCP uses Tor's proxy to resolve TCP addresses instead of the
// system resolver that ResolveTCPAddr and related functions use. Only TCP
// resolution is supported.
func TorResolveTCP(address, socksPort 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
}
// Look up the host's IP address via Tor.
ip, err := TorLookupHost(host, socksPort)
if err != nil {
return nil, err
}
// Convert port to an int.
p, err := strconv.Atoi(port)
if err != nil {
return nil, err
}
// Return a *net.TCPAddr exactly like net.ResolveTCPAddr.
return &net.TCPAddr{
IP: net.ParseIP(ip[0]),
Port: p,
}, nil
}