1#!/usr/bin/env python3.4 2# 3# Copyright 2019 - 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 17import itertools 18import pprint 19import queue 20import re 21import time 22 23import acts.base_test 24import acts.signals as signals 25import acts.test_utils.wifi.wifi_test_utils as wutils 26import acts.utils 27 28from acts import asserts 29from acts.test_decorators import test_tracker_info 30from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G 31from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G 32from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest 33from scapy.all import * 34from acts.controllers.ap_lib import hostapd_constants 35 36WifiEnums = wutils.WifiEnums 37 38# Default timeout used for reboot, toggle WiFi and Airplane mode, 39# for the system to settle down after the operation. 40DEFAULT_TIMEOUT = 10 41SHORT_TIMEOUT = 5 42 43# Constants for WiFi state change operations. 44FORGET = 1 45TOGGLE = 2 46REBOOT_DUT = 3 47REBOOT_AP = 4 48 49# MAC Randomization setting constants. 50RANDOMIZATION_NONE = 0 51RANDOMIZATION_PERSISTENT = 1 52 53 54class WifiMacRandomizationTest(WifiBaseTest): 55 """Tests for APIs in Android's WifiManager class. 56 57 Test Bed Requirement: 58 * Atleast one Android device and atleast two Access Points. 59 * Several Wi-Fi networks visible to the device. 60 """ 61 62 def __init__(self, controllers): 63 WifiBaseTest.__init__(self, controllers) 64 65 def setup_class(self): 66 self.dut = self.android_devices[0] 67 self.dut_client = self.android_devices[1] 68 wutils.wifi_test_device_init(self.dut) 69 wutils.wifi_test_device_init(self.dut_client) 70 req_params = ["dbs_supported_models"] 71 opt_param = [ 72 "open_network", "reference_networks", "wep_networks" 73 ] 74 self.unpack_userparams( 75 req_param_names=req_params, opt_param_names=opt_param) 76 77 if not hasattr(self, 'packet_capture'): 78 raise signals.TestFailure("Needs packet_capture attribute to " 79 "support sniffing.") 80 self.configure_packet_capture() 81 82 if "AccessPoint" in self.user_params: 83 if "AccessPoint" in self.user_params: 84 self.legacy_configure_ap_and_start(wep_network=True, ap_count=2) 85 86 asserts.assert_true( 87 len(self.reference_networks) > 0, 88 "Need at least one reference network with psk.") 89 90 # Reboot device to reset factory MAC of wlan1 91 self.dut.reboot() 92 self.dut_client.reboot() 93 time.sleep(DEFAULT_TIMEOUT) 94 wutils.wifi_toggle_state(self.dut, True) 95 wutils.wifi_toggle_state(self.dut_client, True) 96 self.soft_ap_factory_mac = self.get_soft_ap_mac_address() 97 self.sta_factory_mac = self.dut.droid.wifigetFactorymacAddresses()[0] 98 99 self.wpapsk_2g = self.reference_networks[0]["2g"] 100 self.wpapsk_5g = self.reference_networks[0]["5g"] 101 self.wep_2g = self.wep_networks[0]["2g"] 102 self.wep_5g = self.wep_networks[0]["5g"] 103 self.open_2g = self.open_network[0]["2g"] 104 self.open_5g = self.open_network[0]["5g"] 105 106 def setup_test(self): 107 for ad in self.android_devices: 108 ad.droid.wakeLockAcquireBright() 109 ad.droid.wakeUpNow() 110 wutils.wifi_toggle_state(ad, True) 111 112 def teardown_test(self): 113 for ad in self.android_devices: 114 ad.droid.wakeLockRelease() 115 ad.droid.goToSleepNow() 116 wutils.reset_wifi(self.dut) 117 wutils.reset_wifi(self.dut_client) 118 119 def on_fail(self, test_name, begin_time): 120 self.dut.cat_adb_log(test_name, begin_time) 121 self.dut.take_bug_report(test_name, begin_time) 122 123 def teardown_class(self): 124 if "AccessPoint" in self.user_params: 125 del self.user_params["reference_networks"] 126 del self.user_params["open_network"] 127 del self.user_params["wep_networks"] 128 129 130 """Helper Functions""" 131 132 133 def get_randomized_mac(self, network): 134 """Get the randomized MAC address. 135 136 Args: 137 network: dict, network information. 138 139 Returns: 140 The randomized MAC address string for the network. 141 142 """ 143 return self.dut.droid.wifigetRandomizedMacAddress(network) 144 145 def connect_to_network_and_verify_mac_randomization(self, network, 146 status=RANDOMIZATION_PERSISTENT): 147 """Connect to the given network and verify MAC. 148 149 Args: 150 network: dict, the network information. 151 status: int, MAC randomization level. 152 153 Returns: 154 The randomized MAC addresss string. 155 156 """ 157 wutils.connect_to_wifi_network(self.dut, network) 158 return self.verify_mac_randomization(network, status=status) 159 160 def verify_mac_randomization_and_add_to_list(self, network, mac_list): 161 """Connect to a network and populate it's MAC in a reference list, 162 that will be used to verify any repeated MAC addresses. 163 164 Args: 165 network: dict, the network information. 166 mac_list: list of MAC addresss strings. 167 168 """ 169 rand_mac = self.connect_to_network_and_verify_mac_randomization( 170 network) 171 if rand_mac in mac_list: 172 raise signals.TestFailure('A new Randomized MAC was not generated ' 173 ' for this network %s.' % network) 174 mac_list.append(rand_mac) 175 176 def verify_mac_randomization(self, network, status=RANDOMIZATION_PERSISTENT): 177 """Get the various types of MAC addresses for the device and verify. 178 179 Args: 180 network: dict, the network information. 181 status: int, MAC randomization level. 182 183 Returns: 184 The randomized MAC address string for the network. 185 186 """ 187 randomized_mac = self.get_randomized_mac(network) 188 default_mac = self.get_sta_mac_address() 189 self.log.info("Factory MAC = %s\nRandomized MAC = %s\nDefault MAC = %s" % 190 (self.sta_factory_mac, randomized_mac, default_mac)) 191 message = ('Randomized MAC and Factory MAC are the same. ' 192 'Randomized MAC = %s, Factory MAC = %s' % (randomized_mac, self.sta_factory_mac)) 193 asserts.assert_true(randomized_mac != self.sta_factory_mac, message) 194 if status == RANDOMIZATION_NONE: 195 asserts.assert_true(default_mac == self.sta_factory_mac, "Connection is not " 196 "using Factory MAC as the default MAC.") 197 else: 198 message = ('Connection is not using randomized MAC as the default MAC. ' 199 'Randomized MAC = %s, Deafult MAC = %s' % (randomized_mac, default_mac)) 200 asserts.assert_true(default_mac == randomized_mac, message) 201 return randomized_mac 202 203 def check_mac_persistence(self, network, condition): 204 """Check if the MAC is persistent after carrying out specific operations 205 like forget WiFi, toggle WiFi, reboot device and AP. 206 207 Args: 208 network: dict, The network information. 209 condition: int, value to trigger certain operation on the device. 210 211 Raises: 212 TestFaikure is the MAC is not persistent. 213 214 """ 215 rand_mac1 = self.connect_to_network_and_verify_mac_randomization(network) 216 217 if condition == FORGET: 218 wutils.wifi_forget_network(self.dut, network['SSID']) 219 220 elif condition == TOGGLE: 221 wutils.wifi_toggle_state(self.dut, False) 222 wutils.wifi_toggle_state(self.dut, True) 223 224 elif condition == REBOOT_DUT: 225 self.dut.reboot() 226 time.sleep(DEFAULT_TIMEOUT) 227 228 elif condition == REBOOT_AP: 229 wutils.turn_ap_off(self, 1) 230 time.sleep(DEFAULT_TIMEOUT) 231 wutils.turn_ap_on(self, 1) 232 time.sleep(DEFAULT_TIMEOUT) 233 234 rand_mac2 = self.connect_to_network_and_verify_mac_randomization(network) 235 236 if rand_mac1 != rand_mac2: 237 raise signals.TestFailure('Randomized MAC is not persistent after ' 238 'forgetting networ. Old MAC = %s New MAC' 239 ' = %s' % (rand_mac1, rand_mac2)) 240 241 def verify_mac_not_found_in_pcap(self, mac, packets): 242 for pkt in packets: 243 self.log.debug("Packet Summary = %s" % pkt.summary()) 244 if mac in pkt.summary(): 245 raise signals.TestFailure("Caught Factory MAC in packet sniffer." 246 "Packet = %s" % pkt.show()) 247 248 def verify_mac_is_found_in_pcap(self, mac, packets): 249 for pkt in packets: 250 self.log.debug("Packet Summary = %s" % pkt.summary()) 251 if mac in pkt.summary(): 252 return 253 raise signals.TestFailure("Did not find MAC = %s in packet sniffer." 254 % mac) 255 256 def get_sta_mac_address(self): 257 """Gets the current MAC address being used for client mode.""" 258 out = self.dut.adb.shell("ifconfig wlan0") 259 res = re.match(".* HWaddr (\S+).*", out, re.S) 260 return res.group(1) 261 262 def get_soft_ap_mac_address(self): 263 """Gets the current MAC address being used for SoftAp.""" 264 if self.dut.model in self.dbs_supported_models: 265 out = self.dut.adb.shell("ifconfig wlan1") 266 return re.match(".* HWaddr (\S+).*", out, re.S).group(1) 267 else: 268 return self.get_sta_mac_address() 269 """Tests""" 270 271 272 @test_tracker_info(uuid="2dd0a05e-a318-45a6-81cd-962e098fa242") 273 def test_set_mac_randomization_to_none(self): 274 self.pcap_procs = wutils.start_pcap( 275 self.packet_capture, 'dual', self.log_path, self.test_name) 276 network = self.wpapsk_2g 277 # Set macRandomizationSetting to RANDOMIZATION_NONE. 278 network["macRand"] = RANDOMIZATION_NONE 279 self.connect_to_network_and_verify_mac_randomization(network, 280 status=RANDOMIZATION_NONE) 281 pcap_fname = os.path.join(self.log_path, self.test_name, 282 (self.test_name + '_2G.pcap')) 283 time.sleep(SHORT_TIMEOUT) 284 wutils.stop_pcap(self.packet_capture, self.pcap_procs, False) 285 packets = rdpcap(pcap_fname) 286 self.verify_mac_is_found_in_pcap(self.sta_factory_mac, packets) 287 288 @test_tracker_info(uuid="d9e64202-02d5-421a-967c-42e45f1f7f91") 289 def test_mac_randomization_wpapsk(self): 290 """Verify MAC randomization for a WPA network. 291 292 Steps: 293 1. Connect to WPA network. 294 2. Get the Factory, Randomized and Default MACs. 295 3. Verify randomized MAC is the default MAC for the device. 296 297 """ 298 self.connect_to_network_and_verify_mac_randomization(self.wpapsk_2g) 299 300 @test_tracker_info(uuid="b5be7c53-2edf-449e-ba70-a1fb7acf735e") 301 def test_mac_randomization_wep(self): 302 """Verify MAC randomization for a WEP network. 303 304 Steps: 305 1. Connect to WEP network. 306 2. Get the Factory, Randomized and Default MACs. 307 3. Verify randomized MAC is the default MAC for the device. 308 309 """ 310 self.connect_to_network_and_verify_mac_randomization(self.wep_2g) 311 312 @test_tracker_info(uuid="f5347ac0-68d5-4882-a58d-1bd0d575503c") 313 def test_mac_randomization_open(self): 314 """Verify MAC randomization for a open network. 315 316 Steps: 317 1. Connect to open network. 318 2. Get the Factory, Randomized and Default MACs. 319 3. Verify randomized MAC is the default MAC for the device. 320 321 """ 322 self.connect_to_network_and_verify_mac_randomization(self.open_2g) 323 324 @test_tracker_info(uuid="5d260421-2adf-4ace-b281-3d15aec39b2a") 325 def test_persistent_mac_after_forget(self): 326 """Check if MAC is persistent after forgetting/adding a network. 327 328 Steps: 329 1. Connect to WPA network and get the randomized MAC. 330 2. Forget the network. 331 3. Connect to the same network again. 332 4. Verify randomized MAC has not changed. 333 334 """ 335 self.check_mac_persistence(self.wpapsk_2g, FORGET) 336 337 @test_tracker_info(uuid="09d40a93-ead2-45ca-9905-14b05fd79f34") 338 def test_persistent_mac_after_toggle(self): 339 """Check if MAC is persistent after toggling WiFi network. 340 341 Steps: 342 1. Connect to WPA network and get the randomized MAC. 343 2. Turn WiFi ON/OFF. 344 3. Connect to the same network again. 345 4. Verify randomized MAC has not changed. 346 347 """ 348 self.check_mac_persistence(self.wpapsk_2g, TOGGLE) 349 350 @test_tracker_info(uuid="a514f-8562-44e8-bfe0-4ecab9af165b") 351 def test_persistent_mac_after_device_reboot(self): 352 """Check if MAC is persistent after a device reboot. 353 354 Steps: 355 1. Connect to WPA network and get the randomized MAC. 356 2. Reboot DUT. 357 3. Connect to the same network again. 358 4. Verify randomized MAC has not changed. 359 360 """ 361 self.check_mac_persistence(self.wpapsk_2g, REBOOT_DUT) 362 363 # Disable reboot test for debugging purpose. 364 #@test_tracker_info(uuid="82d691a0-22e4-4a3d-9596-e150531fcd34") 365 def persistent_mac_after_ap_reboot(self): 366 """Check if MAC is persistent after AP reboots itself. 367 368 Steps: 369 1. Connect to WPA network and get the randomized MAC. 370 2. Reboot AP(basically restart hostapd in our case). 371 3. Connect to the same network again. 372 4. Verify randomized MAC has not changed. 373 374 """ 375 self.check_mac_persistence(self.wpapsk_2g, REBOOT_AP) 376 377 @test_tracker_info(uuid="e1f33dbc-808c-4e61-8a4a-3a72c1f63c7e") 378 def test_mac_randomization_multiple_networks(self): 379 """Connect to multiple networks and verify same MAC. 380 381 Steps: 382 1. Connect to network A, get randomizd MAC. 383 2. Conenct to network B, get randomized MAC. 384 3. Connect back to network A and verify same MAC. 385 4. Connect back to network B and verify same MAC. 386 387 """ 388 mac_list = list() 389 390 # Connect to two different networks and get randomized MAC addresses. 391 self.verify_mac_randomization_and_add_to_list(self.wpapsk_2g, mac_list) 392 self.verify_mac_randomization_and_add_to_list(self.open_2g, mac_list) 393 394 # Connect to the previous network and check MAC is persistent. 395 mac_wpapsk = self.connect_to_network_and_verify_mac_randomization( 396 self.wpapsk_2g) 397 msg = ('Randomized MAC is not persistent for this network %s. Old MAC = ' 398 '%s \nNew MAC = %s') 399 if mac_wpapsk != mac_list[0]: 400 raise signals.TestFailure(msg % (self.wpapsk_5g, mac_list[0], mac_wpapsk)) 401 mac_open = self.connect_to_network_and_verify_mac_randomization( 402 self.open_2g) 403 if mac_open != mac_list[1]: 404 raise signals.TestFailure(msg %(self.open_5g, mac_list[1], mac_open)) 405 406 @test_tracker_info(uuid="edb5a0e5-7f3b-4147-b1d3-48ad7ad9799e") 407 def test_mac_randomization_differnet_APs(self): 408 """Verify randomization using two different APs. 409 410 Steps: 411 1. Connect to network A on AP1, get the randomized MAC. 412 2. Connect to network B on AP2, get the randomized MAC. 413 3. Veirfy the two MACs are different. 414 415 """ 416 ap1 = self.wpapsk_2g 417 ap2 = self.reference_networks[1]["5g"] 418 mac_ap1 = self.connect_to_network_and_verify_mac_randomization(ap1) 419 mac_ap2 = self.connect_to_network_and_verify_mac_randomization(ap2) 420 if mac_ap1 == mac_ap2: 421 raise signals.TestFailure("Same MAC address was generated for both " 422 "APs: %s" % mac_ap1) 423 424 @test_tracker_info(uuid="b815e9ce-bccd-4fc3-9774-1e1bc123a2a8") 425 def test_mac_randomization_ap_sta(self): 426 """Bring up STA and softAP and verify MAC randomization. 427 428 Steps: 429 1. Connect to a network and get randomized MAC. 430 2. Bring up softAP on the DUT. 431 3. Connect to softAP network on the client and get MAC. 432 4. Verify AP and STA use different randomized MACs. 433 5. Find the channel of the SoftAp network. 434 6. Configure sniffer on that channel. 435 7. Verify the factory MAC is not leaked. 436 437 """ 438 self.dut.droid.wifiSetCountryCode(wutils.WifiEnums.CountryCode.US) 439 self.dut_client.droid.wifiSetCountryCode(wutils.WifiEnums.CountryCode.US) 440 mac_sta = self.connect_to_network_and_verify_mac_randomization( 441 self.wpapsk_2g) 442 softap = wutils.start_softap_and_verify(self, WIFI_CONFIG_APBAND_2G) 443 wutils.connect_to_wifi_network(self.dut_client, softap) 444 softap_info = self.dut_client.droid.wifiGetConnectionInfo() 445 mac_ap = softap_info['mac_address'] 446 if mac_sta == mac_ap: 447 raise signals.TestFailure("Same MAC address was used for both " 448 "AP and STA: %s" % mac_sta) 449 450 # Verify SoftAp MAC is randomized 451 softap_mac = self.get_soft_ap_mac_address() 452 message = ('Randomized SoftAp MAC and Factory SoftAp MAC are the same. ' 453 'Randomized SoftAp MAC = %s, Factory SoftAp MAC = %s' 454 % (softap_mac, self.soft_ap_factory_mac)) 455 asserts.assert_true(softap_mac != self.soft_ap_factory_mac, message) 456 457 softap_channel = hostapd_constants.CHANNEL_MAP[softap_info['frequency']] 458 self.log.info("softap_channel = %s\n" % (softap_channel)) 459 result = self.packet_capture.configure_monitor_mode( 460 hostapd_constants.BAND_2G, softap_channel) 461 if not result: 462 raise ValueError("Failed to configure channel for 2G band") 463 self.pcap_procs = wutils.start_pcap( 464 self.packet_capture, 'dual', self.log_path, self.test_name) 465 # re-connect to the softAp network after sniffer is started 466 wutils.connect_to_wifi_network(self.dut_client, self.wpapsk_2g) 467 wutils.connect_to_wifi_network(self.dut_client, softap) 468 time.sleep(SHORT_TIMEOUT) 469 wutils.stop_pcap(self.packet_capture, self.pcap_procs, False) 470 pcap_fname = os.path.join(self.log_path, self.test_name, 471 (self.test_name + '_2G.pcap')) 472 packets = rdpcap(pcap_fname) 473 self.verify_mac_not_found_in_pcap(self.soft_ap_factory_mac, packets) 474 self.verify_mac_not_found_in_pcap(self.sta_factory_mac, packets) 475 self.verify_mac_is_found_in_pcap(softap_mac, packets) 476 self.verify_mac_is_found_in_pcap(self.get_sta_mac_address(), packets) 477 478 @test_tracker_info(uuid="3ca3f911-29f1-41fb-b836-4d25eac1669f") 479 def test_roaming_mac_randomization(self): 480 """test MAC randomization in the roaming scenario. 481 482 Steps: 483 1. Connect to network A on AP1, get randomized MAC. 484 2. Set AP1 to MAX attenuation so that we roam to AP2. 485 3. Wait for device to roam to AP2 and get randomized MAC. 486 4. Veirfy that the device uses same AMC for both APs. 487 488 """ 489 AP1_network = self.reference_networks[0]["5g"] 490 AP2_network = self.reference_networks[1]["5g"] 491 wutils.set_attns(self.attenuators, "AP1_on_AP2_off") 492 mac_before_roam = self.connect_to_network_and_verify_mac_randomization( 493 AP1_network) 494 wutils.trigger_roaming_and_validate(self.dut, self.attenuators, 495 "AP1_off_AP2_on", AP2_network) 496 mac_after_roam = self.get_randomized_mac(AP2_network) 497 if mac_after_roam != mac_before_roam: 498 raise signals.TestFailure("Randomized MAC address changed after " 499 "roaming from AP1 to AP2.\nMAC before roam = %s\nMAC after " 500 "roam = %s" %(mac_before_roam, mac_after_roam)) 501 wutils.trigger_roaming_and_validate(self.dut, self.attenuators, 502 "AP1_on_AP2_off", AP1_network) 503 mac_after_roam = self.get_randomized_mac(AP1_network) 504 if mac_after_roam != mac_before_roam: 505 raise signals.TestFailure("Randomized MAC address changed after " 506 "roaming from AP1 to AP2.\nMAC before roam = %s\nMAC after " 507 "roam = %s" %(mac_before_roam, mac_after_roam)) 508 509 @test_tracker_info(uuid="17b12f1a-7c62-4188-b5a5-52d7a0bb7849") 510 def test_check_mac_sta_with_link_probe(self): 511 """Test to ensure Factory MAC is not exposed, using sniffer data. 512 513 Steps: 514 1. Configure and start the sniffer on 5GHz band. 515 2. Connect to 5GHz network. 516 3. Send link probes. 517 4. Stop the sniffer. 518 5. Invoke scapy to read the .pcap file. 519 6. Read each packet summary and make sure Factory MAC is not used. 520 521 """ 522 self.pcap_procs = wutils.start_pcap( 523 self.packet_capture, 'dual', self.log_path, self.test_name) 524 time.sleep(SHORT_TIMEOUT) 525 network = self.wpapsk_5g 526 rand_mac = self.connect_to_network_and_verify_mac_randomization(network) 527 wutils.send_link_probes(self.dut, 3, 3) 528 pcap_fname = os.path.join(self.log_path, self.test_name, 529 (self.test_name + '_5G.pcap')) 530 wutils.stop_pcap(self.packet_capture, self.pcap_procs, False) 531 time.sleep(SHORT_TIMEOUT) 532 packets = rdpcap(pcap_fname) 533 self.verify_mac_not_found_in_pcap(self.sta_factory_mac, packets) 534 self.verify_mac_is_found_in_pcap(self.get_sta_mac_address(), packets) 535 536 @test_tracker_info(uuid="1c2cc0fd-a340-40c4-b679-6acc5f526451") 537 def test_check_mac_in_wifi_scan(self): 538 """Test to ensure Factory MAC is not exposed, in Wi-Fi scans 539 540 Steps: 541 1. Configure and start the sniffer on both bands. 542 2. Perform a full scan. 543 3. Stop the sniffer. 544 4. Invoke scapy to read the .pcap file. 545 5. Read each packet summary and make sure Factory MAC is not used. 546 547 """ 548 self.pcap_procs = wutils.start_pcap( 549 self.packet_capture, 'dual', self.log_path, self.test_name) 550 wutils.start_wifi_connection_scan(self.dut) 551 time.sleep(SHORT_TIMEOUT) 552 wutils.stop_pcap(self.packet_capture, self.pcap_procs, False) 553 pcap_fname = os.path.join(self.log_path, self.test_name, 554 (self.test_name + '_2G.pcap')) 555 packets = rdpcap(pcap_fname) 556 self.verify_mac_not_found_in_pcap(self.sta_factory_mac, packets) 557