1#!/usr/bin/env python3.4
2#
3#   Copyright 2018 - 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 time
21
22import acts.base_test
23import acts.signals as signals
24from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
25import acts.test_utils.wifi.wifi_test_utils as wutils
26import acts.utils as utils
27
28from acts import asserts
29from acts.test_decorators import test_tracker_info
30from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
31
32WifiEnums = wutils.WifiEnums
33# Default timeout used for reboot, toggle WiFi and Airplane mode,
34# for the system to settle down after the operation.
35DEFAULT_TIMEOUT = 10
36GET_MAC_ADDRESS= ("ip addr show wlan0"
37                  "| grep 'link/ether'"
38                  "| cut -d ' ' -f6")
39MAC_SETTING = "wifi_connected_mac_randomization_enabled"
40GET_MAC_RANDOMIZATION_STATUS = "settings get global {}".format(MAC_SETTING)
41TURN_ON_MAC_RANDOMIZATION = "settings put global {} 1".format(MAC_SETTING)
42TURN_OFF_MAC_RANDOMIZATION = "settings put global {} 0".format(MAC_SETTING)
43LOG_CLEAR = "logcat -c"
44LOG_GREP = "logcat -d | grep {}"
45
46class WifiConnectedMacRandomizationTest(WifiBaseTest):
47    """Tests for Connected MAC Randomization.
48
49    Test Bed Requirement:
50    * Two Android devices with the first one supporting MAC randomization.
51    * At least two Wi-Fi networks to connect to.
52    """
53
54    def setup_class(self):
55        super().setup_class()
56
57        self.dut = self.android_devices[0]
58        self.dut_softap = self.android_devices[1]
59        wutils.wifi_test_device_init(self.dut)
60        wutils.wifi_test_device_init(self.dut_softap)
61
62        self.reset_mac_address_to_factory_mac()
63        self.dut.adb.shell(TURN_ON_MAC_RANDOMIZATION)
64        asserts.assert_equal(
65            self.dut.adb.shell(GET_MAC_RANDOMIZATION_STATUS), "1",
66            "Failed to enable Connected MAC Randomization on dut.")
67
68        req_params = ["reference_networks"]
69        opt_param = []
70        self.unpack_userparams(
71            req_param_names=req_params, opt_param_names=opt_param)
72
73        if "AccessPoint" in self.user_params:
74            self.legacy_configure_ap_and_start()
75
76        asserts.assert_true(
77            self.reference_networks[0]["2g"],
78            "Need at least 1 2.4Ghz reference network with psk.")
79        asserts.assert_true(
80            self.reference_networks[0]["5g"],
81            "Need at least 1 5Ghz reference network with psk.")
82        self.wpapsk_2g = self.reference_networks[0]["2g"]
83        self.wpapsk_5g = self.reference_networks[0]["5g"]
84
85    def setup_test(self):
86        self.dut.droid.wakeLockAcquireBright()
87        self.dut.droid.wakeUpNow()
88        wutils.wifi_toggle_state(self.dut, True)
89        wutils.wifi_toggle_state(self.dut_softap, False)
90
91    def teardown_test(self):
92        self.dut.droid.wakeLockRelease()
93        self.dut.droid.goToSleepNow()
94        wutils.reset_wifi(self.dut)
95        wutils.reset_wifi(self.dut_softap)
96
97    def on_fail(self, test_name, begin_time):
98        self.dut.take_bug_report(test_name, begin_time)
99        self.dut.cat_adb_log(test_name, begin_time)
100
101    def teardown_class(self):
102        wutils.stop_wifi_tethering(self.dut_softap)
103        self.reset_mac_address_to_factory_mac()
104        if "AccessPoint" in self.user_params:
105            del self.user_params["reference_networks"]
106            del self.user_params["open_network"]
107
108    """Helper Functions"""
109    def get_current_mac_address(self, ad):
110        """Get the device's wlan0 MAC address.
111
112        Args:
113            ad: AndroidDevice to get MAC address of.
114
115        Returns:
116            A MAC address string in the format of "12:34:56:78:90:12".
117        """
118        return ad.adb.shell(GET_MAC_ADDRESS)
119
120    def is_valid_randomized_mac_address(self, mac):
121        """Check if the given MAC address is a valid randomized MAC address.
122
123        Args:
124            mac: MAC address to check in the format of "12:34:56:78:90:12".
125        """
126        asserts.assert_true(
127            mac != self.dut_factory_mac,
128            "Randomized MAC address is same as factory MAC address.")
129        first_byte = int(mac[:2], 16)
130        asserts.assert_equal(first_byte & 1, 0, "MAC address is not unicast.")
131        asserts.assert_equal(first_byte & 2, 2, "MAC address is not local.")
132
133    def reset_mac_address_to_factory_mac(self):
134        """Reset dut to and store factory MAC address by turning off
135        Connected MAC Randomization and rebooting dut.
136        """
137        self.dut.adb.shell(TURN_OFF_MAC_RANDOMIZATION)
138        asserts.assert_equal(
139            self.dut.adb.shell(GET_MAC_RANDOMIZATION_STATUS), "0",
140            "Failed to disable Connected MAC Randomization on dut.")
141        self.dut.reboot()
142        time.sleep(DEFAULT_TIMEOUT)
143        self.dut_factory_mac = self.get_current_mac_address(self.dut)
144
145    def get_connection_data(self, ad, network):
146        """Connect and get network id and ssid info from connection data.
147
148        Args:
149            ad: AndroidDevice to use for connection
150            network: network info of the network to connect to
151
152        Returns:
153            A convenience dict with the connected network's ID and SSID.
154        """
155        wutils.connect_to_wifi_network(ad, network)
156        connect_data = ad.droid.wifiGetConnectionInfo()
157        ssid_id_dict = dict()
158        ssid_id_dict[WifiEnums.NETID_KEY] = connect_data[WifiEnums.NETID_KEY]
159        ssid_id_dict[WifiEnums.SSID_KEY] = connect_data[WifiEnums.SSID_KEY]
160        return ssid_id_dict
161
162    """Tests"""
163    @test_tracker_info(uuid="")
164    def test_wifi_connection_2G_with_mac_randomization(self):
165        """Tests connection to 2G network with Connected MAC Randomization.
166        """
167        wutils.connect_to_wifi_network(self.dut, self.wpapsk_2g)
168        mac = self.get_current_mac_address(self.dut)
169        self.is_valid_randomized_mac_address(mac)
170
171    @test_tracker_info(uuid="")
172    def test_wifi_connection_5G_with_mac_randomization(self):
173        """Tests connection to 5G network with Connected MAC Randomization.
174        """
175        wutils.connect_to_wifi_network(self.dut, self.wpapsk_5g)
176        mac = self.get_current_mac_address(self.dut)
177        self.is_valid_randomized_mac_address(mac)
178
179    @test_tracker_info(uuid="")
180    def test_randomized_mac_persistent_between_connections(self):
181        """Tests that randomized MAC address assigned to each network is
182        persistent between connections.
183
184        Steps:
185        1. Connect to a 2GHz network.
186        2. Connect to a 5GHz network.
187        3. Reconnect to the 2GHz network using its network id.
188        4. Verify that MAC addresses in Steps 1 and 3 are equal.
189        5. Reconnect to the 5GHz network using its network id.
190        6. Verify that MAC addresses in Steps 2 and 5 are equal.
191        """
192        connect_data_2g = self.get_connection_data(self.dut, self.wpapsk_2g)
193        old_mac_2g = self.get_current_mac_address(self.dut)
194        self.is_valid_randomized_mac_address(old_mac_2g)
195
196        connect_data_5g = self.get_connection_data(self.dut, self.wpapsk_5g)
197        old_mac_5g = self.get_current_mac_address(self.dut)
198        self.is_valid_randomized_mac_address(old_mac_5g)
199
200        asserts.assert_true(
201            old_mac_2g != old_mac_5g,
202            "Randomized MAC addresses for 2G and 5G networks are equal.")
203
204        reconnect_2g = wutils.connect_to_wifi_network_with_id(
205            self.dut,
206            connect_data_2g[WifiEnums.NETID_KEY],
207            connect_data_2g[WifiEnums.SSID_KEY])
208        if not reconnect_2g:
209            raise signals.TestFailure("Device did not connect to the correct"
210                                      " 2GHz network.")
211        new_mac_2g = self.get_current_mac_address(self.dut)
212        asserts.assert_equal(
213            old_mac_2g,
214            new_mac_2g,
215            "Randomized MAC for 2G is not persistent between connections.")
216
217        reconnect_5g = wutils.connect_to_wifi_network_with_id(
218            self.dut,
219            connect_data_5g[WifiEnums.NETID_KEY],
220            connect_data_5g[WifiEnums.SSID_KEY])
221        if not reconnect_5g:
222            raise signals.TestFailure("Device did not connect to the correct"
223                                      " 5GHz network.")
224        new_mac_5g = self.get_current_mac_address(self.dut)
225        asserts.assert_equal(
226            old_mac_5g,
227            new_mac_5g,
228            "Randomized MAC for 5G is not persistent between connections.")
229
230    @test_tracker_info(uuid="")
231    def test_randomized_mac_used_during_connection(self):
232        """Verify that the randomized MAC address and not the factory
233        MAC address is used during connection by checking the softap logs.
234
235        Steps:
236        1. Set up softAP on dut_softap.
237        2. Have dut connect to the softAp.
238        3. Verify that only randomized MAC appears in softAp logs.
239        """
240        self.dut_softap.adb.shell(LOG_CLEAR)
241        config = wutils.create_softap_config()
242        wutils.start_wifi_tethering(self.dut_softap,
243                                    config[wutils.WifiEnums.SSID_KEY],
244                                    config[wutils.WifiEnums.PWD_KEY],
245                                    WIFI_CONFIG_APBAND_2G)
246
247        # Internet validation fails when dut_softap does not have a valid sim
248        # supporting softap. Since this test is not checking for internet
249        # validation, we suppress failure signals.
250        wutils.connect_to_wifi_network(self.dut, config, assert_on_fail=False)
251        mac = self.get_current_mac_address(self.dut)
252        wutils.stop_wifi_tethering(self.dut_softap)
253
254        self.is_valid_randomized_mac_address(mac)
255        log = self.dut_softap.adb.shell(LOG_GREP.format(mac))
256        asserts.assert_true(len(log) > 0, "Randomized MAC not in log.")
257        log = self.dut_softap.adb.shell(LOG_GREP.format(self.dut_factory_mac))
258        asserts.assert_true(len(log) == 0, "Factory MAC is in log.")
259