8245194: Unix domain socket channel implementation

Reviewed-by: erikj, dfuchs, alanb, chegar
This commit is contained in:
Michael McMahon 2020-10-28 17:26:26 +00:00
parent 8bde2f4e3d
commit 6bb7e45e8e
73 changed files with 5434 additions and 1116 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 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
@ -31,8 +31,14 @@ package sun.nio.ch;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnixDomainSocketAddress;
import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.channels.spi.*;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
@ -55,16 +61,17 @@ class PipeImpl
private static final Random RANDOM_NUMBER_GENERATOR = new SecureRandom();
// Source and sink channels
private SourceChannel source;
private SinkChannel sink;
private final SourceChannel source;
private final SinkChannel sink;
private class Initializer
implements PrivilegedExceptionAction<Void>
{
private final SelectorProvider sp;
private IOException ioe = null;
private IOException ioe;
SourceChannelImpl source;
SinkChannelImpl sink;
private Initializer(SelectorProvider sp) {
this.sp = sp;
@ -103,23 +110,20 @@ class PipeImpl
ServerSocketChannel ssc = null;
SocketChannel sc1 = null;
SocketChannel sc2 = null;
// Loopback address
SocketAddress sa = null;
try {
// Create secret with a backing array.
ByteBuffer secret = ByteBuffer.allocate(NUM_SECRET_BYTES);
ByteBuffer bb = ByteBuffer.allocate(NUM_SECRET_BYTES);
// Loopback address
InetAddress lb = InetAddress.getLoopbackAddress();
assert(lb.isLoopbackAddress());
InetSocketAddress sa = null;
for(;;) {
// Bind ServerSocketChannel to a port on the loopback
// address
if (ssc == null || !ssc.isOpen()) {
ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(lb, 0));
sa = new InetSocketAddress(lb, ssc.socket().getLocalPort());
ssc = createListener();
sa = ssc.getLocalAddress();
}
// Establish connection (assume connections are eagerly
@ -160,18 +164,43 @@ class PipeImpl
try {
if (ssc != null)
ssc.close();
if (sa instanceof UnixDomainSocketAddress) {
Path path = ((UnixDomainSocketAddress) sa).getPath();
Files.deleteIfExists(path);
}
} catch (IOException e2) {}
}
}
}
}
PipeImpl(final SelectorProvider sp) throws IOException {
/**
* Creates a Pipe implementation that supports buffering.
*/
PipeImpl(SelectorProvider sp) throws IOException {
this(sp, true);
}
/**
* Creates Pipe implementation that supports optionally buffering.
*
* @implNote The pipe uses Unix domain sockets where possible. It uses a
* loopback connection on older editions of Windows. When buffering is
* disabled then it sets TCP_NODELAY on the sink channel.
*/
PipeImpl(SelectorProvider sp, boolean buffering) throws IOException {
Initializer initializer = new Initializer(sp);
try {
AccessController.doPrivileged(new Initializer(sp));
} catch (PrivilegedActionException x) {
throw (IOException)x.getCause();
AccessController.doPrivileged(initializer);
SinkChannelImpl sink = initializer.sink;
if (sink.isNetSocket() && !buffering) {
sink.setOption(StandardSocketOptions.TCP_NODELAY, true);
}
} catch (PrivilegedActionException pae) {
throw (IOException) pae.getCause();
}
this.source = initializer.source;
this.sink = initializer.sink;
}
public SourceChannel source() {
@ -182,4 +211,25 @@ class PipeImpl
return sink;
}
private static volatile boolean noUnixDomainSockets;
private static ServerSocketChannel createListener() throws IOException {
ServerSocketChannel listener = null;
if (!noUnixDomainSockets) {
try {
listener = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
return listener.bind(null);
} catch (UnsupportedOperationException | IOException e) {
// IOException is most likely to be caused by the temporary directory
// name being too long. Possibly should log this.
noUnixDomainSockets = true;
if (listener != null)
listener.close();
}
}
listener = ServerSocketChannel.open();
InetAddress lb = InetAddress.getLoopbackAddress();
listener.bind(new InetSocketAddress(lb, 0));
return listener;
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 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
@ -30,6 +30,7 @@ package sun.nio.ch;
import java.io.IOException;
import java.io.FileDescriptor;
import java.net.SocketOption;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.channels.spi.*;
@ -44,19 +45,27 @@ class SinkChannelImpl
implements SelChImpl
{
// The SocketChannel assoicated with this pipe
final SocketChannel sc;
private final SocketChannelImpl sc;
public FileDescriptor getFD() {
return ((SocketChannelImpl)sc).getFD();
return sc.getFD();
}
public int getFDVal() {
return ((SocketChannelImpl)sc).getFDVal();
return sc.getFDVal();
}
SinkChannelImpl(SelectorProvider sp, SocketChannel sc) {
super(sp);
this.sc = sc;
this.sc = (SocketChannelImpl) sc;
}
boolean isNetSocket() {
return sc.isNetSocket();
}
<T> void setOption(SocketOption<T> name, T value) throws IOException {
sc.setOption(name, value);
}
protected void implCloseSelectableChannel() throws IOException {

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2002, 2018, 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.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.AccessController;
import java.security.PrivilegedAction;
import sun.net.NetProperties;
import jdk.internal.util.StaticProperty;
class UnixDomainSocketsUtil {
private UnixDomainSocketsUtil() { }
static Charset getCharset() {
return StandardCharsets.UTF_8;
}
/**
* Return the temp directory for storing automatically bound
* server sockets.
*
* On Windows we search the following directories in sequence:
*
* 1. ${jdk.net.unixdomain.tmpdir} if set as system property
* 2. ${jdk.net.unixdomain.tmpdir} if set as net property
* 3. %TEMP%
* 4. ${java.io.tmpdir}
*/
static String getTempDir() {
PrivilegedAction<String> action = () -> {
String s = NetProperties.get("jdk.net.unixdomain.tmpdir");
if (s != null) {
return s;
}
String temp = System.getenv("TEMP");
if (temp != null) {
return temp;
}
return StaticProperty.javaIoTmpDir();
};
return AccessController.doPrivileged(action);
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 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
@ -30,6 +30,7 @@ import java.nio.channels.ClosedSelectorException;
import java.nio.channels.Pipe;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SelectableChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.ArrayDeque;
import java.util.ArrayList;
@ -139,14 +140,9 @@ class WindowsSelectorImpl extends SelectorImpl {
WindowsSelectorImpl(SelectorProvider sp) throws IOException {
super(sp);
pollWrapper = new PollArrayWrapper(INIT_CAP);
wakeupPipe = Pipe.open();
wakeupPipe = new PipeImpl(sp, false);
wakeupSourceFd = ((SelChImpl)wakeupPipe.source()).getFDVal();
// Disable the Nagle algorithm so that the wakeup is more immediate
SinkChannelImpl sink = (SinkChannelImpl)wakeupPipe.sink();
(sink.sc).socket().setTcpNoDelay(true);
wakeupSinkFd = ((SelChImpl)sink).getFDVal();
wakeupSinkFd = ((SelChImpl)wakeupPipe.sink()).getFDVal();
pollWrapper.addWakeupSocket(wakeupSourceFd, 0);
}
@ -413,19 +409,19 @@ class WindowsSelectorImpl extends SelectorImpl {
// processDeregisterQueue.
if (me == null)
continue;
SelectionKeyImpl sk = me.ski;
SelectionKeyImpl ski = me.ski;
// The descriptor may be in the exceptfds set because there is
// OOB data queued to the socket. If there is OOB data then it
// is discarded and the key is not added to the selected set.
if (isExceptFds &&
(sk.channel() instanceof SocketChannelImpl) &&
discardUrgentData(desc))
{
SelectableChannel sc = ski.channel();
if (isExceptFds && (sc instanceof SocketChannelImpl)
&& ((SocketChannelImpl) sc).isNetSocket()
&& discardUrgentData(desc)) {
continue;
}
int updated = processReadyEvents(rOps, sk, action);
int updated = processReadyEvents(rOps, ski, action);
if (updated > 0 && me.updateCount != updateCount) {
me.updateCount = updateCount;
numKeysUpdated++;

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 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
@ -28,6 +28,7 @@ package sun.nio.fs;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.net.URI;
import java.util.concurrent.ExecutorService;
import java.io.*;
@ -622,4 +623,12 @@ class WindowsFileSystemProvider
String target = WindowsLinkSupport.readLink(link);
return WindowsPath.createFromNormalizedPath(fs, target);
}
@Override
public byte[] getSunPathForSocketFile(Path obj) {
WindowsPath file = WindowsPath.toWindowsPath(obj);
String s = file.toString();
return s.getBytes(StandardCharsets.UTF_8);
}
}

View file

@ -0,0 +1,19 @@
#
# Default directory where automatically bound Unix domain server
# sockets are stored. Sockets are automatically bound when bound
# with a null address.
#
# The search order for the directory on Windows is:
#
# 1. System property "jdk.net.unixdomain.tmpdir"
#
# 2. Networking property "jdk.net.unixdomain.tmpdir" specified
# in this file (not set by default)
#
# 3. The TEMP environment variable (the effective default)
#
# 4. The java.io.tmpdir system property
#
#jdk.net.unixdomain.tmpdir=
#

View file

@ -0,0 +1,196 @@
/*
* 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. 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 <windows.h>
#include <winsock2.h>
#include "jni.h"
#include "jni_util.h"
#include "jvm.h"
#include "jlong.h"
#include "nio.h"
#include "nio_util.h"
#include "net_util.h"
#include "java_net_InetAddress.h"
#include "sun_nio_ch_Net.h"
#include "sun_nio_ch_PollArrayWrapper.h"
jbyteArray sockaddrToUnixAddressBytes(JNIEnv *env, struct sockaddr_un *sa, socklen_t len)
{
if (sa->sun_family == AF_UNIX) {
int namelen = (int)strlen(sa->sun_path);
jbyteArray name = (*env)->NewByteArray(env, namelen);
if (name != NULL) {
(*env)->SetByteArrayRegion(env, name, 0, namelen, (jbyte*)sa->sun_path);
if ((*env)->ExceptionOccurred(env)) {
return NULL;
}
}
return name;
}
return NULL;
}
jint unixSocketAddressToSockaddr(JNIEnv *env, jbyteArray addr, struct sockaddr_un *sa, int *len)
{
memset(sa, 0, sizeof(struct sockaddr_un));
sa->sun_family = AF_UNIX;
if (addr == 0L) {
/* Do explicit bind on Windows */
*len = (int)(offsetof(struct sockaddr_un, sun_path));
return 0;
}
int ret;
jboolean isCopy;
char *pname = (*env)->GetByteArrayElements(env, addr, &isCopy);
if (pname == NULL) {
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Unix domain path not present");
return -1;
}
size_t name_len = (size_t)(*env)->GetArrayLength(env, addr);
if (name_len > MAX_UNIX_DOMAIN_PATH_LEN) {
JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Unix domain path too long");
ret = -1;
} else {
strncpy(sa->sun_path, pname, name_len);
*len = (int)(offsetof(struct sockaddr_un, sun_path) + name_len);
ret = 0;
}
(*env)->ReleaseByteArrayElements(env, addr, pname, JNI_ABORT);
return ret;
}
JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_UnixDomainSockets_socketSupported(JNIEnv *env, jclass cl)
{
SOCKET s = socket(PF_UNIX, SOCK_STREAM, 0);
if (s == INVALID_SOCKET) {
return JNI_FALSE;
}
closesocket(s);
return JNI_TRUE;
}
JNIEXPORT jint JNICALL
Java_sun_nio_ch_UnixDomainSockets_socket0(JNIEnv *env, jclass cl)
{
SOCKET s = socket(PF_UNIX, SOCK_STREAM, 0);
if (s == INVALID_SOCKET) {
return handleSocketError(env, WSAGetLastError());
}
SetHandleInformation((HANDLE)s, HANDLE_FLAG_INHERIT, 0);
return (int)s;
}
/**
* Windows does not support auto bind. So, the windows version of unixSocketAddressToSockaddr
* looks out for a null 'uaddr' and handles it specially
*/
JNIEXPORT void JNICALL
Java_sun_nio_ch_UnixDomainSockets_bind0(JNIEnv *env, jclass clazz, jobject fdo, jbyteArray addr)
{
struct sockaddr_un sa;
int sa_len = 0;
int rv = 0;
if (unixSocketAddressToSockaddr(env, addr, &sa, &sa_len) != 0)
return;
rv = bind(fdval(env, fdo), (struct sockaddr *)&sa, sa_len);
if (rv == SOCKET_ERROR) {
int err = WSAGetLastError();
NET_ThrowNew(env, err, "bind");
}
}
JNIEXPORT jint JNICALL
Java_sun_nio_ch_UnixDomainSockets_connect0(JNIEnv *env, jclass clazz, jobject fdo, jbyteArray addr)
{
struct sockaddr_un sa;
int sa_len = 0;
int rv;
if (unixSocketAddressToSockaddr(env, addr, &sa, &sa_len) != 0) {
return IOS_THROWN;
}
rv = connect(fdval(env, fdo), (const struct sockaddr *)&sa, sa_len);
if (rv != 0) {
int err = WSAGetLastError();
if (err == WSAEINPROGRESS || err == WSAEWOULDBLOCK) {
return IOS_UNAVAILABLE;
}
NET_ThrowNew(env, err, "connect");
return IOS_THROWN;
}
return 1;
}
JNIEXPORT jint JNICALL
Java_sun_nio_ch_UnixDomainSockets_accept0(JNIEnv *env, jclass clazz, jobject fdo, jobject newfdo,
jobjectArray array)
{
jint fd = fdval(env, fdo);
jint newfd;
struct sockaddr_un sa;
socklen_t sa_len = sizeof(sa);
jbyteArray address;
memset((char *)&sa, 0, sizeof(sa));
newfd = (jint) accept(fd, (struct sockaddr *)&sa, &sa_len);
if (newfd == INVALID_SOCKET) {
int theErr = (jint)WSAGetLastError();
if (theErr == WSAEWOULDBLOCK) {
return IOS_UNAVAILABLE;
}
JNU_ThrowIOExceptionWithLastError(env, "Accept failed");
return IOS_THROWN;
}
SetHandleInformation((HANDLE)(UINT_PTR)newfd, HANDLE_FLAG_INHERIT, 0);
setfdval(env, newfdo, newfd);
address = sockaddrToUnixAddressBytes(env, &sa, sa_len);
CHECK_NULL_RETURN(address, IOS_THROWN);
(*env)->SetObjectArrayElement(env, array, 0, address);
return 1;
}
JNIEXPORT jbyteArray JNICALL
Java_sun_nio_ch_UnixDomainSockets_localAddress0(JNIEnv *env, jclass clazz, jobject fdo)
{
struct sockaddr_un sa;
int sa_len = sizeof(sa);
if (getsockname(fdval(env, fdo), (struct sockaddr *)&sa, &sa_len) == SOCKET_ERROR) {
JNU_ThrowIOExceptionWithLastError(env, "getsockname");
return NULL;
}
return sockaddrToUnixAddressBytes(env, &sa, sa_len);
}

View file

@ -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
@ -24,6 +24,8 @@
*/
#include <winsock2.h>
#include <ws2tcpip.h>
#include <afunix.h>
#include "jni.h"
@ -35,6 +37,9 @@
*/
#define MAX_BUFFER_SIZE ((128*1024)-1)
#define MAX_UNIX_DOMAIN_PATH_LEN \
(int)(sizeof(((struct sockaddr_un *)0)->sun_path)-2)
jint fdval(JNIEnv *env, jobject fdo);
void setfdval(JNIEnv *env, jobject fdo, jint val);
jlong handleval(JNIEnv *env, jobject fdo);
@ -74,3 +79,11 @@ struct iovec {
/* POLLCONN must not equal any of the other constants (see winsock2.h). */
#define POLLCONN 0x2000
#endif
/* Defined in UnixDomainSockets.c */
jbyteArray sockaddrToUnixAddressBytes(JNIEnv *env, struct sockaddr_un *sa, socklen_t len);
jint unixSocketAddressToSockaddr(JNIEnv *env, jbyteArray uaddr,
struct sockaddr_un *sa, int *len);