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

时间:2012-03-06 10:58:57

标签: php ipv6 whitelist

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

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

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

截至目前,仅支持IPv4,过滤器允许使用以下白名单条目:

  1. 通过指定BEGIN - END(192.168.0.1-192.168.0.5
  2. 的IP范围
  3. 单个IP地址(例如192.168.0.2
  4. 使用* -wildcard(例如192.168.0.*
  5. 的IP范围

    检查每种情况的方法如下所示,其中$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 个答案:

答案 0 :(得分: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最有用。