8233435: (dc) DatagramChannel should allow IPv6 socket join IPv4 multicast groups (macOS, win)
Reviewed-by: dfuchs
--- a/src/java.base/share/classes/sun/nio/ch/DatagramChannelImpl.java Sat Nov 09 09:13:04 2019 +0000
+++ b/src/java.base/share/classes/sun/nio/ch/DatagramChannelImpl.java Sat Nov 09 11:48:37 2019 +0000
@@ -239,6 +239,40 @@
}
}
+ /**
+ * Returns the protocol family to specify to set/getSocketOption for the
+ * given socket option.
+ */
+ private ProtocolFamily familyFor(SocketOption<?> name) {
+ assert Thread.holdsLock(stateLock);
+
+ // unspecified (most options)
+ if (SocketOptionRegistry.findOption(name, Net.UNSPEC) != null)
+ return Net.UNSPEC;
+
+ // IPv4 socket
+ if (family == StandardProtocolFamily.INET)
+ return StandardProtocolFamily.INET;
+
+ // IPv6 socket that is unbound
+ if (localAddress == null)
+ return StandardProtocolFamily.INET6;
+
+ // IPv6 socket bound to wildcard or IPv6 address
+ InetAddress address = localAddress.getAddress();
+ if (address.isAnyLocalAddress() || (address instanceof Inet6Address))
+ return StandardProtocolFamily.INET6;
+
+ // IPv6 socket bound to IPv4 address
+ if (Net.canUseIPv6OptionsWithIPv4LocalAddress()) {
+ // IPV6_XXX options can be used
+ return StandardProtocolFamily.INET6;
+ } else {
+ // IPV6_XXX options cannot be used
+ return StandardProtocolFamily.INET;
+ }
+ }
+
@Override
public <T> DatagramChannel setOption(SocketOption<T> name, T value)
throws IOException
@@ -252,14 +286,7 @@
synchronized (stateLock) {
ensureOpen();
- if (name == StandardSocketOptions.IP_TOS ||
- name == StandardSocketOptions.IP_MULTICAST_TTL ||
- name == StandardSocketOptions.IP_MULTICAST_LOOP)
- {
- // options are protocol dependent
- Net.setSocketOption(fd, family, name, value);
- return this;
- }
+ ProtocolFamily family = familyFor(name);
if (name == StandardSocketOptions.IP_MULTICAST_IF) {
NetworkInterface interf = (NetworkInterface)value;
@@ -285,7 +312,7 @@
}
// remaining options don't need any special handling
- Net.setSocketOption(fd, Net.UNSPEC, name, value);
+ Net.setSocketOption(fd, family, name, value);
return this;
}
}
@@ -302,12 +329,7 @@
synchronized (stateLock) {
ensureOpen();
- if (name == StandardSocketOptions.IP_TOS ||
- name == StandardSocketOptions.IP_MULTICAST_TTL ||
- name == StandardSocketOptions.IP_MULTICAST_LOOP)
- {
- return (T) Net.getSocketOption(fd, family, name);
- }
+ ProtocolFamily family = familyFor(name);
if (name == StandardSocketOptions.IP_MULTICAST_IF) {
if (family == StandardProtocolFamily.INET) {
@@ -333,11 +355,11 @@
}
if (name == StandardSocketOptions.SO_REUSEADDR && reuseAddressEmulated) {
- return (T)Boolean.valueOf(isReuseAddress);
+ return (T) Boolean.valueOf(isReuseAddress);
}
// no special handling
- return (T) Net.getSocketOption(fd, Net.UNSPEC, name);
+ return (T) Net.getSocketOption(fd, family, name);
}
}
--- a/src/java.base/share/classes/sun/nio/ch/Net.java Sat Nov 09 09:13:04 2019 +0000
+++ b/src/java.base/share/classes/sun/nio/ch/Net.java Sat Nov 09 11:48:37 2019 +0000
@@ -121,6 +121,14 @@
return canJoin6WithIPv4Group0();
}
+ /**
+ * Tells whether IPV6_XXX socket options should be used on an IPv6 socket
+ * that is bound to an IPv4 address.
+ */
+ static boolean canUseIPv6OptionsWithIPv4LocalAddress() {
+ return canUseIPv6OptionsWithIPv4LocalAddress0();
+ }
+
public static InetSocketAddress checkAddress(SocketAddress sa) {
if (sa == null)
throw new NullPointerException();
@@ -434,6 +442,8 @@
private static native boolean canJoin6WithIPv4Group0();
+ private static native boolean canUseIPv6OptionsWithIPv4LocalAddress0();
+
static FileDescriptor socket(boolean stream) throws IOException {
return socket(UNSPEC, stream);
}
--- a/src/java.base/unix/native/libnio/ch/Net.c Sat Nov 09 09:13:04 2019 +0000
+++ b/src/java.base/unix/native/libnio/ch/Net.c Sat Nov 09 11:48:37 2019 +0000
@@ -158,24 +158,34 @@
JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_Net_canIPv6SocketJoinIPv4Group0(JNIEnv* env, jclass cl)
{
-#if defined(__APPLE__) || defined(_AIX)
- /* for now IPv6 sockets cannot join IPv4 multicast groups */
+#if defined(__linux__) || defined(__APPLE__) || defined(__solaris__)
+ /* IPv6 sockets can join IPv4 multicast groups */
+ return JNI_TRUE;
+#else
+ /* IPv6 sockets cannot join IPv4 multicast groups */
return JNI_FALSE;
-#else
- return JNI_TRUE;
#endif
}
JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_Net_canJoin6WithIPv4Group0(JNIEnv* env, jclass cl)
{
-#ifdef __solaris__
+#if defined(__APPLE__) || defined(__solaris__)
+ /* IPV6_ADD_MEMBERSHIP can be used to join IPv4 multicast groups */
return JNI_TRUE;
#else
+ /* IPV6_ADD_MEMBERSHIP cannot be used to join IPv4 multicast groups */
return JNI_FALSE;
#endif
}
+JNIEXPORT jboolean JNICALL
+Java_sun_nio_ch_Net_canUseIPv6OptionsWithIPv4LocalAddress0(JNIEnv* env, jclass cl)
+{
+ /* IPV6_XXX socket options can be used on IPv6 sockets bound to IPv4 address */
+ return JNI_TRUE;
+}
+
JNIEXPORT jint JNICALL
Java_sun_nio_ch_Net_socket0(JNIEnv *env, jclass cl, jboolean preferIPv6,
jboolean stream, jboolean reuse, jboolean ignored)
--- a/src/java.base/windows/native/libnio/ch/Net.c Sat Nov 09 09:13:04 2019 +0000
+++ b/src/java.base/windows/native/libnio/ch/Net.c Sat Nov 09 11:48:37 2019 +0000
@@ -127,12 +127,21 @@
JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_Net_canIPv6SocketJoinIPv4Group0(JNIEnv* env, jclass cl)
{
- return JNI_FALSE;
+ /* IPv6 sockets can join IPv4 multicast groups */
+ return JNI_TRUE;
}
JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_Net_canJoin6WithIPv4Group0(JNIEnv* env, jclass cl)
{
+ /* IPV6_ADD_MEMBERSHIP cannot be used to join IPv4 multicast groups */
+ return JNI_FALSE;
+}
+
+JNIEXPORT jboolean JNICALL
+Java_sun_nio_ch_Net_canUseIPv6OptionsWithIPv4LocalAddress0(JNIEnv* env, jclass cl)
+{
+ /* IPV6_XXX socket options cannot be used on IPv6 sockets bound to IPv4 address */
return JNI_FALSE;
}
@@ -279,7 +288,7 @@
SOCKETADDRESS sa;
int sa_len = sizeof(sa);
- if (getsockname(fdval(env, fdo), &sa.sa, &sa_len) < 0) {
+ if (getsockname(fdval(env, fdo), &sa.sa, &sa_len) == SOCKET_ERROR) {
int error = WSAGetLastError();
if (error == WSAEINVAL) {
return 0;
@@ -297,7 +306,7 @@
int sa_len = sizeof(sa);
int port;
- if (getsockname(fdval(env, fdo), &sa.sa, &sa_len) < 0) {
+ if (getsockname(fdval(env, fdo), &sa.sa, &sa_len) == SOCKET_ERROR) {
NET_ThrowNew(env, WSAGetLastError(), "getsockname");
return NULL;
}
@@ -310,7 +319,7 @@
SOCKETADDRESS sa;
int sa_len = sizeof(sa);
- if (getpeername(fdval(env, fdo), &sa.sa, &sa_len) < 0) {
+ if (getpeername(fdval(env, fdo), &sa.sa, &sa_len) == SOCKET_ERROR) {
int error = WSAGetLastError();
if (error == WSAEINVAL) {
return 0;
@@ -328,7 +337,7 @@
int sa_len = sizeof(sa);
int port;
- if (getpeername(fdval(env, fdo), &sa.sa, &sa_len) < 0) {
+ if (getpeername(fdval(env, fdo), &sa.sa, &sa_len) == SOCKET_ERROR) {
NET_ThrowNew(env, WSAGetLastError(), "getsockname");
return NULL;
}
@@ -366,7 +375,7 @@
} else {
n = getsockopt(fdval(env, fdo), level, opt, arg, &arglen);
}
- if (n < 0) {
+ if (n == SOCKET_ERROR) {
handleSocketError(env, WSAGetLastError());
return IOS_THROWN;
}
@@ -410,7 +419,7 @@
} else {
n = setsockopt(fdval(env, fdo), level, opt, parg, arglen);
}
- if (n < 0)
+ if (n == SOCKET_ERROR)
handleSocketError(env, WSAGetLastError());
}
@@ -439,7 +448,7 @@
}
n = setsockopt(fdval(env,fdo), IPPROTO_IP, opt, optval, optlen);
- if (n < 0) {
+ if (n == SOCKET_ERROR) {
if (join && (WSAGetLastError() == WSAENOPROTOOPT))
return IOS_UNAVAILABLE;
handleSocketError(env, WSAGetLastError());
@@ -461,7 +470,7 @@
n = setsockopt(fdval(env,fdo), IPPROTO_IP, opt,
(void*)&mreq_source, sizeof(mreq_source));
- if (n < 0) {
+ if (n == SOCKET_ERROR) {
if (block && (WSAGetLastError() == WSAENOPROTOOPT))
return IOS_UNAVAILABLE;
handleSocketError(env, WSAGetLastError());
@@ -516,8 +525,8 @@
n = setGroupSourceReqOption(env, fdo, opt, group, index, source);
}
- if (n < 0) {
- handleSocketError(env, errno);
+ if (n == SOCKET_ERROR) {
+ handleSocketError(env, WSAGetLastError());
}
return 0;
}
@@ -528,8 +537,8 @@
{
int opt = (block) ? MCAST_BLOCK_SOURCE : MCAST_UNBLOCK_SOURCE;
int n = setGroupSourceReqOption(env, fdo, opt, group, index, source);
- if (n < 0) {
- handleSocketError(env, errno);
+ if (n == SOCKET_ERROR) {
+ handleSocketError(env, WSAGetLastError());
}
return 0;
}
@@ -545,7 +554,7 @@
n = setsockopt(fdval(env, fdo), IPPROTO_IP, IP_MULTICAST_IF,
(void*)&(in.s_addr), arglen);
- if (n < 0) {
+ if (n == SOCKET_ERROR) {
handleSocketError(env, WSAGetLastError());
}
}
@@ -558,7 +567,7 @@
int n;
n = getsockopt(fdval(env, fdo), IPPROTO_IP, IP_MULTICAST_IF, (void*)&in, &arglen);
- if (n < 0) {
+ if (n == SOCKET_ERROR) {
handleSocketError(env, WSAGetLastError());
return IOS_THROWN;
}
@@ -568,27 +577,27 @@
JNIEXPORT void JNICALL
Java_sun_nio_ch_Net_setInterface6(JNIEnv* env, jobject this, jobject fdo, jint index)
{
- int value = (jint)index;
+ DWORD value = (jint)index;
int arglen = sizeof(value);
int n;
n = setsockopt(fdval(env, fdo), IPPROTO_IPV6, IPV6_MULTICAST_IF,
(void*)&(index), arglen);
- if (n < 0) {
- handleSocketError(env, errno);
+ if (n == SOCKET_ERROR) {
+ handleSocketError(env, WSAGetLastError());
}
}
JNIEXPORT jint JNICALL
Java_sun_nio_ch_Net_getInterface6(JNIEnv* env, jobject this, jobject fdo)
{
- int index;
+ DWORD index;
int arglen = sizeof(index);
int n;
n = getsockopt(fdval(env, fdo), IPPROTO_IPV6, IPV6_MULTICAST_IF, (void*)&index, &arglen);
- if (n < 0) {
- handleSocketError(env, errno);
+ if (n == SOCKET_ERROR) {
+ handleSocketError(env, WSAGetLastError());
return -1;
}
return (jint)index;
--- a/test/jdk/java/nio/channels/DatagramChannel/MulticastSendReceiveTests.java Sat Nov 09 09:13:04 2019 +0000
+++ b/test/jdk/java/nio/channels/DatagramChannel/MulticastSendReceiveTests.java Sat Nov 09 11:48:37 2019 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 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
@@ -22,11 +22,12 @@
*/
/* @test
- * @bug 4527345 7026376 6633549
+ * @bug 4527345 7026376 6633549 8233435
* @summary Unit test for DatagramChannel's multicast support
* @library /test/lib
* @build jdk.test.lib.NetworkConfiguration
* jdk.test.lib.Platform
+ * jdk.test.lib.net.IPSupport
* MulticastSendReceiveTests
* @run main MulticastSendReceiveTests
* @run main/othervm -Djava.net.preferIPv4Stack=true MulticastSendReceiveTests
@@ -41,6 +42,7 @@
import java.io.IOException;
import java.util.stream.Collectors;
+import jdk.test.lib.Platform;
import jdk.test.lib.NetworkConfiguration;
import jdk.test.lib.net.IPSupport;
@@ -242,23 +244,37 @@
public static void main(String[] args) throws IOException {
IPSupport.throwSkippedExceptionIfNonOperational();
+ // IPv4 and IPv6 interfaces that support multicasting
NetworkConfiguration config = NetworkConfiguration.probe();
+ List<NetworkInterface> ip4MulticastInterfaces = config.ip4MulticastInterfaces()
+ .collect(Collectors.toList());
+ List<NetworkInterface> ip6MulticastInterfaces = config.ip6MulticastInterfaces()
+ .collect(Collectors.toList());
// multicast groups used for the test
InetAddress ip4Group = InetAddress.getByName("225.4.5.6");
InetAddress ip6Group = InetAddress.getByName("ff02::a");
- for (NetworkInterface nif: config.ip4MulticastInterfaces()
- .collect(Collectors.toList())) {
+
+ // Platforms that allow dual sockets join IPv4 multicast groups
+ boolean canIPv6JoinIPv4Group =
+ Platform.isLinux() ||
+ Platform.isOSX() ||
+ Platform.isSolaris() ||
+ Platform.isWindows();
+
+ for (NetworkInterface nif : ip4MulticastInterfaces) {
InetAddress source = config.ip4Addresses(nif).iterator().next();
+ test(UNSPEC, nif, ip4Group, source);
test(INET, nif, ip4Group, source);
- test(UNSPEC, nif, ip4Group, source);
+ if (IPSupport.hasIPv6() && canIPv6JoinIPv4Group) {
+ test(INET6, nif, ip4Group, source);
+ }
}
- for (NetworkInterface nif: config.ip6MulticastInterfaces()
- .collect(Collectors.toList())) {
+ for (NetworkInterface nif : ip6MulticastInterfaces) {
InetAddress source = config.ip6Addresses(nif).iterator().next();
+ test(UNSPEC, nif, ip6Group, source);
test(INET6, nif, ip6Group, source);
- test(UNSPEC, nif, ip6Group, source);
}
}
}
--- a/test/jdk/java/nio/channels/DatagramChannel/SocketOptionTests.java Sat Nov 09 09:13:04 2019 +0000
+++ b/test/jdk/java/nio/channels/DatagramChannel/SocketOptionTests.java Sat Nov 09 11:48:37 2019 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 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
@@ -22,10 +22,15 @@
*/
/* @test
- * @bug 4640544 8044773
+ * @bug 4640544 8044773 8233435
* @summary Unit test for setOption/getOption/options methods
* @requires !vm.graal.enabled
+ * @library /test/lib
+ * @build jdk.test.lib.net.IPSupport
+ * jdk.test.lib.NetworkConfiguration
+ * SocketOptionTests
* @run main SocketOptionTests
+ * @run main/othervm -Djava.net.preferIPv4Stack=true SocketOptionTests
* @run main/othervm --limit-modules=java.base SocketOptionTests
*/
@@ -34,23 +39,61 @@
import java.net.*;
import java.io.IOException;
import java.util.*;
+import static java.net.StandardProtocolFamily.*;
import static java.net.StandardSocketOptions.*;
+import jdk.test.lib.NetworkConfiguration;
+import jdk.test.lib.net.IPSupport;
+
public class SocketOptionTests {
- static <T> void checkOption(DatagramChannel dc,
- SocketOption<T> name,
- T expectedValue)
- throws IOException
- {
- T value = dc.getOption(name);
- if (!value.equals(expectedValue))
- throw new RuntimeException("value not as expected");
+ public static void main(String[] args) throws IOException {
+ IPSupport.throwSkippedExceptionIfNonOperational();
+
+ NetworkConfiguration config = NetworkConfiguration.probe();
+ InetAddress ip4Address = config.ip4Addresses().findAny().orElse(null);
+ InetAddress ip6Address = config.ip6Addresses().findAny().orElse(null);
+
+ System.out.println("[UNSPEC, bound to wildcard address]");
+ try (DatagramChannel dc = DatagramChannel.open()) {
+ test(dc, new InetSocketAddress(0));
+ }
+
+ if (IPSupport.hasIPv4()) {
+ System.out.println("[INET, bound to wildcard address]");
+ try (DatagramChannel dc = DatagramChannel.open(INET)) {
+ test(dc, new InetSocketAddress(0));
+ }
+ System.out.println("[INET, bound to IPv4 address]");
+ try (DatagramChannel dc = DatagramChannel.open(INET)) {
+ test(dc, new InetSocketAddress(ip4Address, 0));
+ }
+ }
+
+ if (IPSupport.hasIPv6()) {
+ System.out.println("[INET6, bound to wildcard address]");
+ try (DatagramChannel dc = DatagramChannel.open(INET6)) {
+ test(dc, new InetSocketAddress(0));
+ }
+ System.out.println("[INET6, bound to IPv6 address]");
+ try (DatagramChannel dc = DatagramChannel.open(INET6)) {
+ test(dc, new InetSocketAddress(ip6Address, 0));
+ }
+ }
+
+ if (IPSupport.hasIPv4() && IPSupport.hasIPv6()) {
+ System.out.println("[UNSPEC, bound to IPv4 address]");
+ try (DatagramChannel dc = DatagramChannel.open()) {
+ test(dc, new InetSocketAddress(ip4Address, 0));
+ }
+ System.out.println("[INET6, bound to IPv4 address]");
+ try (DatagramChannel dc = DatagramChannel.open(INET6)) {
+ test(dc, new InetSocketAddress(ip4Address, 0));
+ }
+ }
}
- public static void main(String[] args) throws IOException {
- DatagramChannel dc = DatagramChannel.open();
-
+ static void test(DatagramChannel dc, SocketAddress localAddress) throws IOException {
// check supported options
Set<SocketOption<?>> options = dc.supportedOptions();
boolean reuseport = options.contains(SO_REUSEPORT);
@@ -101,7 +144,7 @@
checkOption(dc, SO_REUSEPORT, false);
}
// bind socket
- dc.bind(new InetSocketAddress(0));
+ dc.bind(localAddress);
// allow to change when bound
dc.setOption(SO_BROADCAST, true);
@@ -116,7 +159,6 @@
dc.setOption(IP_MULTICAST_LOOP, true);
checkOption(dc, IP_MULTICAST_LOOP, true);
-
// NullPointerException
try {
dc.setOption(null, "value");
@@ -137,4 +179,14 @@
} catch (ClosedChannelException x) {
}
}
+
+ static <T> void checkOption(DatagramChannel dc,
+ SocketOption<T> name,
+ T expectedValue)
+ throws IOException
+ {
+ T value = dc.getOption(name);
+ if (!value.equals(expectedValue))
+ throw new RuntimeException("value not as expected");
+ }
}