8231259: (dc) DatagramChannel::disconnect re-binds socket to the wildcard address (macOS)

Reviewed-by: dfuchs, chegar
This commit is contained in:
Alan Bateman 2019-11-20 08:35:53 +00:00
parent 62d6862485
commit 7e42642939
11 changed files with 494 additions and 146 deletions

View File

@ -156,6 +156,13 @@ class KQueueSelectorImpl extends SelectorImpl {
int newEvents = ski.translateInterestOps();
int registeredEvents = ski.registeredEvents();
// DatagramChannelImpl::disconnect has reset socket
if (ski.getAndClearReset() && registeredEvents != 0) {
KQueue.register(kqfd, fd, EVFILT_READ, EV_DELETE);
registeredEvents = 0;
}
if (newEvents != registeredEvents) {
// add or delete interest in read events

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -34,6 +34,8 @@ import java.nio.channels.IllegalSelectorException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Arrays;
import java.util.function.Consumer;
/**
@ -171,6 +173,20 @@ public abstract class AbstractSelectableChannel
}
}
/**
* Invokes an action for each key.
*
* This method is invoked by DatagramChannelImpl::disconnect.
*/
private void forEach(Consumer<SelectionKey> action) {
synchronized (keyLock) {
SelectionKey[] keys = this.keys;
if (keys != null) {
Arrays.stream(keys).filter(k -> k != null).forEach(action::accept);
}
}
}
/**
* Registers this channel with the given selector, returning a selection key.
*

View File

@ -31,6 +31,7 @@ import java.io.UncheckedIOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.ref.Cleaner.Cleanable;
import java.lang.reflect.Method;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.Inet6Address;
@ -54,12 +55,18 @@ import java.nio.channels.IllegalBlockingModeException;
import java.nio.channels.MembershipKey;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.nio.channels.spi.AbstractSelectableChannel;
import java.nio.channels.spi.SelectorProvider;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import jdk.internal.ref.CleanerFactory;
import sun.net.ResourceManager;
@ -113,10 +120,13 @@ class DatagramChannelImpl
private long readerThread;
private long writerThread;
// Binding and remote address (when connected)
// Local and remote (connected) address
private InetSocketAddress localAddress;
private InetSocketAddress remoteAddress;
// Local address prior to connecting
private InetSocketAddress initialLocalAddress;
// Socket adaptor, created lazily
private static final VarHandle SOCKET;
static {
@ -1103,6 +1113,9 @@ class DatagramChannelImpl
bindInternal(null);
}
// capture local address before connect
initialLocalAddress = localAddress;
int n = Net.connect(family,
fd,
isa.getAddress(),
@ -1160,21 +1173,19 @@ class DatagramChannelImpl
remoteAddress = null;
state = ST_UNCONNECTED;
// check whether rebind is needed
InetSocketAddress isa = Net.localAddress(fd);
if (isa.getPort() == 0) {
// On Linux, if bound to ephemeral port,
// disconnect does not preserve that port.
// In this case, try to rebind to the previous port.
int port = localAddress.getPort();
localAddress = isa; // in case Net.bind fails
Net.bind(family, fd, isa.getAddress(), port);
isa = Net.localAddress(fd); // refresh address
assert isa.getPort() == port;
// refresh localAddress, should be same as it was prior to connect
localAddress = Net.localAddress(fd);
try {
if (!localAddress.equals(initialLocalAddress)) {
// Workaround connect(2) issues on Linux and macOS
repairSocket(initialLocalAddress);
assert (localAddress != null)
&& localAddress.equals(Net.localAddress(fd))
&& localAddress.equals(initialLocalAddress);
}
} finally {
initialLocalAddress = null;
}
// refresh localAddress
localAddress = isa;
}
} finally {
writeLock.unlock();
@ -1185,6 +1196,134 @@ class DatagramChannelImpl
return this;
}
/**
* "Repair" the channel's socket after a disconnect that didn't restore the
* local address.
*
* On Linux, connect(2) dissolves the association but changes the local port
* to 0 when it was initially bound to an ephemeral port. The workaround here
* is to rebind to the original port.
*
* On macOS, connect(2) dissolves the association but rebinds the socket to
* the wildcard address when it was initially bound to a specific address.
* The workaround here is to re-create the socket.
*/
private void repairSocket(InetSocketAddress target)
throws IOException
{
assert Thread.holdsLock(stateLock);
// Linux: try to bind the socket to the original address/port
if (localAddress.getPort() == 0) {
assert localAddress.getAddress().equals(target.getAddress());
Net.bind(family, fd, target.getAddress(), target.getPort());
localAddress = Net.localAddress(fd);
return;
}
// capture the value of all existing socket options
Map<SocketOption<?>, Object> map = new HashMap<>();
for (SocketOption<?> option : supportedOptions()) {
Object value = getOption(option);
if (value != null) {
map.put(option, value);
}
}
// macOS: re-create the socket.
FileDescriptor newfd = Net.socket(family, false);
try {
// copy the socket options that are protocol family agnostic
for (Map.Entry<SocketOption<?>, Object> e : map.entrySet()) {
SocketOption<?> option = e.getKey();
if (SocketOptionRegistry.findOption(option, Net.UNSPEC) != null) {
Object value = e.getValue();
try {
Net.setSocketOption(newfd, Net.UNSPEC, option, value);
} catch (IOException ignore) { }
}
}
// copy the blocking mode
if (!isBlocking()) {
IOUtil.configureBlocking(newfd, false);
}
// dup this channel's socket to the new socket. If this succeeds then
// fd will reference the new socket. If it fails then it will still
// reference the old socket.
nd.dup(newfd, fd);
} finally {
// release the file descriptor
nd.close(newfd);
}
// bind to the original local address
try {
Net.bind(family, fd, target.getAddress(), target.getPort());
} catch (IOException ioe) {
// bind failed, socket is left unbound
localAddress = null;
throw ioe;
}
// restore local address
localAddress = Net.localAddress(fd);
// restore all socket options (including those set in first pass)
for (Map.Entry<SocketOption<?>, Object> e : map.entrySet()) {
@SuppressWarnings("unchecked")
SocketOption<Object> option = (SocketOption<Object>) e.getKey();
Object value = e.getValue();
try {
setOption(option, value);
} catch (IOException ignore) { }
}
// restore multicast group membership
MembershipRegistry registry = this.registry;
if (registry != null) {
registry.forEach(k -> {
if (k instanceof MembershipKeyImpl.Type6) {
MembershipKeyImpl.Type6 key6 = (MembershipKeyImpl.Type6) k;
Net.join6(fd, key6.groupAddress(), key6.index(), key6.source());
} else {
MembershipKeyImpl.Type4 key4 = (MembershipKeyImpl.Type4) k;
Net.join4(fd, key4.groupAddress(), key4.interfaceAddress(), key4.source());
}
});
}
// reset registration in all Selectors that this channel is registered with
AbstractSelectableChannels.forEach(this, SelectionKeyImpl::reset);
}
/**
* Defines static methods to access AbstractSelectableChannel non-public members.
*/
private static class AbstractSelectableChannels {
private static final Method FOREACH;
static {
try {
PrivilegedExceptionAction<Method> pae = () -> {
Method m = AbstractSelectableChannel.class.getDeclaredMethod("forEach", Consumer.class);
m.setAccessible(true);
return m;
};
FOREACH = AccessController.doPrivileged(pae);
} catch (Exception e) {
throw new InternalError(e);
}
}
static void forEach(AbstractSelectableChannel ch, Consumer<SelectionKeyImpl> action) {
try {
FOREACH.invoke(ch, action);
} catch (Exception e) {
throw new InternalError(e);
}
}
}
/**
* Joins channel's socket to the given group/interface and
* optional source address.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2009, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,10 +25,14 @@
package sun.nio.ch;
import java.nio.channels.*;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.*;
import java.nio.channels.MembershipKey;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Simple registry of membership keys for a MulticastChannel.
@ -38,8 +42,8 @@ import java.util.*;
class MembershipRegistry {
// map multicast group to keys
private Map<InetAddress,List<MembershipKeyImpl>> groups = null;
// map multicast group to list of keys
private Map<InetAddress, List<MembershipKeyImpl>> groups;
MembershipRegistry() {
}
@ -116,16 +120,29 @@ class MembershipRegistry {
}
}
@FunctionalInterface
interface ThrowingConsumer<T, X extends Throwable> {
void accept(T action) throws X;
}
/**
* Invalidate all keys in the registry
* Invoke an action for each key in the registry
*/
void invalidateAll() {
<X extends Throwable>
void forEach(ThrowingConsumer<MembershipKeyImpl, X> action) throws X {
if (groups != null) {
for (InetAddress group: groups.keySet()) {
for (MembershipKeyImpl key: groups.get(group)) {
key.invalidate();
for (List<MembershipKeyImpl> keys : groups.values()) {
for (MembershipKeyImpl key : keys) {
action.accept(key);
}
}
}
}
/**
* Invalidate all keys in the registry
*/
void invalidateAll() {
forEach(MembershipKeyImpl::invalidate);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,15 +25,15 @@
package sun.nio.ch;
import java.io.*;
import java.io.FileDescriptor;
import java.io.IOException;
/**
* Allows different platforms to call different native methods
* for read and write operations.
*/
abstract class NativeDispatcher
{
abstract class NativeDispatcher {
abstract int read(FileDescriptor fd, long address, int len)
throws IOException;
@ -77,4 +77,13 @@ abstract class NativeDispatcher
// Do nothing by default; this is only needed on Unix
}
/**
* Duplicates a file descriptor.
* @param fd1 the file descriptor to duplicate
* @param fd2 the new file descriptor, the socket or file that it is connected
* to will be closed by this method
*/
void dup(FileDescriptor fd1, FileDescriptor fd2) throws IOException {
throw new UnsupportedOperationException();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -58,6 +58,9 @@ public final class SelectionKeyImpl
// registered events in kernel, used by some Selector implementations
private int registeredEvents;
// registered events need to be reset, used by some Selector implementations
private volatile boolean reset;
// index of key in pollfd array, used by some Selector implementations
private int index;
@ -184,6 +187,26 @@ public final class SelectionKeyImpl
index = i;
}
/**
* Sets the reset flag, re-queues the key, and wakeups up the Selector
*/
void reset() {
reset = true;
selector.setEventOps(this);
selector.wakeup();
}
/**
* Clears the reset flag, returning the previous value of the flag
*/
boolean getAndClearReset() {
assert Thread.holdsLock(selector);
boolean r = reset;
if (r)
reset = false;
return r;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,16 +25,16 @@
package sun.nio.ch;
import java.io.*;
import java.net.*;
import java.io.FileDescriptor;
import java.io.IOException;
/**
* Allows different platforms to call different native methods
* for read and write operations.
*/
class DatagramDispatcher extends NativeDispatcher
{
class DatagramDispatcher extends NativeDispatcher {
static {
IOUtil.load();
}
@ -63,6 +63,10 @@ class DatagramDispatcher extends NativeDispatcher
FileDispatcherImpl.preClose0(fd);
}
void dup(FileDescriptor fd1, FileDescriptor fd2) throws IOException {
FileDispatcherImpl.dup0(fd1, fd2);
}
static native int read0(FileDescriptor fd, long address, int len)
throws IOException;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -108,6 +108,10 @@ class FileDispatcherImpl extends FileDispatcher {
preClose0(fd);
}
void dup(FileDescriptor fd1, FileDescriptor fd2) throws IOException {
dup0(fd1, fd2);
}
FileDescriptor duplicateForMapping(FileDescriptor fd) {
// file descriptor not required for mapping operations; okay
// to return invalid file descriptor.
@ -176,6 +180,8 @@ class FileDispatcherImpl extends FileDispatcher {
static native void preClose0(FileDescriptor fd) throws IOException;
static native void dup0(FileDescriptor fd1, FileDescriptor fd2) throws IOException;
static native void closeIntFD(int fd) throws IOException;
static native int setDirect0(FileDescriptor fd) throws IOException;

View File

@ -311,6 +311,14 @@ Java_sun_nio_ch_FileDispatcherImpl_preClose0(JNIEnv *env, jclass clazz, jobject
}
}
JNIEXPORT void JNICALL
Java_sun_nio_ch_FileDispatcherImpl_dup0(JNIEnv *env, jobject this, jobject fdo1, jobject fdo2)
{
if (dup2(fdval(env, fdo1), fdval(env, fdo2)) < 0) {
JNU_ThrowIOExceptionWithLastError(env, "dup2 failed");
}
}
JNIEXPORT void JNICALL
Java_sun_nio_ch_FileDispatcherImpl_closeIntFD(JNIEnv *env, jclass clazz, jint fd)
{

View File

@ -1,109 +0,0 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/* @test
* @library /test/lib
* @summary Test DatagramChannel local address after disconnect.
* @requires (os.family != "mac")
* @run testng/othervm AddressesAfterDisconnect
* @run testng/othervm -Djava.net.preferIPv6Addresses=true AddressesAfterDisconnect
* @run testng/othervm -Djava.net.preferIPv4Stack=true AddressesAfterDisconnect
*/
import jdk.test.lib.net.IPSupport;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.StandardProtocolFamily;
import java.nio.channels.DatagramChannel;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.assertFalse;
public class AddressesAfterDisconnect {
public static void main(String[] args) throws IOException {
new AddressesAfterDisconnect().execute();
}
@Test
public void execute() throws IOException {
IPSupport.throwSkippedExceptionIfNonOperational();
boolean preferIPv6 = Boolean.getBoolean("java.net.preferIPv6Addresses");
// test with default protocol family
try (DatagramChannel dc = DatagramChannel.open()) {
System.out.println("Test with default");
dc.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
test(dc);
test(dc);
}
if (IPSupport.hasIPv6()) {
// test with IPv6 only
System.out.println("Test with IPv6 only");
try (DatagramChannel dc = DatagramChannel.open(StandardProtocolFamily.INET6)) {
dc.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
test(dc);
test(dc);
}
}
if (IPSupport.hasIPv4() && !preferIPv6) {
// test with IPv4 only
System.out.println("Test with IPv4 only");
try (DatagramChannel dc = DatagramChannel.open(StandardProtocolFamily.INET)) {
dc.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
test(dc);
test(dc);
}
}
}
/**
* Connect DatagramChannel to a server, write a datagram and disconnect. Invoke
* a second or subsequent time with the same DatagramChannel instance to check
* that disconnect works as expected.
*/
static void test(DatagramChannel dc) throws IOException {
SocketAddress local = dc.getLocalAddress();
try (DatagramChannel server = DatagramChannel.open()) {
server.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
SocketAddress remote = server.getLocalAddress();
dc.connect(remote);
assertTrue(dc.isConnected());
// comment the following two lines on OS X to see JDK-8231259
assertEquals(dc.getLocalAddress(), local, "local address after connect");
assertEquals(dc.getRemoteAddress(), remote, "remote address after connect");
dc.disconnect();
assertFalse(dc.isConnected());
assertEquals(dc.getLocalAddress(), local, "local address after disconnect");
assertEquals(dc.getRemoteAddress(), null, "remote address after disconnect");
}
}
}

View File

@ -0,0 +1,228 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/* @test
* @bug 8231880 8231258
* @library /test/lib
* @summary Test DatagramChannel bound to specific address/ephemeral port after disconnect
* @run testng/othervm AfterDisconnect
* @run testng/othervm -Djava.net.preferIPv4Stack=true AfterDisconnect
* @run testng/othervm -Djava.net.preferIPv6Addresses=true AfterDisconnect
*/
import java.io.IOException;
import java.net.InetAddress;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketOption;
import java.net.StandardSocketOptions;
import java.net.StandardProtocolFamily;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.MembershipKey;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.HashMap;
import java.util.Map;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
import jdk.test.lib.net.IPSupport;
public class AfterDisconnect {
@Test
public void execute() throws IOException {
IPSupport.throwSkippedExceptionIfNonOperational();
boolean preferIPv6 = Boolean.getBoolean("java.net.preferIPv6Addresses");
InetAddress lb = InetAddress.getLoopbackAddress();
// test with default protocol family
try (DatagramChannel dc = DatagramChannel.open()) {
System.out.println("Test with default");
dc.bind(new InetSocketAddress(lb, 0));
test(dc);
test(dc);
}
// test with IPv6 socket
if (IPSupport.hasIPv6()) {
System.out.println("Test with IPv6 socket");
try (DatagramChannel dc = DatagramChannel.open(StandardProtocolFamily.INET6)) {
dc.bind(new InetSocketAddress(lb, 0));
test(dc);
test(dc);
}
}
// test with IPv4 socket
if (IPSupport.hasIPv4() && !preferIPv6) {
System.out.println("Test with IPv4 socket");
try (DatagramChannel dc = DatagramChannel.open(StandardProtocolFamily.INET)) {
dc.bind(new InetSocketAddress(lb, 0));
test(dc);
test(dc);
}
}
}
void test(DatagramChannel dc) throws IOException {
testLocalAddress(dc);
testSocketOptions(dc);
testSelectorRegistration(dc);
testMulticastGroups(dc);
}
/**
* Test that disconnect restores local address
*/
void testLocalAddress(DatagramChannel dc) throws IOException {
try (DatagramChannel server = DatagramChannel.open()) {
server.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
SocketAddress local = dc.getLocalAddress();
SocketAddress remote = server.getLocalAddress();
dc.connect(remote);
assertTrue(dc.isConnected());
assertEquals(dc.getLocalAddress(), local);
assertEquals(dc.getRemoteAddress(), remote);
dc.disconnect();
assertFalse(dc.isConnected());
assertEquals(dc.getLocalAddress(), local);
assertTrue(dc.getRemoteAddress() == null);
}
}
/**
* Test that disconnect does not change socket options
*/
void testSocketOptions(DatagramChannel dc) throws IOException {
// set a few socket options
dc.setOption(StandardSocketOptions.SO_SNDBUF, 32*1024);
dc.setOption(StandardSocketOptions.SO_RCVBUF, 64*1024);
InetAddress ia = dc.socket().getLocalAddress();
NetworkInterface ni = NetworkInterface.getByInetAddress(ia);
if (ni != null && ni.supportsMulticast())
dc.setOption(StandardSocketOptions.IP_MULTICAST_IF, ni);
// capture values of socket options
Map<SocketOption<?>, Object> map = options(dc);
dc.connect(dc.getLocalAddress());
dc.disconnect();
// check socket options have not changed
assertEquals(map, options(dc));
}
/**
* Returns a map of the given channel's socket options and values.
*/
private Map<SocketOption<?>, Object> options(DatagramChannel dc) throws IOException {
Map<SocketOption<?>, Object> map = new HashMap<>();
for (SocketOption<?> option : dc.supportedOptions()) {
try {
Object value = dc.getOption(option);
if (value != null) {
map.put(option, value);
}
} catch (IOException ignore) { }
}
return map;
}
/**
* Test that disconnect does not interfere with Selector registrations
*/
void testSelectorRegistration(DatagramChannel dc) throws IOException {
try (Selector sel = Selector.open()) {
dc.configureBlocking(false);
SelectionKey key = dc.register(sel, SelectionKey.OP_READ);
// ensure socket is registered
sel.selectNow();
dc.connect(dc.getLocalAddress());
dc.disconnect();
// selection key should still be valid
assertTrue(key.isValid());
// check blocking mode with non-blocking receive
ByteBuffer bb = ByteBuffer.allocate(100);
SocketAddress sender = dc.receive(bb);
assertTrue(sender == null);
// send datagram and ensure that channel is selected
dc.send(ByteBuffer.wrap("Hello".getBytes("UTF-8")), dc.getLocalAddress());
assertFalse(key.isReadable());
while (sel.select() == 0);
assertTrue(key.isReadable());
sender = dc.receive(bb);
assertEquals(sender, dc.getLocalAddress());
// cancel key, flush from Selector, and restore blocking mode
key.cancel();
sel.selectNow();
dc.configureBlocking(true);
}
}
/**
* Test that disconnect does not interfere with multicast group membership
*/
void testMulticastGroups(DatagramChannel dc) throws IOException {
InetAddress localAddress = dc.socket().getLocalAddress();
InetAddress group;
if (localAddress instanceof Inet6Address) {
group = InetAddress.getByName("ff02::a");
} else {
group = InetAddress.getByName("225.4.5.6");
}
NetworkInterface ni = NetworkInterface.getByInetAddress(localAddress);
if (ni != null && ni.supportsMulticast()) {
// join group
MembershipKey key = dc.join(group, ni);
dc.connect(dc.getLocalAddress());
dc.disconnect();
// membership key should still be valid
assertTrue(key.isValid());
// send datagram to multicast group, should be received
dc.send(ByteBuffer.wrap("Hello".getBytes("UTF-8")), dc.getLocalAddress());
ByteBuffer bb = ByteBuffer.allocate(100);
SocketAddress sender = dc.receive(bb);
assertEquals(sender, dc.getLocalAddress());
// drop membership
key.drop();
}
}
}