mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-26 02:33:02 +02:00
Merge pull request #1366 from kernelkit/misc
This commit is contained in:
@@ -286,6 +286,73 @@ def parse_interface_info(ifname):
|
||||
return result
|
||||
|
||||
|
||||
def parse_stations(ifname):
|
||||
"""
|
||||
Parse 'iw dev <name> station dump' output
|
||||
Returns: list of connected stations with stats
|
||||
"""
|
||||
output = run_iw('dev', ifname, 'station', 'dump')
|
||||
if not output:
|
||||
return []
|
||||
|
||||
stations = []
|
||||
current = None
|
||||
|
||||
for line in output.splitlines():
|
||||
stripped = line.strip()
|
||||
|
||||
# New station entry: "Station aa:bb:cc:dd:ee:ff (on wifiX)"
|
||||
if stripped.startswith('Station '):
|
||||
if current:
|
||||
stations.append(current)
|
||||
parts = stripped.split()
|
||||
if len(parts) >= 2:
|
||||
current = {'mac-address': parts[1].lower()}
|
||||
else:
|
||||
current = None
|
||||
continue
|
||||
|
||||
if not current or ':' not in stripped:
|
||||
continue
|
||||
|
||||
key, _, value = stripped.partition(':')
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
|
||||
try:
|
||||
if key == 'signal':
|
||||
# Format: "-42 dBm" or "-42 [-44, -45] dBm"
|
||||
current['rssi'] = int(value.split()[0])
|
||||
elif key == 'connected time':
|
||||
# Format: "123 seconds"
|
||||
current['connected-time'] = int(value.split()[0])
|
||||
elif key == 'rx bytes':
|
||||
current['rx-bytes'] = value # counter64: string-encoded
|
||||
elif key == 'tx bytes':
|
||||
current['tx-bytes'] = value # counter64: string-encoded
|
||||
elif key == 'rx packets':
|
||||
current['rx-packets'] = value # counter64: string-encoded
|
||||
elif key == 'tx packets':
|
||||
current['tx-packets'] = value # counter64: string-encoded
|
||||
elif key == 'tx bitrate':
|
||||
# Format: "866.7 MBit/s ..." - convert to 100kbit/s units
|
||||
speed_mbps = float(value.split()[0])
|
||||
current['tx-speed'] = int(speed_mbps * 10)
|
||||
elif key == 'rx bitrate':
|
||||
speed_mbps = float(value.split()[0])
|
||||
current['rx-speed'] = int(speed_mbps * 10)
|
||||
elif key == 'inactive time':
|
||||
# Format: "1234 ms"
|
||||
current['inactive-time'] = int(value.split()[0])
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
|
||||
if current:
|
||||
stations.append(current)
|
||||
|
||||
return stations
|
||||
|
||||
|
||||
def parse_survey(ifname):
|
||||
"""
|
||||
Parse 'iw dev <name> survey dump' output
|
||||
@@ -396,6 +463,66 @@ def parse_dev():
|
||||
return result
|
||||
|
||||
|
||||
def parse_link(ifname):
|
||||
"""
|
||||
Parse 'iw dev <name> link' output for station mode
|
||||
Returns: {connected, bssid, ssid, frequency, signal, tx_bitrate, rx_bitrate}
|
||||
"""
|
||||
output = run_iw('dev', ifname, 'link')
|
||||
if not output:
|
||||
return {'connected': False}
|
||||
|
||||
if 'Not connected' in output:
|
||||
return {'connected': False}
|
||||
|
||||
result = {'connected': True}
|
||||
|
||||
for line in output.splitlines():
|
||||
stripped = line.strip()
|
||||
|
||||
# Connected to aa:bb:cc:dd:ee:ff
|
||||
if stripped.startswith('Connected to '):
|
||||
parts = stripped.split()
|
||||
if len(parts) >= 3:
|
||||
result['bssid'] = parts[2].lower()
|
||||
|
||||
# SSID: NetworkName
|
||||
elif stripped.startswith('SSID: '):
|
||||
result['ssid'] = stripped[6:]
|
||||
|
||||
# freq: 5180
|
||||
elif stripped.startswith('freq: '):
|
||||
try:
|
||||
result['frequency'] = int(stripped[6:])
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# signal: -42 dBm
|
||||
elif stripped.startswith('signal: '):
|
||||
try:
|
||||
result['rssi'] = int(stripped.split()[1])
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
|
||||
# tx bitrate: 866.7 MBit/s ...
|
||||
elif stripped.startswith('tx bitrate: '):
|
||||
try:
|
||||
speed = float(stripped.split()[2])
|
||||
result['tx-speed'] = int(speed * 10) # 100kbit/s units
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
|
||||
# rx bitrate: 780.0 MBit/s ...
|
||||
elif stripped.startswith('rx bitrate: '):
|
||||
try:
|
||||
speed = float(stripped.split()[2])
|
||||
result['rx-speed'] = int(speed * 10)
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(json.dumps({
|
||||
@@ -404,14 +531,17 @@ def main():
|
||||
'list': 'List all PHY devices',
|
||||
'dev': 'List all interfaces grouped by PHY',
|
||||
'info': 'Get PHY or interface information (requires device)',
|
||||
'survey': 'Get channel survey data (requires interface name)'
|
||||
'survey': 'Get channel survey data (requires interface)',
|
||||
'station': 'Get connected stations in AP mode (requires interface)',
|
||||
'link': 'Get link info in station mode (requires interface)'
|
||||
},
|
||||
'examples': [
|
||||
'iw.py list',
|
||||
'iw.py dev',
|
||||
'iw.py info radio0',
|
||||
'iw.py info phy4',
|
||||
'iw.py info wlan0',
|
||||
'iw.py station wifi0',
|
||||
'iw.py link wlan0',
|
||||
'iw.py survey wlan0'
|
||||
]
|
||||
}, indent=2))
|
||||
@@ -434,12 +564,21 @@ def main():
|
||||
data = parse_phy_info(device)
|
||||
else:
|
||||
data = parse_interface_info(device)
|
||||
elif command == 'station':
|
||||
if len(sys.argv) < 3:
|
||||
data = {'error': 'station command requires interface argument'}
|
||||
else:
|
||||
data = parse_stations(sys.argv[2])
|
||||
elif command == 'link':
|
||||
if len(sys.argv) < 3:
|
||||
data = {'error': 'link command requires interface argument'}
|
||||
else:
|
||||
data = parse_link(sys.argv[2])
|
||||
elif command == 'survey':
|
||||
if len(sys.argv) < 3:
|
||||
data = {'error': 'survey command requires device argument'}
|
||||
data = {'error': 'survey command requires interface argument'}
|
||||
else:
|
||||
device = sys.argv[2]
|
||||
data = parse_survey(device)
|
||||
data = parse_survey(sys.argv[2])
|
||||
else:
|
||||
data = {'error': f'Unknown command: {command}'}
|
||||
|
||||
|
||||
+5
-24
@@ -84,46 +84,27 @@ admin@example:/config/hardware/component/radio0/wifi-radio/> leave
|
||||
|
||||
**Key radio parameters:**
|
||||
- `country-code`: Two-letter ISO 3166-1 code - determines allowed channels and maximum power. Examples: US, DE, GB, SE, FR, JP. **Must match your physical location for legal compliance.**
|
||||
- `band`: 2.4GHz, 5GHz, or 6GHz (required for AP mode). Band selection automatically enables appropriate WiFi standards (2.4GHz: 802.11n, 5GHz: 802.11n/ac, 6GHz: 802.11n/ac/ax)
|
||||
- `band`: 2.4GHz, 5GHz, or 6GHz (required for AP mode). Automatically enables appropriate WiFi standards (2.4GHz: 802.11n/ax, 5GHz: 802.11n/ac/ax, 6GHz: 802.11ax)
|
||||
- `channel`: Channel number (1-196) or "auto" (required for AP mode). When set to "auto", defaults to channel 6 for 2.4GHz, channel 36 for 5GHz, or channel 109 for 6GHz
|
||||
- `enable-80211ax`: Boolean (default: false). Opt-in to enable 802.11ax (WiFi 6) on 2.4GHz and 5GHz bands. The 6GHz band always uses 802.11ax regardless of this setting
|
||||
|
||||
> [!NOTE]
|
||||
> TX power and channel width are automatically determined by the driver based on regulatory constraints, PHY mode, and hardware capabilities.
|
||||
|
||||
### WiFi 6 (802.11ax) Support
|
||||
|
||||
WiFi 6 (802.11ax) provides improved performance in congested environments through
|
||||
features like OFDMA, Target Wake Time, and BSS Coloring. By default, WiFi 6 is
|
||||
only enabled on the 6GHz band (WiFi 6E requirement).
|
||||
WiFi 6 (802.11ax) is always enabled in AP mode on all bands, providing improved
|
||||
performance through features like OFDMA, BSS Coloring, and beamforming.
|
||||
|
||||
To enable WiFi 6 on 2.4GHz or 5GHz bands:
|
||||
|
||||
```
|
||||
admin@example:/> configure
|
||||
admin@example:/config/> edit hardware component radio0 wifi-radio
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set country-code DE
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set band 5GHz
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set channel 36
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> set enable-80211ax true
|
||||
admin@example:/config/hardware/component/radio0/wifi-radio/> leave
|
||||
```
|
||||
|
||||
**WiFi 6 Benefits:**
|
||||
**WiFi 6 Features (always enabled):**
|
||||
- **OFDMA**: Better multi-user efficiency in dense environments
|
||||
- **Target Wake Time**: Improved battery life for client devices
|
||||
- **1024-QAM**: Higher throughput with strong signal conditions
|
||||
- **BSS Coloring**: Reduced interference from neighboring networks
|
||||
- **Beamforming**: Improved signal quality and range
|
||||
|
||||
**Requirements:**
|
||||
- Hardware must support 802.11ax
|
||||
- Client devices must support WiFi 6 for full benefits
|
||||
- Older WiFi 5/4 clients can still connect but won't use WiFi 6 features
|
||||
|
||||
> [!NOTE]
|
||||
> The 6GHz band always uses WiFi 6 (802.11ax) regardless of the `enable-80211ax`
|
||||
> setting, as WiFi 6E requires 802.11ax support.
|
||||
|
||||
## Discovering Available Networks (Scanning)
|
||||
|
||||
Before connecting to a WiFi network, you need to discover which networks
|
||||
|
||||
+1
-1
@@ -580,7 +580,7 @@ static int resolve_dst(const char **dst, const struct infix_ds **ds, char **path
|
||||
static int copy(const char *src, const char *dst)
|
||||
{
|
||||
char *srcpath = NULL, *dstpath = NULL;
|
||||
const struct infix_ds *srcds, *dstds;
|
||||
const struct infix_ds *srcds = NULL, *dstds = NULL;
|
||||
bool rmsrc = false;
|
||||
mode_t oldmask;
|
||||
int err = 1;
|
||||
|
||||
+169
-166
@@ -231,30 +231,22 @@ static int wifi_find_radio_aps(struct lyd_node *cifs, const char *radio_name,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Generate BSS section for secondary AP (multi-SSID) */
|
||||
static int wifi_gen_bss_section(FILE *hostapd, struct lyd_node *cifs, const char *ifname, struct lyd_node *config)
|
||||
/* Helper: Write SSID and security configuration (shared between primary and BSS) */
|
||||
static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd_node *config, bool is_bss)
|
||||
{
|
||||
const char *ssid, *hidden, *security_mode, *secret_name, *secret;
|
||||
struct lyd_node *cif, *wifi, *ap, *security, *secret_node;
|
||||
struct lyd_node *wifi, *ap, *security, *secret_node;
|
||||
const char *ifname;
|
||||
char bssid[18];
|
||||
|
||||
/* Find the interface node for this BSS */
|
||||
LYX_LIST_FOR_EACH(cifs, cif, "interface") {
|
||||
const char *name = lydx_get_cattr(cif, "name");
|
||||
if (strcmp(name, ifname) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!cif) {
|
||||
ERROR("Failed to find interface %s for BSS section", ifname);
|
||||
return SR_ERR_INVAL_ARG;
|
||||
}
|
||||
|
||||
ifname = lydx_get_cattr(cif, "name");
|
||||
wifi = lydx_get_child(cif, "wifi");
|
||||
ap = lydx_get_child(wifi, "access-point");
|
||||
|
||||
fprintf(hostapd, "\n# BSS %s\n", ifname);
|
||||
fprintf(hostapd, "bss=%s\n", ifname);
|
||||
if (is_bss) {
|
||||
fprintf(hostapd, "\n# BSS %s\n", ifname);
|
||||
fprintf(hostapd, "bss=%s\n", ifname);
|
||||
}
|
||||
|
||||
/* Set BSSID if custom MAC is configured */
|
||||
if (!interface_get_phys_addr(cif, bssid))
|
||||
@@ -269,8 +261,31 @@ static int wifi_gen_bss_section(FILE *hostapd, struct lyd_node *cifs, const char
|
||||
if (hidden && !strcmp(hidden, "true"))
|
||||
fprintf(hostapd, "ignore_broadcast_ssid=1\n");
|
||||
|
||||
/*
|
||||
* Sane defaults for AP operation
|
||||
*/
|
||||
|
||||
/* WMM (Wi-Fi Multimedia) required for 802.11n/ac/ax and proper QoS */
|
||||
fprintf(hostapd, "wmm_enabled=1\n");
|
||||
/* Security configuration */
|
||||
|
||||
/* Allow UTF-8 characters in SSID for international network names */
|
||||
fprintf(hostapd, "utf8_ssid=1\n");
|
||||
|
||||
/* Use short preamble for better throughput on modern clients */
|
||||
fprintf(hostapd, "preamble=1\n");
|
||||
|
||||
/* Disconnect clients with poor link quality to free up airtime */
|
||||
fprintf(hostapd, "disassoc_low_ack=1\n");
|
||||
|
||||
/* Poll inactive clients before disconnecting to detect sleepy devices */
|
||||
fprintf(hostapd, "skip_inactivity_poll=0\n");
|
||||
|
||||
/* Disable opportunistic key caching, reduces roaming attack surface */
|
||||
fprintf(hostapd, "okc=0\n");
|
||||
|
||||
/* Disable PMKSA caching, forces fresh authentication for better security */
|
||||
fprintf(hostapd, "disable_pmksa_caching=1\n");
|
||||
|
||||
security = lydx_get_child(ap, "security");
|
||||
security_mode = lydx_get_cattr(security, "mode");
|
||||
|
||||
@@ -291,57 +306,160 @@ static int wifi_gen_bss_section(FILE *hostapd, struct lyd_node *cifs, const char
|
||||
}
|
||||
|
||||
if (!strcmp(security_mode, "open")) {
|
||||
fprintf(hostapd, "# Open network\n");
|
||||
/* auth_algs=1: Open System authentication (unencrypted) */
|
||||
fprintf(hostapd, "auth_algs=1\n");
|
||||
} else if (!strcmp(security_mode, "wpa2-personal")) {
|
||||
fprintf(hostapd, "# WPA2-Personal\n");
|
||||
/* wpa=2: WPA2 only (RSN), no legacy WPA1 */
|
||||
fprintf(hostapd, "wpa=2\n");
|
||||
/* WPA-PSK: Pre-shared key authentication */
|
||||
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n");
|
||||
/* CCMP: AES-based encryption, mandatory for WPA2 */
|
||||
fprintf(hostapd, "wpa_pairwise=CCMP\n");
|
||||
if (secret)
|
||||
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
|
||||
} else if (!strcmp(security_mode, "wpa3-personal")) {
|
||||
fprintf(hostapd, "# WPA3-Personal\n");
|
||||
/* wpa=2: Uses RSN (WPA2) frame format for WPA3 */
|
||||
fprintf(hostapd, "wpa=2\n");
|
||||
/* SAE: Simultaneous Authentication of Equals, resistant to offline dictionary attacks */
|
||||
fprintf(hostapd, "wpa_key_mgmt=SAE\n");
|
||||
fprintf(hostapd, "rsn_pairwise=CCMP\n");
|
||||
if (secret)
|
||||
fprintf(hostapd, "sae_password=%s\n", secret);
|
||||
/* ieee80211w=2: Management Frame Protection required for WPA3 */
|
||||
fprintf(hostapd, "ieee80211w=2\n");
|
||||
} else if (!strcmp(security_mode, "wpa2-wpa3-personal")) {
|
||||
fprintf(hostapd, "# WPA2/WPA3 Mixed\n");
|
||||
/* Transition mode: supports both WPA2 and WPA3 clients */
|
||||
fprintf(hostapd, "wpa=2\n");
|
||||
/* Allow both PSK (WPA2) and SAE (WPA3) authentication */
|
||||
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n");
|
||||
fprintf(hostapd, "rsn_pairwise=CCMP\n");
|
||||
if (secret) {
|
||||
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
|
||||
fprintf(hostapd, "sae_password=%s\n", secret);
|
||||
}
|
||||
/* ieee80211w=1: MFP capable but optional, for WPA2 client compatibility */
|
||||
fprintf(hostapd, "ieee80211w=1\n");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
/* Helper: Write radio-specific configuration */
|
||||
static void wifi_gen_radio_config(FILE *hostapd, struct lyd_node *radio_node)
|
||||
{
|
||||
const char *country, *channel, *band;
|
||||
|
||||
country = lydx_get_cattr(radio_node, "country-code");
|
||||
band = lydx_get_cattr(radio_node, "band");
|
||||
channel = lydx_get_cattr(radio_node, "channel");
|
||||
|
||||
if (country)
|
||||
fprintf(hostapd, "country_code=%s\n", country);
|
||||
|
||||
/*
|
||||
* 802.11d broadcasts country info in beacons, required for clients
|
||||
* to know which channels/power levels are legal in this region.
|
||||
*/
|
||||
fprintf(hostapd, "ieee80211d=1\n");
|
||||
|
||||
/*
|
||||
* 802.11h enables DFS (radar detection) and TPC (power control),
|
||||
* required for 5GHz operation in most regulatory domains.
|
||||
*/
|
||||
fprintf(hostapd, "ieee80211h=1\n");
|
||||
|
||||
fprintf(hostapd, "beacon_int=100\n");
|
||||
|
||||
if (band) {
|
||||
if (!strcmp(band, "2.4GHz")) {
|
||||
/* hw_mode=g: 2.4GHz with 802.11g (OFDM) as baseline */
|
||||
fprintf(hostapd, "hw_mode=g\n");
|
||||
|
||||
/*
|
||||
* Disable legacy 802.11b rates (1, 2, 5.5, 11 Mbps).
|
||||
* Slow clients using these rates consume excessive
|
||||
* airtime, degrading performance for all clients.
|
||||
* Rates in 0.5 Mbps units: 60=6M, 90=9M, etc.
|
||||
*/
|
||||
fprintf(hostapd, "supported_rates=60 90 120 180 240 360 480 540\n");
|
||||
fprintf(hostapd, "basic_rates=60 120 240\n");
|
||||
} else if (!strcmp(band, "5GHz") || !strcmp(band, "6GHz")) {
|
||||
/* hw_mode=a: 5GHz/6GHz with 802.11a (OFDM) as baseline */
|
||||
fprintf(hostapd, "hw_mode=a\n");
|
||||
}
|
||||
|
||||
if (channel) {
|
||||
if (strcmp(channel, "auto") == 0) {
|
||||
/*
|
||||
* Default channels when "auto" selected:
|
||||
* - Ch 6: Center of 2.4GHz, least overlap with 1 and 11
|
||||
* - Ch 36: First UNII-1 channel, indoor use, no DFS
|
||||
* - Ch 109: 6GHz PSC channel, preferred for discovery
|
||||
* TODO: Replace with ACS (channel=acs_survey) when driver support is verified.
|
||||
*/
|
||||
/* set to channel=acs_survey, if succeed to find a survey:
|
||||
iw dev wlan0 survey dump
|
||||
good:
|
||||
Survey data from wlan0
|
||||
frequency: 2412 MHz
|
||||
noise: -95 dBm
|
||||
channel active time: 154 ms
|
||||
channel busy time: 0 ms
|
||||
channel receive time: 0 ms
|
||||
channel transmit time: 0 ms
|
||||
bad:
|
||||
Survey data from wlan0
|
||||
frequency: 2412 MHz
|
||||
[in use]
|
||||
|
||||
*/
|
||||
if (!strcmp(band, "2.4GHz")) {
|
||||
fprintf(hostapd, "channel=6\n");
|
||||
} else if (!strcmp(band, "5GHz")) {
|
||||
fprintf(hostapd, "channel=36\n");
|
||||
} else if (!strcmp(band, "6GHz")) {
|
||||
fprintf(hostapd, "channel=109\n");
|
||||
} else {
|
||||
fprintf(hostapd, "channel=0\n");
|
||||
}
|
||||
} else {
|
||||
fprintf(hostapd, "channel=%s\n", channel);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable high-throughput modes per band:
|
||||
* - 802.11n (HT): Required for speeds >54Mbps, all bands
|
||||
* - 802.11ac (VHT): 5GHz only, enables 80/160MHz and MU-MIMO
|
||||
* - 802.11ax (HE): Always enabled, improves dense deployments with OFDMA
|
||||
*/
|
||||
if (!strcmp(band, "2.4GHz")) {
|
||||
fprintf(hostapd, "ieee80211n=1\n");
|
||||
} else if (!strcmp(band, "5GHz")) {
|
||||
fprintf(hostapd, "ieee80211n=1\n");
|
||||
fprintf(hostapd, "ieee80211ac=1\n");
|
||||
}
|
||||
/* 802.11ax (WiFi 6) always enabled for better performance */
|
||||
fprintf(hostapd, "ieee80211ax=1\n");
|
||||
/* BSS coloring reduces interference in dense deployments */
|
||||
fprintf(hostapd, "he_bss_color=1\n");
|
||||
/* Beamforming improves signal quality and range */
|
||||
fprintf(hostapd, "he_su_beamformer=1\n");
|
||||
fprintf(hostapd, "he_su_beamformee=1\n");
|
||||
}
|
||||
fprintf(hostapd, "\n");
|
||||
}
|
||||
|
||||
/* Generate hostapd config for all APs on a radio (multi-SSID support) */
|
||||
static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs,
|
||||
struct lyd_node *radio_node, struct lyd_node *config)
|
||||
{
|
||||
const char *ssid, *hidden, *security_mode, *secret_name, *secret;
|
||||
struct lyd_node *primary_cif, *cif;
|
||||
struct lyd_node *primary_wifi, *primary_ap;
|
||||
struct lyd_node *security, *secret_node;
|
||||
const char *country, *channel, *band;
|
||||
const char *primary_ifname;
|
||||
char hostapd_conf[256];
|
||||
char **ap_list = NULL;
|
||||
FILE *hostapd = NULL;
|
||||
bool ax_enabled;
|
||||
int ap_count = 0;
|
||||
char bssid[18];
|
||||
int i;
|
||||
|
||||
int rc = SR_ERR_OK;
|
||||
int i;
|
||||
|
||||
wifi_find_radio_aps(cifs, radio_name, &ap_list, &ap_count);
|
||||
|
||||
@@ -367,34 +485,6 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs,
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
primary_wifi = lydx_get_child(primary_cif, "wifi");
|
||||
primary_ap = lydx_get_child(primary_wifi, "access-point");
|
||||
|
||||
/* Get AP configuration */
|
||||
ssid = lydx_get_cattr(primary_ap, "ssid");
|
||||
hidden = lydx_get_cattr(primary_ap, "hidden");
|
||||
security = lydx_get_child(primary_ap, "security");
|
||||
security_mode = lydx_get_cattr(security, "mode");
|
||||
secret_name = lydx_get_cattr(security, "secret");
|
||||
secret = NULL;
|
||||
|
||||
/* Get radio configuration */
|
||||
country = lydx_get_cattr(radio_node, "country-code");
|
||||
band = lydx_get_cattr(radio_node, "band");
|
||||
channel = lydx_get_cattr(radio_node, "channel");
|
||||
ax_enabled = lydx_get_bool(radio_node, "enable-80211ax");
|
||||
|
||||
/* Get secret from keystore if not open network */
|
||||
if (secret_name && strcmp(security_mode, "open") != 0) {
|
||||
secret_node = lydx_get_xpathf(config,
|
||||
"/keystore/symmetric-keys/symmetric-key[name='%s']/symmetric-key",
|
||||
secret_name);
|
||||
if (secret_node) {
|
||||
secret = lyd_get_value(secret_node);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(hostapd_conf, sizeof(hostapd_conf), HOSTAPD_CONF_NEXT, radio_name);
|
||||
|
||||
hostapd = fopen(hostapd_conf, "w");
|
||||
@@ -413,122 +503,35 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs,
|
||||
|
||||
fprintf(hostapd, "interface=%s\n", primary_ifname);
|
||||
fprintf(hostapd, "driver=nl80211\n");
|
||||
fprintf(hostapd, "ctrl_interface=/run/hostapd\n");
|
||||
fprintf(hostapd, "ctrl_interface=/run/hostapd\n\n");
|
||||
|
||||
/* Set BSSID if custom MAC is configured */
|
||||
if (!interface_get_phys_addr(primary_cif, bssid))
|
||||
fprintf(hostapd, "bssid=%s\n", bssid);
|
||||
/* Primary AP SSID and security configuration */
|
||||
wifi_gen_ssid_config(hostapd, primary_cif, config, false);
|
||||
fprintf(hostapd, "\n");
|
||||
|
||||
fprintf(hostapd, "ssid=%s\n", ssid);
|
||||
if (hidden && !strcmp(hidden, "true"))
|
||||
fprintf(hostapd, "ignore_broadcast_ssid=1\n");
|
||||
fprintf(hostapd, "\n");
|
||||
|
||||
if (country)
|
||||
fprintf(hostapd, "country_code=%s\n", country);
|
||||
|
||||
/* Enable 802.11d (regulatory domain) and 802.11h (spectrum management/DFS) */
|
||||
fprintf(hostapd, "ieee80211d=1\n");
|
||||
fprintf(hostapd, "ieee80211h=1\n");
|
||||
fprintf(hostapd, "wmm_enabled=1\n");
|
||||
|
||||
/* Band and channel configuration */
|
||||
if (band) {
|
||||
/* Set hardware mode based on band */
|
||||
if (!strcmp(band, "2.4GHz")) {
|
||||
fprintf(hostapd, "hw_mode=g\n");
|
||||
|
||||
/* Disable 802.11b rates, ancient devices. This will improve range. */
|
||||
fprintf(hostapd, "supported_rates=60 90 120 180 240 360 480 540\n");
|
||||
fprintf(hostapd, "basic_rates=60 120 240\n");
|
||||
} else if (!strcmp(band, "5GHz") || !strcmp(band, "6GHz")) {
|
||||
fprintf(hostapd, "hw_mode=a\n");
|
||||
}
|
||||
|
||||
/* Set channel */
|
||||
if (channel) {
|
||||
if (strcmp(channel, "auto") == 0) {
|
||||
/*
|
||||
Use default channels: 6 for 2.4GHz, 36 for 5GHz, 109 for 6GHz, this
|
||||
is a temporary hack, replace with logic for finding best free channel.
|
||||
*/
|
||||
if (!strcmp(band, "2.4GHz")) {
|
||||
fprintf(hostapd, "channel=6\n");
|
||||
} else if (!strcmp(band, "5GHz")) {
|
||||
fprintf(hostapd, "channel=36\n");
|
||||
} else if (!strcmp(band, "6GHz")) {
|
||||
fprintf(hostapd, "channel=109\n");
|
||||
} else {
|
||||
/* Unknown band - use ACS */
|
||||
fprintf(hostapd, "channel=0\n");
|
||||
}
|
||||
} else {
|
||||
fprintf(hostapd, "channel=%s\n", channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (band) {
|
||||
if (!strcmp(band, "2.4GHz")) {
|
||||
/* 2.4GHz: Enable 802.11n (HT), optionally 802.11ax */
|
||||
fprintf(hostapd, "ieee80211n=1\n");
|
||||
if (ax_enabled) {
|
||||
fprintf(hostapd, "ieee80211ax=1\n");
|
||||
}
|
||||
} else if (!strcmp(band, "5GHz")) {
|
||||
/* 5GHz: Enable 802.11n and 802.11ac, optionally 802.11ax */
|
||||
fprintf(hostapd, "ieee80211n=1\n");
|
||||
fprintf(hostapd, "ieee80211ac=1\n");
|
||||
if (ax_enabled) {
|
||||
fprintf(hostapd, "ieee80211ax=1\n");
|
||||
}
|
||||
} else if (!strcmp(band, "6GHz")) {
|
||||
/* 6GHz: Enable 802.11ax (required for 6GHz) */
|
||||
fprintf(hostapd, "ieee80211n=1\n");
|
||||
fprintf(hostapd, "ieee80211ac=1\n");
|
||||
fprintf(hostapd, "ieee80211ax=1\n");
|
||||
}
|
||||
}
|
||||
fprintf(hostapd, "\n");
|
||||
|
||||
/* Security configuration */
|
||||
if (!strcmp(security_mode, "open")) {
|
||||
fprintf(hostapd, "# Open network (no encryption)\n");
|
||||
fprintf(hostapd, "auth_algs=1\n");
|
||||
} else if (!strcmp(security_mode, "wpa2-personal")) {
|
||||
fprintf(hostapd, "# WPA2-Personal\n");
|
||||
fprintf(hostapd, "wpa=2\n");
|
||||
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK\n");
|
||||
fprintf(hostapd, "wpa_pairwise=CCMP\n");
|
||||
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
|
||||
} else if (!strcmp(security_mode, "wpa3-personal")) {
|
||||
fprintf(hostapd, "# WPA3-Personal\n");
|
||||
fprintf(hostapd, "wpa=2\n");
|
||||
fprintf(hostapd, "wpa_key_mgmt=SAE\n");
|
||||
fprintf(hostapd, "rsn_pairwise=CCMP\n");
|
||||
fprintf(hostapd, "sae_password=%s\n", secret);
|
||||
fprintf(hostapd, "ieee80211w=2\n");
|
||||
} else if (!strcmp(security_mode, "wpa2-wpa3-personal")) {
|
||||
fprintf(hostapd, "# WPA2/WPA3 Mixed Mode\n");
|
||||
fprintf(hostapd, "wpa=2\n");
|
||||
fprintf(hostapd, "wpa_key_mgmt=WPA-PSK SAE\n");
|
||||
fprintf(hostapd, "rsn_pairwise=CCMP\n");
|
||||
fprintf(hostapd, "wpa_passphrase=%s\n", secret);
|
||||
fprintf(hostapd, "sae_password=%s\n", secret);
|
||||
fprintf(hostapd, "ieee80211w=1\n");
|
||||
}
|
||||
/* Radio-specific configuration */
|
||||
wifi_gen_radio_config(hostapd, radio_node);
|
||||
|
||||
/* Add BSS sections for secondary APs (multi-SSID) */
|
||||
for (i = 1; i < ap_count; i++) {
|
||||
DEBUG("Adding BSS section for secondary AP %s", ap_list[i]);
|
||||
rc = wifi_gen_bss_section(hostapd, cifs, ap_list[i], config);
|
||||
if (rc != SR_ERR_OK) {
|
||||
ERROR("Failed to generate BSS section for %s", ap_list[i]);
|
||||
struct lyd_node *bss_cif = NULL;
|
||||
|
||||
LYX_LIST_FOR_EACH(cifs, cif, "interface") {
|
||||
if (!strcmp(lydx_get_cattr(cif, "name"), ap_list[i])) {
|
||||
bss_cif = cif;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bss_cif) {
|
||||
ERROR("Failed to find interface %s for BSS section", ap_list[i]);
|
||||
fclose(hostapd);
|
||||
rc = SR_ERR_INVAL_ARG;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
DEBUG("Adding BSS section for secondary AP %s", ap_list[i]);
|
||||
wifi_gen_ssid_config(hostapd, bss_cif, config, true);
|
||||
}
|
||||
|
||||
fclose(hostapd);
|
||||
|
||||
@@ -262,27 +262,6 @@ module infix-hardware {
|
||||
congestion in most environments.";
|
||||
}
|
||||
|
||||
leaf enable-80211ax {
|
||||
type boolean;
|
||||
description
|
||||
"Enable 802.11ax (WiFi 6) on 2.4GHz and 5GHz bands.
|
||||
|
||||
By default, 802.11ax is enabled only on 6GHz (WiFi 6E).
|
||||
Set to 'true' to enable 802.11ax on 2.4GHz and 5GHz bands.
|
||||
|
||||
802.11ax provides:
|
||||
- OFDMA (better multi-user efficiency)
|
||||
- Target Wake Time (better battery life)
|
||||
- 1024-QAM (higher throughput)
|
||||
- BSS Coloring (reduced interference)
|
||||
|
||||
Requires:
|
||||
- Hardware support for 802.11ax
|
||||
- Compatible clients for full benefits
|
||||
|
||||
Note: 6GHz band always uses 802.11ax regardless of this setting.";
|
||||
}
|
||||
|
||||
/*
|
||||
* Operational state
|
||||
*/
|
||||
|
||||
@@ -1,242 +1,93 @@
|
||||
from ..host import HOST
|
||||
"""
|
||||
WiFi operational state provider using iw.py for interface data.
|
||||
Scanning still uses wpa_supplicant for better compatibility.
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
|
||||
from ..host import HOST
|
||||
|
||||
def detect_wifi_mode(ifname):
|
||||
"""Detect if interface is in AP or Station mode"""
|
||||
|
||||
def get_iw_info(ifname):
|
||||
"""Get interface info via iw.py on target"""
|
||||
try:
|
||||
output = HOST.run(tuple(f"iw dev {ifname} info".split()), default="")
|
||||
for line in output.splitlines():
|
||||
if 'type' in line.lower():
|
||||
if 'ap' in line.lower():
|
||||
return 'ap'
|
||||
else:
|
||||
return 'station'
|
||||
data = HOST.run(('/usr/libexec/infix/iw.py', 'info', ifname), default='{}')
|
||||
return json.loads(data)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Default to station mode
|
||||
return 'station'
|
||||
return {}
|
||||
|
||||
|
||||
def find_primary_interface_from_config(ifname):
|
||||
"""Find primary interface by reading hostapd config files"""
|
||||
def get_iw_stations(ifname):
|
||||
"""Get connected stations via iw.py (AP mode)"""
|
||||
try:
|
||||
file_list = HOST.run(("sh", "-c", "ls /etc/hostapd-*.conf"), default="")
|
||||
if not file_list:
|
||||
return None
|
||||
|
||||
for config_file in file_list.splitlines():
|
||||
config_file = config_file.strip()
|
||||
if not config_file:
|
||||
continue
|
||||
|
||||
try:
|
||||
content = HOST.run(tuple(f"cat {config_file}".split()), default="")
|
||||
if not content:
|
||||
continue
|
||||
|
||||
if f"interface={ifname}" in content or f"bss={ifname}" in content:
|
||||
for line in content.splitlines():
|
||||
if line.startswith("interface="):
|
||||
return line.split("=", 1)[1].strip()
|
||||
except Exception:
|
||||
continue
|
||||
data = HOST.run(('/usr/libexec/infix/iw.py', 'station', ifname), default='[]')
|
||||
return json.loads(data)
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
return []
|
||||
|
||||
|
||||
def get_iw_link(ifname):
|
||||
"""Get link info via iw.py (station mode)"""
|
||||
try:
|
||||
data = HOST.run(('/usr/libexec/infix/iw.py', 'link', ifname), default='{}')
|
||||
result = json.loads(data)
|
||||
if result:
|
||||
return result
|
||||
except Exception:
|
||||
pass
|
||||
return {'connected': False}
|
||||
|
||||
|
||||
def wifi_ap(ifname):
|
||||
"""Get operational data for AP mode using hostapd_cli"""
|
||||
"""Get operational data for AP mode using iw"""
|
||||
ap_data = {}
|
||||
|
||||
try:
|
||||
primary_if = find_primary_interface_from_config(ifname)
|
||||
if not primary_if:
|
||||
return {}
|
||||
# Get interface info (includes SSID for AP mode)
|
||||
info = get_iw_info(ifname)
|
||||
|
||||
data = HOST.run(tuple(f"hostapd_cli -i {primary_if} status".split()), default="")
|
||||
if not data:
|
||||
return {}
|
||||
if info.get('ssid'):
|
||||
ap_data['ssid'] = info['ssid']
|
||||
|
||||
# Find our interface's SSID, different for bss and primary, because it is
|
||||
if ifname == primary_if:
|
||||
# Primary interface - get ssid[0] or ssid
|
||||
for line in data.splitlines():
|
||||
if "=" in line:
|
||||
try:
|
||||
k, v = line.split("=", 1)
|
||||
if k in ("ssid[0]", "ssid"):
|
||||
ap_data["ssid"] = v
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
else:
|
||||
# Secondary BSS - find in BSS array
|
||||
bss_idx = None
|
||||
for line in data.splitlines():
|
||||
if "=" in line:
|
||||
try:
|
||||
k, v = line.split("=", 1)
|
||||
if v == ifname and k.startswith("bss["):
|
||||
bss_idx = k[4:-1] # Extract index from bss[N]
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
# Get connected stations
|
||||
stations = get_iw_stations(ifname)
|
||||
if stations:
|
||||
ap_data['stations'] = {'station': stations}
|
||||
|
||||
if bss_idx:
|
||||
for line in data.splitlines():
|
||||
if "=" in line:
|
||||
try:
|
||||
k, v = line.split("=", 1)
|
||||
if k == f"ssid[{bss_idx}]":
|
||||
ap_data["ssid"] = v
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
stations_data = HOST.run(tuple(f"iw dev {ifname} station dump".split()), default="")
|
||||
stations = parse_iw_stations(stations_data)
|
||||
|
||||
if stations:
|
||||
ap_data["stations"] = {
|
||||
"station": stations
|
||||
}
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Nest data inside access-point container to match YANG schema
|
||||
return {
|
||||
"access-point": ap_data
|
||||
} if ap_data else {}
|
||||
|
||||
|
||||
def parse_iw_stations(output):
|
||||
"""Parse iw station dump output to get connected stations"""
|
||||
stations = []
|
||||
current_station = None
|
||||
|
||||
for line in output.splitlines():
|
||||
line = line.strip()
|
||||
|
||||
# Station line: "Station aa:bb:cc:dd:ee:ff (on wifiX)"
|
||||
if line.startswith("Station "):
|
||||
if current_station:
|
||||
stations.append(current_station)
|
||||
# Extract MAC address
|
||||
parts = line.split()
|
||||
if len(parts) >= 2:
|
||||
current_station = {
|
||||
"mac-address": parts[1].lower()
|
||||
}
|
||||
elif current_station:
|
||||
# Parse station attributes
|
||||
try:
|
||||
# Lines are in format "key: value" with tabs
|
||||
if ":" not in line:
|
||||
continue
|
||||
|
||||
parts = line.split(":", 1)
|
||||
key = parts[0].strip()
|
||||
value = parts[1].strip()
|
||||
|
||||
if key == "signal":
|
||||
# Format: "-42 dBm" or "-42 [-44] dBm"
|
||||
rssi = int(value.split()[0])
|
||||
current_station["rssi"] = rssi
|
||||
elif key == "connected time":
|
||||
# Format: "123 seconds"
|
||||
seconds = int(value.split()[0])
|
||||
current_station["connected-time"] = seconds
|
||||
elif key == "rx packets":
|
||||
current_station["rx-packets"] = value
|
||||
elif key == "tx packets":
|
||||
current_station["tx-packets"] = value
|
||||
elif key == "rx bytes":
|
||||
current_station["rx-bytes"] = value
|
||||
elif key == "tx bytes":
|
||||
current_station["tx-bytes"] = value
|
||||
elif key == "tx bitrate":
|
||||
# Format: "866.7 MBit/s ..." - extract speed and convert to 100kbit/s units
|
||||
speed_mbps = float(value.split()[0])
|
||||
current_station["tx-speed"] = int(speed_mbps * 10)
|
||||
elif key == "rx bitrate":
|
||||
# Format: "780.0 MBit/s ..." - extract speed and convert to 100kbit/s units
|
||||
speed_mbps = float(value.split()[0])
|
||||
current_station["rx-speed"] = int(speed_mbps * 10)
|
||||
except (ValueError, KeyError, IndexError):
|
||||
# Skip invalid values
|
||||
continue
|
||||
|
||||
# Add last station
|
||||
if current_station:
|
||||
stations.append(current_station)
|
||||
|
||||
return stations
|
||||
return {'access-point': ap_data} if ap_data else {}
|
||||
|
||||
|
||||
def wifi_station(ifname):
|
||||
"""Get operational data for Station mode using wpa_cli"""
|
||||
"""Get operational data for Station mode using iw + wpa_cli for scanning"""
|
||||
station_data = {}
|
||||
|
||||
# Get link info (includes SSID and RSSI when connected)
|
||||
link = get_iw_link(ifname)
|
||||
|
||||
if link.get('connected'):
|
||||
if link.get('ssid'):
|
||||
station_data['ssid'] = link['ssid']
|
||||
if link.get('rssi') is not None:
|
||||
station_data['rssi'] = link['rssi']
|
||||
|
||||
# Get scan results from wpa_supplicant (better scan support)
|
||||
try:
|
||||
data = HOST.run(tuple(f"wpa_cli -i {ifname} status".split()), default="")
|
||||
|
||||
if data != "":
|
||||
for line in data.splitlines():
|
||||
try:
|
||||
if "=" not in line:
|
||||
continue
|
||||
k, v = line.split("=", 1)
|
||||
if k == "ssid":
|
||||
station_data["ssid"] = v
|
||||
except ValueError:
|
||||
# Skip malformed lines
|
||||
continue
|
||||
|
||||
try:
|
||||
data = HOST.run(tuple(f"wpa_cli -i {ifname} signal_poll".split()), default="FAIL")
|
||||
|
||||
# signal_poll return FAIL if not connected
|
||||
if data.strip() != "FAIL":
|
||||
for line in data.splitlines():
|
||||
try:
|
||||
if "=" not in line:
|
||||
continue
|
||||
k, v = line.strip().split("=", 1)
|
||||
if k == "RSSI":
|
||||
station_data["rssi"] = int(v)
|
||||
except (ValueError, KeyError):
|
||||
# Skip malformed lines or invalid integers
|
||||
continue
|
||||
except Exception:
|
||||
# If signal_poll fails, continue without RSSI
|
||||
pass
|
||||
except Exception:
|
||||
# If status query fails entirely, continue with scan results
|
||||
pass
|
||||
|
||||
try:
|
||||
data = HOST.run(tuple(f"wpa_cli -i {ifname} scan_result".split()), default="FAIL")
|
||||
if data != "FAIL":
|
||||
data = HOST.run(('wpa_cli', '-i', ifname, 'scan_result'), default='FAIL')
|
||||
if data and data != 'FAIL':
|
||||
scan_results = parse_wpa_scan_result(data)
|
||||
if scan_results:
|
||||
station_data["scan-results"] = scan_results
|
||||
station_data['scan-results'] = scan_results
|
||||
except Exception:
|
||||
# If scan results fail, just omit them
|
||||
pass
|
||||
|
||||
# Always nest data inside station container to match YANG schema
|
||||
# In scan-only mode, this will be just scan-results with no ssid/rssi
|
||||
return {"station": station_data} if station_data else {}
|
||||
return {'station': station_data} if station_data else {}
|
||||
|
||||
|
||||
def wifi(ifname):
|
||||
"""Main entry point - detect mode and return appropriate data"""
|
||||
mode = detect_wifi_mode(ifname)
|
||||
info = get_iw_info(ifname)
|
||||
mode = info.get('iftype', '').lower()
|
||||
|
||||
if mode == 'ap':
|
||||
return wifi_ap(ifname)
|
||||
@@ -245,11 +96,10 @@ def wifi(ifname):
|
||||
|
||||
|
||||
def parse_wpa_scan_result(scan_output):
|
||||
"""Parse wpa_cli scan_result output"""
|
||||
networks = {}
|
||||
lines = scan_output.strip().split('\n')
|
||||
|
||||
# Skip header line and any empty lines
|
||||
for line in lines:
|
||||
for line in scan_output.strip().split('\n'):
|
||||
try:
|
||||
line = line.strip()
|
||||
if not line or 'bssid / frequency' in line.lower():
|
||||
@@ -264,24 +114,20 @@ def parse_wpa_scan_result(scan_output):
|
||||
frequency = int(parts[1].strip())
|
||||
rssi = int(parts[2].strip())
|
||||
except ValueError:
|
||||
# Skip lines with invalid frequency or RSSI
|
||||
continue
|
||||
|
||||
flags = parts[3].strip()
|
||||
ssid = parts[4].strip() if len(parts) > 4 else ""
|
||||
|
||||
# Skip hidden SSIDs (empty or whitespace only)
|
||||
if not ssid or ssid.isspace() or '\\x00' in ssid:
|
||||
# Skip hidden SSIDs (empty or null-filled)
|
||||
if not ssid or ssid.isspace() or '\\x00' in ssid:
|
||||
continue
|
||||
|
||||
# Extract encryption information from flags
|
||||
encryption = extract_encryption(flags)
|
||||
|
||||
# Convert frequency to channel
|
||||
channel = frequency_to_channel(frequency)
|
||||
|
||||
# Keep only the network with best (highest) RSSI per SSID
|
||||
if ssid not in networks or rssi < networks[ssid]['rssi']:
|
||||
# Keep best RSSI per SSID
|
||||
if ssid not in networks or rssi > networks[ssid]['rssi']:
|
||||
networks[ssid] = {
|
||||
'bssid': bssid,
|
||||
'ssid': ssid,
|
||||
@@ -290,21 +136,21 @@ def parse_wpa_scan_result(scan_output):
|
||||
'channel': channel
|
||||
}
|
||||
except Exception:
|
||||
# Skip any malformed scan result lines
|
||||
continue
|
||||
|
||||
# Convert to list and sort by RSSI (best first)
|
||||
# Sort by RSSI (best first)
|
||||
result = list(networks.values())
|
||||
result.sort(key=lambda x: x['rssi'], reverse=True)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def frequency_to_channel(frequency):
|
||||
"""Convert frequency (MHz) to WiFi channel number"""
|
||||
freq = int(frequency)
|
||||
|
||||
# 2.4 GHz band (channels 1-14)
|
||||
if 2412 <= freq <= 2484: # Channel 14 is special
|
||||
if 2412 <= freq <= 2484:
|
||||
if freq == 2484:
|
||||
return 14
|
||||
return (freq - 2412) // 5 + 1
|
||||
@@ -317,16 +163,15 @@ def frequency_to_channel(frequency):
|
||||
elif 5955 <= freq <= 7115:
|
||||
return (freq - 5950) // 5
|
||||
|
||||
else:
|
||||
return f"Unknown ({freq} MHz)"
|
||||
return 0
|
||||
|
||||
|
||||
def extract_encryption(flags):
|
||||
"""Extract detailed encryption information from flags string"""
|
||||
"""Extract encryption information from flags string"""
|
||||
flags = flags.upper()
|
||||
encryption_info = {
|
||||
'protocols': [],
|
||||
'key_mgmt': [],
|
||||
'ciphers': [],
|
||||
'auth_type': 'Unknown'
|
||||
}
|
||||
|
||||
@@ -345,22 +190,12 @@ def extract_encryption(flags):
|
||||
if 'EAP' in flags:
|
||||
encryption_info['key_mgmt'].append('EAP')
|
||||
encryption_info['auth_type'] = 'Enterprise'
|
||||
if 'SAE' in flags: # WPA3 Personal
|
||||
if 'SAE' in flags:
|
||||
encryption_info['key_mgmt'].append('SAE')
|
||||
encryption_info['auth_type'] = 'Personal'
|
||||
if 'OWE' in flags: # Enhanced Open (WPA3)
|
||||
if 'OWE' in flags:
|
||||
encryption_info['key_mgmt'].append('OWE')
|
||||
encryption_info['auth_type'] = 'Enhanced Open'
|
||||
if 'FT' in flags:
|
||||
encryption_info['key_mgmt'].append('FT')
|
||||
|
||||
# Extract cipher suites
|
||||
if 'CCMP' in flags:
|
||||
encryption_info['ciphers'].append('CCMP')
|
||||
if 'TKIP' in flags:
|
||||
encryption_info['ciphers'].append('TKIP')
|
||||
if 'GCMP' in flags:
|
||||
encryption_info['ciphers'].append('GCMP')
|
||||
|
||||
# Handle special cases
|
||||
if 'WEP' in flags:
|
||||
@@ -369,7 +204,7 @@ def extract_encryption(flags):
|
||||
if not encryption_info['protocols'] and 'ESS' in flags:
|
||||
return ['Open']
|
||||
|
||||
# Return array of supported protocols with auth type
|
||||
# Return protocols with auth type
|
||||
result = []
|
||||
for protocol in encryption_info['protocols']:
|
||||
if encryption_info['auth_type'] == 'Enterprise':
|
||||
|
||||
Reference in New Issue
Block a user