mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 14:54:52 +02:00
8236925: (dc) Upgrade DatagramChannel socket adaptor to extend MulticastSocket
Reviewed-by: dfuchs
This commit is contained in:
parent
6ef474a4f4
commit
d4c3278889
7 changed files with 847 additions and 145 deletions
|
@ -115,7 +115,6 @@ public class DatagramSocket implements java.io.Closeable {
|
||||||
/**
|
/**
|
||||||
* Various states of this socket.
|
* Various states of this socket.
|
||||||
*/
|
*/
|
||||||
private boolean created = false;
|
|
||||||
private boolean bound = false;
|
private boolean bound = false;
|
||||||
private boolean closed = false;
|
private boolean closed = false;
|
||||||
private Object closeLock = new Object();
|
private Object closeLock = new Object();
|
||||||
|
@ -123,12 +122,12 @@ public class DatagramSocket implements java.io.Closeable {
|
||||||
/*
|
/*
|
||||||
* The implementation of this DatagramSocket.
|
* The implementation of this DatagramSocket.
|
||||||
*/
|
*/
|
||||||
DatagramSocketImpl impl;
|
private final DatagramSocketImpl impl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Are we using an older DatagramSocketImpl?
|
* Are we using an older DatagramSocketImpl?
|
||||||
*/
|
*/
|
||||||
boolean oldImpl = false;
|
final boolean oldImpl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set when a socket is ST_CONNECTED until we are certain
|
* Set when a socket is ST_CONNECTED until we are certain
|
||||||
|
@ -255,7 +254,7 @@ public class DatagramSocket implements java.io.Closeable {
|
||||||
if (impl == null)
|
if (impl == null)
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
this.impl = impl;
|
this.impl = impl;
|
||||||
checkOldImpl();
|
this.oldImpl = checkOldImpl(impl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -282,8 +281,17 @@ public class DatagramSocket implements java.io.Closeable {
|
||||||
* @since 1.4
|
* @since 1.4
|
||||||
*/
|
*/
|
||||||
public DatagramSocket(SocketAddress bindaddr) throws SocketException {
|
public DatagramSocket(SocketAddress bindaddr) throws SocketException {
|
||||||
|
// Special case initialization for the DatagramChannel socket adaptor.
|
||||||
|
if (this instanceof sun.nio.ch.DatagramSocketAdaptor) {
|
||||||
|
this.impl = null; // no DatagramSocketImpl
|
||||||
|
this.oldImpl = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// create a datagram socket.
|
// create a datagram socket.
|
||||||
createImpl();
|
boolean multicast = (this instanceof MulticastSocket);
|
||||||
|
this.impl = createImpl(multicast);
|
||||||
|
this.oldImpl = checkOldImpl(impl);
|
||||||
if (bindaddr != null) {
|
if (bindaddr != null) {
|
||||||
try {
|
try {
|
||||||
bind(bindaddr);
|
bind(bindaddr);
|
||||||
|
@ -346,9 +354,11 @@ public class DatagramSocket implements java.io.Closeable {
|
||||||
this(new InetSocketAddress(laddr, port));
|
this(new InetSocketAddress(laddr, port));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkOldImpl() {
|
/**
|
||||||
if (impl == null)
|
* Return true if the given DatagramSocketImpl is an "old" impl. An old impl
|
||||||
return;
|
* is one that doesn't implement the abstract methods added in Java SE 1.4.
|
||||||
|
*/
|
||||||
|
private static boolean checkOldImpl(DatagramSocketImpl impl) {
|
||||||
// DatagramSocketImpl.peekData() is a protected method, therefore we need to use
|
// DatagramSocketImpl.peekData() is a protected method, therefore we need to use
|
||||||
// getDeclaredMethod, therefore we need permission to access the member
|
// getDeclaredMethod, therefore we need permission to access the member
|
||||||
try {
|
try {
|
||||||
|
@ -361,42 +371,40 @@ public class DatagramSocket implements java.io.Closeable {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return false;
|
||||||
} catch (java.security.PrivilegedActionException e) {
|
} catch (java.security.PrivilegedActionException e) {
|
||||||
oldImpl = true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Class<?> implClass = null;
|
static Class<?> implClass = null;
|
||||||
|
|
||||||
void createImpl() throws SocketException {
|
/**
|
||||||
if (impl == null) {
|
* Creates a DatagramSocketImpl.
|
||||||
if (factory != null) {
|
* @param multicast true if the DatagramSocketImpl is for a MulticastSocket
|
||||||
impl = factory.createDatagramSocketImpl();
|
*/
|
||||||
checkOldImpl();
|
private static DatagramSocketImpl createImpl(boolean multicast) throws SocketException {
|
||||||
} else {
|
DatagramSocketImpl impl;
|
||||||
boolean isMulticast = (this instanceof MulticastSocket) ? true : false;
|
DatagramSocketImplFactory factory = DatagramSocket.factory;
|
||||||
impl = DefaultDatagramSocketImplFactory.createDatagramSocketImpl(isMulticast);
|
if (factory != null) {
|
||||||
|
impl = factory.createDatagramSocketImpl();
|
||||||
checkOldImpl();
|
} else {
|
||||||
}
|
impl = DefaultDatagramSocketImplFactory.createDatagramSocketImpl(multicast);
|
||||||
}
|
}
|
||||||
// creates a udp socket
|
// creates a udp socket
|
||||||
impl.create();
|
impl.create();
|
||||||
created = true;
|
return impl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the {@code DatagramSocketImpl} attached to this socket,
|
* Return the {@code DatagramSocketImpl} attached to this socket.
|
||||||
* creating it if necessary.
|
|
||||||
*
|
*
|
||||||
* @return the {@code DatagramSocketImpl} attached to that
|
* @return the {@code DatagramSocketImpl} attached to that
|
||||||
* DatagramSocket
|
* DatagramSocket
|
||||||
* @throws SocketException if creation fails.
|
* @throws SocketException never thrown
|
||||||
* @since 1.4
|
* @since 1.4
|
||||||
*/
|
*/
|
||||||
DatagramSocketImpl getImpl() throws SocketException {
|
DatagramSocketImpl getImpl() throws SocketException {
|
||||||
if (!created)
|
|
||||||
createImpl();
|
|
||||||
return impl;
|
return impl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1329,7 +1337,7 @@ public class DatagramSocket implements java.io.Closeable {
|
||||||
/**
|
/**
|
||||||
* User defined factory for all datagram sockets.
|
* User defined factory for all datagram sockets.
|
||||||
*/
|
*/
|
||||||
static DatagramSocketImplFactory factory;
|
private static volatile DatagramSocketImplFactory factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the datagram socket implementation factory for the
|
* Sets the datagram socket implementation factory for the
|
||||||
|
|
|
@ -29,6 +29,7 @@ import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The multicast datagram socket class is useful for sending
|
* The multicast datagram socket class is useful for sending
|
||||||
* and receiving IP multicast packets. A MulticastSocket is
|
* and receiving IP multicast packets. A MulticastSocket is
|
||||||
|
@ -208,6 +209,10 @@ public class MulticastSocket extends DatagramSocket {
|
||||||
public MulticastSocket(SocketAddress bindaddr) throws IOException {
|
public MulticastSocket(SocketAddress bindaddr) throws IOException {
|
||||||
super((SocketAddress) null);
|
super((SocketAddress) null);
|
||||||
|
|
||||||
|
// No further initialization when this is a DatagramChannel socket adaptor
|
||||||
|
if (this instanceof sun.nio.ch.DatagramSocketAdaptor)
|
||||||
|
return;
|
||||||
|
|
||||||
// Enable SO_REUSEADDR before binding
|
// Enable SO_REUSEADDR before binding
|
||||||
setReuseAddress(true);
|
setReuseAddress(true);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -234,9 +234,6 @@ public abstract class DatagramChannel
|
||||||
/**
|
/**
|
||||||
* Retrieves a datagram socket associated with this channel.
|
* Retrieves a datagram socket associated with this channel.
|
||||||
*
|
*
|
||||||
* <p> The returned object will not declare any public methods that are not
|
|
||||||
* declared in the {@link java.net.DatagramSocket} class. </p>
|
|
||||||
*
|
|
||||||
* @return A datagram socket associated with this channel
|
* @return A datagram socket associated with this channel
|
||||||
*/
|
*/
|
||||||
public abstract DatagramSocket socket();
|
public abstract DatagramSocket socket();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2001, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -1584,6 +1584,22 @@ class DatagramChannelImpl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds an existing membership of a multicast group. Returns null if this
|
||||||
|
* channel's socket is not a member of the group.
|
||||||
|
*
|
||||||
|
* @apiNote This method is for use by the socket adaptor
|
||||||
|
*/
|
||||||
|
MembershipKey findMembership(InetAddress group, NetworkInterface interf) {
|
||||||
|
synchronized (stateLock) {
|
||||||
|
if (registry != null) {
|
||||||
|
return registry.checkMembership(group, interf, null);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block datagrams from the given source.
|
* Block datagrams from the given source.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2001, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -26,15 +26,17 @@
|
||||||
package sun.nio.ch;
|
package sun.nio.ch;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
import java.lang.invoke.MethodHandles;
|
import java.lang.invoke.MethodHandles;
|
||||||
import java.lang.invoke.MethodHandles.Lookup;
|
import java.lang.invoke.MethodHandles.Lookup;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
import java.lang.invoke.VarHandle;
|
import java.lang.invoke.VarHandle;
|
||||||
import java.net.DatagramPacket;
|
import java.net.DatagramPacket;
|
||||||
import java.net.DatagramSocket;
|
import java.net.DatagramSocket;
|
||||||
import java.net.DatagramSocketImpl;
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.NetworkInterface;
|
import java.net.NetworkInterface;
|
||||||
|
import java.net.MulticastSocket;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.net.SocketOption;
|
import java.net.SocketOption;
|
||||||
|
@ -43,21 +45,26 @@ import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.AlreadyConnectedException;
|
import java.nio.channels.AlreadyConnectedException;
|
||||||
import java.nio.channels.ClosedChannelException;
|
import java.nio.channels.ClosedChannelException;
|
||||||
import java.nio.channels.DatagramChannel;
|
import java.nio.channels.DatagramChannel;
|
||||||
|
import java.nio.channels.MembershipKey;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
|
import java.security.PrivilegedExceptionAction;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
// Make a datagram-socket channel look like a datagram socket.
|
/**
|
||||||
//
|
* A multicast datagram socket based on a datagram channel.
|
||||||
// The methods in this class are defined in exactly the same order as in
|
*
|
||||||
// java.net.DatagramSocket so as to simplify tracking future changes to that
|
* This class overrides every public method defined by java.net.DatagramSocket
|
||||||
// class.
|
* and java.net.MulticastSocket. The methods in this class are defined in exactly
|
||||||
//
|
* the same order as in java.net.DatagramSocket and java.net.MulticastSocket so
|
||||||
|
* as to simplify tracking changes.
|
||||||
class DatagramSocketAdaptor
|
*/
|
||||||
extends DatagramSocket
|
public class DatagramSocketAdaptor
|
||||||
|
extends MulticastSocket
|
||||||
{
|
{
|
||||||
// The channel being adapted
|
// The channel being adapted
|
||||||
private final DatagramChannelImpl dc;
|
private final DatagramChannelImpl dc;
|
||||||
|
@ -65,14 +72,17 @@ class DatagramSocketAdaptor
|
||||||
// Timeout "option" value for receives
|
// Timeout "option" value for receives
|
||||||
private volatile int timeout;
|
private volatile int timeout;
|
||||||
|
|
||||||
// create DatagramSocket with useless impl
|
private DatagramSocketAdaptor(DatagramChannelImpl dc) throws IOException {
|
||||||
private DatagramSocketAdaptor(DatagramChannelImpl dc) {
|
super(/*SocketAddress*/null);
|
||||||
super(new DummyDatagramSocketImpl());
|
|
||||||
this.dc = dc;
|
this.dc = dc;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DatagramSocket create(DatagramChannelImpl dc) {
|
static DatagramSocket create(DatagramChannelImpl dc) {
|
||||||
return new DatagramSocketAdaptor(dc);
|
try {
|
||||||
|
return new DatagramSocketAdaptor(dc);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new Error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void connectInternal(SocketAddress remote) throws SocketException {
|
private void connectInternal(SocketAddress remote) throws SocketException {
|
||||||
|
@ -409,114 +419,263 @@ class DatagramSocketAdaptor
|
||||||
return dc.supportedOptions();
|
return dc.supportedOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -- java.net.MulticastSocket --
|
||||||
|
|
||||||
|
// used to coordinate changing TTL with the deprecated send method
|
||||||
|
private final ReentrantLock sendLock = new ReentrantLock();
|
||||||
|
|
||||||
|
// cached outgoing interface (for use by setInterface/getInterface)
|
||||||
|
private final Object outgoingInterfaceLock = new Object();
|
||||||
|
private NetworkInterface outgoingNetworkInterface;
|
||||||
|
private InetAddress outgoingInetAddress;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void setTTL(byte ttl) throws IOException {
|
||||||
|
setTimeToLive(Byte.toUnsignedInt(ttl));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTimeToLive(int ttl) throws IOException {
|
||||||
|
sendLock.lock();
|
||||||
|
try {
|
||||||
|
setIntOption(StandardSocketOptions.IP_MULTICAST_TTL, ttl);
|
||||||
|
} finally {
|
||||||
|
sendLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public byte getTTL() throws IOException {
|
||||||
|
return (byte) getTimeToLive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTimeToLive() throws IOException {
|
||||||
|
sendLock.lock();
|
||||||
|
try {
|
||||||
|
return getIntOption(StandardSocketOptions.IP_MULTICAST_TTL);
|
||||||
|
} finally {
|
||||||
|
sendLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void joinGroup(InetAddress group) throws IOException {
|
||||||
|
Objects.requireNonNull(group);
|
||||||
|
try {
|
||||||
|
joinGroup(new InetSocketAddress(group, 0), null);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
// 1-arg joinGroup does not specify IllegalArgumentException
|
||||||
|
throw (SocketException) new SocketException("joinGroup failed").initCause(iae);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void leaveGroup(InetAddress group) throws IOException {
|
||||||
|
Objects.requireNonNull(group);
|
||||||
|
try {
|
||||||
|
leaveGroup(new InetSocketAddress(group, 0), null);
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
// 1-arg leaveGroup does not specify IllegalArgumentException
|
||||||
|
throw (SocketException) new SocketException("leaveGroup failed").initCause(iae);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DatagramSocketImpl implementation where all methods throw an error.
|
* Checks a SocketAddress to ensure that it is a multicast address.
|
||||||
|
*
|
||||||
|
* @return the multicast group
|
||||||
|
* @throws IllegalArgumentException if group is null, an unsupported address
|
||||||
|
* type, or an unresolved address
|
||||||
|
* @throws SocketException if group is not a multicast address
|
||||||
*/
|
*/
|
||||||
private static class DummyDatagramSocketImpl extends DatagramSocketImpl {
|
private static InetAddress checkGroup(SocketAddress mcastaddr) throws SocketException {
|
||||||
private static <T> T shouldNotGetHere() {
|
if (mcastaddr == null || !(mcastaddr instanceof InetSocketAddress))
|
||||||
throw new InternalError("Should not get here");
|
throw new IllegalArgumentException("Unsupported address type");
|
||||||
|
InetAddress group = ((InetSocketAddress) mcastaddr).getAddress();
|
||||||
|
if (group == null)
|
||||||
|
throw new IllegalArgumentException("Unresolved address");
|
||||||
|
if (!group.isMulticastAddress())
|
||||||
|
throw new SocketException("Not a multicast address");
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void joinGroup(SocketAddress mcastaddr, NetworkInterface netIf) throws IOException {
|
||||||
|
InetAddress group = checkGroup(mcastaddr);
|
||||||
|
NetworkInterface ni = (netIf != null) ? netIf : defaultNetworkInterface();
|
||||||
|
if (isClosed())
|
||||||
|
throw new SocketException("Socket is closed");
|
||||||
|
synchronized (this) {
|
||||||
|
MembershipKey key = dc.findMembership(group, ni);
|
||||||
|
if (key != null) {
|
||||||
|
// already a member but need to check permission anyway
|
||||||
|
SecurityManager sm = System.getSecurityManager();
|
||||||
|
if (sm != null)
|
||||||
|
sm.checkMulticast(group);
|
||||||
|
throw new SocketException("Already a member of group");
|
||||||
|
}
|
||||||
|
dc.join(group, ni); // checks permission
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leaveGroup(SocketAddress mcastaddr, NetworkInterface netIf) throws IOException {
|
||||||
|
InetAddress group = checkGroup(mcastaddr);
|
||||||
|
NetworkInterface ni = (netIf != null) ? netIf : defaultNetworkInterface();
|
||||||
|
if (isClosed())
|
||||||
|
throw new SocketException("Socket is closed");
|
||||||
|
SecurityManager sm = System.getSecurityManager();
|
||||||
|
if (sm != null)
|
||||||
|
sm.checkMulticast(group);
|
||||||
|
synchronized (this) {
|
||||||
|
MembershipKey key = dc.findMembership(group, ni);
|
||||||
|
if (key == null)
|
||||||
|
throw new SocketException("Not a member of group");
|
||||||
|
key.drop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void setInterface(InetAddress inf) throws SocketException {
|
||||||
|
if (inf == null)
|
||||||
|
throw new SocketException("Invalid value 'null'");
|
||||||
|
NetworkInterface ni = NetworkInterface.getByInetAddress(inf);
|
||||||
|
if (ni == null) {
|
||||||
|
String address = inf.getHostAddress();
|
||||||
|
throw new SocketException("No network interface with address " + address);
|
||||||
|
}
|
||||||
|
synchronized (outgoingInterfaceLock) {
|
||||||
|
// set interface and update cached values
|
||||||
|
setNetworkInterface(ni);
|
||||||
|
outgoingNetworkInterface = ni;
|
||||||
|
outgoingInetAddress = inf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public InetAddress getInterface() throws SocketException {
|
||||||
|
synchronized (outgoingInterfaceLock) {
|
||||||
|
NetworkInterface ni = outgoingNetworkInterface();
|
||||||
|
if (ni != null) {
|
||||||
|
if (ni.equals(outgoingNetworkInterface)) {
|
||||||
|
return outgoingInetAddress;
|
||||||
|
} else {
|
||||||
|
// network interface has changed so update cached values
|
||||||
|
PrivilegedAction<InetAddress> pa;
|
||||||
|
pa = () -> ni.inetAddresses().findFirst().orElse(null);
|
||||||
|
InetAddress ia = AccessController.doPrivileged(pa);
|
||||||
|
if (ia == null)
|
||||||
|
throw new SocketException("Network interface has no IP address");
|
||||||
|
outgoingNetworkInterface = ni;
|
||||||
|
outgoingInetAddress = ia;
|
||||||
|
return ia;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// no interface set
|
||||||
protected void create() {
|
return anyInetAddress();
|
||||||
shouldNotGetHere();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void bind(int lport, InetAddress laddr) {
|
public void setNetworkInterface(NetworkInterface netIf) throws SocketException {
|
||||||
shouldNotGetHere();
|
try {
|
||||||
|
setOption(StandardSocketOptions.IP_MULTICAST_IF, netIf);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Net.translateToSocketException(e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void send(DatagramPacket p) {
|
public NetworkInterface getNetworkInterface() throws SocketException {
|
||||||
shouldNotGetHere();
|
NetworkInterface ni = outgoingNetworkInterface();
|
||||||
|
if (ni == null) {
|
||||||
|
// return NetworkInterface with index == 0 as placeholder
|
||||||
|
ni = anyNetworkInterface();
|
||||||
}
|
}
|
||||||
|
return ni;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int peek(InetAddress address) {
|
@Deprecated
|
||||||
return shouldNotGetHere();
|
public void setLoopbackMode(boolean disable) throws SocketException {
|
||||||
}
|
boolean enable = !disable;
|
||||||
|
setBooleanOption(StandardSocketOptions.IP_MULTICAST_LOOP, enable);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int peekData(DatagramPacket p) {
|
@Deprecated
|
||||||
return shouldNotGetHere();
|
public boolean getLoopbackMode() throws SocketException {
|
||||||
}
|
boolean enabled = getBooleanOption(StandardSocketOptions.IP_MULTICAST_LOOP);
|
||||||
|
return !enabled;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void receive(DatagramPacket p) {
|
@Deprecated
|
||||||
shouldNotGetHere();
|
public void send(DatagramPacket p, byte ttl) throws IOException {
|
||||||
|
sendLock.lock();
|
||||||
|
try {
|
||||||
|
int oldValue = getTimeToLive();
|
||||||
|
try {
|
||||||
|
setTTL(ttl);
|
||||||
|
send(p);
|
||||||
|
} finally {
|
||||||
|
setTimeToLive(oldValue);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
sendLock.unlock();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Deprecated
|
/**
|
||||||
protected void setTTL(byte ttl) {
|
* Returns the outgoing NetworkInterface or null if not set.
|
||||||
shouldNotGetHere();
|
*/
|
||||||
|
private NetworkInterface outgoingNetworkInterface() throws SocketException {
|
||||||
|
try {
|
||||||
|
return getOption(StandardSocketOptions.IP_MULTICAST_IF);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Net.translateToSocketException(e);
|
||||||
|
return null; // keep compiler happy
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Deprecated
|
/**
|
||||||
protected byte getTTL() {
|
* Returns the default NetworkInterface to use when joining or leaving a
|
||||||
return shouldNotGetHere();
|
* multicast group and a network interface is not specified.
|
||||||
}
|
* This method will return the outgoing NetworkInterface if set, otherwise
|
||||||
|
* the result of NetworkInterface.getDefault(), otherwise a NetworkInterface
|
||||||
|
* with index == 0 as a placeholder for "any network interface".
|
||||||
|
*/
|
||||||
|
private NetworkInterface defaultNetworkInterface() throws SocketException {
|
||||||
|
NetworkInterface ni = outgoingNetworkInterface();
|
||||||
|
if (ni == null)
|
||||||
|
ni = NetworkInterfaces.getDefault(); // macOS
|
||||||
|
if (ni == null)
|
||||||
|
ni = anyNetworkInterface();
|
||||||
|
return ni;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
protected void setTimeToLive(int ttl) {
|
* Returns the placeholder for "any network interface", its index is 0.
|
||||||
shouldNotGetHere();
|
*/
|
||||||
}
|
private NetworkInterface anyNetworkInterface() {
|
||||||
|
InetAddress[] addrs = new InetAddress[1];
|
||||||
|
addrs[0] = anyInetAddress();
|
||||||
|
return NetworkInterfaces.newNetworkInterface(addrs[0].getHostName(), 0, addrs);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
protected int getTimeToLive() {
|
* Returns the InetAddress representing anyLocalAddress.
|
||||||
return shouldNotGetHere();
|
*/
|
||||||
}
|
private InetAddress anyInetAddress() {
|
||||||
|
return new InetSocketAddress(0).getAddress();
|
||||||
@Override
|
|
||||||
protected void join(InetAddress group) {
|
|
||||||
shouldNotGetHere();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void leave(InetAddress inetaddr) {
|
|
||||||
shouldNotGetHere();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void joinGroup(SocketAddress group, NetworkInterface netIf) {
|
|
||||||
shouldNotGetHere();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void leaveGroup(SocketAddress mcastaddr, NetworkInterface netIf) {
|
|
||||||
shouldNotGetHere();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void close() {
|
|
||||||
shouldNotGetHere();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getOption(int optID) {
|
|
||||||
return shouldNotGetHere();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOption(int optID, Object value) {
|
|
||||||
shouldNotGetHere();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected <T> void setOption(SocketOption<T> name, T value) {
|
|
||||||
shouldNotGetHere();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected <T> T getOption(SocketOption<T> name) {
|
|
||||||
return shouldNotGetHere();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Set<SocketOption<?>> supportedOptions() {
|
|
||||||
return shouldNotGetHere();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -528,13 +687,8 @@ class DatagramSocketAdaptor
|
||||||
private static final VarHandle BUF_LENGTH;
|
private static final VarHandle BUF_LENGTH;
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
PrivilegedAction<Lookup> pa = () -> {
|
PrivilegedExceptionAction<Lookup> pa = () ->
|
||||||
try {
|
MethodHandles.privateLookupIn(DatagramPacket.class, MethodHandles.lookup());
|
||||||
return MethodHandles.privateLookupIn(DatagramPacket.class, MethodHandles.lookup());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ExceptionInInitializerError(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
MethodHandles.Lookup l = AccessController.doPrivileged(pa);
|
MethodHandles.Lookup l = AccessController.doPrivileged(pa);
|
||||||
LENGTH = l.findVarHandle(DatagramPacket.class, "length", int.class);
|
LENGTH = l.findVarHandle(DatagramPacket.class, "length", int.class);
|
||||||
BUF_LENGTH = l.findVarHandle(DatagramPacket.class, "bufLength", int.class);
|
BUF_LENGTH = l.findVarHandle(DatagramPacket.class, "bufLength", int.class);
|
||||||
|
@ -562,4 +716,47 @@ class DatagramSocketAdaptor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines static methods to invoke non-public NetworkInterface methods.
|
||||||
|
*/
|
||||||
|
private static class NetworkInterfaces {
|
||||||
|
static final MethodHandle GET_DEFAULT;
|
||||||
|
static final MethodHandle CONSTRUCTOR;
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
PrivilegedExceptionAction<Lookup> pa = () ->
|
||||||
|
MethodHandles.privateLookupIn(NetworkInterface.class, MethodHandles.lookup());
|
||||||
|
MethodHandles.Lookup l = AccessController.doPrivileged(pa);
|
||||||
|
MethodType methodType = MethodType.methodType(NetworkInterface.class);
|
||||||
|
GET_DEFAULT = l.findStatic(NetworkInterface.class, "getDefault", methodType);
|
||||||
|
methodType = MethodType.methodType(void.class, String.class, int.class, InetAddress[].class);
|
||||||
|
CONSTRUCTOR = l.findConstructor(NetworkInterface.class, methodType);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ExceptionInInitializerError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default network interface or null.
|
||||||
|
*/
|
||||||
|
static NetworkInterface getDefault() {
|
||||||
|
try {
|
||||||
|
return (NetworkInterface) GET_DEFAULT.invokeExact();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new InternalError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a NetworkInterface with the given name index and addresses.
|
||||||
|
*/
|
||||||
|
static NetworkInterface newNetworkInterface(String name, int index, InetAddress[] addrs) {
|
||||||
|
try {
|
||||||
|
return (NetworkInterface) CONSTRUCTOR.invoke(name, index, addrs);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new InternalError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2001, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
@ -587,6 +587,13 @@ Java_sun_nio_ch_Net_joinOrDrop4(JNIEnv *env, jobject this, jboolean join, jobjec
|
||||||
}
|
}
|
||||||
|
|
||||||
n = setsockopt(fdval(env,fdo), IPPROTO_IP, opt, optval, optlen);
|
n = setsockopt(fdval(env,fdo), IPPROTO_IP, opt, optval, optlen);
|
||||||
|
#ifdef __APPLE__
|
||||||
|
// workaround macOS bug where IP_ADD_MEMBERSHIP fails intermittently
|
||||||
|
if (n < 0 && errno == ENOMEM) {
|
||||||
|
n = setsockopt(fdval(env,fdo), IPPROTO_IP, opt, optval, optlen);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (n < 0) {
|
if (n < 0) {
|
||||||
if (join && (errno == ENOPROTOOPT || errno == EOPNOTSUPP))
|
if (join && (errno == ENOPROTOOPT || errno == EOPNOTSUPP))
|
||||||
return IOS_UNAVAILABLE;
|
return IOS_UNAVAILABLE;
|
||||||
|
@ -657,6 +664,13 @@ Java_sun_nio_ch_Net_joinOrDrop6(JNIEnv *env, jobject this, jboolean join, jobjec
|
||||||
}
|
}
|
||||||
|
|
||||||
n = setsockopt(fdval(env,fdo), IPPROTO_IPV6, opt, optval, optlen);
|
n = setsockopt(fdval(env,fdo), IPPROTO_IPV6, opt, optval, optlen);
|
||||||
|
#ifdef __APPLE__
|
||||||
|
// workaround macOS bug where IPV6_ADD_MEMBERSHIP fails intermittently
|
||||||
|
if (n < 0 && errno == ENOMEM) {
|
||||||
|
n = setsockopt(fdval(env,fdo), IPPROTO_IPV6, opt, optval, optlen);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (n < 0) {
|
if (n < 0) {
|
||||||
if (join && (errno == ENOPROTOOPT || errno == EOPNOTSUPP))
|
if (join && (errno == ENOPROTOOPT || errno == EOPNOTSUPP))
|
||||||
return IOS_UNAVAILABLE;
|
return IOS_UNAVAILABLE;
|
||||||
|
|
|
@ -0,0 +1,465 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2020, 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 8236925
|
||||||
|
* @summary Test DatagramChannel socket adaptor as a MulticastSocket
|
||||||
|
* @library /test/lib
|
||||||
|
* @build jdk.test.lib.NetworkConfiguration
|
||||||
|
* jdk.test.lib.net.IPSupport
|
||||||
|
* @run main AdaptorMulticasting
|
||||||
|
* @run main/othervm -Djava.net.preferIPv4Stack=true AdaptorMulticasting
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.MulticastSocket;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.net.ProtocolFamily;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.SocketOption;
|
||||||
|
import java.nio.channels.DatagramChannel;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import static java.net.StandardSocketOptions.*;
|
||||||
|
import static java.net.StandardProtocolFamily.*;
|
||||||
|
|
||||||
|
import jdk.test.lib.NetworkConfiguration;
|
||||||
|
import jdk.test.lib.net.IPSupport;
|
||||||
|
|
||||||
|
public class AdaptorMulticasting {
|
||||||
|
static final ProtocolFamily UNSPEC = () -> "UNSPEC";
|
||||||
|
|
||||||
|
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 ni : ip4MulticastInterfaces) {
|
||||||
|
test(INET, ip4Group, ni);
|
||||||
|
if (IPSupport.hasIPv6()) {
|
||||||
|
test(UNSPEC, ip4Group, ni);
|
||||||
|
test(INET6, ip4Group, ni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (NetworkInterface ni : ip6MulticastInterfaces) {
|
||||||
|
test(UNSPEC, ip6Group, ni);
|
||||||
|
test(INET6, ip6Group, ni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test(ProtocolFamily family, InetAddress group, NetworkInterface ni)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
System.out.format("Test family=%s, multicast group=%s, interface=%s%n",
|
||||||
|
family.name(), group, ni.getName());
|
||||||
|
|
||||||
|
// test 1-arg joinGroup/leaveGroup
|
||||||
|
try (MulticastSocket s = create(family)) {
|
||||||
|
testJoinGroup1(family, s, group, ni);
|
||||||
|
}
|
||||||
|
|
||||||
|
// test 2-arg joinGroup/leaveGroup
|
||||||
|
try (MulticastSocket s = create(family)) {
|
||||||
|
testJoinGroup2(family, s, group, ni);
|
||||||
|
}
|
||||||
|
|
||||||
|
// test socket options
|
||||||
|
try (MulticastSocket s = create(family)) {
|
||||||
|
testNetworkInterface(s, ni);
|
||||||
|
testTimeToLive(s);
|
||||||
|
testLoopbackMode(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a MulticastSocket. The SO_REUSEADDR socket option is set and it
|
||||||
|
* is bound to the wildcard address.
|
||||||
|
*/
|
||||||
|
static MulticastSocket create(ProtocolFamily family) throws IOException {
|
||||||
|
DatagramChannel dc = (family == UNSPEC)
|
||||||
|
? DatagramChannel.open()
|
||||||
|
: DatagramChannel.open(family);
|
||||||
|
try {
|
||||||
|
dc.setOption(SO_REUSEADDR, true).bind(new InetSocketAddress(0));
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
dc.close();
|
||||||
|
throw ioe;
|
||||||
|
}
|
||||||
|
return (MulticastSocket) dc.socket();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test 1-arg joinGroup/leaveGroup
|
||||||
|
*/
|
||||||
|
static void testJoinGroup1(ProtocolFamily family,
|
||||||
|
MulticastSocket s,
|
||||||
|
InetAddress group,
|
||||||
|
NetworkInterface ni) throws IOException {
|
||||||
|
// check network interface not set
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_IF) == null);
|
||||||
|
|
||||||
|
// join
|
||||||
|
s.joinGroup(group);
|
||||||
|
|
||||||
|
// join should not set the outgoing multicast interface
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_IF) == null);
|
||||||
|
|
||||||
|
// already a member (exception not specified)
|
||||||
|
assertThrows(SocketException.class, () -> s.joinGroup(group));
|
||||||
|
|
||||||
|
// leave
|
||||||
|
s.leaveGroup(group);
|
||||||
|
|
||||||
|
// not a member (exception not specified)
|
||||||
|
assertThrows(SocketException.class, () -> s.leaveGroup(group));
|
||||||
|
|
||||||
|
// join/leave with outgoing multicast interface set and check that
|
||||||
|
// multicast datagrams can be sent and received
|
||||||
|
s.setOption(IP_MULTICAST_IF, ni);
|
||||||
|
s.joinGroup(group);
|
||||||
|
testSendReceive(s, group);
|
||||||
|
s.leaveGroup(group);
|
||||||
|
testSendNoReceive(s, group);
|
||||||
|
|
||||||
|
// not a multicast address
|
||||||
|
var localHost = InetAddress.getLocalHost();
|
||||||
|
assertThrows(SocketException.class, () -> s.joinGroup(localHost));
|
||||||
|
assertThrows(SocketException.class, () -> s.leaveGroup(localHost));
|
||||||
|
|
||||||
|
// IPv4 socket cannot join IPv6 group (exception not specified)
|
||||||
|
if (family == INET) {
|
||||||
|
InetAddress ip6Group = InetAddress.getByName("ff02::a");
|
||||||
|
assertThrows(SocketException.class, () -> s.joinGroup(ip6Group));
|
||||||
|
assertThrows(SocketException.class, () -> s.leaveGroup(ip6Group));
|
||||||
|
}
|
||||||
|
|
||||||
|
// null (exception not specified)
|
||||||
|
assertThrows(NullPointerException.class, () -> s.joinGroup(null));
|
||||||
|
assertThrows(NullPointerException.class, () -> s.leaveGroup(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test 2-arg joinGroup/leaveGroup
|
||||||
|
*/
|
||||||
|
static void testJoinGroup2(ProtocolFamily family,
|
||||||
|
MulticastSocket s,
|
||||||
|
InetAddress group,
|
||||||
|
NetworkInterface ni) throws IOException {
|
||||||
|
// check network interface not set
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_IF) == null);
|
||||||
|
|
||||||
|
// join on default interface
|
||||||
|
s.joinGroup(new InetSocketAddress(group, 0), null);
|
||||||
|
|
||||||
|
// join should not change the outgoing multicast interface
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_IF) == null);
|
||||||
|
|
||||||
|
// already a member (exception not specified)
|
||||||
|
assertThrows(SocketException.class,
|
||||||
|
() -> s.joinGroup(new InetSocketAddress(group, 0), null));
|
||||||
|
|
||||||
|
// leave
|
||||||
|
s.leaveGroup(new InetSocketAddress(group, 0), null);
|
||||||
|
|
||||||
|
// not a member (exception not specified)
|
||||||
|
assertThrows(SocketException.class,
|
||||||
|
() -> s.leaveGroup(new InetSocketAddress(group, 0), null));
|
||||||
|
|
||||||
|
// join on specified interface
|
||||||
|
s.joinGroup(new InetSocketAddress(group, 0), ni);
|
||||||
|
|
||||||
|
// join should not change the outgoing multicast interface
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_IF) == null);
|
||||||
|
|
||||||
|
// already a member (exception not specified)
|
||||||
|
assertThrows(SocketException.class,
|
||||||
|
() -> s.joinGroup(new InetSocketAddress(group, 0), ni));
|
||||||
|
|
||||||
|
// leave
|
||||||
|
s.leaveGroup(new InetSocketAddress(group, 0), ni);
|
||||||
|
|
||||||
|
// not a member (exception not specified)
|
||||||
|
assertThrows(SocketException.class,
|
||||||
|
() -> s.leaveGroup(new InetSocketAddress(group, 0), ni));
|
||||||
|
|
||||||
|
// join/leave with outgoing multicast interface set and check that
|
||||||
|
// multicast datagrams can be sent and received
|
||||||
|
s.setOption(IP_MULTICAST_IF, ni);
|
||||||
|
s.joinGroup(new InetSocketAddress(group, 0), null);
|
||||||
|
testSendReceive(s, group);
|
||||||
|
s.leaveGroup(new InetSocketAddress(group, 0), null);
|
||||||
|
testSendNoReceive(s, group);
|
||||||
|
s.joinGroup(new InetSocketAddress(group, 0), ni);
|
||||||
|
testSendReceive(s, group);
|
||||||
|
s.leaveGroup(new InetSocketAddress(group, 0), ni);
|
||||||
|
testSendNoReceive(s, group);
|
||||||
|
|
||||||
|
// not a multicast address
|
||||||
|
var localHost = InetAddress.getLocalHost();
|
||||||
|
assertThrows(SocketException.class,
|
||||||
|
() -> s.joinGroup(new InetSocketAddress(localHost, 0), null));
|
||||||
|
assertThrows(SocketException.class,
|
||||||
|
() -> s.leaveGroup(new InetSocketAddress(localHost, 0), null));
|
||||||
|
assertThrows(SocketException.class,
|
||||||
|
() -> s.joinGroup(new InetSocketAddress(localHost, 0), ni));
|
||||||
|
assertThrows(SocketException.class,
|
||||||
|
() -> s.leaveGroup(new InetSocketAddress(localHost, 0), ni));
|
||||||
|
|
||||||
|
// not an InetSocketAddress
|
||||||
|
var customSocketAddress = new SocketAddress() { };
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> s.joinGroup(customSocketAddress, null));
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> s.leaveGroup(customSocketAddress, null));
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> s.joinGroup(customSocketAddress, ni));
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> s.leaveGroup(customSocketAddress, ni));
|
||||||
|
|
||||||
|
// IPv4 socket cannot join IPv6 group
|
||||||
|
if (family == INET) {
|
||||||
|
InetAddress ip6Group = InetAddress.getByName("ff02::a");
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> s.joinGroup(new InetSocketAddress(ip6Group, 0), null));
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> s.joinGroup(new InetSocketAddress(ip6Group, 0), ni));
|
||||||
|
|
||||||
|
// not a member of IPv6 group (exception not specified)
|
||||||
|
assertThrows(SocketException.class,
|
||||||
|
() -> s.leaveGroup(new InetSocketAddress(ip6Group, 0), null));
|
||||||
|
assertThrows(SocketException.class,
|
||||||
|
() -> s.leaveGroup(new InetSocketAddress(ip6Group, 0), ni));
|
||||||
|
}
|
||||||
|
|
||||||
|
// null
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> s.joinGroup(null, null));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> s.leaveGroup(null, null));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> s.joinGroup(null, ni));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> s.leaveGroup(null, ni));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test getNetworkInterface/setNetworkInterface/getInterface/setInterface
|
||||||
|
* and IP_MULTICAST_IF socket option.
|
||||||
|
*/
|
||||||
|
static void testNetworkInterface(MulticastSocket s,
|
||||||
|
NetworkInterface ni) throws IOException {
|
||||||
|
// default value
|
||||||
|
NetworkInterface nif = s.getNetworkInterface();
|
||||||
|
assertTrue(nif.getIndex() == 0);
|
||||||
|
assertTrue(nif.inetAddresses().count() == 1);
|
||||||
|
assertTrue(nif.inetAddresses().findAny().orElseThrow().isAnyLocalAddress());
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_IF) == null);
|
||||||
|
assertTrue(s.getInterface().isAnyLocalAddress());
|
||||||
|
|
||||||
|
// setNetworkInterface
|
||||||
|
s.setNetworkInterface(ni);
|
||||||
|
assertTrue(s.getNetworkInterface().equals(ni));
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_IF).equals(ni));
|
||||||
|
InetAddress address = s.getInterface();
|
||||||
|
assertTrue(ni.inetAddresses().filter(address::equals).findAny().isPresent());
|
||||||
|
|
||||||
|
// setInterface
|
||||||
|
s.setInterface(address);
|
||||||
|
assertTrue(s.getInterface().equals(address));
|
||||||
|
assertTrue(s.getNetworkInterface()
|
||||||
|
.inetAddresses()
|
||||||
|
.filter(address::equals)
|
||||||
|
.findAny()
|
||||||
|
.isPresent());
|
||||||
|
|
||||||
|
// null (exception not specified)
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> s.setNetworkInterface(null));
|
||||||
|
assertThrows(SocketException.class, () -> s.setInterface(null));
|
||||||
|
|
||||||
|
// setOption(IP_MULTICAST_IF)
|
||||||
|
s.setOption(IP_MULTICAST_IF, ni);
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_IF).equals(ni));
|
||||||
|
assertTrue(s.getNetworkInterface().equals(ni));
|
||||||
|
|
||||||
|
// bad values for IP_MULTICAST_IF
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> s.setOption(IP_MULTICAST_IF, null));
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> s.setOption((SocketOption) IP_MULTICAST_IF, "badValue"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test getTimeToLive/setTimeToLive/getTTL/getTTL and IP_MULTICAST_TTL socket
|
||||||
|
* option.
|
||||||
|
*/
|
||||||
|
static void testTimeToLive(MulticastSocket s) throws IOException {
|
||||||
|
// should be 1 by default
|
||||||
|
assertTrue(s.getTimeToLive() == 1);
|
||||||
|
assertTrue(s.getTTL() == 1);
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_TTL) == 1);
|
||||||
|
|
||||||
|
// setTimeToLive
|
||||||
|
for (int ttl = 0; ttl <= 2; ttl++) {
|
||||||
|
s.setTimeToLive(ttl);
|
||||||
|
assertTrue(s.getTimeToLive() == ttl);
|
||||||
|
assertTrue(s.getTTL() == ttl);
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_TTL) == ttl);
|
||||||
|
}
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> s.setTimeToLive(-1));
|
||||||
|
|
||||||
|
// setTTL
|
||||||
|
for (byte ttl = (byte) -2; ttl <= 2; ttl++) {
|
||||||
|
s.setTTL(ttl);
|
||||||
|
assertTrue(s.getTTL() == ttl);
|
||||||
|
int intValue = Byte.toUnsignedInt(ttl);
|
||||||
|
assertTrue(s.getTimeToLive() == intValue);
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_TTL) == intValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// setOption(IP_MULTICAST_TTL)
|
||||||
|
for (int ttl = 0; ttl <= 2; ttl++) {
|
||||||
|
s.setOption(IP_MULTICAST_TTL, ttl);
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_TTL) == ttl);
|
||||||
|
assertTrue(s.getTimeToLive() == ttl);
|
||||||
|
assertTrue(s.getTTL() == ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// bad values for IP_MULTICAST_TTL
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> s.setOption(IP_MULTICAST_TTL, -1));
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> s.setOption(IP_MULTICAST_TTL, null));
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> s.setOption((SocketOption) IP_MULTICAST_TTL, "badValue"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test getLoopbackMode/setLoopbackMode and IP_MULTICAST_LOOP socket option.
|
||||||
|
*/
|
||||||
|
static void testLoopbackMode(MulticastSocket s) throws IOException {
|
||||||
|
// should be enabled by default
|
||||||
|
assertTrue(s.getLoopbackMode() == false);
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_LOOP) == true);
|
||||||
|
|
||||||
|
// setLoopbackMode
|
||||||
|
s.setLoopbackMode(true); // disable
|
||||||
|
assertTrue(s.getLoopbackMode());
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_LOOP) == false);
|
||||||
|
s.setLoopbackMode(false); // enable
|
||||||
|
assertTrue(s.getLoopbackMode() == false);
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_LOOP) == true);
|
||||||
|
|
||||||
|
// setOption(IP_MULTICAST_LOOP)
|
||||||
|
s.setOption(IP_MULTICAST_LOOP, false); // disable
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_LOOP) == false);
|
||||||
|
assertTrue(s.getLoopbackMode() == true);
|
||||||
|
s.setOption(IP_MULTICAST_LOOP, true); // enable
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_LOOP) == true);
|
||||||
|
assertTrue(s.getLoopbackMode() == false);
|
||||||
|
|
||||||
|
// bad values for IP_MULTICAST_LOOP
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> s.setOption(IP_MULTICAST_LOOP, null));
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> s.setOption((SocketOption) IP_MULTICAST_LOOP, "badValue"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a datagram to the given multicast group and check that it is received.
|
||||||
|
*/
|
||||||
|
static void testSendReceive(MulticastSocket s, InetAddress group) throws IOException {
|
||||||
|
// outgoing multicast interface needs to be set
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_IF) != null);
|
||||||
|
|
||||||
|
SocketAddress target = new InetSocketAddress(group, s.getLocalPort());
|
||||||
|
byte[] message = "hello".getBytes("UTF-8");
|
||||||
|
|
||||||
|
// send message to multicast group
|
||||||
|
DatagramPacket p = new DatagramPacket(message, message.length);
|
||||||
|
p.setSocketAddress(target);
|
||||||
|
s.send(p, (byte) 1);
|
||||||
|
|
||||||
|
// receive message
|
||||||
|
s.setSoTimeout(0);
|
||||||
|
p = new DatagramPacket(new byte[1024], 100);
|
||||||
|
s.receive(p);
|
||||||
|
|
||||||
|
assertTrue(p.getLength() == message.length);
|
||||||
|
assertTrue(p.getPort() == s.getLocalPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a datagram to the given multicast group and check that it is not
|
||||||
|
* received.
|
||||||
|
*/
|
||||||
|
static void testSendNoReceive(MulticastSocket s, InetAddress group) throws IOException {
|
||||||
|
// outgoing multicast interface needs to be set
|
||||||
|
assertTrue(s.getOption(IP_MULTICAST_IF) != null);
|
||||||
|
|
||||||
|
SocketAddress target = new InetSocketAddress(group, s.getLocalPort());
|
||||||
|
byte[] message = "hello".getBytes("UTF-8");
|
||||||
|
|
||||||
|
// send datagram to multicast group
|
||||||
|
DatagramPacket p = new DatagramPacket(message, message.length);
|
||||||
|
p.setSocketAddress(target);
|
||||||
|
s.send(p, (byte) 1);
|
||||||
|
|
||||||
|
// datagram should not be received
|
||||||
|
s.setSoTimeout(500);
|
||||||
|
p = new DatagramPacket(new byte[1024], 100);
|
||||||
|
try {
|
||||||
|
s.receive(p);
|
||||||
|
assertTrue(false);
|
||||||
|
} catch (SocketTimeoutException expected) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void assertTrue(boolean e) {
|
||||||
|
if (!e) throw new RuntimeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ThrowableRunnable {
|
||||||
|
void run() throws Exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assertThrows(Class<?> exceptionClass, ThrowableRunnable task) {
|
||||||
|
try {
|
||||||
|
task.run();
|
||||||
|
throw new RuntimeException("Exception not thrown");
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (!exceptionClass.isInstance(e)) {
|
||||||
|
throw new RuntimeException("expected: " + exceptionClass + ", actual: " + e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue