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_contrib.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_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
31from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
32from acts_contrib.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 setup_class(self):
63        super().setup_class()
64
65        self.dut = self.android_devices[0]
66        self.dut_client = self.android_devices[1]
67        wutils.wifi_test_device_init(self.dut)
68        wutils.wifi_test_device_init(self.dut_client)
69        req_params = ["sta_sta_supported_models", "dbs_supported_models",
70                      "support_one_factory_mac_address", "roaming_attn"]
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            self.legacy_configure_ap_and_start(wep_network=True,
84                                               ap_count=2)
85        elif "OpenWrtAP" in self.user_params:
86            self.configure_openwrt_ap_and_start(open_network=True,
87                                                wpa_network=True,
88                                                wep_network=True,
89                                                mirror_ap=True,
90                                                ap_count=2)
91
92        asserts.assert_true(
93            len(self.reference_networks) > 0,
94            "Need at least one reference network with psk.")
95
96        # Reboot device to reset factory MAC of wlan1
97        self.dut.reboot()
98        self.dut_client.reboot()
99        time.sleep(DEFAULT_TIMEOUT)
100        wutils.wifi_toggle_state(self.dut, True)
101        wutils.wifi_toggle_state(self.dut_client, True)
102        if self.dut.model in self.support_one_factory_mac_address:
103            self.soft_ap_factory_mac = (self.dut.droid
104                                        .wifigetFactorymacAddresses()[0])
105        else:
106            self.soft_ap_factory_mac = self.get_soft_ap_mac_address()
107        self.sta_factory_mac = self.dut.droid.wifigetFactorymacAddresses()[0]
108
109        self.wpapsk_2g = self.reference_networks[0]["2g"]
110        self.wpapsk_5g = self.reference_networks[0]["5g"]
111        self.wep_2g = self.wep_networks[0]["2g"]
112        self.wep_5g = self.wep_networks[0]["5g"]
113        self.open_2g = self.open_network[0]["2g"]
114        self.open_5g = self.open_network[0]["5g"]
115
116    def setup_test(self):
117        super().setup_test()
118        for ad in self.android_devices:
119            ad.droid.wakeLockAcquireBright()
120            ad.droid.wakeUpNow()
121            wutils.wifi_toggle_state(ad, True)
122
123    def teardown_test(self):
124        super().teardown_test()
125        for ad in self.android_devices:
126            ad.droid.wakeLockRelease()
127            ad.droid.goToSleepNow()
128        self.dut.droid.wifiRemoveNetworkSuggestions([])
129        wutils.reset_wifi(self.dut)
130        wutils.reset_wifi(self.dut_client)
131
132    def teardown_class(self):
133        if "AccessPoint" in self.user_params:
134            del self.user_params["reference_networks"]
135            del self.user_params["open_network"]
136            del self.user_params["wep_networks"]
137
138
139    """Helper Functions"""
140
141
142    def get_randomized_mac(self, network):
143        """Get the randomized MAC address.
144
145        Args:
146            network: dict, network information.
147
148        Returns:
149            The randomized MAC address string for the network.
150
151        """
152        return self.dut.droid.wifigetRandomizedMacAddress(network)
153
154    def connect_to_network_and_verify_mac_randomization(self, network,
155            status=RANDOMIZATION_PERSISTENT):
156        """Connect to the given network and verify MAC.
157
158          Args:
159              network: dict, the network information.
160              status: int, MAC randomization level.
161
162          Returns:
163              The randomized MAC addresss string.
164
165        """
166        wutils.connect_to_wifi_network(self.dut, network)
167        return self.verify_mac_randomization(network, status=status)
168
169    def verify_mac_randomization_and_add_to_list(self, network, mac_list):
170        """Connect to a network and populate it's MAC in a reference list,
171        that will be used to verify any repeated MAC addresses.
172
173        Args:
174            network: dict, the network information.
175            mac_list: list of MAC addresss strings.
176
177        """
178        rand_mac = self.connect_to_network_and_verify_mac_randomization(
179                network)
180        if rand_mac in mac_list:
181            raise signals.TestFailure('A new Randomized MAC was not generated '
182                                      ' for this network %s.' % network)
183        mac_list.append(rand_mac)
184
185    def verify_mac_randomization(self, network, status=RANDOMIZATION_PERSISTENT):
186        """Get the various types of MAC addresses for the device and verify.
187
188        Args:
189            network: dict, the network information.
190            status: int, MAC randomization level.
191
192        Returns:
193            The randomized MAC address string for the network.
194
195        """
196        randomized_mac = self.get_randomized_mac(network)
197        default_mac = self.get_sta_mac_address()
198        self.log.info("Factory MAC = %s\nRandomized MAC = %s\nDefault MAC = %s" %
199              (self.sta_factory_mac, randomized_mac, default_mac))
200        message = ('Randomized MAC and Factory MAC are the same. '
201                   'Randomized MAC = %s, Factory MAC = %s' % (randomized_mac, self.sta_factory_mac))
202        asserts.assert_true(randomized_mac != self.sta_factory_mac, message)
203        if status == RANDOMIZATION_NONE:
204            asserts.assert_true(default_mac == self.sta_factory_mac, "Connection is not "
205                "using Factory MAC as the default MAC.")
206        else:
207            message = ('Connection is not using randomized MAC as the default MAC. '
208                       'Randomized MAC = %s, Deafult MAC = %s' % (randomized_mac, default_mac))
209            asserts.assert_true(default_mac == randomized_mac, message)
210        return randomized_mac
211
212    def check_mac_persistence(self, network, condition):
213        """Check if the MAC is persistent after carrying out specific operations
214        like forget WiFi, toggle WiFi, reboot device and AP.
215
216        Args:
217            network: dict, The network information.
218            condition: int, value to trigger certain  operation on the device.
219
220        Raises:
221            TestFaikure is the MAC is not persistent.
222
223        """
224        rand_mac1 = self.connect_to_network_and_verify_mac_randomization(network)
225
226        if condition == FORGET:
227            wutils.wifi_forget_network(self.dut, network['SSID'])
228
229        elif condition == TOGGLE:
230            wutils.wifi_toggle_state(self.dut, False)
231            wutils.wifi_toggle_state(self.dut, True)
232
233        elif condition == REBOOT_DUT:
234            self.dut.reboot()
235            time.sleep(DEFAULT_TIMEOUT)
236
237        elif condition == REBOOT_AP:
238            wutils.turn_ap_off(self, 1)
239            time.sleep(DEFAULT_TIMEOUT)
240            wutils.turn_ap_on(self, 1)
241            time.sleep(DEFAULT_TIMEOUT)
242
243        rand_mac2 = self.connect_to_network_and_verify_mac_randomization(network)
244
245        if rand_mac1 != rand_mac2:
246            raise signals.TestFailure('Randomized MAC is not persistent after '
247                                      'forgetting networ. Old MAC = %s New MAC'
248                                      ' = %s' % (rand_mac1, rand_mac2))
249
250    def verify_mac_not_found_in_pcap(self, mac, packets):
251        for pkt in packets:
252            self.log.debug("Packet Summary = %s" % pkt.summary())
253            if mac in pkt.summary():
254                raise signals.TestFailure("Caught Factory MAC in packet sniffer"
255                                          "Packet = %s Device = %s"
256                                           % (pkt.show(), self.dut))
257
258    def verify_mac_is_found_in_pcap(self, mac, packets):
259        for pkt in packets:
260            self.log.debug("Packet Summary = %s" % pkt.summary())
261            if mac in pkt.summary():
262                return
263        raise signals.TestFailure("Did not find MAC = %s in packet sniffer."
264                                  "for device %s" % (mac, self.dut))
265
266    def get_sta_mac_address(self):
267        """Gets the current MAC address being used for client mode."""
268        out = self.dut.adb.shell("ifconfig wlan0")
269        res = re.match(".* HWaddr (\S+).*", out, re.S)
270        return res.group(1)
271
272    def get_soft_ap_mac_address(self):
273        """Gets the current MAC address being used for SoftAp."""
274        if self.dut.model in self.sta_sta_supported_models:
275            out = self.dut.adb.shell("ifconfig wlan2")
276            return re.match(".* HWaddr (\S+).*", out, re.S).group(1)
277        if self.dut.model in self.dbs_supported_models:
278            out = self.dut.adb.shell("ifconfig wlan1")
279            return re.match(".* HWaddr (\S+).*", out, re.S).group(1)
280        else:
281            return self.get_sta_mac_address()
282
283    def _add_suggestion_and_verify_mac_randomization(self, network_suggestion):
284        """Add wifi network suggestion and verify MAC randomization.
285
286        Args:
287            network_suggestion: network suggestion to add.
288
289        Returns:
290            Randomized MAC address.
291        """
292        self.log.info("Adding network suggestion")
293        asserts.assert_true(
294            self.dut.droid.wifiAddNetworkSuggestions([network_suggestion]),
295            "Failed to add suggestions")
296        wutils.start_wifi_connection_scan_and_ensure_network_found(
297            self.dut, network_suggestion[WifiEnums.SSID_KEY])
298        wutils.wait_for_connect(self.dut, network_suggestion[WifiEnums.SSID_KEY])
299        default_mac = self.get_sta_mac_address()
300        randomized_mac = self.dut.droid.wifiGetConnectionInfo()["mac_address"]
301        self.log.info("Factory MAC = %s\nRandomized MAC = %s\nDefault MAC = %s" %
302                      (self.sta_factory_mac, randomized_mac, default_mac))
303        asserts.assert_true(
304            default_mac == randomized_mac,
305            "Connection is not using randomized MAC as the default MAC.")
306        return randomized_mac
307
308    def _remove_suggestion_and_verify_disconnect(self, network_suggestion):
309        """Remove wifi network suggestion and verify device disconnects.
310
311        Args:
312            network_suggestion: network suggestion to remove.
313        """
314        self.dut.log.info("Removing network suggestions")
315        asserts.assert_true(
316            self.dut.droid.wifiRemoveNetworkSuggestions([network_suggestion]),
317            "Failed to remove suggestions")
318        wutils.wait_for_disconnect(self.dut)
319        self.dut.ed.clear_all_events()
320        asserts.assert_false(
321            wutils.wait_for_connect(
322                self.dut,
323                network_suggestion[WifiEnums.SSID_KEY],
324                assert_on_fail=False),
325            "Device should not connect back")
326
327    """Tests"""
328
329
330    @test_tracker_info(uuid="2dd0a05e-a318-45a6-81cd-962e098fa242")
331    def test_set_mac_randomization_to_none(self):
332        self.pcap_procs = wutils.start_pcap(
333            self.packet_capture, 'dual', self.test_name)
334        network = self.wpapsk_2g
335        # Set macRandomizationSetting to RANDOMIZATION_NONE.
336        network["macRand"] = RANDOMIZATION_NONE
337        self.connect_to_network_and_verify_mac_randomization(network,
338            status=RANDOMIZATION_NONE)
339        pcap_fname = '%s_%s.pcap' % \
340            (self.pcap_procs[hostapd_constants.BAND_2G][1],
341             hostapd_constants.BAND_2G.upper())
342        time.sleep(SHORT_TIMEOUT)
343        wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
344        packets = rdpcap(pcap_fname)
345        self.verify_mac_is_found_in_pcap(self.sta_factory_mac, packets)
346
347    @test_tracker_info(uuid="d9e64202-02d5-421a-967c-42e45f1f7f91")
348    def test_mac_randomization_wpapsk(self):
349        """Verify MAC randomization for a WPA network.
350
351        Steps:
352            1. Connect to WPA network.
353            2. Get the Factory, Randomized and Default MACs.
354            3. Verify randomized MAC is the default MAC for the device.
355
356        """
357        self.connect_to_network_and_verify_mac_randomization(self.wpapsk_2g)
358
359    @test_tracker_info(uuid="b5be7c53-2edf-449e-ba70-a1fb7acf735e")
360    def test_mac_randomization_wep(self):
361        """Verify MAC randomization for a WEP network.
362
363        Steps:
364            1. Connect to WEP network.
365            2. Get the Factory, Randomized and Default MACs.
366            3. Verify randomized MAC is the default MAC for the device.
367
368        """
369        self.connect_to_network_and_verify_mac_randomization(self.wep_2g)
370
371    @test_tracker_info(uuid="f5347ac0-68d5-4882-a58d-1bd0d575503c")
372    def test_mac_randomization_open(self):
373        """Verify MAC randomization for a open network.
374
375        Steps:
376            1. Connect to open network.
377            2. Get the Factory, Randomized and Default MACs.
378            3. Verify randomized MAC is the default MAC for the device.
379
380        """
381        self.connect_to_network_and_verify_mac_randomization(self.open_2g)
382
383    @test_tracker_info(uuid="5d260421-2adf-4ace-b281-3d15aec39b2a")
384    def test_persistent_mac_after_forget(self):
385        """Check if MAC is persistent after forgetting/adding a network.
386
387        Steps:
388            1. Connect to WPA network and get the randomized MAC.
389            2. Forget the network.
390            3. Connect to the same network again.
391            4. Verify randomized MAC has not changed.
392
393        """
394        self.check_mac_persistence(self.wpapsk_2g, FORGET)
395
396    @test_tracker_info(uuid="09d40a93-ead2-45ca-9905-14b05fd79f34")
397    def test_persistent_mac_after_toggle(self):
398        """Check if MAC is persistent after toggling WiFi network.
399
400        Steps:
401            1. Connect to WPA network and get the randomized MAC.
402            2. Turn WiFi ON/OFF.
403            3. Connect to the same network again.
404            4. Verify randomized MAC has not changed.
405
406        """
407        self.check_mac_persistence(self.wpapsk_2g, TOGGLE)
408
409    @test_tracker_info(uuid="b3aa514f-8562-44e8-bfe0-4ecab9af165b")
410    def test_persistent_mac_after_device_reboot(self):
411        """Check if MAC is persistent after a device reboot.
412
413        Steps:
414            1. Connect to WPA network and get the randomized MAC.
415            2. Reboot DUT.
416            3. Connect to the same network again.
417            4. Verify randomized MAC has not changed.
418
419        """
420        self.check_mac_persistence(self.wpapsk_2g, REBOOT_DUT)
421
422    # Disable reboot test for debugging purpose.
423    #@test_tracker_info(uuid="82d691a0-22e4-4a3d-9596-e150531fcd34")
424    def persistent_mac_after_ap_reboot(self):
425        """Check if MAC is persistent after AP reboots itself.
426
427        Steps:
428            1. Connect to WPA network and get the randomized MAC.
429            2. Reboot AP(basically restart hostapd in our case).
430            3. Connect to the same network again.
431            4. Verify randomized MAC has not changed.
432
433        """
434        self.check_mac_persistence(self.wpapsk_2g, REBOOT_AP)
435
436    @test_tracker_info(uuid="e1f33dbc-808c-4e61-8a4a-3a72c1f63c7e")
437    def test_mac_randomization_multiple_networks(self):
438        """Connect to multiple networks and verify same MAC.
439
440        Steps:
441            1. Connect to network A, get randomizd MAC.
442            2. Conenct to network B, get randomized MAC.
443            3. Connect back to network A and verify same MAC.
444            4. Connect back to network B and verify same MAC.
445
446        """
447        mac_list = list()
448
449        # Connect to two different networks and get randomized MAC addresses.
450        self.verify_mac_randomization_and_add_to_list(self.wpapsk_2g, mac_list)
451        self.verify_mac_randomization_and_add_to_list(self.open_2g, mac_list)
452
453        # Connect to the previous network and check MAC is persistent.
454        mac_wpapsk = self.connect_to_network_and_verify_mac_randomization(
455                self.wpapsk_2g)
456        msg = ('Randomized MAC is not persistent for this network %s. Old MAC = '
457               '%s \nNew MAC = %s')
458        if mac_wpapsk != mac_list[0]:
459            raise signals.TestFailure(msg % (self.wpapsk_5g, mac_list[0], mac_wpapsk))
460        mac_open = self.connect_to_network_and_verify_mac_randomization(
461                self.open_2g)
462        if mac_open != mac_list[1]:
463            raise signals.TestFailure(msg %(self.open_5g, mac_list[1], mac_open))
464
465    @test_tracker_info(uuid="edb5a0e5-7f3b-4147-b1d3-48ad7ad9799e")
466    def test_mac_randomization_different_APs(self):
467        """Verify randomization using two different APs.
468
469        Steps:
470            1. Connect to network A on AP1, get the randomized MAC.
471            2. Connect to network B on AP2, get the randomized MAC.
472            3. Veirfy the two MACs are different.
473
474        """
475        ap1 = self.wpapsk_2g
476        ap2 = self.reference_networks[1]["5g"]
477        mac_ap1 = self.connect_to_network_and_verify_mac_randomization(ap1)
478        mac_ap2 = self.connect_to_network_and_verify_mac_randomization(ap2)
479        if mac_ap1 == mac_ap2:
480            raise signals.TestFailure("Same MAC address was generated for both "
481                                      "APs: %s" % mac_ap1)
482
483    @test_tracker_info(uuid="b815e9ce-bccd-4fc3-9774-1e1bc123a2a8")
484    def test_mac_randomization_ap_sta(self):
485        """Bring up STA and softAP and verify MAC randomization.
486
487        Steps:
488            1. Connect to a network and get randomized MAC.
489            2. Bring up softAP on the DUT.
490            3. Connect to softAP network on the client and get MAC.
491            4. Verify AP and STA use different randomized MACs.
492            5. Find the channel of the SoftAp network.
493            6. Configure sniffer on that channel.
494            7. Verify the factory MAC is not leaked.
495
496        """
497        wutils.set_wifi_country_code(self.dut, wutils.WifiEnums.CountryCode.US)
498        wutils.set_wifi_country_code(self.dut_client, wutils.WifiEnums.CountryCode.US)
499        mac_sta = self.connect_to_network_and_verify_mac_randomization(
500                self.wpapsk_2g)
501        softap = wutils.start_softap_and_verify(self, WIFI_CONFIG_APBAND_2G)
502        wutils.connect_to_wifi_network(self.dut_client, softap)
503        softap_info = self.dut_client.droid.wifiGetConnectionInfo()
504        mac_ap = softap_info['mac_address']
505        if mac_sta == mac_ap:
506            raise signals.TestFailure("Same MAC address was used for both "
507                                      "AP and STA: %s" % mac_sta)
508
509        # Verify SoftAp MAC is randomized
510        softap_mac = self.get_soft_ap_mac_address()
511        message = ('Randomized SoftAp MAC and Factory SoftAp MAC are the same. '
512                   'Randomized SoftAp MAC = %s, Factory SoftAp MAC = %s'
513                   % (softap_mac, self.soft_ap_factory_mac))
514        asserts.assert_true(softap_mac != self.soft_ap_factory_mac, message)
515
516        softap_channel = hostapd_constants.CHANNEL_MAP[softap_info['frequency']]
517        self.log.info("softap_channel = %s\n" % (softap_channel))
518        result = self.packet_capture.configure_monitor_mode(
519            hostapd_constants.BAND_2G, softap_channel)
520        if not result:
521            raise ValueError("Failed to configure channel for 2G band")
522        self.pcap_procs = wutils.start_pcap(
523            self.packet_capture, 'dual', self.test_name)
524        # re-connect to the softAp network after sniffer is started
525        wutils.connect_to_wifi_network(self.dut_client, self.wpapsk_2g)
526        wutils.connect_to_wifi_network(self.dut_client, softap)
527        time.sleep(SHORT_TIMEOUT)
528        wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
529        pcap_fname = '%s_%s.pcap' % \
530            (self.pcap_procs[hostapd_constants.BAND_2G][1],
531             hostapd_constants.BAND_2G.upper())
532        packets = rdpcap(pcap_fname)
533        self.verify_mac_not_found_in_pcap(self.soft_ap_factory_mac, packets)
534        self.verify_mac_not_found_in_pcap(self.sta_factory_mac, packets)
535        self.verify_mac_is_found_in_pcap(softap_mac, packets)
536        self.verify_mac_is_found_in_pcap(self.get_sta_mac_address(), packets)
537
538    @test_tracker_info(uuid="3ca3f911-29f1-41fb-b836-4d25eac1669f")
539    def test_roaming_mac_randomization(self):
540        """test MAC randomization in the roaming scenario.
541
542        Steps:
543            1. Connect to network A on AP1, get randomized MAC.
544            2. Set AP1 to MAX attenuation so that we roam to AP2.
545            3. Wait for device to roam to AP2 and get randomized MAC.
546            4. Veirfy that the device uses same AMC for both APs.
547
548        """
549        AP1_network = self.reference_networks[0]["5g"]
550        AP2_network = self.reference_networks[1]["5g"]
551        if "OpenWrtAP" in self.user_params:
552            AP1_network["bssid"] = self.bssid_map[0]["5g"][AP1_network["SSID"]]
553            AP2_network["bssid"] = self.bssid_map[1]["5g"][AP2_network["SSID"]]
554        wutils.set_attns(self.attenuators, "AP1_on_AP2_off", self.roaming_attn)
555        mac_before_roam = self.connect_to_network_and_verify_mac_randomization(
556                AP1_network)
557        wutils.trigger_roaming_and_validate(self.dut, self.attenuators,
558                "AP1_off_AP2_on", AP2_network, self.roaming_attn)
559        mac_after_roam = self.get_randomized_mac(AP2_network)
560        if mac_after_roam != mac_before_roam:
561            raise signals.TestFailure("Randomized MAC address changed after "
562                   "roaming from AP1 to AP2.\nMAC before roam = %s\nMAC after "
563                   "roam = %s" %(mac_before_roam, mac_after_roam))
564        wutils.trigger_roaming_and_validate(self.dut, self.attenuators,
565                "AP1_on_AP2_off", AP1_network, self.roaming_attn)
566        mac_after_roam = self.get_randomized_mac(AP1_network)
567        if mac_after_roam != mac_before_roam:
568            raise signals.TestFailure("Randomized MAC address changed after "
569                   "roaming from AP1 to AP2.\nMAC before roam = %s\nMAC after "
570                   "roam = %s" %(mac_before_roam, mac_after_roam))
571
572    @test_tracker_info(uuid="17b12f1a-7c62-4188-b5a5-52d7a0bb7849")
573    def test_check_mac_sta_with_link_probe(self):
574        """Test to ensure Factory MAC is not exposed, using sniffer data.
575
576        Steps:
577            1. Configure and start the sniffer on 5GHz band.
578            2. Connect to 5GHz network.
579            3. Send link probes.
580            4. Stop the sniffer.
581            5. Invoke scapy to read the .pcap file.
582            6. Read each packet summary and make sure Factory MAC is not used.
583
584        """
585        self.pcap_procs = wutils.start_pcap(
586            self.packet_capture, 'dual', self.test_name)
587        time.sleep(SHORT_TIMEOUT)
588        network = self.wpapsk_5g
589        rand_mac = self.connect_to_network_and_verify_mac_randomization(network)
590        pcap_fname_bflink = '%s_%s.pcap' % \
591            (self.pcap_procs[hostapd_constants.BAND_5G][1],
592             hostapd_constants.BAND_5G.upper())
593        wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
594        time.sleep(SHORT_TIMEOUT)
595        packets_bflink = rdpcap(pcap_fname_bflink)
596        self.verify_mac_not_found_in_pcap(self.sta_factory_mac, packets_bflink)
597        self.verify_mac_is_found_in_pcap(rand_mac, packets_bflink)
598        self.pcap_procs = wutils.start_pcap(
599            self.packet_capture, 'dual', self.test_name)
600        time.sleep(SHORT_TIMEOUT)
601        wutils.send_link_probes(self.dut, 3, 3)
602        pcap_fname = '%s_%s.pcap' % \
603            (self.pcap_procs[hostapd_constants.BAND_5G][1],
604             hostapd_constants.BAND_5G.upper())
605        wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
606        time.sleep(SHORT_TIMEOUT)
607        packets = rdpcap(pcap_fname)
608        self.verify_mac_not_found_in_pcap(self.sta_factory_mac, packets)
609        self.verify_mac_is_found_in_pcap(rand_mac, packets)
610
611    @test_tracker_info(uuid="1c2cc0fd-a340-40c4-b679-6acc5f526451")
612    def test_check_mac_in_wifi_scan(self):
613        """Test to ensure Factory MAC is not exposed, in Wi-Fi scans
614
615        Steps:
616          1. Configure and start the sniffer on both bands.
617          2. Perform a full scan.
618          3. Stop the sniffer.
619          4. Invoke scapy to read the .pcap file.
620          5. Read each packet summary and make sure Factory MAC is not used.
621
622        """
623        self.pcap_procs = wutils.start_pcap(
624            self.packet_capture, 'dual', self.test_name)
625        wutils.start_wifi_connection_scan(self.dut)
626        time.sleep(SHORT_TIMEOUT)
627        wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
628        pcap_fname = '%s_%s.pcap' % \
629            (self.pcap_procs[hostapd_constants.BAND_2G][1],
630             hostapd_constants.BAND_2G.upper())
631        packets = rdpcap(pcap_fname)
632        self.verify_mac_not_found_in_pcap(self.sta_factory_mac, packets)
633
634    @test_tracker_info(uuid="7714d31f-bb08-4f29-b246-0ce1398a3c03")
635    def test_mac_randomization_for_network_suggestion(self):
636        """Add network suggestion and verify MAC randomization.
637
638        Steps:
639            1. Add a network suggestion and verify device connects to it.
640            2. Verify the device uses randomized MAC address for this network.
641        """
642        network_suggestion = self.reference_networks[0]["5g"]
643        self._add_suggestion_and_verify_mac_randomization(network_suggestion)
644
645    @test_tracker_info(uuid="144ad0b4-b79d-4b1d-a8a9-3c612a76c32c")
646    def test_enhanced_mac_randomization_for_network_suggestion(self):
647        """Test enhanced MAC randomization.
648
649        Steps:
650            1. Add a network suggestion with enhanced mac randomization enabled.
651            2. Connect to the network and verify the MAC address is random.
652            3. Remove the suggestion network and add it back.
653            4. Connect to the network. Verify the MAC address is random and
654               different from the randomized MAC observed in step 2.
655        """
656        asserts.skip_if(not self.dut.droid.isSdkAtLeastS(),
657                        "This feature is only supported on S and later.")
658
659        network_suggestion = self.reference_networks[0]["5g"]
660        network_suggestion["enhancedMacRandomizationEnabled"] = True
661
662        # add network suggestion with enhanced mac randomization
663        randomized_mac1 = self._add_suggestion_and_verify_mac_randomization(
664            network_suggestion)
665
666        # remove network suggestion and verify no connection
667        self._remove_suggestion_and_verify_disconnect(network_suggestion)
668
669        # add network suggestion and verify device connects back
670        randomized_mac2 = self._add_suggestion_and_verify_mac_randomization(
671            network_suggestion)
672
673        # verify both randomized mac addrs are different
674        asserts.assert_true(randomized_mac1 != randomized_mac2,
675                            "Randomized MAC addresses are same.")
676