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
This commit is contained in:
Patrick Rohr 2025-07-23 10:57:09 -07:00
commit 3b8377fbc7
3 changed files with 70 additions and 21 deletions

View file

@ -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);
}

View file

@ -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<String, NetworkCapabilities> 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) {

View file

@ -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<Available>()
}
@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<Available>()
}
@Test
fun testNonNcmNetwork() {
setIncludeTestInterfaces(TEST_INTERFACE_MODE_ETHERNET)
val iface = createInterface()
val eth = requestNetwork(ETH_REQUEST)
eth.expect<Available>()
val ncm = runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS) { requestNetwork(LOCAL_REQUEST) }
ncm.assertNoCallback()
eth.assertNeverLost()
}
}