[wlan][policy] Allow SME layer to complete its re-connect attempts
When it detects a disassociation, the SME layer automatically attempts
to reconnect. This reconnect is visible within the `connecting_to`
field of the SME status response.
Prior to this change, the Policy layer ignored the `connecting_to` field
and performed its own reconnect logic. When the SME layer was already
reconnecting, this interrupted the reconnect and resulted in a longer
delay to get reconnected.
Bug: 65611
Test: fx test wlancfg-tests
Multiply: wlancfg-tests
Change-Id: I91ca7e0cca0235d4842ac2333c9f6c73cd5c1fb0
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/476519
Fuchsia-Auto-Submit: Marc Khouri <mnck@google.com>
Reviewed-by: Kevin Sakuma <sakuma@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
diff --git a/src/connectivity/wlan/wlancfg/src/client/state_machine.rs b/src/connectivity/wlan/wlancfg/src/client/state_machine.rs
index 5b958ff..aaf886e 100644
--- a/src/connectivity/wlan/wlancfg/src/client/state_machine.rs
+++ b/src/connectivity/wlan/wlancfg/src/client/state_machine.rs
@@ -594,6 +594,7 @@
let status_response = status_response.map_err(|e| {
ExitReason(Err(format_err!("failed to get sme status: {:?}", e)))
})?;
+ // Check if we're still connected to a network.
match status_response.connected_to {
Some(bss_info) => {
// TODO(fxbug.dev/53545): send some stats to the saved network manager
@@ -603,27 +604,41 @@
}
}
None => {
- let next_connecting_options = ConnectingOptions {
- connect_responder: None,
- connect_request: types::ConnectRequest {
- reason: types::ConnectReason::RetryAfterDisconnectDetected,
- target: types::ConnectionCandidate {
- // strip out the bss info to force a new scan
- bss: None,
- observed_in_passive_scan: None,
- ..currently_fulfilled_request.target.clone()
- }
- },
- attempt_counter: 0,
- };
- let options = DisconnectingOptions {
- disconnect_responder: None,
- previous_network: Some((currently_fulfilled_request.target.network.clone(), types::DisconnectStatus::ConnectionFailed)),
- next_network: Some(next_connecting_options),
- reason: types::DisconnectReason::DisconnectDetectedFromSme
- };
- info!("Detected disconnection from network, will attempt reconnection");
- return Ok(disconnecting_state(common_options, options).into_state());
+ // We're no longer connected to a network. When the SME layer detects a
+ // Disassociation, it will automatically attempt a reconnection. We can
+ // check for that scenario via the `connecting_to_ssid` field.
+ if status_response.connecting_to_ssid == currently_fulfilled_request.target.network.ssid {
+ info!("Detected disconnection, SME layer is reconnecting to network");
+ // Log a disconnect in Cobalt
+ common_options.cobalt_api.log_event(
+ DISCONNECTION_METRIC_ID,
+ types::DisconnectReason::DisconnectDetectedFromSme
+ );
+ // TODO(fxbug.dev/53545): record this blip in connectivity
+ // TODO(fxbug.dev/67605): record a re-connection metric, if successful
+ } else {
+ let next_connecting_options = ConnectingOptions {
+ connect_responder: None,
+ connect_request: types::ConnectRequest {
+ reason: types::ConnectReason::RetryAfterDisconnectDetected,
+ target: types::ConnectionCandidate {
+ // strip out the bss info to force a new scan
+ bss: None,
+ observed_in_passive_scan: None,
+ ..currently_fulfilled_request.target.clone()
+ }
+ },
+ attempt_counter: 0,
+ };
+ let options = DisconnectingOptions {
+ disconnect_responder: None,
+ previous_network: Some((currently_fulfilled_request.target.network.clone(), types::DisconnectStatus::ConnectionFailed)),
+ next_network: Some(next_connecting_options),
+ reason: types::DisconnectReason::DisconnectDetectedFromSme
+ };
+ info!("Detected disconnection from network, will attempt reconnection");
+ return Ok(disconnecting_state(common_options, options).into_state());
+ }
}
}
},
@@ -2772,6 +2787,98 @@
}
#[test]
+ fn connected_state_detects_sme_reconnect() {
+ let mut exec = fasync::Executor::new().expect("failed to create an executor");
+ let mut test_values = exec.run_singlethreaded(test_setup());
+
+ let network_ssid = "foo";
+ let bss_desc = generate_random_bss_desc();
+ let connect_request = types::ConnectRequest {
+ target: types::ConnectionCandidate {
+ network: types::NetworkIdentifier {
+ ssid: network_ssid.as_bytes().to_vec(),
+ type_: types::SecurityType::Wpa2,
+ },
+ credential: Credential::None,
+ observed_in_passive_scan: Some(true),
+ bss: bss_desc.clone(),
+ multiple_bss_candidates: Some(true),
+ },
+ reason: types::ConnectReason::IdleInterfaceAutoconnect,
+ };
+ let initial_state = connected_state(test_values.common_options, connect_request.clone());
+ let fut = run_state_machine(initial_state);
+ pin_mut!(fut);
+ let sme_fut = test_values.sme_req_stream.into_future();
+ pin_mut!(sme_fut);
+
+ // Run the state machine
+ assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending);
+
+ // Respond to the SME status with `connecting_to_ssid: ssid`
+ assert_variant!(
+ poll_sme_req(&mut exec, &mut sme_fut),
+ Poll::Ready(fidl_sme::ClientSmeRequest::Status{ responder }) => {
+ responder.send(&mut fidl_sme::ClientStatusResponse{
+ connecting_to_ssid: network_ssid.as_bytes().to_vec(),
+ connected_to: None
+ }).expect("could not send sme response");
+ }
+ );
+
+ // Run the state machine
+ assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending);
+ assert_variant!(exec.wake_next_timer(), Some(_)); // wake timer for next SME status request
+ assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending);
+
+ // Respond to the next SME status request showing we are connected
+ assert_variant!(
+ poll_sme_req(&mut exec, &mut sme_fut),
+ Poll::Ready(fidl_sme::ClientSmeRequest::Status{ responder }) => {
+ responder.send(&mut fidl_sme::ClientStatusResponse{
+ connecting_to_ssid: vec![],
+ connected_to: Some(Box::new(fidl_sme::BssInfo{
+ bssid: [0, 0, 0, 0, 0, 0],
+ ssid: network_ssid.as_bytes().to_vec(),
+ rssi_dbm: 0,
+ snr_db: 0,
+ channel: fidl_common::WlanChan {
+ primary: 1,
+ cbw: fidl_common::Cbw::Cbw20,
+ secondary80: 0,
+ },
+ protection: fidl_sme::Protection::Unknown,
+ compatible: true,
+ bss_desc: bss_desc.clone(),
+ }))
+ }).expect("could not send sme response");
+ }
+ );
+
+ // Run the state machine
+ assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending);
+ assert_variant!(exec.wake_next_timer(), Some(_)); // wake timer for next SME status request
+ assert_variant!(exec.run_until_stalled(&mut fut), Poll::Pending);
+
+ // Next request should be another Status request
+ assert_variant!(
+ poll_sme_req(&mut exec, &mut sme_fut),
+ Poll::Ready(fidl_sme::ClientSmeRequest::Status{ .. }) => {}
+ );
+
+ // Check there were no state updates
+ assert_variant!(test_values.update_receiver.try_next(), Err(_));
+
+ // Cobalt metrics logged
+ validate_cobalt_events!(
+ test_values.cobalt_events,
+ DISCONNECTION_METRIC_ID,
+ types::DisconnectReason::DisconnectDetectedFromSme
+ );
+ validate_no_cobalt_events!(test_values.cobalt_events);
+ }
+
+ #[test]
fn disconnecting_state_completes_and_exits() {
let mut exec = fasync::Executor::new().expect("failed to create an executor");
let mut test_values = exec.run_singlethreaded(test_setup());