210 lines
5.9 KiB
Nix
210 lines
5.9 KiB
Nix
|
{
|
||
|
lib ? import ../.,
|
||
|
}:
|
||
|
let
|
||
|
inherit (builtins)
|
||
|
map
|
||
|
match
|
||
|
genList
|
||
|
length
|
||
|
concatMap
|
||
|
head
|
||
|
toString
|
||
|
;
|
||
|
|
||
|
inherit (lib) lists strings trivial;
|
||
|
|
||
|
inherit (lib.lists) last;
|
||
|
|
||
|
/*
|
||
|
IPv6 addresses are 128-bit identifiers. The preferred form is 'x:x:x:x:x:x:x:x',
|
||
|
where the 'x's are one to four hexadecimal digits of the eight 16-bit pieces of
|
||
|
the address. See RFC 4291.
|
||
|
*/
|
||
|
ipv6Bits = 128;
|
||
|
ipv6Pieces = 8; # 'x:x:x:x:x:x:x:x'
|
||
|
ipv6PieceBits = 16; # One piece in range from 0 to 0xffff.
|
||
|
ipv6PieceMaxValue = 65535; # 2^16 - 1
|
||
|
in
|
||
|
let
|
||
|
/**
|
||
|
Expand an IPv6 address by removing the "::" compression and padding them
|
||
|
with the necessary number of zeros. Converts an address from the string to
|
||
|
the list of strings which then can be parsed using `_parseExpanded`.
|
||
|
Throws an error when the address is malformed.
|
||
|
|
||
|
# Type: String -> [ String ]
|
||
|
|
||
|
# Example:
|
||
|
|
||
|
```nix
|
||
|
expandIpv6 "2001:DB8::ffff"
|
||
|
=> ["2001" "DB8" "0" "0" "0" "0" "0" "ffff"]
|
||
|
```
|
||
|
*/
|
||
|
expandIpv6 =
|
||
|
addr:
|
||
|
if match "^[0-9A-Fa-f:]+$" addr == null then
|
||
|
throw "${addr} contains malformed characters for IPv6 address"
|
||
|
else
|
||
|
let
|
||
|
pieces = strings.splitString ":" addr;
|
||
|
piecesNoEmpty = lists.remove "" pieces;
|
||
|
piecesNoEmptyLen = length piecesNoEmpty;
|
||
|
zeros = genList (_: "0") (ipv6Pieces - piecesNoEmptyLen);
|
||
|
hasPrefix = strings.hasPrefix "::" addr;
|
||
|
hasSuffix = strings.hasSuffix "::" addr;
|
||
|
hasInfix = strings.hasInfix "::" addr;
|
||
|
in
|
||
|
if addr == "::" then
|
||
|
zeros
|
||
|
else if
|
||
|
let
|
||
|
emptyCount = length pieces - piecesNoEmptyLen;
|
||
|
emptyExpected =
|
||
|
# splitString produces two empty pieces when "::" in the beginning
|
||
|
# or in the end, and only one when in the middle of an address.
|
||
|
if hasPrefix || hasSuffix then
|
||
|
2
|
||
|
else if hasInfix then
|
||
|
1
|
||
|
else
|
||
|
0;
|
||
|
in
|
||
|
emptyCount != emptyExpected
|
||
|
|| (hasInfix && piecesNoEmptyLen >= ipv6Pieces) # "::" compresses at least one group of zeros.
|
||
|
|| (!hasInfix && piecesNoEmptyLen != ipv6Pieces)
|
||
|
then
|
||
|
throw "${addr} is not a valid IPv6 address"
|
||
|
# Create a list of 8 elements, filling some of them with zeros depending
|
||
|
# on where the "::" was found.
|
||
|
else if hasPrefix then
|
||
|
zeros ++ piecesNoEmpty
|
||
|
else if hasSuffix then
|
||
|
piecesNoEmpty ++ zeros
|
||
|
else if hasInfix then
|
||
|
concatMap (piece: if piece == "" then zeros else [ piece ]) pieces
|
||
|
else
|
||
|
pieces;
|
||
|
|
||
|
/**
|
||
|
Parses an expanded IPv6 address (see `expandIpv6`), converting each part
|
||
|
from a string to an u16 integer. Returns an internal representation of IPv6
|
||
|
address (list of integers) that can be easily processed by other helper
|
||
|
functions.
|
||
|
Throws an error some element is not an u16 integer.
|
||
|
|
||
|
# Type: [ String ] -> IPv6
|
||
|
|
||
|
# Example:
|
||
|
|
||
|
```nix
|
||
|
parseExpandedIpv6 ["2001" "DB8" "0" "0" "0" "0" "0" "ffff"]
|
||
|
=> [8193 3512 0 0 0 0 0 65535]
|
||
|
```
|
||
|
*/
|
||
|
parseExpandedIpv6 =
|
||
|
addr:
|
||
|
assert lib.assertMsg (
|
||
|
length addr == ipv6Pieces
|
||
|
) "parseExpandedIpv6: expected list of integers with ${ipv6Pieces} elements";
|
||
|
let
|
||
|
u16FromHexStr =
|
||
|
hex:
|
||
|
let
|
||
|
parsed = trivial.fromHexString hex;
|
||
|
in
|
||
|
if 0 <= parsed && parsed <= ipv6PieceMaxValue then
|
||
|
parsed
|
||
|
else
|
||
|
throw "0x${hex} is not a valid u16 integer";
|
||
|
in
|
||
|
map (piece: u16FromHexStr piece) addr;
|
||
|
in
|
||
|
let
|
||
|
/**
|
||
|
Parses an IPv6 address from a string to the internal representation (list
|
||
|
of integers).
|
||
|
|
||
|
# Type: String -> IPv6
|
||
|
|
||
|
# Example:
|
||
|
|
||
|
```nix
|
||
|
parseIpv6FromString "2001:DB8::ffff"
|
||
|
=> [8193 3512 0 0 0 0 0 65535]
|
||
|
```
|
||
|
*/
|
||
|
parseIpv6FromString = addr: parseExpandedIpv6 (expandIpv6 addr);
|
||
|
in
|
||
|
{
|
||
|
/*
|
||
|
Internally, an IPv6 address is stored as a list of 16-bit integers with 8
|
||
|
elements. Wherever you see `IPv6` in internal functions docs, it means that
|
||
|
it is a list of integers produced by one of the internal parsers, such as
|
||
|
`parseIpv6FromString`
|
||
|
*/
|
||
|
_ipv6 = {
|
||
|
/**
|
||
|
Converts an internal representation of an IPv6 address (i.e, a list
|
||
|
of integers) to a string. The returned string is not a canonical
|
||
|
representation as defined in RFC 5952, i.e zeros are not compressed.
|
||
|
|
||
|
# Type: IPv6 -> String
|
||
|
|
||
|
# Example:
|
||
|
|
||
|
```nix
|
||
|
parseIpv6FromString [8193 3512 0 0 0 0 0 65535]
|
||
|
=> "2001:db8:0:0:0:0:0:ffff"
|
||
|
```
|
||
|
*/
|
||
|
toStringFromExpandedIp =
|
||
|
pieces: strings.concatMapStringsSep ":" (piece: strings.toLower (trivial.toHexString piece)) pieces;
|
||
|
|
||
|
/**
|
||
|
Extract an address and subnet prefix length from a string. The subnet
|
||
|
prefix length is optional and defaults to 128. The resulting address and
|
||
|
prefix length are validated and converted to an internal representation
|
||
|
that can be used by other functions.
|
||
|
|
||
|
# Type: String -> [ {address :: IPv6, prefixLength :: Int} ]
|
||
|
|
||
|
# Example:
|
||
|
|
||
|
```nix
|
||
|
split "2001:DB8::ffff/32"
|
||
|
=> {
|
||
|
address = [8193 3512 0 0 0 0 0 65535];
|
||
|
prefixLength = 32;
|
||
|
}
|
||
|
```
|
||
|
*/
|
||
|
split =
|
||
|
addr:
|
||
|
let
|
||
|
splitted = strings.splitString "/" addr;
|
||
|
splittedLength = length splitted;
|
||
|
in
|
||
|
if splittedLength == 1 then # [ ip ]
|
||
|
{
|
||
|
address = parseIpv6FromString addr;
|
||
|
prefixLength = ipv6Bits;
|
||
|
}
|
||
|
else if splittedLength == 2 then # [ ip subnet ]
|
||
|
{
|
||
|
address = parseIpv6FromString (head splitted);
|
||
|
prefixLength =
|
||
|
let
|
||
|
n = strings.toInt (last splitted);
|
||
|
in
|
||
|
if 1 <= n && n <= ipv6Bits then
|
||
|
n
|
||
|
else
|
||
|
throw "${addr} IPv6 subnet should be in range [1;${toString ipv6Bits}], got ${toString n}";
|
||
|
}
|
||
|
else
|
||
|
throw "${addr} is not a valid IPv6 address in CIDR notation";
|
||
|
};
|
||
|
}
|