1 /*
2  * Copyright (C) 2017 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.system.OsConstants.RT_SCOPE_UNIVERSE;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertTrue;
24 import static org.junit.Assert.fail;
25 import static org.mockito.Mockito.any;
26 import static org.mockito.Mockito.anyString;
27 import static org.mockito.Mockito.eq;
28 import static org.mockito.Mockito.never;
29 import static org.mockito.Mockito.reset;
30 import static org.mockito.Mockito.timeout;
31 import static org.mockito.Mockito.times;
32 import static org.mockito.Mockito.verify;
33 import static org.mockito.Mockito.verifyNoMoreInteractions;
34 import static org.mockito.Mockito.when;
35 
36 import static java.util.Collections.emptySet;
37 
38 import android.app.AlarmManager;
39 import android.content.ContentResolver;
40 import android.content.Context;
41 import android.content.res.Resources;
42 import android.net.ConnectivityManager;
43 import android.net.INetd;
44 import android.net.InetAddresses;
45 import android.net.IpPrefix;
46 import android.net.LinkAddress;
47 import android.net.LinkProperties;
48 import android.net.MacAddress;
49 import android.net.NetworkStackIpMemoryStore;
50 import android.net.RouteInfo;
51 import android.net.ipmemorystore.NetworkAttributes;
52 import android.net.metrics.IpConnectivityLog;
53 import android.net.shared.InitialConfiguration;
54 import android.net.shared.ProvisioningConfiguration;
55 import android.net.util.InterfaceParams;
56 
57 import androidx.test.filters.SmallTest;
58 import androidx.test.runner.AndroidJUnit4;
59 
60 import com.android.server.NetworkObserver;
61 import com.android.server.NetworkObserverRegistry;
62 import com.android.server.NetworkStackService;
63 import com.android.server.connectivity.ipmemorystore.IpMemoryStoreService;
64 import com.android.testutils.HandlerUtilsKt;
65 
66 import org.junit.Before;
67 import org.junit.Test;
68 import org.junit.runner.RunWith;
69 import org.mockito.ArgumentCaptor;
70 import org.mockito.Mock;
71 import org.mockito.MockitoAnnotations;
72 
73 import java.net.Inet4Address;
74 import java.net.Inet6Address;
75 import java.net.InetAddress;
76 import java.util.Arrays;
77 import java.util.HashSet;
78 import java.util.List;
79 import java.util.Set;
80 
81 
82 /**
83  * Tests for IpClient.
84  */
85 @RunWith(AndroidJUnit4.class)
86 @SmallTest
87 public class IpClientTest {
88     private static final String VALID = "VALID";
89     private static final String INVALID = "INVALID";
90     private static final String TEST_IFNAME = "test_wlan0";
91     private static final int TEST_IFINDEX = 1001;
92     // See RFC 7042#section-2.1.2 for EUI-48 documentation values.
93     private static final MacAddress TEST_MAC = MacAddress.fromString("00:00:5E:00:53:01");
94     private static final int TEST_TIMEOUT_MS = 400;
95     private static final String TEST_L2KEY = "some l2key";
96     private static final String TEST_CLUSTER = "some cluster";
97 
98     private static final String TEST_GLOBAL_ADDRESS = "1234:4321::548d:2db2:4fcf:ef75/64";
99     private static final String[] TEST_LOCAL_ADDRESSES = {
100             "fe80::a4be:f92:e1f7:22d1/64",
101             "fe80::f04a:8f6:6a32:d756/64",
102             "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"
103     };
104     private static final String TEST_IPV4_LINKADDRESS = "192.168.42.24/24";
105     private static final String[] TEST_PREFIXES = { "fe80::/64", "fd2c:4e57:8e3c::/64" };
106     private static final String[] TEST_DNSES = { "fd2c:4e57:8e3c::42" };
107     private static final String TEST_IPV6_GATEWAY = "fd2c:4e57:8e3c::43";
108     private static final String TEST_IPV4_GATEWAY = "192.168.42.11";
109     private static final long TEST_DNS_LIFETIME = 3600;
110 
111     @Mock private Context mContext;
112     @Mock private ConnectivityManager mCm;
113     @Mock private NetworkObserverRegistry mObserverRegistry;
114     @Mock private INetd mNetd;
115     @Mock private Resources mResources;
116     @Mock private IIpClientCallbacks mCb;
117     @Mock private AlarmManager mAlarm;
118     @Mock private IpClient.Dependencies mDependencies;
119     @Mock private ContentResolver mContentResolver;
120     @Mock private NetworkStackService.NetworkStackServiceManager mNetworkStackServiceManager;
121     @Mock private NetworkStackIpMemoryStore mIpMemoryStore;
122     @Mock private IpMemoryStoreService mIpMemoryStoreService;
123     @Mock private InterfaceParams mInterfaceParams;
124     @Mock private IpConnectivityLog mMetricsLog;
125 
126     private NetworkObserver mObserver;
127     private InterfaceParams mIfParams;
128 
129     @Before
setUp()130     public void setUp() throws Exception {
131         MockitoAnnotations.initMocks(this);
132 
133         when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm);
134         when(mContext.getSystemService(eq(ConnectivityManager.class))).thenReturn(mCm);
135         when(mContext.getResources()).thenReturn(mResources);
136         when(mDependencies.getNetd(any())).thenReturn(mNetd);
137         when(mCm.shouldAvoidBadWifi()).thenReturn(true);
138         when(mContext.getContentResolver()).thenReturn(mContentResolver);
139         when(mNetworkStackServiceManager.getIpMemoryStoreService())
140                 .thenReturn(mIpMemoryStoreService);
141         when(mDependencies.getInterfaceParams(any())).thenReturn(mInterfaceParams);
142         when(mDependencies.getIpMemoryStore(mContext, mNetworkStackServiceManager))
143                 .thenReturn(mIpMemoryStore);
144         when(mDependencies.getIpConnectivityLog()).thenReturn(mMetricsLog);
145 
146         mIfParams = null;
147     }
148 
setTestInterfaceParams(String ifname)149     private void setTestInterfaceParams(String ifname) {
150         mIfParams = (ifname != null)
151                 ? new InterfaceParams(ifname, TEST_IFINDEX, TEST_MAC)
152                 : null;
153         when(mDependencies.getInterfaceParams(anyString())).thenReturn(mIfParams);
154     }
155 
makeIpClient(String ifname)156     private IpClient makeIpClient(String ifname) throws Exception {
157         setTestInterfaceParams(ifname);
158         final IpClient ipc = new IpClient(mContext, ifname, mCb, mObserverRegistry,
159                 mNetworkStackServiceManager, mDependencies);
160         verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(ifname, false);
161         verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(ifname);
162         ArgumentCaptor<NetworkObserver> arg = ArgumentCaptor.forClass(NetworkObserver.class);
163         verify(mObserverRegistry, times(1)).registerObserverForNonblockingCallback(arg.capture());
164         mObserver = arg.getValue();
165         reset(mObserverRegistry);
166         reset(mNetd);
167         // Verify IpClient doesn't call onLinkPropertiesChange() when it starts.
168         verify(mCb, never()).onLinkPropertiesChange(any());
169         reset(mCb);
170         return ipc;
171     }
172 
makeEmptyLinkProperties(String iface)173     private static LinkProperties makeEmptyLinkProperties(String iface) {
174         final LinkProperties empty = new LinkProperties();
175         empty.setInterfaceName(iface);
176         return empty;
177     }
178 
verifyNetworkAttributesStored(final String l2Key, final NetworkAttributes attributes)179     private void verifyNetworkAttributesStored(final String l2Key,
180             final NetworkAttributes attributes) {
181         // TODO : when storing is implemented, turn this on
182         // verify(mIpMemoryStore).storeNetworkAttributes(eq(l2Key), eq(attributes), any());
183     }
184 
185     @Test
testNullInterfaceNameMostDefinitelyThrows()186     public void testNullInterfaceNameMostDefinitelyThrows() throws Exception {
187         setTestInterfaceParams(null);
188         try {
189             final IpClient ipc = new IpClient(mContext, null, mCb, mObserverRegistry,
190                     mNetworkStackServiceManager, mDependencies);
191             ipc.shutdown();
192             fail();
193         } catch (NullPointerException npe) {
194             // Phew; null interface names not allowed.
195         }
196     }
197 
198     @Test
testNullCallbackMostDefinitelyThrows()199     public void testNullCallbackMostDefinitelyThrows() throws Exception {
200         final String ifname = "lo";
201         setTestInterfaceParams(ifname);
202         try {
203             final IpClient ipc = new IpClient(mContext, ifname, null, mObserverRegistry,
204                     mNetworkStackServiceManager, mDependencies);
205             ipc.shutdown();
206             fail();
207         } catch (NullPointerException npe) {
208             // Phew; null callbacks not allowed.
209         }
210     }
211 
212     @Test
testInvalidInterfaceDoesNotThrow()213     public void testInvalidInterfaceDoesNotThrow() throws Exception {
214         setTestInterfaceParams(TEST_IFNAME);
215         final IpClient ipc = new IpClient(mContext, TEST_IFNAME, mCb, mObserverRegistry,
216                 mNetworkStackServiceManager, mDependencies);
217         verifyNoMoreInteractions(mIpMemoryStore);
218         ipc.shutdown();
219     }
220 
221     @Test
testInterfaceNotFoundFailsImmediately()222     public void testInterfaceNotFoundFailsImmediately() throws Exception {
223         setTestInterfaceParams(null);
224         final IpClient ipc = new IpClient(mContext, TEST_IFNAME, mCb, mObserverRegistry,
225                 mNetworkStackServiceManager, mDependencies);
226         ipc.startProvisioning(new ProvisioningConfiguration());
227         verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningFailure(any());
228         verify(mIpMemoryStore, never()).storeNetworkAttributes(any(), any(), any());
229         ipc.shutdown();
230     }
231 
makeIPv6ProvisionedLinkProperties()232     private LinkProperties makeIPv6ProvisionedLinkProperties() {
233         // Add local addresses, and a global address with global scope
234         final Set<LinkAddress> addresses = links(TEST_LOCAL_ADDRESSES);
235         addresses.add(new LinkAddress(TEST_GLOBAL_ADDRESS, 0, RT_SCOPE_UNIVERSE));
236 
237         // Add a route on the interface for each prefix, and a global route
238         final Set<RouteInfo> routes = routes(TEST_PREFIXES);
239         routes.add(defaultIPV6Route(TEST_IPV6_GATEWAY));
240 
241         return linkproperties(addresses, routes, ips(TEST_DNSES));
242     }
243 
doProvisioningWithDefaultConfiguration()244     private IpClient doProvisioningWithDefaultConfiguration() throws Exception {
245         final IpClient ipc = makeIpClient(TEST_IFNAME);
246 
247         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
248                 .withoutIPv4()
249                 // TODO: mock IpReachabilityMonitor's dependencies (NetworkInterface, PowerManager)
250                 // and enable it in this test
251                 .withoutIpReachabilityMonitor()
252                 .build();
253 
254         ipc.startProvisioning(config);
255         verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setNeighborDiscoveryOffload(true);
256         verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setFallbackMulticastFilter(false);
257 
258         final LinkProperties lp = makeIPv6ProvisionedLinkProperties();
259         lp.getRoutes().forEach(mObserver::onRouteUpdated);
260         lp.getLinkAddresses().forEach(la -> mObserver.onInterfaceAddressUpdated(la, TEST_IFNAME));
261         mObserver.onInterfaceDnsServerInfo(TEST_IFNAME, TEST_DNS_LIFETIME,
262                 lp.getDnsServers().stream().map(InetAddress::getHostAddress)
263                         .toArray(String[]::new));
264 
265         HandlerUtilsKt.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
266         verify(mCb, never()).onProvisioningFailure(any());
267         verify(mIpMemoryStore, never()).storeNetworkAttributes(any(), any(), any());
268 
269         verify(mCb).onProvisioningSuccess(lp);
270         return ipc;
271     }
272 
addIPv4Provisioning(LinkProperties lp)273     private void addIPv4Provisioning(LinkProperties lp) {
274         final LinkAddress la = new LinkAddress(TEST_IPV4_LINKADDRESS);
275         final RouteInfo defaultRoute = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
276                 InetAddresses.parseNumericAddress(TEST_IPV4_GATEWAY), TEST_IFNAME);
277         mObserver.onInterfaceAddressUpdated(la, TEST_IFNAME);
278         mObserver.onRouteUpdated(defaultRoute);
279 
280         lp.addLinkAddress(la);
281         lp.addRoute(defaultRoute);
282     }
283 
284     /**
285      * Simulate loss of IPv6 provisioning (default route lost).
286      *
287      * @return The expected new LinkProperties.
288      */
doIPv6ProvisioningLoss(LinkProperties lp)289     private void doIPv6ProvisioningLoss(LinkProperties lp) {
290         final RouteInfo defaultRoute = defaultIPV6Route(TEST_IPV6_GATEWAY);
291         mObserver.onRouteRemoved(defaultRoute);
292 
293         lp.removeRoute(defaultRoute);
294     }
295 
doDefaultIPv6ProvisioningConfigurationAndProvisioningLossTest(boolean avoidBadWifi)296     private void doDefaultIPv6ProvisioningConfigurationAndProvisioningLossTest(boolean avoidBadWifi)
297             throws Exception {
298         when(mCm.shouldAvoidBadWifi()).thenReturn(avoidBadWifi);
299         final IpClient ipc = doProvisioningWithDefaultConfiguration();
300         final LinkProperties lp = makeIPv6ProvisionedLinkProperties();
301 
302         reset(mCb);
303         doIPv6ProvisioningLoss(lp);
304         HandlerUtilsKt.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
305         verify(mCb).onProvisioningFailure(lp);
306         verify(mCb).onLinkPropertiesChange(makeEmptyLinkProperties(TEST_IFNAME));
307 
308         verifyShutdown(ipc);
309     }
310 
311     @Test
testDefaultIPv6ProvisioningConfiguration_AvoidBadWifi()312     public void testDefaultIPv6ProvisioningConfiguration_AvoidBadWifi() throws Exception {
313         doDefaultIPv6ProvisioningConfigurationAndProvisioningLossTest(true /* avoidBadWifi */);
314     }
315 
316     @Test
testDefaultIPv6ProvisioningConfiguration_StayOnBadWifi()317     public void testDefaultIPv6ProvisioningConfiguration_StayOnBadWifi() throws Exception {
318         // Even when avoidBadWifi=false, if IPv6 only, loss of all provisioning causes
319         // onProvisioningFailure to be called.
320         doDefaultIPv6ProvisioningConfigurationAndProvisioningLossTest(false /* avoidBadWifi */);
321     }
322 
doDefaultDualStackProvisioningConfigurationTest( boolean avoidBadWifi)323     private void doDefaultDualStackProvisioningConfigurationTest(
324             boolean avoidBadWifi) throws Exception {
325         when(mCm.shouldAvoidBadWifi()).thenReturn(avoidBadWifi);
326         final IpClient ipc = doProvisioningWithDefaultConfiguration();
327         final LinkProperties lp = makeIPv6ProvisionedLinkProperties();
328         addIPv4Provisioning(lp);
329         HandlerUtilsKt.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
330 
331         reset(mCb);
332         doIPv6ProvisioningLoss(lp);
333         HandlerUtilsKt.waitForIdle(ipc.getHandler(), TEST_TIMEOUT_MS);
334         if (avoidBadWifi) { // Provisioning failure is expected only when avoidBadWifi is true
335             verify(mCb).onProvisioningFailure(lp);
336             verify(mCb).onLinkPropertiesChange(makeEmptyLinkProperties(TEST_IFNAME));
337         } else {
338             verify(mCb, never()).onProvisioningFailure(any());
339             verify(mCb).onLinkPropertiesChange(lp);
340         }
341 
342         verifyShutdown(ipc);
343     }
344 
345     @Test
testDefaultDualStackProvisioningConfiguration_AvoidBadWifi()346     public void testDefaultDualStackProvisioningConfiguration_AvoidBadWifi() throws Exception {
347         doDefaultDualStackProvisioningConfigurationTest(true /* avoidBadWifi */);
348     }
349 
350     @Test
testDefaultDualStackProvisioningConfiguration_StayOnBadWifi()351     public void testDefaultDualStackProvisioningConfiguration_StayOnBadWifi() throws Exception {
352         doDefaultDualStackProvisioningConfigurationTest(false /* avoidBadWifi */);
353     }
354 
355     @Test
testProvisioningWithInitialConfiguration()356     public void testProvisioningWithInitialConfiguration() throws Exception {
357         final String iface = TEST_IFNAME;
358         final IpClient ipc = makeIpClient(iface);
359         final String l2Key = TEST_L2KEY;
360         final String cluster = TEST_CLUSTER;
361 
362         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
363                 .withoutIPv4()
364                 .withoutIpReachabilityMonitor()
365                 .withInitialConfiguration(
366                         conf(links(TEST_LOCAL_ADDRESSES), prefixes(TEST_PREFIXES), ips()))
367                 .build();
368 
369         ipc.startProvisioning(config);
370         verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setNeighborDiscoveryOffload(true);
371         verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setFallbackMulticastFilter(false);
372         verify(mCb, never()).onProvisioningFailure(any());
373         ipc.setL2KeyAndCluster(l2Key, cluster);
374 
375         for (String addr : TEST_LOCAL_ADDRESSES) {
376             String[] parts = addr.split("/");
377             verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1))
378                     .interfaceAddAddress(iface, parts[0], Integer.parseInt(parts[1]));
379         }
380 
381         final int lastAddr = TEST_LOCAL_ADDRESSES.length - 1;
382 
383         // Add N - 1 addresses
384         for (int i = 0; i < lastAddr; i++) {
385             mObserver.onInterfaceAddressUpdated(new LinkAddress(TEST_LOCAL_ADDRESSES[i]), iface);
386             verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(any());
387             reset(mCb);
388         }
389 
390         // Add Nth address
391         mObserver.onInterfaceAddressUpdated(new LinkAddress(TEST_LOCAL_ADDRESSES[lastAddr]), iface);
392         LinkProperties want = linkproperties(links(TEST_LOCAL_ADDRESSES),
393                 routes(TEST_PREFIXES), emptySet() /* dnses */);
394         want.setInterfaceName(iface);
395         verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(want);
396         verifyNetworkAttributesStored(l2Key, new NetworkAttributes.Builder()
397                 .setCluster(cluster)
398                 .build());
399     }
400 
verifyShutdown(IpClient ipc)401     private void verifyShutdown(IpClient ipc) throws Exception {
402         ipc.shutdown();
403         verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(TEST_IFNAME, false);
404         verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(TEST_IFNAME);
405         verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
406                 .onLinkPropertiesChange(makeEmptyLinkProperties(TEST_IFNAME));
407         verifyNoMoreInteractions(mIpMemoryStore);
408     }
409 
410     @Test
testIsProvisioned()411     public void testIsProvisioned() throws Exception {
412         InitialConfiguration empty = conf(links(), prefixes());
413         IsProvisionedTestCase[] testcases = {
414             // nothing
415             notProvisionedCase(links(), routes(), dns(), null),
416             notProvisionedCase(links(), routes(), dns(), empty),
417 
418             // IPv4
419             provisionedCase(links("192.0.2.12/24"), routes(), dns(), empty),
420 
421             // IPv6
422             notProvisionedCase(
423                     links("fe80::a4be:f92:e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
424                     routes(), dns(), empty),
425             notProvisionedCase(
426                     links("fe80::a4be:f92:e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
427                     routes("fe80::/64", "fd2c:4e57:8e3c::/64"), dns("fd00:1234:5678::1000"), empty),
428             provisionedCase(
429                     links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"),
430                     routes("::/0"),
431                     dns("2001:db8:dead:beef:f00::02"), empty),
432 
433             // Initial configuration
434             provisionedCase(
435                     links("fe80::e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
436                     routes("fe80::/64", "fd2c:4e57:8e3c::/64"),
437                     dns(),
438                     conf(links("fe80::e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"),
439                         prefixes( "fe80::/64", "fd2c:4e57:8e3c::/64"), ips()))
440         };
441 
442         for (IsProvisionedTestCase testcase : testcases) {
443             if (IpClient.isProvisioned(testcase.lp, testcase.config) != testcase.isProvisioned) {
444                 fail(testcase.errorMessage());
445             }
446         }
447     }
448 
449     static class IsProvisionedTestCase {
450         boolean isProvisioned;
451         LinkProperties lp;
452         InitialConfiguration config;
453 
errorMessage()454         String errorMessage() {
455             return String.format("expected %s with config %s to be %s, but was %s",
456                      lp, config, provisioned(isProvisioned), provisioned(!isProvisioned));
457         }
458 
provisioned(boolean isProvisioned)459         static String provisioned(boolean isProvisioned) {
460             return isProvisioned ? "provisioned" : "not provisioned";
461         }
462     }
463 
provisionedCase(Set<LinkAddress> lpAddrs, Set<RouteInfo> lpRoutes, Set<InetAddress> lpDns, InitialConfiguration config)464     static IsProvisionedTestCase provisionedCase(Set<LinkAddress> lpAddrs, Set<RouteInfo> lpRoutes,
465             Set<InetAddress> lpDns, InitialConfiguration config) {
466         return provisioningTest(true, lpAddrs, lpRoutes, lpDns, config);
467     }
468 
notProvisionedCase(Set<LinkAddress> lpAddrs, Set<RouteInfo> lpRoutes, Set<InetAddress> lpDns, InitialConfiguration config)469     static IsProvisionedTestCase notProvisionedCase(Set<LinkAddress> lpAddrs,
470             Set<RouteInfo> lpRoutes, Set<InetAddress> lpDns, InitialConfiguration config) {
471         return provisioningTest(false, lpAddrs, lpRoutes, lpDns, config);
472     }
473 
provisioningTest(boolean isProvisioned, Set<LinkAddress> lpAddrs, Set<RouteInfo> lpRoutes, Set<InetAddress> lpDns, InitialConfiguration config)474     static IsProvisionedTestCase provisioningTest(boolean isProvisioned, Set<LinkAddress> lpAddrs,
475             Set<RouteInfo> lpRoutes, Set<InetAddress> lpDns, InitialConfiguration config) {
476         IsProvisionedTestCase testcase = new IsProvisionedTestCase();
477         testcase.isProvisioned = isProvisioned;
478         testcase.lp = makeEmptyLinkProperties(TEST_IFNAME);
479         testcase.lp.setLinkAddresses(lpAddrs);
480         for (RouteInfo route : lpRoutes) {
481             testcase.lp.addRoute(route);
482         }
483         for (InetAddress dns : lpDns) {
484             testcase.lp.addDnsServer(dns);
485         }
486         testcase.config = config;
487         return testcase;
488     }
489 
490     @Test
testInitialConfigurations()491     public void testInitialConfigurations() throws Exception {
492         InitialConfigurationTestCase[] testcases = {
493             validConf("valid IPv4 configuration",
494                     links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns("192.0.2.2")),
495             validConf("another valid IPv4 configuration",
496                     links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns()),
497             validConf("valid IPv6 configurations",
498                     links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"),
499                     prefixes("2001:db8:dead:beef::/64", "fe80::/64"),
500                     dns("2001:db8:dead:beef:f00::02")),
501             validConf("valid IPv6 configurations",
502                     links("fe80::1/64"), prefixes("fe80::/64"), dns()),
503             validConf("valid IPv6/v4 configuration",
504                     links("2001:db8:dead:beef:f00::a0/48", "192.0.2.12/24"),
505                     prefixes("2001:db8:dead:beef::/64", "192.0.2.0/24"),
506                     dns("192.0.2.2", "2001:db8:dead:beef:f00::02")),
507             validConf("valid IPv6 configuration without any GUA.",
508                     links("fd00:1234:5678::1/48"),
509                     prefixes("fd00:1234:5678::/48"),
510                     dns("fd00:1234:5678::1000")),
511 
512             invalidConf("empty configuration", links(), prefixes(), dns()),
513             invalidConf("v4 addr and dns not in any prefix",
514                     links("192.0.2.12/24"), prefixes("198.51.100.0/24"), dns("192.0.2.2")),
515             invalidConf("v4 addr not in any prefix",
516                     links("198.51.2.12/24"), prefixes("198.51.100.0/24"), dns("192.0.2.2")),
517             invalidConf("v4 dns addr not in any prefix",
518                     links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns("198.51.100.2")),
519             invalidConf("v6 addr not in any prefix",
520                     links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"),
521                     prefixes("2001:db8:dead:beef::/64"),
522                     dns("2001:db8:dead:beef:f00::02")),
523             invalidConf("v6 dns addr not in any prefix",
524                     links("fe80::1/64"), prefixes("fe80::/64"), dns("2001:db8:dead:beef:f00::02")),
525             invalidConf("default ipv6 route and no GUA",
526                     links("fd01:1111:2222:3333::a0/128"), prefixes("::/0"), dns()),
527             invalidConf("invalid v6 prefix length",
528                     links("2001:db8:dead:beef:f00::a0/128"), prefixes("2001:db8:dead:beef::/32"),
529                     dns()),
530             invalidConf("another invalid v6 prefix length",
531                     links("2001:db8:dead:beef:f00::a0/128"), prefixes("2001:db8:dead:beef::/72"),
532                     dns())
533         };
534 
535         for (InitialConfigurationTestCase testcase : testcases) {
536             if (testcase.config.isValid() != testcase.isValid) {
537                 fail(testcase.errorMessage());
538             }
539         }
540     }
541 
542     static class InitialConfigurationTestCase {
543         String descr;
544         boolean isValid;
545         InitialConfiguration config;
errorMessage()546         public String errorMessage() {
547             return String.format("%s: expected configuration %s to be %s, but was %s",
548                     descr, config, validString(isValid), validString(!isValid));
549         }
validString(boolean isValid)550         static String validString(boolean isValid) {
551             return isValid ? VALID : INVALID;
552         }
553     }
554 
validConf(String descr, Set<LinkAddress> links, Set<IpPrefix> prefixes, Set<InetAddress> dns)555     static InitialConfigurationTestCase validConf(String descr, Set<LinkAddress> links,
556             Set<IpPrefix> prefixes, Set<InetAddress> dns) {
557         return confTestCase(descr, true, conf(links, prefixes, dns));
558     }
559 
invalidConf(String descr, Set<LinkAddress> links, Set<IpPrefix> prefixes, Set<InetAddress> dns)560     static InitialConfigurationTestCase invalidConf(String descr, Set<LinkAddress> links,
561             Set<IpPrefix> prefixes, Set<InetAddress> dns) {
562         return confTestCase(descr, false, conf(links, prefixes, dns));
563     }
564 
confTestCase( String descr, boolean isValid, InitialConfiguration config)565     static InitialConfigurationTestCase confTestCase(
566             String descr, boolean isValid, InitialConfiguration config) {
567         InitialConfigurationTestCase testcase = new InitialConfigurationTestCase();
568         testcase.descr = descr;
569         testcase.isValid = isValid;
570         testcase.config = config;
571         return testcase;
572     }
573 
linkproperties(Set<LinkAddress> addresses, Set<RouteInfo> routes, Set<InetAddress> dnses)574     static LinkProperties linkproperties(Set<LinkAddress> addresses,
575             Set<RouteInfo> routes, Set<InetAddress> dnses) {
576         LinkProperties lp = makeEmptyLinkProperties(TEST_IFNAME);
577         lp.setLinkAddresses(addresses);
578         routes.forEach(lp::addRoute);
579         dnses.forEach(lp::addDnsServer);
580         return lp;
581     }
582 
conf(Set<LinkAddress> links, Set<IpPrefix> prefixes)583     static InitialConfiguration conf(Set<LinkAddress> links, Set<IpPrefix> prefixes) {
584         return conf(links, prefixes, new HashSet<>());
585     }
586 
conf( Set<LinkAddress> links, Set<IpPrefix> prefixes, Set<InetAddress> dns)587     static InitialConfiguration conf(
588             Set<LinkAddress> links, Set<IpPrefix> prefixes, Set<InetAddress> dns) {
589         InitialConfiguration conf = new InitialConfiguration();
590         conf.ipAddresses.addAll(links);
591         conf.directlyConnectedRoutes.addAll(prefixes);
592         conf.dnsServers.addAll(dns);
593         return conf;
594     }
595 
routes(String... routes)596     static Set<RouteInfo> routes(String... routes) {
597         return mapIntoSet(routes, (r) -> new RouteInfo(new IpPrefix(r), null /* gateway */,
598                 TEST_IFNAME));
599     }
600 
defaultIPV6Route(String gateway)601     static RouteInfo defaultIPV6Route(String gateway) {
602         return new RouteInfo(new IpPrefix(Inet6Address.ANY, 0),
603                 InetAddresses.parseNumericAddress(gateway), TEST_IFNAME);
604     }
605 
prefixes(String... prefixes)606     static Set<IpPrefix> prefixes(String... prefixes) {
607         return mapIntoSet(prefixes, IpPrefix::new);
608     }
609 
links(String... addresses)610     static Set<LinkAddress> links(String... addresses) {
611         return mapIntoSet(addresses, LinkAddress::new);
612     }
613 
ips(String... addresses)614     static Set<InetAddress> ips(String... addresses) {
615         return mapIntoSet(addresses, InetAddress::getByName);
616     }
617 
dns(String... addresses)618     static Set<InetAddress> dns(String... addresses) {
619         return ips(addresses);
620     }
621 
mapIntoSet(A[] in, Fn<A, B> fn)622     static <A, B> Set<B> mapIntoSet(A[] in, Fn<A, B> fn) {
623         Set<B> out = new HashSet<>(in.length);
624         for (A item : in) {
625             try {
626                 out.add(fn.call(item));
627             } catch (Exception e) {
628                 throw new RuntimeException(e);
629             }
630         }
631         return out;
632     }
633 
634     interface Fn<A,B> {
call(A a)635         B call(A a) throws Exception;
636     }
637 
638     @Test
testAll()639     public void testAll() {
640         List<String> list1 = Arrays.asList();
641         List<String> list2 = Arrays.asList("foo");
642         List<String> list3 = Arrays.asList("bar", "baz");
643         List<String> list4 = Arrays.asList("foo", "bar", "baz");
644 
645         assertTrue(InitialConfiguration.all(list1, (x) -> false));
646         assertFalse(InitialConfiguration.all(list2, (x) -> false));
647         assertTrue(InitialConfiguration.all(list3, (x) -> true));
648         assertTrue(InitialConfiguration.all(list2, (x) -> x.charAt(0) == 'f'));
649         assertFalse(InitialConfiguration.all(list4, (x) -> x.charAt(0) == 'f'));
650     }
651 
652     @Test
testAny()653     public void testAny() {
654         List<String> list1 = Arrays.asList();
655         List<String> list2 = Arrays.asList("foo");
656         List<String> list3 = Arrays.asList("bar", "baz");
657         List<String> list4 = Arrays.asList("foo", "bar", "baz");
658 
659         assertFalse(InitialConfiguration.any(list1, (x) -> true));
660         assertTrue(InitialConfiguration.any(list2, (x) -> true));
661         assertTrue(InitialConfiguration.any(list2, (x) -> x.charAt(0) == 'f'));
662         assertFalse(InitialConfiguration.any(list3, (x) -> x.charAt(0) == 'f'));
663         assertTrue(InitialConfiguration.any(list4, (x) -> x.charAt(0) == 'f'));
664     }
665 
666     @Test
testFindAll()667     public void testFindAll() {
668         List<String> list1 = Arrays.asList();
669         List<String> list2 = Arrays.asList("foo");
670         List<String> list3 = Arrays.asList("foo", "bar", "baz");
671 
672         assertEquals(list1, IpClient.findAll(list1, (x) -> true));
673         assertEquals(list1, IpClient.findAll(list3, (x) -> false));
674         assertEquals(list3, IpClient.findAll(list3, (x) -> true));
675         assertEquals(list2, IpClient.findAll(list3, (x) -> x.charAt(0) == 'f'));
676     }
677 }
678