All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kiwiproject.net.KiwiInternetAddresses Maven / Gradle / Ivy

package org.kiwiproject.net;

import static com.google.common.base.Strings.nullToEmpty;
import static java.util.stream.Collectors.toList;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.net.HostAndPort;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;

import java.io.UncheckedIOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;

/**
 * Utilities for {@link InetAddress} and other things related to internet addresses. Note that Google Guava also has
 * the {@link com.google.common.net.InetAddresses} class, so if something is not in here, check there. This set of
 * utilities only contains things that are not in the JDK or Guava.
 * 

* Note that some of the methods accept (e.g. as default value) or return {@link SimpleHostInfo}. This is because * the {@link InetAddress} class has no public constructors and can only be instantiated by its static factory * methods. As a result, if you want to specify your own custom host name and/or IP address as the default value, for * example to use a different host alias or subnet IP, then you must use the methods that work with {@link SimpleHostInfo}. * Otherwise when working with {@link InetAddress} instances, using {@link InetAddress#getLoopbackAddress()} is about the * only other default value you could use. *

* Last, note that {@link SimpleHostInfo} only contains the host name and host address as a string. */ @SuppressWarnings("UnstableApiUsage") // Because Guava's InetAddresses is marked @Beta (but has been there a long time) @UtilityClass @Slf4j public class KiwiInternetAddresses { @VisibleForTesting @Setter(AccessLevel.PACKAGE) static InetAddressFinder addressFinder = new InetAddressFinder(); /** * Get local host as an optional of {@link SimpleHostInfo}. * * @return optional containing local host info, or empty if any error occurred */ public static Optional getLocalHostInfo() { try { var address = addressFinder.getLocalHost(); var hostInfo = SimpleHostInfo.fromInetAddress(address); return Optional.of(hostInfo); } catch (UnknownHostException e) { LOG.warn("Unable to get local host", e); return Optional.empty(); } } /** * Get local host as an optional of {@link InetAddress} * * @return optional containing local host info, or empty if any error occurred */ public static Optional getLocalHostInetAddress() { try { var address = addressFinder.getLocalHost(); return Optional.of(address); } catch (UnknownHostException e) { LOG.warn("Unable to get local host", e); return Optional.empty(); } } /** * Get local host as a {@link SimpleHostInfo}. * * @param defaultValue the default value to use if local host could not be obtained for any reason * @return the local host info, or the specified {@code defaultValue} if any error occurred */ public static SimpleHostInfo getLocalHostInfo(SimpleHostInfo defaultValue) { return getLocalHostInfo().orElse(defaultValue); } /** * Get local host as a {@link InetAddress}. * * @param defaultValue the default value to use if local host address could not be obtained for any reason * @return the local host info, or the specified {@code defaultValue} if any error occurred */ public static InetAddress getLocalHostInetAddress(InetAddress defaultValue) { return getLocalHostInetAddress().orElse(defaultValue); } /** * Get local host as a {@link SimpleHostInfo}. * * @param defaultValueSupplier supplier for the default value to use if local host could not be obtained * for any reason * @return the local host info, or the value obtained from the {@code defaultValueSupplier} * if any error occurred */ public static SimpleHostInfo getLocalHostInfo(Supplier defaultValueSupplier) { return getLocalHostInfo().orElseGet(defaultValueSupplier); } /** * Get local host as a {@link InetAddress}. * * @param defaultValueSupplier supplier for the default value to use if local host address could not be obtained * for any reason * @return the local host info, or the value obtained from the {@code defaultValueSupplier} * if any error occurred */ public static InetAddress getLocalHostInetAddress(Supplier defaultValueSupplier) { return getLocalHostInetAddress().orElseGet(defaultValueSupplier); } /** * Returns the port in the given URL, or an empty optional otherwise. * * @param url a URL * @return an optional containing the port, or empty * @throws UncheckedMalformedURLException if the given string URL is not valid * @see URL#URL(String) */ public static Optional portFrom(String url) { try { return portFrom(new URL(nullToEmpty(url))); } catch (MalformedURLException e) { throw new UncheckedMalformedURLException(e); } } /** * Returns the port in the given URL, or an empty optional otherwise. * * @param url a URL * @return an optional containing the port, or empty if the URL did not have a port * @implNote The {@link URL#getPort()} returns {@code -1} if there is no port. Here we change that to an {@link Optional} * @see URL#getPort() */ public static Optional portFrom(URL url) { int port = url.getPort(); return port == -1 ? Optional.empty() : Optional.of(port); } /** * Returns a {@link HostAndPort} from the given URL. * * @param url a URL as a string * @return HostAndPort instance */ public static HostAndPort hostAndPortFrom(String url) { try { return hostAndPortFrom(new URL(nullToEmpty(url))); } catch (MalformedURLException e) { throw new UncheckedMalformedURLException(e); } } /** * Returns a {@link HostAndPort} from the given URL. * * @param url a URL * @return HostAndPort instance */ public static HostAndPort hostAndPortFrom(URL url) { if (url.getPort() == -1) { return HostAndPort.fromHost(url.getHost()); } return HostAndPort.fromParts(url.getHost(), url.getPort()); } /** * Finds the first IP address on the machine that matches one of the given subnet CIDRs or returns null if not found. * The {@link IpScheme} is used to filter the IP addresses by IPv4 or IPv6. * * @param subnetCidrs A list of CIDRs used to match against the machine's IP addresses. * @param ipScheme Whether to filter by IPv4 or IPv6. * @return the first found matching IP address or null. */ public static String findFirstMatchingAddressOrNull(List subnetCidrs, IpScheme ipScheme) { return findFirstMatchingAddress(subnetCidrs, ipScheme).orElse(null); } /** * Finds the first IP address from a given list of ip addresses that matches one of the given subnet CIDRs or returns * null if not found. * * @param subnetCidrs A list of CIDRs used to match against the machine's IP addresses. * @param ipAddresses A list of IP addresses to search for a match. * @return the first found matching IP address or null. */ public static String findFirstMatchingAddressOrNull(List subnetCidrs, List ipAddresses) { return findFirstMatchingAddress(subnetCidrs, ipAddresses).orElse(null); } /** * Finds the first IP address on the machine that matches one of the given subnet CIDRs or throws * IllegalStateException if not found. The {@link IpScheme} is used to filter the IP addresses by IPv4 or IPv6. * * @param subnetCidrs A list of CIDRs used to match against the machine's IP addresses. * @param ipScheme Whether to filter by IPv4 or IPv6. * @return the first found matching IP address. * @throws IllegalStateException if a matching IP address can not be found. */ public static String findFirstMatchingAddressOrThrow(List subnetCidrs, IpScheme ipScheme) { return findFirstMatchingAddress(subnetCidrs, ipScheme).orElseThrow(() -> new IllegalStateException("Unable to find IP address matching a valid subnet CIDR in: " + subnetCidrs)); } /** * Finds the first IP address on the machine that matches one of the given subnet CIDRs or throws * IllegalStateException if not found. * * @param subnetCidrs A list of CIDRs used to match against the machine's IP addresses. * @param ipAddresses A list of IP addresses to search for a match. * @return the first found matching IP address. * @throws IllegalStateException if a matching IP address can not be found. */ public static String findFirstMatchingAddressOrThrow(List subnetCidrs, List ipAddresses) { return findFirstMatchingAddress(subnetCidrs, ipAddresses).orElseThrow(() -> new IllegalStateException("Unable to find IP address matching a valid subnet CIDR in: " + subnetCidrs)); } /** * Attempts to find the first IP address on the machine that matches one of the given subnet CIDRs. The * {@link IpScheme} is used to filter the IP addresses by IPv4 or IPv6. * * @param subnetCidrs A list of CIDRs used to match against the machine's IP addresses. * @param ipScheme Whether to filter by IPv4 or IPv6 * @return an Optional containing the first found matching IP address or {@link Optional#empty()} if not found. */ public static Optional findFirstMatchingAddress(List subnetCidrs, IpScheme ipScheme) { var ipAddresses = getEnumeratedNetworkAddresses(ipScheme); return findFirstMatchingAddress(subnetCidrs, ipAddresses); } /** * Attempts to find the first IP address from a given list of ip addresses that matches one of the given subnet CIDRs. * * @param subnetCidrs A list of CIDRs used to match against the machine's IP addresses. * @param ipAddresses A list of IP addresses to search for a match. * @return an Optional containing the first found matching IP address or {@link Optional#empty()} if not found. */ public static Optional findFirstMatchingAddress(List subnetCidrs, List ipAddresses) { return findMatchingAddresses(subnetCidrs, ipAddresses) .stream() .findFirst(); } /** * Finds all IP addresses on the machine that matches one of the given subnet CIDRs. The {@link IpScheme} is used to * filter the IP addresses by IPv4 or IPv6. * * @param subnetCidrs A list of CIDRs used to match against the machine's IP addresses. * @param ipScheme Whether to filter by IPv4 or IPv6 * @return a list of matching IP addresses. */ public static List findMatchingAddresses(List subnetCidrs, IpScheme ipScheme) { var ipAddresses = getEnumeratedNetworkAddresses(ipScheme); return findMatchingAddresses(subnetCidrs, ipAddresses); } /** * Finds all IP addresses from a given list of ip addresses that matches one of the given subnet CIDRs. * * @param subnetCidrs A list of CIDRs used to match against the machine's IP addresses. * @param ipAddresses A list of IP addresses to search for a match. * @return a list of matching IP addresses. */ public static List findMatchingAddresses(List subnetCidrs, List ipAddresses) { return subnetCidrs.stream() .map(CidrRange::new) .map(cidr -> ipAddresses.stream().filter(cidr::isInRange).findFirst()) .flatMap(Optional::stream) .collect(toList()); } @VisibleForTesting static List getEnumeratedNetworkAddresses(IpScheme ipScheme) { try { var interfaces = NetworkInterface.getNetworkInterfaces(); return Collections.list(interfaces) .stream() .map(networkInterface -> getInterfaceIps(networkInterface, ipScheme)) .flatMap(List::stream) .collect(toList()); } catch (SocketException e) { throw new UncheckedIOException("Error getting enumeration of network interfaces.", e); } } private static List getInterfaceIps(NetworkInterface networkInterface, IpScheme ipScheme) { var addresses = networkInterface.getInetAddresses(); return Collections.list(addresses) .stream() .filter(address -> ipScheme.getInetAddressClass().isAssignableFrom(address.getClass())) .map(InetAddress::getHostAddress) .collect(toList()); } /** * Simple value class encapsulating a host name and IP address */ @Getter @EqualsAndHashCode @Builder @ToString public static class SimpleHostInfo { private final String hostName; private final String ipAddr; /** * Create instance from an {@link InetAddress} * * @param address the internet address * @return a new SimpleHostInfo instance */ public static SimpleHostInfo fromInetAddress(InetAddress address) { return from(address.getHostName(), address.getHostAddress()); } /** * Create instance from host name and ip address * * @param hostName the host name * @param ipAddr the IP address * @return a new SimpleHostInfo instance */ public static SimpleHostInfo from(String hostName, String ipAddr) { return SimpleHostInfo.builder() .hostName(hostName) .ipAddr(ipAddr) .build(); } } @VisibleForTesting static class InetAddressFinder { InetAddress getLocalHost() throws UnknownHostException { return InetAddress.getLocalHost(); } } /** * Enum that defines the IP scheme to use when looking up a machine's IP addresses. */ public enum IpScheme { IPV6(Inet6Address.class), IPV4(Inet4Address.class); @Getter private final Class inetAddressClass; IpScheme(Class inetAddressClass) { this.inetAddressClass = inetAddressClass; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy