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.
CodePudding user response:
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.