Merge "IpManager: use InitialConfiguration for provisioning"
am: 5b218bdc8e

Change-Id: I131589680b68222e4fda4e9165ac2f50894ee28e
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index 46c1292..facdb85 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -36,6 +36,7 @@
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.IpManagerEvent;
 import android.net.util.MultinetworkPolicyTracker;
+import android.net.util.NetdService;
 import android.net.util.NetworkConstants;
 import android.net.util.SharedLog;
 import android.os.INetworkManagementService;
@@ -66,9 +67,11 @@
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Objects;
+import java.util.List;
 import java.util.Set;
 import java.util.StringJoiner;
 import java.util.function.Predicate;
@@ -423,6 +426,10 @@
         }
 
         public boolean isValid() {
+            if (ipAddresses.isEmpty()) {
+                return false;
+            }
+
             // For every IP address, there must be at least one prefix containing that address.
             for (LinkAddress addr : ipAddresses) {
                 if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
@@ -459,14 +466,47 @@
             return true;
         }
 
+        /**
+         * @return true if the given list of addressess and routes satisfies provisioning for this
+         * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
+         * because addresses and routes seen by Netlink will contain additional fields like flags,
+         * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
+         * provisioning check always fails.
+         *
+         * If the given list of routes is null, only addresses are taken into considerations.
+         */
+        public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
+            if (ipAddresses.isEmpty()) {
+                return false;
+            }
+
+            for (LinkAddress addr : ipAddresses) {
+                if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
+                    return false;
+                }
+            }
+
+            if (routes != null) {
+                for (IpPrefix prefix : directlyConnectedRoutes) {
+                    if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
+            return !route.hasGateway() && prefix.equals(route.getDestination());
+        }
+
         private static boolean isPrefixLengthCompliant(LinkAddress addr) {
-            return (addr.getAddress() instanceof Inet4Address)
-                    || isCompliantIPv6PrefixLength(addr.getPrefixLength());
+            return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
         }
 
         private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
-            return (prefix.getAddress() instanceof Inet4Address)
-                    || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
+            return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
         }
 
         private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
@@ -479,28 +519,7 @@
         }
 
         private static boolean isIPv6GUA(LinkAddress addr) {
-            return (addr.getAddress() instanceof Inet6Address) && addr.isGlobalPreferred();
-        }
-
-        private static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
-            for (T t : coll) {
-                if (fn.test(t)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        private static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
-            return !any(coll, not(fn));
-        }
-
-        private static <T> Predicate<T> not(Predicate<T> fn) {
-            return (t) -> !fn.test(t);
-        }
-
-        private static <T> String join(String delimiter, Collection<T> coll) {
-            return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
+            return addr.isIPv6() && addr.isGlobalPreferred();
         }
     }
 
@@ -549,6 +568,7 @@
     private final LocalLog mConnectivityPacketLog;
     private final MessageHandlingLogger mMsgStateLogger;
     private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
+    private final INetd mNetd;
 
     private NetworkInterface mNetworkInterface;
 
@@ -568,14 +588,22 @@
 
     public IpManager(Context context, String ifName, Callback callback) {
         this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
-                ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)));
+                ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)),
+                NetdService.getInstance());
     }
 
     /**
      * An expanded constructor, useful for dependency injection.
+     * TODO: migrate all test users to mock IpManager directly and remove this ctor.
      */
     public IpManager(Context context, String ifName, Callback callback,
             INetworkManagementService nwService) {
+        this(context, ifName, callback, nwService, NetdService.getInstance());
+    }
+
+    @VisibleForTesting
+    IpManager(Context context, String ifName, Callback callback,
+            INetworkManagementService nwService, INetd netd) {
         super(IpManager.class.getSimpleName() + "." + ifName);
         mTag = getName();
 
@@ -584,6 +612,7 @@
         mClatInterfaceName = CLAT_PREFIX + ifName;
         mCallback = new LoggingCallbackWrapper(callback);
         mNwService = nwService;
+        mNetd = netd;
 
         mLog = new SharedLog(MAX_LOG_RECORDS, mTag);
         mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
@@ -886,10 +915,20 @@
     }
 
     // For now: use WifiStateMachine's historical notion of provisioned.
-    private static boolean isProvisioned(LinkProperties lp) {
+    @VisibleForTesting
+    static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) {
         // For historical reasons, we should connect even if all we have is
         // an IPv4 address and nothing else.
-        return lp.isProvisioned() || lp.hasIPv4Address();
+        if (lp.hasIPv4Address() || lp.isProvisioned()) {
+            return true;
+        }
+        if (config == null) {
+            return false;
+        }
+
+        // When an InitialConfiguration is specified, ignore any difference with previous
+        // properties and instead check if properties observed match the desired properties.
+        return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes());
     }
 
     // TODO: Investigate folding all this into the existing static function
@@ -898,12 +937,11 @@
     // object that is a correct and complete assessment of what changed, taking
     // account of the asymmetries described in the comments in this function.
     // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
-    private ProvisioningChange compareProvisioning(
-            LinkProperties oldLp, LinkProperties newLp) {
+    private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) {
         ProvisioningChange delta;
-
-        final boolean wasProvisioned = isProvisioned(oldLp);
-        final boolean isProvisioned = isProvisioned(newLp);
+        InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null;
+        final boolean wasProvisioned = isProvisioned(oldLp, config);
+        final boolean isProvisioned = isProvisioned(newLp, config);
 
         if (!wasProvisioned && isProvisioned) {
             delta = ProvisioningChange.GAINED_PROVISIONING;
@@ -1016,10 +1054,6 @@
         return delta;
     }
 
-    private boolean linkPropertiesUnchanged(LinkProperties newLp) {
-        return Objects.equals(newLp, mLinkProperties);
-    }
-
     private LinkProperties assembleLinkProperties() {
         // [1] Create a new LinkProperties object to populate.
         LinkProperties newLp = new LinkProperties();
@@ -1066,9 +1100,26 @@
             newLp.setHttpProxy(mHttpProxy);
         }
 
-        if (VDBG) {
-            Log.d(mTag, "newLp{" + newLp + "}");
+        // [5] Add data from InitialConfiguration
+        if (mConfiguration != null && mConfiguration.mInitialConfig != null) {
+            InitialConfiguration config = mConfiguration.mInitialConfig;
+            // Add InitialConfiguration routes and dns server addresses once all addresses
+            // specified in the InitialConfiguration have been observed with Netlink.
+            if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) {
+                for (IpPrefix prefix : config.directlyConnectedRoutes) {
+                    newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName));
+                }
+            }
+            addAllReachableDnsServers(newLp, config.dnsServers);
         }
+        final LinkProperties oldLp = mLinkProperties;
+        if (VDBG) {
+            Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s",
+                    netlinkLinkProperties, newLp, oldLp));
+        }
+
+        // TODO: also learn via netlink routes specified by an InitialConfiguration and specified
+        // from a static IP v4 config instead of manually patching them in in steps [3] and [5].
         return newLp;
     }
 
@@ -1087,7 +1138,7 @@
     // Returns false if we have lost provisioning, true otherwise.
     private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
         final LinkProperties newLp = assembleLinkProperties();
-        if (linkPropertiesUnchanged(newLp)) {
+        if (Objects.equals(newLp, mLinkProperties)) {
             return true;
         }
         final ProvisioningChange delta = setLinkProperties(newLp);
@@ -1218,6 +1269,26 @@
         return true;
     }
 
+    private boolean applyInitialConfig(InitialConfiguration config) {
+        if (mNetd == null) {
+            logError("tried to add %s to %s but INetd was null", config, mInterfaceName);
+            return false;
+        }
+
+        // TODO: also support specifying a static IPv4 configuration in InitialConfiguration.
+        for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) {
+            try {
+                mNetd.interfaceAddAddress(
+                        mInterfaceName, addr.getAddress().getHostAddress(), addr.getPrefixLength());
+            } catch (ServiceSpecificException | RemoteException e) {
+                logError("failed to add %s to %s: %s", addr, mInterfaceName, e);
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     private boolean startIpReachabilityMonitor() {
         try {
             mIpReachabilityMonitor = new IpReachabilityMonitor(
@@ -1446,6 +1517,14 @@
                 return;
             }
 
+            InitialConfiguration config = mConfiguration.mInitialConfig;
+            if ((config != null) && !applyInitialConfig(config)) {
+                // TODO introduce a new IpManagerEvent constant to distinguish this error case.
+                doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING);
+                transitionTo(mStoppingState);
+                return;
+            }
+
             if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) {
                 doImmediateProvisioningFailure(
                         IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR);
@@ -1652,4 +1731,39 @@
                                  receivedInState, processedInState);
         }
     }
+
+    // TODO: extract out into CollectionUtils.
+    static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
+        for (T t : coll) {
+            if (fn.test(t)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
+        return !any(coll, not(fn));
+    }
+
+    static <T> Predicate<T> not(Predicate<T> fn) {
+        return (t) -> !fn.test(t);
+    }
+
+    static <T> String join(String delimiter, Collection<T> coll) {
+        return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
+    }
+
+    static <T> T find(Iterable<T> coll, Predicate<T> fn) {
+        for (T t: coll) {
+            if (fn.test(t)) {
+              return t;
+            }
+        }
+        return null;
+    }
+
+    static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) {
+        return coll.stream().filter(fn).collect(Collectors.toList());
+    }
 }
diff --git a/tests/net/java/android/net/ip/IpManagerTest.java b/tests/net/java/android/net/ip/IpManagerTest.java
index dc77e22..541f91ad 100644
--- a/tests/net/java/android/net/ip/IpManagerTest.java
+++ b/tests/net/java/android/net/ip/IpManagerTest.java
@@ -17,6 +17,8 @@
 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.eq;
@@ -32,8 +34,11 @@
 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.RouteInfo;
 import android.net.ip.IpManager.Callback;
 import android.net.ip.IpManager.InitialConfiguration;
 import android.net.ip.IpManager.ProvisioningConfiguration;
@@ -45,16 +50,20 @@
 
 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;
 
@@ -71,11 +80,14 @@
 
     @Mock private Context mContext;
     @Mock private INetworkManagementService mNMService;
+    @Mock private INetd mNetd;
     @Mock private Resources mResources;
     @Mock private Callback mCb;
     @Mock private AlarmManager mAlarm;
     private MockContentResolver mContentResolver;
 
+    BaseNetworkObserver mObserver;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -91,9 +103,13 @@
     }
 
     private IpManager makeIpManager(String ifname) throws Exception {
-        final IpManager ipm = new IpManager(mContext, ifname, mCb, mNMService);
+        final IpManager ipm = new IpManager(mContext, ifname, mCb, mNMService, mNetd);
         verify(mNMService, timeout(100).times(1)).disableIpv6(ifname);
         verify(mNMService, timeout(100).times(1)).clearInterfaceAddresses(ifname);
+        ArgumentCaptor<BaseNetworkObserver> arg =
+                ArgumentCaptor.forClass(BaseNetworkObserver.class);
+        verify(mNMService, times(1)).registerObserver(arg.capture());
+        mObserver = arg.getValue();
         reset(mNMService);
         return ipm;
     }
@@ -131,6 +147,134 @@
     }
 
     @Test
+    public void testProvisioningWithInitialConfiguration() throws Exception {
+        final String iface = "test_wlan0";
+        final IpManager ipm = makeIpManager(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();
+
+        ipm.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).times(1)).onLinkPropertiesChange(any());
+        }
+
+        // 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));
+
+        ipm.stop();
+        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 (IpManager.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",
@@ -152,6 +296,7 @@
                     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",
@@ -189,10 +334,9 @@
             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 String validString(boolean isValid) {
+            return isValid ? VALID : INVALID;
+        }
     }
 
     static InitialConfigurationTestCase validConf(String descr, Set<LinkAddress> links,
@@ -214,6 +358,19 @@
         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();
@@ -223,6 +380,10 @@
         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);
     }
@@ -254,4 +415,44 @@
     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(IpManager.all(list1, (x) -> false));
+        assertFalse(IpManager.all(list2, (x) -> false));
+        assertTrue(IpManager.all(list3, (x) -> true));
+        assertTrue(IpManager.all(list2, (x) -> x.charAt(0) == 'f'));
+        assertFalse(IpManager.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(IpManager.any(list1, (x) -> true));
+        assertTrue(IpManager.any(list2, (x) -> true));
+        assertTrue(IpManager.any(list2, (x) -> x.charAt(0) == 'f'));
+        assertFalse(IpManager.any(list3, (x) -> x.charAt(0) == 'f'));
+        assertTrue(IpManager.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, IpManager.findAll(list1, (x) -> true));
+        assertEquals(list1, IpManager.findAll(list3, (x) -> false));
+        assertEquals(list3, IpManager.findAll(list3, (x) -> true));
+        assertEquals(list2, IpManager.findAll(list3, (x) -> x.charAt(0) == 'f'));
+    }
 }