fcoin/lib/net/hostlist.js
2017-03-16 00:39:30 -07:00

1633 lines
31 KiB
JavaScript

/*!
* hostlist.js - address management for bcoin
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
var assert = require('assert');
var util = require('../utils/util');
var IP = require('../utils/ip');
var co = require('../utils/co');
var Network = require('../protocol/network');
var NetAddress = require('../primitives/netaddress');
var List = require('../utils/list');
var murmur3 = require('../utils/murmur3');
var Map = require('../utils/map');
var common = require('./common');
var seeds = require('./seeds');
var dns = require('./dns');
var Logger = require('../node/logger');
var fs = require('../utils/fs');
/**
* Host List
* @alias module:net.HostList
* @constructor
* @param {Object} options
*/
function HostList(options) {
if (!(this instanceof HostList))
return new HostList(options);
this.options = new HostListOptions(options);
this.network = this.options.network;
this.logger = this.options.logger.context('hostlist');
this.address = this.options.address;
this.resolve = this.options.resolve;
this.dnsSeeds = [];
this.dnsNodes = [];
this.map = {};
this.fresh = [];
this.totalFresh = 0;
this.used = [];
this.totalUsed = 0;
this.nodes = [];
this.local = new Map();
this.banned = {};
this.timer = null;
this.needsFlush = false;
this._init();
}
/**
* Number of days before considering
* an address stale.
* @const {Number}
* @default
*/
HostList.HORIZON_DAYS = 30;
/**
* Number of retries (without success)
* before considering an address stale.
* @const {Number}
* @default
*/
HostList.RETRIES = 3;
/**
* Number of days after reaching
* MAX_FAILURES to consider an
* address stale.
* @const {Number}
* @default
*/
HostList.MIN_FAIL_DAYS = 7;
/**
* Maximum number of failures
* allowed before considering
* an address stale.
* @const {Number}
* @default
*/
HostList.MAX_FAILURES = 10;
/**
* Maximum number of references
* in fresh buckets.
* @const {Number}
* @default
*/
HostList.MAX_REFS = 8;
/**
* Serialization version.
* @const {Number}
* @default
*/
HostList.VERSION = 0;
/**
* Local address scores.
* @enum {Number}
* @default
*/
HostList.scores = {
NONE: 0,
IF: 1,
BIND: 2,
UPNP: 3,
HTTP: 3,
MANUAL: 4,
MAX: 5
};
/**
* Initialize list.
* @private
*/
HostList.prototype._init = function init() {
var options = this.options;
var scores = HostList.scores;
var hosts = IP.getPublic();
var port = this.address.port;
var i, host;
for (i = 0; i < this.options.maxBuckets; i++)
this.fresh.push(new Map());
for (i = 0; i < this.options.maxBuckets; i++)
this.used.push(new List());
this.setSeeds(options.seeds);
this.setNodes(options.nodes);
this.pushLocal(this.address, scores.MANUAL);
this.addLocal(options.host, options.port, scores.BIND);
for (i = 0; i < hosts.length; i++) {
host = hosts[i];
this.addLocal(host, port, scores.IF);
}
};
/**
* Open hostlist and read hosts file.
* @method
* @returns {Promise}
*/
HostList.prototype.open = co(function* open() {
try {
yield this.loadFile();
} catch (e) {
this.logger.warning('Hosts deserialization failed.');
this.logger.error(e);
}
if (this.size() === 0)
this.injectSeeds();
yield this.discoverNodes();
this.start();
});
/**
* Close hostlist.
* @method
* @returns {Promise}
*/
HostList.prototype.close = co(function* close() {
this.stop();
yield this.flush();
this.reset();
});
/**
* Start flush interval.
*/
HostList.prototype.start = function start() {
if (!this.options.persistent)
return;
if (!this.options.filename)
return;
assert(this.timer == null);
this.timer = co.setInterval(this.flush, this.options.flushInterval, this);
};
/**
* Stop flush interval.
*/
HostList.prototype.stop = function stop() {
if (!this.options.persistent)
return;
if (!this.options.filename)
return;
assert(this.timer != null);
co.clearInterval(this.timer);
this.timer = null;
};
/**
* Read and initialize from hosts file.
* @method
* @returns {Promise}
*/
HostList.prototype.injectSeeds = function injectSeeds() {
var nodes = seeds.get(this.network.type);
var i, node, addr;
for (i = 0; i < nodes.length; i++) {
node = nodes[i];
addr = NetAddress.fromHostname(node, this.network);
if (!addr.isRoutable())
continue;
if (!this.options.onion && addr.isOnion())
continue;
if (addr.port === 0)
continue;
this.add(addr);
}
};
/**
* Read and initialize from hosts file.
* @method
* @returns {Promise}
*/
HostList.prototype.loadFile = co(function* loadFile() {
var filename = this.options.filename;
var data, json;
if (fs.unsupported)
return;
if (!this.options.persistent)
return;
if (!filename)
return;
try {
data = yield fs.readFile(filename, 'utf8');
} catch (e) {
if (e.code === 'ENOENT')
return;
throw e;
}
json = JSON.parse(data);
this.fromJSON(json);
});
/**
* Flush addrs to hosts file.
* @method
* @returns {Promise}
*/
HostList.prototype.flush = co(function* flush() {
var filename = this.options.filename;
var json, data;
if (fs.unsupported)
return;
if (!this.options.persistent)
return;
if (!filename)
return;
if (!this.needsFlush)
return;
this.needsFlush = false;
this.logger.debug('Writing hosts to %s.', filename);
json = this.toJSON();
data = JSON.stringify(json);
try {
yield fs.writeFile(filename, data, 'utf8');
} catch (e) {
this.logger.warning('Writing hosts failed.');
this.logger.error(e);
}
});
/**
* Get list size.
* @returns {Number}
*/
HostList.prototype.size = function size() {
return this.totalFresh + this.totalUsed;
};
/**
* Test whether the host list is full.
* @returns {Boolean}
*/
HostList.prototype.isFull = function isFull() {
var max = this.options.maxBuckets * this.options.maxEntries;
return this.size() >= max;
};
/**
* Reset host list.
*/
HostList.prototype.reset = function reset() {
var i, bucket;
this.map = {};
for (i = 0; i < this.fresh.length; i++) {
bucket = this.fresh[i];
bucket.reset();
}
for (i = 0; i < this.used.length; i++) {
bucket = this.used[i];
bucket.reset();
}
this.totalFresh = 0;
this.totalUsed = 0;
this.nodes.length = 0;
};
/**
* Mark a peer as banned.
* @param {String} host
*/
HostList.prototype.ban = function ban(host) {
this.banned[host] = util.now();
};
/**
* Unban host.
* @param {String} host
*/
HostList.prototype.unban = function unban(host) {
delete this.banned[host];
};
/**
* Clear banned hosts.
*/
HostList.prototype.clearBanned = function clearBanned() {
this.banned = {};
};
/**
* Test whether the host is banned.
* @param {String} host
* @returns {Boolean}
*/
HostList.prototype.isBanned = function isBanned(host) {
var time = this.banned[host];
if (time == null)
return false;
if (util.now() > time + this.options.banTime) {
delete this.banned[host];
return false;
}
return true;
};
/**
* Allocate a new host.
* @returns {HostEntry}
*/
HostList.prototype.getHost = function getHost() {
var now = this.network.now();
var buckets = null;
var factor = 1;
var index, key, bucket, entry, num;
if (this.totalFresh > 0)
buckets = this.fresh;
if (this.totalUsed > 0) {
if (this.totalFresh === 0 || util.random(0, 2) === 0)
buckets = this.used;
}
if (!buckets)
return;
for (;;) {
index = util.random(0, buckets.length);
bucket = buckets[index];
if (bucket.size === 0)
continue;
index = util.random(0, bucket.size);
if (buckets === this.used) {
entry = bucket.head;
while (index--)
entry = entry.next;
} else {
key = bucket.keys()[index];
entry = bucket.get(key);
}
num = util.random(0, 1 << 30);
if (num < factor * entry.chance(now) * (1 << 30))
return entry;
factor *= 1.2;
}
};
/**
* Get fresh bucket for host.
* @private
* @param {HostEntry} entry
* @returns {Map}
*/
HostList.prototype.freshBucket = function freshBucket(entry) {
var addr = entry.addr;
var src = entry.src;
var data, hash, index;
data = util.concat(addr.raw, src.raw);
hash = murmur3(data, 0xfba4c795);
index = hash % this.fresh.length;
return this.fresh[index];
};
/**
* Get used bucket for host.
* @private
* @param {HostEntry} entry
* @returns {List}
*/
HostList.prototype.usedBucket = function usedBucket(entry) {
var addr = entry.addr;
var hash = murmur3(addr.raw, 0xfba4c795);
var index = hash % this.used.length;
return this.used[index];
};
/**
* Add host to host list.
* @param {NetAddress} addr
* @param {NetAddress?} src
* @returns {Boolean}
*/
HostList.prototype.add = function add(addr, src) {
var now = this.network.now();
var penalty = 2 * 60 * 60;
var interval = 24 * 60 * 60;
var factor = 1;
var i, entry, bucket;
assert(addr.port !== 0);
entry = this.map[addr.hostname];
if (entry) {
// No source means we're inserting
// this ourselves. No penalty.
if (!src)
penalty = 0;
// Update services.
entry.addr.services |= addr.services;
entry.addr.services >>>= 0;
// Online?
if (now - addr.ts < 24 * 60 * 60)
interval = 60 * 60;
// Periodically update time.
if (entry.addr.ts < addr.ts - interval - penalty) {
entry.addr.ts = addr.ts;
this.needsFlush = true;
}
// Do not update if no new
// information is present.
if (entry.addr.ts && addr.ts <= entry.addr.ts)
return false;
// Do not update if the entry was
// already in the "used" table.
if (entry.used)
return false;
assert(entry.refCount > 0);
// Do not update if the max
// reference count is reached.
if (entry.refCount === HostList.MAX_REFS)
return false;
assert(entry.refCount < HostList.MAX_REFS);
// Stochastic test: previous refCount
// N: 2^N times harder to increase it.
for (i = 0; i < entry.refCount; i++)
factor *= 2;
if (util.random(0, factor) !== 0)
return false;
} else {
if (this.isFull())
return false;
if (!src)
src = this.address;
entry = new HostEntry(addr, src);
this.totalFresh++;
}
bucket = this.freshBucket(entry);
if (bucket.has(entry.key()))
return false;
if (bucket.size >= this.options.maxEntries)
this.evictFresh(bucket);
bucket.set(entry.key(), entry);
entry.refCount++;
this.map[entry.key()] = entry;
this.needsFlush = true;
return true;
};
/**
* Evict a host from fresh bucket.
* @param {Map} bucket
*/
HostList.prototype.evictFresh = function evictFresh(bucket) {
var keys = bucket.keys();
var i, key, entry, old;
for (i = 0; i < keys.length; i++) {
key = keys[i];
entry = bucket.get(key);
if (this.isStale(entry)) {
bucket.remove(entry.key());
if (--entry.refCount === 0) {
delete this.map[entry.key()];
this.totalFresh--;
}
continue;
}
if (!old) {
old = entry;
continue;
}
if (entry.addr.ts < old.addr.ts)
old = entry;
}
if (!old)
return;
bucket.remove(old.key());
if (--old.refCount === 0) {
delete this.map[old.key()];
this.totalFresh--;
}
};
/**
* Test whether a host is evictable.
* @param {HostEntry} entry
* @returns {Boolean}
*/
HostList.prototype.isStale = function isStale(entry) {
var now = this.network.now();
if (entry.lastAttempt && entry.lastAttempt >= now - 60)
return false;
if (entry.addr.ts > now + 10 * 60)
return true;
if (entry.addr.ts === 0)
return true;
if (now - entry.addr.ts > HostList.HORIZON_DAYS * 24 * 60 * 60)
return true;
if (entry.lastSuccess === 0 && entry.attempts >= HostList.RETRIES)
return true;
if (now - entry.lastSuccess > HostList.MIN_FAIL_DAYS * 24 * 60 * 60) {
if (entry.attempts >= HostList.MAX_FAILURES)
return true;
}
return false;
};
/**
* Remove host from host list.
* @param {String} hostname
* @returns {NetAddress}
*/
HostList.prototype.remove = function remove(hostname) {
var entry = this.map[hostname];
var i, head, bucket;
if (!entry)
return;
if (entry.used) {
assert(entry.refCount === 0);
head = entry;
while (head.prev)
head = head.prev;
for (i = 0; i < this.used.length; i++) {
bucket = this.used[i];
if (bucket.head === head) {
bucket.remove(entry);
this.totalUsed--;
break;
}
}
assert(i < this.used.length);
} else {
for (i = 0; i < this.fresh.length; i++) {
bucket = this.fresh[i];
if (bucket.remove(entry.key()))
entry.refCount--;
}
this.totalFresh--;
assert(entry.refCount === 0);
}
delete this.map[entry.key()];
return entry.addr;
};
/**
* Mark host as failed.
* @param {String} hostname
*/
HostList.prototype.markAttempt = function markAttempt(hostname) {
var entry = this.map[hostname];
var now = this.network.now();
if (!entry)
return;
entry.attempts++;
entry.lastAttempt = now;
};
/**
* Mark host as successfully connected.
* @param {String} hostname
*/
HostList.prototype.markSuccess = function markSuccess(hostname) {
var entry = this.map[hostname];
var now = this.network.now();
if (!entry)
return;
if (now - entry.addr.ts > 20 * 60)
entry.addr.ts = now;
};
/**
* Mark host as successfully ack'd.
* @param {String} hostname
* @param {Number} services
*/
HostList.prototype.markAck = function markAck(hostname, services) {
var entry = this.map[hostname];
var now = this.network.now();
var i, bucket, evicted, old, fresh;
if (!entry)
return;
entry.addr.services |= services;
entry.addr.services >>>= 0;
entry.lastSuccess = now;
entry.lastAttempt = now;
entry.attempts = 0;
if (entry.used)
return;
assert(entry.refCount > 0);
// Remove from fresh.
for (i = 0; i < this.fresh.length; i++) {
bucket = this.fresh[i];
if (bucket.remove(entry.key())) {
entry.refCount--;
old = bucket;
}
}
assert(old);
assert(entry.refCount === 0);
this.totalFresh--;
// Find room in used bucket.
bucket = this.usedBucket(entry);
if (bucket.size < this.options.maxEntries) {
entry.used = true;
bucket.push(entry);
this.totalUsed++;
return;
}
// No room. Evict.
evicted = this.evictUsed(bucket);
fresh = this.freshBucket(evicted);
// Move to entry's old bucket if no room.
if (fresh.size >= this.options.maxEntries)
fresh = old;
// Swap to evicted's used bucket.
entry.used = true;
bucket.replace(evicted, entry);
// Move evicted to fresh bucket.
evicted.used = false;
fresh.set(evicted.key(), evicted);
assert(evicted.refCount === 0);
evicted.refCount++;
this.totalFresh++;
};
/**
* Pick used for eviction.
* @param {List} bucket
*/
HostList.prototype.evictUsed = function evictUsed(bucket) {
var old = bucket.head;
var entry;
for (entry = bucket.head; entry; entry = entry.next) {
if (entry.addr.ts < old.addr.ts)
old = entry;
}
return old;
};
/**
* Convert address list to array.
* @returns {NetAddress[]}
*/
HostList.prototype.toArray = function toArray() {
var keys = Object.keys(this.map);
var out = [];
var i, key, entry;
for (i = 0; i < keys.length; i++) {
key = keys[i];
entry = this.map[key];
out.push(entry.addr);
}
assert.equal(out.length, this.size());
return out;
};
/**
* Add a preferred seed.
* @param {String} host
*/
HostList.prototype.addSeed = function addSeed(host) {
var addr = IP.fromHostname(host, this.network.port);
if (addr.type === IP.types.DNS) {
// Defer for resolution.
this.dnsSeeds.push(addr);
return;
}
addr = NetAddress.fromHost(addr.host, addr.port, this.network);
this.add(addr);
return addr;
};
/**
* Add a priority node.
* @param {String} host
*/
HostList.prototype.addNode = function addNode(host) {
var addr = IP.fromHostname(host, this.network.port);
if (addr.type === IP.types.DNS) {
// Defer for resolution.
this.dnsNodes.push(addr);
return;
}
addr = NetAddress.fromHost(addr.host, addr.port, this.network);
this.nodes.push(addr);
this.add(addr);
return addr;
};
/**
* Set initial seeds.
* @param {String[]} seeds
*/
HostList.prototype.setSeeds = function setSeeds(seeds) {
var i, host;
this.dnsSeeds.length = 0;
for (i = 0; i < seeds.length; i++) {
host = seeds[i];
this.addSeed(host);
}
};
/**
* Set priority nodes.
* @param {String[]} nodes
*/
HostList.prototype.setNodes = function setNodes(nodes) {
var i, host;
this.dnsNodes.length = 0;
this.nodes.length = 0;
for (i = 0; i < nodes.length; i++) {
host = nodes[i];
this.addNode(host);
}
};
/**
* Add a local address.
* @param {String} host
* @param {Number} port
* @param {Number} score
* @returns {Boolean}
*/
HostList.prototype.addLocal = function addLocal(host, port, score) {
var addr = NetAddress.fromHost(host, port, this.network);
addr.services = this.options.services;
return this.pushLocal(addr, score);
};
/**
* Add a local address.
* @param {NetAddress} addr
* @param {Number} score
* @returns {Boolean}
*/
HostList.prototype.pushLocal = function pushLocal(addr, score) {
var local;
if (!addr.isRoutable())
return false;
if (this.local.has(addr.hostname))
return false;
local = new LocalAddress(addr, score);
this.local.set(addr.hostname, local);
return true;
};
/**
* Get local address based on reachability.
* @param {NetAddress?} src
* @returns {NetAddress}
*/
HostList.prototype.getLocal = function getLocal(src) {
var keys = this.local.keys();
var bestReach = -1;
var bestScore = -1;
var bestDest = null;
var i, key, dest, reach;
if (!src)
src = this.address;
if (keys.length === 0)
return null;
for (i = 0; i < keys.length; i++) {
key = keys[i];
dest = this.local.get(key);
reach = src.getReachability(dest.addr);
if (reach < bestReach)
continue;
if (reach > bestReach || dest.score > bestScore) {
bestReach = reach;
bestScore = dest.score;
bestDest = dest.addr;
}
}
bestDest.ts = this.network.now();
return bestDest;
};
/**
* Mark local address as seen during a handshake.
* @param {NetAddress} addr
* @returns {Boolean}
*/
HostList.prototype.markLocal = function markLocal(addr) {
var local = this.local.get(addr.hostname);
if (!local)
return false;
local.score++;
return true;
};
/**
* Discover hosts from seeds.
* @method
* @returns {Promise}
*/
HostList.prototype.discoverSeeds = co(function* discoverSeeds() {
var jobs = [];
var i, seed;
for (i = 0; i < this.dnsSeeds.length; i++) {
seed = this.dnsSeeds[i];
jobs.push(this.populateSeed(seed));
}
yield Promise.all(jobs);
});
/**
* Discover hosts from nodes.
* @method
* @returns {Promise}
*/
HostList.prototype.discoverNodes = co(function* discoverNodes() {
var jobs = [];
var i, node;
for (i = 0; i < this.dnsNodes.length; i++) {
node = this.dnsNodes[i];
jobs.push(this.populateNode(node));
}
yield Promise.all(jobs);
});
/**
* Lookup node's domain.
* @method
* @param {Object} addr
* @returns {Promise}
*/
HostList.prototype.populateNode = co(function* populateNode(addr) {
var addrs = yield this.populate(addr);
if (addrs.length === 0)
return;
this.nodes.push(addrs[0]);
this.add(addrs[0]);
});
/**
* Populate from seed.
* @method
* @param {Object} seed
* @returns {Promise}
*/
HostList.prototype.populateSeed = co(function* populateSeed(seed) {
var addrs = yield this.populate(seed);
var i, addr;
for (i = 0; i < addrs.length; i++) {
addr = addrs[i];
this.add(addr);
}
});
/**
* Lookup hosts from dns host.
* @method
* @param {Object} target
* @returns {Promise}
*/
HostList.prototype.populate = co(function* populate(target) {
var addrs = [];
var i, addr, hosts, host;
assert(target.type === IP.types.DNS, 'Resolved host passed.');
this.logger.info('Resolving host: %s.', target.host);
try {
hosts = yield this.resolve(target.host);
} catch (e) {
this.logger.error(e);
return addrs;
}
for (i = 0; i < hosts.length; i++) {
host = hosts[i];
addr = NetAddress.fromHost(host, target.port, this.network);
addrs.push(addr);
}
return addrs;
});
/**
* Convert host list to json-friendly object.
* @returns {Object}
*/
HostList.prototype.toJSON = function toJSON() {
var addrs = [];
var fresh = [];
var used = [];
var i, keys, key, bucket, entry;
keys = Object.keys(this.map);
for (i = 0; i < keys.length; i++) {
key = keys[i];
entry = this.map[key];
addrs.push(entry.toJSON());
}
for (i = 0; i < this.fresh.length; i++) {
bucket = this.fresh[i];
keys = bucket.keys();
fresh.push(keys);
}
for (i = 0; i < this.used.length; i++) {
bucket = this.used[i];
keys = [];
for (entry = bucket.head; entry; entry = entry.next)
keys.push(entry.key());
used.push(keys);
}
return {
version: HostList.VERSION,
addrs: addrs,
fresh: fresh,
used: used
};
};
/**
* Inject properties from json object.
* @private
* @param {Object} json
* @returns {HostList}
*/
HostList.prototype.fromJSON = function fromJSON(json) {
var sources = {};
var map = {};
var fresh = [];
var totalFresh = 0;
var used = [];
var totalUsed = 0;
var i, j, bucket, keys, key, addr, entry, src;
assert(json && typeof json === 'object');
assert(json.version === HostList.VERSION,
'Bad address serialization version.');
assert(Array.isArray(json.addrs));
for (i = 0; i < json.addrs.length; i++) {
addr = json.addrs[i];
entry = HostEntry.fromJSON(addr, this.network);
src = sources[entry.src.hostname];
// Save some memory.
if (!src) {
src = entry.src;
sources[src.hostname] = src;
}
entry.src = src;
map[entry.key()] = entry;
}
assert(Array.isArray(json.fresh));
assert(json.fresh.length <= this.options.maxBuckets,
'Buckets mismatch.');
for (i = 0; i < json.fresh.length; i++) {
keys = json.fresh[i];
bucket = new Map();
for (j = 0; j < keys.length; j++) {
key = keys[j];
entry = map[key];
assert(entry);
if (entry.refCount === 0)
totalFresh++;
entry.refCount++;
bucket.set(key, entry);
}
assert(bucket.size <= this.options.maxEntries,
'Bucket size mismatch.');
fresh.push(bucket);
}
assert(fresh.length === this.fresh.length,
'Buckets mismatch.');
assert(Array.isArray(json.used));
assert(json.used.length <= this.options.maxBuckets,
'Buckets mismatch.');
for (i = 0; i < json.used.length; i++) {
keys = json.used[i];
bucket = new List();
for (j = 0; j < keys.length; j++) {
key = keys[j];
entry = map[key];
assert(entry);
assert(entry.refCount === 0);
assert(!entry.used);
entry.used = true;
totalUsed++;
bucket.push(entry);
}
assert(bucket.size <= this.options.maxEntries,
'Bucket size mismatch.');
used.push(bucket);
}
assert(used.length === this.used.length,
'Buckets mismatch.');
keys = Object.keys(map);
for (i = 0; i < keys.length; i++) {
key = keys[i];
entry = map[key];
assert(entry.used || entry.refCount > 0);
}
this.map = map;
this.fresh = fresh;
this.totalFresh = totalFresh;
this.used = used;
this.totalUsed = totalUsed;
return this;
};
/**
* Instantiate host list from json object.
* @param {Object} options
* @param {Object} json
* @returns {HostList}
*/
HostList.fromJSON = function fromJSON(options, json) {
return new HostEntry(options).fromJSON(json);
};
/**
* HostEntry
* @alias module:net.HostEntry
* @constructor
* @param {NetAddress} addr
* @param {NetAddress} src
*/
function HostEntry(addr, src) {
if (!(this instanceof HostEntry))
return new HostEntry(addr, src);
this.addr = addr || new NetAddress();
this.src = src || new NetAddress();
this.prev = null;
this.next = null;
this.used = false;
this.refCount = 0;
this.attempts = 0;
this.lastSuccess = 0;
this.lastAttempt = 0;
if (addr)
this.fromOptions(addr, src);
}
/**
* Inject properties from options.
* @private
* @param {NetAddress} addr
* @param {NetAddress} src
* @returns {HostEntry}
*/
HostEntry.prototype.fromOptions = function fromOptions(addr, src) {
assert(addr instanceof NetAddress);
assert(src instanceof NetAddress);
this.addr = addr;
this.src = src;
return this;
};
/**
* Instantiate host entry from options.
* @param {NetAddress} addr
* @param {NetAddress} src
* @returns {HostEntry}
*/
HostEntry.fromOptions = function fromOptions(addr, src) {
return new HostEntry().fromOptions(addr, src);
};
/**
* Get key suitable for a hash table (hostname).
* @returns {String}
*/
HostEntry.prototype.key = function key() {
return this.addr.hostname;
};
/**
* Get host priority.
* @param {Number} now
* @returns {Number}
*/
HostEntry.prototype.chance = function _chance(now) {
var attempts = this.attempts;
var chance = 1;
if (now - this.lastAttempt < 60 * 10)
chance *= 0.01;
chance *= Math.pow(0.66, Math.min(attempts, 8));
return chance;
};
/**
* Inspect host address.
* @returns {Object}
*/
HostEntry.prototype.inspect = function inspect() {
return {
addr: this.addr,
src: this.src,
used: this.used,
refCount: this.refCount,
attempts: this.attempts,
lastSuccess: util.date(this.lastSuccess),
lastAttempt: util.date(this.lastAttempt)
};
};
/**
* Convert host entry to json-friendly object.
* @returns {Object}
*/
HostEntry.prototype.toJSON = function toJSON() {
return {
addr: this.addr.hostname,
src: this.src.hostname,
services: this.addr.services.toString(2),
ts: this.addr.ts,
attempts: this.attempts,
lastSuccess: this.lastSuccess,
lastAttempt: this.lastAttempt
};
};
/**
* Inject properties from json object.
* @private
* @param {Object} json
* @param {Network} network
* @returns {HostEntry}
*/
HostEntry.prototype.fromJSON = function fromJSON(json, network) {
assert(json && typeof json === 'object');
assert(typeof json.addr === 'string');
assert(typeof json.src === 'string');
this.addr.fromHostname(json.addr, network);
if (json.services != null) {
assert(typeof json.services === 'string');
assert(json.services.length > 0);
assert(json.services.length <= 32);
this.addr.services = parseInt(json.services, 2);
assert(util.isUInt32(this.addr.services));
}
if (json.ts != null) {
assert(util.isNumber(json.ts));
assert(json.ts >= 0);
this.addr.ts = json.ts;
}
if (json.src != null) {
assert(typeof json.src === 'string');
this.src.fromHostname(json.src, network);
}
if (json.attempts != null) {
assert(util.isNumber(json.attempts));
assert(json.attempts >= 0);
this.attempts = json.attempts;
}
if (json.lastSuccess != null) {
assert(util.isNumber(json.lastSuccess));
assert(json.lastSuccess >= 0);
this.lastSuccess = json.lastSuccess;
}
if (json.lastAttempt != null) {
assert(util.isNumber(json.lastAttempt));
assert(json.lastAttempt >= 0);
this.lastAttempt = json.lastAttempt;
}
return this;
};
/**
* Instantiate host entry from json object.
* @param {Object} json
* @param {Network} network
* @returns {HostEntry}
*/
HostEntry.fromJSON = function fromJSON(json, network) {
return new HostEntry().fromJSON(json, network);
};
/**
* LocalAddress
* @alias module:net.LocalAddress
* @constructor
* @param {NetAddress} addr
* @param {Number?} score
*/
function LocalAddress(addr, score) {
this.addr = addr;
this.score = score || 0;
}
/**
* Host List Options
* @alias module:net.HostListOptions
* @constructor
* @param {Object?} options
*/
function HostListOptions(options) {
if (!(this instanceof HostListOptions))
return new HostListOptions(options);
this.network = Network.primary;
this.logger = Logger.global;
this.resolve = dns.lookup;
this.host = '0.0.0.0';
this.port = this.network.port;
this.services = common.LOCAL_SERVICES;
this.onion = false;
this.banTime = common.BAN_TIME;
this.address = new NetAddress();
this.address.services = this.services;
this.address.ts = this.network.now();
this.seeds = this.network.seeds;
this.nodes = [];
this.maxBuckets = 20;
this.maxEntries = 50;
this.prefix = null;
this.filename = null;
this.persistent = false;
this.flushInterval = 120000;
if (options)
this.fromOptions(options);
}
/**
* Inject properties from options.
* @private
* @param {Object} options
*/
HostListOptions.prototype.fromOptions = function fromOptions(options) {
var raw;
assert(options, 'Options are required.');
if (options.network != null) {
this.network = Network.get(options.network);
this.seeds = this.network.seeds;
this.address.port = this.network.port;
this.port = this.network.port;
}
if (options.logger != null) {
assert(typeof options.logger === 'object');
this.logger = options.logger;
}
if (options.resolve != null) {
assert(typeof options.resolve === 'function');
this.resolve = options.resolve;
}
if (options.banTime != null) {
assert(options.banTime >= 0);
this.banTime = options.banTime;
}
if (options.seeds) {
assert(Array.isArray(options.seeds));
this.seeds = options.seeds;
}
if (options.nodes) {
assert(Array.isArray(options.nodes));
this.nodes = options.nodes;
}
if (options.host != null) {
assert(typeof options.host === 'string');
raw = IP.toBuffer(options.host);
this.host = IP.toString(raw);
if (IP.isRoutable(raw))
this.address.setHost(this.host);
}
if (options.port != null) {
assert(typeof options.port === 'number');
assert(options.port > 0 && options.port <= 0xffff);
this.port = options.port;
this.address.setPort(this.port);
}
if (options.publicHost != null) {
assert(typeof options.publicHost === 'string');
this.address.setHost(options.publicHost);
}
if (options.publicPort != null) {
assert(typeof options.publicPort === 'number');
assert(options.publicPort > 0 && options.publicPort <= 0xffff);
this.address.setPort(options.publicPort);
}
if (options.services != null) {
assert(typeof options.services === 'number');
this.services = options.services;
}
if (options.onion != null) {
assert(typeof options.onion === 'boolean');
this.onion = options.onion;
}
if (options.maxBuckets != null) {
assert(typeof options.maxBuckets === 'number');
this.maxBuckets = options.maxBuckets;
}
if (options.maxEntries != null) {
assert(typeof options.maxEntries === 'number');
this.maxEntries = options.maxEntries;
}
if (options.persistent != null) {
assert(typeof options.persistent === 'boolean');
this.persistent = options.persistent;
}
if (options.prefix != null) {
assert(typeof options.prefix === 'string');
this.prefix = options.prefix;
this.filename = this.prefix + '/hosts.json';
}
if (options.filename != null) {
assert(typeof options.filename === 'string');
this.filename = options.filename;
}
if (options.flushInterval != null) {
assert(options.flushInterval >= 0);
this.flushInterval = options.flushInterval;
}
this.address.ts = this.network.now();
this.address.services = this.services;
return this;
};
/*
* Expose
*/
module.exports = HostList;