PHP Classes

File: CIDR.php

Recommend this page to a friend!
  Classes of Frank Forte   PHP IP CIDR Check   CIDR.php   Download  
File: CIDR.php
Role: Class source
Content type: text/plain
Description: Class source
Class: PHP IP CIDR Check
Check if IP address is within a range
Author: By
Last change:
Date: 8 years ago
Size: 6,492 bytes
 

Contents

Class file image Download
<?php
/**
 * CIDR class helps validate an ip address against a CIDR ip range.
 *
 * This class has not been optimized, but works comparing an ip address
 * with a wild card address or a CIDR ip range.
 *
 * @package CIDR
 * @version 1.0
 * @author Frank Forte
 * @copyright Copyright (c) 2015 Frank Forte
 * @license BSD-3 License https://opensource.org/licenses/BSD-3-Clause
 */
class CIDR{

  
/**
    * compare an IPv4 or IPv6 address with a CIDR address or range
    *
    * @param string $address a valid IPv6 address
    * @param string $subnet a valid IPv6 subnet[/mask]
    * @return boolean whether $address is within the ip range made up of the subnet and mask
    */
   
public static function match($ip, $cidr){
       
       
// make sure we compare ip addresses as case insensitive
       
$ip = strtolower($ip);
       
$cidr = strtolower($cidr);
       
       
// comparing exact ip?
       
if($ip == $cidr){ return true; }

       
        if(
strpos($cidr,'/') !== false){
             list(
$subnet, $mask) = explode('/', $cidr);
        } else {
           
$subnet = $cidr;
           
$mask = '';
        }

       
// validate ips and shorten them
       
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {

           
$ipVersion = 'v4';
           
// shorten ip
           
$ip = preg_replace( '/^(.*\.|.*:)?0+([1-9])/','$1$2',$ip );

        } elseif (
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {

           
$ipVersion = 'v6';
           
// shorten ip
           
$ip = self::IPv6_compress($ip);

        } else {

            return
false; // invalid ip
       
}

       
// shorten cidr and subnet
       
if(strpos($subnet,':') === false)
        {
           
$subnet = preg_replace( '/^(.*\.|.*:)?0+([1-9])/','$1$2',$subnet );
        }
        else
        {
           
$pos = strpos($subnet,'*');
            if(
$pos !== false)
            {
               
$subnet = substr($subnet,0,$pos);
               
$i = 0;
               
$subnet = explode(':',$subnet);
               
$size = $j = sizeof($subnet);
                while(
$j < 8){
                   
$subnet[] = '0';
                   
$j++;
                   
$i++;
                }
               
$subnet = implode(':',$subnet);
                if(
substr($subnet,-1) == ':'){
                   
$subnet .= '0';
                }
            }
           
           
$subnet = self::IPv6_compress($subnet);
           
            if(
$pos !== false)
            {
               
$subnet = explode(':',$subnet);
               
$j = sizeof($subnet);
                while(
$j > $size)
                {
                   
array_pop($subnet);
                   
$j--;
                }
               
$subnet = implode(':',$subnet).'*';
            }
        }

       
// shortened cidr
       
$cidr = ($mask ? $subnet.'/'.$mask : $subnet);
       
       
// if $cidr is ipv6, convert $ip to ipv6 for easier comparison
       
if(strpos($subnet,':') !== false && $ipVersion == 'v4') {
           
           
$v6bits = array('0000','0000','0000','0000','0000','0000',$ip);
           
           
$ip4parts = explode('.', $v6bits[count($v6bits)-1]);
           
$ip6trans = sprintf("%02x%02x:%02x%02x", $ip4parts[0], $ip4parts[1], $ip4parts[2], $ip4parts[3]);
           
$v6bits[count($v6bits)-1] = $ip6trans;

           
$ip = implode(':', $v6bits);
           
// shorten ip
           
$ip = self::IPv6_compress($ip);
           
           
$ipVersion = 'v6';
        }

        if(
$ip == $cidr)
        {
            return
true;
        }

       
// wildcard matching (easier since we already shortened or "canonicalized" ip and cidr above)
       
$pos = strpos($cidr,'*');
        if(
$pos !== false)
        {
            if(
substr($ip,0,$pos) == substr($cidr,0,$pos))
            {
                return
true;
            }
            else
            {
                return
false;
            }
        }

         switch (
$ipVersion) {
            case
'v4':
                return
self::IPv4Match($ip, $subnet, $mask);
                break;
            case
'v6':
                return
self::IPv6Match($ip, $subnet, $mask);
                break;
        }
    }



  
/**
    * Check IPv6 address is within an IP range
    *
    * @param string $address a valid IPv6 address
    * @param string $subnet a valid IPv6 subnet
    * @param string $mask a valid IPv6 subnet mask
    * @return boolean whether $address is within the ip range made up of the subnet and mask
    */
   
private static function IPv6Match($ip, $subnet, $mask)
    {
       
$subnet = inet_pton($subnet);
       
$ip = inet_pton($ip);

       
// thanks to MW on http://stackoverflow.com/questions/7951061/matching-ipv6-address-to-a-cidr-subnet
       
$binMask = str_repeat("f", $mask / 4);
        switch (
$mask % 4) {
            case
0:
                break;
            case
1:
               
$binMask .= "8";
                break;
            case
2:
               
$binMask .= "c";
                break;
            case
3:
               
$binMask .= "e";
                break;
        }
       
$binMask = str_pad($binMask, 32, '0');
       
$binMask = pack("H*", $binMask);


        return (
$ip & $binMask) == $subnet;
    }

  
/**
    * Check IPv4 address is within an IP range
    *
    * @param string $address a valid IPv4 address
    * @param string $subnet a valid IPv4 subnet
    * @param string $mask a valid IPv4 subnet mask
    * @return boolean whether $address is within the ip rage made up of the subnet and mask
    */
    
private static function IPv4Match($address, $subnet, $mask)
    {
       
// credit goes to Sam on http://stackoverflow.com/questions/594112/matching-an-ip-to-a-cidr-mask-in-php5
       
if ((ip2long($address) & ~((1 << (32 - $mask)) - 1)) == ip2long($subnet)) {
            return
true;
        }

        return
false;
    }

   
/**
    * Compress an IPv6 Address
    *
    * @param string $ip a valid IPv6 address or CIDR
    * @return string IPv6 ip address or CIDR in short form notation
    */
   
public static function IPv6_compress($ip)
    {
       
$bits = explode('/',$ip); // in case this is a CIDR range
       
        // want to expand and re-compress in case we have "::" in different spots.
       
$bits[0] = self::IPv6_expand($bits[0]);

       
$bits[0] = inet_ntop(inet_pton($bits[0]));
        return
strtolower(implode('/',$bits));
    }

   
/**
    * Expand an IPv6 Address
    *
    * @param string $ip a valid IPv6 address
    * @return string IPv6 ip address in long form notation
    */
   
public static function IPv6_expand($ip){
       
       
$bits = explode('/',$ip); // in case this is a CIDR range
       
        // add missing components
       
if (strpos($bits[0], '::') !== false) {
           
$part = explode('::', $bits[0]);
           
$part[0] = explode(':', $part[0]);
           
$part[1] = explode(':', $part[1]);
           
$missing = array();
            for (
$i = 0; $i < (8 - (count($part[0]) + count($part[1]))); $i++)
               
array_push($missing, '0000');
           
$missing = array_merge($part[0], $missing);
           
$part = array_merge($missing, $part[1]);
        } else {
           
$part = explode(":", $bits[0]);
        }
       
       
// Pad components to 4 characters
       
foreach ($part as &$p) {
            while (
strlen($p) < 4) $p = '0' . $p;
        }
        unset(
$p);

       
$bits[0] = implode(':', $part);

       
// if it is the incorrect length, something went wrong.
       
if (strlen($bits[0]) != 39) {
            return
false;
        }
        return
strtolower(implode('/',$bits));
    }
}
?>