From 284bba746a2ffd9e0b6e81c85c38d97e626dc193 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 25 May 2016 13:09:13 -0700 Subject: [PATCH] use node-ip. --- lib/bcoin/peer.js | 2 +- lib/bcoin/pool.js | 20 +- lib/bcoin/protocol/framer.js | 2 +- lib/bcoin/protocol/parser.js | 2 +- lib/bcoin/utils.js | 111 +++++---- vendor/ip.js | 444 +++++++++++++++++++++++++++++++++++ 6 files changed, 518 insertions(+), 63 deletions(-) create mode 100644 vendor/ip.js diff --git a/lib/bcoin/peer.js b/lib/bcoin/peer.js index 1a863c7b..5c338104 100644 --- a/lib/bcoin/peer.js +++ b/lib/bcoin/peer.js @@ -1459,7 +1459,7 @@ Peer.prototype._handleGetAddr = function _handleGetAddr() { seed = utils.parseHost(this.pool.seeds[i]); seed = this.pool.getPeer(seed.host) || seed; - if (!utils.isIP(seed.host)) + if (utils.ip.version(seed.host) === -1) continue; if (hosts[seed.host]) diff --git a/lib/bcoin/pool.js b/lib/bcoin/pool.js index 4340a135..5d2832f7 100644 --- a/lib/bcoin/pool.js +++ b/lib/bcoin/pool.js @@ -428,7 +428,7 @@ Pool.prototype.listen = function listen(callback) { } this.server.on('connection', function(socket) { - var hostname; + var hostname, host; if (!socket.remoteAddress) { bcoin.debug('Ignoring disconnected leech.'); @@ -436,15 +436,17 @@ Pool.prototype.listen = function listen(callback) { return; } + host = utils.ip.normalize(socket.remoteAddress); + if (self.peers.leeches.length >= self.maxLeeches) { - hostname = utils.hostname(socket.remoteAddress, socket.remotePort); + hostname = utils.hostname(host, socket.remotePort); bcoin.debug('Ignoring leech: too many leeches (%s).', hostname); socket.destroy(); return; } - if (self.isMisbehaving(socket.remoteAddress)) { - hostname = utils.hostname(socket.remoteAddress, socket.remotePort); + if (self.isMisbehaving(host)) { + hostname = utils.hostname(host, socket.remotePort); bcoin.debug('Ignoring misbehaving leech (%s).', hostname); socket.destroy(); return; @@ -2132,10 +2134,10 @@ Pool.prototype.getIP = function getIP(callback) { ip = body.trim(); - if (!utils.isIP(ip)) + if (utils.ip.version(ip) == -1) return self.getIP2(callback); - return callback(null, ip); + return callback(null, utils.ip.normalize(ip)); }); }; @@ -2159,12 +2161,12 @@ Pool.prototype.getIP2 = function getIP2(callback) { if (err) return callback(err); - ip = /IP Address:\s*([0-9.:]+)/i.exec(body); + ip = /IP Address:\s*([0-9a-f.:]+)/i.exec(body); - if (!ip || !utils.isIP(ip[1])) + if (!ip || utils.ip.version(ip[1]) === -1) return callback(new Error('Could not find IP.')); - return callback(null, ip[1]); + return callback(null, utils.ip.normalize(ip[1])); }); }; diff --git a/lib/bcoin/protocol/framer.js b/lib/bcoin/protocol/framer.js index f0329070..35d81af8 100644 --- a/lib/bcoin/protocol/framer.js +++ b/lib/bcoin/protocol/framer.js @@ -432,7 +432,7 @@ Framer.address = function address(data, full, writer) { } p.writeU64(data.services || 0); - p.writeBytes(utils.ip2array(data.host)); + p.writeBytes(utils.ip.toBuffer(data.host)); p.writeU16BE(data.port || 0); if (!writer) diff --git a/lib/bcoin/protocol/parser.js b/lib/bcoin/protocol/parser.js index dc3f672e..1bcd9bfe 100644 --- a/lib/bcoin/protocol/parser.js +++ b/lib/bcoin/protocol/parser.js @@ -1197,7 +1197,7 @@ Parser.parseAddress = function parseAddress(p, full) { return { ts: ts, services: services, - host: utils.array2ip(ip), + host: utils.ip.toString(ip), port: port }; }; diff --git a/lib/bcoin/utils.js b/lib/bcoin/utils.js index 8d5922b7..dfbdba11 100644 --- a/lib/bcoin/utils.js +++ b/lib/bcoin/utils.js @@ -16,6 +16,7 @@ var utils = exports; var assert = require('assert'); var bn = require('bn.js'); var util = require('util'); +var IP = require('../../vendor/ip'); var crypto, supersha, hash, aes; /** @@ -850,7 +851,7 @@ utils.isBTC = function isBTC(value) { */ utils.parseHost = function parseHost(addr) { - var parts; + var parts, host, port; assert(addr); @@ -862,9 +863,15 @@ utils.parseHost = function parseHost(addr) { else parts = addr.split(':'); + host = parts[0].replace(/[\[\]]/g, ''); + port = +parts[1] || 0; + + if (utils.ip.version(host) !== -1) + host = utils.ip.normalize(host); + return { - host: parts[0].replace(/[\[\]]/g, ''), - port: +parts[1] || 0 + host: host, + port: port }; }; @@ -876,28 +883,34 @@ utils.parseHost = function parseHost(addr) { */ utils.hostname = function hostname(host, port) { - if (utils.isIP(host) === 6) + if (utils.ip.version(host) === 6) host = '[' + host + ']'; return host + ':' + port; }; +/** + * IP utilities. + */ + +utils.ip = utils.merge({}, IP); + /** * Test whether a string is an IP address. * @param {String?} ip * @returns {Number} IP version (4 or 6). */ -utils.isIP = function isIP(ip) { +utils.ip.version = function version(ip) { if (typeof ip !== 'string') - return 0; + return -1; - if (/^\d+\.\d+\.\d+\.\d+$/.test(ip)) + if (IP.isV4Format(ip)) return 4; - if (/:[0-9a-f]{1,4}/i.test(ip)) + if (IP.isV6Format(ip)) return 6; - return 0; + return -1; }; /** @@ -905,18 +918,18 @@ utils.isIP = function isIP(ip) { * @returns {Boolean} */ -utils.isMapped = function isMapped(ip) { +utils.ip.isMapped = function isMapped(ip) { var i; - if (!Buffer.isBuffer(ip)) - return false; + assert(Buffer.isBuffer(ip)); + assert(ip.length === 16); for (i = 0; i < ip.length - 6; i++) { if (ip[i] !== 0) return false; } - if (ip[10] !== 0xff && ip[11] !== 0xff) + if (ip[ip.length - 6] !== 0xff && ip[ip.length - 5] !== 0xff) return false; return true; @@ -928,40 +941,35 @@ utils.isMapped = function isMapped(ip) { * @returns {Buffer} */ -utils.ip2array = function ip2array(ip) { - var version, parts, out; +utils.ip.toBuffer = function toArray(ip) { + var out; if (Buffer.isBuffer(ip)) { assert(ip.length === 16); return ip; } - version = utils.isIP(ip); + if (!ip) + return toArray('0.0.0.0'); - if (version === 0) - return ip2array('0.0.0.0'); + assert(typeof ip === 'string'); + assert(utils.ip.version(ip) !== -1); - if (version === 4) { - parts = ip.split('.'); - assert(parts.length === 4); + ip = IP.toBuffer(ip); + + if (ip.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]; + out[12] = ip[0]; + out[13] = ip[1]; + out[14] = ip[2]; + out[15] = ip[3]; return out; } - if (version === 6) { - ip = new Buffer(ip.replace(/:/g, ''), 'hex'); - assert(ip.length === 16); - return ip; - } - - assert(false, 'Bad IP.'); + return ip; }; /** @@ -970,35 +978,36 @@ utils.ip2array = function ip2array(ip) { * @returns {String} */ -utils.array2ip = function array2ip(ip) { - var i, out, hi, lo; - +utils.ip.toString = function toString(ip) { if (typeof ip === 'string') { - assert(utils.isIP(ip) !== 0); + assert(utils.ip.version(ip) !== -1); return ip; } - if (!Buffer.isBuffer(ip)) + if (!ip) return '0.0.0.0'; + assert(Buffer.isBuffer(ip)); assert(ip.length === 16); - if (utils.isMapped(ip)) - return ip[12] + '.' + ip[13] + '.' + ip[14] + '.' + ip[15]; - - 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); + if (utils.ip.isMapped(ip)) { + return ip[ip.length - 4] + + '.' + ip[ip.length - 3] + + '.' + ip[ip.length - 2] + + '.' + ip[ip.length - 1]; } - return out.join(':'); + return IP.toString(ip); +}; + +/** + * Normalize an ip. + * @param {String} ip + * @returns {String} + */ + +utils.ip.normalize = function normalize(ip) { + return utils.ip.toString(utils.ip.toBuffer(ip)); }; /** diff --git a/vendor/ip.js b/vendor/ip.js new file mode 100644 index 00000000..21270a65 --- /dev/null +++ b/vendor/ip.js @@ -0,0 +1,444 @@ +'use strict'; + +/*! + * https://github.com/indutny/node-ip + * + * This software is licensed under the MIT License. + * + * Copyright Fedor Indutny, 2012. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +var ip = exports; + +ip.toBuffer = function(ip, buff, offset) { + offset = ~~offset; + + var result; + + if (this.isV4Format(ip)) { + result = buff || new Buffer(offset + 4); + ip.split(/\./g).map(function(byte) { + result[offset++] = parseInt(byte, 10) & 0xff; + }); + } else if (this.isV6Format(ip)) { + var sections = ip.split(':', 8); + + var i; + for (i = 0; i < sections.length; i++) { + var isv4 = this.isV4Format(sections[i]); + var v4Buffer; + + if (isv4) { + v4Buffer = this.toBuffer(sections[i]); + sections[i] = v4Buffer.slice(0, 2).toString('hex'); + } + + if (v4Buffer && ++i < 8) { + sections.splice(i, 0, v4Buffer.slice(2, 4).toString('hex')); + } + } + + if (sections[0] === '') { + while (sections.length < 8) sections.unshift('0'); + } else if (sections[sections.length - 1] === '') { + while (sections.length < 8) sections.push('0'); + } else if (sections.length < 8) { + for (i = 0; i < sections.length && sections[i] !== ''; i++); + var argv = [ i, 1 ]; + for (i = 9 - sections.length; i > 0; i--) { + argv.push('0'); + } + sections.splice.apply(sections, argv); + } + + result = buff || new Buffer(offset + 16); + for (i = 0; i < sections.length; i++) { + var word = parseInt(sections[i], 16); + result[offset++] = (word >> 8) & 0xff; + result[offset++] = word & 0xff; + } + } + + if (!result) { + throw Error('Invalid ip address: ' + ip); + } + + return result; +}; + +ip.toString = function(buff, offset, length) { + offset = ~~offset; + length = length || (buff.length - offset); + + var result = []; + if (length === 4) { + // IPv4 + for (var i = 0; i < length; i++) { + result.push(buff[offset + i]); + } + result = result.join('.'); + } else if (length === 16) { + // IPv6 + for (var i = 0; i < length; i += 2) { + result.push(buff.readUInt16BE(offset + i).toString(16)); + } + result = result.join(':'); + result = result.replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3'); + result = result.replace(/:{3,4}/, '::'); + } + + return result; +}; + +var ipv4Regex = /^(\d{1,3}\.){3,3}\d{1,3}$/; +var ipv6Regex = + /^(::)?(((\d{1,3}\.){3}(\d{1,3}){1})?([0-9a-f]){0,4}:{0,2}){1,8}(::)?$/i; + +ip.isV4Format = function(ip) { + return ipv4Regex.test(ip); +}; + +ip.isV6Format = function(ip) { + return ipv6Regex.test(ip); +}; +function _normalizeFamily(family) { + return family ? family.toLowerCase() : 'ipv4'; +} + +ip.fromPrefixLen = function(prefixlen, family) { + if (prefixlen > 32) { + family = 'ipv6'; + } else { + family = _normalizeFamily(family); + } + + var len = 4; + if (family === 'ipv6') { + len = 16; + } + var buff = new Buffer(len); + + for (var i = 0, n = buff.length; i < n; ++i) { + var bits = 8; + if (prefixlen < 8) { + bits = prefixlen; + } + prefixlen -= bits; + + buff[i] = ~(0xff >> bits); + } + + return ip.toString(buff); +}; + +ip.mask = function(addr, mask) { + addr = ip.toBuffer(addr); + mask = ip.toBuffer(mask); + + var result = new Buffer(Math.max(addr.length, mask.length)); + + // Same protocol - do bitwise and + if (addr.length === mask.length) { + for (var i = 0; i < addr.length; i++) { + result[i] = addr[i] & mask[i]; + } + } else if (mask.length === 4) { + // IPv6 address and IPv4 mask + // (Mask low bits) + for (var i = 0; i < mask.length; i++) { + result[i] = addr[addr.length - 4 + i] & mask[i]; + } + } else { + // IPv6 mask and IPv4 addr + for (var i = 0; i < result.length - 6; i++) { + result[i] = 0; + } + + // ::ffff:ipv4 + result[10] = 0xff; + result[11] = 0xff; + for (var i = 0; i < addr.length; i++) { + result[i + 12] = addr[i] & mask[i + 12]; + } + } + + return ip.toString(result); +}; + +ip.cidr = function(cidrString) { + var cidrParts = cidrString.split('/'); + + var addr = cidrParts[0]; + if (cidrParts.length !== 2) + throw new Error('invalid CIDR subnet: ' + addr); + + var mask = ip.fromPrefixLen(parseInt(cidrParts[1], 10)); + + return ip.mask(addr, mask); +}; + +ip.subnet = function(addr, mask) { + var networkAddress = ip.toLong(ip.mask(addr, mask)); + + // Calculate the mask's length. + var maskBuffer = ip.toBuffer(mask); + var maskLength = 0; + + for (var i = 0; i < maskBuffer.length; i++) { + if (maskBuffer[i] === 0xff) { + maskLength += 8; + } else { + var octet = maskBuffer[i] & 0xff; + while (octet) { + octet = (octet << 1) & 0xff; + maskLength++; + } + } + } + + var numberOfAddresses = Math.pow(2, 32 - maskLength); + + return { + networkAddress: ip.fromLong(networkAddress), + firstAddress: numberOfAddresses <= 2 ? + ip.fromLong(networkAddress) : + ip.fromLong(networkAddress + 1), + lastAddress: numberOfAddresses <= 2 ? + ip.fromLong(networkAddress + numberOfAddresses - 1) : + ip.fromLong(networkAddress + numberOfAddresses - 2), + broadcastAddress: ip.fromLong(networkAddress + numberOfAddresses - 1), + subnetMask: mask, + subnetMaskLength: maskLength, + numHosts: numberOfAddresses <= 2 ? + numberOfAddresses : numberOfAddresses - 2, + length: numberOfAddresses, + contains: function(other) { + return networkAddress === ip.toLong(ip.mask(other, mask)); + } + }; +}; + +ip.cidrSubnet = function(cidrString) { + var cidrParts = cidrString.split('/'); + + var addr = cidrParts[0]; + if (cidrParts.length !== 2) + throw new Error('invalid CIDR subnet: ' + addr); + + var mask = ip.fromPrefixLen(parseInt(cidrParts[1], 10)); + + return ip.subnet(addr, mask); +}; + +ip.not = function(addr) { + var buff = ip.toBuffer(addr); + for (var i = 0; i < buff.length; i++) { + buff[i] = 0xff ^ buff[i]; + } + return ip.toString(buff); +}; + +ip.or = function(a, b) { + a = ip.toBuffer(a); + b = ip.toBuffer(b); + + // same protocol + if (a.length === b.length) { + for (var i = 0; i < a.length; ++i) { + a[i] |= b[i]; + } + return ip.toString(a); + + // mixed protocols + } else { + var buff = a; + var other = b; + if (b.length > a.length) { + buff = b; + other = a; + } + + var offset = buff.length - other.length; + for (var i = offset; i < buff.length; ++i) { + buff[i] |= other[i - offset]; + } + + return ip.toString(buff); + } +}; + +ip.isEqual = function(a, b) { + a = ip.toBuffer(a); + b = ip.toBuffer(b); + + // Same protocol + if (a.length === b.length) { + for (var i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; + } + return true; + } + + // Swap + if (b.length === 4) { + var t = b; + b = a; + a = t; + } + + // a - IPv4, b - IPv6 + for (var i = 0; i < 10; i++) { + if (b[i] !== 0) return false; + } + + var word = b.readUInt16BE(10); + if (word !== 0 && word !== 0xffff) return false; + + for (var i = 0; i < 4; i++) { + if (a[i] !== b[i + 12]) return false; + } + + return true; +}; + +ip.isPrivate = function(addr) { + return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i + .test(addr) || + /^(::f{4}:)?192\.168\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) || + /^(::f{4}:)?172\.(1[6-9]|2\d|30|31)\.([0-9]{1,3})\.([0-9]{1,3})$/i + .test(addr) || + /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) || + /^(::f{4}:)?169\.254\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) || + /^f[cd][0-9a-f]{2}:/i.test(addr) || + /^fe80:/i.test(addr) || + /^::1$/.test(addr) || + /^::$/.test(addr); +}; + +ip.isPublic = function(addr) { + return !ip.isPrivate(addr); +}; + +ip.isLoopback = function(addr) { + return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/ + .test(addr) || + /^fe80::1$/.test(addr) || + /^::1$/.test(addr) || + /^::$/.test(addr); +}; + +ip.loopback = function(family) { + // + // Default to `ipv4` + // + family = _normalizeFamily(family); + + if (family !== 'ipv4' && family !== 'ipv6') { + throw new Error('family must be ipv4 or ipv6'); + } + + return family === 'ipv4' ? '127.0.0.1' : 'fe80::1'; +}; + +// +// ### function address (name, family) +// #### @name {string|'public'|'private'} **Optional** Name or security +// of the network interface. +// #### @family {ipv4|ipv6} **Optional** IP family of the address (defaults +// to ipv4). +// +// Returns the address for the network interface on the current system with +// the specified `name`: +// * String: First `family` address of the interface. +// If not found see `undefined`. +// * 'public': the first public ip address of family. +// * 'private': the first private ip address of family. +// * undefined: First address with `ipv4` or loopback address `127.0.0.1`. +// +ip.address = function(name, family) { + var os; + + try { + os = require('o' + 's'); + } catch (e) { + return '127.0.0.1'; + } + + var interfaces = os.networkInterfaces(); + var all; + + // + // Default to `ipv4` + // + family = _normalizeFamily(family); + + // + // If a specific network interface has been named, + // return the address. + // + if (name && name !== 'private' && name !== 'public') { + var res = interfaces[name].filter(function(details) { + var itemFamily = details.family.toLowerCase(); + return itemFamily === family; + }); + if (res.length === 0) + return undefined; + return res[0].address; + } + + var all = Object.keys(interfaces).map(function (nic) { + // + // Note: name will only be `public` or `private` + // when this is called. + // + var addresses = interfaces[nic].filter(function (details) { + details.family = details.family.toLowerCase(); + if (details.family !== family || ip.isLoopback(details.address)) { + return false; + } else if (!name) { + return true; + } + + return name === 'public' ? ip.isPrivate(details.address) : + ip.isPublic(details.address); + }); + + return addresses.length ? addresses[0].address : undefined; + }).filter(Boolean); + + return !all.length ? ip.loopback(family) : all[0]; +}; + +ip.toLong = function(ip) { + var ipl = 0; + ip.split('.').forEach(function(octet) { + ipl <<= 8; + ipl += parseInt(octet); + }); + return(ipl >>> 0); +}; + +ip.fromLong = function(ipl) { + return ((ipl >>> 24) + '.' + + (ipl >> 16 & 255) + '.' + + (ipl >> 8 & 255) + '.' + + (ipl & 255) ); +};