Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 65be3025 authored by Felipe Leme's avatar Felipe Leme
Browse files

Refactored NetworkManagerService to support Data Saver.

Netd provides 2 bandwidth control rules to restrict which uids can use
metered networks:

- bw_penalty_box is a blacklist-based firewall chain used to determine
  which uids do not have access to metered interfaces.

- bw_happy_box is whitelist-based firewall chain used to determine which
  uids have access to metered interfaces.

Currently, both NetworkManagerService (NMS) and
NetworkPolicyManagerService (NPMS) uses just the bw_penalty_box rule,
which makes turning Data Saver mode on / off too slow (since NPMS needs
to build the bw_penalty_box on demand); this CL adds support for both
rules on NMS, although NPMS doesn't take advantage of it yet (it will be
refactored in a separate CL).

BUG: 27127112
BUG: 26685616
Change-Id: Ib954574f7c86269fc9b4cf8ce4ba72ba5878c23d
parent 4a2f9857
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -293,7 +293,9 @@ interface INetworkManagementService
    /**
     * Control network activity of a UID over interfaces with a quota limit.
     */
    void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces);
    void setUidMeteredNetworkBlacklist(int uid, boolean enable);
    void setUidMeteredNetworkWhitelist(int uid, boolean enable);
    boolean setDataSaverModeEnabled(boolean enable);

    void setUidCleartextNetworkPolicy(int uid, int policy);

+101 −28
Original line number Diff line number Diff line
@@ -209,9 +209,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub
    /** Set of interfaces with active alerts. */
    @GuardedBy("mQuotaLock")
    private HashMap<String, Long> mActiveAlerts = Maps.newHashMap();
    /** Set of UIDs with active reject rules. */
    /** Set of UIDs blacklisted on metered networks. */
    @GuardedBy("mQuotaLock")
    private SparseBooleanArray mUidRejectOnQuota = new SparseBooleanArray();
    private SparseBooleanArray mUidRejectOnMetered = new SparseBooleanArray();
    /** Set of UIDs whitelisted on metered networks. */
    @GuardedBy("mQuotaLock")
    private SparseBooleanArray mUidAllowOnMetered = new SparseBooleanArray();
    /** Set of UIDs with cleartext penalties. */
    @GuardedBy("mQuotaLock")
    private SparseIntArray mUidCleartextPolicy = new SparseIntArray();
@@ -240,6 +243,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub
    @GuardedBy("mQuotaLock")
    final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray();

    @GuardedBy("mQuotaLock")
    private boolean mDataSaverMode;

    private Object mIdleTimerLock = new Object();
    /** Set of interfaces with active idle timers. */
    private static class IdleTimerParams {
@@ -561,6 +567,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub

        // push any existing quota or UID rules
        synchronized (mQuotaLock) {

            setDataSaverModeEnabled(mDataSaverMode);

            int size = mActiveQuotas.size();
            if (size > 0) {
                if (DBG) Slog.d(TAG, "Pushing " + size + " active quota rules");
@@ -581,13 +590,25 @@ public class NetworkManagementService extends INetworkManagementService.Stub
                }
            }

            size = mUidRejectOnQuota.size();
            size = mUidRejectOnMetered.size();
            if (size > 0) {
                if (DBG) Slog.d(TAG, "Pushing " + size + " active UID rules");
                final SparseBooleanArray uidRejectOnQuota = mUidRejectOnQuota;
                mUidRejectOnQuota = new SparseBooleanArray();
                if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered whitelist rules");
                final SparseBooleanArray uidRejectOnQuota = mUidRejectOnMetered;
                mUidRejectOnMetered = new SparseBooleanArray();
                for (int i = 0; i < uidRejectOnQuota.size(); i++) {
                    setUidNetworkRules(uidRejectOnQuota.keyAt(i), uidRejectOnQuota.valueAt(i));
                    setUidMeteredNetworkBlacklist(uidRejectOnQuota.keyAt(i),
                            uidRejectOnQuota.valueAt(i));
                }
            }

            size = mUidAllowOnMetered.size();
            if (size > 0) {
                if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered blacklist rules");
                final SparseBooleanArray uidAcceptOnQuota = mUidAllowOnMetered;
                mUidAllowOnMetered = new SparseBooleanArray();
                for (int i = 0; i < uidAcceptOnQuota.size(); i++) {
                    setUidMeteredNetworkWhitelist(uidAcceptOnQuota.keyAt(i),
                            uidAcceptOnQuota.valueAt(i));
                }
            }

@@ -738,6 +759,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub
    private class NetdCallbackReceiver implements INativeDaemonConnectorCallbacks {
        @Override
        public void onDaemonConnected() {
            Slog.i(TAG, "onDaemonConnected()");
            // event is dispatched from internal NDC thread, so we prepare the
            // daemon back on main thread.
            if (mConnectedSignal != null) {
@@ -1698,28 +1720,30 @@ public class NetworkManagementService extends INetworkManagementService.Stub
        }
    }

    @Override
    public void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) {
    private void setUidOnMeteredNetworkList(SparseBooleanArray quotaList, int uid,
            boolean blacklist, boolean enable) {
        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);

        // silently discard when control disabled
        // TODO: eventually migrate to be always enabled
        if (!mBandwidthControlEnabled) return;

        final String chain = blacklist ? "naughtyapps" : "niceapps";
        final String suffix = enable ? "add" : "remove";

        synchronized (mQuotaLock) {
            final boolean oldRejectOnQuota = mUidRejectOnQuota.get(uid, false);
            if (oldRejectOnQuota == rejectOnQuotaInterfaces) {
            final boolean oldEnable = quotaList.get(uid, false);
            if (oldEnable == enable) {
                // TODO: eventually consider throwing
                return;
            }

            try {
                mConnector.execute("bandwidth",
                        rejectOnQuotaInterfaces ? "addnaughtyapps" : "removenaughtyapps", uid);
                if (rejectOnQuotaInterfaces) {
                    mUidRejectOnQuota.put(uid, true);
                mConnector.execute("bandwidth", suffix + chain, uid);
                if (enable) {
                    quotaList.put(uid, true);
                } else {
                    mUidRejectOnQuota.delete(uid);
                    quotaList.delete(uid);
                }
            } catch (NativeDaemonConnectorException e) {
                throw e.rethrowAsParcelableException();
@@ -1727,6 +1751,39 @@ public class NetworkManagementService extends INetworkManagementService.Stub
        }
    }

    @Override
    public void setUidMeteredNetworkBlacklist(int uid, boolean enable) {
        setUidOnMeteredNetworkList(mUidRejectOnMetered, uid, true, enable);
    }

    @Override
    public void setUidMeteredNetworkWhitelist(int uid, boolean enable) {
        setUidOnMeteredNetworkList(mUidAllowOnMetered, uid, false, enable);
    }

    @Override
    public boolean setDataSaverModeEnabled(boolean enable) {
        if (DBG) Log.d(TAG, "setDataSaverMode: " + enable);
        synchronized (mQuotaLock) {
            if (mDataSaverMode == enable) {
                Log.w(TAG, "setDataSaverMode(): already " + mDataSaverMode);
                return true;
            }
            try {
                final boolean changed = mNetdService.bandwidthEnableDataSaver(enable);
                if (changed) {
                    mDataSaverMode = enable;
                } else {
                    Log.w(TAG, "setDataSaverMode(" + enable + "): netd command silently failed");
                }
                return changed;
            } catch (RemoteException e) {
                Log.w(TAG, "setDataSaverMode(" + enable + "): netd command failed", e);
                return false;
            }
        }
    }

    @Override
    public void setUidCleartextNetworkPolicy(int uid, int policy) {
        if (Binder.getCallingUid() != uid) {
@@ -2221,29 +2278,22 @@ public class NetworkManagementService extends INetworkManagementService.Stub
        synchronized (mQuotaLock) {
            pw.print("Active quota ifaces: "); pw.println(mActiveQuotas.toString());
            pw.print("Active alert ifaces: "); pw.println(mActiveAlerts.toString());
        }

        synchronized (mUidRejectOnQuota) {
            pw.print("UID reject on quota ifaces: [");
            final int size = mUidRejectOnQuota.size();
            for (int i = 0; i < size; i++) {
                pw.print(mUidRejectOnQuota.keyAt(i));
                if (i < size - 1) pw.print(",");
            }
            pw.println("]");
            pw.print("Data saver mode: "); pw.println(mDataSaverMode);
            dumpUidRuleOnQuotaLocked(pw, "blacklist", mUidRejectOnMetered);
            dumpUidRuleOnQuotaLocked(pw, "whitelist", mUidAllowOnMetered);
        }

        synchronized (mUidFirewallRules) {
            dumpUidFirewallRule(pw, "", mUidFirewallRules);
        }

        pw.println("UID firewall standby chain enabled: " +
        pw.print("UID firewall standby chain enabled: "); pw.println(
                mFirewallChainStates.get(FIREWALL_CHAIN_STANDBY));
        synchronized (mUidFirewallStandbyRules) {
            dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_STANDBY, mUidFirewallStandbyRules);
        }

        pw.println("UID firewall dozable chain enabled: " +
        pw.print("UID firewall dozable chain enabled: "); pw.println(
                mFirewallChainStates.get(FIREWALL_CHAIN_DOZABLE));
        synchronized (mUidFirewallDozableRules) {
            dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_DOZABLE, mUidFirewallDozableRules);
@@ -2267,6 +2317,29 @@ public class NetworkManagementService extends INetworkManagementService.Stub
        }

        pw.print("Firewall enabled: "); pw.println(mFirewallEnabled);
        pw.print("Netd service status: " );
        if (mNetdService == null) {
            pw.println("disconnected");
        } else {
            try {
                final boolean alive = mNetdService.isAlive();
                pw.println(alive ? "alive": "dead");
            } catch (RemoteException e) {
                pw.println("unreachable");
            }
        }
    }

    private void dumpUidRuleOnQuotaLocked(PrintWriter pw, String name, SparseBooleanArray list) {
        pw.print("UID bandwith control ");
        pw.print(name);
        pw.print(" rule: [");
        final int size = list.size();
        for (int i = 0; i < size; i++) {
            pw.print(list.keyAt(i));
            if (i < size - 1) pw.print(",");
        }
        pw.println("]");
    }

    private void dumpUidFirewallRule(PrintWriter pw, String name, SparseIntArray rules) {
+1 −1
Original line number Diff line number Diff line
@@ -2803,7 +2803,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {

    private void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) {
        try {
            mNetworkManager.setUidNetworkRules(uid, rejectOnQuotaInterfaces);
            mNetworkManager.setUidMeteredNetworkBlacklist(uid, rejectOnQuotaInterfaces);
        } catch (IllegalStateException e) {
            Log.wtf(TAG, "problem setting uid rules", e);
        } catch (RemoteException e) {
+17 −17
Original line number Diff line number Diff line
@@ -313,7 +313,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
    public void testScreenChangesRules() throws Exception {
        Future<Void> future;

        expectSetUidNetworkRules(UID_A, false);
        expectSetUidMeteredNetworkBlacklist(UID_A, false);
        expectSetUidForeground(UID_A, true);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
@@ -322,7 +322,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // push strict policy for foreground uid, verify ALLOW rule
        expectSetUidNetworkRules(UID_A, false);
        expectSetUidMeteredNetworkBlacklist(UID_A, false);
        expectSetUidForeground(UID_A, true);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
@@ -332,7 +332,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {

        // now turn screen off and verify REJECT rule
        expect(mPowerManager.isInteractive()).andReturn(false).atLeastOnce();
        expectSetUidNetworkRules(UID_A, true);
        expectSetUidMeteredNetworkBlacklist(UID_A, true);
        expectSetUidForeground(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
        replay();
@@ -342,7 +342,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {

        // and turn screen back on, verify ALLOW rule restored
        expect(mPowerManager.isInteractive()).andReturn(true).atLeastOnce();
        expectSetUidNetworkRules(UID_A, false);
        expectSetUidMeteredNetworkBlacklist(UID_A, false);
        expectSetUidForeground(UID_A, true);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
@@ -354,7 +354,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
    public void testPolicyNone() throws Exception {
        Future<Void> future;

        expectSetUidNetworkRules(UID_A, false);
        expectSetUidMeteredNetworkBlacklist(UID_A, false);
        expectSetUidForeground(UID_A, true);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
@@ -363,7 +363,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // POLICY_NONE should RULE_ALLOW in foreground
        expectSetUidNetworkRules(UID_A, false);
        expectSetUidMeteredNetworkBlacklist(UID_A, false);
        expectSetUidForeground(UID_A, true);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
@@ -372,7 +372,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // POLICY_NONE should RULE_ALLOW in background
        expectSetUidNetworkRules(UID_A, false);
        expectSetUidMeteredNetworkBlacklist(UID_A, false);
        expectSetUidForeground(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
@@ -385,7 +385,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        Future<Void> future;

        // POLICY_REJECT should RULE_ALLOW in background
        expectSetUidNetworkRules(UID_A, true);
        expectSetUidMeteredNetworkBlacklist(UID_A, true);
        expectSetUidForeground(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
        replay();
@@ -394,7 +394,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // POLICY_REJECT should RULE_ALLOW in foreground
        expectSetUidNetworkRules(UID_A, false);
        expectSetUidMeteredNetworkBlacklist(UID_A, false);
        expectSetUidForeground(UID_A, true);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
@@ -403,7 +403,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // POLICY_REJECT should RULE_REJECT in background
        expectSetUidNetworkRules(UID_A, true);
        expectSetUidMeteredNetworkBlacklist(UID_A, true);
        expectSetUidForeground(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
        replay();
@@ -416,7 +416,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        Future<Void> future;

        // POLICY_NONE should have RULE_ALLOW in background
        expectSetUidNetworkRules(UID_A, false);
        expectSetUidMeteredNetworkBlacklist(UID_A, false);
        expectSetUidForeground(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
@@ -426,7 +426,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // adding POLICY_REJECT should cause RULE_REJECT
        expectSetUidNetworkRules(UID_A, true);
        expectSetUidMeteredNetworkBlacklist(UID_A, true);
        expectSetUidForeground(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
        replay();
@@ -435,7 +435,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // removing POLICY_REJECT should return us to RULE_ALLOW
        expectSetUidNetworkRules(UID_A, false);
        expectSetUidMeteredNetworkBlacklist(UID_A, false);
        expectSetUidForeground(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
@@ -632,7 +632,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        Future<Void> future;

        // POLICY_REJECT should RULE_REJECT in background
        expectSetUidNetworkRules(UID_A, true);
        expectSetUidMeteredNetworkBlacklist(UID_A, true);
        expectSetUidForeground(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_REJECT_METERED);
        replay();
@@ -641,7 +641,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        verifyAndReset();

        // uninstall should clear RULE_REJECT
        expectSetUidNetworkRules(UID_A, false);
        expectSetUidMeteredNetworkBlacklist(UID_A, false);
        expectSetUidForeground(UID_A, false);
        future = expectRulesChanged(UID_A, RULE_ALLOW_ALL);
        replay();
@@ -890,9 +890,9 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase {
        expectLastCall().atLeastOnce();
    }

    private void expectSetUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces)
    private void expectSetUidMeteredNetworkBlacklist(int uid, boolean rejectOnQuotaInterfaces)
            throws Exception {
        mNetworkManager.setUidNetworkRules(uid, rejectOnQuotaInterfaces);
        mNetworkManager.setUidMeteredNetworkBlacklist(uid, rejectOnQuotaInterfaces);
        expectLastCall().atLeastOnce();
    }