【问题标题】:Efficient way to see if an IP address is in a list of subnets查看 IP 地址是否在子网列表中的有效方法
【发布时间】:2021-10-20 11:07:44
【问题描述】:

我基本上有一个 2 列的表,其中一个 IP 子网作为键/索引,一个描述作为值。例如:

10.20.30.0/30  "Subnet 1"

我需要编写一个 REST 服务,该服务将返回包含给定 IP 地址的子网描述,或者如果 IP 地址匹配多个,则返回子网列表。

起初,我以为我会简单地使用后端数据库 (Postgres) 并扩展所有子网,因为我没有处理大量数据。所以上面的例子会扩展为:

10.20.30.0  "Subnet 1"
10.20.30.1  "Subnet 1"
10.20.30.2  "Subnet 1"
10.20.30.3  "Subnet 1"

这在存储方面效率低下,尤其是当子网变大时。但是,它既快速又简单,并且现有数据库具有非常有效的方法来查找 IP 地址作为索引,所以这是我的第一个想法。我最关心的是查找效率,因为我的数据库中将有大约 100 万个条目。

但是,我发现我对 IPv6 也有要求,而且涉及的子网非常庞大。这意味着我不再有扩展所有子网的选项。

在开始编写自定义 REST API 之前,我想知道使用子网作为索引检查 IP 地址的 POSTGRES 查询是否有效。检查不是微不足道的,我不知道如何在内部进行以保持效率。

有人知道 POSTGRES 如何检查子网索引表中的 IP 地址吗?

编辑:这是我正在谈论的在 POSTGRES 中查找 IP 地址的示例(使用在线 https://extendsclass.com/postgresql-online.html)

drop table ipdesc;
create table ipdesc (addr inet, category varchar(20));
insert into ipdesc (addr, category) values ('10.10.10.0/24', 'tens');
insert into ipdesc (addr, category) values ('20.20.20.0/24', 'twenties');
insert into ipdesc (addr, category) values ('50.50.50.0/24', 'fifties');
insert into ipdesc (addr, category) values ('50.50.50.0/30', 'sub-fifty');
select * from ipdesc where inet '50.50.50.1' << addr;

结果:

addr           category
----           --------
50.50.50.0/24  fifties
50.50.50.0/30  sub-fifty

谢谢!

【问题讨论】:

  • 在网络中,不仅需要地址,还需要掩码或掩码长度来将地址分解为网络和主机部分。简单地拥有像10.20.30.0 这样的地址并不能告诉您网络是什么,您需要类似上面的地址(10.20.30.0/30),它的掩码长度告诉您地址的前 30 位是网络。几乎所有 IPv6 网络都将是 /64,因为其他网络规模会破坏 IPv6 功能。
  • 确定地址是否在网络中的正确方法是对带有网络掩码的地址执行按位AND,以查看它是否与网络地址匹配。 This two-part answer` 解释了如何做到这一点。
  • 谢谢@RonMaupin,但我认为你过度简化了我的问题。我知道如何检查 IP 地址是否在子网中。但是,通过子网索引的数据库有效地做到这一点并非易事。由于 Postgres 支持 IP 子网作为原始类型,并且您可以使用 SQL 查询进行检查,我想知道它的效率如何。 Postgres 在后台做一些魔术吗?或者它是在进行简单的搜索并将 IP 与每个子网进行匹配?我不知道,希望在这里得到答案。
  • 那你可能想问dba.stackexchange.com

标签: postgresql ipv6 ipv4


【解决方案1】:

由于 CIDR 是连续的,您可以添加两列:范围内的最小和最大 IP 地址,并在它们上创建索引。我写了一个gist here,如果你想看看整个事情并玩它。

基本上:

create table mod_ipdesc
as select addr, category,
  inet(host(network(addr))) as amin,
  inet(host(broadcast(addr))) as amax
from ipdesc;

create index mod_ipdesc_addr on mod_ipdesc(amin, amax);

然后:

select *
from test a
left outer join mod_ipdesc b
on (a.ip between b.amin and b.amax);

根据您的表定义,加上一个带有几个ip 值的测试表(如inet),我们得到:

ip addr category amin amax
168.192.1.10 null null null null
10.10.10.20 10.10.10.0/24 tens 10.10.10.0 10.10.10.255
50.50.50.1 50.50.50.0/24 fifties 50.50.50.0 50.50.50.255
50.50.50.1 50.50.50.0/30 sub-fifty 50.50.50.0 50.50.50.3
50.50.50.10 50.50.50.0/24 fifties 50.50.50.0 50.50.50.255

更新:查询计划比较

对于这样的小表,我最初无法判断的一件事是是否使用了索引(在小表上,没有使用索引,但这可能只是 Postgresql 确定扫描更快)。 [是的](适用于较大的表格)。

第二个问题是:Postgres 是否具有某种魔力,可以让它使用inet 列上的索引来进行&lt;&lt; 类型查询? [NO](至少据我所知,使用 Postgres 9.6)。

我创建了another gist,每个表格包含 1000 个条目。

您可以看到使用了amin、amax 索引(下面的第二个计划),但没有直接在addr 上使用索引(下面的第一个计划:全表扫描):

explain
select * from test a
left outer join ipdesc b
on (b.addr >> a.ip);
|查询计划 | | :------------------------------------------------ --------------------- | |嵌套循环左连接(成本=0.00..20442.10 行=6800 宽度=68)| |加入过滤器:(b.addr >> a.ip) | | -> 测试 a 上的 Seq Scan(成本=0.00..23.60 行=1360 宽度=32)| | -> 实现(成本=0.00..21.00 行=1000 宽度=36)| | -> ipdesc b 上的 Seq Scan(成本=0.00..16.00 行=1000 宽度=36)|
explain
select * from test a
left outer join mod_ipdesc b
on (a.ip between b.amin and b.amax);
|查询计划 | | :------------------------------------------------ ------------------------------------------------------ | |嵌套循环左连接(成本=0.28..8001.60 行=151111 宽度=132)| | -> 测试 a 上的 Seq Scan(成本=0.00..23.60 行=1360 宽度=32)| | -> 在 mod_ipdesc b 上使用 mod_ipdesc_addr 进行索引扫描(成本=0.28..4.76 行=111 宽度=100)| |指数条件:((a.ip >= amin) AND (a.ip

【讨论】:

  • 因此,换句话说,如果我希望我的查询高效,我应该将子网扩展到低/高 IP 对,因为没有任何花哨的索引魔法适用于 '> >' 运算符。非常感谢您的详细回答!
猜你喜欢
  • 2019-06-21
  • 2017-10-30
  • 2020-01-03
  • 2023-04-06
  • 2012-01-25
  • 2015-09-11
  • 2013-04-30
  • 1970-01-01
  • 2016-12-24
相关资源
最近更新 更多