package net.i2p.util; /* * public domain */ import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.InetAddress; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.apache.http.conn.util.InetAddressUtils; import net.i2p.I2PAppContext; import net.i2p.data.DataHelper; /** * Methods to get the local addresses, and other IP utilities * * @since 0.8.3 moved to core from router/transport * @author zzz */ public abstract class Addresses { private static final File IF_INET6_FILE = new File("/proc/net/if_inet6"); private static final long INET6_CACHE_EXPIRE = 10*60*1000; private static final boolean INET6_CACHE_ENABLED = !SystemVersion.isMac() && !SystemVersion.isWindows() && !SystemVersion.isAndroid() && IF_INET6_FILE.exists(); private static final int FLAG_PERMANENT = 0x80; private static final int FLAG_DEPRECATED = 0x20; private static final int FLAG_TEMPORARY = 0x01; private static long _ifCacheTime; private static final Map<Inet6Address, Inet6Addr> _ifCache = INET6_CACHE_ENABLED ? new HashMap<Inet6Address, Inet6Addr>(8) : null; /** * Do we have any non-loop, non-wildcard IPv4 address at all? * @since 0.9.4 */ public static boolean isConnected() { // not as good as using a Java DBus implementation to talk to NetworkManager... return !getAddresses(true, false, false).isEmpty(); } /** * Do we have any non-loop, non-wildcard IPv6 address at all? * @since 0.9.29 */ public static boolean isConnectedIPv6() { // not as good as using a Java DBus implementation to talk to NetworkManager... for (String ip : getAddresses(false, true)) { if (ip.contains(":")) return true; } return false; } /** @return the first non-local address IPv4 address it finds, or null */ public static String getAnyAddress() { SortedSet<String> a = getAddresses(); if (!a.isEmpty()) return a.first(); return null; } /** * @return a sorted set of all addresses, excluding * IPv6, local, broadcast, multicast, etc. */ public static SortedSet<String> getAddresses() { return getAddresses(false, false); } /** * @return a sorted set of all addresses, excluding * only link local and multicast * @since 0.8.3 */ public static SortedSet<String> getAllAddresses() { return getAddresses(true, true); } /** * Warning: When includeLocal is false, * all returned addresses should be routable, but they are not necessarily * appropriate for external use. For example, Teredo and 6to4 addresses * are included with IPv6 results. Additional validation is recommended. * See e.g. TransportUtil.isPubliclyRoutable(). * * @return a sorted set of all addresses including wildcard * @param includeLocal whether to include local * @param includeIPv6 whether to include IPV6 * @return a Set of all addresses * @since 0.8.3 */ public static SortedSet<String> getAddresses(boolean includeLocal, boolean includeIPv6) { return getAddresses(includeLocal, includeLocal, includeIPv6); } /** * Warning: When includeSiteLocal and includeLoopbackAndWildcard are false, * all returned addresses should be routable, but they are not necessarily * appropriate for external use. For example, Teredo and 6to4 addresses * are included with IPv6 results. Additional validation is recommended. * See e.g. TransportUtil.isPubliclyRoutable(). * * @return a sorted set of all addresses * @param includeSiteLocal whether to include private like 192.168.x.x * @param includeLoopbackAndWildcard whether to include 127.x.x.x and 0.0.0.0 * @param includeIPv6 whether to include IPV6 * @return a Set of all addresses * @since 0.9.4 */ public static SortedSet<String> getAddresses(boolean includeSiteLocal, boolean includeLoopbackAndWildcard, boolean includeIPv6) { boolean haveIPv4 = false; boolean haveIPv6 = false; SortedSet<String> rv = new TreeSet<String>(); final boolean omitDeprecated = INET6_CACHE_ENABLED && !includeSiteLocal && includeIPv6; try { InetAddress localhost = InetAddress.getLocalHost(); InetAddress[] allMyIps = InetAddress.getAllByName(localhost.getCanonicalHostName()); if (allMyIps != null) { for (int i = 0; i < allMyIps.length; i++) { boolean isv4 = allMyIps[i] instanceof Inet4Address; if (isv4) haveIPv4 = true; else haveIPv6 = true; if (omitDeprecated && !isv4) { if (isDeprecated((Inet6Address) allMyIps[i])) continue; } if (shouldInclude(allMyIps[i], includeSiteLocal, includeLoopbackAndWildcard, includeIPv6)) { rv.add(stripScope(allMyIps[i].getHostAddress())); } } } } catch (UnknownHostException e) {} try { Enumeration<NetworkInterface> ifcs = NetworkInterface.getNetworkInterfaces(); if (ifcs != null) { while (ifcs.hasMoreElements()) { NetworkInterface ifc = ifcs.nextElement(); for(Enumeration<InetAddress> addrs = ifc.getInetAddresses(); addrs.hasMoreElements();) { InetAddress addr = addrs.nextElement(); boolean isv4 = addr instanceof Inet4Address; if (isv4) haveIPv4 = true; else haveIPv6 = true; if (omitDeprecated && !isv4) { if (isDeprecated((Inet6Address) addr)) continue; } if (shouldInclude(addr, includeSiteLocal, includeLoopbackAndWildcard, includeIPv6)) { rv.add(stripScope(addr.getHostAddress())); } } } } } catch (SocketException e) { } catch (java.lang.Error e) { // Windows, possibly when IPv6 only... // https://bugs.openjdk.java.net/browse/JDK-8046500 // java.lang.Error: IP Helper Library GetIfTable function failed // at java.net.NetworkInterface.getAll(Native Method) // at java.net.NetworkInterface.getNetworkInterfaces(Unknown Source) // at net.i2p.util.Addresses.getAddresses ... } if (includeLoopbackAndWildcard) { if (haveIPv4) rv.add("0.0.0.0"); if (includeIPv6 && haveIPv6) rv.add("0:0:0:0:0:0:0:0"); // we could do "::" but all the other ones are probably in long form } return rv; } /** * Strip the trailing "%nn" from Inet6Address.getHostAddress() * @since IPv6 */ private static String stripScope(String ip) { int pct = ip.indexOf('%'); if (pct > 0) ip = ip.substring(0, pct); return ip; } private static boolean shouldInclude(InetAddress ia, boolean includeSiteLocal, boolean includeLoopbackAndWildcard, boolean includeIPv6) { return (!ia.isLinkLocalAddress()) && // 169.254.x.x (!ia.isMulticastAddress()) && (includeLoopbackAndWildcard || ((!ia.isAnyLocalAddress()) && (!ia.isLoopbackAddress()))) && (includeSiteLocal || ((!ia.isSiteLocalAddress()) && // disallow fc00::/8 and fd00::/8 (Unique local addresses RFC 4193) // not recognized as local by InetAddress (ia.getAddress().length != 16 || (ia.getAddress()[0] & 0xfe) != 0xfc))) && // Hamachi 5/8 allocated to RIPE (30 November 2010) // Removed from TransportImpl.isPubliclyRoutable() // Check moved to here, for now, but will eventually need to // remove it from here also. //(includeLocal || //(!ia.getHostAddress().startsWith("5."))) && (includeIPv6 || (ia instanceof Inet4Address)); } /** * Convenience method to convert an IP address to a String * without throwing an exception. * @return "null" for null, and "bad IP length x" if length is invalid * @since 0.8.12 */ public static String toString(byte[] addr) { if (addr == null) return "null"; try { return InetAddress.getByAddress(addr).getHostAddress(); } catch (UnknownHostException uhe) { return "bad IP length " + addr.length; } } /** * Convenience method to convert an IP address and port to a String * without throwing an exception. * @return "ip:port" * @since 0.8.12 */ public static String toString(byte[] addr, int port) { if (addr == null) return "null:" + port; try { String ip = InetAddress.getByAddress(addr).getHostAddress(); if (addr.length != 16) return ip + ':' + port; return '[' + ip + "]:" + port; } catch (UnknownHostException uhe) { return "(bad IP length " + addr.length + "):" + port; } } /** * Convenience method to convert and validate a port String * without throwing an exception. * Does not trim. * * @return 1-65535 or 0 if invalid * @since 0.9.3 */ public static int getPort(String port) { int rv = 0; if (port != null) { try { int iport = Integer.parseInt(port); if (iport > 0 && iport <= 65535) rv = iport; } catch (NumberFormatException nfe) {} } return rv; } /** * Textual IP to bytes, because InetAddress.getByName() is slow. * * @since 0.9.3 */ private static final Map<String, byte[]> _IPAddress; static { int size; I2PAppContext ctx = I2PAppContext.getCurrentContext(); if (ctx != null && ctx.isRouterContext()) { long maxMemory = SystemVersion.getMaxMemory(); long min = 256; long max = 4096; // 1024 nominal for 128 MB size = (int) Math.max(min, Math.min(max, 1 + (maxMemory / (128*1024)))); } else { size = 32; } _IPAddress = new LHMCache<String, byte[]>(size); } /** * Caching version of InetAddress.getByName(host).getAddress(), which is slow. * Caches numeric host names only. * Will resolve but not cache DNS host names. * * Unlike InetAddress.getByName(), we do NOT allow numeric IPs * of the form d.d.d, d.d, or d, as these are almost certainly mistakes. * * @param host DNS or IPv4 or IPv6 host name; if null returns null * @return IP or null * @since 0.9.3 */ public static byte[] getIP(String host) { if (host == null) return null; byte[] rv; synchronized (_IPAddress) { rv = _IPAddress.get(host); } if (rv == null) { try { rv = InetAddress.getByName(host).getAddress(); if (InetAddressUtils.isIPv4Address(host) || InetAddressUtils.isIPv6Address(host)) { synchronized (_IPAddress) { _IPAddress.put(host, rv); } } } catch (UnknownHostException uhe) {} } return rv; } /** * For literal IP addresses, this is the same as getIP(String). * For host names, will return the preferred type (IPv4/v6) if available, * else the other type if available. * Will resolve but not cache DNS host names. * * @param host DNS or IPv4 or IPv6 host name; if null returns null * @return IP or null * @since 0.9.28 */ public static byte[] getIP(String host, boolean preferIPv6) { if (host == null) return null; if (InetAddressUtils.isIPv4Address(host) || InetAddressUtils.isIPv6Address(host)) return getIP(host); byte[] rv = null; try { InetAddress[] addrs = InetAddress.getAllByName(host); if (addrs == null || addrs.length == 0) return null; for (int i = 0; i < addrs.length; i++) { rv = addrs[i].getAddress(); if (preferIPv6) { if (rv.length == 16) break; } else { if (rv.length == 4) break; } } } catch (UnknownHostException uhe) {} return rv; } /** * For literal IP addresses, this is the same as getIP(String). * For host names, may return multiple addresses, both IPv4 and IPv6, * even if those addresses are not reachable due to configuration or available interfaces. * Will resolve but not cache DNS host names. * * Note that order of returned results, and whether * multiple results for either IPv4 or IPv6 or both are actually * returned, is platform-specific and may also depend on * JVM options such as java.net.preverIPv4Stack and java.net.preferIPv6Addresses. * Number of results may also change based on caching at various layers, * even if the ultimate name server results did not change. * * @param host DNS or IPv4 or IPv6 host name; if null returns null * @return non-empty list IPs, or null if none * @since 0.9.28 */ public static List<byte[]> getIPs(String host) { if (host == null) return null; if (InetAddressUtils.isIPv4Address(host) || InetAddressUtils.isIPv6Address(host)) { byte[] brv = getIP(host); if (brv == null) return null; return Collections.singletonList(brv); } try { InetAddress[] addrs = InetAddress.getAllByName(host); if (addrs == null || addrs.length == 0) return null; List<byte[]> rv = new ArrayList<byte[]>(addrs.length); for (int i = 0; i < addrs.length; i++) { rv.add(addrs[i].getAddress()); } return rv; } catch (UnknownHostException uhe) {} return null; } //////// IPv6 Cache Utils /////// /** * @since 0.9.28 */ private static class Inet6Addr { private final Inet6Address addr; private final boolean isDyn, isDep, isTemp; public Inet6Addr(Inet6Address a, int flags) { addr = a; isDyn = (flags & FLAG_PERMANENT) == 0; isDep = (flags & FLAG_DEPRECATED) != 0; isTemp = (flags & FLAG_TEMPORARY) != 0; } public Inet6Address getAddress() { return addr; } public boolean isDynamic() { return isDyn; } public boolean isDeprecated() { return isDep; } public boolean isTemporary() { return isTemp; } } /** * Only call if INET6_CACHE_ENABLED. * Caller must sync on _ifCache. * @since 0.9.28 */ private static void refreshCache() { long now = System.currentTimeMillis(); if (now - _ifCacheTime < INET6_CACHE_EXPIRE) return; _ifCache.clear(); BufferedReader in = null; try { in = new BufferedReader(new InputStreamReader(new FileInputStream(IF_INET6_FILE), "ISO-8859-1"), 1024); String line = null; while ( (line = in.readLine()) != null) { // http://tldp.org/HOWTO/html_single/Linux+IPv6-HOWTO/#PROC-NET // 00000000000000000000000000000001 01 80 10 80 lo String[] parts = DataHelper.split(line, " ", 6); if (parts.length < 5) continue; String as = parts[0]; if (as.length() != 32) continue; StringBuilder buf = new StringBuilder(40); int i = 0; while(true) { buf.append(as.substring(i, i+4)); i += 4; if (i >= 32) break; buf.append(':'); } Inet6Address addr; try { addr = (Inet6Address) InetAddress.getByName(buf.toString()); } catch (UnknownHostException uhe) { continue; } int flags = FLAG_PERMANENT; try { flags = Integer.parseInt(parts[4], 16); } catch (NumberFormatException nfe) {} Inet6Addr a = new Inet6Addr(addr, flags); _ifCache.put(addr, a); } } catch (IOException ioe) { } finally { if (in != null) try { in.close(); } catch (IOException ioe) {} } _ifCacheTime = now; } /** * Is this address dynamic? * Returns false if unknown. * @since 0.9.28 */ public static boolean isDynamic(Inet6Address addr) { if (!INET6_CACHE_ENABLED) return false; Inet6Addr a; synchronized(_ifCache) { refreshCache(); a = _ifCache.get(addr); } if (a == null) return false; return a.isDynamic(); } /** * Is this address deprecated? * Returns false if unknown. * @since 0.9.28 */ public static boolean isDeprecated(Inet6Address addr) { if (!INET6_CACHE_ENABLED) return false; Inet6Addr a; synchronized(_ifCache) { refreshCache(); a = _ifCache.get(addr); } if (a == null) return false; return a.isDeprecated(); } /** * Is this address temporary? * Returns false if unknown. * @since 0.9.28 */ public static boolean isTemporary(Inet6Address addr) { if (!INET6_CACHE_ENABLED) return false; Inet6Addr a; synchronized(_ifCache) { refreshCache(); a = _ifCache.get(addr); } if (a == null) return false; return a.isTemporary(); } //////// End IPv6 Cache Utils /////// /** * @since 0.9.3 */ public static void clearCaches() { synchronized(_IPAddress) { _IPAddress.clear(); } if (_ifCache != null) { synchronized(_ifCache) { _ifCache.clear(); _ifCacheTime = 0; } } } /** * Print out the local addresses */ public static void main(String[] args) { System.out.println("External IPv4 Addresses:"); Set<String> a = getAddresses(false, false, false); for (String s : a) System.out.println(s); System.out.println("\nExternal and Local IPv4 Addresses:"); a = getAddresses(true, false, false); for (String s : a) System.out.println(s); System.out.println("\nAll External Addresses:"); a = getAddresses(false, false, true); for (String s : a) System.out.println(s); System.out.println("\nAll External and Local Addresses:"); a = getAddresses(true, false, true); for (String s : a) System.out.println(s); System.out.println("\nAll addresses:"); a = getAddresses(true, true, true); for (String s : a) System.out.println(s); System.out.println("\nIPv6 address flags:"); for (String s : a) { if (!s.contains(":")) continue; StringBuilder buf = new StringBuilder(64); buf.append(s); Inet6Address addr; try { addr = (Inet6Address) InetAddress.getByName(buf.toString()); if (addr.isSiteLocalAddress()) buf.append(" host"); else if (addr.isLinkLocalAddress()) buf.append(" link"); else if (addr.isAnyLocalAddress()) buf.append(" wildcard"); else if (addr.isLoopbackAddress()) buf.append(" loopback"); else buf.append(" global"); if (isTemporary(addr)) buf.append(" temporary"); if (isDeprecated(addr)) buf.append(" deprecated"); if (isDynamic(addr)) buf.append(" dynamic"); } catch (UnknownHostException uhe) {} System.out.println(buf.toString()); } System.out.println("\nIs connected? " + isConnected() + "\nHas IPv6? " + isConnectedIPv6()); } }