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
17 slaveIfs = map (i: cfg.interfaces.${i}) (filter (i: cfg.interfaces ? ${i}) slaves);
18
19 rstpBridges = flip filterAttrs cfg.bridges (_: { rstp, ... }: rstp);
20
21 needsMstpd = rstpBridges != { };
22
23 bridgeStp = optional needsMstpd (pkgs.writeTextFile {
24 name = "bridge-stp";
25 executable = true;
26 destination = "/bin/bridge-stp";
27 text = ''
28 #!${pkgs.stdenv.shell} -e
29 export PATH="${pkgs.mstpd}/bin"
30
31 BRIDGES=(${concatStringsSep " " (attrNames rstpBridges)})
32 for BRIDGE in $BRIDGES; do
33 if [ "$BRIDGE" = "$1" ]; then
34 if [ "$2" = "start" ]; then
35 mstpctl addbridge "$BRIDGE"
36 exit 0
37 elif [ "$2" = "stop" ]; then
38 mstpctl delbridge "$BRIDGE"
39 exit 0
40 fi
41 exit 1
42 fi
43 done
44 exit 1
45 '';
46 });
47
48 # We must escape interfaces due to the systemd interpretation
49 subsystemDevice = interface:
50 "sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
51
52 addrOpts = v:
53 assert v == 4 || v == 6;
54 {
55 address = mkOption {
56 type = types.str;
57 description = ''
58 IPv${toString v} address of the interface. Leave empty to configure the
59 interface using DHCP.
60 '';
61 };
62
63 prefixLength = mkOption {
64 type = types.addCheck types.int (n: n >= 0 && n <= (if v == 4 then 32 else 128));
65 description = ''
66 Subnet mask of the interface, specified as the number of
67 bits in the prefix (<literal>${if v == 4 then "24" else "64"}</literal>).
68 '';
69 };
70 };
71
72 interfaceOpts = { name, ... }: {
73
74 options = {
75
76 name = mkOption {
77 example = "eth0";
78 type = types.str;
79 description = "Name of the interface.";
80 };
81
82 useDHCP = mkOption {
83 type = types.nullOr types.bool;
84 default = null;
85 description = ''
86 Whether this interface should be configured with dhcp.
87 Null implies the old behavior which depends on whether ip addresses
88 are specified or not.
89 '';
90 };
91
92 ip4 = mkOption {
93 default = [ ];
94 example = [
95 { address = "10.0.0.1"; prefixLength = 16; }
96 { address = "192.168.1.1"; prefixLength = 24; }
97 ];
98 type = types.listOf types.optionSet;
99 options = addrOpts 4;
100 description = ''
101 List of IPv4 addresses that will be statically assigned to the interface.
102 '';
103 };
104
105 ip6 = mkOption {
106 default = [ ];
107 example = [
108 { address = "fdfd:b3f0:482::1"; prefixLength = 48; }
109 { address = "2001:1470:fffd:2098::e006"; prefixLength = 64; }
110 ];
111 type = types.listOf types.optionSet;
112 options = addrOpts 6;
113 description = ''
114 List of IPv6 addresses that will be statically assigned to the interface.
115 '';
116 };
117
118 ipAddress = mkOption {
119 default = null;
120 example = "10.0.0.1";
121 type = types.nullOr types.str;
122 description = ''
123 IP address of the interface. Leave empty to configure the
124 interface using DHCP.
125 '';
126 };
127
128 prefixLength = mkOption {
129 default = null;
130 example = 24;
131 type = types.nullOr types.int;
132 description = ''
133 Subnet mask of the interface, specified as the number of
134 bits in the prefix (<literal>24</literal>).
135 '';
136 };
137
138 subnetMask = mkOption {
139 default = null;
140 description = ''
141 Defunct, supply the prefix length instead.
142 '';
143 };
144
145 ipv6Address = mkOption {
146 default = null;
147 example = "2001:1470:fffd:2098::e006";
148 type = types.nullOr types.str;
149 description = ''
150 IPv6 address of the interface. Leave empty to configure the
151 interface using NDP.
152 '';
153 };
154
155 ipv6PrefixLength = mkOption {
156 default = 64;
157 example = 64;
158 type = types.int;
159 description = ''
160 Subnet mask of the interface, specified as the number of
161 bits in the prefix (<literal>64</literal>).
162 '';
163 };
164
165 macAddress = mkOption {
166 default = null;
167 example = "00:11:22:33:44:55";
168 type = types.nullOr (types.str);
169 description = ''
170 MAC address of the interface. Leave empty to use the default.
171 '';
172 };
173
174 mtu = mkOption {
175 default = null;
176 example = 9000;
177 type = types.nullOr types.int;
178 description = ''
179 MTU size for packets leaving the interface. Leave empty to use the default.
180 '';
181 };
182
183 virtual = mkOption {
184 default = false;
185 type = types.bool;
186 description = ''
187 Whether this interface is virtual and should be created by tunctl.
188 This is mainly useful for creating bridges between a host a virtual
189 network such as VPN or a virtual machine.
190 '';
191 };
192
193 virtualOwner = mkOption {
194 default = "root";
195 type = types.str;
196 description = ''
197 In case of a virtual device, the user who owns it.
198 '';
199 };
200
201 virtualType = mkOption {
202 default = null;
203 type = types.nullOr (types.addCheck types.str (v: v == "tun" || v == "tap"));
204 description = ''
205 The explicit type of interface to create. Accepts tun or tap strings.
206 Also accepts null to implicitly detect the type of device.
207 '';
208 };
209
210 proxyARP = mkOption {
211 default = false;
212 type = types.bool;
213 description = ''
214 Turn on proxy_arp for this device (and proxy_ndp for ipv6).
215 This is mainly useful for creating pseudo-bridges between a real
216 interface and a virtual network such as VPN or a virtual machine for
217 interfaces that don't support real bridging (most wlan interfaces).
218 As ARP proxying acts slightly above the link-layer, below-ip traffic
219 isn't bridged, so things like DHCP won't work. The advantage above
220 using NAT lies in the fact that no IP addresses are shared, so all
221 hosts are reachable/routeable.
222
223 WARNING: turns on ip-routing, so if you have multiple interfaces, you
224 should think of the consequence and setup firewall rules to limit this.
225 '';
226 };
227
228 };
229
230 config = {
231 name = mkDefault name;
232 };
233
234 };
235
236 hexChars = stringToCharacters "0123456789abcdef";
237
238 isHexString = s: all (c: elem c hexChars) (stringToCharacters (toLower s));
239
240in
241
242{
243
244 ###### interface
245
246 options = {
247
248 networking.hostName = mkOption {
249 default = "nixos";
250 type = types.str;
251 description = ''
252 The name of the machine. Leave it empty if you want to obtain
253 it from a DHCP server (if using DHCP).
254 '';
255 };
256
257 networking.hostId = mkOption {
258 default = null;
259 example = "4e98920d";
260 type = types.nullOr types.str;
261 description = ''
262 The 32-bit host ID of the machine, formatted as 8 hexadecimal characters.
263
264 You should try to make this ID unique among your machines. You can
265 generate a random 32-bit ID using the following commands:
266
267 <literal>cksum /etc/machine-id | while read c rest; do printf "%x" $c; done</literal>
268
269 (this derives it from the machine-id that systemd generates) or
270
271 <literal>head -c4 /dev/urandom | od -A none -t x4</literal>
272 '';
273 };
274
275 networking.enableIPv6 = mkOption {
276 default = true;
277 type = types.bool;
278 description = ''
279 Whether to enable support for IPv6.
280 '';
281 };
282
283 networking.defaultGateway = mkOption {
284 default = null;
285 example = "131.211.84.1";
286 type = types.nullOr types.str;
287 description = ''
288 The default gateway. It can be left empty if it is auto-detected through DHCP.
289 '';
290 };
291
292 networking.defaultGateway6 = mkOption {
293 default = null;
294 example = "2001:4d0:1e04:895::1";
295 type = types.nullOr types.str;
296 description = ''
297 The default ipv6 gateway. It can be left empty if it is auto-detected through DHCP.
298 '';
299 };
300
301 networking.defaultGatewayWindowSize = mkOption {
302 default = null;
303 example = 524288;
304 type = types.nullOr types.int;
305 description = ''
306 The window size of the default gateway. It limits maximal data bursts that TCP peers
307 are allowed to send to us.
308 '';
309 };
310
311 networking.nameservers = mkOption {
312 default = [];
313 example = ["130.161.158.4" "130.161.33.17"];
314 description = ''
315 The list of nameservers. It can be left empty if it is auto-detected through DHCP.
316 '';
317 };
318
319 networking.search = mkOption {
320 default = [];
321 example = [ "example.com" "local.domain" ];
322 type = types.listOf types.str;
323 description = ''
324 The list of search paths used when resolving domain names.
325 '';
326 };
327
328 networking.domain = mkOption {
329 default = null;
330 example = "home";
331 type = types.nullOr types.str;
332 description = ''
333 The domain. It can be left empty if it is auto-detected through DHCP.
334 '';
335 };
336
337 networking.useHostResolvConf = mkOption {
338 type = types.bool;
339 default = false;
340 description = ''
341 In containers, whether to use the
342 <filename>resolv.conf</filename> supplied by the host.
343 '';
344 };
345
346 networking.localCommands = mkOption {
347 default = "";
348 example = "text=anything; echo You can put $text here.";
349 description = ''
350 Shell commands to be executed at the end of the
351 <literal>network-setup</literal> systemd service. Note that if
352 you are using DHCP to obtain the network configuration,
353 interfaces may not be fully configured yet.
354 '';
355 };
356
357 networking.interfaces = mkOption {
358 default = {};
359 example =
360 { eth0.ip4 = [ {
361 address = "131.211.84.78";
362 prefixLength = 25;
363 } ];
364 };
365 description = ''
366 The configuration for each network interface. If
367 <option>networking.useDHCP</option> is true, then every
368 interface not listed here will be configured using DHCP.
369 '';
370 type = types.loaOf types.optionSet;
371 options = [ interfaceOpts ];
372 };
373
374 networking.bridges = mkOption {
375 default = { };
376 example =
377 { br0.interfaces = [ "eth0" "eth1" ];
378 br1.interfaces = [ "eth2" "wlan0" ];
379 };
380 description =
381 ''
382 This option allows you to define Ethernet bridge devices
383 that connect physical networks together. The value of this
384 option is an attribute set. Each attribute specifies a
385 bridge, with the attribute name specifying the name of the
386 bridge's network interface.
387 '';
388
389 type = types.attrsOf types.optionSet;
390
391 options = {
392
393 interfaces = mkOption {
394 example = [ "eth0" "eth1" ];
395 type = types.listOf types.str;
396 description =
397 "The physical network interfaces connected by the bridge.";
398 };
399
400 rstp = mkOption {
401 example = true;
402 default = false;
403 type = types.bool;
404 description = "Whether the bridge interface should enable rstp.";
405 };
406
407 };
408
409 };
410
411 networking.bonds = mkOption {
412 default = { };
413 example = {
414 bond0 = {
415 interfaces = [ "eth0" "wlan0" ];
416 miimon = 100;
417 mode = "active-backup";
418 };
419 fatpipe.interfaces = [ "enp4s0f0" "enp4s0f1" "enp5s0f0" "enp5s0f1" ];
420 };
421 description = ''
422 This option allows you to define bond devices that aggregate multiple,
423 underlying networking interfaces together. The value of this option is
424 an attribute set. Each attribute specifies a bond, with the attribute
425 name specifying the name of the bond's network interface
426 '';
427
428 type = types.attrsOf types.optionSet;
429
430 options = {
431
432 interfaces = mkOption {
433 example = [ "enp4s0f0" "enp4s0f1" "wlan0" ];
434 type = types.listOf types.str;
435 description = "The interfaces to bond together";
436 };
437
438 lacp_rate = mkOption {
439 default = null;
440 example = "fast";
441 type = types.nullOr types.str;
442 description = ''
443 Option specifying the rate in which we'll ask our link partner
444 to transmit LACPDU packets in 802.3ad mode.
445 '';
446 };
447
448 miimon = mkOption {
449 default = null;
450 example = 100;
451 type = types.nullOr types.int;
452 description = ''
453 Miimon is the number of millisecond in between each round of polling
454 by the device driver for failed links. By default polling is not
455 enabled and the driver is trusted to properly detect and handle
456 failure scenarios.
457 '';
458 };
459
460 mode = mkOption {
461 default = null;
462 example = "active-backup";
463 type = types.nullOr types.str;
464 description = ''
465 The mode which the bond will be running. The default mode for
466 the bonding driver is balance-rr, optimizing for throughput.
467 More information about valid modes can be found at
468 https://www.kernel.org/doc/Documentation/networking/bonding.txt
469 '';
470 };
471
472 xmit_hash_policy = mkOption {
473 default = null;
474 example = "layer2+3";
475 type = types.nullOr types.str;
476 description = ''
477 Selects the transmit hash policy to use for slave selection in
478 balance-xor, 802.3ad, and tlb modes.
479 '';
480 };
481
482 };
483 };
484
485 networking.macvlans = mkOption {
486 type = types.attrsOf types.optionSet;
487 default = { };
488 example = {
489 wan = {
490 interface = "enp2s0";
491 mode = "vepa";
492 };
493 };
494 description = ''
495 This option allows you to define macvlan interfaces which should
496 be automatically created.
497 '';
498 options = {
499
500 interface = mkOption {
501 example = "enp4s0";
502 type = types.string;
503 description = "The interface the macvlan will transmit packets through.";
504 };
505
506 mode = mkOption {
507 default = null;
508 type = types.nullOr types.str;
509 example = "vepa";
510 description = "The mode of the macvlan device.";
511 };
512
513 };
514 };
515
516 networking.sits = mkOption {
517 type = types.attrsOf types.optionSet;
518 default = { };
519 example = {
520 hurricane = {
521 remote = "10.0.0.1";
522 local = "10.0.0.22";
523 ttl = 255;
524 };
525 msipv6 = {
526 remote = "192.168.0.1";
527 dev = "enp3s0";
528 ttl = 127;
529 };
530 };
531 description = ''
532 This option allows you to define 6-to-4 interfaces which should be automatically created.
533 '';
534 options = {
535
536 remote = mkOption {
537 type = types.nullOr types.str;
538 default = null;
539 example = "10.0.0.1";
540 description = ''
541 The address of the remote endpoint to forward traffic over.
542 '';
543 };
544
545 local = mkOption {
546 type = types.nullOr types.str;
547 default = null;
548 example = "10.0.0.22";
549 description = ''
550 The address of the local endpoint which the remote
551 side should send packets to.
552 '';
553 };
554
555 ttl = mkOption {
556 type = types.nullOr types.int;
557 default = null;
558 example = 255;
559 description = ''
560 The time-to-live of the connection to the remote tunnel endpoint.
561 '';
562 };
563
564 dev = mkOption {
565 type = types.nullOr types.str;
566 default = null;
567 example = "enp4s0f0";
568 description = ''
569 The underlying network device on which the tunnel resides.
570 '';
571 };
572
573 };
574 };
575
576 networking.vlans = mkOption {
577 default = { };
578 example = {
579 vlan0 = {
580 id = 3;
581 interface = "enp3s0";
582 };
583 vlan1 = {
584 id = 1;
585 interface = "wlan0";
586 };
587 };
588 description =
589 ''
590 This option allows you to define vlan devices that tag packets
591 on top of a physical interface. The value of this option is an
592 attribute set. Each attribute specifies a vlan, with the name
593 specifying the name of the vlan interface.
594 '';
595
596 type = types.attrsOf types.optionSet;
597
598 options = {
599
600 id = mkOption {
601 example = 1;
602 type = types.int;
603 description = "The vlan identifier";
604 };
605
606 interface = mkOption {
607 example = "enp4s0";
608 type = types.string;
609 description = "The interface the vlan will transmit packets through.";
610 };
611
612 };
613 };
614
615 networking.useDHCP = mkOption {
616 type = types.bool;
617 default = true;
618 description = ''
619 Whether to use DHCP to obtain an IP address and other
620 configuration for all network interfaces that are not manually
621 configured.
622 '';
623 };
624
625 networking.useNetworkd = mkOption {
626 default = false;
627 type = types.bool;
628 description = ''
629 Whether we should use networkd as the network configuration backend or
630 the legacy script based system. Note that this option is experimental,
631 enable at your own risk.
632 '';
633 };
634
635 };
636
637
638 ###### implementation
639
640 config = {
641
642 assertions =
643 (flip map interfaces (i: {
644 assertion = i.subnetMask == null;
645 message = "The networking.interfaces.${i.name}.subnetMask option is defunct. Use prefixLength instead.";
646 })) ++ (flip map slaveIfs (i: {
647 assertion = i.ip4 == [ ] && i.ipAddress == null && i.ip6 == [ ] && i.ipv6Address == null;
648 message = "The networking.interfaces.${i.name} must not have any defined ips when it is a slave.";
649 })) ++ [
650 {
651 assertion = cfg.hostId == null || (stringLength cfg.hostId == 8 && isHexString cfg.hostId);
652 message = "Invalid value given to the networking.hostId option.";
653 }
654 ];
655
656 boot.kernelModules = [ ]
657 ++ optional cfg.enableIPv6 "ipv6"
658 ++ optional hasVirtuals "tun"
659 ++ optional hasSits "sit"
660 ++ optional hasBonds "bonding";
661
662 boot.extraModprobeConfig =
663 # This setting is intentional as it prevents default bond devices
664 # from being created.
665 optionalString hasBonds "options bonding max_bonds=0";
666
667 boot.kernel.sysctl = {
668 "net.net.ipv4.conf.all.promote_secondaries" = true;
669 "net.ipv6.conf.all.disable_ipv6" = mkDefault (!cfg.enableIPv6);
670 "net.ipv6.conf.default.disable_ipv6" = mkDefault (!cfg.enableIPv6);
671 "net.ipv4.conf.all_forwarding" = mkDefault (any (i: i.proxyARP) interfaces);
672 "net.ipv6.conf.all.forwarding" = mkDefault (any (i: i.proxyARP) interfaces);
673 } // listToAttrs (concatLists (flip map (filter (i: i.proxyARP) interfaces)
674 (i: flip map [ "4" "6" ] (v: nameValuePair "net.ipv${v}.conf.${i.name}.proxy_arp" true))
675 ));
676
677 security.setuidPrograms = [ "ping" "ping6" ];
678
679 # Set the host and domain names in the activation script. Don't
680 # clear it if it's not configured in the NixOS configuration,
681 # since it may have been set by dhcpcd in the meantime.
682 system.activationScripts.hostname =
683 optionalString (cfg.hostName != "") ''
684 hostname "${cfg.hostName}"
685 '';
686 system.activationScripts.domain =
687 optionalString (cfg.domain != null) ''
688 domainname "${cfg.domain}"
689 '';
690
691 environment.etc = mkIf (cfg.hostId != null)
692 [
693 {
694 target = "hostid";
695 source = pkgs.runCommand "gen-hostid" {} ''
696 hi="${cfg.hostId}"
697 ${if pkgs.stdenv.isBigEndian then ''
698 echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > $out
699 '' else ''
700 echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > $out
701 ''}
702 '';
703 }
704 ];
705
706 environment.systemPackages =
707 [ pkgs.host
708 pkgs.iproute
709 pkgs.iputils
710 pkgs.nettools
711 pkgs.openresolv
712 ]
713 ++ optionals (!config.boot.isContainer) [
714 pkgs.wirelesstools # FIXME: obsolete?
715 pkgs.iw
716 pkgs.rfkill
717 ]
718 ++ bridgeStp;
719
720 systemd.targets."network-interfaces" =
721 { description = "All Network Interfaces";
722 wantedBy = [ "network.target" ];
723 before = [ "network.target" ];
724 after = [ "network-pre.target" ];
725 unitConfig.X-StopOnReconfiguration = true;
726 };
727
728 systemd.services = {
729 network-local-commands = {
730 description = "Extra networking commands.";
731 before = [ "network.target" ];
732 wantedBy = [ "network.target" ];
733 after = [ "network-pre.target" ];
734 unitConfig.ConditionCapability = "CAP_NET_ADMIN";
735 path = [ pkgs.iproute ];
736 serviceConfig.Type = "oneshot";
737 serviceConfig.RemainAfterExit = true;
738 script = ''
739 # Run any user-specified commands.
740 ${cfg.localCommands}
741 '';
742 };
743 } // (listToAttrs (flip map interfaces (i:
744 nameValuePair "network-link-${i.name}"
745 { description = "Link configuration of ${i.name}";
746 wantedBy = [ "network-interfaces.target" ];
747 before = [ "network-interfaces.target" ];
748 bindsTo = [ (subsystemDevice i.name) ];
749 after = [ (subsystemDevice i.name) "network-pre.target" ];
750 path = [ pkgs.iproute ];
751 serviceConfig = {
752 Type = "oneshot";
753 RemainAfterExit = true;
754 };
755 script =
756 ''
757 echo "Configuring link..."
758 '' + optionalString (i.macAddress != null) ''
759 echo "setting MAC address to ${i.macAddress}..."
760 ip link set "${i.name}" address "${i.macAddress}"
761 '' + optionalString (i.mtu != null) ''
762 echo "setting MTU to ${toString i.mtu}..."
763 ip link set "${i.name}" mtu "${toString i.mtu}"
764 '';
765 })));
766
767 services.mstpd = mkIf needsMstpd { enable = true; };
768
769 };
770
771}