fcoin/lib/utils/ip.js
2017-01-16 14:42:36 -08:00

510 lines
9.9 KiB
JavaScript

/*!
* ip.js - ip utils for bcoin
* Copyright (c) 2014-2015, Fedor Indutny (MIT License).
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*
* Parts of this software are based on node-ip.
* https://github.com/indutny/node-ip
* Copyright (c) 2012, Fedor Indutny (MIT License).
*/
'use strict';
var assert = require('assert');
var IP = exports;
/*
* Constants
*/
var ipv4Regex = /^(\d{1,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;
/**
* Parse a hostname.
* @param {String} addr
* @param {Number?} fallback - Fallback port.
* @returns {Object} Contains `host`, `port`, and `version`.
*/
IP.parseHost = function parseHost(addr, fallback) {
var parts, host, port, version, hostname;
assert(typeof addr === 'string');
assert(addr.length > 0, 'Bad address.');
if (addr[0] === '[') {
if (addr[addr.length - 1] === ']') {
// Case:
// [::1]
host = addr.slice(1, -1);
port = null;
} else {
// Case:
// [::1]:80
addr = addr.slice(1);
parts = addr.split(']:');
assert(parts.length === 2, 'Bad IPv6 address.');
host = parts[0];
port = parts[1];
}
} else {
parts = addr.split(':');
switch (parts.length) {
case 2:
// Cases:
// 127.0.0.1:80
// localhost:80
host = parts[0];
port = parts[1];
break;
case 1:
// Cases:
// 127.0.0.1
// localhost
host = parts[0];
port = null;
break;
default:
// Case:
// ::1
assert(IP.isV6Format(addr), 'Bad IPv6 address.');
host = addr;
port = null;
break;
}
}
assert(host.length > 0, 'Bad host.');
if (port != null) {
assert(port.length <= 5, 'Bad port.');
assert(/^\d+$/.test(port), 'Bad port.');
port = parseInt(port, 10);
assert(port <= 0xffff);
} else {
port = fallback || 0;
}
version = IP.version(host);
if (version !== -1)
host = IP.normalize(host);
hostname = host;
if (version === 6)
hostname = '[' + hostname + ']';
hostname += ':' + port;
return new Address(host, port, version, hostname);
};
/**
* Concatenate a host and port.
* @param {String} host
* @param {Number} port
* @returns {String}
*/
IP.hostname = function hostname(host, port) {
var version;
assert(typeof host === 'string');
assert(host.length > 0);
assert(typeof port === 'number');
assert(port >= 0 && port <= 0xffff);
assert(!/[\[\]]/.test(host), 'Bad host.');
version = IP.version(host);
if (host.indexOf(':') !== -1)
assert(version === 6, 'Bad host.');
if (version !== -1)
host = IP.normalize(host);
if (version === 6)
host = '[' + host + ']';
return host + ':' + port;
};
/**
* Test whether a string is an IP address.
* @param {String?} str
* @returns {Number} IP version (4 or 6).
*/
IP.version = function version(str) {
if (IP.isV4Format(str))
return 4;
if (IP.isV6Format(str))
return 6;
return -1;
};
/**
* Test whether a string is IPv4.
* @param {String?} str
* @returns {Boolean}
*/
IP.isV4Format = function isV4Format(str) {
assert(typeof str === 'string');
if (str.length < 7)
return false;
if (str.length > 15)
return false;
return ipv4Regex.test(str);
};
/**
* Test whether a string is IPv6.
* @param {String?} str
* @returns {Boolean}
*/
IP.isV6Format = function isV6Format(str) {
assert(typeof str === 'string');
if (str.length < 2)
return false;
if (str.length > 39)
return false;
return ipv6Regex.test(str);
};
/**
* Test whether a buffer is an ipv4-mapped ipv6 address.
* @param {Buffer} buf
* @returns {Boolean}
*/
IP.isMapped = function isMapped(buf) {
var i;
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;
};
/**
* Convert an IP string to a buffer.
* @param {String} str
* @returns {Buffer}
*/
IP.toBuffer = function toBuffer(str) {
var buf = 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);
}
if (IP.isV6Format(str))
return IP.parseV6(str, buf, 0);
throw Error('Invalid IP address: ' + str);
};
/**
* Convert an IPv4 string to a buffer.
* @private
* @param {String} str
* @param {Buffer} buf
* @param {Number} offset
* @returns {Buffer}
*/
IP.parseV4 = function parseV4(str, buf, offset) {
var parts = str.split('.');
var i, ch;
assert(parts.length === 4);
for (i = 0; i < parts.length; i++) {
ch = parts[i];
assert(ch.length > 0);
assert(ch.length <= 3);
ch = parseInt(ch, 10);
assert(ch >= 0 && ch <= 255);
buf[offset++] = ch;
}
return buf;
};
/**
* Convert an IPv6 string to a buffer.
* @private
* @param {String} str
* @param {Buffer} buf
* @param {Number} offset
* @returns {Buffer}
*/
IP.parseV6 = function parseV6(str, buf, 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++) {
word = parts[i];
if (IP.isV4Format(word))
missing--;
}
assert(missing >= 0, 'IPv6 address is too long.');
for (i = 0; i < parts.length; i++) {
word = parts[i];
if (word.length === 0) {
assert(!colon, 'Overuse of double colon in IPv6 address.');
colon = true;
missing += 1;
// Eat extra colons.
// e.g. :::0
while (i + 1 < parts.length) {
word = parts[i + 1];
if (word.length !== 0)
break;
missing += 1;
i++;
}
while (missing > 0) {
buf[offset++] = 0;
buf[offset++] = 0;
missing--;
}
continue;
}
if (IP.isV4Format(word)) {
IP.parseV4(word, buf, offset);
offset += 4;
continue;
}
assert(word.length <= 4);
word = parseInt(word, 16);
assert(word === word, 'Non-number in IPv6 address.');
buf[offset++] = (word >> 8) & 0xff;
buf[offset++] = word & 0xff;
}
assert(missing === 0, 'IPv6 address has missing sections.');
assert.equal(offset, start + 16);
return buf;
};
/**
* Convert a buffer to an ip string.
* @param {Buffer} buf
* @returns {String}
*/
IP.toString = function toString(buf) {
var str = '';
var i;
assert(Buffer.isBuffer(buf));
if (buf.length === 4) {
str += buf[0];
str += '.' + buf[1];
str += '.' + buf[2];
str += '.' + buf[3];
return str;
}
if (buf.length === 16) {
if (IP.isMapped(buf)) {
str += buf[12];
str += '.' + buf[13];
str += '.' + buf[14];
str += '.' + buf[15];
return str;
}
str += buf.readUInt16BE(0, true).toString(16);
for (i = 2; i < 16; i += 2) {
str += ':';
str += buf.readUInt16BE(i, true).toString(16);
}
str = str.replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3');
str = str.replace(/:{3,4}/, '::');
return str;
}
throw Error('Invalid IP address: ' + buf.toString('hex'));
};
/**
* Normalize an ip.
* @param {String} str
* @returns {String}
*/
IP.normalize = function normalize(str) {
return IP.toString(IP.toBuffer(str));
};
/**
* Test whether an IP is null.
* @param {String} str - Normalized ip.
* @returns {Boolean}
*/
IP.isNull = function isNull(str) {
return str === '0.0.0.0' || str === '::';
};
/**
* Test whether the IP is a broadcast address.
* @param {String} str - Normalized ip.
* @returns {Boolean}
*/
IP.isBroadcast = function isBroadcast(str) {
return str === '255.255.255.255'
|| str === '::ffff:ffff:ffff';
};
/**
* Test whether the IP is valid.
* @param {String} str - Normalized ip.
* @returns {Boolean}
*/
IP.isValid = function isValid(str) {
return !IP.isNull(str) && !IP.isBroadcast(str);
};
/**
* Test whether the IP is routable.
* @param {String} str - Normalized ip.
* @returns {Boolean}
*/
IP.isRoutable = function isRoutable(str) {
return IP.isValid(str) && !IP.isLoopback(str);
};
/**
* Test whether a string is a private address.
* @param {String} str
* @returns {Boolean}
*/
IP.isPrivate = function isPrivate(str) {
assert(typeof str === 'string');
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);
};
/**
* Test whether a string is a public address.
* @param {String} str
* @returns {Boolean}
*/
IP.isPublic = function isPublic(str) {
return !IP.isPrivate(str);
};
/**
* Test whether a string is a loopback address.
* @param {String} str
* @returns {Boolean}
*/
IP.isLoopback = function isLoopback(str) {
assert(typeof str === 'string');
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);
};
/**
* Get loopback address for ip family.
* @param {String} family - ipv4 or ipv6.
* @returns {String}
*/
IP.loopback = function loopback(family) {
if (!family)
family = 'ipv4';
family = family.toLowerCase();
if (family !== 'ipv4' && family !== 'ipv6')
throw new Error('Family must be ipv4 or ipv6.');
return family === 'ipv4' ? '127.0.0.1' : 'fe80::1';
};
/*
* Helpers
*/
function Address(host, port, version, hostname) {
this.host = host;
this.port = port;
this.version = version;
this.hostname = hostname;
}