Merge changes I7fae1964,Ic7cc9e1d into main
* changes:
Add carrier-privileged app bypass for start/stopTethering
Fix CarrierConfigRule#setHoldCarrierPrivilege race
diff --git a/bpf/loader/NetBpfLoad.cpp b/bpf/loader/NetBpfLoad.cpp
index d70a2c8..63de1a6 100644
--- a/bpf/loader/NetBpfLoad.cpp
+++ b/bpf/loader/NetBpfLoad.cpp
@@ -556,9 +556,9 @@
vector<string> csSymNames;
ret = getSectionSymNames(elfFile, oldName, csSymNames, STT_FUNC);
if (ret || !csSymNames.size()) return ret;
- for (size_t i = 0; i < progDefNames.size(); ++i) {
- if (!progDefNames[i].compare(csSymNames[0] + "_def")) {
- cs_temp.prog_def = pd[i];
+ for (size_t j = 0; j < progDefNames.size(); ++j) {
+ if (!progDefNames[j].compare(csSymNames[0] + "_def")) {
+ cs_temp.prog_def = pd[j];
break;
}
}
@@ -769,7 +769,7 @@
const size_t max_name = 256;
char kvTypeName[max_name];
int64_t keySize, valueSize;
- uint32_t kvId;
+ int32_t kvId;
if (snprintf(kvTypeName, max_name, "____btf_map_%s", mapName) == max_name) {
ALOGE("____btf_map_%s is too long", mapName);
@@ -858,14 +858,16 @@
struct btf *btf = NULL;
auto scopeGuard = base::make_scope_guard([btf] { if (btf) btf__free(btf); });
- if (isAtLeastKernelVersion(4, 18, 0)) {
+ if (isAtLeastKernelVersion(5, 10, 0)) {
+ // Untested on Linux Kernel 5.4, but likely compatible.
// On Linux Kernels older than 4.18 BPF_BTF_LOAD command doesn't exist.
+ // On Linux Kernels older than 5.2 BTF_KIND_VAR and BTF_KIND_DATASEC don't exist.
ret = readSectionByName(".BTF", elfFile, btfData);
if (ret) {
ALOGE("Failed to read .BTF section, ret:%d", ret);
return ret;
}
- struct btf *btf = btf__new(btfData.data(), btfData.size());
+ btf = btf__new(btfData.data(), btfData.size());
if (btf == NULL) {
ALOGE("btf__new failed, errno: %d", errno);
return -errno;
diff --git a/bpf/loader/netbpfload.rc b/bpf/loader/netbpfload.rc
index 10bfbb2..4cc6284 100644
--- a/bpf/loader/netbpfload.rc
+++ b/bpf/loader/netbpfload.rc
@@ -1,3 +1,5 @@
+# 2025 2 36 0 0 # 25q2 sdk/api level 36.0 - Android 16 Baklava QPR0
+
# Note: This will actually execute /apex/com.android.tethering/bin/netbpfload
# by virtue of 'service bpfloader' being overridden by the apex shipped .rc
# Warning: most of the below settings are irrelevant unless the apex is missing.
diff --git a/bpf/netd/BpfHandler.cpp b/bpf/netd/BpfHandler.cpp
index d41aa81..680c05e 100644
--- a/bpf/netd/BpfHandler.cpp
+++ b/bpf/netd/BpfHandler.cpp
@@ -268,6 +268,16 @@
RETURN_IF_NOT_OK(initMaps());
if (isAtLeast25Q2) {
+ struct rlimit limit = {
+ .rlim_cur = 1u << 30, // 1 GiB
+ .rlim_max = 1u << 30, // 1 GiB
+ };
+ // 25Q2 netd.rc includes "rlimit memlock 1073741824 1073741824"
+ // so this should be a no-op, and thus just succeed.
+ // make sure it isn't lowered in platform netd.rc...
+ if (setrlimit(RLIMIT_MEMLOCK, &limit))
+ return statusFromErrno(errno, "Failed to set 1GiB RLIMIT_MEMLOCK");
+
// Make sure netd can create & write maps. sepolicy is V+, but enough to enforce on 25Q2+
int key = 1;
int value = 123;
diff --git a/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java b/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java
index 95265b9..48e8b06 100644
--- a/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java
+++ b/common/src/com/android/net/module/util/bpf/LocalNetAccessKey.java
@@ -61,12 +61,46 @@
@Override
public String toString() {
- return "LocalNetAccessKey{"
- + "lpmBitlen=" + lpmBitlen
- + ", ifIndex=" + ifIndex
- + ", remoteAddress=" + remoteAddress
- + ", protocol=" + protocol
- + ", remotePort=" + remotePort
- + "}";
+ String s = "LocalNetAccessKey{lpmBitlen=" + lpmBitlen;
+
+ long bits = lpmBitlen;
+
+ // u32 ifIndex
+ if (bits <= 0 && ifIndex != 0) s += " ??";
+ if (bits > 0 || ifIndex != 0) s += " ifIndex=" + ifIndex;
+ if (bits > 0 && bits < 32) s += "/" + bits + "[LE]";
+ bits -= 32;
+
+ // u128 remoteAddress
+ if (bits <= 0 && !remoteAddress.isAnyLocalAddress()) s += " ??";
+ if (bits > 0 || !remoteAddress.isAnyLocalAddress()) {
+ s += " remoteAddress=";
+ String ip = remoteAddress.toString();
+ if (ip.startsWith("/::ffff:")) { // technically wrong IPv4-mapped IPv6 address detection
+ s += ip.substring(8);
+ if (bits >= 96 && bits < 128) s += "/" + (bits - 96);
+ } else if (ip.startsWith("/")) {
+ s += ip.substring(1);
+ if (bits >= 0 && bits < 128) s += "/" + bits;
+ } else { // WTF, includes a hostname or what?
+ s += ip;
+ }
+ }
+ bits -= 128;
+
+ // u16 protocol
+ if (bits <= 0 && protocol != 0) s += " ??";
+ if (bits > 0 || protocol != 0) s += " protocol=" + protocol;
+ if (bits > 0 && bits < 16) s += "/" + bits + "[LE16]";
+ bits -= 16;
+
+ // be16 remotePort
+ if (bits <= 0 && remotePort != 0) s += " ??";
+ if (bits > 0 || remotePort != 0) s += " remotePort=" + remotePort;
+ if (bits > 0 && bits < 16) s += "/" + bits + "[BE16]";
+ bits -= 16;
+
+ s += "}";
+ return s;
}
}
diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java
index c6b62ee..8355d31 100644
--- a/framework/src/android/net/NetworkCapabilities.java
+++ b/framework/src/android/net/NetworkCapabilities.java
@@ -360,6 +360,8 @@
mUnderlyingNetworks = null;
mEnterpriseId = 0;
mReservationId = RES_ID_UNSET;
+ // TODO: Change to default disabled when introduce this filtering.
+ mMatchNonThreadLocalNetworks = true;
}
/**
@@ -395,6 +397,7 @@
mUnderlyingNetworks = nc.mUnderlyingNetworks;
mEnterpriseId = nc.mEnterpriseId;
mReservationId = nc.mReservationId;
+ mMatchNonThreadLocalNetworks = nc.mMatchNonThreadLocalNetworks;
}
/**
@@ -2236,7 +2239,8 @@
&& (onlyImmutable || satisfiedBySSID(nc))
&& (onlyImmutable || satisfiedByRequestor(nc))
&& (onlyImmutable || satisfiedBySubscriptionIds(nc)))
- && satisfiedByReservationId(nc);
+ && satisfiedByReservationId(nc)
+ && satisfiedByMatchNonThreadLocalNetworks(nc);
}
/**
@@ -2351,7 +2355,8 @@
&& equalsSubscriptionIds(that)
&& equalsUnderlyingNetworks(that)
&& equalsEnterpriseCapabilitiesId(that)
- && equalsReservationId(that);
+ && equalsReservationId(that)
+ && equalsMatchNonThreadLocalNetworks(that);
}
@Override
@@ -2371,15 +2376,15 @@
+ Objects.hashCode(mAllowedUids) * 41
+ Objects.hashCode(mSSID) * 43
+ Objects.hashCode(mTransportInfo) * 47
- + Objects.hashCode(mPrivateDnsBroken) * 53
+ + Boolean.hashCode(mPrivateDnsBroken) * 53
+ Objects.hashCode(mRequestorUid) * 59
+ Objects.hashCode(mRequestorPackageName) * 61
+ Arrays.hashCode(mAdministratorUids) * 67
+ Objects.hashCode(mSubIds) * 71
+ Objects.hashCode(mUnderlyingNetworks) * 73
+ mEnterpriseId * 79
- + mReservationId * 83;
-
+ + mReservationId * 83
+ + Boolean.hashCode(mMatchNonThreadLocalNetworks) * 89;
}
@Override
@@ -2418,6 +2423,7 @@
dest.writeTypedList(mUnderlyingNetworks);
dest.writeInt(mEnterpriseId & ALL_VALID_ENTERPRISE_IDS);
dest.writeInt(mReservationId);
+ dest.writeBoolean(mMatchNonThreadLocalNetworks);
}
public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
@@ -2454,8 +2460,10 @@
netCap.setUnderlyingNetworks(in.createTypedArrayList(Network.CREATOR));
netCap.mEnterpriseId = in.readInt() & ALL_VALID_ENTERPRISE_IDS;
netCap.mReservationId = in.readInt();
+ netCap.mMatchNonThreadLocalNetworks = in.readBoolean();
return netCap;
}
+
@Override
public NetworkCapabilities[] newArray(int size) {
return new NetworkCapabilities[size];
@@ -2561,6 +2569,10 @@
sb.append(" ReservationId: ").append(isReservationOffer ? "*" : mReservationId);
}
+ if (mMatchNonThreadLocalNetworks) {
+ sb.append(" MatchNonThreadLocalNetworks");
+ }
+
sb.append(" UnderlyingNetworks: ");
if (mUnderlyingNetworks != null) {
sb.append("[");
@@ -2945,7 +2957,45 @@
return mReservationId == nc.mReservationId;
}
+ /**
+ * Flag to control whether a NetworkRequest can match non-thread local networks.
+ * @hide
+ */
+ // TODO: Change to default disabled when introduce this filtering.
+ private boolean mMatchNonThreadLocalNetworks = true;
+ /**
+ * Returns the match non-thread local networks flag.
+ *
+ * @hide
+ */
+ public boolean getMatchNonThreadLocalNetworks() {
+ return mMatchNonThreadLocalNetworks;
+ }
+
+ /**
+ * Set the match non-thread local networks flag.
+ * @hide
+ */
+ public void setMatchNonThreadLocalNetworks(boolean enabled) {
+ mMatchNonThreadLocalNetworks = enabled;
+ }
+
+ private boolean equalsMatchNonThreadLocalNetworks(@NonNull NetworkCapabilities nc) {
+ return mMatchNonThreadLocalNetworks == nc.mMatchNonThreadLocalNetworks;
+ }
+
+ // If the flag was set, the NetworkRequest can match all local networks.
+ // Otherwise, it can only see local networks created by Thread.
+ @SuppressWarnings("FlaggedApi")
+ private boolean satisfiedByMatchNonThreadLocalNetworks(@NonNull NetworkCapabilities nc) {
+ // If the network is not a local network, out of scope.
+ if (!nc.hasCapability(NET_CAPABILITY_LOCAL_NETWORK)) return true;
+ // If there is no restriction on matching non-thread local networks, return.
+ if (mMatchNonThreadLocalNetworks) return true;
+
+ return nc.hasTransport(TRANSPORT_THREAD);
+ }
/**
* Returns a bitmask of all the applicable redactions (based on the permissions held by the
diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
index 2261c69..3b2520e 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -139,13 +139,13 @@
/**
* Restrict local network access.
- *
* Apps targeting a release after V will require permissions to access the local network.
*
+ * ToDo: Update the target SDK version once it's finalized.
* @hide
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT)
+ @EnabledAfter(targetSdkVersion = 36)
public static final long RESTRICT_LOCAL_NETWORK = 365139289L;
private ConnectivityCompatChanges() {
diff --git a/service-t/src/com/android/server/ethernet/EthernetTracker.java b/service-t/src/com/android/server/ethernet/EthernetTracker.java
index 9b3c7ba..48467ed 100644
--- a/service-t/src/com/android/server/ethernet/EthernetTracker.java
+++ b/service-t/src/com/android/server/ethernet/EthernetTracker.java
@@ -755,7 +755,17 @@
private void parseEthernetConfig(String configString) {
final EthernetTrackerConfig config = createEthernetTrackerConfig(configString);
NetworkCapabilities nc;
- if (TextUtils.isEmpty(config.mCapabilities)) {
+ // Starting with Android B (API level 36), we provide default NetworkCapabilities
+ // for Ethernet interfaces when no explicit capabilities are specified in the
+ // configuration string. This change is made to ensure consistent and expected
+ // network behavior for Ethernet devices.
+ //
+ // It's possible that OEMs or device manufacturers may have relied on the previous
+ // behavior (where interfaces without specified capabilities would have minimal
+ // capabilities) to prevent certain Ethernet interfaces from becoming
+ // the default network. To avoid breaking existing device configurations, this
+ // change is gated by the SDK level.
+ if (SdkLevel.isAtLeastB() && TextUtils.isEmpty(config.mCapabilities)) {
boolean isTestIface = config.mIface.matches(TEST_IFACE_REGEXP);
nc = createDefaultNetworkCapabilities(isTestIface, config.mTransport);
} else {
diff --git a/service-t/src/com/android/server/net/NetworkStatsFactory.java b/service-t/src/com/android/server/net/NetworkStatsFactory.java
index 5ff708d..c5a69c0 100644
--- a/service-t/src/com/android/server/net/NetworkStatsFactory.java
+++ b/service-t/src/com/android/server/net/NetworkStatsFactory.java
@@ -19,6 +19,7 @@
import static android.net.NetworkStats.INTERFACES_ALL;
import static android.net.NetworkStats.TAG_ALL;
import static android.net.NetworkStats.UID_ALL;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
import android.annotation.NonNull;
import android.content.Context;
@@ -26,15 +27,26 @@
import android.net.UnderlyingNetworkInfo;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.DeviceConfigUtils;
import com.android.server.BpfNetMaps;
import com.android.server.connectivity.InterfaceTracker;
import java.io.IOException;
import java.net.ProtocolException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -65,6 +77,18 @@
/** Set containing info about active VPNs and their underlying networks. */
private volatile UnderlyingNetworkInfo[] mUnderlyingNetworkInfos = new UnderlyingNetworkInfo[0];
+ static final String CONFIG_PER_UID_TAG_THROTTLING = "per_uid_tag_throttling";
+ static final String CONFIG_PER_UID_TAG_THROTTLING_THRESHOLD =
+ "per_uid_tag_throttling_threshold";
+ private static final int DEFAULT_TAGS_PER_UID_THRESHOLD = 1000;
+ private static final int DUMP_TAGS_PER_UID_COUNT = 20;
+ private final boolean mSupportPerUidTagThrottling;
+ private final int mPerUidTagThrottlingThreshold;
+
+ // Map for set of distinct tags per uid. Used for tag count limiting.
+ @GuardedBy("mPersistentDataLock")
+ private final SparseArray<SparseBooleanArray> mUidTagSets = new SparseArray<>();
+
// A persistent snapshot of cumulative stats since device start
@GuardedBy("mPersistentDataLock")
private NetworkStats mPersistSnapshot;
@@ -110,6 +134,26 @@
public BpfNetMaps createBpfNetMaps(@NonNull Context ctx) {
return new BpfNetMaps(ctx, new InterfaceTracker(ctx));
}
+
+ /**
+ * Check whether one specific feature is not disabled.
+ * @param name Flag name of the experiment in the tethering namespace.
+ * @see DeviceConfigUtils#isTetheringFeatureNotChickenedOut(Context, String)
+ */
+ public boolean isFeatureNotChickenedOut(@NonNull Context context, @NonNull String name) {
+ return DeviceConfigUtils.isTetheringFeatureNotChickenedOut(context, name);
+ }
+
+ /**
+ * Wrapper method for DeviceConfigUtils#getDeviceConfigPropertyInt for test injections.
+ *
+ * See {@link DeviceConfigUtils#getDeviceConfigPropertyInt(String, String, int)}
+ * for more detailed information.
+ */
+ public int getDeviceConfigPropertyInt(@NonNull String name, int defaultValue) {
+ return DeviceConfigUtils.getDeviceConfigPropertyInt(
+ NAMESPACE_TETHERING, name, defaultValue);
+ }
}
/**
@@ -162,6 +206,10 @@
}
mContext = ctx;
mDeps = deps;
+ mSupportPerUidTagThrottling = mDeps.isFeatureNotChickenedOut(
+ ctx, CONFIG_PER_UID_TAG_THROTTLING);
+ mPerUidTagThrottlingThreshold = mDeps.getDeviceConfigPropertyInt(
+ CONFIG_PER_UID_TAG_THROTTLING_THRESHOLD, DEFAULT_TAGS_PER_UID_THRESHOLD);
}
/**
@@ -210,10 +258,13 @@
requestSwapActiveStatsMapLocked();
// Stats are always read from the inactive map, so they must be read after the
// swap
- final NetworkStats stats = mDeps.getNetworkStatsDetail();
+ final NetworkStats diff = mDeps.getNetworkStatsDetail();
+ // Filter based on UID tag set before merging.
+ final NetworkStats filteredDiff = mSupportPerUidTagThrottling
+ ? filterStatsByUidTagSets(diff) : diff;
// BPF stats are incremental; fold into mPersistSnapshot.
- mPersistSnapshot.setElapsedRealtime(stats.getElapsedRealtime());
- mPersistSnapshot.combineAllValues(stats);
+ mPersistSnapshot.setElapsedRealtime(diff.getElapsedRealtime());
+ mPersistSnapshot.combineAllValues(filteredDiff);
NetworkStats adjustedStats = adjustForTunAnd464Xlat(mPersistSnapshot, prev, vpnArray);
@@ -224,6 +275,41 @@
}
@GuardedBy("mPersistentDataLock")
+ private NetworkStats filterStatsByUidTagSets(NetworkStats stats) {
+ final NetworkStats filteredStats =
+ new NetworkStats(stats.getElapsedRealtime(), stats.size());
+
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ final Set<Integer> tooManyTagsUidSet = new ArraySet<>();
+ for (int i = 0; i < stats.size(); i++) {
+ stats.getValues(i, entry);
+ final int uid = entry.uid;
+ final int tag = entry.tag;
+
+ if (tag == NetworkStats.TAG_NONE) {
+ filteredStats.combineValues(entry);
+ continue;
+ }
+
+ SparseBooleanArray tagSet = mUidTagSets.get(uid);
+ if (tagSet == null) {
+ tagSet = new SparseBooleanArray();
+ }
+ if (tagSet.size() < mPerUidTagThrottlingThreshold || tagSet.get(tag)) {
+ filteredStats.combineValues(entry);
+ tagSet.put(tag, true);
+ mUidTagSets.put(uid, tagSet);
+ } else {
+ tooManyTagsUidSet.add(uid);
+ }
+ }
+ if (tooManyTagsUidSet.size() > 0) {
+ Log.wtf(TAG, "Too many tags detected for uids: " + tooManyTagsUidSet);
+ }
+ return filteredStats;
+ }
+
+ @GuardedBy("mPersistentDataLock")
private NetworkStats adjustForTunAnd464Xlat(NetworkStats uidDetailStats,
NetworkStats previousStats, UnderlyingNetworkInfo[] vpnArray) {
// Calculate delta from last snapshot
@@ -307,4 +393,34 @@
pe.initCause(cause);
return pe;
}
+
+ /**
+ * Dump the contents of NetworkStatsFactory.
+ */
+ public void dump(IndentingPrintWriter pw) {
+ dumpUidTagSets(pw);
+ }
+
+ private void dumpUidTagSets(IndentingPrintWriter pw) {
+ pw.println("Top distinct tag counts in UidTagSets:");
+ pw.increaseIndent();
+ final List<Pair<Integer, Integer>> countForUidList = new ArrayList<>();
+ synchronized (mPersistentDataLock) {
+ for (int i = 0; i < mUidTagSets.size(); i++) {
+ final Pair<Integer, Integer> countForUid =
+ new Pair<>(mUidTagSets.keyAt(i), mUidTagSets.valueAt(i).size());
+ countForUidList.add(countForUid);
+ }
+ }
+ Collections.sort(countForUidList,
+ (entry1, entry2) -> Integer.compare(entry2.second, entry1.second));
+ final int dumpSize = Math.min(countForUidList.size(), DUMP_TAGS_PER_UID_COUNT);
+ for (int j = 0; j < dumpSize; j++) {
+ final Pair<Integer, Integer> entry = countForUidList.get(j);
+ pw.print(entry.first);
+ pw.print("=");
+ pw.println(entry.second);
+ }
+ pw.decreaseIndent();
+ }
}
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 5c5f4ca..75d30a9 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -3228,6 +3228,12 @@
pw.increaseIndent();
mSkDestroyListener.dump(pw);
pw.decreaseIndent();
+
+ pw.println();
+ pw.println("NetworkStatsFactory logs:");
+ pw.increaseIndent();
+ mStatsFactory.dump(pw);
+ pw.decreaseIndent();
}
}
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index 5c0ba78..28b46c1 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -48,6 +48,8 @@
<!-- Configuration values for ThreadNetworkService -->
<item type="bool" name="config_thread_default_enabled" />
+ <item type="bool" name="config_thread_border_router_default_enabled" />
+ <item type="bool" name="config_thread_country_code_enabled" />
<item type="bool" name="config_thread_location_use_for_country_code_enabled" />
<item type="string" name="config_thread_vendor_name" />
<item type="string" name="config_thread_vendor_oui" />
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index 9bd407d..523ffee 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -1327,7 +1327,7 @@
+ value.iif1 + "(" + mDeps.getIfName(value.iif1) + "), "
+ value.iif2 + "(" + mDeps.getIfName(value.iif2) + ")");
if (sLocalNetBlockedUidMap != null) {
- BpfDump.dumpMap(sLocalNetAccessMap, pw, "sLocalNetAccessMap",
+ BpfDump.dumpMap(sLocalNetAccessMap, pw, "sLocalNetAccessMap (default is true meaning global)",
(key, value) -> "" + key + ": " + value.val);
}
if (sLocalNetBlockedUidMap != null) {
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index e27b72d..b9b590b 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -10059,10 +10059,12 @@
// Adds dns allow rule to LocalNetAccessMap for both TCP and UDP protocol at port 53,
// if it is a local dns (ie. it falls in the local prefix range).
if (prefix.contains(dnsServer)) {
- mBpfNetMaps.addLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+ mBpfNetMaps.addLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
IPPROTO_UDP, 53, true);
- mBpfNetMaps.addLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+ mBpfNetMaps.addLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
IPPROTO_TCP, 53, true);
+ mBpfNetMaps.addLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
+ IPPROTO_TCP, 853, true); // DNS over TLS
}
}
}
@@ -10081,25 +10083,17 @@
// Removes dns allow rule from LocalNetAccessMap for both TCP and UDP protocol
// at port 53, if it is a local dns (ie. it falls in the prefix range).
if (prefix.contains(dnsServer)) {
- mBpfNetMaps.removeLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+ mBpfNetMaps.removeLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
IPPROTO_UDP, 53);
- mBpfNetMaps.removeLocalNetAccess(getIpv4MappedAddressBitLen(), iface, dnsServer,
+ mBpfNetMaps.removeLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
IPPROTO_TCP, 53);
+ mBpfNetMaps.removeLocalNetAccess(32 + 128 + 16 + 16, iface, dnsServer,
+ IPPROTO_TCP, 853); // DNS over TLS
}
}
}
/**
- * Returns total bit length of an Ipv4 mapped address.
- */
- private int getIpv4MappedAddressBitLen() {
- final int ifaceLen = 32; // bit length of interface
- final int inetAddressLen = 32 + 96; // length of ipv4 mapped addresses
- final int portProtocolLen = 32; //16 for port + 16 for protocol;
- return ifaceLen + inetAddressLen + portProtocolLen;
- }
-
- /**
* Have netd update routes from oldLp to newLp.
* @return true if routes changed between oldLp and newLp
*/
diff --git a/service/src/com/android/server/L2capNetworkProvider.java b/service/src/com/android/server/L2capNetworkProvider.java
index 0352ad5..149979f 100644
--- a/service/src/com/android/server/L2capNetworkProvider.java
+++ b/service/src/com/android/server/L2capNetworkProvider.java
@@ -597,10 +597,12 @@
final ClientRequestInfo cri = mClientNetworkRequests.get(specifier);
if (cri == null) return;
+ // Release ClientNetworkRequest before sending onUnavailable() to ensure the app
+ // first receives an onLost() callback if a network had been created.
+ releaseClientNetworkRequest(cri);
for (NetworkRequest request : cri.requests) {
mProvider.declareNetworkRequestUnfulfillable(request);
}
- releaseClientNetworkRequest(cri);
}
}
diff --git a/staticlibs/tests/unit/host/python/apf_utils_test.py b/staticlibs/tests/unit/host/python/apf_utils_test.py
index d4753b7..55fbe58 100644
--- a/staticlibs/tests/unit/host/python/apf_utils_test.py
+++ b/staticlibs/tests/unit/host/python/apf_utils_test.py
@@ -27,6 +27,7 @@
get_apf_counters_from_dumpsys,
get_ipv4_addresses,
get_non_tentative_ipv6_addresses,
+ get_exclude_all_host_ipv6_multicast_addresses,
get_hardware_address,
is_send_raw_packet_downstream_supported,
is_packet_capture_supported,
@@ -170,6 +171,31 @@
ip_addresses = get_non_tentative_ipv6_addresses(self.mock_ad, "wlan0")
asserts.assert_equal(ip_addresses, [])
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_exclude_all_host_ipv6_multicast_addresses_success(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = """
+47: wlan0
+ inet6 ff02::1:ff99:37b0
+ inet6 ff02::1:ffb7:cba2 users 2
+ inet6 ff02::1
+ inet6 ff01::1
+"""
+ ip_addresses = get_exclude_all_host_ipv6_multicast_addresses(self.mock_ad, "wlan0")
+ asserts.assert_equal(ip_addresses,
+ ["ff02::1:ff99:37b0",
+ "ff02::1:ffb7:cba2"])
+
+ @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+ def test_get_exclude_all_host_ipv6_multicast_addresses_not_found(
+ self, mock_adb_shell: MagicMock
+ ) -> None:
+ mock_adb_shell.return_value = ""
+ ip_addresses = get_exclude_all_host_ipv6_multicast_addresses(self.mock_ad, "wlan0")
+ asserts.assert_equal(ip_addresses, [])
+
@patch("net_tests_utils.host.python.adb_utils.adb_shell")
def test_send_raw_packet_downstream_success(
self, mock_adb_shell: MagicMock
diff --git a/staticlibs/tests/unit/host/python/packet_utils_test.py b/staticlibs/tests/unit/host/python/packet_utils_test.py
deleted file mode 100644
index 8ad9576..0000000
--- a/staticlibs/tests/unit/host/python/packet_utils_test.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# 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.
-
-from mobly import asserts
-from mobly import base_test
-from net_tests_utils.host.python import packet_utils
-
-class TestPacketUtils(base_test.BaseTestClass):
- def test_unicast_arp_request(self):
- # Using scapy to generate unicast arp request packet:
- # eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06")
- # arp = ARP(op=1, pdst="192.168.1.1", hwsrc="00:01:02:03:04:05", psrc="192.168.1.2")
- # pkt = eth/arp
- expect_arp_request = """
- 01020304050600010203040508060001080006040001000102030405c0a80102000000000000c0a80101
- """.upper().replace(" ", "").replace("\n", "")
- arp_request = packet_utils.construct_arp_packet(
- src_mac="00:01:02:03:04:05",
- dst_mac="01:02:03:04:05:06",
- src_ip="192.168.1.2",
- dst_ip="192.168.1.1",
- op=packet_utils.ARP_REQUEST_OP
- )
- asserts.assert_equal(expect_arp_request, arp_request)
-
- def test_broadcast_arp_request(self):
- # Using scapy to generate unicast arp request packet:
- # eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF")
- # arp = ARP(op=1, pdst="192.168.1.1", hwsrc="00:01:02:03:04:05", psrc="192.168.1.2")
- # pkt = eth/arp
- expect_arp_request = """
- ffffffffffff00010203040508060001080006040001000102030405c0a80102000000000000c0a80101
- """.upper().replace(" ", "").replace("\n", "")
- arp_request = packet_utils.construct_arp_packet(
- src_mac="00:01:02:03:04:05",
- dst_mac=packet_utils.ETHER_BROADCAST_MAC_ADDRESS,
- src_ip="192.168.1.2",
- dst_ip="192.168.1.1",
- op=packet_utils.ARP_REQUEST_OP
- )
- asserts.assert_equal(expect_arp_request, arp_request)
-
- def test_arp_reply(self):
- # Using scapy to generate unicast arp request packet:
- # eth = Ether(src="01:02:03:04:05:06", dst="00:01:02:03:04:05")
- # arp = ARP(op=2, pdst="192.168.1.2", \
- # hwsrc="01:02:03:04:05:06", \
- # psrc="192.168.1.1", \
- # hwdst="00:01:02:03:04:05")
- # pkt = eth/arp
- expect_arp_reply = """
- 00010203040501020304050608060001080006040002010203040506c0a80101000102030405c0a80102
- """.upper().replace(" ", "").replace("\n", "")
- arp_reply = packet_utils.construct_arp_packet(
- src_mac="01:02:03:04:05:06",
- dst_mac="00:01:02:03:04:05",
- src_ip="192.168.1.1",
- dst_ip="192.168.1.2",
- op=packet_utils.ARP_REPLY_OP
- )
- asserts.assert_equal(expect_arp_reply, arp_reply)
diff --git a/staticlibs/tests/unit/host/python/run_tests.py b/staticlibs/tests/unit/host/python/run_tests.py
index 498dbaf..fa6a310 100644
--- a/staticlibs/tests/unit/host/python/run_tests.py
+++ b/staticlibs/tests/unit/host/python/run_tests.py
@@ -18,7 +18,6 @@
from host.python.adb_utils_test import TestAdbUtils
from host.python.apf_utils_test import TestApfUtils
from host.python.assert_utils_test import TestAssertUtils
-from host.python.packet_utils_test import TestPacketUtils
from mobly import suite_runner
@@ -32,5 +31,5 @@
sys.argv.pop(1)
# TODO: make the tests can be executed without manually list classes.
suite_runner.run_suite(
- [TestAssertUtils, TestAdbUtils, TestApfUtils, TestPacketUtils], sys.argv
+ [TestAssertUtils, TestAdbUtils, TestApfUtils], sys.argv
)
diff --git a/staticlibs/testutils/host/python/apf_test_base.py b/staticlibs/testutils/host/python/apf_test_base.py
index 33b3838..6a62e21 100644
--- a/staticlibs/testutils/host/python/apf_test_base.py
+++ b/staticlibs/testutils/host/python/apf_test_base.py
@@ -70,6 +70,9 @@
# Enable doze mode to activate APF.
adb_utils.set_doze_mode(self.clientDevice, True)
+ # wait for APF to become active.
+ time.sleep(3)
+
def teardown_class(self):
adb_utils.set_doze_mode(self.clientDevice, False)
tether_utils.cleanup_tethering_for_upstream_type(
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index 1648d36..4835c23 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -148,6 +148,38 @@
else:
return []
+def get_exclude_all_host_ipv6_multicast_addresses(
+ ad: android_device.AndroidDevice, iface_name: str
+) -> list[str]:
+ """Retrieves the IPv6 multicast addresses of a given interface on an Android device.
+
+ This function executes an ADB shell command (`ip -6 maddr show`) to get the
+ network interface information and extracts the IPv6 multicast address from the output.
+ If devices have no IPv6 multicast address, raise PatternNotFoundException.
+
+ Args:
+ ad: The Android device object.
+ iface_name: The name of the network interface (e.g., "wlan0").
+
+ Returns:
+ The IPv6 multicast addresses of the interface as a list of string.
+ Return empty list if no IPv6 multicast address.
+ """
+ # output format
+ # 47: wlan0
+ # inet6 ff02::1:ff99:37b0
+ # inet6 ff02::1:ffb7:cba2 users 2
+ # inet6 ff02::1
+ # inet6 ff01::1
+ output = adb_utils.adb_shell(ad, f"ip -6 maddr show {iface_name}")
+ pattern = r"inet6\s+([a-fA-F0-9:]+)(?:\s+users\s+\d+)?"
+ matches = re.findall(pattern, output)
+
+ if matches:
+ return [addr for addr in matches if addr not in ("ff02::1", "ff01::1")]
+ else:
+ return []
+
def get_hardware_address(
ad: android_device.AndroidDevice, iface_name: str
) -> str:
@@ -407,10 +439,28 @@
def wrapper(self, *args, **kwargs):
asserts.abort_class_if(
(not hasattr(self, 'client')) or (not hasattr(self.client, 'isAtLeastB')),
- "client device is not B+"
+ "no valid client attribute"
)
- asserts.abort_class_if(not self.client.isAtLeastB(), "not B+")
+ asserts.abort_class_if(not self.client.isAtLeastB(), "client device is not Android B+")
+ return test_function(self, *args, **kwargs)
+ return wrapper
+ return decorator
+
+def apf_ram_at_least(size):
+ def decorator(test_function):
+ @functools.wraps(test_function)
+ def wrapper(self, *args, **kwargs):
+ asserts.abort_class_if(
+ (not hasattr(self, 'clientDevice')) or (not hasattr(self, 'client_iface_name')),
+ "no valid client attribute"
+ )
+
+ caps = get_apf_capabilities(self.clientDevice, self.client_iface_name)
+ asserts.skip_if(
+ caps.apf_ram_size < size,
+ f'APF rame size {caps.apf_ram_size} < {size}'
+ )
return test_function(self, *args, **kwargs)
return wrapper
return decorator
diff --git a/staticlibs/testutils/host/python/packet_utils.py b/staticlibs/testutils/host/python/packet_utils.py
deleted file mode 100644
index b613f03..0000000
--- a/staticlibs/testutils/host/python/packet_utils.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# 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.
-from ipaddress import IPv4Address
-from socket import inet_aton
-
-ETHER_BROADCAST_MAC_ADDRESS = "FF:FF:FF:FF:FF:FF"
-ARP_REQUEST_OP = 1
-ARP_REPLY_OP = 2
-
-"""
-This variable defines a template for constructing ARP packets in hexadecimal format.
-It's used to provide the common fields for ARP packet, and replaced needed fields when constructing
-"""
-ARP_TEMPLATE = (
- # Ether Header (14 bytes)
- "{dst_mac}" + # DA
- "{src_mac}" + # SA
- "0806" + # ARP
- # ARP Header (28 bytes)
- "0001" + # Hardware type (Ethernet)
- "0800" + # Protocol type (IPv4)
- "06" + # hardware address length
- "04" + # protocol address length
- "{opcode}" + # opcode
- "{sender_mac}" + # sender MAC
- "{sender_ip}" + # sender IP
- "{target_mac}" + # target MAC
- "{target_ip}" # target IP
-)
-
-def construct_arp_packet(src_mac, dst_mac, src_ip, dst_ip, op) -> str:
- """Constructs an ARP packet as a hexadecimal string.
-
- This function creates an ARP packet by filling in the required fields
- in a predefined ARP packet template.
-
- Args:
- src_mac: The MAC address of the sender. (e.g. "11:22:33:44:55:66")
- dst_mac: The MAC address of the recipient. (e.g. "aa:bb:cc:dd:ee:ff")
- src_ip: The IP address of the sender. (e.g. "1.1.1.1")
- dst_ip: The IP address of the target machine. (e.g. "2.2.2.2")
- op: The op code of the ARP packet, refer to ARP_*_OP
-
- Returns:
- A string representing the ARP packet in hexadecimal format.
- """
- # Replace the needed fields from packet template
- arp_pkt = ARP_TEMPLATE.format(
- dst_mac=dst_mac.replace(":",""),
- src_mac=src_mac.replace(":",""),
- opcode=str(op).rjust(4, "0"),
- sender_mac=src_mac.replace(":",""),
- sender_ip=inet_aton(src_ip).hex(),
- target_mac=("000000000000" if op == ARP_REQUEST_OP else dst_mac.replace(":", "")),
- target_ip=inet_aton(dst_ip).hex()
- )
-
- # always convert to upper case hex string
- return arp_pkt.upper()
\ No newline at end of file
diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java
index d694637..3fc2af0 100644
--- a/tests/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java
@@ -56,6 +56,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
import static android.net.NetworkCapabilities.TRANSPORT_SATELLITE;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.NetworkCapabilities.TRANSPORT_THREAD;
import static android.net.NetworkCapabilities.TRANSPORT_USB;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
@@ -1532,4 +1533,93 @@
nc.setReservationId(43);
assertNotEquals(nc, other);
}
+
+ @Test
+ public void testMatchNonThreadLocalNetworks_equals() {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+ nc.setMatchNonThreadLocalNetworks(true);
+ final NetworkCapabilities other = new NetworkCapabilities(nc);
+ assertEquals(nc, other);
+
+ nc.setMatchNonThreadLocalNetworks(false);
+ assertNotEquals(nc, other);
+ }
+
+ @Test
+ public void testMatchNonThreadLocalNetworks_enabled() {
+ doTestMatchNonThreadLocalNetworks(true);
+ }
+
+ @Test
+ public void testMatchNonThreadLocalNetworks_disabled() {
+ doTestMatchNonThreadLocalNetworks(false);
+ }
+
+ private void doTestMatchNonThreadLocalNetworks(boolean enabled) {
+ // Setup request NCs.
+ final NetworkCapabilities noTransportRequestNc = new NetworkCapabilities();
+ final NetworkCapabilities threadRequestNc =
+ new NetworkCapabilities.Builder().addTransportType(TRANSPORT_THREAD).build();
+ final NetworkCapabilities wifiRequestNc =
+ new NetworkCapabilities.Builder().addTransportType(TRANSPORT_WIFI).build();
+ final NetworkCapabilities multiTransportRequestNc =
+ new NetworkCapabilities.Builder().addTransportType(
+ TRANSPORT_THREAD).addTransportType(TRANSPORT_WIFI).build();
+
+ // Setup network NCs.
+ final NetworkCapabilities localNoTransportNc = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK).build();
+ final NetworkCapabilities localThreadsNc = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .addTransportType(TRANSPORT_THREAD).build();
+ final NetworkCapabilities localWifiNc = new NetworkCapabilities.Builder()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .addTransportType(TRANSPORT_WIFI).build();
+ final NetworkCapabilities wanWifiNc = new NetworkCapabilities.Builder()
+ .addTransportType(TRANSPORT_WIFI).build();
+
+ // Mark flags accordingly.
+ noTransportRequestNc.setMatchNonThreadLocalNetworks(enabled);
+ threadRequestNc.setMatchNonThreadLocalNetworks(enabled);
+ wifiRequestNc.setMatchNonThreadLocalNetworks(enabled);
+ multiTransportRequestNc.setMatchNonThreadLocalNetworks(enabled);
+
+ if (enabled) {
+ // A request with no specific transport matches all networks.
+ assertTrue(noTransportRequestNc.satisfiedByNetworkCapabilities(localNoTransportNc));
+ assertTrue(noTransportRequestNc.satisfiedByNetworkCapabilities(localWifiNc));
+ } else {
+ // A request with no specific transport only matches thread networks.
+ assertFalse(noTransportRequestNc.satisfiedByNetworkCapabilities(localNoTransportNc));
+ assertFalse(noTransportRequestNc.satisfiedByNetworkCapabilities(localWifiNc));
+ }
+ assertTrue(noTransportRequestNc.satisfiedByNetworkCapabilities(localThreadsNc));
+ assertTrue(noTransportRequestNc.satisfiedByNetworkCapabilities(wanWifiNc));
+
+ // A request with TRANSPORT_THREAD only matches thread networks.
+ assertFalse(threadRequestNc.satisfiedByNetworkCapabilities(localNoTransportNc));
+ assertTrue(threadRequestNc.satisfiedByNetworkCapabilities(localThreadsNc));
+ assertFalse(threadRequestNc.satisfiedByNetworkCapabilities(localWifiNc));
+ assertFalse(threadRequestNc.satisfiedByNetworkCapabilities(wanWifiNc));
+
+ assertFalse(multiTransportRequestNc.satisfiedByNetworkCapabilities(localNoTransportNc));
+ assertTrue(multiTransportRequestNc.satisfiedByNetworkCapabilities(localThreadsNc));
+ assertTrue(multiTransportRequestNc.satisfiedByNetworkCapabilities(wanWifiNc));
+ if (enabled) {
+ assertTrue(multiTransportRequestNc.satisfiedByNetworkCapabilities(localWifiNc));
+ } else {
+ // A request with multiple transports only matches thread networks.
+ assertFalse(multiTransportRequestNc.satisfiedByNetworkCapabilities(localWifiNc));
+ }
+
+ assertFalse(wifiRequestNc.satisfiedByNetworkCapabilities(localNoTransportNc));
+ assertFalse(wifiRequestNc.satisfiedByNetworkCapabilities(localThreadsNc));
+ assertTrue(wifiRequestNc.satisfiedByNetworkCapabilities(wanWifiNc));
+ if (enabled) {
+ assertTrue(wifiRequestNc.satisfiedByNetworkCapabilities(localWifiNc));
+ } else {
+ // A request without TRANSPORT_THREAD matches nothing.
+ assertFalse(wifiRequestNc.satisfiedByNetworkCapabilities(localWifiNc));
+ }
+ }
}
diff --git a/tests/cts/multidevices/apfv6_test.py b/tests/cts/multidevices/apfv6_test.py
index fb45f4a..2404966 100644
--- a/tests/cts/multidevices/apfv6_test.py
+++ b/tests/cts/multidevices/apfv6_test.py
@@ -14,10 +14,23 @@
from mobly import asserts
from scapy.layers.inet import IP, ICMP, IPOption_Router_Alert
-from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest, ICMPv6EchoReply
-from scapy.layers.l2 import Ether
+from scapy.layers.inet6 import (
+ IPv6,
+ IPv6ExtHdrHopByHop,
+ ICMPv6EchoRequest,
+ ICMPv6EchoReply,
+ ICMPv6MLQuery2,
+ ICMPv6MLReport2,
+ ICMPv6MLDMultAddrRec,
+ ICMPv6NDOptSrcLLAddr,
+ ICMPv6NDOptDstLLAddr,
+ ICMPv6ND_NS,
+ ICMPv6ND_NA,
+ RouterAlert
+)
+from scapy.layers.l2 import ARP, Ether
from scapy.contrib.igmpv3 import IGMPv3, IGMPv3mq, IGMPv3mr, IGMPv3gr
-from net_tests_utils.host.python import apf_test_base, apf_utils, adb_utils, assert_utils, packet_utils
+from net_tests_utils.host.python import apf_test_base, apf_utils, adb_utils, assert_utils
APFV6_VERSION = 6000
ARP_OFFLOAD_REPLY_LEN = 60
@@ -40,51 +53,75 @@
super().teardown_class()
def test_unicast_arp_request_offload(self):
- arp_request = packet_utils.construct_arp_packet(
- src_mac=self.server_mac_address,
- dst_mac=self.client_mac_address,
- src_ip=self.server_ipv4_addresses[0],
- dst_ip=self.client_ipv4_addresses[0],
- op=packet_utils.ARP_REQUEST_OP
+ eth = Ether(src=self.server_mac_address, dst=self.client_mac_address)
+ arp = ARP(
+ op=1,
+ psrc=self.server_ipv4_addresses[0],
+ pdst=self.client_ipv4_addresses[0],
+ hwsrc=self.server_mac_address
)
+ arp_request = bytes(eth/arp).hex()
- arp_reply = packet_utils.construct_arp_packet(
- src_mac=self.client_mac_address,
- dst_mac=self.server_mac_address,
- src_ip=self.client_ipv4_addresses[0],
- dst_ip=self.server_ipv4_addresses[0],
- op=packet_utils.ARP_REPLY_OP
+ eth = Ether(src=self.client_mac_address, dst=self.server_mac_address)
+ arp = ARP(
+ op=2,
+ psrc=self.client_ipv4_addresses[0],
+ pdst=self.server_ipv4_addresses[0],
+ hwsrc=self.client_mac_address,
+ hwdst=self.server_mac_address
)
+ expected_arp_reply = bytes(eth/arp).hex()
# Add zero padding up to 60 bytes, since APFv6 ARP offload always sent out 60 bytes reply
- arp_reply = arp_reply.ljust(ARP_OFFLOAD_REPLY_LEN * 2, "0")
+ expected_arp_reply = expected_arp_reply.ljust(ARP_OFFLOAD_REPLY_LEN * 2, "0")
self.send_packet_and_expect_reply_received(
- arp_request, "DROPPED_ARP_REQUEST_REPLIED", arp_reply
+ arp_request, "DROPPED_ARP_REQUEST_REPLIED", expected_arp_reply
)
def test_broadcast_arp_request_offload(self):
- arp_request = packet_utils.construct_arp_packet(
- src_mac=self.server_mac_address,
- dst_mac=packet_utils.ETHER_BROADCAST_MAC_ADDRESS,
- src_ip=self.server_ipv4_addresses[0],
- dst_ip=self.client_ipv4_addresses[0],
- op=packet_utils.ARP_REQUEST_OP
+ eth = Ether(src=self.server_mac_address, dst='ff:ff:ff:ff:ff:ff')
+ arp = ARP(
+ op=1,
+ psrc=self.server_ipv4_addresses[0],
+ pdst=self.client_ipv4_addresses[0],
+ hwsrc=self.server_mac_address
)
+ arp_request = bytes(eth/arp).hex()
- arp_reply = packet_utils.construct_arp_packet(
- src_mac=self.client_mac_address,
- dst_mac=self.server_mac_address,
- src_ip=self.client_ipv4_addresses[0],
- dst_ip=self.server_ipv4_addresses[0],
- op=packet_utils.ARP_REPLY_OP
+ eth = Ether(src=self.client_mac_address, dst=self.server_mac_address)
+ arp = ARP(
+ op=2,
+ psrc=self.client_ipv4_addresses[0],
+ pdst=self.server_ipv4_addresses[0],
+ hwsrc=self.client_mac_address,
+ hwdst=self.server_mac_address
)
+ expected_arp_reply = bytes(eth/arp).hex()
# Add zero padding up to 60 bytes, since APFv6 ARP offload always sent out 60 bytes reply
- arp_reply = arp_reply.ljust(ARP_OFFLOAD_REPLY_LEN * 2, "0")
+ expected_arp_reply = expected_arp_reply.ljust(ARP_OFFLOAD_REPLY_LEN * 2, "0")
self.send_packet_and_expect_reply_received(
- arp_request, "DROPPED_ARP_REQUEST_REPLIED", arp_reply
+ arp_request, "DROPPED_ARP_REQUEST_REPLIED", expected_arp_reply
+ )
+
+ def test_non_dad_ipv6_neighbor_solicitation_offload(self):
+ eth = Ether(src=self.server_mac_address, dst=self.client_mac_address)
+ ip = IPv6(src=self.server_ipv6_addresses[0], dst=self.client_ipv6_addresses[0])
+ icmpv6 = ICMPv6ND_NS(tgt=self.client_ipv6_addresses[0])
+ opt = ICMPv6NDOptSrcLLAddr(lladdr=self.server_mac_address)
+ neighbor_solicitation = bytes(eth/ip/icmpv6/opt).hex()
+
+ eth = Ether(src=self.client_mac_address, dst=self.server_mac_address)
+ ip = IPv6(src=self.client_ipv6_addresses[0], dst=self.server_ipv6_addresses[0])
+ icmpv6 = ICMPv6ND_NA(tgt=self.client_ipv6_addresses[0], R=1, S=1, O=1)
+ opt = ICMPv6NDOptDstLLAddr(lladdr=self.client_mac_address)
+ expected_neighbor_advertisement = bytes(eth/ip/icmpv6/opt).hex()
+ self.send_packet_and_expect_reply_received(
+ neighbor_solicitation,
+ "DROPPED_IPV6_NS_REPLIED_NON_DAD",
+ expected_neighbor_advertisement
)
@apf_utils.at_least_B()
@@ -103,6 +140,7 @@
)
@apf_utils.at_least_B()
+ @apf_utils.apf_ram_at_least(3000)
def test_ipv6_icmp_echo_request_offload(self):
eth = Ether(src=self.server_mac_address, dst=self.client_mac_address)
ip = IPv6(src=self.server_ipv6_addresses[0], dst=self.client_ipv6_addresses[0])
@@ -161,3 +199,28 @@
self.clientDevice,
f'ip addr del {addr}/32 dev {self.client_iface_name}'
)
+
+ @apf_utils.at_least_B()
+ @apf_utils.apf_ram_at_least(3000)
+ def test_mldv2_general_query_offload(self):
+ ether = Ether(src=self.server_mac_address, dst='33:33:00:00:00:01')
+ ip = IPv6(src=self.server_ipv6_addresses[0], dst='ff02::1', hlim=1)
+ hopOpts = IPv6ExtHdrHopByHop(options=[RouterAlert(otype=5)])
+ mld = ICMPv6MLQuery2()
+ mldv2_general_query = bytes(ether/ip/hopOpts/mld).hex()
+
+ ether = Ether(src=self.client_mac_address, dst='33:33:00:00:00:16')
+ ip = IPv6(src=self.client_ipv6_addresses[0], dst='ff02::16', hlim=1)
+
+ mcast_addrs = apf_utils.get_exclude_all_host_ipv6_multicast_addresses(
+ self.clientDevice, self.client_iface_name
+ )
+
+ mld_records = []
+ for addr in mcast_addrs:
+ mld_records.append(ICMPv6MLDMultAddrRec(dst=addr, rtype=2))
+ mld = ICMPv6MLReport2(records=mld_records)
+ expected_mldv2_report = bytes(ether/ip/hopOpts/mld).hex()
+ self.send_packet_and_expect_reply_received(
+ mldv2_general_query, "DROPPED_IPV6_MLD_V2_GENERAL_QUERY_REPLIED", expected_mldv2_report
+ )
diff --git a/tests/cts/net/AndroidManifest.xml b/tests/cts/net/AndroidManifest.xml
index 098cc0a..acf89be 100644
--- a/tests/cts/net/AndroidManifest.xml
+++ b/tests/cts/net/AndroidManifest.xml
@@ -26,6 +26,7 @@
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
<uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
diff --git a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
index df4dab5..d531e7a 100644
--- a/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
+++ b/tests/cts/net/src/android/net/cts/DscpPolicyTest.kt
@@ -774,7 +774,9 @@
runAsShell(MANAGE_TEST_NETWORKS) { agent.register() }
// Without the fix, this will crash the system with SIGSEGV.
agent.sendAddDscpPolicy(DscpPolicy.Builder(1, 1).build())
- agent.expectCallback<OnDscpPolicyStatusUpdated>()
+ // Will receive OnNetworkCreated first if the agent is created early. To avoid reading
+ // the flag here, use eventuallyExpect.
+ agent.eventuallyExpect<OnDscpPolicyStatusUpdated>()
}
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 4c3bce0..4703ac7 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -15,6 +15,7 @@
*/
package android.net.cts
+import android.Manifest.permission.NEARBY_WIFI_DEVICES
import android.Manifest.permission.NETWORK_SETTINGS
import android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE
import android.app.Instrumentation
@@ -247,6 +248,12 @@
@Before
fun setUp() {
instrumentation.getUiAutomation().adoptShellPermissionIdentity()
+ if (SdkLevel.isAtLeastT()) {
+ instrumentation.getUiAutomation().grantRuntimePermission(
+ "android.net.cts",
+ NEARBY_WIFI_DEVICES
+ )
+ }
mHandlerThread.start()
}
@@ -751,12 +758,24 @@
tryTest {
// This process is not the carrier service UID, so allowedUids should be ignored in all
// the following cases.
- doTestAllowedUidsWithSubId(defaultSubId, TRANSPORT_CELLULAR, uid,
- expectUidsPresent = false)
- doTestAllowedUidsWithSubId(defaultSubId, TRANSPORT_WIFI, uid,
- expectUidsPresent = false)
- doTestAllowedUidsWithSubId(defaultSubId, TRANSPORT_BLUETOOTH, uid,
- expectUidsPresent = false)
+ doTestAllowedUidsWithSubId(
+ defaultSubId,
+ TRANSPORT_CELLULAR,
+ uid,
+ expectUidsPresent = false
+ )
+ doTestAllowedUidsWithSubId(
+ defaultSubId,
+ TRANSPORT_WIFI,
+ uid,
+ expectUidsPresent = false
+ )
+ doTestAllowedUidsWithSubId(
+ defaultSubId,
+ TRANSPORT_BLUETOOTH,
+ uid,
+ expectUidsPresent = false
+ )
// The tools to set the carrier service package override do not exist before U,
// so there is no way to test the rest of this test on < U.
@@ -774,9 +793,11 @@
val timeout = SystemClock.elapsedRealtime() + DEFAULT_TIMEOUT_MS
while (true) {
if (SystemClock.elapsedRealtime() > timeout) {
- fail("Couldn't make $servicePackage the service package for $defaultSubId: " +
+ fail(
+ "Couldn't make $servicePackage the service package for $defaultSubId: " +
"dumpsys connectivity".execute().split("\n")
- .filter { it.contains("Logical slot = $defaultSlotIndex.*") })
+ .filter { it.contains("Logical slot = $defaultSlotIndex.*") }
+ )
}
if ("dumpsys connectivity"
.execute()
@@ -799,10 +820,18 @@
// TODO(b/315136340): Allow ownerUid to see allowedUids and enable below test case
// doTestAllowedUids(defaultSubId, TRANSPORT_WIFI, uid, expectUidsPresent = true)
}
- doTestAllowedUidsWithSubId(defaultSubId, TRANSPORT_BLUETOOTH, uid,
- expectUidsPresent = false)
- doTestAllowedUidsWithSubId(defaultSubId, intArrayOf(TRANSPORT_CELLULAR, TRANSPORT_WIFI),
- uid, expectUidsPresent = false)
+ doTestAllowedUidsWithSubId(
+ defaultSubId,
+ TRANSPORT_BLUETOOTH,
+ uid,
+ expectUidsPresent = false
+ )
+ doTestAllowedUidsWithSubId(
+ defaultSubId,
+ intArrayOf(TRANSPORT_CELLULAR, TRANSPORT_WIFI),
+ uid,
+ expectUidsPresent = false
+ )
}
}
@@ -1860,8 +1889,10 @@
it.setTransportInfo(VpnTransportInfo(
VpnManager.TYPE_VPN_PLATFORM,
sessionId,
- /*bypassable=*/ false,
- /*longLivedTcpConnectionsExpensive=*/ false
+ /*bypassable=*/
+ false,
+ /*longLivedTcpConnectionsExpensive=*/
+ false
))
it.underlyingNetworks = listOf()
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
index babcba9..ee5b4ee 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSL2capProviderTest.kt
@@ -38,12 +38,14 @@
import android.net.NetworkSpecifier
import android.net.RouteInfo
import android.os.Build
+import android.os.Handler
import android.os.HandlerThread
import android.os.ParcelFileDescriptor
import com.android.server.net.L2capNetwork.L2capIpClient
import com.android.server.net.L2capPacketForwarder
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.RecorderCallback.CallbackEntry.Reserved
import com.android.testutils.RecorderCallback.CallbackEntry.Unavailable
import com.android.testutils.TestableNetworkCallback
@@ -59,6 +61,7 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.isNull
import org.mockito.Mockito.doAnswer
@@ -394,4 +397,34 @@
val cb2 = requestNetwork(nr)
cb2.expectAvailableCallbacks(anyNetwork(), validated = false)
}
+
+ /** Test to ensure onLost() is sent before onUnavailable() when the network is torn down. */
+ @Test
+ fun testClientNetwork_gracefulTearDown() {
+ val specifier = L2capNetworkSpecifier.Builder()
+ .setRole(ROLE_CLIENT)
+ .setHeaderCompression(HEADER_COMPRESSION_NONE)
+ .setRemoteAddress(MacAddress.fromBytes(REMOTE_MAC))
+ .setPsm(PSM)
+ .build()
+
+ val nr = REQUEST.copyWithSpecifier(specifier)
+ val cb = requestNetwork(nr)
+ cb.expectAvailableCallbacks(anyNetwork(), validated = false)
+
+ // Capture the L2capPacketForwarder callback object to tear down the network.
+ val handlerCaptor = ArgumentCaptor.forClass(Handler::class.java)
+ val forwarderCbCaptor = ArgumentCaptor.forClass(L2capPacketForwarder.ICallback::class.java)
+ verify(providerDeps).createL2capPacketForwarder(
+ handlerCaptor.capture(), any(), any(), any(), forwarderCbCaptor.capture())
+ val handler = handlerCaptor.value
+ val forwarderCb = forwarderCbCaptor.value
+
+ // Trigger a forwarding error
+ handler.post { forwarderCb.onError() }
+ handler.waitForIdle(HANDLER_TIMEOUT_MS)
+
+ cb.expect<Lost>()
+ cb.expect<Unavailable>()
+ }
}
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
index 63daebc..89acf69 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -20,6 +20,7 @@
import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
import static android.net.NetworkStats.METERED_ALL;
import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.METERED_YES;
import static android.net.NetworkStats.ROAMING_ALL;
import static android.net.NetworkStats.ROAMING_NO;
import static android.net.NetworkStats.SET_ALL;
@@ -29,6 +30,8 @@
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
+import static com.android.server.net.NetworkStatsFactory.CONFIG_PER_UID_TAG_THROTTLING;
+import static com.android.server.net.NetworkStatsFactory.CONFIG_PER_UID_TAG_THROTTLING_THRESHOLD;
import static com.android.server.net.NetworkStatsFactory.kernelToTag;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
@@ -36,6 +39,9 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import android.content.Context;
@@ -52,12 +58,15 @@
import com.android.server.BpfNetMaps;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.com.android.testutils.SetFeatureFlagsRule;
+import com.android.testutils.com.android.testutils.SetFeatureFlagsRule.FeatureFlag;
import libcore.io.IoUtils;
import libcore.testing.io.TestIoUtils;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -66,6 +75,7 @@
import java.io.File;
import java.io.IOException;
import java.net.ProtocolException;
+import java.util.HashMap;
/** Tests for {@link NetworkStatsFactory}. */
@RunWith(DevSdkIgnoreRunner.class)
@@ -73,6 +83,7 @@
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
public class NetworkStatsFactoryTest extends NetworkStatsBaseTest {
private static final String CLAT_PREFIX = "v4-";
+ private static final int TEST_TAGS_PER_UID_THRESHOLD = 10;
private File mTestProc;
private NetworkStatsFactory mFactory;
@@ -80,6 +91,16 @@
@Mock private NetworkStatsFactory.Dependencies mDeps;
@Mock private BpfNetMaps mBpfNetMaps;
+ final HashMap<String, Boolean> mFeatureFlags = new HashMap<>();
+ // This will set feature flags from @FeatureFlag annotations
+ // into the map before setUp() runs.
+ @Rule
+ public final SetFeatureFlagsRule mSetFeatureFlagsRule =
+ new SetFeatureFlagsRule((name, enabled) -> {
+ mFeatureFlags.put(name, enabled);
+ return null;
+ }, (name) -> mFeatureFlags.getOrDefault(name, false));
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -90,6 +111,10 @@
// related to networkStatsFactory is compiled to a minimal native library and loaded here.
System.loadLibrary("networkstatsfactorytestjni");
doReturn(mBpfNetMaps).when(mDeps).createBpfNetMaps(any());
+ doAnswer(invocation -> mFeatureFlags.getOrDefault((String) invocation.getArgument(1), true))
+ .when(mDeps).isFeatureNotChickenedOut(any(), anyString());
+ doReturn(TEST_TAGS_PER_UID_THRESHOLD).when(mDeps)
+ .getDeviceConfigPropertyInt(eq(CONFIG_PER_UID_TAG_THROTTLING_THRESHOLD), anyInt());
mFactory = new NetworkStatsFactory(mContext, mDeps);
mFactory.updateUnderlyingNetworkInfos(new UnderlyingNetworkInfo[0]);
@@ -498,6 +523,71 @@
assertValues(removedUidsStats, TEST_IFACE, UID_GREEN, 64L, 3L, 1024L, 8L);
}
+ @FeatureFlag(name = CONFIG_PER_UID_TAG_THROTTLING)
+ @Test
+ public void testFilterTooManyTags_featureEnabled() throws Exception {
+ doTestFilterTooManyTags(true);
+ }
+
+ @FeatureFlag(name = CONFIG_PER_UID_TAG_THROTTLING, enabled = false)
+ @Test
+ public void testFilterTooManyTags_featureDisabled() throws Exception {
+ doTestFilterTooManyTags(false);
+ }
+
+ private void doTestFilterTooManyTags(boolean supportPerUidTagThrottling) throws Exception {
+ // Add entries for UID_RED which reaches the threshold.
+ final NetworkStats statsWithManyTags = new NetworkStats(0L, TEST_TAGS_PER_UID_THRESHOLD);
+ for (int tag = 1; tag <= TEST_TAGS_PER_UID_THRESHOLD; tag++) {
+ statsWithManyTags.combineValues(
+ new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT, tag,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12L, 18L, 14L, 1L, 0L));
+ }
+ doReturn(statsWithManyTags).when(mDeps).getNetworkStatsDetail();
+ final NetworkStats stats1 = mFactory.readNetworkStatsDetail();
+ assertEquals(stats1.size(), TEST_TAGS_PER_UID_THRESHOLD);
+
+ // Add 2 new entries with pre-existing tag, verify they can be added no matter what.
+ final NetworkStats newDiffWithExistingTag = new NetworkStats(0L, 2);
+ // This one should be added as a new entry, as the metered data doesn't exist yet.
+ newDiffWithExistingTag.combineValues(
+ new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
+ TEST_TAGS_PER_UID_THRESHOLD,
+ METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 3L, 5L, 8L, 1L, 1L));
+ // This one should be combined into existing entry.
+ newDiffWithExistingTag.combineValues(
+ new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
+ TEST_TAGS_PER_UID_THRESHOLD,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1L, 2L, 3L, 4L, 5L));
+
+ doReturn(newDiffWithExistingTag).when(mDeps).getNetworkStatsDetail();
+ final NetworkStats stats2 = mFactory.readNetworkStatsDetail();
+ assertEquals(stats2.size(), TEST_TAGS_PER_UID_THRESHOLD + 1);
+ assertValues(stats2, TEST_IFACE, UID_RED, SET_DEFAULT, TEST_TAGS_PER_UID_THRESHOLD,
+ METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 3L, 5L, 8L, 1L, 1L);
+ assertValues(stats2, TEST_IFACE, UID_RED, SET_DEFAULT, TEST_TAGS_PER_UID_THRESHOLD,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13L, 20L, 17L, 5L, 5L);
+
+ // Add an entry which exceeds the threshold, verify the entry is filtered out.
+ final NetworkStats newDiffWithNonExistingTag = new NetworkStats(0L, 1);
+ newDiffWithNonExistingTag.combineValues(
+ new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
+ TEST_TAGS_PER_UID_THRESHOLD + 1,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12L, 18L, 14L, 1L, 0L));
+ doReturn(newDiffWithNonExistingTag).when(mDeps).getNetworkStatsDetail();
+ final NetworkStats stats3 = mFactory.readNetworkStatsDetail();
+ if (supportPerUidTagThrottling) {
+ assertEquals(stats3.size(), TEST_TAGS_PER_UID_THRESHOLD + 1);
+ assertNoStatsEntry(stats3, TEST_IFACE, UID_RED, SET_DEFAULT,
+ TEST_TAGS_PER_UID_THRESHOLD + 1);
+ } else {
+ assertEquals(stats3.size(), TEST_TAGS_PER_UID_THRESHOLD + 2);
+ assertValues(stats3, TEST_IFACE, UID_RED, SET_DEFAULT,
+ TEST_TAGS_PER_UID_THRESHOLD + 1,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12L, 18L, 14L, 1L, 0L);
+ }
+ }
+
private NetworkStats buildEmptyStats() {
return new NetworkStats(SystemClock.elapsedRealtime(), 0);
}
diff --git a/thread/docs/build-an-android-border-router.md b/thread/docs/build-an-android-border-router.md
index f90a23b..2687e26 100644
--- a/thread/docs/build-an-android-border-router.md
+++ b/thread/docs/build-an-android-border-router.md
@@ -380,10 +380,12 @@
[config_thread.xml](https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Connectivity/service/ServiceConnectivityResources/res/values/config_thread.xml)
for the full list.
-Typically, you must change the `config_thread_vendor_name`,
-`config_thread_vendor_oui` and `config_thread_model_name` to your vendor or
-product values. Those values will be included in the `_meshcop._udp` mDNS
-service which is always advertised by a Thread Border Router.
+Typically, you must set `config_thread_border_router_default_enabled` to `true`
+to enable your device as a Thread Border Router, and change the
+`config_thread_vendor_name`, `config_thread_vendor_oui` and
+`config_thread_model_name` to your vendor or product values. Those values will
+be included in the `_meshcop._udp` mDNS service which is always advertised by a
+Thread Border Router.
To add the overlay, you need to create a new `ConnectivityOverlayOrange`
runtime_resource_overlay target for your Orange device. Create a new
@@ -436,6 +438,7 @@
```
- `config_thread.xml`:
```
+ <bool name="config_thread_border_router_default_enabled">true</bool>
<string translatable="false" name="config_thread_vendor_name">Banana Inc.</string>
<string translatable="false" name="config_thread_vendor_oui">AC:DE:48</string>
<string translatable="false" name="config_thread_model_name">Orange</string>
diff --git a/thread/tests/integration/AndroidManifest.xml b/thread/tests/integration/AndroidManifest.xml
index a049184..8bee1e1 100644
--- a/thread/tests/integration/AndroidManifest.xml
+++ b/thread/tests/integration/AndroidManifest.xml
@@ -24,6 +24,7 @@
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.THREAD_NETWORK_PRIVILEGED"/>
<uses-permission android:name="android.permission.NETWORK_SETTINGS"/>
+ <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
<uses-permission android:name="android.permission.INTERNET"/>
<application android:debuggable="true">
diff --git a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
index b608c5d..70f17ff 100644
--- a/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
+++ b/thread/tests/integration/src/android/net/thread/ThreadIntegrationTest.java
@@ -27,6 +27,8 @@
import static android.net.thread.utils.ThreadNetworkControllerWrapper.JOIN_TIMEOUT;
import static android.os.SystemClock.elapsedRealtime;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
import static com.android.testutils.TestPermissionUtil.runAsShell;
@@ -121,6 +123,12 @@
@Before
public void setUp() throws Exception {
+ getInstrumentation()
+ .getUiAutomation()
+ .grantRuntimePermission(
+ "com.android.thread.tests.integration",
+ "android.permission.NEARBY_WIFI_DEVICES");
+
mExecutor = Executors.newSingleThreadExecutor();
mFtd = new FullThreadDevice(10 /* nodeId */);
mOtCtl = new OtDaemonController();