diff --git a/README.md b/README.md index 60b1b4bd..50b859fe 100644 --- a/README.md +++ b/README.md @@ -327,6 +327,24 @@ $ ./configure --enable-secure-renegotiation Or by defining `-DHAVE_SECURE_RENEGOTIATION`. +### Native File Descriptor Events + +wolfSSL JNI/JSSE internally makes several calls that operate on native +file descriptors inside Java Socket objects. These native file descriptors +are watched for read and write events with either `select()` or `poll()`. + +By default `poll()` will be used, unless `WOLFJNI_USE_IO_SELECT` is defined +or added to CFLAGS when compiling the native JNI sources (see `java.sh`). +Windows builds will also default to using `select()` since `poll()` is not +available there. + +wolfSSL JNI/JSSE does not select/poll on a large number of file descriptors +(typically just one). Although if used in applications that make lots of +connections, when using `select()` the `FD_ISSET` and other related macros +result in undefined behavior when the file descriptor number is larger than +`FD_SETSIZE` (defaults to 1024 on most systems). For this reason, `poll()` is +used as the default descriptor monitoring function. + ## Release Notes Release notes can be found in [ChangeLog.md](./ChangeLog.md). diff --git a/native/com_wolfssl_WolfSSL.h b/native/com_wolfssl_WolfSSL.h index 2c6fadd2..8f1572dc 100644 --- a/native/com_wolfssl_WolfSSL.h +++ b/native/com_wolfssl_WolfSSL.h @@ -9,8 +9,18 @@ extern "C" { #endif #undef com_wolfssl_WolfSSL_JNI_SESSION_UNAVAILABLE #define com_wolfssl_WolfSSL_JNI_SESSION_UNAVAILABLE -10001L -#undef com_wolfssl_WolfSSL_WOLFJNI_TIMEOUT -#define com_wolfssl_WolfSSL_WOLFJNI_TIMEOUT -11L +#undef com_wolfssl_WolfSSL_WOLFJNI_IO_EVENT_FAIL +#define com_wolfssl_WolfSSL_WOLFJNI_IO_EVENT_FAIL -10L +#undef com_wolfssl_WolfSSL_WOLFJNI_IO_EVENT_TIMEOUT +#define com_wolfssl_WolfSSL_WOLFJNI_IO_EVENT_TIMEOUT -11L +#undef com_wolfssl_WolfSSL_WOLFJNI_IO_EVENT_ERROR +#define com_wolfssl_WolfSSL_WOLFJNI_IO_EVENT_ERROR -14L +#undef com_wolfssl_WolfSSL_WOLFJNI_IO_EVENT_FD_CLOSED +#define com_wolfssl_WolfSSL_WOLFJNI_IO_EVENT_FD_CLOSED -15L +#undef com_wolfssl_WolfSSL_WOLFJNI_IO_EVENT_POLLHUP +#define com_wolfssl_WolfSSL_WOLFJNI_IO_EVENT_POLLHUP -16L +#undef com_wolfssl_WolfSSL_WOLFJNI_IO_EVENT_INVALID_TIMEOUT +#define com_wolfssl_WolfSSL_WOLFJNI_IO_EVENT_INVALID_TIMEOUT -17L #undef com_wolfssl_WolfSSL_SSL_ERROR_NONE #define com_wolfssl_WolfSSL_SSL_ERROR_NONE 0L #undef com_wolfssl_WolfSSL_SSL_FAILURE diff --git a/native/com_wolfssl_WolfSSLSession.c b/native/com_wolfssl_WolfSSLSession.c index 0d95b344..60e9431e 100644 --- a/native/com_wolfssl_WolfSSLSession.c +++ b/native/com_wolfssl_WolfSSLSession.c @@ -33,6 +33,11 @@ #include #include #endif +#ifdef WOLFJNI_USE_IO_SELECT + #include +#else + #include +#endif #ifndef WOLFSSL_JNI_DEFAULT_PEEK_TIMEOUT /* Default wolfSSL_peek() timeout for wolfSSL_get_session(), ms */ @@ -186,9 +191,6 @@ int NativeSSLVerifyCallback(int preverify_ok, WOLFSSL_X509_STORE_CTX* store) return retval; } - -/* jni functions */ - JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLSession_newSSL (JNIEnv* jenv, jobject jcl, jlong ctx) { @@ -599,22 +601,50 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_getFd return wolfSSL_get_fd(ssl); } -/* enum values used in socketSelect() */ +/* enum values used in socketSelect() and socketPoll(). Some of these + * values are also duplicated in WolfSSL.java for access from Java classes. + * If updated here, make sure to update in WolfSSL.java too. */ enum { - WOLFJNI_SELECT_FAIL = -10, - WOLFJNI_TIMEOUT = -11, /* also in WolfSSL.java */ - WOLFJNI_RECV_READY = -12, - WOLFJNI_SEND_READY = -13, - WOLFJNI_ERROR_READY = -14 + WOLFJNI_IO_EVENT_FAIL = -10, + WOLFJNI_IO_EVENT_TIMEOUT = -11, + WOLFJNI_IO_EVENT_RECV_READY = -12, + WOLFJNI_IO_EVENT_SEND_READY = -13, + WOLFJNI_IO_EVENT_ERROR = -14, + WOLFJNI_IO_EVENT_FD_CLOSED = -15, + WOLFJNI_IO_EVENT_POLLHUP = -16, + WOLFJNI_IO_EVENT_INVALID_TIMEOUT = -17 }; -/* perform a select() call on underlying socket to wait for socket to be ready - * to read/write, or timeout. Note that we explicitly set the underlying - * socket descriptor to non-blocking so we can select() on it. +#ifdef WOLFJNI_USE_IO_SELECT + +/* Perform a select() call on the underlying socket to wait for socket to be + * ready for read/write, or timeout. Note that we explicitly set the underlying + * socket descriptor to non-blocking. + * + * NOTE: the FD_ISSET macro behavior is undefined if the descriptor value is + * less than 0 or greater than or equal to FD_SETSIZE (1024 by default). + * + * On a Java Socket, a timeout of 0 is an infinite timeout. Greater than zero + * is a timeout in milliseconds. Negative timeout is invalid and not supported. + * For select(), a non-NULL timeval struct specifies maximum timeout to wait, + * a NULL timeval struct is an infinite timeout. A zero-valued timeval struct + * will return immediately (no timeout). + * + * @param sockfd socket descriptor to select() + * @param timeout_ms timeout in milliseconds. 0 indicates infinite timeout, to + * match Java timeout behavior. Negative timeout not + * supported, since not supported on Java Socket. + * @param rx set to 1 to monitor readability on socket descriptor, + * otherwise 0 to monitor writability * - * The Java socket timeout value representing no timeout is NULL, not 0 like - * C. We adjust for this when handling timeout_ms here. timeout_ms is in - * milliseconds. */ + * @return possible return values are: + * WOLFJNI_IO_EVENT_FAIL + * WOLFJNI_IO_EVENT_ERROR + * WOLFJNI_IO_EVENT_TIMEOUT + * WOLFJNI_IO_EVENT_RECV_READY + * WOLFJNI_IO_EVENT_SEND_READY + * WOLFJNI_IO_EVENT_INVALID_TIMEOUT + */ static int socketSelect(int sockfd, int timeout_ms, int rx) { fd_set fds, errfds; @@ -624,6 +654,11 @@ static int socketSelect(int sockfd, int timeout_ms, int rx) int result = 0; struct timeval timeout; + /* Java Socket does not support negative timeouts, sanitize */ + if (timeout_ms < 0) { + return WOLFJNI_IO_EVENT_INVALID_TIMEOUT; + } + #ifndef USE_WINDOWS_API do { #endif @@ -648,31 +683,118 @@ static int socketSelect(int sockfd, int timeout_ms, int rx) } if (result == 0) { - return WOLFJNI_TIMEOUT; + return WOLFJNI_IO_EVENT_TIMEOUT; } else if (result > 0) { if (FD_ISSET(sockfd, &fds)) { if (rx) { - return WOLFJNI_RECV_READY; + return WOLFJNI_IO_EVENT_RECV_READY; } else { - return WOLFJNI_SEND_READY; + return WOLFJNI_IO_EVENT_SEND_READY; } } else if (FD_ISSET(sockfd, &errfds)) { - return WOLFJNI_ERROR_READY; + return WOLFJNI_IO_EVENT_ERROR; } } #ifndef USE_WINDOWS_API - } while ((result == -1) && (errno == EINTR)); + } while ((result == -1) && ((errno == EINTR) || (errno == EAGAIN))); #endif - /* Return on error, unless select() was interrupted, try again above */ - return WOLFJNI_SELECT_FAIL; + /* Return on error, unless errno EINTR or EAGAIN, try again above */ + return WOLFJNI_IO_EVENT_FAIL; } +#else /* !WOLFJNI_USE_IO_SELECT */ + +/* Perform poll() on underlying socket descriptor to wait for socket to be + * ready for read/write, or timeout. Note that we are explicitly setting + * the underlying descriptor to non-blocking. + * + * On a Java Socket, a timeout of 0 is an infinite timeout. Greater than zero + * is a timeout in milliseconds. Negative timeout is invalid and not supported. + * For poll(), timeout greater than 0 specifies max timeout in milliseconds, + * zero timeout will return immediately (no timeout), and -1 will block + * indefinitely. + * + * @param sockfd socket descriptor to poll() + * @param timeout_ms timeout in milliseconds. 0 indicates infinite timeout, to + * match Java timeout behavior. Negative timeout not + * supported, since not supported on Java Socket. + * @param rx set to 1 to monitor readability on socket descriptor, + * otherwise 0 to ignore readability events + * @param tx set to 1 to monitor writability on socket descriptor, + * otherwise 0 to ignore writability events + * + * @return possible return values are: + * WOLFJNI_IO_EVENT_FAIL + * WOLFJNI_IO_EVENT_ERROR + * WOLFJNI_IO_EVENT_TIMEOUT + * WOLFJNI_IO_EVENT_RECV_READY + * WOLFJNI_IO_EVENT_SEND_READY + * WOLFJNI_IO_EVENT_FD_CLOSED + * WOLFJNI_IO_EVENT_POLLHUP + * WOLFJNI_IO_EVENT_INVALID_TIMEOUT + */ +static int socketPoll(int sockfd, int timeout_ms, int rx, int tx) +{ + int ret; + int timeout; + struct pollfd fds[1]; + + /* Sanitize timeout and convert from Java to poll() expectations */ + timeout = timeout_ms; + if (timeout < 0) { + return WOLFJNI_IO_EVENT_INVALID_TIMEOUT; + } else if (timeout == 0) { + timeout = -1; + } + + fds[0].fd = sockfd; + fds[0].events = 0; + if (tx) { + fds[0].events |= POLLOUT; + } + if (rx) { + fds[0].events |= POLLIN; + } + + do { + ret = poll(fds, 1, timeout); + if (ret == 0) { + return WOLFJNI_IO_EVENT_TIMEOUT; + + } else if (ret > 0) { + if (fds[0].revents & POLLIN || + fds[0].revents & POLLPRI) { /* read possible */ + return WOLFJNI_IO_EVENT_RECV_READY; + + } else if (fds[0].revents & POLLOUT) { /* write possible */ + return WOLFJNI_IO_EVENT_SEND_READY; + + } else if (fds[0].revents & POLLNVAL) { /* fd not open */ + return WOLFJNI_IO_EVENT_FD_CLOSED; + + } else if (fds[0].revents & POLLERR) { /* exceptional error */ + return WOLFJNI_IO_EVENT_ERROR; + + } else if (fds[0].revents & POLLHUP) { /* sock disconnected */ + return WOLFJNI_IO_EVENT_POLLHUP; + } + } + + } while ((ret == -1) && ((errno == EINTR) || (errno == EAGAIN))); + + return WOLFJNI_IO_EVENT_FAIL; +} + +#endif /* WOLFJNI_USE_IO_SELECT */ + JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_connect (JNIEnv* jenv, jobject jcl, jlong sslPtr, jint timeout) { int ret = 0, err = 0, sockfd = 0; + int pollRx = 0; + int pollTx = 0; wolfSSL_Mutex* jniSessLock = NULL; SSLAppData* appData = NULL; WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; @@ -726,12 +848,28 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_connect break; } - ret = socketSelect(sockfd, (int)timeout, 1); - if (ret == WOLFJNI_RECV_READY || ret == WOLFJNI_SEND_READY) { + if (err == SSL_ERROR_WANT_READ) { + pollRx = 1; + } + else if (err == SSL_ERROR_WANT_WRITE) { + pollTx = 1; + } + + #if defined(WOLFJNI_USE_IO_SELECT) || defined(USE_WINDOWS_API) + ret = socketSelect(sockfd, (int)timeout, pollRx); + #else + ret = socketPoll(sockfd, (int)timeout, pollRx, pollTx); + #endif + if ((ret == WOLFJNI_IO_EVENT_RECV_READY) || + (ret == WOLFJNI_IO_EVENT_SEND_READY)) { /* I/O ready, continue handshake and try again */ continue; - } else if (ret == WOLFJNI_TIMEOUT) { - /* Java will throw SocketTimeoutException */ + } else if (ret == WOLFJNI_IO_EVENT_TIMEOUT || + ret == WOLFJNI_IO_EVENT_FD_CLOSED || + ret == WOLFJNI_IO_EVENT_ERROR || + ret == WOLFJNI_IO_EVENT_POLLHUP || + ret == WOLFJNI_IO_EVENT_FAIL) { + /* Java will throw SocketTimeoutException or SocketException */ break; } else { /* error */ @@ -758,6 +896,8 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_write { byte* data = NULL; int ret = SSL_FAILURE, err, sockfd; + int pollRx = 0; + int pollTx = 0; wolfSSL_Mutex* jniSessLock = NULL; SSLAppData* appData = NULL; WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; @@ -819,12 +959,32 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_write break; } - ret = socketSelect(sockfd, (int)timeout, 0); - if (ret == WOLFJNI_RECV_READY || ret == WOLFJNI_SEND_READY) { + if (err == SSL_ERROR_WANT_READ) { + pollRx = 1; + } + else if (err == SSL_ERROR_WANT_WRITE) { + pollTx = 1; + } + + #if defined(WOLFJNI_USE_IO_SELECT) || defined(USE_WINDOWS_API) + ret = socketSelect(sockfd, (int)timeout, pollRx); + #else + ret = socketPoll(sockfd, (int)timeout, pollRx, pollTx); + #endif + if ((ret == WOLFJNI_IO_EVENT_RECV_READY) || + (ret == WOLFJNI_IO_EVENT_SEND_READY)) { /* loop around and try wolfSSL_write() again */ continue; + } else if (ret == WOLFJNI_IO_EVENT_TIMEOUT || + ret == WOLFJNI_IO_EVENT_FD_CLOSED || + ret == WOLFJNI_IO_EVENT_ERROR || + ret == WOLFJNI_IO_EVENT_POLLHUP || + ret == WOLFJNI_IO_EVENT_FAIL) { + /* Java will throw SocketTimeoutException or + * SocketException */ + break; } else { - /* error or timeout occurred during select */ + /* error */ ret = WOLFSSL_FAILURE; break; } @@ -847,6 +1007,8 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_read { byte* data = NULL; int size = 0, ret, err, sockfd; + int pollRx = 0; + int pollTx = 0; wolfSSL_Mutex* jniSessLock = NULL; SSLAppData* appData = NULL; WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; @@ -905,12 +1067,32 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_read break; } - ret = socketSelect(sockfd, timeout, 1); - if (ret == WOLFJNI_RECV_READY || ret == WOLFJNI_SEND_READY) { + if (err == SSL_ERROR_WANT_READ) { + pollRx = 1; + } + else if (err == SSL_ERROR_WANT_WRITE) { + pollTx = 1; + } + + #if defined(WOLFJNI_USE_IO_SELECT) || defined(USE_WINDOWS_API) + ret = socketSelect(sockfd, (int)timeout, pollRx); + #else + ret = socketPoll(sockfd, (int)timeout, pollRx, pollTx); + #endif + if ((ret == WOLFJNI_IO_EVENT_RECV_READY) || + (ret == WOLFJNI_IO_EVENT_SEND_READY)) { /* loop around and try wolfSSL_read() again */ continue; + } else if (ret == WOLFJNI_IO_EVENT_TIMEOUT || + ret == WOLFJNI_IO_EVENT_FD_CLOSED || + ret == WOLFJNI_IO_EVENT_ERROR || + ret == WOLFJNI_IO_EVENT_POLLHUP || + ret == WOLFJNI_IO_EVENT_FAIL) { + /* Java will throw SocketTimeoutException or + * SocketException */ + break; } else { - /* error or timeout occurred during select */ + /* other error occurred */ size = ret; break; } @@ -930,6 +1112,8 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_accept (JNIEnv* jenv, jobject jcl, jlong sslPtr, jint timeout) { int ret = 0, err, sockfd; + int pollRx = 0; + int pollTx = 0; wolfSSL_Mutex* jniSessLock = NULL; SSLAppData* appData = NULL; WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; @@ -983,12 +1167,33 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_accept break; } - ret = socketSelect(sockfd, (int)timeout, 1); - if (ret == WOLFJNI_RECV_READY || ret == WOLFJNI_SEND_READY) { - /* I/O ready, continue handshake and try again */ + if (err == SSL_ERROR_WANT_READ) { + pollRx = 1; + } + else if (err == SSL_ERROR_WANT_WRITE) { + pollTx = 1; + } + + #if defined(WOLFJNI_USE_IO_SELECT) || defined(USE_WINDOWS_API) + ret = socketSelect(sockfd, (int)timeout, pollRx); + #else + ret = socketPoll(sockfd, (int)timeout, pollRx, pollTx); + #endif + if ((ret == WOLFJNI_IO_EVENT_RECV_READY) || + (ret == WOLFJNI_IO_EVENT_SEND_READY)) { + /* loop around and try wolfSSL_accept() again */ continue; + } else if (ret == WOLFJNI_IO_EVENT_TIMEOUT || + ret == WOLFJNI_IO_EVENT_FD_CLOSED || + ret == WOLFJNI_IO_EVENT_ERROR || + ret == WOLFJNI_IO_EVENT_POLLHUP || + ret == WOLFJNI_IO_EVENT_FAIL) { + /* Java will throw SocketTimeoutException or + * SocketException */ + break; } else { - /* error or timeout */ + /* other error occurred */ + ret = SSL_FAILURE; break; } } @@ -1156,6 +1361,8 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_shutdownSSL (JNIEnv* jenv, jobject jcl, jlong sslPtr, jint timeout) { int ret = 0, err, sockfd; + int pollRx = 0; + int pollTx = 0; wolfSSL_Mutex* jniSessLock; SSLAppData* appData = NULL; WOLFSSL* ssl = (WOLFSSL*)(uintptr_t)sslPtr; @@ -1209,12 +1416,33 @@ JNIEXPORT jint JNICALL Java_com_wolfssl_WolfSSLSession_shutdownSSL break; } - ret = socketSelect(sockfd, timeout, 1); - if (ret == WOLFJNI_RECV_READY || ret == WOLFJNI_SEND_READY) { - /* I/O ready, continue handshake and try again */ + if (err == SSL_ERROR_WANT_READ) { + pollRx = 1; + } + else if (err == SSL_ERROR_WANT_WRITE) { + pollTx = 1; + } + + #if defined(WOLFJNI_USE_IO_SELECT) || defined(USE_WINDOWS_API) + ret = socketSelect(sockfd, (int)timeout, pollRx); + #else + ret = socketPoll(sockfd, (int)timeout, pollRx, pollTx); + #endif + if ((ret == WOLFJNI_IO_EVENT_RECV_READY) || + (ret == WOLFJNI_IO_EVENT_SEND_READY)) { + /* loop around and try wolfSSL_shutdown() again */ continue; + } else if (ret == WOLFJNI_IO_EVENT_TIMEOUT || + ret == WOLFJNI_IO_EVENT_FD_CLOSED || + ret == WOLFJNI_IO_EVENT_ERROR || + ret == WOLFJNI_IO_EVENT_POLLHUP || + ret == WOLFJNI_IO_EVENT_FAIL) { + /* Java will throw SocketTimeoutException or + * SocketException */ + break; } else { - /* error or timeout */ + /* other error occurred */ + ret = SSL_FAILURE; break; } } @@ -1401,9 +1629,16 @@ JNIEXPORT jlong JNICALL Java_com_wolfssl_WolfSSLSession_get1Session break; } + #if defined(WOLFJNI_USE_IO_SELECT) || defined(USE_WINDOWS_API) + /* Default to select() on Windows or if WOLFJNI_USE_IO_SELECT */ ret = socketSelect(sockfd, (int)WOLFSSL_JNI_DEFAULT_PEEK_TIMEOUT, 1); - if (ret == WOLFJNI_RECV_READY || ret == WOLFJNI_SEND_READY) { + #else + ret = socketPoll(sockfd, + (int)WOLFSSL_JNI_DEFAULT_PEEK_TIMEOUT, 1, 0); + #endif + if ((ret == WOLFJNI_IO_EVENT_RECV_READY) || + (ret == WOLFJNI_IO_EVENT_SEND_READY)) { /* I/O ready, continue handshake and try again */ continue; } else { diff --git a/src/java/com/wolfssl/WolfSSL.java b/src/java/com/wolfssl/WolfSSL.java index 1d0fcf66..17b35196 100644 --- a/src/java/com/wolfssl/WolfSSL.java +++ b/src/java/com/wolfssl/WolfSSL.java @@ -54,9 +54,36 @@ public enum TLS_VERSION { public static final int JNI_SESSION_UNAVAILABLE = -10001; /** - * Socket timed out, matches com_wolfssl_WolfSSLSession.c socketSelect() - * return value */ - public static final int WOLFJNI_TIMEOUT = -11; + * Socket select/poll() failed, matches com_wolfssl_WolfSSLSession.c + * socketSelect() and socketPoll() return value. + */ + public static final int WOLFJNI_IO_EVENT_FAIL = -10; + + /** + * Socket timed out, matches com_wolfssl_WolfSSLSession.c + * socketSelect() and socketPoll() return value. + */ + public static final int WOLFJNI_IO_EVENT_TIMEOUT = -11; + + /** + * Socket poll() exceptional error, matches com_wolfssl_WolfSSLSession.c + * socketPoll() return value */ + public static final int WOLFJNI_IO_EVENT_ERROR = -14; + + /** + * Socket file descriptor closed, matches com_wolfssl_WolfSSLSession.c + * socketPoll() return value */ + public static final int WOLFJNI_IO_EVENT_FD_CLOSED = -15; + + /** + * Socket disconnected during poll(), matches + * com_wolfssl_WolfSSLSession.c socketPoll() return value */ + public static final int WOLFJNI_IO_EVENT_POLLHUP = -16; + + /** + * Socket invalid timeout during poll/select(), matches + * com_wolfssl_WolfSSLSession.c socketPoll/socketSelect() return value */ + public static final int WOLFJNI_IO_EVENT_INVALID_TIMEOUT = -17; /* ----------------------- wolfSSL codes ---------------------------- */ diff --git a/src/java/com/wolfssl/WolfSSLSession.java b/src/java/com/wolfssl/WolfSSLSession.java index f3f08c23..9d22e4f9 100644 --- a/src/java/com/wolfssl/WolfSSLSession.java +++ b/src/java/com/wolfssl/WolfSSLSession.java @@ -94,11 +94,6 @@ public class WolfSSLSession { /* lock around native WOLFSSL pointer use */ private final Object sslLock = new Object(); - /* return values from naitve socketSelect(), should match - * ones in native/com_wolfssl_WolfSSLSession.c */ - private int WOLFJNI_SELECT_FAIL = -10; - private int WOLFJNI_TIMEOUT = -11; - /* SNI requested by this WolfSSLSession if client side and useSNI() * was called successfully. */ private byte[] clientSNIRequested = null; @@ -567,6 +562,36 @@ public int getFd() } } + /** + * Helper method to throw appropriate exception based on native + * result of poll()/select() from API that does I/O. + */ + private static void throwExceptionFromIOReturnValue( + int ret, String nativeFunc) + throws SocketTimeoutException, SocketException { + + if (ret == WolfSSL.WOLFJNI_IO_EVENT_TIMEOUT) { + throw new SocketTimeoutException( + "Native socket timed out during " + nativeFunc); + } + else if (ret == WolfSSL.WOLFJNI_IO_EVENT_FD_CLOSED) { + throw new SocketException("Socket fd closed during poll(), " + + "errno = " + WolfSSL.getErrno()); + } + else if (ret == WolfSSL.WOLFJNI_IO_EVENT_ERROR) { + throw new SocketException("Socket fd poll() exceptional error, " + + "errno = " + WolfSSL.getErrno()); + } + else if (ret == WolfSSL.WOLFJNI_IO_EVENT_POLLHUP) { + throw new SocketException("Socket disconnected during poll(), " + + "errno = " + WolfSSL.getErrno()); + } + else if (ret == WolfSSL.WOLFJNI_IO_EVENT_FAIL) { + throw new SocketException("Socket select/poll() failed, " + + "errno = " + WolfSSL.getErrno()); + } + } + /** * Initializes an SSL/TLS handshake with a server. * This function is called on the client side. When called, the underlying @@ -602,29 +627,12 @@ public int getFd() * a more detailed error code, call getError(). * @throws IllegalStateException WolfSSLContext has been freed * @throws SocketTimeoutException if underlying socket timed out - * @throws SocketException Native socket select() failed + * @throws SocketException Native socket select() or poll() failed */ public int connect() throws IllegalStateException, SocketTimeoutException, SocketException { - int ret = 0; - - confirmObjectIsActive(); - - synchronized (sslLock) { - ret = connect(this.sslPtr, 0); - } - - if (ret == WolfSSL.WOLFJNI_TIMEOUT) { - throw new SocketTimeoutException( - "Native socket timed out during SSL_connect()"); - } - else if (ret == WOLFJNI_SELECT_FAIL) { - throw new SocketException("Socket select() failed, errno = " + - WolfSSL.getErrno()); - } - - return ret; + return connect(0); } /** @@ -657,14 +665,15 @@ else if (ret == WOLFJNI_SELECT_FAIL) { *

* before calling newSSL(), though it's not recommended. * - * @param timeout read timeout, milliseconds. + * @param timeout read timeout, milliseconds. Specify 0 to use infinite + * timeout * * @return SSL_SUCCESS if successful, otherwise - * SSL_FATAL_ERROR if an error occurred. To get + * SSL_FAILURE if an error occurred. To get * a more detailed error code, call getError(). * @throws IllegalStateException WolfSSLContext has been freed * @throws SocketTimeoutException if socket timeout occurs - * @throws SocketException Native socket select() failed + * @throws SocketException Native socket select() or poll() failed */ public int connect(int timeout) throws IllegalStateException, SocketTimeoutException, SocketException { @@ -677,13 +686,7 @@ public int connect(int timeout) ret = connect(this.sslPtr, timeout); } - if (ret == WOLFJNI_TIMEOUT) { - throw new SocketTimeoutException("Socket connect timeout"); - } - else if (ret == WOLFJNI_SELECT_FAIL) { - throw new SocketException("Socket select() failed, errno = " + - WolfSSL.getErrno()); - } + throwExceptionFromIOReturnValue(ret, "wolfSSL_connect()"); return ret; } @@ -710,7 +713,7 @@ else if (ret == WOLFJNI_SELECT_FAIL) { * @param length size, in bytes, of data to send to the peer * @return the number of bytes written upon success. 0 * or a negative value will be returned upon failure. - * SSL_FATAL_ERRORreturn upon failure when either + * SSL_FAILUREreturn upon failure when either * an error occurred or, when using non-blocking sockets, * the SSL_ERROR_WANT_READ or * SSL_ERROR_WANT_WRITE error was received and the @@ -718,10 +721,11 @@ else if (ret == WOLFJNI_SELECT_FAIL) { * BAD_FUNC_ARC when bad arguments are used. * Use getError to get a specific error code. * @throws IllegalStateException WolfSSLContext has been freed - * @throws SocketException Native socket select() failed + * @throws SocketTimeoutException if socket timeout occurs + * @throws SocketException Native socket select() or poll() failed */ public int write(byte[] data, int length) - throws IllegalStateException, SocketException { + throws IllegalStateException, SocketTimeoutException, SocketException { int ret; long localPtr; @@ -741,10 +745,7 @@ public int write(byte[] data, int length) * occur if needed */ ret = write(localPtr, data, 0, length, 0); - if (ret == WOLFJNI_SELECT_FAIL) { - throw new SocketException("Socket select() failed, errno = " + - WolfSSL.getErrno()); - } + throwExceptionFromIOReturnValue(ret, "wolfSSL_write()"); return ret; } @@ -782,7 +783,7 @@ public int write(byte[] data, int length) * Use getError to get a specific error code. * @throws IllegalStateException WolfSSLContext has been freed * @throws SocketTimeoutException if socket timeout occurs - * @throws SocketException Native socket select() failed + * @throws SocketException Native socket select/poll() failed */ public int write(byte[] data, int length, int timeout) throws IllegalStateException, SocketTimeoutException, SocketException { @@ -826,7 +827,7 @@ public int write(byte[] data, int length, int timeout) * Use getError to get a specific error code. * @throws IllegalStateException WolfSSLContext has been freed * @throws SocketTimeoutException if socket timeout occurs - * @throws SocketException Native socket select() failed + * @throws SocketException Native socket select/poll() failed */ public int write(byte[] data, int offset, int length, int timeout) throws IllegalStateException, SocketTimeoutException, SocketException { @@ -849,13 +850,7 @@ public int write(byte[] data, int offset, int length, int timeout) * occur if needed */ ret = write(localPtr, data, offset, length, timeout); - if (ret == WOLFJNI_TIMEOUT) { - throw new SocketTimeoutException("Socket write timeout"); - } - else if (ret == WOLFJNI_SELECT_FAIL) { - throw new SocketException("Socket select() failed, errno = " + - WolfSSL.getErrno()); - } + throwExceptionFromIOReturnValue(ret, "wolfSSL_write()"); return ret; } @@ -893,10 +888,12 @@ else if (ret == WOLFJNI_SELECT_FAIL) { * get a specific error code. * BAD_FUNC_ARC when bad arguments are used. * @throws IllegalStateException WolfSSLContext has been freed - * @throws SocketException Native socket select() failed + * @throws SocketTimeoutException if socket timeout occurs, should not + * occur since infinite timeout is used for this call. + * @throws SocketException Native socket select/poll() failed */ public int read(byte[] data, int sz) - throws IllegalStateException, SocketException { + throws IllegalStateException, SocketTimeoutException, SocketException { int ret; long localPtr; @@ -916,10 +913,7 @@ public int read(byte[] data, int sz) * occur if needed */ ret = read(localPtr, data, 0, sz, 0); - if (ret == WOLFJNI_SELECT_FAIL) { - throw new SocketException("Socket select() failed, errno = " + - WolfSSL.getErrno()); - } + throwExceptionFromIOReturnValue(ret, "wolfSSL_read()"); return ret; } @@ -959,7 +953,7 @@ public int read(byte[] data, int sz) * BAD_FUNC_ARC when bad arguments are used. * @throws IllegalStateException WolfSSLContext has been freed * @throws SocketTimeoutException if socket timeout occurs - * @throws SocketException Native socket select() failed + * @throws SocketException Native socket select/poll() failed */ public int read(byte[] data, int sz, int timeout) throws IllegalStateException, SocketTimeoutException, SocketException { @@ -1005,7 +999,7 @@ public int read(byte[] data, int sz, int timeout) * BAD_FUNC_ARC when bad arguments are used. * @throws IllegalStateException WolfSSLContext has been freed * @throws SocketTimeoutException if socket timeout occurs - * @throws SocketException Native socket select() failed + * @throws SocketException Native socket select/poll() failed */ public int read(byte[] data, int offset, int sz, int timeout) throws IllegalStateException, SocketTimeoutException, SocketException { @@ -1028,13 +1022,7 @@ public int read(byte[] data, int offset, int sz, int timeout) * occur if needed */ ret = read(localPtr, data, offset, sz, timeout); - if (ret == WOLFJNI_TIMEOUT) { - throw new SocketTimeoutException("Socket read timeout"); - } - else if (ret == WOLFJNI_SELECT_FAIL) { - throw new SocketException("Socket select() failed, errno = " + - WolfSSL.getErrno()); - } + throwExceptionFromIOReturnValue(ret, "wolfSSL_read()"); return ret; } @@ -1063,31 +1051,14 @@ else if (ret == WOLFJNI_SELECT_FAIL) { * error code, call getError(). * @throws IllegalStateException WolfSSLContext has been freed * @throws SocketTimeoutException if underlying socket timed out - * @throws SocketException Native socket select() failed + * @throws SocketException Native socket select/accept() failed * @see #getError(int) * @see #connect() */ public int accept() throws IllegalStateException, SocketTimeoutException, SocketException { - int ret; - - confirmObjectIsActive(); - - synchronized (sslLock) { - ret = accept(this.sslPtr, 0); - } - - if (ret == WolfSSL.WOLFJNI_TIMEOUT) { - throw new SocketTimeoutException( - "Native socket timed out during SSL_accept()"); - } - else if (ret == WOLFJNI_SELECT_FAIL) { - throw new SocketException("Socket select() failed, errno = " + - WolfSSL.getErrno()); - } - - return ret; + return accept(0); } /** @@ -1132,14 +1103,7 @@ public int accept(int timeout) ret = accept(this.sslPtr, timeout); } - if (ret == WolfSSL.WOLFJNI_TIMEOUT) { - throw new SocketTimeoutException( - "Native socket timed out during SSL_accept()"); - } - else if (ret == WOLFJNI_SELECT_FAIL) { - throw new SocketException("Socket select() failed, errno = " + - WolfSSL.getErrno()); - } + throwExceptionFromIOReturnValue(ret, "wolfSSL_accept()"); return ret; } @@ -1199,28 +1163,17 @@ public synchronized void freeSSL() * SSL_FATAL_ERROR upon failure. Call * getError() for a more specific error code. * @throws IllegalStateException WolfSSLContext has been freed - * @throws SocketException Native socket select() failed + * @throws SocketTimeoutException if socket timeout occurs, should not + * since infinite timeout is used for this call. + * @throws SocketException Native socket select/poll() failed * @see #shutdownSSL(int) * @see #freeSSL(long) * @see WolfSSLContext#free() */ public int shutdownSSL() - throws IllegalStateException, SocketException { - - int ret; - - confirmObjectIsActive(); - - synchronized (sslLock) { - ret = shutdownSSL(this.sslPtr, 0); - } - - if (ret == WOLFJNI_SELECT_FAIL) { - throw new SocketException("Socket select() failed, errno = " + - WolfSSL.getErrno()); - } + throws IllegalStateException, SocketTimeoutException, SocketException { - return ret; + return shutdownSSL(0); } /** @@ -1250,8 +1203,8 @@ public int shutdownSSL() * SSL_FATAL_ERROR upon failure. Call * getError() for a more specific error code. * @throws IllegalStateException WolfSSLContext has been freed - * @throws SocketTimeoutException if read timeout occurs. - * @throws SocketException Native socket select() failed + * @throws SocketTimeoutException if socket timeout occurs. + * @throws SocketException Native socket select/poll() failed * @see #freeSSL(long) * @see WolfSSLContext#free() */ @@ -1266,13 +1219,7 @@ public int shutdownSSL(int timeout) ret = shutdownSSL(this.sslPtr, timeout); } - if (ret == WOLFJNI_TIMEOUT) { - throw new SocketTimeoutException("Socket read timeout"); - } - else if (ret == WOLFJNI_SELECT_FAIL) { - throw new SocketException("Socket select() failed, errno = " + - WolfSSL.getErrno()); - } + throwExceptionFromIOReturnValue(ret, "wolfSSL_shutdown()"); return ret; } diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java b/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java index 2dd53c5f..9642aaaa 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLEngine.java @@ -353,8 +353,11 @@ private synchronized void UpdateCloseNotifyStatus() { * * @return WolfSSL.SSL_SUCCESS on success, zero or negative on error * @throws SocketException if ssl.shutdownSSL() encounters a socket error + * @throws SocketTimeoutException if ssl.shutdownSSL() times out */ - private synchronized int ClosingConnection() throws SocketException { + private synchronized int ClosingConnection() + throws SocketException, SocketTimeoutException { + int ret; /* Save session into WolfSSLAuthStore cache, saves session @@ -395,7 +398,13 @@ private synchronized int ClosingConnection() throws SocketException { /** * Starts or continues SSL/TLS handshake. - * Returns WolfSSL.SSL_SUCCESS or WolfSSL.SSL_FAILURE + * + * @return WolfSSL.SSL_SUCCESS or WolfSSL.SSL_FAILURE + * @throws SocketException if ssl.connect() or ssl.accept() encounters + * a socket exception. + * @throws SocketTimeoutException if ssl.connect() or ssl.accept() + * times out. This should not happen since infinite timeout is + * being used for these calls. */ private synchronized int DoHandshake() throws SSLException { int ret = WolfSSL.SSL_SUCCESS; @@ -434,10 +443,12 @@ private synchronized int DoHandshake() throws SSLException { * (SSLSession.getApplicationBufferSize()). * * @throws SocketException if ssl.write() encounters a socket error + * @throws SocketTimeoutException if ssl.write() times out. Shouldn't + * happen since this is using an infinite timeout. * @return bytes sent on success, negative on error */ private synchronized int SendAppData(ByteBuffer[] in, int ofst, int len) - throws SocketException { + throws SocketException, SocketTimeoutException { int i = 0; int ret = 0; @@ -629,7 +640,7 @@ public synchronized SSLEngineResult wrap(ByteBuffer[] in, int ofst, int len, try { ClosingConnection(); - } catch (SocketException e) { + } catch (SocketException | SocketTimeoutException e) { throw new SSLException(e); } produced += CopyOutPacket(out); @@ -653,7 +664,7 @@ else if (produced == 0) { if (ret > 0) { consumed += ret; } - } catch (SocketException e) { + } catch (SocketException | SocketTimeoutException e) { throw new SSLException(e); } } @@ -781,7 +792,7 @@ private synchronized int RecvAppData(ByteBuffer[] out, int ofst, int length) synchronized (ioLock) { try { ret = this.ssl.read(tmp, maxOutSz); - } catch (SocketException e) { + } catch (SocketTimeoutException | SocketException e) { throw new SSLException(e); } WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, @@ -821,7 +832,8 @@ private synchronized int RecvAppData(ByteBuffer[] out, int ofst, int length) * 0, or err */ ret = 0; } - } catch (SocketException e) { + } catch (SocketException | + SocketTimeoutException e) { throw new SSLException(e); } return ret; @@ -998,7 +1010,7 @@ else if (hs == SSLEngineResult.HandshakeStatus.NEED_WRAP && * release, global JNI verify callback pointer */ this.engineHelper.unsetVerifyCallback(); } - } catch (SocketException e) { + } catch (SocketException | SocketTimeoutException e) { throw new SSLException(e); } } diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java b/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java index 9109fa67..50fc54c5 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLEngineHelper.java @@ -1220,7 +1220,7 @@ private void initHandshakeInternal(SSLSocket socket, SSLEngine engine) /* TODO: SunJSSE sends a Handshake Failure alert instead here */ try { this.ssl.shutdownSSL(); - } catch (SocketException e) { + } catch (SocketException | SocketTimeoutException e) { throw new SSLException(e); } @@ -1336,7 +1336,8 @@ else if (peerAddr != null) { } catch (SocketException e) { /* SocketException may be thrown if native socket - * select() fails. Propogate errno back inside exception. */ + * select/poll() fails. Propogate errno back inside new + * SSLException. */ throw new SSLException(e); } diff --git a/src/java/com/wolfssl/provider/jsse/WolfSSLSocket.java b/src/java/com/wolfssl/provider/jsse/WolfSSLSocket.java index c2b1a001..93cf19ec 100644 --- a/src/java/com/wolfssl/provider/jsse/WolfSSLSocket.java +++ b/src/java/com/wolfssl/provider/jsse/WolfSSLSocket.java @@ -943,7 +943,7 @@ private void setFd() throws IllegalArgumentException, WolfSSLException { "Failed to set native Socket fd"); } WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - "registered SSLSocket with native wolfSSL"); + "registered SSLSocket(this) with native wolfSSL"); } else { ret = ssl.setFd(this.socket); @@ -962,7 +962,7 @@ private void setFd() throws IllegalArgumentException, WolfSSLException { } else { WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - "registered Socket with native wolfSSL"); + "registered Socket(this.socket) with native wolfSSL"); } } } @@ -1888,14 +1888,23 @@ public synchronized void close() throws IOException { } } - if (this.socket != null) { - ret = ssl.shutdownSSL(this.socket.getSoTimeout()); - } else { - ret = ssl.shutdownSSL(super.getSoTimeout()); - } + try { + if (this.socket != null) { + ret = ssl.shutdownSSL( + this.socket.getSoTimeout()); + } else { + ret = ssl.shutdownSSL( + super.getSoTimeout()); + } - WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - "ssl.shutdownSSL() ret = " + ret); + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "ssl.shutdownSSL() ret = " + ret); + + } catch (SocketException | SocketTimeoutException e) { + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "Exception while trying to ssl.shutdownSSL(), " + + "ignoring to finish cleanup"); + } WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, "thread trying to get handshakeLock"); @@ -2052,7 +2061,8 @@ public synchronized void connect(SocketAddress endpoint, int timeout) InetSocketAddress address = null; WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, - "entered connect(SocketAddress endpoint, int timeout)"); + "entered connect(SocketAddress endpoint, int timeout / " + + timeout + " ms)"); if (!(endpoint instanceof InetSocketAddress)) { throw new IllegalArgumentException("endpoint is not of type " + @@ -2066,6 +2076,8 @@ public synchronized void connect(SocketAddress endpoint, int timeout) } address = (InetSocketAddress)endpoint; + WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO, + "Underlying Java Socket connected to peer: " + address); /* register host/port for session resumption in case where createSocket() was called without host/port, but @@ -2510,6 +2522,16 @@ public synchronized int read(byte[] b, int off, int len) " (error code: " + err + ")"); } + } catch (SocketException e) { + /* ssl.read() can throw SocketException from poll() if fd + * closed or peer shut down connection */ + if (e.getMessage().contains("fd closed during poll") || + e.getMessage().contains("disconnected during poll")) { + /* end of stream */ + return -1; + } + throw e; + } catch (IllegalStateException e) { throw new IOException(e); }