summaryrefslogtreecommitdiff
path: root/tests/src/android/net/ip/IpClientTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'tests/src/android/net/ip/IpClientTest.java')
-rw-r--r--tests/src/android/net/ip/IpClientTest.java533
1 files changed, 533 insertions, 0 deletions
diff --git a/tests/src/android/net/ip/IpClientTest.java b/tests/src/android/net/ip/IpClientTest.java
new file mode 100644
index 0000000..f21809f
--- /dev/null
+++ b/tests/src/android/net/ip/IpClientTest.java
@@ -0,0 +1,533 @@
+/*
+ * 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 android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable;
+
+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.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+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.Context;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+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.shared.InitialConfiguration;
+import android.net.shared.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.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+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.InetAddress;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+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");
+ private static final int TEST_TIMEOUT_MS = 400;
+
+ @Mock private Context mContext;
+ @Mock private ConnectivityManager mCm;
+ @Mock private INetworkManagementService mNMService;
+ @Mock private INetd mNetd;
+ @Mock private Resources mResources;
+ @Mock private IIpClientCallbacks 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.getSystemServiceName(ConnectivityManager.class))
+ .thenReturn(Context.CONNECTIVITY_SERVICE);
+ when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
+ 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(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(ifname, false);
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(ifname);
+ ArgumentCaptor<BaseNetworkObserver> arg =
+ ArgumentCaptor.forClass(BaseNetworkObserver.class);
+ verify(mNMService, times(1)).registerObserver(arg.capture());
+ mObserver = arg.getValue();
+ reset(mNMService);
+ reset(mNetd);
+ // Verify IpClient doesn't call onLinkPropertiesChange() when it starts.
+ verify(mCb, never()).onLinkPropertiesChange(any());
+ reset(mCb);
+ return ipc;
+ }
+
+ private static LinkProperties makeEmptyLinkProperties(String iface) {
+ final LinkProperties empty = new LinkProperties();
+ empty.setInterfaceName(iface);
+ return empty;
+ }
+
+ @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 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(TEST_TIMEOUT_MS).times(1)).setFallbackMulticastFilter(false);
+ verify(mCb, never()).onProvisioningFailure(any());
+
+ ipc.shutdown();
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false);
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface);
+ verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
+ .onLinkPropertiesChange(argThat(
+ lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(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(TEST_TIMEOUT_MS).times(1)).setFallbackMulticastFilter(false);
+ verify(mCb, never()).onProvisioningFailure(any());
+
+ for (String addr : addresses) {
+ String[] parts = addr.split("/");
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).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(TEST_TIMEOUT_MS)).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(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(argThat(
+ lp -> fromStableParcelable(lp).equals(want)));
+
+ ipc.shutdown();
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceSetEnableIPv6(iface, false);
+ verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)).interfaceClearAddrs(iface);
+ verify(mCb, timeout(TEST_TIMEOUT_MS).times(1))
+ .onLinkPropertiesChange(argThat(
+ lp -> fromStableParcelable(lp).equals(makeEmptyLinkProperties(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<LinkAddress> lpAddrs, Set<RouteInfo> lpRoutes,
+ Set<InetAddress> lpDns, InitialConfiguration config) {
+ return provisioningTest(true, lpAddrs, lpRoutes, lpDns, config);
+ }
+
+ static IsProvisionedTestCase notProvisionedCase(Set<LinkAddress> lpAddrs,
+ Set<RouteInfo> lpRoutes, Set<InetAddress> lpDns, InitialConfiguration config) {
+ return provisioningTest(false, lpAddrs, lpRoutes, lpDns, config);
+ }
+
+ static IsProvisionedTestCase provisioningTest(boolean isProvisioned, Set<LinkAddress> lpAddrs,
+ Set<RouteInfo> lpRoutes, Set<InetAddress> 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<LinkAddress> links,
+ Set<IpPrefix> prefixes, Set<InetAddress> dns) {
+ return confTestCase(descr, true, conf(links, prefixes, dns));
+ }
+
+ static InitialConfigurationTestCase invalidConf(String descr, Set<LinkAddress> links,
+ Set<IpPrefix> prefixes, Set<InetAddress> 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<LinkAddress> addresses, Set<RouteInfo> routes) {
+ LinkProperties lp = new LinkProperties();
+ lp.setLinkAddresses(addresses);
+ for (RouteInfo route : routes) {
+ lp.addRoute(route);
+ }
+ return lp;
+ }
+
+ static InitialConfiguration conf(Set<LinkAddress> links, Set<IpPrefix> prefixes) {
+ return conf(links, prefixes, new HashSet<>());
+ }
+
+ static InitialConfiguration conf(
+ Set<LinkAddress> links, Set<IpPrefix> prefixes, Set<InetAddress> dns) {
+ InitialConfiguration conf = new InitialConfiguration();
+ conf.ipAddresses.addAll(links);
+ conf.directlyConnectedRoutes.addAll(prefixes);
+ conf.dnsServers.addAll(dns);
+ return conf;
+ }
+
+ static Set<RouteInfo> routes(String... routes) {
+ return mapIntoSet(routes, (r) -> new RouteInfo(new IpPrefix(r)));
+ }
+
+ static Set<IpPrefix> prefixes(String... prefixes) {
+ return mapIntoSet(prefixes, IpPrefix::new);
+ }
+
+ static Set<LinkAddress> links(String... addresses) {
+ return mapIntoSet(addresses, LinkAddress::new);
+ }
+
+ static Set<InetAddress> ips(String... addresses) {
+ return mapIntoSet(addresses, InetAddress::getByName);
+ }
+
+ static Set<InetAddress> dns(String... addresses) {
+ return ips(addresses);
+ }
+
+ static <A, B> Set<B> mapIntoSet(A[] in, Fn<A, B> fn) {
+ Set<B> 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<A,B> {
+ B call(A a) throws Exception;
+ }
+
+ @Test
+ public void testAll() {
+ List<String> list1 = Arrays.asList();
+ List<String> list2 = Arrays.asList("foo");
+ List<String> list3 = Arrays.asList("bar", "baz");
+ List<String> list4 = Arrays.asList("foo", "bar", "baz");
+
+ assertTrue(InitialConfiguration.all(list1, (x) -> false));
+ assertFalse(InitialConfiguration.all(list2, (x) -> false));
+ assertTrue(InitialConfiguration.all(list3, (x) -> true));
+ assertTrue(InitialConfiguration.all(list2, (x) -> x.charAt(0) == 'f'));
+ assertFalse(InitialConfiguration.all(list4, (x) -> x.charAt(0) == 'f'));
+ }
+
+ @Test
+ public void testAny() {
+ List<String> list1 = Arrays.asList();
+ List<String> list2 = Arrays.asList("foo");
+ List<String> list3 = Arrays.asList("bar", "baz");
+ List<String> list4 = Arrays.asList("foo", "bar", "baz");
+
+ assertFalse(InitialConfiguration.any(list1, (x) -> true));
+ assertTrue(InitialConfiguration.any(list2, (x) -> true));
+ assertTrue(InitialConfiguration.any(list2, (x) -> x.charAt(0) == 'f'));
+ assertFalse(InitialConfiguration.any(list3, (x) -> x.charAt(0) == 'f'));
+ assertTrue(InitialConfiguration.any(list4, (x) -> x.charAt(0) == 'f'));
+ }
+
+ @Test
+ public void testFindAll() {
+ List<String> list1 = Arrays.asList();
+ List<String> list2 = Arrays.asList("foo");
+ List<String> 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'));
+ }
+}