Kjell-Magne Øierud

Software developer

IPv6 addresses and MySQL 19 Feb 2009

I usually prefer storing IP addresses as integers (unless the DBMS have a special data type for it). Using integers gives compact storage and makes it easy to answer questions like what is the next IP address? In which subnets does it belong? Etc. IPv6 addresses is 128 bits long, so to store that as an integer in MySQL you need to use the DECIMAL data type.

In MySQL there is two functions that help you translate between the familiar canonical version and the numeric version of IP addresses: INET_ATON() and INET_NTOA(). However these functions do not work with IPv6 addresses.

One possible solution to this is to implement INET_ATON6() and INET_NTOA6() yourself as stored functions:

INET_ATON6

DELIMITER //
CREATE FUNCTION INET_ATON6(n CHAR(39))
RETURNS DECIMAL(39) UNSIGNED
DETERMINISTIC
BEGIN
    RETURN CAST(CONV(SUBSTRING(n FROM  1 FOR 4), 16, 10) AS DECIMAL(39))
                       * 5192296858534827628530496329220096 -- 65536 ^ 7
         + CAST(CONV(SUBSTRING(n FROM  6 FOR 4), 16, 10) AS DECIMAL(39))
                       *      79228162514264337593543950336 -- 65536 ^ 6
         + CAST(CONV(SUBSTRING(n FROM 11 FOR 4), 16, 10) AS DECIMAL(39))
                       *          1208925819614629174706176 -- 65536 ^ 5
         + CAST(CONV(SUBSTRING(n FROM 16 FOR 4), 16, 10) AS DECIMAL(39))
                       *               18446744073709551616 -- 65536 ^ 4
         + CAST(CONV(SUBSTRING(n FROM 21 FOR 4), 16, 10) AS DECIMAL(39))
                       *                    281474976710656 -- 65536 ^ 3
         + CAST(CONV(SUBSTRING(n FROM 26 FOR 4), 16, 10) AS DECIMAL(39))
                       *                         4294967296 -- 65536 ^ 2
         + CAST(CONV(SUBSTRING(n FROM 31 FOR 4), 16, 10) AS DECIMAL(39))
                       *                              65536 -- 65536 ^ 1
         + CAST(CONV(SUBSTRING(n FROM 36 FOR 4), 16, 10) AS DECIMAL(39))
         ;
END;
//
DELIMITER ;

INET_NTOA6

DELIMITER //
CREATE FUNCTION INET_NTOA6(n DECIMAL(39) UNSIGNED)
RETURNS CHAR(39)
DETERMINISTIC
BEGIN
  DECLARE a CHAR(39)             DEFAULT '';
  DECLARE i INT                  DEFAULT 7;
  DECLARE q DECIMAL(39) UNSIGNED DEFAULT 0;
  DECLARE r INT                  DEFAULT 0;
  WHILE i DO
    -- DIV doesn't work with nubers > bigint
    SET q := FLOOR(n / 65536);
    SET r := n MOD 65536;
    SET n := q;
    SET a := CONCAT_WS(':', LPAD(CONV(r, 10, 16), 4, '0'), a);

    SET i := i - 1;
  END WHILE;

  SET a := TRIM(TRAILING ':' FROM CONCAT_WS(':',
                                            LPAD(CONV(n, 10, 16), 4, '0'),
                                            a));

  RETURN a;

END;
//
DELIMITER ;

The code is to be regarded as a proof of concept as it needs sanity checks and handling of simplified address notations before production use. However the following small test verifies that it is not completely broken:

mysql> SELECT INET_NTOA6(INET_ATON6(
    ->   '2001:0db8:85a3:0000:0000:8a2e:0370:7334'));
+-------------------------------------------------------------------+
| INET_NTOA6(INET_ATON6('2001:0db8:85a3:0000:0000:8a2e:0370:7334')) |
+-------------------------------------------------------------------+
| 2001:0DB8:85A3:0000:0000:8A2E:0370:7334                           |
+-------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT INET_NTOA6(INET_ATON6(
    ->   'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'));
+-------------------------------------------------------------------+
| INET_NTOA6(INET_ATON6('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff')) |
+-------------------------------------------------------------------+
| FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF                           |
+-------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select INET_NTOA6(INET_ATON6(
    ->   '0000:0000:0000:0000:0000:0000:0000:0000'));
+-------------------------------------------------------------------+
| INET_NTOA6(INET_ATON6('0000:0000:0000:0000:0000:0000:0000:0000')) |
+-------------------------------------------------------------------+
| 0000:0000:0000:0000:0000:0000:0000:0000                           |
+-------------------------------------------------------------------+
1 row in set (0.01 sec)

Copyright © 2008–2017 Kjell-Magne Øierud.