conman: Avoid losing track of wpa_supplicant link.

Under circumstances not yet fully understood, conman may mistakenly
think wpa_supplicant is down and never discover the mistake, causing it
to remove routes for that interface.  This adds a regular check for the
link state to make sure this doesn't happen.

BUG=31261343

Change-Id: I3fe8cd905ef44f605d94952ef34685f1ca5fb862
diff --git a/conman/interface.py b/conman/interface.py
index 66855dc..b741555 100755
--- a/conman/interface.py
+++ b/conman/interface.py
@@ -488,9 +488,7 @@
     """
     status = {}
 
-    if self._wpa_control and self._wpa_control.attached:
-      logging.debug('%s ctrl_iface_path %s',
-                    self, self._wpa_control.ctrl_iface_path)
+    if self.attached():
       lines = []
       try:
         lines = self._wpa_control.request('STATUS').splitlines()
@@ -504,6 +502,7 @@
         k, v = line.strip().split('=', 1)
         status[k] = v
 
+    logging.debug('wpa_status is %r', status)
     return status
 
   def get_wpa_control(self, socket):
@@ -525,6 +524,9 @@
       self.wpa_supplicant = False
       return
 
+    # b/31261343:  Make sure we didn't miss wpa_supplicant being up.
+    self.wpa_supplicant = self.wpa_status().get('wpa_state', '') == 'COMPLETED'
+
     while self._wpa_control.pending():
       match = self.WPA_EVENT_RE.match(self._wpa_control.recv())
       if match:
diff --git a/conman/interface_test.py b/conman/interface_test.py
index f9c1e6d..14fb795 100755
--- a/conman/interface_test.py
+++ b/conman/interface_test.py
@@ -175,7 +175,6 @@
 
 def generic_wifi_test(w, wpa_path):
   # Not currently connected.
-  # w.start_wpa_supplicant_testonly(wpa_path)
   subprocess.wifi.WPA_PATH = wpa_path
   w.attach_wpa_control(wpa_path)
   wvtest.WVFAIL(w.wpa_supplicant)
@@ -330,5 +329,62 @@
     shutil.rmtree(interface.CWMP_PATH)
 
 
+@wvtest.wvtest
+def b31261343_test():
+  """Test Wifi."""
+  w = Wifi('wcli0', '21')
+  w.initialize()
+
+  try:
+    wpa_path = tempfile.mkdtemp()
+    conman_path = tempfile.mkdtemp()
+    subprocess.set_conman_paths(conman_path, None)
+    subprocess.mock('wifi', 'interfaces',
+                    subprocess.wifi.MockInterface(phynum='0', bands=['5'],
+                                                  driver='cfg80211'))
+    subprocess.wifi.WPA_PATH = wpa_path
+
+    w.attach_wpa_control(wpa_path)
+    wvtest.WVFAIL(w.wpa_supplicant)
+
+    # Set up.
+    ssid = 'my=ssid'
+    psk = 'passphrase'
+    subprocess.mock('wifi', 'remote_ap', ssid=ssid, psk=psk, band='5',
+                    bssid='00:00:00:00:00:00', connection_check_result='succeed')
+    subprocess.check_call(['wifi', 'setclient', '--ssid', ssid, '--band', '5'],
+                          env={'WIFI_CLIENT_PSK': psk})
+
+    w.set_gateway_ip('192.168.1.1')
+    w.set_subnet('192.168.1.0/24')
+    wvtest.WVFAIL(w.wpa_supplicant)
+    w.attach_wpa_control(wpa_path)
+    w.handle_wpa_events()
+
+    def check_working():
+      w.update_routes(True)
+      wvtest.WVPASS(w.wpa_supplicant)
+      wvtest.WVPASS('default' in w.current_routes())
+
+    def check_broken():
+      w.update_routes(True)
+      wvtest.WVFAIL(w.wpa_supplicant)
+      wvtest.WVFAIL('default' in w.current_routes())
+
+    check_working()
+
+    # This is the buggy state.
+    w.wpa_supplicant = False
+    check_broken()
+
+    # Should fix itself when we next run handle_wpa_events.
+    w.handle_wpa_events()
+    check_working()
+
+  finally:
+    shutil.rmtree(wpa_path)
+    shutil.rmtree(conman_path)
+
+
 if __name__ == '__main__':
   wvtest.wvtest_main()