TetherInterfaceStateMachineTest.java revision 5d0dc453e90554e739c5994a417e73a560edc547
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.connectivity.tethering; 18 19import static org.junit.Assert.assertFalse; 20import static org.junit.Assert.assertTrue; 21import static org.mockito.Matchers.any; 22import static org.mockito.Matchers.anyString; 23import static org.mockito.Matchers.eq; 24import static org.mockito.Mockito.doThrow; 25import static org.mockito.Mockito.inOrder; 26import static org.mockito.Mockito.reset; 27import static org.mockito.Mockito.verify; 28import static org.mockito.Mockito.verifyNoMoreInteractions; 29import static org.mockito.Mockito.when; 30 31import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR; 32import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; 33import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR; 34import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; 35import static android.net.ConnectivityManager.TETHERING_USB; 36import static android.net.ConnectivityManager.TETHERING_WIFI; 37import static com.android.server.connectivity.tethering.IControlsTethering.STATE_AVAILABLE; 38import static com.android.server.connectivity.tethering.IControlsTethering.STATE_TETHERED; 39import static com.android.server.connectivity.tethering.IControlsTethering.STATE_UNAVAILABLE; 40 41import android.net.INetworkStatsService; 42import android.net.InterfaceConfiguration; 43import android.net.LinkAddress; 44import android.net.LinkProperties; 45import android.net.RouteInfo; 46import android.net.util.SharedLog; 47import android.os.INetworkManagementService; 48import android.os.RemoteException; 49import android.os.test.TestLooper; 50import android.support.test.filters.SmallTest; 51import android.support.test.runner.AndroidJUnit4; 52import android.text.TextUtils; 53 54import java.net.Inet4Address; 55 56import org.junit.Before; 57import org.junit.Test; 58import org.junit.runner.RunWith; 59import org.mockito.ArgumentCaptor; 60import org.mockito.InOrder; 61import org.mockito.Mock; 62import org.mockito.MockitoAnnotations; 63 64@RunWith(AndroidJUnit4.class) 65@SmallTest 66public class TetherInterfaceStateMachineTest { 67 private static final String IFACE_NAME = "testnet1"; 68 private static final String UPSTREAM_IFACE = "upstream0"; 69 private static final String UPSTREAM_IFACE2 = "upstream1"; 70 71 @Mock private INetworkManagementService mNMService; 72 @Mock private INetworkStatsService mStatsService; 73 @Mock private IControlsTethering mTetherHelper; 74 @Mock private InterfaceConfiguration mInterfaceConfiguration; 75 @Mock private SharedLog mSharedLog; 76 @Mock private TetheringDependencies mTetheringDependencies; 77 78 private final TestLooper mLooper = new TestLooper(); 79 private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor = 80 ArgumentCaptor.forClass(LinkProperties.class); 81 private TetherInterfaceStateMachine mTestedSm; 82 83 private void initStateMachine(int interfaceType) throws Exception { 84 mTestedSm = new TetherInterfaceStateMachine( 85 IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, 86 mNMService, mStatsService, mTetherHelper, mTetheringDependencies); 87 mTestedSm.start(); 88 // Starting the state machine always puts us in a consistent state and notifies 89 // the rest of the world that we've changed from an unknown to available state. 90 mLooper.dispatchAll(); 91 reset(mNMService, mStatsService, mTetherHelper); 92 when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); 93 } 94 95 private void initTetheredStateMachine(int interfaceType, String upstreamIface) throws Exception { 96 initStateMachine(interfaceType); 97 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED); 98 if (upstreamIface != null) { 99 dispatchTetherConnectionChanged(upstreamIface); 100 } 101 reset(mNMService, mStatsService, mTetherHelper); 102 when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); 103 } 104 105 @Before public void setUp() throws Exception { 106 MockitoAnnotations.initMocks(this); 107 when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog); 108 } 109 110 @Test 111 public void startsOutAvailable() { 112 mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), 113 TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mTetherHelper, 114 mTetheringDependencies); 115 mTestedSm.start(); 116 mLooper.dispatchAll(); 117 verify(mTetherHelper).updateInterfaceState( 118 mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); 119 verify(mTetherHelper).updateLinkProperties(eq(mTestedSm), any(LinkProperties.class)); 120 verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService); 121 } 122 123 @Test 124 public void shouldDoNothingUntilRequested() throws Exception { 125 initStateMachine(TETHERING_BLUETOOTH); 126 final int [] NOOP_COMMANDS = { 127 TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED, 128 TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR, 129 TetherInterfaceStateMachine.CMD_IP_FORWARDING_DISABLE_ERROR, 130 TetherInterfaceStateMachine.CMD_START_TETHERING_ERROR, 131 TetherInterfaceStateMachine.CMD_STOP_TETHERING_ERROR, 132 TetherInterfaceStateMachine.CMD_SET_DNS_FORWARDERS_ERROR, 133 TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED 134 }; 135 for (int command : NOOP_COMMANDS) { 136 // None of these commands should trigger us to request action from 137 // the rest of the system. 138 dispatchCommand(command); 139 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 140 } 141 } 142 143 @Test 144 public void handlesImmediateInterfaceDown() throws Exception { 145 initStateMachine(TETHERING_BLUETOOTH); 146 147 dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); 148 verify(mTetherHelper).updateInterfaceState( 149 mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR); 150 verify(mTetherHelper).updateLinkProperties(eq(mTestedSm), any(LinkProperties.class)); 151 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 152 } 153 154 @Test 155 public void canBeTethered() throws Exception { 156 initStateMachine(TETHERING_BLUETOOTH); 157 158 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED); 159 InOrder inOrder = inOrder(mTetherHelper, mNMService); 160 inOrder.verify(mNMService).tetherInterface(IFACE_NAME); 161 inOrder.verify(mTetherHelper).updateInterfaceState( 162 mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR); 163 inOrder.verify(mTetherHelper).updateLinkProperties( 164 eq(mTestedSm), any(LinkProperties.class)); 165 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 166 } 167 168 @Test 169 public void canUnrequestTethering() throws Exception { 170 initTetheredStateMachine(TETHERING_BLUETOOTH, null); 171 172 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); 173 InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); 174 inOrder.verify(mNMService).untetherInterface(IFACE_NAME); 175 inOrder.verify(mTetherHelper).updateInterfaceState( 176 mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); 177 inOrder.verify(mTetherHelper).updateLinkProperties( 178 eq(mTestedSm), any(LinkProperties.class)); 179 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 180 } 181 182 @Test 183 public void canBeTetheredAsUsb() throws Exception { 184 initStateMachine(TETHERING_USB); 185 186 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED); 187 InOrder inOrder = inOrder(mTetherHelper, mNMService); 188 inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME); 189 inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); 190 inOrder.verify(mNMService).tetherInterface(IFACE_NAME); 191 inOrder.verify(mTetherHelper).updateInterfaceState( 192 mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR); 193 inOrder.verify(mTetherHelper).updateLinkProperties( 194 eq(mTestedSm), mLinkPropertiesCaptor.capture()); 195 assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue()); 196 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 197 } 198 199 @Test 200 public void handlesFirstUpstreamChange() throws Exception { 201 initTetheredStateMachine(TETHERING_BLUETOOTH, null); 202 203 // Telling the state machine about its upstream interface triggers 204 // a little more configuration. 205 dispatchTetherConnectionChanged(UPSTREAM_IFACE); 206 InOrder inOrder = inOrder(mNMService); 207 inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE); 208 inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); 209 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 210 } 211 212 @Test 213 public void handlesChangingUpstream() throws Exception { 214 initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE); 215 216 dispatchTetherConnectionChanged(UPSTREAM_IFACE2); 217 InOrder inOrder = inOrder(mNMService, mStatsService); 218 inOrder.verify(mStatsService).forceUpdate(); 219 inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); 220 inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); 221 inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); 222 inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); 223 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 224 } 225 226 @Test 227 public void handlesChangingUpstreamNatFailure() throws Exception { 228 initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); 229 230 doThrow(RemoteException.class).when(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); 231 232 dispatchTetherConnectionChanged(UPSTREAM_IFACE2); 233 InOrder inOrder = inOrder(mNMService, mStatsService); 234 inOrder.verify(mStatsService).forceUpdate(); 235 inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); 236 inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); 237 inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); 238 inOrder.verify(mStatsService).forceUpdate(); 239 inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); 240 inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2); 241 } 242 243 @Test 244 public void handlesChangingUpstreamInterfaceForwardingFailure() throws Exception { 245 initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); 246 247 doThrow(RemoteException.class).when(mNMService).startInterfaceForwarding( 248 IFACE_NAME, UPSTREAM_IFACE2); 249 250 dispatchTetherConnectionChanged(UPSTREAM_IFACE2); 251 InOrder inOrder = inOrder(mNMService, mStatsService); 252 inOrder.verify(mStatsService).forceUpdate(); 253 inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); 254 inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); 255 inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); 256 inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); 257 inOrder.verify(mStatsService).forceUpdate(); 258 inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); 259 inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2); 260 } 261 262 @Test 263 public void canUnrequestTetheringWithUpstream() throws Exception { 264 initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE); 265 266 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); 267 InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); 268 inOrder.verify(mStatsService).forceUpdate(); 269 inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); 270 inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); 271 inOrder.verify(mNMService).untetherInterface(IFACE_NAME); 272 inOrder.verify(mTetherHelper).updateInterfaceState( 273 mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR); 274 inOrder.verify(mTetherHelper).updateLinkProperties( 275 eq(mTestedSm), any(LinkProperties.class)); 276 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 277 } 278 279 @Test 280 public void interfaceDownLeadsToUnavailable() throws Exception { 281 for (boolean shouldThrow : new boolean[]{true, false}) { 282 initTetheredStateMachine(TETHERING_USB, null); 283 284 if (shouldThrow) { 285 doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME); 286 } 287 dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); 288 InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper); 289 usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); 290 usbTeardownOrder.verify(mNMService).setInterfaceConfig( 291 IFACE_NAME, mInterfaceConfiguration); 292 usbTeardownOrder.verify(mTetherHelper).updateInterfaceState( 293 mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR); 294 usbTeardownOrder.verify(mTetherHelper).updateLinkProperties( 295 eq(mTestedSm), mLinkPropertiesCaptor.capture()); 296 assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); 297 } 298 } 299 300 @Test 301 public void usbShouldBeTornDownOnTetherError() throws Exception { 302 initStateMachine(TETHERING_USB); 303 304 doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME); 305 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED); 306 InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper); 307 usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); 308 usbTeardownOrder.verify(mNMService).setInterfaceConfig( 309 IFACE_NAME, mInterfaceConfiguration); 310 usbTeardownOrder.verify(mTetherHelper).updateInterfaceState( 311 mTestedSm, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR); 312 usbTeardownOrder.verify(mTetherHelper).updateLinkProperties( 313 eq(mTestedSm), mLinkPropertiesCaptor.capture()); 314 assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); 315 } 316 317 @Test 318 public void shouldTearDownUsbOnUpstreamError() throws Exception { 319 initTetheredStateMachine(TETHERING_USB, null); 320 321 doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString()); 322 dispatchTetherConnectionChanged(UPSTREAM_IFACE); 323 InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper); 324 usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); 325 usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); 326 usbTeardownOrder.verify(mTetherHelper).updateInterfaceState( 327 mTestedSm, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR); 328 usbTeardownOrder.verify(mTetherHelper).updateLinkProperties( 329 eq(mTestedSm), mLinkPropertiesCaptor.capture()); 330 assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue()); 331 } 332 333 @Test 334 public void ignoresDuplicateUpstreamNotifications() throws Exception { 335 initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE); 336 337 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 338 339 for (int i = 0; i < 5; i++) { 340 dispatchTetherConnectionChanged(UPSTREAM_IFACE); 341 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); 342 } 343 } 344 345 /** 346 * Send a command to the state machine under test, and run the event loop to idle. 347 * 348 * @param command One of the TetherInterfaceStateMachine.CMD_* constants. 349 * @param arg1 An additional argument to pass. 350 */ 351 private void dispatchCommand(int command, int arg1) { 352 mTestedSm.sendMessage(command, arg1); 353 mLooper.dispatchAll(); 354 } 355 356 /** 357 * Send a command to the state machine under test, and run the event loop to idle. 358 * 359 * @param command One of the TetherInterfaceStateMachine.CMD_* constants. 360 */ 361 private void dispatchCommand(int command) { 362 mTestedSm.sendMessage(command); 363 mLooper.dispatchAll(); 364 } 365 366 /** 367 * Special override to tell the state machine that the upstream interface has changed. 368 * 369 * @see #dispatchCommand(int) 370 * @param upstreamIface String name of upstream interface (or null) 371 */ 372 private void dispatchTetherConnectionChanged(String upstreamIface) { 373 mTestedSm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, 374 upstreamIface); 375 mLooper.dispatchAll(); 376 } 377 378 private void assertIPv4AddressAndDirectlyConnectedRoute(LinkProperties lp) { 379 // Find the first IPv4 LinkAddress. 380 LinkAddress addr4 = null; 381 for (LinkAddress addr : lp.getLinkAddresses()) { 382 if (!(addr.getAddress() instanceof Inet4Address)) continue; 383 addr4 = addr; 384 break; 385 } 386 assertTrue("missing IPv4 address", addr4 != null); 387 388 // Assert the presence of the associated directly connected route. 389 final RouteInfo directlyConnected = new RouteInfo(addr4, null, lp.getInterfaceName()); 390 assertTrue("missing directly connected route: '" + directlyConnected.toString() + "'", 391 lp.getRoutes().contains(directlyConnected)); 392 } 393 394 private void assertNoAddressesNorRoutes(LinkProperties lp) { 395 assertTrue(lp.getLinkAddresses().isEmpty()); 396 assertTrue(lp.getRoutes().isEmpty()); 397 // We also check that interface name is non-empty, because we should 398 // never see an empty interface name in any LinkProperties update. 399 assertFalse(TextUtils.isEmpty(lp.getInterfaceName())); 400 } 401} 402