--- a/src/java.base/share/classes/java/net/AbstractPlainDatagramSocketImpl.java Thu Oct 17 20:54:25 2019 +0100
+++ b/src/java.base/share/classes/java/net/AbstractPlainDatagramSocketImpl.java Thu Oct 17 21:15:33 2019 +0100
@@ -30,7 +30,7 @@
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
-
+import sun.net.PlatformDatagramSocketImpl;
import sun.net.ResourceManager;
import sun.net.ext.ExtendedSocketOptions;
import sun.net.util.IPAddressUtil;
@@ -46,13 +46,13 @@
* @author Pavani Diwanji
*/
-abstract class AbstractPlainDatagramSocketImpl extends DatagramSocketImpl
+abstract class AbstractPlainDatagramSocketImpl extends PlatformDatagramSocketImpl
{
/* timeout value for receive() */
- int timeout = 0;
- boolean connected = false;
- private int trafficClass = 0;
- protected InetAddress connectedAddress = null;
+ int timeout;
+ boolean connected;
+ private int trafficClass;
+ protected InetAddress connectedAddress;
private int connectedPort = -1;
private static final String os =
@@ -84,6 +84,12 @@
return isReusePortAvailable;
}
+ private final boolean isMulticast;
+
+ AbstractPlainDatagramSocketImpl(boolean isMulticast) {
+ this.isMulticast = isMulticast;
+ }
+
/**
* Creates a datagram socket
*/
@@ -430,7 +436,7 @@
@Override
protected Set<SocketOption<?>> supportedOptions() {
- if (getDatagramSocket() instanceof MulticastSocket)
+ if (isMulticast)
return multicastSocketOptions;
else
return datagramSocketOptions;
@@ -523,7 +529,8 @@
protected abstract void connect0(InetAddress address, int port) throws SocketException;
protected abstract void disconnect0(int family);
- protected boolean nativeConnectDisabled() {
+ @Override
+ public boolean nativeConnectDisabled() {
return connectDisabled;
}
--- a/src/java.base/share/classes/java/net/DatagramSocket.java Thu Oct 17 20:54:25 2019 +0100
+++ b/src/java.base/share/classes/java/net/DatagramSocket.java Thu Oct 17 21:15:33 2019 +0100
@@ -25,6 +25,8 @@
package java.net;
+import sun.net.PlatformDatagramSocketImpl;
+
import java.io.IOException;
import java.nio.channels.DatagramChannel;
import java.security.AccessController;
@@ -147,8 +149,8 @@
bind(new InetSocketAddress(0));
// old impls do not support connect/disconnect
- if (oldImpl || (impl instanceof AbstractPlainDatagramSocketImpl &&
- ((AbstractPlainDatagramSocketImpl)impl).nativeConnectDisabled())) {
+ if (oldImpl || (impl instanceof PlatformDatagramSocketImpl &&
+ ((PlatformDatagramSocketImpl)impl).nativeConnectDisabled())) {
connectState = ST_CONNECTED_NO_IMPL;
} else {
try {
@@ -338,7 +340,6 @@
}
// creates a udp socket
impl.create();
- impl.setDatagramSocket(this);
created = true;
}
--- a/src/java.base/share/classes/java/net/DatagramSocketImpl.java Thu Oct 17 20:54:25 2019 +0100
+++ b/src/java.base/share/classes/java/net/DatagramSocketImpl.java Thu Oct 17 21:15:33 2019 +0100
@@ -48,20 +48,6 @@
*/
protected FileDescriptor fd;
- /**
- * The DatagramSocket or MulticastSocket
- * that owns this impl
- */
- DatagramSocket socket;
-
- void setDatagramSocket(DatagramSocket socket) {
- this.socket = socket;
- }
-
- DatagramSocket getDatagramSocket() {
- return socket;
- }
-
int dataAvailable() {
// default impl returns zero, which disables the calling
// functionality
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/net/PlatformDatagramSocketImpl.java Thu Oct 17 21:15:33 2019 +0100
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package sun.net;
+
+import java.net.DatagramSocketImpl;
+
+public abstract class PlatformDatagramSocketImpl extends DatagramSocketImpl {
+ public abstract boolean nativeConnectDisabled();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/nio/ch/NioDatagramSocketImpl.java Thu Oct 17 21:15:33 2019 +0100
@@ -0,0 +1,905 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package sun.nio.ch;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.ProtocolFamily;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.SocketOption;
+import java.net.SocketOptions;
+import java.net.SocketTimeoutException;
+import java.net.StandardProtocolFamily;
+import java.net.StandardSocketOptions;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+import jdk.internal.ref.CleanerFactory;
+import sun.net.PlatformDatagramSocketImpl;
+import sun.net.ResourceManager;
+import sun.net.ext.ExtendedSocketOptions;
+import sun.net.util.IPAddressUtil;
+import sun.security.action.GetPropertyAction;
+import static java.net.StandardProtocolFamily.INET6;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+/**
+ * A DatagramSocketImpl based on low-level NIO primitives.
+ */
+public class NioDatagramSocketImpl extends PlatformDatagramSocketImpl {
+
+ private static final NativeDispatcher nd = new SocketDispatcher();
+
+ private static final int MAX_PACKET_LEN = 65536;
+
+ private static final ProtocolFamily family = family();
+
+ // Lock held by current reading or connecting thread
+ private final ReentrantLock readLock = new ReentrantLock();
+
+ // Lock held by current writing or connecting thread
+ private final ReentrantLock writeLock = new ReentrantLock();
+
+ // The stateLock for read/changing state
+ private final Object stateLock = new Object();
+ private static final int ST_NEW = 0;
+ private static final int ST_UNCONNECTED = 1;
+ private static final int ST_CONNECTING = 2;
+ private static final int ST_CONNECTED = 3;
+ private static final int ST_CLOSING = 4;
+ private static final int ST_CLOSED = 5;
+ private volatile int state; // need stateLock to change
+
+ // set by create, protected by stateLock
+ private FileDescriptorCloser closer;
+
+ // set to true when the socket is in non-blocking mode
+ private volatile boolean nonBlocking;
+
+ // used by connect/read/write/accept, protected by stateLock
+ private long readerThread;
+ private long writerThread;
+
+ // Binding and remote address (when connected)
+ private InetSocketAddress remoteAddress;
+
+ // receive timeout in millis
+ private volatile int timeout;
+
+ /** Returns true if the socket is open. */
+ private boolean isOpen() {
+ return state < ST_CLOSING;
+ }
+
+ /** Throws SocketException if the socket is not open. */
+ private void ensureOpen() throws SocketException {
+ int state = this.state;
+ if (state == ST_NEW)
+ throw new SocketException("Socket not created");
+ if (state >= ST_CLOSING)
+ throw new SocketException("Socket closed");
+ }
+
+ /**
+ * Returns the socket protocol family.
+ */
+ private static ProtocolFamily family() {
+ if (Net.isIPv6Available()) {
+ return StandardProtocolFamily.INET6;
+ } else {
+ return StandardProtocolFamily.INET;
+ }
+ }
+
+ @Override
+ protected void create() throws SocketException {
+ synchronized (stateLock) {
+ if (state != ST_NEW)
+ throw new SocketException("Already created");
+ ResourceManager.beforeUdpCreate();
+ FileDescriptor fd;
+ try {
+ fd = Net.socket(false);
+ } catch (IOException ioe) {
+ ResourceManager.afterUdpClose();
+ SocketException se = new SocketException(ioe.getMessage());
+ se.initCause(ioe);
+ throw se;
+ }
+ this.fd = fd;
+ this.closer = FileDescriptorCloser.create(this);
+ this.state = ST_UNCONNECTED;
+ }
+ }
+
+ @Override
+ protected void bind(int port, InetAddress addr) throws SocketException {
+ synchronized (stateLock) {
+ ensureOpen();
+ if (localPort != 0)
+ throw new SocketException("Already bound");
+ try {
+ Net.bind(fd, addr, port);
+ localPort = Net.localAddress(fd).getPort();
+ } catch (SocketException e) {
+ throw e;
+ } catch (IOException ioe) {
+ SocketException se = new SocketException(ioe.getMessage());
+ se.initCause(ioe);
+ throw se;
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <E extends Throwable> void sneakyThrow(Throwable e) throws E {
+ throw (E) e;
+ }
+
+ @Override
+ protected int peek(InetAddress i) {
+ DatagramPacket packet = new DatagramPacket(new byte[1], 0, 1);
+ try {
+ receive(packet, true);
+ return packet.getPort();
+ } catch (IOException e) {
+ sneakyThrow(e);
+ throw new InternalError("should not reach here");
+ }
+ }
+
+ @Override
+ protected int peekData(DatagramPacket packet) {
+ try {
+ receive(packet, true);
+ return packet.getPort();
+ } catch (IOException e) {
+ sneakyThrow(e);
+ throw new InternalError("should not reach here");
+ }
+ }
+
+ /**
+ * Disables the current thread for scheduling purposes until the socket is
+ * ready for I/O, or is asynchronously closed, for up to the specified
+ * waiting time.
+ * @throws IOException if an I/O error occurs
+ */
+ private void park(int event, long nanos) throws IOException {
+ long millis;
+ if (nanos == 0) {
+ millis = -1;
+ } else {
+ millis = NANOSECONDS.toMillis(nanos);
+ }
+ Net.poll(fd, event, millis);
+ }
+
+ /**
+ * Disables the current thread for scheduling purposes until the socket is
+ * ready for I/O or is asynchronously closed.
+ * @throws IOException if an I/O error occurs
+ */
+ private void park(int event) throws IOException {
+ park(event, 0);
+ }
+
+ /**
+ * Marks the beginning of a write operation that might block.
+ * @throws SocketException if the socket is closed or not connected
+ */
+ private InetSocketAddress beginWrite() throws SocketException {
+ synchronized (stateLock) {
+ ensureOpen();
+ writerThread = NativeThread.current();
+ return remoteAddress;
+ }
+ }
+ /**
+ * Marks the end of a write operation that may have blocked.
+ */
+ private void endWrite(boolean completed) throws SocketException {
+ synchronized (stateLock) {
+ writerThread = 0;
+ int state = this.state;
+ if (state == ST_CLOSING)
+ tryFinishClose();
+ if (!completed && state >= ST_CLOSING)
+ throw new SocketException("Socket closed");
+ }
+ }
+
+ /**
+ * Attempts to send bytes from the given byte array, to the given (optional)
+ * address.
+ */
+ private int trySend(byte[] b, int off, int len, InetAddress address, int port)
+ throws IOException
+ {
+ ByteBuffer src = Util.getTemporaryDirectBuffer(len);
+ assert src.position() == 0 : "Expected source position of 0, in " + src;
+ assert src.remaining() == len : "Expected remaining " + len + ", in " + src;
+ try {
+ src.put(b, off, len);
+ return send0(true, fd, ((DirectBuffer)src).address(), len, address, port);
+ } finally {
+ Util.offerFirstTemporaryDirectBuffer(src);
+ }
+ }
+
+ @Override
+ protected void send(DatagramPacket p) throws IOException {
+ Objects.requireNonNull(p);
+ InetSocketAddress target = Net.checkAddress(p.getSocketAddress());
+ byte[] b = p.getData();
+ int off = p.getOffset();
+ int len = p.getLength();
+ if (len > MAX_PACKET_LEN)
+ len = MAX_PACKET_LEN;
+
+ writeLock.lock();
+ try {
+ int n = 0;
+ InetAddress targetAddress = null;
+ int targetPort = 0;
+ try {
+ SocketAddress remote = beginWrite();
+ if (remote != null) {
+ // connected
+ if (!target.equals(remote)) {
+ String msg = "Connected address and packet address differ";
+ throw new IllegalArgumentException(msg);
+ }
+ } else {
+ // not connected
+ if (target.getAddress().isLinkLocalAddress())
+ target = IPAddressUtil.toScopedAddress(target);
+ targetAddress = target.getAddress();
+ targetPort = target.getPort();
+ }
+ n = trySend(b, off, len, targetAddress, targetPort);
+ while (IOStatus.okayToRetry(n) && isOpen()) {
+ park(Net.POLLOUT);
+ n = trySend(b, off, len, targetAddress, targetPort);
+ }
+ } finally {
+ endWrite(n > 0);
+ assert n >= 0;
+ }
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ /**
+ * Configures the socket to blocking mode. This method is a no-op if the
+ * socket is already in blocking mode.
+ * @throws IOException if closed or there is an I/O error changing the mode
+ */
+ private void configureBlocking() throws IOException {
+ assert readLock.isHeldByCurrentThread();
+ if (nonBlocking) {
+ synchronized (stateLock) {
+ ensureOpen();
+ IOUtil.configureBlocking(fd, true);
+ nonBlocking = false;
+ }
+ }
+ }
+
+ /**
+ * Configures the socket to non-blocking mode. This method is a no-op if the
+ * socket is already in non-blocking mode.
+ * @throws IOException if closed or there is an I/O error changing the mode
+ */
+ private void configureNonBlocking() throws IOException {
+ assert readLock.isHeldByCurrentThread();
+ if (!nonBlocking) {
+ synchronized (stateLock) {
+ ensureOpen();
+ IOUtil.configureBlocking(fd, false);
+ nonBlocking = true;
+ }
+ }
+ }
+
+ private InetSocketAddress beginRead() throws SocketException {
+ synchronized (stateLock) {
+ ensureOpen();
+ readerThread = NativeThread.current();
+ return remoteAddress;
+ }
+ }
+
+ private void endRead(boolean completed) throws SocketException {
+ synchronized (stateLock) {
+ readerThread = 0;
+ int state = this.state;
+ if (state == ST_CLOSING)
+ tryFinishClose();
+ if (!completed && state >= ST_CLOSING)
+ throw new SocketException("Socket closed");
+ }
+ }
+
+ private InetSocketAddress sender = new InetSocketAddress(0); // Set by receive0
+
+ /**
+ * Attempts to read bytes from the socket into the given byte array.
+ */
+ private int tryReceive(byte[] b, int off, int len, boolean isPeek)
+ throws IOException
+ {
+ ByteBuffer dst = Util.getTemporaryDirectBuffer(len);
+ assert dst.position() == 0;
+ assert dst.remaining() >= len;
+ try {
+ int n = receive0(fd, ((DirectBuffer)dst).address(), len, isPeek,
+ sender.getAddress(), sender.getPort());
+ assert n <= len : "received:" + n + ", expected len:" + len;
+ if (n > 0) {
+ dst.get(b, off, n);
+ }
+ return n;
+ } finally {
+ Util.offerFirstTemporaryDirectBuffer(dst);
+ }
+ }
+
+ /**
+ * Reads bytes from the socket into the given byte array with a timeout.
+ * @throws SocketTimeoutException if the read timeout elapses
+ */
+ private int timedReceive(byte[] b, int off, int len, long nanos, boolean isPeek)
+ throws IOException
+ {
+ long startNanos = System.nanoTime();
+ int n = tryReceive(b, off, len, isPeek);
+ while (n == IOStatus.UNAVAILABLE && isOpen()) {
+ long remainingNanos = nanos - (System.nanoTime() - startNanos);
+ if (remainingNanos <= 0) {
+ throw new SocketTimeoutException("Receive timed out");
+ }
+ park(Net.POLLIN, remainingNanos);
+ n = tryReceive(b, off, len, isPeek);
+ }
+ return n;
+ }
+
+ @Override
+ protected void receive(DatagramPacket p) throws IOException {
+ receive(p, false);
+ }
+
+ private void receive(DatagramPacket p, boolean isPeek) throws IOException {
+ Objects.requireNonNull(p);
+ byte[] b = p.getData();
+ int off = p.getOffset();
+ int len = b.length - off;
+ assert len >= 0;
+ if (len > MAX_PACKET_LEN)
+ len = MAX_PACKET_LEN;
+
+ readLock.lock();
+ try {
+ int n = 0;
+ try {
+ SocketAddress remote = beginRead();
+ boolean connected = (remote != null);
+ int timeout = this.timeout;
+ if (timeout > 0) {
+ // receive with timeout
+ configureNonBlocking();
+ long nanos = MILLISECONDS.toNanos(timeout);
+ n = timedReceive(b, off, len, nanos, isPeek);
+ } else {
+ // receive, no timeout
+ n = tryReceive(b, off, len, isPeek);
+ while (IOStatus.okayToRetry(n) && isOpen()) {
+ park(Net.POLLIN);
+ n = tryReceive(b, off, len, isPeek);
+ }
+ }
+ assert n > 0;
+ assert sender != null;
+ if (p.getAddress() == null || !p.getAddress().equals(sender.getAddress()))
+ p.setAddress(sender.getAddress());
+ if (p.getPort() != sender.getPort())
+ p.setPort(sender.getPort());
+ p.setLength(n);
+ } catch (IOException e) {
+ // #### reset packet offset and length! ??
+ throw e;
+ } finally {
+ endRead(n > 0);
+ assert IOStatus.check(n);
+ }
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ @Override
+ protected void connect(InetAddress address, int port) throws SocketException {
+ readLock.lock();
+ try {
+ writeLock.lock();
+ try {
+ synchronized (stateLock) {
+ ensureOpen();
+ if (state == ST_CONNECTED) {
+ // #### already connected? throw? or connect to new remote
+ }
+
+ int n = Net.connect(family, fd, address, port);
+ if (n <= 0)
+ throw new InternalError("should not reach here");
+
+ remoteAddress = new InetSocketAddress(address, port);
+ state = ST_CONNECTED;
+
+ // refresh local address
+ localPort = Net.localAddress(fd).getPort();
+
+ // flush any packets already received.
+ try {
+ byte[] ba = new byte[1];
+ configureNonBlocking();
+ while (tryReceive(ba, 0, 1, false) > 0) { }
+ } finally {
+ configureBlocking();
+ }
+ }
+ } catch (SocketException e) {
+ throw e;
+ } catch (IOException e) {
+ throw new SocketException(e.getMessage());
+ } finally {
+ writeLock.unlock();
+ }
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ @Override
+ protected void disconnect() {
+ readLock.lock();
+ try {
+ writeLock.lock();
+ try {
+ synchronized (stateLock) {
+ if (!isOpen() || (state != ST_CONNECTED))
+ return;
+
+ try {
+ disconnect0(fd, family == INET6);
+
+ // no longer connected
+ remoteAddress = null;
+ state = ST_UNCONNECTED;
+
+ // check whether rebind is needed
+ InetSocketAddress isa = Net.localAddress(fd);
+ if (isa.getPort() == 0) {
+ // On Linux, if bound to ephemeral port,
+ // disconnect does not preserve that port.
+ // In this case, try to rebind to the previous port.
+ int port = localPort;
+ Net.bind(family, fd, isa.getAddress(), port);
+ isa = Net.localAddress(fd); // refresh address
+ assert isa.getPort() == port;
+ }
+
+ // refresh local port
+ localPort = isa.getPort();
+ } catch (IOException e) {
+ sneakyThrow(e);
+ throw new InternalError("should not reach here");
+ }
+ }
+ } finally {
+ writeLock.unlock();
+ }
+ } finally {
+ readLock.unlock();
+ }
+ }
+
+ /**
+ * Closes the socket if there are no I/O operations in progress.
+ */
+ private boolean tryClose() throws IOException {
+ assert Thread.holdsLock(stateLock) && state == ST_CLOSING;
+ if (readerThread == 0 && writerThread == 0) {
+ try {
+ closer.run();
+ } catch (UncheckedIOException ioe) {
+ throw ioe.getCause();
+ } finally {
+ state = ST_CLOSED;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Invokes tryClose to attempt to close the socket.
+ *
+ * This method is used for deferred closing by I/O operations.
+ */
+ private void tryFinishClose() {
+ try {
+ tryClose();
+ } catch (IOException ignore) { }
+ }
+
+ /**
+ * Closes the socket. If there are I/O operations in progress then the
+ * socket is pre-closed and the threads are signalled. The socket will be
+ * closed when the last I/O operation aborts.
+ */
+ @Override
+ protected void close() {
+ synchronized (stateLock) {
+ int state = this.state;
+ if (state >= ST_CLOSING)
+ return;
+ if (state == ST_NEW) {
+ this.state = ST_CLOSED;
+ return;
+ }
+ this.state = ST_CLOSING;
+
+ // Attempt to close the socket. If there are I/O operations in
+ // progress then the socket is pre-closed and the thread(s)
+ // signalled. The last thread will close the file descriptor.
+ try {
+ if (!tryClose()) {
+ nd.preClose(fd);
+ long reader = readerThread;
+ if (reader != 0)
+ NativeThread.signal(reader);
+ long writer = writerThread;
+ if (writer != 0)
+ NativeThread.signal(writer);
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e); // Ugh!
+ }
+ }
+ }
+
+ private static final Set<SocketOption<?>> socketOptions = socketOptions();
+
+ private static Set<SocketOption<?>> socketOptions() {
+ HashSet<SocketOption<?>> options = new HashSet<>();
+ options.add(StandardSocketOptions.SO_SNDBUF);
+ options.add(StandardSocketOptions.SO_RCVBUF);
+ options.add(StandardSocketOptions.SO_REUSEADDR);
+ options.add(StandardSocketOptions.IP_TOS);
+ if (Net.isReusePortAvailable())
+ options.add(StandardSocketOptions.SO_REUSEPORT);
+ options.addAll(ExtendedSocketOptions.datagramSocketOptions());
+ return Collections.unmodifiableSet(options);
+ }
+
+ @Override
+ protected Set<SocketOption<?>> supportedOptions() {
+ return socketOptions;
+ }
+
+ @Override
+ protected <T> void setOption(SocketOption<T> opt, T value) throws IOException {
+ if (!supportedOptions().contains(opt))
+ throw new UnsupportedOperationException("'" + opt + "' not supported");
+ if (!opt.type().isInstance(value))
+ throw new IllegalArgumentException("Invalid value '" + value + "'");
+ synchronized (stateLock) {
+ ensureOpen();
+ if (opt == StandardSocketOptions.IP_TOS) {
+ // maps to IP_TOS or IPV6_TCLASS
+ Net.setSocketOption(fd, family(), opt, value);
+ } else if (opt == StandardSocketOptions.SO_REUSEADDR) {
+ setOption(SocketOptions.SO_REUSEADDR, value);
+ } else if (opt == StandardSocketOptions.SO_REUSEPORT) {
+ setOption(SocketOptions.SO_REUSEPORT, value);
+ } else {
+ // option does not need special handling
+ Net.setSocketOption(fd, opt, value);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected <T> T getOption(SocketOption<T> opt) throws IOException {
+ if (!supportedOptions().contains(opt))
+ throw new UnsupportedOperationException("'" + opt + "' not supported");
+ synchronized (stateLock) {
+ ensureOpen();
+ if (opt == StandardSocketOptions.IP_TOS) {
+ return (T) Net.getSocketOption(fd, family(), opt);
+ } else if (opt == StandardSocketOptions.SO_REUSEADDR) {
+ return (T) getOption(SocketOptions.SO_REUSEADDR);
+ } else if (opt == StandardSocketOptions.SO_REUSEPORT) {
+ return (T) getOption(SocketOptions.SO_REUSEPORT);
+ } else {
+ // option does not need special handling
+ return (T) Net.getSocketOption(fd, opt);
+ }
+ }
+ }
+
+ private static boolean booleanValue(Object value, String desc)
+ throws SocketException
+ {
+ if (!(value instanceof Boolean))
+ throw new SocketException("Bad value for " + desc);
+ return (boolean) value;
+ }
+
+ private static int intValue(Object value, String desc) throws SocketException {
+ if (!(value instanceof Integer))
+ throw new SocketException("Bad value for " + desc);
+ return (int) value;
+ }
+
+ @Override
+ public void setOption(int opt, Object value) throws SocketException {
+ synchronized (stateLock) {
+ ensureOpen();
+ try {
+ switch (opt) {
+ case SO_TIMEOUT: {
+ int i = intValue(value, "SO_TIMEOUT");
+ if (i < 0)
+ throw new IllegalArgumentException("timeout < 0");
+ timeout = i;
+ break;
+ }
+ case IP_TOS: {
+ int i = intValue(value, "IP_TOS");
+ Net.setSocketOption(fd, family, StandardSocketOptions.IP_TOS, i);
+ break;
+ }
+ case SO_REUSEADDR: {
+ boolean b = booleanValue(value, "SO_REUSEADDR");
+ Net.setSocketOption(fd, StandardSocketOptions.SO_REUSEADDR, b);
+ break;
+ }
+ case SO_BROADCAST: {
+ boolean b = booleanValue(value, "SO_BROADCAST");
+ Net.setSocketOption(fd, StandardSocketOptions.SO_BROADCAST, b);
+ break;
+ }
+ case SO_BINDADDR: {
+ throw new SocketException("Cannot re-bind Socket");
+ }
+ case SO_RCVBUF: {
+ int i = intValue(value, "SO_RCVBUF");
+ if (i <= 0)
+ throw new SocketException("SO_RCVBUF <= 0");
+ Net.setSocketOption(fd, StandardSocketOptions.SO_RCVBUF, i);
+ break;
+ }
+ case SO_SNDBUF: {
+ int i = intValue(value, "SO_SNDBUF");
+ if (i <= 0)
+ throw new SocketException("SO_SNDBUF <= 0");
+ Net.setSocketOption(fd, StandardSocketOptions.SO_SNDBUF, i);
+ break;
+ }
+ case SO_REUSEPORT: {
+ if (!Net.isReusePortAvailable())
+ throw new UnsupportedOperationException("SO_REUSEPORT not supported");
+ boolean b = booleanValue(value, "SO_REUSEPORT");
+ Net.setSocketOption(fd, StandardSocketOptions.SO_REUSEPORT, b);
+ break;
+ }
+ default:
+ throw new SocketException("unknown option: " + opt);
+ }
+ } catch (SocketException e) {
+ throw e;
+ } catch (IllegalArgumentException | IOException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+ }
+
+ @Override
+ public Object getOption(int opt) throws SocketException {
+ synchronized (stateLock) {
+ ensureOpen();
+ try {
+ switch (opt) {
+ case SO_TIMEOUT:
+ return timeout;
+ case IP_TOS:
+ return Net.getSocketOption(fd, family(), StandardSocketOptions.IP_TOS);
+ case SO_BINDADDR:
+ return Net.localAddress(fd).getAddress();
+ case SO_RCVBUF:
+ return Net.getSocketOption(fd, StandardSocketOptions.SO_RCVBUF);
+ case SO_SNDBUF:
+ return Net.getSocketOption(fd, StandardSocketOptions.SO_SNDBUF);
+ case SO_REUSEADDR:
+ return Net.getSocketOption(fd, StandardSocketOptions.SO_REUSEADDR);
+ case SO_BROADCAST:
+ return Net.getSocketOption(fd, StandardSocketOptions.SO_BROADCAST);
+ case SO_REUSEPORT:
+ if (!Net.isReusePortAvailable())
+ throw new UnsupportedOperationException("SO_REUSEPORT not supported");
+ return Net.getSocketOption(fd, StandardSocketOptions.SO_REUSEPORT);
+ default:
+ throw new SocketException("Unknown option " + opt);
+ }
+ } catch (SocketException e) {
+ throw e;
+ } catch (IllegalArgumentException | IOException e) {
+ throw new SocketException(e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * A task that closes a DatagramSocketImpl's file descriptor. The task is
+ * run when the NioDatagramSocketImpl is explicitly closed or when the
+ * NioDatagramSocketImpl becomes phantom reachable.
+ */
+ private static class FileDescriptorCloser implements Runnable {
+ private static final VarHandle CLOSED;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ CLOSED = l.findVarHandle(FileDescriptorCloser.class,
+ "closed",
+ boolean.class);
+ } catch (Exception e) {
+ throw new InternalError(e);
+ }
+ }
+ private volatile boolean closed; // accessed through VarHandle
+ private final FileDescriptor fd;
+
+ FileDescriptorCloser(FileDescriptor fd) { this.fd = fd; }
+
+ static FileDescriptorCloser create(NioDatagramSocketImpl impl) {
+ assert Thread.holdsLock(impl.stateLock);
+ var closer = new FileDescriptorCloser(impl.fd);
+ CleanerFactory.cleaner().register(impl, closer);
+ return closer;
+ }
+
+ @Override
+ public void run() {
+ if (CLOSED.compareAndSet(this, false, true)) {
+ try {
+ nd.close(fd);
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ } finally {
+ ResourceManager.afterUdpClose();
+ }
+ }
+ }
+ }
+
+ @Deprecated
+ @Override
+ protected void setTTL(byte ttl) {
+ throw new InternalError("should not reach here");
+ }
+
+ @Deprecated
+ @Override
+ protected byte getTTL() {
+ throw new InternalError("should not reach here");
+ }
+
+ @Override
+ protected void setTimeToLive(int ttl) {
+ throw new InternalError("should not reach here");
+ }
+
+ @Override
+ protected int getTimeToLive() {
+ throw new InternalError("should not reach here");
+ }
+
+ @Override
+ protected void join(InetAddress inetaddr) {
+ throw new InternalError("should not reach here");
+ }
+
+ @Override
+ protected void leave(InetAddress inetaddr) {
+ throw new InternalError("should not reach here");
+ }
+
+ @Override
+ protected void joinGroup(SocketAddress mcastaddr, NetworkInterface netIf) {
+ throw new InternalError("should not reach here");
+ }
+
+ @Override
+ protected void leaveGroup(SocketAddress mcastaddr, NetworkInterface netIf) {
+ throw new InternalError("should not reach here");
+ }
+
+ /** Set if the native connect() call is not to be used */
+ private static final boolean connectDisabled =
+ GetPropertyAction.privilegedGetProperty("os.name").contains("OS X");
+
+ @Override
+ public boolean nativeConnectDisabled() {
+ return connectDisabled;
+ }
+
+ // -- Native methods --
+
+ private static native void initIDs();
+
+ private native int receive0(FileDescriptor fd,
+ long address,
+ int len,
+ boolean isPeek,
+ InetAddress cachedSenderAddress,
+ int cachedSenderPort)
+ throws IOException;
+
+ private static native int send0(boolean preferIPv6,
+ FileDescriptor fd,
+ long address,
+ int len,
+ InetAddress addr,
+ int port)
+ throws IOException;
+
+ private static native void disconnect0(FileDescriptor fd,
+ boolean isIPv6)
+ throws IOException;
+
+ static {
+ IOUtil.load();
+ initIDs();
+ }
+}
--- a/src/java.base/unix/classes/java/net/DefaultDatagramSocketImplFactory.java Thu Oct 17 20:54:25 2019 +0100
+++ b/src/java.base/unix/classes/java/net/DefaultDatagramSocketImplFactory.java Thu Oct 17 21:15:33 2019 +0100
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,6 +25,10 @@
package java.net;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import sun.net.NetProperties;
+import sun.nio.ch.NioDatagramSocketImpl;
import sun.security.action.GetPropertyAction;
/**
@@ -52,13 +56,30 @@
}
}
+ private static final boolean USE_PLAINDATAGRAMSOCKETIMPL = usePlainDatagramSocketImpl();
+
+ private static boolean usePlainDatagramSocketImpl() {
+ PrivilegedAction<String> pa = () -> NetProperties.get("jdk.net.usePlainDatagramSocketImpl");
+ String s = AccessController.doPrivileged(pa);
+ return (s != null) && !s.equalsIgnoreCase("false");
+ }
+
+ /** Creates an instance of platform's DatagramSocketImpl */
+ static DatagramSocketImpl createPlatformSocketDatagramImpl(boolean isMulticast) {
+ if (USE_PLAINDATAGRAMSOCKETIMPL || isMulticast) {
+ return new PlainDatagramSocketImpl(isMulticast);
+ } else {
+ return new NioDatagramSocketImpl();
+ }
+ }
+
/**
* Creates a new <code>DatagramSocketImpl</code> instance.
*
- * @param isMulticast true if this impl if for a MutlicastSocket
+ * @param isMulticast true if this impl is for a MutlicastSocket
* @return a new instance of a <code>DatagramSocketImpl</code>.
*/
- static DatagramSocketImpl createDatagramSocketImpl(boolean isMulticast /*unused on unix*/)
+ static DatagramSocketImpl createDatagramSocketImpl(boolean isMulticast)
throws SocketException {
if (prefixImplClass != null) {
try {
@@ -69,7 +90,7 @@
throw new SocketException("can't instantiate DatagramSocketImpl");
}
} else {
- return new java.net.PlainDatagramSocketImpl();
+ return createPlatformSocketDatagramImpl(isMulticast);
}
}
}
--- a/src/java.base/unix/classes/java/net/PlainDatagramSocketImpl.java Thu Oct 17 20:54:25 2019 +0100
+++ b/src/java.base/unix/classes/java/net/PlainDatagramSocketImpl.java Thu Oct 17 21:15:33 2019 +0100
@@ -37,6 +37,10 @@
class PlainDatagramSocketImpl extends AbstractPlainDatagramSocketImpl
{
+ PlainDatagramSocketImpl(boolean isMulticast) {
+ super(isMulticast);
+ }
+
static {
init();
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/unix/native/libnio/ch/NioDatagramSocketImpl.c Thu Oct 17 21:15:33 2019 +0100
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+#include "jni.h"
+#include "jni_util.h"
+#include "jvm.h"
+#include "jlong.h"
+
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#if defined(__linux__) || defined(_ALLBSD_SOURCE)
+#include <netinet/in.h>
+#endif
+
+#include "net_util.h"
+#include "net_util_md.h"
+#include "nio.h"
+#include "nio_util.h"
+
+#include "sun_nio_ch_NioDatagramSocketImpl.h"
+
+static jfieldID dsi_senderID; /* sender in sun.nio.ch.NioDatagramSocketImpl */
+static jclass isa_class; /* java.net.InetSocketAddress */
+static jmethodID isa_ctorID; /* InetSocketAddress(InetAddress, int) */
+
+JNIEXPORT void JNICALL
+Java_sun_nio_ch_NioDatagramSocketImpl_initIDs(JNIEnv *env, jclass clazz)
+{
+ clazz = (*env)->FindClass(env, "java/net/InetSocketAddress");
+ CHECK_NULL(clazz);
+ isa_class = (*env)->NewGlobalRef(env, clazz);
+ if (isa_class == NULL) {
+ JNU_ThrowOutOfMemoryError(env, NULL);
+ return;
+ }
+ isa_ctorID = (*env)->GetMethodID(env, clazz, "<init>", "(Ljava/net/InetAddress;I)V");
+ CHECK_NULL(isa_ctorID);
+
+ clazz = (*env)->FindClass(env, "sun/nio/ch/NioDatagramSocketImpl");
+ CHECK_NULL(clazz);
+ dsi_senderID = (*env)->GetFieldID(env, clazz, "sender", "Ljava/net/InetSocketAddress;");
+ CHECK_NULL(dsi_senderID);
+}
+
+JNIEXPORT jint JNICALL
+Java_sun_nio_ch_NioDatagramSocketImpl_send0(JNIEnv *env,
+ jclass clazz,
+ jboolean preferIPv6,
+ jobject fdo,
+ jlong address,
+ jint len,
+ jobject destAddress,
+ jint destPort)
+{
+ jint fd = fdval(env, fdo);
+ void *buf = (void *)jlong_to_ptr(address);
+ SOCKETADDRESS sa;
+ struct sockaddr *saP = NULL;
+ int sa_len = 0;
+ jint n = 0;
+
+ if (destAddress != NULL) { // not connected
+ if (NET_InetAddressToSockaddr(env, destAddress, destPort, &sa,
+ &sa_len, preferIPv6) != 0) {
+ return IOS_THROWN;
+ }
+ saP = &sa.sa;
+ }
+
+ n = sendto(fd, buf, len, 0, saP, sa_len);
+ if (n < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return IOS_UNAVAILABLE;
+ }
+ if (errno == EINTR) {
+ return IOS_INTERRUPTED;
+ }
+ if (errno == ECONNREFUSED) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException", 0);
+ return IOS_THROWN;
+ }
+ return handleSocketError(env, errno);
+ }
+ return n;
+}
+
+JNIEXPORT jint JNICALL
+Java_sun_nio_ch_NioDatagramSocketImpl_receive0(JNIEnv *env,
+ jobject this,
+ jobject fdo,
+ jlong address,
+ jint len,
+ jboolean isPeek,
+ jobject cachedSenderAddress,
+ jint cachedSenderPort)
+{
+ jint fd = fdval(env, fdo);
+ void *buf = (void *)jlong_to_ptr(address);
+ SOCKETADDRESS sa;
+ socklen_t sa_len = sizeof(SOCKETADDRESS);
+ jboolean retry = JNI_FALSE;
+ int flags = 0;
+ jint n = 0;
+ jobject senderAddr;
+
+ if (isPeek == JNI_TRUE) {
+ flags = MSG_PEEK;
+ }
+
+ n = recvfrom(fd, buf, len, flags, &sa.sa, &sa_len);
+ if (n < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return IOS_UNAVAILABLE;
+ } else if (errno == EINTR) {
+ return IOS_INTERRUPTED;
+ } else if (errno == ECONNREFUSED) {
+ JNU_ThrowByName(env, JNU_JAVANETPKG "PortUnreachableException",
+ "ICMP Port Unreachable");
+ return IOS_THROWN;
+ } else {
+ return handleSocketError(env, errno);
+ }
+ }
+
+ // If the cached address does not match, then create a new one.
+ if (cachedSenderAddress == NULL ||
+ !NET_SockaddrEqualsInetAddress(env, &sa, cachedSenderAddress) ||
+ cachedSenderPort != NET_GetPortFromSockaddr(&sa)) {
+
+ jobject isa = NULL;
+ int port = 0;
+ jobject ia = NET_SockaddrToInetAddress(env, &sa, &port);
+ if (ia != NULL) {
+ isa = (*env)->NewObject(env, isa_class, isa_ctorID, ia, port);
+ }
+ CHECK_NULL_RETURN(isa, IOS_THROWN);
+
+ (*env)->SetObjectField(env, this, dsi_senderID, isa);
+ }
+
+ return n;
+}
+
+JNIEXPORT void JNICALL
+Java_sun_nio_ch_NioDatagramSocketImpl_disconnect0(JNIEnv *env,
+ jobject clazz,
+ jobject fdo,
+ jboolean isIPv6)
+{
+ jint fd = fdval(env, fdo);
+ int rv;
+
+#if defined(__solaris__)
+ rv = connect(fd, 0, 0);
+#else
+ SOCKETADDRESS sa;
+ socklen_t len = isIPv6 ? sizeof(struct sockaddr_in6) :
+ sizeof(struct sockaddr_in);
+
+ memset(&sa, 0, sizeof(sa));
+#if defined(_ALLBSD_SOURCE)
+ sa.sa.sa_family = isIPv6 ? AF_INET6 : AF_INET;
+#else
+ sa.sa.sa_family = AF_UNSPEC;
+#endif
+
+ rv = connect(fd, &sa.sa, len);
+
+#if defined(_ALLBSD_SOURCE)
+ if (rv < 0 && errno == EADDRNOTAVAIL)
+ rv = errno = 0;
+#elif defined(_AIX)
+ /* See W. Richard Stevens, "UNIX Network Programming, Volume 1", p. 254:
+ * 'Setting the address family to AF_UNSPEC might return EAFNOSUPPORT
+ * but that is acceptable.
+ */
+ if (rv < 0 && errno == EAFNOSUPPORT)
+ rv = errno = 0;
+#endif // defined(_ALLBSD_SOURCE) || defined(_AIX)
+
+#endif // defined(__solaris__)
+
+ if (rv < 0)
+ handleSocketError(env, errno);
+}
--- a/test/jdk/java/net/DatagramSocket/B6411513.java Thu Oct 17 20:54:25 2019 +0100
+++ b/test/jdk/java/net/DatagramSocket/B6411513.java Thu Oct 17 21:15:33 2019 +0100
@@ -28,7 +28,10 @@
*/
import java.net.*;
+import java.nio.ByteBuffer;
+import java.nio.channels.DatagramChannel;
import java.util.*;
+import static java.lang.System.out;
public class B6411513 {
@@ -47,7 +50,7 @@
// out IPv6 address here. The test should be revisited
// later when aforementioned bug gets fixed.
if (addr instanceof Inet4Address) {
- System.out.printf("%s : %s\n", nic.getName(), addr);
+ out.printf("%s : %s\n", nic.getName(), addr);
testConnectedUDP(addr);
}
}
@@ -65,20 +68,23 @@
try {
DatagramSocket s = new DatagramSocket(0, addr);
DatagramSocket ss = new DatagramSocket(0, addr);
- System.out.print("\tconnect...");
+ out.println("localaddress: " + s.getLocalSocketAddress());
+ out.println("\tconnect...");
s.connect(ss.getLocalAddress(), ss.getLocalPort());
- System.out.print("disconnect...");
+ out.println("localaddress: " + s.getLocalSocketAddress());
+ out.println("disconnect...");
s.disconnect();
+ out.println("localaddress: " + s.getLocalSocketAddress());
byte[] data = { 0, 1, 2 };
DatagramPacket p = new DatagramPacket(data, data.length,
s.getLocalAddress(), s.getLocalPort());
s.setSoTimeout( 10000 );
- System.out.print("send...");
+ out.print("send...");
s.send( p );
- System.out.print("recv...");
+ out.print("recv...");
s.receive( p );
- System.out.println("OK");
+ out.println("OK");
ss.close();
s.close();
@@ -87,4 +93,29 @@
throw e;
}
}
+
+ // Tests with DatagramChannel
+// private static void testConnectedUDPNIO(InetAddress addr) throws Exception {
+// DatagramChannel s = DatagramChannel.open();
+// s.bind(new InetSocketAddress(addr, 0));
+// DatagramChannel ss = DatagramChannel.open();
+// ss.bind(new InetSocketAddress(addr, 0));
+// out.println("localaddress: " + s.getLocalAddress());
+// out.println("\tconnect...");
+// s.connect(ss.getLocalAddress());
+// out.println("localaddress: " + s.getLocalAddress());
+// out.println("disconnect...");
+// s.disconnect();
+// out.println("localaddress: " + s.getLocalAddress());
+//
+// byte[] data = {0, 1, 2};
+// out.print("send...");
+// s.send(ByteBuffer.wrap(data), s.getLocalAddress());
+// out.print("recv...");
+// s.receive(ByteBuffer.allocate(100));
+// out.println("OK");
+//
+// ss.close();
+// s.close();
+// }
}