630 lines
20 KiB
JavaScript
630 lines
20 KiB
JavaScript
"use strict";
|
|
var __create = Object.create;
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __getProtoOf = Object.getPrototypeOf;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
// If the importer is in node compatibility mode or this is not an ESM
|
|
// file that has been converted to a CommonJS file using a Babel-
|
|
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
mod
|
|
));
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
var ip_tools_exports = {};
|
|
__export(ip_tools_exports, {
|
|
IPTools: () => IPTools,
|
|
default: () => ip_tools_default
|
|
});
|
|
module.exports = __toCommonJS(ip_tools_exports);
|
|
var dns = __toESM(require("dns"));
|
|
var import_lib = require("../lib");
|
|
/**
|
|
* IP Tools
|
|
* Pokemon Showdown - http://pokemonshowdown.com/
|
|
*
|
|
* IPTools file has various tools for IP parsing and IP-based blocking.
|
|
*
|
|
* These include DNSBLs: DNS-based blackhole lists, which list IPs known for
|
|
* running proxies, spamming, or other abuse.
|
|
*
|
|
* We also maintain our own database of datacenter IP ranges (usually
|
|
* proxies). These are taken from https://github.com/client9/ipcat
|
|
* but include our own database as well.
|
|
*
|
|
* @license MIT
|
|
*/
|
|
const BLOCKLISTS = ["sbl.spamhaus.org", "rbl.efnetrbl.org"];
|
|
const HOSTS_FILE = "config/hosts.csv";
|
|
const PROXIES_FILE = "config/proxies.csv";
|
|
function removeNohost(hostname) {
|
|
if (hostname?.includes("-nohost")) {
|
|
const parts = hostname.split(".");
|
|
const suffix = parts.pop();
|
|
return `${parts.join(".")}?/${suffix?.replace("-nohost", "")}`;
|
|
}
|
|
return hostname;
|
|
}
|
|
const IPTools = new class {
|
|
constructor() {
|
|
this.dnsblCache = /* @__PURE__ */ new Map([
|
|
["127.0.0.1", null]
|
|
]);
|
|
this.connectionTestCache = /* @__PURE__ */ new Map();
|
|
this.ipRegex = /^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$/;
|
|
this.ipRangeRegex = /^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]|\*)){0,2}\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]|\*)$/;
|
|
this.hostRegex = /^.+\..{2,}$/;
|
|
/**
|
|
* Proxy and host management functions
|
|
*/
|
|
this.ranges = [];
|
|
this.singleIPOpenProxies = /* @__PURE__ */ new Set();
|
|
this.torProxyIps = /* @__PURE__ */ new Set();
|
|
this.proxyHosts = /* @__PURE__ */ new Set();
|
|
this.residentialHosts = /* @__PURE__ */ new Set();
|
|
this.mobileHosts = /* @__PURE__ */ new Set();
|
|
}
|
|
async lookup(ip) {
|
|
const [dnsbl, host] = await Promise.all([
|
|
IPTools.queryDnsbl(ip),
|
|
IPTools.getHost(ip)
|
|
]);
|
|
const shortHost = this.shortenHost(host);
|
|
const hostType = this.getHostType(shortHost, ip);
|
|
return { dnsbl, host, shortHost, hostType };
|
|
}
|
|
queryDnsblLoop(ip, callback, reversedIpDot, index) {
|
|
if (index >= BLOCKLISTS.length) {
|
|
IPTools.dnsblCache.set(ip, null);
|
|
callback(null);
|
|
return;
|
|
}
|
|
const blocklist = BLOCKLISTS[index];
|
|
dns.lookup(reversedIpDot + blocklist, 4, (err, res) => {
|
|
if (!err) {
|
|
IPTools.dnsblCache.set(ip, blocklist);
|
|
callback(blocklist);
|
|
return;
|
|
}
|
|
IPTools.queryDnsblLoop(ip, callback, reversedIpDot, index + 1);
|
|
});
|
|
}
|
|
/**
|
|
* IPTools.queryDnsbl(ip, callback)
|
|
*
|
|
* Calls callb
|
|
* ack(blocklist), where blocklist is the blocklist domain
|
|
* if the passed IP is in a blocklist, or null if the IP is not in
|
|
* any blocklist.
|
|
*
|
|
* Return value matches isBlocked when treated as a boolean.
|
|
*/
|
|
queryDnsbl(ip) {
|
|
if (!Config.dnsbl)
|
|
return Promise.resolve(null);
|
|
if (IPTools.dnsblCache.has(ip)) {
|
|
return Promise.resolve(IPTools.dnsblCache.get(ip) || null);
|
|
}
|
|
const reversedIpDot = ip.split(".").reverse().join(".") + ".";
|
|
return new Promise((resolve, reject) => {
|
|
IPTools.queryDnsblLoop(ip, resolve, reversedIpDot, 0);
|
|
});
|
|
}
|
|
/*********************************************************
|
|
* IP parsing
|
|
*********************************************************/
|
|
ipToNumber(ip) {
|
|
ip = ip.trim();
|
|
if (ip.includes(":") && !ip.includes(".")) {
|
|
return null;
|
|
}
|
|
if (ip.startsWith("::ffff:"))
|
|
ip = ip.slice(7);
|
|
else if (ip.startsWith("::"))
|
|
ip = ip.slice(2);
|
|
let num = 0;
|
|
const parts = ip.split(".");
|
|
if (parts.length !== 4)
|
|
return null;
|
|
for (const part of parts) {
|
|
num *= 256;
|
|
const partAsInt = import_lib.Utils.parseExactInt(part);
|
|
if (isNaN(partAsInt) || partAsInt < 0 || partAsInt > 255)
|
|
return null;
|
|
num += partAsInt;
|
|
}
|
|
return num;
|
|
}
|
|
numberToIP(num) {
|
|
const ipParts = [];
|
|
if (num < 0 || num >= 256 ** 4 || num !== Math.trunc(num))
|
|
return null;
|
|
while (num) {
|
|
const part = num % 256;
|
|
num = (num - part) / 256;
|
|
ipParts.unshift(part.toString());
|
|
}
|
|
while (ipParts.length < 4)
|
|
ipParts.unshift("0");
|
|
if (ipParts.length !== 4)
|
|
return null;
|
|
return ipParts.join(".");
|
|
}
|
|
getCidrRange(cidr) {
|
|
if (!cidr)
|
|
return null;
|
|
const index = cidr.indexOf("/");
|
|
if (index <= 0) {
|
|
const ip = IPTools.ipToNumber(cidr);
|
|
if (ip === null)
|
|
return null;
|
|
return { minIP: ip, maxIP: ip };
|
|
}
|
|
const low = IPTools.ipToNumber(cidr.slice(0, index));
|
|
const bits = import_lib.Utils.parseExactInt(cidr.slice(index + 1));
|
|
if (low === null || !bits || bits < 2 || bits > 32)
|
|
return null;
|
|
const high = low + (1 << 32 - bits) - 1;
|
|
return { minIP: low, maxIP: high };
|
|
}
|
|
/** Is this an IP range supported by `stringToRange`? Note that exact IPs are also valid IP ranges. */
|
|
isValidRange(range) {
|
|
return IPTools.stringToRange(range) !== null;
|
|
}
|
|
stringToRange(range) {
|
|
if (!range)
|
|
return null;
|
|
if (range.endsWith("*")) {
|
|
const parts = range.replace(".*", "").split(".");
|
|
if (parts.length > 3)
|
|
return null;
|
|
const [a, b, c] = parts;
|
|
const minIP2 = IPTools.ipToNumber(`${a || "0"}.${b || "0"}.${c || "0"}.0`);
|
|
const maxIP2 = IPTools.ipToNumber(`${a || "255"}.${b || "255"}.${c || "255"}.255`);
|
|
if (minIP2 === null || maxIP2 === null)
|
|
return null;
|
|
return { minIP: minIP2, maxIP: maxIP2 };
|
|
}
|
|
const index = range.indexOf("-");
|
|
if (index <= 0) {
|
|
if (range.includes("/"))
|
|
return IPTools.getCidrRange(range);
|
|
const ip = IPTools.ipToNumber(range);
|
|
if (ip === null)
|
|
return null;
|
|
return { maxIP: ip, minIP: ip };
|
|
}
|
|
const minIP = IPTools.ipToNumber(range.slice(0, index));
|
|
const maxIP = IPTools.ipToNumber(range.slice(index + 1));
|
|
if (minIP === null || maxIP === null || maxIP < minIP)
|
|
return null;
|
|
return { minIP, maxIP };
|
|
}
|
|
rangeToString(range, sep = "-") {
|
|
return `${this.numberToIP(range.minIP)}${sep}${this.numberToIP(range.maxIP)}`;
|
|
}
|
|
/******************************
|
|
* Range management functions *
|
|
******************************/
|
|
checkPattern(patterns, num) {
|
|
if (num === null)
|
|
return false;
|
|
for (const pattern of patterns) {
|
|
if (num >= pattern.minIP && num <= pattern.maxIP) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Returns a checker function for the passed IP range or array of
|
|
* ranges. The checker function returns true if its passed IP is
|
|
* in the range.
|
|
*/
|
|
checker(rangeString) {
|
|
if (!rangeString?.length)
|
|
return () => false;
|
|
let ranges = [];
|
|
if (typeof rangeString === "string") {
|
|
const rangePatterns = IPTools.stringToRange(rangeString);
|
|
if (rangePatterns)
|
|
ranges = [rangePatterns];
|
|
} else {
|
|
ranges = rangeString.map(IPTools.stringToRange).filter((x) => x);
|
|
}
|
|
return (ip) => {
|
|
const ipNumber = IPTools.ipToNumber(ip);
|
|
return IPTools.checkPattern(ranges, ipNumber);
|
|
};
|
|
}
|
|
async loadHostsAndRanges() {
|
|
const data = await (0, import_lib.FS)(HOSTS_FILE).readIfExists() + await (0, import_lib.FS)(PROXIES_FILE).readIfExists();
|
|
const rows = data.split("\n").map((row) => row.replace("\r", ""));
|
|
const ranges = [];
|
|
for (const row of rows) {
|
|
if (!row)
|
|
continue;
|
|
let [type, hostOrLowIP, highIP, host] = row.split(",");
|
|
if (!hostOrLowIP)
|
|
continue;
|
|
host = removeNohost(host);
|
|
hostOrLowIP = removeNohost(hostOrLowIP);
|
|
switch (type) {
|
|
case "IP":
|
|
IPTools.singleIPOpenProxies.add(hostOrLowIP);
|
|
break;
|
|
case "HOST":
|
|
IPTools.proxyHosts.add(hostOrLowIP);
|
|
break;
|
|
case "RESIDENTIAL":
|
|
IPTools.residentialHosts.add(hostOrLowIP);
|
|
break;
|
|
case "MOBILE":
|
|
IPTools.mobileHosts.add(hostOrLowIP);
|
|
break;
|
|
case "RANGE":
|
|
if (!host)
|
|
continue;
|
|
const minIP = IPTools.ipToNumber(hostOrLowIP);
|
|
if (minIP === null) {
|
|
Monitor.error(`Bad IP address in host or proxy file: '${hostOrLowIP}'`);
|
|
continue;
|
|
}
|
|
const maxIP = IPTools.ipToNumber(highIP);
|
|
if (maxIP === null) {
|
|
Monitor.error(`Bad IP address in host or proxy file: '${highIP}'`);
|
|
continue;
|
|
}
|
|
const range = { host: IPTools.urlToHost(host), maxIP, minIP };
|
|
if (range.maxIP < range.minIP)
|
|
throw new Error(`Bad range at ${hostOrLowIP}.`);
|
|
ranges.push(range);
|
|
break;
|
|
}
|
|
}
|
|
IPTools.ranges = ranges;
|
|
IPTools.sortRanges();
|
|
}
|
|
saveHostsAndRanges() {
|
|
let hostsData = "";
|
|
let proxiesData = "";
|
|
for (const ip of IPTools.singleIPOpenProxies) {
|
|
proxiesData += `IP,${ip}
|
|
`;
|
|
}
|
|
for (const host of IPTools.proxyHosts) {
|
|
proxiesData += `HOST,${host}
|
|
`;
|
|
}
|
|
for (const host of IPTools.residentialHosts) {
|
|
hostsData += `RESIDENTIAL,${host}
|
|
`;
|
|
}
|
|
for (const host of IPTools.mobileHosts) {
|
|
hostsData += `MOBILE,${host}
|
|
`;
|
|
}
|
|
IPTools.sortRanges();
|
|
for (const range of IPTools.ranges) {
|
|
const data = `RANGE,${IPTools.rangeToString(range, ",")}${range.host ? `,${range.host}` : ``}
|
|
`;
|
|
if (range.host?.endsWith("/proxy")) {
|
|
proxiesData += data;
|
|
} else {
|
|
hostsData += data;
|
|
}
|
|
}
|
|
void (0, import_lib.FS)(HOSTS_FILE).write(hostsData);
|
|
void (0, import_lib.FS)(PROXIES_FILE).write(proxiesData);
|
|
}
|
|
addOpenProxies(ips) {
|
|
for (const ip of ips) {
|
|
IPTools.singleIPOpenProxies.add(ip);
|
|
}
|
|
return IPTools.saveHostsAndRanges();
|
|
}
|
|
addProxyHosts(hosts) {
|
|
for (const host of hosts) {
|
|
IPTools.proxyHosts.add(host);
|
|
}
|
|
return IPTools.saveHostsAndRanges();
|
|
}
|
|
addMobileHosts(hosts) {
|
|
for (const host of hosts) {
|
|
IPTools.mobileHosts.add(host);
|
|
}
|
|
return IPTools.saveHostsAndRanges();
|
|
}
|
|
addResidentialHosts(hosts) {
|
|
for (const host of hosts) {
|
|
IPTools.residentialHosts.add(host);
|
|
}
|
|
return IPTools.saveHostsAndRanges();
|
|
}
|
|
removeOpenProxies(ips) {
|
|
for (const ip of ips) {
|
|
IPTools.singleIPOpenProxies.delete(ip);
|
|
}
|
|
return IPTools.saveHostsAndRanges();
|
|
}
|
|
removeResidentialHosts(hosts) {
|
|
for (const host of hosts) {
|
|
IPTools.residentialHosts.delete(host);
|
|
}
|
|
return IPTools.saveHostsAndRanges();
|
|
}
|
|
removeProxyHosts(hosts) {
|
|
for (const host of hosts) {
|
|
IPTools.proxyHosts.delete(host);
|
|
}
|
|
return IPTools.saveHostsAndRanges();
|
|
}
|
|
removeMobileHosts(hosts) {
|
|
for (const host of hosts) {
|
|
IPTools.mobileHosts.delete(host);
|
|
}
|
|
return IPTools.saveHostsAndRanges();
|
|
}
|
|
rangeIntersects(a, b) {
|
|
try {
|
|
this.checkRangeConflicts(a, [b]);
|
|
} catch {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
checkRangeConflicts(insertion, sortedRanges, widen) {
|
|
if (insertion.maxIP < insertion.minIP) {
|
|
throw new Error(
|
|
`Invalid data for address range ${IPTools.rangeToString(insertion)} (${insertion.host})`
|
|
);
|
|
}
|
|
let iMin = 0;
|
|
let iMax = sortedRanges.length;
|
|
while (iMin < iMax) {
|
|
const i = Math.floor((iMax + iMin) / 2);
|
|
if (insertion.minIP > sortedRanges[i].minIP) {
|
|
iMin = i + 1;
|
|
} else {
|
|
iMax = i;
|
|
}
|
|
}
|
|
if (iMin < sortedRanges.length) {
|
|
const next = sortedRanges[iMin];
|
|
if (insertion.minIP === next.minIP && insertion.maxIP === next.maxIP) {
|
|
throw new Error(`The address range ${IPTools.rangeToString(insertion)} (${insertion.host}) already exists`);
|
|
}
|
|
if (insertion.minIP <= next.minIP && insertion.maxIP >= next.maxIP) {
|
|
if (widen) {
|
|
if (sortedRanges[iMin + 1]?.minIP <= insertion.maxIP) {
|
|
throw new Error("You can only widen one address range at a time.");
|
|
}
|
|
return iMin;
|
|
}
|
|
throw new Error(
|
|
`Too wide: ${IPTools.rangeToString(insertion)} (${insertion.host})
|
|
Intersects with: ${IPTools.rangeToString(next)} (${next.host})`
|
|
);
|
|
}
|
|
if (insertion.maxIP >= next.minIP) {
|
|
throw new Error(
|
|
`Could not insert: ${IPTools.rangeToString(insertion)} ${insertion.host}
|
|
Intersects with: ${IPTools.rangeToString(next)} (${next.host})`
|
|
);
|
|
}
|
|
}
|
|
if (iMin > 0) {
|
|
const prev = sortedRanges[iMin - 1];
|
|
if (insertion.minIP >= prev.minIP && insertion.maxIP <= prev.maxIP) {
|
|
throw new Error(
|
|
`Too narrow: ${IPTools.rangeToString(insertion)} (${insertion.host})
|
|
Intersects with: ${IPTools.rangeToString(prev)} (${prev.host})`
|
|
);
|
|
}
|
|
if (insertion.minIP <= prev.maxIP) {
|
|
throw new Error(
|
|
`Could not insert: ${IPTools.rangeToString(insertion)} (${insertion.host})
|
|
Intersects with: ${IPTools.rangeToString(prev)} (${prev.host})`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
/*********************************************************
|
|
* Range handling functions
|
|
*********************************************************/
|
|
urlToHost(url) {
|
|
if (url.startsWith("http://"))
|
|
url = url.slice(7);
|
|
if (url.startsWith("https://"))
|
|
url = url.slice(8);
|
|
if (url.startsWith("www."))
|
|
url = url.slice(4);
|
|
const slashIndex = url.indexOf("/");
|
|
if (slashIndex > 0 && url[slashIndex - 1] !== "?")
|
|
url = url.slice(0, slashIndex);
|
|
return url;
|
|
}
|
|
sortRanges() {
|
|
import_lib.Utils.sortBy(IPTools.ranges, (range) => range.minIP);
|
|
}
|
|
getRange(minIP, maxIP) {
|
|
for (const range of IPTools.ranges) {
|
|
if (range.minIP === minIP && range.maxIP === maxIP)
|
|
return range;
|
|
}
|
|
}
|
|
addRange(range) {
|
|
if (IPTools.getRange(range.minIP, range.maxIP)) {
|
|
IPTools.removeRange(range.minIP, range.maxIP);
|
|
}
|
|
IPTools.ranges.push(range);
|
|
return IPTools.saveHostsAndRanges();
|
|
}
|
|
removeRange(minIP, maxIP) {
|
|
IPTools.ranges = IPTools.ranges.filter((dc) => dc.minIP !== minIP || dc.maxIP !== maxIP);
|
|
return IPTools.saveHostsAndRanges();
|
|
}
|
|
/**
|
|
* Will not reject; IPs with no RDNS entry will resolve to
|
|
* '[byte1].[byte2]?/unknown'.
|
|
*/
|
|
getHost(ip) {
|
|
return new Promise((resolve) => {
|
|
if (!ip) {
|
|
resolve("");
|
|
return;
|
|
}
|
|
const ipNumber = IPTools.ipToNumber(ip);
|
|
if (ipNumber === null)
|
|
throw new Error(`Bad IP address: '${ip}'`);
|
|
for (const range of IPTools.ranges) {
|
|
if (ipNumber >= range.minIP && ipNumber <= range.maxIP) {
|
|
resolve(range.host);
|
|
return;
|
|
}
|
|
}
|
|
dns.reverse(ip, (err, hosts) => {
|
|
if (err) {
|
|
resolve(`${ip.split(".").slice(0, 2).join(".")}?/unknown`);
|
|
return;
|
|
}
|
|
if (!hosts?.[0]) {
|
|
if (ip.startsWith("50.")) {
|
|
resolve("comcast.net?/res");
|
|
} else if (ipNumber >= telstraRange.minIP && ipNumber <= telstraRange.maxIP) {
|
|
resolve(telstraRange.host);
|
|
} else {
|
|
this.testConnection(ip, (result) => {
|
|
if (result) {
|
|
resolve(`${ip.split(".").slice(0, 2).join(".")}?/proxy`);
|
|
} else {
|
|
resolve(`${ip.split(".").slice(0, 2).join(".")}?/unknown`);
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
resolve(hosts[0]);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
/**
|
|
* Does this IP respond to port 80? In theory, proxies are likely to
|
|
* respond, while residential connections are likely to reject connections.
|
|
*
|
|
* Callback is guaranteed to be called exactly once, within a 1000ms
|
|
* timeout.
|
|
*/
|
|
testConnection(ip, callback) {
|
|
const cachedValue = this.connectionTestCache.get(ip);
|
|
if (cachedValue !== void 0) {
|
|
return callback(cachedValue);
|
|
}
|
|
let connected = false;
|
|
const socket = require("net").createConnection({
|
|
port: 80,
|
|
host: ip,
|
|
timeout: 1e3
|
|
}, () => {
|
|
connected = true;
|
|
this.connectionTestCache.set(ip, true);
|
|
socket.destroy();
|
|
return callback(true);
|
|
});
|
|
socket.on("error", () => {
|
|
});
|
|
socket.on("timeout", () => socket.destroy());
|
|
socket.on("close", () => {
|
|
if (!connected) {
|
|
this.connectionTestCache.set(ip, false);
|
|
return callback(false);
|
|
}
|
|
});
|
|
}
|
|
shortenHost(host) {
|
|
if (host.split(".").pop()?.includes("/"))
|
|
return host;
|
|
let dotLoc = host.lastIndexOf(".");
|
|
const tld = host.slice(dotLoc);
|
|
if (tld === ".uk" || tld === ".au" || tld === ".br")
|
|
dotLoc = host.lastIndexOf(".", dotLoc - 1);
|
|
dotLoc = host.lastIndexOf(".", dotLoc - 1);
|
|
return host.slice(dotLoc + 1);
|
|
}
|
|
/**
|
|
* Host types:
|
|
* - 'res' - normal residential ISP
|
|
* - 'shared' - like res, but shared among many people: bans will have collateral damage
|
|
* - 'mobile' - like res, but unstable IP (IP bans don't work)
|
|
* - 'proxy' - datacenters, VPNs, proxy services, other untrustworthy sources
|
|
* (note that bots will usually be hosted on these)
|
|
* - 'res?' - likely res, but host not specifically whitelisted
|
|
* - 'unknown' - no rdns entry, treat with suspicion
|
|
*/
|
|
getHostType(host, ip) {
|
|
if (Punishments.isSharedIp(ip)) {
|
|
return "shared";
|
|
}
|
|
if (this.singleIPOpenProxies.has(ip) || this.torProxyIps.has(ip)) {
|
|
return "proxy";
|
|
}
|
|
if (/^he\.net(\?|)\/proxy$/.test(host)) {
|
|
if (["74.82.60.", "72.52.87.", "65.49.126."].some((range) => ip.startsWith(range))) {
|
|
return "proxy";
|
|
}
|
|
return "unknown";
|
|
}
|
|
if (this.proxyHosts.has(host) || host.endsWith("/proxy")) {
|
|
return "proxy";
|
|
}
|
|
if (this.residentialHosts.has(host) || host.endsWith("/res")) {
|
|
return "res";
|
|
}
|
|
if (this.mobileHosts.has(host) || host.endsWith("/mobile")) {
|
|
return "mobile";
|
|
}
|
|
if (/^ip-[0-9]+-[0-9]+-[0-9]+\.net$/.test(host) || /^ip-[0-9]+-[0-9]+-[0-9]+\.eu$/.test(host)) {
|
|
return "proxy";
|
|
}
|
|
if (host.endsWith("/unknown")) {
|
|
return "unknown";
|
|
}
|
|
return "res?";
|
|
}
|
|
async updateTorRanges() {
|
|
try {
|
|
const raw = await (0, import_lib.Net)("https://check.torproject.org/torbulkexitlist").get();
|
|
const torIps = raw.split("\n");
|
|
for (const ip of torIps) {
|
|
if (this.ipRegex.test(ip)) {
|
|
this.torProxyIps.add(ip);
|
|
}
|
|
}
|
|
} catch {
|
|
}
|
|
}
|
|
}();
|
|
const telstraRange = {
|
|
minIP: IPTools.ipToNumber("101.160.0.0"),
|
|
maxIP: IPTools.ipToNumber("101.191.255.255"),
|
|
host: "telstra.net?/res"
|
|
};
|
|
var ip_tools_default = IPTools;
|
|
void IPTools.updateTorRanges();
|
|
//# sourceMappingURL=ip-tools.js.map
|