diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 6d3d0f6721c6e..1bc74238436f9 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -56,6 +56,7 @@ import android.net.NetworkInfo; import android.net.NetworkState; import android.net.NetworkUtils; import android.net.RouteInfo; +import android.net.util.InterfaceSet; import android.net.util.PrefixUtils; import android.net.util.SharedLog; import android.net.util.VersionedBroadcastListener; @@ -98,6 +99,7 @@ import com.android.server.connectivity.tethering.SimChangeListener; import com.android.server.connectivity.tethering.TetherInterfaceStateMachine; import com.android.server.connectivity.tethering.TetheringConfiguration; import com.android.server.connectivity.tethering.TetheringDependencies; +import com.android.server.connectivity.tethering.TetheringInterfaceUtils; import com.android.server.connectivity.tethering.UpstreamNetworkMonitor; import com.android.server.net.BaseNetworkObserver; @@ -184,7 +186,7 @@ public class Tethering extends BaseNetworkObserver { private final TetheringDependencies mDeps; private volatile TetheringConfiguration mConfig; - private String mCurrentUpstreamIface; + private InterfaceSet mCurrentUpstreamIfaceSet; private Notification.Builder mTetheredNotificationBuilder; private int mLastNotificationId; @@ -1162,12 +1164,11 @@ public class Tethering extends BaseNetworkObserver { } // Needed because the canonical source of upstream truth is just the - // upstream interface name, |mCurrentUpstreamIface|. This is ripe for - // future simplification, once the upstream Network is canonical. + // upstream interface set, |mCurrentUpstreamIfaceSet|. private boolean pertainsToCurrentUpstream(NetworkState ns) { - if (ns != null && ns.linkProperties != null && mCurrentUpstreamIface != null) { + if (ns != null && ns.linkProperties != null && mCurrentUpstreamIfaceSet != null) { for (String ifname : ns.linkProperties.getAllInterfaceNames()) { - if (mCurrentUpstreamIface.equals(ifname)) { + if (mCurrentUpstreamIfaceSet.ifnames.contains(ifname)) { return true; } } @@ -1362,31 +1363,27 @@ public class Tethering extends BaseNetworkObserver { } protected void setUpstreamNetwork(NetworkState ns) { - String iface = null; + InterfaceSet ifaces = null; if (ns != null) { // Find the interface with the default IPv4 route. It may be the // interface described by linkProperties, or one of the interfaces // stacked on top of it. mLog.i("Looking for default routes on: " + ns.linkProperties); - final String iface4 = getIPv4DefaultRouteInterface(ns); - final String iface6 = getIPv6DefaultRouteInterface(ns); - mLog.i("IPv4/IPv6 upstream interface(s): " + iface4 + "/" + iface6); - - iface = (iface4 != null) ? iface4 : null /* TODO: iface6 */; + ifaces = TetheringInterfaceUtils.getTetheringInterfaces(ns); + mLog.i("Found upstream interface(s): " + ifaces); } - if (iface != null) { + if (ifaces != null) { setDnsForwarders(ns.network, ns.linkProperties); } - notifyDownstreamsOfNewUpstreamIface(iface); + notifyDownstreamsOfNewUpstreamIface(ifaces); if (ns != null && pertainsToCurrentUpstream(ns)) { // If we already have NetworkState for this network examine // it immediately, because there likely will be no second // EVENT_ON_AVAILABLE (it was already received). handleNewUpstreamNetworkState(ns); - } else if (mCurrentUpstreamIface == null) { - // There are no available upstream networks, or none that - // have an IPv4 default route (current metric for success). + } else if (mCurrentUpstreamIfaceSet == null) { + // There are no available upstream networks. handleNewUpstreamNetworkState(null); } } @@ -1413,12 +1410,10 @@ public class Tethering extends BaseNetworkObserver { } } - protected void notifyDownstreamsOfNewUpstreamIface(String ifaceName) { - mLog.log("Notifying downstreams of upstream=" + ifaceName); - mCurrentUpstreamIface = ifaceName; + protected void notifyDownstreamsOfNewUpstreamIface(InterfaceSet ifaces) { + mCurrentUpstreamIfaceSet = ifaces; for (TetherInterfaceStateMachine sm : mNotifyList) { - sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, - ifaceName); + sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, ifaces); } } @@ -1490,7 +1485,7 @@ public class Tethering extends BaseNetworkObserver { // For example, after CONNECTIVITY_ACTION listening is removed, here // is where we could observe a Wi-Fi network becoming available and // passing validation. - if (mCurrentUpstreamIface == null) { + if (mCurrentUpstreamIfaceSet == null) { // If we have no upstream interface, try to run through upstream // selection again. If, for example, IPv4 connectivity has shown up // after IPv6 (e.g., 464xlat became available) we want the chance to @@ -1514,8 +1509,7 @@ public class Tethering extends BaseNetworkObserver { handleNewUpstreamNetworkState(ns); break; case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES: - setDnsForwarders(ns.network, ns.linkProperties); - handleNewUpstreamNetworkState(ns); + chooseUpstreamType(false); break; case UpstreamNetworkMonitor.EVENT_ON_LOST: // TODO: Re-evaluate possible upstreams. Currently upstream @@ -1588,7 +1582,7 @@ public class Tethering extends BaseNetworkObserver { if (VDBG) Log.d(TAG, "Tether Mode requested by " + who); handleInterfaceServingStateActive(message.arg1, who); who.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, - mCurrentUpstreamIface); + mCurrentUpstreamIfaceSet); // If there has been a change and an upstream is now // desired, kick off the selection process. final boolean previousUpstreamWanted = updateUpstreamWanted(); @@ -1866,7 +1860,7 @@ public class Tethering extends BaseNetworkObserver { pw.println(" - lastError = " + tetherState.lastError); } pw.println("Upstream wanted: " + upstreamWanted()); - pw.println("Current upstream interface: " + mCurrentUpstreamIface); + pw.println("Current upstream interface(s): " + mCurrentUpstreamIfaceSet); pw.decreaseIndent(); } diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java index 518f6c1440ccb..ba67c94d1cd45 100644 --- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java +++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java @@ -30,10 +30,8 @@ import android.util.Log; import java.net.Inet6Address; import java.net.InetAddress; -import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.LinkedList; import java.util.Random; @@ -119,7 +117,7 @@ public class IPv6TetheringCoordinator { if (VDBG) { Log.d(TAG, "updateUpstreamNetworkState: " + toDebugString(ns)); } - if (!canTetherIPv6(ns, mLog)) { + if (TetheringInterfaceUtils.getIPv6Interface(ns) == null) { stopIPv6TetheringOnAllInterfaces(); setUpstreamNetworkState(null); return; @@ -208,70 +206,6 @@ public class IPv6TetheringCoordinator { return null; } - private static boolean canTetherIPv6(NetworkState ns, SharedLog sharedLog) { - // Broadly speaking: - // - // [1] does the upstream have an IPv6 default route? - // - // and - // - // [2] does the upstream have one or more global IPv6 /64s - // dedicated to this device? - // - // In lieu of Prefix Delegation and other evaluation of whether a - // prefix may or may not be dedicated to this device, for now just - // check whether the upstream is TRANSPORT_CELLULAR. This works - // because "[t]he 3GPP network allocates each default bearer a unique - // /64 prefix", per RFC 6459, Section 5.2. - - final boolean canTether = - (ns != null) && (ns.network != null) && - (ns.linkProperties != null) && (ns.networkCapabilities != null) && - // At least one upstream DNS server: - ns.linkProperties.isProvisioned() && - // Minimal amount of IPv6 provisioning: - ns.linkProperties.hasIPv6DefaultRoute() && - ns.linkProperties.hasGlobalIPv6Address() && - // Temporary approximation of "dedicated prefix": - ns.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR); - - // For now, we do not support separate IPv4 and IPv6 upstreams (e.g. - // tethering with 464xlat involved). TODO: Rectify this shortcoming, - // likely by calling NetworkManagementService#startInterfaceForwarding() - // for all upstream interfaces. - RouteInfo v4default = null; - RouteInfo v6default = null; - if (canTether) { - for (RouteInfo r : ns.linkProperties.getAllRoutes()) { - if (r.isIPv4Default()) { - v4default = r; - } else if (r.isIPv6Default()) { - v6default = r; - } - - if (v4default != null && v6default != null) { - break; - } - } - } - - final boolean supportedConfiguration = - (v4default != null) && (v6default != null) && - (v4default.getInterface() != null) && - v4default.getInterface().equals(v6default.getInterface()); - - final boolean outcome = canTether && supportedConfiguration; - - if (ns == null) { - sharedLog.log("No available upstream."); - } else { - sharedLog.log(String.format("IPv6 tethering is %s for upstream: %s", - (outcome ? "available" : "not available"), toDebugString(ns))); - } - - return outcome; - } - private static LinkProperties getIPv6OnlyLinkProperties(LinkProperties lp) { final LinkProperties v6only = new LinkProperties(); if (lp == null) { diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java index e4c7ca0769d82..5ed14a07cb6cc 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java @@ -31,7 +31,7 @@ import android.net.ip.InterfaceController; import android.net.ip.RouterAdvertisementDaemon; import android.net.ip.RouterAdvertisementDaemon.RaParams; import android.net.util.InterfaceParams; -import android.net.util.NetdService; +import android.net.util.InterfaceSet; import android.net.util.SharedLog; import android.os.INetworkManagementService; import android.os.Looper; @@ -49,12 +49,12 @@ import com.android.internal.util.StateMachine; import java.net.Inet6Address; import java.net.InetAddress; -import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashSet; import java.util.Objects; import java.util.Random; +import java.util.Set; /** * Provides the interface to IP-layer serving functionality for a given network @@ -121,7 +121,7 @@ public class TetherInterfaceStateMachine extends StateMachine { private int mLastError; private int mServingMode; - private String mMyUpstreamIfaceName; // may change over time + private InterfaceSet mUpstreamIfaceSet; // may change over time private InterfaceParams mInterfaceParams; // TODO: De-duplicate this with mLinkProperties above. Currently, these link // properties are those selected by the IPv6TetheringCoordinator and relayed @@ -622,10 +622,10 @@ public class TetherInterfaceStateMachine extends StateMachine { } private void cleanupUpstream() { - if (mMyUpstreamIfaceName == null) return; + if (mUpstreamIfaceSet == null) return; - cleanupUpstreamInterface(mMyUpstreamIfaceName); - mMyUpstreamIfaceName = null; + for (String ifname : mUpstreamIfaceSet.ifnames) cleanupUpstreamInterface(ifname); + mUpstreamIfaceSet = null; } private void cleanupUpstreamInterface(String upstreamIface) { @@ -661,34 +661,66 @@ public class TetherInterfaceStateMachine extends StateMachine { mLog.e("CMD_TETHER_REQUESTED while already tethering."); break; case CMD_TETHER_CONNECTION_CHANGED: - String newUpstreamIfaceName = (String)(message.obj); - if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) || - (mMyUpstreamIfaceName != null && - mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) { + final InterfaceSet newUpstreamIfaceSet = (InterfaceSet) message.obj; + if (noChangeInUpstreamIfaceSet(newUpstreamIfaceSet)) { if (VDBG) Log.d(TAG, "Connection changed noop - dropping"); break; } - cleanupUpstream(); - if (newUpstreamIfaceName != null) { + + if (newUpstreamIfaceSet == null) { + cleanupUpstream(); + break; + } + + for (String removed : upstreamInterfacesRemoved(newUpstreamIfaceSet)) { + cleanupUpstreamInterface(removed); + } + + final Set added = upstreamInterfacesAdd(newUpstreamIfaceSet); + // This makes the call to cleanupUpstream() in the error + // path for any interface neatly cleanup all the interfaces. + mUpstreamIfaceSet = newUpstreamIfaceSet; + + for (String ifname : added) { try { - mNMService.enableNat(mIfaceName, newUpstreamIfaceName); - mNMService.startInterfaceForwarding(mIfaceName, - newUpstreamIfaceName); + mNMService.enableNat(mIfaceName, ifname); + mNMService.startInterfaceForwarding(mIfaceName, ifname); } catch (Exception e) { mLog.e("Exception enabling NAT: " + e); - cleanupUpstreamInterface(newUpstreamIfaceName); + cleanupUpstream(); mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR; transitionTo(mInitialState); return true; } } - mMyUpstreamIfaceName = newUpstreamIfaceName; break; default: return false; } return true; } + + private boolean noChangeInUpstreamIfaceSet(InterfaceSet newIfaces) { + if (mUpstreamIfaceSet == null && newIfaces == null) return true; + if (mUpstreamIfaceSet != null && newIfaces != null) { + return mUpstreamIfaceSet.equals(newIfaces); + } + return false; + } + + private Set upstreamInterfacesRemoved(InterfaceSet newIfaces) { + if (mUpstreamIfaceSet == null) return new HashSet<>(); + + final HashSet removed = new HashSet<>(mUpstreamIfaceSet.ifnames); + removed.removeAll(newIfaces.ifnames); + return removed; + } + + private Set upstreamInterfacesAdd(InterfaceSet newIfaces) { + final HashSet added = new HashSet<>(newIfaces.ifnames); + if (mUpstreamIfaceSet != null) added.removeAll(mUpstreamIfaceSet.ifnames); + return added; + } } /** diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java b/services/core/java/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java new file mode 100644 index 0000000000000..6c7ff91761acb --- /dev/null +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity.tethering; + +import android.annotation.Nullable; +import android.net.LinkProperties; +import android.net.NetworkCapabilities; +import android.net.NetworkState; +import android.net.RouteInfo; +import android.net.util.InterfaceSet; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; + +/** + * @hide + */ +public final class TetheringInterfaceUtils { + /** + * Get upstream interfaces for tethering based on default routes for IPv4/IPv6. + * @return null if there is no usable interface, or a set of at least one interface otherwise. + */ + public static @Nullable InterfaceSet getTetheringInterfaces(NetworkState ns) { + if (ns == null) { + return null; + } + + final LinkProperties lp = ns.linkProperties; + final String if4 = getInterfaceForDestination(lp, Inet4Address.ANY); + final String if6 = getIPv6Interface(ns); + + return (if4 == null && if6 == null) ? null : new InterfaceSet(if4, if6); + } + + /** + * Get the upstream interface for IPv6 tethering. + * @return null if there is no usable interface, or the interface name otherwise. + */ + public static @Nullable String getIPv6Interface(NetworkState ns) { + // Broadly speaking: + // + // [1] does the upstream have an IPv6 default route? + // + // and + // + // [2] does the upstream have one or more global IPv6 /64s + // dedicated to this device? + // + // In lieu of Prefix Delegation and other evaluation of whether a + // prefix may or may not be dedicated to this device, for now just + // check whether the upstream is TRANSPORT_CELLULAR. This works + // because "[t]he 3GPP network allocates each default bearer a unique + // /64 prefix", per RFC 6459, Section 5.2. + final boolean canTether = + (ns != null) && (ns.network != null) && + (ns.linkProperties != null) && (ns.networkCapabilities != null) && + // At least one upstream DNS server: + ns.linkProperties.hasIPv6DnsServer() && + // Minimal amount of IPv6 provisioning: + ns.linkProperties.hasGlobalIPv6Address() && + // Temporary approximation of "dedicated prefix": + ns.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR); + + return canTether + ? getInterfaceForDestination(ns.linkProperties, Inet6Address.ANY) + : null; + } + + private static String getInterfaceForDestination(LinkProperties lp, InetAddress dst) { + final RouteInfo ri = (lp != null) + ? RouteInfo.selectBestRoute(lp.getAllRoutes(), dst) + : null; + return (ri != null) ? ri.getInterface() : null; + } +} diff --git a/services/net/java/android/net/util/InterfaceSet.java b/services/net/java/android/net/util/InterfaceSet.java new file mode 100644 index 0000000000000..9f26fa17ef5c4 --- /dev/null +++ b/services/net/java/android/net/util/InterfaceSet.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.util; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.StringJoiner; + + +/** + * @hide + */ +public class InterfaceSet { + public final Set ifnames; + + public InterfaceSet(String... names) { + final Set nameSet = new HashSet<>(); + for (String name : names) { + if (name != null) nameSet.add(name); + } + ifnames = Collections.unmodifiableSet(nameSet); + } + + @Override + public String toString() { + final StringJoiner sj = new StringJoiner(",", "[", "]"); + for (String ifname : ifnames) sj.add(ifname); + return sj.toString(); + } + + @Override + public boolean equals(Object obj) { + return obj != null + && obj instanceof InterfaceSet + && ifnames.equals(((InterfaceSet)obj).ifnames); + } +} diff --git a/tests/net/java/android/net/util/InterfaceSetTest.java b/tests/net/java/android/net/util/InterfaceSetTest.java new file mode 100644 index 0000000000000..8012838d16fc7 --- /dev/null +++ b/tests/net/java/android/net/util/InterfaceSetTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.util; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class InterfaceSetTest { + @Test + public void testNullNamesIgnored() { + final InterfaceSet set = new InterfaceSet(null, "if1", null, "if2", null); + assertEquals(2, set.ifnames.size()); + assertTrue(set.ifnames.contains("if1")); + assertTrue(set.ifnames.contains("if2")); + } + + @Test + public void testToString() { + final InterfaceSet set = new InterfaceSet("if1", "if2"); + final String setString = set.toString(); + assertTrue(setString.equals("[if1,if2]") || setString.equals("[if2,if1]")); + } + + @Test + public void testToString_Empty() { + final InterfaceSet set = new InterfaceSet(null, null); + assertEquals("[]", set.toString()); + } + + @Test + public void testEquals() { + assertEquals(new InterfaceSet(null, "if1", "if2"), new InterfaceSet("if2", "if1")); + assertEquals(new InterfaceSet(null, null), new InterfaceSet()); + assertFalse(new InterfaceSet("if1", "if3").equals(new InterfaceSet("if1", "if2"))); + assertFalse(new InterfaceSet("if1", "if2").equals(new InterfaceSet("if1"))); + assertFalse(new InterfaceSet().equals(null)); + } +} diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java index ed20c7a988727..27f90f261ce0c 100644 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java @@ -29,7 +29,6 @@ import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.notNull; @@ -225,7 +224,8 @@ public class TetheringTest { } } - private static NetworkState buildMobileUpstreamState(boolean withIPv4, boolean withIPv6) { + private static NetworkState buildMobileUpstreamState(boolean withIPv4, boolean withIPv6, + boolean with464xlat) { final NetworkInfo info = new NetworkInfo(ConnectivityManager.TYPE_MOBILE, 0, null, null); info.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null); final LinkProperties prop = new LinkProperties(); @@ -245,6 +245,15 @@ public class TetheringTest { NetworkUtils.numericToInetAddress("2001:db8::1"), TEST_MOBILE_IFNAME)); } + if (with464xlat) { + final LinkProperties stackedLink = new LinkProperties(); + stackedLink.setInterfaceName(TEST_XLAT_MOBILE_IFNAME); + stackedLink.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), + NetworkUtils.numericToInetAddress("192.0.0.1"), TEST_XLAT_MOBILE_IFNAME)); + + prop.addStackedLink(stackedLink); + } + final NetworkCapabilities capabilities = new NetworkCapabilities() .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);; @@ -252,11 +261,19 @@ public class TetheringTest { } private static NetworkState buildMobileIPv4UpstreamState() { - return buildMobileUpstreamState(true, false); + return buildMobileUpstreamState(true, false, false); + } + + private static NetworkState buildMobileIPv6UpstreamState() { + return buildMobileUpstreamState(false, true, false); } private static NetworkState buildMobileDualStackUpstreamState() { - return buildMobileUpstreamState(true, true); + return buildMobileUpstreamState(true, true, false); + } + + private static NetworkState buildMobile464xlatUpstreamState() { + return buildMobileUpstreamState(false, true, true); } @Before @@ -521,7 +538,7 @@ public class TetheringTest { for (TetherInterfaceStateMachine tism : mTetheringDependencies.ipv6CoordinatorNotifyList) { - NetworkState ipv6OnlyState = buildMobileUpstreamState(false, true); + NetworkState ipv6OnlyState = buildMobileUpstreamState(false, true, false); tism.sendMessage(TetherInterfaceStateMachine.CMD_IPV6_TETHER_UPDATE, 0, 0, upstreamState.linkProperties.isIPv6Provisioned() ? ipv6OnlyState.linkProperties @@ -548,6 +565,19 @@ public class TetheringTest { verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull()); } + @Test + public void workingMobileUsbTethering_IPv6() throws Exception { + NetworkState upstreamState = buildMobileIPv6UpstreamState(); + runUsbTethering(upstreamState); + + verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + + sendIPv6TetherUpdates(upstreamState); + verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull()); + verify(mNetd, times(1)).tetherApplyDnsInterfaces(); + } + @Test public void workingMobileUsbTethering_DualStack() throws Exception { NetworkState upstreamState = buildMobileDualStackUpstreamState(); @@ -562,6 +592,48 @@ public class TetheringTest { verify(mNetd, times(1)).tetherApplyDnsInterfaces(); } + @Test + public void workingMobileUsbTethering_MultipleUpstreams() throws Exception { + NetworkState upstreamState = buildMobile464xlatUpstreamState(); + runUsbTethering(upstreamState); + + verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); + verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, + TEST_XLAT_MOBILE_IFNAME); + + sendIPv6TetherUpdates(upstreamState); + verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull()); + verify(mNetd, times(1)).tetherApplyDnsInterfaces(); + } + + @Test + public void workingMobileUsbTethering_v6Then464xlat() throws Exception { + // Setup IPv6 + NetworkState upstreamState = buildMobileIPv6UpstreamState(); + runUsbTethering(upstreamState); + + // Then 464xlat comes up + upstreamState = buildMobile464xlatUpstreamState(); + when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())) + .thenReturn(upstreamState); + + // Upstream LinkProperties changed: UpstreamNetworkMonitor sends EVENT_ON_LINKPROPERTIES. + mTetheringDependencies.upstreamNetworkMonitorMasterSM.sendMessage( + Tethering.TetherMasterSM.EVENT_UPSTREAM_CALLBACK, + UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES, + 0, + upstreamState); + mLooper.dispatchAll(); + + // Forwarding is added for 464xlat, and was still added only once for v6 + verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME); + verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME); + verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, + TEST_XLAT_MOBILE_IFNAME); + } @Test public void workingLocalOnlyHotspotEnrichedApBroadcastWithIfaceChanged() throws Exception { diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java index 5f3fc54379a76..7c77cf59524d9 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java @@ -43,6 +43,7 @@ import android.net.InterfaceConfiguration; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.RouteInfo; +import android.net.util.InterfaceSet; import android.net.util.SharedLog; import android.os.INetworkManagementService; import android.os.RemoteException; @@ -371,7 +372,7 @@ public class TetherInterfaceStateMachineTest { */ private void dispatchTetherConnectionChanged(String upstreamIface) { mTestedSm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, - upstreamIface); + new InterfaceSet(upstreamIface)); mLooper.dispatchAll(); }