mirror of
https://github.com/billz/raspap-webgui.git
synced 2025-12-26 23:26:47 +01:00
Merge pull request #1992 from RaspAP/feat/80211ax-80211be
Feature: Add support for 802.11ax/be (Wi-Fi 6 / 7)
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
@@ -25,6 +25,148 @@
|
||||
"vht_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}"
|
||||
]
|
||||
},
|
||||
"ax": {
|
||||
"settings": [
|
||||
"# Basic settings",
|
||||
"hw_mode=a",
|
||||
"# Enable 802.11n/ac",
|
||||
"ieee80211d=1",
|
||||
"ieee80211n=1",
|
||||
"ieee80211ac=1",
|
||||
"# Enable 802.11ax",
|
||||
"ieee80211ax=1",
|
||||
"# 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",
|
||||
"# 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 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",
|
||||
"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"
|
||||
]
|
||||
},
|
||||
"be": {
|
||||
"settings": [
|
||||
"# Basic settings",
|
||||
"hw_mode=a",
|
||||
"# Enable 802.11n/ac/ax",
|
||||
"ieee80211n=1",
|
||||
"ieee80211ac=1",
|
||||
"ieee80211ax=1",
|
||||
"# 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 -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",
|
||||
"# 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_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}",
|
||||
"# IEEE 802.11be WiFi 7 configuration",
|
||||
"ieee80211be=1",
|
||||
"# EHT configuration",
|
||||
"eht_su_beamformer=1",
|
||||
"eht_su_beamformee=1",
|
||||
"eht_mu_beamformer=1",
|
||||
"# 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",
|
||||
"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",
|
||||
"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",
|
||||
|
||||
@@ -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,29 @@ 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;
|
||||
$is_duplicate = false;
|
||||
foreach ($processed_keys as $processed_key) {
|
||||
if (strpos($line, $processed_key) === 0) {
|
||||
$is_duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$is_duplicate && !in_array($line, $config, true)) {
|
||||
$config[] = $line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,8 +102,11 @@ class HostapdManager
|
||||
if (!empty($config['ieee80211ac']) && strval($config['ieee80211ac']) === '1') {
|
||||
$selected = 'ac';
|
||||
}
|
||||
if (!empty($config['ieee80211w']) && strval($config['ieee80211w']) === '2') {
|
||||
$selected = 'w';
|
||||
if (!empty($config['ieee80211ax']) && strval($config['ieee80211ax']) === '1') {
|
||||
$selected = 'ax';
|
||||
}
|
||||
if (!empty($config['ieee80211be']) && strval($config['ieee80211be']) === '1') {
|
||||
$selected = 'be';
|
||||
}
|
||||
|
||||
return $selected;
|
||||
@@ -151,6 +154,7 @@ class HostapdManager
|
||||
$config[] = 'auth_algs=1';
|
||||
|
||||
$wpa = $params['wpa'];
|
||||
$wpa_numeric = $wpa;
|
||||
$wpa_key_mgmt = 'WPA-PSK';
|
||||
|
||||
if ($wpa == 4) {
|
||||
@@ -185,20 +189,42 @@ 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
|
||||
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'] ?? [];
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -439,5 +465,241 @@ class HostapdManager
|
||||
return is_array($configs) ? count($configs) : 0;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* 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+';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 - 2.4/5/6 GHz',
|
||||
'be' => '802.11be - 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,7 +127,6 @@ class HotspotService
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates user input + saves configs for hostapd, dnsmasq & dhcp
|
||||
*
|
||||
@@ -138,7 +170,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;
|
||||
}
|
||||
|
||||
@@ -152,6 +183,13 @@ class HotspotService
|
||||
$validated['dualmode'] = !empty($states['DualAPEnable']);
|
||||
$validated['txpower'] = $post_data['txpower'];
|
||||
|
||||
// add 802.11ax/be specific parameters if present
|
||||
if (in_array($validated['hw_mode'], ['ax', 'be'])) {
|
||||
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']);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user