Merge "Relocate NetworkManagementServiceTest to framework"
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/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/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index da69a8d..97a255f 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -16,27 +16,16 @@
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;
@@ -44,39 +33,27 @@
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;
@@ -88,10 +65,8 @@
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;
@@ -106,12 +81,8 @@
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;
@@ -122,18 +93,14 @@
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;
@@ -231,137 +198,6 @@
(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.
@@ -486,22 +322,6 @@
}
}
- 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();
@@ -589,260 +409,6 @@
// 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.
@@ -876,54 +442,6 @@
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);
@@ -962,27 +480,6 @@
+ 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),
@@ -1010,124 +507,6 @@
});
}
- // 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),
@@ -1140,37 +519,16 @@
// TODO: test BPF offload maps {rule, stats}.
}
- // TODO: remove ipv4 verification (is4To6 = false) once upstream connected notification race is
- // fixed. See #runUdp4Test.
+ // Test network topology:
//
- // 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;
- }
-
+ // 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));
@@ -1268,96 +626,6 @@
}
}
- // 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))
@@ -1760,146 +1028,6 @@
(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),
@@ -1945,8 +1073,4 @@
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/service/mdns/com/android/server/connectivity/mdns/MdnsServiceInfo.java b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceInfo.java
index f1b2def..9683bc9 100644
--- a/service/mdns/com/android/server/connectivity/mdns/MdnsServiceInfo.java
+++ b/service/mdns/com/android/server/connectivity/mdns/MdnsServiceInfo.java
@@ -270,7 +270,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);
}
@@ -342,7 +343,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 +354,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 +373,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/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/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
index d3934c2..ebdb73f 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceInfoTest.java
@@ -164,7 +164,8 @@
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=")));
beforeParcel.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
@@ -208,11 +209,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 +242,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 +271,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/tools/gn2bp/Android.bp.swp b/tools/gn2bp/Android.bp.swp
index e6f3978..f8f2b6a 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",
],
}
@@ -5698,7 +5685,10 @@
],
tool_files: [
"build/android/gyp/gcc_preprocess.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
"build/android/java/templates/BuildConfig.template",
+ "build/gn_helpers.py",
],
}
@@ -5706,7 +5696,7 @@
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: [
@@ -7829,6 +7819,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",
],
}
@@ -7841,7 +7835,7 @@
"-f " +
"$(location build/util/LASTCHANGE) " +
"-e " +
- "API_LEVEL=20 " +
+ "'API_LEVEL=20' " +
"-o " +
"$(out) " +
"$(location components/cronet/android/java/src/org/chromium/net/impl/ImplVersion.template)",
@@ -7870,6 +7864,9 @@
],
tool_files: [
"build/android/gyp/gcc_preprocess.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/gn_helpers.py",
"components/cronet/android/java/src/org/chromium/net/impl/IntegratedModeState.template",
],
}
@@ -7882,7 +7879,7 @@
"-f " +
"$(location build/util/LASTCHANGE) " +
"-e " +
- "API_LEVEL=20 " +
+ "'API_LEVEL=20' " +
"-o " +
"$(out) " +
"$(location components/cronet/android/api/src/org/chromium/net/ApiVersion.template)",
@@ -7911,7 +7908,11 @@
],
tool_files: [
"build/android/gyp/gcc_preprocess.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/gn_helpers.py",
"components/cronet/android/java/src/org/chromium/net/impl/LoadState.template",
+ "net/base/load_states_list.h",
],
}
@@ -7926,6 +7927,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 +7946,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 +7965,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 +7984,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 +8003,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",
],
}
@@ -8261,7 +8282,7 @@
cmd: "$(location build/util/version.py) -f " +
"$(location chrome/VERSION) " +
"-e " +
- "VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH) " +
+ "'VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH)' " +
"-o " +
"$(out) " +
"$(location components/cronet/version.h.in)",
@@ -8286,7 +8307,7 @@
cmd: "$(location build/util/version.py) -f " +
"$(location chrome/VERSION) " +
"-e " +
- "VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH) " +
+ "'VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH)' " +
"-o " +
"$(out) " +
"$(location components/cronet/version.h.in)",
@@ -8311,7 +8332,7 @@
cmd: "$(location build/util/version.py) -f " +
"$(location chrome/VERSION) " +
"-e " +
- "VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH) " +
+ "'VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH)' " +
"-o " +
"$(out) " +
"$(location components/cronet/version.h.in)",
@@ -8336,7 +8357,7 @@
cmd: "$(location build/util/version.py) -f " +
"$(location chrome/VERSION) " +
"-e " +
- "VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH) " +
+ "'VERSION_FULL=\"%s.%s.%s.%s\" % (MAJOR,MINOR,BUILD,PATCH)' " +
"-o " +
"$(out) " +
"$(location components/cronet/version.h.in)",
@@ -9601,6 +9622,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",
@@ -9765,6 +9788,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",
@@ -9784,7 +9811,11 @@
],
tool_files: [
"build/android/gyp/gcc_preprocess.py",
+ "build/android/gyp/util/__init__.py",
+ "build/android/gyp/util/build_utils.py",
+ "build/gn_helpers.py",
"net/android/java/NetError.template",
+ "net/base/net_error_list.h",
],
}
@@ -11090,6 +11121,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 ef05126..233e6f8 100755
--- a/tools/gn2bp/gen_android_bp
+++ b/tools/gn2bp/gen_android_bp
@@ -746,6 +746,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 +776,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 +824,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)
@@ -849,8 +880,15 @@
# args for the version.py contain file path without leading --arg key. So apply sanitize
# function for all the args.
self._update_all_args(self._sanitize_filepath_with_location_tag)
+ 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)
@@ -863,6 +901,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)
@@ -881,6 +936,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)
@@ -921,39 +982,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
@@ -1230,7 +1266,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..130f8ff 100644
--- a/tools/gn2bp/gn_utils.py
+++ b/tools/gn2bp/gn_utils.py
@@ -429,7 +429,13 @@
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):