Skip to content

Instantly share code, notes, and snippets.

@jonavon
Created March 13, 2012 13:50
Show Gist options
  • Save jonavon/2028872 to your computer and use it in GitHub Desktop.
Save jonavon/2028872 to your computer and use it in GitHub Desktop.
CIDR class for IPv4

Several useful functions available for IPv4 addresses. It is implemented using mostly bitwise expressions so it should be easily ported to other languages. There is very little done in the way of error checking. References are stated within comment blocks.

Introduction

For a midsize project I wanted to store IP ranges in the database with the option to to also store CIDR blocks. CIDR blocks, though powerful are somewhat difficult for a typical user. Additional using them is not as precise for all ip ranges. There are a plethora of tools available on the internet that will do what I would like. However, incorporating these tools would not be practical. What I want is to mimic the functionality of these tools so that it can be easily imported into any of my projects. The most coveted tool for me would be to convert an IP range to a precise range of CIDR blocks. This required specific functionality not naturally provided in PHP.

  • Check for Valid Netmask
  • Check whether an IP address is within a CIDR block.
  • Take user input and a Netmask and make it into a valid CIDR block.
  • CIDR number into Netmask
  • Netmask to CIDR
  • Take an IP range and fit it into an exact range of CIDR blocks.

This presents some difficulty in that PHP's [network functions] phpnetwork are not thorough enough. The revelation came when I realized that an IP address is merely a number. In fact the whole protocol is rooted in binary using very specific patterns. With that in mind I thought we could develop very light weight methods to solve our problem.

The Code

It is important to note that the methods provided are meant for IPv4 addresses only are only tested on a 32bit system. Also, I did not care to do much in the way of error checking, but doing so, like testing whether the CIDR number is unsigned and less than or equal to 32, should be trivial.

Though the solution I sought after would require PHP I didn't limit myself to that language only. In fact the PHP code I found seemed inefficient. Most involved a number conversions or parsing the address using sprintf using loops and nested if statements. Indeed the most efficient code, which shouldn't surprise most was in ANSI C. Bit Twiddling Hacks resource proved very useful.

About Binary

I am not attempting to teach binary math. Since the code does not "read like prose" a small amount of knowledge is required in order to understand the code. Some excellent resources are Wikipedia's article on CIDR and PHP binary operators.

<?php
/**
* CIDR.php
*
* Utility Functions for IPv4 ip addresses.
* Supports PHP 5.3+ (32 & 64 bit)
* @author Jonavon Wilcox <[email protected]>
* @revision Carlos Guimarães <[email protected]>
* @version Wed Mar 12 13:00:00 EDT 2014
*/
/**
* class CIDR.
* Holds static functions for ip address manipulation.
*/
class CIDR {
/**
* method CIDRtoMask
* Return a netmask string if given an integer between 0 and 32. I am
* not sure how this works on 64 bit machines.
* Usage:
* CIDR::CIDRtoMask(22);
* Result:
* string(13) "255.255.252.0"
* @param $int int Between 0 and 32.
* @access public
* @static
* @return String Netmask ip address
*/
public static function CIDRtoMask($int) {
return long2ip(-1 << (32 - (int)$int));
}
/**
* method validIP.
* Determine if a given input is a valid IPv4 address.
* Usage:
* CIDR::validIP('0.50.45.50');
* Result:
* bool(false)
* @param $ipinput String a IPv4 formatted ip address.
* @access public
* @static
* @return bool True if the input is valid.
*/
public static function validIP($ipinput)
{
return filter_var($ipinput, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
}
/**
* method countSetBits.
* Return the number of bits that are set in an integer.
* Usage:
* CIDR::countSetBits(ip2long('255.255.252.0'));
* Result:
* int(22)
* @param $int int a number
* @access public
* @static
* @see http://stackoverflow.com/questions/109023/best-algorithm-to-co\
* unt-the-number-of-set-bits-in-a-32-bit-integer
* @return int number of bits set.
*/
public static function countSetbits($int){
$int = $int & 0xFFFFFFFF;
$int = ( $int & 0x55555555 ) + ( ( $int >> 1 ) & 0x55555555 );
$int = ( $int & 0x33333333 ) + ( ( $int >> 2 ) & 0x33333333 );
$int = ( $int & 0x0F0F0F0F ) + ( ( $int >> 4 ) & 0x0F0F0F0F );
$int = ( $int & 0x00FF00FF ) + ( ( $int >> 8 ) & 0x00FF00FF );
$int = ( $int & 0x0000FFFF ) + ( ( $int >>16 ) & 0x0000FFFF );
$int = $int & 0x0000003F;
return $int;
}
/**
* method validNetMask.
* Determine if a string is a valid netmask.
* Usage:
* CIDR::validNetMask('255.255.252.0');
* CIDR::validNetMask('127.0.0.1');
* Result:
* bool(true)
* bool(false)
* @param $netmask String a 1pv4 formatted ip address.
* @see http://www.actionsnip.com/snippets/tomo_atlacatl/calculate-if-\
* a-netmask-is-valid--as2-
* @access public
* @static
* return bool True if a valid netmask.
*/
public static function validNetMask($netmask){
$netmask = ip2long($netmask);
if($netmask === false) return false;
$neg = ((~(int)$netmask) & 0xFFFFFFFF);
return (($neg + 1) & $neg) === 0;
}
/**
* method maskToCIDR.
* Return a CIDR block number when given a valid netmask.
* Usage:
* CIDR::maskToCIDR('255.255.252.0');
* Result:
* int(22)
* @param $netmask String a 1pv4 formatted ip address.
* @access public
* @static
* @return int CIDR number.
*/
public static function maskToCIDR($netmask){
if(self::validNetMask($netmask)){
return self::countSetBits(ip2long($netmask));
}
else {
throw new Exception('Invalid Netmask');
}
}
/**
* method alignedCIDR.
* It takes an ip address and a netmask and returns a valid CIDR
* block.
* Usage:
* CIDR::alignedCIDR('127.0.0.1','255.255.252.0');
* Result:
* string(12) "127.0.0.0/22"
* @param $ipinput String a IPv4 formatted ip address.
* @param $netmask String a 1pv4 formatted ip address.
* @access public
* @static
* @return String CIDR block.
*/
public static function alignedCIDR($ipinput,$netmask){
$alignedIP = long2ip((ip2long($ipinput)) & (ip2long($netmask)));
return "$alignedIP/" . self::maskToCIDR($netmask);
}
/**
* method IPisWithinCIDR.
* Check whether an IP is within a CIDR block.
* Usage:
* CIDR::IPisWithinCIDR('127.0.0.33','127.0.0.1/24');
* CIDR::IPisWithinCIDR('127.0.0.33','127.0.0.1/27');
* Result:
* bool(true)
* bool(false)
* @param $ipinput String a IPv4 formatted ip address.
* @param $cidr String a IPv4 formatted CIDR block. Block is aligned
* during execution.
* @access public
* @static
* @return String CIDR block.
*/
public static function IPisWithinCIDR($ipinput,$cidr){
$cidr = explode('/',$cidr);
$cidr = self::alignedCIDR($cidr[0],self::CIDRtoMask((int)$cidr[1]));
$cidr = explode('/',$cidr);
$ipinput = (ip2long($ipinput));
$ip1 = (ip2long($cidr[0]));
$ip2 = ($ip1 + pow(2, (32 - (int)$cidr[1])) - 1);
return (($ip1 <= $ipinput) && ($ipinput <= $ip2));
}
/**
* method maxBlock.
* Determines the largest CIDR block that an IP address will fit into.
* Used to develop a list of CIDR blocks.
* Usage:
* CIDR::maxBlock("127.0.0.1");
* CIDR::maxBlock("127.0.0.0");
* Result:
* int(32)
* int(8)
* @param $ipinput String a IPv4 formatted ip address.
* @access public
* @static
* @return int CIDR number.
*/
public static function maxBlock($ipinput) {
return self::maskToCIDR(long2ip(-(ip2long($ipinput) & -(ip2long($ipinput)))));
}
/**
* method rangeToCIDRList.
* Returns an array of CIDR blocks that fit into a specified range of
* ip addresses.
* Usage:
* CIDR::rangeToCIDRList("127.0.0.1","127.0.0.34");
* Result:
* array(7) {
* [0]=> string(12) "127.0.0.1/32"
* [1]=> string(12) "127.0.0.2/31"
* [2]=> string(12) "127.0.0.4/30"
* [3]=> string(12) "127.0.0.8/29"
* [4]=> string(13) "127.0.0.16/28"
* [5]=> string(13) "127.0.0.32/31"
* [6]=> string(13) "127.0.0.34/32"
* }
* @param $startIPinput String a IPv4 formatted ip address.
* @param $startIPinput String a IPv4 formatted ip address.
* @see http://null.pp.ru/src/php/Netmask.phps
* @return Array CIDR blocks in a numbered array.
*/
public static function rangeToCIDRList($startIPinput,$endIPinput=NULL) {
$start = ip2long($startIPinput);
$end =(empty($endIPinput))?$start:ip2long($endIPinput);
while($end >= $start) {
$maxsize = self::maxBlock(long2ip($start));
$maxdiff = 32 - intval(log($end - $start + 1)/log(2));
$size = ($maxsize > $maxdiff)?$maxsize:$maxdiff;
$listCIDRs[] = long2ip($start) . "/$size";
$start += pow(2, (32 - $size));
}
return $listCIDRs;
}
/**
* method cidrToRange.
* Returns an array of only two IPv4 addresses that have the lowest ip
* address as the first entry. If you need to check to see if an IPv4
* address is within range please use the IPisWithinCIDR method above.
* Usage:
* CIDR::cidrToRange("127.0.0.128/25");
* Result:
* array(2) {
* [0]=> string(11) "127.0.0.128"
* [1]=> string(11) "127.0.0.255"
* }
* @param $cidr string CIDR block
* @return Array low end of range then high end of range.
*/
public static function cidrToRange($cidr) {
$range = array();
$cidr = explode('/', $cidr);
$range[0] = long2ip((ip2long($cidr[0])) & ((-1 << (32 - (int)$cidr[1]))));
$range[1] = long2ip((ip2long($cidr[0])) + pow(2, (32 - (int)$cidr[1])) - 1);
return $range;
}
}
@kruvas
Copy link

kruvas commented Apr 10, 2013

your code of countSetbits not safe for 64 bit php. it is generate bits higher than 32 bits and result is inapropriate
you can use more readable and safe code of this algo (but with less speed)
$int = $int & 0xFFFFFFFF; // fix for extra 32 bits
$int = ( $int & 0x55555555 ) + ( ( $int >> 1 ) & 0x55555555 );
$int = ( $int & 0x33333333 ) + ( ( $int >> 2 ) & 0x33333333 );
$int = ( $int & 0x0F0F0F0F ) + ( ( $int >> 4 ) & 0x0F0F0F0F );
$int = ( $int & 0x00FF00FF ) + ( ( $int >> 8 ) & 0x00FF00FF );
$int = ( $int & 0x0000FFFF ) + ( ( $int >>16 ) & 0x0000FFFF );
$int = $int & 0x0000003F;
return $int;

or use & 0xFFFFFFFF after multple and before 24 right shift

@maykino
Copy link

maykino commented May 15, 2013

The function rangeToCIDRList errors out when provided with some specific parameters. For example when I use the following ip addresses: 206.125.46.26 - 206.125.46.27 PHP starts spinning the code and returns an error:

Fatal error: Allowed memory size of 1572864000 bytes exhausted (tried to allocate 71 bytes) in /var/www/utility-includer.php on line 77

@sajanp
Copy link

sajanp commented May 30, 2013

Would be useful to have a method that returned an array of all single IPs inside a block.

@jonavon
Copy link
Author

jonavon commented Oct 25, 2013

This class may break with 64bit PHP.

@programer2012
Copy link

I use IPisWithinCIDR function ,then i get different result on windows and centos system,why?

@tobsn
Copy link

tobsn commented Dec 1, 2014

could you add a simple function to also get all IPs from CIDR instead of just the first and last of the range?

@kcmerrill
Copy link

@tobsn I realize this is probably no longer of use to you, but for anybody else who might have had the same question, there was a blog post located here: http://www.v-nessa.net/2012/08/07/basic-fun-with-ips-in-php

It included a while loop towards the bottom that would do this that you can pluck out and put into the object if you'd like.

@tsettle
Copy link

tsettle commented Sep 1, 2018

Seems to be a bug in cidrToRange() that returns incorrect information if you provide an address in the middle of the range. Simple fix.

php > var_dump(CIDR::cidrToRange('192.168.17.227/26'));
array(2) {
  [0]=>
  string(14) "192.168.17.192"
  [1]=>
  string(13) "192.168.18.34"
}

$range[1] should use $range[0] instead of $cidr[0] in the calculation:

	public static function cidrToRange($cidr) {
		$range = array();
		$cidr = explode('/', $cidr);
		$range[0] = long2ip((ip2long($cidr[0])) & ((-1 << (32 - (int)$cidr[1]))));
		$range[1] = long2ip((ip2long($range[0])) + pow(2, (32 - (int)$cidr[1])) - 1);
		return $range;
	}
php > var_dump(CIDR::cidrToRange('192.168.17.227/26'));
array(2) {
  [0]=>
  string(14) "192.168.17.192"
  [1]=>
  string(14) "192.168.17.255"
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment