4

我目前正在一个基于 PHP 的项目中使用 IPv4 和 IPv6 地址,我需要能够比较两个 IP 以确定哪个 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 的方法。谢谢。

我正在使用 Zend Engine v2.3.0 和 ionCube PHP Loader v4.6.1 运行 PHP 版本 5.3.3

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

4

2 回答 2

3

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

于 2015-03-18T16:43:57.167 回答
1

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

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

于 2015-03-15T00:15:20.433 回答