org.bidib.wizard.common.utils.NetworkAddressHelper Maven / Gradle / Ivy
package org.bidib.wizard.common.utils;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class NetworkAddressHelper {
private static final Logger LOG = LoggerFactory.getLogger(NetworkAddressHelper.class);
// An implementation can honor these if it wants (the default does)
public static final String SYSTEM_PROPERTY_NET_IFACES = "org.bidib.jbidibc.discovery.network.useInterfaces";
public static final String SYSTEM_PROPERTY_NET_ADDRESSES = "org.bidib.jbidibc.discovery.network.useAddresses";
public static final String SYSTEM_PROPERTY_NET_LOCAL_ADDRESS =
"org.bidib.jbidibc.discovery.network.useLocalAddress";
final protected Set useInterfaces = new HashSet<>();
final protected Set useAddresses = new HashSet<>();
final protected List networkInterfaces = new ArrayList<>();
final protected List bindAddresses = new ArrayList<>();
public NetworkAddressHelper() {
String useInterfacesString = System.getProperty(SYSTEM_PROPERTY_NET_IFACES);
if (useInterfacesString != null) {
String[] userInterfacesStrings = useInterfacesString.split(",");
useInterfaces.addAll(Arrays.asList(userInterfacesStrings));
}
String useAddressesString = System.getProperty(SYSTEM_PROPERTY_NET_ADDRESSES);
if (useAddressesString != null) {
String[] useAddressesStrings = useAddressesString.split(",");
useAddresses.addAll(Arrays.asList(useAddressesStrings));
}
discoverNetworkInterfaces();
discoverBindAddresses();
if ((networkInterfaces.size() == 0 || bindAddresses.size() == 0)) {
LOG.warn("No usable network interface or addresses found");
if (requiresNetworkInterface()) {
throw new IllegalStateException("Could not discover any usable network interfaces and/or addresses");
}
}
}
/**
* @return true
(the default) if a MissingNetworkInterfaceException
should be thrown
*/
protected boolean requiresNetworkInterface() {
return true;
}
// @Override
public Iterator getNetworkInterfaces() {
return new Synchronized(networkInterfaces) {
@Override
protected void synchronizedRemove(int index) {
synchronized (networkInterfaces) {
networkInterfaces.remove(index);
}
}
};
}
public InetAddress[] getBindAddresses() {
return bindAddresses.toArray(new InetAddress[0]);
}
// @Override
public boolean hasUsableNetwork() {
return networkInterfaces.size() > 0 && bindAddresses.size() > 0;
}
protected void discoverNetworkInterfaces() throws InitializationException {
try {
Enumeration interfaceEnumeration = NetworkInterface.getNetworkInterfaces();
for (NetworkInterface iface : Collections.list(interfaceEnumeration)) {
// displayInterfaceInformation(iface);
LOG.trace("Analyzing network interface: {}", iface.getDisplayName());
if (isUsableNetworkInterface(iface, useInterfaces)) {
LOG
.debug("Discovered usable network interface: {}, name: {}", iface.getDisplayName(),
iface.getName());
synchronized (networkInterfaces) {
networkInterfaces.add(iface);
}
}
else {
LOG.trace("Ignoring non-usable network interface: " + iface.getDisplayName());
}
}
}
catch (Exception ex) {
throw new InitializationException("Could not not analyze local network interfaces: " + ex, ex);
}
}
/**
* Validation of every discovered network interface.
*
* Override this method to customize which network interfaces are used.
*
*
* The given implementation ignores interfaces which are
*
*
* - loopback (yes, we do not bind to lo0)
* - down
* - have no bound IP addresses
* - named "vmnet*" (OS X VMWare does not properly stop interfaces when it quits)
* - named "vnic*" (OS X Parallels interfaces should be ignored as well)
* - named "vboxnet*" (OS X Virtual Box interfaces should be ignored as well)
* - named "*virtual*" (VirtualBox interfaces, for example
* - named "ppp*"
*
*
* @param iface
* The interface to validate.
* @return True if the given interface matches all validation criteria.
* @throws Exception
* If any validation test failed with an un-recoverable error.
*/
public static boolean isUsableNetworkInterface(NetworkInterface iface, final Set useInterfaces) {
LOG.trace("Current network interface, displayName: {}, name: {}", iface.getDisplayName(), iface.getName());
try {
if (!iface.isUp()) {
LOG.trace("Skipping network interface (down): " + iface.getDisplayName());
return false;
}
if (getInetAddresses(iface).size() == 0) {
LOG.trace("Skipping network interface without bound IP addresses: " + iface.getDisplayName());
return false;
}
if (iface.getName().toLowerCase(Locale.ROOT).startsWith("vmnet") || (iface.getDisplayName() != null
&& iface.getDisplayName().toLowerCase(Locale.ROOT).contains("vmnet"))) {
LOG.trace("Skipping network interface (VMWare): " + iface.getDisplayName());
return false;
}
if (iface.getName().toLowerCase(Locale.ROOT).startsWith("vnic")) {
LOG.trace("Skipping network interface (Parallels): " + iface.getDisplayName());
return false;
}
if (iface.getName().toLowerCase(Locale.ROOT).startsWith("vboxnet") || (iface.getDisplayName() != null
&& iface.getDisplayName().toLowerCase(Locale.ROOT).startsWith("virtual"))) {
LOG.trace("Skipping network interface (Virtual Box): " + iface.getDisplayName());
return false;
}
if (iface.getDisplayName() != null
&& iface.getDisplayName().toLowerCase(Locale.ROOT).startsWith("hyper-v")) {
LOG.trace("Skipping network interface (Hyper-V): {}", iface.getDisplayName());
return false;
}
if (iface.getName().toLowerCase(Locale.ROOT).contains("tap-windows") || (iface.getDisplayName() != null
&& iface.getDisplayName().toLowerCase(Locale.ROOT).contains("tap-windows"))) {
LOG.trace("Skipping network interface (named '*tap-windows*'): " + iface.getDisplayName());
return false;
}
if (iface.getName().toLowerCase(Locale.ROOT).contains("virtual")) {
LOG.trace("Skipping network interface (named '*virtual*'): " + iface.getDisplayName());
return false;
}
if (iface.getName().toLowerCase(Locale.ROOT).startsWith("ppp")) {
LOG.trace("Skipping network interface (PPP): " + iface.getDisplayName());
return false;
}
if (iface.isLoopback()) {
LOG.trace("Skipping network interface (ignoring loopback): " + iface.getDisplayName());
return false;
}
if (useInterfaces != null && useInterfaces.size() > 0 && !useInterfaces.contains(iface.getName())) {
LOG
.trace("Skipping unwanted network interface (-D" + SYSTEM_PROPERTY_NET_IFACES + "): "
+ iface.getName());
return false;
}
if (!iface.supportsMulticast()) {
LOG.warn("Network interface may not be multicast capable: " + iface.getDisplayName());
}
return true;
}
catch (SocketException ex) {
LOG.warn("Detect if network interface is usable failed.", ex);
return false;
}
}
protected static List getInetAddresses(NetworkInterface networkInterface) {
return Collections.list(networkInterface.getInetAddresses());
}
protected void discoverBindAddresses() throws InitializationException {
try {
synchronized (networkInterfaces) {
Iterator it = networkInterfaces.iterator();
while (it.hasNext()) {
NetworkInterface networkInterface = it.next();
LOG.trace("Discovering addresses of interface: " + networkInterface.getDisplayName());
int usableAddresses = 0;
for (InetAddress inetAddress : getInetAddresses(networkInterface)) {
if (inetAddress == null) {
LOG.warn("Network has a null address: " + networkInterface.getDisplayName());
continue;
}
if (isUsableAddress(networkInterface, inetAddress)) {
LOG.debug("Discovered usable network interface address: " + inetAddress.getHostAddress());
usableAddresses++;
synchronized (bindAddresses) {
bindAddresses.add(inetAddress);
}
}
else {
LOG.trace("Ignoring non-usable network interface address: " + inetAddress.getHostAddress());
}
}
if (usableAddresses == 0) {
LOG
.trace("Network interface has no usable addresses, removing: "
+ networkInterface.getDisplayName());
it.remove();
}
}
}
}
catch (Exception ex) {
throw new InitializationException("Could not not analyze local network interfaces: " + ex, ex);
}
}
/**
* Validation of every discovered local address.
*
* Override this method to customize which network addresses are used.
*
*
* The given implementation ignores addresses which are
*
*
* - not IPv4
* - the local loopback (yes, we ignore 127.0.0.1)
*
*
* @param networkInterface
* The interface to validate.
* @param address
* The address of this interface to validate.
* @return True if the given address matches all validation criteria.
*/
protected boolean isUsableAddress(NetworkInterface networkInterface, InetAddress address) {
// if (!(address instanceof Inet4Address)) {
// LOG.trace("Skipping unsupported non-IPv4 address: " + address);
// return false;
// }
if ((address instanceof Inet6Address) && address.isLinkLocalAddress()) {
LOG.info("Skipping link local ipv6 address: " + address);
return false;
}
if (address.isAnyLocalAddress()) {
LOG.info("Skipping any local address: " + address);
return false;
}
if (address.isLoopbackAddress()) {
LOG.trace("Skipping loopback address: " + address);
return false;
}
if (useAddresses.size() > 0 && !useAddresses.contains(address.getHostAddress())) {
LOG.trace("Skipping unwanted address: " + address);
return false;
}
return true;
}
/*************************************************************************
* Get the current IP address of the computer. This will return the first address of the first network interface
* that is a "real" IP address of the given type.
*
* @param cl
* the type of address we are searching for.
* @return the address as string or "" if not found.
* @throws UnknownHostException
************************************************************************/
public static InetAddress getLocalAddressOfType(Class extends InetAddress> cl) throws UnknownHostException {
try {
String overrideIP = System.getProperty(SYSTEM_PROPERTY_NET_LOCAL_ADDRESS);
if (StringUtils.isNotBlank(overrideIP)) {
return InetAddress.getByName(overrideIP.trim());
}
Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
List localAddresses = new ArrayList<>();
while (interfaces.hasMoreElements()) {
NetworkInterface current = interfaces.nextElement();
LOG.debug("Current network interface: {}", current);
if (!isUsableNetworkInterface(current, Collections.emptySet())) {
continue;
}
Enumeration addresses = current.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress adr = addresses.nextElement();
if (cl.isInstance(adr)) {
localAddresses.add(adr);
}
}
}
if (localAddresses.isEmpty()) {
InetAddress localAddress = InetAddress.getLocalHost();
LOG.debug("No local addresses detected, resolved to: {}", localAddress);
return localAddress;
}
else {
InetAddress localAddress = localAddresses.get(0);
if (localAddresses.size() > 1) {
// if we have multiple addresses we prefer the localHost address
if (Inet4Address.class.equals(cl)) {
InetAddress localHost = Inet4Address.getLocalHost();
if (localAddresses.contains(localHost)) {
localAddress = localHost;
LOG.debug("Found local IPv4 in local addresses, resolved to: {}", localAddress);
}
}
else if (Inet6Address.class.equals(cl)) {
InetAddress localHost = Inet6Address.getLocalHost();
if (localAddresses.contains(localHost)) {
localAddress = localHost;
LOG.debug("Found local IPv6 in local addresses, resolved to: {}", localAddress);
}
}
}
LOG.debug("Local address resolved to: {}", localAddress);
return localAddress;
}
}
catch (Exception e) {
LOG.debug("Local address not resolvable.");
return InetAddress.getLocalHost();
}
}
/**
* Wraps a collection and provides stable iteration with thread-safe removal.
*
* Internally uses the iterator of a CopyOnWriteArrayList
, when remove()
is called,
* delegates to {@link #synchronizedRemove(int)}.
*
*/
static public abstract class Synchronized implements Iterator {
final Iterator wrapped;
int nextIndex = 0;
boolean removedCurrent = false;
public Synchronized(Collection collection) {
this.wrapped = new CopyOnWriteArrayList(collection).iterator();
}
@Override
public boolean hasNext() {
return wrapped.hasNext();
}
@Override
public E next() {
removedCurrent = false;
nextIndex++;
return wrapped.next();
}
@Override
public void remove() {
if (nextIndex == 0) {
throw new IllegalStateException("Call next() first");
}
if (removedCurrent) {
throw new IllegalStateException("Already removed current, call next()");
}
synchronizedRemove(nextIndex - 1);
removedCurrent = true;
}
/**
* Must remove the element at the given index from the original collection in a thread-safe fashion.
*/
abstract protected void synchronizedRemove(int index);
}
}