package org.jolokia.util; import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.*; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Utility class for network related stuff * * @author roland * @since 05.02.14 */ public final class NetworkUtil { // Only available for Java 6 private static Method isUp; private static Method supportsMulticast; static { // Check for JDK method d which are available only for JDK6 try { isUp = NetworkInterface.class.getMethod("isUp", (Class<?>[]) null); supportsMulticast = NetworkInterface.class.getMethod("supportsMulticast", (Class<?>[]) null); } catch (NoSuchMethodException e) { isUp = null; supportsMulticast = null; } } // Utility class private NetworkUtil() { } // Debug info public static void main(String[] args) throws UnknownHostException, SocketException { System.out.println(dumpLocalNetworkInfo()); // NOSONAR } /** * Get a local, IP4 Address, preferable a non-loopback address which is bound to an interface. * * @return * @throws UnknownHostException * @throws SocketException */ public static InetAddress getLocalAddress() throws UnknownHostException, SocketException { InetAddress addr = InetAddress.getLocalHost(); NetworkInterface nif = NetworkInterface.getByInetAddress(addr); if (addr.isLoopbackAddress() || addr instanceof Inet6Address || nif == null) { // Find local address that isn't a loopback address InetAddress lookedUpAddr = findLocalAddressViaNetworkInterface(); // If a local, multicast enabled address can be found, use it. Otherwise // we are using the local address, which might not be what you want addr = lookedUpAddr != null ? lookedUpAddr : InetAddress.getByName("127.0.0.1"); } return addr; } /** * Get a local address which supports multicast. A loopback adress is returned, but only if not * another is available * * @return a multicast enabled address of null if none could be found * @throws UnknownHostException * @throws SocketException */ public static InetAddress getLocalAddressWithMulticast() throws UnknownHostException, SocketException { InetAddress addr = InetAddress.getLocalHost(); NetworkInterface nif = NetworkInterface.getByInetAddress(addr); if (addr.isLoopbackAddress() || addr instanceof Inet6Address || !isMulticastSupported(nif)) { // Find local address that isn't a loopback address InetAddress lookedUpAddr = findLocalAddressViaNetworkInterface(); // If a local, multicast enabled address can be found, use it. Otherwise // we are using the local address, which might not be what you want if (lookedUpAddr != null) { return lookedUpAddr; } addr = InetAddress.getByName("127.0.0.1"); } if (isMulticastSupported(addr)) { return addr; } else { throw new UnknownHostException("Cannot find address of local host which can be used for multicasting"); } } // returns null if none has been found public static InetAddress findLocalAddressViaNetworkInterface() { Enumeration<NetworkInterface> networkInterfaces; try { networkInterfaces = NetworkInterface.getNetworkInterfaces(); } catch (SocketException e) { return null; } while (networkInterfaces.hasMoreElements()) { NetworkInterface nif = networkInterfaces.nextElement(); for (Enumeration<InetAddress> addrEnum = nif.getInetAddresses(); addrEnum.hasMoreElements(); ) { InetAddress interfaceAddress = addrEnum.nextElement(); if (useInetAddress(nif, interfaceAddress)) { return interfaceAddress; } } } return null; } /** * Check, whether multicast is supported at all by at least one interface * * @return true if at least one network interface supports multicast */ public static boolean isMulticastSupported() throws SocketException { return getMulticastAddresses().size() != 0; } /** * Check whether the given interface supports multicast and is up * * @param pNif check whether the given interface supports multicast * @return true if multicast is supported and the interface is up */ public static boolean isMulticastSupported(NetworkInterface pNif) { return pNif != null && checkMethod(pNif, isUp) && checkMethod(pNif, supportsMulticast); } /** * Check whether the given address' interface supports multicast * * @param pAddr address to check * @return true if the underlying networkinterface is up and supports multicast * @throws SocketException */ public static boolean isMulticastSupported(InetAddress pAddr) throws SocketException { return isMulticastSupported(NetworkInterface.getByInetAddress(pAddr)); } /** * Get all local addresses on which a multicast can be send * * @return list of all multi cast capable addresses */ public static List<InetAddress> getMulticastAddresses() throws SocketException { Enumeration<NetworkInterface> nifs = NetworkInterface.getNetworkInterfaces(); List<InetAddress> ret = new ArrayList<InetAddress>(); while (nifs.hasMoreElements()) { NetworkInterface nif = nifs.nextElement(); if (checkMethod(nif, supportsMulticast) && checkMethod(nif, isUp)) { Enumeration<InetAddress> addresses = nif.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress addr = addresses.nextElement(); // TODO: IpV6 support if (!(addr instanceof Inet6Address)) { ret.add(addr); } } } } return ret; } public static String getAgentId(int objectId, String type) { String address; try { address = getLocalAddress().getHostAddress(); } catch (IOException exp) { address = "local"; } return address + "-" + getProcessId() + "-" + Integer.toHexString(objectId) + "-" + type; } /** * Examine the given URL and replace the host with a non-loopback host if possible. It is checked, * whether the port is open as well. * <p/> * A replaced host uses the IP address instead of a (possibly non resolvable) name. * * @param pRequestURL url to examine and to update * @return the 'sane' URL (or the original one if no san */ public static String sanitizeLocalUrl(String pRequestURL) { try { URL url = new URL(pRequestURL); String host = url.getHost(); int port = getPort(url); InetAddress address = findLocalAddressListeningOnPort(host, port); return new URL(url.getProtocol(), address.getHostAddress(), port, url.getFile()).toExternalForm(); } catch (IOException e) { // Best effort, we at least tried it return pRequestURL; } } private static int getPort(URL url) { int port = url.getPort(); if (port != -1) { return port; } // Return default ports return url.getProtocol().equalsIgnoreCase("https") ? 443 : 80; } // ======================================================================================================= // Only use the given interface on the given network interface if it is up and supports multicast private static boolean useInetAddress(NetworkInterface networkInterface, InetAddress interfaceAddress) { return checkMethod(networkInterface, isUp) && checkMethod(networkInterface, supportsMulticast) && // TODO: IpV6 support !(interfaceAddress instanceof Inet6Address) && !interfaceAddress.isLoopbackAddress(); } // Call a method and return the result as boolean. In case of problems, return false. private static Boolean checkMethod(NetworkInterface iface, Method toCheck) { if (toCheck != null) { try { return (Boolean) toCheck.invoke(iface, (Object[]) null); } catch (IllegalAccessException e) { return false; } catch (InvocationTargetException e) { return false; } } // Cannot check, hence we assume that is true return true; } // Check for an non-loopback, local adress listening on the given port private static InetAddress findLocalAddressListeningOnPort(String pHost, int pPort) throws UnknownHostException, SocketException { InetAddress address = InetAddress.getByName(pHost); if (address.isLoopbackAddress()) { // First check local address InetAddress localAddress = getLocalAddress(); if (!localAddress.isLoopbackAddress() && isPortOpen(localAddress, pPort)) { return localAddress; } // Then try all addresses attache to all interfaces localAddress = getLocalAddressFromNetworkInterfacesListeningOnPort(pPort); if (localAddress != null) { return localAddress; } } return address; } private static InetAddress getLocalAddressFromNetworkInterfacesListeningOnPort(int pPort) { try { Enumeration<NetworkInterface> networkInterfaces; networkInterfaces = NetworkInterface.getNetworkInterfaces(); while (networkInterfaces.hasMoreElements()) { NetworkInterface nif = networkInterfaces.nextElement(); for (Enumeration<InetAddress> addrEnum = nif.getInetAddresses(); addrEnum.hasMoreElements(); ) { InetAddress interfaceAddress = addrEnum.nextElement(); if (!interfaceAddress.isLoopbackAddress() && checkMethod(nif, isUp) && isPortOpen(interfaceAddress, pPort)) { return interfaceAddress; } } } } catch (SocketException e) { return null; } return null; } // Check a port by connecting to it. Try only 200ms. private static boolean isPortOpen(InetAddress pAddress, int pPort) { Socket socket = null; try { socket = new Socket(); socket.setReuseAddress(true); SocketAddress sa = new InetSocketAddress(pAddress, pPort); socket.connect(sa, 200); return socket.isConnected(); } catch (IOException e) { return false; } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { // Best effort. Hate that close throws an IOException, btw. Never saw a real use case for that. } } } } // Hack for finding the process id. Used in creating an unique agent id. private static String getProcessId() { // something like '<pid>@<hostname>', at least in SUN / Oracle JVMs final String jvmName = ManagementFactory.getRuntimeMXBean().getName(); final int index = jvmName.indexOf('@'); return index < 0 ? jvmName : jvmName.substring(0, index); } /** * Get the local network info as a string * * @return return a description of the current network setup of the local host. * @throws UnknownHostException * @throws SocketException */ public static String dumpLocalNetworkInfo() throws UnknownHostException, SocketException { StringBuffer buffer = new StringBuffer(); InetAddress addr = InetAddress.getLocalHost(); buffer.append("Localhost: " + getAddrInfo(addr) + "\n"); Enumeration<NetworkInterface> nifs = NetworkInterface.getNetworkInterfaces(); buffer.append("Network interfaces:\n"); while (nifs.hasMoreElements()) { NetworkInterface nif = nifs.nextElement(); buffer.append(" - " + getNetworkInterfaceInfo(nif) + "\n"); Enumeration<InetAddress> addresses = nif.getInetAddresses(); while (addresses.hasMoreElements()) { addr = addresses.nextElement(); buffer.append(" " + getAddrInfo(addr) + "\n"); } } return buffer.toString(); } private static final Pattern EXPRESSION_EXTRACTOR = Pattern.compile("\\$\\{?\\s*([\\w:-_.]+)\\s*}?"); /** * Replace expression ${host} and ${ip} with the localhost name or IP in the given string. * In addition the notation ${env:ENV_VAR} and ${prop:sysprop} can be used to refer to environment * and system properties respectively. * * @param pValue value to examine * @return the value with the variables replaced. * @throws IllegalArgumentException when the expression is unknown or an error occurs when extracting the host name */ public static String replaceExpression(String pValue) { if (pValue == null) { return null; } Matcher matcher = EXPRESSION_EXTRACTOR.matcher(pValue); StringBuffer ret = new StringBuffer(); try { while (matcher.find()) { String var = matcher.group(1); String value; if (var.equalsIgnoreCase("host")) { value = getLocalAddress().getHostName(); } else if (var.equalsIgnoreCase("ip")) { value = getLocalAddress().getHostAddress(); } else { String key = extractKey(var,"env"); if (key != null) { value = System.getenv(key); } else { key = extractKey(var,"prop"); if (key != null) { value = System.getProperty(key); } else { throw new IllegalArgumentException("Unknown expression " + var + " in " + pValue); } } } matcher.appendReplacement(ret, value != null ? value.trim() : null); } matcher.appendTail(ret); } catch (IOException e) { throw new IllegalArgumentException("Cannot lookup host" + e, e); } return ret.toString(); } private static String extractKey(String pVar, String pPrefix) { if (pVar.toLowerCase().startsWith(pPrefix + ":")) { String ret = pVar.substring(pPrefix.length() + 1); if (ret.length() == 0) { throw new IllegalArgumentException("Expression with " + pPrefix + ": must not contain spaces"); } return ret; } return null; } // ============================================================================================================================================== // Dump methods private static String getAddrInfo(InetAddress pAddr) throws SocketException { String ret = pAddr.getHostName() != null ? pAddr.getHostName() + " (" + pAddr.getHostAddress() + ")" : pAddr.getHostAddress(); ret += " [site-local: " + pAddr.isSiteLocalAddress() + ", link-local: " + pAddr.isLinkLocalAddress() + ", lb: " + pAddr.isLoopbackAddress() + "]"; NetworkInterface nif = NetworkInterface.getByInetAddress(pAddr); ret += " -- nif: " + getNetworkInterfaceInfo(nif); return ret; } private static String getNetworkInterfaceInfo(NetworkInterface pNif) throws SocketException { if (pNif == null) { return "[null]"; } return pNif.getDisplayName() + " [up: " + pNif.isUp() + ", mc: " + pNif.supportsMulticast() + ", lb: " + pNif.isLoopback() + ", hw: " + formatHwAddress(pNif.getHardwareAddress()) + "]"; } private static String formatHwAddress(byte[] pHardwareAddress) { if (pHardwareAddress == null) { return "[none]"; } StringBuilder sb = new StringBuilder(18); for (byte b : pHardwareAddress) { if (sb.length() > 0) { sb.append(':'); } sb.append(String.format("%02x", b)); } return sb.toString(); } }