diff --git a/vio/vio.cc b/vio/vio.cc --- a/vio/vio.cc +++ b/vio/vio.cc @@ -165,6 +165,7 @@ vio->is_blocking =vio_is_blocking; vio->set_blocking =vio_set_blocking; vio->is_blocking_flag = true; + vio->is_blocking_func = true; return false; } if (type == VIO_TYPE_SHARED_MEMORY) @@ -185,6 +186,7 @@ vio->is_blocking =vio_is_blocking; vio->set_blocking =vio_set_blocking; vio->is_blocking_flag = true; + vio->is_blocking_func = true; return false; } #endif /* _WIN32 */ @@ -206,8 +208,9 @@ vio->has_data =vio_ssl_has_data; vio->timeout =vio_socket_timeout; vio->is_blocking =vio_is_blocking; - vio->set_blocking =vio_set_blocking; + vio->set_blocking =vio_ssl_set_blocking; vio->is_blocking_flag = true; + vio->is_blocking_func = true; return false; } #endif /* HAVE_OPENSSL */ @@ -229,6 +232,7 @@ vio->set_blocking =vio_set_blocking; vio->is_blocking_flag = true; + vio->is_blocking_func = true; return false; } diff --git a/vio/viosocket.cc b/vio/viosocket.cc --- a/vio/viosocket.cc +++ b/vio/viosocket.cc @@ -292,6 +292,15 @@ DBUG_RETURN(0); } +int vio_ssl_set_blocking(Vio *vio, bool status) +{ + DBUG_ENTER(__func__); + int ret; + ret = vio_set_blocking(vio, false); + vio->is_blocking_func = status; + DBUG_RETURN(ret); +} + bool vio_is_blocking(Vio * vio) { diff --git a/vio/viossl.cc b/vio/viossl.cc --- a/vio/viossl.cc +++ b/vio/viossl.cc @@ -204,6 +204,15 @@ if (!ssl_should_retry(vio, ret, &event, &ssl_errno_not_used)) break; + if (!vio->is_blocking_func) { + socket_errno = SOCKET_EWOULDBLOCK; + switch (event) { + case VIO_IO_EVENT_READ: DBUG_RETURN(VIO_SOCKET_WANT_READ); + case VIO_IO_EVENT_WRITE: DBUG_RETURN(VIO_SOCKET_WANT_WRITE); + default: DBUG_RETURN(VIO_SOCKET_ERROR); + } + } + /* Attempt to wait for an I/O event. */ if (vio_socket_io_wait(vio, event)) break; @@ -245,6 +254,15 @@ if (!ssl_should_retry(vio, ret, &event, &ssl_errno_not_used)) break; + if (!vio->is_blocking_func) { + socket_errno = SOCKET_EWOULDBLOCK; + switch (event) { + case VIO_IO_EVENT_READ: DBUG_RETURN(VIO_SOCKET_WANT_READ); + case VIO_IO_EVENT_WRITE: DBUG_RETURN(VIO_SOCKET_WANT_WRITE); + default: DBUG_RETURN(VIO_SOCKET_ERROR); + } + } + /* Attempt to wait for an I/O event. */ if (vio_socket_io_wait(vio, event)) break; @@ -392,6 +410,12 @@ if (!ssl_should_retry(vio, ret, &event, ssl_errno_holder)) break; + if (!vio->is_blocking_func) { + socket_errno = SOCKET_EWOULDBLOCK; + DBUG_ASSERT(false); + return -1; + } + /* Wait for I/O so that the handshake can proceed. */ if (vio_socket_io_wait(vio, event)) break; diff --git a/client/mysqltest.cc b/client/mysqltest.cc --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -6874,7 +6874,7 @@ opt_protocol= MYSQL_PROTOCOL_PIPE; } - if (con_ssl || (opt_compress || con_compress)) + if (opt_compress || con_compress) { enable_async_client = false; } @@ -10173,7 +10173,7 @@ #endif SSL_SET_OPTIONS(&con->mysql); - if ((opt_ssl_ca || opt_ssl_capath) || opt_compress) + if (opt_compress) { enable_async_client = false; } diff --git a/include/violite.h b/include/violite.h --- a/include/violite.h +++ b/include/violite.h @@ -127,6 +127,10 @@ VIO_IO_EVENT_CONNECT }; +#define VIO_SOCKET_ERROR ((size_t) -1) +#define VIO_SOCKET_WANT_READ ((size_t) -2) +#define VIO_SOCKET_WANT_WRITE ((size_t) -3) + #define VIO_LOCALHOST 1 /* a localhost connection */ #define VIO_BUFFERED_READ 2 /* use buffered read */ #define VIO_READ_BUFFER_SIZE 16384 /* size of read buffer */ @@ -153,6 +157,7 @@ my_socket sd, void *ssl, uint flags); bool vio_is_blocking(Vio* vio); int vio_set_blocking(Vio * vio, bool set_blocking_mode); +int vio_ssl_set_blocking(Vio * vio, bool set_blocking_mode); size_t vio_read(MYSQL_VIO vio, uchar * buf, size_t size); size_t vio_read_buff(MYSQL_VIO vio, uchar * buf, size_t size); size_t vio_write(MYSQL_VIO vio, const uchar * buf, size_t size); @@ -381,7 +386,15 @@ #endif /* _WIN32 */ bool (*is_blocking)(Vio* vio); int (*set_blocking)(Vio* vio, bool val); + /* Indicates whether socket is blocking or not. */ bool is_blocking_flag; + /* + Indicates whether a nonblocking function is called. This is not + necessarily in sync with is_blocking_flag because for SSL, the socket is + always set to nonblocking and is_blocking_func is used to determine + whether or not to wait. + */ + bool is_blocking_func; private: friend st_vio *internal_vio_create(uint flags); diff --git a/mysql-test/r/ssl-big-packet.result b/mysql-test/r/ssl-big-packet.result new file mode 100644 --- /dev/null +++ b/mysql-test/r/ssl-big-packet.result @@ -0,0 +1,6 @@ +MD5("zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +2d1979bcedb242e29c5b2e607955bd24 +LENGTH("zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +17000016 +Q zzzzzzzzzzzzzzzz<17mb of X's> +Q zzzzzzzzzzzzzzzz<17mb of X's> END diff --git a/mysql-test/t/ssl-big-packet-master.opt b/mysql-test/t/ssl-big-packet-master.opt new file mode 100644 --- /dev/null +++ b/mysql-test/t/ssl-big-packet-master.opt @@ -0,0 +1,5 @@ +--ssl=1 +--ssl-ca=$MYSQL_TEST_DIR/std_data/cacert.pem +--ssl-cert=$MYSQL_TEST_DIR/std_data/server-cert.pem +--ssl-key=$MYSQL_TEST_DIR/std_data/server-key.pem +--max_allowed_packet=128M diff --git a/mysql-test/t/ssl-big-packet.test b/mysql-test/t/ssl-big-packet.test new file mode 100644 --- /dev/null +++ b/mysql-test/t/ssl-big-packet.test @@ -0,0 +1,27 @@ +# Turn on ssl between the client and server and run some big queries + +-- source include/have_ssl.inc +-- source include/big_test.inc + +# Save the initial number of concurrent sessions +--source include/count_sessions.inc + +connect (ssl_con,localhost,root,,,,,SSL); + +let $str = `SELECT REPEAT('X', 17000000)`; +let $str = zzzzzzzzzzzzzzzz$str; + +--disable_query_log + +--eval SELECT MD5("$str") +--eval SELECT LENGTH("$str") +--replace_regex /X+/<17mb of X's>/ +--eval SELECT "Q $str END" + +connection default; +disconnect ssl_con; + +--enable_query_log + +# Wait till all disconnects are completed +--source include/wait_until_count_sessions.inc diff --git a/sql-common/net_serv.cc b/sql-common/net_serv.cc --- a/sql-common/net_serv.cc +++ b/sql-common/net_serv.cc @@ -75,8 +75,6 @@ #include "mysql_com_server.h" #endif -#define VIO_SOCKET_ERROR ((size_t) -1) - static bool net_write_buff(NET *, const uchar *, size_t); /** Init with packet info. */ @@ -570,29 +568,63 @@ vio_set_blocking(net->vio, false); } - *res = writev(net->fd, vec, - net->async_write_vector_size - net->async_write_vector_current); + // If OpenSSL is being used, vio_write will end up calling SSL_write. Since + // there is no SSL writev() equivalent, call vio_write multiple times to + // consume the io vector. + if (net->vio->ssl_arg != nullptr) { + while (net->async_write_vector_current != net->async_write_vector_size) { + *res = vio_write(net->vio, (uchar*)vec->iov_base, vec->iov_len); + + if (*res < 0) { + if (errno == SOCKET_EAGAIN || + errno == SOCKET_EWOULDBLOCK) { + // In the unlikely event that there is a renegotiation and + // SSL_ERROR_WANT_READ is returned, set blocking state to read. + if (static_cast(*res) == VIO_SOCKET_WANT_READ) { + net->async_blocking_state = NET_NONBLOCKING_READ; + } else { + net->async_blocking_state = NET_NONBLOCKING_WRITE; + } + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + DBUG_RETURN(NET_ASYNC_COMPLETE); + } - if (*res < 0) { - if (errno == SOCKET_EAGAIN || - errno == SOCKET_EWOULDBLOCK) { - net->async_blocking_state = NET_NONBLOCKING_WRITE; - DBUG_RETURN(NET_ASYNC_NOT_READY); + size_t bytes_written = static_cast(*res); + vec->iov_len -= bytes_written; + vec->iov_base = (char*)vec->iov_base + bytes_written; + + if (vec->iov_len != 0) + break; + + ++net->async_write_vector_current; + vec++; } - DBUG_RETURN(NET_ASYNC_COMPLETE); - } + } else { + *res = writev(net->fd, vec, + net->async_write_vector_size - net->async_write_vector_current); - size_t bytes_written = static_cast(*res); - while (bytes_written > vec->iov_len) { - ++net->async_write_vector_current; - bytes_written -= vec->iov_len; - ++vec; - } + if (*res < 0) { + if (errno == SOCKET_EAGAIN || + errno == SOCKET_EWOULDBLOCK) { + net->async_blocking_state = NET_NONBLOCKING_WRITE; + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + size_t bytes_written = static_cast(*res); + while (bytes_written > vec->iov_len) { + ++net->async_write_vector_current; + bytes_written -= vec->iov_len; + ++vec; + } - vec->iov_len -= bytes_written; - vec->iov_base = (char*)vec->iov_base + bytes_written; - if (vec->iov_len == 0) { - ++net->async_write_vector_current; + vec->iov_len -= bytes_written; + vec->iov_base = (char*)vec->iov_base + bytes_written; + if (vec->iov_len == 0) { + ++net->async_write_vector_current; + } } if (net->async_write_vector_current == net->async_write_vector_size) { @@ -639,7 +671,6 @@ goto done; } - net->async_blocking_state = NET_NONBLOCKING_WRITE; DBUG_RETURN(NET_ASYNC_NOT_READY); case NET_ASYNC_OP_COMPLETE: @@ -1127,6 +1158,10 @@ Returns packet_error (-1) on EOF or other errors, 0 if the read would block, and otherwise the number of bytes read (which may be less than the requested amount). + + When 0 is returned the async_blocking_state is set inside this function. + With SSL, the async blocking state can also become NET_NONBLOCKING_WRITE + (when renegotiation occurs). */ static ulong net_read_available(NET *net, size_t count) @@ -1142,10 +1177,26 @@ recvcnt= vio_read(net->vio, net->cur_pos, count); + // When OpenSSL is used in non-blocking mode, it is possible that an + // SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE error is returned after a + // SSL_read() operation (if a renegotiation takes place). + // We are treating this case here and signaling correctly the next expected + // operation in the async_blocking_state. + if (socket_errno == SOCKET_EWOULDBLOCK) { + if (recvcnt == VIO_SOCKET_WANT_READ) { + net->async_blocking_state = NET_NONBLOCKING_READ; + DBUG_RETURN(0); + } else if (recvcnt == VIO_SOCKET_WANT_WRITE) { + net->async_blocking_state = NET_NONBLOCKING_WRITE; + DBUG_RETURN(0); + } + } + /* Call would block, just return with socket_errno set */ if (recvcnt == VIO_SOCKET_ERROR && (socket_errno == SOCKET_EAGAIN || socket_errno == SOCKET_EWOULDBLOCK)) { + net->async_blocking_state = NET_NONBLOCKING_READ; DBUG_RETURN(0); } @@ -1191,7 +1242,6 @@ bytes_read = (size_t) rc; net->async_bytes_wanted -= bytes_read; if (net->async_bytes_wanted != 0) { - net->async_blocking_state = NET_NONBLOCKING_READ; DBUG_PRINT("partial read", ("wanted/remaining: %lu, %lu", count, net->async_bytes_wanted)); DBUG_RETURN(NET_ASYNC_NOT_READY); @@ -1220,7 +1270,6 @@ uchar pkt_nr; if (net_read_data_nonblocking(net, NET_HEADER_SIZE, err_ptr) == NET_ASYNC_NOT_READY) { - net->async_blocking_state = NET_NONBLOCKING_READ; DBUG_RETURN(NET_ASYNC_NOT_READY); } if (*err_ptr) { @@ -1279,7 +1328,6 @@ case NET_ASYNC_PACKET_READ_HEADER: if (net_read_packet_header_nonblock(net, &err) == NET_ASYNC_NOT_READY) { - net->async_blocking_state = NET_NONBLOCKING_READ; DBUG_RETURN(NET_ASYNC_NOT_READY); } /* Retrieve packet length and number. */ @@ -1309,7 +1357,6 @@ if (net_read_data_nonblocking( net, net->async_packet_length, &err) == NET_ASYNC_NOT_READY) { - net->async_blocking_state = NET_NONBLOCKING_READ; DBUG_RETURN(NET_ASYNC_NOT_READY); } @@ -1415,7 +1462,6 @@ { if (net_read_packet_nonblocking(net, len_ptr, complen_ptr) == NET_ASYNC_NOT_READY) { - net->async_blocking_state = NET_NONBLOCKING_READ; return NET_ASYNC_NOT_READY; }