diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix index 051c55393816..eea10613ea58 100644 --- a/nixos/modules/system/boot/networkd.nix +++ b/nixos/modules/system/boot/networkd.nix @@ -94,7 +94,7 @@ let checkNetwork = checkUnitConfig "Network" [ (assertOnlyFields [ "Description" "DHCP" "DHCPServer" "IPForward" "IPMasquerade" "IPv4LL" "IPv4LLRoute" - "LLMNR" "MulticastDNS" "Domains" "Bridge" "Bond" + "LLMNR" "MulticastDNS" "Domains" "Bridge" "Bond" "IPv6PrivacyExtensions" ]) (assertValueOneOf "DHCP" ["both" "none" "v4" "v6"]) (assertValueOneOf "DHCPServer" boolValues) @@ -104,6 +104,7 @@ let (assertValueOneOf "IPv4LLRoute" boolValues) (assertValueOneOf "LLMNR" boolValues) (assertValueOneOf "MulticastDNS" boolValues) + (assertValueOneOf "IPv6PrivacyExtensions" ["yes" "no" "prefer-public" "kernel"]) ]; checkAddress = checkUnitConfig "Address" [ diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix index 5d72ad0f1bde..be7f52a76def 100644 --- a/nixos/modules/tasks/network-interfaces-systemd.nix +++ b/nixos/modules/tasks/network-interfaces-systemd.nix @@ -91,6 +91,7 @@ in (if i.useDHCP != null then i.useDHCP else cfg.useDHCP && interfaceIps i == [ ])); address = flip map (interfaceIps i) (ip: "${ip.address}/${toString ip.prefixLength}"); + networkConfig.IPv6PrivacyExtensions = "kernel"; } ]; }))) (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: { diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index f4851988d63d..f80c5045c07d 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -155,6 +155,16 @@ let description = "Name of the interface."; }; + preferTempAddress = mkOption { + type = types.bool; + default = cfg.enableIPv6; + defaultText = literalExample "config.networking.enableIpv6"; + description = '' + When using SLAAC prefer a temporary (IPv6) address over the EUI-64 + address for originating connections. This is used to reduce tracking. + ''; + }; + useDHCP = mkOption { type = types.nullOr types.bool; default = null; @@ -941,6 +951,11 @@ in message = '' The networking.interfaces."${i.name}" must not have any defined ips when it is a slave. ''; + })) ++ (flip map interfaces (i: { + assertion = i.preferTempAddress -> cfg.enableIPv6; + message = '' + Temporary addresses are only needed when IPv6 is enabled. + ''; })) ++ [ { assertion = cfg.hostId == null || (stringLength cfg.hostId == 8 && isHexString cfg.hostId); @@ -963,9 +978,10 @@ in "net.ipv6.conf.all.disable_ipv6" = mkDefault (!cfg.enableIPv6); "net.ipv6.conf.default.disable_ipv6" = mkDefault (!cfg.enableIPv6); "net.ipv6.conf.all.forwarding" = mkDefault (any (i: i.proxyARP) interfaces); - } // listToAttrs (concatLists (flip map (filter (i: i.proxyARP) interfaces) - (i: flip map [ "4" "6" ] (v: nameValuePair "net.ipv${v}.conf.${i.name}.proxy_arp" true)) - )); + } // listToAttrs (flip concatMap (filter (i: i.proxyARP) interfaces) + (i: flip map [ "4" "6" ] (v: nameValuePair "net.ipv${v}.conf.${i.name}.proxy_arp" true))) + // listToAttrs (flip map (filter (i: i.preferTempAddress) interfaces) + (i: nameValuePair "net.ipv6.conf.${i.name}.use_tempaddr" 2)); # Capabilities won't work unless we have at-least a 4.3 Linux # kernel because we need the ambient capability diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix index 182328b32962..fa3dc0538729 100644 --- a/nixos/tests/networking.nix +++ b/nixos/tests/networking.nix @@ -476,6 +476,63 @@ let ); ''; }; + privacy = { + name = "Privacy"; + nodes.router = { config, pkgs, ... }: { + virtualisation.vlans = [ 1 ]; + boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true; + networking = { + useNetworkd = networkd; + interfaces.eth1 = { + ipv6Address = "fd00:1234:5678:1::1"; + ipv6PrefixLength = 64; + }; + }; + services.radvd = { + enable = true; + config = '' + interface eth1 { + AdvSendAdvert on; + AdvManagedFlag on; + AdvOtherConfigFlag on; + + prefix fd00:1234:5678:1::/64 { + AdvAutonomous on; + AdvOnLink on; + }; + }; + ''; + }; + }; + nodes.client = { config, pkgs, ... }: with pkgs.lib; { + virtualisation.vlans = [ 1 ]; + networking = { + useNetworkd = networkd; + useDHCP = true; + interfaces.eth1 = { + preferTempAddress = true; + ip4 = mkOverride 0 [ ]; + ip6 = mkOverride 0 [ ]; + }; + }; + }; + testScript = { nodes, ... }: + '' + startAll; + + $client->waitForUnit("network.target"); + $router->waitForUnit("network-online.target"); + + # Wait until we have an ip address + $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'"); + + # Test vlan 1 + $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1"); + + # Test address used is temporary + $client->succeed("! ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'"); + ''; + }; }; in mapAttrs (const (attrs: makeTest (attrs // {