# HG changeset patch # User alanb # Date 1573300117 0 # Node ID 612c58965775a60262fe43623095470b0567ad69 # Parent 6bc29ebe053e0a6dd8f874d8ec66e690ce595d2a 8233435: (dc) DatagramChannel should allow IPv6 socket join IPv4 multicast groups (macOS, win) Reviewed-by: dfuchs diff -r 6bc29ebe053e -r 612c58965775 src/java.base/share/classes/sun/nio/ch/DatagramChannelImpl.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 DatagramChannel setOption(SocketOption 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); } } diff -r 6bc29ebe053e -r 612c58965775 src/java.base/share/classes/sun/nio/ch/Net.java --- 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); } diff -r 6bc29ebe053e -r 612c58965775 src/java.base/unix/native/libnio/ch/Net.c --- 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) diff -r 6bc29ebe053e -r 612c58965775 src/java.base/windows/native/libnio/ch/Net.c --- 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; diff -r 6bc29ebe053e -r 612c58965775 test/jdk/java/nio/channels/DatagramChannel/MulticastSendReceiveTests.java --- 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 ip4MulticastInterfaces = config.ip4MulticastInterfaces() + .collect(Collectors.toList()); + List 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); } } } diff -r 6bc29ebe053e -r 612c58965775 test/jdk/java/nio/channels/DatagramChannel/SocketOptionTests.java --- 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 void checkOption(DatagramChannel dc, - SocketOption 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> 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 void checkOption(DatagramChannel dc, + SocketOption name, + T expectedValue) + throws IOException + { + T value = dc.getOption(name); + if (!value.equals(expectedValue)) + throw new RuntimeException("value not as expected"); + } }