/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net.ip; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.AlarmManager; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.net.INetd; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.MacAddress; import android.net.RouteInfo; import android.net.ip.IpClient.Callback; import android.net.ip.IpClient.InitialConfiguration; import android.net.ip.IpClient.ProvisioningConfiguration; import android.net.util.InterfaceParams; import android.os.INetworkManagementService; import android.provider.Settings; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.test.mock.MockContentResolver; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.R; import com.android.server.net.BaseNetworkObserver; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.util.Arrays; import java.util.List; import java.util.HashSet; import java.util.Set; /** * Tests for IpClient. */ @RunWith(AndroidJUnit4.class) @SmallTest public class IpClientTest { private static final int DEFAULT_AVOIDBADWIFI_CONFIG_VALUE = 1; private static final String VALID = "VALID"; private static final String INVALID = "INVALID"; private static final String TEST_IFNAME = "test_wlan0"; private static final int TEST_IFINDEX = 1001; // See RFC 7042#section-2.1.2 for EUI-48 documentation values. private static final MacAddress TEST_MAC = MacAddress.fromString("00:00:5E:00:53:01"); @Mock private Context mContext; @Mock private INetworkManagementService mNMService; @Mock private INetd mNetd; @Mock private Resources mResources; @Mock private Callback mCb; @Mock private AlarmManager mAlarm; @Mock private IpClient.Dependencies mDependecies; private MockContentResolver mContentResolver; private BaseNetworkObserver mObserver; private InterfaceParams mIfParams; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); when(mContext.getSystemService(eq(Context.ALARM_SERVICE))).thenReturn(mAlarm); when(mContext.getResources()).thenReturn(mResources); when(mResources.getInteger(R.integer.config_networkAvoidBadWifi)) .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE); mContentResolver = new MockContentResolver(); mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); when(mContext.getContentResolver()).thenReturn(mContentResolver); mIfParams = null; when(mDependecies.getNMS()).thenReturn(mNMService); when(mDependecies.getNetd()).thenReturn(mNetd); } private void setTestInterfaceParams(String ifname) { mIfParams = (ifname != null) ? new InterfaceParams(ifname, TEST_IFINDEX, TEST_MAC) : null; when(mDependecies.getInterfaceParams(anyString())).thenReturn(mIfParams); } private IpClient makeIpClient(String ifname) throws Exception { setTestInterfaceParams(ifname); final IpClient ipc = new IpClient(mContext, ifname, mCb, mDependecies); verify(mNMService, timeout(100).times(1)).disableIpv6(ifname); verify(mNMService, timeout(100).times(1)).clearInterfaceAddresses(ifname); ArgumentCaptor arg = ArgumentCaptor.forClass(BaseNetworkObserver.class); verify(mNMService, times(1)).registerObserver(arg.capture()); mObserver = arg.getValue(); reset(mNMService); return ipc; } @Test public void testNullInterfaceNameMostDefinitelyThrows() throws Exception { setTestInterfaceParams(null); try { final IpClient ipc = new IpClient(mContext, null, mCb, mDependecies); ipc.shutdown(); fail(); } catch (NullPointerException npe) { // Phew; null interface names not allowed. } } @Test public void testNullCallbackMostDefinitelyThrows() throws Exception { final String ifname = "lo"; setTestInterfaceParams(ifname); try { final IpClient ipc = new IpClient(mContext, ifname, null, mDependecies); ipc.shutdown(); fail(); } catch (NullPointerException npe) { // Phew; null callbacks not allowed. } } @Test public void testInvalidInterfaceDoesNotThrow() throws Exception { setTestInterfaceParams(TEST_IFNAME); final IpClient ipc = new IpClient(mContext, TEST_IFNAME, mCb, mDependecies); ipc.shutdown(); } @Test public void testInterfaceNotFoundFailsImmediately() throws Exception { setTestInterfaceParams(null); final IpClient ipc = new IpClient(mContext, TEST_IFNAME, mCb, mDependecies); ipc.startProvisioning(new IpClient.ProvisioningConfiguration()); verify(mCb, times(1)).onProvisioningFailure(any()); ipc.shutdown(); } @Test public void testDefaultProvisioningConfiguration() throws Exception { final String iface = TEST_IFNAME; final IpClient ipc = makeIpClient(iface); ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIPv4() // TODO: mock IpReachabilityMonitor's dependencies (NetworkInterface, PowerManager) // and enable it in this test .withoutIpReachabilityMonitor() .build(); ipc.startProvisioning(config); verify(mCb, times(1)).setNeighborDiscoveryOffload(true); verify(mCb, timeout(100).times(1)).setFallbackMulticastFilter(false); verify(mCb, never()).onProvisioningFailure(any()); ipc.shutdown(); verify(mNMService, timeout(100).times(1)).disableIpv6(iface); verify(mNMService, timeout(100).times(1)).clearInterfaceAddresses(iface); } @Test public void testProvisioningWithInitialConfiguration() throws Exception { final String iface = TEST_IFNAME; final IpClient ipc = makeIpClient(iface); String[] addresses = { "fe80::a4be:f92:e1f7:22d1/64", "fe80::f04a:8f6:6a32:d756/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64" }; String[] prefixes = { "fe80::/64", "fd2c:4e57:8e3c::/64" }; ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIPv4() .withoutIpReachabilityMonitor() .withInitialConfiguration(conf(links(addresses), prefixes(prefixes), ips())) .build(); ipc.startProvisioning(config); verify(mCb, times(1)).setNeighborDiscoveryOffload(true); verify(mCb, timeout(100).times(1)).setFallbackMulticastFilter(false); verify(mCb, never()).onProvisioningFailure(any()); for (String addr : addresses) { String[] parts = addr.split("/"); verify(mNetd, timeout(100).times(1)) .interfaceAddAddress(iface, parts[0], Integer.parseInt(parts[1])); } final int lastAddr = addresses.length - 1; // Add N - 1 addresses for (int i = 0; i < lastAddr; i++) { mObserver.addressUpdated(iface, new LinkAddress(addresses[i])); verify(mCb, timeout(100)).onLinkPropertiesChange(any()); reset(mCb); } // Add Nth address mObserver.addressUpdated(iface, new LinkAddress(addresses[lastAddr])); LinkProperties want = linkproperties(links(addresses), routes(prefixes)); want.setInterfaceName(iface); verify(mCb, timeout(100).times(1)).onProvisioningSuccess(eq(want)); ipc.shutdown(); verify(mNMService, timeout(100).times(1)).disableIpv6(iface); verify(mNMService, timeout(100).times(1)).clearInterfaceAddresses(iface); } @Test public void testIsProvisioned() throws Exception { InitialConfiguration empty = conf(links(), prefixes()); IsProvisionedTestCase[] testcases = { // nothing notProvisionedCase(links(), routes(), dns(), null), notProvisionedCase(links(), routes(), dns(), empty), // IPv4 provisionedCase(links("192.0.2.12/24"), routes(), dns(), empty), // IPv6 notProvisionedCase( links("fe80::a4be:f92:e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"), routes(), dns(), empty), notProvisionedCase( links("fe80::a4be:f92:e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"), routes("fe80::/64", "fd2c:4e57:8e3c::/64"), dns("fd00:1234:5678::1000"), empty), provisionedCase( links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"), routes("::/0"), dns("2001:db8:dead:beef:f00::02"), empty), // Initial configuration provisionedCase( links("fe80::e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"), routes("fe80::/64", "fd2c:4e57:8e3c::/64"), dns(), conf(links("fe80::e1f7:22d1/64", "fd2c:4e57:8e3c:0:548d:2db2:4fcf:ef75/64"), prefixes( "fe80::/64", "fd2c:4e57:8e3c::/64"), ips())) }; for (IsProvisionedTestCase testcase : testcases) { if (IpClient.isProvisioned(testcase.lp, testcase.config) != testcase.isProvisioned) { fail(testcase.errorMessage()); } } } static class IsProvisionedTestCase { boolean isProvisioned; LinkProperties lp; InitialConfiguration config; String errorMessage() { return String.format("expected %s with config %s to be %s, but was %s", lp, config, provisioned(isProvisioned), provisioned(!isProvisioned)); } static String provisioned(boolean isProvisioned) { return isProvisioned ? "provisioned" : "not provisioned"; } } static IsProvisionedTestCase provisionedCase(Set lpAddrs, Set lpRoutes, Set lpDns, InitialConfiguration config) { return provisioningTest(true, lpAddrs, lpRoutes, lpDns, config); } static IsProvisionedTestCase notProvisionedCase(Set lpAddrs, Set lpRoutes, Set lpDns, InitialConfiguration config) { return provisioningTest(false, lpAddrs, lpRoutes, lpDns, config); } static IsProvisionedTestCase provisioningTest(boolean isProvisioned, Set lpAddrs, Set lpRoutes, Set lpDns, InitialConfiguration config) { IsProvisionedTestCase testcase = new IsProvisionedTestCase(); testcase.isProvisioned = isProvisioned; testcase.lp = new LinkProperties(); testcase.lp.setLinkAddresses(lpAddrs); for (RouteInfo route : lpRoutes) { testcase.lp.addRoute(route); } for (InetAddress dns : lpDns) { testcase.lp.addDnsServer(dns); } testcase.config = config; return testcase; } @Test public void testInitialConfigurations() throws Exception { InitialConfigurationTestCase[] testcases = { validConf("valid IPv4 configuration", links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns("192.0.2.2")), validConf("another valid IPv4 configuration", links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns()), validConf("valid IPv6 configurations", links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"), prefixes("2001:db8:dead:beef::/64", "fe80::/64"), dns("2001:db8:dead:beef:f00::02")), validConf("valid IPv6 configurations", links("fe80::1/64"), prefixes("fe80::/64"), dns()), validConf("valid IPv6/v4 configuration", links("2001:db8:dead:beef:f00::a0/48", "192.0.2.12/24"), prefixes("2001:db8:dead:beef::/64", "192.0.2.0/24"), dns("192.0.2.2", "2001:db8:dead:beef:f00::02")), validConf("valid IPv6 configuration without any GUA.", links("fd00:1234:5678::1/48"), prefixes("fd00:1234:5678::/48"), dns("fd00:1234:5678::1000")), invalidConf("empty configuration", links(), prefixes(), dns()), invalidConf("v4 addr and dns not in any prefix", links("192.0.2.12/24"), prefixes("198.51.100.0/24"), dns("192.0.2.2")), invalidConf("v4 addr not in any prefix", links("198.51.2.12/24"), prefixes("198.51.100.0/24"), dns("192.0.2.2")), invalidConf("v4 dns addr not in any prefix", links("192.0.2.12/24"), prefixes("192.0.2.0/24"), dns("198.51.100.2")), invalidConf("v6 addr not in any prefix", links("2001:db8:dead:beef:f00::a0/64", "fe80::1/64"), prefixes("2001:db8:dead:beef::/64"), dns("2001:db8:dead:beef:f00::02")), invalidConf("v6 dns addr not in any prefix", links("fe80::1/64"), prefixes("fe80::/64"), dns("2001:db8:dead:beef:f00::02")), invalidConf("default ipv6 route and no GUA", links("fd01:1111:2222:3333::a0/128"), prefixes("::/0"), dns()), invalidConf("invalid v6 prefix length", links("2001:db8:dead:beef:f00::a0/128"), prefixes("2001:db8:dead:beef::/32"), dns()), invalidConf("another invalid v6 prefix length", links("2001:db8:dead:beef:f00::a0/128"), prefixes("2001:db8:dead:beef::/72"), dns()) }; for (InitialConfigurationTestCase testcase : testcases) { if (testcase.config.isValid() != testcase.isValid) { fail(testcase.errorMessage()); } } } static class InitialConfigurationTestCase { String descr; boolean isValid; InitialConfiguration config; public String errorMessage() { return String.format("%s: expected configuration %s to be %s, but was %s", descr, config, validString(isValid), validString(!isValid)); } static String validString(boolean isValid) { return isValid ? VALID : INVALID; } } static InitialConfigurationTestCase validConf(String descr, Set links, Set prefixes, Set dns) { return confTestCase(descr, true, conf(links, prefixes, dns)); } static InitialConfigurationTestCase invalidConf(String descr, Set links, Set prefixes, Set dns) { return confTestCase(descr, false, conf(links, prefixes, dns)); } static InitialConfigurationTestCase confTestCase( String descr, boolean isValid, InitialConfiguration config) { InitialConfigurationTestCase testcase = new InitialConfigurationTestCase(); testcase.descr = descr; testcase.isValid = isValid; testcase.config = config; return testcase; } static LinkProperties linkproperties(Set addresses, Set routes) { LinkProperties lp = new LinkProperties(); lp.setLinkAddresses(addresses); for (RouteInfo route : routes) { lp.addRoute(route); } return lp; } static InitialConfiguration conf(Set links, Set prefixes) { return conf(links, prefixes, new HashSet<>()); } static InitialConfiguration conf( Set links, Set prefixes, Set dns) { InitialConfiguration conf = new InitialConfiguration(); conf.ipAddresses.addAll(links); conf.directlyConnectedRoutes.addAll(prefixes); conf.dnsServers.addAll(dns); return conf; } static Set routes(String... routes) { return mapIntoSet(routes, (r) -> new RouteInfo(new IpPrefix(r))); } static Set prefixes(String... prefixes) { return mapIntoSet(prefixes, IpPrefix::new); } static Set links(String... addresses) { return mapIntoSet(addresses, LinkAddress::new); } static Set ips(String... addresses) { return mapIntoSet(addresses, InetAddress::getByName); } static Set dns(String... addresses) { return ips(addresses); } static Set mapIntoSet(A[] in, Fn fn) { Set out = new HashSet<>(in.length); for (A item : in) { try { out.add(fn.call(item)); } catch (Exception e) { throw new RuntimeException(e); } } return out; } interface Fn { B call(A a) throws Exception; } @Test public void testAll() { List list1 = Arrays.asList(); List list2 = Arrays.asList("foo"); List list3 = Arrays.asList("bar", "baz"); List list4 = Arrays.asList("foo", "bar", "baz"); assertTrue(IpClient.all(list1, (x) -> false)); assertFalse(IpClient.all(list2, (x) -> false)); assertTrue(IpClient.all(list3, (x) -> true)); assertTrue(IpClient.all(list2, (x) -> x.charAt(0) == 'f')); assertFalse(IpClient.all(list4, (x) -> x.charAt(0) == 'f')); } @Test public void testAny() { List list1 = Arrays.asList(); List list2 = Arrays.asList("foo"); List list3 = Arrays.asList("bar", "baz"); List list4 = Arrays.asList("foo", "bar", "baz"); assertFalse(IpClient.any(list1, (x) -> true)); assertTrue(IpClient.any(list2, (x) -> true)); assertTrue(IpClient.any(list2, (x) -> x.charAt(0) == 'f')); assertFalse(IpClient.any(list3, (x) -> x.charAt(0) == 'f')); assertTrue(IpClient.any(list4, (x) -> x.charAt(0) == 'f')); } @Test public void testFindAll() { List list1 = Arrays.asList(); List list2 = Arrays.asList("foo"); List list3 = Arrays.asList("foo", "bar", "baz"); assertEquals(list1, IpClient.findAll(list1, (x) -> true)); assertEquals(list1, IpClient.findAll(list3, (x) -> false)); assertEquals(list3, IpClient.findAll(list3, (x) -> true)); assertEquals(list2, IpClient.findAll(list3, (x) -> x.charAt(0) == 'f')); } }