• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.net.ip;
18 
19 import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
20 import static android.net.ConnectivityManager.TETHERING_USB;
21 import static android.net.ConnectivityManager.TETHERING_WIFI;
22 import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
23 import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
24 import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
25 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
26 import static android.net.ip.IpServer.STATE_AVAILABLE;
27 import static android.net.ip.IpServer.STATE_TETHERED;
28 import static android.net.ip.IpServer.STATE_UNAVAILABLE;
29 import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
30 
31 import static org.junit.Assert.assertEquals;
32 import static org.junit.Assert.assertFalse;
33 import static org.junit.Assert.assertNotNull;
34 import static org.junit.Assert.assertTrue;
35 import static org.junit.Assert.fail;
36 import static org.mockito.ArgumentMatchers.argThat;
37 import static org.mockito.Matchers.any;
38 import static org.mockito.Matchers.anyString;
39 import static org.mockito.Matchers.eq;
40 import static org.mockito.Mockito.doAnswer;
41 import static org.mockito.Mockito.doThrow;
42 import static org.mockito.Mockito.inOrder;
43 import static org.mockito.Mockito.never;
44 import static org.mockito.Mockito.reset;
45 import static org.mockito.Mockito.timeout;
46 import static org.mockito.Mockito.times;
47 import static org.mockito.Mockito.verify;
48 import static org.mockito.Mockito.verifyNoMoreInteractions;
49 import static org.mockito.Mockito.when;
50 
51 import android.net.INetd;
52 import android.net.INetworkStatsService;
53 import android.net.InterfaceConfiguration;
54 import android.net.IpPrefix;
55 import android.net.LinkAddress;
56 import android.net.LinkProperties;
57 import android.net.MacAddress;
58 import android.net.RouteInfo;
59 import android.net.dhcp.DhcpServingParamsParcel;
60 import android.net.dhcp.IDhcpServer;
61 import android.net.dhcp.IDhcpServerCallbacks;
62 import android.net.util.InterfaceParams;
63 import android.net.util.InterfaceSet;
64 import android.net.util.SharedLog;
65 import android.os.INetworkManagementService;
66 import android.os.RemoteException;
67 import android.os.test.TestLooper;
68 import android.text.TextUtils;
69 
70 import androidx.test.filters.SmallTest;
71 import androidx.test.runner.AndroidJUnit4;
72 
73 import org.junit.Before;
74 import org.junit.Test;
75 import org.junit.runner.RunWith;
76 import org.mockito.ArgumentCaptor;
77 import org.mockito.Captor;
78 import org.mockito.InOrder;
79 import org.mockito.Mock;
80 import org.mockito.MockitoAnnotations;
81 
82 import java.net.Inet4Address;
83 
84 @RunWith(AndroidJUnit4.class)
85 @SmallTest
86 public class IpServerTest {
87     private static final String IFACE_NAME = "testnet1";
88     private static final String UPSTREAM_IFACE = "upstream0";
89     private static final String UPSTREAM_IFACE2 = "upstream1";
90     private static final int DHCP_LEASE_TIME_SECS = 3600;
91 
92     private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams(
93             IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */);
94 
95     private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000;
96 
97     @Mock private INetworkManagementService mNMService;
98     @Mock private INetd mNetd;
99     @Mock private INetworkStatsService mStatsService;
100     @Mock private IpServer.Callback mCallback;
101     @Mock private InterfaceConfiguration mInterfaceConfiguration;
102     @Mock private SharedLog mSharedLog;
103     @Mock private IDhcpServer mDhcpServer;
104     @Mock private RouterAdvertisementDaemon mRaDaemon;
105     @Mock private IpServer.Dependencies mDependencies;
106 
107     @Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
108 
109     private final TestLooper mLooper = new TestLooper();
110     private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor =
111             ArgumentCaptor.forClass(LinkProperties.class);
112     private IpServer mIpServer;
113 
initStateMachine(int interfaceType)114     private void initStateMachine(int interfaceType) throws Exception {
115         initStateMachine(interfaceType, false /* usingLegacyDhcp */);
116     }
117 
initStateMachine(int interfaceType, boolean usingLegacyDhcp)118     private void initStateMachine(int interfaceType, boolean usingLegacyDhcp) throws Exception {
119         doAnswer(inv -> {
120             final IDhcpServerCallbacks cb = inv.getArgument(2);
121             new Thread(() -> {
122                 try {
123                     cb.onDhcpServerCreated(STATUS_SUCCESS, mDhcpServer);
124                 } catch (RemoteException e) {
125                     fail(e.getMessage());
126                 }
127             }).run();
128             return null;
129         }).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any());
130         when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
131         when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS);
132         when(mDependencies.getNetdService()).thenReturn(mNetd);
133 
134         mIpServer = new IpServer(
135                 IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog,
136                 mNMService, mStatsService, mCallback, usingLegacyDhcp, mDependencies);
137         mIpServer.start();
138         // Starting the state machine always puts us in a consistent state and notifies
139         // the rest of the world that we've changed from an unknown to available state.
140         mLooper.dispatchAll();
141         reset(mNMService, mStatsService, mCallback);
142         when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
143 
144         when(mRaDaemon.start()).thenReturn(true);
145     }
146 
initTetheredStateMachine(int interfaceType, String upstreamIface)147     private void initTetheredStateMachine(int interfaceType, String upstreamIface)
148             throws Exception {
149         initTetheredStateMachine(interfaceType, upstreamIface, false);
150     }
151 
initTetheredStateMachine(int interfaceType, String upstreamIface, boolean usingLegacyDhcp)152     private void initTetheredStateMachine(int interfaceType, String upstreamIface,
153             boolean usingLegacyDhcp) throws Exception {
154         initStateMachine(interfaceType, usingLegacyDhcp);
155         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
156         if (upstreamIface != null) {
157             dispatchTetherConnectionChanged(upstreamIface);
158         }
159         reset(mNMService, mStatsService, mCallback);
160         when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
161     }
162 
setUp()163     @Before public void setUp() throws Exception {
164         MockitoAnnotations.initMocks(this);
165         when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog);
166     }
167 
168     @Test
startsOutAvailable()169     public void startsOutAvailable() {
170         mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(),
171                 TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mCallback,
172                 false /* usingLegacyDhcp */, mDependencies);
173         mIpServer.start();
174         mLooper.dispatchAll();
175         verify(mCallback).updateInterfaceState(
176                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
177         verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
178         verifyNoMoreInteractions(mCallback, mNMService, mStatsService);
179     }
180 
181     @Test
shouldDoNothingUntilRequested()182     public void shouldDoNothingUntilRequested() throws Exception {
183         initStateMachine(TETHERING_BLUETOOTH);
184         final int [] NOOP_COMMANDS = {
185             IpServer.CMD_TETHER_UNREQUESTED,
186             IpServer.CMD_IP_FORWARDING_ENABLE_ERROR,
187             IpServer.CMD_IP_FORWARDING_DISABLE_ERROR,
188             IpServer.CMD_START_TETHERING_ERROR,
189             IpServer.CMD_STOP_TETHERING_ERROR,
190             IpServer.CMD_SET_DNS_FORWARDERS_ERROR,
191             IpServer.CMD_TETHER_CONNECTION_CHANGED
192         };
193         for (int command : NOOP_COMMANDS) {
194             // None of these commands should trigger us to request action from
195             // the rest of the system.
196             dispatchCommand(command);
197             verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
198         }
199     }
200 
201     @Test
handlesImmediateInterfaceDown()202     public void handlesImmediateInterfaceDown() throws Exception {
203         initStateMachine(TETHERING_BLUETOOTH);
204 
205         dispatchCommand(IpServer.CMD_INTERFACE_DOWN);
206         verify(mCallback).updateInterfaceState(
207                 mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
208         verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
209         verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
210     }
211 
212     @Test
canBeTethered()213     public void canBeTethered() throws Exception {
214         initStateMachine(TETHERING_BLUETOOTH);
215 
216         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
217         InOrder inOrder = inOrder(mCallback, mNMService);
218         inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
219         inOrder.verify(mCallback).updateInterfaceState(
220                 mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
221         inOrder.verify(mCallback).updateLinkProperties(
222                 eq(mIpServer), any(LinkProperties.class));
223         verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
224     }
225 
226     @Test
canUnrequestTethering()227     public void canUnrequestTethering() throws Exception {
228         initTetheredStateMachine(TETHERING_BLUETOOTH, null);
229 
230         dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
231         InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback);
232         inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
233         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
234         inOrder.verify(mCallback).updateInterfaceState(
235                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
236         inOrder.verify(mCallback).updateLinkProperties(
237                 eq(mIpServer), any(LinkProperties.class));
238         verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
239     }
240 
241     @Test
canBeTetheredAsUsb()242     public void canBeTetheredAsUsb() throws Exception {
243         initStateMachine(TETHERING_USB);
244 
245         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
246         InOrder inOrder = inOrder(mCallback, mNMService);
247         inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
248         inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
249         inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
250         inOrder.verify(mCallback).updateInterfaceState(
251                 mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
252         inOrder.verify(mCallback).updateLinkProperties(
253                 eq(mIpServer), mLinkPropertiesCaptor.capture());
254         assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
255         verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
256     }
257 
258     @Test
handlesFirstUpstreamChange()259     public void handlesFirstUpstreamChange() throws Exception {
260         initTetheredStateMachine(TETHERING_BLUETOOTH, null);
261 
262         // Telling the state machine about its upstream interface triggers
263         // a little more configuration.
264         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
265         InOrder inOrder = inOrder(mNMService);
266         inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE);
267         inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
268         verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
269     }
270 
271     @Test
handlesChangingUpstream()272     public void handlesChangingUpstream() throws Exception {
273         initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
274 
275         dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
276         InOrder inOrder = inOrder(mNMService, mStatsService);
277         inOrder.verify(mStatsService).forceUpdate();
278         inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
279         inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
280         inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
281         inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
282         verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
283     }
284 
285     @Test
handlesChangingUpstreamNatFailure()286     public void handlesChangingUpstreamNatFailure() throws Exception {
287         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
288 
289         doThrow(RemoteException.class).when(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
290 
291         dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
292         InOrder inOrder = inOrder(mNMService, mStatsService);
293         inOrder.verify(mStatsService).forceUpdate();
294         inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
295         inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
296         inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
297         inOrder.verify(mStatsService).forceUpdate();
298         inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
299         inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
300     }
301 
302     @Test
handlesChangingUpstreamInterfaceForwardingFailure()303     public void handlesChangingUpstreamInterfaceForwardingFailure() throws Exception {
304         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
305 
306         doThrow(RemoteException.class).when(mNMService).startInterfaceForwarding(
307                 IFACE_NAME, UPSTREAM_IFACE2);
308 
309         dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
310         InOrder inOrder = inOrder(mNMService, mStatsService);
311         inOrder.verify(mStatsService).forceUpdate();
312         inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
313         inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
314         inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
315         inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
316         inOrder.verify(mStatsService).forceUpdate();
317         inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
318         inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
319     }
320 
321     @Test
canUnrequestTetheringWithUpstream()322     public void canUnrequestTetheringWithUpstream() throws Exception {
323         initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
324 
325         dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
326         InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback);
327         inOrder.verify(mStatsService).forceUpdate();
328         inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
329         inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
330         inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
331         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
332         inOrder.verify(mCallback).updateInterfaceState(
333                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
334         inOrder.verify(mCallback).updateLinkProperties(
335                 eq(mIpServer), any(LinkProperties.class));
336         verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
337     }
338 
339     @Test
interfaceDownLeadsToUnavailable()340     public void interfaceDownLeadsToUnavailable() throws Exception {
341         for (boolean shouldThrow : new boolean[]{true, false}) {
342             initTetheredStateMachine(TETHERING_USB, null);
343 
344             if (shouldThrow) {
345                 doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME);
346             }
347             dispatchCommand(IpServer.CMD_INTERFACE_DOWN);
348             InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback);
349             usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
350             usbTeardownOrder.verify(mNMService).setInterfaceConfig(
351                     IFACE_NAME, mInterfaceConfiguration);
352             usbTeardownOrder.verify(mCallback).updateInterfaceState(
353                     mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
354             usbTeardownOrder.verify(mCallback).updateLinkProperties(
355                     eq(mIpServer), mLinkPropertiesCaptor.capture());
356             assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue());
357         }
358     }
359 
360     @Test
usbShouldBeTornDownOnTetherError()361     public void usbShouldBeTornDownOnTetherError() throws Exception {
362         initStateMachine(TETHERING_USB);
363 
364         doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
365         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
366         InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback);
367         usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
368         usbTeardownOrder.verify(mNMService).setInterfaceConfig(
369                 IFACE_NAME, mInterfaceConfiguration);
370         usbTeardownOrder.verify(mCallback).updateInterfaceState(
371                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR);
372         usbTeardownOrder.verify(mCallback).updateLinkProperties(
373                 eq(mIpServer), mLinkPropertiesCaptor.capture());
374         assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue());
375     }
376 
377     @Test
shouldTearDownUsbOnUpstreamError()378     public void shouldTearDownUsbOnUpstreamError() throws Exception {
379         initTetheredStateMachine(TETHERING_USB, null);
380 
381         doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString());
382         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
383         InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback);
384         usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
385         usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
386         usbTeardownOrder.verify(mCallback).updateInterfaceState(
387                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
388         usbTeardownOrder.verify(mCallback).updateLinkProperties(
389                 eq(mIpServer), mLinkPropertiesCaptor.capture());
390         assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue());
391     }
392 
393     @Test
ignoresDuplicateUpstreamNotifications()394     public void ignoresDuplicateUpstreamNotifications() throws Exception {
395         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
396 
397         verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
398 
399         for (int i = 0; i < 5; i++) {
400             dispatchTetherConnectionChanged(UPSTREAM_IFACE);
401             verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
402         }
403     }
404 
405     @Test
startsDhcpServer()406     public void startsDhcpServer() throws Exception {
407         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
408         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
409 
410         assertDhcpStarted(new IpPrefix("192.168.43.0/24"));
411     }
412 
413     @Test
startsDhcpServerOnBluetooth()414     public void startsDhcpServerOnBluetooth() throws Exception {
415         initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
416         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
417 
418         assertDhcpStarted(new IpPrefix("192.168.44.0/24"));
419     }
420 
421     @Test
doesNotStartDhcpServerIfDisabled()422     public void doesNotStartDhcpServerIfDisabled() throws Exception {
423         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */);
424         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
425 
426         verify(mDependencies, never()).makeDhcpServer(any(), any(), any());
427     }
428 
assertDhcpStarted(IpPrefix expectedPrefix)429     private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
430         verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any());
431         verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).start(any());
432         final DhcpServingParamsParcel params = mDhcpParamsCaptor.getValue();
433         // Last address byte is random
434         assertTrue(expectedPrefix.contains(intToInet4AddressHTH(params.serverAddr)));
435         assertEquals(expectedPrefix.getPrefixLength(), params.serverAddrPrefixLength);
436         assertEquals(1, params.defaultRouters.length);
437         assertEquals(params.serverAddr, params.defaultRouters[0]);
438         assertEquals(1, params.dnsServers.length);
439         assertEquals(params.serverAddr, params.dnsServers[0]);
440         assertEquals(DHCP_LEASE_TIME_SECS, params.dhcpLeaseTimeSecs);
441     }
442 
443     /**
444      * Send a command to the state machine under test, and run the event loop to idle.
445      *
446      * @param command One of the IpServer.CMD_* constants.
447      * @param arg1 An additional argument to pass.
448      */
dispatchCommand(int command, int arg1)449     private void dispatchCommand(int command, int arg1) {
450         mIpServer.sendMessage(command, arg1);
451         mLooper.dispatchAll();
452     }
453 
454     /**
455      * Send a command to the state machine under test, and run the event loop to idle.
456      *
457      * @param command One of the IpServer.CMD_* constants.
458      */
dispatchCommand(int command)459     private void dispatchCommand(int command) {
460         mIpServer.sendMessage(command);
461         mLooper.dispatchAll();
462     }
463 
464     /**
465      * Special override to tell the state machine that the upstream interface has changed.
466      *
467      * @see #dispatchCommand(int)
468      * @param upstreamIface String name of upstream interface (or null)
469      */
dispatchTetherConnectionChanged(String upstreamIface)470     private void dispatchTetherConnectionChanged(String upstreamIface) {
471         mIpServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED,
472                 new InterfaceSet(upstreamIface));
473         mLooper.dispatchAll();
474     }
475 
assertIPv4AddressAndDirectlyConnectedRoute(LinkProperties lp)476     private void assertIPv4AddressAndDirectlyConnectedRoute(LinkProperties lp) {
477         // Find the first IPv4 LinkAddress.
478         LinkAddress addr4 = null;
479         for (LinkAddress addr : lp.getLinkAddresses()) {
480             if (!(addr.getAddress() instanceof Inet4Address)) continue;
481             addr4 = addr;
482             break;
483         }
484         assertNotNull("missing IPv4 address", addr4);
485 
486         // Assert the presence of the associated directly connected route.
487         final RouteInfo directlyConnected = new RouteInfo(addr4, null, lp.getInterfaceName());
488         assertTrue("missing directly connected route: '" + directlyConnected.toString() + "'",
489                    lp.getRoutes().contains(directlyConnected));
490     }
491 
assertNoAddressesNorRoutes(LinkProperties lp)492     private void assertNoAddressesNorRoutes(LinkProperties lp) {
493         assertTrue(lp.getLinkAddresses().isEmpty());
494         assertTrue(lp.getRoutes().isEmpty());
495         // We also check that interface name is non-empty, because we should
496         // never see an empty interface name in any LinkProperties update.
497         assertFalse(TextUtils.isEmpty(lp.getInterfaceName()));
498     }
499 }
500