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.
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();
}
}
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.
Related
I have some Java code that is generating a socket binding. It's hard to provide a minimal example as this is part of a web framework, but it effectively does this check at some point.
private static boolean portInUse(int port) {
// try to bind to this port, if it succeeds the port is not in use
try (ServerSocket socket = new ServerSocket(port)) {
socket.setReuseAddress(true);
return false;
} catch (IOException e) {
return true;
}
}
I can see that if I run two distinct Java processes with the same port, they both fall into the first conditional and return false, thus both are able to bind to the same port. I've read through some related socket questions and explanations like this one, but they seem to make it sound like this shouldn't be possible with the options I've specified. Looking at the implementation of setReuseAddress it only seems to set SO_REUSEADDR on the socket.
I can see one process ends up with a socket like ServerSocket[addr=0.0.0.0/0.0.0.0,localport=56674] in a debugger. If I run something like sudo lsof -n -i | grep -e LISTEN -e ESTABLISHED | grep 56674 I can see two processes binding to the same port:
java 68863 natdempk 1256u IPv4 0xbbac93fff9a6e677 0t0 TCP *:56674 (LISTEN)
java 68998 natdempk 985u IPv6 0xbbac93fff2f84daf 0t0 TCP *:56674 (LISTEN)
I can also see some other projects like gRPC and Node mention this behavior as being observed with their servers in issue trackers, but they never explain why this is possible. How can distinct processes bind to the same socket on macOS?
I am running macOS 11.6.3 (20G415) if that is at all helpful. Happy to provide more debug info as well if anyone has anything I should add here.
They are not binding to the same port. One is binding to TCP on top of IPv6, the other is binding to TCP on top of IPv4.
To expand on the Java details a bit: new ServerSocket(port) in Java uses InetAddress.anyLocalAddress() because no InetAddress was passed in. InetAddress.anyLocalAddress() can return either an IPv4 or IPv6 address, which means this isn't guaranteed to be the same value to bind to across JVMs despite the same port being passed in.
I am trying to use the VpnService from android to setup a simple tun device on the client side and on the receiving side I have a c++ server running.
I am having a lot of problems with the VpnService. This is what I need,
I need ALL packets outbound from the Android phone to be routed to the tun device, and in the program I route it through a Datagram channel to the server. When I send a string, it works fine, but when I send other data through this Datagram channel, i don't see any UDP packets in Wireshark :\
Also, I am new to Java and Datagram channels. Here Is my code
//To establish the tunnel
builder.setSession("MyVPNService")
.addAddress("192.168.56.0", 32)
.addDnsServer("8.8.8.4")
.addRoute("0.0.0.0", 1);
mInterface=builder.establish();
What exactly are the above configurations doing? Isn't a tun device supposed to have ONE IP(from my experience from doing it on linux), then what is ""192.168.56.0", 32". Also when i try to add a route "0.0.0.0", 0 the whole android phone hangs and restarts :\
while (true) {
int length;
// Read the outgoing packet from the input stream.
length=in.read(packet_bytes);
//int length = in.read(packet.array());
if (length > 0) {
// Write the outgoing packet to the tunnel.
//packet.limit(length);
//tunnel.send(packe,server);
tunnel.send(packet,server);
packet.put(packet_bytes,0,length);
tunnel.write(packet);
packet.clear();
}
Thread.sleep(200);
// Read the incoming packet from the tunnel.
length = tunnel.read(packet);
if (length > 0) {
out.write(packet.array(), 0, length);
packet.clear();
// If we were sending, switch to receiving.
}
Thread.sleep(200);
}
This is the part where I take it from interface and put it on the other.
First, let me start by explaining Builder configuration above.
builder.setSession("MyVPNService") // This one is optional.
.addAddress("192.168.56.0", 32) // This is used to assign interface address. First param is IP address, and second in prefix length. "Prefix" length is also commonly known as subnet mask.
.addDnsServer("8.8.8.4") // This configures the DNS network for VPN network. For ex - All DNS resolutions would go to 8.8.8.4:53. Note that the DNS request packets gets routed through the tun interface.
.addRoute("0.0.0.0", 1); // This controls the IP addresses which gets routed through tun interface.
Note - that tun interface can support multiple address families (IPv4/IPv6). As an example, you can assign multiple interface addresses (say a v4, a v6, or two v6 addresses, or whatever combo).
Similarly, you can add routes that you want your VPN to handle. Now, the main question is how do you decide which routes should my VPN handle?
Well there are bunch of options.
Route everything - Adding 0.0.0.0/0 (for IPv4), and ::/0 (for IPv6) would route traffic for all destinations through VPN (Note: 0.0.0.0/0 represents entire IPv4 range i.e. 0.0.0.0 to 255.255.255.255).
Route specific routes - You would have typically noticed that talking to IoT devices does not work when VPN is running. That is typically due to "route everything" config setup which breaks local networking (ex - chromecast). So, excluding link local traffic requires doing some math that involves subtracting link local subnets from above subnets (0.0.0.0/0, ::/0 (for v6 local subnets)). The math involved is not very straightforward which makes this option a lot more complex. As for what constitutes link local subnets, here is a list from wikipedia, and from IETF for IPv4 and IPv6 special addresses.
That said, here are some answers to your questions.
I need ALL packets outbound from the Android phone to be routed to the tun device
See "route everything" above.
Isn't a tun device supposed to have ONE IP?
An interface on linux can have multiple interface addresses assigned to it from different address families.
Then what is "192.168.56.0", 32".
As explained above, first part is the IP address, and second defines the subnet mask. Also see CIDR notation.
Also when I try to add a route "0.0.0.0", 0 the whole android phone hangs and restarts.
0.0.0.0/0 means entire IPv4 address space will get routed through the VPN. Typically, a VPN cannot handle link local traffic as I have mentioned above. So, you will have to exclude certain local subnets (see links above). As for phone hanging and restarting, I'm not sure if that has anything to do with the VPN unless VPN is not handling traffic correctly (which would lead networking related apps to break).
Is there a way to listen to DNS changes using Java? I'm looking to update an external system with DNS changes as they happen.
Existing solution is to listen to bind.log for changes.
Monitoring a DNS server for changes over the internet requires that you are granted DNS zone transfer access (AXFR/IXFR). Once you have zone transfer access, you can pull a copy of the DNS data held by the server, but without such permission your options are rather limited. For instance, you can poll the server for RR changes for known names, but you can't possibly detect new names using just public DNS access.
Another alternative is to use a passive DNS data provider, but the coverage these provide may be limited and the service might be expensive. At least one of the major providers of passive DNS data works by tapping into DNS traffic, so they see new names and changes as they appear in DNS traffic.
This is similar to this question: Resolving ip-address of a hostname
To resolve a host in Java:
InetAddress address = InetAddress.getByName("www.example.com");
Now you could run this on a separate Thread and listen for the change:
public void launchThread()
{
Thread thread = new Thread(new Runnable()
{
InetAddress start = InetAddress.getByName("www.example.com");
while(start.equals(InetAddress.getByName("www.example.com")))
{
try
{
Thread.sleep(1000);
}catch(Exception e){e.printStackTrace();}
}
System.out.println("Domain resolution has changed.");
})
}
It there an API (NetworkInterface, InetAddress, etc) in Java with which I can detect that a network card is not connected.
The problem is that addressing some of the network API has a large timeout. But if the network card is not connected to any cable then we can skip it. The problem seems only to occur with Windows 7.
I had this same problem. I ended up using this:
Enumeration<NetworkInterface> eni = NetworkInterface.getNetworkInterfaces();
while(eni.hasMoreElements()) {
Enumeration<InetAddress> eia = eni.nextElement().getInetAddresses();
while(eia.hasMoreElements()) {
InetAddress ia = eia.nextElement();
if (!ia.isAnyLocalAddress() && !ia.isLoopbackAddress() && !ia.isSiteLocalAddress()) {
if (!ia.getHostName().equals(ia.getHostAddress()))
return true;
}
}
}
Works with Java 5+. Also indirectly checks DNS (by comparing names/addresses).
You could use some java system properties to set up the default time out for socket connection using
sun.net.client.defaultConnectTimeout
(all properties here)
But it seems to me that checking wether a specific Medium Access Controller is present or not is the job of the underlying OS. I can't tell you much about windows 7, I got a good OS since 12 years now (linux).
Regards,
Stéphane
I'm using Clojure, but I can read Java, so this isn't a Clojure specific question. This doesn't even seem to be working from Java.
I'm trying to implement a bit of a 'ping' function using isReachable. The code I'm using is this:
(.isReachable (java.net.InetAddress/getByName "www.microsoft.com") 5000)
Translated to Java by a good friend of mine:
public class NetTest {
public static void main (String[] args) throws Exception{
String host = "acidrayne.net";
InetAddress a = InetAddress.getByName(host);
System.out.println(a.isReachable(10000));
}
}
Both of these return false. I suppose I must be doin' it wrong, but Google research is telling me differently. I'm confuzzled!
Updated in response to comment that this is wrong:
Using Unix/Linux??
http://bordet.blogspot.com/2006/07/icmp-and-inetaddressisreachable.html says:
Linux/Unix, instead, supports an ICMP "ping" system call. So the implementation of java.net.InetAddress.isReachable() first tries to perform the "ping" system call**; if this fails, it falls back trying to open a TCP socket on [sic - to] port 7, as in Windows.
It turns out that in Linux/Unix the ping system call requires root privileges, so most of the times java.net.InetAddress.isReachable() will fail, because many Java programs are not run as root, and because the target address unlikely has the echo service up and running. Too bad.
The comment below from #EJP indicates the part of about the echo service is wrong, wrong wrong:
That's not correct. isReachable returns true if it gets a ConnectException trying to connect to port 7, as that proves that the host is up and able to send RST segments.
In cases like these, I use a packet sniffer like WireShark, tcpdump (WinDump on Windows) or snoop (Solaris) to confirm what is really happening on the wire.
The correct answer is not actually correct I think. Microsoft.com simply ignore ICMP requests, probably to avoid basic ping flood attacks. As for the second host I've no idea what the problem with the ping might be, but I'm using GNU/Linux and isReachable works just fine.