进行简单 IP 地址比较的最高效方法是啥?

Posted

技术标签:

【中文标题】进行简单 IP 地址比较的最高效方法是啥?【英文标题】:What's the most performant way to do simple IP address comparisons?进行简单 IP 地址比较的最高效方法是什么? 【发布时间】:2015-09-29 14:40:57 【问题描述】:

我在 node.js 中工作,我想做类似下面的伪代码...

let ip_range = [50.1.100.1, 51.1.30.1]; // Converted from a CIDR string.

let ip_address = 50.2.200.2; // Input IP address.

if(ip_address >= ip_range[0] && ip_address <= ip_range[1])
    block(ip_address);

对最快的方法有什么想法吗?

我已经查看了 cidr-js,它提供了 CIDR 转换功能,但不提供 IP 地址比较。似乎 node-ip 可能是一个不错的选择。

谢谢!

【问题讨论】:

为什么要放弃 CIDR? CIDR 是位掩码,允许您只比较前 N 位,这与您将获得的效率差不多。 当然。我实际上已经考虑了一点。 CIDR 作为字符串输入。这是位掩码的问题还是我应该只转换为数字? 假设您只是在处理 IPv4 地址,只需将地址转换为 32 位整数并进行比较。 您需要将字符串转换为数字,然后您可以在 CIDR 和 IP 之间进行按位比较(我相信是(cidr &amp; ip) == cidr)。 我相信这行得通。现在我只是将它们转换为多头并以这种方式进行比较。感谢您的帮助! 【参考方案1】:

我们所知道的 IP 地址只是 32 位数值的字符串表示形式。

通过将字符串表示形式转换回其数值,使用本机数字比较检查地址范围内的成员关系变得非常容易,如下面的代码所示:

var atoi = function atoi(addr) 
  var parts = addr.split('.').map(function(str) 
    return parseInt(str); 
  );

  return (parts[0] ? parts[0] << 24 : 0) +
         (parts[1] ? parts[1] << 16 : 0) +
         (parts[2] ? parts[2] << 8  : 0) +
          parts[3];
;

var checkIpaddrInRange = function checkIpaddrInRange(ipaddr, start, end) 
  var num = atoi(ipaddr);
  return (num >= atoi(start)) && (num <= atoi(end));



checkIpaddrInRange('10.0.1.1', '10.0.0.1', '10.0.2.1'); // => true

checkIpaddrInRange('10.0.3.1', '10.0.0.1', '10.0.2.1'); // => false

见fiddle。

这里是同样的东西,完整的注释和正确的错误检查:

/**
 * Checks if ipaddr is valid.
 * @property string ipaddr
 * @throws Error 
 */
var assertIsIpaddr = function assertIsIpaddr(ipaddr) 

  if('string' !== typeof ipaddr && ipaddr) 
    throw new Error('ipaddr must be a non-empty string');
  

  var parts=ipaddr.split(/\./);

  if(parts.length !== 4)
    throw new Error('ipaddr must have four octets');
  

  var i=0;
  parts.map(function(str)
      var val=parseInt(str),
          octet = 4 - i++;;
      if(val < 0 || val > 255)
        throw new Error('octet '+octet+' must be between 0 and 255');
      
  );
;

/**
 * Converts an ipaddr to a 32bit integer value.
 * @property string addr - the ipaddr to convert
 * @returns number
 */
var atoi = function atoi(addr) 

  // test for validity - will throw!
  assertIsIpaddr(addr);

  // convert octets to numbers
  var parts = addr.split('.').map(function(str) 
    return parseInt(str); 
  );

  // construct result
  var result = (parts[0] ? parts[0] << 24 : 0) +   // if > 0, shift 4th octet left by 24
               (parts[1] ? parts[1] << 16 : 0) +   // if > 0, shift 3rd octet left by 16
               (parts[2] ? parts[2] << 8  : 0) +   // if > 0, shift 2nd octet left by 8
                parts[3];

  // note that if all octets are 255, result will overflow 
  // javascript (32bit) number to become -1, so we have to 
  // special case it. I think throwing an error here is a 
  // reasonable solution, since 255.255.255.255 is actually 
  // a broadcast addr.

  if(result < 0) 
    throw new Error('255.255.255.255 is not a legal host ipaddr');
  

  return result;
;

/**
 * Checks ipaddr membership within a range of ipaddrs.
 * @property string ipaddr - ipaddr to check
 * @property string start - the start of the ipaddr range 
 * @property string end - the end of the ipaddr range
 * @returns boolean - true if ipaddr is between start and end (inclusive)
 */
var checkIpaddrInRange = function checkIpaddrInRange(ipaddr, start, end) 
  var num = atoi(ipaddr);
  return (num >= atoi(start)) && (num <= atoi(end));


// OK, test it out...

checkIpaddrInRange('10.0.1.1','10.0.0.1','10.0.2.1'); // => true

checkIpaddrInRange('10.0.3.1','10.0.0.1','10.0.2.1'); // => false

【讨论】:

如果第一部分大于 127,则此代码不起作用。128 &lt;&lt; 24 给出-2147483648。为了使它工作,我用乘法const result = (parts[0] ? parts[0] * Math.pow(256, 3) : 0) + (parts[1] ? parts[1] * Math.pow(256, 2) : 0) + (parts[2] ? parts[2] * Math.pow(256, 1) : 0) + parts[3]; 替换了位操作【参考方案2】:

可能有一种更简单的方法来做到这一点,但这是我可能会采用的方法。

function pad(n) 
    return (n.length < 3) ? pad('0' + n) : n;


// return a number with each part of the IP padded out to 3 digits
function convert(ip) 
    return parseInt(ip.split('.').map(function (el) 
       return pad(el);
    ).join(''), 10);


// check the number against the range
function check(range, ip) 
  ip = convert(ip);
  return ip >= range[0] && ip <= range[1];


// convert the range
var range = ['50.1.100.1', '51.1.30.1'].map(function (el) 
  return convert(el);
);

check(range, '51.1.20.2'); // true
check(range, '51.1.40.2'); // false

DEMO

【讨论】:

【参考方案3】:

使用ipaddr.js (npm i ipaddr.js):

const ipaddr = require('ipaddr.js')

const addr = ipaddr.parse('2001:db8:1234::1')

addr.match(ipaddr.parseCIDR('2001:db8::/32')) // => true

或者:

const rangeList = 
  documentationOnly: [ ipaddr.parse('2001:db8::'), 32 ],
  tunnelProviders: [
    [ ipaddr.parse('2001:470::'), 32 ], // he.net
    [ ipaddr.parse('2001:5c0::'), 32 ]  // freenet6
  ]

ipaddr.subnetMatch(ipaddr.parse('2001:470:8:66::1'), rangeList, 'unknown') // => "tunnelProviders"

Official documentation

【讨论】:

【参考方案4】:

也许是一种简短的方法

const ipAddress = '10.120.238.254';

const asNumber = ipAdress.split('.')
 .map(p => parseInt(p))
 .reverse()
 .reduce((acc,val,i) => acc+(val*(256**i)),0)

然后比较数字

【讨论】:

以上是关于进行简单 IP 地址比较的最高效方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在 Flutter 中使用设置的 FPS 绘制大量简单、填充颜色的矩形的最高效方法是啥?

重写大型 IN 子句的最高效方法是啥?

从IP地址获取用户纬度和经度的最简单方法是啥[关闭]

提高 CSS 特异性的最高效的方式是啥?

React-Native 中动态样式的最高效方式是啥?

查询三个集合、对它们进行排序和限制的最高效方法?