mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-15 14:26:33 +01:00
d559eed93a
Add a library function to parse and validate an IPv6 address from a string. It can parse the first two versions of an IPv6 address according to https://datatracker.ietf.org/doc/html/rfc4291#section-2.2. The third form "x❌x❌x:x.d.d.d.d" is not yet implemented. Optionally parser can accept prefix length (128 is default). Add shell script network.sh to test IPv6 parser functionality.
209 lines
5.9 KiB
Nix
209 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";
|
|
};
|
|
}
|