github.com/decred/dcrlnd@v0.7.6/discovery/bootstrapper.go (about) 1 package discovery 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/rand" 7 "crypto/sha256" 8 "errors" 9 "fmt" 10 prand "math/rand" 11 "net" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/miekg/dns" 17 18 "github.com/davecgh/go-spew/spew" 19 "github.com/decred/dcrd/bech32" 20 "github.com/decred/dcrd/dcrec/secp256k1/v4" 21 "github.com/decred/dcrlnd/autopilot" 22 "github.com/decred/dcrlnd/lnwire" 23 "github.com/decred/dcrlnd/tor" 24 ) 25 26 // ErrNoAddressesFound is returned by the MultiSourceBootstrap to signal that 27 // after attempting all available sub-bootstrappers, none returned a usable 28 // address. 29 var ErrNoAddressesFound = errors.New("no addresses found") 30 31 func init() { 32 prand.Seed(time.Now().Unix()) 33 } 34 35 // NetworkPeerBootstrapper is an interface that represents an initial peer 36 // bootstrap mechanism. This interface is to be used to bootstrap a new peer to 37 // the connection by providing it with the pubkey+address of a set of existing 38 // peers on the network. Several bootstrap mechanisms can be implemented such 39 // as DNS, in channel graph, DHT's, etc. 40 type NetworkPeerBootstrapper interface { 41 // SampleNodeAddrs uniformly samples a set of specified address from 42 // the network peer bootstrapper source. The num addrs field passed in 43 // denotes how many valid peer addresses to return. The passed set of 44 // node nodes allows the caller to ignore a set of nodes perhaps 45 // because they already have connections established. 46 SampleNodeAddrs(ctx context.Context, numAddrs uint32, 47 ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) 48 49 // Name returns a human readable string which names the concrete 50 // implementation of the NetworkPeerBootstrapper. 51 Name() string 52 } 53 54 // MultiSourceBootstrap attempts to utilize a set of NetworkPeerBootstrapper 55 // passed in to return the target (numAddrs) number of peer addresses that can 56 // be used to bootstrap a peer just joining the Lightning Network. Each 57 // bootstrapper will be queried successively until the target amount is met. If 58 // the ignore map is populated, then the bootstrappers will be instructed to 59 // skip those nodes. 60 func MultiSourceBootstrap(ctx context.Context, ignore map[autopilot.NodeID]struct{}, numAddrs uint32, 61 bootstrappers ...NetworkPeerBootstrapper) ([]*lnwire.NetAddress, error) { 62 63 // We'll randomly shuffle our bootstrappers before querying them in 64 // order to avoid from querying the same bootstrapper method over and 65 // over, as some of these might tend to provide better/worse results 66 // than others. 67 bootstrappers = shuffleBootstrappers(bootstrappers) 68 69 var addrs []*lnwire.NetAddress 70 for _, bootstrapper := range bootstrappers { 71 // If we already have enough addresses, then we can exit early 72 // w/o querying the additional bootstrappers. 73 if uint32(len(addrs)) >= numAddrs { 74 break 75 } 76 77 log.Infof("Attempting to bootstrap with: %v", bootstrapper.Name()) 78 79 // If we still need additional addresses, then we'll compute 80 // the number of address remaining that we need to fetch. 81 numAddrsLeft := numAddrs - uint32(len(addrs)) 82 log.Tracef("Querying for %v addresses", numAddrsLeft) 83 netAddrs, err := bootstrapper.SampleNodeAddrs(ctx, numAddrsLeft, ignore) 84 if err != nil { 85 // If we encounter an error with a bootstrapper, then 86 // we'll continue on to the next available 87 // bootstrapper. 88 log.Errorf("Unable to query bootstrapper %v: %v", 89 bootstrapper.Name(), err) 90 continue 91 } 92 93 addrs = append(addrs, netAddrs...) 94 } 95 96 if len(addrs) == 0 { 97 return nil, ErrNoAddressesFound 98 } 99 100 log.Infof("Obtained %v addrs to bootstrap network with", len(addrs)) 101 102 return addrs, nil 103 } 104 105 // shuffleBootstrappers shuffles the set of bootstrappers in order to avoid 106 // querying the same bootstrapper over and over. To shuffle the set of 107 // candidates, we use a version of the Fisher–Yates shuffle algorithm. 108 func shuffleBootstrappers(candidates []NetworkPeerBootstrapper) []NetworkPeerBootstrapper { 109 shuffled := make([]NetworkPeerBootstrapper, len(candidates)) 110 perm := prand.Perm(len(candidates)) 111 112 for i, v := range perm { 113 shuffled[v] = candidates[i] 114 } 115 116 return shuffled 117 } 118 119 // ChannelGraphBootstrapper is an implementation of the NetworkPeerBootstrapper 120 // which attempts to retrieve advertised peers directly from the active channel 121 // graph. This instance requires a backing autopilot.ChannelGraph instance in 122 // order to operate properly. 123 type ChannelGraphBootstrapper struct { 124 chanGraph autopilot.ChannelGraph 125 126 // hashAccumulator is a set of 32 random bytes that are read upon the 127 // creation of the channel graph bootstrapper. We use this value to 128 // randomly select nodes within the known graph to connect to. After 129 // each selection, we rotate the accumulator by hashing it with itself. 130 hashAccumulator [32]byte 131 132 tried map[autopilot.NodeID]struct{} 133 } 134 135 // A compile time assertion to ensure that ChannelGraphBootstrapper meets the 136 // NetworkPeerBootstrapper interface. 137 var _ NetworkPeerBootstrapper = (*ChannelGraphBootstrapper)(nil) 138 139 // NewGraphBootstrapper returns a new instance of a ChannelGraphBootstrapper 140 // backed by an active autopilot.ChannelGraph instance. This type of network 141 // peer bootstrapper will use the authenticated nodes within the known channel 142 // graph to bootstrap connections. 143 func NewGraphBootstrapper(cg autopilot.ChannelGraph) (NetworkPeerBootstrapper, error) { 144 145 c := &ChannelGraphBootstrapper{ 146 chanGraph: cg, 147 tried: make(map[autopilot.NodeID]struct{}), 148 } 149 150 if _, err := rand.Read(c.hashAccumulator[:]); err != nil { 151 return nil, err 152 } 153 154 return c, nil 155 } 156 157 // SampleNodeAddrs uniformly samples a set of specified address from the 158 // network peer bootstrapper source. The num addrs field passed in denotes how 159 // many valid peer addresses to return. 160 // 161 // NOTE: Part of the NetworkPeerBootstrapper interface. 162 func (c *ChannelGraphBootstrapper) SampleNodeAddrs(ctx context.Context, 163 numAddrs uint32, ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) { 164 165 // We'll merge the ignore map with our currently selected map in order 166 // to ensure we don't return any duplicate nodes. 167 for n := range ignore { 168 log.Tracef("Ignored node %x for bootstrapping", n) 169 c.tried[n] = struct{}{} 170 } 171 172 // In order to bootstrap, we'll iterate all the nodes in the channel 173 // graph, accumulating nodes until either we go through all active 174 // nodes, or we reach our limit. We ensure that we meet the randomly 175 // sample constraint as we maintain an xor accumulator to ensure we 176 // randomly sample nodes independent of the iteration of the channel 177 // graph. 178 sampleAddrs := func() ([]*lnwire.NetAddress, error) { 179 var ( 180 a []*lnwire.NetAddress 181 182 // We'll create a special error so we can return early 183 // and abort the transaction once we find a match. 184 errFound = fmt.Errorf("found node") 185 ) 186 187 err := c.chanGraph.ForEachNode(func(node autopilot.Node) error { 188 nID := autopilot.NodeID(node.PubKey()) 189 if _, ok := c.tried[nID]; ok { 190 return nil 191 } 192 193 // We'll select the first node we come across who's 194 // public key is less than our current accumulator 195 // value. When comparing, we skip the first byte as 196 // it's 50/50. If it isn't less, than then we'll 197 // continue forward. 198 nodePubKeyBytes := node.PubKey() 199 if bytes.Compare(c.hashAccumulator[:], nodePubKeyBytes[1:]) > 0 { 200 return nil 201 } 202 203 for _, nodeAddr := range node.Addrs() { 204 // If we haven't yet reached our limit, then 205 // we'll copy over the details of this node 206 // into the set of addresses to be returned. 207 switch nodeAddr.(type) { 208 case *net.TCPAddr, *tor.OnionAddr: 209 default: 210 // If this isn't a valid address 211 // supported by the protocol, then we'll 212 // skip this node. 213 return nil 214 } 215 216 nodePub, err := secp256k1.ParsePubKey(nodePubKeyBytes[:]) 217 if err != nil { 218 return err 219 } 220 221 // At this point, we've found an eligible node, 222 // so we'll return early with our shibboleth 223 // error. 224 a = append(a, &lnwire.NetAddress{ 225 IdentityKey: nodePub, 226 Address: nodeAddr, 227 }) 228 } 229 230 c.tried[nID] = struct{}{} 231 232 return errFound 233 }) 234 if err != nil && err != errFound { 235 return nil, err 236 } 237 238 return a, nil 239 } 240 241 // We'll loop and sample new addresses from the graph source until 242 // we've reached our target number of outbound connections or we hit 50 243 // attempts, which ever comes first. 244 var ( 245 addrs []*lnwire.NetAddress 246 tries uint32 247 ) 248 for tries < 30 && uint32(len(addrs)) < numAddrs { 249 sampleAddrs, err := sampleAddrs() 250 if err != nil { 251 return nil, err 252 } 253 254 tries++ 255 256 // We'll now rotate our hash accumulator one value forwards. 257 c.hashAccumulator = sha256.Sum256(c.hashAccumulator[:]) 258 259 // If this attempt didn't yield any addresses, then we'll exit 260 // early. 261 if len(sampleAddrs) == 0 { 262 continue 263 } 264 265 addrs = append(addrs, sampleAddrs...) 266 } 267 268 log.Tracef("Ending hash accumulator state: %x", c.hashAccumulator) 269 270 return addrs, nil 271 } 272 273 // Name returns a human readable string which names the concrete implementation 274 // of the NetworkPeerBootstrapper. 275 // 276 // NOTE: Part of the NetworkPeerBootstrapper interface. 277 func (c *ChannelGraphBootstrapper) Name() string { 278 return "Authenticated Channel Graph" 279 } 280 281 // DNSSeedBootstrapper as an implementation of the NetworkPeerBootstrapper 282 // interface which implements peer bootstrapping via a special DNS seed as 283 // defined in BOLT-0010. For further details concerning Lightning's current DNS 284 // boot strapping protocol, see this link: 285 // - https://github.com/lightningnetwork/lightning-rfc/blob/master/10-dns-bootstrap.md 286 type DNSSeedBootstrapper struct { 287 // dnsSeeds is an array of two tuples we'll use for bootstrapping. The 288 // first item in the tuple is the primary host we'll use to attempt the 289 // SRV lookup we require. If we're unable to receive a response over 290 // UDP, then we'll fall back to manual TCP resolution. The second item 291 // in the tuple is a special A record that we'll query in order to 292 // receive the IP address of the current authoritative DNS server for 293 // the network seed. 294 dnsSeeds [][2]string 295 net tor.Net 296 297 // timeout is the maximum amount of time a dial will wait for a connect to 298 // complete. 299 timeout time.Duration 300 } 301 302 // A compile time assertion to ensure that DNSSeedBootstrapper meets the 303 // NetworkPeerjBootstrapper interface. 304 var _ NetworkPeerBootstrapper = (*ChannelGraphBootstrapper)(nil) 305 306 // NewDNSSeedBootstrapper returns a new instance of the DNSSeedBootstrapper. 307 // The set of passed seeds should point to DNS servers that properly implement 308 // Lightning's DNS peer bootstrapping protocol as defined in BOLT-0010. The set 309 // of passed DNS seeds should come in pairs, with the second host name to be 310 // used as a fallback for manual TCP resolution in the case of an error 311 // receiving the UDP response. The second host should return a single A record 312 // with the IP address of the authoritative name server. 313 func NewDNSSeedBootstrapper( 314 seeds [][2]string, net tor.Net, 315 timeout time.Duration) NetworkPeerBootstrapper { 316 return &DNSSeedBootstrapper{dnsSeeds: seeds, net: net, timeout: timeout} 317 } 318 319 // fallBackSRVLookup attempts to manually query for SRV records we need to 320 // properly bootstrap. We do this by querying the special record at the "soa." 321 // sub-domain of supporting DNS servers. The retuned IP address will be the IP 322 // address of the authoritative DNS server. Once we have this IP address, we'll 323 // connect manually over TCP to request the SRV record. This is necessary as 324 // the records we return are currently too large for a class of resolvers, 325 // causing them to be filtered out. The targetEndPoint is the original end 326 // point that was meant to be hit. 327 func (d *DNSSeedBootstrapper) fallBackSRVLookup(soaShim string, 328 targetEndPoint string) ([]*net.SRV, error) { 329 330 log.Tracef("Attempting to query fallback DNS seed") 331 332 // First, we'll lookup the IP address of the server that will act as 333 // our shim. 334 addrs, err := d.net.LookupHost(soaShim) 335 if err != nil { 336 return nil, err 337 } 338 339 // Once we have the IP address, we'll establish a TCP connection using 340 // port 53. 341 dnsServer := net.JoinHostPort(addrs[0], "53") 342 conn, err := d.net.Dial("tcp", dnsServer, d.timeout) 343 if err != nil { 344 return nil, err 345 } 346 347 dnsHost := fmt.Sprintf("_nodes._tcp.%v.", targetEndPoint) 348 dnsConn := &dns.Conn{Conn: conn} 349 defer dnsConn.Close() 350 351 // With the connection established, we'll craft our SRV query, write 352 // toe request, then wait for the server to give our response. 353 msg := new(dns.Msg) 354 msg.SetQuestion(dnsHost, dns.TypeSRV) 355 if err := dnsConn.WriteMsg(msg); err != nil { 356 return nil, err 357 } 358 resp, err := dnsConn.ReadMsg() 359 if err != nil { 360 return nil, err 361 } 362 363 // If the message response code was not the success code, fail. 364 if resp.Rcode != dns.RcodeSuccess { 365 return nil, fmt.Errorf("unsuccessful SRV request, "+ 366 "received: %v", resp.Rcode) 367 } 368 369 // Retrieve the RR(s) of the Answer section, and covert to the format 370 // that net.LookupSRV would normally return. 371 var rrs []*net.SRV 372 for _, rr := range resp.Answer { 373 srv := rr.(*dns.SRV) 374 rrs = append(rrs, &net.SRV{ 375 Target: srv.Target, 376 Port: srv.Port, 377 Priority: srv.Priority, 378 Weight: srv.Weight, 379 }) 380 } 381 382 return rrs, nil 383 } 384 385 // SampleNodeAddrs uniformly samples a set of specified address from the 386 // network peer bootstrapper source. The num addrs field passed in denotes how 387 // many valid peer addresses to return. The set of DNS seeds are used 388 // successively to retrieve eligible target nodes. 389 func (d *DNSSeedBootstrapper) SampleNodeAddrs(ctx context.Context, 390 numAddrs uint32, ignore map[autopilot.NodeID]struct{}) ([]*lnwire.NetAddress, error) { 391 392 var netAddrs []*lnwire.NetAddress 393 394 if len(d.dnsSeeds) == 0 { 395 return nil, errors.New("empty dns seeder") 396 } 397 398 // We'll try all the registered DNS seeds, exiting early if one of them 399 // gives us all the peers we need. 400 // 401 // TODO(roasbeef): should combine results from both 402 search: 403 for _, dnsSeedTuple := range d.dnsSeeds { 404 // We'll first query the seed with an SRV record so we can 405 // obtain a random sample of the encoded public keys of nodes. 406 // We use the lndLookupSRV function for this task. 407 primarySeed := dnsSeedTuple[0] 408 _, addrs, err := d.net.LookupSRV( 409 "nodes", "tcp", primarySeed, d.timeout, 410 ) 411 if err != nil { 412 log.Tracef("Unable to lookup SRV records via "+ 413 "primary seed (%v): %v", primarySeed, err) 414 415 log.Trace("Falling back to secondary") 416 417 // If the host of the secondary seed is blank, then 418 // we'll bail here as we can't proceed. 419 if dnsSeedTuple[1] == "" { 420 log.Tracef("DNS seed %v has no secondary, "+ 421 "skipping fallback", primarySeed) 422 continue 423 } 424 425 // If we get an error when trying to query via the 426 // primary seed, we'll fallback to the secondary seed 427 // before concluding failure. 428 soaShim := dnsSeedTuple[1] 429 addrs, err = d.fallBackSRVLookup( 430 soaShim, primarySeed, 431 ) 432 if err != nil { 433 log.Tracef("Unable to query fall "+ 434 "back dns seed (%v): %v", soaShim, err) 435 continue 436 } 437 438 log.Tracef("Successfully queried fallback DNS seed") 439 } 440 441 log.Tracef("Retrieved SRV records from dns seed: %v", 442 newLogClosure(func() string { 443 return spew.Sdump(addrs) 444 }), 445 ) 446 447 // Next, we'll need to issue an A record request for each of 448 // the nodes, skipping it if nothing comes back. 449 for _, nodeSrv := range addrs { 450 if uint32(len(netAddrs)) >= numAddrs { 451 break search 452 } 453 454 // With the SRV target obtained, we'll now perform 455 // another query to obtain the IP address for the 456 // matching bech32 encoded node key. We use the 457 // lndLookup function for this task. 458 bechNodeHost := nodeSrv.Target 459 addrs, err := d.net.LookupHost(bechNodeHost) 460 if err != nil { 461 return nil, err 462 } 463 464 if len(addrs) == 0 { 465 log.Tracef("No addresses for %v, skipping", 466 bechNodeHost) 467 continue 468 } 469 470 log.Tracef("Attempting to convert: %v", bechNodeHost) 471 472 // If the host isn't correctly formatted, then we'll 473 // skip it. 474 if len(bechNodeHost) == 0 || 475 !strings.Contains(bechNodeHost, ".") { 476 477 continue 478 } 479 480 // If we have a set of valid addresses, then we'll need 481 // to parse the public key from the original bech32 482 // encoded string. 483 bechNode := strings.Split(bechNodeHost, ".") 484 _, nodeBytes5Bits, err := bech32.Decode(bechNode[0]) 485 if err != nil { 486 return nil, err 487 } 488 489 // Once we have the bech32 decoded pubkey, we'll need 490 // to convert the 5-bit word grouping into our regular 491 // 8-bit word grouping so we can convert it into a 492 // public key. 493 nodeBytes, err := bech32.ConvertBits( 494 nodeBytes5Bits, 5, 8, false, 495 ) 496 if err != nil { 497 return nil, err 498 } 499 nodeKey, err := secp256k1.ParsePubKey( 500 nodeBytes, 501 ) 502 if err != nil { 503 return nil, err 504 } 505 506 // If we have an ignore list, and this node is in the 507 // ignore list, then we'll go to the next candidate. 508 if ignore != nil { 509 nID := autopilot.NewNodeID(nodeKey) 510 if _, ok := ignore[nID]; ok { 511 continue 512 } 513 } 514 515 // Finally we'll convert the host:port peer to a proper 516 // TCP address to use within the lnwire.NetAddress. We 517 // don't need to use the lndResolveTCP function here 518 // because we already have the host:port peer. 519 addr := net.JoinHostPort( 520 addrs[0], 521 strconv.FormatUint(uint64(nodeSrv.Port), 10), 522 ) 523 tcpAddr, err := net.ResolveTCPAddr("tcp", addr) 524 if err != nil { 525 return nil, err 526 } 527 528 // Finally, with all the information parsed, we'll 529 // return this fully valid address as a connection 530 // attempt. 531 lnAddr := &lnwire.NetAddress{ 532 IdentityKey: nodeKey, 533 Address: tcpAddr, 534 } 535 536 log.Tracef("Obtained %v as valid reachable "+ 537 "node", lnAddr) 538 539 netAddrs = append(netAddrs, lnAddr) 540 } 541 } 542 543 return netAddrs, nil 544 } 545 546 // Name returns a human readable string which names the concrete 547 // implementation of the NetworkPeerBootstrapper. 548 func (d *DNSSeedBootstrapper) Name() string { 549 return fmt.Sprintf("BOLT-0010 DNS Seed: %v", d.dnsSeeds) 550 }