mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
Wi-Fi: Add support for mesh-point (802.11s)
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
service name:wpa_supplicant :%i \
|
||||
[2345] wpa_supplicant -s -i %i -c /etc/wpa_supplicant-%i.conf -P/var/run/wpa_supplicant-%i.pid \
|
||||
-- Wi-Fi Mesh @%i
|
||||
@@ -257,15 +257,15 @@ def parse_interface_info(ifname):
|
||||
for line in output.splitlines():
|
||||
stripped = line.strip()
|
||||
|
||||
# Interface type
|
||||
# Interface type (can be multi-word, e.g. "mesh point")
|
||||
if stripped.startswith('type '):
|
||||
result['iftype'] = stripped.split()[1]
|
||||
result['iftype'] = ' '.join(stripped.split()[1:])
|
||||
|
||||
# MAC address
|
||||
elif stripped.startswith('addr '):
|
||||
result['mac'] = stripped.split()[1]
|
||||
|
||||
# SSID
|
||||
# SSID (AP mode) or mesh-id (mesh point mode) — kernel uses same attr
|
||||
elif stripped.startswith('ssid '):
|
||||
result['ssid'] = decode_iw_ssid(' '.join(stripped.split()[1:]))
|
||||
|
||||
@@ -538,6 +538,43 @@ def parse_link(ifname):
|
||||
return result
|
||||
|
||||
|
||||
def parse_phy_caps(phy_name):
|
||||
"""
|
||||
Parse 'iw phy <name> info' for HT and VHT capability bitmasks.
|
||||
Returns: {ht_cap: int, vht_cap: int}
|
||||
|
||||
iw phy info output format:
|
||||
Capabilities: 0x1ef
|
||||
...
|
||||
VHT Capabilities (0x339071b2):
|
||||
...
|
||||
"""
|
||||
actual_phy = normalize_phy_name(phy_name)
|
||||
output = run_iw('phy', actual_phy, 'info')
|
||||
if not output:
|
||||
output = run_iw(actual_phy, 'info')
|
||||
if not output:
|
||||
return {'ht_cap': 0, 'vht_cap': 0}
|
||||
|
||||
ht_cap = 0
|
||||
vht_cap = 0
|
||||
|
||||
for line in output.splitlines():
|
||||
stripped = line.strip()
|
||||
|
||||
# HT Capabilities: "Capabilities: 0x1ef"
|
||||
ht_match = re.match(r'Capabilities:\s+(0x[0-9a-fA-F]+)', stripped)
|
||||
if ht_match:
|
||||
ht_cap = int(ht_match.group(1), 16)
|
||||
|
||||
# VHT Capabilities: "VHT Capabilities (0x339071b2):"
|
||||
vht_match = re.match(r'VHT Capabilities\s+\((0x[0-9a-fA-F]+)\)', stripped)
|
||||
if vht_match:
|
||||
vht_cap = int(vht_match.group(1), 16)
|
||||
|
||||
return {'ht_cap': ht_cap, 'vht_cap': vht_cap}
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(json.dumps({
|
||||
@@ -548,7 +585,8 @@ def main():
|
||||
'info': 'Get PHY or interface information (requires device)',
|
||||
'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)'
|
||||
'link': 'Get link info in station mode (requires interface)',
|
||||
'caps': 'Get HT/VHT capability bitmasks (requires PHY/radio)'
|
||||
},
|
||||
'examples': [
|
||||
'iw.py list',
|
||||
@@ -557,7 +595,8 @@ def main():
|
||||
'iw.py info wlan0',
|
||||
'iw.py station wifi0',
|
||||
'iw.py link wlan0',
|
||||
'iw.py survey wlan0'
|
||||
'iw.py survey wlan0',
|
||||
'iw.py caps radio0'
|
||||
]
|
||||
}, indent=2))
|
||||
sys.exit(1)
|
||||
@@ -594,6 +633,11 @@ def main():
|
||||
data = {'error': 'survey command requires interface argument'}
|
||||
else:
|
||||
data = parse_survey(sys.argv[2])
|
||||
elif command == 'caps':
|
||||
if len(sys.argv) < 3:
|
||||
data = {'error': 'caps command requires PHY/radio argument'}
|
||||
else:
|
||||
data = parse_phy_caps(sys.argv[2])
|
||||
else:
|
||||
data = {'error': f'Unknown command: {command}'}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ config BR2_PACKAGE_FEATURE_WIFI
|
||||
select BR2_PACKAGE_WPA_SUPPLICANT_DEBUG_SYSLOG
|
||||
select BR2_PACKAGE_WPA_SUPPLICANT_AUTOSCAN
|
||||
select BR2_PACKAGE_WPA_SUPPLICANT_CLI
|
||||
select BR2_PACKAGE_WPA_SUPPLICANT_AP_SUPPORT
|
||||
select BR2_PACKAGE_WPA_SUPPLICANT_MESH_NETWORKING
|
||||
select BR2_PACKAGE_WIRELESS_REGDB
|
||||
select BR2_PACKAGE_HOSTAPD
|
||||
select BR2_PACKAGE_HOSTAPD_DRIVER_NL80211
|
||||
|
||||
@@ -69,7 +69,7 @@ define FEATURE_WIFI_LINUX_CONFIG_FIXUPS
|
||||
endef
|
||||
|
||||
define FEATURE_WIFI_INSTALL_IN_ROMFS
|
||||
mkdir -p $(TARGET_DIR)/etc/modprobe.d $(TARGET_DIR)/etc/udev/rules.d
|
||||
mkdir -p $(TARGET_DIR)/etc/modprobe.d $(TARGET_DIR)/etc/udev/rules.d $(TARGET_DIR)/usr/libexec
|
||||
cp $(FEATURE_WIFI_PKGDIR)/mt7915e.conf $(TARGET_DIR)/etc/modprobe.d/mt7915e.conf
|
||||
cp $(FEATURE_WIFI_PKGDIR)/60-rename-wifi-phy.rules $(TARGET_DIR)/etc/udev/rules.d/60-rename-wifi-phy.rules
|
||||
cp $(FEATURE_WIFI_PKGDIR)/70-remove-virtual-wifi-interfaces.rules $(TARGET_DIR)/etc/udev/rules.d/70-remove-virtual-wifi-interfaces.rules
|
||||
|
||||
+308
-24
@@ -221,12 +221,11 @@ static const char *resolve_mobility_domain(const char *mobility_domain, const ch
|
||||
|
||||
/*
|
||||
* Find an AP interface on a higher-band radio (5/6 GHz) advertising the
|
||||
* same SSID as the caller's 2.4 GHz BSS. Used to emit
|
||||
* no_probe_resp_if_seen_on=, which suppresses 2.4 GHz probe responses to
|
||||
* clients that have recently probed the 5/6 GHz twin, forcing dual-band
|
||||
* clients to discover the higher band. 2.4-only clients are unaffected:
|
||||
* hostapd only suppresses if it has actually seen the MAC on the listed
|
||||
* interface, so a 2.4-only client always gets a probe response.
|
||||
* same SSID as the caller's 2.4 GHz BSS, for no_probe_resp_if_seen_on=.
|
||||
* hostapd suppresses a 2.4 GHz probe response only once it has actually
|
||||
* seen that MAC on the listed interface, so 2.4-only clients always get a
|
||||
* response while dual-band clients are nudged to the higher band. Note:
|
||||
* sta_track spans bands only when both radios run in one hostapd process.
|
||||
*
|
||||
* Returns a pointer into the config tree (do not free) or NULL.
|
||||
*/
|
||||
@@ -286,7 +285,7 @@ static int wifi_find_interfaces_on_radio(struct lyd_node *ifs, const char *radio
|
||||
|
||||
if (lydx_get_op(iface) == LYDX_OP_DELETE)
|
||||
continue;
|
||||
list = realloc(list, sizeof(struct lyd_node *) * n + 1);
|
||||
list = realloc(list, sizeof(struct lyd_node *) * (n + 1));
|
||||
list[n++] = iface;
|
||||
}
|
||||
|
||||
@@ -316,8 +315,7 @@ static int wifi_find_radio_aps(struct lyd_node *cifs, const char *radio_name,
|
||||
ap = lydx_get_child(wifi, "access-point");
|
||||
if (!ap)
|
||||
continue;
|
||||
list = realloc(list, sizeof(char *) *n+1);
|
||||
|
||||
list = realloc(list, sizeof(char *) * (n + 1));
|
||||
|
||||
ifname = lydx_get_cattr(cif, "name");
|
||||
list[n++] = strdup(ifname);
|
||||
@@ -484,7 +482,10 @@ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd
|
||||
if (enable_80211r) {
|
||||
fprintf(hostapd, "# Fast BSS Transition (802.11r)\n");
|
||||
fprintf(hostapd, "mobility_domain=%s\n", mobility_domain);
|
||||
fprintf(hostapd, "ft_over_ds=1\n");
|
||||
/* Over-the-air FT: the client authenticates directly with the
|
||||
* target AP. Over-DS would relay the exchange through the wireless
|
||||
* mesh backhaul, adding latency and packet loss, so it is not used. */
|
||||
fprintf(hostapd, "ft_over_ds=0\n");
|
||||
fprintf(hostapd, "ft_psk_generate_local=1\n");
|
||||
fprintf(hostapd, "nas_identifier=%s\n", nas_identifier_cfg);
|
||||
}
|
||||
@@ -510,7 +511,9 @@ static void wifi_gen_ssid_config(FILE *hostapd, struct lyd_node *cif, struct lyd
|
||||
fprintf(hostapd, "okc=1\n");
|
||||
}
|
||||
|
||||
/* MBO: Multi-Band Operation (band steering, needs 802.11v above) */
|
||||
/* MBO: Multi-Band Operation. Advertises multi-band capability so
|
||||
* MBO-aware clients make their own band-steering decisions; pairs
|
||||
* with 802.11v BSS Transition Management above. */
|
||||
if (enable_mbo) {
|
||||
const char *twin;
|
||||
|
||||
@@ -595,10 +598,276 @@ static const char *wifi_ht40_dir(int ch)
|
||||
return ((ch / 4) % 2) ? "[HT40+]" : "[HT40-]";
|
||||
}
|
||||
|
||||
/*
|
||||
* Read HT/VHT capability bitmasks from hardware via iw.py.
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
static int wifi_read_phy_caps(const char *radio_name, unsigned int *ht_cap, unsigned int *vht_cap)
|
||||
{
|
||||
json_error_t jerr;
|
||||
json_t *root, *jht, *jvht;
|
||||
char buf[256];
|
||||
size_t len;
|
||||
FILE *pp;
|
||||
|
||||
*ht_cap = 0;
|
||||
*vht_cap = 0;
|
||||
|
||||
pp = popenf("r", "/usr/libexec/infix/iw.py caps %s", radio_name);
|
||||
if (!pp)
|
||||
return -1;
|
||||
|
||||
len = fread(buf, 1, sizeof(buf) - 1, pp);
|
||||
pclose(pp);
|
||||
buf[len] = '\0';
|
||||
|
||||
/*
|
||||
* Parse JSON output: {"ht_cap": NNN, "vht_cap": NNN}
|
||||
* Use jansson since hardware.c already includes it.
|
||||
*/
|
||||
root = json_loads(buf, 0, &jerr);
|
||||
if (!root)
|
||||
return -1;
|
||||
|
||||
jht = json_object_get(root, "ht_cap");
|
||||
jvht = json_object_get(root, "vht_cap");
|
||||
|
||||
if (json_is_integer(jht))
|
||||
*ht_cap = (unsigned int)json_integer_value(jht);
|
||||
if (json_is_integer(jvht))
|
||||
*vht_cap = (unsigned int)json_integer_value(jvht);
|
||||
|
||||
json_decref(root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build hostapd ht_capab string from HT capability bitmask.
|
||||
* IEEE 802.11-2016 Table 9-153 — HT Capabilities Info field
|
||||
*
|
||||
* The ht40_dir string ("[HT40+]" or "[HT40-]") is prepended when
|
||||
* the configured channel width is 40MHz or wider.
|
||||
*/
|
||||
static void wifi_build_ht_capab(char *out, size_t sz, unsigned int ht_cap,
|
||||
const char *ht40_dir, int want_ht40)
|
||||
{
|
||||
char *p = out;
|
||||
size_t rem = sz;
|
||||
int n;
|
||||
|
||||
*p = '\0';
|
||||
|
||||
if (want_ht40 && ht40_dir) {
|
||||
n = snprintf(p, rem, "%s", ht40_dir);
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
|
||||
if (ht_cap & 0x0001) {
|
||||
n = snprintf(p, rem, "[LDPC]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
if (ht_cap & 0x0020) {
|
||||
n = snprintf(p, rem, "[SHORT-GI-20]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
if (ht_cap & 0x0040) {
|
||||
n = snprintf(p, rem, "[SHORT-GI-40]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
if (ht_cap & 0x0080) {
|
||||
n = snprintf(p, rem, "[TX-STBC]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
|
||||
/* RX-STBC: bits 8-9 */
|
||||
switch ((ht_cap >> 8) & 0x3) {
|
||||
case 1:
|
||||
n = snprintf(p, rem, "[RX-STBC1]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
break;
|
||||
case 2:
|
||||
n = snprintf(p, rem, "[RX-STBC12]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
break;
|
||||
case 3:
|
||||
n = snprintf(p, rem, "[RX-STBC123]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Max A-MSDU Length: bit 11 */
|
||||
if (ht_cap & 0x0800) {
|
||||
n = snprintf(p, rem, "[MAX-AMSDU-7935]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Build hostapd vht_capab string from VHT capability bitmask.
|
||||
* IEEE 802.11-2016 Table 9-250 — VHT Capabilities Info field
|
||||
*
|
||||
* When the configured width is less than 160MHz, the VHT160 and
|
||||
* SHORT-GI-160 flags are masked out to prevent hostapd from
|
||||
* advertising capabilities the configuration does not use.
|
||||
*/
|
||||
static void wifi_build_vht_capab(char *out, size_t sz, unsigned int vht_cap, int chwidth)
|
||||
{
|
||||
char *p = out;
|
||||
size_t rem = sz;
|
||||
int n;
|
||||
|
||||
*p = '\0';
|
||||
|
||||
/* Max MPDU Length: bits 0-1 */
|
||||
switch (vht_cap & 0x3) {
|
||||
case 1:
|
||||
n = snprintf(p, rem, "[MAX-MPDU-7991]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
break;
|
||||
case 2:
|
||||
n = snprintf(p, rem, "[MAX-MPDU-11454]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Supported Channel Width: bits 2-3 */
|
||||
if (chwidth >= 2) {
|
||||
switch ((vht_cap >> 2) & 0x3) {
|
||||
case 1:
|
||||
n = snprintf(p, rem, "[VHT160]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
break;
|
||||
case 2:
|
||||
n = snprintf(p, rem, "[VHT160-80PLUS80]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* RXLDPC: bit 4 */
|
||||
if (vht_cap & 0x10) {
|
||||
n = snprintf(p, rem, "[RXLDPC]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
|
||||
/* Short GI for 80MHz: bit 5 */
|
||||
if (vht_cap & 0x20) {
|
||||
n = snprintf(p, rem, "[SHORT-GI-80]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
|
||||
/* Short GI for 160MHz: bit 6 — only when configured for 160MHz */
|
||||
if (chwidth >= 2 && (vht_cap & 0x40)) {
|
||||
n = snprintf(p, rem, "[SHORT-GI-160]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
|
||||
/* TX STBC: bit 7 */
|
||||
if (vht_cap & 0x80) {
|
||||
n = snprintf(p, rem, "[TX-STBC-2BY1]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
|
||||
/* RX STBC: bits 8-10 */
|
||||
switch ((vht_cap >> 8) & 0x7) {
|
||||
case 1:
|
||||
n = snprintf(p, rem, "[RX-STBC-1]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
break;
|
||||
case 2:
|
||||
n = snprintf(p, rem, "[RX-STBC-12]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
break;
|
||||
case 3:
|
||||
n = snprintf(p, rem, "[RX-STBC-123]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
break;
|
||||
case 4:
|
||||
n = snprintf(p, rem, "[RX-STBC-1234]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
break;
|
||||
}
|
||||
|
||||
/* SU Beamformer: bit 11 */
|
||||
if (vht_cap & 0x800) {
|
||||
n = snprintf(p, rem, "[SU-BEAMFORMER]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
|
||||
/* SU Beamformee: bit 12 */
|
||||
if (vht_cap & 0x1000) {
|
||||
n = snprintf(p, rem, "[SU-BEAMFORMEE]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
|
||||
/* MU Beamformer: bit 19 */
|
||||
if (vht_cap & 0x80000) {
|
||||
n = snprintf(p, rem, "[MU-BEAMFORMER]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
|
||||
/* MU Beamformee: bit 20 */
|
||||
if (vht_cap & 0x100000) {
|
||||
n = snprintf(p, rem, "[MU-BEAMFORMEE]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
|
||||
/* VHT TXOP PS: bit 21 */
|
||||
if (vht_cap & 0x200000) {
|
||||
n = snprintf(p, rem, "[VHT-TXOP-PS]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
|
||||
/* HTC-VHT: bit 22 */
|
||||
if (vht_cap & 0x400000) {
|
||||
n = snprintf(p, rem, "[HTC-VHT]");
|
||||
p += n;
|
||||
rem -= n;
|
||||
}
|
||||
|
||||
/* Max A-MPDU Length Exponent: bits 23-25 */
|
||||
n = (vht_cap >> 23) & 0x7;
|
||||
if (n) {
|
||||
int wrote = snprintf(p, rem, "[MAX-A-MPDU-LEN-EXP%d]", n);
|
||||
p += wrote;
|
||||
rem -= wrote;
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper: Write radio-specific configuration */
|
||||
static void wifi_gen_radio_config(FILE *hostapd, struct lyd_node *radio_node)
|
||||
static void wifi_gen_radio_config(FILE *hostapd, const char *radio_name,
|
||||
struct lyd_node *radio_node)
|
||||
{
|
||||
const char *country, *channel, *band, *width;
|
||||
unsigned int ht_cap = 0, vht_cap = 0;
|
||||
char ht_capab[512], vht_capab[512];
|
||||
int chwidth = 0; /* 0=20/40, 1=80, 2=160 */
|
||||
int ch = 0;
|
||||
|
||||
country = lydx_get_cattr(radio_node, "country-code");
|
||||
@@ -608,6 +877,9 @@ static void wifi_gen_radio_config(FILE *hostapd, struct lyd_node *radio_node)
|
||||
if (channel && strcmp(channel, "auto"))
|
||||
ch = atoi(channel);
|
||||
|
||||
/* Read HT/VHT hardware capabilities from PHY */
|
||||
wifi_read_phy_caps(radio_name, &ht_cap, &vht_cap);
|
||||
|
||||
if (country)
|
||||
fprintf(hostapd, "country_code=%s\n", country);
|
||||
|
||||
@@ -686,14 +958,13 @@ static void wifi_gen_radio_config(FILE *hostapd, struct lyd_node *radio_node)
|
||||
}
|
||||
|
||||
/*
|
||||
* Channel width configuration.
|
||||
* Channel width + HT/VHT capability configuration.
|
||||
*
|
||||
* 6GHz: bandwidth is determined by op_class (131-134),
|
||||
* hostapd ignores he_oper_chwidth on 6GHz. No VHT/HT.
|
||||
*
|
||||
* 5GHz: requires explicit ht_capab/vht_capab strings
|
||||
* matching hardware. Without vht_capab, 160MHz is not
|
||||
* advertised in beacons.
|
||||
* hostapd requires explicit ht_capab and vht_capab strings
|
||||
* that match hardware capabilities. Without vht_capab, 160MHz
|
||||
* support is not advertised in beacons, causing clients to
|
||||
* connect at 80MHz. Read capabilities from hardware via iw.py
|
||||
* and build the capability strings from the bitmasks.
|
||||
*/
|
||||
if (!strcmp(band, "6GHz")) {
|
||||
int op_class = 133; /* default 80MHz for 6GHz */
|
||||
@@ -719,13 +990,18 @@ static void wifi_gen_radio_config(FILE *hostapd, struct lyd_node *radio_node)
|
||||
}
|
||||
} else if (width && strcmp(width, "auto")) {
|
||||
if (!strcmp(width, "20MHz")) {
|
||||
fprintf(hostapd, "ht_capab=\n");
|
||||
chwidth = 0;
|
||||
wifi_build_ht_capab(ht_capab, sizeof(ht_capab), ht_cap, NULL, 0);
|
||||
fprintf(hostapd, "ht_capab=%s\n", ht_capab);
|
||||
if (strcmp(band, "2.4GHz")) {
|
||||
fprintf(hostapd, "vht_oper_chwidth=0\n");
|
||||
fprintf(hostapd, "he_oper_chwidth=0\n");
|
||||
}
|
||||
} else if (!strcmp(width, "40MHz")) {
|
||||
fprintf(hostapd, "ht_capab=%s\n", ch ? wifi_ht40_dir(ch) : "[HT40+]");
|
||||
chwidth = 0;
|
||||
wifi_build_ht_capab(ht_capab, sizeof(ht_capab), ht_cap,
|
||||
ch ? wifi_ht40_dir(ch) : "[HT40+]", 1);
|
||||
fprintf(hostapd, "ht_capab=%s\n", ht_capab);
|
||||
if (strcmp(band, "2.4GHz")) {
|
||||
fprintf(hostapd, "vht_oper_chwidth=0\n");
|
||||
fprintf(hostapd, "he_oper_chwidth=0\n");
|
||||
@@ -733,7 +1009,11 @@ static void wifi_gen_radio_config(FILE *hostapd, struct lyd_node *radio_node)
|
||||
} else if (!strcmp(width, "80MHz") && ch) {
|
||||
int center = wifi_center_chan_80(ch);
|
||||
|
||||
fprintf(hostapd, "ht_capab=%s\n", wifi_ht40_dir(ch));
|
||||
chwidth = 1;
|
||||
wifi_build_ht_capab(ht_capab, sizeof(ht_capab), ht_cap, wifi_ht40_dir(ch), 1);
|
||||
wifi_build_vht_capab(vht_capab, sizeof(vht_capab), vht_cap, chwidth);
|
||||
fprintf(hostapd, "ht_capab=%s\n", ht_capab);
|
||||
fprintf(hostapd, "vht_capab=%s\n", vht_capab);
|
||||
fprintf(hostapd, "vht_oper_chwidth=1\n");
|
||||
fprintf(hostapd, "he_oper_chwidth=1\n");
|
||||
if (center) {
|
||||
@@ -743,7 +1023,11 @@ static void wifi_gen_radio_config(FILE *hostapd, struct lyd_node *radio_node)
|
||||
} else if (!strcmp(width, "160MHz") && ch) {
|
||||
int center = wifi_center_chan_160(ch);
|
||||
|
||||
fprintf(hostapd, "ht_capab=%s\n", wifi_ht40_dir(ch));
|
||||
chwidth = 2;
|
||||
wifi_build_ht_capab(ht_capab, sizeof(ht_capab), ht_cap, wifi_ht40_dir(ch), 1);
|
||||
wifi_build_vht_capab(vht_capab, sizeof(vht_capab), vht_cap, chwidth);
|
||||
fprintf(hostapd, "ht_capab=%s\n", ht_capab);
|
||||
fprintf(hostapd, "vht_capab=%s\n", vht_capab);
|
||||
fprintf(hostapd, "vht_oper_chwidth=2\n");
|
||||
fprintf(hostapd, "he_oper_chwidth=2\n");
|
||||
if (center) {
|
||||
@@ -823,7 +1107,7 @@ static int wifi_gen_aps_on_radio(const char *radio_name, struct lyd_node *cifs,
|
||||
fprintf(hostapd, "\n");
|
||||
|
||||
/* Radio-specific configuration */
|
||||
wifi_gen_radio_config(hostapd, radio_node);
|
||||
wifi_gen_radio_config(hostapd, radio_name, radio_node);
|
||||
|
||||
/* Add BSS sections for secondary APs (multi-SSID) */
|
||||
for (i = 1; i < ap_count; i++) {
|
||||
|
||||
+234
-19
@@ -79,19 +79,24 @@ int wifi_validate_secret(sr_session_ctx_t *session, struct lyd_node *cif)
|
||||
|
||||
wifi_mode_t wifi_get_mode(struct lyd_node *iface)
|
||||
{
|
||||
struct lyd_node *ap, *wifi;
|
||||
struct lyd_node *ap, *mesh, *wifi;
|
||||
|
||||
wifi = lydx_get_child(iface, "wifi");
|
||||
if (!wifi)
|
||||
return wifi_unknown;
|
||||
|
||||
|
||||
ap = lydx_get_child(wifi, "access-point");
|
||||
if (ap) {
|
||||
if (lydx_get_op(ap) != LYDX_OP_DELETE)
|
||||
return wifi_ap;
|
||||
}
|
||||
|
||||
mesh = lydx_get_child(wifi, "mesh-point");
|
||||
if (mesh) {
|
||||
if (lydx_get_op(mesh) != LYDX_OP_DELETE)
|
||||
return wifi_mesh;
|
||||
}
|
||||
|
||||
/*
|
||||
* Need to return station even if "station" also is false,
|
||||
* because station is the default scanning mode.
|
||||
@@ -101,19 +106,25 @@ wifi_mode_t wifi_get_mode(struct lyd_node *iface)
|
||||
|
||||
int wifi_mode_changed(struct lyd_node *wifi)
|
||||
{
|
||||
enum lydx_op ap_op = LYDX_OP_DELETE;
|
||||
struct lyd_node *ap;
|
||||
enum lydx_op op = LYDX_OP_DELETE;
|
||||
struct lyd_node *node;
|
||||
|
||||
if (!wifi)
|
||||
return 0;
|
||||
|
||||
ap = lydx_get_child(wifi, "access-point");
|
||||
if (ap)
|
||||
ap_op = lydx_get_op(ap);
|
||||
node = lydx_get_child(wifi, "access-point");
|
||||
if (node)
|
||||
op = lydx_get_op(node);
|
||||
if (node && (op == LYDX_OP_CREATE || op == LYDX_OP_DELETE))
|
||||
return 1;
|
||||
|
||||
DEBUG("MODE CHANGED: %d", ap && (ap_op == LYDX_OP_CREATE || ap_op == LYDX_OP_DELETE));
|
||||
node = lydx_get_child(wifi, "mesh-point");
|
||||
if (node)
|
||||
op = lydx_get_op(node);
|
||||
if (node && (op == LYDX_OP_CREATE || op == LYDX_OP_DELETE))
|
||||
return 1;
|
||||
|
||||
return (ap && (ap_op == LYDX_OP_CREATE || ap_op == LYDX_OP_DELETE));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -150,7 +161,8 @@ int wifi_gen_station(struct lyd_node *cif)
|
||||
secret_name = NULL;
|
||||
}
|
||||
|
||||
radio_node = lydx_get_xpathf(cif, "../../hardware/component[name='%s']/wifi-radio", radio);
|
||||
radio_node = lydx_get_xpathf(cif,
|
||||
"/ietf-hardware:hardware/component[name='%s']/infix-hardware:wifi-radio", radio);
|
||||
country = lydx_get_cattr(radio_node, "country-code");
|
||||
|
||||
if (secret_name && strcmp(security_mode, "disabled") != 0) {
|
||||
@@ -171,10 +183,8 @@ int wifi_gen_station(struct lyd_node *cif)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Background scanning every 10 seconds while not associated, when we
|
||||
* have an SSID (below), bgscan assumes this task.
|
||||
*/
|
||||
/* autoscan drives scanning every 10s while unassociated; once
|
||||
* associated no background scanning runs (bgscan disabled below). */
|
||||
fprintf(wpa_supplicant,
|
||||
"ctrl_interface=/run/wpa_supplicant\n"
|
||||
"autoscan=periodic:10\n"
|
||||
@@ -193,9 +203,12 @@ int wifi_gen_station(struct lyd_node *cif)
|
||||
"key_mgmt=FT-SAE FT-PSK SAE WPA-PSK\n"
|
||||
" psk=\"%s\"", secret);
|
||||
|
||||
/* bgscan="" disables background scanning once associated: on a
|
||||
* fixed backhaul link the periodic off-channel sweep stalls
|
||||
* traffic and buys no roaming benefit. */
|
||||
fprintf(wpa_supplicant,
|
||||
"network={\n"
|
||||
" bgscan=\"simple: 30:-45:300\"\n"
|
||||
" bgscan=\"\"\n"
|
||||
" ssid=\"%s\"\n"
|
||||
" %s\n"
|
||||
"}\n", ssid, security_str);
|
||||
@@ -227,6 +240,195 @@ out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Center channel for 80MHz VHT/HE operation.
|
||||
* 5GHz 80MHz channel groups and their center channels:
|
||||
* 36-48(42), 52-64(58), 100-112(106),
|
||||
* 116-128(122), 132-144(138), 149-161(155)
|
||||
*/
|
||||
static int wifi_center_chan_80(int ch)
|
||||
{
|
||||
static const int grp[][2] = {
|
||||
{36, 42}, {52, 58}, {100, 106}, {116, 122}, {132, 138}, {149, 155}
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 6; i++)
|
||||
if (ch >= grp[i][0] && ch < grp[i][0] + 16)
|
||||
return grp[i][1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Center channel for 160MHz VHT/HE operation.
|
||||
* 5GHz 160MHz groups: 36-64(50), 100-128(114)
|
||||
*/
|
||||
static int wifi_center_chan_160(int ch)
|
||||
{
|
||||
if (ch >= 36 && ch <= 64)
|
||||
return 50;
|
||||
if (ch >= 100 && ch <= 128)
|
||||
return 114;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert WiFi channel number to frequency in MHz.
|
||||
* Band is determined from channel range:
|
||||
* 2.4GHz: channels 1-14
|
||||
* 5GHz: channels 32-177
|
||||
* 6GHz: channels 1-233 (identified by band string)
|
||||
*/
|
||||
static int wifi_chan_to_freq(int channel, const char *band)
|
||||
{
|
||||
if (!strcmp(band, "6GHz"))
|
||||
return 5950 + channel * 5;
|
||||
|
||||
if (channel >= 1 && channel <= 13)
|
||||
return 2407 + channel * 5;
|
||||
if (channel == 14)
|
||||
return 2484;
|
||||
|
||||
/* 5GHz */
|
||||
return 5000 + channel * 5;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate wpa_supplicant config for 802.11s mesh mode
|
||||
*/
|
||||
int wifi_gen_mesh(struct lyd_node *cif)
|
||||
{
|
||||
const char *ifname, *mesh_id, *secret_name, *radio;
|
||||
struct lyd_node *mesh, *security, *secret_node, *radio_node, *wifi;
|
||||
const char *country, *band, *width;
|
||||
FILE *wpa_supplicant = NULL;
|
||||
unsigned char *secret = NULL;
|
||||
int rc = SR_ERR_OK;
|
||||
int channel, freq;
|
||||
mode_t oldmask;
|
||||
bool forwarding;
|
||||
|
||||
ifname = lydx_get_cattr(cif, "name");
|
||||
wifi = lydx_get_child(cif, "wifi");
|
||||
if (!wifi)
|
||||
return SR_ERR_OK;
|
||||
|
||||
radio = lydx_get_cattr(wifi, "radio");
|
||||
mesh = lydx_get_child(wifi, "mesh-point");
|
||||
if (!mesh)
|
||||
return SR_ERR_OK;
|
||||
|
||||
mesh_id = lydx_get_cattr(mesh, "mesh-id");
|
||||
forwarding = lydx_is_enabled(mesh, "forwarding");
|
||||
|
||||
security = lydx_get_child(mesh, "security");
|
||||
secret_name = lydx_get_cattr(security, "secret");
|
||||
|
||||
radio_node = lydx_get_xpathf(cif,
|
||||
"/ietf-hardware:hardware/component[name='%s']/infix-hardware:wifi-radio", radio);
|
||||
country = lydx_get_cattr(radio_node, "country-code");
|
||||
band = lydx_get_cattr(radio_node, "band");
|
||||
width = lydx_get_cattr(radio_node, "channel-width");
|
||||
channel = atoi(lydx_get_cattr(radio_node, "channel") ? : "0");
|
||||
if (!channel && band && width && !strcmp(width, "auto")) {
|
||||
if (!strcmp(band, "2.4GHz"))
|
||||
channel = 6;
|
||||
else if (!strcmp(band, "5GHz"))
|
||||
channel = 36;
|
||||
else if (!strcmp(band, "6GHz"))
|
||||
channel = 1;
|
||||
}
|
||||
|
||||
freq = wifi_chan_to_freq(channel, band);
|
||||
|
||||
if (secret_name) {
|
||||
const char *b64;
|
||||
|
||||
secret_node = lydx_get_xpathf(cif,
|
||||
"../../keystore/symmetric-keys/symmetric-key[name='%s']",
|
||||
secret_name);
|
||||
b64 = lydx_get_cattr(secret_node, "cleartext-symmetric-key");
|
||||
if (b64)
|
||||
secret = base64_decode((const unsigned char *)b64, strlen(b64), NULL);
|
||||
}
|
||||
|
||||
oldmask = umask(0077);
|
||||
wpa_supplicant = fopenf("w", WPA_SUPPLICANT_CONF, ifname);
|
||||
if (!wpa_supplicant) {
|
||||
rc = SR_ERR_INTERNAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
fprintf(wpa_supplicant, "ctrl_interface=/run/wpa_supplicant\n");
|
||||
if (country)
|
||||
fprintf(wpa_supplicant, "country=%s\n", country);
|
||||
|
||||
/* Global scope only (maps to peer_link_timeout): raise the 300s
|
||||
* default so a fixed backhaul peer is not idled out and re-peered
|
||||
* (MESH_CLOSE, reason 55), which briefly breaks forwarding. */
|
||||
fprintf(wpa_supplicant, "mesh_max_inactivity=3600\n");
|
||||
|
||||
fprintf(wpa_supplicant, "\nnetwork={\n");
|
||||
fprintf(wpa_supplicant, " mode=5\n");
|
||||
fprintf(wpa_supplicant, " ssid=\"%s\"\n", mesh_id);
|
||||
fprintf(wpa_supplicant, " frequency=%d\n", freq);
|
||||
|
||||
if (band && !strcmp(band, "6GHz") && (!width || !strcmp(width, "auto"))) {
|
||||
int center = wifi_center_chan_80(channel);
|
||||
|
||||
fprintf(wpa_supplicant, " ht40=1\n");
|
||||
fprintf(wpa_supplicant, " he=1\n");
|
||||
fprintf(wpa_supplicant, " max_oper_chwidth=1\n");
|
||||
if (center)
|
||||
fprintf(wpa_supplicant, " vht_center_freq1=%d\n", wifi_chan_to_freq(center, band));
|
||||
} else if (width && strcmp(width, "auto")) {
|
||||
if (!strcmp(width, "20MHz")) {
|
||||
fprintf(wpa_supplicant, " disable_ht40=1\n");
|
||||
} else if (!strcmp(width, "40MHz")) {
|
||||
fprintf(wpa_supplicant, " ht40=1\n");
|
||||
} else if (!strcmp(width, "80MHz")) {
|
||||
int center = wifi_center_chan_80(channel);
|
||||
|
||||
fprintf(wpa_supplicant, " ht40=1\n");
|
||||
if (!strcmp(band, "6GHz"))
|
||||
fprintf(wpa_supplicant, " he=1\n");
|
||||
else
|
||||
fprintf(wpa_supplicant, " vht=1\n");
|
||||
fprintf(wpa_supplicant, " max_oper_chwidth=1\n");
|
||||
if (center)
|
||||
fprintf(wpa_supplicant, " vht_center_freq1=%d\n", wifi_chan_to_freq(center, band));
|
||||
} else if (!strcmp(width, "160MHz")) {
|
||||
int center = wifi_center_chan_160(channel);
|
||||
|
||||
fprintf(wpa_supplicant, " ht40=1\n");
|
||||
if (!strcmp(band, "6GHz"))
|
||||
fprintf(wpa_supplicant, " he=1\n");
|
||||
else
|
||||
fprintf(wpa_supplicant, " vht=1\n");
|
||||
fprintf(wpa_supplicant, " max_oper_chwidth=2\n");
|
||||
if (center)
|
||||
fprintf(wpa_supplicant, " vht_center_freq1=%d\n", wifi_chan_to_freq(center, band));
|
||||
}
|
||||
}
|
||||
fprintf(wpa_supplicant, " mesh_fwding=%d\n", forwarding ? 1 : 0);
|
||||
|
||||
fprintf(wpa_supplicant, " key_mgmt=SAE\n");
|
||||
fprintf(wpa_supplicant, " ieee80211w=2\n");
|
||||
if (secret)
|
||||
fprintf(wpa_supplicant, " sae_password=\"%s\"\n", secret);
|
||||
|
||||
fprintf(wpa_supplicant, "}\n");
|
||||
|
||||
out:
|
||||
free(secret);
|
||||
if (wpa_supplicant)
|
||||
fclose(wpa_supplicant);
|
||||
umask(oldmask);
|
||||
|
||||
return rc;
|
||||
}
|
||||
/*
|
||||
* Get probe-timeout for a radio from sysrepo config.
|
||||
* Returns 0 if not set.
|
||||
@@ -283,7 +485,7 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net)
|
||||
|
||||
fprintf(iw, "# Generated by Infix confd - WiFi Interface Creation\n");
|
||||
fprintf(iw, "# Create %s interface %s on radio %s\n",
|
||||
mode == wifi_station ? "station" : "access point", ifname, radio);
|
||||
mode == wifi_station ? "station" : (mode == wifi_mesh ? "mesh" : "access point"), ifname, radio);
|
||||
|
||||
/* Wait for PHY if probe-timeout is set (slow USB dongles) */
|
||||
if (probe_timeout > 0) {
|
||||
@@ -317,6 +519,12 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net)
|
||||
case wifi_ap:
|
||||
fprintf(iw, "iw phy %s interface add %s type __ap\n", radio, ifname);
|
||||
break;
|
||||
case wifi_mesh:
|
||||
fprintf(iw, "iw phy %s interface add %s type mesh\n", radio, ifname);
|
||||
wifi_gen_mesh(cif);
|
||||
fprintf(iw, "initctl -bfq enable mesh@%s\n", ifname);
|
||||
fprintf(iw, "initctl -bfq touch mesh@%s\n", ifname);
|
||||
break;
|
||||
default:
|
||||
ERROR("WiFi mode %d unknown", mode);
|
||||
rc = SR_ERR_INVAL_ARG;
|
||||
@@ -350,9 +558,16 @@ int wifi_del_iface(struct lyd_node *dif, struct dagger *net)
|
||||
fprintf(iw, "iw dev %s del 2>/dev/null || ip link del %s 2>/dev/null || true\n", ifname, ifname);
|
||||
|
||||
wifi = lydx_get_child(dif, "wifi");
|
||||
if (wifi && wifi_get_mode(wifi) != wifi_ap) {
|
||||
erasef(WPA_SUPPLICANT_CONF, ifname);
|
||||
fprintf(iw, "initctl -bfq disable wifi@%s\n", ifname);
|
||||
if (wifi) {
|
||||
int mode = wifi_get_mode(wifi);
|
||||
|
||||
if (mode == wifi_mesh) {
|
||||
erasef(WPA_SUPPLICANT_CONF, ifname);
|
||||
fprintf(iw, "initctl -bfq disable mesh@%s\n", ifname);
|
||||
} else if (mode != wifi_ap) {
|
||||
erasef(WPA_SUPPLICANT_CONF, ifname);
|
||||
fprintf(iw, "initctl -bfq disable wifi@%s\n", ifname);
|
||||
}
|
||||
}
|
||||
fclose(iw);
|
||||
|
||||
|
||||
@@ -428,6 +428,8 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net,
|
||||
if (wifi_get_mode(cif) == wifi_station)
|
||||
return wifi_validate_secret(session, cif)
|
||||
? : wifi_gen_station(cif);
|
||||
if (wifi_get_mode(cif) == wifi_mesh)
|
||||
return wifi_gen_mesh(cif);
|
||||
return 0;
|
||||
case IFT_DUMMY:
|
||||
case IFT_GRE:
|
||||
|
||||
@@ -130,6 +130,7 @@ int bridge_port_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
|
||||
typedef enum wifi_mode_t {
|
||||
wifi_station,
|
||||
wifi_ap,
|
||||
wifi_mesh,
|
||||
wifi_unknown
|
||||
} wifi_mode_t;
|
||||
|
||||
@@ -138,6 +139,7 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net);
|
||||
int wifi_del_iface(struct lyd_node *dif, struct dagger *net);
|
||||
int wifi_mode_changed(struct lyd_node *wifi);
|
||||
int wifi_gen_station(struct lyd_node *cif);
|
||||
int wifi_gen_mesh(struct lyd_node *cif);
|
||||
wifi_mode_t wifi_get_mode(struct lyd_node *wifi);
|
||||
|
||||
/* if-gre.c */
|
||||
|
||||
@@ -939,8 +939,8 @@ submodule infix-if-bridge {
|
||||
must "not(../ip:ipv4/ip:address or ../ip:ipv6/ip:address)" {
|
||||
error-message "Bridge ports cannot have IP addresses configured.";
|
||||
}
|
||||
must "not(derived-from-or-self(../if:type, 'infix-ift:wifi')) or ../infix-if:wifi/infix-if:access-point" {
|
||||
error-message "WiFi interfaces can only be bridge ports when configured as Access Points.";
|
||||
must "not(derived-from-or-self(../if:type, 'infix-ift:wifi')) or ../infix-if:wifi/infix-if:access-point or ../infix-if:wifi/infix-if:mesh-point" {
|
||||
error-message "WiFi interfaces can only be bridge ports when configured as Access Points or Mesh Points.";
|
||||
}
|
||||
description "Bridge association and port specific settings.";
|
||||
uses bridge-port-common;
|
||||
|
||||
@@ -48,15 +48,12 @@ submodule infix-if-wifi {
|
||||
- Security: WPA2/WPA3 with keystore integration
|
||||
- Operational state: Connection status, RSSI, client lists";
|
||||
|
||||
revision 2026-03-04 {
|
||||
revision 2026-03-06 {
|
||||
description
|
||||
"Add band-steering (MBO) support and OKC configuration.";
|
||||
reference "internal";
|
||||
}
|
||||
|
||||
revision 2026-02-12 {
|
||||
description
|
||||
"Add support for roaming, by adding support for 802.11v/k/r"
|
||||
"Adding mesh support and roaming.
|
||||
- Add 802.11s mesh point mode support.
|
||||
- Add band-steering (MBO) support and OKC configuration.
|
||||
- Add support for roaming, by adding support for 802.11v/k/r";
|
||||
reference "internal";
|
||||
}
|
||||
|
||||
@@ -160,12 +157,14 @@ submodule infix-if-wifi {
|
||||
Once you've identified a network, configure either:
|
||||
- Station mode: Connect to an existing WiFi network
|
||||
- Access Point mode: Create a WiFi network for clients
|
||||
- Mesh Point mode: Create an 802.11s mesh link
|
||||
|
||||
Note: A radio can host either:
|
||||
- Multiple AP interfaces (multi-SSID), OR
|
||||
- A single Station interface
|
||||
- A single Station interface, OR
|
||||
- A single Mesh Point interface
|
||||
|
||||
Mixing AP and Station on the same radio is not supported.";
|
||||
Mixing AP and Mesh Point on the same radio is not supported.";
|
||||
|
||||
case station {
|
||||
container station {
|
||||
@@ -644,6 +643,145 @@ submodule infix-if-wifi {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case mesh-point {
|
||||
container mesh-point {
|
||||
presence "Configure 802.11s mesh point mode";
|
||||
|
||||
must "/iehw:hardware/iehw:component[iehw:name = current()/../radio]/ih:wifi-radio/ih:band" {
|
||||
error-message "Parent radio must have 'band' configured for mesh mode";
|
||||
}
|
||||
|
||||
must "/iehw:hardware/iehw:component[iehw:name = current()/../radio]/ih:wifi-radio/ih:channel" {
|
||||
error-message "Parent radio must have 'channel' configured for mesh mode";
|
||||
}
|
||||
|
||||
must "/iehw:hardware/iehw:component[iehw:name = current()/../radio]/ih:wifi-radio/ih:country-code != '00'" {
|
||||
error-message "Country code '00' is not allowed for mesh mode.";
|
||||
}
|
||||
|
||||
must "not(/if:interfaces/if:interface[wifi/access-point][wifi/radio = current()/../radio])" {
|
||||
error-message "Mesh point and access point cannot coexist on the same radio";
|
||||
}
|
||||
|
||||
description
|
||||
"802.11s Mesh Point mode configuration.
|
||||
|
||||
In mesh mode, the interface creates a peer-to-peer mesh
|
||||
link with other mesh points sharing the same mesh ID.
|
||||
|
||||
Only one mesh point interface is allowed per radio.
|
||||
Mesh point and access point cannot share the same radio.
|
||||
|
||||
Example use case: Wireless backhaul between APs.";
|
||||
|
||||
leaf mesh-id {
|
||||
type string {
|
||||
length "1..32";
|
||||
}
|
||||
mandatory true;
|
||||
description
|
||||
"Mesh network identifier.
|
||||
|
||||
All mesh points that should form a mesh network
|
||||
must use the same mesh ID.";
|
||||
}
|
||||
|
||||
leaf forwarding {
|
||||
type boolean;
|
||||
default true;
|
||||
description
|
||||
"Enable layer-2 mesh forwarding (mesh_fwding).
|
||||
|
||||
When true, the mesh interface can be added to a Linux
|
||||
bridge for transparent L2 connectivity (mesh portal).
|
||||
|
||||
When false, only locally destined traffic is received.";
|
||||
}
|
||||
|
||||
container security {
|
||||
description
|
||||
"Mesh security configuration.
|
||||
|
||||
All mesh links use WPA3-SAE encryption.";
|
||||
|
||||
leaf secret {
|
||||
type ks:central-symmetric-key-ref;
|
||||
mandatory true;
|
||||
description
|
||||
"Pre-shared key (PSK) reference for SAE mesh.
|
||||
|
||||
References a symmetric key in the keystore.
|
||||
All mesh points in the same mesh must share
|
||||
the same key.";
|
||||
}
|
||||
}
|
||||
|
||||
/* Operational state */
|
||||
|
||||
container peers {
|
||||
list peer {
|
||||
config false;
|
||||
key mac-address;
|
||||
description
|
||||
"List of currently connected mesh peers.";
|
||||
|
||||
leaf mac-address {
|
||||
type yang:mac-address;
|
||||
description "Mesh peer MAC address.";
|
||||
}
|
||||
|
||||
leaf signal-strength {
|
||||
type int16;
|
||||
units "dBm";
|
||||
description "Peer signal strength in dBm.";
|
||||
}
|
||||
|
||||
leaf connected-time {
|
||||
type uint32;
|
||||
units "seconds";
|
||||
description "Time since peer connected, in seconds.";
|
||||
}
|
||||
|
||||
leaf rx-packets {
|
||||
type yang:counter64;
|
||||
description "Packets received from this peer.";
|
||||
}
|
||||
|
||||
leaf tx-packets {
|
||||
type yang:counter64;
|
||||
description "Packets transmitted to this peer.";
|
||||
}
|
||||
|
||||
leaf rx-bytes {
|
||||
type yang:counter64;
|
||||
units "octets";
|
||||
description "Bytes received from this peer.";
|
||||
}
|
||||
|
||||
leaf tx-bytes {
|
||||
type yang:counter64;
|
||||
units "octets";
|
||||
description "Bytes transmitted to this peer.";
|
||||
}
|
||||
|
||||
leaf rx-speed {
|
||||
type uint32;
|
||||
units "100 kbps";
|
||||
description
|
||||
"Last received data rate from this peer in 100 kbps.";
|
||||
}
|
||||
|
||||
leaf tx-speed {
|
||||
type uint32;
|
||||
units "100 kbps";
|
||||
description
|
||||
"Last transmitted data rate to this peer in 100 kbps.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1382,17 +1382,85 @@ class Iface:
|
||||
|
||||
stations_table.print()
|
||||
|
||||
def pr_wifi_peers(self):
|
||||
if not self.wifi:
|
||||
return
|
||||
|
||||
mesh = self.wifi.get("mesh-point", {})
|
||||
peers_data = mesh.get("peers", {})
|
||||
peers = peers_data.get("peer", [])
|
||||
|
||||
if not peers:
|
||||
return
|
||||
|
||||
print("\nCONNECTED PEERS:")
|
||||
peers_table = SimpleTable([
|
||||
Column('MAC'),
|
||||
Column('SIGNAL'),
|
||||
Column('TIME'),
|
||||
Column('RX PKT'),
|
||||
Column('TX PKT'),
|
||||
Column('RX BYTES'),
|
||||
Column('TX BYTES'),
|
||||
Column('RX SPEED'),
|
||||
Column('TX SPEED')
|
||||
])
|
||||
|
||||
for peer in peers:
|
||||
mac = peer.get("mac-address", "unknown")
|
||||
signal = peer.get("signal-strength")
|
||||
signal_str = signal_to_status(signal) if signal is not None else "------"
|
||||
|
||||
conn_time = peer.get("connected-time", 0)
|
||||
time_str = f"{conn_time}s"
|
||||
|
||||
rx_pkt = peer.get("rx-packets", 0)
|
||||
tx_pkt = peer.get("tx-packets", 0)
|
||||
rx_bytes = peer.get("rx-bytes", 0)
|
||||
tx_bytes = peer.get("tx-bytes", 0)
|
||||
|
||||
rx_speed = peer.get("rx-speed", 0)
|
||||
tx_speed = peer.get("tx-speed", 0)
|
||||
rx_speed_str = f"{rx_speed / 10:.1f}" if rx_speed else "-"
|
||||
tx_speed_str = f"{tx_speed / 10:.1f}" if tx_speed else "-"
|
||||
|
||||
peers_table.row(mac, signal_str, time_str, rx_pkt, tx_pkt,
|
||||
rx_bytes, tx_bytes, rx_speed_str, tx_speed_str)
|
||||
|
||||
peers_table.print()
|
||||
|
||||
|
||||
def pr_proto_wifi(self, pipe=''):
|
||||
if self.wifi and (ap := self.wifi.get("access-point", {})):
|
||||
ssid = ap.get("ssid", "------")
|
||||
stations = ap.get("stations", {}).get("station", [])
|
||||
data_str = f"access-point ssid: {ssid} stations: {len(stations)}"
|
||||
elif self.wifi and (station := self.wifi.get("station", {})):
|
||||
ssid = station.get("ssid", "------")
|
||||
data_str = f"station ssid: {ssid}"
|
||||
if (signal := station.get("signal-strength")) is not None:
|
||||
data_str += f" signal: {signal_to_status(signal)}"
|
||||
ssid = None
|
||||
signal = None
|
||||
mode = None
|
||||
|
||||
if self.wifi:
|
||||
if "access-point" in self.wifi:
|
||||
ap = self.wifi["access-point"]
|
||||
ssid = ap.get("ssid", "------")
|
||||
mode = "AP"
|
||||
stations_data = ap.get("stations", {})
|
||||
stations = stations_data.get("station", [])
|
||||
station_count = len(stations)
|
||||
data_str = f"{mode}, ssid: {ssid}, stations: {station_count}"
|
||||
elif "mesh-point" in self.wifi:
|
||||
mesh = self.wifi["mesh-point"]
|
||||
mesh_id = mesh.get("mesh-id", "------")
|
||||
mode = "Mesh"
|
||||
peers_data = mesh.get("peers", {})
|
||||
peers = peers_data.get("peer", [])
|
||||
data_str = f"{mode}, mesh-id: {mesh_id}, peers: {len(peers)}"
|
||||
else:
|
||||
station=self.wifi.get("station", {})
|
||||
ssid = station.get("ssid", "------")
|
||||
signal = station.get("signal-strength")
|
||||
mode = "Station"
|
||||
if signal is not None:
|
||||
signal_str = signal_to_status(signal)
|
||||
data_str = f"{mode}, ssid: {ssid}, signal: {signal_str}"
|
||||
else:
|
||||
data_str = f"{mode}, ssid: {ssid}"
|
||||
else:
|
||||
data_str = "ssid: ------"
|
||||
|
||||
@@ -1664,9 +1732,8 @@ class Iface:
|
||||
print(f"{'out-octets':<{19}}: {self.out_octets}")
|
||||
|
||||
if self.wifi:
|
||||
# Detect mode: AP has "stations", Station has "signal-strength" or "scan-results"
|
||||
ap = self.wifi.get('access-point')
|
||||
if ap:
|
||||
if "access-point" in self.wifi:
|
||||
ap = self.wifi['access-point']
|
||||
mode = "access-point"
|
||||
ssid = ap.get('ssid', "----")
|
||||
stations_data = ap.get("stations", {})
|
||||
@@ -1675,6 +1742,16 @@ class Iface:
|
||||
print(f"{'ssid':<{19}}: {ssid}")
|
||||
print(f"{'connected stations':<{19}}: {len(stations)}")
|
||||
self.pr_wifi_stations()
|
||||
elif "mesh-point" in self.wifi:
|
||||
mesh = self.wifi['mesh-point']
|
||||
mode = "mesh-point"
|
||||
mesh_id = mesh.get('mesh-id', "----")
|
||||
peers_data = mesh.get("peers", {})
|
||||
peers = peers_data.get("peer", [])
|
||||
print(f"{'mode':<{20}}: {mode}")
|
||||
print(f"{'mesh-id':<{20}}: {mesh_id}")
|
||||
print(f"{'connected peers':<{20}}: {len(peers)}")
|
||||
self.pr_wifi_peers()
|
||||
else:
|
||||
mode = "station"
|
||||
station = self.wifi.get('station', {})
|
||||
|
||||
@@ -58,6 +58,21 @@ def wifi_ap(ifname):
|
||||
return {'access-point': ap_data} if ap_data else {}
|
||||
|
||||
|
||||
def wifi_mesh(ifname, info=None):
|
||||
"""Get operational data for mesh point mode using iw"""
|
||||
mesh_data = {}
|
||||
|
||||
if info is None:
|
||||
info = get_iw_info(ifname)
|
||||
if info.get('ssid'):
|
||||
mesh_data['mesh-id'] = info['ssid']
|
||||
|
||||
peers = get_iw_stations(ifname)
|
||||
if peers:
|
||||
mesh_data['peers'] = {'peer': peers}
|
||||
|
||||
return {'mesh-point': mesh_data}
|
||||
|
||||
def wifi_station(ifname):
|
||||
"""Get operational data for Station mode using iw + wpa_cli for scanning"""
|
||||
station_data = {}
|
||||
@@ -100,6 +115,8 @@ def wifi(ifname):
|
||||
|
||||
if mode == 'ap':
|
||||
result.update(wifi_ap(ifname))
|
||||
elif mode == 'mesh point':
|
||||
result.update(wifi_mesh(ifname, info))
|
||||
else:
|
||||
result.update(wifi_station(ifname))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user