使用MySQL的通配符IP禁止

时间:2011-07-17 14:33:42

标签: php mysql

我正在尝试使用MySQL在我的网络应用程序中实现IP禁止系统,我知道我可以使用.htaccess来实现它,但这对我来说不是很好。

基本上我目前的表是:

ip_blacklist(id, ip, date)

在php中我查找数据库中的客户端IP以查看它是否被阻止:

$sql = "SELECT ip FROM ip_blacklist WHERE ip = ? LIMIT 1"
$query = $this->db->query($sql, array($ip));
if($query->num_rows() > 0){
 // Gotcha
}

现在..这工作正常,但我希望能够在数据库中输入通配符IP范围,如:

42.21.58.*
42.21.*.*
53.*.*.*

怎么做?

提前致谢。

5 个答案:

答案 0 :(得分:7)

如果您总是一次检查一个IP地址您的禁用范围永远不会相交,您应该以数字格式存储范围的起始和结束地址。

说,你想禁止192.168.1.0 192.168.1.15 192.168.1.0/28

您可以创建一个这样的表:

CREATE TABLE ban (start_ip INT UNSIGNED NOT NULL PRIMARY KEY, end_ip INT UNSIGNED NOT NULL)

,在那里插入范围:

INSERT
INTO    ban
VALUES  (INET_ATON('192.168.1.0'), INET_ATON('192.168.1.0') + POWER(2, 32 - 28) - 1)

然后检查:

SELECT  (
        SELECT  end_ip
        FROM    ban
        WHERE   start_ip <= INET_ATON('192.168.1.14')
        ORDER BY
                start_ip DESC
        LIMIT 1
        ) >= INET_ATON('192.168.1.14')

ORDER BYLIMIT部分是查询有效的必要条件。

如前所述,这假设一次不相交的块和一个IP。

如果块相交(例如,您同时禁止192.168.1.0/28192.168.1.0/24),查询可能会返回漏报。

如果您想一次查询多个IP(例如,更新包含长IP地址列表的表),则此查询效率低下(MySQL不会优化{{1}在相关子查询中)

在这两种情况下,您都需要将范围存储为range并使用空间索引进行快速搜索:

LineString

答案 1 :(得分:3)

如果要禁止子网

,这会更棘手

注意:

  • “通配符”映射应使用0(例如42.21.58.0)来定义子网
  • 由于CIDR(.128,.192等),.0可能不是子网

所以:

  • 将IP存储为4字节二进制或无符号整数
  • 将子网掩码存储为二进制(或uint)4以进行子网黑名单
  • 查看INET_NTOA and INET_ATON转换IP地址

然后WHERE子句变为

WHERE ip = @ip   --whole IP
      OR
      (ip & mask = @ip) --subnet

如果您为确切的IP地址设置了掩码0xffffffff,那么您始终可以ip & mask = @ipip & mask作为计算列

此外,您还可以考虑使用IPv6

答案 2 :(得分:2)

Quick'n'Dirty,但无法使用正确的索引:

SELECT ip FROM ip_blacklist WHERE ? LIKE REPLACE(ip,'*','%') LIMIT 1

答案 3 :(得分:1)

我的建议可能会让人感到有些畏缩,但是你似乎在这个项目中非常传统,所以在这里: 将数据库中的每个IP解析为可以与用户的IP进行比较的正则表达式。例如:

<?php
//Fetch IP's and begin to contruct regex
$regex = array();
while($arr = mysql_fetch_array($result)) {
    $regex[] = '('.$arr['ip'].')';
}

$regex = implode('|', $regex); //Regex now becomes (1.1.1.1)|(2.2.2.2)|etc.
$regex = str_replace('.', '\.', $regex); //Escape dots for regex
$regex = str_replace('*', '((25[0-5])|(2[0-4]\d)|(1\d\d)|(\d\d?))', $regex); //Deal with wildcards

$httpVars = array( 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR' );
foreach( $httpVars as $httpVar ) {  //No hiding behind proxies
    if( isset( $IP = $_SERVER[$httpVar] ) ) {
        break;
    }
}

if(preg_match('/^'.$regex.'$/', $IP) != 0) {
    die(header('HTTP/1.1 403 Forbidden'));  //Magical regex says user should be banned
}
?>

当然,你可以做更多的事情。您可以缓存正则表达式以保存在每个请求中查询数据库,甚至可以扩展您的通配符选项以包括IP范围。

答案 4 :(得分:0)

将通配符从42.21.*.*转换为42.21.0.0,并在写入或读取数据库中的条目时返回。为了提高效率(低内存和磁盘占用空间,性能)将其存储为整数,请使用INET_NTOA and INET_ATON进行转换。

当你查找IP地址时a.b.c.d:

SELECT ip FROM ip_blacklist WHERE ip=INET_ATON('a.b.c.d') or ip=INET_ATON('a.b.c.0') or ip=INET_ATON('a.b.0.0') or ip=INET_ATON('a.0.0.0')

好的,最后一场比赛可能是愚蠢

不要忘记添加索引。