在Oracle中表示IPv4 / IPv6地址

时间:2011-02-09 21:00:52

标签: oracle ipv6 ipv4

在Oracle中,用于表示网络地址的适当数据类型或技术是什么,哪些地址可能是IPv4或IPv6?

背景:我正在转换使用PostgreSQL inet data type构建的表记录网络活动,以便在同一个表中保存v4和v6地址。

但是,没有行包含v4和v6地址。 (也就是说,记录来自机器的v4堆栈或机器的v6堆栈。)

7 个答案:

答案 0 :(得分:16)

  

在Oracle中,什么是合适的   数据类型或技术   代表网络地址,   地址可以是IPv4或IPv6

有两种方法:

  1. 仅存储。
  2. 存储传统表示
  3. 仅供存储。 IPV4地址应该是一个整数(32位就足够了)。对于IP V6,128位,INTEGER(类似于Number(38))将会这样做。当然,这是存储。该方法认为表示是应用程序的问题。

    如果采用相反的策略来存储传统表示,则需要确保IP V4和IPV6地址仅具有一个传统(字符串)表示。它以ipV4而闻名。至于IPV6,还有一种标准格式。

    我的偏好是第一个策略。在最坏的情况下,您可以采用混合方法(尽管非酸)并且将二进制和ascii表示并排存储到二进制值的“优先级”。

      

    没有行包含v4和v6   但是,地址。

    IPV6格式的IPV4地址的标准表示形式为:::ffff:192.0.2.128

    我不知道上下文,但我会保留2列,一列用于IPV4,另一列用于不同的ipV6地址。

    <强>更新
    在@ sleepyMonad的好评之后,我想指出的是,不是使用 Number 数据类型,而是使用INTEGER数据类型,它将很乐意适应可能的最高值。表示为128位整数'ff ... ff'(需要 39 十进制数字)。 38是10的的最高功率,范围从0到9 ,可以在128位上编码,但仍然可以插入 2 ** 128 - 1 的最大无符号值(十进制340282366920938463463374607431768211455)。这是一个很小的测试来说明这种可能性。

    create table test (
      id integer primary key,
      ipv6_address_bin INTEGER );
    
    -- Let's enter 2**128 - 1 in the nueric field
    insert into test (id, ipv6_address_bin) values ( 1, to_number ( 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX') ) ;
    
    -- retrieve it to make sure it's not "truncated".
    select to_char ( ipv6_address_bin, 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' ) from test where id = 1 ;
    -- yields 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'
    
    select to_char ( ipv6_address_bin ) from test where id = 1 ;
    -- yields 340282366920938463463374607431768211455
    
    select LOG(2, ipv6_address_bin) from test where id = 1 ;
    -- yields 128
    
    select LOG(10, ipv6_address_bin) from test where id = 1 ;
    -- yields > 38
    

答案 1 :(得分:7)

将其存储在RAW中。

RAW是可变长度的字节数组,所以......

  • 只将IPv4视为4字节数组
  • 和IPv6作为16字节的数组

...并将其中一个直接存储在RAW(16)中。


RAW可以被编入索引,可以是PK,UNIQUE或FOREIGN KEY,因此您可以使用VARCHAR2或INT / NUMBER / DECIMAL执行任何操作,但转换和存储开销较少。

为了说明INT相对于RAW的存储开销,请考虑以下示例:

CREATE TABLE IP_TABLE (
    ID INT PRIMARY KEY,
    IP_RAW RAW(16), 
    IP_INT INT
);

INSERT INTO IP_TABLE (ID, IP_RAW, IP_INT) VALUES (
    1,
    HEXTORAW('FFFFFFFF'),
    TO_NUMBER('FFFFFFFF', 'XXXXXXXX')
);

INSERT INTO IP_TABLE (ID, IP_RAW, IP_INT) VALUES (
    2,
    HEXTORAW('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'),
    TO_NUMBER('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX')
);

SELECT VSIZE(IP_RAW), VSIZE(IP_INT), IP_TABLE.*  FROM IP_TABLE;

结果(在Oracle 10.2下):

table IP_TABLE created.
1 rows inserted.
1 rows inserted.
VSIZE(IP_RAW)          VSIZE(IP_INT)          ID                     IP_RAW                           IP_INT                 
---------------------- ---------------------- ---------------------- -------------------------------- ---------------------- 
4                      6                      1                      FFFFFFFF                         4294967295             
16                     21                     2                      FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 340282366920938463463374607431768211455 

答案 2 :(得分:4)

@Alain Pannetier(因为我无法发表评论): ANSI INTEGER数据类型根据http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/sql_elements001.htm#i54335映射到Oracle中的NUMBER(38)。 在表格下方,您可以找到NUMBER仅提供126位二进制精度的信息,这对于128位IPv6地址不是必需的。 最大值可能存储正常,但会有地址被路由到下一个较低的地址。

内部数字格式为ROUND((length(p)+ s)/ 2))+ 1(http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/datatype.htm#i16209)。

更新:再次解决此问题之后,我现在找到了一个解决方案,可以对包含IPv6地址的网络进行高性能查询:在RAW中存储IPv6地址和子网掩码(16 )列并使用UTL_RAW.BIT_AND:

进行比较
SELECT name, DECODE(UTL_RAW.BIT_AND('20010DB8000000000000000000000001', ipv6_mask), ipv6_net, 1, 0)
FROM ip_net
WHERE ipv6_net IS NOT NULL;

答案 3 :(得分:1)

你也可以使用自定义的oracle对象。

SQL>set SERVEROUTPUT on
SQL>drop table test;

Table dropped.

SQL>drop type body inaddr;

Type body dropped.

SQL>drop type inaddr;

Type dropped.

SQL>create type inaddr as object
  2  ( /* TODO enter attribute and method declarations here */
  3  A number(5),
  4  B number(5),
  5  C number(5),
  6  D number(5),
  7  E number(5),
  8  F number(5),
  9  G number(5),
 10  H NUMBER(5),
 11  MAP MEMBER FUNCTION display RETURN VARCHAR2,
 12  MEMBER FUNCTION toString( SELF IN INADDR , CONTRACT BOOLEAN DEFAULT TRUE) RETURN VARCHAR2,
 13  CONSTRUCTOR FUNCTION INADDR(SELF IN OUT NOCOPY INADDR, INADDRASSTRING VARCHAR2)  RETURN SELF AS RESULT
 14  
 15  ) NOT FINAL;
 16  /

SP2-0816: Type created with compilation warnings

SQL>
SQL>
SQL>CREATE TYPE BODY INADDR AS
  2  
  3  MAP MEMBER FUNCTION display RETURN VARCHAR2
  4  IS BEGIN
  5  return tostring(FALSE);
  6  END;
  7  
  8  
  9  MEMBER FUNCTION TOSTRING( SELF IN  INADDR , CONTRACT BOOLEAN DEFAULT TRUE) RETURN VARCHAR2 IS
 10  IP4 VARCHAR2(6) := 'FM990';
 11  ip6 varchar2(6) := 'FM0XXX';
 12    BEGIN
 13  IF CONTRACT THEN
 14    ip6 := 'FMXXXX';
 15  end if;
 16  
 17  IF CONTRACT AND A =0 AND B=0 AND C = 0 AND D=0 AND E =0 AND F = 65535 THEN --ipv4
 18      RETURN  '::FFFF:'||TO_CHAR(TRUNC(G/256),'FM990.')||TO_CHAR(MOD(G,256),'FM990.')||TO_CHAR(TRUNC(H/256),'FM990.')||TO_CHAR(MOD(H,256),'FM990');
 19  ELSE
 20      RETURN
 21  TO_CHAR(A,ip6)||':'||
 22  TO_CHAR(B,IP6)||':'||
 23  TO_CHAR(C,ip6)||':'||
 24  TO_CHAR(D,ip6)||':'||
 25  TO_CHAR(E,ip6)||':'||
 26  TO_CHAR(F,ip6)||':'||
 27  TO_CHAR(G,ip6)||':'||
 28  TO_CHAR(H,ip6);
 29  end if;
 30    end;
 31  
 32      CONSTRUCTOR FUNCTION inaddr(SELF IN OUT NOCOPY inaddr, inaddrasstring VARCHAR2)
 33                                 RETURN SELF AS RESULT IS
 34      begin
 35          if instr(inaddrasstring,'.') > 0 then
 36            --ip4
 37  null;
 38              a := 0;
 39              B := 0;
 40              C := 0;
 41              D := 0;
 42              E := 0;
 43              F := TO_NUMBER('FFFF', 'XXXX');
 44              G := TO_NUMBER(TO_CHAR(TO_NUMBER(REGEXP_SUBSTR(INADDRASSTRING,'([0-9]{1,3}).',1,1,'i',1),'999'),'FM0X')
 45  ||TO_CHAR(TO_NUMBER(REGEXP_SUBSTR(INADDRASSTRING,'([0-9]{1,3}).',1,2,'i',1),'999'),'FM0X')
 46  ,'XXXX');
 47              h := TO_NUMBER(TO_CHAR(TO_NUMBER(REGEXP_SUBSTR(INADDRASSTRING,'([0-9]{1,3}).',1,3,'i',1),'999'),'FM0X')
 48  ||TO_CHAR(TO_NUMBER(REGEXP_SUBSTR(INADDRASSTRING,'([0-9]{1,3})',1,4,'i',1),'999'),'FM0X')
 49  ,'XXXX');
 50  
 51          ELSIF instr(inaddrasstring,':') > 0 then
 52              --ip6
 53              a := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,1,'i',1),'XXXX');
 54              b := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,2,'i',1),'XXXX');
 55              c := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,3,'i',1),'XXXX');
 56              d := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,4,'i',1),'XXXX');
 57              E := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,5,'i',1),'XXXX');
 58              f := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,6,'i',1),'XXXX');
 59              g := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,7,'i',1),'XXXX');
 60              H := TO_NUMBER(REGEXP_SUBSTR(inaddrasstring,'([0-9a-fA-F]{1,4})',1,8,'i',1),'XXXX');
 61          end if;
 62  
 63          RETURN;
 64      END;
 65  end;
 66  /

Type body created.

SQL>
SQL>create table test
  2  (id integer primary key,
  3  address inaddr);

Table created.

SQL>
SQL>select * from test;

no rows selected

SQL>
SQL>
SQL>insert into test values (1, INADDR('fe80:0000:0000:0000:0202:b3ff:fe1e:8329') );

1 row created.

SQL>INSERT INTO TEST VALUES (2, INADDR('192.0.2.128') );

1 row created.

SQL>insert into test values (3, INADDR('20.0.20.1') );

1 row created.

SQL>insert into test values (4, INADDR('fe80:0001:0002:0003:0202:b3ff:fe1e:8329') );

1 row created.

SQL>insert into test values (5, INADDR('fe80:0003:0002:0003:0202:b3ff:fe1e:8329') );

1 row created.

SQL>INSERT INTO TEST VALUES (6, INADDR('fe80:0003:0001:0003:0202:b3ff:fe1e:8329') );

1 row created.

SQL>INSERT INTO TEST VALUES (7, INADDR('fe80:0003:0001:0003:0202:b3ff:fe1e:8328') );

1 row created.

SQL>INSERT INTO TEST VALUES (8, INADDR('dead:beef:f00d:cafe:dea1:aced:b00b:1234') );

1 row created.

SQL>
SQL>COLUMN INET_ADDRESS_SHORT FORMAT A40
SQL>column inet_address_full format a40
SQL>
SQL>select t.address.toString() inet_address_short, t.address.display( ) inet_address_full
  2  from test T
  3  order by t.address ;

INET_ADDRESS_SHORT                       INET_ADDRESS_FULL
---------------------------------------- ----------------------------------------
::FFFF:20.0.20.1                         0000:0000:0000:0000:0000:FFFF:1400:1401
::FFFF:192.0.2.128                       0000:0000:0000:0000:0000:FFFF:C000:0280
DEAD:BEEF:F00D:CAFE:DEA1:ACED:B00B:1234  DEAD:BEEF:F00D:CAFE:DEA1:ACED:B00B:1234
FE80:0:0:0:202:B3FF:FE1E:8329            FE80:0000:0000:0000:0202:B3FF:FE1E:8329
FE80:1:2:3:202:B3FF:FE1E:8329            FE80:0001:0002:0003:0202:B3FF:FE1E:8329
FE80:3:1:3:202:B3FF:FE1E:8328            FE80:0003:0001:0003:0202:B3FF:FE1E:8328
FE80:3:1:3:202:B3FF:FE1E:8329            FE80:0003:0001:0003:0202:B3FF:FE1E:8329
FE80:3:2:3:202:B3FF:FE1E:8329            FE80:0003:0002:0003:0202:B3FF:FE1E:8329

8 rows selected.

SQL>spool off

我只是在最后一小时把它放在一起(同时教自己的物体)所以我相信它可以改进。如果我做更新,我会在这里重新发布

答案 4 :(得分:1)

我希望存储的IP地址只是字符串格式,由SYS_CONTEXT返回(&#39; USERENV&#39;,&#39; IP_ADDRESS&#39;)

在11g中的SYS_CONTEXT的引用中,仅将默认返回值长度描述为256字节,并且没有描述exacly&#39; IP_ADDRESS&#39;的返回值大小。上下文。

在文件Oracle Database and IPv6 Statement of Direction中描述:

  

Oracle Database 11g第2版支持标准IPv6地址   RFC2732指定的符号。通常是128位IP地址   表示为8组,每组4个十六进制数字,其中“:”符号为   组分隔符。每组中的前导零被删除。对于   例如,1080:0:0:0:8:800:200C:417A将是有效的IPv6地址。一   或者更多连续的零字段可以选择用   “::“ 分隔器。例如,1080 :: 8:800:200C:417A。

根据这些说明,我更喜欢将列 IP_ADDRESS varchar2(39)设为允许存储8组4位数和7组之间的分隔符。

答案 5 :(得分:0)

Oracle文档确实说明INTEGER是NUMBER(38)的别名,但这可能是一个错字,因为它上面的段落指出:

  

NUMBER(p,s)其中:p是精度... Oracle保证   数字的可移植性,精度高达20个基数-100位,   相当于39或40位十进制数字,具体取决于   小数点的位置。

因此NUMBER可以存储39到40位数字,而INTEGER可能是NUMBER(最大精度)而不是NUMBER(38)的别名。这就是为什么提供的示例有效(如果您将INTEGER更改为NUMBER,它就有效)。

答案 6 :(得分:0)

可能性是:

  • 以字符串形式存储,即VARCHAR2(示例1080::8:800:200c:417a
  • 存储为数值
    • NUMBER数据类型
    • INTEGER数据类型
  • RAW值存储
    • 一个RAW值,分别为IPv4或IPv6的RAW(4)RAW(16)
    • 分别为IPv4或IPv6的4 x RAW(1)或8 x RAW(2)

我建议使用RAW值,因为

  • 如果使用字符串,则必须考虑不同的IPv6格式。

    1080::8:800:200C:417A
    1080::8:800:200c:417a
    1080::8:800:32.12.65.122
    1080:0:0:0:8:800:200C:417A
    1080:0:0:0:0008:0800:200C:417A
    1080:0000:0000:0000:0008:0800:200C:417A
    

    是同一IPv6 IP地址的所有合法表示。您的应用程序需要强制执行适当使用的通用格式,例如在WHERE条件下使用。

  • NUMBER/INTEGER值无意义,无需转换为人类可读的格式。您不能在PL / SQL

    中使用INTEGER数据类型
    i INTEGER := 2**128-1; -- i.e. ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
    
    -> ORA-06502: PL/SQL: numeric or value error: number precision too large. 
    
  • 如果您必须使用子网划分,则无法使用函数BITAND - 它还支持最多2 ^ 127的数字

  • 您可以使用UTL_RAW函数UTL_RAW.BIT_ANDUTL_RAW.BIT_COMPLEMENTUTL_RAW.BIT_OR进行子网操作。

  • 如果你必须处理真正大量的数据(我说的是数十亿行),将IP地址分成几个RAW值可能是有益的,即4 x RAW(1)或8 x RAW(2)。这些列将预定为Bitmap-Indexes,您可以节省大量磁盘空间并获得性能。