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