有没有办法直接从SELECT查询匹配IP与IP + CIDR?

时间:2009-02-27 17:28:44

标签: mysql ip mask bitmask cidr

这样的东西
  

SELECT COUNT(*) AS c FROM BANS WHERE typeid=6 AND (SELECT ipaddr,cidr FROM BANS) MATCH AGAINST 'this_ip';

因此,您不首先从数据库中获取所有记录,然后逐个匹配它们。

如果c> 0然后匹配。

BANS表:

id int auto incr PK
typeid TINYINT (1=hostname, 4=ipv4, 6=ipv6)
ipaddr BINARY(128)
cidr INT
host VARCHAR(255)

DB:MySQL 5

查询时已知IP和IPv6类型(4或6)。

IP例如是二进制格式的:: 1

BANNED IP例如是:: 1/64

5 个答案:

答案 0 :(得分:25)

请记住,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包含一个称为“index merge intersect”的远程查询优化,它允许加速此类查询(并避免全表扫描),只要:

  • 有一个多列索引,它按顺序与查询中的列完全匹配。所以 - 对于上面的查询示例,索引需要是(network, broadcast)
  • 可以从索引中检索所有数据。这适用于COUNT(*),但SELECT * ... LIMIT 1不适用。

MySQL 5.6包含一个称为MRR的优化,它也可以加速完整行检索,但这超出了这个答案的范围。

答案 1 :(得分:3)

IPv4地址,网络地址和网络掩码都是UINT32号码,以人类可读的形式呈现为“dotted-quads”。当检查地址是否在给定的网络空间(网络/网络掩码)中时,内核中的路由表代码执行非常快速的逐位AND比较。这里的技巧是将表中的虚线四元组IP地址,网络地址和网络掩码存储为UINT32,然后为匹配执行相同的32位逐位AND。例如

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 |
+----------------+

答案 2 :(得分:2)

对于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

答案 3 :(得分:0)

嗯。您可以构建一个cidr掩码表,加入它,然后将ip anded(MySQL中的&)与带有禁止块ipaddress的掩码进行比较。那会做你想要的吗?

如果您不想构建掩码表,则可以使用-1 << (x-cidr)x = 64来计算掩码为32

答案 4 :(得分:0)

将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;