I am currently working with IPv4 and IPv6 addresses in a PHP based project, and I need to be able to compare two IPs to determine which one is a higher number. For example, that 192.168.1.9 is greater than 192.168.1.1. In order to do this, I converted the IPs to binary strings using inet_pton and unpack (I am familiar with ip2long, however it is limited to IPv4).
This method seemed to work fine at first, however I soon discovered that I get the incorrect result when I compare any IP ending in .32 to a lower IP address. For example, if I compare 192.168.1.0 to 192.168.1.32, my script tells me that 192.168.1.0 is greater than 192.168.1.32. This only happens when one of the IPs ends in .32. The first three octets of the IP can be changed, and the result is the same.
The following PHP code generates a page that illustrates this issue:
// Loop through every possible last octet, starting with zero
for ($i = 0; $i <= 255; $i++) {
// Define two IPs, with second IP increasing on each loop by 1
$IP1 = "192.168.1.0";
$IP2 = "192.168.1.".$i;
// Convert each IP to a binary string
$IP1_bin = current(unpack("A4",inet_pton($IP1)));
$IP2_bin = current(unpack("A4",inet_pton($IP2)));
// Convert each IP back to human readable format, just to show they were converted properly
$IP1_string = inet_ntop(pack("A4",$IP1_bin));
$IP2_string = inet_ntop(pack("A4",$IP2_bin));
// Compare each IP and echo the result
if ($IP1_bin < $IP2_bin) {echo '<p>'.$IP1_string.' is LESS than '.$IP2_string.'</p>';}
if ($IP1_bin === $IP2_bin) {echo '<p>'.$IP1_string.' is EQUAL to '.$IP2_string.'</p>';}
if ($IP1_bin > $IP2_bin) {echo '<p>'.$IP1_string.' is GREATER than '.$IP2_string.'</p>';}
// I have also tried using strcmp for the binary comparison, with the same result
// if (strcmp($IP1_bin,$IP2_bin) < 0) {echo '<p>'.$IP1_string.' is LESS than '.$IP2_string.'</p>';}
// if (strcmp($IP1_bin,$IP2_bin) === 0) {echo '<p>'.$IP1_string.' iS EQUAL to '.$IP2_string.'</p>';}
// if (strcmp($IP1_bin,$IP2_bin) > 0) {echo '<p>'.$IP1_string.' is GREATER than '.$IP2_string.'</p>';}
}
?>
Here is a sample of the result:
192.168.1.0 is EQUAL to 192.168.1.0
192.168.1.0 is LESS than 192.168.1.1
192.168.1.0 is LESS than 192.168.1.2
192.168.1.0 is LESS than 192.168.1.3
192.168.1.0 is LESS than 192.168.1.4
...
192.168.1.0 is LESS than 192.168.1.31
192.168.1.0 is GREATER than 192.168.1.32
192.168.1.0 is LESS than 192.168.1.33
...
Converting the IPs back to human readable format returns the correct IP, so I believe the issue is with the comparison. I have tried switching to strcmp for the binary comparison, however the result was the same.
Any help in determining the cause of this would be greatly appreciated. I am not set on using the IP conversion and comparison methods shown in the example script, however I do need to stick with methods that support both IPv4 and IPv6. Thanks.
I am running PHP verison 5.3.3, with Zend Engine v2.3.0 and ionCube PHP Loader v4.6.1
Edit: I resolved the issue by changing the unpack format from "A4" (space-padded strings) to "a4" (NUL-padded strings). See my answer below for details.
After much troubleshooting, I discovered the cause of this issue. When using the unpack function, I had used the format code "A4" which is for space-padded strings. This was causing anything ending with the number 32 to get treated as whitespace, which would then be trimmed. For example, after unpacking the binary value for 192.168.1.32
, I converted it to hex. The result was C0A801
but should have been C0A80120
. The result was even worse if the IP was 192.32.32.32
, as it would trim all the way down to C0
(essentially 192.0.0.0
).
Switching to unpack format "a4" (NUL-padded strings) fixed the issue. Now the only IP's that get truncated are the ones ending in .0, which is the expected behavior. I find it odd that this fixed the issue, as nearly every example I've come across for unpacking IPv4 and IPv6 addresses used the format "A4". Perhaps they changed inet_pton to space-padded in a newer version.