diff --git a/tor/README.md b/tor/README.md new file mode 100644 index 00000000..a3d84d1d --- /dev/null +++ b/tor/README.md @@ -0,0 +1,15 @@ +tor +=== + +The tor package contains utility functions that allow for interacting with the +Tor daemon. So far, supported functions include routing all traffic over Tor's +exposed socks5 proxy and routing DNS queries over Tor (A, AAAA, SRV). In the +future more features will be added: automatic setup of v2 hidden service +functionality, control port functionality, and handling manually setup v3 hidden +services. + +## Installation and Updating + +```bash +$ go get -u github.com/lightningnetwork/lnd/tor +``` diff --git a/tor/net.go b/tor/net.go new file mode 100644 index 00000000..febf7227 --- /dev/null +++ b/tor/net.go @@ -0,0 +1,104 @@ +package tor + +import ( + "errors" + "net" +) + +// TODO: this interface and its implementations should ideally be moved +// elsewhere as they are not Tor-specific. + +// Net is an interface housing a Dial function and several DNS functions that +// allows us to abstract the implementations of these functions over different +// networks, e.g. clearnet, Tor net, etc. +type Net interface { + // Dial connects to the address on the named network. + Dial(network, address string) (net.Conn, error) + + // LookupHost performs DNS resolution on a given host and returns its + // addresses. + LookupHost(host string) ([]string, error) + + // LookupSRV tries to resolve an SRV query of the given service, + // protocol, and domain name. + LookupSRV(service, proto, name string) (string, []*net.SRV, error) + + // ResolveTCPAddr resolves TCP addresses. + ResolveTCPAddr(network, address string) (*net.TCPAddr, error) +} + +// ClearNet is an implementation of the Net interface that defines behaviour +// for regular network connections. +type ClearNet struct{} + +// Dial on the regular network uses net.Dial +func (r *ClearNet) Dial(network, address string) (net.Conn, error) { + return net.Dial(network, address) +} + +// LookupHost for regular network uses the net.LookupHost function +func (r *ClearNet) LookupHost(host string) ([]string, error) { + return net.LookupHost(host) +} + +// LookupSRV for regular network uses net.LookupSRV function +func (r *ClearNet) LookupSRV(service, proto, name string) (string, []*net.SRV, error) { + return net.LookupSRV(service, proto, name) +} + +// ResolveTCPAddr for regular network uses net.ResolveTCPAddr function +func (r *ClearNet) ResolveTCPAddr(network, address string) (*net.TCPAddr, error) { + return net.ResolveTCPAddr(network, address) +} + +// ProxyNet is an implementation of the Net interface that defines behaviour +// for Tor network connections. +type ProxyNet struct { + // SOCKS is the host:port which Tor's exposed SOCKS5 proxy is listening + // on. + SOCKS string + + // DNS is the host:port of the DNS server for Tor to use for SRV + // queries. + DNS string + + // StreamIsolation is a bool that determines if we should force the + // creation of a new circuit for this connection. If true, then this + // means that our traffic may be harder to correlate as each connection + // will now use a distinct circuit. + StreamIsolation bool +} + +// Dial uses the Tor Dial function in order to establish connections through +// Tor. Since Tor only supports TCP connections, only TCP networks are allowed. +func (p *ProxyNet) Dial(network, address string) (net.Conn, error) { + switch network { + case "tcp", "tcp4", "tcp6": + default: + return nil, errors.New("cannot dial non-tcp network via Tor") + } + return Dial(address, p.SOCKS, p.StreamIsolation) +} + +// LookupHost uses the Tor LookupHost function in order to resolve hosts over +// Tor. +func (p *ProxyNet) LookupHost(host string) ([]string, error) { + return LookupHost(host, p.SOCKS) +} + +// LookupSRV uses the Tor LookupSRV function in order to resolve SRV DNS queries +// over Tor. +func (p *ProxyNet) LookupSRV(service, proto, name string) (string, []*net.SRV, error) { + return LookupSRV(service, proto, name, p.SOCKS, p.DNS, p.StreamIsolation) +} + +// ResolveTCPAddr uses the Tor ResolveTCPAddr function in order to resolve TCP +// addresses over Tor. +func (p *ProxyNet) ResolveTCPAddr(network, address string) (*net.TCPAddr, error) { + switch network { + case "tcp", "tcp4", "tcp6": + default: + return nil, errors.New("cannot dial non-tcp network via Tor") + } + return ResolveTCPAddr(address, p.SOCKS) +} diff --git a/tor/tor.go b/tor/tor.go new file mode 100644 index 00000000..68e18680 --- /dev/null +++ b/tor/tor.go @@ -0,0 +1,160 @@ +package tor + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "net" + "strconv" + + "github.com/miekg/dns" + "github.com/roasbeef/btcd/connmgr" + "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", + } +) + +// Dial establishes a connection to the address 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 Dial(address, socksAddr string, streamIsolation bool) (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:]), + } + } + + // Establish the connection through Tor's SOCKS proxy. + dialer, err := proxy.SOCKS5("tcp", socksAddr, auth, proxy.Direct) + if err != nil { + return nil, 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) (string, []*net.SRV, error) { + + // Connect to the DNS server we'll be using to query SRV records. + conn, err := Dial(dnsServer, socksAddr, streamIsolation) + 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 + } + + ip, err := LookupHost(host, socksAddr) + if err != nil { + return nil, err + } + + p, err := strconv.Atoi(port) + if err != nil { + return nil, err + } + + return &net.TCPAddr{ + IP: net.ParseIP(ip[0]), + Port: p, + }, nil +} diff --git a/torsvc/README.md b/torsvc/README.md deleted file mode 100644 index a9d57f96..00000000 --- a/torsvc/README.md +++ /dev/null @@ -1,15 +0,0 @@ -torsvc -========== - -The torsvc package contains utility functions that allow for interacting -with the Tor daemon. So far, supported functions include routing all traffic -over Tor's exposed socks5 proxy and routing DNS queries over Tor (A, AAAA, SRV). -In the future more features will be added: automatic setup of v2 hidden service -functionality, control port functionality, and handling manually setup v3 hidden -services. - -## Installation and Updating - -```bash -$ go get -u github.com/lightningnetwork/lnd/torsvc -``` diff --git a/torsvc/interface.go b/torsvc/interface.go deleted file mode 100644 index 451615d9..00000000 --- a/torsvc/interface.go +++ /dev/null @@ -1,24 +0,0 @@ -package torsvc - -import ( - "net" -) - -// Net is an interface housing a Dial function and several DNS functions, to -// abstract the implementation of these functions over both Regular and Tor -type Net interface { - // Dial accepts a network and address and returns a connection to a remote - // peer. - Dial(string, string) (net.Conn, error) - - // LookupHost performs DNS resolution on a given hostname and returns - // addresses of that hostname - LookupHost(string) ([]string, error) - - // LookupSRV allows a service and network to be specified and makes queries - // to a given DNS server for SRV queries. - LookupSRV(string, string, string) (string, []*net.SRV, error) - - // ResolveTCPAddr is a used to resolve publicly advertised TCP addresses. - ResolveTCPAddr(string, string) (*net.TCPAddr, error) -} diff --git a/torsvc/net.go b/torsvc/net.go deleted file mode 100644 index 859189ac..00000000 --- a/torsvc/net.go +++ /dev/null @@ -1,76 +0,0 @@ -package torsvc - -import ( - "fmt" - "net" -) - -// RegularNet is an implementation of the Net interface that defines behaviour -// for Regular network connections -type RegularNet struct{} - -// Dial on the regular network uses net.Dial -func (r *RegularNet) Dial(network, address string) (net.Conn, error) { - return net.Dial(network, address) -} - -// LookupHost for regular network uses the net.LookupHost function -func (r *RegularNet) LookupHost(host string) ([]string, error) { - return net.LookupHost(host) -} - -// LookupSRV for regular network uses net.LookupSRV function -func (r *RegularNet) LookupSRV(service, proto, name string) (string, []*net.SRV, error) { - return net.LookupSRV(service, proto, name) -} - -// ResolveTCPAddr for regular network uses net.ResolveTCPAddr function -func (r *RegularNet) ResolveTCPAddr(network, address string) (*net.TCPAddr, error) { - return net.ResolveTCPAddr(network, address) -} - -// TorProxyNet is an implementation of the Net interface that defines behaviour -// for Tor network connections -type TorProxyNet struct { - // TorDNS is the IP:PORT of the DNS server for Tor to use for SRV queries - TorDNS string - - // TorSocks is the port which Tor's exposed SOCKS5 proxy is listening on. - // This is used for an outbound-only mode, so the node will not listen for - // incoming connections - TorSocks string - - // StreamIsolation is a bool that determines if we should force the - // creation of a new circuit for this connection. If true, then this - // means that our traffic may be harder to correlate as each connection - // will now use a distinct circuit. - StreamIsolation bool -} - -// Dial on the Tor network uses the torsvc TorDial() function, and requires -// that network specified be tcp because only that is supported -func (t *TorProxyNet) Dial(network, address string) (net.Conn, error) { - if network != "tcp" { - return nil, fmt.Errorf("Cannot dial non-tcp network via Tor") - } - return TorDial(address, t.TorSocks, t.StreamIsolation) -} - -// LookupHost on Tor network uses the torsvc TorLookupHost function. -func (t *TorProxyNet) LookupHost(host string) ([]string, error) { - return TorLookupHost(host, t.TorSocks) -} - -// LookupSRV on Tor network uses the torsvc TorLookupHost function. -func (t *TorProxyNet) LookupSRV(service, proto, name string) (string, []*net.SRV, error) { - return TorLookupSRV(service, proto, name, t.TorSocks, t.TorDNS) -} - -// ResolveTCPAddr on Tor network uses the towsvc TorResolveTCP function, and -// requires network to be "tcp" because only "tcp" is supported -func (t *TorProxyNet) ResolveTCPAddr(network, address string) (*net.TCPAddr, error) { - if network != "tcp" { - return nil, fmt.Errorf("Cannot dial non-tcp network via Tor") - } - return TorResolveTCP(address, t.TorSocks) -} diff --git a/torsvc/torsvc.go b/torsvc/torsvc.go deleted file mode 100644 index 29369373..00000000 --- a/torsvc/torsvc.go +++ /dev/null @@ -1,168 +0,0 @@ -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 -}