mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2026-05-11 09:18:38 +02:00
merge: branch 'mlo-bgscan-dedup-v2'
wifi: dedupe MLO per-link BSSIDs in bgscan multi-AP heuristic https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2405
This commit is contained in:
commit
eb64b01339
6 changed files with 218 additions and 24 deletions
|
|
@ -3100,12 +3100,19 @@ build_supplicant_config(NMDeviceWifi *self,
|
|||
goto error;
|
||||
}
|
||||
|
||||
if (!nm_supplicant_config_add_bgscan(config,
|
||||
connection,
|
||||
nm_settings_connection_get_num_seen_bssids(sett_conn),
|
||||
error)) {
|
||||
g_prefix_error(error, "bgscan: ");
|
||||
goto error;
|
||||
{
|
||||
const char *seen_bssids[NM_SETTINGS_CONNECTION_SEEN_BSSIDS_MAX + 1];
|
||||
guint num_seen_bssids;
|
||||
|
||||
num_seen_bssids = nm_settings_connection_get_seen_bssids(sett_conn, seen_bssids);
|
||||
if (!nm_supplicant_config_add_bgscan(config,
|
||||
connection,
|
||||
num_seen_bssids,
|
||||
seen_bssids,
|
||||
error)) {
|
||||
g_prefix_error(error, "bgscan: ");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
ap_isolation = nm_setting_wireless_get_ap_isolation(s_wireless);
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@
|
|||
#include "nm-dbus-manager.h"
|
||||
#include "settings/plugins/keyfile/nms-keyfile-storage.h"
|
||||
|
||||
#define SEEN_BSSIDS_MAX 30
|
||||
|
||||
#define _NM_SETTINGS_UPDATE2_FLAG_ALL_PERSIST_MODES \
|
||||
((NMSettingsUpdate2Flags) (NM_SETTINGS_UPDATE2_FLAG_TO_DISK \
|
||||
| NM_SETTINGS_UPDATE2_FLAG_IN_MEMORY \
|
||||
|
|
@ -220,9 +218,7 @@ static const GDBusSignalInfo signal_info_updated;
|
|||
static const GDBusSignalInfo signal_info_removed;
|
||||
static const NMDBusInterfaceInfoExtended interface_info_settings_connection;
|
||||
|
||||
static void update_agent_secrets_cache(NMSettingsConnection *self, NMConnection *new);
|
||||
static guint _get_seen_bssids(NMSettingsConnection *self,
|
||||
const char *strv_buf[static(SEEN_BSSIDS_MAX + 1)]);
|
||||
static void update_agent_secrets_cache(NMSettingsConnection *self, NMConnection *new);
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
|
|
@ -1405,7 +1401,7 @@ get_settings_auth_cb(NMSettingsConnection *self,
|
|||
GError *error,
|
||||
gpointer data)
|
||||
{
|
||||
const char *seen_bssids_strv[SEEN_BSSIDS_MAX + 1];
|
||||
const char *seen_bssids_strv[NM_SETTINGS_CONNECTION_SEEN_BSSIDS_MAX + 1];
|
||||
NMConnectionSerializationOptions options = {};
|
||||
|
||||
if (error) {
|
||||
|
|
@ -1426,7 +1422,7 @@ get_settings_auth_cb(NMSettingsConnection *self,
|
|||
* from the same reason as timestamp. Thus we put it here to GetSettings()
|
||||
* return settings too.
|
||||
*/
|
||||
_get_seen_bssids(self, seen_bssids_strv);
|
||||
nm_settings_connection_get_seen_bssids(self, seen_bssids_strv);
|
||||
options.seen_bssids = seen_bssids_strv;
|
||||
|
||||
/* Secrets should *never* be returned by the GetSettings method, they
|
||||
|
|
@ -2434,7 +2430,7 @@ _nm_settings_connection_register_kf_dbs(NMSettingsConnection *self,
|
|||
SeenBssidEntry *entry;
|
||||
|
||||
nm_assert(result_len == nm_g_hash_table_size(priv->seen_bssids_hash));
|
||||
if (result_len >= SEEN_BSSIDS_MAX)
|
||||
if (result_len >= NM_SETTINGS_CONNECTION_SEEN_BSSIDS_MAX)
|
||||
break;
|
||||
|
||||
if (!_nm_utils_hwaddr_aton_exact(tmp_strv[i], &addr_bin, sizeof(addr_bin)))
|
||||
|
|
@ -2459,12 +2455,14 @@ _nm_settings_connection_register_kf_dbs(NMSettingsConnection *self,
|
|||
nm_clear_pointer(&priv->seen_bssids_hash, g_hash_table_destroy);
|
||||
|
||||
nm_assert(nm_g_hash_table_size(priv->seen_bssids_hash) == result_len);
|
||||
nm_assert(result_len <= SEEN_BSSIDS_MAX);
|
||||
nm_assert(result_len <= NM_SETTINGS_CONNECTION_SEEN_BSSIDS_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
static guint
|
||||
_get_seen_bssids(NMSettingsConnection *self, const char *strv_buf[static(SEEN_BSSIDS_MAX + 1)])
|
||||
guint
|
||||
nm_settings_connection_get_seen_bssids(
|
||||
NMSettingsConnection *self,
|
||||
const char *strv_buf[static(NM_SETTINGS_CONNECTION_SEEN_BSSIDS_MAX + 1)])
|
||||
{
|
||||
NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
|
||||
SeenBssidEntry *entry;
|
||||
|
|
@ -2472,7 +2470,7 @@ _get_seen_bssids(NMSettingsConnection *self, const char *strv_buf[static(SEEN_BS
|
|||
|
||||
i = 0;
|
||||
c_list_for_each_entry (entry, &priv->seen_bssids_lst_head, seen_bssids_lst) {
|
||||
nm_assert(i <= SEEN_BSSIDS_MAX);
|
||||
nm_assert(i <= NM_SETTINGS_CONNECTION_SEEN_BSSIDS_MAX);
|
||||
strv_buf[i++] = entry->bssid;
|
||||
}
|
||||
strv_buf[i] = NULL;
|
||||
|
|
@ -2515,7 +2513,7 @@ void
|
|||
nm_settings_connection_add_seen_bssid(NMSettingsConnection *self, const char *seen_bssid)
|
||||
{
|
||||
NMSettingsConnectionPrivate *priv = NM_SETTINGS_CONNECTION_GET_PRIVATE(self);
|
||||
const char *seen_bssids_strv[SEEN_BSSIDS_MAX + 1];
|
||||
const char *seen_bssids_strv[NM_SETTINGS_CONNECTION_SEEN_BSSIDS_MAX + 1];
|
||||
NMEtherAddr addr_bin;
|
||||
const char *connection_uuid;
|
||||
SeenBssidEntry entry_stack;
|
||||
|
|
@ -2546,14 +2544,14 @@ nm_settings_connection_add_seen_bssid(NMSettingsConnection *self, const char *se
|
|||
if (!g_hash_table_add(priv->seen_bssids_hash, entry))
|
||||
nm_assert_not_reached();
|
||||
|
||||
if (g_hash_table_size(priv->seen_bssids_hash) > SEEN_BSSIDS_MAX) {
|
||||
if (g_hash_table_size(priv->seen_bssids_hash) > NM_SETTINGS_CONNECTION_SEEN_BSSIDS_MAX) {
|
||||
g_hash_table_remove(
|
||||
priv->seen_bssids_hash,
|
||||
c_list_last_entry(&priv->seen_bssids_lst_head, SeenBssidEntry, seen_bssids_lst));
|
||||
}
|
||||
}
|
||||
|
||||
nm_assert(g_hash_table_size(priv->seen_bssids_hash) <= SEEN_BSSIDS_MAX);
|
||||
nm_assert(g_hash_table_size(priv->seen_bssids_hash) <= NM_SETTINGS_CONNECTION_SEEN_BSSIDS_MAX);
|
||||
nm_assert(g_hash_table_size(priv->seen_bssids_hash)
|
||||
== c_list_length(&priv->seen_bssids_lst_head));
|
||||
|
||||
|
|
@ -2564,7 +2562,7 @@ nm_settings_connection_add_seen_bssid(NMSettingsConnection *self, const char *se
|
|||
if (!connection_uuid)
|
||||
return;
|
||||
|
||||
i = _get_seen_bssids(self, seen_bssids_strv);
|
||||
i = nm_settings_connection_get_seen_bssids(self, seen_bssids_strv);
|
||||
nm_key_file_db_set_string_list(priv->kf_db_seen_bssids, connection_uuid, seen_bssids_strv, i);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -348,6 +348,15 @@ void nm_settings_connection_add_seen_bssid(NMSettingsConnection *self, const cha
|
|||
|
||||
guint nm_settings_connection_get_num_seen_bssids(NMSettingsConnection *self);
|
||||
|
||||
/* Maximum number of BSSIDs tracked per connection. Public so that callers
|
||||
* needing the full list can stack-allocate a fixed-size strv buffer for
|
||||
* nm_settings_connection_get_seen_bssids(). */
|
||||
#define NM_SETTINGS_CONNECTION_SEEN_BSSIDS_MAX 30u
|
||||
|
||||
guint nm_settings_connection_get_seen_bssids(
|
||||
NMSettingsConnection *self,
|
||||
const char *strv_buf[static(NM_SETTINGS_CONNECTION_SEEN_BSSIDS_MAX + 1)]);
|
||||
|
||||
gboolean nm_settings_connection_autoconnect_is_blocked(NMSettingsConnection *self);
|
||||
|
||||
NMSettingsAutoconnectBlockedReason
|
||||
|
|
|
|||
|
|
@ -752,10 +752,66 @@ nm_supplicant_config_add_setting_wireless(NMSupplicantConfig *self,
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
/* Maximum number of seen BSSIDs we treat as evidence of a single Wi-Fi 7
|
||||
* MLO AP. 802.11be defines tri-link (2.4 + 5 + 6 GHz) as the maximum;
|
||||
* beyond 3 the heuristic does not apply and the multi-AP path runs.
|
||||
*/
|
||||
#define _BGSCAN_MLO_HEURISTIC_MAX_BSSIDS 3u
|
||||
|
||||
/* _seen_bssids_look_like_mlo_per_link:
|
||||
*
|
||||
* Heuristic returning TRUE when @seen_bssids appears to be the per-link
|
||||
* BSSIDs of one Wi-Fi 7 MLO-capable AP rather than several physical APs.
|
||||
*
|
||||
* In MLO, one physical AP advertises one BSSID per link (2 for 5+6 GHz,
|
||||
* 3 for tri-link). Vendors observed in the wild derive the per-link
|
||||
* BSSIDs as locally-administered virtual MAC addresses sharing octets
|
||||
* 1-4 with each other. The MLD address itself may or may not be in the
|
||||
* seen-bssids list.
|
||||
*
|
||||
* Real multi-AP ESSes (mesh, enterprise) typically use vendor-assigned
|
||||
* MACs (LAA bit unset) and unrelated address blocks per AP, so the
|
||||
* heuristic should not false-positive on them.
|
||||
*/
|
||||
static gboolean
|
||||
_seen_bssids_look_like_mlo_per_link(const char *const *seen_bssids, guint num_seen_bssids)
|
||||
{
|
||||
guint8 common_octets_1_to_4[4];
|
||||
guint i;
|
||||
|
||||
if (num_seen_bssids < 2u || num_seen_bssids > _BGSCAN_MLO_HEURISTIC_MAX_BSSIDS)
|
||||
return FALSE;
|
||||
if (!seen_bssids)
|
||||
return FALSE;
|
||||
|
||||
for (i = 0; i < num_seen_bssids; i++) {
|
||||
const char *bssid = seen_bssids[i];
|
||||
guint8 addr[6];
|
||||
|
||||
if (!bssid || !nm_utils_hwaddr_aton(bssid, addr, sizeof(addr)))
|
||||
return FALSE;
|
||||
|
||||
/* Locally-administered bit is bit 1 (value 0x02) of the first octet. */
|
||||
if (!(addr[0] & 0x02u))
|
||||
return FALSE;
|
||||
|
||||
/* Octets 1-4 must be identical across all seen BSSIDs (only octets
|
||||
* 0 and 5 vary across per-link MAC addresses for the same MLD).
|
||||
*/
|
||||
if (i == 0)
|
||||
memcpy(common_octets_1_to_4, &addr[1], 4);
|
||||
else if (memcmp(common_octets_1_to_4, &addr[1], 4) != 0)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
nm_supplicant_config_add_bgscan(NMSupplicantConfig *self,
|
||||
NMConnection *connection,
|
||||
guint num_seen_bssids,
|
||||
const char *const *seen_bssids,
|
||||
GError **error)
|
||||
{
|
||||
NMSettingWireless *s_wifi;
|
||||
|
|
@ -792,8 +848,12 @@ nm_supplicant_config_add_bgscan(NMSupplicantConfig *self,
|
|||
* in which we want more reliable roaming between APs. Thus trigger scans
|
||||
* when the signal is still somewhat OK so we have an up-to-date roam
|
||||
* candidate list when the signal gets bad.
|
||||
*
|
||||
* Exception: when @seen_bssids look like the per-link addresses of a
|
||||
* single WiFi-7 MLO AP (locally-administered bit set + shared octets
|
||||
* 1-4), keep the long interval. See _seen_bssids_look_like_mlo_per_link().
|
||||
*/
|
||||
if (num_seen_bssids > 1u
|
||||
if ((num_seen_bssids > 1u && !_seen_bssids_look_like_mlo_per_link(seen_bssids, num_seen_bssids))
|
||||
|| ((s_wsec = nm_connection_get_setting_wireless_security(connection))
|
||||
&& NM_IN_STRSET(nm_setting_wireless_security_get_key_mgmt(s_wsec),
|
||||
"ieee8021x",
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ gboolean nm_supplicant_config_add_setting_wireless(NMSupplicantConfig *self,
|
|||
gboolean nm_supplicant_config_add_bgscan(NMSupplicantConfig *self,
|
||||
NMConnection *connection,
|
||||
guint num_seen_bssids,
|
||||
const char *const *seen_bssids,
|
||||
GError **error);
|
||||
|
||||
gboolean nm_supplicant_config_add_setting_wireless_security(NMSupplicantConfig *self,
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ build_supplicant_config(NMConnection *connection,
|
|||
g_assert_no_error(error);
|
||||
g_assert(success);
|
||||
|
||||
success = nm_supplicant_config_add_bgscan(config, connection, 0, &error);
|
||||
success = nm_supplicant_config_add_bgscan(config, connection, 0, NULL, &error);
|
||||
g_assert_no_error(error);
|
||||
g_assert(success);
|
||||
|
||||
|
|
@ -914,6 +914,124 @@ test_suppl_cap_mask(void)
|
|||
|
||||
/*****************************************************************************/
|
||||
|
||||
static void
|
||||
test_wifi_bgscan_mlo_dedup(void)
|
||||
{
|
||||
/* The bgscan multi-AP heuristic should treat per-link BSSIDs of one
|
||||
* Wi-Fi 7 MLO AP as a single AP (long-interval bgscan) and treat
|
||||
* vendor-assigned multi-AP BSSIDs as multi-AP (short-interval bgscan).
|
||||
*/
|
||||
gs_unref_object NMConnection *connection = NULL;
|
||||
const unsigned char ssid_data[] = {'M', 'L', 'O', '-', 't', 'e', 's', 't'};
|
||||
gs_unref_bytes GBytes *ssid = g_bytes_new(ssid_data, sizeof(ssid_data));
|
||||
|
||||
/* Per-link addresses for one MLO AP: LAA bit set on first octet,
|
||||
* octets 1-4 shared, only octet 0 and 5 vary across links.
|
||||
* Captured from a TP-Link Deco BE63 mesh.
|
||||
*/
|
||||
const char *mlo_seen[] = {
|
||||
"f6:75:0c:74:4b:75",
|
||||
"ca:75:0c:74:4b:70",
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Real multi-AP setup: vendor-assigned MACs (UAA bit unset) with
|
||||
* unrelated address blocks per AP.
|
||||
*/
|
||||
const char *multi_ap_seen[] = {
|
||||
"00:11:22:33:44:55",
|
||||
"aa:bb:cc:dd:ee:ff",
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* All-LAA but octets 1-4 do not match: must NOT be treated as MLO. */
|
||||
const char *laa_unrelated_seen[] = {
|
||||
"f6:75:0c:74:4b:75",
|
||||
"f2:99:88:77:66:55",
|
||||
NULL,
|
||||
};
|
||||
|
||||
/* Tri-link MLO: 3 BSSIDs all matching the per-link MAC pattern.
|
||||
* 802.11be defines tri-link (2.4 + 5 + 6 GHz) as the maximum;
|
||||
* the heuristic's upper bound is 3.
|
||||
*/
|
||||
const char *mlo_seen_3link[] = {
|
||||
"f6:75:0c:74:4b:75",
|
||||
"ca:75:0c:74:4b:70",
|
||||
"e2:75:0c:74:4b:80",
|
||||
NULL,
|
||||
};
|
||||
|
||||
connection = new_basic_connection("MLO Wi-Fi", ssid, NULL);
|
||||
g_object_set(nm_connection_get_setting_wireless(connection),
|
||||
NM_SETTING_WIRELESS_BAND,
|
||||
"a",
|
||||
NULL);
|
||||
|
||||
{
|
||||
gs_unref_object NMSupplicantConfig *config = NULL;
|
||||
GError *error = NULL;
|
||||
gboolean success;
|
||||
|
||||
config =
|
||||
nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE,
|
||||
nm_utils_get_connection_first_permissions_user(connection));
|
||||
NMTST_EXPECT_NM_INFO("Config: added 'bgscan' value 'simple:30:-70:86400'*");
|
||||
success = nm_supplicant_config_add_bgscan(config, connection, 2, mlo_seen, &error);
|
||||
g_assert_no_error(error);
|
||||
g_assert(success);
|
||||
g_test_assert_expected_messages();
|
||||
}
|
||||
|
||||
{
|
||||
gs_unref_object NMSupplicantConfig *config = NULL;
|
||||
GError *error = NULL;
|
||||
gboolean success;
|
||||
|
||||
config =
|
||||
nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE,
|
||||
nm_utils_get_connection_first_permissions_user(connection));
|
||||
NMTST_EXPECT_NM_INFO("Config: added 'bgscan' value 'simple:30:-65:300'*");
|
||||
success = nm_supplicant_config_add_bgscan(config, connection, 2, multi_ap_seen, &error);
|
||||
g_assert_no_error(error);
|
||||
g_assert(success);
|
||||
g_test_assert_expected_messages();
|
||||
}
|
||||
|
||||
{
|
||||
gs_unref_object NMSupplicantConfig *config = NULL;
|
||||
GError *error = NULL;
|
||||
gboolean success;
|
||||
|
||||
config =
|
||||
nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE,
|
||||
nm_utils_get_connection_first_permissions_user(connection));
|
||||
NMTST_EXPECT_NM_INFO("Config: added 'bgscan' value 'simple:30:-65:300'*");
|
||||
success =
|
||||
nm_supplicant_config_add_bgscan(config, connection, 2, laa_unrelated_seen, &error);
|
||||
g_assert_no_error(error);
|
||||
g_assert(success);
|
||||
g_test_assert_expected_messages();
|
||||
}
|
||||
|
||||
{
|
||||
gs_unref_object NMSupplicantConfig *config = NULL;
|
||||
GError *error = NULL;
|
||||
gboolean success;
|
||||
|
||||
config =
|
||||
nm_supplicant_config_new(NM_SUPPL_CAP_MASK_NONE,
|
||||
nm_utils_get_connection_first_permissions_user(connection));
|
||||
NMTST_EXPECT_NM_INFO("Config: added 'bgscan' value 'simple:30:-70:86400'*");
|
||||
success = nm_supplicant_config_add_bgscan(config, connection, 3, mlo_seen_3link, &error);
|
||||
g_assert_no_error(error);
|
||||
g_assert(success);
|
||||
g_test_assert_expected_messages();
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
NMTST_DEFINE();
|
||||
|
||||
int
|
||||
|
|
@ -930,6 +1048,7 @@ main(int argc, char **argv)
|
|||
g_test_add_func("/supplicant-config/wifi-sae", test_wifi_sae);
|
||||
g_test_add_func("/supplicant-config/test_suppl_cap_mask", test_suppl_cap_mask);
|
||||
g_test_add_func("/supplicant-config/wifi-eap-suite-b-192", test_wifi_eap_suite_b_generation);
|
||||
g_test_add_func("/supplicant-config/wifi-bgscan-mlo-dedup", test_wifi_bgscan_mlo_dedup);
|
||||
|
||||
return g_test_run();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue