TetherInterfaceStateMachineTest.java revision dd4d5820344205085c19dc142aec252ea9ac220b
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.TETHER_ERROR_UNTETHER_IFACE_ERROR;
35import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
36import static android.net.ConnectivityManager.TETHERING_USB;
37import static android.net.ConnectivityManager.TETHERING_WIFI;
38import static com.android.server.connectivity.tethering.IControlsTethering.STATE_AVAILABLE;
39import static com.android.server.connectivity.tethering.IControlsTethering.STATE_TETHERED;
40import static com.android.server.connectivity.tethering.IControlsTethering.STATE_UNAVAILABLE;
41
42import android.net.ConnectivityManager;
43import android.net.INetworkStatsService;
44import android.net.InterfaceConfiguration;
45import android.net.LinkAddress;
46import android.net.LinkProperties;
47import android.net.RouteInfo;
48import android.net.util.SharedLog;
49import android.os.INetworkManagementService;
50import android.os.RemoteException;
51import android.os.test.TestLooper;
52import android.support.test.filters.SmallTest;
53import android.support.test.runner.AndroidJUnit4;
54import android.text.TextUtils;
55
56import java.net.Inet4Address;
57
58import org.junit.Before;
59import org.junit.Test;
60import org.junit.runner.RunWith;
61import org.mockito.ArgumentCaptor;
62import org.mockito.InOrder;
63import org.mockito.Mock;
64import org.mockito.MockitoAnnotations;
65
66@RunWith(AndroidJUnit4.class)
67@SmallTest
68public class TetherInterfaceStateMachineTest {
69    private static final String IFACE_NAME = "testnet1";
70    private static final String UPSTREAM_IFACE = "upstream0";
71    private static final String UPSTREAM_IFACE2 = "upstream1";
72
73    @Mock private INetworkManagementService mNMService;
74    @Mock private INetworkStatsService mStatsService;
75    @Mock private IControlsTethering mTetherHelper;
76    @Mock private InterfaceConfiguration mInterfaceConfiguration;
77    @Mock private SharedLog mSharedLog;
78
79    private final TestLooper mLooper = new TestLooper();
80    private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor =
81            ArgumentCaptor.forClass(LinkProperties.class);
82    private TetherInterfaceStateMachine mTestedSm;
83
84    private void initStateMachine(int interfaceType) throws Exception {
85        mTestedSm = new TetherInterfaceStateMachine(
86                IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog,
87                mNMService, mStatsService, mTetherHelper);
88        mTestedSm.start();
89        // Starting the state machine always puts us in a consistent state and notifies
90        // the rest of the world that we've changed from an unknown to available state.
91        mLooper.dispatchAll();
92        reset(mNMService, mStatsService, mTetherHelper);
93        when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
94    }
95
96    private void initTetheredStateMachine(int interfaceType, String upstreamIface) throws Exception {
97        initStateMachine(interfaceType);
98        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED, STATE_TETHERED);
99        if (upstreamIface != null) {
100            dispatchTetherConnectionChanged(upstreamIface);
101        }
102        reset(mNMService, mStatsService, mTetherHelper);
103        when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
104    }
105
106    @Before public void setUp() throws Exception {
107        MockitoAnnotations.initMocks(this);
108        when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog);
109    }
110
111    @Test
112    public void startsOutAvailable() {
113        mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(),
114                TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mTetherHelper);
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 obj 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