Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matching IPv6 address to a CIDR subnet

Tags:

php

ip

ipv6

cidr

Is there a good way to match an IPv6 address to an IPv6 subnet using CIDR notation? What I am looking for is the IPv6 equivalent to this: Matching an IP to a CIDR mask in PHP 5?

The example given above can't be used since an IPv6 address is 128 bits long, preventing the bitwise left-shift from working properly. Can you think of any other way?

EDIT: Added my own solution to the list of answers.

like image 220
MW. Avatar asked Oct 31 '11 07:10

MW.


4 Answers

Since you cannot convert IPv6 addresses to integer, you should operate bits, like this:

$ip='21DA:00D3:0000:2F3B:02AC:00FF:FE28:9C5A';
$cidrnet='21DA:00D3:0000:2F3B::/64';

// converts inet_pton output to string with bits
function inet_to_bits($inet) 
{
   $splitted = str_split($inet);
   $binaryip = '';
   foreach ($splitted as $char) {
             $binaryip .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
   }
   return $binaryip;
}    

$ip = inet_pton($ip);
$binaryip=inet_to_bits($ip);

list($net,$maskbits)=explode('/',$cidrnet);
$net=inet_pton($net);
$binarynet=inet_to_bits($net);

$ip_net_bits=substr($binaryip,0,$maskbits);
$net_bits   =substr($binarynet,0,$maskbits);

if($ip_net_bits!==$net_bits) echo 'Not in subnet';
else echo 'In subnet';

Also, if you use some database to store IPs, it may already have all the functions to compare them. For example, Postgres has an inet type and can determine, whether IP is contained within subnet like this:

SELECT 
   '21DA:00D3:0000:2F3B:02AC:00FF:FE28:9C5A'::inet << 
   '21DA:00D3:0000:2F3B::/64'::inet;

9.11. Network Address Functions and Operators in PostgreSQL

like image 135
Snifff Avatar answered Nov 18 '22 17:11

Snifff


You can also use the IpUtils class from symfony/http-foundation package:

IpUtils::checkIp6('2a01:8760:2:3001::1', '2a01:8760:2:3001::1/64')

This will check the IPv6 validity and range match. Will return false if it's not the case.

like image 32
Soullivaneuh Avatar answered Nov 18 '22 18:11

Soullivaneuh


I created my own solution, using the following code:

function iPv6MaskToByteArray($subnetMask) {
  $addr = str_repeat("f", $subnetMask / 4);
  switch ($subnetMask % 4) {
    case 0:
      break;
    case 1:
      $addr .= "8";
      break;
    case 2:
      $addr .= "c";
      break;
    case 3:
      $addr .= "e";
      break;
  }
  $addr = str_pad($addr, 32, '0');
  $addr = pack("H*" , $addr);
  return $addr;
}

function iPv6CidrMatch($address, $subnetAddress, $subnetMask) {
  $binMask = iPv6MaskToByteArray($subnetMask);
  return ($address & $binMask) == $subnetAddress;
}

Note that $address and $subnetAddress were obtained by running the string address through inet_pton. Call the function as follows:

$subnet = inet_pton("2001:06b8::");
$mask = 32;
$addr = inet_pton("2001:06b8:0000:0000:0000:0000:1428:07ab");
$match = iPv6CidrMatch($addr, $subnet, $mask); // TRUE
like image 43
MW. Avatar answered Nov 18 '22 19:11

MW.


PHP can perform bitwise operations on strings!

  • IPv4 or IPv6
  • No base conversion
  • No ASCII bit strings
  • Pretty fast
<?php

/**
 * Does the given IP match the CIDR prefix?
 */
function matchIp(string $ip, string $cidr): bool
{
  // Get mask bits
  list($net, $maskBits) = explode('/', $cidr);

  // Size
  $size = (strpos($ip, ':') === false) ? 4 : 16;

  // Convert to binary
  $ip = inet_pton($ip);
  $net = inet_pton($net);
  if (!$ip || !$net) {
    throw new InvalidArgumentException('Invalid IP address');
  }

  // Build mask
  $solid = floor($maskBits / 8);
  $solidBits = $solid * 8;
  $mask = str_repeat(chr(255), $solid);
  for ($i = $solidBits; $i < $maskBits; $i += 8) {
    $bits = max(0, min(8, $maskBits - $i));
    $mask .= chr((pow(2, $bits) - 1) << (8 - $bits));
  }
  $mask = str_pad($mask, $size, chr(0));

  // Compare the mask
  return ($ip & $mask) === ($net & $mask);
}

like image 3
jchook Avatar answered Nov 18 '22 18:11

jchook