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