Merge "Remove unused function" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 375397b..4cf93a8 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -77,18 +77,6 @@
"name": "libnetworkstats_test"
},
{
- "name": "NetHttpCoverageTests",
- "options": [
- {
- "exclude-annotation": "com.android.testutils.SkipPresubmit"
- },
- {
- // These sometimes take longer than 1 min which is the presubmit timeout
- "exclude-annotation": "androidx.test.filters.LargeTest"
- }
- ]
- },
- {
"name": "CtsTetheringTestLatestSdk",
"options": [
{
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 81e18ab..00d9152 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -69,7 +69,6 @@
import com.android.net.module.util.InterfaceParams;
import com.android.net.module.util.NetworkStackConstants;
import com.android.net.module.util.SharedLog;
-import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.bpf.Tether4Key;
import com.android.net.module.util.bpf.Tether4Value;
@@ -1343,19 +1342,6 @@
pw.decreaseIndent();
}
- private <K extends Struct, V extends Struct> void dumpRawMap(IBpfMap<K, V> map,
- IndentingPrintWriter pw) throws ErrnoException {
- if (map == null) {
- pw.println("No BPF support");
- return;
- }
- if (map.isEmpty()) {
- pw.println("No entries");
- return;
- }
- map.forEach((k, v) -> pw.println(BpfDump.toBase64EncodedString(k, v)));
- }
-
/**
* Dump raw BPF map into the base64 encoded strings "<base64 key>,<base64 value>".
* Allow to dump only one map path once. For test only.
@@ -1375,16 +1361,16 @@
// TODO: dump downstream4 map.
if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_STATS)) {
try (IBpfMap<TetherStatsKey, TetherStatsValue> statsMap = mDeps.getBpfStatsMap()) {
- dumpRawMap(statsMap, pw);
- } catch (ErrnoException | IOException e) {
+ BpfDump.dumpRawMap(statsMap, pw);
+ } catch (IOException e) {
pw.println("Error dumping stats map: " + e);
}
return;
}
if (CollectionUtils.contains(args, DUMPSYS_RAWMAP_ARG_UPSTREAM4)) {
try (IBpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map()) {
- dumpRawMap(upstreamMap, pw);
- } catch (ErrnoException | IOException e) {
+ BpfDump.dumpRawMap(upstreamMap, pw);
+ } catch (IOException e) {
pw.println("Error dumping IPv4 map: " + e);
}
return;
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
index d5d71bc..f8aa69f 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
@@ -22,19 +22,24 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.net.MacAddress;
import android.os.Build;
+import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.ArrayMap;
import com.android.net.module.util.BpfMap;
+import com.android.net.module.util.SingleWriterBpfMap;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -42,10 +47,18 @@
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
import java.io.File;
import java.net.InetAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -61,6 +74,17 @@
private BpfMap<TetherDownstream6Key, Tether6Value> mTestMap;
+ private final boolean mShouldTestSingleWriterMap;
+
+ @Parameterized.Parameters
+ public static Collection<Boolean> shouldTestSingleWriterMap() {
+ return Arrays.asList(true, false);
+ }
+
+ public BpfMapTest(boolean shouldTestSingleWriterMap) {
+ mShouldTestSingleWriterMap = shouldTestSingleWriterMap;
+ }
+
@BeforeClass
public static void setupOnce() {
System.loadLibrary(getTetheringJniLibraryName());
@@ -82,11 +106,16 @@
initTestMap();
}
- private void initTestMap() throws Exception {
- mTestMap = new BpfMap<>(
- TETHER_DOWNSTREAM6_FS_PATH,
- TetherDownstream6Key.class, Tether6Value.class);
+ private BpfMap<TetherDownstream6Key, Tether6Value> openTestMap() throws Exception {
+ return mShouldTestSingleWriterMap
+ ? new SingleWriterBpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, TetherDownstream6Key.class,
+ Tether6Value.class)
+ : new BpfMap<>(TETHER_DOWNSTREAM6_FS_PATH, TetherDownstream6Key.class,
+ Tether6Value.class);
+ }
+ private void initTestMap() throws Exception {
+ mTestMap = openTestMap();
mTestMap.forEach((key, value) -> {
try {
assertTrue(mTestMap.deleteEntry(key));
@@ -357,6 +386,25 @@
}
@Test
+ public void testMapContentsCorrectOnOpen() throws Exception {
+ final BpfMap<TetherDownstream6Key, Tether6Value> map1, map2;
+
+ map1 = openTestMap();
+ map1.clear();
+ for (int i = 0; i < mTestData.size(); i++) {
+ map1.insertEntry(mTestData.keyAt(i), mTestData.valueAt(i));
+ }
+
+ // We can't close and reopen map1, because close does nothing. Open another map instead.
+ map2 = openTestMap();
+ for (int i = 0; i < mTestData.size(); i++) {
+ assertEquals(mTestData.valueAt(i), map2.getValue(mTestData.keyAt(i)));
+ }
+
+ map1.clear();
+ }
+
+ @Test
public void testInsertOverflow() throws Exception {
final ArrayMap<TetherDownstream6Key, Tether6Value> testData =
new ArrayMap<>();
@@ -396,8 +444,18 @@
}
}
- private static int getNumOpenFds() {
- return new File("/proc/" + Os.getpid() + "/fd").listFiles().length;
+ private static int getNumOpenBpfMapFds() throws Exception {
+ int numFds = 0;
+ File[] openFiles = new File("/proc/self/fd").listFiles();
+ for (int i = 0; i < openFiles.length; i++) {
+ final Path path = openFiles[i].toPath();
+ if (!Files.isSymbolicLink(path)) continue;
+ if ("anon_inode:bpf-map".equals(Files.readSymbolicLink(path).toString())) {
+ numFds++;
+ }
+ }
+ assertNotEquals("Couldn't find any BPF map fds opened by this process", 0, numFds);
+ return numFds;
}
@Test
@@ -406,7 +464,7 @@
// cache, expect that the fd amount is not increased in the iterations.
// See the comment of BpfMap#close.
final int iterations = 1000;
- final int before = getNumOpenFds();
+ final int before = getNumOpenBpfMapFds();
for (int i = 0; i < iterations; i++) {
try (BpfMap<TetherDownstream6Key, Tether6Value> map = new BpfMap<>(
TETHER_DOWNSTREAM6_FS_PATH,
@@ -414,15 +472,74 @@
// do nothing
}
}
- final int after = getNumOpenFds();
+ final int after = getNumOpenBpfMapFds();
// Check that the number of open fds is the same as before.
- // If this exact match becomes flaky, we probably need to distinguish that fd is belong
- // to "bpf-map".
- // ex:
- // $ adb shell ls -all /proc/16196/fd
- // [..] network_stack 64 2022-07-26 22:01:02.300002956 +0800 749 -> anon_inode:bpf-map
- // [..] network_stack 64 2022-07-26 22:01:02.188002956 +0800 75 -> anon_inode:[eventfd]
assertEquals("Fd leak after " + iterations + " iterations: ", before, after);
}
+
+ @Test
+ public void testNullKey() {
+ assertThrows(NullPointerException.class, () ->
+ mTestMap.insertOrReplaceEntry(null, mTestData.valueAt(0)));
+ }
+
+ private void runBenchmarkThread(BpfMap<TetherDownstream6Key, Tether6Value> map,
+ CompletableFuture<Integer> future, int runtimeMs) {
+ int numReads = 0;
+ final Random r = new Random();
+ final long start = SystemClock.elapsedRealtime();
+ final long stop = start + runtimeMs;
+ while (SystemClock.elapsedRealtime() < stop) {
+ try {
+ final Tether6Value v = map.getValue(mTestData.keyAt(r.nextInt(mTestData.size())));
+ assertNotNull(v);
+ numReads++;
+ } catch (Exception e) {
+ future.completeExceptionally(e);
+ return;
+ }
+ }
+ future.complete(numReads);
+ }
+
+ @Test
+ public void testSingleWriterCacheEffectiveness() throws Exception {
+ assumeTrue(mShouldTestSingleWriterMap);
+
+ // Ensure the map is not empty.
+ for (int i = 0; i < mTestData.size(); i++) {
+ mTestMap.insertEntry(mTestData.keyAt(i), mTestData.valueAt(i));
+ }
+
+ // Benchmark parameters.
+ final int timeoutMs = 5_000; // Only hit if threads don't complete.
+ final int benchmarkTimeMs = 2_000;
+ final int minReads = 50;
+ // Local testing on cuttlefish suggests that caching is ~10x faster.
+ // Only require 3x to reduce test flakiness.
+ final int expectedSpeedup = 3;
+
+ final BpfMap cachedMap = new SingleWriterBpfMap(TETHER_DOWNSTREAM6_FS_PATH,
+ TetherDownstream6Key.class, Tether6Value.class);
+ final BpfMap uncachedMap = new BpfMap(TETHER_DOWNSTREAM6_FS_PATH,
+ TetherDownstream6Key.class, Tether6Value.class);
+
+ final CompletableFuture<Integer> cachedResult = new CompletableFuture<>();
+ final CompletableFuture<Integer> uncachedResult = new CompletableFuture<>();
+
+ new Thread(() -> runBenchmarkThread(uncachedMap, uncachedResult, benchmarkTimeMs)).start();
+ new Thread(() -> runBenchmarkThread(cachedMap, cachedResult, benchmarkTimeMs)).start();
+
+ final int cached = cachedResult.get(timeoutMs, TimeUnit.MILLISECONDS);
+ final int uncached = uncachedResult.get(timeoutMs, TimeUnit.MILLISECONDS);
+
+ // Uncomment to see benchmark results.
+ // fail("Cached " + cached + ", uncached " + uncached + ": " + cached / uncached +"x");
+
+ assertTrue("Less than " + minReads + "cached reads observed", cached > minReads);
+ assertTrue("Less than " + minReads + "uncached reads observed", uncached > minReads);
+ assertTrue("Cached map not at least " + expectedSpeedup + "x faster",
+ cached > expectedSpeedup * uncached);
+ }
}
diff --git a/common/flags.aconfig b/common/flags.aconfig
index 6c3e89d..bc4168b 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -107,3 +107,11 @@
description: "Flag for BLOCKED_REASON_NETWORK_RESTRICTED API"
bug: "339559837"
}
+
+flag {
+ name: "net_capability_not_bandwidth_constrained"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED API"
+ bug: "343823469"
+}
diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java
index 48d40e6..b21e22a 100644
--- a/framework-t/src/android/net/nsd/NsdManager.java
+++ b/framework-t/src/android/net/nsd/NsdManager.java
@@ -1782,16 +1782,16 @@
if (!hasServiceName && !hasServiceType && serviceInfo.getPort() == 0) {
return ServiceValidationType.NO_SERVICE;
}
- if (hasServiceName && hasServiceType) {
- if (serviceInfo.getPort() < 0) {
- throw new IllegalArgumentException("Invalid port");
- }
- if (serviceInfo.getPort() == 0) {
- return ServiceValidationType.HAS_SERVICE_ZERO_PORT;
- }
- return ServiceValidationType.HAS_SERVICE;
+ if (!hasServiceName || !hasServiceType) {
+ throw new IllegalArgumentException("The service name or the service type is missing");
}
- throw new IllegalArgumentException("The service name or the service type is missing");
+ if (serviceInfo.getPort() < 0) {
+ throw new IllegalArgumentException("Invalid port");
+ }
+ if (serviceInfo.getPort() == 0) {
+ return ServiceValidationType.HAS_SERVICE_ZERO_PORT;
+ }
+ return ServiceValidationType.HAS_SERVICE;
}
/**
diff --git a/framework/api/current.txt b/framework/api/current.txt
index ef8415c..7bc0cf3 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -337,6 +337,7 @@
field public static final int NET_CAPABILITY_MCX = 23; // 0x17
field public static final int NET_CAPABILITY_MMS = 0; // 0x0
field public static final int NET_CAPABILITY_MMTEL = 33; // 0x21
+ field @FlaggedApi("com.android.net.flags.net_capability_not_bandwidth_constrained") public static final int NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED = 37; // 0x25
field public static final int NET_CAPABILITY_NOT_CONGESTED = 20; // 0x14
field public static final int NET_CAPABILITY_NOT_METERED = 11; // 0xb
field public static final int NET_CAPABILITY_NOT_RESTRICTED = 13; // 0xd
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index 0b37fa5..4eaf973 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -28,6 +28,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresApi;
@@ -1207,6 +1208,16 @@
})
public @interface FirewallRule {}
+ /** @hide */
+ public static final long FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS = 1L;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @LongDef(flag = true, prefix = "FEATURE_", value = {
+ FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS
+ })
+ public @interface ConnectivityManagerFeature {}
+
/**
* A kludge to facilitate static access where a Context pointer isn't available, like in the
* case of the static set/getProcessDefaultNetwork methods and from the Network class.
@@ -1220,6 +1231,14 @@
@GuardedBy("mTetheringEventCallbacks")
private TetheringManager mTetheringManager;
+ private final Object mEnabledConnectivityManagerFeaturesLock = new Object();
+ // mEnabledConnectivityManagerFeatures is lazy-loaded in this ConnectivityManager instance, but
+ // fetched from ConnectivityService, where it is loaded in ConnectivityService startup, so it
+ // should have consistent values.
+ @GuardedBy("sEnabledConnectivityManagerFeaturesLock")
+ @ConnectivityManagerFeature
+ private Long mEnabledConnectivityManagerFeatures = null;
+
private TetheringManager getTetheringManager() {
synchronized (mTetheringEventCallbacks) {
if (mTetheringManager == null) {
@@ -4529,6 +4548,20 @@
return request;
}
+ private boolean isFeatureEnabled(@ConnectivityManagerFeature long connectivityManagerFeature) {
+ synchronized (mEnabledConnectivityManagerFeaturesLock) {
+ if (mEnabledConnectivityManagerFeatures == null) {
+ try {
+ mEnabledConnectivityManagerFeatures =
+ mService.getEnabledConnectivityManagerFeatures();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ return (mEnabledConnectivityManagerFeatures & connectivityManagerFeature) != 0;
+ }
+ }
+
private NetworkRequest sendRequestForNetwork(NetworkCapabilities need, NetworkCallback callback,
int timeoutMs, NetworkRequest.Type reqType, int legacyType, CallbackHandler handler) {
return sendRequestForNetwork(Process.INVALID_UID, need, callback, timeoutMs, reqType,
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index 55c7085..f9de8ed 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -259,4 +259,6 @@
void setTestLowTcpPollingTimerForKeepalive(long timeMs);
IBinder getRoutingCoordinatorService();
+
+ long getEnabledConnectivityManagerFeatures();
}
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index 45efbfe..6a14bde 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -36,9 +36,11 @@
import android.os.Process;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.Log;
import android.util.Range;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BitUtils;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.NetworkCapabilitiesUtils;
@@ -134,6 +136,8 @@
"com.android.net.flags.request_restricted_wifi";
static final String SUPPORT_TRANSPORT_SATELLITE =
"com.android.net.flags.support_transport_satellite";
+ static final String NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED =
+ "com.android.net.flags.net_capability_not_bandwidth_constrained";
}
/**
@@ -458,6 +462,7 @@
NET_CAPABILITY_PRIORITIZE_LATENCY,
NET_CAPABILITY_PRIORITIZE_BANDWIDTH,
NET_CAPABILITY_LOCAL_NETWORK,
+ NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED,
})
public @interface NetCapability { }
@@ -740,7 +745,26 @@
@FlaggedApi(Flags.FLAG_NET_CAPABILITY_LOCAL_NETWORK)
public static final int NET_CAPABILITY_LOCAL_NETWORK = 36;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_LOCAL_NETWORK;
+ /**
+ * Indicates that this is not a bandwidth-constrained network.
+ *
+ * Starting from {@link Build.VERSION_CODES.VANILLA_ICE_CREAM}, this capability is by default
+ * set in {@link NetworkRequest}s and true for most networks.
+ *
+ * If a network lacks this capability, it is bandwidth-constrained. Bandwidth constrained
+ * networks cannot support high-bandwidth data transfers and applications that request and use
+ * them must ensure that they limit bandwidth usage to below the values returned by
+ * {@link #getLinkDownstreamBandwidthKbps()} and {@link #getLinkUpstreamBandwidthKbps()} and
+ * limit the frequency of their network usage. If applications perform high-bandwidth data
+ * transfers on constrained networks or perform network access too frequently, the system may
+ * block the app's access to the network. The system may take other measures to reduce network
+ * usage on constrained networks, such as disabling network access to apps that are not in the
+ * foreground.
+ */
+ @FlaggedApi(Flags.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)
+ public static final int NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED = 37;
+
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED;
// Set all bits up to the MAX_NET_CAPABILITY-th bit
private static final long ALL_VALID_CAPABILITIES = (2L << MAX_NET_CAPABILITY) - 1;
@@ -784,10 +808,17 @@
/**
* Capabilities that are set by default when the object is constructed.
*/
- private static final long DEFAULT_CAPABILITIES =
- (1L << NET_CAPABILITY_NOT_RESTRICTED) |
- (1L << NET_CAPABILITY_TRUSTED) |
- (1L << NET_CAPABILITY_NOT_VPN);
+ private static final long DEFAULT_CAPABILITIES;
+ static {
+ long defaultCapabilities =
+ (1L << NET_CAPABILITY_NOT_RESTRICTED)
+ | (1L << NET_CAPABILITY_TRUSTED)
+ | (1L << NET_CAPABILITY_NOT_VPN);
+ if (SdkLevel.isAtLeastV()) {
+ defaultCapabilities |= (1L << NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+ }
+ DEFAULT_CAPABILITIES = defaultCapabilities;
+ }
/**
* Capabilities that are managed by ConnectivityService.
@@ -814,7 +845,9 @@
(1L << NET_CAPABILITY_NOT_ROAMING) |
(1L << NET_CAPABILITY_NOT_CONGESTED) |
(1L << NET_CAPABILITY_NOT_SUSPENDED) |
- (1L << NET_CAPABILITY_NOT_VCN_MANAGED);
+ (1L << NET_CAPABILITY_NOT_VCN_MANAGED) |
+ (1L << NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
+
/**
* Extra allowed capabilities for test networks that do not have TRANSPORT_CELLULAR. Test
@@ -843,7 +876,10 @@
// If the given capability was previously added to the list of forbidden capabilities
// then the capability will also be removed from the list of forbidden capabilities.
// TODO: Add forbidden capabilities to the public API
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG, "addCapability is called with invalid capability: " + capability);
+ return this;
+ }
mNetworkCapabilities |= 1L << capability;
// remove from forbidden capability list
mForbiddenNetworkCapabilities &= ~(1L << capability);
@@ -864,7 +900,10 @@
* @hide
*/
public void addForbiddenCapability(@NetCapability int capability) {
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG, "addForbiddenCapability is called with invalid capability: " + capability);
+ return;
+ }
mForbiddenNetworkCapabilities |= 1L << capability;
mNetworkCapabilities &= ~(1L << capability); // remove from requested capabilities
}
@@ -878,7 +917,10 @@
* @hide
*/
public @NonNull NetworkCapabilities removeCapability(@NetCapability int capability) {
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG, "removeCapability is called with invalid capability: " + capability);
+ return this;
+ }
final long mask = ~(1L << capability);
mNetworkCapabilities &= mask;
return this;
@@ -893,7 +935,11 @@
* @hide
*/
public @NonNull NetworkCapabilities removeForbiddenCapability(@NetCapability int capability) {
- checkValidCapability(capability);
+ if (!isValidCapability(capability)) {
+ Log.e(TAG,
+ "removeForbiddenCapability is called with invalid capability: " + capability);
+ return this;
+ }
mForbiddenNetworkCapabilities &= ~(1L << capability);
return this;
}
@@ -2589,6 +2635,7 @@
case NET_CAPABILITY_PRIORITIZE_LATENCY: return "PRIORITIZE_LATENCY";
case NET_CAPABILITY_PRIORITIZE_BANDWIDTH: return "PRIORITIZE_BANDWIDTH";
case NET_CAPABILITY_LOCAL_NETWORK: return "LOCAL_NETWORK";
+ case NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED: return "NOT_BANDWIDTH_CONSTRAINED";
default: return Integer.toString(capability);
}
}
@@ -2632,12 +2679,6 @@
return capability >= 0 && capability <= MAX_NET_CAPABILITY;
}
- private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) {
- if (!isValidCapability(capability)) {
- throw new IllegalArgumentException("NetworkCapability " + capability + " out of range");
- }
- }
-
private static boolean isValidEnterpriseId(
@NetworkCapabilities.EnterpriseId int enterpriseId) {
return enterpriseId >= NET_ENTERPRISE_ID_1
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index f7600b2..502ac6f 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -20,6 +20,7 @@
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_NOT_BANDWIDTH_CONSTRAINED;
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;
@@ -286,7 +287,8 @@
NET_CAPABILITY_PARTIAL_CONNECTIVITY,
NET_CAPABILITY_TEMPORARILY_NOT_METERED,
NET_CAPABILITY_TRUSTED,
- NET_CAPABILITY_VALIDATED);
+ NET_CAPABILITY_VALIDATED,
+ NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
private final NetworkCapabilities mNetworkCapabilities;
diff --git a/framework/src/android/net/apf/ApfCapabilities.java b/framework/src/android/net/apf/ApfCapabilities.java
index 8efb182..6b18629 100644
--- a/framework/src/android/net/apf/ApfCapabilities.java
+++ b/framework/src/android/net/apf/ApfCapabilities.java
@@ -112,12 +112,12 @@
/**
* Determines whether the APF interpreter advertises support for the data buffer access opcodes
* LDDW (LoaD Data Word) and STDW (STore Data Word). Full LDDW (LoaD Data Word) and
- * STDW (STore Data Word) support is present from APFv4 on.
+ * STDW (STore Data Word) support is present from APFv3 on.
*
* @return {@code true} if the IWifiStaIface#readApfPacketFilterData is supported.
*/
public boolean hasDataAccess() {
- return apfVersionSupported >= 4;
+ return apfVersionSupported > 2;
}
/**
diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
index c726dab..51df8ab 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -121,6 +121,20 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
public static final long NETWORK_BLOCKED_WITHOUT_INTERNET_PERMISSION = 333340911L;
+ /**
+ * Enable caching for TrafficStats#get* APIs.
+ *
+ * Apps targeting Android V or later or running on Android V or later may take up to several
+ * seconds to see the updated results.
+ * Apps targeting lower android SDKs do not see cached result for backward compatibility,
+ * results of TrafficStats#get* APIs are reflecting network statistics immediately.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE = 74210811L;
+
private ConnectivityCompatChanges() {
}
}
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
index 832ac03..1e36676 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
@@ -28,9 +28,7 @@
import static org.junit.Assume.assumeTrue;
import android.app.UiAutomation;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothManager;
-import android.bluetooth.cts.BTAdapterUtils;
+import android.bluetooth.test_utils.EnableBluetoothRule;
import android.content.Context;
import android.location.LocationManager;
import android.nearby.BroadcastCallback;
@@ -58,6 +56,7 @@
import com.android.modules.utils.build.SdkLevel;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -76,6 +75,9 @@
@RunWith(AndroidJUnit4.class)
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public class NearbyManagerTest {
+
+ @ClassRule public static final EnableBluetoothRule sEnableBluetooth = new EnableBluetoothRule();
+
private static final byte[] SALT = new byte[]{1, 2};
private static final byte[] SECRET_ID = new byte[]{1, 2, 3, 4};
private static final byte[] META_DATA_ENCRYPTION_KEY = new byte[14];
@@ -128,8 +130,6 @@
mContext = InstrumentationRegistry.getContext();
mNearbyManager = mContext.getSystemService(NearbyManager.class);
-
- enableBluetooth();
}
@Test
@@ -281,14 +281,6 @@
assertThrows(SecurityException.class, () -> mNearbyManager.getPoweredOffFindingMode());
}
- private void enableBluetooth() {
- BluetoothManager manager = mContext.getSystemService(BluetoothManager.class);
- BluetoothAdapter bluetoothAdapter = manager.getAdapter();
- if (!bluetoothAdapter.isEnabled()) {
- assertThat(BTAdapterUtils.enableAdapter(bluetoothAdapter, mContext)).isTrue();
- }
- }
-
private void enableLocation() {
LocationManager locationManager = mContext.getSystemService(LocationManager.class);
UserHandle user = Process.myUserHandle();
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index dc67f83..80df552 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -54,6 +54,7 @@
namespace android {
namespace bpf {
+using base::StartsWith;
using base::EndsWith;
using std::string;
@@ -228,6 +229,30 @@
return !access("/metadata/gsi/dsu/booted", F_OK);
}
+static bool hasGSM() {
+ static string ph = base::GetProperty("gsm.current.phone-type", "");
+ static bool gsm = (ph != "");
+ static bool logged = false;
+ if (!logged) {
+ logged = true;
+ ALOGI("hasGSM(gsm.current.phone-type='%s'): %s", ph.c_str(), gsm ? "true" : "false");
+ }
+ return gsm;
+}
+
+static bool isTV() {
+ if (hasGSM()) return false; // TVs don't do GSM
+
+ static string key = base::GetProperty("ro.oem.key1", "");
+ static bool tv = StartsWith(key, "ATV00");
+ static bool logged = false;
+ if (!logged) {
+ logged = true;
+ ALOGI("isTV(ro.oem.key1='%s'): %s.", key.c_str(), tv ? "true" : "false");
+ }
+ return tv;
+}
+
static int doLoad(char** argv, char * const envp[]) {
const int device_api_level = android_get_device_api_level();
const bool isAtLeastT = (device_api_level >= __ANDROID_API_T__);
@@ -261,24 +286,36 @@
return 1;
}
+ // both S and T require kernel 4.9 (and eBpf support)
if (isAtLeastT && !isAtLeastKernelVersion(4, 9, 0)) {
ALOGE("Android T requires kernel 4.9.");
return 1;
}
+ // U bumps the kernel requirement up to 4.14
if (isAtLeastU && !isAtLeastKernelVersion(4, 14, 0)) {
ALOGE("Android U requires kernel 4.14.");
return 1;
}
+ // V bumps the kernel requirement up to 4.19
+ // see also: //system/netd/tests/kernel_test.cpp TestKernel419
if (isAtLeastV && !isAtLeastKernelVersion(4, 19, 0)) {
ALOGE("Android V requires kernel 4.19.");
return 1;
}
- if (isAtLeastV && isX86() && !isKernel64Bit()) {
+ // Technically already required by U, but only enforce on V+
+ // see also: //system/netd/tests/kernel_test.cpp TestKernel64Bit
+ if (isAtLeastV && isKernel32Bit() && isAtLeastKernelVersion(5, 16, 0)) {
+ ALOGE("Android V+ platform with 32 bit kernel version >= 5.16.0 is unsupported");
+ if (!isTV()) return 1;
+ }
+
+ // Various known ABI layout issues, particularly wrt. bpf and ipsec/xfrm.
+ if (isAtLeastV && isKernel32Bit() && isX86()) {
ALOGE("Android V requires X86 kernel to be 64-bit.");
- return 1;
+ if (!isTV()) return 1;
}
if (isAtLeastV) {
@@ -306,7 +343,6 @@
if (bad && !isGSI()) {
ALOGE("Unsupported kernel version (%07x).", kernelVersion());
- sleep(60);
}
}
@@ -326,12 +362,16 @@
* Some of these have userspace or kernel workarounds/hacks.
* Some of them don't...
* We're going to be removing the hacks.
+ * (for example "ANDROID: xfrm: remove in_compat_syscall() checks").
+ * Note: this check/enforcement only applies to *system* userspace code,
+ * it does not affect unprivileged apps, the 32-on-64 compatibility
+ * problems are AFAIK limited to various CAP_NET_ADMIN protected interfaces.
*
* Additionally the 32-bit kernel jit support is poor,
* and 32-bit userspace on 64-bit kernel bpf ringbuffer compatibility is broken.
*/
ALOGE("64-bit userspace required on 6.2+ kernels.");
- return 1;
+ if (!isTV()) return 1;
}
// Ensure we can determine the Android build type.
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index 91fec90..925ee50 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -85,31 +85,6 @@
return Status("U+ platform with kernel version < 4.14.0 is unsupported");
}
- if (modules::sdklevel::IsAtLeastV()) {
- // V bumps the kernel requirement up to 4.19
- // see also: //system/netd/tests/kernel_test.cpp TestKernel419
- if (!bpf::isAtLeastKernelVersion(4, 19, 0)) {
- return Status("V+ platform with kernel version < 4.19.0 is unsupported");
- }
-
- // Technically already required by U, but only enforce on V+
- // see also: //system/netd/tests/kernel_test.cpp TestKernel64Bit
- if (bpf::isKernel32Bit() && bpf::isAtLeastKernelVersion(5, 16, 0)) {
- return Status("V+ platform with 32 bit kernel, version >= 5.16.0 is unsupported");
- }
- }
-
- // Linux 6.1 is highest version supported by U, starting with V new kernels,
- // ie. 6.2+ we are dropping various kernel/system userspace 32-on-64 hacks
- // (for example "ANDROID: xfrm: remove in_compat_syscall() checks").
- // Note: this check/enforcement only applies to *system* userspace code,
- // it does not affect unprivileged apps, the 32-on-64 compatibility
- // problems are AFAIK limited to various CAP_NET_ADMIN protected interfaces.
- // see also: //system/bpf/bpfloader/BpfLoader.cpp main()
- if (bpf::isUserspace32bit() && bpf::isAtLeastKernelVersion(6, 2, 0)) {
- return Status("32 bit userspace with Kernel version >= 6.2.0 is unsupported");
- }
-
// U mandates this mount point (though it should also be the case on T)
if (modules::sdklevel::IsAtLeastU() && !!strcmp(cg2_path, "/sys/fs/cgroup")) {
return Status("U+ platform with cg2_path != /sys/fs/cgroup is unsupported");
diff --git a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
index 385adc6..a92dfaf 100644
--- a/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
+++ b/service-t/src/com/android/metrics/NetworkNsdReportedMetrics.java
@@ -193,7 +193,8 @@
* @param sentQueryCount The count of sent queries before stop discovery.
*/
public void reportServiceDiscoveryStop(boolean isLegacy, int transactionId, long durationMs,
- int foundCallbackCount, int lostCallbackCount, int servicesCount, int sentQueryCount) {
+ int foundCallbackCount, int lostCallbackCount, int servicesCount, int sentQueryCount,
+ boolean isServiceFromCache) {
final Builder builder = makeReportedBuilder(isLegacy, transactionId);
builder.setType(NsdEventType.NET_DISCOVER);
builder.setQueryResult(MdnsQueryResult.MQR_SERVICE_DISCOVERY_STOP);
@@ -202,6 +203,7 @@
builder.setLostCallbackCount(lostCallbackCount);
builder.setFoundServiceCount(servicesCount);
builder.setSentQueryCount(sentQueryCount);
+ builder.setIsKnownService(isServiceFromCache);
mDependencies.statsWrite(builder.build());
}
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 0a8adf0..f8b0d53 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -1634,6 +1634,12 @@
NsdManager.nameOf(code), transactionId));
switch (code) {
case NsdManager.SERVICE_FOUND:
+ // Set the ServiceFromCache flag only if the service is actually being
+ // retrieved from the cache. This flag should not be overridden by later
+ // service found event, which may not be cached.
+ if (event.mIsServiceFromCache) {
+ request.setServiceFromCache(true);
+ }
clientInfo.onServiceFound(clientRequestId, info, request);
break;
case NsdManager.SERVICE_LOST:
@@ -2887,7 +2893,8 @@
request.getFoundServiceCount(),
request.getLostServiceCount(),
request.getServicesCount(),
- request.getSentQueryCount());
+ request.getSentQueryCount(),
+ request.isServiceFromCache());
} else if (listener instanceof ResolutionListener) {
mMetrics.reportServiceResolutionStop(false /* isLegacy */, transactionId,
request.calculateRequestDurationMs(mClock.elapsedRealtime()),
@@ -2927,7 +2934,8 @@
request.getFoundServiceCount(),
request.getLostServiceCount(),
request.getServicesCount(),
- NO_SENT_QUERY_COUNT);
+ NO_SENT_QUERY_COUNT,
+ request.isServiceFromCache());
break;
case NsdManager.RESOLVE_SERVICE:
stopResolveService(transactionId);
@@ -3050,7 +3058,8 @@
request.getFoundServiceCount(),
request.getLostServiceCount(),
request.getServicesCount(),
- request.getSentQueryCount());
+ request.getSentQueryCount(),
+ request.isServiceFromCache());
try {
mCb.onStopDiscoverySucceeded(listenerKey);
} catch (RemoteException e) {
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 8305c1e..5323392 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -57,6 +57,7 @@
import static android.net.TrafficStats.TYPE_TX_PACKETS;
import static android.net.TrafficStats.UID_TETHERING;
import static android.net.TrafficStats.UNSUPPORTED;
+import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
@@ -91,6 +92,7 @@
import android.app.AlarmManager;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
+import android.app.compat.CompatChanges;
import android.app.usage.NetworkStatsManager;
import android.content.ApexEnvironment;
import android.content.BroadcastReceiver;
@@ -472,7 +474,9 @@
private final TrafficStatsRateLimitCache mTrafficStatsUidCache;
static final String TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG =
"trafficstats_rate_limit_cache_enabled_flag";
- private final boolean mSupportTrafficStatsRateLimitCache;
+ private final boolean mAlwaysUseTrafficStatsRateLimitCache;
+ private final int mTrafficStatsRateLimitCacheExpiryDuration;
+ private final int mTrafficStatsRateLimitCacheMaxEntries;
private final Object mOpenSessionCallsLock = new Object();
@@ -663,15 +667,18 @@
mEventLogger = null;
}
- final long cacheExpiryDurationMs = mDeps.getTrafficStatsRateLimitCacheExpiryDuration();
- final int cacheMaxEntries = mDeps.getTrafficStatsRateLimitCacheMaxEntries();
- mSupportTrafficStatsRateLimitCache = mDeps.supportTrafficStatsRateLimitCache(mContext);
+ mAlwaysUseTrafficStatsRateLimitCache =
+ mDeps.alwaysUseTrafficStatsRateLimitCache(mContext);
+ mTrafficStatsRateLimitCacheExpiryDuration =
+ mDeps.getTrafficStatsRateLimitCacheExpiryDuration();
+ mTrafficStatsRateLimitCacheMaxEntries =
+ mDeps.getTrafficStatsRateLimitCacheMaxEntries();
mTrafficStatsTotalCache = new TrafficStatsRateLimitCache(mClock,
- cacheExpiryDurationMs, cacheMaxEntries);
+ mTrafficStatsRateLimitCacheExpiryDuration, mTrafficStatsRateLimitCacheMaxEntries);
mTrafficStatsIfaceCache = new TrafficStatsRateLimitCache(mClock,
- cacheExpiryDurationMs, cacheMaxEntries);
+ mTrafficStatsRateLimitCacheExpiryDuration, mTrafficStatsRateLimitCacheMaxEntries);
mTrafficStatsUidCache = new TrafficStatsRateLimitCache(mClock,
- cacheExpiryDurationMs, cacheMaxEntries);
+ mTrafficStatsRateLimitCacheExpiryDuration, mTrafficStatsRateLimitCacheMaxEntries);
// TODO: Remove bpfNetMaps creation and always start SkDestroyListener
// Following code is for the experiment to verify the SkDestroyListener refactoring. Based
@@ -920,13 +927,14 @@
}
/**
- * Get whether TrafficStats rate-limit cache is supported.
+ * Get whether TrafficStats rate-limit cache is always applied.
*
* This method should only be called once in the constructor,
* to ensure that the code does not need to deal with flag values changing at runtime.
*/
- public boolean supportTrafficStatsRateLimitCache(@NonNull Context ctx) {
- return false;
+ public boolean alwaysUseTrafficStatsRateLimitCache(@NonNull Context ctx) {
+ return SdkLevel.isAtLeastV() && DeviceConfigUtils.isTetheringFeatureNotChickenedOut(
+ ctx, TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG);
}
/**
@@ -954,6 +962,13 @@
}
/**
+ * Wrapper method for {@link CompatChanges#isChangeEnabled(long, int)}
+ */
+ public boolean isChangeEnabled(final long changeId, final int uid) {
+ return CompatChanges.isChangeEnabled(changeId, uid);
+ }
+
+ /**
* Retrieves native network total statistics.
*
* @return A NetworkStats.Entry containing the native statistics, or
@@ -2084,14 +2099,14 @@
}
if (!isEntryValueTypeValid(type)) return UNSUPPORTED;
- if (!mSupportTrafficStatsRateLimitCache) {
- return getEntryValueForType(mDeps.nativeGetUidStat(uid), type);
+ if (mAlwaysUseTrafficStatsRateLimitCache
+ || mDeps.isChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, callingUid)) {
+ final NetworkStats.Entry entry = mTrafficStatsUidCache.getOrCompute(IFACE_ALL, uid,
+ () -> mDeps.nativeGetUidStat(uid));
+ return getEntryValueForType(entry, type);
}
- final NetworkStats.Entry entry = mTrafficStatsUidCache.getOrCompute(IFACE_ALL, uid,
- () -> mDeps.nativeGetUidStat(uid));
-
- return getEntryValueForType(entry, type);
+ return getEntryValueForType(mDeps.nativeGetUidStat(uid), type);
}
@Nullable
@@ -2113,14 +2128,15 @@
Objects.requireNonNull(iface);
if (!isEntryValueTypeValid(type)) return UNSUPPORTED;
- if (!mSupportTrafficStatsRateLimitCache) {
- return getEntryValueForType(getIfaceStatsInternal(iface), type);
+ if (mAlwaysUseTrafficStatsRateLimitCache
+ || mDeps.isChangeEnabled(
+ ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, Binder.getCallingUid())) {
+ final NetworkStats.Entry entry = mTrafficStatsIfaceCache.getOrCompute(iface, UID_ALL,
+ () -> getIfaceStatsInternal(iface));
+ return getEntryValueForType(entry, type);
}
- final NetworkStats.Entry entry = mTrafficStatsIfaceCache.getOrCompute(iface, UID_ALL,
- () -> getIfaceStatsInternal(iface));
-
- return getEntryValueForType(entry, type);
+ return getEntryValueForType(getIfaceStatsInternal(iface), type);
}
private long getEntryValueForType(@Nullable NetworkStats.Entry entry, int type) {
@@ -2166,14 +2182,15 @@
@Override
public long getTotalStats(int type) {
if (!isEntryValueTypeValid(type)) return UNSUPPORTED;
- if (!mSupportTrafficStatsRateLimitCache) {
- return getEntryValueForType(getTotalStatsInternal(), type);
+ if (mAlwaysUseTrafficStatsRateLimitCache
+ || mDeps.isChangeEnabled(
+ ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, Binder.getCallingUid())) {
+ final NetworkStats.Entry entry = mTrafficStatsTotalCache.getOrCompute(
+ IFACE_ALL, UID_ALL, () -> getTotalStatsInternal());
+ return getEntryValueForType(entry, type);
}
- final NetworkStats.Entry entry = mTrafficStatsTotalCache.getOrCompute(IFACE_ALL, UID_ALL,
- () -> getTotalStatsInternal());
-
- return getEntryValueForType(entry, type);
+ return getEntryValueForType(getTotalStatsInternal(), type);
}
@Override
@@ -2937,13 +2954,12 @@
} catch (IOException e) {
pw.println("(failed to dump FastDataInput counters)");
}
- pw.print("trafficstats.cache.supported", mSupportTrafficStatsRateLimitCache);
+ pw.print("trafficstats.cache.alwaysuse", mAlwaysUseTrafficStatsRateLimitCache);
pw.println();
pw.print(TRAFFIC_STATS_CACHE_EXPIRY_DURATION_NAME,
- mDeps.getTrafficStatsRateLimitCacheExpiryDuration());
+ mTrafficStatsRateLimitCacheExpiryDuration);
pw.println();
- pw.print(TRAFFIC_STATS_CACHE_MAX_ENTRIES_NAME,
- mDeps.getTrafficStatsRateLimitCacheMaxEntries());
+ pw.print(TRAFFIC_STATS_CACHE_MAX_ENTRIES_NAME, mTrafficStatsRateLimitCacheMaxEntries);
pw.println();
pw.decreaseIndent();
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 1047232..a30735a 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -72,6 +72,7 @@
import com.android.net.module.util.BpfDump;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap;
+import com.android.net.module.util.SingleWriterBpfMap;
import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.S32;
import com.android.net.module.util.Struct.U32;
@@ -188,7 +189,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U32> getConfigurationMap() {
try {
- return new BpfMap<>(
+ return new SingleWriterBpfMap<>(
CONFIGURATION_MAP_PATH, S32.class, U32.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open netd configuration map", e);
@@ -198,7 +199,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, UidOwnerValue> getUidOwnerMap() {
try {
- return new BpfMap<>(
+ return new SingleWriterBpfMap<>(
UID_OWNER_MAP_PATH, S32.class, UidOwnerValue.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open uid owner map", e);
@@ -208,7 +209,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U8> getUidPermissionMap() {
try {
- return new BpfMap<>(
+ return new SingleWriterBpfMap<>(
UID_PERMISSION_MAP_PATH, S32.class, U8.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open uid permission map", e);
@@ -218,6 +219,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<CookieTagMapKey, CookieTagMapValue> getCookieTagMap() {
try {
+ // Cannot use SingleWriterBpfMap because it's written by ClatCoordinator as well.
return new BpfMap<>(COOKIE_TAG_MAP_PATH,
CookieTagMapKey.class, CookieTagMapValue.class);
} catch (ErrnoException e) {
@@ -228,7 +230,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<S32, U8> getDataSaverEnabledMap() {
try {
- return new BpfMap<>(
+ return new SingleWriterBpfMap<>(
DATA_SAVER_ENABLED_MAP_PATH, S32.class, U8.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open data saver enabled map", e);
@@ -238,7 +240,7 @@
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private static IBpfMap<IngressDiscardKey, IngressDiscardValue> getIngressDiscardMap() {
try {
- return new BpfMap<>(INGRESS_DISCARD_MAP_PATH,
+ return new SingleWriterBpfMap<>(INGRESS_DISCARD_MAP_PATH,
IngressDiscardKey.class, IngressDiscardValue.class);
} catch (ErrnoException e) {
throw new IllegalStateException("Cannot open ingress discard map", e);
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index ca99935..ca2cb15 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -77,6 +77,7 @@
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_BANDWIDTH_CONSTRAINED;
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;
@@ -403,6 +404,8 @@
private static final String NETWORK_ARG = "networks";
private static final String REQUEST_ARG = "requests";
private static final String TRAFFICCONTROLLER_ARG = "trafficcontroller";
+ public static final String CLATEGRESS4RAWBPFMAP_ARG = "clatEgress4RawBpfMap";
+ public static final String CLATINGRESS6RAWBPFMAP_ARG = "clatIngress6RawBpfMap";
private static final boolean DBG = true;
private static final boolean DDBG = Log.isLoggable(TAG, Log.DEBUG);
@@ -487,6 +490,8 @@
private final boolean mRequestRestrictedWifiEnabled;
private final boolean mBackgroundFirewallChainEnabled;
+ private final boolean mUseDeclaredMethodsForCallbacksEnabled;
+
/**
* Uids ConnectivityService tracks blocked status of to send blocked status callbacks.
* Key is uid based on mAsUid of registered networkRequestInfo
@@ -1848,6 +1853,8 @@
&& mDeps.isFeatureEnabled(context, REQUEST_RESTRICTED_WIFI);
mBackgroundFirewallChainEnabled = mDeps.isAtLeastV() && mDeps.isFeatureNotChickenedOut(
context, ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN);
+ mUseDeclaredMethodsForCallbacksEnabled = mDeps.isFeatureEnabled(context,
+ ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS);
mCarrierPrivilegeAuthenticator = mDeps.makeCarrierPrivilegeAuthenticator(
mContext, mTelephonyManager, mRequestRestrictedWifiEnabled,
this::handleUidCarrierPrivilegesLost, mHandler);
@@ -4141,6 +4148,12 @@
boolean verbose = !CollectionUtils.contains(args, SHORT_ARG);
dumpTrafficController(pw, fd, verbose);
return;
+ } else if (CollectionUtils.contains(args, CLATEGRESS4RAWBPFMAP_ARG)) {
+ dumpClatBpfRawMap(pw, true /* isEgress4Map */);
+ return;
+ } else if (CollectionUtils.contains(args, CLATINGRESS6RAWBPFMAP_ARG)) {
+ dumpClatBpfRawMap(pw, false /* isEgress4Map */);
+ return;
}
pw.println("NetworkProviders for:");
@@ -4400,6 +4413,15 @@
}
}
+ private void dumpClatBpfRawMap(IndentingPrintWriter pw, boolean isEgress4Map) {
+ for (NetworkAgentInfo nai : networksSortedById()) {
+ if (nai.clatd != null) {
+ nai.clatd.dumpRawBpfMap(pw, isEgress4Map);
+ break;
+ }
+ }
+ }
+
private void dumpAllRequestInfoLogsToLogcat() {
try (PrintWriter logPw = new PrintWriter(new Writer() {
@Override
@@ -4777,7 +4799,15 @@
final String logMsg = !TextUtils.isEmpty(redirectUrl)
? " with redirect to " + redirectUrl
: "";
- log(nai.toShortString() + " validation " + (valid ? "passed" : "failed") + logMsg);
+ final String statusMsg;
+ if (valid) {
+ statusMsg = "passed";
+ } else if (!TextUtils.isEmpty(redirectUrl)) {
+ statusMsg = "detected a portal";
+ } else {
+ statusMsg = "failed";
+ }
+ log(nai.toShortString() + " validation " + statusMsg + logMsg);
}
if (valid != wasValidated) {
final FullScore oldScore = nai.getScore();
@@ -13257,11 +13287,12 @@
requests.add(createDefaultInternetRequestForTransport(
TYPE_NONE, NetworkRequest.Type.TRACK_DEFAULT));
- // request: restricted Satellite internet
+ // request: Satellite internet, satellite network could be restricted or constrained
final NetworkCapabilities cap = new NetworkCapabilities.Builder()
.addCapability(NET_CAPABILITY_INTERNET)
.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
.removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .removeCapability(NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)
.addTransportType(NetworkCapabilities.TRANSPORT_SATELLITE)
.build();
requests.add(createNetworkRequest(NetworkRequest.Type.REQUEST, cap));
@@ -14020,4 +14051,16 @@
enforceNetworkStackPermission(mContext);
return mRoutingCoordinatorService;
}
+
+ @Override
+ public long getEnabledConnectivityManagerFeatures() {
+ long features = 0;
+ // The bitmask must be built based on final properties initialized in the constructor, to
+ // ensure that it does not change over time and is always consistent between
+ // ConnectivityManager and ConnectivityService.
+ if (mUseDeclaredMethodsForCallbacksEnabled) {
+ features |= ConnectivityManager.FEATURE_USE_DECLARED_METHODS_FOR_CALLBACKS;
+ }
+ return features;
+ }
}
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index 843b7b3..4d39d7d 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -267,6 +267,7 @@
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+ nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED);
nc.setNetworkSpecifier(new TestNetworkSpecifier(iface));
nc.setAdministratorUids(administratorUids);
if (!isMetered) {
diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java
index 634a8fa..d886182 100644
--- a/service/src/com/android/server/connectivity/ClatCoordinator.java
+++ b/service/src/com/android/server/connectivity/ClatCoordinator.java
@@ -41,6 +41,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.BpfDump;
import com.android.net.module.util.BpfMap;
import com.android.net.module.util.IBpfMap;
import com.android.net.module.util.InterfaceParams;
@@ -659,6 +660,8 @@
final int pid = mDeps.startClatd(tunFd.getFileDescriptor(),
readSock6.getFileDescriptor(), writeSock6.getFileDescriptor(), iface, pfx96Str,
v4Str, v6Str);
+ // The file descriptors have been duplicated (dup2) to clatd in native_startClatd().
+ // Close these file descriptor stubs in finally block.
// [6] Initialize and store clatd tracker object.
mClatdTracker = new ClatdTracker(iface, ifIndex, tunIface, tunIfIndex, v4, v6, pfx96,
@@ -677,20 +680,17 @@
Log.e(TAG, "untagSocket cookie " + cookie + " failed: " + e2);
}
}
- try {
- if (tunFd != null) {
- tunFd.close();
- }
- if (readSock6 != null) {
- readSock6.close();
- }
- if (writeSock6 != null) {
- writeSock6.close();
- }
- } catch (IOException e2) {
- Log.e(TAG, "Fail to cleanup fd ", e);
- }
throw new IOException("Failed to start clat ", e);
+ } finally {
+ if (tunFd != null) {
+ tunFd.close();
+ }
+ if (readSock6 != null) {
+ readSock6.close();
+ }
+ if (writeSock6 != null) {
+ writeSock6.close();
+ }
}
}
@@ -795,6 +795,29 @@
}
/**
+ * Dump raw BPF map into base64 encoded strings {@literal "<base64 key>,<base64 value>"}.
+ * Allow to dump only one map in each call. For test only.
+ *
+ * @param pw print writer.
+ * @param isEgress4Map whether to dump the egress4 map (true) or the ingress6 map (false).
+ *
+ * Usage:
+ * $ dumpsys connectivity {clatEgress4RawBpfMap|clatIngress6RawBpfMap}
+ *
+ * Output:
+ * {@literal <base64 encoded key #1>,<base64 encoded value #1>}
+ * {@literal <base64 encoded key #2>,<base64 encoded value #2>}
+ * ..
+ */
+ public void dumpRawMap(@NonNull IndentingPrintWriter pw, boolean isEgress4Map) {
+ if (isEgress4Map) {
+ BpfDump.dumpRawMap(mEgressMap, pw);
+ } else {
+ BpfDump.dumpRawMap(mIngressMap, pw);
+ }
+ }
+
+ /**
* Dump the coordinator information.
*
* @param pw print writer.
diff --git a/service/src/com/android/server/connectivity/ConnectivityFlags.java b/service/src/com/android/server/connectivity/ConnectivityFlags.java
index 7ea7f95..1ee1ed7 100644
--- a/service/src/com/android/server/connectivity/ConnectivityFlags.java
+++ b/service/src/com/android/server/connectivity/ConnectivityFlags.java
@@ -46,6 +46,9 @@
public static final String DELAY_DESTROY_SOCKETS = "delay_destroy_sockets";
+ public static final String USE_DECLARED_METHODS_FOR_CALLBACKS =
+ "use_declared_methods_for_callbacks";
+
private boolean mNoRematchAllRequestsOnRegister;
/**
diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java
index 489dab5..a979681 100644
--- a/service/src/com/android/server/connectivity/Nat464Xlat.java
+++ b/service/src/com/android/server/connectivity/Nat464Xlat.java
@@ -622,6 +622,18 @@
}
}
+ /**
+ * Dump the raw BPF maps in 464XLAT
+ *
+ * @param pw print writer.
+ * @param isEgress4Map whether to dump the egress4 map (true) or the ingress6 map (false).
+ */
+ public void dumpRawBpfMap(IndentingPrintWriter pw, boolean isEgress4Map) {
+ if (SdkLevel.isAtLeastT()) {
+ mClatCoordinator.dumpRawMap(pw, isEgress4Map);
+ }
+ }
+
@Override
public String toString() {
return "mBaseIface: " + mBaseIface + ", mIface: " + mIface + ", mState: " + mState;
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index ede6d3f..e2834b0 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -135,6 +135,7 @@
"device/com/android/net/module/util/BpfUtils.java",
"device/com/android/net/module/util/IBpfMap.java",
"device/com/android/net/module/util/JniUtil.java",
+ "device/com/android/net/module/util/SingleWriterBpfMap.java",
"device/com/android/net/module/util/TcUtils.java",
],
sdk_version: "module_current",
diff --git a/staticlibs/device/com/android/net/module/util/BpfDump.java b/staticlibs/device/com/android/net/module/util/BpfDump.java
index 7549e71..4227194 100644
--- a/staticlibs/device/com/android/net/module/util/BpfDump.java
+++ b/staticlibs/device/com/android/net/module/util/BpfDump.java
@@ -81,6 +81,26 @@
}
/**
+ * Dump the BpfMap entries with base64 format encoding.
+ */
+ public static <K extends Struct, V extends Struct> void dumpRawMap(IBpfMap<K, V> map,
+ PrintWriter pw) {
+ try {
+ if (map == null) {
+ pw.println("BPF map is null");
+ return;
+ }
+ if (map.isEmpty()) {
+ pw.println("No entries");
+ return;
+ }
+ map.forEach((k, v) -> pw.println(toBase64EncodedString(k, v)));
+ } catch (ErrnoException e) {
+ pw.println("Map dump end with error: " + Os.strerror(e.errno));
+ }
+ }
+
+ /**
* Dump the BpfMap name and entries
*/
public static <K extends Struct, V extends Struct> void dumpMap(IBpfMap<K, V> map,
diff --git a/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
new file mode 100644
index 0000000..3eb59d8
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/SingleWriterBpfMap.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import android.os.Build;
+import android.system.ErrnoException;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.util.HashMap;
+import java.util.NoSuchElementException;
+
+/**
+ * Subclass of BpfMap for maps that are only ever written by one userspace writer.
+ *
+ * This class stores all map data in a userspace HashMap in addition to in the BPF map. This makes
+ * reads (but not iterations) much faster because they do not require a system call or converting
+ * the raw map read to the Value struct. See, e.g., b/343166906 .
+ *
+ * Users of this class must ensure that no BPF program ever writes to the map, and that all
+ * userspace writes to the map occur through this object. Other userspace code may still read from
+ * the map; only writes are required to go through this object.
+ *
+ * Reads and writes to this object are thread-safe and internally synchronized. The read and write
+ * methods are synchronized to ensure that current writers always result in a consistent internal
+ * state (without synchronization, two concurrent writes might update the underlying map and the
+ * cache in the opposite order, resulting in the cache being out of sync with the map).
+ *
+ * getNextKey and iteration over the map are not synchronized or cached and always access the
+ * isunderlying map. The values returned by these calls may be temporarily out of sync with the
+ * values read and written through this object.
+ *
+ * TODO: consider caching reads on iterations as well. This is not trivial because the semantics for
+ * iterating BPF maps require passing in the previously-returned key, and Java iterators only
+ * support iterating from the beginning. It could be done by implementing forEach and possibly by
+ * making getFirstKey and getNextKey private (if no callers are using them). Because HashMap is not
+ * thread-safe, implementing forEach would require either making that method synchronized (and
+ * block reads and updates from other threads until iteration is complete) or switching the
+ * internal HashMap to ConcurrentHashMap.
+ *
+ * @param <K> the key of the map.
+ * @param <V> the value of the map.
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public class SingleWriterBpfMap<K extends Struct, V extends Struct> extends BpfMap<K, V> {
+ // HashMap instead of ArrayMap because it performs better on larger maps, and many maps used in
+ // our code can contain hundreds of items.
+ private final HashMap<K, V> mCache = new HashMap<>();
+
+ protected SingleWriterBpfMap(@NonNull final String path, final int flag, final Class<K> key,
+ final Class<V> value) throws ErrnoException, NullPointerException {
+ super(path, flag, key, value);
+
+ if (flag != BPF_F_RDWR) {
+ throw new IllegalArgumentException(
+ "Using " + getClass().getName() + " for read-only maps does not make sense");
+ }
+
+ // Populate cache with the current map contents.
+ K currentKey = super.getFirstKey();
+ while (currentKey != null) {
+ mCache.put(currentKey, super.getValue(currentKey));
+ currentKey = super.getNextKey(currentKey);
+ }
+ }
+
+ public SingleWriterBpfMap(@NonNull final String path, final Class<K> key,
+ final Class<V> value) throws ErrnoException, NullPointerException {
+ this(path, BPF_F_RDWR, key, value);
+ }
+
+ @Override
+ public synchronized void updateEntry(K key, V value) throws ErrnoException {
+ super.updateEntry(key, value);
+ mCache.put(key, value);
+ }
+
+ @Override
+ public synchronized void insertEntry(K key, V value)
+ throws ErrnoException, IllegalStateException {
+ super.insertEntry(key, value);
+ mCache.put(key, value);
+ }
+
+ @Override
+ public synchronized void replaceEntry(K key, V value)
+ throws ErrnoException, NoSuchElementException {
+ super.replaceEntry(key, value);
+ mCache.put(key, value);
+ }
+
+ @Override
+ public synchronized boolean insertOrReplaceEntry(K key, V value) throws ErrnoException {
+ final boolean ret = super.insertOrReplaceEntry(key, value);
+ mCache.put(key, value);
+ return ret;
+ }
+
+ @Override
+ public synchronized boolean deleteEntry(K key) throws ErrnoException {
+ final boolean ret = super.deleteEntry(key);
+ mCache.remove(key);
+ return ret;
+ }
+
+ @Override
+ public synchronized boolean containsKey(@NonNull K key) throws ErrnoException {
+ return mCache.containsKey(key);
+ }
+
+ @Override
+ public synchronized V getValue(@NonNull K key) throws ErrnoException {
+ return mCache.get(key);
+ }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 19d8bbe..319d51a 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -180,6 +180,7 @@
public static final int ICMPV6_NS_HEADER_LEN = 24;
public static final int ICMPV6_NA_HEADER_LEN = 24;
public static final int ICMPV6_ND_OPTION_TLLA_LEN = 8;
+ public static final int ICMPV6_ND_OPTION_SLLA_LEN = 8;
public static final int NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER = 1 << 31;
public static final int NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED = 1 << 30;
diff --git a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
index cb02de8..2a0e8e0 100644
--- a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
+++ b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
@@ -128,20 +128,16 @@
});
}
-inline int mapRetrieve(const char* pathname, uint32_t flag) {
- return bpfFdGet(pathname, flag);
-}
-
inline int mapRetrieveRW(const char* pathname) {
- return mapRetrieve(pathname, 0);
+ return bpfFdGet(pathname, 0);
}
inline int mapRetrieveRO(const char* pathname) {
- return mapRetrieve(pathname, BPF_F_RDONLY);
+ return bpfFdGet(pathname, BPF_F_RDONLY);
}
inline int mapRetrieveWO(const char* pathname) {
- return mapRetrieve(pathname, BPF_F_WRONLY);
+ return bpfFdGet(pathname, BPF_F_WRONLY);
}
inline int retrieveProgram(const char* pathname) {
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
index f76916a..66362d4 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -360,17 +360,29 @@
timeoutMs: Long = defaultTimeoutMs,
errorMsg: String? = null,
test: (T) -> Boolean = { true }
- ) = (poll(timeoutMs) ?: fail("Did not receive ${T::class.simpleName} after ${timeoutMs}ms"))
+ ) = (poll(timeoutMs) ?: failWithErrorReason(errorMsg,
+ "Did not receive ${T::class.simpleName} after ${timeoutMs}ms"))
.also {
- if (it !is T) fail("Expected callback ${T::class.simpleName}, got $it")
+ if (it !is T) {
+ failWithErrorReason(
+ errorMsg,
+ "Expected callback ${T::class.simpleName}, got $it"
+ )
+ }
if (ANY_NETWORK !== network && it.network != network) {
- fail("Expected network $network for callback : $it")
+ failWithErrorReason(errorMsg, "Expected network $network for callback : $it")
}
if (!test(it)) {
- fail("${errorMsg ?: "Callback doesn't match predicate"} : $it")
+ failWithErrorReason(errorMsg, "Callback doesn't match predicate : $it")
}
} as T
+ // "Nothing" is the return type to declare a function never returns a value.
+ fun failWithErrorReason(errorMsg: String?, errorReason: String): Nothing {
+ val message = if (errorMsg != null) "$errorMsg : $errorReason" else errorReason
+ fail(message)
+ }
+
inline fun <reified T : CallbackEntry> expect(
network: HasNetwork,
timeoutMs: Long = defaultTimeoutMs,
diff --git a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
index 600a623..6d03042 100644
--- a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
+++ b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
@@ -61,6 +61,7 @@
setUpdaterNetworkingEnabled(testInfo, enableChain = true,
enablePkgs = UPDATER_PKGS.associateWith { false })
runPreparerApk(testInfo)
+ refreshTime(testInfo)
}
private fun runPreparerApk(testInfo: TestInformation) {
@@ -141,6 +142,14 @@
.contains(":deny")
}
+ private fun refreshTime(testInfo: TestInformation,) {
+ // Forces a synchronous time refresh using the network. Time is fetched synchronously but
+ // this does not guarantee that system time is updated when it returns.
+ // This avoids flakes where the system clock rolls back, for example when using test
+ // settings like test_url_expiration_time in NetworkMonitor.
+ testInfo.exec("cmd network_time_update_service force_refresh")
+ }
+
override fun tearDown(testInfo: TestInformation, e: Throwable?) {
if (isTearDownDisabled) return
installer.tearDown(testInfo, e)
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index 6eb56c7b..0f0e2f1 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -61,6 +61,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
import static android.os.Process.INVALID_UID;
+
import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.modules.utils.build.SdkLevel.isAtLeastV;
@@ -68,6 +69,7 @@
import static com.android.testutils.MiscAsserts.assertEmpty;
import static com.android.testutils.MiscAsserts.assertThrows;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -83,21 +85,25 @@
import android.os.Build;
import android.util.ArraySet;
import android.util.Range;
+
import androidx.test.filters.SmallTest;
+
import com.android.testutils.CompatUtil;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
@SmallTest
@RunWith(DevSdkIgnoreRunner.class)
// NetworkCapabilities is only updatable on S+, and this test covers behavior which implementation
@@ -1465,4 +1471,26 @@
assertEquals("-SUPL-VALIDATED-CAPTIVE_PORTAL+MMS+OEM_PAID",
nc1.describeCapsDifferencesFrom(nc2));
}
+
+ @Test
+ public void testInvalidCapability() {
+ final int invalidCapability = Integer.MAX_VALUE;
+ // Passing invalid capability does not throw
+ final NetworkCapabilities nc1 = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addForbiddenCapability(NET_CAPABILITY_NOT_ROAMING)
+ .removeCapability(invalidCapability)
+ .removeForbiddenCapability(invalidCapability)
+ .addCapability(invalidCapability)
+ .addForbiddenCapability(invalidCapability)
+ .build();
+
+ final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addForbiddenCapability(NET_CAPABILITY_NOT_ROAMING)
+ .build();
+
+ // nc1 and nc2 are the same since invalid capability is ignored
+ assertEquals(nc1, nc2);
+ }
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index 50d6e76..e186c6b 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -22,6 +22,7 @@
import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
import static android.content.pm.PackageManager.FEATURE_WIFI;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
@@ -51,6 +52,7 @@
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.RecorderCallback.CallbackEntry.BLOCKED_STATUS_INT;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -969,19 +971,30 @@
final TestableNetworkCallback otherUidCallback = new TestableNetworkCallback();
final TestableNetworkCallback myUidCallback = new TestableNetworkCallback();
if (SdkLevel.isAtLeastS()) {
- final int otherUid =
- UserHandle.of(5 /* userId */).getUid(Process.FIRST_APPLICATION_UID);
+ // Using the same appId with the test to make sure otherUid has the internet permission.
+ // This works because the UID permission map only stores the app ID and not the whole
+ // UID. If the otherUid does not have the internet permission, network access from
+ // otherUid could be considered blocked on V+.
+ final int appId = UserHandle.getAppId(Process.myUid());
+ final int otherUid = UserHandle.of(5 /* userId */).getUid(appId);
final Handler h = new Handler(Looper.getMainLooper());
runWithShellPermissionIdentity(() -> {
registerSystemDefaultNetworkCallback(systemDefaultCallback, h);
registerDefaultNetworkCallbackForUid(otherUid, otherUidCallback, h);
registerDefaultNetworkCallbackForUid(Process.myUid(), myUidCallback, h);
}, NETWORK_SETTINGS);
- for (TestableNetworkCallback callback :
- List.of(systemDefaultCallback, otherUidCallback, myUidCallback)) {
+ for (TestableNetworkCallback callback : List.of(systemDefaultCallback, myUidCallback)) {
callback.expectAvailableCallbacks(defaultNetwork, false /* suspended */,
true /* validated */, false /* blocked */, TIMEOUT_MS);
}
+ // On V+, ConnectivityService generates blockedReasons based on bpf map contents even if
+ // the otherUid does not exist on device. So if the background chain is enabled,
+ // otherUid is blocked.
+ final boolean isOtherUidBlocked = SdkLevel.isAtLeastV()
+ && runAsShell(NETWORK_SETTINGS, () -> mCM.getFirewallChainEnabled(
+ FIREWALL_CHAIN_BACKGROUND));
+ otherUidCallback.expectAvailableCallbacks(defaultNetwork, false /* suspended */,
+ true /* validated */, isOtherUidBlocked, TIMEOUT_MS);
}
FileDescriptor fd = openSocketFdInOtherApp(TEST_HOST, 80, TIMEOUT_MS);
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index ad25562..12ea23b 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -35,4 +35,5 @@
"general-tests",
"sts",
],
+ sdk_version: "test_current",
}
diff --git a/tests/cts/hostside/networkslicingtestapp/Android.bp b/tests/cts/hostside/networkslicingtestapp/Android.bp
index 100b6e4..c220000 100644
--- a/tests/cts/hostside/networkslicingtestapp/Android.bp
+++ b/tests/cts/hostside/networkslicingtestapp/Android.bp
@@ -44,6 +44,7 @@
"CtsHostsideNetworkCapTestsAppDefaults",
],
manifest: "AndroidManifestWithoutProperty.xml",
+ sdk_version: "test_current",
}
android_test_helper_app {
@@ -53,6 +54,7 @@
"CtsHostsideNetworkCapTestsAppDefaults",
],
manifest: "AndroidManifestWithProperty.xml",
+ sdk_version: "test_current",
}
android_test_helper_app {
@@ -63,4 +65,5 @@
],
target_sdk_version: "33",
manifest: "AndroidManifestWithoutProperty.xml",
+ sdk_version: "test_current",
}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
index f0a87af..cea60f9 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
@@ -18,8 +18,6 @@
import android.platform.test.annotations.RequiresDevice;
-import com.android.testutils.SkipPresubmit;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -37,13 +35,11 @@
uninstallPackage(TEST_APP2_PKG, true);
}
- @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testChangeUnderlyingNetworks() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testChangeUnderlyingNetworks");
}
- @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testDefault() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testDefault");
@@ -166,7 +162,6 @@
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testBlockIncomingPackets");
}
- @SkipPresubmit(reason = "Out of SLO flakiness")
@Test
public void testSetVpnDefaultForUids() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testSetVpnDefaultForUids");
diff --git a/tests/cts/multidevices/Android.bp b/tests/cts/multidevices/Android.bp
index 5ac4229..5f9b3ef 100644
--- a/tests/cts/multidevices/Android.bp
+++ b/tests/cts/multidevices/Android.bp
@@ -14,12 +14,16 @@
package {
default_applicable_licenses: ["Android-Apache-2.0"],
+ default_team: "trendy_team_fwk_core_networking",
}
python_test_host {
name: "CtsConnectivityMultiDevicesTestCases",
main: "connectivity_multi_devices_test.py",
- srcs: ["connectivity_multi_devices_test.py"],
+ srcs: [
+ "connectivity_multi_devices_test.py",
+ "tether_utils.py",
+ ],
libs: [
"mobly",
],
diff --git a/tests/cts/multidevices/connectivity_multi_devices_test.py b/tests/cts/multidevices/connectivity_multi_devices_test.py
index ab88504..417db99 100644
--- a/tests/cts/multidevices/connectivity_multi_devices_test.py
+++ b/tests/cts/multidevices/connectivity_multi_devices_test.py
@@ -1,23 +1,16 @@
# Lint as: python3
"""Connectivity multi devices tests."""
-import base64
import sys
-import uuid
-
-from mobly import asserts
from mobly import base_test
from mobly import test_runner
from mobly import utils
from mobly.controllers import android_device
+import tether_utils
+from tether_utils import UpstreamType
CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE = "com.google.snippet.connectivity"
-class UpstreamType:
- CELLULAR = 1
- WIFI = 2
-
-
class ConnectivityMultiDevicesTest(base_test.BaseTestClass):
def setup_class(self):
@@ -40,66 +33,27 @@
raise_on_exception=True,
)
- @staticmethod
- def generate_uuid32_base64():
- """Generates a UUID32 and encodes it in Base64.
-
- Returns:
- str: The Base64-encoded UUID32 string. Which is 22 characters.
- """
- return base64.b64encode(uuid.uuid1().bytes).decode("utf-8").strip("=")
-
- def _do_test_hotspot_for_upstream_type(self, upstream_type):
- """Test hotspot with the specified upstream type.
-
- This test create a hotspot, make the client connect
- to it, and verify the packet is forwarded by the hotspot.
- """
- server = self.serverDevice.connectivity_multi_devices_snippet
- client = self.clientDevice.connectivity_multi_devices_snippet
-
- # Assert pre-conditions specific to each upstream type.
- asserts.skip_if(not client.hasWifiFeature(), "Client requires Wifi feature")
- asserts.skip_if(
- not server.hasHotspotFeature(), "Server requires hotspot feature"
- )
- if upstream_type == UpstreamType.CELLULAR:
- asserts.skip_if(
- not server.hasTelephonyFeature(), "Server requires Telephony feature"
- )
- server.requestCellularAndEnsureDefault()
- elif upstream_type == UpstreamType.WIFI:
- asserts.skip_if(
- not server.isStaApConcurrencySupported(),
- "Server requires Wifi AP + STA concurrency",
- )
- server.ensureWifiIsDefault()
- else:
- raise ValueError(f"Invalid upstream type: {upstream_type}")
-
- # Generate ssid/passphrase with random characters to make sure nearby devices won't
- # connect unexpectedly. Note that total length of ssid cannot go over 32.
- testSsid = "HOTSPOT-" + self.generate_uuid32_base64()
- testPassphrase = self.generate_uuid32_base64()
-
- try:
- # Create a hotspot with fixed SSID and password.
- server.startHotspot(testSsid, testPassphrase)
-
- # Make the client connects to the hotspot.
- client.connectToWifi(testSsid, testPassphrase, True)
-
- finally:
- if upstream_type == UpstreamType.CELLULAR:
- server.unrequestCellular()
- # Teardown the hotspot.
- server.stopAllTethering()
-
def test_hotspot_upstream_wifi(self):
- self._do_test_hotspot_for_upstream_type(UpstreamType.WIFI)
+ try:
+ # Connectivity of the client verified by asserting the validated capability.
+ tether_utils.setup_hotspot_and_client_for_upstream_type(
+ self.serverDevice, self.clientDevice, UpstreamType.WIFI
+ )
+ finally:
+ tether_utils.cleanup_tethering_for_upstream_type(
+ self.serverDevice, UpstreamType.WIFI
+ )
def test_hotspot_upstream_cellular(self):
- self._do_test_hotspot_for_upstream_type(UpstreamType.CELLULAR)
+ try:
+ # Connectivity of the client verified by asserting the validated capability.
+ tether_utils.setup_hotspot_and_client_for_upstream_type(
+ self.serverDevice, self.clientDevice, UpstreamType.CELLULAR
+ )
+ finally:
+ tether_utils.cleanup_tethering_for_upstream_type(
+ self.serverDevice, UpstreamType.CELLULAR
+ )
if __name__ == "__main__":
diff --git a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
index 258648f..8805edd 100644
--- a/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
+++ b/tests/cts/multidevices/snippet/ConnectivityMultiDevicesSnippet.kt
@@ -16,11 +16,11 @@
package com.google.snippet.connectivity
+import android.Manifest.permission.NETWORK_SETTINGS
import android.Manifest.permission.OVERRIDE_WIFI_CONFIG
import android.content.pm.PackageManager.FEATURE_TELEPHONY
import android.content.pm.PackageManager.FEATURE_WIFI
import android.net.ConnectivityManager
-import android.net.Network
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
@@ -30,20 +30,24 @@
import android.net.wifi.SoftApConfiguration
import android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
import android.net.wifi.WifiConfiguration
+import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiNetworkSpecifier
import android.net.wifi.WifiSsid
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.AutoReleaseNetworkCallbackRule
import com.android.testutils.ConnectUtil
import com.android.testutils.NetworkCallbackHelper
-import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.runAsShell
import com.google.android.mobly.snippet.Snippet
import com.google.android.mobly.snippet.rpc.Rpc
+import org.junit.Rule
class ConnectivityMultiDevicesSnippet : Snippet {
+ @get:Rule
+ val networkCallbackRule = AutoReleaseNetworkCallbackRule()
private val context = InstrumentationRegistry.getInstrumentation().getTargetContext()
private val wifiManager = context.getSystemService(WifiManager::class.java)!!
private val cm = context.getSystemService(ConnectivityManager::class.java)!!
@@ -88,10 +92,8 @@
// Suppress warning because WifiManager methods to connect to a config are
// documented not to be deprecated for privileged users.
@Suppress("DEPRECATION")
- fun connectToWifi(ssid: String, passphrase: String, requireValidation: Boolean): Network {
+ fun connectToWifi(ssid: String, passphrase: String): Long {
val specifier = WifiNetworkSpecifier.Builder()
- .setSsid(ssid)
- .setWpa2Passphrase(passphrase)
.setBand(ScanResult.WIFI_BAND_24_GHZ)
.build()
val wifiConfig = WifiConfiguration()
@@ -102,27 +104,28 @@
wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
- // Register network callback for the specific wifi.
+ // Add the test configuration and connect to it.
+ val connectUtil = ConnectUtil(context)
+ connectUtil.connectToWifiConfig(wifiConfig)
+
+ // Implement manual SSID matching. Specifying the SSID in
+ // NetworkSpecifier is ineffective
+ // (see WifiNetworkAgentSpecifier#canBeSatisfiedBy for details).
+ // Note that holding permission is necessary when waiting for
+ // the callbacks. The handler thread checks permission; if
+ // it's not present, the SSID will be redacted.
val networkCallback = TestableNetworkCallback()
- val wifiRequest = NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI)
- .setNetworkSpecifier(specifier)
- .build()
- cm.registerNetworkCallback(wifiRequest, networkCallback)
-
- try {
- // Add the test configuration and connect to it.
- val connectUtil = ConnectUtil(context)
- connectUtil.connectToWifiConfig(wifiConfig)
-
- val event = networkCallback.expect<Available>()
- if (requireValidation) {
- networkCallback.eventuallyExpect<CapabilitiesChanged> {
- it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
- }
- }
- return event.network
- } finally {
- cm.unregisterNetworkCallback(networkCallback)
+ val wifiRequest = NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build()
+ return runAsShell(NETWORK_SETTINGS) {
+ // Register the network callback is needed here.
+ // This is to avoid the race condition where callback is fired before
+ // acquiring permission.
+ networkCallbackRule.registerNetworkCallback(wifiRequest, networkCallback)
+ return@runAsShell networkCallback.eventuallyExpect<CapabilitiesChanged> {
+ // Remove double quotes.
+ val ssidFromCaps = (WifiInfo::sanitizeSsid)(it.caps.ssid)
+ ssidFromCaps == ssid && it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
+ }.network.networkHandle
}
}
diff --git a/tests/cts/multidevices/tether_utils.py b/tests/cts/multidevices/tether_utils.py
new file mode 100644
index 0000000..a2d703c
--- /dev/null
+++ b/tests/cts/multidevices/tether_utils.py
@@ -0,0 +1,103 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import base64
+import uuid
+
+from mobly import asserts
+from mobly.controllers import android_device
+
+
+class UpstreamType:
+ CELLULAR = 1
+ WIFI = 2
+
+
+def generate_uuid32_base64() -> str:
+ """Generates a UUID32 and encodes it in Base64.
+
+ Returns:
+ str: The Base64-encoded UUID32 string. Which is 22 characters.
+ """
+ # Strip padding characters to make it safer for hotspot name length limit.
+ return base64.b64encode(uuid.uuid1().bytes).decode("utf-8").strip("=")
+
+
+def setup_hotspot_and_client_for_upstream_type(
+ server_device: android_device,
+ client_device: android_device,
+ upstream_type: UpstreamType,
+) -> (str, int):
+ """Setup the hotspot with a connected client with the specified upstream type.
+
+ This creates a hotspot, make the client connect
+ to it, and verify the packet is forwarded by the hotspot.
+ And returns interface name of both if successful.
+ """
+ server = server_device.connectivity_multi_devices_snippet
+ client = client_device.connectivity_multi_devices_snippet
+
+ # Assert pre-conditions specific to each upstream type.
+ asserts.skip_if(not client.hasWifiFeature(), "Client requires Wifi feature")
+ asserts.skip_if(
+ not server.hasHotspotFeature(), "Server requires hotspot feature"
+ )
+ if upstream_type == UpstreamType.CELLULAR:
+ asserts.skip_if(
+ not server.hasTelephonyFeature(), "Server requires Telephony feature"
+ )
+ server.requestCellularAndEnsureDefault()
+ elif upstream_type == UpstreamType.WIFI:
+ asserts.skip_if(
+ not server.isStaApConcurrencySupported(),
+ "Server requires Wifi AP + STA concurrency",
+ )
+ server.ensureWifiIsDefault()
+ else:
+ raise ValueError(f"Invalid upstream type: {upstream_type}")
+
+ # Generate ssid/passphrase with random characters to make sure nearby devices won't
+ # connect unexpectedly. Note that total length of ssid cannot go over 32.
+ test_ssid = "HOTSPOT-" + generate_uuid32_base64()
+ test_passphrase = generate_uuid32_base64()
+
+ # Create a hotspot with fixed SSID and password.
+ hotspot_interface = server.startHotspot(test_ssid, test_passphrase)
+
+ # Make the client connects to the hotspot.
+ client_network = client.connectToWifi(test_ssid, test_passphrase)
+
+ return hotspot_interface, client_network
+
+
+def cleanup_tethering_for_upstream_type(
+ server_device: android_device, upstream_type: UpstreamType
+) -> None:
+ server = server_device.connectivity_multi_devices_snippet
+ if upstream_type == UpstreamType.CELLULAR:
+ server.unrequestCellular()
+ # Teardown the hotspot.
+ server.stopAllTethering()
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 768ba12..ae85701 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -180,4 +180,5 @@
"cts",
"general-tests",
],
+ sdk_version: "test_current",
}
diff --git a/tests/cts/net/api23Test/Android.bp b/tests/cts/net/api23Test/Android.bp
index 2ec3a70..587d5a5 100644
--- a/tests/cts/net/api23Test/Android.bp
+++ b/tests/cts/net/api23Test/Android.bp
@@ -56,4 +56,5 @@
":CtsNetTestAppForApi23",
],
per_testcase_directory: true,
+ sdk_version: "test_current",
}
diff --git a/tests/cts/net/native/dns/Android.bp b/tests/cts/net/native/dns/Android.bp
index 8e24fba..de4a3bf 100644
--- a/tests/cts/net/native/dns/Android.bp
+++ b/tests/cts/net/native/dns/Android.bp
@@ -3,6 +3,11 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+filegroup {
+ name: "dns_async_test_default_map",
+ srcs: ["dns_async_test_default.map"],
+}
+
cc_defaults {
name: "dns_async_defaults",
@@ -20,6 +25,14 @@
srcs: [
"NativeDnsAsyncTest.cpp",
],
+ // This test runs on older platform versions, so many libraries (such as libbase and libc++)
+ // need to be linked statically. The test also needs to be linked with a version script to
+ // ensure that the statically-linked library isn't exported from the executable, where it
+ // would override the shared libraries that the platform itself uses.
+ // See http://b/333438055 for an example of what goes wrong when libc++ is partially exported
+ // from an executable.
+ version_script: ":dns_async_test_default_map",
+ stl: "libc++_static",
shared_libs: [
"libandroid",
"liblog",
diff --git a/tests/cts/net/native/dns/dns_async_test_default.map b/tests/cts/net/native/dns/dns_async_test_default.map
new file mode 100644
index 0000000..e342e43
--- /dev/null
+++ b/tests/cts/net/native/dns/dns_async_test_default.map
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+{
+ local:
+ *;
+};
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index a679498..0b6637d 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -51,9 +51,11 @@
import android.util.Log
import androidx.test.filters.RequiresDevice
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel
import com.android.compatibility.common.util.PropertyUtil.getVsrApiLevel
import com.android.compatibility.common.util.SystemUtil.runShellCommand
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
+import com.android.compatibility.common.util.VsrTest
import com.android.internal.util.HexDump
import com.android.net.module.util.PacketReader
import com.android.testutils.DevSdkIgnoreRule
@@ -191,8 +193,8 @@
futureReply!!.complete(recvbuf.sliceArray(8..<length))
}
- fun sendPing(data: ByteArray) {
- require(data.size == 56)
+ fun sendPing(data: ByteArray, payloadSize: Int) {
+ require(data.size == payloadSize)
// rfc4443#section-4.1: Echo Request Message
// 0 1 2 3
@@ -299,6 +301,10 @@
}
}
+ @VsrTest(
+ requirements = ["VSR-5.3.12-001", "VSR-5.3.12-003", "VSR-5.3.12-004", "VSR-5.3.12-009",
+ "VSR-5.3.12-012"]
+ )
@Test
fun testApfCapabilities() {
// APF became mandatory in Android 14 VSR.
@@ -318,7 +324,7 @@
if (caps.apfVersionSupported > 4) {
assertThat(caps.maximumApfProgramSize).isAtLeast(2048)
- assertThat(caps.apfVersionSupported).isEqualTo(6000) // v6.0000
+ assertThat(caps.apfVersionSupported).isEqualTo(6000) // v6.0000
}
// DEVICEs launching with Android 15 (AOSP experimental) or higher with CHIPSETs that set
@@ -349,6 +355,9 @@
return HexDump.hexStringToByteArray(progHexString)
}
+ @VsrTest(
+ requirements = ["VSR-5.3.12-007", "VSR-5.3.12-008", "VSR-5.3.12-010", "VSR-5.3.12-011"]
+ )
@SkipPresubmit(reason = "This test takes longer than 1 minute, do not run it on presubmit.")
// APF integration is mostly broken before V, only run the full read / write test on V+.
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@@ -356,9 +365,15 @@
fun testReadWriteProgram() {
assumeApfVersionSupportAtLeast(4)
- // Only test down to 2 bytes. The first byte always stays PASS.
+ val minReadWriteSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ 2
+ } else {
+ 8
+ }
+
+ // The minReadWriteSize is 2 bytes. The first byte always stays PASS.
val program = ByteArray(caps.maximumApfProgramSize)
- for (i in caps.maximumApfProgramSize downTo 2) {
+ for (i in caps.maximumApfProgramSize downTo minReadWriteSize) {
// Randomize bytes in range [1, i). And install first [0, i) bytes of program.
// Note that only the very first instruction (PASS) is valid APF bytecode.
Random.nextBytes(program, 1 /* fromIndex */, i /* toIndex */)
@@ -366,7 +381,15 @@
// Compare entire memory region.
val readResult = readProgram()
- assertWithMessage("read/write $i byte prog failed").that(readResult).isEqualTo(program)
+ val errMsg = """
+ read/write $i byte prog failed.
+ In APFv4, the APF memory region MUST NOT be modified or cleared except by APF
+ instructions executed by the interpreter or by Android OS calls to the HAL. If this
+ requirement cannot be met, the firmware cannot declare that it supports APFv4 and
+ it should declare that it only supports APFv3(if counter is partially supported) or
+ APFv2.
+ """.trimIndent()
+ assertWithMessage(errMsg).that(readResult).isEqualTo(program)
}
}
@@ -385,9 +408,14 @@
}
// APF integration is mostly broken before V
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
fun testDropPingReply() {
+ // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link
+ // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF
+ // should be turned on.
+ assume().that(getVsrApiLevel()).isAtLeast(34)
assumeApfVersionSupportAtLeast(4)
// clear any active APF filter
@@ -396,8 +424,13 @@
readProgram() // wait for install completion
// Assert that initial ping does not get filtered.
- val data = ByteArray(56).also { Random.nextBytes(it) }
- packetReader.sendPing(data)
+ val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ 68
+ } else {
+ 4
+ }
+ val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
+ packetReader.sendPing(data, payloadSize)
assertThat(packetReader.expectPingReply()).isEqualTo(data)
// Generate an APF program that drops the next ping
@@ -417,16 +450,21 @@
installProgram(program)
readProgram() // wait for install completion
- packetReader.sendPing(data)
+ packetReader.sendPing(data, payloadSize)
packetReader.expectPingDropped()
}
fun clearApfMemory() = installProgram(ByteArray(caps.maximumApfProgramSize))
// APF integration is mostly broken before V
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@Test
fun testPrefilledMemorySlotsV4() {
+ // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link
+ // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF
+ // should be turned on.
+ assume().that(getVsrApiLevel()).isAtLeast(34)
// Test v4 memory slots on both v4 and v6 interpreters.
assumeApfVersionSupportAtLeast(4)
clearApfMemory()
@@ -455,8 +493,13 @@
readProgram() // wait for install completion
// Trigger the program by sending a ping and waiting on the reply.
- val data = ByteArray(56).also { Random.nextBytes(it) }
- packetReader.sendPing(data)
+ val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
+ 68
+ } else {
+ 4
+ }
+ val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
+ packetReader.sendPing(data, payloadSize)
packetReader.expectPingReply()
val readResult = readProgram()
@@ -464,8 +507,52 @@
expect.withMessage("PROGRAM_SIZE").that(buffer.getInt()).isEqualTo(program.size)
expect.withMessage("RAM_LEN").that(buffer.getInt()).isEqualTo(caps.maximumApfProgramSize)
expect.withMessage("IPV4_HEADER_SIZE").that(buffer.getInt()).isEqualTo(0)
- // Ping packet (64) + IPv6 header (40) + ethernet header (14)
- expect.withMessage("PACKET_SIZE").that(buffer.getInt()).isEqualTo(64 + 40 + 14)
+ // Ping packet payload + ICMPv6 header (8) + IPv6 header (40) + ethernet header (14)
+ expect.withMessage("PACKET_SIZE").that(buffer.getInt()).isEqualTo(payloadSize + 8 + 40 + 14)
expect.withMessage("FILTER_AGE_SECONDS").that(buffer.getInt()).isLessThan(5)
}
+
+ // APF integration is mostly broken before V
+ @VsrTest(requirements = ["VSR-5.3.12-002", "VSR-5.3.12-005"])
+ @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Test
+ fun testFilterAgeIncreasesBetweenPackets() {
+ // VSR-14 mandates APF to be turned on when the screen is off and the Wi-Fi link
+ // is idle or traffic is less than 10 Mbps. Before that, we don't mandate when the APF
+ // should be turned on.
+ assume().that(getVsrApiLevel()).isAtLeast(34)
+ assumeApfVersionSupportAtLeast(4)
+ clearApfMemory()
+ val gen = ApfV4Generator(4)
+
+ // If not ICMPv6 Echo Reply -> PASS
+ gen.addPassIfNotIcmpv6EchoReply()
+
+ // Store all prefilled memory slots in counter region [500, 520)
+ val counterRegion = 500
+ gen.addLoadImmediate(R1, counterRegion)
+ gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS)
+ gen.addStoreData(R0, 0)
+
+ installProgram(gen.generate())
+ readProgram() // wait for install completion
+
+ val payloadSize = 56
+ val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
+ packetReader.sendPing(data, payloadSize)
+ packetReader.expectPingReply()
+
+ var buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */)
+ val filterAgeSecondsOrig = buffer.getInt()
+
+ Thread.sleep(5100)
+
+ packetReader.sendPing(data, payloadSize)
+ packetReader.expectPingReply()
+
+ buffer = ByteBuffer.wrap(readProgram(), counterRegion, 4 /* length */)
+ val filterAgeSeconds = buffer.getInt()
+ // Assert that filter age has increased, but not too much.
+ assertThat(filterAgeSeconds - filterAgeSecondsOrig).isEqualTo(5)
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index bf40462..633f2b6 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -309,6 +309,7 @@
// Airplane Mode BroadcastReceiver Timeout
private static final long AIRPLANE_MODE_CHANGE_TIMEOUT_MS = 10_000L;
+ private static final long CELL_DATA_AVAILABLE_TIMEOUT_MS = 120_000L;
// Timeout for applying uids allowed on restricted networks
private static final long APPLYING_UIDS_ALLOWED_ON_RESTRICTED_NETWORKS_TIMEOUT_MS = 3_000L;
@@ -2242,7 +2243,10 @@
// connectToCell only registers a request, it cannot / does not need to be called twice
mCtsNetUtils.ensureWifiConnected();
if (verifyWifi) waitForAvailable(wifiCb);
- if (supportTelephony) waitForAvailable(telephonyCb);
+ if (supportTelephony) {
+ telephonyCb.eventuallyExpect(
+ CallbackEntry.AVAILABLE, CELL_DATA_AVAILABLE_TIMEOUT_MS);
+ }
} finally {
// Restore the previous state of airplane mode and permissions:
runShellCommand("cmd connectivity airplane-mode "
diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
index 5b53839..ff10e1a 100644
--- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java
@@ -19,19 +19,19 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import static com.android.testutils.DevSdkIgnoreRuleKt.VANILLA_ICE_CREAM;
-
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
@@ -66,9 +66,9 @@
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -512,30 +512,20 @@
assertArrayEquals(netCapabilities, nr.getCapabilities());
}
- @Test @IgnoreUpTo(VANILLA_ICE_CREAM) @Ignore("b/338200742")
+ // Default capabilities and default forbidden capabilities must not be changed on U- because
+ // this could cause the system server crash when there is a module rollback (b/313030307)
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R) @IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void testDefaultCapabilities() {
final NetworkRequest defaultNR = new NetworkRequest.Builder().build();
- assertTrue(defaultNR.hasForbiddenCapability(NET_CAPABILITY_LOCAL_NETWORK));
- assertFalse(defaultNR.hasCapability(NET_CAPABILITY_LOCAL_NETWORK));
+
+ assertEquals(4, defaultNR.getCapabilities().length);
+ assertTrue(defaultNR.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ assertTrue(defaultNR.hasCapability(NET_CAPABILITY_TRUSTED));
assertTrue(defaultNR.hasCapability(NET_CAPABILITY_NOT_VPN));
+ assertTrue(defaultNR.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED));
- final NetworkCapabilities emptyNC =
- NetworkCapabilities.Builder.withoutDefaultCapabilities().build();
- assertFalse(defaultNR.canBeSatisfiedBy(emptyNC));
-
- // defaultNC represent the capabilities of a network agent, so they must not contain
- // forbidden capabilities by default.
- final NetworkCapabilities defaultNC = new NetworkCapabilities.Builder().build();
- assertArrayEquals(new int[0], defaultNC.getForbiddenCapabilities());
- // A default NR can be satisfied by default NC.
- assertTrue(defaultNR.canBeSatisfiedBy(defaultNC));
-
- // Conversely, network requests have forbidden capabilities by default to manage
- // backward compatibility, so test that these forbidden capabilities are in place.
- // Starting in V, NET_CAPABILITY_LOCAL_NETWORK is introduced but is not seen by
- // default, thanks to a default forbidden capability in NetworkRequest.
- defaultNC.addCapability(NET_CAPABILITY_LOCAL_NETWORK);
- assertFalse(defaultNR.canBeSatisfiedBy(defaultNC));
+ assertEquals(0, defaultNR.getForbiddenCapabilities().length);
}
@Test
diff --git a/tests/cts/netpermission/internetpermission/Android.bp b/tests/cts/netpermission/internetpermission/Android.bp
index 7d5ca2f..e0424ac 100644
--- a/tests/cts/netpermission/internetpermission/Android.bp
+++ b/tests/cts/netpermission/internetpermission/Android.bp
@@ -31,4 +31,5 @@
"general-tests",
],
host_required: ["net-tests-utils-host-common"],
+ sdk_version: "test_current",
}
diff --git a/tests/cts/netpermission/updatestatspermission/Android.bp b/tests/cts/netpermission/updatestatspermission/Android.bp
index 2fde1ce..689ce74 100644
--- a/tests/cts/netpermission/updatestatspermission/Android.bp
+++ b/tests/cts/netpermission/updatestatspermission/Android.bp
@@ -19,11 +19,17 @@
android_test {
name: "CtsNetTestCasesUpdateStatsPermission",
- defaults: ["cts_defaults"],
+ defaults: [
+ "cts_defaults",
+ "framework-connectivity-test-defaults",
+ ],
srcs: ["src/**/*.java"],
-
- static_libs: ["ctstestrunner-axt"],
+ platform_apis: true,
+ static_libs: [
+ "ctstestrunner-axt",
+ "net-tests-utils",
+ ],
// Tag this module as a cts test artifact
test_suites: [
diff --git a/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java b/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
index bea843c..56bad31 100644
--- a/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
+++ b/tests/cts/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
@@ -16,6 +16,10 @@
package android.net.cts.networkpermission.updatestatspermission;
+import static android.Manifest.permission.NETWORK_SETTINGS;
+
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -25,6 +29,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.build.SdkLevel;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -67,6 +73,11 @@
out.write(buf);
out.close();
socket.close();
+ // Clear TrafficStats cache is needed to avoid rate-limit caching for
+ // TrafficStats API results on V+ devices.
+ if (SdkLevel.isAtLeastV()) {
+ runAsShell(NETWORK_SETTINGS, () -> TrafficStats.clearRateLimitCaches());
+ }
long uidTxBytesAfter = TrafficStats.getUidTxBytes(Process.myUid());
long uidTxDeltaBytes = uidTxBytesAfter - uidTxBytesBefore;
assertTrue("uidtxb: " + uidTxBytesBefore + " -> " + uidTxBytesAfter + " delta="
diff --git a/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
index aa28e5a..10ba6a4 100644
--- a/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
+++ b/tests/unit/java/com/android/metrics/NetworkNsdReportedMetricsTest.kt
@@ -163,7 +163,8 @@
val sentQueryCount = 150
val metrics = NetworkNsdReportedMetrics(clientId, deps)
metrics.reportServiceDiscoveryStop(true /* isLegacy */, transactionId, durationMs,
- foundCallbackCount, lostCallbackCount, servicesCount, sentQueryCount)
+ foundCallbackCount, lostCallbackCount, servicesCount, sentQueryCount,
+ true /* isServiceFromCache */)
val eventCaptor = ArgumentCaptor.forClass(NetworkNsdReported::class.java)
verify(deps).statsWrite(eventCaptor.capture())
@@ -179,6 +180,7 @@
assertEquals(servicesCount, it.foundServiceCount)
assertEquals(durationMs, it.eventDurationMillisec)
assertEquals(sentQueryCount, it.sentQueryCount)
+ assertTrue(it.isKnownService)
}
}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 44a8222..8526a9a 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -2178,9 +2178,8 @@
switch (name) {
case ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER:
case ConnectivityFlags.CARRIER_SERVICE_CHANGED_USE_CALLBACK:
- return true;
case ConnectivityFlags.REQUEST_RESTRICTED_WIFI:
- return true;
+ case ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS:
case KEY_DESTROY_FROZEN_SOCKETS_VERSION:
return true;
default:
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index aece3f7..979e0a1 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -1196,7 +1196,7 @@
Instant.MAX /* expirationTime */);
// Verify onServiceNameDiscovered callback
- listener.onServiceNameDiscovered(foundInfo, false /* isServiceFromCache */);
+ listener.onServiceNameDiscovered(foundInfo, true /* isServiceFromCache */);
verify(discListener, timeout(TIMEOUT_MS)).onServiceFound(argThat(info ->
info.getServiceName().equals(SERVICE_NAME)
// Service type in discovery callbacks has a dot at the end
@@ -1232,7 +1232,7 @@
verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).requestStopWhenInactive();
verify(mMetrics).reportServiceDiscoveryStop(false /* isLegacy */, discId,
10L /* durationMs */, 1 /* foundCallbackCount */, 1 /* lostCallbackCount */,
- 1 /* servicesCount */, 3 /* sentQueryCount */);
+ 1 /* servicesCount */, 3 /* sentQueryCount */, true /* isServiceFromCache */);
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
index 0590fbb..3ad8de8 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSBlockedReasonsTest.kt
@@ -387,6 +387,10 @@
BLOCKED_REASON_NETWORK_RESTRICTED or BLOCKED_REASON_APP_BACKGROUND
)
}
+ // waitForIdle since stubbing bpfNetMaps while CS handler thread calls
+ // bpfNetMaps.getNetPermForUid throws exception.
+ // ConnectivityService might haven't finished checking blocked status for all requests.
+ waitForIdle()
// Disable background firewall chain
doReturn(BLOCKED_REASON_NONE)
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
similarity index 69%
rename from tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
rename to tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
index 9024641..88c2738 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkFallbackTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSSatelliteNetworkTest.kt
@@ -24,6 +24,7 @@
import android.net.NativeNetworkType
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
@@ -41,10 +42,13 @@
import android.util.ArraySet
import com.android.net.module.util.CollectionUtils
import com.android.server.ConnectivityService.PREFERENCE_ORDER_SATELLITE_FALLBACK
+import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.TestableNetworkCallback
import com.android.testutils.visibleOnHandlerThread
import org.junit.Assert
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
@@ -61,7 +65,10 @@
@DevSdkIgnoreRunner.MonitorThreadLeak
@RunWith(DevSdkIgnoreRunner::class)
@IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
-class CSSatelliteNetworkPreferredTest : CSTest() {
+class CSSatelliteNetworkTest : CSTest() {
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule()
+
/**
* Test createMultiLayerNrisFromSatelliteNetworkPreferredUids returns correct
* NetworkRequestInfo.
@@ -80,54 +87,81 @@
}
/**
- * Test that SATELLITE_NETWORK_PREFERENCE_UIDS changes will send correct net id and uid ranges
- * to netd.
+ * Test that satellite network satisfies satellite fallback per-app default network request and
+ * send correct net id and uid ranges to netd.
*/
- @Test
- fun testSatelliteNetworkPreferredUidsChanged() {
+ private fun doTestSatelliteNetworkFallbackUids(restricted: Boolean) {
val netdInOrder = inOrder(netd)
- val satelliteAgent = createSatelliteAgent("satellite0")
+ val satelliteAgent = createSatelliteAgent("satellite0", restricted)
satelliteAgent.connect()
val satelliteNetId = satelliteAgent.network.netId
+ val permission = if (restricted) {INetd.PERMISSION_SYSTEM} else {INetd.PERMISSION_NONE}
netdInOrder.verify(netd).networkCreate(
- nativeNetworkConfigPhysical(satelliteNetId, INetd.PERMISSION_NONE))
+ nativeNetworkConfigPhysical(satelliteNetId, permission))
val uid1 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
val uid2 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2)
val uid3 = SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)
- // Initial satellite network preferred uids status.
- setAndUpdateSatelliteNetworkPreferredUids(setOf())
+ // Initial satellite network fallback uids status.
+ updateSatelliteNetworkFallbackUids(setOf())
netdInOrder.verify(netd, never()).networkAddUidRangesParcel(any())
netdInOrder.verify(netd, never()).networkRemoveUidRangesParcel(any())
- // Set SATELLITE_NETWORK_PREFERENCE_UIDS setting and verify that net id and uid ranges
- // send to netd
+ // Update satellite network fallback uids and verify that net id and uid ranges send to netd
var uids = mutableSetOf(uid1, uid2, uid3)
val uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids))
val config1 = NativeUidRangeConfig(
satelliteNetId, uidRanges1,
PREFERENCE_ORDER_SATELLITE_FALLBACK
)
- setAndUpdateSatelliteNetworkPreferredUids(uids)
+ updateSatelliteNetworkFallbackUids(uids)
netdInOrder.verify(netd).networkAddUidRangesParcel(config1)
netdInOrder.verify(netd, never()).networkRemoveUidRangesParcel(any())
- // Set SATELLITE_NETWORK_PREFERENCE_UIDS setting again and verify that old rules are removed
- // and new rules are added.
+ // Update satellite network fallback uids and verify that net id and uid ranges send to netd
uids = mutableSetOf(uid1)
val uidRanges2: Array<UidRangeParcel?> = toUidRangeStableParcels(uidRangesForUids(uids))
val config2 = NativeUidRangeConfig(
satelliteNetId, uidRanges2,
PREFERENCE_ORDER_SATELLITE_FALLBACK
)
- setAndUpdateSatelliteNetworkPreferredUids(uids)
+ updateSatelliteNetworkFallbackUids(uids)
netdInOrder.verify(netd).networkRemoveUidRangesParcel(config1)
netdInOrder.verify(netd).networkAddUidRangesParcel(config2)
}
+ @Test
+ fun testSatelliteNetworkFallbackUids_restricted() {
+ doTestSatelliteNetworkFallbackUids(restricted = true)
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testSatelliteNetworkFallbackUids_nonRestricted() {
+ doTestSatelliteNetworkFallbackUids(restricted = false)
+ }
+
+ private fun doTestSatelliteNeverBecomeDefaultNetwork(restricted: Boolean) {
+ val agent = createSatelliteAgent("satellite0", restricted)
+ agent.connect()
+ val defaultCb = TestableNetworkCallback()
+ cm.registerDefaultNetworkCallback(defaultCb)
+ // Satellite network must not become the default network
+ defaultCb.assertNoCallback()
+ }
+
+ @Test
+ fun testSatelliteNeverBecomeDefaultNetwork_restricted() {
+ doTestSatelliteNeverBecomeDefaultNetwork(restricted = true)
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testSatelliteNeverBecomeDefaultNetwork_notRestricted() {
+ doTestSatelliteNeverBecomeDefaultNetwork(restricted = false)
+ }
+
private fun assertCreateMultiLayerNrisFromSatelliteNetworkPreferredUids(uids: Set<Int>) {
val nris: Set<ConnectivityService.NetworkRequestInfo> =
service.createMultiLayerNrisFromSatelliteNetworkFallbackUids(uids)
@@ -140,7 +174,7 @@
assertEquals(PREFERENCE_ORDER_SATELLITE_FALLBACK, nri.mPreferenceOrder)
}
- private fun setAndUpdateSatelliteNetworkPreferredUids(uids: Set<Int>) {
+ private fun updateSatelliteNetworkFallbackUids(uids: Set<Int>) {
visibleOnHandlerThread(csHandler) {
deps.satelliteNetworkFallbackUidUpdate!!.accept(uids)
}
@@ -150,9 +184,9 @@
NativeNetworkConfig(netId, NativeNetworkType.PHYSICAL, permission,
false /* secure */, VpnManager.TYPE_VPN_NONE, false /* excludeLocalRoutes */)
- private fun createSatelliteAgent(name: String): CSAgentWrapper {
+ private fun createSatelliteAgent(name: String, restricted: Boolean = true): CSAgentWrapper {
return Agent(score = keepScore(), lp = lp(name),
- nc = nc(TRANSPORT_SATELLITE, NET_CAPABILITY_INTERNET)
+ nc = satelliteNc(restricted)
)
}
@@ -176,17 +210,19 @@
return uidRangesForUids(*CollectionUtils.toIntArray(uids))
}
- private fun nc(transport: Int, vararg caps: Int) = NetworkCapabilities.Builder().apply {
- addTransportType(transport)
- caps.forEach {
- addCapability(it)
- }
- // Useful capabilities for everybody
- addCapability(NET_CAPABILITY_NOT_RESTRICTED)
- addCapability(NET_CAPABILITY_NOT_SUSPENDED)
- addCapability(NET_CAPABILITY_NOT_ROAMING)
- addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
- }.build()
+ private fun satelliteNc(restricted: Boolean) =
+ NetworkCapabilities.Builder().apply {
+ addTransportType(TRANSPORT_SATELLITE)
+
+ addCapability(NET_CAPABILITY_INTERNET)
+ addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ addCapability(NET_CAPABILITY_NOT_ROAMING)
+ addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ if (restricted) {
+ removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ }
+ removeCapability(NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)
+ }.build()
private fun lp(iface: String) = LinkProperties().apply {
interfaceName = iface
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 47a6763..ed72fd2 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -164,6 +164,7 @@
it[ConnectivityFlags.INGRESS_TO_VPN_ADDRESS_FILTERING] = true
it[ConnectivityFlags.BACKGROUND_FIREWALL_CHAIN] = true
it[ConnectivityFlags.DELAY_DESTROY_SOCKETS] = true
+ it[ConnectivityFlags.USE_DECLARED_METHODS_FOR_CALLBACKS] = true
}
fun setFeatureEnabled(flag: String, enabled: Boolean) = enabledFeatures.set(flag, enabled)
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index 9026481..c997b01 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -56,6 +56,7 @@
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.net.TrafficStats.UID_REMOVED;
import static android.net.TrafficStats.UID_TETHERING;
+import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_UID_TAG;
import static android.net.netstats.NetworkStatsDataMigrationUtils.PREFIX_XT;
@@ -306,6 +307,7 @@
private HandlerThread mObserverHandlerThread;
final TestDependencies mDeps = new TestDependencies();
final HashMap<String, Boolean> mFeatureFlags = new HashMap<>();
+ final HashMap<Long, Boolean> mCompatChanges = new HashMap<>();
// This will set feature flags from @FeatureFlag annotations
// into the map before setUp() runs.
@@ -594,7 +596,7 @@
}
@Override
- public boolean supportTrafficStatsRateLimitCache(Context ctx) {
+ public boolean alwaysUseTrafficStatsRateLimitCache(Context ctx) {
return mFeatureFlags.getOrDefault(TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG, false);
}
@@ -608,6 +610,14 @@
return DEFAULT_TRAFFIC_STATS_CACHE_MAX_ENTRIES;
}
+ @Override
+ public boolean isChangeEnabled(long changeId, int uid) {
+ return mCompatChanges.getOrDefault(changeId, true);
+ }
+
+ public void setChangeEnabled(long changeId, boolean enabled) {
+ mCompatChanges.put(changeId, enabled);
+ }
@Nullable
@Override
public NetworkStats.Entry nativeGetTotalStat() {
@@ -2413,17 +2423,33 @@
@FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
@Test
- public void testTrafficStatsRateLimitCache_disabled() throws Exception {
- doTestTrafficStatsRateLimitCache(false /* cacheEnabled */);
+ public void testTrafficStatsRateLimitCache_disabledWithCompatChangeEnabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, true);
+ doTestTrafficStatsRateLimitCache(true /* expectCached */);
}
@FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG)
@Test
- public void testTrafficStatsRateLimitCache_enabled() throws Exception {
- doTestTrafficStatsRateLimitCache(true /* cacheEnabled */);
+ public void testTrafficStatsRateLimitCache_enabledWithCompatChangeEnabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, true);
+ doTestTrafficStatsRateLimitCache(true /* expectCached */);
}
- private void doTestTrafficStatsRateLimitCache(boolean cacheEnabled) throws Exception {
+ @FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG, enabled = false)
+ @Test
+ public void testTrafficStatsRateLimitCache_disabledWithCompatChangeDisabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, false);
+ doTestTrafficStatsRateLimitCache(false /* expectCached */);
+ }
+
+ @FeatureFlag(name = TRAFFICSTATS_RATE_LIMIT_CACHE_ENABLED_FLAG)
+ @Test
+ public void testTrafficStatsRateLimitCache_enabledWithCompatChangeDisabled() throws Exception {
+ mDeps.setChangeEnabled(ENABLE_TRAFFICSTATS_RATE_LIMIT_CACHE, false);
+ doTestTrafficStatsRateLimitCache(true /* expectCached */);
+ }
+
+ private void doTestTrafficStatsRateLimitCache(boolean expectCached) throws Exception {
mockDefaultSettings();
// Calling uid is not injected into the service, use the real uid to pass the caller check.
final int myUid = Process.myUid();
@@ -2433,7 +2459,7 @@
// Verify the values are cached.
incrementCurrentTime(DEFAULT_TRAFFIC_STATS_CACHE_EXPIRY_DURATION_MS / 2);
mockTrafficStatsValues(65L, 8L, 1055L, 9L);
- if (cacheEnabled) {
+ if (expectCached) {
assertTrafficStatsValues(TEST_IFACE, myUid, 64L, 3L, 1024L, 8L);
} else {
assertTrafficStatsValues(TEST_IFACE, myUid, 65L, 8L, 1055L, 9L);
diff --git a/thread/README.md b/thread/README.md
index f50e0cd..41b73ac 100644
--- a/thread/README.md
+++ b/thread/README.md
@@ -1,3 +1,18 @@
# Thread
Bring the [Thread](https://www.threadgroup.org/) networking protocol to Android.
+
+## Try Thread with Cuttlefish
+
+```
+# Get the code and go to the Android source code root directory
+
+source build/envsetup.sh
+lunch aosp_cf_x86_64_phone-trunk_staging-userdebug
+m
+
+launch_cvd
+```
+
+Open `https://localhost:8443/` in your web browser, you can find the Thread
+demoapp (with the Thread logo) in the cuttlefish instance. Open it and have fun with Thread!
diff --git a/thread/service/java/com/android/server/thread/NsdPublisher.java b/thread/service/java/com/android/server/thread/NsdPublisher.java
index 2c14f1d..d0cb9b8 100644
--- a/thread/service/java/com/android/server/thread/NsdPublisher.java
+++ b/thread/service/java/com/android/server/thread/NsdPublisher.java
@@ -19,11 +19,15 @@
import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
+import android.net.DnsResolver;
import android.net.InetAddresses;
+import android.net.Network;
import android.net.nsd.DiscoveryRequest;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.RemoteException;
import android.text.TextUtils;
@@ -34,6 +38,7 @@
import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.INsdDiscoverServiceCallback;
import com.android.server.thread.openthread.INsdPublisher;
+import com.android.server.thread.openthread.INsdResolveHostCallback;
import com.android.server.thread.openthread.INsdResolveServiceCallback;
import com.android.server.thread.openthread.INsdStatusReceiver;
@@ -41,6 +46,7 @@
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -56,24 +62,36 @@
* {@code mHandler} itself.
*/
public final class NsdPublisher extends INsdPublisher.Stub {
- // TODO: b/321883491 - specify network for mDNS operations
private static final String TAG = NsdPublisher.class.getSimpleName();
+
+ // TODO: b/321883491 - specify network for mDNS operations
+ @Nullable private Network mNetwork;
private final NsdManager mNsdManager;
+ private final DnsResolver mDnsResolver;
private final Handler mHandler;
private final Executor mExecutor;
private final SparseArray<RegistrationListener> mRegistrationListeners = new SparseArray<>(0);
private final SparseArray<DiscoveryListener> mDiscoveryListeners = new SparseArray<>(0);
private final SparseArray<ServiceInfoListener> mServiceInfoListeners = new SparseArray<>(0);
+ private final SparseArray<HostInfoListener> mHostInfoListeners = new SparseArray<>(0);
@VisibleForTesting
- public NsdPublisher(NsdManager nsdManager, Handler handler) {
+ public NsdPublisher(NsdManager nsdManager, DnsResolver dnsResolver, Handler handler) {
+ mNetwork = null;
mNsdManager = nsdManager;
+ mDnsResolver = dnsResolver;
mHandler = handler;
mExecutor = runnable -> mHandler.post(runnable);
}
public static NsdPublisher newInstance(Context context, Handler handler) {
- return new NsdPublisher(context.getSystemService(NsdManager.class), handler);
+ return new NsdPublisher(
+ context.getSystemService(NsdManager.class), DnsResolver.getInstance(), handler);
+ }
+
+ // TODO: b/321883491 - NsdPublisher should be disabled when mNetwork is null
+ public void setNetworkForHostResolution(@Nullable Network network) {
+ mNetwork = network;
}
@Override
@@ -291,6 +309,53 @@
}
}
+ @Override
+ public void resolveHost(String name, INsdResolveHostCallback callback, int listenerId) {
+ mHandler.post(() -> resolveHostInternal(name, callback, listenerId));
+ }
+
+ private void resolveHostInternal(
+ String name, INsdResolveHostCallback callback, int listenerId) {
+ checkOnHandlerThread();
+
+ String fullHostname = name + ".local";
+ CancellationSignal cancellationSignal = new CancellationSignal();
+ HostInfoListener listener =
+ new HostInfoListener(name, callback, cancellationSignal, listenerId);
+ mDnsResolver.query(
+ mNetwork,
+ fullHostname,
+ DnsResolver.FLAG_NO_CACHE_LOOKUP,
+ mExecutor,
+ cancellationSignal,
+ listener);
+ mHostInfoListeners.append(listenerId, listener);
+
+ Log.i(TAG, "Resolving host." + " Listener ID: " + listenerId + ", hostname: " + name);
+ }
+
+ @Override
+ public void stopHostResolution(int listenerId) {
+ mHandler.post(() -> stopHostResolutionInternal(listenerId));
+ }
+
+ private void stopHostResolutionInternal(int listenerId) {
+ checkOnHandlerThread();
+
+ HostInfoListener listener = mHostInfoListeners.get(listenerId);
+ if (listener == null) {
+ Log.w(
+ TAG,
+ "Failed to stop host resolution. Listener ID: "
+ + listenerId
+ + ". The listener is null.");
+ return;
+ }
+ Log.i(TAG, "Stopping host resolution. Listener: " + listener);
+ listener.cancel();
+ mHostInfoListeners.remove(listenerId);
+ }
+
private void checkOnHandlerThread() {
if (mHandler.getLooper().getThread() != Thread.currentThread()) {
throw new IllegalStateException(
@@ -586,4 +651,78 @@
return "ID: " + mListenerId + ", service name: " + mName + ", service type: " + mType;
}
}
+
+ class HostInfoListener implements DnsResolver.Callback<List<InetAddress>> {
+ private final String mHostname;
+ private final INsdResolveHostCallback mResolveHostCallback;
+ private final CancellationSignal mCancellationSignal;
+ private final int mListenerId;
+
+ HostInfoListener(
+ @NonNull String hostname,
+ INsdResolveHostCallback resolveHostCallback,
+ CancellationSignal cancellationSignal,
+ int listenerId) {
+ this.mHostname = hostname;
+ this.mResolveHostCallback = resolveHostCallback;
+ this.mCancellationSignal = cancellationSignal;
+ this.mListenerId = listenerId;
+ }
+
+ @Override
+ public void onAnswer(@NonNull List<InetAddress> answerList, int rcode) {
+ checkOnHandlerThread();
+
+ Log.i(
+ TAG,
+ "Host is resolved."
+ + " Listener ID: "
+ + mListenerId
+ + ", hostname: "
+ + mHostname
+ + ", addresses: "
+ + answerList
+ + ", return code: "
+ + rcode);
+ List<String> addressStrings = new ArrayList<>();
+ for (InetAddress address : answerList) {
+ addressStrings.add(address.getHostAddress());
+ }
+ try {
+ mResolveHostCallback.onHostResolved(mHostname, addressStrings);
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ mHostInfoListeners.remove(mListenerId);
+ }
+
+ @Override
+ public void onError(@NonNull DnsResolver.DnsException error) {
+ checkOnHandlerThread();
+
+ Log.i(
+ TAG,
+ "Failed to resolve host."
+ + " Listener ID: "
+ + mListenerId
+ + ", hostname: "
+ + mHostname,
+ error);
+ try {
+ mResolveHostCallback.onHostResolved(mHostname, Collections.emptyList());
+ } catch (RemoteException e) {
+ // do nothing if the client is dead
+ }
+ mHostInfoListeners.remove(mListenerId);
+ }
+
+ public String toString() {
+ return "ID: " + mListenerId + ", hostname: " + mHostname;
+ }
+
+ void cancel() {
+ mCancellationSignal.cancel();
+ mHostInfoListeners.remove(mListenerId);
+ }
+ }
}
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 02c6a09..737ec41 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -623,7 +623,10 @@
return !mForceStopOtDaemonEnabled
&& !mUserRestricted
- && (!mAirplaneModeOn || enabledInAirplaneMode)
+ // FIXME(b/340744397): Note that here we need to call `isAirplaneModeOn()` to get
+ // the latest state of airplane mode but can't use `mIsAirplaneMode`. This is for
+ // avoiding the race conditions described in b/340744397
+ && (!isAirplaneModeOn() || enabledInAirplaneMode)
&& mPersistentSettings.get(ThreadPersistentSettings.THREAD_ENABLED);
}
@@ -714,6 +717,7 @@
if (mNetworkToInterface.containsKey(mUpstreamNetwork)) {
enableBorderRouting(mNetworkToInterface.get(mUpstreamNetwork));
}
+ mNsdPublisher.setNetworkForHostResolution(mUpstreamNetwork);
}
}
}
@@ -1384,14 +1388,6 @@
}
}
- private void notifyThreadEnabledUpdated(IStateCallback callback, int enabledState) {
- try {
- callback.onThreadEnableStateChanged(enabledState);
- } catch (RemoteException ignored) {
- // do nothing if the client is dead
- }
- }
-
public void unregisterStateCallback(IStateCallback callback) {
checkOnHandlerThread();
if (!mStateCallbacks.containsKey(callback)) {
@@ -1458,15 +1454,19 @@
}
}
- @Override
- public void onThreadEnabledChanged(int state) {
- mHandler.post(() -> onThreadEnabledChangedInternal(state));
- }
-
- private void onThreadEnabledChangedInternal(int state) {
+ private void onThreadEnabledChanged(int state, long listenerId) {
checkOnHandlerThread();
- for (IStateCallback callback : mStateCallbacks.keySet()) {
- notifyThreadEnabledUpdated(callback, otStateToAndroidState(state));
+ boolean stateChanged = (mState == null || mState.threadEnabled != state);
+
+ for (var callbackEntry : mStateCallbacks.entrySet()) {
+ if (!stateChanged && callbackEntry.getValue().id != listenerId) {
+ continue;
+ }
+ try {
+ callbackEntry.getKey().onThreadEnableStateChanged(otStateToAndroidState(state));
+ } catch (RemoteException ignored) {
+ // do nothing if the client is dead
+ }
}
}
@@ -1493,6 +1493,7 @@
onInterfaceStateChanged(newState.isInterfaceUp);
onDeviceRoleChanged(newState.deviceRole, listenerId);
onPartitionIdChanged(newState.partitionId, listenerId);
+ onThreadEnabledChanged(newState.threadEnabled, listenerId);
mState = newState;
ActiveOperationalDataset newActiveDataset;
diff --git a/thread/tests/cts/AndroidTest.xml b/thread/tests/cts/AndroidTest.xml
index ffc181c..6eda1e9 100644
--- a/thread/tests/cts/AndroidTest.xml
+++ b/thread/tests/cts/AndroidTest.xml
@@ -38,6 +38,11 @@
<option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController">
+ <option name="required-feature" value="android.hardware.thread_network" />
+ </object>
+
<!-- Install test -->
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="CtsThreadNetworkTestCases.apk" />
diff --git a/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java b/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java
index 0e76930..996d22d 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ActiveOperationalDatasetTest.java
@@ -30,6 +30,8 @@
import android.net.thread.ActiveOperationalDataset.Builder;
import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
import android.net.thread.OperationalDatasetTimestamp;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import android.util.SparseArray;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -38,6 +40,7 @@
import com.google.common.primitives.Bytes;
import com.google.common.testing.EqualsTester;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,6 +49,7 @@
/** CTS tests for {@link ActiveOperationalDataset}. */
@SmallTest
+@RequiresThreadFeature
@RunWith(AndroidJUnit4.class)
public final class ActiveOperationalDatasetTest {
private static final int TYPE_ACTIVE_TIMESTAMP = 14;
@@ -81,6 +85,8 @@
private static final ActiveOperationalDataset DEFAULT_DATASET =
ActiveOperationalDataset.fromThreadTlvs(VALID_DATASET_TLVS);
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
private static byte[] removeTlv(byte[] dataset, int type) {
ByteArrayOutputStream os = new ByteArrayOutputStream(dataset.length);
int i = 0;
diff --git a/thread/tests/cts/src/android/net/thread/cts/OperationalDatasetTimestampTest.java b/thread/tests/cts/src/android/net/thread/cts/OperationalDatasetTimestampTest.java
index 9be3d56..4d7c7f1 100644
--- a/thread/tests/cts/src/android/net/thread/cts/OperationalDatasetTimestampTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/OperationalDatasetTimestampTest.java
@@ -21,12 +21,15 @@
import static org.junit.Assert.assertThrows;
import android.net.thread.OperationalDatasetTimestamp;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.google.common.testing.EqualsTester;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -34,8 +37,11 @@
/** Tests for {@link OperationalDatasetTimestamp}. */
@SmallTest
+@RequiresThreadFeature
@RunWith(AndroidJUnit4.class)
public final class OperationalDatasetTimestampTest {
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
@Test
public void fromInstant_tooLargeInstant_throwsIllegalArgument() {
assertThrows(
diff --git a/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java b/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java
index 0bb18ce..76be054 100644
--- a/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/PendingOperationalDatasetTest.java
@@ -28,6 +28,8 @@
import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
import android.net.thread.OperationalDatasetTimestamp;
import android.net.thread.PendingOperationalDataset;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import android.util.SparseArray;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -36,6 +38,7 @@
import com.google.common.primitives.Bytes;
import com.google.common.testing.EqualsTester;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -44,8 +47,11 @@
/** Tests for {@link PendingOperationalDataset}. */
@SmallTest
+@RequiresThreadFeature
@RunWith(AndroidJUnit4.class)
public final class PendingOperationalDatasetTest {
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
private static ActiveOperationalDataset createActiveDataset() throws Exception {
SparseArray<byte[]> channelMask = new SparseArray<>(1);
channelMask.put(0, new byte[] {0x00, 0x1f, (byte) 0xff, (byte) 0xe0});
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 dea4279..11c4819 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -43,7 +43,6 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
@@ -174,6 +173,17 @@
}
@Test
+ public void subscribeThreadEnableState_getActiveDataset_onThreadEnableStateChangedNotCalled()
+ throws Exception {
+ EnabledStateListener listener = new EnabledStateListener(mController);
+ listener.expectThreadEnabledState(STATE_ENABLED);
+
+ getActiveOperationalDataset(mController);
+
+ listener.expectCallbackNotCalled();
+ }
+
+ @Test
public void registerStateCallback_returnsUpdatedEnabledStates() throws Exception {
CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
@@ -1016,7 +1026,11 @@
}
public void expectThreadEnabledState(int enabled) {
- assertNotNull(mReadHead.poll(ENABLED_TIMEOUT_MILLIS, e -> (e == enabled)));
+ assertThat(mReadHead.poll(ENABLED_TIMEOUT_MILLIS, e -> (e == enabled))).isNotNull();
+ }
+
+ public void expectCallbackNotCalled() {
+ assertThat(mReadHead.poll(CALLBACK_TIMEOUT_MILLIS, e -> true)).isNull();
}
public void unregisterStateCallback() {
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
index 7d9ae81..4de2e13 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkExceptionTest.java
@@ -25,17 +25,23 @@
import static org.junit.Assert.assertThrows;
import android.net.thread.ThreadNetworkException;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/** CTS tests for {@link ThreadNetworkException}. */
@SmallTest
+@RequiresThreadFeature
@RunWith(AndroidJUnit4.class)
public final class ThreadNetworkExceptionTest {
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
@Test
public void constructor_validValues_valuesAreConnectlySet() throws Exception {
ThreadNetworkException errorThreadDisabled =
diff --git a/thread/tests/integration/AndroidTest.xml b/thread/tests/integration/AndroidTest.xml
index 152c1c3..8f98941 100644
--- a/thread/tests/integration/AndroidTest.xml
+++ b/thread/tests/integration/AndroidTest.xml
@@ -31,6 +31,11 @@
<option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController">
+ <option name="required-feature" value="android.hardware.thread_network" />
+ </object>
+
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<!-- Install test -->
diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
index 46cf562..d24059a 100644
--- a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
@@ -17,16 +17,16 @@
import static android.net.thread.utils.IntegrationTestUtils.SERVICE_DISCOVERY_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
-
import static com.google.common.io.BaseEncoding.base16;
+import static java.util.concurrent.TimeUnit.SECONDS;
import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.nsd.NsdServiceInfo;
import android.net.thread.ActiveOperationalDataset;
-
+import android.os.Handler;
+import android.os.HandlerThread;
import com.google.errorprone.annotations.FormatMethod;
-
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
@@ -39,6 +39,8 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -60,10 +62,13 @@
private static final float PING_TIMEOUT_0_1_SECOND = 0.1f;
// 1 second timeout should be used when response is expected.
private static final float PING_TIMEOUT_1_SECOND = 1f;
+ private static final int READ_LINE_TIMEOUT_SECONDS = 5;
private final Process mProcess;
private final BufferedReader mReader;
private final BufferedWriter mWriter;
+ private final HandlerThread mReaderHandlerThread;
+ private final Handler mReaderHandler;
private ActiveOperationalDataset mActiveOperationalDataset;
@@ -87,11 +92,15 @@
}
mReader = new BufferedReader(new InputStreamReader(mProcess.getInputStream()));
mWriter = new BufferedWriter(new OutputStreamWriter(mProcess.getOutputStream()));
+ mReaderHandlerThread = new HandlerThread("FullThreadDeviceReader");
+ mReaderHandlerThread.start();
+ mReaderHandler = new Handler(mReaderHandlerThread.getLooper());
mActiveOperationalDataset = null;
}
public void destroy() {
mProcess.destroy();
+ mReaderHandlerThread.quit();
}
/**
@@ -213,7 +222,7 @@
public String udpReceive() throws IOException {
Pattern pattern =
Pattern.compile("> (\\d+) bytes from ([\\da-f:]+) (\\d+) ([\\x00-\\x7F]+)");
- Matcher matcher = pattern.matcher(mReader.readLine());
+ Matcher matcher = pattern.matcher(readLine());
matcher.matches();
return matcher.group(4);
@@ -500,10 +509,27 @@
}
}
+ private String readLine() throws IOException {
+ final CompletableFuture<String> future = new CompletableFuture<>();
+ mReaderHandler.post(
+ () -> {
+ try {
+ future.complete(mReader.readLine());
+ } catch (IOException e) {
+ future.completeExceptionally(e);
+ }
+ });
+ try {
+ return future.get(READ_LINE_TIMEOUT_SECONDS, SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ throw new IOException("Failed to read a line from ot-cli-ftd");
+ }
+ }
+
private List<String> readUntilDone() throws IOException {
ArrayList<String> result = new ArrayList<>();
String line;
- while ((line = mReader.readLine()) != null) {
+ while ((line = readLine()) != null) {
if (line.equals("Done")) {
break;
}
diff --git a/thread/tests/unit/AndroidTest.xml b/thread/tests/unit/AndroidTest.xml
index d16e423..58e9bdd 100644
--- a/thread/tests/unit/AndroidTest.xml
+++ b/thread/tests/unit/AndroidTest.xml
@@ -31,6 +31,11 @@
<option name="mainline-module-package-name" value="com.google.android.tethering" />
</object>
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.DeviceFeatureModuleController">
+ <option name="required-feature" value="android.hardware.thread_network" />
+ </object>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="test-file-name" value="ThreadNetworkUnitTests.apk" />
<option name="check-min-sdk" value="true" />
diff --git a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
index 8886c73..3cae84f 100644
--- a/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/NsdPublisherTest.java
@@ -16,6 +16,7 @@
package com.android.server.thread;
+import static android.net.DnsResolver.ERROR_SYSTEM;
import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
@@ -30,15 +31,19 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.net.DnsResolver;
import android.net.InetAddresses;
+import android.net.Network;
import android.net.nsd.DiscoveryRequest;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.test.TestLooper;
import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.INsdDiscoverServiceCallback;
+import com.android.server.thread.openthread.INsdResolveHostCallback;
import com.android.server.thread.openthread.INsdResolveServiceCallback;
import com.android.server.thread.openthread.INsdStatusReceiver;
@@ -61,11 +66,14 @@
/** Unit tests for {@link NsdPublisher}. */
public final class NsdPublisherTest {
@Mock private NsdManager mMockNsdManager;
+ @Mock private DnsResolver mMockDnsResolver;
@Mock private INsdStatusReceiver mRegistrationReceiver;
@Mock private INsdStatusReceiver mUnregistrationReceiver;
@Mock private INsdDiscoverServiceCallback mDiscoverServiceCallback;
@Mock private INsdResolveServiceCallback mResolveServiceCallback;
+ @Mock private INsdResolveHostCallback mResolveHostCallback;
+ @Mock private Network mNetwork;
private TestLooper mTestLooper;
private NsdPublisher mNsdPublisher;
@@ -637,6 +645,84 @@
}
@Test
+ public void resolveHost_hostResolved() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.resolveHost("test", mResolveHostCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<DnsResolver.Callback<List<InetAddress>>> resolveHostCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(DnsResolver.Callback.class);
+ verify(mMockDnsResolver, times(1))
+ .query(
+ eq(mNetwork),
+ eq("test.local"),
+ eq(DnsResolver.FLAG_NO_CACHE_LOOKUP),
+ any(Executor.class),
+ any(CancellationSignal.class),
+ resolveHostCallbackArgumentCaptor.capture());
+ resolveHostCallbackArgumentCaptor
+ .getValue()
+ .onAnswer(
+ List.of(
+ InetAddresses.parseNumericAddress("2001::1"),
+ InetAddresses.parseNumericAddress("2001::2")),
+ 0);
+ mTestLooper.dispatchAll();
+
+ verify(mResolveHostCallback, times(1))
+ .onHostResolved("test", List.of("2001::1", "2001::2"));
+ }
+
+ @Test
+ public void resolveHost_errorReported() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.resolveHost("test", mResolveHostCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<DnsResolver.Callback<List<InetAddress>>> resolveHostCallbackArgumentCaptor =
+ ArgumentCaptor.forClass(DnsResolver.Callback.class);
+ verify(mMockDnsResolver, times(1))
+ .query(
+ eq(mNetwork),
+ eq("test.local"),
+ eq(DnsResolver.FLAG_NO_CACHE_LOOKUP),
+ any(Executor.class),
+ any(CancellationSignal.class),
+ resolveHostCallbackArgumentCaptor.capture());
+ resolveHostCallbackArgumentCaptor
+ .getValue()
+ .onError(new DnsResolver.DnsException(ERROR_SYSTEM, null /* cause */));
+ mTestLooper.dispatchAll();
+
+ verify(mResolveHostCallback, times(1)).onHostResolved("test", Collections.emptyList());
+ }
+
+ @Test
+ public void stopHostResolution() throws Exception {
+ prepareTest();
+
+ mNsdPublisher.resolveHost("test", mResolveHostCallback, 10 /* listenerId */);
+ mTestLooper.dispatchAll();
+ ArgumentCaptor<CancellationSignal> cancellationSignalArgumentCaptor =
+ ArgumentCaptor.forClass(CancellationSignal.class);
+ verify(mMockDnsResolver, times(1))
+ .query(
+ eq(mNetwork),
+ eq("test.local"),
+ eq(DnsResolver.FLAG_NO_CACHE_LOOKUP),
+ any(Executor.class),
+ cancellationSignalArgumentCaptor.capture(),
+ any(DnsResolver.Callback.class));
+
+ mNsdPublisher.stopHostResolution(10 /* listenerId */);
+ mTestLooper.dispatchAll();
+
+ assertThat(cancellationSignalArgumentCaptor.getValue().isCanceled()).isTrue();
+ }
+
+ @Test
public void reset_unregisterAll() {
prepareTest();
@@ -780,6 +866,7 @@
private void prepareTest() {
mTestLooper = new TestLooper();
Handler handler = new Handler(mTestLooper.getLooper());
- mNsdPublisher = new NsdPublisher(mMockNsdManager, handler);
+ mNsdPublisher = new NsdPublisher(mMockNsdManager, mMockDnsResolver, handler);
+ mNsdPublisher.setNetworkForHostResolution(mNetwork);
}
}
diff --git a/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java b/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java
index bee9ceb..38a6e90 100644
--- a/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java
+++ b/thread/tests/utils/src/android/net/thread/utils/ThreadFeatureCheckerRule.java
@@ -21,7 +21,6 @@
import static org.junit.Assume.assumeTrue;
import android.content.Context;
-import android.net.thread.ThreadNetworkManager;
import android.os.SystemProperties;
import android.os.VintfRuntimeInfo;
@@ -122,7 +121,10 @@
/** Returns {@code true} if this device has the Thread feature supported. */
private static boolean hasThreadFeature() {
final Context context = ApplicationProvider.getApplicationContext();
- return context.getSystemService(ThreadNetworkManager.class) != null;
+
+ // Use service name rather than `ThreadNetworkManager.class` to avoid
+ // `ClassNotFoundException` on U- devices.
+ return context.getSystemService("thread_network") != null;
}
/**