forked from mirrors_public/oddlama_nix-config
chore: wip: add better options to hostapd module
This commit is contained in:
parent
076db4963f
commit
3dd210d5cf
4 changed files with 508 additions and 239 deletions
|
@ -4,67 +4,29 @@
|
||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
}: {
|
}: {
|
||||||
services.hostapd = {
|
imports = [../../modules/hostapd.nix];
|
||||||
enable = true;
|
|
||||||
interface = "wlan1";
|
|
||||||
ssid = "🍯🐝💨";
|
|
||||||
wpa = 3;
|
|
||||||
# Use 2.4GHz, this network is ment for dumb embedded devices
|
|
||||||
hwMode = "g";
|
|
||||||
# Automatic Channel Selection (ACS) is unfortunately not implemented for mt7612u.
|
|
||||||
channel = 13;
|
|
||||||
# Respect the local regulations
|
|
||||||
countryCode = "DE";
|
|
||||||
# TODO away
|
|
||||||
logLevel = 0;
|
|
||||||
};
|
|
||||||
# TODO dont adverttise!
|
|
||||||
#wpa_psk_file=${config.rekey.secrets.wifi-clients.path}
|
|
||||||
|
|
||||||
# Associates each known client to a unique password
|
# Associates each known client to a unique password
|
||||||
rekey.secrets.wifi-clients.file = ./secrets/wifi-clients.age;
|
rekey.secrets.wifi-clients.file = ./secrets/wifi-clients.age;
|
||||||
systemd.services.hostapd = {
|
|
||||||
# Filter the clients to get a list of all known MAC addresses, which we
|
|
||||||
# then use for MAC access control. Afterwards, add the password for each
|
|
||||||
# client to the hostapd config.
|
|
||||||
preStart = lib.mkBefore ''
|
|
||||||
grep -o '^..:..:..:..:..:..' ${config.rekey.secrets.wifi-clients.path} > /run/hostapd/client-macs
|
|
||||||
hostapd_conf=$(cat ''${systemd.services.hostapd.serviceConfig.ExecStart})
|
|
||||||
sae_passwords=$(echo -e "sae_password=aa|mac=13:13:13:13:13:13\nsae_password=aa|mac=12:12:12:12:12:12")
|
|
||||||
hostapd_conf=''${hostapd_conf//"{{SAE_PASSWORDS}}"/$sae_passwords}
|
|
||||||
echo "$hostapd_conf" > /run/hostapd/config
|
|
||||||
'';
|
|
||||||
# Add some missing options to the upstream config
|
|
||||||
serviceConfig = {
|
|
||||||
ExecStart = lib.mkForce "${pkgs.hostapd}/bin/hostapd /run/hostapd/config";
|
|
||||||
ExecReload = "/bin/kill -HUP $MAINPID";
|
|
||||||
RuntimeDirectory = "hostapd";
|
|
||||||
|
|
||||||
# Hardening
|
services.hostapd = {
|
||||||
LockPersonality = true;
|
enable = true;
|
||||||
MemoryDenyWriteExecute = true;
|
interfaces = {
|
||||||
DevicePolicy = "closed";
|
"wlan1" = {
|
||||||
DeviceAllow = "/dev/rfkill rw";
|
ssid = "🍯🐝💨";
|
||||||
NoNewPrivileges = true;
|
hwMode = "g";
|
||||||
PrivateUsers = false; # hostapd requires real system root access.
|
#wifi4.enable = true;
|
||||||
PrivateTmp = true;
|
#wifi5.enable = true;
|
||||||
ProtectClock = true;
|
countryCode = "DE";
|
||||||
ProtectControlGroups = true;
|
# Automatic Channel Selection (ACS) is unfortunately not implemented for mt7612u.
|
||||||
ProtectHome = true;
|
channel = 13;
|
||||||
ProtectHostname = true;
|
|
||||||
ProtectKernelLogs = true;
|
#wpa = 3;
|
||||||
ProtectKernelModules = true;
|
# TODO dont adverttise!
|
||||||
ProtectKernelTunables = true;
|
|
||||||
ProtectProc = "invisible";
|
# TODO away
|
||||||
ProcSubset = "pid";
|
logLevel = 0;
|
||||||
ProtectSystem = "strict";
|
};
|
||||||
RestrictAddressFamilies = ["AF_UNIX" "AF_NETLINK" "AF_INET" "AF_INET6"];
|
|
||||||
RestrictNamespaces = true;
|
|
||||||
RestrictRealtime = true;
|
|
||||||
RestrictSUIDSGID = true;
|
|
||||||
SystemCallArchitectures = "native";
|
|
||||||
SystemCallFilter = ["@system-service" "~@privileged" "@chown"];
|
|
||||||
UMask = "0077";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./hostapd.nix
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -6,96 +6,143 @@
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
with lib; let
|
with lib; let
|
||||||
|
# TODO: add multi AP support (aka EasyMesh(TM))
|
||||||
|
# TODO DFS as separate setting ?
|
||||||
disabledModules = ["services/networking/hostapd.nix"];
|
disabledModules = ["services/networking/hostapd.nix"];
|
||||||
|
|
||||||
cfg = config.services.hostapd;
|
cfg = config.services.hostapd;
|
||||||
|
|
||||||
escapedInterface = utils.escapeSystemdPath cfg.interface;
|
# Escapes a string as hex (hello -> 68656c6c6f)
|
||||||
|
escapeHex = s: toLower (stringAsChars (x: toHexString (strings.charToInt x)) s);
|
||||||
|
|
||||||
configFile = pkgs.writeText "hostapd.conf" ''
|
# Maps the specified acl mode to values understood by hostapd
|
||||||
# logging (debug level)
|
macaddrAclModes = {
|
||||||
logger_syslog=-1
|
"allow" = 0;
|
||||||
logger_syslog_level=${toString cfg.logLevel}
|
"deny" = 1;
|
||||||
logger_stdout=-1
|
"radius" = 2;
|
||||||
logger_stdout_level=${toString cfg.logLevel}
|
};
|
||||||
|
# Maps the specified ignore broadcast ssid mode to values understood by hostapd
|
||||||
|
ignoreBroadcastSsidModes = {
|
||||||
|
"disabled" = 0;
|
||||||
|
"empty" = 1;
|
||||||
|
"clear" = 2;
|
||||||
|
};
|
||||||
|
# Maps the specified vht and he channel widths to values understood by hostapd
|
||||||
|
operatingChannelWidth = {
|
||||||
|
"20or40" = 0;
|
||||||
|
"80" = 1;
|
||||||
|
"160" = 2;
|
||||||
|
"80+80" = 3;
|
||||||
|
};
|
||||||
|
|
||||||
ctrl_interface=/run/hostapd
|
configFileForInterface = interface: let
|
||||||
ctrl_interface_group=${cfg.group}
|
ifcfg = cfg.interfaces.${interface};
|
||||||
|
escapedInterface = utils.escapeSystemdPath interface;
|
||||||
|
hasMacAllowList = count ifcfg.macAllow > 0 || ifcfg.macAllowFile != null;
|
||||||
|
hasMacDenyList = count ifcfg.macDeny > 0 || ifcfg.macDenyFile != null;
|
||||||
|
bool01 = b:
|
||||||
|
if b
|
||||||
|
then "1"
|
||||||
|
else "0";
|
||||||
|
in
|
||||||
|
pkgs.writeText "hostapd-${escapedInterface}.conf" ''
|
||||||
|
logger_syslog=-1
|
||||||
|
logger_syslog_level=${toString ifcfg.logLevel}
|
||||||
|
logger_stdout=-1
|
||||||
|
logger_stdout_level=${toString ifcfg.logLevel}
|
||||||
|
|
||||||
interface=${cfg.interface}
|
interface=${interface}
|
||||||
driver=${cfg.driver}
|
driver=${ifcfg.driver}
|
||||||
utf8_ssid=1
|
ctrl_interface=/run/hostapd
|
||||||
ssid=${cfg.ssid}
|
ctrl_interface_group=${ifcfg.group}
|
||||||
hw_mode=${cfg.hwMode}
|
|
||||||
channel=${toString cfg.channel}
|
|
||||||
|
|
||||||
${optionalString cfg.wpa ''
|
##### IEEE 802.11 related configuration #######################################
|
||||||
|
|
||||||
|
ssid2=${escapeHex ifcfg.ssid}
|
||||||
|
utf8_ssid=${ifcfg.hwMode}
|
||||||
|
${optionalString (ifcfg.countryCode != null) ''
|
||||||
|
country_code=${ifcfg.countryCode}
|
||||||
|
# IEEE 802.11d: Limit to frequencies allowed in country
|
||||||
|
ieee80211d=1
|
||||||
|
# IEEE 802.11h: Enable radar detection and DFS (Dynamic Frequency Selection)
|
||||||
|
ieee80211h=1
|
||||||
|
''}
|
||||||
|
hw_mode=${ifcfg.hwMode}
|
||||||
|
channel=${toString ifcfg.channel}
|
||||||
|
${optionalString ifcfg.noScan "noscan=1"}
|
||||||
|
# Set the MAC-address access control mode
|
||||||
|
macaddr_acl=${macaddrAclModes.${ifcfg.macAcl}}
|
||||||
|
${optionalString hasMacAllowList ''
|
||||||
|
accept_mac_file=/run/hostapd/mac-${escapedInterface}.allow
|
||||||
|
''}
|
||||||
|
${optionalString hasMacDenyList ''
|
||||||
|
deny_mac_file=/run/hostapd/mac-${escapedInterface}.deny
|
||||||
|
''}
|
||||||
|
# Only allow WPA, disable WEP (insecure)
|
||||||
|
auth_algs=1
|
||||||
|
# Set ssid broadcasting mode (0=normal, 1=empty, 2=clear)
|
||||||
|
ignore_broadcast_ssid=${ignoreBroadcastSsidModes.${ifcfg.ignoreBroadcastSsid}}
|
||||||
|
# Always enable QoS, which is required for 802.11n/ac/ax
|
||||||
|
wmm_enabled=1
|
||||||
|
# Whether to disallow clients to communicate with each other
|
||||||
|
ap_isolate=${bool01 ifcfg.apIsolate}
|
||||||
|
|
||||||
|
##### IEEE 802.11n (WiFi 4) related configuration #######################################
|
||||||
|
# MIMO and channel bonding support
|
||||||
|
ieee80211n=${bool01 ifcfg.ieee80211n}
|
||||||
|
ht_capab=${concatMapStrings (x: "[${x}]") ifcfg.htCapab}
|
||||||
|
require_ht=${bool01 ifcfg.requireHt}
|
||||||
|
|
||||||
|
##### IEEE 802.11ac (WiFi 5) related configuration #####################################
|
||||||
|
|
||||||
|
ieee80211ac=${bool01 ifcfg.ieee80211ac}
|
||||||
|
vht_capab=${concatMapStrings (x: "[${x}]") ifcfg.vhtCapab}
|
||||||
|
require_vht=${bool01 ifcfg.requireVht}
|
||||||
|
vht_oper_chwidth=${operatingChannelWidth.${ifcfg.vhtOperatingChannelWidth}}
|
||||||
|
|
||||||
|
##### IEEE 802.11ax (WiFi 6) related configuration #####################################
|
||||||
|
|
||||||
|
ieee80211ax=${bool01 ifcfg.ieee80211ax}
|
||||||
|
require_he=${bool01 ifcfg.requireHe}
|
||||||
|
he_oper_chwidth=${operatingChannelWidth.${ifcfg.heOperatingChannelWidth}}
|
||||||
|
he_su_beamformer=${bool01 ifcfg.heSuBeamformer}
|
||||||
|
he_su_beamformee=${bool01 ifcfg.heSuBeamformee}
|
||||||
|
he_mu_beamformer=${bool01 ifcfg.heMuBeamformer}
|
||||||
|
|
||||||
|
##### IEEE 802.11be (WiFi 7) related configuration #####################################
|
||||||
|
|
||||||
|
ieee80211be=${bool01 ifcfg.ieee80211be}
|
||||||
|
eht_oper_chwidth=${operatingChannelWidth.${ifcfg.ehtOperatingChannelWidth}}
|
||||||
|
eht_su_beamformer=${bool01 ifcfg.ehtSuBeamformer}
|
||||||
|
eht_su_beamformee=${bool01 ifcfg.ehtSuBeamformee}
|
||||||
|
eht_mu_beamformer=${bool01 ifcfg.ehtMuBeamformer}
|
||||||
|
|
||||||
|
##### WPA/IEEE 802.11i configuration ##########################################
|
||||||
|
|
||||||
|
# WPA3
|
||||||
wpa=2
|
wpa=2
|
||||||
wpa_pairwise=CCMP
|
wpa_pairwise=CCMP CCMP-256
|
||||||
wpa_passphrase=${cfg.wpaPassphrase}
|
rsn_pairwise=CCMP CCMP-256
|
||||||
''}
|
wpa_key_mgmt=SAE
|
||||||
${optionalString cfg.noScan "noscan=1"}
|
# Encrypt management frames to protect against deauthentication and similar attacks
|
||||||
|
ieee80211w=2
|
||||||
|
# Force WPA3-Personal without transition
|
||||||
|
transition_disable=0x01
|
||||||
|
# Derive PWE using both hunting-and-pecking loop and hash-to-element
|
||||||
|
sae_pwe=2
|
||||||
|
# SAE passwords can be set via wpa_passphrase but not via wpa_psk_file. This sucks
|
||||||
|
# and means we have to add the passwords in pre-start to prevent them being visible here
|
||||||
|
{{SAE_PASSWORDS}}
|
||||||
|
|
||||||
# Enable QoS, required for 802.11n/ac/ax
|
${ifcfg.extraConfig}
|
||||||
wmm_enabled=1
|
'';
|
||||||
|
|
||||||
${optionalString (cfg.countryCode != null) ''
|
|
||||||
# DFS (IEEE 802.11d, IEEE 802.11h)
|
|
||||||
# Limit to frequencies allowed in country
|
|
||||||
ieee80211d=1
|
|
||||||
country_code=${cfg.countryCode}
|
|
||||||
# Ensure TX Power and frequencies compliance with local regulatory requirements
|
|
||||||
ieee80211h=1
|
|
||||||
''}
|
|
||||||
|
|
||||||
# IEEE 802.11ac (WiFi 4) - MIMO and channel bonding support
|
|
||||||
ieee80211n=1
|
|
||||||
ht_capab=[LDPC][HT40+][HT40-][GF][SHORT-GI-20][SHORT-GI-40][TX-STBC][RX-STBC1]
|
|
||||||
|
|
||||||
# IEEE 802.11ac (WiFi 5) - adds wider channel-width support and MU-MIMO (multi user MIMO)
|
|
||||||
ieee80211ac=1
|
|
||||||
#vht_capab=[SHORT-GI-80][TX-STBC-2BY1][RX-STBC-1][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]
|
|
||||||
#vht_oper_chwidth=1
|
|
||||||
|
|
||||||
# WPA3
|
|
||||||
wpa=2
|
|
||||||
wpa_pairwise=CCMP CCMP-256
|
|
||||||
rsn_pairwise=CCMP CCMP-256
|
|
||||||
wpa_key_mgmt=SAE
|
|
||||||
# Require WPA, disable WEP
|
|
||||||
auth_algs=1
|
|
||||||
# Encrypt management frames to protect against deauthentication and similar attacks
|
|
||||||
ieee80211w=2
|
|
||||||
# Force WPA3-Personal without transition
|
|
||||||
transition_disable=0x01
|
|
||||||
# Derive PWE using both hunting-and-pecking loop and hash-to-element
|
|
||||||
sae_pwe=2
|
|
||||||
# SAE passwords can be set via wpa_passphrase but not via wpa_psk_file. This sucks
|
|
||||||
# and means we have to add the passwords in pre-start to prevent them being visible here
|
|
||||||
{{SAE_PASSWORDS}}
|
|
||||||
|
|
||||||
# Use a MAC-address access control list
|
|
||||||
macaddr_acl=1
|
|
||||||
${optionalString (cfg.macaddrAcl != null) ''
|
|
||||||
accept_mac_file=${cfg.macaddrAcl}
|
|
||||||
''}
|
|
||||||
|
|
||||||
# Hide network and require devices to know the ssid in advance
|
|
||||||
#ignore_broadcast_ssid=${cfg.ignoreBroadcastSsid}
|
|
||||||
# Don't allow clients to communicate with each other
|
|
||||||
ap_isolate=${cfg.apIsolate}
|
|
||||||
|
|
||||||
${cfg.extraConfig}
|
|
||||||
'';
|
|
||||||
in {
|
in {
|
||||||
# TODO assert interfaces >= 1
|
options = {
|
||||||
|
|
||||||
options = with types; {
|
|
||||||
services.hostapd = {
|
services.hostapd = {
|
||||||
enable = mkOption {
|
enable = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = false;
|
default = false;
|
||||||
description = lib.mdDoc ''
|
description = mdDoc ''
|
||||||
Whether to enable hostapd. hostapd is a user space daemon for access point and
|
Whether to enable hostapd. hostapd is a user space daemon for access point and
|
||||||
authentication servers. It implements IEEE 802.11 access point management,
|
authentication servers. It implements IEEE 802.11 access point management,
|
||||||
IEEE 802.1X/WPA/WPA2/EAP Authenticators, RADIUS client, EAP server, and RADIUS
|
IEEE 802.1X/WPA/WPA2/EAP Authenticators, RADIUS client, EAP server, and RADIUS
|
||||||
|
@ -116,17 +163,25 @@ in {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
description = lib.mdDoc ''
|
description = mdDoc ''
|
||||||
This option allows you to define APs for one or multiple interfaces.
|
This option allows you to define APs for one or multiple interfaces.
|
||||||
Each attribute specifies a interface and associates it to its configuration.
|
Each attribute specifies a interface and associates it to its configuration.
|
||||||
At least one interface must be specified.
|
At least one interface must be specified.
|
||||||
|
|
||||||
|
Each interface can only support a single hardware-mode that is configured via
|
||||||
|
({option}`services.hostapd.interfaces.<name>.hwMode`). To create a dual-band
|
||||||
|
or tri-band AP, you will have to use a device that supports configuring multiple APs
|
||||||
|
(Refer to valid interface combinations in {command}`iw list`). For each mode hostapd
|
||||||
|
requires a separate logical interface (like wlp3s0, wlp3s1, ...). Often this needs
|
||||||
|
to be configured manually by utilizing udev rules - details will differ for each device.
|
||||||
|
Alternatively, one can also just use distinct devices for each mode.
|
||||||
'';
|
'';
|
||||||
type = attrsOf (submodule {
|
type = types.attrsOf (types.submodule {
|
||||||
options = {
|
options = {
|
||||||
noScan = mkOption {
|
noScan = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
default = false;
|
default = false;
|
||||||
description = lib.mdDoc ''
|
description = mdDoc ''
|
||||||
Disables scan for overlapping BSSs in HT40+/- mode.
|
Disables scan for overlapping BSSs in HT40+/- mode.
|
||||||
Caution: turning this on will likely violate regulatory requirements!
|
Caution: turning this on will likely violate regulatory requirements!
|
||||||
'';
|
'';
|
||||||
|
@ -136,7 +191,7 @@ in {
|
||||||
default = "nl80211";
|
default = "nl80211";
|
||||||
example = "none";
|
example = "none";
|
||||||
type = types.str;
|
type = types.str;
|
||||||
description = lib.mdDoc ''
|
description = mdDoc ''
|
||||||
The driver {command}`hostapd` will use.
|
The driver {command}`hostapd` will use.
|
||||||
{var}`nl80211` is used with all Linux mac80211 drivers.
|
{var}`nl80211` is used with all Linux mac80211 drivers.
|
||||||
{var}`none` is used if building a standalone RADIUS server that does
|
{var}`none` is used if building a standalone RADIUS server that does
|
||||||
|
@ -144,113 +199,370 @@ in {
|
||||||
Most applications will probably use the default.
|
Most applications will probably use the default.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
logLevel = mkOption {
|
||||||
|
default = 2;
|
||||||
|
type = types.int;
|
||||||
|
description = mdDoc ''
|
||||||
|
Levels (minimum value for logged events):
|
||||||
|
0 = verbose debugging
|
||||||
|
1 = debugging
|
||||||
|
2 = informational messages
|
||||||
|
3 = notification
|
||||||
|
4 = warning
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
group = mkOption {
|
||||||
|
default = "wheel";
|
||||||
|
example = "network";
|
||||||
|
type = types.str;
|
||||||
|
description = mdDoc ''
|
||||||
|
Members of this group can access the control socket for this interface.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
utf8Ssid = mkOption {
|
||||||
|
default = true;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc "Whether the SSID is to be interpreted using UTF-8 encoding";
|
||||||
|
};
|
||||||
|
|
||||||
|
ssid = mkOption {
|
||||||
|
default = config.system.nixos.distroId;
|
||||||
|
defaultText = literalExpression "config.system.nixos.distroId";
|
||||||
|
example = "mySpecialSSID";
|
||||||
|
type = types.str;
|
||||||
|
description = mdDoc "SSID to be used in IEEE 802.11 management frames.";
|
||||||
|
};
|
||||||
|
|
||||||
|
countryCode = mkOption {
|
||||||
|
default = null;
|
||||||
|
example = "US";
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
description = mdDoc ''
|
||||||
|
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).
|
||||||
|
|
||||||
|
Setting this will force you to also enable IEEE 802.11d and IEEE 802.11h.
|
||||||
|
|
||||||
|
IEEE 802.11d: This advertises the countryCode and the set of allowed channels
|
||||||
|
and transmit power levels based on the regulatory limits.
|
||||||
|
|
||||||
|
IEEE802.11h: This enables radar detection and DFS (Dynamic Frequency Selection)
|
||||||
|
support if available. DFS support is required on outdoor 5 GHz channels in most
|
||||||
|
countries of the world.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
hwMode = mkOption {
|
||||||
|
default = "g";
|
||||||
|
type = types.enum ["a" "b" "g" "ad" "any"];
|
||||||
|
description = mdDoc ''
|
||||||
|
Operation mode (a = IEEE 802.11a (5 GHz), b = IEEE 802.11b (2.4 GHz),
|
||||||
|
g = IEEE 802.11g (2.4 GHz), ad = IEEE 802.11ad (60 GHz); a/g options are used
|
||||||
|
with IEEE 802.11n (HT), too, to specify band). For IEEE 802.11ac (VHT), this
|
||||||
|
needs to be set to hw_mode=a. For IEEE 802.11ax (HE) on 6 GHz this needs
|
||||||
|
to be set to hw_mode=a. When using ACS (see channel parameter), a
|
||||||
|
special value "any" can be used to indicate that any support band can be used.
|
||||||
|
This special case is currently supported only with drivers with which
|
||||||
|
offloaded ACS is used.
|
||||||
|
|
||||||
|
Most likely you to select a (5GHz & 6GHz a/n/ac/ax) or g (2Ghz b/g/n) here.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
channel = mkOption {
|
||||||
|
default = 7;
|
||||||
|
example = 11;
|
||||||
|
type = types.int;
|
||||||
|
description = mdDoc ''
|
||||||
|
The channel to operate on. Use 0 to enable ACS (Automatic Channel Selection).
|
||||||
|
Beware that not every device supports ACS in which case {command}`hostapd`
|
||||||
|
will fail to start.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
macAcl = mkOption {
|
||||||
|
default = "allow";
|
||||||
|
type = types.enum ["allow" "deny" "radius"];
|
||||||
|
description = mdDoc ''
|
||||||
|
Station MAC address -based authentication. The following modes are available:
|
||||||
|
|
||||||
|
- {var}`"allow"`: Allow unless listed in {option}`macDeny` (default)
|
||||||
|
- {var}`"deny"`: Deny unless listed in {option}`macAllow`
|
||||||
|
- {var}`"radius"`: Use external radius server, but check both {option}`macAllow` and {option}`macDeny` first
|
||||||
|
|
||||||
|
Please note that this kind of access control requires a driver that uses
|
||||||
|
hostapd to take care of management frame processing and as such, this can be
|
||||||
|
used with driver=hostap or driver=nl80211, but not with driver=atheros.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
macAllow = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
example = ["11:22:33:44:55:66"];
|
||||||
|
description = mdDoc ''
|
||||||
|
Specifies the MAC addresses to allow if {option}`macAcl` is set to {var}`"deny"` or {var}`"radius"`.
|
||||||
|
These values will be world-readable in the Nix store. Values will automatically be merged with
|
||||||
|
{option}`macAllowFile` if necessary.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
macAllowFile = mkOption {
|
||||||
|
type = types.uniq (types.nullOr types.path);
|
||||||
|
default = null;
|
||||||
|
description = mdDoc ''
|
||||||
|
Specifies a file containing the MAC addresses to allow if {option}`macAcl` is set to {var}`"deny"` or {var}`"radius"`.
|
||||||
|
The file should contain exactly one MAC address per line. Comments and empty lines are ignored,
|
||||||
|
only lines matching the regex `^..:..:..:..:..:..\b` will be considered.
|
||||||
|
Any content after the MAC address is ignored.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
macDeny = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
example = ["11:22:33:44:55:66"];
|
||||||
|
description = mdDoc ''
|
||||||
|
Specifies the MAC addresses to deny if {option}`macAcl` is set to {var}`"allow"` or {var}`"radius"`.
|
||||||
|
These values will be world-readable in the Nix store. Values will automatically be merged with
|
||||||
|
{option}`macDenyFile` if necessary.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
macDenyFile = mkOption {
|
||||||
|
type = types.uniq (types.nullOr types.path);
|
||||||
|
default = null;
|
||||||
|
description = mdDoc ''
|
||||||
|
Specifies a file containing the MAC addresses to allow if {option}`macAcl` is set to {var}`"deny"` or {var}`"radius"`.
|
||||||
|
The file should contain exactly one MAC address per line. Comments and empty lines are ignored,
|
||||||
|
only lines matching the regex `^..:..:..:..:..:..\b` will be considered.
|
||||||
|
Any content after the MAC address is ignored.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
ignoreBroadcastSsid = mkOption {
|
||||||
|
default = "disabled";
|
||||||
|
type = types.enum ["disabled" "empty" "clear"];
|
||||||
|
description = mdDoc ''
|
||||||
|
Send empty SSID in beacons and ignore probe request frames that do not
|
||||||
|
specify full SSID, i.e., require stations to know SSID.
|
||||||
|
|
||||||
|
- {var}`"disabled"`: Advertise ssid normally.
|
||||||
|
- {var}`"empty"`: send empty (length=0) SSID in beacon and ignore probe request for broadcast SSID
|
||||||
|
- {var}`"clear"`: clear SSID (ASCII 0), but keep the original length (this may be required with some
|
||||||
|
legacy clients that do not support empty SSID) and ignore probe requests for broadcast SSID. Only
|
||||||
|
use this if empty does not work with your clients.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
apIsolate = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc ''
|
||||||
|
Isolate traffic between stations (clients) and prevent
|
||||||
|
them from communicating with each other.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfig = mkOption {
|
||||||
|
default = "";
|
||||||
|
example = ''
|
||||||
|
multi_ap=1
|
||||||
|
'';
|
||||||
|
type = types.lines;
|
||||||
|
description = mdDoc "Extra configuration options to put in hostapd.conf.";
|
||||||
|
};
|
||||||
|
|
||||||
|
#### IEEE 802.11n (WiFi 4) related configuration
|
||||||
|
|
||||||
|
ieee80211n = mkOption {
|
||||||
|
default = true;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc "Enables support for IEEE 802.11n (WiFi 4, HT)";
|
||||||
|
};
|
||||||
|
|
||||||
|
htCapab = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = ["HT40" "HT40-" "SHORT-GI-20" "SHORT-GI-40"];
|
||||||
|
example = ["LDPC" "HT40+" "HT40-" "GF" "SHORT-GI-20" "SHORT-GI-40" "TX-STBC" "RX-STBC1"];
|
||||||
|
description = mdDoc ''
|
||||||
|
HT (High Throughput) capabilities given as a list of flags.
|
||||||
|
Please refer to the hostapd documentation for allowed values and
|
||||||
|
only set values supported by your physical adapter.
|
||||||
|
|
||||||
|
The default contains common values supported by most adapters.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
requireHt = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc "Require stations (clients) to support WiFi 4 (HT) and disassociate them if they don't.";
|
||||||
|
};
|
||||||
|
|
||||||
|
#### IEEE 802.11ac (WiFi 5) related configuration
|
||||||
|
|
||||||
|
ieee80211ac = mkOption {
|
||||||
|
default = true;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc "Enables support for IEEE 802.11ac (WiFi 5, VHT)";
|
||||||
|
};
|
||||||
|
|
||||||
|
vhtCapab = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
example = ["SHORT-GI-80" "TX-STBC-2BY1" "RX-STBC-1" "RX-ANTENNA-PATTERN" "TX-ANTENNA-PATTERN"];
|
||||||
|
description = mdDoc ''
|
||||||
|
VHT (Very High Throughput) capabilities given as a list of flags.
|
||||||
|
Please refer to the hostapd documentation for allowed values and
|
||||||
|
only set values supported by your physical adapter.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
requireVht = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc "Require stations (clients) to support WiFi 5 (VHT) and disassociate them if they don't.";
|
||||||
|
};
|
||||||
|
|
||||||
|
vhtOperatingChannelWidth = mkOption {
|
||||||
|
default = "20or40";
|
||||||
|
type = types.enum ["20or40" "80" "160" "80+80"];
|
||||||
|
description = mdDoc ''
|
||||||
|
Determines the operating channel width for VHT.
|
||||||
|
|
||||||
|
- {var}`"20or40"`: 20 or 40 MHz operating channel width
|
||||||
|
- {var}`"80"`: 80 MHz channel width
|
||||||
|
- {var}`"160"`: 160 MHz channel width
|
||||||
|
- {var}`"80+80"`: 80+80 MHz channel width
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
##### IEEE 802.11ax (WiFi 6) related configuration
|
||||||
|
|
||||||
|
ieee80211ax = mkOption {
|
||||||
|
default = true;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc "Enables support for IEEE 802.11ax (WiFi 6, HE)";
|
||||||
|
};
|
||||||
|
|
||||||
|
requireHe = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc "Require stations (clients) to support WiFi 6 (HE) and disassociate them if they don't.";
|
||||||
|
};
|
||||||
|
|
||||||
|
heSuBeamformer = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc "HE single user beamformer support";
|
||||||
|
};
|
||||||
|
|
||||||
|
heSuBeamformee = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc "HE single user beamformee support";
|
||||||
|
};
|
||||||
|
|
||||||
|
heMuBeamformer = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc "HE multi user beamformee support";
|
||||||
|
};
|
||||||
|
|
||||||
|
heOperatingChannelWidth = mkOption {
|
||||||
|
default = "20or40";
|
||||||
|
type = types.enum ["20or40" "80" "160" "80+80"];
|
||||||
|
description = mdDoc ''
|
||||||
|
Determines the operating channel width for HE.
|
||||||
|
|
||||||
|
- {var}`"20or40"`: 20 or 40 MHz operating channel width
|
||||||
|
- {var}`"80"`: 80 MHz channel width
|
||||||
|
- {var}`"160"`: 160 MHz channel width
|
||||||
|
- {var}`"80+80"`: 80+80 MHz channel width
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
##### IEEE 802.11be (WiFi 7) related configuration
|
||||||
|
|
||||||
|
ieee80211be = mkOption {
|
||||||
|
default = true;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc "Enables support for IEEE 802.11be (WiFi 7, EHT)";
|
||||||
|
};
|
||||||
|
|
||||||
|
ehtSuBeamformer = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc "EHT single user beamformer support";
|
||||||
|
};
|
||||||
|
|
||||||
|
ehtSuBeamformee = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc "EHT single user beamformee support";
|
||||||
|
};
|
||||||
|
|
||||||
|
ehtMuBeamformer = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
description = mdDoc "EHT multi user beamformee support";
|
||||||
|
};
|
||||||
|
|
||||||
|
ehtOperatingChannelWidth = mkOption {
|
||||||
|
default = "20or40";
|
||||||
|
type = types.enum ["20or40" "80" "160" "80+80"];
|
||||||
|
description = mdDoc ''
|
||||||
|
Determines the operating channel width for EHT.
|
||||||
|
|
||||||
|
- {var}`"20or40"`: 20 or 40 MHz operating channel width
|
||||||
|
- {var}`"80"`: 80 MHz channel width
|
||||||
|
- {var}`"160"`: 160 MHz channel width
|
||||||
|
- {var}`"80+80"`: 80+80 MHz channel width
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
ssid = mkOption {
|
#wpa = mkOption {
|
||||||
default = config.system.nixos.distroId;
|
# type = types.bool;
|
||||||
defaultText = literalExpression "config.system.nixos.distroId";
|
# default = true;
|
||||||
example = "mySpecialSSID";
|
# description = mdDoc ''
|
||||||
type = types.str;
|
# Enable WPA (IEEE 802.11i/D3.0) to authenticate with the access point.
|
||||||
description = lib.mdDoc "SSID to be used in IEEE 802.11 management frames.";
|
# '';
|
||||||
};
|
#};
|
||||||
|
|
||||||
hwMode = mkOption {
|
#wpaPassphrase = mkOption {
|
||||||
default = "g";
|
# default = "my_sekret";
|
||||||
type = types.enum ["a" "b" "g"];
|
# example = "any_64_char_string";
|
||||||
description = lib.mdDoc ''
|
# type = types.str;
|
||||||
Operation mode.
|
# description = mdDoc ''
|
||||||
(a = IEEE 802.11a, b = IEEE 802.11b, g = IEEE 802.11g).
|
# 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!
|
||||||
channel = mkOption {
|
# '';
|
||||||
default = 7;
|
#};
|
||||||
example = 11;
|
|
||||||
type = types.int;
|
|
||||||
description = lib.mdDoc ''
|
|
||||||
Channel number (IEEE 802.11)
|
|
||||||
Please note that some drivers do not use this value from
|
|
||||||
{command}`hostapd` and the channel will need to be configured
|
|
||||||
separately with {command}`iwconfig`.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
group = mkOption {
|
|
||||||
default = "wheel";
|
|
||||||
example = "network";
|
|
||||||
type = types.str;
|
|
||||||
description = lib.mdDoc ''
|
|
||||||
Members of this group can control {command}`hostapd`.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
wpa = mkOption {
|
|
||||||
type = types.bool;
|
|
||||||
default = true;
|
|
||||||
description = lib.mdDoc ''
|
|
||||||
Enable WPA (IEEE 802.11i/D3.0) to authenticate with the access point.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
wpaPassphrase = mkOption {
|
|
||||||
default = "my_sekret";
|
|
||||||
example = "any_64_char_string";
|
|
||||||
type = types.str;
|
|
||||||
description = lib.mdDoc ''
|
|
||||||
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!
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
logLevel = mkOption {
|
|
||||||
default = 2;
|
|
||||||
type = types.int;
|
|
||||||
description = lib.mdDoc ''
|
|
||||||
Levels (minimum value for logged events):
|
|
||||||
0 = verbose debugging
|
|
||||||
1 = debugging
|
|
||||||
2 = informational messages
|
|
||||||
3 = notification
|
|
||||||
4 = warning
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
countryCode = mkOption {
|
|
||||||
default = null;
|
|
||||||
example = "US";
|
|
||||||
type = with types; nullOr str;
|
|
||||||
description = lib.mdDoc ''
|
|
||||||
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).
|
|
||||||
If set this enables IEEE 802.11d. This advertises the countryCode and
|
|
||||||
the set of allowed channels and transmit power levels based on the
|
|
||||||
regulatory limits.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
extraConfig = mkOption {
|
|
||||||
default = "";
|
|
||||||
example = ''
|
|
||||||
auth_algo=0
|
|
||||||
ieee80211n=1
|
|
||||||
ht_capab=[HT40-][SHORT-GI-40][DSSS_CCK-40]
|
|
||||||
'';
|
|
||||||
type = types.lines;
|
|
||||||
description = lib.mdDoc "Extra configuration options to put in hostapd.conf.";
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
###### implementation
|
###### implementation
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = count cfg.interfaces > 0;
|
||||||
|
message = "At least one interface must be configured with hostapd!";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
environment.systemPackages = [pkgs.hostapd];
|
environment.systemPackages = [pkgs.hostapd];
|
||||||
|
|
||||||
services.udev.packages = optionals (cfg.countryCode != null) [pkgs.crda];
|
services.udev.packages = optionals (cfg.countryCode != null) [pkgs.crda];
|
||||||
|
@ -264,7 +576,7 @@ in {
|
||||||
requiredBy = ["network-link-${cfg.interface}.service"];
|
requiredBy = ["network-link-${cfg.interface}.service"];
|
||||||
wantedBy = ["multi-user.target"];
|
wantedBy = ["multi-user.target"];
|
||||||
|
|
||||||
preStart = lib.mkBefore ''
|
preStart = mkBefore ''
|
||||||
grep -o '^..:..:..:..:..:..' ${config.rekey.secrets.wifi-clients.path} > /run/hostapd/client-macs
|
grep -o '^..:..:..:..:..:..' ${config.rekey.secrets.wifi-clients.path} > /run/hostapd/client-macs
|
||||||
hostapd_conf=$(cat ''${systemd.services.hostapd.serviceConfig.ExecStart})
|
hostapd_conf=$(cat ''${systemd.services.hostapd.serviceConfig.ExecStart})
|
||||||
sae_passwords=$(echo -e "sae_password=aa|mac=13:13:13:13:13:13\nsae_password=aa|mac=12:12:12:12:12:12")
|
sae_passwords=$(echo -e "sae_password=aa|mac=13:13:13:13:13:13\nsae_password=aa|mac=12:12:12:12:12:12")
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{nixpkgs, ...}:
|
{nixpkgs, ...}:
|
||||||
nixpkgs.lib.concatMapAttrs (nodeName: fileType:
|
nixpkgs.lib.concatMapAttrs (nodeName: fileType:
|
||||||
if fileType == "directory"
|
if fileType == "directory" && nodeName != "common"
|
||||||
then {${nodeName} = import (../hosts + "/${nodeName}/meta.nix");}
|
then {${nodeName} = import (../hosts + "/${nodeName}/meta.nix");}
|
||||||
else {}) (builtins.readDir ../hosts)
|
else {}) (builtins.readDir ../hosts)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue