diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java index 293f8eae32..fe92204c25 100644 --- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java +++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java @@ -66,6 +66,7 @@ public class OffloadHardwareInterface { private final Handler mHandler; private final SharedLog mLog; + private final Dependencies mDeps; private IOffloadControl mOffloadControl; private TetheringOffloadCallback mTetheringOffloadCallback; private ControlCallback mControlCallback; @@ -126,8 +127,76 @@ public class OffloadHardwareInterface { } public OffloadHardwareInterface(Handler h, SharedLog log) { + this(h, log, new Dependencies(log)); + } + + OffloadHardwareInterface(Handler h, SharedLog log, Dependencies deps) { mHandler = h; mLog = log.forSubComponent(TAG); + mDeps = deps; + } + + /** Capture OffloadHardwareInterface dependencies, for injection. */ + static class Dependencies { + private final SharedLog mLog; + + Dependencies(SharedLog log) { + mLog = log; + } + + public IOffloadConfig getOffloadConfig() { + try { + return IOffloadConfig.getService(true /*retry*/); + } catch (RemoteException | NoSuchElementException e) { + mLog.e("getIOffloadConfig error " + e); + return null; + } + } + + public IOffloadControl getOffloadControl() { + try { + return IOffloadControl.getService(true /*retry*/); + } catch (RemoteException | NoSuchElementException e) { + mLog.e("tethering offload control not supported: " + e); + return null; + } + } + + public NativeHandle createConntrackSocket(final int groups) { + final FileDescriptor fd; + try { + fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER); + } catch (ErrnoException e) { + mLog.e("Unable to create conntrack socket " + e); + return null; + } + + final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups); + try { + Os.bind(fd, sockAddr); + } catch (ErrnoException | SocketException e) { + mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e); + try { + SocketUtils.closeSocket(fd); + } catch (IOException ie) { + // Nothing we can do here + } + return null; + } + try { + Os.connect(fd, sockAddr); + } catch (ErrnoException | SocketException e) { + mLog.e("connect to kernel fail for groups " + groups + " error: " + e); + try { + SocketUtils.closeSocket(fd); + } catch (IOException ie) { + // Nothing we can do here + } + return null; + } + + return new NativeHandle(fd, true); + } } /** Get default value indicating whether offload is supported. */ @@ -141,13 +210,7 @@ public class OffloadHardwareInterface { * share them with offload management process. */ public boolean initOffloadConfig() { - IOffloadConfig offloadConfig; - try { - offloadConfig = IOffloadConfig.getService(true /*retry*/); - } catch (RemoteException | NoSuchElementException e) { - mLog.e("getIOffloadConfig error " + e); - return false; - } + final IOffloadConfig offloadConfig = mDeps.getOffloadConfig(); if (offloadConfig == null) { mLog.e("Could not find IOffloadConfig service"); return false; @@ -159,11 +222,11 @@ public class OffloadHardwareInterface { // // h2 provides a file descriptor bound to the following netlink groups // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY). - final NativeHandle h1 = createConntrackSocket( + final NativeHandle h1 = mDeps.createConntrackSocket( NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY); if (h1 == null) return false; - final NativeHandle h2 = createConntrackSocket( + final NativeHandle h2 = mDeps.createConntrackSocket( NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY); if (h2 == null) { closeFdInNativeHandle(h1); @@ -198,53 +261,12 @@ public class OffloadHardwareInterface { } } - private NativeHandle createConntrackSocket(final int groups) { - FileDescriptor fd; - try { - fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER); - } catch (ErrnoException e) { - mLog.e("Unable to create conntrack socket " + e); - return null; - } - - final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups); - try { - Os.bind(fd, sockAddr); - } catch (ErrnoException | SocketException e) { - mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e); - try { - SocketUtils.closeSocket(fd); - } catch (IOException ie) { - // Nothing we can do here - } - return null; - } - try { - Os.connect(fd, sockAddr); - } catch (ErrnoException | SocketException e) { - mLog.e("connect to kernel fail for groups " + groups + " error: " + e); - try { - SocketUtils.closeSocket(fd); - } catch (IOException ie) { - // Nothing we can do here - } - return null; - } - - return new NativeHandle(fd, true); - } - /** Initialize the tethering offload HAL. */ public boolean initOffloadControl(ControlCallback controlCb) { mControlCallback = controlCb; if (mOffloadControl == null) { - try { - mOffloadControl = IOffloadControl.getService(true /*retry*/); - } catch (RemoteException | NoSuchElementException e) { - mLog.e("tethering offload control not supported: " + e); - return false; - } + mOffloadControl = mDeps.getOffloadControl(); if (mOffloadControl == null) { mLog.e("tethering IOffloadControl.getService() returned null"); return false; diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java new file mode 100644 index 0000000000..f8ff1cb29c --- /dev/null +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.networkstack.tethering; + +import static android.net.util.TetheringUtils.uint16; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +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.net.util.SharedLog; +import android.os.Handler; +import android.os.NativeHandle; +import android.os.test.TestLooper; +import android.system.OsConstants; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public final class OffloadHardwareInterfaceTest { + private static final String RMNET0 = "test_rmnet_data0"; + + private final TestLooper mTestLooper = new TestLooper(); + + private OffloadHardwareInterface mOffloadHw; + private ITetheringOffloadCallback mTetheringOffloadCallback; + private OffloadHardwareInterface.ControlCallback mControlCallback; + + @Mock private IOffloadConfig mIOffloadConfig; + @Mock private IOffloadControl mIOffloadControl; + @Mock private NativeHandle mNativeHandle; + + class MyDependencies extends OffloadHardwareInterface.Dependencies { + MyDependencies(SharedLog log) { + super(log); + } + + @Override + public IOffloadConfig getOffloadConfig() { + return mIOffloadConfig; + } + + @Override + public IOffloadControl getOffloadControl() { + return mIOffloadControl; + } + + @Override + public NativeHandle createConntrackSocket(final int groups) { + return mNativeHandle; + } + } + + @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()); + } + + private void startOffloadHardwareInterface() throws Exception { + mOffloadHw.initOffloadConfig(); + mOffloadHw.initOffloadControl(mControlCallback); + final ArgumentCaptor mOffloadCallbackCaptor = + ArgumentCaptor.forClass(ITetheringOffloadCallback.class); + verify(mIOffloadControl).initOffload(mOffloadCallbackCaptor.capture(), any()); + mTetheringOffloadCallback = mOffloadCallbackCaptor.getValue(); + } + + @Test + public void testGetForwardedStats() throws Exception { + startOffloadHardwareInterface(); + final OffloadHardwareInterface.ForwardedStats stats = mOffloadHw.getForwardedStats(RMNET0); + verify(mIOffloadControl).getForwardedStats(eq(RMNET0), any()); + assertNotNull(stats); + } + + @Test + public void testSetLocalPrefixes() throws Exception { + startOffloadHardwareInterface(); + final ArrayList localPrefixes = new ArrayList<>(); + localPrefixes.add("127.0.0.0/8"); + localPrefixes.add("fe80::/64"); + mOffloadHw.setLocalPrefixes(localPrefixes); + verify(mIOffloadControl).setLocalPrefixes(eq(localPrefixes), any()); + } + + @Test + public void testSetDataLimit() throws Exception { + startOffloadHardwareInterface(); + final long limit = 12345; + mOffloadHw.setDataLimit(RMNET0, limit); + verify(mIOffloadControl).setDataLimit(eq(RMNET0), eq(limit), any()); + } + + @Test + public void testSetUpstreamParameters() throws Exception { + startOffloadHardwareInterface(); + final String v4addr = "192.168.10.1"; + final String v4gateway = "192.168.10.255"; + final ArrayList v6gws = new ArrayList<>(0); + v6gws.add("2001:db8::1"); + mOffloadHw.setUpstreamParameters(RMNET0, v4addr, v4gateway, v6gws); + verify(mIOffloadControl).setUpstreamParameters(eq(RMNET0), eq(v4addr), eq(v4gateway), + eq(v6gws), any()); + + final ArgumentCaptor> mArrayListCaptor = + ArgumentCaptor.forClass(ArrayList.class); + mOffloadHw.setUpstreamParameters(null, null, null, null); + verify(mIOffloadControl).setUpstreamParameters(eq(""), eq(""), eq(""), + mArrayListCaptor.capture(), any()); + assertEquals(mArrayListCaptor.getValue().size(), 0); + } + + @Test + public void testUpdateDownstreamPrefix() throws Exception { + startOffloadHardwareInterface(); + final String ifName = "wlan1"; + final String prefix = "192.168.43.0/24"; + mOffloadHw.addDownstreamPrefix(ifName, prefix); + verify(mIOffloadControl).addDownstream(eq(ifName), eq(prefix), any()); + + mOffloadHw.removeDownstreamPrefix(ifName, prefix); + verify(mIOffloadControl).removeDownstream(eq(ifName), eq(prefix), any()); + } + + @Test + public void testTetheringOffloadCallback() throws Exception { + startOffloadHardwareInterface(); + + mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STARTED); + mTestLooper.dispatchAll(); + verify(mControlCallback).onStarted(); + + mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR); + mTestLooper.dispatchAll(); + verify(mControlCallback).onStoppedError(); + + mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED); + mTestLooper.dispatchAll(); + verify(mControlCallback).onStoppedUnsupported(); + + mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE); + mTestLooper.dispatchAll(); + verify(mControlCallback).onSupportAvailable(); + + mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED); + mTestLooper.dispatchAll(); + verify(mControlCallback).onStoppedLimitReached(); + + final NatTimeoutUpdate tcpParams = buildNatTimeoutUpdate(NetworkProtocol.TCP); + mTetheringOffloadCallback.updateTimeout(tcpParams); + mTestLooper.dispatchAll(); + verify(mControlCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_TCP), + eq(tcpParams.src.addr), + eq(uint16(tcpParams.src.port)), + eq(tcpParams.dst.addr), + eq(uint16(tcpParams.dst.port))); + + final NatTimeoutUpdate udpParams = buildNatTimeoutUpdate(NetworkProtocol.UDP); + mTetheringOffloadCallback.updateTimeout(udpParams); + mTestLooper.dispatchAll(); + verify(mControlCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_UDP), + eq(udpParams.src.addr), + eq(uint16(udpParams.src.port)), + eq(udpParams.dst.addr), + eq(uint16(udpParams.dst.port))); + } + + private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) { + final NatTimeoutUpdate params = new NatTimeoutUpdate(); + params.proto = proto; + params.src.addr = "192.168.43.200"; + params.src.port = 100; + params.dst.addr = "172.50.46.169"; + params.dst.port = 150; + return params; + } +}