JavaScript:IP 在这些子网之一中吗?

Posted

技术标签:

【中文标题】JavaScript:IP 在这些子网之一中吗?【英文标题】:JavaScript: Is IP In One Of These Subnets? 【发布时间】:2010-10-04 22:06:13 【问题描述】:

所以我有 ~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

【问题讨论】:

【参考方案1】:

最好的方法是 IMO 使用位运算符。例如,123.123.48.0/22 表示 (123&lt;&lt;24)+(123&lt;&lt;16)+(48&lt;&lt;8)+0(=2071670784;这可能是一个负数)作为 32 位数字 IP 地址,-1&lt;&lt;(32-22) = -1024 作为掩码。有了这个,同样,你的测试 IP 地址转换为一个数字,你可以这样做:

(inputIP & testMask) == testIP

例如,123.123.49.123 在该范围内,因为 2071671163 &amp; -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 函数作为掩码。

【讨论】:

我喜欢你对位运算符的使用,但是 javascript 能保证你的印度编码吗?我将 ip 范围分为低和高,以允许在索引列时的 O(logN) 行为,而不是使用这种方法的 O(N)。但这可能取决于 sqlite 的实现。不确定。 “保证字节序编码”?我不明白为什么必须这样做。这只是数字。无论如何,您确实可以分别使用 IP&mask 和 IP|~mask (~-1024==1023) 找到范围的下限和上限,因此您可以使用 BETWEEN 搜索范围的匹配项。 @bart 我只有输入 IP 和掩码,我想检查输入 IP 是否在掩码范围内(格式为 255.255.252.0 的掩码)如何检查? 一个错误:如果您的子网没有正确定义右侧 0,这将失败。要使错误的定义起作用,请使用 (IPnumber('123.123.49.123') & IPmask('22')) == (IPnumber('123.123.48.1') & IPmask('22'))【参考方案2】:

试试这个:

var ip2long = function(ip)
    var components;

    if(components = ip.match(/^(\d1,3)\.(\d1,3)\.(\d1,3)\.(\d1,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(/^(.*?)\/(\d1,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

【讨论】:

但是您的代码不包括第一个和最后一个 IP。我又添加了一项检查,现在可以了 return (long_ip &gt; base_ip || long_ip === base_ip) &amp;&amp; ((long_ip &lt; base_ip + freedom - 1) || (long_ip === base_ip + freedom - 1));【参考方案3】:

我设法通过使用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

【讨论】:

【参考方案4】:

将范围内的下ip和上ip转换为整数并将范围存储在db中,然后确保两列都被索引。

我的头顶(伪代码):

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

查询优化器应该能够大大加快这个速度。

【讨论】:

感谢您的回复,如何使用 JavaScript 将子网转换为范围?还有呢: 123.123.48.0/22 123.123.48.0/24 它们重叠。 范围的开始和结束 ips 会有所不同。所以重叠应该不是问题。 不是:123.123.48.0/22:123.123.48.1 - 123.123.51.254 & 123.123.48.0/24:123.123.48.1 - 123.123.48.254 结局不同,有什么问题? 去掉多余的 /24 不是更好的解决方案吗?我想我总是可以只取最低的 /x 并删除所有更高的子网。【参考方案5】:

函数IPnumberIPmask 很好,但我宁愿测试如下:

(IPnumber('123.123.49.123') & IPmask('22')) == (IPnumber('123.123.48.0')  & IPmask('22'))

因为对于每个地址,您只需要考虑地址的网络部分。因此,IPmask('22') 会将地址的计算机部分归零,您应该对网络地址执行相同操作。

【讨论】:

【参考方案6】:

关键字:二分查找、预处理、排序

我遇到了类似的问题,如果您可以预处理子网列表并对其进行排序,那么二进制搜索似乎非常有效。然后你可以实现 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)

您可以get a PAC script* 并查看它在 5000 个子网中的表现(它从其他地方加载中国 IP 列表,对它们进行排序并适当地格式化它们)。在实践中,它的速度令人惊讶地令人满意。

可以使用上述页面上的 F12 开发工具检查预处理代码。简而言之,您需要将1.2.3.4/16 转换为[0x01020304, 0xFFFF0000],即IP 地址和网络掩码的32 位无符号整数。

* 链接到我的个人网站。

【讨论】:

您的二分搜索错过了列表大小为 1 的边缘情况。只需更新循环以使用 do while 语法即可解决此问题。 @this-sam 我不这么认为。当列表大小为 1 时,它会跳过带有 x=0 的循环,并在检查相等性之前继续应用子网掩码。二分查找的目的是找到list[x] &lt;= ip &lt; list[x+1] 所在的元素x,所以唯一的边缘情况是ip &lt; list[0] 之前已经处理过。 你是对的。我错过了初步检查。在我的实现中,我还添加了一个空输入列表检查。

以上是关于JavaScript:IP 在这些子网之一中吗?的主要内容,如果未能解决你的问题,请参考以下文章

ip地址子网掩码网关与网卡DNS的区别及用处

从子网掩码计算 IP 范围

IP,DNS,子网掩码,路由器,交换机,ISP,这些网络术语是什么意思

计算机网络: IP地址,子网掩码,默认网关,DNS服务器详解

IPCIDR广播地址子网掩码MAC地址--这些是什么鬼

计算机网络: IP地址,子网掩码,网段表示法,默认网关,DNS服务器详解