summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2021-10-23 16:04:01 +0200
committerGregor Kleen <gkleen@yggdrasil.li>2021-10-23 16:04:01 +0200
commit919684939475ffb8781b1bbb406d05d46c9d5cb1 (patch)
treee06329e8d405e87ef2dc50543f7759748ad63748
parent3aa74950b3099e75a6ef30333359ae5cec4e26d2 (diff)
downloadnixos-919684939475ffb8781b1bbb406d05d46c9d5cb1.tar
nixos-919684939475ffb8781b1bbb406d05d46c9d5cb1.tar.gz
nixos-919684939475ffb8781b1bbb406d05d46c9d5cb1.tar.bz2
nixos-919684939475ffb8781b1bbb406d05d46c9d5cb1.tar.xz
nixos-919684939475ffb8781b1bbb406d05d46c9d5cb1.zip
networkd: fix route generation
-rw-r--r--modules/network-interfaces-systemd.nix294
1 files changed, 294 insertions, 0 deletions
diff --git a/modules/network-interfaces-systemd.nix b/modules/network-interfaces-systemd.nix
new file mode 100644
index 00000000..a3a49f5e
--- /dev/null
+++ b/modules/network-interfaces-systemd.nix
@@ -0,0 +1,294 @@
1{ config, lib, utils, pkgs, ... }:
2
3with utils;
4with lib;
5
6let
7
8 cfg = config.networking;
9 interfaces = attrValues cfg.interfaces;
10
11 interfaceIps = i:
12 i.ipv4.addresses
13 ++ optionals cfg.enableIPv6 i.ipv6.addresses;
14
15 dhcpStr = useDHCP: if useDHCP == true || useDHCP == null then "yes" else "no";
16
17 slaves =
18 concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds))
19 ++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges))
20 ++ map (sit: sit.dev) (attrValues cfg.sits)
21 ++ map (vlan: vlan.interface) (attrValues cfg.vlans)
22 # add dependency to physical or independently created vswitch member interface
23 # TODO: warn the user that any address configured on those interfaces will be useless
24 ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues cfg.vswitches);
25
26in
27
28{
29 disabledModules = [ "tasks/network-interfaces-systemd.nix" ];
30
31 config = mkIf cfg.useNetworkd {
32
33 assertions = [ {
34 assertion = cfg.defaultGatewayWindowSize == null;
35 message = "networking.defaultGatewayWindowSize is not supported by networkd.";
36 } {
37 assertion = cfg.defaultGateway == null || cfg.defaultGateway.interface == null;
38 message = "networking.defaultGateway.interface is not supported by networkd.";
39 } {
40 assertion = cfg.defaultGateway6 == null || cfg.defaultGateway6.interface == null;
41 message = "networking.defaultGateway6.interface is not supported by networkd.";
42 } {
43 assertion = cfg.useDHCP == false;
44 message = ''
45 networking.useDHCP is not supported by networkd.
46 Please use per interface configuration and set the global option to false.
47 '';
48 } ] ++ flip mapAttrsToList cfg.bridges (n: { rstp, ... }: {
49 assertion = !rstp;
50 message = "networking.bridges.${n}.rstp is not supported by networkd.";
51 });
52
53 networking.dhcpcd.enable = mkDefault false;
54
55 systemd.network =
56 let
57 domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain);
58 genericNetwork = override:
59 let gateway = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address
60 ++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address;
61 in optionalAttrs (gateway != [ ]) {
62 routes = override (map (gateway: {
63 routeConfig = {
64 Gateway = gateway;
65 GatewayOnLink = false;
66 };
67 }) gateway);
68 } // optionalAttrs (domains != [ ]) {
69 domains = override domains;
70 };
71 in mkMerge [ {
72 enable = true;
73 }
74 (mkMerge (forEach interfaces (i: {
75 netdevs = mkIf i.virtual ({
76 "40-${i.name}" = {
77 netdevConfig = {
78 Name = i.name;
79 Kind = i.virtualType;
80 };
81 "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) {
82 User = i.virtualOwner;
83 };
84 };
85 });
86 networks."40-${i.name}" = mkMerge [ (genericNetwork mkDefault) {
87 name = mkDefault i.name;
88 DHCP = mkForce (dhcpStr
89 (if i.useDHCP != null then i.useDHCP else false));
90 address = forEach (interfaceIps i)
91 (ip: "${ip.address}/${toString ip.prefixLength}");
92 networkConfig.IPv6PrivacyExtensions = "kernel";
93 linkConfig = optionalAttrs (i.macAddress != null) {
94 MACAddress = i.macAddress;
95 } // optionalAttrs (i.mtu != null) {
96 MTUBytes = toString i.mtu;
97 };
98 }];
99 })))
100 (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
101 netdevs."40-${name}" = {
102 netdevConfig = {
103 Name = name;
104 Kind = "bridge";
105 };
106 };
107 networks = listToAttrs (forEach bridge.interfaces (bi:
108 nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
109 DHCP = mkOverride 0 (dhcpStr false);
110 networkConfig.Bridge = name;
111 } ])));
112 })))
113 (mkMerge (flip mapAttrsToList cfg.bonds (name: bond: {
114 netdevs."40-${name}" = {
115 netdevConfig = {
116 Name = name;
117 Kind = "bond";
118 };
119 bondConfig = let
120 # manual mapping as of 2017-02-03
121 # man 5 systemd.netdev [BOND]
122 # to https://www.kernel.org/doc/Documentation/networking/bonding.txt
123 # driver options.
124 driverOptionMapping = let
125 trans = f: optName: { valTransform = f; optNames = [optName]; };
126 simp = trans id;
127 ms = trans (v: v + "ms");
128 in {
129 Mode = simp "mode";
130 TransmitHashPolicy = simp "xmit_hash_policy";
131 LACPTransmitRate = simp "lacp_rate";
132 MIIMonitorSec = ms "miimon";
133 UpDelaySec = ms "updelay";
134 DownDelaySec = ms "downdelay";
135 LearnPacketIntervalSec = simp "lp_interval";
136 AdSelect = simp "ad_select";
137 FailOverMACPolicy = simp "fail_over_mac";
138 ARPValidate = simp "arp_validate";
139 # apparently in ms for this value?! Upstream bug?
140 ARPIntervalSec = simp "arp_interval";
141 ARPIPTargets = simp "arp_ip_target";
142 ARPAllTargets = simp "arp_all_targets";
143 PrimaryReselectPolicy = simp "primary_reselect";
144 ResendIGMP = simp "resend_igmp";
145 PacketsPerSlave = simp "packets_per_slave";
146 GratuitousARP = { valTransform = id;
147 optNames = [ "num_grat_arp" "num_unsol_na" ]; };
148 AllSlavesActive = simp "all_slaves_active";
149 MinLinks = simp "min_links";
150 };
151
152 do = bond.driverOptions;
153 assertNoUnknownOption = let
154 knownOptions = flatten (mapAttrsToList (_: kOpts: kOpts.optNames)
155 driverOptionMapping);
156 # options that apparently don’t exist in the networkd config
157 unknownOptions = [ "primary" ];
158 assertTrace = bool: msg: if bool then true else builtins.trace msg false;
159 in assert all (driverOpt: assertTrace
160 (elem driverOpt (knownOptions ++ unknownOptions))
161 "The bond.driverOption `${driverOpt}` cannot be mapped to the list of known networkd bond options. Please add it to the mapping above the assert or to `unknownOptions` should it not exist in networkd.")
162 (mapAttrsToList (k: _: k) do); "";
163 # get those driverOptions that have been set
164 filterSystemdOptions = filterAttrs (sysDOpt: kOpts:
165 any (kOpt: do ? ${kOpt}) kOpts.optNames);
166 # build final set of systemd options to bond values
167 buildOptionSet = mapAttrs (_: kOpts: with kOpts;
168 # we simply take the first set kernel bond option
169 # (one option has multiple names, which is silly)
170 head (map (optN: valTransform (do.${optN}))
171 # only map those that exist
172 (filter (o: do ? ${o}) optNames)));
173 in seq assertNoUnknownOption
174 (buildOptionSet (filterSystemdOptions driverOptionMapping));
175
176 };
177
178 networks = listToAttrs (forEach bond.interfaces (bi:
179 nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
180 DHCP = mkOverride 0 (dhcpStr false);
181 networkConfig.Bond = name;
182 } ])));
183 })))
184 (mkMerge (flip mapAttrsToList cfg.macvlans (name: macvlan: {
185 netdevs."40-${name}" = {
186 netdevConfig = {
187 Name = name;
188 Kind = "macvlan";
189 };
190 macvlanConfig = optionalAttrs (macvlan.mode != null) { Mode = macvlan.mode; };
191 };
192 networks."40-${macvlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
193 macvlan = [ name ];
194 } ]);
195 })))
196 (mkMerge (flip mapAttrsToList cfg.sits (name: sit: {
197 netdevs."40-${name}" = {
198 netdevConfig = {
199 Name = name;
200 Kind = "sit";
201 };
202 tunnelConfig =
203 (optionalAttrs (sit.remote != null) {
204 Remote = sit.remote;
205 }) // (optionalAttrs (sit.local != null) {
206 Local = sit.local;
207 }) // (optionalAttrs (sit.ttl != null) {
208 TTL = sit.ttl;
209 });
210 };
211 networks = mkIf (sit.dev != null) {
212 "40-${sit.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
213 tunnel = [ name ];
214 } ]);
215 };
216 })))
217 (mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
218 netdevs."40-${name}" = {
219 netdevConfig = {
220 Name = name;
221 Kind = "vlan";
222 };
223 vlanConfig.Id = vlan.id;
224 };
225 networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
226 vlan = [ name ];
227 } ]);
228 })))
229 ];
230
231 # We need to prefill the slaved devices with networking options
232 # This forces the network interface creator to initialize slaves.
233 networking.interfaces = listToAttrs (map (i: nameValuePair i { }) slaves);
234
235 systemd.services = let
236 # We must escape interfaces due to the systemd interpretation
237 subsystemDevice = interface:
238 "sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
239 # support for creating openvswitch switches
240 createVswitchDevice = n: v: nameValuePair "${n}-netdev"
241 (let
242 deps = map subsystemDevice (attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces));
243 ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules;
244 in
245 { description = "Open vSwitch Interface ${n}";
246 wantedBy = [ "network.target" (subsystemDevice n) ];
247 # and create bridge before systemd-networkd starts because it might create internal interfaces
248 before = [ "systemd-networkd.service" ];
249 # shutdown the bridge when network is shutdown
250 partOf = [ "network.target" ];
251 # requires ovs-vswitchd to be alive at all times
252 bindsTo = [ "ovs-vswitchd.service" ];
253 # start switch after physical interfaces and vswitch daemon
254 after = [ "network-pre.target" "ovs-vswitchd.service" ] ++ deps;
255 wants = deps; # if one or more interface fails, the switch should continue to run
256 serviceConfig.Type = "oneshot";
257 serviceConfig.RemainAfterExit = true;
258 path = [ pkgs.iproute2 config.virtualisation.vswitch.package ];
259 preStart = ''
260 echo "Resetting Open vSwitch ${n}..."
261 ovs-vsctl --if-exists del-br ${n} -- add-br ${n} \
262 -- set bridge ${n} protocols=${concatStringsSep "," v.supportedOpenFlowVersions}
263 '';
264 script = ''
265 echo "Configuring Open vSwitch ${n}..."
266 ovs-vsctl ${concatStrings (mapAttrsToList (name: config: " -- add-port ${n} ${name}" + optionalString (config.vlan != null) " tag=${toString config.vlan}") v.interfaces)} \
267 ${concatStrings (mapAttrsToList (name: config: optionalString (config.type != null) " -- set interface ${name} type=${config.type}") v.interfaces)} \
268 ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \
269 ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)}
270
271
272 echo "Adding OpenFlow rules for Open vSwitch ${n}..."
273 ovs-ofctl --protocols=${v.openFlowVersion} add-flows ${n} ${ofRules}
274 '';
275 postStop = ''
276 echo "Cleaning Open vSwitch ${n}"
277 echo "Shuting down internal ${n} interface"
278 ip link set ${n} down || true
279 echo "Deleting flows for ${n}"
280 ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
281 echo "Deleting Open vSwitch ${n}"
282 ovs-vsctl --if-exists del-br ${n} || true
283 '';
284 });
285 in mapAttrs' createVswitchDevice cfg.vswitches
286 // {
287 "network-local-commands" = {
288 after = [ "systemd-networkd.service" ];
289 bindsTo = [ "systemd-networkd.service" ];
290 };
291 };
292 };
293
294}