Merge "Add unit tests checking MTU of interfaces are set."
diff --git a/Cronet/tests/cts/src/org/chromium/net/test/CronetApiTest.java b/Cronet/tests/cts/src/org/chromium/net/test/CronetApiTest.java
deleted file mode 100644
index 6465006..0000000
--- a/Cronet/tests/cts/src/org/chromium/net/test/CronetApiTest.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2019 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 org.chromium.net.test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.os.Handler;
-import android.os.Looper;
-
-import androidx.annotation.NonNull;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.chromium.net.CronetEngine;
-import org.chromium.net.CronetException;
-import org.chromium.net.UrlRequest;
-import org.chromium.net.UrlResponseInfo;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.nio.ByteBuffer;
-import java.util.Random;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-public class CronetApiTest {
- private static final String TAG = CronetApiTest.class.getSimpleName();
- private static final String HTTPS_PREFIX = "https://";
- private static final int TIMEOUT_MS = 12_000;
-
- private final String[] mTestDomains = {"www.google.com", "www.android.com"};
- @NonNull private CronetEngine mCronetEngine;
- @NonNull private ConnectivityManager mCm;
- @NonNull private Executor mExecutor;
-
- @Before
- public void setUp() throws Exception {
- Context context = InstrumentationRegistry.getInstrumentation().getContext();
- mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- CronetEngine.Builder builder = new CronetEngine.Builder(context);
- builder.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY, 100 * 1024)
- .enableHttp2(true)
- // .enableBrotli(true)
- .enableQuic(true);
- mCronetEngine = builder.build();
- mExecutor = new Handler(Looper.getMainLooper())::post;
- }
-
- private static void assertGreaterThan(String msg, int first, int second) {
- assertTrue(msg + " Excepted " + first + " to be greater than " + second, first > second);
- }
-
- private void assertHasTestableNetworks() {
- assertNotNull("This test requires a working Internet connection", mCm.getActiveNetwork());
- }
-
- private String getRandomDomain() {
- int index = (new Random()).nextInt(mTestDomains.length);
- return mTestDomains[index];
- }
-
- private static class TestUrlRequestCallback extends UrlRequest.Callback {
- private final CountDownLatch mLatch = new CountDownLatch(1);
- private final String mUrl;
-
- TestUrlRequestCallback(@NonNull String url) {
- this.mUrl = url;
- }
-
- public boolean waitForAnswer() throws InterruptedException {
- return mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
- }
-
- @Override
- public void onRedirectReceived(
- UrlRequest request, UrlResponseInfo info, String newLocationUrl) {
- request.followRedirect();
- }
-
- @Override
- public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
- request.read(ByteBuffer.allocateDirect(32 * 1024));
- }
-
- @Override
- public void onReadCompleted(
- UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) {
- byteBuffer.clear();
- request.read(byteBuffer);
- }
-
- @Override
- public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
- assertEquals(
- "Unexpected http status code from " + mUrl + ".",
- 200,
- info.getHttpStatusCode());
- assertGreaterThan(
- "Received byte from " + mUrl + " is 0.", (int) info.getReceivedByteCount(), 0);
- mLatch.countDown();
- }
-
- @Override
- public void onFailed(UrlRequest request, UrlResponseInfo info, CronetException error) {
- fail(mUrl + error.getMessage());
- }
- }
-
- @Test
- public void testUrlRequestGet_CompletesSuccessfully() throws Exception {
- assertHasTestableNetworks();
- String url = HTTPS_PREFIX + getRandomDomain();
- TestUrlRequestCallback callback = new TestUrlRequestCallback(url);
- UrlRequest.Builder builder = mCronetEngine.newUrlRequestBuilder(url, callback, mExecutor);
- builder.build().start();
- assertTrue(url + " but not complete after " + TIMEOUT_MS + "ms.", callback.waitForAnswer());
- }
-}
diff --git a/Cronet/tests/cts/src/org/chromium/net/test/CronetUrlRequestTest.java b/Cronet/tests/cts/src/org/chromium/net/test/CronetUrlRequestTest.java
new file mode 100644
index 0000000..7dd9a9a
--- /dev/null
+++ b/Cronet/tests/cts/src/org/chromium/net/test/CronetUrlRequestTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 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 org.chromium.net.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.chromium.net.CronetEngine;
+import org.chromium.net.UrlRequest;
+import org.chromium.net.UrlResponseInfo;
+import org.chromium.net.test.util.TestUrlRequestCallback;
+import org.chromium.net.test.util.TestUrlRequestCallback.ResponseStep;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+
+@RunWith(AndroidJUnit4.class)
+public class CronetUrlRequestTest {
+ private static final String TAG = CronetUrlRequestTest.class.getSimpleName();
+ private static final String HTTPS_PREFIX = "https://";
+
+ private final String[] mTestDomains = {"www.google.com", "www.android.com"};
+ @NonNull private CronetEngine mCronetEngine;
+ @NonNull private ConnectivityManager mCm;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ CronetEngine.Builder builder = new CronetEngine.Builder(context);
+ builder.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY, 100 * 1024)
+ .enableHttp2(true)
+ // .enableBrotli(true)
+ .enableQuic(true);
+ mCronetEngine = builder.build();
+ }
+
+ private static void assertGreaterThan(String msg, int first, int second) {
+ assertTrue(msg + " Excepted " + first + " to be greater than " + second, first > second);
+ }
+
+ private void assertHasTestableNetworks() {
+ assertNotNull("This test requires a working Internet connection", mCm.getActiveNetwork());
+ }
+
+ private String getRandomDomain() {
+ int index = (new Random()).nextInt(mTestDomains.length);
+ return mTestDomains[index];
+ }
+
+ @Test
+ public void testUrlRequestGet_CompletesSuccessfully() throws Exception {
+ assertHasTestableNetworks();
+ String url = HTTPS_PREFIX + getRandomDomain();
+ TestUrlRequestCallback callback = new TestUrlRequestCallback();
+ UrlRequest.Builder builder = mCronetEngine.newUrlRequestBuilder(url, callback,
+ callback.getExecutor());
+ builder.build().start();
+
+ callback.expectCallback(ResponseStep.ON_SUCCEEDED);
+
+ UrlResponseInfo info = callback.mResponseInfo;
+ assertEquals("Unexpected http status code from " + url + ".", 200,
+ info.getHttpStatusCode());
+ assertGreaterThan(
+ "Received byte from " + url + " is 0.", (int) info.getReceivedByteCount(), 0);
+ }
+}
diff --git a/Tethering/jni/com_android_networkstack_tethering_BpfCoordinator.cpp b/Tethering/jni/com_android_networkstack_tethering_BpfCoordinator.cpp
index 27357f8..c8c86bc 100644
--- a/Tethering/jni/com_android_networkstack_tethering_BpfCoordinator.cpp
+++ b/Tethering/jni/com_android_networkstack_tethering_BpfCoordinator.cpp
@@ -17,7 +17,7 @@
#include <jni.h>
#include <nativehelper/JNIHelp.h>
-#include "bpf_tethering.h"
+#include "offload.h"
namespace android {
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 6a5089d..51c7c9c 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -126,7 +126,7 @@
private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
- /** The names of all the BPF counters defined in bpf_tethering.h. */
+ /** The names of all the BPF counters defined in offload.h. */
public static final String[] sBpfCounterNames = getBpfCounterNames();
private static String makeMapPath(String which) {
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index fbb342d..846abcb 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -295,8 +295,7 @@
NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
if (h1 == null) return false;
- sendIpv4NfGenMsg(h1, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET),
- (short) (NLM_F_REQUEST | NLM_F_DUMP));
+ requestSocketDump(h1);
final NativeHandle h2 = mDeps.createConntrackSocket(
NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
@@ -325,7 +324,7 @@
}
@VisibleForTesting
- public void sendIpv4NfGenMsg(@NonNull NativeHandle handle, short type, short flags) {
+ 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 ByteBuffer byteBuffer = ByteBuffer.wrap(msg);
@@ -350,6 +349,12 @@
}
}
+ @VisibleForTesting
+ void requestSocketDump(NativeHandle handle) {
+ sendIpv4NfGenMsg(handle, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET),
+ (short) (NLM_F_REQUEST | NLM_F_DUMP));
+ }
+
private void closeFdInNativeHandle(final NativeHandle h) {
try {
h.close();
diff --git a/Tethering/tests/integration/src/android/net/CtsEthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/CtsEthernetTetheringTest.java
new file mode 100644
index 0000000..aea6728
--- /dev/null
+++ b/Tethering/tests/integration/src/android/net/CtsEthernetTetheringTest.java
@@ -0,0 +1,1076 @@
+/*
+ * 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 android.net;
+
+import static android.Manifest.permission.DUMP;
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringTester.TestDnsPacket;
+import static android.net.TetheringTester.isExpectedIcmpPacket;
+import static android.net.TetheringTester.isExpectedUdpDnsPacket;
+import static android.system.OsConstants.ICMP_ECHO;
+import static android.system.OsConstants.ICMP_ECHOREPLY;
+import static android.system.OsConstants.IPPROTO_ICMP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
+import static com.android.net.module.util.HexDump.dumpHexString;
+import static com.android.net.module.util.IpUtils.icmpChecksum;
+import static com.android.net.module.util.IpUtils.ipChecksum;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
+import static com.android.net.module.util.NetworkStackConstants.ICMP_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
+import static com.android.testutils.DeviceInfoUtils.KVersion;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.net.TetheringManager.TetheringRequest;
+import android.net.TetheringTester.TetheredDevice;
+import android.os.Build;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.VintfRuntimeInfo;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.BpfDump;
+import com.android.net.module.util.Ipv6Utils;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.bpf.Tether4Key;
+import com.android.net.module.util.bpf.Tether4Value;
+import com.android.net.module.util.bpf.TetherStatsKey;
+import com.android.net.module.util.bpf.TetherStatsValue;
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv4Header;
+import com.android.net.module.util.structs.Ipv4Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.UdpHeader;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DeviceInfoUtils;
+import com.android.testutils.DumpTestUtils;
+import com.android.testutils.TapPacketReader;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileDescriptor;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class CtsEthernetTetheringTest extends EthernetTetheringTestBase {
+ @Rule
+ public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
+ private static final String TAG = CtsEthernetTetheringTest.class.getSimpleName();
+
+ private static final int DUMP_POLLING_MAX_RETRY = 100;
+ private static final int DUMP_POLLING_INTERVAL_MS = 50;
+ // Kernel treats a confirmed UDP connection which active after two seconds as stream mode.
+ // See upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5.
+ private static final int UDP_STREAM_TS_MS = 2000;
+ // Give slack time for waiting UDP stream mode because handling conntrack event in user space
+ // may not in precise time. Used to reduce the flaky rate.
+ private static final int UDP_STREAM_SLACK_MS = 500;
+ // Per RX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
+ private static final int RX_UDP_PACKET_SIZE = 30;
+ private static final int RX_UDP_PACKET_COUNT = 456;
+ // Per TX UDP packet size: ethhdr (14) + iphdr (20) + udphdr (8) + payload (2) = 44 bytes.
+ private static final int TX_UDP_PACKET_SIZE = 44;
+ private static final int TX_UDP_PACKET_COUNT = 123;
+
+ private static final short DNS_PORT = 53;
+
+ private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
+ private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
+ private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
+ private static final String LINE_DELIMITER = "\\n";
+
+ private static final short ICMPECHO_CODE = 0x0;
+ private static final short ICMPECHO_ID = 0x0;
+ private static final short ICMPECHO_SEQ = 0x0;
+
+ // TODO: use class DnsPacket to build DNS query and reply message once DnsPacket supports
+ // building packet for given arguments.
+ private static final ByteBuffer DNS_QUERY = ByteBuffer.wrap(new byte[] {
+ // scapy.DNS(
+ // id=0xbeef,
+ // qr=0,
+ // qd=scapy.DNSQR(qname="hello.example.com"))
+ //
+ /* Header */
+ (byte) 0xbe, (byte) 0xef, /* Transaction ID: 0xbeef */
+ (byte) 0x01, (byte) 0x00, /* Flags: rd */
+ (byte) 0x00, (byte) 0x01, /* Questions: 1 */
+ (byte) 0x00, (byte) 0x00, /* Answer RRs: 0 */
+ (byte) 0x00, (byte) 0x00, /* Authority RRs: 0 */
+ (byte) 0x00, (byte) 0x00, /* Additional RRs: 0 */
+ /* Queries */
+ (byte) 0x05, (byte) 0x68, (byte) 0x65, (byte) 0x6c,
+ (byte) 0x6c, (byte) 0x6f, (byte) 0x07, (byte) 0x65,
+ (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
+ (byte) 0x6c, (byte) 0x65, (byte) 0x03, (byte) 0x63,
+ (byte) 0x6f, (byte) 0x6d, (byte) 0x00, /* Name: hello.example.com */
+ (byte) 0x00, (byte) 0x01, /* Type: A */
+ (byte) 0x00, (byte) 0x01 /* Class: IN */
+ });
+
+ private static final byte[] DNS_REPLY = new byte[] {
+ // scapy.DNS(
+ // id=0,
+ // qr=1,
+ // qd=scapy.DNSQR(qname="hello.example.com"),
+ // an=scapy.DNSRR(rrname="hello.example.com", rdata='1.2.3.4'))
+ //
+ /* Header */
+ (byte) 0x00, (byte) 0x00, /* Transaction ID: 0x0, must be updated by dns query id */
+ (byte) 0x81, (byte) 0x00, /* Flags: qr rd */
+ (byte) 0x00, (byte) 0x01, /* Questions: 1 */
+ (byte) 0x00, (byte) 0x01, /* Answer RRs: 1 */
+ (byte) 0x00, (byte) 0x00, /* Authority RRs: 0 */
+ (byte) 0x00, (byte) 0x00, /* Additional RRs: 0 */
+ /* Queries */
+ (byte) 0x05, (byte) 0x68, (byte) 0x65, (byte) 0x6c,
+ (byte) 0x6c, (byte) 0x6f, (byte) 0x07, (byte) 0x65,
+ (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
+ (byte) 0x6c, (byte) 0x65, (byte) 0x03, (byte) 0x63,
+ (byte) 0x6f, (byte) 0x6d, (byte) 0x00, /* Name: hello.example.com */
+ (byte) 0x00, (byte) 0x01, /* Type: A */
+ (byte) 0x00, (byte) 0x01, /* Class: IN */
+ /* Answers */
+ (byte) 0x05, (byte) 0x68, (byte) 0x65, (byte) 0x6c,
+ (byte) 0x6c, (byte) 0x6f, (byte) 0x07, (byte) 0x65,
+ (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
+ (byte) 0x6c, (byte) 0x65, (byte) 0x03, (byte) 0x63,
+ (byte) 0x6f, (byte) 0x6d, (byte) 0x00, /* Name: hello.example.com */
+ (byte) 0x00, (byte) 0x01, /* Type: A */
+ (byte) 0x00, (byte) 0x01, /* Class: IN */
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, /* Time to live: 0 */
+ (byte) 0x00, (byte) 0x04, /* Data length: 4 */
+ (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04 /* Address: 1.2.3.4 */
+ };
+
+ @Test
+ public void testVirtualEthernetAlreadyExists() throws Exception {
+ // This test requires manipulating packets. Skip if there is a physical Ethernet connected.
+ assumeFalse(isInterfaceForTetheringAvailable());
+
+ TestNetworkInterface downstreamIface = null;
+ MyTetheringEventCallback tetheringEventCallback = null;
+ TapPacketReader downstreamReader = null;
+
+ try {
+ downstreamIface = createTestInterface();
+ // This must be done now because as soon as setIncludeTestInterfaces(true) is called,
+ // the interface will be placed in client mode, which will delete the link-local
+ // address. At that point NetworkInterface.getByName() will cease to work on the
+ // interface, because starting in R NetworkInterface can no longer see interfaces
+ // without IP addresses.
+ int mtu = getMTU(downstreamIface);
+
+ Log.d(TAG, "Including test interfaces");
+ setIncludeTestInterfaces(true);
+
+ final String iface = getTetheredInterface();
+ assertEquals("TetheredInterfaceCallback for unexpected interface",
+ downstreamIface.getInterfaceName(), iface);
+
+ // Check virtual ethernet.
+ FileDescriptor fd = downstreamIface.getFileDescriptor().getFileDescriptor();
+ downstreamReader = makePacketReader(fd, mtu);
+ tetheringEventCallback = enableEthernetTethering(downstreamIface.getInterfaceName(),
+ null /* any upstream */);
+ checkTetheredClientCallbacks(downstreamReader, tetheringEventCallback);
+ } finally {
+ maybeStopTapPacketReader(downstreamReader);
+ maybeCloseTestInterface(downstreamIface);
+ maybeUnregisterTetheringEventCallback(tetheringEventCallback);
+ }
+ }
+
+ @Test
+ public void testVirtualEthernet() throws Exception {
+ // This test requires manipulating packets. Skip if there is a physical Ethernet connected.
+ assumeFalse(isInterfaceForTetheringAvailable());
+
+ CompletableFuture<String> futureIface = requestTetheredInterface();
+
+ setIncludeTestInterfaces(true);
+
+ TestNetworkInterface downstreamIface = null;
+ MyTetheringEventCallback tetheringEventCallback = null;
+ TapPacketReader downstreamReader = null;
+
+ try {
+ downstreamIface = createTestInterface();
+
+ final String iface = futureIface.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ assertEquals("TetheredInterfaceCallback for unexpected interface",
+ downstreamIface.getInterfaceName(), iface);
+
+ // Check virtual ethernet.
+ FileDescriptor fd = downstreamIface.getFileDescriptor().getFileDescriptor();
+ downstreamReader = makePacketReader(fd, getMTU(downstreamIface));
+ tetheringEventCallback = enableEthernetTethering(downstreamIface.getInterfaceName(),
+ null /* any upstream */);
+ checkTetheredClientCallbacks(downstreamReader, tetheringEventCallback);
+ } finally {
+ maybeStopTapPacketReader(downstreamReader);
+ maybeCloseTestInterface(downstreamIface);
+ maybeUnregisterTetheringEventCallback(tetheringEventCallback);
+ }
+ }
+
+ @Test
+ public void testStaticIpv4() throws Exception {
+ assumeFalse(isInterfaceForTetheringAvailable());
+
+ setIncludeTestInterfaces(true);
+
+ TestNetworkInterface downstreamIface = null;
+ MyTetheringEventCallback tetheringEventCallback = null;
+ TapPacketReader downstreamReader = null;
+
+ try {
+ downstreamIface = createTestInterface();
+
+ final String iface = getTetheredInterface();
+ assertEquals("TetheredInterfaceCallback for unexpected interface",
+ downstreamIface.getInterfaceName(), iface);
+
+ assertInvalidStaticIpv4Request(iface, null, null);
+ assertInvalidStaticIpv4Request(iface, "2001:db8::1/64", "2001:db8:2::/64");
+ assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", "2001:db8:2::/28");
+ assertInvalidStaticIpv4Request(iface, "2001:db8:2::/28", "192.0.2.2/28");
+ assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", null);
+ assertInvalidStaticIpv4Request(iface, null, "192.0.2.2/28");
+ assertInvalidStaticIpv4Request(iface, "192.0.2.3/27", "192.0.2.2/28");
+
+ final String localAddr = "192.0.2.3/28";
+ final String clientAddr = "192.0.2.2/28";
+ tetheringEventCallback = enableEthernetTethering(iface,
+ requestWithStaticIpv4(localAddr, clientAddr), null /* any upstream */);
+
+ tetheringEventCallback.awaitInterfaceTethered();
+ assertInterfaceHasIpAddress(iface, localAddr);
+
+ byte[] client1 = MacAddress.fromString("1:2:3:4:5:6").toByteArray();
+ byte[] client2 = MacAddress.fromString("a:b:c:d:e:f").toByteArray();
+
+ FileDescriptor fd = downstreamIface.getFileDescriptor().getFileDescriptor();
+ downstreamReader = makePacketReader(fd, getMTU(downstreamIface));
+ TetheringTester tester = new TetheringTester(downstreamReader);
+ DhcpResults dhcpResults = tester.runDhcp(client1);
+ assertEquals(new LinkAddress(clientAddr), dhcpResults.ipAddress);
+
+ try {
+ tester.runDhcp(client2);
+ fail("Only one client should get an IP address");
+ } catch (TimeoutException expected) { }
+ } finally {
+ maybeStopTapPacketReader(downstreamReader);
+ maybeCloseTestInterface(downstreamIface);
+ maybeUnregisterTetheringEventCallback(tetheringEventCallback);
+ }
+ }
+
+ private static void expectLocalOnlyAddresses(String iface) throws Exception {
+ final List<InterfaceAddress> interfaceAddresses =
+ NetworkInterface.getByName(iface).getInterfaceAddresses();
+
+ boolean foundIpv6Ula = false;
+ for (InterfaceAddress ia : interfaceAddresses) {
+ final InetAddress addr = ia.getAddress();
+ if (isIPv6ULA(addr)) {
+ foundIpv6Ula = true;
+ }
+ final int prefixlen = ia.getNetworkPrefixLength();
+ final LinkAddress la = new LinkAddress(addr, prefixlen);
+ if (la.isIpv6() && la.isGlobalPreferred()) {
+ fail("Found global IPv6 address on local-only interface: " + interfaceAddresses);
+ }
+ }
+
+ assertTrue("Did not find IPv6 ULA on local-only interface " + iface,
+ foundIpv6Ula);
+ }
+
+ @Test
+ public void testLocalOnlyTethering() throws Exception {
+ assumeFalse(isInterfaceForTetheringAvailable());
+
+ setIncludeTestInterfaces(true);
+
+ TestNetworkInterface downstreamIface = null;
+ MyTetheringEventCallback tetheringEventCallback = null;
+ TapPacketReader downstreamReader = null;
+
+ try {
+ downstreamIface = createTestInterface();
+
+ final String iface = getTetheredInterface();
+ assertEquals("TetheredInterfaceCallback for unexpected interface",
+ downstreamIface.getInterfaceName(), iface);
+
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_ETHERNET)
+ .setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build();
+ tetheringEventCallback = enableEthernetTethering(iface, request,
+ null /* any upstream */);
+ tetheringEventCallback.awaitInterfaceLocalOnly();
+
+ // makePacketReader only works after tethering is started, because until then the
+ // interface does not have an IP address, and unprivileged apps cannot see interfaces
+ // without IP addresses. This shouldn't be flaky because the TAP interface will buffer
+ // all packets even before the reader is started.
+ downstreamReader = makePacketReader(downstreamIface);
+
+ waitForRouterAdvertisement(downstreamReader, iface, WAIT_RA_TIMEOUT_MS);
+ expectLocalOnlyAddresses(iface);
+ } finally {
+ maybeStopTapPacketReader(downstreamReader);
+ maybeCloseTestInterface(downstreamIface);
+ maybeUnregisterTetheringEventCallback(tetheringEventCallback);
+ }
+ }
+
+ private boolean isAdbOverNetwork() {
+ // If adb TCP port opened, this test may running by adb over network.
+ return (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1)
+ || (SystemProperties.getInt("service.adb.tcp.port", -1) > -1);
+ }
+
+ @Test
+ public void testPhysicalEthernet() throws Exception {
+ assumeTrue(isInterfaceForTetheringAvailable());
+ // Do not run this test if adb is over network and ethernet is connected.
+ // It is likely the adb run over ethernet, the adb would break when ethernet is switching
+ // from client mode to server mode. See b/160389275.
+ assumeFalse(isAdbOverNetwork());
+
+ MyTetheringEventCallback tetheringEventCallback = null;
+ try {
+ // Get an interface to use.
+ final String iface = getTetheredInterface();
+
+ // Enable Ethernet tethering and check that it starts.
+ tetheringEventCallback = enableEthernetTethering(iface, null /* any upstream */);
+ } finally {
+ maybeUnregisterTetheringEventCallback(tetheringEventCallback);
+ }
+ // There is nothing more we can do on a physical interface without connecting an actual
+ // client, which is not possible in this test.
+ }
+
+ private void checkTetheredClientCallbacks(final TapPacketReader packetReader,
+ final MyTetheringEventCallback tetheringEventCallback) throws Exception {
+ // Create a fake client.
+ byte[] clientMacAddr = new byte[6];
+ new Random().nextBytes(clientMacAddr);
+
+ TetheringTester tester = new TetheringTester(packetReader);
+ DhcpResults dhcpResults = tester.runDhcp(clientMacAddr);
+
+ final Collection<TetheredClient> clients = tetheringEventCallback.awaitClientConnected();
+ assertEquals(1, clients.size());
+ final TetheredClient client = clients.iterator().next();
+
+ // Check the MAC address.
+ assertEquals(MacAddress.fromBytes(clientMacAddr), client.getMacAddress());
+ assertEquals(TETHERING_ETHERNET, client.getTetheringType());
+
+ // Check the hostname.
+ assertEquals(1, client.getAddresses().size());
+ TetheredClient.AddressInfo info = client.getAddresses().get(0);
+ assertEquals(TetheringTester.DHCP_HOSTNAME, info.getHostname());
+
+ // Check the address is the one that was handed out in the DHCP ACK.
+ assertLinkAddressMatches(dhcpResults.ipAddress, info.getAddress());
+
+ // Check that the lifetime is correct +/- 10s.
+ final long now = SystemClock.elapsedRealtime();
+ final long actualLeaseDuration = (info.getAddress().getExpirationTime() - now) / 1000;
+ final String msg = String.format("IP address should have lifetime of %d, got %d",
+ dhcpResults.leaseDuration, actualLeaseDuration);
+ assertTrue(msg, Math.abs(dhcpResults.leaseDuration - actualLeaseDuration) < 10);
+ }
+
+ public void assertLinkAddressMatches(LinkAddress l1, LinkAddress l2) {
+ // Check all fields except the deprecation and expiry times.
+ String msg = String.format("LinkAddresses do not match. expected: %s actual: %s", l1, l2);
+ assertTrue(msg, l1.isSameAddressAs(l2));
+ assertEquals("LinkAddress flags do not match", l1.getFlags(), l2.getFlags());
+ assertEquals("LinkAddress scope does not match", l1.getScope(), l2.getScope());
+ }
+
+ private TetheringRequest requestWithStaticIpv4(String local, String client) {
+ LinkAddress localAddr = local == null ? null : new LinkAddress(local);
+ LinkAddress clientAddr = client == null ? null : new LinkAddress(client);
+ return new TetheringRequest.Builder(TETHERING_ETHERNET)
+ .setStaticIpv4Addresses(localAddr, clientAddr)
+ .setShouldShowEntitlementUi(false).build();
+ }
+
+ private void assertInvalidStaticIpv4Request(String iface, String local, String client)
+ throws Exception {
+ try {
+ enableEthernetTethering(iface, requestWithStaticIpv4(local, client),
+ null /* any upstream */);
+ fail("Unexpectedly accepted invalid IPv4 configuration: " + local + ", " + client);
+ } catch (IllegalArgumentException | NullPointerException expected) { }
+ }
+
+ private void assertInterfaceHasIpAddress(String iface, String expected) throws Exception {
+ LinkAddress expectedAddr = new LinkAddress(expected);
+ NetworkInterface nif = NetworkInterface.getByName(iface);
+ for (InterfaceAddress ia : nif.getInterfaceAddresses()) {
+ final LinkAddress addr = new LinkAddress(ia.getAddress(), ia.getNetworkPrefixLength());
+ if (expectedAddr.equals(addr)) {
+ return;
+ }
+ }
+ fail("Expected " + iface + " to have IP address " + expected + ", found "
+ + nif.getInterfaceAddresses());
+ }
+
+ @Test
+ public void testIcmpv6Echo() throws Exception {
+ runPing6Test(initTetheringTester(toList(TEST_IP4_ADDR, TEST_IP6_ADDR),
+ toList(TEST_IP4_DNS, TEST_IP6_DNS)));
+ }
+
+ private void runPing6Test(TetheringTester tester) throws Exception {
+ TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
+ Inet6Address remoteIp6Addr = (Inet6Address) parseNumericAddress("2400:222:222::222");
+ ByteBuffer request = Ipv6Utils.buildEchoRequestPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv6Addr, remoteIp6Addr);
+ tester.verifyUpload(request, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+
+ return isExpectedIcmpPacket(p, false /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ECHO_REQUEST_TYPE);
+ });
+
+ ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(remoteIp6Addr, tethered.ipv6Addr);
+ tester.verifyDownload(reply, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+
+ return isExpectedIcmpPacket(p, true /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ECHO_REPLY_TYPE);
+ });
+ }
+
+ @Test
+ public void testTetherUdpV6() throws Exception {
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
+ toList(TEST_IP6_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
+ sendUploadPacketUdp(tethered.macAddr, tethered.routerMacAddr,
+ tethered.ipv6Addr, REMOTE_IP6_ADDR, tester, false /* is4To6 */);
+ sendDownloadPacketUdp(REMOTE_IP6_ADDR, tethered.ipv6Addr, tester, false /* is6To4 */);
+
+ // TODO: test BPF offload maps {rule, stats}.
+ }
+
+ // Test network topology:
+ //
+ // public network (rawip) private network
+ // | UE |
+ // +------------+ V +------------+------------+ V +------------+
+ // | Sever +---------+ Upstream | Downstream +---------+ Client |
+ // +------------+ +------------+------------+ +------------+
+ // remote ip public ip private ip
+ // 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876
+ //
+ private void runUdp4Test(boolean verifyBpf) throws Exception {
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
+ toList(TEST_IP4_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
+
+ // TODO: remove the connectivity verification for upstream connected notification race.
+ // Because async upstream connected notification can't guarantee the tethering routing is
+ // ready to use. Need to test tethering connectivity before testing.
+ // For short term plan, consider using IPv6 RA to get MAC address because the prefix comes
+ // from upstream. That can guarantee that the routing is ready. Long term plan is that
+ // refactors upstream connected notification from async to sync.
+ probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
+
+ final MacAddress srcMac = tethered.macAddr;
+ final MacAddress dstMac = tethered.routerMacAddr;
+ final InetAddress remoteIp = REMOTE_IP4_ADDR;
+ final InetAddress tetheringUpstreamIp = TEST_IP4_ADDR.getAddress();
+ final InetAddress clientIp = tethered.ipv4Addr;
+ sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
+ sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
+
+ if (verifyBpf) {
+ // Send second UDP packet in original direction.
+ // The BPF coordinator only offloads the ASSURED conntrack entry. The "request + reply"
+ // packets can make status IPS_SEEN_REPLY to be set. Need one more packet to make
+ // conntrack status IPS_ASSURED_BIT to be set. Note the third packet needs to delay
+ // 2 seconds because kernel monitors a UDP connection which still alive after 2 seconds
+ // and apply ASSURED flag.
+ // See kernel upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5 and
+ // nf_conntrack_udp_packet in net/netfilter/nf_conntrack_proto_udp.c
+ Thread.sleep(UDP_STREAM_TS_MS);
+ sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
+
+ // Give a slack time for handling conntrack event in user space.
+ Thread.sleep(UDP_STREAM_SLACK_MS);
+
+ // [1] Verify IPv4 upstream rule map.
+ final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump(
+ Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4);
+ assertNotNull(upstreamMap);
+ assertEquals(1, upstreamMap.size());
+
+ final Map.Entry<Tether4Key, Tether4Value> rule =
+ upstreamMap.entrySet().iterator().next();
+
+ final Tether4Key upstream4Key = rule.getKey();
+ assertEquals(IPPROTO_UDP, upstream4Key.l4proto);
+ assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), upstream4Key.src4));
+ assertEquals(LOCAL_PORT, upstream4Key.srcPort);
+ assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), upstream4Key.dst4));
+ assertEquals(REMOTE_PORT, upstream4Key.dstPort);
+
+ final Tether4Value upstream4Value = rule.getValue();
+ assertTrue(Arrays.equals(tetheringUpstreamIp.getAddress(),
+ InetAddress.getByAddress(upstream4Value.src46).getAddress()));
+ assertEquals(LOCAL_PORT, upstream4Value.srcPort);
+ assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(),
+ InetAddress.getByAddress(upstream4Value.dst46).getAddress()));
+ assertEquals(REMOTE_PORT, upstream4Value.dstPort);
+
+ // [2] Verify stats map.
+ // Transmit packets on both direction for verifying stats. Because we only care the
+ // packet count in stats test, we just reuse the existing packets to increaes
+ // the packet count on both direction.
+
+ // Send packets on original direction.
+ for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
+ sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester,
+ false /* is4To6 */);
+ }
+
+ // Send packets on reply direction.
+ for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
+ sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
+ }
+
+ // Dump stats map to verify.
+ final HashMap<TetherStatsKey, TetherStatsValue> statsMap = pollRawMapFromDump(
+ TetherStatsKey.class, TetherStatsValue.class, DUMPSYS_RAWMAP_ARG_STATS);
+ assertNotNull(statsMap);
+ assertEquals(1, statsMap.size());
+
+ final Map.Entry<TetherStatsKey, TetherStatsValue> stats =
+ statsMap.entrySet().iterator().next();
+
+ // TODO: verify the upstream index in TetherStatsKey.
+
+ final TetherStatsValue statsValue = stats.getValue();
+ assertEquals(RX_UDP_PACKET_COUNT, statsValue.rxPackets);
+ assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE, statsValue.rxBytes);
+ assertEquals(0, statsValue.rxErrors);
+ assertEquals(TX_UDP_PACKET_COUNT, statsValue.txPackets);
+ assertEquals(TX_UDP_PACKET_COUNT * TX_UDP_PACKET_SIZE, statsValue.txBytes);
+ assertEquals(0, statsValue.txErrors);
+ }
+ }
+
+ private static boolean isUdpOffloadSupportedByKernel(final String kernelVersion) {
+ final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion);
+ return current.isInRange(new KVersion(4, 14, 222), new KVersion(4, 19, 0))
+ || current.isInRange(new KVersion(4, 19, 176), new KVersion(5, 4, 0))
+ || current.isAtLeast(new KVersion(5, 4, 98));
+ }
+
+ @Test
+ public void testIsUdpOffloadSupportedByKernel() throws Exception {
+ assertFalse(isUdpOffloadSupportedByKernel("4.14.221"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.14.222"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.16.0"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.18.0"));
+ assertFalse(isUdpOffloadSupportedByKernel("4.19.0"));
+
+ assertFalse(isUdpOffloadSupportedByKernel("4.19.175"));
+ assertTrue(isUdpOffloadSupportedByKernel("4.19.176"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.2.0"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.3.0"));
+ assertFalse(isUdpOffloadSupportedByKernel("5.4.0"));
+
+ assertFalse(isUdpOffloadSupportedByKernel("5.4.97"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.4.98"));
+ assertTrue(isUdpOffloadSupportedByKernel("5.10.0"));
+ }
+
+ private static void assumeKernelSupportBpfOffloadUdpV4() {
+ final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
+ assumeTrue("Kernel version " + kernelVersion + " doesn't support IPv4 UDP BPF offload",
+ isUdpOffloadSupportedByKernel(kernelVersion));
+ }
+
+ @Test
+ public void testKernelSupportBpfOffloadUdpV4() throws Exception {
+ assumeKernelSupportBpfOffloadUdpV4();
+ }
+
+ @Test
+ public void testTetherConfigBpfOffloadEnabled() throws Exception {
+ assumeTrue(isTetherConfigBpfOffloadEnabled());
+ }
+
+ /**
+ * Basic IPv4 UDP tethering test. Verify that UDP tethered packets are transferred no matter
+ * using which data path.
+ */
+ @Test
+ public void testTetherUdpV4() throws Exception {
+ runUdp4Test(false /* verifyBpf */);
+ }
+
+ /**
+ * BPF offload IPv4 UDP tethering test. Verify that UDP tethered packets are offloaded by BPF.
+ * Minimum test requirement:
+ * 1. S+ device.
+ * 2. Tethering config enables tethering BPF offload.
+ * 3. Kernel supports IPv4 UDP BPF offload. See #isUdpOffloadSupportedByKernel.
+ *
+ * TODO: consider enabling the test even tethering config disables BPF offload. See b/238288883
+ */
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testTetherUdpV4_VerifyBpf() throws Exception {
+ assumeTrue("Tethering config disabled BPF offload", isTetherConfigBpfOffloadEnabled());
+ assumeKernelSupportBpfOffloadUdpV4();
+
+ runUdp4Test(true /* verifyBpf */);
+ }
+
+ @NonNull
+ private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
+ Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+ throws Exception {
+ final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg};
+ final String rawMapStr = runAsShell(DUMP, () ->
+ DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args));
+ final HashMap<K, V> map = new HashMap<>();
+
+ for (final String line : rawMapStr.split(LINE_DELIMITER)) {
+ final Pair<K, V> rule =
+ BpfDump.fromBase64EncodedString(keyClass, valueClass, line.trim());
+ map.put(rule.first, rule.second);
+ }
+ return map;
+ }
+
+ @Nullable
+ private <K extends Struct, V extends Struct> HashMap<K, V> pollRawMapFromDump(
+ Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
+ throws Exception {
+ for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) {
+ final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, mapArg);
+ if (!map.isEmpty()) return map;
+
+ Thread.sleep(DUMP_POLLING_INTERVAL_MS);
+ }
+
+ fail("Cannot get rules after " + DUMP_POLLING_MAX_RETRY * DUMP_POLLING_INTERVAL_MS + "ms");
+ return null;
+ }
+
+ private boolean isTetherConfigBpfOffloadEnabled() throws Exception {
+ final String dumpStr = runAsShell(DUMP, () ->
+ DumpTestUtils.dumpService(Context.TETHERING_SERVICE, "--short"));
+
+ // BPF offload tether config can be overridden by "config_tether_enable_bpf_offload" in
+ // packages/modules/Connectivity/Tethering/res/values/config.xml. OEM may disable config by
+ // RRO to override the enabled default value. Get the tethering config via dumpsys.
+ // $ dumpsys tethering
+ // mIsBpfEnabled: true
+ boolean enabled = dumpStr.contains("mIsBpfEnabled: true");
+ if (!enabled) {
+ Log.d(TAG, "BPF offload tether config not enabled: " + dumpStr);
+ }
+ return enabled;
+ }
+
+ @NonNull
+ private Inet6Address getClatIpv6Address(TetheringTester tester, TetheredDevice tethered)
+ throws Exception {
+ // Send an IPv4 UDP packet from client and check that a CLAT translated IPv6 UDP packet can
+ // be found on upstream interface. Get CLAT IPv6 address from the CLAT translated IPv6 UDP
+ // packet.
+ byte[] expectedPacket = probeV4TetheringConnectivity(tester, tethered, true /* is4To6 */);
+
+ // Above has guaranteed that the found packet is an IPv6 packet without ether header.
+ return Struct.parse(Ipv6Header.class, ByteBuffer.wrap(expectedPacket)).srcIp;
+ }
+
+ // Test network topology:
+ //
+ // public network (rawip) private network
+ // | UE (CLAT support) |
+ // +---------------+ V +------------+------------+ V +------------+
+ // | NAT64 Gateway +---------+ Upstream | Downstream +---------+ Client |
+ // +---------------+ +------------+------------+ +------------+
+ // remote ip public ip private ip
+ // [64:ff9b::808:808]:443 [clat ipv6]:9876 [TetheredDevice ipv4]:9876
+ //
+ // Note that CLAT IPv6 address is generated by ClatCoordinator. Get the CLAT IPv6 address by
+ // sending out an IPv4 packet and extracting the source address from CLAT translated IPv6
+ // packet.
+ //
+ private void runClatUdpTest() throws Exception {
+ // CLAT only starts on IPv6 only network.
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
+ toList(TEST_IP6_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
+
+ // Get CLAT IPv6 address.
+ final Inet6Address clatIp6 = getClatIpv6Address(tester, tethered);
+
+ // Send an IPv4 UDP packet in original direction.
+ // IPv4 packet -- CLAT translation --> IPv6 packet
+ sendUploadPacketUdp(tethered.macAddr, tethered.routerMacAddr, tethered.ipv4Addr,
+ REMOTE_IP4_ADDR, tester, true /* is4To6 */);
+
+ // Send an IPv6 UDP packet in reply direction.
+ // IPv6 packet -- CLAT translation --> IPv4 packet
+ sendDownloadPacketUdp(REMOTE_NAT64_ADDR, clatIp6, tester, true /* is6To4 */);
+
+ // TODO: test CLAT bpf maps.
+ }
+
+ // TODO: support R device. See b/234727688.
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testTetherClatUdp() throws Exception {
+ runClatUdpTest();
+ }
+
+ // PacketBuilder doesn't support IPv4 ICMP packet. It may need to refactor PacketBuilder first
+ // because ICMP is a specific layer 3 protocol for PacketBuilder which expects packets always
+ // have layer 3 (IP) and layer 4 (TCP, UDP) for now. Since we don't use IPv4 ICMP packet too
+ // much in this test, we just write a ICMP packet builder here.
+ // TODO: move ICMPv4 packet build function to common utilis.
+ @NonNull
+ private ByteBuffer buildIcmpEchoPacketV4(
+ @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
+ @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp,
+ int type, short id, short seq) throws Exception {
+ if (type != ICMP_ECHO && type != ICMP_ECHOREPLY) {
+ fail("Unsupported ICMP type: " + type);
+ }
+
+ // Build ICMP echo id and seq fields as payload. Ignore the data field.
+ final ByteBuffer payload = ByteBuffer.allocate(4);
+ payload.putShort(id);
+ payload.putShort(seq);
+ payload.rewind();
+
+ final boolean hasEther = (srcMac != null && dstMac != null);
+ final int etherHeaderLen = hasEther ? Struct.getSize(EthernetHeader.class) : 0;
+ final int ipv4HeaderLen = Struct.getSize(Ipv4Header.class);
+ final int Icmpv4HeaderLen = Struct.getSize(Icmpv4Header.class);
+ final int payloadLen = payload.limit();
+ final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv4HeaderLen
+ + Icmpv4HeaderLen + payloadLen);
+
+ // [1] Ethernet header
+ if (hasEther) {
+ final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, ETHER_TYPE_IPV4);
+ ethHeader.writeToByteBuffer(packet);
+ }
+
+ // [2] IP header
+ final Ipv4Header ipv4Header = new Ipv4Header(TYPE_OF_SERVICE,
+ (short) 0 /* totalLength, calculate later */, ID,
+ FLAGS_AND_FRAGMENT_OFFSET, TIME_TO_LIVE, (byte) IPPROTO_ICMP,
+ (short) 0 /* checksum, calculate later */, srcIp, dstIp);
+ ipv4Header.writeToByteBuffer(packet);
+
+ // [3] ICMP header
+ final Icmpv4Header icmpv4Header = new Icmpv4Header((byte) type, ICMPECHO_CODE,
+ (short) 0 /* checksum, calculate later */);
+ icmpv4Header.writeToByteBuffer(packet);
+
+ // [4] Payload
+ packet.put(payload);
+ packet.flip();
+
+ // [5] Finalize packet
+ // Used for updating IP header fields. If there is Ehternet header, IPv4 header offset
+ // in buffer equals ethernet header length because IPv4 header is located next to ethernet
+ // header. Otherwise, IPv4 header offset is 0.
+ final int ipv4HeaderOffset = hasEther ? etherHeaderLen : 0;
+
+ // Populate the IPv4 totalLength field.
+ packet.putShort(ipv4HeaderOffset + IPV4_LENGTH_OFFSET,
+ (short) (ipv4HeaderLen + Icmpv4HeaderLen + payloadLen));
+
+ // Populate the IPv4 header checksum field.
+ packet.putShort(ipv4HeaderOffset + IPV4_CHECKSUM_OFFSET,
+ ipChecksum(packet, ipv4HeaderOffset /* headerOffset */));
+
+ // Populate the ICMP checksum field.
+ packet.putShort(ipv4HeaderOffset + IPV4_HEADER_MIN_LEN + ICMP_CHECKSUM_OFFSET,
+ icmpChecksum(packet, ipv4HeaderOffset + IPV4_HEADER_MIN_LEN,
+ Icmpv4HeaderLen + payloadLen));
+ return packet;
+ }
+
+ @NonNull
+ private ByteBuffer buildIcmpEchoPacketV4(@NonNull final Inet4Address srcIp,
+ @NonNull final Inet4Address dstIp, int type, short id, short seq)
+ throws Exception {
+ return buildIcmpEchoPacketV4(null /* srcMac */, null /* dstMac */, srcIp, dstIp,
+ type, id, seq);
+ }
+
+ @Test
+ public void testIcmpv4Echo() throws Exception {
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
+ toList(TEST_IP4_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
+
+ // TODO: remove the connectivity verification for upstream connected notification race.
+ // See the same reason in runUdp4Test().
+ probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
+
+ final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */,
+ tethered.routerMacAddr /* dstMac */, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, ICMP_ECHO, ICMPECHO_ID, ICMPECHO_SEQ);
+ tester.verifyUpload(request, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+
+ return isExpectedIcmpPacket(p, false /* hasEth */, true /* isIpv4 */, ICMP_ECHO);
+ });
+
+ final ByteBuffer reply = buildIcmpEchoPacketV4(REMOTE_IP4_ADDR /* srcIp*/,
+ (Inet4Address) TEST_IP4_ADDR.getAddress() /* dstIp */, ICMP_ECHOREPLY, ICMPECHO_ID,
+ ICMPECHO_SEQ);
+ tester.verifyDownload(reply, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+
+ return isExpectedIcmpPacket(p, true /* hasEth */, true /* isIpv4 */, ICMP_ECHOREPLY);
+ });
+ }
+
+ // TODO: support R device. See b/234727688.
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testTetherClatIcmp() throws Exception {
+ // CLAT only starts on IPv6 only network.
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
+ toList(TEST_IP6_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
+
+ // Get CLAT IPv6 address.
+ final Inet6Address clatIp6 = getClatIpv6Address(tester, tethered);
+
+ // Send an IPv4 ICMP packet in original direction.
+ // IPv4 packet -- CLAT translation --> IPv6 packet
+ final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */,
+ tethered.routerMacAddr /* dstMac */, tethered.ipv4Addr /* srcIp */,
+ (Inet4Address) REMOTE_IP4_ADDR /* dstIp */, ICMP_ECHO, ICMPECHO_ID, ICMPECHO_SEQ);
+ tester.verifyUpload(request, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+
+ return isExpectedIcmpPacket(p, false /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ECHO_REQUEST_TYPE);
+ });
+
+ // Send an IPv6 ICMP packet in reply direction.
+ // IPv6 packet -- CLAT translation --> IPv4 packet
+ final ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(
+ (Inet6Address) REMOTE_NAT64_ADDR /* srcIp */, clatIp6 /* dstIp */);
+ tester.verifyDownload(reply, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+
+ return isExpectedIcmpPacket(p, true /* hasEth */, true /* isIpv4 */, ICMP_ECHOREPLY);
+ });
+ }
+
+ @NonNull
+ private ByteBuffer buildDnsReplyMessageById(short id) {
+ byte[] replyMessage = Arrays.copyOf(DNS_REPLY, DNS_REPLY.length);
+ // Assign transaction id of reply message pattern with a given DNS transaction id.
+ replyMessage[0] = (byte) ((id >> 8) & 0xff);
+ replyMessage[1] = (byte) (id & 0xff);
+ Log.d(TAG, "Built DNS reply: " + dumpHexString(replyMessage));
+
+ return ByteBuffer.wrap(replyMessage);
+ }
+
+ @NonNull
+ private void sendDownloadPacketDnsV4(@NonNull final Inet4Address srcIp,
+ @NonNull final Inet4Address dstIp, short srcPort, short dstPort, short dnsId,
+ @NonNull final TetheringTester tester) throws Exception {
+ // DNS response transaction id must be copied from DNS query. Used by the requester
+ // to match up replies to outstanding queries. See RFC 1035 section 4.1.1.
+ final ByteBuffer dnsReplyMessage = buildDnsReplyMessageById(dnsId);
+ final ByteBuffer testPacket = buildUdpPacket((InetAddress) srcIp,
+ (InetAddress) dstIp, srcPort, dstPort, dnsReplyMessage);
+
+ tester.verifyDownload(testPacket, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+ return isExpectedUdpDnsPacket(p, true /* hasEther */, true /* isIpv4 */,
+ dnsReplyMessage);
+ });
+ }
+
+ // Send IPv4 UDP DNS packet and return the forwarded DNS packet on upstream.
+ @NonNull
+ private byte[] sendUploadPacketDnsV4(@NonNull final MacAddress srcMac,
+ @NonNull final MacAddress dstMac, @NonNull final Inet4Address srcIp,
+ @NonNull final Inet4Address dstIp, short srcPort, short dstPort,
+ @NonNull final TetheringTester tester) throws Exception {
+ final ByteBuffer testPacket = buildUdpPacket(srcMac, dstMac, srcIp, dstIp,
+ srcPort, dstPort, DNS_QUERY);
+
+ return tester.verifyUpload(testPacket, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+ return isExpectedUdpDnsPacket(p, false /* hasEther */, true /* isIpv4 */,
+ DNS_QUERY);
+ });
+ }
+
+ @Test
+ public void testTetherUdpV4Dns() throws Exception {
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
+ toList(TEST_IP4_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
+
+ // TODO: remove the connectivity verification for upstream connected notification race.
+ // See the same reason in runUdp4Test().
+ probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
+
+ // [1] Send DNS query.
+ // tethered device --> downstream --> dnsmasq forwarding --> upstream --> DNS server
+ //
+ // Need to extract DNS transaction id and source port from dnsmasq forwarded DNS query
+ // packet. dnsmasq forwarding creats new query which means UDP source port and DNS
+ // transaction id are changed from original sent DNS query. See forward_query() in
+ // external/dnsmasq/src/forward.c. Note that #TetheringTester.isExpectedUdpDnsPacket
+ // guarantees that |forwardedQueryPacket| is a valid DNS packet. So we can parse it as DNS
+ // packet.
+ final MacAddress srcMac = tethered.macAddr;
+ final MacAddress dstMac = tethered.routerMacAddr;
+ final Inet4Address clientIp = tethered.ipv4Addr;
+ final Inet4Address gatewayIp = tethered.ipv4Gatway;
+ final byte[] forwardedQueryPacket = sendUploadPacketDnsV4(srcMac, dstMac, clientIp,
+ gatewayIp, LOCAL_PORT, DNS_PORT, tester);
+ final ByteBuffer buf = ByteBuffer.wrap(forwardedQueryPacket);
+ Struct.parse(Ipv4Header.class, buf);
+ final UdpHeader udpHeader = Struct.parse(UdpHeader.class, buf);
+ final TestDnsPacket dnsQuery = TestDnsPacket.getTestDnsPacket(buf);
+ assertNotNull(dnsQuery);
+ Log.d(TAG, "Forwarded UDP source port: " + udpHeader.srcPort + ", DNS query id: "
+ + dnsQuery.getHeader().getId());
+
+ // [2] Send DNS reply.
+ // DNS server --> upstream --> dnsmasq forwarding --> downstream --> tethered device
+ //
+ // DNS reply transaction id must be copied from DNS query. Used by the requester to match
+ // up replies to outstanding queries. See RFC 1035 section 4.1.1.
+ final Inet4Address remoteIp = (Inet4Address) TEST_IP4_DNS;
+ final Inet4Address tetheringUpstreamIp = (Inet4Address) TEST_IP4_ADDR.getAddress();
+ sendDownloadPacketDnsV4(remoteIp, tetheringUpstreamIp, DNS_PORT,
+ (short) udpHeader.srcPort, (short) dnsQuery.getHeader().getId(), tester);
+ }
+
+ @Test
+ public void testTetherTcpV4() throws Exception {
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
+ toList(TEST_IP4_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
+
+ // TODO: remove the connectivity verification for upstream connected notification race.
+ // See the same reason in runUdp4Test().
+ probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
+
+ runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
+ tethered.ipv4Addr /* uploadSrcIp */, REMOTE_IP4_ADDR /* uploadDstIp */,
+ REMOTE_IP4_ADDR /* downloadSrcIp */, TEST_IP4_ADDR.getAddress() /* downloadDstIp */,
+ tester, false /* isClat */);
+ }
+
+ @Test
+ public void testTetherTcpV6() throws Exception {
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
+ toList(TEST_IP6_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
+
+ runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
+ tethered.ipv6Addr /* uploadSrcIp */, REMOTE_IP6_ADDR /* uploadDstIp */,
+ REMOTE_IP6_ADDR /* downloadSrcIp */, tethered.ipv6Addr /* downloadDstIp */,
+ tester, false /* isClat */);
+ }
+
+ // TODO: support R device. See b/234727688.
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testTetherClatTcp() throws Exception {
+ // CLAT only starts on IPv6 only network.
+ final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
+ toList(TEST_IP6_DNS));
+ final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
+
+ // Get CLAT IPv6 address.
+ final Inet6Address clatIp6 = getClatIpv6Address(tester, tethered);
+
+ runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
+ tethered.ipv4Addr /* uploadSrcIp */, REMOTE_IP4_ADDR /* uploadDstIp */,
+ REMOTE_NAT64_ADDR /* downloadSrcIp */, clatIp6 /* downloadDstIp */,
+ tester, true /* isClat */);
+ }
+}
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
deleted file mode 100644
index da69a8d..0000000
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ /dev/null
@@ -1,1952 +0,0 @@
-/*
- * 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 android.net;
-
-import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
-import static android.Manifest.permission.DUMP;
-import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
-import static android.Manifest.permission.NETWORK_SETTINGS;
-import static android.Manifest.permission.TETHER_PRIVILEGED;
-import static android.content.pm.PackageManager.FEATURE_WIFI;
-import static android.net.InetAddresses.parseNumericAddress;
-import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
-import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
-import static android.net.TetheringManager.TETHERING_ETHERNET;
-import static android.net.TetheringTester.TestDnsPacket;
-import static android.net.TetheringTester.isExpectedIcmpPacket;
-import static android.net.TetheringTester.isExpectedTcpPacket;
-import static android.net.TetheringTester.isExpectedUdpDnsPacket;
-import static android.net.TetheringTester.isExpectedUdpPacket;
-import static android.system.OsConstants.ICMP_ECHO;
-import static android.system.OsConstants.ICMP_ECHOREPLY;
-import static android.system.OsConstants.IPPROTO_ICMP;
-import static android.system.OsConstants.IPPROTO_IP;
-import static android.system.OsConstants.IPPROTO_IPV6;
-import static android.system.OsConstants.IPPROTO_TCP;
-import static android.system.OsConstants.IPPROTO_UDP;
-
-import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
-import static com.android.net.module.util.HexDump.dumpHexString;
-import static com.android.net.module.util.IpUtils.icmpChecksum;
-import static com.android.net.module.util.IpUtils.ipChecksum;
-import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
-import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
-import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
-import static com.android.net.module.util.NetworkStackConstants.ICMP_CHECKSUM_OFFSET;
-import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET;
-import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
-import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
-import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK;
-import static com.android.net.module.util.NetworkStackConstants.TCPHDR_SYN;
-import static com.android.testutils.DeviceInfoUtils.KVersion;
-import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
-import static com.android.testutils.TestPermissionUtil.runAsShell;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.Context;
-import android.net.EthernetManager.TetheredInterfaceCallback;
-import android.net.EthernetManager.TetheredInterfaceRequest;
-import android.net.TetheringManager.StartTetheringCallback;
-import android.net.TetheringManager.TetheringEventCallback;
-import android.net.TetheringManager.TetheringRequest;
-import android.net.TetheringTester.TetheredDevice;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.VintfRuntimeInfo;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.modules.utils.build.SdkLevel;
-import com.android.net.module.util.BpfDump;
-import com.android.net.module.util.Ipv6Utils;
-import com.android.net.module.util.PacketBuilder;
-import com.android.net.module.util.Struct;
-import com.android.net.module.util.bpf.Tether4Key;
-import com.android.net.module.util.bpf.Tether4Value;
-import com.android.net.module.util.bpf.TetherStatsKey;
-import com.android.net.module.util.bpf.TetherStatsValue;
-import com.android.net.module.util.structs.EthernetHeader;
-import com.android.net.module.util.structs.Icmpv4Header;
-import com.android.net.module.util.structs.Ipv4Header;
-import com.android.net.module.util.structs.Ipv6Header;
-import com.android.net.module.util.structs.UdpHeader;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
-import com.android.testutils.DeviceInfoUtils;
-import com.android.testutils.DumpTestUtils;
-import com.android.testutils.HandlerUtils;
-import com.android.testutils.TapPacketReader;
-import com.android.testutils.TestNetworkTracker;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.FileDescriptor;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.net.InterfaceAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Random;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-@RunWith(AndroidJUnit4.class)
-@MediumTest
-public class EthernetTetheringTest extends EthernetTetheringTestBase {
- @Rule
- public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
-
- private static final String TAG = EthernetTetheringTest.class.getSimpleName();
-
- private static final int DUMP_POLLING_MAX_RETRY = 100;
- private static final int DUMP_POLLING_INTERVAL_MS = 50;
- // Kernel treats a confirmed UDP connection which active after two seconds as stream mode.
- // See upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5.
- private static final int UDP_STREAM_TS_MS = 2000;
- // Give slack time for waiting UDP stream mode because handling conntrack event in user space
- // may not in precise time. Used to reduce the flaky rate.
- private static final int UDP_STREAM_SLACK_MS = 500;
- // Per RX UDP packet size: iphdr (20) + udphdr (8) + payload (2) = 30 bytes.
- private static final int RX_UDP_PACKET_SIZE = 30;
- private static final int RX_UDP_PACKET_COUNT = 456;
- // Per TX UDP packet size: ethhdr (14) + iphdr (20) + udphdr (8) + payload (2) = 44 bytes.
- private static final int TX_UDP_PACKET_SIZE = 44;
- private static final int TX_UDP_PACKET_COUNT = 123;
-
- private static final short DNS_PORT = 53;
-
- private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
- private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
- private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
- private static final String LINE_DELIMITER = "\\n";
-
- private static final short ICMPECHO_CODE = 0x0;
- private static final short ICMPECHO_ID = 0x0;
- private static final short ICMPECHO_SEQ = 0x0;
-
- // TODO: use class DnsPacket to build DNS query and reply message once DnsPacket supports
- // building packet for given arguments.
- private static final ByteBuffer DNS_QUERY = ByteBuffer.wrap(new byte[] {
- // scapy.DNS(
- // id=0xbeef,
- // qr=0,
- // qd=scapy.DNSQR(qname="hello.example.com"))
- //
- /* Header */
- (byte) 0xbe, (byte) 0xef, /* Transaction ID: 0xbeef */
- (byte) 0x01, (byte) 0x00, /* Flags: rd */
- (byte) 0x00, (byte) 0x01, /* Questions: 1 */
- (byte) 0x00, (byte) 0x00, /* Answer RRs: 0 */
- (byte) 0x00, (byte) 0x00, /* Authority RRs: 0 */
- (byte) 0x00, (byte) 0x00, /* Additional RRs: 0 */
- /* Queries */
- (byte) 0x05, (byte) 0x68, (byte) 0x65, (byte) 0x6c,
- (byte) 0x6c, (byte) 0x6f, (byte) 0x07, (byte) 0x65,
- (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
- (byte) 0x6c, (byte) 0x65, (byte) 0x03, (byte) 0x63,
- (byte) 0x6f, (byte) 0x6d, (byte) 0x00, /* Name: hello.example.com */
- (byte) 0x00, (byte) 0x01, /* Type: A */
- (byte) 0x00, (byte) 0x01 /* Class: IN */
- });
-
- private static final byte[] DNS_REPLY = new byte[] {
- // scapy.DNS(
- // id=0,
- // qr=1,
- // qd=scapy.DNSQR(qname="hello.example.com"),
- // an=scapy.DNSRR(rrname="hello.example.com", rdata='1.2.3.4'))
- //
- /* Header */
- (byte) 0x00, (byte) 0x00, /* Transaction ID: 0x0, must be updated by dns query id */
- (byte) 0x81, (byte) 0x00, /* Flags: qr rd */
- (byte) 0x00, (byte) 0x01, /* Questions: 1 */
- (byte) 0x00, (byte) 0x01, /* Answer RRs: 1 */
- (byte) 0x00, (byte) 0x00, /* Authority RRs: 0 */
- (byte) 0x00, (byte) 0x00, /* Additional RRs: 0 */
- /* Queries */
- (byte) 0x05, (byte) 0x68, (byte) 0x65, (byte) 0x6c,
- (byte) 0x6c, (byte) 0x6f, (byte) 0x07, (byte) 0x65,
- (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
- (byte) 0x6c, (byte) 0x65, (byte) 0x03, (byte) 0x63,
- (byte) 0x6f, (byte) 0x6d, (byte) 0x00, /* Name: hello.example.com */
- (byte) 0x00, (byte) 0x01, /* Type: A */
- (byte) 0x00, (byte) 0x01, /* Class: IN */
- /* Answers */
- (byte) 0x05, (byte) 0x68, (byte) 0x65, (byte) 0x6c,
- (byte) 0x6c, (byte) 0x6f, (byte) 0x07, (byte) 0x65,
- (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
- (byte) 0x6c, (byte) 0x65, (byte) 0x03, (byte) 0x63,
- (byte) 0x6f, (byte) 0x6d, (byte) 0x00, /* Name: hello.example.com */
- (byte) 0x00, (byte) 0x01, /* Type: A */
- (byte) 0x00, (byte) 0x01, /* Class: IN */
- (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, /* Time to live: 0 */
- (byte) 0x00, (byte) 0x04, /* Data length: 4 */
- (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04 /* Address: 1.2.3.4 */
- };
-
- private TetheredInterfaceRequester mTetheredInterfaceRequester;
- private MyTetheringEventCallback mTetheringEventCallback;
-
- @Before
- public void setUp() throws Exception {
- mHandlerThread = new HandlerThread(getClass().getSimpleName());
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
-
- mRunTests = runAsShell(NETWORK_SETTINGS, TETHER_PRIVILEGED, () ->
- mTm.isTetheringSupported());
- assumeTrue(mRunTests);
-
- mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm);
- }
-
- private void maybeStopTapPacketReader(final TapPacketReader tapPacketReader)
- throws Exception {
- if (tapPacketReader != null) {
- TapPacketReader reader = tapPacketReader;
- mHandler.post(() -> reader.stop());
- }
- }
-
- private void maybeCloseTestInterface(final TestNetworkInterface testInterface)
- throws Exception {
- if (testInterface != null) {
- testInterface.getFileDescriptor().close();
- Log.d(TAG, "Deleted test interface " + testInterface.getInterfaceName());
- }
- }
-
- private void maybeUnregisterTetheringEventCallback(final MyTetheringEventCallback callback)
- throws Exception {
- if (callback != null) {
- callback.awaitInterfaceUntethered();
- callback.unregister();
- }
- }
-
- private void cleanUp() throws Exception {
- setPreferTestNetworks(false);
-
- if (mUpstreamTracker != null) {
- runAsShell(MANAGE_TEST_NETWORKS, () -> {
- mUpstreamTracker.teardown();
- mUpstreamTracker = null;
- });
- }
- if (mUpstreamReader != null) {
- TapPacketReader reader = mUpstreamReader;
- mHandler.post(() -> reader.stop());
- mUpstreamReader = null;
- }
-
- maybeStopTapPacketReader(mDownstreamReader);
- mDownstreamReader = null;
- // To avoid flaky which caused by the next test started but the previous interface is not
- // untracked from EthernetTracker yet. Just delete the test interface without explicitly
- // calling TetheringManager#stopTethering could let EthernetTracker untrack the test
- // interface from server mode before tethering stopped. Thus, awaitInterfaceUntethered
- // could not only make sure tethering is stopped but also guarantee the test interface is
- // untracked from EthernetTracker.
- maybeCloseTestInterface(mDownstreamIface);
- mDownstreamIface = null;
- maybeUnregisterTetheringEventCallback(mTetheringEventCallback);
- mTetheringEventCallback = null;
-
- runAsShell(NETWORK_SETTINGS, () -> mTetheredInterfaceRequester.release());
- setIncludeTestInterfaces(false);
- }
-
- @After
- public void tearDown() throws Exception {
- try {
- if (mRunTests) cleanUp();
- } finally {
- mHandlerThread.quitSafely();
- mUiAutomation.dropShellPermissionIdentity();
- }
- }
-
- private boolean isInterfaceForTetheringAvailable() throws Exception {
- // Before T, all ethernet interfaces could be used for server mode. Instead of
- // waiting timeout, just checking whether the system currently has any
- // ethernet interface is more reliable.
- if (!SdkLevel.isAtLeastT()) {
- return runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS, () -> mEm.isAvailable());
- }
-
- // If previous test case doesn't release tethering interface successfully, the other tests
- // after that test may be skipped as unexcepted.
- // TODO: figure out a better way to check default tethering interface existenion.
- final TetheredInterfaceRequester requester = new TetheredInterfaceRequester(mHandler, mEm);
- try {
- // Use short timeout (200ms) for requesting an existing interface, if any, because
- // it should reurn faster than requesting a new tethering interface. Using default
- // timeout (5000ms, TIMEOUT_MS) may make that total testing time is over 1 minute
- // test module timeout on internal testing.
- // TODO: if this becomes flaky, consider using default timeout (5000ms) and moving
- // this check into #setUpOnce.
- return requester.getInterface(AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS) != null;
- } catch (TimeoutException e) {
- return false;
- } finally {
- runAsShell(NETWORK_SETTINGS, () -> {
- requester.release();
- });
- }
- }
-
- private void setIncludeTestInterfaces(boolean include) {
- runAsShell(NETWORK_SETTINGS, () -> {
- mEm.setIncludeTestInterfaces(include);
- });
- }
-
- private void setPreferTestNetworks(boolean prefer) {
- runAsShell(NETWORK_SETTINGS, () -> {
- mTm.setPreferTestNetworks(prefer);
- });
- }
-
- private String getTetheredInterface() throws Exception {
- return mTetheredInterfaceRequester.getInterface();
- }
-
- private CompletableFuture<String> requestTetheredInterface() throws Exception {
- return mTetheredInterfaceRequester.requestInterface();
- }
-
- @Test
- public void testVirtualEthernetAlreadyExists() throws Exception {
- // This test requires manipulating packets. Skip if there is a physical Ethernet connected.
- assumeFalse(isInterfaceForTetheringAvailable());
-
- TestNetworkInterface downstreamIface = null;
- MyTetheringEventCallback tetheringEventCallback = null;
- TapPacketReader downstreamReader = null;
-
- try {
- downstreamIface = createTestInterface();
- // This must be done now because as soon as setIncludeTestInterfaces(true) is called,
- // the interface will be placed in client mode, which will delete the link-local
- // address. At that point NetworkInterface.getByName() will cease to work on the
- // interface, because starting in R NetworkInterface can no longer see interfaces
- // without IP addresses.
- int mtu = getMTU(downstreamIface);
-
- Log.d(TAG, "Including test interfaces");
- setIncludeTestInterfaces(true);
-
- final String iface = getTetheredInterface();
- assertEquals("TetheredInterfaceCallback for unexpected interface",
- downstreamIface.getInterfaceName(), iface);
-
- // Check virtual ethernet.
- FileDescriptor fd = downstreamIface.getFileDescriptor().getFileDescriptor();
- downstreamReader = makePacketReader(fd, mtu);
- tetheringEventCallback = enableEthernetTethering(downstreamIface.getInterfaceName(),
- null /* any upstream */);
- checkTetheredClientCallbacks(downstreamReader, tetheringEventCallback);
- } finally {
- maybeStopTapPacketReader(downstreamReader);
- maybeCloseTestInterface(downstreamIface);
- maybeUnregisterTetheringEventCallback(tetheringEventCallback);
- }
- }
-
- @Test
- public void testVirtualEthernet() throws Exception {
- // This test requires manipulating packets. Skip if there is a physical Ethernet connected.
- assumeFalse(isInterfaceForTetheringAvailable());
-
- CompletableFuture<String> futureIface = requestTetheredInterface();
-
- setIncludeTestInterfaces(true);
-
- TestNetworkInterface downstreamIface = null;
- MyTetheringEventCallback tetheringEventCallback = null;
- TapPacketReader downstreamReader = null;
-
- try {
- downstreamIface = createTestInterface();
-
- final String iface = futureIface.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
- assertEquals("TetheredInterfaceCallback for unexpected interface",
- downstreamIface.getInterfaceName(), iface);
-
- // Check virtual ethernet.
- FileDescriptor fd = downstreamIface.getFileDescriptor().getFileDescriptor();
- downstreamReader = makePacketReader(fd, getMTU(downstreamIface));
- tetheringEventCallback = enableEthernetTethering(downstreamIface.getInterfaceName(),
- null /* any upstream */);
- checkTetheredClientCallbacks(downstreamReader, tetheringEventCallback);
- } finally {
- maybeStopTapPacketReader(downstreamReader);
- maybeCloseTestInterface(downstreamIface);
- maybeUnregisterTetheringEventCallback(tetheringEventCallback);
- }
- }
-
- @Test
- public void testStaticIpv4() throws Exception {
- assumeFalse(isInterfaceForTetheringAvailable());
-
- setIncludeTestInterfaces(true);
-
- TestNetworkInterface downstreamIface = null;
- MyTetheringEventCallback tetheringEventCallback = null;
- TapPacketReader downstreamReader = null;
-
- try {
- downstreamIface = createTestInterface();
-
- final String iface = getTetheredInterface();
- assertEquals("TetheredInterfaceCallback for unexpected interface",
- downstreamIface.getInterfaceName(), iface);
-
- assertInvalidStaticIpv4Request(iface, null, null);
- assertInvalidStaticIpv4Request(iface, "2001:db8::1/64", "2001:db8:2::/64");
- assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", "2001:db8:2::/28");
- assertInvalidStaticIpv4Request(iface, "2001:db8:2::/28", "192.0.2.2/28");
- assertInvalidStaticIpv4Request(iface, "192.0.2.2/28", null);
- assertInvalidStaticIpv4Request(iface, null, "192.0.2.2/28");
- assertInvalidStaticIpv4Request(iface, "192.0.2.3/27", "192.0.2.2/28");
-
- final String localAddr = "192.0.2.3/28";
- final String clientAddr = "192.0.2.2/28";
- tetheringEventCallback = enableEthernetTethering(iface,
- requestWithStaticIpv4(localAddr, clientAddr), null /* any upstream */);
-
- tetheringEventCallback.awaitInterfaceTethered();
- assertInterfaceHasIpAddress(iface, localAddr);
-
- byte[] client1 = MacAddress.fromString("1:2:3:4:5:6").toByteArray();
- byte[] client2 = MacAddress.fromString("a:b:c:d:e:f").toByteArray();
-
- FileDescriptor fd = downstreamIface.getFileDescriptor().getFileDescriptor();
- downstreamReader = makePacketReader(fd, getMTU(downstreamIface));
- TetheringTester tester = new TetheringTester(downstreamReader);
- DhcpResults dhcpResults = tester.runDhcp(client1);
- assertEquals(new LinkAddress(clientAddr), dhcpResults.ipAddress);
-
- try {
- tester.runDhcp(client2);
- fail("Only one client should get an IP address");
- } catch (TimeoutException expected) { }
- } finally {
- maybeStopTapPacketReader(downstreamReader);
- maybeCloseTestInterface(downstreamIface);
- maybeUnregisterTetheringEventCallback(tetheringEventCallback);
- }
- }
-
- private static void waitForRouterAdvertisement(TapPacketReader reader, String iface,
- long timeoutMs) {
- final long deadline = SystemClock.uptimeMillis() + timeoutMs;
- do {
- byte[] pkt = reader.popPacket(timeoutMs);
- if (isExpectedIcmpPacket(pkt, true /* hasEth */, false /* isIpv4 */,
- ICMPV6_ROUTER_ADVERTISEMENT)) {
- return;
- }
-
- timeoutMs = deadline - SystemClock.uptimeMillis();
- } while (timeoutMs > 0);
- fail("Did not receive router advertisement on " + iface + " after "
- + timeoutMs + "ms idle");
- }
-
- private static void expectLocalOnlyAddresses(String iface) throws Exception {
- final List<InterfaceAddress> interfaceAddresses =
- NetworkInterface.getByName(iface).getInterfaceAddresses();
-
- boolean foundIpv6Ula = false;
- for (InterfaceAddress ia : interfaceAddresses) {
- final InetAddress addr = ia.getAddress();
- if (isIPv6ULA(addr)) {
- foundIpv6Ula = true;
- }
- final int prefixlen = ia.getNetworkPrefixLength();
- final LinkAddress la = new LinkAddress(addr, prefixlen);
- if (la.isIpv6() && la.isGlobalPreferred()) {
- fail("Found global IPv6 address on local-only interface: " + interfaceAddresses);
- }
- }
-
- assertTrue("Did not find IPv6 ULA on local-only interface " + iface,
- foundIpv6Ula);
- }
-
- @Test
- public void testLocalOnlyTethering() throws Exception {
- assumeFalse(isInterfaceForTetheringAvailable());
-
- setIncludeTestInterfaces(true);
-
- TestNetworkInterface downstreamIface = null;
- MyTetheringEventCallback tetheringEventCallback = null;
- TapPacketReader downstreamReader = null;
-
- try {
- downstreamIface = createTestInterface();
-
- final String iface = getTetheredInterface();
- assertEquals("TetheredInterfaceCallback for unexpected interface",
- downstreamIface.getInterfaceName(), iface);
-
- final TetheringRequest request = new TetheringRequest.Builder(TETHERING_ETHERNET)
- .setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build();
- tetheringEventCallback = enableEthernetTethering(iface, request,
- null /* any upstream */);
- tetheringEventCallback.awaitInterfaceLocalOnly();
-
- // makePacketReader only works after tethering is started, because until then the
- // interface does not have an IP address, and unprivileged apps cannot see interfaces
- // without IP addresses. This shouldn't be flaky because the TAP interface will buffer
- // all packets even before the reader is started.
- downstreamReader = makePacketReader(downstreamIface);
-
- waitForRouterAdvertisement(downstreamReader, iface, WAIT_RA_TIMEOUT_MS);
- expectLocalOnlyAddresses(iface);
- } finally {
- maybeStopTapPacketReader(downstreamReader);
- maybeCloseTestInterface(downstreamIface);
- maybeUnregisterTetheringEventCallback(tetheringEventCallback);
- }
- }
-
- private boolean isAdbOverNetwork() {
- // If adb TCP port opened, this test may running by adb over network.
- return (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1)
- || (SystemProperties.getInt("service.adb.tcp.port", -1) > -1);
- }
-
- @Test
- public void testPhysicalEthernet() throws Exception {
- assumeTrue(isInterfaceForTetheringAvailable());
- // Do not run this test if adb is over network and ethernet is connected.
- // It is likely the adb run over ethernet, the adb would break when ethernet is switching
- // from client mode to server mode. See b/160389275.
- assumeFalse(isAdbOverNetwork());
-
- MyTetheringEventCallback tetheringEventCallback = null;
- try {
- // Get an interface to use.
- final String iface = getTetheredInterface();
-
- // Enable Ethernet tethering and check that it starts.
- tetheringEventCallback = enableEthernetTethering(iface, null /* any upstream */);
- } finally {
- maybeUnregisterTetheringEventCallback(tetheringEventCallback);
- }
- // There is nothing more we can do on a physical interface without connecting an actual
- // client, which is not possible in this test.
- }
-
- private static final class MyTetheringEventCallback implements TetheringEventCallback {
- private final TetheringManager mTm;
- private final CountDownLatch mTetheringStartedLatch = new CountDownLatch(1);
- private final CountDownLatch mTetheringStoppedLatch = new CountDownLatch(1);
- private final CountDownLatch mLocalOnlyStartedLatch = new CountDownLatch(1);
- private final CountDownLatch mLocalOnlyStoppedLatch = new CountDownLatch(1);
- private final CountDownLatch mClientConnectedLatch = new CountDownLatch(1);
- private final CountDownLatch mUpstreamLatch = new CountDownLatch(1);
- private final CountDownLatch mCallbackRegisteredLatch = new CountDownLatch(1);
- private final TetheringInterface mIface;
- private final Network mExpectedUpstream;
-
- private boolean mAcceptAnyUpstream = false;
-
- private volatile boolean mInterfaceWasTethered = false;
- private volatile boolean mInterfaceWasLocalOnly = false;
- private volatile boolean mUnregistered = false;
- private volatile Collection<TetheredClient> mClients = null;
- private volatile Network mUpstream = null;
-
- MyTetheringEventCallback(TetheringManager tm, String iface) {
- this(tm, iface, null);
- mAcceptAnyUpstream = true;
- }
-
- MyTetheringEventCallback(TetheringManager tm, String iface, Network expectedUpstream) {
- mTm = tm;
- mIface = new TetheringInterface(TETHERING_ETHERNET, iface);
- mExpectedUpstream = expectedUpstream;
- }
-
- public void unregister() {
- mTm.unregisterTetheringEventCallback(this);
- mUnregistered = true;
- }
- @Override
- public void onTetheredInterfacesChanged(List<String> interfaces) {
- fail("Should only call callback that takes a Set<TetheringInterface>");
- }
-
- @Override
- public void onTetheredInterfacesChanged(Set<TetheringInterface> interfaces) {
- // Ignore stale callbacks registered by previous test cases.
- if (mUnregistered) return;
-
- if (!mInterfaceWasTethered && interfaces.contains(mIface)) {
- // This interface is being tethered for the first time.
- Log.d(TAG, "Tethering started: " + interfaces);
- mInterfaceWasTethered = true;
- mTetheringStartedLatch.countDown();
- } else if (mInterfaceWasTethered && !interfaces.contains(mIface)) {
- Log.d(TAG, "Tethering stopped: " + interfaces);
- mTetheringStoppedLatch.countDown();
- }
- }
-
- @Override
- public void onLocalOnlyInterfacesChanged(List<String> interfaces) {
- fail("Should only call callback that takes a Set<TetheringInterface>");
- }
-
- @Override
- public void onLocalOnlyInterfacesChanged(Set<TetheringInterface> interfaces) {
- // Ignore stale callbacks registered by previous test cases.
- if (mUnregistered) return;
-
- if (!mInterfaceWasLocalOnly && interfaces.contains(mIface)) {
- // This interface is being put into local-only mode for the first time.
- Log.d(TAG, "Local-only started: " + interfaces);
- mInterfaceWasLocalOnly = true;
- mLocalOnlyStartedLatch.countDown();
- } else if (mInterfaceWasLocalOnly && !interfaces.contains(mIface)) {
- Log.d(TAG, "Local-only stopped: " + interfaces);
- mLocalOnlyStoppedLatch.countDown();
- }
- }
-
- public void awaitInterfaceTethered() throws Exception {
- assertTrue("Ethernet not tethered after " + TIMEOUT_MS + "ms",
- mTetheringStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
-
- public void awaitInterfaceLocalOnly() throws Exception {
- assertTrue("Ethernet not local-only after " + TIMEOUT_MS + "ms",
- mLocalOnlyStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- }
-
- // Used to check if the callback has registered. When the callback is registered,
- // onSupportedTetheringTypes is celled in onCallbackStarted(). After
- // onSupportedTetheringTypes called, drop the permission for registering callback.
- // See MyTetheringEventCallback#register, TetheringManager#onCallbackStarted.
- @Override
- public void onSupportedTetheringTypes(Set<Integer> supportedTypes) {
- // Used to check callback registered.
- mCallbackRegisteredLatch.countDown();
- }
-
- public void awaitCallbackRegistered() throws Exception {
- if (!mCallbackRegisteredLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- fail("Did not receive callback registered signal after " + TIMEOUT_MS + "ms");
- }
- }
-
- public void awaitInterfaceUntethered() throws Exception {
- // Don't block teardown if the interface was never tethered.
- // This is racy because the interface might become tethered right after this check, but
- // that can only happen in tearDown if startTethering timed out, which likely means
- // the test has already failed.
- if (!mInterfaceWasTethered && !mInterfaceWasLocalOnly) return;
-
- if (mInterfaceWasTethered) {
- assertTrue(mIface + " not untethered after " + TIMEOUT_MS + "ms",
- mTetheringStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } else if (mInterfaceWasLocalOnly) {
- assertTrue(mIface + " not untethered after " + TIMEOUT_MS + "ms",
- mLocalOnlyStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } else {
- fail(mIface + " cannot be both tethered and local-only. Update this test class.");
- }
- }
-
- @Override
- public void onError(String ifName, int error) {
- // Ignore stale callbacks registered by previous test cases.
- if (mUnregistered) return;
-
- fail("TetheringEventCallback got error:" + error + " on iface " + ifName);
- }
-
- @Override
- public void onClientsChanged(Collection<TetheredClient> clients) {
- // Ignore stale callbacks registered by previous test cases.
- if (mUnregistered) return;
-
- Log.d(TAG, "Got clients changed: " + clients);
- mClients = clients;
- if (clients.size() > 0) {
- mClientConnectedLatch.countDown();
- }
- }
-
- public Collection<TetheredClient> awaitClientConnected() throws Exception {
- assertTrue("Did not receive client connected callback after " + TIMEOUT_MS + "ms",
- mClientConnectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- return mClients;
- }
-
- @Override
- public void onUpstreamChanged(Network network) {
- // Ignore stale callbacks registered by previous test cases.
- if (mUnregistered) return;
-
- Log.d(TAG, "Got upstream changed: " + network);
- mUpstream = network;
- if (mAcceptAnyUpstream || Objects.equals(mUpstream, mExpectedUpstream)) {
- mUpstreamLatch.countDown();
- }
- }
-
- public Network awaitUpstreamChanged(boolean throwTimeoutException) throws Exception {
- if (!mUpstreamLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- final String errorMessage = "Did not receive upstream "
- + (mAcceptAnyUpstream ? "any" : mExpectedUpstream)
- + " callback after " + TIMEOUT_MS + "ms";
-
- if (throwTimeoutException) {
- throw new TimeoutException(errorMessage);
- } else {
- fail(errorMessage);
- }
- }
- return mUpstream;
- }
- }
-
- private MyTetheringEventCallback enableEthernetTethering(String iface,
- TetheringRequest request, Network expectedUpstream) throws Exception {
- // Enable ethernet tethering with null expectedUpstream means the test accept any upstream
- // after etherent tethering started.
- final MyTetheringEventCallback callback;
- if (expectedUpstream != null) {
- callback = new MyTetheringEventCallback(mTm, iface, expectedUpstream);
- } else {
- callback = new MyTetheringEventCallback(mTm, iface);
- }
- runAsShell(NETWORK_SETTINGS, () -> {
- mTm.registerTetheringEventCallback(mHandler::post, callback);
- // Need to hold the shell permission until callback is registered. This helps to avoid
- // the test become flaky.
- callback.awaitCallbackRegistered();
- });
- final CountDownLatch tetheringStartedLatch = new CountDownLatch(1);
- StartTetheringCallback startTetheringCallback = new StartTetheringCallback() {
- @Override
- public void onTetheringStarted() {
- Log.d(TAG, "Ethernet tethering started");
- tetheringStartedLatch.countDown();
- }
-
- @Override
- public void onTetheringFailed(int resultCode) {
- fail("Unexpectedly got onTetheringFailed");
- }
- };
- Log.d(TAG, "Starting Ethernet tethering");
- runAsShell(TETHER_PRIVILEGED, () -> {
- mTm.startTethering(request, mHandler::post /* executor */, startTetheringCallback);
- // Binder call is an async call. Need to hold the shell permission until tethering
- // started. This helps to avoid the test become flaky.
- if (!tetheringStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- fail("Did not receive tethering started callback after " + TIMEOUT_MS + "ms");
- }
- });
-
- final int connectivityType = request.getConnectivityScope();
- switch (connectivityType) {
- case CONNECTIVITY_SCOPE_GLOBAL:
- callback.awaitInterfaceTethered();
- break;
- case CONNECTIVITY_SCOPE_LOCAL:
- callback.awaitInterfaceLocalOnly();
- break;
- default:
- fail("Unexpected connectivity type requested: " + connectivityType);
- }
-
- return callback;
- }
-
- private MyTetheringEventCallback enableEthernetTethering(String iface, Network expectedUpstream)
- throws Exception {
- return enableEthernetTethering(iface,
- new TetheringRequest.Builder(TETHERING_ETHERNET)
- .setShouldShowEntitlementUi(false).build(), expectedUpstream);
- }
-
- private int getMTU(TestNetworkInterface iface) throws SocketException {
- NetworkInterface nif = NetworkInterface.getByName(iface.getInterfaceName());
- assertNotNull("Can't get NetworkInterface object for " + iface.getInterfaceName(), nif);
- return nif.getMTU();
- }
-
- private TapPacketReader makePacketReader(final TestNetworkInterface iface) throws Exception {
- FileDescriptor fd = iface.getFileDescriptor().getFileDescriptor();
- return makePacketReader(fd, getMTU(iface));
- }
-
- private TapPacketReader makePacketReader(FileDescriptor fd, int mtu) {
- final TapPacketReader reader = new TapPacketReader(mHandler, fd, mtu);
- mHandler.post(() -> reader.start());
- HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
- return reader;
- }
-
- private void checkTetheredClientCallbacks(final TapPacketReader packetReader,
- final MyTetheringEventCallback tetheringEventCallback) throws Exception {
- // Create a fake client.
- byte[] clientMacAddr = new byte[6];
- new Random().nextBytes(clientMacAddr);
-
- TetheringTester tester = new TetheringTester(packetReader);
- DhcpResults dhcpResults = tester.runDhcp(clientMacAddr);
-
- final Collection<TetheredClient> clients = tetheringEventCallback.awaitClientConnected();
- assertEquals(1, clients.size());
- final TetheredClient client = clients.iterator().next();
-
- // Check the MAC address.
- assertEquals(MacAddress.fromBytes(clientMacAddr), client.getMacAddress());
- assertEquals(TETHERING_ETHERNET, client.getTetheringType());
-
- // Check the hostname.
- assertEquals(1, client.getAddresses().size());
- TetheredClient.AddressInfo info = client.getAddresses().get(0);
- assertEquals(TetheringTester.DHCP_HOSTNAME, info.getHostname());
-
- // Check the address is the one that was handed out in the DHCP ACK.
- assertLinkAddressMatches(dhcpResults.ipAddress, info.getAddress());
-
- // Check that the lifetime is correct +/- 10s.
- final long now = SystemClock.elapsedRealtime();
- final long actualLeaseDuration = (info.getAddress().getExpirationTime() - now) / 1000;
- final String msg = String.format("IP address should have lifetime of %d, got %d",
- dhcpResults.leaseDuration, actualLeaseDuration);
- assertTrue(msg, Math.abs(dhcpResults.leaseDuration - actualLeaseDuration) < 10);
- }
-
- private static final class TetheredInterfaceRequester implements TetheredInterfaceCallback {
- private final Handler mHandler;
- private final EthernetManager mEm;
-
- private TetheredInterfaceRequest mRequest;
- private final CompletableFuture<String> mFuture = new CompletableFuture<>();
-
- TetheredInterfaceRequester(Handler handler, EthernetManager em) {
- mHandler = handler;
- mEm = em;
- }
-
- @Override
- public void onAvailable(String iface) {
- Log.d(TAG, "Ethernet interface available: " + iface);
- mFuture.complete(iface);
- }
-
- @Override
- public void onUnavailable() {
- mFuture.completeExceptionally(new IllegalStateException("onUnavailable received"));
- }
-
- public CompletableFuture<String> requestInterface() {
- assertNull("BUG: more than one tethered interface request", mRequest);
- Log.d(TAG, "Requesting tethered interface");
- mRequest = runAsShell(NETWORK_SETTINGS, () ->
- mEm.requestTetheredInterface(mHandler::post, this));
- return mFuture;
- }
-
- public String getInterface(int timeout) throws Exception {
- return requestInterface().get(timeout, TimeUnit.MILLISECONDS);
- }
-
- public String getInterface() throws Exception {
- return getInterface(TIMEOUT_MS);
- }
-
- public void release() {
- if (mRequest != null) {
- mFuture.obtrudeException(new IllegalStateException("Request already released"));
- mRequest.release();
- mRequest = null;
- }
- }
- }
-
- public void assertLinkAddressMatches(LinkAddress l1, LinkAddress l2) {
- // Check all fields except the deprecation and expiry times.
- String msg = String.format("LinkAddresses do not match. expected: %s actual: %s", l1, l2);
- assertTrue(msg, l1.isSameAddressAs(l2));
- assertEquals("LinkAddress flags do not match", l1.getFlags(), l2.getFlags());
- assertEquals("LinkAddress scope does not match", l1.getScope(), l2.getScope());
- }
-
- private TetheringRequest requestWithStaticIpv4(String local, String client) {
- LinkAddress localAddr = local == null ? null : new LinkAddress(local);
- LinkAddress clientAddr = client == null ? null : new LinkAddress(client);
- return new TetheringRequest.Builder(TETHERING_ETHERNET)
- .setStaticIpv4Addresses(localAddr, clientAddr)
- .setShouldShowEntitlementUi(false).build();
- }
-
- private void assertInvalidStaticIpv4Request(String iface, String local, String client)
- throws Exception {
- try {
- enableEthernetTethering(iface, requestWithStaticIpv4(local, client),
- null /* any upstream */);
- fail("Unexpectedly accepted invalid IPv4 configuration: " + local + ", " + client);
- } catch (IllegalArgumentException | NullPointerException expected) { }
- }
-
- private void assertInterfaceHasIpAddress(String iface, String expected) throws Exception {
- LinkAddress expectedAddr = new LinkAddress(expected);
- NetworkInterface nif = NetworkInterface.getByName(iface);
- for (InterfaceAddress ia : nif.getInterfaceAddresses()) {
- final LinkAddress addr = new LinkAddress(ia.getAddress(), ia.getNetworkPrefixLength());
- if (expectedAddr.equals(addr)) {
- return;
- }
- }
- fail("Expected " + iface + " to have IP address " + expected + ", found "
- + nif.getInterfaceAddresses());
- }
-
- private TestNetworkInterface createTestInterface() throws Exception {
- TestNetworkManager tnm = runAsShell(MANAGE_TEST_NETWORKS, () ->
- mContext.getSystemService(TestNetworkManager.class));
- TestNetworkInterface iface = runAsShell(MANAGE_TEST_NETWORKS, () ->
- tnm.createTapInterface());
- Log.d(TAG, "Created test interface " + iface.getInterfaceName());
- return iface;
- }
-
- private TestNetworkTracker createTestUpstream(final List<LinkAddress> addresses,
- final List<InetAddress> dnses) throws Exception {
- setPreferTestNetworks(true);
-
- final LinkProperties lp = new LinkProperties();
- lp.setLinkAddresses(addresses);
- lp.setDnsServers(dnses);
- lp.setNat64Prefix(TEST_NAT64PREFIX);
-
- return runAsShell(MANAGE_TEST_NETWORKS, () -> initTestNetwork(mContext, lp, TIMEOUT_MS));
- }
-
- @Test
- public void testIcmpv6Echo() throws Exception {
- runPing6Test(initTetheringTester(toList(TEST_IP4_ADDR, TEST_IP6_ADDR),
- toList(TEST_IP4_DNS, TEST_IP6_DNS)));
- }
-
- private void runPing6Test(TetheringTester tester) throws Exception {
- TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
- Inet6Address remoteIp6Addr = (Inet6Address) parseNumericAddress("2400:222:222::222");
- ByteBuffer request = Ipv6Utils.buildEchoRequestPacket(tethered.macAddr,
- tethered.routerMacAddr, tethered.ipv6Addr, remoteIp6Addr);
- tester.verifyUpload(request, p -> {
- Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
-
- return isExpectedIcmpPacket(p, false /* hasEth */, false /* isIpv4 */,
- ICMPV6_ECHO_REQUEST_TYPE);
- });
-
- ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(remoteIp6Addr, tethered.ipv6Addr);
- tester.verifyDownload(reply, p -> {
- Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
-
- return isExpectedIcmpPacket(p, true /* hasEth */, false /* isIpv4 */,
- ICMPV6_ECHO_REPLY_TYPE);
- });
- }
-
- // Test network topology:
- //
- // public network (rawip) private network
- // | UE |
- // +------------+ V +------------+------------+ V +------------+
- // | Sever +---------+ Upstream | Downstream +---------+ Client |
- // +------------+ +------------+------------+ +------------+
- // remote ip public ip private ip
- // 8.8.8.8:443 <Upstream ip>:9876 <TetheredDevice ip>:9876
- //
-
- private short getEthType(@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp) {
- return isAddressIpv4(srcIp, dstIp) ? (short) ETHER_TYPE_IPV4 : (short) ETHER_TYPE_IPV6;
- }
-
- private int getIpProto(@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp) {
- return isAddressIpv4(srcIp, dstIp) ? IPPROTO_IP : IPPROTO_IPV6;
- }
-
- @NonNull
- private ByteBuffer buildUdpPacket(
- @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
- @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp,
- short srcPort, short dstPort, @Nullable final ByteBuffer payload)
- throws Exception {
- final int ipProto = getIpProto(srcIp, dstIp);
- final boolean hasEther = (srcMac != null && dstMac != null);
- final int payloadLen = (payload == null) ? 0 : payload.limit();
- final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_UDP,
- payloadLen);
- final PacketBuilder packetBuilder = new PacketBuilder(buffer);
-
- // [1] Ethernet header
- if (hasEther) {
- packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp));
- }
-
- // [2] IP header
- if (ipProto == IPPROTO_IP) {
- packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
- TIME_TO_LIVE, (byte) IPPROTO_UDP, (Inet4Address) srcIp, (Inet4Address) dstIp);
- } else {
- packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_UDP,
- HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp);
- }
-
- // [3] UDP header
- packetBuilder.writeUdpHeader(srcPort, dstPort);
-
- // [4] Payload
- if (payload != null) {
- buffer.put(payload);
- // in case data might be reused by caller, restore the position and
- // limit of bytebuffer.
- payload.clear();
- }
-
- return packetBuilder.finalizePacket();
- }
-
- @NonNull
- private ByteBuffer buildUdpPacket(@NonNull final InetAddress srcIp,
- @NonNull final InetAddress dstIp, short srcPort, short dstPort,
- @Nullable final ByteBuffer payload) throws Exception {
- return buildUdpPacket(null /* srcMac */, null /* dstMac */, srcIp, dstIp, srcPort,
- dstPort, payload);
- }
-
- private boolean isAddressIpv4(@NonNull final InetAddress srcIp,
- @NonNull final InetAddress dstIp) {
- if (srcIp instanceof Inet4Address && dstIp instanceof Inet4Address) return true;
- if (srcIp instanceof Inet6Address && dstIp instanceof Inet6Address) return false;
-
- fail("Unsupported conditions: srcIp " + srcIp + ", dstIp " + dstIp);
- return false; // unreachable
- }
-
- private void sendDownloadPacketUdp(@NonNull final InetAddress srcIp,
- @NonNull final InetAddress dstIp, @NonNull final TetheringTester tester,
- boolean is6To4) throws Exception {
- if (is6To4) {
- assertFalse("CLAT download test must sends IPv6 packet", isAddressIpv4(srcIp, dstIp));
- }
-
- // Expected received UDP packet IP protocol. While testing CLAT (is6To4 = true), the packet
- // on downstream must be IPv4. Otherwise, the IP protocol of test packet is the same on
- // both downstream and upstream.
- final boolean isIpv4 = is6To4 ? true : isAddressIpv4(srcIp, dstIp);
-
- final ByteBuffer testPacket = buildUdpPacket(srcIp, dstIp, REMOTE_PORT /* srcPort */,
- LOCAL_PORT /* dstPort */, RX_PAYLOAD);
- tester.verifyDownload(testPacket, p -> {
- Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, true /* hasEther */, isIpv4, RX_PAYLOAD);
- });
- }
-
- private void sendUploadPacketUdp(@NonNull final MacAddress srcMac,
- @NonNull final MacAddress dstMac, @NonNull final InetAddress srcIp,
- @NonNull final InetAddress dstIp, @NonNull final TetheringTester tester,
- boolean is4To6) throws Exception {
- if (is4To6) {
- assertTrue("CLAT upload test must sends IPv4 packet", isAddressIpv4(srcIp, dstIp));
- }
-
- // Expected received UDP packet IP protocol. While testing CLAT (is4To6 = true), the packet
- // on upstream must be IPv6. Otherwise, the IP protocol of test packet is the same on
- // both downstream and upstream.
- final boolean isIpv4 = is4To6 ? false : isAddressIpv4(srcIp, dstIp);
-
- final ByteBuffer testPacket = buildUdpPacket(srcMac, dstMac, srcIp, dstIp,
- LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */, TX_PAYLOAD);
- tester.verifyUpload(testPacket, p -> {
- Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedUdpPacket(p, false /* hasEther */, isIpv4, TX_PAYLOAD);
- });
- }
-
- @Test
- public void testTetherUdpV6() throws Exception {
- final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
- toList(TEST_IP6_DNS));
- final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
- sendUploadPacketUdp(tethered.macAddr, tethered.routerMacAddr,
- tethered.ipv6Addr, REMOTE_IP6_ADDR, tester, false /* is4To6 */);
- sendDownloadPacketUdp(REMOTE_IP6_ADDR, tethered.ipv6Addr, tester, false /* is6To4 */);
-
- // TODO: test BPF offload maps {rule, stats}.
- }
-
- // TODO: remove ipv4 verification (is4To6 = false) once upstream connected notification race is
- // fixed. See #runUdp4Test.
- //
- // This function sends a probe packet to downstream interface and exam the result from upstream
- // interface to make sure ipv4 tethering is ready. Return the entire packet which received from
- // upstream interface.
- @NonNull
- private byte[] probeV4TetheringConnectivity(TetheringTester tester, TetheredDevice tethered,
- boolean is4To6) throws Exception {
- final ByteBuffer probePacket = buildUdpPacket(tethered.macAddr,
- tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
- REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
- TEST_REACHABILITY_PAYLOAD);
-
- // Send a UDP packet from client and check the packet can be found on upstream interface.
- for (int i = 0; i < TETHER_REACHABILITY_ATTEMPTS; i++) {
- byte[] expectedPacket = tester.testUpload(probePacket, p -> {
- Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- // If is4To6 is true, the ipv4 probe packet would be translated to ipv6 by Clat and
- // would see this translated ipv6 packet in upstream interface.
- return isExpectedUdpPacket(p, false /* hasEther */, !is4To6 /* isIpv4 */,
- TEST_REACHABILITY_PAYLOAD);
- });
- if (expectedPacket != null) return expectedPacket;
- }
-
- fail("Can't verify " + (is4To6 ? "ipv4 to ipv6" : "ipv4") + " tethering connectivity after "
- + TETHER_REACHABILITY_ATTEMPTS + " attempts");
- return null;
- }
-
- private void runUdp4Test(boolean verifyBpf) throws Exception {
- final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
- toList(TEST_IP4_DNS));
- final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
-
- // TODO: remove the connectivity verification for upstream connected notification race.
- // Because async upstream connected notification can't guarantee the tethering routing is
- // ready to use. Need to test tethering connectivity before testing.
- // For short term plan, consider using IPv6 RA to get MAC address because the prefix comes
- // from upstream. That can guarantee that the routing is ready. Long term plan is that
- // refactors upstream connected notification from async to sync.
- probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
-
- final MacAddress srcMac = tethered.macAddr;
- final MacAddress dstMac = tethered.routerMacAddr;
- final InetAddress remoteIp = REMOTE_IP4_ADDR;
- final InetAddress tetheringUpstreamIp = TEST_IP4_ADDR.getAddress();
- final InetAddress clientIp = tethered.ipv4Addr;
- sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
- sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
-
- if (verifyBpf) {
- // Send second UDP packet in original direction.
- // The BPF coordinator only offloads the ASSURED conntrack entry. The "request + reply"
- // packets can make status IPS_SEEN_REPLY to be set. Need one more packet to make
- // conntrack status IPS_ASSURED_BIT to be set. Note the third packet needs to delay
- // 2 seconds because kernel monitors a UDP connection which still alive after 2 seconds
- // and apply ASSURED flag.
- // See kernel upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5 and
- // nf_conntrack_udp_packet in net/netfilter/nf_conntrack_proto_udp.c
- Thread.sleep(UDP_STREAM_TS_MS);
- sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester, false /* is4To6 */);
-
- // Give a slack time for handling conntrack event in user space.
- Thread.sleep(UDP_STREAM_SLACK_MS);
-
- // [1] Verify IPv4 upstream rule map.
- final HashMap<Tether4Key, Tether4Value> upstreamMap = pollRawMapFromDump(
- Tether4Key.class, Tether4Value.class, DUMPSYS_RAWMAP_ARG_UPSTREAM4);
- assertNotNull(upstreamMap);
- assertEquals(1, upstreamMap.size());
-
- final Map.Entry<Tether4Key, Tether4Value> rule =
- upstreamMap.entrySet().iterator().next();
-
- final Tether4Key upstream4Key = rule.getKey();
- assertEquals(IPPROTO_UDP, upstream4Key.l4proto);
- assertTrue(Arrays.equals(tethered.ipv4Addr.getAddress(), upstream4Key.src4));
- assertEquals(LOCAL_PORT, upstream4Key.srcPort);
- assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(), upstream4Key.dst4));
- assertEquals(REMOTE_PORT, upstream4Key.dstPort);
-
- final Tether4Value upstream4Value = rule.getValue();
- assertTrue(Arrays.equals(tetheringUpstreamIp.getAddress(),
- InetAddress.getByAddress(upstream4Value.src46).getAddress()));
- assertEquals(LOCAL_PORT, upstream4Value.srcPort);
- assertTrue(Arrays.equals(REMOTE_IP4_ADDR.getAddress(),
- InetAddress.getByAddress(upstream4Value.dst46).getAddress()));
- assertEquals(REMOTE_PORT, upstream4Value.dstPort);
-
- // [2] Verify stats map.
- // Transmit packets on both direction for verifying stats. Because we only care the
- // packet count in stats test, we just reuse the existing packets to increaes
- // the packet count on both direction.
-
- // Send packets on original direction.
- for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) {
- sendUploadPacketUdp(srcMac, dstMac, clientIp, remoteIp, tester,
- false /* is4To6 */);
- }
-
- // Send packets on reply direction.
- for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) {
- sendDownloadPacketUdp(remoteIp, tetheringUpstreamIp, tester, false /* is6To4 */);
- }
-
- // Dump stats map to verify.
- final HashMap<TetherStatsKey, TetherStatsValue> statsMap = pollRawMapFromDump(
- TetherStatsKey.class, TetherStatsValue.class, DUMPSYS_RAWMAP_ARG_STATS);
- assertNotNull(statsMap);
- assertEquals(1, statsMap.size());
-
- final Map.Entry<TetherStatsKey, TetherStatsValue> stats =
- statsMap.entrySet().iterator().next();
-
- // TODO: verify the upstream index in TetherStatsKey.
-
- final TetherStatsValue statsValue = stats.getValue();
- assertEquals(RX_UDP_PACKET_COUNT, statsValue.rxPackets);
- assertEquals(RX_UDP_PACKET_COUNT * RX_UDP_PACKET_SIZE, statsValue.rxBytes);
- assertEquals(0, statsValue.rxErrors);
- assertEquals(TX_UDP_PACKET_COUNT, statsValue.txPackets);
- assertEquals(TX_UDP_PACKET_COUNT * TX_UDP_PACKET_SIZE, statsValue.txBytes);
- assertEquals(0, statsValue.txErrors);
- }
- }
-
- // TODO: remove triggering upstream reselection once test network can replace selected upstream
- // network in Tethering module.
- private void maybeRetryTestedUpstreamChanged(final Network expectedUpstream,
- final TimeoutException fallbackException) throws Exception {
- // Fall back original exception because no way to reselect if there is no WIFI feature.
- assertTrue(fallbackException.toString(), mPackageManager.hasSystemFeature(FEATURE_WIFI));
-
- // Try to toggle wifi network, if any, to reselect upstream network via default network
- // switching. Because test network has higher priority than internet network, this can
- // help selecting test network to be upstream network for testing. This tries to avoid
- // the flaky upstream selection under multinetwork environment. Internet and test network
- // upstream changed event order is not guaranteed. Once tethering selects non-test
- // upstream {wifi, ..}, test network won't be selected anymore. If too many test cases
- // trigger the reselection, the total test time may over test suite 1 minmute timeout.
- // Probably need to disable/restore all internet networks in a common place of test
- // process. Currently, EthernetTetheringTest is part of CTS test which needs wifi network
- // connection if device has wifi feature. CtsNetUtils#toggleWifi() checks wifi connection
- // during the toggling process.
- // See Tethering#chooseUpstreamType, CtsNetUtils#toggleWifi.
- // TODO: toggle cellular network if the device has no WIFI feature.
- Log.d(TAG, "Toggle WIFI to retry upstream selection");
- mCtsNetUtils.toggleWifi();
-
- // Wait for expected upstream.
- final CompletableFuture<Network> future = new CompletableFuture<>();
- final TetheringEventCallback callback = new TetheringEventCallback() {
- @Override
- public void onUpstreamChanged(Network network) {
- Log.d(TAG, "Got upstream changed: " + network);
- if (Objects.equals(expectedUpstream, network)) {
- future.complete(network);
- }
- }
- };
- try {
- mTm.registerTetheringEventCallback(mHandler::post, callback);
- assertEquals("onUpstreamChanged for unexpected network", expectedUpstream,
- future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } catch (TimeoutException e) {
- throw new AssertionError("Did not receive upstream " + expectedUpstream
- + " callback after " + TIMEOUT_MS + "ms");
- } finally {
- mTm.unregisterTetheringEventCallback(callback);
- }
- }
-
- private TetheringTester initTetheringTester(List<LinkAddress> upstreamAddresses,
- List<InetAddress> upstreamDnses) throws Exception {
- assumeFalse(isInterfaceForTetheringAvailable());
-
- // MyTetheringEventCallback currently only support await first available upstream. Tethering
- // may select internet network as upstream if test network is not available and not be
- // preferred yet. Create test upstream network before enable tethering.
- mUpstreamTracker = createTestUpstream(upstreamAddresses, upstreamDnses);
-
- mDownstreamIface = createTestInterface();
- setIncludeTestInterfaces(true);
-
- // Make sure EtherentTracker use "mDownstreamIface" as server mode interface.
- assertEquals("TetheredInterfaceCallback for unexpected interface",
- mDownstreamIface.getInterfaceName(), mTetheredInterfaceRequester.getInterface());
-
- mTetheringEventCallback = enableEthernetTethering(mDownstreamIface.getInterfaceName(),
- mUpstreamTracker.getNetwork());
-
- try {
- assertEquals("onUpstreamChanged for test network", mUpstreamTracker.getNetwork(),
- mTetheringEventCallback.awaitUpstreamChanged(
- true /* throwTimeoutException */));
- } catch (TimeoutException e) {
- // Due to race condition inside tethering module, test network may not be selected as
- // tethering upstream. Force tethering retry upstream if possible. If it is not
- // possible to retry, fail the test with the original timeout exception.
- maybeRetryTestedUpstreamChanged(mUpstreamTracker.getNetwork(), e);
- }
-
- mDownstreamReader = makePacketReader(mDownstreamIface);
- mUpstreamReader = makePacketReader(mUpstreamTracker.getTestIface());
-
- final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
- // Currently tethering don't have API to tell when ipv6 tethering is available. Thus, make
- // sure tethering already have ipv6 connectivity before testing.
- if (cm.getLinkProperties(mUpstreamTracker.getNetwork()).hasGlobalIpv6Address()) {
- waitForRouterAdvertisement(mDownstreamReader, mDownstreamIface.getInterfaceName(),
- WAIT_RA_TIMEOUT_MS);
- }
-
- return new TetheringTester(mDownstreamReader, mUpstreamReader);
- }
-
- private static boolean isUdpOffloadSupportedByKernel(final String kernelVersion) {
- final KVersion current = DeviceInfoUtils.getMajorMinorSubminorVersion(kernelVersion);
- return current.isInRange(new KVersion(4, 14, 222), new KVersion(4, 19, 0))
- || current.isInRange(new KVersion(4, 19, 176), new KVersion(5, 4, 0))
- || current.isAtLeast(new KVersion(5, 4, 98));
- }
-
- @Test
- public void testIsUdpOffloadSupportedByKernel() throws Exception {
- assertFalse(isUdpOffloadSupportedByKernel("4.14.221"));
- assertTrue(isUdpOffloadSupportedByKernel("4.14.222"));
- assertTrue(isUdpOffloadSupportedByKernel("4.16.0"));
- assertTrue(isUdpOffloadSupportedByKernel("4.18.0"));
- assertFalse(isUdpOffloadSupportedByKernel("4.19.0"));
-
- assertFalse(isUdpOffloadSupportedByKernel("4.19.175"));
- assertTrue(isUdpOffloadSupportedByKernel("4.19.176"));
- assertTrue(isUdpOffloadSupportedByKernel("5.2.0"));
- assertTrue(isUdpOffloadSupportedByKernel("5.3.0"));
- assertFalse(isUdpOffloadSupportedByKernel("5.4.0"));
-
- assertFalse(isUdpOffloadSupportedByKernel("5.4.97"));
- assertTrue(isUdpOffloadSupportedByKernel("5.4.98"));
- assertTrue(isUdpOffloadSupportedByKernel("5.10.0"));
- }
-
- private static void assumeKernelSupportBpfOffloadUdpV4() {
- final String kernelVersion = VintfRuntimeInfo.getKernelVersion();
- assumeTrue("Kernel version " + kernelVersion + " doesn't support IPv4 UDP BPF offload",
- isUdpOffloadSupportedByKernel(kernelVersion));
- }
-
- @Test
- public void testKernelSupportBpfOffloadUdpV4() throws Exception {
- assumeKernelSupportBpfOffloadUdpV4();
- }
-
- @Test
- public void testTetherConfigBpfOffloadEnabled() throws Exception {
- assumeTrue(isTetherConfigBpfOffloadEnabled());
- }
-
- /**
- * Basic IPv4 UDP tethering test. Verify that UDP tethered packets are transferred no matter
- * using which data path.
- */
- @Test
- public void testTetherUdpV4() throws Exception {
- runUdp4Test(false /* verifyBpf */);
- }
-
- /**
- * BPF offload IPv4 UDP tethering test. Verify that UDP tethered packets are offloaded by BPF.
- * Minimum test requirement:
- * 1. S+ device.
- * 2. Tethering config enables tethering BPF offload.
- * 3. Kernel supports IPv4 UDP BPF offload. See #isUdpOffloadSupportedByKernel.
- *
- * TODO: consider enabling the test even tethering config disables BPF offload. See b/238288883
- */
- @Test
- @IgnoreUpTo(Build.VERSION_CODES.R)
- public void testTetherUdpV4_VerifyBpf() throws Exception {
- assumeTrue("Tethering config disabled BPF offload", isTetherConfigBpfOffloadEnabled());
- assumeKernelSupportBpfOffloadUdpV4();
-
- runUdp4Test(true /* verifyBpf */);
- }
-
- @NonNull
- private <K extends Struct, V extends Struct> HashMap<K, V> dumpAndParseRawMap(
- Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
- throws Exception {
- final String[] args = new String[] {DUMPSYS_TETHERING_RAWMAP_ARG, mapArg};
- final String rawMapStr = runAsShell(DUMP, () ->
- DumpTestUtils.dumpService(Context.TETHERING_SERVICE, args));
- final HashMap<K, V> map = new HashMap<>();
-
- for (final String line : rawMapStr.split(LINE_DELIMITER)) {
- final Pair<K, V> rule =
- BpfDump.fromBase64EncodedString(keyClass, valueClass, line.trim());
- map.put(rule.first, rule.second);
- }
- return map;
- }
-
- @Nullable
- private <K extends Struct, V extends Struct> HashMap<K, V> pollRawMapFromDump(
- Class<K> keyClass, Class<V> valueClass, @NonNull String mapArg)
- throws Exception {
- for (int retryCount = 0; retryCount < DUMP_POLLING_MAX_RETRY; retryCount++) {
- final HashMap<K, V> map = dumpAndParseRawMap(keyClass, valueClass, mapArg);
- if (!map.isEmpty()) return map;
-
- Thread.sleep(DUMP_POLLING_INTERVAL_MS);
- }
-
- fail("Cannot get rules after " + DUMP_POLLING_MAX_RETRY * DUMP_POLLING_INTERVAL_MS + "ms");
- return null;
- }
-
- private boolean isTetherConfigBpfOffloadEnabled() throws Exception {
- final String dumpStr = runAsShell(DUMP, () ->
- DumpTestUtils.dumpService(Context.TETHERING_SERVICE, "--short"));
-
- // BPF offload tether config can be overridden by "config_tether_enable_bpf_offload" in
- // packages/modules/Connectivity/Tethering/res/values/config.xml. OEM may disable config by
- // RRO to override the enabled default value. Get the tethering config via dumpsys.
- // $ dumpsys tethering
- // mIsBpfEnabled: true
- boolean enabled = dumpStr.contains("mIsBpfEnabled: true");
- if (!enabled) {
- Log.d(TAG, "BPF offload tether config not enabled: " + dumpStr);
- }
- return enabled;
- }
-
- @NonNull
- private Inet6Address getClatIpv6Address(TetheringTester tester, TetheredDevice tethered)
- throws Exception {
- // Send an IPv4 UDP packet from client and check that a CLAT translated IPv6 UDP packet can
- // be found on upstream interface. Get CLAT IPv6 address from the CLAT translated IPv6 UDP
- // packet.
- byte[] expectedPacket = probeV4TetheringConnectivity(tester, tethered, true /* is4To6 */);
-
- // Above has guaranteed that the found packet is an IPv6 packet without ether header.
- return Struct.parse(Ipv6Header.class, ByteBuffer.wrap(expectedPacket)).srcIp;
- }
-
- // Test network topology:
- //
- // public network (rawip) private network
- // | UE (CLAT support) |
- // +---------------+ V +------------+------------+ V +------------+
- // | NAT64 Gateway +---------+ Upstream | Downstream +---------+ Client |
- // +---------------+ +------------+------------+ +------------+
- // remote ip public ip private ip
- // [64:ff9b::808:808]:443 [clat ipv6]:9876 [TetheredDevice ipv4]:9876
- //
- // Note that CLAT IPv6 address is generated by ClatCoordinator. Get the CLAT IPv6 address by
- // sending out an IPv4 packet and extracting the source address from CLAT translated IPv6
- // packet.
- //
- private void runClatUdpTest() throws Exception {
- // CLAT only starts on IPv6 only network.
- final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
- toList(TEST_IP6_DNS));
- final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
-
- // Get CLAT IPv6 address.
- final Inet6Address clatIp6 = getClatIpv6Address(tester, tethered);
-
- // Send an IPv4 UDP packet in original direction.
- // IPv4 packet -- CLAT translation --> IPv6 packet
- sendUploadPacketUdp(tethered.macAddr, tethered.routerMacAddr, tethered.ipv4Addr,
- REMOTE_IP4_ADDR, tester, true /* is4To6 */);
-
- // Send an IPv6 UDP packet in reply direction.
- // IPv6 packet -- CLAT translation --> IPv4 packet
- sendDownloadPacketUdp(REMOTE_NAT64_ADDR, clatIp6, tester, true /* is6To4 */);
-
- // TODO: test CLAT bpf maps.
- }
-
- // TODO: support R device. See b/234727688.
- @Test
- @IgnoreUpTo(Build.VERSION_CODES.R)
- public void testTetherClatUdp() throws Exception {
- runClatUdpTest();
- }
-
- // PacketBuilder doesn't support IPv4 ICMP packet. It may need to refactor PacketBuilder first
- // because ICMP is a specific layer 3 protocol for PacketBuilder which expects packets always
- // have layer 3 (IP) and layer 4 (TCP, UDP) for now. Since we don't use IPv4 ICMP packet too
- // much in this test, we just write a ICMP packet builder here.
- // TODO: move ICMPv4 packet build function to common utilis.
- @NonNull
- private ByteBuffer buildIcmpEchoPacketV4(
- @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
- @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp,
- int type, short id, short seq) throws Exception {
- if (type != ICMP_ECHO && type != ICMP_ECHOREPLY) {
- fail("Unsupported ICMP type: " + type);
- }
-
- // Build ICMP echo id and seq fields as payload. Ignore the data field.
- final ByteBuffer payload = ByteBuffer.allocate(4);
- payload.putShort(id);
- payload.putShort(seq);
- payload.rewind();
-
- final boolean hasEther = (srcMac != null && dstMac != null);
- final int etherHeaderLen = hasEther ? Struct.getSize(EthernetHeader.class) : 0;
- final int ipv4HeaderLen = Struct.getSize(Ipv4Header.class);
- final int Icmpv4HeaderLen = Struct.getSize(Icmpv4Header.class);
- final int payloadLen = payload.limit();
- final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv4HeaderLen
- + Icmpv4HeaderLen + payloadLen);
-
- // [1] Ethernet header
- if (hasEther) {
- final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, ETHER_TYPE_IPV4);
- ethHeader.writeToByteBuffer(packet);
- }
-
- // [2] IP header
- final Ipv4Header ipv4Header = new Ipv4Header(TYPE_OF_SERVICE,
- (short) 0 /* totalLength, calculate later */, ID,
- FLAGS_AND_FRAGMENT_OFFSET, TIME_TO_LIVE, (byte) IPPROTO_ICMP,
- (short) 0 /* checksum, calculate later */, srcIp, dstIp);
- ipv4Header.writeToByteBuffer(packet);
-
- // [3] ICMP header
- final Icmpv4Header icmpv4Header = new Icmpv4Header((byte) type, ICMPECHO_CODE,
- (short) 0 /* checksum, calculate later */);
- icmpv4Header.writeToByteBuffer(packet);
-
- // [4] Payload
- packet.put(payload);
- packet.flip();
-
- // [5] Finalize packet
- // Used for updating IP header fields. If there is Ehternet header, IPv4 header offset
- // in buffer equals ethernet header length because IPv4 header is located next to ethernet
- // header. Otherwise, IPv4 header offset is 0.
- final int ipv4HeaderOffset = hasEther ? etherHeaderLen : 0;
-
- // Populate the IPv4 totalLength field.
- packet.putShort(ipv4HeaderOffset + IPV4_LENGTH_OFFSET,
- (short) (ipv4HeaderLen + Icmpv4HeaderLen + payloadLen));
-
- // Populate the IPv4 header checksum field.
- packet.putShort(ipv4HeaderOffset + IPV4_CHECKSUM_OFFSET,
- ipChecksum(packet, ipv4HeaderOffset /* headerOffset */));
-
- // Populate the ICMP checksum field.
- packet.putShort(ipv4HeaderOffset + IPV4_HEADER_MIN_LEN + ICMP_CHECKSUM_OFFSET,
- icmpChecksum(packet, ipv4HeaderOffset + IPV4_HEADER_MIN_LEN,
- Icmpv4HeaderLen + payloadLen));
- return packet;
- }
-
- @NonNull
- private ByteBuffer buildIcmpEchoPacketV4(@NonNull final Inet4Address srcIp,
- @NonNull final Inet4Address dstIp, int type, short id, short seq)
- throws Exception {
- return buildIcmpEchoPacketV4(null /* srcMac */, null /* dstMac */, srcIp, dstIp,
- type, id, seq);
- }
-
- @Test
- public void testIcmpv4Echo() throws Exception {
- final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
- toList(TEST_IP4_DNS));
- final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
-
- // TODO: remove the connectivity verification for upstream connected notification race.
- // See the same reason in runUdp4Test().
- probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
-
- final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */,
- tethered.routerMacAddr /* dstMac */, tethered.ipv4Addr /* srcIp */,
- REMOTE_IP4_ADDR /* dstIp */, ICMP_ECHO, ICMPECHO_ID, ICMPECHO_SEQ);
- tester.verifyUpload(request, p -> {
- Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
-
- return isExpectedIcmpPacket(p, false /* hasEth */, true /* isIpv4 */, ICMP_ECHO);
- });
-
- final ByteBuffer reply = buildIcmpEchoPacketV4(REMOTE_IP4_ADDR /* srcIp*/,
- (Inet4Address) TEST_IP4_ADDR.getAddress() /* dstIp */, ICMP_ECHOREPLY, ICMPECHO_ID,
- ICMPECHO_SEQ);
- tester.verifyDownload(reply, p -> {
- Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
-
- return isExpectedIcmpPacket(p, true /* hasEth */, true /* isIpv4 */, ICMP_ECHOREPLY);
- });
- }
-
- // TODO: support R device. See b/234727688.
- @Test
- @IgnoreUpTo(Build.VERSION_CODES.R)
- public void testTetherClatIcmp() throws Exception {
- // CLAT only starts on IPv6 only network.
- final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
- toList(TEST_IP6_DNS));
- final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
-
- // Get CLAT IPv6 address.
- final Inet6Address clatIp6 = getClatIpv6Address(tester, tethered);
-
- // Send an IPv4 ICMP packet in original direction.
- // IPv4 packet -- CLAT translation --> IPv6 packet
- final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */,
- tethered.routerMacAddr /* dstMac */, tethered.ipv4Addr /* srcIp */,
- (Inet4Address) REMOTE_IP4_ADDR /* dstIp */, ICMP_ECHO, ICMPECHO_ID, ICMPECHO_SEQ);
- tester.verifyUpload(request, p -> {
- Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
-
- return isExpectedIcmpPacket(p, false /* hasEth */, false /* isIpv4 */,
- ICMPV6_ECHO_REQUEST_TYPE);
- });
-
- // Send an IPv6 ICMP packet in reply direction.
- // IPv6 packet -- CLAT translation --> IPv4 packet
- final ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(
- (Inet6Address) REMOTE_NAT64_ADDR /* srcIp */, clatIp6 /* dstIp */);
- tester.verifyDownload(reply, p -> {
- Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
-
- return isExpectedIcmpPacket(p, true /* hasEth */, true /* isIpv4 */, ICMP_ECHOREPLY);
- });
- }
-
- @NonNull
- private ByteBuffer buildDnsReplyMessageById(short id) {
- byte[] replyMessage = Arrays.copyOf(DNS_REPLY, DNS_REPLY.length);
- // Assign transaction id of reply message pattern with a given DNS transaction id.
- replyMessage[0] = (byte) ((id >> 8) & 0xff);
- replyMessage[1] = (byte) (id & 0xff);
- Log.d(TAG, "Built DNS reply: " + dumpHexString(replyMessage));
-
- return ByteBuffer.wrap(replyMessage);
- }
-
- @NonNull
- private void sendDownloadPacketDnsV4(@NonNull final Inet4Address srcIp,
- @NonNull final Inet4Address dstIp, short srcPort, short dstPort, short dnsId,
- @NonNull final TetheringTester tester) throws Exception {
- // DNS response transaction id must be copied from DNS query. Used by the requester
- // to match up replies to outstanding queries. See RFC 1035 section 4.1.1.
- final ByteBuffer dnsReplyMessage = buildDnsReplyMessageById(dnsId);
- final ByteBuffer testPacket = buildUdpPacket((InetAddress) srcIp,
- (InetAddress) dstIp, srcPort, dstPort, dnsReplyMessage);
-
- tester.verifyDownload(testPacket, p -> {
- Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
- return isExpectedUdpDnsPacket(p, true /* hasEther */, true /* isIpv4 */,
- dnsReplyMessage);
- });
- }
-
- // Send IPv4 UDP DNS packet and return the forwarded DNS packet on upstream.
- @NonNull
- private byte[] sendUploadPacketDnsV4(@NonNull final MacAddress srcMac,
- @NonNull final MacAddress dstMac, @NonNull final Inet4Address srcIp,
- @NonNull final Inet4Address dstIp, short srcPort, short dstPort,
- @NonNull final TetheringTester tester) throws Exception {
- final ByteBuffer testPacket = buildUdpPacket(srcMac, dstMac, srcIp, dstIp,
- srcPort, dstPort, DNS_QUERY);
-
- return tester.verifyUpload(testPacket, p -> {
- Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
- return isExpectedUdpDnsPacket(p, false /* hasEther */, true /* isIpv4 */,
- DNS_QUERY);
- });
- }
-
- @Test
- public void testTetherUdpV4Dns() throws Exception {
- final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
- toList(TEST_IP4_DNS));
- final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
-
- // TODO: remove the connectivity verification for upstream connected notification race.
- // See the same reason in runUdp4Test().
- probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
-
- // [1] Send DNS query.
- // tethered device --> downstream --> dnsmasq forwarding --> upstream --> DNS server
- //
- // Need to extract DNS transaction id and source port from dnsmasq forwarded DNS query
- // packet. dnsmasq forwarding creats new query which means UDP source port and DNS
- // transaction id are changed from original sent DNS query. See forward_query() in
- // external/dnsmasq/src/forward.c. Note that #TetheringTester.isExpectedUdpDnsPacket
- // guarantees that |forwardedQueryPacket| is a valid DNS packet. So we can parse it as DNS
- // packet.
- final MacAddress srcMac = tethered.macAddr;
- final MacAddress dstMac = tethered.routerMacAddr;
- final Inet4Address clientIp = tethered.ipv4Addr;
- final Inet4Address gatewayIp = tethered.ipv4Gatway;
- final byte[] forwardedQueryPacket = sendUploadPacketDnsV4(srcMac, dstMac, clientIp,
- gatewayIp, LOCAL_PORT, DNS_PORT, tester);
- final ByteBuffer buf = ByteBuffer.wrap(forwardedQueryPacket);
- Struct.parse(Ipv4Header.class, buf);
- final UdpHeader udpHeader = Struct.parse(UdpHeader.class, buf);
- final TestDnsPacket dnsQuery = TestDnsPacket.getTestDnsPacket(buf);
- assertNotNull(dnsQuery);
- Log.d(TAG, "Forwarded UDP source port: " + udpHeader.srcPort + ", DNS query id: "
- + dnsQuery.getHeader().getId());
-
- // [2] Send DNS reply.
- // DNS server --> upstream --> dnsmasq forwarding --> downstream --> tethered device
- //
- // DNS reply transaction id must be copied from DNS query. Used by the requester to match
- // up replies to outstanding queries. See RFC 1035 section 4.1.1.
- final Inet4Address remoteIp = (Inet4Address) TEST_IP4_DNS;
- final Inet4Address tetheringUpstreamIp = (Inet4Address) TEST_IP4_ADDR.getAddress();
- sendDownloadPacketDnsV4(remoteIp, tetheringUpstreamIp, DNS_PORT,
- (short) udpHeader.srcPort, (short) dnsQuery.getHeader().getId(), tester);
- }
-
- @NonNull
- private ByteBuffer buildTcpPacket(
- @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
- @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp,
- short srcPort, short dstPort, final short seq, final short ack,
- final byte tcpFlags, @NonNull final ByteBuffer payload) throws Exception {
- final int ipProto = getIpProto(srcIp, dstIp);
- final boolean hasEther = (srcMac != null && dstMac != null);
- final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_TCP,
- payload.limit());
- final PacketBuilder packetBuilder = new PacketBuilder(buffer);
-
- // [1] Ethernet header
- if (hasEther) {
- packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp));
- }
-
- // [2] IP header
- if (ipProto == IPPROTO_IP) {
- packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
- TIME_TO_LIVE, (byte) IPPROTO_TCP, (Inet4Address) srcIp, (Inet4Address) dstIp);
- } else {
- packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_TCP,
- HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp);
- }
-
- // [3] TCP header
- packetBuilder.writeTcpHeader(srcPort, dstPort, seq, ack, tcpFlags, WINDOW, URGENT_POINTER);
-
- // [4] Payload
- buffer.put(payload);
- // in case data might be reused by caller, restore the position and
- // limit of bytebuffer.
- payload.clear();
-
- return packetBuilder.finalizePacket();
- }
-
- private void sendDownloadPacketTcp(@NonNull final InetAddress srcIp,
- @NonNull final InetAddress dstIp, short seq, short ack, byte tcpFlags,
- @NonNull final ByteBuffer payload, @NonNull final TetheringTester tester,
- boolean is6To4) throws Exception {
- if (is6To4) {
- assertFalse("CLAT download test must sends IPv6 packet", isAddressIpv4(srcIp, dstIp));
- }
-
- // Expected received TCP packet IP protocol. While testing CLAT (is6To4 = true), the packet
- // on downstream must be IPv4. Otherwise, the IP protocol of test packet is the same on
- // both downstream and upstream.
- final boolean isIpv4 = is6To4 ? true : isAddressIpv4(srcIp, dstIp);
-
- final ByteBuffer testPacket = buildTcpPacket(null /* srcMac */, null /* dstMac */,
- srcIp, dstIp, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */, seq, ack,
- tcpFlags, payload);
- tester.verifyDownload(testPacket, p -> {
- Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
-
- return isExpectedTcpPacket(p, true /* hasEther */, isIpv4, seq, payload);
- });
- }
-
- private void sendUploadPacketTcp(@NonNull final MacAddress srcMac,
- @NonNull final MacAddress dstMac, @NonNull final InetAddress srcIp,
- @NonNull final InetAddress dstIp, short seq, short ack, byte tcpFlags,
- @NonNull final ByteBuffer payload, @NonNull final TetheringTester tester,
- boolean is4To6) throws Exception {
- if (is4To6) {
- assertTrue("CLAT upload test must sends IPv4 packet", isAddressIpv4(srcIp, dstIp));
- }
-
- // Expected received TCP packet IP protocol. While testing CLAT (is4To6 = true), the packet
- // on upstream must be IPv6. Otherwise, the IP protocol of test packet is the same on
- // both downstream and upstream.
- final boolean isIpv4 = is4To6 ? false : isAddressIpv4(srcIp, dstIp);
-
- final ByteBuffer testPacket = buildTcpPacket(srcMac, dstMac, srcIp, dstIp,
- LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */, seq, ack, tcpFlags,
- payload);
- tester.verifyUpload(testPacket, p -> {
- Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
-
- return isExpectedTcpPacket(p, false /* hasEther */, isIpv4, seq, payload);
- });
- }
-
- void runTcpTest(
- @NonNull final MacAddress uploadSrcMac, @NonNull final MacAddress uploadDstMac,
- @NonNull final InetAddress uploadSrcIp, @NonNull final InetAddress uploadDstIp,
- @NonNull final InetAddress downloadSrcIp, @NonNull final InetAddress downloadDstIp,
- @NonNull final TetheringTester tester, boolean isClat) throws Exception {
- // Three way handshake and data transfer.
- //
- // Server (base seq = 2000) Client (base seq = 1000)
- // | |
- // | [1] [SYN] SEQ = 1000 |
- // |<---------------------------------------------------------| -
- // | | ^
- // | [2] [SYN + ACK] SEQ = 2000, ACK = 1000+1 | |
- // |--------------------------------------------------------->| three way handshake
- // | | |
- // | [3] [ACK] SEQ = 1001, ACK = 2000+1 | v
- // |<---------------------------------------------------------| -
- // | | ^
- // | [4] [ACK] SEQ = 1001, ACK = 2001, 2 byte payload | |
- // |<---------------------------------------------------------| data transfer
- // | | |
- // | [5] [ACK] SEQ = 2001, ACK = 1001+2, 2 byte payload | v
- // |--------------------------------------------------------->| -
- // | |
- //
-
- // This test can only verify the packets are transferred end to end but TCP state.
- // TODO: verify TCP state change via /proc/net/nf_conntrack or netlink conntrack event.
- // [1] [UPLOAD] [SYN]: SEQ = 1000
- sendUploadPacketTcp(uploadSrcMac, uploadDstMac, uploadSrcIp, uploadDstIp,
- (short) 1000 /* seq */, (short) 0 /* ack */, TCPHDR_SYN, EMPTY_PAYLOAD,
- tester, isClat /* is4To6 */);
-
- // [2] [DONWLOAD] [SYN + ACK]: SEQ = 2000, ACK = 1001
- sendDownloadPacketTcp(downloadSrcIp, downloadDstIp, (short) 2000 /* seq */,
- (short) 1001 /* ack */, (byte) ((TCPHDR_SYN | TCPHDR_ACK) & 0xff), EMPTY_PAYLOAD,
- tester, isClat /* is6To4 */);
-
- // [3] [UPLOAD] [ACK]: SEQ = 1001, ACK = 2001
- sendUploadPacketTcp(uploadSrcMac, uploadDstMac, uploadSrcIp, uploadDstIp,
- (short) 1001 /* seq */, (short) 2001 /* ack */, TCPHDR_ACK, EMPTY_PAYLOAD, tester,
- isClat /* is4To6 */);
-
- // [4] [UPLOAD] [ACK]: SEQ = 1001, ACK = 2001, 2 byte payload
- sendUploadPacketTcp(uploadSrcMac, uploadDstMac, uploadSrcIp, uploadDstIp,
- (short) 1001 /* seq */, (short) 2001 /* ack */, TCPHDR_ACK, TX_PAYLOAD,
- tester, isClat /* is4To6 */);
-
- // [5] [DONWLOAD] [ACK]: SEQ = 2001, ACK = 1003, 2 byte payload
- sendDownloadPacketTcp(downloadSrcIp, downloadDstIp, (short) 2001 /* seq */,
- (short) 1003 /* ack */, TCPHDR_ACK, RX_PAYLOAD, tester, isClat /* is6To4 */);
-
- // TODO: test BPF offload maps.
- }
-
- @Test
- public void testTetherTcpV4() throws Exception {
- final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR),
- toList(TEST_IP4_DNS));
- final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */);
-
- // TODO: remove the connectivity verification for upstream connected notification race.
- // See the same reason in runUdp4Test().
- probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */);
-
- runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
- tethered.ipv4Addr /* uploadSrcIp */, REMOTE_IP4_ADDR /* uploadDstIp */,
- REMOTE_IP4_ADDR /* downloadSrcIp */, TEST_IP4_ADDR.getAddress() /* downloadDstIp */,
- tester, false /* isClat */);
- }
-
- @Test
- public void testTetherTcpV6() throws Exception {
- final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
- toList(TEST_IP6_DNS));
- final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
-
- runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
- tethered.ipv6Addr /* uploadSrcIp */, REMOTE_IP6_ADDR /* uploadDstIp */,
- REMOTE_IP6_ADDR /* downloadSrcIp */, tethered.ipv6Addr /* downloadDstIp */,
- tester, false /* isClat */);
- }
-
- // TODO: support R device. See b/234727688.
- @Test
- @IgnoreUpTo(Build.VERSION_CODES.R)
- public void testTetherClatTcp() throws Exception {
- // CLAT only starts on IPv6 only network.
- final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR),
- toList(TEST_IP6_DNS));
- final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */);
-
- // Get CLAT IPv6 address.
- final Inet6Address clatIp6 = getClatIpv6Address(tester, tethered);
-
- runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */,
- tethered.ipv4Addr /* uploadSrcIp */, REMOTE_IP4_ADDR /* uploadDstIp */,
- REMOTE_NAT64_ADDR /* downloadSrcIp */, clatIp6 /* downloadDstIp */,
- tester, true /* isClat */);
- }
-
- private <T> List<T> toList(T... array) {
- return Arrays.asList(array);
- }
-}
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTestBase.java
index d58a60c..7685981 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTestBase.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTestBase.java
@@ -16,24 +16,85 @@
package android.net;
+import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.TETHER_PRIVILEGED;
+import static android.content.pm.PackageManager.FEATURE_WIFI;
import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
+import static android.net.TetheringManager.TETHERING_ETHERNET;
+import static android.net.TetheringTester.isExpectedIcmpPacket;
+import static android.net.TetheringTester.isExpectedTcpPacket;
+import static android.net.TetheringTester.isExpectedUdpPacket;
+import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_IPV6;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static com.android.net.module.util.HexDump.dumpHexString;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK;
+import static com.android.net.module.util.NetworkStackConstants.TCPHDR_SYN;
+import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
import android.app.UiAutomation;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.net.EthernetManager.TetheredInterfaceCallback;
+import android.net.EthernetManager.TetheredInterfaceRequest;
+import android.net.TetheringManager.StartTetheringCallback;
+import android.net.TetheringManager.TetheringEventCallback;
+import android.net.TetheringManager.TetheringRequest;
+import android.net.TetheringTester.TetheredDevice;
import android.net.cts.util.CtsNetUtils;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.PacketBuilder;
+import com.android.testutils.HandlerUtils;
import com.android.testutils.TapPacketReader;
import com.android.testutils.TestNetworkTracker;
+import org.junit.After;
+import org.junit.Before;
+
+import java.io.FileDescriptor;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* TODO: Common variables or methods shared between CtsEthernetTetheringTest and
@@ -46,8 +107,8 @@
// Used to check if any tethering interface is available. Choose 200ms to be request timeout
// because the average interface requested time on cuttlefish@acloud is around 10ms.
// See TetheredInterfaceRequester.getInterface, isInterfaceForTetheringAvailable.
- protected static final int AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS = 200;
- protected static final int TETHER_REACHABILITY_ATTEMPTS = 20;
+ private static final int AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS = 200;
+ private static final int TETHER_REACHABILITY_ATTEMPTS = 20;
protected static final long WAIT_RA_TIMEOUT_MS = 2000;
// Address and NAT prefix definition.
@@ -72,9 +133,9 @@
protected static final byte TYPE_OF_SERVICE = 0;
// IPv6 header definition.
- protected static final short HOP_LIMIT = 0x40;
+ private static final short HOP_LIMIT = 0x40;
// version=6, traffic class=0x0, flowlabel=0x0;
- protected static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000;
+ private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000;
// UDP and TCP header definition.
// LOCAL_PORT is used by public port and private port. Assume port 9876 has not been used yet
@@ -82,34 +143,877 @@
// NAT port forwarding could be different between private port and public port.
protected static final short LOCAL_PORT = 9876;
protected static final short REMOTE_PORT = 433;
- protected static final short WINDOW = (short) 0x2000;
- protected static final short URGENT_POINTER = 0;
+ private static final short WINDOW = (short) 0x2000;
+ private static final short URGENT_POINTER = 0;
// Payload definition.
protected static final ByteBuffer EMPTY_PAYLOAD = ByteBuffer.wrap(new byte[0]);
- protected static final ByteBuffer TEST_REACHABILITY_PAYLOAD =
+ private static final ByteBuffer TEST_REACHABILITY_PAYLOAD =
ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
protected static final ByteBuffer RX_PAYLOAD =
ByteBuffer.wrap(new byte[] { (byte) 0x12, (byte) 0x34 });
protected static final ByteBuffer TX_PAYLOAD =
ByteBuffer.wrap(new byte[] { (byte) 0x56, (byte) 0x78 });
- protected final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
- protected final EthernetManager mEm = mContext.getSystemService(EthernetManager.class);
- protected final TetheringManager mTm = mContext.getSystemService(TetheringManager.class);
- protected final PackageManager mPackageManager = mContext.getPackageManager();
- protected final CtsNetUtils mCtsNetUtils = new CtsNetUtils(mContext);
- protected final UiAutomation mUiAutomation =
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ private final EthernetManager mEm = mContext.getSystemService(EthernetManager.class);
+ private final TetheringManager mTm = mContext.getSystemService(TetheringManager.class);
+ private final PackageManager mPackageManager = mContext.getPackageManager();
+ private final CtsNetUtils mCtsNetUtils = new CtsNetUtils(mContext);
+ private final UiAutomation mUiAutomation =
InstrumentationRegistry.getInstrumentation().getUiAutomation();
// Late initialization in setUp()
- protected boolean mRunTests;
- protected HandlerThread mHandlerThread;
- protected Handler mHandler;
+ private boolean mRunTests;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private TetheredInterfaceRequester mTetheredInterfaceRequester;
// Late initialization in initTetheringTester().
- protected TapPacketReader mUpstreamReader;
- protected TestNetworkTracker mUpstreamTracker;
- protected TestNetworkInterface mDownstreamIface;
- protected TapPacketReader mDownstreamReader;
+ private TapPacketReader mUpstreamReader;
+ private TestNetworkTracker mUpstreamTracker;
+ private TestNetworkInterface mDownstreamIface;
+ private TapPacketReader mDownstreamReader;
+ private MyTetheringEventCallback mTetheringEventCallback;
+
+ @Before
+ public void setUp() throws Exception {
+ mHandlerThread = new HandlerThread(getClass().getSimpleName());
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+
+ mRunTests = runAsShell(NETWORK_SETTINGS, TETHER_PRIVILEGED, () ->
+ mTm.isTetheringSupported());
+ assumeTrue(mRunTests);
+
+ mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm);
+ }
+
+ protected void maybeStopTapPacketReader(final TapPacketReader tapPacketReader)
+ throws Exception {
+ if (tapPacketReader != null) {
+ TapPacketReader reader = tapPacketReader;
+ mHandler.post(() -> reader.stop());
+ }
+ }
+
+ protected void maybeCloseTestInterface(final TestNetworkInterface testInterface)
+ throws Exception {
+ if (testInterface != null) {
+ testInterface.getFileDescriptor().close();
+ Log.d(TAG, "Deleted test interface " + testInterface.getInterfaceName());
+ }
+ }
+
+ protected void maybeUnregisterTetheringEventCallback(final MyTetheringEventCallback callback)
+ throws Exception {
+ if (callback != null) {
+ callback.awaitInterfaceUntethered();
+ callback.unregister();
+ }
+ }
+
+ protected void cleanUp() throws Exception {
+ setPreferTestNetworks(false);
+
+ if (mUpstreamTracker != null) {
+ runAsShell(MANAGE_TEST_NETWORKS, () -> {
+ mUpstreamTracker.teardown();
+ mUpstreamTracker = null;
+ });
+ }
+ if (mUpstreamReader != null) {
+ TapPacketReader reader = mUpstreamReader;
+ mHandler.post(() -> reader.stop());
+ mUpstreamReader = null;
+ }
+
+ maybeStopTapPacketReader(mDownstreamReader);
+ mDownstreamReader = null;
+ // To avoid flaky which caused by the next test started but the previous interface is not
+ // untracked from EthernetTracker yet. Just delete the test interface without explicitly
+ // calling TetheringManager#stopTethering could let EthernetTracker untrack the test
+ // interface from server mode before tethering stopped. Thus, awaitInterfaceUntethered
+ // could not only make sure tethering is stopped but also guarantee the test interface is
+ // untracked from EthernetTracker.
+ maybeCloseTestInterface(mDownstreamIface);
+ mDownstreamIface = null;
+ maybeUnregisterTetheringEventCallback(mTetheringEventCallback);
+ mTetheringEventCallback = null;
+
+ runAsShell(NETWORK_SETTINGS, () -> mTetheredInterfaceRequester.release());
+ setIncludeTestInterfaces(false);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ try {
+ if (mRunTests) cleanUp();
+ } finally {
+ mHandlerThread.quitSafely();
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ protected boolean isInterfaceForTetheringAvailable() throws Exception {
+ // Before T, all ethernet interfaces could be used for server mode. Instead of
+ // waiting timeout, just checking whether the system currently has any
+ // ethernet interface is more reliable.
+ if (!SdkLevel.isAtLeastT()) {
+ return runAsShell(CONNECTIVITY_USE_RESTRICTED_NETWORKS, () -> mEm.isAvailable());
+ }
+
+ // If previous test case doesn't release tethering interface successfully, the other tests
+ // after that test may be skipped as unexcepted.
+ // TODO: figure out a better way to check default tethering interface existenion.
+ final TetheredInterfaceRequester requester = new TetheredInterfaceRequester(mHandler, mEm);
+ try {
+ // Use short timeout (200ms) for requesting an existing interface, if any, because
+ // it should reurn faster than requesting a new tethering interface. Using default
+ // timeout (5000ms, TIMEOUT_MS) may make that total testing time is over 1 minute
+ // test module timeout on internal testing.
+ // TODO: if this becomes flaky, consider using default timeout (5000ms) and moving
+ // this check into #setUpOnce.
+ return requester.getInterface(AVAILABLE_TETHER_IFACE_REQUEST_TIMEOUT_MS) != null;
+ } catch (TimeoutException e) {
+ return false;
+ } finally {
+ runAsShell(NETWORK_SETTINGS, () -> {
+ requester.release();
+ });
+ }
+ }
+
+ protected void setIncludeTestInterfaces(boolean include) {
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mEm.setIncludeTestInterfaces(include);
+ });
+ }
+
+ protected void setPreferTestNetworks(boolean prefer) {
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mTm.setPreferTestNetworks(prefer);
+ });
+ }
+
+ protected String getTetheredInterface() throws Exception {
+ return mTetheredInterfaceRequester.getInterface();
+ }
+
+ protected CompletableFuture<String> requestTetheredInterface() throws Exception {
+ return mTetheredInterfaceRequester.requestInterface();
+ }
+
+ protected static void waitForRouterAdvertisement(TapPacketReader reader, String iface,
+ long timeoutMs) {
+ final long deadline = SystemClock.uptimeMillis() + timeoutMs;
+ do {
+ byte[] pkt = reader.popPacket(timeoutMs);
+ if (isExpectedIcmpPacket(pkt, true /* hasEth */, false /* isIpv4 */,
+ ICMPV6_ROUTER_ADVERTISEMENT)) {
+ return;
+ }
+
+ timeoutMs = deadline - SystemClock.uptimeMillis();
+ } while (timeoutMs > 0);
+ fail("Did not receive router advertisement on " + iface + " after "
+ + timeoutMs + "ms idle");
+ }
+
+
+ protected static final class MyTetheringEventCallback implements TetheringEventCallback {
+ private final TetheringManager mTm;
+ private final CountDownLatch mTetheringStartedLatch = new CountDownLatch(1);
+ private final CountDownLatch mTetheringStoppedLatch = new CountDownLatch(1);
+ private final CountDownLatch mLocalOnlyStartedLatch = new CountDownLatch(1);
+ private final CountDownLatch mLocalOnlyStoppedLatch = new CountDownLatch(1);
+ private final CountDownLatch mClientConnectedLatch = new CountDownLatch(1);
+ private final CountDownLatch mUpstreamLatch = new CountDownLatch(1);
+ private final CountDownLatch mCallbackRegisteredLatch = new CountDownLatch(1);
+ private final TetheringInterface mIface;
+ private final Network mExpectedUpstream;
+
+ private boolean mAcceptAnyUpstream = false;
+
+ private volatile boolean mInterfaceWasTethered = false;
+ private volatile boolean mInterfaceWasLocalOnly = false;
+ private volatile boolean mUnregistered = false;
+ private volatile Collection<TetheredClient> mClients = null;
+ private volatile Network mUpstream = null;
+
+ MyTetheringEventCallback(TetheringManager tm, String iface) {
+ this(tm, iface, null);
+ mAcceptAnyUpstream = true;
+ }
+
+ MyTetheringEventCallback(TetheringManager tm, String iface, Network expectedUpstream) {
+ mTm = tm;
+ mIface = new TetheringInterface(TETHERING_ETHERNET, iface);
+ mExpectedUpstream = expectedUpstream;
+ }
+
+ public void unregister() {
+ mTm.unregisterTetheringEventCallback(this);
+ mUnregistered = true;
+ }
+ @Override
+ public void onTetheredInterfacesChanged(List<String> interfaces) {
+ fail("Should only call callback that takes a Set<TetheringInterface>");
+ }
+
+ @Override
+ public void onTetheredInterfacesChanged(Set<TetheringInterface> interfaces) {
+ // Ignore stale callbacks registered by previous test cases.
+ if (mUnregistered) return;
+
+ if (!mInterfaceWasTethered && interfaces.contains(mIface)) {
+ // This interface is being tethered for the first time.
+ Log.d(TAG, "Tethering started: " + interfaces);
+ mInterfaceWasTethered = true;
+ mTetheringStartedLatch.countDown();
+ } else if (mInterfaceWasTethered && !interfaces.contains(mIface)) {
+ Log.d(TAG, "Tethering stopped: " + interfaces);
+ mTetheringStoppedLatch.countDown();
+ }
+ }
+
+ @Override
+ public void onLocalOnlyInterfacesChanged(List<String> interfaces) {
+ fail("Should only call callback that takes a Set<TetheringInterface>");
+ }
+
+ @Override
+ public void onLocalOnlyInterfacesChanged(Set<TetheringInterface> interfaces) {
+ // Ignore stale callbacks registered by previous test cases.
+ if (mUnregistered) return;
+
+ if (!mInterfaceWasLocalOnly && interfaces.contains(mIface)) {
+ // This interface is being put into local-only mode for the first time.
+ Log.d(TAG, "Local-only started: " + interfaces);
+ mInterfaceWasLocalOnly = true;
+ mLocalOnlyStartedLatch.countDown();
+ } else if (mInterfaceWasLocalOnly && !interfaces.contains(mIface)) {
+ Log.d(TAG, "Local-only stopped: " + interfaces);
+ mLocalOnlyStoppedLatch.countDown();
+ }
+ }
+
+ public void awaitInterfaceTethered() throws Exception {
+ assertTrue("Ethernet not tethered after " + TIMEOUT_MS + "ms",
+ mTetheringStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ public void awaitInterfaceLocalOnly() throws Exception {
+ assertTrue("Ethernet not local-only after " + TIMEOUT_MS + "ms",
+ mLocalOnlyStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ }
+
+ // Used to check if the callback has registered. When the callback is registered,
+ // onSupportedTetheringTypes is celled in onCallbackStarted(). After
+ // onSupportedTetheringTypes called, drop the permission for registering callback.
+ // See MyTetheringEventCallback#register, TetheringManager#onCallbackStarted.
+ @Override
+ public void onSupportedTetheringTypes(Set<Integer> supportedTypes) {
+ // Used to check callback registered.
+ mCallbackRegisteredLatch.countDown();
+ }
+
+ public void awaitCallbackRegistered() throws Exception {
+ if (!mCallbackRegisteredLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Did not receive callback registered signal after " + TIMEOUT_MS + "ms");
+ }
+ }
+
+ public void awaitInterfaceUntethered() throws Exception {
+ // Don't block teardown if the interface was never tethered.
+ // This is racy because the interface might become tethered right after this check, but
+ // that can only happen in tearDown if startTethering timed out, which likely means
+ // the test has already failed.
+ if (!mInterfaceWasTethered && !mInterfaceWasLocalOnly) return;
+
+ if (mInterfaceWasTethered) {
+ assertTrue(mIface + " not untethered after " + TIMEOUT_MS + "ms",
+ mTetheringStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } else if (mInterfaceWasLocalOnly) {
+ assertTrue(mIface + " not untethered after " + TIMEOUT_MS + "ms",
+ mLocalOnlyStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } else {
+ fail(mIface + " cannot be both tethered and local-only. Update this test class.");
+ }
+ }
+
+ @Override
+ public void onError(String ifName, int error) {
+ // Ignore stale callbacks registered by previous test cases.
+ if (mUnregistered) return;
+
+ fail("TetheringEventCallback got error:" + error + " on iface " + ifName);
+ }
+
+ @Override
+ public void onClientsChanged(Collection<TetheredClient> clients) {
+ // Ignore stale callbacks registered by previous test cases.
+ if (mUnregistered) return;
+
+ Log.d(TAG, "Got clients changed: " + clients);
+ mClients = clients;
+ if (clients.size() > 0) {
+ mClientConnectedLatch.countDown();
+ }
+ }
+
+ public Collection<TetheredClient> awaitClientConnected() throws Exception {
+ assertTrue("Did not receive client connected callback after " + TIMEOUT_MS + "ms",
+ mClientConnectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ return mClients;
+ }
+
+ @Override
+ public void onUpstreamChanged(Network network) {
+ // Ignore stale callbacks registered by previous test cases.
+ if (mUnregistered) return;
+
+ Log.d(TAG, "Got upstream changed: " + network);
+ mUpstream = network;
+ if (mAcceptAnyUpstream || Objects.equals(mUpstream, mExpectedUpstream)) {
+ mUpstreamLatch.countDown();
+ }
+ }
+
+ public Network awaitUpstreamChanged(boolean throwTimeoutException) throws Exception {
+ if (!mUpstreamLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ final String errorMessage = "Did not receive upstream "
+ + (mAcceptAnyUpstream ? "any" : mExpectedUpstream)
+ + " callback after " + TIMEOUT_MS + "ms";
+
+ if (throwTimeoutException) {
+ throw new TimeoutException(errorMessage);
+ } else {
+ fail(errorMessage);
+ }
+ }
+ return mUpstream;
+ }
+ }
+
+ protected MyTetheringEventCallback enableEthernetTethering(String iface,
+ TetheringRequest request, Network expectedUpstream) throws Exception {
+ // Enable ethernet tethering with null expectedUpstream means the test accept any upstream
+ // after etherent tethering started.
+ final MyTetheringEventCallback callback;
+ if (expectedUpstream != null) {
+ callback = new MyTetheringEventCallback(mTm, iface, expectedUpstream);
+ } else {
+ callback = new MyTetheringEventCallback(mTm, iface);
+ }
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mTm.registerTetheringEventCallback(mHandler::post, callback);
+ // Need to hold the shell permission until callback is registered. This helps to avoid
+ // the test become flaky.
+ callback.awaitCallbackRegistered();
+ });
+ final CountDownLatch tetheringStartedLatch = new CountDownLatch(1);
+ StartTetheringCallback startTetheringCallback = new StartTetheringCallback() {
+ @Override
+ public void onTetheringStarted() {
+ Log.d(TAG, "Ethernet tethering started");
+ tetheringStartedLatch.countDown();
+ }
+
+ @Override
+ public void onTetheringFailed(int resultCode) {
+ fail("Unexpectedly got onTetheringFailed");
+ }
+ };
+ Log.d(TAG, "Starting Ethernet tethering");
+ runAsShell(TETHER_PRIVILEGED, () -> {
+ mTm.startTethering(request, mHandler::post /* executor */, startTetheringCallback);
+ // Binder call is an async call. Need to hold the shell permission until tethering
+ // started. This helps to avoid the test become flaky.
+ if (!tetheringStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ fail("Did not receive tethering started callback after " + TIMEOUT_MS + "ms");
+ }
+ });
+
+ final int connectivityType = request.getConnectivityScope();
+ switch (connectivityType) {
+ case CONNECTIVITY_SCOPE_GLOBAL:
+ callback.awaitInterfaceTethered();
+ break;
+ case CONNECTIVITY_SCOPE_LOCAL:
+ callback.awaitInterfaceLocalOnly();
+ break;
+ default:
+ fail("Unexpected connectivity type requested: " + connectivityType);
+ }
+
+ return callback;
+ }
+
+ protected MyTetheringEventCallback enableEthernetTethering(String iface,
+ Network expectedUpstream) throws Exception {
+ return enableEthernetTethering(iface,
+ new TetheringRequest.Builder(TETHERING_ETHERNET)
+ .setShouldShowEntitlementUi(false).build(), expectedUpstream);
+ }
+
+ protected int getMTU(TestNetworkInterface iface) throws SocketException {
+ NetworkInterface nif = NetworkInterface.getByName(iface.getInterfaceName());
+ assertNotNull("Can't get NetworkInterface object for " + iface.getInterfaceName(), nif);
+ return nif.getMTU();
+ }
+
+ protected TapPacketReader makePacketReader(final TestNetworkInterface iface) throws Exception {
+ FileDescriptor fd = iface.getFileDescriptor().getFileDescriptor();
+ return makePacketReader(fd, getMTU(iface));
+ }
+
+ protected TapPacketReader makePacketReader(FileDescriptor fd, int mtu) {
+ final TapPacketReader reader = new TapPacketReader(mHandler, fd, mtu);
+ mHandler.post(() -> reader.start());
+ HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
+ return reader;
+ }
+
+ protected static final class TetheredInterfaceRequester implements TetheredInterfaceCallback {
+ private final Handler mHandler;
+ private final EthernetManager mEm;
+
+ private TetheredInterfaceRequest mRequest;
+ private final CompletableFuture<String> mFuture = new CompletableFuture<>();
+
+ TetheredInterfaceRequester(Handler handler, EthernetManager em) {
+ mHandler = handler;
+ mEm = em;
+ }
+
+ @Override
+ public void onAvailable(String iface) {
+ Log.d(TAG, "Ethernet interface available: " + iface);
+ mFuture.complete(iface);
+ }
+
+ @Override
+ public void onUnavailable() {
+ mFuture.completeExceptionally(new IllegalStateException("onUnavailable received"));
+ }
+
+ public CompletableFuture<String> requestInterface() {
+ assertNull("BUG: more than one tethered interface request", mRequest);
+ Log.d(TAG, "Requesting tethered interface");
+ mRequest = runAsShell(NETWORK_SETTINGS, () ->
+ mEm.requestTetheredInterface(mHandler::post, this));
+ return mFuture;
+ }
+
+ public String getInterface(int timeout) throws Exception {
+ return requestInterface().get(timeout, TimeUnit.MILLISECONDS);
+ }
+
+ public String getInterface() throws Exception {
+ return getInterface(TIMEOUT_MS);
+ }
+
+ public void release() {
+ if (mRequest != null) {
+ mFuture.obtrudeException(new IllegalStateException("Request already released"));
+ mRequest.release();
+ mRequest = null;
+ }
+ }
+ }
+
+ protected TestNetworkInterface createTestInterface() throws Exception {
+ TestNetworkManager tnm = runAsShell(MANAGE_TEST_NETWORKS, () ->
+ mContext.getSystemService(TestNetworkManager.class));
+ TestNetworkInterface iface = runAsShell(MANAGE_TEST_NETWORKS, () ->
+ tnm.createTapInterface());
+ Log.d(TAG, "Created test interface " + iface.getInterfaceName());
+ return iface;
+ }
+
+ protected TestNetworkTracker createTestUpstream(final List<LinkAddress> addresses,
+ final List<InetAddress> dnses) throws Exception {
+ setPreferTestNetworks(true);
+
+ final LinkProperties lp = new LinkProperties();
+ lp.setLinkAddresses(addresses);
+ lp.setDnsServers(dnses);
+ lp.setNat64Prefix(TEST_NAT64PREFIX);
+
+ return runAsShell(MANAGE_TEST_NETWORKS, () -> initTestNetwork(mContext, lp, TIMEOUT_MS));
+ }
+
+ private short getEthType(@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp) {
+ return isAddressIpv4(srcIp, dstIp) ? (short) ETHER_TYPE_IPV4 : (short) ETHER_TYPE_IPV6;
+ }
+
+ private int getIpProto(@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp) {
+ return isAddressIpv4(srcIp, dstIp) ? IPPROTO_IP : IPPROTO_IPV6;
+ }
+
+ @NonNull
+ protected ByteBuffer buildUdpPacket(
+ @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
+ @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp,
+ short srcPort, short dstPort, @Nullable final ByteBuffer payload)
+ throws Exception {
+ final int ipProto = getIpProto(srcIp, dstIp);
+ final boolean hasEther = (srcMac != null && dstMac != null);
+ final int payloadLen = (payload == null) ? 0 : payload.limit();
+ final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_UDP,
+ payloadLen);
+ final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+
+ // [1] Ethernet header
+ if (hasEther) {
+ packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp));
+ }
+
+ // [2] IP header
+ if (ipProto == IPPROTO_IP) {
+ packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+ TIME_TO_LIVE, (byte) IPPROTO_UDP, (Inet4Address) srcIp, (Inet4Address) dstIp);
+ } else {
+ packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_UDP,
+ HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp);
+ }
+
+ // [3] UDP header
+ packetBuilder.writeUdpHeader(srcPort, dstPort);
+
+ // [4] Payload
+ if (payload != null) {
+ buffer.put(payload);
+ // in case data might be reused by caller, restore the position and
+ // limit of bytebuffer.
+ payload.clear();
+ }
+
+ return packetBuilder.finalizePacket();
+ }
+
+ @NonNull
+ protected ByteBuffer buildUdpPacket(@NonNull final InetAddress srcIp,
+ @NonNull final InetAddress dstIp, short srcPort, short dstPort,
+ @Nullable final ByteBuffer payload) throws Exception {
+ return buildUdpPacket(null /* srcMac */, null /* dstMac */, srcIp, dstIp, srcPort,
+ dstPort, payload);
+ }
+
+ private boolean isAddressIpv4(@NonNull final InetAddress srcIp,
+ @NonNull final InetAddress dstIp) {
+ if (srcIp instanceof Inet4Address && dstIp instanceof Inet4Address) return true;
+ if (srcIp instanceof Inet6Address && dstIp instanceof Inet6Address) return false;
+
+ fail("Unsupported conditions: srcIp " + srcIp + ", dstIp " + dstIp);
+ return false; // unreachable
+ }
+
+ protected void sendDownloadPacketUdp(@NonNull final InetAddress srcIp,
+ @NonNull final InetAddress dstIp, @NonNull final TetheringTester tester,
+ boolean is6To4) throws Exception {
+ if (is6To4) {
+ assertFalse("CLAT download test must sends IPv6 packet", isAddressIpv4(srcIp, dstIp));
+ }
+
+ // Expected received UDP packet IP protocol. While testing CLAT (is6To4 = true), the packet
+ // on downstream must be IPv4. Otherwise, the IP protocol of test packet is the same on
+ // both downstream and upstream.
+ final boolean isIpv4 = is6To4 ? true : isAddressIpv4(srcIp, dstIp);
+
+ final ByteBuffer testPacket = buildUdpPacket(srcIp, dstIp, REMOTE_PORT /* srcPort */,
+ LOCAL_PORT /* dstPort */, RX_PAYLOAD);
+ tester.verifyDownload(testPacket, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, true /* hasEther */, isIpv4, RX_PAYLOAD);
+ });
+ }
+
+ protected void sendUploadPacketUdp(@NonNull final MacAddress srcMac,
+ @NonNull final MacAddress dstMac, @NonNull final InetAddress srcIp,
+ @NonNull final InetAddress dstIp, @NonNull final TetheringTester tester,
+ boolean is4To6) throws Exception {
+ if (is4To6) {
+ assertTrue("CLAT upload test must sends IPv4 packet", isAddressIpv4(srcIp, dstIp));
+ }
+
+ // Expected received UDP packet IP protocol. While testing CLAT (is4To6 = true), the packet
+ // on upstream must be IPv6. Otherwise, the IP protocol of test packet is the same on
+ // both downstream and upstream.
+ final boolean isIpv4 = is4To6 ? false : isAddressIpv4(srcIp, dstIp);
+
+ final ByteBuffer testPacket = buildUdpPacket(srcMac, dstMac, srcIp, dstIp,
+ LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */, TX_PAYLOAD);
+ tester.verifyUpload(testPacket, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+ return isExpectedUdpPacket(p, false /* hasEther */, isIpv4, TX_PAYLOAD);
+ });
+ }
+
+
+ @NonNull
+ private ByteBuffer buildTcpPacket(
+ @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac,
+ @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp,
+ short srcPort, short dstPort, final short seq, final short ack,
+ final byte tcpFlags, @NonNull final ByteBuffer payload) throws Exception {
+ final int ipProto = getIpProto(srcIp, dstIp);
+ final boolean hasEther = (srcMac != null && dstMac != null);
+ final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_TCP,
+ payload.limit());
+ final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+
+ // [1] Ethernet header
+ if (hasEther) {
+ packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp));
+ }
+
+ // [2] IP header
+ if (ipProto == IPPROTO_IP) {
+ packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+ TIME_TO_LIVE, (byte) IPPROTO_TCP, (Inet4Address) srcIp, (Inet4Address) dstIp);
+ } else {
+ packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_TCP,
+ HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp);
+ }
+
+ // [3] TCP header
+ packetBuilder.writeTcpHeader(srcPort, dstPort, seq, ack, tcpFlags, WINDOW, URGENT_POINTER);
+
+ // [4] Payload
+ buffer.put(payload);
+ // in case data might be reused by caller, restore the position and
+ // limit of bytebuffer.
+ payload.clear();
+
+ return packetBuilder.finalizePacket();
+ }
+
+ protected void sendDownloadPacketTcp(@NonNull final InetAddress srcIp,
+ @NonNull final InetAddress dstIp, short seq, short ack, byte tcpFlags,
+ @NonNull final ByteBuffer payload, @NonNull final TetheringTester tester,
+ boolean is6To4) throws Exception {
+ if (is6To4) {
+ assertFalse("CLAT download test must sends IPv6 packet", isAddressIpv4(srcIp, dstIp));
+ }
+
+ // Expected received TCP packet IP protocol. While testing CLAT (is6To4 = true), the packet
+ // on downstream must be IPv4. Otherwise, the IP protocol of test packet is the same on
+ // both downstream and upstream.
+ final boolean isIpv4 = is6To4 ? true : isAddressIpv4(srcIp, dstIp);
+
+ final ByteBuffer testPacket = buildTcpPacket(null /* srcMac */, null /* dstMac */,
+ srcIp, dstIp, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */, seq, ack,
+ tcpFlags, payload);
+ tester.verifyDownload(testPacket, p -> {
+ Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
+
+ return isExpectedTcpPacket(p, true /* hasEther */, isIpv4, seq, payload);
+ });
+ }
+
+ protected void sendUploadPacketTcp(@NonNull final MacAddress srcMac,
+ @NonNull final MacAddress dstMac, @NonNull final InetAddress srcIp,
+ @NonNull final InetAddress dstIp, short seq, short ack, byte tcpFlags,
+ @NonNull final ByteBuffer payload, @NonNull final TetheringTester tester,
+ boolean is4To6) throws Exception {
+ if (is4To6) {
+ assertTrue("CLAT upload test must sends IPv4 packet", isAddressIpv4(srcIp, dstIp));
+ }
+
+ // Expected received TCP packet IP protocol. While testing CLAT (is4To6 = true), the packet
+ // on upstream must be IPv6. Otherwise, the IP protocol of test packet is the same on
+ // both downstream and upstream.
+ final boolean isIpv4 = is4To6 ? false : isAddressIpv4(srcIp, dstIp);
+
+ final ByteBuffer testPacket = buildTcpPacket(srcMac, dstMac, srcIp, dstIp,
+ LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */, seq, ack, tcpFlags,
+ payload);
+ tester.verifyUpload(testPacket, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+
+ return isExpectedTcpPacket(p, false /* hasEther */, isIpv4, seq, payload);
+ });
+ }
+
+ protected void runTcpTest(
+ @NonNull final MacAddress uploadSrcMac, @NonNull final MacAddress uploadDstMac,
+ @NonNull final InetAddress uploadSrcIp, @NonNull final InetAddress uploadDstIp,
+ @NonNull final InetAddress downloadSrcIp, @NonNull final InetAddress downloadDstIp,
+ @NonNull final TetheringTester tester, boolean isClat) throws Exception {
+ // Three way handshake and data transfer.
+ //
+ // Server (base seq = 2000) Client (base seq = 1000)
+ // | |
+ // | [1] [SYN] SEQ = 1000 |
+ // |<---------------------------------------------------------| -
+ // | | ^
+ // | [2] [SYN + ACK] SEQ = 2000, ACK = 1000+1 | |
+ // |--------------------------------------------------------->| three way handshake
+ // | | |
+ // | [3] [ACK] SEQ = 1001, ACK = 2000+1 | v
+ // |<---------------------------------------------------------| -
+ // | | ^
+ // | [4] [ACK] SEQ = 1001, ACK = 2001, 2 byte payload | |
+ // |<---------------------------------------------------------| data transfer
+ // | | |
+ // | [5] [ACK] SEQ = 2001, ACK = 1001+2, 2 byte payload | v
+ // |--------------------------------------------------------->| -
+ // | |
+ //
+
+ // This test can only verify the packets are transferred end to end but TCP state.
+ // TODO: verify TCP state change via /proc/net/nf_conntrack or netlink conntrack event.
+ // [1] [UPLOAD] [SYN]: SEQ = 1000
+ sendUploadPacketTcp(uploadSrcMac, uploadDstMac, uploadSrcIp, uploadDstIp,
+ (short) 1000 /* seq */, (short) 0 /* ack */, TCPHDR_SYN, EMPTY_PAYLOAD,
+ tester, isClat /* is4To6 */);
+
+ // [2] [DONWLOAD] [SYN + ACK]: SEQ = 2000, ACK = 1001
+ sendDownloadPacketTcp(downloadSrcIp, downloadDstIp, (short) 2000 /* seq */,
+ (short) 1001 /* ack */, (byte) ((TCPHDR_SYN | TCPHDR_ACK) & 0xff), EMPTY_PAYLOAD,
+ tester, isClat /* is6To4 */);
+
+ // [3] [UPLOAD] [ACK]: SEQ = 1001, ACK = 2001
+ sendUploadPacketTcp(uploadSrcMac, uploadDstMac, uploadSrcIp, uploadDstIp,
+ (short) 1001 /* seq */, (short) 2001 /* ack */, TCPHDR_ACK, EMPTY_PAYLOAD, tester,
+ isClat /* is4To6 */);
+
+ // [4] [UPLOAD] [ACK]: SEQ = 1001, ACK = 2001, 2 byte payload
+ sendUploadPacketTcp(uploadSrcMac, uploadDstMac, uploadSrcIp, uploadDstIp,
+ (short) 1001 /* seq */, (short) 2001 /* ack */, TCPHDR_ACK, TX_PAYLOAD,
+ tester, isClat /* is4To6 */);
+
+ // [5] [DONWLOAD] [ACK]: SEQ = 2001, ACK = 1003, 2 byte payload
+ sendDownloadPacketTcp(downloadSrcIp, downloadDstIp, (short) 2001 /* seq */,
+ (short) 1003 /* ack */, TCPHDR_ACK, RX_PAYLOAD, tester, isClat /* is6To4 */);
+
+ // TODO: test BPF offload maps.
+ }
+
+ // TODO: remove ipv4 verification (is4To6 = false) once upstream connected notification race is
+ // fixed. See #runUdp4Test.
+ //
+ // This function sends a probe packet to downstream interface and exam the result from upstream
+ // interface to make sure ipv4 tethering is ready. Return the entire packet which received from
+ // upstream interface.
+ @NonNull
+ protected byte[] probeV4TetheringConnectivity(TetheringTester tester, TetheredDevice tethered,
+ boolean is4To6) throws Exception {
+ final ByteBuffer probePacket = buildUdpPacket(tethered.macAddr,
+ tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */,
+ REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */,
+ TEST_REACHABILITY_PAYLOAD);
+
+ // Send a UDP packet from client and check the packet can be found on upstream interface.
+ for (int i = 0; i < TETHER_REACHABILITY_ATTEMPTS; i++) {
+ byte[] expectedPacket = tester.testUpload(probePacket, p -> {
+ Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
+ // If is4To6 is true, the ipv4 probe packet would be translated to ipv6 by Clat and
+ // would see this translated ipv6 packet in upstream interface.
+ return isExpectedUdpPacket(p, false /* hasEther */, !is4To6 /* isIpv4 */,
+ TEST_REACHABILITY_PAYLOAD);
+ });
+ if (expectedPacket != null) return expectedPacket;
+ }
+
+ fail("Can't verify " + (is4To6 ? "ipv4 to ipv6" : "ipv4") + " tethering connectivity after "
+ + TETHER_REACHABILITY_ATTEMPTS + " attempts");
+ return null;
+ }
+
+ // TODO: remove triggering upstream reselection once test network can replace selected upstream
+ // network in Tethering module.
+ private void maybeRetryTestedUpstreamChanged(final Network expectedUpstream,
+ final TimeoutException fallbackException) throws Exception {
+ // Fall back original exception because no way to reselect if there is no WIFI feature.
+ assertTrue(fallbackException.toString(), mPackageManager.hasSystemFeature(FEATURE_WIFI));
+
+ // Try to toggle wifi network, if any, to reselect upstream network via default network
+ // switching. Because test network has higher priority than internet network, this can
+ // help selecting test network to be upstream network for testing. This tries to avoid
+ // the flaky upstream selection under multinetwork environment. Internet and test network
+ // upstream changed event order is not guaranteed. Once tethering selects non-test
+ // upstream {wifi, ..}, test network won't be selected anymore. If too many test cases
+ // trigger the reselection, the total test time may over test suite 1 minmute timeout.
+ // Probably need to disable/restore all internet networks in a common place of test
+ // process. Currently, EthernetTetheringTest is part of CTS test which needs wifi network
+ // connection if device has wifi feature. CtsNetUtils#toggleWifi() checks wifi connection
+ // during the toggling process.
+ // See Tethering#chooseUpstreamType, CtsNetUtils#toggleWifi.
+ // TODO: toggle cellular network if the device has no WIFI feature.
+ Log.d(TAG, "Toggle WIFI to retry upstream selection");
+ mCtsNetUtils.toggleWifi();
+
+ // Wait for expected upstream.
+ final CompletableFuture<Network> future = new CompletableFuture<>();
+ final TetheringEventCallback callback = new TetheringEventCallback() {
+ @Override
+ public void onUpstreamChanged(Network network) {
+ Log.d(TAG, "Got upstream changed: " + network);
+ if (Objects.equals(expectedUpstream, network)) {
+ future.complete(network);
+ }
+ }
+ };
+ try {
+ mTm.registerTetheringEventCallback(mHandler::post, callback);
+ assertEquals("onUpstreamChanged for unexpected network", expectedUpstream,
+ future.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } catch (TimeoutException e) {
+ throw new AssertionError("Did not receive upstream " + expectedUpstream
+ + " callback after " + TIMEOUT_MS + "ms");
+ } finally {
+ mTm.unregisterTetheringEventCallback(callback);
+ }
+ }
+
+ protected TetheringTester initTetheringTester(List<LinkAddress> upstreamAddresses,
+ List<InetAddress> upstreamDnses) throws Exception {
+ assumeFalse(isInterfaceForTetheringAvailable());
+
+ // MyTetheringEventCallback currently only support await first available upstream. Tethering
+ // may select internet network as upstream if test network is not available and not be
+ // preferred yet. Create test upstream network before enable tethering.
+ mUpstreamTracker = createTestUpstream(upstreamAddresses, upstreamDnses);
+
+ mDownstreamIface = createTestInterface();
+ setIncludeTestInterfaces(true);
+
+ // Make sure EtherentTracker use "mDownstreamIface" as server mode interface.
+ assertEquals("TetheredInterfaceCallback for unexpected interface",
+ mDownstreamIface.getInterfaceName(), mTetheredInterfaceRequester.getInterface());
+
+ mTetheringEventCallback = enableEthernetTethering(mDownstreamIface.getInterfaceName(),
+ mUpstreamTracker.getNetwork());
+
+ try {
+ assertEquals("onUpstreamChanged for test network", mUpstreamTracker.getNetwork(),
+ mTetheringEventCallback.awaitUpstreamChanged(
+ true /* throwTimeoutException */));
+ } catch (TimeoutException e) {
+ // Due to race condition inside tethering module, test network may not be selected as
+ // tethering upstream. Force tethering retry upstream if possible. If it is not
+ // possible to retry, fail the test with the original timeout exception.
+ maybeRetryTestedUpstreamChanged(mUpstreamTracker.getNetwork(), e);
+ }
+
+ mDownstreamReader = makePacketReader(mDownstreamIface);
+ mUpstreamReader = makePacketReader(mUpstreamTracker.getTestIface());
+
+ final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
+ // Currently tethering don't have API to tell when ipv6 tethering is available. Thus, make
+ // sure tethering already have ipv6 connectivity before testing.
+ if (cm.getLinkProperties(mUpstreamTracker.getNetwork()).hasGlobalIpv6Address()) {
+ waitForRouterAdvertisement(mDownstreamReader, mDownstreamIface.getInterfaceName(),
+ WAIT_RA_TIMEOUT_MS);
+ }
+
+ return new TetheringTester(mDownstreamReader, mUpstreamReader);
+ }
+
+ protected <T> List<T> toList(T... array) {
+ return Arrays.asList(array);
+ }
}
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
index d38a7c3..23fb60c 100644
--- a/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/ConntrackSocketTest.java
@@ -16,28 +16,32 @@
package com.android.networkstack.tethering;
+import static android.system.OsConstants.EAGAIN;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.NETLINK_NETFILTER;
+
import static com.android.net.module.util.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE;
-import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
-import static com.android.net.module.util.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 static org.junit.Assert.fail;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.NativeHandle;
-import android.system.Os;
+import android.system.ErrnoException;
+import android.util.Log;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.SharedLog;
+import com.android.net.module.util.netlink.ConntrackMessage;
+import com.android.net.module.util.netlink.NetlinkMessage;
+import com.android.net.module.util.netlink.NetlinkSocket;
import com.android.net.module.util.netlink.StructNlMsgHdr;
import org.junit.Before;
@@ -45,18 +49,18 @@
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
+import java.io.FileDescriptor;
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 static final String TAG = ConntrackSocketTest.class.getSimpleName();
private HandlerThread mHandlerThread;
private Handler mHandler;
@@ -80,51 +84,72 @@
mOffloadHw = new OffloadHardwareInterface(mHandler, mLog, mDeps);
}
+ void findConnectionOrThrow(FileDescriptor fd, InetSocketAddress local, InetSocketAddress remote)
+ throws Exception {
+ Log.d(TAG, "Looking for socket " + local + " -> " + remote);
+
+ // Loop until the socket is found (and return) or recvMessage throws an exception.
+ while (true) {
+ final ByteBuffer buffer = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT);
+
+ // Parse all the netlink messages in the dump.
+ // NetlinkMessage#parse returns null if the message is truncated or invalid.
+ while (buffer.remaining() > 0) {
+ NetlinkMessage nlmsg = NetlinkMessage.parse(buffer, NETLINK_NETFILTER);
+ Log.d(TAG, "Got netlink message: " + nlmsg);
+ if (!(nlmsg instanceof ConntrackMessage)) {
+ continue;
+ }
+
+ StructNlMsgHdr nlmsghdr = nlmsg.getHeader();
+ ConntrackMessage ctmsg = (ConntrackMessage) nlmsg;
+ ConntrackMessage.Tuple tuple = ctmsg.tupleOrig;
+
+ if (nlmsghdr.nlmsg_type == (NFNL_SUBSYS_CTNETLINK << 8 | IPCTNL_MSG_CT_NEW)
+ && tuple.protoNum == IPPROTO_TCP
+ && tuple.srcIp.equals(local.getAddress())
+ && tuple.dstIp.equals(remote.getAddress())
+ && tuple.srcPort == (short) local.getPort()
+ && tuple.dstPort == (short) remote.getPort()) {
+ return;
+ }
+ }
+ }
+ }
+
@Test
public void testIpv4ConntrackSocket() throws Exception {
// Set up server and connect.
- final InetSocketAddress anyAddress = new InetSocketAddress(
- InetAddress.getByName("127.0.0.1"), 0);
+ final InetAddress localhost = InetAddress.getByName("127.0.0.1");
+ final InetSocketAddress anyAddress = new InetSocketAddress(localhost, 0);
final ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(anyAddress);
- final SocketAddress theAddress = serverSocket.getLocalSocketAddress();
+ final InetSocketAddress theAddress =
+ (InetSocketAddress) serverSocket.getLocalSocketAddress();
// Make a connection to the server.
final Socket socket = new Socket();
socket.connect(theAddress);
+ final InetSocketAddress localAddress = (InetSocketAddress) socket.getLocalSocketAddress();
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());
+ mOffloadHw.requestSocketDump(handle);
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;
- }
+ findConnectionOrThrow(handle.getFileDescriptor(), localAddress, theAddress);
+ // No exceptions? Socket was found, test passes.
+ } catch (ErrnoException e) {
+ if (e.errno == EAGAIN) {
+ fail("Did not find socket " + localAddress + "->" + theAddress + " in dump");
+ } else {
+ throw e;
}
} finally {
socket.close();
serverSocket.close();
+ acceptedSocket.close();
}
- assertTrue("Did not receive any NFNL_SUBSYS_CTNETLINK/IPCTNL_MSG_CT_NEW message",
- foundConntrackEntry);
}
}
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index d78fbfd..36d9a63 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -68,7 +68,7 @@
"framework-minus-apex",
"framework-res",
"framework-bluetooth.stubs.module_lib",
- "framework-configinfrastructure",
+ "framework-configinfrastructure.stubs.module_lib",
"framework-connectivity.impl",
"framework-connectivity-t.impl",
"framework-tethering.impl",
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 225fed7..53984a8 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -2180,7 +2180,7 @@
new TetherDevValue(UPSTREAM_IFINDEX));
// dumpCounters
- // The error code is defined in packages/modules/Connectivity/bpf_progs/bpf_tethering.h.
+ // The error code is defined in packages/modules/Connectivity/bpf_progs/offload.h.
mBpfErrorMap.insertEntry(
new S32(0 /* INVALID_IPV4_VERSION */),
new S32(1000 /* count */));
diff --git a/bpf_progs/bpf_shared.h b/bpf_progs/bpf_shared.h
index 7b1106a..cc88680 100644
--- a/bpf_progs/bpf_shared.h
+++ b/bpf_progs/bpf_shared.h
@@ -196,32 +196,4 @@
// Entry in the configuration map that stores which stats map is currently in use.
#define CURRENT_STATS_MAP_CONFIGURATION_KEY 1
-typedef struct {
- uint32_t iif; // The input interface index
- struct in6_addr pfx96; // The source /96 nat64 prefix, bottom 32 bits must be 0
- struct in6_addr local6; // The full 128-bits of the destination IPv6 address
-} ClatIngress6Key;
-STRUCT_SIZE(ClatIngress6Key, 4 + 2 * 16); // 36
-
-typedef struct {
- uint32_t oif; // The output interface to redirect to (0 means don't redirect)
- struct in_addr local4; // The destination IPv4 address
-} ClatIngress6Value;
-STRUCT_SIZE(ClatIngress6Value, 4 + 4); // 8
-
-typedef struct {
- uint32_t iif; // The input interface index
- struct in_addr local4; // The source IPv4 address
-} ClatEgress4Key;
-STRUCT_SIZE(ClatEgress4Key, 4 + 4); // 8
-
-typedef struct {
- uint32_t oif; // The output interface to redirect to
- struct in6_addr local6; // The full 128-bits of the source IPv6 address
- struct in6_addr pfx96; // The destination /96 nat64 prefix, bottom 32 bits must be 0
- bool oifIsEthernet; // Whether the output interface requires ethernet header
- uint8_t pad[3];
-} ClatEgress4Value;
-STRUCT_SIZE(ClatEgress4Value, 4 + 2 * 16 + 1 + 3); // 40
-
#undef STRUCT_SIZE
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index fc10d09..14cddf6 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -35,7 +35,7 @@
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
-#include "bpf_shared.h"
+#include "clatd.h"
#include "clat_mark.h"
// IP flags. (from kernel's include/net/ip.h)
diff --git a/bpf_progs/clatd.h b/bpf_progs/clatd.h
new file mode 100644
index 0000000..b5f1cdc
--- /dev/null
+++ b/bpf_progs/clatd.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include <linux/in.h>
+#include <linux/in6.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+
+// This header file is shared by eBPF kernel programs (C) and netd (C++) and
+// some of the maps are also accessed directly from Java mainline module code.
+//
+// Hence: explicitly pad all relevant structures and assert that their size
+// is the sum of the sizes of their fields.
+#define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
+
+typedef struct {
+ uint32_t iif; // The input interface index
+ struct in6_addr pfx96; // The source /96 nat64 prefix, bottom 32 bits must be 0
+ struct in6_addr local6; // The full 128-bits of the destination IPv6 address
+} ClatIngress6Key;
+STRUCT_SIZE(ClatIngress6Key, 4 + 2 * 16); // 36
+
+typedef struct {
+ uint32_t oif; // The output interface to redirect to (0 means don't redirect)
+ struct in_addr local4; // The destination IPv4 address
+} ClatIngress6Value;
+STRUCT_SIZE(ClatIngress6Value, 4 + 4); // 8
+
+typedef struct {
+ uint32_t iif; // The input interface index
+ struct in_addr local4; // The source IPv4 address
+} ClatEgress4Key;
+STRUCT_SIZE(ClatEgress4Key, 4 + 4); // 8
+
+typedef struct {
+ uint32_t oif; // The output interface to redirect to
+ struct in6_addr local6; // The full 128-bits of the source IPv6 address
+ struct in6_addr pfx96; // The destination /96 nat64 prefix, bottom 32 bits must be 0
+ bool oifIsEthernet; // Whether the output interface requires ethernet header
+ uint8_t pad[3];
+} ClatEgress4Value;
+STRUCT_SIZE(ClatEgress4Value, 4 + 2 * 16 + 1 + 3); // 40
+
+#undef STRUCT_SIZE
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index c7b444d..a8612df 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -48,7 +48,7 @@
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
-#include "bpf_tethering.h"
+#include "offload.h"
// From kernel:include/net/ip.h
#define IP_DF 0x4000 // Flag: "Don't Fragment"
@@ -93,7 +93,7 @@
// Note that pre-T devices with Mediatek chipsets may have a kernel bug (bad patch
// "[ALPS05162612] bpf: fix ubsan error") making it impossible to write to non-zero
-// offset of bpf map ARRAYs. This file (offload.o) loads on T, but luckily this
+// offset of bpf map ARRAYs. This file (offload.o) loads on S+, but luckily this
// array is only written by bpf code, and only read by userspace.
DEFINE_BPF_MAP_RO(tether_error_map, ARRAY, uint32_t, uint32_t, BPF_TETHER_ERR__MAX, TETHERING_GID)
diff --git a/bpf_progs/bpf_tethering.h b/bpf_progs/offload.h
similarity index 100%
rename from bpf_progs/bpf_tethering.h
rename to bpf_progs/offload.h
diff --git a/bpf_progs/test.c b/bpf_progs/test.c
index c11c358..d1f780f 100644
--- a/bpf_progs/test.c
+++ b/bpf_progs/test.c
@@ -46,7 +46,7 @@
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
-#include "bpf_tethering.h"
+#include "offload.h"
// Used only by TetheringPrivilegedTests, not by production code.
DEFINE_BPF_MAP_GRW(tether_downstream6_map, HASH, TetherDownstream6Key, Tether6Value, 16,
diff --git a/service/Android.bp b/service/Android.bp
index 50fb4f5..98bbbac 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -179,10 +179,7 @@
"networkstack-client",
"PlatformProperties",
"service-connectivity-protos",
- // TODO: Adding the stats protos currently affects test coverage.
- // So remove the protos in ConnectivityService until all
- // tests for proto apis are added.
- //"service-connectivity-stats-protos",
+ "service-connectivity-stats-protos",
"NetworkStackApiStableShims",
],
apex_available: [
@@ -206,6 +203,7 @@
libs: [
"framework-annotations-lib",
"framework-connectivity-pre-jarjar",
+ "framework-tethering",
"framework-wifi",
"service-connectivity-pre-jarjar",
],
diff --git a/service/mdns/com/android/server/connectivity/mdns/ConnectivityMonitor.java b/service/mdns/com/android/server/connectivity/mdns/ConnectivityMonitor.java
index 2b99d0a..1623669 100644
--- a/service/mdns/com/android/server/connectivity/mdns/ConnectivityMonitor.java
+++ b/service/mdns/com/android/server/connectivity/mdns/ConnectivityMonitor.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity.mdns;
+import android.net.Network;
+
/** Interface for monitoring connectivity changes. */
public interface ConnectivityMonitor {
/**
@@ -29,6 +31,9 @@
void notifyConnectivityChange();
+ /** Get available network which is received from connectivity change. */
+ Network getAvailableNetwork();
+
/** Listener interface for receiving connectivity changes. */
interface Listener {
void onConnectivityChanged();
diff --git a/service/mdns/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java b/service/mdns/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java
index 3563d61..551e3db 100644
--- a/service/mdns/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java
+++ b/service/mdns/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManager.java
@@ -16,6 +16,7 @@
package com.android.server.connectivity.mdns;
+import android.annotation.Nullable;
import android.annotation.TargetApi;
import android.content.Context;
import android.net.ConnectivityManager;
@@ -37,6 +38,7 @@
// TODO(b/71901993): Ideally we shouldn't need this flag. However we still don't have clues why
// the receiver is unregistered twice yet.
private boolean isCallbackRegistered = false;
+ private Network lastAvailableNetwork = null;
@SuppressWarnings({"nullness:assignment", "nullness:method.invocation"})
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@@ -50,6 +52,7 @@
@Override
public void onAvailable(Network network) {
LOGGER.log("network available.");
+ lastAvailableNetwork = network;
notifyConnectivityChange();
}
@@ -103,4 +106,10 @@
connectivityManager.unregisterNetworkCallback(networkCallback);
isCallbackRegistered = false;
}
+
+ @Override
+ @Nullable
+ public Network getAvailableNetwork() {
+ return lastAvailableNetwork;
+ }
}
\ No newline at end of file
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java b/service/mdns/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
index 1faa6ce..0f3c23a 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsDiscoveryManager.java
@@ -21,6 +21,7 @@
import android.annotation.RequiresPermission;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.connectivity.mdns.util.MdnsLogger;
@@ -34,7 +35,8 @@
* notify them when a mDNS service instance is found, updated, or removed?
*/
public class MdnsDiscoveryManager implements MdnsSocketClient.Callback {
-
+ private static final String TAG = MdnsDiscoveryManager.class.getSimpleName();
+ public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private static final MdnsLogger LOGGER = new MdnsLogger("MdnsDiscoveryManager");
private final ExecutorProvider executorProvider;
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java b/service/mdns/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
new file mode 100644
index 0000000..6090415
--- /dev/null
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsInterfaceSocket.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2022 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.server.connectivity.mdns;
+
+import static com.android.server.connectivity.mdns.MdnsSocket.MULTICAST_IPV4_ADDRESS;
+import static com.android.server.connectivity.mdns.MdnsSocket.MULTICAST_IPV6_ADDRESS;
+
+import android.annotation.NonNull;
+import android.net.LinkAddress;
+import android.net.util.SocketUtils;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetSocketAddress;
+import java.net.MulticastSocket;
+import java.net.NetworkInterface;
+import java.util.List;
+
+/**
+ * {@link MdnsInterfaceSocket} provides a similar interface to {@link MulticastSocket} and binds to
+ * an available multicast network interfaces.
+ *
+ * <p>This isn't thread safe and should be always called on the same thread unless specified
+ * otherwise.
+ *
+ * @see MulticastSocket for javadoc of each public method.
+ */
+public class MdnsInterfaceSocket {
+ private static final String TAG = MdnsInterfaceSocket.class.getSimpleName();
+ @NonNull private final MulticastSocket mMulticastSocket;
+ @NonNull private final NetworkInterface mNetworkInterface;
+ private boolean mJoinedIpv4 = false;
+ private boolean mJoinedIpv6 = false;
+
+ public MdnsInterfaceSocket(@NonNull NetworkInterface networkInterface, int port)
+ throws IOException {
+ mNetworkInterface = networkInterface;
+ mMulticastSocket = new MulticastSocket(port);
+ // RFC Spec: https://tools.ietf.org/html/rfc6762. Time to live is set 255
+ mMulticastSocket.setTimeToLive(255);
+ mMulticastSocket.setNetworkInterface(networkInterface);
+
+ // Bind socket to the interface for receiving from that interface only.
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(mMulticastSocket)) {
+ SocketUtils.bindSocketToInterface(pfd.getFileDescriptor(), mNetworkInterface.getName());
+ } catch (ErrnoException e) {
+ throw new IOException("Error setting socket options", e);
+ }
+ }
+
+ /**
+ * Sends a datagram packet from this socket.
+ *
+ * <p>This method could be used on any thread.
+ */
+ public void send(@NonNull DatagramPacket packet) throws IOException {
+ mMulticastSocket.send(packet);
+ }
+
+ /**
+ * Receives a datagram packet from this socket.
+ *
+ * <p>This method could be used on any thread.
+ */
+ public void receive(@NonNull DatagramPacket packet) throws IOException {
+ mMulticastSocket.receive(packet);
+ }
+
+ private boolean hasIpv4Address(List<LinkAddress> addresses) {
+ for (LinkAddress address : addresses) {
+ if (address.isIpv4()) return true;
+ }
+ return false;
+ }
+
+ private boolean hasIpv6Address(List<LinkAddress> addresses) {
+ for (LinkAddress address : addresses) {
+ if (address.isIpv6()) return true;
+ }
+ return false;
+ }
+
+ /*** Joins both IPv4 and IPv6 multicast groups. */
+ public void joinGroup(@NonNull List<LinkAddress> addresses) {
+ maybeJoinIpv4(addresses);
+ maybeJoinIpv6(addresses);
+ }
+
+ private boolean joinGroup(InetSocketAddress multicastAddress) {
+ try {
+ mMulticastSocket.joinGroup(multicastAddress, mNetworkInterface);
+ return true;
+ } catch (IOException e) {
+ // The address may have just been removed
+ Log.e(TAG, "Error joining multicast group for " + mNetworkInterface, e);
+ return false;
+ }
+ }
+
+ private void maybeJoinIpv4(List<LinkAddress> addresses) {
+ final boolean hasAddr = hasIpv4Address(addresses);
+ if (!mJoinedIpv4 && hasAddr) {
+ mJoinedIpv4 = joinGroup(MULTICAST_IPV4_ADDRESS);
+ } else if (!hasAddr) {
+ // Lost IPv4 address
+ mJoinedIpv4 = false;
+ }
+ }
+
+ private void maybeJoinIpv6(List<LinkAddress> addresses) {
+ final boolean hasAddr = hasIpv6Address(addresses);
+ if (!mJoinedIpv6 && hasAddr) {
+ mJoinedIpv6 = joinGroup(MULTICAST_IPV6_ADDRESS);
+ } else if (!hasAddr) {
+ // Lost IPv6 address
+ mJoinedIpv6 = false;
+ }
+ }
+
+ /*** Destroy this socket by leaving all joined multicast groups and closing this socket. */
+ public void destroy() {
+ if (mJoinedIpv4) {
+ try {
+ mMulticastSocket.leaveGroup(MULTICAST_IPV4_ADDRESS, mNetworkInterface);
+ } catch (IOException e) {
+ Log.e(TAG, "Error leaving IPv4 group for " + mNetworkInterface, e);
+ }
+ }
+ if (mJoinedIpv6) {
+ try {
+ mMulticastSocket.leaveGroup(MULTICAST_IPV6_ADDRESS, mNetworkInterface);
+ } catch (IOException e) {
+ Log.e(TAG, "Error leaving IPv4 group for " + mNetworkInterface, e);
+ }
+ }
+ mMulticastSocket.close();
+ }
+
+ /**
+ * Returns the index of the network interface that this socket is bound to. If the interface
+ * cannot be determined, returns -1.
+ *
+ * <p>This method could be used on any thread.
+ */
+ public int getInterfaceIndex() {
+ return mNetworkInterface.getIndex();
+ }
+
+ /*** Returns whether this socket has joined IPv4 group */
+ public boolean hasJoinedIpv4() {
+ return mJoinedIpv4;
+ }
+
+ /*** Returns whether this socket has joined IPv6 group */
+ public boolean hasJoinedIpv6() {
+ return mJoinedIpv6;
+ }
+}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsResponse.java b/service/mdns/com/android/server/connectivity/mdns/MdnsResponse.java
index 623168c..3a41978 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsResponse.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsResponse.java
@@ -17,6 +17,7 @@
package com.android.server.connectivity.mdns;
import android.annotation.Nullable;
+import android.net.Network;
import com.android.internal.annotations.VisibleForTesting;
@@ -35,13 +36,16 @@
private MdnsInetAddressRecord inet4AddressRecord;
private MdnsInetAddressRecord inet6AddressRecord;
private long lastUpdateTime;
- private int interfaceIndex = MdnsSocket.INTERFACE_INDEX_UNSPECIFIED;
+ private final int interfaceIndex;
+ @Nullable private final Network network;
/** Constructs a new, empty response. */
- public MdnsResponse(long now) {
+ public MdnsResponse(long now, int interfaceIndex, @Nullable Network network) {
lastUpdateTime = now;
records = new LinkedList<>();
pointerRecords = new LinkedList<>();
+ this.interfaceIndex = interfaceIndex;
+ this.network = network;
}
// This generic typed helper compares records for equality.
@@ -208,21 +212,21 @@
}
/**
- * Updates the index of the network interface at which this response was received. Can be set to
- * {@link MdnsSocket#INTERFACE_INDEX_UNSPECIFIED} if unset.
- */
- public synchronized void setInterfaceIndex(int interfaceIndex) {
- this.interfaceIndex = interfaceIndex;
- }
-
- /**
* Returns the index of the network interface at which this response was received. Can be set to
* {@link MdnsSocket#INTERFACE_INDEX_UNSPECIFIED} if unset.
*/
- public synchronized int getInterfaceIndex() {
+ public int getInterfaceIndex() {
return interfaceIndex;
}
+ /**
+ * Returns the network at which this response was received, or null if the network is unknown.
+ */
+ @Nullable
+ public Network getNetwork() {
+ return network;
+ }
+
/** Gets the IPv6 address record. */
public synchronized MdnsInetAddressRecord getInet6AddressRecord() {
return inet6AddressRecord;
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsResponseDecoder.java b/service/mdns/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
index 6c2bc19..7cf84f6 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.net.Network;
import android.os.SystemClock;
import com.android.server.connectivity.mdns.util.MdnsLogger;
@@ -95,10 +96,11 @@
* @param packet The packet to read from.
* @param interfaceIndex the network interface index (or {@link
* MdnsSocket#INTERFACE_INDEX_UNSPECIFIED} if not known) at which the packet was received
+ * @param network the network at which the packet was received, or null if it is unknown.
* @return A list of mDNS responses, or null if the packet contained no appropriate responses.
*/
public int decode(@NonNull DatagramPacket packet, @NonNull List<MdnsResponse> responses,
- int interfaceIndex) {
+ int interfaceIndex, @Nullable Network network) {
MdnsPacketReader reader = new MdnsPacketReader(packet);
List<MdnsRecord> records;
@@ -253,12 +255,11 @@
MdnsResponse response = findResponseWithPointer(responses,
pointerRecord.getPointer());
if (response == null) {
- response = new MdnsResponse(now);
+ response = new MdnsResponse(now, interfaceIndex, network);
responses.add(response);
}
// Set interface index earlier because some responses have PTR record only.
// Need to know every response is getting from which interface.
- response.setInterfaceIndex(interfaceIndex);
response.addPointerRecord((MdnsPointerRecord) record);
}
}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsSearchOptions.java b/service/mdns/com/android/server/connectivity/mdns/MdnsSearchOptions.java
index 195bc8e..583c4a9 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsSearchOptions.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsSearchOptions.java
@@ -17,6 +17,8 @@
package com.android.server.connectivity.mdns;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -43,7 +45,8 @@
@Override
public MdnsSearchOptions createFromParcel(Parcel source) {
return new MdnsSearchOptions(source.createStringArrayList(),
- source.readBoolean(), source.readBoolean());
+ source.readBoolean(), source.readBoolean(),
+ source.readParcelable(null));
}
@Override
@@ -56,15 +59,19 @@
private final boolean isPassiveMode;
private final boolean removeExpiredService;
+ // The target network for searching. Null network means search on all possible interfaces.
+ @Nullable private final Network mNetwork;
- /** Parcelable constructs for a {@link MdnsServiceInfo}. */
- MdnsSearchOptions(List<String> subtypes, boolean isPassiveMode, boolean removeExpiredService) {
+ /** Parcelable constructs for a {@link MdnsSearchOptions}. */
+ MdnsSearchOptions(List<String> subtypes, boolean isPassiveMode, boolean removeExpiredService,
+ @Nullable Network network) {
this.subtypes = new ArrayList<>();
if (subtypes != null) {
this.subtypes.addAll(subtypes);
}
this.isPassiveMode = isPassiveMode;
this.removeExpiredService = removeExpiredService;
+ mNetwork = network;
}
/** Returns a {@link Builder} for {@link MdnsSearchOptions}. */
@@ -98,6 +105,16 @@
return removeExpiredService;
}
+ /**
+ * Returns the network which the mdns query should target on.
+ *
+ * @return the target network or null if search on all possible interfaces.
+ */
+ @Nullable
+ public Network getNetwork() {
+ return mNetwork;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -108,6 +125,7 @@
out.writeStringList(subtypes);
out.writeBoolean(isPassiveMode);
out.writeBoolean(removeExpiredService);
+ out.writeParcelable(mNetwork, 0);
}
/** A builder to create {@link MdnsSearchOptions}. */
@@ -115,6 +133,7 @@
private final Set<String> subtypes;
private boolean isPassiveMode = true;
private boolean removeExpiredService;
+ private Network mNetwork;
private Builder() {
subtypes = new ArraySet<>();
@@ -165,10 +184,20 @@
return this;
}
+ /**
+ * Sets if the mdns query should target on specific network.
+ *
+ * @param network the mdns query will target on given network.
+ */
+ public Builder setNetwork(Network network) {
+ mNetwork = network;
+ return this;
+ }
+
/** Builds a {@link MdnsSearchOptions} with the arguments supplied to this builder. */
public MdnsSearchOptions build() {
- return new MdnsSearchOptions(
- new ArrayList<>(subtypes), isPassiveMode, removeExpiredService);
+ return new MdnsSearchOptions(new ArrayList<>(subtypes), isPassiveMode,
+ removeExpiredService, mNetwork);
}
}
}
\ No newline at end of file
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceBrowserListener.java b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceBrowserListener.java
index 7a8fcc0..7c19359 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceBrowserListener.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceBrowserListener.java
@@ -28,21 +28,24 @@
public interface MdnsServiceBrowserListener {
/**
- * Called when an mDNS service instance is found.
+ * Called when an mDNS service instance is found. This method would be called only if all
+ * service records (PTR, SRV, TXT, A or AAAA) are received .
*
* @param serviceInfo The found mDNS service instance.
*/
void onServiceFound(@NonNull MdnsServiceInfo serviceInfo);
/**
- * Called when an mDNS service instance is updated.
+ * Called when an mDNS service instance is updated. This method would be called only if all
+ * service records (PTR, SRV, TXT, A or AAAA) are received before.
*
* @param serviceInfo The updated mDNS service instance.
*/
void onServiceUpdated(@NonNull MdnsServiceInfo serviceInfo);
/**
- * Called when an mDNS service instance is no longer valid and removed.
+ * Called when a mDNS service instance is no longer valid and removed. This method would be
+ * called only if all service records (PTR, SRV, TXT, A or AAAA) are received before.
*
* @param serviceInfo The service instance of the removed mDNS service.
*/
@@ -75,4 +78,19 @@
* @param errorCode The error code, defined in {@link MdnsResponseErrorCode}.
*/
void onFailedToParseMdnsResponse(int receivedPacketNumber, int errorCode);
+
+ /**
+ * Called when a mDNS service instance is discovered. This method would be called if the PTR
+ * record has been received.
+ *
+ * @param serviceInfo The discovered mDNS service instance.
+ */
+ void onServiceNameDiscovered(@NonNull MdnsServiceInfo serviceInfo);
+
+ /**
+ * Called when a discovered mDNS service instance is no longer valid and removed.
+ *
+ * @param serviceInfo The service instance of the removed mDNS service.
+ */
+ void onServiceNameRemoved(@NonNull MdnsServiceInfo serviceInfo);
}
\ No newline at end of file
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceInfo.java b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceInfo.java
index f1b2def..938fc3f 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceInfo.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceInfo.java
@@ -16,8 +16,11 @@
package com.android.server.connectivity.mdns;
+import static com.android.server.connectivity.mdns.MdnsSocket.INTERFACE_INDEX_UNSPECIFIED;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.net.Network;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -58,7 +61,8 @@
source.readString(),
source.createStringArrayList(),
source.createTypedArrayList(TextEntry.CREATOR),
- source.readInt());
+ source.readInt(),
+ source.readParcelable(null));
}
@Override
@@ -82,6 +86,8 @@
private final int interfaceIndex;
private final Map<String, byte[]> attributes;
+ @Nullable
+ private final Network network;
/** Constructs a {@link MdnsServiceInfo} object with default values. */
public MdnsServiceInfo(
@@ -103,7 +109,8 @@
ipv6Address,
textStrings,
/* textEntries= */ null,
- /* interfaceIndex= */ -1);
+ /* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
+ /* network= */ null);
}
/** Constructs a {@link MdnsServiceInfo} object with default values. */
@@ -127,7 +134,8 @@
ipv6Address,
textStrings,
textEntries,
- /* interfaceIndex= */ -1);
+ /* interfaceIndex= */ INTERFACE_INDEX_UNSPECIFIED,
+ /* network= */ null);
}
/**
@@ -146,6 +154,37 @@
@Nullable List<String> textStrings,
@Nullable List<TextEntry> textEntries,
int interfaceIndex) {
+ this(
+ serviceInstanceName,
+ serviceType,
+ subtypes,
+ hostName,
+ port,
+ ipv4Address,
+ ipv6Address,
+ textStrings,
+ textEntries,
+ interfaceIndex,
+ /* network= */ null);
+ }
+
+ /**
+ * Constructs a {@link MdnsServiceInfo} object with default values.
+ *
+ * @hide
+ */
+ public MdnsServiceInfo(
+ String serviceInstanceName,
+ String[] serviceType,
+ @Nullable List<String> subtypes,
+ String[] hostName,
+ int port,
+ @Nullable String ipv4Address,
+ @Nullable String ipv6Address,
+ @Nullable List<String> textStrings,
+ @Nullable List<TextEntry> textEntries,
+ int interfaceIndex,
+ @Nullable Network network) {
this.serviceInstanceName = serviceInstanceName;
this.serviceType = serviceType;
this.subtypes = new ArrayList<>();
@@ -180,6 +219,7 @@
}
this.attributes = Collections.unmodifiableMap(attributes);
this.interfaceIndex = interfaceIndex;
+ this.network = network;
}
private static List<TextEntry> parseTextStrings(List<String> textStrings) {
@@ -244,6 +284,14 @@
}
/**
+ * Returns the network at which this response was received, or null if the network is unknown.
+ */
+ @Nullable
+ public Network getNetwork() {
+ return network;
+ }
+
+ /**
* Returns attribute value for {@code key} as a UTF-8 string. It's the caller who must make sure
* that the value of {@code key} is indeed a UTF-8 string. {@code null} will be returned if no
* attribute value exists for {@code key}.
@@ -270,7 +318,8 @@
public Map<String, String> getAttributes() {
Map<String, String> map = new HashMap<>(attributes.size());
for (Map.Entry<String, byte[]> kv : attributes.entrySet()) {
- map.put(kv.getKey(), new String(kv.getValue(), UTF_8));
+ final byte[] value = kv.getValue();
+ map.put(kv.getKey(), value == null ? null : new String(value, UTF_8));
}
return Collections.unmodifiableMap(map);
}
@@ -292,6 +341,7 @@
out.writeStringList(textStrings);
out.writeTypedList(textEntries);
out.writeInt(interfaceIndex);
+ out.writeParcelable(network, 0);
}
@Override
@@ -342,7 +392,7 @@
// 2. If there is no '=' in a DNS-SD TXT record string, then it is a
// boolean attribute, simply identified as being present, with no value.
if (delimitPos < 0) {
- return new TextEntry(new String(textBytes, US_ASCII), "");
+ return new TextEntry(new String(textBytes, US_ASCII), (byte[]) null);
} else if (delimitPos == 0) {
return null;
}
@@ -353,13 +403,13 @@
/** Creates a new {@link TextEntry} with given key and value of a UTF-8 string. */
public TextEntry(String key, String value) {
- this(key, value.getBytes(UTF_8));
+ this(key, value == null ? null : value.getBytes(UTF_8));
}
/** Creates a new {@link TextEntry} with given key and value of a byte array. */
public TextEntry(String key, byte[] value) {
this.key = key;
- this.value = value.clone();
+ this.value = value == null ? null : value.clone();
}
private TextEntry(Parcel in) {
@@ -372,17 +422,24 @@
}
public byte[] getValue() {
- return value.clone();
+ return value == null ? null : value.clone();
}
/** Converts this {@link TextEntry} instance to '=' separated byte array. */
public byte[] toBytes() {
- return ByteUtils.concat(key.getBytes(US_ASCII), new byte[]{'='}, value);
+ final byte[] keyBytes = key.getBytes(US_ASCII);
+ if (value == null) {
+ return keyBytes;
+ }
+ return ByteUtils.concat(keyBytes, new byte[]{'='}, value);
}
/** Converts this {@link TextEntry} instance to '=' separated string. */
@Override
public String toString() {
+ if (value == null) {
+ return key;
+ }
return key + "=" + new String(value, UTF_8);
}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index 0fd6025..538f376 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -130,7 +130,8 @@
ipv6Address,
textStrings,
textEntries,
- response.getInterfaceIndex());
+ response.getInterfaceIndex(),
+ response.getNetwork());
}
/**
@@ -148,10 +149,11 @@
this.searchOptions = searchOptions;
if (listeners.add(listener)) {
for (MdnsResponse existingResponse : instanceNameToResponse.values()) {
+ final MdnsServiceInfo info =
+ buildMdnsServiceInfoFromResponse(existingResponse, serviceTypeLabels);
+ listener.onServiceNameDiscovered(info);
if (existingResponse.isComplete()) {
- listener.onServiceFound(
- buildMdnsServiceInfoFromResponse(existingResponse,
- serviceTypeLabels));
+ listener.onServiceFound(info);
}
}
}
@@ -226,6 +228,7 @@
boolean newServiceFound = false;
boolean existingServiceChanged = false;
+ boolean serviceBecomesComplete = false;
if (currentResponse == null) {
newServiceFound = true;
currentResponse = response;
@@ -233,10 +236,13 @@
if (serviceInstanceName != null) {
instanceNameToResponse.put(serviceInstanceName, currentResponse);
}
- } else if (currentResponse.mergeRecordsFrom(response)) {
- existingServiceChanged = true;
+ } else {
+ boolean before = currentResponse.isComplete();
+ existingServiceChanged = currentResponse.mergeRecordsFrom(response);
+ boolean after = currentResponse.isComplete();
+ serviceBecomesComplete = !before && after;
}
- if (!currentResponse.isComplete() || (!newServiceFound && !existingServiceChanged)) {
+ if (!newServiceFound && !existingServiceChanged) {
return;
}
MdnsServiceInfo serviceInfo =
@@ -244,9 +250,15 @@
for (MdnsServiceBrowserListener listener : listeners) {
if (newServiceFound) {
- listener.onServiceFound(serviceInfo);
- } else {
- listener.onServiceUpdated(serviceInfo);
+ listener.onServiceNameDiscovered(serviceInfo);
+ }
+
+ if (currentResponse.isComplete()) {
+ if (newServiceFound || serviceBecomesComplete) {
+ listener.onServiceFound(serviceInfo);
+ } else {
+ listener.onServiceUpdated(serviceInfo);
+ }
}
}
}
@@ -259,7 +271,10 @@
for (MdnsServiceBrowserListener listener : listeners) {
final MdnsServiceInfo serviceInfo =
buildMdnsServiceInfoFromResponse(response, serviceTypeLabels);
- listener.onServiceRemoved(serviceInfo);
+ if (response.isComplete()) {
+ listener.onServiceRemoved(serviceInfo);
+ }
+ listener.onServiceNameRemoved(serviceInfo);
}
}
@@ -423,7 +438,7 @@
Iterator<MdnsResponse> iter = instanceNameToResponse.values().iterator();
while (iter.hasNext()) {
MdnsResponse existingResponse = iter.next();
- if (existingResponse.isComplete()
+ if (existingResponse.hasServiceRecord()
&& existingResponse
.getServiceRecord()
.getRemainingTTL(SystemClock.elapsedRealtime())
@@ -436,7 +451,10 @@
final MdnsServiceInfo serviceInfo =
buildMdnsServiceInfoFromResponse(
existingResponse, serviceTypeLabels);
- listener.onServiceRemoved(serviceInfo);
+ if (existingResponse.isComplete()) {
+ listener.onServiceRemoved(serviceInfo);
+ }
+ listener.onServiceNameRemoved(serviceInfo);
}
}
}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsSocket.java b/service/mdns/com/android/server/connectivity/mdns/MdnsSocket.java
index 0a9b2fc..64c4495 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsSocket.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsSocket.java
@@ -17,6 +17,8 @@
package com.android.server.connectivity.mdns;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.connectivity.mdns.util.MdnsLogger;
@@ -38,9 +40,9 @@
private static final MdnsLogger LOGGER = new MdnsLogger("MdnsSocket");
static final int INTERFACE_INDEX_UNSPECIFIED = -1;
- private static final InetSocketAddress MULTICAST_IPV4_ADDRESS =
+ protected static final InetSocketAddress MULTICAST_IPV4_ADDRESS =
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT);
- private static final InetSocketAddress MULTICAST_IPV6_ADDRESS =
+ protected static final InetSocketAddress MULTICAST_IPV6_ADDRESS =
new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT);
private final MulticastNetworkInterfaceProvider multicastNetworkInterfaceProvider;
private final MulticastSocket multicastSocket;
@@ -125,6 +127,14 @@
}
}
+ /**
+ * Returns the available network that this socket is used to, or null if the network is unknown.
+ */
+ @Nullable
+ public Network getNetwork() {
+ return multicastNetworkInterfaceProvider.getAvailableNetwork();
+ }
+
public boolean isOnIPv6OnlyNetwork() {
return isOnIPv6OnlyNetwork;
}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsSocketClient.java b/service/mdns/com/android/server/connectivity/mdns/MdnsSocketClient.java
index 758221a..6a321d1 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsSocketClient.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsSocketClient.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.Context;
+import android.net.Network;
import android.net.wifi.WifiManager.MulticastLock;
import android.os.SystemClock;
import android.text.format.DateUtils;
@@ -397,7 +398,8 @@
responseType,
/* interfaceIndex= */ (socket == null || !propagateInterfaceIndex)
? MdnsSocket.INTERFACE_INDEX_UNSPECIFIED
- : socket.getInterfaceIndex());
+ : socket.getInterfaceIndex(),
+ /* network= */ socket.getNetwork());
}
} catch (IOException e) {
if (!shouldStopSocketLoop) {
@@ -408,12 +410,12 @@
LOGGER.log("Receive thread stopped.");
}
- private int processResponsePacket(
- @NonNull DatagramPacket packet, String responseType, int interfaceIndex) {
+ private int processResponsePacket(@NonNull DatagramPacket packet, String responseType,
+ int interfaceIndex, @Nullable Network network) {
int packetNumber = ++receivedPacketNumber;
List<MdnsResponse> responses = new LinkedList<>();
- int errorCode = responseDecoder.decode(packet, responses, interfaceIndex);
+ int errorCode = responseDecoder.decode(packet, responses, interfaceIndex, network);
if (errorCode == MdnsResponseDecoder.SUCCESS) {
if (responseType.equals(MULTICAST_TYPE)) {
receivedMulticastResponse = true;
diff --git a/service/mdns/com/android/server/connectivity/mdns/MdnsSocketProvider.java b/service/mdns/com/android/server/connectivity/mdns/MdnsSocketProvider.java
new file mode 100644
index 0000000..b8c324e
--- /dev/null
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsSocketProvider.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2022 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.server.connectivity.mdns;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.INetd;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.TetheringManager;
+import android.net.TetheringManager.TetheringEventCallback;
+import android.os.Handler;
+import android.os.Looper;
+import android.system.OsConstants;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
+import com.android.net.module.util.ip.NetlinkMonitor;
+import com.android.net.module.util.netlink.NetlinkConstants;
+import com.android.net.module.util.netlink.NetlinkMessage;
+import com.android.server.connectivity.mdns.util.MdnsLogger;
+
+import java.io.IOException;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The {@link MdnsSocketProvider} manages the multiple sockets for mDns.
+ *
+ * <p>This class is not thread safe, it is intended to be used only from the looper thread.
+ * However, the constructor is an exception, as it is called on another thread;
+ * therefore for thread safety all members of this class MUST either be final or initialized
+ * to their default value (0, false or null).
+ *
+ */
+public class MdnsSocketProvider {
+ private static final String TAG = MdnsSocketProvider.class.getSimpleName();
+ private static final boolean DBG = MdnsDiscoveryManager.DBG;
+ private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
+ @NonNull private final Context mContext;
+ @NonNull private final Handler mHandler;
+ @NonNull private final Dependencies mDependencies;
+ @NonNull private final NetworkCallback mNetworkCallback;
+ @NonNull private final TetheringEventCallback mTetheringEventCallback;
+ @NonNull private final NetlinkMonitor mNetlinkMonitor;
+ private final ArrayMap<Network, SocketInfo> mNetworkSockets = new ArrayMap<>();
+ private final ArrayMap<String, SocketInfo> mTetherInterfaceSockets = new ArrayMap<>();
+ private final ArrayMap<Network, LinkProperties> mActiveNetworksLinkProperties =
+ new ArrayMap<>();
+ private final ArrayMap<SocketCallback, Network> mCallbacksToRequestedNetworks =
+ new ArrayMap<>();
+ private final List<String> mLocalOnlyInterfaces = new ArrayList<>();
+ private final List<String> mTetheredInterfaces = new ArrayList<>();
+ private boolean mMonitoringSockets = false;
+
+ public MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper) {
+ this(context, looper, new Dependencies());
+ }
+
+ MdnsSocketProvider(@NonNull Context context, @NonNull Looper looper,
+ @NonNull Dependencies deps) {
+ mContext = context;
+ mHandler = new Handler(looper);
+ mDependencies = deps;
+ mNetworkCallback = new NetworkCallback() {
+ @Override
+ public void onLost(Network network) {
+ mActiveNetworksLinkProperties.remove(network);
+ removeSocket(network, null /* interfaceName */);
+ }
+
+ @Override
+ public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
+ handleLinkPropertiesChanged(network, lp);
+ }
+ };
+ mTetheringEventCallback = new TetheringEventCallback() {
+ @Override
+ public void onLocalOnlyInterfacesChanged(@NonNull List<String> interfaces) {
+ handleTetherInterfacesChanged(mLocalOnlyInterfaces, interfaces);
+ }
+
+ @Override
+ public void onTetheredInterfacesChanged(@NonNull List<String> interfaces) {
+ handleTetherInterfacesChanged(mTetheredInterfaces, interfaces);
+ }
+ };
+
+ mNetlinkMonitor = new SocketNetlinkMonitor(mHandler);
+ }
+
+ /**
+ * Dependencies of MdnsSocketProvider, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /*** Get network interface by given interface name */
+ public NetworkInterfaceWrapper getNetworkInterfaceByName(String interfaceName)
+ throws SocketException {
+ final NetworkInterface ni = NetworkInterface.getByName(interfaceName);
+ return ni == null ? null : new NetworkInterfaceWrapper(ni);
+ }
+
+ /*** Check whether given network interface can support mdns */
+ public boolean canScanOnInterface(NetworkInterfaceWrapper networkInterface) {
+ return MulticastNetworkInterfaceProvider.canScanOnInterface(networkInterface);
+ }
+
+ /*** Create a MdnsInterfaceSocket */
+ public MdnsInterfaceSocket createMdnsInterfaceSocket(NetworkInterface networkInterface,
+ int port) throws IOException {
+ return new MdnsInterfaceSocket(networkInterface, port);
+ }
+ }
+
+ /*** Data class for storing socket related info */
+ private static class SocketInfo {
+ final MdnsInterfaceSocket mSocket;
+ final List<LinkAddress> mAddresses = new ArrayList<>();
+
+ SocketInfo(MdnsInterfaceSocket socket, List<LinkAddress> addresses) {
+ mSocket = socket;
+ mAddresses.addAll(addresses);
+ }
+ }
+
+ private static class SocketNetlinkMonitor extends NetlinkMonitor {
+ SocketNetlinkMonitor(Handler handler) {
+ super(handler, LOGGER.mLog, TAG, OsConstants.NETLINK_ROUTE,
+ NetlinkConstants.RTMGRP_IPV4_IFADDR | NetlinkConstants.RTMGRP_IPV6_IFADDR);
+ }
+
+ @Override
+ public void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) {
+ // TODO: Handle netlink message.
+ }
+ }
+
+ private void ensureRunningOnHandlerThread() {
+ if (mHandler.getLooper().getThread() != Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Not running on Handler thread: " + Thread.currentThread().getName());
+ }
+ }
+
+ /*** Start monitoring sockets by listening callbacks for sockets creation or removal */
+ public void startMonitoringSockets() {
+ ensureRunningOnHandlerThread();
+ if (mMonitoringSockets) {
+ Log.d(TAG, "Already monitoring sockets.");
+ return;
+ }
+ if (DBG) Log.d(TAG, "Start monitoring sockets.");
+ mContext.getSystemService(ConnectivityManager.class).registerNetworkCallback(
+ new NetworkRequest.Builder().clearCapabilities().build(),
+ mNetworkCallback, mHandler);
+
+ final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
+ tetheringManager.registerTetheringEventCallback(mHandler::post, mTetheringEventCallback);
+
+ mHandler.post(mNetlinkMonitor::start);
+ mMonitoringSockets = true;
+ }
+
+ /*** Stop monitoring sockets and unregister callbacks */
+ public void stopMonitoringSockets() {
+ ensureRunningOnHandlerThread();
+ if (!mMonitoringSockets) {
+ Log.d(TAG, "Monitoring sockets hasn't been started.");
+ return;
+ }
+ if (DBG) Log.d(TAG, "Stop monitoring sockets.");
+ mContext.getSystemService(ConnectivityManager.class)
+ .unregisterNetworkCallback(mNetworkCallback);
+
+ final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class);
+ tetheringManager.unregisterTetheringEventCallback(mTetheringEventCallback);
+
+ mHandler.post(mNetlinkMonitor::stop);
+ mMonitoringSockets = false;
+ }
+
+ private static boolean isNetworkMatched(@Nullable Network targetNetwork,
+ @NonNull Network currentNetwork) {
+ return targetNetwork == null || targetNetwork.equals(currentNetwork);
+ }
+
+ private boolean matchRequestedNetwork(Network network) {
+ for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
+ final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
+ if (isNetworkMatched(requestedNetwork, network)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean hasAllNetworksRequest() {
+ return mCallbacksToRequestedNetworks.containsValue(null);
+ }
+
+ private void handleLinkPropertiesChanged(Network network, LinkProperties lp) {
+ mActiveNetworksLinkProperties.put(network, lp);
+ if (!matchRequestedNetwork(network)) {
+ if (DBG) {
+ Log.d(TAG, "Ignore LinkProperties change. There is no request for the"
+ + " Network:" + network);
+ }
+ return;
+ }
+
+ final SocketInfo socketInfo = mNetworkSockets.get(network);
+ if (socketInfo == null) {
+ createSocket(network, lp);
+ } else {
+ // Update the addresses of this socket.
+ final List<LinkAddress> addresses = lp.getLinkAddresses();
+ socketInfo.mAddresses.clear();
+ socketInfo.mAddresses.addAll(addresses);
+ // Try to join the group again.
+ socketInfo.mSocket.joinGroup(addresses);
+
+ notifyAddressesChanged(network, lp);
+ }
+ }
+
+ private static LinkProperties createLPForTetheredInterface(String interfaceName) {
+ final LinkProperties linkProperties = new LinkProperties();
+ linkProperties.setInterfaceName(interfaceName);
+ // TODO: Use NetlinkMonitor to update addresses for tethering interfaces.
+ return linkProperties;
+ }
+
+ private void handleTetherInterfacesChanged(List<String> current, List<String> updated) {
+ if (!hasAllNetworksRequest()) {
+ // Currently, the network for tethering can not be requested, so the sockets for
+ // tethering are only created if there is a request for all networks (interfaces).
+ // Therefore, this change can skip if there is no such request.
+ if (DBG) {
+ Log.d(TAG, "Ignore tether interfaces change. There is no request for all"
+ + " networks.");
+ }
+ return;
+ }
+
+ final CompareResult<String> interfaceDiff = new CompareResult<>(
+ current, updated);
+ for (String name : interfaceDiff.added) {
+ createSocket(new Network(INetd.LOCAL_NET_ID), createLPForTetheredInterface(name));
+ }
+ for (String name : interfaceDiff.removed) {
+ removeSocket(new Network(INetd.LOCAL_NET_ID), name);
+ }
+ current.clear();
+ current.addAll(updated);
+ }
+
+ private static List<LinkAddress> getLinkAddressFromNetworkInterface(
+ NetworkInterfaceWrapper networkInterface) {
+ List<LinkAddress> addresses = new ArrayList<>();
+ for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) {
+ addresses.add(new LinkAddress(address));
+ }
+ return addresses;
+ }
+
+ private void createSocket(Network network, LinkProperties lp) {
+ final String interfaceName = lp.getInterfaceName();
+ if (interfaceName == null) {
+ Log.e(TAG, "Can not create socket with null interface name.");
+ return;
+ }
+
+ try {
+ final NetworkInterfaceWrapper networkInterface =
+ mDependencies.getNetworkInterfaceByName(interfaceName);
+ if (networkInterface == null || !mDependencies.canScanOnInterface(networkInterface)) {
+ return;
+ }
+
+ if (DBG) {
+ Log.d(TAG, "Create a socket on network:" + network
+ + " with interfaceName:" + interfaceName);
+ }
+ final MdnsInterfaceSocket socket = mDependencies.createMdnsInterfaceSocket(
+ networkInterface.getNetworkInterface(), MdnsConstants.MDNS_PORT);
+ final List<LinkAddress> addresses;
+ if (network.netId == INetd.LOCAL_NET_ID) {
+ addresses = getLinkAddressFromNetworkInterface(networkInterface);
+ mTetherInterfaceSockets.put(interfaceName, new SocketInfo(socket, addresses));
+ } else {
+ addresses = lp.getLinkAddresses();
+ mNetworkSockets.put(network, new SocketInfo(socket, addresses));
+ }
+ // Try to join IPv4/IPv6 group.
+ socket.joinGroup(addresses);
+
+ // Notify the listeners which need this socket.
+ notifySocketCreated(network, socket, addresses);
+ } catch (IOException e) {
+ Log.e(TAG, "Create a socket failed with interface=" + interfaceName, e);
+ }
+ }
+
+ private void removeSocket(Network network, String interfaceName) {
+ final SocketInfo socketInfo = network.netId == INetd.LOCAL_NET_ID
+ ? mTetherInterfaceSockets.remove(interfaceName)
+ : mNetworkSockets.remove(network);
+ if (socketInfo == null) return;
+
+ socketInfo.mSocket.destroy();
+ notifyInterfaceDestroyed(network, socketInfo.mSocket);
+ }
+
+ private void notifySocketCreated(Network network, MdnsInterfaceSocket socket,
+ List<LinkAddress> addresses) {
+ for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
+ final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
+ if (isNetworkMatched(requestedNetwork, network)) {
+ mCallbacksToRequestedNetworks.keyAt(i).onSocketCreated(network, socket, addresses);
+ }
+ }
+ }
+
+ private void notifyInterfaceDestroyed(Network network, MdnsInterfaceSocket socket) {
+ for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
+ final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
+ if (isNetworkMatched(requestedNetwork, network)) {
+ mCallbacksToRequestedNetworks.keyAt(i).onInterfaceDestroyed(network, socket);
+ }
+ }
+ }
+
+ private void notifyAddressesChanged(Network network, LinkProperties lp) {
+ for (int i = 0; i < mCallbacksToRequestedNetworks.size(); i++) {
+ final Network requestedNetwork = mCallbacksToRequestedNetworks.valueAt(i);
+ if (isNetworkMatched(requestedNetwork, network)) {
+ mCallbacksToRequestedNetworks.keyAt(i)
+ .onAddressesChanged(network, lp.getLinkAddresses());
+ }
+ }
+ }
+
+ private void retrieveAndNotifySocketFromNetwork(Network network, SocketCallback cb) {
+ final SocketInfo socketInfo = mNetworkSockets.get(network);
+ if (socketInfo == null) {
+ final LinkProperties lp = mActiveNetworksLinkProperties.get(network);
+ if (lp == null) {
+ // The requested network is not existed. Maybe wait for LinkProperties change later.
+ if (DBG) Log.d(TAG, "There is no LinkProperties for this network:" + network);
+ return;
+ }
+ createSocket(network, lp);
+ } else {
+ // Notify the socket for requested network.
+ cb.onSocketCreated(network, socketInfo.mSocket, socketInfo.mAddresses);
+ }
+ }
+
+ private void retrieveAndNotifySocketFromInterface(String interfaceName, SocketCallback cb) {
+ final SocketInfo socketInfo = mTetherInterfaceSockets.get(interfaceName);
+ if (socketInfo == null) {
+ createSocket(
+ new Network(INetd.LOCAL_NET_ID), createLPForTetheredInterface(interfaceName));
+ } else {
+ // Notify the socket for requested network.
+ cb.onSocketCreated(
+ new Network(INetd.LOCAL_NET_ID), socketInfo.mSocket, socketInfo.mAddresses);
+ }
+ }
+
+ /**
+ * Request a socket for given network.
+ *
+ * @param network the required network for a socket. Null means create sockets on all possible
+ * networks (interfaces).
+ * @param cb the callback to listen the socket creation.
+ */
+ public void requestSocket(@Nullable Network network, @NonNull SocketCallback cb) {
+ ensureRunningOnHandlerThread();
+ mCallbacksToRequestedNetworks.put(cb, network);
+ if (network == null) {
+ // Does not specify a required network, create sockets for all possible
+ // networks (interfaces).
+ for (int i = 0; i < mActiveNetworksLinkProperties.size(); i++) {
+ retrieveAndNotifySocketFromNetwork(mActiveNetworksLinkProperties.keyAt(i), cb);
+ }
+
+ for (String localInterface : mLocalOnlyInterfaces) {
+ retrieveAndNotifySocketFromInterface(localInterface, cb);
+ }
+
+ for (String tetheredInterface : mTetheredInterfaces) {
+ retrieveAndNotifySocketFromInterface(tetheredInterface, cb);
+ }
+ } else {
+ retrieveAndNotifySocketFromNetwork(network, cb);
+ }
+ }
+
+ /*** Unrequest the socket */
+ public void unrequestSocket(@NonNull SocketCallback cb) {
+ ensureRunningOnHandlerThread();
+ mCallbacksToRequestedNetworks.remove(cb);
+ if (hasAllNetworksRequest()) {
+ // Still has a request for all networks (interfaces).
+ return;
+ }
+
+ // Check if remaining requests are matched any of sockets.
+ for (int i = mNetworkSockets.size() - 1; i >= 0; i--) {
+ if (matchRequestedNetwork(mNetworkSockets.keyAt(i))) continue;
+ mNetworkSockets.removeAt(i).mSocket.destroy();
+ }
+
+ // Remove all sockets for tethering interface because these sockets do not have associated
+ // networks, and they should invoke by a request for all networks (interfaces). If there is
+ // no such request, the sockets for tethering interface should be removed.
+ for (int i = mTetherInterfaceSockets.size() - 1; i >= 0; i--) {
+ mTetherInterfaceSockets.removeAt(i).mSocket.destroy();
+ }
+ }
+
+ /*** Callbacks for listening socket changes */
+ public interface SocketCallback {
+ /*** Notify the socket is created */
+ default void onSocketCreated(@NonNull Network network, @NonNull MdnsInterfaceSocket socket,
+ @NonNull List<LinkAddress> addresses) {}
+ /*** Notify the interface is destroyed */
+ default void onInterfaceDestroyed(@NonNull Network network,
+ @NonNull MdnsInterfaceSocket socket) {}
+ /*** Notify the addresses is changed on the network */
+ default void onAddressesChanged(@NonNull Network network,
+ @NonNull List<LinkAddress> addresses) {}
+ }
+}
diff --git a/service/mdns/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java b/service/mdns/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
index e0d8fa6..ade7b95 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MulticastNetworkInterfaceProvider.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.net.Network;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.connectivity.mdns.util.MdnsLogger;
@@ -56,7 +57,7 @@
context, this::onConnectivityChanged);
}
- private void onConnectivityChanged() {
+ private synchronized void onConnectivityChanged() {
connectivityChanged = true;
}
@@ -141,7 +142,13 @@
return networkInterfaceWrappers;
}
- private boolean canScanOnInterface(@Nullable NetworkInterfaceWrapper networkInterface) {
+ @Nullable
+ public Network getAvailableNetwork() {
+ return connectivityMonitor.getAvailableNetwork();
+ }
+
+ /*** Check whether given network interface can support mdns */
+ public static boolean canScanOnInterface(@Nullable NetworkInterfaceWrapper networkInterface) {
try {
if ((networkInterface == null)
|| networkInterface.isLoopback()
@@ -160,7 +167,7 @@
return false;
}
- private boolean hasInet4Address(@NonNull NetworkInterfaceWrapper networkInterface) {
+ private static boolean hasInet4Address(@NonNull NetworkInterfaceWrapper networkInterface) {
for (InterfaceAddress ifAddr : networkInterface.getInterfaceAddresses()) {
if (ifAddr.getAddress() instanceof Inet4Address) {
return true;
@@ -169,7 +176,7 @@
return false;
}
- private boolean hasInet6Address(@NonNull NetworkInterfaceWrapper networkInterface) {
+ private static boolean hasInet6Address(@NonNull NetworkInterfaceWrapper networkInterface) {
for (InterfaceAddress ifAddr : networkInterface.getInterfaceAddresses()) {
if (ifAddr.getAddress() instanceof Inet6Address) {
return true;
diff --git a/service/src/com/android/server/BpfNetMaps.java b/service/src/com/android/server/BpfNetMaps.java
index aea2103..b8a8fb4 100644
--- a/service/src/com/android/server/BpfNetMaps.java
+++ b/service/src/com/android/server/BpfNetMaps.java
@@ -281,7 +281,7 @@
if (sEnableJavaBpfMap == null) {
sEnableJavaBpfMap = DeviceConfigUtils.isFeatureEnabled(context,
DeviceConfig.NAMESPACE_TETHERING, BPF_NET_MAPS_ENABLE_JAVA_BPF_MAP,
- SdkLevel.isAtLeastU() /* defaultValue */);
+ false /* defaultValue */) || SdkLevel.isAtLeastU();
}
Log.d(TAG, "BpfNetMaps is initialized with sEnableJavaBpfMap=" + sEnableJavaBpfMap);
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 4c9e3a3..a44494c 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -738,6 +738,12 @@
private static final int EVENT_INITIAL_EVALUATION_TIMEOUT = 57;
/**
+ * Used internally when the user does not want the network from captive portal app.
+ * obj = Network
+ */
+ private static final int EVENT_USER_DOES_NOT_WANT = 58;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -5065,6 +5071,10 @@
public void appResponse(final int response) {
if (response == CaptivePortal.APP_RETURN_WANTED_AS_IS) {
enforceSettingsPermission();
+ } else if (response == CaptivePortal.APP_RETURN_UNWANTED) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_USER_DOES_NOT_WANT, mNetwork));
+ // Since the network will be disconnected, skip notifying NetworkMonitor
+ return;
}
final NetworkMonitorManager nm = getNetworkMonitorManager(mNetwork);
@@ -5508,6 +5518,12 @@
case EVENT_INGRESS_RATE_LIMIT_CHANGED:
handleIngressRateLimitChanged();
break;
+ case EVENT_USER_DOES_NOT_WANT:
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork((Network) msg.obj);
+ if (nai == null) break;
+ nai.onPreventAutomaticReconnect();
+ nai.disconnect();
+ break;
}
}
}
diff --git a/tests/cts/hostside/app/AndroidManifest.xml b/tests/cts/hostside/app/AndroidManifest.xml
index d56e5d4..56d3cb5 100644
--- a/tests/cts/hostside/app/AndroidManifest.xml
+++ b/tests/cts/hostside/app/AndroidManifest.xml
@@ -30,6 +30,7 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<application android:requestLegacyExternalStorage="true">
<uses-library android:name="android.test.runner"/>
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 108a86e..a281aed 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -56,6 +56,7 @@
import android.os.Bundle;
import android.os.RemoteCallback;
import android.os.SystemClock;
+import android.os.PowerManager;
import android.provider.DeviceConfig;
import android.service.notification.NotificationListenerService;
import android.util.Log;
@@ -163,6 +164,8 @@
private int mMyUid;
private MyServiceClient mServiceClient;
private DeviceConfigStateHelper mDeviceIdleDeviceConfigStateHelper;
+ private PowerManager mPowerManager;
+ private PowerManager.WakeLock mLock;
@Rule
public final RuleChain mRuleChain = RuleChain.outerRule(new RequiredPropertiesRule())
@@ -181,8 +184,10 @@
mMyUid = getUid(mContext.getPackageName());
mServiceClient = new MyServiceClient(mContext);
mServiceClient.bind();
+ mPowerManager = mContext.getSystemService(PowerManager.class);
executeShellCommand("cmd netpolicy start-watching " + mUid);
setAppIdle(false);
+ mLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
Log.i(TAG, "Apps status:\n"
+ "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
@@ -192,6 +197,7 @@
protected void tearDown() throws Exception {
executeShellCommand("cmd netpolicy stop-watching");
mServiceClient.unbind();
+ if (mLock.isHeld()) mLock.release();
}
protected int getUid(String packageName) throws Exception {
@@ -695,11 +701,13 @@
}
protected void turnScreenOff() throws Exception {
+ if (!mLock.isHeld()) mLock.acquire();
executeSilentShellCommand("input keyevent KEYCODE_SLEEP");
}
protected void turnScreenOn() throws Exception {
executeSilentShellCommand("input keyevent KEYCODE_WAKEUP");
+ if (mLock.isHeld()) mLock.release();
executeSilentShellCommand("wm dismiss-keyguard");
}
diff --git a/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java b/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
index e2821cb..3c71c90 100644
--- a/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/BatteryStatsManagerTest.java
@@ -155,6 +155,13 @@
// removing data activity tracking.
mCtsNetUtils.ensureWifiConnected();
+ // There's rate limit to update mobile battery so if ConnectivityService calls
+ // BatteryStatsManager.reportMobileRadioPowerState when default network changed,
+ // the mobile stats might not be updated. But if the mobile update due to other
+ // reasons (plug/unplug, battery level change, etc) will be unaffected. Thus here
+ // dumps the battery stats to trigger a full sync of data.
+ executeShellCommand("dumpsys batterystats");
+
// Check cellular battery stats are updated.
runAsShell(UPDATE_DEVICE_STATS,
() -> assertStatsEventually(mBsm::getCellularBatteryStats,
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 6c6070e..96acac3 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -2856,7 +2856,7 @@
// Wifi will not automatically reconnect to the network. ensureWifiDisconnected cannot
// apply here. Thus, turn off wifi first and restart to restore.
mTestValidationConfigRule.runAfterNextCleanup(() -> {
- runShellCommand("svc wifi disable");
+ mCtsNetUtils.disableWifi();
mCtsNetUtils.ensureWifiConnected();
});
}
@@ -2898,7 +2898,7 @@
/// Wifi will not automatically reconnect to the network. ensureWifiDisconnected cannot
// apply here. Thus, turn off wifi first and restart to restore.
mTestValidationConfigRule.runAfterNextCleanup(() -> {
- runShellCommand("svc wifi disable");
+ mCtsNetUtils.disableWifi();
mCtsNetUtils.ensureWifiConnected();
});
}
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index 9d1fa60..3f2ff9d 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -57,6 +57,8 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.ShellIdentityUtils;
import com.android.compatibility.common.util.SystemUtil;
import com.android.net.module.util.ConnectivitySettingsUtils;
import com.android.testutils.ConnectUtil;
@@ -268,6 +270,19 @@
}
/**
+ * Disable WiFi and wait for the connection info to be cleared.
+ */
+ public void disableWifi() throws Exception {
+ SystemUtil.runShellCommand("svc wifi disable");
+ PollingCheck.check(
+ "Wifi not disconnected! Current network is not null "
+ + mWifiManager.getConnectionInfo().getNetworkId(),
+ TimeUnit.SECONDS.toMillis(CONNECTIVITY_CHANGE_TIMEOUT_SECS),
+ () -> ShellIdentityUtils.invokeWithShellPermissions(
+ () -> mWifiManager.getConnectionInfo().getNetworkId()) == -1);
+ }
+
+ /**
* Disable WiFi and wait for it to become disconnected from the network.
*
* @param wifiNetworkToCheck If non-null, a network that should be disconnected. This network
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
index 8b904bc..f506c23 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
@@ -478,7 +478,9 @@
waitForWifiEnabled(ctx);
return runAsShell(ACCESS_WIFI_STATE, () -> wm.isPortableHotspotSupported());
} finally {
- if (!previousWifiEnabledState) SystemUtil.runShellCommand("svc wifi disable");
+ if (!previousWifiEnabledState) {
+ new CtsNetUtils(ctx).disableWifi();
+ }
}
}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 437622b..8ed735a 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -66,7 +66,6 @@
"java/android/net/netstats/NetworkStatsDataMigrationUtilsTest.kt",
"java/com/android/internal/net/NetworkUtilsInternalTest.java",
"java/com/android/internal/net/VpnProfileTest.java",
- "java/com/android/server/NetworkManagementServiceTest.java",
"java/com/android/server/VpnManagerServiceTest.java",
"java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java",
"java/com/android/server/connectivity/IpConnectivityMetricsTest.java",
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index c327868..45a9dbc 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -498,7 +498,7 @@
public void testConnectivityManagerDoesNotLeakContext() throws Exception {
final WeakReference<Context> ref = makeConnectivityManagerAndReturnContext();
- final int attempts = 100;
+ final int attempts = 600;
final long waitIntervalMs = 50;
for (int i = 0; i < attempts; i++) {
forceGC();
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 33f9d84..16f7039 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -228,6 +228,7 @@
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.location.LocationManager;
+import android.net.CaptivePortal;
import android.net.CaptivePortalData;
import android.net.ConnectionInfo;
import android.net.ConnectivityDiagnosticsManager.DataStallReport;
@@ -4440,6 +4441,27 @@
validatedCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
}
+ private Intent startCaptivePortalApp(TestNetworkAgentWrapper networkAgent) throws Exception {
+ Network network = networkAgent.getNetwork();
+ // Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
+ mCm.startCaptivePortalApp(network);
+ waitForIdle();
+ verify(networkAgent.mNetworkMonitor).launchCaptivePortalApp();
+
+ // NetworkMonitor uses startCaptivePortal(Network, Bundle) (startCaptivePortalAppInternal)
+ final Bundle testBundle = new Bundle();
+ final String testKey = "testkey";
+ final String testValue = "testvalue";
+ testBundle.putString(testKey, testValue);
+ mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+ PERMISSION_GRANTED);
+ mCm.startCaptivePortalApp(network, testBundle);
+ final Intent signInIntent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
+ assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction());
+ assertEquals(testValue, signInIntent.getStringExtra(testKey));
+ return signInIntent;
+ }
+
@Test
public void testCaptivePortalApp() throws Exception {
final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
@@ -4476,22 +4498,7 @@
captivePortalCallback.expect(CallbackEntry.NETWORK_CAPS_UPDATED,
mWiFiNetworkAgent);
- // Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
- mCm.startCaptivePortalApp(wifiNetwork);
- waitForIdle();
- verify(mWiFiNetworkAgent.mNetworkMonitor).launchCaptivePortalApp();
-
- // NetworkMonitor uses startCaptivePortal(Network, Bundle) (startCaptivePortalAppInternal)
- final Bundle testBundle = new Bundle();
- final String testKey = "testkey";
- final String testValue = "testvalue";
- testBundle.putString(testKey, testValue);
- mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
- PERMISSION_GRANTED);
- mCm.startCaptivePortalApp(wifiNetwork, testBundle);
- final Intent signInIntent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
- assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction());
- assertEquals(testValue, signInIntent.getStringExtra(testKey));
+ startCaptivePortalApp(mWiFiNetworkAgent);
// Report that the captive portal is dismissed, and check that callbacks are fired
mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
@@ -4504,6 +4511,37 @@
}
@Test
+ public void testCaptivePortalApp_IgnoreNetwork() throws Exception {
+ final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
+ final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
+ mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
+
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connectWithCaptivePortal(TEST_REDIRECT_URL, false);
+ captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+
+ final Intent signInIntent = startCaptivePortalApp(mWiFiNetworkAgent);
+ final CaptivePortal captivePortal = signInIntent
+ .getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
+
+ captivePortal.ignoreNetwork();
+ waitForIdle();
+
+ // Since network will disconnect, ensure no notification of response to NetworkMonitor
+ verify(mWiFiNetworkAgent.mNetworkMonitor, never())
+ .notifyCaptivePortalAppFinished(CaptivePortal.APP_RETURN_UNWANTED);
+
+ // Report that the network is disconnected
+ mWiFiNetworkAgent.expectDisconnected();
+ mWiFiNetworkAgent.expectPreventReconnectReceived();
+ verify(mWiFiNetworkAgent.mNetworkMonitor).notifyNetworkDisconnected();
+ captivePortalCallback.expect(CallbackEntry.LOST, mWiFiNetworkAgent);
+
+ mCm.unregisterNetworkCallback(captivePortalCallback);
+ }
+
+ @Test
public void testAvoidOrIgnoreCaptivePortals() throws Exception {
final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
diff --git a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java b/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
deleted file mode 100644
index 7688a6b..0000000
--- a/tests/unit/java/com/android/server/NetworkManagementServiceTest.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Copyright (C) 2012 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.server;
-
-import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
-import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
-import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
-import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
-import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
-import static android.util.DebugUtils.valueToString;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.INetd;
-import android.net.INetdUnsolicitedEventListener;
-import android.net.LinkAddress;
-import android.net.NetworkPolicyManager;
-import android.os.BatteryStats;
-import android.os.Binder;
-import android.os.Build;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.ArrayMap;
-
-import com.android.internal.app.IBatteryStats;
-import com.android.server.NetworkManagementService.Dependencies;
-import com.android.server.net.BaseNetworkObserver;
-import com.android.testutils.DevSdkIgnoreRule;
-import com.android.testutils.DevSdkIgnoreRunner;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.function.BiFunction;
-
-/**
- * Tests for {@link NetworkManagementService}.
- */
-@RunWith(DevSdkIgnoreRunner.class)
-@SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
-public class NetworkManagementServiceTest {
- private NetworkManagementService mNMService;
- @Mock private Context mContext;
- @Mock private ConnectivityManager mCm;
- @Mock private IBatteryStats.Stub mBatteryStatsService;
- @Mock private INetd.Stub mNetdService;
-
- private static final int TEST_UID = 111;
-
- @NonNull
- @Captor
- private ArgumentCaptor<INetdUnsolicitedEventListener> mUnsolListenerCaptor;
-
- private final MockDependencies mDeps = new MockDependencies();
-
- private final class MockDependencies extends Dependencies {
- @Override
- public IBinder getService(String name) {
- switch (name) {
- case BatteryStats.SERVICE_NAME:
- return mBatteryStatsService;
- default:
- throw new UnsupportedOperationException("Unknown service " + name);
- }
- }
-
- @Override
- public void registerLocalService(NetworkManagementInternal nmi) {
- }
-
- @Override
- public INetd getNetd() {
- return mNetdService;
- }
-
- @Override
- public int getCallingUid() {
- return Process.SYSTEM_UID;
- }
- }
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- doNothing().when(mNetdService)
- .registerUnsolicitedEventListener(mUnsolListenerCaptor.capture());
- doReturn(Context.CONNECTIVITY_SERVICE).when(mContext).getSystemServiceName(
- eq(ConnectivityManager.class));
- doReturn(mCm).when(mContext).getSystemService(eq(Context.CONNECTIVITY_SERVICE));
- // Start the service and wait until it connects to our socket.
- mNMService = NetworkManagementService.create(mContext, mDeps);
- }
-
- @After
- public void tearDown() throws Exception {
- mNMService.shutdown();
- }
-
- private static <T> T expectSoon(T mock) {
- return verify(mock, timeout(200));
- }
-
- /**
- * Tests that network observers work properly.
- */
- @Test
- public void testNetworkObservers() throws Exception {
- BaseNetworkObserver observer = mock(BaseNetworkObserver.class);
- doReturn(new Binder()).when(observer).asBinder(); // Used by registerObserver.
- mNMService.registerObserver(observer);
-
- // Forget everything that happened to the mock so far, so we can explicitly verify
- // everything that happens and does not happen to it from now on.
-
- INetdUnsolicitedEventListener unsolListener = mUnsolListenerCaptor.getValue();
- reset(observer);
- // Now call unsolListener methods and ensure that the observer methods are
- // called. After every method we expect a callback soon after; to ensure that
- // invalid messages don't cause any callbacks, we call verifyNoMoreInteractions at the end.
-
- /**
- * Interface changes.
- */
- unsolListener.onInterfaceAdded("rmnet12");
- expectSoon(observer).interfaceAdded("rmnet12");
-
- unsolListener.onInterfaceRemoved("eth1");
- expectSoon(observer).interfaceRemoved("eth1");
-
- unsolListener.onInterfaceChanged("clat4", true);
- expectSoon(observer).interfaceStatusChanged("clat4", true);
-
- unsolListener.onInterfaceLinkStateChanged("rmnet0", false);
- expectSoon(observer).interfaceLinkStateChanged("rmnet0", false);
-
- /**
- * Bandwidth control events.
- */
- unsolListener.onQuotaLimitReached("data", "rmnet_usb0");
- expectSoon(observer).limitReached("data", "rmnet_usb0");
-
- /**
- * Interface class activity.
- */
- unsolListener.onInterfaceClassActivityChanged(true, 1, 1234, TEST_UID);
- expectSoon(observer).interfaceClassDataActivityChanged(1, true, 1234, TEST_UID);
-
- unsolListener.onInterfaceClassActivityChanged(false, 9, 5678, TEST_UID);
- expectSoon(observer).interfaceClassDataActivityChanged(9, false, 5678, TEST_UID);
-
- unsolListener.onInterfaceClassActivityChanged(false, 9, 4321, TEST_UID);
- expectSoon(observer).interfaceClassDataActivityChanged(9, false, 4321, TEST_UID);
-
- /**
- * IP address changes.
- */
- unsolListener.onInterfaceAddressUpdated("fe80::1/64", "wlan0", 128, 253);
- expectSoon(observer).addressUpdated("wlan0", new LinkAddress("fe80::1/64", 128, 253));
-
- unsolListener.onInterfaceAddressRemoved("fe80::1/64", "wlan0", 128, 253);
- expectSoon(observer).addressRemoved("wlan0", new LinkAddress("fe80::1/64", 128, 253));
-
- unsolListener.onInterfaceAddressRemoved("2001:db8::1/64", "wlan0", 1, 0);
- expectSoon(observer).addressRemoved("wlan0", new LinkAddress("2001:db8::1/64", 1, 0));
-
- /**
- * DNS information broadcasts.
- */
- unsolListener.onInterfaceDnsServerInfo("rmnet_usb0", 3600, new String[]{"2001:db8::1"});
- expectSoon(observer).interfaceDnsServerInfo("rmnet_usb0", 3600,
- new String[]{"2001:db8::1"});
-
- unsolListener.onInterfaceDnsServerInfo("wlan0", 14400,
- new String[]{"2001:db8::1", "2001:db8::2"});
- expectSoon(observer).interfaceDnsServerInfo("wlan0", 14400,
- new String[]{"2001:db8::1", "2001:db8::2"});
-
- // We don't check for negative lifetimes, only for parse errors.
- unsolListener.onInterfaceDnsServerInfo("wlan0", -3600, new String[]{"::1"});
- expectSoon(observer).interfaceDnsServerInfo("wlan0", -3600,
- new String[]{"::1"});
-
- // No syntax checking on the addresses.
- unsolListener.onInterfaceDnsServerInfo("wlan0", 600,
- new String[]{"", "::", "", "foo", "::1"});
- expectSoon(observer).interfaceDnsServerInfo("wlan0", 600,
- new String[]{"", "::", "", "foo", "::1"});
-
- // Make sure nothing else was called.
- verifyNoMoreInteractions(observer);
- }
-
- @Test
- public void testFirewallEnabled() {
- mNMService.setFirewallEnabled(true);
- assertTrue(mNMService.isFirewallEnabled());
-
- mNMService.setFirewallEnabled(false);
- assertFalse(mNMService.isFirewallEnabled());
- }
-
- @Test
- public void testNetworkRestrictedDefault() {
- assertFalse(mNMService.isNetworkRestricted(TEST_UID));
- }
-
- @Test
- public void testMeteredNetworkRestrictions() throws RemoteException {
- // Make sure the mocked netd method returns true.
- doReturn(true).when(mNetdService).bandwidthEnableDataSaver(anyBoolean());
-
- // Restrict usage of mobile data in background
- mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, true);
- assertTrue("Should be true since mobile data usage is restricted",
- mNMService.isNetworkRestricted(TEST_UID));
- verify(mCm).addUidToMeteredNetworkDenyList(TEST_UID);
-
- mNMService.setDataSaverModeEnabled(true);
- verify(mNetdService).bandwidthEnableDataSaver(true);
-
- mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, false);
- assertTrue("Should be true since data saver is on and the uid is not allowlisted",
- mNMService.isNetworkRestricted(TEST_UID));
- verify(mCm).removeUidFromMeteredNetworkDenyList(TEST_UID);
-
- mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, true);
- assertFalse("Should be false since data saver is on and the uid is allowlisted",
- mNMService.isNetworkRestricted(TEST_UID));
- verify(mCm).addUidToMeteredNetworkAllowList(TEST_UID);
-
- // remove uid from allowlist and turn datasaver off again
- mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false);
- verify(mCm).removeUidFromMeteredNetworkAllowList(TEST_UID);
- mNMService.setDataSaverModeEnabled(false);
- verify(mNetdService).bandwidthEnableDataSaver(false);
- assertFalse("Network should not be restricted when data saver is off",
- mNMService.isNetworkRestricted(TEST_UID));
- }
-
- @Test
- public void testFirewallChains() {
- final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>();
- // Dozable chain
- final ArrayMap<Integer, Boolean> isRestrictedForDozable = new ArrayMap<>();
- isRestrictedForDozable.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
- isRestrictedForDozable.put(INetd.FIREWALL_RULE_ALLOW, false);
- isRestrictedForDozable.put(INetd.FIREWALL_RULE_DENY, true);
- expected.put(FIREWALL_CHAIN_DOZABLE, isRestrictedForDozable);
- // Powersaver chain
- final ArrayMap<Integer, Boolean> isRestrictedForPowerSave = new ArrayMap<>();
- isRestrictedForPowerSave.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
- isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_ALLOW, false);
- isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_DENY, true);
- expected.put(FIREWALL_CHAIN_POWERSAVE, isRestrictedForPowerSave);
- // Standby chain
- final ArrayMap<Integer, Boolean> isRestrictedForStandby = new ArrayMap<>();
- isRestrictedForStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, false);
- isRestrictedForStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
- isRestrictedForStandby.put(INetd.FIREWALL_RULE_DENY, true);
- expected.put(FIREWALL_CHAIN_STANDBY, isRestrictedForStandby);
- // Restricted mode chain
- final ArrayMap<Integer, Boolean> isRestrictedForRestrictedMode = new ArrayMap<>();
- isRestrictedForRestrictedMode.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
- isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_ALLOW, false);
- isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_DENY, true);
- expected.put(FIREWALL_CHAIN_RESTRICTED, isRestrictedForRestrictedMode);
- // Low Power Standby chain
- final ArrayMap<Integer, Boolean> isRestrictedForLowPowerStandby = new ArrayMap<>();
- isRestrictedForLowPowerStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
- isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
- isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_DENY, true);
- expected.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, isRestrictedForLowPowerStandby);
-
- final int[] chains = {
- FIREWALL_CHAIN_STANDBY,
- FIREWALL_CHAIN_POWERSAVE,
- FIREWALL_CHAIN_DOZABLE,
- FIREWALL_CHAIN_RESTRICTED,
- FIREWALL_CHAIN_LOW_POWER_STANDBY
- };
- final int[] states = {
- INetd.FIREWALL_RULE_ALLOW,
- INetd.FIREWALL_RULE_DENY,
- NetworkPolicyManager.FIREWALL_RULE_DEFAULT
- };
- BiFunction<Integer, Integer, String> errorMsg = (chain, state) -> {
- return String.format("Unexpected value for chain: %s and state: %s",
- valueToString(INetd.class, "FIREWALL_CHAIN_", chain),
- valueToString(INetd.class, "FIREWALL_RULE_", state));
- };
- for (int chain : chains) {
- final ArrayMap<Integer, Boolean> expectedValues = expected.get(chain);
- mNMService.setFirewallChainEnabled(chain, true);
- verify(mCm).setFirewallChainEnabled(chain, true /* enabled */);
- for (int state : states) {
- mNMService.setFirewallUidRule(chain, TEST_UID, state);
- assertEquals(errorMsg.apply(chain, state),
- expectedValues.get(state), mNMService.isNetworkRestricted(TEST_UID));
- }
- mNMService.setFirewallChainEnabled(chain, false);
- verify(mCm).setFirewallChainEnabled(chain, false /* enabled */);
- }
- }
-}
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index e6745d1..677e7b6 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -1268,6 +1268,23 @@
intent.getIntExtra(VpnManager.EXTRA_ERROR_CLASS, -1 /* defaultValue */));
assertEquals(errorCode,
intent.getIntExtra(VpnManager.EXTRA_ERROR_CODE, -1 /* defaultValue */));
+ // CATEGORY_EVENT_DEACTIVATED_BY_USER & CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED won't
+ // send NetworkCapabilities & LinkProperties to VPN app.
+ // For ERROR_CODE_NETWORK_LOST, the NetworkCapabilities & LinkProperties of underlying
+ // network will be cleared. So the VPN app will receive null for those 2 extra values.
+ if (category.equals(VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER)
+ || category.equals(VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED)
+ || errorCode == VpnManager.ERROR_CODE_NETWORK_LOST) {
+ assertNull(intent.getParcelableExtra(
+ VpnManager.EXTRA_UNDERLYING_NETWORK_CAPABILITIES));
+ assertNull(intent.getParcelableExtra(VpnManager.EXTRA_UNDERLYING_LINK_PROPERTIES));
+ } else {
+ assertNotNull(intent.getParcelableExtra(
+ VpnManager.EXTRA_UNDERLYING_NETWORK_CAPABILITIES));
+ assertNotNull(intent.getParcelableExtra(
+ VpnManager.EXTRA_UNDERLYING_LINK_PROPERTIES));
+ }
+
if (profileState != null) {
assertEquals(profileState[i], intent.getParcelableExtra(
VpnManager.EXTRA_VPN_PROFILE_STATE, VpnProfileState.class));
@@ -1470,6 +1487,12 @@
when(mNetd.interfaceGetCfg(anyString())).thenReturn(config);
final NetworkCallback cb = networkCallbackCaptor.getValue();
cb.onAvailable(TEST_NETWORK);
+ // Trigger onCapabilitiesChanged() and onLinkPropertiesChanged() so the test can verify that
+ // if NetworkCapabilities and LinkProperties of underlying network will be sent/cleared or
+ // not.
+ // See verifyVpnManagerEvent().
+ cb.onCapabilitiesChanged(TEST_NETWORK, new NetworkCapabilities());
+ cb.onLinkPropertiesChanged(TEST_NETWORK, new LinkProperties());
return cb;
}
@@ -1488,6 +1511,11 @@
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
.thenReturn(mVpnProfile.encode());
+ doReturn(new NetworkCapabilities()).when(mConnectivityManager)
+ .getRedactedNetworkCapabilitiesForPackage(any(), anyInt(), anyString());
+ doReturn(new LinkProperties()).when(mConnectivityManager)
+ .getRedactedLinkPropertiesForPackage(any(), anyInt(), anyString());
+
final String sessionKey = vpn.startVpnProfile(TEST_VPN_PKG);
final NetworkCallback cb = triggerOnAvailableAndGetCallback();
@@ -1498,8 +1526,18 @@
verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
.createIkeSession(any(), any(), any(), any(), captor.capture(), any());
reset(mIkev2SessionCreator);
- final IkeSessionCallback ikeCb = captor.getValue();
- ikeCb.onClosedWithException(exception);
+ // For network lost case, the process should be triggered by calling onLost(), which is the
+ // same process with the real case.
+ if (errorCode == VpnManager.ERROR_CODE_NETWORK_LOST) {
+ cb.onLost(TEST_NETWORK);
+ final ArgumentCaptor<Runnable> runnableCaptor =
+ ArgumentCaptor.forClass(Runnable.class);
+ verify(mExecutor).schedule(runnableCaptor.capture(), anyLong(), any());
+ runnableCaptor.getValue().run();
+ } else {
+ final IkeSessionCallback ikeCb = captor.getValue();
+ ikeCb.onClosedWithException(exception);
+ }
verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
reset(mDeviceIdleInternal);
@@ -1508,7 +1546,9 @@
if (errorType == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
.unregisterNetworkCallback(eq(cb));
- } else if (errorType == VpnManager.ERROR_CLASS_RECOVERABLE) {
+ } else if (errorType == VpnManager.ERROR_CLASS_RECOVERABLE
+ // Vpn won't retry when there is no usable underlying network.
+ && errorCode != VpnManager.ERROR_CODE_NETWORK_LOST) {
int retryIndex = 0;
final IkeSessionCallback ikeCb2 = verifyRetryAndGetNewIkeCb(retryIndex++);
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManagerTests.java b/tests/unit/java/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManagerTests.java
index f84e2d8..8fb7be1 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManagerTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/ConnectivityMonitorWithConnectivityManagerTests.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -111,7 +112,7 @@
any(NetworkRequest.class), callbackCaptor.capture());
final NetworkCallback callback = callbackCaptor.getValue();
- final Network testNetwork = new Network(1 /* netId */);
+ final Network testNetwork = mock(Network.class);
// Simulate network available.
callback.onAvailable(testNetwork);
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
index 02e00c2..4cae447 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.mock;
import android.net.InetAddresses;
+import android.net.Network;
import com.android.net.module.util.HexDump;
import com.android.testutils.DevSdkIgnoreRule;
@@ -165,7 +166,8 @@
packet.setSocketAddress(
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT));
responses.clear();
- int errorCode = decoder.decode(packet, responses, MdnsSocket.INTERFACE_INDEX_UNSPECIFIED);
+ int errorCode = decoder.decode(
+ packet, responses, MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class));
assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
assertEquals(1, responses.size());
}
@@ -178,7 +180,8 @@
packet.setSocketAddress(
new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), MdnsConstants.MDNS_PORT));
responses.clear();
- int errorCode = decoder.decode(packet, responses, MdnsSocket.INTERFACE_INDEX_UNSPECIFIED);
+ int errorCode = decoder.decode(
+ packet, responses, MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class));
assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
assertEquals(2, responses.size());
}
@@ -237,7 +240,8 @@
new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
responses.clear();
- int errorCode = decoder.decode(packet, responses, MdnsSocket.INTERFACE_INDEX_UNSPECIFIED);
+ int errorCode = decoder.decode(
+ packet, responses, MdnsSocket.INTERFACE_INDEX_UNSPECIFIED, mock(Network.class));
assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
MdnsResponse response = responses.get(0);
@@ -287,10 +291,13 @@
new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
responses.clear();
- int errorCode = decoder.decode(packet, responses, /* interfaceIndex= */ 10);
+ final Network network = mock(Network.class);
+ int errorCode = decoder.decode(
+ packet, responses, /* interfaceIndex= */ 10, network);
assertEquals(errorCode, MdnsResponseDecoder.SUCCESS);
assertEquals(responses.size(), 1);
assertEquals(responses.get(0).getInterfaceIndex(), 10);
+ assertEquals(network, responses.get(0).getNetwork());
}
@Test
@@ -306,7 +313,8 @@
new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
responses.clear();
- int errorCode = decoder.decode(packet, responses, /* interfaceIndex= */ 0);
+ int errorCode = decoder.decode(
+ packet, responses, /* interfaceIndex= */ 0, mock(Network.class));
assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
// This should emit two records:
@@ -340,7 +348,8 @@
new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), MdnsConstants.MDNS_PORT));
responses.clear();
- int errorCode = decoder.decode(packet, responses, /* interfaceIndex= */ 0);
+ int errorCode = decoder.decode(
+ packet, responses, /* interfaceIndex= */ 0, mock(Network.class));
assertEquals(MdnsResponseDecoder.SUCCESS, errorCode);
// This should emit only two records:
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java
index 771e42c..ec57dc8 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java
@@ -21,8 +21,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+import android.net.Network;
import com.android.net.module.util.HexDump;
import com.android.testutils.DevSdkIgnoreRule;
@@ -92,6 +96,9 @@
+ "3839300878797A3D"
+ "21402324");
+ private static final int INTERFACE_INDEX = 999;
+ private final Network mNetwork = mock(Network.class);
+
// The following helper classes act as wrappers so that IPv4 and IPv6 address records can
// be explicitly created by type using same constructor signature as all other records.
static class MdnsInet4AddressRecord extends MdnsInetAddressRecord {
@@ -127,7 +134,7 @@
// Construct an MdnsResponse with the specified data packets applied.
private MdnsResponse makeMdnsResponse(long time, List<PacketAndRecordClass> responseList)
throws IOException {
- MdnsResponse response = new MdnsResponse(time);
+ MdnsResponse response = new MdnsResponse(time, INTERFACE_INDEX, mNetwork);
for (PacketAndRecordClass responseData : responseList) {
DatagramPacket packet =
new DatagramPacket(responseData.packetData, responseData.packetData.length);
@@ -159,7 +166,7 @@
String[] name = reader.readLabels();
reader.skip(2); // skip record type indication.
MdnsInetAddressRecord record = new MdnsInetAddressRecord(name, MdnsRecord.TYPE_A, reader);
- MdnsResponse response = new MdnsResponse(0);
+ MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
assertFalse(response.hasInet4AddressRecord());
assertTrue(response.setInet4AddressRecord(record));
assertEquals(response.getInet4AddressRecord(), record);
@@ -173,7 +180,7 @@
reader.skip(2); // skip record type indication.
MdnsInetAddressRecord record =
new MdnsInetAddressRecord(name, MdnsRecord.TYPE_AAAA, reader);
- MdnsResponse response = new MdnsResponse(0);
+ MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
assertFalse(response.hasInet6AddressRecord());
assertTrue(response.setInet6AddressRecord(record));
assertEquals(response.getInet6AddressRecord(), record);
@@ -186,7 +193,7 @@
String[] name = reader.readLabels();
reader.skip(2); // skip record type indication.
MdnsPointerRecord record = new MdnsPointerRecord(name, reader);
- MdnsResponse response = new MdnsResponse(0);
+ MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
assertFalse(response.hasPointerRecords());
assertTrue(response.addPointerRecord(record));
List<MdnsPointerRecord> recordList = response.getPointerRecords();
@@ -202,7 +209,7 @@
String[] name = reader.readLabels();
reader.skip(2); // skip record type indication.
MdnsServiceRecord record = new MdnsServiceRecord(name, reader);
- MdnsResponse response = new MdnsResponse(0);
+ MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
assertFalse(response.hasServiceRecord());
assertTrue(response.setServiceRecord(record));
assertEquals(response.getServiceRecord(), record);
@@ -215,23 +222,31 @@
String[] name = reader.readLabels();
reader.skip(2); // skip record type indication.
MdnsTextRecord record = new MdnsTextRecord(name, reader);
- MdnsResponse response = new MdnsResponse(0);
+ MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
assertFalse(response.hasTextRecord());
assertTrue(response.setTextRecord(record));
assertEquals(response.getTextRecord(), record);
}
@Test
- public void getInterfaceIndex_returnsDefaultValue() {
- MdnsResponse response = new MdnsResponse(/* now= */ 0);
- assertEquals(response.getInterfaceIndex(), -1);
+ public void getInterfaceIndex() {
+ final MdnsResponse response1 = new MdnsResponse(/* now= */ 0, INTERFACE_INDEX, mNetwork);
+ assertEquals(INTERFACE_INDEX, response1.getInterfaceIndex());
+
+ final MdnsResponse response2 =
+ new MdnsResponse(/* now= */ 0, 1234 /* interfaceIndex */, mNetwork);
+ assertEquals(1234, response2.getInterfaceIndex());
}
@Test
- public void getInterfaceIndex_afterSet_returnsValue() {
- MdnsResponse response = new MdnsResponse(/* now= */ 0);
- response.setInterfaceIndex(5);
- assertEquals(response.getInterfaceIndex(), 5);
+ public void testGetNetwork() {
+ final MdnsResponse response1 =
+ new MdnsResponse(/* now= */ 0, INTERFACE_INDEX, null /* network */);
+ assertNull(response1.getNetwork());
+
+ final MdnsResponse response2 =
+ new MdnsResponse(/* now= */ 0, 1234 /* interfaceIndex */, mNetwork);
+ assertEquals(mNetwork, response2.getNetwork());
}
@Test
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
index d3934c2..76728cf 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
@@ -16,13 +16,16 @@
package com.android.server.connectivity.mdns;
+import static com.android.server.connectivity.mdns.MdnsSocket.INTERFACE_INDEX_UNSPECIFIED;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import android.net.Network;
import android.os.Parcel;
import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
@@ -128,7 +131,7 @@
"2001::1",
List.of());
- assertEquals(info.getInterfaceIndex(), -1);
+ assertEquals(info.getInterfaceIndex(), INTERFACE_INDEX_UNSPECIFIED);
}
@Test
@@ -150,6 +153,41 @@
}
@Test
+ public void testGetNetwork() {
+ final MdnsServiceInfo info1 =
+ new MdnsServiceInfo(
+ "my-mdns-service",
+ new String[] {"_googlecast", "_tcp"},
+ List.of(),
+ new String[] {"my-host", "local"},
+ 12345,
+ "192.168.1.1",
+ "2001::1",
+ List.of(),
+ /* textEntries= */ null,
+ /* interfaceIndex= */ 20);
+
+ assertNull(info1.getNetwork());
+
+ final Network network = mock(Network.class);
+ final MdnsServiceInfo info2 =
+ new MdnsServiceInfo(
+ "my-mdns-service",
+ new String[] {"_googlecast", "_tcp"},
+ List.of(),
+ new String[] {"my-host", "local"},
+ 12345,
+ "192.168.1.1",
+ "2001::1",
+ List.of(),
+ /* textEntries= */ null,
+ /* interfaceIndex= */ 20,
+ network);
+
+ assertEquals(network, info2.getNetwork());
+ }
+
+ @Test
public void parcelable_canBeParceledAndUnparceled() {
Parcel parcel = Parcel.obtain();
MdnsServiceInfo beforeParcel =
@@ -164,7 +202,10 @@
List.of("vn=Alphabet Inc.", "mn=Google Nest Hub Max", "id=12345"),
List.of(
MdnsServiceInfo.TextEntry.fromString("vn=Google Inc."),
- MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max")));
+ MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max"),
+ MdnsServiceInfo.TextEntry.fromString("test=")),
+ 20 /* interfaceIndex */,
+ new Network(123));
beforeParcel.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
@@ -178,6 +219,8 @@
assertEquals(beforeParcel.getIpv4Address(), afterParcel.getIpv4Address());
assertEquals(beforeParcel.getIpv6Address(), afterParcel.getIpv6Address());
assertEquals(beforeParcel.getAttributes(), afterParcel.getAttributes());
+ assertEquals(beforeParcel.getInterfaceIndex(), afterParcel.getInterfaceIndex());
+ assertEquals(beforeParcel.getNetwork(), afterParcel.getNetwork());
}
@Test
@@ -208,11 +251,11 @@
}
@Test
- public void textEntry_fromStringWithoutAssignPunc_valueisEmpty() {
+ public void textEntry_fromStringWithoutAssignPunc_noValue() {
TextEntry entry = TextEntry.fromString("AA");
assertEquals("AA", entry.getKey());
- assertArrayEquals(new byte[] {}, entry.getValue());
+ assertNull(entry.getValue());
}
@Test
@@ -241,11 +284,11 @@
}
@Test
- public void textEntry_fromBytesWithoutAssignPunc_valueisEmpty() {
+ public void textEntry_fromBytesWithoutAssignPunc_noValue() {
TextEntry entry = TextEntry.fromBytes(new byte[] {'A', 'A'});
assertEquals("AA", entry.getKey());
- assertArrayEquals(new byte[] {}, entry.getValue());
+ assertNull(entry.getValue());
}
@Test
@@ -270,4 +313,12 @@
assertEquals(new TextEntry("BB", "xxyyzz"), new TextEntry("BB", "xxyyzz"));
assertEquals(new TextEntry("AA", "XXYYZZ"), new TextEntry("AA", "XXYYZZ"));
}
+
+ @Test
+ public void textEntry_fromString_valueIsEmpty() {
+ TextEntry entry = TextEntry.fromString("AA=");
+
+ assertEquals("AA", entry.getKey());
+ assertArrayEquals(new byte[] {}, entry.getValue());
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index 6f8b85a..697116c 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -18,6 +18,7 @@
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -26,16 +27,19 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static java.nio.charset.StandardCharsets.UTF_8;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+import android.net.Network;
import android.text.TextUtils;
import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
@@ -49,6 +53,7 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -72,9 +77,10 @@
@RunWith(DevSdkIgnoreRunner.class)
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
public class MdnsServiceTypeClientTests {
-
+ private static final int INTERFACE_INDEX = 999;
private static final String SERVICE_TYPE = "_googlecast._tcp.local";
private static final String[] SERVICE_TYPE_LABELS = TextUtils.split(SERVICE_TYPE, "\\.");
+ private static final Network NETWORK = mock(Network.class);
@Mock
private MdnsServiceBrowserListener mockListenerOne;
@@ -379,15 +385,45 @@
assertNull(currentThreadExecutor.getAndClearLastScheduledRunnable());
}
+ private static void verifyServiceInfo(MdnsServiceInfo serviceInfo, String serviceName,
+ String[] serviceType, String ipv4Address, String ipv6Address, int port,
+ List<String> subTypes, Map<String, String> attributes, int interfaceIndex,
+ Network network) {
+ assertEquals(serviceName, serviceInfo.getServiceInstanceName());
+ assertArrayEquals(serviceType, serviceInfo.getServiceType());
+ assertEquals(ipv4Address, serviceInfo.getIpv4Address());
+ assertEquals(ipv6Address, serviceInfo.getIpv6Address());
+ assertEquals(port, serviceInfo.getPort());
+ assertEquals(subTypes, serviceInfo.getSubtypes());
+ for (String key : attributes.keySet()) {
+ assertEquals(attributes.get(key), serviceInfo.getAttributeByKey(key));
+ }
+ assertEquals(interfaceIndex, serviceInfo.getInterfaceIndex());
+ assertEquals(network, serviceInfo.getNetwork());
+ }
+
@Test
public void processResponse_incompleteResponse() {
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
MdnsResponse response = mock(MdnsResponse.class);
when(response.getServiceInstanceName()).thenReturn("service-instance-1");
+ doReturn(INTERFACE_INDEX).when(response).getInterfaceIndex();
+ doReturn(NETWORK).when(response).getNetwork();
when(response.isComplete()).thenReturn(false);
client.processResponse(response);
+ verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
+ "service-instance-1",
+ SERVICE_TYPE_LABELS,
+ null /* ipv4Address */,
+ null /* ipv6Address */,
+ 0 /* port */,
+ List.of() /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ INTERFACE_INDEX,
+ NETWORK);
verify(mockListenerOne, never()).onServiceFound(any(MdnsServiceInfo.class));
verify(mockListenerOne, never()).onServiceUpdated(any(MdnsServiceInfo.class));
@@ -404,9 +440,10 @@
"service-instance-1",
ipV4Address,
5353,
- Collections.singletonList("ABCDE"),
+ /* subtype= */ "ABCDE",
Collections.emptyMap(),
- /* interfaceIndex= */ 20);
+ /* interfaceIndex= */ 20,
+ NETWORK);
client.processResponse(initialResponse);
// Process a second response with a different port and updated text attributes.
@@ -415,24 +452,39 @@
"service-instance-1",
ipV4Address,
5354,
- Collections.singletonList("ABCDE"),
+ /* subtype= */ "ABCDE",
Collections.singletonMap("key", "value"),
- /* interfaceIndex= */ 20);
+ /* interfaceIndex= */ 20,
+ NETWORK);
client.processResponse(secondResponse);
+ // Verify onServiceNameDiscovered was called once for the initial response.
+ verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
+ "service-instance-1",
+ SERVICE_TYPE_LABELS,
+ ipV4Address /* ipv4Address */,
+ null /* ipv6Address */,
+ 5353 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ 20 /* interfaceIndex */,
+ NETWORK);
+
// Verify onServiceFound was called once for the initial response.
verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
- MdnsServiceInfo initialServiceInfo = serviceInfoCaptor.getAllValues().get(0);
+ MdnsServiceInfo initialServiceInfo = serviceInfoCaptor.getAllValues().get(1);
assertEquals(initialServiceInfo.getServiceInstanceName(), "service-instance-1");
assertEquals(initialServiceInfo.getIpv4Address(), ipV4Address);
assertEquals(initialServiceInfo.getPort(), 5353);
assertEquals(initialServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
assertNull(initialServiceInfo.getAttributeByKey("key"));
assertEquals(initialServiceInfo.getInterfaceIndex(), 20);
+ assertEquals(NETWORK, initialServiceInfo.getNetwork());
// Verify onServiceUpdated was called once for the second response.
verify(mockListenerOne).onServiceUpdated(serviceInfoCaptor.capture());
- MdnsServiceInfo updatedServiceInfo = serviceInfoCaptor.getAllValues().get(1);
+ MdnsServiceInfo updatedServiceInfo = serviceInfoCaptor.getAllValues().get(2);
assertEquals(updatedServiceInfo.getServiceInstanceName(), "service-instance-1");
assertEquals(updatedServiceInfo.getIpv4Address(), ipV4Address);
assertEquals(updatedServiceInfo.getPort(), 5354);
@@ -440,6 +492,7 @@
assertEquals(updatedServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
assertEquals(updatedServiceInfo.getAttributeByKey("key"), "value");
assertEquals(updatedServiceInfo.getInterfaceIndex(), 20);
+ assertEquals(NETWORK, updatedServiceInfo.getNetwork());
}
@Test
@@ -453,9 +506,10 @@
"service-instance-1",
ipV6Address,
5353,
- Collections.singletonList("ABCDE"),
+ /* subtype= */ "ABCDE",
Collections.emptyMap(),
- /* interfaceIndex= */ 20);
+ /* interfaceIndex= */ 20,
+ NETWORK);
client.processResponse(initialResponse);
// Process a second response with a different port and updated text attributes.
@@ -464,27 +518,42 @@
"service-instance-1",
ipV6Address,
5354,
- Collections.singletonList("ABCDE"),
+ /* subtype= */ "ABCDE",
Collections.singletonMap("key", "value"),
- /* interfaceIndex= */ 20);
+ /* interfaceIndex= */ 20,
+ NETWORK);
client.processResponse(secondResponse);
System.out.println("secondResponses ip"
+ secondResponse.getInet6AddressRecord().getInet6Address().getHostAddress());
+ // Verify onServiceNameDiscovered was called once for the initial response.
+ verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
+ "service-instance-1",
+ SERVICE_TYPE_LABELS,
+ null /* ipv4Address */,
+ ipV6Address /* ipv6Address */,
+ 5353 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ 20 /* interfaceIndex */,
+ NETWORK);
+
// Verify onServiceFound was called once for the initial response.
verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
- MdnsServiceInfo initialServiceInfo = serviceInfoCaptor.getAllValues().get(0);
+ MdnsServiceInfo initialServiceInfo = serviceInfoCaptor.getAllValues().get(1);
assertEquals(initialServiceInfo.getServiceInstanceName(), "service-instance-1");
assertEquals(initialServiceInfo.getIpv6Address(), ipV6Address);
assertEquals(initialServiceInfo.getPort(), 5353);
assertEquals(initialServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
assertNull(initialServiceInfo.getAttributeByKey("key"));
assertEquals(initialServiceInfo.getInterfaceIndex(), 20);
+ assertEquals(NETWORK, initialServiceInfo.getNetwork());
// Verify onServiceUpdated was called once for the second response.
verify(mockListenerOne).onServiceUpdated(serviceInfoCaptor.capture());
- MdnsServiceInfo updatedServiceInfo = serviceInfoCaptor.getAllValues().get(1);
+ MdnsServiceInfo updatedServiceInfo = serviceInfoCaptor.getAllValues().get(2);
assertEquals(updatedServiceInfo.getServiceInstanceName(), "service-instance-1");
assertEquals(updatedServiceInfo.getIpv6Address(), ipV6Address);
assertEquals(updatedServiceInfo.getPort(), 5354);
@@ -492,6 +561,26 @@
assertEquals(updatedServiceInfo.getSubtypes(), Collections.singletonList("ABCDE"));
assertEquals(updatedServiceInfo.getAttributeByKey("key"), "value");
assertEquals(updatedServiceInfo.getInterfaceIndex(), 20);
+ assertEquals(NETWORK, updatedServiceInfo.getNetwork());
+ }
+
+ private void verifyServiceRemovedNoCallback(MdnsServiceBrowserListener listener) {
+ verify(listener, never()).onServiceRemoved(any());
+ verify(listener, never()).onServiceNameRemoved(any());
+ }
+
+ private void verifyServiceRemovedCallback(MdnsServiceBrowserListener listener,
+ String serviceName, String[] serviceType, int interfaceIndex, Network network) {
+ verify(listener).onServiceRemoved(argThat(
+ info -> serviceName.equals(info.getServiceInstanceName())
+ && Arrays.equals(serviceType, info.getServiceType())
+ && info.getInterfaceIndex() == interfaceIndex
+ && network.equals(info.getNetwork())));
+ verify(listener).onServiceNameRemoved(argThat(
+ info -> serviceName.equals(info.getServiceInstanceName())
+ && Arrays.equals(serviceType, info.getServiceType())
+ && info.getInterfaceIndex() == interfaceIndex
+ && network.equals(info.getNetwork())));
}
@Test
@@ -501,37 +590,34 @@
final String serviceName = "service-instance-1";
final String ipV6Address = "2000:3333::da6c:63ff:fe7c:7483";
- final int interfaceIndex = 999;
// Process the initial response.
final MdnsResponse initialResponse =
createResponse(
serviceName,
ipV6Address,
5353 /* port */,
- Collections.singletonList("ABCDE"),
+ /* subtype= */ "ABCDE",
Collections.emptyMap(),
- interfaceIndex);
+ INTERFACE_INDEX,
+ NETWORK);
client.processResponse(initialResponse);
MdnsResponse response = mock(MdnsResponse.class);
doReturn("goodbye-service").when(response).getServiceInstanceName();
- doReturn(interfaceIndex).when(response).getInterfaceIndex();
+ doReturn(INTERFACE_INDEX).when(response).getInterfaceIndex();
+ doReturn(NETWORK).when(response).getNetwork();
doReturn(true).when(response).isGoodbye();
client.processResponse(response);
- // Verify onServiceRemoved won't be called if the service is not existed.
- verify(mockListenerOne, never()).onServiceRemoved(any());
- verify(mockListenerTwo, never()).onServiceRemoved(any());
+ // Verify removed callback won't be called if the service is not existed.
+ verifyServiceRemovedNoCallback(mockListenerOne);
+ verifyServiceRemovedNoCallback(mockListenerTwo);
- // Verify onServiceRemoved would be called.
+ // Verify removed callback would be called.
doReturn(serviceName).when(response).getServiceInstanceName();
client.processResponse(response);
- verify(mockListenerOne).onServiceRemoved(argThat(
- info -> serviceName.equals(info.getServiceInstanceName())
- && Arrays.equals(SERVICE_TYPE_LABELS, info.getServiceType())
- && info.getInterfaceIndex() == interfaceIndex));
- verify(mockListenerTwo).onServiceRemoved(argThat(
- info -> serviceName.equals(info.getServiceInstanceName())
- && Arrays.equals(SERVICE_TYPE_LABELS, info.getServiceType())
- && info.getInterfaceIndex() == interfaceIndex));
+ verifyServiceRemovedCallback(
+ mockListenerOne, serviceName, SERVICE_TYPE_LABELS, INTERFACE_INDEX, NETWORK);
+ verifyServiceRemovedCallback(
+ mockListenerTwo, serviceName, SERVICE_TYPE_LABELS, INTERFACE_INDEX, NETWORK);
}
@Test
@@ -542,15 +628,30 @@
"service-instance-1",
"192.168.1.1",
5353,
- Collections.singletonList("ABCDE"),
- Collections.emptyMap());
+ /* subtype= */ "ABCDE",
+ Collections.emptyMap(),
+ INTERFACE_INDEX,
+ NETWORK);
client.processResponse(initialResponse);
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
+ // Verify onServiceNameDiscovered was called once for the existing response.
+ verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
+ "service-instance-1",
+ SERVICE_TYPE_LABELS,
+ "192.168.1.1" /* ipv4Address */,
+ null /* ipv6Address */,
+ 5353 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ INTERFACE_INDEX,
+ NETWORK);
+
// Verify onServiceFound was called once for the existing response.
verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
- MdnsServiceInfo existingServiceInfo = serviceInfoCaptor.getAllValues().get(0);
+ MdnsServiceInfo existingServiceInfo = serviceInfoCaptor.getAllValues().get(1);
assertEquals(existingServiceInfo.getServiceInstanceName(), "service-instance-1");
assertEquals(existingServiceInfo.getIpv4Address(), "192.168.1.1");
assertEquals(existingServiceInfo.getPort(), 5353);
@@ -567,6 +668,7 @@
// Verify onServiceFound was not called on the newly registered listener after the existing
// response is gone.
+ verify(mockListenerTwo, never()).onServiceNameDiscovered(any(MdnsServiceInfo.class));
verify(mockListenerTwo, never()).onServiceFound(any(MdnsServiceInfo.class));
}
@@ -580,9 +682,9 @@
// Process the initial response.
MdnsResponse initialResponse =
- createResponse(
+ createMockResponse(
serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
- Map.of());
+ Map.of(), INTERFACE_INDEX, NETWORK);
client.processResponse(initialResponse);
// Clear the scheduled runnable.
@@ -592,8 +694,8 @@
when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
firstMdnsTask.run();
- // Verify onServiceRemoved was not called.
- verify(mockListenerOne, never()).onServiceRemoved(any());
+ // Verify removed callback was not called.
+ verifyServiceRemovedNoCallback(mockListenerOne);
}
@Test
@@ -614,9 +716,9 @@
// Process the initial response.
MdnsResponse initialResponse =
- createResponse(
+ createMockResponse(
serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
- Map.of(), 999 /* interfaceIndex */);
+ Map.of(), INTERFACE_INDEX, NETWORK);
client.processResponse(initialResponse);
// Clear the scheduled runnable.
@@ -626,18 +728,16 @@
when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 1000);
firstMdnsTask.run();
- // Verify onServiceRemoved was not called.
- verify(mockListenerOne, never()).onServiceRemoved(any());
+ // Verify removed callback was not called.
+ verifyServiceRemovedNoCallback(mockListenerOne);
// Simulate the case where the response is after TTL.
when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
firstMdnsTask.run();
- // Verify onServiceRemoved was called.
- verify(mockListenerOne, times(1)).onServiceRemoved(argThat(
- info -> serviceInstanceName.equals(info.getServiceInstanceName())
- && Arrays.equals(SERVICE_TYPE_LABELS, info.getServiceType())
- && info.getInterfaceIndex() == 999));
+ // Verify removed callback was called.
+ verifyServiceRemovedCallback(mockListenerOne, serviceInstanceName, SERVICE_TYPE_LABELS,
+ INTERFACE_INDEX, NETWORK);
}
@Test
@@ -656,9 +756,9 @@
// Process the initial response.
MdnsResponse initialResponse =
- createResponse(
+ createMockResponse(
serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
- Map.of());
+ Map.of(), INTERFACE_INDEX, NETWORK);
client.processResponse(initialResponse);
// Clear the scheduled runnable.
@@ -668,8 +768,8 @@
when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
firstMdnsTask.run();
- // Verify onServiceRemoved was not called.
- verify(mockListenerOne, never()).onServiceRemoved(any());
+ // Verify removed callback was not called.
+ verifyServiceRemovedNoCallback(mockListenerOne);
}
@Test
@@ -690,9 +790,9 @@
// Process the initial response.
MdnsResponse initialResponse =
- createResponse(
+ createMockResponse(
serviceInstanceName, "192.168.1.1", 5353, List.of("ABCDE"),
- Map.of(), 999 /* interfaceIndex */);
+ Map.of(), INTERFACE_INDEX, NETWORK);
client.processResponse(initialResponse);
// Clear the scheduled runnable.
@@ -702,11 +802,125 @@
when(initialResponse.getServiceRecord().getRemainingTTL(anyLong())).thenReturn((long) 0);
firstMdnsTask.run();
- // Verify onServiceRemoved was called.
- verify(mockListenerOne, times(1)).onServiceRemoved(argThat(
- info -> serviceInstanceName.equals(info.getServiceInstanceName())
- && Arrays.equals(SERVICE_TYPE_LABELS, info.getServiceType())
- && info.getInterfaceIndex() == 999));
+ // Verify removed callback was called.
+ verifyServiceRemovedCallback(mockListenerOne, serviceInstanceName, SERVICE_TYPE_LABELS,
+ INTERFACE_INDEX, NETWORK);
+ }
+
+ @Test
+ public void testProcessResponse_InOrder() throws Exception {
+ final String serviceName = "service-instance";
+ final String ipV4Address = "192.0.2.0";
+ final String ipV6Address = "2001:db8::";
+ client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
+ InOrder inOrder = inOrder(mockListenerOne);
+
+ // Process the initial response which is incomplete.
+ final MdnsResponse initialResponse =
+ createResponse(
+ serviceName,
+ null,
+ 5353,
+ "ABCDE" /* subtype */,
+ Collections.emptyMap(),
+ INTERFACE_INDEX,
+ NETWORK);
+ client.processResponse(initialResponse);
+
+ // Process a second response which has ip address to make response become complete.
+ final MdnsResponse secondResponse =
+ createResponse(
+ serviceName,
+ ipV4Address,
+ 5353,
+ "ABCDE" /* subtype */,
+ Collections.emptyMap(),
+ INTERFACE_INDEX,
+ NETWORK);
+ client.processResponse(secondResponse);
+
+ // Process a third response with a different ip address, port and updated text attributes.
+ final MdnsResponse thirdResponse =
+ createResponse(
+ serviceName,
+ ipV6Address,
+ 5354,
+ "ABCDE" /* subtype */,
+ Collections.singletonMap("key", "value"),
+ INTERFACE_INDEX,
+ NETWORK);
+ client.processResponse(thirdResponse);
+
+ // Process the last response which is goodbye message.
+ final MdnsResponse lastResponse = mock(MdnsResponse.class);
+ doReturn(serviceName).when(lastResponse).getServiceInstanceName();
+ doReturn(true).when(lastResponse).isGoodbye();
+ client.processResponse(lastResponse);
+
+ // Verify onServiceNameDiscovered was first called for the initial response.
+ inOrder.verify(mockListenerOne).onServiceNameDiscovered(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(0),
+ serviceName,
+ SERVICE_TYPE_LABELS,
+ null /* ipv4Address */,
+ null /* ipv6Address */,
+ 5353 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ INTERFACE_INDEX,
+ NETWORK);
+
+ // Verify onServiceFound was second called for the second response.
+ inOrder.verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(1),
+ serviceName,
+ SERVICE_TYPE_LABELS,
+ ipV4Address /* ipv4Address */,
+ null /* ipv6Address */,
+ 5353 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", null) /* attributes */,
+ INTERFACE_INDEX,
+ NETWORK);
+
+ // Verify onServiceUpdated was third called for the third response.
+ inOrder.verify(mockListenerOne).onServiceUpdated(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(2),
+ serviceName,
+ SERVICE_TYPE_LABELS,
+ ipV4Address /* ipv4Address */,
+ ipV6Address /* ipv6Address */,
+ 5354 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", "value") /* attributes */,
+ INTERFACE_INDEX,
+ NETWORK);
+
+ // Verify onServiceRemoved was called for the last response.
+ inOrder.verify(mockListenerOne).onServiceRemoved(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(3),
+ serviceName,
+ SERVICE_TYPE_LABELS,
+ ipV4Address /* ipv4Address */,
+ ipV6Address /* ipv6Address */,
+ 5354 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", "value") /* attributes */,
+ INTERFACE_INDEX,
+ NETWORK);
+
+ // Verify onServiceNameRemoved was called for the last response.
+ inOrder.verify(mockListenerOne).onServiceNameRemoved(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getAllValues().get(4),
+ serviceName,
+ SERVICE_TYPE_LABELS,
+ ipV4Address /* ipv4Address */,
+ ipV6Address /* ipv6Address */,
+ 5354 /* port */,
+ Collections.singletonList("ABCDE") /* subTypes */,
+ Collections.singletonMap("key", "value") /* attributes */,
+ INTERFACE_INDEX,
+ NETWORK);
}
// verifies that the right query was enqueued with the right delay, and send query by executing
@@ -771,44 +985,32 @@
}
}
- private MdnsResponse createResponse(
- @NonNull String serviceInstanceName,
- @NonNull String host,
- int port,
- @NonNull List<String> subtypes,
- @NonNull Map<String, String> textAttributes)
- throws Exception {
- return createResponse(serviceInstanceName, host, port, subtypes, textAttributes,
- /* interfaceIndex= */ -1);
- }
-
- // Creates a complete mDNS response.
- private MdnsResponse createResponse(
+ // Creates a mock mDNS response.
+ private MdnsResponse createMockResponse(
@NonNull String serviceInstanceName,
@NonNull String host,
int port,
@NonNull List<String> subtypes,
@NonNull Map<String, String> textAttributes,
- int interfaceIndex)
+ int interfaceIndex,
+ Network network)
throws Exception {
String[] hostName = new String[]{"hostname"};
MdnsServiceRecord serviceRecord = mock(MdnsServiceRecord.class);
when(serviceRecord.getServiceHost()).thenReturn(hostName);
when(serviceRecord.getServicePort()).thenReturn(port);
- MdnsResponse response = spy(new MdnsResponse(0));
+ MdnsResponse response = spy(new MdnsResponse(0, interfaceIndex, network));
MdnsInetAddressRecord inetAddressRecord = mock(MdnsInetAddressRecord.class);
if (host.contains(":")) {
when(inetAddressRecord.getInet6Address())
.thenReturn((Inet6Address) Inet6Address.getByName(host));
response.setInet6AddressRecord(inetAddressRecord);
- response.setInterfaceIndex(interfaceIndex);
} else {
when(inetAddressRecord.getInet4Address())
.thenReturn((Inet4Address) Inet4Address.getByName(host));
response.setInet4AddressRecord(inetAddressRecord);
- response.setInterfaceIndex(interfaceIndex);
}
MdnsTextRecord textRecord = mock(MdnsTextRecord.class);
@@ -830,4 +1032,73 @@
doReturn(new ArrayList<>(subtypes)).when(response).getSubtypes();
return response;
}
+
+ // Creates a mDNS response.
+ private MdnsResponse createResponse(
+ @NonNull String serviceInstanceName,
+ @Nullable String host,
+ int port,
+ @NonNull String subtype,
+ @NonNull Map<String, String> textAttributes,
+ int interfaceIndex,
+ Network network)
+ throws Exception {
+ MdnsResponse response = new MdnsResponse(0, interfaceIndex, network);
+
+ // Set PTR record
+ final MdnsPointerRecord pointerRecord = new MdnsPointerRecord(
+ new String[]{subtype, MdnsConstants.SUBTYPE_LABEL, "test"} /* name */,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ new String[]{serviceInstanceName});
+ response.addPointerRecord(pointerRecord);
+
+ // Set SRV record.
+ final MdnsServiceRecord serviceRecord = new MdnsServiceRecord(
+ new String[] {"service"} /* name */,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ 0 /* servicePriority */,
+ 0 /* serviceWeight */,
+ port,
+ new String[]{"hostname"});
+ response.setServiceRecord(serviceRecord);
+
+ // Set A/AAAA record.
+ if (host != null) {
+ if (InetAddresses.parseNumericAddress(host) instanceof Inet6Address) {
+ final MdnsInetAddressRecord inetAddressRecord = new MdnsInetAddressRecord(
+ new String[] {"address"} /* name */,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ Inet6Address.getByName(host));
+ response.setInet6AddressRecord(inetAddressRecord);
+ } else {
+ final MdnsInetAddressRecord inetAddressRecord = new MdnsInetAddressRecord(
+ new String[] {"address"} /* name */,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ Inet4Address.getByName(host));
+ response.setInet4AddressRecord(inetAddressRecord);
+ }
+ }
+
+ // Set TXT record.
+ final List<TextEntry> textEntries = new ArrayList<>();
+ for (Map.Entry<String, String> kv : textAttributes.entrySet()) {
+ textEntries.add(new TextEntry(kv.getKey(), kv.getValue().getBytes(UTF_8)));
+ }
+ final MdnsTextRecord textRecord = new MdnsTextRecord(
+ new String[] {"text"} /* name */,
+ 0L /* receiptTimeMillis */,
+ false /* cacheFlush */,
+ 120000L /* ttlMillis */,
+ textEntries);
+ response.setTextRecord(textRecord);
+ return response;
+ }
}
\ No newline at end of file
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
index b4442a5..1d61cd3 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketClientTests.java
@@ -501,8 +501,7 @@
//MdnsConfigsFlagsImpl.allowNetworkInterfaceIndexPropagation.override(true);
when(mockMulticastSocket.getInterfaceIndex()).thenReturn(21);
- mdnsClient =
- new MdnsSocketClient(mContext, mockMulticastLock) {
+ mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock) {
@Override
MdnsSocket createMdnsSocket(int port) {
if (port == MdnsConstants.MDNS_PORT) {
@@ -525,8 +524,7 @@
//MdnsConfigsFlagsImpl.allowNetworkInterfaceIndexPropagation.override(false);
when(mockMulticastSocket.getInterfaceIndex()).thenReturn(21);
- mdnsClient =
- new MdnsSocketClient(mContext, mockMulticastLock) {
+ mdnsClient = new MdnsSocketClient(mContext, mockMulticastLock) {
@Override
MdnsSocket createMdnsSocket(int port) {
if (port == MdnsConstants.MDNS_PORT) {
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
new file mode 100644
index 0000000..2bb61a6a
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsSocketProviderTest.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2022 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.server.connectivity.mdns;
+
+import static com.android.testutils.ContextUtils.mockService;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.INetd;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.TetheringManager;
+import android.net.TetheringManager.TetheringEventCallback;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import com.android.net.module.util.ArrayTrackRecord;
+import com.android.server.connectivity.mdns.MdnsSocketProvider.Dependencies;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.HandlerUtils;
+
+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.io.IOException;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(DevSdkIgnoreRunner.class)
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
+public class MdnsSocketProviderTest {
+ private static final String TEST_IFACE_NAME = "test";
+ private static final String LOCAL_ONLY_IFACE_NAME = "local_only";
+ private static final String TETHERED_IFACE_NAME = "tethered";
+ private static final long DEFAULT_TIMEOUT = 2000L;
+ private static final long NO_CALLBACK_TIMEOUT = 200L;
+ private static final LinkAddress LINKADDRV4 = new LinkAddress("192.0.2.0/24");
+ private static final LinkAddress LINKADDRV6 =
+ new LinkAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334/64");
+ private static final Network TEST_NETWORK = new Network(123);
+ private static final Network LOCAL_NETWORK = new Network(INetd.LOCAL_NET_ID);
+
+ @Mock private Context mContext;
+ @Mock private Dependencies mDeps;
+ @Mock private ConnectivityManager mCm;
+ @Mock private TetheringManager mTm;
+ @Mock private NetworkInterfaceWrapper mTestNetworkIfaceWrapper;
+ @Mock private NetworkInterfaceWrapper mLocalOnlyIfaceWrapper;
+ @Mock private NetworkInterfaceWrapper mTetheredIfaceWrapper;
+ private Handler mHandler;
+ private MdnsSocketProvider mSocketProvider;
+ private NetworkCallback mNetworkCallback;
+ private TetheringEventCallback mTetheringEventCallback;
+
+ @Before
+ public void setUp() throws IOException {
+ MockitoAnnotations.initMocks(this);
+ mockService(mContext, ConnectivityManager.class, Context.CONNECTIVITY_SERVICE, mCm);
+ mockService(mContext, TetheringManager.class, Context.TETHERING_SERVICE, mTm);
+ doReturn(true).when(mDeps).canScanOnInterface(any());
+ doReturn(mTestNetworkIfaceWrapper).when(mDeps).getNetworkInterfaceByName(TEST_IFACE_NAME);
+ doReturn(mLocalOnlyIfaceWrapper).when(mDeps)
+ .getNetworkInterfaceByName(LOCAL_ONLY_IFACE_NAME);
+ doReturn(mTetheredIfaceWrapper).when(mDeps).getNetworkInterfaceByName(TETHERED_IFACE_NAME);
+ doReturn(mock(MdnsInterfaceSocket.class))
+ .when(mDeps).createMdnsInterfaceSocket(any(), anyInt());
+ final HandlerThread thread = new HandlerThread("MdnsSocketProviderTest");
+ thread.start();
+ mHandler = new Handler(thread.getLooper());
+
+ final ArgumentCaptor<NetworkCallback> nwCallbackCaptor =
+ ArgumentCaptor.forClass(NetworkCallback.class);
+ final ArgumentCaptor<TetheringEventCallback> teCallbackCaptor =
+ ArgumentCaptor.forClass(TetheringEventCallback.class);
+ mSocketProvider = new MdnsSocketProvider(mContext, thread.getLooper(), mDeps);
+ mHandler.post(mSocketProvider::startMonitoringSockets);
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ verify(mCm).registerNetworkCallback(any(), nwCallbackCaptor.capture(), any());
+ verify(mTm).registerTetheringEventCallback(any(), teCallbackCaptor.capture());
+
+ mNetworkCallback = nwCallbackCaptor.getValue();
+ mTetheringEventCallback = teCallbackCaptor.getValue();
+ }
+
+ private class TestSocketCallback implements MdnsSocketProvider.SocketCallback {
+ private class SocketEvent {
+ public final Network mNetwork;
+ public final List<LinkAddress> mAddresses;
+
+ SocketEvent(Network network, List<LinkAddress> addresses) {
+ mNetwork = network;
+ mAddresses = Collections.unmodifiableList(addresses);
+ }
+ }
+
+ private class SocketCreatedEvent extends SocketEvent {
+ SocketCreatedEvent(Network nw, List<LinkAddress> addresses) {
+ super(nw, addresses);
+ }
+ }
+
+ private class InterfaceDestroyedEvent extends SocketEvent {
+ InterfaceDestroyedEvent(Network nw, List<LinkAddress> addresses) {
+ super(nw, addresses);
+ }
+ }
+
+ private class AddressesChangedEvent extends SocketEvent {
+ AddressesChangedEvent(Network nw, List<LinkAddress> addresses) {
+ super(nw, addresses);
+ }
+ }
+
+ private final ArrayTrackRecord<SocketEvent>.ReadHead mHistory =
+ new ArrayTrackRecord<SocketEvent>().newReadHead();
+
+ @Override
+ public void onSocketCreated(Network network, MdnsInterfaceSocket socket,
+ List<LinkAddress> addresses) {
+ mHistory.add(new SocketCreatedEvent(network, addresses));
+ }
+
+ @Override
+ public void onInterfaceDestroyed(Network network, MdnsInterfaceSocket socket) {
+ mHistory.add(new InterfaceDestroyedEvent(network, List.of()));
+ }
+
+ @Override
+ public void onAddressesChanged(Network network, List<LinkAddress> addresses) {
+ mHistory.add(new AddressesChangedEvent(network, addresses));
+ }
+
+ public void expectedSocketCreatedForNetwork(Network network, List<LinkAddress> addresses) {
+ final SocketEvent event = mHistory.poll(DEFAULT_TIMEOUT, c -> true);
+ assertNotNull(event);
+ assertTrue(event instanceof SocketCreatedEvent);
+ assertEquals(network, event.mNetwork);
+ assertEquals(addresses, event.mAddresses);
+ }
+
+ public void expectedInterfaceDestroyedForNetwork(Network network) {
+ final SocketEvent event = mHistory.poll(DEFAULT_TIMEOUT, c -> true);
+ assertNotNull(event);
+ assertTrue(event instanceof InterfaceDestroyedEvent);
+ assertEquals(network, event.mNetwork);
+ }
+
+ public void expectedAddressesChangedForNetwork(Network network,
+ List<LinkAddress> addresses) {
+ final SocketEvent event = mHistory.poll(DEFAULT_TIMEOUT, c -> true);
+ assertNotNull(event);
+ assertTrue(event instanceof AddressesChangedEvent);
+ assertEquals(network, event.mNetwork);
+ assertEquals(event.mAddresses, addresses);
+ }
+
+ public void expectedNoCallback() {
+ final SocketEvent event = mHistory.poll(NO_CALLBACK_TIMEOUT, c -> true);
+ assertNull(event);
+ }
+ }
+
+ @Test
+ public void testSocketRequestAndUnrequestSocket() {
+ final TestSocketCallback testCallback1 = new TestSocketCallback();
+ mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback1));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ testCallback1.expectedNoCallback();
+
+ final LinkProperties testLp = new LinkProperties();
+ testLp.setInterfaceName(TEST_IFACE_NAME);
+ testLp.setLinkAddresses(List.of(LINKADDRV4));
+ mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ verify(mTestNetworkIfaceWrapper).getNetworkInterface();
+ testCallback1.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
+
+ final TestSocketCallback testCallback2 = new TestSocketCallback();
+ mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback2));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ testCallback1.expectedNoCallback();
+ testCallback2.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
+
+ final TestSocketCallback testCallback3 = new TestSocketCallback();
+ mHandler.post(() -> mSocketProvider.requestSocket(null /* network */, testCallback3));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ testCallback1.expectedNoCallback();
+ testCallback2.expectedNoCallback();
+ testCallback3.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
+
+ mHandler.post(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(
+ List.of(LOCAL_ONLY_IFACE_NAME)));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ verify(mLocalOnlyIfaceWrapper).getNetworkInterface();
+ testCallback1.expectedNoCallback();
+ testCallback2.expectedNoCallback();
+ testCallback3.expectedSocketCreatedForNetwork(LOCAL_NETWORK, List.of());
+
+ mHandler.post(() -> mTetheringEventCallback.onTetheredInterfacesChanged(
+ List.of(TETHERED_IFACE_NAME)));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ verify(mTetheredIfaceWrapper).getNetworkInterface();
+ testCallback1.expectedNoCallback();
+ testCallback2.expectedNoCallback();
+ testCallback3.expectedSocketCreatedForNetwork(LOCAL_NETWORK, List.of());
+
+ mHandler.post(() -> mSocketProvider.unrequestSocket(testCallback1));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ testCallback1.expectedNoCallback();
+ testCallback2.expectedNoCallback();
+ testCallback3.expectedNoCallback();
+
+ mHandler.post(() -> mNetworkCallback.onLost(TEST_NETWORK));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ testCallback1.expectedNoCallback();
+ testCallback2.expectedInterfaceDestroyedForNetwork(TEST_NETWORK);
+ testCallback3.expectedInterfaceDestroyedForNetwork(TEST_NETWORK);
+
+ mHandler.post(() -> mTetheringEventCallback.onLocalOnlyInterfacesChanged(List.of()));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ testCallback1.expectedNoCallback();
+ testCallback2.expectedNoCallback();
+ testCallback3.expectedInterfaceDestroyedForNetwork(LOCAL_NETWORK);
+
+ mHandler.post(() -> mSocketProvider.unrequestSocket(testCallback3));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ testCallback1.expectedNoCallback();
+ testCallback2.expectedNoCallback();
+ testCallback3.expectedNoCallback();
+ }
+
+ @Test
+ public void testAddressesChanged() throws Exception {
+ final TestSocketCallback testCallback = new TestSocketCallback();
+ mHandler.post(() -> mSocketProvider.requestSocket(TEST_NETWORK, testCallback));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ testCallback.expectedNoCallback();
+
+ final LinkProperties testLp = new LinkProperties();
+ testLp.setInterfaceName(TEST_IFACE_NAME);
+ testLp.setLinkAddresses(List.of(LINKADDRV4));
+ mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, testLp));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ verify(mTestNetworkIfaceWrapper, times(1)).getNetworkInterface();
+ testCallback.expectedSocketCreatedForNetwork(TEST_NETWORK, List.of(LINKADDRV4));
+
+ final LinkProperties newTestLp = new LinkProperties();
+ newTestLp.setInterfaceName(TEST_IFACE_NAME);
+ newTestLp.setLinkAddresses(List.of(LINKADDRV4, LINKADDRV6));
+ mHandler.post(() -> mNetworkCallback.onLinkPropertiesChanged(TEST_NETWORK, newTestLp));
+ HandlerUtils.waitForIdle(mHandler, DEFAULT_TIMEOUT);
+ verify(mTestNetworkIfaceWrapper, times(1)).getNetworkInterface();
+ testCallback.expectedAddressesChangedForNetwork(
+ TEST_NETWORK, List.of(LINKADDRV4, LINKADDRV6));
+ }
+}
diff --git a/tools/gn2bp/Android.bp.swp b/tools/gn2bp/Android.bp.swp
index 401ffca..212735a 100644
--- a/tools/gn2bp/Android.bp.swp
+++ b/tools/gn2bp/Android.bp.swp
@@ -2356,31 +2356,10 @@
"base/metrics/histogram_base.h",
"base/task/task_traits.h",
"build/android/gyp/java_cpp_enum.py",
- ],
-}
-
-// GN: //base:base_java_aidl
-java_genrule {
- name: "cronet_aml_base_base_java_aidl",
- cmd: "$(location build/android/gyp/aidl.py) --aidl-path " +
- "../../third_party/android_sdk/public/build-tools/33.0.0/aidl " +
- "--imports " +
- "[\"../../third_party/android_sdk/public/platforms/android-33/framework.aidl\"] " +
- "--srcjar " +
- "gen/base/base_java_aidl.srcjar " +
- "--depfile " +
- "gen/base/base_java_aidl.d " +
- "--includes " +
- "[\"../../base/android/java/src\"] " +
- "../../base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl " +
- "../../base/android/java/src/org/chromium/base/process_launcher/IParentProcess.aidl",
- out: [
- "base/base_java_aidl.srcjar",
- ],
- tool_files: [
- "base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl",
- "base/android/java/src/org/chromium/base/process_launcher/IParentProcess.aidl",
- "build/android/gyp/aidl.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/android/gyp/util/java_cpp_utils.py",
+ "build/gn_helpers.py",
],
}
@@ -4231,18 +4210,22 @@
"base/task/task_features.cc",
],
cmd: "$(location build/android/gyp/java_cpp_features.py) --srcjar " +
- "gen/base/java_features_srcjar.srcjar " +
+ "$(out) " +
"--template " +
- "../../base/android/java/src/org/chromium/base/BaseFeatures.java.tmpl " +
- "../../base/android/base_features.cc " +
- "../../base/features.cc " +
- "../../base/task/task_features.cc",
+ "$(location base/android/java/src/org/chromium/base/BaseFeatures.java.tmpl) " +
+ "$(location base/android/base_features.cc) " +
+ "$(location base/features.cc) " +
+ "$(location base/task/task_features.cc)",
out: [
"base/java_features_srcjar.srcjar",
],
tool_files: [
"base/android/java/src/org/chromium/base/BaseFeatures.java.tmpl",
"build/android/gyp/java_cpp_features.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/android/gyp/util/java_cpp_utils.py",
+ "build/gn_helpers.py",
],
}
@@ -4253,16 +4236,20 @@
"base/base_switches.cc",
],
cmd: "$(location build/android/gyp/java_cpp_strings.py) --srcjar " +
- "gen/base/java_switches_srcjar.srcjar " +
+ "$(out) " +
"--template " +
- "../../base/android/java/src/org/chromium/base/BaseSwitches.java.tmpl " +
- "../../base/base_switches.cc",
+ "$(location base/android/java/src/org/chromium/base/BaseSwitches.java.tmpl) " +
+ "$(location base/base_switches.cc)",
out: [
"base/java_switches_srcjar.srcjar",
],
tool_files: [
"base/android/java/src/org/chromium/base/BaseSwitches.java.tmpl",
"build/android/gyp/java_cpp_strings.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/android/gyp/util/java_cpp_utils.py",
+ "build/gn_helpers.py",
],
}
@@ -5684,29 +5671,53 @@
}
// GN: //build/android:build_config_gen
-java_genrule {
+genrule {
name: "cronet_aml_build_android_build_config_gen",
- cmd: "$(location build/android/gyp/gcc_preprocess.py) --include-dirs " +
- "[\"../../\", \"gen\"] " +
- "--output " +
- "gen/build/android/build_config_gen.srcjar " +
- "--define " +
- "_ENABLE_ASSERTS " +
- "../../build/android/java/templates/BuildConfig.template",
- out: [
- "build/android/build_config_gen.srcjar",
+ srcs: [
+ ":cronet_aml_build_android_build_config_gen_preprocess",
],
- tool_files: [
- "build/android/gyp/gcc_preprocess.py",
+ tools: [
+ "soong_zip",
+ ],
+ cmd: "cp $(in) $(genDir)/BuildConfig.java && " +
+ "$(location soong_zip) -o $(out) -srcjar -f $(genDir)/BuildConfig.java",
+ out: [
+ "BuildConfig.srcjar",
+ ],
+}
+
+// GN: //build/android:build_config_gen
+cc_object {
+ name: "cronet_aml_build_android_build_config_gen_preprocess",
+ srcs: [
+ ":cronet_aml_build_android_build_config_gen_rename",
+ ],
+ cflags: [
+ "-DANDROID",
+ "-D_ENABLE_ASSERTS",
+ "-E",
+ "-P",
+ ],
+ compile_multilib: "first",
+}
+
+// GN: //build/android:build_config_gen
+genrule {
+ name: "cronet_aml_build_android_build_config_gen_rename",
+ srcs: [
"build/android/java/templates/BuildConfig.template",
],
+ cmd: "cp $(in) $(out)",
+ out: [
+ "BuildConfig.cc",
+ ],
}
// GN: //build/android:native_libraries_gen
java_genrule {
name: "cronet_aml_build_android_native_libraries_gen",
cmd: "$(location build/android/gyp/write_native_libraries_java.py) --output " +
- "gen/build/android/native_libraries_gen.srcjar " +
+ "$(out) " +
"--cpu-family " +
"CPU_FAMILY_ARM",
out: [
@@ -6692,11 +6703,9 @@
"cronet_aml_url_url",
],
generated_headers: [
- "cronet_aml_components_cronet_android_cronet_jni_registration",
"cronet_aml_third_party_metrics_proto_metrics_proto_gen_headers",
],
export_generated_headers: [
- "cronet_aml_components_cronet_android_cronet_jni_registration",
"cronet_aml_third_party_metrics_proto_metrics_proto_gen_headers",
],
defaults: [
@@ -6748,6 +6757,7 @@
"cronet_aml_build_chromeos_buildflags__android_arm",
"cronet_aml_components_cronet_android_buildflags__android_arm",
"cronet_aml_components_cronet_android_cronet_jni_headers__android_arm",
+ "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm",
"cronet_aml_components_cronet_cronet_buildflags__android_arm",
"cronet_aml_components_cronet_cronet_version_header_action__android_arm",
"cronet_aml_url_buildflags__android_arm",
@@ -6758,6 +6768,7 @@
"cronet_aml_build_chromeos_buildflags__android_arm",
"cronet_aml_components_cronet_android_buildflags__android_arm",
"cronet_aml_components_cronet_android_cronet_jni_headers__android_arm",
+ "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm",
"cronet_aml_components_cronet_cronet_buildflags__android_arm",
"cronet_aml_components_cronet_cronet_version_header_action__android_arm",
"cronet_aml_url_buildflags__android_arm",
@@ -6770,6 +6781,7 @@
"cronet_aml_build_chromeos_buildflags__android_arm64",
"cronet_aml_components_cronet_android_buildflags__android_arm64",
"cronet_aml_components_cronet_android_cronet_jni_headers__android_arm64",
+ "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm64",
"cronet_aml_components_cronet_cronet_buildflags__android_arm64",
"cronet_aml_components_cronet_cronet_version_header_action__android_arm64",
"cronet_aml_url_buildflags__android_arm64",
@@ -6780,6 +6792,7 @@
"cronet_aml_build_chromeos_buildflags__android_arm64",
"cronet_aml_components_cronet_android_buildflags__android_arm64",
"cronet_aml_components_cronet_android_cronet_jni_headers__android_arm64",
+ "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm64",
"cronet_aml_components_cronet_cronet_buildflags__android_arm64",
"cronet_aml_components_cronet_cronet_version_header_action__android_arm64",
"cronet_aml_url_buildflags__android_arm64",
@@ -6795,6 +6808,7 @@
"cronet_aml_build_chromeos_buildflags__android_x86",
"cronet_aml_components_cronet_android_buildflags__android_x86",
"cronet_aml_components_cronet_android_cronet_jni_headers__android_x86",
+ "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86",
"cronet_aml_components_cronet_cronet_buildflags__android_x86",
"cronet_aml_components_cronet_cronet_version_header_action__android_x86",
"cronet_aml_url_buildflags__android_x86",
@@ -6805,6 +6819,7 @@
"cronet_aml_build_chromeos_buildflags__android_x86",
"cronet_aml_components_cronet_android_buildflags__android_x86",
"cronet_aml_components_cronet_android_cronet_jni_headers__android_x86",
+ "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86",
"cronet_aml_components_cronet_cronet_buildflags__android_x86",
"cronet_aml_components_cronet_cronet_version_header_action__android_x86",
"cronet_aml_url_buildflags__android_x86",
@@ -6820,6 +6835,7 @@
"cronet_aml_build_chromeos_buildflags__android_x86_64",
"cronet_aml_components_cronet_android_buildflags__android_x86_64",
"cronet_aml_components_cronet_android_cronet_jni_headers__android_x86_64",
+ "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86_64",
"cronet_aml_components_cronet_cronet_buildflags__android_x86_64",
"cronet_aml_components_cronet_cronet_version_header_action__android_x86_64",
"cronet_aml_url_buildflags__android_x86_64",
@@ -6830,6 +6846,7 @@
"cronet_aml_build_chromeos_buildflags__android_x86_64",
"cronet_aml_components_cronet_android_buildflags__android_x86_64",
"cronet_aml_components_cronet_android_cronet_jni_headers__android_x86_64",
+ "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86_64",
"cronet_aml_components_cronet_cronet_buildflags__android_x86_64",
"cronet_aml_components_cronet_cronet_version_header_action__android_x86_64",
"cronet_aml_url_buildflags__android_x86_64",
@@ -7062,9 +7079,948 @@
],
}
-// GN: //components/cronet/android:cronet_jni_registration
+// GN: //components/cronet/android:cronet_jni_registration__android_arm
cc_genrule {
- name: "cronet_aml_components_cronet_android_cronet_jni_registration",
+ name: "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm",
+ srcs: [
+ "base/android/java/src/org/chromium/base/ActivityState.java",
+ "base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java",
+ "base/android/java/src/org/chromium/base/ApkAssets.java",
+ "base/android/java/src/org/chromium/base/ApplicationStatus.java",
+ "base/android/java/src/org/chromium/base/BaseFeatureList.java",
+ "base/android/java/src/org/chromium/base/BuildInfo.java",
+ "base/android/java/src/org/chromium/base/BundleUtils.java",
+ "base/android/java/src/org/chromium/base/ByteArrayGenerator.java",
+ "base/android/java/src/org/chromium/base/Callback.java",
+ "base/android/java/src/org/chromium/base/CallbackController.java",
+ "base/android/java/src/org/chromium/base/CollectionUtil.java",
+ "base/android/java/src/org/chromium/base/CommandLine.java",
+ "base/android/java/src/org/chromium/base/CommandLineInitUtil.java",
+ "base/android/java/src/org/chromium/base/Consumer.java",
+ "base/android/java/src/org/chromium/base/ContentUriUtils.java",
+ "base/android/java/src/org/chromium/base/ContextUtils.java",
+ "base/android/java/src/org/chromium/base/CpuFeatures.java",
+ "base/android/java/src/org/chromium/base/DiscardableReferencePool.java",
+ "base/android/java/src/org/chromium/base/EarlyTraceEvent.java",
+ "base/android/java/src/org/chromium/base/EventLog.java",
+ "base/android/java/src/org/chromium/base/FeatureList.java",
+ "base/android/java/src/org/chromium/base/Features.java",
+ "base/android/java/src/org/chromium/base/FieldTrialList.java",
+ "base/android/java/src/org/chromium/base/FileUtils.java",
+ "base/android/java/src/org/chromium/base/Function.java",
+ "base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
+ "base/android/java/src/org/chromium/base/IntStringCallback.java",
+ "base/android/java/src/org/chromium/base/IntentUtils.java",
+ "base/android/java/src/org/chromium/base/JNIUtils.java",
+ "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
+ "base/android/java/src/org/chromium/base/JavaHandlerThread.java",
+ "base/android/java/src/org/chromium/base/JniException.java",
+ "base/android/java/src/org/chromium/base/JniStaticTestMocker.java",
+ "base/android/java/src/org/chromium/base/LifetimeAssert.java",
+ "base/android/java/src/org/chromium/base/LocaleUtils.java",
+ "base/android/java/src/org/chromium/base/Log.java",
+ "base/android/java/src/org/chromium/base/MathUtils.java",
+ "base/android/java/src/org/chromium/base/MemoryPressureListener.java",
+ "base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java",
+ "base/android/java/src/org/chromium/base/ObserverList.java",
+ "base/android/java/src/org/chromium/base/PackageManagerUtils.java",
+ "base/android/java/src/org/chromium/base/PackageUtils.java",
+ "base/android/java/src/org/chromium/base/PathService.java",
+ "base/android/java/src/org/chromium/base/PathUtils.java",
+ "base/android/java/src/org/chromium/base/PiiElider.java",
+ "base/android/java/src/org/chromium/base/PowerMonitor.java",
+ "base/android/java/src/org/chromium/base/PowerMonitorForQ.java",
+ "base/android/java/src/org/chromium/base/Predicate.java",
+ "base/android/java/src/org/chromium/base/Promise.java",
+ "base/android/java/src/org/chromium/base/RadioUtils.java",
+ "base/android/java/src/org/chromium/base/StreamUtil.java",
+ "base/android/java/src/org/chromium/base/StrictModeContext.java",
+ "base/android/java/src/org/chromium/base/SysUtils.java",
+ "base/android/java/src/org/chromium/base/ThreadUtils.java",
+ "base/android/java/src/org/chromium/base/TimeUtils.java",
+ "base/android/java/src/org/chromium/base/TimezoneUtils.java",
+ "base/android/java/src/org/chromium/base/TraceEvent.java",
+ "base/android/java/src/org/chromium/base/UnguessableToken.java",
+ "base/android/java/src/org/chromium/base/UnownedUserData.java",
+ "base/android/java/src/org/chromium/base/UnownedUserDataHost.java",
+ "base/android/java/src/org/chromium/base/UnownedUserDataKey.java",
+ "base/android/java/src/org/chromium/base/UserData.java",
+ "base/android/java/src/org/chromium/base/UserDataHost.java",
+ "base/android/java/src/org/chromium/base/WrappedClassLoader.java",
+ "base/android/java/src/org/chromium/base/annotations/AccessedByNative.java",
+ "base/android/java/src/org/chromium/base/annotations/CalledByNative.java",
+ "base/android/java/src/org/chromium/base/annotations/CalledByNativeForTesting.java",
+ "base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java",
+ "base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java",
+ "base/android/java/src/org/chromium/base/annotations/JNINamespace.java",
+ "base/android/java/src/org/chromium/base/annotations/JniIgnoreNatives.java",
+ "base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java",
+ "base/android/java/src/org/chromium/base/annotations/NativeMethods.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForM.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForN.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForO.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForOMR1.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForP.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForQ.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForR.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForS.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/DummyJankTracker.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/FrameMetrics.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsListener.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsStore.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankActivityTracker.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankMetricCalculator.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankMetrics.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankReportingRunnable.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankReportingScheduler.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankScenario.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankTracker.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankTrackerImpl.java",
+ "base/android/java/src/org/chromium/base/library_loader/LegacyLinker.java",
+ "base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
+ "base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
+ "base/android/java/src/org/chromium/base/library_loader/Linker.java",
+ "base/android/java/src/org/chromium/base/library_loader/LinkerJni.java",
+ "base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java",
+ "base/android/java/src/org/chromium/base/library_loader/ModernLinker.java",
+ "base/android/java/src/org/chromium/base/library_loader/ModernLinkerJni.java",
+ "base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java",
+ "base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java",
+ "base/android/java/src/org/chromium/base/lifetime/DestroyChecker.java",
+ "base/android/java/src/org/chromium/base/lifetime/Destroyable.java",
+ "base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java",
+ "base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java",
+ "base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java",
+ "base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java",
+ "base/android/java/src/org/chromium/base/memory/MemoryPurgeManager.java",
+ "base/android/java/src/org/chromium/base/metrics/CachingUmaRecorder.java",
+ "base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java",
+ "base/android/java/src/org/chromium/base/metrics/NoopUmaRecorder.java",
+ "base/android/java/src/org/chromium/base/metrics/RecordHistogram.java",
+ "base/android/java/src/org/chromium/base/metrics/RecordUserAction.java",
+ "base/android/java/src/org/chromium/base/metrics/ScopedSysTraceEvent.java",
+ "base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java",
+ "base/android/java/src/org/chromium/base/metrics/TimingMetric.java",
+ "base/android/java/src/org/chromium/base/metrics/UmaRecorder.java",
+ "base/android/java/src/org/chromium/base/metrics/UmaRecorderHolder.java",
+ "base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java",
+ "base/android/java/src/org/chromium/base/process_launcher/BindService.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnection.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionDelegate.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionFactory.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionImpl.java",
+ "base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java",
+ "base/android/java/src/org/chromium/base/supplier/BooleanSupplier.java",
+ "base/android/java/src/org/chromium/base/supplier/DestroyableObservableSupplier.java",
+ "base/android/java/src/org/chromium/base/supplier/ObservableSupplier.java",
+ "base/android/java/src/org/chromium/base/supplier/ObservableSupplierImpl.java",
+ "base/android/java/src/org/chromium/base/supplier/OneShotCallback.java",
+ "base/android/java/src/org/chromium/base/supplier/OneshotSupplier.java",
+ "base/android/java/src/org/chromium/base/supplier/OneshotSupplierImpl.java",
+ "base/android/java/src/org/chromium/base/supplier/Supplier.java",
+ "base/android/java/src/org/chromium/base/supplier/UnownedUserDataSupplier.java",
+ "base/android/java/src/org/chromium/base/task/AsyncTask.java",
+ "base/android/java/src/org/chromium/base/task/BackgroundOnlyAsyncTask.java",
+ "base/android/java/src/org/chromium/base/task/ChainedTasks.java",
+ "base/android/java/src/org/chromium/base/task/ChoreographerTaskRunner.java",
+ "base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java",
+ "base/android/java/src/org/chromium/base/task/DefaultTaskExecutor.java",
+ "base/android/java/src/org/chromium/base/task/PostTask.java",
+ "base/android/java/src/org/chromium/base/task/SequencedTaskRunner.java",
+ "base/android/java/src/org/chromium/base/task/SequencedTaskRunnerImpl.java",
+ "base/android/java/src/org/chromium/base/task/SerialExecutor.java",
+ "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunner.java",
+ "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java",
+ "base/android/java/src/org/chromium/base/task/TaskExecutor.java",
+ "base/android/java/src/org/chromium/base/task/TaskRunner.java",
+ "base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java",
+ "base/android/java/src/org/chromium/base/task/TaskTraits.java",
+ "base/android/java/src/org/chromium/base/task/TaskTraitsExtensionDescriptor.java",
+ "build/android/java/src/org/chromium/build/annotations/AlwaysInline.java",
+ "build/android/java/src/org/chromium/build/annotations/CheckDiscard.java",
+ "build/android/java/src/org/chromium/build/annotations/DoNotClassMerge.java",
+ "build/android/java/src/org/chromium/build/annotations/DoNotInline.java",
+ "build/android/java/src/org/chromium/build/annotations/IdentifierNameString.java",
+ "build/android/java/src/org/chromium/build/annotations/MainDex.java",
+ "build/android/java/src/org/chromium/build/annotations/MockedInTests.java",
+ "build/android/java/src/org/chromium/build/annotations/UsedByReflection.java",
+ "components/cronet/android/api/src/org/chromium/net/BidirectionalStream.java",
+ "components/cronet/android/api/src/org/chromium/net/CallbackException.java",
+ "components/cronet/android/api/src/org/chromium/net/CronetEngine.java",
+ "components/cronet/android/api/src/org/chromium/net/CronetException.java",
+ "components/cronet/android/api/src/org/chromium/net/CronetProvider.java",
+ "components/cronet/android/api/src/org/chromium/net/ExperimentalBidirectionalStream.java",
+ "components/cronet/android/api/src/org/chromium/net/ExperimentalCronetEngine.java",
+ "components/cronet/android/api/src/org/chromium/net/ExperimentalUrlRequest.java",
+ "components/cronet/android/api/src/org/chromium/net/ICronetEngineBuilder.java",
+ "components/cronet/android/api/src/org/chromium/net/InlineExecutionProhibitedException.java",
+ "components/cronet/android/api/src/org/chromium/net/NetworkException.java",
+ "components/cronet/android/api/src/org/chromium/net/NetworkQualityRttListener.java",
+ "components/cronet/android/api/src/org/chromium/net/NetworkQualityThroughputListener.java",
+ "components/cronet/android/api/src/org/chromium/net/QuicException.java",
+ "components/cronet/android/api/src/org/chromium/net/RequestFinishedInfo.java",
+ "components/cronet/android/api/src/org/chromium/net/UploadDataProvider.java",
+ "components/cronet/android/api/src/org/chromium/net/UploadDataProviders.java",
+ "components/cronet/android/api/src/org/chromium/net/UploadDataSink.java",
+ "components/cronet/android/api/src/org/chromium/net/UrlRequest.java",
+ "components/cronet/android/api/src/org/chromium/net/UrlResponseInfo.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/ByteArrayCronetCallback.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/ContentTypeParametersParser.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetRequestCompletionListener.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetResponse.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/ImplicitFlowControlCallback.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/InMemoryTransformCronetCallback.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/JsonCronetCallback.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandler.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandlers.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/StringCronetCallback.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/UploadDataProviders.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/UrlRequestCallbacks.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetController.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetEngine.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetProvider.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlRequest.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlResponse.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/ResponseMatcher.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/UrlResponseMatcher.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamBuilderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamNetworkException.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CallbackExceptionImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBase.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBuilderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetExceptionImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetLogger.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetLoggerFactory.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetMetrics.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/InputStreamChannel.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngineBuilderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetProvider.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaUploadDataSinkBase.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequestUtils.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderWithLibraryLoaderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetProvider.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/Preconditions.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/QuicExceptionImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/RequestFinishedInfoImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBase.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBuilderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/UrlResponseInfoImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/UserAgent.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/VersionSafeCallbacks.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetBufferedOutputStream.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetChunkedOutputStream.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetFixedModeOutputStream.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLStreamHandler.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetInputStream.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetOutputStream.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetURLStreamHandlerFactory.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/MessageLoop.java",
+ "net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java",
+ "net/android/java/src/org/chromium/net/AndroidKeyStore.java",
+ "net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java",
+ "net/android/java/src/org/chromium/net/AndroidTrafficStats.java",
+ "net/android/java/src/org/chromium/net/ChromiumNetworkAdapter.java",
+ "net/android/java/src/org/chromium/net/DnsStatus.java",
+ "net/android/java/src/org/chromium/net/GURLUtils.java",
+ "net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
+ "net/android/java/src/org/chromium/net/HttpNegotiateConstants.java",
+ "net/android/java/src/org/chromium/net/HttpUtil.java",
+ "net/android/java/src/org/chromium/net/MimeTypeFilter.java",
+ "net/android/java/src/org/chromium/net/NetStringUtil.java",
+ "net/android/java/src/org/chromium/net/NetworkActiveNotifier.java",
+ "net/android/java/src/org/chromium/net/NetworkChangeNotifier.java",
+ "net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java",
+ "net/android/java/src/org/chromium/net/NetworkTrafficAnnotationTag.java",
+ "net/android/java/src/org/chromium/net/ProxyBroadcastReceiver.java",
+ "net/android/java/src/org/chromium/net/ProxyChangeListener.java",
+ "net/android/java/src/org/chromium/net/RegistrationPolicyAlwaysRegister.java",
+ "net/android/java/src/org/chromium/net/RegistrationPolicyApplicationStatus.java",
+ "net/android/java/src/org/chromium/net/ThreadStatsUid.java",
+ "net/android/java/src/org/chromium/net/X509Util.java",
+ "url/android/java/src/org/chromium/url/IDNStringUtil.java",
+ ],
+ cmd: "current_dir=`basename \\`pwd\\``; " +
+ "for f in $(in); " +
+ "do " +
+ "echo \"../$$current_dir/$$f\" >> $(genDir)/java.sources; " +
+ "done; " +
+ "python3 $(location base/android/jni_generator/jni_registration_generator.py) --srcjar-path " +
+ "$(genDir)/components/cronet/android/cronet_jni_registration.srcjar " +
+ "--depfile " +
+ "$(genDir)/components/cronet/android/cronet_jni_registration.d " +
+ "--sources-files " +
+ "$(genDir)/java.sources " +
+ "--include_test_only " +
+ "--use_proxy_hash " +
+ "--header-path " +
+ "$(genDir)/components/cronet/android/cronet_jni_registration.h " +
+ "--manual_jni_registration " +
+ ";sed -i -e 's/OUT_SOONG_.TEMP_SBOX_.*_OUT/GEN/g' " +
+ "$(genDir)/components/cronet/android/cronet_jni_registration.h",
+ out: [
+ "components/cronet/android/cronet_jni_registration.h",
+ "components/cronet/android/cronet_jni_registration.srcjar",
+ ],
+ tool_files: [
+ "base/android/jni_generator/jni_generator.py",
+ "base/android/jni_generator/jni_registration_generator.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/gn_helpers.py",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ ],
+}
+
+// GN: //components/cronet/android:cronet_jni_registration__android_arm64
+cc_genrule {
+ name: "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm64",
+ srcs: [
+ "base/android/java/src/org/chromium/base/ActivityState.java",
+ "base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java",
+ "base/android/java/src/org/chromium/base/ApkAssets.java",
+ "base/android/java/src/org/chromium/base/ApplicationStatus.java",
+ "base/android/java/src/org/chromium/base/BaseFeatureList.java",
+ "base/android/java/src/org/chromium/base/BuildInfo.java",
+ "base/android/java/src/org/chromium/base/BundleUtils.java",
+ "base/android/java/src/org/chromium/base/ByteArrayGenerator.java",
+ "base/android/java/src/org/chromium/base/Callback.java",
+ "base/android/java/src/org/chromium/base/CallbackController.java",
+ "base/android/java/src/org/chromium/base/CollectionUtil.java",
+ "base/android/java/src/org/chromium/base/CommandLine.java",
+ "base/android/java/src/org/chromium/base/CommandLineInitUtil.java",
+ "base/android/java/src/org/chromium/base/Consumer.java",
+ "base/android/java/src/org/chromium/base/ContentUriUtils.java",
+ "base/android/java/src/org/chromium/base/ContextUtils.java",
+ "base/android/java/src/org/chromium/base/CpuFeatures.java",
+ "base/android/java/src/org/chromium/base/DiscardableReferencePool.java",
+ "base/android/java/src/org/chromium/base/EarlyTraceEvent.java",
+ "base/android/java/src/org/chromium/base/EventLog.java",
+ "base/android/java/src/org/chromium/base/FeatureList.java",
+ "base/android/java/src/org/chromium/base/Features.java",
+ "base/android/java/src/org/chromium/base/FieldTrialList.java",
+ "base/android/java/src/org/chromium/base/FileUtils.java",
+ "base/android/java/src/org/chromium/base/Function.java",
+ "base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
+ "base/android/java/src/org/chromium/base/IntStringCallback.java",
+ "base/android/java/src/org/chromium/base/IntentUtils.java",
+ "base/android/java/src/org/chromium/base/JNIUtils.java",
+ "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
+ "base/android/java/src/org/chromium/base/JavaHandlerThread.java",
+ "base/android/java/src/org/chromium/base/JniException.java",
+ "base/android/java/src/org/chromium/base/JniStaticTestMocker.java",
+ "base/android/java/src/org/chromium/base/LifetimeAssert.java",
+ "base/android/java/src/org/chromium/base/LocaleUtils.java",
+ "base/android/java/src/org/chromium/base/Log.java",
+ "base/android/java/src/org/chromium/base/MathUtils.java",
+ "base/android/java/src/org/chromium/base/MemoryPressureListener.java",
+ "base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java",
+ "base/android/java/src/org/chromium/base/ObserverList.java",
+ "base/android/java/src/org/chromium/base/PackageManagerUtils.java",
+ "base/android/java/src/org/chromium/base/PackageUtils.java",
+ "base/android/java/src/org/chromium/base/PathService.java",
+ "base/android/java/src/org/chromium/base/PathUtils.java",
+ "base/android/java/src/org/chromium/base/PiiElider.java",
+ "base/android/java/src/org/chromium/base/PowerMonitor.java",
+ "base/android/java/src/org/chromium/base/PowerMonitorForQ.java",
+ "base/android/java/src/org/chromium/base/Predicate.java",
+ "base/android/java/src/org/chromium/base/Promise.java",
+ "base/android/java/src/org/chromium/base/RadioUtils.java",
+ "base/android/java/src/org/chromium/base/StreamUtil.java",
+ "base/android/java/src/org/chromium/base/StrictModeContext.java",
+ "base/android/java/src/org/chromium/base/SysUtils.java",
+ "base/android/java/src/org/chromium/base/ThreadUtils.java",
+ "base/android/java/src/org/chromium/base/TimeUtils.java",
+ "base/android/java/src/org/chromium/base/TimezoneUtils.java",
+ "base/android/java/src/org/chromium/base/TraceEvent.java",
+ "base/android/java/src/org/chromium/base/UnguessableToken.java",
+ "base/android/java/src/org/chromium/base/UnownedUserData.java",
+ "base/android/java/src/org/chromium/base/UnownedUserDataHost.java",
+ "base/android/java/src/org/chromium/base/UnownedUserDataKey.java",
+ "base/android/java/src/org/chromium/base/UserData.java",
+ "base/android/java/src/org/chromium/base/UserDataHost.java",
+ "base/android/java/src/org/chromium/base/WrappedClassLoader.java",
+ "base/android/java/src/org/chromium/base/annotations/AccessedByNative.java",
+ "base/android/java/src/org/chromium/base/annotations/CalledByNative.java",
+ "base/android/java/src/org/chromium/base/annotations/CalledByNativeForTesting.java",
+ "base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java",
+ "base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java",
+ "base/android/java/src/org/chromium/base/annotations/JNINamespace.java",
+ "base/android/java/src/org/chromium/base/annotations/JniIgnoreNatives.java",
+ "base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java",
+ "base/android/java/src/org/chromium/base/annotations/NativeMethods.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForM.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForN.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForO.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForOMR1.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForP.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForQ.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForR.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForS.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/DummyJankTracker.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/FrameMetrics.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsListener.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsStore.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankActivityTracker.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankMetricCalculator.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankMetrics.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankReportingRunnable.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankReportingScheduler.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankScenario.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankTracker.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankTrackerImpl.java",
+ "base/android/java/src/org/chromium/base/library_loader/LegacyLinker.java",
+ "base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
+ "base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
+ "base/android/java/src/org/chromium/base/library_loader/Linker.java",
+ "base/android/java/src/org/chromium/base/library_loader/LinkerJni.java",
+ "base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java",
+ "base/android/java/src/org/chromium/base/library_loader/ModernLinker.java",
+ "base/android/java/src/org/chromium/base/library_loader/ModernLinkerJni.java",
+ "base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java",
+ "base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java",
+ "base/android/java/src/org/chromium/base/lifetime/DestroyChecker.java",
+ "base/android/java/src/org/chromium/base/lifetime/Destroyable.java",
+ "base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java",
+ "base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java",
+ "base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java",
+ "base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java",
+ "base/android/java/src/org/chromium/base/memory/MemoryPurgeManager.java",
+ "base/android/java/src/org/chromium/base/metrics/CachingUmaRecorder.java",
+ "base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java",
+ "base/android/java/src/org/chromium/base/metrics/NoopUmaRecorder.java",
+ "base/android/java/src/org/chromium/base/metrics/RecordHistogram.java",
+ "base/android/java/src/org/chromium/base/metrics/RecordUserAction.java",
+ "base/android/java/src/org/chromium/base/metrics/ScopedSysTraceEvent.java",
+ "base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java",
+ "base/android/java/src/org/chromium/base/metrics/TimingMetric.java",
+ "base/android/java/src/org/chromium/base/metrics/UmaRecorder.java",
+ "base/android/java/src/org/chromium/base/metrics/UmaRecorderHolder.java",
+ "base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java",
+ "base/android/java/src/org/chromium/base/process_launcher/BindService.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnection.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionDelegate.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionFactory.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionImpl.java",
+ "base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java",
+ "base/android/java/src/org/chromium/base/supplier/BooleanSupplier.java",
+ "base/android/java/src/org/chromium/base/supplier/DestroyableObservableSupplier.java",
+ "base/android/java/src/org/chromium/base/supplier/ObservableSupplier.java",
+ "base/android/java/src/org/chromium/base/supplier/ObservableSupplierImpl.java",
+ "base/android/java/src/org/chromium/base/supplier/OneShotCallback.java",
+ "base/android/java/src/org/chromium/base/supplier/OneshotSupplier.java",
+ "base/android/java/src/org/chromium/base/supplier/OneshotSupplierImpl.java",
+ "base/android/java/src/org/chromium/base/supplier/Supplier.java",
+ "base/android/java/src/org/chromium/base/supplier/UnownedUserDataSupplier.java",
+ "base/android/java/src/org/chromium/base/task/AsyncTask.java",
+ "base/android/java/src/org/chromium/base/task/BackgroundOnlyAsyncTask.java",
+ "base/android/java/src/org/chromium/base/task/ChainedTasks.java",
+ "base/android/java/src/org/chromium/base/task/ChoreographerTaskRunner.java",
+ "base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java",
+ "base/android/java/src/org/chromium/base/task/DefaultTaskExecutor.java",
+ "base/android/java/src/org/chromium/base/task/PostTask.java",
+ "base/android/java/src/org/chromium/base/task/SequencedTaskRunner.java",
+ "base/android/java/src/org/chromium/base/task/SequencedTaskRunnerImpl.java",
+ "base/android/java/src/org/chromium/base/task/SerialExecutor.java",
+ "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunner.java",
+ "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java",
+ "base/android/java/src/org/chromium/base/task/TaskExecutor.java",
+ "base/android/java/src/org/chromium/base/task/TaskRunner.java",
+ "base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java",
+ "base/android/java/src/org/chromium/base/task/TaskTraits.java",
+ "base/android/java/src/org/chromium/base/task/TaskTraitsExtensionDescriptor.java",
+ "build/android/java/src/org/chromium/build/annotations/AlwaysInline.java",
+ "build/android/java/src/org/chromium/build/annotations/CheckDiscard.java",
+ "build/android/java/src/org/chromium/build/annotations/DoNotClassMerge.java",
+ "build/android/java/src/org/chromium/build/annotations/DoNotInline.java",
+ "build/android/java/src/org/chromium/build/annotations/IdentifierNameString.java",
+ "build/android/java/src/org/chromium/build/annotations/MainDex.java",
+ "build/android/java/src/org/chromium/build/annotations/MockedInTests.java",
+ "build/android/java/src/org/chromium/build/annotations/UsedByReflection.java",
+ "components/cronet/android/api/src/org/chromium/net/BidirectionalStream.java",
+ "components/cronet/android/api/src/org/chromium/net/CallbackException.java",
+ "components/cronet/android/api/src/org/chromium/net/CronetEngine.java",
+ "components/cronet/android/api/src/org/chromium/net/CronetException.java",
+ "components/cronet/android/api/src/org/chromium/net/CronetProvider.java",
+ "components/cronet/android/api/src/org/chromium/net/ExperimentalBidirectionalStream.java",
+ "components/cronet/android/api/src/org/chromium/net/ExperimentalCronetEngine.java",
+ "components/cronet/android/api/src/org/chromium/net/ExperimentalUrlRequest.java",
+ "components/cronet/android/api/src/org/chromium/net/ICronetEngineBuilder.java",
+ "components/cronet/android/api/src/org/chromium/net/InlineExecutionProhibitedException.java",
+ "components/cronet/android/api/src/org/chromium/net/NetworkException.java",
+ "components/cronet/android/api/src/org/chromium/net/NetworkQualityRttListener.java",
+ "components/cronet/android/api/src/org/chromium/net/NetworkQualityThroughputListener.java",
+ "components/cronet/android/api/src/org/chromium/net/QuicException.java",
+ "components/cronet/android/api/src/org/chromium/net/RequestFinishedInfo.java",
+ "components/cronet/android/api/src/org/chromium/net/UploadDataProvider.java",
+ "components/cronet/android/api/src/org/chromium/net/UploadDataProviders.java",
+ "components/cronet/android/api/src/org/chromium/net/UploadDataSink.java",
+ "components/cronet/android/api/src/org/chromium/net/UrlRequest.java",
+ "components/cronet/android/api/src/org/chromium/net/UrlResponseInfo.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/ByteArrayCronetCallback.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/ContentTypeParametersParser.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetRequestCompletionListener.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetResponse.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/ImplicitFlowControlCallback.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/InMemoryTransformCronetCallback.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/JsonCronetCallback.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandler.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandlers.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/StringCronetCallback.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/UploadDataProviders.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/UrlRequestCallbacks.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetController.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetEngine.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetProvider.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlRequest.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlResponse.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/ResponseMatcher.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/UrlResponseMatcher.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamBuilderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamNetworkException.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CallbackExceptionImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBase.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBuilderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetExceptionImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetLogger.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetLoggerFactory.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetMetrics.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/InputStreamChannel.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngineBuilderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetProvider.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaUploadDataSinkBase.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequestUtils.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderWithLibraryLoaderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetProvider.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/Preconditions.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/QuicExceptionImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/RequestFinishedInfoImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBase.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBuilderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/UrlResponseInfoImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/UserAgent.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/VersionSafeCallbacks.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetBufferedOutputStream.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetChunkedOutputStream.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetFixedModeOutputStream.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLStreamHandler.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetInputStream.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetOutputStream.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetURLStreamHandlerFactory.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/MessageLoop.java",
+ "net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java",
+ "net/android/java/src/org/chromium/net/AndroidKeyStore.java",
+ "net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java",
+ "net/android/java/src/org/chromium/net/AndroidTrafficStats.java",
+ "net/android/java/src/org/chromium/net/ChromiumNetworkAdapter.java",
+ "net/android/java/src/org/chromium/net/DnsStatus.java",
+ "net/android/java/src/org/chromium/net/GURLUtils.java",
+ "net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
+ "net/android/java/src/org/chromium/net/HttpNegotiateConstants.java",
+ "net/android/java/src/org/chromium/net/HttpUtil.java",
+ "net/android/java/src/org/chromium/net/MimeTypeFilter.java",
+ "net/android/java/src/org/chromium/net/NetStringUtil.java",
+ "net/android/java/src/org/chromium/net/NetworkActiveNotifier.java",
+ "net/android/java/src/org/chromium/net/NetworkChangeNotifier.java",
+ "net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java",
+ "net/android/java/src/org/chromium/net/NetworkTrafficAnnotationTag.java",
+ "net/android/java/src/org/chromium/net/ProxyBroadcastReceiver.java",
+ "net/android/java/src/org/chromium/net/ProxyChangeListener.java",
+ "net/android/java/src/org/chromium/net/RegistrationPolicyAlwaysRegister.java",
+ "net/android/java/src/org/chromium/net/RegistrationPolicyApplicationStatus.java",
+ "net/android/java/src/org/chromium/net/ThreadStatsUid.java",
+ "net/android/java/src/org/chromium/net/X509Util.java",
+ "url/android/java/src/org/chromium/url/IDNStringUtil.java",
+ ],
+ cmd: "current_dir=`basename \\`pwd\\``; " +
+ "for f in $(in); " +
+ "do " +
+ "echo \"../$$current_dir/$$f\" >> $(genDir)/java.sources; " +
+ "done; " +
+ "python3 $(location base/android/jni_generator/jni_registration_generator.py) --srcjar-path " +
+ "$(genDir)/components/cronet/android/cronet_jni_registration.srcjar " +
+ "--depfile " +
+ "$(genDir)/components/cronet/android/cronet_jni_registration.d " +
+ "--sources-files " +
+ "$(genDir)/java.sources " +
+ "--include_test_only " +
+ "--use_proxy_hash " +
+ "--header-path " +
+ "$(genDir)/components/cronet/android/cronet_jni_registration.h " +
+ "--manual_jni_registration " +
+ ";sed -i -e 's/OUT_SOONG_.TEMP_SBOX_.*_OUT/GEN/g' " +
+ "$(genDir)/components/cronet/android/cronet_jni_registration.h",
+ out: [
+ "components/cronet/android/cronet_jni_registration.h",
+ "components/cronet/android/cronet_jni_registration.srcjar",
+ ],
+ tool_files: [
+ "base/android/jni_generator/jni_generator.py",
+ "base/android/jni_generator/jni_registration_generator.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/gn_helpers.py",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ ],
+}
+
+// GN: //components/cronet/android:cronet_jni_registration__android_x86
+cc_genrule {
+ name: "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86",
+ srcs: [
+ "base/android/java/src/org/chromium/base/ActivityState.java",
+ "base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java",
+ "base/android/java/src/org/chromium/base/ApkAssets.java",
+ "base/android/java/src/org/chromium/base/ApplicationStatus.java",
+ "base/android/java/src/org/chromium/base/BaseFeatureList.java",
+ "base/android/java/src/org/chromium/base/BuildInfo.java",
+ "base/android/java/src/org/chromium/base/BundleUtils.java",
+ "base/android/java/src/org/chromium/base/ByteArrayGenerator.java",
+ "base/android/java/src/org/chromium/base/Callback.java",
+ "base/android/java/src/org/chromium/base/CallbackController.java",
+ "base/android/java/src/org/chromium/base/CollectionUtil.java",
+ "base/android/java/src/org/chromium/base/CommandLine.java",
+ "base/android/java/src/org/chromium/base/CommandLineInitUtil.java",
+ "base/android/java/src/org/chromium/base/Consumer.java",
+ "base/android/java/src/org/chromium/base/ContentUriUtils.java",
+ "base/android/java/src/org/chromium/base/ContextUtils.java",
+ "base/android/java/src/org/chromium/base/CpuFeatures.java",
+ "base/android/java/src/org/chromium/base/DiscardableReferencePool.java",
+ "base/android/java/src/org/chromium/base/EarlyTraceEvent.java",
+ "base/android/java/src/org/chromium/base/EventLog.java",
+ "base/android/java/src/org/chromium/base/FeatureList.java",
+ "base/android/java/src/org/chromium/base/Features.java",
+ "base/android/java/src/org/chromium/base/FieldTrialList.java",
+ "base/android/java/src/org/chromium/base/FileUtils.java",
+ "base/android/java/src/org/chromium/base/Function.java",
+ "base/android/java/src/org/chromium/base/ImportantFileWriterAndroid.java",
+ "base/android/java/src/org/chromium/base/IntStringCallback.java",
+ "base/android/java/src/org/chromium/base/IntentUtils.java",
+ "base/android/java/src/org/chromium/base/JNIUtils.java",
+ "base/android/java/src/org/chromium/base/JavaExceptionReporter.java",
+ "base/android/java/src/org/chromium/base/JavaHandlerThread.java",
+ "base/android/java/src/org/chromium/base/JniException.java",
+ "base/android/java/src/org/chromium/base/JniStaticTestMocker.java",
+ "base/android/java/src/org/chromium/base/LifetimeAssert.java",
+ "base/android/java/src/org/chromium/base/LocaleUtils.java",
+ "base/android/java/src/org/chromium/base/Log.java",
+ "base/android/java/src/org/chromium/base/MathUtils.java",
+ "base/android/java/src/org/chromium/base/MemoryPressureListener.java",
+ "base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java",
+ "base/android/java/src/org/chromium/base/ObserverList.java",
+ "base/android/java/src/org/chromium/base/PackageManagerUtils.java",
+ "base/android/java/src/org/chromium/base/PackageUtils.java",
+ "base/android/java/src/org/chromium/base/PathService.java",
+ "base/android/java/src/org/chromium/base/PathUtils.java",
+ "base/android/java/src/org/chromium/base/PiiElider.java",
+ "base/android/java/src/org/chromium/base/PowerMonitor.java",
+ "base/android/java/src/org/chromium/base/PowerMonitorForQ.java",
+ "base/android/java/src/org/chromium/base/Predicate.java",
+ "base/android/java/src/org/chromium/base/Promise.java",
+ "base/android/java/src/org/chromium/base/RadioUtils.java",
+ "base/android/java/src/org/chromium/base/StreamUtil.java",
+ "base/android/java/src/org/chromium/base/StrictModeContext.java",
+ "base/android/java/src/org/chromium/base/SysUtils.java",
+ "base/android/java/src/org/chromium/base/ThreadUtils.java",
+ "base/android/java/src/org/chromium/base/TimeUtils.java",
+ "base/android/java/src/org/chromium/base/TimezoneUtils.java",
+ "base/android/java/src/org/chromium/base/TraceEvent.java",
+ "base/android/java/src/org/chromium/base/UnguessableToken.java",
+ "base/android/java/src/org/chromium/base/UnownedUserData.java",
+ "base/android/java/src/org/chromium/base/UnownedUserDataHost.java",
+ "base/android/java/src/org/chromium/base/UnownedUserDataKey.java",
+ "base/android/java/src/org/chromium/base/UserData.java",
+ "base/android/java/src/org/chromium/base/UserDataHost.java",
+ "base/android/java/src/org/chromium/base/WrappedClassLoader.java",
+ "base/android/java/src/org/chromium/base/annotations/AccessedByNative.java",
+ "base/android/java/src/org/chromium/base/annotations/CalledByNative.java",
+ "base/android/java/src/org/chromium/base/annotations/CalledByNativeForTesting.java",
+ "base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java",
+ "base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java",
+ "base/android/java/src/org/chromium/base/annotations/JNINamespace.java",
+ "base/android/java/src/org/chromium/base/annotations/JniIgnoreNatives.java",
+ "base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java",
+ "base/android/java/src/org/chromium/base/annotations/NativeMethods.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForM.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForN.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForO.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForOMR1.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForP.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForQ.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForR.java",
+ "base/android/java/src/org/chromium/base/compat/ApiHelperForS.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/DummyJankTracker.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/FrameMetrics.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsListener.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/FrameMetricsStore.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankActivityTracker.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankMetricCalculator.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankMetricUMARecorder.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankMetrics.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankReportingRunnable.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankReportingScheduler.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankScenario.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankTracker.java",
+ "base/android/java/src/org/chromium/base/jank_tracker/JankTrackerImpl.java",
+ "base/android/java/src/org/chromium/base/library_loader/LegacyLinker.java",
+ "base/android/java/src/org/chromium/base/library_loader/LibraryLoader.java",
+ "base/android/java/src/org/chromium/base/library_loader/LibraryPrefetcher.java",
+ "base/android/java/src/org/chromium/base/library_loader/Linker.java",
+ "base/android/java/src/org/chromium/base/library_loader/LinkerJni.java",
+ "base/android/java/src/org/chromium/base/library_loader/LoaderErrors.java",
+ "base/android/java/src/org/chromium/base/library_loader/ModernLinker.java",
+ "base/android/java/src/org/chromium/base/library_loader/ModernLinkerJni.java",
+ "base/android/java/src/org/chromium/base/library_loader/NativeLibraryPreloader.java",
+ "base/android/java/src/org/chromium/base/library_loader/ProcessInitException.java",
+ "base/android/java/src/org/chromium/base/lifetime/DestroyChecker.java",
+ "base/android/java/src/org/chromium/base/lifetime/Destroyable.java",
+ "base/android/java/src/org/chromium/base/memory/JavaHeapDumpGenerator.java",
+ "base/android/java/src/org/chromium/base/memory/MemoryPressureCallback.java",
+ "base/android/java/src/org/chromium/base/memory/MemoryPressureMonitor.java",
+ "base/android/java/src/org/chromium/base/memory/MemoryPressureUma.java",
+ "base/android/java/src/org/chromium/base/memory/MemoryPurgeManager.java",
+ "base/android/java/src/org/chromium/base/metrics/CachingUmaRecorder.java",
+ "base/android/java/src/org/chromium/base/metrics/NativeUmaRecorder.java",
+ "base/android/java/src/org/chromium/base/metrics/NoopUmaRecorder.java",
+ "base/android/java/src/org/chromium/base/metrics/RecordHistogram.java",
+ "base/android/java/src/org/chromium/base/metrics/RecordUserAction.java",
+ "base/android/java/src/org/chromium/base/metrics/ScopedSysTraceEvent.java",
+ "base/android/java/src/org/chromium/base/metrics/StatisticsRecorderAndroid.java",
+ "base/android/java/src/org/chromium/base/metrics/TimingMetric.java",
+ "base/android/java/src/org/chromium/base/metrics/UmaRecorder.java",
+ "base/android/java/src/org/chromium/base/metrics/UmaRecorderHolder.java",
+ "base/android/java/src/org/chromium/base/multidex/ChromiumMultiDexInstaller.java",
+ "base/android/java/src/org/chromium/base/process_launcher/BindService.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildConnectionAllocator.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildProcessConstants.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildProcessLauncher.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildProcessService.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildProcessServiceDelegate.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnection.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionDelegate.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionFactory.java",
+ "base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionImpl.java",
+ "base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java",
+ "base/android/java/src/org/chromium/base/supplier/BooleanSupplier.java",
+ "base/android/java/src/org/chromium/base/supplier/DestroyableObservableSupplier.java",
+ "base/android/java/src/org/chromium/base/supplier/ObservableSupplier.java",
+ "base/android/java/src/org/chromium/base/supplier/ObservableSupplierImpl.java",
+ "base/android/java/src/org/chromium/base/supplier/OneShotCallback.java",
+ "base/android/java/src/org/chromium/base/supplier/OneshotSupplier.java",
+ "base/android/java/src/org/chromium/base/supplier/OneshotSupplierImpl.java",
+ "base/android/java/src/org/chromium/base/supplier/Supplier.java",
+ "base/android/java/src/org/chromium/base/supplier/UnownedUserDataSupplier.java",
+ "base/android/java/src/org/chromium/base/task/AsyncTask.java",
+ "base/android/java/src/org/chromium/base/task/BackgroundOnlyAsyncTask.java",
+ "base/android/java/src/org/chromium/base/task/ChainedTasks.java",
+ "base/android/java/src/org/chromium/base/task/ChoreographerTaskRunner.java",
+ "base/android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java",
+ "base/android/java/src/org/chromium/base/task/DefaultTaskExecutor.java",
+ "base/android/java/src/org/chromium/base/task/PostTask.java",
+ "base/android/java/src/org/chromium/base/task/SequencedTaskRunner.java",
+ "base/android/java/src/org/chromium/base/task/SequencedTaskRunnerImpl.java",
+ "base/android/java/src/org/chromium/base/task/SerialExecutor.java",
+ "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunner.java",
+ "base/android/java/src/org/chromium/base/task/SingleThreadTaskRunnerImpl.java",
+ "base/android/java/src/org/chromium/base/task/TaskExecutor.java",
+ "base/android/java/src/org/chromium/base/task/TaskRunner.java",
+ "base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java",
+ "base/android/java/src/org/chromium/base/task/TaskTraits.java",
+ "base/android/java/src/org/chromium/base/task/TaskTraitsExtensionDescriptor.java",
+ "build/android/java/src/org/chromium/build/annotations/AlwaysInline.java",
+ "build/android/java/src/org/chromium/build/annotations/CheckDiscard.java",
+ "build/android/java/src/org/chromium/build/annotations/DoNotClassMerge.java",
+ "build/android/java/src/org/chromium/build/annotations/DoNotInline.java",
+ "build/android/java/src/org/chromium/build/annotations/IdentifierNameString.java",
+ "build/android/java/src/org/chromium/build/annotations/MainDex.java",
+ "build/android/java/src/org/chromium/build/annotations/MockedInTests.java",
+ "build/android/java/src/org/chromium/build/annotations/UsedByReflection.java",
+ "components/cronet/android/api/src/org/chromium/net/BidirectionalStream.java",
+ "components/cronet/android/api/src/org/chromium/net/CallbackException.java",
+ "components/cronet/android/api/src/org/chromium/net/CronetEngine.java",
+ "components/cronet/android/api/src/org/chromium/net/CronetException.java",
+ "components/cronet/android/api/src/org/chromium/net/CronetProvider.java",
+ "components/cronet/android/api/src/org/chromium/net/ExperimentalBidirectionalStream.java",
+ "components/cronet/android/api/src/org/chromium/net/ExperimentalCronetEngine.java",
+ "components/cronet/android/api/src/org/chromium/net/ExperimentalUrlRequest.java",
+ "components/cronet/android/api/src/org/chromium/net/ICronetEngineBuilder.java",
+ "components/cronet/android/api/src/org/chromium/net/InlineExecutionProhibitedException.java",
+ "components/cronet/android/api/src/org/chromium/net/NetworkException.java",
+ "components/cronet/android/api/src/org/chromium/net/NetworkQualityRttListener.java",
+ "components/cronet/android/api/src/org/chromium/net/NetworkQualityThroughputListener.java",
+ "components/cronet/android/api/src/org/chromium/net/QuicException.java",
+ "components/cronet/android/api/src/org/chromium/net/RequestFinishedInfo.java",
+ "components/cronet/android/api/src/org/chromium/net/UploadDataProvider.java",
+ "components/cronet/android/api/src/org/chromium/net/UploadDataProviders.java",
+ "components/cronet/android/api/src/org/chromium/net/UploadDataSink.java",
+ "components/cronet/android/api/src/org/chromium/net/UrlRequest.java",
+ "components/cronet/android/api/src/org/chromium/net/UrlResponseInfo.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/ByteArrayCronetCallback.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/ContentTypeParametersParser.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetRequestCompletionListener.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/CronetResponse.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/ImplicitFlowControlCallback.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/InMemoryTransformCronetCallback.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/JsonCronetCallback.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandler.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/RedirectHandlers.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/StringCronetCallback.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/UploadDataProviders.java",
+ "components/cronet/android/api/src/org/chromium/net/apihelpers/UrlRequestCallbacks.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetController.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetEngine.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/FakeCronetProvider.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlRequest.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/FakeUrlResponse.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/ResponseMatcher.java",
+ "components/cronet/android/fake/java/org/chromium/net/test/UrlResponseMatcher.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamBuilderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/BidirectionalStreamNetworkException.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CallbackExceptionImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetBidirectionalStream.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBase.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBuilderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetExceptionImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetLibraryLoader.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetLogger.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetLoggerFactory.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetMetrics.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetUploadDataStream.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequest.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/CronetUrlRequestContext.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/InputStreamChannel.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngine.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetEngineBuilderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaCronetProvider.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaUploadDataSinkBase.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequest.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/JavaUrlRequestUtils.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetEngineBuilderWithLibraryLoaderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/NativeCronetProvider.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/NetworkExceptionImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/NoOpLogger.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/Preconditions.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/QuicExceptionImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/RequestFinishedInfoImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBase.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/UrlRequestBuilderImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/UrlResponseInfoImpl.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/UserAgent.java",
+ "components/cronet/android/java/src/org/chromium/net/impl/VersionSafeCallbacks.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetBufferedOutputStream.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetChunkedOutputStream.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetFixedModeOutputStream.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLConnection.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetHttpURLStreamHandler.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetInputStream.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetOutputStream.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/CronetURLStreamHandlerFactory.java",
+ "components/cronet/android/java/src/org/chromium/net/urlconnection/MessageLoop.java",
+ "net/android/java/src/org/chromium/net/AndroidCertVerifyResult.java",
+ "net/android/java/src/org/chromium/net/AndroidKeyStore.java",
+ "net/android/java/src/org/chromium/net/AndroidNetworkLibrary.java",
+ "net/android/java/src/org/chromium/net/AndroidTrafficStats.java",
+ "net/android/java/src/org/chromium/net/ChromiumNetworkAdapter.java",
+ "net/android/java/src/org/chromium/net/DnsStatus.java",
+ "net/android/java/src/org/chromium/net/GURLUtils.java",
+ "net/android/java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
+ "net/android/java/src/org/chromium/net/HttpNegotiateConstants.java",
+ "net/android/java/src/org/chromium/net/HttpUtil.java",
+ "net/android/java/src/org/chromium/net/MimeTypeFilter.java",
+ "net/android/java/src/org/chromium/net/NetStringUtil.java",
+ "net/android/java/src/org/chromium/net/NetworkActiveNotifier.java",
+ "net/android/java/src/org/chromium/net/NetworkChangeNotifier.java",
+ "net/android/java/src/org/chromium/net/NetworkChangeNotifierAutoDetect.java",
+ "net/android/java/src/org/chromium/net/NetworkTrafficAnnotationTag.java",
+ "net/android/java/src/org/chromium/net/ProxyBroadcastReceiver.java",
+ "net/android/java/src/org/chromium/net/ProxyChangeListener.java",
+ "net/android/java/src/org/chromium/net/RegistrationPolicyAlwaysRegister.java",
+ "net/android/java/src/org/chromium/net/RegistrationPolicyApplicationStatus.java",
+ "net/android/java/src/org/chromium/net/ThreadStatsUid.java",
+ "net/android/java/src/org/chromium/net/X509Util.java",
+ "url/android/java/src/org/chromium/url/IDNStringUtil.java",
+ ],
+ cmd: "current_dir=`basename \\`pwd\\``; " +
+ "for f in $(in); " +
+ "do " +
+ "echo \"../$$current_dir/$$f\" >> $(genDir)/java.sources; " +
+ "done; " +
+ "python3 $(location base/android/jni_generator/jni_registration_generator.py) --srcjar-path " +
+ "$(genDir)/components/cronet/android/cronet_jni_registration.srcjar " +
+ "--depfile " +
+ "$(genDir)/components/cronet/android/cronet_jni_registration.d " +
+ "--sources-files " +
+ "$(genDir)/java.sources " +
+ "--include_test_only " +
+ "--use_proxy_hash " +
+ "--header-path " +
+ "$(genDir)/components/cronet/android/cronet_jni_registration.h " +
+ "--manual_jni_registration " +
+ ";sed -i -e 's/OUT_SOONG_.TEMP_SBOX_.*_OUT/GEN/g' " +
+ "$(genDir)/components/cronet/android/cronet_jni_registration.h",
+ out: [
+ "components/cronet/android/cronet_jni_registration.h",
+ "components/cronet/android/cronet_jni_registration.srcjar",
+ ],
+ tool_files: [
+ "base/android/jni_generator/jni_generator.py",
+ "base/android/jni_generator/jni_registration_generator.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/gn_helpers.py",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ ],
+}
+
+// GN: //components/cronet/android:cronet_jni_registration__android_x86_64
+cc_genrule {
+ name: "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86_64",
srcs: [
"base/android/java/src/org/chromium/base/ActivityState.java",
"base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java",
@@ -7716,7 +8672,6 @@
"cronet_aml_url_url",
],
generated_headers: [
- "cronet_aml_components_cronet_android_cronet_jni_registration",
"cronet_aml_third_party_metrics_proto_metrics_proto_gen_headers",
],
defaults: [
@@ -7768,6 +8723,7 @@
"cronet_aml_build_chromeos_buildflags__android_arm",
"cronet_aml_components_cronet_android_buildflags__android_arm",
"cronet_aml_components_cronet_android_cronet_jni_headers__android_arm",
+ "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm",
"cronet_aml_components_cronet_cronet_buildflags__android_arm",
"cronet_aml_components_cronet_cronet_version_header_action__android_arm",
"cronet_aml_url_buildflags__android_arm",
@@ -7780,6 +8736,7 @@
"cronet_aml_build_chromeos_buildflags__android_arm64",
"cronet_aml_components_cronet_android_buildflags__android_arm64",
"cronet_aml_components_cronet_android_cronet_jni_headers__android_arm64",
+ "cronet_aml_components_cronet_android_cronet_jni_registration__android_arm64",
"cronet_aml_components_cronet_cronet_buildflags__android_arm64",
"cronet_aml_components_cronet_cronet_version_header_action__android_arm64",
"cronet_aml_url_buildflags__android_arm64",
@@ -7795,6 +8752,7 @@
"cronet_aml_build_chromeos_buildflags__android_x86",
"cronet_aml_components_cronet_android_buildflags__android_x86",
"cronet_aml_components_cronet_android_cronet_jni_headers__android_x86",
+ "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86",
"cronet_aml_components_cronet_cronet_buildflags__android_x86",
"cronet_aml_components_cronet_cronet_version_header_action__android_x86",
"cronet_aml_url_buildflags__android_x86",
@@ -7810,6 +8768,7 @@
"cronet_aml_build_chromeos_buildflags__android_x86_64",
"cronet_aml_components_cronet_android_buildflags__android_x86_64",
"cronet_aml_components_cronet_android_cronet_jni_headers__android_x86_64",
+ "cronet_aml_components_cronet_android_cronet_jni_registration__android_x86_64",
"cronet_aml_components_cronet_cronet_buildflags__android_x86_64",
"cronet_aml_components_cronet_cronet_version_header_action__android_x86_64",
"cronet_aml_url_buildflags__android_x86_64",
@@ -7829,6 +8788,10 @@
],
tool_files: [
"build/android/gyp/java_cpp_enum.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/android/gyp/util/java_cpp_utils.py",
+ "build/gn_helpers.py",
"components/cronet/url_request_context_config.h",
],
}
@@ -7858,20 +8821,45 @@
}
// GN: //components/cronet/android:integrated_mode_state
-java_genrule {
+genrule {
name: "cronet_aml_components_cronet_android_integrated_mode_state",
- cmd: "$(location build/android/gyp/gcc_preprocess.py) --include-dirs " +
- "[\"../../\", \"gen\"] " +
- "--output " +
- "gen/components/cronet/android/integrated_mode_state.srcjar " +
- "../../components/cronet/android/java/src/org/chromium/net/impl/IntegratedModeState.template",
- out: [
- "components/cronet/android/integrated_mode_state.srcjar",
+ srcs: [
+ ":cronet_aml_components_cronet_android_integrated_mode_state_preprocess",
],
- tool_files: [
- "build/android/gyp/gcc_preprocess.py",
+ tools: [
+ "soong_zip",
+ ],
+ cmd: "cp $(in) $(genDir)/IntegratedModeState.java && " +
+ "$(location soong_zip) -o $(out) -srcjar -f $(genDir)/IntegratedModeState.java",
+ out: [
+ "IntegratedModeState.srcjar",
+ ],
+}
+
+// GN: //components/cronet/android:integrated_mode_state
+cc_object {
+ name: "cronet_aml_components_cronet_android_integrated_mode_state_preprocess",
+ srcs: [
+ ":cronet_aml_components_cronet_android_integrated_mode_state_rename",
+ ],
+ cflags: [
+ "-DANDROID",
+ "-E",
+ "-P",
+ ],
+ compile_multilib: "first",
+}
+
+// GN: //components/cronet/android:integrated_mode_state
+genrule {
+ name: "cronet_aml_components_cronet_android_integrated_mode_state_rename",
+ srcs: [
"components/cronet/android/java/src/org/chromium/net/impl/IntegratedModeState.template",
],
+ cmd: "cp $(in) $(out)",
+ out: [
+ "IntegratedModeState.cc",
+ ],
}
// GN: //components/cronet/android:interface_api_version
@@ -7899,20 +8887,45 @@
}
// GN: //components/cronet/android:load_states_list
-java_genrule {
+genrule {
name: "cronet_aml_components_cronet_android_load_states_list",
- cmd: "$(location build/android/gyp/gcc_preprocess.py) --include-dirs " +
- "[\"../../\", \"gen\"] " +
- "--output " +
- "gen/components/cronet/android/load_states_list.srcjar " +
- "../../components/cronet/android/java/src/org/chromium/net/impl/LoadState.template",
- out: [
- "components/cronet/android/load_states_list.srcjar",
+ srcs: [
+ ":cronet_aml_components_cronet_android_load_states_list_preprocess",
],
- tool_files: [
- "build/android/gyp/gcc_preprocess.py",
+ tools: [
+ "soong_zip",
+ ],
+ cmd: "cp $(in) $(genDir)/LoadState.java && " +
+ "$(location soong_zip) -o $(out) -srcjar -f $(genDir)/LoadState.java",
+ out: [
+ "LoadState.srcjar",
+ ],
+}
+
+// GN: //components/cronet/android:load_states_list
+cc_object {
+ name: "cronet_aml_components_cronet_android_load_states_list_preprocess",
+ srcs: [
+ ":cronet_aml_components_cronet_android_load_states_list_rename",
+ ],
+ cflags: [
+ "-DANDROID",
+ "-E",
+ "-P",
+ ],
+ compile_multilib: "first",
+}
+
+// GN: //components/cronet/android:load_states_list
+genrule {
+ name: "cronet_aml_components_cronet_android_load_states_list_rename",
+ srcs: [
"components/cronet/android/java/src/org/chromium/net/impl/LoadState.template",
],
+ cmd: "cp $(in) $(out)",
+ out: [
+ "LoadState.cc",
+ ],
}
// GN: //components/cronet/android:net_idempotency_java
@@ -7926,6 +8939,10 @@
],
tool_files: [
"build/android/gyp/java_cpp_enum.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/android/gyp/util/java_cpp_utils.py",
+ "build/gn_helpers.py",
"net/base/idempotency.h",
],
}
@@ -7941,6 +8958,10 @@
],
tool_files: [
"build/android/gyp/java_cpp_enum.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/android/gyp/util/java_cpp_utils.py",
+ "build/gn_helpers.py",
"net/base/request_priority.h",
],
}
@@ -7956,6 +8977,10 @@
],
tool_files: [
"build/android/gyp/java_cpp_enum.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/android/gyp/util/java_cpp_utils.py",
+ "build/gn_helpers.py",
"net/nqe/network_quality_observation_source.h",
],
}
@@ -7971,6 +8996,10 @@
],
tool_files: [
"build/android/gyp/java_cpp_enum.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/android/gyp/util/java_cpp_utils.py",
+ "build/gn_helpers.py",
"net/nqe/network_quality.h",
],
}
@@ -7986,6 +9015,10 @@
],
tool_files: [
"build/android/gyp/java_cpp_enum.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/android/gyp/util/java_cpp_utils.py",
+ "build/gn_helpers.py",
"components/cronet/android/url_request_error.h",
],
}
@@ -9467,6 +10500,25 @@
java_library {
name: "cronet_aml_java",
srcs: [
+ ":cronet_aml_base_base_android_java_enums_srcjar",
+ ":cronet_aml_base_java_features_srcjar",
+ ":cronet_aml_base_java_switches_srcjar",
+ ":cronet_aml_build_android_build_config_gen",
+ ":cronet_aml_build_android_native_libraries_gen",
+ ":cronet_aml_components_cronet_android_cronet_jni_registration__java",
+ ":cronet_aml_components_cronet_android_http_cache_type_java",
+ ":cronet_aml_components_cronet_android_implementation_api_version",
+ ":cronet_aml_components_cronet_android_integrated_mode_state",
+ ":cronet_aml_components_cronet_android_interface_api_version",
+ ":cronet_aml_components_cronet_android_load_states_list",
+ ":cronet_aml_components_cronet_android_net_idempotency_java",
+ ":cronet_aml_components_cronet_android_net_request_priority_java",
+ ":cronet_aml_components_cronet_android_network_quality_observation_source_java",
+ ":cronet_aml_components_cronet_android_rtt_throughput_values_java",
+ ":cronet_aml_components_cronet_android_url_request_error_java",
+ ":cronet_aml_net_android_net_android_java_enums_srcjar",
+ ":cronet_aml_net_android_net_errors_java",
+ ":cronet_aml_net_effective_connection_type_java",
"base/android/java/src/org/chromium/base/ActivityState.java",
"base/android/java/src/org/chromium/base/ApiCompatibilityUtils.java",
"base/android/java/src/org/chromium/base/ApkAssets.java",
@@ -9601,6 +10653,8 @@
"base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionFactory.java",
"base/android/java/src/org/chromium/base/process_launcher/ChildServiceConnectionImpl.java",
"base/android/java/src/org/chromium/base/process_launcher/FileDescriptorInfo.java",
+ "base/android/java/src/org/chromium/base/process_launcher/IChildProcessService.aidl",
+ "base/android/java/src/org/chromium/base/process_launcher/IParentProcess.aidl",
"base/android/java/src/org/chromium/base/supplier/BooleanSupplier.java",
"base/android/java/src/org/chromium/base/supplier/DestroyableObservableSupplier.java",
"base/android/java/src/org/chromium/base/supplier/ObservableSupplier.java",
@@ -9742,6 +10796,56 @@
"net/android/java/src/org/chromium/net/X509Util.java",
"url/android/java/src/org/chromium/url/IDNStringUtil.java",
],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.tethering",
+ ],
+ libs: [
+ "android-support-multidex",
+ "androidx.annotation_annotation",
+ "androidx.annotation_annotation-experimental-nodeps",
+ "androidx.collection_collection",
+ "androidx.core_core-nodeps",
+ "framework-connectivity-t.stubs.module_lib",
+ "framework-connectivity.stubs.module_lib",
+ "framework-mediaprovider.stubs.module_lib",
+ "framework-tethering.stubs.module_lib",
+ "framework-wifi.stubs.module_lib",
+ "jsr305",
+ ],
+ aidl: {
+ include_dirs: [
+ "frameworks/base/core/java/",
+ ],
+ local_include_dirs: [
+ "base/android/java/src/",
+ ],
+ },
+ plugins: [
+ "cronet_aml_java_jni_annotation_preprocessor",
+ ],
+ sdk_version: "module_current",
+}
+
+// GN: //base/android/jni_generator:jni_processor
+java_plugin {
+ name: "cronet_aml_java_jni_annotation_preprocessor",
+ srcs: [
+ ":cronet_aml_build_android_build_config_gen",
+ "base/android/java/src/org/chromium/base/JniException.java",
+ "base/android/java/src/org/chromium/base/JniStaticTestMocker.java",
+ "base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java",
+ "base/android/java/src/org/chromium/base/annotations/NativeMethods.java",
+ "base/android/jni_generator/java/src/org/chromium/jni_generator/JniProcessor.java",
+ "build/android/java/src/org/chromium/build/annotations/CheckDiscard.java",
+ "build/android/java/src/org/chromium/build/annotations/MainDex.java",
+ ],
+ static_libs: [
+ "auto_service_annotations",
+ "guava",
+ "javapoet",
+ ],
+ processor_class: "org.chromium.jni_generator.JniProcessor",
}
// GN: //net/android:net_android_java_enums_srcjar
@@ -9765,6 +10869,10 @@
],
tool_files: [
"build/android/gyp/java_cpp_enum.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/android/gyp/util/java_cpp_utils.py",
+ "build/gn_helpers.py",
"net/android/cert_verify_result_android.h",
"net/android/keystore.h",
"net/base/network_change_notifier.h",
@@ -9772,20 +10880,45 @@
}
// GN: //net/android:net_errors_java
-java_genrule {
+genrule {
name: "cronet_aml_net_android_net_errors_java",
- cmd: "$(location build/android/gyp/gcc_preprocess.py) --include-dirs " +
- "[\"../../\", \"gen\"] " +
- "--output " +
- "gen/net/android/net_errors_java.srcjar " +
- "../../net/android/java/NetError.template",
- out: [
- "net/android/net_errors_java.srcjar",
+ srcs: [
+ ":cronet_aml_net_android_net_errors_java_preprocess",
],
- tool_files: [
- "build/android/gyp/gcc_preprocess.py",
+ tools: [
+ "soong_zip",
+ ],
+ cmd: "cp $(in) $(genDir)/NetError.java && " +
+ "$(location soong_zip) -o $(out) -srcjar -f $(genDir)/NetError.java",
+ out: [
+ "NetError.srcjar",
+ ],
+}
+
+// GN: //net/android:net_errors_java
+cc_object {
+ name: "cronet_aml_net_android_net_errors_java_preprocess",
+ srcs: [
+ ":cronet_aml_net_android_net_errors_java_rename",
+ ],
+ cflags: [
+ "-DANDROID",
+ "-E",
+ "-P",
+ ],
+ compile_multilib: "first",
+}
+
+// GN: //net/android:net_errors_java
+genrule {
+ name: "cronet_aml_net_android_net_errors_java_rename",
+ srcs: [
"net/android/java/NetError.template",
],
+ cmd: "cp $(in) $(out)",
+ out: [
+ "NetError.cc",
+ ],
}
// GN: //net/base/registry_controlled_domains:registry_controlled_domains__android_arm
@@ -11090,6 +12223,10 @@
],
tool_files: [
"build/android/gyp/java_cpp_enum.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/android/gyp/util/java_cpp_utils.py",
+ "build/gn_helpers.py",
"net/nqe/effective_connection_type.h",
],
}
diff --git a/tools/gn2bp/gen_android_bp b/tools/gn2bp/gen_android_bp
index 1836fd1..c57524d 100755
--- a/tools/gn2bp/gen_android_bp
+++ b/tools/gn2bp/gen_android_bp
@@ -34,6 +34,7 @@
import re
import sys
import copy
+from pathlib import Path
import gn_utils
@@ -313,7 +314,13 @@
self.cppflags = set()
self.rtti = False
# Name of the output. Used for setting .so file name for libcronet
+ self.libs = set()
self.stem = None
+ self.compile_multilib = None
+ self.aidl = dict()
+ self.plugins = set()
+ self.processor_class = None
+ self.sdk_version = None
def to_string(self, output):
if self.comment:
@@ -360,7 +367,13 @@
self._output_field(output, 'proto')
self._output_field(output, 'linker_scripts')
self._output_field(output, 'cppflags')
+ self._output_field(output, 'libs')
self._output_field(output, 'stem')
+ self._output_field(output, 'compile_multilib')
+ self._output_field(output, 'aidl')
+ self._output_field(output, 'plugins')
+ self._output_field(output, 'processor_class')
+ self._output_field(output, 'sdk_version')
if self.rtti:
self._output_field(output, 'rtti')
@@ -618,6 +631,53 @@
blueprint.add_module(module)
+def create_gcc_preprocess_modules(blueprint, target):
+ # gcc_preprocess.py internally execute host gcc which is not allowed in genrule.
+ # So, this function create multiple modules and realize equivalent processing
+ # TODO: Consider to support gcc_preprocess.py in different way
+ # It's not great to have genrule and cc_object in the dependency from java_library
+ assert (len(target.sources) == 1)
+ source = list(target.sources)[0]
+ assert (Path(source).suffix == '.template')
+ stem = Path(source).stem
+
+ bp_module_name = label_to_module_name(target.name)
+
+ # Rename .template to .cc since cc_object does not accept .template file as srcs
+ rename_module = Module('genrule', bp_module_name + '_rename', target.name)
+ rename_module.srcs.add(gn_utils.label_to_path(source))
+ rename_module.out.add(stem + '.cc')
+ rename_module.cmd = 'cp $(in) $(out)'
+ blueprint.add_module(rename_module)
+
+ # Preprocess template file and generates java file
+ preprocess_module = Module('cc_object', bp_module_name + '_preprocess', target.name)
+ # -E: stop after preprocessing.
+ # -P: disable line markers, i.e. '#line 309'
+ preprocess_module.cflags.update(['-E', '-P', '-DANDROID'])
+ preprocess_module.srcs.add(':' + rename_module.name)
+ defines = ['-D' + target.args[i+1] for i, arg in enumerate(target.args) if arg == '--define']
+ preprocess_module.cflags.update(defines)
+ # HACK: Specifying compile_multilib to build cc_object only once.
+ # Without this, soong complain to genrule that depends on cc_object when built for 64bit target.
+ # It seems this is because cc object is a module with per-architecture variants and genrule is a
+ # module with default variant. For 64bit target, cc_object is built multiple times for 32/64bit
+ # modes and genrule doesn't know which one to depend on.
+ preprocess_module.compile_multilib = 'first'
+ blueprint.add_module(preprocess_module)
+
+ # Generates srcjar using soong_zip
+ module = Module('genrule', bp_module_name, target.name)
+ module.srcs.add(':' + preprocess_module.name)
+ module.out.add(stem + '.srcjar')
+ module.cmd = NEWLINE.join([
+ f'cp $(in) $(genDir)/{stem}.java &&',
+ f'$(location soong_zip) -o $(out) -srcjar -f $(genDir)/{stem}.java'
+ ])
+ module.tools.add('soong_zip')
+ blueprint.add_module(module)
+ return module
+
class BaseActionSanitizer():
def __init__(self, target):
@@ -746,6 +806,23 @@
def get_outputs(self):
return self.target.outputs
+ def get_srcs(self):
+ # gn treats inputs and sources for actions equally.
+ # soong only supports source files inside srcs, non-source files are added as
+ # tool_files dependency.
+ files = self.target.sources.union(self.target.inputs)
+ return {gn_utils.label_to_path(file) for file in files if is_supported_source_file(file)}
+
+ def get_tool_files(self):
+ # gn treats inputs and sources for actions equally.
+ # soong only supports source files inside srcs, non-source files are added as
+ # tool_files dependency.
+ files = self.target.sources.union(self.target.inputs)
+ tool_files = {gn_utils.label_to_path(file)
+ for file in files if not is_supported_source_file(file)}
+ tool_files.add(gn_utils.label_to_path(self.target.script))
+ return tool_files
+
def _sanitize_args(self):
# Handle passing parameters via response file by piping them into the script
# and reading them from /dev/stdin.
@@ -759,9 +836,13 @@
def _sanitize_outputs(self):
pass
+ def _sanitize_inputs(self):
+ pass
+
def sanitize(self):
self._sanitize_args()
self._sanitize_outputs()
+ self._sanitize_inputs()
# Whether this target generates header files
def is_header_generated(self):
@@ -803,7 +884,17 @@
self.target.outputs = {re.sub('^jni_headers/', '', out) for out in self.target.outputs}
super()._sanitize_outputs()
+ def get_tool_files(self):
+ tool_files = super().get_tool_files()
+ # android_jar.classes should be part of the tools as it list implicit classes
+ # for the script to generate JNI headers.
+ tool_files.add("base/android/jni_generator/android_jar.classes")
+ return tool_files
+
class JniRegistrationGeneratorSanitizer(BaseActionSanitizer):
+ def _sanitize_inputs(self):
+ self.target.inputs = [file for file in self.target.inputs if not file.startswith('//out/')]
+
def _sanitize_args(self):
self._update_value_arg('--depfile', self._sanitize_filepath)
self._update_value_arg('--srcjar-path', self._sanitize_filepath)
@@ -852,6 +943,12 @@
self._set_value_arg('-e', "'%s'" % self._get_value_arg('-e'))
super()._sanitize_args()
+ def get_tool_files(self):
+ tool_files = super().get_tool_files()
+ # android_chrome_version.py is not specified in anywhere but version.py imports this file
+ tool_files.add('build/util/android_chrome_version.py')
+ return tool_files
+
class JavaCppEnumSanitizer(BaseActionSanitizer):
def _sanitize_args(self):
self._update_all_args(self._sanitize_filepath_with_location_tag)
@@ -864,6 +961,23 @@
# (e.g. registry_controlled_domain.cc)
return True
+class JavaCppFeatureSanitizer(BaseActionSanitizer):
+ def _sanitize_args(self):
+ self._update_all_args(self._sanitize_filepath_with_location_tag)
+ self._set_value_arg('--srcjar', '$(out)')
+ super()._sanitize_args()
+
+class JavaCppStringSanitizer(BaseActionSanitizer):
+ def _sanitize_args(self):
+ self._update_all_args(self._sanitize_filepath_with_location_tag)
+ self._set_value_arg('--srcjar', '$(out)')
+ super()._sanitize_args()
+
+class WriteNativeLibrariesJavaSanitizer(BaseActionSanitizer):
+ def _sanitize_args(self):
+ self._set_value_arg('--output', '$(out)')
+ super()._sanitize_args()
+
def get_action_sanitizer(target, type):
if target.script == "//build/write_buildflag_header.py":
return WriteBuildFlagHeaderSanitizer(target)
@@ -882,6 +996,12 @@
return JavaCppEnumSanitizer(target)
elif target.script == "//net/tools/dafsa/make_dafsa.py":
return MakeDafsaSanitizer(target)
+ elif target.script == '//build/android/gyp/java_cpp_features.py':
+ return JavaCppFeatureSanitizer(target)
+ elif target.script == '//build/android/gyp/java_cpp_strings.py':
+ return JavaCppStringSanitizer(target)
+ elif target.script == '//build/android/gyp/write_native_libraries_java.py':
+ return WriteNativeLibrariesJavaSanitizer(target)
else:
# TODO: throw exception here once all script hacks have been converted.
return BaseActionSanitizer(target)
@@ -922,39 +1042,14 @@
def create_action_module(blueprint, target, type):
sanitizer = get_action_sanitizer(target, type)
sanitizer.sanitize()
+
module = Module(type, sanitizer.get_name(), target.name)
module.cmd = sanitizer.get_cmd()
module.out = sanitizer.get_outputs()
if sanitizer.is_header_generated():
module.genrule_headers.add(module.name)
-
- if target.script == '//base/android/jni_generator/jni_generator.py':
- # android_jar.classes should be part of the tools as it list implicit classes
- # for the script to generate JNI headers.
- module.tool_files.add("base/android/jni_generator/android_jar.classes")
-
- elif target.script == '//base/android/jni_generator/jni_registration_generator.py':
- # jni_registration_generator.py pulls in some config dependencies that we
- # do not handle. Remove them.
- # TODO: find a better way to do this.
- target.deps.clear()
-
- target.inputs = [file for file in target.inputs if not file.startswith('//out/')]
- elif target.script == "//build/util/version.py":
- # android_chrome_version.py is not specified in anywhere but version.py imports this file
- module.tool_files.add('build/util/android_chrome_version.py')
-
- script = gn_utils.label_to_path(target.script)
- module.tool_files.add(script)
-
- # gn treats inputs and sources for actions equally.
- # soong only supports source files inside srcs, non-source files are added as
- # tool_files dependency.
- for it in target.sources or target.inputs:
- if is_supported_source_file(it):
- module.srcs.add(gn_utils.label_to_path(it))
- else:
- module.tool_files.add(gn_utils.label_to_path(it))
+ module.srcs = sanitizer.get_srcs()
+ module.tool_files = sanitizer.get_tool_files()
blueprint.add_module(module)
return module
@@ -976,15 +1071,6 @@
if '-fexceptions' in flag:
module.cppflags.add('-fexceptions')
-def add_genrule_per_arch(module, dep_module, type):
- module.generated_headers.update(dep_module.genrule_headers)
- # If the module is a static library, export all the generated headers.
- if type == 'cc_library_static':
- module.export_generated_headers.update(dep_module.genrule_headers)
- module.srcs.update(dep_module.genrule_srcs)
- module.shared_libs.update(dep_module.genrule_shared_libs)
- module.header_libs.update(dep_module.genrule_header_libs)
-
def set_module_include_dirs(module, cflags, include_dirs):
for flag in cflags:
if '-isystem' in flag:
@@ -1213,12 +1299,62 @@
(dep_module.name, target.name, dep_module.type))
return module
+def create_java_jni_preprocessor(blueprint):
+ bp_module_name = module_prefix + 'java_jni_annotation_preprocessor'
+ module = Module('java_plugin', bp_module_name, '//base/android/jni_generator:jni_processor')
+ module.srcs.update(
+ [
+ "base/android/jni_generator/java/src/org/chromium/jni_generator/JniProcessor.java",
+ # Avoids a circular dependency with base:base_java. This is okay because
+ # no target should ever expect to package an annotation processor.
+ "build/android/java/src/org/chromium/build/annotations/CheckDiscard.java",
+ "build/android/java/src/org/chromium/build/annotations/MainDex.java",
+ "base/android/java/src/org/chromium/base/JniStaticTestMocker.java",
+ "base/android/java/src/org/chromium/base/NativeLibraryLoadedStatus.java",
+ "base/android/java/src/org/chromium/base/annotations/NativeMethods.java",
+ "base/android/java/src/org/chromium/base/JniException.java",
+ ":cronet_aml_build_android_build_config_gen",
+ ])
+ module.static_libs.update({
+ "javapoet",
+ "guava",
+ "auto_service_annotations",
+ })
+ module.processor_class = "org.chromium.jni_generator.JniProcessor"
+ blueprint.add_module(module)
+ return module
+
def create_java_module(blueprint, gn):
bp_module_name = module_prefix + 'java'
module = Module('java_library', bp_module_name, '//gn:java')
module.srcs.update([gn_utils.label_to_path(source) for source in gn.java_sources])
+ module.libs = {
+ "androidx.annotation_annotation",
+ "jsr305",
+ "androidx.core_core-nodeps",
+ "androidx.collection_collection",
+ "androidx.annotation_annotation-experimental-nodeps",
+ "android-support-multidex",
+ "framework-connectivity.stubs.module_lib",
+ "framework-connectivity-t.stubs.module_lib",
+ "framework-tethering.stubs.module_lib",
+ "framework-wifi.stubs.module_lib",
+ "framework-mediaprovider.stubs.module_lib",
+ }
+ module.aidl["include_dirs"] = {"frameworks/base/core/java/"}
+ module.aidl["local_include_dirs"] = {"base/android/java/src/"}
+ module.sdk_version = "module_current"
+ module.apex_available.add(tethering_apex)
+ # TODO: remove following workaround required to make this module visible to make (b/203203405)
+ module.apex_available.add("//apex_available:platform")
for dep in gn.java_actions:
- dep_module = create_action_module(blueprint, gn.get_target(dep), 'java_genrule')
+ target = gn.get_target(dep)
+ if target.script == '//build/android/gyp/gcc_preprocess.py':
+ module.srcs.add(':' + create_gcc_preprocess_modules(blueprint, target).name)
+ else:
+ module.srcs.add(':' + create_action_module(blueprint, target, 'java_genrule').name)
+ preprocessor_module = create_java_jni_preprocessor(blueprint)
+ module.plugins.add(preprocessor_module.name)
blueprint.add_module(module)
def update_jni_registration_module(module, gn):
@@ -1231,7 +1367,8 @@
# TODO: java_sources might not contain all the required java files
module.srcs.update([gn_utils.label_to_path(source)
- for source in gn.java_sources if source not in deny_list])
+ for source in gn.java_sources
+ if source.endswith('.java') and source not in deny_list])
def create_blueprint_for_targets(gn, targets):
"""Generate a blueprint for a list of GN targets."""
diff --git a/tools/gn2bp/gn_utils.py b/tools/gn2bp/gn_utils.py
index 0511e8a..6cbbd67 100644
--- a/tools/gn2bp/gn_utils.py
+++ b/tools/gn2bp/gn_utils.py
@@ -50,8 +50,6 @@
'//:perfetto_integrationtests',
}
ARCH_REGEX = r'(android_x86_64|android_x86|android_arm|android_arm64|host)'
-DEX_REGEX = '.*__dex__%s$' % ARCH_REGEX
-COMPILE_JAVA_REGEX = '.*__compile_java__%s$' % ARCH_REGEX
RESPONSE_FILE = '{{response_file_name}}'
def repo_root():
@@ -260,11 +258,11 @@
return ' '.join(formatted_flags)
- def _is_java_target(self, target):
+ def _is_java_group(self, type_, target_name):
# Per https://chromium.googlesource.com/chromium/src/build/+/HEAD/android/docs/java_toolchain.md
# java target names must end in "_java".
# TODO: There are some other possible variations we might need to support.
- return target.type == 'group' and re.match('.*_java$', target.name)
+ return type_ == 'group' and re.match('.*_java$', target_name)
def _get_arch(self, toolchain):
if toolchain == '//build/toolchain/android:android_clang_x86':
@@ -289,7 +287,7 @@
return self.all_targets[label_without_toolchain(gn_target_name)]
- def parse_gn_desc(self, gn_desc, gn_target_name):
+ def parse_gn_desc(self, gn_desc, gn_target_name, is_java_target = False):
"""Parses a gn desc tree and resolves all target dependencies.
It bubbles up variables from source_set dependencies as described in the
@@ -302,12 +300,13 @@
type_ = desc['type']
arch = self._get_arch(desc['toolchain'])
+ is_java_target = is_java_target or self._is_java_group(type_, target_name)
+
# Action modules can differ depending on the target architecture, yet
# genrule's do not allow to overload cmd per target OS / arch. Create a
# separate action for every architecture.
# Cover both action and action_foreach
- if type_.startswith('action') and \
- not is_java_action(desc.get("script", ""), desc.get("outputs", [])):
+ if type_.startswith('action') and not is_java_target:
# Don't meddle with the java actions name
target_name += '__' + arch
@@ -342,7 +341,8 @@
elif target.type in LINKER_UNIT_TYPES:
self.linker_units[gn_target_name] = target
target.arch[arch].sources.update(desc.get('sources', []))
- elif desc.get("script", "") in JAVA_BANNED_SCRIPTS or self._is_java_target(target):
+ elif (desc.get("script", "") in JAVA_BANNED_SCRIPTS
+ or self._is_java_group(target.type, target.name)):
# java_group identifies the group target generated by the android_library
# or java_library template. A java_group must not be added as a dependency, but sources are collected
log.debug('Found java target %s', target.name)
@@ -382,7 +382,7 @@
# Recurse in dependencies.
for gn_dep_name in desc.get('deps', []):
- dep = self.parse_gn_desc(gn_desc, gn_dep_name)
+ dep = self.parse_gn_desc(gn_desc, gn_dep_name, is_java_target)
if dep.type == 'proto_library':
target.proto_deps.add(dep.name)
target.transitive_proto_deps.add(dep.name)
@@ -423,13 +423,19 @@
# dependency of the __dex target)
# Note: this skips prebuilt java dependencies. These will have to be
# added manually when building the jar.
- if re.match(DEX_REGEX, target.name):
- if re.match(COMPILE_JAVA_REGEX, dep.name):
+ if target.name.endswith('__dex'):
+ if dep.name.endswith('__compile_java'):
log.debug('Adding java sources for %s', dep.name)
java_srcs = [src for src in dep.inputs if _is_java_source(src)]
self.java_sources.update(java_srcs)
if dep.type in ["action"] and target.type == "java_group":
- self.java_actions.add(dep.name)
+ # //base:base_java_aidl generates srcjar from .aidl files. But java_library in soong can
+ # directly have .aidl files in srcs. So adding .aidl files to the java_sources.
+ # TODO: Find a better way/place to do this.
+ if dep.name == '//base:base_java_aidl':
+ self.java_sources.update(dep.arch[arch].sources)
+ else:
+ self.java_actions.add(dep.name)
return target
def get_proto_exports(self, proto_desc):