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