IPv6 usage is slowly starting nowadays, so I'm currently in the process of fixing and updating all applications to be prepared for IPv6.
One of the applications is the Java editor JOSM (http://josm.openstreetmap.de/). Java does not really use IPv6 in the default configuration even if the OS uses IPv6.
According to
http://docs.oracle.com/javase/1.5.0/docs/guide/net/ipv6_guide/#using
I set java.net.preferIPv6Addresses
to true
to let it use IPv6. Result have been user bug reports about broken internet connection.
It seems Java only switches to use IPv6 address instead of IPv4, but does nothing else. All C/C++ based software I maintain has been changed to check and try all available IP addresses, so broken IPv6 (or IPv4) addresses are skipped as long as one of the addresses works. For me it looks like Java only tries once, which does not work in real world.
Also usually the OS prefers IPv4 over IPv6, when IPv6 is tunneled. It seems like Java does ignore this settings as well.
So my question is: Are there any good ways to get a Java application to use IPV6 by default when available without breaking the application for IPv4 users.
User-bug reports: http://josm.openstreetmap.de/ticket/8562, http://josm.openstreetmap.de/ticket/8627.
So you have two problems here:
Operating system vendors ship OSes with broken default IPv6 configurations, and/or users enable broken IPv6 configurations.
When it doesn't work, they mistakenly blame you.
There are two things you can do here:
Advise users on how to disable unnecessary and broken IPv6 transition mechanisms such as Teredo, ISATAP and 6to4. Instructions for these are widely available on the Internet.
It would also be nice if certain OS vendors would not enable this crap by default, but that's probably asking too much.
Implement Happy Eyeballs (RFC 6555) in your application. This is how modern web browsers solve this problem.
Happy Eyeballs specifies an algorithm whereby an application tries to connect via IPv6 and IPv4 at (almost) the same time, and if IPv6 isn't working within a short amount of time, to fall back to the IPv4 connection. The results of this trial are also cached for a few minutes.
Unfortunately I'm not familiar enough with Java to give you specific code to bypass all the interesting stuff Oracle is hiding from you by default, but it should be doable.
It seems that topic is interesting for others as well, so I describe my current solution.
- The software does an detection whether IPv6 works or not and remembers the state -> This is done by doing a TCP connect to a known IPv6 address (Ping of isReachable() is not reliable, see this bug report: https://josm.openstreetmap.de/ticket/11452).
- Based on the remembered state the software starts with "java.net.preferIPv6Addresses" set to "true".
- This means for a switch from IPv4 to a IPv6 network it will use IPv4 until next restart which is ok.
- For a switch from an IPv6 enabled to an IPv4 only network it will not work at all which is solved by restarting the software.
- In case of doubt we assume IPv6 does not work.
- It is not possible to change "java.net.preferIPv6Addresses" after doing the detection, as that values seems to be read only before the first network connection. If there is a way to reset that state during runtime I'd like to know about it.
This solution seems to work, we have about 4% IPv6 connections in our logs ATM, but is is not really a satisfying solution.
/**
* Check if IPv6 can be safely enabled and do so. Because this cannot be done after network activation,
* disabling or enabling IPV6 may only be done with next start.
*/
private static void checkIPv6() {
if ("auto".equals(Main.pref.get("prefer.ipv6", "auto"))) {
new Thread(new Runnable() { /* this may take some time (DNS, Connect) */
public void run() {
boolean hasv6 = false;
boolean wasv6 = Main.pref.getBoolean("validated.ipv6", false);
try {
/* Use the check result from last run of the software, as after the test, value
changes have no effect anymore */
if (wasv6) {
Utils.updateSystemProperty("java.net.preferIPv6Addresses", "true");
}
for (InetAddress a : InetAddress.getAllByName("josm.openstreetmap.de")) {
if (a instanceof Inet6Address) {
if (a.isReachable(1000)) {
/* be sure it REALLY works */
Socket s = new Socket();
s.connect(new InetSocketAddress(a, 80), 1000);
s.close();
Utils.updateSystemProperty("java.net.preferIPv6Addresses", "true");
if (!wasv6) {
Main.info(tr("Detected useable IPv6 network, prefering IPv6 over IPv4 after next restart."));
} else {
Main.info(tr("Detected useable IPv6 network, prefering IPv6 over IPv4."));
}
hasv6 = true;
}
break; /* we're done */
}
}
} catch (IOException | SecurityException e) {
if (Main.isDebugEnabled()) {
Main.debug("Exception while checking IPv6 connectivity: "+e);
}
}
if (wasv6 && !hasv6) {
Main.info(tr("Detected no useable IPv6 network, prefering IPv4 over IPv6 after next restart."));
Main.pref.put("validated.ipv6", hasv6); // be sure it is stored before the restart!
new RestartAction().actionPerformed(null);
}
Main.pref.put("validated.ipv6", hasv6);
}
}, "IPv6-checker").start();
}
}