/* * Copyright (c) 1997, 2011, 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 <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #if defined(__linux__) && !defined(USE_SELECT) #include <sys/poll.h> #endif #include <netinet/tcp.h> /* Defines TCP_NODELAY, needed for 2.6 */ #include <netinet/in.h> #ifdef __linux__ #include <netinet/ip.h> #endif #include <netdb.h> #include <stdlib.h> #ifdef __solaris__ #include <fcntl.h> #endif #ifdef __linux__ #include <unistd.h> //#include <sys/sysctl.h> #endif #include "jvm.h" #include "jni_util.h" #include "net_util.h" #include "java_net_SocketOptions.h" #include "java_net_PlainSocketImpl.h" #include "JNIHelp.h" #define NATIVE_METHOD(className, functionName, signature) \ { #functionName, signature, (void*)(className ## _ ## functionName) } /************************************************************************ * PlainSocketImpl */ 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; extern void setDefaultScopeID(JNIEnv *env, struct sockaddr *him); #define SET_NONBLOCKING(fd) { \ int flags = fcntl(fd, F_GETFL); \ flags |= O_NONBLOCK; \ fcntl(fd, F_SETFL, flags); \ } #define SET_BLOCKING(fd) { \ int flags = fcntl(fd, F_GETFL); \ flags &= ~O_NONBLOCK; \ fcntl(fd, F_SETFL, flags); \ } /* * Return the file descriptor given a PlainSocketImpl */ 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); } static void PlainSocketImpl_initProto(JNIEnv *env) { jclass cls = (*env)->FindClass(env, "java/net/PlainSocketImpl"); 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); psi_trafficClassID = (*env)->GetFieldID(env, cls, "trafficClass", "I"); CHECK_NULL(psi_trafficClassID); psi_serverSocketID = (*env)->GetFieldID(env, cls, "serverSocket", "Ljava/net/ServerSocket;"); CHECK_NULL(psi_serverSocketID); psi_fdLockID = (*env)->GetFieldID(env, cls, "fdLock", "Ljava/lang/Object;"); CHECK_NULL(psi_fdLockID); psi_closePendingID = (*env)->GetFieldID(env, cls, "closePending", "Z"); CHECK_NULL(psi_closePendingID); IO_fd_fdID = NET_GetFileDescriptorID(env); CHECK_NULL(IO_fd_fdID); } /* a global reference to the java.net.SocketException class. In * socketCreate, we ensure that this is initialized. This is to * prevent the problem where socketCreate runs out of file * descriptors, and is then unable to load the exception class. */ static jclass socketExceptionCls; /* * Class: java_net_PlainSocketImpl * Method: socketCreate * Signature: (Z)V */ JNIEXPORT void JNICALL PlainSocketImpl_socketCreate(JNIEnv *env, jobject this, jboolean stream) { jobject fdObj, ssObj; int fd; int type = (stream ? SOCK_STREAM : SOCK_DGRAM); #ifdef AF_INET6 int domain = ipv6_available() ? AF_INET6 : AF_INET; #else int domain = AF_INET; #endif 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, this, psi_fdID); if (fdObj == NULL) { (*env)->ThrowNew(env, socketExceptionCls, "null fd object"); return; } if ((fd = JVM_Socket(domain, type, 0)) == JVM_IO_ERR) { /* note: if you run out of fds, you may not be able to load * the exception class, and get a NoClassDefFoundError * instead. */ NET_ThrowNew(env, errno, "can't create socket"); return; } tagSocket(env, fd); #ifdef AF_INET6 /* Disable IPV6_V6ONLY to ensure dual-socket support */ if (domain == AF_INET6) { int arg = 0; if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&arg, sizeof(int)) < 0) { NET_ThrowNew(env, errno, "cannot set IPPROTO_IPV6"); untagSocket(env, fd); close(fd); return; } } #endif /* AF_INET6 */ /* * If this is a server socket then enable SO_REUSEADDR * automatically and set to non blocking. */ ssObj = (*env)->GetObjectField(env, this, psi_serverSocketID); if (ssObj != NULL) { int arg = 1; SET_NONBLOCKING(fd); if (JVM_SetSockOpt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&arg, sizeof(arg)) < 0) { NET_ThrowNew(env, errno, "cannot set SO_REUSEADDR"); untagSocket(env, fd); close(fd); return; } } (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd); } /* * inetAddress is the address object passed to the socket connect * call. * * Class: java_net_PlainSocketImpl * Method: socketConnect * Signature: (Ljava/net/InetAddress;I)V */ JNIEXPORT void JNICALL PlainSocketImpl_socketConnect(JNIEnv *env, jobject this, jobject iaObj, jint port, jint timeout) { jint localport = (*env)->GetIntField(env, this, psi_localportID); int len = 0; /* fdObj is the FileDescriptor field on this */ jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID); jclass clazz = (*env)->GetObjectClass(env, this); jobject fdLock; jint trafficClass = (*env)->GetIntField(env, this, psi_trafficClassID); /* fd is an int field on iaObj */ jint fd; SOCKADDR him; /* The result of the connection */ 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; } /* connect */ if (NET_InetAddressToSockaddr(env, iaObj, port, (struct sockaddr *)&him, &len, JNI_TRUE) != 0) { return; } setDefaultScopeID(env, (struct sockaddr *)&him); #ifdef AF_INET6 if (trafficClass != 0 && ipv6_available()) { NET_SetTrafficClass((struct sockaddr *)&him, trafficClass); } #endif /* AF_INET6 */ if (timeout <= 0) { connect_rv = NET_Connect(fd, (struct sockaddr *)&him, len); #ifdef __solaris__ if (connect_rv == JVM_IO_ERR && errno == EINPROGRESS ) { /* This can happen if a blocking connect is interrupted by a signal. * See 6343810. */ while (1) { #ifndef USE_SELECT { struct pollfd pfd; pfd.fd = fd; pfd.events = POLLOUT; connect_rv = NET_Poll(&pfd, 1, -1); } #else { fd_set wr, ex; FD_ZERO(&wr); FD_SET(fd, &wr); FD_ZERO(&ex); FD_SET(fd, &ex); connect_rv = NET_Select(fd+1, 0, &wr, &ex, 0); } #endif if (connect_rv == JVM_IO_ERR) { if (errno == EINTR) { continue; } else { break; } } if (connect_rv > 0) { int optlen; /* has connection been established */ optlen = sizeof(connect_rv); if (JVM_GetSockOpt(fd, SOL_SOCKET, SO_ERROR, (void*)&connect_rv, &optlen) <0) { connect_rv = errno; } if (connect_rv != 0) { /* restore errno */ errno = connect_rv; connect_rv = JVM_IO_ERR; } break; } } } #endif } else { /* * A timeout was specified. We put the socket into non-blocking * mode, connect, and then wait for the connection to be * established, fail, or timeout. */ SET_NONBLOCKING(fd); /* no need to use NET_Connect as non-blocking */ connect_rv = connect(fd, (struct sockaddr *)&him, len); /* connection not established immediately */ if (connect_rv != 0) { int optlen; jlong prevTime = JVM_CurrentTimeMillis(env, 0); if (errno != EINPROGRESS) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException", "connect failed"); SET_BLOCKING(fd); return; } /* * Wait for the connection to be established or a * timeout occurs. poll/select needs to handle EINTR in * case lwp sig handler redirects any process signals to * this thread. */ while (1) { jlong newTime; #ifndef USE_SELECT { struct pollfd pfd; pfd.fd = fd; pfd.events = POLLOUT; errno = 0; connect_rv = NET_Poll(&pfd, 1, timeout); } #else { fd_set wr, ex; struct timeval t; t.tv_sec = timeout / 1000; t.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&wr); FD_SET(fd, &wr); FD_ZERO(&ex); FD_SET(fd, &ex); errno = 0; connect_rv = NET_Select(fd+1, 0, &wr, &ex, &t); } #endif if (connect_rv >= 0) { break; } if (errno != EINTR) { break; } /* * The poll was interrupted so adjust timeout and * restart */ newTime = JVM_CurrentTimeMillis(env, 0); timeout -= (newTime - prevTime); if (timeout <= 0) { connect_rv = 0; break; } prevTime = newTime; } /* while */ if (connect_rv == 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", "connect timed out"); /* * Timeout out but connection may still be established. * At the high level it should be closed immediately but * just in case we make the socket blocking again and * shutdown input & output. */ SET_BLOCKING(fd); JVM_SocketShutdown(fd, 2); return; } /* has connection been established */ optlen = sizeof(connect_rv); if (JVM_GetSockOpt(fd, SOL_SOCKET, SO_ERROR, (void*)&connect_rv, &optlen) <0) { connect_rv = errno; } } /* make socket blocking again */ SET_BLOCKING(fd); /* restore errno */ if (connect_rv != 0) { errno = connect_rv; connect_rv = JVM_IO_ERR; } } /* report the appropriate exception */ if (connect_rv < 0) { #ifdef __linux__ /* * Linux/GNU distribution setup /etc/hosts so that * InetAddress.getLocalHost gets back the loopback address * rather than the host address. Thus a socket can be * bound to the loopback address and the connect will * fail with EADDRNOTAVAIL. In addition the Linux kernel * returns the wrong error in this case - it returns EINVAL * instead of EADDRNOTAVAIL. We handle this here so that * a more descriptive exception text is used. */ if (connect_rv == JVM_IO_ERR && errno == EINVAL) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Invalid argument or cannot assign requested address"); return; } #endif if (connect_rv == JVM_IO_INTR) { JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException", "operation interrupted"); #if defined(EPROTO) } else if (errno == EPROTO) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ProtocolException", "Protocol error"); #endif } else if (errno == ECONNREFUSED) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException", "Connection refused"); } else if (errno == ETIMEDOUT) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "ConnectException", "Connection timed out"); } else if (errno == EHOSTUNREACH) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "NoRouteToHostException", "Host unreachable"); } else if (errno == EADDRNOTAVAIL) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "NoRouteToHostException", "Address not available"); } else if ((errno == EISCONN) || (errno == EBADF)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); } else { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "connect failed"); } return; } (*env)->SetIntField(env, fdObj, IO_fd_fdID, fd); /* set the remote peer address and port */ (*env)->SetObjectField(env, this, psi_addressID, iaObj); (*env)->SetIntField(env, this, psi_portID, port); /* * 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 = SOCKADDR_LEN; if (JVM_GetSockName(fd, (struct sockaddr *)&him, &len) == -1) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Error getting socket name"); } else { localport = NET_GetPortFromSockaddr((struct sockaddr *)&him); (*env)->SetIntField(env, this, psi_localportID, localport); } } } /* * Class: java_net_PlainSocketImpl * Method: socketBind * Signature: (Ljava/net/InetAddress;I)V */ JNIEXPORT void JNICALL PlainSocketImpl_socketBind(JNIEnv *env, jobject this, jobject iaObj, jint localport) { /* fdObj is the FileDescriptor field on this */ jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID); /* fd is an int field on fdObj */ int fd; int len; SOCKADDR him; 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; } /* bind */ if (NET_InetAddressToSockaddr(env, iaObj, localport, (struct sockaddr *)&him, &len, JNI_TRUE) != 0) { return; } setDefaultScopeID(env, (struct sockaddr *)&him); if (NET_Bind(fd, (struct sockaddr *)&him, len) < 0) { if (errno == EADDRINUSE || errno == EADDRNOTAVAIL || errno == EPERM || errno == EACCES) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "BindException", "Bind failed"); } else { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Bind failed"); } return; } /* set the address */ (*env)->SetObjectField(env, this, psi_addressID, iaObj); /* intialize the local port */ 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. */ if (JVM_GetSockName(fd, (struct sockaddr *)&him, &len) == -1) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Error getting socket name"); return; } localport = NET_GetPortFromSockaddr((struct sockaddr *)&him); (*env)->SetIntField(env, this, psi_localportID, localport); } else { (*env)->SetIntField(env, this, psi_localportID, localport); } } /* * Class: java_net_PlainSocketImpl * Method: socketListen * Signature: (I)V */ JNIEXPORT void JNICALL PlainSocketImpl_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; if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return; } else { fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); } /* * Workaround for bugid 4101691 in Solaris 2.6. See 4106600. * If listen backlog is Integer.MAX_VALUE then subtract 1. */ if (count == 0x7fffffff) count -= 1; if (JVM_Listen(fd, count) == JVM_IO_ERR) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Listen failed"); } } /* * Class: java_net_PlainSocketImpl * Method: socketAccept * Signature: (Ljava/net/SocketImpl;)V */ JNIEXPORT void JNICALL PlainSocketImpl_socketAccept(JNIEnv *env, jobject this, jobject socket) { /* fields on this */ int port; jint timeout = (*env)->GetIntField(env, this, psi_timeoutID); jlong prevTime = 0; jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID); /* the FileDescriptor field on socket */ jobject socketFdObj; /* the InetAddress field on socket */ jobject socketAddressObj; /* the ServerSocket fd int field on fdObj */ jint fd; /* accepted fd */ jint newfd; SOCKADDR him; int len; len = SOCKADDR_LEN; 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; } /* * accept connection but ignore ECONNABORTED indicating that * connection was eagerly accepted by the OS but was reset * before accept() was called. * * If accept timeout in place and timeout is adjusted with * each ECONNABORTED or EWOULDBLOCK to ensure that semantics * of timeout are preserved. */ for (;;) { int ret; /* first usage pick up current time */ if (prevTime == 0 && timeout > 0) { prevTime = JVM_CurrentTimeMillis(env, 0); } /* passing a timeout of 0 to poll will return immediately, but in the case of ServerSocket 0 means infinite. */ if (timeout <= 0) { ret = NET_Timeout(fd, -1); } else { ret = NET_Timeout(fd, timeout); } if (ret == 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", "Accept timed out"); return; } else if (ret == JVM_IO_ERR) { if (errno == EBADF) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); } else { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Accept failed"); } return; } else if (ret == JVM_IO_INTR) { JNU_ThrowByName(env, JNU_JAVAIOPKG "InterruptedIOException", "operation interrupted"); return; } newfd = NET_Accept(fd, (struct sockaddr *)&him, (jint*)&len); /* connection accepted */ if (newfd >= 0) { SET_BLOCKING(newfd); break; } /* non (ECONNABORTED or EWOULDBLOCK) error */ if (!(errno == ECONNABORTED || errno == EWOULDBLOCK)) { break; } /* ECONNABORTED or EWOULDBLOCK error so adjust timeout if there is one. */ if (timeout) { jlong currTime = JVM_CurrentTimeMillis(env, 0); timeout -= (currTime - prevTime); if (timeout <= 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketTimeoutException", "Accept timed out"); return; } prevTime = currTime; } } 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 { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Accept failed"); } } return; } /* * fill up the remote peer port and address in the new socket structure. */ socketAddressObj = NET_SockaddrToInetAddress(env, (struct sockaddr *)&him, &port); if (socketAddressObj == NULL) { /* should be pending exception */ untagSocket(env, fd); close(newfd); return; } /* * Populate SocketImpl.fd.fd */ 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); /* also fill up the local port information */ port = (*env)->GetIntField(env, this, psi_localportID); (*env)->SetIntField(env, socket, psi_localportID, port); } /* * Class: java_net_PlainSocketImpl * Method: socketAvailable * Signature: ()I */ JNIEXPORT jint JNICALL PlainSocketImpl_socketAvailable(JNIEnv *env, jobject this) { jint ret = -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); } /* JVM_SocketAvailable returns 0 for failure, 1 for success */ if (!JVM_SocketAvailable(fd, &ret)){ if (errno == ECONNRESET) { JNU_ThrowByName(env, "sun/net/ConnectionResetException", ""); } else { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "ioctl FIONREAD failed"); } } return ret; } /* * Class: java_net_PlainSocketImpl * Method: socketClose0 * Signature: ()V */ JNIEXPORT void JNICALL PlainSocketImpl_socketClose0(JNIEnv *env, jobject this) { 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); } if (fd != -1) { (*env)->SetIntField(env, fdObj, IO_fd_fdID, -1); untagSocket(env, fd); NET_SocketClose(fd); } } /* * Class: java_net_PlainSocketImpl * Method: socketShutdown * Signature: (I)V */ JNIEXPORT void JNICALL PlainSocketImpl_socketShutdown(JNIEnv *env, jobject this, jint howto) { jobject fdObj = (*env)->GetObjectField(env, this, psi_fdID); jint fd; /* * WARNING: THIS NEEDS LOCKING. ALSO: SHOULD WE CHECK for fd being * -1 already? */ if (IS_NULL(fdObj)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "socket already closed"); return; } else { fd = (*env)->GetIntField(env, fdObj, IO_fd_fdID); } JVM_SocketShutdown(fd, howto); } /* * Class: java_net_PlainSocketImpl * Method: socketSetOption * Signature: (IZLjava/lang/Object;)V */ JNIEXPORT void JNICALL PlainSocketImpl_socketSetOption(JNIEnv *env, jobject this, jint cmd, jboolean on, jobject value) { int fd; int level, optname, optlen; union { int i; struct linger ling; } optval; /* * Check that socket hasn't been closed */ fd = getFD(env, this); if (fd < 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return; } /* * SO_TIMEOUT is a no-op on Solaris/Linux */ if (cmd == java_net_SocketOptions_SO_TIMEOUT) { return; } /* * Map the Java level socket option to the platform specific * level and option name. */ if (NET_MapSocketOption(cmd, &level, &optname)) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Invalid option"); return; } switch (cmd) { case java_net_SocketOptions_SO_SNDBUF : case java_net_SocketOptions_SO_RCVBUF : case java_net_SocketOptions_SO_LINGER : 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); 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; } /* Boolean -> int */ default : optval.i = (on ? 1 : 0); optlen = sizeof(optval.i); } if (NET_SetSockOpt(fd, level, optname, (const void *)&optval, optlen) < 0) { #ifdef __solaris__ if (errno == EINVAL) { // On Solaris setsockopt will set errno to EINVAL if the socket // is closed. The default error message is then confusing char fullMsg[128]; jio_snprintf(fullMsg, sizeof(fullMsg), "Invalid option or socket reset by remote peer"); JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", fullMsg); return; } #endif /* __solaris__ */ NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Error setting socket option"); } } /* * Class: java_net_PlainSocketImpl * Method: socketGetOption * Signature: (I)I */ JNIEXPORT jint JNICALL PlainSocketImpl_socketGetOption(JNIEnv *env, jobject this, jint cmd, jobject iaContainerObj) { int fd; int level, optname, optlen; union { int i; struct linger ling; } optval; /* * Check that socket hasn't been closed */ fd = getFD(env, this); if (fd < 0) { JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Socket closed"); return -1; } /* * SO_BINDADDR isn't a socket option */ if (cmd == java_net_SocketOptions_SO_BINDADDR) { SOCKADDR him; socklen_t len = 0; int port; jobject iaObj; jclass iaCntrClass; jfieldID iaFieldID; len = SOCKADDR_LEN; if (getsockname(fd, (struct sockaddr *)&him, &len) < 0) { NET_ThrowByNameWithLastError(env, JNU_JAVANETPKG "SocketException", "Error getting socket name"); return -1; } iaObj = NET_SockaddrToInetAddress(env, (struct sockaddr *)&him, &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; /* notice change from before */ } /* * Map the Java level socket option to the platform specific * level and option name. */ if (NET_MapSocketOption(cmd, &level, &optname)) { JNU_ThrowByName(env, JNU_JAVANETPKG "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 (NET_GetSockOpt(fd, level, optname, (void *)&optval, &optlen) < 0) { NET_ThrowByNameWithLastError(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: case java_net_SocketOptions_IP_TOS: return optval.i; default : return (optval.i == 0) ? -1 : 1; } } /* * Class: java_net_PlainSocketImpl * Method: socketSendUrgentData * Signature: (B)V */ JNIEXPORT void JNICALL PlainSocketImpl_socketSendUrgentData(JNIEnv *env, jobject this, jint data) { /* The fd field */ jobject fdObj = (*env)->GetObjectField(env, this, 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); /* Bug 4086704 - If the Socket associated with this file descriptor * was closed (sysCloseFD), the the file descriptor is set to -1. */ if (fd == -1) { JNU_ThrowByName(env, "java/net/SocketException", "Socket closed"); return; } } n = JVM_Send(fd, (char *)&d, 1, MSG_OOB); if (n == JVM_IO_ERR) { NET_ThrowByNameWithLastError(env, "java/io/IOException", "Write failed"); return; } if (n == JVM_IO_INTR) { JNU_ThrowByName(env, "java/io/InterruptedIOException", 0); return; } } static JNINativeMethod gMethods[] = { NATIVE_METHOD(PlainSocketImpl, socketSendUrgentData, "(I)V"), NATIVE_METHOD(PlainSocketImpl, socketGetOption, "(ILjava/lang/Object;)I"), NATIVE_METHOD(PlainSocketImpl, socketSetOption, "(IZLjava/lang/Object;)V"), NATIVE_METHOD(PlainSocketImpl, socketShutdown, "(I)V"), NATIVE_METHOD(PlainSocketImpl, socketClose0, "()V"), NATIVE_METHOD(PlainSocketImpl, socketAccept, "(Ljava/net/SocketImpl;)V"), NATIVE_METHOD(PlainSocketImpl, socketAvailable, "()I"), NATIVE_METHOD(PlainSocketImpl, socketListen, "(I)V"), NATIVE_METHOD(PlainSocketImpl, socketBind, "(Ljava/net/InetAddress;I)V"), NATIVE_METHOD(PlainSocketImpl, socketConnect, "(Ljava/net/InetAddress;II)V"), NATIVE_METHOD(PlainSocketImpl, socketCreate, "(Z)V"), }; void register_java_net_PlainSocketImpl(JNIEnv* env) { jniRegisterNativeMethods(env, "java/net/PlainSocketImpl", gMethods, NELEM(gMethods)); PlainSocketImpl_initProto(env); }