From 5d0dc453e90554e739c5994a417e73a560edc547 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Thu, 15 Mar 2018 11:57:14 +0900 Subject: [PATCH] Add tests for IPv4 of dual stack tethering. Also refactoring some Tethering and TetherInterfaceStateMachine calls to address testability issues. This is in preparation of other work to have IPv6-only or 464xlat tethering working. Test: runtest frameworks-net Bug: 38218697 Bug: 64382985 Bug: 64976379 Bug: 64995262 Merged-In: I3b91125b1a715690c2cd417b1e937e568c755d9f Merged-In: I05de77d9b90d147bf1d6ee7f7ee19a049afddfa1 (cherry-pick of aosp I721aca4789ddfbee5a97316aae0b378d79ee2107) Change-Id: Idfdd1b9cd5419c1f51f0fbb1eba2f36a9c12474b --- .../server/connectivity/Tethering.java | 14 +- .../TetherInterfaceStateMachine.java | 15 +- .../tethering/TetheringDependencies.java | 31 ++ .../server/connectivity/TetheringTest.java | 305 ++++++++++++++---- .../TetherInterfaceStateMachineTest.java | 10 +- .../tethering/UpstreamNetworkMonitorTest.java | 10 + 6 files changed, 296 insertions(+), 89 deletions(-) diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index eee830f3299b4..6d3d0f6721c6e 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 @@ public class Tethering extends BaseNetworkObserver { 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 @@ public class Tethering extends BaseNetworkObserver { 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 @@ public class Tethering extends BaseNetworkObserver { 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 @@ public class Tethering extends BaseNetworkObserver { 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 @@ public class Tethering extends BaseNetworkObserver { addState(mSetDnsForwardersErrorState); mNotifyList = new ArrayList<>(); - mIPv6TetheringCoordinator = new IPv6TetheringCoordinator(mNotifyList, mLog); + mIPv6TetheringCoordinator = deps.getIPv6TetheringCoordinator(mNotifyList, mLog); mOffload = new OffloadWrapper(); setInitialState(mInitialState); @@ -1997,7 +1999,7 @@ public class Tethering extends BaseNetworkObserver { 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 2224913b2cf68..e4c7ca0769d82 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 @@ public class TetherInterfaceStateMachine extends StateMachine { 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 class TetherInterfaceStateMachine extends StateMachine { 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 @@ public class TetherInterfaceStateMachine extends StateMachine { } 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 b8174b6c8d2bc..66afb0f2cb145 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 class TetheringDependencies { 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 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 e692652c7ea42..ed20c7a988727 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_INTERFACE_NAME; 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.Matchers.eq; 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 static org.mockito.Mockito.mock; 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.support.test.runner.AndroidJUnit4; 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.junit.runner.RunWith; 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 @@ public class TetheringTest { @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 mIntents; private BroadcastInterceptingContext mServiceContext; @@ -146,23 +181,104 @@ public class TetheringTest { } } + public class MockTetheringDependencies extends TetheringDependencies { + private StateMachine upstreamNetworkMonitorMasterSM; + private ArrayList 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 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 @@ public class TetheringTest { }; 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 @@ public class TetheringTest { } 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 @@ public class TetheringTest { // 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 @@ public class TetheringTest { // 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 @@ public class TetheringTest { // 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 @@ public class TetheringTest { // 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 @@ public class TetheringTest { // 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 @@ public class TetheringTest { // 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 @@ public class TetheringTest { // 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 @@ public class TetheringTest { // 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 @@ public class TetheringTest { // 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 @@ public class TetheringTest { // 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 @@ public class TetheringTest { // 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 @@ public class TetheringTest { // 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 @@ public class TetheringTest { @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 @@ public class TetheringTest { @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 @@ public class TetheringTest { @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 db5373ac34b2c..5f3fc54379a76 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 org.mockito.Mockito.when; 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 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 @@ public class TetherInterfaceStateMachineTest { @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 mLinkPropertiesCaptor = @@ -84,7 +83,7 @@ public class TetherInterfaceStateMachineTest { 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 @@ public class TetherInterfaceStateMachineTest { @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 @@ public class TetherInterfaceStateMachineTest { * 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 c3b9defdec4e6..9661dc24ca2ea 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java @@ -146,6 +146,16 @@ public class UpstreamNetworkMonitorTest { assertTrue(mCM.hasNoCallbacks()); } + @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());