# SPDX-FileCopyrightText: 2024 Ryan Lahfa # # SPDX-License-Identifier: EUPL-1.2 { config, pkgs, lib, nodeMeta, ... }: let svc = config.system.service; inherit (nodeMeta.extraNodeSettings) building floor ap-no real-ap-no ; hex = x: lib.fixedWidthString 2 "0" (lib.toHexString x); mac-1 = "02:5B:6A:${hex (building * 4)}:${hex floor}:${hex ap-no}"; mac-2 = "02:5B:6A:${hex (building * 4 + 1)}:${hex floor}:${hex ap-no}"; channel-1 = builtins.elemAt [ 1 6 11 ] (lib.mod (real-ap-no + floor) 3); channel-2 = builtins.elemAt [ 36 52 100 ] (lib.mod (real-ap-no + floor) 3); secrets-1 = { ssid = "DGNum"; }; secrets-2 = { ssid = "DGNum 5G"; }; baseParams = { country_code = "FR"; hw_mode = "g"; channel = channel-1; wmm_enabled = 1; ieee80211n = 1; ht_capab = "[LDPC][GF][HT40-][HT40+][SHORT-GI-40][MAX-AMSDU-7935][TX-STBC]"; auth_algs = 1; wpa = 2; wpa_pairwise = "TKIP CCMP"; rsn_pairwise = "CCMP"; }; radiusKeyMgmt = { wpa_key_mgmt = "WPA-EAP"; }; modernParams = { hw_mode = "a"; he_su_beamformer = 1; he_su_beamformee = 1; he_mu_beamformer = 1; preamble = 1; # Allow radar detection. ieee80211d = 1; ieee80211h = 1; ieee80211ac = 1; ieee80211ax = 1; vht_capab = "[MAX-MPDU-7991][SU-BEAMFORMEE][SU-BEAMFORMER][RXLDPC][SHORT-GI-80][MAX-A-MPDU-LEN-EXP3][RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN][TX-STBC-2BY1][RX-STBC-1][MU-BEAMFORMER]"; vht_oper_chwidth = 1; he_oper_chwidth = 1; channel = channel-2; # TODO understand interferences vht_oper_centr_freq_seg0_idx = channel-2 + 6; he_oper_centr_freq_seg0_idx = channel-2 + 6; require_vht = 1; }; clientRadius = { ieee8021x = 1; eapol_version = 2; use_pae_group_addr = 1; dynamic_vlan = 3; vlan_tagged_interface = "lan"; }; externalRadius = { # TODO: when we have proper IPAM, set the right value here. own_ip_addr = "127.0.0.1"; nas_identifier = "ap01.dgnum.eu"; # No DNS here, hostapd do not support this mode. auth_server_addr = "129.199.195.129"; auth_server_port = 1812; auth_server_shared_secret = let secret = builtins.getEnv "RADIUS_SECRET"; in if secret == "" then lib.warn "Using a dummy RADIUS secret. Please do not use in production" "DUMMYSECRET" else secret; }; mkWifiSta = params: interface: secrets: svc.hostapd.build { inherit interface; package = pkgs.hostapd-radius; params = params // secrets; dependencies = [ config.services.jitter ]; }; in { hardware.wlanMacAddresses = { wlan0 = mac-1; wlan1 = mac-2; }; services = { # wlan0 is the 2.4GHz interface. hostap-1 = mkWifiSta ( baseParams // clientRadius // externalRadius // radiusKeyMgmt ) config.hardware.networkInterfaces.wlan0 secrets-1; hostap-1-ready = svc.hostapd-ready.build { interface = config.hardware.networkInterfaces.wlan0; }; # wlan1 is the 5GHz interface, e.g. AX capable. hostap-2 = mkWifiSta ( baseParams // clientRadius // externalRadius // radiusKeyMgmt // modernParams ) config.hardware.networkInterfaces.wlan1 secrets-2; # Oneshot that waits until the hostapd has set the interface in operational state. hostap-2-ready = svc.hostapd-ready.build { interface = config.hardware.networkInterfaces.wlan1; }; }; }