Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d00ddf56 authored by Hugo Benichi's avatar Hugo Benichi
Browse files

IpManager: use InitialConfiguration for provisioning

This patch changes IpManager to take into account static provisioning
information specified in the InitialConfiguration for IPv6 static
configuration.

When a valid InitialConfiguration with IPv6 content is specified,
IpManager will do the following things:

- at start(), it will push the IPv6 addresses in the config to netd
- it will observe all addresses be notified via Netlink
- when all addresses are there, it will patch in the associated IPv6
  routes in the config, so that they get passed to ConnectivityService
  through the usual mechanism

The logic triggering onProvisioningSuccess is also changed to take into
account InitialConfiguration: when all addresses and all routes in the
config are seen the provisioning is successful.

Bug: 62988545
Test: runtest frameworks-net, with newly added tests
Change-Id: I77ed7c576c4b198de7a4726be70c78b74689e98b
parent 23868e9c
Loading
Loading
Loading
Loading
+154 −40
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.net.dhcp.DhcpClient;
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.Inet6Address;
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 class IpManager extends StateMachine {
        }

        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,48 +466,60 @@ public class IpManager extends StateMachine {
            return true;
        }

        private static boolean isPrefixLengthCompliant(LinkAddress addr) {
            return (addr.getAddress() instanceof Inet4Address)
                    || isCompliantIPv6PrefixLength(addr.getPrefixLength());
        /**
         * @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;
            }

        private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
            return (prefix.getAddress() instanceof Inet4Address)
                    || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
            for (LinkAddress addr : ipAddresses) {
                if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
                    return false;
                }

        private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
            return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
                    && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH);
            }

        private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
            return prefix.getAddress().equals(Inet6Address.ANY);
            if (routes != null) {
                for (IpPrefix prefix : directlyConnectedRoutes) {
                    if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
                        return false;
                    }
                }

        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;
        }

        private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
            return !route.hasGateway() && prefix.equals(route.getDestination());
        }
            return false;

        private static boolean isPrefixLengthCompliant(LinkAddress addr) {
            return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
        }

        private static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
            return !any(coll, not(fn));
        private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
            return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
        }

        private static <T> Predicate<T> not(Predicate<T> fn) {
            return (t) -> !fn.test(t);
        private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
            return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
                    && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH);
        }

        private static <T> String join(String delimiter, Collection<T> coll) {
            return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter));
        private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
            return prefix.getAddress().equals(Inet6Address.ANY);
        }

        private static boolean isIPv6GUA(LinkAddress addr) {
            return addr.isIPv6() && addr.isGlobalPreferred();
        }
    }

@@ -549,6 +568,7 @@ public class IpManager extends StateMachine {
    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 class IpManager extends StateMachine {

    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 @@ public class IpManager extends StateMachine {
        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 @@ public class IpManager extends StateMachine {
    }

    // 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 @@ public class IpManager extends StateMachine {
    // 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 @@ public class IpManager extends StateMachine {
        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 @@ public class IpManager extends StateMachine {
            newLp.setHttpProxy(mHttpProxy);
        }

        // [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, "newLp{" + newLp + "}");
            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 @@ public class IpManager extends StateMachine {
    // 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 @@ public class IpManager extends StateMachine {
        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 @@ public class IpManager extends StateMachine {
                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 @@ public class IpManager extends StateMachine {
                                 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());
    }
}
+206 −5
Original line number Diff line number Diff line
@@ -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.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.RouteInfo;
import android.net.ip.IpManager.Callback;
import android.net.ip.IpManager.InitialConfiguration;
import android.net.ip.IpManager.ProvisioningConfiguration;
@@ -45,16 +50,20 @@ 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;

@@ -71,11 +80,14 @@ public class IpManagerTest {

    @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 @@ public class IpManagerTest {
    }

    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;
    }
@@ -130,6 +146,134 @@ public class IpManagerTest {
        verify(mNMService, timeout(100).times(1)).clearInterfaceAddresses(iface);
    }

    @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 = {
@@ -152,6 +296,7 @@ public class IpManagerTest {
                    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,11 +334,10 @@ public class IpManagerTest {
            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) {
@@ -214,6 +358,19 @@ public class IpManagerTest {
        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 @@ public class IpManagerTest {
        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 @@ public class IpManagerTest {
    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'));
    }
}