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