at 25.11-pre 9.5 kB view raw
1{ 2 config, 3 lib, 4 utils, 5 pkgs, 6 ... 7}: 8 9with lib; 10 11let 12 13 cfg = config.networking.supplicant; 14 15 # We must escape interfaces due to the systemd interpretation 16 subsystemDevice = 17 interface: "sys-subsystem-net-devices-${utils.escapeSystemdPath interface}.device"; 18 19 serviceName = 20 iface: 21 "supplicant-${ 22 if (iface == "WLAN") then 23 "wlan@" 24 else 25 ( 26 if (iface == "LAN") then 27 "lan@" 28 else 29 (if (iface == "DBUS") then "dbus" else (replaceStrings [ " " ] [ "-" ] iface)) 30 ) 31 }"; 32 33 # TODO: Use proper privilege separation for wpa_supplicant 34 supplicantService = 35 iface: suppl: 36 let 37 deps = 38 ( 39 if (iface == "WLAN" || iface == "LAN") then 40 [ "sys-subsystem-net-devices-%i.device" ] 41 else 42 (if (iface == "DBUS") then [ "dbus.service" ] else (map subsystemDevice (splitString " " iface))) 43 ) 44 ++ optional (suppl.bridge != "") (subsystemDevice suppl.bridge); 45 46 ifaceArg = concatStringsSep " -N " (map (i: "-i${i}") (splitString " " iface)); 47 driverArg = optionalString (suppl.driver != null) "-D${suppl.driver}"; 48 bridgeArg = optionalString (suppl.bridge != "") "-b${suppl.bridge}"; 49 extraConfFile = pkgs.writeText "supplicant-extra-conf-${replaceStrings [ " " ] [ "-" ] iface}" '' 50 ${optionalString suppl.userControlled.enable "ctrl_interface=DIR=${suppl.userControlled.socketDir} GROUP=${suppl.userControlled.group}"} 51 ${optionalString suppl.configFile.writable "update_config=1"} 52 ${suppl.extraConf} 53 ''; 54 confArgs = escapeShellArgs ( 55 if suppl.configFile.path == null then 56 [ "-c${extraConfFile}" ] 57 else 58 [ 59 "-c${suppl.configFile.path}" 60 "-I${extraConfFile}" 61 ] 62 ); 63 in 64 { 65 description = "Supplicant ${iface}${optionalString (iface == "WLAN" || iface == "LAN") " %I"}"; 66 wantedBy = [ "multi-user.target" ] ++ deps; 67 wants = [ "network.target" ]; 68 bindsTo = deps; 69 after = deps; 70 before = [ "network.target" ]; 71 72 path = [ pkgs.coreutils ]; 73 74 preStart = '' 75 ${optionalString (suppl.configFile.path != null && suppl.configFile.writable) '' 76 (umask 077 && touch -a "${suppl.configFile.path}") 77 ''} 78 ${optionalString suppl.userControlled.enable '' 79 install -dm770 -g "${suppl.userControlled.group}" "${suppl.userControlled.socketDir}" 80 ''} 81 ''; 82 83 serviceConfig.ExecStart = "${pkgs.wpa_supplicant}/bin/wpa_supplicant -s ${driverArg} ${confArgs} ${bridgeArg} ${suppl.extraCmdArgs} ${ 84 if (iface == "WLAN" || iface == "LAN") then 85 "-i%I" 86 else 87 (if (iface == "DBUS") then "-u" else ifaceArg) 88 }"; 89 90 }; 91 92in 93 94{ 95 96 ###### interface 97 98 options = { 99 100 networking.supplicant = mkOption { 101 type = 102 with types; 103 attrsOf (submodule { 104 options = { 105 106 configFile = { 107 108 path = mkOption { 109 type = types.nullOr types.path; 110 default = null; 111 example = literalExpression "/etc/wpa_supplicant.conf"; 112 description = '' 113 External `wpa_supplicant.conf` configuration file. 114 The configuration options defined declaratively within `networking.supplicant` have 115 precedence over options defined in `configFile`. 116 ''; 117 }; 118 119 writable = mkOption { 120 type = types.bool; 121 default = false; 122 description = '' 123 Whether the configuration file at `configFile.path` should be written to by 124 `wpa_supplicant`. 125 ''; 126 }; 127 128 }; 129 130 extraConf = mkOption { 131 type = types.lines; 132 default = ""; 133 example = '' 134 ap_scan=1 135 device_name=My-NixOS-Device 136 device_type=1-0050F204-1 137 driver_param=use_p2p_group_interface=1 138 disable_scan_offload=1 139 p2p_listen_reg_class=81 140 p2p_listen_channel=1 141 p2p_oper_reg_class=81 142 p2p_oper_channel=1 143 manufacturer=NixOS 144 model_name=NixOS_Unstable 145 model_number=2015 146 ''; 147 description = '' 148 Configuration options for `wpa_supplicant.conf`. 149 Options defined here have precedence over options in `configFile`. 150 NOTE: Do not write sensitive data into `extraConf` as it will 151 be world-readable in the `nix-store`. For sensitive information 152 use the `configFile` instead. 153 ''; 154 }; 155 156 extraCmdArgs = mkOption { 157 type = types.str; 158 default = ""; 159 example = "-e/run/wpa_supplicant/entropy.bin"; 160 description = "Command line arguments to add when executing `wpa_supplicant`."; 161 }; 162 163 driver = mkOption { 164 type = types.nullOr types.str; 165 default = "nl80211,wext"; 166 description = "Force a specific wpa_supplicant driver."; 167 }; 168 169 bridge = mkOption { 170 type = types.str; 171 default = ""; 172 description = "Name of the bridge interface that wpa_supplicant should listen at."; 173 }; 174 175 userControlled = { 176 177 enable = mkOption { 178 type = types.bool; 179 default = false; 180 description = '' 181 Allow normal users to control wpa_supplicant through wpa_gui or wpa_cli. 182 This is useful for laptop users that switch networks a lot and don't want 183 to depend on a large package such as NetworkManager just to pick nearby 184 access points. 185 ''; 186 }; 187 188 socketDir = mkOption { 189 type = types.str; 190 default = "/run/wpa_supplicant"; 191 description = "Directory of sockets for controlling wpa_supplicant."; 192 }; 193 194 group = mkOption { 195 type = types.str; 196 default = "wheel"; 197 example = "network"; 198 description = "Members of this group can control wpa_supplicant."; 199 }; 200 201 }; 202 }; 203 }); 204 205 default = { }; 206 207 example = literalExpression '' 208 { "wlan0 wlan1" = { 209 configFile.path = "/etc/wpa_supplicant.conf"; 210 userControlled.group = "network"; 211 extraConf = ''' 212 ap_scan=1 213 p2p_disabled=1 214 '''; 215 extraCmdArgs = "-u -W"; 216 bridge = "br0"; 217 }; 218 } 219 ''; 220 221 description = '' 222 Interfaces for which to start {command}`wpa_supplicant`. 223 The supplicant is used to scan for and associate with wireless networks, 224 or to authenticate with 802.1x capable network switches. 225 226 The value of this option is an attribute set. Each attribute configures a 227 {command}`wpa_supplicant` service, where the attribute name specifies 228 the name of the interface that {command}`wpa_supplicant` operates on. 229 The attribute name can be a space separated list of interfaces. 230 The attribute names `WLAN`, `LAN` and `DBUS` 231 have a special meaning. `WLAN` and `LAN` are 232 configurations for universal {command}`wpa_supplicant` service that is 233 started for each WLAN interface or for each LAN interface, respectively. 234 `DBUS` defines a device-unrelated {command}`wpa_supplicant` 235 service that can be accessed through `D-Bus`. 236 ''; 237 238 }; 239 240 }; 241 242 ###### implementation 243 244 config = mkIf (cfg != { }) { 245 246 environment.systemPackages = [ pkgs.wpa_supplicant ]; 247 248 services.dbus.packages = [ pkgs.wpa_supplicant ]; 249 250 systemd.services = mapAttrs' (n: v: nameValuePair (serviceName n) (supplicantService n v)) cfg; 251 252 services.udev.packages = [ 253 (pkgs.writeTextFile { 254 name = "99-zzz-60-supplicant.rules"; 255 destination = "/etc/udev/rules.d/99-zzz-60-supplicant.rules"; 256 text = '' 257 ${flip (concatMapStringsSep "\n") 258 (filter (n: n != "WLAN" && n != "LAN" && n != "DBUS") (attrNames cfg)) 259 ( 260 iface: 261 flip (concatMapStringsSep "\n") (splitString " " iface) ( 262 i: 263 ''ACTION=="add", SUBSYSTEM=="net", ENV{INTERFACE}=="${i}", TAG+="systemd", ENV{SYSTEMD_WANTS}+="supplicant-${ 264 replaceStrings [ " " ] [ "-" ] iface 265 }.service", TAG+="SUPPLICANT_ASSIGNED"'' 266 ) 267 ) 268 } 269 270 ${optionalString (hasAttr "WLAN" cfg) '' 271 ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", TAG!="SUPPLICANT_ASSIGNED", TAG+="systemd", PROGRAM="/run/current-system/systemd/bin/systemd-escape -p %E{INTERFACE}", ENV{SYSTEMD_WANTS}+="supplicant-wlan@$result.service" 272 ''} 273 ${optionalString (hasAttr "LAN" cfg) '' 274 ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="lan", TAG!="SUPPLICANT_ASSIGNED", TAG+="systemd", PROGRAM="/run/current-system/systemd/bin/systemd-escape -p %E{INTERFACE}", ENV{SYSTEMD_WANTS}+="supplicant-lan@$result.service" 275 ''} 276 ''; 277 }) 278 ]; 279 280 }; 281 282}