8233435: (dc) DatagramChannel should allow IPv6 socket join IPv4 multicast groups (macOS, win)
authoralanb
Sat, 09 Nov 2019 11:48:37 +0000
changeset 59000 612c58965775
parent 58999 6bc29ebe053e
child 59001 d595f1faace2
8233435: (dc) DatagramChannel should allow IPv6 socket join IPv4 multicast groups (macOS, win) Reviewed-by: dfuchs
src/java.base/share/classes/sun/nio/ch/DatagramChannelImpl.java
src/java.base/share/classes/sun/nio/ch/Net.java
src/java.base/unix/native/libnio/ch/Net.c
src/java.base/windows/native/libnio/ch/Net.c
test/jdk/java/nio/channels/DatagramChannel/MulticastSendReceiveTests.java
test/jdk/java/nio/channels/DatagramChannel/SocketOptionTests.java
--- 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");
+    }
 }