Source: net/hostlist.js

  1. /*!
  2. * hostlist.js - address management for bcoin
  3. * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
  4. * https://github.com/bcoin-org/bcoin
  5. */
  6. 'use strict';
  7. const assert = require('bsert');
  8. const path = require('path');
  9. const fs = require('bfile');
  10. const IP = require('binet');
  11. const dns = require('bdns');
  12. const Logger = require('blgr');
  13. const murmur3 = require('bcrypto/lib/murmur3');
  14. const List = require('blst');
  15. const {randomRange} = require('bcrypto/lib/random');
  16. const util = require('../utils/util');
  17. const Network = require('../protocol/network');
  18. const NetAddress = require('./netaddress');
  19. const common = require('./common');
  20. const seeds = require('./seeds');
  21. const {inspectSymbol} = require('../utils');
  22. /*
  23. * Constants
  24. */
  25. const POOL32 = Buffer.allocUnsafe(32);
  26. /**
  27. * Host List
  28. * @alias module:net.HostList
  29. */
  30. class HostList {
  31. /**
  32. * Create a host list.
  33. * @constructor
  34. * @param {Object} options
  35. */
  36. constructor(options) {
  37. this.options = new HostListOptions(options);
  38. this.network = this.options.network;
  39. this.logger = this.options.logger.context('hostlist');
  40. this.address = this.options.address;
  41. this.resolve = this.options.resolve;
  42. this.dnsSeeds = [];
  43. this.dnsNodes = [];
  44. this.map = new Map();
  45. this.fresh = [];
  46. this.totalFresh = 0;
  47. this.used = [];
  48. this.totalUsed = 0;
  49. this.nodes = [];
  50. this.local = new Map();
  51. this.banned = new Map();
  52. this.timer = null;
  53. this.needsFlush = false;
  54. this.flushing = false;
  55. this.init();
  56. }
  57. /**
  58. * Initialize list.
  59. * @private
  60. */
  61. init() {
  62. const options = this.options;
  63. const scores = HostList.scores;
  64. for (let i = 0; i < options.maxBuckets; i++)
  65. this.fresh.push(new Map());
  66. for (let i = 0; i < options.maxBuckets; i++)
  67. this.used.push(new List());
  68. this.setSeeds(options.seeds);
  69. this.setNodes(options.nodes);
  70. this.pushLocal(this.address, scores.MANUAL);
  71. this.addLocal(options.host, options.port, scores.BIND);
  72. const hosts = IP.getPublic();
  73. const port = this.address.port;
  74. for (const host of hosts)
  75. this.addLocal(host, port, scores.IF);
  76. }
  77. /**
  78. * Open hostlist and read hosts file.
  79. * @method
  80. * @returns {Promise}
  81. */
  82. async open() {
  83. try {
  84. await this.loadFile();
  85. } catch (e) {
  86. this.logger.warning('Hosts deserialization failed.');
  87. this.logger.error(e);
  88. }
  89. if (this.size() === 0)
  90. this.injectSeeds();
  91. await this.discoverNodes();
  92. this.start();
  93. }
  94. /**
  95. * Close hostlist.
  96. * @method
  97. * @returns {Promise}
  98. */
  99. async close() {
  100. this.stop();
  101. await this.flush();
  102. this.reset();
  103. }
  104. /**
  105. * Start flush interval.
  106. */
  107. start() {
  108. if (this.options.memory)
  109. return;
  110. if (!this.options.filename)
  111. return;
  112. assert(this.timer == null);
  113. this.timer = setInterval(() => this.flush(), this.options.flushInterval);
  114. }
  115. /**
  116. * Stop flush interval.
  117. */
  118. stop() {
  119. if (this.options.memory)
  120. return;
  121. if (!this.options.filename)
  122. return;
  123. assert(this.timer != null);
  124. clearInterval(this.timer);
  125. this.timer = null;
  126. }
  127. /**
  128. * Read and initialize from hosts file.
  129. * @method
  130. * @returns {Promise}
  131. */
  132. injectSeeds() {
  133. const nodes = seeds.get(this.network.type);
  134. for (const node of nodes) {
  135. const addr = NetAddress.fromHostname(node, this.network);
  136. if (!addr.isRoutable())
  137. continue;
  138. if (!this.options.onion && addr.isOnion())
  139. continue;
  140. if (addr.port === 0)
  141. continue;
  142. this.add(addr);
  143. }
  144. }
  145. /**
  146. * Read and initialize from hosts file.
  147. * @method
  148. * @returns {Promise}
  149. */
  150. async loadFile() {
  151. const filename = this.options.filename;
  152. if (fs.unsupported)
  153. return;
  154. if (this.options.memory)
  155. return;
  156. if (!filename)
  157. return;
  158. let data;
  159. try {
  160. data = await fs.readFile(filename, 'utf8');
  161. } catch (e) {
  162. if (e.code === 'ENOENT')
  163. return;
  164. throw e;
  165. }
  166. const json = JSON.parse(data);
  167. this.fromJSON(json);
  168. }
  169. /**
  170. * Flush addrs to hosts file.
  171. * @method
  172. * @returns {Promise}
  173. */
  174. async flush() {
  175. const filename = this.options.filename;
  176. if (fs.unsupported)
  177. return;
  178. if (this.options.memory)
  179. return;
  180. if (!filename)
  181. return;
  182. if (!this.needsFlush)
  183. return;
  184. if (this.flushing)
  185. return;
  186. this.needsFlush = false;
  187. this.logger.debug('Writing hosts to %s.', filename);
  188. const json = this.toJSON();
  189. const data = JSON.stringify(json);
  190. this.flushing = true;
  191. try {
  192. await fs.writeFile(filename, data, 'utf8');
  193. } catch (e) {
  194. this.logger.warning('Writing hosts failed.');
  195. this.logger.error(e);
  196. }
  197. this.flushing = false;
  198. }
  199. /**
  200. * Get list size.
  201. * @returns {Number}
  202. */
  203. size() {
  204. return this.totalFresh + this.totalUsed;
  205. }
  206. /**
  207. * Test whether the host list is full.
  208. * @returns {Boolean}
  209. */
  210. isFull() {
  211. const max = this.options.maxBuckets * this.options.maxEntries;
  212. return this.size() >= max;
  213. }
  214. /**
  215. * Reset host list.
  216. */
  217. reset() {
  218. this.map.clear();
  219. for (const bucket of this.fresh)
  220. bucket.clear();
  221. for (const bucket of this.used)
  222. bucket.reset();
  223. this.totalFresh = 0;
  224. this.totalUsed = 0;
  225. this.nodes.length = 0;
  226. }
  227. /**
  228. * Mark a peer as banned.
  229. * @param {String} host
  230. */
  231. ban(host) {
  232. this.banned.set(host, util.now());
  233. }
  234. /**
  235. * Unban host.
  236. * @param {String} host
  237. */
  238. unban(host) {
  239. this.banned.delete(host);
  240. }
  241. /**
  242. * Clear banned hosts.
  243. */
  244. clearBanned() {
  245. this.banned.clear();
  246. }
  247. /**
  248. * Test whether the host is banned.
  249. * @param {String} host
  250. * @returns {Boolean}
  251. */
  252. isBanned(host) {
  253. const time = this.banned.get(host);
  254. if (time == null)
  255. return false;
  256. if (util.now() > time + this.options.banTime) {
  257. this.banned.delete(host);
  258. return false;
  259. }
  260. return true;
  261. }
  262. /**
  263. * Allocate a new host.
  264. * @returns {HostEntry}
  265. */
  266. getHost() {
  267. let buckets = null;
  268. if (this.totalFresh > 0)
  269. buckets = this.fresh;
  270. if (this.totalUsed > 0) {
  271. if (this.totalFresh === 0 || random(2) === 0)
  272. buckets = this.used;
  273. }
  274. if (!buckets)
  275. return null;
  276. const now = this.network.now();
  277. let factor = 1;
  278. for (;;) {
  279. const i = random(buckets.length);
  280. const bucket = buckets[i];
  281. if (bucket.size === 0)
  282. continue;
  283. let index = random(bucket.size);
  284. let entry;
  285. if (buckets === this.used) {
  286. entry = bucket.head;
  287. while (index--)
  288. entry = entry.next;
  289. } else {
  290. for (entry of bucket.values()) {
  291. if (index === 0)
  292. break;
  293. index -= 1;
  294. }
  295. }
  296. const num = random(1 << 30);
  297. if (num < factor * entry.chance(now) * (1 << 30))
  298. return entry;
  299. factor *= 1.2;
  300. }
  301. }
  302. /**
  303. * Get fresh bucket for host.
  304. * @private
  305. * @param {HostEntry} entry
  306. * @returns {Map}
  307. */
  308. freshBucket(entry) {
  309. const addr = entry.addr;
  310. const src = entry.src;
  311. const data = concat32(addr.raw, src.raw);
  312. const hash = murmur3.sum(data, 0xfba4c795);
  313. const index = hash % this.fresh.length;
  314. return this.fresh[index];
  315. }
  316. /**
  317. * Get used bucket for host.
  318. * @private
  319. * @param {HostEntry} entry
  320. * @returns {List}
  321. */
  322. usedBucket(entry) {
  323. const addr = entry.addr;
  324. const hash = murmur3.sum(addr.raw, 0xfba4c795);
  325. const index = hash % this.used.length;
  326. return this.used[index];
  327. }
  328. /**
  329. * Add host to host list.
  330. * @param {NetAddress} addr
  331. * @param {NetAddress?} src
  332. * @returns {Boolean}
  333. */
  334. add(addr, src) {
  335. assert(addr.port !== 0);
  336. let entry = this.map.get(addr.hostname);
  337. if (entry) {
  338. let penalty = 2 * 60 * 60;
  339. let interval = 24 * 60 * 60;
  340. // No source means we're inserting
  341. // this ourselves. No penalty.
  342. if (!src)
  343. penalty = 0;
  344. // Update services.
  345. entry.addr.services |= addr.services;
  346. entry.addr.services >>>= 0;
  347. // Online?
  348. const now = this.network.now();
  349. if (now - addr.time < 24 * 60 * 60)
  350. interval = 60 * 60;
  351. // Periodically update time.
  352. if (entry.addr.time < addr.time - interval - penalty) {
  353. entry.addr.time = addr.time;
  354. this.needsFlush = true;
  355. }
  356. // Do not update if no new
  357. // information is present.
  358. if (entry.addr.time && addr.time <= entry.addr.time)
  359. return false;
  360. // Do not update if the entry was
  361. // already in the "used" table.
  362. if (entry.used)
  363. return false;
  364. assert(entry.refCount > 0);
  365. // Do not update if the max
  366. // reference count is reached.
  367. if (entry.refCount === HostList.MAX_REFS)
  368. return false;
  369. assert(entry.refCount < HostList.MAX_REFS);
  370. // Stochastic test: previous refCount
  371. // N: 2^N times harder to increase it.
  372. let factor = 1;
  373. for (let i = 0; i < entry.refCount; i++)
  374. factor *= 2;
  375. if (random(factor) !== 0)
  376. return false;
  377. } else {
  378. if (this.isFull())
  379. return false;
  380. if (!src)
  381. src = this.address;
  382. entry = new HostEntry(addr, src);
  383. this.totalFresh += 1;
  384. }
  385. const bucket = this.freshBucket(entry);
  386. if (bucket.has(entry.key()))
  387. return false;
  388. if (bucket.size >= this.options.maxEntries)
  389. this.evictFresh(bucket);
  390. bucket.set(entry.key(), entry);
  391. entry.refCount += 1;
  392. this.map.set(entry.key(), entry);
  393. this.needsFlush = true;
  394. return true;
  395. }
  396. /**
  397. * Evict a host from fresh bucket.
  398. * @param {Map} bucket
  399. */
  400. evictFresh(bucket) {
  401. let old = null;
  402. for (const entry of bucket.values()) {
  403. if (this.isStale(entry)) {
  404. bucket.delete(entry.key());
  405. if (--entry.refCount === 0) {
  406. this.map.delete(entry.key());
  407. this.totalFresh -= 1;
  408. }
  409. continue;
  410. }
  411. if (!old) {
  412. old = entry;
  413. continue;
  414. }
  415. if (entry.addr.time < old.addr.time)
  416. old = entry;
  417. }
  418. if (!old)
  419. return;
  420. bucket.delete(old.key());
  421. if (--old.refCount === 0) {
  422. this.map.delete(old.key());
  423. this.totalFresh -= 1;
  424. }
  425. }
  426. /**
  427. * Test whether a host is evictable.
  428. * @param {HostEntry} entry
  429. * @returns {Boolean}
  430. */
  431. isStale(entry) {
  432. const now = this.network.now();
  433. if (entry.lastAttempt && entry.lastAttempt >= now - 60)
  434. return false;
  435. if (entry.addr.time > now + 10 * 60)
  436. return true;
  437. if (entry.addr.time === 0)
  438. return true;
  439. if (now - entry.addr.time > HostList.HORIZON_DAYS * 24 * 60 * 60)
  440. return true;
  441. if (entry.lastSuccess === 0 && entry.attempts >= HostList.RETRIES)
  442. return true;
  443. if (now - entry.lastSuccess > HostList.MIN_FAIL_DAYS * 24 * 60 * 60) {
  444. if (entry.attempts >= HostList.MAX_FAILURES)
  445. return true;
  446. }
  447. return false;
  448. }
  449. /**
  450. * Remove host from host list.
  451. * @param {String} hostname
  452. * @returns {NetAddress}
  453. */
  454. remove(hostname) {
  455. const entry = this.map.get(hostname);
  456. if (!entry)
  457. return null;
  458. if (entry.used) {
  459. let head = entry;
  460. assert(entry.refCount === 0);
  461. while (head.prev)
  462. head = head.prev;
  463. for (const bucket of this.used) {
  464. if (bucket.head === head) {
  465. bucket.remove(entry);
  466. this.totalUsed -= 1;
  467. head = null;
  468. break;
  469. }
  470. }
  471. assert(!head);
  472. } else {
  473. for (const bucket of this.fresh) {
  474. if (bucket.delete(entry.key()))
  475. entry.refCount -= 1;
  476. }
  477. this.totalFresh -= 1;
  478. assert(entry.refCount === 0);
  479. }
  480. this.map.delete(entry.key());
  481. return entry.addr;
  482. }
  483. /**
  484. * Mark host as failed.
  485. * @param {String} hostname
  486. */
  487. markAttempt(hostname) {
  488. const entry = this.map.get(hostname);
  489. const now = this.network.now();
  490. if (!entry)
  491. return;
  492. entry.attempts += 1;
  493. entry.lastAttempt = now;
  494. }
  495. /**
  496. * Mark host as successfully connected.
  497. * @param {String} hostname
  498. */
  499. markSuccess(hostname) {
  500. const entry = this.map.get(hostname);
  501. const now = this.network.now();
  502. if (!entry)
  503. return;
  504. if (now - entry.addr.time > 20 * 60)
  505. entry.addr.time = now;
  506. }
  507. /**
  508. * Mark host as successfully ack'd.
  509. * @param {String} hostname
  510. * @param {Number} services
  511. */
  512. markAck(hostname, services) {
  513. const entry = this.map.get(hostname);
  514. if (!entry)
  515. return;
  516. const now = this.network.now();
  517. entry.addr.services |= services;
  518. entry.addr.services >>>= 0;
  519. entry.lastSuccess = now;
  520. entry.lastAttempt = now;
  521. entry.attempts = 0;
  522. if (entry.used)
  523. return;
  524. assert(entry.refCount > 0);
  525. // Remove from fresh.
  526. let old = null;
  527. for (const bucket of this.fresh) {
  528. if (bucket.delete(entry.key())) {
  529. entry.refCount -= 1;
  530. old = bucket;
  531. }
  532. }
  533. assert(old);
  534. assert(entry.refCount === 0);
  535. this.totalFresh -= 1;
  536. // Find room in used bucket.
  537. const bucket = this.usedBucket(entry);
  538. if (bucket.size < this.options.maxEntries) {
  539. entry.used = true;
  540. bucket.push(entry);
  541. this.totalUsed += 1;
  542. return;
  543. }
  544. // No room. Evict.
  545. const evicted = this.evictUsed(bucket);
  546. let fresh = this.freshBucket(evicted);
  547. // Move to entry's old bucket if no room.
  548. if (fresh.size >= this.options.maxEntries)
  549. fresh = old;
  550. // Swap to evicted's used bucket.
  551. entry.used = true;
  552. bucket.replace(evicted, entry);
  553. // Move evicted to fresh bucket.
  554. evicted.used = false;
  555. fresh.set(evicted.key(), evicted);
  556. assert(evicted.refCount === 0);
  557. evicted.refCount += 1;
  558. this.totalFresh += 1;
  559. }
  560. /**
  561. * Pick used for eviction.
  562. * @param {List} bucket
  563. */
  564. evictUsed(bucket) {
  565. let old = bucket.head;
  566. for (let entry = bucket.head; entry; entry = entry.next) {
  567. if (entry.addr.time < old.addr.time)
  568. old = entry;
  569. }
  570. return old;
  571. }
  572. /**
  573. * Convert address list to array.
  574. * @returns {NetAddress[]}
  575. */
  576. toArray() {
  577. const out = [];
  578. for (const entry of this.map.values())
  579. out.push(entry.addr);
  580. assert.strictEqual(out.length, this.size());
  581. return out;
  582. }
  583. /**
  584. * Add a preferred seed.
  585. * @param {String} host
  586. */
  587. addSeed(host) {
  588. const ip = IP.fromHostname(host, this.network.port);
  589. if (ip.type === IP.types.DNS) {
  590. // Defer for resolution.
  591. this.dnsSeeds.push(ip);
  592. return null;
  593. }
  594. const addr = NetAddress.fromHost(ip.host, ip.port, this.network);
  595. this.add(addr);
  596. return addr;
  597. }
  598. /**
  599. * Add a priority node.
  600. * @param {String} host
  601. * @returns {NetAddress}
  602. */
  603. addNode(host) {
  604. const ip = IP.fromHostname(host, this.network.port);
  605. if (ip.type === IP.types.DNS) {
  606. // Defer for resolution.
  607. this.dnsNodes.push(ip);
  608. return null;
  609. }
  610. const addr = NetAddress.fromHost(ip.host, ip.port, this.network);
  611. this.nodes.push(addr);
  612. this.add(addr);
  613. return addr;
  614. }
  615. /**
  616. * Remove a priority node.
  617. * @param {String} host
  618. * @returns {Boolean}
  619. */
  620. removeNode(host) {
  621. const addr = IP.fromHostname(host, this.network.port);
  622. for (let i = 0; i < this.nodes.length; i++) {
  623. const node = this.nodes[i];
  624. if (node.host !== addr.host)
  625. continue;
  626. if (node.port !== addr.port)
  627. continue;
  628. this.nodes.splice(i, 1);
  629. return true;
  630. }
  631. return false;
  632. }
  633. /**
  634. * Set initial seeds.
  635. * @param {String[]} seeds
  636. */
  637. setSeeds(seeds) {
  638. this.dnsSeeds.length = 0;
  639. for (const host of seeds)
  640. this.addSeed(host);
  641. }
  642. /**
  643. * Set priority nodes.
  644. * @param {String[]} nodes
  645. */
  646. setNodes(nodes) {
  647. this.dnsNodes.length = 0;
  648. this.nodes.length = 0;
  649. for (const host of nodes)
  650. this.addNode(host);
  651. }
  652. /**
  653. * Add a local address.
  654. * @param {String} host
  655. * @param {Number} port
  656. * @param {Number} score
  657. * @returns {Boolean}
  658. */
  659. addLocal(host, port, score) {
  660. const addr = NetAddress.fromHost(host, port, this.network);
  661. addr.services = this.options.services;
  662. return this.pushLocal(addr, score);
  663. }
  664. /**
  665. * Add a local address.
  666. * @param {NetAddress} addr
  667. * @param {Number} score
  668. * @returns {Boolean}
  669. */
  670. pushLocal(addr, score) {
  671. if (!addr.isRoutable())
  672. return false;
  673. if (this.local.has(addr.hostname))
  674. return false;
  675. const local = new LocalAddress(addr, score);
  676. this.local.set(addr.hostname, local);
  677. return true;
  678. }
  679. /**
  680. * Get local address based on reachability.
  681. * @param {NetAddress?} src
  682. * @returns {NetAddress}
  683. */
  684. getLocal(src) {
  685. let bestReach = -1;
  686. let bestScore = -1;
  687. let bestDest = null;
  688. if (!src)
  689. src = this.address;
  690. if (this.local.size === 0)
  691. return null;
  692. for (const dest of this.local.values()) {
  693. const reach = src.getReachability(dest.addr);
  694. if (reach < bestReach)
  695. continue;
  696. if (reach > bestReach || dest.score > bestScore) {
  697. bestReach = reach;
  698. bestScore = dest.score;
  699. bestDest = dest.addr;
  700. }
  701. }
  702. bestDest.time = this.network.now();
  703. return bestDest;
  704. }
  705. /**
  706. * Mark local address as seen during a handshake.
  707. * @param {NetAddress} addr
  708. * @returns {Boolean}
  709. */
  710. markLocal(addr) {
  711. const local = this.local.get(addr.hostname);
  712. if (!local)
  713. return false;
  714. local.score += 1;
  715. return true;
  716. }
  717. /**
  718. * Discover hosts from seeds.
  719. * @method
  720. * @returns {Promise}
  721. */
  722. async discoverSeeds() {
  723. const jobs = [];
  724. for (const seed of this.dnsSeeds)
  725. jobs.push(this.populateSeed(seed));
  726. return Promise.all(jobs);
  727. }
  728. /**
  729. * Discover hosts from nodes.
  730. * @method
  731. * @returns {Promise}
  732. */
  733. async discoverNodes() {
  734. const jobs = [];
  735. for (const node of this.dnsNodes)
  736. jobs.push(this.populateNode(node));
  737. return Promise.all(jobs);
  738. }
  739. /**
  740. * Lookup node's domain.
  741. * @method
  742. * @param {Object} addr
  743. * @returns {Promise}
  744. */
  745. async populateNode(addr) {
  746. const addrs = await this.populate(addr);
  747. if (addrs.length === 0)
  748. return;
  749. this.nodes.push(addrs[0]);
  750. this.add(addrs[0]);
  751. }
  752. /**
  753. * Populate from seed.
  754. * @method
  755. * @param {Object} seed
  756. * @returns {Promise}
  757. */
  758. async populateSeed(seed) {
  759. const addrs = await this.populate(seed);
  760. for (const addr of addrs)
  761. this.add(addr);
  762. }
  763. /**
  764. * Lookup hosts from dns host.
  765. * @method
  766. * @param {Object} target
  767. * @returns {Promise}
  768. */
  769. async populate(target) {
  770. const addrs = [];
  771. assert(target.type === IP.types.DNS, 'Resolved host passed.');
  772. this.logger.info('Resolving host: %s.', target.host);
  773. let hosts;
  774. try {
  775. hosts = await this.resolve(target.host);
  776. } catch (e) {
  777. this.logger.error(e);
  778. return addrs;
  779. }
  780. for (const host of hosts) {
  781. const addr = NetAddress.fromHost(host, target.port, this.network);
  782. addrs.push(addr);
  783. }
  784. return addrs;
  785. }
  786. /**
  787. * Convert host list to json-friendly object.
  788. * @returns {Object}
  789. */
  790. toJSON() {
  791. const addrs = [];
  792. const fresh = [];
  793. const used = [];
  794. for (const entry of this.map.values())
  795. addrs.push(entry.toJSON());
  796. for (const bucket of this.fresh) {
  797. const keys = [];
  798. for (const key of bucket.keys())
  799. keys.push(key);
  800. fresh.push(keys);
  801. }
  802. for (const bucket of this.used) {
  803. const keys = [];
  804. for (let entry = bucket.head; entry; entry = entry.next)
  805. keys.push(entry.key());
  806. used.push(keys);
  807. }
  808. return {
  809. version: HostList.VERSION,
  810. network: this.network.type,
  811. addrs: addrs,
  812. fresh: fresh,
  813. used: used
  814. };
  815. }
  816. /**
  817. * Inject properties from json object.
  818. * @private
  819. * @param {Object} json
  820. * @returns {HostList}
  821. */
  822. fromJSON(json) {
  823. const sources = new Map();
  824. const map = new Map();
  825. const fresh = [];
  826. const used = [];
  827. let totalFresh = 0;
  828. let totalUsed = 0;
  829. assert(json && typeof json === 'object');
  830. assert(!json.network || json.network === this.network.type,
  831. 'Network mistmatch.');
  832. assert(json.version === HostList.VERSION,
  833. 'Bad address serialization version.');
  834. assert(Array.isArray(json.addrs));
  835. for (const addr of json.addrs) {
  836. const entry = HostEntry.fromJSON(addr, this.network);
  837. let src = sources.get(entry.src.hostname);
  838. // Save some memory.
  839. if (!src) {
  840. src = entry.src;
  841. sources.set(src.hostname, src);
  842. }
  843. entry.src = src;
  844. map.set(entry.key(), entry);
  845. }
  846. assert(Array.isArray(json.fresh));
  847. assert(json.fresh.length <= this.options.maxBuckets,
  848. 'Buckets mismatch.');
  849. for (const keys of json.fresh) {
  850. const bucket = new Map();
  851. for (const key of keys) {
  852. const entry = map.get(key);
  853. assert(entry);
  854. if (entry.refCount === 0)
  855. totalFresh += 1;
  856. entry.refCount += 1;
  857. bucket.set(key, entry);
  858. }
  859. assert(bucket.size <= this.options.maxEntries,
  860. 'Bucket size mismatch.');
  861. fresh.push(bucket);
  862. }
  863. assert(fresh.length === this.fresh.length,
  864. 'Buckets mismatch.');
  865. assert(Array.isArray(json.used));
  866. assert(json.used.length <= this.options.maxBuckets,
  867. 'Buckets mismatch.');
  868. for (const keys of json.used) {
  869. const bucket = new List();
  870. for (const key of keys) {
  871. const entry = map.get(key);
  872. assert(entry);
  873. assert(entry.refCount === 0);
  874. assert(!entry.used);
  875. entry.used = true;
  876. totalUsed += 1;
  877. bucket.push(entry);
  878. }
  879. assert(bucket.size <= this.options.maxEntries,
  880. 'Bucket size mismatch.');
  881. used.push(bucket);
  882. }
  883. assert(used.length === this.used.length,
  884. 'Buckets mismatch.');
  885. for (const entry of map.values())
  886. assert(entry.used || entry.refCount > 0);
  887. this.map = map;
  888. this.fresh = fresh;
  889. this.totalFresh = totalFresh;
  890. this.used = used;
  891. this.totalUsed = totalUsed;
  892. return this;
  893. }
  894. /**
  895. * Instantiate host list from json object.
  896. * @param {Object} options
  897. * @param {Object} json
  898. * @returns {HostList}
  899. */
  900. static fromJSON(options, json) {
  901. return new this(options).fromJSON(json);
  902. }
  903. }
  904. /**
  905. * Number of days before considering
  906. * an address stale.
  907. * @const {Number}
  908. * @default
  909. */
  910. HostList.HORIZON_DAYS = 30;
  911. /**
  912. * Number of retries (without success)
  913. * before considering an address stale.
  914. * @const {Number}
  915. * @default
  916. */
  917. HostList.RETRIES = 3;
  918. /**
  919. * Number of days after reaching
  920. * MAX_FAILURES to consider an
  921. * address stale.
  922. * @const {Number}
  923. * @default
  924. */
  925. HostList.MIN_FAIL_DAYS = 7;
  926. /**
  927. * Maximum number of failures
  928. * allowed before considering
  929. * an address stale.
  930. * @const {Number}
  931. * @default
  932. */
  933. HostList.MAX_FAILURES = 10;
  934. /**
  935. * Maximum number of references
  936. * in fresh buckets.
  937. * @const {Number}
  938. * @default
  939. */
  940. HostList.MAX_REFS = 8;
  941. /**
  942. * Serialization version.
  943. * @const {Number}
  944. * @default
  945. */
  946. HostList.VERSION = 0;
  947. /**
  948. * Local address scores.
  949. * @enum {Number}
  950. * @default
  951. */
  952. HostList.scores = {
  953. NONE: 0,
  954. IF: 1,
  955. BIND: 2,
  956. UPNP: 3,
  957. DNS: 3,
  958. MANUAL: 4,
  959. MAX: 5
  960. };
  961. /**
  962. * Host Entry
  963. * @alias module:net.HostEntry
  964. */
  965. class HostEntry {
  966. /**
  967. * Create a host entry.
  968. * @constructor
  969. * @param {NetAddress} addr
  970. * @param {NetAddress} src
  971. */
  972. constructor(addr, src) {
  973. this.addr = addr || new NetAddress();
  974. this.src = src || new NetAddress();
  975. this.prev = null;
  976. this.next = null;
  977. this.used = false;
  978. this.refCount = 0;
  979. this.attempts = 0;
  980. this.lastSuccess = 0;
  981. this.lastAttempt = 0;
  982. if (addr)
  983. this.fromOptions(addr, src);
  984. }
  985. /**
  986. * Inject properties from options.
  987. * @private
  988. * @param {NetAddress} addr
  989. * @param {NetAddress} src
  990. * @returns {HostEntry}
  991. */
  992. fromOptions(addr, src) {
  993. assert(addr instanceof NetAddress);
  994. assert(src instanceof NetAddress);
  995. this.addr = addr;
  996. this.src = src;
  997. return this;
  998. }
  999. /**
  1000. * Instantiate host entry from options.
  1001. * @param {NetAddress} addr
  1002. * @param {NetAddress} src
  1003. * @returns {HostEntry}
  1004. */
  1005. static fromOptions(addr, src) {
  1006. return new this().fromOptions(addr, src);
  1007. }
  1008. /**
  1009. * Get key suitable for a hash table (hostname).
  1010. * @returns {String}
  1011. */
  1012. key() {
  1013. return this.addr.hostname;
  1014. }
  1015. /**
  1016. * Get host priority.
  1017. * @param {Number} now
  1018. * @returns {Number}
  1019. */
  1020. chance(now) {
  1021. let c = 1;
  1022. if (now - this.lastAttempt < 60 * 10)
  1023. c *= 0.01;
  1024. c *= Math.pow(0.66, Math.min(this.attempts, 8));
  1025. return c;
  1026. }
  1027. /**
  1028. * Inspect host address.
  1029. * @returns {Object}
  1030. */
  1031. [inspectSymbol]() {
  1032. return {
  1033. addr: this.addr,
  1034. src: this.src,
  1035. used: this.used,
  1036. refCount: this.refCount,
  1037. attempts: this.attempts,
  1038. lastSuccess: util.date(this.lastSuccess),
  1039. lastAttempt: util.date(this.lastAttempt)
  1040. };
  1041. }
  1042. /**
  1043. * Convert host entry to json-friendly object.
  1044. * @returns {Object}
  1045. */
  1046. toJSON() {
  1047. return {
  1048. addr: this.addr.hostname,
  1049. src: this.src.hostname,
  1050. services: this.addr.services.toString(2),
  1051. time: this.addr.time,
  1052. attempts: this.attempts,
  1053. lastSuccess: this.lastSuccess,
  1054. lastAttempt: this.lastAttempt
  1055. };
  1056. }
  1057. /**
  1058. * Inject properties from json object.
  1059. * @private
  1060. * @param {Object} json
  1061. * @param {Network} network
  1062. * @returns {HostEntry}
  1063. */
  1064. fromJSON(json, network) {
  1065. assert(json && typeof json === 'object');
  1066. assert(typeof json.addr === 'string');
  1067. assert(typeof json.src === 'string');
  1068. this.addr.fromHostname(json.addr, network);
  1069. if (json.services != null) {
  1070. assert(typeof json.services === 'string');
  1071. assert(json.services.length > 0);
  1072. assert(json.services.length <= 32);
  1073. const services = parseInt(json.services, 2);
  1074. assert((services >>> 0) === services);
  1075. this.addr.services = services;
  1076. }
  1077. if (json.time != null) {
  1078. assert(Number.isSafeInteger(json.time));
  1079. assert(json.time >= 0);
  1080. this.addr.time = json.time;
  1081. }
  1082. if (json.src != null) {
  1083. assert(typeof json.src === 'string');
  1084. this.src.fromHostname(json.src, network);
  1085. }
  1086. if (json.attempts != null) {
  1087. assert((json.attempts >>> 0) === json.attempts);
  1088. this.attempts = json.attempts;
  1089. }
  1090. if (json.lastSuccess != null) {
  1091. assert(Number.isSafeInteger(json.lastSuccess));
  1092. assert(json.lastSuccess >= 0);
  1093. this.lastSuccess = json.lastSuccess;
  1094. }
  1095. if (json.lastAttempt != null) {
  1096. assert(Number.isSafeInteger(json.lastAttempt));
  1097. assert(json.lastAttempt >= 0);
  1098. this.lastAttempt = json.lastAttempt;
  1099. }
  1100. return this;
  1101. }
  1102. /**
  1103. * Instantiate host entry from json object.
  1104. * @param {Object} json
  1105. * @param {Network} network
  1106. * @returns {HostEntry}
  1107. */
  1108. static fromJSON(json, network) {
  1109. return new this().fromJSON(json, network);
  1110. }
  1111. }
  1112. /**
  1113. * Local Address
  1114. * @alias module:net.LocalAddress
  1115. */
  1116. class LocalAddress {
  1117. /**
  1118. * Create a local address.
  1119. * @constructor
  1120. * @param {NetAddress} addr
  1121. * @param {Number?} score
  1122. */
  1123. constructor(addr, score) {
  1124. this.addr = addr;
  1125. this.score = score || 0;
  1126. }
  1127. }
  1128. /**
  1129. * Host List Options
  1130. * @alias module:net.HostListOptions
  1131. */
  1132. class HostListOptions {
  1133. /**
  1134. * Create host list options.
  1135. * @constructor
  1136. * @param {Object?} options
  1137. */
  1138. constructor(options) {
  1139. this.network = Network.primary;
  1140. this.logger = Logger.global;
  1141. this.resolve = dns.lookup;
  1142. this.host = '0.0.0.0';
  1143. this.port = this.network.port;
  1144. this.services = common.LOCAL_SERVICES;
  1145. this.onion = false;
  1146. this.banTime = common.BAN_TIME;
  1147. this.address = new NetAddress();
  1148. this.address.services = this.services;
  1149. this.address.time = this.network.now();
  1150. this.seeds = this.network.seeds;
  1151. this.nodes = [];
  1152. this.maxBuckets = 20;
  1153. this.maxEntries = 50;
  1154. this.prefix = null;
  1155. this.filename = null;
  1156. this.memory = true;
  1157. this.flushInterval = 120000;
  1158. if (options)
  1159. this.fromOptions(options);
  1160. }
  1161. /**
  1162. * Inject properties from options.
  1163. * @private
  1164. * @param {Object} options
  1165. */
  1166. fromOptions(options) {
  1167. assert(options, 'Options are required.');
  1168. if (options.network != null) {
  1169. this.network = Network.get(options.network);
  1170. this.seeds = this.network.seeds;
  1171. this.address.port = this.network.port;
  1172. this.port = this.network.port;
  1173. }
  1174. if (options.logger != null) {
  1175. assert(typeof options.logger === 'object');
  1176. this.logger = options.logger;
  1177. }
  1178. if (options.resolve != null) {
  1179. assert(typeof options.resolve === 'function');
  1180. this.resolve = options.resolve;
  1181. }
  1182. if (options.banTime != null) {
  1183. assert(options.banTime >= 0);
  1184. this.banTime = options.banTime;
  1185. }
  1186. if (options.seeds) {
  1187. assert(Array.isArray(options.seeds));
  1188. this.seeds = options.seeds;
  1189. }
  1190. if (options.nodes) {
  1191. assert(Array.isArray(options.nodes));
  1192. this.nodes = options.nodes;
  1193. }
  1194. if (options.host != null) {
  1195. assert(typeof options.host === 'string');
  1196. const raw = IP.toBuffer(options.host);
  1197. this.host = IP.toString(raw);
  1198. if (IP.isRoutable(raw))
  1199. this.address.setHost(this.host);
  1200. }
  1201. if (options.port != null) {
  1202. assert(typeof options.port === 'number');
  1203. assert(options.port > 0 && options.port <= 0xffff);
  1204. this.port = options.port;
  1205. this.address.setPort(this.port);
  1206. }
  1207. if (options.publicHost != null) {
  1208. assert(typeof options.publicHost === 'string');
  1209. this.address.setHost(options.publicHost);
  1210. }
  1211. if (options.publicPort != null) {
  1212. assert(typeof options.publicPort === 'number');
  1213. assert(options.publicPort > 0 && options.publicPort <= 0xffff);
  1214. this.address.setPort(options.publicPort);
  1215. }
  1216. if (options.services != null) {
  1217. assert(typeof options.services === 'number');
  1218. this.services = options.services;
  1219. }
  1220. if (options.onion != null) {
  1221. assert(typeof options.onion === 'boolean');
  1222. this.onion = options.onion;
  1223. }
  1224. if (options.maxBuckets != null) {
  1225. assert(typeof options.maxBuckets === 'number');
  1226. this.maxBuckets = options.maxBuckets;
  1227. }
  1228. if (options.maxEntries != null) {
  1229. assert(typeof options.maxEntries === 'number');
  1230. this.maxEntries = options.maxEntries;
  1231. }
  1232. if (options.memory != null) {
  1233. assert(typeof options.memory === 'boolean');
  1234. this.memory = options.memory;
  1235. }
  1236. if (options.prefix != null) {
  1237. assert(typeof options.prefix === 'string');
  1238. this.prefix = options.prefix;
  1239. this.filename = path.join(this.prefix, 'hosts.json');
  1240. }
  1241. if (options.filename != null) {
  1242. assert(typeof options.filename === 'string');
  1243. this.filename = options.filename;
  1244. }
  1245. if (options.flushInterval != null) {
  1246. assert(options.flushInterval >= 0);
  1247. this.flushInterval = options.flushInterval;
  1248. }
  1249. this.address.time = this.network.now();
  1250. this.address.services = this.services;
  1251. return this;
  1252. }
  1253. }
  1254. /*
  1255. * Helpers
  1256. */
  1257. function concat32(left, right) {
  1258. const data = POOL32;
  1259. left.copy(data, 0);
  1260. right.copy(data, 32);
  1261. return data;
  1262. }
  1263. function random(max) {
  1264. return randomRange(0, max);
  1265. }
  1266. /*
  1267. * Expose
  1268. */
  1269. module.exports = HostList;