Allow callers of startTethering to choose local-only mode.
This is useful for OEMs that want to use RNDIS or NCM as a
local-only link that is directly connected to some other host.
This can be used to implement USB tethering using NCM, which
currently only supports local-only mode.
Bug: 175090447
Test: TetheringIntegrationTests:EthernetTetheringTest#testLocalOnlyTethering
Change-Id: I0ffaa46e4640e5b235340a15d25909106ceb0c07
diff --git a/Tethering/common/TetheringLib/api/system-current.txt b/Tethering/common/TetheringLib/api/system-current.txt
index edd1ebb..105bab1 100644
--- a/Tethering/common/TetheringLib/api/system-current.txt
+++ b/Tethering/common/TetheringLib/api/system-current.txt
@@ -27,6 +27,8 @@
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int);
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
+ field public static final int CONNECTIVITY_SCOPE_GLOBAL = 1; // 0x1
+ field public static final int CONNECTIVITY_SCOPE_LOCAL = 2; // 0x2
field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY";
field public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
field public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
@@ -72,6 +74,7 @@
public static interface TetheringManager.TetheringEventCallback {
method public default void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>);
method public default void onError(@NonNull String, int);
+ method public default void onLocalOnlyInterfacesChanged(@NonNull java.util.List<java.lang.String>);
method public default void onOffloadStatusChanged(int);
method public default void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>);
method public default void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>);
@@ -81,6 +84,7 @@
public static class TetheringManager.TetheringRequest {
method @Nullable public android.net.LinkAddress getClientStaticIpv4Address();
+ method public int getConnectivityScope();
method @Nullable public android.net.LinkAddress getLocalIpv4Address();
method public boolean getShouldShowEntitlementUi();
method public int getTetheringType();
@@ -90,6 +94,7 @@
public static class TetheringManager.TetheringRequest.Builder {
ctor public TetheringManager.TetheringRequest.Builder(int);
method @NonNull public android.net.TetheringManager.TetheringRequest build();
+ method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setConnectivityScope(int);
method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setShouldShowEntitlementUi(boolean);
method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress);
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 97fb497..c64da8a 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -557,6 +557,28 @@
}
/**
+ * Indicates that this tethering connection will provide connectivity beyond this device (e.g.,
+ * global Internet access).
+ */
+ public static final int CONNECTIVITY_SCOPE_GLOBAL = 1;
+
+ /**
+ * Indicates that this tethering connection will only provide local connectivity.
+ */
+ public static final int CONNECTIVITY_SCOPE_LOCAL = 2;
+
+ /**
+ * Connectivity scopes for {@link TetheringRequest.Builder#setConnectivityScope}.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "CONNECTIVITY_SCOPE_", value = {
+ CONNECTIVITY_SCOPE_GLOBAL,
+ CONNECTIVITY_SCOPE_LOCAL,
+ })
+ public @interface ConnectivityScope {}
+
+ /**
* Use with {@link #startTethering} to specify additional parameters when starting tethering.
*/
public static class TetheringRequest {
@@ -579,6 +601,7 @@
mBuilderParcel.staticClientAddress = null;
mBuilderParcel.exemptFromEntitlementCheck = false;
mBuilderParcel.showProvisioningUi = true;
+ mBuilderParcel.connectivityScope = getDefaultConnectivityScope(type);
}
/**
@@ -624,7 +647,21 @@
return this;
}
- /** Build {@link TetheringRequest] with the currently set configuration. */
+ /**
+ * Sets the connectivity scope to be provided by this tethering downstream.
+ */
+ @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
+ @NonNull
+ public Builder setConnectivityScope(@ConnectivityScope int scope) {
+ if (!checkConnectivityScope(mBuilderParcel.tetheringType, scope)) {
+ throw new IllegalArgumentException("Invalid connectivity scope " + scope);
+ }
+
+ mBuilderParcel.connectivityScope = scope;
+ return this;
+ }
+
+ /** Build {@link TetheringRequest} with the currently set configuration. */
@NonNull
public TetheringRequest build() {
return new TetheringRequest(mBuilderParcel);
@@ -655,6 +692,12 @@
return mRequestParcel.tetheringType;
}
+ /** Get connectivity type */
+ @ConnectivityScope
+ public int getConnectivityScope() {
+ return mRequestParcel.connectivityScope;
+ }
+
/** Check if exempt from entitlement check. */
public boolean isExemptFromEntitlementCheck() {
return mRequestParcel.exemptFromEntitlementCheck;
@@ -679,6 +722,26 @@
}
/**
+ * Returns the default connectivity scope for the given tethering type. Usually this is
+ * CONNECTIVITY_SCOPE_GLOBAL, except for NCM which for historical reasons defaults to local.
+ * @hide
+ */
+ public static @ConnectivityScope int getDefaultConnectivityScope(int tetheringType) {
+ return tetheringType != TETHERING_NCM
+ ? CONNECTIVITY_SCOPE_GLOBAL
+ : CONNECTIVITY_SCOPE_LOCAL;
+ }
+
+ /**
+ * Checks whether the requested connectivity scope is allowed.
+ * @hide
+ */
+ private static boolean checkConnectivityScope(int type, int scope) {
+ if (scope == CONNECTIVITY_SCOPE_GLOBAL) return true;
+ return type == TETHERING_USB || type == TETHERING_ETHERNET || type == TETHERING_NCM;
+ }
+
+ /**
* Get a TetheringRequestParcel from the configuration
* @hide
*/
@@ -940,6 +1003,15 @@
default void onTetheredInterfacesChanged(@NonNull List<String> interfaces) {}
/**
+ * Called when there was a change in the list of local-only interfaces.
+ *
+ * <p>This will be called immediately after the callback is registered, and may be called
+ * multiple times later upon changes.
+ * @param interfaces The list of 0 or more String of active local-only interface names.
+ */
+ default void onLocalOnlyInterfacesChanged(@NonNull List<String> interfaces) {}
+
+ /**
* Called when an error occurred configuring tethering.
*
* <p>This will be called immediately after the callback is registered if the latest status
@@ -1045,6 +1117,7 @@
private final HashMap<String, Integer> mErrorStates = new HashMap<>();
private String[] mLastTetherableInterfaces = null;
private String[] mLastTetheredInterfaces = null;
+ private String[] mLastLocalOnlyInterfaces = null;
@Override
public void onUpstreamChanged(Network network) throws RemoteException {
@@ -1082,6 +1155,14 @@
Collections.unmodifiableList(Arrays.asList(mLastTetheredInterfaces)));
}
+ private synchronized void maybeSendLocalOnlyIfacesChangedCallback(
+ final TetherStatesParcel newStates) {
+ if (Arrays.equals(mLastLocalOnlyInterfaces, newStates.localOnlyList)) return;
+ mLastLocalOnlyInterfaces = newStates.localOnlyList.clone();
+ callback.onLocalOnlyInterfacesChanged(
+ Collections.unmodifiableList(Arrays.asList(mLastLocalOnlyInterfaces)));
+ }
+
// Called immediately after the callbacks are registered.
@Override
public void onCallbackStarted(TetheringCallbackStartedParcel parcel) {
@@ -1092,6 +1173,7 @@
sendRegexpsChanged(parcel.config);
maybeSendTetherableIfacesChangedCallback(parcel.states);
maybeSendTetheredIfacesChangedCallback(parcel.states);
+ maybeSendLocalOnlyIfacesChangedCallback(parcel.states);
callback.onClientsChanged(parcel.tetheredClients);
callback.onOffloadStatusChanged(parcel.offloadStatus);
});
@@ -1122,6 +1204,7 @@
sendErrorCallbacks(states);
maybeSendTetherableIfacesChangedCallback(states);
maybeSendTetheredIfacesChangedCallback(states);
+ maybeSendLocalOnlyIfacesChangedCallback(states);
});
}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl b/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
index c0280d3..f13c970 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringRequestParcel.aidl
@@ -28,4 +28,5 @@
LinkAddress staticClientAddress;
boolean exemptFromEntitlementCheck;
boolean showProvisioningUi;
+ int connectivityScope;
}
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index da15fa8..c45ce83 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -1426,7 +1426,7 @@
break;
case CMD_INTERFACE_DOWN:
transitionTo(mUnavailableState);
- mLog.i("Untethered (interface down) and restarting" + mIfaceName);
+ mLog.i("Untethered (interface down) and restarting " + mIfaceName);
mCallback.requestEnableTethering(mInterfaceType, true /* enabled */);
break;
default:
diff --git a/Tethering/src/android/net/util/TetheringUtils.java b/Tethering/src/android/net/util/TetheringUtils.java
index 9e7cc2f..29900d9 100644
--- a/Tethering/src/android/net/util/TetheringUtils.java
+++ b/Tethering/src/android/net/util/TetheringUtils.java
@@ -162,7 +162,8 @@
&& Objects.equals(request.localIPv4Address, otherRequest.localIPv4Address)
&& Objects.equals(request.staticClientAddress, otherRequest.staticClientAddress)
&& request.exemptFromEntitlementCheck == otherRequest.exemptFromEntitlementCheck
- && request.showProvisioningUi == otherRequest.showProvisioningUi;
+ && request.showProvisioningUi == otherRequest.showProvisioningUi
+ && request.connectivityScope == otherRequest.connectivityScope;
}
/** Get inet6 address for all nodes given scope ID. */
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 1815ff3..acbfa8c 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -29,6 +29,7 @@
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER;
@@ -90,6 +91,7 @@
import android.net.TetheredClient;
import android.net.TetheringCallbackStartedParcel;
import android.net.TetheringConfigurationParcel;
+import android.net.TetheringManager.TetheringRequest;
import android.net.TetheringRequestParcel;
import android.net.ip.IpServer;
import android.net.shared.NetdUtils;
@@ -731,7 +733,7 @@
return;
}
maybeTrackNewInterfaceLocked(iface, TETHERING_ETHERNET);
- changeInterfaceState(iface, IpServer.STATE_TETHERED);
+ changeInterfaceState(iface, getRequestedState(TETHERING_ETHERNET));
mConfiguredEthernetIface = iface;
}
}
@@ -748,10 +750,10 @@
}
}
- void tether(String iface, final IIntResultListener listener) {
+ void tether(String iface, int requestedState, final IIntResultListener listener) {
mHandler.post(() -> {
try {
- listener.onResult(tether(iface, IpServer.STATE_TETHERED));
+ listener.onResult(tether(iface, requestedState));
} catch (RemoteException e) { }
});
}
@@ -855,6 +857,22 @@
return true;
}
+ private int getRequestedState(int type) {
+ final TetheringRequestParcel request = mActiveTetheringRequests.get(type);
+
+ // The request could have been deleted before we had a chance to complete it.
+ // If so, assume that the scope is the default scope for this tethering type.
+ // This likely doesn't matter - if the request has been deleted, then tethering is
+ // likely going to be stopped soon anyway.
+ final int connectivityScope = (request != null)
+ ? request.connectivityScope
+ : TetheringRequest.getDefaultConnectivityScope(type);
+
+ return connectivityScope == CONNECTIVITY_SCOPE_LOCAL
+ ? IpServer.STATE_LOCAL_ONLY
+ : IpServer.STATE_TETHERED;
+ }
+
// TODO: Figure out how to update for local hotspot mode interfaces.
private void sendTetherStateChangedBroadcast() {
if (!isTetheringSupported()) return;
@@ -994,9 +1012,11 @@
mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_USB);
} else if (usbConfigured && rndisEnabled) {
// Tether if rndis is enabled and usb is configured.
- tetherMatchingInterfaces(IpServer.STATE_TETHERED, TETHERING_USB);
+ final int state = getRequestedState(TETHERING_USB);
+ tetherMatchingInterfaces(state, TETHERING_USB);
} else if (usbConnected && ncmEnabled) {
- tetherMatchingInterfaces(IpServer.STATE_LOCAL_ONLY, TETHERING_NCM);
+ final int state = getRequestedState(TETHERING_NCM);
+ tetherMatchingInterfaces(state, TETHERING_NCM);
}
mRndisEnabled = usbConfigured && rndisEnabled;
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index 1906ca7..e36df7f 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -105,7 +105,7 @@
IIntResultListener listener) {
if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
- mTethering.tether(iface, listener);
+ mTethering.tether(iface, IpServer.STATE_TETHERED, listener);
}
@Override
diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index d206ea0..fe4e696 100644
--- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -19,7 +19,14 @@
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.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_ETHERNET;
+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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -50,6 +57,10 @@
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.EthernetHeader;
+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;
@@ -60,6 +71,7 @@
import java.io.FileDescriptor;
import java.net.Inet4Address;
+import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
@@ -229,6 +241,82 @@
}
+ private static boolean isRouterAdvertisement(byte[] pkt) {
+ if (pkt == null) return false;
+
+ ByteBuffer buf = ByteBuffer.wrap(pkt);
+
+ final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
+ if (ethHdr.etherType != ETHER_TYPE_IPV6) return false;
+
+ final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
+ if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return false;
+
+ final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
+ return icmpv6Hdr.type == (short) ICMPV6_ROUTER_ADVERTISEMENT;
+ }
+
+ private static void expectRouterAdvertisement(TapPacketReader reader, String iface,
+ long timeoutMs) {
+ final long deadline = SystemClock.uptimeMillis() + timeoutMs;
+ do {
+ byte[] pkt = reader.popPacket(timeoutMs);
+ if (isRouterAdvertisement(pkt)) return;
+ timeoutMs = deadline - SystemClock.uptimeMillis();
+ } while (timeoutMs > 0);
+ fail("Did not receive router advertisement on " + iface + " after "
+ + timeoutMs + "ms idle");
+ }
+
+ private static void expectLocalOnlyAddresses(String iface) throws Exception {
+ final List<InterfaceAddress> interfaceAddresses =
+ NetworkInterface.getByName(iface).getInterfaceAddresses();
+
+ boolean foundIpv6Ula = false;
+ for (InterfaceAddress ia : interfaceAddresses) {
+ final InetAddress addr = ia.getAddress();
+ if (isIPv6ULA(addr)) {
+ foundIpv6Ula = true;
+ }
+ final int prefixlen = ia.getNetworkPrefixLength();
+ final LinkAddress la = new LinkAddress(addr, prefixlen);
+ if (la.isIpv6() && la.isGlobalPreferred()) {
+ fail("Found global IPv6 address on local-only interface: " + interfaceAddresses);
+ }
+ }
+
+ assertTrue("Did not find IPv6 ULA on local-only interface " + iface,
+ foundIpv6Ula);
+ }
+
+ @Test
+ public void testLocalOnlyTethering() throws Exception {
+ assumeFalse(mEm.isAvailable());
+
+ mEm.setIncludeTestInterfaces(true);
+
+ mTestIface = createTestInterface();
+
+ final String iface = mTetheredInterfaceRequester.getInterface();
+ assertEquals("TetheredInterfaceCallback for unexpected interface",
+ mTestIface.getInterfaceName(), iface);
+
+ final TetheringRequest request = new TetheringRequest.Builder(TETHERING_ETHERNET)
+ .setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build();
+ mTetheringEventCallback = enableEthernetTethering(iface, request);
+ mTetheringEventCallback.awaitInterfaceLocalOnly();
+
+ // makePacketReader only works after tethering is started, because until then the interface
+ // does not have an IP address, and unprivileged apps cannot see interfaces without IP
+ // addresses. This shouldn't be flaky because the TAP interface will buffer all packets even
+ // before the reader is started.
+ FileDescriptor fd = mTestIface.getFileDescriptor().getFileDescriptor();
+ mTapPacketReader = makePacketReader(fd, getMTU(mTestIface));
+
+ expectRouterAdvertisement(mTapPacketReader, iface, 2000 /* timeoutMs */);
+ expectLocalOnlyAddresses(iface);
+ }
+
private boolean isAdbOverNetwork() {
// If adb TCP port opened, this test may running by adb over network.
return (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1)
@@ -257,10 +345,13 @@
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 String mIface;
private volatile boolean mInterfaceWasTethered = false;
+ private volatile boolean mInterfaceWasLocalOnly = false;
private volatile boolean mUnregistered = false;
private volatile Collection<TetheredClient> mClients = null;
@@ -279,7 +370,6 @@
// Ignore stale callbacks registered by previous test cases.
if (mUnregistered) return;
- final boolean wasTethered = mTetheringStartedLatch.getCount() == 0;
if (!mInterfaceWasTethered && (mIface == null || interfaces.contains(mIface))) {
// This interface is being tethered for the first time.
Log.d(TAG, "Tethering started: " + interfaces);
@@ -291,20 +381,48 @@
}
}
+ @Override
+ public void onLocalOnlyInterfacesChanged(List<String> interfaces) {
+ // Ignore stale callbacks registered by previous test cases.
+ if (mUnregistered) return;
+
+ if (!mInterfaceWasLocalOnly && (mIface == null || 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));
+ }
+
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) return;
+ if (!mInterfaceWasTethered && !mInterfaceWasLocalOnly) return;
- assertTrue(mIface + " not untethered after " + TIMEOUT_MS + "ms",
- mTetheringStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ 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
@@ -347,7 +465,19 @@
};
Log.d(TAG, "Starting Ethernet tethering");
mTm.startTethering(request, mHandler::post /* executor */, startTetheringCallback);
- callback.awaitInterfaceTethered();
+
+ 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;
}
@@ -444,7 +574,6 @@
}
private static final class TetheredInterfaceRequester implements TetheredInterfaceCallback {
- private final CountDownLatch mInterfaceAvailableLatch = new CountDownLatch(1);
private final Handler mHandler;
private final EthernetManager mEm;
diff --git a/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java b/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
index 9968b5f..e5d0b1c 100644
--- a/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
+++ b/Tethering/tests/unit/src/android/net/util/TetheringUtilsTest.java
@@ -15,6 +15,7 @@
*/
package android.net.util;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.system.OsConstants.AF_UNIX;
@@ -78,7 +79,7 @@
}
@Test
- public void testIsTetheringRequestEquals() throws Exception {
+ public void testIsTetheringRequestEquals() {
TetheringRequestParcel request = makeTetheringRequestParcel();
assertTrue(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, mTetheringRequest));
@@ -104,7 +105,11 @@
request.showProvisioningUi = false;
assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
- MiscAsserts.assertFieldCountEquals(5, TetheringRequestParcel.class);
+ request = makeTetheringRequestParcel();
+ request.connectivityScope = CONNECTIVITY_SCOPE_LOCAL;
+ assertFalse(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, request));
+
+ MiscAsserts.assertFieldCountEquals(6, TetheringRequestParcel.class);
}
// Writes the specified packet to a filedescriptor, skipping the Ethernet header.
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index be98f60..7204ff6 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -37,6 +37,7 @@
import android.net.ITetheringConnector;
import android.net.ITetheringEventCallback;
import android.net.TetheringRequestParcel;
+import android.net.ip.IpServer;
import android.os.Bundle;
import android.os.Handler;
import android.os.ResultReceiver;
@@ -158,7 +159,7 @@
private void runTether(final TestTetheringResult result) throws Exception {
mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringSupported();
- verify(mTethering).tether(eq(TEST_IFACE_NAME), eq(result));
+ verify(mTethering).tether(TEST_IFACE_NAME, IpServer.STATE_TETHERED, result);
}
@Test
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index b207af9..7c3dd23 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -33,6 +33,7 @@
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
+import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER;
@@ -702,17 +703,19 @@
}
private TetheringRequestParcel createTetheringRequestParcel(final int type) {
- return createTetheringRequestParcel(type, null, null, false);
+ return createTetheringRequestParcel(type, null, null, false, CONNECTIVITY_SCOPE_GLOBAL);
}
private TetheringRequestParcel createTetheringRequestParcel(final int type,
- final LinkAddress serverAddr, final LinkAddress clientAddr, final boolean exempt) {
+ final LinkAddress serverAddr, final LinkAddress clientAddr, final boolean exempt,
+ final int scope) {
final TetheringRequestParcel request = new TetheringRequestParcel();
request.tetheringType = type;
request.localIPv4Address = serverAddr;
request.staticClientAddress = clientAddr;
request.exemptFromEntitlementCheck = exempt;
request.showProvisioningUi = false;
+ request.connectivityScope = scope;
return request;
}
@@ -1973,16 +1976,14 @@
final ResultListener thirdResult = new ResultListener(TETHER_ERROR_NO_ERROR);
// Enable USB tethering and check that Tethering starts USB.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- null, null, false), firstResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), firstResult);
mLooper.dispatchAll();
firstResult.assertHasResult();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
verifyNoMoreInteractions(mUsbManager);
// Enable USB tethering again with the same request and expect no change to USB.
- mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- null, null, false), secondResult);
+ mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), secondResult);
mLooper.dispatchAll();
secondResult.assertHasResult();
verify(mUsbManager, never()).setCurrentFunctions(UsbManager.FUNCTION_NONE);
@@ -1991,7 +1992,7 @@
// Enable USB tethering with a different request and expect that USB is stopped and
// started.
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- serverLinkAddr, clientLinkAddr, false), thirdResult);
+ serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL), thirdResult);
mLooper.dispatchAll();
thirdResult.assertHasResult();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
@@ -2014,7 +2015,7 @@
final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor =
ArgumentCaptor.forClass(DhcpServingParamsParcel.class);
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- serverLinkAddr, clientLinkAddr, false), null);
+ serverLinkAddr, clientLinkAddr, false, CONNECTIVITY_SCOPE_GLOBAL), null);
mLooper.dispatchAll();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
@@ -2080,7 +2081,8 @@
public void testExemptFromEntitlementCheck() throws Exception {
setupForRequiredProvisioning();
final TetheringRequestParcel wifiNotExemptRequest =
- createTetheringRequestParcel(TETHERING_WIFI, null, null, false);
+ createTetheringRequestParcel(TETHERING_WIFI, null, null, false,
+ CONNECTIVITY_SCOPE_GLOBAL);
mTethering.startTethering(wifiNotExemptRequest, null);
mLooper.dispatchAll();
verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false);
@@ -2093,7 +2095,8 @@
setupForRequiredProvisioning();
final TetheringRequestParcel wifiExemptRequest =
- createTetheringRequestParcel(TETHERING_WIFI, null, null, true);
+ createTetheringRequestParcel(TETHERING_WIFI, null, null, true,
+ CONNECTIVITY_SCOPE_GLOBAL);
mTethering.startTethering(wifiExemptRequest, null);
mLooper.dispatchAll();
verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false);
@@ -2386,7 +2389,7 @@
mTethering.interfaceStatusChanged(TEST_BT_IFNAME, false);
mTethering.interfaceStatusChanged(TEST_BT_IFNAME, true);
final ResultListener tetherResult = new ResultListener(TETHER_ERROR_NO_ERROR);
- mTethering.tether(TEST_BT_IFNAME, tetherResult);
+ mTethering.tether(TEST_BT_IFNAME, IpServer.STATE_TETHERED, tetherResult);
mLooper.dispatchAll();
tetherResult.assertHasResult();