{"id":166,"date":"2009-04-27T23:48:38","date_gmt":"2009-04-27T22:48:38","guid":{"rendered":"http:\/\/pgregg.com\/wp\/?p=166"},"modified":"2013-02-21T15:31:18","modified_gmt":"2013-02-21T15:31:18","slug":"php-algorithms-determining-if-an-ip-is-within-a-specific-range","status":"publish","type":"post","link":"https:\/\/blog.pgregg.com\/blog\/2009\/04\/php-algorithms-determining-if-an-ip-is-within-a-specific-range\/","title":{"rendered":"PHP algorithms: Determining if an IP is within a specific range."},"content":{"rendered":"<p>I spend a lot of time lurking in the #PHP channel (efnet and freenode, please &#8211; no flamewars) and this topic is a commonly asked one that usually gets a simplified answer in the form of using strpos(), or at best an ip2long() in a greater than and less than answer.<\/p>\n<p>Unfortunately although people usually understand that an IP address is simply an unsigned 32 bit integer, and is easily determined, usually with $_SERVER[&#8216;REMOTE_ADDR&#8217;], where the real challenge is &#8211; is in specifying the range within which they wish to check that IP address.\u00a0 IP ranges are usually specified in three common ways (in increasing complexity):<\/p>\n<blockquote>\n<ol>\n<li>Wildcard: 192.168.10.*<\/li>\n<li>Start-End range: 10.1.0.0-10.1.255.255<\/li>\n<li>CIDR*: 172.16.1.0\/24<\/li>\n<\/ol>\n<p><em>* Classless Inter-Domain Routing<\/em><\/p><\/blockquote>\n<p>The Wildcard method, or &#8220;classy&#8221;, allows you to work at Class A (10.*.*.*), Class B (172.16.*.*) or Class C (192.168.10.*) levels of granularity which is how we used to do things in the old days (before the Web decided to make the Internet popular).\u00a0 But, increasingly, this just isn&#8217;t granular enough for practical purposes.<\/p>\n<p>Thus was born CIDR (yes, I&#8217;m skipping talking about Start-End ranges for now).\u00a0 CIDR brought about the concept that we really didn&#8217;t need to break networks on 8, 16, 24 bit boundaries and we could be more granular by allowing the use of any number (from 2-30) to specify a range of networks.\u00a0 Details on why you can&#8217;t use &#8220;31&#8221; is beyond the scope of this article.<\/p>\n<p>CIDR renamed the former Class A, B and C networks as \/8, \/16 and \/24 respectively and reflects the left-most significant bits of the 32-bit IP address.\u00a0 Thus was born the ability to specify very specific IP ranges in the form a.b.c.d\/xx.\u00a0\u00a0 However, part of the problem with this is that although it concisely describes the network start and end, most normal mortal humans couldn&#8217;t decipher it. CIDR addressing can also be specified in the form of a longer netmask, e.g. a.b.c.d\/255.255.255.224<\/p>\n<p>Thus, the simplified form of Start IP &#8211; End IP was put in place for mere mortals and is typically used by those without a networking background.\u00a0 It also features heavily in consumer broadband routers and notably in Microsoft Windows DHCP server.<\/p>\n<p>So having explained how a range, and by inference, that a netmask is, how can we use this knowledge to help us in determining if an IP is within a range?<\/p>\n<p>What this article will attempt to do is guide you though the construction of algorithms to make the checking of IPs simpler.<\/p>\n<p>Logically, Method 1 (the Wildcard), can be easily converted to Method 2 (Start-End range) by using setting Start and End to the Wildcard string and replacing the &#8220;*&#8221; character with 0 for the Start and 255 for the End, thus for example, &#8220;192.168.10.*&#8221; becomes &#8220;192.168.10.0-192.168.10.255&#8221; which should (I hope) be obvious to everyone.<\/p>\n<p>We can then proceed to evaluate both Method1 and Method2 in the same way.\u00a0 In this we&#8217;re simply going to use the PHP built in function ip2long() on all 3 values and perform a mathematical check for Start &lt;= IP &lt;= End.<\/p>\n<blockquote>\n<pre><span style=\"color: #000000;\"><span style=\"color: #007700;\">list(<\/span><span style=\"color: #0000bb;\">$lower<\/span><span style=\"color: #007700;\">,\u00a0<\/span><span style=\"color: #0000bb;\">$upper<\/span><span style=\"color: #007700;\">)\u00a0=\u00a0<\/span><span style=\"color: #0000bb;\">explode<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #dd0000;\">'-'<\/span><span style=\"color: #007700;\">,\u00a0<\/span><span style=\"color: #0000bb;\">$range<\/span><span style=\"color: #007700;\">,\u00a0<\/span><span style=\"color: #0000bb;\">2<\/span><span style=\"color: #007700;\">);<\/span><\/span>\r\n<span style=\"color: #0000bb;\">$lower_dec\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">ip2long<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$lower<\/span><span style=\"color: #007700;\">);<\/span>\r\n<span style=\"color: #0000bb;\">$upper_dec\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">ip2long<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$upper<\/span><span style=\"color: #007700;\">);<\/span>\r\n<span style=\"color: #0000bb;\">$ip_dec\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">ip2long<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$ip<\/span><span style=\"color: #007700;\">);<\/span>\r\n<span style=\"color: #007700;\"><span style=\"color: #333333;\">return\u00a0(\u00a0(<\/span><span style=\"color: #0000bb;\">$ip_dec<\/span><span>&gt;=<\/span><span style=\"color: #0000bb;\">$lower_dec<\/span><span>)\u00a0&amp;&amp;\u00a0(<\/span><span style=\"color: #0000bb;\">$ip_dec<\/span><span>&lt;=<\/span><span style=\"color: #0000bb;\">$upper_dec<\/span><span>)\u00a0);<\/span><\/span><\/pre>\n<\/blockquote>\n<p>We have, however, a complicating factor here &#8211; PHP does not do unsigned integers (32 bit) &#8211; which would be necessary for this math to work properly.\u00a0 We can negate this by switing to floating point data types.\u00a0 PHP stores\u00a0 floating types as 64 bit and so will have no problem with IPv4 address space (note &#8211; this obviously isn&#8217;t granular enough for 128bit IPv6 addressing). Therefore the simplest way to solve the Start &lt;= IP &lt;= End problem with IPs and floating point numers is the following piece of code:<\/p>\n<blockquote>\n<pre><span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$lower_dec\u00a0<\/span><span style=\"color: #007700;\">=\u00a0(float)<\/span><span style=\"color: #0000bb;\">sprintf<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #dd0000;\">\"%u\"<\/span><span style=\"color: #007700;\">,<\/span><span style=\"color: #0000bb;\">ip2long<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$lower<\/span><span style=\"color: #007700;\">));<\/span><\/span>\r\n<span style=\"color: #0000bb;\">$upper_dec\u00a0<\/span><span style=\"color: #007700;\">=\u00a0(float)<\/span><span style=\"color: #0000bb;\">sprintf<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #dd0000;\">\"%u\"<\/span><span style=\"color: #007700;\">,<\/span><span style=\"color: #0000bb;\">ip2long<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$upper<\/span><span style=\"color: #007700;\">));<\/span>\r\n<span style=\"color: #0000bb;\">$ip_dec\u00a0<\/span><span style=\"color: #007700;\">=\u00a0(float)<\/span><span style=\"color: #0000bb;\">sprintf<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #dd0000;\">\"%u\"<\/span><span style=\"color: #007700;\">,<\/span><span style=\"color: #0000bb;\">ip2long<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$ip<\/span><span style=\"color: #007700;\">));<\/span>\r\nreturn\u00a0(\u00a0(<span style=\"color: #0000bb;\">$ip_dec<\/span><span style=\"color: #007700;\">&gt;=<\/span><span style=\"color: #0000bb;\">$lower_dec<\/span><span style=\"color: #007700;\">)\u00a0&amp;&amp;\u00a0(<\/span><span style=\"color: #0000bb;\">$ip_dec<\/span><span style=\"color: #007700;\">&lt;=<\/span><span style=\"color: #0000bb;\">$upper_dec<\/span><span style=\"color: #007700;\">)\u00a0);<\/span><\/pre>\n<\/blockquote>\n<p>Next we have the challenge of handing the CIDR netmasks. What we could do is to take a CIDR format IPaddress\/netmask and calculate the Start and End IPs of that block and proceed as before &#8211; but that would be no fun &#8211; and would mean I haven&#8217;t really taught anything through this article.<\/p>\n<p>The method we&#8217;re going to use here is how all the world&#8217;s Internet routers determine if a destination IP is in a specific CIDR address space. And we&#8217;re going to get down and dirty with bitmasks and logical bitwise operators.<\/p>\n<p>So using a real world example, my webserver IP 80.76.201.37 and the netblock within which it resides is 80.76.201.32\/27, how does this all work?<\/p>\n<p>Well the \/27 indicates that the first 27 bits of the IP address are the same network and IP address in that network (range) will have those same identical first 27 bits.\u00a0 Bits 28-32 are variable and allow 5 bits of variation.\u00a0 If you know your binary, then this means 32 possible IPs. (However with routing, you can&#8217;t use the bottom and top IP from any range as these are special and mean the network and broadcast addresses respectively. [This is also why a \/31 isn&#8217;t much use (except for PPP links) as you can&#8217;t use the 2 addresses that space gives you]).<\/p>\n<p>So thinking logically, bitwise, if I take my IP address and the CIDR spec, then all I have to do is check that the first 27 bits all match and I&#8217;m good. Correct.\u00a0 So how would we do this in PHP? Sound&#8217;s simple, lets just use PHP&#8217;s bitwise logical AND operator: &amp;<\/p>\n<p>Again, correct.<\/p>\n<p>In order to do this we need to convert 27 into what 27 really means &#8211; a 32 bit number of 27 ones and 5 zeros in binary (which is what 255.255.255.224 really looks like).<\/p>\n<p>In pseudo-code you could then do if (IP &amp; BITMASK) == (RANGE &amp; BITMASK) then all is good and you know that the IP is within the range.<\/p>\n<p>Visualising this using our real IP address (using the very handy unix tool ipcalc):<\/p>\n<blockquote>\n<pre><span style=\"font-family: 'courier new', courier, monospace;\"><small>Address: <strong>80.76.201.37<\/strong> <strong>01010000.01001100.11001001.001<\/strong>00101\r\nNetmask:   255.255.255.224 11111111.11111111.11111111.11100000\r\nWildcard:  0.0.0.31        00000000.00000000.00000000.00011111\r\nNetwork:   80.76.201.32\/27 01010000.01001100.11001001.00100000\r\nHostMin:   80.76.201.33    01010000.01001100.11001001.00100001\r\nHostMax:   80.76.201.62    01010000.01001100.11001001.00111110\r\nBroadcast: 80.76.201.63    01010000.01001100.11001001.00111111\r\nHosts\/Net: 30<\/small><\/span><\/pre>\n<\/blockquote>\n<p>You can see this in the Wildcard line of 0.0.0.31, and the Network ORed with Wildcard results in the Broadcast address: 80.76.201.63.<\/p>\n<p>Knowing this, then the IP address ANDed with the Network address will result in the same value as the Range ANDed with the Network address and so can be used as a comparison for an IP residing within that broadcast range.<\/p>\n<p>How can we work out this Network address in PHP, again we have two strategies, one is to so a simple substr() and take the left most significant bits of the range and then simply pad out to the right with 0s.\u00a0 Or we can do some math with &#8220;NOT of 2 to the power of (32-range) &#8211; 1&#8221;. Thus for our value \/27 this gives us the decimal value 31, NOTed results in (65536-31)\u00a0 (representational in the bit form &#8211; PHP will see it as a negative integer, but we don&#8217;t need to worry about that).<\/p>\n<p>I&#8217;m sure by now, your screaming for some code (and if you stuck around this long, you really deserve it).<\/p>\n<p>Code to manipulate a range\/netmask into a broadcast address, using math, assuming:<\/p>\n<blockquote>\n<pre><span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$ip\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #dd0000;\">\"80.76.201.37\"<\/span><span style=\"color: #007700;\">;<\/span><\/span>\r\n<span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$range\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #dd0000;\">\"80.76.201.32\"<\/span><span style=\"color: #007700;\">;<\/span><\/span>\r\n<span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$netmask\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">27<\/span><span style=\"color: #007700;\">;<\/span><\/span><\/pre>\n<\/blockquote>\n<p>We can convert the IPs to long integers using ip2long (denoted by variable_dec &#8211; dec being short for decimal):<\/p>\n<blockquote>\n<pre><span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$range_dec\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">ip2long<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$range<\/span><span style=\"color: #007700;\">);<\/span><\/span>\r\n<span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$ip_dec\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">ip2long<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">$ip<\/span><span style=\"color: #007700;\">);<\/span><\/span><\/pre>\n<\/blockquote>\n<p>This gives us the basis of our math, we now just need to work out the broadcast address.<\/p>\n<p>Strategy 1 using str_pad to create a string by padding with 1s and 0s.<\/p>\n<blockquote>\n<pre><span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$netmask_dec\u00a0<\/span><span style=\"color: #007700;\">= <\/span><\/span><span style=\"color: #000000;\"><span style=\"color: #0000bb;\">bindec( str_pad<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #dd0000;\">''<\/span><span style=\"color: #007700;\">, <\/span><span style=\"color: #0000bb;\">$netmask<\/span><span style=\"color: #007700;\">,\u00a0<\/span><span style=\"color: #dd0000;\">'1'<\/span><span style=\"color: #007700;\">)<\/span><\/span>\r\n<span style=\"color: #000000;\"><span style=\"color: #007700;\">\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 . <\/span><span style=\"color: #0000bb;\">str_pad<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #dd0000;\">''<\/span><span style=\"color: #007700;\">,\u00a0<\/span><span style=\"color: #0000bb;\">32<\/span><span style=\"color: #007700;\">-<\/span><span style=\"color: #0000bb;\">$netmask<\/span><span style=\"color: #007700;\">,\u00a0<\/span><span style=\"color: #dd0000;\">'0'<\/span><span style=\"color: #007700;\">) );<\/span><\/span><\/pre>\n<\/blockquote>\n<p>We can achieve the same result though mathematics by NOTing the wildcard value. This is our Strategy 2:<\/p>\n<blockquote>\n<pre><span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$wildcard_dec\u00a0<\/span><span style=\"color: #007700;\">=\u00a0<\/span><span style=\"color: #0000bb;\">pow<\/span><span style=\"color: #007700;\">(<\/span><span style=\"color: #0000bb;\">2<\/span><span style=\"color: #007700;\">,\u00a0(<\/span><span style=\"color: #0000bb;\">32<\/span><span style=\"color: #007700;\">-<\/span><span style=\"color: #0000bb;\">$netmask<\/span><span style=\"color: #007700;\">))\u00a0-\u00a0<\/span><span style=\"color: #0000bb;\">1<\/span><span style=\"color: #007700;\">;<\/span><\/span>\r\n<span style=\"color: #000000;\"><span style=\"color: #0000bb;\">$netmask_dec\u00a0<\/span><span style=\"color: #007700;\">=\u00a0~\u00a0<\/span><span style=\"color: #0000bb;\">$wildcard_dec<\/span><span style=\"color: #007700;\">;<\/span><\/span><\/pre>\n<\/blockquote>\n<p>Once we know the netmask address (in decimal) as we have here, we can know that, if by ANDing this with the original IP to check results against the Range ANDed with the Netmask, then the IP is within the range defined by the range\/mask.<\/p>\n<p>This can be checked easily with:<\/p>\n<blockquote>\n<pre><span style=\"color: #000000;\"><span style=\"color: #007700;\">return\u00a0((<\/span><span style=\"color: #0000bb;\">$ip_dec\u00a0<\/span><span style=\"color: #007700;\">&amp;\u00a0<\/span><span style=\"color: #0000bb;\">$netmask_dec<\/span><span style=\"color: #007700;\">)\u00a0==\u00a0(<\/span><span style=\"color: #0000bb;\">$range_dec\u00a0<\/span><span style=\"color: #007700;\">&amp;\u00a0<\/span><span style=\"color: #0000bb;\">$netmask_dec<\/span><span style=\"color: #007700;\">));<\/span><\/span><\/pre>\n<p>&nbsp;<\/p><\/blockquote>\n<p>I have pulled all of this logic together in a easily included file to provide a single function called ip_in_range($ip, $range) in which $ip is the IP address you want to validate and $range is a any of the above formats, Wildcard, Start-End addressing or CIDR.\u00a0 The function will return a simple TRUE or FALSE if the IP is in that range.<\/p>\n<p>The source code to the all-in function is available here:<\/p>\n<blockquote>\n<address><a href=\"http:\/\/pgregg.com\/projects\/php\/ip_in_range\/ip_in_range.phps\">http:\/\/pgregg.com\/projects\/php\/ip_in_range\/ip_in_range.phps<\/a><\/address>\n<\/blockquote>\n<p>With an example run (and source code):<\/p>\n<blockquote>\n<address><a href=\"http:\/\/pgregg.com\/projects\/php\/ip_in_range\/test.php\">http:\/\/pgregg.com\/projects\/php\/ip_in_range\/test.php<\/a><\/address>\n<\/blockquote>\n<p>I hope this article has been educational, please feel free to leave comments or feedback.<br \/>\nUpdate: There have been questions about PHP&#8217;s signed integers and my use of bit operations in the code.\u00a0\u00a0 It is important to recognise that when dealing with signed or unsigned 32 bit integers purely as bit patterns for masking with a netmask or broadcast address pattern &#8211; the fact that a number (128.0.0.0 or above) really is negative, doesn&#8217;t have any impact on the validity of the result.\u00a0 The only impact to not having signed 32 bit integers is in the Start-End range check (example 2 above: 10.1.0.0-10.1.255.255) where a range spanning the switch from positive to negative would be catastrophic to the check.\u00a0 We can safely work around that problem by using floating point numbers as we are only doing &lt;= and &gt;= comparisons and not attempting any bitwise operators (which don&#8217;t work on floats).<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I spend a lot of time lurking in the #PHP channel (efnet and freenode, please &#8211; no flamewars) and this topic is a commonly asked one that usually gets a simplified answer in the form of using strpos(), or at best an ip2long() in a greater than and less than answer. Unfortunately although people usually &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/blog.pgregg.com\/blog\/2009\/04\/php-algorithms-determining-if-an-ip-is-within-a-specific-range\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;PHP algorithms: Determining if an IP is within a specific range.&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[10,11],"tags":[],"class_list":["post-166","post","type-post","status-publish","format-standard","hentry","category-php","category-software"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/pbQOUu-2G","jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/posts\/166","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/comments?post=166"}],"version-history":[{"count":4,"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/posts\/166\/revisions"}],"predecessor-version":[{"id":509,"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/posts\/166\/revisions\/509"}],"wp:attachment":[{"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/media?parent=166"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/categories?post=166"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.pgregg.com\/blog\/wp-json\/wp\/v2\/tags?post=166"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}