torsvc: Added new module to house Tor functions

This commit adds a new module named 'torsvc' which houses all Tor
functionality in an attempt to isolate it and make it reusable in
other projecs. Some additional tweaks were made to config.go and
to the bootstrapper.
This commit is contained in:
nsa 2017-10-24 22:02:29 -04:00 committed by Olaoluwa Osuntokun
parent e2142c778f
commit e132ad8433
5 changed files with 205 additions and 154 deletions

159
config.go

@ -17,15 +17,12 @@ import (
"strings"
"time"
"github.com/btcsuite/btcd/connmgr"
flags "github.com/jessevdk/go-flags"
"github.com/btcsuite/go-socks/socks"
"github.com/lightningnetwork/lnd/brontide"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/miekg/dns"
"github.com/lightningnetwork/lnd/torsvc"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcutil"
"golang.org/x/net/proxy"
)
const (
@ -163,7 +160,9 @@ type config struct {
CPUProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"`
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65535"`
TorSocks string `long:"torsocks" description:"The port that Tor's exposed SOCKS5 proxy is listening on -- NOTE port must be between 1024 and 65535"`
TorDNS string `long:"tordns" description:"The DNS server that Tor will use for SRV queries"`
DebugHTLC bool `long:"debughtlc" description:"Activate the debug htlc mode. With the debug HTLC mode, all payments sent use a pre-determined R-Hash. Additionally, all HTLCs sent to a node with the debug HTLC R-Hash are immediately settled in the next available state transition."`
HodlHTLC bool `long:"hodlhtlc" description:"Activate the hodl HTLC mode. With hodl HTLC mode, all incoming HTLCs will be accepted by the receiving node, but no attempt will be made to settle the payment with the sender."`
@ -192,7 +191,6 @@ type config struct {
lookup func(string) ([]string, error)
lookupSRV func(string, string, string) (string, []*net.SRV, error)
resolveTCP func(string, string) (*net.TCPAddr, error)
// TODO(eugene) - onionDial & related onion functions
}
// loadConfig initializes and parses the config using a config file and command
@ -297,15 +295,15 @@ func loadConfig() (*config, error) {
return nil, err
}
// Setup dial and DNS resolution (lookup, lookupSRV, resolveTCP) functions
// depending on the specified options. The default is to use the standard
// golang "net" package functions. When Tor's proxy is specified, the dial
// function is set to the proxy specific dial function and the DNS
// resolution functions use Tor.
// Setup dial and DNS resolution functions depending on the specified
// options. The default is to use the standard golang "net" package
// functions. When Tor's proxy is specified, the dial function is set to
// the proxy specific dial function and the DNS resolution functions use
// Tor.
cfg.lookup = net.LookupHost
cfg.lookupSRV = net.LookupSRV
cfg.resolveTCP = net.ResolveTCPAddr
if cfg.TorSocks != "" {
if cfg.TorSocks != "" && cfg.TorDNS != "" {
// Validate Tor port number
torport, err := strconv.Atoi(cfg.TorSocks)
if err != nil || torport < 1024 || torport > 65535 {
@ -316,8 +314,6 @@ func loadConfig() (*config, error) {
return nil, err
}
proxyAddr := "127.0.0.1:" + cfg.TorSocks
// If ExternalIPs is set, throw an error since we cannot
// listen for incoming connections via Tor's SOCKS5 proxy.
if len(cfg.ExternalIPs) != 0 {
@ -328,49 +324,22 @@ func loadConfig() (*config, error) {
return nil, err
}
// We use go-socks for the dialer that actually connects to peers
// since it preserves the connection information in a *ProxiedAddr
// struct. golang's proxy package abstracts this away and is
// therefore unsuitable.
p := &socks.Proxy{
Addr: proxyAddr,
}
cfg.dial = p.Dial
cfg.dial = torsvc.TorDial(cfg.TorSocks)
// If we are using Tor, since we only want connections routed
// through Tor, listening is disabled.
cfg.DisableListen = true
dialer, err := proxy.SOCKS5(
"tcp",
proxyAddr,
nil,
proxy.Direct,
)
if err != nil {
str := "%s: Unable to set up SRV dialer: %v"
err := fmt.Errorf(str, funcName, err)
return nil, err
}
// Perform all DNS resolution through the Tor proxy. We set the
// lookup, lookupSRV, & resolveTCP functions here.
cfg.lookup = func(host string) ([]string, error) {
return proxyLookup(host, proxyAddr)
return torsvc.TorLookupHost(host, cfg.TorSocks)
}
// lookupSRV uses golang's proxy package since go-socks will
// cause the SRV request to hang.
cfg.lookupSRV = func(service, proto, name string) (cname string,
addrs []*net.SRV, err error) {
return proxySRV(dialer, service, proto, name)
return torsvc.TorLookupSRV(service, proto, name,
cfg.TorSocks, cfg.TorDNS)
}
// resolveTCP uses TCP over Tor to resolve TCP addresses and
// returns a pointer to a net.TCPAddr struct in the same manner
// that the net.ResolveTCPAddr function does.
cfg.resolveTCP = func(network, address string) (*net.TCPAddr, error) {
return proxyTCP(address, cfg.lookup)
return torsvc.TorResolveTCP(address, cfg.TorSocks)
}
}
@ -744,106 +713,6 @@ func lndResolveTCP(network, address string) (*net.TCPAddr, error) {
return cfg.resolveTCP(network, address)
}
// proxyLookup uses Tor to resolve DNS via the SOCKS extension they provide for
// resolution over the Tor network. Only IPV4 is supported. Unlike btcd's
// TorLookupIP function, however, this function returns a ([]string, error)
// tuple.
func proxyLookup(host string, proxy string) ([]string, error) {
ip, err := connmgr.TorLookupIP(host, proxy)
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
}
// proxyTCP uses Tor's proxy to resolve tcp addresses instead of the system resolver
// that ResolveTCPAddr and related functions use. This resolver only queries DNS
// servers in the case a hostname is passed in the address parameter. Only TCP
// resolution is supported.
func proxyTCP(address string, lookupFn func(string) ([]string, error)) (*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 := lookupFn(host)
if err != nil {
return nil, err
}
// Convert port to an int
p, err := strconv.Atoi(port)
if err != nil {
return nil, err
}
// Return a pointer to a net.TCPAddr struct exactly like ResolveTCPAddr.
return &net.TCPAddr{
IP: net.ParseIP(ip[0]),
Port: p,
}, nil
}
// proxySRV uses Tor's proxy to route DNS SRV requests. Tor does not support
// SRV queries. Therefore, we must route all SRV requests THROUGH the Tor proxy
// and connect directly to a DNS server and query it. Note: the DNS server must
// be a Lightning DNS server that follows the BOLT#10 specification.
func proxySRV(dialer proxy.Dialer, service, proto, name string) (string, []*net.SRV, error) {
// _service._proto.name as described in RFC#2782.
host := "_" + service + "._" + proto + "." + name + "."
// Dial Eugene's lseed.
conn, err := dialer.Dial("tcp", "108.26.210.110:8053")
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 resp.Rcode != dns.RcodeSuccess {
// TODO(eugene) - table of Rcode fmt.Errors
return "", nil, fmt.Errorf("Unsuccessful SRV request, received: %d",
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
}
func parseRPCParams(cConfig *chainConfig, nodeConfig interface{}, net chainCode,
funcName string) error {

@ -247,7 +247,8 @@ type DNSSeedBootstrapper struct {
// receive the IP address of the current authoritative DNS server for
// the network seed.
dnsSeeds [][2]string
lookupFns []interface{}
lookupHost func(string) ([]string, error)
lookupSRV func(string, string, string) (string, []*net.SRV, error)
}
// A compile time assertion to ensure that DNSSeedBootstrapper meets the
@ -261,10 +262,13 @@ var _ NetworkPeerBootstrapper = (*ChannelGraphBootstrapper)(nil)
// used as a fallback for manual TCP resolution in the case of an error
// receiving the UDP response. The second host should return a single A record
// with the IP address of the authoritative name server.
func NewDNSSeedBootstrapper(seeds [][2]string, lookupFns []interface{}) (NetworkPeerBootstrapper, error) {
func NewDNSSeedBootstrapper(seeds [][2]string, lookupHost func(string) ([]string, error),
lookupSRV func(string, string, string) (string, []*net.SRV, error)) (
NetworkPeerBootstrapper, error) {
return &DNSSeedBootstrapper{
dnsSeeds: seeds,
lookupFns: lookupFns,
lookupHost: lookupHost,
lookupSRV: lookupSRV,
}, nil
}
@ -351,7 +355,7 @@ search:
// keys of nodes. We use the lndLookupSRV function for
// this task.
primarySeed := dnsSeedTuple[0]
_, addrs, err := d.lookupFns[1].(func(string, string, string) (string, []*net.SRV, error))("nodes", "tcp", primarySeed)
_, addrs, err := d.lookupSRV("nodes", "tcp", primarySeed)
if err != nil {
log.Tracef("Unable to lookup SRV records via " +
"primary seed, falling back to secondary")
@ -390,7 +394,7 @@ search:
// key. We use the lndLookup function for this
// task.
bechNodeHost := nodeSrv.Target
addrs, err := d.lookupFns[0].(func(string) ([]string, error))(bechNodeHost)
addrs, err := d.lookupHost(bechNodeHost)
if err != nil {
return nil, err
}

@ -605,7 +605,8 @@ func initNetworkBootstrappers(s *server) ([]discovery.NetworkPeerBootstrapper, e
dnsBootStrapper, err := discovery.NewDNSSeedBootstrapper(
dnsSeeds,
[]interface{}{lndLookup, lndLookupSRV},
lndLookup,
lndLookupSRV,
)
if err != nil {
return nil, err

15
torsvc/README.md Normal file

@ -0,0 +1,15 @@
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
```

162
torsvc/torsvc.go Normal file

@ -0,0 +1,162 @@
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 go-socks proxy dialer function that can be used to make
// connections to peers through Tor's exposed socks proxy.
func TorDial(socksPort string) func(string, string) (net.Conn, error) {
p := &socks.Proxy{Addr: localhost + ":" + socksPort}
return p.Dial
}
// 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. This resolver
// only queries DNS servers in the case that a hostname is passed in the
// address parameter. 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
}