1{
2 config,
3 lib,
4 utils,
5 pkgs,
6 ...
7}:
8
9with utils;
10with lib;
11
12let
13
14 cfg = config.networking;
15 interfaces = attrValues cfg.interfaces;
16
17 interfaceIps = i: i.ipv4.addresses ++ optionals cfg.enableIPv6 i.ipv6.addresses;
18
19 interfaceRoutes = i: i.ipv4.routes ++ optionals cfg.enableIPv6 i.ipv6.routes;
20
21 dhcpStr = useDHCP: if useDHCP == true || useDHCP == null then "yes" else "no";
22
23 slaves =
24 concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds))
25 ++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges))
26 ++ map (sit: sit.dev) (attrValues cfg.sits)
27 ++ map (gre: gre.dev) (attrValues cfg.greTunnels)
28 ++ map (vlan: vlan.interface) (attrValues cfg.vlans)
29 # add dependency to physical or independently created vswitch member interface
30 # TODO: warn the user that any address configured on those interfaces will be useless
31 ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (
32 attrValues cfg.vswitches
33 );
34
35 defaultGateways = mkMerge (
36 forEach [ cfg.defaultGateway cfg.defaultGateway6 ] (
37 gateway:
38 optionalAttrs (gateway != null && gateway.interface != null) {
39 networks."40-${gateway.interface}" = {
40 matchConfig.Name = gateway.interface;
41 routes = [
42 (
43 {
44 Gateway = gateway.address;
45 }
46 // optionalAttrs (gateway.metric != null) {
47 Metric = gateway.metric;
48 }
49 )
50 ];
51 };
52 }
53 )
54 );
55
56 genericDhcpNetworks = mkIf cfg.useDHCP {
57 networks."99-ethernet-default-dhcp" = {
58 matchConfig = {
59 Type = "ether";
60 Kind = "!*"; # physical interfaces have no kind
61 };
62 DHCP = "yes";
63 networkConfig.IPv6PrivacyExtensions = "kernel";
64 };
65 networks."99-wireless-client-dhcp" = {
66 matchConfig.WLANInterfaceType = "station";
67 DHCP = "yes";
68 networkConfig.IPv6PrivacyExtensions = "kernel";
69 # We also set the route metric to one more than the default
70 # of 1024, so that Ethernet is preferred if both are
71 # available.
72 dhcpV4Config.RouteMetric = 1025;
73 ipv6AcceptRAConfig.RouteMetric = 1025;
74 };
75 };
76
77 interfaceNetworks = mkMerge (
78 forEach interfaces (i: {
79 netdevs = mkIf i.virtual ({
80 "40-${i.name}" = {
81 netdevConfig = {
82 Name = i.name;
83 Kind = i.virtualType;
84 };
85 "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) {
86 User = i.virtualOwner;
87 };
88 };
89 });
90 networks."40-${i.name}" = {
91 name = mkDefault i.name;
92 DHCP = mkForce (
93 dhcpStr (
94 if i.useDHCP != null then i.useDHCP else (config.networking.useDHCP && i.ipv4.addresses == [ ])
95 )
96 );
97 address = forEach (interfaceIps i) (ip: "${ip.address}/${toString ip.prefixLength}");
98 routes = forEach (interfaceRoutes i) (
99 route:
100 mkMerge [
101 # Most of these route options have not been tested.
102 # Please fix or report any mistakes you may find.
103 (mkIf (route.address != null && route.prefixLength != null) {
104 Destination = "${route.address}/${toString route.prefixLength}";
105 })
106 (mkIf (route.options ? fastopen_no_cookie) {
107 FastOpenNoCookie = route.options.fastopen_no_cookie;
108 })
109 (mkIf (route.via != null) {
110 Gateway = route.via;
111 })
112 (mkIf (route.type != null) {
113 Type = route.type;
114 })
115 (mkIf (route.options ? onlink) {
116 GatewayOnLink = true;
117 })
118 (mkIf (route.options ? initrwnd) {
119 InitialAdvertisedReceiveWindow = route.options.initrwnd;
120 })
121 (mkIf (route.options ? initcwnd) {
122 InitialCongestionWindow = route.options.initcwnd;
123 })
124 (mkIf (route.options ? pref) {
125 IPv6Preference = route.options.pref;
126 })
127 (mkIf (route.options ? mtu) {
128 MTUBytes = route.options.mtu;
129 })
130 (mkIf (route.options ? metric) {
131 Metric = route.options.metric;
132 })
133 (mkIf (route.options ? src) {
134 PreferredSource = route.options.src;
135 })
136 (mkIf (route.options ? protocol) {
137 Protocol = route.options.protocol;
138 })
139 (mkIf (route.options ? quickack) {
140 QuickAck = route.options.quickack;
141 })
142 (mkIf (route.options ? scope) {
143 Scope = route.options.scope;
144 })
145 (mkIf (route.options ? from) {
146 Source = route.options.from;
147 })
148 (mkIf (route.options ? table) {
149 Table = route.options.table;
150 })
151 (mkIf (route.options ? advmss) {
152 TCPAdvertisedMaximumSegmentSize = route.options.advmss;
153 })
154 (mkIf (route.options ? ttl-propagate) {
155 TTLPropagate = route.options.ttl-propagate == "enabled";
156 })
157 ]
158 );
159 networkConfig.IPv6PrivacyExtensions = "kernel";
160 linkConfig =
161 optionalAttrs (i.macAddress != null) {
162 MACAddress = i.macAddress;
163 }
164 // optionalAttrs (i.mtu != null) {
165 MTUBytes = toString i.mtu;
166 };
167 };
168 })
169 );
170
171 bridgeNetworks = mkMerge (
172 flip mapAttrsToList cfg.bridges (
173 name: bridge: {
174 netdevs."40-${name}" = {
175 netdevConfig = {
176 Name = name;
177 Kind = "bridge";
178 };
179 };
180 networks = listToAttrs (
181 forEach bridge.interfaces (
182 bi:
183 nameValuePair "40-${bi}" {
184 DHCP = mkOverride 0 (dhcpStr false);
185 networkConfig.Bridge = name;
186 }
187 )
188 );
189 }
190 )
191 );
192
193 vlanNetworks = mkMerge (
194 flip mapAttrsToList cfg.vlans (
195 name: vlan: {
196 netdevs."40-${name}" = {
197 netdevConfig = {
198 Name = name;
199 Kind = "vlan";
200 };
201 vlanConfig.Id = vlan.id;
202 };
203 networks."40-${vlan.interface}" = {
204 vlan = [ name ];
205 };
206 }
207 )
208 );
209
210in
211
212{
213 config = mkMerge [
214
215 (mkIf config.boot.initrd.network.enable {
216 # Note this is if initrd.network.enable, not if
217 # initrd.systemd.network.enable. By setting the latter and not the
218 # former, the user retains full control over the configuration.
219 boot.initrd.systemd.network = mkMerge [
220 defaultGateways
221 genericDhcpNetworks
222 interfaceNetworks
223 bridgeNetworks
224 vlanNetworks
225 ];
226 boot.initrd.availableKernelModules =
227 optional (cfg.bridges != { }) "bridge"
228 ++ optional (cfg.vlans != { }) "8021q";
229 })
230
231 (mkIf cfg.useNetworkd {
232
233 assertions =
234 [
235 {
236 assertion = cfg.defaultGatewayWindowSize == null;
237 message = "networking.defaultGatewayWindowSize is not supported by networkd.";
238 }
239 {
240 assertion = cfg.defaultGateway != null -> cfg.defaultGateway.interface != null;
241 message = "networking.defaultGateway.interface is not optional when using networkd.";
242 }
243 {
244 assertion = cfg.defaultGateway6 != null -> cfg.defaultGateway6.interface != null;
245 message = "networking.defaultGateway6.interface is not optional when using networkd.";
246 }
247 ]
248 ++ flip mapAttrsToList cfg.bridges (
249 n:
250 { rstp, ... }:
251 {
252 assertion = !rstp;
253 message = "networking.bridges.${n}.rstp is not supported by networkd.";
254 }
255 )
256 ++ flip mapAttrsToList cfg.fooOverUDP (
257 n:
258 { local, ... }:
259 {
260 assertion = local == null;
261 message = "networking.fooOverUDP.${n}.local is not supported by networkd.";
262 }
263 );
264
265 networking.dhcpcd.enable = mkDefault false;
266
267 systemd.network = mkMerge [
268 {
269 enable = true;
270 }
271 defaultGateways
272 genericDhcpNetworks
273 interfaceNetworks
274 bridgeNetworks
275 (mkMerge (
276 flip mapAttrsToList cfg.bonds (
277 name: bond: {
278 netdevs."40-${name}" = {
279 netdevConfig = {
280 Name = name;
281 Kind = "bond";
282 };
283 bondConfig =
284 let
285 # manual mapping as of 2017-02-03
286 # man 5 systemd.netdev [BOND]
287 # to https://www.kernel.org/doc/Documentation/networking/bonding.txt
288 # driver options.
289 driverOptionMapping =
290 let
291 trans = f: optName: {
292 valTransform = f;
293 optNames = [ optName ];
294 };
295 simp = trans id;
296 ms = trans (v: v + "ms");
297 in
298 {
299 Mode = simp "mode";
300 TransmitHashPolicy = simp "xmit_hash_policy";
301 LACPTransmitRate = simp "lacp_rate";
302 MIIMonitorSec = ms "miimon";
303 UpDelaySec = ms "updelay";
304 DownDelaySec = ms "downdelay";
305 LearnPacketIntervalSec = simp "lp_interval";
306 AdSelect = simp "ad_select";
307 FailOverMACPolicy = simp "fail_over_mac";
308 ARPValidate = simp "arp_validate";
309 # apparently in ms for this value?! Upstream bug?
310 ARPIntervalSec = simp "arp_interval";
311 ARPIPTargets = simp "arp_ip_target";
312 ARPAllTargets = simp "arp_all_targets";
313 PrimaryReselectPolicy = simp "primary_reselect";
314 ResendIGMP = simp "resend_igmp";
315 PacketsPerSlave = simp "packets_per_slave";
316 GratuitousARP = {
317 valTransform = id;
318 optNames = [
319 "num_grat_arp"
320 "num_unsol_na"
321 ];
322 };
323 AllSlavesActive = simp "all_slaves_active";
324 MinLinks = simp "min_links";
325 };
326
327 do = bond.driverOptions;
328 assertNoUnknownOption =
329 let
330 knownOptions = flatten (mapAttrsToList (_: kOpts: kOpts.optNames) driverOptionMapping);
331 # options that apparently don’t exist in the networkd config
332 unknownOptions = [ "primary" ];
333 assertTrace = bool: msg: if bool then true else builtins.trace msg false;
334 in
335 assert all (
336 driverOpt:
337 assertTrace (elem driverOpt (knownOptions ++ unknownOptions))
338 "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."
339 ) (mapAttrsToList (k: _: k) do);
340 "";
341 # get those driverOptions that have been set
342 filterSystemdOptions = filterAttrs (sysDOpt: kOpts: any (kOpt: do ? ${kOpt}) kOpts.optNames);
343 # build final set of systemd options to bond values
344 buildOptionSet = mapAttrs (
345 _: kOpts:
346 with kOpts;
347 # we simply take the first set kernel bond option
348 # (one option has multiple names, which is silly)
349 head (
350 map (optN: valTransform (do.${optN}))
351 # only map those that exist
352 (filter (o: do ? ${o}) optNames)
353 )
354 );
355 in
356 seq assertNoUnknownOption (buildOptionSet (filterSystemdOptions driverOptionMapping));
357
358 };
359
360 networks = listToAttrs (
361 forEach bond.interfaces (
362 bi:
363 nameValuePair "40-${bi}" {
364 DHCP = mkOverride 0 (dhcpStr false);
365 networkConfig.Bond = name;
366 }
367 )
368 );
369 }
370 )
371 ))
372 (mkMerge (
373 flip mapAttrsToList cfg.macvlans (
374 name: macvlan: {
375 netdevs."40-${name}" = {
376 netdevConfig = {
377 Name = name;
378 Kind = "macvlan";
379 };
380 macvlanConfig = optionalAttrs (macvlan.mode != null) { Mode = macvlan.mode; };
381 };
382 networks."40-${macvlan.interface}" = {
383 macvlan = [ name ];
384 };
385 }
386 )
387 ))
388 (mkMerge (
389 flip mapAttrsToList cfg.fooOverUDP (
390 name: fou: {
391 netdevs."40-${name}" = {
392 netdevConfig = {
393 Name = name;
394 Kind = "fou";
395 };
396 # unfortunately networkd cannot encode dependencies of netdevs on addresses/routes,
397 # so we cannot specify Local=, Peer=, PeerPort=. this looks like a missing feature
398 # in networkd.
399 fooOverUDPConfig =
400 {
401 Port = fou.port;
402 Encapsulation = if fou.protocol != null then "FooOverUDP" else "GenericUDPEncapsulation";
403 }
404 // (optionalAttrs (fou.protocol != null) {
405 Protocol = fou.protocol;
406 });
407 };
408 }
409 )
410 ))
411 (mkMerge (
412 flip mapAttrsToList cfg.sits (
413 name: sit: {
414 netdevs."40-${name}" = {
415 netdevConfig = {
416 Name = name;
417 Kind = "sit";
418 };
419 tunnelConfig =
420 (optionalAttrs (sit.remote != null) {
421 Remote = sit.remote;
422 })
423 // (optionalAttrs (sit.local != null) {
424 Local = sit.local;
425 })
426 // (optionalAttrs (sit.ttl != null) {
427 TTL = sit.ttl;
428 })
429 // (optionalAttrs (sit.encapsulation != null) (
430 {
431 FooOverUDP = true;
432 Encapsulation = if sit.encapsulation.type == "fou" then "FooOverUDP" else "GenericUDPEncapsulation";
433 FOUDestinationPort = sit.encapsulation.port;
434 }
435 // (optionalAttrs (sit.encapsulation.sourcePort != null) {
436 FOUSourcePort = sit.encapsulation.sourcePort;
437 })
438 ));
439 };
440 networks = mkIf (sit.dev != null) {
441 "40-${sit.dev}" = {
442 tunnel = [ name ];
443 };
444 };
445 }
446 )
447 ))
448 (mkMerge (
449 flip mapAttrsToList cfg.greTunnels (
450 name: gre: {
451 netdevs."40-${name}" = {
452 netdevConfig = {
453 Name = name;
454 Kind = gre.type;
455 };
456 tunnelConfig =
457 (optionalAttrs (gre.remote != null) {
458 Remote = gre.remote;
459 })
460 // (optionalAttrs (gre.local != null) {
461 Local = gre.local;
462 })
463 // (optionalAttrs (gre.ttl != null) {
464 TTL = gre.ttl;
465 });
466 };
467 networks = mkIf (gre.dev != null) {
468 "40-${gre.dev}" = {
469 tunnel = [ name ];
470 };
471 };
472 }
473 )
474 ))
475 vlanNetworks
476 ];
477
478 # We need to prefill the slaved devices with networking options
479 # This forces the network interface creator to initialize slaves.
480 networking.interfaces = listToAttrs (map (i: nameValuePair i { }) slaves);
481
482 systemd.services =
483 let
484 # We must escape interfaces due to the systemd interpretation
485 subsystemDevice = interface: "sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
486 # support for creating openvswitch switches
487 createVswitchDevice =
488 n: v:
489 nameValuePair "${n}-netdev" (
490 let
491 deps = map subsystemDevice (
492 attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces)
493 );
494 ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules;
495 in
496 {
497 description = "Open vSwitch Interface ${n}";
498 wantedBy = [
499 "network.target"
500 (subsystemDevice n)
501 ];
502 # and create bridge before systemd-networkd starts because it might create internal interfaces
503 before = [ "systemd-networkd.service" ];
504 # shutdown the bridge when network is shutdown
505 partOf = [ "network.target" ];
506 # requires ovs-vswitchd to be alive at all times
507 bindsTo = [ "ovs-vswitchd.service" ];
508 # start switch after physical interfaces and vswitch daemon
509 after = [
510 "network-pre.target"
511 "ovs-vswitchd.service"
512 ] ++ deps;
513 wants = deps; # if one or more interface fails, the switch should continue to run
514 serviceConfig.Type = "oneshot";
515 serviceConfig.RemainAfterExit = true;
516 path = [
517 pkgs.iproute2
518 config.virtualisation.vswitch.package
519 ];
520 preStart = ''
521 echo "Resetting Open vSwitch ${n}..."
522 ovs-vsctl --if-exists del-br ${n} -- add-br ${n} \
523 -- set bridge ${n} protocols=${concatStringsSep "," v.supportedOpenFlowVersions}
524 '';
525 script = ''
526 echo "Configuring Open vSwitch ${n}..."
527 ovs-vsctl ${
528 concatStrings (
529 mapAttrsToList (
530 name: config:
531 " -- add-port ${n} ${name}" + optionalString (config.vlan != null) " tag=${toString config.vlan}"
532 ) v.interfaces
533 )
534 } \
535 ${
536 concatStrings (
537 mapAttrsToList (
538 name: config: optionalString (config.type != null) " -- set interface ${name} type=${config.type}"
539 ) v.interfaces
540 )
541 } \
542 ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \
543 ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)}
544
545
546 echo "Adding OpenFlow rules for Open vSwitch ${n}..."
547 ovs-ofctl --protocols=${v.openFlowVersion} add-flows ${n} ${ofRules}
548 '';
549 postStop = ''
550 echo "Cleaning Open vSwitch ${n}"
551 echo "Shutting down internal ${n} interface"
552 ip link set dev ${n} down || true
553 echo "Deleting flows for ${n}"
554 ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
555 echo "Deleting Open vSwitch ${n}"
556 ovs-vsctl --if-exists del-br ${n} || true
557 '';
558 }
559 );
560 in
561 mapAttrs' createVswitchDevice cfg.vswitches
562 // {
563 "network-local-commands" = {
564 after = [ "systemd-networkd.service" ];
565 bindsTo = [ "systemd-networkd.service" ];
566 };
567 };
568 })
569
570 ];
571}