Skip to content

Commit d1fe60a

Browse files
committed
Configurable network settings
* Options to customise every VM's networking features, such as MAC, IP, NIC name, etc. * hosts file is automatically updated whenever the associated options, such as hostname or IP address, are changed. * Conflict detection with assertion in hosts file. Signed-off-by: Enes Öztürk <enes.ozturk@unikie.com>
1 parent 699649b commit d1fe60a

File tree

12 files changed

+319
-67
lines changed

12 files changed

+319
-67
lines changed

modules/common/common.nix

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ in
2424
# This line breaks build of GUIVM. No investigations of a
2525
# root cause are done so far.
2626
#(modulesPath + "/profiles/minimal.nix")
27+
2728
];
2829

2930
options.ghaf = {
@@ -43,6 +44,17 @@ in
4344
default = [ ];
4445
description = "List of app hosts currently enabled.";
4546
};
47+
extraNetworking = {
48+
hosts = mkOption {
49+
type =
50+
let
51+
extraNetworkingType = import ./networking/common_types.nix { inherit lib; };
52+
in
53+
types.attrsOf extraNetworkingType;
54+
description = "Extra host entries that override or extend the generated ones.";
55+
default = { };
56+
};
57+
};
4658
hardware = {
4759
nics = mkOption {
4860
type = types.listOf types.attrs;
@@ -69,8 +81,8 @@ in
6981
"app-vm"
7082
];
7183
};
72-
};
7384

85+
};
7486
config = {
7587

7688
# Populate the shared namespace
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors
2+
# SPDX-License-Identifier: Apache-2.0
3+
{ lib }:
4+
5+
with lib;
6+
7+
types.submodule {
8+
options = {
9+
interfaceName = lib.mkOption {
10+
type = types.nullOr types.str;
11+
default = null;
12+
description = "Name of the network interface.";
13+
};
14+
name = mkOption {
15+
type = types.nullOr types.str;
16+
description = "Host name as string.";
17+
default = null;
18+
};
19+
mac = mkOption {
20+
type = types.nullOr types.str;
21+
description = "MAC address as string.";
22+
default = null;
23+
};
24+
ipv4 = mkOption {
25+
type = types.nullOr types.str;
26+
description = "IPv4 address as string.";
27+
default = null;
28+
};
29+
ipv6 = mkOption {
30+
type = types.nullOr types.str;
31+
description = "IPv6 address as string.";
32+
default = null;
33+
};
34+
ipv4SubnetPrefixLength = mkOption {
35+
type = types.nullOr types.int;
36+
default = null;
37+
description = "The IPv4 subnet prefix length (e.g. 24 for 255.255.255.0)";
38+
example = 24;
39+
};
40+
};
41+
}

modules/common/networking/hosts.nix

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors
22
# SPDX-License-Identifier: Apache-2.0
33
{ config, lib, ... }:
4+
45
let
56
inherit (lib)
67
foldr
@@ -42,6 +43,12 @@ let
4243
IPv6 address as string.
4344
'';
4445
};
46+
ipv4SubnetPrefixLength = mkOption {
47+
type = types.int;
48+
default = 24;
49+
description = "The IPv4 subnet prefix length (e.g. 24 for 255.255.255.0)";
50+
example = 24;
51+
};
4552
cid = mkOption {
4653
type = types.int;
4754
description = ''
@@ -66,14 +73,14 @@ let
6673
ipv6BaseAddress = "fd00::100:";
6774

6875
# Generate host entries
69-
# TODO Add sockets
70-
hosts =
76+
generatedHosts =
7177
lib.lists.imap1 (idx: name: {
7278
inherit name;
7379
mac = "${macBaseAddress}${optionalString (idx < 16) "0"}${trivial.toHexString idx}";
7480
ipv4 = "${ipv4BaseAddress}${toString idx}";
7581
ipv6 = "${ipv6BaseAddress}${toString idx}";
7682
cid = if name == "net-vm" then (length hostList) + 1 else idx;
83+
ipv4SubnetPrefixLength = 24;
7784
}) hostList
7885
++ lib.lists.imap1 (
7986
index: name:
@@ -86,35 +93,111 @@ let
8693
ipv4 = "${ipv4BaseAddress}${toString idx}";
8794
ipv6 = "${ipv6BaseAddress}${toString idx}";
8895
cid = idx;
96+
ipv4SubnetPrefixLength = 24;
8997
}
9098
) config.ghaf.common.appHosts;
99+
100+
# Evaluate generated hosts as attrset
101+
generatedHostAttrs = listToAttrs (map (host: nameValuePair host.name host) generatedHosts);
102+
# Extract names of all extra hosts
103+
extraHostNames = lib.attrNames config.ghaf.common.extraNetworking.hosts;
104+
105+
# Merge logic per host
106+
mergedExtraHosts = listToAttrs (
107+
map (
108+
name:
109+
let
110+
gen = generatedHostAttrs.${name};
111+
extra = config.ghaf.common.extraNetworking.hosts.${name};
112+
in
113+
nameValuePair name {
114+
inherit name;
115+
mac = if extra ? mac && extra.mac != null then extra.mac else gen.mac;
116+
ipv4 = if extra ? ipv4 && extra.ipv4 != null then extra.ipv4 else gen.ipv4;
117+
ipv6 = if extra ? ipv6 && extra.ipv6 != null then extra.ipv6 else gen.ipv6;
118+
ipv4SubnetPrefixLength =
119+
if extra ? ipv4SubnetPrefixLength && extra.ipv4SubnetPrefixLength != null then
120+
extra.ipv4SubnetPrefixLength
121+
else
122+
gen.ipv4SubnetPrefixLength;
123+
124+
inherit (gen) cid;
125+
}
126+
) extraHostNames
127+
);
128+
129+
# Combine generated and extra hosts (extra overrides or extends)
130+
combinedHosts = generatedHostAttrs // mergedExtraHosts;
131+
132+
# networking.hosts derived from merged host entries
133+
networkingHosts = foldr recursiveUpdate { } (
134+
map (host: {
135+
"${host.ipv4}" = [ host.name ];
136+
}) (lib.attrValues combinedHosts)
137+
);
138+
# Extract values to check for uniqueness
139+
allHosts = lib.attrValues combinedHosts;
140+
getField = field: map (h: h.${field}) allHosts;
141+
142+
checkUnique =
143+
field:
144+
let
145+
values = getField field;
146+
unique = lib.lists.unique values;
147+
148+
# Find duplicates by filtering values that occur more than once
149+
duplicates = lib.lists.filter (
150+
value: lib.lists.length (lib.lists.filter (x: x == value) values) > 1
151+
) unique;
152+
153+
# Create a list of duplicates with the corresponding host names
154+
duplicateNames = lib.lists.filter (
155+
host: lib.lists.length (lib.lists.filter (x: x == host.${field}) values) > 1
156+
) allHosts;
157+
158+
in
159+
{
160+
inherit field;
161+
ok = values == unique;
162+
inherit duplicates;
163+
# Extract host names for duplicates
164+
duplicateNames = map (host: host.name) duplicateNames;
165+
};
166+
167+
uniquenessChecks = map checkUnique [
168+
"mac"
169+
"ipv4"
170+
"ipv6"
171+
"cid"
172+
"name"
173+
];
174+
175+
uniquenessAssertions = map (check: {
176+
assertion = check.ok;
177+
message = "Duplicate ${check.field} values detected: ${lib.concatStringsSep ", " check.duplicates}, conflict between:${lib.concatStringsSep ", " check.duplicateNames}";
178+
179+
}) uniquenessChecks;
91180
in
92181
{
93182
options.ghaf.networking = {
94183
hosts = mkOption {
95184
type = types.attrsOf hostEntrySubmodule;
96-
description = ''
97-
List of hosts entries.
98-
'';
99-
default = null;
185+
description = "List of hosts entries.";
186+
default = { };
100187
};
188+
101189
};
102190

103191
config = {
104-
105192
assertions = [
106193
{
107194
assertion = lib.length config.ghaf.common.vms < 255;
108195
message = "Too many VMs defined - maximum is 254";
109196
}
110-
];
197+
] ++ uniquenessAssertions;
111198

112-
ghaf.networking.hosts = listToAttrs (map (host: nameValuePair "${host.name}" host) hosts);
199+
ghaf.networking.hosts = combinedHosts;
113200

114-
networking.hosts = foldr recursiveUpdate { } (
115-
map (vm: {
116-
"${vm.ipv4}" = [ "${vm.name}" ];
117-
}) hosts
118-
);
201+
networking.hosts = networkingHosts;
119202
};
120203
}

modules/microvm/appvm.nix

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ let
1919
optionalAttrs
2020
;
2121
inherit (configHost.ghaf.virtualization.microvm-host) sharedVmDirectory;
22-
2322
makeVm =
2423
{ vm }:
2524
let
@@ -104,10 +103,14 @@ let
104103
};
105104

106105
# Networking
107-
virtualization.microvm.vm-networking = {
108-
enable = true;
109-
inherit vmName;
110-
};
106+
virtualization.microvm.vm-networking =
107+
{
108+
enable = true;
109+
inherit vmName;
110+
}
111+
// lib.optionalAttrs ((vm.extraNetworking.interfaceName or null) != null) {
112+
inherit (vm.extraNetworking) interfaceName;
113+
};
111114

112115
# Services
113116
waypipe =
@@ -302,11 +305,14 @@ in
302305
type = types.listOf types.package;
303306
default = [ ];
304307
};
305-
macAddress = mkOption {
306-
description = ''
307-
AppVM's network interface MAC address
308-
'';
309-
type = types.str;
308+
extraNetworking = lib.mkOption {
309+
type =
310+
let
311+
extraNetworkingType = import ../common/networking/common_types.nix { inherit lib; };
312+
in
313+
extraNetworkingType;
314+
description = "Extra Networking option";
315+
default = { };
310316
};
311317
ramMb = mkOption {
312318
description = ''
@@ -444,5 +450,13 @@ in
444450
}) vmsWithWaypipe;
445451
}
446452
];
453+
454+
ghaf.common.extraNetworking.hosts = lib.mapAttrs' (name: vm: {
455+
name = "${name}-vm";
456+
value = lib.recursiveUpdate vm.extraNetworking {
457+
name = "${name}-vm"; # For example, add or override the `name` field
458+
};
459+
}) vms;
460+
447461
};
448462
}

modules/microvm/common/vm-networking.nix

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ in
7575
};
7676
networks."10-${cfg.interfaceName}" = {
7777
matchConfig.MACAddress = hosts.${cfg.vmName}.mac;
78-
addresses = [ { Address = "${hosts.${cfg.vmName}.ipv4}/24"; } ];
78+
addresses = [
79+
{ Address = "${hosts.${cfg.vmName}.ipv4}/${toString hosts.${cfg.vmName}.ipv4SubnetPrefixLength}"; }
80+
];
7981
linkConfig.RequiredForOnline = "routable";
8082
linkConfig.ActivationPolicy = "always-up";
8183
} // lib.optionalAttrs (!cfg.isGateway) { inherit gateway; };

modules/microvm/host/microvm-host.nix

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ in
3333
options.ghaf.virtualization.microvm-host = {
3434
enable = mkEnableOption "MicroVM Host";
3535
networkSupport = mkEnableOption "Network support services to run host applications.";
36+
extraNetworking = lib.mkOption {
37+
type =
38+
let
39+
extraNetworkingType = import ../../common/networking/common_types.nix { inherit lib; };
40+
in
41+
extraNetworkingType;
42+
description = "Extra Networking option";
43+
default = { };
44+
};
3645
sharedVmDirectory = {
3746
enable = mkEnableOption "shared directory" // {
3847
default = true;
@@ -49,12 +58,11 @@ in
4958
inotifyPassthrough = mkEnableOption "inotify passthrough" // {
5059
default = true;
5160
};
61+
5262
};
5363
};
5464

5565
config = mkMerge [
56-
# Always set the hostname
57-
{ networking.hostName = lib.mkDefault "ghaf-host"; }
5866
(mkIf cfg.enable {
5967
microvm.host.enable = true;
6068
# microvm.host.useNotifySockets = true;
@@ -81,6 +89,7 @@ in
8189
givc.host.enable = true;
8290
development.nix-setup.automatic-gc.enable = config.ghaf.development.nix-setup.enable;
8391
logging.client.enable = config.ghaf.logging.enable;
92+
common.extraNetworking.hosts.ghaf-host = cfg.extraNetworking;
8493
};
8594

8695
services.logind.lidSwitch = "ignore";

0 commit comments

Comments
 (0)