From 4976d7268395e9b8cf9ccd4e30252bc4dc761664 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 8 Nov 2025 09:48:44 +0100 Subject: [PATCH 01/22] Update with 802.11ax/be settings --- config/defaults.json | 67 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/config/defaults.json b/config/defaults.json index c9770cc1..0fbf7888 100644 --- a/config/defaults.json +++ b/config/defaults.json @@ -25,6 +25,73 @@ "vht_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}" ] }, + "ax": { + "settings": [ + "# Basic settings", + "hw_mode=a", + "# Enable 802.11n/ac", + "ieee80211n=1", + "ieee80211ac=1", + "# Enable 802.11ax", + "ieee80211ax=1", + "# High efficiency capabilities", + "he_su_beamformer=1", + "he_su_beamformee=1", + "he_mu_beamformer=1", + "# BSS color for spatial reuse, value 1-63", + "he_bss_color=1", + "he_oper_chwidth=2", + "he_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}", + "wmm_enabled=1" + ] + }, + "be": { + "settings": [ + "# Basic settings", + "hw_mode=a", + "# Enable 802.11n/ac/ax", + "ieee80211n=1", + "ieee80211ac=1", + "ieee80211ax=1", + "vht_oper_chwidth=2 # 160 MHz for VHT", + "vht_oper_centr_freq_seg0_idx=50", + "vht_capab=[MAX-MPDU-11454][RXLDPC][SHORT-GI-80][SHORT-GI-160][TX-STBC-2BY1][RX-STBC-1][SU-BEAMFORMER][SU-BEAMFORMEE][MU-BEAMFORMER][MU-BEAMFORMEE]", + "he_su_beamformer=1", + "he_su_beamformee=1", + "he_mu_beamformer=1", + "he_bss_color=1", + "he_oper_chwidth=2 # 160 MHz for HE", + "he_oper_centr_freq_seg0_idx=50", + "# Enable 802.11be", + "ieee80211be=1", + "# EHT configuration", + "eht_su_beamformer=1", + "eht_su_beamformee=1", + "eht_mu_beamformer=1", + "eht_oper_chwidth=2", + "# EHT operation parameters", + "eht_oper_chwidth=2 # 160 MHz, 0=20, 1=40, 2=80, 3=160, 4=320", + "eht_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}", + "# WMM configuration", + "wmm_enabled=1", + "wmm_ac_bk_cwmin=4", + "wmm_ac_bk_cwmax=10", + "wmm_ac_bk_aifs=7", + "wmm_ac_bk_txop_limit=0", + "wmm_ac_be_aifs=3", + "wmm_ac_be_cwmin=4", + "wmm_ac_be_cwmax=10", + "wmm_ac_be_txop_limit=0", + "wmm_ac_vi_aifs=2", + "wmm_ac_vi_cwmin=3", + "wmm_ac_vi_cwmax=4", + "wmm_ac_vi_txop_limit=94", + "wmm_ac_vo_aifs=2", + "wmm_ac_vo_cwmin=2", + "wmm_ac_vo_cwmax=3", + "wmm_ac_vo_txop_limit=47" + ] + }, "g": { "settings": [ "hw_mode=g", From 3ae73a9728dea7bd6bec55f5a2424b08ab36677f Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 8 Nov 2025 09:49:59 +0100 Subject: [PATCH 02/22] Add getModeCapabilities(), validate channel widths for 802.11ax/be --- .../Networking/Hotspot/HostapdManager.php | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/RaspAP/Networking/Hotspot/HostapdManager.php b/src/RaspAP/Networking/Hotspot/HostapdManager.php index c9d0a5ef..0bcbbcb4 100644 --- a/src/RaspAP/Networking/Hotspot/HostapdManager.php +++ b/src/RaspAP/Networking/Hotspot/HostapdManager.php @@ -102,6 +102,12 @@ class HostapdManager if (!empty($config['ieee80211ac']) && strval($config['ieee80211ac']) === '1') { $selected = 'ac'; } + if (!empty($config['ieee80211ax']) && strval($config['ieee80211ax']) === '1') { + $selected = 'ax'; + } + if (!empty($config['ieee80211be']) && strval($config['ieee80211be']) === '1') { + $selected = 'be'; + } if (!empty($config['ieee80211w']) && strval($config['ieee80211w']) === '2') { $selected = 'w'; } @@ -189,10 +195,24 @@ class HostapdManager $vht_freq_idx = ($params['channel'] < RASPI_5GHZ_CHANNEL_MIN) ? 42 : 155; $hwMode = isset($params['hw_mode']) ? $params['hw_mode'] : ''; + // validate channel width for 802.11ax/be + if (in_array($hwMode, ['ax', 'be'])) { + // for 6GHz band (channels 1-233) wider bandwidths are available + $is6GHz = ($params['channel'] >= 1 && $params['channel'] <= 233); + + // for 802.11be, 320 MHz only available on 6GHz + if ($hwMode === 'be' && !$is6GHz && isset($params['eht_oper_chwidth']) && $params['eht_oper_chwidth'] == 4) { + // reset to 160 MHz if 320 MHz requested on non-6GHz + $params['eht_oper_chwidth'] = 2; + } + } + // fetch settings for selected mode $modeSettings = getDefaultNetOpts('hostapd', 'modes', $hwMode); $settings = $modeSettings[$hwMode]['settings'] ?? []; + error_log("HostapdManager::buildConfig() -> settings\n" . var_export($settings, true)); + if (!empty($settings)) { foreach ($settings as $line) { if (!is_string($line)) { @@ -457,5 +477,26 @@ class HostapdManager return is_array($configs) ? count($configs) : 0; } + /** + * Gets capabilities for a given IEEE 802.11 mode + * + * @param string $mode + * @return array + */ + public function getModeCapabilities(string $mode): array + { + $capabilities = [ + 'a' => ['bands' => ['5'], 'max_width' => 20], + 'b' => ['bands' => ['2.4'], 'max_width' => 22], + 'g' => ['bands' => ['2.4'], 'max_width' => 20], + 'n' => ['bands' => ['2.4', '5'], 'max_width' => 40], + 'ac' => ['bands' => ['5'], 'max_width' => 160], + 'ax' => ['bands' => ['2.4', '5', '6'], 'max_width' => 160], + 'be' => ['bands' => ['2.4', '5', '6'], 'max_width' => 320] + ]; + + return $capabilities[$mode] ?? $capabilities['g']; + } + } From 2b61f91ba51206d7053d2b6c79bc03de1788a8b5 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 8 Nov 2025 09:52:15 +0100 Subject: [PATCH 03/22] Define HE/EHT channel widths, add 802.11ax/be specific parameters --- .../Networking/Hotspot/HotspotService.php | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/src/RaspAP/Networking/Hotspot/HotspotService.php b/src/RaspAP/Networking/Hotspot/HotspotService.php index 2f6a3c60..d8f37c39 100644 --- a/src/RaspAP/Networking/Hotspot/HotspotService.php +++ b/src/RaspAP/Networking/Hotspot/HotspotService.php @@ -32,7 +32,9 @@ class HotspotService 'b' => '802.11b - 2.4 GHz', 'g' => '802.11g - 2.4 GHz', 'n' => '802.11n - 2.4/5 GHz', - 'ac' => '802.11ac - 5 GHz' + 'ac' => '802.11ac - 5 GHz', + 'ax' => '802.11ax (Wi-Fi 6) - 2.4/5/6 GHz', + // 'be' => '802.11be (Wi-Fi 7) - 2.4/5/6 GHz' ]; // encryption types @@ -42,6 +44,21 @@ class HotspotService 'TKIP CCMP' => 'TKIP+CCMP' ]; + // 802.11ax (Wi-Fi 6) channel widths + private const HE_CHANNEL_WIDTHS = [ + 0 => '20/40 MHz', + 1 => '80 MHz', + 2 => '160 MHz' + ]; + + // 802.11be (Wi-Fi 7) channel widths + private const EHT_CHANNEL_WIDTHS = [ + 0 => '20 MHz', + 1 => '40 MHz', + 2 => '80 MHz', + 3 => '160 MHz', + 4 => '320 MHz (6 GHz only)' + ]; public function __construct() { @@ -67,7 +84,23 @@ class HotspotService } /** - * Returns translated security modes. + * Returns 802.11ax (Wi-Fi 6) channel widths + */ + public static function getHeChannelWidths(): array + { + return self::HE_CHANNEL_WIDTHS; + } + + /** + * Returns 802.11be (Wi-Fi 7) channel widths + */ + public static function getEhtChannelWidths(): array + { + return self::EHT_CHANNEL_WIDTHS; + } + + /** + * Returns translated security modes */ public static function getSecurityModes(): array { @@ -94,6 +127,26 @@ class HotspotService ]; } + /** + * Checks if hardware mode supports advanced features + * + * @param string $mode + * @return array capabilities + */ + public static function getModeCapabilities(string $mode): array + { + $capabilities = [ + 'a' => ['wifi_generation' => 1, 'max_width' => 20, 'bands' => ['5']], + 'b' => ['wifi_generation' => 2, 'max_width' => 22, 'bands' => ['2.4']], + 'g' => ['wifi_generation' => 3, 'max_width' => 20, 'bands' => ['2.4']], + 'n' => ['wifi_generation' => 4, 'max_width' => 40, 'bands' => ['2.4', '5']], + 'ac' => ['wifi_generation' => 5, 'max_width' => 160, 'bands' => ['5']], + 'ax' => ['wifi_generation' => 6, 'max_width' => 160, 'bands' => ['2.4', '5', '6'], 'supports_he' => true], + 'be' => ['wifi_generation' => 7, 'max_width' => 320, 'bands' => ['2.4', '5', '6'], 'supports_he' => true, 'supports_eht' => true] + ]; + + return $capabilities[$mode] ?? $capabilities['g']; + } /** * Validates user input + saves configs for hostapd, dnsmasq & dhcp @@ -151,6 +204,23 @@ class HotspotService $validated['dualmode'] = !empty($states['DualAPEnable']); $validated['txpower'] = $post_data['txpower']; + error_log("HotspotService::saveSettings() -> validated\n" . var_export($validated, true)); + + // add 802.11ax/be specific parameters if present + if (in_array($validated['hw_mode'], ['ax', 'be'])) { + // Log advanced mode configuration + error_log(sprintf( + "Configuring advanced WiFi mode: %s with channel %d", + $validated['hw_mode'], + $validated['channel'] + )); + + // Validate WPA3 for WiFi 6/7 + if ($validated['wpa'] < 4 && $validated['hw_mode'] === 'be') { + $status->addMessage('Note: WiFi 7 works best with WPA3 security', 'info'); + } + } + // hostapd $config = $this->hostapd->buildConfig($validated, $status); $this->hostapd->saveConfig($config, $dualAPEnable, $validated['interface']); From 0c8e1a310cae6cef2687af0038afdadfceac9ca3 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 8 Nov 2025 09:55:03 +0100 Subject: [PATCH 04/22] Validate 802.11ax/be specific parameters --- .../Hotspot/Validators/HostapdValidator.php | 109 +++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/src/RaspAP/Networking/Hotspot/Validators/HostapdValidator.php b/src/RaspAP/Networking/Hotspot/Validators/HostapdValidator.php index 83627e11..80dbb832 100644 --- a/src/RaspAP/Networking/Hotspot/Validators/HostapdValidator.php +++ b/src/RaspAP/Networking/Hotspot/Validators/HostapdValidator.php @@ -16,6 +16,15 @@ use RaspAP\Messages\StatusMessage; class HostapdValidator { + // Valid channel widths for 802.11ax (HE) + private const HE_VALID_CHWIDTHS = [0, 1, 2]; // 20/40, 80, 160 MHz + + // Valid channel widths for 802.11be (EHT) + private const EHT_VALID_CHWIDTHS = [0, 1, 2, 3, 4]; // 20, 40, 80, 160, 320 MHz + + // 6 GHz channel range (US) + private const CHANNEL_6GHZ_MIN = 1; + private const CHANNEL_6GHZ_MAX = 233; /** * Validates full hostapd parameter set @@ -64,6 +73,16 @@ class HostapdValidator $goodInput = false; } + // validate 802.11ax specific parameters + if ($post['hw_mode'] === 'ax' && !$this->validateAxParams($post, $status)) { + $goodInput = false; + } + + // validate 802.11be specific parameters + if ($post['hw_mode'] === 'be' && !$this->validateBeParams($post, $status)) { + $goodInput = false; + } + // validate SSID if (empty($post['ssid']) || strlen($post['ssid']) > 32) { $status->addMessage('SSID must be between 1 and 32 characters', 'danger'); @@ -200,9 +219,97 @@ class HostapdValidator 'bridgeStaticIp' => ($post['bridgeStaticIp']), 'bridgeNetmask' => ($post['bridgeNetmask']), 'bridgeGateway' => ($post['bridgeGateway']), - 'bridgeDNS' => ($post['bridgeDNS']) + 'bridgeDNS' => ($post['bridgeDNS']), + 'he_oper_chwidth' => $post['he_oper_chwidth'] ?? null, // 802.11ax parameters + 'he_bss_color' => $post['he_bss_color'] ?? null, // 802.11be parameters + 'eht_oper_chwidth' => $post['eht_oper_chwidth'] ?? null ]; } + /** + * Validates 802.11ax (Wi-Fi 6) specific parameters + * + * @param array $post + * @param StatusMessage $status + * @return bool + */ + private function validateAxParams(array $post, StatusMessage $status): bool + { + $valid = true; + + // Validate HE channel width + if (isset($post['he_oper_chwidth'])) { + $chwidth = (int)$post['he_oper_chwidth']; + if (!in_array($chwidth, self::HE_VALID_CHWIDTHS, true)) { + $status->addMessage('Invalid 802.11ax channel width. Must be 0 (20/40 MHz), 1 (80 MHz), or 2 (160 MHz)', 'danger'); + $valid = false; + } + } + + // Validate BSS color (1-63) + if (isset($post['he_bss_color'])) { + $bssColor = (int)$post['he_bss_color']; + if ($bssColor < 1 || $bssColor > 63) { + $status->addMessage('802.11ax BSS color must be between 1 and 63', 'danger'); + $valid = false; + } + } + + return $valid; + } + + /** + * Validates 802.11be (Wi-Fi 7) specific parameters + * + * @param array $post + * @param StatusMessage $status + * @return bool + */ + private function validateBeParams(array $post, StatusMessage $status): bool + { + $valid = true; + $channel = (int)$post['channel']; + + // Validate EHT channel width + if (isset($post['eht_oper_chwidth'])) { + $chwidth = (int)$post['eht_oper_chwidth']; + + if (!in_array($chwidth, self::EHT_VALID_CHWIDTHS, true)) { + $status->addMessage('Invalid 802.11be channel width. Must be 0-4 (20, 40, 80, 160, or 320 MHz)', 'danger'); + $valid = false; + } + + // 320 MHz only valid on 6 GHz band + if ($chwidth === 4) { + if ($channel < self::CHANNEL_6GHZ_MIN || $channel > self::CHANNEL_6GHZ_MAX) { + $status->addMessage('802.11be 320 MHz channel width is only available on 6 GHz band (channels 1-233)', 'danger'); + $valid = false; + } + } + } + + // Validate BSS color (same as 802.11ax, inherited) + if (isset($post['he_bss_color'])) { + $bssColor = (int)$post['he_bss_color']; + if ($bssColor < 1 || $bssColor > 63) { + $status->addMessage('BSS color must be between 1 and 63', 'danger'); + $valid = false; + } + } + + return $valid; + } + + /** + * Checks if channel is in 6GHz band + * + * @param int $channel + * @return bool + */ + private function is6GHzChannel(int $channel): bool + { + return $channel >= self::CHANNEL_6GHZ_MIN && $channel <= self::CHANNEL_6GHZ_MAX; + } + } From e360211ae42aeb0fcf4053e5511740266ef6338a Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 16 Nov 2025 14:41:12 +0100 Subject: [PATCH 05/22] Update 802.11ax he, ht + vht params --- config/defaults.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/config/defaults.json b/config/defaults.json index 0fbf7888..56f1532c 100644 --- a/config/defaults.json +++ b/config/defaults.json @@ -30,6 +30,7 @@ "# Basic settings", "hw_mode=a", "# Enable 802.11n/ac", + "ieee80211d=1", "ieee80211n=1", "ieee80211ac=1", "# Enable 802.11ax", @@ -40,8 +41,11 @@ "he_mu_beamformer=1", "# BSS color for spatial reuse, value 1-63", "he_bss_color=1", - "he_oper_chwidth=2", - "he_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}", + "he_oper_chwidth=1", + "ht_capab=[HT40+][SHORT-GI-20][SHORT-GI-40]", + "vht_capab=[SHORT-GI-80]", + "he_oper_centr_freq_seg0_idx=155", + "vht_oper_centr_freq_seg0_idx=155", "wmm_enabled=1" ] }, From 955add1b251b7f0d9502354bb30f738c35813307 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 7 Dec 2025 14:54:51 +0100 Subject: [PATCH 06/22] Update hw_mode channel mapping in loadChannelSelect() --- app/js/ajax/main.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/js/ajax/main.js b/app/js/ajax/main.js index f8bf997a..ce9edf6b 100644 --- a/app/js/ajax/main.js +++ b/app/js/ajax/main.js @@ -381,12 +381,15 @@ function loadChannelSelect(selected) { // Map selected hw_mode to available channels if (hw_mode === 'a') { selectableChannels = data.filter(item => item.MHz.toString().startsWith('5')); - } else if (hw_mode !== 'ac') { - selectableChannels = data.filter(item => item.MHz.toString().startsWith('24')); - } else if (hw_mode === 'b') { - selectableChannels = data.filter(item => item.MHz.toString().startsWith('24')); } else if (hw_mode === 'ac') { selectableChannels = data.filter(item => item.MHz.toString().startsWith('5')); + } else if (hw_mode === 'ax') { + selectableChannels = data.filter(item => item.MHz.toString().startsWith('5')); + } else if (hw_mode === 'be') { + selectableChannels = data.filter(item => item.MHz.toString().startsWith('5')); + } else { + // hw_mode 'b', 'g', or default to 2.4GHz + selectableChannels = data.filter(item => item.MHz.toString().startsWith('24')); } // If selected channel doeesn't exist in allowed channels, set default or null (unsupported) From de9f7548d5cc13eff2844a612f8f1f11ed559041 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 7 Dec 2025 14:57:17 +0100 Subject: [PATCH 07/22] Replace static HE/VHT center freq + HT40 dir values with placeholders --- config/defaults.json | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/config/defaults.json b/config/defaults.json index 56f1532c..10fcd97a 100644 --- a/config/defaults.json +++ b/config/defaults.json @@ -14,7 +14,7 @@ "# N", "ieee80211n=1", "require_ht=1", - "ht_capab=[MAX-AMSDU-3839][HT40+][SHORT-GI-20][SHORT-GI-40][DSSS_CCK-40]", + "ht_capab=[MAX-AMSDU-3839][{HT40_DIR}][SHORT-GI-20][SHORT-GI-40][DSSS_CCK-40]", "# AC", "ieee80211ac=1", "require_vht=1", @@ -35,17 +35,23 @@ "ieee80211ac=1", "# Enable 802.11ax", "ieee80211ax=1", - "# High efficiency capabilities", + "# HE (802.11ax) capabilities", "he_su_beamformer=1", "he_su_beamformee=1", "he_mu_beamformer=1", "# BSS color for spatial reuse, value 1-63", "he_bss_color=1", "he_oper_chwidth=1", - "ht_capab=[HT40+][SHORT-GI-20][SHORT-GI-40]", - "vht_capab=[SHORT-GI-80]", - "he_oper_centr_freq_seg0_idx=155", - "vht_oper_centr_freq_seg0_idx=155", + "# HE/VHT channel widths", + "he_oper_chwidth=1", + "vht_oper_chwidth=1", + "he_oper_centr_freq_seg0_idx={HE_FREQ_IDX}", + "vht_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}", + "# HT capabilities (802.11n)", + "ht_capab=[{HT40_DIR}][LDPC][SHORT-GI-20][SHORT-GI-40][TX-STBC][RX-STBC1][MAX-AMSDU-7935]", + "# VHT capabilities (802.11ac) - no SHORT-GI-160", + "vht_capab=[RXLDPC][SHORT-GI-80][TX-STBC-2BY1][RX-STBC-1][MAX-MPDU-11454][MAX-A-MPDU-LEN-EXP7]", + "# WMM/QoS", "wmm_enabled=1" ] }, From c29f45867c1509a88a986a52c1a0bdd45a28e515 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 7 Dec 2025 15:17:07 +0100 Subject: [PATCH 08/22] Add dynamic center frequency + HT40 direction for 802.11ax/be --- .../Networking/Hotspot/HostapdManager.php | 254 +++++++++++++++++- 1 file changed, 249 insertions(+), 5 deletions(-) diff --git a/src/RaspAP/Networking/Hotspot/HostapdManager.php b/src/RaspAP/Networking/Hotspot/HostapdManager.php index 0bcbbcb4..495722b9 100644 --- a/src/RaspAP/Networking/Hotspot/HostapdManager.php +++ b/src/RaspAP/Networking/Hotspot/HostapdManager.php @@ -191,8 +191,6 @@ class HostapdManager $config[] = 'ssid=' . $params['ssid']; $config[] = 'channel=' . $params['channel']; - // choose VHT segment index (fallback only if required) - $vht_freq_idx = ($params['channel'] < RASPI_5GHZ_CHANNEL_MIN) ? 42 : 155; $hwMode = isset($params['hw_mode']) ? $params['hw_mode'] : ''; // validate channel width for 802.11ax/be @@ -211,14 +209,24 @@ class HostapdManager $modeSettings = getDefaultNetOpts('hostapd', 'modes', $hwMode); $settings = $modeSettings[$hwMode]['settings'] ?? []; - error_log("HostapdManager::buildConfig() -> settings\n" . var_export($settings, true)); + // extract channel width from settings to calculate center frequency + $chwidth = $this->extractChannelWidth($settings, $hwMode); + + // calculate center frequency indices based on channel + width + $vht_freq_idx = $this->calculateCenterFreqIndex((int)$params['channel'], $chwidth); + $he_freq_idx = $vht_freq_idx; // For most cases, HE and VHT use the same center frequency + + // calculate HT40 direction based on channel and width + $ht40_dir = $this->calculateHT40Direction((int)$params['channel'], $chwidth); if (!empty($settings)) { foreach ($settings as $line) { if (!is_string($line)) { continue; } - $replaced = str_replace('{VHT_FREQ_IDX}', (string) $vht_freq_idx ?? '',$line); + $replaced = str_replace('{VHT_FREQ_IDX}', (string) $vht_freq_idx ?? '', $line); + $replaced = str_replace('{HE_FREQ_IDX}', (string) $he_freq_idx ?? '', $replaced); + $replaced = str_replace('{HT40_DIR}', (string) $ht40_dir ?? '', $replaced); $config[] = $replaced; } } @@ -498,5 +506,241 @@ class HostapdManager return $capabilities[$mode] ?? $capabilities['g']; } -} + /** + * Extracts channel width from mode settings + * + * @param array $settings mode settings array + * @param string $hwMode hardware mode (ac, ax, be) + * @return int channel width in MHz (20, 40, 80, 160, 320) + */ + private function extractChannelWidth(array $settings, string $hwMode): int + { + $chwidthParam = ''; + // determine parameter based on mode + if ($hwMode === 'ac') { + $chwidthParam = 'vht_oper_chwidth'; + } elseif ($hwMode === 'ax') { + $chwidthParam = 'he_oper_chwidth'; + } elseif ($hwMode === 'be') { + $chwidthParam = 'eht_oper_chwidth'; + } else { + return 20; // 20 MHz default for other modes + } + + // parse settings to find channel width + foreach ($settings as $line) { + if (!is_string($line)) { + continue; + } + + // skip comments + if (strpos(trim($line), '#') === 0) { + continue; + } + + if (strpos($line, $chwidthParam . '=') !== false) { + $parts = explode('=', $line, 2); + if (count($parts) === 2) { + // extract numeric value + $value = trim($parts[1]); + // remove any inline comments + $value = preg_replace('/\s*#.*$/', '', $value); + $chwidthCode = (int) $value; + + // convert hostapd encoding to MHz based on mode + if ($hwMode === 'be') { + // EHT uses: 0=20, 1=40, 2=80, 3=160, 4=320 + switch ($chwidthCode) { + case 0: return 20; + case 1: return 40; + case 2: return 80; + case 3: return 160; + case 4: return 320; + default: return 20; + } + } else { + // VHT/HE uses: 0=20/40, 1=80, 2=160, 3=80+80 + switch ($chwidthCode) { + case 0: return 40; + case 1: return 80; + case 2: return 160; + case 3: return 160; // 80+80 treated as 160 + default: return 20; + } + } + } + } + } + + return 20; // default to 20 MHz channel if not found + } + + /** + * Calculates center frequency segment 0 index for given channel and width + * + * @param int $channel primary channel number + * @param int $chwidthMHz channel width in MHz (20, 40, 80, 160, 320) + * @return int center frequency segment 0 index + */ + private function calculateCenterFreqIndex(int $channel, int $chwidthMHz): int + { + // determine band based on channel number + $is24GHz = ($channel >= 1 && $channel <= 14); + $is5GHz = ($channel >= 36 && $channel <= 177); + $is6GHz = ($channel >= 1 && $channel <= 233 && !$is24GHz); // 6 GHz uses 1-233 + + // 20 MHz - center is always primary channel + if ($chwidthMHz <= 20) { + return $channel; + } + + // 2.4 GHz band + if ($is24GHz) { + if ($chwidthMHz == 40) { + // for 2.4 GHz, typically use HT40+ (center is primary + 2) + // channels 1-7 use HT40+, channels 8-13 use HT40- + return ($channel <= 7) ? $channel + 2 : $channel - 2; + } + // wider bandwidths not supported on 2.4 GHz + return $channel; + } + + // 5 GHz band + if ($is5GHz) { + if ($chwidthMHz == 40) { + // HT40+ configuration: center = primary + 2 + // adjust for upper/lower position in 40 MHz pair + if (in_array($channel, [36, 44, 52, 60, 100, 108, 116, 124, 132, 140, 149, 157, 165, 173])) { + return $channel + 2; + } else { + return $channel - 2; + } + } + + if ($chwidthMHz == 80) { + // map channel to 80 MHz center frequency + if ($channel >= 36 && $channel <= 48) return 42; + if ($channel >= 52 && $channel <= 64) return 58; + if ($channel >= 100 && $channel <= 112) return 106; + if ($channel >= 116 && $channel <= 128) return 122; + if ($channel >= 132 && $channel <= 144) return 138; + if ($channel >= 149 && $channel <= 161) return 155; + if ($channel >= 165 && $channel <= 177) return 171; + } + + if ($chwidthMHz == 160) { + // map channel to 160 MHz center frequency + if ($channel >= 36 && $channel <= 64) return 50; + if ($channel >= 100 && $channel <= 128) return 114; + // channels 149-177 don't support 160 MHz in most regions + if ($channel >= 149 && $channel <= 177) return 163; + } + } + + // 6 GHz band (UNII-5 through UNII-8) + if ($is6GHz && !$is24GHz) { + // 6 GHz uses different channel numbering: 1, 5, 9, 13, ... (every 4) + if ($chwidthMHz == 40) { + // center is at the midpoint between two 20 MHz channels + return $channel + 2; + } + + if ($chwidthMHz == 80) { + // calculate 80 MHz center + $blockStart = (int)(($channel - 1) / 16) * 16 + 1; + return $blockStart + 6; + } + + if ($chwidthMHz == 160) { + // calculate 160 MHz center + $blockStart = (int)(($channel - 1) / 32) * 32 + 1; + return $blockStart + 14; + } + + if ($chwidthMHz == 320) { + // calculate 320 MHz center + $blockStart = (int)(($channel - 1) / 64) * 64 + 1; + return $blockStart + 30; + } + } + + // fallback: return primary channel + return $channel; + } + + /** + * Calculates HT40 direction (+ or -) based on channel and bandwidth + * + * @param int $channel primary channel number + * @param int $chwidthMHz channel width in MHz + * @return string HT40 direction: "HT40+" or "HT40-" or "" for 20MHz + */ + private function calculateHT40Direction(int $channel, int $chwidthMHz): string + { + // only applicable for 40 MHz and wider on 5 GHz + if ($chwidthMHz < 40) { + return ''; + } + + $is24GHz = ($channel >= 1 && $channel <= 14); + $is5GHz = ($channel >= 36 && $channel <= 177); + + // 2.4 GHz band + if ($is24GHz) { + // channels 1-7 use HT40+, channels 8-13 use HT40- + return ($channel <= 7) ? 'HT40+' : 'HT40-'; + } + + // 5 GHz band + if ($is5GHz) { + if ($chwidthMHz == 40) { + // for pure 40 MHz mode + if (in_array($channel, [36, 44, 52, 60, 100, 108, 116, 124, 132, 140, 149, 157, 165, 173])) { + return 'HT40+'; + } else { + return 'HT40-'; + } + } + + if ($chwidthMHz >= 80) { + // for 80 MHz and wider, determine based on position within the 80 MHz block + // lower half of 80 MHz block uses HT40+, upper half uses HT40- + + // determine which 80 MHz block this channel belongs to + if ($channel >= 36 && $channel <= 48) { + // block: 36, 40, 44, 48 (center 42) + return ($channel <= 40) ? 'HT40+' : 'HT40-'; + } + if ($channel >= 52 && $channel <= 64) { + // block: 52, 56, 60, 64 (center 58) + return ($channel <= 56) ? 'HT40+' : 'HT40-'; + } + if ($channel >= 100 && $channel <= 112) { + // block: 100, 104, 108, 112 (center 106) + return ($channel <= 104) ? 'HT40+' : 'HT40-'; + } + if ($channel >= 116 && $channel <= 128) { + // block: 116, 120, 124, 128 (center 122) + return ($channel <= 120) ? 'HT40+' : 'HT40-'; + } + if ($channel >= 132 && $channel <= 144) { + // block: 132, 136, 140, 144 (center 138) + return ($channel <= 136) ? 'HT40+' : 'HT40-'; + } + if ($channel >= 149 && $channel <= 161) { + // block: 149, 153, 157, 161 (center 155) + return ($channel <= 153) ? 'HT40+' : 'HT40-'; + } + if ($channel >= 165 && $channel <= 177) { + // block: 165, 169, 173, 177 (center 171) + return ($channel <= 169) ? 'HT40+' : 'HT40-'; + } + } + } + + // default fallback + return 'HT40+'; + } + +} From 8628c8d5b1fc0f0d2e311fcfc181a1bf97364fc7 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 7 Dec 2025 18:25:08 +0100 Subject: [PATCH 09/22] Update 802.11be defaults --- config/defaults.json | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/config/defaults.json b/config/defaults.json index 10fcd97a..ff8609a8 100644 --- a/config/defaults.json +++ b/config/defaults.json @@ -63,15 +63,14 @@ "ieee80211n=1", "ieee80211ac=1", "ieee80211ax=1", - "vht_oper_chwidth=2 # 160 MHz for VHT", - "vht_oper_centr_freq_seg0_idx=50", - "vht_capab=[MAX-MPDU-11454][RXLDPC][SHORT-GI-80][SHORT-GI-160][TX-STBC-2BY1][RX-STBC-1][SU-BEAMFORMER][SU-BEAMFORMEE][MU-BEAMFORMER][MU-BEAMFORMEE]", + "# HE capabilities", "he_su_beamformer=1", "he_su_beamformee=1", "he_mu_beamformer=1", "he_bss_color=1", - "he_oper_chwidth=2 # 160 MHz for HE", - "he_oper_centr_freq_seg0_idx=50", + "# 160 MHz for HE", + "he_oper_chwidth=2", + "he_oper_centr_freq_seg0_idx={HE_FREQ_IDX}", "# Enable 802.11be", "ieee80211be=1", "# EHT configuration", @@ -80,8 +79,13 @@ "eht_mu_beamformer=1", "eht_oper_chwidth=2", "# EHT operation parameters", - "eht_oper_chwidth=2 # 160 MHz, 0=20, 1=40, 2=80, 3=160, 4=320", + "# 160 MHz, 0=20, 1=40, 2=80, 3=160, 4=320", + "eht_oper_chwidth=2", "eht_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}", + "# VHT operation parameters", + "vht_oper_chwidth=2", + "vht_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}", + "vht_capab=[MAX-MPDU-11454][RXLDPC][SHORT-GI-80][SHORT-GI-160][TX-STBC-2BY1][RX-STBC-1][SU-BEAMFORMER][SU-BEAMFORMEE][MU-BEAMFORMER][MU-BEAMFORMEE]", "# WMM configuration", "wmm_enabled=1", "wmm_ac_bk_cwmin=4", From fc8508b260e2a6822934509d57fc1ac0a0723d85 Mon Sep 17 00:00:00 2001 From: billz Date: Mon, 8 Dec 2025 13:27:16 +0100 Subject: [PATCH 10/22] Expand 802.11be / WiFi 7 default settings --- config/defaults.json | 73 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 6 deletions(-) diff --git a/config/defaults.json b/config/defaults.json index ff8609a8..cfb2dac1 100644 --- a/config/defaults.json +++ b/config/defaults.json @@ -63,23 +63,84 @@ "ieee80211n=1", "ieee80211ac=1", "ieee80211ax=1", - "# HE capabilities", + "# Maximum MPDU Length of HE 6 GHz band capabilities.", + "# Indicates maximum MPDU length", + "# 0 = 3895 octets", + "# 1 = 7991 octets", + "# 2 = 11454 octets", + "he_6ghz_max_mpdu=2", + "# Maximum A-MPDU Length Exponent of HE 6 GHz band capabilities. Indicates", + "# the maximum length of A-MPDU pre-EOF padding that # the STA can receive.", + "# 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", + "# 0 = AMPDU length of 8k", + "# 1 = AMPDU length of 16k", + "# 2 = AMPDU length of 32k", + "# 3 = AMPDU length of 65k", + "# 4 = AMPDU length of 131k", + "# 5 = AMPDU length of 262kv", + "# 6 = AMPDU length of 524k", + "# 7 = AMPDU length of 1048k", + "he_6ghz_max_ampdu_len_exp=7", + "# 0 = Indoor AP", + "# 1 = Standard power AP", + "# 2 = Very low power AP (default)", + "# 3 = Indoor enabled AP", + "# 4 = Indoor standard power AP", + "he_6ghz_reg_pwr_type=0", + "# HE beamforming capabilities", "he_su_beamformer=1", "he_su_beamformee=1", "he_mu_beamformer=1", - "he_bss_color=1", + "he_mu_edca_qos_info_param_count=0", + "he_mu_edca_qos_info_q_ack=0", + "he_mu_edca_qos_info_queue_request=0", + "he_mu_edca_qos_info_txop_request=0", + "he_mu_edca_ac_be_aifsn=8", + "he_mu_edca_ac_be_aci=0", + "he_mu_edca_ac_be_ecwmin=9", + "he_mu_edca_ac_be_ecwmax=10", + "he_mu_edca_ac_be_timer=255", + "he_mu_edca_ac_bk_aifsn=15", + "he_mu_edca_ac_bk_aci=1", + "he_mu_edca_ac_bk_ecwmin=9", + "he_mu_edca_ac_bk_ecwmax=10", + "he_mu_edca_ac_bk_timer=255", + "he_mu_edca_ac_vi_ecwmin=5", + "he_mu_edca_ac_vi_ecwmax=7", + "he_mu_edca_ac_vi_aifsn=5", + "he_mu_edca_ac_vi_aci=2", + "he_mu_edca_ac_vi_timer=255", + "he_mu_edca_ac_vo_aifsn=5", + "he_mu_edca_ac_vo_aci=3", + "he_mu_edca_ac_vo_ecwmin=5", + "he_mu_edca_ac_vo_ecwmax=7", + "he_mu_edca_ac_vo_timer=255", + "# EHT beamforming capabilities", + "eht_su_beamformer=0", + "eht_su_beamformee=0", + "eht_mu_beamformer=0", + "# used by clients to discern the source of interference", + "# each AP in your area needs to use a different number", + "# allowed: 1-63", + "he_bss_color=37", "# 160 MHz for HE", "he_oper_chwidth=2", "he_oper_centr_freq_seg0_idx={HE_FREQ_IDX}", - "# Enable 802.11be", + "# IEEE 802.11be (WiFi 7) configuration", "ieee80211be=1", "# EHT configuration", "eht_su_beamformer=1", "eht_su_beamformee=1", "eht_mu_beamformer=1", - "eht_oper_chwidth=2", - "# EHT operation parameters", - "# 160 MHz, 0=20, 1=40, 2=80, 3=160, 4=320", + "# EHT operating channel information; see matching he_* parameters for details.", + "# The field eht_oper_centr_freq_seg0_idx field is used to indicate center", + "# frequency of 40, 80, and 160 MHz bandwidth operation.", + "# In the 6 GHz band, eht_oper_chwidth is ignored and the channel width is", + "# derived from the configured operating class (IEEE P802.11be/D1.5,", + "# Annex E.1 - Country information and operating classes).", + "# Channel width (0 = 40 MHz, 1 = 80 Mhz, 2 = 160 Mhz)", "eht_oper_chwidth=2", "eht_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}", "# VHT operation parameters", From ac8a6fe4a88f09aba49480589c8b5f81c2ee85e2 Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 13 Dec 2025 12:12:55 +0100 Subject: [PATCH 11/22] Remove parentheses from JSON, resolves PHP warning --- config/defaults.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/config/defaults.json b/config/defaults.json index cfb2dac1..3c4315e4 100644 --- a/config/defaults.json +++ b/config/defaults.json @@ -35,7 +35,7 @@ "ieee80211ac=1", "# Enable 802.11ax", "ieee80211ax=1", - "# HE (802.11ax) capabilities", + "# HE 802.11ax capabilities", "he_su_beamformer=1", "he_su_beamformee=1", "he_mu_beamformer=1", @@ -47,9 +47,9 @@ "vht_oper_chwidth=1", "he_oper_centr_freq_seg0_idx={HE_FREQ_IDX}", "vht_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}", - "# HT capabilities (802.11n)", + "# HT 802.11n capabilities", "ht_capab=[{HT40_DIR}][LDPC][SHORT-GI-20][SHORT-GI-40][TX-STBC][RX-STBC1][MAX-AMSDU-7935]", - "# VHT capabilities (802.11ac) - no SHORT-GI-160", + "# VHT capabilities 802.11ac", "vht_capab=[RXLDPC][SHORT-GI-80][TX-STBC-2BY1][RX-STBC-1][MAX-MPDU-11454][MAX-A-MPDU-LEN-EXP7]", "# WMM/QoS", "wmm_enabled=1" @@ -85,7 +85,7 @@ "he_6ghz_max_ampdu_len_exp=7", "# 0 = Indoor AP", "# 1 = Standard power AP", - "# 2 = Very low power AP (default)", + "# 2 = Very low power AP", "# 3 = Indoor enabled AP", "# 4 = Indoor standard power AP", "he_6ghz_reg_pwr_type=0", @@ -128,7 +128,7 @@ "# 160 MHz for HE", "he_oper_chwidth=2", "he_oper_centr_freq_seg0_idx={HE_FREQ_IDX}", - "# IEEE 802.11be (WiFi 7) configuration", + "# IEEE 802.11be WiFi 7 configuration", "ieee80211be=1", "# EHT configuration", "eht_su_beamformer=1", @@ -138,9 +138,9 @@ "# The field eht_oper_centr_freq_seg0_idx field is used to indicate center", "# frequency of 40, 80, and 160 MHz bandwidth operation.", "# In the 6 GHz band, eht_oper_chwidth is ignored and the channel width is", - "# derived from the configured operating class (IEEE P802.11be/D1.5,", + "# derived from the configured operating class IEEE P802.11be/D1.5,", "# Annex E.1 - Country information and operating classes).", - "# Channel width (0 = 40 MHz, 1 = 80 Mhz, 2 = 160 Mhz)", + "# Channel width 0 = 40 MHz, 1 = 80 Mhz, 2 = 160 Mhz", "eht_oper_chwidth=2", "eht_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}", "# VHT operation parameters", From a25e0f1956dc2429efc01b8e242746316198947f Mon Sep 17 00:00:00 2001 From: billz Date: Sat, 13 Dec 2025 12:13:28 +0100 Subject: [PATCH 12/22] Fix TypeError when hostapd config parsing fails --- app/img/wifi-qr-code.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/img/wifi-qr-code.php b/app/img/wifi-qr-code.php index 19171094..a0017c3a 100755 --- a/app/img/wifi-qr-code.php +++ b/app/img/wifi-qr-code.php @@ -12,6 +12,12 @@ if (!isset($_SERVER['HTTP_REFERER'])) { $hostapd = parse_ini_file(RASPI_HOSTAPD_CONFIG, false, INI_SCANNER_RAW); +// handle parse failure +if ($hostapd === false) { + header('HTTP/1.0 500 Internal Server Error'); + exit('Error: Unable to parse hostapd configuration'); +} + // assume WPA encryption and get the passphrase $type = "WPA"; $password = isset($hostapd['wpa_psk']) ? $hostapd['wpa_psk'] : $hostapd['wpa_passphrase']; From 3ab66d3e7659048fe54703f3b8f3e983ea1065a9 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 14 Dec 2025 08:12:26 +0100 Subject: [PATCH 13/22] Fix undefined array key warning in hostapd config parsing --- ajax/networking/get_channel.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ajax/networking/get_channel.php b/ajax/networking/get_channel.php index 716456c4..e158588e 100644 --- a/ajax/networking/get_channel.php +++ b/ajax/networking/get_channel.php @@ -13,7 +13,9 @@ foreach ($hostapdconfig as $hostapdconfigline) { continue; } $arrLine = explode("=", $hostapdconfigline); - $arrConfig[$arrLine[0]]=$arrLine[1]; + if (count($arrLine) >= 2) { + $arrConfig[$arrLine[0]]=$arrLine[1]; + } }; $channel = intval($arrConfig['channel']); echo json_encode($channel); From 0a3dbef531e92a3f7f0fb02bacccdaaa9c448c63 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 14 Dec 2025 08:29:25 +0100 Subject: [PATCH 14/22] Fix 802.11be mode not displaying in UI when WPA3 is enabled --- src/RaspAP/Networking/Hotspot/HostapdManager.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/RaspAP/Networking/Hotspot/HostapdManager.php b/src/RaspAP/Networking/Hotspot/HostapdManager.php index 495722b9..532d41a0 100644 --- a/src/RaspAP/Networking/Hotspot/HostapdManager.php +++ b/src/RaspAP/Networking/Hotspot/HostapdManager.php @@ -108,9 +108,6 @@ class HostapdManager if (!empty($config['ieee80211be']) && strval($config['ieee80211be']) === '1') { $selected = 'be'; } - if (!empty($config['ieee80211w']) && strval($config['ieee80211w']) === '2') { - $selected = 'w'; - } return $selected; } From c03f81719c3c9c1379bebf76333fb618680cb1fc Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 14 Dec 2025 08:30:14 +0100 Subject: [PATCH 15/22] Enable 802.11be / WiFi 7 mode in getModeCapabilities() --- src/RaspAP/Networking/Hotspot/HotspotService.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/RaspAP/Networking/Hotspot/HotspotService.php b/src/RaspAP/Networking/Hotspot/HotspotService.php index d8f37c39..854fbae3 100644 --- a/src/RaspAP/Networking/Hotspot/HotspotService.php +++ b/src/RaspAP/Networking/Hotspot/HotspotService.php @@ -34,7 +34,7 @@ class HotspotService 'n' => '802.11n - 2.4/5 GHz', 'ac' => '802.11ac - 5 GHz', 'ax' => '802.11ax (Wi-Fi 6) - 2.4/5/6 GHz', - // 'be' => '802.11be (Wi-Fi 7) - 2.4/5/6 GHz' + 'be' => '802.11be (Wi-Fi 7) - 2.4/5/6 GHz' ]; // encryption types @@ -191,7 +191,6 @@ class HotspotService if ($validated === false) { $status->addMessage('Unable to save WiFi hotspot settings due to validation errors', 'danger'); - error_log("HotspotService::validate() -> validated = false"); return false; } @@ -204,8 +203,6 @@ class HotspotService $validated['dualmode'] = !empty($states['DualAPEnable']); $validated['txpower'] = $post_data['txpower']; - error_log("HotspotService::saveSettings() -> validated\n" . var_export($validated, true)); - // add 802.11ax/be specific parameters if present if (in_array($validated['hw_mode'], ['ax', 'be'])) { // Log advanced mode configuration From 23b964a17243631469107b016d6780bf3b0409e4 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 14 Dec 2025 08:30:54 +0100 Subject: [PATCH 16/22] Remove parentheses from 802.11be config template --- config/defaults.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/defaults.json b/config/defaults.json index 3c4315e4..a586eebd 100644 --- a/config/defaults.json +++ b/config/defaults.json @@ -72,7 +72,7 @@ "# Maximum A-MPDU Length Exponent of HE 6 GHz band capabilities. Indicates", "# the maximum length of A-MPDU pre-EOF padding that # the STA can receive.", "# 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", + "# this field is equal to 2 pow -1", "# octets", "# 0 = AMPDU length of 8k", "# 1 = AMPDU length of 16k", @@ -139,7 +139,7 @@ "# frequency of 40, 80, and 160 MHz bandwidth operation.", "# In the 6 GHz band, eht_oper_chwidth is ignored and the channel width is", "# derived from the configured operating class IEEE P802.11be/D1.5,", - "# Annex E.1 - Country information and operating classes).", + "# Annex E.1 - Country information and operating classes.", "# Channel width 0 = 40 MHz, 1 = 80 Mhz, 2 = 160 Mhz", "eht_oper_chwidth=2", "eht_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}", From 321173553492da943b0ad55df3e80b45e54a568c Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 21 Dec 2025 19:13:33 +0100 Subject: [PATCH 17/22] Fix dhcpcd.conf duplicate entries + trailing newline accumulation --- .../Networking/Hotspot/DhcpcdManager.php | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/RaspAP/Networking/Hotspot/DhcpcdManager.php b/src/RaspAP/Networking/Hotspot/DhcpcdManager.php index 7d5ad084..90115f69 100644 --- a/src/RaspAP/Networking/Hotspot/DhcpcdManager.php +++ b/src/RaspAP/Networking/Hotspot/DhcpcdManager.php @@ -131,15 +131,15 @@ class DhcpcdManager $dhcp_cfg = $this->removeIface($dhcp_cfg,'uap0'); $dhcp_cfg .= $config; } else { - if (strpos($dhcp_cfg, 'interface '.$ap_iface) !== false && - strpos($dhcp_cfg, 'nogateway') !== false) { - $config[] = 'nogateway'; - } $config = join(PHP_EOL, $config); $dhcp_cfg = $this->removeIface($dhcp_cfg,'br0'); $dhcp_cfg = $this->removeIface($dhcp_cfg,'uap0'); if (!strpos($dhcp_cfg, 'metric')) { - $dhcp_cfg = preg_replace('/^#\sRaspAP\s'.$ap_iface.'\s.*?(?:\s*^\s*$|\s*nogateway)/ms', $config, $dhcp_cfg, 1); + $pattern = '/^#\sRaspAP\s' . preg_quote($ap_iface, '/') . '\sconfiguration\n' . + '(?:.*\n)*?' . + '(?:\n)*' . + '(?=#\sRaspAP\s|\z)/m'; + $dhcp_cfg = preg_replace($pattern, $config . "\n\n", $dhcp_cfg, 1); } else { $metrics = true; } @@ -199,12 +199,11 @@ class DhcpcdManager $status->addMessage('DHCP configuration for '.$iface.' added.', 'success'); } else { $cfg = join(PHP_EOL, $cfg); - $dhcp_cfg = preg_replace( - '/^#\sRaspAP\s'.$iface.'\s.*?(?=\n*(?:^#\sRaspAP|^interface\s(?!'.$iface.'$)|\z))/ms', - $cfg . PHP_EOL, - $dhcp_cfg, - 1 - ); + $pattern = '/^#\sRaspAP\s' . preg_quote($iface, '/') . '\sconfiguration\n' . + '(?:.*\n)*?' . + '(?:\n)*' . + '(?=#\sRaspAP\s|\z)/m'; + $dhcp_cfg = preg_replace($pattern, $cfg . "\n\n", $dhcp_cfg, 1); } return $dhcp_cfg; @@ -363,18 +362,31 @@ class DhcpcdManager ]; // merge existing settings with updates + $processed_keys = []; foreach ($existing_config as $line) { $matched = false; foreach ($static_settings as $key => $value) { if (strpos($line, $key) === 0) { $config[] = "$key=$value"; $matched = true; + $processed_keys[] = $key; unset($static_settings[$key]); break; } } if (!$matched && !preg_match('/^interface/', $line)) { - $config[] = $line; + // check if this line matches a key we've already processed (prevents duplicates) + $is_duplicate = false; + foreach ($processed_keys as $processed_key) { + if (strpos($line, $processed_key) === 0) { + $is_duplicate = true; + break; + } + } + // also check if the line already exists in config (for non-static settings like nogateway) + if (!$is_duplicate && !in_array($line, $config, true)) { + $config[] = $line; + } } } From dac77234413fb3e913623d8ace366de6799ce178 Mon Sep 17 00:00:00 2001 From: billz Date: Sun, 21 Dec 2025 20:25:15 +0100 Subject: [PATCH 18/22] Minor: comments --- src/RaspAP/Networking/Hotspot/DhcpcdManager.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/RaspAP/Networking/Hotspot/DhcpcdManager.php b/src/RaspAP/Networking/Hotspot/DhcpcdManager.php index 90115f69..f4c0052a 100644 --- a/src/RaspAP/Networking/Hotspot/DhcpcdManager.php +++ b/src/RaspAP/Networking/Hotspot/DhcpcdManager.php @@ -375,7 +375,6 @@ class DhcpcdManager } } if (!$matched && !preg_match('/^interface/', $line)) { - // check if this line matches a key we've already processed (prevents duplicates) $is_duplicate = false; foreach ($processed_keys as $processed_key) { if (strpos($line, $processed_key) === 0) { @@ -383,7 +382,6 @@ class DhcpcdManager break; } } - // also check if the line already exists in config (for non-static settings like nogateway) if (!$is_duplicate && !in_array($line, $config, true)) { $config[] = $line; } From e92329231ff1f1179d8838386d37e5402511ba51 Mon Sep 17 00:00:00 2001 From: billz Date: Wed, 24 Dec 2025 17:46:19 +0100 Subject: [PATCH 19/22] Standardize labels for IEEE 802.11 standards --- src/RaspAP/Networking/Hotspot/HotspotService.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RaspAP/Networking/Hotspot/HotspotService.php b/src/RaspAP/Networking/Hotspot/HotspotService.php index 69908883..69d2f624 100644 --- a/src/RaspAP/Networking/Hotspot/HotspotService.php +++ b/src/RaspAP/Networking/Hotspot/HotspotService.php @@ -33,8 +33,8 @@ class HotspotService 'g' => '802.11g - 2.4 GHz', 'n' => '802.11n - 2.4/5 GHz', 'ac' => '802.11ac - 5 GHz', - 'ax' => '802.11ax (Wi-Fi 6) - 2.4/5/6 GHz', - 'be' => '802.11be (Wi-Fi 7) - 2.4/5/6 GHz' + 'ax' => '802.11ax - 2.4/5/6 GHz', + 'be' => '802.11be - 2.4/5/6 GHz' ]; // encryption types From b4f9b3d5f4a49b10283a0a46b9a6c2100e96c147 Mon Sep 17 00:00:00 2001 From: billz Date: Wed, 24 Dec 2025 17:55:48 +0100 Subject: [PATCH 20/22] Resolve PHP warning: undefined var $wpa_numeric --- src/RaspAP/Networking/Hotspot/HostapdManager.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/RaspAP/Networking/Hotspot/HostapdManager.php b/src/RaspAP/Networking/Hotspot/HostapdManager.php index b87092e8..5ca9b474 100644 --- a/src/RaspAP/Networking/Hotspot/HostapdManager.php +++ b/src/RaspAP/Networking/Hotspot/HostapdManager.php @@ -154,6 +154,7 @@ class HostapdManager $config[] = 'auth_algs=1'; $wpa = $params['wpa']; + $wpa_numeric = $wpa; $wpa_key_mgmt = 'WPA-PSK'; if ($wpa == 4) { From 632f72a5a7df3b95fab0e55aba5c4c1489ccaf8f Mon Sep 17 00:00:00 2001 From: billz Date: Wed, 24 Dec 2025 17:56:37 +0100 Subject: [PATCH 21/22] Prune unused class method --- .../Networking/Hotspot/HotspotService.php | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/src/RaspAP/Networking/Hotspot/HotspotService.php b/src/RaspAP/Networking/Hotspot/HotspotService.php index 69d2f624..8322cd1c 100644 --- a/src/RaspAP/Networking/Hotspot/HotspotService.php +++ b/src/RaspAP/Networking/Hotspot/HotspotService.php @@ -127,27 +127,6 @@ class HotspotService ]; } - /** - * Checks if hardware mode supports advanced features - * - * @param string $mode - * @return array capabilities - */ - public static function getModeCapabilities(string $mode): array - { - $capabilities = [ - 'a' => ['wifi_generation' => 1, 'max_width' => 20, 'bands' => ['5']], - 'b' => ['wifi_generation' => 2, 'max_width' => 22, 'bands' => ['2.4']], - 'g' => ['wifi_generation' => 3, 'max_width' => 20, 'bands' => ['2.4']], - 'n' => ['wifi_generation' => 4, 'max_width' => 40, 'bands' => ['2.4', '5']], - 'ac' => ['wifi_generation' => 5, 'max_width' => 160, 'bands' => ['5']], - 'ax' => ['wifi_generation' => 6, 'max_width' => 160, 'bands' => ['2.4', '5', '6'], 'supports_he' => true], - 'be' => ['wifi_generation' => 7, 'max_width' => 320, 'bands' => ['2.4', '5', '6'], 'supports_he' => true, 'supports_eht' => true] - ]; - - return $capabilities[$mode] ?? $capabilities['g']; - } - /** * Validates user input + saves configs for hostapd, dnsmasq & dhcp * @@ -206,14 +185,6 @@ class HotspotService // add 802.11ax/be specific parameters if present if (in_array($validated['hw_mode'], ['ax', 'be'])) { - // Log advanced mode configuration - error_log(sprintf( - "Configuring advanced WiFi mode: %s with channel %d", - $validated['hw_mode'], - $validated['channel'] - )); - - // Validate WPA3 for WiFi 6/7 if ($validated['wpa'] < 4 && $validated['hw_mode'] === 'be') { $status->addMessage('Note: WiFi 7 works best with WPA3 security', 'info'); } From 01caca9fad4083bc42ada735b032385aebe9f50b Mon Sep 17 00:00:00 2001 From: billz Date: Wed, 24 Dec 2025 18:29:11 +0100 Subject: [PATCH 22/22] Prune unused class method --- .../Networking/Hotspot/HostapdManager.php | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/src/RaspAP/Networking/Hotspot/HostapdManager.php b/src/RaspAP/Networking/Hotspot/HostapdManager.php index 5ca9b474..6caf56be 100644 --- a/src/RaspAP/Networking/Hotspot/HostapdManager.php +++ b/src/RaspAP/Networking/Hotspot/HostapdManager.php @@ -465,27 +465,6 @@ class HostapdManager return is_array($configs) ? count($configs) : 0; } - /** - * Gets capabilities for a given IEEE 802.11 mode - * - * @param string $mode - * @return array - */ - public function getModeCapabilities(string $mode): array - { - $capabilities = [ - 'a' => ['bands' => ['5'], 'max_width' => 20], - 'b' => ['bands' => ['2.4'], 'max_width' => 22], - 'g' => ['bands' => ['2.4'], 'max_width' => 20], - 'n' => ['bands' => ['2.4', '5'], 'max_width' => 40], - 'ac' => ['bands' => ['5'], 'max_width' => 160], - 'ax' => ['bands' => ['2.4', '5', '6'], 'max_width' => 160], - 'be' => ['bands' => ['2.4', '5', '6'], 'max_width' => 320] - ]; - - return $capabilities[$mode] ?? $capabilities['g']; - } - /** * Extracts channel width from mode settings *