Continues with mesh support

This commit is contained in:
Mattias Walström
2026-06-03 11:18:47 +02:00
parent 665f25e306
commit f6917da95d
10 changed files with 181 additions and 69 deletions
@@ -0,0 +1,7 @@
service name:wpa_cli :%i pid:/run/wpa_cli-%i.pid \
[2345] wpa_cli -P /run/wpa_cli-%i.pid -i %i -a /usr/libexec/mesh-action.sh -r \
-- Wi-Fi Mesh action @%i
service name:wpa_supplicant :%i <pid/wpa_cli:%i> \
[2345] wpa_supplicant -s -i %i -c /etc/wpa_supplicant-%i.conf -P/var/run/wpa_supplicant-%i.pid \
-- Wi-Fi Mesh @%i
+5 -1
View File
@@ -265,10 +265,14 @@ def parse_interface_info(ifname):
elif stripped.startswith('addr '):
result['mac'] = stripped.split()[1]
# SSID
# SSID (AP mode)
elif stripped.startswith('ssid '):
result['ssid'] = decode_iw_ssid(' '.join(stripped.split()[1:]))
# Mesh ID (mesh point mode)
elif stripped.startswith('mesh_id '):
result['mesh_id'] = decode_iw_ssid(' '.join(stripped.split()[1:]))
# Channel/frequency
elif stripped.startswith('channel '):
parts = stripped.split()
-1
View File
@@ -74,7 +74,6 @@ define FEATURE_WIFI_INSTALL_IN_ROMFS
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
$(INSTALL) -m 0755 $(FEATURE_WIFI_PKGDIR)/mesh-action.sh $(TARGET_DIR)/usr/libexec/mesh-action.sh
$(INSTALL) -m 0755 $(FEATURE_WIFI_PKGDIR)/mesh-post.sh $(TARGET_DIR)/usr/libexec/mesh-post.sh
endef
FEATURE_WIFI_POST_INSTALL_TARGET_HOOKS += FEATURE_WIFI_INSTALL_IN_ROMFS
+3 -8
View File
@@ -1,14 +1,9 @@
#!/bin/sh
# mesh-action.sh - wpa_cli action script for 802.11s mesh
# Called by wpa_cli with: $1="<iface> <EVENT ...>"
IFACE="${1%% *}"
EVENT="${1#* }"
IFACE="$1"
EVENT="$2"
case "$EVENT" in
MESH-GROUP-STARTED*)
if [ -f "/run/mesh-params-${IFACE}.conf" ]; then
. "/run/mesh-params-${IFACE}.conf"
fi
[ -f "/run/mesh-params-${IFACE}.conf" ] && . "/run/mesh-params-${IFACE}.conf"
;;
esac
-11
View File
@@ -1,11 +0,0 @@
#!/bin/sh
IFACE="$1"
CTRL="/run/wpa_supplicant/$IFACE"
i=0
while [ $i -lt 10 ] && [ ! -S "$CTRL" ]; do
sleep 1
i=$((i + 1))
done
[ -S "$CTRL" ] && wpa_cli -i "$IFACE" -a /usr/libexec/mesh-action.sh -B
+58 -36
View File
@@ -16,6 +16,7 @@
#include "base64.h"
#define WPA_SUPPLICANT_CONF "/etc/wpa_supplicant-%s.conf"
#define MESH_PARAMS_CONF "/run/mesh-params-%s.conf"
int wifi_validate_secret(sr_session_ctx_t *session, struct lyd_node *cif)
@@ -161,7 +162,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) {
@@ -272,11 +274,6 @@ static int wifi_center_chan_160(int ch)
return 0;
}
/* HT40 secondary channel direction for mesh: returns "+" or "-" */
static const char *wifi_mesh_ht40_dir(int ch)
{
return ((ch / 4) % 2) ? "+" : "-";
}
/*
* Convert WiFi channel number to frequency in MHz.
* Band is determined from channel range:
@@ -329,15 +326,19 @@ int wifi_gen_mesh(struct lyd_node *cif)
security = lydx_get_child(mesh, "security");
secret_name = lydx_get_cattr(security, "secret");
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");
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 (!band || !channel) {
ERROR("%s: mesh requires radio band and channel", ifname);
return SR_ERR_INVAL_ARG;
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);
@@ -366,41 +367,41 @@ int wifi_gen_mesh(struct lyd_node *cif)
fprintf(wpa_supplicant, "\nnetwork={\n");
fprintf(wpa_supplicant, " mode=5\n");
fprintf(wpa_supplicant, " mesh_id=\"%s\"\n", mesh_id);
fprintf(wpa_supplicant, " ssid=\"%s\"\n", mesh_id);
fprintf(wpa_supplicant, " frequency=%d\n", freq);
/*
* Channel width configuration for mesh.
* wpa_supplicant uses mesh_ht_mode instead of hostapd's ht_capab/vht_oper_chwidth.
* For 6GHz, use HE modes; for 5GHz, use VHT modes.
* vht_center_freq1 takes frequency in MHz (not channel number).
*/
if (width && strcmp(width, "auto")) {
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, " mesh_ht_mode=HT20\n");
fprintf(wpa_supplicant, " disable_ht40=1\n");
} else if (!strcmp(width, "40MHz")) {
fprintf(wpa_supplicant, " mesh_ht_mode=HT40%s\n", wifi_mesh_ht40_dir(channel));
fprintf(wpa_supplicant, " ht40=1\n");
} else if (!strcmp(width, "80MHz")) {
int center = wifi_center_chan_80(channel);
if (!strcmp(band, "6GHz")) {
fprintf(wpa_supplicant, " mesh_ht_mode=HE80\n");
fprintf(wpa_supplicant, " ht40=1\n");
if (!strcmp(band, "6GHz"))
fprintf(wpa_supplicant, " he=1\n");
} else {
fprintf(wpa_supplicant, " mesh_ht_mode=VHT\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);
if (!strcmp(band, "6GHz")) {
fprintf(wpa_supplicant, " mesh_ht_mode=HE160\n");
fprintf(wpa_supplicant, " ht40=1\n");
if (!strcmp(band, "6GHz"))
fprintf(wpa_supplicant, " he=1\n");
} else {
fprintf(wpa_supplicant, " mesh_ht_mode=VHT\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));
@@ -415,6 +416,19 @@ int wifi_gen_mesh(struct lyd_node *cif)
fprintf(wpa_supplicant, "}\n");
/* Generate mesh params file, sourced by mesh-action.sh on MESH-GROUP-STARTED */
if (lydx_is_enabled(mesh, "gate-announcements")) {
FILE *params;
params = fopenf("w", MESH_PARAMS_CONF, ifname);
if (params) {
fprintf(params, "iw dev %s set mesh_param mesh_gate_announcements 1\n", ifname);
fclose(params);
}
} else {
erasef(MESH_PARAMS_CONF, ifname);
}
out:
free(secret);
if (wpa_supplicant)
@@ -516,8 +530,8 @@ int wifi_add_iface(struct lyd_node *cif, struct dagger *net)
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 wifi@%s\n", ifname);
fprintf(iw, "initctl -bfq touch wifi@%s\n", ifname);
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);
@@ -552,9 +566,17 @@ 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);
erasef(MESH_PARAMS_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);
+2 -2
View File
@@ -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;
+13
View File
@@ -691,6 +691,19 @@ submodule infix-if-wifi {
When false, only locally destined traffic is received.";
}
leaf gate-announcements {
type boolean;
default false;
description
"Enable mesh gate announcements (GANN frames).
When true, this node advertises itself as a mesh gate
(i.e., it has external network connectivity). Other mesh
nodes proactively maintain a path to this node.
Typically enabled on nodes with a wired uplink.";
}
container security {
description
"Mesh security configuration.
+89 -9
View File
@@ -1382,17 +1382,87 @@ 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:
# Detect mode: AP has "stations", Station has "signal-strength" or "scan-results"
ap=self.wifi.get("access-point", {})
if ap:
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}"
else:
mesh = self.wifi.get("mesh-point", {})
if mesh:
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: ------"
@@ -1675,6 +1745,16 @@ class Iface:
print(f"{'ssid':<{19}}: {ssid}")
print(f"{'connected stations':<{19}}: {len(stations)}")
self.pr_wifi_stations()
elif self.wifi.get('mesh-point'):
mesh = self.wifi.get('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', {})
@@ -62,7 +62,10 @@ def wifi_mesh(ifname):
"""Get operational data for mesh point mode using iw"""
mesh_data = {}
# iw station dump works for mesh peers (same nl80211 API as AP stations)
info = get_iw_info(ifname)
if info.get('mesh_id'):
mesh_data['mesh-id'] = info['mesh_id']
peers = get_iw_stations(ifname)
if peers:
mesh_data['peers'] = {'peer': peers}