Mark De Ruyter | 1a7ae57 | 2018-03-02 15:35:36 -0800 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 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 | |
| 17 | import logging |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 18 | import time |
| 19 | from acts import utils |
Qi Jiang | b411031 | 2017-10-05 22:00:29 -0700 | [diff] [blame] | 20 | from acts.libs.proc import job |
Qi Jiang | 53ff16a | 2018-02-15 13:12:10 -0800 | [diff] [blame] | 21 | from acts.controllers.ap_lib import bridge_interface as bi |
Xianyuan Jia | 63751fb | 2020-11-17 00:07:40 +0000 | [diff] [blame] | 22 | from acts_contrib.test_utils.wifi import wifi_test_utils as wutils |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 23 | from acts.controllers.ap_lib import hostapd_security |
| 24 | from acts.controllers.ap_lib import hostapd_ap_preset |
Daniel Barros | d167271 | 2017-11-06 21:50:50 -0800 | [diff] [blame] | 25 | |
Daniel Barros | 1cefafd | 2017-08-04 15:36:40 -0700 | [diff] [blame] | 26 | # http://www.secdev.org/projects/scapy/ |
Daniel Barros | 57c14d8 | 2019-01-17 19:03:05 -0800 | [diff] [blame] | 27 | # On ubuntu, sudo pip3 install scapy |
Daniel Barros | 1cefafd | 2017-08-04 15:36:40 -0700 | [diff] [blame] | 28 | import scapy.all as scapy |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 29 | |
Daniel Barros | 32ed847 | 2017-09-30 16:45:37 -0700 | [diff] [blame] | 30 | GET_FROM_PHONE = 'get_from_dut' |
| 31 | GET_FROM_AP = 'get_from_ap' |
Qi Jiang | 6035b44 | 2018-04-20 15:38:35 -0700 | [diff] [blame] | 32 | ENABLED_MODULATED_DTIM = 'gEnableModulatedDTIM=' |
| 33 | MAX_MODULATED_DTIM = 'gMaxLIModulatedDTIM=' |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 34 | |
| 35 | |
Qi Jiang | e26642e | 2017-11-03 14:57:25 -0700 | [diff] [blame] | 36 | def change_dtim(ad, gEnableModulatedDTIM, gMaxLIModulatedDTIM=10): |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 37 | """Function to change the DTIM setting in the phone. |
| 38 | |
| 39 | Args: |
| 40 | ad: the target android device, AndroidDevice object |
| 41 | gEnableModulatedDTIM: Modulated DTIM, int |
| 42 | gMaxLIModulatedDTIM: Maximum modulated DTIM, int |
| 43 | """ |
Qi Jiang | 6035b44 | 2018-04-20 15:38:35 -0700 | [diff] [blame] | 44 | # First trying to find the ini file with DTIM settings |
| 45 | ini_file_phone = ad.adb.shell('ls /vendor/firmware/wlan/*/*.ini') |
| 46 | ini_file_local = ini_file_phone.split('/')[-1] |
| 47 | |
| 48 | # Pull the file and change the DTIM to desired value |
| 49 | ad.adb.pull('{} {}'.format(ini_file_phone, ini_file_local)) |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 50 | |
| 51 | with open(ini_file_local, 'r') as fin: |
| 52 | for line in fin: |
Qi Jiang | 6035b44 | 2018-04-20 15:38:35 -0700 | [diff] [blame] | 53 | if ENABLED_MODULATED_DTIM in line: |
| 54 | gE_old = line.strip('\n') |
| 55 | gEDTIM_old = line.strip(ENABLED_MODULATED_DTIM).strip('\n') |
| 56 | if MAX_MODULATED_DTIM in line: |
| 57 | gM_old = line.strip('\n') |
| 58 | gMDTIM_old = line.strip(MAX_MODULATED_DTIM).strip('\n') |
| 59 | fin.close() |
Qi Jiang | e26642e | 2017-11-03 14:57:25 -0700 | [diff] [blame] | 60 | if int(gEDTIM_old) == gEnableModulatedDTIM and int( |
| 61 | gMDTIM_old) == gMaxLIModulatedDTIM: |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 62 | ad.log.info('Current DTIM is already the desired value,' |
| 63 | 'no need to reset it') |
Qi Jiang | 1785a4a | 2018-05-20 17:20:32 -0700 | [diff] [blame] | 64 | return 0 |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 65 | |
Qi Jiang | 6035b44 | 2018-04-20 15:38:35 -0700 | [diff] [blame] | 66 | gE_new = ENABLED_MODULATED_DTIM + str(gEnableModulatedDTIM) |
| 67 | gM_new = MAX_MODULATED_DTIM + str(gMaxLIModulatedDTIM) |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 68 | |
Qi Jiang | 6035b44 | 2018-04-20 15:38:35 -0700 | [diff] [blame] | 69 | sed_gE = 'sed -i \'s/{}/{}/g\' {}'.format(gE_old, gE_new, ini_file_local) |
| 70 | sed_gM = 'sed -i \'s/{}/{}/g\' {}'.format(gM_old, gM_new, ini_file_local) |
| 71 | job.run(sed_gE) |
| 72 | job.run(sed_gM) |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 73 | |
Qi Jiang | 6035b44 | 2018-04-20 15:38:35 -0700 | [diff] [blame] | 74 | # Push the file to the phone |
| 75 | push_file_to_phone(ad, ini_file_local, ini_file_phone) |
| 76 | ad.log.info('DTIM changes checked in and rebooting...') |
| 77 | ad.reboot() |
Qi Jiang | 1785a4a | 2018-05-20 17:20:32 -0700 | [diff] [blame] | 78 | # Wait for auto-wifi feature to start |
| 79 | time.sleep(20) |
Qi Jiang | 83f4588 | 2018-09-18 11:50:06 -0700 | [diff] [blame] | 80 | ad.adb.shell('dumpsys battery set level 100') |
Qi Jiang | 6035b44 | 2018-04-20 15:38:35 -0700 | [diff] [blame] | 81 | ad.log.info('DTIM updated and device back from reboot') |
Qi Jiang | 1785a4a | 2018-05-20 17:20:32 -0700 | [diff] [blame] | 82 | return 1 |
Qi Jiang | 6035b44 | 2018-04-20 15:38:35 -0700 | [diff] [blame] | 83 | |
| 84 | |
| 85 | def push_file_to_phone(ad, file_local, file_phone): |
| 86 | """Function to push local file to android phone. |
| 87 | |
| 88 | Args: |
| 89 | ad: the target android device |
| 90 | file_local: the locla file to push |
| 91 | file_phone: the file/directory on the phone to be pushed |
| 92 | """ |
| 93 | ad.adb.root() |
| 94 | cmd_out = ad.adb.remount() |
| 95 | if 'Permission denied' in cmd_out: |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 96 | ad.log.info('Need to disable verity first and reboot') |
Qi Jiang | 6035b44 | 2018-04-20 15:38:35 -0700 | [diff] [blame] | 97 | ad.adb.disable_verity() |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 98 | time.sleep(1) |
| 99 | ad.reboot() |
| 100 | ad.log.info('Verity disabled and device back from reboot') |
Qi Jiang | 6035b44 | 2018-04-20 15:38:35 -0700 | [diff] [blame] | 101 | ad.adb.root() |
| 102 | ad.adb.remount() |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 103 | time.sleep(1) |
Qi Jiang | 6035b44 | 2018-04-20 15:38:35 -0700 | [diff] [blame] | 104 | ad.adb.push('{} {}'.format(file_local, file_phone)) |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 105 | |
| 106 | |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 107 | def ap_setup(ap, network, bandwidth=80): |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 108 | """Set up the whirlwind AP with provided network info. |
| 109 | |
| 110 | Args: |
| 111 | ap: access_point object of the AP |
| 112 | network: dict with information of the network, including ssid, password |
| 113 | bssid, channel etc. |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 114 | bandwidth: the operation bandwidth for the AP, default 80MHz |
Qi Jiang | 53ff16a | 2018-02-15 13:12:10 -0800 | [diff] [blame] | 115 | Returns: |
| 116 | brconfigs: the bridge interface configs |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 117 | """ |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 118 | log = logging.getLogger() |
| 119 | bss_settings = [] |
| 120 | ssid = network[wutils.WifiEnums.SSID_KEY] |
Qi Jiang | b411031 | 2017-10-05 22:00:29 -0700 | [diff] [blame] | 121 | if "password" in network.keys(): |
| 122 | password = network["password"] |
| 123 | security = hostapd_security.Security( |
| 124 | security_mode="wpa", password=password) |
| 125 | else: |
| 126 | security = hostapd_security.Security(security_mode=None, password=None) |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 127 | channel = network["channel"] |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 128 | config = hostapd_ap_preset.create_ap_preset( |
| 129 | channel=channel, |
| 130 | ssid=ssid, |
| 131 | security=security, |
| 132 | bss_settings=bss_settings, |
Qi Jiang | ef7de18 | 2017-12-05 14:56:08 -0800 | [diff] [blame] | 133 | vht_bandwidth=bandwidth, |
| 134 | profile_name='whirlwind', |
| 135 | iface_wlan_2g=ap.wlan_2g, |
| 136 | iface_wlan_5g=ap.wlan_5g) |
Qi Jiang | 53ff16a | 2018-02-15 13:12:10 -0800 | [diff] [blame] | 137 | config_bridge = ap.generate_bridge_configs(channel) |
| 138 | brconfigs = bi.BridgeInterfaceConfigs(config_bridge[0], config_bridge[1], |
| 139 | config_bridge[2]) |
| 140 | ap.bridge.startup(brconfigs) |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 141 | ap.start_ap(config) |
| 142 | log.info("AP started on channel {} with SSID {}".format(channel, ssid)) |
Qi Jiang | 53ff16a | 2018-02-15 13:12:10 -0800 | [diff] [blame] | 143 | return brconfigs |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 144 | |
| 145 | |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 146 | def run_iperf_client_nonblocking(ad, server_host, extra_args=""): |
| 147 | """Start iperf client on the device with nohup. |
| 148 | |
| 149 | Return status as true if iperf client start successfully. |
| 150 | And data flow information as results. |
| 151 | |
| 152 | Args: |
| 153 | ad: the android device under test |
| 154 | server_host: Address of the iperf server. |
| 155 | extra_args: A string representing extra arguments for iperf client, |
| 156 | e.g. "-i 1 -t 30". |
| 157 | |
| 158 | """ |
| 159 | log = logging.getLogger() |
Kevin Klug | 8286e7b | 2019-03-20 11:58:35 -0700 | [diff] [blame] | 160 | ad.adb.shell_nb("nohup >/dev/null 2>&1 sh -c 'iperf3 -c {} {} &'".format( |
| 161 | server_host, extra_args)) |
Qi Jiang | 591437d | 2017-08-01 17:46:55 -0700 | [diff] [blame] | 162 | log.info("IPerf client started") |
| 163 | |
| 164 | |
| 165 | def get_wifi_rssi(ad): |
| 166 | """Get the RSSI of the device. |
| 167 | |
| 168 | Args: |
| 169 | ad: the android device under test |
| 170 | Returns: |
| 171 | RSSI: the rssi level of the device |
| 172 | """ |
| 173 | RSSI = ad.droid.wifiGetConnectionInfo()['rssi'] |
| 174 | return RSSI |
Qi Jiang | 2384247 | 2017-08-08 12:58:40 -0700 | [diff] [blame] | 175 | |
| 176 | |
| 177 | def get_phone_ip(ad): |
| 178 | """Get the WiFi IP address of the phone. |
| 179 | |
| 180 | Args: |
| 181 | ad: the android device under test |
| 182 | Returns: |
| 183 | IP: IP address of the phone for WiFi, as a string |
| 184 | """ |
| 185 | IP = ad.droid.connectivityGetIPv4Addresses('wlan0')[0] |
| 186 | |
| 187 | return IP |
| 188 | |
| 189 | |
| 190 | def get_phone_mac(ad): |
| 191 | """Get the WiFi MAC address of the phone. |
| 192 | |
| 193 | Args: |
| 194 | ad: the android device under test |
| 195 | Returns: |
| 196 | mac: MAC address of the phone for WiFi, as a string |
| 197 | """ |
| 198 | mac = ad.droid.wifiGetConnectionInfo()["mac_address"] |
| 199 | |
| 200 | return mac |
| 201 | |
| 202 | |
| 203 | def get_phone_ipv6(ad): |
| 204 | """Get the WiFi IPV6 address of the phone. |
| 205 | |
| 206 | Args: |
| 207 | ad: the android device under test |
| 208 | Returns: |
| 209 | IPv6: IPv6 address of the phone for WiFi, as a string |
| 210 | """ |
| 211 | IPv6 = ad.droid.connectivityGetLinkLocalIpv6Address('wlan0')[:-6] |
| 212 | |
| 213 | return IPv6 |
Daniel Barros | 1cefafd | 2017-08-04 15:36:40 -0700 | [diff] [blame] | 214 | |
| 215 | |
Qi | de56d67 | 2019-08-27 13:33:21 -0700 | [diff] [blame] | 216 | def wait_for_dhcp(interface_name): |
Qi Jiang | b411031 | 2017-10-05 22:00:29 -0700 | [diff] [blame] | 217 | """Wait the DHCP address assigned to desired interface. |
| 218 | |
| 219 | Getting DHCP address takes time and the wait time isn't constant. Utilizing |
| 220 | utils.timeout to keep trying until success |
| 221 | |
| 222 | Args: |
Qi | de56d67 | 2019-08-27 13:33:21 -0700 | [diff] [blame] | 223 | interface_name: desired interface name |
Qi Jiang | b411031 | 2017-10-05 22:00:29 -0700 | [diff] [blame] | 224 | Returns: |
| 225 | ip: ip address of the desired interface name |
| 226 | Raise: |
| 227 | TimeoutError: After timeout, if no DHCP assigned, raise |
| 228 | """ |
| 229 | log = logging.getLogger() |
Qi | de56d67 | 2019-08-27 13:33:21 -0700 | [diff] [blame] | 230 | reset_host_interface(interface_name) |
| 231 | start_time = time.time() |
| 232 | time_limit_seconds = 60 |
Qi Jiang | b411031 | 2017-10-05 22:00:29 -0700 | [diff] [blame] | 233 | ip = '0.0.0.0' |
Qi | 6ca8a92 | 2019-09-03 12:32:25 -0700 | [diff] [blame] | 234 | while start_time + time_limit_seconds > time.time(): |
Qi | de56d67 | 2019-08-27 13:33:21 -0700 | [diff] [blame] | 235 | ip = scapy.get_if_addr(interface_name) |
| 236 | if ip == '0.0.0.0': |
| 237 | time.sleep(1) |
| 238 | else: |
Qi | fb9140b68 | 2019-09-13 18:07:02 -0700 | [diff] [blame] | 239 | log.info( |
| 240 | 'DHCP address assigned to %s as %s' % (interface_name, ip)) |
Qi | de56d67 | 2019-08-27 13:33:21 -0700 | [diff] [blame] | 241 | return ip |
| 242 | raise TimeoutError('Timed out while getting if_addr after %s seconds.' % |
Qi | fb9140b68 | 2019-09-13 18:07:02 -0700 | [diff] [blame] | 243 | time_limit_seconds) |
| 244 | |
Qi Jiang | b411031 | 2017-10-05 22:00:29 -0700 | [diff] [blame] | 245 | |
Qi | de56d67 | 2019-08-27 13:33:21 -0700 | [diff] [blame] | 246 | def reset_host_interface(intferface_name): |
Qi Jiang | b411031 | 2017-10-05 22:00:29 -0700 | [diff] [blame] | 247 | """Reset the host interface. |
| 248 | |
| 249 | Args: |
Qi | de56d67 | 2019-08-27 13:33:21 -0700 | [diff] [blame] | 250 | intferface_name: the desired interface to reset |
Qi Jiang | b411031 | 2017-10-05 22:00:29 -0700 | [diff] [blame] | 251 | """ |
| 252 | log = logging.getLogger() |
Qi | de56d67 | 2019-08-27 13:33:21 -0700 | [diff] [blame] | 253 | intf_down_cmd = 'ifconfig %s down' % intferface_name |
| 254 | intf_up_cmd = 'ifconfig %s up' % intferface_name |
Qi Jiang | b411031 | 2017-10-05 22:00:29 -0700 | [diff] [blame] | 255 | try: |
| 256 | job.run(intf_down_cmd) |
Qi Jiang | b29f391 | 2018-02-02 15:12:02 -0800 | [diff] [blame] | 257 | time.sleep(10) |
Qi Jiang | b411031 | 2017-10-05 22:00:29 -0700 | [diff] [blame] | 258 | job.run(intf_up_cmd) |
Qi | de56d67 | 2019-08-27 13:33:21 -0700 | [diff] [blame] | 259 | log.info('{} has been reset'.format(intferface_name)) |
Qi Jiang | b411031 | 2017-10-05 22:00:29 -0700 | [diff] [blame] | 260 | except job.Error: |
| 261 | raise Exception('No such interface') |
| 262 | |
Qi | fb9140b68 | 2019-09-13 18:07:02 -0700 | [diff] [blame] | 263 | |
Qi | de56d67 | 2019-08-27 13:33:21 -0700 | [diff] [blame] | 264 | def bringdown_host_interface(intferface_name): |
| 265 | """Reset the host interface. |
| 266 | |
| 267 | Args: |
| 268 | intferface_name: the desired interface to reset |
| 269 | """ |
| 270 | log = logging.getLogger() |
| 271 | intf_down_cmd = 'ifconfig %s down' % intferface_name |
| 272 | try: |
| 273 | job.run(intf_down_cmd) |
| 274 | time.sleep(2) |
| 275 | log.info('{} has been brought down'.format(intferface_name)) |
| 276 | except job.Error: |
| 277 | raise Exception('No such interface') |
Qi Jiang | b411031 | 2017-10-05 22:00:29 -0700 | [diff] [blame] | 278 | |
Qi | fb9140b68 | 2019-09-13 18:07:02 -0700 | [diff] [blame] | 279 | |
Daniel Barros | 32ed847 | 2017-09-30 16:45:37 -0700 | [diff] [blame] | 280 | def create_pkt_config(test_class): |
| 281 | """Creates the config for generating multicast packets |
| 282 | |
| 283 | Args: |
| 284 | test_class: object with all networking paramters |
| 285 | |
| 286 | Returns: |
| 287 | Dictionary with the multicast packet config |
| 288 | """ |
| 289 | addr_type = (scapy.IPV6_ADDR_LINKLOCAL |
| 290 | if test_class.ipv6_src_type == 'LINK_LOCAL' else |
| 291 | scapy.IPV6_ADDR_GLOBAL) |
| 292 | |
| 293 | mac_dst = test_class.mac_dst |
| 294 | if GET_FROM_PHONE in test_class.mac_dst: |
| 295 | mac_dst = get_phone_mac(test_class.dut) |
| 296 | |
| 297 | ipv4_dst = test_class.ipv4_dst |
| 298 | if GET_FROM_PHONE in test_class.ipv4_dst: |
| 299 | ipv4_dst = get_phone_ip(test_class.dut) |
| 300 | |
| 301 | ipv6_dst = test_class.ipv6_dst |
| 302 | if GET_FROM_PHONE in test_class.ipv6_dst: |
| 303 | ipv6_dst = get_phone_ipv6(test_class.dut) |
| 304 | |
| 305 | ipv4_gw = test_class.ipv4_gwt |
| 306 | if GET_FROM_AP in test_class.ipv4_gwt: |
| 307 | ipv4_gw = test_class.access_point.ssh_settings.hostname |
| 308 | |
| 309 | pkt_gen_config = { |
| 310 | 'interf': test_class.pkt_sender.interface, |
| 311 | 'subnet_mask': test_class.sub_mask, |
| 312 | 'src_mac': test_class.mac_src, |
| 313 | 'dst_mac': mac_dst, |
| 314 | 'src_ipv4': test_class.ipv4_src, |
| 315 | 'dst_ipv4': ipv4_dst, |
| 316 | 'src_ipv6': test_class.ipv6_src, |
| 317 | 'src_ipv6_type': addr_type, |
| 318 | 'dst_ipv6': ipv6_dst, |
| 319 | 'gw_ipv4': ipv4_gw |
| 320 | } |
| 321 | return pkt_gen_config |