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.
Related
I am new with the network programming and I have a few questions, that I couldn't find anywhere.
I don't understand if there is a difference in code between IPv4 and IPv6, when establishing connection.
Example Code :
Socket socket = new Socket(“127.0.0.1”, 5000)
The above code is used for IPv4, as I understood. But how do I initialize the socket if I want to use IPv6?
I don't understand if there is a difference in code between ipv4 and ipv6, when establishing connection.
There is little difference.
If you want to use an explicit IPv6 address, you will typically just instantiate the Socket with a IP address string in IPv6 syntax.
If you use a DNS name, then the available network stacks will determine whether you use IPv4 or IPv6:
If only one stack is supported (by the OS) and available, that is used.
If both stacks are available, the setting of the java.net.preferIPv4Stack property determines which is used.
For more information, read Networking IPv6 User Guide from the Oracle Java documentation.
For example this: Socket socket = new Socket("127.0.0.1", 5000) is used for ip4, as I understood. But how do I initialize the socket if I want to use ip6?
Socket socket = new Socket("::1", 5000);
See also: What is IPV6 for localhost and 0.0.0.0?
If a Java application creates a ServerSocket that accepts TCP connections, is there a way to restrict which processes are allowed to connect to it?
For example, this is my current code:
ServerSocket serverSocket = new ServerSocket(1234);
Socket socket = serverSocket.accept();
and I want to make sure that other devices on my network and even other processes running on the same machine are not able to connect to it (it would be a security risk if they did). I was able to solve the former by binding serverSocket only to the loopback address (checking if socket.getRemoteAddress() points to the local host would work too) but I couldn't find a way to restrict it to my current process.
This is even more of a problem when doing it on Android. In my application, I want to create a WebView (owned by my process) and point it to serverSocket but I don't want others apps to be able to connect to it.
Is there a way to solve this problem?
You can bind it to 127.0.0.1,[1] which prevents any process outside the localhost from even seeing it. But any process in the localhost can connect to it. That being what it's for. If you want to restrict that to certain processes you will have to implement an authentication step in your protocol.
Hard to see why. If you can't trust other processes in the localhost you have a rather large problemm in general, not just here.
[1] Or indeed 127.0.0.x where 1 <= x <= 254. Using an obscure number like 200 might help by obscurity but it still isn't really secure.
I don't think that you can prevent other processes from connecting to the ServerSocket but you accept a connection you can definitely determine if it belongs to you or to some other process. The first step is figure out if the connection originated from localhost:
InetSocketAddress remoteAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
String hostname = remoteAddress.getHostName();
if (!hostname.equals("localhost")) { socket.close(); }
Alternatively you can bind the socket to a loopback address like 127.0.0.1 or 0.0.0.0 (like EJP mentioned) and skip this step. Once you know that the connection came from localhost all you have to do is find the remote port and figure out if your process owns it.
int remotePort = remoteAddress.getPort();
if (ownPort(remotePort) == 1) { socket.close(); }
As far as I know, Java doesn't have an API that you can use to list your process ports but you can definitely do that via JNI. On the Java side you would need something like:
private native int doOwnPort(int port);
And on the native side:
JNIEXPORT jint JNICALL Java_something_doOwnPort(JNIEnv *env, jobject object, jint port) {
long totalFDs = getdtablesize();
struct sockaddr_in sa;
struct stat sb;
// Iterate through all file descriptors
for (int i = 0; i < totalFDs; ++i) {
// Check if "i" is a valid FD
memset(&sb, 0, sizeof(sb));
if (fstat(i, &sb) < 0)
continue;
// Check if "i" is a socket
if (!S_ISSOCK(sb.st_mode))
continue;
// Get local address of socket with FD "i"
memset(&sa, 0, sizeof(sa));
socklen_t sa_len = sizeof(sa);
getsockname(i, (struct sockaddr*) &sa, &sa_len);
// Check if the port matches
if (sa.sin_port == port)
return 1; // We own the port
}
return -1; // We don't own the port
}
PS: This code is for Linux but should work on Android/Windows/OSX too.
Maybe there is a more direct/efficient way to check if the port is owned by the current process without having to iterate through the FD table but that's a separate problem. HTH!
You can get this type of security
using firewalls
implementing some kind of authentication yourself on your ServerSocket. Ask for username and password?
Sockets were not designed for restricting to distinct processes.
From Android Security Tips:
We have seen some applications use localhost network ports for handling sensitive IPC. We discourage this approach since these interfaces are accessible by other applications on the device. Instead, you should use an Android IPC mechanism where authentication is possible such as with a Service. (Even worse than using loopback is to bind to INADDR_ANY since then your application may receive requests from anywhere.)
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.
I want to find an open local port in some range.
How can I do that in the most efficient way, without connecting to the port.
If you want to find a local open port to bind a server to, then you can create a ServerSocket and if it does not throw an Exception, then it's open.
I did the following in one of my projects:
private int getAvailablePort() throws IOException {
int port = 0;
do {
port = RANDOM.get().nextInt(20000) + 10000;
} while (!isPortAvailable(port));
return port;
}
private boolean isPortAvailable(final int port) throws IOException {
ServerSocket ss = null;
try {
ss = new ServerSocket(port);
ss.setReuseAddress(true);
return true;
} catch (final IOException e) {
} finally {
if (ss != null) {
ss.close();
}
}
return false;
}
RANDOM is a ThreadLocal here, but of course you can do an incrementing part there.
There's a little problem you may face in a multitasking windows/unix environment: if some isPortAvailable(final int port) from any of the answers returns a true for you, that doesn't mean, that at the moment when you will actually bind it it still will be available. The better solution would be create a method
ServerSocket tryBind(int portRangeBegin, int portRangeEnd) throws UnableToBindException;
So that you will just launch it and receive open socket for you, on some available port in the given range.
If you mean port in a remote server, then you might need a library that support raw-socket to send a sync packet and wait for sync-ack packet, just like nmap does.
One way to do is use some native network command and parse the output.
You can try netstat command as its available on Windows and *nix platforms.
Typical command would be netstat -n
Its output is of following format.
you need to parse the 'Foreign Address column for localhost or 127.0.0.1' and get a list of busy ports. Then see if they are in the range you specified.
If this is not about port-sniffing, but about service discovery, consider using a rendez-vous server (like an RMI server) or using the UDP protocol. Back in the day we used JXTA for this, but I hope there is a better alternative for this now.
Essentially the same idea as Karaszi, but instead of constructing that many sockets, use the InetSocketAddress and try to bind a ServerSocket to every address in range, until you hit an open one.
If you don't want to bind to that port (although if you don't, the socket may as well be bound the next moment after you check), use a plain Socket Object and try to connect to the ports - if it works, the port is taken, if it doesn't (and you don't have a firewall forbiding the connection), then it's most likely free.
Just pass zero as the port number to new ServerSocket(), then it will find one for you. But you can forget about the range, it will choose from the system-defined range.
When using Netty, I was surprised that if I use reuseAddress option, it allows a ServerSocket to bind to the same address without raising an "already bind exception"
ServerBootstrap bootstrap = new ServerBootstrap(
new NioServerSocketChannelFactory(Executors
.newCachedThreadPool(), Executors.newCachedThreadPool()));
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
#Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline p = pipeline();
p.addLast("handler", new DummyHandler());
return p;
}
});
bootstrap.setOption("reuseAddress", true);
bootstrap.bind(new InetSocketAddress(2000));
bootstrap.bind(new InetSocketAddress(2000));
I just thought that reuseAddress allows a new socket to reuse a close-wait socket, but this is different. The following is the result of a netstat command
C:\Users\secmask>netstat -a -n|grep 2000
TCP 0.0.0.0:2000 0.0.0.0:0 LISTENING
TCP 0.0.0.0:2000 0.0.0.0:0 LISTENING
Am I missing something? What's going on?
What you are seeing is what reuseAddress is supposed to do. Multiple sockets can be bound to the same IP/Port at the same time, regardless of their states.
I assume that Windows allows this due to history. It is a bit of a security issue. See http://msdn.microsoft.com/en-us/library/ms740618 for some information about how the involved options interact. Which socket gets a connection is undefined. Maybe if you narrow down the version of Windows you are using you could narrow down what the response will be although it is probably just to not depend on it.