1{
2 config,
3 options,
4 lib,
5 pkgs,
6 utils,
7 ...
8}:
9
10with lib;
11with utils;
12
13let
14
15 cfg = config.networking;
16 opt = options.networking;
17 interfaces = attrValues cfg.interfaces;
18 hasVirtuals = any (i: i.virtual) interfaces;
19 hasSits = cfg.sits != { };
20 hasGres = cfg.greTunnels != { };
21 hasBonds = cfg.bonds != { };
22 hasFous = cfg.fooOverUDP != { } || filterAttrs (_: s: s.encapsulation != null) cfg.sits != { };
23
24 slaves =
25 concatMap (i: i.interfaces) (attrValues cfg.bonds)
26 ++ concatMap (i: i.interfaces) (attrValues cfg.bridges)
27 ++ concatMap (
28 i:
29 attrNames (
30 filterAttrs (name: config: !(config.type == "internal" || hasAttr name cfg.interfaces)) i.interfaces
31 )
32 ) (attrValues cfg.vswitches);
33
34 slaveIfs = map (i: cfg.interfaces.${i}) (filter (i: cfg.interfaces ? ${i}) slaves);
35
36 rstpBridges = flip filterAttrs cfg.bridges (_: { rstp, ... }: rstp);
37
38 needsMstpd = rstpBridges != { };
39
40 bridgeStp = optional needsMstpd (
41 pkgs.writeTextFile {
42 name = "bridge-stp";
43 executable = true;
44 destination = "/bin/bridge-stp";
45 text = ''
46 #!${pkgs.runtimeShell} -e
47 export PATH="${pkgs.mstpd}/bin"
48
49 BRIDGES=(${concatStringsSep " " (attrNames rstpBridges)})
50 for BRIDGE in $BRIDGES; do
51 if [ "$BRIDGE" = "$1" ]; then
52 if [ "$2" = "start" ]; then
53 mstpctl addbridge "$BRIDGE"
54 exit 0
55 elif [ "$2" = "stop" ]; then
56 mstpctl delbridge "$BRIDGE"
57 exit 0
58 fi
59 exit 1
60 fi
61 done
62 exit 1
63 '';
64 }
65 );
66
67 # We must escape interfaces due to the systemd interpretation
68 subsystemDevice = interface: "sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
69
70 addrOpts =
71 v:
72 assert v == 4 || v == 6;
73 {
74 options = {
75 address = mkOption {
76 type = types.str;
77 description = ''
78 IPv${toString v} address of the interface. Leave empty to configure the
79 interface using DHCP.
80 '';
81 };
82
83 prefixLength = mkOption {
84 type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
85 description = ''
86 Subnet mask of the interface, specified as the number of
87 bits in the prefix (`${if v == 4 then "24" else "64"}`).
88 '';
89 };
90 };
91 };
92
93 routeOpts = v: {
94 options = {
95 address = mkOption {
96 type = types.str;
97 description = "IPv${toString v} address of the network.";
98 };
99
100 prefixLength = mkOption {
101 type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
102 description = ''
103 Subnet mask of the network, specified as the number of
104 bits in the prefix (`${if v == 4 then "24" else "64"}`).
105 '';
106 };
107
108 type = mkOption {
109 type = types.nullOr (
110 types.enum [
111 "unicast"
112 "local"
113 "broadcast"
114 "multicast"
115 ]
116 );
117 default = null;
118 description = ''
119 Type of the route. See the `Route types` section
120 in the {manpage}`ip-route(8)` manual page for the details.
121
122 Note that `prohibit`, `blackhole`,
123 `unreachable`, and `throw` cannot
124 be configured per device, so they are not available here. Similarly,
125 `nat` hasn't been supported since kernel 2.6.
126 '';
127 };
128
129 via = mkOption {
130 type = types.nullOr types.str;
131 default = null;
132 description = "IPv${toString v} address of the next hop.";
133 };
134
135 options = mkOption {
136 type = types.attrsOf types.str;
137 default = { };
138 example = {
139 mtu = "1492";
140 window = "524288";
141 };
142 description = ''
143 Other route options. See the symbol `OPTIONS`
144 in the {manpage}`ip-route(8)` manual page for the details.
145 You may also specify `metric`,
146 `src`, `protocol`,
147 `scope`, `from`
148 and `table`, which are technically
149 not route options, in the sense used in the manual.
150 '';
151 };
152
153 };
154 };
155
156 gatewayCoerce = address: { inherit address; };
157
158 gatewayOpts =
159 { ... }:
160 {
161
162 options = {
163
164 address = mkOption {
165 type = types.str;
166 description = "The default gateway address.";
167 };
168
169 interface = mkOption {
170 type = types.nullOr types.str;
171 default = null;
172 example = "enp0s3";
173 description = "The default gateway interface.";
174 };
175
176 metric = mkOption {
177 type = types.nullOr types.int;
178 default = null;
179 example = 42;
180 description = "The default gateway metric/preference.";
181 };
182
183 };
184
185 };
186
187 interfaceOpts =
188 { name, ... }:
189 {
190
191 options = {
192 name = mkOption {
193 example = "eth0";
194 type = types.str;
195 description = "Name of the interface.";
196 };
197
198 tempAddress = mkOption {
199 type = types.enum (lib.attrNames tempaddrValues);
200 default = cfg.tempAddresses;
201 defaultText = literalExpression ''config.networking.tempAddresses'';
202 description = ''
203 When IPv6 is enabled with SLAAC, this option controls the use of
204 temporary address (aka privacy extensions) on this
205 interface. This is used to reduce tracking.
206
207 See also the global option
208 [](#opt-networking.tempAddresses), which
209 applies to all interfaces where this is not set.
210
211 Possible values are:
212 ${tempaddrDoc}
213 '';
214 };
215
216 useDHCP = mkOption {
217 type = types.nullOr types.bool;
218 default = null;
219 description = ''
220 Whether this interface should be configured with DHCP. Overrides the
221 default set by {option}`networking.useDHCP`. If `null` (the default),
222 DHCP is enabled if the interface has no IPv4 addresses configured
223 with {option}`networking.interfaces.<name>.ipv4.addresses`, and
224 disabled otherwise.
225 '';
226 };
227
228 ipv4.addresses = mkOption {
229 default = [ ];
230 example = [
231 {
232 address = "10.0.0.1";
233 prefixLength = 16;
234 }
235 {
236 address = "192.168.1.1";
237 prefixLength = 24;
238 }
239 ];
240 type = with types; listOf (submodule (addrOpts 4));
241 description = ''
242 List of IPv4 addresses that will be statically assigned to the interface.
243 '';
244 };
245
246 ipv6.addresses = mkOption {
247 default = [ ];
248 example = [
249 {
250 address = "fdfd:b3f0:482::1";
251 prefixLength = 48;
252 }
253 {
254 address = "2001:1470:fffd:2098::e006";
255 prefixLength = 64;
256 }
257 ];
258 type = with types; listOf (submodule (addrOpts 6));
259 description = ''
260 List of IPv6 addresses that will be statically assigned to the interface.
261 '';
262 };
263
264 ipv4.routes = mkOption {
265 default = [ ];
266 example = [
267 {
268 address = "10.0.0.0";
269 prefixLength = 16;
270 }
271 {
272 address = "192.168.2.0";
273 prefixLength = 24;
274 via = "192.168.1.1";
275 }
276 ];
277 type = with types; listOf (submodule (routeOpts 4));
278 description = ''
279 List of extra IPv4 static routes that will be assigned to the interface.
280
281 ::: {.warning}
282 If the route type is the default `unicast`, then the scope
283 is set differently depending on the value of {option}`networking.useNetworkd`:
284 the script-based backend sets it to `link`, while networkd sets
285 it to `global`.
286 :::
287
288 If you want consistency between the two implementations,
289 set the scope of the route manually with
290 `networking.interfaces.eth0.ipv4.routes = [{ options.scope = "global"; }]`
291 for example.
292 '';
293 };
294
295 ipv6.routes = mkOption {
296 default = [ ];
297 example = [
298 {
299 address = "fdfd:b3f0::";
300 prefixLength = 48;
301 }
302 {
303 address = "2001:1470:fffd:2098::";
304 prefixLength = 64;
305 via = "fdfd:b3f0::1";
306 }
307 ];
308 type = with types; listOf (submodule (routeOpts 6));
309 description = ''
310 List of extra IPv6 static routes that will be assigned to the interface.
311 '';
312 };
313
314 macAddress = mkOption {
315 default = null;
316 example = "00:11:22:33:44:55";
317 type = types.nullOr (types.str);
318 description = ''
319 MAC address of the interface. Leave empty to use the default.
320 '';
321 };
322
323 mtu = mkOption {
324 default = null;
325 example = 9000;
326 type = types.nullOr types.int;
327 description = ''
328 MTU size for packets leaving the interface. Leave empty to use the default.
329 '';
330 };
331
332 virtual = mkOption {
333 default = false;
334 type = types.bool;
335 description = ''
336 Whether this interface is virtual and should be created by tunctl.
337 This is mainly useful for creating bridges between a host and a virtual
338 network such as VPN or a virtual machine.
339 '';
340 };
341
342 virtualOwner = mkOption {
343 default = "root";
344 type = types.str;
345 description = ''
346 In case of a virtual device, the user who owns it.
347 '';
348 };
349
350 virtualType = mkOption {
351 default = if hasPrefix "tun" name then "tun" else "tap";
352 defaultText = literalExpression ''if hasPrefix "tun" name then "tun" else "tap"'';
353 type =
354 with types;
355 enum [
356 "tun"
357 "tap"
358 ];
359 description = ''
360 The type of interface to create.
361 The default is TUN for an interface name starting
362 with "tun", otherwise TAP.
363 '';
364 };
365
366 proxyARP = mkOption {
367 default = false;
368 type = types.bool;
369 description = ''
370 Turn on proxy_arp for this device.
371 This is mainly useful for creating pseudo-bridges between a real
372 interface and a virtual network such as VPN or a virtual machine for
373 interfaces that don't support real bridging (most wlan interfaces).
374 As ARP proxying acts slightly above the link-layer, below-ip traffic
375 isn't bridged, so things like DHCP won't work. The advantage above
376 using NAT lies in the fact that no IP addresses are shared, so all
377 hosts are reachable/routeable.
378
379 WARNING: turns on ip-routing, so if you have multiple interfaces, you
380 should think of the consequence and setup firewall rules to limit this.
381 '';
382 };
383
384 wakeOnLan = {
385 enable = mkOption {
386 type = types.bool;
387 default = false;
388 description = "Whether to enable wol on this interface.";
389 };
390 policy = mkOption {
391 type =
392 with types;
393 listOf (enum [
394 "phy"
395 "unicast"
396 "multicast"
397 "broadcast"
398 "arp"
399 "magic"
400 "secureon"
401 ]);
402 default = [ "magic" ];
403 description = ''
404 The [Wake-on-LAN policy](https://www.freedesktop.org/software/systemd/man/systemd.link.html#WakeOnLan=)
405 to set for the device.
406
407 The options are
408 - `phy`: Wake on PHY activity
409 - `unicast`: Wake on unicast messages
410 - `multicast`: Wake on multicast messages
411 - `broadcast`: Wake on broadcast messages
412 - `arp`: Wake on ARP
413 - `magic`: Wake on receipt of a magic packet
414 '';
415 };
416 };
417 };
418
419 config = {
420 name = mkDefault name;
421 };
422
423 # Renamed or removed options
424 imports =
425 let
426 defined = x: x != "_mkMergedOptionModule";
427 in
428 [
429 (mkChangedOptionModule [ "preferTempAddress" ] [ "tempAddress" ] (
430 config:
431 let
432 bool = getAttrFromPath [ "preferTempAddress" ] config;
433 in
434 if bool then "default" else "enabled"
435 ))
436 (mkRenamedOptionModule [ "ip4" ] [ "ipv4" "addresses" ])
437 (mkRenamedOptionModule [ "ip6" ] [ "ipv6" "addresses" ])
438 (mkRemovedOptionModule [ "subnetMask" ] ''
439 Supply a prefix length instead; use option
440 networking.interfaces.<name>.ipv{4,6}.addresses'')
441 (mkMergedOptionModule
442 [
443 [ "ipAddress" ]
444 [ "prefixLength" ]
445 ]
446 [ "ipv4" "addresses" ]
447 (
448 cfg:
449 with cfg;
450 optional (defined ipAddress && defined prefixLength) {
451 address = ipAddress;
452 prefixLength = prefixLength;
453 }
454 )
455 )
456 (mkMergedOptionModule
457 [
458 [ "ipv6Address" ]
459 [ "ipv6PrefixLength" ]
460 ]
461 [ "ipv6" "addresses" ]
462 (
463 cfg:
464 with cfg;
465 optional (defined ipv6Address && defined ipv6PrefixLength) {
466 address = ipv6Address;
467 prefixLength = ipv6PrefixLength;
468 }
469 )
470 )
471
472 ({
473 options.warnings = options.warnings;
474 options.assertions = options.assertions;
475 })
476 ];
477
478 };
479
480 vswitchInterfaceOpts =
481 { name, ... }:
482 {
483
484 options = {
485
486 name = mkOption {
487 description = "Name of the interface";
488 example = "eth0";
489 type = types.str;
490 };
491
492 vlan = mkOption {
493 description = "Vlan tag to apply to interface";
494 example = 10;
495 type = types.nullOr types.int;
496 default = null;
497 };
498
499 type = mkOption {
500 description = "Openvswitch type to assign to interface";
501 example = "internal";
502 type = types.nullOr types.str;
503 default = null;
504 };
505 };
506 };
507
508 hexChars = stringToCharacters "0123456789abcdef";
509
510 isHexString = s: all (c: elem c hexChars) (stringToCharacters (toLower s));
511
512 tempaddrValues = {
513 disabled = {
514 sysctl = "0";
515 description = "completely disable IPv6 temporary addresses";
516 };
517 enabled = {
518 sysctl = "1";
519 description = "generate IPv6 temporary addresses but still use EUI-64 addresses as source addresses";
520 };
521 default = {
522 sysctl = "2";
523 description = "generate IPv6 temporary addresses and use these as source addresses in routing";
524 };
525 };
526 tempaddrDoc = concatStringsSep "\n" (
527 mapAttrsToList (name: { description, ... }: ''- `"${name}"` to ${description};'') tempaddrValues
528 );
529
530 hostidFile = pkgs.runCommand "gen-hostid" { preferLocalBuild = true; } ''
531 hi="${cfg.hostId}"
532 ${
533 if pkgs.stdenv.hostPlatform.isBigEndian then
534 ''
535 echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > $out
536 ''
537 else
538 ''
539 echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > $out
540 ''
541 }
542 '';
543
544in
545
546{
547
548 ###### interface
549
550 options = {
551
552 networking.hostName = mkOption {
553 default = config.system.nixos.distroId;
554 defaultText = literalExpression "config.system.nixos.distroId";
555 # Only allow hostnames without the domain name part (i.e. no FQDNs, see
556 # e.g. "man 5 hostname") and require valid DNS labels (recommended
557 # syntax). Note: We also allow underscores for compatibility/legacy
558 # reasons (as undocumented feature):
559 type = types.strMatching "^$|^[[:alnum:]]([[:alnum:]_-]{0,61}[[:alnum:]])?$";
560 description = ''
561 The name of the machine. Leave it empty if you want to obtain it from a
562 DHCP server (if using DHCP). The hostname must be a valid DNS label (see
563 RFC 1035 section 2.3.1: "Preferred name syntax", RFC 1123 section 2.1:
564 "Host Names and Numbers") and as such must not contain the domain part.
565 This means that the hostname must start with a letter or digit,
566 end with a letter or digit, and have as interior characters only
567 letters, digits, and hyphen. The maximum length is 63 characters.
568 Additionally it is recommended to only use lower-case characters.
569 If (e.g. for legacy reasons) a FQDN is required as the Linux kernel
570 network node hostname (uname --nodename) the option
571 boot.kernel.sysctl."kernel.hostname" can be used as a workaround (but
572 the 64 character limit still applies).
573
574 WARNING: Do not use underscores (_) or you may run into unexpected issues.
575 '';
576 # warning until the issues in https://github.com/NixOS/nixpkgs/pull/138978
577 # are resolved
578 };
579
580 networking.fqdn = mkOption {
581 type = types.str;
582 default =
583 if (cfg.hostName != "" && cfg.domain != null) then
584 "${cfg.hostName}.${cfg.domain}"
585 else
586 throw ''
587 The FQDN is required but cannot be determined from `networking.hostName`
588 and `networking.domain`. Please ensure these options are set properly or
589 set `networking.fqdn` directly.
590 '';
591 defaultText = literalExpression ''"''${networking.hostName}.''${networking.domain}"'';
592 description = ''
593 The fully qualified domain name (FQDN) of this host. By default, it is
594 the result of combining `networking.hostName` and `networking.domain.`
595
596 Using this option will result in an evaluation error if the hostname is empty or
597 no domain is specified.
598
599 Modules that accept a mere `networking.hostName` but prefer a fully qualified
600 domain name may use `networking.fqdnOrHostName` instead.
601 '';
602 };
603
604 networking.fqdnOrHostName = mkOption {
605 readOnly = true;
606 type = types.str;
607 default =
608 if (cfg.domain != null || opt.fqdn.highestPrio < (mkOptionDefault { }).priority) then
609 cfg.fqdn
610 else
611 cfg.hostName;
612 defaultText = literalExpression ''
613 if config.networking.domain != null || config.networking.fqdn is set then config.networking.fqdn else config.networking.hostName
614 '';
615 description = ''
616 Either the fully qualified domain name (FQDN), or just the host name if
617 it does not exist.
618
619 This is a convenience option for modules to read instead of `fqdn` when
620 a mere `hostName` is also an acceptable value; this option does not
621 throw an error when `domain` or `fqdn` is unset.
622 '';
623 };
624
625 networking.hostId = mkOption {
626 default = null;
627 example = "4e98920d";
628 type = types.nullOr types.str;
629 description = ''
630 The 32-bit host ID of the machine, formatted as 8 hexadecimal characters.
631
632 You should try to make this ID unique among your machines. You can
633 generate a random 32-bit ID using the following commands:
634
635 `head -c 8 /etc/machine-id`
636
637 (this derives it from the machine-id that systemd generates) or
638
639 `head -c4 /dev/urandom | od -A none -t x4`
640
641 The primary use case is to ensure when using ZFS that a pool isn't imported
642 accidentally on a wrong machine.
643 '';
644 };
645
646 networking.enableIPv6 = mkOption {
647 default = true;
648 type = types.bool;
649 description = ''
650 Whether to enable support for IPv6.
651 '';
652 };
653
654 networking.defaultGateway = mkOption {
655 default = null;
656 example = {
657 address = "131.211.84.1";
658 interface = "enp3s0";
659 };
660 type = types.nullOr (types.coercedTo types.str gatewayCoerce (types.submodule gatewayOpts));
661 description = ''
662 The default gateway. It can be left empty if it is auto-detected through DHCP.
663 It can be specified as a string or an option set along with a network interface.
664 '';
665 };
666
667 networking.defaultGateway6 = mkOption {
668 default = null;
669 example = {
670 address = "2001:4d0:1e04:895::1";
671 interface = "enp3s0";
672 };
673 type = types.nullOr (types.coercedTo types.str gatewayCoerce (types.submodule gatewayOpts));
674 description = ''
675 The default ipv6 gateway. It can be left empty if it is auto-detected through DHCP.
676 It can be specified as a string or an option set along with a network interface.
677 '';
678 };
679
680 networking.defaultGatewayWindowSize = mkOption {
681 default = null;
682 example = 524288;
683 type = types.nullOr types.int;
684 description = ''
685 The window size of the default gateway. It limits maximal data bursts that TCP peers
686 are allowed to send to us.
687 '';
688 };
689
690 networking.nameservers = mkOption {
691 type = types.listOf types.str;
692 default = [ ];
693 example = [
694 "130.161.158.4"
695 "130.161.33.17"
696 ];
697 description = ''
698 The list of nameservers. It can be left empty if it is auto-detected through DHCP.
699 '';
700 };
701
702 networking.search = mkOption {
703 default = [ ];
704 example = [
705 "example.com"
706 "home.arpa"
707 ];
708 type = types.listOf types.str;
709 description = ''
710 The list of search paths used when resolving domain names.
711 '';
712 };
713
714 networking.domain = mkOption {
715 default = null;
716 example = "home.arpa";
717 type = types.nullOr types.str;
718 description = ''
719 The domain. It can be left empty if it is auto-detected through DHCP.
720 '';
721 };
722
723 networking.useHostResolvConf = mkOption {
724 type = types.bool;
725 default = false;
726 description = ''
727 In containers, whether to use the
728 {file}`resolv.conf` supplied by the host.
729 '';
730 };
731
732 networking.localCommands = mkOption {
733 type = types.lines;
734 default = "";
735 example = "text=anything; echo You can put $text here.";
736 description = ''
737 Shell commands to be executed at the end of the
738 `network-setup` systemd service. Note that if
739 you are using DHCP to obtain the network configuration,
740 interfaces may not be fully configured yet.
741 '';
742 };
743
744 networking.interfaces = mkOption {
745 default = { };
746 example = {
747 eth0.ipv4.addresses = [
748 {
749 address = "131.211.84.78";
750 prefixLength = 25;
751 }
752 ];
753 };
754 description = ''
755 The configuration for each network interface.
756
757 Please note that {option}`systemd.network.netdevs` has more features
758 and is better maintained. When building new things, it is advised to
759 use that instead.
760 '';
761 type = with types; attrsOf (submodule interfaceOpts);
762 };
763
764 networking.vswitches = mkOption {
765 default = { };
766 example = {
767 vs0.interfaces = {
768 eth0 = { };
769 lo1 = {
770 type = "internal";
771 };
772 };
773 vs1.interfaces = [
774 { name = "eth2"; }
775 {
776 name = "lo2";
777 type = "internal";
778 }
779 ];
780 };
781 description = ''
782 This option allows you to define Open vSwitches that connect
783 physical networks together. The value of this option is an
784 attribute set. Each attribute specifies a vswitch, with the
785 attribute name specifying the name of the vswitch's network
786 interface.
787 '';
788
789 type =
790 with types;
791 attrsOf (submodule {
792
793 options = {
794
795 interfaces = mkOption {
796 description = "The physical network interfaces connected by the vSwitch.";
797 type = with types; attrsOf (submodule vswitchInterfaceOpts);
798 };
799
800 controllers = mkOption {
801 type = types.listOf types.str;
802 default = [ ];
803 example = [ "ptcp:6653:[::1]" ];
804 description = ''
805 Specify the controller targets. For the allowed options see `man 8 ovs-vsctl`.
806 '';
807 };
808
809 openFlowRules = mkOption {
810 type = types.lines;
811 default = "";
812 example = ''
813 actions=normal
814 '';
815 description = ''
816 OpenFlow rules to insert into the Open vSwitch. All `openFlowRules` are
817 loaded with `ovs-ofctl` within one atomic operation.
818 '';
819 };
820
821 # TODO: custom "openflow version" type, with list from existing openflow protocols
822 supportedOpenFlowVersions = mkOption {
823 type = types.listOf types.str;
824 example = [
825 "OpenFlow10"
826 "OpenFlow13"
827 "OpenFlow14"
828 ];
829 default = [ "OpenFlow13" ];
830 description = ''
831 Supported versions to enable on this switch.
832 '';
833 };
834
835 # TODO: use same type as elements from supportedOpenFlowVersions
836 openFlowVersion = mkOption {
837 type = types.str;
838 default = "OpenFlow13";
839 description = ''
840 Version of OpenFlow protocol to use when communicating with the switch internally (e.g. with `openFlowRules`).
841 '';
842 };
843
844 extraOvsctlCmds = mkOption {
845 type = types.lines;
846 default = "";
847 example = ''
848 set-fail-mode <switch_name> secure
849 set Bridge <switch_name> stp_enable=true
850 '';
851 description = ''
852 Commands to manipulate the Open vSwitch database. Every line executed with `ovs-vsctl`.
853 All commands are bundled together with the operations for adding the interfaces
854 into one atomic operation.
855 '';
856 };
857
858 };
859
860 });
861
862 };
863
864 networking.bridges = mkOption {
865 default = { };
866 example = {
867 br0.interfaces = [
868 "eth0"
869 "eth1"
870 ];
871 br1.interfaces = [
872 "eth2"
873 "wlan0"
874 ];
875 };
876 description = ''
877 This option allows you to define Ethernet bridge devices
878 that connect physical networks together. The value of this
879 option is an attribute set. Each attribute specifies a
880 bridge, with the attribute name specifying the name of the
881 bridge's network interface.
882 '';
883
884 type =
885 with types;
886 attrsOf (submodule {
887
888 options = {
889
890 interfaces = mkOption {
891 example = [
892 "eth0"
893 "eth1"
894 ];
895 type = types.listOf types.str;
896 description = "The physical network interfaces connected by the bridge.";
897 };
898
899 rstp = mkOption {
900 default = false;
901 type = types.bool;
902 description = "Whether the bridge interface should enable rstp.";
903 };
904
905 };
906
907 });
908
909 };
910
911 networking.bonds =
912 let
913 driverOptionsExample = ''
914 {
915 miimon = "100";
916 mode = "active-backup";
917 }
918 '';
919 in
920 mkOption {
921 default = { };
922 example = literalExpression ''
923 {
924 bond0 = {
925 interfaces = [ "eth0" "wlan0" ];
926 driverOptions = ${driverOptionsExample};
927 };
928 anotherBond.interfaces = [ "enp4s0f0" "enp4s0f1" "enp5s0f0" "enp5s0f1" ];
929 }
930 '';
931 description = ''
932 This option allows you to define bond devices that aggregate multiple,
933 underlying networking interfaces together. The value of this option is
934 an attribute set. Each attribute specifies a bond, with the attribute
935 name specifying the name of the bond's network interface
936 '';
937
938 type =
939 with types;
940 attrsOf (submodule {
941
942 options = {
943
944 interfaces = mkOption {
945 example = [
946 "enp4s0f0"
947 "enp4s0f1"
948 "wlan0"
949 ];
950 type = types.listOf types.str;
951 description = "The interfaces to bond together";
952 };
953
954 driverOptions = mkOption {
955 type = types.attrsOf types.str;
956 default = { };
957 example = literalExpression driverOptionsExample;
958 description = ''
959 Options for the bonding driver.
960 Documentation can be found in
961 <https://www.kernel.org/doc/Documentation/networking/bonding.txt>
962 '';
963
964 };
965
966 lacp_rate = mkOption {
967 default = null;
968 example = "fast";
969 type = types.nullOr types.str;
970 description = ''
971 DEPRECATED, use `driverOptions`.
972 Option specifying the rate in which we'll ask our link partner
973 to transmit LACPDU packets in 802.3ad mode.
974 '';
975 };
976
977 miimon = mkOption {
978 default = null;
979 example = 100;
980 type = types.nullOr types.int;
981 description = ''
982 DEPRECATED, use `driverOptions`.
983 Miimon is the number of millisecond in between each round of polling
984 by the device driver for failed links. By default polling is not
985 enabled and the driver is trusted to properly detect and handle
986 failure scenarios.
987 '';
988 };
989
990 mode = mkOption {
991 default = null;
992 example = "active-backup";
993 type = types.nullOr types.str;
994 description = ''
995 DEPRECATED, use `driverOptions`.
996 The mode which the bond will be running. The default mode for
997 the bonding driver is balance-rr, optimizing for throughput.
998 More information about valid modes can be found at
999 https://www.kernel.org/doc/Documentation/networking/bonding.txt
1000 '';
1001 };
1002
1003 xmit_hash_policy = mkOption {
1004 default = null;
1005 example = "layer2+3";
1006 type = types.nullOr types.str;
1007 description = ''
1008 DEPRECATED, use `driverOptions`.
1009 Selects the transmit hash policy to use for slave selection in
1010 balance-xor, 802.3ad, and tlb modes.
1011 '';
1012 };
1013
1014 };
1015
1016 });
1017 };
1018
1019 networking.macvlans = mkOption {
1020 default = { };
1021 example = literalExpression ''
1022 {
1023 wan = {
1024 interface = "enp2s0";
1025 mode = "vepa";
1026 };
1027 }
1028 '';
1029 description = ''
1030 This option allows you to define macvlan interfaces which should
1031 be automatically created.
1032 '';
1033 type =
1034 with types;
1035 attrsOf (submodule {
1036 options = {
1037
1038 interface = mkOption {
1039 example = "enp4s0";
1040 type = types.str;
1041 description = "The interface the macvlan will transmit packets through.";
1042 };
1043
1044 mode = mkOption {
1045 default = null;
1046 type = types.nullOr types.str;
1047 example = "vepa";
1048 description = "The mode of the macvlan device.";
1049 };
1050
1051 };
1052
1053 });
1054 };
1055
1056 networking.fooOverUDP = mkOption {
1057 default = { };
1058 example = {
1059 primary = {
1060 port = 9001;
1061 local = {
1062 address = "192.0.2.1";
1063 dev = "eth0";
1064 };
1065 };
1066 backup = {
1067 port = 9002;
1068 };
1069 };
1070 description = ''
1071 This option allows you to configure Foo Over UDP and Generic UDP Encapsulation
1072 endpoints. See {manpage}`ip-fou(8)` for details.
1073 '';
1074 type =
1075 with types;
1076 attrsOf (submodule {
1077 options = {
1078 port = mkOption {
1079 type = port;
1080 description = ''
1081 Local port of the encapsulation UDP socket.
1082 '';
1083 };
1084
1085 protocol = mkOption {
1086 type = nullOr (ints.between 1 255);
1087 default = null;
1088 description = ''
1089 Protocol number of the encapsulated packets. Specifying `null`
1090 (the default) creates a GUE endpoint, specifying a protocol number will create
1091 a FOU endpoint.
1092 '';
1093 };
1094
1095 local = mkOption {
1096 type = nullOr (submodule {
1097 options = {
1098 address = mkOption {
1099 type = types.str;
1100 description = ''
1101 Local address to bind to. The address must be available when the FOU
1102 endpoint is created, using the scripted network setup this can be achieved
1103 either by setting `dev` or adding dependency information to
1104 `systemd.services.<name>-fou-encap`; it isn't supported
1105 when using networkd.
1106 '';
1107 };
1108
1109 dev = mkOption {
1110 type = nullOr str;
1111 default = null;
1112 example = "eth0";
1113 description = ''
1114 Network device to bind to.
1115 '';
1116 };
1117 };
1118 });
1119 default = null;
1120 example = {
1121 address = "203.0.113.22";
1122 };
1123 description = ''
1124 Local address (and optionally device) to bind to using the given port.
1125 '';
1126 };
1127 };
1128 });
1129 };
1130
1131 networking.sits = mkOption {
1132 default = { };
1133 example = literalExpression ''
1134 {
1135 hurricane = {
1136 remote = "10.0.0.1";
1137 local = "10.0.0.22";
1138 ttl = 255;
1139 };
1140 msipv6 = {
1141 remote = "192.168.0.1";
1142 dev = "enp3s0";
1143 ttl = 127;
1144 };
1145 }
1146 '';
1147 description = ''
1148 This option allows you to define 6-to-4 interfaces which should be automatically created.
1149 '';
1150 type =
1151 with types;
1152 attrsOf (submodule {
1153 options = {
1154
1155 remote = mkOption {
1156 type = types.nullOr types.str;
1157 default = null;
1158 example = "10.0.0.1";
1159 description = ''
1160 The address of the remote endpoint to forward traffic over.
1161 '';
1162 };
1163
1164 local = mkOption {
1165 type = types.nullOr types.str;
1166 default = null;
1167 example = "10.0.0.22";
1168 description = ''
1169 The address of the local endpoint which the remote
1170 side should send packets to.
1171 '';
1172 };
1173
1174 ttl = mkOption {
1175 type = types.nullOr types.int;
1176 default = null;
1177 example = 255;
1178 description = ''
1179 The time-to-live of the connection to the remote tunnel endpoint.
1180 '';
1181 };
1182
1183 dev = mkOption {
1184 type = types.nullOr types.str;
1185 default = null;
1186 example = "enp4s0f0";
1187 description = ''
1188 The underlying network device on which the tunnel resides.
1189 '';
1190 };
1191
1192 encapsulation =
1193 with types;
1194 mkOption {
1195 type = nullOr (submodule {
1196 options = {
1197 type = mkOption {
1198 type = enum [
1199 "fou"
1200 "gue"
1201 ];
1202 description = ''
1203 Selects encapsulation type. See
1204 {manpage}`ip-link(8)` for details.
1205 '';
1206 };
1207
1208 port = mkOption {
1209 type = port;
1210 example = 9001;
1211 description = ''
1212 Destination port for encapsulated packets.
1213 '';
1214 };
1215
1216 sourcePort = mkOption {
1217 type = nullOr types.port;
1218 default = null;
1219 example = 9002;
1220 description = ''
1221 Source port for encapsulated packets. Will be chosen automatically by
1222 the kernel if unset.
1223 '';
1224 };
1225 };
1226 });
1227 default = null;
1228 example = {
1229 type = "fou";
1230 port = 9001;
1231 };
1232 description = ''
1233 Configures encapsulation in UDP packets.
1234 '';
1235 };
1236
1237 };
1238
1239 });
1240 };
1241
1242 networking.greTunnels = mkOption {
1243 default = { };
1244 example = literalExpression ''
1245 {
1246 greBridge = {
1247 remote = "10.0.0.1";
1248 local = "10.0.0.22";
1249 dev = "enp4s0f0";
1250 type = "tap";
1251 ttl = 255;
1252 };
1253 gre6Tunnel = {
1254 remote = "fd7a:5634::1";
1255 local = "fd7a:5634::2";
1256 dev = "enp4s0f0";
1257 type = "tun6";
1258 ttl = 255;
1259 };
1260 }
1261 '';
1262 description = ''
1263 This option allows you to define Generic Routing Encapsulation (GRE) tunnels.
1264 '';
1265 type =
1266 with types;
1267 attrsOf (submodule {
1268 options = {
1269
1270 remote = mkOption {
1271 type = types.nullOr types.str;
1272 default = null;
1273 example = "10.0.0.1";
1274 description = ''
1275 The address of the remote endpoint to forward traffic over.
1276 '';
1277 };
1278
1279 local = mkOption {
1280 type = types.nullOr types.str;
1281 default = null;
1282 example = "10.0.0.22";
1283 description = ''
1284 The address of the local endpoint which the remote
1285 side should send packets to.
1286 '';
1287 };
1288
1289 dev = mkOption {
1290 type = types.nullOr types.str;
1291 default = null;
1292 example = "enp4s0f0";
1293 description = ''
1294 The underlying network device on which the tunnel resides.
1295 '';
1296 };
1297
1298 ttl = mkOption {
1299 type = types.nullOr types.int;
1300 default = null;
1301 example = 255;
1302 description = ''
1303 The time-to-live/hoplimit of the connection to the remote tunnel endpoint.
1304 '';
1305 };
1306
1307 type = mkOption {
1308 type =
1309 with types;
1310 enum [
1311 "tun"
1312 "tap"
1313 "tun6"
1314 "tap6"
1315 ];
1316 default = "tap";
1317 example = "tap";
1318 apply =
1319 v:
1320 {
1321 tun = "gre";
1322 tap = "gretap";
1323 tun6 = "ip6gre";
1324 tap6 = "ip6gretap";
1325 }
1326 .${v};
1327 description = ''
1328 Whether the tunnel routes layer 2 (tap) or layer 3 (tun) traffic.
1329 '';
1330 };
1331 };
1332 });
1333 };
1334
1335 networking.vlans = mkOption {
1336 default = { };
1337 example = literalExpression ''
1338 {
1339 vlan0 = {
1340 id = 3;
1341 interface = "enp3s0";
1342 };
1343 vlan1 = {
1344 id = 1;
1345 interface = "wlan0";
1346 };
1347 }
1348 '';
1349 description = ''
1350 This option allows you to define vlan devices that tag packets
1351 on top of a physical interface. The value of this option is an
1352 attribute set. Each attribute specifies a vlan, with the name
1353 specifying the name of the vlan interface.
1354 '';
1355
1356 type =
1357 with types;
1358 attrsOf (submodule {
1359
1360 options = {
1361
1362 id = mkOption {
1363 example = 1;
1364 type = types.int;
1365 description = "The vlan identifier";
1366 };
1367
1368 interface = mkOption {
1369 example = "enp4s0";
1370 type = types.str;
1371 description = "The interface the vlan will transmit packets through.";
1372 };
1373
1374 };
1375
1376 });
1377
1378 };
1379
1380 networking.wlanInterfaces = mkOption {
1381 default = { };
1382 example = literalExpression ''
1383 {
1384 wlan-station0 = {
1385 device = "wlp6s0";
1386 };
1387 wlan-adhoc0 = {
1388 type = "ibss";
1389 device = "wlp6s0";
1390 mac = "02:00:00:00:00:01";
1391 };
1392 wlan-p2p0 = {
1393 device = "wlp6s0";
1394 mac = "02:00:00:00:00:02";
1395 };
1396 wlan-ap0 = {
1397 device = "wlp6s0";
1398 mac = "02:00:00:00:00:03";
1399 };
1400 }
1401 '';
1402 description = ''
1403 Creating multiple WLAN interfaces on top of one physical WLAN device (NIC).
1404
1405 The name of the WLAN interface corresponds to the name of the attribute.
1406 A NIC is referenced by the persistent device name of the WLAN interface that
1407 `udev` assigns to a NIC by default.
1408 If a NIC supports multiple WLAN interfaces, then the one NIC can be used as
1409 `device` for multiple WLAN interfaces.
1410 If a NIC is used for creating WLAN interfaces, then the default WLAN interface
1411 with a persistent device name form `udev` is not created.
1412 A WLAN interface with the persistent name assigned from `udev`
1413 would have to be created explicitly.
1414 '';
1415
1416 type =
1417 with types;
1418 attrsOf (submodule {
1419
1420 options = {
1421
1422 device = mkOption {
1423 type = types.str;
1424 example = "wlp6s0";
1425 description = "The name of the underlying hardware WLAN device as assigned by `udev`.";
1426 };
1427
1428 type = mkOption {
1429 type = types.enum [
1430 "managed"
1431 "ibss"
1432 "monitor"
1433 "mesh"
1434 "wds"
1435 ];
1436 default = "managed";
1437 example = "ibss";
1438 description = ''
1439 The type of the WLAN interface.
1440 The type has to be supported by the underlying hardware of the device.
1441 '';
1442 };
1443
1444 meshID = mkOption {
1445 type = types.nullOr types.str;
1446 default = null;
1447 description = "MeshID of interface with type `mesh`.";
1448 };
1449
1450 flags = mkOption {
1451 type =
1452 with types;
1453 nullOr (enum [
1454 "none"
1455 "fcsfail"
1456 "control"
1457 "otherbss"
1458 "cook"
1459 "active"
1460 ]);
1461 default = null;
1462 example = "control";
1463 description = ''
1464 Flags for interface of type `monitor`.
1465 '';
1466 };
1467
1468 fourAddr = mkOption {
1469 type = types.nullOr types.bool;
1470 default = null;
1471 description = "Whether to enable `4-address mode` with type `managed`.";
1472 };
1473
1474 mac = mkOption {
1475 type = types.nullOr types.str;
1476 default = null;
1477 example = "02:00:00:00:00:01";
1478 description = ''
1479 MAC address to use for the device. If `null`, then the MAC of the
1480 underlying hardware WLAN device is used.
1481
1482 INFO: Locally administered MAC addresses are of the form:
1483 - x2:xx:xx:xx:xx:xx
1484 - x6:xx:xx:xx:xx:xx
1485 - xA:xx:xx:xx:xx:xx
1486 - xE:xx:xx:xx:xx:xx
1487 '';
1488 };
1489
1490 };
1491
1492 });
1493
1494 };
1495
1496 networking.useDHCP = mkOption {
1497 type = types.bool;
1498 default = true;
1499 description = ''
1500 Whether to use DHCP to obtain an IP address and other
1501 configuration for all network interfaces that do not have any manually
1502 configured IPv4 addresses.
1503 '';
1504 };
1505
1506 networking.useNetworkd = mkOption {
1507 default = false;
1508 type = types.bool;
1509 description = ''
1510 Whether we should use networkd as the network configuration backend or
1511 the legacy script based system. Note that this option is experimental,
1512 enable at your own risk.
1513 '';
1514 };
1515
1516 networking.tempAddresses = mkOption {
1517 default = if cfg.enableIPv6 then "default" else "disabled";
1518 defaultText = literalExpression ''
1519 if ''${config.${opt.enableIPv6}} then "default" else "disabled"
1520 '';
1521 type = types.enum (lib.attrNames tempaddrValues);
1522 description = ''
1523 Whether to enable IPv6 Privacy Extensions for interfaces not
1524 configured explicitly in
1525 [](#opt-networking.interfaces._name_.tempAddress).
1526
1527 This sets the ipv6.conf.*.use_tempaddr sysctl for all
1528 interfaces. Possible values are:
1529
1530 ${tempaddrDoc}
1531 '';
1532 };
1533
1534 };
1535
1536 ###### implementation
1537
1538 config = {
1539
1540 warnings =
1541 (concatMap (i: i.warnings) interfaces)
1542 ++ (lib.optional (config.systemd.network.enable && cfg.useDHCP && !cfg.useNetworkd) ''
1543 The combination of `systemd.network.enable = true`, `networking.useDHCP = true` and `networking.useNetworkd = false` can cause both networkd and dhcpcd to manage the same interfaces. This can lead to loss of networking. It is recommended you choose only one of networkd (by also enabling `networking.useNetworkd`) or scripting (by disabling `systemd.network.enable`)
1544 '');
1545
1546 assertions =
1547 (forEach interfaces (i: {
1548 # With the linux kernel, interface name length is limited by IFNAMSIZ
1549 # to 16 bytes, including the trailing null byte.
1550 # See include/linux/if.h in the kernel sources
1551 assertion = stringLength i.name < 16;
1552 message = ''
1553 The name of networking.interfaces."${i.name}" is too long, it needs to be less than 16 characters.
1554 '';
1555 }))
1556 ++ (forEach slaveIfs (i: {
1557 assertion = i.ipv4.addresses == [ ] && i.ipv6.addresses == [ ];
1558 message = ''
1559 The networking.interfaces."${i.name}" must not have any defined ips when it is a slave.
1560 '';
1561 }))
1562 ++ (forEach interfaces (i: {
1563 assertion = i.tempAddress != "disabled" -> cfg.enableIPv6;
1564 message = ''
1565 Temporary addresses are only needed when IPv6 is enabled.
1566 '';
1567 }))
1568 ++ (forEach interfaces (i: {
1569 assertion = (i.virtual && i.virtualType == "tun") -> i.macAddress == null;
1570 message = ''
1571 Setting a MAC Address for tun device ${i.name} isn't supported.
1572 '';
1573 }))
1574 ++ [
1575 {
1576 assertion = cfg.hostId == null || (stringLength cfg.hostId == 8 && isHexString cfg.hostId);
1577 message = "Invalid value given to the networking.hostId option.";
1578 }
1579 ];
1580
1581 boot.kernelModules =
1582 [ ]
1583 ++ optional hasVirtuals "tun"
1584 ++ optional hasSits "sit"
1585 ++ optional hasGres "gre"
1586 ++ optional hasBonds "bonding"
1587 ++ optional hasFous "fou";
1588
1589 boot.extraModprobeConfig =
1590 # This setting is intentional as it prevents default bond devices
1591 # from being created.
1592 optionalString hasBonds "options bonding max_bonds=0";
1593
1594 boot.kernel.sysctl =
1595 {
1596 "net.ipv4.conf.all.forwarding" = mkDefault (any (i: i.proxyARP) interfaces);
1597 "net.ipv6.conf.all.disable_ipv6" = mkDefault (!cfg.enableIPv6);
1598 "net.ipv6.conf.default.disable_ipv6" = mkDefault (!cfg.enableIPv6);
1599 # allow all users to do ICMP echo requests (ping)
1600 "net.ipv4.ping_group_range" = mkDefault "0 2147483647";
1601 # networkmanager falls back to "/proc/sys/net/ipv6/conf/default/use_tempaddr"
1602 "net.ipv6.conf.default.use_tempaddr" = tempaddrValues.${cfg.tempAddresses}.sysctl;
1603 }
1604 // listToAttrs (
1605 forEach interfaces (
1606 i: nameValuePair "net.ipv4.conf.${replaceStrings [ "." ] [ "/" ] i.name}.proxy_arp" i.proxyARP
1607 )
1608 )
1609 // listToAttrs (
1610 forEach interfaces (
1611 i:
1612 let
1613 opt = i.tempAddress;
1614 val = tempaddrValues.${opt}.sysctl;
1615 in
1616 nameValuePair "net.ipv6.conf.${replaceStrings [ "." ] [ "/" ] i.name}.use_tempaddr" val
1617 )
1618 );
1619
1620 systemd.services.domainname = lib.mkIf (cfg.domain != null) {
1621 wantedBy = [ "sysinit.target" ];
1622 before = [
1623 "sysinit.target"
1624 "shutdown.target"
1625 ];
1626 conflicts = [ "shutdown.target" ];
1627 unitConfig.DefaultDependencies = false;
1628 serviceConfig.ExecStart = ''${pkgs.nettools}/bin/domainname "${cfg.domain}"'';
1629 serviceConfig.Type = "oneshot";
1630 };
1631
1632 environment.etc.hostid = mkIf (cfg.hostId != null) { source = hostidFile; };
1633 boot.initrd.systemd.contents."/etc/hostid" = mkIf (cfg.hostId != null) { source = hostidFile; };
1634
1635 # static hostname configuration needed for hostnamectl and the
1636 # org.freedesktop.hostname1 dbus service (both provided by systemd)
1637 environment.etc.hostname = mkIf (cfg.hostName != "") {
1638 text = cfg.hostName + "\n";
1639 };
1640
1641 environment.systemPackages =
1642 [
1643 pkgs.host
1644 pkgs.iproute2
1645 pkgs.iputils
1646 pkgs.nettools
1647 ]
1648 ++ optionals config.networking.wireless.enable [
1649 pkgs.wirelesstools # FIXME: obsolete?
1650 pkgs.iw
1651 ]
1652 ++ bridgeStp;
1653
1654 # Wake-on-LAN configuration is shared by the scripted and networkd backends.
1655 systemd.network.links = pipe interfaces [
1656 (filter (i: i.wakeOnLan.enable))
1657 (map (
1658 i:
1659 nameValuePair "40-${i.name}" {
1660 matchConfig.OriginalName = i.name;
1661 linkConfig.WakeOnLan = concatStringsSep " " i.wakeOnLan.policy;
1662 }
1663 ))
1664 listToAttrs
1665 ];
1666
1667 systemd.services = {
1668 network-local-commands = {
1669 enable = (cfg.localCommands != "");
1670 description = "Extra networking commands.";
1671 before = [ "network.target" ];
1672 wantedBy = [ "network.target" ];
1673 after = [ "network-pre.target" ];
1674 unitConfig.ConditionCapability = "CAP_NET_ADMIN";
1675 path = [ pkgs.iproute2 ];
1676 serviceConfig.Type = "oneshot";
1677 serviceConfig.RemainAfterExit = true;
1678 script = ''
1679 # Run any user-specified commands.
1680 ${cfg.localCommands}
1681 '';
1682 };
1683 };
1684 services.mstpd = mkIf needsMstpd { enable = true; };
1685
1686 virtualisation.vswitch = mkIf (cfg.vswitches != { }) { enable = true; };
1687
1688 services.udev.packages =
1689 [
1690 (pkgs.writeTextFile rec {
1691 name = "ipv6-privacy-extensions.rules";
1692 destination = "/etc/udev/rules.d/98-${name}";
1693 text =
1694 let
1695 sysctl-value = tempaddrValues.${cfg.tempAddresses}.sysctl;
1696 in
1697 ''
1698 # enable and prefer IPv6 privacy addresses by default
1699 ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.bash}/bin/sh -c 'echo ${sysctl-value} > /proc/sys/net/ipv6/conf/$name/use_tempaddr'"
1700 '';
1701 })
1702 (pkgs.writeTextFile rec {
1703 name = "ipv6-privacy-extensions.rules";
1704 destination = "/etc/udev/rules.d/99-${name}";
1705 text = concatMapStrings (
1706 i:
1707 let
1708 opt = i.tempAddress;
1709 val = tempaddrValues.${opt}.sysctl;
1710 msg = tempaddrValues.${opt}.description;
1711 in
1712 ''
1713 # override to ${msg} for ${i.name}
1714 ACTION=="add", SUBSYSTEM=="net", NAME=="${i.name}", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.${
1715 replaceStrings [ "." ] [ "/" ] i.name
1716 }.use_tempaddr=${val}"
1717 ''
1718 ) (filter (i: i.tempAddress != cfg.tempAddresses) interfaces);
1719 })
1720 ]
1721 ++ lib.optional (cfg.wlanInterfaces != { }) (
1722 pkgs.writeTextFile {
1723 name = "99-zzz-40-wlanInterfaces.rules";
1724 destination = "/etc/udev/rules.d/99-zzz-40-wlanInterfaces.rules";
1725 text =
1726 let
1727 # Collect all interfaces that are defined for a device
1728 # as device:interface key:value pairs.
1729 wlanDeviceInterfaces =
1730 let
1731 allDevices = unique (mapAttrsToList (_: v: v.device) cfg.wlanInterfaces);
1732 interfacesOfDevice = d: filterAttrs (_: v: v.device == d) cfg.wlanInterfaces;
1733 in
1734 genAttrs allDevices (d: interfacesOfDevice d);
1735
1736 # Convert device:interface key:value pairs into a list, and if it exists,
1737 # place the interface which is named after the device at the beginning.
1738 wlanListDeviceFirst =
1739 device: interfaces:
1740 if hasAttr device interfaces then
1741 mapAttrsToList (n: v: v // { _iName = n; }) (filterAttrs (n: _: n == device) interfaces)
1742 ++ mapAttrsToList (n: v: v // { _iName = n; }) (filterAttrs (n: _: n != device) interfaces)
1743 else
1744 mapAttrsToList (n: v: v // { _iName = n; }) interfaces;
1745
1746 # Udev script to execute for the default WLAN interface with the persistend udev name.
1747 # The script creates the required, new WLAN interfaces interfaces and configures the
1748 # existing, default interface.
1749 curInterfaceScript =
1750 device: current: new:
1751 pkgs.writeScript "udev-run-script-wlan-interfaces-${device}.sh" ''
1752 #!${pkgs.runtimeShell}
1753 # Change the wireless phy device to a predictable name.
1754 ${pkgs.iw}/bin/iw phy `${pkgs.coreutils}/bin/cat /sys/class/net/$INTERFACE/phy80211/name` set name ${device}
1755
1756 # Add new WLAN interfaces
1757 ${flip concatMapStrings new (i: ''
1758 ${pkgs.iw}/bin/iw phy ${device} interface add ${i._iName} type managed
1759 '')}
1760
1761 # Configure the current interface
1762 ${pkgs.iw}/bin/iw dev ${device} set type ${current.type}
1763 ${optionalString (
1764 current.type == "mesh" && current.meshID != null
1765 ) "${pkgs.iw}/bin/iw dev ${device} set meshid ${current.meshID}"}
1766 ${optionalString (
1767 current.type == "monitor" && current.flags != null
1768 ) "${pkgs.iw}/bin/iw dev ${device} set monitor ${current.flags}"}
1769 ${optionalString (
1770 current.type == "managed" && current.fourAddr != null
1771 ) "${pkgs.iw}/bin/iw dev ${device} set 4addr ${if current.fourAddr then "on" else "off"}"}
1772 ${optionalString (
1773 current.mac != null
1774 ) "${pkgs.iproute2}/bin/ip link set dev ${device} address ${current.mac}"}
1775 '';
1776
1777 # Udev script to execute for a new WLAN interface. The script configures the new WLAN interface.
1778 newInterfaceScript =
1779 new:
1780 pkgs.writeScript "udev-run-script-wlan-interfaces-${new._iName}.sh" ''
1781 #!${pkgs.runtimeShell}
1782 # Configure the new interface
1783 ${pkgs.iw}/bin/iw dev ${new._iName} set type ${new.type}
1784 ${optionalString (
1785 new.type == "mesh" && new.meshID != null
1786 ) "${pkgs.iw}/bin/iw dev ${new._iName} set meshid ${new.meshID}"}
1787 ${optionalString (
1788 new.type == "monitor" && new.flags != null
1789 ) "${pkgs.iw}/bin/iw dev ${new._iName} set monitor ${new.flags}"}
1790 ${optionalString (
1791 new.type == "managed" && new.fourAddr != null
1792 ) "${pkgs.iw}/bin/iw dev ${new._iName} set 4addr ${if new.fourAddr then "on" else "off"}"}
1793 ${optionalString (
1794 new.mac != null
1795 ) "${pkgs.iproute2}/bin/ip link set dev ${new._iName} address ${new.mac}"}
1796 '';
1797
1798 # Udev attributes for systemd to name the device and to create a .device target.
1799 systemdAttrs =
1800 n:
1801 ''NAME:="${n}", ENV{INTERFACE}="${n}", ENV{SYSTEMD_ALIAS}="/sys/subsystem/net/devices/${n}", TAG+="systemd"'';
1802 in
1803 flip (concatMapStringsSep "\n") (attrNames wlanDeviceInterfaces) (
1804 device:
1805 let
1806 interfaces = wlanListDeviceFirst device wlanDeviceInterfaces.${device};
1807 curInterface = elemAt interfaces 0;
1808 newInterfaces = drop 1 interfaces;
1809 in
1810 ''
1811 # It is important to have that rule first as overwriting the NAME attribute also prevents the
1812 # next rules from matching.
1813 ${flip (concatMapStringsSep "\n") (wlanListDeviceFirst device wlanDeviceInterfaces.${device}) (
1814 interface:
1815 ''ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", ENV{INTERFACE}=="${interface._iName}", ${systemdAttrs interface._iName}, RUN+="${newInterfaceScript interface}"''
1816 )}
1817
1818 # Add the required, new WLAN interfaces to the default WLAN interface with the
1819 # persistent, default name as assigned by udev.
1820 ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", NAME=="${device}", ${systemdAttrs curInterface._iName}, RUN+="${
1821 curInterfaceScript device curInterface newInterfaces
1822 }"
1823 # Generate the same systemd events for both 'add' and 'move' udev events.
1824 ACTION=="move", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", NAME=="${device}", ${systemdAttrs curInterface._iName}
1825 ''
1826 );
1827 }
1828 );
1829 };
1830
1831}