1#!/usr/bin/env python3 2# 3# Copyright 2017 Google, Inc. 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 logging 18import time 19from acts import utils 20from acts.libs.proc import job 21from acts.controllers.ap_lib import bridge_interface as bi 22from acts_contrib.test_utils.wifi import wifi_test_utils as wutils 23from acts.controllers.adb_lib.error import AdbCommandError 24from acts.controllers.ap_lib import hostapd_security 25from acts.controllers.ap_lib import hostapd_ap_preset 26 27# http://www.secdev.org/projects/scapy/ 28# On ubuntu, sudo pip3 install scapy 29import scapy.all as scapy 30 31GET_FROM_PHONE = 'get_from_dut' 32GET_FROM_AP = 'get_from_ap' 33ENABLED_MODULATED_DTIM = 'gEnableModulatedDTIM=' 34MAX_MODULATED_DTIM = 'gMaxLIModulatedDTIM=' 35 36CHRE_WIFI_SCAN_TYPE = { 37 'active': 'active', 38 'passive': 'passive', 39 'activePassiveDfs': 'active_passive_dfs', 40 'noPreference': 'no_preference' 41} 42 43CHRE_WIFI_RADIO_CHAIN = { 44 'lowLatency': 'low_latency', 45 'lowPower': 'low_power', 46 'highAccuracy': 'high_accuracy' 47} 48 49CHRE_WIFI_CHANNEL_SET = { 50 'all': 'all', 51 'nonDfs': 'non_dfs' 52} 53 54 55def change_dtim(ad, gEnableModulatedDTIM, gMaxLIModulatedDTIM=10): 56 """Function to change the DTIM setting in the phone. 57 58 Args: 59 ad: the target android device, AndroidDevice object 60 gEnableModulatedDTIM: Modulated DTIM, int 61 gMaxLIModulatedDTIM: Maximum modulated DTIM, int 62 """ 63 ad.log.info('Sets dtim to {}'.format(gEnableModulatedDTIM)) 64 65 # In P21 the dtim setting method changed and an AdbCommandError will take 66 # place to get ini_file_phone. Thus add try/except block for the old method. 67 # If error occurs, use change_dtim_adb method later. Otherwise, first trying 68 # to find the ini file with DTIM settings 69 try: 70 ini_file_phone = ad.adb.shell('ls /vendor/firmware/wlan/*/*.ini') 71 72 except AdbCommandError as e: 73 74 # Gets AdbCommandError, change dtim later with change_dtim_adb merthod. 75 # change_dtim_adb requires that wifi connection is on. 76 ad.log.info('Gets AdbCommandError, change dtim with change_dtim_adb.') 77 change_dtim_adb(ad, gEnableModulatedDTIM) 78 return 0 79 80 ini_file_local = ini_file_phone.split('/')[-1] 81 82 # Pull the file and change the DTIM to desired value 83 ad.adb.pull('{} {}'.format(ini_file_phone, ini_file_local)) 84 85 with open(ini_file_local, 'r') as fin: 86 for line in fin: 87 if ENABLED_MODULATED_DTIM in line: 88 gE_old = line.strip('\n') 89 gEDTIM_old = line.strip(ENABLED_MODULATED_DTIM).strip('\n') 90 if MAX_MODULATED_DTIM in line: 91 gM_old = line.strip('\n') 92 gMDTIM_old = line.strip(MAX_MODULATED_DTIM).strip('\n') 93 fin.close() 94 if int(gEDTIM_old) == gEnableModulatedDTIM and int( 95 gMDTIM_old) == gMaxLIModulatedDTIM: 96 ad.log.info('Current DTIM is already the desired value,' 97 'no need to reset it') 98 return 0 99 100 gE_new = ENABLED_MODULATED_DTIM + str(gEnableModulatedDTIM) 101 gM_new = MAX_MODULATED_DTIM + str(gMaxLIModulatedDTIM) 102 103 sed_gE = 'sed -i \'s/{}/{}/g\' {}'.format(gE_old, gE_new, ini_file_local) 104 sed_gM = 'sed -i \'s/{}/{}/g\' {}'.format(gM_old, gM_new, ini_file_local) 105 job.run(sed_gE) 106 job.run(sed_gM) 107 108 # Push the file to the phone 109 push_file_to_phone(ad, ini_file_local, ini_file_phone) 110 ad.log.info('DTIM changes checked in and rebooting...') 111 ad.reboot() 112 # Wait for auto-wifi feature to start 113 time.sleep(20) 114 ad.adb.shell('dumpsys battery set level 100') 115 ad.log.info('DTIM updated and device back from reboot') 116 return 1 117 118def change_dtim_adb(ad, gEnableModulatedDTIM): 119 """Function to change the DTIM setting in the P21 phone. 120 121 This method should be run after connecting wifi. 122 123 Args: 124 ad: the target android device, AndroidDevice object 125 gEnableModulatedDTIM: Modulated DTIM, int 126 """ 127 ad.log.info('Changes DTIM to {} with adb'.format(gEnableModulatedDTIM)) 128 ad.adb.root() 129 screen_status = ad.adb.shell('dumpsys nfc | grep Screen') 130 screen_is_on = 'ON_UNLOCKED' in screen_status 131 132 # To read the dtim with 'adb shell wl bcn_li_dtim', the screen should be off 133 if screen_is_on: 134 ad.log.info('The screen is on. Set it to off before change dtim') 135 ad.droid.goToSleepNow() 136 time_limit_seconds = 60 137 _wait_screen_off(ad, time_limit_seconds) 138 139 old_dtim = _read_dtim_adb(ad) 140 ad.log.info('The dtim before change is {}'.format(old_dtim)) 141 try: 142 if int(old_dtim) == gEnableModulatedDTIM: 143 ad.log.info('Current DTIM is already the desired value,' 144 'no need to reset it') 145 if screen_is_on: 146 ad.log.info('Changes the screen to the original on status') 147 ad.droid.wakeUpNow() 148 return 149 except Exception as e: 150 ad.log.info('old_dtim is not available from adb') 151 152 current_dtim = _set_dtim(ad, gEnableModulatedDTIM) 153 ad.log.info( 154 'Old DTIM is {}, current DTIM is {}'.format(old_dtim, current_dtim)) 155 if screen_is_on: 156 ad.log.info('Changes the screen to the original on status') 157 ad.droid.wakeUpNow() 158 159def _set_dtim(ad, gEnableModulatedDTIM): 160 out = ad.adb.shell("halutil -dtim_config {}".format(gEnableModulatedDTIM)) 161 ad.log.info('set dtim to {}, stdout: {}'.format( 162 gEnableModulatedDTIM, out)) 163 return _read_dtim_adb(ad) 164 165def _read_dtim_adb(ad): 166 try: 167 old_dtim = ad.adb.shell('wl bcn_li_dtim') 168 return old_dtim 169 except Exception as e: 170 ad.log.info('When reading dtim get error {}'.format(e)) 171 return 'The dtim value is not available from adb' 172 173def _wait_screen_off(ad, time_limit_seconds): 174 while time_limit_seconds > 0: 175 screen_status = ad.adb.shell('dumpsys nfc | grep Screen') 176 if 'OFF_UNLOCKED' in screen_status: 177 ad.log.info('The screen status is {}'.format(screen_status)) 178 return 179 time.sleep(1) 180 time_limit_seconds -= 1 181 raise TimeoutError('Timed out while waiting the screen off after {} ' 182 'seconds.'.format(time_limit_seconds)) 183 184 185def push_file_to_phone(ad, file_local, file_phone): 186 """Function to push local file to android phone. 187 188 Args: 189 ad: the target android device 190 file_local: the locla file to push 191 file_phone: the file/directory on the phone to be pushed 192 """ 193 ad.adb.root() 194 cmd_out = ad.adb.remount() 195 if 'Permission denied' in cmd_out: 196 ad.log.info('Need to disable verity first and reboot') 197 ad.adb.disable_verity() 198 time.sleep(1) 199 ad.reboot() 200 ad.log.info('Verity disabled and device back from reboot') 201 ad.adb.root() 202 ad.adb.remount() 203 time.sleep(1) 204 ad.adb.push('{} {}'.format(file_local, file_phone)) 205 206 207def ap_setup(ap, network, bandwidth=80, dtim_period=None): 208 """Set up the whirlwind AP with provided network info. 209 210 Args: 211 ap: access_point object of the AP 212 network: dict with information of the network, including ssid, password 213 bssid, channel etc. 214 bandwidth: the operation bandwidth for the AP, default 80MHz 215 dtim_period: the dtim period of access point 216 Returns: 217 brconfigs: the bridge interface configs 218 """ 219 log = logging.getLogger() 220 bss_settings = [] 221 ssid = network[wutils.WifiEnums.SSID_KEY] 222 if "password" in network.keys(): 223 password = network["password"] 224 security = hostapd_security.Security( 225 security_mode="wpa", password=password) 226 else: 227 security = hostapd_security.Security(security_mode=None, password=None) 228 channel = network["channel"] 229 config = hostapd_ap_preset.create_ap_preset( 230 channel=channel, 231 ssid=ssid, 232 dtim_period=dtim_period, 233 security=security, 234 bss_settings=bss_settings, 235 vht_bandwidth=bandwidth, 236 profile_name='whirlwind', 237 iface_wlan_2g=ap.wlan_2g, 238 iface_wlan_5g=ap.wlan_5g) 239 config_bridge = ap.generate_bridge_configs(channel) 240 brconfigs = bi.BridgeInterfaceConfigs(config_bridge[0], config_bridge[1], 241 config_bridge[2]) 242 ap.bridge.startup(brconfigs) 243 ap.start_ap(config) 244 log.info("AP started on channel {} with SSID {}".format(channel, ssid)) 245 return brconfigs 246 247 248def run_iperf_client_nonblocking(ad, server_host, extra_args=""): 249 """Start iperf client on the device with nohup. 250 251 Return status as true if iperf client start successfully. 252 And data flow information as results. 253 254 Args: 255 ad: the android device under test 256 server_host: Address of the iperf server. 257 extra_args: A string representing extra arguments for iperf client, 258 e.g. "-i 1 -t 30". 259 260 """ 261 log = logging.getLogger() 262 ad.adb.shell_nb("nohup >/dev/null 2>&1 sh -c 'iperf3 -c {} {} &'".format( 263 server_host, extra_args)) 264 log.info("IPerf client started") 265 266 267def get_wifi_rssi(ad): 268 """Get the RSSI of the device. 269 270 Args: 271 ad: the android device under test 272 Returns: 273 RSSI: the rssi level of the device 274 """ 275 RSSI = ad.droid.wifiGetConnectionInfo()['rssi'] 276 return RSSI 277 278 279def get_phone_ip(ad): 280 """Get the WiFi IP address of the phone. 281 282 Args: 283 ad: the android device under test 284 Returns: 285 IP: IP address of the phone for WiFi, as a string 286 """ 287 IP = ad.droid.connectivityGetIPv4Addresses('wlan0')[0] 288 289 return IP 290 291 292def get_phone_mac(ad): 293 """Get the WiFi MAC address of the phone. 294 295 Args: 296 ad: the android device under test 297 Returns: 298 mac: MAC address of the phone for WiFi, as a string 299 """ 300 mac = ad.droid.wifiGetConnectionInfo()["mac_address"] 301 302 return mac 303 304 305def get_phone_ipv6(ad): 306 """Get the WiFi IPV6 address of the phone. 307 308 Args: 309 ad: the android device under test 310 Returns: 311 IPv6: IPv6 address of the phone for WiFi, as a string 312 """ 313 IPv6 = ad.droid.connectivityGetLinkLocalIpv6Address('wlan0')[:-6] 314 315 return IPv6 316 317 318def wait_for_dhcp(interface_name): 319 """Wait the DHCP address assigned to desired interface. 320 321 Getting DHCP address takes time and the wait time isn't constant. Utilizing 322 utils.timeout to keep trying until success 323 324 Args: 325 interface_name: desired interface name 326 Returns: 327 ip: ip address of the desired interface name 328 Raise: 329 TimeoutError: After timeout, if no DHCP assigned, raise 330 """ 331 log = logging.getLogger() 332 reset_host_interface(interface_name) 333 start_time = time.time() 334 time_limit_seconds = 60 335 ip = '0.0.0.0' 336 while start_time + time_limit_seconds > time.time(): 337 ip = scapy.get_if_addr(interface_name) 338 if ip == '0.0.0.0': 339 time.sleep(1) 340 else: 341 log.info( 342 'DHCP address assigned to %s as %s' % (interface_name, ip)) 343 return ip 344 raise TimeoutError('Timed out while getting if_addr after %s seconds.' % 345 time_limit_seconds) 346 347 348def reset_host_interface(intferface_name): 349 """Reset the host interface. 350 351 Args: 352 intferface_name: the desired interface to reset 353 """ 354 log = logging.getLogger() 355 intf_down_cmd = 'ifconfig %s down' % intferface_name 356 intf_up_cmd = 'ifconfig %s up' % intferface_name 357 try: 358 job.run(intf_down_cmd) 359 time.sleep(10) 360 job.run(intf_up_cmd) 361 log.info('{} has been reset'.format(intferface_name)) 362 except job.Error: 363 raise Exception('No such interface') 364 365 366def bringdown_host_interface(intferface_name): 367 """Reset the host interface. 368 369 Args: 370 intferface_name: the desired interface to reset 371 """ 372 log = logging.getLogger() 373 intf_down_cmd = 'ifconfig %s down' % intferface_name 374 try: 375 job.run(intf_down_cmd) 376 time.sleep(2) 377 log.info('{} has been brought down'.format(intferface_name)) 378 except job.Error: 379 raise Exception('No such interface') 380 381 382def create_pkt_config(test_class): 383 """Creates the config for generating multicast packets 384 385 Args: 386 test_class: object with all networking paramters 387 388 Returns: 389 Dictionary with the multicast packet config 390 """ 391 addr_type = (scapy.IPV6_ADDR_LINKLOCAL 392 if test_class.ipv6_src_type == 'LINK_LOCAL' else 393 scapy.IPV6_ADDR_GLOBAL) 394 395 mac_dst = test_class.mac_dst 396 if GET_FROM_PHONE in test_class.mac_dst: 397 mac_dst = get_phone_mac(test_class.dut) 398 399 ipv4_dst = test_class.ipv4_dst 400 if GET_FROM_PHONE in test_class.ipv4_dst: 401 ipv4_dst = get_phone_ip(test_class.dut) 402 403 ipv6_dst = test_class.ipv6_dst 404 if GET_FROM_PHONE in test_class.ipv6_dst: 405 ipv6_dst = get_phone_ipv6(test_class.dut) 406 407 ipv4_gw = test_class.ipv4_gwt 408 if GET_FROM_AP in test_class.ipv4_gwt: 409 ipv4_gw = test_class.access_point.ssh_settings.hostname 410 411 pkt_gen_config = { 412 'interf': test_class.pkt_sender.interface, 413 'subnet_mask': test_class.sub_mask, 414 'src_mac': test_class.mac_src, 415 'dst_mac': mac_dst, 416 'src_ipv4': test_class.ipv4_src, 417 'dst_ipv4': ipv4_dst, 418 'src_ipv6': test_class.ipv6_src, 419 'src_ipv6_type': addr_type, 420 'dst_ipv6': ipv6_dst, 421 'gw_ipv4': ipv4_gw 422 } 423 return pkt_gen_config 424