Loading services/core/java/com/android/server/connectivity/tethering/OffloadController.java +49 −16 Original line number Diff line number Diff line Loading @@ -63,6 +63,8 @@ import java.util.concurrent.TimeUnit; */ public class OffloadController { private static final String TAG = OffloadController.class.getSimpleName(); private static final String ANYIP = "0.0.0.0"; private static final ForwardedStats EMPTY_STATS = new ForwardedStats(); private final Handler mHandler; private final OffloadHardwareInterface mHwInterface; Loading Loading @@ -148,6 +150,14 @@ public class OffloadController { public void onStoppedUnsupported() { if (!started()) return; mLog.log("onStoppedUnsupported"); // Poll for statistics and trigger a sweep of tethering // stats by observers. This might not succeed, but it's // worth trying anyway. We need to do this because from // this point on we continue with software forwarding, // and we need to synchronize stats and limits between // software and hardware forwarding. updateStatsForAllUpstreams(); forceTetherStatsPoll(); } @Override Loading @@ -155,11 +165,15 @@ public class OffloadController { if (!started()) return; mLog.log("onSupportAvailable"); // [1] Poll for statistics and notify NetworkStats // [2] (Re)Push all state: // [a] push local prefixes // [b] push downstreams // [c] push upstream parameters // [1] Poll for statistics and trigger a sweep of stats // by observers. We need to do this to ensure that any // limits set take into account any software tethering // traffic that has been happening in the meantime. updateStatsForAllUpstreams(); forceTetherStatsPoll(); // [2] (Re)Push all state. // TODO: computeAndPushLocalPrefixes() // TODO: push all downstream state. pushUpstreamParameters(null); } Loading @@ -181,12 +195,7 @@ public class OffloadController { // The stats for the previous upstream were already updated on this thread // just after the upstream was changed, so they are also up-to-date. updateStatsForCurrentUpstream(); try { mNms.tetherLimitReached(mStatsProvider); } catch (RemoteException e) { mLog.e("Cannot report data limit reached: " + e); } forceTetherStatsPoll(); } @Override Loading Loading @@ -305,13 +314,33 @@ public class OffloadController { maybeUpdateStats(currentUpstreamInterface()); } private void updateStatsForAllUpstreams() { // In practice, there should only ever be a single digit number of // upstream interfaces over the lifetime of an active tethering session. // Roughly speaking, imagine a very ambitious one or two of each of the // following interface types: [ "rmnet_data", "wlan", "eth", "rndis" ]. for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) { maybeUpdateStats(kv.getKey()); } } private void forceTetherStatsPoll() { try { mNms.tetherLimitReached(mStatsProvider); } catch (RemoteException e) { mLog.e("Cannot report data limit reached: " + e); } } public void setUpstreamLinkProperties(LinkProperties lp) { if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return; String prevUpstream = (mUpstreamLinkProperties != null) ? mUpstreamLinkProperties.getInterfaceName() : null; final String prevUpstream = currentUpstreamInterface(); mUpstreamLinkProperties = (lp != null) ? new LinkProperties(lp) : null; // Make sure we record this interface in the ForwardedStats map. final String iface = currentUpstreamInterface(); if (!TextUtils.isEmpty(iface)) mForwardedStats.putIfAbsent(iface, EMPTY_STATS); // TODO: examine return code and decide what to do if programming // upstream parameters fails (probably just wait for a subsequent Loading Loading @@ -378,16 +407,20 @@ public class OffloadController { } private boolean pushUpstreamParameters(String prevUpstream) { if (mUpstreamLinkProperties == null) { final String iface = currentUpstreamInterface(); if (TextUtils.isEmpty(iface)) { final boolean rval = mHwInterface.setUpstreamParameters("", ANYIP, ANYIP, null); // Update stats after we've told the hardware to stop forwarding so // we don't miss packets. maybeUpdateStats(prevUpstream); return mHwInterface.setUpstreamParameters(null, null, null, null); return rval; } // A stacked interface cannot be an upstream for hardware offload. // Consequently, we examine only the primary interface name, look at // getAddresses() rather than getAllAddresses(), and check getRoutes() // rather than getAllRoutes(). final String iface = mUpstreamLinkProperties.getInterfaceName(); final ArrayList<String> v6gateways = new ArrayList<>(); String v4addr = null; String v4gateway = null; Loading tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java +72 −3 Original line number Diff line number Diff line Loading @@ -32,12 +32,13 @@ import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.Context; Loading Loading @@ -441,6 +442,9 @@ public class OffloadControllerTest { ethernetStats.txBytes = 100000; when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats); offload.setUpstreamLinkProperties(null); // Expect that we first clear the HAL's upstream parameters. inOrder.verify(mHardware, times(1)).setUpstreamParameters( eq(""), eq("0.0.0.0"), eq("0.0.0.0"), eq(null)); // Expect that we fetch stats from the previous upstream. inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface)); Loading @@ -450,8 +454,6 @@ public class OffloadControllerTest { waitForIdle(); // There is no current upstream, so no stats are fetched. inOrder.verify(mHardware, never()).getForwardedStats(any()); inOrder.verify(mHardware, times(1)).setUpstreamParameters( eq(null), eq(null), eq(null), eq(null)); inOrder.verifyNoMoreInteractions(); assertEquals(2, stats.size()); Loading Loading @@ -626,6 +628,73 @@ public class OffloadControllerTest { inOrder.verifyNoMoreInteractions(); } @Test public void testControlCallbackOnStoppedUnsupportedFetchesAllStats() throws Exception { setupFunctioningHardwareInterface(); enableOffload(); final OffloadController offload = makeOffloadController(); offload.start(); // Pretend to set a few different upstreams (only the interface name // matters for this test; we're ignoring IP and route information). final LinkProperties upstreamLp = new LinkProperties(); for (String ifname : new String[]{RMNET0, WLAN0, RMNET0}) { upstreamLp.setInterfaceName(ifname); offload.setUpstreamLinkProperties(upstreamLp); } // Clear invocation history, especially the getForwardedStats() calls // that happen with setUpstreamParameters(). clearInvocations(mHardware); OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue(); callback.onStoppedUnsupported(); // Verify forwarded stats behaviour. verify(mHardware, times(1)).getForwardedStats(eq(RMNET0)); verify(mHardware, times(1)).getForwardedStats(eq(WLAN0)); verifyNoMoreInteractions(mHardware); verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue()); verifyNoMoreInteractions(mNMService); } @Test public void testControlCallbackOnSupportAvailableFetchesAllStatsAndPushesAllParameters() throws Exception { setupFunctioningHardwareInterface(); enableOffload(); final OffloadController offload = makeOffloadController(); offload.start(); // Pretend to set a few different upstreams (only the interface name // matters for this test; we're ignoring IP and route information). final LinkProperties upstreamLp = new LinkProperties(); for (String ifname : new String[]{RMNET0, WLAN0, RMNET0}) { upstreamLp.setInterfaceName(ifname); offload.setUpstreamLinkProperties(upstreamLp); } // Clear invocation history, especially the getForwardedStats() calls // that happen with setUpstreamParameters(). clearInvocations(mHardware); OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue(); callback.onSupportAvailable(); // Verify forwarded stats behaviour. verify(mHardware, times(1)).getForwardedStats(eq(RMNET0)); verify(mHardware, times(1)).getForwardedStats(eq(WLAN0)); verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue()); verifyNoMoreInteractions(mNMService); // TODO: verify local prefixes and downstreams are also pushed to the HAL. verify(mHardware, times(1)).setUpstreamParameters(eq(RMNET0), any(), any(), any()); verify(mHardware, times(1)).setDataLimit(eq(RMNET0), anyLong()); verifyNoMoreInteractions(mHardware); } private static void assertArrayListContains(ArrayList<String> list, String... elems) { for (String element : elems) { assertTrue(list.contains(element)); Loading Loading
services/core/java/com/android/server/connectivity/tethering/OffloadController.java +49 −16 Original line number Diff line number Diff line Loading @@ -63,6 +63,8 @@ import java.util.concurrent.TimeUnit; */ public class OffloadController { private static final String TAG = OffloadController.class.getSimpleName(); private static final String ANYIP = "0.0.0.0"; private static final ForwardedStats EMPTY_STATS = new ForwardedStats(); private final Handler mHandler; private final OffloadHardwareInterface mHwInterface; Loading Loading @@ -148,6 +150,14 @@ public class OffloadController { public void onStoppedUnsupported() { if (!started()) return; mLog.log("onStoppedUnsupported"); // Poll for statistics and trigger a sweep of tethering // stats by observers. This might not succeed, but it's // worth trying anyway. We need to do this because from // this point on we continue with software forwarding, // and we need to synchronize stats and limits between // software and hardware forwarding. updateStatsForAllUpstreams(); forceTetherStatsPoll(); } @Override Loading @@ -155,11 +165,15 @@ public class OffloadController { if (!started()) return; mLog.log("onSupportAvailable"); // [1] Poll for statistics and notify NetworkStats // [2] (Re)Push all state: // [a] push local prefixes // [b] push downstreams // [c] push upstream parameters // [1] Poll for statistics and trigger a sweep of stats // by observers. We need to do this to ensure that any // limits set take into account any software tethering // traffic that has been happening in the meantime. updateStatsForAllUpstreams(); forceTetherStatsPoll(); // [2] (Re)Push all state. // TODO: computeAndPushLocalPrefixes() // TODO: push all downstream state. pushUpstreamParameters(null); } Loading @@ -181,12 +195,7 @@ public class OffloadController { // The stats for the previous upstream were already updated on this thread // just after the upstream was changed, so they are also up-to-date. updateStatsForCurrentUpstream(); try { mNms.tetherLimitReached(mStatsProvider); } catch (RemoteException e) { mLog.e("Cannot report data limit reached: " + e); } forceTetherStatsPoll(); } @Override Loading Loading @@ -305,13 +314,33 @@ public class OffloadController { maybeUpdateStats(currentUpstreamInterface()); } private void updateStatsForAllUpstreams() { // In practice, there should only ever be a single digit number of // upstream interfaces over the lifetime of an active tethering session. // Roughly speaking, imagine a very ambitious one or two of each of the // following interface types: [ "rmnet_data", "wlan", "eth", "rndis" ]. for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) { maybeUpdateStats(kv.getKey()); } } private void forceTetherStatsPoll() { try { mNms.tetherLimitReached(mStatsProvider); } catch (RemoteException e) { mLog.e("Cannot report data limit reached: " + e); } } public void setUpstreamLinkProperties(LinkProperties lp) { if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return; String prevUpstream = (mUpstreamLinkProperties != null) ? mUpstreamLinkProperties.getInterfaceName() : null; final String prevUpstream = currentUpstreamInterface(); mUpstreamLinkProperties = (lp != null) ? new LinkProperties(lp) : null; // Make sure we record this interface in the ForwardedStats map. final String iface = currentUpstreamInterface(); if (!TextUtils.isEmpty(iface)) mForwardedStats.putIfAbsent(iface, EMPTY_STATS); // TODO: examine return code and decide what to do if programming // upstream parameters fails (probably just wait for a subsequent Loading Loading @@ -378,16 +407,20 @@ public class OffloadController { } private boolean pushUpstreamParameters(String prevUpstream) { if (mUpstreamLinkProperties == null) { final String iface = currentUpstreamInterface(); if (TextUtils.isEmpty(iface)) { final boolean rval = mHwInterface.setUpstreamParameters("", ANYIP, ANYIP, null); // Update stats after we've told the hardware to stop forwarding so // we don't miss packets. maybeUpdateStats(prevUpstream); return mHwInterface.setUpstreamParameters(null, null, null, null); return rval; } // A stacked interface cannot be an upstream for hardware offload. // Consequently, we examine only the primary interface name, look at // getAddresses() rather than getAllAddresses(), and check getRoutes() // rather than getAllRoutes(). final String iface = mUpstreamLinkProperties.getInterfaceName(); final ArrayList<String> v6gateways = new ArrayList<>(); String v4addr = null; String v4gateway = null; Loading
tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java +72 −3 Original line number Diff line number Diff line Loading @@ -32,12 +32,13 @@ import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.content.Context; Loading Loading @@ -441,6 +442,9 @@ public class OffloadControllerTest { ethernetStats.txBytes = 100000; when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats); offload.setUpstreamLinkProperties(null); // Expect that we first clear the HAL's upstream parameters. inOrder.verify(mHardware, times(1)).setUpstreamParameters( eq(""), eq("0.0.0.0"), eq("0.0.0.0"), eq(null)); // Expect that we fetch stats from the previous upstream. inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface)); Loading @@ -450,8 +454,6 @@ public class OffloadControllerTest { waitForIdle(); // There is no current upstream, so no stats are fetched. inOrder.verify(mHardware, never()).getForwardedStats(any()); inOrder.verify(mHardware, times(1)).setUpstreamParameters( eq(null), eq(null), eq(null), eq(null)); inOrder.verifyNoMoreInteractions(); assertEquals(2, stats.size()); Loading Loading @@ -626,6 +628,73 @@ public class OffloadControllerTest { inOrder.verifyNoMoreInteractions(); } @Test public void testControlCallbackOnStoppedUnsupportedFetchesAllStats() throws Exception { setupFunctioningHardwareInterface(); enableOffload(); final OffloadController offload = makeOffloadController(); offload.start(); // Pretend to set a few different upstreams (only the interface name // matters for this test; we're ignoring IP and route information). final LinkProperties upstreamLp = new LinkProperties(); for (String ifname : new String[]{RMNET0, WLAN0, RMNET0}) { upstreamLp.setInterfaceName(ifname); offload.setUpstreamLinkProperties(upstreamLp); } // Clear invocation history, especially the getForwardedStats() calls // that happen with setUpstreamParameters(). clearInvocations(mHardware); OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue(); callback.onStoppedUnsupported(); // Verify forwarded stats behaviour. verify(mHardware, times(1)).getForwardedStats(eq(RMNET0)); verify(mHardware, times(1)).getForwardedStats(eq(WLAN0)); verifyNoMoreInteractions(mHardware); verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue()); verifyNoMoreInteractions(mNMService); } @Test public void testControlCallbackOnSupportAvailableFetchesAllStatsAndPushesAllParameters() throws Exception { setupFunctioningHardwareInterface(); enableOffload(); final OffloadController offload = makeOffloadController(); offload.start(); // Pretend to set a few different upstreams (only the interface name // matters for this test; we're ignoring IP and route information). final LinkProperties upstreamLp = new LinkProperties(); for (String ifname : new String[]{RMNET0, WLAN0, RMNET0}) { upstreamLp.setInterfaceName(ifname); offload.setUpstreamLinkProperties(upstreamLp); } // Clear invocation history, especially the getForwardedStats() calls // that happen with setUpstreamParameters(). clearInvocations(mHardware); OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue(); callback.onSupportAvailable(); // Verify forwarded stats behaviour. verify(mHardware, times(1)).getForwardedStats(eq(RMNET0)); verify(mHardware, times(1)).getForwardedStats(eq(WLAN0)); verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue()); verifyNoMoreInteractions(mNMService); // TODO: verify local prefixes and downstreams are also pushed to the HAL. verify(mHardware, times(1)).setUpstreamParameters(eq(RMNET0), any(), any(), any()); verify(mHardware, times(1)).setDataLimit(eq(RMNET0), anyLong()); verifyNoMoreInteractions(mHardware); } private static void assertArrayListContains(ArrayList<String> list, String... elems) { for (String element : elems) { assertTrue(list.contains(element)); Loading