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