1{ config, lib, pkgs, utils, stdenv, ... }:
2
3with lib;
4with utils;
5
6let
7
8 cfg = config.networking;
9 interfaces = attrValues cfg.interfaces;
10 hasVirtuals = any (i: i.virtual) interfaces;
11 hasSits = cfg.sits != { };
12 hasBonds = cfg.bonds != { };
13
14 slaves = concatMap (i: i.interfaces) (attrValues cfg.bonds)
15 ++ concatMap (i: i.interfaces) (attrValues cfg.bridges)
16 ++ concatMap (i: i.interfaces) (attrValues cfg.vswitches);
17
18 slaveIfs = map (i: cfg.interfaces.${i}) (filter (i: cfg.interfaces ? ${i}) slaves);
19
20 rstpBridges = flip filterAttrs cfg.bridges (_: { rstp, ... }: rstp);
21
22 needsMstpd = rstpBridges != { };
23
24 bridgeStp = optional needsMstpd (pkgs.writeTextFile {
25 name = "bridge-stp";
26 executable = true;
27 destination = "/bin/bridge-stp";
28 text = ''
29 #!${pkgs.stdenv.shell} -e
30 export PATH="${pkgs.mstpd}/bin"
31
32 BRIDGES=(${concatStringsSep " " (attrNames rstpBridges)})
33 for BRIDGE in $BRIDGES; do
34 if [ "$BRIDGE" = "$1" ]; then
35 if [ "$2" = "start" ]; then
36 mstpctl addbridge "$BRIDGE"
37 exit 0
38 elif [ "$2" = "stop" ]; then
39 mstpctl delbridge "$BRIDGE"
40 exit 0
41 fi
42 exit 1
43 fi
44 done
45 exit 1
46 '';
47 });
48
49 # Collect all interfaces that are defined for a device
50 # as device:interface key:value pairs.
51 wlanDeviceInterfaces =
52 let
53 allDevices = unique (mapAttrsToList (_: v: v.device) cfg.wlanInterfaces);
54 interfacesOfDevice = d: filterAttrs (_: v: v.device == d) cfg.wlanInterfaces;
55 in
56 genAttrs allDevices (d: interfacesOfDevice d);
57
58 # Convert device:interface key:value pairs into a list, and if it exists,
59 # place the interface which is named after the device at the beginning.
60 wlanListDeviceFirst = device: interfaces:
61 if hasAttr device interfaces
62 then mapAttrsToList (n: v: v//{_iName=n;}) (filterAttrs (n: _: n==device) interfaces) ++ mapAttrsToList (n: v: v//{_iName=n;}) (filterAttrs (n: _: n!=device) interfaces)
63 else mapAttrsToList (n: v: v // {_iName = n;}) interfaces;
64
65 # udev script that configures a physical wlan device and adds virtual interfaces
66 wlanDeviceUdevScript = device: interfaceList: pkgs.writeScript "wlan-${device}-udev-script" ''
67 #!${pkgs.stdenv.shell}
68
69 # Change the wireless phy device to a predictable name.
70 if [ -e "/sys/class/net/${device}/phy80211/name" ]; then
71 ${pkgs.iw}/bin/iw phy `${pkgs.coreutils}/bin/cat /sys/class/net/${device}/phy80211/name` set name ${device} || true
72 fi
73
74 # Crate new, virtual interfaces and configure them at the same time
75 ${flip concatMapStrings (drop 1 interfaceList) (i: ''
76 ${pkgs.iw}/bin/iw dev ${device} interface add ${i._iName} type ${i.type} \
77 ${optionalString (i.type == "mesh" && i.meshID != null) "mesh_id ${i.meshID}"} \
78 ${optionalString (i.type == "monitor" && i.flags != null) "flags ${i.flags}"} \
79 ${optionalString (i.type == "managed" && i.fourAddr != null) "4addr ${if i.fourAddr then "on" else "off"}"} \
80 ${optionalString (i.mac != null) "addr ${i.mac}"}
81 '')}
82
83 # Reconfigure and rename the default interface that already exists
84 ${flip concatMapStrings (take 1 interfaceList) (i: ''
85 ${pkgs.iw}/bin/iw dev ${device} set type ${i.type}
86 ${optionalString (i.type == "mesh" && i.meshID != null) "${pkgs.iw}/bin/iw dev ${device} set meshid ${i.meshID}"}
87 ${optionalString (i.type == "monitor" && i.flags != null) "${pkgs.iw}/bin/iw dev ${device} set monitor ${i.flags}"}
88 ${optionalString (i.type == "managed" && i.fourAddr != null) "${pkgs.iw}/bin/iw dev ${device} set 4addr ${if i.fourAddr then "on" else "off"}"}
89 ${optionalString (i.mac != null) "${pkgs.iproute}/bin/ip link set dev ${device} address ${i.mac}"}
90 ${optionalString (device != i._iName) "${pkgs.iproute}/bin/ip link set dev ${device} name ${i._iName}"}
91 '')}
92 '';
93
94 # We must escape interfaces due to the systemd interpretation
95 subsystemDevice = interface:
96 "sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
97
98 addrOpts = v:
99 assert v == 4 || v == 6;
100 { options = {
101 address = mkOption {
102 type = types.str;
103 description = ''
104 IPv${toString v} address of the interface. Leave empty to configure the
105 interface using DHCP.
106 '';
107 };
108
109 prefixLength = mkOption {
110 type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
111 description = ''
112 Subnet mask of the interface, specified as the number of
113 bits in the prefix (<literal>${if v == 4 then "24" else "64"}</literal>).
114 '';
115 };
116 };
117 };
118
119 gatewayCoerce = address: { inherit address; };
120
121 gatewayOpts = { ... }: {
122
123 options = {
124
125 address = mkOption {
126 type = types.str;
127 description = "The default gateway address.";
128 };
129
130 interface = mkOption {
131 type = types.nullOr types.str;
132 default = null;
133 example = "enp0s3";
134 description = "The default gateway interface.";
135 };
136
137 metric = mkOption {
138 type = types.nullOr types.int;
139 default = null;
140 example = 42;
141 description = "The default gateway metric/preference.";
142 };
143
144 };
145
146 };
147
148 interfaceOpts = { name, ... }: {
149
150 options = {
151
152 name = mkOption {
153 example = "eth0";
154 type = types.str;
155 description = "Name of the interface.";
156 };
157
158 useDHCP = mkOption {
159 type = types.nullOr types.bool;
160 default = null;
161 description = ''
162 Whether this interface should be configured with dhcp.
163 Null implies the old behavior which depends on whether ip addresses
164 are specified or not.
165 '';
166 };
167
168 ip4 = mkOption {
169 default = [ ];
170 example = [
171 { address = "10.0.0.1"; prefixLength = 16; }
172 { address = "192.168.1.1"; prefixLength = 24; }
173 ];
174 type = with types; listOf (submodule (addrOpts 4));
175 description = ''
176 List of IPv4 addresses that will be statically assigned to the interface.
177 '';
178 };
179
180 ip6 = mkOption {
181 default = [ ];
182 example = [
183 { address = "fdfd:b3f0:482::1"; prefixLength = 48; }
184 { address = "2001:1470:fffd:2098::e006"; prefixLength = 64; }
185 ];
186 type = with types; listOf (submodule (addrOpts 6));
187 description = ''
188 List of IPv6 addresses that will be statically assigned to the interface.
189 '';
190 };
191
192 ipAddress = mkOption {
193 default = null;
194 example = "10.0.0.1";
195 type = types.nullOr types.str;
196 description = ''
197 IP address of the interface. Leave empty to configure the
198 interface using DHCP.
199 '';
200 };
201
202 prefixLength = mkOption {
203 default = null;
204 example = 24;
205 type = types.nullOr types.int;
206 description = ''
207 Subnet mask of the interface, specified as the number of
208 bits in the prefix (<literal>24</literal>).
209 '';
210 };
211
212 subnetMask = mkOption {
213 default = null;
214 description = ''
215 Defunct, supply the prefix length instead.
216 '';
217 };
218
219 ipv6Address = mkOption {
220 default = null;
221 example = "2001:1470:fffd:2098::e006";
222 type = types.nullOr types.str;
223 description = ''
224 IPv6 address of the interface. Leave empty to configure the
225 interface using NDP.
226 '';
227 };
228
229 ipv6PrefixLength = mkOption {
230 default = 64;
231 example = 64;
232 type = types.int;
233 description = ''
234 Subnet mask of the interface, specified as the number of
235 bits in the prefix (<literal>64</literal>).
236 '';
237 };
238
239 macAddress = mkOption {
240 default = null;
241 example = "00:11:22:33:44:55";
242 type = types.nullOr (types.str);
243 description = ''
244 MAC address of the interface. Leave empty to use the default.
245 '';
246 };
247
248 mtu = mkOption {
249 default = null;
250 example = 9000;
251 type = types.nullOr types.int;
252 description = ''
253 MTU size for packets leaving the interface. Leave empty to use the default.
254 '';
255 };
256
257 virtual = mkOption {
258 default = false;
259 type = types.bool;
260 description = ''
261 Whether this interface is virtual and should be created by tunctl.
262 This is mainly useful for creating bridges between a host and a virtual
263 network such as VPN or a virtual machine.
264 '';
265 };
266
267 virtualOwner = mkOption {
268 default = "root";
269 type = types.str;
270 description = ''
271 In case of a virtual device, the user who owns it.
272 '';
273 };
274
275 virtualType = mkOption {
276 default = null;
277 type = with types; nullOr (enum [ "tun" "tap" ]);
278 description = ''
279 The explicit type of interface to create. Accepts tun or tap strings.
280 Also accepts null to implicitly detect the type of device.
281 '';
282 };
283
284 proxyARP = mkOption {
285 default = false;
286 type = types.bool;
287 description = ''
288 Turn on proxy_arp for this device (and proxy_ndp for ipv6).
289 This is mainly useful for creating pseudo-bridges between a real
290 interface and a virtual network such as VPN or a virtual machine for
291 interfaces that don't support real bridging (most wlan interfaces).
292 As ARP proxying acts slightly above the link-layer, below-ip traffic
293 isn't bridged, so things like DHCP won't work. The advantage above
294 using NAT lies in the fact that no IP addresses are shared, so all
295 hosts are reachable/routeable.
296
297 WARNING: turns on ip-routing, so if you have multiple interfaces, you
298 should think of the consequence and setup firewall rules to limit this.
299 '';
300 };
301
302 };
303
304 config = {
305 name = mkDefault name;
306 };
307
308 };
309
310 hexChars = stringToCharacters "0123456789abcdef";
311
312 isHexString = s: all (c: elem c hexChars) (stringToCharacters (toLower s));
313
314in
315
316{
317
318 ###### interface
319
320 options = {
321
322 networking.hostName = mkOption {
323 default = "nixos";
324 type = types.str;
325 description = ''
326 The name of the machine. Leave it empty if you want to obtain
327 it from a DHCP server (if using DHCP).
328 '';
329 };
330
331 networking.hostId = mkOption {
332 default = null;
333 example = "4e98920d";
334 type = types.nullOr types.str;
335 description = ''
336 The 32-bit host ID of the machine, formatted as 8 hexadecimal characters.
337
338 You should try to make this ID unique among your machines. You can
339 generate a random 32-bit ID using the following commands:
340
341 <literal>cksum /etc/machine-id | while read c rest; do printf "%x" $c; done</literal>
342
343 (this derives it from the machine-id that systemd generates) or
344
345 <literal>head -c4 /dev/urandom | od -A none -t x4</literal>
346 '';
347 };
348
349 networking.enableIPv6 = mkOption {
350 default = true;
351 type = types.bool;
352 description = ''
353 Whether to enable support for IPv6.
354 '';
355 };
356
357 networking.defaultGateway = mkOption {
358 default = null;
359 example = {
360 address = "131.211.84.1";
361 interface = "enp3s0";
362 };
363 type = types.nullOr (types.coercedTo types.str gatewayCoerce (types.submodule gatewayOpts));
364 description = ''
365 The default gateway. It can be left empty if it is auto-detected through DHCP.
366 It can be specified as a string or an option set along with a network interface.
367 '';
368 };
369
370 networking.defaultGateway6 = mkOption {
371 default = null;
372 example = {
373 address = "2001:4d0:1e04:895::1";
374 interface = "enp3s0";
375 };
376 type = types.nullOr (types.coercedTo types.str gatewayCoerce (types.submodule gatewayOpts));
377 description = ''
378 The default ipv6 gateway. It can be left empty if it is auto-detected through DHCP.
379 It can be specified as a string or an option set along with a network interface.
380 '';
381 };
382
383 networking.defaultGatewayWindowSize = mkOption {
384 default = null;
385 example = 524288;
386 type = types.nullOr types.int;
387 description = ''
388 The window size of the default gateway. It limits maximal data bursts that TCP peers
389 are allowed to send to us.
390 '';
391 };
392
393 networking.nameservers = mkOption {
394 type = types.listOf types.str;
395 default = [];
396 example = ["130.161.158.4" "130.161.33.17"];
397 description = ''
398 The list of nameservers. It can be left empty if it is auto-detected through DHCP.
399 '';
400 };
401
402 networking.search = mkOption {
403 default = [];
404 example = [ "example.com" "local.domain" ];
405 type = types.listOf types.str;
406 description = ''
407 The list of search paths used when resolving domain names.
408 '';
409 };
410
411 networking.domain = mkOption {
412 default = null;
413 example = "home";
414 type = types.nullOr types.str;
415 description = ''
416 The domain. It can be left empty if it is auto-detected through DHCP.
417 '';
418 };
419
420 networking.useHostResolvConf = mkOption {
421 type = types.bool;
422 default = false;
423 description = ''
424 In containers, whether to use the
425 <filename>resolv.conf</filename> supplied by the host.
426 '';
427 };
428
429 networking.localCommands = mkOption {
430 type = types.lines;
431 default = "";
432 example = "text=anything; echo You can put $text here.";
433 description = ''
434 Shell commands to be executed at the end of the
435 <literal>network-setup</literal> systemd service. Note that if
436 you are using DHCP to obtain the network configuration,
437 interfaces may not be fully configured yet.
438 '';
439 };
440
441 networking.interfaces = mkOption {
442 default = {};
443 example =
444 { eth0.ip4 = [ {
445 address = "131.211.84.78";
446 prefixLength = 25;
447 } ];
448 };
449 description = ''
450 The configuration for each network interface. If
451 <option>networking.useDHCP</option> is true, then every
452 interface not listed here will be configured using DHCP.
453 '';
454 type = with types; loaOf (submodule interfaceOpts);
455 };
456
457 networking.vswitches = mkOption {
458 default = { };
459 example =
460 { vs0.interfaces = [ "eth0" "eth1" ];
461 vs1.interfaces = [ "eth2" "wlan0" ];
462 };
463 description =
464 ''
465 This option allows you to define Open vSwitches that connect
466 physical networks together. The value of this option is an
467 attribute set. Each attribute specifies a vswitch, with the
468 attribute name specifying the name of the vswitch's network
469 interface.
470 '';
471
472 type = with types; attrsOf (submodule {
473
474 options = {
475
476 interfaces = mkOption {
477 example = [ "eth0" "eth1" ];
478 type = types.listOf types.str;
479 description =
480 "The physical network interfaces connected by the vSwitch.";
481 };
482
483 controllers = mkOption {
484 type = types.listOf types.str;
485 default = [];
486 example = [ "ptcp:6653:[::1]" ];
487 description = ''
488 Specify the controller targets. For the allowed options see <literal>man 8 ovs-vsctl</literal>.
489 '';
490 };
491
492 openFlowRules = mkOption {
493 type = types.lines;
494 default = "";
495 example = ''
496 actions=normal
497 '';
498 description = ''
499 OpenFlow rules to insert into the Open vSwitch. All <literal>openFlowRules</literal> are
500 loaded with <literal>ovs-ofctl</literal> within one atomic operation.
501 '';
502 };
503
504 extraOvsctlCmds = mkOption {
505 type = types.lines;
506 default = "";
507 example = ''
508 set-fail-mode <switch_name> secure
509 set Bridge <switch_name> stp_enable=true
510 '';
511 description = ''
512 Commands to manipulate the Open vSwitch database. Every line executed with <literal>ovs-vsctl</literal>.
513 All commands are bundled together with the operations for adding the interfaces
514 into one atomic operation.
515 '';
516 };
517
518 };
519
520 });
521
522 };
523
524 networking.bridges = mkOption {
525 default = { };
526 example =
527 { br0.interfaces = [ "eth0" "eth1" ];
528 br1.interfaces = [ "eth2" "wlan0" ];
529 };
530 description =
531 ''
532 This option allows you to define Ethernet bridge devices
533 that connect physical networks together. The value of this
534 option is an attribute set. Each attribute specifies a
535 bridge, with the attribute name specifying the name of the
536 bridge's network interface.
537 '';
538
539 type = with types; attrsOf (submodule {
540
541 options = {
542
543 interfaces = mkOption {
544 example = [ "eth0" "eth1" ];
545 type = types.listOf types.str;
546 description =
547 "The physical network interfaces connected by the bridge.";
548 };
549
550 rstp = mkOption {
551 default = false;
552 type = types.bool;
553 description = "Whether the bridge interface should enable rstp.";
554 };
555
556 };
557
558 });
559
560 };
561
562 networking.bonds =
563 let
564 driverOptionsExample = {
565 miimon = "100";
566 mode = "active-backup";
567 };
568 in mkOption {
569 default = { };
570 example = literalExample {
571 bond0 = {
572 interfaces = [ "eth0" "wlan0" ];
573 driverOptions = driverOptionsExample;
574 };
575 anotherBond.interfaces = [ "enp4s0f0" "enp4s0f1" "enp5s0f0" "enp5s0f1" ];
576 };
577 description = ''
578 This option allows you to define bond devices that aggregate multiple,
579 underlying networking interfaces together. The value of this option is
580 an attribute set. Each attribute specifies a bond, with the attribute
581 name specifying the name of the bond's network interface
582 '';
583
584 type = with types; attrsOf (submodule {
585
586 options = {
587
588 interfaces = mkOption {
589 example = [ "enp4s0f0" "enp4s0f1" "wlan0" ];
590 type = types.listOf types.str;
591 description = "The interfaces to bond together";
592 };
593
594 driverOptions = mkOption {
595 type = types.attrsOf types.str;
596 default = {};
597 example = literalExample driverOptionsExample;
598 description = ''
599 Options for the bonding driver.
600 Documentation can be found in
601 <link xlink:href="https://www.kernel.org/doc/Documentation/networking/bonding.txt" />
602 '';
603
604 };
605
606 lacp_rate = mkOption {
607 default = null;
608 example = "fast";
609 type = types.nullOr types.str;
610 description = ''
611 DEPRECATED, use `driverOptions`.
612 Option specifying the rate in which we'll ask our link partner
613 to transmit LACPDU packets in 802.3ad mode.
614 '';
615 };
616
617 miimon = mkOption {
618 default = null;
619 example = 100;
620 type = types.nullOr types.int;
621 description = ''
622 DEPRECATED, use `driverOptions`.
623 Miimon is the number of millisecond in between each round of polling
624 by the device driver for failed links. By default polling is not
625 enabled and the driver is trusted to properly detect and handle
626 failure scenarios.
627 '';
628 };
629
630 mode = mkOption {
631 default = null;
632 example = "active-backup";
633 type = types.nullOr types.str;
634 description = ''
635 DEPRECATED, use `driverOptions`.
636 The mode which the bond will be running. The default mode for
637 the bonding driver is balance-rr, optimizing for throughput.
638 More information about valid modes can be found at
639 https://www.kernel.org/doc/Documentation/networking/bonding.txt
640 '';
641 };
642
643 xmit_hash_policy = mkOption {
644 default = null;
645 example = "layer2+3";
646 type = types.nullOr types.str;
647 description = ''
648 DEPRECATED, use `driverOptions`.
649 Selects the transmit hash policy to use for slave selection in
650 balance-xor, 802.3ad, and tlb modes.
651 '';
652 };
653
654 };
655
656 });
657 };
658
659 networking.macvlans = mkOption {
660 default = { };
661 example = literalExample {
662 wan = {
663 interface = "enp2s0";
664 mode = "vepa";
665 };
666 };
667 description = ''
668 This option allows you to define macvlan interfaces which should
669 be automatically created.
670 '';
671 type = with types; attrsOf (submodule {
672 options = {
673
674 interface = mkOption {
675 example = "enp4s0";
676 type = types.str;
677 description = "The interface the macvlan will transmit packets through.";
678 };
679
680 mode = mkOption {
681 default = null;
682 type = types.nullOr types.str;
683 example = "vepa";
684 description = "The mode of the macvlan device.";
685 };
686
687 };
688
689 });
690 };
691
692 networking.sits = mkOption {
693 default = { };
694 example = literalExample {
695 hurricane = {
696 remote = "10.0.0.1";
697 local = "10.0.0.22";
698 ttl = 255;
699 };
700 msipv6 = {
701 remote = "192.168.0.1";
702 dev = "enp3s0";
703 ttl = 127;
704 };
705 };
706 description = ''
707 This option allows you to define 6-to-4 interfaces which should be automatically created.
708 '';
709 type = with types; attrsOf (submodule {
710 options = {
711
712 remote = mkOption {
713 type = types.nullOr types.str;
714 default = null;
715 example = "10.0.0.1";
716 description = ''
717 The address of the remote endpoint to forward traffic over.
718 '';
719 };
720
721 local = mkOption {
722 type = types.nullOr types.str;
723 default = null;
724 example = "10.0.0.22";
725 description = ''
726 The address of the local endpoint which the remote
727 side should send packets to.
728 '';
729 };
730
731 ttl = mkOption {
732 type = types.nullOr types.int;
733 default = null;
734 example = 255;
735 description = ''
736 The time-to-live of the connection to the remote tunnel endpoint.
737 '';
738 };
739
740 dev = mkOption {
741 type = types.nullOr types.str;
742 default = null;
743 example = "enp4s0f0";
744 description = ''
745 The underlying network device on which the tunnel resides.
746 '';
747 };
748
749 };
750
751 });
752 };
753
754 networking.vlans = mkOption {
755 default = { };
756 example = literalExample {
757 vlan0 = {
758 id = 3;
759 interface = "enp3s0";
760 };
761 vlan1 = {
762 id = 1;
763 interface = "wlan0";
764 };
765 };
766 description =
767 ''
768 This option allows you to define vlan devices that tag packets
769 on top of a physical interface. The value of this option is an
770 attribute set. Each attribute specifies a vlan, with the name
771 specifying the name of the vlan interface.
772 '';
773
774 type = with types; attrsOf (submodule {
775
776 options = {
777
778 id = mkOption {
779 example = 1;
780 type = types.int;
781 description = "The vlan identifier";
782 };
783
784 interface = mkOption {
785 example = "enp4s0";
786 type = types.str;
787 description = "The interface the vlan will transmit packets through.";
788 };
789
790 };
791
792 });
793
794 };
795
796 networking.wlanInterfaces = mkOption {
797 default = { };
798 example = literalExample {
799 "wlan-station0" = {
800 device = "wlp6s0";
801 };
802 "wlan-adhoc0" = {
803 type = "ibss";
804 device = "wlp6s0";
805 mac = "02:00:00:00:00:01";
806 };
807 "wlan-p2p0" = {
808 device = "wlp6s0";
809 mac = "02:00:00:00:00:02";
810 };
811 "wlan-ap0" = {
812 device = "wlp6s0";
813 mac = "02:00:00:00:00:03";
814 };
815 };
816 description =
817 ''
818 Creating multiple WLAN interfaces on top of one physical WLAN device (NIC).
819
820 The name of the WLAN interface corresponds to the name of the attribute.
821 A NIC is referenced by the persistent device name of the WLAN interface that
822 <literal>udev</literal> assigns to a NIC by default.
823 If a NIC supports multiple WLAN interfaces, then the one NIC can be used as
824 <literal>device</literal> for multiple WLAN interfaces.
825 If a NIC is used for creating WLAN interfaces, then the default WLAN interface
826 with a persistent device name form <literal>udev</literal> is not created.
827 A WLAN interface with the persistent name assigned from <literal>udev</literal>
828 would have to be created explicitly.
829 '';
830
831 type = with types; attrsOf (submodule {
832
833 options = {
834
835 device = mkOption {
836 type = types.string;
837 example = "wlp6s0";
838 description = "The name of the underlying hardware WLAN device as assigned by <literal>udev</literal>.";
839 };
840
841 type = mkOption {
842 type = types.enum [ "managed" "ibss" "monitor" "mesh" "wds" ];
843 default = "managed";
844 example = "ibss";
845 description = ''
846 The type of the WLAN interface.
847 The type has to be supported by the underlying hardware of the device.
848 '';
849 };
850
851 meshID = mkOption {
852 type = types.nullOr types.string;
853 default = null;
854 description = "MeshID of interface with type <literal>mesh</literal>.";
855 };
856
857 flags = mkOption {
858 type = with types; nullOr (enum [ "none" "fcsfail" "control" "otherbss" "cook" "active" ]);
859 default = null;
860 example = "control";
861 description = ''
862 Flags for interface of type <literal>monitor</literal>.
863 '';
864 };
865
866 fourAddr = mkOption {
867 type = types.nullOr types.bool;
868 default = null;
869 description = "Whether to enable <literal>4-address mode</literal> with type <literal>managed</literal>.";
870 };
871
872 mac = mkOption {
873 type = types.nullOr types.str;
874 default = null;
875 example = "02:00:00:00:00:01";
876 description = ''
877 MAC address to use for the device. If <literal>null</literal>, then the MAC of the
878 underlying hardware WLAN device is used.
879
880 INFO: Locally administered MAC addresses are of the form:
881 <itemizedlist>
882 <listitem><para>x2:xx:xx:xx:xx:xx</para></listitem>
883 <listitem><para>x6:xx:xx:xx:xx:xx</para></listitem>
884 <listitem><para>xA:xx:xx:xx:xx:xx</para></listitem>
885 <listitem><para>xE:xx:xx:xx:xx:xx</para></listitem>
886 </itemizedlist>
887 '';
888 };
889
890 };
891
892 });
893
894 };
895
896 networking.useDHCP = mkOption {
897 type = types.bool;
898 default = true;
899 description = ''
900 Whether to use DHCP to obtain an IP address and other
901 configuration for all network interfaces that are not manually
902 configured.
903 '';
904 };
905
906 networking.useNetworkd = mkOption {
907 default = false;
908 type = types.bool;
909 description = ''
910 Whether we should use networkd as the network configuration backend or
911 the legacy script based system. Note that this option is experimental,
912 enable at your own risk.
913 '';
914 };
915
916 };
917
918
919 ###### implementation
920
921 config = {
922
923 assertions =
924 (flip map interfaces (i: {
925 assertion = i.subnetMask == null;
926 message = "The networking.interfaces.${i.name}.subnetMask option is defunct. Use prefixLength instead.";
927 })) ++ (flip map slaveIfs (i: {
928 assertion = i.ip4 == [ ] && i.ipAddress == null && i.ip6 == [ ] && i.ipv6Address == null;
929 message = "The networking.interfaces.${i.name} must not have any defined ips when it is a slave.";
930 })) ++ [
931 {
932 assertion = cfg.hostId == null || (stringLength cfg.hostId == 8 && isHexString cfg.hostId);
933 message = "Invalid value given to the networking.hostId option.";
934 }
935 ];
936
937 boot.kernelModules = [ ]
938 ++ optional cfg.enableIPv6 "ipv6"
939 ++ optional hasVirtuals "tun"
940 ++ optional hasSits "sit"
941 ++ optional hasBonds "bonding";
942
943 boot.extraModprobeConfig =
944 # This setting is intentional as it prevents default bond devices
945 # from being created.
946 optionalString hasBonds "options bonding max_bonds=0";
947
948 boot.kernel.sysctl = {
949 "net.ipv6.conf.all.disable_ipv6" = mkDefault (!cfg.enableIPv6);
950 "net.ipv6.conf.default.disable_ipv6" = mkDefault (!cfg.enableIPv6);
951 "net.ipv6.conf.all.forwarding" = mkDefault (any (i: i.proxyARP) interfaces);
952 } // listToAttrs (concatLists (flip map (filter (i: i.proxyARP) interfaces)
953 (i: flip map [ "4" "6" ] (v: nameValuePair "net.ipv${v}.conf.${i.name}.proxy_arp" true))
954 ));
955
956 # Capabilities won't work unless we have at-least a 4.3 Linux
957 # kernel because we need the ambient capability
958 security.wrappers = if (versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.3") then {
959 ping = {
960 source = "${pkgs.iputils.out}/bin/ping";
961 capabilities = "cap_net_raw+p";
962 };
963 } else {
964 ping.source = "${pkgs.iputils.out}/bin/ping";
965 };
966
967 # Set the host and domain names in the activation script. Don't
968 # clear it if it's not configured in the NixOS configuration,
969 # since it may have been set by dhcpcd in the meantime.
970 system.activationScripts.hostname =
971 optionalString (cfg.hostName != "") ''
972 hostname "${cfg.hostName}"
973 '';
974 system.activationScripts.domain =
975 optionalString (cfg.domain != null) ''
976 domainname "${cfg.domain}"
977 '';
978
979 environment.etc."hostid" = mkIf (cfg.hostId != null)
980 { source = pkgs.runCommand "gen-hostid" {} ''
981 hi="${cfg.hostId}"
982 ${if pkgs.stdenv.isBigEndian then ''
983 echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > $out
984 '' else ''
985 echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > $out
986 ''}
987 '';
988 };
989
990 # static hostname configuration needed for hostnamectl and the
991 # org.freedesktop.hostname1 dbus service (both provided by systemd)
992 environment.etc."hostname" = mkIf (cfg.hostName != "")
993 {
994 text = cfg.hostName + "\n";
995 };
996
997 environment.systemPackages =
998 [ pkgs.host
999 pkgs.iproute
1000 pkgs.iputils
1001 pkgs.nettools
1002 pkgs.openresolv
1003 ]
1004 ++ optionals config.networking.wireless.enable [
1005 pkgs.wirelesstools # FIXME: obsolete?
1006 pkgs.iw
1007 pkgs.rfkill
1008 ]
1009 ++ bridgeStp;
1010
1011 # The network-interfaces target is kept for backwards compatibility.
1012 # New modules must NOT use it.
1013 systemd.targets."network-interfaces" =
1014 { description = "All Network Interfaces (deprecated)";
1015 wantedBy = [ "network.target" ];
1016 before = [ "network.target" ];
1017 after = [ "network-pre.target" ];
1018 unitConfig.X-StopOnReconfiguration = true;
1019 };
1020
1021 systemd.services = {
1022 network-local-commands = {
1023 description = "Extra networking commands.";
1024 before = [ "network.target" ];
1025 wantedBy = [ "network.target" ];
1026 after = [ "network-pre.target" ];
1027 unitConfig.ConditionCapability = "CAP_NET_ADMIN";
1028 path = [ pkgs.iproute ];
1029 serviceConfig.Type = "oneshot";
1030 serviceConfig.RemainAfterExit = true;
1031 script = ''
1032 # Run any user-specified commands.
1033 ${cfg.localCommands}
1034 '';
1035 };
1036 } // (listToAttrs (flip map interfaces (i:
1037 let
1038 deviceDependency = if config.boot.isContainer
1039 then []
1040 else [ (subsystemDevice i.name) ];
1041 in
1042 nameValuePair "network-link-${i.name}"
1043 { description = "Link configuration of ${i.name}";
1044 wantedBy = [ "network-interfaces.target" ];
1045 before = [ "network-interfaces.target" ];
1046 bindsTo = deviceDependency;
1047 after = [ "network-pre.target" ] ++ deviceDependency;
1048 path = [ pkgs.iproute ];
1049 serviceConfig = {
1050 Type = "oneshot";
1051 RemainAfterExit = true;
1052 };
1053 script =
1054 ''
1055 echo "Configuring link..."
1056 '' + optionalString (i.macAddress != null) ''
1057 echo "setting MAC address to ${i.macAddress}..."
1058 ip link set "${i.name}" address "${i.macAddress}"
1059 '' + optionalString (i.mtu != null) ''
1060 echo "setting MTU to ${toString i.mtu}..."
1061 ip link set "${i.name}" mtu "${toString i.mtu}"
1062 '';
1063 })));
1064
1065 services.mstpd = mkIf needsMstpd { enable = true; };
1066
1067 virtualisation.vswitch = mkIf (cfg.vswitches != { }) { enable = true; };
1068
1069 services.udev.packages = mkIf (cfg.wlanInterfaces != {}) [
1070 (pkgs.writeTextFile {
1071 name = "99-zzz-40-wlanInterfaces.rules";
1072 destination = "/etc/udev/rules.d/99-zzz-40-wlanInterfaces.rules";
1073 text =
1074 let
1075 # Collect all interfaces that are defined for a device
1076 # as device:interface key:value pairs.
1077 wlanDeviceInterfaces =
1078 let
1079 allDevices = unique (mapAttrsToList (_: v: v.device) cfg.wlanInterfaces);
1080 interfacesOfDevice = d: filterAttrs (_: v: v.device == d) cfg.wlanInterfaces;
1081 in
1082 genAttrs allDevices (d: interfacesOfDevice d);
1083
1084 # Convert device:interface key:value pairs into a list, and if it exists,
1085 # place the interface which is named after the device at the beginning.
1086 wlanListDeviceFirst = device: interfaces:
1087 if hasAttr device interfaces
1088 then mapAttrsToList (n: v: v//{_iName=n;}) (filterAttrs (n: _: n==device) interfaces) ++ mapAttrsToList (n: v: v//{_iName=n;}) (filterAttrs (n: _: n!=device) interfaces)
1089 else mapAttrsToList (n: v: v // {_iName = n;}) interfaces;
1090
1091 # Udev script to execute for the default WLAN interface with the persistend udev name.
1092 # The script creates the required, new WLAN interfaces interfaces and configures the
1093 # existing, default interface.
1094 curInterfaceScript = device: current: new: pkgs.writeScript "udev-run-script-wlan-interfaces-${device}.sh" ''
1095 #!${pkgs.stdenv.shell}
1096 # Change the wireless phy device to a predictable name.
1097 ${pkgs.iw}/bin/iw phy `${pkgs.coreutils}/bin/cat /sys/class/net/$INTERFACE/phy80211/name` set name ${device}
1098
1099 # Add new WLAN interfaces
1100 ${flip concatMapStrings new (i: ''
1101 ${pkgs.iw}/bin/iw phy ${device} interface add ${i._iName} type managed
1102 '')}
1103
1104 # Configure the current interface
1105 ${pkgs.iw}/bin/iw dev ${device} set type ${current.type}
1106 ${optionalString (current.type == "mesh" && current.meshID!=null) "${pkgs.iw}/bin/iw dev ${device} set meshid ${current.meshID}"}
1107 ${optionalString (current.type == "monitor" && current.flags!=null) "${pkgs.iw}/bin/iw dev ${device} set monitor ${current.flags}"}
1108 ${optionalString (current.type == "managed" && current.fourAddr!=null) "${pkgs.iw}/bin/iw dev ${device} set 4addr ${if current.fourAddr then "on" else "off"}"}
1109 ${optionalString (current.mac != null) "${pkgs.iproute}/bin/ip link set dev ${device} address ${current.mac}"}
1110 '';
1111
1112 # Udev script to execute for a new WLAN interface. The script configures the new WLAN interface.
1113 newInterfaceScript = device: new: pkgs.writeScript "udev-run-script-wlan-interfaces-${new._iName}.sh" ''
1114 #!${pkgs.stdenv.shell}
1115 # Configure the new interface
1116 ${pkgs.iw}/bin/iw dev ${new._iName} set type ${new.type}
1117 ${optionalString (new.type == "mesh" && new.meshID!=null) "${pkgs.iw}/bin/iw dev ${device} set meshid ${new.meshID}"}
1118 ${optionalString (new.type == "monitor" && new.flags!=null) "${pkgs.iw}/bin/iw dev ${device} set monitor ${new.flags}"}
1119 ${optionalString (new.type == "managed" && new.fourAddr!=null) "${pkgs.iw}/bin/iw dev ${device} set 4addr ${if new.fourAddr then "on" else "off"}"}
1120 ${optionalString (new.mac != null) "${pkgs.iproute}/bin/ip link set dev ${device} address ${new.mac}"}
1121 '';
1122
1123 # Udev attributes for systemd to name the device and to create a .device target.
1124 systemdAttrs = n: ''NAME:="${n}", ENV{INTERFACE}:="${n}", ENV{SYSTEMD_ALIAS}:="/sys/subsystem/net/devices/${n}", TAG+="systemd"'';
1125 in
1126 flip (concatMapStringsSep "\n") (attrNames wlanDeviceInterfaces) (device:
1127 let
1128 interfaces = wlanListDeviceFirst device wlanDeviceInterfaces."${device}";
1129 curInterface = elemAt interfaces 0;
1130 newInterfaces = drop 1 interfaces;
1131 in ''
1132 # It is important to have that rule first as overwriting the NAME attribute also prevents the
1133 # next rules from matching.
1134 ${flip (concatMapStringsSep "\n") (wlanListDeviceFirst device wlanDeviceInterfaces."${device}") (interface:
1135 ''ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", ENV{INTERFACE}=="${interface._iName}", ${systemdAttrs interface._iName}, RUN+="${newInterfaceScript device interface}"'')}
1136
1137 # Add the required, new WLAN interfaces to the default WLAN interface with the
1138 # persistent, default name as assigned by udev.
1139 ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", NAME=="${device}", ${systemdAttrs curInterface._iName}, RUN+="${curInterfaceScript device curInterface newInterfaces}"
1140 # Generate the same systemd events for both 'add' and 'move' udev events.
1141 ACTION=="move", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", NAME=="${device}", ${systemdAttrs curInterface._iName}
1142 '');
1143 }) ];
1144
1145 };
1146
1147}