diff options
Diffstat (limited to 'services')
3 files changed, 121 insertions, 45 deletions
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 07855d9..aa3dfa6 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -19,7 +19,7 @@ package com.android.server; import static android.Manifest.permission.MANAGE_NETWORK_POLICY; import static android.net.ConnectivityManager.isNetworkTypeValid; import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; -import static android.net.NetworkPolicyManager.RULE_REJECT_PAID; +import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import android.bluetooth.BluetoothTetheringDataTracker; import android.content.ContentResolver; @@ -71,6 +71,7 @@ import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; import com.google.android.collect.Lists; +import com.google.android.collect.Sets; import java.io.FileDescriptor; import java.io.IOException; @@ -78,8 +79,10 @@ import java.io.PrintWriter; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.GregorianCalendar; +import java.util.HashSet; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -108,8 +111,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { private Vpn mVpn; + /** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */ + private Object mRulesLock = new Object(); /** Currently active network rules by UID. */ private SparseIntArray mUidRules = new SparseIntArray(); + /** Set of ifaces that are costly. */ + private HashSet<String> mMeteredIfaces = Sets.newHashSet(); /** * Sometimes we want to refer to the individual network state @@ -570,31 +577,35 @@ public class ConnectivityService extends IConnectivityManager.Stub { } /** - * Check if UID is blocked from using the given {@link NetworkInfo}. + * Check if UID should be blocked from using the network represented by the + * given {@link NetworkStateTracker}. */ - private boolean isNetworkBlocked(NetworkInfo info, int uid) { - synchronized (mUidRules) { - // TODO: expand definition of "paid" network to cover tethered or - // paid hotspot use cases. - final boolean networkIsPaid = info.getType() != ConnectivityManager.TYPE_WIFI; - final int uidRules = mUidRules.get(uid, RULE_ALLOW_ALL); + private boolean isNetworkBlocked(NetworkStateTracker tracker, int uid) { + final String iface = tracker.getLinkProperties().getInterfaceName(); - if (networkIsPaid && (uidRules & RULE_REJECT_PAID) != 0) { - return true; - } + final boolean networkCostly; + final int uidRules; + synchronized (mRulesLock) { + networkCostly = mMeteredIfaces.contains(iface); + uidRules = mUidRules.get(uid, RULE_ALLOW_ALL); + } - // no restrictive rules; network is visible - return false; + if (networkCostly && (uidRules & RULE_REJECT_METERED) != 0) { + return true; } + + // no restrictive rules; network is visible + return false; } /** - * Return a filtered version of the given {@link NetworkInfo}, potentially - * marked {@link DetailedState#BLOCKED} based on - * {@link #isNetworkBlocked(NetworkInfo, int)}. + * Return a filtered {@link NetworkInfo}, potentially marked + * {@link DetailedState#BLOCKED} based on + * {@link #isNetworkBlocked(NetworkStateTracker, int)}. */ - private NetworkInfo filterNetworkInfo(NetworkInfo info, int uid) { - if (isNetworkBlocked(info, uid)) { + private NetworkInfo getFilteredNetworkInfo(NetworkStateTracker tracker, int uid) { + NetworkInfo info = tracker.getNetworkInfo(); + if (isNetworkBlocked(tracker, uid)) { // network is blocked; clone and override state info = new NetworkInfo(info); info.setDetailedState(DetailedState.BLOCKED, null, null); @@ -634,7 +645,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (isNetworkTypeValid(networkType)) { final NetworkStateTracker tracker = mNetTrackers[networkType]; if (tracker != null) { - info = filterNetworkInfo(tracker.getNetworkInfo(), uid); + info = getFilteredNetworkInfo(tracker, uid); } } return info; @@ -645,10 +656,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { enforceAccessPermission(); final int uid = Binder.getCallingUid(); final ArrayList<NetworkInfo> result = Lists.newArrayList(); - synchronized (mUidRules) { + synchronized (mRulesLock) { for (NetworkStateTracker tracker : mNetTrackers) { if (tracker != null) { - result.add(filterNetworkInfo(tracker.getNetworkInfo(), uid)); + result.add(getFilteredNetworkInfo(tracker, uid)); } } } @@ -685,10 +696,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { enforceAccessPermission(); final int uid = Binder.getCallingUid(); final ArrayList<NetworkState> result = Lists.newArrayList(); - synchronized (mUidRules) { + synchronized (mRulesLock) { for (NetworkStateTracker tracker : mNetTrackers) { if (tracker != null) { - final NetworkInfo info = filterNetworkInfo(tracker.getNetworkInfo(), uid); + final NetworkInfo info = getFilteredNetworkInfo(tracker, uid); result.add(new NetworkState( info, tracker.getLinkProperties(), tracker.getLinkCapabilities())); } @@ -1139,15 +1150,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() { @Override - public void onRulesChanged(int uid, int uidRules) { + public void onUidRulesChanged(int uid, int uidRules) { // only someone like NPMS should only be calling us mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); if (LOGD_RULES) { - Slog.d(TAG, "onRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")"); + Slog.d(TAG, "onUidRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")"); } - synchronized (mUidRules) { + synchronized (mRulesLock) { // skip update when we've already applied rules final int oldRules = mUidRules.get(uid, RULE_ALLOW_ALL); if (oldRules == uidRules) return; @@ -1158,6 +1169,24 @@ public class ConnectivityService extends IConnectivityManager.Stub { // TODO: dispatch into NMS to push rules towards kernel module // TODO: notify UID when it has requested targeted updates } + + @Override + public void onMeteredIfacesChanged(String[] meteredIfaces) { + // only someone like NPMS should only be calling us + mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); + + if (LOGD_RULES) { + Slog.d(TAG, + "onMeteredIfacesChanged(ifaces=" + Arrays.toString(meteredIfaces) + ")"); + } + + synchronized (mRulesLock) { + mMeteredIfaces.clear(); + for (String iface : meteredIfaces) { + mMeteredIfaces.add(iface); + } + } + } }; /** diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java index 9cbe82d..43f3c63 100644 --- a/services/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java @@ -29,9 +29,9 @@ import static android.net.NetworkPolicyManager.ACTION_DATA_USAGE_LIMIT; import static android.net.NetworkPolicyManager.ACTION_DATA_USAGE_WARNING; import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE; import static android.net.NetworkPolicyManager.POLICY_NONE; -import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND; +import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; -import static android.net.NetworkPolicyManager.RULE_REJECT_PAID; +import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.NetworkPolicyManager.computeLastCycleBoundary; import static android.net.NetworkPolicyManager.dumpPolicy; import static android.net.NetworkPolicyManager.dumpRules; @@ -87,6 +87,7 @@ import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Objects; import com.google.android.collect.Lists; import com.google.android.collect.Maps; +import com.google.android.collect.Sets; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -103,6 +104,7 @@ import java.net.ProtocolException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import libcore.io.IoUtils; @@ -164,6 +166,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { /** Current derived network rules for each UID. */ private SparseIntArray mUidRules = new SparseIntArray(); + /** Set of ifaces that are metered. */ + private HashSet<String> mMeteredIfaces = Sets.newHashSet(); + /** Foreground at both UID and PID granularity. */ private SparseBooleanArray mUidForeground = new SparseBooleanArray(); private SparseArray<SparseBooleanArray> mUidPidForeground = new SparseArray< @@ -536,6 +541,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis(); + mMeteredIfaces.clear(); + // apply each policy that we found ifaces for; compute remaining data // based on current cycle and historical stats, and push to kernel. for (NetworkPolicy policy : rules.keySet()) { @@ -566,8 +573,27 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // remaining "quota" is based on usage in current cycle final long quota = Math.max(0, policy.limitBytes - total); //kernelSetIfacesQuota(ifaces, quota); + + for (String iface : ifaces) { + mMeteredIfaces.add(iface); + } } } + + // dispatch changed rule to existing listeners + // TODO: dispatch outside of holding lock + final String[] meteredIfaces = mMeteredIfaces.toArray(new String[mMeteredIfaces.size()]); + final int length = mListeners.beginBroadcast(); + for (int i = 0; i < length; i++) { + final INetworkPolicyListener listener = mListeners.getBroadcastItem(i); + if (listener != null) { + try { + listener.onMeteredIfacesChanged(meteredIfaces); + } catch (RemoteException e) { + } + } + } + mListeners.finishBroadcast(); } /** @@ -754,17 +780,29 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { synchronized (mRulesLock) { // dispatch any existing rules to new listeners + // TODO: dispatch outside of holding lock final int size = mUidRules.size(); for (int i = 0; i < size; i++) { final int uid = mUidRules.keyAt(i); final int uidRules = mUidRules.valueAt(i); if (uidRules != RULE_ALLOW_ALL) { try { - listener.onRulesChanged(uid, uidRules); + listener.onUidRulesChanged(uid, uidRules); } catch (RemoteException e) { } } } + + // dispatch any metered ifaces to new listeners + // TODO: dispatch outside of holding lock + if (mMeteredIfaces.size() > 0) { + final String[] meteredIfaces = mMeteredIfaces.toArray( + new String[mMeteredIfaces.size()]); + try { + listener.onMeteredIfacesChanged(meteredIfaces); + } catch (RemoteException e) { + } + } } } @@ -921,9 +959,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // derive active rules based on policy and active state int uidRules = RULE_ALLOW_ALL; - if (!uidForeground && (uidPolicy & POLICY_REJECT_PAID_BACKGROUND) != 0) { - // uid in background, and policy says to block paid data - uidRules = RULE_REJECT_PAID; + if (!uidForeground && (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) { + // uid in background, and policy says to block metered data + uidRules = RULE_REJECT_METERED; } // TODO: only dispatch when rules actually change @@ -931,16 +969,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // record rule locally to dispatch to new listeners mUidRules.put(uid, uidRules); - final boolean rejectPaid = (uidRules & RULE_REJECT_PAID) != 0; + final boolean rejectMetered = (uidRules & RULE_REJECT_METERED) != 0; //kernelSetUidRejectPaid(uid, rejectPaid); // dispatch changed rule to existing listeners + // TODO: dispatch outside of holding lock final int length = mListeners.beginBroadcast(); for (int i = 0; i < length; i++) { final INetworkPolicyListener listener = mListeners.getBroadcastItem(i); if (listener != null) { try { - listener.onRulesChanged(uid, uidRules); + listener.onUidRulesChanged(uid, uidRules); } catch (RemoteException e) { } } diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java index edccf6c..5cb1763 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java @@ -19,13 +19,14 @@ package com.android.server; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkPolicyManager.POLICY_NONE; -import static android.net.NetworkPolicyManager.POLICY_REJECT_PAID_BACKGROUND; +import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; -import static android.net.NetworkPolicyManager.RULE_REJECT_PAID; +import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.NetworkPolicyManager.computeLastCycleBoundary; import static android.net.NetworkStats.UID_ALL; import static android.net.TrafficStats.TEMPLATE_WIFI; import static org.easymock.EasyMock.anyInt; +import static org.easymock.EasyMock.aryEq; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.eq; @@ -182,7 +183,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { final Future<Intent> backgroundChanged = mServiceContext.nextBroadcastIntent( ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED); - mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND); + mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND); backgroundChanged.get(); } @@ -225,12 +226,12 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { expectRulesChanged(UID_A, RULE_ALLOW_ALL); replay(); mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, true); - mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND); + mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND); verifyAndReset(); // now turn screen off and verify REJECT rule expect(mPowerManager.isScreenOn()).andReturn(false).atLeastOnce(); - expectRulesChanged(UID_A, RULE_REJECT_PAID); + expectRulesChanged(UID_A, RULE_REJECT_METERED); replay(); mServiceContext.sendBroadcast(new Intent(Intent.ACTION_SCREEN_OFF)); verifyAndReset(); @@ -260,9 +261,9 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { public void testPolicyReject() throws Exception { // POLICY_REJECT should RULE_ALLOW in background - expectRulesChanged(UID_A, RULE_REJECT_PAID); + expectRulesChanged(UID_A, RULE_REJECT_METERED); replay(); - mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND); + mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND); verifyAndReset(); // POLICY_REJECT should RULE_ALLOW in foreground @@ -272,7 +273,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { verifyAndReset(); // POLICY_REJECT should RULE_REJECT in background - expectRulesChanged(UID_A, RULE_REJECT_PAID); + expectRulesChanged(UID_A, RULE_REJECT_METERED); replay(); mProcessObserver.onForegroundActivitiesChanged(PID_1, UID_A, false); verifyAndReset(); @@ -287,9 +288,9 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { verifyAndReset(); // adding POLICY_REJECT should cause RULE_REJECT - expectRulesChanged(UID_A, RULE_REJECT_PAID); + expectRulesChanged(UID_A, RULE_REJECT_METERED); replay(); - mService.setUidPolicy(UID_A, POLICY_REJECT_PAID_BACKGROUND); + mService.setUidPolicy(UID_A, POLICY_REJECT_METERED_BACKGROUND); verifyAndReset(); // removing POLICY_REJECT should return us to RULE_ALLOW @@ -353,6 +354,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { state = new NetworkState[] { buildWifi() }; expect(mConnManager.getAllNetworkState()).andReturn(state).atLeastOnce(); expectTime(TIME_MAR_10 + elapsedRealtime); + expectMeteredIfacesChanged(); replay(); mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION)); @@ -373,6 +375,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { // TODO: write up NetworkManagementService mock expectClearNotifications(); + expectMeteredIfacesChanged(TEST_IFACE); replay(); setNetworkPolicies(new NetworkPolicy(TEMPLATE_WIFI, null, CYCLE_DAY, 1024L, 2048L)); @@ -411,7 +414,12 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { } private void expectRulesChanged(int uid, int policy) throws Exception { - mPolicyListener.onRulesChanged(eq(uid), eq(policy)); + mPolicyListener.onUidRulesChanged(eq(uid), eq(policy)); + expectLastCall().atLeastOnce(); + } + + private void expectMeteredIfacesChanged(String... ifaces) throws Exception { + mPolicyListener.onMeteredIfacesChanged(aryEq(ifaces)); expectLastCall().atLeastOnce(); } |