diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadController.java b/Tethering/src/com/android/networkstack/tethering/OffloadController.java index 44e3916bdf..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; @@ -114,11 +116,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 @@ -249,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, @@ -263,7 +308,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 +368,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); }); } @@ -374,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); @@ -459,24 +520,32 @@ 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 maybeUpdateDataLimit(String iface) { - // setDataLimit may only be called while offload is occurring on this upstream. + 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. if (!started() || !TextUtils.equals(iface, currentUpstreamInterface())) { return true; } - Long limit = mInterfaceQuotas.get(iface); - if (limit == null) { - limit = Long.MAX_VALUE; + final InterfaceQuota quota = mInterfaceQuotas.getOrDefault(iface, InterfaceQuota.MAX_VALUE); + 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 mHwInterface.setDataLimit(iface, limit); + return ret; } private void updateStatsForCurrentUpstream() { @@ -630,7 +699,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/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/OffloadControllerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java index 88f205445e..d800816055 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; @@ -150,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,77 +503,167 @@ public class OffloadControllerTest { expectedUidStatsDiff); } + /** + * Test OffloadController with different combinations of HAL and framework versions can set + * data warning and/or limit correctly. + */ @Test - public void testSetInterfaceQuota() 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 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); + 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 @@ -761,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()); @@ -796,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); + } } 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 { 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); }