mirror of
https://github.com/openwrt/openwrt.git
synced 2026-10-14 02:19:06 +04:00
wpa_supplicant: keep a station's association on a credential-only change
A station whose configuration changed only in its credentials was removed and added again, or with RECONFIGURE deauthenticated, although the new settings could still allow the association in use. For example, a change from PSK and SAE to SAE only, while the station is associated with SAE, made the station associate a second time. The iface method network_update() reads the regenerated file with wpa_config_read() and, where its one network is the current one and the association in use (SSID, AKM, protocol, ciphers, PMF, the effective password and SAE PWE) still fits it, moves the credential fields into the current network without the PMKSA flush that SET_NETWORK does. Otherwise it refuses, and the caller removes and adds the interface as before. wpa_supplicant.uc takes this path for a plain station and a station MLD whose configuration differs only in the credential lines, and applies the MLD's freq_list again after it. Signed-off-by: Felix Fietkau <nbd@nbd.name>
This commit is contained in:
1 parent
22e7917d8e
commit
f9c2013aed
2 files changed
+205
No files matched your search
@@ -67,6 +67,62 @@ function iface_start(phydev, iface, macaddr_list)
|
||||
iface.running = true;
|
||||
}
|
||||
|
||||
// a change of these lines needs no new netdev
|
||||
const network_update_keys = [
|
||||
"ssid", "psk", "sae_password", "key_mgmt", "ieee80211w", "sae_pwe",
|
||||
"proto", "pairwise", "group",
|
||||
];
|
||||
// the options of a station MLD that only produce those lines
|
||||
const mld_network_keys = [
|
||||
"ssid", "key", "encryption", "ieee80211w", "sae_pwe", "sae_ext_key",
|
||||
];
|
||||
const config_line_re = /^(\t?)([a-z0-9_]+)=/;
|
||||
|
||||
function config_equal_except(config_a, config_b, keys)
|
||||
{
|
||||
let rest_a = { ...config_a };
|
||||
let rest_b = { ...config_b };
|
||||
|
||||
for (let key in keys) {
|
||||
delete rest_a[key];
|
||||
delete rest_b[key];
|
||||
}
|
||||
|
||||
return is_equal(rest_a, rest_b);
|
||||
}
|
||||
|
||||
// every radio of a station MLD writes its own freq_list into the shared file
|
||||
function config_line_keep(line)
|
||||
{
|
||||
let m = match(line, config_line_re);
|
||||
if (!m)
|
||||
return true;
|
||||
if (!m[1])
|
||||
return m[2] != "freq_list";
|
||||
|
||||
return index(network_update_keys, m[2]) < 0;
|
||||
}
|
||||
|
||||
function config_data_strip(config_data)
|
||||
{
|
||||
return filter(split(config_data ?? "", "\n"), config_line_keep);
|
||||
}
|
||||
|
||||
function iface_network_apply(ifname, old_data, new_data, freq_list)
|
||||
{
|
||||
let iface = wpas.interfaces[ifname];
|
||||
if (!iface ||
|
||||
!is_equal(config_data_strip(old_data), config_data_strip(new_data)) ||
|
||||
!iface.network_update())
|
||||
return false;
|
||||
|
||||
wpas.printf(`Update network of interface ${ifname} in place`);
|
||||
if (length(freq_list) > 0)
|
||||
iface.config('freq_list', freq_list);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function iface_cb(new_if, old_if)
|
||||
{
|
||||
if (old_if && new_if && is_equal(old_if.config, new_if.config)) {
|
||||
@@ -74,6 +130,14 @@ function iface_cb(new_if, old_if)
|
||||
return;
|
||||
}
|
||||
|
||||
if (old_if?.running && new_if && new_if.config.mode == "sta" &&
|
||||
config_equal_except(old_if.config, new_if.config, [ "config_data" ]) &&
|
||||
iface_network_apply(old_if.config.iface, old_if.config.config_data,
|
||||
new_if.config.config_data)) {
|
||||
new_if.running = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (new_if && old_if)
|
||||
wpas.printf(`Update configuration for interface ${old_if.config.iface}`);
|
||||
else if (old_if)
|
||||
@@ -233,6 +297,7 @@ function mld_add(data, phy_list)
|
||||
return;
|
||||
}
|
||||
|
||||
data.config_data = readfile(first_config?.config);
|
||||
wpas.add_iface(first_config);
|
||||
|
||||
let iface = wpas.interfaces[name];
|
||||
@@ -262,6 +327,21 @@ function mld_add_links(data)
|
||||
mld_add(data);
|
||||
}
|
||||
|
||||
function mld_reload(data, phy_list)
|
||||
{
|
||||
let first_config = data.phy_config[mld_first_phy(data)];
|
||||
let config_data = readfile(first_config?.config);
|
||||
|
||||
if (iface_network_apply(data.name, data.config_data, config_data,
|
||||
data.freq_list)) {
|
||||
data.config_data = config_data;
|
||||
return;
|
||||
}
|
||||
|
||||
mld_remove(data);
|
||||
mld_add(data, phy_list);
|
||||
}
|
||||
|
||||
function mld_set_config(config)
|
||||
{
|
||||
let prev_mld = { ...wpas.data.mld };
|
||||
@@ -279,6 +359,15 @@ function mld_set_config(config)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prev && config_equal_except(prev.config, data, mld_network_keys)) {
|
||||
wpas.printf(`Update network of MLD interface ${name}`);
|
||||
prev.config = data;
|
||||
prev.reload = true;
|
||||
new_mld[name] = prev;
|
||||
delete prev_mld[name];
|
||||
continue;
|
||||
}
|
||||
|
||||
let radio_mask = 0;
|
||||
for (let r in data.radios)
|
||||
if (r != null)
|
||||
@@ -360,7 +449,12 @@ function mld_start() {
|
||||
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 reload = data.reload;
|
||||
delete data.reload;
|
||||
|
||||
let add_mask = data.radio_mask_present & ~data.radio_mask_up;
|
||||
if (!add_mask && reload && data.radio_mask_up)
|
||||
mld_reload(data, phy_list);
|
||||
if (!add_mask)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "utils/base64.h"
|
||||
#include "drivers/driver.h"
|
||||
#include "ap/hostapd.h"
|
||||
#include "common/sae.h"
|
||||
#include "rsn_supp/wpa.h"
|
||||
#include "wpa_supplicant_i.h"
|
||||
#include "wps_supplicant.h"
|
||||
#include "ctrl_iface.h"
|
||||
@@ -785,6 +787,114 @@ uc_wpas_iface_config(uc_vm_t *vm, size_t nargs)
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SAE
|
||||
static bool
|
||||
uc_wpas_sae_password_equal(struct wpa_ssid *a, struct wpa_ssid *b)
|
||||
{
|
||||
const char *pw_a = a->sae_password ? a->sae_password : a->passphrase;
|
||||
const char *pw_b = b->sae_password ? b->sae_password : b->passphrase;
|
||||
|
||||
return pw_a && pw_b && !os_strcmp(pw_a, pw_b);
|
||||
}
|
||||
#endif /* CONFIG_SAE */
|
||||
|
||||
static const char *
|
||||
uc_wpas_network_mismatch(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid)
|
||||
{
|
||||
struct wpa_ssid *cur = wpa_s->current_ssid;
|
||||
int pmf = wpas_get_ssid_pmf(wpa_s, ssid);
|
||||
int pmf_used = wpa_sm_pmf_enabled(wpa_s->wpa);
|
||||
|
||||
if (cur->ssid_len != ssid->ssid_len ||
|
||||
os_memcmp(cur->ssid, ssid->ssid, ssid->ssid_len) != 0)
|
||||
return "SSID";
|
||||
if (!(ssid->key_mgmt & wpa_s->key_mgmt))
|
||||
return "AKM";
|
||||
if (!(ssid->proto & wpa_s->wpa_proto))
|
||||
return "protocol";
|
||||
if (!(ssid->pairwise_cipher & wpa_s->pairwise_cipher) ||
|
||||
!(ssid->group_cipher & wpa_s->group_cipher))
|
||||
return "cipher";
|
||||
if ((pmf == MGMT_FRAME_PROTECTION_REQUIRED && !pmf_used) ||
|
||||
(pmf == NO_MGMT_FRAME_PROTECTION && pmf_used))
|
||||
return "PMF";
|
||||
if (cur->ext_psk || ssid->ext_psk)
|
||||
return "external password";
|
||||
if (wpa_key_mgmt_wpa_psk_no_sae(wpa_s->key_mgmt) &&
|
||||
(!cur->psk_set || !ssid->psk_set ||
|
||||
os_memcmp(cur->psk, ssid->psk, PMK_LEN) != 0))
|
||||
return "PSK";
|
||||
#ifdef CONFIG_SAE
|
||||
if (wpa_key_mgmt_sae(wpa_s->key_mgmt) &&
|
||||
(!uc_wpas_sae_password_equal(cur, ssid) ||
|
||||
wpas_get_ssid_sae_pwe(wpa_s, cur) !=
|
||||
wpas_get_ssid_sae_pwe(wpa_s, ssid)))
|
||||
return "SAE password or PWE";
|
||||
#endif /* CONFIG_SAE */
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* The PMKSA cache stays: flushing it deauthenticates a station that holds
|
||||
* an SAE PMKSA.
|
||||
*/
|
||||
static void
|
||||
uc_wpas_network_move(struct wpa_ssid *cur, struct wpa_ssid *ssid)
|
||||
{
|
||||
str_clear_free(cur->passphrase);
|
||||
cur->passphrase = ssid->passphrase;
|
||||
ssid->passphrase = NULL;
|
||||
str_clear_free(cur->sae_password);
|
||||
cur->sae_password = ssid->sae_password;
|
||||
ssid->sae_password = NULL;
|
||||
os_memcpy(cur->psk, ssid->psk, PMK_LEN);
|
||||
cur->psk_set = ssid->psk_set;
|
||||
cur->key_mgmt = ssid->key_mgmt;
|
||||
cur->ieee80211w = ssid->ieee80211w;
|
||||
cur->sae_pwe = ssid->sae_pwe;
|
||||
cur->proto = ssid->proto;
|
||||
cur->pairwise_cipher = ssid->pairwise_cipher;
|
||||
cur->group_cipher = ssid->group_cipher;
|
||||
#ifdef CONFIG_SAE
|
||||
sae_deinit_pt(cur->pt);
|
||||
cur->pt = NULL;
|
||||
#endif /* CONFIG_SAE */
|
||||
}
|
||||
|
||||
static uc_value_t *
|
||||
uc_wpas_iface_network_update(uc_vm_t *vm, size_t nargs)
|
||||
{
|
||||
struct wpa_supplicant *wpa_s = uc_fn_thisval("wpas.iface");
|
||||
struct wpa_config *conf;
|
||||
const char *mismatch = "configuration";
|
||||
|
||||
if (!wpa_s)
|
||||
return NULL;
|
||||
|
||||
if (!wpa_s->current_ssid || wpa_s->current_ssid != wpa_s->conf->ssid ||
|
||||
wpa_s->conf->ssid->next || wpa_s->wpa_state != WPA_COMPLETED ||
|
||||
!wpa_key_mgmt_wpa_psk(wpa_s->key_mgmt) || !wpa_s->confname)
|
||||
return ucv_boolean_new(false);
|
||||
|
||||
conf = wpa_config_read(wpa_s->confname, NULL, false, false);
|
||||
if (!conf)
|
||||
return ucv_boolean_new(false);
|
||||
|
||||
if (conf->ssid && !conf->ssid->next)
|
||||
mismatch = uc_wpas_network_mismatch(wpa_s, conf->ssid);
|
||||
|
||||
if (mismatch)
|
||||
wpa_printf(MSG_INFO, "%s: new network does not fit the association: %s",
|
||||
wpa_s->ifname, mismatch);
|
||||
else
|
||||
uc_wpas_network_move(wpa_s->current_ssid, conf->ssid);
|
||||
|
||||
wpa_config_free(conf);
|
||||
|
||||
return ucv_boolean_new(!mismatch);
|
||||
}
|
||||
|
||||
int wpas_ucode_init(struct wpa_global *gl)
|
||||
{
|
||||
static const uc_function_list_t global_fns[] = {
|
||||
@@ -798,6 +908,7 @@ int wpas_ucode_init(struct wpa_global *gl)
|
||||
{ "status", uc_wpas_iface_status },
|
||||
{ "ctrl", uc_wpas_iface_ctrl },
|
||||
{ "config", uc_wpas_iface_config },
|
||||
{ "network_update", uc_wpas_iface_network_update },
|
||||
{ "wps_set_m7", uc_wpas_iface_wps_set_m7 },
|
||||
#ifdef CONFIG_DPP
|
||||
{ "dpp_send_action", uc_wpas_iface_dpp_send_action },
|
||||
|
||||
Reference in new issue
Block a user