diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index e1059b02..35576fbf 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -139,7 +139,7 @@ Peer.prototype._init = function init() { } this._ping.timer = setInterval(function() { - self.challenge = self._nonce(); + self.challenge = utils.nonce(); self._write(self.framer.ping({ nonce: self.challenge })); @@ -430,7 +430,7 @@ Peer.prototype._handleAddr = function handleAddr(addrs) { this.emit('addr', { date: new Date(addr.ts * 1000), ts: addr.ts, - service: addr.service, + services: addr.services, ipv4: addr.ipv4, ipv6: addr.ipv6, host: addr.ipv4, @@ -462,55 +462,44 @@ Peer.prototype._handlePong = function handlePong(data) { }; Peer.prototype._handleGetAddr = function handleGetAddr() { - var used = []; - var peers, addrs; - - // NOTE: For IPv6 BTC uses: - // '0000:0000:0000:0000:0000:xxxx:xxxx:ffff' + var hosts = {}; + var peers; peers = this.pool.peers.all.map(function(peer) { + var ip, version, ipv4, ipv6; + if (!peer.socket || !peer.socket.remoteAddress) return; - return { - host: peer.socket.remoteAddress, - port: peer.socket.remotePort || network.port, - ts: peer.ts - }; - }).filter(function(peer) { - if (!peer || ~used.indexOf(peer.host)) - return; - used.push(peer.host); - return !!peer.host && utils.isIP(peer.host); - }).map(function(peer) { - var ip = peer.host; - var ver = utils.isIP(ip); - return { - ipv4: ver === 4 ? ip : '127.0.0.1', - ipv6: ver === 6 ? ip : '0000:0000:0000:0000:0000:0000:0000:ffff', - port: peer.port, - ts: peer.ts, - ver: ver - }; - }); - addrs = peers.map(function(peer) { - if (peer.ver === 6) { - while (peer.ipv6.split(':').length < 8) - peer.ipv6 = '0000:' + peer.ipv6; - if (peer.ipv6.split(':').length > 8) - return; + ip = peer.socket.remoteAddress; + version = utils.isIP(ip); + + if (!version) + return; + + if (hosts[ip]) + return; + + hosts[ip] = true; + + if (version === 4) { + ipv4 = utils.ip2array(ip, 4); + ipv6 = utils.ip2array(ipv4, 6); + } else if (version === 6) { + ipv6 = utils.ip2array(ip, 6); + ipv4 = utils.ip2array(ipv6, 4); } - peer.ipv4 = peer.ipv4.split('.').map(function(n) { - return +n; - }); - - peer.ipv6 = utils.toArray(peer.ipv6, 'hex'); - - return peer; + return { + ts: peer.ts, + services: peer.version ? peer.version.services : null, + ipv4: ipv4, + ipv6: ipv6, + port: peer.socket.remotePort || network.port + }; }).filter(Boolean); - return this._write(this.framer.addr(addrs)); + return this._write(this.framer.addr(peers)); }; Peer.prototype._handleInv = function handleInv(items) { @@ -598,14 +587,6 @@ Peer.prototype.reject = function reject(details) { this._write(this.framer.reject(details)); }; -Peer.nonce = new bn(0xffffffff).ushln(32).uor(new bn(0xffffffff)); - -Peer.prototype._nonce = function _nonce() { - var nonce = Peer.nonce.clone(); - nonce.imuln(Math.random()); - return nonce; -}; - /** * Expose */ diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index 0bc24bb9..61952ea9 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -58,22 +58,58 @@ Framer.prototype.packet = function packet(cmd, payload) { return h.concat(payload); }; -Framer.prototype._addr = function addr(buf, off) { - writeU32(buf, 1, off); - writeU32(buf, 0, off + 4); - writeU32(buf, 0, off + 8); - writeU32(buf, 0, off + 12); - writeU32(buf, 0xffff0000, off + 16); - writeU32(buf, 0, off + 20); - buf[off + 24] = 0; - buf[off + 25] = 0; - return 26; +Framer.prototype._addr = function addr(p, off, data, full) { + var start = off; + + if (!data) + data = {}; + + if (!data.ts) + data.ts = utils.now() - (process.uptime() | 0); + + if (!data.services) + data.services = constants.services.network; + + if (!data.ipv4) + data.ipv4 = []; + + if (!data.ipv6) + data.ipv6 = []; + + if (!data.port) + data.port = network.port; + + // timestamp + if (full) + off += utils.writeU32(p, data.ts, off); + + // NODE_NETWORK services + off += utils.writeU64(p, data.services, off); + + // Empty bytes after services + // (services takes the place of ts) + if (!full) + off += utils.writeU32(p, 0, off); + + // ipv6 + off += utils.writeU32BE(p, utils.readU32BE(data.ipv6, 0), off); + off += utils.writeU32BE(p, utils.readU32BE(data.ipv6, 4), off); + off += utils.writeU32BE(p, utils.readU32BE(data.ipv6, 8), off); + + // ipv4 + if (full) + off += utils.writeU32BE(p, utils.readU32BE(data.ipv4, 0), off); + + // port + off += utils.writeU16BE(p, data.port, off); + + return off - start; }; Framer.prototype.version = function version(packet) { var p = new Array(86 + this.agent.length); var off = 0; - var ts, i; + var i; if (!packet) packet = {}; @@ -82,21 +118,19 @@ Framer.prototype.version = function version(packet) { off += writeU32(p, constants.version, off); // Services - off += writeU32(p, constants.services.network, off); - off += writeU32(p, 0, off); + off += utils.writeU64(p, constants.services.network, off); // Timestamp - ts = utils.now(); - off += writeU32(p, ts, off); - off += writeU32(p, 0, off); + off += utils.write64(p, utils.now(), off); - // Remote and local addresses - off += this._addr(p, off); - off += this._addr(p, off); + // Their address (recv) + off += this._addr(p, off, packet.remote || {}); + + // Our address (from) + off += this._addr(p, off, packet.local || {}); // Nonce, very dramatic - off += writeU32(p, (Math.random() * 0xffffffff) | 0, off); - off += writeU32(p, (Math.random() * 0xffffffff) | 0, off); + off += utils.writeU64(p, utils.nonce(), off); // User-agent assert.equal(off, 80); @@ -390,31 +424,20 @@ Framer.prototype.reject = function reject(details) { Framer.prototype.addr = function addr(peers) { var p = []; var off = 0; - var start = utils.now() - (process.uptime() | 0); var i, peer; - // count off += utils.writeIntv(p, peers.length, off); for (i = 0; i < peers.length; i++) { peer = peers[i]; - // timestamp - off += utils.writeU32(p, peer.ts || start, off); - - // NODE_NETWORK service - off += utils.writeU64(p, 1, off); - - // ipv6 - off += utils.writeU32BE(p, utils.readU32BE(peer.ipv6, 4), off); - off += utils.writeU32BE(p, utils.readU32BE(peer.ipv6, 8), off); - off += utils.writeU32BE(p, utils.readU32BE(peer.ipv6, 12), off); - - // ipv4 - off += utils.writeU32BE(p, utils.readU32BE(peer.ipv4, 0), off); - - // port - off += utils.writeU16BE(p, peer.port, off); + off += this._addr(p, off, { + ts: peer.ts, + services: 1, + ipv6: peer.ipv6, + ipv4: peer.ipv4, + port: peer.port + }, true); } return this.packet('addr', p); diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index ff9138e4..bf89fd05 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -167,7 +167,7 @@ Parser.prototype.parsePong = function parsePong(p) { }; Parser.prototype.parseVersion = function parseVersion(p) { - var v, services, ts, nonce, result, off, agent, height, relay; + var v, services, ts, recv, from, nonce, result, off, agent, height, relay; if (p.length < 85) return this._error('version packet is too small'); @@ -178,6 +178,12 @@ Parser.prototype.parseVersion = function parseVersion(p) { // Timestamp ts = utils.read64(p, 12); + // Our address (recv) + recv = this._parseAddr(p, 20); + + // Their Address (from) + from = this._parseAddr(p, 46); + // Nonce, very dramatic nonce = readU64(p, 72); @@ -194,10 +200,24 @@ Parser.prototype.parseVersion = function parseVersion(p) { // Relay relay = p.length > off ? p[off] === 1 : true; + try { + ts = ts.toNumber(); + } catch (e) { + ts = 0; + } + + try { + services = services.toNumber(); + } catch (e) { + services = 1; + } + return { v: v, services: services, ts: ts, + local: recv, + remote: from, nonce: nonce, agent: utils.stringify(agent), height: height, @@ -490,50 +510,72 @@ Parser.prototype.parseReject = function parseReject(p) { }; }; +Parser.prototype._parseAddr = function _parseAddr(p, off, full) { + var ts, services, ip, ipv6, ipv4, port; + + if (!off) + off = 0; + + // timestamp - LE + if (full) { + ts = utils.readU32(p, off); + off += 4; + } else { + ts = 0; + } + + // NODE_NETWORK services - LE + services = utils.readU64(p, off); + off += 8; + + // Empty bytes after services + // (services takes the place of ts) + if (!full) + off += 4; + + // ipv6 - BE + ipv6 = utils.toArray(p.slice(off, off + 12)); + off += 12; + + // ipv4 - BE + if (full) { + ipv4 = utils.toArray(p.slice(off, off + 4)); + off += 4; + } + + // port - BE + port = utils.readU16BE(p, off); + off += 2; + + try { + services = services.toNumber(); + } catch (e) { + services = 1; + } + + return { + ts: ts, + services: services, + ipv6: utils.array2ip(ipv6, 6), + ipv4: utils.array2ip(ipv4 || ipv6, 4), + port: port + }; +}; + Parser.prototype.parseAddr = function parseAddr(p) { if (p.length < 31) return this._error('Invalid addr size'); var addrs = []; - var i, len, off, count, ts, service, ipv6, ipv4, port; + var i, off, count; - // count - len = utils.readIntv(p, 0); - off = len.off; - count = len.r; + count = utils.readIntv(p, 0); + off = count.off; + count = count.r; - p = p.slice(off); - - for (i = 0; i < count && p.length; i++) { - // timestamp - LE - ts = utils.readU32(p, 0); - - // NODE_NETWORK service - LE - service = utils.readU64(p, 4); - - // ipv6 - BE - ipv6 = utils.toHex(p.slice(12, 24)); - ipv6 = '::' + ipv6.replace(/(.{4})/g, '$1:').slice(0, -1); - - // ipv4 - BE - ipv4 = utils.readU32BE(p, 24); - ipv4 = ((ipv4 >> 24) & 0xff) - + '.' + ((ipv4 >> 16) & 0xff) - + '.' + ((ipv4 >> 8) & 0xff) - + '.' + ((ipv4 >> 0) & 0xff); - - // port - BE - port = utils.readU16BE(p, 28); - - addrs.push({ - ts: ts, - service: service, - ipv6: ipv6, - ipv4: ipv4, - port: port - }); - - p = p.slice(30); + for (i = 0; i < count && off < p.length; i++) { + addrs.push(this._parseAddr(p, off, true)); + off += 30; } return addrs; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 70167746..ccc4e5d0 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -532,15 +532,97 @@ utils.isIP = function isIP(ip) { if (typeof ip !== 'string') return 0; - if (ip.indexOf('.') !== -1) + if (/^\d+\.\d+\.\d+\.\d+$/.test(ip)) return 4; - if (ip.indexOf(':') !== -1) + if (/:[0-9a-f]{1,4}/i.test(ip)) return 6; return 0; }; +utils.ip2array = function ip2array(ip, version) { + if (Array.isArray(ip)) { + ip = ip.slice(); + + utils.assert(version === 4 || version === 6); + + if (version === 4) { + ip = ip.slice(-4); + + while (ip.length < 4) + ip.unshift(0); + + return ip; + } + + if (version === 6) { + while (ip.length < 4) + ip.unshift(0); + + while (ip.length < 6) + ip.unshift(0xff); + + while (ip.length < 16) + ip.unshift(0); + + return ip; + } + + return; + } + + version = utils.isIP(ip); + + if (!version) + return ip; + + if (version === 4) { + ip = ip.split('.').map(function(n) { + return +n; + }); + utils.assert(ip.length <= 4); + return utils.ip2array(ip, 4); + } + + if (version === 6) { + ip = utils.toArray(ip.replace(/:/g, ''), 'hex'); + utils.assert(ip.length <= 16); + return utils.ip2array(ip, 6); + } +}; + +utils.array2ip = function array2ip(ip, version) { + var out, i, hi, lo; + + if (!Array.isArray(ip)) + return ip; + + utils.assert(version === 4 || version === 6); + utils.assert(ip.length <= 16); + + ip = utils.ip2array(ip, version); + + if (version === 4) + return ip.join('.'); + + if (version === 6) { + 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(':'); + } +}; + utils.isArrayLike = function isArrayLike(msg) { return msg && !Array.isArray(msg) @@ -737,6 +819,14 @@ utils.hash = function hash(obj, enc) { throw new Error('Cannot get hash of object'); }; +utils.U64 = new bn(0xffffffff).ushln(32).uor(new bn(0xffffffff)); + +utils.nonce = function nonce() { + var nonce = utils.U64.clone(); + nonce.imuln(Math.random()); + return nonce; +}; + // // Integer Functions // @@ -1011,7 +1101,7 @@ utils.write32BE = function write32BE(dst, num, off) { }; utils.write64 = function write64(dst, num, off) { - var i; + var i, bytes; if (!(num instanceof bn)) { num = +num; @@ -1026,22 +1116,22 @@ utils.write64 = function write64(dst, num, off) { off = off >>> 0; - num = num.maskn(64).toArray(); + bytes = num.maskn(64).toArray(); - while (num.length < 8) - num.unshift(0); + while (bytes.length < 8) + bytes.unshift(0); if (num.isNeg()) - num[0] |= 0x80; + bytes[0] |= 0x80; - for (i = num.length - 1; i >= 0; i--) - dst[off++] = num[i] & 0xff; + for (i = bytes.length - 1; i >= 0; i--) + dst[off++] = bytes[i] & 0xff; return 8; }; utils.write64BE = function write64BE(dst, num, off) { - var i; + var i, bytes; if (!(num instanceof bn)) { num = +num; @@ -1056,16 +1146,16 @@ utils.write64BE = function write64BE(dst, num, off) { off = off >>> 0; - num = num.maskn(64).toArray(); + bytes = num.maskn(64).toArray(); - while (num.length < 8) - num.unshift(0); + while (bytes.length < 8) + bytes.unshift(0); if (num.isNeg()) - num[0] |= 0x80; + bytes[0] |= 0x80; - for (i = 0; i < num.length; i++) - dst[off++] = num[i] & 0xff; + for (i = 0; i < bytes.length; i++) + dst[off++] = bytes[i] & 0xff; return 8; };