匹配PHP中的IP地址白名单

时间:2016-05-13 15:10:22

标签: php regex network-programming ip

我有一个IP范围列表,如

$whitelist=array('50*','202.16*','123.168*',');

我想阻止所有其他流量看到该页面。

我试过

 if(in_array($_SERVER['REMOTE_ADDR'],$whitelist)){
    //display page
    }

4 个答案:

答案 0 :(得分:3)

in_array不使用正则表达式进行比较。你的正则表达式也是错误的*是量词。这允许零个或多个前一个字符。

尝试:

$whitelist=array('50\..*','202\.16\..*','123\.168\..*');
if(preg_match('/^(' . implode('|', $whitelist) . ')/', $_SERVER['REMOTE_ADDR'])){

.*允许任何内容(非常多(请参阅s修饰符http://php.net/manual/en/reference.pcre.pattern.modifiers.php),.是任何字符,然后与之前提到的量词配对。) / ^是字符串的开头。
\.是文字.
|是一个或。

演示:https://eval.in/571019
正则表达式演示:https://regex101.com/r/dC5uI0/1

答案 1 :(得分:1)

这应该适合你:

你可以遍历白名单ip并修剪空格和*(如果从右边找到)。

使用substr之后,您可以在循环中剪切相同长度的白名单ip的IP地址,并比较两者。

$whitelists = array('50*','202.16*','123.168*');
foreach($whitelists as $whitelist){
    $whitelist = rtrim($whitelist, "*\r\n\t\0 ");
    if(substr($_SERVER['REMOTE_ADDR'], 0, strlen($whitelist)) == $whitelist) {
        $match = true;
        break;
    }
}
echo $match ? 'Match' : 'Not Match';

答案 2 :(得分:1)

如@ chris85所述,in_array不使用正则表达式。

要做这样的事情,你可以简单地使用这样的循环:

if(preg_match('/^(' . implode('|', $whitelist) . ')/i', $_SERVER['REMOTE_ADDR'])){
// Do your stuff

}

你的'*'没有按照你的想法工作..没关系:

$whitelist=array('50\.*','202.16\.*','123.168\.*');

答案 3 :(得分:0)

希望这对某人有帮助。看来您没有特别询问正则表达式,并且由于该主题是关于匹配IP地址的,所以我想把它放在那儿,以便对遇到类似问题的人有所帮助。

服务器软件通常会力求尽可能快和高效。匹配IP地址通常是通过算术完成的。话虽这么说,我会在找到可能的替代方案之前,先通过一种快速的方法来准确地完成您要尝试的操作。

如果您只是对IP地址字符串执行通配符匹配,建议您使用此方法。它已针对您的用例进行了量身定制,但我将单独包括简单的匹配功能。

与之相反,与使用PHP的RegEx函数相比,我还将使用这种方法的示例输出与执行时间包括在内(对于复杂性而言,这是 运行方式 模式匹配)

注意:

    我将此处引用的功能用于非常特定的目的。它们适用于这种情况,因为IP地址中没有'*'字符。在编写时,如果您要测试的变量中带有'*'字符,则它将仅与通配符匹配,因此那里的信息丢失的可能性很小。
  • 如果您正在编写命令行守护程​​序,或者您的进程在其生命周期内将使用相同的IP列表进行多次检查,则使用RegEx库将非常有益。仅当必须首先加载并准备IP列表RegEx以便首次使用时,才能在这里使用我的方法带来的小速度收益。
  • 您可以将代码从“ wildcard_match()”移至“ match_ip()”内部,以获取更多好处,从而避免其他函数调用的开销。

代码(您可以复制和粘贴):

<?php 
/**
 * This function compares a string ("$test") to see if it is
 * equal to another string ("$wild").  An '*' in "$wild" will 
 * match any characters in "$test".
 *
 * @param string $wild - The string to compare against.  This may
 * be either an exact character string to match, or a string
 * with a wild card ('*') character that will match any character(s) 
 * found in "$test".
 * 
 * @param string $test - A character string we're comparing against
 * "$wild" to determine if there is a match.
 * 
 * @return bool Returns TRUE if "$test" is either an exact match to
 * "$wild", or it fits the bill taking any wild card characters into
 * consideration.
 * 
 **/ 
function wildcard_match( $pattern, $test ) {
    $p = 0;
    $a_name = explode("*", $pattern);
    $segs = count($a_name);
    $max_seg = ($segs-1);
    $plen = 0;
    $test_len = strlen($test);
    for ($i = 0; $i < $segs; $i++) {
        $part = $a_name[$i];
        $plen = strlen($part);
        if ($plen === 0) {
            if ($i === $max_seg) return true;
            continue;
        }
        $p = strpos($test, $part, $p);
        if ($p === false) {
            return false;
        }
        $p+=$plen;
    }
    if ($p===$test_len) {
        return true;
    }
    return false;
}
/**
 * Function to quickly traverse an array of whole, or 
 * wild card IPv4 addresses given in "$whitelist" and
 * determine if they match the given whole IPv4 
 * address in "$test".
 *
 * @param array $whitelist  - An array of IPv4 addresses, either
 * whole, or containing an '*' character wherein any character(s)
 * in "$test" will match.
 *
 * @param string $test      - A complete string (dot-decimal) IPv4
 * address to compare against the contents of the array given in
 * parameter one ("$whitelist"). 
 *
 * @return bool Returns TRUE, if the IPv4 address given in "$test" was
 * matched to an IPv4 address or IPv4 wild card pattern in the array
 * given in parameter one ("$whitelist").
 *
 **/
function match_ip( $whitelist, $test ) {
    foreach ($whitelist as $w) {
        if (wildcard_match($w, $test)) return true;
    }
    return false;
}

    /* The array of IP addresses we're going to validate */
    $check_array = array("50.245.1.9", "35.125.25.255", "202.16.15.25");

    /* The array as given in your example (minus the extra ' at the end) */
    $whitelist1=array('50*','202.16*','123.168*');

    /* An array for RegEx matching */
    $whitelist2=array('50\..*','202\.16\..*','123\.168\..*');

    microtime(true); /* Execute this once to make sure its module is loaded */
    echo "Giving PHP a second to get its ducks in a row...\n";
    usleep(1000000); /** Give PHP a second to load and prepare */


    $st = microtime(true);
    foreach ($check_array as $c) {
        if (match_ip($whitelist1, $c)) {
            echo "$c....Match!\n";
        } else {
            echo "$c....No match!\n";
        }
    }
    $dt = microtime(true)-$st;
    echo "Time: $dt\n\n\n\n";


    $st = microtime(true);
    foreach ($check_array as $c) {
        if(preg_match('/^(' . implode('|', $whitelist2) . ')/', $c)){
            echo "$c....Match!\n";
        } else {
            echo "$c....No Match!\n";
        }
    }
    $dt = microtime(true)-$st;
    echo "Time: $dt\n\n";

输出为:

Giving PHP a second to get its ducks in a row...
50.245.1.9....Match!
35.125.25.255....No match!
202.16.15.25....Match!
Time 1: 0.00027704238891602

50.245.1.9....Match!
35.125.25.255....No Match!
202.16.15.25....Match!
Time 2: 0.00040698051452637

第一个结果集来自“ match_ip()”函数,第二个结果集来自启动RegEx库。

现在,通配符匹配IP的一种可能更好的解决方案是在数组中采用CIDR表示法的IP地址数组。通常,您希望允许来自特定网络或IP地址范围的IP通信。

这里有很多假设,例如(使用“ $ whitelist”数组):

“ 50 *”可能被解释为“我希望允许访问50.xxx.xxx.xxx中的所有IP地址。

在这种情况下,您将指定格式“ 50.0.0.0/8”。 (“ 50”后面的“ 0”可以是任何数字。由于“ / 8”,它们将被完全忽略。)

xxx.xxx.xxx.xxx 
 |   |   |   |
 8   16  24  32

IPv4地址在计算上是32位,因此,上面所说的是,您关心的只是前8位匹配。

'123.168 *'将为“ 123.168.0.0/16”

“ 101.23.54.0/24”将允许所有以“ 101.23.54”开头的IP地址访问。

“ 44.32.240.10/32”将 允许IP地址“ 44.32.240.10”访问。没有范围。

因此您可以执行以下操作:

<?php 

/**
 * Determines if the two given IPv4 addresses 
 * are equal, or are on the same network using
 * the given number of "$mask" bits.
 * 
 * @param string $ip1  - The first string dot-decimal IPv4
 * address.
 * 
 * @param string $ip1  - The second string dot-decimal IPv4
 * address.
 * 
 * @param int $mask    - The number of bits in the mask.
 * 
 * @return bool Returns TRUE if they match after being
 * masked, or FALSE if not.
 * 
 */
function ip_match( $ip1, $ip2, $mask) {
    $mask = (int)$mask;
    $ip1  = ip2long($ip1);
    $ip2  = ip2long($ip2);
    if ($ip1 === false || $ip2 === false) return false;
    if ($mask < 1 || $mask > 32) return false;
    $mask =  (0x00000000FFFFFFFF & 0x00000000FFFFFFFF << (32-$mask));
    if ( ($ip1 & $mask) === ($ip2 & $mask) ) {
        return true;
    }
    return false;
}
/**
 * Takes an array of string (CIDR) network representations and 
 * sorts them into an array used later for checking against IP
 * addresses.
 * 
 * @param array $cidr_array - An array of IP addressess in
 * CIDR notation e.g. 192.168.1.1/24
 * 
 * @return array Returns an array of objects with the following
 * properties:
 *      'ip'   - The string (dot-decimal) IP address that
 *               has been numerically verified for use 
 *               in comparisons later.
 * 
 *      'mask' - The number of bits used for creating 
 *               the subnet mask against which IP 
 *               addresses will be compared.
 * 
 **/
function make_whitelist( $cidr_array ) {
    $wl   = array();
    $lip  = 0;
    $bm   = 0;
    $spos = 0;
    if (!is_array($cidr_array)) return false;
    foreach ($cidr_array as $ip) {
        $spos = strpos($ip, "/");
        if ($spos === false) {
            $bm = 32; /* If there's no "/", assume
                       * that we want an EXACT IP
                       * address. Hence the 32 bit
                       * mask
                       **/
        } else {
            $bm = (int)substr($ip, ($spos+1));
            $ip = substr($ip, 0, $spos++);
        }

        $lip = ip2long($ip); /* Using this here to check IP validity
                              * before storing it in the array...
                              * We use ip2long() later for comparisons.
                              *
                              * You can store it this way - as a long - 
                              * instead of as a string (I do) to
                              * use less memory if you wish.
                              *
                              **/

        if ($bm === 0) continue; /* A bit mask of ZERO will block 
                                  * ALL IP addresses, skip it
                                  * for the example.
                                  **/
        if ($lip === false) continue; /* If it's an invalid IP, skip it,
                                       * you could optionally try to 
                                       * resolve it as a hostname using
                                       * gethostbyname() or gethostbynamel()
                                       * here...
                                       **/
        array_push($wl, (object)array('ip'=>$ip, 'mask'=>$bm));
    }
    return $wl;
}


    $whitelist = make_whitelist(array("50.0.0.0/8", "202.16.0.0/16", "123.168.0.0/16", "1.1.1.1"));

    $ips_to_check = array("50.1.174.41", "42.123.100.23", "123.168.4.79", "1.1.1.2", "1.1.1.1");

    foreach ($ips_to_check as $ip) {
        foreach ($whitelist as $w) {
            if (ip_match($ip, $w->ip, $w->mask)) {
                echo "\"$ip\" is allowed!\n";
                continue 2;
            }
        }
        echo "\"$ip\" is NOT allowed!\n";
    }

我知道很多,但是这里有很多可供人们思考,寻找和寻找的地方,希望能够使他们的生活更轻松!