1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.net; 18 19 import static android.Manifest.permission.DUMP; 20 import static android.system.OsConstants.IPPROTO_UDP; 21 22 import static com.android.testutils.DeviceInfoUtils.KVersion; 23 import static com.android.testutils.TestPermissionUtil.runAsShell; 24 25 import static org.junit.Assert.assertEquals; 26 import static org.junit.Assert.assertFalse; 27 import static org.junit.Assert.assertNotNull; 28 import static org.junit.Assert.assertTrue; 29 import static org.junit.Assert.fail; 30 import static org.junit.Assume.assumeTrue; 31 32 import android.content.Context; 33 import android.net.TetheringTester.TetheredDevice; 34 import android.os.Build; 35 import android.os.VintfRuntimeInfo; 36 import android.util.Log; 37 import android.util.Pair; 38 39 import androidx.annotation.NonNull; 40 import androidx.annotation.Nullable; 41 import androidx.test.filters.MediumTest; 42 import androidx.test.runner.AndroidJUnit4; 43 44 import com.android.net.module.util.BpfDump; 45 import com.android.net.module.util.Struct; 46 import com.android.net.module.util.bpf.Tether4Key; 47 import com.android.net.module.util.bpf.Tether4Value; 48 import com.android.net.module.util.bpf.TetherStatsKey; 49 import com.android.net.module.util.bpf.TetherStatsValue; 50 import com.android.testutils.DevSdkIgnoreRule; 51 import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; 52 import com.android.testutils.DeviceInfoUtils; 53 import com.android.testutils.DumpTestUtils; 54 55 import org.junit.Rule; 56 import org.junit.Test; 57 import org.junit.runner.RunWith; 58 59 import java.net.InetAddress; 60 import java.util.Arrays; 61 import java.util.HashMap; 62 import java.util.Map; 63 64 @RunWith(AndroidJUnit4.class) 65 @MediumTest 66 public class MtsEthernetTetheringTest extends EthernetTetheringTestBase { 67 @Rule 68 public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); 69 70 private static final String TAG = MtsEthernetTetheringTest.class.getSimpleName(); 71 72 private static final int DUMP_POLLING_MAX_RETRY = 100; 73 private static final int DUMP_POLLING_INTERVAL_MS = 50; 74 // Kernel treats a confirmed UDP connection which active after two seconds as stream mode. 75 // See upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5. 76 private static final int UDP_STREAM_TS_MS = 2000; 77 // Give slack time for waiting UDP stream mode because handling conntrack event in user space 78 // may not in precise time. Used to reduce the flaky rate. 79 private static final int UDP_STREAM_SLACK_MS = 500; 80 // Per RX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes. 81 private static final int RX_UDP_PACKET_SIZE = 30; 82 private static final int RX_UDP_PACKET_COUNT = 456; 83 // Per TX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes. 84 private static final int TX_UDP_PACKET_SIZE = 30; 85 private static final int TX_UDP_PACKET_COUNT = 123; 86 87 private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap"; 88 private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats"; 89 private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4"; 90 private static final String LINE_DELIMITER = "\\n"; 91 isUdpOffloadSupportedByKernel(final String kernelVersion)92 private static boolean isUdpOffloadSupportedByKernel(final String kernelVersion) { 93 final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion); 94 return current.isInRange(new KVersion(4, 14, 222), new KVersion(4, 19, 0)) 95 || current.isInRange(new KVersion(4, 19, 176), new KVersion(5, 4, 0)) 96 || current.isAtLeast(new KVersion(5, 4, 98)); 97 } 98 99 @Test testIsUdpOffloadSupportedByKernel()100 public void testIsUdpOffloadSupportedByKernel() throws Exception { 101 assertFalse(isUdpOffloadSupportedByKernel("4.14.221")); 102 assertTrue(isUdpOffloadSupportedByKernel("4.14.222")); 103 assertTrue(isUdpOffloadSupportedByKernel("4.16.0")); 104 assertTrue(isUdpOffloadSupportedByKernel("4.18.0")); 105 assertFalse(isUdpOffloadSupportedByKernel("4.19.0")); 106 107 assertFalse(isUdpOffloadSupportedByKernel("4.19.175")); 108 assertTrue(isUdpOffloadSupportedByKernel("4.19.176")); 109 assertTrue(isUdpOffloadSupportedByKernel("5.2.0")); 110 assertTrue(isUdpOffloadSupportedByKernel("5.3.0")); 111 assertFalse(isUdpOffloadSupportedByKernel("5.4.0")); 112 113 assertFalse(isUdpOffloadSupportedByKernel("5.4.97")); 114 assertTrue(isUdpOffloadSupportedByKernel("5.4.98")); 115 assertTrue(isUdpOffloadSupportedByKernel("5.10.0")); 116 } 117 assumeKernelSupportBpfOffloadUdpV4()118 private static void assumeKernelSupportBpfOffloadUdpV4() { 119 final String kernelVersion = VintfRuntimeInfo.getKernelVersion(); 120 assumeTrue("Kernel version " + kernelVersion + " doesn't support IPv4 UDP BPF offload", 121 isUdpOffloadSupportedByKernel(kernelVersion)); 122 } 123 124 @Test testKernelSupportBpfOffloadUdpV4()125 public void testKernelSupportBpfOffloadUdpV4() throws Exception { 126 assumeKernelSupportBpfOffloadUdpV4(); 127 } 128 isTetherConfigBpfOffloadEnabled()129 private boolean isTetherConfigBpfOffloadEnabled() throws Exception { 130 final String dumpStr = runAsShell(DUMP, () -> 131 DumpTestUtils.dumpService(Context.TETHERING_SERVICE, "--short")); 132 133 // BPF offload tether config can be overridden by "config_tether_enable_bpf_offload" in 134 // packages/modules/Connectivity/Tethering/res/values/config.xml. OEM may disable config by 135 // RRO to override the enabled default value. Get the tethering config via dumpsys. 136 // $ dumpsys tethering 137 // mIsBpfEnabled: true 138 boolean enabled = dumpStr.contains("mIsBpfEnabled: true"); 139 if (!enabled) { 140 Log.d(TAG, "BPF offload tether config not enabled: " + dumpStr); 141 } 142 return enabled; 143 } 144 145 @Test testTetherConfigBpfOffloadEnabled()146 public void testTetherConfigBpfOffloadEnabled() throws Exception { 147 assumeTrue(isTetherConfigBpfOffloadEnabled()); 148 } 149 150 @NonNull dumpAndParseRawMap( Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)151 private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap( 152 Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg) 153 throws Exception { 154 final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg}; 155 final String rawMapStr = runAsShell(DUMP, () -> 156 DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args)); 157 final HashMap<K, V> map = new HashMap<>(); 158 159 for (final String line : rawMapStr.split(LINE_DELIMITER)) { 160 final Pair<K, V> rule = 161 BpfDump.fromBase64EncodedString(keyClass, valueClass, line.trim()); 162 map.put(rule.first, rule.second); 163 } 164 return map; 165 } 166 167 @Nullable pollRawMapFromDump( Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)168 private <K extends Struct, V extends Struct> HashMap<K, V> pollRawMapFromDump( 169 Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg) 170 throws Exception { 171 for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) { 172 final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, mapArg); 173 if (!map.isEmpty()) return map; 174 175 Thread.sleep(DUMP_POLLING_INTERVAL_MS); 176 } 177 178 fail("Cannot get rules after " + DUMP_POLLING_MAX_RETRY * DUMP_POLLING_INTERVAL_MS + "ms"); 179 return null; 180 } 181 182 // Test network topology: 183 // 184 // public network (rawip) private network 185 // | UE | 186 // +------------+ V +------------+------------+ V +------------+ 187 // | Sever +---------+ Upstream | Downstream +---------+ Client | 188 // +------------+ +------------+------------+ +------------+ 189 // remote ip public ip private ip 190 // 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876 191 // runUdp4Test()192 private void runUdp4Test() throws Exception { 193 final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR), 194 toList(TEST_IP4_DNS)); 195 final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */); 196 197 // TODO: remove the connectivity verification for upstream connected notification race. 198 // Because async upstream connected notification can't guarantee the tethering routing is 199 // ready to use. Need to test tethering connectivity before testing. 200 // For short term plan, consider using IPv6 RA to get MAC address because the prefix comes 201 // from upstream. That can guarantee that the routing is ready. Long term plan is that 202 // refactors upstream connected notification from async to sync. 203 probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */); 204 205 final MacAddress srcMac = tethered.macAddr; 206 final MacAddress dstMac = tethered.routerMacAddr; 207 final InetAddress remoteIp = REMOTE_IP4_ADDR; 208 final InetAddress tetheringUpstreamIp = TEST_IP4_ADDR.getAddress(); 209 final InetAddress clientIp = tethered.ipv4Addr; 210 sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */); 211 sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */); 212 213 // Send second UDP packet in original direction. 214 // The BPF coordinator only offloads the ASSURED conntrack entry. The "request + reply" 215 // packets can make status IPS_SEEN_REPLY to be set. Need one more packet to make 216 // conntrack status IPS_ASSURED_BIT to be set. Note the third packet needs to delay 217 // 2 seconds because kernel monitors a UDP connection which still alive after 2 seconds 218 // and apply ASSURED flag. 219 // See kernel upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5 and 220 // nf_conntrack_udp_packet in net/netfilter/nf_conntrack_proto_udp.c 221 Thread.sleep(UDP_STREAM_TS_MS); 222 sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */); 223 224 // Give a slack time for handling conntrack event in user space. 225 Thread.sleep(UDP_STREAM_SLACK_MS); 226 227 // [1] Verify IPv4 upstream rule map. 228 final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump( 229 Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4); 230 assertNotNull(upstreamMap); 231 assertEquals(1, upstreamMap.size()); 232 233 final Map.Entry<Tether4Key, Tether4Value> rule = 234 upstreamMap.entrySet().iterator().next(); 235 236 final Tether4Key upstream4Key = rule.getKey(); 237 assertEquals(IPPROTO_UDP, upstream4Key.l4proto); 238 assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), upstream4Key.src4)); 239 assertEquals(LOCAL_PORT, upstream4Key.srcPort); 240 assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), upstream4Key.dst4)); 241 assertEquals(REMOTE_PORT, upstream4Key.dstPort); 242 243 final Tether4Value upstream4Value = rule.getValue(); 244 assertTrue(Arrays.equals(tetheringUpstreamIp.getAddress(), 245 InetAddress.getByAddress(upstream4Value.src46).getAddress())); 246 assertEquals(LOCAL_PORT, upstream4Value.srcPort); 247 assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), 248 InetAddress.getByAddress(upstream4Value.dst46).getAddress())); 249 assertEquals(REMOTE_PORT, upstream4Value.dstPort); 250 251 // [2] Verify stats map. 252 // Transmit packets on both direction for verifying stats. Because we only care the 253 // packet count in stats test, we just reuse the existing packets to increaes 254 // the packet count on both direction. 255 256 // Send packets on original direction. 257 for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) { 258 sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, 259 false /* is4To6 */); 260 } 261 262 // Send packets on reply direction. 263 for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) { 264 sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */); 265 } 266 267 // Dump stats map to verify. 268 final HashMap<TetherStatsKey, TetherStatsValue> statsMap = pollRawMapFromDump( 269 TetherStatsKey.class, TetherStatsValue.class, DUMPSYS_RAWMAP_ARG_STATS); 270 assertNotNull(statsMap); 271 assertEquals(1, statsMap.size()); 272 273 final Map.Entry<TetherStatsKey, TetherStatsValue> stats = 274 statsMap.entrySet().iterator().next(); 275 276 // TODO: verify the upstream index in TetherStatsKey. 277 278 final TetherStatsValue statsValue = stats.getValue(); 279 assertEquals(RX_UDP_PACKET_COUNT, statsValue.rxPackets); 280 assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE, statsValue.rxBytes); 281 assertEquals(0, statsValue.rxErrors); 282 assertEquals(TX_UDP_PACKET_COUNT, statsValue.txPackets); 283 assertEquals(TX_UDP_PACKET_COUNT * TX_UDP_PACKET_SIZE, statsValue.txBytes); 284 assertEquals(0, statsValue.txErrors); 285 } 286 287 /** 288 * BPF offload IPv4 UDP tethering test. Verify that UDP tethered packets are offloaded by BPF. 289 * Minimum test requirement: 290 * 1. S+ device. 291 * 2. Tethering config enables tethering BPF offload. 292 * 3. Kernel supports IPv4 UDP BPF offload. See #isUdpOffloadSupportedByKernel. 293 * 294 * TODO: consider enabling the test even tethering config disables BPF offload. See b/238288883 295 */ 296 @Test 297 @IgnoreUpTo(Build.VERSION_CODES.R) testTetherBpfOffloadUdpV4()298 public void testTetherBpfOffloadUdpV4() throws Exception { 299 assumeTrue("Tethering config disabled BPF offload", isTetherConfigBpfOffloadEnabled()); 300 assumeKernelSupportBpfOffloadUdpV4(); 301 302 runUdp4Test(); 303 } 304 } 305