/* * Copyright (c) 2007, 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 "net_util.h" #include "java_net_DualStackPlainSocketImpl.h" #include "java_net_SocketOptions.h" /************************************************************************ * DualStackPlainSocketImpl */ static jfieldID IO_fd_fdID; jfieldID psi_fdID; jfieldID psi_addressID; jfieldID psi_portID; jfieldID psi_localportID; jfieldID psi_timeoutID; static jboolean configureBlocking(JNIEnv *env, jint fd, jboolean blocking) { u_long arg = (blocking == JNI_TRUE) ? 0 : 1; if (ioctlsocket(fd, FIONBIO, &arg) == SOCKET_ERROR) { if (!(*env)->ExceptionCheck(env)) { NET_ThrowNew(env, WSAGetLastError(), "configureBlocking"); } return JNI_FALSE; } return JNI_TRUE; } /* * Class: java_net_DualStackPlainSocketImpl * Method: initProto * Signature: ()V */ JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_initProto (JNIEnv *env, jclass cls) { psi_fdID = (*env)->GetFieldID(env, cls , "fd", "Ljava/io/FileDescriptor;"); CHECK_NULL(psi_fdID); psi_addressID = (*env)->GetFieldID(env, cls, "address", "Ljava/net/InetAddress;"); CHECK_NULL(psi_addressID); psi_portID = (*env)->GetFieldID(env, cls, "port", "I"); CHECK_NULL(psi_portID); psi_localportID = (*env)->GetFieldID(env, cls, "localport", "I"); CHECK_NULL(psi_localportID); psi_timeoutID = (*env)->GetFieldID(env, cls, "timeout", "I"); CHECK_NULL(psi_timeoutID); initInetAddressIDs(env); // implement read timeout with select. isRcvTimeoutSupported = 0; IO_fd_fdID = NET_GetFileDescriptorID(env); CHECK_NULL(IO_fd_fdID); } /* * Class: java_net_DualStackPlainSocketImpl * Method: socketCreate * Signature: (Z)V */ JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_socketCreate(JNIEnv *env, jobject this, jboolean stream) { jobject fdObj; int fd; fdObj = (*env)->GetObjectField(env, this, psi_fdID); if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "null fd object"); return; } fd = NET_Socket(AF_INET6, (stream ? SOCK_STREAM : SOCK_DGRAM), 0); if (fd == INVALID_SOCKET) { NET_ThrowNew(env, WSAGetLastError(), "create"); return; } else { int arg = 0; if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&arg, sizeof(int)) == SOCKET_ERROR) { NET_ThrowNew(env, WSAGetLastError(), "create"); } /* Set socket attribute so it is not passed to any child process */ SetHandleInformation((HANDLE)(UINT_PTR)fd, HANDLE_FLAG_INHERIT, FALSE); (*env)->SetIntField(env, fdObj, IO_fd_fdID, (int)fd); } } /* * Class: java_net_DualStackPlainSocketImpl * Method: socketBind * Signature: (Ljava/net/InetAddress;I)V */ JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_socketBind(JNIEnv *env, jobject this, jobject iaObj, jint localport, jboolean exclBind) { /* fdObj is the FileDescriptor field on this */ jobject fdObj; /* fd is an int field on fdObj */ int fd, len = 0; int rv; SOCKETADDRESS sa; fdObj = (*env)->GetObjectField(env, this, psi_fdID); 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"); return; } if (NET_InetAddressToSockaddr(env, iaObj, localport, &sa, &len, JNI_TRUE) != 0) { return; } rv = NET_WinBind(fd, &sa, len, exclBind); if (rv == SOCKET_ERROR) { NET_ThrowNew(env, WSAGetLastError(), "NET_Bind"); return; } /* set the address */ (*env)->SetObjectField(env, this, psi_addressID, iaObj); /* intialize the local port */ if (localport == 0) { /* Now that we're a bound socket, let's extract the port number * that the system chose for us and store it in the Socket object. */ int len = sizeof(SOCKETADDRESS); u_short port; if (getsockname(fd, &sa.sa, &len) == -1) { NET_ThrowCurrent(env, "getsockname in plain socketBind"); return; } port = ntohs((u_short) GET_PORT (&sa)); (*env)->SetIntField(env, this, psi_localportID, (int)port); } else { (*env)->SetIntField(env, this, psi_localportID, localport); } } /* * Class: java_net_DualStackPlainSocketImpl * Method: socketConnect * Signature: (Ljava/net/InetAddress;I)V */ JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_socketConnect(JNIEnv *env, jobject this, jobject iaObj, jint port, jint timeout) { jint localport = (*env)->GetIntField(env, this, psi_localportID); /* family and localport are int fields of iaObj */ jint fd = -1; jint len; jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID); SOCKETADDRESS sa; /* The result of the connection */ int connect_res; memset((char *)&sa, 0, sizeof(sa)); if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "socket closed"); return; } fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); if (IS_NULL(iaObj)) { JNU_ThrowNullPointerException(env, "inet address argument is null."); return; } if (NET_InetAddressToSockaddr(env, iaObj, port, &sa, &len, JNI_TRUE) != 0) { return; } if (fd == -1) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Destination unreachable"); return; } if (timeout <= 0) { connect_res = connect(fd, &sa.sa, len); if (connect_res == SOCKET_ERROR) { int err = WSAGetLastError(); if (err == WSAEADDRNOTAVAIL) { JNU_ThrowByName(env, JNU_JAVANETPKG "ConnectException", "connect: Address is invalid on local machine, or port is not valid on remote machine"); } else { NET_ThrowNew(env, err, "connect"); } return; } } else { if (configureBlocking(env, fd, JNI_FALSE) == JNI_FALSE) { // exception pending return; } /* initiate the connect */ connect_res = connect(fd, &sa.sa, len); if (connect_res == SOCKET_ERROR) { int err = WSAGetLastError(); if (err != WSAEWOULDBLOCK) { connect_res = err; } else { fd_set wr, ex; struct timeval t; FD_ZERO(&wr); FD_ZERO(&ex); FD_SET(fd, &wr); FD_SET(fd, &ex); t.tv_sec = timeout / 1000; t.tv_usec = (timeout % 1000) * 1000; /* * Wait for timout, connection established or * connection failed. */ connect_res = select(fd+1, 0, &wr, &ex, &t); /* * Timeout before connection is established/failed so * we throw exception and shutdown input/output to prevent * socket from being used. * The socket should be closed immediately by the caller. */ if (connect_res == 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", "connect timed out"); shutdown( fd, SD_BOTH ); /* make socket blocking again - just in case */ configureBlocking(env, fd, JNI_TRUE); return; } /* * Socket is writable or error occurred. On some Windows editions * the socket will appear writable when the connect fails so we * check for error rather than writable. */ if (!FD_ISSET(fd, &ex)) { connect_res = 0; /* connection established */ } else { int optlen = sizeof(&connect_res); /* * Connection failed. The logic here is designed to work around * bug on Windows NT whereby using getsockopt to obtain the * last error (SO_ERROR) indicates there is no error. The workaround * on NT is to allow winsock to be scheduled and this is done by * yielding and retrying. As yielding is problematic in heavy * load conditions we attempt up to 3 times to get the error reason. */ int retry; for (retry=0; retry<3; retry++) { NET_GetSockOpt(fd, SOL_SOCKET, SO_ERROR, (char*)&connect_res, &optlen); if (connect_res) { break; } Sleep(0); } if (connect_res == 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Unable to establish connection"); } else { NET_ThrowNew(env, connect_res, "connect"); } return; } } } if (configureBlocking(env, fd, JNI_TRUE) == JNI_FALSE) { // exception pending return; } } if (connect_res) { if (connect_res == WSAEADDRNOTAVAIL) { JNU_ThrowByName(env, JNU_JAVANETPKG "ConnectException", "connect: Address is invalid on local machine, or port is not valid on remote machine"); } else { NET_ThrowNew(env, connect_res, "connect"); } return; } /* * we need to initialize the local port field if bind was called * previously to the connect (by the client) then localport field * will already be initialized */ if (localport == 0) { /* Now that we're a connected socket, let's extract the port number * that the system chose for us and store it in the Socket object. */ len = sizeof(sa); memset((char *)&sa, 0, len); if (getsockname(fd, &sa.sa, &len) == SOCKET_ERROR) { if (WSAGetLastError() == WSAENOTSOCK) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); } else { NET_ThrowNew(env, WSAGetLastError(), "getsockname failed"); } return; } localport = (jint) ntohs((u_short)GET_PORT(&sa)); (*env)->SetIntField(env, this, psi_localportID, (int) localport); } } /* * Class: java_net_DualStackPlainSocketImpl * Method: socketListen * Signature: (I)V */ JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_socketListen (JNIEnv *env, jobject this, jint count) { /* this FileDescriptor fd field */ jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID); /* fdObj's int fd field */ int fd = INVALID_SOCKET; if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "socket closed"); return; } fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); if (listen(fd, count) == SOCKET_ERROR) { NET_ThrowNew(env, WSAGetLastError(), "listen failed"); } } /* * Class: java_net_DualStackPlainSocketImpl * Method: socketAccept * Signature: (Ljava/net/SocketImpl;)V */ JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_socketAccept(JNIEnv *env, jobject this, jobject socket) { /* fields on this */ jint port=0; jint timeout = (*env)->GetIntField(env, this, psi_timeoutID); jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID); /* the FileDescriptor field on socket */ jobject socketFdObj; /* the InetAddress field on socket */ jobject socketAddressObj; /* the fd int field on fdObj */ jint fd=-1; /* accepted fd */ jint newfd; SOCKETADDRESS sa; jint len; int ret; if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return; } fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); if (IS_NULL(socket)) { JNU_ThrowNullPointerException(env, "socket is null"); return; } else { socketFdObj = (*env)->GetObjectField(env, socket, psi_fdID); } if (IS_NULL(socketFdObj)) { JNU_ThrowNullPointerException(env, "socket fd obj"); return; } if (timeout > 0) { if (configureBlocking(env, fd, JNI_FALSE) == JNI_FALSE) { // exception pending return; } ret = NET_Timeout(fd, timeout); if (ret == 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", "Accept timed out"); configureBlocking(env, fd, JNI_TRUE); return; } else if (ret == -1) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "socket closed"); configureBlocking(env, fd, JNI_TRUE); return; } else if (ret == -2) { JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException", "operation interrupted"); configureBlocking(env, fd, JNI_TRUE); return; } } len = sizeof(sa); memset((char *)&sa, 0, len); newfd = accept(fd, &sa.sa, &len); if (timeout > 0) { if (configureBlocking(env, fd, JNI_TRUE) == JNI_FALSE) { if (newfd != INVALID_SOCKET) { closesocket(newfd); } // exception pending return; } } if (newfd == INVALID_SOCKET) { if (WSAGetLastError() == -2) { JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException", "operation interrupted"); } else { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "socket closed"); } (*env)->SetIntField(env, socketFdObj, IO_fd_fdID, -1); return; } SetHandleInformation((HANDLE)(UINT_PTR)newfd, HANDLE_FLAG_INHERIT, 0); if (configureBlocking(env, newfd, JNI_TRUE) == JNI_FALSE) { closesocket(newfd); // exception pending return; } socketAddressObj = NET_SockaddrToInetAddress(env, &sa, &port); if (socketAddressObj == NULL) { /* should be pending exception */ closesocket(newfd); return; } (*env)->SetIntField(env, socketFdObj, IO_fd_fdID, newfd); (*env)->SetObjectField(env, socket, psi_addressID, socketAddressObj); (*env)->SetIntField(env, socket, psi_portID, (int)port); /* also fill up the local port information */ port = (*env)->GetIntField(env, this, psi_localportID); (*env)->SetIntField(env, socket, psi_localportID, port); } /* * Class: java_net_DualStackPlainSocketImpl * Method: socketAvailable * Signature: ()I */ JNIEXPORT jint JNICALL Java_java_net_DualStackPlainSocketImpl_socketAvailable(JNIEnv *env, jobject this) { jint available = -1; jobject fdObj = (*env)->GetObjectField(env, this, 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 (ioctlsocket(fd, FIONREAD, &available) == SOCKET_ERROR) { NET_ThrowNew(env, WSAGetLastError(), "socket available"); } return available; } /* * Class: java_net_DualStackPlainSocketImpl * Method: socketClose * Signature: ()V */ JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_socketClose0(JNIEnv *env, jobject this, jboolean useDeferredClose) { jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID); jint fd=-1; if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "socket already closed"); return; } if (!IS_NULL(fdObj)) { fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); } if (fd != -1) { (*env)->SetIntField(env, fdObj, IO_fd_fdID, -1); NET_SocketClose(fd); } } /* * Class: java_net_DualStackPlainSocketImpl * Method: socketShutdown * Signature: (I)V */ JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_socketShutdown(JNIEnv *env, jobject this, jint howto) { jobject fdObj = (*env)->GetObjectField(env, this, 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); } shutdown(fd, howto); } /* * Class: java_net_DualStackPlainSocketImpl * Method: socketNativeSetOption * Signature: (IZLjava/lang/Object;)V */ JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_socketNativeSetOption (JNIEnv *env, jobject this, jint cmd, jboolean on, jobject value) { /* The fd field */ jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID); jint fd; int level = 0, optname = 0, optlen = 0; union { int i; struct linger ling; } optval; memset((char *)&optval, 0, sizeof(optval)); /* * Get SOCKET and check that it hasn't been closed */ if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return; } else { fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); } if (fd < 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return; } if (cmd == java_net_SocketOptions_SO_TIMEOUT) { // timeout implemented through select. return; } /* * Map the Java level socket option to the platform specific * level and option name. */ if (NET_MapSocketOption(cmd, &level, &optname)) { JNU_ThrowByName(env, "java/net/SocketException", "Invalid option"); return; } switch (cmd) { case java_net_SocketOptions_TCP_NODELAY: case java_net_SocketOptions_SO_OOBINLINE: case java_net_SocketOptions_SO_KEEPALIVE: case java_net_SocketOptions_SO_REUSEADDR: optval.i = (on ? 1 : 0); optlen = sizeof(optval.i); break; case java_net_SocketOptions_SO_SNDBUF: case java_net_SocketOptions_SO_RCVBUF: case java_net_SocketOptions_IP_TOS: { jclass cls; jfieldID fid; cls = (*env)->FindClass(env, "java/lang/Integer"); CHECK_NULL(cls); fid = (*env)->GetFieldID(env, cls, "value", "I"); CHECK_NULL(fid); optval.i = (*env)->GetIntField(env, value, fid); optlen = sizeof(optval.i); } break; 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 (on) { optval.ling.l_onoff = 1; optval.ling.l_linger = (unsigned short)(*env)->GetIntField(env, value, fid); } else { optval.ling.l_onoff = 0; optval.ling.l_linger = 0; } optlen = sizeof(optval.ling); } break; default: /* shouldn't get here */ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Option not supported by DualStackPlainSocketImpl"); return; } if (NET_SetSockOpt(fd, level, optname, (void *)&optval, optlen) < 0) { NET_ThrowNew(env, WSAGetLastError(), "setsockopt"); } } /* * Class: java_net_DualStackPlainSocketImpl * Method: socketNativeGetOption * Signature: (ILjava/lang/Object;)I */ JNIEXPORT jint JNICALL Java_java_net_DualStackPlainSocketImpl_socketNativeGetOption (JNIEnv *env, jobject this, jint opt, jobject iaContainerObj) { /* The fd field */ jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID); int fd; int level = 0, optname = 0, optlen = 0; union { int i; struct linger ling; } optval; /* * Get SOCKET and check it hasn't been closed */ if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return -1; } else { fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); } if (fd < 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return -1; } memset((char *)&optval, 0, sizeof(optval)); /* * SO_BINDADDR isn't a socket option */ if (opt == java_net_SocketOptions_SO_BINDADDR) { SOCKETADDRESS sa; int len = sizeof(sa); int port; jobject iaObj; jclass iaContainerClass; jfieldID iaFieldID; if (getsockname(fd, &sa.sa, &len) == SOCKET_ERROR) { NET_ThrowNew(env, WSAGetLastError(), "Error getting socket name"); return -1; } iaObj = NET_SockaddrToInetAddress(env, &sa, &port); CHECK_NULL_RETURN(iaObj, -1); iaContainerClass = (*env)->GetObjectClass(env, iaContainerObj); iaFieldID = (*env)->GetFieldID(env, iaContainerClass, "addr", "Ljava/net/InetAddress;"); CHECK_NULL_RETURN(iaFieldID, -1); (*env)->SetObjectField(env, iaContainerObj, iaFieldID, iaObj); return 0; /* notice change from before */ } /* * Map the Java level socket option to the platform specific * level and option name. */ if (NET_MapSocketOption(opt, &level, &optname)) { JNU_ThrowByName(env, "java/net/SocketException", "Invalid option"); return -1; } /* * Args are int except for SO_LINGER */ if (opt == java_net_SocketOptions_SO_LINGER) { optlen = sizeof(optval.ling); } else { optlen = sizeof(optval.i); optval.i = 0; } if (NET_GetSockOpt(fd, level, optname, (void *)&optval, &optlen) < 0) { NET_ThrowNew(env, WSAGetLastError(), "getsockopt"); return -1; } switch (opt) { 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: case java_net_SocketOptions_IP_TOS: return optval.i; case java_net_SocketOptions_TCP_NODELAY: case java_net_SocketOptions_SO_OOBINLINE: case java_net_SocketOptions_SO_KEEPALIVE: case java_net_SocketOptions_SO_REUSEADDR: return (optval.i == 0) ? -1 : 1; default: /* shouldn't get here */ JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Option not supported by TwoStacksPlainSocketImpl"); return -1; } } /* * Class: java_net_DualStackPlainSocketImpl * Method: socketSendUrgentData * Signature: (B)V */ JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_socketSendUrgentData(JNIEnv *env, jobject this, jint data) { /* The fd field */ jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID); int n, fd; unsigned char d = (unsigned char) 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 = send(fd, (char *)&data, 1, MSG_OOB); if (n == SOCKET_ERROR) { NET_ThrowNew(env, WSAGetLastError(), "send"); } }