changeset 57736:4b49cfba69fe

8236925: (dc) Upgrade DatagramChannel socket adaptor to extend MulticastSocket Reviewed-by: dfuchs
author alanb
date Sun, 19 Jan 2020 08:02:46 +0000
parents fdf6c221ebdc
children 882fc6a4d53c
files src/java.base/share/classes/java/net/DatagramSocket.java src/java.base/share/classes/java/net/MulticastSocket.java src/java.base/share/classes/java/nio/channels/DatagramChannel.java src/java.base/share/classes/sun/nio/ch/DatagramChannelImpl.java src/java.base/share/classes/sun/nio/ch/DatagramSocketAdaptor.java src/java.base/unix/native/libnio/ch/Net.c test/jdk/java/nio/channels/DatagramChannel/AdaptorMulticasting.java
diffstat 7 files changed, 839 insertions(+), 137 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/net/DatagramSocket.java	Sat Jan 18 19:11:28 2020 +0000
+++ b/src/java.base/share/classes/java/net/DatagramSocket.java	Sun Jan 19 08:02:46 2020 +0000
@@ -115,7 +115,6 @@
     /**
      * Various states of this socket.
      */
-    private boolean created = false;
     private boolean bound = false;
     private boolean closed = false;
     private Object closeLock = new Object();
@@ -123,12 +122,12 @@
     /*
      * The implementation of this DatagramSocket.
      */
-    DatagramSocketImpl impl;
+    private final DatagramSocketImpl impl;
 
     /**
      * Are we using an older DatagramSocketImpl?
      */
-    boolean oldImpl = false;
+    final boolean oldImpl;
 
     /**
      * Set when a socket is ST_CONNECTED until we are certain
@@ -255,7 +254,7 @@
         if (impl == null)
             throw new NullPointerException();
         this.impl = impl;
-        checkOldImpl();
+        this.oldImpl = checkOldImpl(impl);
     }
 
     /**
@@ -282,8 +281,17 @@
      * @since   1.4
      */
     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.
-        createImpl();
+        boolean multicast = (this instanceof MulticastSocket);
+        this.impl = createImpl(multicast);
+        this.oldImpl = checkOldImpl(impl);
         if (bindaddr != null) {
             try {
                 bind(bindaddr);
@@ -346,9 +354,11 @@
         this(new InetSocketAddress(laddr, port));
     }
 
-    private void checkOldImpl() {
-        if (impl == null)
-            return;
+    /**
+     * Return true if the given DatagramSocketImpl is an "old" impl. An old impl
+     * 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
         // getDeclaredMethod, therefore we need permission to access the member
         try {
@@ -361,42 +371,40 @@
                         return null;
                     }
                 });
+            return false;
         } catch (java.security.PrivilegedActionException e) {
-            oldImpl = true;
+            return true;
         }
     }
 
     static Class<?> implClass = null;
 
-    void createImpl() throws SocketException {
-        if (impl == null) {
-            if (factory != null) {
-                impl = factory.createDatagramSocketImpl();
-                checkOldImpl();
-            } else {
-                boolean isMulticast = (this instanceof MulticastSocket) ? true : false;
-                impl = DefaultDatagramSocketImplFactory.createDatagramSocketImpl(isMulticast);
-
-                checkOldImpl();
-            }
+    /**
+     * Creates a DatagramSocketImpl.
+     * @param multicast true if the DatagramSocketImpl is for a MulticastSocket
+     */
+    private static DatagramSocketImpl createImpl(boolean multicast) throws SocketException {
+        DatagramSocketImpl impl;
+        DatagramSocketImplFactory factory = DatagramSocket.factory;
+        if (factory != null) {
+            impl = factory.createDatagramSocketImpl();
+        } else {
+            impl = DefaultDatagramSocketImplFactory.createDatagramSocketImpl(multicast);
         }
         // creates a udp socket
         impl.create();
-        created = true;
+        return impl;
     }
 
     /**
-     * Get the {@code DatagramSocketImpl} attached to this socket,
-     * creating it if necessary.
+     * Return the {@code DatagramSocketImpl} attached to this socket.
      *
      * @return  the {@code DatagramSocketImpl} attached to that
      *          DatagramSocket
-     * @throws SocketException if creation fails.
+     * @throws SocketException never thrown
      * @since 1.4
      */
     DatagramSocketImpl getImpl() throws SocketException {
-        if (!created)
-            createImpl();
         return impl;
     }
 
@@ -1329,7 +1337,7 @@
     /**
      * User defined factory for all datagram sockets.
      */
-    static DatagramSocketImplFactory factory;
+    private static volatile DatagramSocketImplFactory factory;
 
     /**
      * Sets the datagram socket implementation factory for the
--- a/src/java.base/share/classes/java/net/MulticastSocket.java	Sat Jan 18 19:11:28 2020 +0000
+++ b/src/java.base/share/classes/java/net/MulticastSocket.java	Sun Jan 19 08:02:46 2020 +0000
@@ -29,6 +29,7 @@
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.Set;
+
 /**
  * The multicast datagram socket class is useful for sending
  * and receiving IP multicast packets. A MulticastSocket is
@@ -208,6 +209,10 @@
     public MulticastSocket(SocketAddress bindaddr) throws IOException {
         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
         setReuseAddress(true);
 
--- a/src/java.base/share/classes/java/nio/channels/DatagramChannel.java	Sat Jan 18 19:11:28 2020 +0000
+++ b/src/java.base/share/classes/java/nio/channels/DatagramChannel.java	Sun Jan 19 08:02:46 2020 +0000
@@ -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.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -234,9 +234,6 @@
     /**
      * 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
      */
     public abstract DatagramSocket socket();
--- a/src/java.base/share/classes/sun/nio/ch/DatagramChannelImpl.java	Sat Jan 18 19:11:28 2020 +0000
+++ b/src/java.base/share/classes/sun/nio/ch/DatagramChannelImpl.java	Sun Jan 19 08:02:46 2020 +0000
@@ -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.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -1585,6 +1585,22 @@
     }
 
     /**
+     * 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.
      */
     void block(MembershipKeyImpl key, InetAddress source)
--- a/src/java.base/share/classes/sun/nio/ch/DatagramSocketAdaptor.java	Sat Jan 18 19:11:28 2020 +0000
+++ b/src/java.base/share/classes/sun/nio/ch/DatagramSocketAdaptor.java	Sun Jan 19 08:02:46 2020 +0000
@@ -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.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,15 +26,17 @@
 package sun.nio.ch;
 
 import java.io.IOException;
+import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
 import java.lang.invoke.VarHandle;
 import java.net.DatagramPacket;
 import java.net.DatagramSocket;
-import java.net.DatagramSocketImpl;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.NetworkInterface;
+import java.net.MulticastSocket;
 import java.net.SocketAddress;
 import java.net.SocketException;
 import java.net.SocketOption;
@@ -43,21 +45,26 @@
 import java.nio.channels.AlreadyConnectedException;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.DatagramChannel;
+import java.nio.channels.MembershipKey;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
+import java.security.PrivilegedExceptionAction;
+import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
-// Make a datagram-socket channel look like a datagram socket.
-//
-// 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
-// class.
-//
-
-class DatagramSocketAdaptor
-    extends DatagramSocket
+/**
+ * A multicast datagram socket based on a datagram channel.
+ *
+ * This class overrides every public method defined by java.net.DatagramSocket
+ * 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.
+ */
+public class DatagramSocketAdaptor
+    extends MulticastSocket
 {
     // The channel being adapted
     private final DatagramChannelImpl dc;
@@ -65,14 +72,17 @@
     // Timeout "option" value for receives
     private volatile int timeout;
 
-    // create DatagramSocket with useless impl
-    private DatagramSocketAdaptor(DatagramChannelImpl dc) {
-        super(new DummyDatagramSocketImpl());
+    private DatagramSocketAdaptor(DatagramChannelImpl dc) throws IOException {
+        super(/*SocketAddress*/null);
         this.dc = 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 {
@@ -409,114 +419,263 @@
         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 <T> T shouldNotGetHere() {
-            throw new InternalError("Should not get here");
-        }
+    private static InetAddress checkGroup(SocketAddress mcastaddr) throws SocketException {
+        if (mcastaddr == null || !(mcastaddr instanceof InetSocketAddress))
+            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
-        protected void create() {
-            shouldNotGetHere();
+    @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
-        protected void bind(int lport, InetAddress laddr) {
-            shouldNotGetHere();
-        }
-
-        @Override
-        protected void send(DatagramPacket p) {
-            shouldNotGetHere();
-        }
+    }
 
-        @Override
-        protected int peek(InetAddress address) {
-            return shouldNotGetHere();
+    @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
-        protected int peekData(DatagramPacket p) {
-            return shouldNotGetHere();
-        }
-
-        @Override
-        protected void receive(DatagramPacket p) {
-            shouldNotGetHere();
+    @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);
         }
-
-        @Deprecated
-        protected void setTTL(byte ttl) {
-            shouldNotGetHere();
+        synchronized (outgoingInterfaceLock) {
+            // set interface and update cached values
+            setNetworkInterface(ni);
+            outgoingNetworkInterface = ni;
+            outgoingInetAddress = inf;
         }
+    }
 
-        @Deprecated
-        protected byte getTTL() {
-            return shouldNotGetHere();
-        }
-
-        @Override
-        protected void setTimeToLive(int ttl) {
-            shouldNotGetHere();
+    @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
-        protected int getTimeToLive() {
-            return shouldNotGetHere();
-        }
+        // no interface set
+        return anyInetAddress();
+    }
 
-        @Override
-        protected void join(InetAddress group) {
-            shouldNotGetHere();
+    @Override
+    public void setNetworkInterface(NetworkInterface netIf) throws SocketException {
+        try {
+            setOption(StandardSocketOptions.IP_MULTICAST_IF, netIf);
+        } catch (IOException e) {
+            Net.translateToSocketException(e);
         }
+    }
 
-        @Override
-        protected void leave(InetAddress inetaddr) {
-            shouldNotGetHere();
+    @Override
+    public NetworkInterface getNetworkInterface() throws SocketException {
+        NetworkInterface ni = outgoingNetworkInterface();
+        if (ni == null) {
+            // return NetworkInterface with index == 0 as placeholder
+            ni = anyNetworkInterface();
         }
+        return ni;
+    }
 
-        @Override
-        protected void joinGroup(SocketAddress group, NetworkInterface netIf) {
-            shouldNotGetHere();
-        }
+    @Override
+    @Deprecated
+    public void setLoopbackMode(boolean disable) throws SocketException {
+        boolean enable = !disable;
+        setBooleanOption(StandardSocketOptions.IP_MULTICAST_LOOP, enable);
+    }
 
-        @Override
-        protected void leaveGroup(SocketAddress mcastaddr, NetworkInterface netIf) {
-            shouldNotGetHere();
-        }
+    @Override
+    @Deprecated
+    public boolean getLoopbackMode() throws SocketException {
+        boolean enabled = getBooleanOption(StandardSocketOptions.IP_MULTICAST_LOOP);
+        return !enabled;
+    }
 
-        @Override
-        protected void close() {
-            shouldNotGetHere();
+    @Override
+    @Deprecated
+    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();
         }
+    }
 
-        @Override
-        public Object getOption(int optID) {
-            return shouldNotGetHere();
+    /**
+     * Returns the outgoing NetworkInterface or null if not set.
+     */
+    private NetworkInterface outgoingNetworkInterface() throws SocketException {
+        try {
+            return getOption(StandardSocketOptions.IP_MULTICAST_IF);
+        } catch (IOException e) {
+            Net.translateToSocketException(e);
+            return null; // keep compiler happy
         }
-
-        @Override
-        public void setOption(int optID, Object value) {
-            shouldNotGetHere();
-        }
+    }
 
-        @Override
-        protected <T> void setOption(SocketOption<T> name, T value) {
-            shouldNotGetHere();
-        }
+    /**
+     * Returns the default NetworkInterface to use when joining or leaving a
+     * 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 <T> T getOption(SocketOption<T> name) {
-            return shouldNotGetHere();
-        }
+    /**
+     * Returns the placeholder for "any network interface", its index is 0.
+     */
+    private NetworkInterface anyNetworkInterface() {
+        InetAddress[] addrs = new InetAddress[1];
+        addrs[0] = anyInetAddress();
+        return NetworkInterfaces.newNetworkInterface(addrs[0].getHostName(), 0, addrs);
+    }
 
-        @Override
-        protected Set<SocketOption<?>> supportedOptions() {
-            return shouldNotGetHere();
-        }
+    /**
+     * Returns the InetAddress representing anyLocalAddress.
+     */
+    private InetAddress anyInetAddress() {
+        return new InetSocketAddress(0).getAddress();
     }
 
     /**
@@ -528,13 +687,8 @@
         private static final VarHandle BUF_LENGTH;
         static {
             try {
-                PrivilegedAction<Lookup> pa = () -> {
-                    try {
-                        return MethodHandles.privateLookupIn(DatagramPacket.class, MethodHandles.lookup());
-                    } catch (Exception e) {
-                        throw new ExceptionInInitializerError(e);
-                    }
-                };
+                PrivilegedExceptionAction<Lookup> pa = () ->
+                    MethodHandles.privateLookupIn(DatagramPacket.class, MethodHandles.lookup());
                 MethodHandles.Lookup l = AccessController.doPrivileged(pa);
                 LENGTH = l.findVarHandle(DatagramPacket.class, "length", int.class);
                 BUF_LENGTH = l.findVarHandle(DatagramPacket.class, "bufLength", int.class);
@@ -562,4 +716,47 @@
             }
         }
     }
+
+    /**
+     * 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);
+            }
+        }
+    }
 }
\ No newline at end of file
--- a/src/java.base/unix/native/libnio/ch/Net.c	Sat Jan 18 19:11:28 2020 +0000
+++ b/src/java.base/unix/native/libnio/ch/Net.c	Sun Jan 19 08:02:46 2020 +0000
@@ -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.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -587,6 +587,13 @@
     }
 
     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 (join && (errno == ENOPROTOOPT || errno == EOPNOTSUPP))
             return IOS_UNAVAILABLE;
@@ -657,6 +664,13 @@
     }
 
     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 (join && (errno == ENOPROTOOPT || errno == EOPNOTSUPP))
             return IOS_UNAVAILABLE;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/nio/channels/DatagramChannel/AdaptorMulticasting.java	Sun Jan 19 08:02:46 2020 +0000
@@ -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);
+            }
+        }
+    }
+}