From e2142c778feb4445c1d086f6be7115c5e9741902 Mon Sep 17 00:00:00 2001 From: nsa Date: Fri, 20 Oct 2017 17:45:23 -0400 Subject: [PATCH] multi: Added Tor support This commit adds Tor support. Users can set the --TorSocks flag to specify which port Tor's SOCKS5 proxy is listening on so that lnd can connect to it. When this flag is set, ALL traffic gets routed over Tor including DNS traffic. Special functions for DNS lookups were added, and since Tor doesn't natively support SRV requests, the proxySRV function routes connects us to a DNS server via Tor and SRV requests can be issued directly to the DNS server. Co-authored-by: MeshCollider --- brontide/conn.go | 14 ++- brontide/listener.go | 2 + channeldb/channel.go | 12 +++ channeldb/nodes.go | 5 +- config.go | 218 +++++++++++++++++++++++++++++++++++++- discovery/bootstrapper.go | 23 ++-- rpcserver.go | 3 +- server.go | 64 +++++++++-- 8 files changed, 314 insertions(+), 27 deletions(-) diff --git a/brontide/conn.go b/brontide/conn.go index da87c9c1..9903c790 100644 --- a/brontide/conn.go +++ b/brontide/conn.go @@ -32,9 +32,19 @@ var _ net.Conn = (*Conn)(nil) // remote peer located at address which has remotePub as its long-term static // public key. In the case of a handshake failure, the connection is closed and // a non-nil error is returned. -func Dial(localPriv *btcec.PrivateKey, netAddr *lnwire.NetAddress) (*Conn, error) { +func Dial(localPriv *btcec.PrivateKey, netAddr *lnwire.NetAddress, + dialer ...func(string, string) (net.Conn, error)) (*Conn, error) { ipAddr := netAddr.Address.String() - conn, err := net.Dial("tcp", ipAddr) + var conn net.Conn + var err error + if dialer == nil { + // A Tor proxy dial function WAS NOT passed in. + conn, err = net.Dial("tcp", ipAddr) + } else { + // A Tor proxy dial function WAS passed in so we use it instead + // of golang's net.Dial. + conn, err = dialer[0]("tcp", ipAddr) + } if err != nil { return nil, err } diff --git a/brontide/listener.go b/brontide/listener.go index 1cc171f6..98de432e 100644 --- a/brontide/listener.go +++ b/brontide/listener.go @@ -24,6 +24,8 @@ var _ net.Listener = (*Listener)(nil) // NewListener returns a new net.Listener which enforces the Brontide scheme // during both initial connection establishment and data transfer. +// Note: though this function uses ResolveTCPAddr, we don't need to call the +// general lndResolveTCP function since we are resolving a local address. func NewListener(localStatic *btcec.PrivateKey, listenAddr string) (*Listener, error) { addr, err := net.ResolveTCPAddr("tcp", listenAddr) diff --git a/channeldb/channel.go b/channeldb/channel.go index 8e246e3e..f93ef3b9 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -65,6 +65,12 @@ var ( // channel closure. This key should be accessed from within the // sub-bucket of a target channel, identified by its channel point. revocationLogBucket = []byte("revocation-log-key") + + // resolveTCP is a resolver that is used to resolve nodes' + // publicly advertised addresses. It is set to net.ResolveTCPAddr + // initially, but the SetResolver function can be used to change this + // to a Tor-specific resolver. + resolveTCP = net.ResolveTCPAddr ) var ( @@ -389,6 +395,12 @@ type OpenChannel struct { sync.RWMutex } +// SetResolver sets resolveTCP to a resolver other than the default +// net.ResolveTCPAddr resolver function. +func SetResolver(resolver func(string, string) (*net.TCPAddr, error)) { + resolveTCP = resolver +} + // FullSync serializes, and writes to disk the *full* channel state, using // both the active channel bucket to store the prefixed column fields, and the // remote node's ID to store the remainder of the channel state. diff --git a/channeldb/nodes.go b/channeldb/nodes.go index e1bafb14..e42a15df 100644 --- a/channeldb/nodes.go +++ b/channeldb/nodes.go @@ -275,7 +275,10 @@ func deserializeLinkNode(r io.Reader) (*LinkNode, error) { return nil, err } - addr, err := net.ResolveTCPAddr("tcp", addrString) + // We use the general resolveTCP function in case a separate + // resolver was specified in the SetResolver function. By + // default resolveTCP = net.ResolveTCPAddr. + addr, err := resolveTCP("tcp", addrString) if err != nil { return nil, err } diff --git a/config.go b/config.go index 9ef63acc..4e662aa8 100644 --- a/config.go +++ b/config.go @@ -17,11 +17,15 @@ 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/roasbeef/btcd/btcec" "github.com/roasbeef/btcutil" + "golang.org/x/net/proxy" ) const ( @@ -158,7 +162,8 @@ 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 65536"` + 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"` 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."` @@ -182,6 +187,12 @@ type config struct { Alias string `long:"alias" description:"The node alias. Used as a moniker by peers and intelligence services"` Color string `long:"color" description:"The color of the node in hex format (i.e. '#3399FF'). Used to customize node appearance in intelligence services"` + + dial func(string, string) (net.Conn, error) + 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 @@ -286,6 +297,83 @@ 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. + cfg.lookup = net.LookupHost + cfg.lookupSRV = net.LookupSRV + cfg.resolveTCP = net.ResolveTCPAddr + if cfg.TorSocks != "" { + // Validate Tor port number + torport, err := strconv.Atoi(cfg.TorSocks) + if err != nil || torport < 1024 || torport > 65535 { + str := "%s: The tor socks5 port must be between 1024 and 65535" + err := fmt.Errorf(str, funcName) + fmt.Fprintln(os.Stderr, err) + fmt.Fprintln(os.Stderr, usageMessage) + 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 { + str := "%s: Cannot set externalip flag with proxy flag - " + + "cannot listen for incoming connections via Tor's " + + "socks5 proxy" + err := fmt.Errorf(str, funcName) + 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 + + // 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) + } + + // 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) + } + + // 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) + } + } + switch { // At this moment, multiple active chains are not supported. case cfg.Litecoin.Active && cfg.Bitcoin.Active: @@ -627,10 +715,136 @@ func supportedSubsystems() []string { func noiseDial(idPriv *btcec.PrivateKey) func(net.Addr) (net.Conn, error) { return func(a net.Addr) (net.Conn, error) { lnAddr := a.(*lnwire.NetAddress) - return brontide.Dial(idPriv, lnAddr) + if cfg.dial == nil { + return brontide.Dial(idPriv, lnAddr) + } + return brontide.Dial(idPriv, lnAddr, cfg.dial) } } +// lndLookup resolves the IP address of a given host using the correct DNS +// function depending on the lookup configuration options. Lookup can be done +// via golang's system resolver or via Tor. When Tor is used, only IPv4 +// addresses are returned. +func lndLookup(host string) ([]string, error) { + return cfg.lookup(host) +} + +// lndLookupSRV queries a DNS server with SRV requests depending on the +// configuration options. SRV queries can be done via golang's system +// resolver or through (but not by!) Tor. +func lndLookupSRV(service, proto, name string) (string, []*net.SRV, error) { + return cfg.lookupSRV(service, proto, name) +} + +// lndResolveTCP resolves TCP addresses into type *net.TCPAddr. Depending on +// configuration options, this resolution can be done via golang's system +// resolver or via Tor. +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 { // If the configuration has already set the RPCUser and RPCPass, and diff --git a/discovery/bootstrapper.go b/discovery/bootstrapper.go index b880c262..cb89efe2 100644 --- a/discovery/bootstrapper.go +++ b/discovery/bootstrapper.go @@ -247,6 +247,7 @@ type DNSSeedBootstrapper struct { // receive the IP address of the current authoritative DNS server for // the network seed. dnsSeeds [][2]string + lookupFns []interface{} } // A compile time assertion to ensure that DNSSeedBootstrapper meets the @@ -260,12 +261,10 @@ 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. -// -// TODO(roasbeef): add a lookUpFunc param to pass in, so can divert queries -// over Tor in future -func NewDNSSeedBootstrapper(seeds [][2]string) (NetworkPeerBootstrapper, error) { +func NewDNSSeedBootstrapper(seeds [][2]string, lookupFns []interface{}) (NetworkPeerBootstrapper, error) { return &DNSSeedBootstrapper{ - dnsSeeds: seeds, + dnsSeeds: seeds, + lookupFns: lookupFns, }, nil } @@ -349,9 +348,10 @@ search: for _, dnsSeedTuple := range d.dnsSeeds { // We'll first query the seed with an SRV record so we // can obtain a random sample of the encoded public - // keys of nodes. + // keys of nodes. We use the lndLookupSRV function for + // this task. primarySeed := dnsSeedTuple[0] - _, addrs, err := net.LookupSRV("nodes", "tcp", primarySeed) + _, addrs, err := d.lookupFns[1].(func(string, string, string) (string, []*net.SRV, error))("nodes", "tcp", primarySeed) if err != nil { log.Tracef("Unable to lookup SRV records via " + "primary seed, falling back to secondary") @@ -387,9 +387,10 @@ search: // With the SRV target obtained, we'll now // perform another query to obtain the IP // address for the matching bech32 encoded node - // key. + // key. We use the lndLookup function for this + // task. bechNodeHost := nodeSrv.Target - addrs, err := net.LookupHost(bechNodeHost) + addrs, err := d.lookupFns[0].(func(string) ([]string, error))(bechNodeHost) if err != nil { return nil, err } @@ -441,7 +442,9 @@ search: // Finally we'll convert the host:port peer to // a proper TCP address to use within the - // lnwire.NetAddress. + // lnwire.NetAddress. We don't need to use + // the lndResolveTCP function here because we + // already have the host:port peer. addr := net.JoinHostPort(addrs[0], strconv.FormatUint(uint64(nodeSrv.Port), 10)) tcpAddr, err := net.ResolveTCPAddr("tcp", addr) diff --git a/rpcserver.go b/rpcserver.go index ef06e480..436889e2 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -589,7 +589,8 @@ func (r *rpcServer) ConnectPeer(ctx context.Context, addr = in.Addr.Host } - host, err := net.ResolveTCPAddr("tcp", addr) + // We use lndResolveTCP here in case we wish to resolve hosts over Tor. + host, err := lndResolveTCP("tcp", addr) if err != nil { return nil, err } diff --git a/server.go b/server.go index 7cb527ab..4a93aeda 100644 --- a/server.go +++ b/server.go @@ -13,6 +13,7 @@ import ( "time" "github.com/boltdb/bolt" + "github.com/btcsuite/go-socks/socks" "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/brontide" @@ -213,7 +214,9 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl, }) // If external IP addresses have been specified, add those to the list - // of this server's addresses. + // of this server's addresses. We need to use the general lndResolveTCP + // function in case we wish to resolve hosts over Tor since domains + // CAN be passed into the ExternalIPs configuration option. selfAddrs := make([]net.Addr, 0, len(cfg.ExternalIPs)) for _, ip := range cfg.ExternalIPs { var addr string @@ -224,7 +227,7 @@ func newServer(listenAddrs []string, chanDB *channeldb.DB, cc *chainControl, addr = ip } - lnAddr, err := net.ResolveTCPAddr("tcp", addr) + lnAddr, err := lndResolveTCP("tcp", addr) if err != nil { return nil, err } @@ -602,6 +605,7 @@ func initNetworkBootstrappers(s *server) ([]discovery.NetworkPeerBootstrapper, e dnsBootStrapper, err := discovery.NewDNSSeedBootstrapper( dnsSeeds, + []interface{}{lndLookup, lndLookupSRV}, ) if err != nil { return nil, err @@ -641,10 +645,18 @@ func (s *server) peerBootstrapper(numTargetPeers uint32, // With our initial set of peers obtained, we'll launch a goroutine to // attempt to connect out to each of them. We'll be waking up shortly - // below to sample how many of these connections succeeded. + // below to sample how many of these connections succeeded. We check + // cfg.dial for nil in case Tor's proxy dialer option was set in the + // configuration. for _, addr := range bootStrapAddrs { go func(a *lnwire.NetAddress) { - conn, err := brontide.Dial(s.identityPriv, a) + var conn *brontide.Conn + var err error + if cfg.dial == nil { + conn, err = brontide.Dial(s.identityPriv, a) + } else { + conn, err = brontide.Dial(s.identityPriv, a, cfg.dial) + } if err != nil { srvrLog.Errorf("unable to connect to %v: %v", a, err) @@ -754,7 +766,16 @@ func (s *server) peerBootstrapper(numTargetPeers uint32, go func(a *lnwire.NetAddress) { // TODO(roasbeef): can do AS, subnet, // country diversity, etc - conn, err := brontide.Dial(s.identityPriv, a) + // We check cfg.dial for nil in case + // a Tor's proxy dialer configuration was + // set. + var conn *brontide.Conn + var err error + if cfg.dial == nil { + conn, err = brontide.Dial(s.identityPriv, a) + } else { + conn, err = brontide.Dial(s.identityPriv, a, cfg.dial) + } if err != nil { srvrLog.Errorf("unable to connect "+ "to %v: %v", a, err) @@ -1271,10 +1292,25 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq, inbound bool) { brontideConn := conn.(*brontide.Conn) - peerAddr := &lnwire.NetAddress{ - IdentityKey: brontideConn.RemotePub(), - Address: conn.RemoteAddr().(*net.TCPAddr), - ChainNet: activeNetParams.Net, + var peerAddr *lnwire.NetAddress + if cfg.dial == nil { + peerAddr = &lnwire.NetAddress{ + IdentityKey: brontideConn.RemotePub(), + Address: conn.RemoteAddr().(*net.TCPAddr), + ChainNet: activeNetParams.Net, + } + } else { + // We are connected to a Tor SOCKS5 proxy, extract our + // connection information. + proxiedAddr := conn.RemoteAddr().(*socks.ProxiedAddr) + peerAddr = &lnwire.NetAddress{ + IdentityKey: brontideConn.RemotePub(), + Address: &net.TCPAddr{ + IP: net.ParseIP(proxiedAddr.Host), + Port: proxiedAddr.Port, + }, + ChainNet: activeNetParams.Net, + } } // With the brontide connection established, we'll now craft the local @@ -1659,8 +1695,14 @@ func (s *server) ConnectToPeer(addr *lnwire.NetAddress, perm bool) error { // If we're not making a persistent connection, then we'll attempt to // connect to the target peer. If the we can't make the connection, or // the crypto negotiation breaks down, then return an error to the - // caller. - conn, err := brontide.Dial(s.identityPriv, addr) + // caller. We check cfg.dial for nil in case Tor's proxy dialer function + // was set in the configuration options. + var conn *brontide.Conn + if cfg.dial == nil { + conn, err = brontide.Dial(s.identityPriv, addr) + } else { + conn, err = brontide.Dial(s.identityPriv, addr, cfg.dial) + } if err != nil { return err }