From 1df2e18f6108469a46888d6e84b003708fa7eb66 Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Sun, 26 Mar 2017 03:16:57 +0200 Subject: [PATCH] autotests: Add FT roaming test and utility functions This tests the FT-PSK initial Mobility Domain authentication, neighbor reports, scanning on a subset of frequencies and FT transition. --- autotests/testFT-PSK-roam/ft-psk-ccmp-1.conf | 41 ++++++ autotests/testFT-PSK-roam/ft-psk-ccmp-2.conf | 41 ++++++ autotests/testFT-PSK-roam/hw.conf | 7 + autotests/testFT-PSK-roam/test.py | 140 +++++++++++++++++++ autotests/util/hostapd.py | 19 +++ autotests/util/iwd.py | 1 + 6 files changed, 249 insertions(+) create mode 100644 autotests/testFT-PSK-roam/ft-psk-ccmp-1.conf create mode 100644 autotests/testFT-PSK-roam/ft-psk-ccmp-2.conf create mode 100644 autotests/testFT-PSK-roam/hw.conf create mode 100644 autotests/testFT-PSK-roam/test.py diff --git a/autotests/testFT-PSK-roam/ft-psk-ccmp-1.conf b/autotests/testFT-PSK-roam/ft-psk-ccmp-1.conf new file mode 100644 index 00000000..e75aac74 --- /dev/null +++ b/autotests/testFT-PSK-roam/ft-psk-ccmp-1.conf @@ -0,0 +1,41 @@ +hw_mode=g +channel=1 +ssid=TestFT +utf8_ssid=1 +ctrl_interface=/var/run/hostapd + +r1_key_holder=120000000001 +nas_identifier=dummy1 + +wpa=2 +# Can support WPA-PSK and FT-PSK (space separated list) and/or EAP at the same +# time but we want to force FT +wpa_key_mgmt=FT-PSK +wpa_pairwise=CCMP +wpa_passphrase=EasilyGuessedPassword +wpa_ptk_rekey=30 +wpa_group_rekey=80 +ieee80211w=1 +rsn_preauth=1 +rsn_preauth_interfaces=lo +disable_pmksa_caching=0 +# Allow PMK cache to be shared opportunistically among configured interfaces +# and BSSes (i.e., all configurations within a single hostapd process). +okc=1 +mobility_domain=1234 +reassociation_deadline=60000 +r0kh=12:00:00:00:00:01 dummy1 000102030405060708090a0b0c0d0e0f +r0kh=12:00:00:00:00:02 dummy2 000102030405060708090a0b0c0d0e0f +r1kh=12:00:00:00:00:01 00:00:00:00:00:01 000102030405060708090a0b0c0d0e0f +r1kh=12:00:00:00:00:02 00:00:00:00:00:02 000102030405060708090a0b0c0d0e0f +# Push mode only needed for 8021x, not PSK mode since msk already known +pmk_r1_push=0 +# Allow locally generated FT response so we don't have to configure push/pull +# between BSSes running as separate hostapd processes as in the test-runner +# case. Only works with FT-PSK, otherwise brctl needs to be installed and +# CONFIG_BRIDGE enabled in the kernel. +ft_psk_generate_local=1 +ft_over_ds=1 +ap_table_expiration_time=36000 +ap_table_max_size=10 +rrm_neighbor_report=1 diff --git a/autotests/testFT-PSK-roam/ft-psk-ccmp-2.conf b/autotests/testFT-PSK-roam/ft-psk-ccmp-2.conf new file mode 100644 index 00000000..e9ee6a70 --- /dev/null +++ b/autotests/testFT-PSK-roam/ft-psk-ccmp-2.conf @@ -0,0 +1,41 @@ +hw_mode=g +channel=2 +ssid=TestFT +utf8_ssid=1 +ctrl_interface=/var/run/hostapd + +r1_key_holder=120000000002 +nas_identifier=dummy2 + +wpa=2 +# Can support WPA-PSK and FT-PSK (space separated list) and/or EAP at the same +# time but we want to force FT +wpa_key_mgmt=FT-PSK +wpa_pairwise=CCMP +wpa_passphrase=EasilyGuessedPassword +wpa_ptk_rekey=30 +wpa_group_rekey=80 +ieee80211w=1 +rsn_preauth=1 +rsn_preauth_interfaces=lo +disable_pmksa_caching=0 +# Allow PMK cache to be shared opportunistically among configured interfaces +# and BSSes (i.e., all configurations within a single hostapd process). +okc=1 +mobility_domain=1234 +reassociation_deadline=60000 +r0kh=12:00:00:00:00:01 dummy1 000102030405060708090a0b0c0d0e0f +r0kh=12:00:00:00:00:02 dummy2 000102030405060708090a0b0c0d0e0f +r1kh=12:00:00:00:00:01 00:00:00:00:00:01 000102030405060708090a0b0c0d0e0f +r1kh=12:00:00:00:00:02 00:00:00:00:00:02 000102030405060708090a0b0c0d0e0f +# Push mode only needed for 8021x, not PSK mode since msk already known +pmk_r1_push=0 +# Allow locally generated FT response so we don't have to configure push/pull +# between BSSes running as separate hostapd processes as in the test-runner +# case. Only works with FT-PSK, otherwise brctl needs to be installed and +# CONFIG_BRIDGE enabled in the kernel. +ft_psk_generate_local=1 +ft_over_ds=1 +ap_table_expiration_time=36000 +ap_table_max_size=10 +rrm_neighbor_report=1 diff --git a/autotests/testFT-PSK-roam/hw.conf b/autotests/testFT-PSK-roam/hw.conf new file mode 100644 index 00000000..3704213e --- /dev/null +++ b/autotests/testFT-PSK-roam/hw.conf @@ -0,0 +1,7 @@ +[SETUP] +num_radios=3 +max_test_exec_interval_sec=30 + +[HOSTAPD] +rad0=ft-psk-ccmp-1.conf +rad1=ft-psk-ccmp-2.conf diff --git a/autotests/testFT-PSK-roam/test.py b/autotests/testFT-PSK-roam/test.py new file mode 100644 index 00000000..c9a7c2f6 --- /dev/null +++ b/autotests/testFT-PSK-roam/test.py @@ -0,0 +1,140 @@ +#! /usr/bin/python3 + +import unittest +import sys, os + +sys.path.append('../util') +import iwd +from iwd import IWD +from iwd import PSKAgent +from iwd import NetworkType +from hwsim import Hwsim +from hostapd import HostapdCLI +from wiphy import wiphy_map + +class Test(unittest.TestCase): + def test_roam_success(self): + hwsim = Hwsim() + + bss_hostapd = [None, None] + bss_radio = [None, None] + for wname in wiphy_map: + wiphy = wiphy_map[wname] + intf = list(wiphy.values())[0] + if intf.config and '1' in intf.config: + bss_idx = 0 + elif intf.config and '2' in intf.config: + bss_idx = 1 + else: + continue + + for path in hwsim.radios: + radio = hwsim.radios[path] + if radio.name == wname: + break + + bss_hostapd[bss_idx] = HostapdCLI(intf) + bss_radio[bss_idx] = radio + + # Set interface addresses to those expected by hostapd config files + os.system('ifconfig "' + bss_hostapd[0].ifname + + '" down hw ether 12:00:00:00:00:01 up') + os.system('ifconfig "' + bss_hostapd[1].ifname + + '" down hw ether 12:00:00:00:00:02 up') + + bss_hostapd[0].reload() + bss_hostapd[1].reload() + + rule0 = hwsim.rules.create() + rule0.source = bss_radio[0].addresses[0] + rule0.bidirectional = True + + rule1 = hwsim.rules.create() + rule1.source = bss_radio[1].addresses[0] + rule1.bidirectional = True + + # Fill in the neighbor AP tables in both BSSes. By default each + # instance knows only about current BSS, even inside one hostapd + # process. + # FT still works without the neighbor AP table but neighbor reports + # have to be disabled in the .conf files + bss_hostapd[0].set_neighbor('12:00:00:00:00:02', 'TestFT', + '1200000000028f0000005102060603000000') + bss_hostapd[1].set_neighbor('12:00:00:00:00:01', 'TestFT', + '1200000000018f0000005101060603000000') + + wd = IWD() + + psk_agent = PSKAgent("EasilyGuessedPassword") + wd.register_psk_agent(psk_agent) + + device = wd.list_devices()[0]; + + # Check that iwd selects BSS 0 first + rule0.signal = -2500 + rule1.signal = -3500 + + condition = 'not obj.scanning' + wd.wait_for_object_condition(device, condition) + + device.scan() + + condition = 'obj.scanning' + wd.wait_for_object_condition(device, condition) + + condition = 'not obj.scanning' + wd.wait_for_object_condition(device, condition) + + ordered_networks = device.get_ordered_networks() + + self.assertEqual(len(ordered_networks), 1) + ordered_network = ordered_networks[0] + self.assertEqual(ordered_network.name, "TestFT") + self.assertEqual(ordered_network.type, NetworkType.psk) + self.assertEqual(ordered_network.signal_strength, -2500) + + condition = 'not obj.connected' + wd.wait_for_object_condition(ordered_network.network_object, condition) + + self.assertFalse(bss_hostapd[0].list_sta()) + self.assertFalse(bss_hostapd[1].list_sta()) + + ordered_network.network_object.connect() + + condition = 'obj.connected' + wd.wait_for_object_condition(ordered_network.network_object, condition) + + self.assertTrue(bss_hostapd[0].list_sta()) + self.assertFalse(bss_hostapd[1].list_sta()) + + wd.unregister_psk_agent(psk_agent) + + # Check that iwd starts transition to BSS 1 in less than 10 seconds + rule0.signal = -8000 + + condition = 'obj.state == DeviceState.roaming' + wd.wait_for_object_condition(device, condition, 10) + + # Check that iwd is on BSS 1 once out of roaming state and doesn't + # go through 'disconnected', 'autoconnect', 'connecting' in between + condition = 'obj.state != DeviceState.roaming' + wd.wait_for_object_condition(device, condition, 5) + + self.assertEqual(device.state, iwd.DeviceState.connected) + self.assertTrue(bss_hostapd[1].list_sta()) + + device.disconnect() + + condition = 'not obj.connected' + wd.wait_for_object_condition(ordered_network.network_object, condition) + + @classmethod + def setUpClass(cls): + pass + + @classmethod + def tearDownClass(cls): + IWD.clear_storage() + +if __name__ == '__main__': + unittest.main(exit=True) diff --git a/autotests/util/hostapd.py b/autotests/util/hostapd.py index 8d4d7952..a818447f 100644 --- a/autotests/util/hostapd.py +++ b/autotests/util/hostapd.py @@ -21,6 +21,25 @@ class HostapdCLI: def deauthenticate(self, client_address): os.system(self.cmdline + ' deauthenticate ' + client_address) + def reload(self): + # Seemingly all three commands needed for the instance to notice + # interface's address change + cmds = 'reload\ndisable\nenable\n' + proc = os.popen(self.cmdline, mode='w') + lines = proc.write(cmds) + proc.close() + + def list_sta(self): + proc = os.popen(self.cmdline + ' list_sta') + lines = proc.read() + proc.close() + + return [line for line in lines.split('\n') if line] + + def set_neighbor(self, addr, ssid, nr): + os.system(self.cmdline + ' set_neighbor ' + addr + ' ssid=\\""' + ssid + + '"\\" nr=' + nr) + @staticmethod def kill_all(): os.system('killall hostapd') diff --git a/autotests/util/iwd.py b/autotests/util/iwd.py index 3de6490b..6ff8cbd3 100755 --- a/autotests/util/iwd.py +++ b/autotests/util/iwd.py @@ -141,6 +141,7 @@ class DeviceState(Enum): disconnected = 'disconnected' connecting = 'connecting' disconnecting = 'disconnecting' + roaming = 'roaming' def __str__(self): return self.value