首页 文章

重构基于PHP的IP过滤器以使用IPv6

提问于
浏览
2

我在我的一个旧项目中有一个白名单的IP过滤器,我想在新的应用程序中重用它 .

编辑澄清;它的工作原理如下:

白名单包含下面指定格式的条目 . 使用 foreach ($whitelist as $listed) ,我检查当前条目( $listed )的类型,然后将此条目与 $ip 进行比较 . 一旦找到与指定IP匹配的条目,它将返回true,如果在经过整个白名单后没有找到匹配,则它将返回false .

截至目前,仅支持IPv4,并且过滤器允许白名单条目,如下所示:

通过指定BEGIN - END( 192.168.0.1-192.168.0.5

  • IP范围

  • 单个IP地址(例如 192.168.0.2

  • 使用* -wildcard的IP范围(例如 192.168.0.*

检查每个案例的方法如下所示,其中 $ip 是客户端的IP, $listed 是白名单/黑名单中与上述格式之一匹配的条目:

public function checkAgainstRange($ip, $listed)
{
    list($begin, $end) = explode('-', $listed);
    $begin = ip2long($begin);
    $end = ip2long($end);
    $ip = ip2long($ip);
    return ($ip >= $begin && $ip <= $end);
}

public function checkAgainstSingle($ip, $listed)
{
    return long2ip(ip2long($ip)) === long2ip(ip2long($listed));
}

public function checkAgainstWildcard($ip, $listed)
{
    $listedSegment = explode('.', $listed);
    $ipSegment = explode('.', $ip);

    for ($i = 0; $i < count($listedSegment); $i++) {
        // We're finished when the wildcarded block is reached
        // Important: Check for wildcard first, as it won't match the client IP!
        if ($listedSegment[$i] == '*') {
            return true;
        }
        if ($listedSegment[$i] != $ipSegment[$i]) {
            return false;
        }
    }
    // Just to be safe: If we reach this, something went wrong
    return false;
}

关于如何使用IPv6地址,我需要一些指导 .

一些必要的更改是显而易见的:* ip2long() 仅适用于IPv4地址*我必须在 checkAgainstWildcard() 中检查 : 作为可能的分隔符 .

我在php-docs中找到 inet_ntop()inet_pton() . 我可以使用以下内容来比较两个单一的IP地址吗?

public function checkAgainstSingle($ip, $listed)
{
    $ip = inet_ntop($ip);
    $listed = inet_ntop($listed);
    if ($ip === false || $listed === false) {
        throw new \Exception;
    }
    return $ip === $false;
}

用法示例:

$filter = new IpFilter();
$ip = $_SERVER['REMOTE_ADDR'];

$result = $filter->checkAgainstSingle($ip, '192.168.0.1');
$result = $filter->checkAgainstRange($ip, '192.168.0.1-192.168.0.10');
$result = $filter->checkAgainstWildcard($ip, '192.168.0.*');

保留像 checkAgainstRange() 这样的东西是否有用?如果是这样,我怎么能以类似的方式检查IPv6范围?显然我不能在这里改变 ip2long()inet_ntop()

对于通配符范围也是如此 . 我应该保留它们并且将 : 作为段分隔符检查是否足够,如果找不到,则回退到 . 作为分隔符?

1 回答

  • 2

    如果IP地址是人类可读/可打印格式,那么您可以使用inet_pton()将其转换为二进制形式 . 然后,您可以使用bin2hex()以十六进制形式显示二进制数据 .

    例:

    $address = inet_pton("2a00:8640:1::1");
    echo bin2hex($address);
    

    会告诉你:

    2a008640000100000000000000000001
    

    如果您想检查子网而不是单独的地址,则可以比较前16个十六进制字符 . IPv6子网几乎总是/ 64个网络,每个十六进制字符为4位,因此第一个(64/4 =)16个字符显示子网 . 请记住,使用IPv6,计算机可以拥有多个IPv6地址,如果它使用隐私扩展(默认情况下在最近的Windows和Mac OS X系统上启用),则会定期更改其源地址 . 在子网上匹配可能对IPv6最有用 .

相关问题