From 20c7cad598feef06102b36c132a29e609478719c Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Fri, 14 May 2021 17:31:30 +0900 Subject: [PATCH 1/4] Address comments on ag/14486203 Bug: 186458024 Test: comment-only change Merged-In: I3c2563d4ae4e3715d0c6270344ba8f7ef067872f Merged-In: I7b086abbb57354086e8fb1a41c63140f2227173f Change-Id: I7b086abbb57354086e8fb1a41c63140f2227173f (cherry-picked from ag/14540395) --- .../com/android/server/connectivity/NetworkRanker.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/service/src/com/android/server/connectivity/NetworkRanker.java b/service/src/com/android/server/connectivity/NetworkRanker.java index a4085cde4e..d7eb9c8a59 100644 --- a/service/src/com/android/server/connectivity/NetworkRanker.java +++ b/service/src/com/android/server/connectivity/NetworkRanker.java @@ -237,9 +237,9 @@ public class NetworkRanker { partitionInto(candidates, nai -> nai.getScore().hasPolicy(POLICY_TRANSPORT_PRIMARY), accepted, rejected); if (accepted.size() > 0) { - // Some networks are primary. For each transport, keep only the primary, but also - // keep all networks for which there isn't a primary (which are now in the |rejected| - // array). + // Some networks are primary for their transport. For each transport, keep only the + // primary, but also keep all networks for which there isn't a primary (which are now + // in the |rejected| array). // So for each primary network, remove from |rejected| all networks with the same // transports as one of the primary networks. The remaining networks should be accepted. for (final T defaultSubNai : accepted) { @@ -247,6 +247,8 @@ public class NetworkRanker { rejected.removeIf( nai -> Arrays.equals(transports, nai.getCapsNoCopy().getTransportTypes())); } + // Now the |rejected| list contains networks with transports for which there isn't + // a primary network. Add them back to the candidates. accepted.addAll(rejected); candidates = new ArrayList<>(accepted); } From 37a7e620b43be400db713ede554746b371bcf691 Mon Sep 17 00:00:00 2001 From: junyulai Date: Wed, 10 Mar 2021 17:00:29 +0800 Subject: [PATCH 2/4] [SP33] Adapt ITetheringOffloadCallback V1.1 This is a no-op change that redirect both V1.0 and V1.1 callback events to the same handling function. Since the V1.1 callback is extended from V1.0 callback, we can safely use V1.1 callback for both V1.0 and V1.1 control. The change also provides interface for subsequent OffloadController changes to set warning and limit at the same time. Test: atest TetheringTests Bug: 149467454 Merged-In: I6505a04de8c57357dd1fa9ce898c13395e497816 Change-Id: I6505a04de8c57357dd1fa9ce898c13395e497816 (cherry-picked from ag/13973147) --- .../tethering/OffloadHardwareInterface.java | 104 ++++++++++++++---- .../OffloadHardwareInterfaceTest.java | 84 +++++++++++--- 2 files changed, 150 insertions(+), 38 deletions(-) diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java index 7685847680..e3ac660910 100644 --- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java +++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java @@ -24,10 +24,10 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.hardware.tetheroffload.config.V1_0.IOffloadConfig; import android.hardware.tetheroffload.control.V1_0.IOffloadControl; -import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback; import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate; import android.hardware.tetheroffload.control.V1_0.NetworkProtocol; import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent; +import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback; import android.net.netlink.NetlinkSocket; import android.net.netlink.StructNfGenMsg; import android.net.netlink.StructNlMsgHdr; @@ -39,6 +39,7 @@ import android.os.RemoteException; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; +import android.util.Log; import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; @@ -140,6 +141,8 @@ public class OffloadHardwareInterface { public void onSupportAvailable() {} /** Offload stopped because of usage limit reached. */ public void onStoppedLimitReached() {} + /** Indicate that data warning quota is reached. */ + public void onWarningReached() {} /** Indicate to update NAT timeout. */ public void onNatTimeoutUpdate(int proto, @@ -381,7 +384,8 @@ public class OffloadHardwareInterface { (controlCb == null) ? "null" : "0x" + Integer.toHexString(System.identityHashCode(controlCb))); - mTetheringOffloadCallback = new TetheringOffloadCallback(mHandler, mControlCallback, mLog); + mTetheringOffloadCallback = new TetheringOffloadCallback( + mHandler, mControlCallback, mLog, mOffloadControlVersion); final CbResults results = new CbResults(); try { mOffloadControl.initOffload( @@ -480,6 +484,33 @@ public class OffloadHardwareInterface { return results.mSuccess; } + /** Set data warning and limit value to offload management process. */ + public boolean setDataWarningAndLimit(String iface, long warning, long limit) { + if (mOffloadControlVersion < OFFLOAD_HAL_VERSION_1_1) { + throw new IllegalArgumentException( + "setDataWarningAndLimit is not supported below HAL V1.1"); + } + final String logmsg = + String.format("setDataWarningAndLimit(%s, %d, %d)", iface, warning, limit); + + final CbResults results = new CbResults(); + try { + ((android.hardware.tetheroffload.control.V1_1.IOffloadControl) mOffloadControl) + .setDataWarningAndLimit( + iface, warning, limit, + (boolean success, String errMsg) -> { + results.mSuccess = success; + results.mErrMsg = errMsg; + }); + } catch (RemoteException e) { + record(logmsg, e); + return false; + } + + record(logmsg, results); + return results.mSuccess; + } + /** Set upstream parameters to offload management process. */ public boolean setUpstreamParameters( String iface, String v4addr, String v4gateway, ArrayList v6gws) { @@ -565,35 +596,64 @@ public class OffloadHardwareInterface { public final Handler handler; public final ControlCallback controlCb; public final SharedLog log; + private final int mOffloadControlVersion; - TetheringOffloadCallback(Handler h, ControlCallback cb, SharedLog sharedLog) { + TetheringOffloadCallback( + Handler h, ControlCallback cb, SharedLog sharedLog, int offloadControlVersion) { handler = h; controlCb = cb; log = sharedLog; + this.mOffloadControlVersion = offloadControlVersion; + } + + private void handleOnEvent(int event) { + switch (event) { + case OffloadCallbackEvent.OFFLOAD_STARTED: + controlCb.onStarted(); + break; + case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR: + controlCb.onStoppedError(); + break; + case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED: + controlCb.onStoppedUnsupported(); + break; + case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE: + controlCb.onSupportAvailable(); + break; + case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED: + controlCb.onStoppedLimitReached(); + break; + case android.hardware.tetheroffload.control + .V1_1.OffloadCallbackEvent.OFFLOAD_WARNING_REACHED: + controlCb.onWarningReached(); + break; + default: + log.e("Unsupported OffloadCallbackEvent: " + event); + } } @Override public void onEvent(int event) { + // The implementation should never call onEvent()) if the event is already reported + // through newer callback. + if (mOffloadControlVersion > OFFLOAD_HAL_VERSION_1_0) { + Log.wtf(TAG, "onEvent(" + event + ") fired on HAL " + + halVerToString(mOffloadControlVersion)); + } handler.post(() -> { - switch (event) { - case OffloadCallbackEvent.OFFLOAD_STARTED: - controlCb.onStarted(); - break; - case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR: - controlCb.onStoppedError(); - break; - case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED: - controlCb.onStoppedUnsupported(); - break; - case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE: - controlCb.onSupportAvailable(); - break; - case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED: - controlCb.onStoppedLimitReached(); - break; - default: - log.e("Unsupported OffloadCallbackEvent: " + event); - } + handleOnEvent(event); + }); + } + + @Override + public void onEvent_1_1(int event) { + if (mOffloadControlVersion < OFFLOAD_HAL_VERSION_1_1) { + Log.wtf(TAG, "onEvent_1_1(" + event + ") fired on HAL " + + halVerToString(mOffloadControlVersion)); + return; + } + handler.post(() -> { + handleOnEvent(event); }); } diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java index f4194e5d77..a8b3b92de9 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java @@ -22,22 +22,27 @@ import static android.system.OsConstants.AF_UNIX; import static android.system.OsConstants.SOCK_STREAM; import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0; +import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_1; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.hardware.tetheroffload.config.V1_0.IOffloadConfig; import android.hardware.tetheroffload.control.V1_0.IOffloadControl; -import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback; import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate; import android.hardware.tetheroffload.control.V1_0.NetworkProtocol; -import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent; +import android.hardware.tetheroffload.control.V1_1.ITetheringOffloadCallback; +import android.hardware.tetheroffload.control.V1_1.OffloadCallbackEvent; import android.net.netlink.StructNfGenMsg; import android.net.netlink.StructNlMsgHdr; import android.net.util.SharedLog; @@ -56,6 +61,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -76,7 +82,7 @@ public final class OffloadHardwareInterfaceTest { private OffloadHardwareInterface.ControlCallback mControlCallback; @Mock private IOffloadConfig mIOffloadConfig; - @Mock private IOffloadControl mIOffloadControl; + private IOffloadControl mIOffloadControl; @Mock private NativeHandle mNativeHandle; // Random values to test Netlink message. @@ -84,8 +90,10 @@ public final class OffloadHardwareInterfaceTest { private static final short TEST_FLAGS = 263; class MyDependencies extends OffloadHardwareInterface.Dependencies { - MyDependencies(SharedLog log) { + private final int mMockControlVersion; + MyDependencies(SharedLog log, final int mockControlVersion) { super(log); + mMockControlVersion = mockControlVersion; } @Override @@ -95,7 +103,19 @@ public final class OffloadHardwareInterfaceTest { @Override public Pair getOffloadControl() { - return new Pair(mIOffloadControl, OFFLOAD_HAL_VERSION_1_0); + switch (mMockControlVersion) { + case OFFLOAD_HAL_VERSION_1_0: + mIOffloadControl = mock(IOffloadControl.class); + break; + case OFFLOAD_HAL_VERSION_1_1: + mIOffloadControl = + mock(android.hardware.tetheroffload.control.V1_1.IOffloadControl.class); + break; + default: + throw new IllegalArgumentException("Invalid offload control version " + + mMockControlVersion); + } + return new Pair(mIOffloadControl, mMockControlVersion); } @Override @@ -107,14 +127,13 @@ public final class OffloadHardwareInterfaceTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - final SharedLog log = new SharedLog("test"); - mOffloadHw = new OffloadHardwareInterface(new Handler(mTestLooper.getLooper()), log, - new MyDependencies(log)); mControlCallback = spy(new OffloadHardwareInterface.ControlCallback()); } - // TODO: Pass version to test version specific operations. - private void startOffloadHardwareInterface() throws Exception { + private void startOffloadHardwareInterface(int controlVersion) throws Exception { + final SharedLog log = new SharedLog("test"); + mOffloadHw = new OffloadHardwareInterface(new Handler(mTestLooper.getLooper()), log, + new MyDependencies(log, controlVersion)); mOffloadHw.initOffloadConfig(); mOffloadHw.initOffloadControl(mControlCallback); final ArgumentCaptor mOffloadCallbackCaptor = @@ -125,7 +144,7 @@ public final class OffloadHardwareInterfaceTest { @Test public void testGetForwardedStats() throws Exception { - startOffloadHardwareInterface(); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0); final OffloadHardwareInterface.ForwardedStats stats = mOffloadHw.getForwardedStats(RMNET0); verify(mIOffloadControl).getForwardedStats(eq(RMNET0), any()); assertNotNull(stats); @@ -133,7 +152,7 @@ public final class OffloadHardwareInterfaceTest { @Test public void testSetLocalPrefixes() throws Exception { - startOffloadHardwareInterface(); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0); final ArrayList localPrefixes = new ArrayList<>(); localPrefixes.add("127.0.0.0/8"); localPrefixes.add("fe80::/64"); @@ -143,15 +162,32 @@ public final class OffloadHardwareInterfaceTest { @Test public void testSetDataLimit() throws Exception { - startOffloadHardwareInterface(); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0); final long limit = 12345; mOffloadHw.setDataLimit(RMNET0, limit); verify(mIOffloadControl).setDataLimit(eq(RMNET0), eq(limit), any()); } + @Test + public void testSetDataWarningAndLimit() throws Exception { + // Verify V1.0 control HAL would reject the function call with exception. + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0); + final long warning = 12345; + final long limit = 67890; + assertThrows(IllegalArgumentException.class, + () -> mOffloadHw.setDataWarningAndLimit(RMNET0, warning, limit)); + reset(mIOffloadControl); + + // Verify V1.1 control HAL could receive this function call. + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_1); + mOffloadHw.setDataWarningAndLimit(RMNET0, warning, limit); + verify((android.hardware.tetheroffload.control.V1_1.IOffloadControl) mIOffloadControl) + .setDataWarningAndLimit(eq(RMNET0), eq(warning), eq(limit), any()); + } + @Test public void testSetUpstreamParameters() throws Exception { - startOffloadHardwareInterface(); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0); final String v4addr = "192.168.10.1"; final String v4gateway = "192.168.10.255"; final ArrayList v6gws = new ArrayList<>(0); @@ -170,7 +206,7 @@ public final class OffloadHardwareInterfaceTest { @Test public void testUpdateDownstreamPrefix() throws Exception { - startOffloadHardwareInterface(); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0); final String ifName = "wlan1"; final String prefix = "192.168.43.0/24"; mOffloadHw.addDownstreamPrefix(ifName, prefix); @@ -182,7 +218,7 @@ public final class OffloadHardwareInterfaceTest { @Test public void testTetheringOffloadCallback() throws Exception { - startOffloadHardwareInterface(); + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0); mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STARTED); mTestLooper.dispatchAll(); @@ -221,10 +257,26 @@ public final class OffloadHardwareInterfaceTest { eq(uint16(udpParams.src.port)), eq(udpParams.dst.addr), eq(uint16(udpParams.dst.port))); + reset(mControlCallback); + + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_1); + + // Verify the interface will process the events that comes from V1.1 HAL. + mTetheringOffloadCallback.onEvent_1_1(OffloadCallbackEvent.OFFLOAD_STARTED); + mTestLooper.dispatchAll(); + final InOrder inOrder = inOrder(mControlCallback); + inOrder.verify(mControlCallback).onStarted(); + inOrder.verifyNoMoreInteractions(); + + mTetheringOffloadCallback.onEvent_1_1(OffloadCallbackEvent.OFFLOAD_WARNING_REACHED); + mTestLooper.dispatchAll(); + inOrder.verify(mControlCallback).onWarningReached(); + inOrder.verifyNoMoreInteractions(); } @Test public void testSendIpv4NfGenMsg() throws Exception { + startOffloadHardwareInterface(OFFLOAD_HAL_VERSION_1_0); FileDescriptor writeSocket = new FileDescriptor(); FileDescriptor readSocket = new FileDescriptor(); try { From da52dab266a7e0e47d322e065c8433928d40bd85 Mon Sep 17 00:00:00 2001 From: junyulai Date: Wed, 10 Mar 2021 19:36:43 +0800 Subject: [PATCH 3/4] [SP34] Adapt onSetWarningAndLimit This is a no-op change that just adapt new API from NetworkStatsProvider to get warning and limit bytes at the same time. This change also stores them locally for subsequent patches to set warning bytes to hardware. Test: Will be included in the subsequent patch. Bug: 149467454 Merged-In: Iec01cb01fd1ce481ce0bd736762baddde1e38084 Change-Id: Iec01cb01fd1ce481ce0bd736762baddde1e38084 (cherry-picked from ag/13981691) --- .../tethering/OffloadController.java | 72 ++++++++++++++----- .../tethering/OffloadControllerTest.java | 11 +-- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadController.java b/Tethering/src/com/android/networkstack/tethering/OffloadController.java index 44e3916bdf..c612271ee7 100644 --- a/Tethering/src/com/android/networkstack/tethering/OffloadController.java +++ b/Tethering/src/com/android/networkstack/tethering/OffloadController.java @@ -114,11 +114,42 @@ public class OffloadController { private ConcurrentHashMap mForwardedStats = new ConcurrentHashMap<>(16, 0.75F, 1); + private static class InterfaceQuota { + public final long warningBytes; + public final long limitBytes; + + public static InterfaceQuota MAX_VALUE = new InterfaceQuota(Long.MAX_VALUE, Long.MAX_VALUE); + + InterfaceQuota(long warningBytes, long limitBytes) { + this.warningBytes = warningBytes; + this.limitBytes = limitBytes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof InterfaceQuota)) return false; + InterfaceQuota that = (InterfaceQuota) o; + return warningBytes == that.warningBytes + && limitBytes == that.limitBytes; + } + + @Override + public int hashCode() { + return (int) (warningBytes * 3 + limitBytes * 5); + } + + @Override + public String toString() { + return "InterfaceQuota{" + "warning=" + warningBytes + ", limit=" + limitBytes + '}'; + } + } + // 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 mInterfaceQuotas = new HashMap<>(); + private HashMap mInterfaceQuotas = new HashMap<>(); // Tracking remaining alert quota. Unlike limit quota is subject to interface, the alert // quota is interface independent and global for tether offload. Note that this is only @@ -263,7 +294,8 @@ public class OffloadController { mLog.i("tethering offload control not supported"); stop(); } else { - mLog.log("tethering offload started"); + mLog.log("tethering offload started, version: " + + OffloadHardwareInterface.halVerToString(mControlHalVersion)); mNatUpdateCallbacksReceived = 0; mNatUpdateNetlinkErrors = 0; maybeSchedulePollingStats(); @@ -322,24 +354,35 @@ public class OffloadController { @Override public void onSetLimit(String iface, long quotaBytes) { + onSetWarningAndLimit(iface, QUOTA_UNLIMITED, quotaBytes); + } + + @Override + public void onSetWarningAndLimit(@NonNull String iface, + long warningBytes, long limitBytes) { // Listen for all iface is necessary since upstream might be changed after limit // is set. mHandler.post(() -> { - final Long curIfaceQuota = mInterfaceQuotas.get(iface); + final InterfaceQuota curIfaceQuota = mInterfaceQuotas.get(iface); + final InterfaceQuota newIfaceQuota = new InterfaceQuota( + warningBytes == QUOTA_UNLIMITED ? Long.MAX_VALUE : warningBytes, + limitBytes == QUOTA_UNLIMITED ? Long.MAX_VALUE : limitBytes); // If the quota is set to unlimited, the value set to HAL is Long.MAX_VALUE, // which is ~8.4 x 10^6 TiB, no one can actually reach it. Thus, it is not // useful to set it multiple times. // Otherwise, the quota needs to be updated to tell HAL to re-count from now even // if the quota is the same as the existing one. - if (null == curIfaceQuota && QUOTA_UNLIMITED == quotaBytes) return; + if (null == curIfaceQuota && InterfaceQuota.MAX_VALUE.equals(newIfaceQuota)) { + return; + } - if (quotaBytes == QUOTA_UNLIMITED) { + if (InterfaceQuota.MAX_VALUE.equals(newIfaceQuota)) { mInterfaceQuotas.remove(iface); } else { - mInterfaceQuotas.put(iface, quotaBytes); + mInterfaceQuotas.put(iface, newIfaceQuota); } - maybeUpdateDataLimit(iface); + maybeUpdateDataWarningAndLimit(iface); }); } @@ -465,18 +508,15 @@ public class OffloadController { >= DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; } - private boolean maybeUpdateDataLimit(String iface) { - // setDataLimit may only be called while offload is occurring on this upstream. + private boolean maybeUpdateDataWarningAndLimit(String iface) { + // setDataLimit or setDataWarningAndLimit may only be called while offload is occurring + // on this upstream. if (!started() || !TextUtils.equals(iface, currentUpstreamInterface())) { return true; } - Long limit = mInterfaceQuotas.get(iface); - if (limit == null) { - limit = Long.MAX_VALUE; - } - - return mHwInterface.setDataLimit(iface, limit); + final InterfaceQuota quota = mInterfaceQuotas.getOrDefault(iface, InterfaceQuota.MAX_VALUE); + return mHwInterface.setDataLimit(iface, quota.limitBytes); } private void updateStatsForCurrentUpstream() { @@ -630,7 +670,7 @@ public class OffloadController { maybeUpdateStats(prevUpstream); // Data limits can only be set once offload is running on the upstream. - success = maybeUpdateDataLimit(iface); + success = maybeUpdateDataWarningAndLimit(iface); if (!success) { // If we failed to set a data limit, don't use this upstream, because we don't want to // blow through the data limit that we were told to apply. diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java index 88f205445e..14c34f02b4 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java @@ -58,7 +58,6 @@ import android.annotation.NonNull; import android.app.usage.NetworkStatsManager; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.net.ITetheringStatsProvider; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; @@ -503,8 +502,12 @@ public class OffloadControllerTest { expectedUidStatsDiff); } + /** + * Test OffloadController that uses V1.0 HAL setDataLimit with NetworkStatsProvider#onSetLimit + * which is called by R framework. + */ @Test - public void testSetInterfaceQuota() throws Exception { + public void testSetDataLimit() throws Exception { enableOffload(); final OffloadController offload = startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/); @@ -540,9 +543,9 @@ public class OffloadControllerTest { waitForIdle(); inOrder.verify(mHardware).setDataLimit(mobileIface, mobileLimit); - // Setting a limit of ITetheringStatsProvider.QUOTA_UNLIMITED causes the limit to be set + // Setting a limit of NetworkStatsProvider.QUOTA_UNLIMITED causes the limit to be set // to Long.MAX_VALUE. - mTetherStatsProvider.onSetLimit(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED); + mTetherStatsProvider.onSetLimit(mobileIface, NetworkStatsProvider.QUOTA_UNLIMITED); waitForIdle(); inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE); From 991433d8c8e97c806c522167e524d426d1468c67 Mon Sep 17 00:00:00 2001 From: junyulai Date: Wed, 10 Mar 2021 20:22:30 +0800 Subject: [PATCH 4/4] [SP35] Pass data warning to tethering offload This is supported by: 1. Utilize the new API from both NetworkStatsProvider and IOffloadControl to send data warning quota to hardware. And pass the warning reached notification back to NPMS. 2. Disable software solution introduced in R release for V1.1+ hardware, since now we can fully offload data warning and limit notification to hardware. Test: atest TetheringTests Fix: 149467454 Merged-In: Ie49461694d77ab7f25a549433b01b5b0167bd489 Change-Id: Ie49461694d77ab7f25a549433b01b5b0167bd489 (cherry-picked from ag/13981692) --- .../tethering/OffloadController.java | 33 +++- .../tethering/OffloadControllerTest.java | 160 +++++++++++++++--- 2 files changed, 168 insertions(+), 25 deletions(-) diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadController.java b/Tethering/src/com/android/networkstack/tethering/OffloadController.java index c612271ee7..beb18219b7 100644 --- a/Tethering/src/com/android/networkstack/tethering/OffloadController.java +++ b/Tethering/src/com/android/networkstack/tethering/OffloadController.java @@ -26,6 +26,8 @@ import static android.net.NetworkStats.UID_TETHERING; import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED; +import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0; +import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_1; import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_NONE; import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; @@ -280,6 +282,18 @@ public class OffloadController { } } + @Override + public void onWarningReached() { + if (!started()) return; + mLog.log("onWarningReached"); + + updateStatsForCurrentUpstream(); + if (mStatsProvider != null) { + mStatsProvider.pushTetherStats(); + mStatsProvider.notifyWarningReached(); + } + } + @Override public void onNatTimeoutUpdate(int proto, String srcAddr, int srcPort, @@ -417,7 +431,11 @@ public class OffloadController { @Override public void onSetAlert(long quotaBytes) { - // TODO: Ask offload HAL to notify alert without stopping traffic. + // Ignore set alert calls from HAL V1.1 since the hardware supports set warning now. + // Thus, the software polling mechanism is not needed. + if (!useStatsPolling()) { + return; + } // Post it to handler thread since it access remaining quota bytes. mHandler.post(() -> { updateAlertQuota(quotaBytes); @@ -502,12 +520,17 @@ public class OffloadController { private boolean isPollingStatsNeeded() { return started() && mRemainingAlertQuota > 0 + && useStatsPolling() && !TextUtils.isEmpty(currentUpstreamInterface()) && mDeps.getTetherConfig() != null && mDeps.getTetherConfig().getOffloadPollInterval() >= DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; } + private boolean useStatsPolling() { + return mControlHalVersion == OFFLOAD_HAL_VERSION_1_0; + } + private boolean maybeUpdateDataWarningAndLimit(String iface) { // setDataLimit or setDataWarningAndLimit may only be called while offload is occurring // on this upstream. @@ -516,7 +539,13 @@ public class OffloadController { } final InterfaceQuota quota = mInterfaceQuotas.getOrDefault(iface, InterfaceQuota.MAX_VALUE); - return mHwInterface.setDataLimit(iface, quota.limitBytes); + final boolean ret; + if (mControlHalVersion >= OFFLOAD_HAL_VERSION_1_1) { + ret = mHwInterface.setDataWarningAndLimit(iface, quota.warningBytes, quota.limitBytes); + } else { + ret = mHwInterface.setDataLimit(iface, quota.limitBytes); + } + return ret; } private void updateStatsForCurrentUpstream() { diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java index 14c34f02b4..d800816055 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java @@ -149,6 +149,7 @@ public class OffloadControllerTest { when(mHardware.setUpstreamParameters(anyString(), any(), any(), any())).thenReturn(true); when(mHardware.getForwardedStats(any())).thenReturn(new ForwardedStats()); when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true); + when(mHardware.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(true); } private void enableOffload() { @@ -503,80 +504,166 @@ public class OffloadControllerTest { } /** - * Test OffloadController that uses V1.0 HAL setDataLimit with NetworkStatsProvider#onSetLimit - * which is called by R framework. + * Test OffloadController with different combinations of HAL and framework versions can set + * data warning and/or limit correctly. */ @Test - public void testSetDataLimit() throws Exception { + public void testSetDataWarningAndLimit() throws Exception { + // Verify the OffloadController is called by R framework, where the framework doesn't send + // warning. + checkSetDataWarningAndLimit(false, OFFLOAD_HAL_VERSION_1_0); + checkSetDataWarningAndLimit(false, OFFLOAD_HAL_VERSION_1_1); + // Verify the OffloadController is called by S+ framework, where the framework sends + // warning along with limit. + checkSetDataWarningAndLimit(true, OFFLOAD_HAL_VERSION_1_0); + checkSetDataWarningAndLimit(true, OFFLOAD_HAL_VERSION_1_1); + } + + private void checkSetDataWarningAndLimit(boolean isProviderSetWarning, int controlVersion) + throws Exception { enableOffload(); final OffloadController offload = - startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/); + startOffloadController(controlVersion, true /*expectStart*/); final String ethernetIface = "eth1"; final String mobileIface = "rmnet_data0"; final long ethernetLimit = 12345; + final long mobileWarning = 123456; final long mobileLimit = 12345678; final LinkProperties lp = new LinkProperties(); lp.setInterfaceName(ethernetIface); - offload.setUpstreamLinkProperties(lp); final InOrder inOrder = inOrder(mHardware); - when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true); + when(mHardware.setUpstreamParameters( + any(), any(), any(), any())).thenReturn(true); when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true); + when(mHardware.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(true); + offload.setUpstreamLinkProperties(lp); + // Applying an interface sends the initial quota to the hardware. + if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) { + inOrder.verify(mHardware).setDataWarningAndLimit(ethernetIface, Long.MAX_VALUE, + Long.MAX_VALUE); + } else { + inOrder.verify(mHardware).setDataLimit(ethernetIface, Long.MAX_VALUE); + } + inOrder.verifyNoMoreInteractions(); + + // Verify that set to unlimited again won't cause duplicated calls to the hardware. + if (isProviderSetWarning) { + mTetherStatsProvider.onSetWarningAndLimit(ethernetIface, + NetworkStatsProvider.QUOTA_UNLIMITED, NetworkStatsProvider.QUOTA_UNLIMITED); + } else { + mTetherStatsProvider.onSetLimit(ethernetIface, NetworkStatsProvider.QUOTA_UNLIMITED); + } + waitForIdle(); + inOrder.verifyNoMoreInteractions(); // Applying an interface quota to the current upstream immediately sends it to the hardware. - mTetherStatsProvider.onSetLimit(ethernetIface, ethernetLimit); + if (isProviderSetWarning) { + mTetherStatsProvider.onSetWarningAndLimit(ethernetIface, + NetworkStatsProvider.QUOTA_UNLIMITED, ethernetLimit); + } else { + mTetherStatsProvider.onSetLimit(ethernetIface, ethernetLimit); + } waitForIdle(); - inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit); + if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) { + inOrder.verify(mHardware).setDataWarningAndLimit(ethernetIface, Long.MAX_VALUE, + ethernetLimit); + } else { + inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit); + } inOrder.verifyNoMoreInteractions(); // Applying an interface quota to another upstream does not take any immediate action. - mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit); + if (isProviderSetWarning) { + mTetherStatsProvider.onSetWarningAndLimit(mobileIface, mobileWarning, mobileLimit); + } else { + mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit); + } waitForIdle(); - inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong()); + if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) { + inOrder.verify(mHardware, never()).setDataWarningAndLimit(anyString(), anyLong(), + anyLong()); + } else { + 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); + if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) { + inOrder.verify(mHardware).setDataWarningAndLimit(mobileIface, + isProviderSetWarning ? mobileWarning : Long.MAX_VALUE, + mobileLimit); + } else { + inOrder.verify(mHardware).setDataLimit(mobileIface, mobileLimit); + } // Setting a limit of NetworkStatsProvider.QUOTA_UNLIMITED causes the limit to be set // to Long.MAX_VALUE. - mTetherStatsProvider.onSetLimit(mobileIface, NetworkStatsProvider.QUOTA_UNLIMITED); + if (isProviderSetWarning) { + mTetherStatsProvider.onSetWarningAndLimit(mobileIface, + NetworkStatsProvider.QUOTA_UNLIMITED, NetworkStatsProvider.QUOTA_UNLIMITED); + } else { + mTetherStatsProvider.onSetLimit(mobileIface, NetworkStatsProvider.QUOTA_UNLIMITED); + } waitForIdle(); - inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE); + if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) { + inOrder.verify(mHardware).setDataWarningAndLimit(mobileIface, Long.MAX_VALUE, + Long.MAX_VALUE); + } else { + inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE); + } - // If setting upstream parameters fails, then the data limit is not set. + // If setting upstream parameters fails, then the data warning and limit is not set. when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(false); lp.setInterfaceName(ethernetIface); offload.setUpstreamLinkProperties(lp); - mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit); + if (isProviderSetWarning) { + mTetherStatsProvider.onSetWarningAndLimit(mobileIface, mobileWarning, mobileLimit); + } else { + mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit); + } waitForIdle(); inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong()); + inOrder.verify(mHardware, never()).setDataWarningAndLimit(anyString(), anyLong(), + anyLong()); - // If setting the data limit fails while changing upstreams, offload is stopped. + // If setting the data warning and/or limit fails while changing upstreams, offload is + // stopped. when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true); when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(false); + when(mHardware.setDataWarningAndLimit(anyString(), anyLong(), anyLong())).thenReturn(false); lp.setInterfaceName(mobileIface); offload.setUpstreamLinkProperties(lp); - mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit); + if (isProviderSetWarning) { + mTetherStatsProvider.onSetWarningAndLimit(mobileIface, mobileWarning, mobileLimit); + } else { + mTetherStatsProvider.onSetLimit(mobileIface, mobileLimit); + } waitForIdle(); inOrder.verify(mHardware).getForwardedStats(ethernetIface); inOrder.verify(mHardware).stopOffloadControl(); } @Test - public void testDataLimitCallback() throws Exception { + public void testDataWarningAndLimitCallback() throws Exception { enableOffload(); - final OffloadController offload = - startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/); + startOffloadController(OFFLOAD_HAL_VERSION_1_0, true /*expectStart*/); OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue(); callback.onStoppedLimitReached(); mTetherStatsProviderCb.expectNotifyStatsUpdated(); + mTetherStatsProviderCb.expectNotifyWarningOrLimitReached(); + + startOffloadController(OFFLOAD_HAL_VERSION_1_1, true /*expectStart*/); + callback = mControlCallbackCaptor.getValue(); + callback.onWarningReached(); + mTetherStatsProviderCb.expectNotifyStatsUpdated(); + mTetherStatsProviderCb.expectNotifyWarningOrLimitReached(); } @Test @@ -764,9 +851,7 @@ public class OffloadControllerTest { // Initialize with fake eth upstream. final String ethernetIface = "eth1"; InOrder inOrder = inOrder(mHardware); - final LinkProperties lp = new LinkProperties(); - lp.setInterfaceName(ethernetIface); - offload.setUpstreamLinkProperties(lp); + offload.setUpstreamLinkProperties(makeEthernetLinkProperties()); // Previous upstream was null, so no stats are fetched. inOrder.verify(mHardware, never()).getForwardedStats(any()); @@ -799,4 +884,33 @@ public class OffloadControllerTest { mTetherStatsProviderCb.assertNoCallback(); verify(mHardware, never()).getForwardedStats(any()); } + + private static LinkProperties makeEthernetLinkProperties() { + final String ethernetIface = "eth1"; + final LinkProperties lp = new LinkProperties(); + lp.setInterfaceName(ethernetIface); + return lp; + } + + private void checkSoftwarePollingUsed(int controlVersion) throws Exception { + enableOffload(); + setOffloadPollInterval(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS); + OffloadController offload = + startOffloadController(controlVersion, true /*expectStart*/); + offload.setUpstreamLinkProperties(makeEthernetLinkProperties()); + mTetherStatsProvider.onSetAlert(0); + waitForIdle(); + if (controlVersion >= OFFLOAD_HAL_VERSION_1_1) { + mTetherStatsProviderCb.assertNoCallback(); + } else { + mTetherStatsProviderCb.expectNotifyAlertReached(); + } + verify(mHardware, never()).getForwardedStats(any()); + } + + @Test + public void testSoftwarePollingUsed() throws Exception { + checkSoftwarePollingUsed(OFFLOAD_HAL_VERSION_1_0); + checkSoftwarePollingUsed(OFFLOAD_HAL_VERSION_1_1); + } }