mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-06 07:33:01 +02:00
Continues with mesh support
This commit is contained in:
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Executable → Regular
+3
-8
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user