OSDN Git Service

Pass data usage limits to tethering offload code.
authorLorenzo Colitti <lorenzo@google.com>
Fri, 11 Aug 2017 04:47:49 +0000 (13:47 +0900)
committerLorenzo Colitti <lorenzo@google.com>
Mon, 14 Aug 2017 15:09:23 +0000 (00:09 +0900)
Bug: 29337859
Bug: 32163131
Test: builds
Test: OffloadControllerTest passes
Change-Id: I82d3bee030bafa8fe85855885b5fc3893e699181

core/java/android/net/ITetheringStatsProvider.aidl
services/core/java/com/android/server/NetworkManagementService.java
services/core/java/com/android/server/connectivity/tethering/OffloadController.java
services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java

index 769086d..1aeabc1 100644 (file)
@@ -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;
 }
index 097202b..3d638be 100644 (file)
@@ -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
index ccbac31..55e290a 100644 (file)
@@ -73,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;
@@ -195,6 +203,17 @@ public class OffloadController {
 
             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) {
@@ -208,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());
@@ -297,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() {
index 4df566f..86ff0a6 100644 (file)
@@ -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;
index 45525e6..d29a94b 100644 (file)
@@ -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();
+    }
 }