一个优化的方法来比较IP地址与通配符在PHP


An optimized method to compare IP addresses with wildcards in PHP?

有谁知道一种有效而安全的方法来查看这个输入:

$_SERVER['REMOTE_ADDR']

匹配类似于这个不一致过滤器数组的内容(注意200.100.*. .)*可以表示为200.100.*),通配符用*'s:

表示。
array(
  '192.168.1.*',
  '192.168.2.1*',
  '10.0.0.*',
  '200.100.*.*',
  '300.200.*',
)

想法吗?

foreach($instanceSettings['accessControl']['allowedIpV4Addresses'] as $ipV4Address) {
    echo 'Now checking against '.$ipV4Address.'.';
    // Compare each octet
    $ipV4AddressOctets = String::explode('.', $ipV4Address);
    $remoteIpV4AddressOctets = String::explode('.', $_SERVER['REMOTE_ADDR']);
    $remoteIpV4AddressIsAllowed = true;
    for($i = 0; $i < Arr::size($ipV4AddressOctets); $i++) {
        echo 'Comparing '.$ipV4AddressOctets[$i].' against '.$remoteIpV4AddressOctets[$i].'.';
        if($ipV4AddressOctets[$i] != $remoteIpV4AddressOctets[$i] && $ipV4AddressOctets[$i] != '*') {
            echo 'No match.';
            $remoteIpV4AddressIsAllowed = false;
            break;
        }
    }
    // Get out of the foreach if we've found a match
    if($remoteIpV4AddressIsAllowed) {
        break;
    }
}

我还没有对此进行基准测试,但是我会选择使用网络硬件/软件使用的方法…

将任意*替换为0和255。将ip转换为整数

所以如果255.255.255。*变为255.255.255.0和255.255.255.255然后在这两个ip上执行ip2long函数

则可以将给定的ip转换为长ip。例如:255.255.50.51 into long ip.

比较该ip对应的长ip是否在黑名单中转换后的长ip之间。如果是,则不允许,否则允许。

$ips = array("ip1", "ip2");
foreach($ips as $ip){
 $ip1 = str_replace("*", "0", $ip);
 $ip2 = str_replace("*", "255", $ip);
 $ip1 = ip2long($ip1);
 $ip2 = ip2long($ip2);
 $givenip = $_GET["ip"];
 $givenip = ip2long($givenip);
 if($givenip >= $ip1 && $ip <= $givenip){
   echo "blacklist ip hit between {$ip1} and {$ip2} on {$ip}";
 }
}

去掉星号,只做:

$ips = array('192.168.1.', '10.0.0.');
foreach ($ips as $ip) {
    if (strpos($_SERVER['REMOTE_ADDR'], $ip) === 0) {
        // match
    }
}

这个允许问题中的所有情况加上没有星号的短掩码,如123.123。

/**
 * Checks given IP against array of masks like 123.123.123.123, 123.123.*.101, 123.123., 123.123.1*.*
 *
 * @param $ip
 * @param $masks
 * @return bool
 */
public static function checkIp($ip, $masks)
{
    if (in_array($ip, $masks)) {
        return true;  // Simple match
    } else {
        foreach ($masks as $mask) {
            if (substr($mask, -1) == '.' AND substr($ip, 0, strlen($mask)) == $mask) {
                return true; // Case for 123.123. mask
            }
            if (strpos($mask, '*') === false) {
                continue; // No simple matching and no wildcard in the mask, leaves no chance to match
            }
            // Breaking into triads
            $maskParts = explode('.', $mask);
            $ipParts = explode('.', $ip);
            foreach ($maskParts as $key => $maskPart) {
                if ($maskPart == '*') {
                    continue;  // This triad is matching, continue with next triad
                } elseif (strpos($maskPart, '*') !== false) {
                    // Case like 1*, 1*2, *1
                    // Let's use regexp for this
                    $regExp = str_replace('*', ''d{0,3}', $maskPart);
                    if (preg_match('/^' . $regExp . '$/', $ipParts[$key])) {
                        continue;  // Matching, go to check next triad
                    } else {
                        continue 2;  // Not matching, Go to check next mask
                    }
                } else {
                    if ($maskPart != $ipParts[$key]) {
                        continue 2; // If triad has no wildcard and not matching, check next mask
                    }
                    // otherwise just continue
                }
            }
            // We checked all triads and all matched, hence this mask is matching
            return true;
        }
        // We went through all masks and none has matched.
        return false;
    }
}

为什么不直接使用正则表达式呢?

 preg_match("((192''.168''.1)|(10''.0''.0)|(127''.0''.0)''.[012]''d{0,2}|('':'':1))",$_SERVER['REMOTE_ADDR'])

为了好玩,我将对其进行过度设计。嗯,除非你有一个相当长的列表来匹配。

假设您只使用通配符表示"我不关心这个八位字节",那么您可以将数组中的每个条目解析为四个值(每个八位字节一个)。假设使用-1表示通配符,0-255表示完全匹配该值。(如果您需要比O(n)更好的性能,其中n是匹配列表的大小,那么您可以在这里使用更好的数据结构——例如,一个trie。)当然,您只需要做一次,而不是每次请求。

然后可以用同样的方式解析远程地址(除了不使用通配符)。您还可以在这里发现REMOTE_ADDR不是预期的格式。现在检查匹配变得相当简单:

has_match(ip) =
  for n in [0 … L.length)
    if (-1 == L.n.0 || L.n.0 = ip.0) && (-1 == L.n.1 || L.n.1 == ip.1) && …
      return true
  return false

(当然这是伪代码)

这个基于preg_match(),您提供了模式和要测试它的主题。

/**
 * ip_match("172.30.20.*", "172.30.20.162"); // true
 * ip_match("172.30.20", "172.30.20.162"); // true; works if incomplete
 * ip_match("172.30.*.12", "172.30.20.12"); // true
 * ip_match("172.30.*.12", "172.30.20.11"); // false
 * ip_match("172.30.20.*", "172.30.20.*"); // true; wildcards in the subject will match with wildcards in the pattern
 * ip_match("172.30.20.12", "172.30.*.*"); // false
 * 
 * @param $pattern  The pattern to test against as a string
 * @param $subject  The input string
 *
 *  @return bool
 *
 */
function ip_match($pattern, $subject) {
    $pattern = explode(".", trim($pattern, "."));
    $pattern = array_pad($pattern, 4, "*");
        
    $subject = explode(".", trim($subject, "."));
    $subject = array_pad($subject, 4, "1");
        
    foreach ($pattern as $i => $octet) {
        if ($octet != "*" && $subject[$i] != $octet) {
            return false;
        }
    }
        
    return true;
}
<?php
function ipArrCheck($allowedIPArr = [], $IP = '')
{
    $IP_ARR    = explode('.', $IP);
    $resultArr = [];
    foreach ($IP_ARR as $IPkey => $IPvalue) {
        foreach ($allowedIPArr as $IPArrKey => $IPArrValue) {
            $checkIPArr = explode('.', $IPArrValue);
            $resultArr[$IPArrKey][$IPkey] = $checkIPArr[$IPkey] == $IP_ARR[$IPkey] || $checkIPArr[$IPkey] == '*';
        }
    }
    foreach ($resultArr as $value) {
        if (count(array_unique($value)) == 1 && current($value)) {
            return TRUE;
        }
    }
    return FALSE;
}
$MY_IP          = '192.168.52.10';
$ALLOWED_IP_ARR = ['127.0.0.1', '192.168.*.*'];
var_dump(ipArrCheck($ALLOWED_IP_ARR, $MY_IP));