blob: 441084696b268366330356ddf33e47670e6491e2 [file] [log] [blame]
Christopher Wiley08725a82016-05-18 16:32:44 -07001/*
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
Christopher Wiley279eca32016-05-20 13:23:10 -070019import static org.junit.Assert.assertFalse;
20import static org.junit.Assert.assertTrue;
Christopher Wileyf972edc2016-05-23 16:17:30 -070021import static org.mockito.Matchers.anyString;
22import static org.mockito.Mockito.doThrow;
Christopher Wiley279eca32016-05-20 13:23:10 -070023import static org.mockito.Mockito.inOrder;
24import static org.mockito.Mockito.reset;
25import static org.mockito.Mockito.verify;
Christopher Wiley08725a82016-05-18 16:32:44 -070026import static org.mockito.Mockito.verifyNoMoreInteractions;
Christopher Wiley279eca32016-05-20 13:23:10 -070027import static org.mockito.Mockito.when;
Christopher Wiley08725a82016-05-18 16:32:44 -070028
29import android.net.INetworkStatsService;
Christopher Wiley279eca32016-05-20 13:23:10 -070030import android.net.InterfaceConfiguration;
Christopher Wiley08725a82016-05-18 16:32:44 -070031import android.os.INetworkManagementService;
Christopher Wiley279eca32016-05-20 13:23:10 -070032import android.os.RemoteException;
Christopher Wiley08725a82016-05-18 16:32:44 -070033import android.os.test.TestLooper;
Christopher Wiley9bc0df22016-05-25 13:57:27 -070034import android.support.test.filters.SmallTest;
35import android.support.test.runner.AndroidJUnit4;
Christopher Wiley08725a82016-05-18 16:32:44 -070036
37import org.junit.Before;
38import org.junit.Test;
Christopher Wiley9bc0df22016-05-25 13:57:27 -070039import org.junit.runner.RunWith;
Christopher Wiley279eca32016-05-20 13:23:10 -070040import org.mockito.InOrder;
Christopher Wiley08725a82016-05-18 16:32:44 -070041import org.mockito.Mock;
42import org.mockito.MockitoAnnotations;
43
Christopher Wiley9bc0df22016-05-25 13:57:27 -070044@RunWith(AndroidJUnit4.class)
45@SmallTest
Mitchell Wills4622c2d2016-05-23 16:40:10 -070046public class TetherInterfaceStateMachineTest {
Christopher Wiley08725a82016-05-18 16:32:44 -070047 private static final String IFACE_NAME = "testnet1";
Christopher Wiley279eca32016-05-20 13:23:10 -070048 private static final String UPSTREAM_IFACE = "upstream0";
49 private static final String UPSTREAM_IFACE2 = "upstream1";
Christopher Wiley08725a82016-05-18 16:32:44 -070050
51 @Mock private INetworkManagementService mNMService;
52 @Mock private INetworkStatsService mStatsService;
53 @Mock private IControlsTethering mTetherHelper;
Christopher Wiley279eca32016-05-20 13:23:10 -070054 @Mock private InterfaceConfiguration mInterfaceConfiguration;
Christopher Wiley08725a82016-05-18 16:32:44 -070055
56 private final TestLooper mLooper = new TestLooper();
Mitchell Wills4622c2d2016-05-23 16:40:10 -070057 private TetherInterfaceStateMachine mTestedSm;
Christopher Wiley08725a82016-05-18 16:32:44 -070058
Christopher Wileyf972edc2016-05-23 16:17:30 -070059 private void initStateMachine(boolean isUsb) throws Exception {
Christopher Wileyafc46a72016-05-23 13:58:00 -070060 mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), isUsb,
Christopher Wiley279eca32016-05-20 13:23:10 -070061 mNMService, mStatsService, mTetherHelper);
62 mTestedSm.start();
63 // Starting the state machine always puts us in a consistent state and notifies
64 // the test of the world that we've changed from an unknown to available state.
65 mLooper.dispatchAll();
66 reset(mNMService, mStatsService, mTetherHelper);
Christopher Wileyf972edc2016-05-23 16:17:30 -070067 when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
Christopher Wiley279eca32016-05-20 13:23:10 -070068 }
69
Christopher Wileyf972edc2016-05-23 16:17:30 -070070 private void initTetheredStateMachine(boolean isUsb, String upstreamIface) throws Exception {
Christopher Wiley279eca32016-05-20 13:23:10 -070071 initStateMachine(isUsb);
Mitchell Wills4622c2d2016-05-23 16:40:10 -070072 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
Christopher Wiley279eca32016-05-20 13:23:10 -070073 if (upstreamIface != null) {
74 dispatchTetherConnectionChanged(upstreamIface);
75 }
76 reset(mNMService, mStatsService, mTetherHelper);
Christopher Wileyf972edc2016-05-23 16:17:30 -070077 when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
Christopher Wiley279eca32016-05-20 13:23:10 -070078 }
79
Christopher Wiley08725a82016-05-18 16:32:44 -070080 @Before
81 public void setUp() throws Exception {
82 MockitoAnnotations.initMocks(this);
Christopher Wiley279eca32016-05-20 13:23:10 -070083 }
84
85 @Test
86 public void startsOutAvailable() {
Christopher Wileyafc46a72016-05-23 13:58:00 -070087 mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), false,
Christopher Wiley08725a82016-05-18 16:32:44 -070088 mNMService, mStatsService, mTetherHelper);
89 mTestedSm.start();
Christopher Wiley279eca32016-05-20 13:23:10 -070090 mLooper.dispatchAll();
91 assertTrue("Should start out available for tethering", mTestedSm.isAvailable());
92 assertFalse("Should not be tethered initially", mTestedSm.isTethered());
93 assertFalse("Should have no errors initially", mTestedSm.isErrored());
94 verify(mTetherHelper).sendTetherStateChangedBroadcast();
95 verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService);
Christopher Wiley08725a82016-05-18 16:32:44 -070096 }
97
98 @Test
Christopher Wileyf972edc2016-05-23 16:17:30 -070099 public void shouldDoNothingUntilRequested() throws Exception {
Christopher Wiley279eca32016-05-20 13:23:10 -0700100 initStateMachine(false);
Christopher Wiley08725a82016-05-18 16:32:44 -0700101 final int [] NOOP_COMMANDS = {
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700102 TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED,
103 TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR,
104 TetherInterfaceStateMachine.CMD_IP_FORWARDING_DISABLE_ERROR,
105 TetherInterfaceStateMachine.CMD_START_TETHERING_ERROR,
106 TetherInterfaceStateMachine.CMD_STOP_TETHERING_ERROR,
107 TetherInterfaceStateMachine.CMD_SET_DNS_FORWARDERS_ERROR,
108 TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED
Christopher Wiley08725a82016-05-18 16:32:44 -0700109 };
110 for (int command : NOOP_COMMANDS) {
Christopher Wiley279eca32016-05-20 13:23:10 -0700111 // None of these commands should trigger us to request action from
Christopher Wiley08725a82016-05-18 16:32:44 -0700112 // the rest of the system.
Christopher Wiley279eca32016-05-20 13:23:10 -0700113 dispatchCommand(command);
114 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
Christopher Wiley08725a82016-05-18 16:32:44 -0700115 }
116 }
117
Christopher Wiley279eca32016-05-20 13:23:10 -0700118 @Test
Christopher Wileyf972edc2016-05-23 16:17:30 -0700119 public void handlesImmediateInterfaceDown() throws Exception {
Christopher Wiley279eca32016-05-20 13:23:10 -0700120 initStateMachine(false);
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700121 dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
Christopher Wiley279eca32016-05-20 13:23:10 -0700122 verify(mTetherHelper).sendTetherStateChangedBroadcast();
123 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
124 assertFalse("Should not be tetherable when the interface is down", mTestedSm.isAvailable());
125 assertFalse("Should not be tethered when the interface is down", mTestedSm.isTethered());
126 assertFalse("Should have no errors when the interface goes immediately down",
127 mTestedSm.isErrored());
128 }
129
130 @Test
Christopher Wileyf972edc2016-05-23 16:17:30 -0700131 public void canBeTethered() throws Exception {
Christopher Wiley279eca32016-05-20 13:23:10 -0700132 initStateMachine(false);
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700133 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
Christopher Wiley279eca32016-05-20 13:23:10 -0700134 InOrder inOrder = inOrder(mTetherHelper, mNMService);
135 inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm);
Christopher Wiley279eca32016-05-20 13:23:10 -0700136 inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
137 inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
138
139 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
140 assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
141 assertTrue("Should be in a tethered state", mTestedSm.isTethered());
142 assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
143 }
144
145 @Test
146 public void canUnrequestTethering() throws Exception {
147 initTetheredStateMachine(false, null);
148
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700149 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
Christopher Wiley279eca32016-05-20 13:23:10 -0700150 InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
Christopher Wiley279eca32016-05-20 13:23:10 -0700151 inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
Christopher Wileyf972edc2016-05-23 16:17:30 -0700152 inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
Christopher Wiley279eca32016-05-20 13:23:10 -0700153 inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
154 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
155 assertTrue("Should be ready for tethering again", mTestedSm.isAvailable());
156 assertFalse("Should not be tethered", mTestedSm.isTethered());
157 assertFalse("Should have no errors", mTestedSm.isErrored());
158 }
159
160 @Test
Christopher Wileyf972edc2016-05-23 16:17:30 -0700161 public void canBeTetheredAsUsb() throws Exception {
Christopher Wiley279eca32016-05-20 13:23:10 -0700162 initStateMachine(true);
163
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700164 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
Christopher Wiley279eca32016-05-20 13:23:10 -0700165 InOrder inOrder = inOrder(mTetherHelper, mNMService);
166 inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm);
167 inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
168 inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
Christopher Wiley279eca32016-05-20 13:23:10 -0700169 inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
170 inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
171
172 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
173 assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
174 assertTrue("Should be in a tethered state", mTestedSm.isTethered());
175 assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
176 }
177
178 @Test
179 public void handlesFirstUpstreamChange() throws Exception {
180 initTetheredStateMachine(false, null);
181
182 // Telling the state machine about its upstream interface triggers a little more configuration.
183 dispatchTetherConnectionChanged(UPSTREAM_IFACE);
184 InOrder inOrder = inOrder(mNMService);
185 inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE);
186 inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
187 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
188 assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
189 assertTrue("Should be in a tethered state", mTestedSm.isTethered());
190 assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
191 }
192
193 @Test
194 public void handlesChangingUpstream() throws Exception {
195 initTetheredStateMachine(false, UPSTREAM_IFACE);
196
197 dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
198 InOrder inOrder = inOrder(mNMService, mStatsService);
199 inOrder.verify(mStatsService).forceUpdate();
200 inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
201 inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
202 inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
203 inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
204 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
205 assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable());
206 assertTrue("Should be in a tethered state", mTestedSm.isTethered());
207 assertFalse("Should have no errors when tethered", mTestedSm.isErrored());
208 }
209
210 @Test
211 public void canUnrequestTetheringWithUpstream() throws Exception {
212 initTetheredStateMachine(false, UPSTREAM_IFACE);
213
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700214 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
Christopher Wiley279eca32016-05-20 13:23:10 -0700215 InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
Christopher Wileyf972edc2016-05-23 16:17:30 -0700216 inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
Christopher Wiley279eca32016-05-20 13:23:10 -0700217 inOrder.verify(mStatsService).forceUpdate();
218 inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
219 inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
220 inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
Christopher Wiley279eca32016-05-20 13:23:10 -0700221 inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast();
222 verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
223 assertTrue("Should be ready for tethering again", mTestedSm.isAvailable());
224 assertFalse("Should not be tethered", mTestedSm.isTethered());
225 assertFalse("Should have no errors", mTestedSm.isErrored());
226 }
227
Christopher Wileyf972edc2016-05-23 16:17:30 -0700228 @Test
229 public void interfaceDownLeadsToUnavailable() throws Exception {
230 for (boolean shouldThrow : new boolean[]{true, false}) {
231 initTetheredStateMachine(true, null);
232
233 if (shouldThrow) {
234 doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME);
235 }
236 dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
237 InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration);
238 usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
239 usbTeardownOrder.verify(mNMService).setInterfaceConfig(
240 IFACE_NAME, mInterfaceConfiguration);
241 verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
242 assertFalse("Should not be available", mTestedSm.isAvailable());
243 assertFalse("Should not be tethered", mTestedSm.isTethered());
244 }
245 }
246
247 @Test
248 public void usbShouldBeTornDownOnTetherError() throws Exception {
249 initStateMachine(true);
250
251 doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
252 dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
253 InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration);
254 usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
255 usbTeardownOrder.verify(mNMService).setInterfaceConfig(
256 IFACE_NAME, mInterfaceConfiguration);
257 // Initial call is when we transition to the tethered state on request.
258 verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm);
259 // And this call is to notify that we really aren't requested tethering.
260 verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
261 assertTrue("Expected to see an error reported", mTestedSm.isErrored());
262 }
263
264 @Test
265 public void shouldTearDownUsbOnUpstreamError() throws Exception {
266 initTetheredStateMachine(true, null);
267
268 doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString());
269 dispatchTetherConnectionChanged(UPSTREAM_IFACE);
270 InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration);
271 usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
272 usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
273 verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm);
274 }
Christopher Wiley279eca32016-05-20 13:23:10 -0700275
276 /**
277 * Send a command to the state machine under test, and run the event loop to idle.
278 *
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700279 * @param command One of the TetherInterfaceStateMachine.CMD_* constants.
Christopher Wiley279eca32016-05-20 13:23:10 -0700280 */
281 private void dispatchCommand(int command) {
282 mTestedSm.sendMessage(command);
283 mLooper.dispatchAll();
284 }
285
286 /**
287 * Special override to tell the state machine that the upstream interface has changed.
288 *
289 * @see #dispatchCommand(int)
290 * @param upstreamIface String name of upstream interface (or null)
291 */
292 private void dispatchTetherConnectionChanged(String upstreamIface) {
Mitchell Wills4622c2d2016-05-23 16:40:10 -0700293 mTestedSm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
294 upstreamIface);
Christopher Wiley279eca32016-05-20 13:23:10 -0700295 mLooper.dispatchAll();
Christopher Wiley08725a82016-05-18 16:32:44 -0700296 }
Christopher Wiley9bc0df22016-05-25 13:57:27 -0700297}