diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 55af3f0e..988f0087 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -1,5 +1,7 @@ var inherits = require('inherits'); var EventEmitter = require('events').EventEmitter; +var net = require('net'); +var os = require('os'); var bcoin = require('../bcoin'); var utils = bcoin.utils; @@ -276,6 +278,8 @@ Peer.prototype._onPacket = function onPacket(packet) { return this._handlePing(payload); else if (cmd === 'pong') return this._handlePong(payload); + else if (cmd === 'getaddr') + return this._handleGetAddr(); if (cmd === 'merkleblock' || cmd === 'block') { payload = bcoin.block(payload, cmd); @@ -334,6 +338,122 @@ Peer.prototype._handlePong = function handlePong() { // No-op for now }; +Peer.prototype._handleGetAddr = function handleGetAddr() { + var used = []; + var own = this._getOwnIP(); + var peers = [].concat( + this.pool.peers.pending, + this.pool.peers.block, + this.pool.peers.load + ).filter(Boolean); + + // NOTE: For IPv6 BTC uses: + // '0000:0000:0000:0000:0000:xxxx:xxxx:ffff' + + peers = peers.map(function(peer) { + if (!peer.socket || !peer.socket.remoteAddress) return; + return { + host: peer.socket.remoteAddress, + port: peer.socket.remotePort || 8333 + }; + }).filter(function(peer) { + if (~used.indexOf(peer.host)) return; + used.push(peer.host); + return !!peer.host && net.isIP(peer.host); + }).map(function(peer) { + var ip = peer.host; + var ver = net.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, + ver: ver + }; + }); + + if (own) peers.push(own); + + peers = 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; + } + } + + peer.ipv4 = peer.ipv4.split('.').map(function(n) { + return +n; + }); + + peer.ipv6 = peer.ipv6.split(':').slice(5).map(function(n) { + return parseInt(n, 16); + }); + + peer.ipv4 = utils.readU32BE(peer.ipv4, 0); + + peer.ipv6 = utils.readU32BE(peer.ipv6, 0) + * 0x10000 + utils.readU16BE(peer.ipv6, 4); + + return peer; + }).filter(Boolean); + + return this._write(this.framer.addr(peers)); +}; + +Peer.prototype._getOwnIP = function getOwnIP() { + var interfaces = os.networkInterfaces() + , eth + , ipv4 + , ipv6 + , ip + , p4 + , p6 + , l; + + ip = { + ipv4: '127.0.0.1', + ipv6: '0000:0000:0000:0000:0000:0000:0000:ffff', + port: 8333, + ver: 0 + }; + + eth = (interfaces.eth0 && interfaces.eth0.length >= 2 && interfaces.eth0) + || (interfaces.wlan0 && interfaces.wlan0.length >= 2 && interfaces.wlan0) + || interfaces[Object.keys(interfaces).pop()] + || [{ address: '127.0.0.1' }, { address: '::1' }]; + + ipv4 = eth[0] ? eth[0].address : '127.0.0.1'; + ipv6 = eth[1] ? eth[1].address : '0000:0000:0000:0000:0000:0000:0000:ffff'; + + p4 = ipv4.split('.').map(function(n) { return +n; }); + p6 = ipv6.split(':').map(function(n) { + return parseInt(n, 16); + }).reduce(function(out, n) { + out.push((n >> 8) & 0xff); + out.push(n & 0xff); + return out; + }, []); + l = p6.length; + + // Try to figure out if we're behind a NAT or not public for some reason. + if ((p4[0] !== 127 && p4[1] !== 0 && p4[2] !== 0) + && (p4[0] !== 10 && p4[1] !== 0) + && (p4[0] !== 168 && p4[1] !== 0)) { + ip.ipv4 = ipv4; + ip.ver = 4; + } else if ((p6[0] !== 0 && p6[1] !== 0 && p6[2] !== 1) + && (p6[l-1] !== 0xff && p6[l-2] !== 0xff && p6[l-3] !== 0xff)) { + ip.ipv6 = ipv6; + ip.ver = 6; + } else { + return false; + } + + return ip; +}; + Peer.prototype._handleInv = function handleInv(items) { // Always request advertised TXs var txs = items.filter(function(item) { diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index 5bfcd7af..7e0a5733 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -319,3 +319,48 @@ Framer.prototype.merkleBlock = function merkleBlock(block) { // merkleblock here if we have them, as per the offical bitcoin client. return this.packet(Framer.block(block, 'merkleblock')); }; + +Framer.prototype.addr = function addr(peers) { + var p = []; + var i = 0; + var c = 0; + var peer; + + // count + if (peers.length < 0xfd) { + p[c++] = peers.length; + } else if (peers.length <= 0xffff) { + p[c++] = 0xfd; + c += utils.writeU16(p, peers.length, c); + } else if (peers.length <= 0xffffffff) { + p[c++] = 0xfe; + c += utils.writeU32(p, peers.length, c); + } else if (peers.length <= 0xffffffffffffffff) { + p[c++] = 0xff; + c += utils.writeU64(p, peers.length, c); + } else { + return; + } + + for (; i < peers.length; i++) { + peer = peers[i]; + + // date + c += utils.writeU32(p, Date.now() / 1000 | 0, c); + + // NODE_NETWORK service + c += utils.writeU64(p, 1, c); + + // ipv6 + c += utils.writeU32BE(p, utils.readU32BE(peer.ipv6, 0), c); + c += utils.writeU16BE(p, utils.readU16BE(peer.ipv6, 4), c); + + // ipv4 + c += utils.writeU32BE(p, peer.ipv4, c); + + // port + c += utils.writeU16BE(peer.port, c); + } + + return this.packet('addr', p); +}; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index f09e9596..9554d79d 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -158,6 +158,14 @@ utils.readU64 = function readU64(arr, off) { return utils.readU32(arr, off) + utils.readU32(arr, off + 4) * 0x100000000; }; +utils.writeU16 = function writeU16(dst, num, off) { + if (!off) + off = 0; + dst[off] = num & 0xff; + dst[off + 1] = (num >>> 8) & 0xff; + return 2; +}; + utils.writeU32 = function writeU32(dst, num, off) { if (!off) off = 0; @@ -168,6 +176,70 @@ utils.writeU32 = function writeU32(dst, num, off) { return 4; }; +utils.writeU64 = function writeU64(dst, num, off) { + if (!off) + off = 0; + + var n = new bn(num); + var left = n.shrn(32); + //var right = n.andln(0xffffffff); + var right = ((n.words[1] & 0xff) << 24) | n.words[0]; + if (right < 0) right += 0x100000000; + + dst[off] = right & 0xff; + dst[off + 1] = (right >>> 8) & 0xff; + dst[off + 2] = (right >>> 16) & 0xff; + dst[off + 3] = (right >>> 24) & 0xff; + + dst[off + 4] = left & 0xff; + dst[off + 5] = (left >>> 8) & 0xff; + dst[off + 6] = (left >>> 16) & 0xff; + dst[off + 7] = (left >>> 24) & 0xff; + + return 8; +}; + +utils.writeU16BE = function writeU16BE(dst, num, off) { + if (!off) + off = 0; + dst[off] = (num >>> 8) & 0xff; + dst[off + 1] = num & 0xff; + return 2; +}; + +utils.writeU32BE = function writeU32BE(dst, num, off) { + if (!off) + off = 0; + dst[off] = (num >>> 24) & 0xff; + dst[off + 1] = (num >>> 16) & 0xff; + dst[off + 2] = (num >>> 8) & 0xff; + dst[off + 3] = num & 0xff; + return 4; +}; + +utils.writeU64BE = function writeU64BE(dst, num, off) { + if (!off) + off = 0; + + var n = new bn(num); + var left = n.shrn(32); + //var right = n.andln(0xffffffff); + var right = ((n.words[1] & 0xff) << 24) | n.words[0]; + if (right < 0) right += 0x100000000; + + dst[off] = (left >>> 24) & 0xff; + dst[off + 1] = (left >>> 16) & 0xff; + dst[off + 2] = (left >>> 8) & 0xff; + dst[off + 3] = left & 0xff; + + dst[off + 4] = (right >>> 24) & 0xff; + dst[off + 5] = (right >>> 16) & 0xff; + dst[off + 6] = (right >>> 8) & 0xff; + dst[off + 7] = right & 0xff; + + return 8; +}; + utils.readU16BE = function readU16BE(arr, off) { if (!off) off = 0;