blob: f7e5ceb34ceee93aeadb8a0eea891e4faf9f81f8 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2022 The Fuchsia Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
A test that saves various networks and verifies the behavior of save, get, and
remove through the ClientController API of WLAN policy.
"""
import logging
from mobly import signals, test_runner
from antlion.controllers.access_point import setup_ap
from antlion.controllers.ap_lib import hostapd_constants, hostapd_security
from antlion.test_utils.wifi import base_test
from antlion.utils import rand_ascii_str, rand_hex_str
PSK_LEN = 64
TIME_WAIT_FOR_DISCONNECT = 30
TIME_WAIT_FOR_CONNECT = 30
STATE_CONNECTED = "Connected"
STATE_CONNECTING = "Connecting"
CONNECTIONS_ENABLED = "ConnectionsEnabled"
CONNECTIONS_DISABLED = "ConnectionsDisabled"
SECURITY_NONE = "none"
WEP = "wep"
WPA = "wpa"
WPA2 = "wpa2"
WPA3 = "wpa3"
CREDENTIAL_TYPE_NONE = "none"
PASSWORD = "password"
PSK = "psk"
CREDENTIAL_VALUE_NONE = ""
class SavedNetworksTest(base_test.WifiBaseTest):
"""WLAN policy commands test class.
Test Bed Requirement:
* One or more Fuchsia devices
* One Access Point
"""
def setup_class(self):
super().setup_class()
self.log = logging.getLogger()
# Keep track of whether we have started an access point in a test
if len(self.fuchsia_devices) < 1:
raise EnvironmentError("No Fuchsia devices found.")
for fd in self.fuchsia_devices:
fd.configure_wlan(
association_mechanism="policy", preserve_saved_networks=True
)
def setup_test(self):
for fd in self.fuchsia_devices:
if not fd.wlan_policy_controller.remove_all_networks():
raise EnvironmentError("Failed to remove all networks in setup")
self.access_points[0].stop_all_aps()
def teardown_class(self):
for fd in self.fuchsia_devices:
fd.wlan_policy_controller.remove_all_networks()
self.access_points[0].stop_all_aps()
def save_bad_network(self, fd, ssid, security_type, password=""):
"""Saves a network as specified on the given device and verify that we
Args:
fd: The Fuchsia device to save the network on
ssid: The SSID or name of the network to save.
security_type: The security type to save the network as, ie "none",
"wep", "wpa", "wpa2", or "wpa3"
password: The password to save for the network. Empty string represents
no password, and PSK should be provided as 64 character hex string.
"""
if fd.wlan_policy_controller.save_network(
ssid, security_type, password=password
):
self.log.info(
"Attempting to save bad network config %s did not give an error" % ssid
)
raise signals.TestFailure("Failed to get error saving bad network")
def check_get_saved_network(
self, fd, ssid, security_type, credential_type, credential_value
):
"""Verify that get saved networks sees the single specified network. Used
for the tests that save and get a single network. Maps security types of
expected and actual to be case insensitive.
Args:
fd: Fuchsia device to run on.
ssid: The name of the network to check for.
security_type: The security of the network, ie "none", "wep", "wpa",
"wpa2", or "wpa3".
credential_type: The type of credential saved for the network, ie
"none", "password", or "psk".
credential_value: The actual credential, or "" if there is no credential.
"""
expected_networks = [
{
"ssid": ssid,
"security_type": security_type,
"credential_type": credential_type,
"credential_value": credential_value,
}
]
self.check_saved_networks(fd, expected_networks)
def check_saved_networks(self, fd, expected_networks):
"""Verify that the saved networks we get from the device match the provided
list of networks.
Args:
fd: The Fuchsia device to run on.
expected_networks: The list of networks we expect to get from the device,
unordered and in the same format as we would get:
[{"credential_type": _, "credential_value": _,
"security_type": _, "ssid": _}, ...] There should be
no duplicates in expected networks.
"""
actual_networks = list(
map(self.lower_case_network, fd.wlan_policy_controller.get_saved_networks())
)
expected_networks = list(
map(self.lower_case_network, fd.wlan_policy_controller.get_saved_networks())
)
if len(actual_networks) != len(expected_networks):
self.log.info(
"Number of expected saved networks does not match the actual number."
"Expected: %d, actual: %d"
% (len(actual_networks), len(expected_networks))
)
raise signals.TestFailure(
"Failed to get the expected number of saved networks"
)
for network in actual_networks:
if network not in expected_networks:
self.log.info(
"Actual and expected networks do not match. Actual: %s,\n"
"Expected: %s" % (actual_networks, expected_networks)
)
raise signals.TestFailure("Got an unexpected saved network")
def lower_case_network(self, network):
if "security_type" not in network:
self.log.error("Missing security type in network %s" % network)
raise signals.TestFailure("Network is missing security type")
if "credential_type" not in network:
self.log.error("Missing credential type in network %s" % network)
raise signals.TestFailure("Network is missing credential type")
{"ssid": network["ssid"], "security_type": network["security_type"]}
def save_and_check_network(self, ssid, security_type, password=""):
"""Perform a test for saving, getting, and removing a single network on each
device.
Args:
ssid: The network name to use.
security_type: The security of the network as a string, ie "none",
"wep", "wpa", "wpa2", or "wpa3" (case insensitive)
password: The password of the network. PSK should be given as 64
hexadecimal characters and none should be an empty string.
"""
for fd in self.fuchsia_devices:
if not fd.wlan_policy_controller.save_network(
ssid, security_type, password=password
):
raise signals.TestFailure("Failed to save network")
self.check_get_saved_network(
fd, ssid, security_type, self.credentialType(password), password
)
def start_ap(self, ssid, security_type, password=None, hidden=False):
"""Starts an access point.
Args:
ssid: the SSID of the network to broadcast
security_type: the security type of the network to be broadcasted. This can be
None, "wep" "wpa", "wpa2", or "wpa3" (or from hostapd_constants.py)
password: the password to connect to the broadcasted network. The password is ignored
if security type is none.
"""
# Put together the security configuration of the network to be
# broadcasted. Open networks are represented by no security.
if security_type == None or security_type.upper() == SECURITY_NONE:
security = None
else:
security = hostapd_security.Security(
security_mode=security_type, password=password
)
if len(self.access_points) > 0:
# Create an AP with default values other than the specified values.
setup_ap(
self.access_points[0],
"whirlwind",
hostapd_constants.AP_DEFAULT_CHANNEL_5G,
ssid,
security=security,
)
else:
self.log.error("No access point available for test, please check config")
raise EnvironmentError("Failed to set up AP for test")
def credentialType(self, credentialValue):
"""Returns the type of the credential to compare against values reported"""
if len(credentialValue) == PSK_LEN:
return PSK
elif len(credentialValue) == 0:
return "none"
else:
return PASSWORD
def same_network_identifier(self, net_id, ssid, security_type):
"""Returns true if the network id is made of the given ssid and security
type, and false otherwise. Security type check is case insensitive.
"""
return (
net_id["ssid"] == ssid and net_id["type_"].upper() == security_type.upper()
)
"""Tests"""
def test_open_network_with_password(self):
for fd in self.fuchsia_devices:
# Save an open network with a password and verify that it fails to
# save.
self.save_bad_network(
fd, rand_ascii_str(10), SECURITY_NONE, rand_ascii_str(8)
)
self.check_saved_networks(fd, {})
def test_open_network(self):
ssid = rand_ascii_str(10)
self.save_and_check_network(ssid, SECURITY_NONE)
def test_network_with_psk(self):
ssid = rand_ascii_str(11)
# PSK are translated from hex to bytes when saved, and when returned
# by get_saved_networks it will be lower case.
psk = rand_hex_str(PSK_LEN).lower()
self.save_and_check_network(ssid, WPA2, psk)
def test_wep_network(self):
ssid = rand_ascii_str(12)
password = rand_ascii_str(13)
self.save_and_check_network(ssid, WEP, password)
def test_wpa2_network(self):
ssid = rand_ascii_str(9)
password = rand_ascii_str(15)
self.save_and_check_network(ssid, WPA2, password)
def test_wpa_network(self):
ssid = rand_ascii_str(16)
password = rand_ascii_str(9)
self.save_and_check_network(ssid, WPA, password)
def test_wpa3_network(self):
ssid = rand_ascii_str(9)
password = rand_ascii_str(15)
self.save_and_check_network(ssid, WPA3, password)
def test_save_network_persists(self):
ssid = rand_ascii_str(10)
security = WPA2
password = rand_ascii_str(10)
for fd in self.fuchsia_devices:
if not fd.wlan_policy_controller.save_network(
ssid, security, password=password
):
raise signals.TestFailure("Failed to save network")
# Reboot the device. The network should be persistently saved
# before the command is completed.
fd.reboot()
self.check_get_saved_network(fd, ssid, security, PASSWORD, password)
def test_same_ssid_diff_security(self):
for fd in self.fuchsia_devices:
saved_networks = fd.wlan_policy_controller.get_saved_networks()
ssid = rand_ascii_str(19)
password = rand_ascii_str(12)
if not fd.wlan_policy_controller.save_network(
ssid, WPA2, password=password
):
raise signals.TestFailure("Failed to save network")
saved_networks.append(
{
"ssid": ssid,
"security_type": WPA2,
"credential_type": PASSWORD,
"credential_value": password,
}
)
if not fd.wlan_policy_controller.save_network(ssid, SECURITY_NONE):
raise signals.TestFailure("Failed to save network")
saved_networks.append(
{
"ssid": ssid,
"security_type": SECURITY_NONE,
"credential_type": CREDENTIAL_TYPE_NONE,
"credential_value": CREDENTIAL_VALUE_NONE,
}
)
actual_networks = fd.wlan_policy_controller.get_saved_networks()
# Both should be saved and present in network store since the have
# different security types and therefore different network identifiers.
self.check_saved_networks(fd, actual_networks)
def test_remove_disconnects(self):
# If we save, connect to, then remove the network while still connected
# to it, we expect the network will disconnect. This test requires a
# wpa2 network in the test config. Remove all other networks first so
# that we can't auto connect to them
ssid = rand_ascii_str(10)
security = WPA2
password = rand_ascii_str(10)
self.start_ap(ssid, security, password)
for fd in self.fuchsia_devices:
fd.wlan_policy_controller.wait_for_no_connections()
if not fd.wlan_policy_controller.save_and_connect(ssid, security, password):
raise signals.TestFailure("Failed to saved and connect to network")
if (
not fd.wlan_policy_controller.remove_all_networks_and_wait_for_no_connections()
):
raise signals.TestFailure("Failed to disconnect from removed network")
def test_auto_connect_open(self):
# Start up AP with an open network with a random SSID
ssid = rand_ascii_str(10)
self.start_ap(ssid, None)
for fd in self.fuchsia_devices:
fd.wlan_policy_controller.wait_for_no_connections()
# Save the network and make sure that we see the device auto connect to it.
security = SECURITY_NONE
password = CREDENTIAL_VALUE_NONE
if not fd.wlan_policy_controller.save_network(
ssid, security, password=password
):
raise signals.TestFailure("Failed to save network")
if not fd.wlan_policy_controller.wait_for_connect(
ssid, security, timeout=TIME_WAIT_FOR_CONNECT
):
raise signals.TestFailure("Failed to connect to network")
def test_auto_connect_wpa3(self):
# Start up AP with an open network with a random SSID
ssid = rand_ascii_str(10)
security = WPA3
password = rand_ascii_str(10)
self.start_ap(ssid, security, password)
for fd in self.fuchsia_devices:
fd.wlan_policy_controller.wait_for_no_connections()
# Save the network and make sure that we see the device auto connect to it.
if not fd.wlan_policy_controller.save_network(
ssid, security, password=password
):
raise signals.TestFailure("Failed to save network")
if not fd.wlan_policy_controller.wait_for_connect(
ssid, security, timeout=TIME_WAIT_FOR_CONNECT
):
raise signals.TestFailure("Failed to connect to network")
if __name__ == "__main__":
test_runner.main()