Merge "Allow callers of startTethering to choose local-only mode." am: ac9ce08d45 am: af691a13b6 am: f772ab1c61
Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/1674232 Change-Id: I308835fd656b7c61d04f04b816babd0e2031df89
This commit is contained in:
@@ -19,7 +19,14 @@ package android.net;
|
||||
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.InstrumentationRegistry;
|
||||
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 org.junit.runner.RunWith;
|
||||
|
||||
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 @@ public class EthernetTetheringTest {
|
||||
|
||||
}
|
||||
|
||||
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 @@ public class EthernetTetheringTest {
|
||||
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 @@ public class EthernetTetheringTest {
|
||||
// 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 @@ public class EthernetTetheringTest {
|
||||
}
|
||||
}
|
||||
|
||||
@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 @@ public class EthernetTetheringTest {
|
||||
};
|
||||
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 @@ public class EthernetTetheringTest {
|
||||
}
|
||||
|
||||
private static final class TetheredInterfaceRequester implements TetheredInterfaceCallback {
|
||||
private final CountDownLatch mInterfaceAvailableLatch = new CountDownLatch(1);
|
||||
private final Handler mHandler;
|
||||
private final EthernetManager mEm;
|
||||
|
||||
|
||||
@@ -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 @@ public class TetheringUtilsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsTetheringRequestEquals() throws Exception {
|
||||
public void testIsTetheringRequestEquals() {
|
||||
TetheringRequestParcel request = makeTetheringRequestParcel();
|
||||
|
||||
assertTrue(TetheringUtils.isTetheringRequestEquals(mTetheringRequest, mTetheringRequest));
|
||||
@@ -104,7 +105,11 @@ public class TetheringUtilsTest {
|
||||
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.
|
||||
|
||||
@@ -37,6 +37,7 @@ import android.net.IIntResultListener;
|
||||
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 @@ public final class TetheringServiceTest {
|
||||
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
|
||||
|
||||
@@ -33,6 +33,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
|
||||
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 @@ public class TetheringTest {
|
||||
}
|
||||
|
||||
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 @@ public class TetheringTest {
|
||||
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 @@ public class TetheringTest {
|
||||
// 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 @@ public class TetheringTest {
|
||||
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 class TetheringTest {
|
||||
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 @@ public class TetheringTest {
|
||||
|
||||
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 @@ public class TetheringTest {
|
||||
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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user