Posted By

Jdub7 on 06/05/09


Tagged

ipAddress cidr netmask IPv4 32bit


Versions (?)

Who likes this?

2 people have marked this snippet as a favorite

Jdub7
dclau


CIDR class for IPv4


 / Published in: PHP
 

View the Gist.

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 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.

  1. /**
  2.  * CIDR.php
  3.  *
  4.  * Utility Functions for IPv4 ip addresses.
  5.  *
  6.  * @author Jonavon Wilcox <[email protected]>
  7.  * @version Sat Jun 6 21:26:48 EDT 2009
  8.  * @copyright Copyright (c) 2009 Jonavon Wilcox
  9.  */
  10. /**
  11.   * class CIDR.
  12.   * Holds static functions for ip address manipulation.
  13.   */
  14. class CIDR {
  15. /**
  16. * method CIDRtoMask
  17. * Return a netmask string if given an integer between 0 and 32. I am
  18. * not sure how this works on 64 bit machines.
  19. * Usage:
  20. * CIDR::CIDRtoMask(22);
  21. * Result:
  22. * string(13) "255.255.252.0"
  23. * @param $int int Between 0 and 32.
  24. * @access public
  25. * @static
  26. * @return String Netmask ip address
  27. */
  28. public static function CIDRtoMask($int) {
  29. return long2ip(-1 << (32 - (int)$int));
  30. }
  31.  
  32. /**
  33. * method countSetBits.
  34. * Return the number of bits that are set in an integer.
  35. * Usage:
  36. * CIDR::countSetBits(ip2long('255.255.252.0'));
  37. * Result:
  38. * int(22)
  39. * @param $int int a number
  40. * @access public
  41. * @static
  42. * @see http://stackoverflow.com/questions/109023/best-algorithm-to-co\
  43. * unt-the-number-of-set-bits-in-a-32-bit-integer
  44. * @return int number of bits set.
  45. */
  46. public static function countSetbits($int){
  47. $int = $int - (($int >> 1) & 0x55555555);
  48. $int = ($int & 0x33333333) + (($int >> 2) & 0x33333333);
  49. return (($int + ($int >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
  50. }
  51.  
  52. /**
  53. * method validNetMask.
  54. * Determine if a string is a valid netmask.
  55. * Usage:
  56. * CIDR::validNetMask('255.255.252.0');
  57. * CIDR::validNetMask('127.0.0.1');
  58. * Result:
  59. * bool(true)
  60. * bool(false)
  61. * @param $netmask String a 1pv4 formatted ip address.
  62. * @see http://www.actionsnip.com/snippets/tomo_atlacatl/calculate-if-\
  63. * a-netmask-is-valid--as2-
  64. * @access public
  65. * @static
  66. * return bool True if a valid netmask.
  67. */
  68. public static function validNetMask($netmask){
  69. $netmask = ip2long($netmask);
  70. $neg = ((~(int)$netmask) & 0xFFFFFFFF);
  71. return (($neg + 1) & $neg) === 0;
  72. }
  73.  
  74. /**
  75. * method maskToCIDR.
  76. * Return a CIDR block number when given a valid netmask.
  77. * Usage:
  78. * CIDR::maskToCIDR('255.255.252.0');
  79. * Result:
  80. * int(22)
  81. * @param $netmask String a 1pv4 formatted ip address.
  82. * @access public
  83. * @static
  84. * @return int CIDR number.
  85. */
  86. public static function maskToCIDR($netmask){
  87. if(self::validNetMask($netmask)){
  88. return self::countSetBits(ip2long($netmask));
  89. }
  90. else {
  91. throw new Exception('Invalid Netmask');
  92. }
  93. }
  94.  
  95. /**
  96. * method alignedCIDR.
  97. * It takes an ip address and a netmask and returns a valid CIDR
  98. * block.
  99. * Usage:
  100. * CIDR::alignedCIDR('127.0.0.1','255.255.252.0');
  101. * Result:
  102. * string(12) "127.0.0.0/22"
  103. * @param $ipinput String a IPv4 formatted ip address.
  104. * @param $netmask String a 1pv4 formatted ip address.
  105. * @access public
  106. * @static
  107. * @return String CIDR block.
  108. */
  109. public static function alignedCIDR($ipinput,$netmask){
  110. $alignedIP = long2ip((ip2long($ipinput)) & (ip2long($netmask)));
  111. return "$alignedIP/" . self::maskToCIDR($netmask);
  112. }
  113.  
  114. /**
  115. * method IPisWithinCIDR.
  116. * Check whether an IP is within a CIDR block.
  117. * Usage:
  118. * CIDR::IPisWithinCIDR('127.0.0.33','127.0.0.1/24');
  119. * CIDR::IPisWithinCIDR('127.0.0.33','127.0.0.1/27');
  120. * Result:
  121. * bool(true)
  122. * bool(false)
  123. * @param $ipinput String a IPv4 formatted ip address.
  124. * @param $cidr String a IPv4 formatted CIDR block. Block is aligned
  125. * during execution.
  126. * @access public
  127. * @static
  128. * @return String CIDR block.
  129. */
  130. public static function IPisWithinCIDR($ipinput,$cidr){
  131. $cidr = explode('/',$cidr);
  132. $cidr = self::alignedCIDR($cidr[0],self::CIDRtoMask((int)$cidr[1]));
  133. $cidr = explode('/',$cidr);
  134. $ipinput = (ip2long($ipinput));
  135. $ip1 = (ip2long($cidr[0]));
  136. $ip2 = ($ip1 + pow(2, (32 - (int)$cidr[1])) - 1);
  137. return (($ip1 <= $ipinput) && ($ipinput <= $ip2));
  138. }
  139.  
  140. /**
  141. * method maxBlock.
  142. * Determines the largest CIDR block that an IP address will fit into.
  143. * Used to develop a list of CIDR blocks.
  144. * Usage:
  145. * CIDR::maxBlock("127.0.0.1");
  146. * CIDR::maxBlock("127.0.0.0");
  147. * Result:
  148. * int(32)
  149. * int(8)
  150. * @param $ipinput String a IPv4 formatted ip address.
  151. * @access public
  152. * @static
  153. * @return int CIDR number.
  154. */
  155. public static function maxBlock($ipinput) {
  156. return self::maskToCIDR(long2ip(-(ip2long($ipinput) & -(ip2long($ipinput)))));
  157. }
  158.  
  159. /**
  160. * method rangeToCIDRList.
  161. * Returns an array of CIDR blocks that fit into a specified range of
  162. * ip addresses.
  163. * Usage:
  164. * CIDR::rangeToCIDRList("127.0.0.1","127.0.0.34");
  165. * Result:
  166. * array(7) {
  167. * [0]=> string(12) "127.0.0.1/32"
  168. * [1]=> string(12) "127.0.0.2/31"
  169. * [2]=> string(12) "127.0.0.4/30"
  170. * [3]=> string(12) "127.0.0.8/29"
  171. * [4]=> string(13) "127.0.0.16/28"
  172. * [5]=> string(13) "127.0.0.32/31"
  173. * [6]=> string(13) "127.0.0.34/32"
  174. * }
  175. * @param $startIPinput String a IPv4 formatted ip address.
  176. * @param $startIPinput String a IPv4 formatted ip address.
  177. * @see http://null.pp.ru/src/php/Netmask.phps
  178. * @return Array CIDR blocks in a numbered array.
  179. */
  180. public static function rangeToCIDRList($startIPinput,$endIPinput=NULL) {
  181. $start = ip2long($startIPinput);
  182. $end =(empty($endIPinput))?$start:ip2long($endIPinput);
  183. while($end >= $start) {
  184. $maxsize = self::maxBlock(long2ip($start));
  185. $maxdiff = 32 - intval(log($end - $start + 1)/log(2));
  186. $size = ($maxsize > $maxdiff)?$maxsize:$maxdiff;
  187. $listCIDRs[] = long2ip($start) . "/$size";
  188. $start += pow(2, (32 - $size));
  189. }
  190. return $listCIDRs;
  191. }
  192.  
  193. /**
  194. * method cidrToRange.
  195. * Returns an array of only two IPv4 addresses that have the lowest ip
  196.   * address as the first entry. If you need to check to see if an IPv4
  197.   * address is within range please use the IPisWithinCIDR method above.
  198. * Usage:
  199. * CIDR::cidrToRange("127.0.0.128/25");
  200. * Result:
  201.   * array(2) {
  202.   * [0]=> string(11) "127.0.0.128"
  203.   * [1]=> string(11) "127.0.0.255"
  204.   * }
  205. * @param $cidr string CIDR block
  206. * @return Array low end of range then high end of range.
  207. */
  208. public static function cidrToRange($cidr) {
  209. $range = array();
  210. $cidr = explode('/', $cidr);
  211. $range[0] = long2ip((ip2long($cidr[0])) & ((-1 << (32 - (int)$cidr[1])))));
  212. $range[1] = long2ip((ip2long($cidr[0])) + pow(2, (32 - (int)$cidr[1])) - 1);
  213. return $range;
  214. }
  215. }

Report this snippet  

Comments

RSS Icon Subscribe to comments
Posted By: Jdub7 on May 15, 2011

Too many ip2longs

Posted By: Jdub7 on December 11, 2012

Moved to https://gist.github.com/2028872

Posted By: Jdub7 on December 11, 2012

Moved to

You need to login to post a comment.