1

I have a starting IPv4 IP address 5.39.28.128 (or ::ffff:5.39.28.128) and I have the IPv6 network mask length 122, how can I calculate the last IP in the range?

I believe I need to convert the start IP to binary, which I'm doing like below, I don't know where to go from there to get the end IP.

$ipNumber = ip2long('5.39.28.128');
$ipBinary = decbin($ipNumber);

echo $ipBinary; // 101001001110001110010000000

The reason is I'm importing the MaxMind GeoIP database in CSV format into a MySQL database (so MySQL functions can be used if needed). MaxMind no longer provide the end IP, in favour of providing the start IP and the IPv6 network mask length instead.

vhu
  • 12,244
  • 11
  • 38
  • 48
RandomCoder
  • 1,358
  • 5
  • 20
  • 35
  • possible duplicate of [Calculate broadcast address from ip and subnet mask](http://stackoverflow.com/questions/777617/calculate-broadcast-address-from-ip-and-subnet-mask) – Tripp Kinetics May 29 '14 at 13:13
  • 3
    @TrippKinetics not a duplicate of that. I'm asking about PHP not C and I don't have the full netmask, only the length. – RandomCoder May 29 '14 at 13:24
  • 2
    #1, there is no difference in the logic even if the language is different. #2, the netmask and the length are equivalent. http://en.wikipedia.org/wiki/Subnetwork – Tripp Kinetics May 29 '14 at 13:25
  • 2
    @TrippKinetics What you are correct about that, and your link is valuable, this question is not a duplicate. Duplicate questions are to be truly duplicates, and the language difference makes it not a duplicate. – Brad May 29 '14 at 14:17
  • Do you want to do this in php or MySQL? If you are not interested in MySQL, then please remove the tag from the question. – Gordon Linoff Jun 05 '14 at 02:02
  • Have you seen http://www.soucy.org/project/inet6/ ? It includes a PHP function `inet_to_range()` which outputs the first and last IPv6 address for a range, given an address and a CIDR prefix. – Bill Karwin Jun 05 '14 at 22:57

2 Answers2

4

Here you are. I've copied the inet_to_bits function from this response to another question.

<?php

function inet_to_bits($inet) {
   $inet = inet_pton($inet);
   $unpacked = unpack('A16', $inet);
   $unpacked = str_split($unpacked[1]);
   $binaryip = '';
   foreach ($unpacked as $char) {
             $binaryip .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
   }
   return $binaryip;
}

function bits_to_inet($bits) {
    $inet = "";
    for($pos=0; $pos<128; $pos+=8) {
        $inet .= chr(bindec(substr($bits, $pos, 8)));
    }
    return inet_ntop($inet);
}

$ip = "::ffff:5.39.28.128";
$netmask = 122;

// Convert ip to binary representation
$bin = inet_to_bits($ip);

// Generate network address: Length of netmask bits from $bin, padded to the right
// with 0s for network address and 1s for broadcast
$network = str_pad(substr($bin, 0, $netmask), 128, '1', STR_PAD_RIGHT);

// Convert back to ip
print bits_to_inet($network);

Output:

::ffff:5.39.28.191
Community
  • 1
  • 1
Phillip
  • 13,448
  • 29
  • 41
2

Solution is quite simple:

// Your input data
$networkstart = '5.39.28.128';
$networkmask = 122;

// First find the length of the block: IPv6 uses 128 bits for the mask
$networksize = pow(2, 128 - $networkmask);

// Reduce network size by one as we really need last IP address in the range,
// not first one of subsequent range
$networklastip = long2ip(ip2long($networkstart) + $networksize - 1);

$networklastip will have last IP address in the range.

Now this is good solution ONLY for IPv4 addresses in IPv6 address space. Otherwise you need to use IPv6 to/from 128 bit integer functions instead of ip2long/long2ip. However for use by MaxMind data code above is sufficient as I have not seen any actual IPv6 data from them yet.

Vladimir Bashkirtsev
  • 1,334
  • 11
  • 24
  • +1 Thanks for answer. Is it a simple change to do the same thing for IPv6 addresses? – RandomCoder Jun 09 '14 at 09:02
  • Yes, basically you dealing with long integers. In IPv4 is is 32 bit long, in IPv6 it is 128 bit long. Math around prefixes exactly the same: two in power of (max prefix length - given prefix length) gives you block size. Add block size to beginning address and you get beginning address of next block. Minus one and you get last address of the block. – Vladimir Bashkirtsev Jun 09 '14 at 10:23
  • I see. So it's a case of swapping the ip2long and long2ip for the equivalent IPv6 version? Are you aware of those functions? I couldn't find anything on php.net. – RandomCoder Jun 09 '14 at 10:33
  • There are no equivalent IPv6 version, because an IPv6 address is 16 byte long and php does only supports up to 8 bytes natively. You'll have to work with a string representation (like I do in my answer) or a large integer extension like [BC Math](http://www.php.net/manual/en/book.bc.php) or [GMP](http://www.php.net/manual/en/book.gmp.php). – Phillip Jun 09 '14 at 15:55
  • 1
    @Philip My only issue with your solution is inefficiency. Loading MaxMind data calls for good amount of transformations and it will be awfully slow. However I support your thoughts about BC Math or GMP - both may do well. Another option is to break IPv6 into four double quads and convert them into integer. Then apply mask the same way as I have offered. Nothing complex but requires some coding. Performance wise I guess it should be decent. – Vladimir Bashkirtsev Jun 09 '14 at 18:09