1#!/usr/bin/env python3 2# 3# Copyright 2020 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17from mobly import signals 18import multiprocessing as mp 19import time 20 21from acts import utils 22from acts import asserts 23from acts.base_test import BaseTestClass 24from acts.controllers import iperf_server 25from acts.controllers import iperf_client 26from acts.controllers.ap_lib import hostapd_constants 27from acts.controllers.ap_lib import hostapd_security 28from acts.test_utils.abstract_devices.utils_lib import wlan_utils 29from acts.test_utils.abstract_devices.wlan_device import create_wlan_device 30from acts.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap_and_associate 31from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest 32 33ANDROID_DEFAULT_WLAN_PORT = 'wlan0' 34CONNECTIVITY_MODE_LOCAL = 'local_only' 35CONNECTIVITY_MODE_UNRESTRICTED = 'unrestricted' 36DEFAULT_IPERF_PORT = 5201 37INTERFACE_ROLE_AP = 'Ap' 38INTERFACE_ROLE_CLIENT = 'Client' 39INTERFACE_ROLES = {INTERFACE_ROLE_AP, INTERFACE_ROLE_CLIENT} 40OPERATING_BAND_2G = 'only_2_4_ghz' 41OPERATING_BAND_5G = 'only_5_ghz' 42OPERATING_BAND_ANY = 'any' 43SECURITY_OPEN = 'none' 44SECURITY_WEP = 'wep' 45SECURITY_WPA = 'wpa' 46SECURITY_WPA2 = 'wpa2' 47SECURITY_WPA3 = 'wpa3' 48TEST_TYPE_ASSOCIATE_ONLY = 'associate_only' 49TEST_TYPE_ASSOCIATE_AND_PING = 'associate_and_ping' 50TEST_TYPE_ASSOCIATE_AND_PASS_TRAFFIC = 'associate_and_pass_traffic' 51TEST_TYPES = { 52 TEST_TYPE_ASSOCIATE_ONLY, TEST_TYPE_ASSOCIATE_AND_PING, 53 TEST_TYPE_ASSOCIATE_AND_PASS_TRAFFIC 54} 55 56 57def generate_test_name(settings): 58 """Generates a string test name based on the channel and band. 59 60 Args: 61 settings A dict with the soft ap config parameteres. 62 63 Returns: 64 A string test case name. 65 """ 66 return 'test_soft_ap_band_%s_security_%s_mode_%s_loops_%s' % ( 67 settings['operating_band'], settings['security_type'], 68 settings['connectivity_mode'], settings['reconnect_loops']) 69 70 71class SoftApClient(object): 72 def __init__(self, device): 73 self.w_device = create_wlan_device(device) 74 self.ip_client = iperf_client.IPerfClientOverAdb(device.serial) 75 76 77class WlanInterface(object): 78 def __init__(self): 79 self.name = None 80 self.mac_addr = None 81 self.ipv4 = None 82 83 84class SoftApTest(BaseTestClass): 85 """Tests for Fuchsia SoftAP 86 87 Testbed requirement: 88 * One Fuchsia Device 89 * One Client (Android) Device 90 """ 91 def setup_class(self): 92 self.dut = create_wlan_device(self.fuchsia_devices[0]) 93 self.dut.device.netstack_lib.init() 94 95 # TODO(fxb/51313): Add in device agnosticity for clients 96 self.clients = [] 97 for device in self.android_devices: 98 self.clients.append(SoftApClient(device)) 99 self.primary_client = self.clients[0] 100 101 self.iperf_server_config = { 102 'user': self.dut.device.ssh_username, 103 'host': self.dut.device.ip, 104 'ssh_config': self.dut.device.ssh_config 105 } 106 self.iperf_server = iperf_server.IPerfServerOverSsh( 107 self.iperf_server_config, DEFAULT_IPERF_PORT, use_killall=True) 108 self.iperf_server.start() 109 110 try: 111 self.access_point = self.access_points[0] 112 except AttributeError: 113 self.access_point = None 114 115 def teardown_class(self): 116 # Because this is using killall, it will stop all iperf processes, 117 # making it a great teardown cleanup 118 self.iperf_server.stop() 119 120 def setup_test(self): 121 for ad in self.android_devices: 122 ad.droid.wakeLockAcquireBright() 123 ad.droid.wakeUpNow() 124 for client in self.clients: 125 client.w_device.disconnect() 126 client.w_device.reset_wifi() 127 client.w_device.wifi_toggle_state(True) 128 self.dut.device.wlan_ap_policy_lib.wlanStopAllAccessPoint() 129 if self.access_point: 130 self.access_point.stop_all_aps() 131 self.dut.disconnect() 132 133 def teardown_test(self): 134 for client in self.clients: 135 client.w_device.disconnect() 136 for ad in self.android_devices: 137 ad.droid.wakeLockRelease() 138 ad.droid.goToSleepNow() 139 self.dut.device.wlan_ap_policy_lib.wlanStopAllAccessPoint() 140 if self.access_point: 141 self.access_point.stop_all_aps() 142 self.dut.disconnect() 143 144 def on_fail(self, test_name, begin_time): 145 self.dut.take_bug_report(test_name, begin_time) 146 self.dut.get_log(test_name, begin_time) 147 148 def start_soft_ap(self, settings): 149 """Starts a softAP on Fuchsia device. 150 151 Args: 152 settings: a dict containing softAP configuration params 153 ssid: string, SSID of softAP network 154 security_type: string, security type of softAP network 155 - 'none', 'wep', 'wpa', 'wpa2', 'wpa3' 156 password: string, password if applicable 157 connectivity_mode: string, connecitivity_mode for softAP 158 - 'local_only', 'unrestricted' 159 operating_band: string, band for softAP network 160 - 'any', 'only_5_ghz', 'only_2_4_ghz' 161 """ 162 ssid = settings['ssid'] 163 security_type = settings['security_type'] 164 password = settings.get('password', '') 165 connectivity_mode = settings['connectivity_mode'] 166 operating_band = settings['operating_band'] 167 168 self.log.info('Attempting to start SoftAP on DUT with settings: %s' % 169 settings) 170 171 response = self.dut.device.wlan_ap_policy_lib.wlanStartAccessPoint( 172 ssid, security_type, password, connectivity_mode, operating_band) 173 if response.get('error'): 174 raise EnvironmentError('Failed to setup SoftAP. Err: %s' % 175 response['error']) 176 177 self.log.info('SoftAp network (%s) is up.' % ssid) 178 179 def associate_with_soft_ap(self, w_device, settings): 180 """Associates client device with softAP on Fuchsia device. 181 182 Args: 183 w_device: wlan_device to associate with the softAP 184 settings: a dict containing softAP config params (see start_soft_ap) 185 for details 186 187 Raises: 188 TestFailure, if association fails 189 """ 190 self.log.info( 191 'Attempting to associate client %s with SoftAP on FuchsiaDevice ' 192 '(%s).' % (w_device.device.serial, self.dut.device.ip)) 193 194 check_connectivity = settings[ 195 'connectivity_mode'] == CONNECTIVITY_MODE_UNRESTRICTED 196 associated = wlan_utils.associate( 197 w_device, 198 settings['ssid'], 199 password=settings.get('password'), 200 check_connectivity=check_connectivity) 201 202 if not associated: 203 asserts.fail('Failed to connect to SoftAP.') 204 205 self.log.info('Client successfully associated with SoftAP.') 206 207 def disconnect_from_soft_ap(self, w_device): 208 """Disconnects client device from SoftAP. 209 210 Args: 211 w_device: wlan_device to disconnect from SoftAP 212 """ 213 self.log.info('Disconnecting device %s from SoftAP.' % 214 w_device.device.serial) 215 w_device.disconnect() 216 217 def get_dut_interface_by_role(self, role): 218 """Retrieves interface information from the FuchsiaDevice DUT based 219 on the role. 220 221 Args: 222 role: string, the role of the interface to seek (e.g. Client or Ap) 223 224 Raises: 225 ConnectionError, if SL4F calls fail 226 AttributeError, if device does not have an interface matching role 227 228 Returns: 229 WlanInterface object representing the interface matching role 230 """ 231 if not role in INTERFACE_ROLES: 232 raise ValueError('Unsupported interface role %s' % role) 233 234 self.log.info('Getting %s interface info from DUT.' % role) 235 interface = WlanInterface() 236 237 # Determine WLAN interface with role 238 wlan_ifaces = self.dut.device.wlan_lib.wlanGetIfaceIdList() 239 if wlan_ifaces.get('error'): 240 raise ConnectionError('Failed to get wlan interface IDs: %s' % 241 wlan_ifaces['error']) 242 243 for wlan_iface in wlan_ifaces['result']: 244 iface_info = self.dut.device.wlan_lib.wlanQueryInterface( 245 wlan_iface) 246 if iface_info.get('error'): 247 raise ConnectionError('Failed to query wlan iface: %s' % 248 iface_info['error']) 249 250 if iface_info['result']['role'] == role: 251 interface.mac_addr = iface_info['result']['mac_addr'] 252 break 253 else: 254 raise AttributeError('Failed to find a %s interface.' % role) 255 256 # Retrieve interface info from netstack 257 netstack_ifaces = self.dut.device.netstack_lib.netstackListInterfaces() 258 if netstack_ifaces.get('error'): 259 raise ConnectionError('Failed to get netstack ifaces: %s' % 260 netstack_ifaces['error']) 261 262 # TODO(fxb/51315): Once subnet information is available in 263 # netstackListInterfaces store it to verify the clients ip address. 264 for netstack_iface in netstack_ifaces['result']: 265 if netstack_iface['mac'] == interface.mac_addr: 266 interface.name = netstack_iface['name'] 267 if len(netstack_iface['ipv4_addresses']) > 0: 268 interface.ipv4 = '.'.join( 269 str(byte) 270 for byte in netstack_iface['ipv4_addresses'][0]) 271 else: 272 interface.ipv4 = self.wait_for_ipv4_address( 273 self.dut, interface.name) 274 self.log.info('DUT %s interface: %s. Has ipv4 address %s' % 275 (role, interface.name, interface.ipv4)) 276 return interface 277 278 def wait_for_ipv4_address(self, w_device, interface_name, timeout=10): 279 # TODO(fxb/51315): Once subnet information is available in netstack, add a 280 # subnet verification here. 281 """ Waits for interface on a wlan_device to get an ipv4 address. 282 283 Args: 284 w_device: wlan_device to check interface 285 interface_name: name of the interface to check 286 timeout: seconds to wait before raising an error 287 288 Raises: 289 ValueError, if interface does not have an ipv4 address after timeout 290 """ 291 self.log.info( 292 'Checking if device %s interface %s has an ipv4 address. ' 293 'Will retrying for %s seconds.' % 294 (w_device.device.serial, interface_name, timeout)) 295 296 end_time = time.time() + timeout 297 while time.time() < end_time: 298 ips = w_device.get_interface_ip_addresses(interface_name) 299 if len(ips['ipv4_private']) > 0: 300 self.log.info('Device %s interface %s has ipv4 address %s' % 301 (w_device.device.serial, interface_name, 302 ips['ipv4_private'][0])) 303 return ips['ipv4_private'][0] 304 else: 305 time.sleep(1) 306 raise ValueError( 307 'After %s seconds, device %s still doesn not have an ipv4 address ' 308 'on interface %s.' % 309 (timeout, w_device.device.serial, interface_name)) 310 311 def verify_ping(self, w_device, dest_ip): 312 """ Verify wlan_device can ping a destination ip. 313 314 Args: 315 w_device: wlan_device to initiate ping 316 dest_ip: ip to ping from wlan_device 317 318 Raises: 319 TestFailure, if ping fails 320 """ 321 self.log.info('Attempting to ping from device %s to dest ip %s' % 322 (w_device.device.serial, dest_ip)) 323 if not w_device.ping(dest_ip): 324 asserts.fail('Device %s could not ping dest ip %s' % 325 (w_device.device.serial, dest_ip)) 326 self.log.info('Ping successful.') 327 328 def run_iperf_traffic(self, ip_client, server_address, server_port=5201): 329 """Runs traffic between client and ap an verifies throughput. 330 331 Args: 332 ip_client: iperf client to use 333 server_address: ipv4 address of the iperf server to use 334 server_port: port of the iperf server 335 336 Raises: 337 TestFailure, if no traffic passes in either direction 338 """ 339 ip_client_identifier = self.get_iperf_client_identifier(ip_client) 340 self.log.info( 341 'Running traffic from iperf client %s to iperf server %s.' % 342 (ip_client_identifier, server_address)) 343 client_to_ap_path = ip_client.start( 344 server_address, '-i 1 -t 10 -J -p %s' % server_port, 345 'client_to_soft_ap') 346 347 self.log.info( 348 'Running traffic from iperf server %s to iperf client %s.' % 349 (server_address, ip_client_identifier)) 350 ap_to_client_path = ip_client.start( 351 server_address, '-i 1 -t 10 -R -J -p %s' % server_port, 352 'soft_ap_to_client') 353 self.log.info('Getting iperf results') 354 355 client_to_ap_result = iperf_server.IPerfResult(client_to_ap_path) 356 ap_to_client_result = iperf_server.IPerfResult(ap_to_client_path) 357 358 if (not client_to_ap_result.avg_receive_rate): 359 asserts.fail( 360 'Failed to pass traffic from iperf client %s to iperf server %s.' 361 % (ip_client_identifier, server_address)) 362 363 self.log.info( 364 'Passed traffic from iperf client %s to iperf server %s with avg ' 365 'rate of %s MB/s.' % (ip_client_identifier, server_address, 366 client_to_ap_result.avg_receive_rate)) 367 368 if (not ap_to_client_result.avg_receive_rate): 369 asserts.fail( 370 'Failed to pass traffic from iperf server %s to iperf client %s.' 371 % (server_address, ip_client_identifier)) 372 373 self.log.info( 374 'Passed traffic from iperf server %s to iperf client %s with avg ' 375 'rate of %s MB/s.' % (server_address, ip_client_identifier, 376 ap_to_client_result.avg_receive_rate)) 377 378 def run_iperf_traffic_parallel_process(self, 379 ip_client, 380 server_address, 381 error_queue, 382 server_port=5201): 383 """ Executes run_iperf_traffic using a queue to capture errors. Used 384 when running iperf in a parallel process. 385 386 Args: 387 ip_client: iperf client to use 388 server_address: ipv4 address of the iperf server to use 389 error_queue: multiprocessing queue to capture errors 390 server_port: port of the iperf server 391 """ 392 try: 393 self.run_iperf_traffic(ip_client, 394 server_address, 395 server_port=server_port) 396 except Exception as err: 397 error_queue.put('In iperf process from %s to %s: %s' % 398 (self.get_iperf_client_identifier(ip_client), 399 server_address, err)) 400 401 def get_iperf_client_identifier(self, ip_client): 402 """ Retrieves an indentifer string from iperf client, for logging. 403 404 Args: 405 ip_client: iperf client to grab identifier from 406 """ 407 if type(ip_client) == iperf_client.IPerfClientOverAdb: 408 return ip_client._android_device_or_serial 409 return ip_client._ssh_settings.hostname 410 411 def run_config_stress_test(self, settings): 412 """Runs test based on config parameters. 413 414 Args: 415 settings: test configuration settings, see 416 test_soft_ap_stress_from_config for details 417 """ 418 client = settings['client'] 419 test_type = settings['test_type'] 420 if not test_type in TEST_TYPES: 421 raise ValueError('Unrecognized test type %s' % test_type) 422 reconnect_loops = settings['reconnect_loops'] 423 self.log.info('Running test type %s in loop %s times' % 424 (test_type, reconnect_loops)) 425 426 self.start_soft_ap(settings) 427 428 passed_count = 0 429 for run in range(reconnect_loops): 430 try: 431 # Associate with SoftAp 432 self.log.info('Starting SoftApTest run %s' % str(run + 1)) 433 self.associate_with_soft_ap(client.w_device, settings) 434 435 if test_type != TEST_TYPE_ASSOCIATE_ONLY: 436 # Verify client and SoftAP can ping 437 dut_ap_interface = self.get_dut_interface_by_role( 438 INTERFACE_ROLE_AP) 439 client_ipv4 = self.wait_for_ipv4_address( 440 client.w_device, ANDROID_DEFAULT_WLAN_PORT) 441 self.verify_ping(client.w_device, dut_ap_interface.ipv4) 442 self.verify_ping(self.dut, client_ipv4) 443 444 if test_type != TEST_TYPE_ASSOCIATE_AND_PING: 445 # Run traffic between client and SoftAp 446 self.run_iperf_traffic(client.ip_client, 447 dut_ap_interface.ipv4) 448 # Disconnect 449 self.disconnect_from_soft_ap(client.w_device) 450 451 except signals.TestFailure as err: 452 self.log.error('SoftApTest run %s failed. Err: %s' % 453 (str(run + 1), err.details)) 454 else: 455 self.log.info('SoftApTest run %s successful.' % run) 456 passed_count += 1 457 458 if passed_count < reconnect_loops: 459 asserts.fail('SoftAp reconnect test passed on %s/%s runs.' % 460 (passed_count, reconnect_loops)) 461 462 asserts.explicit_pass('SoftAp reconnect test passed on %s/%s runs.' % 463 (passed_count, reconnect_loops)) 464 465 # Test helper functions 466 def verify_soft_ap_associate_only(self, client, settings): 467 self.start_soft_ap(settings) 468 self.associate_with_soft_ap(client.w_device, settings) 469 470 def verify_soft_ap_associate_and_ping(self, client, settings): 471 self.start_soft_ap(settings) 472 self.associate_with_soft_ap(client.w_device, settings) 473 dut_ap_interface = self.get_dut_interface_by_role(INTERFACE_ROLE_AP) 474 client_ipv4 = self.wait_for_ipv4_address(self.primary_client.w_device, 475 ANDROID_DEFAULT_WLAN_PORT) 476 self.verify_ping(client.w_device, dut_ap_interface.ipv4) 477 self.verify_ping(self.dut, client_ipv4) 478 479 def verify_soft_ap_associate_and_pass_traffic(self, client, settings): 480 self.start_soft_ap(settings) 481 self.associate_with_soft_ap(client.w_device, settings) 482 dut_ap_interface = self.get_dut_interface_by_role(INTERFACE_ROLE_AP) 483 client_ipv4 = self.wait_for_ipv4_address(self.primary_client.w_device, 484 ANDROID_DEFAULT_WLAN_PORT) 485 self.verify_ping(client.w_device, dut_ap_interface.ipv4) 486 self.verify_ping(self.dut, client_ipv4) 487 self.run_iperf_traffic(client.ip_client, dut_ap_interface.ipv4) 488 489 490# Test Cases 491 492 def test_soft_ap_2g_open_local(self): 493 self.verify_soft_ap_associate_and_pass_traffic( 494 self.primary_client, { 495 'ssid': utils.rand_ascii_str( 496 hostapd_constants.AP_SSID_LENGTH_2G), 497 'security_type': SECURITY_OPEN, 498 'connectivity_mode': CONNECTIVITY_MODE_LOCAL, 499 'operating_band': OPERATING_BAND_2G 500 }) 501 502 def test_soft_ap_5g_open_local(self): 503 self.verify_soft_ap_associate_and_pass_traffic( 504 self.primary_client, { 505 'ssid': utils.rand_ascii_str( 506 hostapd_constants.AP_SSID_LENGTH_5G), 507 'security_type': SECURITY_OPEN, 508 'connectivity_mode': CONNECTIVITY_MODE_LOCAL, 509 'operating_band': OPERATING_BAND_5G 510 }) 511 512 def test_soft_ap_any_open_local(self): 513 self.verify_soft_ap_associate_and_pass_traffic( 514 self.primary_client, { 515 'ssid': utils.rand_ascii_str( 516 hostapd_constants.AP_SSID_LENGTH_5G), 517 'security_type': SECURITY_OPEN, 518 'connectivity_mode': CONNECTIVITY_MODE_LOCAL, 519 'operating_band': OPERATING_BAND_ANY 520 }) 521 522 def test_soft_ap_2g_wep_local(self): 523 self.verify_soft_ap_associate_and_pass_traffic( 524 self.primary_client, { 525 'ssid': 526 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G), 527 'security_type': 528 SECURITY_WEP, 529 'password': 530 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 531 'connectivity_mode': 532 CONNECTIVITY_MODE_LOCAL, 533 'operating_band': 534 OPERATING_BAND_2G 535 }) 536 537 def test_soft_ap_5g_wep_local(self): 538 self.verify_soft_ap_associate_and_pass_traffic( 539 self.primary_client, { 540 'ssid': 541 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 542 'security_type': 543 SECURITY_WEP, 544 'password': 545 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 546 'connectivity_mode': 547 CONNECTIVITY_MODE_LOCAL, 548 'operating_band': 549 OPERATING_BAND_5G 550 }) 551 552 def test_soft_ap_any_wep_local(self): 553 self.verify_soft_ap_associate_and_pass_traffic( 554 self.primary_client, { 555 'ssid': 556 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 557 'security_type': 558 SECURITY_WEP, 559 'password': 560 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 561 'connectivity_mode': 562 CONNECTIVITY_MODE_LOCAL, 563 'operating_band': 564 OPERATING_BAND_ANY 565 }) 566 567 def test_soft_ap_2g_wpa_local(self): 568 self.verify_soft_ap_associate_and_pass_traffic( 569 self.primary_client, { 570 'ssid': 571 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G), 572 'security_type': 573 SECURITY_WPA, 574 'password': 575 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 576 'connectivity_mode': 577 CONNECTIVITY_MODE_LOCAL, 578 'operating_band': 579 OPERATING_BAND_2G 580 }) 581 582 def test_soft_ap_5g_wpa_local(self): 583 self.verify_soft_ap_associate_and_pass_traffic( 584 self.primary_client, { 585 'ssid': 586 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 587 'security_type': 588 SECURITY_WPA, 589 'password': 590 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 591 'connectivity_mode': 592 CONNECTIVITY_MODE_LOCAL, 593 'operating_band': 594 OPERATING_BAND_5G 595 }) 596 597 def test_soft_ap_any_wpa_local(self): 598 self.verify_soft_ap_associate_and_pass_traffic( 599 self.primary_client, { 600 'ssid': 601 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 602 'security_type': 603 SECURITY_WPA, 604 'password': 605 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 606 'connectivity_mode': 607 CONNECTIVITY_MODE_LOCAL, 608 'operating_band': 609 OPERATING_BAND_ANY 610 }) 611 612 def test_soft_ap_2g_wpa2_local(self): 613 self.verify_soft_ap_associate_and_pass_traffic( 614 self.primary_client, { 615 'ssid': 616 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G), 617 'security_type': 618 SECURITY_WPA2, 619 'password': 620 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 621 'connectivity_mode': 622 CONNECTIVITY_MODE_LOCAL, 623 'operating_band': 624 OPERATING_BAND_2G 625 }) 626 627 def test_soft_ap_5g_wpa2_local(self): 628 self.verify_soft_ap_associate_and_pass_traffic( 629 self.primary_client, { 630 'ssid': 631 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 632 'security_type': 633 SECURITY_WPA2, 634 'password': 635 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 636 'connectivity_mode': 637 CONNECTIVITY_MODE_LOCAL, 638 'operating_band': 639 OPERATING_BAND_5G 640 }) 641 642 def test_soft_ap_any_wpa2_local(self): 643 self.verify_soft_ap_associate_and_pass_traffic( 644 self.primary_client, { 645 'ssid': 646 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 647 'security_type': 648 SECURITY_WPA2, 649 'password': 650 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 651 'connectivity_mode': 652 CONNECTIVITY_MODE_LOCAL, 653 'operating_band': 654 OPERATING_BAND_ANY 655 }) 656 657 def test_soft_ap_2g_wpa3_local(self): 658 self.verify_soft_ap_associate_and_pass_traffic( 659 self.primary_client, { 660 'ssid': 661 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G), 662 'security_type': 663 SECURITY_WPA3, 664 'password': 665 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 666 'connectivity_mode': 667 CONNECTIVITY_MODE_LOCAL, 668 'operating_band': 669 OPERATING_BAND_2G 670 }) 671 672 def test_soft_ap_5g_wpa3_local(self): 673 self.verify_soft_ap_associate_and_pass_traffic( 674 self.primary_client, { 675 'ssid': 676 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 677 'security_type': 678 SECURITY_WPA3, 679 'password': 680 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 681 'connectivity_mode': 682 CONNECTIVITY_MODE_LOCAL, 683 'operating_band': 684 OPERATING_BAND_ANY 685 }) 686 687 def test_soft_ap_any_wpa3_local(self): 688 self.verify_soft_ap_associate_and_pass_traffic( 689 self.primary_client, { 690 'ssid': 691 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 692 'security_type': 693 SECURITY_WPA3, 694 'password': 695 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 696 'connectivity_mode': 697 CONNECTIVITY_MODE_LOCAL, 698 'operating_band': 699 OPERATING_BAND_ANY 700 }) 701 702 def test_soft_ap_2g_open_unrestricted(self): 703 self.verify_soft_ap_associate_and_pass_traffic( 704 self.primary_client, { 705 'ssid': utils.rand_ascii_str( 706 hostapd_constants.AP_SSID_LENGTH_2G), 707 'security_type': SECURITY_OPEN, 708 'connectivity_mode': CONNECTIVITY_MODE_UNRESTRICTED, 709 'operating_band': OPERATING_BAND_2G 710 }) 711 712 def test_soft_ap_5g_open_unrestricted(self): 713 self.verify_soft_ap_associate_and_pass_traffic( 714 self.primary_client, { 715 'ssid': utils.rand_ascii_str( 716 hostapd_constants.AP_SSID_LENGTH_5G), 717 'security_type': SECURITY_OPEN, 718 'connectivity_mode': CONNECTIVITY_MODE_UNRESTRICTED, 719 'operating_band': OPERATING_BAND_5G 720 }) 721 722 def test_soft_ap_any_open_unrestricted(self): 723 self.verify_soft_ap_associate_and_pass_traffic( 724 self.primary_client, { 725 'ssid': utils.rand_ascii_str( 726 hostapd_constants.AP_SSID_LENGTH_5G), 727 'security_type': SECURITY_OPEN, 728 'connectivity_mode': CONNECTIVITY_MODE_UNRESTRICTED, 729 'operating_band': OPERATING_BAND_ANY 730 }) 731 732 def test_soft_ap_2g_wep_unrestricted(self): 733 self.verify_soft_ap_associate_and_pass_traffic( 734 self.primary_client, { 735 'ssid': 736 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G), 737 'security_type': 738 SECURITY_WEP, 739 'password': 740 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 741 'connectivity_mode': 742 CONNECTIVITY_MODE_UNRESTRICTED, 743 'operating_band': 744 OPERATING_BAND_2G 745 }) 746 747 def test_soft_ap_5g_wep_unrestricted(self): 748 self.verify_soft_ap_associate_and_pass_traffic( 749 self.primary_client, { 750 'ssid': 751 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 752 'security_type': 753 SECURITY_WEP, 754 'password': 755 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 756 'connectivity_mode': 757 CONNECTIVITY_MODE_UNRESTRICTED, 758 'operating_band': 759 OPERATING_BAND_5G 760 }) 761 762 def test_soft_ap_any_wep_unrestricted(self): 763 self.verify_soft_ap_associate_and_pass_traffic( 764 self.primary_client, { 765 'ssid': 766 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 767 'security_type': 768 SECURITY_WEP, 769 'password': 770 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 771 'connectivity_mode': 772 CONNECTIVITY_MODE_UNRESTRICTED, 773 'operating_band': 774 OPERATING_BAND_ANY 775 }) 776 777 def test_soft_ap_2g_wpa_unrestricted(self): 778 self.verify_soft_ap_associate_and_pass_traffic( 779 self.primary_client, { 780 'ssid': 781 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G), 782 'security_type': 783 SECURITY_WPA, 784 'password': 785 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 786 'connectivity_mode': 787 CONNECTIVITY_MODE_UNRESTRICTED, 788 'operating_band': 789 OPERATING_BAND_2G 790 }) 791 792 def test_soft_ap_5g_wpa_unrestricted(self): 793 self.verify_soft_ap_associate_and_pass_traffic( 794 self.primary_client, { 795 'ssid': 796 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 797 'security_type': 798 SECURITY_WPA, 799 'password': 800 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 801 'connectivity_mode': 802 CONNECTIVITY_MODE_UNRESTRICTED, 803 'operating_band': 804 OPERATING_BAND_5G 805 }) 806 807 def test_soft_ap_any_wpa_unrestricted(self): 808 self.verify_soft_ap_associate_and_pass_traffic( 809 self.primary_client, { 810 'ssid': 811 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 812 'security_type': 813 SECURITY_WPA, 814 'password': 815 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 816 'connectivity_mode': 817 CONNECTIVITY_MODE_UNRESTRICTED, 818 'operating_band': 819 OPERATING_BAND_ANY 820 }) 821 822 def test_soft_ap_2g_wpa2_unrestricted(self): 823 self.verify_soft_ap_associate_and_pass_traffic( 824 self.primary_client, { 825 'ssid': 826 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G), 827 'security_type': 828 SECURITY_WPA2, 829 'password': 830 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 831 'connectivity_mode': 832 CONNECTIVITY_MODE_UNRESTRICTED, 833 'operating_band': 834 OPERATING_BAND_2G 835 }) 836 837 def test_soft_ap_5g_wpa2_unrestricted(self): 838 self.verify_soft_ap_associate_and_pass_traffic( 839 self.primary_client, { 840 'ssid': 841 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 842 'security_type': 843 SECURITY_WPA2, 844 'password': 845 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 846 'connectivity_mode': 847 CONNECTIVITY_MODE_UNRESTRICTED, 848 'operating_band': 849 OPERATING_BAND_5G 850 }) 851 852 def test_soft_ap_any_wpa2_unrestricted(self): 853 self.verify_soft_ap_associate_and_pass_traffic( 854 self.primary_client, { 855 'ssid': 856 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 857 'security_type': 858 SECURITY_WPA2, 859 'password': 860 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 861 'connectivity_mode': 862 CONNECTIVITY_MODE_UNRESTRICTED, 863 'operating_band': 864 OPERATING_BAND_ANY 865 }) 866 867 def test_soft_ap_2g_wpa3_unrestricted(self): 868 self.verify_soft_ap_associate_and_pass_traffic( 869 self.primary_client, { 870 'ssid': 871 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G), 872 'security_type': 873 SECURITY_WPA3, 874 'password': 875 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 876 'connectivity_mode': 877 CONNECTIVITY_MODE_UNRESTRICTED, 878 'operating_band': 879 OPERATING_BAND_2G 880 }) 881 882 def test_soft_ap_5g_wpa3_unrestricted(self): 883 self.verify_soft_ap_associate_and_pass_traffic( 884 self.primary_client, { 885 'ssid': 886 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 887 'security_type': 888 SECURITY_WPA3, 889 'password': 890 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 891 'connectivity_mode': 892 CONNECTIVITY_MODE_UNRESTRICTED, 893 'operating_band': 894 OPERATING_BAND_ANY 895 }) 896 897 def test_soft_ap_any_wpa3_unrestricted(self): 898 self.verify_soft_ap_associate_and_pass_traffic( 899 self.primary_client, { 900 'ssid': 901 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_5G), 902 'security_type': 903 SECURITY_WPA3, 904 'password': 905 utils.rand_ascii_str(hostapd_constants.MIN_WPA_PSK_LENGTH), 906 'connectivity_mode': 907 CONNECTIVITY_MODE_UNRESTRICTED, 908 'operating_band': 909 OPERATING_BAND_ANY 910 }) 911 912 def test_multi_client_open(self): 913 """Tests multi-client association with a single soft AP network. 914 915 This tests associates a variable length list of clients, verfying it can 916 can ping the SoftAP and pass traffic, and then verfies all previously 917 associated clients can still ping and pass traffic. 918 919 The same occurs in reverse for disassocations. 920 """ 921 asserts.skip_if( 922 len(self.clients) < 2, 'Test requires at least 2 SoftAPClients') 923 924 settings = { 925 'ssid': utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G), 926 'security_type': SECURITY_OPEN, 927 'connectivity_mode': CONNECTIVITY_MODE_LOCAL, 928 'operating_band': OPERATING_BAND_ANY 929 } 930 self.start_soft_ap(settings) 931 932 dut_ap_interface = self.get_dut_interface_by_role(INTERFACE_ROLE_AP) 933 associated = [] 934 935 for client in self.clients: 936 # Associate new client 937 self.associate_with_soft_ap(client.w_device, settings) 938 client_ipv4 = self.wait_for_ipv4_address( 939 client.w_device, ANDROID_DEFAULT_WLAN_PORT) 940 self.run_iperf_traffic(client.ip_client, dut_ap_interface.ipv4) 941 942 # Verify previously associated clients still behave as expected 943 for client_map in associated: 944 associated_client = client_map['client'] 945 associated_client_ipv4 = client_map['ipv4'] 946 self.log.info( 947 'Verifying previously associated client %s still functions correctly.' 948 % associated_client.w_device.device.serial) 949 try: 950 self.verify_ping(self.dut, associated_client_ipv4) 951 self.verify_ping(associated_client.w_device, 952 dut_ap_interface.ipv4) 953 self.run_iperf_traffic(associated_client.ip_client, 954 dut_ap_interface.ipv4) 955 except signals.TestFailure as err: 956 asserts.fail( 957 'Previously associated client %s failed checks after ' 958 'client %s associated. Error: %s' % 959 (associated_client.w_device.device.serial, 960 client.w_device.device.serial, err)) 961 962 associated.append({'client': client, 'ipv4': client_ipv4}) 963 964 self.log.info( 965 'All devices successfully associated. Beginning disassociations.') 966 967 while len(associated) > 0: 968 # Disassociate client 969 client = associated.pop()['client'] 970 self.disconnect_from_soft_ap(client.w_device) 971 972 # Verify still connected clients still behave as expected 973 for client_map in associated: 974 associated_client = client_map['client'] 975 associated_client_ipv4 = client_map['ipv4'] 976 try: 977 self.log.info( 978 'Verifying still associated client %s still functions ' 979 'correctly.' % 980 associated_client.w_device.device.serial) 981 self.verify_ping(self.dut, associated_client_ipv4) 982 self.verify_ping(associated_client.w_device, 983 dut_ap_interface.ipv4) 984 self.run_iperf_traffic(associated_client.ip_client, 985 dut_ap_interface.ipv4) 986 except signals.TestFailure as err: 987 asserts.fail( 988 'Previously associated client %s failed checks after' 989 ' client %s disassociated. Error: %s' % 990 (associated_client.w_device.device.serial, 991 client.w_device.device.serial, err)) 992 993 self.log.info('All disassociations occurred smoothly.') 994 995 def test_soft_ap_and_client(self): 996 """ Tests FuchsiaDevice DUT can act as a client and a SoftAP 997 simultaneously. 998 999 Raises: 1000 ConnectionError: if DUT fails to connect as client 1001 RuntimeError: if parallel processes fail to join 1002 TestFailure: if DUT fails to pass traffic as either a client or an 1003 AP 1004 """ 1005 asserts.skip_if(not self.access_point, 'No access point provided.') 1006 1007 self.log.info('Setting up AP using hostapd.') 1008 1009 # Configure AP 1010 ap_params = self.user_params.get('soft_ap_test_params', 1011 {}).get('ap_params', {}) 1012 channel = ap_params.get('channel', 11) 1013 ssid = ap_params.get('ssid', 'apnet') 1014 security_mode = ap_params.get('security_mode', None) 1015 password = ap_params.get('password', None) 1016 if security_mode: 1017 security = hostapd_security.Security(security_mode, password) 1018 else: 1019 security = None 1020 1021 # Setup AP and associate DUT 1022 if not setup_ap_and_associate(access_point=self.access_point, 1023 client=self.dut, 1024 profile_name='whirlwind', 1025 channel=channel, 1026 security=security, 1027 password=password, 1028 ssid=ssid): 1029 raise ConnectionError( 1030 'FuchsiaDevice DUT failed to connect as client to AP.') 1031 self.log.info('DUT successfully associated to AP network.') 1032 1033 # Verify FuchsiaDevice's client interface has an ip address from AP 1034 dut_client_interface = self.get_dut_interface_by_role( 1035 INTERFACE_ROLE_CLIENT) 1036 1037 # Verify FuchsiaDevice can ping AP 1038 lowest_5ghz_channel = 36 1039 if channel < lowest_5ghz_channel: 1040 ap_interface = self.access_point.wlan_2g 1041 else: 1042 ap_interface = self.access_point.wlan_5g 1043 ap_ipv4 = utils.get_interface_ip_addresses( 1044 self.access_point.ssh, ap_interface)['ipv4_private'][0] 1045 1046 self.verify_ping(self.dut, ap_ipv4) 1047 1048 # Setup SoftAP 1049 soft_ap_settings = { 1050 'ssid': utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G), 1051 'security_type': SECURITY_OPEN, 1052 'connectivity_mode': CONNECTIVITY_MODE_LOCAL, 1053 'operating_band': OPERATING_BAND_2G 1054 } 1055 self.start_soft_ap(soft_ap_settings) 1056 1057 # Get FuchsiaDevice's AP interface info 1058 dut_ap_interface = self.get_dut_interface_by_role(INTERFACE_ROLE_AP) 1059 1060 # Associate primary client with SoftAP 1061 self.associate_with_soft_ap(self.primary_client.w_device, 1062 soft_ap_settings) 1063 1064 # Verify primary client has an ip address from SoftAP 1065 client_ipv4 = self.wait_for_ipv4_address(self.primary_client.w_device, 1066 ANDROID_DEFAULT_WLAN_PORT) 1067 1068 # Verify primary client can ping SoftAP, and reverse 1069 self.verify_ping(self.primary_client.w_device, dut_ap_interface.ipv4) 1070 self.verify_ping(self.dut, client_ipv4) 1071 1072 # Set up secondary iperf server of FuchsiaDevice 1073 self.log.info('Setting up second iperf server on FuchsiaDevice DUT.') 1074 secondary_iperf_server = iperf_server.IPerfServerOverSsh( 1075 self.iperf_server_config, DEFAULT_IPERF_PORT + 1, use_killall=True) 1076 secondary_iperf_server.start() 1077 1078 # Set up iperf client on AP 1079 self.log.info('Setting up iperf client on AP.') 1080 ap_iperf_client = iperf_client.IPerfClientOverSsh( 1081 self.user_params['AccessPoint'][0]['ssh_config']) 1082 1083 # Setup iperf processes: 1084 # Primary client <-> SoftAP interface on FuchsiaDevice 1085 # AP <-> Client interface on FuchsiaDevice 1086 process_errors = mp.Queue() 1087 iperf_soft_ap = mp.Process( 1088 target=self.run_iperf_traffic_parallel_process, 1089 args=[ 1090 self.primary_client.ip_client, dut_ap_interface.ipv4, 1091 process_errors 1092 ]) 1093 1094 iperf_fuchsia_client = mp.Process( 1095 target=self.run_iperf_traffic_parallel_process, 1096 args=[ap_iperf_client, dut_client_interface.ipv4, process_errors], 1097 kwargs={'server_port': 5202}) 1098 1099 # Run iperf processes simultaneously 1100 self.log.info('Running simultaneous iperf traffic: between AP and DUT ' 1101 'client interface, and DUT AP interface and client.') 1102 iperf_soft_ap.start() 1103 iperf_fuchsia_client.start() 1104 1105 # Block until processes can join or timeout 1106 for proc in [iperf_soft_ap, iperf_fuchsia_client]: 1107 proc.join(timeout=30) 1108 if proc.is_alive(): 1109 raise RuntimeError('Failed to join process %s' % proc) 1110 1111 # Stop iperf server (also stopped in teardown class as failsafe) 1112 secondary_iperf_server.stop() 1113 1114 # Check errors from parallel processes 1115 if process_errors.empty(): 1116 asserts.explicit_pass( 1117 'FuchsiaDevice was successfully able to pass traffic as a ' 1118 'client and an AP simultaneously.') 1119 else: 1120 while not process_errors.empty(): 1121 self.log.error('Error in iperf process: %s' % 1122 process_errors.get()) 1123 asserts.fail( 1124 'FuchsiaDevice failed to pass traffic as a client and an AP ' 1125 'simultaneously.') 1126 1127 def test_soft_ap_stress_from_config(self): 1128 """ Runs tests from ACTS config file. 1129 1130 Example Config 1131 "soft_ap_test_params" : { 1132 "soft_ap_tests": [ 1133 { 1134 "ssid": "test_network", 1135 "security_type": "wpa2", 1136 "password": "password", 1137 "connectivity_mode": "local_only", 1138 "operating_band": "only_2_4_ghz", 1139 "reconnect_loops": 10 1140 } 1141 ] 1142 } 1143 """ 1144 tests = self.user_params.get('soft_ap_test_params', 1145 {}).get('soft_ap_tests') 1146 asserts.skip_if(not tests, 'No soft ap tests in the ACTS config.') 1147 1148 test_settings_list = [] 1149 for config_settings in self.user_params['soft_ap_test_params'][ 1150 'soft_ap_tests']: 1151 ssid = config_settings.get( 1152 'ssid', 1153 utils.rand_ascii_str(hostapd_constants.AP_SSID_LENGTH_2G)) 1154 security_type = config_settings.get('security_type', SECURITY_OPEN) 1155 password = config_settings.get('password', '') 1156 connectivity_mode = config_settings.get('connectivity_mode', 1157 CONNECTIVITY_MODE_LOCAL) 1158 operating_band = config_settings.get('operating_band', 1159 OPERATING_BAND_ANY) 1160 test_type = config_settings.get('test_type', 1161 'associate_and_pass_traffic') 1162 reconnect_loops = config_settings.get('reconnect_loops', 1) 1163 test_settings = { 1164 'client': self.primary_client, 1165 'ssid': ssid, 1166 'security_type': security_type, 1167 'password': password, 1168 'connectivity_mode': connectivity_mode, 1169 'operating_band': operating_band, 1170 'test_type': test_type, 1171 'reconnect_loops': reconnect_loops 1172 } 1173 test_settings_list.append(test_settings) 1174 1175 self.run_generated_testcases(self.run_config_stress_test, 1176 test_settings_list, 1177 name_func=generate_test_name)