From e4045513a155b564926f4bb40e508bfcac9679ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Fri, 6 May 2022 13:21:31 +0200 Subject: nixos/modules/hostapd: fork and expand hostapd --- nixos/default.nix | 19 +- nixos/modules/hostapd.nix | 552 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 568 insertions(+), 3 deletions(-) create mode 100644 nixos/modules/hostapd.nix (limited to 'nixos') diff --git a/nixos/default.nix b/nixos/default.nix index e7c8f00..79ce6e1 100644 --- a/nixos/default.nix +++ b/nixos/default.nix @@ -1,4 +1,17 @@ -{ - turris-board = import ./modules/turris-board.nix; - turris-defaults = import ./modules/turris-defaults.nix; +self: let + + modules = { + + turris-board = import ./modules/turris-board.nix; + turris-defaults = import ./modules/turris-defaults.nix; + + hostapd = import ./modules/hostapd.nix; + + }; + +in modules // { + default = { + imports = builtins.attrValues modules; + nixpkgs.overlays = [ self.overlays.default ]; + }; } diff --git a/nixos/modules/hostapd.nix b/nixos/modules/hostapd.nix new file mode 100644 index 0000000..0ca66d0 --- /dev/null +++ b/nixos/modules/hostapd.nix @@ -0,0 +1,552 @@ +{ config, lib, pkgs, utils, ... }: + +# TODO: +# +# asserts +# ensure that the nl80211 module is loaded/compiled in the kernel +# wpa_supplicant and hostapd on the same wireless interface doesn't make any sense + +with builtins; +with lib; + +let + + cfg = config.networking.hostapd; + + options_bss = { + + ssid = mkOption { + type = types.str; + default = "nixos"; + example = "mySpecialSSID"; + description = "SSID to be used in IEEE 802.11 management frames."; + }; + + wpa = mkOption { + type = types.bool; + default = true; + description = '' + Enable WPA (IEEE 802.11i/D3.0) to authenticate with the access point. + ''; + }; + + wpa3 = mkOption { + type = types.bool; + default = true; + description = '' + Use WPA3 instead of WPA2 for authentication. This changes the key + management from WPA_PSK to SAE. + ''; + }; + + wpaPassphrase = mkOption { + type = with types; nullOr str; + default = null; + example = "any_64_char_string"; + description = '' + WPA-PSK (pre-shared-key) passphrase. Clients will need this + passphrase to associate with this access point. + Warning: This passphrase will get put into a world-readable file in + the Nix store! You should use wpaPskFile in most cases. + ''; + }; + + wpaPskFile = mkOption { + type = with types; nullOr str; + default = null; + example = "/etc/hostapd.wpa_psk"; + description = '' + Optionally, WPA PSKs can be read from a separate text file (containing + list of (PSK,MAC address) pairs. This allows more than one PSK to be + configured. Use absolute path name to make sure that the files can be + read on SIGHUP configuration reloads. + This is higly suggested to be used over wpaPassphrase as it won't save + your password to the Nix store! + ''; + }; + + }; + + options_interface = options_bss // { + + enable = mkOption { + type = types.bool; + default = true; + description = '' + Enable putting a wireless interface into infrastructure mode, + allowing other wireless devices to associate with the wireless + interface and do wireless networking. A simple access point will + , set + , and + , as well as DHCP on the wireless + interface to provide IP addresses to the associated stations, and + NAT (from the wireless interface to an upstream interface). + ''; + }; + + group = mkOption { + default = "wheel"; + example = "network"; + type = types.str; + description = '' + Members of this group can control hostapd. + ''; + }; + + logLevel = mkOption { + default = 2; + type = types.int; + description = '' + Levels (minimum value for logged events): + 0 = verbose debugging + 1 = debugging + 2 = informational messages + 3 = notification + 4 = warning + ''; + }; + + driver = mkOption { + default = "nl80211"; + example = "hostapd"; + type = types.str; + description = '' + Which driver hostapd will use. + Most applications will probably use the default. + ''; + }; + + channel = mkOption { + default = 7; + example = 11; + type = types.int; + description = '' + Channel number (IEEE 802.11) + Please note that some drivers do not use this value from + hostapd and the channel will need to be configured + separately with iwconfig. + ''; + }; + + hwMode = mkOption { + default = "g"; + type = types.enum [ "a" "b" "g" ]; + description = '' + Operation mode. + (a = IEEE 802.11a, b = IEEE 802.11b, g = IEEE 802.11g). + ''; + }; + + countryCode = mkOption { + default = null; + example = "US"; + type = with types; nullOr str; + description = '' + Country code (ISO/IEC 3166-1). Used to set regulatory domain. + Set as needed to indicate country in which device is operating. + This can limit available channels and transmit power. + These two octets are used as the first two octets of the Country String + (dot11CountryString). + This enables IEEE 802.11d. This advertises the countryCode and the set + of allowed channels and transmit power levels based on the regulatory + limits. + This is required in most places by law and thus enforced to be set! + ''; + }; + + ieee80211h = mkOption { + type = types.bool; + default = true; + description = '' + Enable IEEE 802.11h. This enables radar detection and DFS support if + available. DFS support is required on outdoor 5 GHz channels in most countries + of the world. + ''; + }; + + ieee80211n = mkOption { + type = types.bool; + default = true; + description = "Whether IEEE 802.11n (HT) is enabled"; + }; + + ht_capab = mkOption { + type = with types; listOf str; + default = ["HT40-" "SHORT-GI-20" "SHORT-GI-40"]; + description = '' + HT capabilities (list of flags) + LDPC coding capability: [LDPC] = supported + Supported channel width set: [HT40-] = both 20 MHz and 40 MHz with secondary + channel below the primary channel; [HT40+] = both 20 MHz and 40 MHz + with secondary channel above the primary channel + (20 MHz only if neither is set) + Note: There are limits on which channels can be used with HT40- and + HT40+. Following table shows the channels that may be available for + HT40- and HT40+ use per IEEE 802.11n Annex J: + freq HT40- HT40+ + 2.4 GHz 5-13 1-7 (1-9 in Europe/Japan) + 5 GHz 40,48,56,64 36,44,52,60 + (depending on the location, not all of these channels may be available + for use) + Please note that 40 MHz channels may switch their primary and secondary + channels if needed or creation of 40 MHz channel maybe rejected based + on overlapping BSSes. These changes are done automatically when hostapd + is setting up the 40 MHz channel. + HT-greenfield: [GF] (disabled if not set) + Short GI for 20 MHz: [SHORT-GI-20] (disabled if not set) + Short GI for 40 MHz: [SHORT-GI-40] (disabled if not set) + Tx STBC: [TX-STBC] (disabled if not set) + Rx STBC: [RX-STBC1] (one spatial stream), [RX-STBC12] (one or two spatial + streams), or [RX-STBC123] (one, two, or three spatial streams); Rx STBC + disabled if none of these set + HT-delayed Block Ack: [DELAYED-BA] (disabled if not set) + Maximum A-MSDU length: [MAX-AMSDU-7935] for 7935 octets (3839 octets if not + set) + DSSS/CCK Mode in 40 MHz: [DSSS_CCK-40] = allowed (not allowed if not set) + 40 MHz intolerant [40-INTOLERANT] (not advertised if not set) + L-SIG TXOP protection support: [LSIG-TXOP-PROT] (disabled if not set) + ''; + }; + + require_ht = mkOption { + type = types.bool; + default = true; + description = "Require stations to support VHT PHY (reject association if they do not)"; + }; + + ieee80211ac = mkOption { + type = types.bool; + default = false; + description = "Whether IEEE 802.11ac (VHT) is enabled"; + }; + + vht_capab = mkOption { + type = with types; listOf str; + default = ["SHORT-GI-80" "HTC-VHT"]; + description = '' + VHT capabilities (list of flags) + + vht_max_mpdu_len: [MAX-MPDU-7991] [MAX-MPDU-11454] + Indicates maximum MPDU length + 0 = 3895 octets (default) + 1 = 7991 octets + 2 = 11454 octets + 3 = reserved + + supported_chan_width: [VHT160] [VHT160-80PLUS80] + Indicates supported Channel widths + 0 = 160 MHz & 80+80 channel widths are not supported (default) + 1 = 160 MHz channel width is supported + 2 = 160 MHz & 80+80 channel widths are supported + 3 = reserved + + Rx LDPC coding capability: [RXLDPC] + Indicates support for receiving LDPC coded pkts + 0 = Not supported (default) + 1 = Supported + + Short GI for 80 MHz: [SHORT-GI-80] + Indicates short GI support for reception of packets transmitted with TXVECTOR + params format equal to VHT and CBW = 80Mhz + 0 = Not supported (default) + 1 = Supported + + Short GI for 160 MHz: [SHORT-GI-160] + Indicates short GI support for reception of packets transmitted with TXVECTOR + params format equal to VHT and CBW = 160Mhz + 0 = Not supported (default) + 1 = Supported + + Tx STBC: [TX-STBC-2BY1] + Indicates support for the transmission of at least 2x1 STBC + 0 = Not supported (default) + 1 = Supported + + Rx STBC: [RX-STBC-1] [RX-STBC-12] [RX-STBC-123] [RX-STBC-1234] + Indicates support for the reception of PPDUs using STBC + 0 = Not supported (default) + 1 = support of one spatial stream + 2 = support of one and two spatial streams + 3 = support of one, two and three spatial streams + 4 = support of one, two, three and four spatial streams + 5,6,7 = reserved + + SU Beamformer Capable: [SU-BEAMFORMER] + Indicates support for operation as a single user beamformer + 0 = Not supported (default) + 1 = Supported + + SU Beamformee Capable: [SU-BEAMFORMEE] + Indicates support for operation as a single user beamformee + 0 = Not supported (default) + 1 = Supported + + Compressed Steering Number of Beamformer Antennas Supported: + [BF-ANTENNA-2] [BF-ANTENNA-3] [BF-ANTENNA-4] + Beamformee's capability indicating the maximum number of beamformer + antennas the beamformee can support when sending compressed beamforming + feedback + If SU beamformer capable, set to maximum value minus 1 + else reserved (default) + + Number of Sounding Dimensions: + [SOUNDING-DIMENSION-2] [SOUNDING-DIMENSION-3] [SOUNDING-DIMENSION-4] + Beamformer's capability indicating the maximum value of the NUM_STS parameter + in the TXVECTOR of a VHT NDP + If SU beamformer capable, set to maximum value minus 1 + else reserved (default) + + MU Beamformer Capable: [MU-BEAMFORMER] + Indicates support for operation as an MU beamformer + 0 = Not supported or sent by Non-AP STA (default) + 1 = Supported + + VHT TXOP PS: [VHT-TXOP-PS] + Indicates whether or not the AP supports VHT TXOP Power Save Mode + or whether or not the STA is in VHT TXOP Power Save mode + 0 = VHT AP doesn't support VHT TXOP PS mode (OR) VHT STA not in VHT TXOP PS + mode + 1 = VHT AP supports VHT TXOP PS mode (OR) VHT STA is in VHT TXOP power save + mode + + +HTC-VHT Capable: [HTC-VHT] + Indicates whether or not the STA supports receiving a VHT variant HT Control + field. + 0 = Not supported (default) + 1 = supported + + Maximum A-MPDU Length Exponent: [MAX-A-MPDU-LEN-EXP0]..[MAX-A-MPDU-LEN-EXP7] + Indicates the maximum length of A-MPDU pre-EOF padding that the STA can recv + This field is an integer in the range of 0 to 7. + The length defined by this field is equal to + 2 pow(13 + Maximum A-MPDU Length Exponent) -1 octets + + VHT Link Adaptation Capable: [VHT-LINK-ADAPT2] [VHT-LINK-ADAPT3] + Indicates whether or not the STA supports link adaptation using VHT variant + HT Control field + If +HTC-VHTcapable is 1 + 0 = (no feedback) if the STA does not provide VHT MFB (default) + 1 = reserved + 2 = (Unsolicited) if the STA provides only unsolicited VHT MFB + 3 = (Both) if the STA can provide VHT MFB in response to VHT MRQ and if the + STA provides unsolicited VHT MFB + Reserved if +HTC-VHTcapable is 0 + + Rx Antenna Pattern Consistency: [RX-ANTENNA-PATTERN] + Indicates the possibility of Rx antenna pattern change + 0 = Rx antenna pattern might change during the lifetime of an association + 1 = Rx antenna pattern does not change during the lifetime of an association + + Tx Antenna Pattern Consistency: [TX-ANTENNA-PATTERN] + Indicates the possibility of Tx antenna pattern change + 0 = Tx antenna pattern might change during the lifetime of an association + 1 = Tx antenna pattern does not change during the lifetime of an association + ''; + }; + + require_vht = mkOption { + type = types.bool; + default = true; + description = "Require stations to support VHT PHY (reject association if they do not)"; + }; + + vht_oper_chwidth= mkOption { + type = types.int; + default = 1; + description = '' + 0 = 20 or 40 MHz operating Channel width + 1 = 80 MHz channel width + 2 = 160 MHz channel width + 3 = 80+80 MHz channel width + ''; + }; + + vht_oper_centr_freq_seg0_idx = mkOption { + type = types.int; + default = 42; + description = '' + center freq 5 GHz + (5 * index) + So index 42 gives center freq 5.210 GHz which is channel 42 in 5G band. + ''; + }; + + vht_oper_centr_freq_seg1_idx = mkOption { + type = types.int; + default = 159; + description = '' + center freq = 5 GHz + (5 * index) + So index 159 gives center freq 5.795 GHz which is channel 159 in 5G band. + ''; + }; + + use_sta_nsts = mkOption { + type = types.bool; + default = false; + description = '' + Workaround to use station's nsts capability in (Re)Association Response frame + This may be needed with some deployed devices as an interoperability + workaround for beamforming if the AP's capability is greater than the + station's capability. This is disabled by default and can be enabled by + setting use_sta_nsts=true. + ''; + }; + + ieee80211ax = mkOption { + type = types.bool; + default = false; + description = "Whether IEEE 802.11ax (HE) is enabled"; + }; + + bss = mkOption { + type = with types; attrsOf (submodule {options = options_bss;}); + default = { }; + example = literalExpression '' + { + "wlan0host" = { + ssid = "HostNetwork"; + wpaPassphrase = "NotSoSecretPassword"; + }; + } + ''; + description = '' + Support for multiple BSSIDs. + ''; + }; + + extraConfig = mkOption { + default = ""; + type = types.lines; + description = "Extra configuration options to put in hostapd.conf."; + }; + + }; + + + configFile = interface: let + icfg = cfg."${interface}"; + in '' + ctrl_interface=/run/hostapd + ctrl_interface_group=${icfg.group} + + # logging (debug level) + logger_syslog=-1 + logger_syslog_level=${toString icfg.logLevel} + logger_stdout=-1 + logger_stdout_level=${toString icfg.logLevel} + + interface=${interface} + driver=${icfg.driver} + hw_mode=${icfg.hwMode} + channel=${toString icfg.channel} + country_code=${icfg.countryCode} + ieee80211d=1 + ${optionalString (icfg.ieee80211h) "ieee80211h=1"} + ${optionalString icfg.ieee80211n '' + ieee80211n=1 + ht_capab=${mapCapab icfg.ht_capab} + require_ht=${boolean icfg.require_ht} + ''} + ${optionalString icfg.ieee80211ac '' + ieee80211ac=1 + vht_capab=${mapCapab icfg.vht_capab} + require_vht=${boolean icfg.require_vht} + vht_oper_chwidth=${toString icfg.vht_oper_chwidth} + vht_oper_centr_freq_seg0_idx=${toString icfg.vht_oper_centr_freq_seg0_idx} + vht_oper_centr_freq_seg1_idx=${toString icfg.vht_oper_centr_freq_seg1_idx} + use_sta_nsts=${boolean icfg.use_sta_nsts} + ''} + ${optionalString icfg.ieee80211ax '' + ieee80211ax=1 + ''} + + ssid=${icfg.ssid} + ${configBss icfg} + + ${concatMapStringsSep "\n" (bss: '' + bss=${bss} + use_driver_iface_addr=1 + ${configBss icfg.bss."${bss}"}'') (attrNames icfg.bss)} + + ${icfg.extraConfig} + ''; + + mapCapab = list: concatStrings (map (key: "[${key}]") list); + boolean = bool: if bool then "1" else "0"; + + configBss = bsscfg: '' + ${optionalString bsscfg.wpa '' + wpa=2 + wpa_pairwise=CCMP TKIP + wpa_key_mgmt=${if bsscfg.wpa3 then "SAE" else "WPA-PSK"} + ${optionalString (bsscfg.wpaPassphrase != null) "wpa_passphrase=${bsscfg.wpaPassphrase}"} + ${optionalString (bsscfg.wpaPskFile != null) "wpa_psk_file=${bsscfg.wpaPskFile}"} + ''} + ''; + + etcConfigs = listToAttrs (map + (n: nameValuePair "hostapd/${n}.conf" {text = configFile n;}) + (attrNames (filterAttrs (n: v: v.enable) cfg)) + ); + +in + +{ + + #disabledModules = [ "services/networking/hostapd.nix" ]; + + ###### interface + + options = { + networking.hostapd = mkOption { + type = with types; attrsOf (submodule {options = options_interface;}); + default = { }; + example = literalExpression '' + { "wlan0" = { + hw_mode = "a"; + channel = 11; + ssid = "MyNetwork"; + wpaPassphrase = "SecretPassword"; + }; + } + ''; + description = '' + Interface for which to start hostapd. + ''; + }; + }; + + + ###### implementation + + config = mkIf (any (val: val.enable) (attrValues cfg)) { + assertions = [{ + assertion = all (val: ! val.enable or val.countryCode != null) (attrValues cfg); + message = "Country code has to be specified to prevent violation of the law."; + }]; + ## TODO mkRenamedOptionModule + + environment.systemPackages = [ pkgs.hostapd ]; + services.udev.packages = [ pkgs.crda ]; + + environment.etc = etcConfigs; + + systemd.services.hostapd = let + interfaces = map utils.escapeSystemdPath (attrNames (filterAttrs (n: v: v.enable) cfg)); + links = interfaces ++ (map utils.escapeSystemdPath (concatMap attrNames (catAttrs "bss" (attrValues (filterAttrs (n: v: v.enable) cfg))))); + devices = map (ifc: "sys-subsystem-net-devices-${ifc}.device") interfaces; + services = map (ifc: "network-link-${ifc}.service") links; + in { + description = "hostapd wireless AP"; + path = [ pkgs.hostapd ]; + after = devices; + bindsTo = devices; + requiredBy = services; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = + { ExecStart = "${pkgs.hostapd}/bin/hostapd ${toString (map (v: "/etc/${v}") (attrNames etcConfigs))}"; + Restart = "always"; + }; + }; + }; +} -- cgit v1.2.3