ThomasH
ThomasH

Reputation: 1165

How to check if an IPv6 address is in a specific network (for ACL implementation) in PHP

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

Answers (1)

ThomasH
ThomasH

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

Related Questions