--- /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();
+ }
+}