/// import utils = require('./index'); /* Common IP functions */ // Return whether the current request is from the local server. export function localHost(req): boolean { return req.connection.remoteAddress.substring(0, 8) == "127.0.0." || req.connection.remoteAddress == "::1"; } // Whether an IP address is IPV6. export function isIPV6(address: string): boolean { if (!address) { return false; } return address.indexOf(':') > -1; } // Return the I.P. address of the request (checking for proxy server forwarding). export function remoteAddress(req): string { // The X-Forwarded-For header is used to identify the originating IP address when a hit is passed via a proxy server. // This is easily faked and cannot be considered authoritative except for trusted proxies, like the Opera Mini proxy. In addition, this // header often contains intranet IP addresses, like 10.1.1.1. Therefore, only use it in special cases. if (req.headers['x-forwarded-for']) { var xf: string = req.headers['x-forwarded-for'].trim(); var xfs: Array = null; // see if multiple addresses are in the XFF header if (xf.indexOf(",") > -1) { xfs = xf.split(','); } else { if (xf.indexOf(" ") > -1) { xfs = xf.split(' '); } } if (xfs != null) { // get first public address, since multiple private routings can occur and be added to forwarded list for (var i = 0; i < xfs.length; i++) { var ipTrim: string = xfs[i].trim(); if (ipTrim != "" && ipTrim.substring(0, 3) != "10." && ipTrim.substring(0, 8) != "127.0.0." && ipTrim.substring(0, 8) != "192.168." && ipTrim != "unknown" && ipTrim != "::1") { return ipTrim; } } xf = xfs[0].trim(); } // a tiny % of hits have an unknown ip address if (xf.substring(0, 7) == "unknown") { return "1.2.3.4"; } return xf; } else { var xf: string = req.connection.remoteAddress; // a tiny % of hits have an unknown ip address, so return a default address if (xf.substring(0, 7) == "unknown") { return "1.2.3.4"; } return xf; } } export function isLoopback(addr) { return /^127\.0\.0\.1/.test(addr) || /^fe80::1/.test(addr) || /^::1/.test(addr); } // Return a 52-bit integer hash from an IPv6 address. This uses a variation of the murmur3 hash algorithm. The collision // rate is small, roughly one in 100,000,000. export function hashIPV6(ip: string): number { var hash = 0, len: number; for (var i = 0, len = ip.length; i < len; i++) { hash = ((hash << 5) - hash) + ip.charCodeAt(i); hash |= 0; // Convert to 32-bit integer } return hash; } // Convert an IP address to a shardable number. export function hash(address: string): number { if (isIPV6(address)) { // hash an IPv6 address to 4 bytes - this is because IPv6 addresses are too big for effective indexing // and it is acceptably close to uniqueness with the hash return hashIPV6(address); } else { var _ip = address.split('.'); // reverse the order of bytes of the IP address for better shard distribution return Number(_ip[0]) * 16777216 + Number(_ip[1]) * 65536 + Number(_ip[2]) * 256 + Number(_ip[3]); } } // Convert an IP address to a number. export function toNumber(address: string, reverse: boolean = false): number { if (isIPV6(address)) { // hash an IPv6 address to 4 bytes - this is because IPv6 addresses are too big for effective indexing // and it is acceptably close to uniqueness with the hash return toNumberIPV6(address); } else { var _ip = address.split('.'); // create a number from the octets if (reverse) return Number(_ip[3]) * 16777216 + Number(_ip[2]) * 65536 + Number(_ip[1]) * 256 + Number(_ip[0]); else return Number(_ip[0]) * 16777216 + Number(_ip[1]) * 65536 + Number(_ip[2]) * 256 + Number(_ip[3]); } } // Format an IP address to a storable format. // If IPv4: the ip address converted to a 32-bit integer // If IPv6: the ip address converted to a 16 byte binary export function compress(address: string): any { if (!isIPV6(address)) { return toNumber32(address); } else { var parts = address.split(':'); var bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; for (var i = 0; i < parts.length; i++) { // When the :: is hit, start filling from the end of the array if (parts[i] == '') { break; } else { parts[i] = ('000' + parts[i]).slice(-4); // deal with missing leading zeroes bytes[i * 2] = parseInt(parts[i].substr(0, 2), 16); bytes[i * 2 + 1] = parseInt(parts[i].substr(2, 2), 16); } } var index = 15; for (var j = parts.length - 1; j > i; j--) { parts[j] = ('000' + parts[j]).slice(-4); // deal with missing leading zeroes bytes[index - 1] = parseInt(parts[j].substr(0, 2), 16); bytes[index] = parseInt(parts[j].substr(2, 2), 16); index -= 2; } return bytes; } } // Convert an IP address to a 32-bit number. export function toNumber32(address: string): number { if (isIPV6(address)) { // hash an IPv6 address to 4 bytes - this is because IPv6 addresses are too big for effective indexing // and it is acceptably close to uniqueness with the hash return toNumberIPV6(address); } else { var _ip = address.split('.'); // create a number from the octets var num: number = Number(_ip[0]) * 16777216 + Number(_ip[1]) * 65536 + Number(_ip[2]) * 256 + Number(_ip[3]); if (num >= 2147483648) num -= 4294967296; return num; } } // Convert an IP number (as created by the toNumber function) to an IP address export function fromNumber(ipNumber: number): string { var addr = utils.toBytes(ipNumber); return addr[3] + '.' + addr[2] + '.' + addr[1] + '.' + addr[0]; } // Convert an IP address to a number. export function toNumberIPV6(address: string): number { // need to come up with an algorithm to convert ipv6 address to rangable numbers return 100; }