查看 IP 地址是否在子网列表中的有效方法

Efficient way to see if an IP address is in a list of subnets

我基本上有一个 2 列 table,IP 子网作为 key/index,描述作为值。例如:

10.20.30.0/30  "Subnet 1"

我需要编写一个 REST 服务,它将 return 包含给定 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 如何检查按子网索引的 table 中的 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

谢谢!

由于 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);

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

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

更新:查询计划比较

一开始我无法用这么小的 tables 判断是否使用了索引(在小的 tables 上,没有使用索引,但是它可能只是 Postgresql 确定扫描速度更快的情况)。 [是](对于更大的 tables)。

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

我创建了 another gist,每个 table 有 1000 个条目。

可以看到使用了aminamax索引(下面第二个方案),但是直接在addr上的索引没有(下面第一个方案:全table扫描):

explain
select * from test a
left outer join ipdesc b
on (b.addr >> a.ip);
| QUERY PLAN                                                              |
| :---------------------------------------------------------------------- |
| Nested Loop Left Join  (cost=0.00..20442.10 rows=6800 width=68)         |
|   Join Filter: (b.addr >> a.ip)                                         |
|   ->  Seq Scan on test a  (cost=0.00..23.60 rows=1360 width=32)         |
|   ->  Materialize  (cost=0.00..21.00 rows=1000 width=36)                |
|         ->  Seq Scan on ipdesc b  (cost=0.00..16.00 rows=1000 width=36) |
explain
select * from test a
left outer join mod_ipdesc b
on (a.ip between b.amin and b.amax);
| QUERY PLAN                                                                                   |
| :------------------------------------------------------------------------------------------- |
| Nested Loop Left Join  (cost=0.28..8001.60 rows=151111 width=132)                            |
|   ->  Seq Scan on test a  (cost=0.00..23.60 rows=1360 width=32)                              |
|   ->  Index Scan using mod_ipdesc_addr on mod_ipdesc b  (cost=0.28..4.76 rows=111 width=100) |
|         Index Cond: ((a.ip >= amin) AND (a.ip <= amax))                                      |