conman: Re-use successful provisioning APs.
When provisioning, if we have already provisionined successfully, try to
re-use that AP before trying others or scanning.
In addition to speeing up reprovisioning, this makes it easier to decide
that we don't have a known path to the ACS, which is useful for
controlling LED state.
Change-Id: I5c56ef6ee75ad4fcb28b7648dd17b10c1cb598d2
diff --git a/conman/connection_manager.py b/conman/connection_manager.py
index 26fe6b6..a2de7d6 100755
--- a/conman/connection_manager.py
+++ b/conman/connection_manager.py
@@ -389,6 +389,7 @@
# This interface is not connected to the WLAN, so scan for potential
# routes to the ACS for provisioning.
if (not self.acs() and
+ not getattr(wifi, 'last_successful_bss_info', None) and
time.time() > wifi.last_wifi_scan_time + self._wifi_scan_period_s):
logging.debug('Performing scan on %s.', wifi.name)
self._wifi_scan(wifi)
@@ -423,6 +424,9 @@
self._status.connected_to_wlan = False
if self.acs():
logging.debug('Connected to ACS on %s', wifi.name)
+ wifi.last_successful_bss_info = getattr(wifi,
+ 'last_attempted_bss_info',
+ None)
now = time.time()
if (self._wlan_configuration and
hasattr(wifi, 'waiting_for_acs_since')):
@@ -461,9 +465,23 @@
return result
def _update_interfaces_and_routes(self):
+ """Touch each interface via update_routes."""
+
self.bridge.update_routes()
for wifi in self.wifi:
wifi.update_routes()
+ # If wifi is connected to something that's not the WLAN, it must be a
+ # provisioning attempt, and in particular that attempt must be via
+ # last_attempted_bss_info. If that is the same as the
+ # last_successful_bss_info (i.e. the last attempt was successful) and we
+ # aren't connected to the ACS after calling update_routes (which expires
+ # the connection status cache), then this BSS is no longer successful.
+ if (wifi.wpa_supplicant and
+ not self._connected_to_wlan(wifi) and
+ (getattr(wifi, 'last_successful_bss_info', None) ==
+ getattr(wifi, 'last_attempted_bss_info', None)) and
+ not wifi.acs()):
+ wifi.last_successful_bss_info = None
# Make sure these get called semi-regularly so that exported status is up-
# to-date.
@@ -619,19 +637,22 @@
if not hasattr(wifi, 'cycler'):
return False
- bss_info = wifi.cycler.next()
+ last_successful_bss_info = getattr(wifi, 'last_successful_bss_info', None)
+ bss_info = last_successful_bss_info or wifi.cycler.next()
if bss_info is not None:
+ logging.debug('Attempting to connect to SSID %s for provisioning',
+ bss_info.ssid)
self._status.trying_open = True
- connected = subprocess.call(self.WIFI_SETCLIENT +
- ['--ssid', bss_info.ssid,
- '--band', wifi.bands[0],
- '--bssid', bss_info.bssid]) == 0
+ connected = self._try_bssid(wifi, bss_info)
if connected:
self._status.connected_to_open = True
now = time.time()
wifi.waiting_for_acs_since = now
wifi.complain_about_acs_at = now + 5
logging.info('Attempting to provision via SSID %s', bss_info.ssid)
+ # If we can no longer connect to this, it's no longer successful.
+ elif bss_info == last_successful_bss_info:
+ wifi.last_successful_bss_info = None
return connected
else:
# TODO(rofrankel): There are probably more cases in which this should be
@@ -643,6 +664,13 @@
return False
+ def _try_bssid(self, wifi, bss_info):
+ wifi.last_attempted_bss_info = bss_info
+ return subprocess.call(self.WIFI_SETCLIENT +
+ ['--ssid', bss_info.ssid,
+ '--band', wifi.bands[0],
+ '--bssid', bss_info.bssid]) == 0
+
def _connected_to_wlan(self, wifi):
return (wifi.wpa_supplicant and
any(config.client_up for band, config
diff --git a/conman/connection_manager_test.py b/conman/connection_manager_test.py
index 9e582e7..6b1142d 100755
--- a/conman/connection_manager_test.py
+++ b/conman/connection_manager_test.py
@@ -221,6 +221,10 @@
self.interface_by_name(wifi)._initially_connected = True
self.scan_has_results = False
+ # Should we be able to connect to open network s2?
+ self.s2_connect = True
+ # Will s2 fail rather than providing ACS access?
+ self.s2_fail = False
@property
def IP_LINK(self):
@@ -235,13 +239,8 @@
wifi = self.wifi_for_band(wlan_configuration.band)
wifi.add_terminating_event()
- def _try_next_bssid(self, wifi):
- if hasattr(wifi, 'cycler'):
- bss_info = wifi.cycler.peek()
- if bss_info:
- self.last_provisioning_attempt = bss_info
-
- super(ConnectionManager, self)._try_next_bssid(wifi)
+ def _try_bssid(self, wifi, bss_info):
+ super(ConnectionManager, self)._try_bssid(wifi, bss_info)
socket = os.path.join(self._wpa_control_interface, wifi.name)
@@ -255,13 +254,21 @@
return True
if bss_info and bss_info.ssid == 's2':
- if wifi.attached():
- wifi.add_connected_event()
+ if self.s2_connect:
+ if wifi.attached():
+ wifi.add_connected_event()
+ else:
+ open(socket, 'w')
+ if self.s2_fail:
+ connection_check_result = 'fail'
+ logging.debug('s2 configured to have no ACS access')
+ else:
+ connection_check_result = 'restricted'
+ wifi.set_connection_check_result(connection_check_result)
+ self.ifplugd_action(wifi.name, True)
+ return True
else:
- open(socket, 'w')
- wifi.set_connection_check_result('restricted')
- self.ifplugd_action(wifi.name, True)
- return True
+ logging.debug('s2 configured not to connect')
return False
@@ -532,8 +539,11 @@
for _ in range(3):
c.run_once()
wvtest.WVPASS(c.has_status_files([status.P.CONNECTED_TO_OPEN]))
- wvtest.WVPASSEQ(c.last_provisioning_attempt.ssid, 's2')
- wvtest.WVPASSEQ(c.last_provisioning_attempt.bssid, '01:23:45:67:89:ab')
+
+ last_bss_info = c.wifi_for_band('2.4').last_attempted_bss_info
+ wvtest.WVPASSEQ(last_bss_info.ssid, 's2')
+ wvtest.WVPASSEQ(last_bss_info.bssid, '01:23:45:67:89:ab')
+
# Wait for the connection to be processed.
c.run_once()
wvtest.WVPASS(c.acs())
@@ -591,6 +601,75 @@
wvtest.WVFAIL(c.wifi_for_band('2.4').current_route())
wvtest.WVPASS(c.bridge.current_route())
+ # Now delete the config and bring down the bridge and make sure we reprovision
+ # via the last working BSS.
+ c.delete_wlan_config('2.4')
+ c.bridge.set_connection_check_result('fail')
+ scan_count_2_4 = c.wifi_for_band('2.4').wifi_scan_counter
+ c.run_until_interface_update()
+ wvtest.WVFAIL(c.acs())
+ wvtest.WVFAIL(c.internet())
+ # s2 is not what the cycler would suggest trying next.
+ wvtest.WVPASSNE('s2', c.wifi_for_band('2.4').cycler.peek())
+ # Run only once, so that only one BSS can be tried. It should be the s2 one,
+ # since that worked previously.
+ c.run_once()
+ wvtest.WVPASS(c.acs())
+ # Make sure we didn't scan on 2.4.
+ wvtest.WVPASSEQ(scan_count_2_4, c.wifi_for_band('2.4').wifi_scan_counter)
+
+ # Now re-create the WLAN config, connect to the WLAN, and make sure that s2 is
+ # unset as last_successful_bss_info if it is no longer available.
+ c.write_wlan_config('2.4', ssid, psk)
+ c.run_once()
+ wvtest.WVPASS(c.acs())
+ wvtest.WVPASS(c.internet())
+
+ c.s2_connect = False
+ c.delete_wlan_config('2.4')
+ c.run_once()
+ wvtest.WVPASSEQ(c.wifi_for_band('2.4').last_successful_bss_info, None)
+
+ # Now do the same, except this time s2 is connected to but doesn't provide ACS
+ # access. This requires first re-establishing s2 as successful, so there are
+ # four steps:
+ #
+ # 1) Connect to WLAN.
+ # 2) Disconnect, reprovision via s2 (establishing it as successful).
+ # 3) Reconnect to WLAN so that we can trigger re-provisioning by
+ # disconnecting.
+ # 4) Connect to s2 but get no ACS access; see that last_successful_bss_info is
+ # unset.
+ c.write_wlan_config('2.4', ssid, psk)
+ c.run_once()
+ wvtest.WVPASS(c.acs())
+ wvtest.WVPASS(c.internet())
+
+ c.delete_wlan_config('2.4')
+ c.run_once()
+ wvtest.WVFAIL(c.wifi_for_band('2.4').acs())
+
+ c.s2_connect = True
+ # Give it time to try all BSSIDs.
+ for _ in range(3):
+ c.run_once()
+ s2_bss = iw.BssInfo('01:23:45:67:89:ab', 's2')
+ wvtest.WVPASSEQ(c.wifi_for_band('2.4').last_successful_bss_info, s2_bss)
+
+ c.s2_fail = True
+ c.write_wlan_config('2.4', ssid, psk)
+ c.run_once()
+ wvtest.WVPASS(c.acs())
+ wvtest.WVPASS(c.internet())
+
+ wvtest.WVPASSEQ(c.wifi_for_band('2.4').last_successful_bss_info, s2_bss)
+ c.delete_wlan_config('2.4')
+ # Run once so that c will reconnect to s2.
+ c.run_once()
+ # Now run until it sees the lack of ACS access.
+ c.run_until_interface_update()
+ wvtest.WVPASSEQ(c.wifi_for_band('2.4').last_successful_bss_info, None)
+
@wvtest.wvtest
@connection_manager_test(WIFI_SHOW_OUTPUT_ONE_RADIO)
diff --git a/conman/iw.py b/conman/iw.py
index f751302..973d653 100755
--- a/conman/iw.py
+++ b/conman/iw.py
@@ -34,7 +34,7 @@
def __eq__(self, other):
# pylint: disable=protected-access
- return self.__attrs() == other.__attrs()
+ return isinstance(other, BssInfo) and self.__attrs() == other.__attrs()
def __hash__(self):
return hash(self.__attrs())