@@ -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