/*******************************************************************************
* Copyright (c) 2011, 2014 Wind River Systems, Inc. and others. All rights reserved.
* This program and the accompanying materials are made available under the terms
* of the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.te.runtime.utils.net;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* The IP address utility provides a safe method to get all local IP addresses,
* check if any given address refers to the local host, and compare IP addresses.
*/
public class IPAddressUtil {
/**
* Constants for address types. They are sorted by "quality", i.e. the higher the total value of all flags is, the
* "better" or "more canonical" an address is assumed to be. Any "global" address type is better than all other
* addresses; among address types, the canonical name is better than the address which is better than a plain name;
* IPv4 is better than IPv6.
*/
public final static int HOSTMAP_IPV6 = 0x01;
public final static int HOSTMAP_IPV4 = 0x02;
public final static int HOSTMAP_NAME = 0x04;
public final static int HOSTMAP_ADDR = 0x08;
public final static int HOSTMAP_CANONICALNAME = 0x10;
public final static int HOSTMAP_CANONICALADDR = 0x20;
public final static int HOSTMAP_MULTICAST = 0x40;
public final static int HOSTMAP_LOOPBACK = 0x80;
public final static int HOSTMAP_LINKLOCAL = 0x100;
public final static int HOSTMAP_SITELOCAL = 0x200;
public final static int HOSTMAP_GLOBAL = 0x400;
// shortcuts
public final static int HOSTMAP_ANY_UNICAST = HOSTMAP_LOOPBACK | HOSTMAP_LINKLOCAL | HOSTMAP_SITELOCAL | HOSTMAP_GLOBAL;
private final Map<String, Integer> fLocalHostAddresses = new HashMap<String, Integer>();
private final Set<String> fNonLocalHostAddresses = new HashSet<String>();
private boolean initialized = false;
IPAddressUtil() {
initializeHostCache();
}
private synchronized void initializeHostCache() {
if (!initialized) {
// first, add the known interfaces. This is the only safe method to get
// the _real_ IP addresses, and get _all_ of them.
addLocalAddressesByInterface();
try {
// Add what Java thinks is the local host.
InetAddress localHostJava = InetAddress.getLocalHost();
// Do _not_ add the address that Java thinks the local host has,
// since it may be wrong! This is due to the method Java uses:
// it takes the host _name_ and does a reverse name lookup
// to get the address. This may be _wrong_ in case the DNS server
// points to a different (or outdated) address for the name.
// In reality, _only_ the addresses given by our own interfaces
// are correct! (As obtained by addLocalAddressesByInterface()).
// addLocalAddress(localHostJava);
// Add what Java thinks is the local host name.
// The local host name correct in the sense that it is configured
// locally and thus known locally. Note that in case of DNS inconsistency,
// DNS servers will return a _different_ address for the name than the
// local one, in this case the host name will be added as non-local.
addHostName(localHostJava.getHostName());
} catch (UnknownHostException e) {
/* no error */
}
// finally, add the "localhost" special host name since it might not be covered
// by the methods above (we cannot get all names for a given address, only the other way round).
addHostName("localhost"); //$NON-NLS-1$
// mark as initialized
initialized = true;
}
}
/**
* Iterate over local interfaces and add IP-addresses. This is the only safe method to get all local IP-addresses,
* since InetAddr.getAllByName() may fail to get the local IP address in case the local hostname is configured to be
* resolved to the loopback adapter (in this case, only the loopback adapter's address is returned). When this
* method has run, we know that we have all IP addresses of this machine. We can not know all the names of this
* machine since there s no method to query all name servers for all addresses. Therefore, new names may be added
* later by @see addHostName(String).
*/
private synchronized void addLocalAddressesByInterface() {
Enumeration<NetworkInterface> interfaces = null;
try {
interfaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
/* no error, try other method */
}
while (interfaces != null && interfaces.hasMoreElements()) {
NetworkInterface iface = interfaces.nextElement();
Enumeration<InetAddress> addresses = iface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement();
addLocalAddress(addr);
}
}
}
private synchronized void addLocalAddress(InetAddress addr) {
int addrtype;
if (addr.isLoopbackAddress()) {
addrtype = HOSTMAP_LOOPBACK;
} else if (addr.isLinkLocalAddress()) {
addrtype = HOSTMAP_LINKLOCAL;
} else if (addr.isSiteLocalAddress()) {
addrtype = HOSTMAP_SITELOCAL;
} else if (addr.isMulticastAddress()) {
addrtype = HOSTMAP_MULTICAST;
} else {
addrtype = HOSTMAP_GLOBAL;
}
if (addr.getAddress().length == 4) {
addrtype |= HOSTMAP_IPV4;
} else {
addrtype |= HOSTMAP_IPV6;
}
String addrAsString = addr.getHostAddress();
fLocalHostAddresses.put(addrAsString, Integer.valueOf(addrtype | HOSTMAP_ADDR));
if (0 == (addrtype & (HOSTMAP_LINKLOCAL | HOSTMAP_SITELOCAL | HOSTMAP_MULTICAST))) {
// Don't do DNS Reverse Loopkup's for non-routable addresses.
// They won't be known to the Name Server anyway, and they
// make startup _much_ slower.
String addrAsNameCan = addr.getCanonicalHostName().toLowerCase();
// query the name after the canonical name, it will re-use
// cached canonical name (if the name was not explicitly set)
String addrAsName = addr.getHostName().toLowerCase();
if (!addrAsNameCan.equals(addrAsString)) {
// We must check if we really got a name, since InetAddress.getHostName()
// returns the original address in case it thinks the name is spoofed!
if (0 == (addrtype & HOSTMAP_LOOPBACK)) {
// Not loopback --> found a Canonical Name.
fLocalHostAddresses.put(addrAsNameCan, Integer.valueOf(addrtype | HOSTMAP_NAME | HOSTMAP_CANONICALNAME));
// override the address as canonical-address
fLocalHostAddresses.put(addrAsString, Integer.valueOf(addrtype | HOSTMAP_ADDR | HOSTMAP_CANONICALADDR));
} else {
// Loopback --> add the found name as non-canonical.
fLocalHostAddresses.put(addrAsNameCan, Integer.valueOf(addrtype | HOSTMAP_NAME));
}
}
if (!addrAsName.equals(addrAsString) && !addrAsName.equals(addrAsNameCan)) {
// don't override the canonical name by the name.
fLocalHostAddresses.put(addrAsName, Integer.valueOf(addrtype | HOSTMAP_NAME));
}
}
}
private synchronized boolean addHostAddress(InetAddress addr, String hostName) {
if (addr == null) {
// Address for host name could not be resolved --> add to non-local-addresses
fNonLocalHostAddresses.add(hostName);
return false;
}
// Get the host address
String hostAddr = addr.getHostAddress();
// Newly discovered loopback addresses are added
// to the local host address list first
if (!fLocalHostAddresses.containsKey(hostAddr) && addr.isLoopbackAddress()) {
addLocalAddress(addr);
}
// Is it a new local host network interface (VPN)?
if (!fLocalHostAddresses.containsKey(hostAddr)) {
boolean added = false;
Enumeration<NetworkInterface> interfaces = null;
try {
interfaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) { /* ignored on purpose */ }
while (interfaces != null && interfaces.hasMoreElements() && !added) {
NetworkInterface iface = interfaces.nextElement();
Enumeration<InetAddress> addresses = iface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress candidate = addresses.nextElement();
if (candidate.equals(addr) && !fLocalHostAddresses.containsKey(hostAddr)) {
addLocalAddress(addr);
added = true;
break;
}
}
}
}
Integer entryType = fLocalHostAddresses.get(hostAddr);
if (entryType != null) {
// found a new name for a known local address ?
if (!fLocalHostAddresses.containsKey(hostName)) {
int addrtype = entryType.intValue() & (~(HOSTMAP_ADDR | HOSTMAP_CANONICALADDR));
fLocalHostAddresses.put(hostName, Integer.valueOf(addrtype | HOSTMAP_NAME));
}
return true;
}
fNonLocalHostAddresses.add(hostName);
fNonLocalHostAddresses.add(hostAddr);
return false;
}
/**
* Add a host name to internal caching tables.
*
* @param hostName String host name or address as String
* @return <code>true</code> if the added Host was considered "local", false otherwise.
*/
public boolean addHostName(String hostName) {
hostName = hostName.toLowerCase();
try {
// Only take the first address: in case of multiple addresses
// on the remote host, some of them could be on different
// subnetworks and thus be non-local although they match one
// of our local addresses!
//
// Make sure that the name service resolving (probably time-consuming!)
// is outside our synchronized block.
InetAddress addr = InetAddress.getByName(hostName);
return addHostAddress(addr, hostName);
} catch (UnknownHostException e) {
/* got an illegal name --> add as non-local. */
return addHostAddress(null, hostName);
}
}
/**
* Return a list of hostnames or addresses for the local host. In case loopback addresses were asked for, these will
* appear first in the list. Example: String[] addresses =
* getLocalHostAddresses(HOSTMAP_ADDR|HOSTMAP_LOOPBACK|HOSTMAP_IPV4);
*
* @param typesToGet an integer bitmask of the types to get, uses the HOSTMAP constants declared in this class:
* HOSTMAP_NAME - get hostnames HOSTMAP_ADDR - get IP-addresses HOSTMAP_CANONICALNAME - get the
* "canonical" hostnames for each interface HOSTMAP_CANONICALADDR - get the "canonical" addresses for each
* interface HOSTMAP_LOOPBACK - get names/addresses for the loopback interface HOSTMAP_LINKLOCAL - get
* names/addresses for link-local non-routable interfaces HOSTMAP_SITELOCAL - get names/addresses for
* site-local non-routable interfaces HOSTMAP_MULTICAST - get multicast names/addresses HOSTMAP_GLOBAL -
* get names/addresses that are globally valid HOSTMAP_ANY_UNICAST - get names/addresses for any local
* non-unicast address HOSTMAP_IPV4 - get IPv4 names/addresses HOSTMAP_IPV6 - get IPv6 names/addresses
* @return String[] array of IP-addresses in String representation.
*/
public synchronized String[] getLocalHostAddresses(int typesToGet) {
if ((typesToGet & HOSTMAP_ADDR) != 0) {
typesToGet |= HOSTMAP_CANONICALADDR; // plain address query includes the canonical address
}
if ((typesToGet & HOSTMAP_NAME) != 0) {
typesToGet |= HOSTMAP_CANONICALNAME; // plain name query includes the canonical name
}
List<String> addresses = new ArrayList<String>(fLocalHostAddresses.size());
Iterator<Entry<String, Integer>> it = fLocalHostAddresses.entrySet().iterator();
while (it.hasNext()) {
Entry<String, Integer> entry = it.next();
int addrtype = entry.getValue().intValue();
if ((addrtype & typesToGet) == addrtype) {
if ((addrtype & HOSTMAP_LOOPBACK) != 0) {
addresses.add(0, entry.getKey()); // add loopback addresses first
} else {
addresses.add(entry.getKey());
}
}
}
return addresses.toArray(new String[addresses.size()]);
}
/**
* Returns an IPv4 address to safely connect to the local host. This method works around limitations in the Java
* library's provisions for obtaining the local host address: - InetAddress.getByName("localhost") might be IPv6,
* and it might be mapped to a non-local host by the name service - InetAddress.getByName(null) might fail due to
* disabled loopback adapter (ifconfig lo down) - InetAddress.getLocalHost() returns non-loopback-address which is
* not optimal (slower than loopback, may disappear when removing a network cable or disconnecting from dial-up
* network connection) Therefore, the method below relies on information obtained from Enumeration
* NetworkInterface.getNetworkInterfaces() to know if an address is local.
*
* @return String representation of IPv4 address referring to the local host, or <code>null</code> if no address is
* found that allows to connect to the local host via IPv4.
*/
public synchronized String getIPv4LoopbackAddress() {
// first, try the official IPv4 loopback address as per Internet RFC.
// This can fail only in case of disabled loopback adapter ("ifconfig lo down").
// Should perhaps be removed here in order to allow configuring preferred
// localhost connection from the outside.
if (isLocalHost("127.0.0.1")) { //$NON-NLS-1$
return "127.0.0.1"; //$NON-NLS-1$
}
// next, check if "localhost" is configured as an IPv4 address:
// if yes, it can be used directly. This allows to configure
// the preferred method for local host connections from the outside.
Integer key = fLocalHostAddresses.get("localhost"); //$NON-NLS-1$
if (key != null && (key.intValue() & HOSTMAP_IPV4) != 0) { return "localhost"; //$NON-NLS-1$
}
// finally ("localhost" mis-configured), obtain an address from NetworkInterfaces.
// loopback addresses are sorted first, so they are preferred
int typemask = HOSTMAP_ADDR | HOSTMAP_IPV4 | HOSTMAP_ANY_UNICAST;
String[] candidates = getLocalHostAddresses(typemask);
if (candidates.length == 0) {
// re-initialize the cache, perhaps some interfaces were brought up
// in the meantime (e.g. hot-plug network cards)
addLocalAddressesByInterface();
candidates = getLocalHostAddresses(typemask);
}
return candidates.length > 0 ? candidates[0] : null;
}
/**
* Returns the canonical name or address of this host. A "best effort" is made to return the address that is assumed
* to be "most canonical". A global IP address is considered better than a host name, since name service
* configuration might not be global.
*
* @return String IP address of the local host as it should be reachable from the outside.
*/
public synchronized String getCanonicalAddress() {
String canonical = null;
Iterator<Entry<String, Integer>> it = fLocalHostAddresses.entrySet().iterator();
String bestAddress = null;
int bestAddrType = 0;
while (it.hasNext()) {
Entry<String, Integer> curEntry = it.next();
int curAddrType = curEntry.getValue().intValue();
if (curAddrType > bestAddrType) {
bestAddress = curEntry.getKey();
bestAddrType = curAddrType;
}
}
canonical = bestAddress;
return canonical;
}
/**
* Returns the canonical IPv4 name or address of this host. A "best effort" is made to return the address
* that is assumed to be "most canonical". A global IP address is considered better than a host name, since
* name service configuration might not be global.
*
* @return String IPv4 address of the local host as it should be reachable from the outside.
*/
public synchronized String getIPv4CanonicalAddress() {
String canonical = null;
int typemask = HOSTMAP_ADDR | HOSTMAP_NAME | HOSTMAP_IPV4 | HOSTMAP_ANY_UNICAST;
String[] candidates = getLocalHostAddresses(typemask);
String bestAddress = null;
int bestAddrType = 0;
for (String candidate : candidates) {
Integer value = fLocalHostAddresses.get(candidate);
if (value != null && value.intValue() > bestAddrType) {
bestAddress = candidate;
bestAddrType = value.intValue();
}
}
canonical = bestAddress;
return canonical;
}
/**
* Returns a list of host names that are considered "most canonical" on this host. The names are guaranteed to refer
* to global IP addresses of this machine. The list may be empty if no proper name is configured.
*
* @return String[] host names, may be empty
*/
public synchronized String[] getCanonicalHostNames() {
int typeMask = IPAddressUtil.HOSTMAP_CANONICALNAME | IPAddressUtil.HOSTMAP_GLOBAL | IPAddressUtil.HOSTMAP_IPV4;
String canonicalNames[] = getLocalHostAddresses(typeMask);
if (canonicalNames.length == 0) {
typeMask |= IPAddressUtil.HOSTMAP_NAME;
canonicalNames = getLocalHostAddresses(typeMask);
}
return canonicalNames;
}
/**
* Find out if the given host name is the local host.
*
* @param host String hostname or IP address
* @return <code>true</code> if the given host refers to the local host.
*/
public boolean isLocalHost(String host) {
if (host == null) { return false; }
String hostLower = host.toLowerCase();
// Don't trim, perhaps it is possible to have addresses ended by space
// hostLower = hostLower.trim();
synchronized (this) {
if (fLocalHostAddresses.containsKey(hostLower)) {
return true;
} else if (fNonLocalHostAddresses.contains(hostLower)) { return false; }
}
// Make sure that the name service lookup (probably time consuming!)
// is outside the synchronized block.
return addHostName(hostLower);
}
/**
* Find out if two IP Addresses refer to the same host.
*
* @param h1 host name or IP address
* @param h2 host name or IP address
* @return
*/
public boolean isSameHost(String h1, String h2) {
if (h1 == null) {
return (h2 == null);
} else if (h2 == null) { return false; }
h1 = h1.trim();
h2 = h2.trim();
if (h1.equalsIgnoreCase(h2)) { return true; }
// The local host can be referred to by several methods...
if (isLocalHost(h1) && isLocalHost(h2)) { return true; }
// Compare IP-addresses? How to know if two hosts are the same?
// Only the first IP-Address we get should be checked. But what if
// h1 and h2 are different IP-Addresses referring to the same host?
// The check would be complex and probably slow since name service
// must be invoked. So we do not do it for now.
return false;
}
// Initialize-On-Demand Holder Class idiom:
// Lazy initialization and thread-safe single instance.
// See http://www-106.ibm.com/developerworks/java/library/j-jtp03304/
private static class LazyHolder {
public static IPAddressUtil instance = new IPAddressUtil();
}
/**
* Return the singleton instance.
*/
public static IPAddressUtil getInstance() {
// LazyRegistryHolder class will be loaded in thread-safe manner
// the first time it is used.
return LazyHolder.instance;
}
}