diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java index 33b9d00e70..da5f25b2a5 100644 --- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java +++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java @@ -28,6 +28,7 @@ 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.netlink.NetlinkSocket; +import android.net.netlink.StructNfGenMsg; import android.net.netlink.StructNlMsgHdr; import android.net.util.SharedLog; import android.net.util.SocketUtils; @@ -41,11 +42,12 @@ import android.system.OsConstants; import com.android.internal.annotations.VisibleForTesting; import java.io.FileDescriptor; -import java.io.InterruptedIOException; import java.io.IOException; +import java.io.InterruptedIOException; import java.net.SocketAddress; import java.net.SocketException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.NoSuchElementException; @@ -66,11 +68,12 @@ public class OffloadHardwareInterface { private static final String NO_IPV4_ADDRESS = ""; private static final String NO_IPV4_GATEWAY = ""; // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h - private static final int NF_NETLINK_CONNTRACK_NEW = 1; - private static final int NF_NETLINK_CONNTRACK_UPDATE = 2; - private static final int NF_NETLINK_CONNTRACK_DESTROY = 4; + public static final int NF_NETLINK_CONNTRACK_NEW = 1; + public static final int NF_NETLINK_CONNTRACK_UPDATE = 2; + public static final int NF_NETLINK_CONNTRACK_DESTROY = 4; // Reference libnetfilter_conntrack/linux_nfnetlink_conntrack.h public static final short NFNL_SUBSYS_CTNETLINK = 1; + public static final short IPCTNL_MSG_CT_NEW = 0; public static final short IPCTNL_MSG_CT_GET = 1; private final long NETLINK_MESSAGE_TIMEOUT_MS = 500; @@ -237,7 +240,7 @@ public class OffloadHardwareInterface { NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY); if (h1 == null) return false; - sendNetlinkMessage(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET), + sendIpv4NfGenMsg(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET), (short) (NLM_F_REQUEST | NLM_F_DUMP)); final NativeHandle h2 = mDeps.createConntrackSocket( @@ -267,16 +270,23 @@ public class OffloadHardwareInterface { } @VisibleForTesting - public void sendNetlinkMessage(@NonNull NativeHandle handle, short type, short flags) { - final int length = StructNlMsgHdr.STRUCT_SIZE; + public void sendIpv4NfGenMsg(@NonNull NativeHandle handle, short type, short flags) { + final int length = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE; final byte[] msg = new byte[length]; - final StructNlMsgHdr nlh = new StructNlMsgHdr(); final ByteBuffer byteBuffer = ByteBuffer.wrap(msg); + byteBuffer.order(ByteOrder.nativeOrder()); + + final StructNlMsgHdr nlh = new StructNlMsgHdr(); nlh.nlmsg_len = length; nlh.nlmsg_type = type; nlh.nlmsg_flags = flags; - nlh.nlmsg_seq = 1; + nlh.nlmsg_seq = 0; nlh.pack(byteBuffer); + + // Header needs to be added to buffer since a generic netlink request is being sent. + final StructNfGenMsg nfh = new StructNfGenMsg((byte) OsConstants.AF_INET); + nfh.pack(byteBuffer); + try { NetlinkSocket.sendMessage(handle.getFileDescriptor(), msg, 0 /* offset */, length, NETLINK_MESSAGE_TIMEOUT_MS); diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java new file mode 100644 index 0000000000..57c28fc67c --- /dev/null +++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java @@ -0,0 +1,130 @@ +/* + * 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.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE; +import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP; +import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import static com.android.networkstack.tethering.OffloadHardwareInterface.IPCTNL_MSG_CT_GET; +import static com.android.networkstack.tethering.OffloadHardwareInterface.IPCTNL_MSG_CT_NEW; +import static com.android.networkstack.tethering.OffloadHardwareInterface.NFNL_SUBSYS_CTNETLINK; +import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_DESTROY; +import static com.android.networkstack.tethering.OffloadHardwareInterface.NF_NETLINK_CONNTRACK_NEW; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.net.netlink.StructNlMsgHdr; +import android.net.util.SharedLog; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.NativeHandle; +import android.system.Os; + +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.MockitoAnnotations; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConntrackSocketTest { + private static final long TIMEOUT = 500; + + private HandlerThread mHandlerThread; + private Handler mHandler; + private final SharedLog mLog = new SharedLog("privileged-test"); + + private OffloadHardwareInterface mOffloadHw; + private OffloadHardwareInterface.Dependencies mDeps; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mHandlerThread = new HandlerThread(getClass().getSimpleName()); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + + // Looper must be prepared here since AndroidJUnitRunner runs tests on separate threads. + if (Looper.myLooper() == null) Looper.prepare(); + + mDeps = new OffloadHardwareInterface.Dependencies(mLog); + mOffloadHw = new OffloadHardwareInterface(mHandler, mLog, mDeps); + } + + @Test + public void testIpv4ConntrackSocket() throws Exception { + // Set up server and connect. + final InetSocketAddress anyAddress = new InetSocketAddress( + InetAddress.getByName("127.0.0.1"), 0); + final ServerSocket serverSocket = new ServerSocket(); + serverSocket.bind(anyAddress); + final SocketAddress theAddress = serverSocket.getLocalSocketAddress(); + + // Make a connection to the server. + final Socket socket = new Socket(); + socket.connect(theAddress); + final Socket acceptedSocket = serverSocket.accept(); + + final NativeHandle handle = mDeps.createConntrackSocket( + NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY); + mOffloadHw.sendIpv4NfGenMsg(handle, + (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET), + (short) (NLM_F_REQUEST | NLM_F_DUMP)); + + boolean foundConntrackEntry = false; + ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_RECV_BUFSIZE); + buffer.order(ByteOrder.nativeOrder()); + + try { + while (Os.read(handle.getFileDescriptor(), buffer) > 0) { + buffer.flip(); + + // TODO: ConntrackMessage should get a parse API like StructNlMsgHdr + // so we can confirm that the conntrack added is for the TCP connection above. + final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(buffer); + assertNotNull(nlmsghdr); + + // As long as 1 conntrack entry is found test case will pass, even if it's not + // the from the TCP connection above. + if (nlmsghdr.nlmsg_type == ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_NEW)) { + foundConntrackEntry = true; + break; + } + } + } finally { + socket.close(); + serverSocket.close(); + } + assertTrue("Did not receive any NFNL_SUBSYS_CTNETLINK/IPCTNL_MSG_CT_NEW message", + foundConntrackEntry); + } +} 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 c543fad62d..38b19dd3da 100644 --- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java +++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java @@ -17,8 +17,9 @@ package com.android.networkstack.tethering; import static android.net.util.TetheringUtils.uint16; -import static android.system.OsConstants.SOCK_STREAM; +import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_UNIX; +import static android.system.OsConstants.SOCK_STREAM; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -35,14 +36,15 @@ 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.netlink.StructNfGenMsg; import android.net.netlink.StructNlMsgHdr; import android.net.util.SharedLog; import android.os.Handler; import android.os.NativeHandle; import android.os.test.TestLooper; import android.system.ErrnoException; -import android.system.OsConstants; import android.system.Os; +import android.system.OsConstants; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -55,8 +57,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.FileDescriptor; -import java.io.OutputStream; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayList; @RunWith(AndroidJUnit4.class) @@ -218,7 +220,7 @@ public final class OffloadHardwareInterfaceTest { } @Test - public void testNetlinkMessage() throws Exception { + public void testSendIpv4NfGenMsg() throws Exception { FileDescriptor writeSocket = new FileDescriptor(); FileDescriptor readSocket = new FileDescriptor(); try { @@ -229,17 +231,25 @@ public final class OffloadHardwareInterfaceTest { } when(mNativeHandle.getFileDescriptor()).thenReturn(writeSocket); - mOffloadHw.sendNetlinkMessage(mNativeHandle, TEST_TYPE, TEST_FLAGS); + mOffloadHw.sendIpv4NfGenMsg(mNativeHandle, TEST_TYPE, TEST_FLAGS); + + ByteBuffer buffer = ByteBuffer.allocate(9823); // Arbitrary value > expectedLen. + buffer.order(ByteOrder.nativeOrder()); - ByteBuffer buffer = ByteBuffer.allocate(StructNlMsgHdr.STRUCT_SIZE); int read = Os.read(readSocket, buffer); + final int expectedLen = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE; + assertEquals(expectedLen, read); buffer.flip(); - assertEquals(StructNlMsgHdr.STRUCT_SIZE, buffer.getInt()); + assertEquals(expectedLen, buffer.getInt()); assertEquals(TEST_TYPE, buffer.getShort()); assertEquals(TEST_FLAGS, buffer.getShort()); - assertEquals(1 /* seq */, buffer.getInt()); + assertEquals(0 /* seq */, buffer.getInt()); assertEquals(0 /* pid */, buffer.getInt()); + assertEquals(AF_INET, buffer.get()); // nfgen_family + assertEquals(0 /* error */, buffer.get()); // version + assertEquals(0 /* error */, buffer.getShort()); // res_id + assertEquals(expectedLen, buffer.position()); } private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) {