diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index 7af3160e2d4..f9410d75922 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -46,6 +46,51 @@ 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 [{"${device}"=interfaces.device; _iName=device;}] ++ mapAttrsToList (n: v: v//{_iName=n;}) (filterAttrs (n: _: n!=device) interfaces)
+ else mapAttrsToList (n: v: v // {_iName = n;}) interfaces;
+
+ # udev script that configures a physical wlan device and adds virtual interfaces
+ wlanDeviceUdevScript = device: interfaceList: pkgs.writeScript "wlan-${device}-udev-script" ''
+ #!${pkgs.stdenv.shell}
+
+ # Change the wireless phy device to a predictable name.
+ if [ -e "/sys/class/net/${device}/phy80211/name" ]; then
+ ${pkgs.iw}/bin/iw phy `${pkgs.coreutils}/bin/cat /sys/class/net/${device}/phy80211/name` set name ${device} || true
+ fi
+
+ # Crate new, virtual interfaces and configure them at the same time
+ ${flip concatMapStrings (drop 1 interfaceList) (i: ''
+ ${pkgs.iw}/bin/iw dev ${device} interface add ${i._iName} type ${i.type} \
+ ${optionalString (i.type == "mesh" && i.meshID != null) "mesh_id ${i.meshID}"} \
+ ${optionalString (i.type == "monitor" && i.flags != null) "flags ${i.flags}"} \
+ ${optionalString (i.type == "managed" && i.fourAddr != null) "4addr ${if i.fourAddr then "on" else "off"}"} \
+ ${optionalString (i.mac != null) "addr ${i.mac}"}
+ '')}
+
+ # Reconfigure and rename the default interface that already exists
+ ${flip concatMapStrings (take 1 interfaceList) (i: ''
+ ${pkgs.iw}/bin/iw dev ${device} set type ${i.type}
+ ${optionalString (i.type == "mesh" && i.meshID != null) "${pkgs.iw}/bin/iw dev ${device} set meshid ${i.meshID}"}
+ ${optionalString (i.type == "monitor" && i.flags != null) "${pkgs.iw}/bin/iw dev ${device} set monitor ${i.flags}"}
+ ${optionalString (i.type == "managed" && i.fourAddr != null) "${pkgs.iw}/bin/iw dev ${device} set 4addr ${if i.fourAddr then "on" else "off"}"}
+ ${optionalString (i.mac != null) "${pkgs.iproute}/bin/ip link set dev ${device} address ${i.mac}"}
+ ${optionalString (device != i._iName) "${pkgs.iproute}/bin/ip link set dev ${device} name ${i._iName}"}
+ '')}
+ '';
+
# We must escape interfaces due to the systemd interpretation
subsystemDevice = interface:
"sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
@@ -688,6 +733,110 @@ in
};
};
+ networking.wlanInterfaces = mkOption {
+ default = { };
+ example = {
+ "wlan-station0" = {
+ device = "wlp6s0";
+ };
+ "wlan-adhoc0" = {
+ type = "ibss";
+ device = "wlp6s0";
+ mac = "02:00:00:00:00:01";
+ };
+ "wlan-p2p0" = {
+ device = "wlp6s0";
+ mac = "02:00:00:00:00:02";
+ };
+ "wlan-ap0" = {
+ device = "wlp6s0";
+ mac = "02:00:00:00:00:03";
+ };
+ };
+ description =
+ ''
+ Creating multiple WLAN interfaces on top of one physical WLAN device (NIC).
+
+ The name of the WLAN interface corresponds to the name of the attribute.
+ A NIC is referenced by the persistent device name of the WLAN interface that
+ udev assigns to a NIC by default.
+ If a NIC supports multiple WLAN interfaces, then the one NIC can be used as
+ device for multiple WLAN interfaces.
+ If a NIC is used for creating WLAN interfaces, then the default WLAN interface
+ with a persistent device name form udev is not created.
+ A WLAN interface with the persistent name assigned from udev
+ would have to be created explicitly.
+ '';
+
+ type = types.attrsOf types.optionSet;
+
+ options = {
+
+ device = mkOption {
+ type = types.string;
+ example = "wlp6s0";
+ description = "The name of the underlying hardware WLAN device as assigned by udev.";
+ };
+
+ type = mkOption {
+ type = types.string;
+ default = "managed";
+ example = "ibss";
+ description = ''
+ The type of the WLAN interface. The type has to be either managed,
+ ibss, monitor, mesh or wds.
+ Also, the type has to be supported by the underlying hardware of the device.
+ '';
+ };
+
+ meshID = mkOption {
+ type = types.nullOr types.string;
+ default = null;
+ description = "MeshID of interface with type mesh.";
+ };
+
+ flags = mkOption {
+ type = types.nullOr types.string;
+ default = null;
+ example = "control";
+ description = ''
+ Flags for interface of type monitor. The valid flags are:
+ none: no special flags
+ fcsfail: show frames with FCS errors
+ control: show control frames
+ otherbss: show frames from other BSSes
+ cook: use cooked mode
+ active: use active mode (ACK incoming unicast packets)
+ '';
+ };
+
+ fourAddr = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Whether to enable 4-address mode with type managed.";
+ };
+
+ mac = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "02:00:00:00:00:01";
+ description = ''
+ MAC address to use for the device. If null, then the MAC of the
+ underlying hardware WLAN device is used.
+
+ INFO: Locally administered MAC addresses are of the form:
+
+ x2:xx:xx:xx:xx:xx
+ x6:xx:xx:xx:xx:xx
+ xA:xx:xx:xx:xx:xx
+ xE:xx:xx:xx:xx:xx
+
+ '';
+ };
+
+ };
+ };
+
networking.useDHCP = mkOption {
type = types.bool;
default = true;
@@ -844,6 +993,25 @@ in
virtualisation.vswitch = mkIf (cfg.vswitches != { }) { enable = true; };
+ services.udev.packages = mkIf (cfg.wlanInterfaces != {}) [
+ (pkgs.writeTextFile {
+ name = "99-zzz-wlanInterfaces-last.rules";
+ destination = "/etc/udev/rules.d/99-zzz-wlanInterfaces-last.rules";
+ text = ''
+ # If persistent udev device name is not used for an interface, then do not
+ # call systemd for that udev device name and only execute the script that
+ # modifies or prepares the WLAN interfaces. All other commands that would
+ # otherwise be executed when the udev device is added, like, e.g., the calling
+ # of systemd-sysctl or the activation of wpa_supplicant is disabled when the
+ # persistend udev device name is not usef for an interface.
+ ${flip (concatMapStringsSep "\n") (attrNames wlanDeviceInterfaces) (device:
+ let script = wlanDeviceUdevScript device (wlanListDeviceFirst device wlanDeviceInterfaces."${device}"); in
+ if hasAttr device cfg.wlanInterfaces
+ then ''ACTION=="add", SUBSYSTEM=="net", NAME=="${device}", ENV{DEVTYPE}=="wlan", RUN+="${script}"''
+ else ''ACTION=="add", SUBSYSTEM=="net", NAME=="${device}", ENV{DEVTYPE}=="wlan", NAME="", TAG-="systemd", RUN:="${script}"'')}
+ '';
+ }) ];
+
};
}