From 5b226a9d37b3a9d0b2f874f40d50d4c575ff0e55 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 30 Oct 2017 19:00:19 -0700 Subject: [PATCH] discovery: add TCP fallback for DNSSeedBootstrapper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this commit, we add a TCP fallback option for the DNSSeedBootstrapper. We’ve received many reports of users unable to bootstrap properly to the network due to the size of the SRV records we currently return. It has been observed that many revolvers will simply truncate and ignore the response due to the (current size). To resolve (no pun intended) we now attempt to detect this failure mode and will fallback to a manual TCP resolution in the case that our SRV query over UDP fails. We do this by querying the special record at the "soa." sub-domain of supporting DNS servers. The retuned IP address will be the IP address of the authoritative DNS server. Once we have this IP address, we'll connect manually over TCP to request the SRV record. This is necessary as the records we return are currently too large for a class of resolvers, causing them to be filtered out. --- discovery/bootstrapper.go | 106 +++++++++++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 7 deletions(-) diff --git a/discovery/bootstrapper.go b/discovery/bootstrapper.go index fc970ddf..ae624ec7 100644 --- a/discovery/bootstrapper.go +++ b/discovery/bootstrapper.go @@ -11,6 +11,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/autopilot" "github.com/lightningnetwork/lnd/lnwire" + "github.com/miekg/dns" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcutil/bech32" ) @@ -237,7 +238,14 @@ func (c *ChannelGraphBootstrapper) Name() string { // boot strapping protocol, see this link: // * https://github.com/lightningnetwork/lightning-rfc/blob/master/10-dns-bootstrap.md type DNSSeedBootstrapper struct { - dnsSeeds []string + // dnsSeeds is an array of two tuples we'll use for bootstrapping. The + // first item in the tuple is the primary host we'll use to attempt the + // SRV lookup we require. If we're unable to receive a response over + // UDP, then we'll fall back to manual TCP resolution. The second item + // in the tuple is a special A record that we'll query in order to + // receive the IP address of the current authoritative DNS server for + // the network seed. + dnsSeeds [][2]string } // A compile time assertion to ensure that DNSSeedBootstrapper meets the @@ -246,17 +254,83 @@ var _ NetworkPeerBootstrapper = (*ChannelGraphBootstrapper)(nil) // NewDNSSeedBootstrapper returns a new instance of the DNSSeedBootstrapper. // The set of passed seeds should point to DNS servers that properly implement -// Lighting's DNS peer bootstrapping protocol as defined in BOLT-0010. -// +// Lighting's DNS peer bootstrapping protocol as defined in BOLT-0010. The set +// of passed DNS seeds should come in pairs, with the second host name to be +// 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 []string) (NetworkPeerBootstrapper, error) { +func NewDNSSeedBootstrapper(seeds [][2]string) (NetworkPeerBootstrapper, error) { return &DNSSeedBootstrapper{ dnsSeeds: seeds, }, nil } +// fallBackSRVLookup attempts to manually query for SRV records we need to +// properly bootstrap. We do this by querying the special record at the "soa." +// sub-domain of supporting DNS servers. The retuned IP address will be the IP +// address of the authoritative DNS server. Once we have this IP address, we'll +// connect manually over TCP to request the SRV record. This is necessary as +// the records we return are currently too large for a class of resolvers, +// causing them to be filtered out. +func fallBackSRVLookup(soaShim string) ([]*net.SRV, error) { + log.Tracef("Attempting to query fallback DNS seed") + + // First, we'll lookup the IP address of the server that will act as + // our shim. + addrs, err := net.LookupHost(soaShim) + if err != nil { + return nil, err + } + + // Once we have the IP address, we'll establish a TCP connection using + // port 53. + dnsServer := net.JoinHostPort(addrs[0], "53") + conn, err := net.Dial("tcp", dnsServer) + if err != nil { + return nil, err + } + + dnsHost := "_nodes._tcp.nodes.lightning.directory." + dnsConn := &dns.Conn{Conn: conn} + defer dnsConn.Close() + + // With the connection established, we'll craft our SRV query, write + // toe request, then wait for the server to give our response. + msg := new(dns.Msg) + msg.SetQuestion(dnsHost, dns.TypeSRV) + if err := dnsConn.WriteMsg(msg); err != nil { + return nil, err + } + 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", resp.Rcode) + } + + // Retrieve the RR(s) of the Answer section, and covert to the format + // that net.LookupSRV would normally return. + 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 +} + // SampleNodeAddrs uniformly samples a set of specified address from the // network peer bootstrapper source. The num addrs field passed in denotes how // many valid peer addresses to return. The set of DNS seeds are used @@ -271,13 +345,31 @@ func (d *DNSSeedBootstrapper) SampleNodeAddrs(numAddrs uint32, // continue to query until we reach our target. search: for uint32(len(netAddrs)) < numAddrs { - for _, dnsSeed := range d.dnsSeeds { + 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. - _, addrs, err := net.LookupSRV("nodes", "tcp", dnsSeed) + primarySeed := dnsSeedTuple[0] + _, addrs, err := net.LookupSRV("nodes", "tcp", primarySeed) if err != nil { - return nil, err + log.Tracef("Unable to lookup SRV records via " + + "primary seed, falling back to secondary") + + // If the host of the secondary seed is blank, + // then we'll bail here as we can't proceed. + if dnsSeedTuple[1] == "" { + return nil, fmt.Errorf("Secondary seed is blank") + } + + // If we get an error when trying to query via + // the primary seed, we'll fallback to the + // secondary seed before concluding failure. + secondarySeed := dnsSeedTuple[1] + addrs, err = fallBackSRVLookup(secondarySeed) + if err != nil { + return nil, err + } + log.Tracef("Successfully queried fallback DNS seed") } log.Tracef("Retrieved SRV records from dns seed: %v",