I have an IP address and I'm given two other IP addresses which together creates an IP range. I want to check if the first IP address is within this range. How can i find that out in PHP?
-
9+1 To bump this question back to 0. I can't see why it's down voted – Hubro Jun 20 '12 at 14:32
-
4@Codemonkey because of the lack of research effort. – CodeCaster Jun 20 '12 at 14:33
-
2How do you know how much research was done before the question was asked? Seems to me like your problem with this question is that it's too short. I'm removing my up vote until he better defines what he means by "range" though – Hubro Jun 20 '12 at 14:35
-
@Codemonkey do I really have to explain the importance of a question asker showing what he/she has tried and found? – CodeCaster Jun 20 '12 at 14:38
-
3im a SHE, ok range is if its within say, from abc.def.ghi.jkl to mno.pqr.stu.vwx – guitarlass Jun 20 '12 at 14:46
-
well ,i did search but i couldn't find a good answer. i think my query was not good enough. – guitarlass Jun 20 '12 at 14:48
-
@guitarlass - I feel your pain, when searching for something without knowing the answer, it often yields a lot of unknown/unrelated results. I had a similiar situation, so my question, and the excellent answer I received might help out somehow at http://stackoverflow.com/questions/10837536/summary-of-mysql-detail-records-matching-by-ip-address-ranges-mysql-jedi-knigh – GDP Jun 20 '12 at 14:57
-
1@GDP _"searching for something without knowing the answer"_ - Why else would you be searching? – CodeCaster Jun 20 '12 at 15:03
-
2I spent most of yesterday looking for some specific answers about "range" "zoom" "charts"....see what YOU get and tell me how many describe the default setting of highcharts. When you don't know the answer, using the right terms in google can be a very exhaustive exercise in futility. I spend at least 2hrs searching before I ask a question, and usually get an answer with the right "keywords", that then yields every specific answer on the topic I could dream of. – GDP Jun 20 '12 at 15:10
-
So what you're saying is *"searching for something without knowing how to properly formulate it"* – Hubro Jun 20 '12 at 15:11
-
All of the answers here work, but they SUCK in terms of performance. Normal indexing does not work with a dataset of range values. See https://blog.jcole.us/2007/11/24/on-efficiently-geo-referencing-ips-with-maxmind-geoip-and-mysql-gis/ – symcbean Aug 07 '16 at 15:35
11 Answers
With ip2long()
it's easy to convert your addresses to numbers. After this, you just have to check if the number is in range:
if ($ip <= $high_ip && $low_ip <= $ip) {
echo "in range";
}

- 75,622
- 18
- 128
- 150

- 51,017
- 10
- 98
- 115
-
Why are IP addresses written as x.x.x.xxx instead of an normal number? – Maciek Semik May 14 '16 at 06:06
-
6@MaciekSemik Because it's easier to read, write and remember for humans. – Carsten Jun 29 '16 at 18:15
-
344 up votes plus approved answer, when it has a fatal bug? Failing test case: `$high_ip = ip2long('129.255.255.255'); $low_ip = ip2long('127.0.0.0'); $ip = ip2long('127.3.4.5'); if ($ip <= $high_ip && $low_ip <= $ip) { echo "$ip is in range of $low_ip to $high_ip"; } else { echo "$ip is NOT in range of $low_ip to $high_ip"; }` Output = `2130904069 is NOT in range of 2130706432 to -2113929217 `. That should give you a clue as to why ip2long() does not work. – Rick James Aug 11 '16 at 21:14
-
1@RickJames I tried in a PHP Sandbox and it seems [to be working](http://sandbox.onlinephpfunctions.com/code/dcea0bdd7c13dc5750aaa38095b33a2afaf71b4c) with PHP version from 4.4 to 7. :) What PHP version have you got? – Almouro Aug 30 '16 at 15:15
-
1I'm looking at `PHP_VERSION=5.4.12 PHP_INT_MAX=2147483647 PHP_INT_SIZE=4`. – Rick James Aug 30 '16 at 15:24
-
6@Rick James's issue is a product of integer size limitations on 32 bit systems where the value 'wraps around' to be a negative number. This answer as written is *not* 32 bit system safe. The workaround on 32 bit system is to run the returned values through sprintf to get unsigned integers before doing the comparison, see http://stackoverflow.com/questions/3062843/php-ip2long-returning-negative-val – siliconrockstar Oct 21 '16 at 19:50
This website offers a great guide and code to do this (which was the first result of a Google search for this question):
<?php
/*
* ip_in_range.php - Function to determine if an IP is located in a
* specific range as specified via several alternative
* formats.
*
* Network ranges can be specified as:
* 1. Wildcard format: 1.2.3.*
* 2. CIDR format: 1.2.3/24 OR 1.2.3.4/255.255.255.0
* 3. Start-End IP format: 1.2.3.0-1.2.3.255
*
* Return value BOOLEAN : ip_in_range($ip, $range);
*
* Copyright 2008: Paul Gregg <pgregg@pgregg.com>
* 10 January 2008
* Version: 1.2
*
* Source website: http://www.pgregg.com/projects/php/ip_in_range/
* Version 1.2
*
* This software is Donationware - if you feel you have benefited from
* the use of this tool then please consider a donation. The value of
* which is entirely left up to your discretion.
* http://www.pgregg.com/donate/
*
* Please do not remove this header, or source attibution from this file.
*/
// decbin32
// In order to simplify working with IP addresses (in binary) and their
// netmasks, it is easier to ensure that the binary strings are padded
// with zeros out to 32 characters - IP addresses are 32 bit numbers
Function decbin32 ($dec) {
return str_pad(decbin($dec), 32, '0', STR_PAD_LEFT);
}
// ip_in_range
// This function takes 2 arguments, an IP address and a "range" in several
// different formats.
// Network ranges can be specified as:
// 1. Wildcard format: 1.2.3.*
// 2. CIDR format: 1.2.3/24 OR 1.2.3.4/255.255.255.0
// 3. Start-End IP format: 1.2.3.0-1.2.3.255
// The function will return true if the supplied IP is within the range.
// Note little validation is done on the range inputs - it expects you to
// use one of the above 3 formats.
Function ip_in_range($ip, $range) {
if (strpos($range, '/') !== false) {
// $range is in IP/NETMASK format
list($range, $netmask) = explode('/', $range, 2);
if (strpos($netmask, '.') !== false) {
// $netmask is a 255.255.0.0 format
$netmask = str_replace('*', '0', $netmask);
$netmask_dec = ip2long($netmask);
return ( (ip2long($ip) & $netmask_dec) == (ip2long($range) & $netmask_dec) );
} else {
// $netmask is a CIDR size block
// fix the range argument
$x = explode('.', $range);
while(count($x)<4) $x[] = '0';
list($a,$b,$c,$d) = $x;
$range = sprintf("%u.%u.%u.%u", empty($a)?'0':$a, empty($b)?'0':$b,empty($c)?'0':$c,empty($d)?'0':$d);
$range_dec = ip2long($range);
$ip_dec = ip2long($ip);
# Strategy 1 - Create the netmask with 'netmask' 1s and then fill it to 32 with 0s
#$netmask_dec = bindec(str_pad('', $netmask, '1') . str_pad('', 32-$netmask, '0'));
# Strategy 2 - Use math to create it
$wildcard_dec = pow(2, (32-$netmask)) - 1;
$netmask_dec = ~ $wildcard_dec;
return (($ip_dec & $netmask_dec) == ($range_dec & $netmask_dec));
}
} else {
// range might be 255.255.*.* or 1.2.3.0-1.2.3.255
if (strpos($range, '*') !==false) { // a.b.*.* format
// Just convert to A-B format by setting * to 0 for A and 255 for B
$lower = str_replace('*', '0', $range);
$upper = str_replace('*', '255', $range);
$range = "$lower-$upper";
}
if (strpos($range, '-')!==false) { // A-B format
list($lower, $upper) = explode('-', $range, 2);
$lower_dec = (float)sprintf("%u",ip2long($lower));
$upper_dec = (float)sprintf("%u",ip2long($upper));
$ip_dec = (float)sprintf("%u",ip2long($ip));
return ( ($ip_dec>=$lower_dec) && ($ip_dec<=$upper_dec) );
}
echo 'Range argument is not in 1.2.3.4/24 or 1.2.3.4/255.255.255.0 format';
return false;
}
}
?>

- 3
- 3

- 217,595
- 99
- 455
- 496
-
Do you happen to have another link for this answer? The one here is no longer working. – Taryn Jun 06 '14 at 01:54
-
-
1
-
-
-
I've used this code for a while, and it's great but have some issues when the ips are not well formatted. For ranges, the ip2long part may return 0 and mess the comparation. For example: ip_in_range('1.1.1.1', '1.2.03.0-1.2.3.255') returns True. – ruizfrontend May 09 '21 at 08:19
I found this little gist which has simpler/shorter solution than already mentioned here.
Second argument (range) can either be a static ip such as 127.0.0.1 or a range like 127.0.0.0/24.
/**
* Check if a given ip is in a network
* @param string $ip IP to check in IPV4 format eg. 127.0.0.1
* @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
* @return boolean true if the ip is in this range / false if not.
*/
function ip_in_range( $ip, $range ) {
if ( strpos( $range, '/' ) === false ) {
$range .= '/32';
}
// $range is in IP/CIDR format eg 127.0.0.1/24
list( $range, $netmask ) = explode( '/', $range, 2 );
$range_decimal = ip2long( $range );
$ip_decimal = ip2long( $ip );
$wildcard_decimal = pow( 2, ( 32 - $netmask ) ) - 1;
$netmask_decimal = ~ $wildcard_decimal;
return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
}

- 6,950
- 3
- 42
- 51
-
10Change `== false` by `=== false`, strpos return 0 value when the position is the first byte, in php 0 is equals to false. `===` compare types. – e-info128 Jun 08 '17 at 15:07
if(version_compare($low_ip, $ip) + version_compare($ip, $high_ip) === -2) {
echo "in range";
}

- 3,016
- 3
- 22
- 18
-
2
-
Glad someone appreciates it after all this time. I thought it was rather clever ;) – Bas Oct 09 '18 at 07:36
-
1
Comparing in range (Including Ipv6 support)
The following two functions were introduced in PHP 5.1.0, inet_pton
and inet_pton
. Their purpose is to convert human readable IP addresses into their packed in_addr
representation. Since the result is not pure binary, we need to use the unpack
function in order to apply bitwise operators.
Both functions support IPv6 as well as IPv4. The only difference is how you unpack the address from the results. With IPv6, you will unpack with contents with A16, and with IPv4, you will unpack with A4.
To put the previous in a perspective here is a little sample output to help clarify:
// Our Example IP's
$ip4= "10.22.99.129";
$ip6= "fe80:1:2:3:a:bad:1dea:dad";
// ip2long examples
var_dump( ip2long($ip4) ); // int(169239425)
var_dump( ip2long($ip6) ); // bool(false)
// inet_pton examples
var_dump( inet_pton( $ip4 ) ); // string(4)
var_dump( inet_pton( $ip6 ) ); // string(16)
We demonstrate above that the inet_* family supports both IPv6 and v4. Our next step will be to translate the packed result into an unpacked variable.
// Unpacking and Packing
$_u4 = current( unpack( "A4", inet_pton( $ip4 ) ) );
var_dump( inet_ntop( pack( "A4", $_u4 ) ) ); // string(12) "10.22.99.129"
$_u6 = current( unpack( "A16", inet_pton( $ip6 ) ) );
var_dump( inet_ntop( pack( "A16", $_u6 ) ) ); //string(25) "fe80:1:2:3:a:bad:1dea:dad"
Note : The current function returns the first index of an array. It is equivelant to saying $array[0].
After the unpacking and packing, we can see we achieved the same result as input. This is a simple proof of concept to ensure we are not losing any data.
Finally use,
if ($ip <= $high_ip && $low_ip <= $ip) {
echo "in range";
}
Reference: php.net

- 1
- 1

- 37,929
- 33
- 189
- 256
-
seems not to work for me, at which point do you would perform the comparation? – Karl Adler Apr 07 '16 at 17:11
-
2Why has this answer taken one down vote? This is actually better than others – Hossein Shahdoost Sep 23 '18 at 12:50
Use the excellent rlanvin/php-ip which supports IPv4 and IPv6 (via the GMP extension):
use PhpIP\IPBlock;
$block = IPBlock::create('10.0.0.0/24');
$block->contains('10.0.0.42'); // true
See their docs for more examples.

- 535
- 5
- 11
I would always suggest ip2long, but sometimes you need to check networks and etc. I've built in the past a IPv4 Networking class, which can be found here on HighOnPHP.
The nice thing about working with IP addressing is it's flexibility especially when using BITWISE operators. AND'ing, OR'ing and BitShifting will work like a charm.

- 13,917
- 6
- 60
- 87
This is old post but there is one good solution on the GitHub what I made.
$ip_in_range = is_ip_in_range('54.208.101.55', array(
'50.16.241.113' => '50.16.241.117',
'54.208.100.253' => '54.208.102.37'
));
This function will return matched IP or boolean false on the not match.
Here is the function:
// https://github.com/CreativForm/PHP-Solutions/blob/master/function.ip.in.range.php
function is_ip_in_range( $ip, $range ){
if(!is_array($range)) return false;
// Let's search first single one
ksort($range);
// We need numerical representation of the IP
$ip2long = ip2long($ip);
// Non IP values needs to be removed
if($ip2long !== false)
{
// Let's loop
foreach($range as $start => $end)
{
// Convert to numerical representations as well
$end = ip2long($end);
$start = ip2long($start);
$is_key = ($start === false);
// Remove bad one
if($end === false) continue;
// Here we looking for single IP does match
if(is_numeric($start) && $is_key && $end === $ip2long)
{
return $ip;
}
else
{
// And here we have check is in the range
if(!$is_key && $ip2long >= $start && $ip2long <= $end)
{
return $ip;
}
}
}
}
// Ok, it's not finded
return false;
}

- 6,249
- 6
- 45
- 78
Btw, in case you need to check multiple ranges at once you can add few rows to the code in order to pass array of ranges. The second argument can be an array or string:
public static function ip_in_range($ip, $range) {
if (is_array($range)) {
foreach ($range as $r) {
return self::ip_in_range($ip, $r);
}
} else {
if ($ip === $range) { // in case you have passed a static IP, not a range
return TRUE;
}
}
// The rest of the code follows here..
// .........
}

- 3,517
- 2
- 26
- 24
Here is my approach of the subject.
function validateIP($whitelist, $ip) {
// e.g ::1
if($whitelist == $ip) {
return true;
}
// split each part of the IP address and set it to an array
$validated1 = explode(".", $whitelist);
$validated2 = explode(".", $ip);
// check array index to avoid undefined index errors
if(count($validated1) >= 3 && count($validated2) == 4) {
// check that each value of the array is identical with our whitelisted IP,
// except from the last part which doesn't matter
if($validated1[0] == $validated2[0] && $validated1[1] == $validated2[1] && $validated1[2] == $validated2[2]) {
return true;
}
}
return false;
}

- 169
- 1
- 5
I used this for my client:
$clientIpArray = explode(".", $clientIp);
$fromArray = explode(".", $from);
$toArray = explode(".", $to);
if( ((str_pad($clientIpArray[0], 3, "0", STR_PAD_LEFT) >= str_pad($fromArray[0], 3, "0", STR_PAD_LEFT)) && (str_pad($clientIpArray[0], 3, "0", STR_PAD_LEFT) <= str_pad($toArray[0], 3, "0", STR_PAD_LEFT)))
&&((str_pad($clientIpArray[1], 3, "0", STR_PAD_LEFT) >= str_pad($fromArray[1], 3, "0", STR_PAD_LEFT)) && (str_pad($clientIpArray[1], 3, "0", STR_PAD_LEFT) <= str_pad($toArray[1], 3, "0", STR_PAD_LEFT)))
&&((str_pad($clientIpArray[2], 3, "0", STR_PAD_LEFT) >= str_pad($fromArray[2], 3, "0", STR_PAD_LEFT)) && (str_pad($clientIpArray[2], 3, "0", STR_PAD_LEFT) <= str_pad($toArray[2], 3, "0", STR_PAD_LEFT)))
&&((str_pad($clientIpArray[3], 3, "0", STR_PAD_LEFT) >= str_pad($fromArray[3], 3, "0", STR_PAD_LEFT)) && (str_pad($clientIpArray[3], 3, "0", STR_PAD_LEFT) <= str_pad($toArray[3], 3, "0", STR_PAD_LEFT)))){
echo "IP within range";
}
For example, let's take this example:
$clientIp = "120.02.3.112";
$from = "1.02.1.112";
$to = "120.02.20.112";
As you can understand this IP is within the range. If you try to compare it as it is it won't work. My solution is to divide the IP into elements, so for example the generated arrays would be:
$clientIpArray = ["120","02","3","112"];
$fromArray = ["1","02","1","112"];
$toArray = ["120","02","20","112"];
We have 4 elements to compare, here I used str_pad function to generate a 3 characters string from each element, so instead of checking if "3" is between "1" and "20" (which is not true) we will check if "003" is between "001" and "020" (which is correct).
-
Make use of [intval()](https://www.php.net/manual/en/function.intval.php) to use simple comparison operators. You don't HAVE to stick with strings. Simple number comparisons would make the code more readable, too. – Peter Krebs Feb 14 '22 at 09:35