• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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