Search code examples
phpipv6cidr

Calculate an IPv6 range from a CIDR prefix?


I am able to do this with IPv4 using code snippets from various online sources. I was wondering if there was a way to do it with IPv6.

Basically I just need a form that I can enter an IPv6 address and prefix (ex: f080:42d2:581a::0/68) and it calculates the network address, first useable address, last useable address, and broadcast address. Then just prints to screen. Not looking to store it in a database or anything yet.


Solution

  • First of all: IPv6 doesn't have network and broadcast addresses. You can use all addresses in a prefix. Second: On a LAN the prefix length is always (well, 99.x% of the time) a /64. Routing a /68 would break IPv6 features like stateless auto configuration.

    Below is a verbose implementation of an IPv6 prefix calculator:

    <?php
    
    /*
     * This is definitely not the fastest way to do it!
     */
    
    // An example prefix
    $prefix = '2001:db8:abc:1400::/54';
    
    // Split in address and prefix length
    list($firstaddrstr, $prefixlen) = explode('/', $prefix);
    
    // Parse the address into a binary string
    $firstaddrbin = inet_pton($firstaddrstr);
    
    // Convert the binary string to a string with hexadecimal characters
    # unpack() can be replaced with bin2hex()
    # unpack() is used for symmetry with pack() below
    $firstaddrhex = reset(unpack('H*', $firstaddrbin));
    
    // Overwriting first address string to make sure notation is optimal
    $firstaddrstr = inet_ntop($firstaddrbin);
    
    // Calculate the number of 'flexible' bits
    $flexbits = 128 - $prefixlen;
    
    // Build the hexadecimal string of the last address
    $lastaddrhex = $firstaddrhex;
    
    // We start at the end of the string (which is always 32 characters long)
    $pos = 31;
    while ($flexbits > 0) {
      // Get the character at this position
      $orig = substr($lastaddrhex, $pos, 1);
    
      // Convert it to an integer
      $origval = hexdec($orig);
    
      // OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
      $newval = $origval | (pow(2, min(4, $flexbits)) - 1);
    
      // Convert it back to a hexadecimal character
      $new = dechex($newval);
    
      // And put that character back in the string
      $lastaddrhex = substr_replace($lastaddrhex, $new, $pos, 1);
    
      // We processed one nibble, move to previous position
      $flexbits -= 4;
      $pos -= 1;
    }
    
    // Convert the hexadecimal string to a binary string
    # Using pack() here
    # Newer PHP version can use hex2bin()
    $lastaddrbin = pack('H*', $lastaddrhex);
    
    // And create an IPv6 address from the binary string
    $lastaddrstr = inet_ntop($lastaddrbin);
    
    // Report to user
    echo "Prefix: $prefix\n";
    echo "First: $firstaddrstr\n";
    echo "Last: $lastaddrstr\n";
    
    ?>
    

    It should output:

    Prefix: 2001:db8:abc:1400::/54
    First: 2001:db8:abc:1400::
    Last: 2001:db8:abc:17ff:ffff:ffff:ffff:ffff