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