Matching an IP to a CIDR mask in PHP 5?

前端 未结 13 1084
无人共我
无人共我 2020-11-28 04:04

I\'m looking for quick/simple method for matching a given IP4 dotted quad IP to a CIDR notation mask.

I have a bunch of IPs I need to see if they match a range of IP

相关标签:
13条回答
  • 2020-11-28 04:25

    If only using IPv4:

    • use ip2long() to convert the IPs and the subnet range into long integers
    • convert the /xx into a subnet mask
    • do a bitwise 'and' (i.e. ip & mask)' and check that that 'result = subnet'

    something like this should work:

    function cidr_match($ip, $range)
    {
        list ($subnet, $bits) = explode('/', $range);
        if ($bits === null) {
            $bits = 32;
        }
        $ip = ip2long($ip);
        $subnet = ip2long($subnet);
        $mask = -1 << (32 - $bits);
        $subnet &= $mask; # nb: in case the supplied subnet wasn't correctly aligned
        return ($ip & $mask) == $subnet;
    }
    
    0 讨论(0)
  • 2020-11-28 04:27

    Perhaps it is useful to someone.

    Convert bit mask into IP mask:

    // convert 12 => 255.240.0.0
    // ip2long('255.255.255.255') == -1
    $ip = long2ip((-1 << (32 - $bit)) & -1);
    

    Convert IP mask into bit mask:

    // convert 255.240.0.0 => 12
    
    // is valid IP
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false) {
        throw new \InvalidArgumentException(sprintf('Invalid IP "%s".', $ip));
    }
    
    // convert decimal to binary
    $mask = '';
    foreach (explode('.', $ip) as $octet) {
        $mask .= str_pad(decbin($octet), 8, '0', STR_PAD_LEFT);
    }
    
    // check mask
    if (strpos('01', $mask) !== false) {
        // valid   11111111111111111111111100000000 -> 255.255.255.0
        // invalid 11111111111111111111111100000001 -> 255.255.255.1
        throw new \InvalidArgumentException(sprintf('IP mask "%s" is not valid.', $ip));
    }
    
    $bit = substr_count($mask, '1'); // bit mask
    
    0 讨论(0)
  • 2020-11-28 04:30

    Here is one fast 64bits function to do it, please comment the return line you don't need. Accepting any valid Ipv4 with or without valid CIDR Routing Prefix for example 63.161.156.0/24 or 63.161.156.0

    <?php
    function cidr2range($ipv4){
    if ($ip=strpos($ipv4,'/'))
    {$n_ip=(1<<(32-substr($ipv4,1+$ip)))-1;   $ip_dec=ip2long(substr($ipv4,0,$ip)); }
    else
    {$n_ip=0;                                   $ip_dec=ip2long($ipv4);             }
    $ip_min=$ip_dec&~$n_ip;
    $ip_max=$ip_min+$n_ip;
    #Array(2) of Decimal Values Range
    return [$ip_min,$ip_max];
    #Array(2) of Ipv4 Human Readable Range
    return [long2ip($ip_min),long2ip($ip_max)];
    #Array(2) of Ipv4 and Subnet Range
    return [long2ip($ip_min),long2ip(~$n_ip)];
    #Array(2) of Ipv4 and Wildcard Bits
    return [long2ip($ip_min),long2ip($n_ip)];
    #Integer Number of Ipv4 in Range
    return ++$n_ip;
    }
    

    To fast check if a given ipv4 is matching a given CIDR you can do it inline like in this example

    <?php
    $given_cidr='55.55.55.0/24';
    $given_ipv4='55.55.55.55';
    if(($range=cidr2range($given_cidr)) &&
    ($check=ip2long($given_ipv4))!==false &&
    $check>=$range[0] && $check<=$range[1])
    {
    echo 'Yes, '.$given_ipv4.' is included in '.$given_cidr;
    }
    else
    {
    echo 'No, '.$given_ipv4.' is not included in '.$given_cidr;
    }
    

    To get the full range as an array for a given IP (with or without CIDR Routing Prefix) you can use the following code but be carefull because for example 25.25.25.25/16 return an array with 65536 elements and you can easily run out of memory using a smaller Routing Prefix

    <?php
    $result=cidr2range($ipv4);
    for($ip_dec=$result[0];$ip_dec<=$result[1];$ip_dec++)
    $full_range[$ip_dec]=long2ip($ip_dec);
    print_r($full_range);
    

    To fast check if a given ipv4 is matching a given array of IP (with or without CIDR Routing Prefix)

    <?php
    #This code is checking if a given ip belongs to googlebot
    $given_ipv4='74.125.61.208';
    $given_cidr_array=['108.59.93.43/32','108.59.93.40/31','108.59.93.44/30','108.59.93.32/29','108.59.93.48/28','108.59.93.0/27','108.59.93.64/26','108.59.93.192/26','108.59.92.192/27','108.59.92.128/26','108.59.92.96/27','108.59.92.0/27','108.59.94.208/29','108.59.94.192/28','108.59.94.240/28','108.59.94.128/26','108.59.94.16/29','108.59.94.0/28','108.59.94.32/27','108.59.94.64/26','108.59.95.0/24','108.59.88.0/22','108.59.81.0/27','108.59.80.0/24','108.59.82.0/23','108.59.84.0/22','108.170.217.128/28','108.170.217.160/27','108.170.217.192/26','108.170.217.0/25','108.170.216.0/24','108.170.218.0/23','108.170.220.0/22','108.170.208.0/21','108.170.192.0/20','108.170.224.0/19','108.177.0.0/17','104.132.0.0/14','104.154.0.0/15','104.196.0.0/14','107.167.160.0/19','107.178.192.0/18','125.17.82.112/30','125.16.7.72/30','74.125.0.0/16','72.14.192.0/18','77.109.131.208/28','77.67.50.32/27','66.102.0.0/20','66.227.77.144/29','66.249.64.0/19','67.148.177.136/29','64.124.98.104/29','64.71.148.240/29','64.68.64.64/26','64.68.80.0/20','64.41.221.192/28','64.41.146.208/28','64.9.224.0/19','64.233.160.0/19','65.171.1.144/28','65.170.13.0/28','65.167.144.64/28','65.220.13.0/24','65.216.183.0/24','70.32.132.0/23','70.32.128.0/22','70.32.136.0/21','70.32.144.0/20','85.182.250.128/26','85.182.250.0/25','80.239.168.192/26','80.149.20.0/25','61.246.224.136/30','61.246.190.124/30','63.237.119.112/29','63.226.245.56/29','63.158.137.224/29','63.166.17.128/25','63.161.156.0/24','63.88.22.0/23','41.206.188.128/26','12.234.149.240/29','12.216.80.0/24','8.34.217.24/29','8.34.217.0/28','8.34.217.32/27','8.34.217.64/26','8.34.217.128/25','8.34.216.0/24','8.34.218.0/23','8.34.220.0/22','8.34.208.128/29','8.34.208.144/28','8.34.208.160/27','8.34.208.192/26','8.34.208.0/25','8.34.209.0/24','8.34.210.0/23','8.34.212.0/22','8.35.195.128/28','8.35.195.160/27','8.35.195.192/26','8.35.195.0/25','8.35.194.0/24','8.35.192.0/23','8.35.196.0/22','8.35.200.0/21','8.8.8.0/24','8.8.4.0/24','8.6.48.0/21','4.3.2.0/24','23.236.48.0/20','23.251.128.0/19','216.239.32.0/19','216.252.220.0/22','216.136.145.128/27','216.33.229.160/29','216.33.229.144/29','216.34.7.176/28','216.58.192.0/19','216.109.75.80/28','216.74.130.48/28','216.74.153.0/27','217.118.234.96/28','208.46.199.160/29','208.44.48.240/29','208.21.209.0/28','208.184.125.240/28','209.185.108.128/25','209.85.128.0/17','213.200.103.128/26','213.200.99.192/26','213.155.151.128/26','199.192.112.224/29','199.192.112.192/27','199.192.112.128/26','199.192.112.0/25','199.192.113.176/28','199.192.113.128/27','199.192.113.192/26','199.192.113.0/25','199.192.115.80/28','199.192.115.96/27','199.192.115.0/28','199.192.115.128/25','199.192.114.192/26','199.192.114.0/25','199.223.232.0/21','198.108.100.192/28','195.16.45.144/29','192.104.160.0/23','192.158.28.0/22','192.178.0.0/15','206.160.135.240/28','207.223.160.0/20','203.222.167.144/28','173.255.125.72/29','173.255.125.80/28','173.255.125.96/27','173.255.125.0/27','173.255.125.128/25','173.255.124.240/29','173.255.124.232/29','173.255.124.192/27','173.255.124.128/29','173.255.124.144/28','173.255.124.160/27','173.255.124.48/29','173.255.124.32/28','173.255.124.0/27','173.255.124.64/26','173.255.126.0/23','173.255.122.128/26','173.255.122.64/26','173.255.123.0/24','173.255.121.128/26','173.255.121.0/25','173.255.120.0/24','173.255.117.32/27','173.255.117.64/26','173.255.117.128/25','173.255.116.192/27','173.255.116.128/26','173.255.116.0/25','173.255.118.0/23','173.255.112.0/22','173.194.0.0/16','172.102.8.0/21','172.253.0.0/16','172.217.0.0/16','162.216.148.0/22','162.222.176.0/21','180.87.33.64/26','128.177.109.0/26','128.177.119.128/25','128.177.163.0/25','130.211.0.0/16','142.250.0.0/15','146.148.0.0/17'];
    echo '<pre>';
    $in_range=false;
    if (($given_ipv4_dec=ip2long($given_ipv4))!==false)
    {
    foreach($given_cidr_array as $given_cidr){
    if(($range=cidr2range($given_cidr)) &&
    $given_ipv4_dec>=$range[0] && $given_ipv4_dec<=$range[1])
    {
    $in_range=true;
    echo $given_ipv4.' matched '.$given_cidr.' ('.join(array_map('long2ip',$range),' - ').")\n";
    }
    }
    }
    echo $given_ipv4.' is probably'.($in_range?'':' not').' a Googlebot IP';
    

    To run fast the function don't check input but formally it should be a string matching the following regex

    #^(?:((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))(?:/((?:(?:0)|(?:3[0-2])|(?:[1-2]?[0-9]))))?)$#
    

    If you want to verify the input before using the function

    <?php
    if (is_string($ipv4) && preg_match('#^(?:((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))\.((?:0)|(?:2(?:(?:[0-4][0-9])|(?:5[0-5])))|(?:1?[0-9]{1,2}))(?:/((?:(?:0)|(?:3[0-2])|(?:[1-2]?[0-9]))))?)$#',$ipv4))
    {
    #This is a valid ipv4 with or without CIDR Routing Prefix
    $result=cidr2range($ipv4);
    print_r($result);
    }
    

    Then the formal answer to your question is the following

    <?php
    #Requiring cidr2range shown above function
    function cidr_match($mixed_ip,$mixed_cidr){
    if (!is_array($mixed_ip)){
    $string_mode=true;
    $mixed_ip=[$mixed_ip=>0];
    }
    else $mixed_ip=array_fill_keys($mixed_ip,0);
    if (!is_array($mixed_cidr)) $mixed_cidr=[$mixed_cidr];
    foreach($mixed_ip   as $ip => &$result)
    foreach($mixed_cidr as $cidr)
    {
    if(($range=cidr2range($cidr)) &&
    ($check=ip2long($ip))!==false &&
    $check>=$range[0] && $check<=$range[1]){
    $result=$cidr;
    break;
    }
    }
    $mixed_ip=array_filter($mixed_ip);
    return $string_mode?($mixed_ip?true:false):$mixed_ip;
    }
    
    print '<pre>';
    
    #Your example
    $ips = array('10.2.1.100', '10.2.1.101', '10.5.1.100', '1.2.3.4');
    
    foreach ($ips as $IP) {
        if (cidr_match($IP, '10.2.0.0/16') == true) {
            print "you're in the 10.2 subnet\n"; 
        }
    }
    
    
    #Also working with IP array and/or CIDR array
    #If IP array is given then return an array containing IP (keys) matching CIDR (values)
    $result=cidr_match($ips,['20.2.0.0/16','10.2.0.0/15']);
    foreach($result as $ip => $cidr){
    print "$ip is in the $cidr subnet\n"; 
    }
    

    You can compile your own function using these examples, hope these few lines have helped you…

    0 讨论(0)
  • 2020-11-28 04:34

    Some function changed:

    • split with explode

    function cidr_match($ip, $range)
    {
        list ($subnet, $bits) = explode('/', $range);
        $ip = ip2long($ip);
        $subnet = ip2long($subnet);
        $mask = -1 << (32 - $bits);
        $subnet &= $mask; 
        return ($ip & $mask) == $subnet;
    }
    
    0 讨论(0)
  • 2020-11-28 04:34

    You also can use Net_IPv4 PEAR library.

    function cidr_match($ip, $net){
      include_once("Net/IPv4.php");
      $objIP = new Net_IPv4();
      return $objIP->ipInNetwork($ip, $net);
    }
    
    0 讨论(0)
  • 2020-11-28 04:35

    I found many of these methods breaking after PHP 5.2. However the following solution works on versions 5.2 and above:

    function cidr_match($ip, $cidr)
    {
        list($subnet, $mask) = explode('/', $cidr);
    
        if ((ip2long($ip) & ~((1 << (32 - $mask)) - 1) ) == ip2long($subnet))
        { 
            return true;
        }
    
        return false;
    }
    

    Example results

    cidr_match("1.2.3.4", "0.0.0.0/0"):         true
    cidr_match("127.0.0.1", "127.0.0.1/32"):    true
    cidr_match("127.0.0.1", "127.0.0.2/32"):    false
    

    Source http://www.php.net/manual/en/function.ip2long.php#82397.

    0 讨论(0)
提交回复
热议问题