package discovery import ( "bytes" "crypto/rand" "crypto/sha256" "fmt" "net" "strings" "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" ) // NetworkPeerBootstrapper is an interface that represents an initial peer // boostrap mechanism. This interface is to be used to bootstrap a new peer to // the connection by providing it with the pubkey+address of a set of existing // peers on the network. Several bootstrap mechanisms can be implemented such // as DNS, in channel graph, DHT's, etc. type NetworkPeerBootstrapper interface { // 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 passed set of // node nodes allows the caller to ignore a set of nodes perhaps // because they already have connections established. SampleNodeAddrs(numAddrs uint32, ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) // Name returns a human readable string which names the concrete // implementation of the NetworkPeerBootstrapper. Name() string } // MultiSourceBootstrap attempts to utilize a set of NetworkPeerBootstrapper // passed in to return the target (numAddrs) number of peer addresses that can // be used to bootstrap a peer just joining the Lightning Network. Each // bootstrapper will be queried successively until the target amount is met. If // the ignore map is populated, then the bootstrappers will be instructed to // skip those nodes. func MultiSourceBootstrap(ignore map[autopilot.NodeID]struct{}, numAddrs uint32, bootStrappers ...NetworkPeerBootstrapper) ([]*lnwire.NetAddress, error) { var addrs []*lnwire.NetAddress for _, bootStrapper := range bootStrappers { // If we already have enough addresses, then we can exit early // w/o querying the additional boostrappers. if uint32(len(addrs)) >= numAddrs { break } log.Infof("Attempting to bootstrap with: %v", bootStrapper.Name()) // If we still need additional addresses, then we'll compute // the number of address remaining that we need to fetch. numAddrsLeft := numAddrs - uint32(len(addrs)) log.Tracef("Querying for %v addresses", numAddrsLeft) netAddrs, err := bootStrapper.SampleNodeAddrs(numAddrsLeft, ignore) if err != nil { // If we encounter an error with a bootstrapper, then // we'll continue on to the next available // bootstrapper. log.Errorf("Unable to query bootstrapper %v: %v", bootStrapper.Name(), err) continue } addrs = append(addrs, netAddrs...) } log.Infof("Obtained %v addrs to bootstrap network with", len(addrs)) return addrs, nil } // ChannelGraphBootstrapper is an implementation of the NetworkPeerBootstrapper // which attempts to retrieve advertised peers directly from the active channel // graph. This instance requires a backing autopilot.ChannelGraph instance in // order to operate properly. type ChannelGraphBootstrapper struct { chanGraph autopilot.ChannelGraph // hashAccumulator is a set of 32 random bytes that are read upon the // creation of the channel graph boostrapper. We use this value to // randomly select nodes within the known graph to connect to. After // each selection, we rotate the accumulator by hashing it with itself. hashAccumulator [32]byte tried map[autopilot.NodeID]struct{} } // A compile time assertion to ensure that ChannelGraphBootstrapper meets the // NetworkPeerBootstrapper interface. var _ NetworkPeerBootstrapper = (*ChannelGraphBootstrapper)(nil) // NewGraphBootstrapper returns a new instance of a ChannelGraphBootstrapper // backed by an active autopilot.ChannelGraph instance. This type of network // peer bootstrapper will use the authenticated nodes within the known channel // graph to bootstrap connections. func NewGraphBootstrapper(cg autopilot.ChannelGraph) (NetworkPeerBootstrapper, error) { c := &ChannelGraphBootstrapper{ chanGraph: cg, tried: make(map[autopilot.NodeID]struct{}), } if _, err := rand.Read(c.hashAccumulator[:]); err != nil { return nil, err } return c, 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. // // NOTE: Part of the NetworkPeerBootstrapper interface. func (c *ChannelGraphBootstrapper) SampleNodeAddrs(numAddrs uint32, ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) { // We'll merge the ignore map with our currently selected map in order // to ensure we don't return any duplicate nodes. for n := range ignore { c.tried[n] = struct{}{} } // In order to bootstrap, we'll iterate all the nodes in the channel // graph, accumulating nodes until either we go through all active // nodes, or we reach our limit. We ensure that we meet the randomly // sample constraint as we maintain an xor accumulator to ensure we // randomly sample nodes independent of the iteration of the channel // graph. sampleAddrs := func() ([]*lnwire.NetAddress, error) { var ( a []*lnwire.NetAddress // We'll create a special error so we can return early // and abort the transaction once we find a match. errFound = fmt.Errorf("found node") ) err := c.chanGraph.ForEachNode(func(node autopilot.Node) error { nID := autopilot.NewNodeID(node.PubKey()) if _, ok := c.tried[nID]; ok { return nil } // We'll select the first node we come across who's // public key is less than our current accumulator // value. When comparing, we skip the first byte as // it's 50/50. If it isn't less, than then we'll // continue forward. nodePub := node.PubKey().SerializeCompressed()[1:] if bytes.Compare(c.hashAccumulator[:], nodePub) > 0 { return nil } for _, nodeAddr := range node.Addrs() { // If we haven't yet reached our limit, then // we'll copy over the details of this node // into the set of addresses to be returned. tcpAddr, ok := nodeAddr.(*net.TCPAddr) if !ok { // If this isn't a valid TCP address, // then we'll ignore it as currently // we'll only attempt to connect out to // TCP peers. return nil } // At this point, we've found an eligible node, // so we'll return early with our shibboleth // error. a = append(a, &lnwire.NetAddress{ IdentityKey: node.PubKey(), Address: tcpAddr, }) } c.tried[nID] = struct{}{} return errFound }) if err != nil && err != errFound { return nil, err } return a, nil } // We'll loop and sample new addresses from the graph source until // we've reached our target number of outbound connections or we hit 50 // attempts, which ever comes first. var ( addrs []*lnwire.NetAddress tries uint32 ) for tries < 30 && uint32(len(addrs)) < numAddrs { sampleAddrs, err := sampleAddrs() if err != nil { return nil, err } tries++ // We'll now rotate our hash accumulator one value forwards. c.hashAccumulator = sha256.Sum256(c.hashAccumulator[:]) // If this attempt didn't yield any addresses, then we'll exit // early. if len(sampleAddrs) == 0 { continue } addrs = append(addrs, sampleAddrs...) } log.Tracef("Ending hash accumulator state: %x", c.hashAccumulator) return addrs, nil } // Name returns a human readable string which names the concrete implementation // of the NetworkPeerBootstrapper. // // NOTE: Part of the NetworkPeerBootstrapper interface. func (c *ChannelGraphBootstrapper) Name() string { return "Authenticated Channel Graph" } // DNSSeedBootstrapper as an implementation of the NetworkPeerBootstrapper // interface which implements peer bootstrapping via a spcial DNS seed as // defined in BOLT-0010. For further details concerning Lightning's current DNS // boot strapping protocol, see this link: // * https://github.com/lightningnetwork/lightning-rfc/blob/master/10-dns-bootstrap.md type DNSSeedBootstrapper struct { // 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 // NetworkPeerjBootstrapper interface. 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. 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 [][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: %v", 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 // successively to retrieve eligible target nodes. func (d *DNSSeedBootstrapper) SampleNodeAddrs(numAddrs uint32, ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) { var netAddrs []*lnwire.NetAddress // We'll continue this loop until we reach our target address limit. // Each SRV query to the seed will return 25 random nodes, so we can // continue to query until we reach our target. search: for uint32(len(netAddrs)) < numAddrs { 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. primarySeed := dnsSeedTuple[0] _, addrs, err := net.LookupSRV("nodes", "tcp", primarySeed) if err != nil { 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", spew.Sdump(addrs)) // Next, we'll need to issue an A record request for // each of the nodes, skipping it if nothing comes // back. for _, nodeSrv := range addrs { if uint32(len(netAddrs)) >= numAddrs { break search } // With the SRV target obtained, we'll now // perform another query to obtain the IP // address for the matching bech32 encoded node // key. bechNodeHost := nodeSrv.Target addrs, err := net.LookupHost(bechNodeHost) if err != nil { return nil, err } if len(addrs) == 0 { log.Tracef("No addresses for %v, skipping", bechNodeHost) continue } log.Tracef("Attempting to convert: %v", bechNodeHost) // If we have a set of valid addresses, then // we'll need to parse the public key from the // original bech32 encoded string. bechNode := strings.Split(bechNodeHost, ".") _, nodeBytes5Bits, err := bech32.Decode(bechNode[0]) if err != nil { return nil, err } // Once we have the bech32 decoded pubkey, // we'll need to convert the 5-bit word // grouping into our regular 8-bit word // grouping so we can convert it into a public // key. nodeBytes, err := bech32.ConvertBits( nodeBytes5Bits, 5, 8, false, ) if err != nil { return nil, err } nodeKey, err := btcec.ParsePubKey( nodeBytes, btcec.S256(), ) if err != nil { return nil, err } // If we have an ignore list, and this node is // in the ignore list, then we'll go to the // next candidate. if ignore != nil { nID := autopilot.NewNodeID(nodeKey) if _, ok := ignore[nID]; ok { continue } } // Finally we'll convert the host:port peer to // a proper TCP address to use within the // lnwire.NetAddress. addr := fmt.Sprintf("%v:%v", addrs[0], nodeSrv.Port) tcpAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { return nil, err } // Finally, with all the information parsed, // we'll return this fully valid address as a // connection attempt. lnAddr := &lnwire.NetAddress{ IdentityKey: nodeKey, Address: tcpAddr, } log.Tracef("Obtained %v as valid reachable "+ "node", lnAddr) netAddrs = append(netAddrs, lnAddr) } } } return netAddrs, nil } // Name returns a human readable string which names the concrete // implementation of the NetworkPeerBootstrapper. func (d *DNSSeedBootstrapper) Name() string { return fmt.Sprintf("BOLT-0010 DNS Seed: %v", d.dnsSeeds) }