Merge "[Thread] add config_thread_mdns_vendor_specific_txts" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 4cf93a8..bcf5e8b 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1,7 +1,7 @@
{
"captiveportal-networkstack-resolve-tethering-mainline-presubmit": [
{
- "name": "CtsNetTestCasesLatestSdk",
+ "name": "CtsNetTestCases",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -89,7 +89,7 @@
// Test with APK modules only, in cases where APEX is not supported, or the other modules
// were simply not updated
{
- "name": "CtsNetTestCasesLatestSdk",
+ "name": "CtsNetTestCases",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -115,7 +115,7 @@
// really exist in the field, but there is no strong guarantee, and it is required by MTS
// testing for module qualification, where modules are tested independently.
{
- "name": "CtsNetTestCasesLatestSdk",
+ "name": "CtsNetTestCases",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -158,8 +158,6 @@
},
// Run in addition to mainline-presubmit as mainline-presubmit is not
// supported in every branch.
- // CtsNetTestCasesLatestSdk uses stable API shims, so does not exercise
- // some latest APIs. Run CtsNetTestCases to get coverage of newer APIs.
{
"name": "CtsNetTestCases",
"options": [
@@ -171,18 +169,6 @@
}
]
},
- // Also run CtsNetTestCasesLatestSdk to ensure tests using older shims pass.
- {
- "name": "CtsNetTestCasesLatestSdk",
- "options": [
- {
- "exclude-annotation": "com.android.testutils.SkipPresubmit"
- },
- {
- "exclude-annotation": "androidx.test.filters.RequiresDevice"
- }
- ]
- },
// CTS tests that target older SDKs.
{
"name": "CtsNetTestCasesMaxTargetSdk30",
@@ -267,11 +253,15 @@
},
{
"name": "FrameworksNetTests"
+ },
+ // TODO: Move to presumit after meet SLO requirement.
+ {
+ "name": "NetworkStaticLibHostPythonTests"
}
],
"mainline-presubmit": [
{
- "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "name": "CtsNetTestCases[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -329,7 +319,7 @@
// Test with APK modules only, in cases where APEX is not supported, or the other modules
// were simply not updated
{
- "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk]",
+ "name": "CtsNetTestCases[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk]",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -353,7 +343,7 @@
// really exist in the field, but there is no strong guarantee, and it is required by MTS
// testing for module qualification, where modules are tested independently.
{
- "name": "CtsNetTestCasesLatestSdk[com.google.android.tethering.apex]",
+ "name": "CtsNetTestCases[com.google.android.tethering.apex]",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -404,7 +394,7 @@
"mainline-postsubmit": [
// Tests on physical devices with SIM cards: postsubmit only for capacity constraints
{
- "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "name": "CtsNetTestCases[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
"keywords": ["sim"]
},
{
@@ -418,7 +408,7 @@
},
// Postsubmit on virtual devices to monitor flakiness of @SkipMainlinePresubmit methods
{
- "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
+ "name": "CtsNetTestCases[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]",
"options": [
{
"exclude-annotation": "androidx.test.filters.RequiresDevice"
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index e84573b..b4426a6 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -69,13 +69,7 @@
"android.hardware.tetheroffload.control-V1.0-java",
"android.hardware.tetheroffload.control-V1.1-java",
"android.hidl.manager-V1.2-java",
- "net-utils-framework-common",
- "net-utils-device-common",
- "net-utils-device-common-bpf",
- "net-utils-device-common-ip",
- "net-utils-device-common-netlink",
- "net-utils-device-common-struct",
- "net-utils-device-common-struct-base",
+ "net-utils-tethering",
"netd-client",
"tetheringstatsprotos",
],
diff --git a/Tethering/common/TetheringLib/lint-baseline.xml b/Tethering/common/TetheringLib/lint-baseline.xml
index ed5fbb0..5171efb 100644
--- a/Tethering/common/TetheringLib/lint-baseline.xml
+++ b/Tethering/common/TetheringLib/lint-baseline.xml
@@ -12,4 +12,15 @@
column="50"/>
</issue>
+ <issue
+ id="FlaggedApi"
+ message="Method `TetheringRequest()` is a flagged API and should be inside an `if (Flags.tetheringRequestWithSoftApConfig())` check (or annotate the surrounding method `build` with `@FlaggedApi(Flags.TETHERING_REQUEST_WITH_SOFT_AP_CONFIG) to transfer requirement to caller`)"
+ errorLine1=" return new TetheringRequest(mBuilderParcel);"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="packages/modules/Connectivity/Tethering/common/TetheringLib/src/android/net/TetheringManager.java"
+ line="814"
+ column="24"/>
+ </issue>
+
</issues>
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 8b3102a..ae0161d 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -1379,6 +1379,9 @@
@RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
public void registerTetheringEventCallback(@NonNull Executor executor,
@NonNull TetheringEventCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "registerTetheringEventCallback caller:" + callerPkg);
@@ -1533,6 +1536,8 @@
Manifest.permission.ACCESS_NETWORK_STATE
})
public void unregisterTetheringEventCallback(@NonNull final TetheringEventCallback callback) {
+ Objects.requireNonNull(callback);
+
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "unregisterTetheringEventCallback caller:" + callerPkg);
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index a213ac4..5cdd6ab 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -44,7 +44,6 @@
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.RouteInfo;
-import android.net.RoutingCoordinatorManager;
import android.net.TetheredClient;
import android.net.TetheringManager;
import android.net.TetheringManager.TetheringRequest;
@@ -72,6 +71,7 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetdUtils;
+import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.SyncStateMachine.StateInfo;
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index c310f16..c11938e 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -91,7 +91,6 @@
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkInfo;
-import android.net.RoutingCoordinatorManager;
import android.net.TetherStatesParcel;
import android.net.TetheredClient;
import android.net.TetheringCallbackStartedParcel;
@@ -138,6 +137,7 @@
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.HandlerUtils;
import com.android.net.module.util.NetdUtils;
+import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.networkstack.apishim.common.BluetoothPanShim;
@@ -2397,9 +2397,6 @@
hasCallingPermission(NETWORK_SETTINGS)
|| hasCallingPermission(PERMISSION_MAINLINE_NETWORK_STACK)
|| hasCallingPermission(NETWORK_STACK);
- if (callback == null) {
- throw new NullPointerException();
- }
mHandler.post(() -> {
mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission));
final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
@@ -2437,9 +2434,6 @@
/** Unregister tethering event callback */
void unregisterTetheringEventCallback(ITetheringEventCallback callback) {
- if (callback == null) {
- throw new NullPointerException();
- }
mHandler.post(() -> {
mTetheringEventCallbacks.unregister(callback);
});
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 3f86056..54dbf6c 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -22,7 +22,6 @@
import android.bluetooth.BluetoothPan;
import android.content.Context;
import android.net.INetd;
-import android.net.RoutingCoordinatorManager;
import android.net.connectivity.ConnectivityInternalApiUtil;
import android.net.ip.IpServer;
import android.os.Build;
@@ -36,6 +35,7 @@
import androidx.annotation.RequiresApi;
import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.networkstack.apishim.BluetoothPanShimImpl;
@@ -132,7 +132,8 @@
public LateSdk<RoutingCoordinatorManager> getRoutingCoordinator(Context context) {
if (!SdkLevel.isAtLeastS()) return new LateSdk<>(null);
return new LateSdk<>(
- ConnectivityInternalApiUtil.getRoutingCoordinatorManager(context));
+ new RoutingCoordinatorManager(
+ context, ConnectivityInternalApiUtil.getRoutingCoordinator(context)));
}
/**
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index a147a4a..454cbf1 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -164,6 +164,8 @@
@Override
public void registerTetheringEventCallback(ITetheringEventCallback callback,
String callerPkg) {
+ // Silently ignore call if the callback is null. This can only happen via reflection.
+ if (callback == null) return;
try {
if (!hasTetherAccessPermission()) {
callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION);
@@ -176,6 +178,8 @@
@Override
public void unregisterTetheringEventCallback(ITetheringEventCallback callback,
String callerPkg) {
+ // Silently ignore call if the callback is null. This can only happen via reflection.
+ if (callback == null) return;
try {
if (!hasTetherAccessPermission()) {
callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION);
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 5c258b2..9cdba2f 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -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.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.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 @@
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 @@
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();
+ }
}
diff --git a/Tethering/tests/mts/src/android/tethering/mts/MtsEthernetTetheringTest.java b/Tethering/tests/mts/src/android/tethering/mts/MtsEthernetTetheringTest.java
deleted file mode 100644
index c2bc812..0000000
--- a/Tethering/tests/mts/src/android/tethering/mts/MtsEthernetTetheringTest.java
+++ /dev/null
@@ -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();
- }
-}
diff --git a/Tethering/tests/privileged/Android.bp b/Tethering/tests/privileged/Android.bp
index ba6be66..3597a91 100644
--- a/Tethering/tests/privileged/Android.bp
+++ b/Tethering/tests/privileged/Android.bp
@@ -53,4 +53,5 @@
"TetheringApiCurrentLib",
],
compile_multilib: "both",
+ min_sdk_version: "30",
}
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 00eb3b1..748f23c 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -73,7 +73,6 @@
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.RouteInfo;
-import android.net.RoutingCoordinatorManager;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.IDhcpEventCallbacks;
@@ -91,6 +90,7 @@
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.networkstack.tethering.BpfCoordinator;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index e9cde28..df7141f 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -142,7 +142,6 @@
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.RouteInfo;
-import android.net.RoutingCoordinatorManager;
import android.net.TetherStatesParcel;
import android.net.TetheredClient;
import android.net.TetheredClient.AddressInfo;
@@ -191,6 +190,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.RoutingCoordinatorManager;
import com.android.net.module.util.SdkUtil.LateSdk;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.ip.IpNeighborMonitor;
diff --git a/framework/Android.bp b/framework/Android.bp
index deb1c5a..4eda0aa 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -95,8 +95,7 @@
"framework-connectivity-javastream-protos",
],
impl_only_static_libs: [
- "net-utils-device-common-bpf",
- "net-utils-device-common-struct-base",
+ "net-utils-framework-connectivity",
],
libs: [
"androidx.annotation_annotation",
@@ -123,8 +122,7 @@
// to generate the SDK stubs.
// Even if the library is included in "impl_only_static_libs" of defaults. This is still
// needed because java_library which doesn't understand "impl_only_static_libs".
- "net-utils-device-common-bpf",
- "net-utils-device-common-struct-base",
+ "net-utils-framework-connectivity",
],
libs: [
// This cannot be in the defaults clause above because if it were, it would be used
@@ -332,7 +330,6 @@
srcs: [
// Files listed here MUST all be annotated with @RequiresApi(Build.VERSION_CODES.S)
// or above as appropriate so that API checks are enforced for R+ users of this library
- "src/android/net/RoutingCoordinatorManager.java",
"src/android/net/connectivity/ConnectivityInternalApiUtil.java",
],
visibility: [
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index ffaf41f..5e41dd9 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -6737,21 +6737,11 @@
}
}
- private static final Object sRoutingCoordinatorManagerLock = new Object();
- @GuardedBy("sRoutingCoordinatorManagerLock")
- private static RoutingCoordinatorManager sRoutingCoordinatorManager = null;
/** @hide */
@RequiresApi(Build.VERSION_CODES.S)
- public RoutingCoordinatorManager getRoutingCoordinatorManager() {
+ public IBinder getRoutingCoordinatorService() {
try {
- synchronized (sRoutingCoordinatorManagerLock) {
- if (null == sRoutingCoordinatorManager) {
- sRoutingCoordinatorManager = new RoutingCoordinatorManager(mContext,
- IRoutingCoordinator.Stub.asInterface(
- mService.getRoutingCoordinatorService()));
- }
- return sRoutingCoordinatorManager;
- }
+ return mService.getRoutingCoordinatorService();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java b/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java
index 79f1f65..6e87ed3 100644
--- a/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java
+++ b/framework/src/android/net/connectivity/ConnectivityInternalApiUtil.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.net.ConnectivityManager;
-import android.net.RoutingCoordinatorManager;
import android.os.Build;
import android.os.IBinder;
@@ -54,8 +53,8 @@
* @return an instance of the coordinator manager
*/
@RequiresApi(Build.VERSION_CODES.S)
- public static RoutingCoordinatorManager getRoutingCoordinatorManager(Context ctx) {
+ public static IBinder getRoutingCoordinator(Context ctx) {
final ConnectivityManager cm = ctx.getSystemService(ConnectivityManager.class);
- return cm.getRoutingCoordinatorManager();
+ return cm.getRoutingCoordinatorService();
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
index fcfb15f..c575d40 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClient.java
@@ -29,7 +29,9 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -225,6 +227,12 @@
Log.wtf(TAG, "No mDns packets to send");
return;
}
+ // Check all packets with the same address
+ if (!MdnsUtils.checkAllPacketsWithSameAddress(packets)) {
+ Log.wtf(TAG, "Some mDNS packets have a different target address. addresses="
+ + CollectionUtils.map(packets, DatagramPacket::getSocketAddress));
+ return;
+ }
final boolean isIpv6 = ((InetSocketAddress) packets.get(0).getSocketAddress())
.getAddress() instanceof Inet6Address;
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index b3bdbe0..643430a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -303,8 +303,8 @@
serviceCache.unregisterServiceExpiredCallback(cacheKey);
}
- private static MdnsServiceInfo buildMdnsServiceInfoFromResponse(
- @NonNull MdnsResponse response, @NonNull String[] serviceTypeLabels) {
+ private static MdnsServiceInfo buildMdnsServiceInfoFromResponse(@NonNull MdnsResponse response,
+ @NonNull String[] serviceTypeLabels, long elapsedRealtimeMillis) {
String[] hostName = null;
int port = 0;
if (response.hasServiceRecord()) {
@@ -351,7 +351,7 @@
textEntries,
response.getInterfaceIndex(),
response.getNetwork(),
- now.plusMillis(response.getMinRemainingTtl(now.toEpochMilli())));
+ now.plusMillis(response.getMinRemainingTtl(elapsedRealtimeMillis)));
}
private List<MdnsResponse> getExistingServices() {
@@ -380,8 +380,8 @@
if (existingInfo == null) {
for (MdnsResponse existingResponse : serviceCache.getCachedServices(cacheKey)) {
if (!responseMatchesOptions(existingResponse, searchOptions)) continue;
- final MdnsServiceInfo info =
- buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
+ final MdnsServiceInfo info = buildMdnsServiceInfoFromResponse(
+ existingResponse, serviceTypeLabels, clock.elapsedRealtime());
listener.onServiceNameDiscovered(info, true /* isServiceFromCache */);
listenerInfo.setServiceDiscovered(info.getServiceInstanceName());
if (existingResponse.isComplete()) {
@@ -561,7 +561,7 @@
if (response.getServiceInstanceName() != null) {
listeners.valueAt(i).unsetServiceDiscovered(response.getServiceInstanceName());
final MdnsServiceInfo serviceInfo = buildMdnsServiceInfoFromResponse(
- response, serviceTypeLabels);
+ response, serviceTypeLabels, clock.elapsedRealtime());
if (response.isComplete()) {
sharedLog.log(message + ". onServiceRemoved: " + serviceInfo);
listener.onServiceRemoved(serviceInfo);
@@ -605,8 +605,8 @@
+ " %b, responseIsComplete: %b",
serviceInstanceName, newInCache, serviceBecomesComplete,
response.isComplete()));
- MdnsServiceInfo serviceInfo =
- buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
+ final MdnsServiceInfo serviceInfo = buildMdnsServiceInfoFromResponse(
+ response, serviceTypeLabels, clock.elapsedRealtime());
for (int i = 0; i < listeners.size(); i++) {
// If a service stops matching the options (currently can only happen if it loses a
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
index 9cfcba1..17e5b31 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -28,7 +28,9 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
+import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.net.DatagramPacket;
@@ -249,6 +251,12 @@
Log.wtf(TAG, "No mDns packets to send");
return;
}
+ // Check all packets with the same address
+ if (!MdnsUtils.checkAllPacketsWithSameAddress(packets)) {
+ Log.wtf(TAG, "Some mDNS packets have a different target address. addresses="
+ + CollectionUtils.map(packets, DatagramPacket::getSocketAddress));
+ return;
+ }
final boolean isIpv4 = ((InetSocketAddress) packets.get(0).getSocketAddress())
.getAddress() instanceof Inet4Address;
diff --git a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
index 3c11a24..226867f 100644
--- a/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
+++ b/service-t/src/com/android/server/connectivity/mdns/util/MdnsUtils.java
@@ -34,6 +34,7 @@
import java.io.IOException;
import java.net.DatagramPacket;
+import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
@@ -361,4 +362,23 @@
return SystemClock.elapsedRealtime();
}
}
+
+ /**
+ * Check all DatagramPackets with the same destination address.
+ */
+ public static boolean checkAllPacketsWithSameAddress(List<DatagramPacket> packets) {
+ // No packet for address check
+ if (packets.isEmpty()) {
+ return true;
+ }
+
+ final InetAddress address =
+ ((InetSocketAddress) packets.get(0).getSocketAddress()).getAddress();
+ for (DatagramPacket packet : packets) {
+ if (!address.equals(((InetSocketAddress) packet.getSocketAddress()).getAddress())) {
+ return false;
+ }
+ }
+ return true;
+ }
}
\ No newline at end of file
diff --git a/service/Android.bp b/service/Android.bp
index 1dd09a9..1a0e045 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -190,18 +190,12 @@
"connectivity_native_aidl_interface-lateststable-java",
"dnsresolver_aidl_interface-V15-java",
"modules-utils-shell-command-handler",
- "net-utils-device-common",
- "net-utils-device-common-ip",
- "net-utils-device-common-netlink",
- "net-utils-services-common",
+ "net-utils-service-connectivity",
"netd-client",
"networkstack-client",
"PlatformProperties",
"service-connectivity-protos",
"service-connectivity-stats-protos",
- // The required dependency net-utils-device-common-struct-base is in the classpath via
- // framework-connectivity
- "net-utils-device-common-struct",
],
apex_available: [
"com.android.tethering",
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 310db9a..f015742 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -40,7 +40,17 @@
import static android.net.ConnectivityManager.BLOCKED_REASON_LOCKDOWN_VPN;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.BLOCKED_REASON_NETWORK_RESTRICTED;
+import static android.net.ConnectivityManager.CALLBACK_AVAILABLE;
+import static android.net.ConnectivityManager.CALLBACK_BLK_CHANGED;
+import static android.net.ConnectivityManager.CALLBACK_CAP_CHANGED;
import static android.net.ConnectivityManager.CALLBACK_IP_CHANGED;
+import static android.net.ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED;
+import static android.net.ConnectivityManager.CALLBACK_LOSING;
+import static android.net.ConnectivityManager.CALLBACK_LOST;
+import static android.net.ConnectivityManager.CALLBACK_PRECHECK;
+import static android.net.ConnectivityManager.CALLBACK_RESUMED;
+import static android.net.ConnectivityManager.CALLBACK_SUSPENDED;
+import static android.net.ConnectivityManager.CALLBACK_UNAVAIL;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
@@ -319,6 +329,7 @@
import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
import com.android.net.module.util.LocationPermissionChecker;
+import com.android.net.module.util.RoutingCoordinatorService;
import com.android.net.module.util.PerUidCounter;
import com.android.net.module.util.PermissionUtils;
import com.android.net.module.util.TcUtils;
@@ -358,7 +369,6 @@
import com.android.server.connectivity.ProfileNetworkPreferenceInfo;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.QosCallbackTracker;
-import com.android.server.connectivity.RoutingCoordinatorService;
import com.android.server.connectivity.SatelliteAccessController;
import com.android.server.connectivity.UidRangeUtils;
import com.android.server.connectivity.VpnNetworkPreferenceInfo;
@@ -5364,7 +5374,7 @@
// by other networks that are already connected. Perhaps that can be done by
// sending all CALLBACK_LOST messages (for requests, not listens) at the end
// of rematchAllNetworksAndRequests
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
+ notifyNetworkCallbacks(nai, CALLBACK_LOST);
mKeepaliveTracker.handleStopAllKeepalives(nai, SocketKeepalive.ERROR_INVALID_NETWORK);
mQosCallbackTracker.handleNetworkReleased(nai.network);
@@ -5486,8 +5496,7 @@
// correctly contains null as an upstream.
if (sendCallbacks) {
nri.setSatisfier(null, null);
- notifyNetworkCallbacks(local,
- ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
+ notifyNetworkCallbacks(local, CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
}
}
@@ -5862,8 +5871,7 @@
log("releasing " + nri.mRequests.get(0) + " (timeout)");
}
handleRemoveNetworkRequest(nri);
- callCallbackForRequest(
- nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
+ callCallbackForRequest(nri, null, CALLBACK_UNAVAIL, 0);
}
private void handleReleaseNetworkRequest(@NonNull final NetworkRequest request,
@@ -5879,7 +5887,7 @@
}
handleRemoveNetworkRequest(nri);
if (callOnUnavailable) {
- callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_UNAVAIL, 0);
+ callCallbackForRequest(nri, null, CALLBACK_UNAVAIL, 0);
}
}
@@ -7035,7 +7043,7 @@
// should have its link properties fixed up for PAC proxies.
mProxyTracker.updateDefaultNetworkProxyPortForPAC(nai.linkProperties, nai.network);
if (nai.everConnected()) {
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_IP_CHANGED);
+ notifyNetworkCallbacks(nai, CALLBACK_IP_CHANGED);
}
}
@@ -7792,10 +7800,52 @@
+ " callback flags: " + mCallbackFlags
+ " order: " + mPreferenceOrder
+ " isUidTracked: " + mUidTrackedForBlockedStatus
- + " declaredMethods: 0x" + Integer.toHexString(mDeclaredMethodsFlags);
+ + " declaredMethods: " + declaredMethodsFlagsToString(mDeclaredMethodsFlags);
}
}
+ /**
+ * Get a readable String for a bitmask of declared methods.
+ */
+ @VisibleForTesting
+ public static String declaredMethodsFlagsToString(int flags) {
+ if (flags == DECLARED_METHODS_NONE) {
+ return "NONE";
+ }
+ if (flags == DECLARED_METHODS_ALL) {
+ return "ALL";
+ }
+ final StringBuilder sb = new StringBuilder();
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_PRECHECK, "PRECHK", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_AVAILABLE, "AVAIL", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_LOSING, "LOSING", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_LOST, "LOST", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_UNAVAIL, "UNAVAIL", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_CAP_CHANGED, "NC", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_IP_CHANGED, "LP", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_SUSPENDED, "SUSP", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_RESUMED, "RESUME", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_BLK_CHANGED, "BLK", sb);
+ flags = maybeAppendDeclaredMethod(flags, CALLBACK_LOCAL_NETWORK_INFO_CHANGED,
+ "LOCALINF", sb);
+ if (flags != 0) {
+ sb.append("|0x").append(Integer.toHexString(flags));
+ }
+ return sb.toString();
+ }
+
+ private static int maybeAppendDeclaredMethod(int declaredMethodsFlags,
+ int callbackId, String callbackName, @NonNull StringBuilder builder) {
+ final int callbackFlag = 1 << callbackId;
+ if ((declaredMethodsFlags & callbackFlag) != 0) {
+ if (builder.length() > 0) {
+ builder.append('|');
+ }
+ builder.append(callbackName);
+ }
+ return declaredMethodsFlags & ~callbackFlag;
+ }
+
// Keep backward compatibility since the ServiceSpecificException is used by
// the API surface, see {@link ConnectivityManager#convertServiceException}.
public static class RequestInfoPerUidCounter extends PerUidCounter {
@@ -9082,7 +9132,7 @@
}
networkAgent.networkMonitor().notifyLinkPropertiesChanged(
new LinkProperties(newLp, true /* parcelSensitiveFields */));
- notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_IP_CHANGED);
+ notifyNetworkCallbacks(networkAgent, CALLBACK_IP_CHANGED);
}
mKeepaliveTracker.handleCheckKeepalivesStillValid(networkAgent);
@@ -9578,8 +9628,7 @@
if (prevSuspended != suspended) {
// TODO (b/73132094) : remove this call once the few users of onSuspended and
// onResumed have been removed.
- notifyNetworkCallbacks(nai, suspended ? ConnectivityManager.CALLBACK_SUSPENDED
- : ConnectivityManager.CALLBACK_RESUMED);
+ notifyNetworkCallbacks(nai, suspended ? CALLBACK_SUSPENDED : CALLBACK_RESUMED);
}
if (prevSuspended != suspended || prevRoaming != roaming) {
// updateNetworkInfo will mix in the suspended info from the capabilities and
@@ -9666,7 +9715,7 @@
// If the requestable capabilities have changed or the score changed, we can't have been
// called by rematchNetworkAndRequests, so it's safe to start a rematch.
rematchAllNetworksAndRequests();
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
+ notifyNetworkCallbacks(nai, CALLBACK_CAP_CHANGED);
}
updateNetworkInfoForRoamingAndSuspended(nai, prevNc, newNc);
@@ -9808,7 +9857,7 @@
// But here there is no new request, so the rematch won't see anything. Send
// callbacks to apps now to tell them about the loss of upstream.
notifyNetworkCallbacks(nai,
- ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
+ CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
return;
}
}
@@ -10114,7 +10163,7 @@
private void sendPendingIntentForRequest(NetworkRequestInfo nri, NetworkAgentInfo networkAgent,
int notificationType) {
- if (notificationType == ConnectivityManager.CALLBACK_AVAILABLE && !nri.mPendingIntentSent) {
+ if (notificationType == CALLBACK_AVAILABLE && !nri.mPendingIntentSent) {
Intent intent = new Intent();
intent.putExtra(ConnectivityManager.EXTRA_NETWORK, networkAgent.network);
// If apps could file multi-layer requests with PendingIntents, they'd need to know
@@ -10207,13 +10256,13 @@
final NetworkRequest nrForCallback = nri.getNetworkRequestForCallback();
putParcelable(bundle, nrForCallback);
Message msg = Message.obtain();
- if (notificationType != ConnectivityManager.CALLBACK_UNAVAIL) {
+ if (notificationType != CALLBACK_UNAVAIL) {
putParcelable(bundle, networkAgent.network);
}
final boolean includeLocationSensitiveInfo =
(nri.mCallbackFlags & NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) != 0;
switch (notificationType) {
- case ConnectivityManager.CALLBACK_AVAILABLE: {
+ case CALLBACK_AVAILABLE: {
final NetworkCapabilities nc =
createWithLocationInfoSanitizedIfNecessaryWhenParceled(
networkCapabilitiesRestrictedForCallerPermissions(
@@ -10232,11 +10281,11 @@
msg.arg1 = arg1;
break;
}
- case ConnectivityManager.CALLBACK_LOSING: {
+ case CALLBACK_LOSING: {
msg.arg1 = arg1;
break;
}
- case ConnectivityManager.CALLBACK_CAP_CHANGED: {
+ case CALLBACK_CAP_CHANGED: {
// networkAgent can't be null as it has been accessed a few lines above.
final NetworkCapabilities netCap =
networkCapabilitiesRestrictedForCallerPermissions(
@@ -10249,17 +10298,17 @@
nri.mCallingAttributionTag));
break;
}
- case ConnectivityManager.CALLBACK_IP_CHANGED: {
+ case CALLBACK_IP_CHANGED: {
putParcelable(bundle, linkPropertiesRestrictedForCallerPermissions(
networkAgent.linkProperties, nri.mPid, nri.mUid));
break;
}
- case ConnectivityManager.CALLBACK_BLK_CHANGED: {
+ case CALLBACK_BLK_CHANGED: {
maybeLogBlockedStatusChanged(nri, networkAgent.network, arg1);
msg.arg1 = arg1;
break;
}
- case ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED: {
+ case CALLBACK_LOCAL_NETWORK_INFO_CHANGED: {
if (!networkAgent.isLocalNetwork()) {
Log.wtf(TAG, "Callback for local info for a non-local network");
return;
@@ -10528,7 +10577,7 @@
private void processListenRequests(@NonNull final NetworkAgentInfo nai) {
// For consistency with previous behaviour, send onLost callbacks before onAvailable.
processNewlyLostListenRequests(nai);
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
+ notifyNetworkCallbacks(nai, CALLBACK_CAP_CHANGED);
processNewlySatisfiedListenRequests(nai);
}
@@ -10541,7 +10590,7 @@
if (!nr.isListen()) continue;
if (nai.isSatisfyingRequest(nr.requestId) && !nai.satisfies(nr)) {
nai.removeRequest(nr.requestId);
- callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_LOST, 0);
+ callCallbackForRequest(nri, nai, CALLBACK_LOST, 0);
}
}
}
@@ -10873,7 +10922,7 @@
notifyNetworkAvailable(event.mNewNetwork, event.mNetworkRequestInfo);
} else {
callCallbackForRequest(event.mNetworkRequestInfo, event.mOldNetwork,
- ConnectivityManager.CALLBACK_LOST, 0);
+ CALLBACK_LOST, 0);
}
}
@@ -10917,7 +10966,7 @@
if (null != localInfoChangedAgents) {
for (final NetworkAgentInfo nai : localInfoChangedAgents) {
notifyNetworkCallbacks(nai,
- ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
+ CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
}
}
@@ -10960,7 +11009,7 @@
if (Objects.equals(nai.networkCapabilities, newNc)) return;
updateNetworkPermissions(nai, newNc);
nai.getAndSetNetworkCapabilities(newNc);
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
+ notifyNetworkCallbacks(nai, CALLBACK_CAP_CHANGED);
}
private void updateLegacyTypeTrackerAndVpnLockdownForRematch(
@@ -11329,7 +11378,7 @@
rematchAllNetworksAndRequests();
// This has to happen after matching the requests, because callbacks are just requests.
- notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
+ notifyNetworkCallbacks(networkAgent, CALLBACK_PRECHECK);
} else if (state == NetworkInfo.State.DISCONNECTED) {
networkAgent.disconnect();
if (networkAgent.isVPN()) {
@@ -11362,7 +11411,7 @@
protected void notifyNetworkAvailable(NetworkAgentInfo nai, NetworkRequestInfo nri) {
mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_REQUEST, nri);
if (nri.mPendingIntent != null) {
- sendPendingIntentForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE);
+ sendPendingIntentForRequest(nri, nai, CALLBACK_AVAILABLE);
// Attempt no subsequent state pushes where intents are involved.
return;
}
@@ -11370,14 +11419,14 @@
final int blockedReasons = mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE);
final boolean metered = nai.networkCapabilities.isMetered();
final boolean vpnBlocked = isUidBlockedByVpn(nri.mAsUid, mVpnBlockedUidRanges);
- callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE,
+ callCallbackForRequest(nri, nai, CALLBACK_AVAILABLE,
getBlockedState(nri.mAsUid, blockedReasons, metered, vpnBlocked));
}
// Notify the requests on this NAI that the network is now lingered.
private void notifyNetworkLosing(@NonNull final NetworkAgentInfo nai, final long now) {
final int lingerTime = (int) (nai.getInactivityExpiry() - now);
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime);
+ notifyNetworkCallbacks(nai, CALLBACK_LOSING, lingerTime);
}
private int getPermissionBlockedState(final int uid, final int reasons) {
@@ -11440,7 +11489,7 @@
final int newBlockedState = getBlockedState(
nri.mAsUid, blockedReasons, newMetered, newVpnBlocked);
if (oldBlockedState != newBlockedState) {
- callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
+ callCallbackForRequest(nri, nai, CALLBACK_BLK_CHANGED,
newBlockedState);
}
}
@@ -11467,7 +11516,7 @@
NetworkRequest nr = nai.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
if (nri != null && nri.mAsUid == uid) {
- callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
+ callCallbackForRequest(nri, nai, CALLBACK_BLK_CHANGED,
newBlockedState);
}
}
diff --git a/service/src/com/android/server/connectivity/ConnectivityNativeService.java b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
index cf6127f..917ad4d 100644
--- a/service/src/com/android/server/connectivity/ConnectivityNativeService.java
+++ b/service/src/com/android/server/connectivity/ConnectivityNativeService.java
@@ -23,6 +23,7 @@
import android.os.Binder;
import android.os.Process;
import android.os.ServiceSpecificException;
+import android.os.UserHandle;
import android.system.ErrnoException;
import android.util.Log;
@@ -67,8 +68,8 @@
}
private void enforceBlockPortPermission() {
- final int uid = Binder.getCallingUid();
- if (uid == Process.ROOT_UID || uid == Process.PHONE_UID) return;
+ final int appId = UserHandle.getAppId(Binder.getCallingUid());
+ if (appId == Process.ROOT_UID || appId == Process.PHONE_UID) return;
PermissionUtils.enforceNetworkStackPermission(mContext);
}
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 34ea9ab..2c3a558 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -456,6 +456,126 @@
visibility: ["//packages/modules/Connectivity/service-t"],
}
+java_library {
+ name: "net-utils-framework-connectivity",
+ srcs: [
+ ":net-utils-framework-connectivity-srcs",
+ ],
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ apex_available: [
+ "com.android.tethering",
+ "//apex_available:platform",
+ ],
+ visibility: [
+ "//packages/modules/Connectivity:__subpackages__",
+ "//packages/modules/NetworkStack:__subpackages__",
+ ],
+ libs: [
+ "androidx.annotation_annotation",
+ "framework-annotations-lib",
+ "framework-configinfrastructure",
+ "framework-connectivity.stubs.module_lib",
+ ],
+ lint: {
+ strict_updatability_linting: true,
+ error_checks: ["NewApi"],
+ },
+}
+
+java_defaults {
+ name: "net-utils-non-bootclasspath-defaults",
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ jarjar_rules: "jarjar-rules-shared.txt",
+ libs: [
+ "androidx.annotation_annotation",
+ "framework-annotations-lib",
+ "framework-configinfrastructure",
+ "framework-connectivity",
+ "framework-connectivity.stubs.module_lib",
+ "framework-connectivity-t.stubs.module_lib",
+ "framework-location.stubs.module_lib",
+ "framework-tethering",
+ "unsupportedappusage",
+ ],
+ static_libs: [
+ "modules-utils-build_system",
+ "modules-utils-statemachine",
+ "net-utils-non-bootclasspath-aidl-java",
+ "netd-client",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ "//apex_available:platform",
+ ],
+ visibility: [
+ "//packages/modules/Connectivity:__subpackages__",
+ ],
+ defaults_visibility: [
+ "//visibility:private",
+ ],
+ lint: {
+ strict_updatability_linting: true,
+ error_checks: ["NewApi"],
+ },
+}
+
+java_library {
+ name: "net-utils-service-connectivity",
+ srcs: [
+ ":net-utils-all-srcs",
+ ],
+ exclude_srcs: [
+ ":net-utils-framework-connectivity-srcs",
+ ],
+ libs: [
+ "net-utils-framework-connectivity",
+ ],
+ defaults: ["net-utils-non-bootclasspath-defaults"],
+}
+
+java_library {
+ name: "net-utils-tethering",
+ srcs: [
+ ":net-utils-all-srcs",
+ ":framework-connectivity-shared-srcs",
+ ],
+ defaults: ["net-utils-non-bootclasspath-defaults"],
+}
+
+aidl_interface {
+ name: "net-utils-non-bootclasspath-aidl",
+ srcs: [
+ ":net-utils-aidl-srcs",
+ ],
+ unstable: true,
+ backend: {
+ java: {
+ enabled: true,
+ min_sdk_version: "30",
+ apex_available: [
+ "com.android.tethering",
+ ],
+ },
+ cpp: {
+ enabled: false,
+ },
+ ndk: {
+ enabled: false,
+ },
+ rust: {
+ enabled: false,
+ },
+ },
+ include_dirs: [
+ "packages/modules/Connectivity/framework/aidl-export",
+ ],
+ visibility: [
+ "//system/tools/aidl/build",
+ ],
+}
+
// Use a filegroup and not a library for telephony sources, as framework-annotations cannot be
// included either (some annotations would be duplicated on the bootclasspath).
filegroup {
@@ -507,3 +627,41 @@
"//packages/modules/Wifi/service",
],
}
+
+// Use a file group containing classes necessary for framework-connectivity. The file group should
+// be as small as possible because because the classes end up in the bootclasspath and R8 is not
+// used to remove unused classes.
+filegroup {
+ name: "net-utils-framework-connectivity-srcs",
+ srcs: [
+ "device/com/android/net/module/util/BpfBitmap.java",
+ "device/com/android/net/module/util/BpfDump.java",
+ "device/com/android/net/module/util/BpfMap.java",
+ "device/com/android/net/module/util/BpfUtils.java",
+ "device/com/android/net/module/util/IBpfMap.java",
+ "device/com/android/net/module/util/JniUtil.java",
+ "device/com/android/net/module/util/SingleWriterBpfMap.java",
+ "device/com/android/net/module/util/Struct.java",
+ "device/com/android/net/module/util/TcUtils.java",
+ "framework/com/android/net/module/util/HexDump.java",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "net-utils-all-srcs",
+ srcs: [
+ "device/**/*.java",
+ ":net-utils-framework-common-srcs",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "net-utils-aidl-srcs",
+ srcs: [
+ "device/**/*.aidl",
+ ],
+ path: "device",
+ visibility: ["//visibility:private"],
+}
diff --git a/framework/src/android/net/IRoutingCoordinator.aidl b/staticlibs/device/com/android/net/module/util/IRoutingCoordinator.aidl
similarity index 89%
rename from framework/src/android/net/IRoutingCoordinator.aidl
rename to staticlibs/device/com/android/net/module/util/IRoutingCoordinator.aidl
index cf02ec4..72a4a94 100644
--- a/framework/src/android/net/IRoutingCoordinator.aidl
+++ b/staticlibs/device/com/android/net/module/util/IRoutingCoordinator.aidl
@@ -14,11 +14,15 @@
* limitations under the License.
*/
-package android.net;
+package com.android.net.module.util;
import android.net.RouteInfo;
/** @hide */
+// TODO: b/350630377 - This @Descriptor annotation workaround is to prevent the DESCRIPTOR from
+// being jarjared which changes the DESCRIPTOR and casues "java.lang.SecurityException: Binder
+// invocation to an incorrect interface" when calling the IPC.
+@Descriptor("value=no.jarjar.com.android.net.module.util.IRoutingCoordinator")
interface IRoutingCoordinator {
/**
* Add a route for specific network
diff --git a/framework/src/android/net/RoutingCoordinatorManager.java b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java
similarity index 95%
rename from framework/src/android/net/RoutingCoordinatorManager.java
rename to staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java
index a9e7eef..e37061c 100644
--- a/framework/src/android/net/RoutingCoordinatorManager.java
+++ b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorManager.java
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package android.net;
+package com.android.net.module.util;
import android.content.Context;
+import android.net.RouteInfo;
import android.os.Build;
+import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.NonNull;
@@ -36,9 +38,9 @@
@NonNull final IRoutingCoordinator mService;
public RoutingCoordinatorManager(@NonNull final Context context,
- @NonNull final IRoutingCoordinator service) {
+ @NonNull final IBinder binder) {
mContext = context;
- mService = service;
+ mService = IRoutingCoordinator.Stub.asInterface(binder);
}
/**
diff --git a/service/src/com/android/server/connectivity/RoutingCoordinatorService.java b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorService.java
similarity index 98%
rename from service/src/com/android/server/connectivity/RoutingCoordinatorService.java
rename to staticlibs/device/com/android/net/module/util/RoutingCoordinatorService.java
index 742a2cc..c75b860 100644
--- a/service/src/com/android/server/connectivity/RoutingCoordinatorService.java
+++ b/staticlibs/device/com/android/net/module/util/RoutingCoordinatorService.java
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-package com.android.server.connectivity;
+package com.android.net.module.util;
import static com.android.net.module.util.NetdUtils.toRouteInfoParcel;
import android.annotation.NonNull;
import android.net.INetd;
-import android.net.IRoutingCoordinator;
+
import android.net.RouteInfo;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
diff --git a/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
index cd6bfec..a638cc4 100644
--- a/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
+++ b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
@@ -19,6 +19,7 @@
import android.system.ErrnoException;
import android.util.Pair;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
@@ -60,6 +61,7 @@
public class SingleWriterBpfMap<K extends Struct, V extends Struct> extends BpfMap<K, V> {
// HashMap instead of ArrayMap because it performs better on larger maps, and many maps used in
// our code can contain hundreds of items.
+ @GuardedBy("this")
private final HashMap<K, V> mCache = new HashMap<>();
// This should only ever be called (hence private) once for a given 'path'.
@@ -72,10 +74,12 @@
super(path, BPF_F_RDWR_EXCLUSIVE, key, value);
// Populate cache with the current map contents.
- K currentKey = super.getFirstKey();
- while (currentKey != null) {
- mCache.put(currentKey, super.getValue(currentKey));
- currentKey = super.getNextKey(currentKey);
+ synchronized (this) {
+ K currentKey = super.getFirstKey();
+ while (currentKey != null) {
+ mCache.put(currentKey, super.getValue(currentKey));
+ currentKey = super.getNextKey(currentKey);
+ }
}
}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
index df7010e..0c49edc 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java
@@ -62,6 +62,17 @@
mInterfaceName = null;
}
+ @VisibleForTesting
+ public RtNetlinkLinkMessage(@NonNull StructNlMsgHdr nlmsghdr,
+ int mtu, @NonNull StructIfinfoMsg ifinfomsg, @NonNull MacAddress hardwareAddress,
+ @NonNull String interfaceName) {
+ super(nlmsghdr);
+ mMtu = mtu;
+ mIfinfomsg = ifinfomsg;
+ mHardwareAddress = hardwareAddress;
+ mInterfaceName = interfaceName;
+ }
+
public int getMtu() {
return mMtu;
}
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index cf67a82..91f94b5 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -27,6 +27,7 @@
"net-utils-device-common-ip",
"net-utils-device-common-struct-base",
"net-utils-device-common-wear",
+ "net-utils-service-connectivity",
],
libs: [
"android.test.runner",
@@ -61,3 +62,29 @@
strict_updatability_linting: true,
},
}
+
+python_test_host {
+ name: "NetworkStaticLibHostPythonTests",
+ srcs: [
+ "host/python/*.py",
+ ],
+ main: "host/python/run_tests.py",
+ libs: [
+ "mobly",
+ "net-tests-utils-host-python-common",
+ ],
+ test_config: "host/python/test_config.xml",
+ test_suites: [
+ "general-tests",
+ ],
+ // MoblyBinaryHostTest doesn't support unit_test.
+ test_options: {
+ unit_test: false,
+ },
+ // Needed for applying VirtualEnv.
+ version: {
+ py3: {
+ embedded_launcher: false,
+ },
+ },
+}
diff --git a/staticlibs/tests/unit/host/python/adb_utils_test.py b/staticlibs/tests/unit/host/python/adb_utils_test.py
new file mode 100644
index 0000000..8fcca37
--- /dev/null
+++ b/staticlibs/tests/unit/host/python/adb_utils_test.py
@@ -0,0 +1,122 @@
+# Copyright (C) 2024 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.
+
+from unittest.mock import MagicMock, patch
+from absl.testing import parameterized
+from mobly import asserts
+from mobly import base_test
+from mobly import config_parser
+from net_tests_utils.host.python import adb_utils
+from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError
+
+
+class TestAdbUtils(base_test.BaseTestClass, parameterized.TestCase):
+
+ def __init__(self, configs: config_parser.TestRunConfig):
+ super().__init__(configs)
+
+ def setup_test(self):
+ self.mock_ad = MagicMock() # Mock Android device object
+ self.mock_ad.log = MagicMock()
+ self.mock_ad.adb.shell.return_value = b"" # Default empty return for shell
+
+ @patch(
+ "net_tests_utils.host.python.adb_utils.expect_dumpsys_state_with_retry"
+ )
+ @patch("net_tests_utils.host.python.adb_utils._set_screen_state")
+ def test_set_doze_mode_enable(
+ self, mock_set_screen_state, mock_expect_dumpsys_state
+ ):
+ adb_utils.set_doze_mode(self.mock_ad, True)
+ mock_set_screen_state.assert_called_once_with(self.mock_ad, False)
+
+ @patch(
+ "net_tests_utils.host.python.adb_utils.expect_dumpsys_state_with_retry"
+ )
+ def test_set_doze_mode_disable(self, mock_expect_dumpsys_state):
+ adb_utils.set_doze_mode(self.mock_ad, False)
+
+ @patch("net_tests_utils.host.python.adb_utils._get_screen_state")
+ def test_set_screen_state_success(self, mock_get_screen_state):
+ mock_get_screen_state.side_effect = [False, True] # Simulate toggle
+ adb_utils._set_screen_state(self.mock_ad, True)
+
+ @patch("net_tests_utils.host.python.adb_utils._get_screen_state")
+ def test_set_screen_state_failure(self, mock_get_screen_state):
+ mock_get_screen_state.return_value = False # State doesn't change
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ adb_utils._set_screen_state(self.mock_ad, True)
+
+ @parameterized.parameters(
+ ("Awake", True),
+ ("Asleep", False),
+ ("Dozing", False),
+ ("SomeOtherState", False),
+ ) # Declare inputs for state_str and expected_result.
+ @patch("net_tests_utils.host.python.adb_utils.get_value_of_key_from_dumpsys")
+ def test_get_screen_state(self, state_str, expected_result, mock_get_value):
+ mock_get_value.return_value = state_str
+ asserts.assert_equal(
+ adb_utils._get_screen_state(self.mock_ad), expected_result
+ )
+
+ def test_get_value_of_key_from_dumpsys(self):
+ self.mock_ad.adb.shell.return_value = (
+ b"mWakefulness=Awake\nmOtherKey=SomeValue"
+ )
+ result = adb_utils.get_value_of_key_from_dumpsys(
+ self.mock_ad, "power", "mWakefulness"
+ )
+ asserts.assert_equal(result, "Awake")
+
+ @parameterized.parameters(
+ (True, ["true"]),
+ (False, ["false"]),
+ (
+ True,
+ ["false", "true"],
+ ), # Expect True, get False which is unexpected, then get True
+ (
+ False,
+ ["true", "false"],
+ ), # Expect False, get True which is unexpected, then get False
+ ) # Declare inputs for expected_state and returned_value
+ @patch("net_tests_utils.host.python.adb_utils.get_value_of_key_from_dumpsys")
+ def test_expect_dumpsys_state_with_retry_success(
+ self, expected_state, returned_value, mock_get_value
+ ):
+ mock_get_value.side_effect = returned_value
+ # Verify the method returns and does not throw.
+ adb_utils.expect_dumpsys_state_with_retry(
+ self.mock_ad, "service", "key", expected_state, 0
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.get_value_of_key_from_dumpsys")
+ def test_expect_dumpsys_state_with_retry_failure(self, mock_get_value):
+ mock_get_value.return_value = "false"
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ adb_utils.expect_dumpsys_state_with_retry(
+ self.mock_ad, "service", "key", True, 0
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.get_value_of_key_from_dumpsys")
+ def test_expect_dumpsys_state_with_retry_not_found(self, mock_get_value):
+ # Simulate the get_value_of_key_from_dumpsys cannot find the give key.
+ mock_get_value.return_value = None
+
+ # Expect the function to raise UnexpectedBehaviorError due to the exception
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ adb_utils.expect_dumpsys_state_with_retry(
+ self.mock_ad, "service", "key", True
+ )
diff --git a/staticlibs/tests/unit/host/python/apf_utils_test.py b/staticlibs/tests/unit/host/python/apf_utils_test.py
new file mode 100644
index 0000000..8b390e3
--- /dev/null
+++ b/staticlibs/tests/unit/host/python/apf_utils_test.py
@@ -0,0 +1,152 @@
+# Copyright (C) 2024 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.
+
+from unittest.mock import MagicMock, patch
+from mobly import asserts
+from mobly import base_test
+from mobly import config_parser
+from mobly.controllers.android_device_lib.adb import AdbError
+from net_tests_utils.host.python.apf_utils import (
+ PatternNotFoundException,
+ UnsupportedOperationException,
+ get_apf_counter,
+ get_apf_counters_from_dumpsys,
+ get_hardware_address,
+ send_broadcast_empty_ethercat_packet,
+ send_raw_packet_downstream,
+)
+from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError
+
+
+class TestApfUtils(base_test.BaseTestClass):
+
+ def __init__(self, configs: config_parser.TestRunConfig):
+ super().__init__(configs)
+
+ def setup_test(self):
+ self.mock_ad = MagicMock() # Mock Android device object
+
+ @patch("net_tests_utils.host.python.adb_utils.get_dumpsys_for_service")
+ def test_get_apf_counters_from_dumpsys_success(
+ self, mock_get_dumpsys: MagicMock
+ ) -> None:
+ mock_get_dumpsys.return_value = """
+IpClient.wlan0
+ APF packet counters:
+ COUNTER_NAME1: 123
+ COUNTER_NAME2: 456
+"""
+ counters = get_apf_counters_from_dumpsys(self.mock_ad, "wlan0")
+ asserts.assert_equal(counters, {"COUNTER_NAME1": 123, "COUNTER_NAME2": 456})
+
+ @patch("net_tests_utils.host.python.adb_utils.get_dumpsys_for_service")
+ def test_get_apf_counters_from_dumpsys_exceptions(
+ self, mock_get_dumpsys: MagicMock
+ ) -> None:
+ test_cases = [
+ "",
+ "IpClient.wlan0\n",
+ "IpClient.wlan0\n APF packet counters:\n",
+ """
+IpClient.wlan1
+ APF packet counters:
+ COUNTER_NAME1: 123
+ COUNTER_NAME2: 456
+""",
+ ]
+
+ for dumpsys_output in test_cases:
+ mock_get_dumpsys.return_value = dumpsys_output
+ with asserts.assert_raises(PatternNotFoundException):
+ get_apf_counters_from_dumpsys(self.mock_ad, "wlan0")
+
+ @patch("net_tests_utils.host.python.apf_utils.get_apf_counters_from_dumpsys")
+ def test_get_apf_counter(self, mock_get_counters: MagicMock) -> None:
+ iface = "wlan0"
+ mock_get_counters.return_value = {
+ "COUNTER_NAME1": 123,
+ "COUNTER_NAME2": 456,
+ }
+ asserts.assert_equal(
+ get_apf_counter(self.mock_ad, iface, "COUNTER_NAME1"), 123
+ )
+ # Not found
+ asserts.assert_equal(
+ get_apf_counter(self.mock_ad, iface, "COUNTER_NAME3"), 0
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_hardware_address_success(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = """
+46: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq ...
+ link/ether 72:05:77:82:21:e0 brd ff:ff:ff:ff:ff:ff
+"""
+ mac_address = get_hardware_address(self.mock_ad, "wlan0")
+ asserts.assert_equal(mac_address, "72:05:77:82:21:E0")
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_hardware_address_not_found(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = "Some output without MAC address"
+ with asserts.assert_raises(PatternNotFoundException):
+ get_hardware_address(self.mock_ad, "wlan0")
+
+ @patch("net_tests_utils.host.python.apf_utils.get_hardware_address")
+ @patch("net_tests_utils.host.python.apf_utils.send_raw_packet_downstream")
+ def test_send_broadcast_empty_ethercat_packet(
+ self,
+ mock_send_raw_packet_downstream: MagicMock,
+ mock_get_hardware_address: MagicMock,
+ ) -> None:
+ mock_get_hardware_address.return_value = "12:34:56:78:90:AB"
+ send_broadcast_empty_ethercat_packet(self.mock_ad, "eth0")
+ # Assuming you'll mock the packet construction part, verify calls to send_raw_packet_downstream.
+ mock_send_raw_packet_downstream.assert_called_once()
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_send_raw_packet_downstream_success(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = "" # Successful command output
+ iface_name = "eth0"
+ packet_in_hex = "AABBCCDDEEFF"
+ send_raw_packet_downstream(self.mock_ad, iface_name, packet_in_hex)
+ mock_adb_shell.assert_called_once_with(
+ self.mock_ad,
+ "cmd network_stack send-raw-packet-downstream"
+ f" {iface_name} {packet_in_hex}",
+ )
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_send_raw_packet_downstream_failure(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = ( # Unexpected command output
+ "Any Unexpected Output"
+ )
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ send_raw_packet_downstream(self.mock_ad, "eth0", "AABBCCDDEEFF")
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_send_raw_packet_downstream_unsupported(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.side_effect = AdbError(
+ cmd="", stdout="Unknown command", stderr="", ret_code=3
+ )
+ with asserts.assert_raises(UnsupportedOperationException):
+ send_raw_packet_downstream(self.mock_ad, "eth0", "AABBCCDDEEFF")
diff --git a/staticlibs/tests/unit/host/python/assert_utils_test.py b/staticlibs/tests/unit/host/python/assert_utils_test.py
new file mode 100644
index 0000000..7a33373
--- /dev/null
+++ b/staticlibs/tests/unit/host/python/assert_utils_test.py
@@ -0,0 +1,94 @@
+# Copyright (C) 2024 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.
+
+from mobly import asserts
+from mobly import base_test
+from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError, expect_with_retry
+
+
+class TestAssertUtils(base_test.BaseTestClass):
+
+ def test_predicate_succeed(self):
+ """Test when the predicate becomes True within retries."""
+ call_count = 0
+
+ def predicate():
+ nonlocal call_count
+ call_count += 1
+ return call_count > 2 # True on the third call
+
+ expect_with_retry(predicate, max_retries=5, retry_interval_sec=0)
+ asserts.assert_equal(call_count, 3) # Ensure it was called exactly 3 times
+
+ def test_predicate_failed(self):
+ """Test when the predicate never becomes True."""
+
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ expect_with_retry(
+ predicate=lambda: False, max_retries=3, retry_interval_sec=0
+ )
+
+ def test_retry_action_not_called_succeed(self):
+ """Test that the retry_action is not called if the predicate returns true in the first try."""
+ retry_action_called = False
+
+ def retry_action():
+ nonlocal retry_action_called
+ retry_action_called = True
+
+ expect_with_retry(
+ predicate=lambda: True,
+ retry_action=retry_action,
+ max_retries=5,
+ retry_interval_sec=0,
+ )
+ asserts.assert_false(
+ retry_action_called, "retry_action called."
+ ) # Assert retry_action was NOT called
+
+ def test_retry_action_not_called_failed(self):
+ """Test that the retry_action is not called if the max_retries is reached."""
+ retry_action_called = False
+
+ def retry_action():
+ nonlocal retry_action_called
+ retry_action_called = True
+
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ expect_with_retry(
+ predicate=lambda: False,
+ retry_action=retry_action,
+ max_retries=1,
+ retry_interval_sec=0,
+ )
+ asserts.assert_false(
+ retry_action_called, "retry_action called."
+ ) # Assert retry_action was NOT called
+
+ def test_retry_action_called(self):
+ """Test that the retry_action is executed when provided."""
+ retry_action_called = False
+
+ def retry_action():
+ nonlocal retry_action_called
+ retry_action_called = True
+
+ with asserts.assert_raises(UnexpectedBehaviorError):
+ expect_with_retry(
+ predicate=lambda: False,
+ retry_action=retry_action,
+ max_retries=2,
+ retry_interval_sec=0,
+ )
+ asserts.assert_true(retry_action_called, "retry_action not called.")
diff --git a/staticlibs/tests/unit/host/python/run_tests.py b/staticlibs/tests/unit/host/python/run_tests.py
new file mode 100644
index 0000000..fa6a310
--- /dev/null
+++ b/staticlibs/tests/unit/host/python/run_tests.py
@@ -0,0 +1,35 @@
+# Copyright (C) 2024 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.
+
+"""Main entrypoint for all of unittest."""
+
+import sys
+from host.python.adb_utils_test import TestAdbUtils
+from host.python.apf_utils_test import TestApfUtils
+from host.python.assert_utils_test import TestAssertUtils
+from mobly import suite_runner
+
+
+if __name__ == "__main__":
+ # For MoblyBinaryHostTest, this entry point will be called twice:
+ # 1. List tests.
+ # <mobly-par-file-name> -- --list_tests
+ # 2. Run tests.
+ # <mobly-par-file-name> -- --config=<yaml-path> --device_serial=<device-serial> --log_path=<log-path>
+ # Strip the "--" since suite runner doesn't recognize it.
+ sys.argv.pop(1)
+ # TODO: make the tests can be executed without manually list classes.
+ suite_runner.run_suite(
+ [TestAssertUtils, TestAdbUtils, TestApfUtils], sys.argv
+ )
diff --git a/staticlibs/tests/unit/host/python/test_config.xml b/staticlibs/tests/unit/host/python/test_config.xml
new file mode 100644
index 0000000..d3b200a
--- /dev/null
+++ b/staticlibs/tests/unit/host/python/test_config.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+<configuration description="Config for NetworkStaticLibHostPythonTests">
+ <target_preparer class="com.android.tradefed.targetprep.PythonVirtualenvPreparer">
+ <option name="dep-module" value="absl-py" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest" >
+ <option name="mobly-par-file-name" value="NetworkStaticLibHostPythonTests" />
+ <option name="mobly-test-timeout" value="3m" />
+ </test>
+</configuration>
diff --git a/tests/unit/java/com/android/server/connectivity/RoutingCoordinatorServiceTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/RoutingCoordinatorServiceTest.kt
similarity index 97%
rename from tests/unit/java/com/android/server/connectivity/RoutingCoordinatorServiceTest.kt
rename to staticlibs/tests/unit/src/com/android/net/module/util/RoutingCoordinatorServiceTest.kt
index 4e15d5f..b04561c 100644
--- a/tests/unit/java/com/android/server/connectivity/RoutingCoordinatorServiceTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/RoutingCoordinatorServiceTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.connectivity
+package com.android.net.module.util
import android.net.INetd
import android.os.Build
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index 3843b90..4749e75 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -42,6 +42,7 @@
"net-utils-device-common-struct",
"net-utils-device-common-struct-base",
"net-utils-device-common-wear",
+ "net-utils-framework-connectivity",
"modules-utils-build_system",
],
lint: {
@@ -86,8 +87,8 @@
java_test_host {
name: "net-tests-utils-host-common",
srcs: [
- "host/**/*.java",
- "host/**/*.kt",
+ "host/java/**/*.java",
+ "host/java/**/*.kt",
],
libs: ["tradefed"],
test_suites: [
@@ -104,3 +105,11 @@
],
data: [":ConnectivityTestPreparer"],
}
+
+python_library_host {
+ name: "net-tests-utils-host-python-common",
+ srcs: [
+ "host/python/*.py",
+ ],
+ pkg_path: "net_tests_utils",
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
index 69fdbf8..8687ac7 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
@@ -90,25 +90,10 @@
Modifier.isStatic(it.modifiers) &&
it.isAnnotationPresent(Parameterized.Parameters::class.java) }
- override fun run(notifier: RunNotifier) {
- if (baseRunner == null) {
- // Report a single, skipped placeholder test for this class, as the class is expected to
- // report results when run. In practice runners that apply the Filterable implementation
- // would see a NoTestsRemainException and not call the run method.
- notifier.fireTestIgnored(
- Description.createTestDescription(klass, "skippedClassForDevSdkMismatch"))
- return
- }
- if (!shouldThreadLeakFailTest) {
- baseRunner.run(notifier)
- return
- }
-
- // Dump threads as a baseline to monitor thread leaks.
- val threadCountsBeforeTest = getAllThreadNameCounts()
-
- baseRunner.run(notifier)
-
+ private fun checkThreadLeak(
+ notifier: RunNotifier,
+ threadCountsBeforeTest: Map<String, Int>
+ ) {
notifier.fireTestStarted(leakMonitorDesc)
val threadCountsAfterTest = getAllThreadNameCounts()
// TODO : move CompareOrUpdateResult to its own util instead of LinkProperties.
@@ -122,13 +107,39 @@
val increasedThreads = threadsDiff.updated
.filter { threadCountsBeforeTest[it.key]!! < it.value }
if (threadsDiff.added.isNotEmpty() || increasedThreads.isNotEmpty()) {
- notifier.fireTestFailure(Failure(leakMonitorDesc,
- IllegalStateException("Unexpected thread changes: $threadsDiff")))
+ notifier.fireTestFailure(Failure(
+ leakMonitorDesc,
+ IllegalStateException("Unexpected thread changes: $threadsDiff")
+ ))
+ }
+ notifier.fireTestFinished(leakMonitorDesc)
+ }
+
+ override fun run(notifier: RunNotifier) {
+ if (baseRunner == null) {
+ // Report a single, skipped placeholder test for this class, as the class is expected to
+ // report results when run. In practice runners that apply the Filterable implementation
+ // would see a NoTestsRemainException and not call the run method.
+ notifier.fireTestIgnored(
+ Description.createTestDescription(klass, "skippedClassForDevSdkMismatch")
+ )
+ return
+ }
+ val threadCountsBeforeTest = if (shouldThreadLeakFailTest) {
+ // Dump threads as a baseline to monitor thread leaks.
+ getAllThreadNameCounts()
+ } else {
+ null
+ }
+
+ baseRunner.run(notifier)
+
+ if (threadCountsBeforeTest != null) {
+ checkThreadLeak(notifier, threadCountsBeforeTest)
}
// Clears up internal state of all inline mocks.
// TODO: Call clearInlineMocks() at the end of each test.
Mockito.framework().clearInlineMocks()
- notifier.fireTestFinished(leakMonitorDesc)
}
private fun getAllThreadNameCounts(): Map<String, Int> {
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
index 66362d4..ae43c15 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -44,13 +44,18 @@
object ANY_NETWORK : Network(-2)
fun anyNetwork() = ANY_NETWORK
-open class RecorderCallback private constructor(
- private val backingRecord: ArrayTrackRecord<CallbackEntry>
-) : NetworkCallback() {
- public constructor() : this(ArrayTrackRecord())
- protected constructor(src: RecorderCallback?) : this(src?.backingRecord ?: ArrayTrackRecord())
+private val DEFAULT_TAG = RecorderCallback::class.simpleName
+ ?: fail("Could not determine class name")
- private val TAG = this::class.simpleName
+open class RecorderCallback private constructor(
+ private val backingRecord: ArrayTrackRecord<CallbackEntry>,
+ val logTag: String
+) : NetworkCallback() {
+ public constructor(logTag: String = DEFAULT_TAG) : this(ArrayTrackRecord(), logTag)
+ protected constructor(src: RecorderCallback?, logTag: String) : this(
+ src?.backingRecord ?: ArrayTrackRecord(),
+ logTag
+ )
sealed class CallbackEntry {
// To get equals(), hashcode(), componentN() etc for free, the child classes of
@@ -123,7 +128,7 @@
val mark get() = history.mark
override fun onAvailable(network: Network) {
- Log.d(TAG, "onAvailable $network")
+ Log.d(logTag, "onAvailable $network")
history.add(Available(network))
}
@@ -131,22 +136,22 @@
// expect the callbacks not to record this, do not listen to PreCheck here.
override fun onCapabilitiesChanged(network: Network, caps: NetworkCapabilities) {
- Log.d(TAG, "onCapabilitiesChanged $network $caps")
+ Log.d(logTag, "onCapabilitiesChanged $network $caps")
history.add(CapabilitiesChanged(network, caps))
}
override fun onLinkPropertiesChanged(network: Network, lp: LinkProperties) {
- Log.d(TAG, "onLinkPropertiesChanged $network $lp")
+ Log.d(logTag, "onLinkPropertiesChanged $network $lp")
history.add(LinkPropertiesChanged(network, lp))
}
override fun onLocalNetworkInfoChanged(network: Network, info: LocalNetworkInfo) {
- Log.d(TAG, "onLocalNetworkInfoChanged $network $info")
+ Log.d(logTag, "onLocalNetworkInfoChanged $network $info")
history.add(LocalInfoChanged(network, info))
}
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
- Log.d(TAG, "onBlockedStatusChanged $network $blocked")
+ Log.d(logTag, "onBlockedStatusChanged $network $blocked")
history.add(BlockedStatus(network, blocked))
}
@@ -154,27 +159,27 @@
// fun onBlockedStatusChanged(network: Network, blocked: Int) {
// because on S, that needs to be "override fun", and on R, that cannot be "override fun".
override fun onNetworkSuspended(network: Network) {
- Log.d(TAG, "onNetworkSuspended $network $network")
+ Log.d(logTag, "onNetworkSuspended $network $network")
history.add(Suspended(network))
}
override fun onNetworkResumed(network: Network) {
- Log.d(TAG, "$network onNetworkResumed $network")
+ Log.d(logTag, "$network onNetworkResumed $network")
history.add(Resumed(network))
}
override fun onLosing(network: Network, maxMsToLive: Int) {
- Log.d(TAG, "onLosing $network $maxMsToLive")
+ Log.d(logTag, "onLosing $network $maxMsToLive")
history.add(Losing(network, maxMsToLive))
}
override fun onLost(network: Network) {
- Log.d(TAG, "onLost $network")
+ Log.d(logTag, "onLost $network")
history.add(Lost(network))
}
override fun onUnavailable() {
- Log.d(TAG, "onUnavailable")
+ Log.d(logTag, "onUnavailable")
history.add(Unavailable())
}
}
@@ -188,10 +193,11 @@
*/
open class TestableNetworkCallback private constructor(
src: TestableNetworkCallback?,
- val defaultTimeoutMs: Long = DEFAULT_TIMEOUT,
- val defaultNoCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT,
- val waiterFunc: Runnable = NOOP // "() -> Unit" would forbid calling with a void func from Java
-) : RecorderCallback(src) {
+ val defaultTimeoutMs: Long,
+ val defaultNoCallbackTimeoutMs: Long,
+ val waiterFunc: Runnable,
+ logTag: String
+) : RecorderCallback(src, logTag) {
/**
* Construct a testable network callback.
* @param timeoutMs the default timeout for expecting a callback. Default 30 seconds. This
@@ -213,14 +219,16 @@
constructor(
timeoutMs: Long = DEFAULT_TIMEOUT,
noCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT,
- waiterFunc: Runnable = NOOP
- ) : this(null, timeoutMs, noCallbackTimeoutMs, waiterFunc)
+ waiterFunc: Runnable = NOOP, // "() -> Unit" would forbid calling with a void func from Java
+ logTag: String = DEFAULT_TAG
+ ) : this(null, timeoutMs, noCallbackTimeoutMs, waiterFunc, logTag)
fun createLinkedCopy() = TestableNetworkCallback(
this,
defaultTimeoutMs,
defaultNoCallbackTimeoutMs,
- waiterFunc
+ waiterFunc,
+ logTag
)
// The last available network, or null if any network was lost since the last call to
diff --git a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt b/staticlibs/testutils/host/java/com/android/testutils/ConnectivityTestTargetPreparer.kt
similarity index 100%
rename from staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
rename to staticlibs/testutils/host/java/com/android/testutils/ConnectivityTestTargetPreparer.kt
diff --git a/staticlibs/testutils/host/com/android/testutils/DisableConfigSyncTargetPreparer.kt b/staticlibs/testutils/host/java/com/android/testutils/DisableConfigSyncTargetPreparer.kt
similarity index 100%
rename from staticlibs/testutils/host/com/android/testutils/DisableConfigSyncTargetPreparer.kt
rename to staticlibs/testutils/host/java/com/android/testutils/DisableConfigSyncTargetPreparer.kt
diff --git a/staticlibs/testutils/host/python/adb_utils.py b/staticlibs/testutils/host/python/adb_utils.py
new file mode 100644
index 0000000..13c0646
--- /dev/null
+++ b/staticlibs/testutils/host/python/adb_utils.py
@@ -0,0 +1,118 @@
+# Copyright (C) 2024 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.
+
+import re
+from mobly.controllers import android_device
+from net_tests_utils.host.python import assert_utils
+
+BYTE_DECODE_UTF_8 = "utf-8"
+
+
+def set_doze_mode(ad: android_device.AndroidDevice, enable: bool) -> None:
+ if enable:
+ adb_shell(ad, "cmd battery unplug")
+ expect_dumpsys_state_with_retry(
+ ad, "deviceidle", key="mCharging", expected_state=False
+ )
+ _set_screen_state(ad, False)
+ adb_shell(ad, "dumpsys deviceidle enable deep")
+ expect_dumpsys_state_with_retry(
+ ad, "deviceidle", key="mDeepEnabled", expected_state=True
+ )
+ adb_shell(ad, "dumpsys deviceidle force-idle deep")
+ expect_dumpsys_state_with_retry(
+ ad, "deviceidle", key="mForceIdle", expected_state=True
+ )
+ else:
+ adb_shell(ad, "cmd battery reset")
+ expect_dumpsys_state_with_retry(
+ ad, "deviceidle", key="mCharging", expected_state=True
+ )
+ adb_shell(ad, "dumpsys deviceidle unforce")
+ expect_dumpsys_state_with_retry(
+ ad, "deviceidle", key="mForceIdle", expected_state=False
+ )
+
+
+def _set_screen_state(
+ ad: android_device.AndroidDevice, target_state: bool
+) -> None:
+ assert_utils.expect_with_retry(
+ predicate=lambda: _get_screen_state(ad) == target_state,
+ retry_action=lambda: adb_shell(
+ ad, "input keyevent KEYCODE_POWER"
+ ), # Toggle power key again when retry.
+ )
+
+
+def _get_screen_state(ad: android_device.AndroidDevice) -> bool:
+ return get_value_of_key_from_dumpsys(ad, "power", "mWakefulness") == "Awake"
+
+
+def get_value_of_key_from_dumpsys(
+ ad: android_device.AndroidDevice, service: str, key: str
+) -> str:
+ output = get_dumpsys_for_service(ad, service)
+ # Search for key=value pattern from the dumpsys output.
+ # e.g. mWakefulness=Awake
+ pattern = rf"{key}=(.*)"
+ # Only look for the first occurrence.
+ match = re.search(pattern, output)
+ if match:
+ ad.log.debug(
+ "Getting key-value from dumpsys: " + key + "=" + match.group(1)
+ )
+ return match.group(1)
+ else:
+ return None
+
+
+def expect_dumpsys_state_with_retry(
+ ad: android_device.AndroidDevice,
+ service: str,
+ key: str,
+ expected_state: bool,
+ retry_interval_sec: int = 1,
+) -> None:
+ def predicate():
+ value = get_value_of_key_from_dumpsys(ad, service, key)
+ if value is None:
+ return False
+ return value.lower() == str(expected_state).lower()
+
+ assert_utils.expect_with_retry(
+ predicate=predicate,
+ retry_interval_sec=retry_interval_sec,
+ )
+
+
+def get_dumpsys_for_service(
+ ad: android_device.AndroidDevice, service: str
+) -> str:
+ return adb_shell(ad, "dumpsys " + service)
+
+
+def adb_shell(ad: android_device.AndroidDevice, shell_cmd: str) -> str:
+ """Runs adb shell command.
+
+ Args:
+ ad: Android device object.
+ shell_cmd: string of list of strings, adb shell command.
+
+ Returns:
+ string, replies from adb shell command.
+ """
+ ad.log.debug("Executing adb shell %s", shell_cmd)
+ data = ad.adb.shell(shell_cmd)
+ return data.decode(BYTE_DECODE_UTF_8).strip()
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
new file mode 100644
index 0000000..f71464c
--- /dev/null
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -0,0 +1,192 @@
+# Copyright (C) 2024 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.
+
+import re
+from mobly.controllers import android_device
+from mobly.controllers.android_device_lib.adb import AdbError
+from net_tests_utils.host.python import adb_utils, assert_utils
+
+
+# Constants.
+ETHER_BROADCAST = "FFFFFFFFFFFF"
+ETH_P_ETHERCAT = "88A4"
+
+
+class PatternNotFoundException(Exception):
+ """Raised when the given pattern cannot be found."""
+
+
+class UnsupportedOperationException(Exception):
+ pass
+
+
+def get_apf_counter(
+ ad: android_device.AndroidDevice, iface: str, counter_name: str
+) -> int:
+ counters = get_apf_counters_from_dumpsys(ad, iface)
+ return counters.get(counter_name, 0)
+
+
+def get_apf_counters_from_dumpsys(
+ ad: android_device.AndroidDevice, iface_name: str
+) -> dict:
+ dumpsys = adb_utils.get_dumpsys_for_service(ad, "network_stack")
+
+ # Extract IpClient section of the specified interface.
+ # This takes inputs like:
+ # IpClient.wlan0
+ # ...
+ # IpClient.wlan1
+ # ...
+ iface_pattern = re.compile(
+ r"^IpClient\." + iface_name + r"\n" + r"((^\s.*\n)+)", re.MULTILINE
+ )
+ iface_result = iface_pattern.search(dumpsys)
+ if iface_result is None:
+ raise PatternNotFoundException("Cannot find IpClient for " + iface_name)
+
+ # Extract APF counters section from IpClient section, which looks like:
+ # APF packet counters:
+ # COUNTER_NAME: VALUE
+ # ....
+ apf_pattern = re.compile(
+ r"APF packet counters:.*\n.(\s+[A-Z_0-9]+: \d+\n)+", re.MULTILINE
+ )
+ apf_result = apf_pattern.search(iface_result.group(0))
+ if apf_result is None:
+ raise PatternNotFoundException(
+ "Cannot find APF counters in text: " + iface_result.group(0)
+ )
+
+ # Extract key-value pairs from APF counters section into a list of tuples,
+ # e.g. [('COUNTER1', '1'), ('COUNTER2', '2')].
+ counter_pattern = re.compile(r"(?P<name>[A-Z_0-9]+): (?P<value>\d+)")
+ counter_result = counter_pattern.findall(apf_result.group(0))
+ if counter_result is None:
+ raise PatternNotFoundException(
+ "Cannot extract APF counters in text: " + apf_result.group(0)
+ )
+
+ # Convert into a dict.
+ result = {}
+ for key, value_str in counter_result:
+ result[key] = int(value_str)
+
+ ad.log.debug("Getting apf counters: " + str(result))
+ return result
+
+
+def get_hardware_address(
+ ad: android_device.AndroidDevice, iface_name: str
+) -> str:
+ """Retrieves the hardware (MAC) address for a given network interface.
+
+ Returns:
+ The hex representative of the MAC address in uppercase.
+ E.g. 12:34:56:78:90:AB
+
+ Raises:
+ PatternNotFoundException: If the MAC address is not found in the command
+ output.
+ """
+
+ # Run the "ip link" command and get its output.
+ ip_link_output = adb_utils.adb_shell(ad, f"ip link show {iface_name}")
+
+ # Regular expression to extract the MAC address.
+ # Parse hardware address from ip link output like below:
+ # 46: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq ...
+ # link/ether 72:05:77:82:21:e0 brd ff:ff:ff:ff:ff:ff
+ pattern = r"link/ether (([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2})"
+ match = re.search(pattern, ip_link_output)
+
+ if match:
+ return match.group(1).upper() # Extract the MAC address string.
+ else:
+ raise PatternNotFoundException(
+ "Cannot get hardware address for " + iface_name
+ )
+
+
+def send_broadcast_empty_ethercat_packet(
+ ad: android_device.AndroidDevice, iface_name: str
+) -> None:
+ """Transmits a broadcast empty EtherCat packet on the specified interface."""
+
+ # Get the interface's MAC address.
+ mac_address = get_hardware_address(ad, iface_name)
+
+ # TODO: Build packet by using scapy library.
+ # Ethernet header (14 bytes).
+ packet = ETHER_BROADCAST # Destination MAC (broadcast)
+ packet += mac_address.replace(":", "") # Source MAC
+ packet += ETH_P_ETHERCAT # EtherType (EtherCAT)
+
+ # EtherCAT header (2 bytes) + 44 bytes of zero padding.
+ packet += "00" * 46
+
+ # Send the packet using a raw socket.
+ send_raw_packet_downstream(ad, iface_name, packet)
+
+
+def send_raw_packet_downstream(
+ ad: android_device.AndroidDevice,
+ iface_name: str,
+ packet_in_hex: str,
+) -> None:
+ """Sends a raw packet over the specified downstream interface.
+
+ This function constructs and sends a raw packet using the
+ `send-raw-packet-downstream`
+ command provided by NetworkStack process. It's primarily intended for testing
+ purposes.
+
+ Args:
+ ad: The AndroidDevice object representing the connected device.
+ iface_name: The name of the network interface to use (e.g., "wlan0",
+ "eth0").
+ packet_in_hex: The raw packet data starting from L2 header encoded in
+ hexadecimal string format.
+
+ Raises:
+ UnsupportedOperationException: If the NetworkStack doesn't support
+ the `send-raw-packet` command.
+ UnexpectedBehaviorException: If the command execution produces unexpected
+ output other than an empty response or "Unknown command".
+
+ Important Considerations:
+ Security: This method only works on tethering downstream interfaces due
+ to security restrictions.
+ Packet Format: The `packet_in_hex` must be a valid hexadecimal
+ representation of a packet starting from L2 header.
+ """
+
+ cmd = (
+ "cmd network_stack send-raw-packet-downstream"
+ f" {iface_name} {packet_in_hex}"
+ )
+
+ # Expect no output or Unknown command if NetworkStack is too old. Throw otherwise.
+ try:
+ output = adb_utils.adb_shell(ad, cmd)
+ except AdbError as e:
+ output = str(e.stdout)
+ if output:
+ if "Unknown command" in output:
+ raise UnsupportedOperationException(
+ "send-raw-packet-downstream command is not supported."
+ )
+ raise assert_utils.UnexpectedBehaviorError(
+ f"Got unexpected output: {output} for command: {cmd}."
+ )
diff --git a/staticlibs/testutils/host/python/assert_utils.py b/staticlibs/testutils/host/python/assert_utils.py
new file mode 100644
index 0000000..da1bb9e
--- /dev/null
+++ b/staticlibs/testutils/host/python/assert_utils.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2024 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.
+
+import time
+from typing import Callable
+
+
+class UnexpectedBehaviorError(Exception):
+ """Raised when there is an unexpected behavior during applying a procedure."""
+
+
+def expect_with_retry(
+ predicate: Callable[[], bool],
+ retry_action: Callable[[], None] = None,
+ max_retries: int = 10,
+ retry_interval_sec: int = 1,
+) -> None:
+ """Executes a predicate and retries if it doesn't return True."""
+
+ for retry in range(max_retries):
+ if predicate():
+ return None
+ else:
+ if retry == max_retries - 1:
+ break
+ if retry_action:
+ retry_action()
+ time.sleep(retry_interval_sec)
+
+ raise UnexpectedBehaviorError(
+ "Predicate didn't become true after " + str(max_retries) + " retries."
+ )
diff --git a/tests/cts/multidevices/utils/mdns_utils.py b/staticlibs/testutils/host/python/mdns_utils.py
similarity index 100%
rename from tests/cts/multidevices/utils/mdns_utils.py
rename to staticlibs/testutils/host/python/mdns_utils.py
diff --git a/tests/cts/multidevices/utils/tether_utils.py b/staticlibs/testutils/host/python/tether_utils.py
similarity index 100%
rename from tests/cts/multidevices/utils/tether_utils.py
rename to staticlibs/testutils/host/python/tether_utils.py
diff --git a/tests/cts/multidevices/Android.bp b/tests/cts/multidevices/Android.bp
index 1d30d68..dc90adb 100644
--- a/tests/cts/multidevices/Android.bp
+++ b/tests/cts/multidevices/Android.bp
@@ -22,10 +22,10 @@
main: "connectivity_multi_devices_test.py",
srcs: [
"connectivity_multi_devices_test.py",
- "utils/*.py",
],
libs: [
"mobly",
+ "net-tests-utils-host-python-common",
],
test_suites: [
"cts",
diff --git a/tests/cts/multidevices/connectivity_multi_devices_test.py b/tests/cts/multidevices/connectivity_multi_devices_test.py
index abd6fe2..0cfc361 100644
--- a/tests/cts/multidevices/connectivity_multi_devices_test.py
+++ b/tests/cts/multidevices/connectivity_multi_devices_test.py
@@ -1,15 +1,17 @@
# Lint as: python3
"""Connectivity multi devices tests."""
import sys
+
+from mobly import asserts
from mobly import base_test
from mobly import test_runner
from mobly import utils
from mobly.controllers import android_device
-from utils import mdns_utils
-from utils import tether_utils
-from utils.tether_utils import UpstreamType
+from net_tests_utils.host.python import adb_utils, apf_utils, assert_utils, mdns_utils, tether_utils
+from net_tests_utils.host.python.tether_utils import UpstreamType
CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE = "com.google.snippet.connectivity"
+COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED = "DROPPED_ETHERTYPE_NOT_ALLOWED"
class ConnectivityMultiDevicesTest(base_test.BaseTestClass):
@@ -69,19 +71,61 @@
try:
# Connectivity of the client verified by asserting the validated capability.
tether_utils.setup_hotspot_and_client_for_upstream_type(
- self.serverDevice, self.clientDevice, UpstreamType.NONE
+ self.serverDevice, self.clientDevice, UpstreamType.NONE
)
mdns_utils.register_mdns_service_and_discover_resolve(
- self.clientDevice, self.serverDevice
+ self.clientDevice, self.serverDevice
)
finally:
- mdns_utils.cleanup_mdns_service(
- self.clientDevice, self.serverDevice
- )
+ mdns_utils.cleanup_mdns_service(self.clientDevice, self.serverDevice)
tether_utils.cleanup_tethering_for_upstream_type(
- self.serverDevice, UpstreamType.NONE
+ self.serverDevice, UpstreamType.NONE
)
+ def test_apf_drop_ethercat(self):
+ tether_utils.assume_hotspot_test_preconditions(
+ self.serverDevice, self.clientDevice, UpstreamType.NONE
+ )
+ client = self.clientDevice.connectivity_multi_devices_snippet
+ try:
+ server_iface_name, client_network = (
+ tether_utils.setup_hotspot_and_client_for_upstream_type(
+ self.serverDevice, self.clientDevice, UpstreamType.NONE
+ )
+ )
+ client_iface_name = client.getInterfaceNameFromNetworkHandle(client_network)
+
+ adb_utils.set_doze_mode(self.clientDevice, True)
+
+ count_before_test = apf_utils.get_apf_counter(
+ self.clientDevice,
+ client_iface_name,
+ COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED,
+ )
+ try:
+ apf_utils.send_broadcast_empty_ethercat_packet(
+ self.serverDevice, server_iface_name
+ )
+ except apf_utils.UnsupportedOperationException:
+ asserts.skip(
+ "NetworkStack is too old to support send raw packet, skip test."
+ )
+
+ assert_utils.expect_with_retry(
+ lambda: apf_utils.get_apf_counter(
+ self.clientDevice,
+ client_iface_name,
+ COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED,
+ )
+ > count_before_test
+ )
+ finally:
+ adb_utils.set_doze_mode(self.clientDevice, False)
+ tether_utils.cleanup_tethering_for_upstream_type(
+ self.serverDevice, UpstreamType.NONE
+ )
+
+
if __name__ == "__main__":
# Take test args
if "--" in sys.argv:
diff --git a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
index f4ad2c4..9bdf4a3 100644
--- a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
+++ b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
@@ -21,6 +21,7 @@
import android.content.pm.PackageManager.FEATURE_TELEPHONY
import android.content.pm.PackageManager.FEATURE_WIFI
import android.net.ConnectivityManager
+import android.net.Network
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
@@ -129,6 +130,12 @@
}
}
+ @Rpc(description = "Get interface name from NetworkHandle")
+ fun getInterfaceNameFromNetworkHandle(networkHandle: Long): String {
+ val network = Network.fromNetworkHandle(networkHandle)
+ return cm.getLinkProperties(network)!!.getInterfaceName()!!
+ }
+
@Rpc(description = "Check whether the device supports hotspot feature.")
fun hasHotspotFeature(): Boolean {
val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback()
@@ -140,7 +147,7 @@
}
@Rpc(description = "Start a hotspot with given SSID and passphrase.")
- fun startHotspot(ssid: String, passphrase: String) {
+ fun startHotspot(ssid: String, passphrase: String): String {
// Store old config.
runAsShell(OVERRIDE_WIFI_CONFIG) {
oldSoftApConfig = wifiManager.getSoftApConfiguration()
@@ -157,7 +164,7 @@
val tetheringCallback = ctsTetheringUtils.registerTetheringEventCallback()
try {
tetheringCallback.expectNoTetheringActive()
- ctsTetheringUtils.startWifiTethering(tetheringCallback)
+ return ctsTetheringUtils.startWifiTethering(tetheringCallback).getInterface()
} finally {
ctsTetheringUtils.unregisterTetheringEventCallback(tetheringCallback)
}
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index ae85701..1cd8327 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -111,25 +111,6 @@
min_sdk_version: "30",
}
-// Networking CTS tests that target the latest released SDK. These tests can be installed on release
-// devices at any point in the Android release cycle and are useful for qualifying mainline modules
-// on release devices.
-android_test {
- name: "CtsNetTestCasesLatestSdk",
- defaults: [
- "ConnectivityTestsLatestSdkDefaults",
- "CtsNetTestCasesDefaults",
- "CtsNetTestCasesApiStableDefaults",
- ],
- test_suites: [
- "general-tests",
- "mts-dnsresolver",
- "mts-networking",
- "mts-tethering",
- "mts-wifi",
- ],
-}
-
java_defaults {
name: "CtsNetTestCasesMaxTargetSdkDefaults",
defaults: [
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 077c3ef..024d3bf 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -48,8 +48,8 @@
those tests with an annotation matching the name of the APK.
This allows us to maintain one AndroidTestTemplate.xml for all CtsNetTestCases*.apk,
- and have CtsNetTestCases and CtsNetTestCasesLatestSdk run all tests, but have
- CtsNetTestCasesMaxTargetSdk31 run only tests that require target SDK 31.
+ and have CtsNetTestCases run all tests, but have CtsNetTestCasesMaxTargetSdk31 run only
+ tests that require target SDK 31.
This relies on the fact that if the class specified in include-annotation exists, then
the runner will only run the tests annotated with that annotation, but if it does not,
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index f6cbeeb..5662fca 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -27,10 +27,17 @@
import android.net.NetworkRequest
import android.net.apf.ApfCapabilities
import android.net.apf.ApfConstants.ETH_ETHERTYPE_OFFSET
+import android.net.apf.ApfConstants.ETH_HEADER_LEN
+import android.net.apf.ApfConstants.ICMP6_CHECKSUM_OFFSET
import android.net.apf.ApfConstants.ICMP6_TYPE_OFFSET
+import android.net.apf.ApfConstants.IPV6_DEST_ADDR_OFFSET
+import android.net.apf.ApfConstants.IPV6_HEADER_LEN
import android.net.apf.ApfConstants.IPV6_NEXT_HEADER_OFFSET
+import android.net.apf.ApfConstants.IPV6_SRC_ADDR_OFFSET
import android.net.apf.ApfCounterTracker
+import android.net.apf.ApfCounterTracker.Counter.DROPPED_IPV6_MULTICAST_PING
import android.net.apf.ApfCounterTracker.Counter.FILTER_AGE_16384THS
+import android.net.apf.ApfCounterTracker.Counter.PASSED_IPV6_ICMP
import android.net.apf.ApfV4Generator
import android.net.apf.ApfV4GeneratorBase
import android.net.apf.ApfV6Generator
@@ -61,6 +68,12 @@
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
import com.android.compatibility.common.util.VsrTest
import com.android.internal.util.HexDump
+import com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN
+import com.android.net.module.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET
+import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN
+import com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET
+import com.android.net.module.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN
+import com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN
import com.android.net.module.util.PacketReader
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
@@ -216,8 +229,8 @@
Os.sendto(sockFd!!, packet, 0, packet.size, 0, PING_DESTINATION)
}
- fun expectPingReply(): ByteArray {
- return futureReply!!.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ fun expectPingReply(timeoutMs: Long = TIMEOUT_MS): ByteArray {
+ return futureReply!!.get(timeoutMs, TimeUnit.MILLISECONDS)
}
fun expectPingDropped() {
@@ -622,4 +635,103 @@
assertThat(timeDiff).isGreaterThan(timeDiffLowerBound)
assertThat(timeDiff).isLessThan(timeDiffUpperBound)
}
+
+ @VsrTest(
+ requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005", "VSR-5.3.12-012", "VSR-5.3.12-013",
+ "VSR-5.3.12-014", "VSR-5.3.12-015", "VSR-5.3.12-016", "VSR-5.3.12-017"]
+ )
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testReplyPing() {
+ assumeApfVersionSupportAtLeast(6000)
+ installProgram(ByteArray(caps.maximumApfProgramSize) { 0 }) // Clear previous program
+ readProgram() // Ensure installation is complete
+
+ val payloadSize = 56
+ val payload = ByteArray(payloadSize).also { Random.nextBytes(it) }
+ val firstByte = payload.take(1).toByteArray()
+
+ val pingRequestIpv6PayloadLen = PING_HEADER_LENGTH + 1
+ val pingRequestPktLen = ETH_HEADER_LEN + IPV6_HEADER_LEN + pingRequestIpv6PayloadLen
+
+ val gen = ApfV6Generator(
+ caps.apfVersionSupported,
+ caps.maximumApfProgramSize,
+ caps.maximumApfProgramSize
+ )
+ val skipPacketLabel = gen.uniqueLabel
+
+ // Summary of the program:
+ // if the packet is not ICMPv6 echo reply
+ // pass
+ // else if the echo reply payload size is 1
+ // increase PASSED_IPV6_ICMP counter
+ // pass
+ // else
+ // transmit a ICMPv6 echo request packet with the first byte of the payload in the reply
+ // increase DROPPED_IPV6_MULTICAST_PING counter
+ // drop
+ val program = gen
+ .addLoad16(R0, ETH_ETHERTYPE_OFFSET)
+ .addJumpIfR0NotEquals(ETH_P_IPV6.toLong(), skipPacketLabel)
+ .addLoad8(R0, IPV6_NEXT_HEADER_OFFSET)
+ .addJumpIfR0NotEquals(IPPROTO_ICMPV6.toLong(), skipPacketLabel)
+ .addLoad8(R0, ICMP6_TYPE_OFFSET)
+ .addJumpIfR0NotEquals(0x81, skipPacketLabel) // Echo reply type
+ .addLoadFromMemory(R0, MemorySlot.PACKET_SIZE)
+ .addCountAndPassIfR0Equals(
+ (ETHER_HEADER_LEN + IPV6_HEADER_LEN + PING_HEADER_LENGTH + firstByte.size)
+ .toLong(),
+ PASSED_IPV6_ICMP
+ )
+ // Ping Packet Generation
+ .addAllocate(pingRequestPktLen)
+ // Eth header
+ .addPacketCopy(ETHER_SRC_ADDR_OFFSET, ETHER_ADDR_LEN) // dst MAC address
+ .addPacketCopy(ETHER_DST_ADDR_OFFSET, ETHER_ADDR_LEN) // src MAC address
+ .addWriteU16(ETH_P_IPV6) // IPv6 type
+ // IPv6 Header
+ .addWrite32(0x60000000) // IPv6 Header: version, traffic class, flowlabel
+ // payload length (2 bytes) | next header: ICMPv6 (1 byte) | hop limit (1 byte)
+ .addWrite32(pingRequestIpv6PayloadLen shl 16 or (IPPROTO_ICMPV6 shl 8 or 64))
+ .addPacketCopy(IPV6_DEST_ADDR_OFFSET, IPV6_ADDR_LEN) // src ip
+ .addPacketCopy(IPV6_SRC_ADDR_OFFSET, IPV6_ADDR_LEN) // dst ip
+ // ICMPv6
+ .addWriteU8(0x80) // type: echo request
+ .addWriteU8(0) // code
+ .addWriteU16(pingRequestIpv6PayloadLen) // checksum
+ // identifier
+ .addPacketCopy(ETHER_HEADER_LEN + IPV6_HEADER_LEN + ICMPV6_HEADER_MIN_LEN, 2)
+ .addWriteU16(0) // sequence number
+ .addDataCopy(firstByte) // data
+ .addTransmitL4(
+ ETHER_HEADER_LEN, // ip_ofs
+ ICMP6_CHECKSUM_OFFSET, // csum_ofs
+ IPV6_SRC_ADDR_OFFSET, // csum_start
+ IPPROTO_ICMPV6, // partial_sum
+ false // udp
+ )
+ // Warning: the program abuse DROPPED_IPV6_MULTICAST_PING for debugging purpose
+ .addCountAndDrop(DROPPED_IPV6_MULTICAST_PING)
+ .defineLabel(skipPacketLabel)
+ .addPass()
+ .generate()
+
+ installProgram(program)
+ readProgram() // Ensure installation is complete
+
+ packetReader.sendPing(payload, payloadSize)
+
+ val replyPayload = try {
+ packetReader.expectPingReply(TIMEOUT_MS * 2)
+ } catch (e: TimeoutException) {
+ byteArrayOf() // Empty payload if timeout occurs
+ }
+
+ val apfCounterTracker = ApfCounterTracker()
+ apfCounterTracker.updateCountersFromData(readProgram())
+ Log.i(TAG, "counter map: ${apfCounterTracker.counters}")
+
+ assertThat(replyPayload).isEqualTo(firstByte)
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
index 284fcae..f45f881 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
@@ -59,7 +59,6 @@
@After
override fun tearDown() {
super.tearDown()
- setIncludeTestInterfaces(false)
}
@Test
@@ -107,7 +106,6 @@
@Test
fun testMdnsDiscoveryWorkOnTetheringInterface() {
assumeFalse(isInterfaceForTetheringAvailable())
- setIncludeTestInterfaces(true)
var downstreamIface: TestNetworkInterface? = null
var tetheringEventCallback: MyTetheringEventCallback? = null
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
index fb3d183..4c71991 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsMultinetworkSocketClientTest.java
@@ -18,8 +18,10 @@
import static com.android.server.connectivity.mdns.MdnsSocketProvider.SocketCallback;
import static com.android.server.connectivity.mdns.MulticastPacketReader.PacketHandler;
+import static com.android.testutils.Cleanup.testAndCleanup;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
@@ -35,6 +37,7 @@
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
+import android.util.Log;
import com.android.net.module.util.HexDump;
import com.android.net.module.util.SharedLog;
@@ -59,6 +62,7 @@
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
@RunWith(DevSdkIgnoreRunner.class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -437,4 +441,34 @@
inOrder.verify(mSocket).send(packets.get(i));
}
}
+
+ @Test
+ public void testSendPacketWithMultiplePacketsWithDifferentAddresses() throws IOException {
+ final SocketCallback callback = expectSocketCallback();
+ final DatagramPacket ipv4Packet = new DatagramPacket(BUFFER, 0 /* offset */, BUFFER.length,
+ InetAddresses.parseNumericAddress("192.0.2.1"), 0 /* port */);
+ final DatagramPacket ipv6Packet = new DatagramPacket(BUFFER, 0 /* offset */, BUFFER.length,
+ InetAddresses.parseNumericAddress("2001:db8::"), 0 /* port */);
+ doReturn(true).when(mSocket).hasJoinedIpv4();
+ doReturn(true).when(mSocket).hasJoinedIpv6();
+ doReturn(createEmptyNetworkInterface()).when(mSocket).getInterface();
+
+ // Notify socket created
+ callback.onSocketCreated(mSocketKey, mSocket, List.of());
+ verify(mSocketCreationCallback).onSocketCreated(mSocketKey);
+
+ // Send packets with IPv4 and IPv6 then verify wtf logs and sending has never been called.
+ // Override the default TerribleFailureHandler, as that handler might terminate the process
+ // (if we're on an eng build).
+ final AtomicBoolean hasFailed = new AtomicBoolean(false);
+ final Log.TerribleFailureHandler originalHandler =
+ Log.setWtfHandler((tag, what, system) -> hasFailed.set(true));
+ testAndCleanup(() -> {
+ mSocketClient.sendPacketRequestingMulticastResponse(List.of(ipv4Packet, ipv6Packet),
+ mSocketKey, false /* onlyUseIpv6OnIpv6OnlyNetworks */);
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ assertTrue(hasFailed.get());
+ verify(mSocket, never()).send(any());
+ }, () -> Log.setWtfHandler(originalHandler));
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
index 1989ed3..ab70e38 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
@@ -16,6 +16,7 @@
package com.android.server.connectivity.mdns;
+import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertFalse;
@@ -38,9 +39,11 @@
import android.annotation.RequiresPermission;
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.InetAddresses;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.MulticastLock;
import android.text.format.DateUtils;
+import android.util.Log;
import com.android.net.module.util.HexDump;
import com.android.net.module.util.SharedLog;
@@ -594,6 +597,29 @@
}
}
+ @Test
+ public void testSendPacketWithMultiplePacketsWithDifferentAddresses() throws IOException {
+ mdnsClient.startDiscovery();
+ final byte[] buffer = new byte[10];
+ final DatagramPacket ipv4Packet = new DatagramPacket(buffer, 0 /* offset */, buffer.length,
+ InetAddresses.parseNumericAddress("192.0.2.1"), 0 /* port */);
+ final DatagramPacket ipv6Packet = new DatagramPacket(buffer, 0 /* offset */, buffer.length,
+ InetAddresses.parseNumericAddress("2001:db8::"), 0 /* port */);
+
+ // Send packets with IPv4 and IPv6 then verify wtf logs and sending has never been called.
+ // Override the default TerribleFailureHandler, as that handler might terminate the process
+ // (if we're on an eng build).
+ final AtomicBoolean hasFailed = new AtomicBoolean(false);
+ final Log.TerribleFailureHandler originalHandler =
+ Log.setWtfHandler((tag, what, system) -> hasFailed.set(true));
+ testAndCleanup(() -> {
+ mdnsClient.sendPacketRequestingMulticastResponse(List.of(ipv4Packet, ipv6Packet),
+ false /* onlyUseIpv6OnIpv6OnlyNetworks */);
+ assertTrue(hasFailed.get());
+ verify(mockMulticastSocket, never()).send(any());
+ }, () -> Log.setWtfHandler(originalHandler));
+ }
+
private DatagramPacket getTestDatagramPacket() {
return new DatagramPacket(buf, 0, 5,
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), 5353 /* port */));
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
index 009205e..cf88d05 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/util/MdnsUtilsTest.kt
@@ -16,9 +16,12 @@
package com.android.server.connectivity.mdns.util
+import android.net.InetAddresses
import android.os.Build
import com.android.server.connectivity.mdns.MdnsConstants
import com.android.server.connectivity.mdns.MdnsConstants.FLAG_TRUNCATED
+import com.android.server.connectivity.mdns.MdnsConstants.IPV4_SOCKET_ADDR
+import com.android.server.connectivity.mdns.MdnsConstants.IPV6_SOCKET_ADDR
import com.android.server.connectivity.mdns.MdnsPacket
import com.android.server.connectivity.mdns.MdnsPacketReader
import com.android.server.connectivity.mdns.MdnsPointerRecord
@@ -193,4 +196,31 @@
}
return MdnsPacket(flags, questions, answers, emptyList(), emptyList())
}
+
+ @Test
+ fun testCheckAllPacketsWithSameAddress() {
+ val buffer = ByteArray(10)
+ val v4Packet = DatagramPacket(buffer, buffer.size, IPV4_SOCKET_ADDR)
+ val otherV4Packet = DatagramPacket(
+ buffer,
+ buffer.size,
+ InetAddresses.parseNumericAddress("192.0.2.1"),
+ 1234
+ )
+ val v6Packet = DatagramPacket(ByteArray(10), 10, IPV6_SOCKET_ADDR)
+ val otherV6Packet = DatagramPacket(
+ buffer,
+ buffer.size,
+ InetAddresses.parseNumericAddress("2001:db8::"),
+ 1234
+ )
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf()))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet)))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet, v4Packet)))
+ assertFalse(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet, otherV4Packet)))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v6Packet)))
+ assertTrue(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v6Packet, v6Packet)))
+ assertFalse(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v6Packet, otherV6Packet)))
+ assertFalse(MdnsUtils.checkAllPacketsWithSameAddress(listOf(v4Packet, v6Packet)))
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
index cf990b1..a7083dc 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSDeclaredMethodsForCallbacksTest.kt
@@ -17,8 +17,11 @@
package com.android.server.connectivityservice
import android.net.ConnectivityManager
+import android.net.ConnectivityManager.CALLBACK_AVAILABLE
+import android.net.ConnectivityManager.CALLBACK_BLK_CHANGED
import android.net.ConnectivityManager.CALLBACK_CAP_CHANGED
import android.net.ConnectivityManager.CALLBACK_IP_CHANGED
+import android.net.ConnectivityManager.CALLBACK_LOCAL_NETWORK_INFO_CHANGED
import android.net.ConnectivityManager.CALLBACK_LOST
import android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_ALL
import android.net.LinkAddress
@@ -35,7 +38,9 @@
import com.android.testutils.RecorderCallback.CallbackEntry
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.tryTest
+import java.lang.reflect.Modifier
import java.util.concurrent.atomic.AtomicInteger
+import kotlin.test.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -129,6 +134,32 @@
listenCb.expect<CallbackEntry.CapabilitiesChanged>()
listenCb.assertNoCallback(timeoutMs = 0L)
}
+
+ @Test
+ fun testDeclaredMethodsFlagsToString() {
+ assertEquals("NONE", ConnectivityService.declaredMethodsFlagsToString(0))
+ assertEquals("ALL", ConnectivityService.declaredMethodsFlagsToString(0.inv()))
+ assertEquals("AVAIL|NC|LP|BLK|LOCALINF", ConnectivityService.declaredMethodsFlagsToString(
+ (1 shl CALLBACK_AVAILABLE) or
+ (1 shl CALLBACK_CAP_CHANGED) or
+ (1 shl CALLBACK_IP_CHANGED) or
+ (1 shl CALLBACK_BLK_CHANGED) or
+ (1 shl CALLBACK_LOCAL_NETWORK_INFO_CHANGED)
+ ))
+
+ // EXPIRE_LEGACY_REQUEST (=8) is only used in ConnectivityManager and not included.
+ // CALLBACK_TRANSITIVE_CALLS_ONLY (=0) is not a callback so not included either.
+ assertEquals(
+ "PRECHK|AVAIL|LOSING|LOST|UNAVAIL|NC|LP|SUSP|RESUME|BLK|LOCALINF|0x7fffe101",
+ ConnectivityService.declaredMethodsFlagsToString(0x7fff_ffff)
+ )
+ // The toString method and the assertion above need to be updated if constants are added
+ val constants = ConnectivityManager::class.java.declaredFields.filter {
+ Modifier.isStatic(it.modifiers) && Modifier.isFinal(it.modifiers) &&
+ it.name.startsWith("CALLBACK_")
+ }
+ assertEquals(12, constants.size)
+ }
}
private fun AtomicInteger.withFlags(vararg flags: Int, action: () -> Unit) {
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index f4b80ac..bf3cedb 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -624,10 +624,12 @@
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- mHandler.post(() -> onUserRestrictionsChanged(isThreadUserRestricted()));
+ onUserRestrictionsChanged(isThreadUserRestricted());
}
},
- new IntentFilter(UserManager.ACTION_USER_RESTRICTIONS_CHANGED));
+ new IntentFilter(UserManager.ACTION_USER_RESTRICTIONS_CHANGED),
+ null /* broadcastPermission */,
+ mHandler);
}
private void onUserRestrictionsChanged(boolean newUserRestrictedState) {
@@ -679,10 +681,12 @@
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- mHandler.post(() -> onAirplaneModeChanged(isAirplaneModeOn()));
+ onAirplaneModeChanged(isAirplaneModeOn());
}
},
- new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+ new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED),
+ null /* broadcastPermission */,
+ mHandler);
}
private void onAirplaneModeChanged(boolean newAirplaneModeOn) {
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index 11c4819..22e7a98 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -45,7 +45,6 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
-
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index b67a9af..bf61c68 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -586,7 +586,9 @@
.when(mContext)
.registerReceiver(
any(BroadcastReceiver.class),
- argThat(actualIntentFilter -> actualIntentFilter.hasAction(action)));
+ argThat(actualIntentFilter -> actualIntentFilter.hasAction(action)),
+ any(),
+ any());
return receiverRef;
}