···144144 };
145145146146 tempAddress = mkOption {
147147- type = types.enum [ "default" "enabled" "disabled" ];
148148- default = if cfg.enableIPv6 then "default" else "disabled";
149149- defaultText = literalExample ''if cfg.enableIPv6 then "default" else "disabled"'';
147147+ type = types.enum (lib.attrNames tempaddrValues);
148148+ default = cfg.tempAddresses;
149149+ defaultText = literalExample ''config.networking.tempAddresses'';
150150 description = ''
151151 When IPv6 is enabled with SLAAC, this option controls the use of
152152- temporary address (aka privacy extensions). This is used to reduce tracking.
153153- The three possible values are:
152152+ temporary address (aka privacy extensions) on this
153153+ interface. This is used to reduce tracking.
154154+155155+ See also the global option
156156+ <xref linkend="opt-networking.tempAddresses"/>, which
157157+ applies to all interfaces where this is not set.
154158155155- <itemizedlist>
156156- <listitem>
157157- <para>
158158- <literal>"default"</literal> to generate temporary addresses and use
159159- them by default;
160160- </para>
161161- </listitem>
162162- <listitem>
163163- <para>
164164- <literal>"enabled"</literal> to generate temporary addresses but keep
165165- using the standard EUI-64 ones by default;
166166- </para>
167167- </listitem>
168168- <listitem>
169169- <para>
170170- <literal>"disabled"</literal> to completely disable temporary addresses.
171171- </para>
172172- </listitem>
173173- </itemizedlist>
159159+ Possible values are:
160160+ ${tempaddrDoc}
174161 '';
175162 };
176163···365352 hexChars = stringToCharacters "0123456789abcdef";
366353367354 isHexString = s: all (c: elem c hexChars) (stringToCharacters (toLower s));
355355+356356+ tempaddrValues = {
357357+ disabled = {
358358+ sysctl = "0";
359359+ description = "completely disable IPv6 temporary addresses";
360360+ };
361361+ enabled = {
362362+ sysctl = "1";
363363+ description = "generate IPv6 temporary addresses but still use EUI-64 addresses as source addresses";
364364+ };
365365+ default = {
366366+ sysctl = "2";
367367+ description = "generate IPv6 temporary addresses and use these as source addresses in routing";
368368+ };
369369+ };
370370+ tempaddrDoc = ''
371371+ <itemizedlist>
372372+ ${concatStringsSep "\n" (mapAttrsToList (name: { description, ... }: ''
373373+ <listitem>
374374+ <para>
375375+ <literal>"${name}"</literal> to ${description};
376376+ </para>
377377+ </listitem>
378378+ '') tempaddrValues)}
379379+ </itemizedlist>
380380+ '';
368381369382in
370383···10391052 '';
10401053 };
1041105410551055+ networking.tempAddresses = mkOption {
10561056+ default = if cfg.enableIPv6 then "default" else "disabled";
10571057+ type = types.enum (lib.attrNames tempaddrValues);
10581058+ description = ''
10591059+ Whether to enable IPv6 Privacy Extensions for interfaces not
10601060+ configured explicitly in
10611061+ <xref linkend="opt-networking.interfaces._name_.tempAddress" />.
10621062+10631063+ This sets the ipv6.conf.*.use_tempaddr sysctl for all
10641064+ interfaces. Possible values are:
10651065+10661066+ ${tempaddrDoc}
10671067+ '';
10681068+ };
10691069+10421070 };
1043107110441072···10981126 // listToAttrs (forEach interfaces
10991127 (i: let
11001128 opt = i.tempAddress;
11011101- val = { disabled = 0; enabled = 1; default = 2; }.${opt};
11291129+ val = tempaddrValues.${opt}.sysctl;
11021130 in nameValuePair "net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr" val));
1103113111041132 # Capabilities won't work unless we have at-least a 4.3 Linux
···11881216 (pkgs.writeTextFile rec {
11891217 name = "ipv6-privacy-extensions.rules";
11901218 destination = "/etc/udev/rules.d/98-${name}";
11911191- text = ''
12191219+ text = let
12201220+ sysctl-value = tempaddrValues.${cfg.tempAddresses}.sysctl;
12211221+ in ''
11921222 # enable and prefer IPv6 privacy addresses by default
11931193- ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.bash}/bin/sh -c 'echo 2 > /proc/sys/net/ipv6/conf/%k/use_tempaddr'"
12231223+ ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.bash}/bin/sh -c 'echo ${sysctl-value} > /proc/sys/net/ipv6/conf/%k/use_tempaddr'"
11941224 '';
11951225 })
11961226 (pkgs.writeTextFile rec {
···11991229 text = concatMapStrings (i:
12001230 let
12011231 opt = i.tempAddress;
12021202- val = if opt == "disabled" then 0 else 1;
12031203- msg = if opt == "disabled"
12041204- then "completely disable IPv6 privacy addresses"
12051205- else "enable IPv6 privacy addresses but prefer EUI-64 addresses";
12321232+ val = tempaddrValues.${opt}.sysctl;
12331233+ msg = tempaddrValues.${opt}.description;
12061234 in
12071235 ''
12081236 # override to ${msg} for ${i.name}
12091209- ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr=${toString val}"
12101210- '') (filter (i: i.tempAddress != "default") interfaces);
12371237+ ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr=${val}"
12381238+ '') (filter (i: i.tempAddress != cfg.tempAddresses) interfaces);
12111239 })
12121240 ] ++ lib.optional (cfg.wlanInterfaces != {})
12131241 (pkgs.writeTextFile {
+60-21
nixos/tests/ipv6.nix
···88 };
991010 nodes =
1111- # Remove the interface configuration provided by makeTest so that the
1212- # interfaces are all configured implicitly
1313- { client = { ... }: { networking.interfaces = lib.mkForce {}; };
1111+ {
1212+ # We use lib.mkForce here to remove the interface configuration
1313+ # provided by makeTest, so that the interfaces are all configured
1414+ # implicitly.
1515+1616+ # This client should use privacy extensions fully, having a
1717+ # completely-default network configuration.
1818+ client_defaults.networking.interfaces = lib.mkForce {};
1919+2020+ # Both of these clients should obtain temporary addresses, but
2121+ # not use them as the default source IP. We thus run the same
2222+ # checks against them — but the configuration resulting in this
2323+ # behaviour is different.
2424+2525+ # Here, by using an altered default value for the global setting...
2626+ client_global_setting = {
2727+ networking.interfaces = lib.mkForce {};
2828+ networking.tempAddresses = "enabled";
2929+ };
3030+ # and here, by setting this on the interface explicitly.
3131+ client_interface_setting = {
3232+ networking.tempAddresses = "disabled";
3333+ networking.interfaces = lib.mkForce {
3434+ eth1.tempAddress = "enabled";
3535+ };
3636+ };
14371538 server =
1616- { ... }:
1739 { services.httpd.enable = true;
1840 services.httpd.adminAddr = "foo@example.org";
1941 networking.firewall.allowedTCPPorts = [ 80 ];
···4062 # Start the router first so that it respond to router solicitations.
4163 router.wait_for_unit("radvd")
42646565+ clients = [client_defaults, client_global_setting, client_interface_setting]
6666+4367 start_all()
44684545- client.wait_for_unit("network.target")
6969+ for client in clients:
7070+ client.wait_for_unit("network.target")
4671 server.wait_for_unit("network.target")
4772 server.wait_for_unit("httpd.service")
4873···648965906691 with subtest("Loopback address can be pinged"):
6767- client.succeed("ping -c 1 ::1 >&2")
6868- client.fail("ping -c 1 ::2 >&2")
9292+ client_defaults.succeed("ping -c 1 ::1 >&2")
9393+ client_defaults.fail("ping -c 1 2001:db8:: >&2")
69947095 with subtest("Local link addresses can be obtained and pinged"):
7171- client_ip = wait_for_address(client, "eth1", "link")
7272- server_ip = wait_for_address(server, "eth1", "link")
7373- client.succeed(f"ping -c 1 {client_ip}%eth1 >&2")
7474- client.succeed(f"ping -c 1 {server_ip}%eth1 >&2")
9696+ for client in clients:
9797+ client_ip = wait_for_address(client, "eth1", "link")
9898+ server_ip = wait_for_address(server, "eth1", "link")
9999+ client.succeed(f"ping -c 1 {client_ip}%eth1 >&2")
100100+ client.succeed(f"ping -c 1 {server_ip}%eth1 >&2")
7510176102 with subtest("Global addresses can be obtained, pinged, and reached via http"):
7777- client_ip = wait_for_address(client, "eth1", "global")
7878- server_ip = wait_for_address(server, "eth1", "global")
7979- client.succeed(f"ping -c 1 {client_ip} >&2")
8080- client.succeed(f"ping -c 1 {server_ip} >&2")
8181- client.succeed(f"curl --fail -g http://[{server_ip}]")
8282- client.fail(f"curl --fail -g http://[{client_ip}]")
103103+ for client in clients:
104104+ client_ip = wait_for_address(client, "eth1", "global")
105105+ server_ip = wait_for_address(server, "eth1", "global")
106106+ client.succeed(f"ping -c 1 {client_ip} >&2")
107107+ client.succeed(f"ping -c 1 {server_ip} >&2")
108108+ client.succeed(f"curl --fail -g http://[{server_ip}]")
109109+ client.fail(f"curl --fail -g http://[{client_ip}]")
831108484- with subtest("Privacy extensions: Global temporary address can be obtained and pinged"):
8585- ip = wait_for_address(client, "eth1", "global", temporary=True)
111111+ with subtest(
112112+ "Privacy extensions: Global temporary address is used as default source address"
113113+ ):
114114+ ip = wait_for_address(client_defaults, "eth1", "global", temporary=True)
86115 # Default route should have "src <temporary address>" in it
8787- client.succeed(f"ip r g ::2 | grep {ip}")
116116+ client_defaults.succeed(f"ip route get 2001:db8:: | grep 'src {ip}'")
881178989- # TODO: test reachability of a machine on another network.
118118+ for client, setting_desc in (
119119+ (client_global_setting, "global"),
120120+ (client_interface_setting, "interface"),
121121+ ):
122122+ with subtest(f'Privacy extensions: "enabled" through {setting_desc} setting)'):
123123+ # We should be obtaining both a temporary address and an EUI-64 address...
124124+ ip = wait_for_address(client, "eth1", "global")
125125+ assert "ff:fe" in ip
126126+ ip_temp = wait_for_address(client, "eth1", "global", temporary=True)
127127+ # But using the EUI-64 one.
128128+ client.succeed(f"ip route get 2001:db8:: | grep 'src {ip}'")
90129 '';
91130})