Transport: Improve IPv6 selection logic

to skip temporary addresses on linux
This commit is contained in:
zzz
2016-11-08 03:24:30 +00:00
parent ab064fd31e
commit cd775fa38d
2 changed files with 255 additions and 21 deletions

View File

@@ -4,14 +4,21 @@ package net.i2p.util;
* public domain * 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.InetAddress;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -21,6 +28,8 @@ import java.util.TreeSet;
import org.apache.http.conn.util.InetAddressUtils; import org.apache.http.conn.util.InetAddressUtils;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
/** /**
* Methods to get the local addresses, and other IP utilities * Methods to get the local addresses, and other IP utilities
@@ -30,6 +39,16 @@ import net.i2p.I2PAppContext;
*/ */
public abstract class Addresses { 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? * Do we have any non-loop, non-wildcard IPv4 address at all?
* @since 0.9.4 * @since 0.9.4
@@ -101,20 +120,27 @@ public abstract class Addresses {
boolean haveIPv4 = false; boolean haveIPv4 = false;
boolean haveIPv6 = false; boolean haveIPv6 = false;
SortedSet<String> rv = new TreeSet<String>(); SortedSet<String> rv = new TreeSet<String>();
final boolean omitDeprecated = INET6_CACHE_ENABLED && !includeSiteLocal && includeIPv6;
try { try {
InetAddress localhost = InetAddress.getLocalHost(); InetAddress localhost = InetAddress.getLocalHost();
InetAddress[] allMyIps = InetAddress.getAllByName(localhost.getCanonicalHostName()); InetAddress[] allMyIps = InetAddress.getAllByName(localhost.getCanonicalHostName());
if (allMyIps != null) { if (allMyIps != null) {
for (int i = 0; i < allMyIps.length; i++) { for (int i = 0; i < allMyIps.length; i++) {
if (allMyIps[i] instanceof Inet4Address) boolean isv4 = allMyIps[i] instanceof Inet4Address;
if (isv4)
haveIPv4 = true; haveIPv4 = true;
else else
haveIPv6 = true; haveIPv6 = true;
if (omitDeprecated && !isv4) {
if (isDeprecated((Inet6Address) allMyIps[i]))
continue;
}
if (shouldInclude(allMyIps[i], includeSiteLocal, if (shouldInclude(allMyIps[i], includeSiteLocal,
includeLoopbackAndWildcard, includeIPv6)) includeLoopbackAndWildcard, includeIPv6)) {
rv.add(stripScope(allMyIps[i].getHostAddress())); rv.add(stripScope(allMyIps[i].getHostAddress()));
} }
} }
}
} catch (UnknownHostException e) {} } catch (UnknownHostException e) {}
try { try {
@@ -124,16 +150,22 @@ public abstract class Addresses {
NetworkInterface ifc = ifcs.nextElement(); NetworkInterface ifc = ifcs.nextElement();
for(Enumeration<InetAddress> addrs = ifc.getInetAddresses(); addrs.hasMoreElements();) { for(Enumeration<InetAddress> addrs = ifc.getInetAddresses(); addrs.hasMoreElements();) {
InetAddress addr = addrs.nextElement(); InetAddress addr = addrs.nextElement();
if (addr instanceof Inet4Address) boolean isv4 = addr instanceof Inet4Address;
if (isv4)
haveIPv4 = true; haveIPv4 = true;
else else
haveIPv6 = true; haveIPv6 = true;
if (omitDeprecated && !isv4) {
if (isDeprecated((Inet6Address) addr))
continue;
}
if (shouldInclude(addr, includeSiteLocal, if (shouldInclude(addr, includeSiteLocal,
includeLoopbackAndWildcard, includeIPv6)) includeLoopbackAndWildcard, includeIPv6)) {
rv.add(stripScope(addr.getHostAddress())); rv.add(stripScope(addr.getHostAddress()));
} }
} }
} }
}
} catch (SocketException e) { } catch (SocketException e) {
} catch (java.lang.Error e) { } catch (java.lang.Error e) {
// Windows, possibly when IPv6 only... // Windows, possibly when IPv6 only...
@@ -333,8 +365,8 @@ public abstract class Addresses {
/** /**
* For literal IP addresses, this is the same as getIP(String). * For literal IP addresses, this is the same as getIP(String).
* For host names, will return the preferred type (IPv4/v6) if available, * For host names, may return multiple addresses, both IPv4 and IPv6,
* else the other type if available. * even if those addresses are not reachable due to configuration or available interfaces.
* Will resolve but not cache DNS host names. * Will resolve but not cache DNS host names.
* *
* Note that order of returned results, and whether * Note that order of returned results, and whether
@@ -370,6 +402,136 @@ public abstract class Addresses {
return null; 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"), 4096);
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 * @since 0.9.3
*/ */
@@ -377,32 +539,66 @@ public abstract class Addresses {
synchronized(_IPAddress) { synchronized(_IPAddress) {
_IPAddress.clear(); _IPAddress.clear();
} }
if (_ifCache != null) {
synchronized(_ifCache) {
_ifCache.clear();
_ifCacheTime = 0;
}
}
} }
/** /**
* Print out the local addresses * Print out the local addresses
*/ */
public static void main(String[] args) { public static void main(String[] args) {
System.err.println("External IPv4 Addresses:"); System.out.println("External IPv4 Addresses:");
Set<String> a = getAddresses(false, false, false); Set<String> a = getAddresses(false, false, false);
for (String s : a) for (String s : a)
System.err.println(s); System.out.println(s);
System.err.println("\nExternal and Local IPv4 Addresses:"); System.out.println("\nExternal and Local IPv4 Addresses:");
a = getAddresses(true, false, false); a = getAddresses(true, false, false);
for (String s : a) for (String s : a)
System.err.println(s); System.out.println(s);
System.err.println("\nAll External Addresses:"); System.out.println("\nAll External Addresses:");
a = getAddresses(false, false, true); a = getAddresses(false, false, true);
for (String s : a) for (String s : a)
System.err.println(s); System.out.println(s);
System.err.println("\nAll External and Local Addresses:"); System.out.println("\nAll External and Local Addresses:");
a = getAddresses(true, false, true); a = getAddresses(true, false, true);
for (String s : a) for (String s : a)
System.err.println(s); System.out.println(s);
System.err.println("\nAll addresses:"); System.out.println("\nAll addresses:");
a = getAddresses(true, true, true); a = getAddresses(true, true, true);
for (String s : a) for (String s : a)
System.err.println(s); System.out.println(s);
System.err.println("\nIs connected? " + isConnected()); 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());
} }
} }

View File

@@ -11,6 +11,7 @@ package net.i2p.router.transport;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Inet6Address;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@@ -165,7 +166,7 @@ public class TransportManager implements TransportEventListener {
// so that NTCP may bind early // so that NTCP may bind early
int port = udp.getRequestedPort(); int port = udp.getRequestedPort();
if (port > 0) if (port > 0)
ntcp.externalAddressReceived(SOURCE_CONFIG, null, port); ntcp.externalAddressReceived(SOURCE_CONFIG, (byte[]) null, port);
} }
} }
if (_transports.isEmpty()) if (_transports.isEmpty())
@@ -182,15 +183,52 @@ public class TransportManager implements TransportEventListener {
*/ */
private void initializeAddress(Transport t) { private void initializeAddress(Transport t) {
Set<String> ipset = Addresses.getAddresses(false, true); // non-local, include IPv6 Set<String> ipset = Addresses.getAddresses(false, true); // non-local, include IPv6
//
// Avoid IPv6 temporary addresses if we have a non-temporary one
//
boolean hasNonTempV6Address = false;
List<InetAddress> addresses = new ArrayList<InetAddress>(4);
List<Inet6Address> tempV6Addresses = new ArrayList<Inet6Address>(4);
for (String ips : ipset) { for (String ips : ipset) {
try { try {
InetAddress ia = InetAddress.getByName(ips); InetAddress addr = InetAddress.getByName(ips);
byte[] ip = ia.getAddress(); if (ips.contains(":") && (addr instanceof Inet6Address)) {
t.externalAddressReceived(SOURCE_INTERFACE, ip, 0); Inet6Address v6addr = (Inet6Address) addr;
// getAddresses(false, true) will not return deprecated addresses
//if (Addresses.isDeprecated(v6addr)) {
// if (_log.shouldWarn())
// _log.warn("Not binding to deprecated temporary address " + bt);
// continue;
//}
if (Addresses.isTemporary(v6addr)) {
// Save temporary addresses
// we only use these if we don't have a non-temporary adress
tempV6Addresses.add(v6addr);
continue;
}
hasNonTempV6Address = true;
}
addresses.add(addr);
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
_log.error("UDP failed to bind to local address", e); _log.error("UDP failed to bind to local address", e);
} }
} }
// we only use these if we don't have a non-temporary adress
if (!tempV6Addresses.isEmpty()) {
if (hasNonTempV6Address) {
if (_log.shouldWarn()) {
for (Inet6Address addr : tempV6Addresses) {
_log.warn("Not binding to temporary address " + addr.getHostAddress());
}
}
} else {
addresses.addAll(tempV6Addresses);
}
}
for (InetAddress ia : addresses) {
byte[] ip = ia.getAddress();
t.externalAddressReceived(SOURCE_INTERFACE, ip, 0);
}
} }
/** /**