首页 > 解决方案 > 将 CIDR 转换为正则表达式的 Java 函数

问题描述

我有一个 Oracle 数据库,其中有一堆 IP 地址作为字符串(varchar2)。我希望能够找到包含在给定 CIDR 块中的那些。由于它不是某种本机“ip”类型,因此我不能使用任何类型的本机 IP 搜索机制。我在想有可能从 cidr 生成一个正则表达式(例如:“10.1.0.0/10”),它会找到正确的值。

任何人都有可以做到这一点的函数或知道java中的库吗?或者任何替代解决方案?

甲骨文 12.1.0.2

标签: javaoraclenetworking

解决方案


OP(原始海报)提到了一个函数“inet_aton”,它可以将 IPv4 地址的字符串表示形式转换为其相应的整数值。唉,在 Oracle SQL 和/或 PL/SQL 中没有这样的功能。

在下面的答案中,我展示了可以用 PL/SQL 编写这样的函数的(微不足道的!)方式。然后我演示如何使用它来解决 OP 的问题。

注意OP 提到“正则表达式”作为解决同一问题的可能方法。我怀疑这些方面的任何事情都会像我在下面提出的那样有效。如果块是有类的(也就是说,如果后缀是 8 的倍数),那么确实很容易写一个regexp_like条件(我们只需要匹配四部分 IP 的第一个、两个或三个“部分”地址)。然而,对于无类块,问题变得更加复杂。尾注

编辑为了它的价值,我只是在一个有 160 万个 IP 地址的表上尝试了转换功能。它在 0.3 秒内将它们全部转换为数字格式。结束编辑

在代码中,我将函数创建为独立对象(函数)。如果愿意,可以在使用它的同一查询中的 WITH 子句中定义该函数。这可能会导致性能稍好一些。(也可以直接select查询中使用函数体中的公式,因此在任何地方都没有函数调用,但性能提升可能很小。)

如果性能很重要,OP 可以在存储 IP 地址的表中添加基于函数的索引;然后,对于相对较小的 CIDR 块,只要过滤器具有足够的选择性,查询就应该非常快。

这是将 IPv4 地址从四部分字符串格式转换为整数的函数的一种实现:

create or replace function ip_str_to_num (s varchar2) return number
  deterministic
as
  pragma udf;
begin
  return 
  ((to_number(substr(s,1,instr(s,'.',1,1)-1))*256+ 
    to_number(substr(s,instr(s,'.',1,1)+1,instr(s,'.',1,2)-instr(s,'.',1,1)-1))
   )*256+
   to_number(substr(s,instr(s,'.',1,2)+1,instr(s,'.',1,3)-instr(s,'.',1,2)-1))
  )*256+to_number(substr(s,instr(s,'.',1,3)+1));
end;
/

特别注意pragma udf- 仅从 Oracle 12.1 开始可用;这使得函数工作得更快,只要它只在 SQL 上下文中使用。

那么这里是一个如何使用这个函数的例子。我创建了一个非常小的“可用地址”表。然后我展示了一个查询,我在其中硬编码了一个 CIDR 块(通常应该将其更改为绑定变量);我展示了我们如何使用转换函数以数字格式生成块中的最小和最大地址,然后我加入地址表以查找属于该块的那些地址。

测试数据:

create table ip_address_list(ip_address varchar2(19));

insert into ip_address_list
  select '123.33.2.234' from dual union all
  select '230.0.0.1'    from dual union all
  select '43.233.83.2'  from dual union all
  select '128.233.2.8'  from dual union all
  select '72.120.0.1'   from dual union all
  select '128.232.1.64' from dual
;

commit;

查询和结果:

with
  inputs(cidr_block) as (
    select '128.224.0.0/10' from dual
  )
, prep(min_addr, suffix) as (
    select ip_str_to_num(substr(cidr_block, 1, instr(cidr_block, '/') - 1)), 
           substr(cidr_block, instr(cidr_block, '/') + 1)
    from   inputs
  )
, address_range(min_addr, max_addr) as (
    select min_addr, min_addr + power(2, 32 - suffix) - 1
    from   prep
  )
select ip_address 
from   ip_address_list join address_range 
                       on ip_str_to_num(ip_address) between min_addr and max_addr
order  by ip_str_to_num(ip_address)
;

IP_ADDRESS         
-------------------
128.232.1.64
128.233.2.8

推荐阅读