1{
2 config,
3 lib,
4 pkgs,
5 utils,
6 ...
7}:
8
9with utils;
10with lib;
11
12let
13
14 cfg = config.networking;
15 interfaces = attrValues cfg.interfaces;
16
17 slaves =
18 concatMap (i: i.interfaces) (attrValues cfg.bonds)
19 ++ concatMap (i: i.interfaces) (attrValues cfg.bridges)
20 ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (
21 attrValues cfg.vswitches
22 )
23 ++ concatMap (i: [ i.interface ]) (attrValues cfg.macvlans)
24 ++ concatMap (i: [ i.interface ]) (attrValues cfg.vlans);
25
26 # We must escape interfaces due to the systemd interpretation
27 subsystemDevice = interface: "sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
28
29 interfaceIps = i: i.ipv4.addresses ++ optionals cfg.enableIPv6 i.ipv6.addresses;
30
31 destroyBond = i: ''
32 while true; do
33 UPDATED=1
34 SLAVES=$(ip link | grep 'master ${i}' | awk -F: '{print $2}')
35 for I in $SLAVES; do
36 UPDATED=0
37 ip link set dev "$I" nomaster
38 done
39 [ "$UPDATED" -eq "1" ] && break
40 done
41 ip link set dev "${i}" down 2>/dev/null || true
42 ip link del dev "${i}" 2>/dev/null || true
43 '';
44
45 # warn that these attributes are deprecated (2017-2-2)
46 # Should be removed in the release after next
47 bondDeprecation = rec {
48 deprecated = [
49 "lacp_rate"
50 "miimon"
51 "mode"
52 "xmit_hash_policy"
53 ];
54 filterDeprecated =
55 bond: (filterAttrs (attrName: attr: elem attrName deprecated && attr != null) bond);
56 };
57
58 bondWarnings =
59 let
60 oneBondWarnings =
61 bondName: bond: mapAttrsToList (bondText bondName) (bondDeprecation.filterDeprecated bond);
62 bondText =
63 bondName: optName: _:
64 "${bondName}.${optName} is deprecated, use ${bondName}.driverOptions";
65 in
66 {
67 warnings = flatten (mapAttrsToList oneBondWarnings cfg.bonds);
68 };
69
70 normalConfig = {
71 systemd.network.links =
72 let
73 createNetworkLink =
74 i:
75 nameValuePair "40-${i.name}" {
76 matchConfig.OriginalName = i.name;
77 linkConfig =
78 optionalAttrs (i.macAddress != null) {
79 MACAddress = i.macAddress;
80 }
81 // optionalAttrs (i.mtu != null) {
82 MTUBytes = toString i.mtu;
83 };
84 };
85 in
86 listToAttrs (map createNetworkLink interfaces);
87 systemd.services =
88 let
89
90 deviceDependency =
91 dev:
92 # Use systemd service if we manage device creation, else
93 # trust udev when not in a container
94 if (dev == null || dev == "lo") then
95 [ ]
96 else if
97 (hasAttr dev (filterAttrs (k: v: v.virtual) cfg.interfaces))
98 || (hasAttr dev cfg.bridges)
99 || (hasAttr dev cfg.bonds)
100 || (hasAttr dev cfg.macvlans)
101 || (hasAttr dev cfg.sits)
102 || (hasAttr dev cfg.vlans)
103 || (hasAttr dev cfg.greTunnels)
104 || (hasAttr dev cfg.vswitches)
105 then
106 [ "${dev}-netdev.service" ]
107 else
108 optional (!config.boot.isContainer) (subsystemDevice dev);
109
110 hasDefaultGatewaySet =
111 (cfg.defaultGateway != null && cfg.defaultGateway.address != "")
112 || (cfg.enableIPv6 && cfg.defaultGateway6 != null && cfg.defaultGateway6.address != "");
113
114 needNetworkSetup =
115 cfg.resolvconf.enable || cfg.defaultGateway != null || cfg.defaultGateway6 != null;
116
117 networkLocalCommands = lib.mkIf needNetworkSetup {
118 after = [ "network-setup.service" ];
119 bindsTo = [ "network-setup.service" ];
120 };
121
122 networkSetup = lib.mkIf needNetworkSetup {
123 description = "Networking Setup";
124
125 after = [ "network-pre.target" ];
126 before = [
127 "network.target"
128 "shutdown.target"
129 ];
130 wants = [ "network.target" ];
131 # exclude bridges from the partOf relationship to fix container networking bug #47210
132 partOf = map (i: "network-addresses-${i.name}.service") (
133 filter (i: !(hasAttr i.name cfg.bridges)) interfaces
134 );
135 conflicts = [ "shutdown.target" ];
136 wantedBy = [ "multi-user.target" ] ++ optional hasDefaultGatewaySet "network-online.target";
137
138 unitConfig.ConditionCapability = "CAP_NET_ADMIN";
139
140 path = [ pkgs.iproute2 ];
141
142 serviceConfig = {
143 Type = "oneshot";
144 RemainAfterExit = true;
145 };
146
147 unitConfig.DefaultDependencies = false;
148
149 script = ''
150 ${optionalString config.networking.resolvconf.enable ''
151 # Set the static DNS configuration, if given.
152 ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <<EOF
153 ${optionalString (cfg.nameservers != [ ] && cfg.domain != null) ''
154 domain ${cfg.domain}
155 ''}
156 ${optionalString (cfg.search != [ ]) ("search " + concatStringsSep " " cfg.search)}
157 ${flip concatMapStrings cfg.nameservers (ns: ''
158 nameserver ${ns}
159 '')}
160 EOF
161 ''}
162
163 # Set the default gateway.
164 ${optionalString (cfg.defaultGateway != null && cfg.defaultGateway.address != "") ''
165 ${optionalString (cfg.defaultGateway.interface != null) ''
166 ip route replace ${cfg.defaultGateway.address} dev ${cfg.defaultGateway.interface} ${
167 optionalString (cfg.defaultGateway.metric != null) "metric ${toString cfg.defaultGateway.metric}"
168 } proto static
169 ''}
170 ip route replace default ${
171 optionalString (cfg.defaultGateway.metric != null) "metric ${toString cfg.defaultGateway.metric}"
172 } via "${cfg.defaultGateway.address}" ${
173 optionalString (
174 cfg.defaultGatewayWindowSize != null
175 ) "window ${toString cfg.defaultGatewayWindowSize}"
176 } ${
177 optionalString (cfg.defaultGateway.interface != null) "dev ${cfg.defaultGateway.interface}"
178 } proto static
179 ''}
180 ${optionalString (cfg.defaultGateway6 != null && cfg.defaultGateway6.address != "") ''
181 ${optionalString (cfg.defaultGateway6.interface != null) ''
182 ip -6 route replace ${cfg.defaultGateway6.address} dev ${cfg.defaultGateway6.interface} ${
183 optionalString (cfg.defaultGateway6.metric != null) "metric ${toString cfg.defaultGateway6.metric}"
184 } proto static
185 ''}
186 ip -6 route replace default ${
187 optionalString (cfg.defaultGateway6.metric != null) "metric ${toString cfg.defaultGateway6.metric}"
188 } via "${cfg.defaultGateway6.address}" ${
189 optionalString (
190 cfg.defaultGatewayWindowSize != null
191 ) "window ${toString cfg.defaultGatewayWindowSize}"
192 } ${
193 optionalString (cfg.defaultGateway6.interface != null) "dev ${cfg.defaultGateway6.interface}"
194 } proto static
195 ''}
196 '';
197 };
198
199 # For each interface <foo>, create a job ‘network-addresses-<foo>.service"
200 # that performs static address configuration. It has a "wants"
201 # dependency on ‘<foo>.service’, which is supposed to create
202 # the interface and need not exist (i.e. for hardware
203 # interfaces). It has a binds-to dependency on the actual
204 # network device, so it only gets started after the interface
205 # has appeared, and it's stopped when the interface
206 # disappears.
207 configureAddrs =
208 i:
209 let
210 ips = interfaceIps i;
211 in
212 nameValuePair "network-addresses-${i.name}" {
213 description = "Address configuration of ${i.name}";
214 wantedBy = [
215 "network-setup.service"
216 "network.target"
217 ];
218 # order before network-setup because the routes that are configured
219 # there may need ip addresses configured
220 before = [ "network-setup.service" ];
221 bindsTo = deviceDependency i.name;
222 after = [ "network-pre.target" ] ++ (deviceDependency i.name);
223 serviceConfig.Type = "oneshot";
224 serviceConfig.RemainAfterExit = true;
225 # Restart rather than stop+start this unit to prevent the
226 # network from dying during switch-to-configuration.
227 stopIfChanged = false;
228 path = [ pkgs.iproute2 ];
229 script = ''
230 state="/run/nixos/network/addresses/${i.name}"
231 mkdir -p "$(dirname "$state")"
232
233 ip link set dev "${i.name}" up
234
235 ${flip concatMapStrings ips (
236 ip:
237 let
238 cidr = "${ip.address}/${toString ip.prefixLength}";
239 in
240 ''
241 echo "${cidr}" >> $state
242 echo -n "adding address ${cidr}... "
243 if out=$(ip addr replace "${cidr}" dev "${i.name}" 2>&1); then
244 echo "done"
245 else
246 echo "'ip addr replace \"${cidr}\" dev \"${i.name}\"' failed: $out"
247 exit 1
248 fi
249 ''
250 )}
251
252 state="/run/nixos/network/routes/${i.name}"
253 mkdir -p "$(dirname "$state")"
254
255 ${flip concatMapStrings (i.ipv4.routes ++ i.ipv6.routes) (
256 route:
257 let
258 cidr = "${route.address}/${toString route.prefixLength}";
259 via = optionalString (route.via != null) ''via "${route.via}"'';
260 options = concatStrings (mapAttrsToList (name: val: "${name} ${val} ") route.options);
261 type = toString route.type;
262 in
263 ''
264 echo "${cidr}" >> $state
265 echo -n "adding route ${cidr}... "
266 if out=$(ip route add ${type} "${cidr}" ${options} ${via} dev "${i.name}" proto static 2>&1); then
267 echo "done"
268 elif ! echo "$out" | grep "File exists" >/dev/null 2>&1; then
269 echo "'ip route add ${type} \"${cidr}\" ${options} ${via} dev \"${i.name}\"' failed: $out"
270 exit 1
271 fi
272 ''
273 )}
274 '';
275 preStop = ''
276 state="/run/nixos/network/routes/${i.name}"
277 if [ -e "$state" ]; then
278 while read -r cidr; do
279 echo -n "deleting route $cidr... "
280 ip route del "$cidr" dev "${i.name}" >/dev/null 2>&1 && echo "done" || echo "failed"
281 done < "$state"
282 rm -f "$state"
283 fi
284
285 state="/run/nixos/network/addresses/${i.name}"
286 if [ -e "$state" ]; then
287 while read -r cidr; do
288 echo -n "deleting address $cidr... "
289 ip addr del "$cidr" dev "${i.name}" >/dev/null 2>&1 && echo "done" || echo "failed"
290 done < "$state"
291 rm -f "$state"
292 fi
293 '';
294 };
295
296 createTunDevice =
297 i:
298 nameValuePair "${i.name}-netdev" {
299 description = "Virtual Network Interface ${i.name}";
300 bindsTo = optional (!config.boot.isContainer) "dev-net-tun.device";
301 after = optional (!config.boot.isContainer) "dev-net-tun.device" ++ [ "network-pre.target" ];
302 wantedBy = [
303 "network-setup.service"
304 (subsystemDevice i.name)
305 ];
306 before = [ "network-setup.service" ];
307 path = [ pkgs.iproute2 ];
308 serviceConfig = {
309 Type = "oneshot";
310 RemainAfterExit = true;
311 };
312 script = ''
313 ip tuntap add dev "${i.name}" mode "${i.virtualType}" user "${i.virtualOwner}"
314 '';
315 postStop = ''
316 ip link del dev ${i.name} || true
317 '';
318 };
319
320 createBridgeDevice =
321 n: v:
322 nameValuePair "${n}-netdev" (
323 let
324 deps = concatLists (map deviceDependency v.interfaces);
325 in
326 {
327 description = "Bridge Interface ${n}";
328 wantedBy = [
329 "network-setup.service"
330 (subsystemDevice n)
331 ];
332 bindsTo = deps ++ optional v.rstp "mstpd.service";
333 partOf = [ "network-setup.service" ] ++ optional v.rstp "mstpd.service";
334 after =
335 [ "network-pre.target" ]
336 ++ deps
337 ++ optional v.rstp "mstpd.service"
338 ++ map (i: "network-addresses-${i}.service") v.interfaces;
339 before = [ "network-setup.service" ];
340 serviceConfig.Type = "oneshot";
341 serviceConfig.RemainAfterExit = true;
342 path = [ pkgs.iproute2 ];
343 script = ''
344 # Remove Dead Interfaces
345 echo "Removing old bridge ${n}..."
346 ip link show dev "${n}" >/dev/null 2>&1 && ip link del dev "${n}"
347
348 echo "Adding bridge ${n}..."
349 ip link add name "${n}" type bridge
350
351 # Enslave child interfaces
352 ${flip concatMapStrings v.interfaces (i: ''
353 ip link set dev "${i}" master "${n}"
354 ip link set dev "${i}" up
355 '')}
356 # Save list of enslaved interfaces
357 echo "${
358 flip concatMapStrings v.interfaces (i: ''
359 ${i}
360 '')
361 }" > /run/${n}.interfaces
362
363 ${optionalString config.virtualisation.libvirtd.enable ''
364 # Enslave dynamically added interfaces which may be lost on nixos-rebuild
365 #
366 # if `libvirtd.service` is not running, do not use `virsh` which would try activate it via 'libvirtd.socket' and thus start it out-of-order.
367 # `libvirtd.service` will set up bridge interfaces when it will start normally.
368 #
369 if /run/current-system/systemd/bin/systemctl --quiet is-active 'libvirtd.service'; then
370 for uri in qemu:///system lxc:///; do
371 for dom in $(${pkgs.libvirt}/bin/virsh -c $uri list --name); do
372 ${pkgs.libvirt}/bin/virsh -c $uri dumpxml "$dom" | \
373 ${pkgs.xmlstarlet}/bin/xmlstarlet sel -t -m "//domain/devices/interface[@type='bridge'][source/@bridge='${n}'][target/@dev]" -v "concat('ip link set dev ',target/@dev,' master ',source/@bridge,';')" | \
374 ${pkgs.bash}/bin/bash
375 done
376 done
377 fi
378 ''}
379
380 # Enable stp on the interface
381 ${optionalString v.rstp ''
382 echo 2 >/sys/class/net/${n}/bridge/stp_state
383 ''}
384
385 ip link set dev "${n}" up
386 '';
387 postStop = ''
388 ip link set dev "${n}" down || true
389 ip link del dev "${n}" || true
390 rm -f /run/${n}.interfaces
391 '';
392 reload = ''
393 # Un-enslave child interfaces (old list of interfaces)
394 for interface in `cat /run/${n}.interfaces`; do
395 ip link set dev "$interface" nomaster up
396 done
397
398 # Enslave child interfaces (new list of interfaces)
399 ${flip concatMapStrings v.interfaces (i: ''
400 ip link set dev "${i}" master "${n}"
401 ip link set dev "${i}" up
402 '')}
403 # Save list of enslaved interfaces
404 echo "${
405 flip concatMapStrings v.interfaces (i: ''
406 ${i}
407 '')
408 }" > /run/${n}.interfaces
409
410 # (Un-)set stp on the bridge
411 echo ${if v.rstp then "2" else "0"} > /sys/class/net/${n}/bridge/stp_state
412 '';
413 reloadIfChanged = true;
414 }
415 );
416
417 createVswitchDevice =
418 n: v:
419 nameValuePair "${n}-netdev" (
420 let
421 deps = concatLists (
422 map deviceDependency (attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces))
423 );
424 internalConfigs = map (i: "network-addresses-${i}.service") (
425 attrNames (filterAttrs (_: config: config.type == "internal") v.interfaces)
426 );
427 ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules;
428 in
429 {
430 description = "Open vSwitch Interface ${n}";
431 wantedBy = [
432 "network-setup.service"
433 (subsystemDevice n)
434 ] ++ internalConfigs;
435 # before = [ "network-setup.service" ];
436 # should work without internalConfigs dependencies because address/link configuration depends
437 # on the device, which is created by ovs-vswitchd with type=internal, but it does not...
438 before = [ "network-setup.service" ] ++ internalConfigs;
439 partOf = [ "network-setup.service" ]; # shutdown the bridge when network is shutdown
440 bindsTo = [ "ovs-vswitchd.service" ]; # requires ovs-vswitchd to be alive at all times
441 after = [
442 "network-pre.target"
443 "ovs-vswitchd.service"
444 ] ++ deps; # start switch after physical interfaces and vswitch daemon
445 wants = deps; # if one or more interface fails, the switch should continue to run
446 serviceConfig.Type = "oneshot";
447 serviceConfig.RemainAfterExit = true;
448 path = [
449 pkgs.iproute2
450 config.virtualisation.vswitch.package
451 ];
452 preStart = ''
453 echo "Resetting Open vSwitch ${n}..."
454 ovs-vsctl --if-exists del-br ${n} -- add-br ${n} \
455 -- set bridge ${n} protocols=${concatStringsSep "," v.supportedOpenFlowVersions}
456 '';
457 script = ''
458 echo "Configuring Open vSwitch ${n}..."
459 ovs-vsctl ${
460 concatStrings (
461 mapAttrsToList (
462 name: config:
463 " -- add-port ${n} ${name}" + optionalString (config.vlan != null) " tag=${toString config.vlan}"
464 ) v.interfaces
465 )
466 } \
467 ${
468 concatStrings (
469 mapAttrsToList (
470 name: config: optionalString (config.type != null) " -- set interface ${name} type=${config.type}"
471 ) v.interfaces
472 )
473 } \
474 ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \
475 ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)}
476
477
478 echo "Adding OpenFlow rules for Open vSwitch ${n}..."
479 ovs-ofctl --protocols=${v.openFlowVersion} add-flows ${n} ${ofRules}
480 '';
481 postStop = ''
482 echo "Cleaning Open vSwitch ${n}"
483 echo "Shutting down internal ${n} interface"
484 ip link set dev ${n} down || true
485 echo "Deleting flows for ${n}"
486 ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
487 echo "Deleting Open vSwitch ${n}"
488 ovs-vsctl --if-exists del-br ${n} || true
489 '';
490 }
491 );
492
493 createBondDevice =
494 n: v:
495 nameValuePair "${n}-netdev" (
496 let
497 deps = concatLists (map deviceDependency v.interfaces);
498 in
499 {
500 description = "Bond Interface ${n}";
501 wantedBy = [
502 "network-setup.service"
503 (subsystemDevice n)
504 ];
505 bindsTo = deps;
506 after = [ "network-pre.target" ] ++ deps ++ map (i: "network-addresses-${i}.service") v.interfaces;
507 before = [ "network-setup.service" ];
508 serviceConfig.Type = "oneshot";
509 serviceConfig.RemainAfterExit = true;
510 path = [
511 pkgs.iproute2
512 pkgs.gawk
513 ];
514 script = ''
515 echo "Destroying old bond ${n}..."
516 ${destroyBond n}
517
518 echo "Creating new bond ${n}..."
519 ip link add name "${n}" type bond \
520 ${
521 let
522 opts = (mapAttrs (const toString) (bondDeprecation.filterDeprecated v)) // v.driverOptions;
523 in
524 concatStringsSep "\n" (mapAttrsToList (set: val: " ${set} ${val} \\") opts)
525 }
526
527 # !!! There must be a better way to wait for the interface
528 while [ ! -d "/sys/class/net/${n}" ]; do sleep 0.1; done;
529
530 # Bring up the bond and enslave the specified interfaces
531 ip link set dev "${n}" up
532 ${flip concatMapStrings v.interfaces (i: ''
533 ip link set dev "${i}" down
534 ip link set dev "${i}" master "${n}"
535 '')}
536 '';
537 postStop = destroyBond n;
538 }
539 );
540
541 createMacvlanDevice =
542 n: v:
543 nameValuePair "${n}-netdev" (
544 let
545 deps = deviceDependency v.interface;
546 in
547 {
548 description = "MACVLAN Interface ${n}";
549 wantedBy = [
550 "network-setup.service"
551 (subsystemDevice n)
552 ];
553 bindsTo = deps;
554 after = [ "network-pre.target" ] ++ deps;
555 before = [ "network-setup.service" ];
556 serviceConfig.Type = "oneshot";
557 serviceConfig.RemainAfterExit = true;
558 path = [ pkgs.iproute2 ];
559 script = ''
560 # Remove Dead Interfaces
561 ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
562 ip link add link "${v.interface}" name "${n}" type macvlan \
563 ${optionalString (v.mode != null) "mode ${v.mode}"}
564 ip link set dev "${n}" up
565 '';
566 postStop = ''
567 ip link delete dev "${n}" || true
568 '';
569 }
570 );
571
572 createFouEncapsulation =
573 n: v:
574 nameValuePair "${n}-fou-encap" (
575 let
576 # if we have a device to bind to we can wait for its addresses to be
577 # configured, otherwise external sequencing is required.
578 deps = optionals (v.local != null && v.local.dev != null) (
579 deviceDependency v.local.dev ++ [ "network-addresses-${v.local.dev}.service" ]
580 );
581 fouSpec = "port ${toString v.port} ${
582 if v.protocol != null then "ipproto ${toString v.protocol}" else "gue"
583 } ${
584 optionalString (v.local != null)
585 "local ${escapeShellArg v.local.address} ${
586 optionalString (v.local.dev != null) "dev ${escapeShellArg v.local.dev}"
587 }"
588 }";
589 in
590 {
591 description = "FOU endpoint ${n}";
592 wantedBy = [
593 "network-setup.service"
594 (subsystemDevice n)
595 ];
596 bindsTo = deps;
597 after = [ "network-pre.target" ] ++ deps;
598 before = [ "network-setup.service" ];
599 serviceConfig.Type = "oneshot";
600 serviceConfig.RemainAfterExit = true;
601 path = [ pkgs.iproute2 ];
602 script = ''
603 # always remove previous incarnation since show can't filter
604 ip fou del ${fouSpec} >/dev/null 2>&1 || true
605 ip fou add ${fouSpec}
606 '';
607 postStop = ''
608 ip fou del ${fouSpec} || true
609 '';
610 }
611 );
612
613 createSitDevice =
614 n: v:
615 nameValuePair "${n}-netdev" (
616 let
617 deps = deviceDependency v.dev;
618 in
619 {
620 description = "6-to-4 Tunnel Interface ${n}";
621 wantedBy = [
622 "network-setup.service"
623 (subsystemDevice n)
624 ];
625 bindsTo = deps;
626 after = [ "network-pre.target" ] ++ deps;
627 before = [ "network-setup.service" ];
628 serviceConfig.Type = "oneshot";
629 serviceConfig.RemainAfterExit = true;
630 path = [ pkgs.iproute2 ];
631 script = ''
632 # Remove Dead Interfaces
633 ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
634 ip link add name "${n}" type sit \
635 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
636 ${optionalString (v.local != null) "local \"${v.local}\""} \
637 ${optionalString (v.ttl != null) "ttl ${toString v.ttl}"} \
638 ${optionalString (v.dev != null) "dev \"${v.dev}\""} \
639 ${optionalString (v.encapsulation != null)
640 "encap ${v.encapsulation.type} encap-dport ${toString v.encapsulation.port} ${
641 optionalString (
642 v.encapsulation.sourcePort != null
643 ) "encap-sport ${toString v.encapsulation.sourcePort}"
644 }"
645 }
646 ip link set dev "${n}" up
647 '';
648 postStop = ''
649 ip link delete dev "${n}" || true
650 '';
651 }
652 );
653
654 createGreDevice =
655 n: v:
656 nameValuePair "${n}-netdev" (
657 let
658 deps = deviceDependency v.dev;
659 ttlarg = if lib.hasPrefix "ip6" v.type then "hoplimit" else "ttl";
660 in
661 {
662 description = "GRE Tunnel Interface ${n}";
663 wantedBy = [
664 "network-setup.service"
665 (subsystemDevice n)
666 ];
667 bindsTo = deps;
668 after = [ "network-pre.target" ] ++ deps;
669 before = [ "network-setup.service" ];
670 serviceConfig.Type = "oneshot";
671 serviceConfig.RemainAfterExit = true;
672 path = [ pkgs.iproute2 ];
673 script = ''
674 # Remove Dead Interfaces
675 ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
676 ip link add name "${n}" type ${v.type} \
677 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
678 ${optionalString (v.local != null) "local \"${v.local}\""} \
679 ${optionalString (v.ttl != null) "${ttlarg} ${toString v.ttl}"} \
680 ${optionalString (v.dev != null) "dev \"${v.dev}\""}
681 ip link set dev "${n}" up
682 '';
683 postStop = ''
684 ip link delete dev "${n}" || true
685 '';
686 }
687 );
688
689 createVlanDevice =
690 n: v:
691 nameValuePair "${n}-netdev" (
692 let
693 deps = deviceDependency v.interface;
694 in
695 {
696 description = "VLAN Interface ${n}";
697 wantedBy = [
698 "network-setup.service"
699 (subsystemDevice n)
700 ];
701 bindsTo = deps;
702 partOf = [ "network-setup.service" ];
703 after = [ "network-pre.target" ] ++ deps;
704 before = [ "network-setup.service" ];
705 serviceConfig.Type = "oneshot";
706 serviceConfig.RemainAfterExit = true;
707 path = [ pkgs.iproute2 ];
708 script = ''
709 # Remove Dead Interfaces
710 ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
711 ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}"
712
713 # We try to bring up the logical VLAN interface. If the master
714 # interface the logical interface is dependent upon is not up yet we will
715 # fail to immediately bring up the logical interface. The resulting logical
716 # interface will brought up later when the master interface is up.
717 ip link set dev "${n}" up || true
718 '';
719 postStop = ''
720 ip link delete dev "${n}" || true
721 '';
722 }
723 );
724
725 in
726 listToAttrs (
727 map configureAddrs interfaces ++ map createTunDevice (filter (i: i.virtual) interfaces)
728 )
729 // mapAttrs' createBridgeDevice cfg.bridges
730 // mapAttrs' createVswitchDevice cfg.vswitches
731 // mapAttrs' createBondDevice cfg.bonds
732 // mapAttrs' createMacvlanDevice cfg.macvlans
733 // mapAttrs' createFouEncapsulation cfg.fooOverUDP
734 // mapAttrs' createSitDevice cfg.sits
735 // mapAttrs' createGreDevice cfg.greTunnels
736 // mapAttrs' createVlanDevice cfg.vlans
737 // {
738 network-setup = networkSetup;
739 network-local-commands = networkLocalCommands;
740 };
741
742 services.udev.extraRules = ''
743 KERNEL=="tun", TAG+="systemd"
744 '';
745
746 };
747
748in
749
750{
751 config = mkMerge [
752 bondWarnings
753 (mkIf (!cfg.useNetworkd) normalConfig)
754 {
755 # Ensure slave interfaces are brought up
756 networking.interfaces = genAttrs slaves (i: { });
757 }
758 ];
759}