about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorNikolay Amiantov <ab@fmap.me>2017-01-15 19:53:33 +0300
committerGitHub <noreply@github.com>2017-01-15 19:53:33 +0300
commit70a662884895a079672a7daa57200d2848b80550 (patch)
tree4894daca3015ccf334d5dacad1abf70180a51f1d /nixos
parent12b283044612c5336638fd3a825e15799c7394aa (diff)
parent86755d923b201e2e450cf08af0ec8260d00b39a9 (diff)
downloadnixlib-70a662884895a079672a7daa57200d2848b80550.tar
nixlib-70a662884895a079672a7daa57200d2848b80550.tar.gz
nixlib-70a662884895a079672a7daa57200d2848b80550.tar.bz2
nixlib-70a662884895a079672a7daa57200d2848b80550.tar.lz
nixlib-70a662884895a079672a7daa57200d2848b80550.tar.xz
nixlib-70a662884895a079672a7daa57200d2848b80550.tar.zst
nixlib-70a662884895a079672a7daa57200d2848b80550.zip
Merge pull request #21882 from abbradar/dhcp6
DHCPv6 improvements
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/rename.nix3
-rw-r--r--nixos/modules/services/networking/dhcpd.nix250
-rw-r--r--nixos/modules/services/networking/firewall.nix9
-rw-r--r--nixos/tests/networking.nix58
4 files changed, 211 insertions, 109 deletions
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 758f229d59d7..ad1ba86980d5 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -164,6 +164,9 @@ with lib;
         else { addr = value inetAddr; port = value inetPort; }
     ))
 
+    # dhcpd
+    (mkRenamedOptionModule [ "services" "dhcpd" ] [ "services" "dhcpd4" ])
+
     # Options that are obsolete and have no replacement.
     (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "")
     (mkRemovedOptionModule [ "programs" "bash" "enable" ] "")
diff --git a/nixos/modules/services/networking/dhcpd.nix b/nixos/modules/services/networking/dhcpd.nix
index d2cd00e74a1f..86bcaa96f345 100644
--- a/nixos/modules/services/networking/dhcpd.nix
+++ b/nixos/modules/services/networking/dhcpd.nix
@@ -4,11 +4,10 @@ with lib;
 
 let
 
-  cfg = config.services.dhcpd;
+  cfg4 = config.services.dhcpd4;
+  cfg6 = config.services.dhcpd6;
 
-  stateDir = "/var/lib/dhcp"; # Don't use /var/state/dhcp; not FHS-compliant.
-
-  configFile = if cfg.configFile != null then cfg.configFile else pkgs.writeText "dhcpd.conf"
+  writeConfig = cfg: pkgs.writeText "dhcpd.conf"
     ''
       default-lease-time 600;
       max-lease-time 7200;
@@ -29,131 +28,180 @@ let
       }
     '';
 
-in
-
-{
-
-  ###### interface
-
-  options = {
+  dhcpdService = postfix: cfg: optionalAttrs cfg.enable {
+    "dhcpd${postfix}" = {
+      description = "DHCPv${postfix} server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      preStart = ''
+        mkdir -m 755 -p ${cfg.stateDir}
+        touch ${cfg.stateDir}/dhcpd.leases
+      '';
+
+      serviceConfig =
+        let
+          configFile = if cfg.configFile != null then cfg.configFile else writeConfig cfg;
+          args = [ "@${pkgs.dhcp}/sbin/dhcpd" "dhcpd${postfix}" "-${postfix}"
+                   "-pf" "/run/dhcpd${postfix}/dhcpd.pid"
+                   "-cf" "${configFile}"
+                   "-lf" "${cfg.stateDir}/dhcpd.leases"
+                   "-user" "dhcpd" "-group" "nogroup"
+                 ] ++ cfg.extraFlags
+                   ++ cfg.interfaces;
+
+        in {
+          ExecStart = concatMapStringsSep " " escapeShellArg args;
+          Type = "forking";
+          Restart = "always";
+          RuntimeDirectory = [ "dhcpd${postfix}" ];
+          PIDFile = "/run/dhcpd${postfix}/dhcpd.pid";
+        };
+    };
+  };
 
-    services.dhcpd = {
+  machineOpts = {...}: {
+    config = {
 
-      enable = mkOption {
-        default = false;
-        description = "
-          Whether to enable the DHCP server.
-        ";
+      hostName = mkOption {
+        type = types.str;
+        example = "foo";
+        description = ''
+          Hostname which is assigned statically to the machine.
+        '';
       };
 
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        example = ''
-          option subnet-mask 255.255.255.0;
-          option broadcast-address 192.168.1.255;
-          option routers 192.168.1.5;
-          option domain-name-servers 130.161.158.4, 130.161.33.17, 130.161.180.1;
-          option domain-name "example.org";
-          subnet 192.168.1.0 netmask 255.255.255.0 {
-            range 192.168.1.100 192.168.1.200;
-          }
+      ethernetAddress = mkOption {
+        type = types.str;
+        example = "00:16:76:9a:32:1d";
+        description = ''
+          MAC address of the machine.
         '';
-        description = "
-          Extra text to be appended to the DHCP server configuration
-          file.  Currently, you almost certainly need to specify
-          something here, such as the options specifying the subnet
-          mask, DNS servers, etc.
-        ";
       };
 
-      extraFlags = mkOption {
-        default = "";
-        example = "-6";
-        description = "
-          Additional command line flags to be passed to the dhcpd daemon.
-        ";
+      ipAddress = mkOption {
+        type = types.str;
+        example = "192.168.1.10";
+        description = ''
+          IP address of the machine.
+        '';
       };
 
-      configFile = mkOption {
-        default = null;
-        description = "
-          The path of the DHCP server configuration file.  If no file
-          is specified, a file is generated using the other options.
-        ";
-      };
+    };
+  };
 
-      interfaces = mkOption {
-        default = ["eth0"];
-        description = "
-          The interfaces on which the DHCP server should listen.
-        ";
-      };
+  dhcpConfig = postfix: {
 
-      machines = mkOption {
-        default = [];
-        example = [
-          { hostName = "foo";
-            ethernetAddress = "00:16:76:9a:32:1d";
-            ipAddress = "192.168.1.10";
-          }
-          { hostName = "bar";
-            ethernetAddress = "00:19:d1:1d:c4:9a";
-            ipAddress = "192.168.1.11";
-          }
-        ];
-        description = "
-          A list mapping ethernet addresses to IP addresses for the
-          DHCP server.
-        ";
-      };
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable the DHCPv${postfix} server.
+      '';
+    };
 
+    stateDir = mkOption {
+      type = types.path;
+      # We use /var/lib/dhcp for DHCPv4 to save backwards compatibility.
+      default = "/var/lib/dhcp${if postfix == "4" then "" else postfix}";
+      description = ''
+        State directory for the DHCP server.
+      '';
     };
 
-  };
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = ''
+        option subnet-mask 255.255.255.0;
+        option broadcast-address 192.168.1.255;
+        option routers 192.168.1.5;
+        option domain-name-servers 130.161.158.4, 130.161.33.17, 130.161.180.1;
+        option domain-name "example.org";
+        subnet 192.168.1.0 netmask 255.255.255.0 {
+          range 192.168.1.100 192.168.1.200;
+        }
+      '';
+      description = ''
+        Extra text to be appended to the DHCP server configuration
+        file. Currently, you almost certainly need to specify something
+        there, such as the options specifying the subnet mask, DNS servers,
+        etc.
+      '';
+    };
 
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Additional command line flags to be passed to the dhcpd daemon.
+      '';
+    };
 
-  ###### implementation
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        The path of the DHCP server configuration file.  If no file
+        is specified, a file is generated using the other options.
+      '';
+    };
 
-  config = mkIf config.services.dhcpd.enable {
+    interfaces = mkOption {
+      type = types.listOf types.str;
+      default = ["eth0"];
+      description = ''
+        The interfaces on which the DHCP server should listen.
+      '';
+    };
 
-    users = {
-      extraUsers.dhcpd = {
-        uid = config.ids.uids.dhcpd;
-        description = "DHCP daemon user";
-      };
+    machines = mkOption {
+      type = types.listOf (types.submodule machineOpts);
+      default = [];
+      example = [
+        { hostName = "foo";
+          ethernetAddress = "00:16:76:9a:32:1d";
+          ipAddress = "192.168.1.10";
+        }
+        { hostName = "bar";
+          ethernetAddress = "00:19:d1:1d:c4:9a";
+          ipAddress = "192.168.1.11";
+        }
+      ];
+      description = ''
+        A list mapping Ethernet addresses to IPv${postfix} addresses for the
+        DHCP server.
+      '';
     };
 
-    systemd.services.dhcpd =
-      { description = "DHCP server";
+  };
+
+in
+
+{
+
+  ###### interface
 
-        wantedBy = [ "multi-user.target" ];
+  options = {
 
-        after = [ "network.target" ];
+    services.dhcpd4 = dhcpConfig "4";
+    services.dhcpd6 = dhcpConfig "6";
 
-        path = [ pkgs.dhcp ];
+  };
 
-        preStart =
-          ''
-            mkdir -m 755 -p ${stateDir}
 
-            touch ${stateDir}/dhcpd.leases
+  ###### implementation
 
-            mkdir -m 755 -p /run/dhcpd
-            chown dhcpd /run/dhcpd
-          '';
+  config = mkIf (cfg4.enable || cfg6.enable) {
 
-        serviceConfig =
-          { ExecStart = "@${pkgs.dhcp}/sbin/dhcpd dhcpd"
-              + " -pf /run/dhcpd/dhcpd.pid -cf ${configFile}"
-              + " -lf ${stateDir}/dhcpd.leases -user dhcpd -group nogroup"
-              + " ${cfg.extraFlags}"
-              + " ${toString cfg.interfaces}";
-            Restart = "always";
-            Type = "forking";
-            PIDFile = "/run/dhcpd/dhcpd.pid";
-          };
+    users = {
+      extraUsers.dhcpd = {
+        uid = config.ids.uids.dhcpd;
+        description = "DHCP daemon user";
       };
+    };
+
+    systemd.services = dhcpdService "4" cfg4 // dhcpdService "6" cfg6;
 
   };
 
diff --git a/nixos/modules/services/networking/firewall.nix b/nixos/modules/services/networking/firewall.nix
index 1c0ea5034df3..ea406864fd3f 100644
--- a/nixos/modules/services/networking/firewall.nix
+++ b/nixos/modules/services/networking/firewall.nix
@@ -172,13 +172,16 @@ let
       }-j nixos-fw-accept
     ''}
 
-    # Accept all ICMPv6 messages except redirects and node
-    # information queries (type 139).  See RFC 4890, section
-    # 4.4.
     ${optionalString config.networking.enableIPv6 ''
+      # Accept all ICMPv6 messages except redirects and node
+      # information queries (type 139).  See RFC 4890, section
+      # 4.4.
       ip6tables -A nixos-fw -p icmpv6 --icmpv6-type redirect -j DROP
       ip6tables -A nixos-fw -p icmpv6 --icmpv6-type 139 -j DROP
       ip6tables -A nixos-fw -p icmpv6 -j nixos-fw-accept
+
+      # Allow this host to act as a DHCPv6 client
+      ip6tables -A nixos-fw -d fe80::/64 -p udp --dport 546 -j nixos-fw-accept
     ''}
 
     ${cfg.extraCommands}
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index 17d4a878d3a4..83103f35d482 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -10,29 +10,61 @@ let
       vlanIfs = range 1 (length config.virtualisation.vlans);
     in {
       virtualisation.vlans = [ 1 2 3 ];
+      boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
       networking = {
         useDHCP = false;
         useNetworkd = networkd;
         firewall.allowPing = true;
+        firewall.checkReversePath = true;
+        firewall.allowedUDPPorts = [ 547 ];
         interfaces = mkOverride 0 (listToAttrs (flip map vlanIfs (n:
           nameValuePair "eth${toString n}" {
             ipAddress = "192.168.${toString n}.1";
             prefixLength = 24;
+            ipv6Address = "fd00:1234:5678:${toString n}::1";
+            ipv6PrefixLength = 64;
           })));
       };
-      services.dhcpd = {
+      services.dhcpd4 = {
         enable = true;
         interfaces = map (n: "eth${toString n}") vlanIfs;
         extraConfig = ''
-          option subnet-mask 255.255.255.0;
+          authoritative;
         '' + flip concatMapStrings vlanIfs (n: ''
           subnet 192.168.${toString n}.0 netmask 255.255.255.0 {
-            option broadcast-address 192.168.${toString n}.255;
             option routers 192.168.${toString n}.1;
+            # XXX: technically it's _not guaranteed_ that IP addresses will be
+            # issued from the first item in range onwards! We assume that in
+            # our tests however.
             range 192.168.${toString n}.2 192.168.${toString n}.254;
           }
         '');
       };
+      services.radvd = {
+        enable = true;
+        config = flip concatMapStrings vlanIfs (n: ''
+          interface eth${toString n} {
+            AdvSendAdvert on;
+            AdvManagedFlag on;
+            AdvOtherConfigFlag on;
+
+            prefix fd00:1234:5678:${toString n}::/64 {
+              AdvAutonomous off;
+            };
+          };
+        '');
+      };
+      services.dhcpd6 = {
+        enable = true;
+        interfaces = map (n: "eth${toString n}") vlanIfs;
+        extraConfig = ''
+          authoritative;
+        '' + flip concatMapStrings vlanIfs (n: ''
+          subnet6 fd00:1234:5678:${toString n}::/64 {
+            range6 fd00:1234:5678:${toString n}::2 fd00:1234:5678:${toString n}::2;
+          }
+        '');
+      };
     };
 
   testCases = {
@@ -108,8 +140,14 @@ let
           useNetworkd = networkd;
           firewall.allowPing = true;
           useDHCP = true;
-          interfaces.eth1.ip4 = mkOverride 0 [ ];
-          interfaces.eth2.ip4 = mkOverride 0 [ ];
+          interfaces.eth1 = {
+            ip4 = mkOverride 0 [ ];
+            ip6 = mkOverride 0 [ ];
+          };
+          interfaces.eth2 = {
+            ip4 = mkOverride 0 [ ];
+            ip6 = mkOverride 0 [ ];
+          };
         };
       };
       testScript = { nodes, ... }:
@@ -121,21 +159,31 @@ let
 
           # Wait until we have an ip address on each interface
           $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q '192.168.1'");
+          $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'");
           $client->waitUntilSucceeds("ip addr show dev eth2 | grep -q '192.168.2'");
+          $client->waitUntilSucceeds("ip addr show dev eth2 | grep -q 'fd00:1234:5678:2:'");
 
           # Test vlan 1
           $client->waitUntilSucceeds("ping -c 1 192.168.1.1");
           $client->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $client->waitUntilSucceeds("ping6 -c 1 fd00:1234:5678:1::1");
+          $client->waitUntilSucceeds("ping6 -c 1 fd00:1234:5678:1::2");
 
           $router->waitUntilSucceeds("ping -c 1 192.168.1.1");
           $router->waitUntilSucceeds("ping -c 1 192.168.1.2");
+          $router->waitUntilSucceeds("ping6 -c 1 fd00:1234:5678:1::1");
+          $router->waitUntilSucceeds("ping6 -c 1 fd00:1234:5678:1::2");
 
           # Test vlan 2
           $client->waitUntilSucceeds("ping -c 1 192.168.2.1");
           $client->waitUntilSucceeds("ping -c 1 192.168.2.2");
+          $client->waitUntilSucceeds("ping6 -c 1 fd00:1234:5678:2::1");
+          $client->waitUntilSucceeds("ping6 -c 1 fd00:1234:5678:2::2");
 
           $router->waitUntilSucceeds("ping -c 1 192.168.2.1");
           $router->waitUntilSucceeds("ping -c 1 192.168.2.2");
+          $router->waitUntilSucceeds("ping6 -c 1 fd00:1234:5678:2::1");
+          $router->waitUntilSucceeds("ping6 -c 1 fd00:1234:5678:2::2");
         '';
     };
     dhcpOneIf = {