JDK-8231260 : (dc) DatagramChannel::disconnect changes the port of the local address to 0 (lnx)
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 1.4.0
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: linux
  • Submitted: 2019-09-19
  • Updated: 2020-02-12
  • Resolved: 2019-10-09
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 14
14 b18Fixed
Related Reports
Blocks :  
CSR :  
Relates :  
Relates :  
Sub Tasks
JDK-8231881 :  
On linux, after calling DatgramChannel::disconnect, DatagramChannel::getLocalAddress reports a soclet address with a local port set to 0.
This appears to be a known side effect to the native `connect` method: DatagramSocket has a work around to palliate the issue (it checks the local address, and if the port is 0, it calls `NET_bind` again. This appears to work on linux, but not on BSD.

java.lang.AssertionError: local address after disconnect should be / found /
URL: https://hg.openjdk.java.net/jdk/jdk/rev/705c3f88a409 User: dfuchs Date: 2019-10-09 16:47:00 +0000

I think the outstanding question is whether disconnect should throw IOException when the socket cannot be rebound to the original source port. The risk is that we silently break any code that has cached the source port. This leads to the question on whether the failure to rebind should attempt to re-connect the socket so that disconnect throwing an IOException leaves the channel connected. It probably should, otherwise we end up with disconnect failing with isConnected() returning false. One other complexity with throwing an exception is that DatagramSocket::disconnect is not specified to throw any exceptions, something the socket adaptor has to deal with.

Above is another possibility - close to what Alan suggested.

diff --git a/src/java.base/share/classes/sun/nio/ch/DatagramChannelImpl.java b/src/java.base/share/classes/sun/nio/ch/DatagramChannelImpl.java --- a/src/java.base/share/classes/sun/nio/ch/DatagramChannelImpl.java +++ b/src/java.base/share/classes/sun/nio/ch/DatagramChannelImpl.java @@ -932,6 +932,21 @@ remoteAddress = null; state = ST_UNCONNECTED; + InetSocketAddress isa = Net.localAddress(fd); + if (isa.getPort() == 0) { + // This may happen on Linux platforms. + // If the channel was previously bound to an ephemeral + // port, disconnect might not preserve that port. + // This is the expected behavior on linux systems. + try { + // try to rebind to previous port + Net.bind(family, fd, isa.getAddress(), localAddress.getPort()); + } catch (IOException io) { + // Previous port may no longer be available. + // try to rebind to a new port instead. + Net.bind(family, fd, isa.getAddress(), 0); + } + } // refresh local address localAddress = Net.localAddress(fd); }

>. .. whether the socket is still usable after disconnect The datagram channel can still operate somewhat. If the first operation post-disconnect is: 1) send - The send operation will implicitly re-bind. Sending to a simple echo service demonstrates this. The echo service reports the remote socket address that it receives the packet from, which has a non-0 port, but datagram channel continues to report its local address as 0. This is because the connect and subsequent disconnect has left a non-null localAddress field, but the implicit kernel bind, resulting from the send operation, goes unnoticed by datagram channel. 2) receive - The datagram channel is will receive nothing ( at least not until after a send operation ). This has been verified with a test program that sends a packet to every port on the system ( this program succeeds to send, and the other side receives, if the connect/disconnect dance is omitted ). The datagram channel is effectively unbound.

A similar patch to that of the one above, suggested by Alan, has already been verified and works around the Linux Kernel behavior. It will work, once the port has not already been taken, otherwise an exception may be thrown.

Can you try this change to disconnect: // refresh local address - localAddress = Net.localAddress(fd); + InetSocketAddress isa = Net.localAddress(fd); + if (isa.getPort() == 0) { + Net.bind(family, fd, isa.getAddress(), localAddress.getPort()); + isa = Net.localAddress(fd); + } + localAddress = isa; }

The surprising behavior is specific to cases where the socket is hasn't explicitly bound to a specific port, meaning the application isn't concerned with the source port. It might be that the right thing is to just clarify the spec to allow for platform specific behavior, and avoid workarounds in the implementation. I think the main thing we need to establish is whether the socket is still usable after disconnect, it would be surprising if this isn't the case.

Edward ( from JPG ) filed this back in 2006: https://bugzilla.kernel.org/show_bug.cgi?id=6646 - closed with "REJECTED WILL_NOT_FIX" "You can only preserve the parts of a local binding which are explicitly specified. Since you make an explicit bind to a source address, that will be preserved by the disconnect. However, since you use a zero anonymous port during the bind, the one choosen by the kernel will not be preserved. If you had choosen an explicit port during bind() it would be preserved by the disconnect. This behavior is intentional and will not change."

The test shown in JDK-8231261 can be used to reproduce - and verify - the issue.