【问题标题】:How do I compare IP addresses in PHP as binary strings?如何将 PHP 中的 IP 地址作为二进制字符串进行比较?
【发布时间】:2015-05-17 06:17:45
【问题描述】:

我目前在一个基于 PHP 的项目中使用 IPv4 和 IPv6 地址,我需要能够比较两个 IP 以确定哪一个是更大的数字。例如,192.168.1.9 大于 192.168.1.1。为此,我使用inet_ptonunpack 将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 版本 5.3.3,带有 Zend Engine v2.3.0 和 ionCube PHP Loader v4.6.1

编辑:我通过将解压缩格式从“A4”(空格填充字符串)更改为“a4”(NUL 填充字符串)解决了这个问题。有关详细信息,请参阅下面的答案。

【问题讨论】:

  • 欢迎使用 PHP。享受你极弱的打字。
  • 但先尝试将它们转换为数字。
  • 对于 IPv6,这些将是非常大的二进制数(太大而无法转换为整数)。我尝试将它们转换为二进制而不改变结果。
  • 另外,strcmp 用于比较二进制字符串。如上所述,当我尝试与此方法进行比较时,结果是相同的。
  • 未经测试 - 可能有用:IPvx provides useful functions for IP (v4/v6) address。似乎转换为二进制格式可能更容易比较?。

标签: php ipv6 ipv4 inet


【解决方案1】:

经过多次故障排除,我发现了这个问题的原因。在使用unpack 函数时,我使用了用于空格填充字符串的格式代码“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 更改为空格。

【讨论】:

    【解决方案2】:

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

    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逻辑)

    【讨论】:

    • 其实函数并没有检查ipv6格式是否正确,但是函数的结果可以认为是正确的,0:0:0:0:0:0:0:1 = 1. 在 PHP 中,评估也将比较像 ipv6ToNum(':::2') === '2' 这样的字符串也是有效的。
    • 感谢您的回复,但我更愿意调试 inet_pton 函数的结果,因为我在很多地方都使用它,并且我需要数据是二进制的。 inet_pton 通常用于在 PHP 中使用 IPv6,所以我希望以前有人遇到过这个问题。
    猜你喜欢
    • 2021-08-30
    • 2011-02-13
    • 2014-09-17
    • 2011-11-22
    • 2012-02-08
    • 2018-05-01
    • 2011-08-15
    • 1970-01-01
    • 2023-03-05
    相关资源
    最近更新 更多