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