我有大约 12600 个子网:
例如。 123.123.208.0/20
和IP。
我可以使用 SQLite 数据库或数组或其他东西
大约一个月前有人问过类似的问题,但是我不是在寻找针对一个子网检查一个IP,而是针对一堆子网(显然是最有效的方法,希望不是O(总子网)):)
如何检查 IP 是否属于这些子网之一,我需要 true 或 false 而不是子网(如果这有助于优化)。
当前列表中有类似的子网,例如: (实际摘录)
123.123.48.0/22 <-- not a typo
123.123.48.0/24 <-- not a typo
123.123.90.0/24
123.123.91.0/24
123.123.217.0/24
总共范围从 4.x.y.z 到 222.x.y.z
IMO 最好的方法是使用按位运算符。例如,
123.123.48.0/22
表示 (123<<24)+(123<<16)+(48<<8)+0
(=2071670784;这可能是负数)作为 32 位数字 IP 地址,而 -1<<(32-22)
= -1024 作为掩码。有了这个,同样,您的测试 IP 地址也转换为数字,您可以执行以下操作:
(inputIP & testMask) == testIP
例如,123.123.49.123 就在该范围内,因为
2071671163 & -1024
是 2071670784
那么,这里有一些工具功能:
function IPnumber(IPaddress) {
var ip = IPaddress.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
if(ip) {
return (+ip[1]<<24) + (+ip[2]<<16) + (+ip[3]<<8) + (+ip[4]);
}
// else ... ?
return null;
}
function IPmask(maskSize) {
return -1<<(32-maskSize)
}
测试:
(IPnumber('123.123.49.123') & IPmask('22')) == IPnumber('123.123.48.0')
产量
true
。
如果您的掩码格式为“255.255.252.0”,那么您也可以对掩码使用 IPnumber 函数。
试试这个:
var ip2long = function(ip){
var components;
if(components = ip.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/))
{
var iplong = 0;
var power = 1;
for(var i=4; i>=1; i-=1)
{
iplong += power * parseInt(components[i]);
power *= 256;
}
return iplong;
}
else return -1;
};
var inSubNet = function(ip, subnet)
{
var mask, base_ip, long_ip = ip2long(ip);
if( (mask = subnet.match(/^(.*?)\/(\d{1,2})$/)) && ((base_ip=ip2long(mask[1])) >= 0) )
{
var freedom = Math.pow(2, 32 - parseInt(mask[2]));
return (long_ip > base_ip) && (long_ip < base_ip + freedom - 1);
}
else return false;
};
用途:
inSubNet('192.30.252.63', '192.30.252.0/22') => true
inSubNet('192.31.252.63', '192.30.252.0/22') => false
我设法使用 node netmask 模块解决了这个问题。 您可以通过执行以下操作来检查 IP 是否属于子网:
import { Netmask } from 'netmask'
const block = new Netmask('123.123.208.0/20')
const ip = '123.123.208.0'
console.log(block.contains(ip))
将在这里打印
true
。
您可以使用以下方式安装它:
npm i --save netmask
将范围内的下限 ip 和上限 ip 转换为整数并将范围存储在数据库中,然后确保两列都已索引。
我的头顶上(伪代码):
function ipmap(w,x,y,z) {
return 16777216*w + 65536*x + 256*y + z;
}
var masks = array[ipmap(128,0,0,0), ipmap(196,0,0,0), ..., ipmap(255,255,255,255)]
function lowrange(w, x, y, z, rangelength) {
return ipmap(w, x, y, z) & masks[rangelength]
}
function hirange(w, x, y, z, rangelength) {
return lowrange(w, x, y, z, ,rangelength) + ipmap(255,255,255,255) - masks[rangelength];
}
应该可以了。
要查找特定 ip 是否属于任何范围,请将其转换为整数并执行以下操作:
SELECT COUNT(*) FROM ipranges WHERE lowrange <= 1234567 AND 1234567 <= highrange
查询优化器应该能够大大加快速度。
函数
IPnumber
和 IPmask
很好,但我宁愿测试如下:
(IPnumber('123.123.49.123') & IPmask('22')) == (IPnumber('123.123.48.0') & IPmask('22'))
因为对于每个地址,您只需要考虑该地址的网络部分。因此,执行
IPmask('22')
会将地址的计算机部分清零,您应该对网络地址执行相同的操作。
关键词:二分查找、预处理、排序
我遇到了类似的问题,如果您可以预处理子网列表并对其进行排序,那么二分搜索似乎非常有效。那么你可以达到O(log n)的渐近时间复杂度。
这是我的代码(MIT许可证,原始位置:https://github.com/iBug/pac/blob/854289a674578d096f60241804f5893a3fa17523/code.js):
function belongsToSubnet(host, list) {
var ip = host.split(".").map(Number);
ip = 0x1000000 * ip[0] + 0x10000 * ip[1] + 0x100 * ip[2] + ip[3];
if (ip < list[0][0])
return false;
// Binary search
var x = 0, y = list.length, middle;
while (y - x > 1) {
middle = Math.floor((x + y) / 2);
if (list[middle][0] < ip)
x = middle;
else
y = middle;
}
// Match
var masked = ip & list[x][1];
return (masked ^ list[x][0]) == 0;
}
还有一个示例用法:
function isLan(host) {
return belongsToSubnet(host, LAN);
}
var LAN = [
[0x0A000000, 0xFF000000], // 10.0.0.0/8
[0x64400000, 0xFFC00000], // 100.64.0.0/10
[0x7F000000, 0xFF000000], // 127.0.0.0/8
[0xA9FE0000, 0xFFFF0000], // 169.254.0.0/16
[0xAC100000, 0xFFF00000], // 172.16.0.0/12
[0xC0A80000, 0xFFFF0000] // 192.168.0.0/16
];
isLan("127.12.34.56"); // => true
isLan("8.8.8.8"); // => false (Google's Public DNS)
您可以获取 PAC 脚本*,并查看它如何针对 5000 个子网执行(它从其他地方加载中国 IP 列表,对它们进行排序并适当格式化)。在实践中,它的速度令人惊讶地令人满意。
可以使用上页的 F12 Dev Tools 检查预处理代码。简而言之,您需要将
1.2.3.4/16
转换为 [0x01020304, 0xFFFF0000]
,即 IP 地址和网络掩码的 32 位无符号整数。
* 链接到我的个人网站。
无需进行数学计算或外部库即可进行简单匹配:
import { BlockList, isIp, isIPv4 } from "net";
function isIpWhitelisted(clientIp: string, allowedCidrs: string[]): boolean {
if (!isIP(clientIp)) {
return false;
}
if (!allowedCidrs.length) {
return true;
}
const whitelist = allowedCidrs.reduce(addToWhitelist, new BlockList());
return whitelist.check(clientIp);
}
function addToWhitelist(whitelist: BlockList, cidr: string): BlockList {
const subnets = cidr.split("/");
if (subnets.length === 1) {
whitelist.addAddress(subnets[0]);
}
if (subnets.length === 2) {
whitelist.addSubnet(subnets[0], parseInt(subnets[1], 10), isIPv4(subnets[0]) ? "ipv4" : "ipv6");
}
return whitelist;
}
用途:
isIpWhitelisted("192.168.0.1", ["192.168.0.0/24", "10.0.0.0/8"]) === true
isIpWhitelisted("192.168.0.1", ["10.0.0.0/8"]) === false