From c1fffc3c651f22451154c9834a43c661112b368c Mon Sep 17 00:00:00 2001 From: Bharathy Satish Date: Fri, 11 Jan 2019 16:47:37 +0100 Subject: [PATCH] WL#11381: Add asynchronous support into the mysql protocol C APIs are synchronous, which means client sends a request and waits until server responds. This makes applications to not do anything until server sends a response. There can be a requirement where client is not bothered about when server sends data back, client can submit a query, do some other tasks and when in need of server response, client can check if server has sent the response and act accordingly. Thus making a need to implement asynchronous communication between client and server. This WL introduces new non blocking version of above C APIs which does not wait for data to be available on the socket to be read. These APIs will return immediately with a return status. Return status is used to decide if the API has completed its operation or needs to be called again at some later point in time to complete the operation. RB: 21150 Reviewed-by: Georgi Kodinov Ramil Kalimullin --- client/mysqltest.cc | 529 +++- include/errmsg.h | 5 +- include/mysql.h | 18 + include/mysql.h.pp | 34 +- include/mysql/client_authentication.h | 8 +- include/mysql/client_plugin.h | 7 +- include/mysql/client_plugin.h.pp | 15 + include/mysql/plugin_auth.h.pp | 12 + include/mysql/plugin_auth_common.h | 28 +- include/mysql_async.h | 199 ++ include/mysql_com.h | 3 +- include/sql_common.h | 28 + include/violite.h | 20 +- libmysql/CMakeLists.txt | 7 + .../auth_ldap_sasl_client.cc | 4 +- libmysql/errmsg.cc | 3 +- libmysql/libmysql.cc | 36 +- mysql-test/mysql-test-run.dox | 6 + mysql-test/mysql-test-run.pl | 8 + mysql-test/r/async_client.result | 95 + mysql-test/r/big_packets.result | 6 + mysql-test/r/big_packets_boundary.result | 96 + mysql-test/r/ssl-big-packet.result | 6 + mysql-test/t/async_client.test | 168 ++ mysql-test/t/big_packets-master.opt | 1 + mysql-test/t/big_packets.test | 13 + mysql-test/t/big_packets_boundary-master.opt | 1 + mysql-test/t/big_packets_boundary.test | 18 + mysql-test/t/ssl-big-packet-master.opt | 5 + mysql-test/t/ssl-big-packet.test | 24 + mysql-test/valgrind.supp | 87 +- plugin/auth/dialog.cc | 4 +- plugin/auth/qa_auth_client.cc | 5 +- plugin/auth/qa_auth_interface.cc | 5 +- plugin/auth/test_plugin.cc | 5 +- plugin/x/client/xconnection_impl.cc | 4 +- sql-common/client.cc | 2668 ++++++++++++++--- sql-common/client_async_authentication.h | 171 ++ sql-common/client_authentication.cc | 407 ++- sql-common/net_serv.cc | 591 +++- testclients/mysql_client_test.cc | 286 ++ vio/vio.cc | 21 +- vio/viosocket.cc | 61 +- vio/viossl.cc | 161 +- 44 files changed, 5346 insertions(+), 533 deletions(-) create mode 100644 include/mysql_async.h create mode 100644 mysql-test/r/async_client.result create mode 100644 mysql-test/r/big_packets.result create mode 100644 mysql-test/r/big_packets_boundary.result create mode 100644 mysql-test/r/ssl-big-packet.result create mode 100644 mysql-test/t/async_client.test create mode 100644 mysql-test/t/big_packets-master.opt create mode 100644 mysql-test/t/big_packets.test create mode 100644 mysql-test/t/big_packets_boundary-master.opt create mode 100644 mysql-test/t/big_packets_boundary.test create mode 100644 mysql-test/t/ssl-big-packet-master.opt create mode 100644 mysql-test/t/ssl-big-packet.test create mode 100644 sql-common/client_async_authentication.h diff --git a/client/mysqltest.cc b/client/mysqltest.cc index 31234a6d51bd..5f6736cf4b25 100644 --- a/client/mysqltest.cc +++ b/client/mysqltest.cc @@ -34,6 +34,7 @@ #include "client/mysqltest/utils.h" #include +#include #include // std::isinf #include #include @@ -54,6 +55,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +64,8 @@ #include #include #ifndef _WIN32 +#include +#include #include #endif #ifdef _WIN32 @@ -219,6 +223,15 @@ static const char *load_default_groups[] = {"mysqltest", "client", 0}; static char line_buffer[MAX_DELIMITER_LENGTH], *line_buffer_pos = line_buffer; static bool can_handle_expired_passwords = true; +/* + These variables control the behavior of the asynchronous operations for + mysqltest client. If --async-client is specified, use_async_client is true. + Each command checks enable_async_client (which can be forced off or + disabled for a single command) to decide the mode it uses to run. +*/ +static bool use_async_client = false; +static bool enable_async_client = false; + // Secondary engine options static const char *opt_load_pool = 0; static const char *opt_offload_count_file; @@ -253,7 +266,8 @@ static struct Property prop_list[] = { {&ps_protocol_enabled, 0, 0, 0, "$ENABLE_PS_PROTOCOL"}, {&disable_query_log, 0, 0, 1, "$ENABLE_QUERY_LOG"}, {&disable_result_log, 0, 0, 1, "$ENABLE_RESULT_LOG"}, - {&disable_warnings, 0, 0, 1, "$ENABLE_WARNINGS"}}; + {&disable_warnings, 0, 0, 1, "$ENABLE_WARNINGS"}, + {&enable_async_client, 0, 0, 0, "$ENABLE_ASYNC_CLIENT"}}; static bool once_property = false; @@ -267,6 +281,7 @@ enum enum_prop { P_QUERY, P_RESULT, P_WARN, + P_ASYNC, P_MAX }; @@ -542,6 +557,8 @@ enum enum_commands { Q_DISABLE_SESSION_TRACK_INFO, Q_ENABLE_METADATA, Q_DISABLE_METADATA, + Q_ENABLE_ASYNC_CLIENT, + Q_DISABLE_ASYNC_CLIENT, Q_EXEC, Q_EXECW, Q_EXEC_BACKGROUND, @@ -615,15 +632,15 @@ const char *command_names[] = { "disable_connect_log", "wait_for_slave_to_stop", "enable_warnings", "disable_warnings", "enable_info", "disable_info", "enable_session_track_info", "disable_session_track_info", - "enable_metadata", "disable_metadata", "exec", "execw", - "exec_in_background", "delimiter", "disable_abort_on_error", - "enable_abort_on_error", "vertical_results", "horizontal_results", - "query_vertical", "query_horizontal", "sorted_result", "lowercase_result", - "start_timer", "end_timer", "character_set", "disable_ps_protocol", - "enable_ps_protocol", "disable_reconnect", "enable_reconnect", "if", - "disable_testcase", "enable_testcase", "replace_regex", - "replace_numeric_round", "remove_file", "file_exists", "write_file", - "copy_file", "perl", "die", + "enable_metadata", "disable_metadata", "enable_async_client", + "disable_async_client", "exec", "execw", "exec_in_background", "delimiter", + "disable_abort_on_error", "enable_abort_on_error", "vertical_results", + "horizontal_results", "query_vertical", "query_horizontal", "sorted_result", + "lowercase_result", "start_timer", "end_timer", "character_set", + "disable_ps_protocol", "enable_ps_protocol", "disable_reconnect", + "enable_reconnect", "if", "disable_testcase", "enable_testcase", + "replace_regex", "replace_numeric_round", "remove_file", "file_exists", + "write_file", "copy_file", "perl", "die", /* Don't execute any more commands, compare result */ "exit", "skip", "chmod", "append_file", "cat_file", "diff_files", @@ -760,6 +777,334 @@ static void free_all_replace() { free_replace_numeric_round(); } +/* + To run tests via the async API, invoke mysqltest with --async-client. +*/ +class AsyncTimer { + public: + explicit AsyncTimer(std::string label) + : label_(label), time_(std::chrono::system_clock::now()), start_(time_) {} + + ~AsyncTimer() { + auto now = std::chrono::system_clock::now(); + auto delta = now - start_; + ulonglong MY_ATTRIBUTE((unused)) micros = + std::chrono::duration_cast(delta).count(); + DBUG_PRINT("async_timing", + ("%s total micros: %llu", label_.c_str(), micros)); + } + + void check() { + auto now = std::chrono::system_clock::now(); + auto delta = now - time_; + time_ = now; + ulonglong MY_ATTRIBUTE((unused)) micros = + std::chrono::duration_cast(delta).count(); + DBUG_PRINT("async_timing", ("%s op micros: %llu", label_.c_str(), micros)); + } + + private: + std::string label_; + std::chrono::system_clock::time_point time_; + std::chrono::system_clock::time_point start_; +}; + +#ifdef _WIN32 +/* + Check if any data is available in the socket to be read or written. +*/ +static int socket_event_listen(net_async_block_state async_blocking_state, + my_socket fd) { + int result; + fd_set readfds, writefds, exceptfds; + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + + FD_SET(fd, &exceptfds); + + switch (async_blocking_state) { + case NET_NONBLOCKING_READ: + FD_SET(fd, &readfds); + break; + case NET_NONBLOCKING_WRITE: + case NET_NONBLOCKING_CONNECT: + FD_SET(fd, &writefds); + break; + default: + DBUG_ASSERT(false); + } + result = select((int)(fd + 1), &readfds, &writefds, &exceptfds, NULL); + if (result < 0) { + perror("select"); + } + return result; +} +#else +static int socket_event_listen(net_async_block_state async_blocking_state, + my_socket fd) { + int result; + pollfd pfd; + pfd.fd = fd; + switch (async_blocking_state) { + case NET_NONBLOCKING_READ: + pfd.events = POLLIN; + break; + case NET_NONBLOCKING_WRITE: + pfd.events = POLLOUT; + break; + case NET_NONBLOCKING_CONNECT: + pfd.events = POLLIN | POLLOUT; + break; + default: + DBUG_ASSERT(false); + } + result = poll(&pfd, 1, -1); + if (result < 0) { + perror("poll"); + } + return result; +} +#endif + +/* + Below async_mysql_*_wrapper functions are used to measure how much time + each nonblocking call spends before completing the operations. +i*/ +static MYSQL_ROW async_mysql_fetch_row_wrapper(MYSQL_RES *res) { + MYSQL_ROW row; + MYSQL *mysql = res->handle; + AsyncTimer t(__func__); + while (mysql_fetch_row_nonblocking(res, &row) == NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA((&(mysql->net))); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return NULL; + } + return row; +} + +static MYSQL_RES *async_mysql_store_result_wrapper(MYSQL *mysql) { + MYSQL_RES *result; + AsyncTimer t(__func__); + while (mysql_store_result_nonblocking(mysql, &result) == + NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return NULL; + } + return result; +} + +static int async_mysql_real_query_wrapper(MYSQL *mysql, const char *query, + ulong length) { + net_async_status status; + AsyncTimer t(__func__); + while ((status = mysql_real_query_nonblocking(mysql, query, length)) == + NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return 1; + } + if (status == NET_ASYNC_ERROR) { + return 1; + } + return 0; +} + +static int async_mysql_send_query_wrapper(MYSQL *mysql, const char *query, + ulong length) { + net_async_status status; + ASYNC_DATA(mysql)->async_query_length = length; + ASYNC_DATA(mysql)->async_query_state = QUERY_SENDING; + + AsyncTimer t(__func__); + while ((status = mysql_send_query_nonblocking(mysql, query, length)) == + NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return 1; + } + if (status == NET_ASYNC_ERROR) { + return 1; + } + return 0; +} + +static bool async_mysql_read_query_result_wrapper(MYSQL *mysql) { + net_async_status status; + AsyncTimer t(__func__); + while ((status = (*mysql->methods->read_query_result_nonblocking)(mysql)) == + NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return true; + } + if (status == NET_ASYNC_ERROR) { + return true; + } + return false; +} + +static int async_mysql_next_result_wrapper(MYSQL *mysql) { + net_async_status status; + AsyncTimer t(__func__); + while ((status = mysql_next_result_nonblocking(mysql)) == + NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return 1; + } + if (status == NET_ASYNC_ERROR) + return 1; + else if (status == NET_ASYNC_COMPLETE_WITH_MORE_RESULTS) + return -1; + else + return 0; +} + +static MYSQL *async_mysql_real_connect_wrapper( + MYSQL *mysql, const char *host, const char *user, const char *passwd, + const char *db, uint port, const char *unix_socket, ulong client_flag) { + net_async_status status; + AsyncTimer t(__func__); + + while ((status = mysql_real_connect_nonblocking( + mysql, host, user, passwd, db, port, unix_socket, client_flag)) == + NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + } + if (status == NET_ASYNC_ERROR) + return nullptr; + else + return mysql; +} + +static int async_mysql_query_wrapper(MYSQL *mysql, const char *query) { + net_async_status status; + AsyncTimer t(__func__); + while ((status = mysql_real_query_nonblocking(mysql, query, strlen(query))) == + NET_ASYNC_NOT_READY) { + t.check(); + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return 1; + } + if (status == NET_ASYNC_ERROR) { + return 1; + } + return 0; +} + +static void async_mysql_free_result_wrapper(MYSQL_RES *result) { + AsyncTimer t(__func__); + while (mysql_free_result_nonblocking(result) == NET_ASYNC_NOT_READY) { + t.check(); + MYSQL *mysql = result->handle; + NET_ASYNC *net_async = NET_ASYNC_DATA(&(mysql->net)); + int result = socket_event_listen(net_async->async_blocking_state, + mysql_get_socket_descriptor(mysql)); + if (result == -1) return; + } + return; +} + +/* + Below are the wrapper functions which are defined on top of standard C APIs + to make a decision on whether to call blocking or non blocking API based on + --async-client option is set or not. +*/ +static MYSQL_ROW mysql_fetch_row_wrapper(MYSQL_RES *res) { + if (enable_async_client) + return async_mysql_fetch_row_wrapper(res); + else + return mysql_fetch_row(res); +} + +static MYSQL_RES *mysql_store_result_wrapper(MYSQL *mysql) { + if (enable_async_client) + return async_mysql_store_result_wrapper(mysql); + else + return mysql_store_result(mysql); +} + +static int mysql_real_query_wrapper(MYSQL *mysql, const char *query, + ulong length) { + if (enable_async_client) + return async_mysql_real_query_wrapper(mysql, query, length); + else + return mysql_real_query(mysql, query, length); +} + +static int mysql_send_query_wrapper(MYSQL *mysql, const char *query, + ulong length) { + if (enable_async_client) + return async_mysql_send_query_wrapper(mysql, query, length); + else + return mysql_send_query(mysql, query, length); +} + +static bool mysql_read_query_result_wrapper(MYSQL *mysql) { + bool ret; + if (enable_async_client) + ret = async_mysql_read_query_result_wrapper(mysql); + else + ret = mysql_read_query_result(mysql); + return ret; +} + +static int mysql_query_wrapper(MYSQL *mysql, const char *query) { + if (enable_async_client) + return async_mysql_query_wrapper(mysql, query); + else + return mysql_query(mysql, query); +} + +static int mysql_next_result_wrapper(MYSQL *mysql) { + if (enable_async_client) + return async_mysql_next_result_wrapper(mysql); + else + return mysql_next_result(mysql); +} + +static MYSQL *mysql_real_connect_wrapper(MYSQL *mysql, const char *host, + const char *user, const char *passwd, + const char *db, uint port, + const char *unix_socket, + ulong client_flag) { + if (enable_async_client) + return async_mysql_real_connect_wrapper(mysql, host, user, passwd, db, port, + unix_socket, client_flag); + else + return mysql_real_connect(mysql, host, user, passwd, db, port, unix_socket, + client_flag); +} + +static void mysql_free_result_wrapper(MYSQL_RES *result) { + if (enable_async_client) + return async_mysql_free_result_wrapper(result); + else + return mysql_free_result(result); +} + +/* async client test code (end) */ + class LogFile { FILE *m_file; char m_file_name[FN_REFLEN]; @@ -984,13 +1329,13 @@ static void show_query(MYSQL *mysql, const char *query) { if (!mysql) DBUG_VOID_RETURN; - if (mysql_query(mysql, query)) { + if (mysql_query_wrapper(mysql, query)) { log_msg("Error running query '%s': %d %s", query, mysql_errno(mysql), mysql_error(mysql)); DBUG_VOID_RETURN; } - if ((res = mysql_store_result(mysql)) == NULL) { + if ((res = mysql_store_result_wrapper(mysql)) == NULL) { /* No result set returned */ DBUG_VOID_RETURN; } @@ -1003,7 +1348,7 @@ static void show_query(MYSQL *mysql, const char *query) { MYSQL_FIELD *fields = mysql_fetch_fields(res); fprintf(stderr, "=== %s ===\n", query); - while ((row = mysql_fetch_row(res))) { + while ((row = mysql_fetch_row_wrapper(res))) { unsigned long *lengths = mysql_fetch_lengths(res); row_num++; @@ -1018,7 +1363,7 @@ static void show_query(MYSQL *mysql, const char *query) { for (i = 0; i < std::strlen(query) + 8; i++) fprintf(stderr, "="); fprintf(stderr, "\n\n"); } - mysql_free_result(res); + mysql_free_result_wrapper(res); DBUG_VOID_RETURN; } @@ -1043,13 +1388,13 @@ static void show_warnings_before_error(MYSQL *mysql) { if (!mysql) DBUG_VOID_RETURN; - if (mysql_query(mysql, query)) { + if (mysql_query_wrapper(mysql, query)) { log_msg("Error running query '%s': %d %s", query, mysql_errno(mysql), mysql_error(mysql)); DBUG_VOID_RETURN; } - if ((res = mysql_store_result(mysql)) == NULL) { + if ((res = mysql_store_result_wrapper(mysql)) == NULL) { /* No result set returned */ DBUG_VOID_RETURN; } @@ -1062,7 +1407,7 @@ static void show_warnings_before_error(MYSQL *mysql) { unsigned int num_fields = mysql_num_fields(res); fprintf(stderr, "\nWarnings from just before the error:\n"); - while ((row = mysql_fetch_row(res))) { + while ((row = mysql_fetch_row_wrapper(res))) { unsigned int i; unsigned long *lengths = mysql_fetch_lengths(res); @@ -1079,7 +1424,7 @@ static void show_warnings_before_error(MYSQL *mysql) { fprintf(stderr, "\n"); } } - mysql_free_result(res); + mysql_free_result_wrapper(res); DBUG_VOID_RETURN; } @@ -2405,8 +2750,8 @@ static void var_query_set(VAR *var, const char *query, const char **query_end) { init_dynamic_string(&ds_query, 0, (end - query) + 32, 256); do_eval(&ds_query, query, end, false); - if (mysql_real_query(mysql, ds_query.str, - static_cast(ds_query.length))) { + if (mysql_real_query_wrapper(mysql, ds_query.str, + static_cast(ds_query.length))) { handle_error(curr_command, mysql_errno(mysql), mysql_error(mysql), mysql_sqlstate(mysql), &ds_res); /* If error was acceptable, return empty string */ @@ -2415,11 +2760,11 @@ static void var_query_set(VAR *var, const char *query, const char **query_end) { DBUG_VOID_RETURN; } - if (!(res = mysql_store_result(mysql))) + if (!(res = mysql_store_result_wrapper(mysql))) die("Query '%s' didn't return a result set", ds_query.str); dynstr_free(&ds_query); - if ((row = mysql_fetch_row(res)) && row[0]) { + if ((row = mysql_fetch_row_wrapper(res)) && row[0]) { /* Concatenate all fields in the first row with tab in between and assign that string to the $variable @@ -2486,7 +2831,7 @@ static void var_query_set(VAR *var, const char *query, const char **query_end) { } else eval_expr(var, "", 0); - mysql_free_result(res); + mysql_free_result_wrapper(res); DBUG_VOID_RETURN; } @@ -2638,8 +2983,8 @@ static void var_set_query_get_value(struct st_command *command, VAR *var) { die("Mismatched \"'s around query '%s'", ds_query.str); /* Run the query */ - if (mysql_real_query(mysql, ds_query.str, - static_cast(ds_query.length))) { + if (mysql_real_query_wrapper(mysql, ds_query.str, + static_cast(ds_query.length))) { handle_error(curr_command, mysql_errno(mysql), mysql_error(mysql), mysql_sqlstate(mysql), &ds_res); /* If error was acceptable, return empty string */ @@ -2649,7 +2994,7 @@ static void var_set_query_get_value(struct st_command *command, VAR *var) { DBUG_VOID_RETURN; } - if (!(res = mysql_store_result(mysql))) + if (!(res = mysql_store_result_wrapper(mysql))) die("Query '%s' didn't return a result set", ds_query.str); { @@ -2666,7 +3011,7 @@ static void var_set_query_get_value(struct st_command *command, VAR *var) { } } if (col_no == -1) { - mysql_free_result(res); + mysql_free_result_wrapper(res); die("Could not find column '%s' in the result of '%s'", ds_col.str, ds_query.str); } @@ -2680,7 +3025,7 @@ static void var_set_query_get_value(struct st_command *command, VAR *var) { long rows = 0; const char *value = "No such row"; - while ((row = mysql_fetch_row(res))) { + while ((row = mysql_fetch_row_wrapper(res))) { if (++rows == row_no) { DBUG_PRINT("info", ("At row %ld, column %d is '%s'", row_no, col_no, row[col_no])); @@ -2696,7 +3041,7 @@ static void var_set_query_get_value(struct st_command *command, VAR *var) { eval_expr(var, value, 0, false, false); } dynstr_free(&ds_query); - mysql_free_result(res); + mysql_free_result_wrapper(res); DBUG_VOID_RETURN; } @@ -4643,23 +4988,23 @@ static void do_wait_for_slave_to_stop( MYSQL_ROW row; int done; - if (mysql_query( + if (mysql_query_wrapper( mysql, "SELECT 'Slave_running' as Variable_name," " IF(count(*)>0,'ON','OFF') as Value FROM" " performance_schema.replication_applier_status ras," "performance_schema.replication_connection_status rcs WHERE " "ras.SERVICE_STATE='ON' AND rcs.SERVICE_STATE='ON'") || - !(res = mysql_store_result(mysql))) + !(res = mysql_store_result_wrapper(mysql))) die("Query failed while probing slave for stop: %s", mysql_error(mysql)); - if (!(row = mysql_fetch_row(res)) || !row[1]) { - mysql_free_result(res); + if (!(row = mysql_fetch_row_wrapper(res)) || !row[1]) { + mysql_free_result_wrapper(res); die("Strange result from query while probing slave for stop"); } done = !std::strcmp(row[1], "OFF"); - mysql_free_result(res); + mysql_free_result_wrapper(res); if (done) break; my_sleep(SLAVE_POLL_INTERVAL); } @@ -4679,14 +5024,14 @@ static void do_sync_with_master2(struct st_command *command, long offset) { sprintf(query_buf, "select master_pos_wait('%s', %ld, %d)", master_pos.file, master_pos.pos + offset, timeout); - if (mysql_query(mysql, query_buf)) + if (mysql_query_wrapper(mysql, query_buf)) die("failed in '%s': %d: %s", query_buf, mysql_errno(mysql), mysql_error(mysql)); - if (!(res = mysql_store_result(mysql))) + if (!(res = mysql_store_result_wrapper(mysql))) die("mysql_store_result() returned NULL for '%s'", query_buf); - if (!(row = mysql_fetch_row(res))) { - mysql_free_result(res); + if (!(row = mysql_fetch_row_wrapper(res))) { + mysql_free_result_wrapper(res); die("empty result in %s", query_buf); } @@ -4694,7 +5039,7 @@ static void do_sync_with_master2(struct st_command *command, long offset) { const char *result_str = row[0]; if (result_str) result = atoi(result_str); - mysql_free_result(res); + mysql_free_result_wrapper(res); if (!result_str || result < 0) { /* master_pos_wait returned NULL or < 0 */ @@ -4755,18 +5100,18 @@ static void ndb_wait_for_binlog_injector(void) { MYSQL *mysql = &cur_con->mysql; const char *query; ulong have_ndbcluster; - if (mysql_query(mysql, query = - "select count(*) from information_schema.engines" - " where engine = 'ndbcluster' and" - " support in ('YES', 'DEFAULT')")) + if (mysql_query_wrapper( + mysql, query = "select count(*) from information_schema.engines" + " where engine = 'ndbcluster' and" + " support in ('YES', 'DEFAULT')")) die("'%s' failed: %d %s", query, mysql_errno(mysql), mysql_error(mysql)); - if (!(res = mysql_store_result(mysql))) + if (!(res = mysql_store_result_wrapper(mysql))) die("mysql_store_result() returned NULL for '%s'", query); - if (!(row = mysql_fetch_row(res))) + if (!(row = mysql_fetch_row_wrapper(res))) die("Query '%s' returned empty result", query); have_ndbcluster = std::strcmp(row[0], "1") == 0; - mysql_free_result(res); + mysql_free_result_wrapper(res); if (!have_ndbcluster) { return; @@ -4785,14 +5130,14 @@ static void ndb_wait_for_binlog_injector(void) { "latest_handled_binlog_epoch="; if (count) my_sleep(100 * 1000); /* 100ms */ - if (mysql_query(mysql, query = "show engine ndb status")) + if (mysql_query_wrapper(mysql, query = "show engine ndb status")) die("failed in '%s': %d %s", query, mysql_errno(mysql), mysql_error(mysql)); - if (!(res = mysql_store_result(mysql))) + if (!(res = mysql_store_result_wrapper(mysql))) die("mysql_store_result() returned NULL for '%s'", query); - while ((row = mysql_fetch_row(res))) { + while ((row = mysql_fetch_row_wrapper(res))) { if (std::strcmp(row[1], binlog) == 0) { const char *status = row[2]; @@ -4846,7 +5191,7 @@ static void ndb_wait_for_binlog_injector(void) { start_handled_binlog_epoch & 0xffffffff); } - mysql_free_result(res); + mysql_free_result_wrapper(res); } } @@ -4862,16 +5207,17 @@ static int do_save_master_pos() { */ ndb_wait_for_binlog_injector(); - if (mysql_query(mysql, query = "show master status")) + if (mysql_query_wrapper(mysql, query = "show master status")) die("failed in 'show master status': %d %s", mysql_errno(mysql), mysql_error(mysql)); - if (!(res = mysql_store_result(mysql))) + if (!(res = mysql_store_result_wrapper(mysql))) die("mysql_store_result() retuned NULL for '%s'", query); - if (!(row = mysql_fetch_row(res))) die("empty result in show master status"); + if (!(row = mysql_fetch_row_wrapper(res))) + die("empty result in show master status"); my_stpnmov(master_pos.file, row[0], sizeof(master_pos.file) - 1); master_pos.pos = strtoul(row[1], (char **)0, 10); - mysql_free_result(res); + mysql_free_result_wrapper(res); DBUG_RETURN(0); } @@ -5503,7 +5849,7 @@ static void do_shutdown_server(struct st_command *command) { } // Tell server to shutdown if timeout > 0. - if (timeout > 0 && mysql_query(mysql, "shutdown")) { + if (timeout > 0 && mysql_query_wrapper(mysql, "shutdown")) { // Failed to issue shutdown command. error = 1; goto end; @@ -6137,9 +6483,9 @@ static void safe_connect(MYSQL *mysql, const char *name, const char *host, "mysqltest"); mysql_options(mysql, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS, &can_handle_expired_passwords); - while ( - !mysql_real_connect(mysql, host, user, pass, db, port, sock, - CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS)) { + while (!mysql_real_connect_wrapper( + mysql, host, user, pass, db, port, sock, + CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS)) { /* Connect failed @@ -6230,8 +6576,9 @@ static int connect_n_handle_errors(struct st_command *command, MYSQL *con, mysql_options4(con, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name", "mysqltest"); mysql_options(con, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS, &can_handle_expired_passwords); - while (!mysql_real_connect(con, host, user, pass, db, port, sock ? sock : 0, - CLIENT_MULTI_STATEMENTS)) { + while (!mysql_real_connect_wrapper(con, host, user, pass, db, port, + sock ? sock : 0, + CLIENT_MULTI_STATEMENTS)) { /* If we have used up all our connections check whether this is expected (by --error). If so, handle the error right away. @@ -6458,6 +6805,10 @@ static void do_connect(struct st_command *command) { opt_protocol = MYSQL_PROTOCOL_PIPE; } + if (opt_compress || con_compress) { + enable_async_client = false; + } + if (opt_protocol) { mysql_options(&con_slot->mysql, MYSQL_OPT_PROTOCOL, (char *)&opt_protocol); /* @@ -7416,6 +7767,9 @@ static struct my_option my_long_options[] = { GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, {"view-protocol", OPT_VIEW_PROTOCOL, "Use views for select.", &view_protocol, &view_protocol, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"async-client", '*', "Use async client.", &use_async_client, + &use_async_client, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}}; static void usage() { @@ -7803,7 +8157,7 @@ static void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) { MYSQL_FIELD *fields = mysql_fetch_fields(res); ulong *lengths; - while ((row = mysql_fetch_row(res))) { + while ((row = mysql_fetch_row_wrapper(res))) { uint i; lengths = mysql_fetch_lengths(res); for (i = 0; i < num_fields; i++) { @@ -8094,16 +8448,16 @@ static int append_warnings(DYNAMIC_STRING *ds, MYSQL *mysql) { DBUG_ASSERT(!mysql_more_results(mysql)); MYSQL_RES *warn_res; - if (mysql_real_query(mysql, "SHOW WARNINGS", 13)) + if (mysql_real_query_wrapper(mysql, "SHOW WARNINGS", 13)) die("Error running query \"SHOW WARNINGS\": %s", mysql_error(mysql)); - if (!(warn_res = mysql_store_result(mysql))) + if (!(warn_res = mysql_store_result_wrapper(mysql))) die("Warning count is %u but didn't get any warnings", count); DYNAMIC_STRING ds_warnings; init_dynamic_string(&ds_warnings, "", 1024, 1024); append_result(&ds_warnings, warn_res); - mysql_free_result(warn_res); + mysql_free_result_wrapper(warn_res); if (disable_warnings && (disabled_warnings->count() || enabled_warnings->count())) @@ -8146,8 +8500,9 @@ static void run_query_normal(struct st_connection *cn, } if (flags & QUERY_SEND_FLAG) { - // Send the query - if (mysql_send_query(&cn->mysql, query, static_cast(query_len))) { + /* Send the query */ + if (mysql_send_query_wrapper(&cn->mysql, query, + static_cast(query_len))) { handle_error(command, mysql_errno(mysql), mysql_error(mysql), mysql_sqlstate(mysql), ds); goto end; @@ -8160,18 +8515,23 @@ static void run_query_normal(struct st_connection *cn, } do { - // When on first result set, call mysql_read_query_result to - // retrieve answer to the query sent earlier. - if ((counter == 0) && mysql_read_query_result(&cn->mysql)) { - // We've failed to collect the result set. + /* + When on first result set, call mysql_read_query_result_wrapper to + retrieve answer to the query sent earlier + */ + if ((counter == 0) && mysql_read_query_result_wrapper(&cn->mysql)) { + /* we've failed to collect the result set */ cn->pending = true; handle_error(command, mysql_errno(mysql), mysql_error(mysql), mysql_sqlstate(mysql), ds); goto end; } - // Store the result of the query if it will return any fields. - if (mysql_field_count(mysql) && ((res = mysql_store_result(mysql)) == 0)) { + /* + Store the result of the query if it will return any fields + */ + if (mysql_field_count(mysql) && + ((res = mysql_store_result_wrapper(mysql)) == 0)) { handle_error(command, mysql_errno(mysql), mysql_error(mysql), mysql_sqlstate(mysql), ds); goto end; @@ -8211,12 +8571,11 @@ static void run_query_normal(struct st_connection *cn, } if (res) { - mysql_free_result(res); + mysql_free_result_wrapper(res); res = 0; } counter++; - } while (!(error = mysql_next_result(mysql))); - + } while (!(error = mysql_next_result_wrapper(mysql))); if (error > 0) { // We got an error from mysql_next_result, maybe expected. handle_error(command, mysql_errno(mysql), mysql_error(mysql), @@ -8366,7 +8725,7 @@ static void run_query_stmt(MYSQL *mysql, struct st_command *command, append_stmt_result(ds, stmt, fields, num_fields); // Free normal result set with meta data - mysql_free_result(res); + mysql_free_result_wrapper(res); // Clear prepare warnings if there are execute warnings, // since they are probably duplicated. @@ -8479,7 +8838,7 @@ static int util_query(MYSQL *org_mysql, const char *query) { cur_con->util_mysql = mysql; } - DBUG_RETURN(mysql_query(mysql, query)); + DBUG_RETURN(mysql_query_wrapper(mysql, query)); } /* @@ -9141,6 +9500,7 @@ int main(int argc, char **argv) { var_set_int("$ENABLE_WARNINGS", 1); var_set_int("$ENABLE_INFO", 0); var_set_int("$ENABLE_METADATA", 0); + var_set_int("$ENABLE_ASYNC_CLIENT", 0); DBUG_PRINT("info", ("result_file: '%s'", result_file_name ? result_file_name : "")); @@ -9158,6 +9518,7 @@ int main(int argc, char **argv) { log_file.open(opt_logdir, result_file_name, ".log"); verbose_msg("Logging to '%s'.", log_file.file_name()); + enable_async_client = use_async_client; /* Creating a temporary log file using current file name if @@ -9220,6 +9581,9 @@ int main(int argc, char **argv) { opt_ssl_mode = SSL_MODE_VERIFY_CA; } #endif + if (opt_compress) { + enable_async_client = false; + } if (SSL_SET_OPTIONS(&con->mysql)) die("%s", SSL_SET_OPTIONS_ERROR); #if defined(_WIN32) if (shared_memory_base_name) @@ -9639,8 +10003,8 @@ int main(int argc, char **argv) { cleanup_and_exit(1); } - handle_command_error(command, - mysql_query(&cur_con->mysql, "shutdown")); + handle_command_error( + command, mysql_query_wrapper(&cur_con->mysql, "shutdown")); break; case Q_SHUTDOWN_SERVER: do_shutdown_server(command); @@ -9678,9 +10042,16 @@ int main(int argc, char **argv) { break; case Q_ENABLE_RECONNECT: set_reconnect(&cur_con->mysql, 1); + enable_async_client = false; /* Close any open statements - no reconnect, need new prepare */ close_statements(); break; + case Q_ENABLE_ASYNC_CLIENT: + set_property(command, P_ASYNC, 1); + break; + case Q_DISABLE_ASYNC_CLIENT: + set_property(command, P_ASYNC, 0); + break; case Q_DISABLE_TESTCASE: if (testcase_disabled == 0) do_disable_testcase(command); diff --git a/include/errmsg.h b/include/errmsg.h index 550233d02b13..419f03bd7075 100644 --- a/include/errmsg.h +++ b/include/errmsg.h @@ -1,7 +1,7 @@ #ifndef ERRMSG_INCLUDED #define ERRMSG_INCLUDED -/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -123,7 +123,8 @@ extern const char *client_errors[]; /* Error messages */ #define CR_INSECURE_API_ERR 2062 #define CR_FILE_NAME_TOO_LONG 2063 #define CR_SSL_FIPS_MODE_ERR 2064 -#define CR_ERROR_LAST /*Copy last error nr:*/ 2064 +#define CR_COMPRESSION_NOT_SUPPORTED 2065 +#define CR_ERROR_LAST /*Copy last error nr:*/ 2065 /* Add error numbers before CR_ERROR_LAST and change it accordingly. */ /* Visual Studio requires '__inline' for C code */ diff --git a/include/mysql.h b/include/mysql.h index b4a7b15645c5..561960cd9250 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -465,6 +465,20 @@ int STDCALL mysql_real_query(MYSQL *mysql, const char *q, unsigned long length); MYSQL_RES *STDCALL mysql_store_result(MYSQL *mysql); MYSQL_RES *STDCALL mysql_use_result(MYSQL *mysql); +enum net_async_status STDCALL mysql_real_connect_nonblocking( + MYSQL *mysql, const char *host, const char *user, const char *passwd, + const char *db, unsigned int port, const char *unix_socket, + unsigned long clientflag); +enum net_async_status STDCALL mysql_send_query_nonblocking( + MYSQL *mysql, const char *query, unsigned long length); +enum net_async_status STDCALL mysql_real_query_nonblocking( + MYSQL *mysql, const char *query, unsigned long length); +enum net_async_status STDCALL +mysql_store_result_nonblocking(MYSQL *mysql, MYSQL_RES **result); +enum net_async_status STDCALL mysql_next_result_nonblocking(MYSQL *mysql); +enum net_async_status STDCALL mysql_select_db_nonblocking(MYSQL *mysql, + const char *db, + bool *error); void STDCALL mysql_get_character_set_info(MYSQL *mysql, MY_CHARSET_INFO *charset); @@ -510,12 +524,16 @@ int STDCALL mysql_options4(MYSQL *mysql, enum mysql_option option, int STDCALL mysql_get_option(MYSQL *mysql, enum mysql_option option, const void *arg); void STDCALL mysql_free_result(MYSQL_RES *result); +enum net_async_status STDCALL mysql_free_result_nonblocking(MYSQL_RES *result); void STDCALL mysql_data_seek(MYSQL_RES *result, my_ulonglong offset); MYSQL_ROW_OFFSET STDCALL mysql_row_seek(MYSQL_RES *result, MYSQL_ROW_OFFSET offset); MYSQL_FIELD_OFFSET STDCALL mysql_field_seek(MYSQL_RES *result, MYSQL_FIELD_OFFSET offset); MYSQL_ROW STDCALL mysql_fetch_row(MYSQL_RES *result); +enum net_async_status STDCALL mysql_fetch_row_nonblocking(MYSQL_RES *res, + MYSQL_ROW *row); + unsigned long *STDCALL mysql_fetch_lengths(MYSQL_RES *result); MYSQL_FIELD *STDCALL mysql_fetch_field(MYSQL_RES *result); MYSQL_RES *STDCALL mysql_list_fields(MYSQL *mysql, const char *table, diff --git a/include/mysql.h.pp b/include/mysql.h.pp index 1b8bc72a0cd5..e34b34ef478c 100644 --- a/include/mysql.h.pp +++ b/include/mysql.h.pp @@ -264,16 +264,31 @@ } protocol; int socket; }; +enum net_async_status { + NET_ASYNC_COMPLETE = 0, + NET_ASYNC_NOT_READY, + NET_ASYNC_ERROR, + NET_ASYNC_COMPLETE_WITH_MORE_RESULTS +}; typedef struct MYSQL_PLUGIN_VIO { int (*read_packet)(struct MYSQL_PLUGIN_VIO *vio, unsigned char **buf); int (*write_packet)(struct MYSQL_PLUGIN_VIO *vio, const unsigned char *packet, int packet_len); void (*info)(struct MYSQL_PLUGIN_VIO *vio, struct MYSQL_PLUGIN_VIO_INFO *info); + enum net_async_status (*read_packet_nonblocking)(struct MYSQL_PLUGIN_VIO *vio, + unsigned char **buf, + int *result); + enum net_async_status (*write_packet_nonblocking)( + struct MYSQL_PLUGIN_VIO *vio, const unsigned char *pkt, int pkt_len, + int *result); } MYSQL_PLUGIN_VIO; struct auth_plugin_t { int type; unsigned int interface_version; const char *name; const char *author; const char *desc; unsigned int version[3]; const char *license; void *mysql_api; int (*init)(char *, size_t, int, va_list); int (*deinit)(void); int (*options)(const char *option, const void *); int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, struct MYSQL *mysql); + enum net_async_status (*authenticate_user_nonblocking)(MYSQL_PLUGIN_VIO *vio, + struct MYSQL *mysql, + int *result); }; typedef struct auth_plugin_t st_mysql_client_plugin_AUTHENTICATION; struct st_mysql_client_plugin *mysql_load_plugin(struct MYSQL *mysql, @@ -309,7 +324,7 @@ void finish_client_errs(void); extern const char *client_errors[]; static inline const char *ER_CLIENT(int client_errno) { - if (client_errno >= 2000 && client_errno <= 2064) + if (client_errno >= 2000 && client_errno <= 2065) return client_errors[client_errno - 2000]; return client_errors[2000]; } @@ -561,6 +576,20 @@ int mysql_real_query(MYSQL *mysql, const char *q, unsigned long length); MYSQL_RES * mysql_store_result(MYSQL *mysql); MYSQL_RES * mysql_use_result(MYSQL *mysql); +enum net_async_status mysql_real_connect_nonblocking( + MYSQL *mysql, const char *host, const char *user, const char *passwd, + const char *db, unsigned int port, const char *unix_socket, + unsigned long clientflag); +enum net_async_status mysql_send_query_nonblocking( + MYSQL *mysql, const char *query, unsigned long length); +enum net_async_status mysql_real_query_nonblocking( + MYSQL *mysql, const char *query, unsigned long length); +enum net_async_status +mysql_store_result_nonblocking(MYSQL *mysql, MYSQL_RES **result); +enum net_async_status mysql_next_result_nonblocking(MYSQL *mysql); +enum net_async_status mysql_select_db_nonblocking(MYSQL *mysql, + const char *db, + bool *error); void mysql_get_character_set_info(MYSQL *mysql, MY_CHARSET_INFO *charset); int mysql_session_track_get_first(MYSQL *mysql, @@ -600,12 +629,15 @@ int mysql_get_option(MYSQL *mysql, enum mysql_option option, const void *arg); void mysql_free_result(MYSQL_RES *result); +enum net_async_status mysql_free_result_nonblocking(MYSQL_RES *result); void mysql_data_seek(MYSQL_RES *result, my_ulonglong offset); MYSQL_ROW_OFFSET mysql_row_seek(MYSQL_RES *result, MYSQL_ROW_OFFSET offset); MYSQL_FIELD_OFFSET mysql_field_seek(MYSQL_RES *result, MYSQL_FIELD_OFFSET offset); MYSQL_ROW mysql_fetch_row(MYSQL_RES *result); +enum net_async_status mysql_fetch_row_nonblocking(MYSQL_RES *res, + MYSQL_ROW *row); unsigned long * mysql_fetch_lengths(MYSQL_RES *result); MYSQL_FIELD * mysql_fetch_field(MYSQL_RES *result); MYSQL_RES * mysql_list_fields(MYSQL *mysql, const char *table, diff --git a/include/mysql/client_authentication.h b/include/mysql/client_authentication.h index cc366f54f92a..c841b9753314 100644 --- a/include/mysql/client_authentication.h +++ b/include/mysql/client_authentication.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -35,5 +35,9 @@ int sha256_password_deinit(void); int caching_sha2_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); int caching_sha2_password_init(char *, size_t, int, va_list); int caching_sha2_password_deinit(void); - +net_async_status caching_sha2_password_auth_client_nonblocking( + MYSQL_PLUGIN_VIO *vio, MYSQL *mysql, int *res); +net_async_status sha256_password_auth_client_nonblocking(MYSQL_PLUGIN_VIO *vio, + MYSQL *mysql, + int *res); #endif diff --git a/include/mysql/client_plugin.h b/include/mysql/client_plugin.h index 3470a56269a7..96e481b33ddd 100644 --- a/include/mysql/client_plugin.h +++ b/include/mysql/client_plugin.h @@ -1,5 +1,5 @@ #ifndef MYSQL_CLIENT_PLUGIN_INCLUDED -/* Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -75,7 +75,7 @@ extern "C" { #define MYSQL_CLIENT_AUTHENTICATION_PLUGIN 2 #define MYSQL_CLIENT_TRACE_PLUGIN 3 -#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION 0x0100 +#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION 0x0101 #define MYSQL_CLIENT_TRACE_PLUGIN_INTERFACE_VERSION 0x0100 #define MYSQL_CLIENT_MAX_PLUGINS 4 @@ -113,6 +113,9 @@ struct MYSQL; struct auth_plugin_t { MYSQL_CLIENT_PLUGIN_HEADER int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, struct MYSQL *mysql); + enum net_async_status (*authenticate_user_nonblocking)(MYSQL_PLUGIN_VIO *vio, + struct MYSQL *mysql, + int *result); }; // Needed for the mysql_declare_client_plugin() macro. Do not use elsewhere. diff --git a/include/mysql/client_plugin.h.pp b/include/mysql/client_plugin.h.pp index 07238a9398c9..fff16b75b669 100644 --- a/include/mysql/client_plugin.h.pp +++ b/include/mysql/client_plugin.h.pp @@ -13,16 +13,31 @@ } protocol; int socket; }; +enum net_async_status { + NET_ASYNC_COMPLETE = 0, + NET_ASYNC_NOT_READY, + NET_ASYNC_ERROR, + NET_ASYNC_COMPLETE_WITH_MORE_RESULTS +}; typedef struct MYSQL_PLUGIN_VIO { int (*read_packet)(struct MYSQL_PLUGIN_VIO *vio, unsigned char **buf); int (*write_packet)(struct MYSQL_PLUGIN_VIO *vio, const unsigned char *packet, int packet_len); void (*info)(struct MYSQL_PLUGIN_VIO *vio, struct MYSQL_PLUGIN_VIO_INFO *info); + enum net_async_status (*read_packet_nonblocking)(struct MYSQL_PLUGIN_VIO *vio, + unsigned char **buf, + int *result); + enum net_async_status (*write_packet_nonblocking)( + struct MYSQL_PLUGIN_VIO *vio, const unsigned char *pkt, int pkt_len, + int *result); } MYSQL_PLUGIN_VIO; struct auth_plugin_t { int type; unsigned int interface_version; const char *name; const char *author; const char *desc; unsigned int version[3]; const char *license; void *mysql_api; int (*init)(char *, size_t, int, va_list); int (*deinit)(void); int (*options)(const char *option, const void *); int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, struct MYSQL *mysql); + enum net_async_status (*authenticate_user_nonblocking)(MYSQL_PLUGIN_VIO *vio, + struct MYSQL *mysql, + int *result); }; typedef struct auth_plugin_t st_mysql_client_plugin_AUTHENTICATION; struct st_mysql_client_plugin *mysql_load_plugin(struct MYSQL *mysql, diff --git a/include/mysql/plugin_auth.h.pp b/include/mysql/plugin_auth.h.pp index 740ac7c2a123..ed21058d520c 100644 --- a/include/mysql/plugin_auth.h.pp +++ b/include/mysql/plugin_auth.h.pp @@ -130,12 +130,24 @@ } protocol; int socket; }; +enum net_async_status { + NET_ASYNC_COMPLETE = 0, + NET_ASYNC_NOT_READY, + NET_ASYNC_ERROR, + NET_ASYNC_COMPLETE_WITH_MORE_RESULTS +}; typedef struct MYSQL_PLUGIN_VIO { int (*read_packet)(struct MYSQL_PLUGIN_VIO *vio, unsigned char **buf); int (*write_packet)(struct MYSQL_PLUGIN_VIO *vio, const unsigned char *packet, int packet_len); void (*info)(struct MYSQL_PLUGIN_VIO *vio, struct MYSQL_PLUGIN_VIO_INFO *info); + enum net_async_status (*read_packet_nonblocking)(struct MYSQL_PLUGIN_VIO *vio, + unsigned char **buf, + int *result); + enum net_async_status (*write_packet_nonblocking)( + struct MYSQL_PLUGIN_VIO *vio, const unsigned char *pkt, int pkt_len, + int *result); } MYSQL_PLUGIN_VIO; struct MYSQL_SERVER_AUTH_INFO { char *user_name; diff --git a/include/mysql/plugin_auth_common.h b/include/mysql/plugin_auth_common.h index 128e406f1710..47bf4bc32533 100644 --- a/include/mysql/plugin_auth_common.h +++ b/include/mysql/plugin_auth_common.h @@ -1,5 +1,5 @@ #ifndef MYSQL_PLUGIN_AUTH_COMMON_INCLUDED -/* Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -126,6 +126,14 @@ struct MYSQL_PLUGIN_VIO_INFO { #endif }; +/* state of an asynchronous operation */ +enum net_async_status { + NET_ASYNC_COMPLETE = 0, + NET_ASYNC_NOT_READY, + NET_ASYNC_ERROR, + NET_ASYNC_COMPLETE_WITH_MORE_RESULTS +}; + /** Provides plugin access to communication channel */ @@ -151,6 +159,24 @@ typedef struct MYSQL_PLUGIN_VIO { void (*info)(struct MYSQL_PLUGIN_VIO *vio, struct MYSQL_PLUGIN_VIO_INFO *info); + /** + Non blocking version of read_packet. This function points buf to starting + position of incoming packet. When this function returns NET_ASYNC_NOT_READY + plugin should call this function again until all incoming packets are read. + If return code is NET_ASYNC_COMPLETE, plugin can do further processing of + read packets. + */ + enum net_async_status (*read_packet_nonblocking)(struct MYSQL_PLUGIN_VIO *vio, + unsigned char **buf, + int *result); + /** + Non blocking version of write_packet. Sends data available in pkt of length + pkt_len to server in asynchrnous way. + */ + enum net_async_status (*write_packet_nonblocking)( + struct MYSQL_PLUGIN_VIO *vio, const unsigned char *pkt, int pkt_len, + int *result); + } MYSQL_PLUGIN_VIO; #endif diff --git a/include/mysql_async.h b/include/mysql_async.h new file mode 100644 index 000000000000..0f8368e722ed --- /dev/null +++ b/include/mysql_async.h @@ -0,0 +1,199 @@ +/* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program 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.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifndef MYSQL_ASYNC_INCLUDED +#define MYSQL_ASYNC_INCLUDED + +#define MYSQL_ASYNC_INCLUDED + +#include + +/* + NOTE:this file should not be included as part of packaging. +*/ +/* Async MySQL extension fields here. */ + +/* + This enum is to represent different asynchronous operations like reading the + network, writing to network, idle state, or complete state. +*/ +enum net_async_operation { + NET_ASYNC_OP_IDLE = 0, /* default state */ + NET_ASYNC_OP_READING, /* used by my_net_read calls */ + NET_ASYNC_OP_WRITING, /* used by my_net_write calls */ + NET_ASYNC_OP_COMPLETE /* network read or write is complete */ +}; + +/* Reading a packet is a multi-step process, so we have a state machine. */ +enum net_async_read_packet_state { + NET_ASYNC_PACKET_READ_IDLE = 0, /* default packet read state */ + NET_ASYNC_PACKET_READ_HEADER, /* read packet header */ + NET_ASYNC_PACKET_READ_BODY, /* read packet contents */ + NET_ASYNC_PACKET_READ_COMPLETE /* state to define if packet is + completely read */ +}; + +/* Different states when reading a query result. */ +enum net_read_query_result_status { + NET_ASYNC_READ_QUERY_RESULT_IDLE = 0, /* default state */ + NET_ASYNC_READ_QUERY_RESULT_FIELD_COUNT, /* read Ok or read field + count sent as part of + COM_QUERY */ + NET_ASYNC_READ_QUERY_RESULT_FIELD_INFO /* read result of above + COM_* command */ +}; + +/* Sending a command involves the write as well as reading the status. */ +enum net_send_command_status { + NET_ASYNC_SEND_COMMAND_IDLE = 0, /* default send command state */ + NET_ASYNC_SEND_COMMAND_WRITE_COMMAND, /* send COM_* command */ + NET_ASYNC_SEND_COMMAND_READ_STATUS /* read result of above COM_* + command */ +}; + +/* + Async operations are broadly classified into 3 phases: + Connection phase, phase of sending data to server (which is writing phase) + and reading data from server (which is reading phase). Below enum describes + the same +*/ +enum net_async_block_state { + NET_NONBLOCKING_CONNECT = 0, + NET_NONBLOCKING_READ, + NET_NONBLOCKING_WRITE +}; + +struct io_vec { + void *iov_base; /* Starting address */ + size_t iov_len; /* Number of bytes to transfer */ +}; + +typedef struct NET_ASYNC { + /* The position in buff we continue reads from when data is next available */ + unsigned char *cur_pos; + /** Blocking state */ + enum net_async_block_state async_blocking_state; + /** Our current operation */ + enum net_async_operation async_operation; + /** How many bytes we want to read */ + size_t async_bytes_wanted; + /* + Simple state to know if we're reading the first row, and + command/query statuses. + */ + bool read_rows_is_first_read; + enum net_send_command_status async_send_command_status; + enum net_read_query_result_status async_read_query_result_status; + + /* State when waiting on an async read */ + enum net_async_read_packet_state async_packet_read_state; + /* Size of the packet we're currently reading */ + size_t async_packet_length; + + /* + Headers and vector for our async writes; see net_serv.c for + detailed description. + */ + unsigned char *async_write_headers; + struct io_vec *async_write_vector; + size_t async_write_vector_size; + size_t async_write_vector_current; + unsigned char + inline_async_write_header[NET_HEADER_SIZE + COMP_HEADER_SIZE + 1 + 1]; + struct io_vec inline_async_write_vector[3]; + + /* State for reading responses that are larger than MAX_PACKET_LENGTH */ + unsigned long async_multipacket_read_saved_whereb; + unsigned long async_multipacket_read_total_len; + bool async_multipacket_read_started; +} NET_ASYNC; + +struct NET_EXTENSION { + NET_ASYNC *net_async_context; +}; + +NET_EXTENSION *net_extension_init(); +void net_extension_free(NET *); + +#define NET_EXTENSION_PTR(N) \ + ((NET_EXTENSION *)((N)->extension ? (N)->extension : NULL)) + +#define NET_ASYNC_DATA(M) \ + ((NET_EXTENSION_PTR(M)) ? (NET_EXTENSION_PTR(M)->net_async_context) : NULL) + +/* + Asynchronous operations are broadly classified into 2 categories. + 1. Connection + 2. Query execution + This classification is defined in below enum +*/ +enum mysql_async_operation_status { + ASYNC_OP_UNSET = 0, + ASYNC_OP_CONNECT, + ASYNC_OP_QUERY +}; + +/* + Query execution in an asynchronous fashion is broadly divided into 3 states + which is described in below enum +*/ +enum mysql_async_query_state_enum { + QUERY_IDLE = 0, + QUERY_SENDING, + QUERY_READING_RESULT +}; + +typedef struct MYSQL_ASYNC { + /* Buffer storing the rows result for cli_read_rows_nonblocking */ + MYSQL_DATA *rows_result_buffer; + /* a pointer to keep track of the previous row of the current result row */ + MYSQL_ROWS **prev_row_ptr; + /* Context needed to track the state of a connection being established */ + struct mysql_async_connect *connect_context; + /* Status of the current async op */ + enum mysql_async_operation_status async_op_status; + /* Size of the current running async query */ + size_t async_query_length; + /* If a query is running, this is its state */ + enum mysql_async_query_state_enum async_query_state; + /* context needed to support metadata read operation */ + unsigned long *async_read_metadata_field_len; + MYSQL_FIELD *async_read_metadata_fields; + MYSQL_ROWS async_read_metadata_data; + unsigned int async_read_metadata_cur_field; + /* a pointer to keep track of the result sets */ + struct MYSQL_RES *async_store_result_result; +} MYSQL_ASYNC; + +enum net_async_status my_net_write_nonblocking(NET *net, + const unsigned char *packet, + size_t len, bool *res); +enum net_async_status net_write_command_nonblocking( + NET *net, unsigned char command, const unsigned char *prefix, + size_t prefix_len, const unsigned char *packet, size_t packet_len, + bool *res); +enum net_async_status my_net_read_nonblocking(NET *net, unsigned long *len_ptr, + unsigned long *complen_ptr); + +int mysql_get_socket_descriptor(MYSQL *mysql); + +#endif /* MYSQL_ASYNC_INCLUDED */ diff --git a/include/mysql_com.h b/include/mysql_com.h index ced715c2328a..ecdb774ad771 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -1001,7 +1001,6 @@ bool net_write_command(struct NET *net, unsigned char command, bool net_write_packet(struct NET *net, const unsigned char *packet, size_t length); unsigned long my_net_read(struct NET *net); - void my_net_set_write_timeout(struct NET *net, unsigned int timeout); void my_net_set_read_timeout(struct NET *net, unsigned int timeout); void my_net_set_retry_count(struct NET *net, unsigned int retry_count); diff --git a/include/sql_common.h b/include/sql_common.h index 2e9f4c832e7d..48c0b21689dc 100644 --- a/include/sql_common.h +++ b/include/sql_common.h @@ -94,6 +94,8 @@ struct st_mysql_trace_info; struct MYSQL_EXTENSION { struct st_mysql_trace_info *trace_data; STATE_INFO state_change; + /* Struct to track the state of asynchronous operations */ + struct MYSQL_ASYNC *mysql_async_context; }; /* "Constructor/destructor" for MYSQL extension structure. */ @@ -109,6 +111,9 @@ void mysql_extension_free(MYSQL_EXTENSION *); ? (H)->extension \ : ((H)->extension = mysql_extension_init(H)))) +#define ASYNC_DATA(M) \ + (NULL != (M) ? (MYSQL_EXTENSION_PTR(M)->mysql_async_context) : NULL) + struct st_mysql_options_extention { char *plugin_dir; char *default_auth; @@ -151,6 +156,21 @@ struct MYSQL_METHODS { int (*read_rows_from_cursor)(MYSQL_STMT *stmt); void (*free_rows)(MYSQL_DATA *cur); #endif + enum net_async_status (*read_query_result_nonblocking)(MYSQL *mysql); + enum net_async_status (*advanced_command_nonblocking)( + MYSQL *mysql, enum enum_server_command command, + const unsigned char *header, unsigned long header_length, + const unsigned char *arg, unsigned long arg_length, bool skip_check, + MYSQL_STMT *stmt, bool *error); + enum net_async_status (*read_rows_nonblocking)(MYSQL *mysql, + MYSQL_FIELD *mysql_fields, + unsigned int fields, + MYSQL_DATA **result); + enum net_async_status (*flush_use_result_nonblocking)(MYSQL *mysql, + bool flush_all_results); + enum net_async_status (*next_result_nonblocking)(MYSQL *mysql); + enum net_async_status (*read_change_user_result_nonblocking)(MYSQL *mysql, + ulong *res); }; #define simple_command(mysql, command, arg, length, skip_check) \ @@ -159,6 +179,11 @@ struct MYSQL_METHODS { length, skip_check, NULL) \ : (set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate), \ 1)) +#define simple_command_nonblocking(mysql, command, arg, length, skip_check, \ + error) \ + (*(mysql)->methods->advanced_command_nonblocking)( \ + mysql, command, 0, 0, arg, length, skip_check, NULL, error) + #define stmt_command(mysql, command, arg, length, stmt) \ ((mysql)->methods \ ? (*(mysql)->methods->advanced_command)(mysql, command, 0, 0, arg, \ @@ -186,6 +211,9 @@ bool cli_advanced_command(MYSQL *mysql, enum enum_server_command command, const unsigned char *arg, size_t arg_length, bool skip_check, MYSQL_STMT *stmt); unsigned long cli_safe_read(MYSQL *mysql, bool *is_data_packet); +enum net_async_status cli_safe_read_nonblocking(MYSQL *mysql, + bool *is_data_packet, + ulong *res); unsigned long cli_safe_read_with_ok(MYSQL *mysql, bool parse_ok, bool *is_data_packet); void net_clear_error(NET *net); diff --git a/include/violite.h b/include/violite.h index c311747d6c7b..c11c77b42842 100644 --- a/include/violite.h +++ b/include/violite.h @@ -129,6 +129,10 @@ enum enum_vio_io_event { 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 */ @@ -154,6 +158,9 @@ void vio_delete(MYSQL_VIO vio); int vio_shutdown(MYSQL_VIO vio); bool vio_reset(MYSQL_VIO vio, enum enum_vio_type type, 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_set_blocking_flag(Vio *vio, bool set_blocking_flag); 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); @@ -186,7 +193,7 @@ ssize_t vio_pending(MYSQL_VIO vio); int vio_timeout(MYSQL_VIO vio, uint which, int timeout_sec); /* Connect to a peer. */ bool vio_socket_connect(MYSQL_VIO vio, struct sockaddr *addr, socklen_t len, - int timeout); + bool nonblocking, int timeout); bool vio_get_normalized_ip_string(const struct sockaddr *addr, size_t addr_length, char *ip_string, @@ -258,7 +265,7 @@ struct st_VioSSLFd { int sslaccept(struct st_VioSSLFd *, MYSQL_VIO, long timeout, unsigned long *errptr); int sslconnect(struct st_VioSSLFd *, MYSQL_VIO, long timeout, - unsigned long *errptr); + unsigned long *errptr, SSL **ssl); struct st_VioSSLFd *new_VioSSLConnectorFd( const char *key_file, const char *cert_file, const char *ca_file, @@ -301,6 +308,9 @@ void vio_end(void); (vio)->peer_addr(vio, buf, prt, buflen) #define vio_io_wait(vio, event, timeout) (vio)->io_wait(vio, event, timeout) #define vio_is_connected(vio) (vio)->is_connected(vio) +#define vio_is_blocking(vio) (vio)->is_blocking(vio) +#define vio_set_blocking(vio, val) (vio)->set_blocking(vio, val) +#define vio_set_blocking_flag(vio, val) (vio)->set_blocking_flag(vio, val) #endif /* !defined(DONT_MAP_VIO) */ /* This enumerator is used in parser - should be always visible */ @@ -410,7 +420,13 @@ struct Vio { HANDLE event_conn_closed = {nullptr}; size_t shared_memory_remain = {0}; char *shared_memory_pos = {nullptr}; + #endif /* _WIN32 */ + bool (*is_blocking)(Vio *vio) = {nullptr}; + int (*set_blocking)(Vio *vio, bool val) = {nullptr}; + int (*set_blocking_flag)(Vio *vio, bool val) = {nullptr}; + /* Indicates whether socket or SSL based communication is blocking or not. */ + bool is_blocking_flag = {true}; private: friend Vio *internal_vio_create(uint flags); diff --git a/libmysql/CMakeLists.txt b/libmysql/CMakeLists.txt index cfba88db3804..ff78e387e610 100644 --- a/libmysql/CMakeLists.txt +++ b/libmysql/CMakeLists.txt @@ -140,6 +140,13 @@ mysql_session_track_get_first mysql_session_track_get_next mysql_reset_server_public_key mysql_result_metadata +mysql_real_connect_nonblocking +mysql_send_query_nonblocking +mysql_real_query_nonblocking +mysql_store_result_nonblocking +mysql_next_result_nonblocking +mysql_fetch_row_nonblocking +mysql_free_result_nonblocking CACHE INTERNAL "Functions exported by client API" diff --git a/libmysql/authentication_ldap/auth_ldap_sasl_client.cc b/libmysql/authentication_ldap/auth_ldap_sasl_client.cc index 6788d744020f..0dcd9c4a0d8a 100644 --- a/libmysql/authentication_ldap/auth_ldap_sasl_client.cc +++ b/libmysql/authentication_ldap/auth_ldap_sasl_client.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -360,4 +360,4 @@ static int sasl_authenticate(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { mysql_declare_client_plugin(AUTHENTICATION) "authentication_ldap_sasl_client", "Yashwant Sahu", "LDAP SASL Client Authentication Plugin", {0, 1, 0}, "PROPRIETARY", NULL, NULL, NULL, NULL, - sasl_authenticate mysql_end_client_plugin; + sasl_authenticate, NULL mysql_end_client_plugin; diff --git a/libmysql/errmsg.cc b/libmysql/errmsg.cc index 638dc4e66fe8..a7d8d4ef971d 100644 --- a/libmysql/errmsg.cc +++ b/libmysql/errmsg.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -105,6 +105,7 @@ const char *client_errors[] = { "Insecure API function call: '%s' Use instead: '%s'", "File name is too long", "Set FIPS mode ON/STRICT failed", + "Compression protocol not supported with asynchronous protocol", ""}; static const char *get_client_errmsg(int nr) { diff --git a/libmysql/libmysql.cc b/libmysql/libmysql.cc index 50258149ab96..784b3686cfe7 100644 --- a/libmysql/libmysql.cc +++ b/libmysql/libmysql.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -4542,6 +4542,40 @@ int STDCALL mysql_next_result(MYSQL *mysql) { DBUG_RETURN(-1); /* No more results */ } +/* + This API reads the next statement result and returns a status to indicate + whether more results exist + + @param[in] mysql connection handle + + @retval NET_ASYNC_ERROR Error + @retval NET_ASYNC_NOT_READY reading next result not + yet completed, call + this API again + @retval NET_ASYNC_COMPLETE finished reading result + @retval NET_ASYNC_COMPLETE_WITH_MORE_RESULTS status to indicate if + more results exist +*/ +net_async_status STDCALL mysql_next_result_nonblocking(MYSQL *mysql) { + DBUG_ENTER(__func__); + net_async_status status; + if (mysql->status != MYSQL_STATUS_READY) { + set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); + DBUG_RETURN(NET_ASYNC_ERROR); + } + net_clear_error(&mysql->net); + mysql->affected_rows = ~(my_ulonglong)0; + + if (mysql->server_status & SERVER_MORE_RESULTS_EXISTS) { + status = (*mysql->methods->next_result_nonblocking)(mysql); + DBUG_RETURN(status); + } else { + MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); + } + + DBUG_RETURN(NET_ASYNC_COMPLETE_WITH_MORE_RESULTS); /* No more results */ +} + int STDCALL mysql_stmt_next_result(MYSQL_STMT *stmt) { MYSQL *mysql = stmt->mysql; int rc; diff --git a/mysql-test/mysql-test-run.dox b/mysql-test/mysql-test-run.dox index e49ada0fdb3d..d83f5bf3d105 100644 --- a/mysql-test/mysql-test-run.dox +++ b/mysql-test/mysql-test-run.dox @@ -3117,6 +3117,12 @@ Display a help message and exit. +
  • + `--async-client` + + Enable asynchronous communication support in mysql protocol. +
  • +
  • `--basedir`=dir_name, -b dir_name diff --git a/mysql-test/mysql-test-run.pl b/mysql-test/mysql-test-run.pl index 5555abfde75b..6067464f013a 100755 --- a/mysql-test/mysql-test-run.pl +++ b/mysql-test/mysql-test-run.pl @@ -88,6 +88,7 @@ my $opt_callgrind; my $opt_charset_for_testdb; my $opt_compress; +my $opt_async_client; my $opt_cursor_protocol; my $opt_debug_common; my $opt_do_suite; @@ -1306,6 +1307,7 @@ sub print_global_resfile { resfile_global("callgrind", $opt_callgrind ? 1 : 0); resfile_global("check-testcases", $opt_check_testcases ? 1 : 0); resfile_global("compress", $opt_compress ? 1 : 0); + resfile_global("async-client", $opt_async_client ? 1 : 0); resfile_global("cursor-protocol", $opt_cursor_protocol ? 1 : 0); resfile_global("debug", $opt_debug ? 1 : 0); resfile_global("fast", $opt_fast ? 1 : 0); @@ -1368,6 +1370,7 @@ sub command_line_setup { my %options = ( # Control what engine/variation to run 'compress' => \$opt_compress, + 'async-client' => \$opt_async_client, 'cursor-protocol' => \$opt_cursor_protocol, 'explain-protocol' => \$opt_explain_protocol, 'json-explain-protocol' => \$opt_json_explain_protocol, @@ -6596,6 +6599,10 @@ ($) mtr_add_arg($args, "--compress"); } + if ($opt_async_client ) { + mtr_add_arg($args, "--async-client"); + } + if ($opt_sleep) { mtr_add_arg($args, "--sleep=%d", $opt_sleep); } @@ -7148,6 +7155,7 @@ ($) combination= Use at least twice to run tests with specified options to mysqld. compress Use the compressed protocol between client and server. + async-client Use async-client with select() to run the test case cursor-protocol Use the cursor protocol between client and server (implies --ps-protocol). defaults-extra-file= diff --git a/mysql-test/r/async_client.result b/mysql-test/r/async_client.result new file mode 100644 index 000000000000..2736fb54d0ac --- /dev/null +++ b/mysql-test/r/async_client.result @@ -0,0 +1,95 @@ +# +# WL#11381: Add asynchronous support into the mysql protocol +# +# case1: default connection with default authentication plugin +CREATE DATABASE wl11381; +CREATE USER caching_sha2@localhost IDENTIFIED BY 'caching'; +GRANT ALL ON *.* TO caching_sha2@localhost; +# connect as caching_sha2 +SELECT USER(), DATABASE(); +USER() DATABASE() +caching_sha2@localhost wl11381 +USE wl11381; +CREATE TABLE t1(i INT, j VARCHAR(2048)); +INSERT INTO t1 VALUES(1,repeat('a',1000)),(2,repeat('def',600)); +SELECT * FROM t1; +i j +1 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +2 defdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdefdef +# case2: request a large packet +SET GLOBAL max_allowed_packet=4*1024; +Warnings: +Warning 1708 The value of 'max_allowed_packet' should be no less than the value of 'net_buffer_length' +SELECT SPACE(@@global.max_allowed_packet); +SPACE(@@global.max_allowed_packet) + +SET GLOBAL max_allowed_packet=default; +# connect with wrong password +connect(localhost,caching_sha2,caching1,wl11381,MASTER_PORT,MASTER_SOCKET); +ERROR 28000: Access denied for user 'caching_sha2'@'localhost' (using password: YES) +# check for timeout +SELECT USER(); +USER() +caching_sha2@localhost +SET @@SESSION.wait_timeout = 2; +SELECT SLEEP(10); +SLEEP(10) +0 +# Check that ssl_con has not disconnected +SELECT 1; +1 +1 +# lock the account +ALTER USER caching_sha2@localhost ACCOUNT LOCK; +# account is locked so connect should fail +connect(localhost,caching_sha2,caching,wl11381,MASTER_PORT,MASTER_SOCKET); +ERROR HY000: Access denied for user 'caching_sha2'@'localhost'. Account is locked. +# lock the account +ALTER USER caching_sha2@localhost ACCOUNT UNLOCK; +# account is unlocked so connect should pass +SELECT "connect succeeded after account is unlocked"; +connect succeeded after account is unlocked +connect succeeded after account is unlocked +# restart: --ssl=off --loose-caching_sha2_password_private_key_path=MYSQL_TEST_DIR/std_data/rsa_private_key.pem --loose-caching_sha2_password_public_key_path=MYSQL_TEST_DIR/std_data/rsa_public_key.pem +# connect as caching_sha2 with SSL disabled +SELECT USER(); +USER() +caching_sha2@localhost +# change to empty password +ALTER USER caching_sha2@localhost IDENTIFIED BY ''; +# connect as caching_sha2 with SSL disabled and empty password +SELECT USER(); +USER() +caching_sha2@localhost +# case3: authenticate user with sha256_password +CREATE USER sha256@localhost IDENTIFIED WITH 'sha256_password' BY 'auth_string'; +# restart: --default-authentication-plugin=sha256_password +# connect as sha256 +SELECT USER(); +USER() +sha256@localhost +# change to empty password +ALTER USER sha256@localhost IDENTIFIED BY ''; +# connect with empty password +SELECT USER(); +USER() +sha256@localhost +# restart: --default-authentication-plugin=sha256_password --ssl=off --loose-sha256_password_private_key_path=MYSQL_TEST_DIR/std_data/rsa_private_key.pem --loose-sha256_password_public_key_path=MYSQL_TEST_DIR/std_data/rsa_public_key.pem +# connect with wrong password and SSL disabled +connect(localhost,sha256,auth_string,test,MASTER_PORT,MASTER_SOCKET); +ERROR 28000: Access denied for user 'sha256'@'localhost' (using password: YES) +# case4: authenticate user with mysql_native_password +CREATE USER native_user@localhost IDENTIFIED WITH 'mysql_native_password' BY 'native'; +# restart: --default-authentication-plugin=mysql_native_password +# connect as native_user +SELECT USER(); +USER() +native_user@localhost +# Change to empty password +ALTER USER native_user@localhost IDENTIFIED BY ''; +# connect as native_user with empty password +SELECT USER(); +USER() +native_user@localhost +DROP USER sha256@localhost, native_user@localhost, caching_sha2@localhost; +DROP DATABASE wl11381; diff --git a/mysql-test/r/big_packets.result b/mysql-test/r/big_packets.result new file mode 100644 index 000000000000..dac269c8e023 --- /dev/null +++ b/mysql-test/r/big_packets.result @@ -0,0 +1,6 @@ +MD5("zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +91e7da4395bd55166023dcddc56c69b9 +LENGTH("zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +33554448 +Q zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring diff --git a/mysql-test/r/big_packets_boundary.result b/mysql-test/r/big_packets_boundary.result new file mode 100644 index 000000000000..4e9309c74093 --- /dev/null +++ b/mysql-test/r/big_packets_boundary.result @@ -0,0 +1,96 @@ +MD5("zXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +c871179747a53194105131ef0ba4328d +LENGTH("zXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777209 +Q zXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +8de4762b7dee325e24af556907f303e4 +LENGTH("zzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777210 +Q zzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +830292a7f4c58ab20717bc6c8d0600da +LENGTH("zzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777211 +Q zzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +7256c4833815dd42ad9b046b5ca053b0 +LENGTH("zzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777212 +Q zzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +c57ddebd2c507217cf52dec95e123d11 +LENGTH("zzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777213 +Q zzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16658c20e22780cefdfd281c6b650c44 +LENGTH("zzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777214 +Q zzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +6db235989af46461a519808ea756a0b9 +LENGTH("zzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777215 +Q zzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +707fcc0cc24f79bc3d2e11a65f61fcee +LENGTH("zzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777216 +Q zzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +01dcbdde50804add0f06a21eaa3b4bb5 +LENGTH("zzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777217 +Q zzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +a4d792fcfed7cab49499a04aaf78d3ca +LENGTH("zzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777218 +Q zzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +5fb1131f61d61f5a5fff367293ec3adb +LENGTH("zzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777219 +Q zzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +b5f8256cc4dac2694cd2b9296370e4bb +LENGTH("zzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777220 +Q zzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +db5649bd5c180405e68301b24bc4182d +LENGTH("zzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777221 +Q zzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +50d94b74220b7aceed6810e8e7558a3e +LENGTH("zzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777222 +Q zzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +badcb2ccca2595fd66d07e2ba8457c6b +LENGTH("zzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777223 +Q zzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring +MD5("zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +bf6cd230abc95d80ecae961868db39f5 +LENGTH("zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +16777224 +Q zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring diff --git a/mysql-test/r/ssl-big-packet.result b/mysql-test/r/ssl-big-packet.result new file mode 100644 index 000000000000..dac269c8e023 --- /dev/null +++ b/mysql-test/r/ssl-big-packet.result @@ -0,0 +1,6 @@ +MD5("zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +91e7da4395bd55166023dcddc56c69b9 +LENGTH("zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +33554448 +Q zzzzzzzzzzzzzzzzXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +verylongstring diff --git a/mysql-test/t/async_client.test b/mysql-test/t/async_client.test new file mode 100644 index 000000000000..7a6afbb81f0a --- /dev/null +++ b/mysql-test/t/async_client.test @@ -0,0 +1,168 @@ +--source include/have_ssl.inc +--source include/have_openssl.inc + +# Save the initial number of concurrent sessions +--source include/count_sessions.inc + +--echo # +--echo # WL#11381: Add asynchronous support into the mysql protocol +--echo # + +--echo # case1: default connection with default authentication plugin +CREATE DATABASE wl11381; +CREATE USER caching_sha2@localhost IDENTIFIED BY 'caching'; +GRANT ALL ON *.* TO caching_sha2@localhost; + +--enable_async_client + +--echo # connect as caching_sha2 +connect(caching_con1,localhost,caching_sha2,caching,wl11381,,,SSL); +SELECT USER(), DATABASE(); + +USE wl11381; +CREATE TABLE t1(i INT, j VARCHAR(2048)); +INSERT INTO t1 VALUES(1,repeat('a',1000)),(2,repeat('def',600)); +SELECT * FROM t1; + +--echo # case2: request a large packet +SET GLOBAL max_allowed_packet=4*1024; +SELECT SPACE(@@global.max_allowed_packet); +SET GLOBAL max_allowed_packet=default; + +connection default; +disconnect caching_con1; + +--echo # connect with wrong password +--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT +--error ER_ACCESS_DENIED_ERROR +connect(caching_con2,localhost,caching_sha2,caching1,wl11381,,,SSL); + +--echo # check for timeout +connect(caching_con3,localhost,caching_sha2,caching,wl11381,,,SSL); +SELECT USER(); +SET @@SESSION.wait_timeout = 2; +SELECT SLEEP(10); + +--echo # Check that ssl_con has not disconnected +SELECT 1; + +connection default; +disconnect caching_con3; + +--disable_async_client +--echo # lock the account +ALTER USER caching_sha2@localhost ACCOUNT LOCK; +--enable_async_client + +--echo # account is locked so connect should fail +--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT +--error 3118 +connect(caching_con3,localhost,caching_sha2,caching,wl11381,,,SSL); + +connection default; +--disable_async_client +--echo # lock the account +ALTER USER caching_sha2@localhost ACCOUNT UNLOCK; +--enable_async_client + +--echo # account is unlocked so connect should pass +connect(caching_con3,localhost,caching_sha2,caching,wl11381,,,SSL); +SELECT "connect succeeded after account is unlocked"; + +connection default; +disconnect caching_con3; + +--disable_async_client +--replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR +--let $restart_parameters=restart: --ssl=off --loose-caching_sha2_password_private_key_path=$MYSQL_TEST_DIR/std_data/rsa_private_key.pem --loose-caching_sha2_password_public_key_path=$MYSQL_TEST_DIR/std_data/rsa_public_key.pem +--source include/restart_mysqld.inc +--enable_async_client + +--echo # connect as caching_sha2 with SSL disabled +connect(caching_con4,localhost,caching_sha2,caching,wl11381,,,); +SELECT USER(); + +connection default; +disconnect caching_con4; + +# default connection on windows is a NAMED_PIPE, on this type of connection +# async operations are not allowed, so disable it. +--disable_async_client +--echo # change to empty password +ALTER USER caching_sha2@localhost IDENTIFIED BY ''; +--enable_async_client + +--echo # connect as caching_sha2 with SSL disabled and empty password +connect(caching_con5,localhost,caching_sha2,,wl11381,,,); +SELECT USER(); + +connection default; +disconnect caching_con5; + +--disable_async_client +--echo # case3: authenticate user with sha256_password +CREATE USER sha256@localhost IDENTIFIED WITH 'sha256_password' BY 'auth_string'; +--let $restart_parameters=restart: --default-authentication-plugin=sha256_password +--source include/restart_mysqld.inc +--enable_async_client + +--echo # connect as sha256 +connect(sha256_con1,localhost,sha256,auth_string,,,,SSL,sha256_password); +SELECT USER(); + +connection default; +disconnect sha256_con1; + +--disable_async_client +--echo # change to empty password +ALTER USER sha256@localhost IDENTIFIED BY ''; +--enable_async_client + +--echo # connect with empty password +connect(sha256_con2,localhost,sha256,,,,,SSL,sha256_password); +SELECT USER(); + +connection default; +disconnect sha256_con2; + +--disable_async_client +--replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR +--let $restart_parameters=restart: --default-authentication-plugin=sha256_password --ssl=off --loose-sha256_password_private_key_path=$MYSQL_TEST_DIR/std_data/rsa_private_key.pem --loose-sha256_password_public_key_path=$MYSQL_TEST_DIR/std_data/rsa_public_key.pem +--source include/restart_mysqld.inc +--enable_async_client + +--echo # connect with wrong password and SSL disabled +--replace_result $MASTER_MYSOCK MASTER_SOCKET $MASTER_MYPORT MASTER_PORT +--error ER_ACCESS_DENIED_ERROR +connect(sha256_con3,localhost,sha256,auth_string,,,,,sha256_password); + +--disable_async_client +--echo # case4: authenticate user with mysql_native_password +CREATE USER native_user@localhost IDENTIFIED WITH 'mysql_native_password' BY 'native'; +--let $restart_parameters=restart: --default-authentication-plugin=mysql_native_password +--source include/restart_mysqld.inc +--enable_async_client + +--echo # connect as native_user +connect(native_con1,localhost,native_user,native,,,,,mysql_native_password); +SELECT USER(); + +connection default; +disconnect native_con1; + +--disable_async_client +--echo # Change to empty password +ALTER USER native_user@localhost IDENTIFIED BY ''; +--enable_async_client + +--echo # connect as native_user with empty password +connect(native_con2,localhost,native_user,,,,,,mysql_native_password); +SELECT USER(); + +connection default; +disconnect native_con2; + +#cleanup +--disable_async_client +DROP USER sha256@localhost, native_user@localhost, caching_sha2@localhost; +DROP DATABASE wl11381; diff --git a/mysql-test/t/big_packets-master.opt b/mysql-test/t/big_packets-master.opt new file mode 100644 index 000000000000..134a09708fdc --- /dev/null +++ b/mysql-test/t/big_packets-master.opt @@ -0,0 +1 @@ +--max_allowed_packet=64M diff --git a/mysql-test/t/big_packets.test b/mysql-test/t/big_packets.test new file mode 100644 index 000000000000..487ab81cd30d --- /dev/null +++ b/mysql-test/t/big_packets.test @@ -0,0 +1,13 @@ +# Test sending and receiving queries that must span packets (ie, +# larger than 16mb). This test is around a 32mb send/receive, testing +# payloads that cross multiple packets. + +let $str = `SELECT REPEAT('X', 33554432)`; +let $str = zzzzzzzzzzzzzzzz$str; + +--disable_query_log +--eval SELECT MD5("$str"); +--eval SELECT LENGTH("$str"); +--replace_column 1 verylongstring +--eval SELECT "Q $str END"; +--enable_query_log diff --git a/mysql-test/t/big_packets_boundary-master.opt b/mysql-test/t/big_packets_boundary-master.opt new file mode 100644 index 000000000000..9a966f9160e2 --- /dev/null +++ b/mysql-test/t/big_packets_boundary-master.opt @@ -0,0 +1 @@ +--max_allowed_packet=128M diff --git a/mysql-test/t/big_packets_boundary.test b/mysql-test/t/big_packets_boundary.test new file mode 100644 index 000000000000..b8191b0a0690 --- /dev/null +++ b/mysql-test/t/big_packets_boundary.test @@ -0,0 +1,18 @@ + +# Test sending and receiving queries that saddle a MAX_PACKET_LENGTH +# boundary (aka 16mb boundary) + +let $str = `SELECT REPEAT('X', 16777208)`; +let $i = 0; + +--disable_query_log +while ($i < 16) +{ + let $str = z$str; + --eval SELECT MD5("$str"); + --eval SELECT LENGTH("$str"); + --replace_column 1 verylongstring + --eval SELECT "Q $str END"; + inc $i; +} +--enable_query_log diff --git a/mysql-test/t/ssl-big-packet-master.opt b/mysql-test/t/ssl-big-packet-master.opt new file mode 100644 index 000000000000..6d3f113b25c1 --- /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=64M diff --git a/mysql-test/t/ssl-big-packet.test b/mysql-test/t/ssl-big-packet.test new file mode 100644 index 000000000000..cdb1e783d4aa --- /dev/null +++ b/mysql-test/t/ssl-big-packet.test @@ -0,0 +1,24 @@ +# Turn on ssl between the client and server and run some big queries +--source include/have_ssl.inc + +# Save the initial number of concurrent sessions +--source include/count_sessions.inc + +connect (ssl_con,localhost,root,,,,,SSL); + +let $str = `SELECT REPEAT('X', 33554432)`; +let $str = zzzzzzzzzzzzzzzz$str; + +--disable_query_log +--eval SELECT MD5("$str") +--eval SELECT LENGTH("$str") +--replace_column 1 verylongstring +--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/mysql-test/valgrind.supp b/mysql-test/valgrind.supp index d373aa6152b4..f556e6e90d86 100644 --- a/mysql-test/valgrind.supp +++ b/mysql-test/valgrind.supp @@ -1,4 +1,4 @@ -# Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2.0, @@ -1959,3 +1959,88 @@ fun:* fun:* } + +# Suppress all issues related to libcrypto library +{ + Conditional jump or move in BN_bin2bn + Memcheck:Cond + fun:BN_bin2bn + obj:/usr/lib64/libcrypto.so.* + fun:* + ... +} +{ + Conditional jump or move in BN_ucmp + Memcheck:Cond + fun:BN_ucmp + ... + obj:/usr/lib64/libcrypto.so.* + fun:* + ... +} +{ + Conditional jump or move in libcrypto + Memcheck:Cond + obj:/usr/lib64/libcrypto.so.* + fun:* + ... +} +{ + Conditional jump or move in BN_mod_exp_mont + Memcheck:Cond + fun:BN_mod_exp_mont + obj:/usr/lib64/libcrypto.so.* + fun:* + ... +} +{ + Use of uninitialised value of size 8 in libcrypto + Memcheck:Value8 + obj:/usr/lib64/libcrypto.so.* + ... +} +{ + Conditional jump or move in BN_mod_mul_montgomery + Memcheck:Cond + fun:BN_mod_mul_montgomery + fun:BN_mod_exp_mont + obj:/usr/lib64/libcrypto.so.* + fun:* + ... +} +{ + Use of uninitialised value of size 8 in libcrypto + Memcheck:Value8 + obj:/usr/lib64/libcrypto.so.* + fun:BN_from_montgomery + fun:BN_mod_exp_mont + obj:* + ... +} +{ + Conditional jump or move in BN_num_bits_word + Memcheck:Cond + fun:BN_num_bits_word + fun:BN_num_bits + obj:/usr/lib64/libcrypto.so.* + fun:* + ... +} +{ + Use of uninitialised value of size 8 in BN_num_bits_word + Memcheck:Value8 + fun:BN_num_bits_word + fun:BN_num_bits + fun:BN_bn2bin + obj:/usr/lib64/libcrypto.so.* + fun:* + ... +} +{ + Syscall param socketcall.sendto(msg) points to uninitialised byte(s) + Memcheck:Param + socketcall.sendto(msg) + fun:send + fun:* + ... +} diff --git a/plugin/auth/dialog.cc b/plugin/auth/dialog.cc index f38eab06997c..728cd43d267d 100644 --- a/plugin/auth/dialog.cc +++ b/plugin/auth/dialog.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -350,4 +350,4 @@ static int init_dialog(char *unused1 MY_ATTRIBUTE((unused)), mysql_declare_client_plugin(AUTHENTICATION) "dialog", "Sergei Golubchik", "Dialog Client Authentication Plugin", {0, 1, 0}, "GPL", NULL, - init_dialog, NULL, NULL, perform_dialog mysql_end_client_plugin; + init_dialog, NULL, NULL, perform_dialog, NULL mysql_end_client_plugin; diff --git a/plugin/auth/qa_auth_client.cc b/plugin/auth/qa_auth_client.cc index bc2223f60cb1..72217c1fd8b7 100644 --- a/plugin/auth/qa_auth_client.cc +++ b/plugin/auth/qa_auth_client.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -111,4 +111,5 @@ static int test_plugin_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { mysql_declare_client_plugin(AUTHENTICATION) "qa_auth_client", "Horst Hunger", "Dialog Client Authentication Plugin", {0, 1, 0}, - "GPL", NULL, NULL, NULL, NULL, test_plugin_client mysql_end_client_plugin; + "GPL", NULL, NULL, NULL, NULL, + test_plugin_client, NULL mysql_end_client_plugin; diff --git a/plugin/auth/qa_auth_interface.cc b/plugin/auth/qa_auth_interface.cc index a1507e619e13..60eab2e63f22 100644 --- a/plugin/auth/qa_auth_interface.cc +++ b/plugin/auth/qa_auth_interface.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -271,4 +271,5 @@ static int test_plugin_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { mysql_declare_client_plugin(AUTHENTICATION) "qa_auth_interface", "Horst Hunger", "Dialog Client Authentication Plugin", {0, 1, 0}, - "GPL", NULL, NULL, NULL, NULL, test_plugin_client mysql_end_client_plugin; + "GPL", NULL, NULL, NULL, NULL, + test_plugin_client, NULL mysql_end_client_plugin; diff --git a/plugin/auth/test_plugin.cc b/plugin/auth/test_plugin.cc index b08a7e087d6a..d292071cbd93 100644 --- a/plugin/auth/test_plugin.cc +++ b/plugin/auth/test_plugin.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -294,4 +294,5 @@ static int test_plugin_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { mysql_declare_client_plugin(AUTHENTICATION) "auth_test_plugin", "Georgi Kodinov", "Dialog Client Authentication Plugin", {0, 1, 0}, - "GPL", NULL, NULL, NULL, NULL, test_plugin_client mysql_end_client_plugin; + "GPL", NULL, NULL, NULL, NULL, + test_plugin_client, NULL mysql_end_client_plugin; diff --git a/plugin/x/client/xconnection_impl.cc b/plugin/x/client/xconnection_impl.cc index 450c2ada11aa..0631b7cbba12 100644 --- a/plugin/x/client/xconnection_impl.cc +++ b/plugin/x/client/xconnection_impl.cc @@ -349,7 +349,7 @@ XError Connection_impl::connect(sockaddr *addr, const std::size_t addr_size) { auto vio = vio_new(s, type, 0); auto error = - vio_socket_connect(vio, addr, static_cast(addr_size), + vio_socket_connect(vio, addr, static_cast(addr_size), false, details::make_vio_timeout( m_context->m_connection_config.m_timeout_connect)); @@ -539,7 +539,7 @@ XError Connection_impl::activate_tls() { // When mode it set to Ssl_config::Mode_ssl_verify_ca // then lower layers are going to verify it unsigned long error; // NOLINT - if (0 != sslconnect(m_vioSslFd, m_vio, 60, &error)) { + if (0 != sslconnect(m_vioSslFd, m_vio, 60, &error, nullptr)) { return get_ssl_error(error); } diff --git a/sql-common/client.cc b/sql-common/client.cc index 42f213fbd7a6..14a749c06cb5 100644 --- a/sql-common/client.cc +++ b/sql-common/client.cc @@ -61,6 +61,7 @@ #include #include +#include "client_async_authentication.h" #include "errmsg.h" #include "lex_string.h" #include "map_helpers.h" @@ -214,6 +215,10 @@ char mysql_server_last_error[MYSQL_ERRMSG_SIZE]; /* forward declaration */ static int read_one_row(MYSQL *mysql, uint fields, MYSQL_ROW row, ulong *lengths); +static net_async_status read_one_row_nonblocking(MYSQL *mysql, uint fields, + MYSQL_ROW row, ulong *lengths, + int *res); + /** Convert the connect timeout option to a timeout value for VIO functions (vio_socket_connect() and vio_io_wait()). @@ -981,6 +986,75 @@ void read_ok_ex(MYSQL *mysql, ulong length) { return; } +/* Helper for cli_safe_read and cli_safe_read_nonblocking */ +static ulong cli_safe_read_with_ok_complete(MYSQL *mysql, bool parse_ok, + bool *is_data_packet, ulong len); + +/** + Read a packet from server in asynchronous way. This function can return + without completly reading the packet, in such a case call this function + again until complete packet is read. + + @param[in] mysql connection handle + @param[in] parse_ok if set to true then parse OK packet if it + was sent by server + @param[out] is_data_packet if set to true then the packet received + was a "data packet". + @param[out] res The length of the packet that was read or + packet_error in case of error. + + @retval NET_ASYNC_NOT_READY packet was not completely read + @retval NET_ASYNC_COMPLETE finished reading packet +*/ +net_async_status cli_safe_read_with_ok_nonblocking(MYSQL *mysql, bool parse_ok, + bool *is_data_packet, + ulong *res) { + NET *net = &mysql->net; + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + ulong len = 0, complen = 0; + DBUG_ENTER(__func__); + + if (net_async->async_multipacket_read_started == false) { + net_async->async_multipacket_read_started = true; + net_async->async_multipacket_read_saved_whereb = net->where_b; + net_async->async_multipacket_read_total_len = 0; + } + + if (net->vio != 0) { + net_async_status status = my_net_read_nonblocking(net, &len, &complen); + if (len != packet_error) { + net_async->async_multipacket_read_total_len += len; + net->where_b += len; + } + + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + } + + net->where_b = net_async->async_multipacket_read_saved_whereb; + net->read_pos = net->buff + net->where_b; + + DBUG_PRINT("info", ("total nb read: %lu", + net_async->async_multipacket_read_total_len)); + *res = cli_safe_read_with_ok_complete( + mysql, parse_ok, is_data_packet, + net_async->async_multipacket_read_total_len); + + net_async->async_multipacket_read_started = false; + net_async->async_multipacket_read_saved_whereb = 0; + + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + +/** + Its a non blocking version of cli_safe_read +*/ +net_async_status cli_safe_read_nonblocking(MYSQL *mysql, bool *is_data_packet, + ulong *res) { + return cli_safe_read_with_ok_nonblocking(mysql, 0, is_data_packet, res); +} + /** Read a packet from server. Give error message if socket was down or packet is an error message @@ -1007,6 +1081,13 @@ ulong cli_safe_read_with_ok(MYSQL *mysql, bool parse_ok, bool *is_data_packet) { if (is_data_packet) *is_data_packet = false; if (net->vio != 0) len = my_net_read(net); + return cli_safe_read_with_ok_complete(mysql, parse_ok, is_data_packet, len); +} + +ulong cli_safe_read_with_ok_complete(MYSQL *mysql, bool parse_ok, + bool *is_data_packet, ulong len) { + NET *net = &mysql->net; + DBUG_ENTER(__func__); if (len == packet_error || len == 0) { char desc[VIO_DESCRIPTION_SIZE]; @@ -1015,7 +1096,7 @@ ulong cli_safe_read_with_ok(MYSQL *mysql, bool parse_ok, bool *is_data_packet) { ("Wrong connection or packet. fd: %s len: %lu", desc, len)); #ifdef MYSQL_SERVER if (net->vio && (net->last_errno == ER_NET_READ_INTERRUPTED)) - return (packet_error); + DBUG_RETURN(packet_error); #endif /*MYSQL_SERVER*/ end_server(mysql); set_mysql_error(mysql, @@ -1023,7 +1104,7 @@ ulong cli_safe_read_with_ok(MYSQL *mysql, bool parse_ok, bool *is_data_packet) { ? CR_NET_PACKET_TOO_LARGE : CR_SERVER_LOST, unknown_sqlstate); - return (packet_error); + DBUG_RETURN(packet_error); } MYSQL_TRACE(PACKET_RECEIVED, mysql, (len, net->read_pos)); @@ -1070,13 +1151,13 @@ ulong cli_safe_read_with_ok(MYSQL *mysql, bool parse_ok, bool *is_data_packet) { DBUG_PRINT("error", ("Got error: %d/%s (%s)", net->last_errno, net->sqlstate, net->last_error)); - return (packet_error); + DBUG_RETURN(packet_error); } else { /* if it is OK packet irrespective of new/old server */ if (net->read_pos[0] == 0) { if (parse_ok) { read_ok_ex(mysql, len); - return len; + DBUG_RETURN(len); } } /* @@ -1092,12 +1173,12 @@ ulong cli_safe_read_with_ok(MYSQL *mysql, bool parse_ok, bool *is_data_packet) { if ((mysql->server_capabilities & CLIENT_DEPRECATE_EOF) && (net->read_pos[0] == 254)) { /* detect huge data packet */ - if (len > MAX_PACKET_LENGTH) return len; + if (len > MAX_PACKET_LENGTH) DBUG_RETURN(len); /* otherwise we have OK packet starting with 0xFE */ if (is_data_packet) *is_data_packet = false; /* parse it if requested */ if (parse_ok) read_ok_ex(mysql, len); - return len; + DBUG_RETURN(len); } /* for old client detect EOF packet */ if (!(mysql->server_capabilities & CLIENT_DEPRECATE_EOF) && @@ -1105,7 +1186,7 @@ ulong cli_safe_read_with_ok(MYSQL *mysql, bool parse_ok, bool *is_data_packet) { if (is_data_packet) *is_data_packet = false; } } - return len; + DBUG_RETURN(len); } /** @@ -1145,6 +1226,10 @@ bool cli_advanced_command(MYSQL *mysql, enum enum_server_command command, if (mysql->net.vio == 0) { /* Do reconnect if possible */ if (mysql_reconnect(mysql) || stmt_skip) DBUG_RETURN(1); } + /* turn off non blocking operations */ + if (!vio_is_blocking(mysql->net.vio)) + vio_set_blocking_flag(mysql->net.vio, true); + if (mysql->status != MYSQL_STATUS_READY || mysql->server_status & SERVER_MORE_RESULTS_EXISTS) { DBUG_PRINT("error", ("state: %d", mysql->status)); @@ -1279,6 +1364,110 @@ bool cli_advanced_command(MYSQL *mysql, enum enum_server_command command, DBUG_RETURN(result); } +net_async_status cli_advanced_command_nonblocking( + MYSQL *mysql, enum enum_server_command command, const uchar *header, + ulong header_length, const uchar *arg, ulong arg_length, bool skip_check, + MYSQL_STMT *stmt, bool *ret) { + NET *net = &mysql->net; + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + bool result = 1; + *ret = result; + bool stmt_skip = stmt ? stmt->state != MYSQL_STMT_INIT_DONE : false; + DBUG_ENTER(__func__); + DBUG_DUMP("sending", header, header_length); + if (arg && arg_length) { + DBUG_DUMP("sending arg", arg, arg_length); + } + + if (mysql->net.vio == 0) { + set_mysql_error(mysql, CR_SERVER_GONE_ERROR, unknown_sqlstate); + goto end; + } + if (net_async->async_send_command_status == NET_ASYNC_SEND_COMMAND_IDLE) { + if (vio_is_blocking(mysql->net.vio)) { + vio_set_blocking_flag(net->vio, false); + } + + if (mysql->status != MYSQL_STATUS_READY || + mysql->server_status & SERVER_MORE_RESULTS_EXISTS) { + DBUG_PRINT("error", ("state: %d", mysql->status)); + set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + net_clear_error(net); + mysql->info = 0; + mysql->affected_rows = ~(my_ulonglong)0; + /* + Do not check the socket/protocol buffer on COM_QUIT as the + result of a previous command might not have been read. This + can happen if a client sends a query but does not reap + the result before attempting to close the connection. + */ + DBUG_ASSERT(command <= COM_END); + net_clear(&mysql->net, (command != COM_QUIT)); + net_async->async_send_command_status = NET_ASYNC_SEND_COMMAND_WRITE_COMMAND; + } + + MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); + if (net_async->async_send_command_status == + NET_ASYNC_SEND_COMMAND_WRITE_COMMAND) { + bool err; + MYSQL_TRACE(SEND_COMMAND, mysql, + (command, header_length, arg_length, header, arg)); + net_async_status status = net_write_command_nonblocking( + net, (uchar)command, header, header_length, arg, arg_length, &err); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (err) { + DBUG_PRINT("error", + ("Can't send command to server. Error: %d", socket_errno)); + if (net->last_errno == ER_NET_PACKET_TOO_LARGE) { + set_mysql_error(mysql, CR_NET_PACKET_TOO_LARGE, unknown_sqlstate); + goto end; + } + end_server(mysql); + if (stmt_skip) goto end; + set_mysql_error(mysql, CR_SERVER_GONE_ERROR, unknown_sqlstate); + goto end; + } + MYSQL_TRACE(PACKET_SENT, mysql, (header_length + arg_length)); + if (skip_check) { + result = 0; + goto end; + } else { + net_async->async_send_command_status = NET_ASYNC_SEND_COMMAND_READ_STATUS; + } + } + + if (net_async->async_send_command_status == + NET_ASYNC_SEND_COMMAND_READ_STATUS) { + ulong pkt_len; + net_async_status status = + cli_safe_read_with_ok_nonblocking(mysql, true, nullptr, &pkt_len); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + mysql->packet_length = pkt_len; + result = (pkt_len == packet_error ? 1 : 0); +#if defined(CLIENT_PROTOCOL_TRACING) + /* + Return to READY_FOR_COMMAND protocol stage in case server reports + error or sends OK packet. + */ + if (!result || mysql->net.read_pos[0] == 0x00) + MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); +#endif + } +end: + if (net_async) + net_async->async_send_command_status = NET_ASYNC_SEND_COMMAND_IDLE; + DBUG_PRINT("exit", ("result: %d", result)); + *ret = result; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + void free_old_query(MYSQL *mysql) { DBUG_ENTER("free_old_query"); if (mysql->field_alloc) { @@ -1293,6 +1482,53 @@ void free_old_query(MYSQL *mysql) { DBUG_VOID_RETURN; } +/** + Finish reading of a partial result set from the server in asynchronous + way. This function can return without completly flushing the result set, + in such a case call this function again until result set in flushed. + Read OK packet incase result set is not a data packet. + + @param[in] mysql connection handle + @param[out] res true in case of protocol error, false otherwise + + @retval NET_ASYNC_NOT_READY result set not flushed yet + @retval NET_ASYNC_COMPLETE finished flushing result set +*/ +static net_async_status flush_one_result_nonblocking(MYSQL *mysql, bool *res) { + DBUG_ENTER(__func__); + + *res = false; + while (1) { + ulong packet_length; + bool is_data_packet; + if (cli_safe_read_nonblocking(mysql, &is_data_packet, &packet_length) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + mysql->packet_length = packet_length; + if (packet_length == packet_error) { + *res = true; + break; + } + if (mysql->net.read_pos[0] != 0 && !is_data_packet) { + if (protocol_41(mysql)) { + uchar *pos = mysql->net.read_pos + 1; + if (mysql->server_capabilities & CLIENT_DEPRECATE_EOF && + !is_data_packet) { + read_ok_ex(mysql, packet_length); + } else { + mysql->warning_count = uint2korr(pos); + pos += 2; + mysql->server_status = uint2korr(pos); + } + pos += 2; + } + break; + } + } + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + /** Finish reading of a partial result set from the server. Get the EOF packet, and update mysql->status @@ -1378,6 +1614,18 @@ static bool opt_flush_ok_packet(MYSQL *mysql, bool *is_ok_packet) { return false; } +static net_async_status cli_flush_use_result_nonblocking( + MYSQL *mysql, bool flush_all_results MY_ATTRIBUTE((unused))) { + DBUG_ENTER(__func__); + /* + flush_all_results is only used for mysql_stmt_close, and async is not + supported for that. + */ + DBUG_ASSERT(!flush_all_results); + bool res; + DBUG_RETURN(flush_one_result_nonblocking(mysql, &res)); +} + /* Flush result set sent from server */ @@ -1498,12 +1746,51 @@ void end_server(MYSQL *mysql) { mysql_prune_stmt_list(mysql); } net_end(&mysql->net); + net_extension_free(&mysql->net); free_old_query(mysql); errno = save_errno; MYSQL_TRACE(DISCONNECTED, mysql, ()); DBUG_VOID_RETURN; } +/** + Frees the memory allocated for a result, set by APIs which would have + returned rows. + + @param[in] result buffer which needs to be freed + + @retval NET_ASYNC_NOT_READY operation not complete, retry again + @retval NET_ASYNC_COMPLETE operation complete +*/ +net_async_status STDCALL mysql_free_result_nonblocking(MYSQL_RES *result) { + DBUG_ENTER(__func__); + DBUG_PRINT("enter", ("mysql_res: %p", result)); + if (!result) DBUG_RETURN(NET_ASYNC_COMPLETE); + + MYSQL *mysql = result->handle; + if (mysql) { + if (mysql->unbuffered_fetch_owner == &result->unbuffered_fetch_cancelled) + mysql->unbuffered_fetch_owner = 0; + if (mysql->status == MYSQL_STATUS_USE_RESULT) { + if (mysql->methods->flush_use_result_nonblocking(mysql, false) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + mysql->status = MYSQL_STATUS_READY; + if (mysql->unbuffered_fetch_owner) *mysql->unbuffered_fetch_owner = true; + } + } + free_rows(result->data); + if (result->field_alloc) { + free_root(result->field_alloc, MYF(0)); + my_free(result->field_alloc); + } + my_free(result->row); + my_free(result); + + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + void STDCALL mysql_free_result(MYSQL_RES *result) { DBUG_ENTER("mysql_free_result"); DBUG_PRINT("enter", ("mysql_res: %p", result)); @@ -2107,6 +2394,107 @@ MYSQL_FIELD *unpack_fields(MYSQL *mysql, MYSQL_ROWS *data, MEM_ROOT *alloc, DBUG_RETURN(result); } +/** + Read metadata resultset from server in asynchronous way. + + @param[in] mysql connection handle + @param[in] alloc memory allocator root + @param[in] field_count total number of fields + @param[in] field number of columns in single field descriptor + @param[out] ret an array of field rows + + @retval NET_ASYNC_NOT_READY metadata resultset not read completely + @retval NET_ASYNC_COMPLETE finished reading metadata resultset +*/ +net_async_status cli_read_metadata_ex_nonblocking(MYSQL *mysql, MEM_ROOT *alloc, + ulong field_count, + unsigned int field, + MYSQL_FIELD **ret) { + DBUG_ENTER(__func__); + uchar *pos; + ulong pkt_len; + NET *net = &mysql->net; + MYSQL_ASYNC *async_data = ASYNC_DATA(mysql); + *ret = nullptr; + + if (!async_data->async_read_metadata_field_len) { + async_data->async_read_metadata_field_len = + (ulong *)alloc_root(alloc, sizeof(ulong) * field); + } + if (!async_data->async_read_metadata_fields) { + async_data->async_read_metadata_fields = (MYSQL_FIELD *)alloc_root( + alloc, (uint)sizeof(MYSQL_FIELD) * field_count); + if (async_data->async_read_metadata_fields) + memset(async_data->async_read_metadata_fields, 0, + sizeof(MYSQL_FIELD) * field_count); + } + + if (!async_data->async_read_metadata_fields) { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + goto end; + } + + if (!async_data->async_read_metadata_data.data) { + async_data->async_read_metadata_data.data = + (MYSQL_ROW)alloc_root(alloc, sizeof(char *) * (field + 1)); + memset(async_data->async_read_metadata_data.data, 0, + sizeof(char *) * (field + 1)); + } + + /* + In this below loop we read each column info as 1 single row + and save it in mysql->fields array + */ + while (async_data->async_read_metadata_cur_field < field_count) { + int res; + if (read_one_row_nonblocking(mysql, field, + async_data->async_read_metadata_data.data, + async_data->async_read_metadata_field_len, + &res) == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (res == -1) { + goto end; + } + + if (unpack_field(mysql, alloc, 0, mysql->server_capabilities, + &async_data->async_read_metadata_data, + async_data->async_read_metadata_fields + + async_data->async_read_metadata_cur_field)) { + goto end; + } + async_data->async_read_metadata_cur_field++; + } + + /* Read EOF packet in case of old client */ + if (!(mysql->server_capabilities & CLIENT_DEPRECATE_EOF)) { + if (cli_safe_read_nonblocking(mysql, nullptr, &pkt_len) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (pkt_len == packet_error) { + goto end; + } + + pos = net->read_pos; + if (*pos == 254) { + mysql->warning_count = uint2korr(pos + 1); + mysql->server_status = uint2korr(pos + 3); + } + } + *ret = async_data->async_read_metadata_fields; + +end: + async_data->async_read_metadata_field_len = nullptr; + async_data->async_read_metadata_fields = nullptr; + memset(&async_data->async_read_metadata_data, 0, + sizeof(async_data->async_read_metadata_data)); + async_data->async_read_metadata_cur_field = 0; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + /** Read metadata resultset from server Memory allocated in a given allocator root. @@ -2165,6 +2553,20 @@ MYSQL_FIELD *cli_read_metadata_ex(MYSQL *mysql, MEM_ROOT *alloc, DBUG_RETURN(result); } +static int alloc_field_alloc(MYSQL *mysql) { + if (mysql->field_alloc == nullptr) { + mysql->field_alloc = (MEM_ROOT *)my_malloc( + key_memory_MYSQL, sizeof(MEM_ROOT), MYF(MY_WME | MY_ZEROFILL)); + if (mysql->field_alloc == nullptr) { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + return 1; + } + init_alloc_root(PSI_NOT_INSTRUMENTED, mysql->field_alloc, 8192, + 0); /* Assume rowlength < 8192 */ + } + return 0; +} + /** Read metadata resultset from server @@ -2177,19 +2579,25 @@ MYSQL_FIELD *cli_read_metadata_ex(MYSQL *mysql, MEM_ROOT *alloc, */ MYSQL_FIELD *cli_read_metadata(MYSQL *mysql, ulong field_count, unsigned int field) { - if (mysql->field_alloc == nullptr) { - mysql->field_alloc = (MEM_ROOT *)my_malloc( - key_memory_MYSQL, sizeof(MEM_ROOT), MYF(MY_WME | MY_ZEROFILL)); - if (mysql->field_alloc == nullptr) { - set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); - return nullptr; - } - init_alloc_root(PSI_NOT_INSTRUMENTED, mysql->field_alloc, 8192, - 0); /* Assume rowlength < 8192 */ - } + alloc_field_alloc(mysql); return cli_read_metadata_ex(mysql, mysql->field_alloc, field_count, field); } +/** + Helper method to read metadata in asynchronous way. +*/ +static net_async_status cli_read_metadata_nonblocking(MYSQL *mysql, + ulong field_count, + unsigned int field, + MYSQL_FIELD **ret) { + alloc_field_alloc(mysql); + if (cli_read_metadata_ex_nonblocking(mysql, mysql->field_alloc, field_count, + field, ret) == NET_ASYNC_NOT_READY) { + return NET_ASYNC_NOT_READY; + } + return NET_ASYNC_COMPLETE; +} + /** Read resultset metadata returned by COM_QUERY command. @@ -2236,57 +2644,142 @@ static int read_com_query_metadata(MYSQL *mysql, uchar *pos, return 0; } -/* Read all rows (data) from server */ +/** + Read resultset metadata returned by COM_QUERY command in asynchronous way. -MYSQL_DATA *cli_read_rows(MYSQL *mysql, MYSQL_FIELD *mysql_fields, - unsigned int fields) { + @param[in] mysql Client connection handle. + @param[in] pos Position in the packet where the metadata + starts. + @param[in] field_count Number of columns in the field descriptor. + @param[out] res set to false incase of success and true for + error. + + @retval NET_ASYNC_NOT_READY metadata resultset not read completely + @retval NET_ASYNC_COMPLETE finished reading metadata resultset +*/ +static net_async_status read_com_query_metadata_nonblocking(MYSQL *mysql, + uchar *pos, + ulong field_count, + int *res) { + DBUG_ENTER(__func__); + /* pos is only set on the first reentrant call. */ + if (pos) { + /* Store resultset metadata flag. */ + if (mysql->client_flag & CLIENT_OPTIONAL_RESULTSET_METADATA) { + mysql->resultset_metadata = + static_cast(*pos); + } else { + mysql->resultset_metadata = RESULTSET_METADATA_FULL; + } + } + + switch (mysql->resultset_metadata) { + case RESULTSET_METADATA_FULL: + /* Read metadata. */ + MYSQL_TRACE_STAGE(mysql, WAIT_FOR_FIELD_DEF); + + if (cli_read_metadata_nonblocking( + mysql, field_count, protocol_41(mysql) ? 7 : 5, &mysql->fields) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (!mysql->fields) { + free_root(mysql->field_alloc, MYF(0)); + *res = 1; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + break; + + case RESULTSET_METADATA_NONE: + /* Skip metadata. */ + mysql->fields = NULL; + break; + + default: + /* Unknown metadata flag. */ + mysql->fields = NULL; + *res = 1; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + *res = 0; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + +net_async_status cli_read_rows_nonblocking(MYSQL *mysql, + MYSQL_FIELD *mysql_fields, + unsigned int fields, + MYSQL_DATA **result_out) { uint field; ulong pkt_len; ulong len; uchar *cp; char *to, *end_to; - MYSQL_DATA *result; - MYSQL_ROWS **prev_ptr, *cur; + MYSQL_ROWS *cur; NET *net = &mysql->net; bool is_data_packet; - DBUG_ENTER("cli_read_rows"); + DBUG_ENTER(__func__); + MYSQL_ASYNC *async_context = ASYNC_DATA(mysql); + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + *result_out = nullptr; - if ((pkt_len = cli_safe_read(mysql, &is_data_packet)) == packet_error) - DBUG_RETURN(0); + if (cli_safe_read_nonblocking(mysql, &is_data_packet, &pkt_len) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } - if (pkt_len == 0) DBUG_RETURN(0); - if (!(result = - (MYSQL_DATA *)my_malloc(key_memory_MYSQL_DATA, sizeof(MYSQL_DATA), - MYF(MY_WME | MY_ZEROFILL))) || - !(result->alloc = - (MEM_ROOT *)my_malloc(key_memory_MYSQL_DATA, sizeof(MEM_ROOT), - MYF(MY_WME | MY_ZEROFILL)))) { - set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); - free_rows(result); - DBUG_RETURN(0); + mysql->packet_length = pkt_len; + if (pkt_len == packet_error) { + if (net_async->read_rows_is_first_read) { + free_rows(async_context->rows_result_buffer); + async_context->rows_result_buffer = nullptr; + } + net_async->read_rows_is_first_read = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + if (net_async->read_rows_is_first_read) { + MYSQL_DATA *result; + if (!(result = + (MYSQL_DATA *)my_malloc(key_memory_MYSQL_DATA, sizeof(MYSQL_DATA), + MYF(MY_WME | MY_ZEROFILL))) || + !(result->alloc = + (MEM_ROOT *)my_malloc(key_memory_MYSQL_DATA, sizeof(MEM_ROOT), + MYF(MY_WME | MY_ZEROFILL)))) { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + net_async->read_rows_is_first_read = true; + free_rows(result); + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + async_context->rows_result_buffer = result; + init_alloc_root(PSI_NOT_INSTRUMENTED, result->alloc, 8192, + 0); /* Assume rowlength < 8192 */ + async_context->prev_row_ptr = &result->data; + result->rows = 0; + result->fields = fields; + + net_async->read_rows_is_first_read = false; } - init_alloc_root(PSI_NOT_INSTRUMENTED, result->alloc, 8192, - 0); /* Assume rowlength < 8192 */ - prev_ptr = &result->data; - result->rows = 0; - result->fields = fields; /* The last EOF packet is either a single 254 character or (in MySQL 4.1) 254 followed by 1-7 status bytes or an OK packet starting with 0xFE */ - while (*(cp = net->read_pos) == 0 || is_data_packet) { + MYSQL_DATA *result = async_context->rows_result_buffer; result->rows++; if (!(cur = (MYSQL_ROWS *)alloc_root(result->alloc, sizeof(MYSQL_ROWS))) || !(cur->data = ((MYSQL_ROW)alloc_root( result->alloc, (fields + 1) * sizeof(char *) + pkt_len)))) { free_rows(result); + async_context->rows_result_buffer = nullptr; set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); - DBUG_RETURN(0); + net_async->read_rows_is_first_read = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); } - *prev_ptr = cur; - prev_ptr = &cur->next; + *async_context->prev_row_ptr = cur; + async_context->prev_row_ptr = &cur->next; to = (char *)(cur->data + fields + 1); end_to = to + pkt_len - 1; for (field = 0; field < fields; field++) { @@ -2297,8 +2790,10 @@ MYSQL_DATA *cli_read_rows(MYSQL *mysql, MYSQL_FIELD *mysql_fields, cur->data[field] = to; if (len > (ulong)(end_to - to)) { free_rows(result); + async_context->rows_result_buffer = nullptr; set_mysql_error(mysql, CR_MALFORMED_PACKET, unknown_sqlstate); - DBUG_RETURN(0); + net_async->read_rows_is_first_read = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); } memcpy(to, (char *)cp, len); to[len] = 0; @@ -2311,12 +2806,20 @@ MYSQL_DATA *cli_read_rows(MYSQL *mysql, MYSQL_FIELD *mysql_fields, } } cur->data[field] = to; /* End of last field */ - if ((pkt_len = cli_safe_read(mysql, &is_data_packet)) == packet_error) { - free_rows(result); - DBUG_RETURN(0); + if (cli_safe_read_nonblocking(mysql, &is_data_packet, &pkt_len) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + mysql->packet_length = pkt_len; + if (pkt_len == packet_error) { + free_rows(async_context->rows_result_buffer); + async_context->rows_result_buffer = nullptr; + net_async->read_rows_is_first_read = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); } } - *prev_ptr = 0; /* last pointer is null */ + + *async_context->prev_row_ptr = 0; /* last pointer is null */ /* read EOF packet or OK packet if it is new client */ if (pkt_len > 1) { if (mysql->server_capabilities & CLIENT_DEPRECATE_EOF && !is_data_packet) @@ -2336,25 +2839,126 @@ MYSQL_DATA *cli_read_rows(MYSQL *mysql, MYSQL_FIELD *mysql_fields, else MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); #endif - DBUG_PRINT("exit", ("Got %lu rows", (ulong)result->rows)); - DBUG_RETURN(result); + DBUG_PRINT("exit", + ("Got %lu rows", (ulong)async_context->rows_result_buffer->rows)); + *result_out = async_context->rows_result_buffer; + async_context->rows_result_buffer = nullptr; + net_async->read_rows_is_first_read = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); } -/* - Read one row. Uses packet buffer as storage for fields. - When next packet is read, the previous field values are destroyed -*/ +/* Read all rows (data) from server */ -static int read_one_row(MYSQL *mysql, uint fields, MYSQL_ROW row, - ulong *lengths) { +MYSQL_DATA *cli_read_rows(MYSQL *mysql, MYSQL_FIELD *mysql_fields, + unsigned int fields) { uint field; - ulong pkt_len, len; + ulong pkt_len; + ulong len; + uchar *cp; + char *to, *end_to; + MYSQL_DATA *result; + MYSQL_ROWS **prev_ptr, *cur; + NET *net = &mysql->net; bool is_data_packet; + DBUG_ENTER("cli_read_rows"); + + if ((pkt_len = cli_safe_read(mysql, &is_data_packet)) == packet_error) + DBUG_RETURN(0); + + if (pkt_len == 0) DBUG_RETURN(0); + if (!(result = + (MYSQL_DATA *)my_malloc(key_memory_MYSQL_DATA, sizeof(MYSQL_DATA), + MYF(MY_WME | MY_ZEROFILL))) || + !(result->alloc = + (MEM_ROOT *)my_malloc(key_memory_MYSQL_DATA, sizeof(MEM_ROOT), + MYF(MY_WME | MY_ZEROFILL)))) { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + free_rows(result); + DBUG_RETURN(0); + } + init_alloc_root(PSI_NOT_INSTRUMENTED, result->alloc, 8192, + 0); /* Assume rowlength < 8192 */ + prev_ptr = &result->data; + result->rows = 0; + result->fields = fields; + + /* + The last EOF packet is either a single 254 character or (in MySQL 4.1) + 254 followed by 1-7 status bytes or an OK packet starting with 0xFE + */ + + while (*(cp = net->read_pos) == 0 || is_data_packet) { + result->rows++; + if (!(cur = (MYSQL_ROWS *)alloc_root(result->alloc, sizeof(MYSQL_ROWS))) || + !(cur->data = ((MYSQL_ROW)alloc_root( + result->alloc, (fields + 1) * sizeof(char *) + pkt_len)))) { + free_rows(result); + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + DBUG_RETURN(0); + } + *prev_ptr = cur; + prev_ptr = &cur->next; + to = (char *)(cur->data + fields + 1); + end_to = to + pkt_len - 1; + for (field = 0; field < fields; field++) { + if ((len = (ulong)net_field_length(&cp)) == + NULL_LENGTH) { /* null field */ + cur->data[field] = 0; + } else { + cur->data[field] = to; + if (len > (ulong)(end_to - to)) { + free_rows(result); + set_mysql_error(mysql, CR_MALFORMED_PACKET, unknown_sqlstate); + DBUG_RETURN(0); + } + memcpy(to, (char *)cp, len); + to[len] = 0; + to += len + 1; + cp += len; + if (mysql_fields) { + if (mysql_fields[field].max_length < len) + mysql_fields[field].max_length = len; + } + } + } + cur->data[field] = to; /* End of last field */ + if ((pkt_len = cli_safe_read(mysql, &is_data_packet)) == packet_error) { + free_rows(result); + DBUG_RETURN(0); + } + } + *prev_ptr = 0; /* last pointer is null */ + /* read EOF packet or OK packet if it is new client */ + if (pkt_len > 1) { + if (mysql->server_capabilities & CLIENT_DEPRECATE_EOF && !is_data_packet) + read_ok_ex(mysql, pkt_len); + else { + mysql->warning_count = uint2korr(cp + 1); + mysql->server_status = uint2korr(cp + 3); + } + + DBUG_PRINT("info", ("status: %u warning_count: %u", mysql->server_status, + mysql->warning_count)); + } + +#if defined(CLIENT_PROTOCOL_TRACING) + if (mysql->server_status & SERVER_MORE_RESULTS_EXISTS) + MYSQL_TRACE_STAGE(mysql, WAIT_FOR_RESULT); + else + MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); +#endif + DBUG_PRINT("exit", ("Got %lu rows", (ulong)result->rows)); + DBUG_RETURN(result); +} + +static int read_one_row_complete(MYSQL *mysql, ulong pkt_len, + bool is_data_packet, uint fields, + MYSQL_ROW row, ulong *lengths) { + uint field; + ulong len; uchar *pos, *prev_pos, *end_pos; NET *net = &mysql->net; - if ((pkt_len = cli_safe_read(mysql, &is_data_packet)) == packet_error) - return -1; if (net->read_pos[0] != 0x00 && !is_data_packet) { if (pkt_len > 1) /* MySQL 4.1 protocol */ { @@ -2399,6 +3003,46 @@ static int read_one_row(MYSQL *mysql, uint fields, MYSQL_ROW row, return 0; } +/* + Read one row. Uses packet buffer as storage for fields. + When next packet is read, the previous field values are destroyed +*/ + +static int read_one_row(MYSQL *mysql, uint fields, MYSQL_ROW row, + ulong *lengths) { + ulong pkt_len; + bool is_data_packet; + + if ((pkt_len = cli_safe_read(mysql, &is_data_packet)) == packet_error) + return -1; + + return read_one_row_complete(mysql, pkt_len, is_data_packet, fields, row, + lengths); +} + +static net_async_status read_one_row_nonblocking(MYSQL *mysql, uint fields, + MYSQL_ROW row, ulong *lengths, + int *res) { + ulong pkt_len; + bool is_data_packet; + net_async_status status; + + status = cli_safe_read_nonblocking(mysql, &is_data_packet, &pkt_len); + if (status == NET_ASYNC_NOT_READY) { + return status; + } + + mysql->packet_length = pkt_len; + if (pkt_len == packet_error) { + *res = -1; + return NET_ASYNC_COMPLETE; + } + + *res = read_one_row_complete(mysql, pkt_len, is_data_packet, fields, row, + lengths); + return NET_ASYNC_COMPLETE; +} + /**************************************************************************** Init MySQL structure or allocate one ****************************************************************************/ @@ -2467,6 +3111,7 @@ MYSQL *STDCALL mysql_init(MYSQL *mysql) { #endif mysql->resultset_metadata = RESULTSET_METADATA_FULL; + ASYNC_DATA(mysql)->async_op_status = ASYNC_OP_UNSET; return mysql; } @@ -2482,13 +3127,19 @@ MYSQL_EXTENSION *mysql_extension_init(MYSQL *mysql MY_ATTRIBUTE((unused))) { ext = static_cast(my_malloc(PSI_NOT_INSTRUMENTED, sizeof(MYSQL_EXTENSION), MYF(MY_WME | MY_ZEROFILL))); + ext->mysql_async_context = static_cast( + my_malloc(PSI_NOT_INSTRUMENTED, sizeof(struct MYSQL_ASYNC), + MYF(MY_WME | MY_ZEROFILL))); + /* set default value */ + ext->mysql_async_context->async_op_status = ASYNC_OP_UNSET; + return ext; } void mysql_extension_free(MYSQL_EXTENSION *ext) { if (!ext) return; if (ext->trace_data) my_free(ext->trace_data); - + if (ext->mysql_async_context) my_free(ext->mysql_async_context); // free state change related resources. free_state_change_info(ext); @@ -2717,12 +3368,18 @@ static int ssl_verify_server_cert(Vio *vio, const char *server_hostname, */ static bool cli_read_query_result(MYSQL *mysql); +static net_async_status cli_read_query_result_nonblocking(MYSQL *mysql); static MYSQL_RES *cli_use_result(MYSQL *mysql); int cli_read_change_user_result(MYSQL *mysql) { return cli_safe_read(mysql, NULL); } +net_async_status cli_read_change_user_result_nonblocking(MYSQL *mysql, + ulong *ret) { + return cli_safe_read_nonblocking(mysql, nullptr, ret); +} + static MYSQL_METHODS client_methods = { cli_read_query_result, /* read_query_result */ cli_advanced_command, /* advanced_command */ @@ -2743,6 +3400,14 @@ static MYSQL_METHODS client_methods = { cli_read_binary_rows, /* read_rows_from_cursor */ free_rows #endif + , + cli_read_query_result_nonblocking, /* read_query_result_nonblocking */ + cli_advanced_command_nonblocking, /* advanced_command_nonblocking */ + cli_read_rows_nonblocking, /* read_rows_nonblocking */ + cli_flush_use_result_nonblocking, /* flush_use_result_nonblocking */ + cli_read_query_result_nonblocking, /* next_result_nonblocking */ + cli_read_change_user_result_nonblocking /* read_change_user_result_nonblocking + */ }; typedef enum my_cs_match_type_enum { @@ -3021,7 +3686,11 @@ int mysql_init_character_set(MYSQL *mysql) { /*********** client side authentication support **************************/ static int client_mpvio_write_packet(MYSQL_PLUGIN_VIO *, const uchar *, int); +static net_async_status client_mpvio_write_packet_nonblocking( + struct MYSQL_PLUGIN_VIO *, const uchar *, int, int *); static int native_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); +static net_async_status native_password_auth_client_nonblocking( + MYSQL_PLUGIN_VIO *vio, MYSQL *mysql, int *result); static int clear_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql); static auth_plugin_t native_password_client_plugin = { @@ -3036,7 +3705,8 @@ static auth_plugin_t native_password_client_plugin = { NULL, NULL, NULL, - native_password_auth_client}; + native_password_auth_client, + native_password_auth_client_nonblocking}; static auth_plugin_t clear_password_client_plugin = { MYSQL_CLIENT_AUTHENTICATION_PLUGIN, @@ -3050,7 +3720,8 @@ static auth_plugin_t clear_password_client_plugin = { NULL, NULL, NULL, - clear_password_auth_client}; + clear_password_auth_client, + NULL}; #if defined(HAVE_OPENSSL) static auth_plugin_t sha256_password_client_plugin = { @@ -3065,7 +3736,8 @@ static auth_plugin_t sha256_password_client_plugin = { sha256_password_init, sha256_password_deinit, NULL, - sha256_password_auth_client}; + sha256_password_auth_client, + sha256_password_auth_client_nonblocking}; static auth_plugin_t caching_sha2_password_client_plugin = { MYSQL_CLIENT_AUTHENTICATION_PLUGIN, @@ -3079,7 +3751,8 @@ static auth_plugin_t caching_sha2_password_client_plugin = { caching_sha2_password_init, caching_sha2_password_deinit, NULL, - caching_sha2_password_auth_client}; + caching_sha2_password_auth_client, + caching_sha2_password_auth_client_nonblocking}; #endif #ifdef AUTHENTICATION_WIN extern "C" auth_plugin_t win_auth_client_plugin; @@ -3166,24 +3839,6 @@ static size_t get_length_store_length(size_t length) { return ptr - &length_buffer[0]; } -/* this is a "superset" of MYSQL_PLUGIN_VIO, in C++ I use inheritance */ -struct MCPVIO_EXT { - int (*read_packet)(MYSQL_PLUGIN_VIO *vio, uchar **buf); - int (*write_packet)(MYSQL_PLUGIN_VIO *vio, const uchar *pkt, int pkt_len); - void (*info)(MYSQL_PLUGIN_VIO *vio, MYSQL_PLUGIN_VIO_INFO *info); - /* -= end of MYSQL_PLUGIN_VIO =- */ - MYSQL *mysql; - auth_plugin_t *plugin; /**< what plugin we're under */ - const char *db; - struct { - uchar *pkt; /**< pointer into NET::buff */ - uint pkt_len; - } cached_server_reply; - int packets_read, packets_written; /**< counters for send/received packets */ - int mysql_change_user; /**< if it's mysql_change_user() */ - int last_read_packet_len; /**< the length of the last *read* packet */ -}; - /* Write 1-8 bytes of string length header infromation to dest depending on value of src_len, then copy src_len bytes from src to dest. @@ -3488,7 +4143,7 @@ static int cli_establish_ssl(MYSQL *mysql) { DBUG_PRINT("info", ("IO layer change in progress...")); MYSQL_TRACE(SSL_CONNECT, mysql, ()); if (sslconnect(ssl_fd, net->vio, (long)(mysql->options.connect_timeout), - &ssl_error)) { + &ssl_error, nullptr)) { char buf[512]; ERR_error_string_n(ssl_error, buf, 512); buf[511] = 0; @@ -3521,8 +4176,266 @@ static int cli_establish_ssl(MYSQL *mysql) { #endif /* HAVE_OPENSSL */ } +/** + This function will establish asynchronous ssl connection by completing 4 + different ssl connection states. Initial state is set to SSL_NONE during + which this functions does priliminary checks like if server supports ssl + or not, if CA certificate is required etc. Once preliminary checks are + done state is changed to SSL_REQUEST. In this state ssl request packet + is sent by client. If this network IO is complete, state is changed to + SSL_CONNECT. During SSL_CONNECT sslconnect() is called which can return + immediately or complete SSL handshake. If it returns immediately client + will save all SSL context in struct mysql_async_auth, so that next call + to this function will ensure that SSL_new() is not called twice. Once + ssl connection is established state is changed to SSL_COMPLETE. + + @param[in] mysql Client connection handle. + @param[out] res set to false incase of success and true for + error. + + @retval NET_ASYNC_NOT_READY ssl connection not yet established + @retval NET_ASYNC_COMPLETE ssl connection established +*/ +static net_async_status cli_establish_ssl_nonblocking(MYSQL *mysql, int *res) { + DBUG_ENTER(__func__); +#ifdef HAVE_OPENSSL + NET *net = &mysql->net; + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + mysql_async_connect *ctx = ASYNC_DATA(mysql)->connect_context; + + if (ctx->ssl_state == SSL_NONE) { + /* Don't fallback on unencrypted connection if SSL required. */ + if (mysql->options.extension && + mysql->options.extension->ssl_mode >= SSL_MODE_REQUIRED && + !(mysql->server_capabilities & CLIENT_SSL)) { + set_mysql_extended_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate, + ER_CLIENT(CR_SSL_CONNECTION_ERROR), + "SSL is required but the server doesn't " + "support it"); + goto error; + } + + /* + If the ssl_mode is VERIFY_CA or VERIFY_IDENTITY, make sure + that the connection doesn't succeed without providing the + CA certificate. + */ + if (mysql->options.extension && + mysql->options.extension->ssl_mode > SSL_MODE_REQUIRED && + !(mysql->options.ssl_ca || mysql->options.ssl_capath)) { + set_mysql_extended_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate, + ER_CLIENT(CR_SSL_CONNECTION_ERROR), + "CA certificate is required if ssl-mode " + "is VERIFY_CA or VERIFY_IDENTITY"); + goto error; + } + + /* + Attempt SSL connection if ssl_mode != SSL_MODE_DISABLED and + the server supports SSL. Fallback on unencrypted + connection otherwise. + */ + if (!mysql->options.extension || + mysql->options.extension->ssl_mode == SSL_MODE_DISABLED || + !(mysql->server_capabilities & CLIENT_SSL)) { + goto done; + } + ctx->ssl_state = SSL_REQUEST; + } + + if (ctx->ssl_state == SSL_REQUEST) { + char buff[33], *end; + + end = mysql_fill_packet_header(mysql, buff, sizeof(buff)); + + /* + Send mysql->client_flag, max_packet_size - unencrypted + otherwise the server does not know we want to do SSL + */ + MYSQL_TRACE(SEND_SSL_REQUEST, mysql, + ((size_t)(end - buff), (const unsigned char *)buff)); + bool ret; + if (my_net_write_nonblocking(net, (uchar *)buff, (size_t)(end - buff), + &ret) == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (ret) { + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER_CLIENT(CR_SERVER_LOST_EXTENDED), + "sending connection information to server", + errno); + goto error; + } + + ctx->ssl_state = SSL_CONNECT; + } + + if (ctx->ssl_state == SSL_CONNECT) { + /* Do the SSL layering. */ + struct st_mysql_options *options = &mysql->options; + struct st_VioSSLFd *ssl_fd; + enum enum_ssl_init_error ssl_init_error; + const char *cert_error; + unsigned long ssl_error; + size_t ret; + + MYSQL_TRACE_STAGE(mysql, SSL_NEGOTIATION); + + if (!mysql->connector_fd) { + /* Create the VioSSLConnectorFd - init SSL and load certs */ + if (!(ssl_fd = new_VioSSLConnectorFd( + options->ssl_key, options->ssl_cert, options->ssl_ca, + options->ssl_capath, options->ssl_cipher, + options->extension ? options->extension->tls_ciphersuites + : NULL, + &ssl_init_error, + options->extension ? options->extension->ssl_crl : NULL, + options->extension ? options->extension->ssl_crlpath : NULL, + options->extension ? options->extension->ssl_ctx_flags : 0))) { + set_mysql_extended_error(mysql, CR_SSL_CONNECTION_ERROR, + unknown_sqlstate, + ER_CLIENT(CR_SSL_CONNECTION_ERROR), + sslGetErrString(ssl_init_error)); + goto error; + } + mysql->connector_fd = (unsigned char *)ssl_fd; + } else { + ssl_fd = (struct st_VioSSLFd *)mysql->connector_fd; + } + + /* Connect to the server */ + DBUG_PRINT("info", ("IO layer change in progress...")); + MYSQL_TRACE(SSL_CONNECT, mysql, ()); + if ((ret = sslconnect(ssl_fd, net->vio, + (long)(mysql->options.connect_timeout), &ssl_error, + &ctx->ssl))) { + switch (ret) { + case VIO_SOCKET_WANT_READ: + net_async->async_blocking_state = NET_NONBLOCKING_READ; + DBUG_RETURN(NET_ASYNC_NOT_READY); + case VIO_SOCKET_WANT_WRITE: + net_async->async_blocking_state = NET_NONBLOCKING_WRITE; + DBUG_RETURN(NET_ASYNC_NOT_READY); + default: + break; + /* continue for error handling */ + } + + char buf[512]; + ERR_error_string_n(ssl_error, buf, 512); + buf[511] = 0; + set_mysql_extended_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate, + ER_CLIENT(CR_SSL_CONNECTION_ERROR), buf); + goto error; + } + DBUG_PRINT("info", ("IO layer change done!")); + + /* sslconnect creates a new vio, so update it. */ + vio_set_blocking_flag(net->vio, !ctx->non_blocking); + + /* Verify server cert */ + if ((mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) && + ssl_verify_server_cert(net->vio, mysql->host, &cert_error)) { + set_mysql_extended_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate, + ER_CLIENT(CR_SSL_CONNECTION_ERROR), cert_error); + goto error; + } + + MYSQL_TRACE(SSL_CONNECTED, mysql, ()); + MYSQL_TRACE_STAGE(mysql, AUTHENTICATE); + } + +done: + *res = 0; + ctx->ssl_state = SSL_COMPLETE; + DBUG_RETURN(NET_ASYNC_COMPLETE); + +error: + *res = 1; + ctx->ssl_state = SSL_COMPLETE; + DBUG_RETURN(NET_ASYNC_COMPLETE); + +#else + (void)mysql; /* avoid warning */ + *res = 0; + ctx->ssl_state = SSL_COMPLETE; + DBUG_RETURN(NET_ASYNC_COMPLETE); +#endif /* HAVE_OPENSSL */ +} + +/** + Asynchronous authentication phase is divided into several smaller chunks + of subtasks like: + 1. Determine the default/initial plugin to use + 2. Call authentication plugin API + 3. Handle response from authentication plugin API + 4. Check if server asked to use a different authentication plugin + 5. In case server asked to use a different authentication plugin + use that plugin to start the authentication process again. + 6. Complete authentication. + + All above tasks are implemented in below authsm_* functions where + authsm stads for authentication state machine. +*/ +static mysql_state_machine_status authsm_begin_plugin_auth( + mysql_async_auth *ctx); +static mysql_state_machine_status authsm_run_first_authenticate_user( + mysql_async_auth *ctx); +static mysql_state_machine_status authsm_handle_first_authenticate_user( + mysql_async_auth *ctx); +static mysql_state_machine_status authsm_read_change_user_result( + mysql_async_auth *ctx); +static mysql_state_machine_status authsm_handle_change_user_result( + mysql_async_auth *ctx); +static mysql_state_machine_status authsm_run_second_authenticate_user( + mysql_async_auth *ctx); +static mysql_state_machine_status authsm_handle_second_authenticate_user( + mysql_async_auth *ctx); +static mysql_state_machine_status authsm_finish_auth(mysql_async_auth *ctx); + +/** + Asynchronous connection phase is divided into several smaller modules + where wach module does following: + 1. Begin the connection to the server, including any DNS resolution + necessary, socket configuration, etc + 2. Complete the connection itself + 3. Connection established, read the first packet + 4. Parse the handshake from the server + 5. Establish SSL connection if needed + 6. Invoke the plugin to send the authentication data to the server + 7. Authenticated, set intial database if specified + 8. Send COM_INIT_DB. + 9. Prepare to send a sequence of init commands. + 10.Send an init command. + + Below are the modules which does all above tasks. +*/ +static mysql_state_machine_status csm_begin_connect(mysql_async_connect *ctx); +static mysql_state_machine_status csm_complete_connect( + mysql_async_connect *ctx); +static mysql_state_machine_status csm_read_greeting(mysql_async_connect *ctx); +static mysql_state_machine_status csm_parse_handshake(mysql_async_connect *ctx); +static mysql_state_machine_status csm_establish_ssl(mysql_async_connect *ctx); +static mysql_state_machine_status csm_authenticate(mysql_async_connect *ctx); +static mysql_state_machine_status csm_prep_select_database( + mysql_async_connect *ctx); +#ifndef MYSQL_SERVER +static mysql_state_machine_status csm_prep_init_commands( + mysql_async_connect *ctx); +static mysql_state_machine_status csm_send_one_init_command( + mysql_async_connect *ctx); +#endif + #define MAX_CONNECTION_ATTR_STORAGE_LENGTH 65536 +int mysql_get_socket_descriptor(MYSQL *mysql) { + if (mysql && mysql->net.vio) { + return vio_fd(mysql->net.vio); + } + return -1; +} + /* clang-format off */ /** @page page_protocol_connection_phase_packets_protocol_handshake_response Protocol::HandshakeResponse: @@ -3701,16 +4614,19 @@ static int cli_establish_ssl(MYSQL *mysql) { @param mpvio The connection to use @param data The scramble to send @param data_len Length of data + @param buff_out Buffer holding client handshake packet + @param buff_len Length of buffer holding client handshake packet @retval 0 ok @retval 1 error @sa mysql_fill_packet_header() page_protocol_conn_packets_protocol_handshake_response */ -static int send_client_reply_packet(MCPVIO_EXT *mpvio, const uchar *data, - int data_len) { +static bool prep_client_reply_packet(MCPVIO_EXT *mpvio, const uchar *data, + int data_len, char **buff_out, + int *buff_len) { + DBUG_ENTER(__func__); MYSQL *mysql = mpvio->mysql; - NET *net = &mysql->net; char *buff, *end; size_t buff_size; size_t connect_attrs_len = @@ -3721,13 +4637,17 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, const uchar *data, DBUG_ASSERT(connect_attrs_len < MAX_CONNECTION_ATTR_STORAGE_LENGTH); + *buff_out = nullptr; + *buff_len = 0; + /* Fixed size of the packet is 32 bytes. See mysql_fill_packet_header. +9 because data is a length encoded binary where meta data size is max 9. */ buff_size = 33 + USERNAME_LENGTH + data_len + 9 + NAME_LEN + NAME_LEN + connect_attrs_len + 9; - buff = static_cast(my_alloca(buff_size)); + buff = static_cast( + my_malloc(PSI_NOT_INSTRUMENTED, buff_size, MYF(MY_WME | MY_ZEROFILL))); /* The client_flags is already calculated. Just fill in the packet header */ end = mysql_fill_packet_header(mysql, buff, buff_size); @@ -3779,7 +4699,32 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, const uchar *data, end = strmake(end, mpvio->plugin->name, NAME_LEN) + 1; end = (char *)send_client_connect_attrs(mysql, (uchar *)end); + *buff_out = buff; + *buff_len = end - buff; + + DBUG_RETURN(false); + +error: + my_free(buff); + DBUG_RETURN(true); +} + +static int send_client_reply_packet(MCPVIO_EXT *mpvio, const uchar *data, + int data_len) { + DBUG_ENTER(__func__); + MYSQL *mysql = mpvio->mysql; + NET *net = &mysql->net; + char *buff = nullptr, *end = nullptr; + int buff_len; + int ret = 0; + bool prep_err; + prep_err = prep_client_reply_packet(mpvio, data, data_len, &buff, &buff_len); + if (prep_err) { + DBUG_RETURN(1); + } + + end = buff + buff_len; /* Write authentication package */ MYSQL_TRACE(SEND_AUTH_RESPONSE, mysql, ((size_t)(end - buff), (const unsigned char *)buff)); @@ -3788,15 +4733,46 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, const uchar *data, set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, ER_CLIENT(CR_SERVER_LOST_EXTENDED), "sending authentication information", errno); - goto error; + ret = 1; } MYSQL_TRACE(PACKET_SENT, mysql, ((size_t)(end - buff))); - return 0; - -error: - return 1; + my_free(buff); + DBUG_RETURN(ret); } +static net_async_status send_client_reply_packet_nonblocking(MCPVIO_EXT *mpvio, + const uchar *pkt, + int pkt_len, + bool *result) { + DBUG_ENTER(__func__); + MYSQL *mysql = mpvio->mysql; + mysql_async_auth *ctx = ASYNC_DATA(mysql)->connect_context->auth_context; + net_async_status status; + + bool error = false; + if (!ctx->change_user_buff) { + error = + prep_client_reply_packet(mpvio, pkt, pkt_len, &ctx->change_user_buff, + &ctx->change_user_buff_len); + if (error) { + goto end; + } + } + + status = my_net_write_nonblocking(&mysql->net, (uchar *)ctx->change_user_buff, + ctx->change_user_buff_len, &error); + + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + +end: + *result = error; + my_free(ctx->change_user_buff); + ctx->change_user_buff = nullptr; + + DBUG_RETURN(NET_ASYNC_COMPLETE); +} /** vio->read_packet() callback method for client authentication plugins @@ -3854,6 +4830,81 @@ static int client_mpvio_read_packet(MYSQL_PLUGIN_VIO *mpv, uchar **buf) { return pkt_len; } +/** + vio->read_packet() nonblocking callback method for client authentication + plugins +*/ +static net_async_status client_mpvio_read_packet_nonblocking( + struct MYSQL_PLUGIN_VIO *mpv, uchar **buf, int *result) { + DBUG_ENTER(__func__); + MCPVIO_EXT *mpvio = (MCPVIO_EXT *)mpv; + MYSQL *mysql = mpvio->mysql; + ulong pkt_len; + int error; + + /* there are cached data left, feed it to a plugin */ + if (mpvio->cached_server_reply.pkt) { + *buf = mpvio->cached_server_reply.pkt; + mpvio->cached_server_reply.pkt = 0; + mpvio->packets_read++; + *result = mpvio->cached_server_reply.pkt_len; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + if (mpvio->packets_read == 0) { + /* + the server handshake packet came from the wrong plugin, + or it's mysql_change_user(). Either way, there is no data + for a plugin to read. send a dummy packet to the server + to initiate a dialog. + */ + net_async_status status = + client_mpvio_write_packet_nonblocking(mpv, 0, 0, &error); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (error) { + *result = (int)packet_error; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } + + /* + packets_read needs to be set here to avoid entering above condition + again. + */ + mpvio->packets_read++; + /* otherwise read the data */ + net_async_status status = + mysql->methods->read_change_user_result_nonblocking(mysql, &pkt_len); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + mpvio->last_read_packet_len = pkt_len; + *buf = mysql->net.read_pos; + + /* was it a request to change plugins ? */ + if (**buf == 254) { + *result = (int)packet_error; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + /* + the server sends \1\255 or \1\254 instead of just \255 or \254 - + for us to not confuse it with an error or "change plugin" packets. + We remove this escaping \1 here. + See also server_mpvio_write_packet() where the escaping is + done. + */ + if (pkt_len && **buf == 1) { + (*buf)++; + pkt_len--; + } + *result = pkt_len; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + /** vio->write_packet() callback method for client authentication plugins @@ -3894,6 +4945,53 @@ static int client_mpvio_write_packet(MYSQL_PLUGIN_VIO *mpv, const uchar *pkt, return res; } +/** + vio->write_packet() nonblocking callback method for client authentication + plugins +*/ +static net_async_status client_mpvio_write_packet_nonblocking( + struct MYSQL_PLUGIN_VIO *mpv, const uchar *pkt, int pkt_len, int *result) { + DBUG_ENTER(__func__); + MCPVIO_EXT *mpvio = (MCPVIO_EXT *)mpv; + bool error = false; + + if (mpvio->packets_written == 0) { + /* mysql_change_user_nonblocking not implemented yet. */ + DBUG_ASSERT(!mpvio->mysql_change_user); + net_async_status status = + send_client_reply_packet_nonblocking(mpvio, pkt, pkt_len, &error); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + } else { + NET *net = &mpvio->mysql->net; + + MYSQL_TRACE(SEND_AUTH_DATA, mpvio->mysql, ((size_t)pkt_len, pkt)); + + if (mpvio->mysql->thd) + *result = 1; /* no chit-chat in embedded */ + else { + net_async_status status = + my_net_write_nonblocking(net, pkt, pkt_len, &error); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + *result = error; + + if (error) { + set_mysql_extended_error(mpvio->mysql, CR_SERVER_LOST, unknown_sqlstate, + ER_CLIENT(CR_SERVER_LOST_EXTENDED), + "sending authentication information", errno); + } else { + MYSQL_TRACE(PACKET_SENT, mpvio->mysql, ((size_t)pkt_len)); + } + } + } + mpvio->packets_written++; + *result = error ? -1 : 0; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + /** fills MYSQL_PLUGIN_VIO_INFO structure with the information about the connection @@ -3944,8 +5042,8 @@ static void client_mpvio_info(MYSQL_PLUGIN_VIO *vio, bool libmysql_cleartext_plugin_enabled = 0; -static bool check_plugin_enabled(MYSQL *mysql, auth_plugin_t *plugin) { - if (plugin == &clear_password_client_plugin && +static bool check_plugin_enabled(MYSQL *mysql, mysql_async_auth *ctx) { + if (ctx->auth_plugin == &clear_password_client_plugin && (!libmysql_cleartext_plugin_enabled && (!mysql->options.extension || !mysql->options.extension->enable_cleartext_plugin))) { @@ -3955,6 +5053,12 @@ static bool check_plugin_enabled(MYSQL *mysql, auth_plugin_t *plugin) { clear_password_client_plugin.name, "plugin not enabled"); return true; } + if (ctx->non_blocking && !ctx->auth_plugin->authenticate_user_nonblocking) { + set_mysql_extended_error( + mysql, CR_AUTH_PLUGIN_CANNOT_LOAD, unknown_sqlstate, + ER_CLIENT(CR_AUTH_PLUGIN_CANNOT_LOAD), ctx->auth_plugin->name, + "plugin does not support nonblocking connect"); + } return false; } @@ -3976,58 +5080,166 @@ static bool check_plugin_enabled(MYSQL *mysql, auth_plugin_t *plugin) { */ int run_plugin_auth(MYSQL *mysql, char *data, uint data_len, const char *data_plugin, const char *db) { - const char *auth_plugin_name; - auth_plugin_t *auth_plugin; - MCPVIO_EXT mpvio; - ulong pkt_length; - int res; - DBUG_ENTER("run_plugin_auth"); + mysql_state_machine_status status; + mysql_async_auth ctx; + memset(&ctx, 0, sizeof(ctx)); + + ctx.mysql = mysql; + ctx.data = data; + ctx.data_len = data_len; + ctx.data_plugin = data_plugin; + ctx.db = db; + ctx.non_blocking = false; + ctx.state_function = authsm_begin_plugin_auth; + + do { + status = ctx.state_function(&ctx); + } while (status != STATE_MACHINE_FAILED && status != STATE_MACHINE_DONE); + + DBUG_RETURN(status == STATE_MACHINE_FAILED); +} + +/** + This functions drives the authentication on client side in a nonblocking + way. This function will call differnt modules in a sequence where each + module is responsible to acheive a particular part in entire authentication + phase. + + @note this is used by both the mysql_real_connect_nonblocking + + @param mysql mysql + @param data pointer to the plugin auth data (scramble) in the + handshake packet + @param data_len the length of the data + @param data_plugin a plugin that data were prepared for + or 0 if it's mysql_change_user() + @param db initial db to use, can be 0 + + @retval NET_ASYNC_NOT_READY authentication not yet complete + @retval NET_ASYNC_COMPLETE authentication done +*/ +mysql_state_machine_status run_plugin_auth_nonblocking(MYSQL *mysql, char *data, + uint data_len, + const char *data_plugin, + const char *db) { + DBUG_ENTER(__func__); + mysql_async_auth *ctx = ASYNC_DATA(mysql)->connect_context->auth_context; + if (!ctx) { + ctx = static_cast( + my_malloc(key_memory_MYSQL, sizeof(*ctx), MYF(MY_WME | MY_ZEROFILL))); + + ctx->mysql = mysql; + ctx->data = data; + ctx->data_len = data_len; + ctx->data_plugin = data_plugin; + ctx->db = db; + ctx->non_blocking = true; + ctx->state_function = authsm_begin_plugin_auth; + ASYNC_DATA(mysql)->connect_context->auth_context = ctx; + } + + mysql_state_machine_status ret = ctx->state_function(ctx); + if (ret == STATE_MACHINE_FAILED || ret == STATE_MACHINE_DONE) { + my_free(ctx); + ASYNC_DATA(mysql)->connect_context->auth_context = nullptr; + } + + DBUG_RETURN(ret); +} + +/** + Determine the default/initial plugin to use +*/ +static mysql_state_machine_status authsm_begin_plugin_auth( + mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; /* determine the default/initial plugin to use */ if (mysql->options.extension && mysql->options.extension->default_auth && mysql->server_capabilities & CLIENT_PLUGIN_AUTH) { - auth_plugin_name = mysql->options.extension->default_auth; - if (!(auth_plugin = (auth_plugin_t *)mysql_client_find_plugin( - mysql, auth_plugin_name, MYSQL_CLIENT_AUTHENTICATION_PLUGIN))) - DBUG_RETURN(1); /* oops, not found */ + ctx->auth_plugin_name = mysql->options.extension->default_auth; + if (!(ctx->auth_plugin = (auth_plugin_t *)mysql_client_find_plugin( + mysql, ctx->auth_plugin_name, + MYSQL_CLIENT_AUTHENTICATION_PLUGIN))) + DBUG_RETURN(STATE_MACHINE_FAILED); /* oops, not found */ } else { - auth_plugin = &caching_sha2_password_client_plugin; - auth_plugin_name = auth_plugin->name; + ctx->auth_plugin = &caching_sha2_password_client_plugin; + ctx->auth_plugin_name = ctx->auth_plugin->name; } - if (check_plugin_enabled(mysql, auth_plugin)) DBUG_RETURN(1); + if (check_plugin_enabled(mysql, ctx)) DBUG_RETURN(STATE_MACHINE_FAILED); - DBUG_PRINT("info", ("using plugin %s", auth_plugin_name)); + DBUG_PRINT("info", ("using plugin %s", ctx->auth_plugin_name)); mysql->net.last_errno = 0; /* just in case */ - if (data_plugin && strcmp(data_plugin, auth_plugin_name)) { + if (ctx->data_plugin && strcmp(ctx->data_plugin, ctx->auth_plugin_name)) { /* data was prepared for a different plugin, don't show it to this one */ - data = 0; - data_len = 0; - } + ctx->data = 0; + ctx->data_len = 0; + } + + ctx->mpvio.mysql_change_user = ctx->data_plugin == 0; + ctx->mpvio.cached_server_reply.pkt = (uchar *)ctx->data; + ctx->mpvio.cached_server_reply.pkt_len = ctx->data_len; + ctx->mpvio.read_packet = client_mpvio_read_packet; + ctx->mpvio.write_packet = client_mpvio_write_packet; + ctx->mpvio.read_packet_nonblocking = client_mpvio_read_packet_nonblocking; + ctx->mpvio.write_packet_nonblocking = client_mpvio_write_packet_nonblocking; + ctx->mpvio.info = client_mpvio_info; + ctx->mpvio.mysql = mysql; + ctx->mpvio.packets_read = ctx->mpvio.packets_written = 0; + ctx->mpvio.db = ctx->db; + ctx->mpvio.plugin = ctx->auth_plugin; + ctx->client_auth_plugin_state = + (int)(client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_READING_PASSWORD); + + ctx->state_function = authsm_run_first_authenticate_user; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - mpvio.mysql_change_user = data_plugin == 0; - mpvio.cached_server_reply.pkt = (uchar *)data; - mpvio.cached_server_reply.pkt_len = data_len; - mpvio.read_packet = client_mpvio_read_packet; - mpvio.write_packet = client_mpvio_write_packet; - mpvio.info = client_mpvio_info; - mpvio.mysql = mysql; - mpvio.packets_read = mpvio.packets_written = 0; - mpvio.db = db; - mpvio.plugin = auth_plugin; +/** + Authentication can have two authenticate_user calls, depending on + what the server responds with; this handles the first. +*/ +static mysql_state_machine_status authsm_run_first_authenticate_user( + mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + MYSQL_TRACE(AUTH_PLUGIN, mysql, (ctx->auth_plugin->name)); + + if (ctx->non_blocking && ctx->auth_plugin->authenticate_user_nonblocking) { + net_async_status status = ctx->auth_plugin->authenticate_user_nonblocking( + (struct MYSQL_PLUGIN_VIO *)&ctx->mpvio, mysql, &ctx->res); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(STATE_MACHINE_WOULD_BLOCK); + } + } else { + ctx->res = ctx->auth_plugin->authenticate_user( + (struct MYSQL_PLUGIN_VIO *)&ctx->mpvio, mysql); + } - MYSQL_TRACE(AUTH_PLUGIN, mysql, (auth_plugin->name)); + ctx->state_function = authsm_handle_first_authenticate_user; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - res = auth_plugin->authenticate_user((MYSQL_PLUGIN_VIO *)&mpvio, mysql); +/** + Handle the result of the first authenticate_user. +*/ +static mysql_state_machine_status authsm_handle_first_authenticate_user( + mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; DBUG_PRINT("info", ("authenticate_user returned %s", - res == CR_OK ? "CR_OK" - : res == CR_ERROR ? "CR_ERROR" - : res == CR_OK_HANDSHAKE_COMPLETE - ? "CR_OK_HANDSHAKE_COMPLETE" - : "error")); + ctx->res == CR_OK + ? "CR_OK" + : ctx->res == CR_ERROR ? "CR_ERROR" + : ctx->res == CR_OK_HANDSHAKE_COMPLETE + ? "CR_OK_HANDSHAKE_COMPLETE" + : "error")); static_assert(CR_OK == -1, ""); static_assert(CR_ERROR == 0, ""); @@ -4040,7 +5252,7 @@ int run_plugin_auth(MYSQL *mysql, char *data, uint data_len, already performed required checks. Further, server side plugin did not really care about plugin used by client in this case. */ - if (res > CR_OK && + if (ctx->res > CR_OK && (!my_net_is_inited(&mysql->net) || (mysql->net.read_pos[0] != 0 && mysql->net.read_pos[0] != 254))) { /* @@ -4048,92 +5260,156 @@ int run_plugin_auth(MYSQL *mysql, char *data, uint data_len, unless the error code is CR_ERROR and mysql->net.last_errno is already set (the plugin has done it) */ - DBUG_PRINT("info", ("res=%d", res)); - if (res > CR_ERROR) - set_mysql_error(mysql, res, unknown_sqlstate); + DBUG_PRINT("info", ("res=%d", ctx->res)); + if (ctx->res > CR_ERROR) + set_mysql_error(mysql, ctx->res, unknown_sqlstate); else if (!mysql->net.last_errno) set_mysql_error(mysql, CR_UNKNOWN_ERROR, unknown_sqlstate); - DBUG_RETURN(1); + DBUG_RETURN(STATE_MACHINE_FAILED); } + ctx->state_function = authsm_read_change_user_result; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} +/** + After the first authenticate_user comes a call to read the result of the + implied change_user. +*/ +static mysql_state_machine_status authsm_read_change_user_result( + mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; /* read the OK packet (or use the cached value in mysql->net.read_pos */ - if (res == CR_OK) - pkt_length = (*mysql->methods->read_change_user_result)(mysql); - else /* res == CR_OK_HANDSHAKE_COMPLETE */ - pkt_length = mpvio.last_read_packet_len; + if (ctx->res == CR_OK) { + if (ctx->non_blocking) { + net_async_status status = + (*mysql->methods->read_change_user_result_nonblocking)( + mysql, &ctx->pkt_length); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(STATE_MACHINE_WOULD_BLOCK); + } + } else { + ctx->pkt_length = (*mysql->methods->read_change_user_result)(mysql); + } + } else /* res == CR_OK_HANDSHAKE_COMPLETE */ + ctx->pkt_length = ctx->mpvio.last_read_packet_len; + + ctx->state_function = authsm_handle_change_user_result; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - DBUG_PRINT("info", ("OK packet length=%lu", pkt_length)); - if (pkt_length == packet_error) { +/** + Check if server asked to use a different authentication plugin +*/ +static mysql_state_machine_status authsm_handle_change_user_result( + mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + DBUG_PRINT("info", ("OK packet length=%lu", ctx->pkt_length)); + if (ctx->pkt_length == packet_error) { if (mysql->net.last_errno == CR_SERVER_LOST) set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, ER_CLIENT(CR_SERVER_LOST_EXTENDED), "reading authorization packet", errno); - DBUG_RETURN(1); + DBUG_RETURN(STATE_MACHINE_FAILED); } if (mysql->net.read_pos[0] == 254) { - /* The server asked to use a different authentication plugin */ - if (pkt_length < 2) { - set_mysql_error(mysql, CR_MALFORMED_PACKET, - unknown_sqlstate); /* purecov: inspected */ - DBUG_RETURN(1); - } else { - /* "use different plugin" packet */ - uint len; - auth_plugin_name = (char *)mysql->net.read_pos + 1; - len = (uint)strlen( - auth_plugin_name); /* safe as my_net_read always appends \0 */ - mpvio.cached_server_reply.pkt_len = pkt_length - len - 2; - mpvio.cached_server_reply.pkt = mysql->net.read_pos + len + 2; - DBUG_PRINT("info", ("change plugin packet from server for plugin %s", - auth_plugin_name)); - } - - if (!(auth_plugin = (auth_plugin_t *)mysql_client_find_plugin( - mysql, auth_plugin_name, MYSQL_CLIENT_AUTHENTICATION_PLUGIN))) - DBUG_RETURN(1); - - if (check_plugin_enabled(mysql, auth_plugin)) DBUG_RETURN(1); + ctx->state_function = authsm_run_second_authenticate_user; + } else + ctx->state_function = authsm_finish_auth; - MYSQL_TRACE(AUTH_PLUGIN, mysql, (auth_plugin->name)); + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - mpvio.plugin = auth_plugin; - res = auth_plugin->authenticate_user((MYSQL_PLUGIN_VIO *)&mpvio, mysql); +/** + Start the authentication process again with the plugin which + server asked for. +*/ +static mysql_state_machine_status authsm_run_second_authenticate_user( + mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + /* The server asked to use a different authentication plugin */ + if (ctx->pkt_length < 2) { + set_mysql_error(mysql, CR_MALFORMED_PACKET, + unknown_sqlstate); /* purecov: inspected */ + DBUG_RETURN(STATE_MACHINE_FAILED); + } else { + /* "use different plugin" packet */ + uint len; + ctx->auth_plugin_name = (char *)mysql->net.read_pos + 1; + len = (uint)strlen( + ctx->auth_plugin_name); /* safe as my_net_read always appends \0 */ + ctx->mpvio.cached_server_reply.pkt_len = ctx->pkt_length - len - 2; + ctx->mpvio.cached_server_reply.pkt = mysql->net.read_pos + len + 2; + DBUG_PRINT("info", ("change plugin packet from server for plugin %s", + ctx->auth_plugin_name)); + } + if (!(ctx->auth_plugin = (auth_plugin_t *)mysql_client_find_plugin( + mysql, ctx->auth_plugin_name, MYSQL_CLIENT_AUTHENTICATION_PLUGIN))) + DBUG_RETURN(STATE_MACHINE_FAILED); + + if (check_plugin_enabled(mysql, ctx)) DBUG_RETURN(STATE_MACHINE_FAILED); + + MYSQL_TRACE(AUTH_PLUGIN, mysql, (ctx->auth_plugin->name)); + + ctx->mpvio.plugin = ctx->auth_plugin; + ctx->res = ctx->auth_plugin->authenticate_user( + (struct MYSQL_PLUGIN_VIO *)&ctx->mpvio, mysql); + + ctx->state_function = authsm_handle_second_authenticate_user; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - DBUG_PRINT("info", ("second authenticate_user returned %s", - res == CR_OK - ? "CR_OK" - : res == CR_ERROR ? "CR_ERROR" - : res == CR_OK_HANDSHAKE_COMPLETE - ? "CR_OK_HANDSHAKE_COMPLETE" - : "error")); - if (res > CR_OK) { - if (res > CR_ERROR) - set_mysql_error(mysql, res, unknown_sqlstate); - else if (!mysql->net.last_errno) - set_mysql_error(mysql, CR_UNKNOWN_ERROR, unknown_sqlstate); - DBUG_RETURN(1); - } +/* Now read the results. */ +static mysql_state_machine_status authsm_handle_second_authenticate_user( + mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + DBUG_PRINT("info", + ("second authenticate_user returned %s", + ctx->res == CR_OK + ? "CR_OK" + : ctx->res == CR_ERROR ? "CR_ERROR" + : ctx->res == CR_OK_HANDSHAKE_COMPLETE + ? "CR_OK_HANDSHAKE_COMPLETE" + : "error")); + if (ctx->res > CR_OK) { + if (ctx->res > CR_ERROR) + set_mysql_error(mysql, ctx->res, unknown_sqlstate); + else if (!mysql->net.last_errno) + set_mysql_error(mysql, CR_UNKNOWN_ERROR, unknown_sqlstate); + DBUG_RETURN(STATE_MACHINE_FAILED); + } - if (res != CR_OK_HANDSHAKE_COMPLETE) { - /* Read what server thinks about out new auth message report */ - if (cli_safe_read(mysql, NULL) == packet_error) { - if (mysql->net.last_errno == CR_SERVER_LOST) - set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, - ER_CLIENT(CR_SERVER_LOST_EXTENDED), - "reading final connect information", errno); - DBUG_RETURN(1); - } + if (ctx->res != CR_OK_HANDSHAKE_COMPLETE) { + /* Read what server thinks about out new auth message report */ + if (cli_safe_read(mysql, NULL) == packet_error) { + if (mysql->net.last_errno == CR_SERVER_LOST) + set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, + ER_CLIENT(CR_SERVER_LOST_EXTENDED), + "reading final connect information", errno); + DBUG_RETURN(STATE_MACHINE_FAILED); } } + + ctx->state_function = authsm_finish_auth; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} + +/* Final cleanup */ +static mysql_state_machine_status authsm_finish_auth(mysql_async_auth *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; /* net->read_pos[0] should always be 0 here if the server implements the protocol correctly */ - res = (mysql->net.read_pos[0] != 0); + ctx->res = (mysql->net.read_pos[0] != 0); MYSQL_TRACE(AUTHENTICATED, mysql, ()); - DBUG_RETURN(res); + DBUG_RETURN(ctx->res ? STATE_MACHINE_FAILED : STATE_MACHINE_DONE); } /** set some default attributes */ @@ -4180,34 +5456,156 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, uint port, const char *unix_socket, ulong client_flag) { - char buff[NAME_LEN + USERNAME_LENGTH + 100]; - int scramble_data_len, pkt_scramble_len = 0; - char *end, *host_info = 0, *server_version_end, *pkt_end; - char *scramble_data; - char *scramble_buffer = NULL; - const char *scramble_plugin; - ulong pkt_length; + DBUG_ENTER("mysql_real_connect"); + mysql_state_machine_status status; + mysql_async_connect ctx; + memset(&ctx, 0, sizeof(ctx)); + + ctx.mysql = mysql; + ctx.host = host; + ctx.port = port; + ctx.db = db; + ctx.user = user; + ctx.passwd = passwd; + ctx.unix_socket = unix_socket; + ctx.client_flag = client_flag; + ctx.state_function = csm_begin_connect; + ctx.ssl_state = SSL_NONE; + + do { + status = ctx.state_function(&ctx); + } while (status != STATE_MACHINE_FAILED && status != STATE_MACHINE_DONE); + + if (status == STATE_MACHINE_DONE) { + DBUG_PRINT("exit", ("Mysql handler: %p", mysql)); + DBUG_RETURN(mysql); + } + + DBUG_PRINT("error", ("message: %u/%s (%s)", mysql->net.last_errno, + mysql->net.sqlstate, mysql->net.last_error)); + { + /* Free alloced memory */ + end_server(mysql); + mysql_close_free(mysql); + if (!(client_flag & CLIENT_REMEMBER_OPTIONS)) + mysql_close_free_options(mysql); + if (ctx.scramble_buffer_allocated) my_free(ctx.scramble_buffer); + } + DBUG_RETURN(0); +} + +/** + This API attempts to initialize all the context needed to make an asynchronous + connection followed by establishing a connection to MySQL database. If this + API returns NET_ASYNC_COMPLETE then connection is established else call this + API from the client application until the status returned is + NET_ASYNC_COMPLETE. + + @param[in] mysql connection handle + @param[in] host host name or IP address + @param[in] user login ID used to connect to host + @param[in] passwd password for this login ID + @param[in] db default database to be set after connection + @param[in] port port number to use for connection + @param[in] unix_socket socket file to use for connection + @param[in] client_flag flag to indidcate what client can handle + + @retval NET_ASYNC_COMPLETE Success. + @retval NET_ASYNC_ERROR Error. +*/ +net_async_status STDCALL mysql_real_connect_nonblocking( + MYSQL *mysql, const char *host, const char *user, const char *passwd, + const char *db, uint port, const char *unix_socket, ulong client_flag) { + DBUG_ENTER(__func__); + + mysql_state_machine_status status; + mysql_async_connect *ctx = ASYNC_DATA(mysql)->connect_context; + + if (client_flag & MYSQL_OPT_COMPRESS) { + set_mysql_error(mysql, CR_COMPRESSION_NOT_SUPPORTED, unknown_sqlstate); + DBUG_RETURN(NET_ASYNC_ERROR); + } + if (!ctx) { + ctx = static_cast( + my_malloc(key_memory_MYSQL, sizeof(*ctx), MYF(MY_WME | MY_ZEROFILL))); + if (!ctx) DBUG_RETURN(NET_ASYNC_ERROR); + + ctx->mysql = mysql; + ctx->host = host; + ctx->port = port; + ctx->db = db; + ctx->user = user; + ctx->passwd = passwd; + ctx->unix_socket = unix_socket; + ctx->client_flag = client_flag; + ctx->non_blocking = true; + ctx->state_function = csm_begin_connect; + ctx->ssl_state = SSL_NONE; + ASYNC_DATA(mysql)->connect_context = ctx; + ASYNC_DATA(mysql)->async_op_status = ASYNC_OP_CONNECT; + } + + do { + status = ctx->state_function(ctx); + } while (status != STATE_MACHINE_FAILED && status != STATE_MACHINE_DONE); + + if (status == STATE_MACHINE_DONE) { + my_free(ASYNC_DATA(mysql)->connect_context); + ASYNC_DATA(mysql)->connect_context = NULL; + ASYNC_DATA(mysql)->async_op_status = ASYNC_OP_UNSET; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + { + DBUG_PRINT("error", ("message: %u/%s (%s)", mysql->net.last_errno, + mysql->net.sqlstate, mysql->net.last_error)); + /* Free alloced memory */ + end_server(mysql); + mysql_close_free(mysql); + if (!(ctx->client_flag & CLIENT_REMEMBER_OPTIONS)) + mysql_close_free_options(mysql); + if (ctx->scramble_buffer_allocated) { + my_free(ctx->scramble_buffer); + ctx->scramble_buffer = nullptr; + } + my_free(ctx); + DBUG_RETURN(NET_ASYNC_ERROR); + } +} +/** + Begin the connection to the server, including any DNS resolution + necessary, socket configuration, etc. +*/ +static mysql_state_machine_status csm_begin_connect(mysql_async_connect *ctx) { + MYSQL *mysql = ctx->mysql; + const char *host = ctx->host; + const char *user = ctx->user; + const char *passwd = ctx->passwd; + const char *db = ctx->db; + uint port = ctx->port; + const char *unix_socket = ctx->unix_socket; + ulong client_flag = ctx->client_flag; + + DBUG_ENTER(__func__); + + DBUG_PRINT("enter", + ("host: %s db: %s user: %s (client)", host ? host : "(Null)", + db ? db : "(Null)", user ? user : "(Null)")); + NET *net = &mysql->net; - bool scramble_buffer_allocated = false; #ifdef _WIN32 HANDLE hPipe = INVALID_HANDLE_VALUE; #endif #ifdef HAVE_SYS_UN_H struct sockaddr_un UNIXaddr; #endif - DBUG_ENTER("mysql_real_connect"); - - DBUG_PRINT("enter", - ("host: %s db: %s user: %s (client)", host ? host : "(Null)", - db ? db : "(Null)", user ? user : "(Null)")); - /* Test whether we're already connected */ if (net->vio) { set_mysql_error(mysql, CR_ALREADY_CONNECTED, unknown_sqlstate); - DBUG_RETURN(0); + DBUG_RETURN(STATE_MACHINE_FAILED); } - if (set_connect_attributes(mysql, buff, sizeof(buff))) DBUG_RETURN(0); + if (set_connect_attributes(mysql, ctx->buff, sizeof(ctx->buff))) + DBUG_RETURN(STATE_MACHINE_FAILED); mysql->methods = &client_methods; net->vio = 0; /* If something goes wrong */ @@ -4247,9 +5645,6 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, MYSQL_TRACE_STAGE(mysql, CONNECTING); MYSQL_TRACE(CONNECTING, mysql, ()); - /* - Part 0: Grab a socket and connect it to the server - */ #if defined(_WIN32) if ((!mysql->options.protocol || mysql->options.protocol == MYSQL_PROTOCOL_MEMORY) && @@ -4265,8 +5660,8 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, ("host: '%s' socket: '%s' shared memory: %s have_tcpip: %d", host ? host : "", unix_socket ? unix_socket : "", mysql->options.shared_memory_base_name, (int)have_tcpip)); - if (mysql->options.protocol == MYSQL_PROTOCOL_MEMORY) goto error; - + if (mysql->options.protocol == MYSQL_PROTOCOL_MEMORY) + DBUG_RETURN(STATE_MACHINE_FAILED); /* Try also with PIPE or TCP/IP. Clear the error from create_shared_memory(). @@ -4277,7 +5672,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, mysql->options.protocol = MYSQL_PROTOCOL_MEMORY; unix_socket = 0; host = mysql->options.shared_memory_base_name; - snprintf(host_info = buff, sizeof(buff) - 1, + snprintf(ctx->host_info = ctx->buff, sizeof(ctx->buff) - 1, ER_CLIENT(CR_SHARED_MEMORY_CONNECTION), host); } } @@ -4293,7 +5688,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, if (sock == SOCKET_ERROR) { set_mysql_extended_error(mysql, CR_SOCKET_CREATE_ERROR, unknown_sqlstate, ER_CLIENT(CR_SOCKET_CREATE_ERROR), socket_errno); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } net->vio = @@ -4302,12 +5697,14 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, DBUG_PRINT("error", ("Unknow protocol %d ", mysql->options.protocol)); set_mysql_error(mysql, CR_CONN_UNKNOW_PROTOCOL, unknown_sqlstate); closesocket(sock); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } + if (ctx->non_blocking) vio_set_blocking_flag(net->vio, !ctx->non_blocking); + host = LOCAL_HOST; if (!unix_socket) unix_socket = mysql_unix_port; - host_info = (char *)ER_CLIENT(CR_LOCALHOST_CONNECTION); + ctx->host_info = (char *)ER_CLIENT(CR_LOCALHOST_CONNECTION); DBUG_PRINT("info", ("Using UNIX sock '%s'", unix_socket)); memset(&UNIXaddr, 0, sizeof(UNIXaddr)); @@ -4318,7 +5715,8 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, my_net_set_retry_count(net, mysql->options.extension->retry_count); if (vio_socket_connect(net->vio, (struct sockaddr *)&UNIXaddr, - sizeof(UNIXaddr), get_vio_connect_timeout(mysql))) { + sizeof(UNIXaddr), ctx->non_blocking, + get_vio_connect_timeout(mysql))) { DBUG_PRINT("error", ("Got error %d on connect to local server", socket_errno)); set_mysql_extended_error(mysql, CR_CONNECTION_ERROR, unknown_sqlstate, @@ -4326,7 +5724,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, socket_errno); vio_delete(net->vio); net->vio = 0; - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } mysql->options.protocol = MYSQL_PROTOCOL_SOCKET; } @@ -4345,11 +5743,11 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, if (mysql->options.protocol == MYSQL_PROTOCOL_PIPE || (host && !strcmp(host, LOCAL_HOST_NAMEDPIPE)) || (unix_socket && !strcmp(unix_socket, MYSQL_NAMEDPIPE))) - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); /* Try also with TCP/IP */ } else { net->vio = vio_new_win32pipe(hPipe); - snprintf(host_info = buff, sizeof(buff) - 1, + snprintf(ctx->host_info = ctx->buff, sizeof(ctx->buff) - 1, ER_CLIENT(CR_NAMEDPIPE_CONNECTION), unix_socket); } } @@ -4370,8 +5768,8 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, if (!host) host = LOCAL_HOST; - snprintf(host_info = buff, sizeof(buff) - 1, ER_CLIENT(CR_TCP_CONNECTION), - host); + snprintf(ctx->host_info = ctx->buff, sizeof(ctx->buff) - 1, + ER_CLIENT(CR_TCP_CONNECTION), host); DBUG_PRINT("info", ("Server name: '%s'. TCP sock: %d", host, port)); memset(&hints, 0, sizeof(hints)); @@ -4391,8 +5789,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, DBUG_PRINT("info", ("IPV6 getaddrinfo error %d", gai_errno)); set_mysql_extended_error(mysql, CR_UNKNOWN_HOST, unknown_sqlstate, ER_CLIENT(CR_UNKNOWN_HOST), host, errno); - - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } /* Get address info for client bind name if it is provided */ @@ -4412,7 +5809,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, mysql->options.bind_address, bind_gai_errno); freeaddrinfo(res_lst); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } DBUG_PRINT("info", (" got address info for client bind name")); } @@ -4480,7 +5877,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, closesocket(sock); freeaddrinfo(res_lst); if (client_bind_ai_lst) freeaddrinfo(client_bind_ai_lst); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } } /* Just reinitialize if one is already allocated. */ @@ -4489,17 +5886,20 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, closesocket(sock); freeaddrinfo(res_lst); if (client_bind_ai_lst) freeaddrinfo(client_bind_ai_lst); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } + if (ctx->non_blocking) + vio_set_blocking_flag(net->vio, !ctx->non_blocking); + DBUG_PRINT("info", ("Connect socket")); if (mysql->options.extension && mysql->options.extension->retry_count) my_net_set_retry_count(net, mysql->options.extension->retry_count); - status = vio_socket_connect(net->vio, t_res->ai_addr, - (socklen_t)t_res->ai_addrlen, - get_vio_connect_timeout(mysql)); + status = vio_socket_connect( + net->vio, t_res->ai_addr, (socklen_t)t_res->ai_addrlen, + ctx->non_blocking, get_vio_connect_timeout(mysql)); /* Here we rely on vio_socket_connect() to return success only if the connect attempt was really successful. Otherwise we would @@ -4526,7 +5926,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, if (sock == SOCKET_ERROR) { set_mysql_extended_error(mysql, CR_IPSOCK_ERROR, unknown_sqlstate, ER_CLIENT(CR_IPSOCK_ERROR), saved_error); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } if (status) { @@ -4535,22 +5935,41 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, set_mysql_extended_error(mysql, CR_CONN_HOST_ERROR, unknown_sqlstate, ER_CLIENT(CR_CONN_HOST_ERROR), host, saved_error); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } } + ctx->state_function = csm_complete_connect; + ctx->host = host; + ctx->user = user; + ctx->passwd = passwd; + ctx->db = db; + ctx->port = port; + ctx->unix_socket = unix_socket; + ctx->client_flag = client_flag; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} + +/** + Complete the connection itself, setting options on the now-connected socket. +*/ +static mysql_state_machine_status csm_complete_connect( + mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + NET *net = &mysql->net; DBUG_PRINT("info", ("net->vio: %p", net->vio)); if (!net->vio) { DBUG_PRINT("error", ("Unknow protocol %d ", mysql->options.protocol)); set_mysql_error(mysql, CR_CONN_UNKNOW_PROTOCOL, unknown_sqlstate); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } if (my_net_init(net, net->vio)) { vio_delete(net->vio); net->vio = 0; set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } vio_keepalive(net->vio, true); @@ -4581,22 +6000,51 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, ER_CLIENT(CR_SERVER_LOST_EXTENDED), "waiting for initial communication packet", socket_errno); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } + ctx->state_function = csm_read_greeting; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - /* - Part 1: Connection established, read and parse first packet - */ +/** + Read the greeting from the server that is read the first packet +*/ +static mysql_state_machine_status csm_read_greeting(mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; DBUG_PRINT("info", ("Read first packet.")); - if ((pkt_length = cli_safe_read(mysql, NULL)) == packet_error) { + if (!ctx->non_blocking) + ctx->pkt_length = cli_safe_read(mysql, NULL); + else { + if (cli_safe_read_nonblocking(mysql, NULL, &ctx->pkt_length) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(STATE_MACHINE_WOULD_BLOCK); + } + } + if (ctx->pkt_length == packet_error) { if (mysql->net.last_errno == CR_SERVER_LOST) set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, ER_CLIENT(CR_SERVER_LOST_EXTENDED), "reading initial communication packet", socket_errno); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } + ctx->state_function = csm_parse_handshake; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} + +/** + Parse the handshake from the server. +*/ +static mysql_state_machine_status csm_parse_handshake( + mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + NET *net = &mysql->net; + int pkt_length = ctx->pkt_length; + int pkt_scramble_len = 0; + char *end, *server_version_end, *pkt_end; pkt_end = (char *)net->read_pos + pkt_length; /* Check if version of protocol matches current one */ mysql->protocol_version = net->read_pos[0]; @@ -4607,7 +6055,7 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, set_mysql_extended_error(mysql, CR_VERSION_ERROR, unknown_sqlstate, ER_CLIENT(CR_VERSION_ERROR), mysql->protocol_version, PROTOCOL_VERSION); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } server_version_end = end = strend((char *)net->read_pos + 1); mysql->thread_id = uint4korr((uchar *)end + 1); @@ -4616,10 +6064,10 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, Scramble is split into two parts because old clients do not understand long scrambles; here goes the first part. */ - scramble_data = end; - scramble_data_len = AUTH_PLUGIN_DATA_PART_1_LENGTH + 1; - scramble_plugin = NULL; - end += scramble_data_len; + ctx->scramble_data = end; + ctx->scramble_data_len = AUTH_PLUGIN_DATA_PART_1_LENGTH + 1; + ctx->scramble_plugin = NULL; + end += ctx->scramble_data_len; if (pkt_end >= end + 1) mysql->server_capabilities = uint2korr((uchar *)end); if (pkt_end >= end + 18) { @@ -4631,34 +6079,34 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, if (pkt_scramble_len < 0) { set_mysql_error(mysql, CR_MALFORMED_PACKET, unknown_sqlstate); /* purecov: inspected */ - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } } end += 18; - if (mysql_init_character_set(mysql)) goto error; + if (mysql_init_character_set(mysql)) DBUG_RETURN(STATE_MACHINE_FAILED); /* Save connection information */ - if (!my_multi_malloc(key_memory_MYSQL, MYF(0), &mysql->host_info, - (uint)strlen(host_info) + 1, &mysql->host, - (uint)strlen(host) + 1, &mysql->unix_socket, - unix_socket ? (uint)strlen(unix_socket) + 1 : (uint)1, - &mysql->server_version, - (uint)(server_version_end - (char *)net->read_pos + 1), - NullS) || - !(mysql->user = my_strdup(key_memory_MYSQL, user, MYF(0))) || - !(mysql->passwd = my_strdup(key_memory_MYSQL, passwd, MYF(0)))) { + if (!my_multi_malloc( + key_memory_MYSQL, MYF(0), &mysql->host_info, + (uint)strlen(ctx->host_info) + 1, &mysql->host, + (uint)strlen(ctx->host) + 1, &mysql->unix_socket, + ctx->unix_socket ? (uint)strlen(ctx->unix_socket) + 1 : (uint)1, + &mysql->server_version, + (uint)(server_version_end - (char *)net->read_pos + 1), NullS) || + !(mysql->user = my_strdup(key_memory_MYSQL, ctx->user, MYF(0))) || + !(mysql->passwd = my_strdup(key_memory_MYSQL, ctx->passwd, MYF(0)))) { set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } - my_stpcpy(mysql->host_info, host_info); - my_stpcpy(mysql->host, host); - if (unix_socket) - my_stpcpy(mysql->unix_socket, unix_socket); + my_stpcpy(mysql->host_info, ctx->host_info); + my_stpcpy(mysql->host, ctx->host); + if (ctx->unix_socket) + my_stpcpy(mysql->unix_socket, ctx->unix_socket); else mysql->unix_socket = 0; my_stpcpy(mysql->server_version, (char *)net->read_pos + 1); - mysql->port = port; + mysql->port = ctx->port; if (pkt_end >= end + SCRAMBLE_LENGTH - AUTH_PLUGIN_DATA_PART_1_LENGTH + 1) @@ -4668,137 +6116,195 @@ MYSQL *STDCALL mysql_real_connect(MYSQL *mysql, const char *host, to get a full continuous scramble. We've read all the header, and can overwrite it now. */ - memmove(end - AUTH_PLUGIN_DATA_PART_1_LENGTH, scramble_data, + memmove(end - AUTH_PLUGIN_DATA_PART_1_LENGTH, ctx->scramble_data, AUTH_PLUGIN_DATA_PART_1_LENGTH); - scramble_data = end - AUTH_PLUGIN_DATA_PART_1_LENGTH; + ctx->scramble_data = end - AUTH_PLUGIN_DATA_PART_1_LENGTH; if (mysql->server_capabilities & CLIENT_PLUGIN_AUTH) { - scramble_data_len = pkt_scramble_len; - scramble_plugin = scramble_data + scramble_data_len; - if (scramble_data + scramble_data_len > pkt_end) - scramble_data_len = (int)(pkt_end - scramble_data); + ctx->scramble_data_len = pkt_scramble_len; + ctx->scramble_plugin = ctx->scramble_data + ctx->scramble_data_len; + if (ctx->scramble_data + ctx->scramble_data_len > pkt_end) + ctx->scramble_data_len = (int)(pkt_end - ctx->scramble_data); } else { - scramble_data_len = (int)(pkt_end - scramble_data); - scramble_plugin = caching_sha2_password_plugin_name; + ctx->scramble_data_len = (int)(pkt_end - ctx->scramble_data); + ctx->scramble_plugin = caching_sha2_password_plugin_name; } } else { set_mysql_error(mysql, CR_MALFORMED_PACKET, unknown_sqlstate); - goto error; + DBUG_RETURN(STATE_MACHINE_FAILED); } + ctx->state_function = csm_establish_ssl; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - MYSQL_TRACE(INIT_PACKET_RECEIVED, mysql, (pkt_length, net->read_pos)); - MYSQL_TRACE_STAGE(mysql, AUTHENTICATE); +/** + Establish SSL if needed. +*/ +static mysql_state_machine_status csm_establish_ssl(mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + /* This check happens to work for both sync and async. */ + if (ctx->ssl_state == SSL_NONE) { + MYSQL_TRACE(INIT_PACKET_RECEIVED, mysql, + (ctx->pkt_length, mysql->net.read_pos)); + MYSQL_TRACE_STAGE(mysql, AUTHENTICATE); #if defined(_WIN32) - if ((mysql->options.extension && - mysql->options.extension->ssl_mode <= SSL_MODE_PREFERRED) && - (mysql->options.protocol == MYSQL_PROTOCOL_MEMORY || - mysql->options.protocol == MYSQL_PROTOCOL_PIPE)) { - mysql->options.extension->ssl_mode = SSL_MODE_DISABLED; - } + if ((mysql->options.extension && + mysql->options.extension->ssl_mode <= SSL_MODE_PREFERRED) && + (mysql->options.protocol == MYSQL_PROTOCOL_MEMORY || + mysql->options.protocol == MYSQL_PROTOCOL_PIPE)) { + mysql->options.extension->ssl_mode = SSL_MODE_DISABLED; + } #endif - /* try and bring up SSL if possible */ - cli_calculate_client_flag(mysql, db, client_flag); + /* try and bring up SSL if possible */ + cli_calculate_client_flag(mysql, ctx->db, ctx->client_flag); - /* - Allocate separate buffer for scramble data if we are going - to attempt TLS connection. This would prevent a possible - overwrite through my_net_write. - */ - if (scramble_data_len && mysql->options.extension && - mysql->options.extension->ssl_mode != SSL_MODE_DISABLED) { - if (!(scramble_buffer = (char *)my_malloc( - key_memory_MYSQL_HANDSHAKE, scramble_data_len, MYF(MY_WME)))) { - set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); - goto error; + /* + Allocate separate buffer for scramble data if we are going + to attempt TLS connection. This would prevent a possible + overwrite through my_net_write. + */ + if (ctx->scramble_data_len && mysql->options.extension && + mysql->options.extension->ssl_mode != SSL_MODE_DISABLED) { + if (!(ctx->scramble_buffer = + (char *)my_malloc(key_memory_MYSQL_HANDSHAKE, + ctx->scramble_data_len, MYF(MY_WME)))) { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + DBUG_RETURN(STATE_MACHINE_FAILED); + } + ctx->scramble_buffer_allocated = true; + memcpy(ctx->scramble_buffer, ctx->scramble_data, ctx->scramble_data_len); + } else { + ctx->scramble_buffer = ctx->scramble_data; + } + } + if (ctx->non_blocking) { + int ret; + if (cli_establish_ssl_nonblocking(mysql, &ret) == NET_ASYNC_NOT_READY) { + DBUG_RETURN(STATE_MACHINE_WOULD_BLOCK); + } + if (ret) { + DBUG_RETURN(STATE_MACHINE_FAILED); } - scramble_buffer_allocated = true; - memcpy(scramble_buffer, scramble_data, scramble_data_len); } else { - scramble_buffer = scramble_data; + if (cli_establish_ssl(mysql)) { + DBUG_RETURN(STATE_MACHINE_FAILED); + } } - if (cli_establish_ssl(mysql)) goto error; - - /* - Part 2: invoke the plugin to send the authentication data to the server - */ + ctx->state_function = csm_authenticate; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - if (run_plugin_auth(mysql, scramble_buffer, scramble_data_len, - scramble_plugin, db)) - goto error; +/** + Invoke the authentication client plugin API to send the authentication + data to the server +*/ +static mysql_state_machine_status csm_authenticate(mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + if (ctx->non_blocking) { + mysql_state_machine_status status = run_plugin_auth_nonblocking( + ctx->mysql, ctx->scramble_data, ctx->scramble_data_len, + ctx->scramble_plugin, ctx->db); + if (status != STATE_MACHINE_DONE) { + DBUG_RETURN(status); + } + } else { + if (run_plugin_auth(mysql, ctx->scramble_buffer, ctx->scramble_data_len, + ctx->scramble_plugin, ctx->db)) { + DBUG_RETURN(STATE_MACHINE_FAILED); + } + } - if (scramble_buffer_allocated == true) { - scramble_buffer_allocated = false; - my_free(scramble_buffer); + if (ctx->scramble_buffer_allocated) { + ctx->scramble_buffer_allocated = false; + my_free(ctx->scramble_buffer); + ctx->scramble_buffer = nullptr; } - MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); + ctx->state_function = csm_prep_select_database; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - /* - Part 3: authenticated, finish the initialization of the connection - */ +/** + Authenticated, set intial database if specified +*/ +static mysql_state_machine_status csm_prep_select_database( + mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + NET *net = &mysql->net; + MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); if (mysql->client_flag & CLIENT_COMPRESS) /* We will use compression */ net->compress = 1; #ifdef CHECK_LICENSE - if (check_license(mysql)) goto error; + if (check_license(mysql)) DBUG_RETURN(STATE_MACHINE_FAILED); #endif - if (db && !mysql->db && mysql_select_db(mysql, db)) { - if (mysql->net.last_errno == CR_SERVER_LOST) - set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate, - ER_CLIENT(CR_SERVER_LOST_EXTENDED), - "Setting intital database", errno); - goto error; - } +#ifdef MYSQL_SERVER + DBUG_RETURN(STATE_MACHINE_DONE); +#else + ctx->state_function = csm_prep_init_commands; +#endif - /* - Using init_commands is not supported when connecting from within the - server. - */ -#ifndef MYSQL_SERVER - if (mysql->options.init_commands) { - char **ptr = mysql->options.init_commands->begin(); - char **end_command = mysql->options.init_commands->end(); + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - bool reconnect = mysql->reconnect; - mysql->reconnect = 0; +#ifndef MYSQL_SERVER +/** + Prepare to send a sequence of init commands. +*/ +static mysql_state_machine_status csm_prep_init_commands( + mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + if (!mysql->options.init_commands) { + DBUG_RETURN(STATE_MACHINE_DONE); + } - for (; ptr < end_command; ptr++) { - int status; + ctx->saved_reconnect = mysql->reconnect; + mysql->reconnect = 0; + ctx->current_init_command = mysql->options.init_commands->begin(); - if (mysql_real_query(mysql, *ptr, (ulong)strlen(*ptr))) goto error; + ctx->state_function = csm_send_one_init_command; + DBUG_RETURN(STATE_MACHINE_CONTINUE); +} - do { - if (mysql->fields) { - MYSQL_RES *res; - if (!(res = cli_use_result(mysql))) goto error; - mysql_free_result(res); - } - if ((status = mysql_next_result(mysql)) > 0) goto error; - } while (status == 0); +/** + Send an init command. This is called once per init command until + they've all been run (or a failure occurs). +*/ +static mysql_state_machine_status csm_send_one_init_command( + mysql_async_connect *ctx) { + DBUG_ENTER(__func__); + MYSQL *mysql = ctx->mysql; + + if (mysql_real_query(mysql, *ctx->current_init_command, + (ulong)strlen(*ctx->current_init_command))) + DBUG_RETURN(STATE_MACHINE_FAILED); + int status; + do { + if (mysql->fields) { + MYSQL_RES *res; + if (!(res = cli_use_result(mysql))) DBUG_RETURN(STATE_MACHINE_FAILED); + mysql_free_result(res); } - mysql->reconnect = reconnect; - } -#endif - - DBUG_PRINT("exit", ("Mysql handler: %p", mysql)); - DBUG_RETURN(mysql); + if ((status = mysql_next_result(mysql)) > 0) + DBUG_RETURN(STATE_MACHINE_FAILED); + } while (status == 0); -error: - DBUG_PRINT("error", ("message: %u/%s (%s)", net->last_errno, net->sqlstate, - net->last_error)); - { - /* Free alloced memory */ - end_server(mysql); - mysql_close_free(mysql); - if (!(client_flag & CLIENT_REMEMBER_OPTIONS)) - mysql_close_free_options(mysql); - if (scramble_buffer_allocated) my_free(scramble_buffer); + ++ctx->current_init_command; + if (ctx->current_init_command < mysql->options.init_commands->end()) { + DBUG_RETURN(STATE_MACHINE_CONTINUE); } - DBUG_RETURN(0); + mysql->reconnect = ctx->saved_reconnect; + DBUG_PRINT("exit", ("Mysql handler: %p", mysql)); + DBUG_RETURN(STATE_MACHINE_DONE); } +#endif bool mysql_reconnect(MYSQL *mysql) { MYSQL tmp_mysql; @@ -5223,7 +6729,17 @@ void STDCALL mysql_close(MYSQL *mysql) { if (mysql->net.vio != 0) { free_old_query(mysql); mysql->status = MYSQL_STATUS_READY; /* Force command */ - simple_command(mysql, COM_QUIT, (uchar *)0, 0, 1); + if (vio_is_blocking(mysql->net.vio)) { + simple_command(mysql, COM_QUIT, (uchar *)0, 0, 1); + } else { + /* + Best effort; try to toss a command on the wire, but we can't wait + to hear back. + */ + bool err; /* unused */ + simple_command_nonblocking(mysql, COM_QUIT, (uchar *)0, 0, 1, &err); + } + mysql->reconnect = 0; end_server(mysql); /* Sets mysql->net.vio= 0 */ } @@ -5293,6 +6809,107 @@ static bool cli_read_query_result(MYSQL *mysql) { DBUG_PRINT("exit", ("ok")); DBUG_RETURN(0); } +static net_async_status cli_read_query_result_nonblocking(MYSQL *mysql) { + DBUG_ENTER(__func__); + NET *net = &mysql->net; + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + uchar *pos = nullptr; + ulong field_count; + ulong length; + + if (net_async->async_read_query_result_status == + NET_ASYNC_READ_QUERY_RESULT_IDLE) { + net_async->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_FIELD_COUNT; + } + + if (net_async->async_read_query_result_status == + NET_ASYNC_READ_QUERY_RESULT_FIELD_COUNT) { + net_async_status status = + cli_safe_read_nonblocking(mysql, nullptr, &length); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (length == packet_error) { + net_async->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_IDLE; + DBUG_RETURN(NET_ASYNC_ERROR); + } + mysql->packet_length = length; + + free_old_query(mysql); /* Free old result */ +#ifndef MYSQL_SERVER /* Avoid warn of unused labels*/ + get_info: +#endif + pos = (uchar *)mysql->net.read_pos; + if ((field_count = net_field_length(&pos)) == 0) { + read_ok_ex(mysql, length); +#if defined(CLIENT_PROTOCOL_TRACING) + if (mysql->server_status & SERVER_MORE_RESULTS_EXISTS) + MYSQL_TRACE_STAGE(mysql, WAIT_FOR_RESULT); + else + MYSQL_TRACE_STAGE(mysql, READY_FOR_COMMAND); +#endif + net_async->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_IDLE; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } +#ifndef MYSQL_SERVER + if (field_count == NULL_LENGTH) /* LOAD DATA LOCAL INFILE */ + { + int error; + + MYSQL_TRACE_STAGE(mysql, FILE_REQUEST); + + if (!(mysql->options.client_flag & CLIENT_LOCAL_FILES)) { + set_mysql_error(mysql, CR_MALFORMED_PACKET, unknown_sqlstate); + net_async->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_IDLE; + DBUG_RETURN(NET_ASYNC_ERROR); + } + + error = handle_local_infile(mysql, (char *)pos); + + MYSQL_TRACE_STAGE(mysql, WAIT_FOR_RESULT); + + /* TODO: Make LOAD DATA LOCAL INFILE asynchronous. */ + if ((length = cli_safe_read(mysql, NULL)) == packet_error || error) { + net_async->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_IDLE; + DBUG_RETURN(NET_ASYNC_ERROR); + } + goto get_info; /* Get info packet */ + } +#endif + if (!(mysql->server_status & SERVER_STATUS_AUTOCOMMIT)) + mysql->server_status |= SERVER_STATUS_IN_TRANS; + + mysql->field_count = (uint)field_count; + net_async->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_FIELD_INFO; + } + + if (net_async->async_read_query_result_status == + NET_ASYNC_READ_QUERY_RESULT_FIELD_INFO) { + int res; + net_async_status status = read_com_query_metadata_nonblocking( + mysql, pos, mysql->field_count, &res); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (res) { + net_async->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_IDLE; + DBUG_RETURN(NET_ASYNC_ERROR); + } + } + + mysql->status = MYSQL_STATUS_GET_RESULT; + DBUG_PRINT("exit", ("ok, %u", mysql->field_count)); + net_async->async_read_query_result_status = NET_ASYNC_READ_QUERY_RESULT_IDLE; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} /* Send the query and return so we can do something else. @@ -5311,6 +6928,38 @@ int STDCALL mysql_send_query(MYSQL *mysql, const char *query, ulong length) { DBUG_RETURN(simple_command(mysql, COM_QUERY, (uchar *)query, length, 1)); } +/** + Executes the SQL statement pointed by query. This API is called by + mysql_real_query_nonblocking to send query to server in asynchronous way. + + @param[in] mysql connection handle + @param[in] query query string to be executed + @param[in] length length of query + + @retval NET_ASYNC_ERROR query execution failed + @retval NET_ASYNC_NOT_READY query not yet completed, call this API again + @retval NET_ASYNC_COMPLETE query execution finished +*/ +net_async_status STDCALL mysql_send_query_nonblocking(MYSQL *mysql, + const char *query, + ulong length) { + DBUG_ENTER(__func__); + STATE_INFO *info; + + if ((info = STATE_DATA(mysql))) + free_state_change_info(static_cast(mysql->extension)); + + bool ret; + if (simple_command_nonblocking(mysql, COM_QUERY, (uchar *)query, length, 1, + &ret) == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (ret) + DBUG_RETURN(NET_ASYNC_ERROR); + else + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + int STDCALL mysql_real_query(MYSQL *mysql, const char *query, ulong length) { int retval; DBUG_ENTER("mysql_real_query"); @@ -5327,6 +6976,72 @@ int STDCALL mysql_real_query(MYSQL *mysql, const char *query, ulong length) { DBUG_RETURN(retval); } +/** + Executes the SQL statement pointed by query. This sql statement length is set + in length parameter. query string can contain multiple sql statements + separated by semicolons. This function can return immediately with status set + to NET_ASYNC_NOT_READY, in this case client application is expected to call + this API until it returns NET_ASYNC_COMPLETE. + + @param[in] mysql connection handle + @param[in] query query string to be executed + @param[in] length length of query + + @retval NET_ASYNC_ERROR query execution failed + @retval NET_ASYNC_NOT_READY query not yet completed, call this API again + @retval NET_ASYNC_COMPLETE query execution finished +*/ +net_async_status STDCALL mysql_real_query_nonblocking(MYSQL *mysql, + const char *query, + ulong length) { + DBUG_ENTER(__func__); + DBUG_PRINT("enter", ("handle: %p", mysql)); + DBUG_PRINT("query", ("Query = '%-.*s'", (int)length, query)); + DBUG_EXECUTE_IF("inject_ER_NET_READ_INTERRUPTED", { + mysql->net.last_errno = ER_NET_READ_INTERRUPTED; + DBUG_SET("-d,inject_ER_NET_READ_INTERRUPTED"); + DBUG_RETURN(NET_ASYNC_ERROR); + }); + MYSQL_ASYNC *async_context = ASYNC_DATA(mysql); + DBUG_ASSERT(async_context->async_op_status == ASYNC_OP_UNSET || + async_context->async_op_status == ASYNC_OP_QUERY); + + net_async_status status = NET_ASYNC_NOT_READY; + /* 1st phase: send query. */ + if (async_context->async_query_state == QUERY_IDLE) { + async_context->async_query_length = length; + async_context->async_op_status = ASYNC_OP_QUERY; + async_context->async_query_state = QUERY_SENDING; + } + + if (async_context->async_query_state == QUERY_SENDING) { + status = mysql_send_query_nonblocking(mysql, query, length); + if (status == NET_ASYNC_NOT_READY) + DBUG_RETURN(NET_ASYNC_NOT_READY); + else if (status == NET_ASYNC_ERROR) + goto end; + async_context->async_query_state = QUERY_READING_RESULT; + } + + /* 2nd phase: read query result (field count, field info) */ + if (async_context->async_query_state == QUERY_READING_RESULT) { + status = (*mysql->methods->read_query_result_nonblocking)(mysql); + if (status == NET_ASYNC_NOT_READY) + DBUG_RETURN(NET_ASYNC_NOT_READY); + else if (status == NET_ASYNC_ERROR) + goto end; + } + +end: + async_context->async_op_status = ASYNC_OP_UNSET; + async_context->async_query_state = QUERY_IDLE; + async_context->async_query_length = 0; + if (status == NET_ASYNC_ERROR) + DBUG_RETURN(NET_ASYNC_ERROR); + else + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + /************************************************************************** Alloc result struct for buffered results. All rows are read to buffer. mysql_data_seek may be used. @@ -5381,6 +7096,87 @@ MYSQL_RES *STDCALL mysql_store_result(MYSQL *mysql) { DBUG_RETURN(result); /* Data fetched */ } +/** + This API reads all result set sent by server in an asynchronous way + + @param[in] mysql connection handle + @param[in] result buffer which holds all result sets. + + @retval NET_ASYNC_NOT_READY reading of result sets not complete + @retval NET_ASYNC_COMPLETE completed this asynchronous operation +*/ +enum net_async_status STDCALL +mysql_store_result_nonblocking(MYSQL *mysql, MYSQL_RES **result) { + DBUG_ENTER(__func__); + MYSQL_ASYNC *async_context = ASYNC_DATA(mysql); + *result = nullptr; + + /* + Some queries (e.g. "CALL") may return an empty resultset. + mysql->field_count is 0 in such cases. + */ + if (!mysql->field_count) { + goto end; + } + if (!async_context->async_store_result_result) { + if (mysql->status != MYSQL_STATUS_GET_RESULT) { + set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); + goto end; + } + mysql->status = MYSQL_STATUS_READY; /* server is ready */ + + if (!(async_context->async_store_result_result = (MYSQL_RES *)my_malloc( + key_memory_MYSQL_RES, + (uint)(sizeof(MYSQL_RES) + sizeof(ulong) * mysql->field_count), + MYF(MY_WME | MY_ZEROFILL)))) { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + goto end; + } + if (!(async_context->async_store_result_result->field_alloc = + (MEM_ROOT *)my_malloc(key_memory_MYSQL, sizeof(MEM_ROOT), + MYF(MY_WME | MY_ZEROFILL)))) { + set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate); + my_free(async_context->async_store_result_result); + goto end; + } + async_context->async_store_result_result->methods = mysql->methods; + async_context->async_store_result_result->eof = 1; /* Marker for buffered */ + async_context->async_store_result_result->lengths = + (ulong *)(async_context->async_store_result_result + 1); + } + + if ((*mysql->methods->read_rows_nonblocking)( + mysql, mysql->fields, mysql->field_count, + &async_context->async_store_result_result->data) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (!async_context->async_store_result_result->data) { + my_free(async_context->async_store_result_result->field_alloc); + my_free(async_context->async_store_result_result); + goto end; + } + mysql->affected_rows = async_context->async_store_result_result->row_count = + async_context->async_store_result_result->data->rows; + async_context->async_store_result_result->data_cursor = + async_context->async_store_result_result->data->data; + async_context->async_store_result_result->fields = mysql->fields; + *async_context->async_store_result_result->field_alloc = + std::move(*mysql->field_alloc); + async_context->async_store_result_result->field_count = mysql->field_count; + async_context->async_store_result_result->metadata = + mysql->resultset_metadata; + /* The rest of result members is zerofilled in my_malloc */ + mysql->fields = 0; /* fields is now in result */ + /* just in case this was mistakenly called after mysql_stmt_execute() */ + mysql->unbuffered_fetch_owner = 0; + *result = async_context->async_store_result_result; +end: + async_context->async_store_result_result = nullptr; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + /************************************************************************** Alloc struct for use with unbuffered reads. Data is fetched by domand when calling to mysql_fetch_row. @@ -5482,6 +7278,73 @@ MYSQL_ROW STDCALL mysql_fetch_row(MYSQL_RES *res) { DBUG_RETURN(res->current_row = tmp); } } +/** + Reads next row of a result set in an asynchronous way. + + @param[in] res buffer in which all rows are stored + @param[out] row return pointer to one row from result set + + @retval NET_ASYNC_NOT_READY fetch operation not complete, retry again + @retval NET_ASYNC_COMPLETE fetch operation complete +*/ +net_async_status STDCALL mysql_fetch_row_nonblocking(MYSQL_RES *res, + MYSQL_ROW *row) { + DBUG_ENTER(__func__); + MYSQL *mysql = res->handle; + *row = nullptr; + + if (!res->data) { /* Unbufferred fetch */ + if (!res->eof) { + if (mysql->status == MYSQL_STATUS_USE_RESULT) { + int ret; + if (read_one_row_nonblocking(mysql, res->field_count, res->row, + res->lengths, + &ret) == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (!ret) { + *row = res->current_row = res->row; + goto end; + } + } + + set_mysql_error(mysql, + res->unbuffered_fetch_cancelled ? CR_FETCH_CANCELED + : CR_COMMANDS_OUT_OF_SYNC, + unknown_sqlstate); + DBUG_PRINT("info", ("end of data")); + res->eof = 1; + mysql->status = MYSQL_STATUS_READY; + /* + Reset only if owner points to us: there is a chance that + somebody started new query after mysql_stmt_close(): + */ + if (mysql->unbuffered_fetch_owner == &res->unbuffered_fetch_cancelled) + mysql->unbuffered_fetch_owner = 0; + /* Don't clear handle in mysql_free_result */ + res->handle = 0; + } + + *row = nullptr; + goto end; + } + { + MYSQL_ROW tmp; + if (!res->data_cursor) { + DBUG_PRINT("info", ("end of data")); + *row = nullptr; + goto end; + } + tmp = res->data_cursor->data; + res->data_cursor = res->data_cursor->next; + *row = res->current_row = tmp; + goto end; + } + +end: + DBUG_RETURN(NET_ASYNC_COMPLETE); +} /************************************************************************** Get column lengths of the current row @@ -6239,6 +8102,95 @@ static int native_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { DBUG_RETURN(CR_OK); } +/** + Client authentication plugin that does native MySQL authentication + in a nonblocking way. + + @param[in] vio the channel to operate on + @param[in] mysql the MYSQL structure to operate on + @param[out] result CR_OK : Success, CR_ERROR : error reading, + CR_SERVER_HANDSHAKE_ERR : malformed handshake data + + @retval NET_ASYNC_NOT_READY authentication not yet complete + @retval NET_ASYNC_COMPLETE authentication done +*/ +static net_async_status native_password_auth_client_nonblocking( + MYSQL_PLUGIN_VIO *vio, MYSQL *mysql, int *result) { + DBUG_ENTER(__func__); + int io_result; + uchar *pkt; + mysql_async_auth *ctx = ASYNC_DATA(mysql)->connect_context->auth_context; + + switch (static_cast( + ctx->client_auth_plugin_state)) { + case client_auth_native_password_plugin_status::NATIVE_READING_PASSWORD: + if (((MCPVIO_EXT *)vio)->mysql_change_user) { + /* mysql_change_user_nonblocking not implemented yet. */ + DBUG_ASSERT(false); + } else { + /* read the scramble */ + net_async_status status = + vio->read_packet_nonblocking(vio, &pkt, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (io_result < 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + if (io_result != SCRAMBLE_LENGTH + 1) { + *result = CR_SERVER_HANDSHAKE_ERR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + /* save it in MYSQL */ + memcpy(mysql->scramble, pkt, SCRAMBLE_LENGTH); + mysql->scramble[SCRAMBLE_LENGTH] = 0; + } + ctx->client_auth_plugin_state = (int) + client_auth_native_password_plugin_status::NATIVE_WRITING_RESPONSE; + + /* fallthrough */ + + case client_auth_native_password_plugin_status::NATIVE_WRITING_RESPONSE: + if (mysql->passwd[0]) { + char scrambled[SCRAMBLE_LENGTH + 1]; + DBUG_PRINT("info", ("sending scramble")); + scramble(scrambled, (char *)pkt, mysql->passwd); + net_async_status status = vio->write_packet_nonblocking( + vio, (uchar *)scrambled, SCRAMBLE_LENGTH, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (io_result < 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } else { + DBUG_PRINT("info", ("no password")); + net_async_status status = vio->write_packet_nonblocking( + vio, 0, 0, &io_result); /* no password */ + + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (io_result < 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } + break; + default: + assert(0); + } + + *result = CR_OK; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} /* clang-format off */ /** @page page_protocol_connection_phase_authentication_methods_clear_text_password Clear text client plugin diff --git a/sql-common/client_async_authentication.h b/sql-common/client_async_authentication.h new file mode 100644 index 000000000000..e5aaa3867a4c --- /dev/null +++ b/sql-common/client_async_authentication.h @@ -0,0 +1,171 @@ +/* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program 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.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "mysql/plugin_auth_common.h" +#include "mysql_async.h" + +#ifdef HAVE_OPENSSL +#include +#endif + +/* this is a "superset" of MYSQL_PLUGIN_VIO, in C++ I use inheritance */ +struct MCPVIO_EXT { + int (*read_packet)(MYSQL_PLUGIN_VIO *vio, uchar **buf); + int (*write_packet)(MYSQL_PLUGIN_VIO *vio, const uchar *pkt, int pkt_len); + void (*info)(MYSQL_PLUGIN_VIO *vio, MYSQL_PLUGIN_VIO_INFO *info); + net_async_status (*read_packet_nonblocking)(struct MYSQL_PLUGIN_VIO *vio, + unsigned char **buf, int *result); + net_async_status (*write_packet_nonblocking)(struct MYSQL_PLUGIN_VIO *vio, + const unsigned char *pkt, + int pkt_len, int *result); + + /* -= end of MYSQL_PLUGIN_VIO =- */ + MYSQL *mysql; + auth_plugin_t *plugin; /**< what plugin we're under */ + const char *db; + struct { + uchar *pkt; /**< pointer into NET::buff */ + uint pkt_len; + } cached_server_reply; + int packets_read, packets_written; /**< counters for send/received packets */ + int mysql_change_user; /**< if it's mysql_change_user() */ + int last_read_packet_len; /**< the length of the last *read* packet */ +}; + +/* Our state machines have four simple return codes: */ +enum mysql_state_machine_status { + STATE_MACHINE_FAILED, /* Completion with a failure. */ + STATE_MACHINE_CONTINUE, /* Keep calling the state machine. */ + STATE_MACHINE_WOULD_BLOCK, /* Needs to block to continue. */ + STATE_MACHINE_DONE /* Completion with a success. */ +}; + +/* state machine for native password autheintication API */ +enum client_auth_native_password_plugin_status { + NATIVE_READING_PASSWORD = 1, + NATIVE_WRITING_RESPONSE +}; + +enum client_auth_sha256_password_plugin_status { + SHA256_READING_PASSWORD = 1, + SHA256_REQUEST_PUBLIC_KEY, + SHA256_READ_PUBLIC_KEY, + SHA256_SEND_ENCRYPTED_PASSWORD, + SHA256_SEND_PLAIN_PASSWORD +}; + +enum client_auth_caching_sha2_password_plugin_status { + CACHING_SHA2_READING_PASSWORD = 1, + CACHING_SHA2_WRITING_RESPONSE, + CACHING_SHA2_CHALLENGE_RESPONSE, + CACHING_SHA2_REQUEST_PUBLIC_KEY, + CACHING_SHA2_READ_PUBLIC_KEY, + CACHING_SHA2_SEND_ENCRYPTED_PASSWORD, + CACHING_SHA2_SEND_PLAIN_PASSWORD +}; + +/* A state machine for authentication itself. */ +struct mysql_async_auth; +typedef mysql_state_machine_status (*authsm_function)(mysql_async_auth *); + +struct mysql_async_auth { + MYSQL *mysql; + bool non_blocking; + + char *data; + uint data_len; + const char *data_plugin; + const char *db; + + const char *auth_plugin_name; + auth_plugin_t *auth_plugin; + MCPVIO_EXT mpvio; + ulong pkt_length; + int res; + + char *change_user_buff; + int change_user_buff_len; + + int client_auth_plugin_state; + authsm_function state_function; +}; + +/* + Connection is handled with a state machine. Each state is + represented by a function pointer (csm_function) which returns + a mysql_state_machine_status to indicate the state of the + connection. + This state machine has boundaries around network IO to allow + reuse between blocking and non-blocking clients. +*/ +struct mysql_async_connect; +typedef mysql_state_machine_status (*csm_function)(mysql_async_connect *); + +/* + define different states of an asynchronous SSL connection phase +*/ +enum ssl_exchange_state { + SSL_REQUEST = 8100, + SSL_CONNECT = 8101, + SSL_COMPLETE = 8102, + SSL_NONE = 8103 +}; + +/* + Struct to track the state of a connection being established. Once + the connection is established, the context should be discarded and + relevant values copied out of it. +*/ +struct mysql_async_connect { + /* state for the overall connection process */ + MYSQL *mysql; + const char *host; + const char *user; + const char *passwd; + const char *db; + uint port; + const char *unix_socket; + ulong client_flag; + bool non_blocking; + + ulong pkt_length; + char *host_info; + char buff[NAME_LEN + USERNAME_LENGTH + 100]; + int scramble_data_len; + char *scramble_data; + const char *scramble_plugin; + char *scramble_buffer; + bool scramble_buffer_allocated; + + /* context needed to establish asynchronous authentication */ + struct mysql_async_auth *auth_context; + /* state for running init_commands */ + bool saved_reconnect; + char **current_init_command; + + ssl_exchange_state ssl_state; +#if defined(HAVE_OPENSSL) + SSL *ssl; +#endif + /* state function that will be called next */ + csm_function state_function; +}; diff --git a/sql-common/client_authentication.cc b/sql-common/client_authentication.cc index b04c5db81e48..4ad2e9dd7da7 100644 --- a/sql-common/client_authentication.cc +++ b/sql-common/client_authentication.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -52,6 +52,7 @@ #include #endif #include +#include "client_async_authentication.h" #include "mysql/plugin.h" #include "sha2.h" #include "violite.h" @@ -269,6 +270,164 @@ int sha256_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { DBUG_RETURN(CR_OK); } +/** + Non blocking version of sha256_password_auth_client +*/ +net_async_status sha256_password_auth_client_nonblocking(MYSQL_PLUGIN_VIO *vio, + MYSQL *mysql, + int *result) { + DBUG_ENTER(__func__); + net_async_status status = NET_ASYNC_NOT_READY; +#if !defined(HAVE_WOLFSSL) + unsigned char encrypted_password[MAX_CIPHER_LENGTH]; + static char request_public_key = '\1'; + static RSA *public_key = NULL; + bool got_public_key_from_server = false; +#endif + int io_result; + bool connection_is_secure = (mysql_get_ssl_cipher(mysql) != NULL); + unsigned char scramble_pkt[20]; + unsigned char *pkt; + unsigned int passwd_len = + static_cast(strlen(mysql->passwd) + 1); + + mysql_async_auth *ctx = ASYNC_DATA(mysql)->connect_context->auth_context; + switch (static_cast( + ctx->client_auth_plugin_state)) { + case client_auth_sha256_password_plugin_status::SHA256_READING_PASSWORD: + status = vio->read_packet_nonblocking(vio, &pkt, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result != SCRAMBLE_LENGTH + 1) { + DBUG_PRINT("info", ("Scramble is not of correct length.")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + if (pkt[SCRAMBLE_LENGTH] != '\0') { + DBUG_PRINT("info", ("Missing protocol token in scramble data.")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + memcpy(scramble_pkt, pkt, SCRAMBLE_LENGTH); + if (connection_is_secure) + ctx->client_auth_plugin_state = + client_auth_sha256_password_plugin_status:: + SHA256_SEND_PLAIN_PASSWORD; + else + ctx->client_auth_plugin_state = + client_auth_sha256_password_plugin_status:: + SHA256_REQUEST_PUBLIC_KEY; + DBUG_RETURN(NET_ASYNC_NOT_READY); + case client_auth_sha256_password_plugin_status::SHA256_REQUEST_PUBLIC_KEY: { + public_key = rsa_init(mysql); +#if !defined(HAVE_WOLFSSL) + /* If no public key; request one from the server. */ + if (public_key == NULL) { + status = vio->write_packet_nonblocking( + vio, (const unsigned char *)&request_public_key, 1, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } +#else + set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_ERR, unknown_sqlstate, + ER_CLIENT(CR_AUTH_PLUGIN_ERR), "sha256_password", + "Authentication requires SSL encryption"); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); +#endif + } + ctx->client_auth_plugin_state = + client_auth_sha256_password_plugin_status::SHA256_READ_PUBLIC_KEY; + /* FALLTHROUGH */ + case client_auth_sha256_password_plugin_status::SHA256_READ_PUBLIC_KEY: + if (public_key == NULL) { + status = vio->read_packet_nonblocking(vio, &pkt, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result <= 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + BIO *bio = BIO_new_mem_buf(pkt, io_result); + public_key = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL); + BIO_free(bio); + if (public_key == 0) { + ERR_clear_error(); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + got_public_key_from_server = true; + } + ctx->client_auth_plugin_state = + client_auth_sha256_password_plugin_status:: + SHA256_SEND_ENCRYPTED_PASSWORD; + /* FALLTHROUGH */ + case client_auth_sha256_password_plugin_status:: + SHA256_SEND_ENCRYPTED_PASSWORD: { + char passwd_scramble[512]; + + if (passwd_len > sizeof(passwd_scramble)) { + /* password too long for the buffer */ + if (got_public_key_from_server) RSA_free(public_key); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + memmove(passwd_scramble, mysql->passwd, passwd_len); + + /* Obfuscate the plain text password with the session scramble */ + xor_string(passwd_scramble, passwd_len - 1, (char *)scramble_pkt, + SCRAMBLE_LENGTH); + /* Encrypt the password and send it to the server */ + int cipher_length = RSA_size(public_key); + /* + When using RSA_PKCS1_OAEP_PADDING the password length must be less + than RSA_size(rsa) - 41. + */ + if (passwd_len + 41 >= (unsigned)cipher_length) { + /* password message is to long */ + if (got_public_key_from_server) RSA_free(public_key); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + RSA_public_encrypt(passwd_len, (unsigned char *)passwd_scramble, + encrypted_password, public_key, + RSA_PKCS1_OAEP_PADDING); + if (got_public_key_from_server) RSA_free(public_key); + status = vio->write_packet_nonblocking(vio, (uchar *)encrypted_password, + cipher_length, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result < 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } break; + case client_auth_sha256_password_plugin_status:: + SHA256_SEND_PLAIN_PASSWORD: { + status = vio->write_packet_nonblocking(vio, (uchar *)mysql->passwd, + passwd_len, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result < 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } break; + default: + assert(0); + } + *result = CR_OK; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} /* caching_sha2_password */ int caching_sha2_password_init(char *, size_t, int, va_list) { return 0; } @@ -462,6 +621,252 @@ int caching_sha2_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { DBUG_RETURN(CR_OK); } +/** + non blocking version of caching_sha2_password_auth_client +*/ +net_async_status caching_sha2_password_auth_client_nonblocking( + MYSQL_PLUGIN_VIO *vio, MYSQL *mysql, int *result) { + DBUG_ENTER(__func__); + int io_result; + net_async_status status = NET_ASYNC_NOT_READY; + static unsigned char encrypted_password[MAX_CIPHER_LENGTH]; + static RSA *public_key = NULL; + bool connection_is_secure = is_secure_transport(mysql); + bool got_public_key_from_server = false; + static unsigned char scramble_pkt[20]; + static int cipher_length = 0; + static unsigned int passwd_len = 0; + unsigned char *pkt; + mysql_async_auth *ctx = ASYNC_DATA(mysql)->connect_context->auth_context; + + switch (static_cast( + ctx->client_auth_plugin_state)) { + case client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_READING_PASSWORD: + /* + Get the scramble from the server because we need it when sending + encrypted password. + */ + status = vio->read_packet_nonblocking(vio, &pkt, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result != SCRAMBLE_LENGTH + 1) { + DBUG_PRINT("info", ("Scramble is not of correct length.")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + if (pkt[SCRAMBLE_LENGTH] != '\0') { + DBUG_PRINT("info", ("Missing protocol token in scramble data.")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + /* + Copy the scramble to the stack or it will be lost on the next use + of the net buffer. + */ + memcpy(scramble_pkt, pkt, SCRAMBLE_LENGTH); + ctx->client_auth_plugin_state = + client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_WRITING_RESPONSE; + /* FALLTHROUGH */ + case client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_WRITING_RESPONSE: + if (mysql->passwd[0] == 0) { + /* We're not using a password */ + static const unsigned char zero_byte = '\0'; + status = vio->write_packet_nonblocking(vio, &zero_byte, 1, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + *result = CR_OK; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } else { + /* Password is a 0-terminated byte array ('\0' character included) */ + passwd_len = static_cast(strlen(mysql->passwd) + 1); + /* First try with SHA2 scramble */ + unsigned char sha2_scramble[SHA2_SCRAMBLE_LENGTH]; + if (generate_sha256_scramble(sha2_scramble, SHA2_SCRAMBLE_LENGTH, + mysql->passwd, passwd_len - 1, + (char *)scramble_pkt, SCRAMBLE_LENGTH)) { + set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_ERR, unknown_sqlstate, + ER_CLIENT(CR_AUTH_PLUGIN_ERR), + "caching_sha2_password", + "Failed to generate scramble"); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + status = vio->write_packet_nonblocking( + vio, sha2_scramble, SHA2_SCRAMBLE_LENGTH, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } + ctx->client_auth_plugin_state = + client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_CHALLENGE_RESPONSE; + /* FALLTHROUGH */ + case client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_CHALLENGE_RESPONSE: + status = vio->read_packet_nonblocking(vio, &pkt, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result == -1) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + if (io_result == 1 && *pkt == fast_auth_success) { + *result = CR_OK; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + if (io_result != 1 || *pkt != perform_full_authentication) { + DBUG_PRINT("info", ("Unexpected reply from server.")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + if (!connection_is_secure) + ctx->client_auth_plugin_state = + client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_REQUEST_PUBLIC_KEY; + else + ctx->client_auth_plugin_state = + client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_SEND_PLAIN_PASSWORD; + DBUG_RETURN(NET_ASYNC_NOT_READY); + /* FALLTHROUGH */ + case client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_REQUEST_PUBLIC_KEY: + /* If connection isn't secure attempt to get the RSA public key file */ + { + public_key = rsa_init(mysql); + + if (public_key == NULL && mysql->options.extension && + mysql->options.extension->get_server_public_key) { + status = vio->write_packet_nonblocking( + vio, (const unsigned char *)&request_public_key, 1, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } + } + ctx->client_auth_plugin_state = + client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_READ_PUBLIC_KEY; + /* FALLTHROUGH */ + case client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_READ_PUBLIC_KEY: { + if (public_key == NULL && mysql->options.extension && + mysql->options.extension->get_server_public_key) { + status = vio->read_packet_nonblocking(vio, &pkt, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result <= 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + int pkt_len = 0; + BIO *bio = BIO_new_mem_buf(pkt, pkt_len); + public_key = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL); + BIO_free(bio); + if (public_key == 0) { +#ifndef HAVE_WOLFSSL + ERR_clear_error(); +#endif /* !HAVE_WOLFSSL */ + DBUG_PRINT("info", ("Failed to parse public key")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + got_public_key_from_server = true; + } + if (public_key) { + char passwd_scramble[512]; + if (passwd_len > sizeof(passwd_scramble)) { + /* password too long for the buffer */ + if (got_public_key_from_server) RSA_free(public_key); + DBUG_PRINT("info", ("Password is too long.")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + memmove(passwd_scramble, mysql->passwd, passwd_len); + /* Obfuscate the plain text password with the session scramble */ + xor_string(passwd_scramble, passwd_len - 1, (char *)scramble_pkt, + SCRAMBLE_LENGTH); + /* Encrypt the password and send it to the server */ + cipher_length = RSA_size(public_key); + /* + When using RSA_PKCS1_OAEP_PADDING the password length + must be less than RSA_size(rsa) - 41. + */ + if (passwd_len + 41 >= (unsigned)cipher_length) { + /* password message is to long */ + if (got_public_key_from_server) RSA_free(public_key); + DBUG_PRINT("info", ("Password is too long to be encrypted using " + "given public key.")); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + RSA_public_encrypt(passwd_len, (unsigned char *)passwd_scramble, + encrypted_password, public_key, + RSA_PKCS1_OAEP_PADDING); + if (got_public_key_from_server) RSA_free(public_key); + } else { + set_mysql_extended_error(mysql, CR_AUTH_PLUGIN_ERR, unknown_sqlstate, + ER_CLIENT(CR_AUTH_PLUGIN_ERR), + "caching_sha2_password", + "Authentication requires secure connection."); + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } + ctx->client_auth_plugin_state = + client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_SEND_ENCRYPTED_PASSWORD; + /* FALLTHROUGH */ + case client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_SEND_ENCRYPTED_PASSWORD: { + status = vio->write_packet_nonblocking(vio, (uchar *)encrypted_password, + cipher_length, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result < 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } break; + case client_auth_caching_sha2_password_plugin_status:: + CACHING_SHA2_SEND_PLAIN_PASSWORD: { + status = vio->write_packet_nonblocking(vio, (uchar *)mysql->passwd, + passwd_len, &io_result); + if (status == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (io_result < 0) { + *result = CR_ERROR; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + } break; + default: + assert(0); + } + *result = CR_OK; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + void STDCALL mysql_reset_server_public_key(void) { DBUG_ENTER("mysql_reset_server_public_key"); mysql_mutex_lock(&g_public_key_mutex); diff --git a/sql-common/net_serv.cc b/sql-common/net_serv.cc index 2ae45667aa51..547faa25a949 100644 --- a/sql-common/net_serv.cc +++ b/sql-common/net_serv.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -50,6 +50,7 @@ #include "my_sys.h" #include "mysql.h" #include "mysql/service_mysql_alloc.h" +#include "mysql_async.h" #include "mysql_com.h" #include "mysqld_error.h" #include "violite.h" @@ -87,10 +88,31 @@ extern void thd_increment_bytes_received(size_t length); #include "mysql_com_server.h" #endif -#define VIO_SOCKET_ERROR ((size_t)-1) - static bool net_write_buff(NET *, const uchar *, size_t); +NET_EXTENSION *net_extension_init() { + NET_EXTENSION *ext = static_cast(my_malloc( + PSI_NOT_INSTRUMENTED, sizeof(NET_EXTENSION), MYF(MY_WME | MY_ZEROFILL))); + ext->net_async_context = static_cast(my_malloc( + PSI_NOT_INSTRUMENTED, sizeof(NET_ASYNC), MYF(MY_WME | MY_ZEROFILL))); + + return ext; +} + +void net_extension_free(NET *net) { + NET_EXTENSION *ext = NET_EXTENSION_PTR(net); + if (ext) { +#ifndef MYSQL_SERVER + if (ext->net_async_context) { + my_free(ext->net_async_context); + ext->net_async_context = nullptr; + } + my_free(ext); + net->extension = 0; +#endif + } +} + /** Init with packet info. */ bool my_net_init(NET *net, Vio *vio) { @@ -114,8 +136,18 @@ bool my_net_init(NET *net, Vio *vio) { net->last_errno = 0; #ifdef MYSQL_SERVER net->extension = NULL; +#else + NET_EXTENSION *ext = net_extension_init(); + ext->net_async_context->cur_pos = net->buff + net->where_b; + ext->net_async_context->read_rows_is_first_read = true; + ext->net_async_context->async_operation = NET_ASYNC_OP_IDLE; + ext->net_async_context->async_send_command_status = + NET_ASYNC_SEND_COMMAND_IDLE; + ext->net_async_context->async_read_query_result_status = + NET_ASYNC_READ_QUERY_RESULT_IDLE; + ext->net_async_context->async_packet_read_state = NET_ASYNC_PACKET_READ_IDLE; + net->extension = ext; #endif - if (vio) { /* For perl DBI/DBD. */ net->fd = vio_fd(vio); @@ -168,7 +200,13 @@ bool net_realloc(NET *net, size_t length) { /* In the server the error is reported by MY_WME flag. */ DBUG_RETURN(1); } +#ifdef MYSQL_SERVER + net->buff = net->write_pos = buff; +#else + size_t cur_pos_offset = NET_ASYNC_DATA(net)->cur_pos - net->buff; net->buff = net->write_pos = buff; + NET_ASYNC_DATA(net)->cur_pos = net->buff + cur_pos_offset; +#endif net->buff_end = buff + (net->max_packet = (ulong)pkt_length); DBUG_RETURN(0); } @@ -353,6 +391,8 @@ static bool net_should_retry(NET *net, bool my_net_write(NET *net, const uchar *packet, size_t len) { uchar buff[NET_HEADER_SIZE]; + DBUG_DUMP("net write", packet, len); + if (unlikely(!net->vio)) /* nowhere to write */ return false; @@ -361,6 +401,8 @@ bool my_net_write(NET *net, const uchar *packet, size_t len) { return 1; };); + /* turn off non blocking operations */ + if (!vio_is_blocking(net->vio)) vio_set_blocking_flag(net->vio, true); /* Big packets are handled by splitting them in packets of MAX_PACKET_LENGTH length. The last packet is always a packet that is < MAX_PACKET_LENGTH. @@ -389,6 +431,287 @@ bool my_net_write(NET *net, const uchar *packet, size_t len) { return net_write_buff(net, packet, len); } +static void reset_packet_write_state(NET *net) { + DBUG_ENTER(__func__); + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + if (net_async->async_write_vector) { + if (net_async->async_write_vector != net_async->inline_async_write_vector) { + my_free(net_async->async_write_vector); + } + net_async->async_write_vector = nullptr; + } + + if (net_async->async_write_headers) { + if (net_async->async_write_headers != + net_async->inline_async_write_header) { + my_free(net_async->async_write_headers); + } + net_async->async_write_headers = nullptr; + } + + net_async->async_write_vector_size = 0; + net_async->async_write_vector_current = 0; + DBUG_VOID_RETURN; +} + +/* + Construct the proper buffers for our nonblocking write. What we do + here is we make an iovector for the entire write (header, command, + and payload). We then continually call writev on this vector, + consuming parts from it as bytes are successfully written. Headers + for the message are all stored inside one buffer, separate from the + payload; this lets us avoid copying the entire query just to insert + the headers every 2**24 bytes. + + The most common case is the query fits in a packet. In that case, + we don't construct the iovector dynamically, instead using one we + pre-allocated inside the net structure. This avoids allocations in + the common path, but requires special casing with our iovec and + header buffer. +*/ +static int begin_packet_write_state(NET *net, uchar command, + const uchar *packet, size_t packet_len, + const uchar *optional_prefix, + size_t prefix_len) { + DBUG_ENTER(__func__); + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + size_t total_len = packet_len + prefix_len; + bool include_command = (command < COM_END); + if (include_command) { + ++total_len; + } + size_t packet_count = 1 + total_len / MAX_PACKET_LENGTH; + reset_packet_write_state(net); + + struct io_vec *vec; + uchar *headers; + if (total_len < MAX_PACKET_LENGTH) { + /* + Most writes hit this case, ie, less than MAX_PACKET_LENGTH of + query text. + */ + vec = net_async->inline_async_write_vector; + headers = net_async->inline_async_write_header; + } else { + /* Large query, create the vector and header buffer dynamically. */ + vec = (struct io_vec *)my_malloc( + PSI_NOT_INSTRUMENTED, sizeof(struct io_vec) * packet_count * 2 + 1, + MYF(MY_ZEROFILL)); + if (!vec) { + DBUG_RETURN(0); + } + + headers = (uchar *)my_malloc(PSI_NOT_INSTRUMENTED, + packet_count * (NET_HEADER_SIZE + 1), + MYF(MY_ZEROFILL)); + if (!headers) { + my_free(vec); + DBUG_RETURN(0); + } + } + /* + Regardless of where vec and headers come from, these are what we + feed to writev and populate below. + */ + net_async->async_write_vector = vec; + net_async->async_write_headers = headers; + + /* + We sneak the command into the first header, so the special casing + below about packet_num == 0 relates to that. This lets us avoid + an extra allocation and copying the input buffers again. + + Every chunk of MAX_PACKET_LENGTH results in a header and a + payload, so we have twice as many entries in the IO + vector as we have packet_count. The first packet may be prefixed with a + small amount of data, so that one actually might + consume *three* iovec entries. + */ + for (size_t packet_num = 0; packet_num < packet_count; ++packet_num) { + /* First packet, our header. */ + uchar *buf = headers + packet_num * NET_HEADER_SIZE; + if (packet_num > 0) { + /* + First packet stole one extra byte from the header buffer for + the command number, so account for it here. + */ + ++buf; + } + size_t header_len = NET_HEADER_SIZE; + size_t bytes_queued = 0; + + size_t packet_size = min(MAX_PACKET_LENGTH, total_len); + int3store(buf, packet_size); + buf[3] = (uchar)net->pkt_nr++; + /* + We sneak the command byte into the header, even though + technically it is payload. This lets us avoid an allocation + or separate one-byte entry in our iovec. + */ + if (packet_num == 0 && include_command) { + buf[4] = command; + ++header_len; + /* Our command byte counts against the packet size. */ + ++bytes_queued; + } + + (*vec).iov_base = buf; + (*vec).iov_len = header_len; + ++vec; + + /* Second packet, our optional prefix (if any). */ + if (packet_num == 0 && optional_prefix != NULL) { + (*vec).iov_base = (void *)optional_prefix; + (*vec).iov_len = prefix_len; + ++vec; + bytes_queued += prefix_len; + } + /* + Final packet, the payload itself. Send however many bytes from + packet we have left, and advance our packet pointer. + */ + size_t remaining_bytes = packet_size - bytes_queued; + (*vec).iov_base = (void *)packet; + (*vec).iov_len = remaining_bytes; + + bytes_queued += remaining_bytes; + + packet += remaining_bytes; + total_len -= bytes_queued; + + ++vec; + + /* Make sure we sent entire packets. */ + if (total_len > 0) { + DBUG_ASSERT(packet_size == MAX_PACKET_LENGTH); + } + } + + /* Make sure we don't have anything left to send. */ + DBUG_ASSERT(total_len == 0); + + net_async->async_write_vector_size = (vec - net_async->async_write_vector); + net_async->async_write_vector_current = 0; + + DBUG_RETURN(1); +} + +static net_async_status net_write_vector_nonblocking(NET *net, ssize_t *res) { + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + struct io_vec *vec = + net_async->async_write_vector + net_async->async_write_vector_current; + DBUG_ENTER(__func__); + + while (net_async->async_write_vector_current != + net_async->async_write_vector_size) { + if (vio_is_blocking(net->vio)) { + vio_set_blocking_flag(net->vio, false); + } + *res = vio_write(net->vio, (uchar *)vec->iov_base, vec->iov_len); + + if (*res < 0) { + if (errno == SOCKET_EAGAIN || (SOCKET_EAGAIN != SOCKET_EWOULDBLOCK && + 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->async_blocking_state = NET_NONBLOCKING_READ; + } else { + net_async->async_blocking_state = NET_NONBLOCKING_WRITE; + } + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + 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->async_write_vector_current; + vec++; + } + if (net_async->async_write_vector_current == + net_async->async_write_vector_size) { + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + net_async->async_blocking_state = NET_NONBLOCKING_WRITE; + DBUG_RETURN(NET_ASYNC_NOT_READY); +} + +/** + Send a command to the server in asynchronous way. This function will first + populate all headers in NET::async_write_headers, followed by payload in + NET::async_write_vector. Once header and payload is populated in NET, were + call net_write_vector_nonblocking to send the packets to server in an + asynchronous way. +*/ +net_async_status net_write_command_nonblocking(NET *net, uchar command, + const uchar *prefix, + size_t prefix_len, + const uchar *packet, + size_t packet_len, bool *res) { + net_async_status status; + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + ssize_t rc; + DBUG_ENTER(__func__); + DBUG_DUMP("net write prefix", prefix, prefix_len); + DBUG_DUMP("net write pkt", packet, packet_len); + if (unlikely(!net->vio)) { + /* nowhere to write */ + *res = false; + goto done; + } + + switch (net_async->async_operation) { + case NET_ASYNC_OP_IDLE: + if (!begin_packet_write_state(net, command, packet, packet_len, prefix, + prefix_len)) { + *res = false; + goto done; + } + net_async->async_operation = NET_ASYNC_OP_WRITING; + /* fallthrough */ + case NET_ASYNC_OP_WRITING: + status = net_write_vector_nonblocking(net, &rc); + if (status == NET_ASYNC_COMPLETE) { + if (rc < 0) { + *res = true; + } else { + *res = false; + } + goto done; + } + DBUG_RETURN(NET_ASYNC_NOT_READY); + net_async->async_operation = NET_ASYNC_OP_COMPLETE; + /* fallthrough */ + case NET_ASYNC_OP_COMPLETE: + *res = false; + goto done; + default: + DBUG_ASSERT(false); + *res = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + +done: + reset_packet_write_state(net); + net_async->async_operation = NET_ASYNC_OP_IDLE; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + +/* + Non blocking version of my_net_write(). +*/ +net_async_status my_net_write_nonblocking(NET *net, const uchar *packet, + size_t len, bool *res) { + return net_write_command_nonblocking(net, COM_END, packet, len, NULL, 0, res); +} + /** Send a command to the server. @@ -418,6 +741,9 @@ bool my_net_write(NET *net, const uchar *packet, size_t len) { bool net_write_command(NET *net, uchar command, const uchar *header, size_t head_len, const uchar *packet, size_t len) { + /* turn off non blocking operations */ + if (!vio_is_blocking(net->vio)) vio_set_blocking_flag(net->vio, true); + size_t length = len + 1 + head_len; /* 1 extra byte for command */ uchar buff[NET_HEADER_SIZE + 1]; uint header_size = NET_HEADER_SIZE + 1; @@ -987,6 +1313,236 @@ static bool net_read_packet_header(NET *net) { return false; } +/* + Helper function to read up to count bytes from the network connection/ + + 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) { + size_t recvcnt; + DBUG_ENTER(__func__); + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + if (net_async->cur_pos + count > net->buff + net->max_packet) { + if (net_realloc(net, net->max_packet + count)) { + DBUG_RETURN(packet_error); + } + } + if (vio_is_blocking(net->vio)) { + vio_set_blocking_flag(net->vio, false); + } + recvcnt = vio_read(net->vio, net_async->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 (recvcnt == VIO_SOCKET_WANT_READ) { + net_async->async_blocking_state = NET_NONBLOCKING_READ; + DBUG_RETURN(0); + } else if (recvcnt == VIO_SOCKET_WANT_WRITE) { + net_async->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_EAGAIN != SOCKET_EWOULDBLOCK && + socket_errno == SOCKET_EWOULDBLOCK))) { + net_async->async_blocking_state = NET_NONBLOCKING_READ; + DBUG_RETURN(0); + } + + /* Not EOF and not an error? Return the bytes read.*/ + if (recvcnt != 0 && recvcnt != VIO_SOCKET_ERROR) { + net_async->cur_pos += recvcnt; +#ifdef MYSQL_SERVER + thd_increment_bytes_received(recvcnt); +#endif + DBUG_RETURN(recvcnt); + } + + /* EOF or hard failure; socket should be closed. */ + net->error = 2; + net->last_errno = ER_NET_READ_ERROR; + DBUG_RETURN(packet_error); +} + +/* Read actual data from the packet */ +static net_async_status net_read_data_nonblocking(NET *net, size_t count, + bool *err_ptr) { + DBUG_ENTER(__func__); + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + size_t bytes_read = 0; + ulong rc; + switch (net_async->async_operation) { + case NET_ASYNC_OP_IDLE: + net_async->async_bytes_wanted = count; + net_async->async_operation = NET_ASYNC_OP_READING; + net_async->cur_pos = net->buff + net->where_b; + /* fallthrough */ + case NET_ASYNC_OP_READING: + rc = net_read_available(net, net_async->async_bytes_wanted); + if (rc == packet_error) { + *err_ptr = rc; + net_async->async_operation = NET_ASYNC_OP_IDLE; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + bytes_read = (size_t)rc; + net_async->async_bytes_wanted -= bytes_read; + if (net_async->async_bytes_wanted != 0) { + DBUG_PRINT("partial read", ("wanted/remaining: %zu, %zu", count, + net_async->async_bytes_wanted)); + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + net_async->async_operation = NET_ASYNC_OP_COMPLETE; + /* fallthrough */ + case NET_ASYNC_OP_COMPLETE: + net_async->async_bytes_wanted = 0; + net_async->async_operation = NET_ASYNC_OP_IDLE; + *err_ptr = false; + DBUG_PRINT("read complete", ("read: %zu", count)); + DBUG_RETURN(NET_ASYNC_COMPLETE); + default: + /* error, sure wish we could log something here */ + DBUG_ASSERT(false); + net_async->async_bytes_wanted = 0; + net_async->async_operation = NET_ASYNC_OP_IDLE; + *err_ptr = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } +} + +static net_async_status net_read_packet_header_nonblocking(NET *net, + bool *err_ptr) { + DBUG_ENTER(__func__); + uchar pkt_nr; + if (net_read_data_nonblocking(net, NET_HEADER_SIZE, err_ptr) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + if (*err_ptr) { + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + DBUG_DUMP("packet_header", net->buff + net->where_b, NET_HEADER_SIZE); + + pkt_nr = net->buff[net->where_b + 3]; + + /* + Verify packet serial number against the truncated packet counter. + The local packet counter must be truncated since its not reset. + */ + if (pkt_nr != (uchar)net->pkt_nr) { + /* Not a NET error on the client. XXX: why? */ +#if defined(MYSQL_SERVER) + my_error(ER_NET_PACKETS_OUT_OF_ORDER, MYF(0)); +#elif defined(EXTRA_DEBUG) + /* + We don't make noise server side, since the client is expected + to break the protocol for e.g. --send LOAD DATA .. LOCAL where + the server expects the client to send a file, but the client + may reply with a new command instead. + */ + fprintf(stderr, "Error: packets out of order (found %u, expected %u)\n", + (uint)pkt_nr, net->pkt_nr); + DBUG_ASSERT(pkt_nr == net->pkt_nr); +#endif + *err_ptr = true; + DBUG_RETURN(NET_ASYNC_COMPLETE); + } + + net->pkt_nr++; + + *err_ptr = false; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + +/* + Read packet header followed by packet data in an asynchronous way. +*/ +static net_async_status net_read_packet_nonblocking(NET *net, ulong *ret, + ulong *complen) { + DBUG_ENTER(__func__); + NET_ASYNC *net_async = NET_ASYNC_DATA(net); + size_t pkt_data_len; + bool err; + + *complen = 0; + + switch (net_async->async_packet_read_state) { + case NET_ASYNC_PACKET_READ_IDLE: + net_async->async_packet_read_state = NET_ASYNC_PACKET_READ_HEADER; + net->reading_or_writing = 0; + /* fallthrough */ + case NET_ASYNC_PACKET_READ_HEADER: + if (net_read_packet_header_nonblocking(net, &err) == + NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + /* Retrieve packet length and number. */ + if (err) goto error; + + net->compress_pkt_nr = net->pkt_nr; + + /* The length of the packet that follows. */ + net_async->async_packet_length = uint3korr(net->buff + net->where_b); + DBUG_PRINT("info", + ("async packet len: %zu", net_async->async_packet_length)); + + /* End of big multi-packet. */ + if (!net_async->async_packet_length) goto end; + + pkt_data_len = + max(static_cast(net_async->async_packet_length), *complen) + + net->where_b; + + /* Expand packet buffer if necessary. */ + if ((pkt_data_len >= net->max_packet) && net_realloc(net, pkt_data_len)) + goto error; + + net_async->async_packet_read_state = NET_ASYNC_PACKET_READ_BODY; + /* fallthrough */ + case NET_ASYNC_PACKET_READ_BODY: + if (net_read_data_nonblocking(net, net_async->async_packet_length, + &err) == NET_ASYNC_NOT_READY) { + DBUG_RETURN(NET_ASYNC_NOT_READY); + } + + if (err) goto error; + + net_async->async_packet_read_state = NET_ASYNC_PACKET_READ_COMPLETE; + /* fallthrough */ + + case NET_ASYNC_PACKET_READ_COMPLETE: + net_async->async_packet_read_state = NET_ASYNC_PACKET_READ_IDLE; + break; + } + +end: + *ret = net_async->async_packet_length; + net->read_pos = net->buff + net->where_b; +#ifdef DEBUG_DATA_PACKETS + DBUG_DUMP("async read output", net->read_pos, *ret); +#endif + + net->read_pos[*ret] = 0; + net->reading_or_writing = 0; + DBUG_RETURN(NET_ASYNC_COMPLETE); + +error: + *ret = packet_error; + net->reading_or_writing = 0; + DBUG_RETURN(NET_ASYNC_COMPLETE); +} + /** Read one (variable-length) MySQL protocol packet. A MySQL packet consists of a header and a payload. @@ -1041,6 +1597,7 @@ static size_t net_read_packet(NET *net, size_t *complen) { if (net_read_raw_loop(net, pkt_len)) goto error; end: + DBUG_DUMP("net read", net->buff + net->where_b, pkt_len); net->reading_or_writing = 0; return pkt_len; @@ -1049,6 +1606,29 @@ static size_t net_read_packet(NET *net, size_t *complen) { return packet_error; } +/* + Non blocking version of my_net_read(). +*/ +net_async_status my_net_read_nonblocking(NET *net, ulong *len_ptr, + ulong *complen_ptr) { + if (net_read_packet_nonblocking(net, len_ptr, complen_ptr) == + NET_ASYNC_NOT_READY) { + return NET_ASYNC_NOT_READY; + } + + if (*len_ptr == packet_error) { + return NET_ASYNC_COMPLETE; + } + + DBUG_PRINT("info", ("chunk nb read: %lu", *len_ptr)); + + if (*len_ptr == MAX_PACKET_LENGTH) { + return NET_ASYNC_NOT_READY; + } else { + return NET_ASYNC_COMPLETE; + } +} + /** Read a packet from the client/server and return it without the internal package header. @@ -1068,6 +1648,9 @@ static size_t net_read_packet(NET *net, size_t *complen) { ulong my_net_read(NET *net) { size_t len, complen; + /* turn off non blocking operations */ + if (!vio_is_blocking(net->vio)) vio_set_blocking_flag(net->vio, true); + if (!net->compress) { len = net_read_packet(net, &complen); if (len == MAX_PACKET_LENGTH) { diff --git a/testclients/mysql_client_test.cc b/testclients/mysql_client_test.cc index 46b272c8ffaa..3a04b2146e2d 100644 --- a/testclients/mysql_client_test.cc +++ b/testclients/mysql_client_test.cc @@ -20144,6 +20144,290 @@ static void test_bug27443252() { myquery(rc); } +void perform_arithmatic() { fprintf(stdout, "\n Do some other stuff."); } + +static void test_wl11381() { + MYSQL *mysql_local; + MYSQL_RES *result; + MYSQL_ROW row; + net_async_status status; + const char *stmt_text, *stmt_text1, *insert_stmt, *insert_stmt1; + + myheader("test_wl11381"); + /*make new non blocking connection to do asynchronous operations */ + if (!(mysql_local = mysql_client_init(NULL))) { + myerror("mysql_client_init() failed"); + exit(1); + } + status = mysql_real_connect_nonblocking( + mysql_local, opt_host, opt_user, opt_password, current_db, opt_port, + opt_unix_socket, CLIENT_MULTI_STATEMENTS); + if (status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n mysql_real_connect_nonblocking() failed"); + exit(1); + } else { + fprintf(stdout, "\n asynchronous connection estalished"); + } + + mysql_autocommit(mysql_local, 1); + + if (mysql_query(mysql_local, "DROP TABLE IF EXISTS test_table")) { + fprintf(stderr, "\n drop table failed with error %s ", + mysql_error(mysql_local)); + exit(1); + } + + if (mysql_query(mysql_local, "CREATE TABLE test_table(col1 int)")) { + fprintf(stderr, "\n create table failed with error %s ", + mysql_error(mysql_local)); + exit(1); + } + + if (mysql_query(mysql_local, + "INSERT INTO test_table values(10), (20), (30)")) { + fprintf(stderr, "\n insert into table failed with error %s ", + mysql_error(mysql_local)); + exit(1); + } + + stmt_text = "SELECT * FROM test_table"; + /* run query in asynchronous way */ + status = mysql_real_query_nonblocking(mysql_local, stmt_text, + (ulong)strlen(stmt_text)); + /* do some other task */ + perform_arithmatic(); + while (status == NET_ASYNC_NOT_READY) { + status = mysql_real_query_nonblocking(mysql_local, stmt_text, + (ulong)strlen(stmt_text)); + } + if (status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n mysql_real_query_nonblocking() failed"); + exit(1); + } else { + fprintf(stdout, "\n mysql_real_query_nonblocking() passed"); + } + status = mysql_store_result_nonblocking(mysql_local, &result); + /* do some other task */ + perform_arithmatic(); + while (status == NET_ASYNC_NOT_READY) { + status = mysql_store_result_nonblocking(mysql_local, &result); + } + if (!result) { + fprintf(stdout, "\n mysql_store_result_nonblocking() fetched 0 records"); + exit(1); + } else { + fprintf(stdout, "\n mysql_store_result_nonblocking() passed"); + } + + row = mysql_fetch_row(result); + DIE_UNLESS(strcmp(row[0], "10") == 0); + fprintf(stdout, "\n mysql_fetch_row() passed"); + + while ((status = mysql_fetch_row_nonblocking(result, &row)) != + NET_ASYNC_COMPLETE) + ; + /* 2nd row fetched */ + DIE_UNLESS(strcmp(row[0], "20") == 0); + fprintf(stdout, "\n mysql_fetch_row_nonblocking() passed"); + + status = mysql_fetch_row_nonblocking(result, &row); + /* do some other task */ + perform_arithmatic(); + if (status == NET_ASYNC_COMPLETE) { + DIE_UNLESS(strcmp(row[0], "30") == 0); + } else { + while ((status = mysql_fetch_row_nonblocking(result, &row)) != + NET_ASYNC_COMPLETE) + ; + /* 3rd row fetched */ + DIE_UNLESS(strcmp(row[0], "30") == 0); + fprintf(stdout, "\n mysql_fetch_row_nonblocking() passed"); + } + + while ((status = mysql_free_result_nonblocking(result)) != NET_ASYNC_COMPLETE) + ; + fprintf(stdout, "\n mysql_free_result_nonblocking() passed"); + + if (mysql_query(mysql_local, "DROP TABLE IF EXISTS test_table1")) { + fprintf(stderr, "\n drop table failed with error %s ", + mysql_error(mysql_local)); + exit(1); + } + + if (mysql_query(mysql_local, + "CREATE TABLE test_table1(col1 int, col2 varchar(2048))")) { + fprintf(stderr, "\n create table failed with error %s ", + mysql_error(mysql_local)); + exit(1); + } + + insert_stmt = + "INSERT INTO test_table1 values(10, repeat('a', 2000)), (2, repeat('bc', " + "1000)), (3, repeat('def', 600))"; + status = NET_ASYNC_NOT_READY; + while (status == NET_ASYNC_NOT_READY) { + status = mysql_real_query_nonblocking(mysql_local, insert_stmt, + (ulong)strlen(insert_stmt)); + } + if (status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n INSERT INTO test_table1... failed"); + exit(1); + } + + insert_stmt1 = + "INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;\ + INSERT INTO test_table1 SELECT * FROM test_table1;"; + status = mysql_real_query_nonblocking(mysql_local, insert_stmt1, + (ulong)strlen(insert_stmt1)); + if (status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n mysql_real_query_nonblocking() failed"); + exit(1); + } + /* do some other task */ + perform_arithmatic(); + while (status == NET_ASYNC_NOT_READY) { + status = mysql_real_query_nonblocking(mysql_local, insert_stmt1, + (ulong)strlen(insert_stmt1)); + } + if (status == NET_ASYNC_ERROR) { + fprintf(stderr, + "\n INSERT INTO test_table1 " + "SELECT * FROM test_table1 with error; %s ", + mysql_error(mysql_local)); + exit(1); + } + + while (mysql_more_results(mysql_local)) { + status = mysql_next_result_nonblocking(mysql_local); + if (status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n mysql_next_result_nonblocking() failed"); + exit(1); + } else { + while (status == NET_ASYNC_NOT_READY) { + status = mysql_store_result_nonblocking(mysql_local, &result); + } + } + } + stmt_text1 = "SELECT * FROM test_table1"; + /* run query in asynchronous way */ + status = mysql_real_query_nonblocking(mysql_local, stmt_text1, + (ulong)strlen(stmt_text1)); + /* do some other task */ + perform_arithmatic(); + while (status == NET_ASYNC_NOT_READY) { + status = mysql_real_query_nonblocking(mysql_local, stmt_text1, + (ulong)strlen(stmt_text1)); + } + if (status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n SELECT * FROM test_table1 failed"); + exit(1); + } + result = mysql_use_result(mysql_local); + while ((status = mysql_free_result_nonblocking(result)) != NET_ASYNC_COMPLETE) + ; + mysql_close(mysql_local); +} + +static void test_wl11381_qa() { + MYSQL *mysql_con1; + MYSQL *mysql_con2; + net_async_status mysql_con1_status; + net_async_status mysql_con2_status; + const char *stmt_text; + int counter = 0; + + myheader("test_wl11381_qa"); + /*make new non blocking connection to do asynchronous operations */ + if (!(mysql_con1 = mysql_client_init(NULL))) { + myerror("mysql_client_init() failed"); + exit(1); + } + /*make new non blocking connection to do asynchronous operations */ + if (!(mysql_con2 = mysql_client_init(NULL))) { + myerror("mysql_client_init() failed"); + exit(1); + } + + mysql_con1_status = (mysql_real_connect_nonblocking( + mysql_con1, opt_host, opt_user, opt_password, current_db, opt_port, + opt_unix_socket, CLIENT_MULTI_STATEMENTS)); + + mysql_con2_status = (mysql_real_connect_nonblocking( + mysql_con2, opt_host, opt_user, opt_password, current_db, opt_port, + opt_unix_socket, CLIENT_MULTI_STATEMENTS)); + + if (mysql_con1_status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n mysql_real_connect_nonblocking() failed"); + exit(1); + } else if (mysql_con1_status == NET_ASYNC_COMPLETE) { + fprintf(stdout, "\n asynchronous connection 1 estalished"); + } + + if (mysql_con2_status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n mysql_real_connect_nonblocking() failed"); + exit(1); + } else if (mysql_con2_status == NET_ASYNC_COMPLETE) { + fprintf(stdout, "\n asynchronous connection 2 estalished"); + } + + mysql_autocommit(mysql_con1, 1); + mysql_autocommit(mysql_con2, 1); + + if (mysql_query(mysql_con1, "DROP TABLE IF EXISTS test_table")) { + fprintf(stderr, "\n drop table failed with error %s ", + mysql_error(mysql_con1)); + exit(1); + } + + if (mysql_query(mysql_con1, "CREATE TABLE test_table(col1 int)")) { + fprintf(stderr, "\n create table failed with error %s ", + mysql_error(mysql_con1)); + exit(1); + } + + if (mysql_query(mysql_con1, + "INSERT INTO test_table values(10), (20), (30)")) { + fprintf(stderr, "\n insert into table failed with error %s ", + mysql_error(mysql_con1)); + exit(1); + } + if (mysql_query(mysql_con1, "LOCK TABLE test_table WRITE")) { + fprintf(stderr, "\n lock table failed with error %s ", + mysql_error(mysql_con1)); + exit(1); + } + stmt_text = "SELECT * FROM test_table"; + /* run query in asynchronous way */ + mysql_con2_status = mysql_real_query_nonblocking(mysql_con2, stmt_text, + (ulong)strlen(stmt_text)); + if (mysql_con2_status == NET_ASYNC_NOT_READY) + printf("\n Query not finished yet."); + while (mysql_con2_status == NET_ASYNC_NOT_READY) { + /* do some other task */ + perform_arithmatic(); + fprintf(stdout, "\n Operation pending."); + counter++; + if (counter == 4) mysql_query(mysql_con1, "UNLOCK TABLES"); + mysql_con2_status = mysql_real_query_nonblocking(mysql_con2, stmt_text, + (ulong)strlen(stmt_text)); + } + if (mysql_con2_status == NET_ASYNC_ERROR) { + fprintf(stdout, "\n mysql_real_query_nonblocking() failed"); + exit(1); + } else { + fprintf(stdout, "\n mysql_real_query_nonblocking() passed"); + } + mysql_close(mysql_con1); + mysql_close(mysql_con2); +} + static struct my_tests_st my_tests[] = { {"disable_query_logs", disable_query_logs}, {"test_view_sp_list_fields", test_view_sp_list_fields}, @@ -20424,6 +20708,8 @@ static struct my_tests_st my_tests[] = { {"test_skip_metadata", test_skip_metadata}, {"test_bug25701141", test_bug25701141}, {"test_bug27443252", test_bug27443252}, + {"test_wl11381", test_wl11381}, + {"test_wl11381_qa", test_wl11381_qa}, {0, 0}}; static struct my_tests_st *get_my_tests() { return my_tests; } diff --git a/vio/vio.cc b/vio/vio.cc index ef36d57a1957..5730d667c252 100644 --- a/vio/vio.cc +++ b/vio/vio.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -142,6 +142,8 @@ Vio &Vio::operator=(Vio &&vio) { read_pos = vio.read_pos; read_end = vio.read_end; + is_blocking_flag = vio.is_blocking_flag; + #ifdef USE_PPOLL_IN_VIO thread_id = vio.thread_id; signal_mask = vio.signal_mask; @@ -175,6 +177,9 @@ Vio &Vio::operator=(Vio &&vio) { io_wait = vio.io_wait; connect = vio.connect; + is_blocking = vio.is_blocking; + set_blocking = vio.set_blocking; + #ifdef _WIN32 overlapped = vio.overlapped; hPipe = vio.hPipe; @@ -243,6 +248,9 @@ static bool vio_init(Vio *vio, enum enum_vio_type type, my_socket sd, vio->io_wait = no_io_wait; vio->is_connected = vio_is_connected_pipe; vio->has_data = has_no_data; + vio->is_blocking = vio_is_blocking; + vio->set_blocking = vio_set_blocking; + vio->is_blocking_flag = true; return false; } if (type == VIO_TYPE_SHARED_MEMORY) { @@ -259,6 +267,9 @@ static bool vio_init(Vio *vio, enum enum_vio_type type, my_socket sd, vio->io_wait = no_io_wait; vio->is_connected = vio_is_connected_shared_memory; vio->has_data = has_no_data; + vio->is_blocking = vio_is_blocking; + vio->set_blocking = vio_set_blocking; + vio->is_blocking_flag = true; return false; } #endif /* _WIN32 */ @@ -278,6 +289,10 @@ static bool vio_init(Vio *vio, enum enum_vio_type type, my_socket sd, vio->is_connected = vio_is_connected; 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_flag = vio_set_blocking_flag; + vio->is_blocking_flag = true; return false; } #endif /* HAVE_OPENSSL */ @@ -295,6 +310,10 @@ static bool vio_init(Vio *vio, enum enum_vio_type type, my_socket sd, vio->is_connected = vio_is_connected; vio->timeout = vio_socket_timeout; vio->has_data = vio->read_buffer ? vio_buff_has_data : has_no_data; + vio->is_blocking = vio_is_blocking; + vio->set_blocking = vio_set_blocking; + vio->set_blocking_flag = vio_set_blocking_flag; + vio->is_blocking_flag = true; return false; } diff --git a/vio/viosocket.cc b/vio/viosocket.cc index 48da4b9cc0a5..9ed2ddf2fcb1 100644 --- a/vio/viosocket.cc +++ b/vio/viosocket.cc @@ -1,5 +1,5 @@ /* - Copyright (c) 2001, 2018, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -145,18 +145,26 @@ size_t vio_read(Vio *vio, uchar *buf, size_t size) { flags)) == -1) { int error = socket_errno; - /* The operation would block? */ + /* Error encountered that is unrelated to blocking; percolate it up. */ #if SOCKET_EAGAIN == SOCKET_EWOULDBLOCK if (error != SOCKET_EAGAIN) #else if (error != SOCKET_EAGAIN && error != SOCKET_EWOULDBLOCK) #endif break; + /* + Nonblocking with either EAGAIN or EWOULDBLOCK. Don't call + io_wait. 0 bytes are available. + */ + DBUG_ASSERT(error == SOCKET_EAGAIN || error == SOCKET_EWOULDBLOCK); + if (!vio_is_blocking(vio)) { + DBUG_PRINT("info", ("vio_read on nonblocking socket read no bytes")); + DBUG_RETURN(-1); + } /* Wait for input data to become available. */ if ((ret = vio_socket_io_wait(vio, VIO_IO_EVENT_READ))) break; } - DBUG_RETURN(ret); } @@ -219,6 +227,11 @@ size_t vio_write(Vio *vio, const uchar *buf, size_t size) { #endif break; + if (!vio_is_blocking(vio)) { + DBUG_PRINT("info", ("vio_write on nonblocking socket written no bytes")); + DBUG_RETURN(-1); + } + /* Wait for the output buffer to become writable.*/ if ((ret = vio_socket_io_wait(vio, VIO_IO_EVENT_WRITE))) break; } @@ -227,12 +240,11 @@ size_t vio_write(Vio *vio, const uchar *buf, size_t size) { } // WL#4896: Not covered -static int vio_set_blocking(Vio *vio, bool status) { +int vio_set_blocking(Vio *vio, bool status) { DBUG_ENTER("vio_set_blocking"); #ifdef _WIN32 - DBUG_ASSERT(vio->type != VIO_TYPE_NAMEDPIPE); - DBUG_ASSERT(vio->type != VIO_TYPE_SHARED_MEMORY); + { int ret; u_long arg = status ? 0 : 1; @@ -266,6 +278,26 @@ static int vio_set_blocking(Vio *vio, bool status) { DBUG_RETURN(0); } +int vio_set_blocking_flag(Vio *vio, bool status) { + DBUG_ENTER(__func__); + int ret = 0; + /* + Asynchronous communication in client is allowed only for below + types of connections. + */ + if (VIO_TYPE_TCPIP == vio->type || VIO_TYPE_SOCKET == vio->type || + VIO_TYPE_SSL == vio->type) { + vio->is_blocking_flag = status; + ret = vio_set_blocking(vio, status); + } + DBUG_RETURN(ret); +} + +bool vio_is_blocking(Vio *vio) { + DBUG_ENTER(__func__); + DBUG_RETURN(vio->is_blocking_flag); +} + int vio_socket_timeout(Vio *vio, uint which MY_ATTRIBUTE((unused)), bool old_mode) { int ret = 0; @@ -988,6 +1020,7 @@ int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout) { @param vio A VIO object. @param addr Socket address containing the peer address. @param len Length of socket address. + @param nonblocking flag to represent if socket is blocking or nonblocking @param timeout Interval (in milliseconds) to wait until a connection is established. @@ -996,7 +1029,7 @@ int vio_io_wait(Vio *vio, enum enum_vio_io_event event, int timeout) { */ bool vio_socket_connect(Vio *vio, struct sockaddr *addr, socklen_t len, - int timeout) { + bool nonblocking, int timeout) { int ret, wait; int retry_count = 0; DBUG_ENTER("vio_socket_connect"); @@ -1005,7 +1038,8 @@ bool vio_socket_connect(Vio *vio, struct sockaddr *addr, socklen_t len, DBUG_ASSERT(vio->type == VIO_TYPE_SOCKET || vio->type == VIO_TYPE_TCPIP); /* If timeout is not infinite, set socket to non-blocking mode. */ - if ((timeout > -1) && vio_set_blocking(vio, false)) DBUG_RETURN(true); + if (((timeout > -1) || nonblocking) && vio_set_blocking(vio, false)) + DBUG_RETURN(true); /* Initiate the connection. */ do { @@ -1034,7 +1068,8 @@ bool vio_socket_connect(Vio *vio, struct sockaddr *addr, socklen_t len, 2. The connection was set up successfully: getsockopt() will return 0 as an error. */ - if (wait && (vio_io_wait(vio, VIO_IO_EVENT_CONNECT, timeout) == 1)) { + if (!nonblocking && wait && + (vio_io_wait(vio, VIO_IO_EVENT_CONNECT, timeout) == 1)) { int error; IF_WIN(int, socklen_t) optlen = sizeof(error); IF_WIN(char, void) *optval = (IF_WIN(char, void) *)&error; @@ -1059,11 +1094,15 @@ bool vio_socket_connect(Vio *vio, struct sockaddr *addr, socklen_t len, } /* If necessary, restore the blocking mode, but only if connect succeeded. */ - if ((timeout > -1) && (ret == 0)) { + if (!nonblocking && (timeout > -1) && (ret == 0)) { if (vio_set_blocking(vio, true)) DBUG_RETURN(true); } - DBUG_RETURN(MY_TEST(ret)); + if (nonblocking && wait) { + DBUG_RETURN(false); + } else { + DBUG_RETURN(MY_TEST(ret)); + } } /** diff --git a/vio/viossl.cc b/vio/viossl.cc index 9fa34095082e..6ebbd1086d37 100644 --- a/vio/viossl.cc +++ b/vio/viossl.cc @@ -1,4 +1,4 @@ -/* Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. +/* Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -286,6 +286,17 @@ size_t vio_ssl_read(Vio *vio, uchar *buf, size_t size) { /* Process the SSL I/O error. */ if (!ssl_should_retry(vio, ret, &event, &ssl_errno_not_used)) break; + if (!vio->is_blocking_flag) { + 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; } @@ -318,6 +329,17 @@ size_t vio_ssl_write(Vio *vio, const uchar *buf, size_t size) { /* Process the SSL I/O error. */ if (!ssl_should_retry(vio, ret, &event, &ssl_errno_not_used)) break; + if (!vio->is_blocking_flag) { + 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; } @@ -397,9 +419,10 @@ typedef int (*ssl_handshake_func_t)(SSL *); @return Return value is 1 on success. */ -static int ssl_handshake_loop(Vio *vio, SSL *ssl, ssl_handshake_func_t func, - unsigned long *ssl_errno_holder) { - int ret; +static size_t ssl_handshake_loop(Vio *vio, SSL *ssl, ssl_handshake_func_t func, + unsigned long *ssl_errno_holder) { + DBUG_ENTER(__func__); + size_t ret = -1; vio->ssl_arg = ssl; @@ -414,12 +437,27 @@ static int ssl_handshake_loop(Vio *vio, SSL *ssl, ssl_handshake_func_t func, */ DBUG_ASSERT(ERR_peek_error() == 0); - ret = func(ssl); + int handshake_ret; + handshake_ret = func(ssl); - if (ret >= 1) break; + if (handshake_ret >= 1) { + ret = 0; + break; + } /* Process the SSL I/O error. */ - if (!ssl_should_retry(vio, ret, &event, ssl_errno_holder)) break; + if (!ssl_should_retry(vio, handshake_ret, &event, ssl_errno_holder)) break; + + if (!vio->is_blocking_flag) { + 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); + } + } /* Wait for I/O so that the handshake can proceed. */ if (vio_socket_io_wait(vio, event)) break; @@ -427,14 +465,13 @@ static int ssl_handshake_loop(Vio *vio, SSL *ssl, ssl_handshake_func_t func, vio->ssl_arg = NULL; - return ret; + DBUG_RETURN(ret); } -static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, - long timeout MY_ATTRIBUTE((unused)), - ssl_handshake_func_t func, unsigned long *ssl_errno_holder) { - int r; - SSL *ssl; +static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, long timeout, + ssl_handshake_func_t func, unsigned long *ssl_errno_holder, + SSL **sslptr) { + SSL *ssl = nullptr; my_socket sd = mysql_socket_getfd(vio->mysql_socket); /* Declared here to make compiler happy */ @@ -445,61 +482,78 @@ static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, DBUG_ENTER("ssl_do"); DBUG_PRINT("enter", ("ptr: %p, sd: %d ctx: %p", ptr, sd, ptr->ssl_context)); - if (!(ssl = SSL_new(ptr->ssl_context))) { - DBUG_PRINT("error", ("SSL_new failure")); - *ssl_errno_holder = ERR_get_error(); - DBUG_RETURN(1); + if (!sslptr) { + sslptr = &ssl; } - DBUG_PRINT("info", ("ssl: %p timeout: %ld", ssl, timeout)); - SSL_clear(ssl); - SSL_set_fd(ssl, sd); + + if (*sslptr == nullptr) { + if (!(ssl = SSL_new(ptr->ssl_context))) { + DBUG_PRINT("error", ("SSL_new failure")); + *ssl_errno_holder = ERR_get_error(); + DBUG_RETURN(1); + } + + DBUG_PRINT("info", ("ssl: %p timeout: %ld", ssl, timeout)); + SSL_clear(ssl); + SSL_SESSION_set_timeout(SSL_get_session(ssl), timeout); + SSL_set_fd(ssl, sd); #if !defined(HAVE_WOLFSSL) && OPENSSL_VERSION_NUMBER > 0x00908000L - SSL_clear_options(ssl, SSL_OP_LEGACY_SERVER_CONNECT); + SSL_clear_options(ssl, SSL_OP_LEGACY_SERVER_CONNECT); #endif #if !defined(HAVE_WOLFSSL) && defined(SSL_OP_NO_COMPRESSION) - SSL_set_options(ssl, SSL_OP_NO_COMPRESSION); /* OpenSSL >= 1.0 only */ + SSL_set_options(ssl, SSL_OP_NO_COMPRESSION); /* OpenSSL >= 1.0 only */ #elif !defined(HAVE_WOLFSSL) && \ OPENSSL_VERSION_NUMBER >= 0x00908000L /* workaround for OpenSSL 0.9.8 */ - sk_SSL_COMP_zero(SSL_COMP_get_compression_methods()); + sk_SSL_COMP_zero(SSL_COMP_get_compression_methods()); #endif #if !defined(HAVE_WOLFSSL) && !defined(DBUG_OFF) - { - STACK_OF(SSL_COMP) *ssl_comp_methods = NULL; - ssl_comp_methods = SSL_COMP_get_compression_methods(); - n = sk_SSL_COMP_num(ssl_comp_methods); - DBUG_PRINT("info", ("Available compression methods:\n")); - if (n == 0) - DBUG_PRINT("info", ("NONE\n")); - else - for (j = 0; j < n; j++) { - SSL_COMP *c = sk_SSL_COMP_value(ssl_comp_methods, j); + { + STACK_OF(SSL_COMP) *ssl_comp_methods = NULL; + ssl_comp_methods = SSL_COMP_get_compression_methods(); + n = sk_SSL_COMP_num(ssl_comp_methods); + DBUG_PRINT("info", ("Available compression methods:\n")); + if (n == 0) + DBUG_PRINT("info", ("NONE\n")); + else + for (j = 0; j < n; j++) { + SSL_COMP *c = sk_SSL_COMP_value(ssl_comp_methods, j); #if OPENSSL_VERSION_NUMBER < 0x10100000L - DBUG_PRINT("info", (" %d: %s\n", c->id, c->name)); + DBUG_PRINT("info", (" %d: %s\n", c->id, c->name)); #else /* OPENSSL_VERSION_NUMBER < 0x10100000L */ - DBUG_PRINT("info", - (" %d: %s\n", SSL_COMP_get_id(c), SSL_COMP_get0_name(c))); + DBUG_PRINT("info", + (" %d: %s\n", SSL_COMP_get_id(c), SSL_COMP_get0_name(c))); #endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */ - } - } + } + } #endif - /* - Since yaSSL does not support non-blocking send operations, use - special transport functions that properly handles non-blocking - sockets. These functions emulate the behavior of blocking I/O - operations by waiting for I/O to become available. - */ + /* + Since yaSSL does not support non-blocking send operations, use + special transport functions that properly handles non-blocking + sockets. These functions emulate the behavior of blocking I/O + operations by waiting for I/O to become available. + */ #ifdef HAVE_WOLFSSL - /* Set first argument of the transport functions. */ - wolfSSL_SetIOReadCtx(ssl, vio); - wolfSSL_SetIOWriteCtx(ssl, vio); + /* Set first argument of the transport functions. */ + wolfSSL_SetIOReadCtx(ssl, vio); + wolfSSL_SetIOWriteCtx(ssl, vio); #endif + *sslptr = ssl; + } else { + ssl = *sslptr; + } + + size_t loop_ret; + if ((loop_ret = ssl_handshake_loop(vio, ssl, func, ssl_errno_holder))) { + if (loop_ret != VIO_SOCKET_ERROR) { + DBUG_RETURN((int)loop_ret); // Don't free SSL + } - if ((r = ssl_handshake_loop(vio, ssl, func, ssl_errno_holder)) < 1) { DBUG_PRINT("error", ("SSL_connect/accept failure")); SSL_free(ssl); - DBUG_RETURN(1); + *sslptr = nullptr; + DBUG_RETURN((int)VIO_SOCKET_ERROR); } /* @@ -508,6 +562,9 @@ static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, and set pointer to the SSL structure */ if (vio_reset(vio, VIO_TYPE_SSL, SSL_get_fd(ssl), ssl, 0)) DBUG_RETURN(1); + if (sslptr != &ssl) { + *sslptr = nullptr; + } #ifndef DBUG_OFF { @@ -541,14 +598,14 @@ static int ssl_do(struct st_VioSSLFd *ptr, Vio *vio, int sslaccept(struct st_VioSSLFd *ptr, Vio *vio, long timeout, unsigned long *ssl_errno_holder) { DBUG_ENTER("sslaccept"); - int ret = ssl_do(ptr, vio, timeout, SSL_accept, ssl_errno_holder); + int ret = ssl_do(ptr, vio, timeout, SSL_accept, ssl_errno_holder, nullptr); DBUG_RETURN(ret); } int sslconnect(struct st_VioSSLFd *ptr, Vio *vio, long timeout, - unsigned long *ssl_errno_holder) { + unsigned long *ssl_errno_holder, SSL **ssl) { DBUG_ENTER("sslconnect"); - int ret = ssl_do(ptr, vio, timeout, SSL_connect, ssl_errno_holder); + int ret = ssl_do(ptr, vio, timeout, SSL_connect, ssl_errno_holder, ssl); DBUG_RETURN(ret); }