diff --git a/src/dpp.c b/src/dpp.c index 6f05aae9..d89c3056 100644 --- a/src/dpp.c +++ b/src/dpp.c @@ -193,6 +193,7 @@ struct dpp_sm { bool roc_started : 1; bool channel_switch : 1; bool mutual_auth : 1; + bool autoconnect : 1; }; static const char *dpp_role_to_string(enum dpp_capability role) @@ -469,6 +470,8 @@ static void dpp_free_auth_data(struct dpp_sm *dpp) static void dpp_reset(struct dpp_sm *dpp) { + struct station *station = station_find(netdev_get_ifindex(dpp->netdev)); + if (dpp->uri) { l_free(dpp->uri); dpp->uri = NULL; @@ -549,12 +552,19 @@ static void dpp_reset(struct dpp_sm *dpp) dpp_property_changed_notify(dpp); dpp->interface = DPP_INTERFACE_UNBOUND; + + if (station) { + if (dpp->station_watch) + station_remove_state_watch(station, dpp->station_watch); + + /* Set the old autoconnect state back to what it was */ + if (dpp->role == DPP_CAPABILITY_ENROLLEE) + station_set_autoconnect(station, dpp->autoconnect); + } } 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) { @@ -572,13 +582,6 @@ 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); - known_networks_watch_remove(dpp->known_network_watch); l_free(dpp); @@ -3721,6 +3724,9 @@ static void dpp_frame_watch(struct dpp_sm *dpp, uint16_t frame_type, L_UINT_TO_PTR(frame_type), NULL); } +static void dpp_start_enrollee(struct dpp_sm *dpp); +static bool dpp_start_pkex_enrollee(struct dpp_sm *dpp); + /* * Station is unaware of DPP's state so we need to handle a few cases here so * weird stuff doesn't happen: @@ -3734,27 +3740,50 @@ static void dpp_frame_watch(struct dpp_sm *dpp, uint16_t frame_type, * 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. + * connecting state or enrolling and going to a roaming state. */ static void dpp_station_state_watch(enum station_state state, void *user_data) { struct dpp_sm *dpp = user_data; - if (dpp->state == DPP_STATE_NOTHING) + if (dpp->state == DPP_STATE_NOTHING || + dpp->interface == DPP_INTERFACE_UNBOUND) return; switch (state) { case STATION_STATE_DISCONNECTED: + /* DPP-initiated disconnect, the enrollee can now start */ + if (dpp->role == DPP_CAPABILITY_ENROLLEE) { + l_debug("Starting enrollee after disconnect"); + + if (dpp->interface == DPP_INTERFACE_DPP) + dpp_start_enrollee(dpp); + else + dpp_start_pkex_enrollee(dpp); + + return; + } + + break; case STATION_STATE_DISCONNECTING: + /* Normal part of disconnecting prior to enrolling */ + if (dpp->role == DPP_CAPABILITY_ENROLLEE) + return; + + /* fall through */ case STATION_STATE_ROAMING: case STATION_STATE_FT_ROAMING: case STATION_STATE_FW_ROAMING: + /* + * An enrollee will always be disconnected prior to starting + * so a roaming condition should never happen. + */ if (L_WARN_ON(dpp->role == DPP_CAPABILITY_ENROLLEE)) - dpp_reset(dpp); + goto reset; if (dpp->role == DPP_CAPABILITY_CONFIGURATOR) { l_debug("Disconnected while configuring, stopping DPP"); - dpp_reset(dpp); + goto reset; } break; @@ -3762,28 +3791,22 @@ static void dpp_station_state_watch(enum station_state state, void *user_data) case STATION_STATE_CONNECTED: case STATION_STATE_CONNECTING_AUTO: case STATION_STATE_NETCONFIG: - 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); + /* + * The user could have issued a connection request during + * enrolling, in which case DPP should be canceled. We should + * never hit this case as a configurator as a disconnect would + * need to come prior. + */ + L_WARN_ON(dpp->role == DPP_CAPABILITY_CONFIGURATOR); - break; + goto reset; } + +reset: + l_debug("Resetting DPP after station state change (state=%u)", state); + dpp_reset(dpp); } static void dpp_create(struct netdev *netdev) @@ -3793,7 +3816,6 @@ 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; @@ -3839,8 +3861,6 @@ 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); dpp->known_network_watch = known_networks_watch_add( dpp_known_network_watch, dpp, NULL); @@ -3927,6 +3947,64 @@ static void dpp_start_presence(struct dpp_sm *dpp, uint32_t *limit_freqs, dpp_start_offchannel(dpp, dpp->current_freq); } +static int dpp_try_disconnect_station(struct dpp_sm *dpp, + bool *out_disconnect_started) +{ + struct station *station = station_find(netdev_get_ifindex(dpp->netdev)); + int ret; + bool started = false; + + /* Unusual case, but an enrollee could still be started */ + if (!station) + goto done; + + dpp->autoconnect = station_get_autoconnect(station); + station_set_autoconnect(station, false); + + switch (station_get_state(station)) { + case STATION_STATE_AUTOCONNECT_QUICK: + case STATION_STATE_AUTOCONNECT_FULL: + /* Should never happen since we just set autoconnect false */ + l_warn("Still in autoconnect state after setting false!"); + + /* fall through */ + case STATION_STATE_DISCONNECTED: + break; + case STATION_STATE_ROAMING: + case STATION_STATE_FT_ROAMING: + case STATION_STATE_FW_ROAMING: + case STATION_STATE_CONNECTING: + case STATION_STATE_CONNECTING_AUTO: + case STATION_STATE_CONNECTED: + case STATION_STATE_NETCONFIG: + /* + * For any connected or connection in progress state, start a + * disconnect + */ + ret = station_disconnect(station); + if (ret < 0) { + l_warn("failed to start disconnecting (%d)", ret); + station_set_autoconnect(station, dpp->autoconnect); + + return ret; + } + /* fall through */ + case STATION_STATE_DISCONNECTING: + l_debug("Delaying DPP start until after disconnect"); + started = true; + + break; + } + + dpp->station_watch = station_add_state_watch(station, + dpp_station_state_watch, + dpp, NULL); +done: + *out_disconnect_started = started; + + return 0; +} + static void dpp_start_enrollee(struct dpp_sm *dpp) { uint32_t freq = band_channel_to_freq(6, BAND_FREQ_2_4_GHZ); @@ -3955,27 +4033,30 @@ static struct l_dbus_message *dpp_dbus_start_enrollee(struct l_dbus *dbus, void *user_data) { struct dpp_sm *dpp = user_data; - struct station *station = station_find(netdev_get_ifindex(dpp->netdev)); + bool wait_for_disconnect; + int ret; if (dpp->state != DPP_STATE_NOTHING || dpp->interface != DPP_INTERFACE_UNBOUND) return dbus_error_busy(message); - /* - * Station isn't actually required for DPP itself, although this will - * prevent connecting to the network once configured. - */ - if (station && station_get_connected_network(station)) { - l_warn("cannot be enrollee while connected, please disconnect"); - return dbus_error_busy(message); - } else if (!station) - l_debug("No station device, continuing anyways..."); - dpp->state = DPP_STATE_PRESENCE; dpp->role = DPP_CAPABILITY_ENROLLEE; dpp->interface = DPP_INTERFACE_DPP; - dpp_start_enrollee(dpp); + ret = dpp_try_disconnect_station(dpp, &wait_for_disconnect); + if (ret < 0) { + dpp_reset(dpp); + return dbus_error_from_errno(ret, message); + } + + if (!wait_for_disconnect) + dpp_start_enrollee(dpp); + + /* + * If a disconnect was started/in progress the enrollee will start once + * that has finished + */ dpp->pending = l_dbus_message_ref(message); @@ -4111,6 +4192,9 @@ static struct l_dbus_message *dpp_start_configurator_common( dpp_property_changed_notify(dpp); + dpp->station_watch = station_add_state_watch(station, + dpp_station_state_watch, dpp, NULL); + l_debug("DPP Start Configurator: %s", dpp->uri); reply = l_dbus_message_new_method_return(message); @@ -4361,7 +4445,8 @@ static struct l_dbus_message *dpp_dbus_pkex_start_enrollee(struct l_dbus *dbus, struct dpp_sm *dpp = user_data; const char *key; const char *id; - struct station *station = station_find(netdev_get_ifindex(dpp->netdev)); + bool wait_for_disconnect; + int ret; l_debug(""); @@ -4369,9 +4454,6 @@ static struct l_dbus_message *dpp_dbus_pkex_start_enrollee(struct l_dbus *dbus, dpp->interface != DPP_INTERFACE_UNBOUND) return dbus_error_busy(message); - if (station && station_get_connected_network(station)) - return dbus_error_busy(message); - if (!dpp_parse_pkex_args(message, &key, &id)) goto invalid_args; @@ -4384,9 +4466,20 @@ static struct l_dbus_message *dpp_dbus_pkex_start_enrollee(struct l_dbus *dbus, dpp->state = DPP_STATE_PKEX_EXCHANGE; dpp->interface = DPP_INTERFACE_PKEX; - if (!dpp_start_pkex_enrollee(dpp)) + ret = dpp_try_disconnect_station(dpp, &wait_for_disconnect); + if (ret < 0) { + dpp_reset(dpp); + return dbus_error_from_errno(ret, message); + } + + if (!wait_for_disconnect && !dpp_start_pkex_enrollee(dpp)) goto invalid_args; + /* + * If a disconnect was started/in progress the PKEX enrollee will start + * once that has finished + */ + return l_dbus_message_new_method_return(message); invalid_args: @@ -4470,6 +4563,9 @@ static struct l_dbus_message *dpp_start_pkex_configurator(struct dpp_sm *dpp, dpp_reset_protocol_timer(dpp, DPP_PKEX_PROTO_TIMEOUT); dpp_property_changed_notify(dpp); + dpp->station_watch = station_add_state_watch(station, + dpp_station_state_watch, dpp, NULL); + if (dpp->pkex_key) l_debug("Starting PKEX configurator for single enrollee"); else