/* * Copyright (c) 1997, 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. */ #include #include "jvm.h" #include "rdma_util_md.h" #include "java_net_SocketOptions.h" #include "net_util.h" #include "net_util_md.h" #include "rdma_ch_LinuxRdmaSocketImpl.h" /************************************************************************ * RdmaSocketImpl */ static jfieldID IO_fd_fdID; jfieldID psi_fdID; jfieldID psi_addressID; jfieldID psi_ipaddressID; jfieldID psi_portID; jfieldID psi_localportID; jfieldID psi_timeoutID; jfieldID psi_trafficClassID; jfieldID psi_serverSocketID; jfieldID psi_fdLockID; jfieldID psi_closePendingID; /* * file descriptor used for dup2 */ static int marker_fd = -1; #define SET_NONBLOCKING(fd) { \ int flags = rfcntl(fd, F_GETFL); \ flags |= O_NONBLOCK; \ rfcntl(fd, F_SETFL, flags); \ } #define SET_BLOCKING(fd) { \ int flags = rfcntl(fd, F_GETFL); \ flags &= ~O_NONBLOCK; \ rfcntl(fd, F_SETFL, flags); \ } static jclass socketExceptionCls; static int getMarkerFD() { int sv[2]; #ifdef AF_UNIX if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) { return -1; } #else return -1; #endif rshutdown(sv[0], 2); rclose(sv[1]); return sv[0]; } static int getFD(JNIEnv *env, jobject this) { jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID); CHECK_NULL_RETURN(fdObj, -1); return (*env)->GetIntField(env, fdObj, IO_fd_fdID); } jfieldID NET_GetFileDescriptorID(JNIEnv *env) { jclass cls = (*env)->FindClass(env, "java/io/FileDescriptor"); CHECK_NULL_RETURN(cls, NULL); return (*env)->GetFieldID(env, cls, "fd", "I"); } JNIEXPORT void JNICALL Java_rdma_ch_LinuxRdmaSocketImpl_initProto(JNIEnv *env, jclass cls) { jclass clazz = (*env)->FindClass(env, "rdma/ch/RdmaSocketImpl"); psi_fdID = (*env)->GetFieldID(env, clazz , "fd", "Ljava/io/FileDescriptor;"); CHECK_NULL(psi_fdID); psi_addressID = (*env)->GetFieldID(env, clazz, "address", "Ljava/net/InetAddress;"); CHECK_NULL(psi_addressID); psi_timeoutID = (*env)->GetFieldID(env, clazz, "timeout", "I"); CHECK_NULL(psi_timeoutID); psi_trafficClassID = (*env)->GetFieldID(env, clazz, "trafficClass", "I"); CHECK_NULL(psi_trafficClassID); psi_portID = (*env)->GetFieldID(env, clazz, "port", "I"); CHECK_NULL(psi_portID); psi_localportID = (*env)->GetFieldID(env, clazz, "localport", "I"); CHECK_NULL(psi_localportID); psi_serverSocketID = (*env)->GetFieldID(env, clazz, "serverSocket", "Ljava/net/ServerSocket;"); CHECK_NULL(psi_serverSocketID); IO_fd_fdID = NET_GetFileDescriptorID(env); CHECK_NULL(IO_fd_fdID); initInetAddressIDs(env); JNU_CHECK_EXCEPTION(env); marker_fd = getMarkerFD(); } JNIEXPORT jboolean JNICALL Java_rdma_ch_LinuxRdmaSocketImpl_isRdmaAvailable0(JNIEnv *env, jclass cls) { return rdma_supported(); } void NET_SetTrafficClass(SOCKETADDRESS *sa, int trafficClass) { if (sa->sa.sa_family == AF_INET6) { sa->sa6.sin6_flowinfo = htonl((trafficClass & 0xff) << 20); } } /* * Class: jdk_net_LinuxRdmaSocketImpl * Method: rdmaSocketCreate * Signature: (Z)V */ JNIEXPORT void JNICALL Java_rdma_ch_LinuxRdmaSocketImpl_rdmaSocketCreate(JNIEnv *env, jobject this, jboolean stream, jobject impl) { jobject fdObj, ssObj; int fd; int type = (stream ? SOCK_STREAM : SOCK_DGRAM); int domain = ipv6_available() ? AF_INET6 : AF_INET; if (socketExceptionCls == NULL) { jclass c = (*env)->FindClass(env, "java/net/SocketException"); CHECK_NULL(c); socketExceptionCls = (jclass)(*env)->NewGlobalRef(env, c); CHECK_NULL(socketExceptionCls); } fdObj = (*env)->GetObjectField(env, impl, psi_fdID); if (fdObj == NULL) { (*env)->ThrowNew(env, socketExceptionCls, "null fd object"); return; } if ((fd = rsocket(domain, type, 0)) == -1) { NET_ThrowNew(env, errno, "can't create socket"); return; } if (domain == AF_INET6) { int arg = 0; if (rsetsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&arg, sizeof(int)) < 0) { NET_ThrowNew(env, errno, "cannot set IPPROTO_IPV6"); rclose(fd); return; } } ssObj = (*env)->GetObjectField(env, impl, psi_serverSocketID); if (ssObj != NULL) { int arg = 1; SET_NONBLOCKING(fd); if (RDMA_SetSockOpt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&arg, sizeof(arg)) < 0) { NET_ThrowNew(env, errno, "cannot set SO_REUSEADDR"); rclose(fd); return; } } (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd); } JNIEXPORT void JNICALL Java_rdma_ch_LinuxRdmaSocketImpl_rdmaSocketConnect(JNIEnv *env, jobject this, jobject impl, jobject iaObj, jint port, jint timeout) { jint localport = (*env)->GetIntField(env, impl, psi_localportID); int len = 0; jobject fdObj = (*env)->GetObjectField(env, impl, psi_fdID); jobject fdLock; jint trafficClass = (*env)->GetIntField(env, impl, psi_trafficClassID); jint fd; SOCKETADDRESS sa; int connect_rv = -1; if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return; } else { fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); } if (IS_NULL(iaObj)) { JNU_ThrowNullPointerException(env, "inet address argument null."); return; } if (NET_InetAddressToSockaddr(env, iaObj, port, &sa, &len, JNI_TRUE) != 0) { return; } if (trafficClass != 0 && ipv6_available()) { NET_SetTrafficClass(&sa, trafficClass); } if (timeout <= 0) { connect_rv = RDMA_Connect(fd, &sa.sa, len); } else { SET_NONBLOCKING(fd); connect_rv = rconnect(fd, &sa.sa, len); if (connect_rv != 0) { socklen_t optlen; jlong nanoTimeout = (jlong) timeout * NET_NSEC_PER_MSEC; jlong prevNanoTime = JVM_NanoTime(env, 0); if (errno != EINPROGRESS) { JNU_ThrowByNameWithMessageAndLastError(env, JNU_JAVANETPKG "ConnectException", "connect failed"); SET_BLOCKING(fd); return; } while (1) { jlong newNanoTime; struct pollfd pfd; pfd.fd = fd; pfd.events = POLLOUT; errno = 0; connect_rv = RDMA_Poll(&pfd, 1, nanoTimeout / NET_NSEC_PER_MSEC); if (connect_rv >= 0) { break; } if (errno != EINTR) { break; } newNanoTime = JVM_NanoTime(env, 0); nanoTimeout -= (newNanoTime - prevNanoTime); if (nanoTimeout < NET_NSEC_PER_MSEC) { connect_rv = 0; break; } prevNanoTime = newNanoTime; } if (connect_rv == 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", "connect timed out"); SET_BLOCKING(fd); rshutdown(fd, 2); return; } optlen = sizeof(connect_rv); if (rgetsockopt(fd, SOL_SOCKET, SO_ERROR, (void*)&connect_rv, &optlen) <0) { connect_rv = errno; } } SET_BLOCKING(fd); if (connect_rv != 0) { errno = connect_rv; connect_rv = -1; } } if (connect_rv < 0) { if (connect_rv == -1 && errno == EINVAL) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Invalid argument or cannot assign requested address"); return; } #if defined(EPROTO) if (errno == EPROTO) { JNU_ThrowByNameWithMessageAndLastError(env, JNU_JAVANETPKG "ProtocolException", "Protocol error"); return; } #endif if (errno == ECONNREFUSED) { JNU_ThrowByNameWithMessageAndLastError(env, JNU_JAVANETPKG "ConnectException", "Connection refused"); } else if (errno == ETIMEDOUT) { JNU_ThrowByNameWithMessageAndLastError(env, JNU_JAVANETPKG "ConnectException", "Connection timed out"); } else if (errno == EHOSTUNREACH) { JNU_ThrowByNameWithMessageAndLastError(env, JNU_JAVANETPKG "NoRouteToHostException", "Host unreachable"); } else if (errno == EADDRNOTAVAIL) { JNU_ThrowByNameWithMessageAndLastError(env, JNU_JAVANETPKG "NoRouteToHostException", "Address not available"); } else if ((errno == EISCONN) || (errno == EBADF)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); } else { JNU_ThrowByNameWithMessageAndLastError (env, JNU_JAVANETPKG "SocketException", "connect failed"); } return; } (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd); (*env)->SetObjectField(env, impl, psi_addressID, iaObj); (*env)->SetIntField(env, impl, psi_portID, port); if (localport == 0) { socklen_t slen = sizeof(SOCKETADDRESS); if (rgetsockname(fd, &sa.sa, &slen) == -1) { JNU_ThrowByNameWithMessageAndLastError (env, JNU_JAVANETPKG "SocketException", "Error getting socket name"); } else { localport = NET_GetPortFromSockaddr(&sa); (*env)->SetIntField(env, impl, psi_localportID, localport); } } } JNIEXPORT void JNICALL Java_rdma_ch_LinuxRdmaSocketImpl_rdmaSocketBind(JNIEnv *env, jobject this, jobject impl, jobject iaObj, jint localport) { jobject fdObj = (*env)->GetObjectField(env, impl, psi_fdID); int fd; int len = 0; SOCKETADDRESS sa; if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return; } else { fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); } if (IS_NULL(iaObj)) { JNU_ThrowNullPointerException(env, "iaObj is null."); return; } if (NET_InetAddressToSockaddr(env, iaObj, localport, &sa, &len, JNI_TRUE) != 0) { return; } if (RDMA_Bind(fd, &sa, len) < 0) { if (errno == EADDRINUSE || errno == EADDRNOTAVAIL || errno == EPERM || errno == EACCES) { JNU_ThrowByNameWithMessageAndLastError(env, JNU_JAVANETPKG "BindException", "Bind failed"); } else { JNU_ThrowByNameWithMessageAndLastError (env, JNU_JAVANETPKG "SocketException", "Bind failed"); } return; } (*env)->SetObjectField(env, impl, psi_addressID, iaObj); if (localport == 0) { socklen_t slen = sizeof(SOCKETADDRESS); if (rgetsockname(fd, &sa.sa, &slen) == -1) { JNU_ThrowByNameWithMessageAndLastError (env, JNU_JAVANETPKG "SocketException", "Error getting socket name"); return; } localport = NET_GetPortFromSockaddr(&sa); (*env)->SetIntField(env, impl, psi_localportID, localport); } else { (*env)->SetIntField(env, impl, psi_localportID, localport); } } JNIEXPORT void JNICALL Java_rdma_ch_LinuxRdmaSocketImpl_rdmaSocketListen(JNIEnv *env, jobject this, jobject impl, jint count) { jobject fdObj = (*env)->GetObjectField(env, impl, psi_fdID); int fd; if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return; } else { fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); } if (count == 0x7fffffff) count -= 1; int rv = rlisten(fd, count); if (rv == -1) { JNU_ThrowByNameWithMessageAndLastError (env, JNU_JAVANETPKG "SocketException", "Listen failed"); } } JNIEXPORT void JNICALL Java_rdma_ch_LinuxRdmaSocketImpl_rdmaSocketAccept(JNIEnv *env, jobject this, jobject socket, jobject impl) { int port; jint timeout = (*env)->GetIntField(env, impl, psi_timeoutID); jlong prevNanoTime = 0; jlong nanoTimeout = (jlong) timeout * NET_NSEC_PER_MSEC; jobject fdObj = (*env)->GetObjectField(env, impl, psi_fdID); jobject socketFdObj; jobject socketAddressObj; jint fd; jint newfd; SOCKETADDRESS sa; socklen_t slen = sizeof(SOCKETADDRESS); if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return; } else { fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); } if (IS_NULL(socket)) { JNU_ThrowNullPointerException(env, "socket is null"); return; } for (;;) { int ret; jlong currNanoTime; if (prevNanoTime == 0 && nanoTimeout > 0) { prevNanoTime = JVM_NanoTime(env, 0); } if (timeout <= 0) { ret = RDMA_Timeout(env, fd, -1, 0); } else { ret = RDMA_Timeout(env, fd, nanoTimeout / NET_NSEC_PER_MSEC, prevNanoTime); } if (ret == 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", "Accept timed out"); return; } else if (ret == -1) { if (errno == EBADF) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); } else if (errno == ENOMEM) { JNU_ThrowOutOfMemoryError(env, "NET_Timeout native heap allocation failed"); } else { JNU_ThrowByNameWithMessageAndLastError (env, JNU_JAVANETPKG "SocketException", "Accept failed"); } return; } newfd = RDMA_Accept(fd, &sa.sa, &slen); if (newfd >= 0) { SET_BLOCKING(newfd); break; } if (!(errno == ECONNABORTED || errno == EWOULDBLOCK)) { break; } if (nanoTimeout >= NET_NSEC_PER_MSEC) { currNanoTime = JVM_NanoTime(env, 0); nanoTimeout -= (currNanoTime - prevNanoTime); if (nanoTimeout < NET_NSEC_PER_MSEC) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", "Accept timed out"); return; } prevNanoTime = currNanoTime; } } if (newfd < 0) { if (newfd == -2) { JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException", "operation interrupted"); } else { if (errno == EINVAL) { errno = EBADF; } if (errno == EBADF) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); } else { JNU_ThrowByNameWithMessageAndLastError (env, JNU_JAVANETPKG "SocketException", "Accept failed"); } } return; } socketAddressObj = NET_SockaddrToInetAddress(env, &sa, &port); if (socketAddressObj == NULL) { rclose(newfd); return; } socketFdObj = (*env)->GetObjectField(env, socket, psi_fdID); (*env)->SetIntField(env, socketFdObj, IO_fd_fdID, newfd); (*env)->SetObjectField(env, socket, psi_addressID, socketAddressObj); (*env)->SetIntField(env, socket, psi_portID, port); port = (*env)->GetIntField(env, impl, psi_localportID); (*env)->SetIntField(env, socket, psi_localportID, port); } JNIEXPORT jint JNICALL Java_rdma_ch_LinuxRdmaSocketImpl_rdmaSocketAvailable(JNIEnv *env, jobject this, jobject impl) { jint ret = -1; jobject fdObj = (*env)->GetObjectField(env, impl, psi_fdID); jint fd; if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return -1; } else { fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); } if (RDMA_SocketAvailable(fd, &ret) == 0){ if (errno == ECONNRESET) { JNU_ThrowByName(env, "sun/net/ConnectionResetException", ""); } else { JNU_ThrowByNameWithMessageAndLastError (env, JNU_JAVANETPKG "SocketException", "ioctl FIONREAD failed"); } } return ret; } JNIEXPORT void JNICALL Java_rdma_ch_LinuxRdmaSocketImpl_rdmaSocketClose(JNIEnv *env, jobject this, jboolean useDeferredClose, jobject impl) { jobject fdObj = (*env)->GetObjectField(env, impl, psi_fdID); jint fd; if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "socket already closed"); return; } else { fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); } if (fd != -1) { if (useDeferredClose && marker_fd >= 0) { RDMA_Dup2(marker_fd, fd); } else { (*env)->SetIntField(env, fdObj, IO_fd_fdID, -1); RDMA_SocketClose(fd); } } } JNIEXPORT void JNICALL Java_rdma_ch_LinuxRdmaSocketImpl_rdmaSocketShutdown(JNIEnv *env, jobject this, jint howto, jobject impl) { jobject fdObj = (*env)->GetObjectField(env, impl, psi_fdID); jint fd; if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "socket already closed"); return; } else { fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); } rshutdown(fd, howto); } JNIEXPORT void JNICALL Java_rdma_ch_LinuxRdmaSocketImpl_rdmaSocketSetOption (JNIEnv *env, jobject this, jobject impl, jint cmd, jboolean on, jobject value) { int fd; int level, optname, optlen; union { int i; struct linger ling; } optval; fd = getFD(env, impl); if (fd < 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return; } if (cmd == java_net_SocketOptions_SO_TIMEOUT) { return; } if (RDMA_MapSocketOption(cmd, &level, &optname)) { JNU_ThrowByName(env, "java/net/SocketException", "Invalid option"); return; } switch (cmd) { case java_net_SocketOptions_SO_SNDBUF : case java_net_SocketOptions_SO_RCVBUF : case java_net_SocketOptions_SO_LINGER : { jclass cls; jfieldID fid; cls = (*env)->FindClass(env, "java/lang/Integer"); CHECK_NULL(cls); fid = (*env)->GetFieldID(env, cls, "value", "I"); CHECK_NULL(fid); if (cmd == java_net_SocketOptions_SO_LINGER) { if (on) { optval.ling.l_onoff = 1; optval.ling.l_linger = (*env)->GetIntField(env, value, fid); } else { optval.ling.l_onoff = 0; optval.ling.l_linger = 0; } optlen = sizeof(optval.ling); } else { optval.i = (*env)->GetIntField(env, value, fid); optlen = sizeof(optval.i); } break; } default : optval.i = (on ? 1 : 0); optlen = sizeof(optval.i); } if (RDMA_SetSockOpt(fd, level, optname, (const void *)&optval, optlen) < 0) { JNU_ThrowByNameWithMessageAndLastError (env, JNU_JAVANETPKG "SocketException", "Error setting socket option"); } } JNIEXPORT jint JNICALL Java_rdma_ch_LinuxRdmaSocketImpl_rdmaSocketGetOption (JNIEnv *env, jobject this, jobject impl, jint cmd, jobject iaContainerObj) { int fd; int level, optname, optlen; union { int i; struct linger ling; } optval; fd = getFD(env, impl); if (fd < 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return -1; } if (cmd == java_net_SocketOptions_SO_BINDADDR) { SOCKETADDRESS sa; socklen_t len = sizeof(SOCKETADDRESS); int port; jobject iaObj; jclass iaCntrClass; jfieldID iaFieldID; if (rgetsockname(fd, &sa.sa, &len) < 0) { JNU_ThrowByNameWithMessageAndLastError (env, JNU_JAVANETPKG "SocketException", "Error getting socket name"); return -1; } iaObj = NET_SockaddrToInetAddress(env, &sa, &port); CHECK_NULL_RETURN(iaObj, -1); iaCntrClass = (*env)->GetObjectClass(env, iaContainerObj); iaFieldID = (*env)->GetFieldID(env, iaCntrClass, "addr", "Ljava/net/InetAddress;"); CHECK_NULL_RETURN(iaFieldID, -1); (*env)->SetObjectField(env, iaContainerObj, iaFieldID, iaObj); return 0; } if (RDMA_MapSocketOption(cmd, &level, &optname)) { JNU_ThrowByName(env, "java/net/SocketException", "Invalid option"); return -1; } /* * Args are int except for SO_LINGER */ if (cmd == java_net_SocketOptions_SO_LINGER) { optlen = sizeof(optval.ling); } else { optlen = sizeof(optval.i); } if (RDMA_GetSockOpt(fd, level, optname, (void *)&optval, &optlen) < 0) { JNU_ThrowByNameWithMessageAndLastError (env, JNU_JAVANETPKG "SocketException", "Error getting socket option"); return -1; } switch (cmd) { case java_net_SocketOptions_SO_LINGER: return (optval.ling.l_onoff ? optval.ling.l_linger: -1); case java_net_SocketOptions_SO_SNDBUF: case java_net_SocketOptions_SO_RCVBUF: return optval.i; default : return (optval.i == 0) ? -1 : 1; } } JNIEXPORT void JNICALL Java_rdma_ch_LinuxRdmaSocketImpl_rdmaSocketSendUrgentData(JNIEnv *env, jobject this, jobject impl, jint data) { jobject fdObj = (*env)->GetObjectField(env, impl, psi_fdID); int n, fd; unsigned char d = data & 0xFF; if (IS_NULL(fdObj)) { JNU_ThrowByName(env, "java/net/SocketException", "Socket closed"); return; } else { fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); if (fd == -1) { JNU_ThrowByName(env, "java/net/SocketException", "Socket closed"); return; } } n = RDMA_Send(fd, (char *)&d, 1, MSG_OOB); if (n == -1) { JNU_ThrowIOExceptionWithLastError(env, "Write failed"); } }