Skip to content
Advertisement

What’s the most performant way to do simple IP address comparisons?

I’m working in node.js, and I want to do something like the following pseudocode…

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);

Any ideas on the fastest way to do this?

I’ve already checked out cidr-js, and it provides functionality for CIDR conversions but not IP address comparisons. Seems like node-ip could be a good choice.

Thanks!

Advertisement

Answer

IP addresses as we know them are simply string representations of 32bit numeric values.

By converting the string representation back into its numeric value, it becomes trivially easy to check membership within a range of addresses using native numeric comparisons, as in the following code:

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

See fiddle.

Here is the same thing, fully commented and properly error-checked:

/**
 * 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
User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement