我正在尝试使用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.*.*.*
怎么做?
提前致谢。
答案 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 BY
和LIMIT
部分是查询有效的必要条件。
如前所述,这假设一次不相交的块和一个IP。
如果块相交(例如,您同时禁止192.168.1.0/28
和192.168.1.0/24
),查询可能会返回漏报。
如果您想一次查询多个IP(例如,更新包含长IP地址列表的表),则此查询效率低下(MySQL
不会优化{{1}在相关子查询中)
在这两种情况下,您都需要将范围存储为range
并使用空间索引进行快速搜索:
LineString
答案 1 :(得分:3)
如果要禁止子网
,这会更棘手注意:
所以:
然后WHERE子句变为
WHERE ip = @ip --whole IP
OR
(ip & mask = @ip) --subnet
如果您为确切的IP地址设置了掩码0xffffffff
,那么您始终可以ip & mask = @ip
,ip & 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')
好的,最后一场比赛可能是愚蠢。
不要忘记添加索引。