Оптимизированный метод для сравнения IP-адресов с подстановочными знаками в PHP? - PullRequest
8 голосов
/ 30 сентября 2011

Кто-нибудь знает эффективный и безопасный метод, чтобы увидеть, если этот вход:

$_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;
    }
}

Ответы [ 5 ]

9 голосов
/ 30 сентября 2011

Я не тестировал это, но я бы предпочел использовать метод, который использует сетевое аппаратное / программное обеспечение ...

Заменить любой * на 0 и 255. Преобразовать IP в целые числа

Таким образом, если 255.255.255. * Становится 255.255.255.0 и 255.255.255.255, тогда выполните функцию ip2long для этих двух ips.

Затем вы можете преобразовать данный ip в длинный ip.например, 255.255.50.51 в длинный ip.

Затем вы можете сравнить, находится ли длинный ip для данного данного ip между преобразованными длинными ips в черном списке.Если это так, то это не разрешено, иначе это так.

$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}";
 }
}
5 голосов
/ 30 сентября 2011

Снимите звездочки и просто сделайте:

$ips = array('192.168.1.', '10.0.0.');

foreach ($ips as $ip) {
    if (strpos($_SERVER['REMOTE_ADDR'], $ip) === 0) {
        // match
    }
}
2 голосов
/ 15 ноября 2016

Этот разрешает все случаи в вопросе плюс короткие маски без звездочек, как 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;
    }
}
1 голос
/ 30 сентября 2011

Почему бы просто не использовать регулярное выражение?

 preg_match("((192\\.168\\.1)|(10\\.0\\.0)|(127\\.0\\.0)\\.[012]\\d{0,2}|(\\:\\:1))",$_SERVER['REMOTE_ADDR'])
0 голосов
/ 30 сентября 2011

Просто ради забавы, я собираюсь переусердствовать в этом. Ну, если у вас нет достаточно длинного списка для сравнения.

Предполагая, что вы используете только символы подстановки для обозначения «мне наплевать на этот октет», вы можете проанализировать каждую запись в вашем массиве в четыре значения (по одному на октет). Допустим, вы используете -1 для обозначения подстановочного знака, 0–255 - для точного соответствия этому значению. (Если вам нужна лучшая производительность, чем O (n), где n - это размер списка соответствий, то здесь вы можете использовать более совершенные структуры данных - например, три.) Вызовите этот массив L Конечно, вам нужно сделать это только один раз, а не по запросу.

Затем можно проанализировать удаленный адрес таким же образом (за исключением без подстановочных знаков). Вы также можете поймать 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

(это, конечно, псевдокод)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...