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