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

Commit e869136a authored by Hugo Benichi's avatar Hugo Benichi Committed by android-build-merger
Browse files

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

am: c6a9b556

Change-Id: I6e42d26ae430c213f65e466a2c556760ff0e14eb
parents 4fa01870 c6a9b556
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'));
    }
}