Merge "Add tests for IPv4 of dual stack tethering." into pi-dev
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index eee830f..6d3d0f6 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -181,6 +181,7 @@
     private final VersionedBroadcastListener mCarrierConfigChange;
     // TODO: Delete SimChangeListener; it's obsolete.
     private final SimChangeListener mSimChange;
+    private final TetheringDependencies mDeps;
 
     private volatile TetheringConfiguration mConfig;
     private String mCurrentUpstreamIface;
@@ -202,12 +203,13 @@
         mPolicyManager = policyManager;
         mLooper = looper;
         mSystemProperties = systemProperties;
+        mDeps = deps;
 
         mPublicSync = new Object();
 
         mTetherStates = new ArrayMap<>();
 
-        mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper);
+        mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps);
         mTetherMasterSM.start();
 
         final Handler smHandler = mTetherMasterSM.getHandler();
@@ -215,8 +217,8 @@
                 deps.getOffloadHardwareInterface(smHandler, mLog),
                 mContext.getContentResolver(), mNMService,
                 mLog);
-        mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
-                mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
+        mUpstreamNetworkMonitor = deps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
+                TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
         mForwardedDownstreams = new HashSet<>();
 
         IntentFilter filter = new IntentFilter();
@@ -1241,7 +1243,7 @@
 
         private static final int UPSTREAM_SETTLE_TIME_MS     = 10000;
 
-        TetherMasterSM(String name, Looper looper) {
+        TetherMasterSM(String name, Looper looper, TetheringDependencies deps) {
             super(name, looper);
 
             mInitialState = new InitialState();
@@ -1261,7 +1263,7 @@
             addState(mSetDnsForwardersErrorState);
 
             mNotifyList = new ArrayList<>();
-            mIPv6TetheringCoordinator = new IPv6TetheringCoordinator(mNotifyList, mLog);
+            mIPv6TetheringCoordinator = deps.getIPv6TetheringCoordinator(mNotifyList, mLog);
             mOffload = new OffloadWrapper();
 
             setInitialState(mInitialState);
@@ -1997,7 +1999,7 @@
         final TetherState tetherState = new TetherState(
                 new TetherInterfaceStateMachine(
                     iface, mLooper, interfaceType, mLog, mNMService, mStatsService,
-                    makeControlCallback(iface)));
+                    makeControlCallback(iface), mDeps));
         mTetherStates.put(iface, tetherState);
         tetherState.stateMachine.start();
     }
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 2224913..e4c7ca0 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -117,6 +117,8 @@
     private final int mInterfaceType;
     private final LinkProperties mLinkProperties;
 
+    private final TetheringDependencies mDeps;
+
     private int mLastError;
     private int mServingMode;
     private String mMyUpstreamIfaceName;  // may change over time
@@ -134,18 +136,19 @@
     public TetherInterfaceStateMachine(
             String ifaceName, Looper looper, int interfaceType, SharedLog log,
             INetworkManagementService nMService, INetworkStatsService statsService,
-            IControlsTethering tetherController) {
+            IControlsTethering tetherController,
+            TetheringDependencies deps) {
         super(ifaceName, looper);
         mLog = log.forSubComponent(ifaceName);
         mNMService = nMService;
-        // TODO: This should be passed in for testability.
-        mNetd = NetdService.getInstance();
+        mNetd = deps.getNetdService();
         mStatsService = statsService;
         mTetherController = tetherController;
         mInterfaceCtrl = new InterfaceController(ifaceName, nMService, mNetd, mLog);
         mIfaceName = ifaceName;
         mInterfaceType = interfaceType;
         mLinkProperties = new LinkProperties();
+        mDeps = deps;
         resetLinkProperties();
         mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
         mServingMode = IControlsTethering.STATE_AVAILABLE;
@@ -246,16 +249,14 @@
     }
 
     private boolean startIPv6() {
-        // TODO: Refactor for better testability.  This is one of the things
-        // that prohibits unittesting IPv6 tethering setup.
-        mInterfaceParams = InterfaceParams.getByName(mIfaceName);
+        mInterfaceParams = mDeps.getInterfaceParams(mIfaceName);
         if (mInterfaceParams == null) {
             mLog.e("Failed to find InterfaceParams");
             stopIPv6();
             return false;
         }
 
-        mRaDaemon = new RouterAdvertisementDaemon(mInterfaceParams);
+        mRaDaemon = mDeps.getRouterAdvertisementDaemon(mInterfaceParams);
         if (!mRaDaemon.start()) {
             stopIPv6();
             return false;
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
index b8174b6..66afb0f 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -16,9 +16,18 @@
 
 package com.android.server.connectivity.tethering;
 
+import android.content.Context;
+import android.net.INetd;
+import android.net.ip.RouterAdvertisementDaemon;
+import android.net.util.InterfaceParams;
+import android.net.util.NetdService;
 import android.os.Handler;
 import android.net.util.SharedLog;
 
+import com.android.internal.util.StateMachine;
+
+import java.util.ArrayList;
+
 
 /**
  * Capture tethering dependencies, for injection.
@@ -29,4 +38,26 @@
     public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) {
         return new OffloadHardwareInterface(h, log);
     }
+
+    public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target,
+            SharedLog log, int what) {
+        return new UpstreamNetworkMonitor(ctx, target, log, what);
+    }
+
+    public IPv6TetheringCoordinator getIPv6TetheringCoordinator(
+            ArrayList<TetherInterfaceStateMachine> notifyList, SharedLog log) {
+        return new IPv6TetheringCoordinator(notifyList, log);
+    }
+
+    public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) {
+        return new RouterAdvertisementDaemon(ifParams);
+    }
+
+    public InterfaceParams getInterfaceParams(String ifName) {
+        return InterfaceParams.getByName(ifName);
+    }
+
+    public INetd getNetdService() {
+        return NetdService.getInstance();
+    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index e692652..ed20c7a 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -27,8 +27,12 @@
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
 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;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
@@ -36,6 +40,7 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -45,18 +50,29 @@
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
 import android.hardware.usb.UsbManager;
 import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
+import android.net.INetd;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
 import android.net.InterfaceConfiguration;
-import android.net.NetworkRequest;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.MacAddress;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkState;
+import android.net.NetworkUtils;
+import android.net.RouteInfo;
+import android.net.ip.RouterAdvertisementDaemon;
+import android.net.util.InterfaceParams;
+import android.net.util.NetworkConstants;
 import android.net.util.SharedLog;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
@@ -74,10 +90,16 @@
 import android.telephony.CarrierConfigManager;
 import android.test.mock.MockContentResolver;
 
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.StateMachine;
 import com.android.internal.util.test.BroadcastInterceptingContext;
 import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.connectivity.tethering.IControlsTethering;
+import com.android.server.connectivity.tethering.IPv6TetheringCoordinator;
 import com.android.server.connectivity.tethering.OffloadHardwareInterface;
+import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
 import com.android.server.connectivity.tethering.TetheringDependencies;
+import com.android.server.connectivity.tethering.UpstreamNetworkMonitor;
 
 import org.junit.After;
 import org.junit.Before;
@@ -86,13 +108,21 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.util.ArrayList;
 import java.util.Vector;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class TetheringTest {
+    private static final int IFINDEX_OFFSET = 100;
+
     private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
+    private static final String TEST_MOBILE_IFNAME = "test_rmnet_data0";
+    private static final String TEST_XLAT_MOBILE_IFNAME = "v4-test_rmnet_data0";
+    private static final String TEST_USB_IFNAME = "test_rndis0";
+    private static final String TEST_WLAN_IFNAME = "test_wlan0";
 
     @Mock private ApplicationInfo mApplicationInfo;
     @Mock private Context mContext;
@@ -103,16 +133,21 @@
     @Mock private MockableSystemProperties mSystemProperties;
     @Mock private OffloadHardwareInterface mOffloadHardwareInterface;
     @Mock private Resources mResources;
-    @Mock private TetheringDependencies mTetheringDependencies;
     @Mock private UsbManager mUsbManager;
     @Mock private WifiManager mWifiManager;
     @Mock private CarrierConfigManager mCarrierConfigManager;
+    @Mock private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
+    @Mock private IPv6TetheringCoordinator mIPv6TetheringCoordinator;
+    @Mock private RouterAdvertisementDaemon mRouterAdvertisementDaemon;
+    @Mock private INetd mNetd;
+
+    private final MockTetheringDependencies mTetheringDependencies =
+            new MockTetheringDependencies();
 
     // Like so many Android system APIs, these cannot be mocked because it is marked final.
     // We have to use the real versions.
     private final PersistableBundle mCarrierConfig = new PersistableBundle();
     private final TestLooper mLooper = new TestLooper();
-    private final String mTestIfname = "test_wlan0";
 
     private Vector<Intent> mIntents;
     private BroadcastInterceptingContext mServiceContext;
@@ -146,23 +181,104 @@
         }
     }
 
+    public class MockTetheringDependencies extends TetheringDependencies {
+        private StateMachine upstreamNetworkMonitorMasterSM;
+        private ArrayList<TetherInterfaceStateMachine> ipv6CoordinatorNotifyList;
+
+        @Override
+        public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) {
+            return mOffloadHardwareInterface;
+        }
+
+        @Override
+        public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx,
+                StateMachine target, SharedLog log, int what) {
+            upstreamNetworkMonitorMasterSM = target;
+            return mUpstreamNetworkMonitor;
+        }
+
+        @Override
+        public IPv6TetheringCoordinator getIPv6TetheringCoordinator(
+                ArrayList<TetherInterfaceStateMachine> notifyList, SharedLog log) {
+            ipv6CoordinatorNotifyList = notifyList;
+            return mIPv6TetheringCoordinator;
+        }
+
+        @Override
+        public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) {
+            return mRouterAdvertisementDaemon;
+        }
+
+        @Override
+        public INetd getNetdService() {
+            return mNetd;
+        }
+
+        @Override
+        public InterfaceParams getInterfaceParams(String ifName) {
+            final String[] ifaces = new String[] { TEST_USB_IFNAME, TEST_WLAN_IFNAME,
+                    TEST_MOBILE_IFNAME };
+            final int index = ArrayUtils.indexOf(ifaces, ifName);
+            assertTrue("Non-mocked interface: " + ifName, index >= 0);
+            return new InterfaceParams(ifName, index + IFINDEX_OFFSET,
+                    MacAddress.ALL_ZEROS_ADDRESS);
+        }
+    }
+
+    private static NetworkState buildMobileUpstreamState(boolean withIPv4, boolean withIPv6) {
+        final NetworkInfo info = new NetworkInfo(ConnectivityManager.TYPE_MOBILE, 0, null, null);
+        info.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
+        final LinkProperties prop = new LinkProperties();
+        prop.setInterfaceName(TEST_MOBILE_IFNAME);
+
+        if (withIPv4) {
+            prop.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
+                    NetworkUtils.numericToInetAddress("10.0.0.1"), TEST_MOBILE_IFNAME));
+        }
+
+        if (withIPv6) {
+            prop.addDnsServer(NetworkUtils.numericToInetAddress("2001:db8::2"));
+            prop.addLinkAddress(
+                    new LinkAddress(NetworkUtils.numericToInetAddress("2001:db8::"),
+                            NetworkConstants.RFC7421_PREFIX_LENGTH));
+            prop.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0),
+                    NetworkUtils.numericToInetAddress("2001:db8::1"), TEST_MOBILE_IFNAME));
+        }
+
+
+        final NetworkCapabilities capabilities = new NetworkCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);;
+        return new NetworkState(info, prop, capabilities, new Network(100), null, "netid");
+    }
+
+    private static NetworkState buildMobileIPv4UpstreamState() {
+        return buildMobileUpstreamState(true, false);
+    }
+
+    private static NetworkState buildMobileDualStackUpstreamState() {
+        return buildMobileUpstreamState(true, true);
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range))
                 .thenReturn(new String[0]);
         when(mResources.getStringArray(com.android.internal.R.array.config_tether_usb_regexs))
-                .thenReturn(new String[0]);
+                .thenReturn(new String[] { "test_rndis\\d" });
         when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs))
-                .thenReturn(new String[]{ "test_wlan\\d", "test_rndis\\d" });
+                .thenReturn(new String[]{ "test_wlan\\d" });
         when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs))
                 .thenReturn(new String[0]);
         when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
                 .thenReturn(new int[0]);
         when(mNMService.listInterfaces())
-                .thenReturn(new String[]{ "test_rmnet_data0", mTestIfname });
+                .thenReturn(new String[] {
+                        TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME});
         when(mNMService.getInterfaceConfig(anyString()))
                 .thenReturn(new InterfaceConfiguration());
+        when(mRouterAdvertisementDaemon.start())
+                .thenReturn(true);
 
         mServiceContext = new MockContext(mContext);
         mContentResolver = new MockContentResolver(mServiceContext);
@@ -176,8 +292,6 @@
         };
         mServiceContext.registerReceiver(mBroadcastReceiver,
                 new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
-        when(mTetheringDependencies.getOffloadHardwareInterface(
-                any(Handler.class), any(SharedLog.class))).thenReturn(mOffloadHardwareInterface);
         mTethering = new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager,
                                    mLooper.getLooper(), mSystemProperties,
                                    mTetheringDependencies);
@@ -264,10 +378,10 @@
     }
 
     private void verifyInterfaceServingModeStarted() throws Exception {
-        verify(mNMService, times(1)).getInterfaceConfig(mTestIfname);
+        verify(mNMService, times(1)).getInterfaceConfig(TEST_WLAN_IFNAME);
         verify(mNMService, times(1))
-                .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
-        verify(mNMService, times(1)).tetherInterface(mTestIfname);
+                .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
+        verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME);
     }
 
     private void verifyTetheringBroadcast(String ifname, String whichExtra) {
@@ -287,7 +401,7 @@
         // per-interface state machine to start up, and telling us that
         // hotspot mode is to be started.
         if (emulateInterfaceStatusChanged) {
-            mTethering.interfaceStatusChanged(mTestIfname, true);
+            mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
         }
         sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
         mLooper.dispatchAll();
@@ -297,27 +411,31 @@
         // broadcast indicating that the interface is "available".
         if (emulateInterfaceStatusChanged) {
             verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
-            verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+            verifyTetheringBroadcast(TEST_WLAN_IFNAME, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
         }
         verifyNoMoreInteractions(mConnectivityManager);
         verifyNoMoreInteractions(mNMService);
         verifyNoMoreInteractions(mWifiManager);
     }
 
-    @Test
-    public void testUsbConfiguredBroadcastStartsTethering() throws Exception {
+    private void prepareUsbTethering(NetworkState upstreamState) {
         when(mConnectivityManager.isTetheringSupported()).thenReturn(true);
+        when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any()))
+                .thenReturn(upstreamState);
 
         // Emulate pressing the USB tethering button in Settings UI.
         mTethering.startTethering(TETHERING_USB, null, false);
         mLooper.dispatchAll();
         verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
 
-        // Pretend we receive a USB connected broadcast. Here we also pretend
-        // that the RNDIS function is somehow enabled, so that we see if we
-        // might trip ourselves up.
-        sendUsbBroadcast(true, false, true);
-        mLooper.dispatchAll();
+        mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
+    }
+
+    @Test
+    public void testUsbConfiguredBroadcastStartsTethering() throws Exception {
+        NetworkState upstreamState = buildMobileIPv4UpstreamState();
+        prepareUsbTethering(upstreamState);
+
         // This should produce no activity of any kind.
         verifyNoMoreInteractions(mConnectivityManager);
         verifyNoMoreInteractions(mNMService);
@@ -328,6 +446,10 @@
         // Now we should see the start of tethering mechanics (in this case:
         // tetherMatchingInterfaces() which starts by fetching all interfaces).
         verify(mNMService, times(1)).listInterfaces();
+
+        // UpstreamNetworkMonitor should receive selected upstream
+        verify(mUpstreamNetworkMonitor, times(1)).selectPreferredUpstreamType(any());
+        verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(upstreamState.network);
     }
 
     @Test
@@ -348,26 +470,21 @@
         // per-interface state machine to start up, and telling us that
         // hotspot mode is to be started.
         if (emulateInterfaceStatusChanged) {
-            mTethering.interfaceStatusChanged(mTestIfname, true);
+            mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
         }
-        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, mTestIfname, IFACE_IP_MODE_LOCAL_ONLY);
+        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_LOCAL_ONLY);
         mLooper.dispatchAll();
 
         verifyInterfaceServingModeStarted();
-        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+        verifyTetheringBroadcast(TEST_WLAN_IFNAME, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
         verify(mNMService, times(1)).setIpForwardingEnabled(true);
         verify(mNMService, times(1)).startTethering(any(String[].class));
         verifyNoMoreInteractions(mNMService);
         verify(mWifiManager).updateInterfaceIpState(
-                mTestIfname, WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
+                TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
         verifyNoMoreInteractions(mWifiManager);
-        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY);
-        // UpstreamNetworkMonitor will be started, and will register two callbacks:
-        // a "listen all" and a "track default".
-        verify(mConnectivityManager, times(1)).registerNetworkCallback(
-                any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
-        verify(mConnectivityManager, times(1)).registerDefaultNetworkCallback(
-                any(NetworkCallback.class), any(Handler.class));
+        verifyTetheringBroadcast(TEST_WLAN_IFNAME, ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY);
+        verify(mUpstreamNetworkMonitor, times(1)).start();
         // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast().
         verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
         verifyNoMoreInteractions(mConnectivityManager);
@@ -375,14 +492,14 @@
         // Emulate externally-visible WifiManager effects, when hotspot mode
         // is being torn down.
         sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
-        mTethering.interfaceRemoved(mTestIfname);
+        mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
         mLooper.dispatchAll();
 
-        verify(mNMService, times(1)).untetherInterface(mTestIfname);
+        verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME);
         // TODO: Why is {g,s}etInterfaceConfig() called more than once?
-        verify(mNMService, atLeastOnce()).getInterfaceConfig(mTestIfname);
+        verify(mNMService, atLeastOnce()).getInterfaceConfig(TEST_WLAN_IFNAME);
         verify(mNMService, atLeastOnce())
-                .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
+                .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
         verify(mNMService, times(1)).stopTethering();
         verify(mNMService, times(1)).setIpForwardingEnabled(false);
         verifyNoMoreInteractions(mNMService);
@@ -390,9 +507,62 @@
         // Asking for the last error after the per-interface state machine
         // has been reaped yields an unknown interface error.
         assertEquals(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE,
-                mTethering.getLastTetherError(mTestIfname));
+                mTethering.getLastTetherError(TEST_WLAN_IFNAME));
     }
 
+    /**
+     * Send CMD_IPV6_TETHER_UPDATE to TISMs as would be done by IPv6TetheringCoordinator.
+     */
+    private void sendIPv6TetherUpdates(NetworkState upstreamState) {
+        // IPv6TetheringCoordinator must have been notified of downstream
+        verify(mIPv6TetheringCoordinator, times(1)).addActiveDownstream(
+                argThat(sm -> sm.linkProperties().getInterfaceName().equals(TEST_USB_IFNAME)),
+                eq(IControlsTethering.STATE_TETHERED));
+
+        for (TetherInterfaceStateMachine tism :
+                mTetheringDependencies.ipv6CoordinatorNotifyList) {
+            NetworkState ipv6OnlyState = buildMobileUpstreamState(false, true);
+            tism.sendMessage(TetherInterfaceStateMachine.CMD_IPV6_TETHER_UPDATE, 0, 0,
+                    upstreamState.linkProperties.isIPv6Provisioned()
+                            ? ipv6OnlyState.linkProperties
+                            : null);
+        }
+        mLooper.dispatchAll();
+    }
+
+    private void runUsbTethering(NetworkState upstreamState) {
+        prepareUsbTethering(upstreamState);
+        sendUsbBroadcast(true, true, true);
+        mLooper.dispatchAll();
+    }
+
+    @Test
+    public void workingMobileUsbTethering_IPv4() throws Exception {
+        NetworkState upstreamState = buildMobileIPv4UpstreamState();
+        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, never()).buildNewRa(any(), notNull());
+    }
+
+    @Test
+    public void workingMobileUsbTethering_DualStack() throws Exception {
+        NetworkState upstreamState = buildMobileDualStackUpstreamState();
+        runUsbTethering(upstreamState);
+
+        verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+        verify(mRouterAdvertisementDaemon, times(1)).start();
+
+        sendIPv6TetherUpdates(upstreamState);
+        verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
+        verify(mNetd, times(1)).tetherApplyDnsInterfaces();
+    }
+
+
     @Test
     public void workingLocalOnlyHotspotEnrichedApBroadcastWithIfaceChanged() throws Exception {
         workingLocalOnlyHotspotEnrichedApBroadcast(true);
@@ -420,12 +590,12 @@
         // Emulate externally-visible WifiManager effects, causing the
         // per-interface state machine to start up, and telling us that
         // tethering mode is to be started.
-        mTethering.interfaceStatusChanged(mTestIfname, true);
+        mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
         sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
         mLooper.dispatchAll();
 
         verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
-        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+        verifyTetheringBroadcast(TEST_WLAN_IFNAME, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
         verifyNoMoreInteractions(mConnectivityManager);
         verifyNoMoreInteractions(mNMService);
         verifyNoMoreInteractions(mWifiManager);
@@ -448,30 +618,23 @@
         // Emulate externally-visible WifiManager effects, causing the
         // per-interface state machine to start up, and telling us that
         // tethering mode is to be started.
-        mTethering.interfaceStatusChanged(mTestIfname, true);
-        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, mTestIfname, IFACE_IP_MODE_TETHERED);
+        mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
+        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
         mLooper.dispatchAll();
 
         verifyInterfaceServingModeStarted();
-        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+        verifyTetheringBroadcast(TEST_WLAN_IFNAME, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
         verify(mNMService, times(1)).setIpForwardingEnabled(true);
         verify(mNMService, times(1)).startTethering(any(String[].class));
         verifyNoMoreInteractions(mNMService);
         verify(mWifiManager).updateInterfaceIpState(
-                mTestIfname, WifiManager.IFACE_IP_MODE_TETHERED);
+                TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
         verifyNoMoreInteractions(mWifiManager);
-        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_ACTIVE_TETHER);
-        // UpstreamNetworkMonitor will be started, and will register two callbacks:
-        // a "listen all" and a "track default".
-        verify(mConnectivityManager, times(1)).registerNetworkCallback(
-                any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
-        verify(mConnectivityManager, times(1)).registerDefaultNetworkCallback(
-                any(NetworkCallback.class), any(Handler.class));
+        verifyTetheringBroadcast(TEST_WLAN_IFNAME, ConnectivityManager.EXTRA_ACTIVE_TETHER);
+        verify(mUpstreamNetworkMonitor, times(1)).start();
         // In tethering mode, in the default configuration, an explicit request
         // for a mobile network is also made.
-        verify(mConnectivityManager, times(1)).requestNetwork(
-                any(NetworkRequest.class), any(NetworkCallback.class), eq(0), anyInt(),
-                any(Handler.class));
+        verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest();
         // TODO: Figure out why this isn't exactly once, for sendTetherStateChangedBroadcast().
         verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
         verifyNoMoreInteractions(mConnectivityManager);
@@ -494,14 +657,14 @@
         // Emulate externally-visible WifiManager effects, when tethering mode
         // is being torn down.
         sendWifiApStateChanged(WifiManager.WIFI_AP_STATE_DISABLED);
-        mTethering.interfaceRemoved(mTestIfname);
+        mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
         mLooper.dispatchAll();
 
-        verify(mNMService, times(1)).untetherInterface(mTestIfname);
+        verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME);
         // TODO: Why is {g,s}etInterfaceConfig() called more than once?
-        verify(mNMService, atLeastOnce()).getInterfaceConfig(mTestIfname);
+        verify(mNMService, atLeastOnce()).getInterfaceConfig(TEST_WLAN_IFNAME);
         verify(mNMService, atLeastOnce())
-                .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
+                .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
         verify(mNMService, times(1)).stopTethering();
         verify(mNMService, times(1)).setIpForwardingEnabled(false);
         verifyNoMoreInteractions(mNMService);
@@ -509,7 +672,7 @@
         // Asking for the last error after the per-interface state machine
         // has been reaped yields an unknown interface error.
         assertEquals(ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE,
-                mTethering.getLastTetherError(mTestIfname));
+                mTethering.getLastTetherError(TEST_WLAN_IFNAME));
     }
 
     // TODO: Test with and without interfaceStatusChanged().
@@ -530,21 +693,21 @@
         // Emulate externally-visible WifiManager effects, causing the
         // per-interface state machine to start up, and telling us that
         // tethering mode is to be started.
-        mTethering.interfaceStatusChanged(mTestIfname, true);
-        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, mTestIfname, IFACE_IP_MODE_TETHERED);
+        mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
+        sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
         mLooper.dispatchAll();
 
         // We verify get/set called twice here: once for setup and once during
         // teardown because all events happen over the course of the single
         // dispatchAll() above.
-        verify(mNMService, times(2)).getInterfaceConfig(mTestIfname);
+        verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME);
         verify(mNMService, times(2))
-                .setInterfaceConfig(eq(mTestIfname), any(InterfaceConfiguration.class));
-        verify(mNMService, times(1)).tetherInterface(mTestIfname);
+                .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
+        verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME);
         verify(mWifiManager).updateInterfaceIpState(
-                mTestIfname, WifiManager.IFACE_IP_MODE_TETHERED);
+                TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
         verify(mConnectivityManager, atLeastOnce()).isTetheringSupported();
-        verifyTetheringBroadcast(mTestIfname, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+        verifyTetheringBroadcast(TEST_WLAN_IFNAME, ConnectivityManager.EXTRA_AVAILABLE_TETHER);
         // This is called, but will throw.
         verify(mNMService, times(1)).setIpForwardingEnabled(true);
         // This never gets called because of the exception thrown above.
@@ -552,9 +715,9 @@
         // When the master state machine transitions to an error state it tells
         // downstream interfaces, which causes us to tell Wi-Fi about the error
         // so it can take down AP mode.
-        verify(mNMService, times(1)).untetherInterface(mTestIfname);
+        verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME);
         verify(mWifiManager).updateInterfaceIpState(
-                mTestIfname, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
+                TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
 
         verifyNoMoreInteractions(mWifiManager);
         verifyNoMoreInteractions(mConnectivityManager);
@@ -596,7 +759,7 @@
 
     @Test
     public void testDisallowTetheringWhenAtLeastOneTetheringInterfaceIsActive() throws Exception {
-        final String[] nonEmptyActiveIfacesList = new String[]{mTestIfname};
+        final String[] nonEmptyActiveIfacesList = new String[]{TEST_WLAN_IFNAME};
         final boolean currDisallow = false;
         final boolean nextDisallow = true;
         final int expectedInteractionsWithShowNotification = 1;
@@ -618,7 +781,7 @@
 
     @Test
     public void testAllowTetheringWhenAtLeastOneTetheringInterfaceIsActive() throws Exception {
-        final String[] nonEmptyActiveIfacesList = new String[]{mTestIfname};
+        final String[] nonEmptyActiveIfacesList = new String[]{TEST_WLAN_IFNAME};
         final boolean currDisallow = true;
         final boolean nextDisallow = false;
         final int expectedInteractionsWithShowNotification = 0;
@@ -629,7 +792,7 @@
 
     @Test
     public void testDisallowTetheringUnchanged() throws Exception {
-        final String[] nonEmptyActiveIfacesList = new String[]{mTestIfname};
+        final String[] nonEmptyActiveIfacesList = new String[]{TEST_WLAN_IFNAME};
         final int expectedInteractionsWithShowNotification = 0;
         boolean currDisallow = true;
         boolean nextDisallow = true;
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 db5373a..5f3fc54 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
@@ -31,7 +31,6 @@
 import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
 import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
 import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
 import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
 import static android.net.ConnectivityManager.TETHERING_USB;
 import static android.net.ConnectivityManager.TETHERING_WIFI;
@@ -39,7 +38,6 @@
 import static com.android.server.connectivity.tethering.IControlsTethering.STATE_TETHERED;
 import static com.android.server.connectivity.tethering.IControlsTethering.STATE_UNAVAILABLE;
 
-import android.net.ConnectivityManager;
 import android.net.INetworkStatsService;
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
@@ -75,6 +73,7 @@
     @Mock private IControlsTethering mTetherHelper;
     @Mock private InterfaceConfiguration mInterfaceConfiguration;
     @Mock private SharedLog mSharedLog;
+    @Mock private TetheringDependencies mTetheringDependencies;
 
     private final TestLooper mLooper = new TestLooper();
     private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor =
@@ -84,7 +83,7 @@
     private void initStateMachine(int interfaceType) throws Exception {
         mTestedSm = new TetherInterfaceStateMachine(
                 IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog,
-                mNMService, mStatsService, mTetherHelper);
+                mNMService, mStatsService, mTetherHelper, mTetheringDependencies);
         mTestedSm.start();
         // Starting the state machine always puts us in a consistent state and notifies
         // the rest of the world that we've changed from an unknown to available state.
@@ -111,7 +110,8 @@
     @Test
     public void startsOutAvailable() {
         mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(),
-                TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mTetherHelper);
+                TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mTetherHelper,
+                mTetheringDependencies);
         mTestedSm.start();
         mLooper.dispatchAll();
         verify(mTetherHelper).updateInterfaceState(
@@ -346,7 +346,7 @@
      * Send a command to the state machine under test, and run the event loop to idle.
      *
      * @param command One of the TetherInterfaceStateMachine.CMD_* constants.
-     * @param obj An additional argument to pass.
+     * @param arg1 An additional argument to pass.
      */
     private void dispatchCommand(int command, int arg1) {
         mTestedSm.sendMessage(command, arg1);
diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index c3b9def..9661dc2 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -147,6 +147,16 @@
     }
 
     @Test
+    public void testCallbacksRegistered() {
+        mUNM.start();
+        verify(mCM, times(1)).registerNetworkCallback(any(), any(), any());
+        verify(mCM, times(1)).registerDefaultNetworkCallback(any(), any());
+
+        mUNM.stop();
+        verify(mCM, times(2)).unregisterNetworkCallback(any(NetworkCallback.class));
+    }
+
+    @Test
     public void testRequestsMobileNetwork() throws Exception {
         assertFalse(mUNM.mobileNetworkRequested());
         assertEquals(0, mCM.requested.size());