Skip to content

Commit 10bfec6

Browse files
committed
Generate ULA network address from router hostname
1 parent 94e257d commit 10bfec6

File tree

4 files changed

+189
-44
lines changed

4 files changed

+189
-44
lines changed

lib-tests.nix

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,66 @@
11
{ lib, emptyFile }:
22
let
33
inherit (import ./lib.nix { inherit lib; })
4+
hostHexetsFromMacAddress
45
leftShift
5-
parseIpv6Network
66
mkIpv6Address
7-
hostHexetsFromMacAddress
7+
mkUlaNetwork
8+
networkMaskHextets
9+
parseIpv6Network
10+
power
811
;
912

1013
assertions = map ({ assertion, message }: lib.assertMsg assertion message) [
14+
{
15+
assertion = power 2 2 == 4;
16+
message = "pow(2, 2) == 4";
17+
}
1118
{
1219
assertion = leftShift 1 8 == 256;
1320
message = "1<<8 == 256";
1421
}
22+
{
23+
assertion =
24+
networkMaskHextets 64 == [
25+
65535
26+
65535
27+
65535
28+
65535
29+
0
30+
0
31+
0
32+
0
33+
];
34+
message = "simple network mask";
35+
}
36+
{
37+
assertion =
38+
mkUlaNetwork [
39+
0
40+
0
41+
0
42+
0
43+
0
44+
0
45+
0
46+
0
47+
] 64 == "fc00:0000:0000:0000:0000:0000:0000:0000/64";
48+
message = "simple ULA network";
49+
}
50+
{
51+
assertion =
52+
mkUlaNetwork [
53+
65535
54+
0
55+
0
56+
0
57+
0
58+
0
59+
0
60+
0
61+
] 64 == "fdff:0000:0000:0000:0000:0000:0000:0000/64";
62+
message = "less simple ULA network";
63+
}
1564
{
1665
assertion =
1766
(parseIpv6Network "2001:db8::/48") == {

lib.nix

Lines changed: 126 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,69 @@
22
lib ? (import <nixpkgs> { }).lib,
33
}:
44
let
5-
leftShift = num: n: if n == 0 then num else leftShift (num * 2) (n - 1);
6-
7-
hexChars = lib.stringToCharacters "0123456789abcdef";
5+
inherit (builtins)
6+
bitAnd
7+
bitOr
8+
bitXor
9+
elemAt
10+
genList
11+
hashString
12+
head
13+
substring
14+
tail
15+
;
16+
17+
inherit (lib)
18+
concatMapStringsSep
19+
findFirst
20+
fixedWidthNumber
21+
flatten
22+
flip
23+
foldl
24+
length
25+
min
26+
mod
27+
optional
28+
range
29+
splitString
30+
stringToCharacters
31+
sublist
32+
toHexString
33+
toInt
34+
toLower
35+
zipLists
36+
zipListsWith
37+
;
38+
39+
power' =
40+
product: base: exp:
41+
if exp == 0 then product else power' (product * base) base (exp - 1);
42+
43+
power = power' 1;
44+
45+
leftShift = flip power' 2;
46+
47+
hexChars = stringToCharacters "0123456789abcdef";
848

949
# Return an integer between 0 and 15 representing the hex digit
1050
fromHexDigit =
11-
c:
12-
(lib.findFirst (x: x.fst == c) c (lib.zipLists hexChars (lib.range 0 (lib.length hexChars - 1))))
13-
.snd;
51+
c: (findFirst (x: x.fst == c) c (zipLists hexChars (range 0 (length hexChars - 1)))).snd;
1452

15-
fromHex = s: lib.foldl (a: b: a * 16 + fromHexDigit b) 0 (lib.stringToCharacters (lib.toLower s));
53+
fromHex = s: foldl (a: b: a * 16 + fromHexDigit b) 0 (stringToCharacters (toLower s));
1654

17-
toNetworkHexString = num: lib.toLower (lib.toHexString num);
55+
toNetworkHexString = num: toLower (toHexString num);
1856

19-
toHextetString = hextetNum: lib.fixedWidthNumber 4 (toNetworkHexString hextetNum);
57+
toHextetString = hextetNum: fixedWidthNumber 4 (toNetworkHexString hextetNum);
2058
in
2159
rec {
22-
inherit leftShift;
60+
inherit leftShift power;
61+
62+
generateHextets =
63+
value:
64+
let
65+
hash = hashString "sha256" value;
66+
in
67+
genList (x: fromHex (substring x 4 hash)) 8;
2368

2469
# Parse an IPv6 network address in CIDR form.
2570
#
@@ -28,34 +73,32 @@ rec {
2873
parseIpv6Network =
2974
networkCidr:
3075
let
31-
split = lib.splitString "/" networkCidr;
76+
split = splitString "/" networkCidr;
3277

33-
prefixLength = lib.toInt (lib.elemAt split 1);
78+
prefixLength = toInt (elemAt split 1);
3479

35-
unfilledHextets = map (lib.splitString ":") (lib.splitString "::" (lib.elemAt split 0));
80+
unfilledHextets = map (splitString ":") (splitString "::" (elemAt split 0));
3681

37-
numNeededHextets = lib.foldl (sum: xs: sum + lib.length xs) 0 unfilledHextets;
82+
numNeededHextets = foldl (sum: xs: sum + length xs) 0 unfilledHextets;
3883

39-
unmodifiedHextets = lib.flatten [
40-
(map fromHex (lib.elemAt unfilledHextets 0))
41-
(builtins.genList (_: 0) numNeededHextets)
42-
(map fromHex (lib.elemAt unfilledHextets 1))
84+
unmodifiedHextets = flatten [
85+
(map fromHex (elemAt unfilledHextets 0))
86+
(genList (_: 0) numNeededHextets)
87+
(map fromHex (elemAt unfilledHextets 1))
4388
];
4489

45-
fullHextets = lib.sublist 0 (prefixLength / 16) unmodifiedHextets;
90+
fullHextets = sublist 0 (prefixLength / 16) unmodifiedHextets;
4691

47-
nextHextetBits = lib.mod prefixLength 16;
92+
nextHextetBits = mod prefixLength 16;
4893

49-
partialHextet = lib.optional (nextHextetBits != 0) (
50-
lib.bitAnd (lib.elemAt unmodifiedHextets (lib.length fullHextets)) (
94+
partialHextet = optional (nextHextetBits != 0) (
95+
bitAnd (elemAt unmodifiedHextets (length fullHextets)) (
5196
leftShift ((leftShift 1 nextHextetBits) - 1) (16 - nextHextetBits)
5297
)
5398
);
5499

55100
hextets =
56-
fullHextets
57-
++ partialHextet
58-
++ builtins.genList (_: 0) (8 - (lib.length fullHextets) - (lib.length partialHextet));
101+
fullHextets ++ partialHextet ++ genList (_: 0) (8 - (length fullHextets) - (length partialHextet));
59102
in
60103
{
61104
inherit prefixLength hextets;
@@ -67,9 +110,9 @@ rec {
67110
# => "2001:0db8:0000:0000:0000:0000:0000:0001"
68111
mkIpv6Address =
69112
networkHextets: hostHextets:
70-
assert lib.length networkHextets == 8;
71-
assert lib.length hostHextets == 8;
72-
lib.concatMapStringsSep ":" toHextetString (lib.zipListsWith lib.bitOr networkHextets hostHextets);
113+
assert length networkHextets == 8;
114+
assert length hostHextets == 8;
115+
concatMapStringsSep ":" toHextetString (zipListsWith bitOr networkHextets hostHextets);
73116

74117
# Generates the hextets of an IPv6 address with the last 64 bits populated
75118
# based on the host's MAC address.
@@ -82,16 +125,63 @@ rec {
82125
ff = fromHex "ff";
83126
fe = fromHex "fe";
84127

85-
macNums = map fromHex (lib.splitString ":" macAddress);
128+
macNums = map fromHex (splitString ":" macAddress);
86129

87-
mkHextet = upper: lower: lib.bitOr (leftShift upper 8) lower;
130+
mkHextet = upper: lower: bitOr (leftShift upper 8) lower;
88131
in
89-
assert lib.length macNums == 6; # ensure the MAC address is the correct length
90-
(builtins.genList (_: 0) 4)
132+
assert length macNums == 6; # ensure the MAC address is the correct length
133+
(genList (_: 0) 4)
91134
++ [
92-
(mkHextet (builtins.bitXor (builtins.elemAt macNums 0) 2) (builtins.elemAt macNums 1))
93-
(mkHextet (builtins.elemAt macNums 2) ff)
94-
(mkHextet fe (builtins.elemAt macNums 3))
95-
(mkHextet (builtins.elemAt macNums 4) (builtins.elemAt macNums 5))
135+
(mkHextet (bitXor (elemAt macNums 0) 2) (elemAt macNums 1))
136+
(mkHextet (elemAt macNums 2) ff)
137+
(mkHextet fe (elemAt macNums 3))
138+
(mkHextet (elemAt macNums 4) (elemAt macNums 5))
96139
];
140+
141+
# Generate a list of hextets for a network address from a prefix length.
142+
#
143+
# Example: networkMaskHextets 64
144+
# => [ 65535 65535 65535 65535 0 0 0 0 ]
145+
networkMaskHextets =
146+
let
147+
networkMaskHextets' =
148+
hextets: bits:
149+
let
150+
bits' = bits - 16;
151+
in
152+
if bits' < 0 then
153+
hextets ++ genList (_: 0) (8 - length hextets)
154+
else
155+
networkMaskHextets' (
156+
hextets
157+
++ [
158+
((leftShift 1 (min 16 bits)) - 1)
159+
]
160+
) bits';
161+
in
162+
networkMaskHextets' [ ];
163+
164+
# A ULA address is any address in the fc00::/7 network.
165+
mkUlaNetwork =
166+
hextets: prefixLength:
167+
let
168+
firstHextet = fromHex "fc00";
169+
170+
firstHextet' = (
171+
bitOr firstHextet (
172+
# take the last 9 bits of the first hextet
173+
bitAnd 511 (head hextets)
174+
)
175+
);
176+
177+
hextets' = [ firstHextet' ] ++ (sublist 0 7 (tail hextets));
178+
179+
address = mkIpv6Address (
180+
# This isn't entirely necessary, but makes the address look normal in
181+
# config files.
182+
zipListsWith bitAnd hextets' (networkMaskHextets prefixLength)
183+
) (genList (_: 0) 8);
184+
in
185+
assert length hextets == 8;
186+
"${address}/${toString prefixLength}";
97187
}

options.nix

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22
let
33
cfg = config.router;
44

5-
_lib = import ./lib.nix { inherit lib; };
5+
inherit (import ./lib.nix { inherit lib; })
6+
generateHextets
7+
mkUlaNetwork
8+
parseIpv6Network
9+
;
610

711
hasStaticGua = cfg.ipv6GuaPrefix != null;
8-
guaNetwork = _lib.parseIpv6Network cfg.ipv6GuaPrefix;
9-
ulaNetwork = _lib.parseIpv6Network cfg.ipv6UlaPrefix;
12+
guaNetwork = parseIpv6Network cfg.ipv6GuaPrefix;
13+
ulaNetwork = parseIpv6Network cfg.ipv6UlaPrefix;
1014
in
1115
{
1216
options.router = with lib; {
@@ -100,11 +104,12 @@ in
100104
};
101105

102106
ipv6UlaPrefix = mkOption {
107+
internal = true;
108+
readOnly = true;
103109
type = types.str;
104110
example = "fd38:5f81:b15d::/64";
105111
description = ''
106-
The 64-bit IPv6 ULA network prefix (in CIDR notation). You can generate
107-
a ULA prefix at https://www.ip-six.de/index.php.
112+
The 64-bit IPv6 ULA network prefix (in CIDR notation).
108113
'';
109114
};
110115

@@ -137,5 +142,7 @@ in
137142
assertion = (hasStaticGua -> (guaNetwork.prefixLength <= 64)) && (ulaNetwork.prefixLength <= 64);
138143
}
139144
];
145+
146+
router.ipv6UlaPrefix = mkUlaNetwork (generateHextets config.networking.hostName) 64;
140147
};
141148
}

test.nix

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ nixosTest {
77
virtualisation.vlans = [ 1 ];
88
router = {
99
enable = true;
10-
ipv6UlaPrefix = "fdc8:2291:4584::/64";
1110
wanInterface = "eth0";
1211
lanInterface = "eth1";
1312
};

0 commit comments

Comments
 (0)