add whois util

This commit is contained in:
HF 2022-08-01 17:25:37 +02:00
parent b355aa60d4
commit 4dee1e24e4
2 changed files with 212 additions and 4 deletions

View File

@ -5,7 +5,51 @@
import { USE_XREALIP } from '../core/config';
/*
* Parse ip4 string to 32bit integer
* @param ipString ip string
* @return ipNum numerical ip
*/
function ip4ToNum(ipString) {
if (!ipString) {
return null;
}
const ipArr = ipString
.trim()
.split('.')
.map((numString) => parseInt(numString, 10));
if (ipArr.length !== 4 || ipArr.some(
(num) => Number.isNaN(num) || num > 255 || num < 0,
)) {
return null;
}
const ipNum = (ipArr[0] << 24)
+ (ipArr[1] << 16)
+ (ipArr[2] << 8)
+ ipArr[3];
return ipNum;
}
/*
* Parse ip4 number to string representation
* @param ipNum numerical ip (32bit integer)
* @return ipString string representation of ip (xxx.xxx.xxx.xxx)
*/
function ip4NumToStr(ipNum) {
return [
ipNum >>> 24,
ipNum >>> 16 & 0xFF,
ipNum >>> 8 & 0xFF,
ipNum & 0xFF,
].join('.');
}
/*
* Get hostname from request
* @param req express req object
* @param includeProto if we include protocol (https, http)
* @return host (like pixelplanet.fun)
*/
export function getHostFromRequest(req, includeProto = true) {
const { headers } = req;
const host = headers['x-forwarded-host']
@ -19,6 +63,11 @@ export function getHostFromRequest(req, includeProto = true) {
return `${proto}://${host}`;
}
/*
* Get IP from request
* @param req express req object
* @return ip as string
*/
export function getIPFromRequest(req) {
if (USE_XREALIP) {
const ip = req.headers['x-real-ip'];
@ -28,25 +77,126 @@ export function getIPFromRequest(req) {
}
const { socket, connection } = req;
let conip = (connection ? connection.remoteAddress : socket.remoteAddress);
conip = conip || '0.0.0.1';
if (!USE_XREALIP) {
// eslint-disable-next-line no-console
console.warn(
`Connection not going through reverse proxy! IP: ${conip}`, req.headers,
);
}
return conip;
}
/*
* Check if IP is v6 or v4
* @param ip ip as string
* @return true if ipv6, false otherwise
*/
export function isIPv6(ip) {
return ip.includes(':');
}
/*
* Set last digits of IPv6 to zero,
* needed because IPv6 assignes subnets to customers, we don't want to
* mess with individual ips
* @param ip ip as string (v4 or v6)
* @return ip as string, and if v6, the last digits set to 0
*/
export function getIPv6Subnet(ip) {
if (ip.includes(':')) {
if (isIPv6(ip)) {
// eslint-disable-next-line max-len
const ipv6sub = `${ip.split(':').slice(0, 4).join(':')}:0000:0000:0000:0000`;
return ipv6sub;
}
return ip;
}
/*
* Get numerical start and end of range
* @param range sring of range in the format 'xxx.xxx.xxx.xxx - xxx.xxx.xxx.xxx'
* @return [start, end] with numerical IPs (32bit integer)
*/
function ip4RangeStrToRangeNum(range) {
const [start, end] = range.split('-')
.map(ip4ToNum);
if (!start || !end || start > end) {
return null;
}
return [start, end];
}
/*
* Get Array of CIDRs for an numerical IPv4 range
* @param [start, end] with numerical IPs (32bit integer)
* @return Array of CIDR strings
*/
function ip4RangeNumToCIDR([start, end]) {
let maskNum = 32;
let mask = 0xFFFFFFFF;
const diff = start ^ end;
while (diff & mask) {
mask <<= 1;
maskNum -= 1;
}
if ((start & (~mask)) || (~(end | mask))) {
const divider = start | (~mask >> 1);
return ip4RangeNumToCIDR([start, divider]).concat(
ip4RangeNumToCIDR([divider + 1, end]),
);
}
return [`${ip4NumToStr(start)}/${maskNum}`];
}
/*
* Get Array of CIDRs for an IPv4 range
* @param range sring of range in the format 'xxx.xxx.xxx.xxx - xxx.xxx.xxx.xxx'
* @return Array of CIDR strings
*/
export function ip4RangeToCIDR(range) {
const rangeNum = ip4RangeStrToRangeNum(range);
if (!rangeNum) {
return null;
}
return ip4RangeNumToCIDR(rangeNum);
}
/*
* Get specific CIDR in numeric range that includes numeric ip
* @param ip numerical ip (32bit integer)
* @param [start, end] with numerical IPs (32bit integer)
* @return CIDR string
*/
function ip4NumInRangeNumToCIDR(ip, [start, end]) {
let maskNum = 32;
let mask = 0xFFFFFFFF;
const diff = start ^ end;
while (diff & mask) {
mask <<= 1;
maskNum -= 1;
}
if ((start & (~mask)) || (~(end | mask))) {
const divider = start | (~mask >> 1);
if (ip <= divider) {
return ip4NumInRangeNumToCIDR(ip, [start, divider]);
}
return ip4NumInRangeNumToCIDR(ip, [divider + 1, end]);
}
return `${ip4NumToStr(start)}/${maskNum}`;
}
/*
* Get specific CIDR in range that includes ip
* @param ip ip string ('xxx.xxx.xxx.xxx')
* @param range string ('xxx.xxx.xxx.xxx - xxx.xxx.xxx.xxx')
* @return CIDR string
*/
export function ip4InRangeToCIDR(ip, range) {
const rangeNum = ip4RangeStrToRangeNum(range);
const ipNum = ip4ToNum(ip);
if (!ipNum || !rangeNum || rangeNum[0] > ip || rangeNum[1] < ip) {
return null;
}
return ip4NumInRangeNumToCIDR(ipNum, rangeNum);
}

58
src/utils/whois.js Normal file
View File

@ -0,0 +1,58 @@
/*
* get information from ip
*/
import whoiser from 'whoiser';
import { isIPv6, ip4InRangeToCIDR } from './ip';
/*
* get CIDR of ip from whois return
* @param ip ip string
* @param whois whois return
* @return cidr string
*/
function cIDRofWhois(ip, whoisData) {
if (isIPv6(ip)) {
return whoisData.inet6num || 'N/A';
}
return ip4InRangeToCIDR(ip, whoisData.range) || 'N/A';
}
/*
* get organisation from whois return
* @param whois whois return
* @return organisation string
*/
function orgFromWhois(whoisData) {
return (whoisData.organisation && whoisData.organisation['org-name'])
|| (whoisData['Contact Master']
&& whoisData['Contact Master'].address.split('\n')[0])
|| whoisData.netname
|| 'N/A';
}
/*
* parse whois return
* @param ip ip string
* @param whois whois return
* @return object with whois data
*/
function parseWhois(ip, whoisData) {
return {
ip,
country: whoisData.country || 'N/A',
cidr: cIDRofWhois(ip, whoisData),
org: orgFromWhois(whoisData),
descr: whoisData.descr || 'N/A',
asn: whoisData.asn || 'N/A',
};
}
async function whois(ip) {
const whoisData = await whoiser.ip(ip);
return parseWhois(ip, whoisData);
}
export default whois;