diff --git a/browser/wsproxy.js b/browser/wsproxy.js index 7ae3222a..670e593e 100644 --- a/browser/wsproxy.js +++ b/browser/wsproxy.js @@ -110,7 +110,7 @@ WSProxy.prototype._handleResolve = function _handleResolve(ws, name, record, cal WSProxy.prototype._handleConnect = function _handleConnect(ws, port, host, nonce) { var self = this; var state = this.sockets.get(ws); - var socket, pow; + var socket, pow, raw; if (state.socket) { this.log('Client is trying to reconnect (%s).', state.host); @@ -149,7 +149,9 @@ WSProxy.prototype._handleConnect = function _handleConnect(ws, port, host, nonce } } - if (IP.version(host) === -1) { + try { + raw = IP.toBuffer(host); + } catch (e) { this.log('Client gave a bad host: %s (%s).', host, state.host); ws.emit('tcp error', { message: 'EHOSTUNREACH', @@ -159,8 +161,10 @@ WSProxy.prototype._handleConnect = function _handleConnect(ws, port, host, nonce return; } - if (IP.isPrivate(host)) { - this.log('Client is trying to connect to a private ip (%s).', state.host); + if (!IP.isRoutable(raw)) { + this.log( + 'Client is trying to connect to a bad ip: %s (%s).', + host, state.host); ws.emit('tcp error', { message: 'ENETUNREACH', code: 'ENETUNREACH' diff --git a/lib/http/server.js b/lib/http/server.js index b0757d61..8f68690f 100644 --- a/lib/http/server.js +++ b/lib/http/server.js @@ -1759,7 +1759,7 @@ HTTPOptions.prototype.fromOptions = function fromOptions(options) { if (options.host != null) { assert(typeof options.host === 'string'); - this.host = options.host; + this.host = IP.normalize(options.host); } if (options.port != null) { @@ -1786,7 +1786,7 @@ HTTPOptions.prototype.fromOptions = function fromOptions(options) { // Allow no-auth implicitly // if we're listening locally. if (!options.apiKey && !options.serviceKey && options.noAuth == null) { - if (IP.isLoopback(this.host)) + if (this.host === '127.0.0.1' || this.host === '::1') this.noAuth = true; } diff --git a/lib/net/pool.js b/lib/net/pool.js index 7ad710e0..b4d57bc9 100644 --- a/lib/net/pool.js +++ b/lib/net/pool.js @@ -3153,10 +3153,13 @@ Pool.prototype.getIP = co(function* getIP() { ip = res.body.trim(); - if (IP.version(ip) === -1) + try { + ip = IP.normalize(ip); + } catch (e) { return yield this.getIP2(); + } - return IP.normalize(ip); + return ip; }); /** @@ -3184,9 +3187,6 @@ Pool.prototype.getIP2 = co(function* getIP2() { ip = match[1]; - if (IP.version(ip) === -1) - throw new Error('Could not parse IP.'); - return IP.normalize(ip); }); @@ -3311,10 +3311,10 @@ PoolOptions.prototype.fromOptions = function fromOptions(options) { if (options.host != null) { assert(typeof options.host === 'string'); - assert(IP.version(options.host) !== -1, '`host` must be an IP.'); - this.host = IP.normalize(options.host); - if (IP.isRoutable(this.host)) - this.address.setHost(this.host); + this.host = options.host; + this.address.setHost(this.host); + if (!this.address.isRoutable()) + this.address.setNull(); } if (options.port != null) { diff --git a/lib/primitives/netaddress.js b/lib/primitives/netaddress.js index 253ada95..971308d9 100644 --- a/lib/primitives/netaddress.js +++ b/lib/primitives/netaddress.js @@ -38,6 +38,7 @@ function NetAddress(options) { this.services = 0; this.ts = 0; this.hostname = '0.0.0.0:0'; + this.raw = IP.ZERO_IP; if (options) this.fromOptions(options); @@ -65,9 +66,8 @@ NetAddress.prototype.fromOptions = function fromOptions(options) { assert(typeof options.host === 'string'); assert(typeof options.port === 'number'); - assert(IP.version(options.host) !== -1); - - this.host = IP.normalize(options.host); + this.raw = IP.toBuffer(options.host); + this.host = IP.toString(this.raw); this.port = options.port; if (options.services) { @@ -105,22 +105,31 @@ NetAddress.prototype.hasServices = function hasServices(services) { return (this.services & services) === services; }; +/** + * Test whether the address is IPv4. + * @returns {Boolean} + */ + +NetAddress.isIPv4 = function isIPv4() { + return IP.isIPv4(this.raw); +}; + +/** + * Test whether the address is IPv6. + * @returns {Boolean} + */ + +NetAddress.isIPv6 = function isIPv6() { + return !IP.isIPv6(this.raw); +}; + /** * Test whether the host is null. * @returns {Boolean} */ NetAddress.prototype.isNull = function isNull() { - return IP.isNull(this.host); -}; - -/** - * Test whether the host is a broadcast address. - * @returns {Boolean} - */ - -NetAddress.prototype.isBroadcast = function isBroadcast() { - return IP.isBroadcast(this.host); + return IP.isNull(this.raw); }; /** @@ -128,17 +137,8 @@ NetAddress.prototype.isBroadcast = function isBroadcast() { * @returns {Boolean} */ -NetAddress.prototype.isLoopback = function isLoopback() { - return IP.isLoopback(this.host); -}; - -/** - * Test whether the host is a private address. - * @returns {Boolean} - */ - -NetAddress.prototype.isPrivate = function isPrivate() { - return IP.isPrivate(this.host); +NetAddress.prototype.isLocal = function isLocal() { + return IP.isLocal(this.raw); }; /** @@ -147,7 +147,7 @@ NetAddress.prototype.isPrivate = function isPrivate() { */ NetAddress.prototype.isValid = function isValid() { - return IP.isValid(this.host); + return IP.isValid(this.raw); }; /** @@ -156,7 +156,26 @@ NetAddress.prototype.isValid = function isValid() { */ NetAddress.prototype.isRoutable = function isRoutable() { - return IP.isRoutable(this.host); + return IP.isRoutable(this.raw); +}; + +/** + * Test whether the host is an onion address. + * @returns {Boolean} + */ + +NetAddress.prototype.isTor = function isTor() { + return IP.isTor(this.raw); +}; + +/** + * Set null host. + */ + +NetAddress.prototype.setNull = function setNull() { + this.raw = IP.ZERO_IP; + this.host = '0.0.0.0'; + this.hostname = IP.hostname(this.host, this.port); }; /** @@ -165,8 +184,9 @@ NetAddress.prototype.isRoutable = function isRoutable() { */ NetAddress.prototype.setHost = function setHost(host) { - this.host = host; - this.hostname = IP.hostname(host, this.port); + this.raw = IP.toBuffer(host); + this.host = IP.toString(this.raw); + this.hostname = IP.hostname(this.host, this.port); }; /** @@ -190,9 +210,8 @@ NetAddress.prototype.setPort = function setPort(port) { NetAddress.prototype.fromHost = function fromHost(host, port, network) { network = Network.get(network); - assert(IP.version(host) !== -1); - - this.host = host; + this.raw = IP.toBuffer(host); + this.host = IP.toString(this.raw); this.port = port || network.port; this.services = NetAddress.DEFAULT_SERVICES; this.ts = network.now(); @@ -284,7 +303,8 @@ NetAddress.prototype.fromReader = function fromReader(br, full) { // are currently unused. br.readU32(); - this.host = IP.toString(br.readBytes(16, true)); + this.raw = br.readBytes(16); + this.host = IP.toString(this.raw); this.port = br.readU16BE(); this.hostname = IP.hostname(this.host, this.port); @@ -337,7 +357,7 @@ NetAddress.prototype.toWriter = function toWriter(bw, full) { bw.writeU32(this.services); bw.writeU32(0); - bw.writeBytes(IP.toBuffer(this.host)); + bw.writeBytes(this.raw); bw.writeU16BE(this.port); return bw; @@ -385,11 +405,11 @@ NetAddress.prototype.toJSON = function toJSON() { */ NetAddress.prototype.fromJSON = function fromJSON(json) { - assert(IP.version(json.host) !== -1); assert(util.isNumber(json.port)); assert(json.port >= 0 && json.port <= 0xffff); assert(util.isNumber(json.services)); assert(util.isNumber(json.ts)); + this.raw = IP.toBuffer(json.host); this.host = json.host; this.port = json.port; this.services = json.services; diff --git a/lib/utils/ip.js b/lib/utils/ip.js index a7ad24a1..86456117 100644 --- a/lib/utils/ip.js +++ b/lib/utils/ip.js @@ -18,10 +18,26 @@ var IP = exports; * Constants */ -var ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; -var ipv6Regex = +var ZERO_IP = new Buffer('00000000000000000000000000000000', 'hex'); +var LOCAL_IP = new Buffer('00000000000000000000000000000001', 'hex'); +var RFC6052 = new Buffer('0064ff9b0000000000000000', 'hex'); +var RFC4862 = new Buffer('fe80000000000000', 'hex'); +var RFC6145 = new Buffer('0000000000000000ffff0000', 'hex'); +var TOR_ONION = new Buffer('fd87d87eeb43', 'hex'); +var V4MAP = new Buffer('00000000000000000000ffff', 'hex'); +var SHIFTED = new Buffer('00000000000000ffff', 'hex'); + +var IPV4_REGEX = /^(\d{1,3}\.){3}\d{1,3}$/; +var IPV6_REGEX = /^(::)?(((\d{1,3}\.){3}(\d{1,3}){1})?([0-9a-f]){0,4}:{0,2}){1,8}(::)?$/i; +/** + * IP address of all zeroes. + * @const {Buffer} + */ + +IP.ZERO_IP = ZERO_IP; + /** * Parse a hostname. * @param {String} addr @@ -30,7 +46,7 @@ var ipv6Regex = */ IP.parseHost = function parseHost(addr, fallback) { - var parts, host, port, version, hostname; + var parts, host, port, version, hostname, raw; assert(typeof addr === 'string'); @@ -91,8 +107,10 @@ IP.parseHost = function parseHost(addr, fallback) { version = IP.version(host); - if (version !== -1) - host = IP.normalize(host); + if (version !== -1) { + raw = IP.toBuffer(host); + host = IP.toString(raw); + } hostname = host; @@ -101,7 +119,7 @@ IP.parseHost = function parseHost(addr, fallback) { hostname += ':' + port; - return new Address(host, port, version, hostname); + return new Address(host, port, version, hostname, raw); }; /** @@ -166,7 +184,7 @@ IP.isV4Format = function isV4Format(str) { if (str.length > 15) return false; - return ipv4Regex.test(str); + return IPV4_REGEX.test(str); }; /** @@ -184,66 +202,64 @@ IP.isV6Format = function isV6Format(str) { if (str.length > 39) return false; - return ipv6Regex.test(str); + return IPV6_REGEX.test(str); }; /** * Test whether a buffer is an ipv4-mapped ipv6 address. - * @param {Buffer} buf + * @param {Buffer} raw * @returns {Boolean} */ -IP.isMapped = function isMapped(buf) { - var i; +IP.isMapped = function isMapped(raw) { + assert(Buffer.isBuffer(raw)); + assert(raw.length === 16); - assert(Buffer.isBuffer(buf)); - assert(buf.length === 16); - - if (buf[10] !== 0xff || buf[11] !== 0xff) - return false; - - for (i = 0; i < 10; i++) { - if (buf[i] !== 0) - return false; - } - - return true; + return raw[0] === 0x00 + && raw[1] === 0x00 + && raw[2] === 0x00 + && raw[3] === 0x00 + && raw[4] === 0x00 + && raw[5] === 0x00 + && raw[6] === 0x00 + && raw[7] === 0x00 + && raw[8] === 0x00 + && raw[9] === 0x00 + && raw[10] === 0xff + && raw[11] === 0xff; }; /** - * Convert an IP string to a buffer. + * Parse an IP string and return a buffer. * @param {String} str * @returns {Buffer} */ IP.toBuffer = function toBuffer(str) { - var buf = new Buffer(16); + var raw = new Buffer(16); assert(typeof str === 'string'); if (IP.isV4Format(str)) { - buf.fill(0); - buf[10] = 0xff; - buf[11] = 0xff; - return IP.parseV4(str, buf, 12); + raw.fill(0); + raw[10] = 0xff; + raw[11] = 0xff; + return IP.parseV4(str, raw, 12); } - if (IP.isV6Format(str)) - return IP.parseV6(str, buf, 0); - - throw Error('Invalid IP address: ' + str); + return IP.parseV6(str, raw, 0); }; /** * Convert an IPv4 string to a buffer. * @private * @param {String} str - * @param {Buffer} buf + * @param {Buffer} raw * @param {Number} offset * @returns {Buffer} */ -IP.parseV4 = function parseV4(str, buf, offset) { +IP.parseV4 = function parseV4(str, raw, offset) { var parts = str.split('.'); var i, ch; @@ -255,29 +271,28 @@ IP.parseV4 = function parseV4(str, buf, offset) { assert(ch.length <= 3); ch = parseInt(ch, 10); assert(ch >= 0 && ch <= 255); - buf[offset++] = ch; + raw[offset++] = ch; } - return buf; + return raw; }; /** * Convert an IPv6 string to a buffer. * @private * @param {String} str - * @param {Buffer} buf + * @param {Buffer} raw * @param {Number} offset * @returns {Buffer} */ -IP.parseV6 = function parseV6(str, buf, offset) { +IP.parseV6 = function parseV6(str, raw, offset) { var parts = str.split(':'); var missing = 8 - parts.length; var start = offset; var colon = false; var i, word; - assert(missing >= 0, 'IPv6 address is too long.'); assert(parts.length >= 2, 'Not an IPv6 address.'); for (i = 0; i < parts.length; i++) { @@ -286,8 +301,6 @@ IP.parseV6 = function parseV6(str, buf, offset) { missing--; } - assert(missing >= 0, 'IPv6 address is too long.'); - for (i = 0; i < parts.length; i++) { word = parts[i]; @@ -308,8 +321,8 @@ IP.parseV6 = function parseV6(str, buf, offset) { } while (missing > 0) { - buf[offset++] = 0; - buf[offset++] = 0; + raw[offset++] = 0; + raw[offset++] = 0; missing--; } @@ -317,7 +330,7 @@ IP.parseV6 = function parseV6(str, buf, offset) { } if (IP.isV4Format(word)) { - IP.parseV4(word, buf, offset); + IP.parseV4(word, raw, offset); offset += 4; continue; } @@ -328,50 +341,50 @@ IP.parseV6 = function parseV6(str, buf, offset) { assert(word === word, 'Non-number in IPv6 address.'); - buf[offset++] = (word >> 8) & 0xff; - buf[offset++] = word & 0xff; + raw[offset++] = (word >> 8) & 0xff; + raw[offset++] = word & 0xff; } assert(missing === 0, 'IPv6 address has missing sections.'); assert.equal(offset, start + 16); - return buf; + return raw; }; /** * Convert a buffer to an ip string. - * @param {Buffer} buf + * @param {Buffer} raw * @returns {String} */ -IP.toString = function toString(buf) { +IP.toString = function toString(raw) { var str = ''; var i; - assert(Buffer.isBuffer(buf)); + assert(Buffer.isBuffer(raw)); - if (buf.length === 4) { - str += buf[0]; - str += '.' + buf[1]; - str += '.' + buf[2]; - str += '.' + buf[3]; + if (raw.length === 4) { + str += raw[0]; + str += '.' + raw[1]; + str += '.' + raw[2]; + str += '.' + raw[3]; return str; } - if (buf.length === 16) { - if (IP.isMapped(buf)) { - str += buf[12]; - str += '.' + buf[13]; - str += '.' + buf[14]; - str += '.' + buf[15]; + if (raw.length === 16) { + if (IP.isMapped(raw)) { + str += raw[12]; + str += '.' + raw[13]; + str += '.' + raw[14]; + str += '.' + raw[15]; return str; } - str += buf.readUInt16BE(0, true).toString(16); + str += raw.readUInt16BE(0, true).toString(16); for (i = 2; i < 16; i += 2) { str += ':'; - str += buf.readUInt16BE(i, true).toString(16); + str += raw.readUInt16BE(i, true).toString(16); } str = str.replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3'); @@ -380,7 +393,31 @@ IP.toString = function toString(buf) { return str; } - throw Error('Invalid IP address: ' + buf.toString('hex')); + throw Error('Invalid IP address: ' + raw.toString('hex')); +}; + +/** + * Convert a buffer to an ip string or .onion. + * @param {Buffer} raw + * @returns {String} + */ + +IP.toHost = function toHost(raw) { + var i, ch, host; + + if (IP.isTor(raw)) { + host = ''; + for (i = 6; i < raw.length; i += 2) { + ch = raw[i]; + ch = ch.toString(32); + while (ch.length < 4) + ch = '0' + ch; + host += ch; + } + return host + '.onion'; + } + + return IP.toString(raw); }; /** @@ -394,116 +431,421 @@ IP.normalize = function normalize(str) { }; /** - * Test whether an IP is null. - * @param {String} str - Normalized ip. + * Test whether the address is IPv4. * @returns {Boolean} */ -IP.isNull = function isNull(str) { - return str === '0.0.0.0' || str === '::'; +IP.isIPv4 = function isIPv4(raw) { + return IP.isMapped(raw); }; /** - * Test whether the IP is a broadcast address. - * @param {String} str - Normalized ip. + * Test whether the address is IPv6. * @returns {Boolean} */ -IP.isBroadcast = function isBroadcast(str) { - return str === '255.255.255.255' - || str === '::ffff:ffff:ffff'; +IP.isIPv6 = function isIPv6(raw) { + return !IP.isMapped(raw) && !IP.isTor(raw); }; /** - * Test whether the IP is valid. - * @param {String} str - Normalized ip. + * Test whether the host is null. * @returns {Boolean} */ -IP.isValid = function isValid(str) { - return !IP.isNull(str) && !IP.isBroadcast(str); +IP.isNull = function isNull(raw) { + if (IP.isIPv4(raw)) { + // 0.0.0.0 + return raw[12] === 0 + && raw[13] === 0 + && raw[14] === 0 + && raw[15] === 0; + } + // :: + return IP.isEqual(raw, ZERO_IP); }; /** - * Test whether the IP is routable. - * @param {String} str - Normalized ip. + * Test whether the host is a broadcast address. * @returns {Boolean} */ -IP.isRoutable = function isRoutable(str) { - return IP.isValid(str) && !IP.isLoopback(str); +IP.isBroadcast = function isBroadcast(raw) { + if (IP.isIPv4(raw)) { + // 255.255.255.255 + return raw[12] === 255 + && raw[13] === 255 + && raw[14] === 255 + && raw[15] === 255; + } + return false; }; /** - * Test whether a string is a private address. - * @param {String} str + * Test whether the ip is RFC 1918. + * @param {Buffer} raw * @returns {Boolean} */ -IP.isPrivate = function isPrivate(str) { - assert(typeof str === 'string'); +IP.isRFC1918 = function isRFC1918(raw) { + if (!IP.isIPv4(raw)) + return false; - return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(str) - || /^(::f{4}:)?192\.168\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(str) - || /^(::f{4}:)?172\.(1[6-9]|2\d|30|31)\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(str) - || /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(str) - || /^(::f{4}:)?169\.254\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(str) - || /^f[cd][0-9a-f]{2}:/i.test(str) - || /^fe80:/i.test(str) - || /^::1$/.test(str) - || /^::$/.test(str); + if (raw[12] === 10) + return true; + + if (raw[12] === 192 && raw[13] === 168) + return true; + + if (raw[12] === 172 && (raw[13] >= 16 && raw[13] <= 31)) + return true; + + return false; }; /** - * Test whether a string is a public address. - * @param {String} str + * Test whether the ip is RFC 2544. + * @param {Buffer} raw * @returns {Boolean} */ -IP.isPublic = function isPublic(str) { - return !IP.isPrivate(str); +IP.isRFC2544 = function isRFC2544(raw) { + if (!IP.isIPv4(raw)) + return false; + + if (raw[12] === 198 && (raw[13] === 18 || raw[13] === 19)) + return true; + + if (raw[12] === 169 && raw[13] === 254) + return true; + + return false; }; /** - * Test whether a string is a loopback address. - * @param {String} str + * Test whether the ip is RFC 3927. + * @param {Buffer} raw * @returns {Boolean} */ -IP.isLoopback = function isLoopback(str) { - assert(typeof str === 'string'); +IP.isRFC3927 = function isRFC3927(raw) { + if (!IP.isIPv4(raw)) + return false; - return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/.test(str) - || /^fe80::1$/.test(str) - || /^::1$/.test(str) - || /^::$/.test(str); + if (raw[12] === 169 && raw[13] === 254) + return true; + + return false; }; /** - * Get loopback address for ip family. - * @param {String} family - ipv4 or ipv6. - * @returns {String} + * Test whether the ip is RFC 6598. + * @param {Buffer} raw + * @returns {Boolean} */ -IP.loopback = function loopback(family) { - if (!family) - family = 'ipv4'; +IP.isRFC6598 = function isRFC6598(raw) { + if (!IP.isIPv4(raw)) + return false; - family = family.toLowerCase(); + if (raw[12] === 100 + && (raw[13] >= 64 && raw[13] <= 127)) { + return true; + } - if (family !== 'ipv4' && family !== 'ipv6') - throw new Error('Family must be ipv4 or ipv6.'); - - return family === 'ipv4' ? '127.0.0.1' : 'fe80::1'; + return false; }; -/* - * Helpers +/** + * Test whether the ip is RFC 5737. + * @param {Buffer} raw + * @returns {Boolean} */ -function Address(host, port, version, hostname) { +IP.isRFC5737 = function isRFC5737(raw) { + if (!IP.isIPv4(raw)) + return false; + + if (raw[12] === 192 + && (raw[13] === 0 && raw[14] === 2)) { + return true; + } + + if (raw[12] === 198 && raw[13] === 51 && raw[14] === 100) + return true; + + if (raw[12] === 203 && raw[13] === 0 && raw[14] === 113) + return true; + + return false; +}; + +/** + * Test whether the ip is RFC 3849. + * @param {Buffer} raw + * @returns {Boolean} + */ + +IP.isRFC3849 = function isRFC3849(raw) { + if (raw[0] === 0x20 && raw[1] === 0x01 + && raw[2] === 0x0d && raw[3] === 0xb8) { + return true; + } + + return false; +}; + +/** + * Test whether the ip is RFC 3964. + * @param {Buffer} raw + * @returns {Boolean} + */ + +IP.isRFC3964 = function isRFC3964(raw) { + if (raw[0] === 0x20 && raw[1] === 0x02) + return true; + + return false; +}; + +/** + * Test whether the ip is RFC 6052. + * @param {Buffer} raw + * @returns {Boolean} + */ + +IP.isRFC6052 = function isRFC6052(raw) { + return IP.hasPrefix(raw, RFC6052); +}; + +/** + * Test whether the ip is RFC 4380. + * @param {Buffer} raw + * @returns {Boolean} + */ + +IP.isRFC4380 = function isRFC4380(raw) { + if (raw[0] === 0x20 && raw[1] === 0x01 + && raw[2] === 0x00 && raw[3] === 0x00) { + return true; + } + + return false; +}; + +/** + * Test whether the ip is RFC 4862. + * @param {Buffer} raw + * @returns {Boolean} + */ + +IP.isRFC4862 = function isRFC4862(raw) { + return IP.hasPrefix(raw, RFC4862); +}; + +/** + * Test whether the ip is RFC 4193. + * @param {Buffer} raw + * @returns {Boolean} + */ + +IP.isRFC4193 = function isRFC4193(raw) { + if ((raw[0] & 0xfe) === 0xfc) + return true; + + return false; +}; + +/** + * Test whether the ip is RFC 6145. + * @param {Buffer} raw + * @returns {Boolean} + */ + +IP.isRFC6145 = function isRFC6145(raw) { + return IP.hasPrefix(raw, RFC6145); +}; + +/** + * Test whether the ip is RFC 4843. + * @param {Buffer} raw + * @returns {Boolean} + */ + +IP.isRFC4843 = function isRFC4843(raw) { + if (raw[0] === 0x20 && raw[1] === 0x01 + && raw[2] === 0x00 && (raw[3] & 0xf0) === 0x10) { + return true; + } + + return false; +}; + +/** + * Test whether the ip has a tor onion prefix. + * @param {Buffer} raw + * @returns {Boolean} + */ + +IP.isTor = function isTor(raw) { + return IP.hasPrefix(raw, TOR_ONION); +}; + +/** + * Test whether the ip is local. + * @param {Buffer} raw + * @returns {Boolean} + */ + +IP.isLocal = function isLocal(raw) { + if (IP.isIPv4(raw)) { + if (raw[12] === 127 && raw[13] === 0) + return true; + return false; + } + + if (IP.isEqual(raw, LOCAL_IP)) + return true; + + return false; +}; + +/** + * Test whether the ip is a multicast address. + * @param {Buffer} raw + * @returns {Boolean} + */ + +IP.isMulticast = function isMulticast(raw) { + if (IP.isIPv4(raw)) { + if ((raw[12] & 0xf0) === 0xe0) + return true; + return false; + } + return raw[0] === 0xff; +}; + +/** + * Test whether the ip is valid. + * @param {Buffer} raw + * @returns {Boolean} + */ + +IP.isValid = function isValid(raw) { + if (IP.hasPrefix(raw, SHIFTED)) + return false; + + if (IP.isNull(raw)) + return false; + + if (IP.isBroadcast(raw)) + return false; + + if (IP.isRFC3849(raw)) + return false; + + return true; +}; + +/** + * Test whether the ip is routable. + * @param {Buffer} raw + * @returns {Boolean} + */ + +IP.isRoutable = function isRoutable(raw) { + if (!IP.isValid(raw)) + return false; + + if (IP.isRFC1918(raw)) + return false; + + if (IP.isRFC2544(raw)) + return false; + + if (IP.isRFC3927(raw)) + return false; + + if (IP.isRFC4862(raw)) + return false; + + if (IP.isRFC6598(raw)) + return false; + + if (IP.isRFC5737(raw)) + return false; + + if (IP.isRFC4193(raw) && !IP.isTor(raw)) + return false; + + if (IP.isRFC4843(raw)) + return false; + + if (IP.isLocal(raw)) + return false; + + return true; +}; + +/** + * Test whether an IP has a prefix. + * @param {Buffer} raw + * @param {Buffer} prefix + * @returns {Boolean} + */ + +IP.hasPrefix = function hasPrefix(raw, prefix) { + var i; + + assert(Buffer.isBuffer(raw)); + assert(Buffer.isBuffer(prefix)); + assert(raw.length >= prefix.length); + + for (i = 0; i < prefix.length; i++) { + if (raw[i] !== prefix[i]) + return false; + } + + return true; +}; + +/** + * Test whether two IPs are equal. + * @param {Buffer} a + * @param {Buffer} b + * @returns {Boolean} + */ + +IP.isEqual = function isEqual(a, b) { + var i; + + assert(a.length === 16); + assert(b.length === 16); + + if (a.compare) + return a.compare(b) === 0; + + for (i = 0; i < a.length; i++) { + if (a[i] !== b[i]) + return false; + } + + return true; +}; + +/** + * Represents a parsed address. + * @param {String} host + * @param {Number} port + * @param {Number} version + * @param {String} hostname + * @param {Buffer} raw + */ + +function Address(host, port, version, hostname, raw) { this.host = host; this.port = port; this.version = version; this.hostname = hostname; + this.raw = raw || ZERO_IP; }