diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp index f65277269f..6e645704ca 100644 --- a/Tethering/common/TetheringLib/Android.bp +++ b/Tethering/common/TetheringLib/Android.bp @@ -22,7 +22,19 @@ java_sdk_library { 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 cf094aac2c..77e78bd2ac 100644 --- a/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl +++ b/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl @@ -49,4 +49,6 @@ oneway interface ITetheringConnector { 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 edd141d383..69bb71f894 100644 --- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -1538,4 +1538,25 @@ public class TetheringManager { } })); } + + /** + * 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 08170f9a69..1559f3b478 100644 --- a/Tethering/src/com/android/networkstack/tethering/Tethering.java +++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java @@ -2601,4 +2601,13 @@ public class Tethering { 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 ff38f717a1..d0a1ac37f2 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 @@ public final class TetheringInterfaceUtils { // 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 722ec8f90d..175b480297 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; @@ -203,6 +204,17 @@ public class TetheringService extends Service { } 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 5584db236f..5a86b84364 100644 --- a/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java +++ b/Tethering/src/com/android/networkstack/tethering/UpstreamNetworkMonitor.java @@ -135,6 +135,7 @@ public class UpstreamNetworkMonitor { 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 @@ public class UpstreamNetworkMonitor { 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 @@ public class UpstreamNetworkMonitor { return null; } + static boolean isTestNetwork(UpstreamNetworkState ns) { + return ((ns != null) && (ns.networkCapabilities != null) + && ns.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_TEST)); + } + + private UpstreamNetworkState findFirstTestNetwork( + Iterable 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 @@ public class UpstreamNetworkMonitor { } 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 2593b1b5aa..c7f5527b13 100644 --- a/Tethering/tests/integration/Android.bp +++ b/Tethering/tests/integration/Android.bp @@ -19,6 +19,7 @@ package { 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 fddfaad29f..c89c55629a 100644 --- a/Tethering/tests/integration/AndroidManifest.xml +++ b/Tethering/tests/integration/AndroidManifest.xml @@ -17,6 +17,11 @@ package="com.android.networkstack.tethering.tests.integration"> + + diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java index 26297a22f8..3d52d453b4 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.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.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 android.system.OsConstants.IPPROTO_ICMPV6; 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.Icmpv6Header; 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.InterfaceAddress; 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 @@ public class EthernetTetheringTest { 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 @@ public class EthernetTetheringTest { 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 @@ public class EthernetTetheringTest { } 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 @@ public class EthernetTetheringTest { // 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 @@ public class EthernetTetheringTest { 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 @@ public class EthernetTetheringTest { 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 @@ public class EthernetTetheringTest { 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 @@ public class EthernetTetheringTest { 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 @@ public class EthernetTetheringTest { // 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 @@ public class EthernetTetheringTest { 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 mClients = null; + private volatile Network mUpstream = null; MyTetheringEventCallback(TetheringManager tm, String iface) { mTm = tm; @@ -465,6 +483,22 @@ public class EthernetTetheringTest { 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 @@ public class EthernetTetheringTest { 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 @@ public class EthernetTetheringTest { } 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 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 List toList(T... array) { + return Arrays.asList(array); + } } diff --git a/tests/common/Android.bp b/tests/common/Android.bp index e1fab094f8..9b136d59c9 100644 --- a/tests/common/Android.bp +++ b/tests/common/Android.bp @@ -115,6 +115,7 @@ java_defaults { // 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.