about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorRobin Gloster <mail@glob.in>2021-05-07 13:01:29 -0500
committerGitHub <noreply@github.com>2021-05-07 13:01:29 -0500
commit29e92116d191cf8610c829afcfdd5cfb583149b9 (patch)
tree4a0100c93ceec19a89012c6b55099e93c391efe6 /nixos
parent144dc280e35a5faaa9b7611693707bf925047045 (diff)
parent47828e7dc0d7c81cc60ae3e48e6640bd1bfdbc4e (diff)
downloadnixlib-29e92116d191cf8610c829afcfdd5cfb583149b9.tar
nixlib-29e92116d191cf8610c829afcfdd5cfb583149b9.tar.gz
nixlib-29e92116d191cf8610c829afcfdd5cfb583149b9.tar.bz2
nixlib-29e92116d191cf8610c829afcfdd5cfb583149b9.tar.lz
nixlib-29e92116d191cf8610c829afcfdd5cfb583149b9.tar.xz
nixlib-29e92116d191cf8610c829afcfdd5cfb583149b9.tar.zst
nixlib-29e92116d191cf8610c829afcfdd5cfb583149b9.zip
Merge pull request #118037 from mayflower/privacy-extensions-configurable
nixos/network: allow configuring tempaddr for undeclared interfaces
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/configuration/ipv6-config.xml8
-rw-r--r--nixos/modules/tasks/network-interfaces.nix96
-rw-r--r--nixos/tests/ipv6.nix85
3 files changed, 130 insertions, 59 deletions
diff --git a/nixos/doc/manual/configuration/ipv6-config.xml b/nixos/doc/manual/configuration/ipv6-config.xml
index 7b89b4092be7..45e85dbf3dfd 100644
--- a/nixos/doc/manual/configuration/ipv6-config.xml
+++ b/nixos/doc/manual/configuration/ipv6-config.xml
@@ -7,8 +7,12 @@
 
  <para>
   IPv6 is enabled by default. Stateless address autoconfiguration is used to
-  automatically assign IPv6 addresses to all interfaces. You can disable IPv6
-  support globally by setting:
+  automatically assign IPv6 addresses to all interfaces, and Privacy
+  Extensions (RFC 4946) are enabled by default. You can adjust the default
+  for this by setting <xref linkend="opt-networking.tempAddresses"/>.
+  This option may be overridden on a per-interface basis by
+  <xref linkend="opt-networking.interfaces._name_.tempAddress"/>.
+  You can disable IPv6 support globally by setting:
 <programlisting>
 <xref linkend="opt-networking.enableIPv6"/> = false;
 </programlisting>
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index f501f85b2a92..3d1628d0783d 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -144,33 +144,20 @@ let
       };
 
       tempAddress = mkOption {
-        type = types.enum [ "default" "enabled" "disabled" ];
-        default = if cfg.enableIPv6 then "default" else "disabled";
-        defaultText = literalExample ''if cfg.enableIPv6 then "default" else "disabled"'';
+        type = types.enum (lib.attrNames tempaddrValues);
+        default = cfg.tempAddresses;
+        defaultText = literalExample ''config.networking.tempAddresses'';
         description = ''
           When IPv6 is enabled with SLAAC, this option controls the use of
-          temporary address (aka privacy extensions). This is used to reduce tracking.
-          The three possible values are:
-
-          <itemizedlist>
-           <listitem>
-            <para>
-             <literal>"default"</literal> to generate temporary addresses and use
-             them by default;
-            </para>
-           </listitem>
-           <listitem>
-            <para>
-             <literal>"enabled"</literal> to generate temporary addresses but keep
-             using the standard EUI-64 ones by default;
-            </para>
-           </listitem>
-           <listitem>
-            <para>
-             <literal>"disabled"</literal> to completely disable temporary addresses.
-            </para>
-           </listitem>
-          </itemizedlist>
+          temporary address (aka privacy extensions) on this
+          interface. This is used to reduce tracking.
+
+          See also the global option
+          <xref linkend="opt-networking.tempAddresses"/>, which
+          applies to all interfaces where this is not set.
+
+          Possible values are:
+          ${tempaddrDoc}
         '';
       };
 
@@ -366,6 +353,32 @@ let
 
   isHexString = s: all (c: elem c hexChars) (stringToCharacters (toLower s));
 
+  tempaddrValues = {
+    disabled = {
+      sysctl = "0";
+      description = "completely disable IPv6 temporary addresses";
+    };
+    enabled = {
+      sysctl = "1";
+      description = "generate IPv6 temporary addresses but still use EUI-64 addresses as source addresses";
+    };
+    default = {
+      sysctl = "2";
+      description = "generate IPv6 temporary addresses and use these as source addresses in routing";
+    };
+  };
+  tempaddrDoc = ''
+    <itemizedlist>
+     ${concatStringsSep "\n" (mapAttrsToList (name: { description, ... }: ''
+       <listitem>
+         <para>
+           <literal>"${name}"</literal> to ${description};
+         </para>
+       </listitem>
+     '') tempaddrValues)}
+    </itemizedlist>
+  '';
+
 in
 
 {
@@ -1039,6 +1052,21 @@ in
       '';
     };
 
+    networking.tempAddresses = mkOption {
+      default = if cfg.enableIPv6 then "default" else "disabled";
+      type = types.enum (lib.attrNames tempaddrValues);
+      description = ''
+        Whether to enable IPv6 Privacy Extensions for interfaces not
+        configured explicitly in
+        <xref linkend="opt-networking.interfaces._name_.tempAddress" />.
+
+        This sets the ipv6.conf.*.use_tempaddr sysctl for all
+        interfaces. Possible values are:
+
+        ${tempaddrDoc}
+      '';
+    };
+
   };
 
 
@@ -1098,7 +1126,7 @@ in
       // listToAttrs (forEach interfaces
         (i: let
           opt = i.tempAddress;
-          val = { disabled = 0; enabled = 1; default = 2; }.${opt};
+          val = tempaddrValues.${opt}.sysctl;
          in nameValuePair "net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr" val));
 
     # Capabilities won't work unless we have at-least a 4.3 Linux
@@ -1188,9 +1216,11 @@ in
       (pkgs.writeTextFile rec {
         name = "ipv6-privacy-extensions.rules";
         destination = "/etc/udev/rules.d/98-${name}";
-        text = ''
+        text = let
+          sysctl-value = tempaddrValues.${cfg.tempAddresses}.sysctl;
+        in ''
           # enable and prefer IPv6 privacy addresses by default
-          ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.bash}/bin/sh -c 'echo 2 > /proc/sys/net/ipv6/conf/%k/use_tempaddr'"
+          ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.bash}/bin/sh -c 'echo ${sysctl-value} > /proc/sys/net/ipv6/conf/%k/use_tempaddr'"
         '';
       })
       (pkgs.writeTextFile rec {
@@ -1199,15 +1229,13 @@ in
         text = concatMapStrings (i:
           let
             opt = i.tempAddress;
-            val = if opt == "disabled" then 0 else 1;
-            msg = if opt == "disabled"
-                  then "completely disable IPv6 privacy addresses"
-                  else "enable IPv6 privacy addresses but prefer EUI-64 addresses";
+            val = tempaddrValues.${opt}.sysctl;
+            msg = tempaddrValues.${opt}.description;
           in
           ''
             # override to ${msg} for ${i.name}
-            ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr=${toString val}"
-          '') (filter (i: i.tempAddress != "default") interfaces);
+            ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr=${val}"
+          '') (filter (i: i.tempAddress != cfg.tempAddresses) interfaces);
       })
     ] ++ lib.optional (cfg.wlanInterfaces != {})
       (pkgs.writeTextFile {
diff --git a/nixos/tests/ipv6.nix b/nixos/tests/ipv6.nix
index f9d6d82b54ac..75faa6f60201 100644
--- a/nixos/tests/ipv6.nix
+++ b/nixos/tests/ipv6.nix
@@ -8,12 +8,34 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
   };
 
   nodes =
-    # Remove the interface configuration provided by makeTest so that the
-    # interfaces are all configured implicitly
-    { client = { ... }: { networking.interfaces = lib.mkForce {}; };
+    {
+      # We use lib.mkForce here to remove the interface configuration
+      # provided by makeTest, so that the interfaces are all configured
+      # implicitly.
+
+      # This client should use privacy extensions fully, having a
+      # completely-default network configuration.
+      client_defaults.networking.interfaces = lib.mkForce {};
+
+      # Both of these clients should obtain temporary addresses, but
+      # not use them as the default source IP. We thus run the same
+      # checks against them — but the configuration resulting in this
+      # behaviour is different.
+
+      # Here, by using an altered default value for the global setting...
+      client_global_setting = {
+        networking.interfaces = lib.mkForce {};
+        networking.tempAddresses = "enabled";
+      };
+      # and here, by setting this on the interface explicitly.
+      client_interface_setting = {
+        networking.tempAddresses = "disabled";
+        networking.interfaces = lib.mkForce {
+          eth1.tempAddress = "enabled";
+        };
+      };
 
       server =
-        { ... }:
         { services.httpd.enable = true;
           services.httpd.adminAddr = "foo@example.org";
           networking.firewall.allowedTCPPorts = [ 80 ];
@@ -40,9 +62,12 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
       # Start the router first so that it respond to router solicitations.
       router.wait_for_unit("radvd")
 
+      clients = [client_defaults, client_global_setting, client_interface_setting]
+
       start_all()
 
-      client.wait_for_unit("network.target")
+      for client in clients:
+          client.wait_for_unit("network.target")
       server.wait_for_unit("network.target")
       server.wait_for_unit("httpd.service")
 
@@ -64,28 +89,42 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
 
 
       with subtest("Loopback address can be pinged"):
-          client.succeed("ping -c 1 ::1 >&2")
-          client.fail("ping -c 1 ::2 >&2")
+          client_defaults.succeed("ping -c 1 ::1 >&2")
+          client_defaults.fail("ping -c 1 2001:db8:: >&2")
 
       with subtest("Local link addresses can be obtained and pinged"):
-          client_ip = wait_for_address(client, "eth1", "link")
-          server_ip = wait_for_address(server, "eth1", "link")
-          client.succeed(f"ping -c 1 {client_ip}%eth1 >&2")
-          client.succeed(f"ping -c 1 {server_ip}%eth1 >&2")
+          for client in clients:
+              client_ip = wait_for_address(client, "eth1", "link")
+              server_ip = wait_for_address(server, "eth1", "link")
+              client.succeed(f"ping -c 1 {client_ip}%eth1 >&2")
+              client.succeed(f"ping -c 1 {server_ip}%eth1 >&2")
 
       with subtest("Global addresses can be obtained, pinged, and reached via http"):
-          client_ip = wait_for_address(client, "eth1", "global")
-          server_ip = wait_for_address(server, "eth1", "global")
-          client.succeed(f"ping -c 1 {client_ip} >&2")
-          client.succeed(f"ping -c 1 {server_ip} >&2")
-          client.succeed(f"curl --fail -g http://[{server_ip}]")
-          client.fail(f"curl --fail -g http://[{client_ip}]")
-
-      with subtest("Privacy extensions: Global temporary address can be obtained and pinged"):
-          ip = wait_for_address(client, "eth1", "global", temporary=True)
+          for client in clients:
+              client_ip = wait_for_address(client, "eth1", "global")
+              server_ip = wait_for_address(server, "eth1", "global")
+              client.succeed(f"ping -c 1 {client_ip} >&2")
+              client.succeed(f"ping -c 1 {server_ip} >&2")
+              client.succeed(f"curl --fail -g http://[{server_ip}]")
+              client.fail(f"curl --fail -g http://[{client_ip}]")
+
+      with subtest(
+          "Privacy extensions: Global temporary address is used as default source address"
+      ):
+          ip = wait_for_address(client_defaults, "eth1", "global", temporary=True)
           # Default route should have "src <temporary address>" in it
-          client.succeed(f"ip r g ::2 | grep {ip}")
-
-      # TODO: test reachability of a machine on another network.
+          client_defaults.succeed(f"ip route get 2001:db8:: | grep 'src {ip}'")
+
+      for client, setting_desc in (
+          (client_global_setting, "global"),
+          (client_interface_setting, "interface"),
+      ):
+          with subtest(f'Privacy extensions: "enabled" through {setting_desc} setting)'):
+              # We should be obtaining both a temporary address and an EUI-64 address...
+              ip = wait_for_address(client, "eth1", "global")
+              assert "ff:fe" in ip
+              ip_temp = wait_for_address(client, "eth1", "global", temporary=True)
+              # But using the EUI-64 one.
+              client.succeed(f"ip route get 2001:db8:: | grep 'src {ip}'")
     '';
 })