Merge changes I1707d28e,Ibb4a2916 into main
* changes:
Dump top distinct tag counts per uid
Support per uid tag throttling to NetworkStats
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/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/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index f6dbf6c..28b46c1 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -49,6 +49,7 @@
<!-- 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/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index bfb51da..b9b590b 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -113,6 +113,7 @@
import static android.net.NetworkCapabilities.RES_ID_UNSET;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+import static android.net.NetworkCapabilities.TRANSPORT_THREAD;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST;
@@ -429,6 +430,7 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import java.util.function.Predicate;
/**
* @hide
@@ -4893,7 +4895,11 @@
// the destroyed flag is only just above the "current satisfier wins"
// tie-breaker. But technically anything that affects scoring should rematch.
rematchAllNetworksAndRequests();
- mHandler.postDelayed(() -> nai.disconnect(), timeoutMs);
+ if (mQueueNetworkAgentEventsInSystemServer) {
+ mHandler.postDelayed(() -> disconnectAndDestroyNetwork(nai), timeoutMs);
+ } else {
+ mHandler.postDelayed(() -> nai.disconnect(), timeoutMs);
+ }
break;
}
}
@@ -5323,12 +5329,12 @@
private void handlePrivateDnsSettingsChanged() {
final PrivateDnsConfig cfg = mDnsManager.getPrivateDnsConfig();
- for (NetworkAgentInfo nai : mNetworkAgentInfos) {
+ forEachNetworkAgentInfo(nai -> {
handlePerNetworkPrivateDnsConfig(nai, cfg);
if (networkRequiresPrivateDnsValidation(nai)) {
handleUpdateLinkProperties(nai, new LinkProperties(nai.linkProperties));
}
- }
+ });
}
private void handlePerNetworkPrivateDnsConfig(NetworkAgentInfo nai, PrivateDnsConfig cfg) {
@@ -5443,15 +5449,18 @@
}
@VisibleForTesting
- protected static boolean shouldCreateNetworksImmediately(@NonNull NetworkCapabilities caps) {
+ protected boolean shouldCreateNetworksImmediately(@NonNull NetworkCapabilities caps) {
// The feature of creating the networks immediately was slated for U, but race conditions
// detected late required this was flagged off.
- // TODO : enable this in a Mainline update or in V, and re-enable the test for this
- // in NetworkAgentTest.
- return caps.hasCapability(NET_CAPABILITY_LOCAL_NETWORK);
+ // TODO : remove when it's determined that the code is stable
+ return mQueueNetworkAgentEventsInSystemServer
+ // Local network agents for Thread used to not create networks immediately,
+ // but other local agents (tethering, P2P) require this to function.
+ || (caps.hasCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ && !caps.hasTransport(TRANSPORT_THREAD));
}
- private static boolean shouldCreateNativeNetwork(@NonNull NetworkAgentInfo nai,
+ private boolean shouldCreateNativeNetwork(@NonNull NetworkAgentInfo nai,
@NonNull NetworkInfo.State state) {
if (nai.isCreated()) return false;
if (state == NetworkInfo.State.CONNECTED) return true;
@@ -5508,6 +5517,11 @@
if (DBG) {
log(nai.toShortString() + " disconnected, was satisfying " + nai.numNetworkRequests());
}
+
+ if (mQueueNetworkAgentEventsInSystemServer) {
+ nai.disconnect();
+ }
+
// Clear all notifications of this network.
mNotifier.clearNotification(nai.network.getNetId());
// A network agent has disconnected.
@@ -5651,16 +5665,16 @@
private void maybeDisableForwardRulesForDisconnectingNai(
@NonNull final NetworkAgentInfo disconnecting, final boolean sendCallbacks) {
// Step 1 : maybe this network was the upstream for one or more local networks.
- for (final NetworkAgentInfo local : mNetworkAgentInfos) {
- if (!local.isLocalNetwork()) continue;
+ forEachNetworkAgentInfo(local -> {
+ if (!local.isLocalNetwork()) return; // return@forEach
final NetworkRequest selector = local.localNetworkConfig.getUpstreamSelector();
- if (null == selector) continue;
+ if (null == selector) return; // return@forEach
final NetworkRequestInfo nri = mNetworkRequests.get(selector);
// null == nri can happen while disconnecting a network, because destroyNetwork() is
// called after removing all associated NRIs from mNetworkRequests.
- if (null == nri) continue;
+ if (null == nri) return; // return@forEach
final NetworkAgentInfo satisfier = nri.getSatisfier();
- if (disconnecting != satisfier) continue;
+ if (disconnecting != satisfier) return; // return@forEach
removeLocalNetworkUpstream(local, disconnecting);
// Set the satisfier to null immediately so that the LOCAL_NETWORK_CHANGED callback
// correctly contains null as an upstream.
@@ -5668,7 +5682,7 @@
nri.setSatisfier(null, null);
notifyNetworkCallbacks(local, CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
}
- }
+ });
// Step 2 : maybe this is a local network that had an upstream.
if (!disconnecting.isLocalNetwork()) return;
@@ -5841,12 +5855,12 @@
mNetworkRequests.put(req, nri);
// TODO: Consider update signal strength for other types.
if (req.isListen()) {
- for (final NetworkAgentInfo network : mNetworkAgentInfos) {
+ forEachNetworkAgentInfo(network -> {
if (req.networkCapabilities.hasSignalStrength()
&& network.satisfiesImmutableCapabilitiesOf(req)) {
updateSignalStrengthThresholds(network, "REGISTER", req);
}
- }
+ });
} else if (req.isRequest() && mNetworkRequestStateStatsMetrics != null) {
mNetworkRequestStateStatsMetrics.onNetworkRequestReceived(req);
}
@@ -6141,13 +6155,13 @@
private void removeListenRequestFromNetworks(@NonNull final NetworkRequest req) {
// listens don't have a singular affected Network. Check all networks to see
// if this listen request applies and remove it.
- for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+ forEachNetworkAgentInfo(nai -> {
nai.removeRequest(req.requestId);
if (req.networkCapabilities.hasSignalStrength()
&& nai.satisfiesImmutableCapabilitiesOf(req)) {
updateSignalStrengthThresholds(nai, "RELEASE", req);
}
- }
+ });
}
/**
@@ -6210,6 +6224,43 @@
}
}
+ /**
+ * Perform the specified operation on all networks.
+ *
+ * This method will run |op| exactly once for each network currently registered at the
+ * time it is called, even if |op| adds or removes networks.
+ *
+ * @param op the operation to perform. The operation is allowed to disconnect any number of
+ * networks.
+ */
+ private void forEachNetworkAgentInfo(final Consumer<NetworkAgentInfo> op) {
+ // Create a copy instead of iterating over the set so |op| is allowed to disconnect any
+ // number of networks (which removes it from mNetworkAgentInfos). The copy is cheap
+ // because there are at most a handful of NetworkAgents connected at any given time.
+ final NetworkAgentInfo[] nais = new NetworkAgentInfo[mNetworkAgentInfos.size()];
+ mNetworkAgentInfos.toArray(nais);
+ for (NetworkAgentInfo nai : nais) {
+ op.accept(nai);
+ }
+ }
+
+ /**
+ * Check whether the specified condition is true for any network.
+ *
+ * This method will stop evaluating as soon as the condition returns true for any network.
+ * The order of iteration is not contractual.
+ *
+ * @param condition the condition to verify. This method must not modify the set of networks in
+ * any way.
+ * @return whether {@code condition} returned true for any network
+ */
+ private boolean anyNetworkAgentInfo(final Predicate<NetworkAgentInfo> condition) {
+ for (int i = mNetworkAgentInfos.size() - 1; i >= 0; i--) {
+ if (condition.test(mNetworkAgentInfos.valueAt(i))) return true;
+ }
+ return false;
+ }
+
private RequestInfoPerUidCounter getRequestCounter(NetworkRequestInfo nri) {
return hasAnyPermissionOf(mContext,
nri.mPid, nri.mUid, NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
@@ -6551,14 +6602,14 @@
ensureRunningOnConnectivityServiceThread();
// Agent info scores and offer scores depend on whether cells yields to bad wifi.
final boolean avoidBadWifi = avoidBadWifi();
- for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+ forEachNetworkAgentInfo(nai -> {
nai.updateScoreForNetworkAgentUpdate();
if (avoidBadWifi) {
// If the device is now avoiding bad wifi, remove notifications that might have
// been put up when the device didn't.
mNotifier.clearNotification(nai.network.getNetId(), NotificationType.LOST_INTERNET);
}
- }
+ });
// UpdateOfferScore will update mNetworkOffers inline, so make a copy first.
final ArrayList<NetworkOfferInfo> offersToUpdate = new ArrayList<>(mNetworkOffers);
for (final NetworkOfferInfo noi : offersToUpdate) {
@@ -6896,19 +6947,15 @@
final Network underpinnedNetwork = ki.getUnderpinnedNetwork();
final Network network = ki.getNetwork();
- boolean networkFound = false;
- boolean underpinnedNetworkFound = false;
- for (NetworkAgentInfo n : mNetworkAgentInfos) {
- if (n.network.equals(network)) networkFound = true;
- if (n.everConnected() && n.network.equals(underpinnedNetwork)) {
- underpinnedNetworkFound = true;
- }
- }
+ final boolean networkFound =
+ anyNetworkAgentInfo(n -> n.network.equals(network));
// If the network no longer exists, then the keepalive should have been
// cleaned up already. There is no point trying to resume keepalives.
if (!networkFound) return;
+ final boolean underpinnedNetworkFound = anyNetworkAgentInfo(
+ n -> n.everConnected() && n.network.equals(underpinnedNetwork));
if (underpinnedNetworkFound) {
mKeepaliveTracker.handleMonitorAutomaticKeepalive(ki,
underpinnedNetwork.netId);
@@ -6978,7 +7025,11 @@
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork((Network) msg.obj);
if (nai == null) break;
nai.onPreventAutomaticReconnect();
- nai.disconnect();
+ if (mQueueNetworkAgentEventsInSystemServer) {
+ disconnectAndDestroyNetwork(nai);
+ } else {
+ nai.disconnect();
+ }
break;
case EVENT_SET_VPN_NETWORK_PREFERENCE:
handleSetVpnNetworkPreference((VpnNetworkPreferenceInfo) msg.obj);
@@ -7368,12 +7419,12 @@
return new UnderlyingNetworkInfo[0];
}
List<UnderlyingNetworkInfo> infoList = new ArrayList<>();
- for (NetworkAgentInfo nai : mNetworkAgentInfos) {
+ forEachNetworkAgentInfo(nai -> {
UnderlyingNetworkInfo info = createVpnInfo(nai);
if (info != null) {
infoList.add(info);
}
- }
+ });
return infoList.toArray(new UnderlyingNetworkInfo[infoList.size()]);
}
@@ -7451,11 +7502,11 @@
*/
private void propagateUnderlyingNetworkCapabilities(Network updatedNetwork) {
ensureRunningOnConnectivityServiceThread();
- for (NetworkAgentInfo nai : mNetworkAgentInfos) {
+ forEachNetworkAgentInfo(nai -> {
if (updatedNetwork == null || hasUnderlyingNetwork(nai, updatedNetwork)) {
updateCapabilitiesForNetwork(nai);
}
- }
+ });
}
private boolean isUidBlockedByVpn(int uid, List<UidRange> blockedUidRanges) {
@@ -7503,11 +7554,11 @@
mPermissionMonitor.updateVpnLockdownUidRanges(requireVpn, ranges);
}
- for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+ forEachNetworkAgentInfo(nai -> {
final boolean curMetered = nai.networkCapabilities.isMetered();
maybeNotifyNetworkBlocked(nai, curMetered, curMetered,
mVpnBlockedUidRanges, newVpnBlockedUidRanges);
- }
+ });
mVpnBlockedUidRanges = newVpnBlockedUidRanges;
}
@@ -9071,6 +9122,9 @@
// Tracks all NetworkAgents that are currently registered.
// NOTE: Only should be accessed on ConnectivityServiceThread, except dump().
+ // Code iterating over this set is recommended to use forAllNetworkAgentInfos(), which allows
+ // code within the loop to disconnect networks during iteration without causing null pointer or
+ // OOB exceptions.
private final ArraySet<NetworkAgentInfo> mNetworkAgentInfos = new ArraySet<>();
// UID ranges for users that are currently blocked by VPNs.
@@ -10439,7 +10493,7 @@
// A NetworkAgent's allowedUids may need to be updated if the app has lost
// carrier config
- for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+ forEachNetworkAgentInfo(nai -> {
if (nai.networkCapabilities.getAllowedUidsNoCopy().contains(uid)
&& getSubscriptionIdFromNetworkCaps(nai.networkCapabilities) == subId) {
final NetworkCapabilities nc = new NetworkCapabilities(nai.networkCapabilities);
@@ -10451,7 +10505,7 @@
mCarrierPrivilegeAuthenticator);
updateCapabilities(nai.getScore(), nai, nc);
}
- }
+ });
}
/**
@@ -11208,7 +11262,11 @@
break;
}
}
- nai.disconnect();
+ if (mQueueNetworkAgentEventsInSystemServer) {
+ disconnectAndDestroyNetwork(nai);
+ } else {
+ nai.disconnect();
+ }
}
private void handleLingerComplete(NetworkAgentInfo oldNetwork) {
@@ -11368,7 +11426,7 @@
throw new IllegalStateException("No user is available");
}
- for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+ forEachNetworkAgentInfo(nai -> {
ArraySet<UidRange> allowedUidRanges = new ArraySet<>();
for (final UserHandle user : users) {
final ArraySet<UidRange> restrictedUidRanges =
@@ -11380,7 +11438,7 @@
final UidRangeParcel[] rangesParcel = toUidRangeStableParcels(allowedUidRanges);
configs.add(new NativeUidRangeConfig(
nai.network.netId, rangesParcel, 0 /* subPriority */));
- }
+ });
// The netd API replaces the previous configs with the current configs.
// Thus, for network disconnection or preference removal, no need to
@@ -11602,9 +11660,7 @@
// Gather the list of all relevant agents.
final ArrayList<NetworkAgentInfo> nais = new ArrayList<>();
- for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
- nais.add(nai);
- }
+ forEachNetworkAgentInfo(nai -> nais.add(nai));
for (final NetworkRequestInfo nri : networkRequests) {
// Non-multilayer listen requests can be ignored.
@@ -11710,14 +11766,14 @@
// Don't send CALLBACK_LOCAL_NETWORK_INFO_CHANGED yet though : they should be sent after
// onAvailable so clients know what network the change is about. Store such changes in
// an array that's only allocated if necessary (because it's almost never necessary).
- ArrayList<NetworkAgentInfo> localInfoChangedAgents = null;
- for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
- if (!nai.isLocalNetwork()) continue;
+ final ArrayList<NetworkAgentInfo> localInfoChangedAgents = new ArrayList<>();
+ forEachNetworkAgentInfo(nai -> {
+ if (!nai.isLocalNetwork()) return; // return@forEach
final NetworkRequest nr = nai.localNetworkConfig.getUpstreamSelector();
- if (null == nr) continue; // No upstream for this local network
+ if (null == nr) return; // return@forEach, no upstream for this local network
final NetworkRequestInfo nri = mNetworkRequests.get(nr);
final NetworkReassignment.RequestReassignment change = changes.getReassignment(nri);
- if (null == change) continue; // No change in upstreams for this network
+ if (null == change) return; // return@forEach, no change in upstreams for this network
final String fromIface = nai.linkProperties.getInterfaceName();
if (!hasSameInterfaceName(change.mOldNetwork, change.mNewNetwork)
|| change.mOldNetwork.isDestroyed()) {
@@ -11745,9 +11801,8 @@
loge("Can't update forwarding rules", e);
}
}
- if (null == localInfoChangedAgents) localInfoChangedAgents = new ArrayList<>();
localInfoChangedAgents.add(nai);
- }
+ });
// Notify requested networks are available after the default net is switched, but
// before LegacyTypeTracker sends legacy broadcasts
@@ -11798,17 +11853,14 @@
}
// Send LOCAL_NETWORK_INFO_CHANGED callbacks now that onAvailable and onLost have been sent.
- if (null != localInfoChangedAgents) {
- for (final NetworkAgentInfo nai : localInfoChangedAgents) {
- notifyNetworkCallbacks(nai,
- CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
- }
+ for (final NetworkAgentInfo nai : localInfoChangedAgents) {
+ notifyNetworkCallbacks(nai, CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
}
updateLegacyTypeTrackerAndVpnLockdownForRematch(changes, nais);
// Tear down all unneeded networks.
- for (NetworkAgentInfo nai : mNetworkAgentInfos) {
+ forEachNetworkAgentInfo(nai -> {
if (unneeded(nai, UnneededFor.TEARDOWN)) {
if (nai.getInactivityExpiry() > 0) {
// This network has active linger timers and no requests, but is not
@@ -11826,7 +11878,7 @@
teardownUnneededNetwork(nai);
}
}
- }
+ });
}
/**
@@ -12215,7 +12267,9 @@
// This has to happen after matching the requests, because callbacks are just requests.
notifyNetworkCallbacks(networkAgent, CALLBACK_PRECHECK);
} else if (state == NetworkInfo.State.DISCONNECTED) {
- networkAgent.disconnect();
+ if (!mQueueNetworkAgentEventsInSystemServer) {
+ networkAgent.disconnect();
+ }
if (networkAgent.isVPN()) {
updateVpnUids(networkAgent, networkAgent.networkCapabilities, null);
}
@@ -12339,7 +12393,7 @@
* @param blockedReasons The reasons for why an uid is blocked.
*/
private void maybeNotifyNetworkBlockedForNewState(int uid, @BlockedReason int blockedReasons) {
- for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+ forEachNetworkAgentInfo(nai -> {
final boolean metered = nai.networkCapabilities.isMetered();
final boolean vpnBlocked = isUidBlockedByVpn(uid, mVpnBlockedUidRanges);
@@ -12347,9 +12401,7 @@
uid, mUidBlockedReasons.get(uid, BLOCKED_REASON_NONE), metered, vpnBlocked);
final int newBlockedState =
getBlockedState(uid, blockedReasons, metered, vpnBlocked);
- if (oldBlockedState == newBlockedState) {
- continue;
- }
+ if (oldBlockedState == newBlockedState) return; // return@forEach
for (int i = 0; i < nai.numNetworkRequests(); i++) {
NetworkRequest nr = nai.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
@@ -12358,7 +12410,7 @@
newBlockedState);
}
}
- }
+ });
}
@VisibleForTesting
@@ -12447,11 +12499,11 @@
activeNetIds.add(nri.getSatisfier().network().netId);
}
}
- for (NetworkAgentInfo nai : mNetworkAgentInfos) {
+ forEachNetworkAgentInfo(nai -> {
if (activeNetIds.contains(nai.network().netId) || nai.isVPN()) {
defaultNetworks.add(nai.network);
}
- }
+ });
return defaultNetworks;
}
@@ -13342,15 +13394,10 @@
}
private boolean ownsVpnRunningOverNetwork(int uid, Network network) {
- for (NetworkAgentInfo virtual : mNetworkAgentInfos) {
- if (virtual.propagateUnderlyingCapabilities()
- && virtual.networkCapabilities.getOwnerUid() == uid
- && CollectionUtils.contains(virtual.declaredUnderlyingNetworks, network)) {
- return true;
- }
- }
-
- return false;
+ return anyNetworkAgentInfo(virtual ->
+ virtual.propagateUnderlyingCapabilities()
+ && virtual.networkCapabilities.getOwnerUid() == uid
+ && CollectionUtils.contains(virtual.declaredUnderlyingNetworks, network));
}
@CheckResult
@@ -13521,18 +13568,16 @@
@Override
public void onInterfaceLinkStateChanged(@NonNull String iface, boolean up) {
mHandler.post(() -> {
- for (NetworkAgentInfo nai : mNetworkAgentInfos) {
- nai.clatd.handleInterfaceLinkStateChanged(iface, up);
- }
+ forEachNetworkAgentInfo(nai ->
+ nai.clatd.handleInterfaceLinkStateChanged(iface, up));
});
}
@Override
public void onInterfaceRemoved(@NonNull String iface) {
mHandler.post(() -> {
- for (NetworkAgentInfo nai : mNetworkAgentInfos) {
- nai.clatd.handleInterfaceRemoved(iface);
- }
+ forEachNetworkAgentInfo(nai ->
+ nai.clatd.handleInterfaceRemoved(iface));
});
}
}
@@ -14313,7 +14358,7 @@
final long oldIngressRateLimit = mIngressRateLimit;
mIngressRateLimit = ConnectivitySettingsManager.getIngressRateLimitInBytesPerSecond(
mContext);
- for (final NetworkAgentInfo networkAgent : mNetworkAgentInfos) {
+ forEachNetworkAgentInfo(networkAgent -> {
if (canNetworkBeRateLimited(networkAgent)) {
// If rate limit has previously been enabled, remove the old limit first.
if (oldIngressRateLimit >= 0) {
@@ -14324,7 +14369,7 @@
mIngressRateLimit);
}
}
- }
+ });
}
private boolean canNetworkBeRateLimited(@NonNull final NetworkAgentInfo networkAgent) {
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/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/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 ee2368d..2404966 100644
--- a/tests/cts/multidevices/apfv6_test.py
+++ b/tests/cts/multidevices/apfv6_test.py
@@ -28,9 +28,9 @@
ICMPv6ND_NA,
RouterAlert
)
-from scapy.layers.l2 import Ether
+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
@@ -53,51 +53,57 @@
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):
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 bd9bd2a..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
@@ -179,6 +180,10 @@
// without affecting the run time of successful runs. Thus, set a very high timeout.
private const val DEFAULT_TIMEOUT_MS = 5000L
+private const val QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER =
+ "queue_network_agent_events_in_system_server"
+
+
// When waiting for a NetworkCallback to determine there was no timeout, waiting is the
// only possible thing (the relevant handler is the one in the real ConnectivityService,
// and then there is the Binder call), so have a short timeout for this as it will be
@@ -203,12 +208,6 @@
private val PREFIX = IpPrefix("2001:db8::/64")
private val NEXTHOP = InetAddresses.parseNumericAddress("fe80::abcd")
-// On T and below, the native network is only created when the agent connects.
-// Starting in U, the native network was to be created as soon as the agent is registered,
-// but this has been flagged off for now pending resolution of race conditions.
-// TODO : enable this in a Mainline update or in V.
-private const val SHOULD_CREATE_NETWORKS_IMMEDIATELY = false
-
@AppModeFull(reason = "Instant apps can't use NetworkAgent because it needs NETWORK_FACTORY'.")
// NetworkAgent is updated as part of the connectivity module, and running NetworkAgent tests in MTS
// for modules other than Connectivity does not provide much value. Only run them in connectivity
@@ -234,9 +233,27 @@
private var qosTestSocket: Closeable? = null // either Socket or DatagramSocket
private val ifacesToCleanUp = mutableListOf<TestNetworkInterface>()
+ // Unless the queuing in system server feature is chickened out, native networks are created
+ // immediately. Historically they would only created as they'd connect, which would force
+ // the code to apply link properties multiple times and suffer errors early on. Creating
+ // them early required that ordering between the client and the system server is guaranteed
+ // (at least to some extent), which has been done by moving the event queue from the client
+ // to the system server. When that feature is not chickened out, create networks immediately.
+ private val SHOULD_CREATE_NETWORKS_IMMEDIATELY
+ get() = mCM.isConnectivityServiceFeatureEnabledForTesting(
+ QUEUE_NETWORK_AGENT_EVENTS_IN_SYSTEM_SERVER
+ )
+
+
@Before
fun setUp() {
instrumentation.getUiAutomation().adoptShellPermissionIdentity()
+ if (SdkLevel.isAtLeastT()) {
+ instrumentation.getUiAutomation().grantRuntimePermission(
+ "android.net.cts",
+ NEARBY_WIFI_DEVICES
+ )
+ }
mHandlerThread.start()
}
@@ -741,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.
@@ -764,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()
@@ -789,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
+ )
}
}
@@ -1660,16 +1699,17 @@
// Connect a third network. Because network1 is awaiting replacement, network3 is preferred
// as soon as it validates (until then, it is outscored by network1).
- // The fact that the first events seen by matchAllCallback is the connection of network3
+ // The fact that the first event seen by matchAllCallback is the connection of network3
// implicitly ensures that no callbacks are sent since network1 was lost.
val (agent3, network3) = connectNetwork(lp = lp)
- matchAllCallback.expectAvailableThenValidatedCallbacks(network3)
- testCallback.expectAvailableDoubleValidatedCallbacks(network3)
- sendAndExpectUdpPacket(network3, reader, iface)
// As soon as the replacement arrives, network1 is disconnected.
// Check that this happens before the replacement timeout (5 seconds) fires.
+ matchAllCallback.expectAvailableCallbacks(network3, validated = false)
matchAllCallback.expect<Lost>(network1, 2_000 /* timeoutMs */)
+ matchAllCallback.expectCaps(network3) { it.hasCapability(NET_CAPABILITY_VALIDATED) }
+ sendAndExpectUdpPacket(network3, reader, iface)
+ testCallback.expectAvailableDoubleValidatedCallbacks(network3)
agent1.expectCallback<OnNetworkUnwanted>()
// Test lingering:
@@ -1717,7 +1757,7 @@
val callback = TestableNetworkCallback()
requestNetwork(makeTestNetworkRequest(specifier = specifier6), callback)
val agent6 = createNetworkAgent(specifier = specifier6)
- val network6 = agent6.register()
+ agent6.register()
if (SHOULD_CREATE_NETWORKS_IMMEDIATELY) {
agent6.expectCallback<OnNetworkCreated>()
} else {
@@ -1787,8 +1827,9 @@
val (newWifiAgent, newWifiNetwork) = connectNetwork(TRANSPORT_WIFI)
testCallback.expectAvailableCallbacks(newWifiNetwork, validated = true)
- matchAllCallback.expectAvailableThenValidatedCallbacks(newWifiNetwork)
+ matchAllCallback.expectAvailableCallbacks(newWifiNetwork, validated = false)
matchAllCallback.expect<Lost>(wifiNetwork)
+ matchAllCallback.expectCaps(newWifiNetwork) { it.hasCapability(NET_CAPABILITY_VALIDATED) }
wifiAgent.expectCallback<OnNetworkUnwanted>()
testCallback.expect<CapabilitiesChanged>(newWifiNetwork)
@@ -1848,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/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 3eefa0f..35a7fbd 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -3092,23 +3092,24 @@
generalCb.expectAvailableCallbacksUnvalidated(net2);
if (expectLingering) {
generalCb.expectLosing(net1);
- }
- generalCb.expectCaps(net2, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
- defaultCb.expectAvailableDoubleValidatedCallbacks(net2);
- // Make sure cell 1 is unwanted immediately if the radio can't time share, but only
- // after some delay if it can.
- if (expectLingering) {
+ // Make sure cell 1 is unwanted immediately if the radio can't time share, but only
+ // after some delay if it can.
+ generalCb.expectCaps(net2, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
+ defaultCb.expectAvailableDoubleValidatedCallbacks(net2);
net1.assertNotDisconnected(TEST_CALLBACK_TIMEOUT_MS); // always incurs the timeout
generalCb.assertNoCallback();
// assertNotDisconnected waited for TEST_CALLBACK_TIMEOUT_MS, so waiting for the
// linger period gives TEST_CALLBACK_TIMEOUT_MS time for the event to process.
net1.expectDisconnected(UNREASONABLY_LONG_ALARM_WAIT_MS);
+ generalCb.expect(LOST, net1);
} else {
net1.expectDisconnected(TEST_CALLBACK_TIMEOUT_MS);
+ net1.disconnect();
+ generalCb.expect(LOST, net1);
+ generalCb.expectCaps(net2, c -> c.hasCapability(NET_CAPABILITY_VALIDATED));
+ defaultCb.expectAvailableDoubleValidatedCallbacks(net2);
}
- net1.disconnect();
- generalCb.expect(LOST, net1);
// Remove primary from net 2
net2.setScore(new NetworkScore.Builder().build());
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>()
+ }
}