8236184: (dc) IP_MULTICAST_* and IP_TOS socket options not effective

Reviewed-by: dfuchs
This commit is contained in:
Alan Bateman 2019-12-19 08:36:40 +00:00
parent 75cd193aac
commit 27e0cdf12d
5 changed files with 229 additions and 12 deletions

View File

@ -338,23 +338,41 @@ class DatagramChannelImpl
ProtocolFamily family = familyFor(name);
// Some platforms require both IPV6_XXX and IP_XXX socket options to
// be set when the channel's socket is IPv6 and it is used to send
// IPv4 multicast datagrams. The IP_XXX socket options are set on a
// best effort basis.
boolean needToSetIPv4Option = (family != Net.UNSPEC)
&& (this.family == StandardProtocolFamily.INET6)
&& Net.shouldSetBothIPv4AndIPv6Options();
// outgoing multicast interface
if (name == StandardSocketOptions.IP_MULTICAST_IF) {
NetworkInterface interf = (NetworkInterface)value;
assert family != Net.UNSPEC;
NetworkInterface interf = (NetworkInterface) value;
if (family == StandardProtocolFamily.INET6) {
int index = interf.getIndex();
if (index == -1)
throw new IOException("Network interface cannot be identified");
Net.setInterface6(fd, index);
} else {
}
if (family == StandardProtocolFamily.INET || needToSetIPv4Option) {
// need IPv4 address to identify interface
Inet4Address target = Net.anyInet4Address(interf);
if (target == null)
if (target != null) {
try {
Net.setInterface4(fd, Net.inet4AsInt(target));
} catch (IOException ioe) {
if (family == StandardProtocolFamily.INET) throw ioe;
}
} else if (family == StandardProtocolFamily.INET) {
throw new IOException("Network interface not configured for IPv4");
int targetAddress = Net.inet4AsInt(target);
Net.setInterface4(fd, targetAddress);
}
}
return this;
}
// SO_REUSEADDR needs special handling as it may be emulated
if (name == StandardSocketOptions.SO_REUSEADDR
&& Net.useExclusiveBind() && localAddress != null) {
reuseAddressEmulated = true;
@ -363,6 +381,12 @@ class DatagramChannelImpl
// remaining options don't need any special handling
Net.setSocketOption(fd, family, name, value);
if (needToSetIPv4Option && family != StandardProtocolFamily.INET) {
try {
Net.setSocketOption(fd, StandardProtocolFamily.INET, name, value);
} catch (IOException ignore) { }
}
return this;
}
}

View File

@ -106,6 +106,16 @@ public class Net {
return exclusiveBind;
}
/**
* Tells whether both IPV6_XXX and IP_XXX socket options should be set on
* IPv6 sockets. On some kernels, both IPV6_XXX and IP_XXX socket options
* need to be set so that the settings are effective for IPv4 multicast
* datagrams sent using the socket.
*/
static boolean shouldSetBothIPv4AndIPv6Options() {
return shouldSetBothIPv4AndIPv6Options0();
}
/**
* Tells whether IPv6 sockets can join IPv4 multicast groups
*/
@ -438,6 +448,8 @@ public class Net {
*/
private static native int isExclusiveBindAvailable();
private static native boolean shouldSetBothIPv4AndIPv6Options0();
private static native boolean canIPv6SocketJoinIPv4Group0();
private static native boolean canJoin6WithIPv4Group0();

View File

@ -155,6 +155,18 @@ Java_sun_nio_ch_Net_isExclusiveBindAvailable(JNIEnv *env, jclass clazz) {
return -1;
}
JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_Net_shouldSetBothIPv4AndIPv6Options0(JNIEnv* env, jclass cl)
{
#if defined(__linux__)
/* Set both IPv4 and IPv6 socket options when setting multicast options */
return JNI_TRUE;
#else
/* Do not set both IPv4 and IPv6 socket options when setting multicast options */
return JNI_FALSE;
#endif
}
JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_Net_canIPv6SocketJoinIPv4Group0(JNIEnv* env, jclass cl)
{
@ -540,12 +552,6 @@ Java_sun_nio_ch_Net_setIntOption0(JNIEnv *env, jclass clazz, jobject fdo,
JNU_JAVANETPKG "SocketException",
"sun.nio.ch.Net.setIntOption");
}
#ifdef __linux__
if (level == IPPROTO_IPV6 && opt == IPV6_TCLASS && isIPv6) {
// set the V4 option also
setsockopt(fdval(env, fdo), IPPROTO_IP, IP_TOS, parg, arglen);
}
#endif
}
JNIEXPORT jint JNICALL

View File

@ -25,11 +25,11 @@
#include <windows.h>
#include <winsock2.h>
#include "jni.h"
#include "jni_util.h"
#include "jvm.h"
#include "jlong.h"
#include "nio.h"
#include "nio_util.h"
#include "net_util.h"
@ -123,6 +123,12 @@ Java_sun_nio_ch_Net_isExclusiveBindAvailable(JNIEnv *env, jclass clazz) {
return 1;
}
JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_Net_shouldSetBothIPv4AndIPv6Options0(JNIEnv* env, jclass cl)
{
/* Set both IPv4 and IPv6 socket options when setting multicast options */
return JNI_TRUE;
}
JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_Net_canIPv6SocketJoinIPv4Group0(JNIEnv* env, jclass cl)

View File

@ -0,0 +1,169 @@
/*
* 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 8236184
* @library /test/lib
* @build jdk.test.lib.NetworkConfiguration
* jdk.test.lib.net.IPSupport
* @requires (os.family == "linux") | (os.family == "windows")
* @run main Loopback
* @run main/othervm -Djava.net.preferIPv4Stack=true Loopback
* @summary Test the IP_MULTICAST_LOOP option
*/
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.ProtocolFamily;
import java.net.SocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.List;
import java.util.stream.Collectors;
import static java.net.StandardProtocolFamily.*;
import static java.net.StandardSocketOptions.IP_MULTICAST_LOOP;
import jdk.test.lib.NetworkConfiguration;
import jdk.test.lib.net.IPSupport;
public class Loopback {
static final ProtocolFamily UNSPEC = () -> "UNSPEC";
public static void main(String[] args) throws Exception {
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());
// IPv4 multicast group
InetAddress ip4Group = InetAddress.getByName("225.4.5.6");
for (NetworkInterface ni : ip4MulticastInterfaces) {
test(UNSPEC, ip4Group, ni);
test(INET, ip4Group, ni);
if (IPSupport.hasIPv6()) {
test(INET6, ip4Group, ni);
}
}
// IPv6 multicast group
InetAddress ip6Group = InetAddress.getByName("ff02::a");
for (NetworkInterface ni : ip6MulticastInterfaces) {
test(UNSPEC, ip6Group, ni);
test(INET6, ip6Group, ni);
}
}
/**
* Joins a multicast group and send datagrams to that group with both
* IP_MULTICAST_LOOP enabled and disabled.
*/
static void test(ProtocolFamily family, InetAddress group, NetworkInterface ni)
throws IOException
{
System.out.format("\n%s socket\n", family.name());
DatagramChannel dc;
if (family == UNSPEC) {
dc = DatagramChannel.open();
} else {
dc = DatagramChannel.open(family);
}
try (dc) {
dc.setOption(StandardSocketOptions.SO_REUSEADDR, true);
dc.bind(new InetSocketAddress(0));
int localPort = dc.socket().getLocalPort();
SocketAddress target = new InetSocketAddress(group, localPort);
System.out.format("join %s @ %s%n", group.getHostAddress(), ni.getName());
dc.join(group, ni);
// -- IP_MULTICAST_LOOP enabled --
assertTrue(dc.getOption(IP_MULTICAST_LOOP), "IP_MULTICAST_LOOP not enabled");
System.out.println("IP_MULTICAST_LOOP enabled");
// send datagram to multicast group
System.out.format("send %s -> %s%n", dc.getLocalAddress(), target);
ByteBuffer src = ByteBuffer.wrap("hello".getBytes("UTF-8"));
dc.send(src, target);
// receive datagram sent to multicast group
ByteBuffer dst = ByteBuffer.allocate(100);
int senderPort;
do {
dst.clear();
SocketAddress sender = dc.receive(dst);
System.out.format("received %s from %s%n", dst, sender);
senderPort = ((InetSocketAddress) sender).getPort();
} while (senderPort != localPort);
dst.flip();
assertTrue(dst.remaining() == src.capacity(), "Unexpected message size");
// -- IP_MULTICAST_LOOP disabled --
dc.setOption(StandardSocketOptions.IP_MULTICAST_LOOP, false);
System.out.println("IP_MULTICAST_LOOP disabled");
// send datagram to multicast group
System.out.format("send %s -> %s%n", dc.getLocalAddress(), target);
src.clear();
dc.send(src, target);
// test that we don't receive the datagram sent to multicast group
dc.configureBlocking(false);
try (Selector sel = Selector.open()) {
dc.register(sel, SelectionKey.OP_READ);
boolean done = false;
while (!done) {
int n = sel.select(3000);
System.out.format("selected %d%n", n);
if (n == 0) {
// timeout, no datagram received
done = true;
} else {
sel.selectedKeys().clear();
SocketAddress sender = dc.receive(dst);
if (sender != null) {
System.out.format("received %s from %s%n", dst, sender);
senderPort = ((InetSocketAddress) sender).getPort();
assertTrue(senderPort != localPort, "Unexpected message");
}
}
}
}
}
}
static void assertTrue(boolean e, String msg) {
if (!e) throw new RuntimeException(msg);
}
}