15 inherit (lib) lists strings trivial;
17 inherit (lib.lists) last;
20 IPv6 addresses are 128-bit identifiers. The preferred form is 'x:x:x:x:x:x:x:x',
21 where the 'x's are one to four hexadecimal digits of the eight 16-bit pieces of
22 the address. See RFC 4291.
25 ipv6Pieces = 8; # 'x:x:x:x:x:x:x:x'
26 ipv6PieceBits = 16; # One piece in range from 0 to 0xffff.
27 ipv6PieceMaxValue = 65535; # 2^16 - 1
31 Expand an IPv6 address by removing the "::" compression and padding them
32 with the necessary number of zeros. Converts an address from the string to
33 the list of strings which then can be parsed using `_parseExpanded`.
34 Throws an error when the address is malformed.
36 # Type: String -> [ String ]
41 expandIpv6 "2001:DB8::ffff"
42 => ["2001" "DB8" "0" "0" "0" "0" "0" "ffff"]
47 if match "^[0-9A-Fa-f:]+$" addr == null then
48 throw "${addr} contains malformed characters for IPv6 address"
51 pieces = strings.splitString ":" addr;
52 piecesNoEmpty = lists.remove "" pieces;
53 piecesNoEmptyLen = length piecesNoEmpty;
54 zeros = genList (_: "0") (ipv6Pieces - piecesNoEmptyLen);
55 hasPrefix = strings.hasPrefix "::" addr;
56 hasSuffix = strings.hasSuffix "::" addr;
57 hasInfix = strings.hasInfix "::" addr;
63 emptyCount = length pieces - piecesNoEmptyLen;
65 # splitString produces two empty pieces when "::" in the beginning
66 # or in the end, and only one when in the middle of an address.
67 if hasPrefix || hasSuffix then
74 emptyCount != emptyExpected
75 || (hasInfix && piecesNoEmptyLen >= ipv6Pieces) # "::" compresses at least one group of zeros.
76 || (!hasInfix && piecesNoEmptyLen != ipv6Pieces)
78 throw "${addr} is not a valid IPv6 address"
79 # Create a list of 8 elements, filling some of them with zeros depending
80 # on where the "::" was found.
81 else if hasPrefix then
82 zeros ++ piecesNoEmpty
83 else if hasSuffix then
84 piecesNoEmpty ++ zeros
86 concatMap (piece: if piece == "" then zeros else [ piece ]) pieces
91 Parses an expanded IPv6 address (see `expandIpv6`), converting each part
92 from a string to an u16 integer. Returns an internal representation of IPv6
93 address (list of integers) that can be easily processed by other helper
95 Throws an error some element is not an u16 integer.
97 # Type: [ String ] -> IPv6
102 parseExpandedIpv6 ["2001" "DB8" "0" "0" "0" "0" "0" "ffff"]
103 => [8193 3512 0 0 0 0 0 65535]
108 assert lib.assertMsg (
109 length addr == ipv6Pieces
110 ) "parseExpandedIpv6: expected list of integers with ${ipv6Pieces} elements";
115 parsed = trivial.fromHexString hex;
117 if 0 <= parsed && parsed <= ipv6PieceMaxValue then
120 throw "0x${hex} is not a valid u16 integer";
122 map (piece: u16FromHexStr piece) addr;
126 Parses an IPv6 address from a string to the internal representation (list
129 # Type: String -> IPv6
134 parseIpv6FromString "2001:DB8::ffff"
135 => [8193 3512 0 0 0 0 0 65535]
138 parseIpv6FromString = addr: parseExpandedIpv6 (expandIpv6 addr);
142 Internally, an IPv6 address is stored as a list of 16-bit integers with 8
143 elements. Wherever you see `IPv6` in internal functions docs, it means that
144 it is a list of integers produced by one of the internal parsers, such as
145 `parseIpv6FromString`
149 Converts an internal representation of an IPv6 address (i.e, a list
150 of integers) to a string. The returned string is not a canonical
151 representation as defined in RFC 5952, i.e zeros are not compressed.
153 # Type: IPv6 -> String
158 parseIpv6FromString [8193 3512 0 0 0 0 0 65535]
159 => "2001:db8:0:0:0:0:0:ffff"
162 toStringFromExpandedIp =
163 pieces: strings.concatMapStringsSep ":" (piece: strings.toLower (trivial.toHexString piece)) pieces;
166 Extract an address and subnet prefix length from a string. The subnet
167 prefix length is optional and defaults to 128. The resulting address and
168 prefix length are validated and converted to an internal representation
169 that can be used by other functions.
171 # Type: String -> [ {address :: IPv6, prefixLength :: Int} ]
176 split "2001:DB8::ffff/32"
178 address = [8193 3512 0 0 0 0 0 65535];
186 splitted = strings.splitString "/" addr;
187 splittedLength = length splitted;
189 if splittedLength == 1 then # [ ip ]
191 address = parseIpv6FromString addr;
192 prefixLength = ipv6Bits;
194 else if splittedLength == 2 then # [ ip subnet ]
196 address = parseIpv6FromString (head splitted);
199 n = strings.toInt (last splitted);
201 if 1 <= n && n <= ipv6Bits then
204 throw "${addr} IPv6 subnet should be in range [1;${toString ipv6Bits}], got ${toString n}";
207 throw "${addr} is not a valid IPv6 address in CIDR notation";