Move MtsEthernetTetheringTest back into TetheringTest

These were originally split in two, so low-level BPF could be changed
more easily. With the introduction of MCTS, updatability of CTS tests is
not a concern anymore, so these tests can be merged back together.

Test: atest TetheringIntegrationTests
Change-Id: Ia0bd91470d445e90230f7a9546de1cb56deef9cc
This commit is contained in:
Patrick Rohr 2024-06-07 11:27:30 -07:00
commit 247e44b2ef
2 changed files with 251 additions and 304 deletions

View file

@ -16,6 +16,7 @@
package android.net;
import static android.Manifest.permission.DUMP;
import static android.net.InetAddresses.parseNumericAddress;
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_ETHERNET;
@ -26,36 +27,51 @@ import static android.net.TetheringTester.isExpectedIcmpPacket;
import static android.net.TetheringTester.isExpectedUdpDnsPacket;
import static android.system.OsConstants.ICMP_ECHO;
import static android.system.OsConstants.ICMP_ECHOREPLY;
import static android.system.OsConstants.IPPROTO_UDP;
import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
import static com.android.net.module.util.HexDump.dumpHexString;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
import static com.android.testutils.DeviceInfoUtils.KVersion;
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.net.TetheringManager.TetheringRequest;
import android.net.TetheringTester.TetheredDevice;
import android.os.Build;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.VintfRuntimeInfo;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.BpfDump;
import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.Struct;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
import com.android.net.module.util.bpf.TetherStatsKey;
import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.UdpHeader;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DeviceInfoUtils;
import com.android.testutils.DumpTestUtils;
import com.android.testutils.NetworkStackModuleTest;
import com.android.testutils.TapPacketReader;
@ -73,7 +89,9 @@ import java.net.NetworkInterface;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeoutException;
@ -89,6 +107,26 @@ public class EthernetTetheringTest extends EthernetTetheringTestBase {
private static final short ICMPECHO_ID = 0x0;
private static final short ICMPECHO_SEQ = 0x0;
private static final int DUMP_POLLING_MAX_RETRY = 100;
private static final int DUMP_POLLING_INTERVAL_MS = 50;
// Kernel treats a confirmed UDP connection which active after two seconds as stream mode.
// See upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5.
private static final int UDP_STREAM_TS_MS = 2000;
// Give slack time for waiting UDP stream mode because handling conntrack event in user space
// may not in precise time. Used to reduce the flaky rate.
private static final int UDP_STREAM_SLACK_MS = 500;
// Per RX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
private static final int RX_UDP_PACKET_SIZE = 30;
private static final int RX_UDP_PACKET_COUNT = 456;
// Per TX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
private static final int TX_UDP_PACKET_SIZE = 30;
private static final int TX_UDP_PACKET_COUNT = 123;
private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
private static final String LINE_DELIMITER = "\\n";
// TODO: use class DnsPacket to build DNS query and reply message once DnsPacket supports
// building packet for given arguments.
private static final ByteBuffer DNS_QUERY = ByteBuffer.wrap(new byte[] {
@ -802,4 +840,217 @@ public class EthernetTetheringTest extends EthernetTetheringTestBase {
final MacAddress macAddress = MacAddress.fromString("11:22:33:44:55:66");
assertTrue(tester.testDhcpServerAlive(macAddress));
}
private static boolean isUdpOffloadSupportedByKernel(final String kernelVersion) {
final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion);
return current.isInRange(new KVersion(4, 14, 222), new KVersion(4, 19, 0))
|| current.isInRange(new KVersion(4, 19, 176), new KVersion(5, 4, 0))
|| current.isAtLeast(new KVersion(5, 4, 98));
}
@Test
public void testIsUdpOffloadSupportedByKernel() throws Exception {
assertFalse(isUdpOffloadSupportedByKernel("4.14.221"));
assertTrue(isUdpOffloadSupportedByKernel("4.14.222"));
assertTrue(isUdpOffloadSupportedByKernel("4.16.0"));
assertTrue(isUdpOffloadSupportedByKernel("4.18.0"));
assertFalse(isUdpOffloadSupportedByKernel("4.19.0"));
assertFalse(isUdpOffloadSupportedByKernel("4.19.175"));
assertTrue(isUdpOffloadSupportedByKernel("4.19.176"));
assertTrue(isUdpOffloadSupportedByKernel("5.2.0"));
assertTrue(isUdpOffloadSupportedByKernel("5.3.0"));
assertFalse(isUdpOffloadSupportedByKernel("5.4.0"));
assertFalse(isUdpOffloadSupportedByKernel("5.4.97"));
assertTrue(isUdpOffloadSupportedByKernel("5.4.98"));
assertTrue(isUdpOffloadSupportedByKernel("5.10.0"));
}
private static void assumeKernelSupportBpfOffloadUdpV4() {
final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
assumeTrue("Kernel version " + kernelVersion + " doesn't support IPv4 UDP BPF offload",
isUdpOffloadSupportedByKernel(kernelVersion));
}
@Test
public void testKernelSupportBpfOffloadUdpV4() throws Exception {
assumeKernelSupportBpfOffloadUdpV4();
}
private boolean isTetherConfigBpfOffloadEnabled() throws Exception {
final String dumpStr = runAsShell(DUMP, () ->
DumpTestUtils.dumpService(Context.TETHERING_SERVICE, "--short"));
// BPF offload tether config can be overridden by "config_tether_enable_bpf_offload" in
// packages/modules/Connectivity/Tethering/res/values/config.xml. OEM may disable config by
// RRO to override the enabled default value. Get the tethering config via dumpsys.
// $ dumpsys tethering
// mIsBpfEnabled: true
boolean enabled = dumpStr.contains("mIsBpfEnabled: true");
if (!enabled) {
Log.d(TAG, "BPF offload tether config not enabled: " + dumpStr);
}
return enabled;
}
@Test
public void testTetherConfigBpfOffloadEnabled() throws Exception {
assumeTrue(isTetherConfigBpfOffloadEnabled());
}
@NonNull
private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
throws Exception {
final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg};
final String rawMapStr = runAsShell(DUMP, () ->
DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args));
final HashMap<K, V> map = new HashMap<>();
for (final String line : rawMapStr.split(LINE_DELIMITER)) {
final Pair<K, V> rule =
BpfDump.fromBase64EncodedString(keyClass, valueClass, line.trim());
map.put(rule.first, rule.second);
}
return map;
}
@Nullable
private <K extends Struct, V extends Struct> HashMap<K, V> pollRawMapFromDump(
Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
throws Exception {
for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) {
final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, mapArg);
if (!map.isEmpty()) return map;
Thread.sleep(DUMP_POLLING_INTERVAL_MS);
}
fail("Cannot get rules after " + DUMP_POLLING_MAX_RETRY * DUMP_POLLING_INTERVAL_MS + "ms");
return null;
}
// Test network topology:
//
// public network (rawip) private network
// | UE |
// +------------+ V +------------+------------+ V +------------+
// | Sever +---------+ Upstream | Downstream +---------+ Client |
// +------------+ +------------+------------+ +------------+
// remote ip public ip private ip
// 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876
//
private void runUdp4Test() throws Exception {
final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
toList(TEST_IP4_DNS));
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
// Because async upstream connected notification can't guarantee the tethering routing is
// ready to use. Need to test tethering connectivity before testing.
// For short term plan, consider using IPv6 RA to get MAC address because the prefix comes
// from upstream. That can guarantee that the routing is ready. Long term plan is that
// refactors upstream connected notification from async to sync.
probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
final MacAddress srcMac = tethered.macAddr;
final MacAddress dstMac = tethered.routerMacAddr;
final InetAddress remoteIp = REMOTE_IP4_ADDR;
final InetAddress tetheringUpstreamIp = TEST_IP4_ADDR.getAddress();
final InetAddress clientIp = tethered.ipv4Addr;
sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
// Send second UDP packet in original direction.
// The BPF coordinator only offloads the ASSURED conntrack entry. The "request + reply"
// packets can make status IPS_SEEN_REPLY to be set. Need one more packet to make
// conntrack status IPS_ASSURED_BIT to be set. Note the third packet needs to delay
// 2 seconds because kernel monitors a UDP connection which still alive after 2 seconds
// and apply ASSURED flag.
// See kernel upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5 and
// nf_conntrack_udp_packet in net/netfilter/nf_conntrack_proto_udp.c
Thread.sleep(UDP_STREAM_TS_MS);
sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
// Give a slack time for handling conntrack event in user space.
Thread.sleep(UDP_STREAM_SLACK_MS);
// [1] Verify IPv4 upstream rule map.
final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump(
Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4);
assertNotNull(upstreamMap);
assertEquals(1, upstreamMap.size());
final Map.Entry<Tether4Key, Tether4Value> rule =
upstreamMap.entrySet().iterator().next();
final Tether4Key upstream4Key = rule.getKey();
assertEquals(IPPROTO_UDP, upstream4Key.l4proto);
assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), upstream4Key.src4));
assertEquals(LOCAL_PORT, upstream4Key.srcPort);
assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), upstream4Key.dst4));
assertEquals(REMOTE_PORT, upstream4Key.dstPort);
final Tether4Value upstream4Value = rule.getValue();
assertTrue(Arrays.equals(tetheringUpstreamIp.getAddress(),
InetAddress.getByAddress(upstream4Value.src46).getAddress()));
assertEquals(LOCAL_PORT, upstream4Value.srcPort);
assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(),
InetAddress.getByAddress(upstream4Value.dst46).getAddress()));
assertEquals(REMOTE_PORT, upstream4Value.dstPort);
// [2] Verify stats map.
// Transmit packets on both direction for verifying stats. Because we only care the
// packet count in stats test, we just reuse the existing packets to increaes
// the packet count on both direction.
// Send packets on original direction.
for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester,
false /* is4To6 */);
}
// Send packets on reply direction.
for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
}
// Dump stats map to verify.
final HashMap<TetherStatsKey, TetherStatsValue> statsMap = pollRawMapFromDump(
TetherStatsKey.class, TetherStatsValue.class, DUMPSYS_RAWMAP_ARG_STATS);
assertNotNull(statsMap);
assertEquals(1, statsMap.size());
final Map.Entry<TetherStatsKey, TetherStatsValue> stats =
statsMap.entrySet().iterator().next();
// TODO: verify the upstream index in TetherStatsKey.
final TetherStatsValue statsValue = stats.getValue();
assertEquals(RX_UDP_PACKET_COUNT, statsValue.rxPackets);
assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE, statsValue.rxBytes);
assertEquals(0, statsValue.rxErrors);
assertEquals(TX_UDP_PACKET_COUNT, statsValue.txPackets);
assertEquals(TX_UDP_PACKET_COUNT * TX_UDP_PACKET_SIZE, statsValue.txBytes);
assertEquals(0, statsValue.txErrors);
}
/**
* BPF offload IPv4 UDP tethering test. Verify that UDP tethered packets are offloaded by BPF.
* Minimum test requirement:
* 1. S+ device.
* 2. Tethering config enables tethering BPF offload.
* 3. Kernel supports IPv4 UDP BPF offload. See #isUdpOffloadSupportedByKernel.
*
* TODO: consider enabling the test even tethering config disables BPF offload. See b/238288883
*/
@Test
@IgnoreUpTo(Build.VERSION_CODES.R)
public void testTetherBpfOffloadUdpV4() throws Exception {
assumeTrue("Tethering config disabled BPF offload", isTetherConfigBpfOffloadEnabled());
assumeKernelSupportBpfOffloadUdpV4();
runUdp4Test();
}
}

View file

@ -1,304 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net;
import static android.Manifest.permission.DUMP;
import static android.system.OsConstants.IPPROTO_UDP;
import static com.android.testutils.DeviceInfoUtils.KVersion;
import static com.android.testutils.TestPermissionUtil.runAsShell;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.net.TetheringTester.TetheredDevice;
import android.os.Build;
import android.os.VintfRuntimeInfo;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.BpfDump;
import com.android.net.module.util.Struct;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
import com.android.net.module.util.bpf.TetherStatsKey;
import com.android.net.module.util.bpf.TetherStatsValue;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DeviceInfoUtils;
import com.android.testutils.DumpTestUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class MtsEthernetTetheringTest extends EthernetTetheringTestBase {
@Rule
public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
private static final String TAG = MtsEthernetTetheringTest.class.getSimpleName();
private static final int DUMP_POLLING_MAX_RETRY = 100;
private static final int DUMP_POLLING_INTERVAL_MS = 50;
// Kernel treats a confirmed UDP connection which active after two seconds as stream mode.
// See upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5.
private static final int UDP_STREAM_TS_MS = 2000;
// Give slack time for waiting UDP stream mode because handling conntrack event in user space
// may not in precise time. Used to reduce the flaky rate.
private static final int UDP_STREAM_SLACK_MS = 500;
// Per RX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
private static final int RX_UDP_PACKET_SIZE = 30;
private static final int RX_UDP_PACKET_COUNT = 456;
// Per TX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
private static final int TX_UDP_PACKET_SIZE = 30;
private static final int TX_UDP_PACKET_COUNT = 123;
private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
private static final String LINE_DELIMITER = "\\n";
private static boolean isUdpOffloadSupportedByKernel(final String kernelVersion) {
final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion);
return current.isInRange(new KVersion(4, 14, 222), new KVersion(4, 19, 0))
|| current.isInRange(new KVersion(4, 19, 176), new KVersion(5, 4, 0))
|| current.isAtLeast(new KVersion(5, 4, 98));
}
@Test
public void testIsUdpOffloadSupportedByKernel() throws Exception {
assertFalse(isUdpOffloadSupportedByKernel("4.14.221"));
assertTrue(isUdpOffloadSupportedByKernel("4.14.222"));
assertTrue(isUdpOffloadSupportedByKernel("4.16.0"));
assertTrue(isUdpOffloadSupportedByKernel("4.18.0"));
assertFalse(isUdpOffloadSupportedByKernel("4.19.0"));
assertFalse(isUdpOffloadSupportedByKernel("4.19.175"));
assertTrue(isUdpOffloadSupportedByKernel("4.19.176"));
assertTrue(isUdpOffloadSupportedByKernel("5.2.0"));
assertTrue(isUdpOffloadSupportedByKernel("5.3.0"));
assertFalse(isUdpOffloadSupportedByKernel("5.4.0"));
assertFalse(isUdpOffloadSupportedByKernel("5.4.97"));
assertTrue(isUdpOffloadSupportedByKernel("5.4.98"));
assertTrue(isUdpOffloadSupportedByKernel("5.10.0"));
}
private static void assumeKernelSupportBpfOffloadUdpV4() {
final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
assumeTrue("Kernel version " + kernelVersion + " doesn't support IPv4 UDP BPF offload",
isUdpOffloadSupportedByKernel(kernelVersion));
}
@Test
public void testKernelSupportBpfOffloadUdpV4() throws Exception {
assumeKernelSupportBpfOffloadUdpV4();
}
private boolean isTetherConfigBpfOffloadEnabled() throws Exception {
final String dumpStr = runAsShell(DUMP, () ->
DumpTestUtils.dumpService(Context.TETHERING_SERVICE, "--short"));
// BPF offload tether config can be overridden by "config_tether_enable_bpf_offload" in
// packages/modules/Connectivity/Tethering/res/values/config.xml. OEM may disable config by
// RRO to override the enabled default value. Get the tethering config via dumpsys.
// $ dumpsys tethering
// mIsBpfEnabled: true
boolean enabled = dumpStr.contains("mIsBpfEnabled: true");
if (!enabled) {
Log.d(TAG, "BPF offload tether config not enabled: " + dumpStr);
}
return enabled;
}
@Test
public void testTetherConfigBpfOffloadEnabled() throws Exception {
assumeTrue(isTetherConfigBpfOffloadEnabled());
}
@NonNull
private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
throws Exception {
final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg};
final String rawMapStr = runAsShell(DUMP, () ->
DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args));
final HashMap<K, V> map = new HashMap<>();
for (final String line : rawMapStr.split(LINE_DELIMITER)) {
final Pair<K, V> rule =
BpfDump.fromBase64EncodedString(keyClass, valueClass, line.trim());
map.put(rule.first, rule.second);
}
return map;
}
@Nullable
private <K extends Struct, V extends Struct> HashMap<K, V> pollRawMapFromDump(
Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
throws Exception {
for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) {
final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, mapArg);
if (!map.isEmpty()) return map;
Thread.sleep(DUMP_POLLING_INTERVAL_MS);
}
fail("Cannot get rules after " + DUMP_POLLING_MAX_RETRY * DUMP_POLLING_INTERVAL_MS + "ms");
return null;
}
// Test network topology:
//
// public network (rawip) private network
// | UE |
// +------------+ V +------------+------------+ V +------------+
// | Sever +---------+ Upstream | Downstream +---------+ Client |
// +------------+ +------------+------------+ +------------+
// remote ip public ip private ip
// 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876
//
private void runUdp4Test() throws Exception {
final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
toList(TEST_IP4_DNS));
final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
// TODO: remove the connectivity verification for upstream connected notification race.
// Because async upstream connected notification can't guarantee the tethering routing is
// ready to use. Need to test tethering connectivity before testing.
// For short term plan, consider using IPv6 RA to get MAC address because the prefix comes
// from upstream. That can guarantee that the routing is ready. Long term plan is that
// refactors upstream connected notification from async to sync.
probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
final MacAddress srcMac = tethered.macAddr;
final MacAddress dstMac = tethered.routerMacAddr;
final InetAddress remoteIp = REMOTE_IP4_ADDR;
final InetAddress tetheringUpstreamIp = TEST_IP4_ADDR.getAddress();
final InetAddress clientIp = tethered.ipv4Addr;
sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
// Send second UDP packet in original direction.
// The BPF coordinator only offloads the ASSURED conntrack entry. The "request + reply"
// packets can make status IPS_SEEN_REPLY to be set. Need one more packet to make
// conntrack status IPS_ASSURED_BIT to be set. Note the third packet needs to delay
// 2 seconds because kernel monitors a UDP connection which still alive after 2 seconds
// and apply ASSURED flag.
// See kernel upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5 and
// nf_conntrack_udp_packet in net/netfilter/nf_conntrack_proto_udp.c
Thread.sleep(UDP_STREAM_TS_MS);
sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
// Give a slack time for handling conntrack event in user space.
Thread.sleep(UDP_STREAM_SLACK_MS);
// [1] Verify IPv4 upstream rule map.
final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump(
Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4);
assertNotNull(upstreamMap);
assertEquals(1, upstreamMap.size());
final Map.Entry<Tether4Key, Tether4Value> rule =
upstreamMap.entrySet().iterator().next();
final Tether4Key upstream4Key = rule.getKey();
assertEquals(IPPROTO_UDP, upstream4Key.l4proto);
assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), upstream4Key.src4));
assertEquals(LOCAL_PORT, upstream4Key.srcPort);
assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), upstream4Key.dst4));
assertEquals(REMOTE_PORT, upstream4Key.dstPort);
final Tether4Value upstream4Value = rule.getValue();
assertTrue(Arrays.equals(tetheringUpstreamIp.getAddress(),
InetAddress.getByAddress(upstream4Value.src46).getAddress()));
assertEquals(LOCAL_PORT, upstream4Value.srcPort);
assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(),
InetAddress.getByAddress(upstream4Value.dst46).getAddress()));
assertEquals(REMOTE_PORT, upstream4Value.dstPort);
// [2] Verify stats map.
// Transmit packets on both direction for verifying stats. Because we only care the
// packet count in stats test, we just reuse the existing packets to increaes
// the packet count on both direction.
// Send packets on original direction.
for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester,
false /* is4To6 */);
}
// Send packets on reply direction.
for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
}
// Dump stats map to verify.
final HashMap<TetherStatsKey, TetherStatsValue> statsMap = pollRawMapFromDump(
TetherStatsKey.class, TetherStatsValue.class, DUMPSYS_RAWMAP_ARG_STATS);
assertNotNull(statsMap);
assertEquals(1, statsMap.size());
final Map.Entry<TetherStatsKey, TetherStatsValue> stats =
statsMap.entrySet().iterator().next();
// TODO: verify the upstream index in TetherStatsKey.
final TetherStatsValue statsValue = stats.getValue();
assertEquals(RX_UDP_PACKET_COUNT, statsValue.rxPackets);
assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE, statsValue.rxBytes);
assertEquals(0, statsValue.rxErrors);
assertEquals(TX_UDP_PACKET_COUNT, statsValue.txPackets);
assertEquals(TX_UDP_PACKET_COUNT * TX_UDP_PACKET_SIZE, statsValue.txBytes);
assertEquals(0, statsValue.txErrors);
}
/**
* BPF offload IPv4 UDP tethering test. Verify that UDP tethered packets are offloaded by BPF.
* Minimum test requirement:
* 1. S+ device.
* 2. Tethering config enables tethering BPF offload.
* 3. Kernel supports IPv4 UDP BPF offload. See #isUdpOffloadSupportedByKernel.
*
* TODO: consider enabling the test even tethering config disables BPF offload. See b/238288883
*/
@Test
@IgnoreUpTo(Build.VERSION_CODES.R)
public void testTetherBpfOffloadUdpV4() throws Exception {
assumeTrue("Tethering config disabled BPF offload", isTetherConfigBpfOffloadEnabled());
assumeKernelSupportBpfOffloadUdpV4();
runUdp4Test();
}
}