什么是简单IP地址比较的最佳性能方式?

4

我正在使用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可能是一个不错的选择。

谢谢!


1
你为什么要放弃CIDR呢?CIDRs是位掩码,它允许您只比较前N位,这是您可以得到的最有效率的处理方式。 - ssube
当然。我其实已经考虑过这个问题了。CIDR被输入为字符串。对于位掩码来说,这是一个问题吗?还是我应该将其转换为数字? - David K.
1
假设你只处理IPv4地址,将地址转换为32位整数并进行比较即可。 - Austin Brunkhorst
1
你需要将字符串转换为数字,然后可以在CIDR和IP之间进行按位比较((cidr & ip) == cidr)。 - ssube
我相信这样可以行得通。现在我只是将它们转换为长整型并进行比较。感谢您的帮助! - David K.
你代码中的IP地址不是有效的字面值,你是否想使用字符串? - Bergi
4个回答

8

我们所知道的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

请查看示例

这里是完全注释和正确检查错误的相同内容:

/**
 * 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 << 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]; - Yurii Kravchuk

1

也许有更简单的方法,但这是我的做法。

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

可以翻译为:

{{链接1:演示}}


1
使用 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"

官方文档


0

也许一个简短的方法是

const ipAddress = '10.120.238.254';

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

然后比较这些数字


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接