merge: branch 'feature/interface-combinations-policy'

wifi: parse NL80211_ATTR_INTERFACE_COMBINATIONS for P2P concurrency detection

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2349
This commit is contained in:
tinnci 2026-02-21 05:21:53 +08:00
commit 75e934e5da
10 changed files with 353 additions and 8 deletions

View file

@ -53,6 +53,9 @@ typedef struct {
guint peer_dump_id;
guint peer_missing_id;
/* Parent Wi-Fi device ifindex for checking interface combination capabilities */
int parent_ifindex;
bool is_waiting_for_supplicant : 1;
bool enabled : 1;
} NMDeviceWifiP2PPrivate;
@ -365,6 +368,32 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
return NM_ACT_STAGE_RETURN_FAILURE;
}
/* Check if the hardware supports concurrent Station + P2P operation.
* We use NL80211_IFTYPE_STATION (2) and NL80211_IFTYPE_P2P_CLIENT (8).
* If concurrency is not supported, log a warning but proceed anyway
* (the kernel/supplicant will handle the actual switching).
*/
if (priv->parent_ifindex > 0) {
guint8 num_channels = 0;
gboolean can_concurrent;
can_concurrent = nm_platform_wifi_can_concurrent(NM_PLATFORM_GET,
priv->parent_ifindex,
NM_WIFI_IFACE_TYPE_STATION,
NM_WIFI_IFACE_TYPE_P2P_CLIENT,
&num_channels);
if (can_concurrent) {
_LOGI(LOGD_DEVICE | LOGD_WIFI,
"P2P: Hardware supports concurrent Station + P2P operation (channels: %u)",
num_channels);
} else {
_LOGW(LOGD_DEVICE | LOGD_WIFI,
"P2P: Hardware may not support concurrent Station + P2P operation. "
"Infrastructure connection may be interrupted.");
}
}
connection = nm_device_get_applied_connection(NM_DEVICE(self));
g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
@ -1158,7 +1187,9 @@ nm_device_wifi_p2p_get_mgmt_iface(NMDeviceWifiP2P *self)
}
void
nm_device_wifi_p2p_set_mgmt_iface(NMDeviceWifiP2P *self, NMSupplicantInterface *iface)
nm_device_wifi_p2p_set_mgmt_iface(NMDeviceWifiP2P *self,
NMSupplicantInterface *iface,
int parent_ifindex)
{
NMDeviceWifiP2PPrivate *priv;
@ -1172,14 +1203,17 @@ nm_device_wifi_p2p_set_mgmt_iface(NMDeviceWifiP2P *self, NMSupplicantInterface *
supplicant_interfaces_release(self, FALSE);
if (!iface)
if (!iface) {
priv->parent_ifindex = 0;
goto done;
}
_LOGD(LOGD_DEVICE | LOGD_WIFI,
"P2P: WPA supplicant management interface changed to %s.",
nm_ref_string_get_str(nm_supplicant_interface_get_object_path(iface)));
priv->mgmt_iface = g_object_ref(iface);
priv->mgmt_iface = g_object_ref(iface);
priv->parent_ifindex = parent_ifindex;
g_signal_connect(priv->mgmt_iface,
NM_SUPPLICANT_INTERFACE_STATE,

View file

@ -31,7 +31,9 @@ GType nm_device_wifi_p2p_get_type(void);
NMDeviceWifiP2P *nm_device_wifi_p2p_new(const char *iface);
NMSupplicantInterface *nm_device_wifi_p2p_get_mgmt_iface(NMDeviceWifiP2P *self);
void nm_device_wifi_p2p_set_mgmt_iface(NMDeviceWifiP2P *self, NMSupplicantInterface *iface);
void nm_device_wifi_p2p_set_mgmt_iface(NMDeviceWifiP2P *self,
NMSupplicantInterface *iface,
int parent_ifindex);
void nm_device_wifi_p2p_remove(NMDeviceWifiP2P *self);

View file

@ -702,7 +702,7 @@ supplicant_interface_release(NMDeviceWifi *self)
if (priv->p2p_device) {
/* Signal to P2P device to also release its reference */
nm_device_wifi_p2p_set_mgmt_iface(priv->p2p_device, NULL);
nm_device_wifi_p2p_set_mgmt_iface(priv->p2p_device, NULL, 0);
}
_scan_notify_is_scanning(self);
@ -1344,8 +1344,22 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
supplicant_state = nm_supplicant_interface_get_state(priv->sup_iface);
if (supplicant_state <= NM_SUPPLICANT_INTERFACE_STATE_STARTING
|| supplicant_state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED)
|| supplicant_state > NM_SUPPLICANT_INTERFACE_STATE_COMPLETED) {
guint8 num_channels = 0;
if (nm_platform_wifi_can_concurrent(nm_device_get_platform(device),
nm_device_get_ifindex(device),
NM_WIFI_IFACE_TYPE_STATION,
NM_WIFI_IFACE_TYPE_P2P_CLIENT,
&num_channels)) {
_LOGI(LOGD_DEVICE | LOGD_WIFI,
"Device is available (state %d) due to hardware concurrency support",
supplicant_state);
return TRUE;
}
return FALSE;
}
return TRUE;
}
@ -2809,7 +2823,9 @@ recheck_p2p_availability(NMDeviceWifi *self)
priv->p2p_device = nm_device_wifi_p2p_new(iface_name);
nm_device_wifi_p2p_set_mgmt_iface(priv->p2p_device, priv->sup_iface);
nm_device_wifi_p2p_set_mgmt_iface(priv->p2p_device,
priv->sup_iface,
nm_device_get_ifindex(NM_DEVICE(self)));
g_signal_emit(self, signals[P2P_DEVICE_CREATED], 0, priv->p2p_device);
g_object_add_weak_pointer(G_OBJECT(priv->p2p_device), (gpointer *) &priv->p2p_device);
@ -2818,7 +2834,9 @@ recheck_p2p_availability(NMDeviceWifi *self)
}
if (p2p_available && priv->p2p_device) {
nm_device_wifi_p2p_set_mgmt_iface(priv->p2p_device, priv->sup_iface);
nm_device_wifi_p2p_set_mgmt_iface(priv->p2p_device,
priv->sup_iface,
nm_device_get_ifindex(NM_DEVICE(self)));
return;
}

View file

@ -10415,6 +10415,18 @@ wifi_set_wake_on_wlan(NMPlatform *platform, int ifindex, _NMSettingWirelessWakeO
return nm_wifi_utils_set_wake_on_wlan(wifi_data, wowl);
}
static gboolean
wifi_can_concurrent(NMPlatform *platform,
int ifindex,
NMWifiIfaceType iftype1,
NMWifiIfaceType iftype2,
guint8 *out_num_channels)
{
WIFI_GET_WIFI_DATA_NETNS(wifi_data, platform, ifindex, FALSE);
return nm_wifi_utils_can_concurrent(wifi_data, iftype1, iftype2, out_num_channels);
}
/*****************************************************************************/
static gboolean
@ -12502,6 +12514,7 @@ nm_linux_platform_class_init(NMLinuxPlatformClass *klass)
platform_class->wifi_indicate_addressing_running = wifi_indicate_addressing_running;
platform_class->wifi_get_wake_on_wlan = wifi_get_wake_on_wlan;
platform_class->wifi_set_wake_on_wlan = wifi_set_wake_on_wlan;
platform_class->wifi_can_concurrent = wifi_can_concurrent;
platform_class->mesh_get_channel = mesh_get_channel;
platform_class->mesh_set_channel = mesh_set_channel;

View file

@ -3472,6 +3472,20 @@ nm_platform_wifi_set_wake_on_wlan(NMPlatform *self, int ifindex, _NMSettingWirel
return klass->wifi_set_wake_on_wlan(self, ifindex, wowl);
}
gboolean
nm_platform_wifi_can_concurrent(NMPlatform *self,
int ifindex,
NMWifiIfaceType iftype1,
NMWifiIfaceType iftype2,
guint8 *out_num_channels)
{
_CHECK_SELF(self, klass, FALSE);
g_return_val_if_fail(ifindex > 0, FALSE);
return klass->wifi_can_concurrent(self, ifindex, iftype1, iftype2, out_num_channels);
}
guint32
nm_platform_mesh_get_channel(NMPlatform *self, int ifindex)
{

View file

@ -8,6 +8,7 @@
#include "libnm-platform/nmp-base.h"
#include "libnm-base/nm-base.h"
#include "libnm-platform/wifi/nm-wifi-utils.h"
#include "nmp-plobj.h"
#define NM_TYPE_PLATFORM (nm_platform_get_type())
@ -1294,6 +1295,11 @@ typedef struct {
gboolean (*wifi_set_wake_on_wlan)(NMPlatform *self,
int ifindex,
_NMSettingWirelessWakeOnWLan wowl);
gboolean (*wifi_can_concurrent)(NMPlatform *self,
int ifindex,
NMWifiIfaceType iftype1,
NMWifiIfaceType iftype2,
guint8 *out_num_channels);
guint32 (*mesh_get_channel)(NMPlatform *self, int ifindex);
gboolean (*mesh_set_channel)(NMPlatform *self, int ifindex, guint32 channel);
@ -2254,6 +2260,12 @@ _NMSettingWirelessWakeOnWLan nm_platform_wifi_get_wake_on_wlan(NMPlatform *self,
gboolean
nm_platform_wifi_set_wake_on_wlan(NMPlatform *self, int ifindex, _NMSettingWirelessWakeOnWLan wowl);
gboolean nm_platform_wifi_can_concurrent(NMPlatform *self,
int ifindex,
NMWifiIfaceType iftype1,
NMWifiIfaceType iftype2,
guint8 *out_num_channels);
guint32 nm_platform_mesh_get_channel(NMPlatform *self, int ifindex);
gboolean nm_platform_mesh_set_channel(NMPlatform *self, int ifindex, guint32 channel);
gboolean nm_platform_mesh_set_ssid(NMPlatform *self, int ifindex, const guint8 *ssid, gsize len);

View file

@ -575,8 +575,18 @@ struct nl80211_device_info {
gboolean supported;
gboolean success;
gboolean can_wowlan;
/* Interface combinations from NL80211_ATTR_INTERFACE_COMBINATIONS */
GArray *iface_combinations;
};
static void
_nm_wifi_iface_combination_clear(NMWifiIfaceCombination *comb)
{
if (comb->limits)
g_array_unref(comb->limits);
}
#define WLAN_CIPHER_SUITE_USE_GROUP 0x000FAC00
#define WLAN_CIPHER_SUITE_WEP40 0x000FAC01
#define WLAN_CIPHER_SUITE_TKIP 0x000FAC02
@ -765,6 +775,87 @@ nl80211_wiphy_info_handler(const struct nl_msg *msg, void *arg)
if (tb[NL80211_ATTR_SUPPORT_IBSS_RSN])
info->caps |= _NM_WIFI_DEVICE_CAP_IBSS_RSN;
/* Parse interface combinations for concurrent mode support */
if (tb[NL80211_ATTR_INTERFACE_COMBINATIONS]) {
struct nlattr *nl_comb;
int rem_comb;
int n_combinations = 0;
/* First pass: count combinations */
nla_for_each_nested (nl_comb, tb[NL80211_ATTR_INTERFACE_COMBINATIONS], rem_comb) {
n_combinations++;
}
if (n_combinations > 0) {
GArray *combs;
combs = g_array_sized_new(FALSE, TRUE, sizeof(NMWifiIfaceCombination), n_combinations);
g_array_set_clear_func(combs, (GDestroyNotify) _nm_wifi_iface_combination_clear);
/* Second pass: parse each combination */
nla_for_each_nested (nl_comb, tb[NL80211_ATTR_INTERFACE_COMBINATIONS], rem_comb) {
struct nlattr *tb_comb[MAX_NL80211_IFACE_COMB + 1];
NMWifiIfaceCombination comb = {0};
if (nla_parse_nested_arr(tb_comb, nl_comb, NULL) < 0)
continue;
if (tb_comb[NL80211_IFACE_COMB_MAXNUM])
comb.max_num = nla_get_u32(tb_comb[NL80211_IFACE_COMB_MAXNUM]);
if (tb_comb[NL80211_IFACE_COMB_NUM_CHANNELS])
comb.num_channels = nla_get_u32(tb_comb[NL80211_IFACE_COMB_NUM_CHANNELS]);
/* Parse limits within this combination */
if (tb_comb[NL80211_IFACE_COMB_LIMITS]) {
struct nlattr *nl_limit;
int rem_limit;
int n_limits = 0;
/* Count limits */
nla_for_each_nested (nl_limit, tb_comb[NL80211_IFACE_COMB_LIMITS], rem_limit) {
n_limits++;
}
if (n_limits > 0) {
comb.limits =
g_array_sized_new(FALSE, TRUE, sizeof(NMWifiIfaceCombLimit), n_limits);
nla_for_each_nested (nl_limit,
tb_comb[NL80211_IFACE_COMB_LIMITS],
rem_limit) {
struct nlattr *tb_limit[MAX_NL80211_IFACE_LIMIT + 1];
NMWifiIfaceCombLimit limit = {0};
if (nla_parse_nested_arr(tb_limit, nl_limit, NULL) < 0)
continue;
if (tb_limit[NL80211_IFACE_LIMIT_MAX])
limit.max = nla_get_u32(tb_limit[NL80211_IFACE_LIMIT_MAX]);
if (tb_limit[NL80211_IFACE_LIMIT_TYPES]) {
struct nlattr *nl_type;
int rem_type;
nla_for_each_nested (nl_type,
tb_limit[NL80211_IFACE_LIMIT_TYPES],
rem_type) {
limit.types |= (1 << nla_type(nl_type));
}
}
g_array_append_val(comb.limits, limit);
}
}
}
g_array_append_val(combs, comb);
}
info->iface_combinations = combs;
_LOGD("parsed %d interface combinations from kernel", n_combinations);
}
}
info->success = TRUE;
return NL_SKIP;
@ -920,6 +1011,9 @@ nm_wifi_utils_nl80211_new(struct nl_sock *genl, guint16 genl_family_id, int ifin
self->parent.caps = device_info.caps;
self->can_wowlan = device_info.can_wowlan;
/* Store interface combination capabilities for concurrent mode support */
self->parent.iface_combinations = device_info.iface_combinations;
_LOGD("using nl80211 for Wi-Fi device control");
return (NMWifiUtils *) g_steal_pointer(&self);

View file

@ -8,6 +8,36 @@
#include "nm-wifi-utils.h"
/**
* NMWifiIfaceCombLimit:
* @max: Maximum number of interfaces in this limit set
* @types: Bitmask of interface types (NL80211_IFTYPE_*)
*
* Represents a single interface limit within a combination.
*/
typedef struct {
guint16 max;
guint16 types;
} NMWifiIfaceCombLimit;
/**
* NMWifiIfaceCombination:
* @limits: Array of interface limits
* @n_limits: Number of limits
* @max_num: Maximum total number of interfaces
* @num_channels: Number of different channels that may be used
* @sta_ap_bi_match: Whether beacon intervals must match
*
* Represents a valid interface combination from the kernel.
*/
typedef struct {
GArray *limits;
guint32 max_num;
guint32 num_channels;
} NMWifiIfaceCombination;
typedef struct {
GObjectClass parent;
@ -61,6 +91,9 @@ struct NMWifiUtils {
int ifindex;
_NMDeviceWifiCapabilities caps;
/* Interface combination capabilities from NL80211_ATTR_INTERFACE_COMBINATIONS */
GArray *iface_combinations;
};
#endif /* __WIFI_UTILS_PRIVATE_H__ */

View file

@ -210,3 +210,93 @@ nm_wifi_utils_indicate_addressing_running(NMWifiUtils *data, gboolean running)
return klass->indicate_addressing_running ? klass->indicate_addressing_running(data, running)
: FALSE;
}
/**
* nm_wifi_utils_can_concurrent:
* @data: The NMWifiUtils instance
* @iftype1: First interface type (NL80211_IFTYPE_*)
* @iftype2: Second interface type (NL80211_IFTYPE_*)
* @out_num_channels: (out) (optional): Number of different channels allowed
*
* Check if two interface types can operate concurrently based on
* the hardware's interface combination capabilities parsed from
* NL80211_ATTR_INTERFACE_COMBINATIONS.
*
* The algorithm tries to find a valid allocation: place iftype1 in one Limit
* and iftype2 in another Limit (or the same Limit if its max >= 2).
*
* Returns: %TRUE if the combination is allowed, %FALSE otherwise.
*/
gboolean
nm_wifi_utils_can_concurrent(NMWifiUtils *data,
NMWifiIfaceType iftype1,
NMWifiIfaceType iftype2,
guint8 *out_num_channels)
{
GArray *combs;
guint i, j, k;
g_return_val_if_fail(data != NULL, FALSE);
combs = data->iface_combinations;
if (!combs || combs->len == 0)
return FALSE;
/* Check each combination to find a valid allocation for both interface types */
for (i = 0; i < combs->len; i++) {
NMWifiIfaceCombination *comb = &g_array_index(combs, NMWifiIfaceCombination, i);
/* Quick check: we need at least 2 interfaces total for concurrent operation */
if (comb->max_num < 2)
continue;
if (!comb->limits || comb->limits->len == 0)
continue;
/* Try to allocate iftype1 to limit[j] and iftype2 to limit[k].
* They can be the same limit (j == k) or different limits (j != k).
*/
for (j = 0; j < comb->limits->len; j++) {
NMWifiIfaceCombLimit *limit_a = &g_array_index(comb->limits, NMWifiIfaceCombLimit, j);
/* Check if limit_a supports iftype1 */
if (!(limit_a->types & (1 << iftype1)))
continue;
for (k = 0; k < comb->limits->len; k++) {
NMWifiIfaceCombLimit *limit_b =
&g_array_index(comb->limits, NMWifiIfaceCombLimit, k);
/* Check if limit_b supports iftype2 */
if (!(limit_b->types & (1 << iftype2)))
continue;
/* Found two limits that support the requested types.
* Now verify the constraints.
*/
if (j == k) {
/* Case 1: Both types in the same limit.
* The limit must allow at least 2 interfaces.
*/
if (limit_a->max >= 2) {
if (out_num_channels)
*out_num_channels = comb->num_channels;
return TRUE;
}
} else {
/* Case 2: Types in different limits.
* Each limit only needs to support 1 interface (which is
* guaranteed since we're here), and we already verified
* comb->max_num >= 2.
*/
if (out_num_channels)
*out_num_channels = comb->num_channels;
return TRUE;
}
}
}
}
return FALSE;
}

View file

@ -10,6 +10,24 @@
#include "libnm-platform/nm-netlink.h"
#include "libnm-base/nm-base.h"
#include <linux/nl80211.h>
typedef enum {
NM_WIFI_IFACE_TYPE_UNSPEC = 0, /* NL80211_IFTYPE_UNSPEC */
NM_WIFI_IFACE_TYPE_ADHOC = NL80211_IFTYPE_ADHOC,
NM_WIFI_IFACE_TYPE_STATION = NL80211_IFTYPE_STATION,
NM_WIFI_IFACE_TYPE_AP = NL80211_IFTYPE_AP,
NM_WIFI_IFACE_TYPE_AP_VLAN = NL80211_IFTYPE_AP_VLAN,
NM_WIFI_IFACE_TYPE_WDS = NL80211_IFTYPE_WDS,
NM_WIFI_IFACE_TYPE_MONITOR = NL80211_IFTYPE_MONITOR,
NM_WIFI_IFACE_TYPE_MESH_POINT = NL80211_IFTYPE_MESH_POINT,
NM_WIFI_IFACE_TYPE_P2P_CLIENT = NL80211_IFTYPE_P2P_CLIENT,
NM_WIFI_IFACE_TYPE_P2P_GO = NL80211_IFTYPE_P2P_GO,
NM_WIFI_IFACE_TYPE_P2P_DEVICE = NL80211_IFTYPE_P2P_DEVICE,
NM_WIFI_IFACE_TYPE_OCB = NL80211_IFTYPE_OCB,
NM_WIFI_IFACE_TYPE_NAN = NL80211_IFTYPE_NAN,
} NMWifiIfaceType;
typedef struct NMWifiUtils NMWifiUtils;
#define NM_TYPE_WIFI_UTILS (nm_wifi_utils_get_type())
@ -69,4 +87,21 @@ gboolean nm_wifi_utils_set_mesh_channel(NMWifiUtils *data, guint32 channel);
gboolean nm_wifi_utils_set_mesh_ssid(NMWifiUtils *data, const guint8 *ssid, gsize len);
/**
* nm_wifi_utils_can_concurrent:
* @data: The NMWifiUtils instance
* @iftype1: First interface type (NL80211_IFTYPE_*)
* @iftype2: Second interface type (NL80211_IFTYPE_*)
* @out_num_channels: (out) (optional): Number of different channels allowed
*
* Check if two interface types can operate concurrently based on
* the hardware's interface combination capabilities.
*
* Returns: %TRUE if the combination is allowed, %FALSE otherwise.
*/
gboolean nm_wifi_utils_can_concurrent(NMWifiUtils *data,
NMWifiIfaceType iftype1,
NMWifiIfaceType iftype2,
guint8 *out_num_channels);
#endif /* __WIFI_UTILS_H__ */