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_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
25import acts_contrib.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_contrib.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        super().setup_test()
87        self.dut.droid.wakeLockAcquireBright()
88        self.dut.droid.wakeUpNow()
89        wutils.wifi_toggle_state(self.dut, True)
90        wutils.wifi_toggle_state(self.dut_softap, False)
91
92    def teardown_test(self):
93        super().teardown_test()
94        self.dut.droid.wakeLockRelease()
95        self.dut.droid.goToSleepNow()
96        wutils.reset_wifi(self.dut)
97        wutils.reset_wifi(self.dut_softap)
98
99    def teardown_class(self):
100        wutils.stop_wifi_tethering(self.dut_softap)
101        self.reset_mac_address_to_factory_mac()
102        if "AccessPoint" in self.user_params:
103            del self.user_params["reference_networks"]
104            del self.user_params["open_network"]
105
106    """Helper Functions"""
107    def get_current_mac_address(self, ad):
108        """Get the device's wlan0 MAC address.
109
110        Args:
111            ad: AndroidDevice to get MAC address of.
112
113        Returns:
114            A MAC address string in the format of "12:34:56:78:90:12".
115        """
116        return ad.adb.shell(GET_MAC_ADDRESS)
117
118    def is_valid_randomized_mac_address(self, mac):
119        """Check if the given MAC address is a valid randomized MAC address.
120
121        Args:
122            mac: MAC address to check in the format of "12:34:56:78:90:12".
123        """
124        asserts.assert_true(
125            mac != self.dut_factory_mac,
126            "Randomized MAC address is same as factory MAC address.")
127        first_byte = int(mac[:2], 16)
128        asserts.assert_equal(first_byte & 1, 0, "MAC address is not unicast.")
129        asserts.assert_equal(first_byte & 2, 2, "MAC address is not local.")
130
131    def reset_mac_address_to_factory_mac(self):
132        """Reset dut to and store factory MAC address by turning off
133        Connected MAC Randomization and rebooting dut.
134        """
135        self.dut.adb.shell(TURN_OFF_MAC_RANDOMIZATION)
136        asserts.assert_equal(
137            self.dut.adb.shell(GET_MAC_RANDOMIZATION_STATUS), "0",
138            "Failed to disable Connected MAC Randomization on dut.")
139        self.dut.reboot()
140        time.sleep(DEFAULT_TIMEOUT)
141        self.dut_factory_mac = self.get_current_mac_address(self.dut)
142
143    def get_connection_data(self, ad, network):
144        """Connect and get network id and ssid info from connection data.
145
146        Args:
147            ad: AndroidDevice to use for connection
148            network: network info of the network to connect to
149
150        Returns:
151            A convenience dict with the connected network's ID and SSID.
152        """
153        wutils.connect_to_wifi_network(ad, network)
154        connect_data = ad.droid.wifiGetConnectionInfo()
155        ssid_id_dict = dict()
156        ssid_id_dict[WifiEnums.NETID_KEY] = connect_data[WifiEnums.NETID_KEY]
157        ssid_id_dict[WifiEnums.SSID_KEY] = connect_data[WifiEnums.SSID_KEY]
158        return ssid_id_dict
159
160    """Tests"""
161    @test_tracker_info(uuid="")
162    def test_wifi_connection_2G_with_mac_randomization(self):
163        """Tests connection to 2G network with Connected MAC Randomization.
164        """
165        wutils.connect_to_wifi_network(self.dut, self.wpapsk_2g)
166        mac = self.get_current_mac_address(self.dut)
167        self.is_valid_randomized_mac_address(mac)
168
169    @test_tracker_info(uuid="")
170    def test_wifi_connection_5G_with_mac_randomization(self):
171        """Tests connection to 5G network with Connected MAC Randomization.
172        """
173        wutils.connect_to_wifi_network(self.dut, self.wpapsk_5g)
174        mac = self.get_current_mac_address(self.dut)
175        self.is_valid_randomized_mac_address(mac)
176
177    @test_tracker_info(uuid="")
178    def test_randomized_mac_persistent_between_connections(self):
179        """Tests that randomized MAC address assigned to each network is
180        persistent between connections.
181
182        Steps:
183        1. Connect to a 2GHz network.
184        2. Connect to a 5GHz network.
185        3. Reconnect to the 2GHz network using its network id.
186        4. Verify that MAC addresses in Steps 1 and 3 are equal.
187        5. Reconnect to the 5GHz network using its network id.
188        6. Verify that MAC addresses in Steps 2 and 5 are equal.
189        """
190        connect_data_2g = self.get_connection_data(self.dut, self.wpapsk_2g)
191        old_mac_2g = self.get_current_mac_address(self.dut)
192        self.is_valid_randomized_mac_address(old_mac_2g)
193
194        connect_data_5g = self.get_connection_data(self.dut, self.wpapsk_5g)
195        old_mac_5g = self.get_current_mac_address(self.dut)
196        self.is_valid_randomized_mac_address(old_mac_5g)
197
198        asserts.assert_true(
199            old_mac_2g != old_mac_5g,
200            "Randomized MAC addresses for 2G and 5G networks are equal.")
201
202        reconnect_2g = wutils.connect_to_wifi_network_with_id(
203            self.dut,
204            connect_data_2g[WifiEnums.NETID_KEY],
205            connect_data_2g[WifiEnums.SSID_KEY])
206        if not reconnect_2g:
207            raise signals.TestFailure("Device did not connect to the correct"
208                                      " 2GHz network.")
209        new_mac_2g = self.get_current_mac_address(self.dut)
210        asserts.assert_equal(
211            old_mac_2g,
212            new_mac_2g,
213            "Randomized MAC for 2G is not persistent between connections.")
214
215        reconnect_5g = wutils.connect_to_wifi_network_with_id(
216            self.dut,
217            connect_data_5g[WifiEnums.NETID_KEY],
218            connect_data_5g[WifiEnums.SSID_KEY])
219        if not reconnect_5g:
220            raise signals.TestFailure("Device did not connect to the correct"
221                                      " 5GHz network.")
222        new_mac_5g = self.get_current_mac_address(self.dut)
223        asserts.assert_equal(
224            old_mac_5g,
225            new_mac_5g,
226            "Randomized MAC for 5G is not persistent between connections.")
227
228    @test_tracker_info(uuid="")
229    def test_randomized_mac_used_during_connection(self):
230        """Verify that the randomized MAC address and not the factory
231        MAC address is used during connection by checking the softap logs.
232
233        Steps:
234        1. Set up softAP on dut_softap.
235        2. Have dut connect to the softAp.
236        3. Verify that only randomized MAC appears in softAp logs.
237        """
238        self.dut_softap.adb.shell(LOG_CLEAR)
239        config = wutils.create_softap_config()
240        wutils.start_wifi_tethering(self.dut_softap,
241                                    config[wutils.WifiEnums.SSID_KEY],
242                                    config[wutils.WifiEnums.PWD_KEY],
243                                    WIFI_CONFIG_APBAND_2G)
244
245        # Internet validation fails when dut_softap does not have a valid sim
246        # supporting softap. Since this test is not checking for internet
247        # validation, we suppress failure signals.
248        wutils.connect_to_wifi_network(self.dut, config, assert_on_fail=False)
249        mac = self.get_current_mac_address(self.dut)
250        wutils.stop_wifi_tethering(self.dut_softap)
251
252        self.is_valid_randomized_mac_address(mac)
253        log = self.dut_softap.adb.shell(LOG_GREP.format(mac))
254        asserts.assert_true(len(log) > 0, "Randomized MAC not in log.")
255        log = self.dut_softap.adb.shell(LOG_GREP.format(self.dut_factory_mac))
256        asserts.assert_true(len(log) == 0, "Factory MAC is in log.")
257