Merge "[Thread] add CtsThreadNetworkTestCases to CTS" into main
diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
index 2933a44..1022b06 100644
--- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java
@@ -83,8 +83,10 @@
import java.net.NetworkInterface;
import java.net.SocketException;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -146,6 +148,8 @@
private static final TetheringManager sTm = sContext.getSystemService(TetheringManager.class);
private static final PackageManager sPackageManager = sContext.getPackageManager();
private static final CtsNetUtils sCtsNetUtils = new CtsNetUtils(sContext);
+ private static final List<String> sCallbackErrors =
+ Collections.synchronizedList(new ArrayList<>());
// Late initialization in setUp()
private boolean mRunTests;
@@ -201,6 +205,7 @@
assumeTrue(mRunTests);
mTetheredInterfaceRequester = new TetheredInterfaceRequester();
+ sCallbackErrors.clear();
}
private boolean isEthernetTetheringSupported() throws Exception {
@@ -280,6 +285,10 @@
mHandlerThread.quitSafely();
mHandlerThread.join();
}
+
+ if (sCallbackErrors.size() > 0) {
+ fail("Some callbacks had errors: " + sCallbackErrors);
+ }
}
protected static boolean isInterfaceForTetheringAvailable() throws Exception {
@@ -391,7 +400,7 @@
}
@Override
public void onTetheredInterfacesChanged(List<String> interfaces) {
- fail("Should only call callback that takes a Set<TetheringInterface>");
+ addCallbackError("Should only call callback that takes a Set<TetheringInterface>");
}
@Override
@@ -412,7 +421,7 @@
@Override
public void onLocalOnlyInterfacesChanged(List<String> interfaces) {
- fail("Should only call callback that takes a Set<TetheringInterface>");
+ addCallbackError("Should only call callback that takes a Set<TetheringInterface>");
}
@Override
@@ -481,7 +490,7 @@
// Ignore stale callbacks registered by previous test cases.
if (mUnregistered) return;
- fail("TetheringEventCallback got error:" + error + " on iface " + ifName);
+ addCallbackError("TetheringEventCallback got error:" + error + " on iface " + ifName);
}
@Override
@@ -536,6 +545,11 @@
}
}
+ private static void addCallbackError(String error) {
+ Log.e(TAG, error);
+ sCallbackErrors.add(error);
+ }
+
protected static MyTetheringEventCallback enableEthernetTethering(String iface,
TetheringRequest request, Network expectedUpstream) throws Exception {
// Enable ethernet tethering with null expectedUpstream means the test accept any upstream
@@ -562,7 +576,7 @@
@Override
public void onTetheringFailed(int resultCode) {
- fail("Unexpectedly got onTetheringFailed");
+ addCallbackError("Unexpectedly got onTetheringFailed");
}
};
Log.d(TAG, "Starting Ethernet tethering");
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index 4de02ac..f7600b2 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -20,7 +20,6 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
@@ -41,8 +40,6 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
-// TODO : replace with android.net.flags.Flags when aconfig is supported on udc-mainline-prod
-// import android.net.NetworkCapabilities.Flags;
import android.net.NetworkCapabilities.NetCapability;
import android.net.NetworkCapabilities.Transport;
import android.os.Build;
@@ -291,18 +288,6 @@
NET_CAPABILITY_TRUSTED,
NET_CAPABILITY_VALIDATED);
- /**
- * Capabilities that are forbidden by default.
- * Forbidden capabilities only make sense in NetworkRequest, not for network agents.
- * Therefore these capabilities are only in NetworkRequest.
- */
- private static final int[] DEFAULT_FORBIDDEN_CAPABILITIES = new int[] {
- // TODO(b/313030307): this should contain NET_CAPABILITY_LOCAL_NETWORK.
- // We cannot currently add it because doing so would crash if the module rolls back,
- // because JobScheduler persists NetworkRequests to disk, and existing production code
- // does not consider LOCAL_NETWORK to be a valid capability.
- };
-
private final NetworkCapabilities mNetworkCapabilities;
// A boolean that represents whether the NOT_VCN_MANAGED capability should be deduced when
@@ -318,16 +303,6 @@
// it for apps that do not have the NETWORK_SETTINGS permission.
mNetworkCapabilities = new NetworkCapabilities();
mNetworkCapabilities.setSingleUid(Process.myUid());
- // Default forbidden capabilities are foremost meant to help with backward
- // compatibility. When adding new types of network identified by a capability that
- // might confuse older apps, a default forbidden capability will have apps not see
- // these networks unless they explicitly ask for it.
- // If the app called clearCapabilities() it will see everything, but then it
- // can be argued that it's fair to send them too, since it asked for everything
- // explicitly.
- for (final int forbiddenCap : DEFAULT_FORBIDDEN_CAPABILITIES) {
- mNetworkCapabilities.addForbiddenCapability(forbiddenCap);
- }
}
/**
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 458d64f..bfcc171 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -382,10 +382,9 @@
});
}
- @VisibleForTesting(visibility = PACKAGE)
- protected void setInterfaceEnabled(@NonNull final String iface, boolean enabled,
- @Nullable final EthernetCallback cb) {
- mHandler.post(() -> updateInterfaceState(iface, enabled, cb));
+ /** Configure the administrative state of ethernet interface by toggling IFF_UP. */
+ public void setInterfaceEnabled(String iface, boolean enabled, EthernetCallback cb) {
+ mHandler.post(() -> setInterfaceAdministrativeState(iface, enabled, cb));
}
IpConfiguration getIpConfiguration(String iface) {
@@ -643,25 +642,40 @@
}
}
- private void updateInterfaceState(String iface, boolean up) {
- updateInterfaceState(iface, up, new EthernetCallback(null /* cb */));
- }
-
- // TODO(b/225315248): enable/disableInterface() should not affect link state.
- private void updateInterfaceState(String iface, boolean up, EthernetCallback cb) {
- final int mode = getInterfaceMode(iface);
- if (mode == INTERFACE_MODE_SERVER || !mFactory.hasInterface(iface)) {
- // The interface is in server mode or is not tracked.
- cb.onError("Failed to set link state " + (up ? "up" : "down") + " for " + iface);
+ private void setInterfaceAdministrativeState(String iface, boolean up, EthernetCallback cb) {
+ if (getInterfaceState(iface) == EthernetManager.STATE_ABSENT) {
+ cb.onError("Failed to enable/disable absent interface: " + iface);
+ return;
+ }
+ if (getInterfaceRole(iface) == EthernetManager.ROLE_SERVER) {
+ // TODO: support setEthernetState for server mode interfaces.
+ cb.onError("Failed to enable/disable interface in server mode: " + iface);
return;
}
+ if (up) {
+ // WARNING! setInterfaceUp() clears the IPv4 address and readds it. Calling
+ // enableInterface() on an active interface can lead to a provisioning failure which
+ // will cause IpClient to be restarted.
+ // TODO: use netlink directly rather than calling into netd.
+ NetdUtils.setInterfaceUp(mNetd, iface);
+ } else {
+ NetdUtils.setInterfaceDown(mNetd, iface);
+ }
+ cb.onResult(iface);
+ }
+
+ private void updateInterfaceState(String iface, boolean up) {
+ final int mode = getInterfaceMode(iface);
+ if (mode == INTERFACE_MODE_SERVER) {
+ // TODO: support tracking link state for interfaces in server mode.
+ return;
+ }
+
+ // If updateInterfaceLinkState returns false, the interface is already in the correct state.
if (mFactory.updateInterfaceLinkState(iface, up)) {
broadcastInterfaceStateChange(iface);
}
- // If updateInterfaceLinkState returns false, the interface is already in the correct state.
- // Always return success.
- cb.onResult(iface);
}
private void maybeUpdateServerModeInterfaceState(String iface, boolean available) {
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 8f09a40..f27e645 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -117,6 +117,9 @@
import static com.android.net.module.util.PermissionUtils.hasAnyPermissionOf;
import static com.android.server.ConnectivityStatsLog.CONNECTIVITY_STATE_SAMPLE;
import static com.android.server.connectivity.ConnectivityFlags.REQUEST_RESTRICTED_WIFI;
+import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING;
+
+import static java.util.Map.Entry;
import android.Manifest;
import android.annotation.CheckResult;
@@ -1002,6 +1005,9 @@
// Uids that ConnectivityService is pending to close sockets of.
private final Set<Integer> mPendingFrozenUids = new ArraySet<>();
+ // Flag to drop packets to VPN addresses ingressing via non-VPN interfaces.
+ private final boolean mIngressToVpnAddressFiltering;
+
/**
* Implements support for the legacy "one network per network type" model.
*
@@ -1979,6 +1985,8 @@
activityManager.registerUidFrozenStateChangedCallback(
(Runnable r) -> r.run(), frozenStateChangedCallback);
}
+ mIngressToVpnAddressFiltering = mDeps.isAtLeastT()
+ && mDeps.isFeatureNotChickenedOut(mContext, INGRESS_TO_VPN_ADDRESS_FILTERING);
}
/**
@@ -2766,6 +2774,7 @@
private boolean canSeeAllowedUids(final int pid, final int uid, final int netOwnerUid) {
return Process.SYSTEM_UID == uid
+ || netOwnerUid == uid
|| hasAnyPermissionOf(mContext, pid, uid,
android.Manifest.permission.NETWORK_FACTORY);
}
@@ -2793,7 +2802,6 @@
}
if (!canSeeAllowedUids(callerPid, callerUid, newNc.getOwnerUid())) {
newNc.setAllowedUids(new ArraySet<>());
- newNc.setSubscriptionIds(Collections.emptySet());
}
redactUnderlyingNetworksForCapabilities(newNc, callerPid, callerUid);
@@ -5344,6 +5352,7 @@
// was is being disconnected the callbacks have already been sent, and if it is being
// destroyed pending replacement they will be sent when it is disconnected.
maybeDisableForwardRulesForDisconnectingNai(nai, false /* sendCallbacks */);
+ updateIngressToVpnAddressFiltering(null, nai.linkProperties, nai);
try {
mNetd.networkDestroy(nai.network.getNetId());
} catch (RemoteException | ServiceSpecificException e) {
@@ -6021,7 +6030,15 @@
if (nm == null) return;
if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) {
- enforceNetworkStackPermission(mContext);
+ // This enforceNetworkStackPermission() should be adopted to check
+ // the required permission but this may be break OEM captive portal
+ // apps. Simply ignore the request if the caller does not have
+ // permission.
+ if (!hasNetworkStackPermission()) {
+ Log.e(TAG, "Calling appRequest() without proper permission. Skip");
+ return;
+ }
+
nm.forceReevaluation(mDeps.getCallingUid());
}
}
@@ -7584,15 +7601,6 @@
"Insufficient permissions to request a specific signal strength");
}
mAppOpsManager.checkPackage(callerUid, callerPackageName);
-
- if (nc.getSubscriptionIds().isEmpty()) {
- return;
- }
- if (mRequestRestrictedWifiEnabled
- && canRequestRestrictedNetworkDueToCarrierPrivileges(nc, callerUid)) {
- return;
- }
- enforceNetworkFactoryPermission();
}
private int[] getSignalStrengthThresholds(@NonNull final NetworkAgentInfo nai) {
@@ -8695,6 +8703,8 @@
// new interface (the interface name -> index map becomes initialized)
updateVpnFiltering(newLp, oldLp, networkAgent);
+ updateIngressToVpnAddressFiltering(newLp, oldLp, networkAgent);
+
updateMtu(newLp, oldLp);
// TODO - figure out what to do for clat
// for (LinkProperties lp : newLp.getStackedLinks()) {
@@ -8986,6 +8996,87 @@
}
}
+ /**
+ * Returns ingress discard rules to drop packets to VPN addresses ingressing via non-VPN
+ * interfaces.
+ * Ingress discard rule is added to the address iff
+ * 1. The address is not a link local address
+ * 2. The address is used by a single VPN interface and not used by any other
+ * interfaces even non-VPN ones
+ * This method can be called during network disconnects, when nai has already been removed from
+ * mNetworkAgentInfos.
+ *
+ * @param nai This method generates rules assuming lp of this nai is the lp at the second
+ * argument.
+ * @param lp This method generates rules assuming lp of nai at the first argument is this lp.
+ * Caller passes old lp to generate old rules and new lp to generate new rules.
+ * @return ingress discard rules. Set of pairs of addresses and interface names
+ */
+ private Set<Pair<InetAddress, String>> generateIngressDiscardRules(
+ @NonNull final NetworkAgentInfo nai, @Nullable final LinkProperties lp) {
+ Set<NetworkAgentInfo> nais = new ArraySet<>(mNetworkAgentInfos);
+ nais.add(nai);
+ // Determine how many networks each IP address is currently configured on.
+ // Ingress rules are added only for IP addresses that are configured on single interface.
+ final Map<InetAddress, Integer> addressOwnerCounts = new ArrayMap<>();
+ for (final NetworkAgentInfo agent : nais) {
+ if (agent.isDestroyed()) {
+ continue;
+ }
+ final LinkProperties agentLp = (nai == agent) ? lp : agent.linkProperties;
+ if (agentLp == null) {
+ continue;
+ }
+ for (final InetAddress addr: agentLp.getAllAddresses()) {
+ addressOwnerCounts.put(addr, addressOwnerCounts.getOrDefault(addr, 0) + 1);
+ }
+ }
+
+ // Iterates all networks instead of only generating rule for nai that was passed in since
+ // lp of the nai change could cause/resolve address collision and result in affecting rule
+ // for different network.
+ final Set<Pair<InetAddress, String>> ingressDiscardRules = new ArraySet<>();
+ for (final NetworkAgentInfo agent : nais) {
+ if (!agent.isVPN() || agent.isDestroyed()) {
+ continue;
+ }
+ final LinkProperties agentLp = (nai == agent) ? lp : agent.linkProperties;
+ if (agentLp == null || agentLp.getInterfaceName() == null) {
+ continue;
+ }
+
+ for (final InetAddress addr: agentLp.getAllAddresses()) {
+ if (addressOwnerCounts.get(addr) == 1 && !addr.isLinkLocalAddress()) {
+ ingressDiscardRules.add(new Pair<>(addr, agentLp.getInterfaceName()));
+ }
+ }
+ }
+ return ingressDiscardRules;
+ }
+
+ private void updateIngressToVpnAddressFiltering(@Nullable LinkProperties newLp,
+ @Nullable LinkProperties oldLp, @NonNull NetworkAgentInfo nai) {
+ // Having isAtleastT to avoid NewApi linter error (b/303382209)
+ if (!mIngressToVpnAddressFiltering || !mDeps.isAtLeastT()) {
+ return;
+ }
+ final CompareOrUpdateResult<InetAddress, Pair<InetAddress, String>> ruleDiff =
+ new CompareOrUpdateResult<>(
+ generateIngressDiscardRules(nai, oldLp),
+ generateIngressDiscardRules(nai, newLp),
+ (rule) -> rule.first);
+ for (Pair<InetAddress, String> rule: ruleDiff.removed) {
+ mBpfNetMaps.removeIngressDiscardRule(rule.first);
+ }
+ for (Pair<InetAddress, String> rule: ruleDiff.added) {
+ mBpfNetMaps.setIngressDiscardRule(rule.first, rule.second);
+ }
+ // setIngressDiscardRule overrides the existing rule
+ for (Pair<InetAddress, String> rule: ruleDiff.updated) {
+ mBpfNetMaps.setIngressDiscardRule(rule.first, rule.second);
+ }
+ }
+
private void updateWakeOnLan(@NonNull LinkProperties lp) {
if (mWolSupportedInterfaces == null) {
mWolSupportedInterfaces = new ArraySet<>(mResources.get().getStringArray(
@@ -9176,7 +9267,7 @@
// 3. The app doesn't have Carrier Privileges
// 4. The app doesn't have permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS
for (final NetworkRequest nr : mNetworkRequests.keySet()) {
- if ((nr.isRequest() || nr.isListen())
+ if (nr.isRequest()
&& !nr.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
&& nr.getRequestorUid() == uid
&& getSubscriptionIdFromNetworkCaps(nr.networkCapabilities) == subId
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index bf09160..a55c683 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -38,6 +38,10 @@
public static final String REQUEST_RESTRICTED_WIFI =
"request_restricted_wifi";
+
+ public static final String INGRESS_TO_VPN_ADDRESS_FILTERING =
+ "ingress_to_vpn_address_filtering";
+
private boolean mNoRematchAllRequestsOnRegister;
/**
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt b/staticlibs/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt
index af4f96d..c6e5f25 100644
--- a/staticlibs/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt
@@ -19,10 +19,77 @@
package com.android.testutils
import java.util.concurrent.CountDownLatch
+import java.util.concurrent.ExecutorService
import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
import kotlin.system.measureTimeMillis
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
// For Java usage
fun durationOf(fn: Runnable) = measureTimeMillis { fn.run() }
fun CountDownLatch.await(timeoutMs: Long): Boolean = await(timeoutMs, TimeUnit.MILLISECONDS)
+
+/**
+ * Quit resources provided as a list by a supplier.
+ *
+ * The supplier may return more resources as the process progresses, for example while interrupting
+ * threads and waiting for them to finish they may spawn more threads, so this implements a
+ * [maxRetryCount] which, in this case, would be the maximum length of the thread chain that can be
+ * terminated.
+ */
+fun <T> quitResources(
+ maxRetryCount: Int,
+ supplier: () -> List<T>,
+ terminator: Consumer<T>
+) {
+ // Run it multiple times since new threads might be generated in a thread
+ // that is about to be terminated
+ for (retryCount in 0 until maxRetryCount) {
+ val resourcesToBeCleared = supplier()
+ if (resourcesToBeCleared.isEmpty()) return
+ for (resource in resourcesToBeCleared) {
+ terminator.accept(resource)
+ }
+ }
+ assertEmpty(supplier())
+}
+
+/**
+ * Implementation of [quitResources] to interrupt and wait for [ExecutorService]s to finish.
+ */
+@JvmOverloads
+fun quitExecutorServices(
+ maxRetryCount: Int,
+ interrupt: Boolean = true,
+ timeoutMs: Long = 10_000L,
+ supplier: () -> List<ExecutorService>
+) {
+ quitResources(maxRetryCount, supplier) { ecs ->
+ if (interrupt) {
+ ecs.shutdownNow()
+ }
+ assertTrue(ecs.awaitTermination(timeoutMs, TimeUnit.MILLISECONDS),
+ "ExecutorServices did not terminate within timeout")
+ }
+}
+
+/**
+ * Implementation of [quitResources] to interrupt and wait for [Thread]s to finish.
+ */
+@JvmOverloads
+fun quitThreads(
+ maxRetryCount: Int,
+ interrupt: Boolean = true,
+ timeoutMs: Long = 10_000L,
+ supplier: () -> List<Thread>
+) {
+ quitResources(maxRetryCount, supplier) { th ->
+ if (interrupt) {
+ th.interrupt()
+ }
+ th.join(timeoutMs)
+ assertFalse(th.isAlive, "Threads did not terminate within timeout.")
+ }
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 1241e18..2ca8832 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -160,10 +160,6 @@
private static final long BROADCAST_TIMEOUT_MS = 5_000;
- // Should be kept in sync with the constant in NetworkPolicyManagerService.
- // TODO: b/322115994 - remove once the feature is in staging.
- private static final boolean ALWAYS_RESTRICT_BACKGROUND_NETWORK = false;
-
protected Context mContext;
protected Instrumentation mInstrumentation;
protected ConnectivityManager mCm;
@@ -233,8 +229,9 @@
}
final String output = executeShellCommand("device_config get backstage_power"
+ " com.android.server.net.network_blocked_for_top_sleeping_and_above");
- return Boolean.parseBoolean(output) && ALWAYS_RESTRICT_BACKGROUND_NETWORK;
+ return Boolean.parseBoolean(output);
}
+
protected int getUid(String packageName) throws Exception {
return mContext.getPackageManager().getPackageUid(packageName, 0);
}
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index b7e5205..7af3c83 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -899,6 +899,20 @@
}
@Test
+ fun testEnableDisableInterface_callbacks() {
+ val iface = createInterface()
+ val listener = EthernetStateListener()
+ addInterfaceStateListener(listener)
+ listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
+
+ disableInterface(iface).expectResult(iface.name)
+ listener.expectCallback(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+
+ enableInterface(iface).expectResult(iface.name)
+ listener.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
+ }
+
+ @Test
fun testUpdateConfiguration_forBothIpConfigAndCapabilities() {
val iface = createInterface()
val cb = requestNetwork(ETH_REQUEST.copyWithEthernetSpecifier(iface.name))
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index 8dbcf2f..0daf7fe 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -2199,15 +2199,10 @@
}
private fun hasServiceTypeClientsForNetwork(clients: List<String>, network: Network): Boolean {
- for (client in clients) {
- val netid = client.substring(
- client.indexOf("network=") + "network=".length,
- client.indexOf("interfaceIndex=") - 1)
- if (netid == network.toString()) {
- return true
- }
+ return clients.any { client -> client.substring(
+ client.indexOf("network=") + "network=".length,
+ client.indexOf("interfaceIndex=") - 1) == network.getNetId().toString()
}
- return false
}
/**
diff --git a/tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt b/tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt
index 104d063..3d948ba 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/NetworkStackInstrumentationService.kt
@@ -18,10 +18,14 @@
import android.app.Service
import android.content.Intent
+import androidx.annotation.GuardedBy
+import com.android.testutils.quitExecutorServices
+import com.android.testutils.quitThreads
import java.net.URL
import java.util.Collections
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
+import java.util.concurrent.ExecutorService
import kotlin.collections.ArrayList
import kotlin.test.fail
@@ -37,7 +41,12 @@
.run {
withDefault { key -> getOrPut(key) { ConcurrentLinkedQueue() } }
}
- private val httpRequestUrls = Collections.synchronizedList(ArrayList<String>())
+ private val httpRequestUrls = Collections.synchronizedList(mutableListOf<String>())
+
+ @GuardedBy("networkMonitorThreads")
+ private val networkMonitorThreads = mutableListOf<Thread>()
+ @GuardedBy("networkMonitorExecutorServices")
+ private val networkMonitorExecutorServices = mutableListOf<ExecutorService>()
/**
* Called when an HTTP request is being processed by NetworkMonitor. Returns the response
@@ -52,10 +61,47 @@
}
/**
+ * Called when NetworkMonitor creates a new Thread.
+ */
+ fun onNetworkMonitorThreadCreated(thread: Thread) {
+ synchronized(networkMonitorThreads) {
+ networkMonitorThreads.add(thread)
+ }
+ }
+
+ /**
+ * Called when NetworkMonitor creates a new ExecutorService.
+ */
+ fun onNetworkMonitorExecutorServiceCreated(executorService: ExecutorService) {
+ synchronized(networkMonitorExecutorServices) {
+ networkMonitorExecutorServices.add(executorService)
+ }
+ }
+
+ /**
* Clear all state of this connector. This is intended for use between two tests, so all
* state should be reset as if the connector was just created.
*/
override fun clearAllState() {
+ quitThreads(
+ maxRetryCount = 3,
+ interrupt = true) {
+ synchronized(networkMonitorThreads) {
+ networkMonitorThreads.toList().also { networkMonitorThreads.clear() }
+ }
+ }
+ quitExecutorServices(
+ maxRetryCount = 3,
+ // NetworkMonitor is expected to have interrupted its executors when probing
+ // finishes, otherwise it's a thread pool leak that should be caught, so they should
+ // not need to be interrupted (the test only needs to wait for them to finish).
+ interrupt = false) {
+ synchronized(networkMonitorExecutorServices) {
+ networkMonitorExecutorServices.toList().also {
+ networkMonitorExecutorServices.clear()
+ }
+ }
+ }
httpResponses.clear()
httpRequestUrls.clear()
}
diff --git a/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
index 7e227c4..e43ce29 100644
--- a/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
+++ b/tests/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
@@ -30,13 +30,14 @@
import com.android.server.NetworkStackService.NetworkStackConnector
import com.android.server.connectivity.NetworkMonitor
import com.android.server.net.integrationtests.NetworkStackInstrumentationService.InstrumentationConnector
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.spy
import java.io.ByteArrayInputStream
import java.net.HttpURLConnection
import java.net.URL
import java.nio.charset.StandardCharsets
+import java.util.concurrent.ExecutorService
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
private const val TEST_NETID = 42
@@ -60,6 +61,10 @@
private class NetworkMonitorDeps(private val privateDnsBypassNetwork: Network) :
NetworkMonitor.Dependencies() {
override fun getPrivateDnsBypassNetwork(network: Network?) = privateDnsBypassNetwork
+ override fun onThreadCreated(thread: Thread) =
+ InstrumentationConnector.onNetworkMonitorThreadCreated(thread)
+ override fun onExecutorServiceCreated(ecs: ExecutorService) =
+ InstrumentationConnector.onNetworkMonitorExecutorServiceCreated(ecs)
}
/**
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 8f768b2..f5eee42 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -172,6 +172,7 @@
import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackRegister;
import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister;
+import static com.android.server.connectivity.ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING;
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.ConcurrentUtils.await;
import static com.android.testutils.ConcurrentUtils.durationOf;
@@ -2179,6 +2180,8 @@
return true;
case ALLOW_SATALLITE_NETWORK_FALLBACK:
return true;
+ case INGRESS_TO_VPN_ADDRESS_FILTERING:
+ return true;
default:
return super.isFeatureNotChickenedOut(context, name);
}
@@ -17338,21 +17341,7 @@
}
@Test
- public void testSubIdsClearedWithoutNetworkFactoryPermission() throws Exception {
- mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED);
- final NetworkCapabilities nc = new NetworkCapabilities();
- nc.setSubscriptionIds(Collections.singleton(Process.myUid()));
-
- final NetworkCapabilities result =
- mService.networkCapabilitiesRestrictedForCallerPermissions(
- nc, Process.myPid(), Process.myUid());
- assertTrue(result.getSubscriptionIds().isEmpty());
- }
-
- @Test
- public void testSubIdsExistWithNetworkFactoryPermission() throws Exception {
- mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
-
+ public void testSubIdsExist() throws Exception {
final Set<Integer> subIds = Collections.singleton(Process.myUid());
final NetworkCapabilities nc = new NetworkCapabilities();
nc.setSubscriptionIds(subIds);
@@ -17378,8 +17367,7 @@
}
@Test
- public void testNetworkRequestWithSubIdsWithNetworkFactoryPermission() throws Exception {
- mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+ public void testNetworkRequestWithSubIds() throws Exception {
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
final NetworkCallback networkCallback1 = new NetworkCallback();
@@ -17395,21 +17383,6 @@
}
@Test
- public void testNetworkRequestWithSubIdsWithoutNetworkFactoryPermission() throws Exception {
- mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED);
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(
- mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
-
- final Class<SecurityException> expected = SecurityException.class;
- assertThrows(
- expected, () -> mCm.requestNetwork(getRequestWithSubIds(), new NetworkCallback()));
- assertThrows(expected, () -> mCm.requestNetwork(getRequestWithSubIds(), pendingIntent));
- assertThrows(
- expected,
- () -> mCm.registerNetworkCallback(getRequestWithSubIds(), new NetworkCallback()));
- }
-
- @Test
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
public void testCarrierConfigAppSendNetworkRequestForRestrictedWifi() throws Exception {
mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
@@ -17544,6 +17517,47 @@
false /* expectUnavailable */,
true /* expectCapChanged */);
}
+
+ @Test
+ public void testAllowedUidsExistWithoutNetworkFactoryPermission() throws Exception {
+ // Make sure NETWORK_FACTORY permission is not granted.
+ mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED);
+ mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
+ final TestNetworkCallback cb = new TestNetworkCallback();
+ mCm.requestNetwork(new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(TRANSPORT_TEST)
+ .addTransportType(TRANSPORT_CELLULAR)
+ .build(),
+ cb);
+
+ final ArraySet<Integer> uids = new ArraySet<>();
+ uids.add(200);
+ final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .setAllowedUids(uids)
+ .setOwnerUid(Process.myUid())
+ .setAdministratorUids(new int[] {Process.myUid()})
+ .build();
+ final TestNetworkAgentWrapper agent = new TestNetworkAgentWrapper(TRANSPORT_TEST,
+ new LinkProperties(), nc);
+ agent.connect(true);
+ cb.expectAvailableThenValidatedCallbacks(agent);
+
+ uids.add(300);
+ uids.add(400);
+ nc.setAllowedUids(uids);
+ agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
+ if (mDeps.isAtLeastT()) {
+ // AllowedUids is not cleared even without the NETWORK_FACTORY permission
+ // because the caller is the owner of the network.
+ cb.expectCaps(agent, c -> c.getAllowedUids().equals(uids));
+ } else {
+ cb.assertNoCallback();
+ }
+ }
+
@Test
public void testAllowedUids() throws Exception {
final int preferenceOrder =
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSCaptivePortalAppTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSCaptivePortalAppTest.kt
index be2b29c..0bad60d 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSCaptivePortalAppTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSCaptivePortalAppTest.kt
@@ -20,6 +20,7 @@
import android.content.Intent
import android.content.pm.PackageManager.PERMISSION_DENIED
import android.content.pm.PackageManager.PERMISSION_GRANTED
+import android.net.CaptivePortal
import android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN
import android.net.ConnectivityManager.EXTRA_CAPTIVE_PORTAL
import android.net.IpPrefix
@@ -33,23 +34,23 @@
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
import android.net.NetworkCapabilities.TRANSPORT_WIFI
-import android.net.NetworkStack
-import android.net.CaptivePortal
import android.net.NetworkRequest
import android.net.NetworkScore
import android.net.NetworkScore.KEEP_CONNECTED_FOR_TEST
+import android.net.NetworkStack
import android.net.RouteInfo
import android.os.Build
import android.os.Bundle
import androidx.test.filters.SmallTest
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
-import com.android.testutils.assertThrows
import com.android.testutils.TestableNetworkCallback
+import kotlin.test.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import kotlin.test.assertEquals
// This allows keeping all the networks connected without having to file individual requests
// for them.
@@ -95,16 +96,22 @@
captivePortalCallback.expectAvailableCallbacksUnvalidated(wifiAgent)
val signInIntent = startCaptivePortalApp(wifiAgent)
// Remove the granted permissions
- context.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
- PERMISSION_DENIED)
+ context.setPermission(
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ PERMISSION_DENIED
+ )
context.setPermission(NETWORK_STACK, PERMISSION_DENIED)
val captivePortal: CaptivePortal? = signInIntent.getParcelableExtra(EXTRA_CAPTIVE_PORTAL)
- assertThrows(SecurityException::class.java, { captivePortal?.reevaluateNetwork() })
+ captivePortal?.reevaluateNetwork()
+ verify(wifiAgent.networkMonitor, never()).forceReevaluation(anyInt())
}
private fun createWifiAgent(): CSAgentWrapper {
- return Agent(score = keepScore(), lp = lp(WIFI_IFACE),
- nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET))
+ return Agent(
+ score = keepScore(),
+ lp = lp(WIFI_IFACE),
+ nc = nc(TRANSPORT_WIFI, NET_CAPABILITY_INTERNET)
+ )
}
private fun startCaptivePortalApp(networkAgent: CSAgentWrapper): Intent {
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
new file mode 100644
index 0000000..e8664c1
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2023 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
+
+import android.net.InetAddresses
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_VPN
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkRequest
+import android.net.VpnManager.TYPE_VPN_SERVICE
+import android.net.VpnTransportInfo
+import android.os.Build
+import androidx.test.filters.SmallTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import com.android.testutils.TestableNetworkCallback
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+
+private const val VPN_IFNAME = "tun10041"
+private const val VPN_IFNAME2 = "tun10042"
+private const val WIFI_IFNAME = "wlan0"
+private const val TIMEOUT_MS = 1_000L
+private const val LONG_TIMEOUT_MS = 5_000
+
+private fun vpnNc() = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_VPN)
+ .removeCapability(NET_CAPABILITY_NOT_VPN)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .setTransportInfo(
+ VpnTransportInfo(
+ TYPE_VPN_SERVICE,
+ "MySession12345",
+ false /* bypassable */,
+ false /* longLivedTcpConnectionsExpensive */))
+ .build()
+
+private fun wifiNc() = NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .build()
+
+private fun nr(transport: Int) = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(transport).apply {
+ if (transport != TRANSPORT_VPN) {
+ addCapability(NET_CAPABILITY_NOT_VPN)
+ }
+ }.build()
+
+private fun lp(iface: String, vararg linkAddresses: LinkAddress) = LinkProperties().apply {
+ interfaceName = iface
+ for (linkAddress in linkAddresses) {
+ addLinkAddress(linkAddress)
+ }
+}
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class CSIngressDiscardRuleTests : CSTest() {
+ private val IPV6_ADDRESS = InetAddresses.parseNumericAddress("2001:db8:1::1")
+ private val IPV6_LINK_ADDRESS = LinkAddress(IPV6_ADDRESS, 64)
+ private val IPV6_ADDRESS2 = InetAddresses.parseNumericAddress("2001:db8:1::2")
+ private val IPV6_LINK_ADDRESS2 = LinkAddress(IPV6_ADDRESS2, 64)
+ private val IPV6_ADDRESS3 = InetAddresses.parseNumericAddress("2001:db8:1::3")
+ private val IPV6_LINK_ADDRESS3 = LinkAddress(IPV6_ADDRESS3, 64)
+ private val LOCAL_IPV6_ADDRRESS = InetAddresses.parseNumericAddress("fe80::1234")
+ private val LOCAL_IPV6_LINK_ADDRRESS = LinkAddress(LOCAL_IPV6_ADDRRESS, 64)
+
+ @Test
+ fun testVpnIngressDiscardRule_UpdateVpnAddress() {
+ // non-VPN network whose address will be not duplicated with VPN address
+ val wifiNc = wifiNc()
+ val wifiLp = lp(WIFI_IFNAME, IPV6_LINK_ADDRESS3)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+
+ val nr = nr(TRANSPORT_VPN)
+ val cb = TestableNetworkCallback()
+ cm.registerNetworkCallback(nr, cb)
+ val nc = vpnNc()
+ val lp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val agent = Agent(nc = nc, lp = lp)
+ agent.connect()
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+
+ // IngressDiscardRule is added to the VPN address
+ verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
+ verify(bpfNetMaps, never()).setIngressDiscardRule(LOCAL_IPV6_ADDRRESS, VPN_IFNAME)
+
+ // The VPN address is changed
+ val newLp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS2, LOCAL_IPV6_LINK_ADDRRESS)
+ agent.sendLinkProperties(newLp)
+ cb.expect<LinkPropertiesChanged>(agent.network)
+
+ // IngressDiscardRule is removed from the old VPN address and added to the new VPN address
+ verify(bpfNetMaps).removeIngressDiscardRule(IPV6_ADDRESS)
+ verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS2, VPN_IFNAME)
+ verify(bpfNetMaps, never()).setIngressDiscardRule(LOCAL_IPV6_ADDRRESS, VPN_IFNAME)
+
+ agent.disconnect()
+ verify(bpfNetMaps, timeout(TIMEOUT_MS)).removeIngressDiscardRule(IPV6_ADDRESS2)
+
+ cm.unregisterNetworkCallback(cb)
+ }
+
+ @Test
+ fun testVpnIngressDiscardRule_UpdateInterfaceName() {
+ val inorder = inOrder(bpfNetMaps)
+
+ val nr = nr(TRANSPORT_VPN)
+ val cb = TestableNetworkCallback()
+ cm.registerNetworkCallback(nr, cb)
+ val nc = vpnNc()
+ val lp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val agent = Agent(nc = nc, lp = lp)
+ agent.connect()
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+
+ // IngressDiscardRule is added to the VPN address
+ inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
+ inorder.verifyNoMoreInteractions()
+
+ // The VPN interface name is changed
+ val newlp = lp(VPN_IFNAME2, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ agent.sendLinkProperties(newlp)
+ cb.expect<LinkPropertiesChanged>(agent.network)
+
+ // IngressDiscardRule is updated with the new interface name
+ inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME2)
+ inorder.verifyNoMoreInteractions()
+
+ agent.disconnect()
+ inorder.verify(bpfNetMaps, timeout(TIMEOUT_MS)).removeIngressDiscardRule(IPV6_ADDRESS)
+
+ cm.unregisterNetworkCallback(cb)
+ }
+
+ @Test
+ fun testVpnIngressDiscardRule_DuplicatedIpAddress_UpdateVpnAddress() {
+ val inorder = inOrder(bpfNetMaps)
+
+ val wifiNc = wifiNc()
+ val wifiLp = lp(WIFI_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+
+ // IngressDiscardRule is not added to non-VPN interfaces
+ inorder.verify(bpfNetMaps, never()).setIngressDiscardRule(any(), any())
+
+ val nr = nr(TRANSPORT_VPN)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+ val vpnNc = vpnNc()
+ val vpnLp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val vpnAgent = Agent(nc = vpnNc, lp = vpnLp)
+ vpnAgent.connect()
+ cb.expectAvailableCallbacks(vpnAgent.network, validated = false)
+
+ // IngressDiscardRule is not added since the VPN address is duplicated with the Wi-Fi
+ // address
+ inorder.verify(bpfNetMaps, never()).setIngressDiscardRule(any(), any())
+
+ // The VPN address is changed to a different address from the Wi-Fi interface
+ val newVpnlp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS2, LOCAL_IPV6_LINK_ADDRRESS)
+ vpnAgent.sendLinkProperties(newVpnlp)
+
+ // IngressDiscardRule is added to the VPN address since the VPN address is not duplicated
+ // with the Wi-Fi address
+ cb.expect<LinkPropertiesChanged>(vpnAgent.network)
+ inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS2, VPN_IFNAME)
+
+ // The VPN address is changed back to the same address as the Wi-Fi interface
+ vpnAgent.sendLinkProperties(vpnLp)
+ cb.expect<LinkPropertiesChanged>(vpnAgent.network)
+
+ // IngressDiscardRule for IPV6_ADDRESS2 is removed but IngressDiscardRule for
+ // IPV6_LINK_ADDRESS is not added since Wi-Fi also uses IPV6_LINK_ADDRESS
+ inorder.verify(bpfNetMaps).removeIngressDiscardRule(IPV6_ADDRESS2)
+ inorder.verifyNoMoreInteractions()
+
+ vpnAgent.disconnect()
+ inorder.verifyNoMoreInteractions()
+
+ cm.unregisterNetworkCallback(cb)
+ }
+
+ @Test
+ fun testVpnIngressDiscardRule_DuplicatedIpAddress_UpdateNonVpnAddress() {
+ val inorder = inOrder(bpfNetMaps)
+
+ val vpnNc = vpnNc()
+ val vpnLp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val vpnAgent = Agent(nc = vpnNc, lp = vpnLp)
+ vpnAgent.connect()
+
+ // IngressDiscardRule is added to the VPN address
+ inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
+ inorder.verifyNoMoreInteractions()
+
+ val nr = nr(TRANSPORT_WIFI)
+ val cb = TestableNetworkCallback()
+ cm.requestNetwork(nr, cb)
+ val wifiNc = wifiNc()
+ val wifiLp = lp(WIFI_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+
+ // IngressDiscardRule is removed since the VPN address is duplicated with the Wi-Fi address
+ inorder.verify(bpfNetMaps).removeIngressDiscardRule(IPV6_ADDRESS)
+
+ // The Wi-Fi address is changed to a different address from the VPN interface
+ val newWifilp = lp(WIFI_IFNAME, IPV6_LINK_ADDRESS2, LOCAL_IPV6_LINK_ADDRRESS)
+ wifiAgent.sendLinkProperties(newWifilp)
+ cb.expect<LinkPropertiesChanged>(wifiAgent.network)
+
+ // IngressDiscardRule is added to the VPN address since the VPN address is not duplicated
+ // with the Wi-Fi address
+ inorder.verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
+ inorder.verifyNoMoreInteractions()
+
+ // The Wi-Fi address is changed back to the same address as the VPN interface
+ wifiAgent.sendLinkProperties(wifiLp)
+ cb.expect<LinkPropertiesChanged>(wifiAgent.network)
+
+ // IngressDiscardRule is removed since the VPN address is duplicated with the Wi-Fi address
+ inorder.verify(bpfNetMaps).removeIngressDiscardRule(IPV6_ADDRESS)
+
+ // IngressDiscardRule is added to the VPN address since Wi-Fi is disconnected
+ wifiAgent.disconnect()
+ inorder.verify(bpfNetMaps, timeout(TIMEOUT_MS))
+ .setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
+
+ vpnAgent.disconnect()
+ inorder.verify(bpfNetMaps, timeout(TIMEOUT_MS)).removeIngressDiscardRule(IPV6_ADDRESS)
+
+ cm.unregisterNetworkCallback(cb)
+ }
+
+ @Test
+ fun testVpnIngressDiscardRule_UnregisterAfterReplacement() {
+ val wifiNc = wifiNc()
+ val wifiLp = lp(WIFI_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val wifiAgent = Agent(nc = wifiNc, lp = wifiLp)
+ wifiAgent.connect()
+ wifiAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS)
+ waitForIdle()
+
+ val vpnNc = vpnNc()
+ val vpnLp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val vpnAgent = Agent(nc = vpnNc, lp = vpnLp)
+ vpnAgent.connect()
+
+ // IngressDiscardRule is added since the Wi-Fi network is destroyed
+ verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
+
+ // IngressDiscardRule is removed since the VPN network is destroyed
+ vpnAgent.unregisterAfterReplacement(LONG_TIMEOUT_MS)
+ waitForIdle()
+ verify(bpfNetMaps).removeIngressDiscardRule(IPV6_ADDRESS)
+ }
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
index 7007b16..13c5cbc 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
@@ -185,6 +185,7 @@
fun sendLocalNetworkConfig(lnc: LocalNetworkConfig) = agent.sendLocalNetworkConfig(lnc)
fun sendNetworkCapabilities(nc: NetworkCapabilities) = agent.sendNetworkCapabilities(nc)
+ fun sendLinkProperties(lp: LinkProperties) = agent.sendLinkProperties(lp)
fun connectWithCaptivePortal(redirectUrl: String) {
setCaptivePortal(redirectUrl)
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 1966cb1..6c9871c 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -53,6 +53,7 @@
import android.os.UserHandle
import android.os.UserManager
import android.permission.PermissionManager.PermissionResult
+import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.testing.TestableContext
import androidx.test.platform.app.InstrumentationRegistry
@@ -131,8 +132,10 @@
init {
if (!SdkLevel.isAtLeastS()) {
- throw UnsupportedApiLevelException("CSTest subclasses must be annotated to only " +
- "run on S+, e.g. @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)")
+ throw UnsupportedApiLevelException(
+ "CSTest subclasses must be annotated to only " +
+ "run on S+, e.g. @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)"
+ )
}
}
@@ -150,6 +153,7 @@
it[ConnectivityService.DELAY_DESTROY_FROZEN_SOCKETS_VERSION] = true
it[ConnectivityService.ALLOW_SYSUI_CONNECTIVITY_REPORTS] = true
it[ConnectivityService.ALLOW_SATALLITE_NETWORK_FALLBACK] = true
+ it[ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING] = true
}
fun enableFeature(f: String) = enabledFeatures.set(f, true)
fun disableFeature(f: String) = enabledFeatures.set(f, false)
@@ -183,6 +187,7 @@
val telephonyManager = mock<TelephonyManager>().also {
doReturn(true).`when`(it).isDataCapable()
}
+ val subscriptionManager = mock<SubscriptionManager>()
val multicastRoutingCoordinatorService = mock<MulticastRoutingCoordinatorService>()
val satelliteAccessController = mock<SatelliteAccessController>()
@@ -252,8 +257,12 @@
AutomaticOnOffKeepaliveTracker(c, h, AOOKTDeps(c))
override fun makeMultinetworkPolicyTracker(c: Context, h: Handler, r: Runnable) =
- MultinetworkPolicyTracker(c, h, r,
- MultinetworkPolicyTrackerTestDependencies(connResources.get()))
+ MultinetworkPolicyTracker(
+ c,
+ h,
+ r,
+ MultinetworkPolicyTrackerTestDependencies(connResources.get())
+ )
override fun makeNetworkRequestStateStatsMetrics(c: Context) =
this@CSTest.networkRequestStateStatsMetrics
@@ -338,8 +347,12 @@
override fun enforceCallingOrSelfPermission(permission: String, message: String?) {
// If the permission result does not set in the mMockedPermissions, it will be
// considered as PERMISSION_GRANTED as existing design to prevent breaking other tests.
- val granted = checkMockedPermission(permission, Process.myPid(), Process.myUid(),
- PERMISSION_GRANTED)
+ val granted = checkMockedPermission(
+ permission,
+ Process.myPid(),
+ Process.myUid(),
+ PERMISSION_GRANTED
+ )
if (!granted.equals(PERMISSION_GRANTED)) {
throw SecurityException("[Test] permission denied: " + permission)
}
@@ -350,8 +363,12 @@
override fun checkCallingOrSelfPermission(permission: String) =
checkMockedPermission(permission, Process.myPid(), Process.myUid(), PERMISSION_GRANTED)
- private fun checkMockedPermission(permission: String, pid: Int, uid: Int, default: Int):
- Int {
+ private fun checkMockedPermission(
+ permission: String,
+ pid: Int,
+ uid: Int,
+ default: Int
+ ): Int {
val processSpecificKey = "$permission,$pid,$uid"
return mMockedPermissions[processSpecificKey]
?: mMockedPermissions[permission] ?: default
@@ -413,6 +430,7 @@
Context.ACTIVITY_SERVICE -> activityManager
Context.SYSTEM_CONFIG_SERVICE -> systemConfigManager
Context.TELEPHONY_SERVICE -> telephonyManager
+ Context.TELEPHONY_SUBSCRIPTION_SERVICE -> subscriptionManager
Context.BATTERY_STATS_SERVICE -> batteryManager
Context.STATS_MANAGER -> null // Stats manager is final and can't be mocked
Context.APP_OPS_SERVICE -> appOpsManager
@@ -422,8 +440,7 @@
internal val orderedBroadcastAsUserHistory = ArrayTrackRecord<Intent>().newReadHead()
fun expectNoDataActivityBroadcast(timeoutMs: Int) {
- assertNull(orderedBroadcastAsUserHistory.poll(
- timeoutMs.toLong()) { intent -> true })
+ assertNull(orderedBroadcastAsUserHistory.poll(timeoutMs.toLong()))
}
override fun sendOrderedBroadcastAsUser(
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 36ce4d5..6c02caa 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -109,6 +109,7 @@
private static final int CALLBACK_TIMEOUT_MILLIS = 1_000;
private static final int ENABLED_TIMEOUT_MILLIS = 2_000;
private static final int SERVICE_DISCOVERY_TIMEOUT_MILLIS = 30_000;
+ private static final int SERVICE_LOST_TIMEOUT_MILLIS = 20_000;
private static final String MESHCOP_SERVICE_TYPE = "_meshcop._udp";
private static final String THREAD_NETWORK_PRIVILEGED =
"android.permission.THREAD_NETWORK_PRIVILEGED";
@@ -881,7 +882,7 @@
discoverForServiceLost(MESHCOP_SERVICE_TYPE, serviceLostFuture);
setEnabledAndWait(mController, false);
try {
- serviceLostFuture.get(SERVICE_DISCOVERY_TIMEOUT_MILLIS, MILLISECONDS);
+ serviceLostFuture.get(SERVICE_LOST_TIMEOUT_MILLIS, MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException ignored) {
// It's fine if the service lost event didn't show up. The service may not ever be
// advertised.
@@ -889,7 +890,9 @@
mNsdManager.stopServiceDiscovery(listener);
}
- assertThrows(TimeoutException.class, () -> discoverService(MESHCOP_SERVICE_TYPE));
+ assertThrows(
+ TimeoutException.class,
+ () -> discoverService(MESHCOP_SERVICE_TYPE, SERVICE_LOST_TIMEOUT_MILLIS));
}
private static void dropAllPermissions() {
@@ -1107,6 +1110,12 @@
// Return the first discovered service instance.
private NsdServiceInfo discoverService(String serviceType) throws Exception {
+ return discoverService(serviceType, SERVICE_DISCOVERY_TIMEOUT_MILLIS);
+ }
+
+ // Return the first discovered service instance.
+ private NsdServiceInfo discoverService(String serviceType, int timeoutMilliseconds)
+ throws Exception {
CompletableFuture<NsdServiceInfo> serviceInfoFuture = new CompletableFuture<>();
NsdManager.DiscoveryListener listener =
new DefaultDiscoveryListener() {
@@ -1117,7 +1126,7 @@
};
mNsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener);
try {
- serviceInfoFuture.get(SERVICE_DISCOVERY_TIMEOUT_MILLIS, MILLISECONDS);
+ serviceInfoFuture.get(timeoutMilliseconds, MILLISECONDS);
} finally {
mNsdManager.stopServiceDiscovery(listener);
}
diff --git a/tools/Android.bp b/tools/Android.bp
index 9216b5b..2c2ed14 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -83,6 +83,8 @@
],
data: [
"testdata/test-jarjar-excludes.txt",
+ // txt with Test classes to test they aren't included when added to jarjar excludes
+ "testdata/test-jarjar-excludes-testclass.txt",
// two unsupportedappusage lists with different classes to test using multiple lists
"testdata/test-unsupportedappusage.txt",
"testdata/test-other-unsupportedappusage.txt",
diff --git a/tools/gen_jarjar_test.py b/tools/gen_jarjar_test.py
index f5bf499..12038e9 100644
--- a/tools/gen_jarjar_test.py
+++ b/tools/gen_jarjar_test.py
@@ -84,6 +84,31 @@
'rule test.utils.TestUtilClass$TestInnerClassTest jarjar.prefix.@0\n',
'rule test.utils.TestUtilClass$TestInnerClassTest$* jarjar.prefix.@0\n'], lines)
+ def test_gen_rules_repeated_testclass_excluded(self):
+ args = gen_jarjar.parse_arguments([
+ "jarjar-rules-generator-testjavalib.jar",
+ "--prefix", "jarjar.prefix",
+ "--output", "test-output-rules.txt",
+ "--apistubs", "framework-connectivity.stubs.module_lib.jar",
+ "--unsupportedapi", ":testdata/test-unsupportedappusage.txt",
+ "--excludes", "testdata/test-jarjar-excludes-testclass.txt",
+ ])
+ gen_jarjar.make_jarjar_rules(args)
+
+ with open(args.output) as out:
+ lines = out.readlines()
+
+ self.maxDiff = None
+ self.assertListEqual([
+ 'rule android.net.IpSecTransform jarjar.prefix.@0\n',
+ 'rule test.unsupportedappusage.OtherUnsupportedUsageClass jarjar.prefix.@0\n',
+ 'rule test.unsupportedappusage.OtherUnsupportedUsageClassTest jarjar.prefix.@0\n',
+ 'rule test.unsupportedappusage.OtherUnsupportedUsageClassTest$* jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClass jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClass$TestInnerClass jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClass$TestInnerClassTest jarjar.prefix.@0\n',
+ 'rule test.utils.TestUtilClass$TestInnerClassTest$* jarjar.prefix.@0\n'], lines)
+
if __name__ == '__main__':
# Need verbosity=2 for the test results parser to find results
diff --git a/tools/testdata/test-jarjar-excludes-testclass.txt b/tools/testdata/test-jarjar-excludes-testclass.txt
new file mode 100644
index 0000000..f7cc2cb
--- /dev/null
+++ b/tools/testdata/test-jarjar-excludes-testclass.txt
@@ -0,0 +1,7 @@
+# Test file for excluded classes
+test\.jarj.rexcluded\.JarjarExcludedCla.s
+test\.jarjarexcluded\.JarjarExcludedClass\$TestInnerCl.ss
+
+# Exclude actual test files
+test\.utils\.TestUtilClassTest
+android\.net\.IpSecTransformTest
\ No newline at end of file