如何将 PHP 中的 IP 地址作为二进制字符串进行比较


How do I compare IP addresses in PHP as binary strings?

我目前正在一个基于 PHP 的项目中使用 IPv4 和 IPv6 地址,我需要能够比较两个 IP 以确定哪个是更高的数字。例如,192.168.1.9 大于 192.168.1.1。为此,我使用 inet_pton 将 IP 转换为二进制字符串并解压缩(我熟悉 ip2long,但它仅限于 IPv4)。

这种方法起初似乎工作正常,但是我很快发现,当我将任何以 .32 结尾的 IP 与较低的 IP 地址进行比较时,我得到的结果不正确。例如,如果我将 192.168.1.0 与 192.168.1.32 进行比较,我的脚本告诉我 192.168.1.0 大于 192.168.1.32。仅当其中一个 IP 以 .32 结尾时,才会发生这种情况。IP的前三个八位字节可以更改,结果是一样的。

以下 PHP 代码生成一个页面来说明此问题:

// 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>';}
}
?>

下面是结果的示例:

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
...

将 IP 转换回人类可读的格式会返回正确的 IP,因此我认为问题出在比较上。 我尝试切换到 strcmp 进行二进制比较,但结果是一样的。

任何帮助确定原因将不胜感激。我没有使用示例脚本中显示的IP转换和比较方法,但是我确实需要坚持使用同时支持IPv4和IPv6的方法。谢谢。

我正在运行PHP verison 5.3.3,带有Zend Engine v2.3.0和ionCube PHP Loader v4.6.1

编辑:我通过将解包格式从"A4"(空格填充字符串)更改为"a4"(NUL 填充字符串)来解决此问题。有关详细信息,请参阅下面的回答。

经过多次故障排除,我发现了此问题的原因。使用解包功能时,我使用了格式代码"A4",用于填充空格的字符串。这导致任何以数字 32 结尾的内容都被视为空格,然后会被修剪。例如,在解压缩192.168.1.32的二进制值后,我将其转换为十六进制。结果是C0A801,但应该C0A80120。如果IP是192.32.32.32,结果会更糟,因为它会一直修剪到C0(基本上是192.0.0.0)。

切换到解包格式"a4"(NUL 填充字符串)修复了此问题。现在,唯一被截断的 IP 是以 .0 结尾的 IP,这是预期的行为。我觉得这解决了这个问题很奇怪,因为我遇到的几乎每个解压缩 IPv4 和 IPv6 地址的示例都使用"A4"格式。也许他们在较新版本中将inet_pton更改为空格填充。

如果您想在下面几个有用的函数下面转换为数字而不是二进制:

function ipv6ToNum($ip)
{
    $binaryNum = '';
    foreach (unpack('C*', inet_pton($ip)) as $byte) {
        $binaryNum .= str_pad(decbin($byte), 8, "0", STR_PAD_LEFT);
    }
    $binToInt = base_convert(ltrim($binaryNum, '0'), 2, 10);
    return $binToInt;
}
function ipv4ToNum($ip)
{
    $result = 0;
    $ipNumbers = explode('.', $ip);
    for ($i=0; $i < count($ipNumbers); $i++) {
        $power = count($ipNumbers) - $i;
        $result += pow(256, $i) * $ipNumbers[$i];
    }
    return $result;
}

(我在ipv4ToNum中重复ip2long逻辑)