net: improvements. see comments.

- Improved local address handling.
- Enabled upnp discovery.
- Added retry for dns seeds.
- Improved hostlist in general.
This commit is contained in:
Christopher Jeffrey 2017-03-07 17:04:16 -08:00
parent 614a952015
commit cdbad54a8c
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
22 changed files with 892 additions and 320 deletions

View File

@ -1,7 +1,6 @@
'use strict';
var net = require('net');
var dns = require('dns');
var EventEmitter = require('events').EventEmitter;
var IOServer = require('socket.io');
var util = require('../lib/utils/util');
@ -65,47 +64,6 @@ WSProxy.prototype._handleSocket = function _handleSocket(ws) {
ws.on('tcp connect', function(port, host, nonce) {
self._handleConnect(ws, port, host, nonce);
});
ws.on('dns resolve', function(name, record, callback) {
self._handleResolve(ws, name, record, callback);
});
};
WSProxy.prototype._handleResolve = function _handleResolve(ws, name, record, callback) {
if (typeof name !== 'string') {
ws.disconnect();
return;
}
if (typeof record !== 'string') {
ws.disconnect();
return;
}
if (typeof callback !== 'function') {
ws.disconnect();
return;
}
if (record !== 'A' && record !== 'AAAA') {
this.log('Client sent a bad record type: %s.', record);
ws.disconnect();
return;
}
if (!NAME_REGEX.test(name) || name.length > 200) {
this.log('Client sent a bad domain: %s.', name);
ws.disconnect();
return;
}
dns.resolve(name, record, function(err, result) {
if (err) {
callback({ message: err.message, code: err.code });
return;
}
callback(null, result);
});
};
WSProxy.prototype._handleConnect = function _handleConnect(ws, port, host, nonce) {

View File

@ -63,6 +63,7 @@ max-inbound: 30
# Proxy Server (browser=websockets, node=socks)
# proxy: foo:bar@127.0.0.1:9050
# onion: true
# upnp: true
# Custom list of DNS seeds
# seeds: seed.bitcoin.sipa.be

View File

@ -386,7 +386,7 @@ RPC.prototype.getnetworkinfo = co(function* getnetworkinfo(args, help) {
version: pkg.version,
subversion: this.pool.options.agent,
protocolversion: this.pool.options.version,
localservices: this.pool.address.services,
localservices: this.pool.options.services,
timeoffset: this.network.time.offset,
connections: this.pool.peers.size(),
networks: [],
@ -522,7 +522,9 @@ RPC.prototype.getpeerinfo = co(function* getpeerinfo(args, help) {
peers.push({
id: id++,
addr: peer.hostname(),
addrlocal: peer.hostname(),
addrlocal: !peer.local.isNull()
? peer.local.hostname
: undefined,
relaytxes: !peer.noRelay,
lastsend: peer.lastSend / 1000 | 0,
lastrecv: peer.lastRecv / 1000 | 0,

View File

@ -623,6 +623,10 @@ HTTPServer.prototype._init = function _init() {
this.get('/', co(function* (req, res) {
var totalTX = this.mempool ? this.mempool.totalTX : 0;
var size = this.mempool ? this.mempool.getSize() : 0;
var addr = this.pool.hosts.getLocal();
if (!addr)
addr = this.pool.hosts.address;
res.send(200, {
version: pkg.version,
@ -633,10 +637,10 @@ HTTPServer.prototype._init = function _init() {
progress: this.chain.getProgress()
},
pool: {
host: this.pool.address.host,
port: this.pool.address.port,
host: addr.host,
port: addr.port,
agent: this.pool.options.agent,
services: this.pool.address.services.toString(2),
services: this.pool.options.services.toString(2),
outbound: this.pool.peers.outbound,
inbound: this.pool.peers.inbound
},

View File

@ -467,7 +467,7 @@ function AuthDB(options) {
return new AuthDB(options);
this.logger = null;
this.resolve = dns.resolve;
this.resolve = dns.lookup;
this.dnsKnown = [];
this.known = {};
@ -476,6 +476,26 @@ function AuthDB(options) {
this._init(options);
}
/**
* Open auth database (lookup known peers).
* @method
* @returns {Promise}
*/
AuthDB.prototype.open = co(function* open() {
yield this.lookup();
});
/**
* Close auth database.
* @method
* @returns {Promise}
*/
AuthDB.prototype.close = co(function* close() {
;
});
/**
* Initialize authdb with options.
* @param {Object} options
@ -601,7 +621,7 @@ AuthDB.prototype.getKnown = function getKnown(hostname) {
* @returns {Promise}
*/
AuthDB.prototype.discover = co(function* discover() {
AuthDB.prototype.lookup = co(function* lookup() {
var jobs = [];
var i, addr;
@ -610,8 +630,6 @@ AuthDB.prototype.discover = co(function* discover() {
jobs.push(this.populate(addr[0], addr[1]));
}
this.dnsKnown.length = 0;
yield Promise.all(jobs);
});

View File

@ -6,36 +6,17 @@
'use strict';
var ProxySocket = require('./proxysocket');
var socket;
/**
* Resolve host (no getaddrinfo).
* @ignore
* @param {String} host
* @param {String?} proxy
* @param {Boolean?} onion
* @param {String?} proxy - Tor socks proxy.
* @returns {Promise}
*/
exports.resolve = function resolve(host, proxy, onion) {
exports.resolve = function resolve(host, proxy) {
return new Promise(function(resolve, reject) {
if (!socket)
socket = new ProxySocket(proxy);
socket.resolve(host, 'A', function(err, result) {
if (err) {
reject(err);
return;
}
if (result.length === 0) {
reject(new Error('No DNS results.'));
return;
}
resolve(result);
});
reject(new Error('DNS not supported.'));
});
};
@ -43,11 +24,12 @@ exports.resolve = function resolve(host, proxy, onion) {
* Resolve host (getaddrinfo).
* @ignore
* @param {String} host
* @param {String?} proxy
* @param {Boolean?} onion
* @param {String?} proxy - Tor socks proxy.
* @returns {Promise}
*/
exports.lookup = function lookup(host, proxy, onion) {
return exports.resolve(host, proxy, onion);
exports.lookup = function lookup(host, proxy) {
return new Promise(function(resolve, reject) {
reject(new Error('DNS not supported.'));
});
};

View File

@ -22,13 +22,12 @@ var options = {
/**
* Resolve host (no getaddrinfo).
* @param {String} host
* @param {String?} proxy
* @param {Boolean?} onion
* @param {String?} proxy - Tor socks proxy.
* @returns {Promise}
*/
exports.resolve = function resolve(host, proxy, onion) {
if (proxy && onion)
exports.resolve = function resolve(host, proxy) {
if (proxy)
return socks.resolve(proxy, host);
return new Promise(function(resolve, reject) {
@ -51,13 +50,12 @@ exports.resolve = function resolve(host, proxy, onion) {
/**
* Resolve host (getaddrinfo).
* @param {String} host
* @param {String?} proxy
* @param {Boolean?} onion
* @param {String?} proxy - Tor socks proxy.
* @returns {Promise}
*/
exports.lookup = function lookup(host, proxy, onion) {
if (proxy && onion)
exports.lookup = function lookup(host, proxy) {
if (proxy)
return socks.resolve(proxy, host);
return new Promise(function(resolve, reject) {

View File

@ -7,6 +7,7 @@
'use strict';
var assert = require('assert');
var fs = require('fs');
var util = require('../utils/util');
var IP = require('../utils/ip');
var co = require('../utils/co');
@ -16,8 +17,9 @@ 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 fs = require('fs');
var Logger = require('../node/logger');
/**
* Host List
@ -30,45 +32,76 @@ function HostList(options) {
if (!(this instanceof HostList))
return new HostList(options);
this.network = Network.primary;
this.logger = null;
this.address = new NetAddress();
this.resolve = dns.resolve;
this.banTime = common.BAN_TIME;
this.options = new HostListOptions(options);
this.network = this.options.network;
this.logger = this.options.logger;
this.address = this.options.address;
this.resolve = this.options.resolve;
this.rawSeeds = this.network.seeds;
this.dnsSeeds = [];
this.rawNodes = [];
this.dnsNodes = [];
this.nodes = [];
this.banned = {};
this.map = {};
this.fresh = [];
this.used = [];
this.totalFresh = 0;
this.used = [];
this.totalUsed = 0;
this.nodes = [];
this.local = new Map();
this.banned = {};
this.maxBuckets = 20;
this.maxEntries = 50;
this.maxAddresses = this.maxBuckets * this.maxEntries;
this.horizonDays = 30;
this.retries = 3;
this.minFailDays = 7;
this.maxFailures = 10;
this.maxRefs = 8;
this.filename = null;
this.flushInterval = 120000;
this.timer = null;
this._initOptions(options);
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}
@ -78,58 +111,19 @@ function HostList(options) {
HostList.VERSION = 0;
/**
* Initialize options.
* @private
* Local address scores.
* @enum {Number}
* @default
*/
HostList.prototype._initOptions = function initOptions(options) {
if (!options)
return;
if (options.network != null) {
this.network = Network.get(options.network);
this.rawSeeds = this.network.seeds;
}
if (options.logger != null) {
assert(typeof options.logger === 'object');
this.logger = options.logger;
}
if (options.address != null) {
assert(options.address instanceof NetAddress);
this.address = options.address;
}
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.rawSeeds = options.seeds;
}
if (options.nodes) {
assert(Array.isArray(options.nodes));
this.rawNodes = options.nodes;
}
if (options.hostLocation != null) {
assert(typeof options.hostLocation === 'string');
this.filename = options.hostLocation;
}
if (options.flushInterval != null) {
assert(options.flushInterval >= 0);
this.flushInterval = options.flushInterval;
}
HostList.scores = {
NONE: 0,
IF: 1,
BIND: 2,
UPNP: 3,
HTTP: 3,
MANUAL: 4,
MAX: 5
};
/**
@ -138,16 +132,28 @@ HostList.prototype._initOptions = function initOptions(options) {
*/
HostList.prototype._init = function init() {
var i;
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.maxBuckets; i++)
for (i = 0; i < this.options.maxBuckets; i++)
this.fresh.push(new Map());
for (i = 0; i < this.maxBuckets; i++)
for (i = 0; i < this.options.maxBuckets; i++)
this.used.push(new List());
this.setSeeds(this.rawSeeds);
this.setNodes(this.rawNodes);
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);
}
};
/**
@ -158,14 +164,17 @@ HostList.prototype._init = function init() {
HostList.prototype.open = co(function* open() {
try {
yield this.read();
yield this.loadFile();
} catch (e) {
if (this.logger) {
this.logger.warning('Hosts deserialization failed.');
this.logger.error(e);
}
this.logger.warning('Hosts deserialization failed.');
this.logger.error(e);
}
if (this.size() === 0)
this.injectSeeds();
yield this.discoverNodes();
this.start();
});
@ -178,6 +187,7 @@ HostList.prototype.open = co(function* open() {
HostList.prototype.close = co(function* close() {
this.stop();
yield this.flush();
this.reset();
});
/**
@ -185,11 +195,11 @@ HostList.prototype.close = co(function* close() {
*/
HostList.prototype.start = function start() {
if (!this.filename)
if (!this.options.hostLocation)
return;
assert(!this.timer);
this.timer = setInterval(this.flush.bind(this), this.flushInterval);
assert(this.timer == null);
this.timer = co.setInterval(this.flush, this.options.flushInterval, this);
};
/**
@ -197,28 +207,56 @@ HostList.prototype.start = function start() {
*/
HostList.prototype.stop = function stop() {
if (!this.filename)
if (!this.options.hostLocation)
return;
assert(this.timer != null);
clearInterval(this.timer);
co.clearInterval(this.timer);
this.timer = null;
};
/**
* Read and initialie from hosts file.
* Read and initialize from hosts file.
* @method
* @returns {Promise}
*/
HostList.prototype.read = co(function* read() {
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.hostLocation;
var data, json;
if (!this.filename)
if (!filename)
return;
try {
data = yield readFile(this.filename, 'utf8');
data = yield readFile(filename, 'utf8');
} catch (e) {
if (e.code === 'ENOENT')
return;
@ -237,24 +275,22 @@ HostList.prototype.read = co(function* read() {
*/
HostList.prototype.flush = co(function* flush() {
var filename = this.options.hostLocation;
var json, data;
if (!this.filename)
if (!filename)
return;
if (this.logger)
this.logger.debug('Writing hosts to %s.', this.filename);
this.logger.debug('Writing hosts to %s.', filename);
json = this.toJSON();
data = JSON.stringify(json);
try {
yield writeFile(this.filename, data, 'utf8');
yield writeFile(filename, data, 'utf8');
} catch (e) {
if (this.logger) {
this.logger.warning('Writing hosts failed.');
this.logger.error(e);
}
this.logger.warning('Writing hosts failed.');
this.logger.error(e);
}
});
@ -273,7 +309,8 @@ HostList.prototype.size = function size() {
*/
HostList.prototype.isFull = function isFull() {
return this.size() >= this.maxAddresses;
var max = this.options.maxBuckets * this.options.maxEntries;
return this.size() >= max;
};
/**
@ -297,6 +334,8 @@ HostList.prototype.reset = function reset() {
this.totalFresh = 0;
this.totalUsed = 0;
this.nodes.length = 0;
};
/**
@ -337,7 +376,7 @@ HostList.prototype.isBanned = function isBanned(host) {
if (time == null)
return false;
if (util.now() > time + this.banTime) {
if (util.now() > time + this.options.banTime) {
delete this.banned[host];
return false;
}
@ -477,10 +516,10 @@ HostList.prototype.add = function add(addr, src) {
// Do not update if the max
// reference count is reached.
if (entry.refCount === this.maxRefs)
if (entry.refCount === HostList.MAX_REFS)
return false;
assert(entry.refCount < this.maxRefs);
assert(entry.refCount < HostList.MAX_REFS);
// Stochastic test: previous refCount
// N: 2^N times harder to increase it.
@ -506,7 +545,7 @@ HostList.prototype.add = function add(addr, src) {
if (bucket.has(entry.key()))
return false;
if (bucket.size >= this.maxEntries)
if (bucket.size >= this.options.maxEntries)
this.evictFresh(bucket);
bucket.set(entry.key(), entry);
@ -579,14 +618,14 @@ HostList.prototype.isStale = function isStale(entry) {
if (entry.addr.ts === 0)
return true;
if (now - entry.addr.ts > this.horizonDays * 24 * 60 * 60)
if (now - entry.addr.ts > HostList.HORIZON_DAYS * 24 * 60 * 60)
return true;
if (entry.lastSuccess === 0 && entry.attempts >= this.retries)
if (entry.lastSuccess === 0 && entry.attempts >= HostList.RETRIES)
return true;
if (now - entry.lastSuccess > this.minFailDays * 24 * 60 * 60) {
if (entry.attempts >= this.maxFailures)
if (now - entry.lastSuccess > HostList.MIN_FAIL_DAYS * 24 * 60 * 60) {
if (entry.attempts >= HostList.MAX_FAILURES)
return true;
}
@ -713,7 +752,7 @@ HostList.prototype.markAck = function markAck(hostname, services) {
// Find room in used bucket.
bucket = this.usedBucket(entry);
if (bucket.size < this.maxEntries) {
if (bucket.size < this.options.maxEntries) {
entry.used = true;
bucket.push(entry);
this.totalUsed++;
@ -725,7 +764,7 @@ HostList.prototype.markAck = function markAck(hostname, services) {
fresh = this.freshBucket(evicted);
// Move to entry's old bucket if no room.
if (fresh.size >= this.maxEntries)
if (fresh.size >= this.options.maxEntries)
fresh = old;
// Swap to evicted's used bucket.
@ -855,27 +894,131 @@ HostList.prototype.setNodes = function setNodes(nodes) {
};
/**
* Discover hosts from seeds and nodes.
* 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.discover = co(function* discover() {
HostList.prototype.discoverSeeds = co(function* discoverSeeds() {
var jobs = [];
var i, node, seed;
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));
}
this.dnsNodes.length = 0;
yield Promise.all(jobs);
});
@ -926,14 +1069,12 @@ HostList.prototype.populate = co(function* populate(target) {
assert(target.type === IP.types.DNS, 'Resolved host passed.');
if (this.logger)
this.logger.info('Resolving host: %s.', target.host);
this.logger.info('Resolving host: %s.', target.host);
try {
hosts = yield this.resolve(target.host);
} catch (e) {
if (this.logger)
this.logger.error(e);
this.logger.error(e);
return addrs;
}
@ -1027,6 +1168,8 @@ HostList.prototype.fromJSON = function fromJSON(json) {
}
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];
@ -1042,7 +1185,7 @@ HostList.prototype.fromJSON = function fromJSON(json) {
bucket.set(key, entry);
}
assert(bucket.size <= this.maxEntries,
assert(bucket.size <= this.options.maxEntries,
'Bucket size mismatch.');
fresh.push(bucket);
@ -1052,6 +1195,8 @@ HostList.prototype.fromJSON = function fromJSON(json) {
'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];
@ -1068,7 +1213,7 @@ HostList.prototype.fromJSON = function fromJSON(json) {
bucket.push(entry);
}
assert(bucket.size <= this.maxEntries,
assert(bucket.size <= this.options.maxEntries,
'Bucket size mismatch.');
used.push(bucket);
@ -1285,6 +1430,161 @@ 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.hostLocation = null;
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.hostLocation != null) {
assert(typeof options.hostLocation === 'string');
this.hostLocation = options.hostLocation;
}
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;
};
/*
* Helpers
*/

View File

@ -169,8 +169,8 @@ function VersionPacket(options) {
this.version = common.PROTOCOL_VERSION;
this.services = common.LOCAL_SERVICES;
this.ts = util.now();
this.recv = new NetAddress();
this.from = new NetAddress();
this.remote = new NetAddress();
this.local = new NetAddress();
this.nonce = encoding.ZERO_U64;
this.agent = common.USER_AGENT;
this.height = 0;
@ -201,11 +201,11 @@ VersionPacket.prototype.fromOptions = function fromOptions(options) {
if (options.ts != null)
this.ts = options.ts;
if (options.recv)
this.recv.fromOptions(options.recv);
if (options.remote)
this.remote.fromOptions(options.remote);
if (options.from)
this.from.fromOptions(options.from);
if (options.local)
this.local.fromOptions(options.local);
if (options.nonce)
this.nonce = options.nonce;
@ -240,8 +240,8 @@ VersionPacket.fromOptions = function fromOptions(options) {
VersionPacket.prototype.getSize = function getSize() {
var size = 0;
size += 20;
size += this.recv.getSize(false);
size += this.from.getSize(false);
size += this.remote.getSize(false);
size += this.local.getSize(false);
size += 8;
size += encoding.sizeVarString(this.agent, 'ascii');
size += 5;
@ -258,8 +258,8 @@ VersionPacket.prototype.toWriter = function toWriter(bw) {
bw.writeU32(this.services);
bw.writeU32(0);
bw.write64(this.ts);
this.recv.toWriter(bw, false);
this.from.toWriter(bw, false);
this.remote.toWriter(bw, false);
this.local.toWriter(bw, false);
bw.writeBytes(this.nonce);
bw.writeVarString(this.agent, 'ascii');
bw.write32(this.height);
@ -292,10 +292,10 @@ VersionPacket.prototype.fromReader = function fromReader(br) {
br.readU32();
this.ts = br.read53();
this.recv.fromReader(br, false);
this.remote.fromReader(br, false);
if (br.left() > 0) {
this.from.fromReader(br, false);
this.local.fromReader(br, false);
this.nonce = br.readBytes(8);
}

View File

@ -84,6 +84,7 @@ function Peer(options) {
this.outbound = false;
this.loader = false;
this.address = new NetAddress();
this.local = new NetAddress();
this.connected = false;
this.destroyed = false;
this.ack = false;
@ -979,8 +980,9 @@ Peer.prototype.sendVersion = function sendVersion() {
packet.version = this.options.version;
packet.services = this.options.services;
packet.ts = this.network.now();
packet.recv = this.address;
packet.from = this.options.address;
packet.remote = this.address;
packet.local.setNull();
packet.local.services = this.options.services;
packet.nonce = this.options.createNonce(this.hostname());
packet.agent = this.options.agent;
packet.height = this.options.getHeight();
@ -1703,6 +1705,7 @@ Peer.prototype.handleVersion = co(function* handleVersion(packet) {
this.height = packet.height;
this.agent = packet.agent;
this.noRelay = packet.noRelay;
this.local = packet.remote;
if (!this.network.selfConnect) {
if (this.options.hasNonce(packet.nonce))
@ -2275,7 +2278,6 @@ function PeerOptions(options) {
this.createSocket = tcp.createSocket;
this.version = common.PROTOCOL_VERSION;
this.services = common.LOCAL_SERVICES;
this.address = new NetAddress();
this.agent = common.USER_AGENT;
this.noRelay = false;
this.spv = false;
@ -2327,11 +2329,6 @@ PeerOptions.prototype.fromOptions = function fromOptions(options) {
this.services = options.services;
}
if (options.address != null) {
assert(typeof options.address === 'object');
this.address = options.address;
}
if (options.agent != null) {
assert(typeof options.agent === 'string');
this.agent = options.agent;

View File

@ -15,7 +15,6 @@ var IP = require('../utils/ip');
var co = require('../utils/co');
var common = require('./common');
var chainCommon = require('../blockchain/common');
var NetAddress = require('../primitives/netaddress');
var Address = require('../primitives/address');
var BIP150 = require('./bip150');
var BIP151 = require('./bip151');
@ -30,12 +29,14 @@ var List = require('../utils/list');
var tcp = require('./tcp');
var dns = require('./dns');
var HostList = require('./hostlist');
var UPNP = require('./upnp');
var InvItem = require('../primitives/invitem');
var Map = require('../utils/map');
var packets = require('./packets');
var services = common.services;
var invTypes = InvItem.types;
var packetTypes = packets.types;
var scores = HostList.scores;
/**
* A pool of peers for handling all network activity.
@ -85,7 +86,6 @@ function Pool(options) {
this.mempool = this.options.mempool;
this.server = this.options.createServer();
this.nonces = this.options.nonces;
this.address = this.options.address;
this.locker = new Lock(true);
this.connected = false;
@ -127,7 +127,15 @@ util.inherits(Pool, AsyncObject);
* @default
*/
Pool.MAX_HEADER_FAILS = 500;
Pool.MAX_HEADER_FAILS = 1000;
/**
* Discovery interval for UPNP and DNS seeds.
* @const {Number}
* @default
*/
Pool.DISCOVERY_INTERVAL = 120000;
/**
* Initialize the pool.
@ -286,38 +294,24 @@ Pool.prototype.connect = co(function* connect() {
*/
Pool.prototype._connect = co(function* connect() {
var ip;
assert(this.loaded, 'Pool is not loaded.');
if (this.connected)
return;
if (this.address.isNull() && !this.options.proxy) {
try {
ip = yield this.getIP();
} catch (e) {
this.logger.error(e);
}
if (ip) {
this.address.setHost(ip);
this.logger.info('External IP found: %s.', ip);
}
}
yield this.hosts.open();
yield this.authdb.open();
yield this.authdb.discover();
yield this.hosts.discover();
if (this.hosts.size() === 0)
throw new Error('No hosts available. Do you have an internet connection?');
this.logger.info('Resolved %d hosts from DNS seeds.', this.hosts.size());
yield this.discoverGateway();
yield this.discoverExternal();
yield this.discoverSeeds();
this.fillOutbound();
yield this.listen();
this.startTimer();
this.connected = true;
});
@ -380,6 +374,11 @@ Pool.prototype._disconnect = co(function* disconnect() {
this.headerChain.reset();
this.headerNext = null;
this.stopTimer();
yield this.authdb.close();
yield this.hosts.close();
yield this.unlisten();
this.disconnecting = false;
@ -423,6 +422,160 @@ Pool.prototype.unlisten = co(function* unlisten() {
yield this.server.close();
});
/**
* Start discovery timer.
* @private
*/
Pool.prototype.startTimer = function startTimer() {
assert(this.timer == null, 'Timer already started.');
this.timer = co.setInterval(this.discover, Pool.DISCOVERY_INTERVAL, this);
};
/**
* Stop discovery timer.
* @private
*/
Pool.prototype.stopTimer = function stopTimer() {
assert(this.timer != null, 'Timer already stopped.');
co.clearInterval(this.timer);
this.timer = null;
};
/**
* Rediscover seeds and internet gateway.
* Attempt to add port mapping once again.
* @returns {Promise}
*/
Pool.prototype.discover = co(function* discover() {
yield this.discoverGateway();
yield this.discoverSeeds(true);
});
/**
* Attempt to add port mapping (i.e.
* remote:8333->local:8333) via UPNP.
* @returns {Promise}
*/
Pool.prototype.discoverGateway = co(function* discoverGateway() {
var src = this.options.publicPort;
var dest = this.options.port;
var wan, host;
// Pointless if we're not listening.
if (!this.options.listen)
return;
// UPNP is always optional, since
// it's likely to not work anyway.
if (!this.options.upnp)
return;
try {
this.logger.debug('Discovering internet gateway (upnp).');
wan = yield UPNP.discover();
} catch (e) {
this.logger.debug('UPNP error:');
this.logger.error(e);
return false;
}
try {
host = yield wan.getExternalIP();
} catch (e) {
this.logger.debug('Could not find external IP.');
this.logger.error(e);
return false;
}
if (this.hosts.addLocal(host, src, scores.UPNP))
this.logger.info('External IP found (upnp): %s.', host);
this.logger.debug(
'Adding port mapping %d->%d.',
src, dest);
try {
yield wan.addPortMapping(host, src, dest);
} catch (e) {
this.logger.debug('Could not add port mapping.');
this.logger.error(e);
return false;
}
return true;
});
/**
* Attempt to resolve DNS seeds if necessary.
* @param {Boolean} checkPeers
* @returns {Promise}
*/
Pool.prototype.discoverSeeds = co(function* discoverSeeds(checkPeers) {
var total = 0;
var max = Math.min(2, this.options.maxOutbound);
var size = this.hosts.size();
var peer;
for (peer = this.peers.head(); peer; peer = peer.next) {
if (peer.handshake) {
if (++total > max)
break;
}
}
if (size === 0 || (checkPeers && total < max)) {
this.logger.warning('Could not find enough peers.');
this.logger.warning('Hitting DNS seeds...');
yield this.hosts.discoverSeeds();
this.logger.info(
'Resolved %d hosts from DNS seeds.',
this.hosts.size() - size);
this.refill();
}
});
/**
* Attempt to discover external IP via HTTP.
* @returns {Promise}
*/
Pool.prototype.discoverExternal = co(function* discoverExternal() {
var port = this.options.publicPort;
var host;
// Pointless if we're not listening.
if (!this.options.listen)
return;
// Never hit an HTTP server if
// we're using an outbound proxy.
if (this.options.proxy)
return;
// Try not to hit this if we can avoid it.
if (this.hosts.local.size > 0)
return;
try {
host = yield this.getIP();
} catch (e) {
this.logger.debug('Could not find external IP.');
this.logger.error(e);
return;
}
if (this.hosts.addLocal(host, port, scores.HTTP))
this.logger.info('External IP found (http): %s.', host);
});
/**
* Handle incoming connection.
* @private
@ -1118,11 +1271,13 @@ Pool.prototype.handleConnect = co(function* handleConnect(peer) {
*/
Pool.prototype.handleOpen = co(function* handleOpen(peer) {
var addr;
// Advertise our address.
if (!this.address.isNull()
&& !this.options.selfish
&& this.options.listen) {
peer.send(new packets.AddrPacket([this.address]));
if (!this.options.selfish && this.options.listen) {
addr = this.hosts.getLocal(peer.address);
if (addr)
peer.send(new packets.AddrPacket([addr]));
}
// We want compact blocks!
@ -1239,6 +1394,9 @@ Pool.prototype.handleVersion = co(function* handleVersion(peer, packet) {
this.network.time.add(peer.hostname(), packet.ts);
this.nonces.remove(peer.hostname());
if (!peer.outbound && packet.remote.isRoutable())
this.hosts.markLocal(packet.remote);
});
/**
@ -2492,7 +2650,7 @@ Pool.prototype.handleSendCmpct = co(function* handleSendCmpct(peer, packet) {
Pool.prototype.handleCmpctBlock = co(function* handleCmpctBlock(peer, packet) {
var block = packet.block;
var hash = block.hash('hex');
var witness = peer.hasWitness();
var witness = peer.compactWitness;
var flags = chainCommon.flags.VERIFY_BODY;
var result;
@ -2507,7 +2665,7 @@ Pool.prototype.handleCmpctBlock = co(function* handleCmpctBlock(peer, packet) {
return;
}
if (!peer.hasCompactSupport()) {
if (!peer.hasCompactSupport() && !peer.hasCompact()) {
this.logger.info(
'Peer sent unsolicited cmpctblock (%s).',
peer.hostname());
@ -3394,8 +3552,6 @@ function PoolOptions(options) {
this.mempool = null;
this.nonces = new NonceList();
this.address = new NetAddress();
this.address.port = this.network.port;
this.checkpoints = true;
this.spv = false;
@ -3405,6 +3561,8 @@ function PoolOptions(options) {
this.noRelay = false;
this.host = '0.0.0.0';
this.port = this.network.port;
this.publicHost = '0.0.0.0';
this.publicPort = this.network.port;
this.maxOutbound = 8;
this.maxInbound = 8;
this.createSocket = this._createSocket.bind(this);
@ -3412,6 +3570,7 @@ function PoolOptions(options) {
this.resolve = this._resolve.bind(this);
this.proxy = null;
this.onion = false;
this.upnp = false;
this.selfish = false;
this.version = common.PROTOCOL_VERSION;
this.agent = common.USER_AGENT;
@ -3441,6 +3600,8 @@ function PoolOptions(options) {
*/
PoolOptions.prototype.fromOptions = function fromOptions(options) {
var raw;
assert(options, 'Pool requires options.');
assert(options.chain && typeof options.chain === 'object',
'Pool options require a blockchain.');
@ -3451,7 +3612,8 @@ PoolOptions.prototype.fromOptions = function fromOptions(options) {
this.port = this.network.port;
this.seeds = this.network.seeds;
this.address.port = this.network.port;
this.port = this.network.port;
this.publicPort = this.network.port;
if (options.logger != null) {
assert(typeof options.logger === 'object');
@ -3501,30 +3663,33 @@ PoolOptions.prototype.fromOptions = function fromOptions(options) {
if (options.host != null) {
assert(typeof options.host === 'string');
this.host = options.host;
this.address.setHost(this.host);
if (!this.address.isRoutable())
this.address.setNull();
raw = IP.toBuffer(options.host);
this.host = IP.toString(raw);
if (IP.isRoutable(raw))
this.publicHost = 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);
this.publicPort = options.port;
}
if (options.publicHost != null) {
assert(typeof options.publicHost === 'string');
this.address.setHost(options.publicHost);
this.publicHost = IP.normalize(options.publicHost);
}
if (options.publicPort != null) {
assert(typeof options.port === 'number');
this.address.setPort(options.publicPort);
assert(typeof options.publicPort === 'number');
assert(options.publicPort > 0 && options.publicPort <= 0xffff);
this.publicPort = options.publicPort;
}
if (options.maxOutbound != null) {
assert(typeof options.maxOutbound === 'number');
assert(options.maxOutbound > 0);
this.maxOutbound = options.maxOutbound;
}
@ -3558,6 +3723,11 @@ PoolOptions.prototype.fromOptions = function fromOptions(options) {
this.onion = options.onion;
}
if (options.upnp != null) {
assert(typeof options.upnp === 'boolean');
this.upnp = options.upnp;
}
if (options.selfish) {
assert(typeof options.selfish === 'boolean');
this.selfish = options.selfish;
@ -3669,9 +3839,6 @@ PoolOptions.prototype.fromOptions = function fromOptions(options) {
this.requiredServices = options.requiredServices;
}
this.address.services = this.services;
this.address.ts = this.network.now();
return this;
};
@ -3791,7 +3958,10 @@ PoolOptions.prototype._createSocket = function createSocket(port, host) {
*/
PoolOptions.prototype._resolve = function resolve(name) {
return dns.resolve(name, this.proxy, this.onion);
if (this.onion)
return dns.lookup(name, this.proxy);
return dns.lookup(name);
};
/**

View File

@ -96,19 +96,6 @@ ProxySocket.prototype._init = function _init() {
});
};
ProxySocket.prototype.resolve = function resolve(name, record, callback) {
var e;
this.socket.emit('dns resolve', name, record, function(err, results) {
if (err) {
e = new Error(err.message);
e.code = err.code || null;
callback(e);
return;
}
callback(null, results);
});
};
ProxySocket.prototype.connect = function connect(port, host) {
var nonce = 0;
var i, pow;

41
lib/net/upnp-browser.js Normal file
View File

@ -0,0 +1,41 @@
/*!
* upnp-browser.js - upnp for bcoin
* Copyright (c) 2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
/**
* UPNP
* @constructor
* @ignore
* @param {String?} host - Multicast IP.
* @param {Number?} port - Multicast port.
* @param {String?} gateway - Gateway name.
*/
function UPNP(host, port, gateway) {
throw new Error('UPNP not supported.');
}
/**
* Discover gateway and resolve service.
* @param {String?} host - Multicast IP.
* @param {Number?} port - Multicast port.
* @param {String?} gateway - Gateway type.
* @param {String[]?} targets - Target service types.
* @returns {Promise} Service.
*/
UPNP.discover = function discover(host, port, gateway, targets) {
return new Promise(function(resolve, reject) {
reject(new Error('UPNP not supported.'));
});
};
/*
* Expose
*/
module.exports = UPNP;

View File

@ -60,7 +60,7 @@ UPNP.WAN_SERVICES = [
* @default
*/
UPNP.RESPONSE_TIMEOUT = 3000;
UPNP.RESPONSE_TIMEOUT = 1000;
/**
* Clean up current job.
@ -430,14 +430,14 @@ UPNPService.prototype.addPortMapping = co(function* addPortMapping(remote, src,
var local = IP.getPrivate();
var xml, child;
if (!local)
if (local.length === 0)
throw new Error('Cannot determine local IP.');
xml = yield this.soapRequest(action, [
['NewRemoteHost', remote],
['NewExternalPort', src],
['NewProtocol', 'TCP'],
['NewInternalClient', local],
['NewInternalClient', local[0]],
['NewInternalPort', dest],
['NewEnabled', 'True'],
['NewPortMappingDescription', 'upnp:bcoin'],

View File

@ -267,6 +267,7 @@ config.toOptions = function toOptions(data) {
options.identityKey = key(data.identitykey);
options.proxy = str(data.proxy);
options.onion = bool(data.onion);
options.upnp = bool(data.upnp);
options.seeds = list(data.seeds);
options.nodes = list(data.nodes);
options.maxOutbound = num(data.maxoutbound);

View File

@ -102,6 +102,7 @@ function FullNode(options) {
maxInbound: this.options.maxInbound,
proxy: this.options.proxy,
onion: this.options.onion,
upnp: this.options.upnp,
seeds: this.options.seeds,
nodes: this.options.nodes,
publicHost: this.options.publicHost,

View File

@ -62,6 +62,7 @@ function SPVNode(options) {
chain: this.chain,
proxy: this.options.proxy,
onion: this.options.onion,
upnp: this.options.upnp,
seeds: this.options.seeds,
nodes: this.options.nodes,
bip151: this.options.bip151,

View File

@ -168,6 +168,29 @@ NetAddress.prototype.isOnion = function isOnion() {
return IP.isOnion(this.raw);
};
/**
* Compare against another network address.
* @returns {Boolean}
*/
NetAddress.prototype.equal = function equal(addr) {
return this.compare(addr) === 0;
};
/**
* Compare against another network address.
* @returns {Number}
*/
NetAddress.prototype.compare = function compare(addr) {
var cmp = util.cmp(this.raw, addr.raw);
if (cmp !== 0)
return cmp;
return this.port - addr.port;
};
/**
* Get reachable score to destination.
* @param {NetAddress} dest
@ -205,6 +228,7 @@ NetAddress.prototype.setHost = function setHost(host) {
*/
NetAddress.prototype.setPort = function setPort(port) {
assert(port >= 0 && port <= 0xffff);
this.port = port;
this.hostname = IP.toHostname(this.host, port);
};

View File

@ -43,17 +43,18 @@ main = network.main = {};
main.type = 'main';
/**
* Default seeds.
* Default DNS seeds.
* @const {String[]}
* @default
*/
main.seeds = [
'seed.bitcoin.sipa.be', // Pieter Wuille
'dnsseed.bluematt.me', // Matt Corallo
'dnsseed.bitcoin.dashjr.org', // Luke Dashjr
'seed.bitcoinstats.com', // Christian Decker
'bitseed.xf2.org', // Jeff Garzik
'seed.bitcoin.jonasschnelli.ch' // Jonas Schnelli
'seed.bitcoin.jonasschnelli.ch', // Jonas Schnelli
'seed.btc.petertodd.org' // Peter Todd
];
/**
@ -469,9 +470,10 @@ testnet = network.testnet = {};
testnet.type = 'testnet';
testnet.seeds = [
'testnet-seed.bitcoin.petertodd.org',
'testnet-seed.bluematt.me',
'testnet-seed.bitcoin.schildbach.de'
'testnet-seed.bitcoin.jonasschnelli.ch', // Jonas Schnelli
'seed.tbtc.petertodd.org', // Peter Todd
'testnet-seed.bluematt.me', // Matt Corallo
'testnet-seed.bitcoin.schildbach.de' // Andreas Schildbach
];
testnet.magic = 0x0709110b;

View File

@ -11,6 +11,7 @@
* @module utils/co
*/
var assert = require('assert');
var nextTick = require('./nexttick');
var every;
@ -230,6 +231,83 @@ every = co(function* every(jobs) {
return true;
});
/**
* Start an interval. Wait for promise
* to resolve on each iteration.
* @param {Function} func
* @param {Number?} time
* @param {Object?} self
* @returns {Object}
*/
function startInterval(func, time, self) {
var cb, ctx;
ctx = {
timer: null,
stopped: false
};
cb = co(function* () {
assert(ctx.timer != null);
ctx.timer = null;
try {
yield func.call(self);
} finally {
if (!ctx.stopped)
ctx.timer = setTimeout(cb, time);
}
});
ctx.timer = setTimeout(cb, time);
return ctx;
}
/**
* Clear an interval.
* @param {Object} ctx
*/
function stopInterval(ctx) {
assert(ctx);
if (ctx.timer != null) {
clearTimeout(ctx.timer);
ctx.timer = null;
}
ctx.stopped = true;
}
/**
* Start a timeout.
* @param {Function} func
* @param {Number?} time
* @param {Object?} self
* @returns {Object}
*/
function startTimeout(func, time, self) {
return {
timer: setTimeout(func.bind(self), time),
stopped: false
};
}
/**
* Clear a timeout.
* @param {Object} ctx
*/
function stopTimeout(ctx) {
assert(ctx);
if (ctx.timer != null) {
clearTimeout(ctx.timer);
ctx.timer = null;
}
ctx.stopped = true;
}
/**
* Create a job object.
* @returns {Job}
@ -282,6 +360,10 @@ exports.timeout = timeout;
exports.wrap = wrap;
exports.promisify = promisify;
exports.every = every;
exports.setInterval = startInterval;
exports.clearInterval = stopInterval;
exports.setTimeout = startTimeout;
exports.clearTimeout = stopTimeout;
exports.job = job;
module.exports = exports;

View File

@ -984,15 +984,16 @@ IP.isEqual = function isEqual(a, b) {
/**
* Get IP address from network interfaces.
* @param {String} name - `public` or `private`.
* @param {String} family - IP family name.
* @param {String?} name - `public` or `private`.
* @param {String?} family - IP family name.
* @returns {String}
*/
IP.getInterface = function getInterface(name, family) {
IP.getInterfaces = function _getInterfaces(name, family) {
var interfaces = getInterfaces();
var keys = Object.keys(interfaces);
var i, j, key, items, details, type, ip;
var result = [];
var i, j, key, items, details, type, raw;
for (i = 0; i < keys.length; i++) {
key = keys[i];
@ -1003,57 +1004,57 @@ IP.getInterface = function getInterface(name, family) {
type = details.family.toLowerCase();
if (type !== family)
if (family && type !== family)
continue;
if (details.internal)
continue;
try {
ip = IP.toBuffer(details.address);
raw = IP.toBuffer(details.address);
} catch (e) {
continue;
}
if (IP.isLocal(ip))
if (IP.isNull(raw))
continue;
if (IP.isLocal(raw))
continue;
if (name === 'public') {
if (!IP.isRoutable(ip))
if (!IP.isRoutable(raw))
continue;
} else if (name === 'private') {
if (IP.isRoutable(raw))
continue;
}
return IP.toString(ip);
result.push(IP.toString(raw));
}
}
return result;
};
/**
* Get private IP from network interfaces.
* @param {String?} family - IP family name.
* @returns {String}
*/
IP.getPrivate = function getPrivate() {
var ip = IP.getInterface('private', 'ipv4');
if (ip)
return ip;
return IP.getInterface('private', 'ipv6');
IP.getPrivate = function getPrivate(family) {
return IP.getInterfaces('private', family);
};
/**
* Get public IP from network interfaces.
* @param {String?} family - IP family name.
* @returns {String}
*/
IP.getPublic = function getPublic() {
var ip = IP.getInterface('public', 'ipv4');
if (ip)
return ip;
return IP.getInterface('public', 'ipv6');
IP.getPublic = function getPublic(family) {
return IP.getInterfaces('public', family);
};
/**

View File

@ -79,6 +79,7 @@
"./lib/mempool/layout": "./lib/mempool/layout-browser.js",
"./lib/net/dns": "./lib/net/dns-browser.js",
"./lib/net/tcp": "./lib/net/tcp-browser.js",
"./lib/net/upnp": "./lib/net/upnp-browser.js",
"./lib/utils/native": "./browser/empty.js",
"./lib/utils/nfkd": "./lib/utils/nfkd-browser.js",
"./lib/utils/nexttick": "./lib/utils/nexttick-browser.js",
@ -87,6 +88,7 @@
"bcoin-native": "./browser/empty.js",
"child_process": "./browser/empty.js",
"crypto": "./browser/empty.js",
"dgram": "./browser/empty.js",
"fs": "./browser/empty.js",
"net": "./browser/empty.js",
"os": "./browser/empty.js",