Add autotest support for Fast Transition

Fast Transition requires that hostapd include special config items
to properly execute a Fast Transition roam. This change includes
support for those configs (nas_id, mdid, r1kh_id, r0kh, r1kh).

Also adding support for a bridge interface. This is necessary for FT
because the two hostapd instances need to communicate with each other
via L2 packets, and can only do this using bridges.

BUG=chromium:791202
TEST=Ran the following autotests with use_ft=True
     network_WiFi_Roam.wifi_roamWPA
     network_WiFi_Roam.wifi_roam1xTLS
     network_WiFi_SimpleConnect.wifi_checkWPA2
     network_WiFi_Manual

Change-Id: Ibedce85558af711b01964d22724533fbee0d0578
Reviewed-on: https://chromium-review.googlesource.com/941796
Commit-Ready: Matthew Wang <matthewmwang@chromium.org>
Tested-by: Matthew Wang <matthewmwang@chromium.org>
Reviewed-by: Kirtika Ruchandani <kirtika@chromium.org>
diff --git a/client/common_lib/cros/network/xmlrpc_security_types.py b/client/common_lib/cros/network/xmlrpc_security_types.py
index 7299228..8d45885 100644
--- a/client/common_lib/cros/network/xmlrpc_security_types.py
+++ b/client/common_lib/cros/network/xmlrpc_security_types.py
@@ -34,6 +34,7 @@
 
     """
     SERVICE_PROPERTY_PASSPHRASE = 'Passphrase'
+    SERVICE_PROPERTY_FT_ENABLED = 'WiFi.FTEnabled'
 
     def __init__(self, security='none'):
         super(SecurityConfig, self).__init__()
@@ -193,7 +194,7 @@
     def __init__(self, psk='', wpa_mode=MODE_DEFAULT, wpa_ciphers=[],
                  wpa2_ciphers=[], wpa_ptk_rekey_period=None,
                  wpa_gtk_rekey_period=None, wpa_gmk_rekey_period=None,
-                 use_strict_rekey=None):
+                 use_strict_rekey=None, use_ft=False):
         """Construct a WPAConfig.
 
         @param psk string a passphrase (64 hex characters or an ASCII phrase up
@@ -221,6 +222,7 @@
         self.wpa_gtk_rekey_period = wpa_gtk_rekey_period
         self.wpa_gmk_rekey_period = wpa_gmk_rekey_period
         self.use_strict_rekey = use_strict_rekey
+        self.use_ft = use_ft
         if len(psk) > 64:
             raise error.TestFail('WPA passphrases can be no longer than 63 '
                                  'characters (or 64 hex digits).')
@@ -247,6 +249,8 @@
 
         ret = {'wpa': self.wpa_mode,
                'wpa_key_mgmt': 'WPA-PSK'}
+        if self.use_ft:
+            ret['wpa_key_mgmt'] = 'FT-PSK'
         if len(self.psk) == 64:
            ret['wpa_psk'] = self.psk
         else:
@@ -269,7 +273,8 @@
 
     def get_shill_service_properties(self):
         """@return dict of shill service properties."""
-        return {self.SERVICE_PROPERTY_PASSPHRASE: self.psk}
+        return {self.SERVICE_PROPERTY_PASSPHRASE: self.psk,
+                self.SERVICE_PROPERTY_FT_ENABLED: self.use_ft}
 
 
     def get_wpa_cli_properties(self):
@@ -283,6 +288,8 @@
         properties.update({'psk': '\\"%s\\"' % self.psk,
                            'key_mgmt': 'WPA-PSK',
                            'proto': ' '.join(protos)})
+        if self.use_ft:
+            properties['key_mgmt'] = 'FT-PSK'
         return properties
 
 
@@ -426,6 +433,8 @@
                     '%s:%s' % (self.client_key_slot_id, self.client_key_id))
         if self.use_system_cas is not None:
             ret[self.SERVICE_PROPERTY_USE_SYSTEM_CAS] = self.use_system_cas
+        if self.use_ft is not None:
+            ret[self.SERVICE_PROPERTY_FT_ENABLED] = self.use_ft
         return ret
 
 
@@ -507,7 +516,7 @@
                  client_ca_cert=None, client_cert=None, client_key=None,
                  client_cert_id=None, client_key_id=None,
                  eap_identity=None, server_eap_users=None,
-                 wpa_mode=WPAConfig.MODE_PURE_WPA):
+                 wpa_mode=WPAConfig.MODE_PURE_WPA, use_ft=False):
         """Construct a DynamicWEPConfig.
 
         @param file_suffix string unique file suffix on DUT.
@@ -532,6 +541,7 @@
                 client_cert_id=client_cert_id, client_key_id=client_key_id,
                 eap_identity=eap_identity, server_eap_users=server_eap_users)
         self.wpa_mode = wpa_mode
+        self.use_ft = use_ft
 
 
     def get_hostapd_config(self):
@@ -543,6 +553,8 @@
         ret.update({'wpa': self.wpa_mode,
                     'wpa_pairwise': WPAConfig.CIPHER_CCMP,
                     'wpa_key_mgmt':'WPA-EAP'})
+        if self.use_ft:
+            ret['wpa_key_mgmt'] = 'FT-EAP'
         return ret
 
 
diff --git a/client/cros/networking/shill_proxy.py b/client/cros/networking/shill_proxy.py
index a121890..c551553 100644
--- a/client/cros/networking/shill_proxy.py
+++ b/client/cros/networking/shill_proxy.py
@@ -90,6 +90,7 @@
     SERVICE_PROPERTY_PASSPHRASE = 'Passphrase'
     SERVICE_PROPERTY_PROFILE = 'Profile'
     SERVICE_PROPERTY_SAVE_CREDENTIALS = 'SaveCredentials'
+    SERVICE_PROPERTY_FT_ENABLED = 'WiFi.FTEnabled'
     # Unless you really care whether a network is WPA (TSN) vs. WPA-2
     # (RSN), you should use SERVICE_PROPERTY_SECURITY_CLASS.
     SERVICE_PROPERTY_SECURITY_RAW = 'Security'
@@ -105,6 +106,11 @@
     SERVICE_PROPERTY_EAP_IDENTITY = 'EAP.Identity'
     SERVICE_PROPERTY_EAP_PASSWORD = 'EAP.Password'
     SERVICE_PROPERTY_EAP_CA_CERT_PEM = 'EAP.CACertPEM'
+    SERVICE_PROPERTY_CLIENT_CERT_ID = 'EAP.CertID'
+    SERVICE_PROPERTY_EAP_KEY_MGMT = 'EAP.KeyMgmt'
+    SERVICE_PROPERTY_EAP_PIN = 'EAP.PIN'
+    SERVICE_PROPERTY_PRIVATE_KEY_ID = 'EAP.KeyID'
+    SERVICE_PROPERTY_USE_SYSTEM_CAS = 'EAP.UseSystemCAs'
 
     # OpenVPN related properties.
     SERVICE_PROPERTY_OPENVPN_CA_CERT_PEM = 'OpenVPN.CACertPEM'
@@ -150,12 +156,18 @@
         SERVICE_PROPERTY_STRENGTH: dbus.Byte,
         SERVICE_PROPERTY_STATE: dbus.String,
         SERVICE_PROPERTY_TYPE: dbus.String,
+        SERVICE_PROPERTY_FT_ENABLED: dbus.Boolean,
 
         SERVICE_PROPERTY_EAP_EAP: dbus.String,
         SERVICE_PROPERTY_EAP_INNER_EAP: dbus.String,
         SERVICE_PROPERTY_EAP_IDENTITY: dbus.String,
         SERVICE_PROPERTY_EAP_PASSWORD: dbus.String,
         SERVICE_PROPERTY_EAP_CA_CERT_PEM: dbus.Array,
+        SERVICE_PROPERTY_CLIENT_CERT_ID: dbus.String,
+        SERVICE_PROPERTY_EAP_KEY_MGMT: dbus.String,
+        SERVICE_PROPERTY_EAP_PIN: dbus.String,
+        SERVICE_PROPERTY_PRIVATE_KEY_ID: dbus.String,
+        SERVICE_PROPERTY_USE_SYSTEM_CAS: dbus.Boolean,
 
         SERVICE_PROPERTY_OPENVPN_CA_CERT_PEM: dbus.Array,
         SERVICE_PROPERTY_OPENVPN_PASSWORD: dbus.String,
diff --git a/server/cros/network/hostap_config.py b/server/cros/network/hostap_config.py
index 473c2c7..e89cfe9 100644
--- a/server/cros/network/hostap_config.py
+++ b/server/cros/network/hostap_config.py
@@ -454,6 +454,11 @@
         """@return int frag threshold value, or None."""
         return self._frag_threshold
 
+    @property
+    def bridge(self):
+        """@return string _bridge value, or None."""
+        return self._bridge
+
 
     def __init__(self, mode=MODE_11B, channel=None, frequency=None,
                  n_capabilities=[], hide_ssid=None, beacon_interval=None,
@@ -467,7 +472,13 @@
                  beacon_footer='',
                  spectrum_mgmt_required=None,
                  scenario_name=None,
-                 min_streams=None):
+                 min_streams=None,
+                 nas_id=None,
+                 mdid=None,
+                 r1kh_id=None,
+                 r0kh=None,
+                 r1kh=None,
+                 bridge=None):
         """Construct a HostapConfig.
 
         You may specify channel or frequency, but not both.  Both options
@@ -501,6 +512,12 @@
         @param scenario_name string to be included in file names, instead
             of the interface name.
         @param min_streams int number of spatial streams required.
+        @param nas_id string for RADIUS messages (needed for 802.11r)
+        @param mdid string used to indicate a group of APs for FT
+        @param r1kh_id string PMK-R1 key holder id for FT
+        @param r0kh string R0KHs in the same mobility domain
+        @param r1kh string R1KHs in the same mobility domain
+        @param bridge string bridge interface
 
         """
         super(HostapConfig, self).__init__()
@@ -570,6 +587,12 @@
         self._spectrum_mgmt_required = spectrum_mgmt_required
         self._scenario_name = scenario_name
         self._min_streams = min_streams
+        self._nas_id = nas_id
+        self._mdid = mdid
+        self._r1kh_id = r1kh_id
+        self._r0kh = r0kh
+        self._r1kh = r1kh
+        self._bridge = bridge
 
 
     def __repr__(self):
@@ -689,6 +712,18 @@
             conf['ieee80211w'] = self._pmf_support
         if self._obss_interval:
             conf['obss_interval'] = self._obss_interval
+        if self._nas_id:
+            conf['nas_identifier'] = self._nas_id
+        if self._mdid:
+            conf['mobility_domain'] = self._mdid
+        if self._r1kh_id:
+            conf['r1_key_holder'] = self._r1kh_id
+        if self._r0kh:
+            conf['r0kh'] = self._r0kh
+        if self._r1kh:
+            conf['r1kh'] = self._r1kh
+        if self._bridge:
+            conf['bridge'] = self._bridge
         conf['interface'] = interface
         conf['ctrl_interface'] = control_interface
         if self._spectrum_mgmt_required:
diff --git a/server/site_linux_router.py b/server/site_linux_router.py
index 9ef78c6..f75eb75 100644
--- a/server/site_linux_router.py
+++ b/server/site_linux_router.py
@@ -450,7 +450,7 @@
         interface = self.hostapd_instances[-1].interface
         self.iw_runner.set_tx_power(interface, 'auto')
         self.set_beacon_footer(interface, configuration.beacon_footer)
-        self.start_local_server(interface)
+        self.start_local_server(interface, bridge=configuration.bridge)
         logging.info('AP configured.')
 
 
@@ -504,6 +504,15 @@
         """
         return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 253))
 
+    def local_bridge_address(self, index):
+        """Get the bridge address for an interface.
+
+        This address is assigned to a local bridge device.
+
+        @param index int describing which local server this is for.
+
+        """
+        return '%d.%d.%d.%d' % (self.SUBNET_PREFIX_OCTETS + (index, 252))
 
     def local_peer_mac_address(self):
         """Get the MAC address of the peer interface.
@@ -550,12 +559,14 @@
     def start_local_server(self,
                            interface,
                            ap_num=None,
-                           server_address_index=None):
+                           server_address_index=None,
+                           bridge=None):
         """Start a local server on an interface.
 
         @param interface string (e.g. wlan0)
         @param ap_num int the ap instance to start the server for
         @param server_address_index int server address index
+        @param bridge string (e.g. br0)
 
         """
         logging.info('Starting up local server...')
@@ -582,6 +593,7 @@
             (server_addr.get_addr_in_block(1),
              server_addr.get_addr_in_block(128)))
         params['interface'] = interface
+        params['bridge'] = bridge
         params['ip_params'] = ('%s broadcast %s dev %s' %
                                (server_addr.netblock,
                                 server_addr.broadcast,
@@ -598,6 +610,12 @@
                         (self.cmd_ip, params['ip_params']))
         self.router.run('%s link set %s up' %
                         (self.cmd_ip, interface))
+        if params['bridge']:
+            bridge_addr = netblock.from_addr(
+                    self.local_bridge_address(server_address_index),
+                    prefix_len=24)
+            self.router.run("ifconfig %s %s" %
+                           (params['bridge'], bridge_addr.netblock))
         self.start_dhcp_server(interface)
 
 
@@ -636,7 +654,7 @@
             'log-dhcp',
             'dhcp-range=%s' % ','.join((server_addr.get_addr_in_block(1),
                                         server_addr.get_addr_in_block(128))),
-            'interface=%s' % params['interface'],
+            'interface=%s' % (params['bridge'] or params['interface']),
             'dhcp-leasefile=%s' % self.dhcpd_leases])
         self.router.run('cat <<EOF >%s\n%s\nEOF\n' %
             (dhcpd_conf_file, dhcp_conf))