Support set test network to tethering upstream

Test: atest EthernetTetheringTest

Change-Id: Ief2a92bc6c573a6bb75c72b7a0630e5c0accfa73
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index f652772..6e64570 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -22,7 +22,19 @@
     defaults: ["framework-module-defaults"],
     impl_library_visibility: [
         "//packages/modules/Connectivity/Tethering:__subpackages__",
+
+        // Using for test only
+        "//cts/tests/netlegacy22.api",
+        "//external/sl4a:__subpackages__",
+        "//frameworks/base/packages/Connectivity/tests:__subpackages__",
+        "//frameworks/libs/net/common/testutils",
+        "//frameworks/libs/net/common/tests:__subpackages__",
+        "//frameworks/opt/telephony/tests/telephonytests",
+        "//packages/modules/CaptivePortalLogin/tests",
+        "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
         "//packages/modules/Connectivity/tests:__subpackages__",
+        "//packages/modules/NetworkStack/tests:__subpackages__",
+        "//packages/modules/Wifi/service/tests/wifitests",
     ],
 
     srcs: [":framework-tethering-srcs"],
diff --git a/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl b/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
index cf094aa..77e78bd 100644
--- a/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
@@ -49,4 +49,6 @@
 
     void stopAllTethering(String callerPkg, String callingAttributionTag,
             IIntResultListener receiver);
+
+    void setPreferTestNetworks(boolean prefer, IIntResultListener listener);
 }
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 73ab908..65b2650 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -1538,4 +1538,25 @@
                     }
                 }));
     }
+
+    /**
+     * Whether to treat networks that have TRANSPORT_TEST as Tethering upstreams. The effects of
+     * this method apply to any test networks that are already present on the system.
+     *
+     * @throws SecurityException If the caller doesn't have the NETWORK_SETTINGS permission.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+    public void setPreferTestNetworks(final boolean prefer) {
+        Log.i(TAG, "setPreferTestNetworks caller: " + mContext.getOpPackageName());
+
+        final RequestDispatcher dispatcher = new RequestDispatcher();
+        final int ret = dispatcher.waitForResult((connector, listener) -> {
+            try {
+                connector.setPreferTestNetworks(prefer, listener);
+            } catch (RemoteException e) {
+                throw new IllegalStateException(e);
+            }
+        });
+    }
 }
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 08170f9..1559f3b 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -2601,4 +2601,13 @@
     private static String[] copy(String[] strarray) {
         return Arrays.copyOf(strarray, strarray.length);
     }
+
+    void setPreferTestNetworks(final boolean prefer, IIntResultListener listener) {
+        mHandler.post(() -> {
+            mUpstreamNetworkMonitor.setPreferTestNetworks(prefer);
+            try {
+                listener.onResult(TETHER_ERROR_NO_ERROR);
+            } catch (RemoteException e) { }
+        });
+    }
 }
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringInterfaceUtils.java b/Tethering/src/com/android/networkstack/tethering/TetheringInterfaceUtils.java
index ff38f71..d0a1ac3 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringInterfaceUtils.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringInterfaceUtils.java
@@ -16,6 +16,10 @@
 
 package com.android.networkstack.tethering;
 
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_TEST;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.LinkProperties;
 import android.net.NetworkCapabilities;
@@ -78,13 +82,17 @@
                 // Minimal amount of IPv6 provisioning:
                 && ns.linkProperties.hasGlobalIpv6Address()
                 // Temporary approximation of "dedicated prefix":
-                && ns.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
+                && allowIpv6Tethering(ns.networkCapabilities);
 
         return canTether
                 ? getInterfaceForDestination(ns.linkProperties, IN6ADDR_ANY)
                 : null;
     }
 
+    private static boolean allowIpv6Tethering(@NonNull final NetworkCapabilities nc) {
+        return nc.hasTransport(TRANSPORT_CELLULAR) || nc.hasTransport(TRANSPORT_TEST);
+    }
+
     private static String getInterfaceForDestination(LinkProperties lp, InetAddress dst) {
         final RouteInfo ri = (lp != null)
                 ? NetUtils.selectBestRoute(lp.getAllRoutes(), dst)
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index 5ab3401..d3d2962 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -17,6 +17,7 @@
 package com.android.networkstack.tethering;
 
 import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.Manifest.permission.TETHER_PRIVILEGED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -199,6 +200,17 @@
             } catch (RemoteException e) { }
         }
 
+        public void setPreferTestNetworks(boolean prefer, IIntResultListener listener) {
+            if (!checkCallingOrSelfPermission(NETWORK_SETTINGS)) {
+                try {
+                    listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
+                } catch (RemoteException e) { }
+                return;
+            }
+
+            mTethering.setPreferTestNetworks(prefer, listener);
+        }
+
         @Override
         protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
                     @Nullable String[] args) {
diff --git a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
index 5584db2..5a86b84 100644
--- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
+++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java
@@ -135,6 +135,7 @@
     private Network mDefaultInternetNetwork;
     // The current upstream network used for tethering.
     private Network mTetheringUpstreamNetwork;
+    private boolean mPreferTestNetworks;
 
     public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what) {
         mContext = ctx;
@@ -325,6 +326,11 @@
         final UpstreamNetworkState dfltState = (mDefaultInternetNetwork != null)
                 ? mNetworkMap.get(mDefaultInternetNetwork)
                 : null;
+        if (mPreferTestNetworks) {
+            final UpstreamNetworkState testState = findFirstTestNetwork(mNetworkMap.values());
+            if (testState != null) return testState;
+        }
+
         if (isNetworkUsableAndNotCellular(dfltState)) return dfltState;
 
         if (!isCellularUpstreamPermitted()) return null;
@@ -656,6 +662,20 @@
         return null;
     }
 
+    static boolean isTestNetwork(UpstreamNetworkState ns) {
+        return ((ns != null) && (ns.networkCapabilities != null)
+                && ns.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_TEST));
+    }
+
+    private UpstreamNetworkState findFirstTestNetwork(
+            Iterable<UpstreamNetworkState> netStates) {
+        for (UpstreamNetworkState ns : netStates) {
+            if (isTestNetwork(ns)) return ns;
+        }
+
+        return null;
+    }
+
     /**
      * Given a legacy type (TYPE_WIFI, ...) returns the corresponding NetworkCapabilities instance.
      * This function is used for deprecated legacy type and be disabled by default.
@@ -681,4 +701,9 @@
         }
         return builder.build();
     }
+
+    /** Set test network as preferred upstream. */
+    public void setPreferTestNetworks(boolean prefer) {
+        mPreferTestNetworks = prefer;
+    }
 }
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index 2593b1b..c7f5527 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -19,6 +19,7 @@
 
 java_defaults {
     name: "TetheringIntegrationTestsDefaults",
+    defaults: ["framework-connectivity-test-defaults"],
     srcs: [
         "src/**/*.java",
         "src/**/*.kt",
diff --git a/Tethering/tests/integration/AndroidManifest.xml b/Tethering/tests/integration/AndroidManifest.xml
index fddfaad..c89c556 100644
--- a/Tethering/tests/integration/AndroidManifest.xml
+++ b/Tethering/tests/integration/AndroidManifest.xml
@@ -17,6 +17,11 @@
           package="com.android.networkstack.tethering.tests.integration">
 
     <uses-permission android:name="android.permission.INTERNET"/>
+    <!-- The test need CHANGE_NETWORK_STATE permission to use requestNetwork API to setup test
+         network. Since R shell application don't have such permission, grant permission to the test
+         here. TODO: Remove CHANGE_NETWORK_STATE permission here and use adopt shell perssion to
+         obtain CHANGE_NETWORK_STATE for testing once R device is no longer supported. -->
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index 26297a2..3d52d45 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -21,6 +21,7 @@
 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.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;
@@ -29,6 +30,7 @@
 import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA;
 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.testutils.TestNetworkTrackerKt.initTestNetwork;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -65,6 +67,7 @@
 import com.android.net.module.util.structs.Ipv6Header;
 import com.android.testutils.HandlerUtils;
 import com.android.testutils.TapPacketReader;
+import com.android.testutils.TestNetworkTracker;
 
 import org.junit.After;
 import org.junit.Before;
@@ -78,6 +81,7 @@
 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.Random;
@@ -102,12 +106,16 @@
             DhcpPacket.DHCP_LEASE_TIME,
     };
     private static final String DHCP_HOSTNAME = "testhostname";
+    private static final LinkAddress TEST_IP4_ADDR = new LinkAddress("10.0.0.1/8");
+    private static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64");
+    private static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8");
+    private static final InetAddress TEST_IP6_DNS = parseNumericAddress("2001:db8:1::888");
 
     private final Context mContext = InstrumentationRegistry.getContext();
     private final EthernetManager mEm = mContext.getSystemService(EthernetManager.class);
     private final TetheringManager mTm = mContext.getSystemService(TetheringManager.class);
 
-    private TestNetworkInterface mTestIface;
+    private TestNetworkInterface mDownstreamIface;
     private HandlerThread mHandlerThread;
     private Handler mHandler;
     private TapPacketReader mTapPacketReader;
@@ -119,6 +127,8 @@
             InstrumentationRegistry.getInstrumentation().getUiAutomation();
     private boolean mRunTests;
 
+    private TestNetworkTracker mUpstreamTracker;
+
     @Before
     public void setUp() throws Exception {
         // Needed to create a TestNetworkInterface, to call requestTetheredInterface, and to receive
@@ -138,6 +148,13 @@
     }
 
     private void cleanUp() throws Exception {
+        mTm.setPreferTestNetworks(false);
+
+        if (mUpstreamTracker != null) {
+            mUpstreamTracker.teardown();
+            mUpstreamTracker = null;
+        }
+
         mTm.stopTethering(TETHERING_ETHERNET);
         if (mTetheringEventCallback != null) {
             mTetheringEventCallback.awaitInterfaceUntethered();
@@ -169,21 +186,21 @@
         // This test requires manipulating packets. Skip if there is a physical Ethernet connected.
         assumeFalse(mEm.isAvailable());
 
-        mTestIface = createTestInterface();
+        mDownstreamIface = createTestInterface();
         // This must be done now because as soon as setIncludeTestInterfaces(true) is called, the
         // interface will be placed in client mode, which will delete the link-local address.
         // At that point NetworkInterface.getByName() will cease to work on the interface, because
         // starting in R NetworkInterface can no longer see interfaces without IP addresses.
-        int mtu = getMTU(mTestIface);
+        int mtu = getMTU(mDownstreamIface);
 
         Log.d(TAG, "Including test interfaces");
         mEm.setIncludeTestInterfaces(true);
 
         final String iface = mTetheredInterfaceRequester.getInterface();
         assertEquals("TetheredInterfaceCallback for unexpected interface",
-                mTestIface.getInterfaceName(), iface);
+                mDownstreamIface.getInterfaceName(), iface);
 
-        checkVirtualEthernet(mTestIface, mtu);
+        checkVirtualEthernet(mDownstreamIface, mtu);
     }
 
     @Test
@@ -195,13 +212,13 @@
 
         mEm.setIncludeTestInterfaces(true);
 
-        mTestIface = createTestInterface();
+        mDownstreamIface = createTestInterface();
 
         final String iface = futureIface.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
         assertEquals("TetheredInterfaceCallback for unexpected interface",
-                mTestIface.getInterfaceName(), iface);
+                mDownstreamIface.getInterfaceName(), iface);
 
-        checkVirtualEthernet(mTestIface, getMTU(mTestIface));
+        checkVirtualEthernet(mDownstreamIface, getMTU(mDownstreamIface));
     }
 
     @Test
@@ -210,11 +227,11 @@
 
         mEm.setIncludeTestInterfaces(true);
 
-        mTestIface = createTestInterface();
+        mDownstreamIface = createTestInterface();
 
         final String iface = mTetheredInterfaceRequester.getInterface();
         assertEquals("TetheredInterfaceCallback for unexpected interface",
-                mTestIface.getInterfaceName(), iface);
+                mDownstreamIface.getInterfaceName(), iface);
 
         assertInvalidStaticIpv4Request(iface, null, null);
         assertInvalidStaticIpv4Request(iface, "2001:db8::1/64", "2001:db8:2::/64");
@@ -235,8 +252,8 @@
         byte[] client1 = MacAddress.fromString("1:2:3:4:5:6").toByteArray();
         byte[] client2 = MacAddress.fromString("a:b:c:d:e:f").toByteArray();
 
-        FileDescriptor fd = mTestIface.getFileDescriptor().getFileDescriptor();
-        mTapPacketReader = makePacketReader(fd, getMTU(mTestIface));
+        FileDescriptor fd = mDownstreamIface.getFileDescriptor().getFileDescriptor();
+        mTapPacketReader = makePacketReader(fd, getMTU(mDownstreamIface));
         DhcpResults dhcpResults = runDhcp(fd, client1);
         assertEquals(new LinkAddress(clientAddr), dhcpResults.ipAddress);
 
@@ -301,11 +318,11 @@
 
         mEm.setIncludeTestInterfaces(true);
 
-        mTestIface = createTestInterface();
+        mDownstreamIface = createTestInterface();
 
         final String iface = mTetheredInterfaceRequester.getInterface();
         assertEquals("TetheredInterfaceCallback for unexpected interface",
-                mTestIface.getInterfaceName(), iface);
+                mDownstreamIface.getInterfaceName(), iface);
 
         final TetheringRequest request = new TetheringRequest.Builder(TETHERING_ETHERNET)
                 .setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build();
@@ -316,8 +333,7 @@
         // does not have an IP address, and unprivileged apps cannot see interfaces without IP
         // addresses. This shouldn't be flaky because the TAP interface will buffer all packets even
         // before the reader is started.
-        FileDescriptor fd = mTestIface.getFileDescriptor().getFileDescriptor();
-        mTapPacketReader = makePacketReader(fd, getMTU(mTestIface));
+        mTapPacketReader = makePacketReader(mDownstreamIface);
 
         expectRouterAdvertisement(mTapPacketReader, iface, 2000 /* timeoutMs */);
         expectLocalOnlyAddresses(iface);
@@ -354,12 +370,14 @@
         private final CountDownLatch mLocalOnlyStartedLatch = new CountDownLatch(1);
         private final CountDownLatch mLocalOnlyStoppedLatch = new CountDownLatch(1);
         private final CountDownLatch mClientConnectedLatch = new CountDownLatch(1);
+        private final CountDownLatch mUpstreamConnectedLatch = new CountDownLatch(1);
         private final TetheringInterface mIface;
 
         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) {
             mTm = tm;
@@ -465,6 +483,22 @@
                     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 (mUpstream != null) mUpstreamConnectedLatch.countDown();
+        }
+
+        public Network awaitFirstUpstreamConnected() throws Exception {
+            assertTrue("Did not receive upstream connected callback after " + TIMEOUT_MS + "ms",
+                    mUpstreamConnectedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            return mUpstream;
+        }
     }
 
     private MyTetheringEventCallback enableEthernetTethering(String iface,
@@ -508,6 +542,11 @@
         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());
@@ -701,10 +740,45 @@
     }
 
     private void maybeDeleteTestInterface() throws Exception {
-        if (mTestIface != null) {
-            mTestIface.getFileDescriptor().close();
-            Log.d(TAG, "Deleted test interface " + mTestIface.getInterfaceName());
-            mTestIface = null;
+        if (mDownstreamIface != null) {
+            mDownstreamIface.getFileDescriptor().close();
+            Log.d(TAG, "Deleted test interface " + mDownstreamIface.getInterfaceName());
+            mDownstreamIface = null;
         }
     }
+
+    private TestNetworkTracker createTestUpstream(final List<LinkAddress> addresses)
+            throws Exception {
+        mTm.setPreferTestNetworks(true);
+
+        return initTestNetwork(mContext, addresses, TIMEOUT_MS);
+    }
+
+    @Test
+    public void testTestNetworkUpstream() throws Exception {
+        assumeFalse(mEm.isAvailable());
+
+        // 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(toList(TEST_IP4_ADDR, TEST_IP6_ADDR));
+
+        mDownstreamIface = createTestInterface();
+        mEm.setIncludeTestInterfaces(true);
+
+        final String iface = mTetheredInterfaceRequester.getInterface();
+        assertEquals("TetheredInterfaceCallback for unexpected interface",
+                mDownstreamIface.getInterfaceName(), iface);
+
+        mTetheringEventCallback = enableEthernetTethering(mDownstreamIface.getInterfaceName());
+        assertEquals("onUpstreamChanged for unexpected network", mUpstreamTracker.getNetwork(),
+                mTetheringEventCallback.awaitFirstUpstreamConnected());
+
+        mTapPacketReader = makePacketReader(mDownstreamIface);
+        // TODO: do basic forwarding test here.
+    }
+
+    private <T> List<T> toList(T... array) {
+        return Arrays.asList(array);
+    }
 }
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index e1fab09..9b136d5 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -115,6 +115,7 @@
         // meaning @hide APIs in framework-connectivity are resolved before @SystemApi
         // stubs in framework
         "framework-connectivity.impl",
+        "framework-tethering.impl",
         "framework",
 
         // if sdk_version="" this gets automatically included, but here we need to add manually.