dpp: add station watch to DPP

DPP (both DPP and PKEX) run the risk of odd behavior if station
decides to change state. DPP is completely unaware of this and
best case would just result in a protocol failure, worst case
duplicate calls to __station_connect_network.

Add a station watch and stop DPP if station changes state during
the protocol.
This commit is contained in:
James Prestwood 2023-11-13 09:53:59 -08:00 committed by Denis Kenzior
parent 485f9f56bf
commit e2f28312e2
1 changed files with 75 additions and 0 deletions

View File

@ -100,6 +100,7 @@ struct dpp_sm {
char *uri;
uint8_t role;
int refcount;
uint32_t station_watch;
uint64_t wdev_id;
@ -536,6 +537,8 @@ static void dpp_reset(struct dpp_sm *dpp)
static void dpp_free(struct dpp_sm *dpp)
{
struct station *station = station_find(netdev_get_ifindex(dpp->netdev));
dpp_reset(dpp);
if (dpp->own_asn1) {
@ -553,6 +556,13 @@ static void dpp_free(struct dpp_sm *dpp)
dpp->boot_private = NULL;
}
/*
* Since this is called when the netdev goes down, station may already
* be gone in which case the state watch will automatically go away.
*/
if (station)
station_remove_state_watch(station, dpp->station_watch);
l_free(dpp);
}
@ -3608,6 +3618,67 @@ static void dpp_frame_watch(struct dpp_sm *dpp, uint16_t frame_type,
L_UINT_TO_PTR(frame_type), NULL);
}
/*
* Station is unaware of DPP's state so we need to handle a few cases here so
* weird stuff doesn't happen:
*
* - While configuring we should stay connected, a disconnection/roam should
* stop DPP since it would fail regardless due to the hardware going idle
* or changing channels since configurators assume all comms will be
* on-channel.
* - While enrolling we should stay disconnected. If station connects during
* enrolling it would cause 2x calls to __station_connect_network after
* DPP finishes.
*
* Other conditions shouldn't ever happen i.e. configuring and going into a
* connecting state or enrolling and going to a disconnected/roaming state.
*/
static void dpp_station_state_watch(enum station_state state, void *user_data)
{
struct dpp_sm *dpp = user_data;
switch (state) {
case STATION_STATE_DISCONNECTED:
case STATION_STATE_DISCONNECTING:
case STATION_STATE_ROAMING:
case STATION_STATE_FT_ROAMING:
case STATION_STATE_FW_ROAMING:
if (L_WARN_ON(dpp->role == DPP_CAPABILITY_ENROLLEE))
dpp_reset(dpp);
if (dpp->role == DPP_CAPABILITY_CONFIGURATOR) {
l_debug("Disconnected while configuring, stopping DPP");
dpp_reset(dpp);
}
break;
case STATION_STATE_CONNECTING:
case STATION_STATE_CONNECTED:
case STATION_STATE_CONNECTING_AUTO:
if (L_WARN_ON(dpp->role == DPP_CAPABILITY_CONFIGURATOR))
dpp_reset(dpp);
if (dpp->role == DPP_CAPABILITY_ENROLLEE) {
l_debug("Connecting while enrolling, stopping DPP");
dpp_reset(dpp);
}
break;
/*
* Autoconnect states are fine for enrollees. This makes it nicer for
* the user since they don't need to explicity Disconnect() to disable
* autoconnect, then re-enable it if DPP fails.
*/
case STATION_STATE_AUTOCONNECT_FULL:
case STATION_STATE_AUTOCONNECT_QUICK:
if (L_WARN_ON(dpp->role == DPP_CAPABILITY_CONFIGURATOR))
dpp_reset(dpp);
break;
}
}
static void dpp_create(struct netdev *netdev)
{
struct l_dbus *dbus = dbus_get_bus();
@ -3615,6 +3686,7 @@ static void dpp_create(struct netdev *netdev)
uint8_t dpp_conf_response_prefix[] = { 0x04, 0x0b };
uint8_t dpp_conf_request_prefix[] = { 0x04, 0x0a };
uint64_t wdev_id = netdev_get_wdev_id(netdev);
struct station *station = station_find(netdev_get_ifindex(netdev));
dpp->netdev = netdev;
dpp->state = DPP_STATE_NOTHING;
@ -3660,6 +3732,9 @@ static void dpp_create(struct netdev *netdev)
sizeof(dpp_conf_request_prefix),
dpp_handle_config_request_frame, dpp, NULL);
dpp->station_watch = station_add_state_watch(station,
dpp_station_state_watch, dpp, NULL);
l_queue_push_tail(dpp_list, dpp);
}