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