diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 2637b80a..092ec3ea 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -244,10 +244,32 @@ Peer.prototype._init = function init() { // Say hello. this.write(this.framer.version({ + version: constants.VERSION, + services: constants.LOCAL_SERVICES, + ts: bcoin.now(), + remote: {}, + local: { + services: constants.LOCAL_SERVICES, + host: this.pool.host, + port: this.pool.port + }, + nonce: this.pool.localNonce, + agent: constants.USER_AGENT, height: this.chain.height, relay: this.options.relay, - nonce: this.pool.localNonce })); + + // Advertise our address. + if (this.pool.host !== '0.0.0.0' + && !this.pool.options.selfish + && this.pool.server) { + this.write(this.framer.addr([{ + ts: utils.now() - (process.uptime() | 0), + services: constants.LOCAL_SERVICES, + host: this.pool.host, + port: this.pool.port + }])); + } }; /** @@ -1302,26 +1324,23 @@ Peer.prototype._handleGetData = function handleGetData(items) { Peer.prototype._handleAddr = function handleAddr(addrs) { var now = utils.now(); - var i, addr, ts, host; + var i, addr, ts; for (i = 0; i < addrs.length; i++) { addr = addrs[i]; ts = addr.ts; - host = addr.ipv4 !== '0.0.0.0' - ? addr.ipv4 - : addr.ipv6; if (ts <= 100000000 || ts > now + 10 * 60) ts = now - 5 * 24 * 60 * 60; - this.addrFilter.add(host, 'ascii'); + this.addrFilter.add(addr.host, 'ascii'); this.emit('addr', { version: addr.version, ts: ts, services: addr.services, - host: host, + host: addr.host, port: addr.port || this.network.port }); } @@ -1375,7 +1394,7 @@ Peer.prototype._handleGetAddr = function handleGetAddr() { var hosts = {}; var items = []; var ts = utils.now() - (process.uptime() | 0); - var i, seed, version, peer; + var i, seed, peer; if (this.pool.options.selfish) return; @@ -1383,9 +1402,8 @@ Peer.prototype._handleGetAddr = function handleGetAddr() { for (i = 0; i < this.pool.seeds.length; i++) { seed = utils.parseHost(this.pool.seeds[i]); seed = this.pool.getPeer(seed.host) || seed; - version = utils.isIP(seed.host); - if (!version) + if (!utils.isIP(seed.host)) continue; if (hosts[seed.host]) @@ -1397,11 +1415,9 @@ Peer.prototype._handleGetAddr = function handleGetAddr() { continue; items.push({ - network: this.network, ts: seed.ts || ts, - services: seed.version ? seed.version.services : null, - ipv4: version === 4 ? seed.host : null, - ipv6: version === 6 ? seed.host : null, + services: seed.version ? seed.version.services : 0, + host: seed.host, port: seed.port || this.network.port }); diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index d69111e1..4ea9e04e 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -108,6 +108,8 @@ function Pool(options) { this.seeds = []; this.hosts = {}; this.setSeeds([]); + this.host = '0.0.0.0'; + this.port = this.network.port; this.server = null; this.destroyed = false; @@ -194,20 +196,7 @@ function Pool(options) { interval: options.invInterval || 3000 }; - function done(err) { - if (err) - return self.emit('error', err); - - self.loaded = true; - self.emit('open'); - - self._init(); - } - - if (this.mempool) - this.mempool.open(done); - else - this.chain.open(done); + this._init(); } utils.inherits(Pool, EventEmitter); @@ -316,6 +305,29 @@ Pool.prototype._init = function _init() { self.emit('full'); bcoin.debug('Chain is fully synced (height=%d).', self.chain.height); }); + + function done(err) { + if (err) + return self.emit('error', err); + + self.loaded = true; + self.emit('open'); + } + + this.getIP(function(err, ip) { + if (err) + bcoin.error(err); + + if (ip) { + self.host = ip; + bcoin.debug('External IP found: %s.', ip); + } + + if (self.mempool) + self.mempool.open(done); + else + self.chain.open(done); + }); }; /** @@ -2088,6 +2100,62 @@ Pool.prototype.reject = function reject(peer, obj, code, reason, score) { peer.setMisbehavior(score); }; +/** + * Attempt to retrieve external IP from icanhazip.com. + * @param {Function} callback + */ + +Pool.prototype.getIP = function getIP(callback) { + var self = this; + var request = require('./http/request'); + var ip; + + if (utils.isBrowser) + return callback(new Error('Could not find IP.')); + + request({ + method: 'GET', + uri: 'http://icanhazip.com', + expect: 'text' + }, function(err, res, body) { + if (err) + return self.getIP2(callback); + + ip = body.trim(); + + if (!utils.isIP(ip)) + return self.getIP2(callback); + + return callback(null, ip); + }); +}; + +/** + * Attempt to retrieve external IP from dyndns.org. + * @param {Function} callback + */ + +Pool.prototype.getIP2 = function getIP2(callback) { + var request = require('./http/request'); + var ip; + + request({ + method: 'GET', + uri: 'http://checkip.dyndns.org', + expect: 'html' + }, function(err, res, body) { + if (err) + return callback(err); + + ip = /IP Address:\s*([0-9.:]+)/i.exec(body); + + if (!ip || !utils.isIP(ip[1])) + return callback(new Error('Could not find IP.')); + + return callback(null, ip[1]); + }); +}; + /** * Represents an in-flight block or transaction. * @exports LoadRequest diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index d6539b11..a238b7a4 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -17,8 +17,6 @@ var DUMMY = new Buffer([]); * @exports Framer * @constructor * @param {Object} options - * @param {String} options.USER_AGENT - User agent string. - * @property {Buffer} agent */ function Framer(options) { @@ -30,8 +28,6 @@ function Framer(options) { this.options = options; this.network = bcoin.network.get(options.network); - - this.agent = options.agent || constants.USER_AGENT; } /** @@ -94,12 +90,6 @@ Framer.prototype.packet = function packet(cmd, payload, checksum) { */ Framer.prototype.version = function version(options) { - if (!options) - options = {}; - - options.agent = this.agent; - options.network = this.network; - return this.packet('version', Framer.version(options)); }; @@ -409,7 +399,8 @@ Framer.prototype.addr = function addr(peers) { */ Framer.prototype.alert = function alert(options) { - return this.packet('alert', Framer.alert(options, this.network)); + options.network = this.network; + return this.packet('alert', Framer.alert(options)); }; /** @@ -432,7 +423,6 @@ Framer.prototype.feeFilter = function feeFilter(options) { Framer.address = function address(data, full, writer) { var p = new BufferWriter(writer); - var network = bcoin.network.get(data.network); if (full) { if (!data.ts) @@ -442,22 +432,8 @@ Framer.address = function address(data, full, writer) { } p.writeU64(data.services || 0); - - if (data.ipv6) { - p.writeBytes(utils.ip2array(data.ipv6, 6)); - } else { - // We don't have an ipv6 address: convert - // ipv4 to ipv4-mapped ipv6 address. - p.writeU32BE(0x00000000); - p.writeU32BE(0x00000000); - p.writeU32BE(0x0000ffff); - if (data.ipv4) - p.writeBytes(utils.ip2array(data.ipv4, 4)); - else - p.writeU32BE(0x00000000); - } - - p.writeU16BE(data.port || network.port); + p.writeBytes(utils.ip2array(data.host)); + p.writeU16BE(data.port || 0); if (!writer) p = p.render(); @@ -475,15 +451,13 @@ Framer.address = function address(data, full, writer) { Framer.version = function version(options, writer) { var p = new BufferWriter(writer); var agent = options.agent || constants.USER_AGENT; + var services = options.services; var remote = options.remote || {}; var local = options.local || {}; var nonce = options.nonce; - if (local.network == null) - local.network = options.network; - - if (remote.network == null) - remote.network = options.network; + if (services == null) + services = constants.LOCAL_SERVICES; if (local.services == null) local.services = constants.LOCAL_SERVICES; @@ -491,9 +465,9 @@ Framer.version = function version(options, writer) { if (!nonce) nonce = utils.nonce(); - p.write32(constants.VERSION); - p.writeU64(constants.LOCAL_SERVICES); - p.write64(utils.now()); + p.write32(options.version || constants.VERSION); + p.writeU64(services); + p.write64(options.ts || bcoin.now()); Framer.address(remote, false, p); Framer.address(local, false, p); p.writeU64(nonce); @@ -1189,28 +1163,19 @@ Framer.reject = function reject(details, writer) { /** * Create an addr packet (without a header). - * @param {InvItem[]} peers + * @param {NetworkAddress[]} hosts * @param {BufferWriter?} writer - A buffer writer to continue writing from. * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. */ -Framer.addr = function addr(peers, writer) { +Framer.addr = function addr(hosts, writer) { var p = new BufferWriter(writer); - var i, peer; + var i; - p.writeVarint(peers.length); + p.writeVarint(hosts.length); - for (i = 0; i < peers.length; i++) { - peer = peers[i]; - Framer.address({ - network: peer.network, - ts: peer.ts, - services: peer.services, - ipv6: peer.ipv6, - ipv4: peer.ipv4, - port: peer.port - }, true, p); - } + for (i = 0; i < hosts.length; i++) + Framer.address(hosts[i], true, p); if (!writer) p = p.render(); @@ -1221,16 +1186,14 @@ Framer.addr = function addr(peers, writer) { /** * Create an alert packet (without a header). * @param {AlertPacket} data - * @param {Network|NetworkType} network * @param {BufferWriter?} writer - A buffer writer to continue writing from. * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. */ -Framer.alert = function alert(data, network, writer) { - var p, i, payload; +Framer.alert = function alert(data, writer) { + var network = bcoin.network.get(data.network); var key = data.key; - - network = bcoin.network.get(network); + var p, i, payload; if (!key && network.alertPrivateKey) key = network.alertPrivateKey; @@ -1470,7 +1433,7 @@ Framer.filterClear = function filterClear() { * @returns {Buffer} Returns a BufferWriter if `writer` was passed in. */ -Framer.feeFilter = function feeFilter(data, network, writer) { +Framer.feeFilter = function feeFilter(data, writer) { var p = new BufferWriter(writer); p.write64(data.rate); diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index 5c72ab3f..306f6d31 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -1198,8 +1198,7 @@ Parser.parseAddress = function parseAddress(p, full) { return { ts: ts, services: services, - ipv6: utils.array2ip(ip, 6), - ipv4: utils.array2ip(ip, 4), + host: utils.array2ip(ip), port: port }; }; diff --git a/lib/bcoin/types.js b/lib/bcoin/types.js index 0edb9b47..bb76417f 100644 --- a/lib/bcoin/types.js +++ b/lib/bcoin/types.js @@ -258,8 +258,7 @@ * @typedef {Object} NetworkAddress * @property {Number?} ts - Timestamp. * @property {Number?} services - Service bits. - * @property {Buffer?} ipv4 - IPv4 address. - * @property {Buffer?} ipv6 - IPv6 address. + * @property {String?} host - IP address (IPv6 or IPv4). * @property {Number?} port - Port. * @global */ diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 6674e3bc..cb5f7fde 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -899,159 +899,104 @@ utils.isIP = function isIP(ip) { }; /** - * Cast an IPv6 or IPv4 to IPv4 or IPv6 respectively - * (if possible using ipv4-mapped ipv6 addresses). - * @param {Buffer} ip - * @param {Number} version - 4 or 6. - * @returns {Buffer} IP address. + * Test whether a buffer is an ipv4-mapped ipv6 address. + * @returns {Boolean} */ -utils.ip2version = function ip2version(ip, version) { - var b, i, j; +utils.isMapped = function isMapped(ip, version) { + var i; - assert(Buffer.isBuffer(ip)); - assert(version === 4 || version === 6, 'Bad IP version.'); + if (!Buffer.isBuffer(ip)) + return false; - if (version === 4) { - // Check to see if this an - // ipv4-mapped ipv6 address. - if (ip.length > 4) { - i = 0; - while (ip[i] === 0) - i++; - - // Found an ipv4 address - if (ip.length - i === 6 && ip[i] === 0xff && ip[i + 1] === 0xff) - return utils.copy(ip.slice(-4)); - - // No ipv4 address - return new Buffer([0, 0, 0, 0]); - } - - // Pad to 4 bytes - if (ip.length < 4) { - b = new Buffer(4); - i = ip.length; - j = b.length; - b.fill(0); - while (i) - b[--j] = ip[--i]; - ip = b; - } - - return ip; + for (i = 0; i < ip.length - 6; i++) { + if (ip[i] !== 0) + return false; } - if (version === 6) { - // Pad to 4 bytes - if (ip.length < 4) { - b = new Buffer(4); - i = ip.length; - j = b.length; - b.fill(0); - while (i) - b[--j] = ip[--i]; - ip = b; - } + if (ip[10] !== 0xff && ip[11] !== 0xff) + return false; - // Try to convert ipv4 address to - // ipv4-mapped ipv6 address. - if (ip.length === 4) { - b = new Buffer(6); - i = ip.length; - j = b.length; - b.fill(0xff); - while (i) - b[--j] = ip[--i]; - ip = b; - } - - // Pad to 16 bytes - if (ip.length < 16) { - b = new Buffer(16); - i = ip.length; - j = b.length; - b.fill(0); - while (i) - b[--j] = ip[--i]; - ip = b; - } - - return ip; - } + return true; }; /** * Convert an IP string to a buffer. * @param {String} ip - * @param {Number} version - 4 or 6. * @returns {Buffer} */ -utils.ip2array = function ip2array(ip, version) { - var type = utils.isIP(ip); - var parts; +utils.ip2array = function ip2array(ip) { + var version, parts, out; - assert(version === 4 || version === 6, 'Bad IP version.'); - - if (type === 0) { - if (!Buffer.isBuffer(ip)) - ip = new Buffer([0, 0, 0, 0]); - } else if (type === 4) { - parts = ip.split('.'); - ip = new Buffer(4); - ip[0] = +parts[0]; - ip[1] = +parts[1]; - ip[2] = +parts[2]; - ip[3] = +parts[3]; - } else if (type === 6) { - ip = new Buffer(ip.replace(/:/g, ''), 'hex'); - assert(ip.length <= 16); + if (Buffer.isBuffer(ip)) { + assert(ip.length === 16); + return ip; } - return utils.ip2version(ip, version); + version = utils.isIP(ip); + + if (version === 0) + return ip2array('0.0.0.0'); + + if (version === 4) { + parts = ip.split('.'); + assert(parts.length === 4); + out = new Buffer(16); + out.fill(0); + out[10] = 0xff; + out[11] = 0xff; + out[12] = +parts[0]; + out[13] = +parts[1]; + out[14] = +parts[2]; + out[15] = +parts[3]; + return out; + } + + if (version === 6) { + ip = new Buffer(ip.replace(/:/g, ''), 'hex'); + assert(ip.length === 16); + return ip; + } + + assert(false, 'Bad IP.'); }; /** * Convert a buffer to an ip string. * @param {Buffer} ip - * @param {Number} version - 4 or 6. * @returns {String} */ -utils.array2ip = function array2ip(ip, version) { - var out, i, hi, lo; +utils.array2ip = function array2ip(ip) { + var i, out, hi, lo; - if (!Buffer.isBuffer(ip)) { - if (utils.isIP(ip)) - ip = utils.ip2array(ip, version); - else - ip = new Buffer([0, 0, 0, 0]); + if (typeof ip === 'string') { + assert(utils.isIP(ip) !== 0); + return ip; } - assert(version === 4 || version === 6, 'Bad IP version.'); - assert(ip.length <= 16); + if (!Buffer.isBuffer(ip)) + return '0.0.0.0'; - ip = utils.ip2version(ip, version); + assert(ip.length === 16); - if (version === 4) - return ip[0] + '.' + ip[1] + '.' + ip[2] + '.' + ip[3]; + if (utils.isMapped(ip)) + return ip[12] + '.' + ip[13] + '.' + ip[14] + '.' + ip[15]; - if (version === 6) { - out = []; + out = []; - for (i = 0; i < ip.length; i += 2) { - hi = ip[i].toString(16); - if (hi.length < 2) - hi = '0' + hi; - lo = ip[i + 1].toString(16); - if (lo.length < 2) - lo = '0' + lo; - out.push(hi + lo); - } - - return out.join(':'); + for (i = 0; i < ip.length; i += 2) { + hi = ip[i].toString(16); + if (hi.length < 2) + hi = '0' + hi; + lo = ip[i + 1].toString(16); + if (lo.length < 2) + lo = '0' + lo; + out.push(hi + lo); } + + return out.join(':'); }; /**