From 6822c1929477c25c296228cc28b4c6e795569873 Mon Sep 17 00:00:00 2001 From: Patrick Rohr Date: Mon, 21 Jul 2025 12:16:26 -0700 Subject: [PATCH 1/3] Add support for TestInterfaceMode in setIncludeTestInterfaces This will allow EthernetManagerTest to include test interfaces as NCM-only interfaces. Note that at this point, setting any TEST_INTERFACE_MODE includes a test interface in both MODE_ETHERNET and MODE_NCM (equivalent to setIncludeTestInterface(true)) as the mode is not propagated further than EthernetServiceImpl. Test: TH Flag: EXEMPT mainline Change-Id: I52a36ebb29bc27a21c7521c1cd9bd186001ac6c5 --- .../src/android/net/EthernetManager.java | 48 +++++++++++++++++-- .../src/android/net/IEthernetManager.aidl | 2 +- .../server/ethernet/EthernetServiceImpl.java | 5 +- .../ethernetlegacy/EthernetServiceImpl.java | 5 +- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/framework-t/src/android/net/EthernetManager.java b/framework-t/src/android/net/EthernetManager.java index 2f0612bd1b..5d54794218 100644 --- a/framework-t/src/android/net/EthernetManager.java +++ b/framework-t/src/android/net/EthernetManager.java @@ -395,6 +395,45 @@ public class EthernetManager { removeInterfaceStateListener(listener); } + /** + * Do not include test interfaces. + * @hide + */ + public static final int TEST_INTERFACE_MODE_NONE = 0; + + /** + * Includes test interfaces as if they were present in the regex. + * @hide + */ + public static final int TEST_INTERFACE_MODE_ETHERNET = 1 << 0; + + /** + * Includes test interfaces as if they were NCM interfaces. + * @hide + */ + public static final int TEST_INTERFACE_MODE_NCM = 1 << 1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "TEST_INTERFACE_MODE_" }, value = { + TEST_INTERFACE_MODE_NONE, + TEST_INTERFACE_MODE_ETHERNET, + TEST_INTERFACE_MODE_NCM, + }) + public @interface TestInterfaceMode {} + + /** + * Version of {@code setIncludeTestInterfaces} that accepts mode flags. + * @hide + */ + public void setIncludeTestInterfaces(@TestInterfaceMode int mode) { + try { + mService.setIncludeTestInterfaces(mode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Whether to treat interfaces created by {@link TestNetworkManager#createTapInterface} * as Ethernet interfaces. The effects of this method apply to any test interfaces that are @@ -403,11 +442,10 @@ public class EthernetManager { */ @SystemApi(client = MODULE_LIBRARIES) public void setIncludeTestInterfaces(boolean include) { - try { - mService.setIncludeTestInterfaces(include); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + final int mode = include + ? TEST_INTERFACE_MODE_ETHERNET | TEST_INTERFACE_MODE_NCM + : TEST_INTERFACE_MODE_NONE; + setIncludeTestInterfaces(mode); } /** diff --git a/framework-t/src/android/net/IEthernetManager.aidl b/framework-t/src/android/net/IEthernetManager.aidl index ea45a9de7c..b9a3b380b8 100644 --- a/framework-t/src/android/net/IEthernetManager.aidl +++ b/framework-t/src/android/net/IEthernetManager.aidl @@ -38,7 +38,7 @@ interface IEthernetManager boolean isAvailable(String iface); void addListener(in IEthernetServiceListener listener, int flags); void removeListener(in IEthernetServiceListener listener); - void setIncludeTestInterfaces(boolean include); + void setIncludeTestInterfaces(int mode); void requestTetheredInterface(in ITetheredInterfaceCallback callback); void releaseTetheredInterface(in ITetheredInterfaceCallback callback); void updateConfiguration(String iface, in EthernetNetworkUpdateRequest request, diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java index fb2a597fd9..4922e40d08 100644 --- a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java +++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java @@ -16,6 +16,7 @@ package com.android.server.ethernet; +import static android.net.EthernetManager.TEST_INTERFACE_MODE_NONE; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_TEST; @@ -25,6 +26,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManager; import android.net.EthernetManager; +import android.net.EthernetManager.TestInterfaceMode; import android.net.EthernetNetworkSpecifier; import android.net.EthernetNetworkUpdateRequest; import android.net.IEthernetServiceListener; @@ -208,7 +210,8 @@ public class EthernetServiceImpl extends BaseEthernetServiceImpl { } @Override - public void setIncludeTestInterfaces(boolean include) { + public void setIncludeTestInterfaces(@TestInterfaceMode int mode) { + final boolean include = (mode != TEST_INTERFACE_MODE_NONE); PermissionUtils.enforceNetworkStackPermissionOr(mContext, android.Manifest.permission.NETWORK_SETTINGS); diff --git a/service-t/src/com/android/server/ethernetlegacy/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernetlegacy/EthernetServiceImpl.java index 8db563e0dd..00836e9248 100644 --- a/service-t/src/com/android/server/ethernetlegacy/EthernetServiceImpl.java +++ b/service-t/src/com/android/server/ethernetlegacy/EthernetServiceImpl.java @@ -16,6 +16,7 @@ package com.android.server.ethernetlegacy; +import static android.net.EthernetManager.TEST_INTERFACE_MODE_NONE; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_TEST; @@ -24,6 +25,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManager; +import android.net.EthernetManager.TestInterfaceMode; import android.net.EthernetNetworkSpecifier; import android.net.EthernetNetworkUpdateRequest; import android.net.IEthernetServiceListener; @@ -165,7 +167,8 @@ public class EthernetServiceImpl extends BaseEthernetServiceImpl { } @Override - public void setIncludeTestInterfaces(boolean include) { + public void setIncludeTestInterfaces(@TestInterfaceMode int mode) { + final boolean include = (mode != TEST_INTERFACE_MODE_NONE); PermissionUtils.enforceNetworkStackPermissionOr(mContext, android.Manifest.permission.NETWORK_SETTINGS); mTracker.setIncludeTestInterfaces(include); From e9d1d080db3bbc6f862ca31cee4083fb5b7bf9d8 Mon Sep 17 00:00:00 2001 From: Patrick Rohr Date: Wed, 23 Jul 2025 10:55:07 -0700 Subject: [PATCH 2/3] Move call to setEthernetEnabled inside setIncludeTestInterfaces setIncludeTestInterfaces() pretty much always requires a call to setEthernetEnabled(currentState) afterwards as a synchronization mechanism. Add this call directly inside the helper function for simplicity. Test: TH Flag: EXEMPT mainline Change-Id: I4c787e9c560ffe60c981ac2e2a730e96138707cb --- .../cts/net/src/android/net/cts/EthernetManagerTest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt index d1b9f8a89c..06e34cb091 100644 --- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt +++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt @@ -467,6 +467,9 @@ class EthernetManagerTest { runAsShell(NETWORK_SETTINGS) { em.setIncludeTestInterfaces(value) } + // Wait for the call to be processed by calling setEthernetEnabled() which waits on a + // completion callback. + setEthernetEnabled(ethernetEnabled) } private fun removeInterface(iface: EthernetTestInterface) { @@ -710,18 +713,15 @@ class EthernetManagerTest { val iface2 = createInterface() val iface3 = createInterface() - // Prevent test interfaces from being tracked and wait for the call to be processed by - // calling setEthernetEnabled() which waits on a completion callback. + // Prevent test interfaces from being tracked. setIncludeTestInterfaces(false) - setEthernetEnabled(true) val listener = EthernetStateListener() addInterfaceStateListener(listener) listener.assertNoCallback() - // Include test interfaces again, and ensure that they are tracked properly. + // Include test interfaces again. setIncludeTestInterfaces(true) - setEthernetEnabled(true) listener.expectCallback(iface1, STATE_LINK_UP, ROLE_CLIENT) listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT) From 3b8377fbc76925630598ebbdc3be4346ba25b2da Mon Sep 17 00:00:00 2001 From: Patrick Rohr Date: Wed, 23 Jul 2025 10:57:09 -0700 Subject: [PATCH 3/3] Add support for TestInterfaceMode to EthernetTracker This change passes TestInterfaceMode into EthernetTracker. It also introduces a small change to isValidTestInterface (so it consistently returns true / false independent of whether test interfaces are currently included). This seems more consistent. Also note that a device is already effectively prevented from configuring "testtap" interfaces by itself as otherwise EthernetManagerTest (and various other tests relying on tap interfaces) would break. Test: atest EthernetManagerTest Flag: EXEMPT mainline Change-Id: I12e4d492d7c00d049ca44663c361c12c4262c7bc --- .../server/ethernet/EthernetServiceImpl.java | 4 +- .../server/ethernet/EthernetTracker.java | 42 ++++++++++------- .../android/net/cts/EthernetManagerTest.kt | 47 +++++++++++++++++-- 3 files changed, 71 insertions(+), 22 deletions(-) diff --git a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java index 4922e40d08..01d86603c4 100644 --- a/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java +++ b/service-t/src/com/android/server/ethernet/EthernetServiceImpl.java @@ -16,7 +16,6 @@ package com.android.server.ethernet; -import static android.net.EthernetManager.TEST_INTERFACE_MODE_NONE; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_TEST; @@ -211,13 +210,12 @@ public class EthernetServiceImpl extends BaseEthernetServiceImpl { @Override public void setIncludeTestInterfaces(@TestInterfaceMode int mode) { - final boolean include = (mode != TEST_INTERFACE_MODE_NONE); PermissionUtils.enforceNetworkStackPermissionOr(mContext, android.Manifest.permission.NETWORK_SETTINGS); final long ident = Binder.clearCallingIdentity(); try { - mTracker.setIncludeTestInterfaces(include); + mTracker.setIncludeTestInterfaces(mode); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java index 38248bf429..788769292c 100644 --- a/service-t/src/com/android/server/ethernet/EthernetTracker.java +++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java @@ -23,6 +23,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_LOWPAN; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; import static android.net.TestNetworkManager.TEST_TAP_PREFIX; +import static android.net.EthernetManager.TEST_INTERFACE_MODE_NONE; import static android.system.OsConstants.ENOTSUP; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; @@ -33,6 +34,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.net.EthernetManager; +import android.net.EthernetManager.TestInterfaceMode; import android.net.INetd; import android.net.ITetheredInterfaceCallback; import android.net.InterfaceConfigurationParcel; @@ -149,7 +151,7 @@ public class EthernetTracker { * Track test interfaces if true, don't track otherwise. * Volatile is needed as getEthernetInterfaceList() does not run on the handler thread. */ - private volatile boolean mIncludeTestInterfaces = false; + private volatile int mTestInterfaceMode = TEST_INTERFACE_MODE_NONE; /** Mapping between {iface name | mac address} -> {NetworkCapabilities} */ private final ConcurrentHashMap mNetworkCapabilities = @@ -561,20 +563,26 @@ public class EthernetTracker { mHandler.post(() -> mListeners.unregister(listener)); } - public void setIncludeTestInterfaces(boolean include) { + /** Include test interfaces as defined by the mode. */ + public void setIncludeTestInterfaces(@TestInterfaceMode int mode) { mHandler.post(() -> { - mIncludeTestInterfaces = include; - if (include) { - mNetlinkMonitor.requestLinkDump(); - } else { - removeTestData(); - // remove all test interfaces - for (EthernetPort port : getAllEthernetPorts()) { - final String iface = port.getInterfaceName(); - if (inferTrackingReason(iface).contains(TrackingReason.REGEX)) continue; + mTestInterfaceMode = mode; + + // A mode change requires re-evaluating all test interfaces. The simplest way is to + // remove them all and let the netlink dump re-discover the ones that should be tracked + // under the new mode. + for (EthernetPort port : getAllEthernetPorts()) { + if (isValidTestInterface(port.getInterfaceName())) { stopTrackingInterface(port); } } + + if (mode != TEST_INTERFACE_MODE_NONE) { + mNetlinkMonitor.requestLinkDump(); + } else { + // If mode is NONE, no need for a link dump, just clean up persisted data. + removeTestData(); + } }); } @@ -880,11 +888,13 @@ public class EthernetTracker { reasons.add(TrackingReason.REGEX); } - // TODO: find a way to conditionally deduce REGEX and NCM TrackingReasons for test - // interfaces. if (isValidTestInterface(iface)) { - reasons.add(TrackingReason.REGEX); - reasons.add(TrackingReason.NCM); + if ((mTestInterfaceMode & EthernetManager.TEST_INTERFACE_MODE_ETHERNET) != 0) { + reasons.add(TrackingReason.REGEX); + } + if ((mTestInterfaceMode & EthernetManager.TEST_INTERFACE_MODE_NCM) != 0) { + reasons.add(TrackingReason.NCM); + } } // TODO: remove this flag after M-2025-09 release. @@ -920,7 +930,7 @@ public class EthernetTracker { * interface prefix, {@code false} otherwise. */ public boolean isValidTestInterface(@NonNull final String iface) { - return mIncludeTestInterfaces && TEST_IFACE_REGEXP.matcher(iface).matches(); + return TEST_IFACE_REGEXP.matcher(iface).matches(); } private void postAndWaitForRunnable(Runnable r) { diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt index 06e34cb091..e0f979a38d 100644 --- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt +++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt @@ -24,12 +24,16 @@ import android.net.EthernetManager import android.net.EthernetManager.ETHERNET_STATE_DISABLED import android.net.EthernetManager.ETHERNET_STATE_ENABLED import android.net.EthernetManager.InterfaceStateListener +import android.net.EthernetManager.LISTENER_FLAG_DEFAULT +import android.net.EthernetManager.LISTENER_FLAG_INCLUDE_NCM import android.net.EthernetManager.ROLE_CLIENT import android.net.EthernetManager.ROLE_NONE import android.net.EthernetManager.ROLE_SERVER import android.net.EthernetManager.STATE_ABSENT import android.net.EthernetManager.STATE_LINK_DOWN import android.net.EthernetManager.STATE_LINK_UP +import android.net.EthernetManager.TEST_INTERFACE_MODE_ETHERNET +import android.net.EthernetManager.TEST_INTERFACE_MODE_NCM import android.net.EthernetManager.TetheredInterfaceCallback import android.net.EthernetManager.TetheredInterfaceRequest import android.net.EthernetNetworkManagementException @@ -377,7 +381,7 @@ class EthernetManagerTest { fun setUp() { assumeTrue(isEthernetSupported()) setIncludeTestInterfaces(true) - addInterfaceStateListener(ifaceListener) + addInterfaceStateListener(ifaceListener, LISTENER_FLAG_INCLUDE_NCM) // Handler.post() events may get processed after native fd events, so it is possible that // RTM_NEWLINK (from a subsequent createInterface() call) arrives before the interface state // listener is registered. This affects the callbacks and breaks the tests. @@ -436,8 +440,11 @@ class EthernetManagerTest { SystemProperties.getInt("service.adb.tcp.port", -1) > -1) } - private fun addInterfaceStateListener(listener: EthernetStateListener) { - em.addInterfaceStateListener(handler::post, listener) + private fun addInterfaceStateListener( + listener: EthernetStateListener, + flags: Int = LISTENER_FLAG_DEFAULT, + ) { + em.addInterfaceStateListener(handler::post, listener, flags) addedListeners.add(listener) } @@ -472,6 +479,15 @@ class EthernetManagerTest { setEthernetEnabled(ethernetEnabled) } + private fun setIncludeTestInterfaces(mode: Int) { + runAsShell(NETWORK_SETTINGS) { + em.setIncludeTestInterfaces(mode) + } + // Wait for the call to be processed by calling setEthernetEnabled() which waits on a + // completion callback. + setEthernetEnabled(ethernetEnabled) + } + private fun removeInterface(iface: EthernetTestInterface) { iface.destroy() createdIfaces.remove(iface) @@ -1217,4 +1233,29 @@ class EthernetManagerTest { releaseRequest(cb2) cb.expect() } + + @Test + fun testNcmOnlyNetwork() { + setIncludeTestInterfaces(TEST_INTERFACE_MODE_NCM) + val iface = createInterface() + + val eth = requestNetwork(ETH_REQUEST) + eth.assertNoCallback() + + val ncm = runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) { requestNetwork(LOCAL_REQUEST) } + ncm.expect() + } + + @Test + fun testNonNcmNetwork() { + setIncludeTestInterfaces(TEST_INTERFACE_MODE_ETHERNET) + val iface = createInterface() + + val eth = requestNetwork(ETH_REQUEST) + eth.expect() + + val ncm = runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) { requestNetwork(LOCAL_REQUEST) } + ncm.assertNoCallback() + eth.assertNeverLost() + } }