Fix meteredness bugs in updateNetworkRulesNL().
The logic in that method appears to have become broken at some
point; refactor it to apply all known network policies, and then
perform one last pass to handle any metered networks that didn't
have policies; typically Wi-Fi networks.
Test: cts-tradefed run commandAndExit cts-dev -m CtsHostsideNetworkTests
Bug: 64274313
Change-Id: Id9cbcc6fdfe3793e058f52505c9edd250bed7089
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index f88a074..0778d09 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -33,6 +33,7 @@
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.SNOOZE_NEVER;
import static android.net.NetworkPolicy.WARNING_DISABLED;
@@ -192,8 +193,6 @@
import libcore.io.IoUtils;
-import com.google.android.collect.Lists;
-
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
@@ -371,8 +370,6 @@
/** Defined network policies. */
final ArrayMap<NetworkTemplate, NetworkPolicy> mNetworkPolicy = new ArrayMap<>();
- /** Currently active network rules for ifaces. */
- final ArrayMap<NetworkPolicy, String[]> mNetworkRules = new ArrayMap<>();
/** Map from subId to subscription plans. */
final SparseArray<SubscriptionPlan[]> mSubscriptionPlans = new SparseArray<>();
@@ -972,7 +969,8 @@
/**
* Receiver that watches for {@link WifiConfiguration} to be loaded so that
- * we can perform upgrade logic.
+ * we can perform upgrade logic. After initial upgrade logic, it updates
+ * {@link #mMeteredIfaces} based on configuration changes.
*/
final private BroadcastReceiver mWifiReceiver = new BroadcastReceiver() {
@Override
@@ -980,10 +978,9 @@
synchronized (mUidRulesFirstLock) {
synchronized (mNetworkPoliciesSecondLock) {
upgradeWifiMeteredOverrideAL();
+ updateNetworkRulesNL();
}
}
- // Only need to perform upgrade logic once
- mContext.unregisterReceiver(this);
}
};
@@ -1490,6 +1487,22 @@
}
/**
+ * Collect all ifaces from a {@link NetworkState} into the given set.
+ */
+ private static void collectIfaces(ArraySet<String> ifaces, NetworkState state) {
+ final String baseIface = state.linkProperties.getInterfaceName();
+ if (baseIface != null) {
+ ifaces.add(baseIface);
+ }
+ for (LinkProperties stackedLink : state.linkProperties.getStackedLinks()) {
+ final String stackedIface = stackedLink.getInterfaceName();
+ if (stackedIface != null) {
+ ifaces.add(stackedIface);
+ }
+ }
+ }
+
+ /**
* Examine all connected {@link NetworkState}, looking for
* {@link NetworkPolicy} that need to be enforced. When matches found, set
* remaining quota based on usage cycle and historical stats.
@@ -1507,60 +1520,33 @@
// First, generate identities of all connected networks so we can
// quickly compare them against all defined policies below.
- final ArrayList<Pair<String, NetworkIdentity>> connIdents = new ArrayList<>(states.length);
- final ArraySet<String> connIfaces = new ArraySet<String>(states.length);
+ final ArrayMap<NetworkState, NetworkIdentity> identified = new ArrayMap<>();
for (NetworkState state : states) {
if (state.networkInfo != null && state.networkInfo.isConnected()) {
final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state);
-
- final String baseIface = state.linkProperties.getInterfaceName();
- if (baseIface != null) {
- connIdents.add(Pair.create(baseIface, ident));
- }
-
- // Stacked interfaces are considered to have same identity as
- // their parent network.
- final List<LinkProperties> stackedLinks = state.linkProperties.getStackedLinks();
- for (LinkProperties stackedLink : stackedLinks) {
- final String stackedIface = stackedLink.getInterfaceName();
- if (stackedIface != null) {
- connIdents.add(Pair.create(stackedIface, ident));
- }
- }
+ identified.put(state, ident);
}
}
- // Apply policies against all connected interfaces found above
- mNetworkRules.clear();
- final ArrayList<String> ifaceList = Lists.newArrayList();
- for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) {
- final NetworkPolicy policy = mNetworkPolicy.valueAt(i);
-
- ifaceList.clear();
- for (int j = connIdents.size() - 1; j >= 0; j--) {
- final Pair<String, NetworkIdentity> ident = connIdents.get(j);
- if (policy.template.matches(ident.second)) {
- ifaceList.add(ident.first);
- }
- }
-
- if (ifaceList.size() > 0) {
- final String[] ifaces = ifaceList.toArray(new String[ifaceList.size()]);
- mNetworkRules.put(policy, ifaces);
- }
- }
-
+ final ArraySet<String> newMeteredIfaces = new ArraySet<>();
long lowestRule = Long.MAX_VALUE;
- final ArraySet<String> newMeteredIfaces = new ArraySet<String>(states.length);
- // apply each policy that we found ifaces for; compute remaining data
- // based on current cycle and historical stats, and push to kernel.
- for (int i = mNetworkRules.size()-1; i >= 0; i--) {
- final NetworkPolicy policy = mNetworkRules.keyAt(i);
- final String[] ifaces = mNetworkRules.valueAt(i);
+ // For every well-defined policy, compute remaining data based on
+ // current cycle and historical stats, and push to kernel.
+ final ArraySet<String> matchingIfaces = new ArraySet<>();
+ for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) {
+ final NetworkPolicy policy = mNetworkPolicy.valueAt(i);
+
+ // Collect all ifaces that match this policy
+ matchingIfaces.clear();
+ for (int j = identified.size() - 1; j >= 0; j--) {
+ if (policy.template.matches(identified.valueAt(j))) {
+ collectIfaces(matchingIfaces, identified.keyAt(j));
+ }
+ }
if (LOGD) {
- Slog.d(TAG, "applying policy " + policy + " to ifaces " + Arrays.toString(ifaces));
+ Slog.d(TAG, "Applying " + policy + " to ifaces " + matchingIfaces);
}
final boolean hasWarning = policy.warningBytes != LIMIT_DISABLED;
@@ -1590,16 +1576,14 @@
quotaBytes = Long.MAX_VALUE;
}
- if (ifaces.length > 1) {
+ if (matchingIfaces.size() > 1) {
// TODO: switch to shared quota once NMS supports
Slog.w(TAG, "shared quota unsupported; generating rule for each iface");
}
- for (String iface : ifaces) {
- // long quotaBytes split up into two ints to fit in message
- mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTA,
- (int) (quotaBytes >> 32), (int) (quotaBytes & 0xFFFFFFFF), iface)
- .sendToTarget();
+ for (int j = matchingIfaces.size() - 1; j >= 0; j--) {
+ final String iface = matchingIfaces.valueAt(j);
+ setInterfaceQuotaAsync(iface, quotaBytes);
newMeteredIfaces.add(iface);
}
}
@@ -1613,29 +1597,36 @@
}
}
- for (int i = connIfaces.size()-1; i >= 0; i--) {
- String iface = connIfaces.valueAt(i);
- // long quotaBytes split up into two ints to fit in message
- mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTA,
- (int) (Long.MAX_VALUE >> 32), (int) (Long.MAX_VALUE & 0xFFFFFFFF), iface)
- .sendToTarget();
- newMeteredIfaces.add(iface);
+ // One final pass to catch any metered ifaces that don't have explicitly
+ // defined policies; typically Wi-Fi networks.
+ for (NetworkState state : states) {
+ if (state.networkInfo != null && state.networkInfo.isConnected()
+ && !state.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)) {
+ matchingIfaces.clear();
+ collectIfaces(matchingIfaces, state);
+ for (int j = matchingIfaces.size() - 1; j >= 0; j--) {
+ final String iface = matchingIfaces.valueAt(j);
+ if (!newMeteredIfaces.contains(iface)) {
+ setInterfaceQuotaAsync(iface, Long.MAX_VALUE);
+ newMeteredIfaces.add(iface);
+ }
+ }
+ }
}
- mHandler.obtainMessage(MSG_ADVISE_PERSIST_THRESHOLD, lowestRule).sendToTarget();
-
- // remove quota on any trailing interfaces
+ // Remove quota from any interfaces that are no longer metered.
for (int i = mMeteredIfaces.size() - 1; i >= 0; i--) {
final String iface = mMeteredIfaces.valueAt(i);
if (!newMeteredIfaces.contains(iface)) {
- mHandler.obtainMessage(MSG_REMOVE_INTERFACE_QUOTA, iface)
- .sendToTarget();
+ removeInterfaceQuotaAsync(iface);
}
}
mMeteredIfaces = newMeteredIfaces;
final String[] meteredIfaces = mMeteredIfaces.toArray(new String[mMeteredIfaces.size()]);
mHandler.obtainMessage(MSG_METERED_IFACES_CHANGED, meteredIfaces).sendToTarget();
+
+ mHandler.obtainMessage(MSG_ADVISE_PERSIST_THRESHOLD, lowestRule).sendToTarget();
}
/**
@@ -4021,6 +4012,12 @@
}
}
+ private void setInterfaceQuotaAsync(String iface, long quotaBytes) {
+ // long quotaBytes split up into two ints to fit in message
+ mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTA, (int) (quotaBytes >> 32),
+ (int) (quotaBytes & 0xFFFFFFFF), iface).sendToTarget();
+ }
+
private void setInterfaceQuota(String iface, long quotaBytes) {
try {
mNetworkManager.setInterfaceQuota(iface, quotaBytes);
@@ -4031,6 +4028,10 @@
}
}
+ private void removeInterfaceQuotaAsync(String iface) {
+ mHandler.obtainMessage(MSG_REMOVE_INTERFACE_QUOTA, iface).sendToTarget();
+ }
+
private void removeInterfaceQuota(String iface) {
try {
mNetworkManager.removeInterfaceQuota(iface);