diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 594ec18d99965713dc682324d8085950767df5f5..334b2316b268ca5b653500bf1866d9426f1f61f4 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -173,6 +173,12 @@ public class NetworkPolicyManager { public static final String FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY = "low_power_standby"; /** @hide */ public static final String FIREWALL_CHAIN_NAME_BACKGROUND = "background"; + /** @hide */ + public static final String FIREWALL_CHAIN_NAME_METERED_ALLOW = "metered_allow"; + /** @hide */ + public static final String FIREWALL_CHAIN_NAME_METERED_DENY_USER = "metered_deny_user"; + /** @hide */ + public static final String FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN = "metered_deny_admin"; private static final boolean ALLOW_PLATFORM_APP_POLICY = true; diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java index d25f529730850f6cd0af9eccc3597989703aa202..5ea3e70f7957c12d26b8522ca127e51895f2aff9 100644 --- a/services/core/java/com/android/server/net/NetworkManagementService.java +++ b/services/core/java/com/android/server/net/NetworkManagementService.java @@ -20,6 +20,9 @@ import static android.Manifest.permission.CONNECTIVITY_INTERNAL; import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND; import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER; import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; @@ -31,6 +34,9 @@ import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_ALLOW; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_USER; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; @@ -143,6 +149,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { private final Object mQuotaLock = new Object(); private final Object mRulesLock = new Object(); + private final boolean mUseMeteredFirewallChains; + /** Set of interfaces with active quotas. */ @GuardedBy("mQuotaLock") private HashMap<String, Long> mActiveQuotas = Maps.newHashMap(); @@ -150,9 +158,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub { @GuardedBy("mQuotaLock") private HashMap<String, Long> mActiveAlerts = Maps.newHashMap(); /** Set of UIDs denied on metered networks. */ + // TODO: b/336693007 - Remove once NPMS has completely migrated to metered firewall chains. @GuardedBy("mRulesLock") private SparseBooleanArray mUidRejectOnMetered = new SparseBooleanArray(); /** Set of UIDs allowed on metered networks. */ + // TODO: b/336693007 - Remove once NPMS has completely migrated to metered firewall chains. @GuardedBy("mRulesLock") private SparseBooleanArray mUidAllowOnMetered = new SparseBooleanArray(); /** Set of UIDs with cleartext penalties. */ @@ -196,10 +206,32 @@ public class NetworkManagementService extends INetworkManagementService.Stub { @GuardedBy("mRulesLock") private final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray(); + /** + * Contains the per-UID firewall rules that are used to allowlist the app from metered-network + * restrictions when data saver is enabled. + */ + @GuardedBy("mRulesLock") + private final SparseIntArray mUidMeteredFirewallAllowRules = new SparseIntArray(); + + /** + * Contains the per-UID firewall rules that are used to deny app access to metered networks + * due to user action. + */ + @GuardedBy("mRulesLock") + private final SparseIntArray mUidMeteredFirewallDenyUserRules = new SparseIntArray(); + + /** + * Contains the per-UID firewall rules that are used to deny app access to metered networks + * due to admin action. + */ + @GuardedBy("mRulesLock") + private final SparseIntArray mUidMeteredFirewallDenyAdminRules = new SparseIntArray(); + /** Set of states for the child firewall chains. True if the chain is active. */ @GuardedBy("mRulesLock") final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray(); + // TODO: b/336693007 - Remove once NPMS has completely migrated to metered firewall chains. @GuardedBy("mQuotaLock") private volatile boolean mDataSaverMode; @@ -217,6 +249,15 @@ public class NetworkManagementService extends INetworkManagementService.Stub { mContext = context; mDeps = deps; + mUseMeteredFirewallChains = Flags.useMeteredFirewallChains(); + + if (mUseMeteredFirewallChains) { + // These firewalls are always on and currently ConnectivityService does not allow + // changing their enabled state. + mFirewallChainStates.put(FIREWALL_CHAIN_METERED_DENY_USER, true); + mFirewallChainStates.put(FIREWALL_CHAIN_METERED_DENY_ADMIN, true); + } + mDaemonHandler = new Handler(FgThread.get().getLooper()); mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener(); @@ -410,33 +451,39 @@ public class NetworkManagementService extends INetworkManagementService.Stub { } } - SparseBooleanArray uidRejectOnQuota = null; - SparseBooleanArray uidAcceptOnQuota = null; - synchronized (mRulesLock) { - size = mUidRejectOnMetered.size(); - if (size > 0) { - if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered denylist rules"); - uidRejectOnQuota = mUidRejectOnMetered; - mUidRejectOnMetered = new SparseBooleanArray(); - } + if (!mUseMeteredFirewallChains) { + SparseBooleanArray uidRejectOnQuota = null; + SparseBooleanArray uidAcceptOnQuota = null; + synchronized (mRulesLock) { + size = mUidRejectOnMetered.size(); + if (size > 0) { + if (DBG) { + Slog.d(TAG, "Pushing " + size + " UIDs to metered denylist rules"); + } + uidRejectOnQuota = mUidRejectOnMetered; + mUidRejectOnMetered = new SparseBooleanArray(); + } - size = mUidAllowOnMetered.size(); - if (size > 0) { - if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered allowlist rules"); - uidAcceptOnQuota = mUidAllowOnMetered; - mUidAllowOnMetered = new SparseBooleanArray(); + size = mUidAllowOnMetered.size(); + if (size > 0) { + if (DBG) { + Slog.d(TAG, "Pushing " + size + " UIDs to metered allowlist rules"); + } + uidAcceptOnQuota = mUidAllowOnMetered; + mUidAllowOnMetered = new SparseBooleanArray(); + } } - } - if (uidRejectOnQuota != null) { - for (int i = 0; i < uidRejectOnQuota.size(); i++) { - setUidOnMeteredNetworkDenylist(uidRejectOnQuota.keyAt(i), - uidRejectOnQuota.valueAt(i)); + if (uidRejectOnQuota != null) { + for (int i = 0; i < uidRejectOnQuota.size(); i++) { + setUidOnMeteredNetworkDenylist(uidRejectOnQuota.keyAt(i), + uidRejectOnQuota.valueAt(i)); + } } - } - if (uidAcceptOnQuota != null) { - for (int i = 0; i < uidAcceptOnQuota.size(); i++) { - setUidOnMeteredNetworkAllowlist(uidAcceptOnQuota.keyAt(i), - uidAcceptOnQuota.valueAt(i)); + if (uidAcceptOnQuota != null) { + for (int i = 0; i < uidAcceptOnQuota.size(); i++) { + setUidOnMeteredNetworkAllowlist(uidAcceptOnQuota.keyAt(i), + uidAcceptOnQuota.valueAt(i)); + } } } @@ -459,8 +506,16 @@ public class NetworkManagementService extends INetworkManagementService.Stub { syncFirewallChainLocked(FIREWALL_CHAIN_RESTRICTED, "restricted "); syncFirewallChainLocked(FIREWALL_CHAIN_LOW_POWER_STANDBY, "low power standby "); syncFirewallChainLocked(FIREWALL_CHAIN_BACKGROUND, FIREWALL_CHAIN_NAME_BACKGROUND); + if (mUseMeteredFirewallChains) { + syncFirewallChainLocked(FIREWALL_CHAIN_METERED_ALLOW, + FIREWALL_CHAIN_NAME_METERED_ALLOW); + syncFirewallChainLocked(FIREWALL_CHAIN_METERED_DENY_USER, + FIREWALL_CHAIN_NAME_METERED_DENY_USER); + syncFirewallChainLocked(FIREWALL_CHAIN_METERED_DENY_ADMIN, + FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN); + } - final int[] chains = { + final int[] chainsToEnable = { FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE, @@ -469,14 +524,13 @@ public class NetworkManagementService extends INetworkManagementService.Stub { FIREWALL_CHAIN_BACKGROUND, }; - for (int chain : chains) { + for (int chain : chainsToEnable) { if (getFirewallChainState(chain)) { setFirewallChainEnabled(chain, true); } } } - try { getBatteryStats().noteNetworkStatsEnabled(); } catch (RemoteException e) { @@ -1077,6 +1131,14 @@ public class NetworkManagementService extends INetworkManagementService.Stub { mContext.getSystemService(ConnectivityManager.class) .setDataSaverEnabled(enable); mDataSaverMode = enable; + if (mUseMeteredFirewallChains) { + // Copy mDataSaverMode state to FIREWALL_CHAIN_METERED_ALLOW + // until ConnectivityService allows manipulation of the data saver mode via + // FIREWALL_CHAIN_METERED_ALLOW. + synchronized (mRulesLock) { + mFirewallChainStates.put(FIREWALL_CHAIN_METERED_ALLOW, enable); + } + } return true; } else { final boolean changed = mNetdService.bandwidthEnableDataSaver(enable); @@ -1191,9 +1253,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub { setFirewallChainState(chain, enable); } - final String chainName = getFirewallChainName(chain); - if (chain == FIREWALL_CHAIN_NONE) { - throw new IllegalArgumentException("Bad child chain: " + chainName); + if (!isValidFirewallChainForSetEnabled(chain)) { + throw new IllegalArgumentException("Invalid chain for setFirewallChainEnabled: " + + NetworkPolicyLogger.getFirewallChainName(chain)); } final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); @@ -1205,38 +1267,29 @@ public class NetworkManagementService extends INetworkManagementService.Stub { } } - private String getFirewallChainName(int chain) { - switch (chain) { - case FIREWALL_CHAIN_STANDBY: - return FIREWALL_CHAIN_NAME_STANDBY; - case FIREWALL_CHAIN_DOZABLE: - return FIREWALL_CHAIN_NAME_DOZABLE; - case FIREWALL_CHAIN_POWERSAVE: - return FIREWALL_CHAIN_NAME_POWERSAVE; - case FIREWALL_CHAIN_RESTRICTED: - return FIREWALL_CHAIN_NAME_RESTRICTED; - case FIREWALL_CHAIN_LOW_POWER_STANDBY: - return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; - case FIREWALL_CHAIN_BACKGROUND: - return FIREWALL_CHAIN_NAME_BACKGROUND; - default: - throw new IllegalArgumentException("Bad child chain: " + chain); - } + private boolean isValidFirewallChainForSetEnabled(int chain) { + return switch (chain) { + case FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE, + FIREWALL_CHAIN_RESTRICTED, FIREWALL_CHAIN_LOW_POWER_STANDBY, + FIREWALL_CHAIN_BACKGROUND -> true; + // METERED_* firewall chains are not yet supported by + // ConnectivityService#setFirewallChainEnabled. + default -> false; + }; } private int getFirewallType(int chain) { switch (chain) { case FIREWALL_CHAIN_STANDBY: + case FIREWALL_CHAIN_METERED_DENY_ADMIN: + case FIREWALL_CHAIN_METERED_DENY_USER: return FIREWALL_DENYLIST; case FIREWALL_CHAIN_DOZABLE: - return FIREWALL_ALLOWLIST; case FIREWALL_CHAIN_POWERSAVE: - return FIREWALL_ALLOWLIST; case FIREWALL_CHAIN_RESTRICTED: - return FIREWALL_ALLOWLIST; case FIREWALL_CHAIN_LOW_POWER_STANDBY: - return FIREWALL_ALLOWLIST; case FIREWALL_CHAIN_BACKGROUND: + case FIREWALL_CHAIN_METERED_ALLOW: return FIREWALL_ALLOWLIST; default: return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST; @@ -1360,6 +1413,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub { return mUidFirewallLowPowerStandbyRules; case FIREWALL_CHAIN_BACKGROUND: return mUidFirewallBackgroundRules; + case FIREWALL_CHAIN_METERED_ALLOW: + return mUidMeteredFirewallAllowRules; + case FIREWALL_CHAIN_METERED_DENY_USER: + return mUidMeteredFirewallDenyUserRules; + case FIREWALL_CHAIN_METERED_DENY_ADMIN: + return mUidMeteredFirewallDenyAdminRules; case FIREWALL_CHAIN_NONE: return mUidFirewallRules; default: @@ -1378,6 +1437,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub { protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + pw.println("Flags:"); + pw.println(Flags.FLAG_USE_METERED_FIREWALL_CHAINS + ": " + mUseMeteredFirewallChains); + pw.println(); + synchronized (mQuotaLock) { pw.print("Active quota ifaces: "); pw.println(mActiveQuotas.toString()); pw.print("Active alert ifaces: "); pw.println(mActiveAlerts.toString()); @@ -1416,6 +1479,27 @@ public class NetworkManagementService extends INetworkManagementService.Stub { pw.print("UID firewall background chain enabled: "); pw.println(getFirewallChainState(FIREWALL_CHAIN_BACKGROUND)); dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_BACKGROUND, mUidFirewallBackgroundRules); + + pw.print("UID firewall metered allow chain enabled (Data saver mode): "); + // getFirewallChainState should maintain a duplicated state from mDataSaverMode when + // mUseMeteredFirewallChains is enabled. + pw.println(getFirewallChainState(FIREWALL_CHAIN_METERED_ALLOW)); + dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_METERED_ALLOW, + mUidMeteredFirewallAllowRules); + + pw.print("UID firewall metered deny_user chain enabled (always-on): "); + // This always-on state should be reflected by getFirewallChainState when + // mUseMeteredFirewallChains is enabled. + pw.println(getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_USER)); + dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_METERED_DENY_USER, + mUidMeteredFirewallDenyUserRules); + + pw.print("UID firewall metered deny_admin chain enabled (always-on): "); + // This always-on state should be reflected by getFirewallChainState when + // mUseMeteredFirewallChains is enabled. + pw.println(getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_ADMIN)); + dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN, + mUidMeteredFirewallDenyAdminRules); } pw.print("Firewall enabled: "); pw.println(mFirewallEnabled); @@ -1520,14 +1604,40 @@ public class NetworkManagementService extends INetworkManagementService.Stub { if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because it is in background"); return true; } - if (mUidRejectOnMetered.get(uid)) { - if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data" - + " in the background"); - return true; - } - if (mDataSaverMode && !mUidAllowOnMetered.get(uid)) { - if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode"); - return true; + if (mUseMeteredFirewallChains) { + if (getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_USER) + && mUidMeteredFirewallDenyUserRules.get(uid) == FIREWALL_RULE_DENY) { + if (DBG) { + Slog.d(TAG, "Uid " + uid + " restricted because of user-restricted metered" + + " data in the background"); + } + return true; + } + if (getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_ADMIN) + && mUidMeteredFirewallDenyAdminRules.get(uid) == FIREWALL_RULE_DENY) { + if (DBG) { + Slog.d(TAG, "Uid " + uid + " restricted because of admin-restricted metered" + + " data in the background"); + } + return true; + } + if (getFirewallChainState(FIREWALL_CHAIN_METERED_ALLOW) + && mUidMeteredFirewallAllowRules.get(uid) != FIREWALL_RULE_ALLOW) { + if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode"); + return true; + } + } else { + if (mUidRejectOnMetered.get(uid)) { + if (DBG) { + Slog.d(TAG, "Uid " + uid + + " restricted because of no metered data in the background"); + } + return true; + } + if (mDataSaverMode && !mUidAllowOnMetered.get(uid)) { + if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode"); + return true; + } } return false; } diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java index 8e2d7780204a49f866548cf28a8961f2eeff0051..681aa8aef219b3da0932bb361cef2183d6f01946 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java +++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java @@ -19,6 +19,9 @@ import static android.net.ConnectivityManager.BLOCKED_REASON_NONE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND; import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER; import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; @@ -28,6 +31,9 @@ import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_ALLOW; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_USER; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; @@ -379,7 +385,7 @@ public class NetworkPolicyLogger { return "Interfaces of netId=" + netId + " changed to " + newIfaces; } - private static String getFirewallChainName(int chain) { + static String getFirewallChainName(int chain) { switch (chain) { case FIREWALL_CHAIN_DOZABLE: return FIREWALL_CHAIN_NAME_DOZABLE; @@ -393,6 +399,12 @@ public class NetworkPolicyLogger { return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; case FIREWALL_CHAIN_BACKGROUND: return FIREWALL_CHAIN_NAME_BACKGROUND; + case FIREWALL_CHAIN_METERED_ALLOW: + return FIREWALL_CHAIN_NAME_METERED_ALLOW; + case FIREWALL_CHAIN_METERED_DENY_USER: + return FIREWALL_CHAIN_NAME_METERED_DENY_USER; + case FIREWALL_CHAIN_METERED_DENY_ADMIN: + return FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN; default: return String.valueOf(chain); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index c6fca9b76f8e4514db3a750d0569f996d3d6c444..a26ac615ba4010c09f5820dac6c0c4273a9c27e1 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -60,6 +60,9 @@ import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND; import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER; import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; @@ -514,6 +517,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { */ private boolean mBackgroundNetworkRestricted; + /** + * Whether or not metered firewall chains should be used for uid policy controlling access to + * metered networks. + */ + private boolean mUseMeteredFirewallChains; + // See main javadoc for instructions on how to use these locks. final Object mUidRulesFirstLock = new Object(); final Object mNetworkPoliciesSecondLock = new Object(); @@ -997,6 +1006,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mAppStandby = LocalServices.getService(AppStandbyInternal.class); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); + mUseMeteredFirewallChains = Flags.useMeteredFirewallChains(); + synchronized (mUidRulesFirstLock) { synchronized (mNetworkPoliciesSecondLock) { updatePowerSaveAllowlistUL(); @@ -4030,8 +4041,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { fout.println(); fout.println("Flags:"); - fout.println("Network blocked for TOP_SLEEPING and above: " + fout.println(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE + ": " + mBackgroundNetworkRestricted); + fout.println(Flags.FLAG_USE_METERED_FIREWALL_CHAINS + ": " + + mUseMeteredFirewallChains); fout.println(); fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode); @@ -5367,23 +5380,44 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { postUidRulesChangedMsg(uid, uidRules); } - // Note that the conditionals below are for avoiding unnecessary calls to netd. - // TODO: Measure the performance for doing a no-op call to netd so that we can - // remove the conditionals to simplify the logic below. We can also further reduce - // some calls to netd if they turn out to be costly. - final int denylistReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED - | BLOCKED_METERED_REASON_USER_RESTRICTED; - if ((oldEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE - || (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE) { - setMeteredNetworkDenylist(uid, - (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE); - } - final int allowlistReasons = ALLOWED_METERED_REASON_FOREGROUND - | ALLOWED_METERED_REASON_USER_EXEMPTED; - if ((oldAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE - || (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE) { - setMeteredNetworkAllowlist(uid, - (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE); + if (mUseMeteredFirewallChains) { + if ((newEffectiveBlockedReasons & BLOCKED_METERED_REASON_ADMIN_DISABLED) + != BLOCKED_REASON_NONE) { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_ADMIN, uid, FIREWALL_RULE_DENY); + } else { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_ADMIN, uid, FIREWALL_RULE_DEFAULT); + } + if ((newEffectiveBlockedReasons & BLOCKED_METERED_REASON_USER_RESTRICTED) + != BLOCKED_REASON_NONE) { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_DENY); + } else { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_DEFAULT); + } + if ((newAllowedReasons & (ALLOWED_METERED_REASON_FOREGROUND + | ALLOWED_METERED_REASON_USER_EXEMPTED)) != ALLOWED_REASON_NONE) { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_ALLOW); + } else { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_DEFAULT); + } + } else { + // Note that the conditionals below are for avoiding unnecessary calls to netd. + // TODO: Measure the performance for doing a no-op call to netd so that we can + // remove the conditionals to simplify the logic below. We can also further reduce + // some calls to netd if they turn out to be costly. + final int denylistReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED + | BLOCKED_METERED_REASON_USER_RESTRICTED; + if ((oldEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE + || (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE) { + setMeteredNetworkDenylist(uid, + (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE); + } + final int allowlistReasons = ALLOWED_METERED_REASON_FOREGROUND + | ALLOWED_METERED_REASON_USER_EXEMPTED; + if ((oldAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE + || (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE) { + setMeteredNetworkAllowlist(uid, + (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE); + } } } @@ -6143,6 +6177,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } else if (chain == FIREWALL_CHAIN_BACKGROUND) { mUidFirewallBackgroundRules.put(uid, rule); } + // Note that we do not need keep a separate cache of uid rules for chains that we do + // not call #setUidFirewallRulesUL for. try { mNetworkManager.setFirewallUidRule(chain, uid, rule); @@ -6200,10 +6236,19 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { FIREWALL_RULE_DEFAULT); mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DEFAULT); - mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false); - mLogger.meteredAllowlistChanged(uid, false); - mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false); - mLogger.meteredDenylistChanged(uid, false); + if (mUseMeteredFirewallChains) { + mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, uid, + FIREWALL_RULE_DEFAULT); + mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_USER, uid, + FIREWALL_RULE_DEFAULT); + mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_METERED_ALLOW, uid, + FIREWALL_RULE_DEFAULT); + } else { + mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false); + mLogger.meteredAllowlistChanged(uid, false); + mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false); + mLogger.meteredDenylistChanged(uid, false); + } } catch (IllegalStateException e) { Log.wtf(TAG, "problem resetting firewall uid rules for " + uid, e); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig index d9491de52d87bb39627a71a038bcd755978fad8b..e986dd81b94b00db030257ed99a8422a51abf190 100644 --- a/services/core/java/com/android/server/net/flags.aconfig +++ b/services/core/java/com/android/server/net/flags.aconfig @@ -7,3 +7,13 @@ flag { description: "Block network access for apps in a low importance background state" bug: "304347838" } + +flag { + name: "use_metered_firewall_chains" + namespace: "backstage_power" + description: "Use metered firewall chains to control access to metered networks" + bug: "336693007" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index b1d50399416af50de7f2881289dc9817ae534302..84bafdac0a0c83749f9913b3a9bf7855ac35aab9 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -25,6 +25,13 @@ value="/data/local/tmp/cts/content/broken_shortcut.xml" /> </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> + <option name="force-skip-system-props" value="true" /> + <option name="set-global-setting" key="verifier_engprod" value="1" /> + <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" /> + <option name="restore-settings" value="true" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java index d6d2b6d9abd277a33dbb8b9e19f117bde63ae990..2a49a868d1f7f4493e524f3c0ffa53cb3faf9e14 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java @@ -19,9 +19,16 @@ package com.android.server.net; import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND; import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER; import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT; +import static android.net.ConnectivityManager.FIREWALL_RULE_DENY; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.util.DebugUtils.valueToString; import static org.junit.Assert.assertEquals; @@ -51,7 +58,10 @@ import android.os.PermissionEnforcer; import android.os.Process; import android.os.RemoteException; import android.os.test.FakePermissionEnforcer; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.test.suitebuilder.annotation.SmallTest; import android.util.ArrayMap; @@ -62,6 +72,7 @@ import com.android.modules.utils.build.SdkLevel; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -84,6 +95,9 @@ public class NetworkManagementServiceTest { @Mock private IBatteryStats.Stub mBatteryStatsService; @Mock private INetd.Stub mNetdService; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + private static final int TEST_UID = 111; @NonNull @@ -254,6 +268,7 @@ public class NetworkManagementServiceTest { } @Test + @DisableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS) public void testMeteredNetworkRestrictions() throws RemoteException { // Make sure the mocked netd method returns true. doReturn(true).when(mNetdService).bandwidthEnableDataSaver(anyBoolean()); @@ -294,6 +309,69 @@ public class NetworkManagementServiceTest { mNMService.isNetworkRestricted(TEST_UID)); } + @Test + @EnableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS) + public void testMeteredNetworkRestrictionsByAdminChain() { + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID, + FIREWALL_RULE_DENY); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID, + FIREWALL_RULE_DENY); + assertTrue("Should be true since mobile data usage is restricted by admin chain", + mNMService.isNetworkRestricted(TEST_UID)); + + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID, + FIREWALL_RULE_DEFAULT); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID, + FIREWALL_RULE_DEFAULT); + assertFalse("Should be false since mobile data usage is no longer restricted by admin", + mNMService.isNetworkRestricted(TEST_UID)); + } + + @Test + @EnableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS) + public void testMeteredNetworkRestrictionsByUserChain() { + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID, + FIREWALL_RULE_DENY); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID, + FIREWALL_RULE_DENY); + assertTrue("Should be true since mobile data usage is restricted by user chain", + mNMService.isNetworkRestricted(TEST_UID)); + + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID, + FIREWALL_RULE_DEFAULT); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID, + FIREWALL_RULE_DEFAULT); + assertFalse("Should be false since mobile data usage is no longer restricted by user", + mNMService.isNetworkRestricted(TEST_UID)); + } + + @Test + @EnableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS) + public void testDataSaverRestrictionsWithAllowChain() { + mNMService.setDataSaverModeEnabled(true); + verify(mCm).setDataSaverEnabled(true); + + assertTrue("Should be true since data saver is on and the uid is not allowlisted", + mNMService.isNetworkRestricted(TEST_UID)); + + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, FIREWALL_RULE_ALLOW); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, FIREWALL_RULE_ALLOW); + assertFalse("Should be false since data saver is on and the uid is allowlisted", + mNMService.isNetworkRestricted(TEST_UID)); + + // remove uid from allowlist and turn datasaver off again + + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, + FIREWALL_RULE_DEFAULT); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, + FIREWALL_RULE_DEFAULT); + mNMService.setDataSaverModeEnabled(false); + verify(mCm).setDataSaverEnabled(false); + + assertFalse("Network should not be restricted when data saver is off", + mNMService.isNetworkRestricted(TEST_UID)); + } + @Test public void testFirewallChains() { final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>();