diff --git a/browser/wsproxy.js b/browser/wsproxy.js index 3c0e62bc..14d58978 100644 --- a/browser/wsproxy.js +++ b/browser/wsproxy.js @@ -150,7 +150,7 @@ WSProxy.prototype._handleConnect = function _handleConnect(ws, port, host, nonce } socket.on('connect', function() { - ws.emit('tcp connect'); + ws.emit('tcp connect', socket.remoteAddress, socket.remotePort); }); socket.on('data', function(data) { diff --git a/lib/net/index.js b/lib/net/index.js index f6ac874d..2bba2566 100644 --- a/lib/net/index.js +++ b/lib/net/index.js @@ -9,4 +9,5 @@ exports.Parser = require('./parser'); exports.Peer = require('./peer'); exports.Pool = require('./pool'); exports.tcp = require('./tcp'); +exports.dns = require('./dns'); exports.time = require('./time'); diff --git a/lib/net/peer.js b/lib/net/peer.js index d0a8e790..147b7b63 100644 --- a/lib/net/peer.js +++ b/lib/net/peer.js @@ -29,6 +29,7 @@ var errors = require('../btc/errors'); var List = require('../utils/list'); var packetTypes = packets.types; var VerifyResult = errors.VerifyResult; +var IP = require('../utils/ip'); /** * Represents a remote peer. @@ -72,9 +73,9 @@ var VerifyResult = errors.VerifyResult; * @emits Peer#ack */ -function Peer(pool, addr, socket) { +function Peer(pool) { if (!(this instanceof Peer)) - return new Peer(pool, addr, socket); + return new Peer(pool); EventEmitter.call(this); @@ -142,16 +143,13 @@ function Peer(pool, addr, socket) { this.queueBlock = new List(); this.queueTX = new List(); - this.uid = 0; this.id = Peer.uid++; this.setMaxListeners(10000); - assert(addr, 'Host required.'); - - this.host = addr.host; - this.port = addr.port; - this.hostname = addr.hostname; + this.host = '0.0.0.0'; + this.port = 0; + this.hostname = '0.0.0.0:0'; if (this.options.bip151) { this.bip151 = new BIP151(); @@ -169,16 +167,6 @@ function Peer(pool, addr, socket) { this.parser = new Parser(this); this.framer = new Framer(this); - if (!socket) { - this.socket = this.connect(this.port, this.host); - this.outbound = true; - } else { - this.socket = socket; - this.ts = util.now(); - this.connected = true; - this.pending = false; - } - this._init(); } @@ -200,7 +188,59 @@ Peer.uid = 0; Peer.prototype._init = function init() { var self = this; + this.parser.on('packet', co(function* (packet) { + try { + yield self._onPacket(packet); + } catch (e) { + self.destroy(); + self.error(e); + } + })); + + this.parser.on('error', function(err) { + self.error(err); + self.reject(null, 'malformed', 'error parsing message', 10); + }); + + if (this.bip151) { + this.bip151.on('error', function(err) { + self.reject(null, 'malformed', 'error parsing message', 10); + self.error(err); + }); + this.bip151.on('rekey', function() { + self.logger.debug('Rekeying with peer (%s).', self.hostname); + self.send(self.bip151.toRekey()); + }); + } +}; + +/** + * Set peer host, port, and hostname. + * @param {String} host + * @param {Number} port + */ + +Peer.prototype.setHost = function setHost(host, port) { + assert(typeof host === 'string'); + assert(typeof port === 'number'); + this.host = host; + this.port = port; + this.hostname = IP.hostname(host, port); +}; + +/** + * Bind to socket. + * @param {net.Socket} socket + */ + +Peer.prototype.bind = function bind(socket) { + var self = this; + + assert(!this.socket); + this.socket = socket; + this.socket.once('error', function(err) { + self.destroy(); self.error(err); switch (err.code) { @@ -219,6 +259,7 @@ Peer.prototype._init = function init() { }); this.socket.once('close', function() { + self.destroy(); self.error('socket hangup'); }); @@ -232,39 +273,32 @@ Peer.prototype._init = function init() { self.parser.feed(chunk); }); +}; - this.parser.on('packet', co(function* (packet) { - try { - yield self._onPacket(packet); - } catch (e) { - self.error(e); - } - })); +/** + * Accept an inbound socket. + * @param {net.Socket} socket + */ - this.parser.on('error', function(err) { - self.error(err, true); - self.reject(null, 'malformed', 'error parsing message', 10); - }); +Peer.prototype.accept = function accept(socket) { + var host = IP.normalize(socket.remoteAddress); + var port = socket.remotePort; - if (this.bip151) { - this.bip151.on('error', function(err) { - self.reject(null, 'malformed', 'error parsing message', 10); - self.error(err, true); - }); - this.bip151.on('rekey', function() { - self.logger.debug('Rekeying with peer (%s).', self.hostname); - self.send(self.bip151.toRekey()); - }); - } + assert(!this.socket); - this.open(); + this.bind(socket); + this.setHost(host, port); + this.ts = util.now(); + this.outbound = false; + this.connected = true; + this.pending = false; }; /** * Create the socket and begin connecting. This method * will use `options.createSocket` if provided. - * @param {String} host * @param {Number} port + * @param {String} host * @returns {net.Socket} */ @@ -280,45 +314,61 @@ Peer.prototype.connect = function connect(port, host) { else socket = tcp.connect(port, host, proxy); + this.bind(socket); + this.setHost(host, port); + this.outbound = true; + this.logger.debug('Connecting to %s.', this.hostname); socket.once('connect', function() { self.logger.info('Connected to %s.', self.hostname); }); - - return socket; }; /** - * Open and initialize the peer. + * Open and perform initial handshake. + * @returns {Promise} */ Peer.prototype.open = co(function* open() { - try { - yield this._connect(); - yield this._stallify(); - yield this._bip151(); - yield this._bip150(); - yield this._handshake(); - yield this._finalize(); - } catch (e) { - this.error(e); - return; - } + yield this._wait(); + yield this._stallify(); + yield this._bip151(); + yield this._bip150(); + yield this._handshake(); + yield this._finalize(); - assert(!this.destroyed); + if (this.destroyed) + throw new Error('Peer was destroyed.'); + + clearTimeout(this.connectTimeout); + this.connectTimeout = null; // Finally we can let the pool know // that this peer is ready to go. this.emit('open'); }); +/** + * Open and perform initial handshake (without rejection). + * @returns {Promise} + */ + +Peer.prototype.tryOpen = co(function* tryOpen() { + try { + yield this.open(); + } catch (e) { + this.destroy(); + this.error(e); + } +}); + /** * Wait for connection. * @private */ -Peer.prototype._connect = function _connect() { +Peer.prototype._wait = function _wait() { var self = this; if (this.connected) { @@ -332,9 +382,6 @@ Peer.prototype._connect = function _connect() { self.connected = true; self.emit('connect'); - clearTimeout(self.connectTimeout); - self.connectTimeout = null; - resolve(); }); @@ -380,7 +427,7 @@ Peer.prototype._bip151 = co(function* _bip151() { try { yield this.bip151.wait(3000); } catch (err) { - this.error(err, true); + this.error(err); } assert(this.bip151.completed); @@ -463,7 +510,7 @@ Peer.prototype._handshake = co(function* _handshake() { * @private */ -Peer.prototype._finalize = function _finalize() { +Peer.prototype._finalize = co(function* _finalize() { var self = this; // Setup the ping interval. @@ -506,9 +553,7 @@ Peer.prototype._finalize = function _finalize() { // Start syncing the chain. this.sync(); - - return Promise.resolve(); -}; +}); /** * Test whether the peer is the loader peer. @@ -857,6 +902,7 @@ Peer.prototype.needsDrain = function needsDrain(size) { var self = this; if (this.maybeStall()) { + this.destroy(); this.error('Peer stalled (drain).'); return; } @@ -869,6 +915,7 @@ Peer.prototype.needsDrain = function needsDrain(size) { 'Peer is not reading: %s buffered (%s).', util.mb(this.drainSize), this.hostname); + this.destroy(); this.error('Peer stalled (drain).'); return; } @@ -887,6 +934,7 @@ Peer.prototype.maybeStall = function maybeStall() { return false; this.drainSize = 0; + this.destroy(); this.error('Peer stalled.'); return true; @@ -933,7 +981,7 @@ Peer.prototype.sendRaw = function sendRaw(cmd, body, checksum) { * @param {...String|Error} err */ -Peer.prototype.error = function error(err, keep) { +Peer.prototype.error = function error(err) { var i, args, msg; if (this.destroyed) @@ -954,9 +1002,6 @@ Peer.prototype.error = function error(err, keep) { err.message += ' (' + this.hostname + ')'; - if (!keep) - this.destroy(); - this.emit('error', err); }; @@ -1021,18 +1066,16 @@ Peer.prototype.response = function response(cmd, payload) { /** * Send `getdata` to peer. - * @param {InvItem[]} items + * @param {LoadRequest[]} items */ Peer.prototype.getData = function getData(items) { - var data = new Array(items.length); + var inv = []; var i, item; for (i = 0; i < items.length; i++) { item = items[i]; - - if (item.toInv) - item = item.toInv(); + item = item.toInv(); if (this.options.compact && this.compactMode @@ -1041,10 +1084,10 @@ Peer.prototype.getData = function getData(items) { item.type = constants.inv.CMPCT_BLOCK; } - data[i] = item; + inv.push(item); } - this.send(new packets.GetDataPacket(data)); + this.send(new packets.GetDataPacket(inv)); }; /** @@ -1878,13 +1921,13 @@ Peer.prototype._handleAddr = co(function* _handleAddr(packet) { if (addr.ts <= 100000000 || addr.ts > now + 10 * 60) addr.ts = now - 5 * 24 * 60 * 60; - this.addrFilter.add(addr.host, 'ascii'); + this.addrFilter.add(addr.hostname, 'ascii'); } this.logger.info( 'Received %d addrs (hosts=%d, peers=%d) (%s).', addrs.length, - this.pool.hosts.items.length, + this.pool.hosts.size(), this.pool.peers.size(), this.hostname); @@ -1962,13 +2005,8 @@ Peer.prototype._handleGetAddr = co(function* _handleGetAddr(packet) { this.sentAddr = true; - for (i = 0; i < this.pool.hosts.items.length; i++) { - addr = this.pool.hosts.items[i]; - - if (!addr.isIP()) - continue; - - if (!this.addrFilter.added(addr.host, 'ascii')) + for (addr = this.pool.hosts.head(); addr; addr = addr.next) { + if (!this.addrFilter.added(addr.hostname, 'ascii')) continue; items.push(addr); @@ -2721,11 +2759,13 @@ function RequestEntry(peer, cmd, resolve, reject) { this.cmd = cmd; this.resolve = resolve; this.reject = reject; - this.id = peer.uid++; + this.id = RequestEntry.uid++; this.onTimeout = this._onTimeout.bind(this); this.timeout = setTimeout(this.onTimeout, this.peer.requestTimeout); } +RequestEntry.uid = 0; + RequestEntry.prototype._onTimeout = function _onTimeout() { var queue = this.peer.requestMap[this.cmd]; diff --git a/lib/net/pool.js b/lib/net/pool.js index 37ded217..0117cd95 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -30,6 +30,7 @@ var request = require('../http/request'); var VerifyError = errors.VerifyError; var VerifyResult = errors.VerifyResult; var List = require('../utils/list'); +var dns = require('./dns'); /** * A pool of peers for handling all network activity. @@ -288,6 +289,26 @@ Pool.prototype._init = function _init() { self.emit('full'); self.logger.info('Chain is fully synced (height=%d).', self.chain.height); }); + + if (!this.options.selfish && !this.options.spv) { + if (this.mempool) { + this.mempool.on('tx', function(tx) { + self.announceTX(tx); + }); + } + + // Normally we would also broadcast + // competing chains, but we want to + // avoid getting banned if an evil + // miner sends us an invalid competing + // chain that we can't connect and + // verify yet. + this.chain.on('block', function(block) { + if (!self.chain.synced) + return; + self.announceBlock(block); + }); + } }; /** @@ -297,18 +318,7 @@ Pool.prototype._init = function _init() { */ Pool.prototype._open = co(function* _open() { - var ip, key; - - 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); - } + var key; if (this.mempool) yield this.mempool.open(); @@ -362,42 +372,53 @@ Pool.prototype._close = co(function* close() { /** * Connect to the network. + * @returns {Promise} */ -Pool.prototype.connect = function connect() { - var self = this; +Pool.prototype.connect = co(function* connect() { + var unlock = yield this.locker.lock(); + try { + return yield this._connect(); + } finally { + unlock(); + } +}); + +/** + * Connect to the network (no lock). + * @returns {Promise} + */ + +Pool.prototype._connect = co(function* connect() { + var ip; assert(this.loaded, 'Pool is not loaded.'); if (this.connected) return; - if (!this.options.selfish && !this.options.spv) { - if (this.mempool) { - this.mempool.on('tx', function(tx) { - self.announceTX(tx); - }); - } - - // Normally we would also broadcast - // competing chains, but we want to - // avoid getting banned if an evil - // miner sends us an invalid competing - // chain that we can't connect and - // verify yet. - this.chain.on('block', function(block) { - if (!self.chain.synced) - return; - self.announceBlock(block); - }); + try { + ip = yield this.getIP(); + } catch (e) { + this.logger.error(e); } - assert(this.hosts.seeds.length !== 0, 'No seeds available.'); + if (ip) { + this.address.setHost(ip); + this.logger.info('External IP found: %s.', ip); + } + + 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()); this.addLoader(); this.connected = true; -}; +}); /** * Start listening on a server socket. @@ -483,12 +504,6 @@ Pool.prototype._handleLeech = function _handleLeech(socket) { return; } - if (this.hosts.isIgnored(addr)) { - this.logger.debug('Ignoring leech (%s).', addr.hostname); - socket.destroy(); - return; - } - // Some kind of weird port collision // between inbound ports and outbound ports. if (this.peers.get(addr)) { @@ -497,7 +512,7 @@ Pool.prototype._handleLeech = function _handleLeech(socket) { return; } - this.addLeech(addr, socket); + this.addInbound(socket); }; /** @@ -604,7 +619,11 @@ Pool.prototype.addLoader = function addLoader() { return; } - addr = this.getLoaderHost(); + addr = this.hosts.getHost(); + + if (this.peers.get(addr)) + return; + peer = this.peers.get(addr); if (peer) { @@ -617,7 +636,7 @@ Pool.prototype.addLoader = function addLoader() { this.startInterval(); } - peer = this.createPeer(addr); + peer = this.createPeer(addr.port, addr.host); this.logger.info('Added loader peer (%s).', peer.hostname); @@ -663,13 +682,13 @@ Pool.prototype.setLoader = function setLoader(peer) { * Start the blockchain sync. */ -Pool.prototype.startSync = function startSync() { +Pool.prototype.startSync = co(function* startSync() { this.syncing = true; this.startInterval(); this.startTimeout(); - this.connect(); + yield this.connect(); if (!this.peers.load) { this.addLoader(); @@ -677,7 +696,7 @@ Pool.prototype.startSync = function startSync() { } this.sync(); -}; +}); /** * Send a sync to each peer. @@ -714,7 +733,7 @@ Pool.prototype.forceSync = function forceSync() { * Stop the blockchain sync. */ -Pool.prototype.stopSync = function stopSync() { +Pool.prototype.stopSync = co(function* stopSync() { var peer; if (!this.syncing) @@ -733,7 +752,7 @@ Pool.prototype.stopSync = function stopSync() { continue; peer.syncSent = false; } -}; +}); /** * Handle `headers` packet from a given peer. @@ -940,10 +959,7 @@ Pool.prototype.__handleInv = co(function* _handleInv(hashes, peer) { */ Pool.prototype._handleBlock = co(function* _handleBlock(block, peer) { - var requested; - - // Fulfill the load request. - requested = this.fulfill(block); + var requested = this.fulfill(block); // Someone is sending us blocks without // us requesting them. @@ -1034,20 +1050,59 @@ Pool.prototype.sendMempool = function sendMempool() { Pool.prototype.sendAlert = function sendAlert(alert) { var peer; - for (peer = this.peers.head(); peer; peer = peer.next) + for (peer = this.peers.head(); peer; peer = peer.next) { + if (peer.pending) + continue; peer.sendAlert(alert); + } }; /** * Create a base peer with no special purpose. * @private - * @param {Object} options + * @param {Number} port + * @param {String} host * @returns {Peer} */ -Pool.prototype.createPeer = function createPeer(addr, socket) { +Pool.prototype.createPeer = function createPeer(port, host) { + var self = this; + var peer = new Peer(this); + + this.bindPeer(peer); + + peer.connect(port, host); + peer.tryOpen(); + + return peer; +}; + +/** + * Accept an inbound socket. + * @private + * @param {net.Socket} socket + * @returns {Peer} + */ + +Pool.prototype.acceptPeer = function acceptPeer(socket) { + var self = this; + var peer = new Peer(this); + + this.bindPeer(peer); + + peer.accept(socket); + peer.tryOpen(); + + return peer; +}; + +/** + * Bind to peer events. + * @private + */ + +Pool.prototype.bindPeer = function bindPeer(peer) { var self = this; - var peer = new Peer(this, addr, socket); peer.once('open', function() { if (!peer.outbound) @@ -1263,8 +1318,6 @@ Pool.prototype.createPeer = function createPeer(addr, socket) { self.emit('error', e); } })); - - return peer; }; /** @@ -1349,10 +1402,8 @@ Pool.prototype.hasReject = function hasReject(hash) { */ Pool.prototype._handleTX = co(function* _handleTX(tx, peer) { - var i, requested, missing; - - // Fulfill the load request. - requested = this.fulfill(tx); + var requested = this.fulfill(tx); + var i, missing; if (!requested) { peer.invFilter.add(tx.hash()); @@ -1379,14 +1430,16 @@ Pool.prototype._handleTX = co(function* _handleTX(tx, peer) { try { missing = yield this.mempool.addTX(tx); } catch (err) { - if (err.type === 'VerifyError') { - if (err.score !== -1) - peer.reject(tx, err.code, err.reason, err.score); - } + if (err.type === 'VerifyError') + peer.reject(tx, err.code, err.reason, err.score); throw err; } - if (this.options.requestMissing && missing) { + if (missing) { + this.logger.debug( + 'Requesting %d missing transactions (%s).', + missing.length, peer.hostname); + try { for (i = 0; i < missing.length; i++) this.getTX(peer, missing[i]); @@ -1399,19 +1452,19 @@ Pool.prototype._handleTX = co(function* _handleTX(tx, peer) { }); /** - * Create a leech peer from an existing socket. + * Create an inbound peer from an existing socket. * @private * @param {net.Socket} socket */ -Pool.prototype.addLeech = function addLeech(addr, socket) { +Pool.prototype.addInbound = function addInbound(socket) { var self = this; var peer; if (!this.loaded) return socket.destroy(); - peer = this.createPeer(addr, socket); + peer = this.acceptPeer(socket); this.logger.info('Added leech peer (%s).', peer.hostname); @@ -1423,12 +1476,12 @@ Pool.prototype.addLeech = function addLeech(addr, socket) { }; /** - * Create a outbound non-loader peer. These primarily + * Create an outbound non-loader peer. These primarily * exist for transaction relaying. * @private */ -Pool.prototype.addPeer = function addPeer() { +Pool.prototype.addOutbound = function addOutbound() { var self = this; var peer, addr; @@ -1444,10 +1497,10 @@ Pool.prototype.addPeer = function addPeer() { addr = this.hosts.getHost(); - if (!addr) + if (this.peers.get(addr)) return; - peer = this.createPeer(addr); + peer = this.createPeer(addr.port, addr.host); this.peers.add(peer); @@ -1469,7 +1522,7 @@ Pool.prototype.fillPeers = function fillPeers() { this.maxOutbound); for (i = 0; i < this.maxOutbound - 1; i++) - this.addPeer(); + this.addOutbound(); }; /** @@ -1829,18 +1882,6 @@ Pool.prototype.setFeeRate = function setFeeRate(rate) { } }; -/** - * Allocate a new loader host. - * @returns {NetworkAddress} - */ - -Pool.prototype.getLoaderHost = function getLoaderHost() { - if (!this.connected && this.options.preferredSeed) - return this.hosts.seeds[0]; - - return this.hosts.getLoaderHost(); -}; - /** * Increase peer's ban score. * @param {Peer} peer @@ -1909,16 +1950,6 @@ Pool.prototype.ignore = function ignore(addr) { peer.destroy(); }; -/** - * Test whether the host is ignored. - * @param {NetworkAddress} addr - * @returns {Boolean} - */ - -Pool.prototype.isIgnored = function isIgnored(addr) { - return this.hosts.isIgnored(addr); -}; - /** * Attempt to retrieve external IP from icanhazip.com. * @returns {Promise} @@ -1981,7 +2012,7 @@ Pool.prototype.getIP2 = co(function* getIP2() { */ function PeerList(pool) { - this.pool = pool; + this.logger = pool.logger; this.map = {}; this.list = new List(); this.load = null; @@ -2028,7 +2059,7 @@ PeerList.prototype.remove = function remove(peer) { delete this.map[peer.hostname]; if (peer.isLoader()) { - this.pool.logger.info('Removed loader peer (%s).', peer.hostname); + this.logger.info('Removed loader peer (%s).', peer.hostname); this.load = null; } @@ -2071,26 +2102,57 @@ PeerList.prototype.destroy = function destroy() { */ function HostList(pool) { - this.pool = pool; + this.network = pool.network; + this.logger = pool.logger; + this.maxOutbound = pool.maxOutbound; + this.list = new List(); this.seeds = []; - this.items = []; this.map = {}; - - // Ignored hosts - this.ignored = {}; - - // Misbehaving hosts this.misbehaving = {}; - - this.setSeeds(this.pool.network.seeds); + this.setSeeds(this.network.seeds); } +/** + * Get head element. + * @returns {NetworkAddress} + */ + +HostList.prototype.head = function head() { + return this.list.head; +}; + +/** + * Get tail element. + * @returns {NetworkAddress} + */ + +HostList.prototype.tail = function tail() { + return this.list.tail; +}; + +/** + * Get list size. + * @returns {Number} + */ + +HostList.prototype.size = function size() { + return this.list.size; +}; + +/** + * Reset host list. + */ + +HostList.prototype.reset = function reset() { + this.map = {}; + this.list.reset(); +}; + /** * Clear misbehaving and ignored. */ HostList.prototype.clear = function clear() { - this.ignored = {}; this.misbehaving = {}; }; @@ -2099,71 +2161,11 @@ HostList.prototype.clear = function clear() { * @returns {NetworkAddress} */ -HostList.prototype.getLoaderHost = function getLoaderHost() { - var addr = this.getRandom(this.seeds); - - if (addr) - return addr; - - addr = this.getRandom(this.items); - - if (addr) - return addr; - - this.pool.logger.warning('All seeds banned or ignored. Clearing...'); - this.clear(); - - return this.getRandom(this.seeds); -}; - -/** - * Allocate a new host which is not currently being used. - * @returns {NetworkAddress} - */ - HostList.prototype.getHost = function getHost() { - var addr = this.getRandom(this.seeds, true); - - if (addr) - return addr; - - return this.getRandom(this.items, true); -}; - -/** - * Get a random host from collection of hosts. - * @param {NetworkAddress[]} hosts - * @param {Boolean} unique - * @returns {NetworkAddress} - */ - -HostList.prototype.getRandom = function getRandom(hosts, unique) { - var index = Math.random() * hosts.length | 0; - var last = -1; - var i, addr; - - for (i = 0; i < hosts.length; i++) { - addr = hosts[i]; - - if (this.isMisbehaving(addr)) - continue; - - if (this.isIgnored(addr)) - continue; - - if (unique && this.pool.peers.get(addr)) - continue; - - if (i >= index) - return addr; - - last = i; - } - - if (last === -1) - return; - - return hosts[last]; + var addr = this.list.shift(); + assert(addr, 'No address available.'); + this.list.push(addr); + return addr; }; /** @@ -2173,14 +2175,13 @@ HostList.prototype.getRandom = function getRandom(hosts, unique) { */ HostList.prototype.add = function add(addr) { - if (this.items.length > 500) + if (this.list.size > 500) return; if (this.map[addr.hostname]) return; - util.binaryInsert(this.items, addr, compare); - + assert(this.list.unshift(addr)); this.map[addr.hostname] = addr; return addr; @@ -2198,15 +2199,14 @@ HostList.prototype.remove = function remove(addr) { if (!item) return; - util.binaryRemove(this.items, item, compare); - + assert(this.list.remove(item)); delete this.map[item.hostname]; return item; }; /** - * Increase peer's ban score. + * Mark a peer as misbehaving. * @param {NetworkAddress} addr */ @@ -2222,7 +2222,6 @@ HostList.prototype.ban = function ban(addr) { HostList.prototype.unban = function unban(addr) { delete this.misbehaving[addr.host]; - delete this.ignored[addr.host]; }; /** @@ -2251,18 +2250,12 @@ HostList.prototype.isMisbehaving = function isMisbehaving(addr) { */ HostList.prototype.ignore = function ignore(addr) { - if (!this.remove(addr)) - this.ignored[addr.host] = true; -}; + var item = this.map[addr.hostname]; -/** - * Test whether the host/peer is ignored. - * @param {NetworkAddress} addr - * @returns {Boolean} - */ + if (!item) + return; -HostList.prototype.isIgnored = function isIgnored(addr) { - return this.ignored[addr.host] === true; + this.list.remove(item); }; /** @@ -2271,14 +2264,13 @@ HostList.prototype.isIgnored = function isIgnored(addr) { */ HostList.prototype.setSeeds = function setSeeds(seeds) { - var i, hostname, seed; + var i, seed; this.seeds.length = 0; for (i = 0; i < seeds.length; i++) { - hostname = seeds[i]; - seed = NetworkAddress.fromHostname(hostname, this.pool.network); - this.seeds.push(seed); + seed = seeds[i]; + this.addSeed(seed); } }; @@ -2288,10 +2280,58 @@ HostList.prototype.setSeeds = function setSeeds(seeds) { */ HostList.prototype.addSeed = function addSeed(hostname) { - var seed = NetworkAddress.fromHostname(hostname, this.pool.network); - this.seeds.unshift(seed); + var addr = IP.parseHost(hostname, this.network.port); + this.seeds.push(addr); }; +/** + * Populate from seed. + * @param {String} seed + */ + +HostList.prototype.discover = co(function* discover() { + var i, seed; + + for (i = 0; i < this.seeds.length; i++) { + seed = this.seeds[i]; + + yield this.populate(seed); + + if (this.list.size >= this.maxOutbound) + break; + } +}); + +/** + * Populate from seed. + * @param {String} seed + */ + +HostList.prototype.populate = co(function* populate(seed) { + var i, addr, hosts, host; + + if (seed.version !== -1) { + addr = NetworkAddress.fromHost(seed.host, seed.port, this.network); + this.add(addr); + return; + } + + this.logger.info('Resolving hosts from seed: %s.', seed.host); + + try { + hosts = yield dns.resolve(seed.host); + } catch (e) { + this.logger.error(e); + return; + } + + for (i = 0; i < hosts.length; i++) { + host = hosts[i]; + addr = NetworkAddress.fromHost(host, seed.port, this.network); + this.add(addr); + } +}); + /** * Represents an in-flight block or transaction. * @exports LoadRequest @@ -2438,10 +2478,9 @@ function BroadcastItem(pool, msg) { item = msg.toInv(); this.pool = pool; - this.msg = msg; - this.hash = item.hash; this.type = item.type; + this.msg = msg; this.callback = []; this.prev = null; diff --git a/lib/net/proxysocket.js b/lib/net/proxysocket.js index 8a4f7bde..24189ae0 100644 --- a/lib/net/proxysocket.js +++ b/lib/net/proxysocket.js @@ -6,6 +6,7 @@ var BufferWriter = require('../utils/writer'); var assert = require('assert'); var EventEmitter = require('events').EventEmitter; var IOClient = require('socket.io-client'); +var IP = require('../utils/ip'); function ProxySocket(uri) { if (!(this instanceof ProxySocket)) @@ -53,9 +54,11 @@ ProxySocket.prototype._init = function _init() { console.error(err); }); - this.socket.on('tcp connect', function() { + this.socket.on('tcp connect', function(addr, port) { if (self.closed) return; + self.remoteAddress = addr; + self.remotePort = port; self.emit('connect'); }); diff --git a/lib/primitives/netaddress.js b/lib/primitives/netaddress.js index c1e17452..08760401 100644 --- a/lib/primitives/netaddress.js +++ b/lib/primitives/netaddress.js @@ -23,7 +23,6 @@ var BufferReader = require('../utils/reader'); * @param {Number?} options.services - Service bits. * @param {String?} options.host - IP address (IPv6 or IPv4). * @param {Number?} options.port - Port. - * @property {Number} id * @property {Host} host * @property {Number} port * @property {Number} services @@ -34,25 +33,19 @@ function NetworkAddress(options) { if (!(this instanceof NetworkAddress)) return new NetworkAddress(options); - this.id = NetworkAddress.uid++; this.host = '0.0.0.0'; this.port = 0; this.services = 0; this.ts = 0; this.hostname = '0.0.0.0:0'; + this.prev = null; + this.next = null; + if (options) this.fromOptions(options); } -/** - * Globally incremented unique id. - * @private - * @type {Number} - */ - -NetworkAddress.uid = 0; - /** * Inject properties from options object. * @private @@ -60,15 +53,12 @@ NetworkAddress.uid = 0; */ NetworkAddress.prototype.fromOptions = function fromOptions(options) { - var host = options.host; - assert(typeof options.host === 'string'); assert(typeof options.port === 'number'); - if (IP.version(host) !== -1) - host = IP.normalize(host); + assert(IP.version(options.host) !== -1); - this.host = host; + this.host = IP.normalize(options.host); this.port = options.port; if (options.services) { @@ -96,15 +86,6 @@ NetworkAddress.fromOptions = function fromOptions(options) { return new NetworkAddress().fromOptions(options); }; -/** - * Test whether the `host` field is an ip address. - * @returns {Boolean} - */ - -NetworkAddress.prototype.isIP = function isIP() { - return IP.version(this.host) !== -1; -}; - /** * Test whether the NETWORK service bit is set. * @returns {Boolean} @@ -162,17 +143,41 @@ NetworkAddress.prototype.setPort = function setPort(port) { }; /** - * Inspect the network address. - * @returns {Object} + * Inject properties from host, port, and network. + * @private + * @param {String} host + * @param {Number} port + * @param {(Network|NetworkType)?} network */ -NetworkAddress.prototype.inspect = function inspect() { - return ''; +NetworkAddress.prototype.fromHost = function fromHost(host, port, network) { + network = Network.get(network); + + assert(IP.version(host) !== -1); + + this.host = host; + this.port = port || network.port; + this.services = constants.services.NETWORK + | constants.services.BLOOM + | constants.services.WITNESS; + this.ts = network.now(); + + this.hostname = IP.hostname(this.host, this.port); + + return this; +}; + +/** + * Instantiate a network address + * from a host and port. + * @param {String} host + * @param {Number} port + * @param {(Network|NetworkType)?} network + * @returns {NetworkAddress} + */ + +NetworkAddress.fromHost = function fromHost(host, port, network) { + return new NetworkAddress().fromHost(host, port, network); }; /** @@ -183,20 +188,10 @@ NetworkAddress.prototype.inspect = function inspect() { */ NetworkAddress.prototype.fromHostname = function fromHostname(hostname, network) { - var address = IP.parseHost(hostname); - + var addr; network = Network.get(network); - - this.host = address.host; - this.port = address.port || network.port; - this.services = constants.services.NETWORK - | constants.services.BLOOM - | constants.services.WITNESS; - this.ts = network.now(); - - this.hostname = IP.hostname(this.host, this.port); - - return this; + addr = IP.parseHost(hostname, network.port); + return this.fromHost(addr.host, addr.port, network); }; /** @@ -218,21 +213,11 @@ NetworkAddress.fromHostname = function fromHostname(hostname, network) { */ NetworkAddress.prototype.fromSocket = function fromSocket(socket, network) { - assert(typeof socket.remoteAddress === 'string'); - assert(typeof socket.remotePort === 'number'); - - network = Network.get(network); - - this.host = IP.normalize(socket.remoteAddress); - this.port = socket.remotePort; - this.services = constants.services.NETWORK - | constants.services.BLOOM - | constants.services.WITNESS; - this.ts = network.now(); - - this.hostname = IP.hostname(this.host, this.port); - - return this; + var host = socket.remoteAddress; + var port = socket.remotePort; + assert(typeof host === 'string'); + assert(typeof port === 'number'); + return this.fromHost(IP.normalize(host), port, network); }; /** @@ -336,6 +321,20 @@ NetworkAddress.prototype.toRaw = function toRaw(full) { return this.toWriter(new StaticWriter(size), full).render(); }; +/** + * Inspect the network address. + * @returns {Object} + */ + +NetworkAddress.prototype.inspect = function inspect() { + return ''; +}; + /* * Expose */ diff --git a/lib/utils/ip.js b/lib/utils/ip.js index 30d500c8..21b58220 100644 --- a/lib/utils/ip.js +++ b/lib/utils/ip.js @@ -15,16 +15,17 @@ var assert = require('assert'); * @example * IP.parseHost('127.0.0.1:3000'); * @param {String} addr + * @param {Number?} fallback - Fallback port. * @returns {Object} Contains `host` and `port`. */ -exports.parseHost = function parseHost(addr) { - var parts, host, port; +exports.parseHost = function parseHost(addr, fallback) { + var port = fallback || 0; + var parts, host, version; - assert(addr); - - if (typeof addr === 'object') - return addr; + assert(typeof addr === 'string'); + assert(addr.length > 0); + assert(typeof port === 'number'); if (addr[0] === '[') { addr = addr.substring(1); @@ -35,15 +36,20 @@ exports.parseHost = function parseHost(addr) { } host = parts[0]; - port = +parts[1] || 0; + assert(host.length > 0, 'Bad host.'); - if (exports.version(host) !== -1) + if (parts.length === 2) { + port = parts[1]; + assert(/^\d+$/.test(port), 'Bad port.'); + port = parseInt(port, 10); + } + + version = exports.version(host); + + if (version !== -1) host = exports.normalize(host); - return { - host: host, - port: port - }; + return new Address(host, port, version); }; /** @@ -54,11 +60,22 @@ exports.parseHost = function parseHost(addr) { */ exports.hostname = function hostname(host, port) { - var version = exports.version(host); + var version + + assert(typeof host === 'string'); + assert(host.length > 0); + assert(typeof port === 'number'); + + assert(!/[\[\]]/.test(host), 'Bad host.'); + + version = exports.version(host); + if (version !== -1) host = exports.normalize(host); + if (version === 6) host = '[' + host + ']'; + return host + ':' + port; }; @@ -69,8 +86,7 @@ exports.hostname = function hostname(host, port) { */ exports.version = function version(ip) { - if (typeof ip !== 'string') - return -1; + assert(typeof ip === 'string'); if (IP.isV4Format(ip)) return 4; @@ -113,14 +129,6 @@ exports.isMapped = function isMapped(ip) { exports.toBuffer = function toBuffer(ip) { var out; - if (Buffer.isBuffer(ip)) { - assert(ip.length === 16); - return ip; - } - - if (!ip) - return toBuffer('0.0.0.0'); - assert(typeof ip === 'string'); assert(exports.version(ip) !== -1); @@ -148,14 +156,6 @@ exports.toBuffer = function toBuffer(ip) { */ exports.toString = function toString(ip) { - if (typeof ip === 'string') { - assert(exports.version(ip) !== -1); - return ip; - } - - if (!ip) - return '0.0.0.0'; - assert(Buffer.isBuffer(ip)); assert(ip.length === 16); @@ -183,6 +183,17 @@ exports.normalize = function normalize(ip) { return exports.toString(exports.toBuffer(ip)); }; + +/* + * Helpers + */ + +function Address(host, port, version) { + this.host = host; + this.port = port; + this.version = version; +} + /* * Expose IP functions. */ diff --git a/package.json b/package.json index 34ab0789..e587c4fa 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "./lib/db/backends": "./lib/db/backends-browser.js", "./lib/hd/wordlist": "./lib/hd/wordlist-browser.js", "./lib/net/tcp": "./lib/net/tcp-browser.js", + "./lib/net/dns": "./lib/net/dns-browser.js", "./lib/blockchain/layout": "./lib/blockchain/layout-browser.js", "./lib/wallet/layout": "./lib/wallet/layout-browser.js", "fs": "./browser/empty.js",