1 { config, lib, utils, pkgs, ... }:
8 cfg = config.networking;
9 interfaces = attrValues cfg.interfaces;
13 ++ optionals cfg.enableIPv6 i.ipv6.addresses;
17 ++ optionals cfg.enableIPv6 i.ipv6.routes;
19 dhcpStr = useDHCP: if useDHCP == true || useDHCP == null then "yes" else "no";
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;
37 Gateway = gateway.address;
38 } // optionalAttrs (gateway.metric != null) {
39 Metric = gateway.metric;
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*"];
63 networkConfig.IPv6PrivacyExtensions = "kernel";
65 networks."99-wireless-client-dhcp" = {
66 # Like above, but this is much more likely to be correct.
67 matchConfig.WLANInterfaceType = "station";
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
73 dhcpV4Config.RouteMetric = 1025;
74 ipv6AcceptRAConfig.RouteMetric = 1025;
79 interfaceNetworks = mkMerge (forEach interfaces (i: {
80 netdevs = mkIf i.virtual ({
86 "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) {
87 User = i.virtualOwner;
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)
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}";
104 (mkIf (route.options ? fastopen_no_cookie) {
105 FastOpenNoCookie = route.options.fastopen_no_cookie;
107 (mkIf (route.via != null) {
110 (mkIf (route.type != null) {
113 (mkIf (route.options ? onlink) {
114 GatewayOnLink = true;
116 (mkIf (route.options ? initrwnd) {
117 InitialAdvertisedReceiveWindow = route.options.initrwnd;
119 (mkIf (route.options ? initcwnd) {
120 InitialCongestionWindow = route.options.initcwnd;
122 (mkIf (route.options ? pref) {
123 IPv6Preference = route.options.pref;
125 (mkIf (route.options ? mtu) {
126 MTUBytes = route.options.mtu;
128 (mkIf (route.options ? metric) {
129 Metric = route.options.metric;
131 (mkIf (route.options ? src) {
132 PreferredSource = route.options.src;
134 (mkIf (route.options ? protocol) {
135 Protocol = route.options.protocol;
137 (mkIf (route.options ? quickack) {
138 QuickAck = route.options.quickack;
140 (mkIf (route.options ? scope) {
141 Scope = route.options.scope;
143 (mkIf (route.options ? from) {
144 Source = route.options.from;
146 (mkIf (route.options ? table) {
147 Table = route.options.table;
149 (mkIf (route.options ? advmss) {
150 TCPAdvertisedMaximumSegmentSize = route.options.advmss;
152 (mkIf (route.options ? ttl-propagate) {
153 TTLPropagate = route.options.ttl-propagate == "enabled";
156 networkConfig.IPv6PrivacyExtensions = "kernel";
157 linkConfig = optionalAttrs (i.macAddress != null) {
158 MACAddress = i.macAddress;
159 } // optionalAttrs (i.mtu != null) {
160 MTUBytes = toString i.mtu;
165 bridgeNetworks = mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
166 netdevs."40-${name}" = {
172 networks = listToAttrs (forEach bridge.interfaces (bi:
173 nameValuePair "40-${bi}" {
174 DHCP = mkOverride 0 (dhcpStr false);
175 networkConfig.Bridge = name;
179 vlanNetworks = mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
180 netdevs."40-${name}" = {
185 vlanConfig.Id = vlan.id;
187 networks."40-${vlan.interface}" = {
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 [
203 (genericDhcpNetworks true)
208 boot.initrd.availableKernelModules =
209 optional (cfg.bridges != {}) "bridge" ++
210 optional (cfg.vlans != {}) "8021q";
213 (mkIf cfg.useNetworkd {
216 assertion = cfg.defaultGatewayWindowSize == null;
217 message = "networking.defaultGatewayWindowSize is not supported by networkd.";
219 assertion = cfg.defaultGateway != null -> cfg.defaultGateway.interface != null;
220 message = "networking.defaultGateway.interface is not optional when using networkd.";
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, ... }: {
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.";
232 networking.dhcpcd.enable = mkDefault false;
239 (genericDhcpNetworks false)
242 (mkMerge (flip mapAttrsToList cfg.bonds (name: bond: {
243 netdevs."40-${name}" = {
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
253 driverOptionMapping = let
254 trans = f: optName: { valTransform = f; optNames = [optName]; };
256 ms = trans (v: v + "ms");
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";
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));
307 networks = listToAttrs (forEach bond.interfaces (bi:
308 nameValuePair "40-${bi}" {
309 DHCP = mkOverride 0 (dhcpStr false);
310 networkConfig.Bond = name;
313 (mkMerge (flip mapAttrsToList cfg.macvlans (name: macvlan: {
314 netdevs."40-${name}" = {
319 macvlanConfig = optionalAttrs (macvlan.mode != null) { Mode = macvlan.mode; };
321 networks."40-${macvlan.interface}" = {
325 (mkMerge (flip mapAttrsToList cfg.fooOverUDP (name: fou: {
326 netdevs."40-${name}" = {
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
336 Encapsulation = if fou.protocol != null then "FooOverUDP" else "GenericUDPEncapsulation";
337 } // (optionalAttrs (fou.protocol != null) {
338 Protocol = fou.protocol;
342 (mkMerge (flip mapAttrsToList cfg.sits (name: sit: {
343 netdevs."40-${name}" = {
349 (optionalAttrs (sit.remote != null) {
351 }) // (optionalAttrs (sit.local != null) {
353 }) // (optionalAttrs (sit.ttl != null) {
355 }) // (optionalAttrs (sit.encapsulation != null) (
359 if sit.encapsulation.type == "fou"
361 else "GenericUDPEncapsulation";
362 FOUDestinationPort = sit.encapsulation.port;
363 } // (optionalAttrs (sit.encapsulation.sourcePort != null) {
364 FOUSourcePort = sit.encapsulation.sourcePort;
367 networks = mkIf (sit.dev != null) {
373 (mkMerge (flip mapAttrsToList cfg.greTunnels (name: gre: {
374 netdevs."40-${name}" = {
380 (optionalAttrs (gre.remote != null) {
382 }) // (optionalAttrs (gre.local != null) {
384 }) // (optionalAttrs (gre.ttl != null) {
388 networks = mkIf (gre.dev != null) {
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"
408 deps = map subsystemDevice (attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces));
409 ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules;
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 ];
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}
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}
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
451 in mapAttrs' createVswitchDevice cfg.vswitches
453 "network-local-commands" = {
454 after = [ "systemd-networkd.service" ];
455 bindsTo = [ "systemd-networkd.service" ];