Reputation: 1165
I have an ACL implementation in PHP that takes an array of IPv4 networks and checks if an address matches one of them, works like this:
$acl = array("192.168.0.0/24", "10.6.0.0/16");
if (address_in_range($_SERVER['REMOTE_ADDR'], $acl) {
// Allow the action...
}
The address_in_range() function I made works for IPv4, I did it like this:
list($subnet, $mask) = explode('/', $cidr);
if ((ip2long($ip) & ~((1 << (32 - $mask)) - 1) ) == ip2long($subnet)) {
return true;
}
However, I also need it to work with IPv6 addresses, so I can supply an array of mixed IPv4 and IPv6 ranges to my function, like this:
$acl = array("192.168.0.0/24", "10.6.0.0/16", "2a01:1098:15::/48");
And the checked IP can be either IPv4 or IPv6.
Upvotes: 2
Views: 1039
Reputation: 1165
I ended up writing this function to do the job:
/**
* Check if an IP address matches an entry in an access control list (ACL)
* Returns true if match, false otherwise (including if $ip is not a valid IP
* address). Works with both IPv4 and IPv6 addresses.
*
* Example: check_acl("10.6.1.16", array("10.6.0.0/16","2a01:fe8:95::/48"));
* @param string $ip IP address to check
* @param array $acl Array of CIDR-notation IP addresses
* @return boolean
*/
function check_acl($ip, $acl) {
$ipb = inet_pton($ip);
$iplen = strlen($ipb);
if (strlen($ipb) < 4) {
// Invalid IP address
return false;
}
foreach ($acl as $cidr) {
$ar = explode('/',$cidr);
$ip1 = $ar[0];
$ip1b = inet_pton($ip1);
$ip1len = strlen($ip1b);
if ($ip1len != $iplen) {
// Different type
continue;
}
if (count($ar)>1) {
$bits=(int)($ar[1]);
} else {
$bits = $iplen * 8;
}
for ($c=0; $bits>0; $c++) {
$bytemask = ($bits < 8) ? 0xff ^ ((1 << (8-$bits))-1) : 0xff;
if (((ord($ipb[$c]) ^ ord($ip1b[$c])) & $bytemask) != 0)
continue 2;
$bits-=8;
}
return true;
}
return false;
}
Upvotes: 3