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

问题描述 投票:0回答:6

类似的东西

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)

数据库:MySQL 5

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

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

BANNED IP 例如::1/64

mysql ip mask bitmask cidr
6个回答
30
投票

请记住,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 的优化,这也可以加快全行检索速度,但这超出了本答案的范围。


4
投票

IPv4 地址、网络地址和网络掩码都是 UINT32 数字,并以人类可读的形式呈现为“点分四边形”。在检查地址是否位于给定网络空间(网络/网络掩码)中时,内核中的路由表代码执行非常快速的按位 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 |
+----------------+

3
投票

对于

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

1
投票

生成整数 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;

0
投票

嗯。您可以构建一个 cidr 掩码表,加入它,然后将 ip 和(MySQL 中的

&
)与带有禁止块 ip 地址的掩码进行比较。这能达到你想要的效果吗?

如果您不想构建掩码表,您可以将掩码计算为

-1 << (x-cidr)
,并根据情况使用
x = 64
32


0
投票
#my subnet 
SET @subnet = '192.168.1.0';
SET @mask = '255.255.254.0';
SET @cidr = 23;


#check 192.168.0.4 is in my subnet

#use mask
SELECT INET_ATON('192.168.0.4') & INET_ATON(@mask) = INET_ATON(@subnet) & INET_ATON(@mask) AS in_subnet;

#use cidr
SELECT INET_ATON('192.168.0.4') & -1 << 32 - @cidr = INET_ATON(@subnet) & -1 << 32 - @cidr AS in_subnet;
© www.soinside.com 2019 - 2024. All rights reserved.