有没有办法直接从 SELECT 查询中将 IP 与 IP+CIDR 进行匹配?
题
就像是
SELECT COUNT(*) AS c FROM BANS WHERE typeid=6 AND (SELECT ipaddr,cidr FROM BANS) MATCH AGAINST 'this_ip';
因此,您不必首先从数据库中获取所有记录,然后将它们一一匹配。
如果 c > 0 则匹配。
禁令表:
id int auto incr PK
typeid TINYINT (1=hostname, 4=ipv4, 6=ipv6)
ipaddr BINARY(128)
cidr INT
host VARCHAR(255)
D B:MySQL 5
查询时已知 IP 和 IPv 类型(4 或 6)。
例如,IP 是二进制格式的 ::1
禁止的 IP 是例如::1/64
解决方案
请记住,IP 不是文本地址,而是数字 ID。我有类似的情况(我们正在进行地理IP查找),如果您将所有IP地址存储为整数(例如,我的IP地址是192.115.22.33,因此它存储为3228767777),那么您可以查找IP通过使用右移运算符可以轻松实现。
所有这些类型的查找的缺点是您无法从索引中受益,并且每当进行查找时都必须进行全表扫描。上述方案可以通过存储 CIDR 网络的网络 IP 地址(范围的开头)和广播地址(范围的结尾)来改进,因此例如要存储 192.168.1.0/24,您可以存储两个列:
network broadcast
3232235776, 3232236031
然后你就可以简单地匹配它
SELECT count(*) FROM bans WHERE 3232235876 >= network AND 3232235876 <= broadcast
这样您就可以将 CIDR 网络存储在数据库中,并利用快速数字索引快速有效地将它们与 IP 地址进行匹配。
下面讨论的注释:
MySQL 5.0 包括一个名为“的范围查询优化”索引合并相交“这允许加速此类查询(并避免全表扫描),只要:
- 有一个多列索引,它按顺序与查询中的列完全匹配。因此 - 对于上面的查询示例,索引需要是
(network, broadcast)
. - 所有数据都可以从索引中检索。这对于
COUNT(*)
, ,但不适用于SELECT * ... LIMIT 1
.
MySQL 5.6 包含一个称为 MRR 的优化,它也可以加速全行检索,但这超出了本答案的范围。
其他提示
IPv4地址,网络地址和网络掩码都UINT32号码和在人类可读的形式为“点分四边形”被呈现。检查时,如果一个地址在给定的网络空间(网络/网络掩码)在内核的路由表的代码执行非常快速的逐位和比较。这里的窍门是将点分四组IP地址,网络地址和掩码存储在您的表作为UINT32,然后执行相同的32位位与你匹配。例如
SET @test_addr = inet_aton('1.2.3.4');
SET @network_one = inet_aton('1.2.3.0');
SET @network_two = inet_aton('4.5.6.0');
SET @network_netmask = inet_aton('255.255.255.0');
SELECT (@test_addr & @network_netmask) = @network_one AS IS_MATCHED;
+------------+
| IS_MATCHED |
+------------+
| 1 |
+------------+
SELECT (@test_addr & @network_netmask) = @network_two AS IS_NOT_MATCHED;
+----------------+
| IS_NOT_MATCHED |
+----------------+
| 0 |
+----------------+
有关IPv4
,可以使用:
SET @length = 4;
SELECT INET_NTOA(ipaddr), INET_NTOA(searchaddr), INET_NTOA(mask)
FROM (
SELECT
(1 << (@length * 8)) - 1 & ~((1 << (@length * 8 - cidr)) - 1) AS mask,
CAST(CONV(SUBSTR(HEX(ipaddr), 1, @length * 2), 16, 10) AS DECIMAL(20)) AS ipaddr,
CAST(CONV(SUBSTR(HEX(@myaddr), 1, @length * 2), 16, 10) AS DECIMAL(20)) AS searchaddr
FROM ip
) ipo
WHERE ipaddr & mask = searchaddr & mask
嗯。你可以建立的CIDR掩码的表,加入它,然后比较与禁令块ip地址掩码相与的IP(&
在MySQL)。但愿你想要做什么?
如果你不希望建立一个屏蔽表,可以计算出面具与-1 << (x-cidr)
或x = 64
根据32
。
生成的IP地址范围为整数
如果您的数据库不支持花哨的位操作,您可以使用一个简化的整数为基础的方法。
下面的例子是使用PostgreSQL:
select (cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 1) as bigint) * (256 * 256 * 256) +
cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 2) as bigint) * (256 * 256 ) +
cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 3) as bigint) * (256 ) +
cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 4) as bigint))
as network,
(cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 1) as bigint) * (256 * 256 * 256) +
cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 2) as bigint) * (256 * 256 ) +
cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 3) as bigint) * (256 ) +
cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 4) as bigint)) + cast(
pow(256, (32 - cast(split_part('4.0.0.0/8', '/', 2) as bigint)) / 8) - 1 as bigint
) as broadcast;