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