nixos/network-interfaces: let networkd handle privacy extensions (#431967)

This commit is contained in:
Ryan Lahfa 2025-08-09 17:53:39 +02:00 committed by GitHub
commit bcc20cad16
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 146 additions and 137 deletions

View File

@ -1884,6 +1884,11 @@ let
networkdOptions = {
networkConfig = mkOption {
default = { };
defaultText = lib.literalExpression ''
{
IPv6PrivacyExtensions = true;
}
'';
example = {
SpeedMeter = true;
ManageForeignRoutingPolicyRules = false;
@ -3127,7 +3132,10 @@ let
};
config = {
networkConfig = optionalAttrs (config.routeTables != { }) {
networkConfig = {
IPv6PrivacyExtensions = lib.mkOptionDefault true;
}
// optionalAttrs (config.routeTables != { }) {
RouteTable = mapAttrsToList (name: number: "${name}:${toString number}") config.routeTables;
};
};

View File

@ -1813,146 +1813,147 @@ in
virtualisation.vswitch = mkIf (cfg.vswitches != { }) { enable = true; };
services.udev.packages = [
(pkgs.writeTextFile rec {
name = "ipv6-privacy-extensions.rules";
destination = "/etc/udev/rules.d/98-${name}";
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 ${sysctl-value} > /proc/sys/net/ipv6/conf/$name/use_tempaddr'"
'';
})
(pkgs.writeTextFile rec {
name = "ipv6-privacy-extensions.rules";
destination = "/etc/udev/rules.d/99-${name}";
text = concatMapStrings (
i:
let
opt = i.tempAddress;
val = tempaddrValues.${opt}.sysctl;
msg = tempaddrValues.${opt}.description;
in
''
# override to ${msg} for ${i.name}
ACTION=="add", SUBSYSTEM=="net", NAME=="${i.name}", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.${
replaceStrings [ "." ] [ "/" ] i.name
}.use_tempaddr=${val}"
''
) (filter (i: i.tempAddress != cfg.tempAddresses) interfaces);
})
]
++ lib.optional (cfg.wlanInterfaces != { }) (
pkgs.writeTextFile {
name = "99-zzz-40-wlanInterfaces.rules";
destination = "/etc/udev/rules.d/99-zzz-40-wlanInterfaces.rules";
text =
let
# Collect all interfaces that are defined for a device
# as device:interface key:value pairs.
wlanDeviceInterfaces =
let
allDevices = unique (mapAttrsToList (_: v: v.device) cfg.wlanInterfaces);
interfacesOfDevice = d: filterAttrs (_: v: v.device == d) cfg.wlanInterfaces;
in
genAttrs allDevices (d: interfacesOfDevice d);
# Convert device:interface key:value pairs into a list, and if it exists,
# place the interface which is named after the device at the beginning.
wlanListDeviceFirst =
device: interfaces:
if hasAttr device interfaces then
mapAttrsToList (n: v: v // { _iName = n; }) (filterAttrs (n: _: n == device) interfaces)
++ mapAttrsToList (n: v: v // { _iName = n; }) (filterAttrs (n: _: n != device) interfaces)
else
mapAttrsToList (n: v: v // { _iName = n; }) interfaces;
# Udev script to execute for the default WLAN interface with the persistend udev name.
# The script creates the required, new WLAN interfaces interfaces and configures the
# existing, default interface.
curInterfaceScript =
device: current: new:
pkgs.writeScript "udev-run-script-wlan-interfaces-${device}.sh" ''
#!${pkgs.runtimeShell}
# Change the wireless phy device to a predictable name.
${pkgs.iw}/bin/iw phy `${pkgs.coreutils}/bin/cat /sys/class/net/$INTERFACE/phy80211/name` set name ${device}
# Add new WLAN interfaces
${flip concatMapStrings new (i: ''
${pkgs.iw}/bin/iw phy ${device} interface add ${i._iName} type managed
'')}
# Configure the current interface
${pkgs.iw}/bin/iw dev ${device} set type ${current.type}
${optionalString (
current.type == "mesh" && current.meshID != null
) "${pkgs.iw}/bin/iw dev ${device} set meshid ${current.meshID}"}
${optionalString (
current.type == "monitor" && current.flags != null
) "${pkgs.iw}/bin/iw dev ${device} set monitor ${current.flags}"}
${optionalString (
current.type == "managed" && current.fourAddr != null
) "${pkgs.iw}/bin/iw dev ${device} set 4addr ${if current.fourAddr then "on" else "off"}"}
${optionalString (
current.mac != null
) "${pkgs.iproute2}/bin/ip link set dev ${device} address ${current.mac}"}
'';
# Udev script to execute for a new WLAN interface. The script configures the new WLAN interface.
newInterfaceScript =
new:
pkgs.writeScript "udev-run-script-wlan-interfaces-${new._iName}.sh" ''
#!${pkgs.runtimeShell}
# Configure the new interface
${pkgs.iw}/bin/iw dev ${new._iName} set type ${new.type}
${optionalString (
new.type == "mesh" && new.meshID != null
) "${pkgs.iw}/bin/iw dev ${new._iName} set meshid ${new.meshID}"}
${optionalString (
new.type == "monitor" && new.flags != null
) "${pkgs.iw}/bin/iw dev ${new._iName} set monitor ${new.flags}"}
${optionalString (
new.type == "managed" && new.fourAddr != null
) "${pkgs.iw}/bin/iw dev ${new._iName} set 4addr ${if new.fourAddr then "on" else "off"}"}
${optionalString (
new.mac != null
) "${pkgs.iproute2}/bin/ip link set dev ${new._iName} address ${new.mac}"}
'';
# Udev attributes for systemd to name the device and to create a .device target.
systemdAttrs =
n:
''NAME:="${n}", ENV{INTERFACE}="${n}", ENV{SYSTEMD_ALIAS}="/sys/subsystem/net/devices/${n}", TAG+="systemd"'';
in
flip (concatMapStringsSep "\n") (attrNames wlanDeviceInterfaces) (
device:
services.udev.packages =
lib.optionals (!config.systemd.network.enable) [
(pkgs.writeTextFile rec {
name = "ipv6-privacy-extensions.rules";
destination = "/etc/udev/rules.d/98-${name}";
text =
let
interfaces = wlanListDeviceFirst device wlanDeviceInterfaces.${device};
curInterface = elemAt interfaces 0;
newInterfaces = drop 1 interfaces;
sysctl-value = tempaddrValues.${cfg.tempAddresses}.sysctl;
in
''
# It is important to have that rule first as overwriting the NAME attribute also prevents the
# next rules from matching.
${flip (concatMapStringsSep "\n") (wlanListDeviceFirst device wlanDeviceInterfaces.${device}) (
interface:
''ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", ENV{INTERFACE}=="${interface._iName}", ${systemdAttrs interface._iName}, RUN+="${newInterfaceScript interface}"''
)}
# Add the required, new WLAN interfaces to the default WLAN interface with the
# persistent, default name as assigned by udev.
ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", NAME=="${device}", ${systemdAttrs curInterface._iName}, RUN+="${
curInterfaceScript device curInterface newInterfaces
}"
# Generate the same systemd events for both 'add' and 'move' udev events.
ACTION=="move", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", NAME=="${device}", ${systemdAttrs curInterface._iName}
# enable and prefer IPv6 privacy addresses by default
ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.bash}/bin/sh -c 'echo ${sysctl-value} > /proc/sys/net/ipv6/conf/$name/use_tempaddr'"
'';
})
(pkgs.writeTextFile rec {
name = "ipv6-privacy-extensions.rules";
destination = "/etc/udev/rules.d/99-${name}";
text = concatMapStrings (
i:
let
opt = i.tempAddress;
val = tempaddrValues.${opt}.sysctl;
msg = tempaddrValues.${opt}.description;
in
''
);
}
);
# override to ${msg} for ${i.name}
ACTION=="add", SUBSYSTEM=="net", NAME=="${i.name}", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.${
replaceStrings [ "." ] [ "/" ] i.name
}.use_tempaddr=${val}"
''
) (filter (i: i.tempAddress != cfg.tempAddresses) interfaces);
})
]
++ lib.optional (cfg.wlanInterfaces != { }) (
pkgs.writeTextFile {
name = "99-zzz-40-wlanInterfaces.rules";
destination = "/etc/udev/rules.d/99-zzz-40-wlanInterfaces.rules";
text =
let
# Collect all interfaces that are defined for a device
# as device:interface key:value pairs.
wlanDeviceInterfaces =
let
allDevices = unique (mapAttrsToList (_: v: v.device) cfg.wlanInterfaces);
interfacesOfDevice = d: filterAttrs (_: v: v.device == d) cfg.wlanInterfaces;
in
genAttrs allDevices (d: interfacesOfDevice d);
# Convert device:interface key:value pairs into a list, and if it exists,
# place the interface which is named after the device at the beginning.
wlanListDeviceFirst =
device: interfaces:
if hasAttr device interfaces then
mapAttrsToList (n: v: v // { _iName = n; }) (filterAttrs (n: _: n == device) interfaces)
++ mapAttrsToList (n: v: v // { _iName = n; }) (filterAttrs (n: _: n != device) interfaces)
else
mapAttrsToList (n: v: v // { _iName = n; }) interfaces;
# Udev script to execute for the default WLAN interface with the persistend udev name.
# The script creates the required, new WLAN interfaces interfaces and configures the
# existing, default interface.
curInterfaceScript =
device: current: new:
pkgs.writeScript "udev-run-script-wlan-interfaces-${device}.sh" ''
#!${pkgs.runtimeShell}
# Change the wireless phy device to a predictable name.
${pkgs.iw}/bin/iw phy `${pkgs.coreutils}/bin/cat /sys/class/net/$INTERFACE/phy80211/name` set name ${device}
# Add new WLAN interfaces
${flip concatMapStrings new (i: ''
${pkgs.iw}/bin/iw phy ${device} interface add ${i._iName} type managed
'')}
# Configure the current interface
${pkgs.iw}/bin/iw dev ${device} set type ${current.type}
${optionalString (
current.type == "mesh" && current.meshID != null
) "${pkgs.iw}/bin/iw dev ${device} set meshid ${current.meshID}"}
${optionalString (
current.type == "monitor" && current.flags != null
) "${pkgs.iw}/bin/iw dev ${device} set monitor ${current.flags}"}
${optionalString (
current.type == "managed" && current.fourAddr != null
) "${pkgs.iw}/bin/iw dev ${device} set 4addr ${if current.fourAddr then "on" else "off"}"}
${optionalString (
current.mac != null
) "${pkgs.iproute2}/bin/ip link set dev ${device} address ${current.mac}"}
'';
# Udev script to execute for a new WLAN interface. The script configures the new WLAN interface.
newInterfaceScript =
new:
pkgs.writeScript "udev-run-script-wlan-interfaces-${new._iName}.sh" ''
#!${pkgs.runtimeShell}
# Configure the new interface
${pkgs.iw}/bin/iw dev ${new._iName} set type ${new.type}
${optionalString (
new.type == "mesh" && new.meshID != null
) "${pkgs.iw}/bin/iw dev ${new._iName} set meshid ${new.meshID}"}
${optionalString (
new.type == "monitor" && new.flags != null
) "${pkgs.iw}/bin/iw dev ${new._iName} set monitor ${new.flags}"}
${optionalString (
new.type == "managed" && new.fourAddr != null
) "${pkgs.iw}/bin/iw dev ${new._iName} set 4addr ${if new.fourAddr then "on" else "off"}"}
${optionalString (
new.mac != null
) "${pkgs.iproute2}/bin/ip link set dev ${new._iName} address ${new.mac}"}
'';
# Udev attributes for systemd to name the device and to create a .device target.
systemdAttrs =
n:
''NAME:="${n}", ENV{INTERFACE}="${n}", ENV{SYSTEMD_ALIAS}="/sys/subsystem/net/devices/${n}", TAG+="systemd"'';
in
flip (concatMapStringsSep "\n") (attrNames wlanDeviceInterfaces) (
device:
let
interfaces = wlanListDeviceFirst device wlanDeviceInterfaces.${device};
curInterface = elemAt interfaces 0;
newInterfaces = drop 1 interfaces;
in
''
# It is important to have that rule first as overwriting the NAME attribute also prevents the
# next rules from matching.
${flip (concatMapStringsSep "\n") (wlanListDeviceFirst device wlanDeviceInterfaces.${device}) (
interface:
''ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", ENV{INTERFACE}=="${interface._iName}", ${systemdAttrs interface._iName}, RUN+="${newInterfaceScript interface}"''
)}
# Add the required, new WLAN interfaces to the default WLAN interface with the
# persistent, default name as assigned by udev.
ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", NAME=="${device}", ${systemdAttrs curInterface._iName}, RUN+="${
curInterfaceScript device curInterface newInterfaces
}"
# Generate the same systemd events for both 'add' and 'move' udev events.
ACTION=="move", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", NAME=="${device}", ${systemdAttrs curInterface._iName}
''
);
}
);
};
}