Snap for 9324941 from 9512ee642f50cdd3200ea703f7bee9c5a00175fb to mainline-permission-release Change-Id: I4ef55b8dba36ed14d39d3f98663a54a3bcf2eb80
diff --git a/Android.bp b/Android.bp index 0aba37c..116d6a6 100644 --- a/Android.bp +++ b/Android.bp
@@ -67,6 +67,7 @@ min_sdk_version: "29", sdk_version: "module_current", libs: [ + "framework-configinfrastructure", "framework-connectivity", "framework-connectivity-t", "framework-statsd",
diff --git a/TEST_MAPPING b/TEST_MAPPING index c1bc9cf..3869fc9 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING
@@ -20,8 +20,16 @@ { "name": "NetworkStackTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]" }, + // This is used to verify NetworkStackRootTests with all latest modules listed below on + // preivous OS platforms. { - "name": "NetworkStackIntegrationTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]" + "name": "NetworkStackRootTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex]" + }, + // This is used to verify NetworkStackRootTests with latest CaptivePortalLoginGoogle.apk + // but with other modules on different version(e.g. an older tethering module to verify + // backwards compatibility of APIs). + { + "name": "NetworkStackRootTests[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk]" } ], "imports": [
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml index d9f3843..71db3ab 100644 --- a/res/values-en-rCA/strings.xml +++ b/res/values-en-rCA/strings.xml
@@ -19,8 +19,8 @@ <string name="notification_channel_name_connected" msgid="1795068343200033922">"Captive portal authentication"</string> <string name="notification_channel_description_connected" msgid="7239184168268014518">"Notifications shown when the device has successfully authenticated to a captive portal network"</string> <string name="notification_channel_name_network_venue_info" msgid="6526543187249265733">"Network venue information"</string> - <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifications shown to indicate that the network has a venue information page"</string> + <string name="notification_channel_description_network_venue_info" msgid="5131499595382733605">"Notifications shown to indicate the network has a venue information page"</string> <string name="connected" msgid="4563643884927480998">"Connected"</string> - <string name="tap_for_info" msgid="6849746325626883711">"Connected/Tap to view website"</string> + <string name="tap_for_info" msgid="6849746325626883711">"Connected / Tap to view website"</string> <string name="application_label" msgid="1322847171305285454">"Network manager"</string> </resources>
diff --git a/src/com/android/networkstack/netlink/TcpSocketTracker.java b/src/com/android/networkstack/netlink/TcpSocketTracker.java index ec130e6..33d3e61 100644 --- a/src/com/android/networkstack/netlink/TcpSocketTracker.java +++ b/src/com/android/networkstack/netlink/TcpSocketTracker.java
@@ -89,14 +89,15 @@ private static final String TAG = TcpSocketTracker.class.getSimpleName(); private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); private static final int[] ADDRESS_FAMILIES = new int[] {AF_INET6, AF_INET}; - // Enough for parsing v1 tcp_info for more than 200 sockets per time. - private static final int DEFAULT_RECV_BUFSIZE = 60_000; + // This is for individual message. The individual messages should not be excessively large. + private static final int DEFAULT_RECV_BUFSIZE = 8192; // Default I/O timeout time in ms of the socket request. private static final long IO_TIMEOUT = 3_000L; /** Cookie offset of an InetMagMessage header. */ private static final int IDIAG_COOKIE_OFFSET = 44; private static final int UNKNOWN_MARK = 0xffffffff; private static final int NULL_MASK = 0; + private static final int END_OF_PARSING = -1; /** * Gather the socket info. * @@ -195,6 +196,7 @@ public boolean pollSocketsInfo() { if (!mDependencies.isTcpInfoParsingSupported()) return false; FileDescriptor fd = null; + try { final long time = SystemClock.elapsedRealtime(); fd = mDependencies.connectToKernel(); @@ -202,61 +204,12 @@ final TcpStat stat = new TcpStat(); for (final int family : ADDRESS_FAMILIES) { mDependencies.sendPollingRequest(fd, mSockDiagMsg.get(family)); - // Messages are composed with the following format. Stop parsing when receiving - // message with nlmsg_type NLMSG_DONE. - // +------------------+---------------+--------------+--------+ - // | Netlink Header | Family Header | Attributes | rtattr | - // | struct nlmsghdr | struct rtmsg | struct rtattr| data | - // +------------------+---------------+--------------+--------+ - // : : : - // +------------------+---------------+--------------+--------+ - // | Netlink Header | Family Header | Attributes | rtattr | - // | struct nlmsghdr | struct rtmsg | struct rtattr| data | - // +------------------+---------------+--------------+--------+ - final ByteBuffer bytes = mDependencies.recvMessage(fd); - try { - while (enoughBytesRemainForValidNlMsg(bytes)) { - final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(bytes); - if (nlmsghdr == null) { - Log.e(TAG, "Badly formatted data."); - break; - } - final int nlmsgLen = nlmsghdr.nlmsg_len; - log("pollSocketsInfo: nlmsghdr=" + nlmsghdr + ", limit=" + bytes.limit()); - // End of the message. Stop parsing. - if (nlmsghdr.nlmsg_type == NLMSG_DONE) break; - if (nlmsghdr.nlmsg_type != SOCK_DIAG_BY_FAMILY) { - Log.e(TAG, "Expect to get family " + family - + " SOCK_DIAG_BY_FAMILY message but get " - + nlmsghdr.nlmsg_type); - break; - } - - if (isValidInetDiagMsgSize(nlmsgLen)) { - // Get the socket cookie value. Composed by two Integers value. - // Corresponds to inet_diag_sockid in - // <linux_src>/include/uapi/linux/inet_diag.h - bytes.position(bytes.position() + IDIAG_COOKIE_OFFSET); - // It's stored in native with 2 int. Parse it as long for convenience. - final long cookie = bytes.getLong(); - // Skip the rest part of StructInetDiagMsg. - bytes.position(bytes.position() - + StructInetDiagMsg.STRUCT_SIZE - IDIAG_COOKIE_OFFSET - - Long.BYTES); - final SocketInfo info = parseSockInfo(bytes, family, nlmsgLen, time); - // Update TcpStats based on previous and current socket info. - stat.accumulate( - calculateLatestPacketsStat(info, mSocketInfos.get(cookie))); - mSocketInfos.put(cookie, info); - } - } - } catch (IllegalArgumentException | BufferUnderflowException e) { - Log.wtf(TAG, "Unexpected socket info parsing, family " + family - + " buffer:" + bytes + " " - + Base64.getEncoder().encodeToString(bytes.array()), e); + while (parseMessage(mDependencies.recvMessage(fd), family, stat, time)) { + log("Pending info exist. Attempt to read more"); } } + // Calculate mLatestReceiveCount, mSentSinceLastRecv and mLatestPacketFailPercentage. mSentSinceLastRecv = (stat.receivedCount == 0) ? (mSentSinceLastRecv + stat.sentCount) : 0; @@ -276,6 +229,85 @@ return false; } + // Return true if there are more pending messages to read + private boolean parseMessage(ByteBuffer bytes, int family, TcpStat stat, long time) { + if (!enoughBytesRemainForValidNlMsg(bytes)) { + // This is unlikely to happen in real cases. Check this first for testing. + Log.e(TAG, "Size is less than header size. Ignored."); + return false; + } + + // Messages are composed with the following format. Stop parsing when receiving + // message with nlmsg_type NLMSG_DONE. + // +------------------+---------------+--------------+--------+ + // | Netlink Header | Family Header | Attributes | rtattr | + // | struct nlmsghdr | struct rtmsg | struct rtattr| data | + // +------------------+---------------+--------------+--------+ + // : : : + // +------------------+---------------+--------------+--------+ + // | Netlink Header | Family Header | Attributes | rtattr | + // | struct nlmsghdr | struct rtmsg | struct rtattr| data | + // +------------------+---------------+--------------+--------+ + try { + do { + final int nlmsgLen = getLengthAndVerifyMsgHeader(bytes, family); + if (nlmsgLen == END_OF_PARSING) return false; + + if (isValidInetDiagMsgSize(nlmsgLen)) { + // Get the socket cookie value. Composed by two Integers value. + // Corresponds to inet_diag_sockid in + // <linux_src>/include/uapi/linux/inet_diag.h + bytes.position(bytes.position() + IDIAG_COOKIE_OFFSET); + + // It's stored in native with 2 int. Parse it as long for + // convenience. + final long cookie = bytes.getLong(); + log("cookie=" + cookie); + // Skip the rest part of StructInetDiagMsg. + bytes.position(bytes.position() + + StructInetDiagMsg.STRUCT_SIZE - IDIAG_COOKIE_OFFSET + - Long.BYTES); + final SocketInfo info = parseSockInfo(bytes, family, nlmsgLen, + time); + // Update TcpStats based on previous and current socket info. + stat.accumulate( + calculateLatestPacketsStat(info, mSocketInfos.get(cookie))); + mSocketInfos.put(cookie, info); + } + } while (enoughBytesRemainForValidNlMsg(bytes)); + } catch (IllegalArgumentException | BufferUnderflowException e) { + Log.wtf(TAG, "Unexpected socket info parsing, family " + family + + " buffer:" + bytes + " " + + Base64.getEncoder().encodeToString(bytes.array()), e); + return false; + } + + return true; + } + + private int getLengthAndVerifyMsgHeader(@NonNull ByteBuffer bytes, int family) { + final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(bytes); + if (nlmsghdr == null) { + Log.e(TAG, "Badly formatted data."); + return END_OF_PARSING; + } + + log("pollSocketsInfo: nlmsghdr=" + nlmsghdr + ", limit=" + bytes.limit()); + // End of the message. Stop parsing. + if (nlmsghdr.nlmsg_type == NLMSG_DONE) { + return END_OF_PARSING; + } + + if (nlmsghdr.nlmsg_type != SOCK_DIAG_BY_FAMILY) { + Log.e(TAG, "Expect to get family " + family + + " SOCK_DIAG_BY_FAMILY message but get " + + nlmsghdr.nlmsg_type); + return END_OF_PARSING; + } + + return nlmsghdr.nlmsg_len; + } + private void cleanupSocketInfo(final long time) { final int size = mSocketInfos.size(); final List<Long> toRemove = new ArrayList<Long>();
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp index fca588d..5926fec 100644 --- a/tests/integration/Android.bp +++ b/tests/integration/Android.bp
@@ -82,6 +82,7 @@ platform_apis: true, test_suites: ["device-tests"], jarjar_rules: ":NetworkStackJarJarRules", + host_required: ["net-tests-utils-host-common"], test_config_template: "AndroidTestTemplate_Integration.xml", } @@ -104,6 +105,7 @@ platform_apis: true, test_suites: ["device-tests"], jarjar_rules: ":NetworkStackJarJarRules", + host_required: ["net-tests-utils-host-common"], test_config_template: "AndroidTestTemplate_Integration.xml", } @@ -123,8 +125,11 @@ ], platform_apis: true, test_suites: ["general-tests", "mts-networking"], + compile_multilib: "both", manifest: "AndroidManifest_root.xml", jarjar_rules: ":NetworkStackJarJarRules", + host_required: ["net-tests-utils-host-common"], + test_config_template: "AndroidTestTemplate_Integration.xml", } // Special version of the network stack tests that includes all tests necessary for code coverage
diff --git a/tests/integration/AndroidTestTemplate_Integration.xml b/tests/integration/AndroidTestTemplate_Integration.xml index 7ea8ad6..6107ccc 100644 --- a/tests/integration/AndroidTestTemplate_Integration.xml +++ b/tests/integration/AndroidTestTemplate_Integration.xml
@@ -23,8 +23,12 @@ that the shell can write to and that the networkstack can read from. --> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + <target_preparer class="com.android.testutils.DisableConfigSyncTargetPreparer" /> + + <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" /> + <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk" /> <test class="com.android.tradefed.testtype.AndroidJUnitTest" > - <option name="package" value="com.android.server.networkstack.integrationtests" /> + <option name="package" value="{PACKAGE}" /> <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> <!-- By default, include and exclude filters go into /data/local/tmp/ajur/, which these tests cannot access because they run in the network_stack selinux context. Move
diff --git a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt index 17153de..3436cb6 100644 --- a/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt +++ b/tests/integration/signature/android/net/util/NetworkStackUtilsIntegrationTest.kt
@@ -59,8 +59,10 @@ import java.net.Inet4Address import java.net.Inet6Address import java.nio.ByteBuffer +import java.util.Arrays import kotlin.reflect.KClass import kotlin.test.assertEquals +import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlin.test.fail @@ -75,6 +77,10 @@ private val TEST_TARGET_MAC = MacAddress.fromString("01:23:45:67:89:0A") private val TEST_INET6ADDR_1 = parseNumericAddress("2001:db8::1") as Inet6Address private val TEST_INET6ADDR_2 = parseNumericAddress("2001:db8::2") as Inet6Address + private val TEST_INET6ADDR_3 = parseNumericAddress("fd01:db8::3") as Inet6Address + + // RFC4291 section 2.7.1 + private val SOLICITED_NODE_MULTICAST_PREFIX = "FF02:0:0:0:0:1:FF00::/104" private val readerHandler = HandlerThread( NetworkStackUtilsIntegrationTest::class.java.simpleName) @@ -185,6 +191,31 @@ assertArrayEquals("Received packet != expected $descr", expected, buffer.copyOfRange(0, readPacket)) } + + private fun assertSolicitedNodeMulticastAddress( + expected: Inet6Address?, + unicast: Inet6Address + ) { + assertNotNull(expected) + val prefix = IpPrefix(SOLICITED_NODE_MULTICAST_PREFIX) + assertTrue(prefix.contains(expected)) + assertTrue(expected.isMulticastAddress()) + // check the last 3 bytes of address + assertArrayEquals(Arrays.copyOfRange(expected.getAddress(), 13, 15), + Arrays.copyOfRange(unicast.getAddress(), 13, 15)) + } + + @Test + fun testConvertIpv6AddressToSolicitedNodeMulticast() { + val addr1 = NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast(TEST_INET6ADDR_1) + assertSolicitedNodeMulticastAddress(addr1, TEST_INET6ADDR_1) + + val addr2 = NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast(TEST_INET6ADDR_2) + assertSolicitedNodeMulticastAddress(addr2, TEST_INET6ADDR_2) + + val addr3 = NetworkStackUtils.ipv6AddressToSolicitedNodeMulticast(TEST_INET6ADDR_3) + assertSolicitedNodeMulticastAddress(addr3, TEST_INET6ADDR_3) + } } private fun ByteBuffer.readAsArray(): ByteArray {
diff --git a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java index 457f0c3..1177f96 100644 --- a/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java +++ b/tests/unit/src/com/android/networkstack/netlink/TcpSocketTrackerTest.java
@@ -23,7 +23,6 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.net.module.util.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE; import static junit.framework.Assert.assertEquals; @@ -35,6 +34,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -76,28 +76,11 @@ private static final int TEST_BUFFER_SIZE = 1024; private static final String DIAG_MSG_HEX = // struct nlmsghdr. - "58000000" + // length = 88 + "10000000" + // length = 16 "1400" + // type = SOCK_DIAG_BY_FAMILY "0301" + // flags = NLM_F_REQUEST | NLM_F_DUMP "00000000" + // seqno - "00000000" + // pid (0 == kernel) - // struct inet_diag_req_v2 - "02" + // family = AF_INET - "06" + // state - "00" + // timer - "00" + // retrans - // inet_diag_sockid - "DEA5" + // idiag_sport = 42462 - "71B9" + // idiag_dport = 47473 - "0a006402000000000000000000000000" + // idiag_src = 10.0.100.2 - "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8 - "00000000" + // idiag_if - "34ED000076270000" + // idiag_cookie = 43387759684916 - "00000000" + // idiag_expires - "00000000" + // idiag_rqueue - "00000000" + // idiag_wqueue - "00000000" + // idiag_uid - "00000000"; // idiag_inode + "00000000"; // pid (0 == kernel) private static final byte[] SOCK_DIAG_MSG_BYTES = HexEncoding.decode(DIAG_MSG_HEX.toCharArray(), false); // Hexadecimal representation of a SOCK_DIAG response with tcp info. @@ -300,8 +283,9 @@ assertEquals(-1, tst.getLatestPacketFailPercentage()); assertEquals(0, tst.getSentSinceLastRecv()); - final ByteBuffer tcpBuffer = getByteBuffer(TEST_RESPONSE_BYTES); - when(mDependencies.recvMessage(any())).thenReturn(tcpBuffer); + final ByteBuffer tcpBufferV6 = getByteBuffer(TEST_RESPONSE_BYTES); + final ByteBuffer tcpBufferV4 = getByteBuffer(TEST_RESPONSE_BYTES); + doReturn(tcpBufferV6, tcpBufferV4).when(mDependencies).recvMessage(any()); assertTrue(tst.pollSocketsInfo()); assertEquals(10, tst.getSentSinceLastRecv()); @@ -377,15 +361,16 @@ // This test requires shims that provide API 30 access assumeTrue(ConstantsShim.VERSION >= 30); final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mNetwork); - ByteBuffer tcpBuffer = getByteBuffer(TEST_RESPONSE_BYTES); - - when(mDependencies.recvMessage(any())).thenReturn(tcpBuffer); + final ByteBuffer tcpBufferV6 = getByteBuffer(TEST_RESPONSE_BYTES); + final ByteBuffer tcpBufferV4 = getByteBuffer(TEST_RESPONSE_BYTES); + doReturn(tcpBufferV6, tcpBufferV4).when(mDependencies).recvMessage(any()); assertTrue(tst.pollSocketsInfo()); assertEquals(10, tst.getSentSinceLastRecv()); assertEquals(50, tst.getLatestPacketFailPercentage()); - tcpBuffer = getByteBuffer(BAD_SOCK_DIAG_MSG_BYTES); - when(mDependencies.recvMessage(any())).thenReturn(tcpBuffer); + final ByteBuffer badTcpBufferV6 = getByteBuffer(BAD_SOCK_DIAG_MSG_BYTES); + final ByteBuffer badTcpBufferV4 = getByteBuffer(BAD_SOCK_DIAG_MSG_BYTES); + doReturn(badTcpBufferV6, badTcpBufferV4).when(mDependencies).recvMessage(any()); assertTrue(tst.pollSocketsInfo()); // Expect no additional packets, so still 10. assertEquals(10, tst.getSentSinceLastRecv()); @@ -398,8 +383,9 @@ when(mNetd.getFwmarkForNetwork(eq(TEST_NETID2))) .thenReturn(makeMarkMaskParcel(NETID_MASK, TEST_NETID2_FWMARK)); final TcpSocketTracker tst = new TcpSocketTracker(mDependencies, mOtherNetwork); - final ByteBuffer tcpBuffer = getByteBuffer(TEST_RESPONSE_BYTES); - when(mDependencies.recvMessage(any())).thenReturn(tcpBuffer); + final ByteBuffer tcpBufferV6 = getByteBuffer(TEST_RESPONSE_BYTES); + final ByteBuffer tcpBufferV4 = getByteBuffer(TEST_RESPONSE_BYTES); + doReturn(tcpBufferV6, tcpBufferV4).when(mDependencies).recvMessage(any()); assertTrue(tst.pollSocketsInfo()); assertEquals(0, tst.getSentSinceLastRecv());