1# Copyright (C) 2024 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""iperf test util.""" 16import time 17from mobly import utils 18from mobly.controllers import android_device 19 20from betocq import nc_constants 21 22# IPv4, 10 sec, 1 stream 23DEFAULT_IPV4_CLIENT_ARGS = '-t 10 -P1' 24DEFAULT_IPV4_SERVER_ARGS = '-J' 25GROUP_OWNER_IPV4_ADDR_LEN_MAX = 15 26IPERF_SERVER_START_DELAY_SEC = 1 27IPERF_DEBUG_TIME_SEC = 300 28 29 30class IPerfServerOnDevice: 31 """Class that handles iperf3 server operations on device.""" 32 33 def __init__(self, serial, arg=DEFAULT_IPV4_SERVER_ARGS): 34 self.iperf_str = 'adb -s {serial} shell iperf3 -s {arg}'.format( 35 serial=serial, arg=arg 36 ) 37 self.iperf_process = None 38 self.started = False 39 40 def start(self): 41 """Starts iperf server on specified port.""" 42 if self.started: 43 return 44 45 cmd = self.iperf_str 46 self.iperf_process = utils.start_standing_subprocess(cmd, shell=True) 47 self.started = True 48 49 def stop(self): 50 if self.started: 51 utils.stop_standing_subprocess(self.iperf_process) 52 self.started = False 53 54 55def run_iperf_test( 56 ad_network_client: android_device.AndroidDevice, 57 ad_network_owner: android_device.AndroidDevice, 58 medium: nc_constants.NearbyConnectionMedium, 59) -> int: 60 """Run iperf test from ad_network_client to ad_network_owner. 61 62 Args: 63 ad_network_client: android device that is the client in the iperf test. 64 ad_network_owner: android device that is the server in the iperf test. 65 medium: wifi medium used in the transfer 66 67 Returns: 68 speed in KB/s if there is a valid result or nc_constants.INVALID_INT. 69 """ 70 speed_kbyte_sec = nc_constants.INVALID_INT 71 owner_addr = get_owner_ip_addr(ad_network_client, ad_network_owner, medium) 72 if not owner_addr: 73 return nc_constants.INVALID_INT 74 75 client_arg = DEFAULT_IPV4_CLIENT_ARGS 76 server_arg = DEFAULT_IPV4_SERVER_ARGS 77 # Add IPv6 option if the owner address is an IPv6 address 78 if len(owner_addr) > GROUP_OWNER_IPV4_ADDR_LEN_MAX: 79 client_arg = DEFAULT_IPV4_CLIENT_ARGS + ' -6' 80 server_arg = DEFAULT_IPV4_SERVER_ARGS + ' -6' 81 82 server = IPerfServerOnDevice(ad_network_owner.serial, server_arg) 83 try: 84 ad_network_owner.log.info('Start iperf server') 85 server.start() 86 time.sleep(IPERF_SERVER_START_DELAY_SEC) 87 ad_network_client.log.info(f'Start iperf client {owner_addr}') 88 success, result_list = ad_network_client.run_iperf_client( 89 owner_addr, client_arg 90 ) 91 result = ''.join(result_list) 92 last_mbits_sec_index = result.rfind('Mbits/sec') 93 if success and last_mbits_sec_index > 0: 94 speed_mbps = int( 95 float(result[:last_mbits_sec_index].strip().split(' ')[-1]) 96 ) 97 speed_kbyte_sec = int(speed_mbps * 1024 / 8) 98 else: 99 ad_network_client.log.info('Can not find valid iperf test result') 100 except android_device.adb.AdbError: 101 ad_network_client.log.info('run_iperf_client() failed') 102 owner_ifconfig = get_ifconfig(ad_network_owner) 103 client_ifconfig = get_ifconfig(ad_network_client) 104 ad_network_client.log.info(client_ifconfig) 105 ad_network_client.log.info(owner_addr) 106 ad_network_client.log.info(client_arg) 107 ad_network_owner.log.info(owner_ifconfig) 108 # time.sleep(IPERF_DEBUG_TIME_SEC) 109 else: 110 server.stop() 111 return speed_kbyte_sec 112 113 114def get_owner_ip_addr( 115 ad_network_client: android_device.AndroidDevice, 116 ad_network_owner: android_device.AndroidDevice, 117 medium: nc_constants.NearbyConnectionMedium, 118) -> str: 119 """Get owner ip address which for ipv6 is postfix-ed by the client interface name.""" 120 ip_addr = '' 121 if medium == nc_constants.NearbyConnectionMedium.WIFI_DIRECT: 122 ip_addr = get_group_owner_addr(ad_network_client) 123 elif medium == nc_constants.NearbyConnectionMedium.WIFI_LAN: 124 ip_addr = get_wlan_ip_addr(ad_network_owner) 125 if len(ip_addr) > GROUP_OWNER_IPV4_ADDR_LEN_MAX: 126 ip_addr += '%' + get_wlan_ifname(ad_network_client) 127 elif medium == nc_constants.NearbyConnectionMedium.WIFI_HOTSPOT: 128 ip_addr = get_p2p_ip_addr(ad_network_owner) 129 if len(ip_addr) > GROUP_OWNER_IPV4_ADDR_LEN_MAX: 130 ip_addr = ip_addr + '%' + get_wlan_ifname(ad_network_client) 131 elif medium == nc_constants.NearbyConnectionMedium.WIFI_AWARE: 132 ip_addr = get_aware_ip_addr(ad_network_owner) 133 if len(ip_addr) > GROUP_OWNER_IPV4_ADDR_LEN_MAX: 134 ip_addr = ip_addr + '%' + get_aware_ifname(ad_network_client) 135 return ip_addr 136 137 138def get_aware_ip_addr(ad: android_device.AndroidDevice) -> str: 139 """Get wlan ip address from ifconfig.""" 140 ifconfig = get_ifconfig_aware(ad) 141 return extract_ip_addr_from_ifconfig(ifconfig) 142 143 144def get_wlan_ip_addr(ad: android_device.AndroidDevice) -> str: 145 """Get wlan ip address from ifconfig.""" 146 ifconfig = get_ifconfig_wlan(ad) 147 return extract_ip_addr_from_ifconfig(ifconfig) 148 149 150def get_p2p_ip_addr(ad: android_device.AndroidDevice) -> str: 151 """Get p2p ip address from ifconfig.""" 152 ifconfig = get_ifconfig_p2p(ad) 153 return extract_ip_addr_from_ifconfig(ifconfig) 154 155 156def get_aware_ifname(ad: android_device.AndroidDevice) -> str: 157 """Get Aware interface name from ifconfig.""" 158 return get_ifconfig_aware(ad).split()[0].strip() 159 160 161def get_wlan_ifname(ad: android_device.AndroidDevice) -> str: 162 """Get WLAN interface name from ifconfig.""" 163 ifconfig = get_ifconfig_wlan(ad) 164 found_wlan1 = ifconfig.rfind('wlan1') 165 if found_wlan1 >= 0: 166 return 'wlan1' 167 found_wlan0 = ifconfig.rfind('wlan0') 168 if found_wlan0 >= 0: 169 return 'wlan0' 170 return '' 171 172 173 174def get_p2p_ifname(ad: android_device.AndroidDevice) -> str: 175 """Get P2P interface name from ifconfig.""" 176 return get_ifconfig_p2p(ad).split()[0].strip() 177 178 179def extract_ip_addr_from_ifconfig(ifconfig: str) -> str: 180 """Extract ip address from ifconfig with IPv6 preferred.""" 181 ipv6 = get_substr_between_prefix_postfix( 182 ifconfig, 'inet6 addr:', '/64 Scope: Link' 183 ) 184 if ipv6: 185 return ipv6 186 ipv4 = get_substr_between_prefix_postfix(ifconfig, 'inet addr:', 'Bcast') 187 if ipv4: 188 return ipv4 189 return '' 190 191 192def get_substr_between_prefix_postfix( 193 string: str, prefix: str, postfix: str 194) -> str: 195 """Get substring between prefix and postfix by searching postfix and then prefix.""" 196 right_index = string.rfind(postfix) 197 if right_index == -1: 198 return '' 199 left_index = string[:right_index].rfind(prefix) 200 if left_index > 0: 201 try: 202 return string[left_index + len(prefix): right_index].strip() 203 except IndexError: 204 return '' 205 return '' 206 207 208def get_ifconfig( 209 ad: android_device.AndroidDevice, 210) -> str: 211 """Get network info from adb shell ifconfig.""" 212 return ad.adb.shell('ifconfig').decode('utf-8').strip() 213 214 215def get_ifconfig_aware( 216 ad: android_device.AndroidDevice, 217) -> str: 218 """Get aware network info from adb shell ifconfig.""" 219 ifconfig = ad.adb.shell('ifconfig | grep -A5 aware').decode('utf-8').strip() 220 # Use the last one if there are multiple aware interfaces. 221 index = ifconfig.rfind('aware') 222 return ifconfig[index:] 223 224 225def get_ifconfig_wlan( 226 ad: android_device.AndroidDevice, 227) -> str: 228 """Get wlan network info from adb shell ifconfig.""" 229 return ad.adb.shell('ifconfig | grep -A5 wlan').decode('utf-8').strip() 230 231 232def get_ifconfig_p2p( 233 ad: android_device.AndroidDevice, 234) -> str: 235 """Get p2p network info from adb shell ifconfig.""" 236 return ad.adb.shell('ifconfig | grep -A5 p2p').decode('utf-8').strip() 237 238 239def get_group_owner_addr( 240 ad: android_device.AndroidDevice, 241) -> str: 242 """Get the group owner address from adb shell dumpsys wifip2p. 243 244 This works only if group owner address is the last substring of the line. 245 246 Args: 247 ad: android device that is the group client 248 Returns: 249 ipv4 address or ipv6 address with the link interface 250 """ 251 252 try: 253 return ( 254 ad.adb.shell( 255 'dumpsys wifip2p | egrep "groupOwnerAddress|groupOwnerIpAddress"' 256 ) 257 .decode('utf-8') 258 .strip() 259 .split()[-1] 260 .replace('/', '') 261 ) 262 except android_device.adb.Error: 263 ad.log.info('Failed to get group owner address.') 264 return '' 265