[openwrt/openwrt] wpa_supplicant: add MLO client support

LEDE Commits lede-commits at lists.infradead.org
Wed Sep 24 04:49:37 PDT 2025


nbd pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/9aca8a97d7911230211d98a24e4dc4b0a0173ec2

commit 9aca8a97d7911230211d98a24e4dc4b0a0173ec2
Author: Felix Fietkau <nbd at nbd.name>
AuthorDate: Sun Sep 21 15:39:35 2025 +0200

    wpa_supplicant: add MLO client support
    
    Can also be used for a client mode interface that is able to connect on
    multiple bands individually, while handling hostapd state for the correct
    band.
    
    Signed-off-by: Felix Fietkau <nbd at nbd.name>
---
 .../files-ucode/usr/share/ucode/wifi/supplicant.uc |   2 +
 .../files/lib/netifd/wireless-device.uc            |  13 +
 .../wifi-scripts/files/lib/netifd/wireless.uc      |  19 +-
 .../services/hostapd/files/wpa_supplicant.uc       | 425 ++++++++++++++++++---
 .../hostapd/patches/601-ucode_support.patch        |  23 +-
 .../services/hostapd/src/wpa_supplicant/ucode.c    | 149 +++++++-
 .../services/hostapd/src/wpa_supplicant/ucode.h    |   5 +
 7 files changed, 557 insertions(+), 79 deletions(-)

diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc
index 30e196ddce..3ef150694f 100644
--- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc
+++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc
@@ -248,6 +248,8 @@ export function generate(config_list, data, interface) {
 		iface: interface.config.ifname,
 		config: file_name,
 		'4addr': !!interface.config.wds,
+		mlo: !!interface.config.mlo,
+		freq_list: data.config.scan_list,
 		powersave: false
 	};
 
diff --git a/package/network/config/wifi-scripts/files/lib/netifd/wireless-device.uc b/package/network/config/wifi-scripts/files/lib/netifd/wireless-device.uc
index d793ef8bfe..fa9a5faafb 100644
--- a/package/network/config/wifi-scripts/files/lib/netifd/wireless-device.uc
+++ b/package/network/config/wifi-scripts/files/lib/netifd/wireless-device.uc
@@ -17,6 +17,16 @@ let wdev_handler = {};
 let wdev_script_task, wdev_script_timeout;
 let handler_timer;
 
+function supplicant_start_mlo()
+{
+	ubus.call({
+		object: "wpa_supplicant",
+		method: "mld_start",
+		return: "ignore",
+		data: { },
+	});
+}
+
 function delete_wdev(name)
 {
 	delete netifd.wireless.devices[name];
@@ -216,6 +226,9 @@ function run_next_handler()
 {
 	while (!wdev_cur && length(wdev_handler) > 0)
 		__run_next_handler();
+
+	if (!wdev_cur && !length(wdev_handler))
+		supplicant_start_mlo();
 }
 
 function run_handler(wdev, op, cb)
diff --git a/package/network/config/wifi-scripts/files/lib/netifd/wireless.uc b/package/network/config/wifi-scripts/files/lib/netifd/wireless.uc
index 360fe2503e..19c38d11e5 100644
--- a/package/network/config/wifi-scripts/files/lib/netifd/wireless.uc
+++ b/package/network/config/wifi-scripts/files/lib/netifd/wireless.uc
@@ -17,12 +17,12 @@ let wireless = netifd.wireless = {
 	path: realpath(netifd.main_path + "/wireless"),
 };
 
-function hostapd_update_mlo()
+function wpad_update_mlo(service, mode)
 {
 	let config = {};
 
 	for (let ifname, data in wireless.mlo) {
-		if (data.mode != "ap")
+		if (data.mode != mode)
 			continue;
 
 		data.phy = find_phy(data.radio_config[0], true);
@@ -33,17 +33,28 @@ function hostapd_update_mlo()
 	}
 
 	ubus.call({
-		object: "hostapd",
+		object: service,
 		method: "mld_set",
 		return: "ignore",
 		data: { config },
 	});
 }
 
+function hostapd_update_mlo()
+{
+	wpad_update_mlo("hostapd", "ap");
+}
+
+function supplicant_update_mlo()
+{
+	wpad_update_mlo("wpa_supplicant", "sta");
+}
+
 function update_config(new_devices, mlo_vifs)
 {
 	wireless.mlo = mlo_vifs;
 	hostapd_update_mlo();
+	supplicant_update_mlo();
 
 	for (let name, dev in wireless.devices)
 		if (!new_devices[name])
@@ -516,6 +527,8 @@ wireless.obj = ubus.publish("network.wireless", ubus_obj);
 wireless.listener = ubus.listener("ubus.object.add", (event, msg) => {
 	if (msg.path == "hostapd")
 		hostapd_update_mlo();
+	else if (msg.path == "wpa_supplicant")
+		supplicant_update_mlo();
 });
 
 return {
diff --git a/package/network/services/hostapd/files/wpa_supplicant.uc b/package/network/services/hostapd/files/wpa_supplicant.uc
index dd371154e1..b4cafe80a6 100644
--- a/package/network/services/hostapd/files/wpa_supplicant.uc
+++ b/package/network/services/hostapd/files/wpa_supplicant.uc
@@ -13,6 +13,7 @@ function ex_handler(e)
 }
 libubus.guard(ex_handler);
 
+wpas.data.mld = {};
 wpas.data.config = {};
 wpas.data.iface_phy = {};
 wpas.data.macaddr_list = {};
@@ -77,50 +78,39 @@ function prepare_config(config, radio)
 	return { config };
 }
 
-function set_config(config_name, phy_name, radio, num_global_macaddr, macaddr_base, config_list)
+function phy_dev_open(phy_name)
 {
-	let phy = wpas.data.config[config_name];
-
-	if (radio < 0)
-		radio = null;
-
+	let phy = wpas.data.config[phy_name];
 	if (!phy) {
-		phy = vlist_new(iface_cb, false);
-		phy.name = phy_name;
-		wpas.data.config[config_name] = phy;
+		warn(`Missing phy config for ${phy_name}\n`);
+		return;
 	}
 
-	phy.radio = radio;
-	phy.num_global_macaddr = num_global_macaddr;
-	phy.macaddr_base = macaddr_base;
+	let phydev = phy_open(phy.name, phy.radio);
+	if (!phydev)
+		return;
 
-	let values = [];
-	for (let config in config_list)
-		push(values, [ config.iface, prepare_config(config) ]);
+	let macaddr_list = wpas.data.macaddr_list[phy_name];
+	phydev.macaddr_init(macaddr_list, {
+		num_global: phy.num_global_macaddr,
+		macaddr_base: phy.macaddr_base,
+	});
 
-	phy.update(values);
+	return phydev;
 }
 
 function start_pending(phy_name)
 {
 	let phy = wpas.data.config[phy_name];
-	let ubus = wpas.data.ubus;
-
 	if (!phy || !phy.data)
 		return;
 
-	let phydev = phy_open(phy.name, phy.radio);
+	let phydev = phy_dev_open(phy_name);
 	if (!phydev) {
 		wpas.printf(`Could not open phy ${phy_name}`);
 		return;
 	}
 
-	let macaddr_list = wpas.data.macaddr_list[phy_name];
-	phydev.macaddr_init(macaddr_list, {
-		num_global: phy.num_global_macaddr,
-		macaddr_base: phy.macaddr_base,
-	});
-
 	for (let ifname in phy.data)
 		iface_start(phydev, phy.data[ifname]);
 }
@@ -136,6 +126,235 @@ function phy_name(phy, radio)
 	return phy;
 }
 
+function mld_remove(data)
+{
+	if (!data.radio_mask_up)
+		return;
+
+	let name = data.name;
+	wpas.printf(`Remove MLD interface ${name}`);
+	wpas.remove_iface(name);
+	wdev_remove(name);
+	data.radio_mask_up = 0;
+}
+
+function mld_first_phy(data)
+{
+	let mask = data.radio_mask_present;
+
+	for (let i = 0; mask; i++, mask >>= 1)
+		if (mask & 1)
+			return i;
+}
+
+function mld_radio_index(data, freq)
+{
+	let phys = data.phy_config;
+	for (let i = 0; i < length(phys); i++)
+		if (phys[i] && index(phys[i].freq_list, freq) >= 0)
+			return i;
+}
+
+function mld_add(data, phy_list)
+{
+	let name = data.name;
+	phy_list ??= [];
+
+	wpas.printf(`Add MLD interface ${name}`);
+
+	let radio = mld_first_phy(data);
+	if (radio == null)
+		return;
+
+	let phy_name = data.phy + '.' + radio;
+	let phydev = phy_list[phy_name];
+	if (!phydev) {
+		phydev = phy_dev_open(phy_name);
+		if (!phydev)
+			return;
+
+		phy_list[phy_name] = phydev;
+	}
+
+	let wdev_config = { ...data.config, radio_mask: data.radio_mask };
+	let ret = phydev.wdev_add(name, wdev_config);
+	if (ret)
+		wpas.printf(`Failed to create device ${name}: ${ret}`);
+
+	let first_config = data.phy_config[radio];
+
+	wdev_set_up(name, true);
+	wpas.add_iface(first_config);
+
+	let iface = wpas.interfaces[name];
+	if (!iface) {
+		wpas.printf(`Interface ${name} not found after adding\n`);
+		wpas.remove_iface(name);
+		wdev_remove(name);
+		return;
+	}
+
+	if (length(data.freq_list) > 0)
+		iface.config('freq_list', data.freq_list);
+
+	data.radio_mask_up = data.radio_mask_present;
+}
+
+function mld_remove_links(data)
+{
+	// TODO
+	mld_remove(data);
+}
+
+function mld_add_links(data)
+{
+	// TODO: incremental update
+	mld_remove(data);
+	mld_add(data);
+}
+
+function mld_set_config(config)
+{
+	let prev_mld = { ...wpas.data.mld };
+	let new_mld = {};
+	let phy_list = {};
+	let new_config = !length(prev_mld);
+
+	wpas.printf(`Set MLD config: ${keys(config)}`);
+
+	for (let name, data in config) {
+		let prev = prev_mld[name];
+		if (prev && is_equal(prev.config, data)) {
+			new_mld[name] = prev;
+			delete prev_mld[name];
+			continue;
+		}
+
+		let radio_mask = 0;
+		for (let r in data.radios)
+			if (r != null)
+				radio_mask |= 1 << r;
+
+		new_mld[name] = {
+			name,
+			config: data,
+			phy: data.phy,
+			phy_config: [],
+			radio_mask,
+			radio_mask_up: 0,
+			radio_mask_present: 0,
+		};
+	}
+
+	for (let name, data in prev_mld)
+		mld_remove(data);
+
+	wpas.data.mld = new_mld;
+
+}
+
+function mld_set_iface_config(name, data, radio, config)
+{
+	wpas.printf(`Set MLD interface ${name} radio ${radio} config: ${keys(config)}`);
+
+	data.phy_config[radio] = config;
+	if (config)
+		data.radio_mask_present |= 1 << radio;
+	else
+		data.radio_mask_present &= ~(1 << radio);
+
+	let freq_list;
+	for (let config in data.phy_config) {
+		if (!config || !config.freq_list)
+			continue;
+		if (!freq_list)
+			freq_list = [ ...config.freq_list ];
+		else
+			push(freq_list, ...config.freq_list);
+	}
+
+	data.freq_list = freq_list;
+}
+
+function mld_update_iface(name, data) {
+	if (!data.radio_mask_up)
+		return;
+
+	if (!data.radio_mask_present) {
+		mld_remove(data);
+		return;
+	}
+
+	let mask = data.radio_mask_up & ~data.radio_mask_present;
+	if (!mask)
+		return;
+
+	mld_remove_links(data);
+}
+
+function mld_update_phy(phy, ifaces) {
+	for (let name, data in wpas.data.mld) {
+		if (data.phy != phy.name)
+			continue;
+
+		mld_set_iface_config(name, data, phy.radio, ifaces[name]);
+		mld_update_iface(name, data);
+	}
+}
+
+function mld_start() {
+	wpas.printf(`Start pending MLD interfaces\n`);
+
+	let phy_list = {};
+	for (let name, data in wpas.data.mld) {
+		wpas.printf(`MLD interface ${name} present=${data.radio_mask_present} up=${data.radio_mask_up}`);
+		let add_mask = data.radio_mask_present & ~data.radio_mask_up;
+		if (!add_mask)
+			continue;
+
+		if (!data.radio_mask_up)
+			mld_add(data, phy_list);
+		else
+			mld_add_links(data);
+	}
+}
+
+function mld_bss_allowed(data, bss) {
+	if (!data.freq_list)
+		return true;
+
+	return index(data.freq_list, bss.freq) >= 0;
+}
+
+function set_config(config_name, phy_name, radio, num_global_macaddr, macaddr_base, config_list)
+{
+	let phy = wpas.data.config[config_name];
+
+	if (radio < 0)
+		radio = null;
+
+	if (!phy) {
+		phy = vlist_new(iface_cb, false);
+		phy.name = phy_name;
+		wpas.data.config[config_name] = phy;
+	}
+
+	phy.radio = radio;
+	phy.num_global_macaddr = num_global_macaddr;
+	phy.macaddr_base = macaddr_base;
+
+	let values = [];
+	let mlo_ifaces = {};
+	for (let config in config_list)
+		if (config.mlo)
+			mlo_ifaces[config.iface] = config;
+		else
+			push(values, [ config.iface, prepare_config(config) ]);
+
+	mld_update_phy(phy, mlo_ifaces);
+	phy.update(values);
+}
+
 let main_obj = {
 	phy_set_state: {
 		args: {
@@ -214,6 +433,25 @@ let main_obj = {
 			return libubus.STATUS_NOT_FOUND;
 		}
 	},
+	mld_set: {
+		args: {
+			config: {}
+		},
+		call: function(req) {
+			if (!req.args.config)
+				return libubus.STATUS_INVALID_ARGUMENT;
+
+			mld_set_config(req.args.config);
+			return 0;
+		}
+	},
+	mld_start: {
+		args: {},
+		call: function(req) {
+			mld_start();
+			return 0;
+		}
+	},
 	config_set: {
 		args: {
 			phy: "",
@@ -315,12 +553,30 @@ function iface_event(type, name, data) {
 	ubus.call("service", "event", { type: `wpa_supplicant.${name}.${type}`, data: {} });
 }
 
-function iface_hostapd_notify(phy, ifname, iface, state)
+function iface_hostapd_fill_radio_link(mld, radio, msg, link)
+{
+	let config = mld.phy_config[radio];
+	if (!config)
+		return;
+
+	let freq_list = config.freq_list;
+	if (!freq_list)
+		return;
+
+	if (!link || index(freq_list, link.frequency) < 0)
+		return;
+
+	msg.frequency = link.frequency;
+	msg.sec_chan_offset = link.sec_chan_offset;
+}
+
+function iface_hostapd_notify(ifname, iface, state)
 {
-	let ubus = wpas.data.ubus;
 	let status = iface.status();
-	let msg = { phy: phy };
+	let ubus = wpas.data.ubus;
+	let msg = {};
 
+	let mld = wpas.data.mld[ifname];
 	switch (state) {
 	case "DISCONNECTED":
 	case "AUTHENTICATING":
@@ -333,26 +589,76 @@ function iface_hostapd_notify(phy, ifname, iface, state)
 		break;
 	case "COMPLETED":
 		msg.up = true;
-		msg.frequency = status.frequency;
-		msg.sec_chan_offset = status.sec_chan_offset;
+		if (!mld) {
+			msg.frequency = status.frequency;
+			msg.sec_chan_offset = status.sec_chan_offset;
+		}
 		break;
 	default:
 		return;
 	}
 
-	ubus.call("hostapd", "apsta_state", msg);
+	if (!mld) {
+		msg.phy = wpas.data.iface_phy[ifname];
+		if (!phy) {
+			wpas.printf(`no PHY for ifname ${ifname}`);
+			return;
+		}
+		ubus.call("hostapd", "apsta_state", msg);
+		return;
+	}
+
+	let radio_mask = mld.radio_mask;
+	for (let i = 0; radio_mask; i++, radio_mask >>= 1) {
+		if (!(radio_mask & 1)) {
+			wpas.printf(`skip radio ${i}`);
+			continue;
+		}
+
+		let radio_msg = {
+			...msg,
+			phy: mld.phy,
+			radio: i,
+		};
+
+		if (state == "COMPLETED") {
+			if (status.links)
+				for (let link in status.links)
+					iface_hostapd_fill_radio_link(mld, i, radio_msg, link);
+			else
+				iface_hostapd_fill_radio_link(mld, i, radio_msg, status);
+		}
+
+		ubus.call("hostapd", "apsta_state", radio_msg);
+	}
 }
 
-function iface_channel_switch(phy, ifname, iface, info)
+function iface_channel_switch(ifname, iface, info)
 {
 	let msg = {
-		phy: phy,
 		up: true,
 		csa: true,
 		csa_count: info.csa_count ? info.csa_count - 1 : 0,
 		frequency: info.frequency,
 		sec_chan_offset: info.sec_chan_offset,
 	};
+
+	let mld = wpas.data.mld[ifname];
+	if (mld) {
+		msg.phy = mld.phy;
+		msg.radio = mld_radio_index(mld, info.frequency);
+		if (msg.radio == null) {
+			wpas.printf(`PHY ${mld.phy} radio for frequency ${info.frequency} not found`);
+			return;
+		}
+	} else {
+		msg.phy = wpas.data.iface_phy[ifname];
+		if (!msg.phy) {
+			wpas.printf(`no PHY for ifname ${ifname}`);
+			return;
+		}
+	}
+
 	ubus.call("hostapd", "apsta_state", msg);
 }
 
@@ -362,6 +668,13 @@ return {
 			set_config(phy, []);
 		wpas.ubus.disconnect();
 	},
+	bss_allowed: function(ifname, bss) {
+		let mld = wpas.data.mld[ifname];
+		if (!mld)
+			return true;
+
+		return mld_bss_allowed(mld, bss);
+	},
 	iface_add: function(name, obj) {
 		iface_event("add", name);
 	},
@@ -369,39 +682,35 @@ return {
 		iface_event("remove", name);
 	},
 	state: function(ifname, iface, state) {
-		let phy = wpas.data.iface_phy[ifname];
-		if (!phy) {
-			wpas.printf(`no PHY for ifname ${ifname}`);
-			return;
-		}
+		try {
+			iface_hostapd_notify(ifname, iface, state);
 
-		iface_hostapd_notify(phy, ifname, iface, state);
+			if (state != "COMPLETED")
+				return;
 
-		if (state != "COMPLETED")
-			return;
+			let phy = wpas.data.iface_phy[ifname];
+			if (!phy)
+				return;
 
-		let phy_data = wpas.data.config[phy];
-		if (!phy_data)
-			return;
+			let phy_data = wpas.data.config[phy];
+			if (!phy_data)
+				return;
 
-		let iface_data = phy_data.data[ifname];
-		if (!iface_data)
-			return;
+			let iface_data = phy_data.data[ifname];
+			if (!iface_data)
+				return;
 
-		let wdev_config = iface_data.config;
-		if (!wdev_config || wdev_config.mode != "mesh")
-			return;
+			let wdev_config = iface_data.config;
+			if (!wdev_config || wdev_config.mode != "mesh")
+				return;
 
-		wdev_set_mesh_params(ifname, wdev_config);
+			wdev_set_mesh_params(ifname, wdev_config);
+		} catch (e) {
+			ex_handler(e);
+		}
 	},
 	event: function(ifname, iface, ev, info) {
-		let phy = wpas.data.iface_phy[ifname];
-		if (!phy) {
-			wpas.printf(`no PHY for ifname ${ifname}`);
-			return;
-		}
-
 		if (ev == "CH_SWITCH_STARTED")
-			iface_channel_switch(phy, ifname, iface, info);
+			iface_channel_switch(ifname, iface, info);
 	}
 };
diff --git a/package/network/services/hostapd/patches/601-ucode_support.patch b/package/network/services/hostapd/patches/601-ucode_support.patch
index 30780ca76e..436acf9220 100644
--- a/package/network/services/hostapd/patches/601-ucode_support.patch
+++ b/package/network/services/hostapd/patches/601-ucode_support.patch
@@ -702,7 +702,28 @@ as adding/removing interfaces.
  CFLAGS += -DEAP_SERVER -DEAP_SERVER_IDENTITY
 --- a/wpa_supplicant/events.c
 +++ b/wpa_supplicant/events.c
-@@ -6293,6 +6293,7 @@ void supplicant_event(void *ctx, enum wp
+@@ -53,6 +53,7 @@
+ #include "wmm_ac.h"
+ #include "nan_usd.h"
+ #include "dpp_supplicant.h"
++#include "ucode.h"
+ 
+ 
+ #define MAX_OWE_TRANSITION_BSS_SELECT_COUNT 5
+@@ -1706,6 +1707,12 @@ struct wpa_ssid * wpa_scan_res_match(str
+ 		return NULL;
+ 	}
+ 
++	if (!wpas_ucode_bss_allowed(wpa_s, bss)) {
++		if (debug_print)
++			wpa_dbg(wpa_s, MSG_DEBUG, "   skip - denied by ucode handler");
++		return NULL;
++	}
++
+ 	for (ssid = group; ssid; ssid = only_first_ssid ? NULL : ssid->pnext) {
+ 		if (wpa_scan_res_ok(wpa_s, ssid, match_ssid, match_ssid_len,
+ 				    bss, bssid_ignore_count, debug_print, link))
+@@ -6293,6 +6300,7 @@ void supplicant_event(void *ctx, enum wp
  		event_to_string(event), event);
  #endif /* CONFIG_NO_STDOUT_DEBUG */
  
diff --git a/package/network/services/hostapd/src/wpa_supplicant/ucode.c b/package/network/services/hostapd/src/wpa_supplicant/ucode.c
index 88d63e0b21..7f0249a423 100644
--- a/package/network/services/hostapd/src/wpa_supplicant/ucode.c
+++ b/package/network/services/hostapd/src/wpa_supplicant/ucode.c
@@ -6,6 +6,7 @@
 #include "wpa_supplicant_i.h"
 #include "wps_supplicant.h"
 #include "ctrl_iface.h"
+#include "config.h"
 #include "bss.h"
 #include "ucode.h"
 
@@ -41,6 +42,21 @@ wpas_ucode_update_interfaces(void)
 	ucv_object_add(ucv_prototype_get(global), "interfaces", ifs);
 }
 
+static uc_value_t *
+wpas_ucode_bss_get_uval(struct wpa_bss *bss)
+{
+	uc_value_t *val;
+
+	val = ucv_object_new(vm);
+	ucv_object_add(val, "freq", ucv_int64_new(bss->freq));
+	ucv_object_add(val, "ssid", ucv_string_new_length(bss->ssid, bss->ssid_len));
+	ucv_object_add(val, "snr", ucv_int64_new(bss->snr));
+	ucv_object_add(val, "signal", ucv_int64_new(bss->level));
+	ucv_object_add(val, "noise", ucv_int64_new(bss->noise));
+
+	return val;
+}
+
 void wpas_ucode_add_bss(struct wpa_supplicant *wpa_s)
 {
 	uc_value_t *val;
@@ -71,6 +87,25 @@ void wpas_ucode_free_bss(struct wpa_supplicant *wpa_s)
 	ucv_put(val);
 }
 
+bool wpas_ucode_bss_allowed(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
+{
+	uc_value_t *val;
+	bool ret = true;
+
+	if (wpa_ucode_call_prepare("bss_allowed"))
+		return true;
+
+	uc_value_push(ucv_string_new(wpa_s->ifname));
+	uc_value_push(wpas_ucode_bss_get_uval(bss));
+	val = wpa_ucode_call(2);
+
+	if (ucv_type(val) == UC_BOOLEAN)
+		ret = ucv_boolean_get(val);
+	ucv_put(val);
+
+	return ret;
+}
+
 void wpas_ucode_update_state(struct wpa_supplicant *wpa_s)
 {
 	const char *state;
@@ -203,6 +238,29 @@ out:
 	return ucv_int64_new(ret);
 }
 
+static void
+uc_wpas_iface_status_bss(uc_value_t *ret, struct wpa_bss *bss)
+{
+	int sec_chan = 0;
+	const u8 *ie;
+
+	ie = wpa_bss_get_ie(bss, WLAN_EID_HT_OPERATION);
+	if (ie && ie[1] >= 2) {
+		const struct ieee80211_ht_operation *ht_oper;
+		int sec;
+
+		ht_oper = (const void *) (ie + 2);
+		sec = ht_oper->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK;
+		if (sec == HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE)
+			sec_chan = 1;
+		else if (sec == HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW)
+			sec_chan = -1;
+	}
+
+	ucv_object_add(ret, "sec_chan_offset", ucv_int64_new(sec_chan));
+	ucv_object_add(ret, "frequency", ucv_int64_new(bss->freq));
+}
+
 static uc_value_t *
 uc_wpas_iface_status(uc_vm_t *vm, size_t nargs)
 {
@@ -218,25 +276,29 @@ uc_wpas_iface_status(uc_vm_t *vm, size_t nargs)
 	ucv_object_add(ret, "state", ucv_string_new(wpa_supplicant_state_txt(wpa_s->wpa_state)));
 
 	bss = wpa_s->current_bss;
-	if (bss) {
-		int sec_chan = 0;
-		const u8 *ie;
-
-		ie = wpa_bss_get_ie(bss, WLAN_EID_HT_OPERATION);
-		if (ie && ie[1] >= 2) {
-			const struct ieee80211_ht_operation *ht_oper;
-			int sec;
-
-			ht_oper = (const void *) (ie + 2);
-			sec = ht_oper->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK;
-			if (sec == HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE)
-				sec_chan = 1;
-			else if (sec == HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW)
-				sec_chan = -1;
+	if (bss)
+		uc_wpas_iface_status_bss(ret, bss);
+
+	if (wpa_s->valid_links) {
+		unsigned int valid_links = wpa_s->valid_links;
+		uc_value_t *link, *links;
+
+		links = ucv_array_new(vm);
+
+		for (size_t i = 0;
+		     valid_links && i < ARRAY_SIZE(wpa_s->links);
+			 i++, valid_links >>= 1) {
+			bss = wpa_s->links[i].bss;
+
+			if (!(valid_links & 1) || !bss)
+				continue;
+
+			link = ucv_object_new(vm);
+			uc_wpas_iface_status_bss(link, bss);
+			ucv_array_set(links, i, link);
 		}
 
-		ucv_object_add(ret, "sec_chan_offset", ucv_int64_new(sec_chan));
-		ucv_object_add(ret, "frequency", ucv_int64_new(bss->freq));
+		ucv_object_add(ret, "links", links);
 	}
 
 #ifdef CONFIG_MESH
@@ -276,6 +338,58 @@ uc_wpas_iface_ctrl(uc_vm_t *vm, size_t nargs)
 	return ret;
 }
 
+static uc_value_t *
+uc_wpas_iface_config(uc_vm_t *vm, size_t nargs)
+{
+	struct wpa_supplicant *wpa_s = uc_fn_thisval("wpas.iface");
+	uc_value_t *arg = uc_fn_arg(0);
+	uc_value_t *val = uc_fn_arg(1);
+	uc_value_t *ret = NULL;
+	bool get = nargs == 1;
+	const char *name;
+	size_t len = 0;
+
+	if (!wpa_s || ucv_type(arg) != UC_STRING)
+		return NULL;
+
+	name = ucv_string_get(arg);
+	if (!strcmp(name, "freq_list")) {
+		if (get) {
+			int *cur = wpa_s->conf->freq_list;
+			if (!cur)
+				return NULL;
+
+			ret = ucv_array_new(vm);
+			while (*cur)
+				ucv_array_set(ret, len++, ucv_int64_new(*(cur++)));
+		} else {
+			size_t len = ucv_array_length(val);
+			int *freq_list;
+
+			if (ucv_type(val) != UC_ARRAY)
+				return NULL;
+
+			freq_list = calloc(len + 1, sizeof(*freq_list));
+			for (size_t i = 0; i < len; i++) {
+				uc_value_t *cur = ucv_array_get(val, i);
+
+				if (ucv_type(cur) != UC_INTEGER) {
+					free(freq_list);
+					return NULL;
+				}
+
+				freq_list[i] = ucv_int64_get(cur);
+			}
+
+			free(wpa_s->conf->freq_list);
+			wpa_s->conf->freq_list = freq_list;
+			ret = ucv_boolean_new(true);
+		}
+	}
+
+	return ret;
+}
+
 int wpas_ucode_init(struct wpa_global *gl)
 {
 	static const uc_function_list_t global_fns[] = {
@@ -288,6 +402,7 @@ int wpas_ucode_init(struct wpa_global *gl)
 	static const uc_function_list_t iface_fns[] = {
 		{ "status", uc_wpas_iface_status },
 		{ "ctrl", uc_wpas_iface_ctrl },
+		{ "config", uc_wpas_iface_config },
 	};
 	uc_value_t *data, *proto;
 
diff --git a/package/network/services/hostapd/src/wpa_supplicant/ucode.h b/package/network/services/hostapd/src/wpa_supplicant/ucode.h
index a429a0ed87..fd339fa3e9 100644
--- a/package/network/services/hostapd/src/wpa_supplicant/ucode.h
+++ b/package/network/services/hostapd/src/wpa_supplicant/ucode.h
@@ -20,6 +20,7 @@ void wpas_ucode_add_bss(struct wpa_supplicant *wpa_s);
 void wpas_ucode_free_bss(struct wpa_supplicant *wpa_s);
 void wpas_ucode_update_state(struct wpa_supplicant *wpa_s);
 void wpas_ucode_event(struct wpa_supplicant *wpa_s, int event, union wpa_event_data *data);
+bool wpas_ucode_bss_allowed(struct wpa_supplicant *wpa_s, struct wpa_bss *bss);
 #else
 static inline int wpas_ucode_init(struct wpa_global *gl)
 {
@@ -44,6 +45,10 @@ static inline void wpas_ucode_event(struct wpa_supplicant *wpa_s, int event, uni
 {
 }
 
+static inline bool wpas_ucode_bss_allowed(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
+{
+	return true;
+}
 #endif
 
 #endif




More information about the lede-commits mailing list