diff --git a/service-t/src/com/android/server/net/NetworkStatsFactory.java b/service-t/src/com/android/server/net/NetworkStatsFactory.java index 5f66f479cb..5ff708d6b6 100644 --- a/service-t/src/com/android/server/net/NetworkStatsFactory.java +++ b/service-t/src/com/android/server/net/NetworkStatsFactory.java @@ -21,7 +21,6 @@ import static android.net.NetworkStats.TAG_ALL; import static android.net.NetworkStats.UID_ALL; import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.Context; import android.net.NetworkStats; import android.net.UnderlyingNetworkInfo; @@ -31,6 +30,7 @@ import android.os.SystemClock; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.BpfNetMaps; +import com.android.server.connectivity.InterfaceTracker; import java.io.IOException; import java.net.ProtocolException; @@ -108,7 +108,7 @@ public class NetworkStatsFactory { /** Create a new {@link BpfNetMaps}. */ public BpfNetMaps createBpfNetMaps(@NonNull Context ctx) { - return new BpfNetMaps(ctx); + return new BpfNetMaps(ctx, new InterfaceTracker(ctx)); } } diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java index 36c0cf997d..25c0617455 100644 --- a/service/src/com/android/server/BpfNetMaps.java +++ b/service/src/com/android/server/BpfNetMaps.java @@ -49,6 +49,8 @@ import static com.android.server.connectivity.NetworkPermissions.TRAFFIC_PERMISS import static com.android.server.connectivity.NetworkPermissions.TRAFFIC_PERMISSION_UNINSTALLED; import static com.android.server.connectivity.NetworkPermissions.TRAFFIC_PERMISSION_UPDATE_DEVICE_STATS; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.StatsManager; import android.content.Context; import android.net.BpfNetMapsUtils; @@ -85,12 +87,14 @@ import com.android.net.module.util.bpf.CookieTagMapValue; import com.android.net.module.util.bpf.IngressDiscardKey; import com.android.net.module.util.bpf.IngressDiscardValue; import com.android.net.module.util.bpf.LocalNetAccessKey; +import com.android.server.connectivity.InterfaceTracker; import java.io.FileDescriptor; import java.io.IOException; import java.net.InetAddress; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.StringJoiner; @@ -142,6 +146,7 @@ public class BpfNetMaps { Pair.create(TRAFFIC_PERMISSION_INTERNET, "PERMISSION_INTERNET"), Pair.create(TRAFFIC_PERMISSION_UPDATE_DEVICE_STATS, "PERMISSION_UPDATE_DEVICE_STATS") ); + private final InterfaceTracker mInterfaceTracker; /** * Set configurationMap for test. @@ -423,23 +428,27 @@ public class BpfNetMaps { /** Constructor used after T that doesn't need to use netd anymore. */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) - public BpfNetMaps(final Context context) { - this(context, null); + public BpfNetMaps(final Context context, @NonNull final InterfaceTracker interfaceTracker) { + this(context, null, interfaceTracker); if (!SdkLevel.isAtLeastT()) throw new IllegalArgumentException("BpfNetMaps need to use netd before T"); } - public BpfNetMaps(final Context context, final INetd netd) { - this(context, netd, new Dependencies()); + public BpfNetMaps(final Context context, final INetd netd, @NonNull final InterfaceTracker + interfaceTracker) { + this(context, netd, new Dependencies(), interfaceTracker); } @VisibleForTesting - public BpfNetMaps(final Context context, final INetd netd, final Dependencies deps) { + public BpfNetMaps(final Context context, final INetd netd, final Dependencies deps, + @NonNull final InterfaceTracker interfaceTracker) { + Objects.requireNonNull(interfaceTracker); if (SdkLevel.isAtLeastT()) { ensureInitialized(context); } mNetd = netd; mDeps = deps; + mInterfaceTracker = interfaceTracker; } private void maybeThrow(final int err, final String msg) { @@ -902,7 +911,7 @@ public class BpfNetMaps { * @param isAllowed is the local network call allowed or blocked. */ @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT) - public void addLocalNetAccess(final int lpmBitlen, final String iface, + public void addLocalNetAccess(final int lpmBitlen, @Nullable final String iface, final InetAddress address, final int protocol, final int remotePort, final boolean isAllowed) { throwIfPre25Q2("addLocalNetAccess is not available on pre-B devices"); @@ -910,7 +919,7 @@ public class BpfNetMaps { if (iface == null) { ifIndex = 0; } else { - ifIndex = mDeps.getIfIndex(iface); + ifIndex = mInterfaceTracker.getInterfaceIndex(iface); } if (ifIndex == 0) { Log.e(TAG, "Failed to get if index, skip addLocalNetAccess for " + address @@ -937,14 +946,14 @@ public class BpfNetMaps { * @param remotePort src/dst port for ingress/egress */ @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT) - public void removeLocalNetAccess(final int lpmBitlen, final String iface, + public void removeLocalNetAccess(final int lpmBitlen, @Nullable final String iface, final InetAddress address, final int protocol, final int remotePort) { throwIfPre25Q2("removeLocalNetAccess is not available on pre-B devices"); final int ifIndex; if (iface == null) { ifIndex = 0; } else { - ifIndex = mDeps.getIfIndex(iface); + ifIndex = mInterfaceTracker.getInterfaceIndex(iface); } if (ifIndex == 0) { Log.e(TAG, "Failed to get if index, skip removeLocalNetAccess for " + address @@ -973,14 +982,14 @@ public class BpfNetMaps { * is not local network or if configuration is allowed like local dns servers. */ @RequiresApi(Build.VERSION_CODES.CUR_DEVELOPMENT) - public boolean getLocalNetAccess(final int lpmBitlen, final String iface, + public boolean getLocalNetAccess(final int lpmBitlen, @Nullable final String iface, final InetAddress address, final int protocol, final int remotePort) { throwIfPre25Q2("getLocalNetAccess is not available on pre-B devices"); final int ifIndex; if (iface == null) { ifIndex = 0; } else { - ifIndex = mDeps.getIfIndex(iface); + ifIndex = mInterfaceTracker.getInterfaceIndex(iface); } if (ifIndex == 0) { Log.e(TAG, "Failed to get if index, returning default from getLocalNetAccess for " diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index b2e49e7f50..3ce3f0227f 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -365,6 +365,7 @@ import com.android.server.connectivity.DnsManager; import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate; import com.android.server.connectivity.DscpPolicyTracker; import com.android.server.connectivity.FullScore; +import com.android.server.connectivity.InterfaceTracker; import com.android.server.connectivity.InvalidTagException; import com.android.server.connectivity.KeepaliveResourceUtil; import com.android.server.connectivity.KeepaliveTracker; @@ -577,6 +578,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private final NetworkStatsManager mStatsManager; private final NetworkPolicyManager mPolicyManager; private final BpfNetMaps mBpfNetMaps; + private final InterfaceTracker mInterfaceTracker; /** * TestNetworkService (lazily) created upon first usage. Locked to prevent creation of multiple @@ -1662,8 +1664,17 @@ public class ConnectivityService extends IConnectivityManager.Stub { * @param netd a netd binder * @return BpfNetMaps implementation. */ - public BpfNetMaps getBpfNetMaps(Context context, INetd netd) { - return new BpfNetMaps(context, netd); + public BpfNetMaps getBpfNetMaps(Context context, INetd netd, + InterfaceTracker interfaceTracker) { + return new BpfNetMaps(context, netd, interfaceTracker); + } + + /** + * Get the InterfaceTracker implementation to use in ConnectivityService. + * @return InterfaceTracker implementation. + */ + public InterfaceTracker getInterfaceTracker(Context context) { + return new InterfaceTracker(context); } /** @@ -1886,7 +1897,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { mWakeUpMask = mask; mNetd = netd; - mBpfNetMaps = mDeps.getBpfNetMaps(mContext, netd); + mInterfaceTracker = mDeps.getInterfaceTracker(mContext); + mBpfNetMaps = mDeps.getBpfNetMaps(mContext, netd, mInterfaceTracker); mHandlerThread = mDeps.makeHandlerThread("ConnectivityServiceThread"); mPermissionMonitorDeps = mPermDeps; mPermissionMonitor = @@ -9605,8 +9617,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { updateIngressToVpnAddressFiltering(newLp, oldLp, networkAgent); - updateLocalNetworkAddresses(newLp, oldLp); - updateMtu(newLp, oldLp); // TODO - figure out what to do for clat // for (LinkProperties lp : newLp.getStackedLinks()) { @@ -9769,16 +9779,23 @@ public class ConnectivityService extends IConnectivityManager.Stub { wakeupModifyInterface(iface, nai, true); mDeps.reportNetworkInterfaceForTransports(mContext, iface, nai.networkCapabilities.getTransportTypes()); + mInterfaceTracker.addInterface(iface); } catch (Exception e) { logw("Exception adding interface: " + e); } } } + + // The local network addresses needs to be updated before interfaces are removed because + // modifying bpf map local_net_access requires mapping interface name to index. + updateLocalNetworkAddresses(newLp, oldLp); + for (final String iface : interfaceDiff.removed) { try { if (DBG) log("Removing iface " + iface + " from network " + netId); wakeupModifyInterface(iface, nai, false); mRoutingCoordinatorService.removeInterfaceFromNetwork(netId, iface); + mInterfaceTracker.removeInterface(iface); } catch (Exception e) { loge("Exception removing interface: " + e); } diff --git a/service/src/com/android/server/connectivity/InterfaceTracker.java b/service/src/com/android/server/connectivity/InterfaceTracker.java new file mode 100644 index 0000000000..0b4abeb9cc --- /dev/null +++ b/service/src/com/android/server/connectivity/InterfaceTracker.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2025 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 com.android.server.connectivity; + +import android.annotation.Nullable; +import android.content.Context; +import android.system.Os; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.BpfNetMaps; + +import java.util.Map; + +/** + * InterfaceTracker is responsible for providing interface mapping and tracking. + * @hide + */ +public class InterfaceTracker { + static { + if (BpfNetMaps.isAtLeast25Q2()) { + System.loadLibrary("service-connectivity"); + } + } + private static final String TAG = "InterfaceTracker"; + private final Dependencies mDeps; + private final Map mInterfaceMap; + + public InterfaceTracker(final Context context) { + this(context, new Dependencies()); + } + + @VisibleForTesting + public InterfaceTracker(final Context context, final Dependencies deps) { + this.mInterfaceMap = new ArrayMap<>(); + this.mDeps = deps; + } + + /** + * To add interface to tracking + * @param interfaceName name of interface added. + */ + public void addInterface(@Nullable final String interfaceName) { + final int interfaceIndex; + if (interfaceName == null) { + interfaceIndex = 0; + } else { + interfaceIndex = mDeps.getIfIndex(interfaceName); + } + if (interfaceIndex == 0) { + Log.e(TAG, "Failed to get interface index for " + interfaceName); + return; + } + synchronized (mInterfaceMap) { + mInterfaceMap.put(interfaceName, interfaceIndex); + } + } + + /** + * To remove interface from tracking + * @param interfaceName name of interface removed. + * @return true if the value was present and now removed. + */ + public boolean removeInterface(@Nullable final String interfaceName) { + if (interfaceName == null) return false; + synchronized (mInterfaceMap) { + return mInterfaceMap.remove(interfaceName) != null; + } + } + + /** + * Get interface index from interface name. + * @param interfaceName name of interface + * @return interface index for given interface name or 0 if interface is not found. + */ + public int getInterfaceIndex(@Nullable final String interfaceName) { + final int interfaceIndex; + if (interfaceName != null) { + synchronized (mInterfaceMap) { + interfaceIndex = mInterfaceMap.getOrDefault(interfaceName, 0); + } + } else { + interfaceIndex = 0; + } + return interfaceIndex; + } + + /** + * Dependencies of InterfaceTracker, for injection in tests. + */ + @VisibleForTesting + public static class Dependencies { + /** + * Get interface index. + */ + public int getIfIndex(final String ifName) { + return Os.if_nametoindex(ifName); + } + + /** + * Get interface name + */ + public String getIfName(final int ifIndex) { + return Os.if_indextoname(ifIndex); + } + + } +} diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt index 437eb819ab..de392156ee 100644 --- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt +++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt @@ -55,11 +55,11 @@ import com.android.net.module.util.BpfUtils import com.android.networkstack.apishim.TelephonyManagerShimImpl import com.android.server.BpfNetMaps import com.android.server.ConnectivityService -import com.android.server.L2capNetworkProvider import com.android.server.NetworkAgentWrapper import com.android.server.TestNetIdManager import com.android.server.connectivity.CarrierPrivilegeAuthenticator import com.android.server.connectivity.ConnectivityResources +import com.android.server.connectivity.InterfaceTracker import com.android.server.connectivity.MockableSystemProperties import com.android.server.connectivity.MultinetworkPolicyTracker import com.android.server.connectivity.PermissionMonitor @@ -114,6 +114,8 @@ class ConnectivityServiceIntegrationTest { @Mock private lateinit var netd: INetd @Mock + private lateinit var interfaceTracker: InterfaceTracker + @Mock private lateinit var dnsResolver: IDnsResolver @Mock private lateinit var systemConfigManager: SystemConfigManager @@ -140,11 +142,15 @@ class ConnectivityServiceIntegrationTest { private val realContext get() = InstrumentationRegistry.getInstrumentation().context private val httpProbeUrl get() = - realContext.getResources().getString(com.android.server.net.integrationtests.R.string - .config_captive_portal_http_url) + realContext.getResources().getString( + com.android.server.net.integrationtests.R.string + .config_captive_portal_http_url + ) private val httpsProbeUrl get() = - realContext.getResources().getString(com.android.server.net.integrationtests.R.string - .config_captive_portal_https_url) + realContext.getResources().getString( + com.android.server.net.integrationtests.R.string + .config_captive_portal_https_url + ) private class InstrumentationServiceConnection : ServiceConnection { override fun onServiceConnected(name: ComponentName?, service: IBinder?) { @@ -166,12 +172,19 @@ class ConnectivityServiceIntegrationTest { fun setUpClass() { val intent = Intent(realContext, NetworkStackInstrumentationService::class.java) intent.action = INetworkStackInstrumentation::class.qualifiedName - assertTrue(realContext.bindService(intent, InstrumentationServiceConnection(), - BIND_AUTO_CREATE or BIND_IMPORTANT), - "Error binding to instrumentation service") - assertTrue(bindingCondition.block(SERVICE_BIND_TIMEOUT_MS), + assertTrue( + realContext.bindService( + intent, + InstrumentationServiceConnection(), + BIND_AUTO_CREATE or BIND_IMPORTANT + ), + "Error binding to instrumentation service" + ) + assertTrue( + bindingCondition.block(SERVICE_BIND_TIMEOUT_MS), "Timed out binding to instrumentation service " + - "after $SERVICE_BIND_TIMEOUT_MS ms") + "after $SERVICE_BIND_TIMEOUT_MS ms" + ) } } @@ -201,7 +214,8 @@ class ConnectivityServiceIntegrationTest { // We don't test the actual notification value strings, so just return an empty array. // It doesn't matter what the values are as long as it's not null. doReturn(emptyArray()).`when`(resources).getStringArray( - R.array.network_switch_type_name) + R.array.network_switch_type_name + ) doReturn(1).`when`(resources).getInteger(R.integer.config_networkAvoidBadWifi) doReturn(R.array.config_networkSupportedKeepaliveCount).`when`(resources) .getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any()) @@ -223,7 +237,13 @@ class ConnectivityServiceIntegrationTest { } private inner class TestConnectivityService(deps: Dependencies) : ConnectivityService( - context, dnsResolver, log, netd, deps, PermissionMonitorDependencies()) + context, + dnsResolver, + log, + netd, + deps, + PermissionMonitorDependencies() + ) private inner class TestDependencies : ConnectivityService.Dependencies() { override fun getNetworkStack() = networkStackClient @@ -231,7 +251,11 @@ class ConnectivityServiceIntegrationTest { mock(ProxyTracker::class.java) override fun getSystemProperties() = mock(MockableSystemProperties::class.java) override fun makeNetIdManager() = TestNetIdManager() - override fun getBpfNetMaps(context: Context?, netd: INetd?) = mock(BpfNetMaps::class.java) + override fun getBpfNetMaps( + context: Context?, + netd: INetd?, + interfaceTracker: InterfaceTracker? + ) = mock(BpfNetMaps::class.java) .also { doReturn(PERMISSION_INTERNET).`when`(it).getNetPermForUid(anyInt()) } @@ -241,13 +265,17 @@ class ConnectivityServiceIntegrationTest { c: Context, h: Handler, r: Runnable - ) = MultinetworkPolicyTracker(c, h, r, + ) = MultinetworkPolicyTracker( + c, + h, + r, object : MultinetworkPolicyTracker.Dependencies() { override fun getResourcesForActiveSubId( connResources: ConnectivityResources, activeSubId: Int ) = resources - }) + } + ) override fun makeHandlerThread(tag: String): HandlerThread = super.makeHandlerThread(tag).also { handlerThreads.add(it) } @@ -259,13 +287,18 @@ class ConnectivityServiceIntegrationTest { listener: BiConsumer, handler: Handler ): CarrierPrivilegeAuthenticator { - return CarrierPrivilegeAuthenticator(context, + return CarrierPrivilegeAuthenticator( + context, object : CarrierPrivilegeAuthenticator.Dependencies() { override fun makeHandlerThread(): HandlerThread = super.makeHandlerThread().also { handlerThreads.add(it) } }, - tm, TelephonyManagerShimImpl.newInstance(tm), - requestRestrictedWifiEnabled, listener, handler) + tm, + TelephonyManagerShimImpl.newInstance(tm), + requestRestrictedWifiEnabled, + listener, + handler + ) } override fun makeSatelliteAccessController( @@ -273,7 +306,8 @@ class ConnectivityServiceIntegrationTest { updateSatellitePreferredUid: Consumer>?, connectivityServiceInternalHandler: Handler ): SatelliteAccessController? = mock( - SatelliteAccessController::class.java) + SatelliteAccessController::class.java + ) override fun makeL2capNetworkProvider(context: Context) = null } @@ -305,8 +339,12 @@ class ConnectivityServiceIntegrationTest { nsInstrumentation.addHttpResponse(HttpResponse(httpProbeUrl, responseCode = 204)) nsInstrumentation.addHttpResponse(HttpResponse(httpsProbeUrl, responseCode = 204)) - val na = NetworkAgentWrapper(TRANSPORT_CELLULAR, LinkProperties(), null /* ncTemplate */, - context) + val na = NetworkAgentWrapper( + TRANSPORT_CELLULAR, + LinkProperties(), + null /* ncTemplate */, + context + ) networkStackClient.verifyNetworkMonitorCreated(na.network, TEST_TIMEOUT_MS) na.addCapability(NET_CAPABILITY_INTERNET) @@ -344,7 +382,8 @@ class ConnectivityServiceIntegrationTest { | "user-portal-url": "https://login.capport.android.com", | "venue-info-url": "https://venueinfo.capport.android.com" |} - """.trimMargin())) + """.trimMargin() + )) // Tests will fail if a non-mocked query is received: mock the HTTPS probe, but not the // HTTP probe as it should not be sent. @@ -398,8 +437,10 @@ class ConnectivityServiceIntegrationTest { BpfUtils.BPF_CGROUP_INET_EGRESS, BpfUtils.BPF_CGROUP_INET_SOCK_CREATE ).forEach { - val ret = SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), - "cmd connectivity bpf-get-cgroup-program-id $it").trim() + val ret = SystemUtil.runShellCommand( + InstrumentationRegistry.getInstrumentation(), + "cmd connectivity bpf-get-cgroup-program-id $it" + ).trim() assertTrue(Integer.parseInt(ret) > 0, "Unexpected output $ret for type $it") } diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java index caf1765d56..1d2e8b0c99 100644 --- a/tests/unit/java/com/android/server/BpfNetMapsTest.java +++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java @@ -109,6 +109,7 @@ import com.android.net.module.util.bpf.CookieTagMapValue; import com.android.net.module.util.bpf.IngressDiscardKey; import com.android.net.module.util.bpf.IngressDiscardValue; import com.android.net.module.util.bpf.LocalNetAccessKey; +import com.android.server.connectivity.InterfaceTracker; import com.android.testutils.DevSdkIgnoreRule; import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; @@ -170,6 +171,8 @@ public final class BpfNetMapsTest { @Mock INetd mNetd; @Mock BpfNetMaps.Dependencies mDeps; @Mock Context mContext; + + @Mock InterfaceTracker mInterfaceTracker; private final IBpfMap mConfigurationMap = new TestBpfMap<>(S32.class, U32.class); private final IBpfMap mUidOwnerMap = new TestBpfMap<>(S32.class, UidOwnerValue.class); @@ -188,6 +191,7 @@ public final class BpfNetMapsTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME); + doReturn(TEST_IF_INDEX).when(mInterfaceTracker).getInterfaceIndex(TEST_IF_NAME); doReturn(TEST_IF_NAME).when(mDeps).getIfName(TEST_IF_INDEX); doReturn(0).when(mDeps).synchronizeKernelRCU(); BpfNetMaps.setConfigurationMapForTest(mConfigurationMap); @@ -202,7 +206,7 @@ public final class BpfNetMapsTest { BpfNetMaps.setDataSaverEnabledMapForTest(mDataSaverEnabledMap); mDataSaverEnabledMap.updateEntry(DATA_SAVER_ENABLED_KEY, new U8(DATA_SAVER_DISABLED)); BpfNetMaps.setIngressDiscardMapForTest(mIngressDiscardMap); - mBpfNetMaps = new BpfNetMaps(mContext, mNetd, mDeps); + mBpfNetMaps = new BpfNetMaps(mContext, mNetd, mDeps, mInterfaceTracker); } @Test diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index c4944b6de0..c28a0f8953 100755 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -413,6 +413,7 @@ import com.android.server.connectivity.CarrierPrivilegeAuthenticator; import com.android.server.connectivity.ClatCoordinator; import com.android.server.connectivity.ConnectivityFlags; import com.android.server.connectivity.ConnectivityResources; +import com.android.server.connectivity.InterfaceTracker; import com.android.server.connectivity.KeepaliveTracker; import com.android.server.connectivity.MultinetworkPolicyTracker; import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies; @@ -2262,7 +2263,8 @@ public class ConnectivityServiceTest { } @Override - public BpfNetMaps getBpfNetMaps(Context context, INetd netd) { + public BpfNetMaps getBpfNetMaps(Context context, INetd netd, + InterfaceTracker interfaceTracker) { return mBpfNetMaps; } diff --git a/tests/unit/java/com/android/server/connectivity/InterfaceTrackerTest.java b/tests/unit/java/com/android/server/connectivity/InterfaceTrackerTest.java new file mode 100644 index 0000000000..8a9ada0c2c --- /dev/null +++ b/tests/unit/java/com/android/server/connectivity/InterfaceTrackerTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2025 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 com.android.server.connectivity; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.doReturn; + +import android.content.Context; +import android.os.Build; + +import androidx.test.filters.SmallTest; + +import com.android.testutils.ConnectivityModuleTest; +import com.android.testutils.DevSdkIgnoreRule; +import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; +import com.android.testutils.DevSdkIgnoreRunner; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(DevSdkIgnoreRunner.class) +@SmallTest +@ConnectivityModuleTest +public class InterfaceTrackerTest { + private static final String TAG = "InterfaceTrackerTest"; + private static final String TEST_IF_NAME = "wlan10"; + private static final String TEST_INCORRECT_IF_NAME = "wlan20"; + private static final int TEST_IF_INDEX = 7; + + @Rule + public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(); + + private InterfaceTracker mInterfaceTracker; + + @Mock Context mContext; + @Mock InterfaceTracker.Dependencies mDeps; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + doReturn(TEST_IF_INDEX).when(mDeps).getIfIndex(TEST_IF_NAME); + mInterfaceTracker = new InterfaceTracker(mContext, mDeps); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM) + public void testAddingInterface_InterfaceNameIndexMappingAdded() { + mInterfaceTracker.addInterface(TEST_IF_NAME); + assertEquals(TEST_IF_INDEX, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME)); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM) + public void testAddingNullInterface_InterfaceNameIndexMappingNotAdded() { + mInterfaceTracker.addInterface(null); + assertEquals(0, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME)); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM) + public void testAddingIncorrectInterface_InterfaceNameIndexMappingNotAdded() { + mInterfaceTracker.addInterface(TEST_INCORRECT_IF_NAME); + + assertEquals(0, mInterfaceTracker.getInterfaceIndex(TEST_INCORRECT_IF_NAME)); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM) + public void testRemovingInterface_InterfaceNameIndexMappingRemoved() { + mInterfaceTracker.addInterface(TEST_IF_NAME); + assertEquals(TEST_IF_INDEX, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME)); + mInterfaceTracker.removeInterface(TEST_IF_NAME); + assertEquals(0, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME)); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM) + public void testRemovingNullInterface_InterfaceNameIndexMappingNotRemoved() { + mInterfaceTracker.addInterface(TEST_IF_NAME); + mInterfaceTracker.removeInterface(null); + assertEquals(TEST_IF_INDEX, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME)); + } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.VANILLA_ICE_CREAM) + public void testRemovingIncorrectInterface_InterfaceNameIndexMappingNotRemoved() { + mInterfaceTracker.addInterface(TEST_IF_NAME); + mInterfaceTracker.removeInterface(TEST_INCORRECT_IF_NAME); + assertEquals(TEST_IF_INDEX, mInterfaceTracker.getInterfaceIndex(TEST_IF_NAME)); + } + +} diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt index 557bfd6612..d7e781e6b1 100644 --- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt +++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt @@ -68,6 +68,7 @@ import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker import com.android.server.connectivity.CarrierPrivilegeAuthenticator import com.android.server.connectivity.ClatCoordinator import com.android.server.connectivity.ConnectivityFlags +import com.android.server.connectivity.InterfaceTracker import com.android.server.connectivity.MulticastRoutingCoordinatorService import com.android.server.connectivity.MultinetworkPolicyTracker import com.android.server.connectivity.MultinetworkPolicyTrackerTestDependencies @@ -193,6 +194,7 @@ open class CSTest { val connResources = makeMockConnResources(sysResources, packageManager) val netd = mock() + val interfaceTracker = mock() val bpfNetMaps = mock().also { doReturn(PERMISSION_INTERNET).`when`(it).getNetPermForUid(anyInt()) } @@ -279,7 +281,11 @@ open class CSTest { inner class CSDeps : ConnectivityService.Dependencies() { override fun getResources(ctx: Context) = connResources - override fun getBpfNetMaps(context: Context, netd: INetd) = this@CSTest.bpfNetMaps + override fun getBpfNetMaps( + context: Context, + netd: INetd, + interfaceTracker: InterfaceTracker + ) = this@CSTest.bpfNetMaps override fun getClatCoordinator(netd: INetd?) = this@CSTest.clatCoordinator override fun getNetworkStack() = this@CSTest.networkStack