diff options
author | Lorenzo Colitti <lorenzo@google.com> | 2017-08-15 01:57:28 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2017-08-15 01:57:28 +0000 |
commit | deb4eb5d05ccf983adcb7252c98c2580a8a36c60 (patch) | |
tree | fe1225c41ac6128870708616cb71786c919233e8 | |
parent | ee6e43c033ac41c82969bb3dd074687de38052d1 (diff) | |
parent | 50b60fc34dcf879007b5ab6e9b67f806e5a57215 (diff) | |
download | base-deb4eb5d05ccf983adcb7252c98c2580a8a36c60.tar.gz |
Merge changes I82d3bee0,I9c9413d7
* changes:
Pass data usage limits to tethering offload code.
Don't time out when fetching tether offload stats.
5 files changed, 195 insertions, 30 deletions
diff --git a/core/java/android/net/ITetheringStatsProvider.aidl b/core/java/android/net/ITetheringStatsProvider.aidl index 769086da42b4..1aeabc1e62de 100644 --- a/core/java/android/net/ITetheringStatsProvider.aidl +++ b/core/java/android/net/ITetheringStatsProvider.aidl @@ -19,7 +19,7 @@ package android.net; import android.net.NetworkStats; /** - * Interface that allows NetworkManagementService to query for tethering statistics. + * Interface for NetworkManagementService to query tethering statistics and set data limits. * * TODO: this does not really need to be an interface since Tethering runs in the same process * as NetworkManagementService. Consider refactoring Tethering to use direct access to @@ -29,5 +29,14 @@ import android.net.NetworkStats; * @hide */ interface ITetheringStatsProvider { + // Returns cumulative statistics for all tethering sessions since boot, on all upstreams. NetworkStats getTetherStats(); + + // Sets the interface quota for the specified upstream interface. This is defined as the number + // of bytes, starting from zero and counting from now, after which data should stop being + // forwarded to/from the specified upstream. A value of QUOTA_UNLIMITED means there is no limit. + void setInterfaceQuota(String iface, long quotaBytes); + + // Indicates that no data usage limit is set. + const int QUOTA_UNLIMITED = -1; } diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 097202b9b0a9..3d638be3e82d 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -1538,6 +1538,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); } + + synchronized (mTetheringStatsProviders) { + for (ITetheringStatsProvider provider : mTetheringStatsProviders.keySet()) { + try { + provider.setInterfaceQuota(iface, quotaBytes); + } catch (RemoteException e) { + Log.e(TAG, "Problem setting tethering data limit on provider " + + mTetheringStatsProviders.get(provider) + ": " + e); + } + } + } } } @@ -1564,6 +1575,17 @@ public class NetworkManagementService extends INetworkManagementService.Stub } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); } + + synchronized (mTetheringStatsProviders) { + for (ITetheringStatsProvider provider : mTetheringStatsProviders.keySet()) { + try { + provider.setInterfaceQuota(iface, ITetheringStatsProvider.QUOTA_UNLIMITED); + } catch (RemoteException e) { + Log.e(TAG, "Problem removing tethering data limit on provider " + + mTetheringStatsProviders.get(provider) + ": " + e); + } + } + } } } @@ -1823,6 +1845,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub } return stats; } + + @Override + public void setInterfaceQuota(String iface, long quotaBytes) { + // Do nothing. netd is already informed of quota changes in setInterfaceQuota. + } } @Override diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java index 1a5ff778010c..55e290a4215e 100644 --- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java +++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java @@ -46,7 +46,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Objects; import java.util.Set; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** @@ -58,8 +57,6 @@ import java.util.concurrent.TimeUnit; public class OffloadController { private static final String TAG = OffloadController.class.getSimpleName(); - private static final int STATS_FETCH_TIMEOUT_MS = 1000; - private final Handler mHandler; private final OffloadHardwareInterface mHwInterface; private final ContentResolver mContentResolver; @@ -76,9 +73,17 @@ public class OffloadController { private Set<String> mLastLocalPrefixStrs; // Maps upstream interface names to offloaded traffic statistics. + // Always contains the latest value received from the hardware for each interface, regardless of + // whether offload is currently running on that interface. private HashMap<String, OffloadHardwareInterface.ForwardedStats> mForwardedStats = new HashMap<>(); + // Maps upstream interface names to interface quotas. + // Always contains the latest value received from the framework for each interface, regardless + // of whether offload is currently running (or is even supported) on that interface. Only + // includes upstream interfaces that have a quota set. + private HashMap<String, Long> mInterfaceQuotas = new HashMap<>(); + public OffloadController(Handler h, OffloadHardwareInterface hwi, ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) { mHandler = h; @@ -177,36 +182,38 @@ public class OffloadController { @Override public NetworkStats getTetherStats() { NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0); - CountDownLatch latch = new CountDownLatch(1); - mHandler.post(() -> { - try { - NetworkStats.Entry entry = new NetworkStats.Entry(); - entry.set = SET_DEFAULT; - entry.tag = TAG_NONE; - entry.uid = UID_TETHERING; - - updateStatsForCurrentUpstream(); - - for (String iface : mForwardedStats.keySet()) { - entry.iface = iface; - entry.rxBytes = mForwardedStats.get(iface).rxBytes; - entry.txBytes = mForwardedStats.get(iface).txBytes; - stats.addValues(entry); - } - } finally { - latch.countDown(); + // We can't just post to mHandler because we are mostly (but not always) called by + // NetworkStatsService#performPollLocked, which is (currently) on the same thread as us. + mHandler.runWithScissors(() -> { + NetworkStats.Entry entry = new NetworkStats.Entry(); + entry.set = SET_DEFAULT; + entry.tag = TAG_NONE; + entry.uid = UID_TETHERING; + + updateStatsForCurrentUpstream(); + + for (String iface : mForwardedStats.keySet()) { + entry.iface = iface; + entry.rxBytes = mForwardedStats.get(iface).rxBytes; + entry.txBytes = mForwardedStats.get(iface).txBytes; + stats.addValues(entry); } - }); - - try { - latch.await(STATS_FETCH_TIMEOUT_MS, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - mLog.e("Tethering stats fetch timed out after " + STATS_FETCH_TIMEOUT_MS + "ms"); - } + }, 0); return stats; } + + public void setInterfaceQuota(String iface, long quotaBytes) { + mHandler.post(() -> { + if (quotaBytes == ITetheringStatsProvider.QUOTA_UNLIMITED) { + mInterfaceQuotas.remove(iface); + } else { + mInterfaceQuotas.put(iface, quotaBytes); + } + maybeUpdateDataLimit(iface); + }); + } } private void maybeUpdateStats(String iface) { @@ -220,6 +227,22 @@ public class OffloadController { mForwardedStats.get(iface).add(mHwInterface.getForwardedStats(iface)); } + private boolean maybeUpdateDataLimit(String iface) { + // setDataLimit may only be called while offload is occuring on this upstream. + if (!started() || + mUpstreamLinkProperties == null || + !TextUtils.equals(iface, mUpstreamLinkProperties.getInterfaceName())) { + return true; + } + + Long limit = mInterfaceQuotas.get(iface); + if (limit == null) { + limit = Long.MAX_VALUE; + } + + return mHwInterface.setDataLimit(iface, limit); + } + private void updateStatsForCurrentUpstream() { if (mUpstreamLinkProperties != null) { maybeUpdateStats(mUpstreamLinkProperties.getInterfaceName()); @@ -309,8 +332,21 @@ public class OffloadController { } } - return mHwInterface.setUpstreamParameters( + boolean success = mHwInterface.setUpstreamParameters( iface, v4addr, v4gateway, (v6gateways.isEmpty() ? null : v6gateways)); + + if (!success) { + return success; + } + + // Data limits can only be set once offload is running on the upstream. + success = maybeUpdateDataLimit(iface); + if (!success) { + mLog.log("Setting data limit for " + iface + " failed, disabling offload."); + stop(); + } + + return success; } private boolean computeAndPushLocalPrefixes() { diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java index 4df566f03d6d..86ff0a607700 100644 --- a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java +++ b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java @@ -188,6 +188,27 @@ public class OffloadHardwareInterface { return results.success; } + public boolean setDataLimit(String iface, long limit) { + + final String logmsg = String.format("setDataLimit(%s, %d)", iface, limit); + + final CbResults results = new CbResults(); + try { + mOffloadControl.setDataLimit( + iface, limit, + (boolean success, String errMsg) -> { + results.success = success; + results.errMsg = errMsg; + }); + } catch (RemoteException e) { + record(logmsg, e); + return false; + } + + record(logmsg, results); + return results.success; + } + public boolean setUpstreamParameters( String iface, String v4addr, String v4gateway, ArrayList<String> v6gws) { iface = (iface != null) ? iface : NO_INTERFACE_NAME; diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java index 45525e624d90..d29a94b9e066 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java @@ -25,6 +25,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; @@ -45,6 +46,7 @@ import android.net.LinkProperties; import android.net.NetworkStats; import android.net.RouteInfo; import android.net.util.SharedLog; +import android.os.ConditionVariable; import android.os.Handler; import android.os.Looper; import android.os.INetworkManagementService; @@ -112,6 +114,12 @@ public class OffloadControllerTest { Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0); } + private void waitForIdle() { + ConditionVariable cv = new ConditionVariable(); + new Handler(Looper.getMainLooper()).post(() -> { cv.open(); }); + cv.block(); + } + private OffloadController makeOffloadController() throws Exception { OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()), mHardware, mContentResolver, mNMService, new SharedLog("test")); @@ -421,4 +429,68 @@ public class OffloadControllerTest { entry = stats.getValues(ethernetPosition, entry); assertNetworkStats(ethernetIface, ethernetStats, entry); } + + @Test + public void testSetInterfaceQuota() throws Exception { + setupFunctioningHardwareInterface(); + enableOffload(); + + final OffloadController offload = makeOffloadController(); + offload.start(); + + final String ethernetIface = "eth1"; + final String mobileIface = "rmnet_data0"; + final long ethernetLimit = 12345; + final long mobileLimit = 12345678; + + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(ethernetIface); + offload.setUpstreamLinkProperties(lp); + + ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue(); + final InOrder inOrder = inOrder(mHardware); + when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true); + when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true); + + // Applying an interface quota to the current upstream immediately sends it to the hardware. + provider.setInterfaceQuota(ethernetIface, ethernetLimit); + waitForIdle(); + inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit); + inOrder.verifyNoMoreInteractions(); + + // Applying an interface quota to another upstream does not take any immediate action. + provider.setInterfaceQuota(mobileIface, mobileLimit); + waitForIdle(); + inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong()); + + // Switching to that upstream causes the quota to be applied if the parameters were applied + // correctly. + lp.setInterfaceName(mobileIface); + offload.setUpstreamLinkProperties(lp); + waitForIdle(); + inOrder.verify(mHardware).setDataLimit(mobileIface, mobileLimit); + + // Setting a limit of ITetheringStatsProvider.QUOTA_UNLIMITED causes the limit to be set + // to Long.MAX_VALUE. + provider.setInterfaceQuota(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED); + waitForIdle(); + inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE); + + // If setting upstream parameters fails, then the data limit is not set. + when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(false); + lp.setInterfaceName(ethernetIface); + offload.setUpstreamLinkProperties(lp); + provider.setInterfaceQuota(mobileIface, mobileLimit); + waitForIdle(); + inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong()); + + // If setting the data limit fails while changing upstreams, offload is stopped. + when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true); + when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(false); + lp.setInterfaceName(mobileIface); + offload.setUpstreamLinkProperties(lp); + provider.setInterfaceQuota(mobileIface, mobileLimit); + waitForIdle(); + inOrder.verify(mHardware).stopOffloadControl(); + } } |