From e58148356dc96389051b5824a92b498a8f4ae123 Mon Sep 17 00:00:00 2001 From: counterpoint Date: Tue, 26 Jan 2016 16:08:02 +0000 Subject: [PATCH 01/26] Split off listener functionality and move SSL initiation from service to listener. Put GWPROTOCOL in its own header file. --- server/core/CMakeLists.txt | 2 +- server/core/config.c | 215 +++++++++++++----- server/core/listener.c | 276 ++++++++++++++++++++++++ server/core/service.c | 43 ++-- server/core/test/testservice.c | 6 +- server/include/dcb.h | 50 +---- server/include/gw_protocol.h | 92 ++++++++ server/include/listener.h | 89 ++++++++ server/include/service.h | 34 +-- server/modules/protocol/httpd.c | 1 + server/modules/protocol/maxscaled.c | 1 + server/modules/protocol/mysql_backend.c | 1 + server/modules/protocol/mysql_client.c | 8 +- server/modules/protocol/telnetd.c | 1 + server/modules/protocol/testprotocol.c | 1 + 15 files changed, 663 insertions(+), 157 deletions(-) create mode 100644 server/core/listener.c create mode 100644 server/include/gw_protocol.h create mode 100644 server/include/listener.h diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index 7d7a03630..26f4123ab 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(maxscale-common SHARED adminusers.c atomic.c buffer.c config.c dbusers.c dcb.c filter.c externcmd.c gwbitmask.c gwdirs.c gw_utils.c hashtable.c hint.c housekeeper.c load_utils.c maxscale_pcre2.c memlog.c modutil.c monitor.c poll.c random_jkiss.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c ${CMAKE_SOURCE_DIR}/log_manager/log_manager.cc ${CMAKE_SOURCE_DIR}/utils/skygw_utils.cc) +add_library(maxscale-common SHARED adminusers.c atomic.c buffer.c config.c dbusers.c dcb.c filter.c externcmd.c gwbitmask.c gwdirs.c gw_utils.c hashtable.c hint.c housekeeper.c listener.c load_utils.c maxscale_pcre2.c memlog.c modutil.c monitor.c poll.c random_jkiss.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c ${CMAKE_SOURCE_DIR}/log_manager/log_manager.cc ${CMAKE_SOURCE_DIR}/utils/skygw_utils.cc) target_link_libraries(maxscale-common ${EMBEDDED_LIB} ${LZMA_LINK_FLAGS} ${PCRE2_LIBRARIES} ${PCRE_LINK_FLAGS} ${CURL_LIBRARIES} ssl aio pthread crypt dl crypto inih z rt m stdc++) diff --git a/server/core/config.c b/server/core/config.c index 5bbcc69c6..ee4794e3c 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -43,7 +43,8 @@ * 20/02/15 Markus Mäkelä Added connection_timeout parameter for services * 05/03/15 Massimiliano Pinto Added notification_feedback support * 20/04/15 Guillaume Lefranc Added available_when_donor parameter - * 22/04/15 Martin Brampton Added disable_master_role_setting parameter + * 22/04/15 Martin Brampton Added disable_master_role_setting parameter + * 26/01/16 Martin Brampton Transfer SSL processing to listener * * @endverbatim */ @@ -90,6 +91,8 @@ static void global_defaults(); static void feedback_defaults(); static void check_config_objects(CONFIG_CONTEXT *context); static int maxscale_getline(char** dest, int* size, FILE* file); +static SSL_LISTENER *make_ssl_structure(CONFIG_CONTEXT *obj); + int config_truth_value(char *str); int config_get_ifaddr(unsigned char *output); int config_get_release_string(char* release); @@ -99,7 +102,7 @@ bool config_has_duplicate_sections(const char* config); int create_new_service(CONFIG_CONTEXT *obj); int create_new_server(CONFIG_CONTEXT *obj); int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* monitorhash); -int create_new_listener(CONFIG_CONTEXT *obj); +int create_new_listener(CONFIG_CONTEXT *obj, bool startnow); int create_new_filter(CONFIG_CONTEXT *obj); int configure_new_service(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj); @@ -150,6 +153,13 @@ static char *listener_params[] = "port", "address", "socket", + "authenticator", + "ssl_cert", + "ssl_ca_cert", + "ssl", + "ssl_key", + "ssl_version", + "ssl_cert_verify_depth", NULL }; @@ -585,7 +595,7 @@ process_config_context(CONFIG_CONTEXT *context) } else if (!strcmp(type, "listener")) { - error_count += create_new_listener(obj); + error_count += create_new_listener(obj, false); } else if (!strcmp(type, "monitor")) { @@ -909,6 +919,7 @@ static struct { "log_info", LOG_INFO, NULL }, { NULL, 0 } }; + /** * Configuration handler for items in the global [MaxScale] section * @@ -1022,6 +1033,118 @@ handle_global_item(const char *name, const char *value) return 1; } +/** + * Form an SSL structure from listener section parameters + * + * @param name The item name + * @param value The item value + * @return 0 on error + */ +static SSL_LISTENER * +make_ssl_structure (CONFIG_CONTEXT *obj) +{ + char *ssl, *ssl_version, *ssl_cert, *ssl_key, *ssl_ca_cert, *ssl_cert_verify_depth; + SSL_LISTENER *new_ssl; + int error_count = 0; + + ssl = config_get_value(obj->parameters, "ssl"); + if (ssl && !strcmp(ssl, "required")) + { + if ((new_ssl = malloc(sizeof(SSL_LISTENER))) == NULL) + { + return NULL; + } + new_ssl->ssl_method_type = SERVICE_SSL_TLS_MAX; + ssl_cert = config_get_value(obj->parameters, "ssl_cert"); + ssl_key = config_get_value(obj->parameters, "ssl_key"); + ssl_ca_cert = config_get_value(obj->parameters, "ssl_ca_cert"); + ssl_version = config_get_value(obj->parameters, "ssl_version"); + ssl_cert_verify_depth = config_get_value(obj->parameters, "ssl_cert_verify_depth"); + new_ssl->ssl_init_done = false; + + if (ssl_version) + { + if (listener_set_ssl_version(new_ssl, ssl_version) != 0) + { + MXS_ERROR("Unknown parameter value for 'ssl_version' for" + " service '%s': %s", obj->object, ssl_version); + error_count++; + } + } + + if (ssl_cert_verify_depth) + { + new_ssl->ssl_cert_verify_depth = atoi(ssl_cert_verify_depth); + if (new_ssl->ssl_cert_verify_depth < 0) + { + MXS_ERROR("Invalid parameter value for 'ssl_cert_verify_depth" + " for service '%s': %s", obj->object, ssl_cert_verify_depth); + new_ssl->ssl_cert_verify_depth = 0; + error_count++; + } + } + + listener_set_certificates(new_ssl, ssl_cert, ssl_key, ssl_ca_cert); + + if (new_ssl->ssl_cert == NULL) + { + error_count++; + MXS_ERROR("Server certificate missing for service '%s'." + "Please provide the path to the server certificate by adding " + "the ssl_cert= parameter", obj->object); + } + + if (new_ssl->ssl_ca_cert == NULL) + { + error_count++; + MXS_ERROR("CA Certificate missing for service '%s'." + "Please provide the path to the certificate authority " + "certificate by adding the ssl_ca_cert= parameter", + obj->object); + } + + if (new_ssl->ssl_key == NULL) + { + error_count++; + MXS_ERROR("Server private key missing for service '%s'. " + "Please provide the path to the server certificate key by " + "adding the ssl_key= parameter", + obj->object); + } + + if (access(new_ssl->ssl_ca_cert, F_OK) != 0) + { + MXS_ERROR("Certificate authority file for service '%s' not found: %s", + obj->object, + new_ssl->ssl_ca_cert); + error_count++; + } + + if (access(new_ssl->ssl_cert, F_OK) != 0) + { + MXS_ERROR("Server certificate file for service '%s' not found: %s", + obj->object, + new_ssl->ssl_cert); + error_count++; + } + + if (access(new_ssl->ssl_key, F_OK) != 0) + { + MXS_ERROR("Server private key file for service '%s' not found: %s", + obj->object, + new_ssl->ssl_key); + error_count++; + } + + if (error_count == 0) + { + return new_ssl; + } + free(new_ssl); + } + return NULL; +} + /** * Configuration handler for items in the feedback [feedback] section * @@ -1386,52 +1509,7 @@ process_config_update(CONFIG_CONTEXT *context) } else if (!strcmp(type, "listener")) { - char *service; - char *port; - char *protocol; - char *address; - char *socket; - - service = config_get_value(obj->parameters, "service"); - address = config_get_value(obj->parameters, "address"); - port = config_get_value(obj->parameters, "port"); - protocol = config_get_value(obj->parameters, "protocol"); - socket = config_get_value(obj->parameters, "socket"); - - if (service && socket && protocol) - { - CONFIG_CONTEXT *ptr = context; - while (ptr && strcmp(ptr->object, service) != 0) - { - ptr = ptr->next; - } - - if (ptr && - ptr->element && - serviceHasProtocol(ptr->element, protocol, 0) == 0) - { - serviceAddProtocol(ptr->element, protocol, socket, 0); - serviceStartProtocol(ptr->element, protocol, 0); - } - } - - if (service && port && protocol) - { - CONFIG_CONTEXT *ptr = context; - - while (ptr && strcmp(ptr->object, service) != 0) - { - ptr = ptr->next; - } - - if (ptr && - ptr->element && - serviceHasProtocol(ptr->element, protocol, atoi(port)) == 0) - { - serviceAddProtocol(ptr->element, protocol, address, atoi(port)); - serviceStartProtocol(ptr->element, protocol, atoi(port)); - } - } + create_new_listener(obj, true); } else if (strcmp(type, "server") != 0 && strcmp(type, "monitor") != 0 && @@ -2633,9 +2711,10 @@ int create_new_monitor(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj, HASHTABLE* /** * Create a new listener for a service * @param obj Listener configuration context + * @param startnow If true, start the listener now * @return Number of errors */ -int create_new_listener(CONFIG_CONTEXT *obj) +int create_new_listener(CONFIG_CONTEXT *obj, bool startnow) { int error_count = 0; char *service_name = config_get_value(obj->parameters, "service"); @@ -2643,20 +2722,52 @@ int create_new_listener(CONFIG_CONTEXT *obj) char *address = config_get_value(obj->parameters, "address"); char *protocol = config_get_value(obj->parameters, "protocol"); char *socket = config_get_value(obj->parameters, "socket"); + char *authenticator = config_get_value(obj->parameters, "authenticator"); if (service_name && protocol && (socket || port)) { SERVICE *service = service_find(service_name); if (service) { + SSL_LISTENER *ssl_info = make_ssl_structure(obj); if (socket) { - serviceAddProtocol(service, protocol, socket, 0); + if (serviceHasProtocol(service, protocol, 0)) + { + MXS_ERROR("Listener '%s', for service '%s', socket %s, already have socket.", + obj->object, + service_name, + socket); + error_count++; + } + else + { + serviceAddProtocol(service, protocol, socket, 0, authenticator, ssl_info); + if (startnow) + { + serviceStartProtocol(service, protocol, 0); + } + } } if (port) { - serviceAddProtocol(service, protocol, address, atoi(port)); + if (serviceHasProtocol(service, protocol, atoi(port))) + { + MXS_ERROR("Listener '%s', for service '%s', already have port %s.", + obj->object, + service_name, + port); + error_count++; + } + else + { + serviceAddProtocol(service, protocol, address, atoi(port), authenticator, ssl_info); + if (startnow) + { + serviceStartProtocol(service, protocol, atoi(port)); + } + } } } else diff --git a/server/core/listener.c b/server/core/listener.c new file mode 100644 index 000000000..015f47272 --- /dev/null +++ b/server/core/listener.c @@ -0,0 +1,276 @@ +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * 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 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + +/** + * @file listener.c - Listener generic functions + * + * Listeners wait for new client connections and, if the connection is successful + * a new session is created. A listener typically knows about a port or a socket, + * and a few other things. It may know about SSL if it is expecting an SSL + * connection. + * + * @verbatim + * Revision History + * + * Date Who Description + * 26/01/16 Martin Brampton Initial implementation + * + * @endverbatim + */ +#include +#include +#include +#include +#include +#include + +static RSA *rsa_512 = NULL; +static RSA *rsa_1024 = NULL; + +static RSA *tmp_rsa_callback(SSL *s, int is_export, int keylength); + +/** + * Set the maximum SSL/TLS version the listener will support + * @param ssl_listener Listener data to configure + * @param version SSL/TLS version string + * @return 0 on success, -1 on invalid version string + */ +int +listener_set_ssl_version(SSL_LISTENER *ssl_listener, char* version) +{ + if (strcasecmp(version,"SSLV3") == 0) + { + ssl_listener->ssl_method_type = SERVICE_SSLV3; + } + else if (strcasecmp(version,"TLSV10") == 0) + { + ssl_listener->ssl_method_type = SERVICE_TLS10; + } +#ifdef OPENSSL_1_0 + else if (strcasecmp(version,"TLSV11") == 0) + { + ssl_listener->ssl_method_type = SERVICE_TLS11; + } + else if (strcasecmp(version,"TLSV12") == 0) + { + ssl_listener->ssl_method_type = SERVICE_TLS12; + } +#endif + else if (strcasecmp(version,"MAX") == 0) + { + ssl_listener->ssl_method_type = SERVICE_SSL_TLS_MAX; + } + else + { + return -1; + } + return 0; +} + +/** + * Set the locations of the listener's SSL certificate, listener's private key + * and the CA certificate which both the client and the listener should trust. + * @param ssl_listener Listener data to configure + * @param cert SSL certificate + * @param key SSL private key + * @param ca_cert SSL CA certificate + */ +void +listener_set_certificates(SSL_LISTENER *ssl_listener, char* cert, char* key, char* ca_cert) +{ + if (ssl_listener->ssl_cert) + { + free(ssl_listener->ssl_cert); + } + ssl_listener->ssl_cert = strdup(cert); + + if (ssl_listener->ssl_key) + { + free(ssl_listener->ssl_key); + } + ssl_listener->ssl_key = strdup(key); + + if (ssl_listener->ssl_ca_cert) + { + free(ssl_listener->ssl_ca_cert); + } + ssl_listener->ssl_ca_cert = strdup(ca_cert); +} + +/** + * Initialize the listener's SSL context. This sets up the generated RSA + * encryption keys, chooses the listener encryption level and configures the + * listener certificate, private key and certificate authority file. + * @param ssl_listener Listener data to initialize + * @return 0 on success, -1 on error + */ +int +listener_init_SSL(SSL_LISTENER *ssl_listener) +{ + DH* dh; + RSA* rsa; + + if (!ssl_listener->ssl_init_done) + { + switch(ssl_listener->ssl_method_type) + { + case SERVICE_SSLV3: + ssl_listener->method = (SSL_METHOD*)SSLv3_server_method(); + break; + case SERVICE_TLS10: + ssl_listener->method = (SSL_METHOD*)TLSv1_server_method(); + break; +#ifdef OPENSSL_1_0 + case SERVICE_TLS11: + ssl_listener->method = (SSL_METHOD*)TLSv1_1_server_method(); + break; + case SERVICE_TLS12: + ssl_listener->method = (SSL_METHOD*)TLSv1_2_server_method(); + break; +#endif + /** Rest of these use the maximum available SSL/TLS methods */ + case SERVICE_SSL_MAX: + ssl_listener->method = (SSL_METHOD*)SSLv23_server_method(); + break; + case SERVICE_TLS_MAX: + ssl_listener->method = (SSL_METHOD*)SSLv23_server_method(); + break; + case SERVICE_SSL_TLS_MAX: + ssl_listener->method = (SSL_METHOD*)SSLv23_server_method(); + break; + default: + ssl_listener->method = (SSL_METHOD*)SSLv23_server_method(); + break; + } + + if ((ssl_listener->ctx = SSL_CTX_new(ssl_listener->method)) == NULL) + { + MXS_ERROR("SSL context initialization failed."); + return -1; + } + + /** Enable all OpenSSL bug fixes */ + SSL_CTX_set_options(ssl_listener->ctx,SSL_OP_ALL); + + /** Generate the 512-bit and 1024-bit RSA keys */ + if (rsa_512 == NULL) + { + rsa_512 = RSA_generate_key(512,RSA_F4,NULL,NULL); + if (rsa_512 == NULL) + { + MXS_ERROR("512-bit RSA key generation failed."); + return -1; + } + } + if (rsa_1024 == NULL) + { + rsa_1024 = RSA_generate_key(1024,RSA_F4,NULL,NULL); + if (rsa_1024 == NULL) + { + MXS_ERROR("1024-bit RSA key generation failed."); + return -1; + } + } + + if (rsa_512 != NULL && rsa_1024 != NULL) + { + SSL_CTX_set_tmp_rsa_callback(ssl_listener->ctx,tmp_rsa_callback); + } + + /** Load the server certificate */ + if (SSL_CTX_use_certificate_file(ssl_listener->ctx, ssl_listener->ssl_cert, SSL_FILETYPE_PEM) <= 0) + { + MXS_ERROR("Failed to set server SSL certificate."); + return -1; + } + + /* Load the private-key corresponding to the server certificate */ + if (SSL_CTX_use_PrivateKey_file(ssl_listener->ctx, ssl_listener->ssl_key, SSL_FILETYPE_PEM) <= 0) + { + MXS_ERROR("Failed to set server SSL key."); + return -1; + } + + /* Check if the server certificate and private-key matches */ + if (!SSL_CTX_check_private_key(ssl_listener->ctx)) + { + MXS_ERROR("Server SSL certificate and key do not match."); + return -1; + } + + /* Load the RSA CA certificate into the SSL_CTX structure */ + if (!SSL_CTX_load_verify_locations(ssl_listener->ctx, ssl_listener->ssl_ca_cert, NULL)) + { + MXS_ERROR("Failed to set Certificate Authority file."); + return -1; + } + + /* Set to require peer (client) certificate verification */ + SSL_CTX_set_verify(ssl_listener->ctx,SSL_VERIFY_PEER,NULL); + + /* Set the verification depth */ + SSL_CTX_set_verify_depth(ssl_listener->ctx,ssl_listener->ssl_cert_verify_depth); + ssl_listener->ssl_init_done = true; + } + return 0; +} + +/** + * The RSA key generation callback function for OpenSSL. + * @param s SSL structure + * @param is_export Not used + * @param keylength Length of the key + * @return Pointer to RSA structure + */ +static RSA * +tmp_rsa_callback(SSL *s, int is_export, int keylength) +{ + RSA *rsa_tmp=NULL; + + switch (keylength) { + case 512: + if (rsa_512) + { + rsa_tmp = rsa_512; + } + else + { + /* generate on the fly, should not happen in this example */ + rsa_tmp = RSA_generate_key(keylength,RSA_F4,NULL,NULL); + rsa_512 = rsa_tmp; /* Remember for later reuse */ + } + break; + case 1024: + if (rsa_1024) + { + rsa_tmp=rsa_1024; + } + break; + default: + /* Generating a key on the fly is very costly, so use what is there */ + if (rsa_1024) + { + rsa_tmp=rsa_1024; + } + else + { + rsa_tmp=rsa_512; /* Use at least a shorter key */ + } + } + return(rsa_tmp); +} diff --git a/server/core/service.c b/server/core/service.c index b0241191b..f4030e5d2 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -48,6 +48,8 @@ #include #include #include +#include +#include #include #include #include @@ -218,7 +220,7 @@ service_isvalid(SERVICE *service) * @return The number of listeners started */ static int -serviceStartPort(SERVICE *service, SERV_PROTOCOL *port) +serviceStartPort(SERVICE *service, SERV_LISTENER *port) { int listeners = 0; char config_bind[40]; @@ -232,6 +234,11 @@ serviceStartPort(SERVICE *service, SERV_PROTOCOL *port) goto retblock; } + if (port->ssl) + { + listener_init_SSL(port->ssl); + } + if (strcmp(port->protocol, "MySQLClient") == 0) { int loaded; @@ -420,7 +427,7 @@ retblock: */ int serviceStartAllPorts(SERVICE* service) { - SERV_PROTOCOL *port = service->ports; + SERV_LISTENER *port = service->ports; int listeners = 0; while (!service->svc_do_shutdown && port) { @@ -507,7 +514,7 @@ serviceStart(SERVICE *service) void serviceStartProtocol(SERVICE *service, char *protocol, int port) { - SERV_PROTOCOL *ptr; + SERV_LISTENER *ptr; ptr = service->ports; while (ptr) @@ -560,7 +567,7 @@ serviceStartAll() int serviceStop(SERVICE *service) { - SERV_PROTOCOL *port; + SERV_LISTENER *port; int listeners = 0; port = service->ports; @@ -592,7 +599,7 @@ serviceStop(SERVICE *service) int serviceRestart(SERVICE *service) { - SERV_PROTOCOL *port; + SERV_LISTENER *port; int listeners = 0; port = service->ports; @@ -686,25 +693,20 @@ service_free(SERVICE *service) * @return TRUE if the protocol/port could be added */ int -serviceAddProtocol(SERVICE *service, char *protocol, char *address, unsigned short port) +serviceAddProtocol(SERVICE *service, char *protocol, char *address, unsigned short port, char *authenticator, SSL_LISTENER *ssl) { - SERV_PROTOCOL *proto; + SERV_LISTENER *proto; - if ((proto = (SERV_PROTOCOL *)malloc(sizeof(SERV_PROTOCOL))) == NULL) + if ((proto = (SERV_LISTENER *)malloc(sizeof(SERV_LISTENER))) == NULL) { return 0; } proto->listener = NULL; proto->protocol = strdup(protocol); - if (address) - { - proto->address = strdup(address); - } - else - { - proto->address = NULL; - } + proto->address = address ? strdup(address) : NULL; proto->port = port; + proto->authenticator = authenticator ? strdup(authenticator) : NULL; + proto->ssl = ssl; spinlock_acquire(&service->spin); proto->next = service->ports; service->ports = proto; @@ -724,7 +726,7 @@ serviceAddProtocol(SERVICE *service, char *protocol, char *address, unsigned sho int serviceHasProtocol(SERVICE *service, char *protocol, unsigned short port) { - SERV_PROTOCOL *proto; + SERV_LISTENER *proto; spinlock_acquire(&service->spin); proto = service->ports; @@ -1457,7 +1459,7 @@ void dListListeners(DCB *dcb) { SERVICE *service; - SERV_PROTOCOL *lptr; + SERV_LISTENER *lptr; spinlock_acquire(&service_spin); service = allServices; @@ -1930,7 +1932,7 @@ serviceListenerRowCallback(RESULTSET *set, void *data) char buf[20]; RESULT_ROW *row; SERVICE *service; - SERV_PROTOCOL *lptr = NULL; + SERV_LISTENER *lptr = NULL; spinlock_acquire(&service_spin); service = allServices; @@ -2135,6 +2137,9 @@ int serviceInitSSL(SERVICE* service) DH* dh; RSA* rsa; + /* Pending removal in favour of processing in listener. */ + return 0; + if (!service->ssl_init_done) { switch(service->ssl_method_type) diff --git a/server/core/test/testservice.c b/server/core/test/testservice.c index d58d37ac1..b75dfcbae 100644 --- a/server/core/test/testservice.c +++ b/server/core/test/testservice.c @@ -58,7 +58,7 @@ init_test_env(NULL); /* Service tests */ ss_dfprintf(stderr, - "testservice : creating service called MyService with router nonexistent"); + "testservice : creating service called MyService with router nonexistent"); service = service_alloc("MyService", "non-existent"); mxs_log_flush_sync(); ss_info_dassert(NULL == service, "New service with invalid router should be null"); @@ -70,7 +70,7 @@ init_test_env(NULL); ss_info_dassert(0 != service_isvalid(service), "Service must be valid after creation"); ss_info_dassert(0 == strcmp("MyService", service_get_name(service)), "Service must have given name"); ss_dfprintf(stderr, "\t..done\nAdding protocol testprotocol."); - ss_info_dassert(0 != serviceAddProtocol(service, "testprotocol", "localhost", 9876), "Add Protocol should succeed"); + ss_info_dassert(0 != serviceAddProtocol(service, "testprotocol", "localhost", 9876, "MySQL", NULL), "Add Protocol should succeed"); ss_info_dassert(0 != serviceHasProtocol(service, "testprotocol", 9876), "Service should have new protocol as requested"); serviceStartProtocol(service, "testprotocol", 9876); mxs_log_flush_sync(); @@ -90,7 +90,7 @@ init_test_env(NULL); ss_dfprintf(stderr, "\t..done\n"); return 0; - + } int main(int argc, char **argv) diff --git a/server/include/dcb.h b/server/include/dcb.h index 94e1f3992..3d7ea4ee6 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -19,13 +19,11 @@ */ #include #include +#include #include #include #include #include -#include -#include -#include #define ERRHANDLE @@ -59,50 +57,13 @@ struct service; * 27/08/2014 Mark Riddoch Addition of write event queuing * 23/09/2014 Mark Riddoch New poll processing queue * 19/06/2015 Martin Brampton Provision of persistent connections + * 20/01/2016 Martin Brampton Moved GWPROTOCOL to gw_protocol.h * * @endverbatim */ struct dcb; -/** - * @verbatim - * The operations that can be performed on the descriptor - * - * read EPOLLIN handler for the socket - * write MaxScale data write entry point - * write_ready EPOLLOUT handler for the socket, indicates - * that the socket is ready to send more data - * error EPOLLERR handler for the socket - * hangup EPOLLHUP handler for the socket - * accept Accept handler for listener socket only - * connect Create a connection to the specified server - * for the session pased in - * close MaxScale close entry point for the socket - * listen Create a listener for the protocol - * auth Authentication entry point - * session Session handling entry point - * @endverbatim - * - * This forms the "module object" for protocol modules within the gateway. - * - * @see load_module - */ -typedef struct gw_protocol -{ - int (*read)(struct dcb *); - int (*write)(struct dcb *, GWBUF *); - int (*write_ready)(struct dcb *); - int (*error)(struct dcb *); - int (*hangup)(struct dcb *); - int (*accept)(struct dcb *); - int (*connect)(struct dcb *, struct server *, struct session *); - int (*close)(struct dcb *); - int (*listen)(struct dcb *, char *); - int (*auth)(struct dcb *, struct server *, struct session *, GWBUF *); - int (*session)(struct dcb *, void *); -} GWPROTOCOL; - /** * The event queue structure used in the polling loop to maintain a queue * of events that need to be processed for the DCB. @@ -128,13 +89,6 @@ typedef struct unsigned long started; } DCBEVENTQ; -/** - * The GWPROTOCOL version data. The following should be updated whenever - * the GWPROTOCOL structure is changed. See the rules defined in modinfo.h - * that define how these numbers should change. - */ -#define GWPROTOCOL_VERSION {1, 0, 0} - #define DCBFD_CLOSED -1 /** diff --git a/server/include/gw_protocol.h b/server/include/gw_protocol.h new file mode 100644 index 000000000..826a87c9d --- /dev/null +++ b/server/include/gw_protocol.h @@ -0,0 +1,92 @@ +#ifndef GW_PROTOCOL_H +#define GW_PROTOCOL_H +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * 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 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + +/** + * @file protocol.h + * + * The listener definitions for MaxScale + * + * @verbatim + * Revision History + * + * Date Who Description + * 22/01/16 Martin Brampton Initial implementation + * + * @endverbatim + */ + +#include +#include +#include +#include +#include + +struct dcb; +struct server; +struct session; + +/** + * @verbatim + * The operations that can be performed on the descriptor + * + * read EPOLLIN handler for the socket + * write MaxScale data write entry point + * write_ready EPOLLOUT handler for the socket, indicates + * that the socket is ready to send more data + * error EPOLLERR handler for the socket + * hangup EPOLLHUP handler for the socket + * accept Accept handler for listener socket only + * connect Create a connection to the specified server + * for the session pased in + * close MaxScale close entry point for the socket + * listen Create a listener for the protocol + * auth Authentication entry point + * session Session handling entry point + * @endverbatim + * + * This forms the "module object" for protocol modules within the gateway. + * + * @see load_module + */ +typedef struct gw_protocol +{ + int (*read)(struct dcb *); + int (*write)(struct dcb *, GWBUF *); + int (*write_ready)(struct dcb *); + int (*error)(struct dcb *); + int (*hangup)(struct dcb *); + int (*accept)(struct dcb *); + int (*connect)(struct dcb *, struct server *, struct session *); + int (*close)(struct dcb *); + int (*listen)(struct dcb *, char *); + int (*auth)(struct dcb *, struct server *, struct session *, GWBUF *); + int (*session)(struct dcb *, void *); +} GWPROTOCOL; + +/** + * The GWPROTOCOL version data. The following should be updated whenever + * the GWPROTOCOL structure is changed. See the rules defined in modinfo.h + * that define how these numbers should change. + */ +#define GWPROTOCOL_VERSION {1, 0, 0} + + +#endif /* GW_PROTOCOL_H */ + diff --git a/server/include/listener.h b/server/include/listener.h new file mode 100644 index 000000000..b9dbfd37a --- /dev/null +++ b/server/include/listener.h @@ -0,0 +1,89 @@ +#ifndef _LISTENER_H +#define _LISTENER_H +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * 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 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + +/** + * @file listener.h + * + * The listener definitions for MaxScale + * + * @verbatim + * Revision History + * + * Date Who Description + * 19/01/16 Martin Brampton Initial implementation + * + * @endverbatim + */ + +#include +#include + +enum +{ + SERVICE_SSLV3, + SERVICE_TLS10, +#ifdef OPENSSL_1_0 + SERVICE_TLS11, + SERVICE_TLS12, +#endif + SERVICE_SSL_MAX, + SERVICE_TLS_MAX, + SERVICE_SSL_TLS_MAX +}; + +/** + * The ssl_listener structure is used to aggregate the SSL configuration items + * and data for a particular listener + */ +typedef struct ssl_listener +{ + SSL_CTX *ctx; + SSL_METHOD *method; /*< SSLv3 or TLS1.0/1.1/1.2 methods + * see: https://www.openssl.org/docs/ssl/SSL_CTX_new.html */ + int ssl_cert_verify_depth; /*< SSL certificate verification depth */ + int ssl_method_type; /*< Which of the SSLv3 or TLS1.0/1.1/1.2 methods to use */ + char *ssl_cert; /*< SSL certificate */ + char *ssl_key; /*< SSL private key */ + char *ssl_ca_cert; /*< SSL CA certificate */ + bool ssl_init_done; /*< If SSL has already been initialized for this service */ +} SSL_LISTENER; + +/** + * The servlistener structure is used to link a service to the protocols that + * are used to support that service. It defines the name of the protocol module + * that should be loaded to support the client connection and the port that the + * protocol should use to listen for incoming client connections. + */ +typedef struct servlistener +{ + char *protocol; /**< Protocol module to load */ + unsigned short port; /**< Port to listen on */ + char *address; /**< Address to listen with */ + char *authenticator; /**< Name of authenticator */ + SSL_LISTENER *ssl; /**< Structure of SSL data or NULL */ + DCB *listener; /**< The DCB for the listener */ + struct servlistener *next; /**< Next service protocol */ +} SERV_LISTENER; + +int listener_set_ssl_version(SSL_LISTENER *ssl_listener, char* version); +void listener_set_certificates(SSL_LISTENER *ssl_listener, char* cert, char* key, char* ca_cert); +int listener_init_SSL(SSL_LISTENER *ssl_listener); + +#endif \ No newline at end of file diff --git a/server/include/service.h b/server/include/service.h index db9d76ad0..4c8f60c9a 100644 --- a/server/include/service.h +++ b/server/include/service.h @@ -19,9 +19,11 @@ */ #include +#include #include #include #include +#include #include #include #include @@ -59,21 +61,6 @@ struct router; struct router_object; struct users; -/** - * The servprotocol structure is used to link a service to the protocols that - * are used to support that service. It defines the name of the protocol module - * that should be loaded to support the client connection and the port that the - * protocol should use to listen for incoming client connections. - */ -typedef struct servprotocol -{ - char *protocol; /**< Protocol module to load */ - unsigned short port; /**< Port to listen on */ - char *address; /**< Address to listen with */ - DCB *listener; /**< The DCB for the listener */ - struct servprotocol *next; /**< Next service protocol */ -} SERV_PROTOCOL; - /** * The service statistics structure */ @@ -120,19 +107,6 @@ typedef enum SSL_REQUIRED } ssl_mode_t; -enum -{ - SERVICE_SSLV3, - SERVICE_TLS10, -#ifdef OPENSSL_1_0 - SERVICE_TLS11, - SERVICE_TLS12, -#endif - SERVICE_SSL_MAX, - SERVICE_TLS_MAX, - SERVICE_SSL_TLS_MAX -}; - #define DEFAULT_SSL_CERT_VERIFY_DEPTH 100 /*< The default certificate verification depth */ #define SERVICE_MAX_RETRY_INTERVAL 3600 /*< The maximum interval between service start retries */ @@ -156,7 +130,7 @@ typedef struct service { char *name; /**< The service name */ int state; /**< The service state */ - SERV_PROTOCOL *ports; /**< Linked list of ports and protocols + SERV_LISTENER *ports; /**< Linked list of ports and protocols * that this service will listen on. */ char *routerModule; /**< Name of router module to use */ @@ -218,7 +192,7 @@ extern SERVICE *service_alloc(const char *, const char *); extern int service_free(SERVICE *); extern SERVICE *service_find(char *); extern int service_isvalid(SERVICE *); -extern int serviceAddProtocol(SERVICE *, char *, char *, unsigned short); +extern int serviceAddProtocol(SERVICE *, char *, char *, unsigned short, char *, SSL_LISTENER *); extern int serviceHasProtocol(SERVICE *, char *, unsigned short); extern void serviceAddBackend(SERVICE *, SERVER *); extern int serviceHasBackend(SERVICE *, SERVER *); diff --git a/server/modules/protocol/httpd.c b/server/modules/protocol/httpd.c index b574adc17..39a738978 100644 --- a/server/modules/protocol/httpd.c +++ b/server/modules/protocol/httpd.c @@ -38,6 +38,7 @@ */ #include +#include #include #include #include diff --git a/server/modules/protocol/maxscaled.c b/server/modules/protocol/maxscaled.c index 9c37e93c8..d22c4187b 100644 --- a/server/modules/protocol/maxscaled.c +++ b/server/modules/protocol/maxscaled.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index 3a5fb4e77..127c50f14 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -50,6 +50,7 @@ * */ #include +#include MODULE_INFO info = { diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 12d740d77..bee021e48 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -47,6 +47,7 @@ * 11/01/2016 Martin Brampton Remove SSL write code, now handled at lower level; * replace gwbuf_consume by gwbuf_free (multiple). */ +#include #include #include #include @@ -74,16 +75,15 @@ static int gw_MySQLWrite_client(DCB *dcb, GWBUF *queue); static int gw_error_client_event(DCB *dcb); static int gw_client_close(DCB *dcb); static int gw_client_hangup_event(DCB *dcb); -int gw_read_client_event_SSL(DCB* dcb); -int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message); -int MySQLSendHandshake(DCB* dcb); +static int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message); +static int MySQLSendHandshake(DCB* dcb); static int gw_mysql_do_authentication(DCB *dcb, GWBUF **queue); static int route_by_statement(SESSION *, GWBUF **); extern char* get_username_from_auth(char* ptr, uint8_t* data); extern int check_db_name_after_auth(DCB *, char *, int); extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db,int); -int do_ssl_accept(MySQLProtocol* protocol); +static int do_ssl_accept(MySQLProtocol* protocol); /* * The "module object" for the mysqld client protocol module. diff --git a/server/modules/protocol/telnetd.c b/server/modules/protocol/telnetd.c index d9ff6bcf3..11e325ec3 100644 --- a/server/modules/protocol/telnetd.c +++ b/server/modules/protocol/telnetd.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include diff --git a/server/modules/protocol/testprotocol.c b/server/modules/protocol/testprotocol.c index 7ceb867d9..b12488b44 100644 --- a/server/modules/protocol/testprotocol.c +++ b/server/modules/protocol/testprotocol.c @@ -33,6 +33,7 @@ #include #include #include +#include MODULE_INFO info = { From 8367d9374661f63d0b57b30f3c2bd9f50bfeef69 Mon Sep 17 00:00:00 2001 From: counterpoint Date: Wed, 27 Jan 2016 15:46:19 +0000 Subject: [PATCH 02/26] Migrate SSL functionality out of service and into listener. Develop config handling accordingly, including making provision for SSL parameters in servers for future use in implementing SSL to backend servers. Some elements still to be tidied mainly in mysql_client.c - but that will be part of detaching the SSL authentication from the MySQL protocol. --- server/core/config.c | 54 ++++--- server/core/dcb.c | 5 +- server/core/listener.c | 20 ++- server/core/service.c | 190 ++----------------------- server/include/dcb.h | 2 + server/include/listener.h | 31 +--- server/include/server.h | 1 + server/include/service.h | 1 - server/modules/protocol/httpd.c | 1 + server/modules/protocol/maxscaled.c | 1 + server/modules/protocol/mysql_client.c | 16 ++- server/modules/protocol/telnetd.c | 1 + 12 files changed, 77 insertions(+), 246 deletions(-) diff --git a/server/core/config.c b/server/core/config.c index ee4794e3c..552f815af 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -91,7 +91,7 @@ static void global_defaults(); static void feedback_defaults(); static void check_config_objects(CONFIG_CONTEXT *context); static int maxscale_getline(char** dest, int* size, FILE* file); -static SSL_LISTENER *make_ssl_structure(CONFIG_CONTEXT *obj); +static SSL_LISTENER *make_ssl_structure(CONFIG_CONTEXT *obj, bool require_cert, int *error_count); int config_truth_value(char *str); int config_get_ifaddr(unsigned char *output); @@ -195,6 +195,14 @@ static char *server_params[] = "monitorpw", "persistpoolmax", "persistmaxtime", + /* The following, or something similar, will be needed for backend SSL + "ssl_cert", + "ssl_ca_cert", + "ssl", + "ssl_key", + "ssl_version", + "ssl_cert_verify_depth", + */ NULL }; @@ -1036,16 +1044,17 @@ handle_global_item(const char *name, const char *value) /** * Form an SSL structure from listener section parameters * - * @param name The item name - * @param value The item value - * @return 0 on error + * @param obj The configuration object for the item being created + * @param require_cert Whether a certificate and key are required + * @param *error_count An error count which may be incremented + * @return SSL_LISTENER structure or NULL */ static SSL_LISTENER * -make_ssl_structure (CONFIG_CONTEXT *obj) +make_ssl_structure (CONFIG_CONTEXT *obj, bool require_cert, int *error_count) { char *ssl, *ssl_version, *ssl_cert, *ssl_key, *ssl_ca_cert, *ssl_cert_verify_depth; + int local_errors = 0; SSL_LISTENER *new_ssl; - int error_count = 0; ssl = config_get_value(obj->parameters, "ssl"); if (ssl && !strcmp(ssl, "required")) @@ -1068,7 +1077,7 @@ make_ssl_structure (CONFIG_CONTEXT *obj) { MXS_ERROR("Unknown parameter value for 'ssl_version' for" " service '%s': %s", obj->object, ssl_version); - error_count++; + local_errors++; } } @@ -1080,15 +1089,15 @@ make_ssl_structure (CONFIG_CONTEXT *obj) MXS_ERROR("Invalid parameter value for 'ssl_cert_verify_depth" " for service '%s': %s", obj->object, ssl_cert_verify_depth); new_ssl->ssl_cert_verify_depth = 0; - error_count++; + local_errors++; } } listener_set_certificates(new_ssl, ssl_cert, ssl_key, ssl_ca_cert); - if (new_ssl->ssl_cert == NULL) + if (require_cert && new_ssl->ssl_cert == NULL) { - error_count++; + local_errors++; MXS_ERROR("Server certificate missing for service '%s'." "Please provide the path to the server certificate by adding " "the ssl_cert= parameter", obj->object); @@ -1096,16 +1105,16 @@ make_ssl_structure (CONFIG_CONTEXT *obj) if (new_ssl->ssl_ca_cert == NULL) { - error_count++; + local_errors++; MXS_ERROR("CA Certificate missing for service '%s'." "Please provide the path to the certificate authority " "certificate by adding the ssl_ca_cert= parameter", obj->object); } - if (new_ssl->ssl_key == NULL) + if (require_cert && new_ssl->ssl_key == NULL) { - error_count++; + local_errors++; MXS_ERROR("Server private key missing for service '%s'. " "Please provide the path to the server certificate key by " "adding the ssl_key= parameter", @@ -1117,29 +1126,30 @@ make_ssl_structure (CONFIG_CONTEXT *obj) MXS_ERROR("Certificate authority file for service '%s' not found: %s", obj->object, new_ssl->ssl_ca_cert); - error_count++; + local_errors++; } - if (access(new_ssl->ssl_cert, F_OK) != 0) + if (require_cert && access(new_ssl->ssl_cert, F_OK) != 0) { MXS_ERROR("Server certificate file for service '%s' not found: %s", obj->object, new_ssl->ssl_cert); - error_count++; + local_errors++; } - if (access(new_ssl->ssl_key, F_OK) != 0) + if (require_cert && access(new_ssl->ssl_key, F_OK) != 0) { MXS_ERROR("Server private key file for service '%s' not found: %s", obj->object, new_ssl->ssl_key); - error_count++; + local_errors++; } - if (error_count == 0) + if (0 == local_errors) { return new_ssl; } + *error_count += local_errors; free(new_ssl); } return NULL; @@ -2325,6 +2335,7 @@ int create_new_service(CONFIG_CONTEXT *obj) } } + /* char *ssl = config_get_value(obj->parameters, "ssl"); if (ssl) { @@ -2368,6 +2379,7 @@ int create_new_service(CONFIG_CONTEXT *obj) } } } + */ /** Parameters for rwsplit router only */ if (strcmp(router, "readwritesplit")) @@ -2503,6 +2515,8 @@ int create_new_server(CONFIG_CONTEXT *obj) CONFIG_PARAMETER *params = obj->parameters; + server->server_ssl = make_ssl_structure(obj, false, &error_count); + while (params) { if (!is_normal_server_parameter(params->name)) @@ -2729,7 +2743,7 @@ int create_new_listener(CONFIG_CONTEXT *obj, bool startnow) SERVICE *service = service_find(service_name); if (service) { - SSL_LISTENER *ssl_info = make_ssl_structure(obj); + SSL_LISTENER *ssl_info = make_ssl_structure(obj, true, &error_count); if (socket) { if (serviceHasProtocol(service, protocol, 0)) diff --git a/server/core/dcb.c b/server/core/dcb.c index d836bf070..c0b6caed6 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -279,6 +279,7 @@ dcb_clone(DCB *orig) clonedcb->flags |= DCBF_CLONE; clonedcb->state = orig->state; clonedcb->data = orig->data; + clonedcb->listen_ssl = orig->listen_ssl; if (orig->remote) { clonedcb->remote = strdup(orig->remote); @@ -2828,12 +2829,12 @@ dcb_count_by_usage(DCB_USAGE usage) */ int dcb_create_SSL(DCB* dcb) { - if (serviceInitSSL(dcb->service) != 0) + if (NULL == dcb->listen_ssl || listener_init_SSL(dcb->listen_ssl) != 0) { return -1; } - if ((dcb->ssl = SSL_new(dcb->service->ctx)) == NULL) + if ((dcb->ssl = SSL_new(dcb->listen_ssl->ctx)) == NULL) { MXS_ERROR("Failed to initialize SSL for connection."); return -1; diff --git a/server/core/listener.c b/server/core/listener.c index 015f47272..8089bc19b 100644 --- a/server/core/listener.c +++ b/server/core/listener.c @@ -93,17 +93,25 @@ listener_set_ssl_version(SSL_LISTENER *ssl_listener, char* version) void listener_set_certificates(SSL_LISTENER *ssl_listener, char* cert, char* key, char* ca_cert) { - if (ssl_listener->ssl_cert) + if (NULL != cert) { - free(ssl_listener->ssl_cert); + if (ssl_listener->ssl_cert) + { + free(ssl_listener->ssl_cert); + } + ssl_listener->ssl_cert = strdup(cert); } - ssl_listener->ssl_cert = strdup(cert); + else ssl_listener->ssl_cert = NULL; - if (ssl_listener->ssl_key) + if (NULL != key) { - free(ssl_listener->ssl_key); + if (ssl_listener->ssl_key) + { + free(ssl_listener->ssl_key); + } + ssl_listener->ssl_key = strdup(key); } - ssl_listener->ssl_key = strdup(key); + else ssl_listener->ssl_key = NULL; if (ssl_listener->ssl_ca_cert) { diff --git a/server/core/service.c b/server/core/service.c index f4030e5d2..3cc5baf5e 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -69,9 +69,6 @@ #include #include -static RSA *rsa_512 = NULL; -static RSA *rsa_1024 = NULL; - /** To be used with configuration type checks */ typedef struct typelib_st { @@ -234,6 +231,8 @@ serviceStartPort(SERVICE *service, SERV_LISTENER *port) goto retblock; } + port->listener->listen_ssl = port->ssl; + if (port->ssl) { listener_init_SSL(port->ssl); @@ -474,24 +473,15 @@ serviceStart(SERVICE *service) if (check_service_permissions(service)) { - if (service->ssl_mode == SSL_DISABLED || - (service->ssl_mode != SSL_DISABLED && serviceInitSSL(service) == 0)) + if ((service->router_instance = service->router->createInstance( + service,service->routerOptions))) { - if ((service->router_instance = service->router->createInstance( - service,service->routerOptions))) - { - listeners += serviceStartAllPorts(service); - } - else - { - MXS_ERROR("%s: Failed to create router instance for service. Service not started.", - service->name); - service->state = SERVICE_STATE_FAILED; - } + listeners += serviceStartAllPorts(service); } else { - MXS_ERROR("%s: SSL initialization failed. Service not started.", service->name); + MXS_ERROR("%s: Failed to create router instance for service. Service not started.", + service->name); service->state = SERVICE_STATE_FAILED; } } @@ -2052,7 +2042,7 @@ serviceRowCallback(RESULTSET *set, void *data) } /** - * Return a resultset that has the current set of services in it + * Return a result set that has the current set of services in it * * @return A Result set */ @@ -2080,170 +2070,6 @@ serviceGetList() return set; } - -/** - * The RSA ket generation callback function for OpenSSL. - * @param s SSL structure - * @param is_export Not used - * @param keylength Length of the key - * @return Pointer to RSA structure - */ -RSA *tmp_rsa_callback(SSL *s, int is_export, int keylength) -{ - RSA *rsa_tmp=NULL; - - switch (keylength) { - case 512: - if (rsa_512) - { - rsa_tmp = rsa_512; - } - else - { - /* generate on the fly, should not happen in this example */ - rsa_tmp = RSA_generate_key(keylength,RSA_F4,NULL,NULL); - rsa_512 = rsa_tmp; /* Remember for later reuse */ - } - break; - case 1024: - if (rsa_1024) - { - rsa_tmp=rsa_1024; - } - break; - default: - /* Generating a key on the fly is very costly, so use what is there */ - if (rsa_1024) - { - rsa_tmp=rsa_1024; - } - else - { - rsa_tmp=rsa_512; /* Use at least a shorter key */ - } - } - return(rsa_tmp); -} - -/** - * Initialize the service's SSL context. This sets up the generated RSA - * encryption keys, chooses the server encryption level and configures the server - * certificate, private key and certificate authority file. - * @param service Service to initialize - * @return 0 on success, -1 on error - */ -int serviceInitSSL(SERVICE* service) -{ - DH* dh; - RSA* rsa; - - /* Pending removal in favour of processing in listener. */ - return 0; - - if (!service->ssl_init_done) - { - switch(service->ssl_method_type) - { - case SERVICE_SSLV3: - service->method = (SSL_METHOD*)SSLv3_server_method(); - break; - case SERVICE_TLS10: - service->method = (SSL_METHOD*)TLSv1_server_method(); - break; -#ifdef OPENSSL_1_0 - case SERVICE_TLS11: - service->method = (SSL_METHOD*)TLSv1_1_server_method(); - break; - case SERVICE_TLS12: - service->method = (SSL_METHOD*)TLSv1_2_server_method(); - break; -#endif - /** Rest of these use the maximum available SSL/TLS methods */ - case SERVICE_SSL_MAX: - service->method = (SSL_METHOD*)SSLv23_server_method(); - break; - case SERVICE_TLS_MAX: - service->method = (SSL_METHOD*)SSLv23_server_method(); - break; - case SERVICE_SSL_TLS_MAX: - service->method = (SSL_METHOD*)SSLv23_server_method(); - break; - default: - service->method = (SSL_METHOD*)SSLv23_server_method(); - break; - } - - if ((service->ctx = SSL_CTX_new(service->method)) == NULL) - { - MXS_ERROR("SSL context initialization failed."); - return -1; - } - - /** Enable all OpenSSL bug fixes */ - SSL_CTX_set_options(service->ctx,SSL_OP_ALL); - - /** Generate the 512-bit and 1024-bit RSA keys */ - if (rsa_512 == NULL) - { - rsa_512 = RSA_generate_key(512,RSA_F4,NULL,NULL); - if (rsa_512 == NULL) - { - MXS_ERROR("512-bit RSA key generation failed."); - return -1; - } - } - if (rsa_1024 == NULL) - { - rsa_1024 = RSA_generate_key(1024,RSA_F4,NULL,NULL); - if (rsa_1024 == NULL) - { - MXS_ERROR("1024-bit RSA key generation failed."); - return -1; - } - } - - if (rsa_512 != NULL && rsa_1024 != NULL) - { - SSL_CTX_set_tmp_rsa_callback(service->ctx,tmp_rsa_callback); - } - - /** Load the server sertificate */ - if (SSL_CTX_use_certificate_file(service->ctx, service->ssl_cert, SSL_FILETYPE_PEM) <= 0) - { - MXS_ERROR("Failed to set server SSL certificate."); - return -1; - } - - /* Load the private-key corresponding to the server certificate */ - if (SSL_CTX_use_PrivateKey_file(service->ctx, service->ssl_key, SSL_FILETYPE_PEM) <= 0) - { - MXS_ERROR("Failed to set server SSL key."); - return -1; - } - - /* Check if the server certificate and private-key matches */ - if (!SSL_CTX_check_private_key(service->ctx)) - { - MXS_ERROR("Server SSL certificate and key do not match."); - return -1; - } - - /* Load the RSA CA certificate into the SSL_CTX structure */ - if (!SSL_CTX_load_verify_locations(service->ctx, service->ssl_ca_cert, NULL)) - { - MXS_ERROR("Failed to set Certificate Authority file."); - return -1; - } - - /* Set to require peer (client) certificate verification */ - SSL_CTX_set_verify(service->ctx,SSL_VERIFY_PEER,NULL); - - /* Set the verification depth */ - SSL_CTX_set_verify_depth(service->ctx,service->ssl_cert_verify_depth); - service->ssl_init_done = true; - } - return 0; -} /** * Function called by the housekeeper thread to retry starting of a service * @param data Service to restart diff --git a/server/include/dcb.h b/server/include/dcb.h index 3d7ea4ee6..c772e9c98 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -201,6 +202,7 @@ typedef struct dcb char *protoname; /**< Name of the protocol */ void *protocol; /**< The protocol specific state */ struct session *session; /**< The owning session */ + SSL_LISTENER *listen_ssl; /**< For a client DCB, the SSL descriptor, if any */ GWPROTOCOL func; /**< The functions for this descriptor */ int writeqlen; /**< Current number of byes in the write queue */ diff --git a/server/include/listener.h b/server/include/listener.h index b9dbfd37a..023b7131d 100644 --- a/server/include/listener.h +++ b/server/include/listener.h @@ -33,38 +33,9 @@ */ #include +#include #include -enum -{ - SERVICE_SSLV3, - SERVICE_TLS10, -#ifdef OPENSSL_1_0 - SERVICE_TLS11, - SERVICE_TLS12, -#endif - SERVICE_SSL_MAX, - SERVICE_TLS_MAX, - SERVICE_SSL_TLS_MAX -}; - -/** - * The ssl_listener structure is used to aggregate the SSL configuration items - * and data for a particular listener - */ -typedef struct ssl_listener -{ - SSL_CTX *ctx; - SSL_METHOD *method; /*< SSLv3 or TLS1.0/1.1/1.2 methods - * see: https://www.openssl.org/docs/ssl/SSL_CTX_new.html */ - int ssl_cert_verify_depth; /*< SSL certificate verification depth */ - int ssl_method_type; /*< Which of the SSLv3 or TLS1.0/1.1/1.2 methods to use */ - char *ssl_cert; /*< SSL certificate */ - char *ssl_key; /*< SSL private key */ - char *ssl_ca_cert; /*< SSL CA certificate */ - bool ssl_init_done; /*< If SSL has already been initialized for this service */ -} SSL_LISTENER; - /** * The servlistener structure is used to link a service to the protocols that * are used to support that service. It defines the name of the protocol module diff --git a/server/include/server.h b/server/include/server.h index c8c884b96..1751858c9 100644 --- a/server/include/server.h +++ b/server/include/server.h @@ -89,6 +89,7 @@ typedef struct server { char *name; /**< Server name/IP address*/ unsigned short port; /**< Port to listen on */ char *protocol; /**< Protocol module to use */ + SSL_LISTENER *server_ssl; /**< SSL data structure for server, if any */ unsigned int status; /**< Status flag bitmap for the server */ char *monuser; /**< User name to use to monitor the db */ char *monpw; /**< Password to use to monitor the db */ diff --git a/server/include/service.h b/server/include/service.h index 4c8f60c9a..0d7e3d671 100644 --- a/server/include/service.h +++ b/server/include/service.h @@ -207,7 +207,6 @@ extern int serviceSetUser(SERVICE *, char *, char *); extern int serviceGetUser(SERVICE *, char **, char **); extern bool serviceSetFilters(SERVICE *, char *); extern int serviceSetSSL(SERVICE *service, char* action); -extern int serviceInitSSL(SERVICE* service); extern int serviceSetSSLVersion(SERVICE *service, char* version); extern int serviceSetSSLVerifyDepth(SERVICE* service, int depth); extern void serviceSetCertificates(SERVICE *service, char* cert,char* key, char* ca_cert); diff --git a/server/modules/protocol/httpd.c b/server/modules/protocol/httpd.c index 39a738978..6b74f3291 100644 --- a/server/modules/protocol/httpd.c +++ b/server/modules/protocol/httpd.c @@ -357,6 +357,7 @@ static int httpd_accept(DCB *dcb) if ((client = dcb_alloc(DCB_ROLE_REQUEST_HANDLER))) { + client->listen_ssl = dcb->listen_ssl; client->fd = so; client->remote = strdup(inet_ntoa(addr.sin_addr)); memcpy(&client->func, &MyObject, sizeof(GWPROTOCOL)); diff --git a/server/modules/protocol/maxscaled.c b/server/modules/protocol/maxscaled.c index d22c4187b..f07307bbd 100644 --- a/server/modules/protocol/maxscaled.c +++ b/server/modules/protocol/maxscaled.c @@ -268,6 +268,7 @@ static int maxscaled_accept(DCB *dcb) close(so); return n_connect; } + client_dcb->listen_ssl = dcb->listen_ssl; client_dcb->fd = so; client_dcb->remote = strdup(inet_ntoa(addr.sin_addr)); memcpy(&client_dcb->func, &MyObject, sizeof(GWPROTOCOL)); diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index bee021e48..81440455f 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -335,7 +335,8 @@ int MySQLSendHandshake(DCB* dcb) mysql_server_capabilities_one[0] &= ~GW_MYSQL_CAPABILITIES_COMPRESS; - if (dcb->service->ssl_mode != SSL_DISABLED) + /* if (dcb->service->ssl_mode != SSL_DISABLED) */ + if (NULL != dcb->listen_ssl) { mysql_server_capabilities_one[1] |= GW_MYSQL_CAPABILITIES_SSL >> 8; } @@ -489,7 +490,8 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf) ssl = protocol->client_capabilities & GW_MYSQL_CAPABILITIES_SSL; /** Client didn't requested SSL when SSL mode was required*/ - if (!ssl && protocol->owner_dcb->service->ssl_mode == SSL_REQUIRED) + /* if (!ssl && protocol->owner_dcb->service->ssl_mode == SSL_REQUIRED) */ + if (!ssl && NULL != protocol->owner_dcb->listen_ssl) { MXS_INFO("User %s@%s connected to service '%s' without SSL when SSL was required.", protocol->owner_dcb->user, @@ -507,7 +509,8 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf) } /** Do the SSL Handshake */ - if (ssl && protocol->owner_dcb->service->ssl_mode != SSL_DISABLED) + /* if (ssl && protocol->owner_dcb->service->ssl_mode != SSL_DISABLED) */ + if (ssl && NULL != protocol->owner_dcb->listen_ssl) { protocol->protocol_auth_state = MYSQL_AUTH_SSL_REQ; @@ -520,7 +523,8 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf) return 0; } } - else if (dcb->service->ssl_mode == SSL_ENABLED) + /* else if (dcb->service->ssl_mode == SSL_ENABLED) */ + else if (NULL != dcb->listen_ssl) { /** This is a non-SSL connection to a SSL enabled service. * We have only read enough of the packet to know that the client @@ -712,7 +716,8 @@ int gw_read_client_event(DCB* dcb) /** SSL handshake is done, communication is now encrypted with SSL */ rc = dcb_read_SSL(dcb, &read_buffer); } - else if (dcb->service->ssl_mode != SSL_DISABLED && + /* else if (dcb->service->ssl_mode != SSL_DISABLED && */ + else if (dcb->listen_ssl != NULL && protocol->protocol_auth_state == MYSQL_AUTH_SENT) { /** The service allows both SSL and non-SSL connections. @@ -1586,6 +1591,7 @@ int gw_MySQLAccept(DCB *listener) goto return_rc; } + client_dcb->listen_ssl = listener->listen_ssl; client_dcb->service = listener->session->service; client_dcb->session = session_set_dummy(client_dcb); client_dcb->fd = c_sock; diff --git a/server/modules/protocol/telnetd.c b/server/modules/protocol/telnetd.c index 11e325ec3..e8b29c383 100644 --- a/server/modules/protocol/telnetd.c +++ b/server/modules/protocol/telnetd.c @@ -297,6 +297,7 @@ static int telnetd_accept(DCB *dcb) close(so); return n_connect; } + client_dcb->listen_ssl = dcb->listen_ssl; client_dcb->fd = so; client_dcb->remote = strdup(inet_ntoa(addr.sin_addr)); memcpy(&client_dcb->func, &MyObject, sizeof(GWPROTOCOL)); From 05fa498dc0734a6d8ac44e4d000db6badc7e65de Mon Sep 17 00:00:00 2001 From: counterpoint Date: Wed, 27 Jan 2016 15:46:50 +0000 Subject: [PATCH 03/26] Add an SSL header needed to support extra data in DCB. --- server/include/gw_ssl.h | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 server/include/gw_ssl.h diff --git a/server/include/gw_ssl.h b/server/include/gw_ssl.h new file mode 100644 index 000000000..3fe6ee95a --- /dev/null +++ b/server/include/gw_ssl.h @@ -0,0 +1,67 @@ +#ifndef _GW_SSL_H +#define _GW_SSL_H +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * 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 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + +/** + * @file gw_ssl.h + * + * The SSL definitions for MaxScale + * + * @verbatim + * Revision History + * + * Date Who Description + * 27/01/16 Martin Brampton Initial implementation + * + * @endverbatim + */ + +#include + +enum +{ + SERVICE_SSLV3, + SERVICE_TLS10, +#ifdef OPENSSL_1_0 + SERVICE_TLS11, + SERVICE_TLS12, +#endif + SERVICE_SSL_MAX, + SERVICE_TLS_MAX, + SERVICE_SSL_TLS_MAX +}; + +/** + * The ssl_listener structure is used to aggregate the SSL configuration items + * and data for a particular listener + */ +typedef struct ssl_listener +{ + SSL_CTX *ctx; + SSL_METHOD *method; /*< SSLv3 or TLS1.0/1.1/1.2 methods + * see: https://www.openssl.org/docs/ssl/SSL_CTX_new.html */ + int ssl_cert_verify_depth; /*< SSL certificate verification depth */ + int ssl_method_type; /*< Which of the SSLv3 or TLS1.0/1.1/1.2 methods to use */ + char *ssl_cert; /*< SSL certificate */ + char *ssl_key; /*< SSL private key */ + char *ssl_ca_cert; /*< SSL CA certificate */ + bool ssl_init_done; /*< If SSL has already been initialized for this service */ +} SSL_LISTENER; + +#endif \ No newline at end of file From 971d1d5de2a32de7304cc64b9fbb3d14d3e5e743 Mon Sep 17 00:00:00 2001 From: counterpoint Date: Wed, 3 Feb 2016 08:54:55 +0000 Subject: [PATCH 04/26] Redesign logic of SSL connections to work with status in client DCB instead of in the protocol. This eases the way to moving SSL logic out of a specific protocol (currently MySQL) so as to be available across any protocol. Also, some simplification. --- server/core/dcb.c | 30 ++- server/include/dcb.h | 13 + .../include/mysql_client_server_protocol.h | 66 ++--- server/modules/protocol/mysql_client.c | 230 +++--------------- 4 files changed, 106 insertions(+), 233 deletions(-) diff --git a/server/core/dcb.c b/server/core/dcb.c index c0b6caed6..39754fd0a 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -223,6 +223,9 @@ dcb_alloc(dcb_role_t role) newdcb->callbacks = NULL; newdcb->data = NULL; + newdcb->listen_ssl = NULL; + newdcb->ssl_state = SSL_HANDSHAKE_UNKNOWN; + newdcb->remote = NULL; newdcb->user = NULL; newdcb->flags = 0; @@ -280,6 +283,7 @@ dcb_clone(DCB *orig) clonedcb->state = orig->state; clonedcb->data = orig->data; clonedcb->listen_ssl = orig->listen_ssl; + clonedcb->ssl_state = orig->ssl_state; if (orig->remote) { clonedcb->remote = strdup(orig->remote); @@ -2863,42 +2867,56 @@ int dcb_create_SSL(DCB* dcb) int dcb_accept_SSL(DCB* dcb) { int ssl_rval; + char *remote; + char *user; + + if (dcb->ssl == NULL && dcb_create_SSL(dcb) != 0) + { + return -1; + } + + remote = dcb->remote ? dcb->remote : ""; + user = dcb->user ? dcb->user : ""; ssl_rval = SSL_accept(dcb->ssl); + switch (SSL_get_error(dcb->ssl, ssl_rval)) { case SSL_ERROR_NONE: - MXS_DEBUG("SSL_accept done for %s", dcb->remote); + MXS_DEBUG("SSL_accept done for %s@%s", user, remote); + dcb->ssl_state = SSL_HANDSHAKE_DONE; return 1; break; case SSL_ERROR_WANT_READ: - MXS_DEBUG("SSL_accept ongoing want read for %s", dcb->remote); + MXS_DEBUG("SSL_accept ongoing want read for %s@%s", user, remote); return 0; break; case SSL_ERROR_WANT_WRITE: - MXS_DEBUG("SSL_accept ongoing want write for %s", dcb->remote); + MXS_DEBUG("SSL_accept ongoing want write for %s@%s", user, remote); return 0; break; case SSL_ERROR_ZERO_RETURN: - MXS_DEBUG("SSL error, shut down cleanly during SSL accept %s", dcb->remote); + MXS_DEBUG("SSL error, shut down cleanly during SSL accept %s@%s", user, remote); dcb_log_errors_SSL(dcb, __func__, 0); poll_fake_hangup_event(dcb); return 0; break; case SSL_ERROR_SYSCALL: - MXS_DEBUG("SSL connection SSL_ERROR_SYSCALL error during accept %s", dcb->remote); + MXS_DEBUG("SSL connection SSL_ERROR_SYSCALL error during accept %s@%s", user, remote); dcb_log_errors_SSL(dcb, __func__, ssl_rval); + dcb->ssl_state = SSL_HANDSHAKE_FAILED; poll_fake_hangup_event(dcb); return -1; break; default: - MXS_DEBUG("SSL connection shut down with error during SSL accept %s", dcb->remote); + MXS_DEBUG("SSL connection shut down with error during SSL accept %s@%s", user, remote); dcb_log_errors_SSL(dcb, __func__, 0); + dcb->ssl_state = SSL_HANDSHAKE_FAILED; poll_fake_hangup_event(dcb); return -1; break; diff --git a/server/include/dcb.h b/server/include/dcb.h index c772e9c98..2818814f6 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -59,6 +59,7 @@ struct service; * 23/09/2014 Mark Riddoch New poll processing queue * 19/06/2015 Martin Brampton Provision of persistent connections * 20/01/2016 Martin Brampton Moved GWPROTOCOL to gw_protocol.h + * 01/02/2016 Martin Brampton Added fields for SSL and authentication * * @endverbatim */ @@ -173,6 +174,17 @@ typedef struct dcb_callback struct dcb_callback *next; /*< Next callback for this DCB */ } DCB_CALLBACK; +/** + * State of SSL connection + */ +typedef enum +{ + SSL_HANDSHAKE_UNKNOWN, /*< The DCB has unknown SSL status */ + SSL_HANDSHAKE_REQUIRED, /*< SSL handshake is needed */ + SSL_HANDSHAKE_DONE, /*< The SSL handshake completed OK */ + SSL_ESTABLISHED, /*< The SSL connection is in use */ + SSL_HANDSHAKE_FAILED /*< The SSL handshake failed */ +} SSL_STATE; /** * Descriptor Control Block @@ -195,6 +207,7 @@ typedef struct dcb DCBEVENTQ evq; /**< The event queue for this DCB */ int fd; /**< The descriptor */ dcb_state_t state; /**< Current descriptor state */ + SSL_STATE ssl_state; /**< Current state of SSL if in use */ int flags; /**< DCB flags */ char *remote; /**< Address of remote end */ char *user; /**< User name for connection */ diff --git a/server/modules/include/mysql_client_server_protocol.h b/server/modules/include/mysql_client_server_protocol.h index ffb04cfe0..22fad2b9e 100644 --- a/server/modules/include/mysql_client_server_protocol.h +++ b/server/modules/include/mysql_client_server_protocol.h @@ -96,13 +96,19 @@ struct dcb; #define MYSQL_FAILED_AUTH_SSL 3 typedef enum { - MYSQL_ALLOC, + MYSQL_ALLOC, /* Initial state of protocol auth state */ + /* The following are used only for backend connections */ MYSQL_PENDING_CONNECT, MYSQL_CONNECTED, + /* The following can be used for either client or backend */ + /* The comments have only been checked for client use at present */ MYSQL_AUTH_SENT, - MYSQL_AUTH_RECV, - MYSQL_AUTH_FAILED, + MYSQL_AUTH_RECV, /* This is only ever a transient value */ + MYSQL_AUTH_FAILED, /* Once this is set, the connection */ + /* will be ended, so this is transient */ + /* The following is used only for backend connections */ MYSQL_HANDSHAKE_FAILED, + /* The following are obsolete and will be removed */ MYSQL_AUTH_SSL_REQ, /*< client requested SSL but SSL_accept hasn't beed called */ MYSQL_AUTH_SSL_HANDSHAKE_DONE, /*< SSL handshake has been fully completed */ MYSQL_AUTH_SSL_HANDSHAKE_FAILED, /*< SSL handshake failed for any reason */ @@ -232,38 +238,38 @@ typedef enum mysql_server_cmd { MYSQL_COM_INIT_DB, MYSQL_COM_QUERY, MYSQL_COM_FIELD_LIST, - MYSQL_COM_CREATE_DB, + MYSQL_COM_CREATE_DB, MYSQL_COM_DROP_DB, - MYSQL_COM_REFRESH, - MYSQL_COM_SHUTDOWN, + MYSQL_COM_REFRESH, + MYSQL_COM_SHUTDOWN, MYSQL_COM_STATISTICS, - MYSQL_COM_PROCESS_INFO, - MYSQL_COM_CONNECT, - MYSQL_COM_PROCESS_KILL, - MYSQL_COM_DEBUG, + MYSQL_COM_PROCESS_INFO, + MYSQL_COM_CONNECT, + MYSQL_COM_PROCESS_KILL, + MYSQL_COM_DEBUG, MYSQL_COM_PING, - MYSQL_COM_TIME, - MYSQL_COM_DELAYED_INSERT, - MYSQL_COM_CHANGE_USER, + MYSQL_COM_TIME, + MYSQL_COM_DELAYED_INSERT, + MYSQL_COM_CHANGE_USER, MYSQL_COM_BINLOG_DUMP, - MYSQL_COM_TABLE_DUMP, - MYSQL_COM_CONNECT_OUT, + MYSQL_COM_TABLE_DUMP, + MYSQL_COM_CONNECT_OUT, MYSQL_COM_REGISTER_SLAVE, - MYSQL_COM_STMT_PREPARE, - MYSQL_COM_STMT_EXECUTE, - MYSQL_COM_STMT_SEND_LONG_DATA, + MYSQL_COM_STMT_PREPARE, + MYSQL_COM_STMT_EXECUTE, + MYSQL_COM_STMT_SEND_LONG_DATA, MYSQL_COM_STMT_CLOSE, - MYSQL_COM_STMT_RESET, - MYSQL_COM_SET_OPTION, - MYSQL_COM_STMT_FETCH, + MYSQL_COM_STMT_RESET, + MYSQL_COM_SET_OPTION, + MYSQL_COM_STMT_FETCH, MYSQL_COM_DAEMON, MYSQL_COM_END /*< Must be the last */ } mysql_server_cmd_t; -/** +/** * List of server commands, and number of response packets are stored here. - * server_command_t is used in MySQLProtocol structure, so for each DCB there is + * server_command_t is used in MySQLProtocol structure, so for each DCB there is * one MySQLProtocol and one server command list. */ typedef struct server_command_st { @@ -275,8 +281,8 @@ typedef struct server_command_st { /** * MySQL Protocol specific state data. - * - * Protocol carries information from client side to backend side, such as + * + * Protocol carries information from client side to backend side, such as * MySQL session command information and history of earlier session commands. */ typedef struct { @@ -286,7 +292,7 @@ typedef struct { int fd; /*< The socket descriptor */ struct dcb *owner_dcb; /*< The DCB of the socket * we are running on */ - SPINLOCK protocol_lock; + SPINLOCK protocol_lock; server_command_t protocol_command; /*< session command list */ server_command_t* protocol_cmd_history; /*< session command history */ mysql_auth_state_t protocol_auth_state; /*< Authentication status */ @@ -346,7 +352,7 @@ int mysql_send_custom_error ( const char* mysql_message); GWBUF* mysql_create_custom_error( - int packet_number, + int packet_number, int affected_rows, const char* msg); @@ -411,9 +417,9 @@ void protocol_archive_srv_command(MySQLProtocol* p); void init_response_status ( - GWBUF* buf, - mysql_server_cmd_t cmd, - int* npackets, + GWBUF* buf, + mysql_server_cmd_t cmd, + int* npackets, ssize_t* nbytes); #endif /** _MYSQL_PROTOCOL_H */ diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 81440455f..7c8db1914 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -77,14 +77,12 @@ static int gw_client_close(DCB *dcb); static int gw_client_hangup_event(DCB *dcb); static int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message); static int MySQLSendHandshake(DCB* dcb); -static int gw_mysql_do_authentication(DCB *dcb, GWBUF **queue); +static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf); static int route_by_statement(SESSION *, GWBUF **); extern char* get_username_from_auth(char* ptr, uint8_t* data); extern int check_db_name_after_auth(DCB *, char *, int); extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db,int); -static int do_ssl_accept(MySQLProtocol* protocol); - /* * The "module object" for the mysqld client protocol module. */ @@ -485,7 +483,7 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf) /** Skip this if the SSL handshake is already done. * If not, start the SSL handshake. */ - if (protocol->protocol_auth_state != MYSQL_AUTH_SSL_HANDSHAKE_DONE) + if (protocol->owner_dcb->ssl_state != SSL_HANDSHAKE_DONE && protocol->owner_dcb->ssl_state != SSL_ESTABLISHED) { ssl = protocol->client_capabilities & GW_MYSQL_CAPABILITIES_SSL; @@ -510,11 +508,13 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf) /** Do the SSL Handshake */ /* if (ssl && protocol->owner_dcb->service->ssl_mode != SSL_DISABLED) */ - if (ssl && NULL != protocol->owner_dcb->listen_ssl) + if (NULL != protocol->owner_dcb->listen_ssl) { - protocol->protocol_auth_state = MYSQL_AUTH_SSL_REQ; - - if (do_ssl_accept(protocol) < 0) + if (SSL_HANDSHAKE_UNKNOWN == protocol->owner_dcb->ssl_state) + { + protocol->owner_dcb->ssl_state = SSL_HANDSHAKE_REQUIRED; + } + if (dcb_accept_SSL(protocol->owner_dcb) < 0) { return MYSQL_FAILED_AUTH; } @@ -611,7 +611,7 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf) } } - /* on succesful auth set user into dcb field */ + /* on successful auth set user into dcb field */ if (auth_ret == 0) { dcb->user = strdup(client_data->user); @@ -679,13 +679,11 @@ int gw_read_client_event(DCB* dcb) #endif - /** SSL authentication is still going on, we need to call do_ssl_accept + /** SSL authentication is still going on, we need to call dcb_accept_SSL * until it return 1 for success or -1 for error */ - if (protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_ONGOING || - protocol->protocol_auth_state == MYSQL_AUTH_SSL_REQ) + if (protocol->owner_dcb->ssl_state == SSL_HANDSHAKE_REQUIRED) { - - switch(do_ssl_accept(protocol)) + switch(dcb_accept_SSL(protocol->owner_dcb)) { case 0: return 0; @@ -711,7 +709,7 @@ int gw_read_client_event(DCB* dcb) } } - if (protocol->use_ssl) + if (SSL_HANDSHAKE_DONE == protocol->owner_dcb->ssl_state || SSL_ESTABLISHED == protocol->owner_dcb->ssl_state) { /** SSL handshake is done, communication is now encrypted with SSL */ rc = dcb_read_SSL(dcb, &read_buffer); @@ -837,21 +835,28 @@ int gw_read_client_event(DCB* dcb) { case MYSQL_AUTH_SENT: { - int auth_val; + int auth_val, packet_number; + packet_number = protocol->owner_dcb->listen_ssl ? 3 : 2; auth_val = gw_mysql_do_authentication(dcb, &read_buffer); - if (protocol->protocol_auth_state == MYSQL_AUTH_SSL_REQ || - protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_ONGOING || - protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_DONE || - protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_FAILED) + if (protocol->owner_dcb->ssl_state == SSL_HANDSHAKE_REQUIRED || + protocol->owner_dcb->ssl_state == SSL_HANDSHAKE_DONE || + protocol->owner_dcb->ssl_state == SSL_HANDSHAKE_FAILED) { /** SSL was requested and the handshake is either done or * still ongoing. After the handshake is done, the client * will send another auth packet. */ - gwbuf_free(read_buffer); - read_buffer = NULL; - break; + if (protocol->owner_dcb->ssl_state == SSL_HANDSHAKE_DONE) + { + protocol->owner_dcb->ssl_state = SSL_ESTABLISHED; + } + else + { + gwbuf_free(read_buffer); + read_buffer = NULL; + break; + } } if (auth_val == 0) @@ -875,9 +880,9 @@ int gw_read_client_event(DCB* dcb) protocol->protocol_auth_state = MYSQL_IDLE; /** * Send an AUTH_OK packet to the client, - * packet sequence is # 2 + * packet sequence is # packet_number */ - mysql_send_ok(dcb, 2, 0, NULL); + mysql_send_ok(dcb, packet_number, 0, NULL); } else { @@ -890,7 +895,7 @@ int gw_read_client_event(DCB* dcb) /** Send ERR 1045 to client */ mysql_send_auth_error(dcb, - 2, + packet_number, 0, "failed to create new session"); @@ -912,7 +917,7 @@ int gw_read_client_event(DCB* dcb) snprintf(fail_str, message_len, "Unknown database '%s'", (char*)((MYSQL_session *)dcb->data)->db); - modutil_send_mysql_err_packet(dcb, 2, 0, 1049, "42000", fail_str); + modutil_send_mysql_err_packet(dcb, packet_number, 0, 1049, "42000", fail_str); } else { @@ -921,108 +926,7 @@ int gw_read_client_event(DCB* dcb) dcb->remote, (char*)((MYSQL_session *)dcb->data)->client_sha1, (char*)((MYSQL_session *)dcb->data)->db,auth_val); - modutil_send_mysql_err_packet(dcb, 2, 0, 1045, "28000", fail_str); - } - if (fail_str) - { - free(fail_str); - } - - MXS_DEBUG("%lu [gw_read_client_event] after " - "gw_mysql_do_authentication, fd %d, " - "state = MYSQL_AUTH_FAILED.", - pthread_self(), - protocol->owner_dcb->fd); - /** - * Release MYSQL_session since it is not used anymore. - */ - if (!DCB_IS_CLONE(dcb)) - { - free(dcb->data); - } - dcb->data = NULL; - - dcb_close(dcb); - } - gwbuf_free(read_buffer); - read_buffer = NULL; - } - break; - - case MYSQL_AUTH_SSL_HANDSHAKE_DONE: - { - int auth_val; - - auth_val = gw_mysql_do_authentication(dcb, &read_buffer); - - if (auth_val == 0) - { - SESSION *session; - - protocol->protocol_auth_state = MYSQL_AUTH_RECV; - /** - * Create session, and a router session for it. - * If successful, there will be backend connection(s) - * after this point. - */ - session = session_alloc(dcb->service, dcb); - - if (session != NULL) - { - CHK_SESSION(session); - ss_dassert(session->state != SESSION_STATE_ALLOC && - session->state != SESSION_STATE_DUMMY); - - protocol->protocol_auth_state = MYSQL_IDLE; - /** - * Send an AUTH_OK packet to the client, - * packet sequence is # 2 - */ - mysql_send_ok(dcb, 3, 0, NULL); - } - else - { - protocol->protocol_auth_state = MYSQL_AUTH_FAILED; - MXS_DEBUG("%lu [gw_read_client_event] session " - "creation failed. fd %d, " - "state = MYSQL_AUTH_FAILED.", - pthread_self(), - protocol->owner_dcb->fd); - - /** Send ERR 1045 to client */ - mysql_send_auth_error(dcb, - 3, - 0, - "failed to create new session"); - - dcb_close(dcb); - } - } - else - { - char* fail_str = NULL; - - protocol->protocol_auth_state = MYSQL_AUTH_FAILED; - - if (auth_val == 2) - { - /** Send error 1049 to client */ - int message_len = 25 + MYSQL_DATABASE_MAXLEN; - - fail_str = calloc(1, message_len+1); - snprintf(fail_str, message_len, "Unknown database '%s'", - (char*)((MYSQL_session *)dcb->data)->db); - - modutil_send_mysql_err_packet(dcb, 3, 0, 1049, "42000", fail_str); - } - else - { - /** Send error 1045 to client */ - fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user, - dcb->remote, - (char*)((MYSQL_session *)dcb->data)->client_sha1, - (char*)((MYSQL_session *)dcb->data)->db,auth_val); - modutil_send_mysql_err_packet(dcb, 3, 0, 1045, "28000", fail_str); + modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str); } if (fail_str) { @@ -1875,71 +1779,3 @@ static int route_by_statement(SESSION* session, GWBUF** p_readbuf) return_rc: return rc; } - -/** - * Do the SSL authentication handshake. - * This creates the DCB SSL structure if one has not been created and starts the - * SSL handshake handling. - * @param protocol Protocol to connect with SSL - * @return 1 on success, 0 when the handshake is ongoing or -1 on error - */ -int do_ssl_accept(MySQLProtocol* protocol) -{ - int rval,errnum; - char errbuf[2014]; - DCB* dcb = protocol->owner_dcb; - if (dcb->ssl == NULL) - { - if (dcb_create_SSL(dcb) != 0) - { - return -1; - } - } - - rval = dcb_accept_SSL(dcb); - - switch(rval) - { - case 0: - /** Not all of the data has been read. Go back to the poll - queue and wait for more.*/ - - rval = 0; - MXS_INFO("SSL_accept ongoing for %s@%s", - protocol->owner_dcb->user, - protocol->owner_dcb->remote); - return 0; - break; - case 1: - spinlock_acquire(&protocol->protocol_lock); - protocol->protocol_auth_state = MYSQL_AUTH_SSL_HANDSHAKE_DONE; - protocol->use_ssl = true; - spinlock_release(&protocol->protocol_lock); - - rval = 1; - - MXS_INFO("SSL_accept done for %s@%s", - protocol->owner_dcb->user, - protocol->owner_dcb->remote); - break; - - case -1: - spinlock_acquire(&protocol->protocol_lock); - protocol->protocol_auth_state = MYSQL_AUTH_SSL_HANDSHAKE_FAILED; - spinlock_release(&protocol->protocol_lock); - rval = -1; - MXS_ERROR("Fatal error in SSL_accept for %s", - protocol->owner_dcb->remote); - break; - - default: - MXS_ERROR("Fatal error in SSL_accept, returned value was %d.", rval); - break; - } -#ifdef SS_DEBUG - MXS_DEBUG("[do_ssl_accept] Protocol state: %s", - gw_mysql_protocol_state2string(protocol->protocol_auth_state)); -#endif - - return rval; -} From 866e91c0883ac46bb53009539d767a37ac0ab5c9 Mon Sep 17 00:00:00 2001 From: counterpoint Date: Mon, 15 Feb 2016 09:29:07 +0000 Subject: [PATCH 05/26] Changes to improve robustness of SSL processing, separate it so far as possible from protocols. Separate the detailed mechanism of authentication from the MySQL protocol with a view to making it into a module later. --- .../Getting-Started/Configuration-Guide.md | 117 ++-- server/core/CMakeLists.txt | 2 +- server/core/config.c | 3 + server/core/dcb.c | 38 +- server/core/doxygen.c | 82 +++ server/core/gw_ssl.c | 188 +++++ server/core/listener.c | 1 + server/core/poll.c | 15 +- server/include/dcb.h | 3 - server/include/gw_ssl.h | 17 +- server/modules/include/mysql_auth.h | 48 ++ .../include/mysql_client_server_protocol.h | 19 +- server/modules/protocol/CMakeLists.txt | 4 +- server/modules/protocol/mysql_auth.c | 547 +++++++++++++++ server/modules/protocol/mysql_backend.c | 1 + server/modules/protocol/mysql_client.c | 661 ++++++------------ server/modules/protocol/mysql_common.c | 213 +----- 17 files changed, 1218 insertions(+), 741 deletions(-) create mode 100644 server/core/doxygen.c create mode 100644 server/core/gw_ssl.c create mode 100644 server/modules/include/mysql_auth.h create mode 100644 server/modules/protocol/mysql_auth.c diff --git a/Documentation/Getting-Started/Configuration-Guide.md b/Documentation/Getting-Started/Configuration-Guide.md index b8d836477..68dcf2a0d 100644 --- a/Documentation/Getting-Started/Configuration-Guide.md +++ b/Documentation/Getting-Started/Configuration-Guide.md @@ -9,9 +9,9 @@ The purpose of this document is to describe how to configure MaxScale and to dis * [Configuration](#configuration) * [Global Settings](#global-settings) * [Service](#service) - * [Service and SSL](#service-and-ssl) * [Server](#server) * [Listener](#listener) + * [Listener and SSL](#listener-and-ssl) * [Router Modules](#routing-modules) * [Diagnostic Modules](#diagnostic-modules) * [Monitor Modules](#monitor-modules) @@ -476,62 +476,6 @@ Example: connection_timeout=300 ``` -### Service and SSL - -This section describes configuration parameters for services that control the SSL/TLS encryption method and the various certificate files involved in it. To enable SSL, you must configure the `ssl` parameter with either `enabled` or `required` and provide the three files for `ssl_cert`, `ssl_key` and `ssl_ca_cert`. After this, MySQL connections to this service can be encrypted with SSL. - -#### `ssl` - -This enables SSL connections to the service. If this parameter is set to either `required` or `enabled` and the three certificate files can be found (these are explained afterwards), then client connections will be encrypted with SSL. If the parameter is `enabled` then both SSL and non-SSL connections can connect to this service. If the parameter is set to `required` then only SSL connections can be used for this service and non-SSL connections will get an error when they try to connect to the service. - -#### `ssl_key` - -The SSL private key the service should use. This will be the private key that is used as the server side private key during a client-server SSL handshake. This is a required parameter for SSL enabled services. - -#### `ssl_cert` - -The SSL certificate the service should use. This will be the public certificate that is used as the server side certificate during a client-server SSL handshake. This is a required parameter for SSL enabled services. - -#### `ssl_ca_cert` - -This is the Certificate Authority file. It will be used to verify that both the client and the server certificates are valid. This is a required parameter for SSL enabled services. - -### `ssl_version` - -This parameter controls the level of encryption used. Accepted values are: - * SSLv3 - * TLSv10 - * TLSv11 - * TLSv12 - * MAX - -### `ssl_cert_verification_depth` - -The maximum length of the certificate authority chain that will be accepted. Accepted values are positive integers. - -``` -# Example -ssl_cert_verification_depth=10 -``` - -Example SSL enabled service configuration: - -``` -[ReadWriteSplitService] -type=service -router=readwritesplit -servers=server1,server2,server3 -user=myuser -passwd=mypasswd -ssl=required -ssl_cert=/home/markus/certs/server-cert.pem -ssl_key=/home/markus/certs/server-key.pem -ssl_ca_cert=/home/markus/certs/ca.pem -ssl_version=TLSv12 -``` - -This configuration requires all connections to be encrypted with SSL. It also specifies that TLSv1.2 should be used as the encryption method. The paths to the server certificate files and the Certificate Authority file are also provided. - ### Server Server sections are used to define the backend database servers that can be formed into a service. A server may be a member of one or more services within MaxScale. Servers are identified by a server name which is the section name in the configuration file. Servers have a type parameter of server, plus address port and protocol parameters. @@ -651,6 +595,65 @@ The protocol used used by the maxadmin client application in order to connect to This protocol module is currently still under development, it provides a means to create HTTP connections to MaxScale for use by web browsers or RESTful API clients. +### Listener and SSL + +This section describes configuration parameters for listeners that control the SSL/TLS encryption method and the various certificate files involved in it. To enable SSL, you must configure the `ssl` parameter to the value `required` and provide the three files for `ssl_cert`, `ssl_key` and `ssl_ca_cert`. After this, MySQL connections to this listener will be encrypted with SSL. Attempts to connect to the listener with a non-SSL client will fail. Note that the same service can have an SSL listener and a non-SSL listener if you wish, although they must be on different ports. + +#### `ssl` + +This enables SSL connections to the listener, when set to `required`. If that is done, the three certificate files mentioned below must also be supplied. Client connections to this listener will then be encrypted with SSL. Non-SSL connections will get an error when they try to connect to the listener. + +#### `ssl_key` + +A string giving a file path that identifies an existing readable file. The file must be the SSL private key the listener should use. This will be the private key that is used as the server side private key during a client-server SSL handshake. This is a required parameter for SSL enabled listeners. + +#### `ssl_cert` + +A string giving a file path that identifies an existing readable file. The file must be the SSL certificate the listener should use. This will be the public certificate that is used as the server side certificate during a client-server SSL handshake. This is a required parameter for SSL enabled listeners. The certificate must be compatible with the key defined above. + +#### `ssl_ca_cert` + +A string giving a file path that identifies an existing readable file. The file must be the SSL Certificate Authority (CA) certificate for the CA that signed the server certificate referred to in the previous parameter. It will be used to verify that the server certificate is valid. This is a required parameter for SSL enabled listeners. + +#### `ssl_version` + +This parameter controls the level of encryption used. Accepted values are: + * SSLv3 + * TLSv10 + * TLSv11 + * TLSv12 + * MAX + +#### `ssl_cert_verification_depth` + +The maximum length of the certificate authority chain that will be accepted. Legal values are positive integers. Note that if the client is to submit an SSL certificate, the `ssl_cert_verification_depth` parameter must not be 0. If no value is specified, the default is 9. + +``` +# Example +ssl_cert_verification_depth=5 +``` + +**Example SSL enabled listener configuration:** + +``` +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +address=10.131.218.83 +port=3306 +authenticator=MySQL +ssl=required +ssl_cert=/usr/local/mariadb/maxscale/ssl/crt.maxscale.pem +ssl_key=/usr/local/mariadb/maxscale/ssl/key.csr.maxscale.pem +ssl_ca_cert=/usr/local/mariadb/maxscale/ssl/crt.ca.maxscale.pem +ssl_version=TLSv12 +ssl_cert_verify_depth=9 +``` + +This example configuration requires all connections to be encrypted with SSL. It also specifies that TLSv1.2 should be used as the encryption method. The paths to the server certificate files and the Certificate Authority file are also provided. + + ## Routing Modules The main task of MaxScale is to accept database connections from client applications and route the connections or the statements sent over those connections to the various services supported by MaxScale. diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index 26f4123ab..75ad8def8 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(maxscale-common SHARED adminusers.c atomic.c buffer.c config.c dbusers.c dcb.c filter.c externcmd.c gwbitmask.c gwdirs.c gw_utils.c hashtable.c hint.c housekeeper.c listener.c load_utils.c maxscale_pcre2.c memlog.c modutil.c monitor.c poll.c random_jkiss.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c ${CMAKE_SOURCE_DIR}/log_manager/log_manager.cc ${CMAKE_SOURCE_DIR}/utils/skygw_utils.cc) +add_library(maxscale-common SHARED adminusers.c atomic.c buffer.c config.c dbusers.c dcb.c filter.c externcmd.c gwbitmask.c gwdirs.c gw_ssl.c gw_utils.c hashtable.c hint.c housekeeper.c listener.c load_utils.c maxscale_pcre2.c memlog.c modutil.c monitor.c poll.c random_jkiss.c resultset.c secrets.c server.c service.c session.c spinlock.c thread.c users.c utils.c ${CMAKE_SOURCE_DIR}/log_manager/log_manager.cc ${CMAKE_SOURCE_DIR}/utils/skygw_utils.cc) target_link_libraries(maxscale-common ${EMBEDDED_LIB} ${LZMA_LINK_FLAGS} ${PCRE2_LIBRARIES} ${PCRE_LINK_FLAGS} ${CURL_LIBRARIES} ssl aio pthread crypt dl crypto inih z rt m stdc++) diff --git a/server/core/config.c b/server/core/config.c index 552f815af..a87cc918d 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -133,12 +133,14 @@ static char *service_params[] = "version_string", "filters", "weightby", + /* These should no longer be required "ssl_cert", "ssl_ca_cert", "ssl", "ssl_key", "ssl_version", "ssl_cert_verify_depth", + * */ "ignore_databases", "ignore_databases_regex", "log_auth_warnings", @@ -1092,6 +1094,7 @@ make_ssl_structure (CONFIG_CONTEXT *obj, bool require_cert, int *error_count) local_errors++; } } + else new_ssl->ssl_cert_verify_depth = 9; listener_set_certificates(new_ssl, ssl_cert, ssl_key, ssl_ca_cert); diff --git a/server/core/dcb.c b/server/core/dcb.c index 39754fd0a..69bc87616 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -61,6 +61,8 @@ * 17/10/2015 Martin Brampton Add hangup for each and bitmask display MaxAdmin * 15/12/2015 Martin Brampton Merge most of SSL write code into non-SSL, * enhance SSL code + * 07/02/2016 Martin Brampton Make dcb_read_SSL & dcb_create_SSL internal, + * further small SSL logic changes * * @endverbatim */ @@ -111,6 +113,8 @@ static bool dcb_maybe_add_persistent(DCB *); static inline bool dcb_write_parameter_check(DCB *dcb, GWBUF *queue); static int dcb_bytes_readable(DCB *dcb); static int dcb_read_no_bytes_available(DCB *dcb, int nreadtotal); +static int dcb_create_SSL(DCB* dcb); +static int dcb_read_SSL(DCB *dcb, GWBUF **head); static GWBUF *dcb_basic_read(DCB *dcb, int bytesavailable, int maxbytes, int nreadtotal, int *nsingleread); static GWBUF *dcb_basic_read_SSL(DCB *dcb, int *nsingleread); #if defined(FAKE_CODE) @@ -118,7 +122,6 @@ static inline void dcb_write_fake_code(DCB *dcb); #endif static void dcb_log_write_failure(DCB *dcb, GWBUF *queue, int eno); static inline void dcb_write_tidy_up(DCB *dcb, bool below_water); -static void dcb_log_ssl_read_error(DCB *dcb, int ssl_errno, int rc); static int gw_write(DCB *dcb, bool *stop_writing); static int gw_write_SSL(DCB *dcb, bool *stop_writing); static void dcb_log_errors_SSL (DCB *dcb, const char *called_by, int ret); @@ -853,6 +856,11 @@ int dcb_read(DCB *dcb, int nsingleread = 0; int nreadtotal = 0; + if (SSL_HANDSHAKE_DONE == dcb->ssl_state || SSL_ESTABLISHED == dcb->ssl_state) + { + return dcb_read_SSL(dcb, head); + } + CHK_DCB(dcb); if (dcb->fd <= 0) @@ -1048,7 +1056,7 @@ dcb_basic_read(DCB *dcb, int bytesavailable, int maxbytes, int nreadtotal, int * * @param head Pointer to linked list to append data to * @return -1 on error, otherwise the total number of bytes read */ -int +static int dcb_read_SSL(DCB *dcb, GWBUF **head) { GWBUF *buffer = NULL; @@ -1075,7 +1083,7 @@ dcb_read_SSL(DCB *dcb, GWBUF **head) nreadtotal += nsingleread; *head = gwbuf_append(*head, buffer); - while (buffer || SSL_pending(dcb->ssl)) + while (SSL_pending(dcb->ssl)) { dcb->last_read = hkheartbeat; buffer = dcb_basic_read_SSL(dcb, &nsingleread); @@ -1224,14 +1232,17 @@ dcb_log_errors_SSL (DCB *dcb, const char *called_by, int ret) char errbuf[STRERROR_BUFLEN]; unsigned long ssl_errno; - MXS_ERROR("SSL operation failed in %s, dcb %p in state " - "%s fd %d. More details may follow.", - called_by, - dcb, - STRDCBSTATE(dcb->state), - dcb->fd); - ssl_errno = ERR_get_error(); + if (ret || ssl_errno) + { + MXS_ERROR("SSL operation failed in %s, dcb %p in state " + "%s fd %d return code %d. More details may follow.", + called_by, + dcb, + STRDCBSTATE(dcb->state), + dcb->fd, + ret); + } if (ret && !ssl_errno) { int local_errno = errno; @@ -2831,7 +2842,8 @@ dcb_count_by_usage(DCB_USAGE usage) * @param dcb * @return -1 on error, 0 otherwise. */ -int dcb_create_SSL(DCB* dcb) +static int +dcb_create_SSL(DCB* dcb) { if (NULL == dcb->listen_ssl || listener_init_SSL(dcb->listen_ssl) != 0) { @@ -2884,7 +2896,8 @@ int dcb_accept_SSL(DCB* dcb) { case SSL_ERROR_NONE: MXS_DEBUG("SSL_accept done for %s@%s", user, remote); - dcb->ssl_state = SSL_HANDSHAKE_DONE; + dcb->ssl_state = SSL_ESTABLISHED; + dcb->ssl_read_want_write = false; return 1; break; @@ -2895,6 +2908,7 @@ int dcb_accept_SSL(DCB* dcb) case SSL_ERROR_WANT_WRITE: MXS_DEBUG("SSL_accept ongoing want write for %s@%s", user, remote); + dcb->ssl_read_want_write = true; return 0; break; diff --git a/server/core/doxygen.c b/server/core/doxygen.c new file mode 100644 index 000000000..665dde785 --- /dev/null +++ b/server/core/doxygen.c @@ -0,0 +1,82 @@ +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * 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 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2014 + */ + +/** + * @file doxygen.c - The MaxScale model for Doxygen directed comment blocks + * + * This file is not built in to MaxScale at all, it exists only as a model + * and is intended to have parts of it copied into new code, or existing + * code that is being upgraded. + * + * @verbatim + * Revision History + * + * Date Who Description + * 05/02/2016 Martin Brampton Initial implementation + * @endverbatim + */ + +/** + * @brief Example showing how to document a function with Doxygen. + * + * Description of what the function does. This part may refer to the parameters + * of the function, like @p param1 or @p param2. A word of code can also be + * inserted like @c this which is equivalent to this and can be useful + * to say that the function returns a @c void or an @c int. If you want to have + * more than one word in typewriter font, then just use @. + * We can also include text verbatim, + * @verbatim like this@endverbatim + * Sometimes it is also convenient to include an example of usage: + * @code + * BoxStruct *out = Box_The_Function_Name(param1, param2); + * printf("something...\n"); + * @endcode + * Or, + * @code{.py} + * pyval = python_func(arg1, arg2) + * print pyval + * @endcode + * when the language is not the one used in the current source file (but + * be careful as this may be supported only by recent versions + * of Doxygen). By the way, this is how you write bold text or, + * if it is just one word, then you can just do @b this. + * @param param1 Description of the first parameter of the function. + * @param param2 The second one, which follows @p param1. + * @return Describe what the function returns. + * @see Box_The_Second_Function + * @see Box_The_Last_One + * @see http://website/ + * @note Something to note. + * @warning Warning. + */ +BOXEXPORT BoxStruct * +Box_The_Function_Name(BoxParamType1 param1, BoxParamType2 param2 /*, ...*/); + +/** + * @brief A simple stub function to show how links do work. + * + * Links are generated automatically for webpages (like http://www.google.co.uk) + * and for structures, like BoxStruct_struct. For typedef-ed types use + * #BoxStruct. + * For functions, automatic links are generated when the parenthesis () follow + * the name of the function, like Box_The_Function_Name(). + * Alternatively, you can use #Box_The_Function_Name. + * @return @c NULL is always returned. + */ +BOXEXPORT void * +Box_The_Second_Function(void); diff --git a/server/core/gw_ssl.c b/server/core/gw_ssl.c new file mode 100644 index 000000000..dc3df439e --- /dev/null +++ b/server/core/gw_ssl.c @@ -0,0 +1,188 @@ +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * 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 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + +/** + * @file gw_ssl.c - SSL generic functions + * + * SSL is intended to be available in conjunction with a variety of protocols + * on either the client or server side. + * + * @verbatim + * Revision History + * + * Date Who Description + * 02/02/16 Martin Brampton Initial implementation + * + * @endverbatim + */ +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief Check client's SSL capability and start SSL if appropriate. + * + * The protocol should determine whether the client is SSL capable and pass + * the result as the second parameter. If the listener requires SSL but the + * client is not SSL capable, an error message is recorded and failure return + * given. If both sides want SSL, and SSL is not already established, the + * process is triggered by calling dcb_accept_SSL. + * + * @param dcb Request handler DCB connected to the client + * @param is_capable Indicates if the client can handle SSL + * @return 0 if ok, >0 if a problem - see return codes defined in gw_ssl.h + */ +int ssl_authenticate_client(DCB *dcb, bool is_capable) +{ + char *user = dcb->user ? dcb->user : ""; + char *remote = dcb->remote ? dcb->remote : ""; + char *service = (dcb->service && dcb->service->name) ? dcb->service->name : ""; + + if (NULL == dcb->listen_ssl) + { + /* Not an SSL connection on account of listener configuration */ + return SSL_AUTH_CHECKS_OK; + } + /* Now we require an SSL connection */ + if (!is_capable) + { + /* Should be SSL, but client is not SSL capable */ + MXS_INFO("User %s@%s connected to service '%s' without SSL when SSL was required.", + user ? user : "", remote ? remote : "", service ? service : ""); + return SSL_ERROR_CLIENT_NOT_SSL; + } + /* Now we know SSL is required and client is capable */ + if (dcb->ssl_state != SSL_HANDSHAKE_DONE && dcb->ssl_state != SSL_ESTABLISHED) + { + int return_code; + /** Do the SSL Handshake */ + if (SSL_HANDSHAKE_UNKNOWN == dcb->ssl_state) + { + dcb->ssl_state = SSL_HANDSHAKE_REQUIRED; + } + /** + * Note that this will often fail to achieve its result, because further + * reading (or possibly writing) of SSL related information is needed. + * When that happens, there is a call in poll.c so that an EPOLLIN + * event that arrives while the SSL state is SSL_HANDSHAKE_REQUIRED + * will trigger dcb_accept_SSL. This situation does not result in a + * negative return code - that indicates a real failure. + */ + return_code = dcb_accept_SSL(dcb); + if (return_code < 0) + { + MXS_INFO("User %s@%s failed to connect to service '%s' with SSL.", + user, remote, service); + return SSL_ERROR_ACCEPT_FAILED; + } + else if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) + { + if (1 == return_code) + { + MXS_INFO("User %s@%s connected to service '%s' with SSL.", + user, remote, service); + } + else + { + MXS_INFO("User %s@%s connect to service '%s' with SSL in progress.", + user, remote, service); + } + } + } + return SSL_AUTH_CHECKS_OK; +} + +/** + * @brief If an SSL connection is required, check that it has been established. + * + * This is called at the end of the authentication of a new connection. + * If the result is not true, the data packet is abandoned with further + * data expected from the client. + * + * @param dcb Request handler DCB connected to the client + * @return Boolean to indicate whether connection is healthy + */ +bool +ssl_is_connection_healthy(DCB *dcb) +{ + /** + * If SSL was never expected, or if the connection has state SSL_ESTABLISHED + * then everything is as we wish. Otherwise, either there is a problem or + * more to be done. + */ + return (NULL == dcb->listen_ssl || dcb->ssl_state == SSL_ESTABLISHED); +} + +/* Looks to be redundant - can remove include for ioctl too */ +bool +ssl_check_data_to_process(DCB *dcb) +{ + /** SSL authentication is still going on, we need to call dcb_accept_SSL + * until it return 1 for success or -1 for error */ + if (dcb->ssl_state == SSL_HANDSHAKE_REQUIRED && 1 == dcb_accept_SSL(dcb)) + { + int b = 0; + ioctl(dcb->fd,FIONREAD,&b); + if (b != 0) + { + return true; + } + else + { + MXS_DEBUG("[gw_read_client_event] No data in socket after SSL auth"); + } + } + return false; +} + +/** + * @brief Check whether a DCB requires SSL. + * + * This is a very simple test, but is placed in an SSL function so that + * the knowledge of the SSL process is removed from the more general + * handling of a connection in the protocols. + * + * @param dcb Request handler DCB connected to the client + * @return Boolean indicating whether SSL is required. + */ +bool +ssl_required_by_dcb(DCB *dcb) +{ + return NULL != dcb->listen_ssl; +} + +/** + * @brief Check whether a DCB requires SSL, but SSL is not yet negotiated. + * + * This is a very simple test, but is placed in an SSL function so that + * the knowledge of the SSL process is removed from the more general + * handling of a connection in the protocols. + * + * @param dcb Request handler DCB connected to the client + * @return Boolean indicating whether SSL is required and not negotiated. + */ +bool +ssl_required_but_not_negotiated(DCB *dcb) +{ + return (NULL != dcb->listen_ssl && SSL_HANDSHAKE_UNKNOWN == dcb->ssl_state); +} \ No newline at end of file diff --git a/server/core/listener.c b/server/core/listener.c index 8089bc19b..9620e90d8 100644 --- a/server/core/listener.c +++ b/server/core/listener.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include diff --git a/server/core/poll.c b/server/core/poll.c index f00e120de..c26534255 100644 --- a/server/core/poll.c +++ b/server/core/poll.c @@ -71,7 +71,8 @@ int max_poll_sleep; * processing. * 07/07/15 Martin Brampton Simplified add and remove DCB, improve error handling. * 23/08/15 Martin Brampton Added test so only DCB with a session link can be added to the poll list - * + * 07/02/16 Martin Brampton Added a small piece of SSL logic to EPOLLIN + * * @endverbatim */ @@ -971,7 +972,17 @@ process_pollq(int thread_id) if (poll_dcb_session_check(dcb, "read")) { - dcb->func.read(dcb); + int return_code = 1; + /** SSL authentication is still going on, we need to call dcb_accept_SSL + * until it return 1 for success or -1 for error */ + if (dcb->ssl_state == SSL_HANDSHAKE_REQUIRED) + { + return_code = dcb_accept_SSL(dcb); + } + if (1 == return_code) + { + dcb->func.read(dcb); + } } } } diff --git a/server/include/dcb.h b/server/include/dcb.h index 2818814f6..d069d277b 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -322,11 +322,8 @@ void dcb_hangup_foreach (struct server* server); size_t dcb_get_session_id(DCB* dcb); bool dcb_get_ses_log_info(DCB* dcb, size_t* sesid, int* enabled_logs); char *dcb_role_name(DCB *); /* Return the name of a role */ -int dcb_create_SSL(DCB* dcb); int dcb_accept_SSL(DCB* dcb); int dcb_connect_SSL(DCB* dcb); -int dcb_read_SSL(DCB *dcb,GWBUF **head); - /** * DCB flags values diff --git a/server/include/gw_ssl.h b/server/include/gw_ssl.h index 3fe6ee95a..7d365b524 100644 --- a/server/include/gw_ssl.h +++ b/server/include/gw_ssl.h @@ -34,6 +34,8 @@ #include +struct dcb; + enum { SERVICE_SSLV3, @@ -47,6 +49,13 @@ enum SERVICE_SSL_TLS_MAX }; +/** + * Return codes for SSL authentication checks + */ +#define SSL_AUTH_CHECKS_OK 0 +#define SSL_ERROR_CLIENT_NOT_SSL 1 +#define SSL_ERROR_ACCEPT_FAILED 2 + /** * The ssl_listener structure is used to aggregate the SSL configuration items * and data for a particular listener @@ -64,4 +73,10 @@ typedef struct ssl_listener bool ssl_init_done; /*< If SSL has already been initialized for this service */ } SSL_LISTENER; -#endif \ No newline at end of file +int ssl_authenticate_client(struct dcb *dcb, bool is_capable); +bool ssl_is_connection_healthy(struct dcb *dcb); +bool ssl_check_data_to_process(struct dcb *dcb); +bool ssl_required_by_dcb(struct dcb *dcb); +bool ssl_required_but_not_negotiated(struct dcb *dcb); + +#endif /* _GW_SSL_H */ \ No newline at end of file diff --git a/server/modules/include/mysql_auth.h b/server/modules/include/mysql_auth.h new file mode 100644 index 000000000..0237e1093 --- /dev/null +++ b/server/modules/include/mysql_auth.h @@ -0,0 +1,48 @@ +#ifndef _MYSQL_AUTH_H +#define _MYSQL_AUTH_H +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * 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 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + +/* + * @verbatim + * Revision History + * + * Date Who Description + * 02/02/2016 Martin Brampton Initial implementation + * + * @endverbatim + */ + +#include +#include +#include +#include + +int mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf); +bool mysql_auth_is_client_ssl_capable (DCB *dcb); +int mysql_auth_authenticate(DCB *dcb, GWBUF **buf); +int gw_check_mysql_scramble_data(DCB *dcb, + uint8_t *token, + unsigned int token_len, + uint8_t *scramble, + unsigned int scramble_len, + char *username, + uint8_t *stage1_hash); +int check_db_name_after_auth(DCB *dcb, char *database, int auth_ret); + +#endif /** _MYSQL_AUTH_H */ diff --git a/server/modules/include/mysql_client_server_protocol.h b/server/modules/include/mysql_client_server_protocol.h index 22fad2b9e..6e36374d6 100644 --- a/server/modules/include/mysql_client_server_protocol.h +++ b/server/modules/include/mysql_client_server_protocol.h @@ -33,9 +33,10 @@ * and repository to gw_check_mysql_scramble_data() * It's now possible to specify a different users' table than * dcb->service->users default - * 26-02-2014 Massimiliano Pinto Removed previouvsly added parameters to gw_check_mysql_scramble_data() and + * 26-02-2014 Massimiliano Pinto Removed previously added parameters to gw_check_mysql_scramble_data() and * gw_find_mysql_user_password_sha1() * 28-02-2014 Massimiliano Pinto MYSQL_DATABASE_MAXLEN,MYSQL_USER_MAXLEN moved to dbusers.h + * 07-02-2016 Martin Brampton Extend MYSQL_session type; add MYSQL_AUTH_SUCCEEDED * */ @@ -91,9 +92,12 @@ #define COM_QUIT_PACKET_SIZE (4+1) struct dcb; +#define MYSQL_AUTH_SUCCEEDED 0 #define MYSQL_FAILED_AUTH 1 #define MYSQL_FAILED_AUTH_DB 2 #define MYSQL_FAILED_AUTH_SSL 3 +#define MYSQL_AUTH_SSL_INCOMPLETE 4 +#define MYSQL_AUTH_NO_SESSION 5 typedef enum { MYSQL_ALLOC, /* Initial state of protocol auth state */ @@ -132,9 +136,11 @@ typedef struct mysql_session { #if defined(SS_DEBUG) skygw_chk_t myses_chk_top; #endif - uint8_t client_sha1[MYSQL_SCRAMBLE_LEN]; /*< SHA1(passowrd) */ + uint8_t client_sha1[MYSQL_SCRAMBLE_LEN]; /*< SHA1(password) */ char user[MYSQL_USER_MAXLEN+1]; /*< username */ char db[MYSQL_DATABASE_MAXLEN+1]; /*< database */ + int auth_token_len; /*< token length */ + uint8_t *auth_token; /*< token */ #if defined(SS_DEBUG) skygw_chk_t myses_chk_tail; #endif @@ -306,7 +312,6 @@ typedef struct { unsigned long tid; /*< MySQL Thread ID, in * handshake */ unsigned int charset; /*< MySQL character set at connect time */ - bool use_ssl; #if defined(SS_DEBUG) skygw_chk_t protocol_chk_tail; #endif @@ -370,14 +375,6 @@ int gw_find_mysql_user_password_sha1( char *username, uint8_t *gateway_password, DCB *dcb); -int gw_check_mysql_scramble_data( - DCB *dcb, - uint8_t *token, - unsigned int token_len, - uint8_t *scramble, - unsigned int scramble_len, - char *username, - uint8_t *stage1_hash); int mysql_send_auth_error ( DCB *dcb, int packet_number, diff --git a/server/modules/protocol/CMakeLists.txt b/server/modules/protocol/CMakeLists.txt index 582779027..2c3435a68 100644 --- a/server/modules/protocol/CMakeLists.txt +++ b/server/modules/protocol/CMakeLists.txt @@ -1,9 +1,9 @@ -add_library(MySQLClient SHARED mysql_client.c mysql_common.c) +add_library(MySQLClient SHARED mysql_client.c mysql_common.c mysql_auth.c) target_link_libraries(MySQLClient maxscale-common) set_target_properties(MySQLClient PROPERTIES VERSION "1.0.0") install(TARGETS MySQLClient DESTINATION ${MAXSCALE_LIBDIR}) -add_library(MySQLBackend SHARED mysql_backend.c mysql_common.c) +add_library(MySQLBackend SHARED mysql_backend.c mysql_common.c mysql_auth.c) target_link_libraries(MySQLBackend maxscale-common) set_target_properties(MySQLBackend PROPERTIES VERSION "2.0.0") install(TARGETS MySQLBackend DESTINATION ${MAXSCALE_LIBDIR}) diff --git a/server/modules/protocol/mysql_auth.c b/server/modules/protocol/mysql_auth.c new file mode 100644 index 000000000..df09a2a77 --- /dev/null +++ b/server/modules/protocol/mysql_auth.c @@ -0,0 +1,547 @@ +/* + * This file is distributed as part of the MariaDB Corporation MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * 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 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2013-2014 + */ + +/** + * @file mysql_auth.c + * + * MySQL Authentication module for handling the checking of clients credentials + * in the MySQL protocol. + * + * @verbatim + * Revision History + * Date Who Description + * 02/02/2016 Martin Brampton Initial version + * + * @endverbatim + */ + +#include +#include + +static int combined_auth_check( + DCB *dcb, + uint8_t *auth_token, + size_t auth_token_len, + MySQLProtocol *protocol, + char *username, + uint8_t *stage1_hash, + char *database +); +static int mysql_auth_set_client_data( + MYSQL_session *client_data, + MySQLProtocol *protocol, + uint8_t *client_auth_packet, + int client_auth_packet_size); + +/** + * @brief Authenticates a MySQL user who is a client to MaxScale. + * + * First call the SSL authentication function, passing the DCB and a boolean + * indicating whether the client is SSL capable. If SSL authentication is + * successful, check whether connection is complete. Fail if we do not have a + * user name. Call other functions to validate the user, reloading the user + * data if the first attempt fails. + * + * @param dcb Request handler DCB connected to the client + * @param buffer Pointer to pointer to buffer containing data from client + * @return Authentication status + * @note Authentication status codes are defined in mysql_client_server_protocol.h + */ +int +mysql_auth_authenticate(DCB *dcb, GWBUF **buffer) +{ + MySQLProtocol *protocol = DCB_PROTOCOL(dcb, MySQLProtocol); + MYSQL_session *client_data = (MYSQL_session *)dcb->data; + int auth_ret, ssl_ret; + + if (0 != (ssl_ret = ssl_authenticate_client(dcb, mysql_auth_is_client_ssl_capable(dcb)))) + { + auth_ret = (SSL_ERROR_CLIENT_NOT_SSL == ssl_ret) ? MYSQL_FAILED_AUTH_SSL : MYSQL_FAILED_AUTH; + } + + else if (!ssl_is_connection_healthy(dcb)) + { + auth_ret = MYSQL_AUTH_SSL_INCOMPLETE; + } + + else if (0 == strlen(client_data->user)) + { + auth_ret = MYSQL_FAILED_AUTH; + } + + else + { + MXS_DEBUG("Receiving connection from '%s' to database '%s'.", + client_data->user, client_data->db); + + auth_ret = combined_auth_check(dcb, client_data->auth_token, client_data->auth_token_len, + protocol, client_data->user, client_data->client_sha1, client_data->db); + + /* On failed authentication try to load user table from backend database */ + /* Success for service_refresh_users returns 0 */ + if (MYSQL_AUTH_SUCCEEDED != auth_ret && 0 == service_refresh_users(dcb->service)) + { + auth_ret = combined_auth_check(dcb, client_data->auth_token, client_data->auth_token_len, protocol, + client_data->user, client_data->client_sha1, client_data->db); + } + + /* on successful authentication, set user into dcb field */ + if (MYSQL_AUTH_SUCCEEDED == auth_ret) + { + dcb->user = strdup(client_data->user); + } + else if (dcb->service->log_auth_warnings) + { + MXS_NOTICE("%s: login attempt for user '%s', authentication failed.", + dcb->service->name, client_data->user); + if (dcb->ipv4.sin_addr.s_addr == 0x0100007F && + !dcb->service->localhost_match_wildcard_host) + { + MXS_NOTICE("If you have a wildcard grant that covers" + " this address, try adding " + "'localhost_match_wildcard_host=true' for " + "service '%s'. ", dcb->service->name); + } + } + + /* let's free the auth_token now */ + if (client_data->auth_token) + { + free(client_data->auth_token); + client_data->auth_token = NULL; + } + } + + return auth_ret; +} + +/** + * @brief Transfer data from the authentication request to the DCB. + * + * The request handler DCB has a field called data that contains protocol + * specific information. This function examines a buffer containing MySQL + * authentication data and puts it into a structure that is referred to + * by the DCB. If the information in the buffer is invalid, then a failure + * code is returned. A call to mysql_auth_set_client_data does the + * detailed work. + * + * @param dcb Request handler DCB connected to the client + * @param buffer Pointer to pointer to buffer containing data from client + * @return Authentication status + * @note Authentication status codes are defined in mysql_client_server_protocol.h + */ +int +mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf) +{ + uint8_t *client_auth_packet = GWBUF_DATA(buf); + MySQLProtocol *protocol = NULL; + MYSQL_session *client_data = NULL; + int client_auth_packet_size = 0; + + protocol = DCB_PROTOCOL(dcb, MySQLProtocol); + CHK_PROTOCOL(protocol); + if (dcb->data == NULL) + { + if (NULL == (client_data = (MYSQL_session *)calloc(1, sizeof(MYSQL_session)))) + { + return MYSQL_FAILED_AUTH; + } +#if defined(SS_DEBUG) + client_data->myses_chk_top = CHK_NUM_MYSQLSES; + client_data->myses_chk_tail = CHK_NUM_MYSQLSES; +#endif + dcb->data = client_data; + } + else + { + client_data = (MYSQL_session *)dcb->data; + } + + client_auth_packet_size = gwbuf_length(buf); + + /* For clients supporting CLIENT_PROTOCOL_41 + * the Handshake Response Packet is: + * + * 4 bytes mysql protocol heade + * 4 bytes capability flags + * 4 max-packet size + * 1 byte character set + * string[23] reserved (all [0]) + * ... + * ... + */ + + /* Detect now if there are enough bytes to continue */ + if (client_auth_packet_size < (4 + 4 + 4 + 1 + 23)) + { + /* Packet is not big enough */ + return MYSQL_FAILED_AUTH; + } + + return mysql_auth_set_client_data(client_data, protocol, client_auth_packet, + client_auth_packet_size); +} + +/** + * @brief Transfer detailed data from the authentication request to the DCB. + * + * The caller has created the data structure pointed to by the DCB, and this + * function fills in the details. If problems are found with the data, the + * return code indicates failure. + * + * @param client_data The data structure for the DCB + * @param protocol The protocol structure for this connection + * @param client_auth_packet The data from the buffer received from client + * @param client_auth_packet size An integer giving the size of the data + * @return Authentication status + * @note Authentication status codes are defined in mysql_client_server_protocol.h + */ +static int +mysql_auth_set_client_data( + MYSQL_session *client_data, + MySQLProtocol *protocol, + uint8_t *client_auth_packet, + int client_auth_packet_size) +{ + int auth_packet_base_size = 4 + 4 + 4 + 1 + 23; + int packet_length_used = 0; + + /* Take data from fixed locations first */ + memcpy(&protocol->client_capabilities, client_auth_packet + 4, 4); + protocol->charset = 0; + memcpy(&protocol->charset, client_auth_packet + 4 + 4 + 4, 1); + + /* Make username and database a null string in case none is provided */ + client_data->user[0] = 0; + client_data->db[0] = 0; + /* Make authentication token length 0 and token null in case none is provided */ + client_data->auth_token_len = 0; + client_data->auth_token = NULL; + + if (client_auth_packet_size > auth_packet_base_size) + { + /* Should have a username */ + char *first_letter_of_username = (char *)(client_auth_packet + auth_packet_base_size); + int user_length = strlen(first_letter_of_username); + if (client_auth_packet_size > (auth_packet_base_size + user_length) + && user_length <= MYSQL_USER_MAXLEN) + { + strcpy(client_data->user, first_letter_of_username); + } + else + { + /* Packet has incomplete or too long username */ + return MYSQL_FAILED_AUTH; + } + if (client_auth_packet_size > (auth_packet_base_size + user_length + 1)) + { + /* Extra 1 is for the terminating null after user name */ + packet_length_used = auth_packet_base_size + user_length + 1; + /* We should find an authentication token next */ + /* One byte of packet is the length of authentication token */ + memcpy(&client_data->auth_token_len, + client_auth_packet + packet_length_used, + 1); + if (client_auth_packet_size > + (packet_length_used + client_data->auth_token_len)) + { + /* Packet is large enough for authentication token */ + if (NULL != (client_data->auth_token = (uint8_t *)malloc(client_data->auth_token_len))) + { + /* The extra 1 is for the token length byte, just extracted*/ + memcpy(client_data->auth_token, + client_auth_packet + auth_packet_base_size + user_length + 1 +1, + client_data->auth_token_len); + } + else + { + /* Failed to allocate space for authentication token string */ + return MYSQL_FAILED_AUTH; + } + } + else + { + /* Packet was too small to contain authentication token */ + return MYSQL_FAILED_AUTH; + } + packet_length_used += 1 + client_data->auth_token_len; + /* + * Note: some clients may pass empty database, CONNECT_WITH_DB !=0 but database ="" + */ + if (GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB & + gw_mysql_get_byte4((uint32_t *)&protocol->client_capabilities) + && client_auth_packet_size > packet_length_used) + { + char *database = (char *)(client_auth_packet + packet_length_used); + int database_length = strlen(database); + if (client_auth_packet_size > + (packet_length_used + database_length) + && strlen(database) <= MYSQL_DATABASE_MAXLEN) + { + strcpy(client_data->db, database); + } + else + { + /* Packet is too short to contain database string */ + /* or database string in packet is too long */ + return MYSQL_FAILED_AUTH; + } + } + } + } + return MYSQL_AUTH_SUCCEEDED; +} + +/** + * @brief Determine whether the client is SSL capable + * + * The authentication request from the client will indicate whether the client + * is expecting to make an SSL connection. The information has been extracted + * in the previous functions. + * + * @param dcb Request handler DCB connected to the client + * @return Boolean indicating whether client is SSL capable + */ +bool +mysql_auth_is_client_ssl_capable(DCB *dcb) +{ + MySQLProtocol *protocol; + + protocol = DCB_PROTOCOL(dcb, MySQLProtocol); + return (protocol->client_capabilities & GW_MYSQL_CAPABILITIES_SSL) ? true : false; +} + +/** + * + * @brief Check authentication token received against stage1_hash and scramble + * + * @param dcb The current dcb + * @param token The token sent by the client in the authentication request + * @param token_len The token size in bytes + * @param scramble The scramble data sent by the server during handshake + * @param scramble_len The scramble size in bytes + * @param username The current username in the authentication request + * @param stage1_hash The SHA1(candidate_password) decoded by this routine + * @return Authentication status + * @note Authentication status codes are defined in mysql_client_server_protocol.h + * + */ +int +gw_check_mysql_scramble_data(DCB *dcb, + uint8_t *token, + unsigned int token_len, + uint8_t *scramble, + unsigned int scramble_len, + char *username, + uint8_t *stage1_hash) +{ + uint8_t step1[GW_MYSQL_SCRAMBLE_SIZE]=""; + uint8_t step2[GW_MYSQL_SCRAMBLE_SIZE +1]=""; + uint8_t check_hash[GW_MYSQL_SCRAMBLE_SIZE]=""; + char hex_double_sha1[2 * GW_MYSQL_SCRAMBLE_SIZE + 1]=""; + uint8_t password[GW_MYSQL_SCRAMBLE_SIZE]=""; + /* The following can be compared using memcmp to detect a null password */ + uint8_t null_client_sha1[MYSQL_SCRAMBLE_LEN]=""; + + + if ((username == NULL) || (scramble == NULL) || (stage1_hash == NULL)) + { + return MYSQL_FAILED_AUTH; + } + + /*< + * get the user's password from repository in SHA1(SHA1(real_password)); + * please note 'real_password' is unknown! + */ + + if (gw_find_mysql_user_password_sha1(username, password, dcb)) + { + /* if password was sent, fill stage1_hash with at least 1 byte in order + * to create right error message: (using password: YES|NO) + */ + if (token_len) + memcpy(stage1_hash, (char *)"_", 1); + + return MYSQL_FAILED_AUTH; + } + + if (token && token_len) + { + /*< + * convert in hex format: this is the content of mysql.user table. + * The field password is without the '*' prefix and it is 40 bytes long + */ + + gw_bin2hex(hex_double_sha1, password, SHA_DIGEST_LENGTH); + } + else + { + /* check if the password is not set in the user table */ + return memcmp(password, null_client_sha1, MYSQL_SCRAMBLE_LEN) ? + MYSQL_FAILED_AUTH : MYSQL_AUTH_SUCCEEDED; + } + + /*< + * Auth check in 3 steps + * + * Note: token = XOR (SHA1(real_password), SHA1(CONCAT(scramble, SHA1(SHA1(real_password))))) + * the client sends token + * + * Now, server side: + * + * + * step 1: compute the STEP1 = SHA1(CONCAT(scramble, gateway_password)) + * the result in step1 is SHA_DIGEST_LENGTH long + */ + + gw_sha1_2_str(scramble, scramble_len, password, SHA_DIGEST_LENGTH, step1); + + /*< + * step2: STEP2 = XOR(token, STEP1) + * + * token is transmitted form client and it's based on the handshake scramble and SHA1(real_passowrd) + * step1 has been computed in the previous step + * the result STEP2 is SHA1(the_password_to_check) and is SHA_DIGEST_LENGTH long + */ + + gw_str_xor(step2, token, step1, token_len); + + /*< + * copy the stage1_hash back to the caller + * stage1_hash will be used for backend authentication + */ + + memcpy(stage1_hash, step2, SHA_DIGEST_LENGTH); + + /*< + * step 3: prepare the check_hash + * + * compute the SHA1(STEP2) that is SHA1(SHA1(the_password_to_check)), and is SHA_DIGEST_LENGTH long + */ + + gw_sha1_str(step2, SHA_DIGEST_LENGTH, check_hash); + + +#ifdef GW_DEBUG_CLIENT_AUTH + { + char inpass[128]=""; + gw_bin2hex(inpass, check_hash, SHA_DIGEST_LENGTH); + + fprintf(stderr, "The CLIENT hex(SHA1(SHA1(password))) for \"%s\" is [%s]", username, inpass); + } +#endif + + /* now compare SHA1(SHA1(gateway_password)) and check_hash: return 0 is MYSQL_AUTH_OK */ + return (0 == memcmp(password, check_hash, SHA_DIGEST_LENGTH)) ? + MYSQL_AUTH_SUCCEEDED : MYSQL_FAILED_AUTH; +} + +/** + * @brief If the client connection specifies a database, check existence + * + * The client can specify a default database, but if so, it must be one + * that exists. This function is chained from the previous one, and will + * amend the given return code if it is previously showing success. + * + * @param dcb Request handler DCB connected to the client + * @param database A string containing the database name + * @param auth_ret The authentication status prior to calling this function. + * @return Authentication status + * @note Authentication status codes are defined in mysql_client_server_protocol.h + */ +int +check_db_name_after_auth(DCB *dcb, char *database, int auth_ret) +{ + int db_exists = -1; + + /* check for database name and possible match in resource hashtable */ + if (database && strlen(database)) + { + /* if database names are loaded we can check if db name exists */ + if (dcb->service->resources != NULL) + { + if (hashtable_fetch(dcb->service->resources, database)) + { + db_exists = 1; + } + else + { + db_exists = 0; + } + } + else + { + /* if database names are not loaded we don't allow connection with db name*/ + db_exists = -1; + } + + if (db_exists == 0 && auth_ret == MYSQL_AUTH_SUCCEEDED) + { + auth_ret = MYSQL_FAILED_AUTH_DB; + } + + if (db_exists < 0 && auth_ret == MYSQL_AUTH_SUCCEEDED) + { + auth_ret = MYSQL_FAILED_AUTH; + } + } + + return auth_ret; +} + +/** + * @brief Function to easily call authentication and database checks. + * + * The two functions are called one after the other, with the return from + * the first passed to the second. For convenience and clarity this function + * combines the calls. + * + * @param dcb Request handler DCB connected to the client + * @param auth_token A string of bytes containing the authentication token + * @param auth_token_len An integer, the length of the preceding parameter + * @param protocol The protocol structure for the connection + * @param username String containing username + * @param stage1_hash A password hash for authentication + * @param database A string containing the name for the default database + * @return Authentication status + * @note Authentication status codes are defined in mysql_client_server_protocol.h + */ +static int combined_auth_check( + DCB *dcb, + uint8_t *auth_token, + size_t auth_token_len, + MySQLProtocol *protocol, + char *username, + uint8_t *stage1_hash, + char *database +) +{ + int auth_ret; + + auth_ret = gw_check_mysql_scramble_data(dcb, + auth_token, + auth_token_len, + protocol->scramble, + sizeof(protocol->scramble), + username, + stage1_hash); + + /* check for database name match in resource hashtable */ + auth_ret = check_db_name_after_auth(dcb, database, auth_ret); + return auth_ret; +} diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index 127c50f14..af3a1b6c8 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -51,6 +51,7 @@ */ #include #include +#include MODULE_INFO info = { diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 7c8db1914..3ff3fbe91 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -46,11 +46,14 @@ * 09/09/2015 Martin Brampton Modify error handler calls * 11/01/2016 Martin Brampton Remove SSL write code, now handled at lower level; * replace gwbuf_consume by gwbuf_free (multiple). + * 07/02/2016 Martin Brampton Split off authentication and SSL. */ #include #include #include #include +#include +#include #include #include #include @@ -77,10 +80,7 @@ static int gw_client_close(DCB *dcb); static int gw_client_hangup_event(DCB *dcb); static int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message); static int MySQLSendHandshake(DCB* dcb); -static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf); static int route_by_statement(SESSION *, GWBUF **); -extern char* get_username_from_auth(char* ptr, uint8_t* data); -extern int check_db_name_after_auth(DCB *, char *, int); extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db,int); /* @@ -333,15 +333,10 @@ int MySQLSendHandshake(DCB* dcb) mysql_server_capabilities_one[0] &= ~GW_MYSQL_CAPABILITIES_COMPRESS; - /* if (dcb->service->ssl_mode != SSL_DISABLED) */ - if (NULL != dcb->listen_ssl) + if (ssl_required_by_dcb(dcb)) { mysql_server_capabilities_one[1] |= GW_MYSQL_CAPABILITIES_SSL >> 8; } - else - { - mysql_server_capabilities_one[0] &= ~GW_MYSQL_CAPABILITIES_SSL; - } memcpy(mysql_handshake_payload, mysql_server_capabilities_one, sizeof(mysql_server_capabilities_one)); mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_server_capabilities_one); @@ -394,251 +389,6 @@ int MySQLSendHandshake(DCB* dcb) return sizeof(mysql_packet_header) + mysql_payload_size; } -/** - * gw_mysql_do_authentication - * - * Performs the MySQL protocol 4.1 authentication, using data in GWBUF **queue. - * - * (MYSQL_session*)client_data including: user, db, client_sha1 are copied into - * the dcb->data and later to dcb->session->data. client_capabilities are copied - * into the dcb->protocol. - * - * If SSL is enabled for the service, the SSL handshake will be done before the - * MySQL authentication. - * - * @param dcb Descriptor Control Block of the client - * @param queue Pointer to the location of the GWBUF with data from client - * @return 0 If succeed, otherwise non-zero value - * - * @note in case of failure, dcb->data is freed before returning. If succeed, - * dcb->data is freed in session.c:session_free. - */ -static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf) -{ - GWBUF* queue = *buf; - MySQLProtocol *protocol = NULL; - /* int compress = -1; */ - int connect_with_db = -1; - uint8_t *client_auth_packet = GWBUF_DATA(queue); - int client_auth_packet_size = 0; - char *username = NULL; - char *database = NULL; - unsigned int auth_token_len = 0; - uint8_t *auth_token = NULL; - uint8_t *stage1_hash = NULL; - int auth_ret = -1; - MYSQL_session *client_data = NULL; - int ssl = 0; - CHK_DCB(dcb); - - protocol = DCB_PROTOCOL(dcb, MySQLProtocol); - CHK_PROTOCOL(protocol); - if (dcb->data == NULL) - { - client_data = (MYSQL_session *)calloc(1, sizeof(MYSQL_session)); -#if defined(SS_DEBUG) - client_data->myses_chk_top = CHK_NUM_MYSQLSES; - client_data->myses_chk_tail = CHK_NUM_MYSQLSES; -#endif - dcb->data = client_data; - } - else - { - client_data = (MYSQL_session *)dcb->data; - } - - stage1_hash = client_data->client_sha1; - username = client_data->user; - - client_auth_packet_size = gwbuf_length(queue); - - /* For clients supporting CLIENT_PROTOCOL_41 - * the Handshake Response Packet is: - * - * 4 bytes mysql protocol heade - * 4 bytes capability flags - * 4 max-packet size - * 1 byte character set - * string[23] reserved (all [0]) - * ... - * ... - */ - - /* Detect now if there are enough bytes to continue */ - if (client_auth_packet_size < (4 + 4 + 4 + 1 + 23)) - { - return MYSQL_FAILED_AUTH; - } - - memcpy(&protocol->client_capabilities, client_auth_packet + 4, 4); - - connect_with_db = - GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB & - gw_mysql_get_byte4((uint32_t *)&protocol->client_capabilities); - /* - compress = - GW_MYSQL_CAPABILITIES_COMPRESS & gw_mysql_get_byte4( - &protocol->client_capabilities); - */ - - /** Skip this if the SSL handshake is already done. - * If not, start the SSL handshake. */ - if (protocol->owner_dcb->ssl_state != SSL_HANDSHAKE_DONE && protocol->owner_dcb->ssl_state != SSL_ESTABLISHED) - { - ssl = protocol->client_capabilities & GW_MYSQL_CAPABILITIES_SSL; - - /** Client didn't requested SSL when SSL mode was required*/ - /* if (!ssl && protocol->owner_dcb->service->ssl_mode == SSL_REQUIRED) */ - if (!ssl && NULL != protocol->owner_dcb->listen_ssl) - { - MXS_INFO("User %s@%s connected to service '%s' without SSL when SSL was required.", - protocol->owner_dcb->user, - protocol->owner_dcb->remote, - protocol->owner_dcb->service->name); - return MYSQL_FAILED_AUTH_SSL; - } - - if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO) && ssl) - { - MXS_INFO("User %s@%s connected to service '%s' with SSL.", - protocol->owner_dcb->user, - protocol->owner_dcb->remote, - protocol->owner_dcb->service->name); - } - - /** Do the SSL Handshake */ - /* if (ssl && protocol->owner_dcb->service->ssl_mode != SSL_DISABLED) */ - if (NULL != protocol->owner_dcb->listen_ssl) - { - if (SSL_HANDSHAKE_UNKNOWN == protocol->owner_dcb->ssl_state) - { - protocol->owner_dcb->ssl_state = SSL_HANDSHAKE_REQUIRED; - } - if (dcb_accept_SSL(protocol->owner_dcb) < 0) - { - return MYSQL_FAILED_AUTH; - } - else - { - return 0; - } - } - /* else if (dcb->service->ssl_mode == SSL_ENABLED) */ - else if (NULL != dcb->listen_ssl) - { - /** This is a non-SSL connection to a SSL enabled service. - * We have only read enough of the packet to know that the client - * is not requesting SSL and the rest of the auth packet is still - * waiting in the socket. We need to read the data from the socket - * to find out the username of the connecting client. */ - int bytes = dcb_read(dcb,&queue, 0); - queue = gwbuf_make_contiguous(queue); - client_auth_packet = GWBUF_DATA(queue); - client_auth_packet_size = gwbuf_length(queue); - *buf = queue; - MXS_DEBUG("%lu Read %d bytes from fd %d",pthread_self(),bytes,dcb->fd); - } - } - - username = get_username_from_auth(username, client_auth_packet); - - if (username == NULL) - { - return MYSQL_FAILED_AUTH; - } - - /* get charset */ - memcpy(&protocol->charset, client_auth_packet + 4 + 4 + 4, sizeof (int)); - - /* get the auth token len */ - memcpy(&auth_token_len, - client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + 1, - 1); - - /* - * Note: some clients may pass empty database, connect_with_db !=0 but database ="" - */ - if (connect_with_db) - { - database = client_data->db; - strncpy(database, - (char *)(client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + - 1 + 1 + auth_token_len), MYSQL_DATABASE_MAXLEN); - } - - /* allocate memory for token only if auth_token_len > 0 */ - if (auth_token_len) - { - auth_token = (uint8_t *)malloc(auth_token_len); - memcpy(auth_token, - client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + 1 + 1, - auth_token_len); - } - - /* - * Decode the token and check the password - * Note: if auth_token_len == 0 && auth_token == NULL, user is without password - */ - MXS_DEBUG("Receiving connection from '%s' to database '%s'.",username,database); - auth_ret = gw_check_mysql_scramble_data(dcb, - auth_token, - auth_token_len, - protocol->scramble, - sizeof(protocol->scramble), - username, - stage1_hash); - - /* check for database name match in resource hashtable */ - auth_ret = check_db_name_after_auth(dcb, database, auth_ret); - - /* On failed auth try to load users' table from backend database */ - if (auth_ret != 0) - { - if (!service_refresh_users(dcb->service)) - { - /* Try authentication again with new repository data */ - /* Note: if no auth client authentication will fail */ - auth_ret = gw_check_mysql_scramble_data(dcb, - auth_token, - auth_token_len, - protocol->scramble, - sizeof(protocol->scramble), - username, - stage1_hash); - - /* Do again the database check */ - auth_ret = check_db_name_after_auth(dcb, database, auth_ret); - } - } - - /* on successful auth set user into dcb field */ - if (auth_ret == 0) - { - dcb->user = strdup(client_data->user); - } - else if (dcb->service->log_auth_warnings) - { - MXS_NOTICE("%s: login attempt for user '%s', authentication failed.", - dcb->service->name, username); - if (dcb->ipv4.sin_addr.s_addr == 0x0100007F && - !dcb->service->localhost_match_wildcard_host) - { - MXS_NOTICE("If you have a wildcard grant that covers" - " this address, try adding " - "'localhost_match_wildcard_host=true' for " - "service '%s'. ", dcb->service->name); - } - } - - /* let's free the auth_token now */ - if (auth_token) - { - free(auth_token); - } - - return auth_ret; -} - /** * Write function for client DCB: writes data from MaxScale to Client * @@ -651,23 +401,18 @@ int gw_MySQLWrite_client(DCB *dcb, GWBUF *queue) } /** - * Client read event triggered by EPOLLIN + * @brief Client read event triggered by EPOLLIN * * @param dcb Descriptor control block * @return 0 if succeed, 1 otherwise */ int gw_read_client_event(DCB* dcb) { - SESSION *session = NULL; - ROUTER_OBJECT *router = NULL; - ROUTER *router_instance = NULL; - void *rsession = NULL; MySQLProtocol *protocol = NULL; GWBUF *read_buffer = NULL; int rc = 0; int nbytes_read = 0; - uint8_t cap = 0; - bool stmt_input = false; /*< router input type */ + int max_bytes = 0; CHK_DCB(dcb); protocol = DCB_PROTOCOL(dcb, MySQLProtocol); @@ -679,72 +424,139 @@ int gw_read_client_event(DCB* dcb) #endif - /** SSL authentication is still going on, we need to call dcb_accept_SSL - * until it return 1 for success or -1 for error */ - if (protocol->owner_dcb->ssl_state == SSL_HANDSHAKE_REQUIRED) + /** + * The use of max_bytes seems like a hack, but no better option is available + * at the time of writing. When a MySQL server receives a new connection + * request, it sends an Initial Handshake Packet. Where the client wants to + * use SSL, it responds with an SSL Request Packet (in place of a Handshake + * Response Packet). The SSL Request Packet contains only the basic header, + * and not the user credentials. It is 36 bytes long. The server then + * initiates the SSL handshake (via calls to OpenSSL). + * + * In many cases, this is what happens. But occasionally, the client seems + * to send a packet much larger than 36 bytes (in tests it was 333 bytes). + * If the whole of the packet is read, it is then lost to the SSL handshake + * process. Why this happens is presently unknown. Reading just 36 bytes + * when the server requires SSL and SSL has not yet been negotiated seems + * to solve the problem. + * + * If a neater solution can be found, so much the better. + */ + if (ssl_required_but_not_negotiated(dcb)) { - switch(dcb_accept_SSL(protocol->owner_dcb)) - { - case 0: - return 0; - break; - case 1: - { - int b = 0; - ioctl(dcb->fd,FIONREAD,&b); - if (b == 0) - { - MXS_DEBUG("[gw_read_client_event] No data in socket after SSL auth"); - return 0; - } - } - break; - - case -1: - return 1; - break; - default: - return 1; - break; - } + max_bytes = 36; } - - if (SSL_HANDSHAKE_DONE == protocol->owner_dcb->ssl_state || SSL_ESTABLISHED == protocol->owner_dcb->ssl_state) - { - /** SSL handshake is done, communication is now encrypted with SSL */ - rc = dcb_read_SSL(dcb, &read_buffer); - } - /* else if (dcb->service->ssl_mode != SSL_DISABLED && */ - else if (dcb->listen_ssl != NULL && - protocol->protocol_auth_state == MYSQL_AUTH_SENT) - { - /** The service allows both SSL and non-SSL connections. - * read only enough of the auth packet to know if the client is - * requesting SSL. If the client is not requesting SSL the rest of - the auth packet will be read later. */ - rc = dcb_read(dcb, &read_buffer,(4 + 4 + 4 + 1 + 23)); - } - else - { - /** Normal non-SSL connection */ - rc = dcb_read(dcb, &read_buffer, 0); - } - + rc = dcb_read(dcb, &read_buffer, max_bytes); if (rc < 0) { dcb_close(dcb); } - nbytes_read = gwbuf_length(read_buffer); - - if (nbytes_read == 0) + if (0 == (nbytes_read = gwbuf_length(read_buffer))) { goto return_rc; } - session = dcb->session; + switch (protocol->protocol_auth_state) + { + /** + * + * When a listener receives a new connection request, it creates a + * request handler DCB to for the client connection. The listener also + * sends the initial authentication request to the client. The first + * time this function is called from the poll loop, the client reply + * to the authentication request should be available. + * + * If the authentication is successful the protocol authentication state + * will be changed to MYSQL_IDLE (see below). + * + */ + case MYSQL_AUTH_SENT: + { + MySQLProtocol *protocol; + /* int compress = -1; */ + int auth_val, packet_number; - if (protocol->protocol_auth_state == MYSQL_IDLE && session != NULL && - SESSION_STATE_DUMMY != session->state) + packet_number = ssl_required_by_dcb(dcb) ? 3 : 2; + + if (MYSQL_AUTH_SUCCEEDED == ( + auth_val = mysql_auth_set_protocol_data(dcb, read_buffer))) + { + /* + compress = + GW_MYSQL_CAPABILITIES_COMPRESS & gw_mysql_get_byte4( + &protocol->client_capabilities); + */ + auth_val = mysql_auth_authenticate(dcb, &read_buffer); + } + + if (MYSQL_AUTH_SUCCEEDED == auth_val) + { + SESSION *session; + + protocol = DCB_PROTOCOL(dcb, MySQLProtocol); + protocol->protocol_auth_state = MYSQL_AUTH_RECV; + /** + * Create session, and a router session for it. + * If successful, there will be backend connection(s) + * after this point. + */ + session = session_alloc(dcb->service, dcb); + + if (session != NULL) + { + CHK_SESSION(session); + ss_dassert(session->state != SESSION_STATE_ALLOC && + session->state != SESSION_STATE_DUMMY); + + protocol->protocol_auth_state = MYSQL_IDLE; + /** + * Send an AUTH_OK packet to the client, + * packet sequence is # packet_number + */ + mysql_send_ok(dcb, packet_number, 0, NULL); + } + else + { + auth_val = MYSQL_AUTH_NO_SESSION; + } + } + if (MYSQL_AUTH_SUCCEEDED != auth_val) + { + protocol->protocol_auth_state = MYSQL_AUTH_FAILED; + mysql_client_auth_error_handling(dcb, auth_val); + /** + * Release MYSQL_session since it is not used anymore. + */ + if (!DCB_IS_CLONE(dcb)) + { + free(dcb->data); + } + dcb->data = NULL; + + dcb_close(dcb); + } + /* One way or another, the buffer is now fully processed */ + gwbuf_free(read_buffer); + read_buffer = NULL; + } + break; + + /** + * + * Once a client connection is authenticated, the protocol authentication + * state will be MYSQL_IDLE and so every event of data received will + * result in a call that comes to this section of code. + * + */ + case MYSQL_IDLE: + { + ROUTER_OBJECT *router = NULL; + ROUTER *router_instance = NULL; + void *rsession = NULL; + uint8_t cap = 0; + bool stmt_input = false; /*< router input type */ + SESSION *session = dcb->session; + if (session != NULL && SESSION_STATE_DUMMY != session->state) { CHK_SESSION(session); router = session->service->router; @@ -831,131 +643,6 @@ int gw_read_client_event(DCB* dcb) /** * Now there should be at least one complete mysql packet in read_buffer. */ - switch (protocol->protocol_auth_state) - { - case MYSQL_AUTH_SENT: - { - int auth_val, packet_number; - - packet_number = protocol->owner_dcb->listen_ssl ? 3 : 2; - auth_val = gw_mysql_do_authentication(dcb, &read_buffer); - - if (protocol->owner_dcb->ssl_state == SSL_HANDSHAKE_REQUIRED || - protocol->owner_dcb->ssl_state == SSL_HANDSHAKE_DONE || - protocol->owner_dcb->ssl_state == SSL_HANDSHAKE_FAILED) - { - /** SSL was requested and the handshake is either done or - * still ongoing. After the handshake is done, the client - * will send another auth packet. */ - if (protocol->owner_dcb->ssl_state == SSL_HANDSHAKE_DONE) - { - protocol->owner_dcb->ssl_state = SSL_ESTABLISHED; - } - else - { - gwbuf_free(read_buffer); - read_buffer = NULL; - break; - } - } - - if (auth_val == 0) - { - SESSION *session; - - protocol->protocol_auth_state = MYSQL_AUTH_RECV; - /** - * Create session, and a router session for it. - * If successful, there will be backend connection(s) - * after this point. - */ - session = session_alloc(dcb->service, dcb); - - if (session != NULL) - { - CHK_SESSION(session); - ss_dassert(session->state != SESSION_STATE_ALLOC && - session->state != SESSION_STATE_DUMMY); - - protocol->protocol_auth_state = MYSQL_IDLE; - /** - * Send an AUTH_OK packet to the client, - * packet sequence is # packet_number - */ - mysql_send_ok(dcb, packet_number, 0, NULL); - } - else - { - protocol->protocol_auth_state = MYSQL_AUTH_FAILED; - MXS_DEBUG("%lu [gw_read_client_event] session " - "creation failed. fd %d, " - "state = MYSQL_AUTH_FAILED.", - pthread_self(), - protocol->owner_dcb->fd); - - /** Send ERR 1045 to client */ - mysql_send_auth_error(dcb, - packet_number, - 0, - "failed to create new session"); - - dcb_close(dcb); - } - } - else - { - char* fail_str = NULL; - - protocol->protocol_auth_state = MYSQL_AUTH_FAILED; - - if (auth_val == 2) - { - /** Send error 1049 to client */ - int message_len = 25 + MYSQL_DATABASE_MAXLEN; - - fail_str = calloc(1, message_len+1); - snprintf(fail_str, message_len, "Unknown database '%s'", - (char*)((MYSQL_session *)dcb->data)->db); - - modutil_send_mysql_err_packet(dcb, packet_number, 0, 1049, "42000", fail_str); - } - else - { - /** Send error 1045 to client */ - fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user, - dcb->remote, - (char*)((MYSQL_session *)dcb->data)->client_sha1, - (char*)((MYSQL_session *)dcb->data)->db,auth_val); - modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str); - } - if (fail_str) - { - free(fail_str); - } - - MXS_DEBUG("%lu [gw_read_client_event] after " - "gw_mysql_do_authentication, fd %d, " - "state = MYSQL_AUTH_FAILED.", - pthread_self(), - protocol->owner_dcb->fd); - /** - * Release MYSQL_session since it is not used anymore. - */ - if (!DCB_IS_CLONE(dcb)) - { - free(dcb->data); - } - dcb->data = NULL; - - dcb_close(dcb); - } - gwbuf_free(read_buffer); - read_buffer = NULL; - } - break; - - case MYSQL_IDLE: - { uint8_t* payload = NULL; session_state_t ses_state; @@ -1096,6 +783,98 @@ return_rc: return rc; } +/** + * @brief Analyse authentication errors and write appropriate log messages + * + * @param dcb Request handler DCB connected to the client + * @param auth_val The type of authentication failure + * @note Authentication status codes are defined in mysql_client_server_protocol.h + */ +void +mysql_client_auth_error_handling(DCB *dcb, int auth_val) +{ + int packet_number, message_len; + char *fail_str = NULL; + + packet_number = ssl_required_by_dcb(dcb) ? 3 : 2; + + switch (auth_val) + { + case MYSQL_AUTH_NO_SESSION: + MXS_DEBUG("%lu [gw_read_client_event] session " + "creation failed. fd %d, " + "state = MYSQL_AUTH_NO_SESSION.", + pthread_self(), + dcb->fd); + + /** Send ERR 1045 to client */ + mysql_send_auth_error(dcb, + packet_number, + 0, + "failed to create new session"); + case MYSQL_FAILED_AUTH_DB: + MXS_DEBUG("%lu [gw_read_client_event] database " + "specified was not valid. fd %d, " + "state = MYSQL_FAILED_AUTH_DB.", + pthread_self(), + dcb->fd); + /** Send error 1049 to client */ + message_len = 25 + MYSQL_DATABASE_MAXLEN; + + fail_str = calloc(1, message_len+1); + snprintf(fail_str, message_len, "Unknown database '%s'", + (char*)((MYSQL_session *)dcb->data)->db); + + modutil_send_mysql_err_packet(dcb, packet_number, 0, 1049, "42000", fail_str); + case MYSQL_FAILED_AUTH_SSL: + MXS_DEBUG("%lu [gw_read_client_event] client is " + "not SSL capable for SSL listener. fd %d, " + "state = MYSQL_FAILED_AUTH_SSL.", + pthread_self(), + dcb->fd); + + /** Send ERR 1045 to client */ + mysql_send_auth_error(dcb, + packet_number, + 0, + "failed to complete SSL authentication"); + case MYSQL_AUTH_SSL_INCOMPLETE: + MXS_DEBUG("%lu [gw_read_client_event] unable to " + "complete SSL authentication. fd %d, " + "state = MYSQL_AUTH_SSL_INCOMPLETE.", + pthread_self(), + dcb->fd); + + /** Send ERR 1045 to client */ + mysql_send_auth_error(dcb, + packet_number, + 0, + "failed to complete SSL authentication"); + case MYSQL_FAILED_AUTH: + MXS_DEBUG("%lu [gw_read_client_event] authentication failed. fd %d, " + "state = MYSQL_FAILED_AUTH.", + pthread_self(), + dcb->fd); + /** Send error 1045 to client */ + fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user, + dcb->remote, + (char*)((MYSQL_session *)dcb->data)->client_sha1, + (char*)((MYSQL_session *)dcb->data)->db, auth_val); + modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str); + default: + MXS_DEBUG("%lu [gw_read_client_event] authentication failed. fd %d, " + "state unrecognized.", + pthread_self(), + dcb->fd); + /** Send error 1045 to client */ + fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user, + dcb->remote, + (char*)((MYSQL_session *)dcb->data)->client_sha1, + (char*)((MYSQL_session *)dcb->data)->db, auth_val); + modutil_send_mysql_err_packet(dcb, packet_number, 0, 1045, "28000", fail_str); + } + free(fail_str); +} /////////////////////////////////////////////// // client write event to Client triggered by EPOLLOUT diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index b8b82224e..a8d894e1c 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -41,7 +41,8 @@ * 03/10/2014 Massimiliano Pinto Added netmask for wildcard in IPv4 hosts. * 24/10/2014 Massimiliano Pinto Added Mysql user@host @db authentication support * 10/11/2014 Massimiliano Pinto Charset at connect is passed to backend during authentication - * 07/07/15 Martin Brampton Fix problem recognising null password + * 07/07/2015 Martin Brampton Fix problem recognising null password + * 07/02/2016 Martin Brampton Remove authentication functions to mysql_auth.c * */ @@ -59,7 +60,6 @@ extern int gw_read_backend_event(DCB* dcb); extern int gw_write_backend_event(DCB *dcb); extern int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue); extern int gw_error_backend_event(DCB *dcb); -char* get_username_from_auth(char* ptr, uint8_t* data); static server_command_t* server_command_init(server_command_t* srvcmd, mysql_server_cmd_t cmd); @@ -1293,132 +1293,6 @@ int gw_send_change_user_to_backend(char *dbname, return rc; } -/** - * gw_check_mysql_scramble_data - * - * Check authentication token received against stage1_hash and scramble - * - * @param dcb The current dcb - * @param token The token sent by the client in the authentication request - * @param token_len The token size in bytes - * @param scramble The scramble data sent by the server during handshake - * @param scramble_len The scrable size in bytes - * @param username The current username in the authentication request - * @param stage1_hash The SHA1(candidate_password) decoded by this routine - * @return 0 on succesful check or 1 on failure - * - */ -int gw_check_mysql_scramble_data(DCB *dcb, - uint8_t *token, - unsigned int token_len, - uint8_t *scramble, - unsigned int scramble_len, - char *username, - uint8_t *stage1_hash) -{ - uint8_t step1[GW_MYSQL_SCRAMBLE_SIZE]=""; - uint8_t step2[GW_MYSQL_SCRAMBLE_SIZE +1]=""; - uint8_t check_hash[GW_MYSQL_SCRAMBLE_SIZE]=""; - char hex_double_sha1[2 * GW_MYSQL_SCRAMBLE_SIZE + 1]=""; - uint8_t password[GW_MYSQL_SCRAMBLE_SIZE]=""; - int ret_val = 1; - - if ((username == NULL) || (scramble == NULL) || (stage1_hash == NULL)) - { - return 1; - } - - /*< - * get the user's password from repository in SHA1(SHA1(real_password)); - * please note 'real_password' is unknown! - */ - - ret_val = gw_find_mysql_user_password_sha1(username, password, dcb); - - if (ret_val) - { - /* if password was sent, fill stage1_hash with at least 1 byte in order - * to create right error message: (using password: YES|NO) - */ - if (token_len) - memcpy(stage1_hash, (char *)"_", 1); - - return 1; - } - - if (token && token_len) - { - /*< - * convert in hex format: this is the content of mysql.user table. - * The field password is without the '*' prefix and it is 40 bytes long - */ - - gw_bin2hex(hex_double_sha1, password, SHA_DIGEST_LENGTH); - } - else - { - /* check if the password is not set in the user table */ - return memcmp(password, null_client_sha1, MYSQL_SCRAMBLE_LEN) ? 1 : 0; - } - - /*< - * Auth check in 3 steps - * - * Note: token = XOR (SHA1(real_password), SHA1(CONCAT(scramble, SHA1(SHA1(real_password))))) - * the client sends token - * - * Now, server side: - * - * - * step 1: compute the STEP1 = SHA1(CONCAT(scramble, gateway_password)) - * the result in step1 is SHA_DIGEST_LENGTH long - */ - - gw_sha1_2_str(scramble, scramble_len, password, SHA_DIGEST_LENGTH, step1); - - /*< - * step2: STEP2 = XOR(token, STEP1) - * - * token is transmitted form client and it's based on the handshake scramble and SHA1(real_passowrd) - * step1 has been computed in the previous step - * the result STEP2 is SHA1(the_password_to_check) and is SHA_DIGEST_LENGTH long - */ - - gw_str_xor(step2, token, step1, token_len); - - /*< - * copy the stage1_hash back to the caller - * stage1_hash will be used for backend authentication - */ - - memcpy(stage1_hash, step2, SHA_DIGEST_LENGTH); - - /*< - * step 3: prepare the check_hash - * - * compute the SHA1(STEP2) that is SHA1(SHA1(the_password_to_check)), and is SHA_DIGEST_LENGTH long - */ - - gw_sha1_str(step2, SHA_DIGEST_LENGTH, check_hash); - - -#ifdef GW_DEBUG_CLIENT_AUTH - { - char inpass[128]=""; - gw_bin2hex(inpass, check_hash, SHA_DIGEST_LENGTH); - - fprintf(stderr, "The CLIENT hex(SHA1(SHA1(password))) for \"%s\" is [%s]", username, inpass); - } -#endif - - /* now compare SHA1(SHA1(gateway_password)) and check_hash: return 0 is MYSQL_AUTH_OK */ - ret_val = memcmp(password, check_hash, SHA_DIGEST_LENGTH); - - if (ret_val != 0) - return 1; - else - return 0; -} /** * gw_find_mysql_user_password_sha1 @@ -2088,89 +1962,6 @@ char* create_auth_failed_msg(GWBUF*readbuf, return errstr; } -/** - * Read username from MySQL authentication packet. - * - * Only for client to server packet, COM_CHANGE_USER packet has different format. - * - * @param ptr address where to write the result or NULL if memory - * is allocated here. - * @param data Address of MySQL packet. - * - * @return Pointer to a copy of the username. NULL if memory allocation - * failed or if username was empty. - */ -char* get_username_from_auth(char* ptr, - uint8_t* data) -{ - char* first_letter; - char* rval; - - first_letter = (char *)(data + 4 + 4 + 4 + 1 + 23); - - if (*first_letter == '\0') - { - rval = NULL; - goto retblock; - } - - if (ptr == NULL) - { - if ((rval = (char *)malloc(MYSQL_USER_MAXLEN + 1)) == NULL) - { - goto retblock; - } - } - else - { - rval = ptr; - } - snprintf(rval, MYSQL_USER_MAXLEN + 1, "%s", first_letter); - -retblock: - - return rval; -} - -int check_db_name_after_auth(DCB *dcb, char *database, int auth_ret) -{ - int db_exists = -1; - - /* check for dabase name and possible match in resource hashtable */ - if (database && strlen(database)) - { - /* if database names are loaded we can check if db name exists */ - if (dcb->service->resources != NULL) - { - if (hashtable_fetch(dcb->service->resources, database)) - { - db_exists = 1; - } - else - { - db_exists = 0; - } - } - else - { - /* if database names are not loaded we don't allow connection with db name*/ - db_exists = -1; - } - - if (db_exists == 0 && auth_ret == 0) - { - auth_ret = 2; - } - - if (db_exists < 0 && auth_ret == 0) - { - auth_ret = 1; - } - } - - return auth_ret; -} - /** * Create a message error string to send via MySQL ERR packet. * From 5077933e41e9ed43ec1df26dd81b631f74579643 Mon Sep 17 00:00:00 2001 From: counterpoint Date: Mon, 22 Feb 2016 11:05:02 +0000 Subject: [PATCH 06/26] Fix bug in mysql_client.c (over optimisation of protocol setting); various clarifications and improvements re code review. --- server/core/config.c | 16 ++++---- server/core/dcb.c | 6 --- server/core/gw_ssl.c | 5 +-- server/core/listener.c | 55 +++++++++++++++----------- server/core/service.c | 22 +++++------ server/include/gw_protocol.h | 4 -- server/include/gw_ssl.h | 2 +- server/include/listener.h | 1 + server/modules/protocol/mysql_auth.c | 6 ++- server/modules/protocol/mysql_client.c | 20 +++++++++- 10 files changed, 75 insertions(+), 62 deletions(-) diff --git a/server/core/config.c b/server/core/config.c index a87cc918d..932237278 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -133,14 +133,6 @@ static char *service_params[] = "version_string", "filters", "weightby", - /* These should no longer be required - "ssl_cert", - "ssl_ca_cert", - "ssl", - "ssl_key", - "ssl_version", - "ssl_cert_verify_depth", - * */ "ignore_databases", "ignore_databases_regex", "log_auth_warnings", @@ -1094,7 +1086,13 @@ make_ssl_structure (CONFIG_CONTEXT *obj, bool require_cert, int *error_count) local_errors++; } } - else new_ssl->ssl_cert_verify_depth = 9; + else + { + /** + * Default of 9 as per Linux man page + */ + new_ssl->ssl_cert_verify_depth = 9; + } listener_set_certificates(new_ssl, ssl_cert, ssl_key, ssl_ca_cert); diff --git a/server/core/dcb.c b/server/core/dcb.c index 69bc87616..642151c55 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -2957,38 +2957,32 @@ int dcb_connect_SSL(DCB* dcb) case SSL_ERROR_NONE: MXS_DEBUG("SSL_connect done for %s", dcb->remote); return 1; - break; case SSL_ERROR_WANT_READ: MXS_DEBUG("SSL_connect ongoing want read for %s", dcb->remote); return 0; - break; case SSL_ERROR_WANT_WRITE: MXS_DEBUG("SSL_connect ongoing want write for %s", dcb->remote); return 0; - break; case SSL_ERROR_ZERO_RETURN: MXS_DEBUG("SSL error, shut down cleanly during SSL connect %s", dcb->remote); dcb_log_errors_SSL(dcb, __func__, 0); poll_fake_hangup_event(dcb); return 0; - break; case SSL_ERROR_SYSCALL: MXS_DEBUG("SSL connection shut down with SSL_ERROR_SYSCALL during SSL connect %s", dcb->remote); dcb_log_errors_SSL(dcb, __func__, ssl_rval); poll_fake_hangup_event(dcb); return -1; - break; default: MXS_DEBUG("SSL connection shut down with error during SSL connect %s", dcb->remote); dcb_log_errors_SSL(dcb, __func__, 0); poll_fake_hangup_event(dcb); return -1; - break; } } diff --git a/server/core/gw_ssl.c b/server/core/gw_ssl.c index dc3df439e..2b74ba7f1 100644 --- a/server/core/gw_ssl.c +++ b/server/core/gw_ssl.c @@ -52,9 +52,8 @@ * @param is_capable Indicates if the client can handle SSL * @return 0 if ok, >0 if a problem - see return codes defined in gw_ssl.h */ -int ssl_authenticate_client(DCB *dcb, bool is_capable) +int ssl_authenticate_client(DCB *dcb, const char *user, bool is_capable) { - char *user = dcb->user ? dcb->user : ""; char *remote = dcb->remote ? dcb->remote : ""; char *service = (dcb->service && dcb->service->name) ? dcb->service->name : ""; @@ -68,7 +67,7 @@ int ssl_authenticate_client(DCB *dcb, bool is_capable) { /* Should be SSL, but client is not SSL capable */ MXS_INFO("User %s@%s connected to service '%s' without SSL when SSL was required.", - user ? user : "", remote ? remote : "", service ? service : ""); + user, remote, service); return SSL_ERROR_CLIENT_NOT_SSL; } /* Now we know SSL is required and client is capable */ diff --git a/server/core/listener.c b/server/core/listener.c index 9620e90d8..91c6f969f 100644 --- a/server/core/listener.c +++ b/server/core/listener.c @@ -45,6 +45,32 @@ static RSA *rsa_1024 = NULL; static RSA *tmp_rsa_callback(SSL *s, int is_export, int keylength); +/** + * Create a new listener structure + * + * @param protocol The name of the protocol module + * @param address The address to listen with + * @param port The port to listen on + * @param authenticator Name of the authenticator to be used + * @param ssl SSL configuration + * @return New listener object or NULL if unable to allocate + */ +SERV_LISTENER * +alloc_listener(char *protocol, char *address, unsigned short port, char *authenticator, SSL_LISTENER *ssl) +{ + SERV_LISTENER *proto = NULL; + if ((proto = (SERV_LISTENER *)malloc(sizeof(SERV_LISTENER))) != NULL) + { + proto->listener = NULL; + proto->protocol = strdup(protocol); + proto->address = address ? strdup(address) : NULL; + proto->port = port; + proto->authenticator = authenticator ? strdup(authenticator) : NULL; + proto->ssl = ssl; + } + return proto; +} + /** * Set the maximum SSL/TLS version the listener will support * @param ssl_listener Listener data to configure @@ -94,31 +120,14 @@ listener_set_ssl_version(SSL_LISTENER *ssl_listener, char* version) void listener_set_certificates(SSL_LISTENER *ssl_listener, char* cert, char* key, char* ca_cert) { - if (NULL != cert) - { - if (ssl_listener->ssl_cert) - { - free(ssl_listener->ssl_cert); - } - ssl_listener->ssl_cert = strdup(cert); - } - else ssl_listener->ssl_cert = NULL; + free(ssl_listener->ssl_cert); + ssl_listener->ssl_cert = cert ? strdup(cert) : NULL; - if (NULL != key) - { - if (ssl_listener->ssl_key) - { - free(ssl_listener->ssl_key); - } - ssl_listener->ssl_key = strdup(key); - } - else ssl_listener->ssl_key = NULL; + free(ssl_listener->ssl_key); + ssl_listener->ssl_key = key ? strdup(key) : NULL; - if (ssl_listener->ssl_ca_cert) - { - free(ssl_listener->ssl_ca_cert); - } - ssl_listener->ssl_ca_cert = strdup(ca_cert); + free(ssl_listener->ssl_ca_cert); + ssl_listener->ssl_ca_cert = ca_cert ? strdup(ca_cert) : NULL; } /** diff --git a/server/core/service.c b/server/core/service.c index 3cc5baf5e..7a8b6e725 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -680,6 +680,8 @@ service_free(SERVICE *service) * @param protocol The name of the protocol module * @param address The address to listen with * @param port The port to listen on + * @param authenticator Name of the authenticator to be used + * @param ssl SSL configuration * @return TRUE if the protocol/port could be added */ int @@ -687,22 +689,16 @@ serviceAddProtocol(SERVICE *service, char *protocol, char *address, unsigned sho { SERV_LISTENER *proto; - if ((proto = (SERV_LISTENER *)malloc(sizeof(SERV_LISTENER))) == NULL) + if ((proto = alloc_listener(protocol, address, port, authenticator, ssl)) != NULL) { - return 0; + spinlock_acquire(&service->spin); + proto->next = service->ports; + service->ports = proto; + spinlock_release(&service->spin); + return 1; } - proto->listener = NULL; - proto->protocol = strdup(protocol); - proto->address = address ? strdup(address) : NULL; - proto->port = port; - proto->authenticator = authenticator ? strdup(authenticator) : NULL; - proto->ssl = ssl; - spinlock_acquire(&service->spin); - proto->next = service->ports; - service->ports = proto; - spinlock_release(&service->spin); - return 1; + return 0; } /** diff --git a/server/include/gw_protocol.h b/server/include/gw_protocol.h index 826a87c9d..5d50d2f05 100644 --- a/server/include/gw_protocol.h +++ b/server/include/gw_protocol.h @@ -33,10 +33,6 @@ */ #include -#include -#include -#include -#include struct dcb; struct server; diff --git a/server/include/gw_ssl.h b/server/include/gw_ssl.h index 7d365b524..0d263ed7c 100644 --- a/server/include/gw_ssl.h +++ b/server/include/gw_ssl.h @@ -73,7 +73,7 @@ typedef struct ssl_listener bool ssl_init_done; /*< If SSL has already been initialized for this service */ } SSL_LISTENER; -int ssl_authenticate_client(struct dcb *dcb, bool is_capable); +int ssl_authenticate_client(struct dcb *dcb, const char *user, bool is_capable); bool ssl_is_connection_healthy(struct dcb *dcb); bool ssl_check_data_to_process(struct dcb *dcb); bool ssl_required_by_dcb(struct dcb *dcb); diff --git a/server/include/listener.h b/server/include/listener.h index 023b7131d..48e426f03 100644 --- a/server/include/listener.h +++ b/server/include/listener.h @@ -53,6 +53,7 @@ typedef struct servlistener struct servlistener *next; /**< Next service protocol */ } SERV_LISTENER; +SERV_LISTENER *alloc_listener(char *protocol, char *address, unsigned short port, char *authenticator, SSL_LISTENER *ssl); int listener_set_ssl_version(SSL_LISTENER *ssl_listener, char* version); void listener_set_certificates(SSL_LISTENER *ssl_listener, char* cert, char* key, char* ca_cert); int listener_init_SSL(SSL_LISTENER *ssl_listener); diff --git a/server/modules/protocol/mysql_auth.c b/server/modules/protocol/mysql_auth.c index df09a2a77..815226a10 100644 --- a/server/modules/protocol/mysql_auth.c +++ b/server/modules/protocol/mysql_auth.c @@ -69,7 +69,7 @@ mysql_auth_authenticate(DCB *dcb, GWBUF **buffer) MYSQL_session *client_data = (MYSQL_session *)dcb->data; int auth_ret, ssl_ret; - if (0 != (ssl_ret = ssl_authenticate_client(dcb, mysql_auth_is_client_ssl_capable(dcb)))) + if (0 != (ssl_ret = ssl_authenticate_client(dcb, client_data->user, mysql_auth_is_client_ssl_capable(dcb)))) { auth_ret = (SSL_ERROR_CLIENT_NOT_SSL == ssl_ret) ? MYSQL_FAILED_AUTH_SSL : MYSQL_FAILED_AUTH; } @@ -144,6 +144,7 @@ mysql_auth_authenticate(DCB *dcb, GWBUF **buffer) * @param buffer Pointer to pointer to buffer containing data from client * @return Authentication status * @note Authentication status codes are defined in mysql_client_server_protocol.h + * @see https://dev.mysql.com/doc/internals/en/client-server-protocol.html */ int mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf) @@ -184,6 +185,7 @@ mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf) * string[23] reserved (all [0]) * ... * ... + * Note that the fixed elements add up to 36 */ /* Detect now if there are enough bytes to continue */ @@ -210,6 +212,7 @@ mysql_auth_set_protocol_data(DCB *dcb, GWBUF *buf) * @param client_auth_packet size An integer giving the size of the data * @return Authentication status * @note Authentication status codes are defined in mysql_client_server_protocol.h + * @see https://dev.mysql.com/doc/internals/en/client-server-protocol.html */ static int mysql_auth_set_client_data( @@ -218,6 +221,7 @@ mysql_auth_set_client_data( uint8_t *client_auth_packet, int client_auth_packet_size) { + /* The numbers are the fixed elements in the client handshake packet */ int auth_packet_base_size = 4 + 4 + 4 + 1 + 23; int packet_length_used = 0; diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index 3ff3fbe91..eb91a1cfe 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -472,12 +472,21 @@ int gw_read_client_event(DCB* dcb) */ case MYSQL_AUTH_SENT: { - MySQLProtocol *protocol; /* int compress = -1; */ int auth_val, packet_number; + MySQLProtocol *protocol = DCB_PROTOCOL(dcb, MySQLProtocol); packet_number = ssl_required_by_dcb(dcb) ? 3 : 2; + /** + * The first step in the authentication process is to extract the + * relevant information from the buffer supplied and place it + * into a data structure pointed to by the DCB. The "success" + * result is not final, it implies only that the process is so + * far successful, not that authentication has completed. If the + * data extraction succeeds, then a call is made to + * mysql_auth_authenticate to carry out the actual user checks. + */ if (MYSQL_AUTH_SUCCEEDED == ( auth_val = mysql_auth_set_protocol_data(dcb, read_buffer))) { @@ -489,11 +498,18 @@ int gw_read_client_event(DCB* dcb) auth_val = mysql_auth_authenticate(dcb, &read_buffer); } + /** + * At this point, if the auth_val return code indicates success + * the user authentication has been successfully completed. + * But in order to have a working connection, a session has to + * be created. Provided that is successful (indicated by a + * non-null session) then the whole process has succeeded. In all + * other cases an error return is made. + */ if (MYSQL_AUTH_SUCCEEDED == auth_val) { SESSION *session; - protocol = DCB_PROTOCOL(dcb, MySQLProtocol); protocol->protocol_auth_state = MYSQL_AUTH_RECV; /** * Create session, and a router session for it. From ef8a20cceb73cf1681428684445ff937406315ce Mon Sep 17 00:00:00 2001 From: counterpoint Date: Mon, 22 Feb 2016 11:16:51 +0000 Subject: [PATCH 07/26] Fix silly mistakes. --- server/include/gw_ssl.h | 4 ++++ server/modules/protocol/mysql_client.c | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/server/include/gw_ssl.h b/server/include/gw_ssl.h index 0d263ed7c..4465e306a 100644 --- a/server/include/gw_ssl.h +++ b/server/include/gw_ssl.h @@ -33,6 +33,10 @@ */ #include +#include +#include +#include +#include struct dcb; diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index eb91a1cfe..e5f341db9 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -81,6 +81,7 @@ static int gw_client_hangup_event(DCB *dcb); static int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message); static int MySQLSendHandshake(DCB* dcb); static int route_by_statement(SESSION *, GWBUF **); +static void mysql_client_auth_error_handling(DCB *dcb, int auth_val); extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db,int); /* @@ -806,7 +807,7 @@ return_rc: * @param auth_val The type of authentication failure * @note Authentication status codes are defined in mysql_client_server_protocol.h */ -void +static void mysql_client_auth_error_handling(DCB *dcb, int auth_val) { int packet_number, message_len; From 395cbdc10361d1bec3f2c66d2ca16f8df55a51db Mon Sep 17 00:00:00 2001 From: counterpoint Date: Tue, 23 Feb 2016 11:13:21 +0000 Subject: [PATCH 08/26] Simplify handling of "client data" by having a pointer to it only in DCB and not in session structure. Change name of session->client to session->client_dcb for greater clarity. Temporary free of client data in DCB, to be moved to authenticator when it becomes a module. Fix incorrect name of listener_alloc. --- server/core/dcb.c | 38 +- server/core/listener.c | 2 +- server/core/service.c | 2 +- server/core/session.c | 233 ++-- server/include/listener.h | 2 +- server/include/session.h | 5 +- server/modules/filter/dbfwfilter.c | 10 +- server/modules/filter/mqfilter.c | 2 +- server/modules/filter/tee.c | 8 +- server/modules/filter/test/harness_common.c | 112 +- server/modules/protocol/mysql_auth.c | 1 + server/modules/protocol/mysql_backend.c | 48 +- server/modules/protocol/mysql_client.c | 8 +- server/modules/protocol/mysql_common.c | 2 +- server/modules/routing/binlog/blr.c | 86 +- server/modules/routing/debugcli.c | 18 +- server/modules/routing/debugcmd.c | 2 +- server/modules/routing/maxinfo/maxinfo.c | 26 +- server/modules/routing/readconnroute.c | 4 +- .../routing/readwritesplit/readwritesplit.c | 1108 ++++++++--------- .../routing/schemarouter/schemarouter.c | 16 +- .../routing/schemarouter/shardrouter.c | 256 ++-- server/modules/routing/webserver.c | 34 +- 23 files changed, 1016 insertions(+), 1007 deletions(-) diff --git a/server/core/dcb.c b/server/core/dcb.c index 642151c55..05ab7de6a 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -126,6 +126,8 @@ static int gw_write(DCB *dcb, bool *stop_writing); static int gw_write_SSL(DCB *dcb, bool *stop_writing); static void dcb_log_errors_SSL (DCB *dcb, const char *called_by, int ret); +static void mysql_auth_free_client_data(DCB *dcb); + size_t dcb_get_session_id( DCB *dcb) { @@ -373,10 +375,10 @@ dcb_final_free(DCB *dcb) * won't try to call dcb_close for client DCB * after this call. */ - if (local_session->client == dcb) + if (local_session->client_dcb == dcb) { spinlock_acquire(&local_session->ses_lock); - local_session->client = NULL; + local_session->client_dcb = NULL; spinlock_release(&local_session->ses_lock); } if (SESSION_STATE_DUMMY != local_session->state) @@ -385,6 +387,8 @@ dcb_final_free(DCB *dcb) } } + mysql_auth_free_client_data(dcb); + if (dcb->protocol && (!DCB_IS_CLONE(dcb))) { free(dcb->protocol); @@ -779,8 +783,8 @@ dcb_connect(SERVER *server, SESSION *session, const char *protocol) server->name, server->port, dcb, - session->client, - session->client->fd); + session->client_dcb, + session->client_dcb->fd); dcb->state = DCB_STATE_DISCONNECTED; dcb_final_free(dcb); return NULL; @@ -793,8 +797,8 @@ dcb_connect(SERVER *server, SESSION *session, const char *protocol) server->name, server->port, dcb, - session->client, - session->client->fd); + session->client_dcb, + session->client_dcb->fd); } /** * Successfully connected to backend. Assign file descriptor to dcb @@ -2132,9 +2136,9 @@ dcb_isclient(DCB *dcb) { if (dcb->state != DCB_STATE_LISTENING && dcb->session) { - if (dcb->session->client) + if (dcb->session->client_dcb) { - return (dcb->session && dcb == dcb->session->client); + return (dcb->session && dcb == dcb->session->client_dcb); } } @@ -3020,3 +3024,21 @@ dcb_role_name(DCB *dcb) } return name; } + +/** + * @brief Free the client data pointed to by the passed DCB. + * + * Currently all that is required is to free the storage pointed to by + * dcb->data. But this is intended to be implemented as part of the + * authentication API at which time this code will be moved into the + * MySQL authenticator. If the data structure were to become more complex + * the mechanism would still work and be the responsibility of the authenticator. + * The DCB should not know authenticator implementation details. + * + * @param dcb Request handler DCB connected to the client + */ +static void +mysql_auth_free_client_data(DCB *dcb) +{ + free(dcb->data); +} \ No newline at end of file diff --git a/server/core/listener.c b/server/core/listener.c index 91c6f969f..e0ce4b792 100644 --- a/server/core/listener.c +++ b/server/core/listener.c @@ -56,7 +56,7 @@ static RSA *tmp_rsa_callback(SSL *s, int is_export, int keylength); * @return New listener object or NULL if unable to allocate */ SERV_LISTENER * -alloc_listener(char *protocol, char *address, unsigned short port, char *authenticator, SSL_LISTENER *ssl) +listener_alloc(char *protocol, char *address, unsigned short port, char *authenticator, SSL_LISTENER *ssl) { SERV_LISTENER *proto = NULL; if ((proto = (SERV_LISTENER *)malloc(sizeof(SERV_LISTENER))) != NULL) diff --git a/server/core/service.c b/server/core/service.c index 7a8b6e725..c48ddb298 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -689,7 +689,7 @@ serviceAddProtocol(SERVICE *service, char *protocol, char *address, unsigned sho { SERV_LISTENER *proto; - if ((proto = alloc_listener(protocol, address, port, authenticator, ssl)) != NULL) + if ((proto = listener_alloc(protocol, address, port, authenticator, ssl)) != NULL) { spinlock_acquire(&service->spin); proto->next = service->ports; diff --git a/server/core/session.c b/server/core/session.c index 285abc4d0..69ba9c9c0 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -117,7 +117,7 @@ session_alloc(SERVICE *service, DCB *client_dcb) session->ses_is_child = (bool) DCB_IS_CLONE(client_dcb); spinlock_init(&session->ses_lock); session->service = service; - session->client = client_dcb; + session->client_dcb = client_dcb; session->n_filters = 0; memset(&session->stats, 0, sizeof(SESSION_STATS)); session->stats.connect = time(0); @@ -129,7 +129,6 @@ session_alloc(SERVICE *service, DCB *client_dcb) * session has not been made available to the other threads at this * point. */ - session->data = client_dcb->data; session->refcount = 1; /*< * This indicates that session is ready to be shared with backend @@ -194,7 +193,7 @@ session_alloc(SERVICE *service, DCB *client_dcb) { session->state = SESSION_STATE_ROUTER_READY; - if (session->client->user == NULL) + if (session->client_dcb->user == NULL) { MXS_INFO("Started session [%lu] for %s service ", session->ses_id, @@ -205,8 +204,8 @@ session_alloc(SERVICE *service, DCB *client_dcb) MXS_INFO("Started %s client session [%lu] for '%s' from %s", service->name, session->ses_id, - session->client->user, - session->client->remote); + session->client_dcb->user, + session->client_dcb->remote); } } else @@ -215,8 +214,8 @@ session_alloc(SERVICE *service, DCB *client_dcb) "closed as soon as all related DCBs have been closed.", service->name, session->ses_id, - session->client->user, - session->client->remote); + session->client_dcb->user, + session->client_dcb->remote); } spinlock_acquire(&session_spin); /** Assign a session id and increase, insert session into list */ @@ -253,12 +252,11 @@ session_set_dummy(DCB *client_dcb) session->ses_is_child = false; spinlock_init(&session->ses_lock); session->service = NULL; - session->client = NULL; + session->client_dcb = NULL; session->n_filters = 0; memset(&session->stats, 0, sizeof(SESSION_STATS)); session->stats.connect = 0; session->state = SESSION_STATE_DUMMY; - session->data = NULL; session->refcount = 1; session->ses_id = 0; session->next = NULL; @@ -272,12 +270,12 @@ session_set_dummy(DCB *client_dcb) * counter. * Generic logging setting has precedence over session-specific setting. * - * @param ses session + * @param session session * @param priority syslog priority */ -void session_enable_log_priority(SESSION* ses, int priority) +void session_enable_log_priority(SESSION* session, int priority) { - ses->enabled_log_priorities |= (1 << priority); + session->enabled_log_priorities |= (1 << priority); atomic_add((int *)&mxs_log_session_count[priority], 1); } @@ -286,14 +284,14 @@ void session_enable_log_priority(SESSION* ses, int priority) * counter. * Generic logging setting has precedence over session-specific setting. * - * @param ses session + * @param session session * @param priority syslog priority */ -void session_disable_log_priority(SESSION* ses, int priority) +void session_disable_log_priority(SESSION* session, int priority) { - if (ses->enabled_log_priorities & (1 << priority)) + if (session->enabled_log_priorities & (1 << priority)) { - ses->enabled_log_priorities &= ~(1 << priority); + session->enabled_log_priorities &= ~(1 << priority); atomic_add((int *)&mxs_log_session_count[priority], -1); } } @@ -345,9 +343,9 @@ int session_unlink_dcb(SESSION* session, if (dcb != NULL) { - if (session->client == dcb) + if (session->client_dcb == dcb) { - session->client = NULL; + session->client_dcb = NULL; } dcb->session = NULL; } @@ -481,11 +479,6 @@ session_free(SESSION *session) if (!session->ses_is_child) { session->state = SESSION_STATE_FREE; - - if (session->data) - { - free(session->data); - } free(session); } return true; @@ -500,19 +493,19 @@ session_free(SESSION *session) int session_isvalid(SESSION *session) { - SESSION *ptr; + SESSION *list_session; int rval = 0; spinlock_acquire(&session_spin); - ptr = allSessions; - while (ptr) + list_session = allSessions; + while (list_session) { - if (ptr == session) + if (list_session == session) { rval = 1; break; } - ptr = ptr->next; + list_session = list_session->next; } spinlock_release(&session_spin); @@ -533,7 +526,7 @@ printSession(SESSION *session) printf("Session %p\n", session); printf("\tState: %s\n", session_state(session->state)); printf("\tService: %s (%p)\n", session->service->name, session->service); - printf("\tClient DCB: %p\n", session->client); + printf("\tClient DCB: %p\n", session->client_dcb); printf("\tConnected: %s", asctime_r(localtime_r(&session->stats.connect, &result), timebuf)); } @@ -547,14 +540,14 @@ printSession(SESSION *session) void printAllSessions() { - SESSION *ptr; + SESSION *list_session; spinlock_acquire(&session_spin); - ptr = allSessions; - while (ptr) + list_session = allSessions; + while (list_session) { - printSession(ptr); - ptr = ptr->next; + printSession(list_session); + list_session = list_session->next; } spinlock_release(&session_spin); } @@ -569,29 +562,29 @@ printAllSessions() void CheckSessions() { - SESSION *ptr; + SESSION *list_session; int noclients = 0; int norouter = 0; spinlock_acquire(&session_spin); - ptr = allSessions; - while (ptr) + list_session = allSessions; + while (list_session) { - if (ptr->state != SESSION_STATE_LISTENER || - ptr->state != SESSION_STATE_LISTENER_STOPPED) + if (list_session->state != SESSION_STATE_LISTENER || + list_session->state != SESSION_STATE_LISTENER_STOPPED) { - if (ptr->client == NULL && ptr->refcount) + if (list_session->client_dcb == NULL && list_session->refcount) { if (noclients == 0) { printf("Sessions without a client DCB.\n"); printf("==============================\n"); } - printSession(ptr); + printSession(list_session); noclients++; } } - ptr = ptr->next; + list_session = list_session->next; } spinlock_release(&session_spin); if (noclients) @@ -599,24 +592,24 @@ CheckSessions() printf("%d Sessions have no clients\n", noclients); } spinlock_acquire(&session_spin); - ptr = allSessions; - while (ptr) + list_session = allSessions; + while (list_session) { - if (ptr->state != SESSION_STATE_LISTENER || - ptr->state != SESSION_STATE_LISTENER_STOPPED) + if (list_session->state != SESSION_STATE_LISTENER || + list_session->state != SESSION_STATE_LISTENER_STOPPED) { - if (ptr->router_session == NULL && ptr->refcount) + if (list_session->router_session == NULL && list_session->refcount) { if (norouter == 0) { printf("Sessions without a router session.\n"); printf("==================================\n"); } - printSession(ptr); + printSession(list_session); norouter++; } } - ptr = ptr->next; + list_session = list_session->next; } spinlock_release(&session_spin); if (norouter) @@ -638,36 +631,36 @@ dprintAllSessions(DCB *dcb) { struct tm result; char timebuf[40]; - SESSION *ptr; + SESSION *list_session; spinlock_acquire(&session_spin); - ptr = allSessions; - while (ptr) + list_session = allSessions; + while (list_session) { - dcb_printf(dcb, "Session %d (%p)\n",ptr->ses_id, ptr); - dcb_printf(dcb, "\tState: %s\n", session_state(ptr->state)); - dcb_printf(dcb, "\tService: %s (%p)\n", ptr->service->name, ptr->service); - dcb_printf(dcb, "\tClient DCB: %p\n", ptr->client); + dcb_printf(dcb, "Session %d (%p)\n",list_session->ses_id, list_session); + dcb_printf(dcb, "\tState: %s\n", session_state(list_session->state)); + dcb_printf(dcb, "\tService: %s (%p)\n", list_session->service->name, list_session->service); + dcb_printf(dcb, "\tClient DCB: %p\n", list_session->client_dcb); - if (ptr->client && ptr->client->remote) + if (list_session->client_dcb && list_session->client_dcb->remote) { dcb_printf(dcb, "\tClient Address: %s%s%s\n", - ptr->client->user?ptr->client->user:"", - ptr->client->user?"@":"", - ptr->client->remote); + list_session->client_dcb->user?list_session->client_dcb->user:"", + list_session->client_dcb->user?"@":"", + list_session->client_dcb->remote); } dcb_printf(dcb, "\tConnected: %s", - asctime_r(localtime_r(&ptr->stats.connect, &result), timebuf)); + asctime_r(localtime_r(&list_session->stats.connect, &result), timebuf)); - if (ptr->client && ptr->client->state == DCB_STATE_POLLING) + if (list_session->client_dcb && list_session->client_dcb->state == DCB_STATE_POLLING) { - double idle = (hkheartbeat - ptr->client->last_read); + double idle = (hkheartbeat - list_session->client_dcb->last_read); idle = idle > 0 ? idle/10.0:0; dcb_printf(dcb, "\tIdle: %.0f seconds\n",idle); } - ptr = ptr->next; + list_session = list_session->next; } spinlock_release(&session_spin); } @@ -679,43 +672,43 @@ dprintAllSessions(DCB *dcb) * to display all active sessions within the gateway * * @param dcb The DCB to print to - * @param ptr The session to print + * @param print_session The session to print */ void -dprintSession(DCB *dcb, SESSION *ptr) +dprintSession(DCB *dcb, SESSION *print_session) { struct tm result; char buf[30]; int i; - dcb_printf(dcb, "Session %d (%p)\n",ptr->ses_id, ptr); - dcb_printf(dcb, "\tState: %s\n", session_state(ptr->state)); - dcb_printf(dcb, "\tService: %s (%p)\n", ptr->service->name, ptr->service); - dcb_printf(dcb, "\tClient DCB: %p\n", ptr->client); - if (ptr->client && ptr->client->remote) + dcb_printf(dcb, "Session %d (%p)\n",print_session->ses_id, print_session); + dcb_printf(dcb, "\tState: %s\n", session_state(print_session->state)); + dcb_printf(dcb, "\tService: %s (%p)\n", print_session->service->name, print_session->service); + dcb_printf(dcb, "\tClient DCB: %p\n", print_session->client_dcb); + if (print_session->client_dcb && print_session->client_dcb->remote) { - double idle = (hkheartbeat - ptr->client->last_read); + double idle = (hkheartbeat - print_session->client_dcb->last_read); idle = idle > 0 ? idle/10.f : 0; dcb_printf(dcb, "\tClient Address: %s%s%s\n", - ptr->client->user?ptr->client->user:"", - ptr->client->user?"@":"", - ptr->client->remote); + print_session->client_dcb->user?print_session->client_dcb->user:"", + print_session->client_dcb->user?"@":"", + print_session->client_dcb->remote); dcb_printf(dcb, "\tConnected: %s\n", - asctime_r(localtime_r(&ptr->stats.connect, &result), buf)); - if (ptr->client->state == DCB_STATE_POLLING) + asctime_r(localtime_r(&print_session->stats.connect, &result), buf)); + if (print_session->client_dcb->state == DCB_STATE_POLLING) { dcb_printf(dcb, "\tIdle: %.0f seconds\n",idle); } } - if (ptr->n_filters) + if (print_session->n_filters) { - for (i = 0; i < ptr->n_filters; i++) + for (i = 0; i < print_session->n_filters; i++) { dcb_printf(dcb, "\tFilter: %s\n", - ptr->filters[i].filter->name); - ptr->filters[i].filter->obj->diagnostics(ptr->filters[i].instance, - ptr->filters[i].session, + print_session->filters[i].filter->name); + print_session->filters[i].filter->obj->diagnostics(print_session->filters[i].instance, + print_session->filters[i].session, dcb); } } @@ -732,26 +725,26 @@ dprintSession(DCB *dcb, SESSION *ptr) void dListSessions(DCB *dcb) { - SESSION *ptr; + SESSION *list_session; spinlock_acquire(&session_spin); - ptr = allSessions; - if (ptr) + list_session = allSessions; + if (list_session) { dcb_printf(dcb, "Sessions.\n"); dcb_printf(dcb, "-----------------+-----------------+----------------+--------------------------\n"); dcb_printf(dcb, "Session | Client | Service | State\n"); dcb_printf(dcb, "-----------------+-----------------+----------------+--------------------------\n"); } - while (ptr) + while (list_session) { - dcb_printf(dcb, "%-16p | %-15s | %-14s | %s\n", ptr, - ((ptr->client && ptr->client->remote) - ? ptr->client->remote : ""), - (ptr->service && ptr->service->name ? ptr->service->name + dcb_printf(dcb, "%-16p | %-15s | %-14s | %s\n", list_session, + ((list_session->client_dcb && list_session->client_dcb->remote) + ? list_session->client_dcb->remote : ""), + (list_session->service && list_session->service->name ? list_session->service->name : ""), - session_state(ptr->state)); - ptr = ptr->next; + session_state(list_session->state)); + list_session = list_session->next; } if (allSessions) { @@ -905,7 +898,7 @@ session_reply(void *instance, void *session, GWBUF *data) { SESSION *the_session = (SESSION *)session; - return the_session->client->func.write(the_session->client, data); + return the_session->client_dcb->func.write(the_session->client_dcb, data); } /** @@ -916,9 +909,9 @@ session_reply(void *instance, void *session, GWBUF *data) char * session_get_remote(SESSION *session) { - if (session && session->client) + if (session && session->client_dcb) { - return session->client->remote; + return session->client_dcb->remote; } return NULL; } @@ -958,7 +951,7 @@ return_succp: char * session_getUser(SESSION *session) { - return (session && session->client) ? session->client->user : NULL; + return (session && session->client_dcb) ? session->client_dcb->user : NULL; } /** * Return the pointer to the list of all sessions. @@ -996,17 +989,17 @@ void process_idle_sessions() * check for it once per second. One heartbeat is 100 milliseconds. */ next_timeout_check = hkheartbeat + 10; spinlock_acquire(&session_spin); - SESSION *ses = get_all_sessions(); + SESSION *all_session = get_all_sessions(); - while (ses) + while (all_session) { - if (ses->service && ses->client && ses->client->state == DCB_STATE_POLLING && - hkheartbeat - ses->client->last_read > ses->service->conn_idle_timeout * 10) + if (all_session->service && all_session->client_dcb && all_session->client_dcb->state == DCB_STATE_POLLING && + hkheartbeat - all_session->client_dcb->last_read > all_session->service->conn_idle_timeout * 10) { - dcb_close(ses->client); + dcb_close(all_session->client_dcb); } - ses = ses->next; + all_session = all_session->next; } spinlock_release(&session_spin); } @@ -1037,20 +1030,20 @@ sessionRowCallback(RESULTSET *set, void *data) int i = 0; char buf[20]; RESULT_ROW *row; - SESSION *ptr; + SESSION *list_session; spinlock_acquire(&session_spin); - ptr = allSessions; + list_session = allSessions; /* Skip to the first non-listener if not showing listeners */ - while (ptr && cbdata->filter == SESSION_LIST_CONNECTION && - ptr->state == SESSION_STATE_LISTENER) + while (list_session && cbdata->filter == SESSION_LIST_CONNECTION && + list_session->state == SESSION_STATE_LISTENER) { - ptr = ptr->next; + list_session = list_session->next; } - while (i < cbdata->index && ptr) + while (i < cbdata->index && list_session) { if (cbdata->filter == SESSION_LIST_CONNECTION && - ptr->state != SESSION_STATE_LISTENER) + list_session->state != SESSION_STATE_LISTENER) { i++; } @@ -1058,15 +1051,15 @@ sessionRowCallback(RESULTSET *set, void *data) { i++; } - ptr = ptr->next; + list_session = list_session->next; } /* Skip to the next non-listener if not showing listeners */ - while (ptr && cbdata->filter == SESSION_LIST_CONNECTION && - ptr->state == SESSION_STATE_LISTENER) + while (list_session && cbdata->filter == SESSION_LIST_CONNECTION && + list_session->state == SESSION_STATE_LISTENER) { - ptr = ptr->next; + list_session = list_session->next; } - if (ptr == NULL) + if (list_session == NULL) { spinlock_release(&session_spin); free(data); @@ -1074,14 +1067,14 @@ sessionRowCallback(RESULTSET *set, void *data) } cbdata->index++; row = resultset_make_row(set); - snprintf(buf,19, "%p", ptr); + snprintf(buf,19, "%p", list_session); buf[19] = '\0'; resultset_row_set(row, 0, buf); - resultset_row_set(row, 1, ((ptr->client && ptr->client->remote) - ? ptr->client->remote : "")); - resultset_row_set(row, 2, (ptr->service && ptr->service->name - ? ptr->service->name : "")); - resultset_row_set(row, 3, session_state(ptr->state)); + resultset_row_set(row, 1, ((list_session->client_dcb && list_session->client_dcb->remote) + ? list_session->client_dcb->remote : "")); + resultset_row_set(row, 2, (list_session->service && list_session->service->name + ? list_session->service->name : "")); + resultset_row_set(row, 3, session_state(list_session->state)); spinlock_release(&session_spin); return row; } diff --git a/server/include/listener.h b/server/include/listener.h index 48e426f03..4e8d9295c 100644 --- a/server/include/listener.h +++ b/server/include/listener.h @@ -53,7 +53,7 @@ typedef struct servlistener struct servlistener *next; /**< Next service protocol */ } SERV_LISTENER; -SERV_LISTENER *alloc_listener(char *protocol, char *address, unsigned short port, char *authenticator, SSL_LISTENER *ssl); +SERV_LISTENER *listener_alloc(char *protocol, char *address, unsigned short port, char *authenticator, SSL_LISTENER *ssl); int listener_set_ssl_version(SSL_LISTENER *ssl_listener, char* version); void listener_set_certificates(SSL_LISTENER *ssl_listener, char* cert, char* key, char* ca_cert); int listener_init_SSL(SSL_LISTENER *ssl_listener); diff --git a/server/include/session.h b/server/include/session.h index 76d9e718f..096dcacc9 100644 --- a/server/include/session.h +++ b/server/include/session.h @@ -129,8 +129,7 @@ typedef struct session session_state_t state; /*< Current descriptor state */ size_t ses_id; /*< Unique session identifier */ int enabled_log_priorities; /*< Bitfield of enabled syslog priorities */ - struct dcb *client; /*< The client connection */ - void *data; /*< The session data */ + struct dcb *client_dcb; /*< The client connection */ void *router_session; /*< The router instance data */ SESSION_STATS stats; /*< Session statistics */ struct service *service; /*< The service this session is using */ @@ -153,7 +152,7 @@ extern bool check_timeouts; * hk_heartbeat.h */ extern long next_timeout_check; -#define SESSION_PROTOCOL(x, type) DCB_PROTOCOL((x)->client, type) +#define SESSION_PROTOCOL(x, type) DCB_PROTOCOL((x)->client_dcb, type) /** * A convenience macro that can be used by the protocol modules to route diff --git a/server/modules/filter/dbfwfilter.c b/server/modules/filter/dbfwfilter.c index 0e158552c..dec057102 100644 --- a/server/modules/filter/dbfwfilter.c +++ b/server/modules/filter/dbfwfilter.c @@ -1429,15 +1429,15 @@ GWBUF* gen_dummy_error(FW_SESSION* session, char* msg) unsigned int errlen; if (session == NULL || session->session == NULL || - session->session->data == NULL || - session->session->client == NULL) + session->session->client_dcb == NULL || + session->session->client_dcb->data == NULL) { MXS_ERROR("Firewall filter session missing data."); return NULL; } - dcb = session->session->client; - mysql_session = (MYSQL_session*) session->session->data; + dcb = session->session->client_dcb; + mysql_session = (MYSQL_session*) dcb->data; errlen = msg != NULL ? strlen(msg) : 0; errmsg = (char*) malloc((512 + errlen) * sizeof(char)); @@ -1955,7 +1955,7 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue) bool accept = my_instance->def_op; char *msg = NULL, *fullquery = NULL, *ipaddr; char uname_addr[128]; - DCB* dcb = my_session->session->client; + DCB* dcb = my_session->session->client_dcb; USER* user = NULL; GWBUF* forward; ipaddr = strdup(dcb->remote); diff --git a/server/modules/filter/mqfilter.c b/server/modules/filter/mqfilter.c index be9a07a63..25c38acd2 100644 --- a/server/modules/filter/mqfilter.c +++ b/server/modules/filter/mqfilter.c @@ -992,7 +992,7 @@ newSession(FILTER *instance, SESSION *session) my_session->was_query = false; my_session->uid = NULL; my_session->session = session; - sessauth = my_session->session->data; + sessauth = my_session->session->client_dcb->data; if (sessauth->db && strnlen(sessauth->db, 128) > 0) { my_session->db = strdup(sessauth->db); diff --git a/server/modules/filter/tee.c b/server/modules/filter/tee.c index 3a9a99b92..3e6a63b8a 100644 --- a/server/modules/filter/tee.c +++ b/server/modules/filter/tee.c @@ -251,7 +251,7 @@ orphan_free(void* data) */ if (ptr->session->state == SESSION_STATE_STOPPING && - ptr->session->refcount == 0 && ptr->session->client == NULL) + ptr->session->refcount == 0 && ptr->session->client_dcb == NULL) { ptr->session->state = SESSION_STATE_TO_BE_FREED; } @@ -485,7 +485,7 @@ newSession(FILTER *instance, SESSION *session) my_session->active = 1; my_session->residual = 0; my_session->tee_replybuf = NULL; - my_session->client_dcb = session->client; + my_session->client_dcb = session->client_dcb; my_session->instance = my_instance; my_session->client_multistatement = false; my_session->queue = NULL; @@ -518,7 +518,7 @@ newSession(FILTER *instance, SESSION *session) FILTER_DEF* dummy; UPSTREAM* dummy_upstream; - if ((dcb = dcb_clone(session->client)) == NULL) + if ((dcb = dcb_clone(session->client_dcb)) == NULL) { freeSession(instance, (void *) my_session); my_session = NULL; @@ -578,7 +578,7 @@ newSession(FILTER *instance, SESSION *session) } ses->tail = *dummy_upstream; - MySQLProtocol* protocol = (MySQLProtocol*) session->client->protocol; + MySQLProtocol* protocol = (MySQLProtocol*) session->client_dcb->protocol; my_session->use_ok = protocol->client_capabilities & (1 << 6); free(dummy_upstream); } diff --git a/server/modules/filter/test/harness_common.c b/server/modules/filter/test/harness_common.c index 02b79687d..e8dc3b7f0 100644 --- a/server/modules/filter/test/harness_common.c +++ b/server/modules/filter/test/harness_common.c @@ -10,7 +10,7 @@ int dcbfun(struct dcb* dcb, GWBUF * buffer) int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){ - int i = 0,rval = 0; + int i = 0,rval = 0; MYSQL_session* mysqlsess; DCB* dcb; char cwd[1024]; @@ -19,12 +19,12 @@ int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){ if(!(argc == 2 && strcmp(argv[1],"-h") == 0)){ mxs_log_init(NULL,NULL,MXS_LOG_TARGET_DEFAULT); } - + if(!(instance.head = calloc(1,sizeof(FILTERCHAIN)))) { printf("Error: Out of memory\n"); MXS_ERROR("Out of memory\n"); - + return 1; } @@ -41,26 +41,26 @@ int harness_init(int argc, char** argv, HARNESS_INSTANCE** inst){ mysqlsess = calloc(1,sizeof(MYSQL_session)); sprintf(mysqlsess->user,"dummyuser"); - sprintf(mysqlsess->db,"dummydb"); + sprintf(mysqlsess->db,"dummydb"); dcb->func.write = dcbfun; dcb->remote = strdup("0.0.0.0"); dcb->user = strdup("user"); - instance.session->client = (void*)dcb; - instance.session->data = (void*)mysqlsess; + instance.session->client_dcb = (void*)dcb; + instance.session->client_dcb->data = (void*)mysqlsess; getcwd(cwd,sizeof(cwd)); sprintf(tmp,"%s",cwd); mxs_log_init(NULL, tmp, MXS_LOG_TARGET_DEFAULT); - + rval = process_opts(argc,argv); - + if(!(instance.thrpool = malloc(instance.thrcount * sizeof(pthread_t)))){ printf("Error: Out of memory\n"); MXS_ERROR("Out of memory\n"); return 1; } - + /**Initialize worker threads*/ pthread_mutex_lock(&instance.work_mtx); size_t thr_num = 1; @@ -100,14 +100,14 @@ void free_buffers() if(instance.buffer){ int i; for(i = 0;i= 0){ close(instance.infile); free(instance.infile_name); @@ -167,7 +167,7 @@ FILTER_PARAMETER** read_params(int* paramc) } pc++; } - + } if(pc >= 64){ do_read = 0; @@ -183,7 +183,7 @@ FILTER_PARAMETER** read_params(int* paramc) } free(names[i]); free(values[i]); - } + } params[pc] = NULL; *paramc = pc; } @@ -207,7 +207,7 @@ int routeQuery(void* ins, void* session, GWBUF* queue) buffsz += strnlen(queue->hint->value,1024); } } - + qstr = calloc(buffsz + 1,sizeof(char)); if(qstr){ @@ -235,7 +235,7 @@ int routeQuery(void* ins, void* session, GWBUF* queue) case HINT_ROUTE_TO_ALL: sprintf(ptr,"|HINT_ROUTE_TO_ALL"); break; - + case HINT_PARAMETER: sprintf(ptr,"|HINT_PARAMETER"); break; @@ -264,9 +264,9 @@ int routeQuery(void* ins, void* session, GWBUF* queue) } if(instance.verbose){ - printf("Query endpoint: %s\n", qstr); + printf("Query endpoint: %s\n", qstr); } - + if(instance.outfile>=0){ write(instance.outfile,qstr,strlen(qstr)); write(instance.outfile,"\n",1); @@ -279,7 +279,7 @@ int routeQuery(void* ins, void* session, GWBUF* queue) int clientReply(void* ins, void* session, GWBUF* queue) { - + if(instance.verbose){ pthread_mutex_lock(&instance.work_mtx); unsigned char* ptr = (unsigned char*)queue->start; @@ -291,13 +291,13 @@ int clientReply(void* ins, void* session, GWBUF* queue) printf("\n"); pthread_mutex_unlock(&instance.work_mtx); } - + if(instance.outfile>=0){ int qlen = queue->end - queue->start; write(instance.outfile,"Reply: ",strlen("Reply: ")); write(instance.outfile,queue->start,qlen); write(instance.outfile,"\n",1); - + } return 1; @@ -313,7 +313,7 @@ int clientReply(void* ins, void* session, GWBUF* queue) int fdgets(int fd, char* buff, int size) { int i = 0; - + while(i < size - 1 && read(fd,&buff[i],1)) { if(buff[i] == '\n' || buff[i] == '\0') @@ -322,7 +322,7 @@ int fdgets(int fd, char* buff, int size) } i++; } - + buff[i] = '\0'; return i; } @@ -365,24 +365,24 @@ int load_query() rval = 1; goto retblock; } - + query_list = tmpbuff; qbuff_sz *= 2; - + } - + query_list[qcount] = calloc((offset + 1),sizeof(char)); strcpy(query_list[qcount],buffer); offset = 0; qcount++; - + } /**TODO check what messes up the first querystring*/ GWBUF** tmpbff = malloc(sizeof(GWBUF*)*(qcount + 1)); if(tmpbff){ for(i = 0;iitem = malloc(sizeof(CONFIG_ITEM)))){ nxt->section = strdup(section); @@ -576,7 +576,7 @@ int load_config( char* fname) while(iter){ item = iter->item; while(item){ - + if(!strcmp("module",item->name)){ if(instance.mod_dir){ @@ -600,7 +600,7 @@ int load_config( char* fname) }else{ if(instance.verbose){ - printf("\t%s\n",iter->section); + printf("\t%s\n",iter->section); } } } @@ -620,7 +620,7 @@ int load_config( char* fname) item = instance.conf->item; } instance.conf = instance.conf->next; - + } cleanup: @@ -629,7 +629,7 @@ int load_config( char* fname) instance.conf = instance.conf->next; item = iter->item; - while(item){ + while(item){ free(item->name); free(item->value); free(item); @@ -651,9 +651,9 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) int sess_err = 0; int x; if(cnf == NULL){ - + fparams = read_params(¶mc); - + }else{ CONFIG* iter = cnf; @@ -661,14 +661,14 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) while(iter){ paramc = -1; item = iter->item; - + while(item){ /**Matching configuration found*/ if(!strcmp(item->name,"module") && !strcmp(item->value,fc->name)){ paramc = 0; item = iter->item; - + while(item){ if(strcmp(item->name,"module") && strcmp(item->name,"type")){ paramc++; @@ -678,7 +678,7 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) item = iter->item; fparams = calloc((paramc + 1),sizeof(FILTER_PARAMETER*)); if(fparams){ - + int i = 0; while(item){ if(strcmp(item->name,"module") != 0 && @@ -740,7 +740,7 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) MXS_WARNING("The filter %s does not support client replies.\n",fc->name); } - if(fc->next && fc->next->next){ + if(fc->next && fc->next->next){ fc->down[i]->routeQuery = (void*)fc->next->instance->routeQuery; fc->down[i]->session = fc->next->session[i]; @@ -775,7 +775,7 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) } } - + if(sess_err){ for(i = 0;ifilter && fc->session[i]){ @@ -788,9 +788,9 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) free(fc->name); free(fc); } - + } - error: + error: if(fparams){ @@ -809,9 +809,9 @@ int load_filter(FILTERCHAIN* fc, CONFIG* cnf) FILTERCHAIN* load_filter_module(char* str) { FILTERCHAIN* flt_ptr = NULL; - if((flt_ptr = calloc(1,sizeof(FILTERCHAIN))) != NULL && + if((flt_ptr = calloc(1,sizeof(FILTERCHAIN))) != NULL && (flt_ptr->session = calloc(instance.session_count,sizeof(SESSION*))) != NULL && - (flt_ptr->down = calloc(instance.session_count,sizeof(DOWNSTREAM*))) != NULL && + (flt_ptr->down = calloc(instance.session_count,sizeof(DOWNSTREAM*))) != NULL && (flt_ptr->up = calloc(instance.session_count,sizeof(UPSTREAM*))) != NULL){ flt_ptr->next = instance.head; } @@ -839,7 +839,7 @@ void route_buffers() fin = instance.buffer_count*instance.session_count, step = (fin/50.f)/fin; FILTERCHAIN* fc = instance.head; - + while(fc->next->next){ fc = fc->next; } @@ -866,7 +866,7 @@ void route_buffers() while(instance.last_ind < instance.session_count){ struct timespec ts1; ts1.tv_sec = 0; - + tprg = ((bprg + (float)instance.last_ind)/fin); if(!instance.verbose){ if(tprg >= trig){ @@ -883,7 +883,7 @@ void route_buffers() instance.sess_ind = 0; instance.last_ind = 0; - + } if(!instance.verbose){ @@ -911,7 +911,7 @@ void work_buffer(void* thr_num) instance.buff_ind < instance.buffer_count) { struct timespec ts1; - ts1.tv_sec = 0; + ts1.tv_sec = 0; if(instance.head->instance->routeQuery(instance.head->filter, @@ -954,7 +954,7 @@ GWBUF* gen_packet(PACKET pkt) if(psize > 0){ buff = gwbuf_alloc(psize); ptr = (unsigned char*)buff->start; - + switch(pkt){ case PACKET_OK: @@ -1007,8 +1007,8 @@ int process_opts(int argc, char** argv) return 1; } - - if( (rval = lseek(fd,0,SEEK_END)) < 0 || + + if( (rval = lseek(fd,0,SEEK_END)) < 0 || lseek(fd,0,SEEK_SET) < 0){ printf("Error: Cannot seek file.\n"); close(fd); @@ -1033,9 +1033,9 @@ int process_opts(int argc, char** argv) } tok = strtok_r(NULL,"=",&saveptr); } - - - + + + free(buff); instance.verbose = 1; @@ -1114,7 +1114,7 @@ int process_opts(int argc, char** argv) break; default: - + break; } diff --git a/server/modules/protocol/mysql_auth.c b/server/modules/protocol/mysql_auth.c index 815226a10..94b7f1d46 100644 --- a/server/modules/protocol/mysql_auth.c +++ b/server/modules/protocol/mysql_auth.c @@ -549,3 +549,4 @@ static int combined_auth_check( auth_ret = check_db_name_after_auth(dcb, database, auth_ret); return auth_ret; } + diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index af3a1b6c8..7a7b86bb4 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -139,7 +139,7 @@ static MYSQL_session* gw_get_shared_session_auth_info(DCB* dcb) if (dcb->session->state != SESSION_STATE_ALLOC && dcb->session->state != SESSION_STATE_DUMMY) { - auth_info = dcb->session->data; + auth_info = dcb->session->client_dcb->data; } else { @@ -549,8 +549,8 @@ static int gw_read_backend_event(DCB *dcb) * still listening the socket for replies. */ if (dcb->session->state == SESSION_STATE_ROUTER_READY && - dcb->session->client != NULL && - dcb->session->client->state == DCB_STATE_POLLING && + dcb->session->client_dcb != NULL && + dcb->session->client_dcb->state == DCB_STATE_POLLING && (session->router_session || router->getCapabilities() & RCAP_TYPE_NO_RSESSION)) { client_protocol = SESSION_PROTOCOL(dcb->session, @@ -571,7 +571,7 @@ static int gw_read_backend_event(DCB *dcb) } goto return_rc; } - else if (dcb->session->client->dcb_role == DCB_ROLE_INTERNAL) + else if (dcb->session->client_dcb->dcb_role == DCB_ROLE_INTERNAL) { gwbuf_set_type(read_buffer, GWBUF_TYPE_MYSQL); router->clientReply(router_instance, session->router_session, read_buffer, dcb); @@ -614,14 +614,14 @@ static int gw_write_backend_event(DCB *dcb) { data = (uint8_t *)GWBUF_DATA(dcb->writeq); - if (dcb->session->client == NULL) + if (dcb->session->client_dcb == NULL) { rc = 0; } else if (!(MYSQL_IS_COM_QUIT(data))) { /*< vraa : errorHandle */ - mysql_send_custom_error(dcb->session->client, + mysql_send_custom_error(dcb->session->client_dcb, 1, 0, "Writing to backend failed due invalid Maxscale " @@ -937,14 +937,14 @@ static int gw_create_backend_connection(DCB *backend_dcb, } /** Copy client flags to backend protocol */ - if (backend_dcb->session->client->protocol) + if (backend_dcb->session->client_dcb->protocol) { /** Copy client flags to backend protocol */ protocol->client_capabilities = - ((MySQLProtocol *)(backend_dcb->session->client->protocol))->client_capabilities; + ((MySQLProtocol *)(backend_dcb->session->client_dcb->protocol))->client_capabilities; /** Copy client charset to backend protocol */ protocol->charset = - ((MySQLProtocol *)(backend_dcb->session->client->protocol))->charset; + ((MySQLProtocol *)(backend_dcb->session->client_dcb->protocol))->charset; } else { @@ -971,7 +971,7 @@ static int gw_create_backend_connection(DCB *backend_dcb, server->name, server->port, protocol->fd, - session->client->fd); + session->client_dcb->fd); break; case 1: @@ -984,7 +984,7 @@ static int gw_create_backend_connection(DCB *backend_dcb, server->name, server->port, protocol->fd, - session->client->fd); + session->client_dcb->fd); break; default: @@ -996,7 +996,7 @@ static int gw_create_backend_connection(DCB *backend_dcb, server->name, server->port, protocol->fd, - session->client->fd); + session->client_dcb->fd); break; } /*< switch */ @@ -1148,7 +1148,7 @@ static int gw_backend_close(DCB *dcb) CHK_SESSION(session); /** * The lock is needed only to protect the read of session->state and - * session->client values. Client's state may change by other thread + * session->client_dcb values. Client's state may change by other thread * but client's close and adding client's DCB to zombies list is executed * only if client's DCB's state does _not_ change in parallel. */ @@ -1158,16 +1158,16 @@ static int gw_backend_close(DCB *dcb) * Otherwise only this backend connection is closed. */ if (session->state == SESSION_STATE_STOPPING && - session->client != NULL) + session->client_dcb != NULL) { - if (session->client->state == DCB_STATE_POLLING) + if (session->client_dcb->state == DCB_STATE_POLLING) { DCB *temp; spinlock_release(&session->ses_lock); /** Close client DCB */ - temp = session->client; - session->client = NULL; + temp = session->client_dcb; + session->client_dcb = NULL; dcb_close(temp); } else @@ -1243,7 +1243,7 @@ static int backend_write_delayqueue(DCB *dcb) MYSQL_session* mses; GWBUF* new_packet; - mses = (MYSQL_session *)dcb->session->client->data; + mses = (MYSQL_session *)dcb->session->client_dcb->data; new_packet = gw_create_change_user_packet(mses, (MySQLProtocol *)dcb->protocol); /** @@ -1327,9 +1327,9 @@ static int gw_change_user(DCB *backend, int rv = -1; int auth_ret = 1; - current_session = (MYSQL_session *)in_session->client->data; + current_session = (MYSQL_session *)in_session->client_dcb->data; backend_protocol = backend->protocol; - client_protocol = in_session->client->protocol; + client_protocol = in_session->client_dcb->protocol; /* now get the user, after 4 bytes header and 1 byte command */ client_auth_packet += 5; @@ -1386,7 +1386,7 @@ static int gw_change_user(DCB *backend, * decode the token and check the password. * Note: if auth_token_len == 0 && auth_token == NULL, user is without password */ - auth_ret = gw_check_mysql_scramble_data(backend->session->client, + auth_ret = gw_check_mysql_scramble_data(backend->session->client_dcb, auth_token, auth_token_len, client_protocol->scramble, @@ -1396,11 +1396,11 @@ static int gw_change_user(DCB *backend, if (auth_ret != 0) { - if (!service_refresh_users(backend->session->client->service)) + if (!service_refresh_users(backend->session->client_dcb->service)) { /* Try authentication again with new repository data */ /* Note: if no auth client authentication will fail */ - auth_ret = gw_check_mysql_scramble_data(backend->session->client, + auth_ret = gw_check_mysql_scramble_data(backend->session->client_dcb, auth_token, auth_token_len, client_protocol->scramble, sizeof(client_protocol->scramble), @@ -1441,7 +1441,7 @@ static int gw_change_user(DCB *backend, * reply to the client. */ message = create_auth_fail_str(username, - backend->session->client->remote, + backend->session->client_dcb->remote, password_set, "", auth_ret); diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index e5f341db9..b0dbd4aef 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -542,14 +542,8 @@ int gw_read_client_event(DCB* dcb) protocol->protocol_auth_state = MYSQL_AUTH_FAILED; mysql_client_auth_error_handling(dcb, auth_val); /** - * Release MYSQL_session since it is not used anymore. + * Close DCB and which will release MYSQL_session */ - if (!DCB_IS_CLONE(dcb)) - { - free(dcb->data); - } - dcb->data = NULL; - dcb_close(dcb); } /* One way or another, the buffer is now fully processed */ diff --git a/server/modules/protocol/mysql_common.c b/server/modules/protocol/mysql_common.c index a8d894e1c..b147e4216 100644 --- a/server/modules/protocol/mysql_common.c +++ b/server/modules/protocol/mysql_common.c @@ -1282,7 +1282,7 @@ int gw_send_change_user_to_backend(char *dbname, int rc; MYSQL_session* mses; - mses = (MYSQL_session*)conn->owner_dcb->session->client->data; + mses = (MYSQL_session*)conn->owner_dcb->session->client_dcb->data; buffer = gw_create_change_user_packet(mses, conn); rc = conn->owner_dcb->func.write(conn->owner_dcb, buffer); diff --git a/server/modules/routing/binlog/blr.c b/server/modules/routing/binlog/blr.c index 629eb99c9..b852f5eb9 100644 --- a/server/modules/routing/binlog/blr.c +++ b/server/modules/routing/binlog/blr.c @@ -184,7 +184,7 @@ GetModuleObject() * The process of creating the instance causes the router to register * with the master server and begin replication of the binlogs from * the master server to MaxScale. - * + * * @param service The service this router is being create for * @param options An array of options for this query router * @@ -456,7 +456,7 @@ char task_name[BLRM_TASK_NAME_LEN+1] = ""; break; } inst->burst_size = size; - + } else if (strcmp(options[i], "heartbeat") == 0) { @@ -614,7 +614,7 @@ char task_name[BLRM_TASK_NAME_LEN+1] = ""; " Fix errors in it or configure with CHANGE MASTER TO ...", inst->service->name, inst->binlogdir); } - + /* Set service user or load db users */ blr_set_service_mysql_user(inst->service); @@ -626,7 +626,7 @@ char task_name[BLRM_TASK_NAME_LEN+1] = ""; } /** - * Initialise the binlog router + * Initialise the binlog router */ if (inst->master_state == BLRM_UNCONNECTED) { @@ -754,7 +754,7 @@ ROUTER_SLAVE *slave; slave->uuid = NULL; slave->hostname = NULL; spinlock_init(&slave->catch_lock); - slave->dcb = session->client; + slave->dcb = session->client_dcb; slave->router = inst; #ifdef BLFILE_IN_SLAVE slave->file = NULL; @@ -775,7 +775,7 @@ ROUTER_SLAVE *slave; spinlock_release(&inst->lock); CHK_CLIENT_RSES(slave); - + return (void *)slave; } @@ -798,10 +798,10 @@ static void freeSession( ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)router_instance; ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_client_ses; int prev_val; - + prev_val = atomic_add(&router->stats.n_slaves, -1); ss_dassert(prev_val > 0); - + /* * Remove the slave session form the list of slaves that are using the * router currently. @@ -811,11 +811,11 @@ int prev_val; router->slaves = slave->next; } else { ROUTER_SLAVE *ptr = router->slaves; - + while (ptr != NULL && ptr->next != slave) { ptr = ptr->next; } - + if (ptr != NULL) { ptr->next = slave->next; } @@ -846,7 +846,7 @@ int prev_val; * @param instance The router instance data * @param router_session The session being closed */ -static void +static void closeSession(ROUTER *instance, void *router_session) { ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance; @@ -927,12 +927,12 @@ ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_session; * @param queue The queue of data buffers to route * @return The number of bytes sent */ -static int +static int routeQuery(ROUTER *instance, void *router_session, GWBUF *queue) { ROUTER_INSTANCE *router = (ROUTER_INSTANCE *)instance; ROUTER_SLAVE *slave = (ROUTER_SLAVE *)router_session; - + return blr_slave_request(router, slave, queue); } @@ -1020,7 +1020,7 @@ struct tm tm; min15 /= 15.0; min10 /= 10.0; min5 /= 5.0; - + if (router_inst->master) dcb_printf(dcb, "\tMaster connection DCB: %p\n", router_inst->master); @@ -1032,7 +1032,7 @@ struct tm tm; localtime_r(&router_inst->stats.lastReply, &tm); asctime_r(&tm, buf); - + dcb_printf(dcb, "\tBinlog directory: %s\n", router_inst->binlogdir); dcb_printf(dcb, "\tHeartbeat period (seconds): %lu\n", @@ -1046,7 +1046,7 @@ struct tm tm; dcb_printf(dcb, "\tCurrent binlog position: %lu\n", router_inst->current_pos); if (router_inst->trx_safe) { - if (router_inst->pending_transaction) { + if (router_inst->pending_transaction) { dcb_printf(dcb, "\tCurrent open transaction pos: %lu\n", router_inst->binlog_position); } @@ -1273,7 +1273,7 @@ struct tm tm; } else if ((session->cstate & CS_UPTODATE) == 0) { - dcb_printf(dcb, "\t\tSlave_mode: catchup. %s%s\n", + dcb_printf(dcb, "\t\tSlave_mode: catchup. %s%s\n", ((session->cstate & CS_EXPECTCB) == 0 ? "" : "Waiting for DCB queue to drain."), ((session->cstate & CS_BUSY) == 0 ? "" : @@ -1426,35 +1426,35 @@ unsigned long mysql_errno; } /** to be inline'd */ -/** +/** * @node Acquires lock to router client session if it is not closed. * * Parameters: * @param rses - in, use - * + * * * @return true if router session was not closed. If return value is true * it means that router is locked, and must be unlocked later. False, if * router was closed before lock was acquired. * - * + * * @details (write detailed description here) * */ static bool rses_begin_locked_router_action(ROUTER_SLAVE *rses) { bool succp = false; - + CHK_CLIENT_RSES(rses); spinlock_acquire(&rses->rses_lock); succp = true; - + return succp; } /** to be inline'd */ -/** +/** * @node Releases router client session lock. * * Parameters: @@ -1463,7 +1463,7 @@ static bool rses_begin_locked_router_action(ROUTER_SLAVE *rses) * * @return void * - * + * * @details (write detailed description here) * */ @@ -1569,7 +1569,7 @@ GWBUF *ret; *ptr++ = 0; *ptr++ = 0; *ptr++ = 1; - *ptr = 0; // OK + *ptr = 0; // OK return slave->dcb->func.write(slave->dcb, ret); } @@ -1591,7 +1591,7 @@ GWBUF *ret; * */ int -blr_send_custom_error(DCB *dcb, int packet_number, int affected_rows, char *msg, char *statemsg, unsigned int errcode) +blr_send_custom_error(DCB *dcb, int packet_number, int affected_rows, char *msg, char *statemsg, unsigned int errcode) { uint8_t *outbuf = NULL; uint32_t mysql_payload_size = 0; @@ -1604,8 +1604,8 @@ unsigned int mysql_errno = 0; const char *mysql_error_msg = NULL; const char *mysql_state = NULL; GWBUF *errbuf = NULL; - - if (errcode == 0) + + if (errcode == 0) mysql_errno = 1064; else mysql_errno = errcode; @@ -1615,52 +1615,52 @@ GWBUF *errbuf = NULL; mysql_state = "42000"; else mysql_state = statemsg; - + field_count = 0xff; gw_mysql_set_byte2(mysql_err, mysql_errno); mysql_statemsg[0]='#'; memcpy(mysql_statemsg+1, mysql_state, 5); - + if (msg != NULL) { mysql_error_msg = msg; } - - mysql_payload_size = sizeof(field_count) + - sizeof(mysql_err) + - sizeof(mysql_statemsg) + + + mysql_payload_size = sizeof(field_count) + + sizeof(mysql_err) + + sizeof(mysql_statemsg) + strlen(mysql_error_msg); - + /** allocate memory for packet header + payload */ errbuf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size); ss_dassert(errbuf != NULL); - + if (errbuf == NULL) { return 0; } outbuf = GWBUF_DATA(errbuf); - + /** write packet header and packet number */ gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size); mysql_packet_header[3] = packet_number; - + /** write header */ memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header)); - + mysql_payload = outbuf + sizeof(mysql_packet_header); - + /** write field */ memcpy(mysql_payload, &field_count, sizeof(field_count)); mysql_payload = mysql_payload + sizeof(field_count); - + /** write errno */ memcpy(mysql_payload, mysql_err, sizeof(mysql_err)); mysql_payload = mysql_payload + sizeof(mysql_err); - + /** write sqlstate */ memcpy(mysql_payload, mysql_statemsg, sizeof(mysql_statemsg)); mysql_payload = mysql_payload + sizeof(mysql_statemsg); - + /** write error message */ memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg)); diff --git a/server/modules/routing/debugcli.c b/server/modules/routing/debugcli.c index 3dcbe0a31..8bae9bf1c 100644 --- a/server/modules/routing/debugcli.c +++ b/server/modules/routing/debugcli.c @@ -121,7 +121,7 @@ GetModuleObject() /** * Create an instance of the router for a particular service * within the gateway. - * + * * @param service The service this router is being create for * @param options Any array of options for the query router * @@ -202,14 +202,14 @@ CLI_SESSION *client; session->state = SESSION_STATE_READY; client->mode = inst->mode; - dcb_printf(session->client, "Welcome the MariaDB Corporation MaxScale Debug Interface (%s).\n", + dcb_printf(session->client_dcb, "Welcome the MariaDB Corporation MaxScale Debug Interface (%s).\n", version_str); if (client->mode == CLIM_DEVELOPER) { - dcb_printf(session->client, "WARNING: This interface is meant for developer usage,\n"); - dcb_printf(session->client, "passing incorrect addresses to commands can endanger your MaxScale server.\n\n"); + dcb_printf(session->client_dcb, "WARNING: This interface is meant for developer usage,\n"); + dcb_printf(session->client_dcb, "passing incorrect addresses to commands can endanger your MaxScale server.\n\n"); } - dcb_printf(session->client, "Type help for a list of available commands.\n\n"); + dcb_printf(session->client_dcb, "Type help for a list of available commands.\n\n"); return (void *)client; } @@ -221,7 +221,7 @@ CLI_SESSION *client; * @param instance The router instance data * @param router_session The session being closed */ -static void +static void closeSession(ROUTER *instance, void *router_session) { CLI_INSTANCE *inst = (CLI_INSTANCE *)instance; @@ -270,7 +270,7 @@ static void freeSession( * @param queue The queue of data buffers to route * @return The number of bytes sent */ -static int +static int execute(ROUTER *instance, void *router_session, GWBUF *queue) { CLI_SESSION *session = (CLI_SESSION *)router_session; @@ -285,9 +285,9 @@ CLI_SESSION *session = (CLI_SESSION *)router_session; if (strrchr(session->cmdbuf, '\n')) { if (execute_cmd(session)) - dcb_printf(session->session->client, "MaxScale> "); + dcb_printf(session->session->client_dcb, "MaxScale> "); else - dcb_close(session->session->client); + dcb_close(session->session->client_dcb); } return 1; } diff --git a/server/modules/routing/debugcmd.c b/server/modules/routing/debugcmd.c index 8000f1f41..0546d4154 100644 --- a/server/modules/routing/debugcmd.c +++ b/server/modules/routing/debugcmd.c @@ -927,7 +927,7 @@ convert_arg(int mode, char *arg, int arg_type) int execute_cmd(CLI_SESSION *cli) { - DCB *dcb = cli->session->client; + DCB *dcb = cli->session->client_dcb; int argc, i, j, found = 0; char *args[MAXARGS + 1]; unsigned long arg1, arg2, arg3; diff --git a/server/modules/routing/maxinfo/maxinfo.c b/server/modules/routing/maxinfo/maxinfo.c index 1bb98f8e0..0de78991d 100644 --- a/server/modules/routing/maxinfo/maxinfo.c +++ b/server/modules/routing/maxinfo/maxinfo.c @@ -147,7 +147,7 @@ GetModuleObject() /** * Create an instance of the router for a particular service * within the gateway. - * + * * @param service The service this router is being create for * @param options Any array of options for the query router * @@ -211,7 +211,7 @@ INFO_SESSION *client; return NULL; } client->session = session; - client->dcb = session->client; + client->dcb = session->client_dcb; client->queue = NULL; spinlock_acquire(&inst->lock); @@ -231,7 +231,7 @@ INFO_SESSION *client; * @param instance The router instance data * @param router_session The session being closed */ -static void +static void closeSession(ROUTER *instance, void *router_session) { INFO_INSTANCE *inst = (INFO_INSTANCE *)instance; @@ -309,19 +309,19 @@ static void handleError( } spinlock_acquire(&session->ses_lock); sesstate = session->state; - client_dcb = session->client; - + client_dcb = session->client_dcb; + if (sesstate == SESSION_STATE_ROUTER_READY) { CHK_DCB(client_dcb); - spinlock_release(&session->ses_lock); + spinlock_release(&session->ses_lock); client_dcb->func.write(client_dcb, gwbuf_clone(errbuf)); } - else + else { spinlock_release(&session->ses_lock); } - + /** false because connection is not available anymore */ dcb_close(backend_dcb); *succp = false; @@ -336,7 +336,7 @@ static void handleError( * @param queue The queue of data buffers to route * @return The number of bytes sent */ -static int +static int execute(ROUTER *rinstance, void *router_session, GWBUF *queue) { INFO_INSTANCE *instance = (INFO_INSTANCE *)rinstance; @@ -471,7 +471,7 @@ int len; *ptr++ = 0; *ptr++ = 0; *ptr++ = 1; - *ptr = 0; // OK + *ptr = 0; // OK return session->dcb->func.write(session->dcb, ret); } @@ -501,7 +501,7 @@ RESULT_ROW *row; /** * The hardwired select @@vercom response - * + * * @param dcb The DCB of the client */ static void @@ -549,7 +549,7 @@ static char buf[40]; /** * The hardwired select ... as starttime response - * + * * @param dcb The DCB of the client */ static void @@ -705,7 +705,7 @@ static struct uri_table { * @param queue The queue of data buffers to route * @return The number of bytes sent */ -static int +static int handle_url(INFO_INSTANCE *instance, INFO_SESSION *session, GWBUF *queue) { char *uri; diff --git a/server/modules/routing/readconnroute.c b/server/modules/routing/readconnroute.c index 890a9efe4..ad830ed27 100644 --- a/server/modules/routing/readconnroute.c +++ b/server/modules/routing/readconnroute.c @@ -847,7 +847,7 @@ diagnostics(ROUTER *router, DCB *dcb) static void clientReply(ROUTER *instance, void *router_session, GWBUF *queue, DCB *backend_dcb) { - ss_dassert(backend_dcb->session->client != NULL); + ss_dassert(backend_dcb->session->client_dcb != NULL); SESSION_ROUTE_REPLY(backend_dcb->session, queue); } @@ -886,7 +886,7 @@ static void handleError(ROUTER *instance, void *router_session, GWBUF *errbuf, } spinlock_acquire(&session->ses_lock); sesstate = session->state; - client_dcb = session->client; + client_dcb = session->client_dcb; if (sesstate == SESSION_STATE_ROUTER_READY) { diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index 0aeb149a7..a3d01ecdc 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -65,7 +65,7 @@ MODULE_INFO info = { * 18/07/2013 Massimiliano Pinto routeQuery now handles COM_QUIT * as QUERY_TYPE_SESSION_WRITE * 17/07/2014 Massimiliano Pinto Server connection counter is updated in closeSession - * + * * 09/09/2015 Martin Brampton Modify error handler * 25/09/2015 Martin Brampton Block callback processing when no router session in the DCB * @@ -293,8 +293,8 @@ static bool handle_error_new_connection( DCB* backend_dcb, GWBUF* errmsg); static void handle_error_reply_client( - SESSION* ses, - ROUTER_CLIENT_SES* rses, + SESSION* ses, + ROUTER_CLIENT_SES* rses, DCB* backend_dcb, GWBUF* errmsg); @@ -393,10 +393,10 @@ ROUTER_OBJECT* GetModuleObject() /** * Refresh the instance by the given parameter value. - * + * * @param router Router instance * @param singleparam Parameter fo be reloaded - * + * * Note: this part is not done. Needs refactoring. */ static void refreshInstance( @@ -406,7 +406,7 @@ static void refreshInstance( CONFIG_PARAMETER* param; bool refresh_single; config_param_type_t paramtype; - + if (singleparam != NULL) { param = singleparam; @@ -418,39 +418,39 @@ static void refreshInstance( refresh_single = false; } paramtype = config_get_paramtype(param); - - while (param != NULL) + + while (param != NULL) { /** Catch unused parameter types */ - ss_dassert(paramtype == COUNT_TYPE || + ss_dassert(paramtype == COUNT_TYPE || paramtype == PERCENT_TYPE || paramtype == SQLVAR_TARGET_TYPE); - + if (paramtype == COUNT_TYPE) { if (strncmp(param->name, "max_slave_connections", MAX_PARAM_LEN) == 0) { int val; bool succp; - + router->rwsplit_config.rw_max_slave_conn_percent = 0; - + succp = config_get_valint(&val, param, NULL, paramtype); - + if (succp) { router->rwsplit_config.rw_max_slave_conn_count = val; } } - else if (strncmp(param->name, - "max_slave_replication_lag", + else if (strncmp(param->name, + "max_slave_replication_lag", MAX_PARAM_LEN) == 0) { int val; bool succp; - + succp = config_get_valint(&val, param, NULL, paramtype); - + if (succp) { router->rwsplit_config.rw_max_slave_replication_lag = val; @@ -463,42 +463,42 @@ static void refreshInstance( { int val; bool succp; - + router->rwsplit_config.rw_max_slave_conn_count = 0; - + succp = config_get_valint(&val, param, NULL, paramtype); - + if (succp) { router->rwsplit_config.rw_max_slave_conn_percent = val; - } + } } } else if (paramtype == SQLVAR_TARGET_TYPE) { - if (strncmp(param->name, - "use_sql_variables_in", + if (strncmp(param->name, + "use_sql_variables_in", MAX_PARAM_LEN) == 0) { target_t valtarget; bool succp; - + succp = config_get_valtarget(&valtarget, param, NULL, paramtype); - + if (succp) { router->rwsplit_config.rw_use_sql_variables_in = valtarget; } } } - + if (refresh_single) { break; } param = param->next; } - + #if defined(NOT_USED) /*< can't read monitor config parameters */ if ((*router->servers)->backend_server->rlag == -2) { @@ -508,7 +508,7 @@ static void refreshInstance( { rlag_enabled = true; } - /** + /** * If replication lag detection is not enabled the measure can't be * used in slave selection. */ @@ -521,8 +521,8 @@ static void refreshInstance( "is not enabled. Replication lag will not be checked.", router->rwsplit_config.rw_max_slave_replication_lag); } - - if (router->rwsplit_config.rw_slave_select_criteria == + + if (router->rwsplit_config.rw_slave_select_criteria == LEAST_BEHIND_MASTER) { MXS_WARNING("Configuration Failed, router option " @@ -531,7 +531,7 @@ static void refreshInstance( "is not enabled.\n\t\t " "slave_selection_criteria=%s will be used instead.", STRCRITERIA(DEFAULT_CRITERIA)); - + router->rwsplit_config.rw_slave_select_criteria = DEFAULT_CRITERIA; } @@ -543,7 +543,7 @@ static void refreshInstance( /** * Create an instance of read/write statement router within the MaxScale. * - * + * * @param service The service this router is being create for * @param options The options for this query router * @@ -559,24 +559,24 @@ createInstance(SERVICE *service, char **options) int i; CONFIG_PARAMETER* param; char *weightby; - + if ((router = calloc(1, sizeof(ROUTER_INSTANCE))) == NULL) { - return NULL; - } + return NULL; + } router->service = service; spinlock_init(&router->lock); - + /** Calculate number of servers */ sref = service->dbref; nservers = 0; - + while (sref != NULL) { nservers++; sref=sref->next; } router->servers = (BACKEND **)calloc(nservers + 1, sizeof(BACKEND *)); - + if (router->servers == NULL) { free(router); @@ -590,7 +590,7 @@ createInstance(SERVICE *service, char **options) sref = service->dbref; nservers= 0; - + while (sref != NULL) { if ((router->servers[nservers] = malloc(sizeof(BACKEND))) == NULL) { @@ -703,7 +703,7 @@ createInstance(SERVICE *service, char **options) */ router->bitmask = 0; router->bitvalue = 0; - + /** Call this before refreshInstance */ if (options) { @@ -722,7 +722,7 @@ createInstance(SERVICE *service, char **options) * configured slaves. */ router->rwsplit_config.rw_max_slave_conn_count = nservers; - + if (router->rwsplit_config.rw_slave_select_criteria == UNDEFINED_CRITERIA) { router->rwsplit_config.rw_slave_select_criteria = DEFAULT_CRITERIA; @@ -732,18 +732,18 @@ createInstance(SERVICE *service, char **options) * Finally, copy version number to indicate that configs match. */ param = config_get_param(service->svc_config_param, "max_slave_connections"); - + if (param != NULL) { refreshInstance(router, param); } - /** + /** * Read default value for slave replication lag upper limit and then * configured value if it exists. */ router->rwsplit_config.rw_max_slave_replication_lag = CONFIG_MAX_SLAVE_RLAG; param = config_get_param(service->svc_config_param, "max_slave_replication_lag"); - + if (param != NULL) { refreshInstance(router, param); @@ -766,7 +766,7 @@ createInstance(SERVICE *service, char **options) router->next = instances; instances = router; spinlock_release(&instlock); - + return (ROUTER *)router; } @@ -794,9 +794,9 @@ static void* newSession( int max_slave_rlag; /*< max allowed replication lag for any slave */ int i; const int min_nservers = 1; /*< hard-coded for now */ - + client_rses = (ROUTER_CLIENT_SES *)calloc(1, sizeof(ROUTER_CLIENT_SES)); - + if (client_rses == NULL) { ss_dassert(false); @@ -808,38 +808,38 @@ static void* newSession( #endif client_rses->router = router; - client_rses->client_dcb = session->client; - /** - * If service config has been changed, reload config from service to + client_rses->client_dcb = session->client_dcb; + /** + * If service config has been changed, reload config from service to * router instance first. */ spinlock_acquire(&router->lock); - + if (router->service->svc_config_version > router->rwsplit_version) { /** re-read all parameters to rwsplit config structure */ refreshInstance(router, NULL); /*< scan through all parameters */ /** increment rwsplit router's config version number */ - router->rwsplit_version = router->service->svc_config_version; + router->rwsplit_version = router->service->svc_config_version; /** Read options */ rwsplit_process_router_options(router, router->service->routerOptions); } /** Copy config struct from router instance */ memcpy(&client_rses->rses_config,&router->rwsplit_config,sizeof(rwsplit_config_t)); - + spinlock_release(&router->lock); - /** - * Set defaults to session variables. + /** + * Set defaults to session variables. */ client_rses->rses_autocommit_enabled = true; client_rses->rses_transaction_active = false; client_rses->have_tmp_tables = false; - + router_nservers = router_get_servercount(router); - - if (!have_enough_servers(&client_rses, - min_nservers, - router_nservers, + + if (!have_enough_servers(&client_rses, + min_nservers, + router_nservers, router)) { goto return_rses; @@ -848,16 +848,16 @@ static void* newSession( * Create backend reference objects for this session. */ backend_ref = (backend_ref_t *)calloc(1, router_nservers*sizeof(backend_ref_t)); - + if (backend_ref == NULL) { - /** log this */ + /** log this */ free(client_rses); free(backend_ref); client_rses = NULL; goto return_rses; } - /** + /** * Initialize backend references with BACKEND ptr. * Initialize session command cursors for each backend reference. */ @@ -876,14 +876,14 @@ static void* newSession( backend_ref[i].bref_sescmd_cur.scmd_cur_active = false; backend_ref[i].bref_sescmd_cur.scmd_cur_ptr_property = &client_rses->rses_properties[RSES_PROP_TYPE_SESCMD]; - backend_ref[i].bref_sescmd_cur.scmd_cur_cmd = NULL; + backend_ref[i].bref_sescmd_cur.scmd_cur_cmd = NULL; } max_nslaves = rses_get_max_slavecount(client_rses, router_nservers); max_slave_rlag = rses_get_max_replication_lag(client_rses); - + spinlock_init(&client_rses->rses_lock); client_rses->rses_backend_ref = backend_ref; - + /** * Find a backend servers to connect to. * This command requires that rsession's lock is held. @@ -908,15 +908,15 @@ static void* newSession( router); rses_end_locked_router_action(client_rses); - - /** - * Master and at least slaves must be found + + /** + * Master and at least slaves must be found */ if (!succp) { free(client_rses->rses_backend_ref); free(client_rses); client_rses = NULL; - goto return_rses; + goto return_rses; } /** Copy backend pointers to router session. */ client_rses->rses_master_ref = master_ref; @@ -934,7 +934,7 @@ static void* newSession( } router->stats.n_sessions += 1; - + /** * Version is bigger than zero once initialized. */ @@ -948,7 +948,7 @@ static void* newSession( router->connections = client_rses; spinlock_release(&router->lock); -return_rses: +return_rses: #if defined(SS_DEBUG) if (client_rses != NULL) { @@ -975,10 +975,10 @@ static void closeSession( backend_ref_t* backend_ref; MXS_DEBUG("%lu [RWSplit:closeSession]", pthread_self()); - - /** + + /** * router session can be NULL if newSession failed and it is discarding - * its connections and DCB's. + * its connections and DCB's. */ if (router_session == NULL) { @@ -986,7 +986,7 @@ static void closeSession( } router_cli_ses = (ROUTER_CLIENT_SES *)router_session; CHK_CLIENT_RSES(router_cli_ses); - + backend_ref = router_cli_ses->rses_backend_ref; /** * Lock router client session for secure read and update. @@ -995,7 +995,7 @@ static void closeSession( rses_begin_locked_router_action(router_cli_ses)) { int i; - /** + /** * This sets router closed. Nobody is allowed to use router * without checking this first. */ @@ -1004,7 +1004,7 @@ static void closeSession( for (i=0; irses_nbackends; i++) { backend_ref_t* bref = &backend_ref[i]; - DCB* dcb = bref->bref_dcb; + DCB* dcb = bref->bref_dcb; /** Close those which had been connected */ if (BREF_IS_IN_USE(bref)) { @@ -1018,7 +1018,7 @@ static void closeSession( { ss_dassert(dcb->session->state == SESSION_STATE_STOPPING); } -#endif +#endif /** Clean operation counter in bref and in SERVER */ while (BREF_IS_WAITING_RESULT(bref)) { @@ -1035,17 +1035,17 @@ static void closeSession( } } /** Unlock */ - rses_end_locked_router_action(router_cli_ses); + rses_end_locked_router_action(router_cli_ses); } } /** - * When router session is closed, freeSession can be called to free allocated + * When router session is closed, freeSession can be called to free allocated * resources. - * + * * @param router_instance The router instance the session belongs to * @param router_client_session Client session - * + * */ static void freeSession( ROUTER* router_instance, @@ -1054,10 +1054,10 @@ static void freeSession( ROUTER_CLIENT_SES* router_cli_ses; ROUTER_INSTANCE* router; int i; - + router_cli_ses = (ROUTER_CLIENT_SES *)router_client_session; router = (ROUTER_INSTANCE *)router_instance; - + spinlock_acquire(&router->lock); if (router->connections == router_cli_ses) { @@ -1068,22 +1068,22 @@ static void freeSession( while (ptr && ptr->next != router_cli_ses) { ptr = ptr->next; } - + if (ptr) { ptr->next = router_cli_ses->next; } } spinlock_release(&router->lock); - - /** - * For each property type, walk through the list, finalize properties - * and free the allocated memory. + + /** + * For each property type, walk through the list, finalize properties + * and free the allocated memory. */ for (i=RSES_PROP_TYPE_FIRST; irses_properties[i]; rses_property_t* q = p; - + while (p != NULL) { q = p->rses_prop_next; @@ -1102,18 +1102,18 @@ static void freeSession( } /** - * Provide the router with a pointer to a suitable backend dcb. - * + * Provide the router with a pointer to a suitable backend dcb. + * * Detect failures in server statuses and reselect backends if necessary. - * If name is specified, server name becomes primary selection criteria. - * Similarly, if max replication lag is specified, skip backends which lag too + * If name is specified, server name becomes primary selection criteria. + * Similarly, if max replication lag is specified, skip backends which lag too * much. - * + * * @param p_dcb Address of the pointer to the resulting DCB * @param rses Pointer to router client session * @param btype Backend type * @param name Name of the backend which is primarily searched. May be NULL. - * + * * @return True if proper DCB was found, false otherwise. */ static bool get_dcb( @@ -1128,10 +1128,10 @@ static bool get_dcb( int i; bool succp = false; BACKEND* master_host; - + CHK_CLIENT_RSES(rses); ss_dassert(p_dcb != NULL && *(p_dcb) == NULL); - + if (p_dcb == NULL) { goto return_succp; @@ -1158,29 +1158,29 @@ static bool get_dcb( if (name != NULL) /*< Choose backend by name from a hint */ { ss_dassert(btype != BE_MASTER); /*< Master dominates and no name should be passed with it */ - + for (i=0; irses_nbackends; i++) { - BACKEND* b = backend_ref[i].bref_backend; + BACKEND* b = backend_ref[i].bref_backend; /** * To become chosen: * backend must be in use, name must match, * root master node must be found, - * backend's role must be either slave, relay + * backend's role must be either slave, relay * server, or master. */ if (BREF_IS_IN_USE((&backend_ref[i])) && (strncasecmp( name, - b->backend_server->unique_name, + b->backend_server->unique_name, PATH_MAX) == 0) && - master_bref->bref_backend != NULL && - (SERVER_IS_SLAVE(b->backend_server) || + master_bref->bref_backend != NULL && + (SERVER_IS_SLAVE(b->backend_server) || SERVER_IS_RELAY_SERVER(b->backend_server) || SERVER_IS_MASTER(b->backend_server))) { *p_dcb = backend_ref[i].bref_dcb; - succp = true; + succp = true; ss_dassert(backend_ref[i].bref_dcb->state != DCB_STATE_ZOMBIE); break; } @@ -1194,7 +1194,7 @@ static bool get_dcb( btype = BE_SLAVE; } } - + if (btype == BE_SLAVE) { backend_ref_t* candidate_bref = NULL; @@ -1202,31 +1202,31 @@ static bool get_dcb( for (i=0; irses_nbackends; i++) { BACKEND* b = (&backend_ref[i])->bref_backend; - /** + /** * Unused backend or backend which is not master nor - * slave can't be used + * slave can't be used */ - if (!BREF_IS_IN_USE(&backend_ref[i]) || + if (!BREF_IS_IN_USE(&backend_ref[i]) || (!SERVER_IS_MASTER(b->backend_server) && !SERVER_IS_SLAVE(b->backend_server))) { continue; } - /** + /** * If there are no candidates yet accept both master or * slave. */ else if (candidate_bref == NULL) { - /** - * Ensure that master has not changed dunring + /** + * Ensure that master has not changed dunring * session and abort if it has. */ if (SERVER_IS_MASTER(b->backend_server) && &backend_ref[i] == master_bref) { /** found master */ - candidate_bref = &backend_ref[i]; + candidate_bref = &backend_ref[i]; succp = true; } /** @@ -1244,7 +1244,7 @@ static bool get_dcb( } } /** - * If candidate is master, any slave which doesn't break + * If candidate is master, any slave which doesn't break * replication lag limits replaces it. */ else if (SERVER_IS_MASTER(candidate_bref->bref_backend->backend_server) && @@ -1256,11 +1256,11 @@ static bool get_dcb( { /** found slave */ candidate_bref = &backend_ref[i]; - succp = true; + succp = true; } - /** + /** * When candidate exists, compare it against the current - * backend and update assign it to new candidate if + * backend and update assign it to new candidate if * necessary. */ else if (SERVER_IS_SLAVE(b->backend_server)) @@ -1289,11 +1289,11 @@ static bool get_dcb( { *p_dcb = candidate_bref->bref_dcb; } - + goto return_succp; } /*< if (btype == BE_SLAVE) */ - /** - * If target was originally master only then the execution jumps + /** + * If target was originally master only then the execution jumps * directly here. */ if (btype == BE_MASTER) @@ -1316,22 +1316,22 @@ static bool get_dcb( succp = false; } } - + return_succp: return succp; } /** - * Find out which of the two backend servers has smaller value for select + * Find out which of the two backend servers has smaller value for select * criteria property. - * + * * @param cand previously selected candidate * @param new challenger * @param sc select criteria - * + * * @return pointer to backend reference of that backend server which has smaller - * value in selection criteria. If either reference pointer is NULL then the + * value in selection criteria. If either reference pointer is NULL then the * other reference pointer value is returned. */ static backend_ref_t* check_candidate_bref( @@ -1342,7 +1342,7 @@ static backend_ref_t* check_candidate_bref( int (*p)(const void *, const void *); /** get compare function */ p = criteria_cmpfun[sc]; - + if (new == NULL) { return cand; @@ -1361,12 +1361,12 @@ static backend_ref_t* check_candidate_bref( /** * Examine the query type, transaction state and routing hints. Find out the * target for query routing. - * - * @param qtype Type of query + * + * @param qtype Type of query * @param trx_active Is transacation active or not * @param hint Pointer to list of hints attached to the query buffer - * - * @return bitfield including the routing target, or the target server name + * + * @return bitfield including the routing target, or the target server name * if the query would otherwise be routed to slave. */ static route_target_t get_route_target ( @@ -1388,7 +1388,7 @@ static route_target_t get_route_target ( QUERY_IS_TYPE(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT) || QUERY_IS_TYPE(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT))) { - /** + /** * This is problematic query because it would be routed to all * backends but since this is SELECT that is not possible: * 1. response set is not handled correctly in clientReply and @@ -1403,7 +1403,7 @@ static route_target_t get_route_target ( * the execution of the prepared statements to the right server would be * an easy one. Currently this is not supported. */ - if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) && + if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) && !( QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_STMT) || QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_NAMED_STMT))) { @@ -1414,7 +1414,7 @@ static route_target_t get_route_target ( "query to two, where SQL variable modifications " "are done in the first and the SELECT in the " "second one."); - + target = TARGET_MASTER; } target |= TARGET_ALL; @@ -1437,7 +1437,7 @@ static route_target_t get_route_target ( (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) || QUERY_IS_TYPE(qtype, QUERY_TYPE_SHOW_TABLES) || /*< 'SHOW TABLES' */ /** Configured to allow reading variables from slaves */ - (use_sql_variables_in == TYPE_ALL && + (use_sql_variables_in == TYPE_ALL && (QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_READ) || QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ) || QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_READ))))) @@ -1450,7 +1450,7 @@ static route_target_t get_route_target ( QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_STMT) || QUERY_IS_TYPE(qtype, QUERY_TYPE_PREPARE_NAMED_STMT) || /** Configured not to allow reading variables from slaves */ - (use_sql_variables_in == TYPE_MASTER && + (use_sql_variables_in == TYPE_MASTER && (QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_READ) || QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ)))) { @@ -1468,7 +1468,7 @@ static route_target_t get_route_target ( } else if (hint->type == HINT_ROUTE_TO_NAMED_SERVER) { - /** + /** * Searching for a named server. If it can't be * found, the oroginal target is chosen. */ @@ -1488,8 +1488,8 @@ static route_target_t get_route_target ( else if (hint->type == HINT_PARAMETER) { if (strncasecmp( - (char *)hint->data, - "max_slave_replication_lag", + (char *)hint->data, + "max_slave_replication_lag", strlen("max_slave_replication_lag")) == 0) { target |= TARGET_RLAG_MAX; @@ -1597,7 +1597,7 @@ void check_drop_tmp_table( CHK_DCB(master_dcb); - data = (MYSQL_session*)master_dcb->session->data; + data = (MYSQL_session*)master_dcb->session->client_dcb->data; if(data == NULL) { @@ -1610,7 +1610,7 @@ void check_drop_tmp_table( if (qc_is_drop_table_query(querybuf)) { tbl = qc_get_table_names(querybuf,&tsize,false); - if(tbl != NULL){ + if(tbl != NULL){ for(i = 0; irses_prop_data.temp_tables) { - if (hashtable_delete(rses_prop_tmp->rses_prop_data.temp_tables, + if (hashtable_delete(rses_prop_tmp->rses_prop_data.temp_tables, (void *)hkey)) { MXS_INFO("Temporary table dropped: %s",hkey); @@ -1689,7 +1689,7 @@ static qc_query_type_t is_read_tmp_table( } CHK_DCB(master_dcb); - data = (MYSQL_session*)master_dcb->session->data; + data = (MYSQL_session*)master_dcb->session->client_dcb->data; if(data == NULL) { @@ -1699,25 +1699,25 @@ static qc_query_type_t is_read_tmp_table( dbname = (char*)data->db; - if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) || + if (QUERY_IS_TYPE(qtype, QUERY_TYPE_READ) || QUERY_IS_TYPE(qtype, QUERY_TYPE_LOCAL_READ) || QUERY_IS_TYPE(qtype, QUERY_TYPE_USERVAR_READ) || QUERY_IS_TYPE(qtype, QUERY_TYPE_SYSVAR_READ) || - QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_READ)) + QUERY_IS_TYPE(qtype, QUERY_TYPE_GSYSVAR_READ)) { tbl = qc_get_table_names(querybuf,&tsize,false); if (tbl != NULL && tsize > 0) - { + { /** Query targets at least one table */ for(i = 0; irses_prop_data.temp_tables) { - - if( (target_tmp_table = + + if( (target_tmp_table = (bool)hashtable_fetch(rses_prop_tmp->rses_prop_data.temp_tables,(void *)hkey))) { /**Query target is a temporary table*/ @@ -1730,7 +1730,7 @@ static qc_query_type_t is_read_tmp_table( } } - + if(tbl != NULL){ for(i = 0; isession->data; + data = (MYSQL_session*)master_dcb->session->client_dcb->data; if(data == NULL) { @@ -1810,9 +1810,9 @@ static void check_create_tmp_table( bool is_temp = true; char* tblname = NULL; - + tblname = qc_get_created_table_name(querybuf); - + if (tblname && strlen(tblname) > 0) { klen = strlen(dbname) + strlen(tblname) + 2; @@ -1825,10 +1825,10 @@ static void check_create_tmp_table( { hkey = NULL; } - + if(rses_prop_tmp == NULL) { - if((rses_prop_tmp = + if((rses_prop_tmp = (rses_property_t*)calloc(1,sizeof(rses_property_t)))) { #if defined(SS_DEBUG) @@ -1859,7 +1859,7 @@ static void check_create_tmp_table( } } - + if (hkey && rses_prop_tmp->rses_prop_data.temp_tables && hashtable_add(rses_prop_tmp->rses_prop_data.temp_tables, (void *)hkey, @@ -1869,7 +1869,7 @@ static void check_create_tmp_table( } #if defined(SS_DEBUG) { - bool retkey = + bool retkey = hashtable_fetch( rses_prop_tmp->rses_prop_data.temp_tables, hkey); @@ -1880,7 +1880,7 @@ static void check_create_tmp_table( } #endif } - + free(hkey); free(tblname); } @@ -1888,9 +1888,9 @@ static void check_create_tmp_table( /** * Get client DCB pointer of the router client session. * This routine must be protected by Router client session lock. - * + * * @param rses Router client session pointer - * + * * @return Pointer to client DCB */ static DCB* rses_get_client_dcb( @@ -1898,15 +1898,15 @@ static DCB* rses_get_client_dcb( { DCB* dcb = NULL; int i; - + for (i=0; irses_nbackends; i++) { if ((dcb = rses->rses_backend_ref[i].bref_dcb) != NULL && BREF_IS_IN_USE(&rses->rses_backend_ref[i]) && dcb->session != NULL && - dcb->session->client != NULL) + dcb->session->client_dcb != NULL) { - return dcb->session->client; + return dcb->session->client_dcb; } } return NULL; @@ -1931,7 +1931,7 @@ static DCB* rses_get_client_dcb( * If routeQuery fails, it means that router session has failed. * In any tolerated failure, handleError is called and if necessary, * an error message is sent to the client. - * + * * For now, routeQuery don't tolerate errors, so any error will close * the session. vraa 14.6.14 */ @@ -1948,17 +1948,17 @@ static int routeQuery( CHK_CLIENT_RSES(router_cli_ses); /** * GWBUF is called "type undefined" when the incoming data isn't parsed - * and MySQL packets haven't been extracted to separate buffers. + * and MySQL packets haven't been extracted to separate buffers. * "Undefined" == "untyped". * Untyped GWBUF means that it can consist of incomplete and/or multiple - * MySQL packets. - * Read and route found MySQL packets one by one and store potential + * MySQL packets. + * Read and route found MySQL packets one by one and store potential * incomplete packet to DCB's dcb_readqueue. */ if (GWBUF_IS_TYPE_UNDEFINED(querybuf)) { GWBUF* tmpbuf = querybuf; - do + do { /** * Try to read complete MySQL packet from tmpbuf. @@ -1969,7 +1969,7 @@ static int routeQuery( if (GWBUF_LENGTH(tmpbuf) > 0) { DCB* dcb = rses_get_client_dcb(router_cli_ses); - + dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, tmpbuf); } succp = true; @@ -1979,21 +1979,21 @@ static int routeQuery( gwbuf_set_type(querybuf, GWBUF_TYPE_MYSQL); gwbuf_set_type(querybuf, GWBUF_TYPE_SINGLE_STMT); - /** + /** * If router is closed, discard the packet */ if (router_cli_ses->rses_closed) { uint8_t* packet; mysql_server_cmd_t packet_type; - + packet = GWBUF_DATA(querybuf); packet_type = packet[4]; - + if (packet_type != MYSQL_COM_QUIT) { char* query_str = modutil_get_query(querybuf); - + MXS_ERROR("Can't route %s:\"%s\" to " "backend server. Router is closed.", STRPACKETTYPE(packet_type), @@ -2009,23 +2009,23 @@ static int routeQuery( succp = route_single_stmt(inst, router_cli_ses, querybuf); } } - while (tmpbuf != NULL); + while (tmpbuf != NULL); } - /** + /** * If router is closed, discard the packet */ else if (router_cli_ses->rses_closed) { uint8_t* packet; mysql_server_cmd_t packet_type; - + packet = GWBUF_DATA(querybuf); packet_type = packet[4]; - + if (packet_type != MYSQL_COM_QUIT) { char* query_str = modutil_get_query(querybuf); - + MXS_ERROR("Can't route %s:\"%s\" to " "backend server. Router is closed.", STRPACKETTYPE(packet_type), @@ -2040,15 +2040,15 @@ static int routeQuery( succp = route_single_stmt(inst, router_cli_ses, querybuf); } - + retblock: #if defined(SS_DEBUG2) if (querybuf != NULL) { char* canonical_query_str; - + canonical_query_str = skygw_get_canonical(querybuf); - + if (canonical_query_str != NULL) { MXS_INFO("Canonical version: %s", canonical_query_str); @@ -2064,12 +2064,12 @@ retblock: /** - * Routing function. Find out query type, backend type, and target DCB(s). + * Routing function. Find out query type, backend type, and target DCB(s). * Then route query to found target(s). * @param inst router instance * @param rses router session * @param querybuf GWBUF including the query - * + * * @return true if routing succeed or if it failed due to unsupported query. * false if backend failure was encountered. */ @@ -2093,9 +2093,9 @@ static bool route_single_stmt( ss_dassert(querybuf->next == NULL); // The buffer must be contiguous. ss_dassert(!GWBUF_IS_TYPE_UNDEFINED(querybuf)); - /** - * Read stored master DCB pointer. If master is not set, routing must - * be aborted + /** + * Read stored master DCB pointer. If master is not set, routing must + * be aborted */ if ((master_dcb = rses->rses_master_ref->bref_dcb) == NULL) { @@ -2114,7 +2114,7 @@ static bool route_single_stmt( packet = GWBUF_DATA(querybuf); packet_len = gw_mysql_get_byte3(packet); - + if(packet_len == 0) { /** Empty packet signals end of LOAD DATA LOCAL INFILE, send it to master*/ @@ -2138,7 +2138,7 @@ static bool route_single_stmt( case MYSQL_COM_CHANGE_USER: /*< 11 all servers change it accordingly */ qtype = QUERY_TYPE_SESSION_WRITE; break; - + case MYSQL_COM_CREATE_DB: /**< 5 DDL must go to the master */ case MYSQL_COM_DROP_DB: /**< 6 DDL must go to the master */ case MYSQL_COM_STMT_CLOSE: /*< free prepared statement */ @@ -2146,21 +2146,21 @@ static bool route_single_stmt( case MYSQL_COM_STMT_RESET: /*< resets the data of a prepared statement */ qtype = QUERY_TYPE_WRITE; break; - + case MYSQL_COM_QUERY: qtype = qc_get_type(querybuf); break; - + case MYSQL_COM_STMT_PREPARE: qtype = qc_get_type(querybuf); qtype |= QUERY_TYPE_PREPARE_STMT; break; - + case MYSQL_COM_STMT_EXECUTE: /** Parsing is not needed for this type of packet */ qtype = QUERY_TYPE_EXEC_STMT; break; - + case MYSQL_COM_SHUTDOWN: /**< 8 where should shutdown be routed ? */ case MYSQL_COM_STATISTICS: /**< 9 ? */ case MYSQL_COM_PROCESS_INFO: /**< 0a ? */ @@ -2172,7 +2172,7 @@ static bool route_single_stmt( default: break; } /**< switch by packet type */ - + if (!rses_begin_locked_router_action(rses)) { succp = false; @@ -2220,7 +2220,7 @@ static bool route_single_stmt( QUERY_IS_TYPE(qtype, QUERY_TYPE_DISABLE_AUTOCOMMIT)) { rses->rses_autocommit_enabled = false; - + if (!rses->rses_transaction_active) { rses->rses_transaction_active = true; @@ -2231,7 +2231,7 @@ static bool route_single_stmt( { rses->rses_transaction_active = true; } - /** + /** * Explicit COMMIT and ROLLBACK, implicit COMMIT. */ if (rses->rses_autocommit_enabled && @@ -2240,14 +2240,14 @@ static bool route_single_stmt( QUERY_IS_TYPE(qtype,QUERY_TYPE_ROLLBACK))) { rses->rses_transaction_active = false; - } + } else if (!rses->rses_autocommit_enabled && QUERY_IS_TYPE(qtype, QUERY_TYPE_ENABLE_AUTOCOMMIT)) { rses->rses_autocommit_enabled = true; rses->rses_transaction_active = false; - } - + } + if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) { if (!rses->rses_load_active) @@ -2279,13 +2279,13 @@ static bool route_single_stmt( "%lu bytes sent.", rses->rses_load_data_sent); } } - /** - * Find out where to route the query. Result may not be clear; it is + /** + * Find out where to route the query. Result may not be clear; it is * possible to have a hint for routing to a named server which can - * be either slave or master. - * If query would otherwise be routed to slave then the hint determines + * be either slave or master. + * If query would otherwise be routed to slave then the hint determines * actual target server if it exists. - * + * * route_target is a bitfield and may include : * TARGET_ALL * - route to all connected backend servers @@ -2293,10 +2293,10 @@ static bool route_single_stmt( * - route primarily according to hints, then to slave and if those * failed, eventually to master * TARGET_MASTER[|TARGET_NAMED_SERVER|TARGET_RLAG_MAX] - * - route primarily according to the hints and if they failed, + * - route primarily according to the hints and if they failed, * eventually to master */ - route_target = get_route_target(qtype, + route_target = get_route_target(qtype, rses->rses_transaction_active, rses->rses_load_active, rses->rses_config.rw_use_sql_variables_in, @@ -2305,14 +2305,14 @@ static bool route_single_stmt( if (TARGET_IS_ALL(route_target)) { /** Multiple, conflicting routing target. Return error */ - if (TARGET_IS_MASTER(route_target) || + if (TARGET_IS_MASTER(route_target) || TARGET_IS_SLAVE(route_target)) { backend_ref_t* bref = rses->rses_backend_ref; - + char* query_str = modutil_get_query(querybuf); char* qtype_str = qc_get_qtype_str(qtype); - + MXS_ERROR("Can't route %s:%s:\"%s\". SELECT with " "session data modification is not supported " "if configuration parameter " @@ -2320,19 +2320,19 @@ static bool route_single_stmt( STRPACKETTYPE(packet_type), qtype_str, (query_str == NULL ? "(empty)" : query_str)); - + MXS_INFO("Unable to route the query " "without losing session data " "modification from other " "servers. <"); - + while (bref != NULL && !BREF_IS_IN_USE(bref)) { bref++; } - + if (bref != NULL && BREF_IS_IN_USE(bref)) - { + { /** Create and add MySQL error to eventqueue */ modutil_reply_parse_error( bref->bref_dcb, @@ -2344,7 +2344,7 @@ static bool route_single_stmt( } else { - /** + /** * If there were no available backend references * available return false - session will be closed */ @@ -2364,12 +2364,12 @@ static bool route_single_stmt( * Router locking is done inside the function. */ succp = route_session_write( - rses, - gwbuf_clone(querybuf), - inst, - packet_type, + rses, + gwbuf_clone(querybuf), + inst, + packet_type, qtype); - + if (succp) { atomic_add(&inst->stats.n_all, 1); @@ -2383,7 +2383,7 @@ static bool route_single_stmt( if (packet_type != MYSQL_COM_QUIT) { char* query_str = modutil_get_query(querybuf); - + MXS_ERROR("Can't route %s:%s:\"%s\" to " "backend server. Router is closed.", STRPACKETTYPE(packet_type), @@ -2396,7 +2396,7 @@ static bool route_single_stmt( } /** * There is a hint which either names the target backend or - * hint which sets maximum allowed replication lag for the + * hint which sets maximum allowed replication lag for the * backend. */ if (TARGET_IS_NAMED_SERVER(route_target) || @@ -2404,15 +2404,15 @@ static bool route_single_stmt( { HINT* hint; char* named_server = NULL; - + hint = querybuf->hint; - + while (hint != NULL) { if (hint->type == HINT_ROUTE_TO_NAMED_SERVER) { /** - * Set the name of searched + * Set the name of searched * backend server. */ named_server = hint->data; @@ -2425,14 +2425,14 @@ static bool route_single_stmt( "max_slave_replication_lag", strlen("max_slave_replication_lag")) == 0)) { - int val = (int) strtol((char *)hint->value, + int val = (int) strtol((char *)hint->value, (char **)NULL, 10); - + if (val != 0 || errno == 0) { /** * Set max. acceptable - * replication lag + * replication lag * value for backend srv */ rlag_max = val; @@ -2443,18 +2443,18 @@ static bool route_single_stmt( } hint = hint->next; } /*< while */ - + if (rlag_max == MAX_RLAG_UNDEFINED) /*< no rlag max hint, use config */ { rlag_max = rses_get_max_replication_lag(rses); } btype = route_target & TARGET_SLAVE ? BE_SLAVE : BE_MASTER; /*< target may be master or slave */ /** - * Search backend server by name or replication lag. + * Search backend server by name or replication lag. * If it fails, then try to find valid slave or master. - */ + */ succp = get_dcb(&target_dcb, rses, btype, named_server,rlag_max); - + if (!succp) { if (TARGET_IS_NAMED_SERVER(route_target)) @@ -2476,14 +2476,14 @@ static bool route_single_stmt( else if (TARGET_IS_SLAVE(route_target)) { btype = BE_SLAVE; - + if (rlag_max == MAX_RLAG_UNDEFINED) /*< no rlag max hint, use config */ { rlag_max = rses_get_max_replication_lag(rses); } /** * Search suitable backend server, get DCB in target_dcb - */ + */ succp = get_dcb(&target_dcb, rses, BE_SLAVE, NULL,rlag_max); if (succp) @@ -2504,13 +2504,13 @@ static bool route_single_stmt( else if (TARGET_IS_MASTER(route_target)) { DCB* curr_master_dcb = NULL; - - succp = get_dcb(&curr_master_dcb, - rses, - BE_MASTER, + + succp = get_dcb(&curr_master_dcb, + rses, + BE_MASTER, NULL, MAX_RLAG_UNDEFINED); - + if (succp && master_dcb == curr_master_dcb) { atomic_add(&inst->stats.n_master, 1); @@ -2537,41 +2537,41 @@ static bool route_single_stmt( goto retblock; } } - + if (succp) /*< Have DCB of the target backend */ { backend_ref_t* bref; sescmd_cursor_t* scur; - + bref = get_bref_from_dcb(rses, target_dcb); scur = &bref->bref_sescmd_cur; - + ss_dassert(target_dcb != NULL); - + MXS_INFO("Route query to %s \t%s:%d <", (SERVER_IS_MASTER(bref->bref_backend->backend_server) ? "master" : "slave"), bref->bref_backend->backend_server->name, bref->bref_backend->backend_server->port); - /** - * Store current stmt if execution of previous session command + /** + * Store current stmt if execution of previous session command * haven't completed yet. - * + * * !!! Note that according to MySQL protocol * there can only be one such non-sescmd stmt at the time. * It is possible that bref->bref_pending_cmd includes a pending - * command if rwsplit is parent or child for another router, + * command if rwsplit is parent or child for another router, * which runs all the same commands. - * - * If the assertion below traps, pending queries are treated - * somehow wrong, or client is sending more queries before + * + * If the assertion below traps, pending queries are treated + * somehow wrong, or client is sending more queries before * previous is received. */ if (sescmd_cursor_is_active(scur)) { ss_dassert(bref->bref_pending_cmd == NULL); bref->bref_pending_cmd = gwbuf_clone(querybuf); - + rses_end_locked_router_action(rses); goto retblock; } @@ -2579,7 +2579,7 @@ static bool route_single_stmt( if ((ret = target_dcb->func.write(target_dcb, gwbuf_clone(querybuf))) == 1) { backend_ref_t* bref; - + atomic_add(&inst->stats.n_queries, 1); /** * Add one query response waiter to backend reference @@ -2595,14 +2595,14 @@ static bool route_single_stmt( } } rses_end_locked_router_action(rses); - + retblock: #if defined(SS_DEBUG2) { char* canonical_query_str; - + canonical_query_str = skygw_get_canonical(querybuf); - + if (canonical_query_str != NULL) { MXS_INFO("Canonical version: %s", canonical_query_str); @@ -2610,22 +2610,22 @@ retblock: } } #endif - return succp; + return succp; } -/** +/** * @node Acquires lock to router client session if it is not closed. * * Parameters: * @param rses - in, use - * + * * * @return true if router session was not closed. If return value is true * it means that router is locked, and must be unlocked later. False, if * router was closed before lock was acquired. * - * + * * @details (write detailed description here) * */ @@ -2640,22 +2640,22 @@ static bool rses_begin_locked_router_action( CHK_CLIENT_RSES(rses); if (rses->rses_closed) { - + goto return_succp; - } + } spinlock_acquire(&rses->rses_lock); if (rses->rses_closed) { spinlock_release(&rses->rses_lock); goto return_succp; - } + } succp = true; - + return_succp: return succp; } /** to be inline'd */ -/** +/** * @node Releases router client session lock. * * Parameters: @@ -2664,7 +2664,7 @@ return_succp: * * @return void * - * + * * @details (write detailed description here) * */ @@ -2761,7 +2761,7 @@ double master_pct = 0.0; * The routine will reply to client for session change with master server data * * @param instance The router instance - * @param router_session The router session + * @param router_session The router session * @param backend_dcb The backend DCB * @param queue The GWBUF with reply data */ @@ -2776,7 +2776,7 @@ static void clientReply ( ROUTER_CLIENT_SES* router_cli_ses; sescmd_cursor_t* scur = NULL; backend_ref_t* bref; - + router_cli_ses = (ROUTER_CLIENT_SES *)router_session; router_inst = (ROUTER_INSTANCE*)instance; CHK_CLIENT_RSES(router_cli_ses); @@ -2792,10 +2792,10 @@ static void clientReply ( } /** Holding lock ensures that router session remains open */ ss_dassert(backend_dcb->session != NULL); - client_dcb = backend_dcb->session->client; + client_dcb = backend_dcb->session->client_dcb; /** Unlock */ - rses_end_locked_router_action(router_cli_ses); + rses_end_locked_router_action(router_cli_ses); /** * 1. Check if backend received reply to sescmd. * 2. Check sescmd's state whether OK_PACKET has been @@ -2803,12 +2803,12 @@ static void clientReply ( * reply to client, and move property cursor forward. Finally * release the lock. * 3. If reply for this sescmd is sent, lock property cursor - * and + * and */ if (client_dcb == NULL) { while ((writebuf = gwbuf_consume( - writebuf, + writebuf, GWBUF_LENGTH(writebuf))) != NULL); /** Log that client was closed before reply */ goto lock_failed; @@ -2830,45 +2830,45 @@ static void clientReply ( goto lock_failed; } #endif - + CHK_BACKEND_REF(bref); scur = &bref->bref_sescmd_cur; /** - * Active cursor means that reply is from session command + * Active cursor means that reply is from session command * execution. */ if (sescmd_cursor_is_active(scur)) { - if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_ERR) && + if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_ERR) && MYSQL_IS_ERROR_PACKET(((uint8_t *)GWBUF_DATA(writebuf)))) { - uint8_t* buf = + uint8_t* buf = (uint8_t *)GWBUF_DATA((scur->scmd_cur_cmd->my_sescmd_buf)); uint8_t* replybuf = (uint8_t *)GWBUF_DATA(writebuf); size_t len = MYSQL_GET_PACKET_LEN(buf); size_t replylen = MYSQL_GET_PACKET_LEN(replybuf); char* cmdstr = strndup(&((char *)buf)[5], len-4); char* err = strndup(&((char *)replybuf)[8], 5); - char* replystr = strndup(&((char *)replybuf)[13], + char* replystr = strndup(&((char *)replybuf)[13], replylen-4-5); - + ss_dassert(len+4 == GWBUF_LENGTH(scur->scmd_cur_cmd->my_sescmd_buf)); - + MXS_ERROR("Failed to execute %s in %s:%d. %s %s", - cmdstr, + cmdstr, bref->bref_backend->backend_server->name, bref->bref_backend->backend_server->port, err, replystr); - + free(cmdstr); free(err); free(replystr); } - + if (GWBUF_IS_TYPE_SESCMD_RESPONSE(writebuf)) { - /** + /** * Discard all those responses that have already been sent to * the client. Return with buffer including response that * needs to be sent to client or NULL. @@ -2888,7 +2888,7 @@ static void clientReply ( router_cli_ses->router); } } - /** + /** * If response will be sent to client, decrease waiter count. * This applies to session commands only. Counter decrement * for other type of queries is done outside this block. @@ -2917,7 +2917,7 @@ static void clientReply ( } /** Unlock router session */ rses_end_locked_router_action(router_cli_ses); - + /** Lock router session */ if (!rses_begin_locked_router_action(router_cli_ses)) { @@ -2933,7 +2933,7 @@ static void clientReply ( "active cursor.", bref->bref_backend->backend_server->name, bref->bref_backend->backend_server->port); - + succp = execute_sescmd_in_backend(bref); ss_dassert(succp); if(!succp) @@ -2946,11 +2946,11 @@ static void clientReply ( else if (bref->bref_pending_cmd != NULL) /*< non-sescmd is waiting to be routed */ { int ret; - + CHK_GWBUF(bref->bref_pending_cmd); - + if ((ret = bref->bref_dcb->func.write( - bref->bref_dcb, + bref->bref_dcb, gwbuf_clone(bref->bref_pending_cmd))) == 1) { ROUTER_INSTANCE* inst = (ROUTER_INSTANCE *)instance; @@ -2980,7 +2980,7 @@ static void clientReply ( } /** Unlock router session */ rses_end_locked_router_action(router_cli_ses); - + lock_failed: return; } @@ -3004,7 +3004,7 @@ int bref_cmp_global_conn( { BACKEND* b1 = ((backend_ref_t *)bref1)->bref_backend; BACKEND* b2 = ((backend_ref_t *)bref2)->bref_backend; - + return ((1000 * b1->backend_server->stats.n_current) / b1->weight) - ((1000 * b2->backend_server->stats.n_current) / b2->weight); } @@ -3012,12 +3012,12 @@ int bref_cmp_global_conn( /** Compare relication lag between backend servers */ int bref_cmp_behind_master( - const void* bref1, + const void* bref1, const void* bref2) { BACKEND* b1 = ((backend_ref_t *)bref1)->bref_backend; BACKEND* b2 = ((backend_ref_t *)bref2)->bref_backend; - + return ((b1->backend_server->rlag < b2->backend_server->rlag) ? -1 : ((b1->backend_server->rlag > b2->backend_server->rlag) ? 1 : 0)); } @@ -3031,11 +3031,11 @@ int bref_cmp_current_load( SERVER* s2 = ((backend_ref_t *)bref2)->bref_backend->backend_server; BACKEND* b1 = ((backend_ref_t *)bref1)->bref_backend; BACKEND* b2 = ((backend_ref_t *)bref2)->bref_backend; - + return ((1000 * s1->stats.n_current_ops) - b1->weight) - ((1000 * s2->stats.n_current_ops) - b2->weight); } - + static void bref_clear_state( backend_ref_t* bref, bref_state_t state) @@ -3053,10 +3053,10 @@ static void bref_clear_state( { int prev1; int prev2; - + /** Decrease waiter count */ prev1 = atomic_add(&bref->bref_num_result_wait, -1); - + if (prev1 <= 0) { atomic_add(&bref->bref_num_result_wait, 1); } @@ -3073,11 +3073,11 @@ static void bref_clear_state( bref->bref_backend->backend_server->name, bref->bref_backend->backend_server->port); } - } + } } } -static void bref_set_state( +static void bref_set_state( backend_ref_t* bref, bref_state_t state) { @@ -3094,7 +3094,7 @@ static void bref_set_state( { int prev1; int prev2; - + /** Increase waiter count */ prev1 = atomic_add(&bref->bref_num_result_wait, 1); ss_dassert(prev1 >= 0); @@ -3120,7 +3120,7 @@ static void bref_set_state( } } -/** +/** * @node Search suitable backend servers from those of router instance. * * Parameters: @@ -3128,13 +3128,13 @@ static void bref_set_state( * Pointer to location where master's backend reference is to be stored. * NULL is not allowed. * - * @param backend_ref - in, use, out + * @param backend_ref - in, use, out * Pointer to backend server reference object array. * NULL is not allowed. * * @param router_nservers - in, use * Number of backend server pointers pointed to by b. - * + * * @param max_nslaves - in, use * Upper limit for the number of slaves. Configuration parameter or default. * @@ -3146,10 +3146,10 @@ static void bref_set_state( * * @param router - in, use * Pointer to router instance. Used when server states are qualified. - * + * * @return true, if at least one master and one slave was found. * - * + * * @details It is assumed that there is only one master among servers of * a router instance. As a result, the first master found is chosen. * There will possibly be more backend references than connected backends @@ -3175,26 +3175,26 @@ static bool select_connect_backend_servers( bool is_synced_master; int (*p)(const void *, const void *); BACKEND* master_host; - + if (p_master_ref == NULL || backend_ref == NULL) { ss_dassert(FALSE); succp = false; goto return_succp; } - - /* get the root Master */ + + /* get the root Master */ master_host = get_root_master(backend_ref, router_nservers); - /** - * Existing session : master is already chosen and connected. - * The function was called because new slave must be selected to replace + /** + * Existing session : master is already chosen and connected. + * The function was called because new slave must be selected to replace * failed one. */ if (*p_master_ref != NULL) { /** - * Ensure that backend reference is in use, stored master is + * Ensure that backend reference is in use, stored master is * still current root master. */ if (!BREF_IS_IN_USE((*p_master_ref)) || @@ -3217,13 +3217,13 @@ static bool select_connect_backend_servers( } /** Check slave selection criteria and set compare function */ p = criteria_cmpfun[select_criteria]; - + if (p == NULL) { succp = false; goto return_succp; } - + if (router->bitvalue != 0) /*< 'synced' is the only bitvalue in rwsplit */ { is_synced_master = true; @@ -3233,9 +3233,9 @@ static bool select_connect_backend_servers( is_synced_master = false; } -#if defined(EXTRA_SS_DEBUG) +#if defined(EXTRA_SS_DEBUG) MXS_INFO("Servers and conns before ordering:"); - + for (i=0; ibackend_server->port, STRSRVSTATUS(b->backend_server)); break; - + case LEAST_ROUTER_CONNECTIONS: MXS_INFO("RWSplit connections : %d in \t%s:%d %s", b->backend_conn_count, @@ -3287,7 +3287,7 @@ static bool select_connect_backend_servers( b->backend_server->port, STRSRVSTATUS(b->backend_server)); break; - + case LEAST_CURRENT_OPERATIONS: MXS_INFO("current operations : %d in \t%s:%d %s", b->backend_server->stats.n_current_ops, @@ -3295,7 +3295,7 @@ static bool select_connect_backend_servers( b->backend_server->port, STRSRVSTATUS(b->backend_server)); break; - + case LEAST_BEHIND_MASTER: MXS_INFO("replication lag : %d in \t%s:%d %s", b->backend_server->rlag, @@ -3305,16 +3305,16 @@ static bool select_connect_backend_servers( default: break; } - } + } } } /*< log only */ - + /** - * Choose at least 1+min_nslaves (master and slave) and at most 1+max_nslaves + * Choose at least 1+min_nslaves (master and slave) and at most 1+max_nslaves * servers from the sorted list. First master found is selected. */ - for (i=0; - ibackend_server) && ((b->backend_server->status & router->bitmask) == router->bitvalue)) { /* check also for relay servers and don't take the master_host */ if (slaves_found < max_nslaves && - (max_slave_rlag == MAX_RLAG_UNDEFINED || + (max_slave_rlag == MAX_RLAG_UNDEFINED || (b->backend_server->rlag != MAX_RLAG_NOT_AVAILABLE && b->backend_server->rlag <= max_slave_rlag)) && - (SERVER_IS_SLAVE(b->backend_server) || + (SERVER_IS_SLAVE(b->backend_server) || SERVER_IS_RELAY_SERVER(b->backend_server)) && - (master_host != NULL && + (master_host != NULL && (b->backend_server != master_host->backend_server))) { if(BREF_HAS_FAILED(&backend_ref[i])) @@ -3345,7 +3345,7 @@ static bool select_connect_backend_servers( } slaves_found += 1; - + /** Slave is already connected */ if (BREF_IS_IN_USE((&backend_ref[i]))) { @@ -3358,7 +3358,7 @@ static bool select_connect_backend_servers( b->backend_server, session, b->backend_server->protocol); - + if (backend_ref[i].bref_dcb != NULL) { slaves_connected += 1; @@ -3367,10 +3367,10 @@ static bool select_connect_backend_servers( * history. */ execute_sescmd_history(&backend_ref[i]); - /** + /** * Here we actually say : When this * type of issue occurs (DCB_REASON_...) - * for this particular DCB, + * for this particular DCB, * call this function. */ dcb_add_callback( @@ -3379,13 +3379,13 @@ static bool select_connect_backend_servers( &router_handle_state_switch, (void *)&backend_ref[i]); backend_ref[i].bref_state = 0; - bref_set_state(&backend_ref[i], + bref_set_state(&backend_ref[i], BREF_IN_USE); - /** + /** * Increase backend connection counter. - * Server's stats are _increased_ in + * Server's stats are _increased_ in * dcb.c:dcb_alloc ! - * But decreased in the calling function + * But decreased in the calling function * of dcb_close. */ atomic_add(&b->backend_conn_count, 1); @@ -3401,32 +3401,32 @@ static bool select_connect_backend_servers( } } /* take the master_host for master */ - else if (master_host && + else if (master_host && (b->backend_server == master_host->backend_server)) { - /** - * *p_master_ref must be assigned with this + /** + * *p_master_ref must be assigned with this * backend_ref pointer because its original value * may have been lost when backend references were * sorted (qsort). */ *p_master_ref = &backend_ref[i]; - + if (master_connected) - { + { continue; } master_found = true; - + backend_ref[i].bref_dcb = dcb_connect( b->backend_server, session, b->backend_server->protocol); - + if (backend_ref[i].bref_dcb != NULL) { master_connected = true; - /** + /** * When server fails, this callback * is called. */ @@ -3437,7 +3437,7 @@ static bool select_connect_backend_servers( (void *)&backend_ref[i]); backend_ref[i].bref_state = 0; - bref_set_state(&backend_ref[i], + bref_set_state(&backend_ref[i], BREF_IN_USE); /** Increase backend connection counters */ atomic_add(&b->backend_conn_count, 1); @@ -3451,17 +3451,17 @@ static bool select_connect_backend_servers( b->backend_server->port); /** handle connect error */ } - } + } } } /*< for */ - -#if defined(EXTRA_SS_DEBUG) + +#if defined(EXTRA_SS_DEBUG) MXS_INFO("Servers and conns after ordering:"); - + for (i=0; ibref_backend->backend_server == master_host->backend_server) && + (master_host && ((*p_master_ref)->bref_backend->backend_server == master_host->backend_server) && SERVER_MASTER)); #endif - + /** * Successful cases */ - if (master_connected && - slaves_connected >= min_nslaves && + if (master_connected && + slaves_connected >= min_nslaves && slaves_connected <= max_nslaves) { succp = true; - + if (slaves_connected == 0 && slaves_found > 0) { #if defined(SS_EXTRA_DEBUG) @@ -3511,7 +3511,7 @@ static bool select_connect_backend_servers( slaves_connected, slaves_found); } - + if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) { for (i=0; ibackend_server), b->backend_server->name, @@ -3532,9 +3532,9 @@ static bool select_connect_backend_servers( * Failure cases */ else - { + { succp = false; - + if (!master_found) { MXS_ERROR("Couldn't find suitable %s from %d " @@ -3556,14 +3556,14 @@ static bool select_connect_backend_servers( MXS_ERROR("Couldn't establish required amount of " "slave connections for router session."); } - + /** Clean up connections */ for (i=0; ibackend_conn_count > 0); - + /** disconnect opened connections */ bref_clear_state(&backend_ref[i], BREF_IN_USE); /** Decrease backend's connection counter. */ @@ -3578,14 +3578,14 @@ return_succp: } -/** +/** * Create a generic router session property strcture. */ static rses_property_t* rses_property_init( rses_property_type_t prop_type) { rses_property_t* prop; - + prop = (rses_property_t*)calloc(1, sizeof(rses_property_t)); if (prop == NULL) { @@ -3597,7 +3597,7 @@ static rses_property_t* rses_property_init( prop->rses_prop_chk_top = CHK_NUM_ROUTER_PROPERTY; prop->rses_prop_chk_tail = CHK_NUM_ROUTER_PROPERTY; #endif - + CHK_RSES_PROP(prop); return prop; } @@ -3614,23 +3614,23 @@ static void rses_property_done( return; } CHK_RSES_PROP(prop); - + switch (prop->rses_prop_type) { case RSES_PROP_TYPE_SESCMD: mysql_sescmd_done(&prop->rses_prop_data.sescmd); break; - + case RSES_PROP_TYPE_TMPTABLES: hashtable_free(prop->rses_prop_data.temp_tables); break; - + default: MXS_DEBUG("%lu [rses_property_done] Unknown property type %d " "in property %p", pthread_self(), prop->rses_prop_type, prop); - + ss_dassert(false); break; } @@ -3641,7 +3641,7 @@ static void rses_property_done( * Add property to the router_client_ses structure's rses_properties * array. The slot is determined by the type of property. * In each slot there is a list of properties of similar type. - * + * * Router client session must be locked. */ static int rses_property_add( @@ -3659,14 +3659,14 @@ static int rses_property_add( return -1; } rses_property_t* p; - + CHK_CLIENT_RSES(rses); CHK_RSES_PROP(prop); ss_dassert(SPINLOCK_IS_LOCKED(&rses->rses_lock)); - + prop->rses_prop_rsession = rses; p = rses->rses_properties[prop->rses_prop_type]; - + if (p == NULL) { rses->rses_properties[prop->rses_prop_type] = prop; @@ -3682,7 +3682,7 @@ static int rses_property_add( return 0; } -/** +/** * Router session must be locked. * Return session command pointer if succeed, NULL if failed. */ @@ -3700,9 +3700,9 @@ static mysql_sescmd_t* rses_property_get_sescmd( CHK_RSES_PROP(prop); ss_dassert(prop->rses_prop_rsession == NULL || SPINLOCK_IS_LOCKED(&prop->rses_prop_rsession->rses_lock)); - + sescmd = &prop->rses_prop_data.sescmd; - + if (sescmd != NULL) { CHK_MYSQL_SESCMD(sescmd); @@ -3720,7 +3720,7 @@ static mysql_sescmd_t* mysql_sescmd_init ( ROUTER_CLIENT_SES* rses) { mysql_sescmd_t* sescmd; - + CHK_RSES_PROP(rses_prop); /** Can't call rses_property_get_sescmd with uninitialized sescmd */ sescmd = &rses_prop->rses_prop_data.sescmd; @@ -3756,14 +3756,14 @@ static void mysql_sescmd_done( * command are handled here. * Read session commands from property list. If command is already replied, * discard packet. Else send reply to client. In both cases move cursor forward - * until all session command replies are handled. - * + * until all session command replies are handled. + * * Cases that are expected to happen and which are handled: * s = response not yet replied to client, S = already replied response, * q = query * 1. q+ for example : select * from mysql.user * 2. s+ for example : set autocommit=1 - * 3. S+ + * 3. S+ * 4. sq+ * 5. Sq+ * 6. Ss+ @@ -3779,16 +3779,16 @@ static GWBUF* sescmd_cursor_process_replies( mysql_sescmd_t* scmd; sescmd_cursor_t* scur; ROUTER_CLIENT_SES* ses; - - scur = &bref->bref_sescmd_cur; + + scur = &bref->bref_sescmd_cur; ss_dassert(SPINLOCK_IS_LOCKED(&(scur->scmd_cur_rses->rses_lock))); scmd = sescmd_cursor_get_command(scur); ses = (*scur->scmd_cur_ptr_property)->rses_prop_rsession; CHK_GWBUF(replybuf); - - /** - * Walk through packets in the message and the list of session - * commands. + + /** + * Walk through packets in the message and the list of session + * commands. */ while (scmd != NULL && replybuf != NULL) { @@ -3798,13 +3798,13 @@ static GWBUF* sescmd_cursor_process_replies( if (scmd->my_sescmd_is_replied) { bool last_packet = false; - + CHK_GWBUF(replybuf); - + while (!last_packet) { int buflen; - + buflen = GWBUF_LENGTH(replybuf); last_packet = GWBUF_IS_TYPE_RESPONSE_END(replybuf); /** discard packet */ @@ -3812,7 +3812,7 @@ static GWBUF* sescmd_cursor_process_replies( } /** Set response status received */ bref_clear_state(bref, BREF_WAITING_RESULT); - + if(bref->reply_cmd != scmd->reply_cmd) { MXS_INFO("Backend server '%s' response differs from master's response. " @@ -3843,14 +3843,14 @@ static GWBUF* sescmd_cursor_process_replies( MXS_INFO("Master '%s' responded to a session command.", bref->bref_backend->backend_server->unique_name); int i; - + for(i=0;irses_nbackends;i++) { if(!BREF_IS_WAITING_RESULT(&ses->rses_backend_ref[i])) { /** This backend has already received a response */ - if(ses->rses_backend_ref[i].reply_cmd != - scmd->reply_cmd && + if(ses->rses_backend_ref[i].reply_cmd != + scmd->reply_cmd && !BREF_IS_CLOSED(&ses->rses_backend_ref[i])) { bref_clear_state(&ses->rses_backend_ref[i],BREF_QUERY_ACTIVE); @@ -3869,7 +3869,7 @@ static GWBUF* sescmd_cursor_process_replies( } } } - + } else { @@ -3885,8 +3885,8 @@ static GWBUF* sescmd_cursor_process_replies( if(replybuf) while((replybuf = gwbuf_consume(replybuf,gwbuf_length(replybuf)))); } - - + + if (sescmd_cursor_next(scur)) { scmd = sescmd_cursor_get_command(scur); @@ -3899,7 +3899,7 @@ static GWBUF* sescmd_cursor_process_replies( } } ss_dassert(replybuf == NULL || *scur->scmd_cur_ptr_property == NULL); - + return replybuf; } @@ -3907,20 +3907,20 @@ static GWBUF* sescmd_cursor_process_replies( /** * Get the address of current session command. - * + * * Router session must be locked */ static mysql_sescmd_t* sescmd_cursor_get_command( sescmd_cursor_t* scur) { mysql_sescmd_t* scmd; - + ss_dassert(SPINLOCK_IS_LOCKED(&(scur->scmd_cur_rses->rses_lock))); scur->scmd_cur_cmd = rses_property_get_sescmd(*scur->scmd_cur_ptr_property); - + CHK_MYSQL_SESCMD(scur->scmd_cur_cmd); - + scmd = scur->scmd_cur_cmd; - + return scmd; } @@ -3952,9 +3952,9 @@ static void sescmd_cursor_set_active( sescmd_cursor->scmd_cur_active = value; } -/** - * Clone session command's command buffer. - * Router session must be locked +/** + * Clone session command's command buffer. + * Router session must be locked */ static GWBUF* sescmd_cursor_clone_querybuf( sescmd_cursor_t* scur) @@ -3966,9 +3966,9 @@ static GWBUF* sescmd_cursor_clone_querybuf( return NULL; } ss_dassert(scur->scmd_cur_cmd != NULL); - + buf = gwbuf_clone_all(scur->scmd_cur_cmd->my_sescmd_buf); - + CHK_GWBUF(buf); return buf; } @@ -3984,7 +3984,7 @@ static bool sescmd_cursor_history_empty( return true; } CHK_SESCMD_CUR(scur); - + if (scur->scmd_cur_rses->rses_properties[RSES_PROP_TYPE_SESCMD] == NULL) { succp = true; @@ -3993,7 +3993,7 @@ static bool sescmd_cursor_history_empty( { succp = false; } - + return succp; } @@ -4012,7 +4012,7 @@ static void sescmd_cursor_reset( rses = scur->scmd_cur_rses; scur->scmd_cur_ptr_property = &rses->rses_properties[RSES_PROP_TYPE_SESCMD]; - + CHK_RSES_PROP((*scur->scmd_cur_ptr_property)); scur->scmd_cur_active = false; scur->scmd_cur_cmd = &(*scur->scmd_cur_ptr_property)->rses_prop_data.sescmd; @@ -4029,10 +4029,10 @@ static bool execute_sescmd_history( return false; } CHK_BACKEND_REF(bref); - + scur = &bref->bref_sescmd_cur; CHK_SESCMD_CUR(scur); - + if (!sescmd_cursor_history_empty(scur)) { sescmd_cursor_reset(scur); @@ -4048,14 +4048,14 @@ static bool execute_sescmd_history( /** * If session command cursor is passive, sends the command to backend for - * execution. - * + * execution. + * * Returns true if command was sent or added successfully to the queue. * Returns false if command sending failed or if there are no pending session * commands. - * + * * Router session must be locked. - */ + */ static bool execute_sescmd_in_backend( backend_ref_t* backend_ref) { @@ -4075,11 +4075,11 @@ static bool execute_sescmd_in_backend( goto return_succp; } dcb = backend_ref->bref_dcb; - + CHK_DCB(dcb); CHK_BACKEND_REF(backend_ref); - - /** + + /** * Get cursor pointer and copy of command buffer to cursor. */ scur = &backend_ref->bref_sescmd_cur; @@ -4089,7 +4089,7 @@ static bool execute_sescmd_in_backend( { succp = false; MXS_INFO("Cursor had no pending session commands."); - + goto return_succp; } @@ -4105,9 +4105,9 @@ static bool execute_sescmd_in_backend( gwbuf_set_type(scur->scmd_cur_cmd->my_sescmd_buf, GWBUF_TYPE_SESCMD); buf = sescmd_cursor_clone_querybuf(scur); rc = dcb->func.auth( - dcb, - NULL, - dcb->session, + dcb, + NULL, + dcb->session, buf); break; @@ -4120,25 +4120,25 @@ static bool execute_sescmd_in_backend( MYSQL_session* data; unsigned int qlen; - data = dcb->session->data; + data = dcb->session->client_dcb->data; tmpbuf = scur->scmd_cur_cmd->my_sescmd_buf; qlen = MYSQL_GET_PACKET_LEN((unsigned char*)tmpbuf->start); memset(data->db,0,MYSQL_DATABASE_MAXLEN+1); if(qlen > 0 && qlen < MYSQL_DATABASE_MAXLEN+1) - strncpy(data->db,tmpbuf->start+5,qlen - 1); + strncpy(data->db,tmpbuf->start+5,qlen - 1); } /** Fallthrough */ case MYSQL_COM_QUERY: default: - /** - * Mark session command buffer, it triggers writing + /** + * Mark session command buffer, it triggers writing * MySQL command to protocol */ gwbuf_set_type(scur->scmd_cur_cmd->my_sescmd_buf, GWBUF_TYPE_SESCMD); buf = sescmd_cursor_clone_querybuf(scur); rc = dcb->func.write( - dcb, + dcb, buf); break; } @@ -4160,8 +4160,8 @@ return_succp: * Moves cursor to next property and copied address of its sescmd to cursor. * Current propery must be non-null. * If current property is the last on the list, *scur->scmd_ptr_property == NULL - * - * Router session must be locked + * + * Router session must be locked */ static bool sescmd_cursor_next( sescmd_cursor_t* scur) @@ -4199,8 +4199,8 @@ static bool sescmd_cursor_next( scur->scmd_cur_ptr_property = &(prop_curr->rses_prop_next); prop_next = *scur->scmd_cur_ptr_property; ss_dassert(prop_next == *(scur->scmd_cur_ptr_property)); - - + + /** If there is a next property move forward */ if (prop_next != NULL) { @@ -4210,7 +4210,7 @@ static bool sescmd_cursor_next( /** Get pointer to next property's sescmd */ scur->scmd_cur_cmd = rses_property_get_sescmd(prop_next); - ss_dassert(prop_next == scur->scmd_cur_cmd->my_sescmd_prop); + ss_dassert(prop_next == scur->scmd_cur_cmd->my_sescmd_prop); CHK_MYSQL_SESCMD(scur->scmd_cur_cmd); CHK_RSES_PROP(scur->scmd_cur_cmd->my_sescmd_prop); } @@ -4254,13 +4254,13 @@ static void tracelog_routed_query( BACKEND* b; backend_type_t be_type; DCB* dcb; - + CHK_BACKEND_REF(bref); b = bref->bref_backend; CHK_BACKEND(b); dcb = bref->bref_dcb; CHK_DCB(dcb); - + be_type = BACKEND_TYPE(b); if (GWBUF_IS_TYPE_MYSQL(buf)) @@ -4268,8 +4268,8 @@ static void tracelog_routed_query( len = packet[0]; len += 256*packet[1]; len += 256*256*packet[2]; - - if (packet_type == '\x03') + + if (packet_type == '\x03') { querystr = (char *)malloc(len); memcpy(querystr, startpos, len-1); @@ -4280,14 +4280,14 @@ static void tracelog_routed_query( (int)buflen, querystr, b->backend_server->name, - b->backend_server->port, + b->backend_server->port, STRBETYPE(be_type), dcb); free(querystr); } - else if (packet_type == '\x22' || - packet_type == 0x22 || - packet_type == '\x26' || + else if (packet_type == '\x22' || + packet_type == 0x22 || + packet_type == '\x26' || packet_type == 0x26 || true) { @@ -4312,7 +4312,7 @@ static void tracelog_routed_query( /** * Return RCAP_TYPE_STMT_INPUT. - */ + */ static int getCapabilities () { return RCAP_TYPE_STMT_INPUT; @@ -4321,22 +4321,22 @@ static int getCapabilities () /** * Execute in backends used by current router session. * Save session variable commands to router session property - * struct. Thus, they can be replayed in backends which are + * struct. Thus, they can be replayed in backends which are * started and joined later. - * + * * Suppress redundant OK packets sent by backends. - * + * * The first OK packet is replied to the client. - * + * * @param router_cli_ses Client's router session pointer * @param querybuf GWBUF including the query to be routed * @param inst Router instance * @param packet_type Type of MySQL packet * @param qtype Query type from query_classifier - * - * @return True if at least one backend is used and routing succeed to all + * + * @return True if at least one backend is used and routing succeed to all * backends being used, otherwise false. - * + * */ static bool route_session_write( ROUTER_CLIENT_SES* router_cli_ses, @@ -4352,18 +4352,18 @@ static bool route_session_write( int max_nslaves; int nbackends; int nsucc; - + MXS_INFO("Session write, routing to all servers."); /** Maximum number of slaves in this router client session */ - max_nslaves = rses_get_max_slavecount(router_cli_ses, + max_nslaves = rses_get_max_slavecount(router_cli_ses, router_cli_ses->rses_nbackends); nsucc = 0; nbackends = 0; backend_ref = router_cli_ses->rses_backend_ref; - + /** * These are one-way messages and server doesn't respond to them. - * Therefore reply processing is unnecessary and session + * Therefore reply processing is unnecessary and session * command property is not needed. It is just routed to all available * backends. */ @@ -4378,11 +4378,11 @@ static bool route_session_write( { goto return_succp; } - + for (i=0; irses_nbackends; i++) { - DCB* dcb = backend_ref[i].bref_dcb; - + DCB* dcb = backend_ref[i].bref_dcb; + if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO) && BREF_IS_IN_USE((&backend_ref[i]))) { @@ -4412,12 +4412,12 @@ static bool route_session_write( { goto return_succp; } - + if (router_cli_ses->rses_nbackends <= 0) { MXS_INFO("Router session doesn't have any backends in use. " "Routing failed. <"); - + goto return_succp; } @@ -4469,8 +4469,8 @@ static bool route_session_write( } } - /** - * Additional reference is created to querybuf to + /** + * Additional reference is created to querybuf to * prevent it from being released before properties * are cleaned up as a part of router sessionclean-up. */ @@ -4481,7 +4481,7 @@ static bool route_session_write( return false; } mysql_sescmd_init(prop, querybuf, packet_type, router_cli_ses); - + /** Add sescmd property to router client session */ if(rses_property_add(router_cli_ses, prop) != 0) { @@ -4489,15 +4489,15 @@ static bool route_session_write( rses_end_locked_router_action(router_cli_ses); return false; } - + for (i=0; irses_nbackends; i++) { if (BREF_IS_IN_USE((&backend_ref[i]))) { sescmd_cursor_t* scur; - + nbackends += 1; - + if (MXS_LOG_PRIORITY_IS_ENABLED(LOG_INFO)) { MXS_INFO("Route query to %s \t%s:%d%s", @@ -4507,16 +4507,16 @@ static bool route_session_write( backend_ref[i].bref_backend->backend_server->port, (i+1==router_cli_ses->rses_nbackends ? " <" : " ")); } - + scur = backend_ref_get_sescmd_cursor(&backend_ref[i]); - - /** + + /** * Add one waiter to backend reference. */ - bref_set_state(get_bref_from_dcb(router_cli_ses, - backend_ref[i].bref_dcb), + bref_set_state(get_bref_from_dcb(router_cli_ses, + backend_ref[i].bref_dcb), BREF_WAITING_RESULT); - /** + /** * Start execution if cursor is not already executing. * Otherwise, cursor will execute pending commands * when it completes with previous commands. @@ -4549,9 +4549,9 @@ static bool route_session_write( /** Unlock router session */ rses_end_locked_router_action(router_cli_ses); - + return_succp: - /** + /** * Routing must succeed to all backends that are used. * There must be at leas one and at most max_nslaves+1 backends. */ @@ -4569,9 +4569,9 @@ static bool router_option_configured( { bool succp = false; char** option; - + option = router->service->routerOptions; - + while (option != NULL) { char* value; @@ -4630,7 +4630,7 @@ static void rwsplit_process_router_options( c == LEAST_BEHIND_MASTER || c == LEAST_CURRENT_OPERATIONS || c == UNDEFINED_CRITERIA); - + if (c == UNDEFINED_CRITERIA) { MXS_WARNING("Unknown " @@ -4815,7 +4815,7 @@ static void handle_error_reply_client( spinlock_acquire(&ses->ses_lock); sesstate = ses->state; - client_dcb = ses->client; + client_dcb = ses->client_dcb; spinlock_release(&ses->ses_lock); /** @@ -4837,16 +4837,16 @@ static void handle_error_reply_client( /** * Check if there is backend reference pointing at failed DCB, and reset its - * flags. Then clear DCB's callback and finally : try to find replacement(s) + * flags. Then clear DCB's callback and finally : try to find replacement(s) * for failed slave(s). - * - * This must be called with router lock. - * + * + * This must be called with router lock. + * * @param inst router instance * @param rses router client session * @param dcb failed DCB * @param errmsg error message which is sent to client if it is waiting - * + * * @return true if there are enough backend connections to continue, false if not */ static bool handle_error_new_connection( @@ -4862,13 +4862,13 @@ static bool handle_error_new_connection( int max_slave_rlag; backend_ref_t* bref; bool succp; - + myrses = *rses; ss_dassert(SPINLOCK_IS_LOCKED(&myrses->rses_lock)); - + ses = backend_dcb->session; CHK_SESSION(ses); - + /** * If bref == NULL it has been replaced already with another one. */ @@ -4878,8 +4878,8 @@ static bool handle_error_new_connection( goto return_succp; } CHK_BACKEND_REF(bref); - - /** + + /** * If query was sent through the bref and it is waiting for reply from * the backend server it is necessary to send an error to the client * because it is waiting for reply. @@ -4887,14 +4887,14 @@ static bool handle_error_new_connection( if (BREF_IS_WAITING_RESULT(bref)) { DCB* client_dcb; - client_dcb = ses->client; + client_dcb = ses->client_dcb; client_dcb->func.write(client_dcb, gwbuf_clone(errmsg)); bref_clear_state(bref, BREF_WAITING_RESULT); } bref_clear_state(bref, BREF_IN_USE); bref_set_state(bref, BREF_CLOSED); - /** + /** * Error handler is already called for this DCB because * it's not polling anymore. It can be assumed that * it succeed because rses isn't closed. @@ -4903,21 +4903,21 @@ static bool handle_error_new_connection( { succp = true; goto return_succp; - } + } /** - * Remove callback because this DCB won't be used + * Remove callback because this DCB won't be used * unless it is reconnected later, and then the callback * is set again. */ - dcb_remove_callback(backend_dcb, - DCB_REASON_NOT_RESPONDING, - &router_handle_state_switch, + dcb_remove_callback(backend_dcb, + DCB_REASON_NOT_RESPONDING, + &router_handle_state_switch, (void *)bref); router_nservers = router_get_servercount(inst); max_nslaves = rses_get_max_slavecount(myrses, router_nservers); max_slave_rlag = rses_get_max_replication_lag(myrses); - /** - * Try to get replacement slave or at least the minimum + /** + * Try to get replacement slave or at least the minimum * number of slave connections for router session. */ if(inst->rwsplit_config.rw_disable_sescmd_hist) @@ -4936,15 +4936,15 @@ static bool handle_error_new_connection( ses, inst); } - + return_succp: - return succp; + return succp; } static void print_error_packet( - ROUTER_CLIENT_SES* rses, - GWBUF* buf, + ROUTER_CLIENT_SES* rses, + GWBUF* buf, DCB* dcb) { #if defined(SS_DEBUG) @@ -4952,20 +4952,20 @@ static void print_error_packet( { while (gwbuf_length(buf) > 0) { - /** - * This works with MySQL protocol only ! + /** + * This works with MySQL protocol only ! * Protocol specific packet print functions would be nice. */ uint8_t* ptr = GWBUF_DATA(buf); size_t len = MYSQL_GET_PACKET_LEN(ptr); - + if (MYSQL_GET_COMMAND(ptr) == 0xff) { SERVER* srv = NULL; backend_ref_t* bref = rses->rses_backend_ref; int i; char* bufstr; - + for (i=0; irses_nbackends; i++) { if (bref[i].bref_dcb == dcb) @@ -4974,9 +4974,9 @@ static void print_error_packet( } } ss_dassert(srv != NULL); - char* str = (char*)&ptr[7]; + char* str = (char*)&ptr[7]; bufstr = strndup(str, len-3); - + MXS_ERROR("Backend server %s:%d responded with " "error : %s", srv->name, @@ -5001,7 +5001,7 @@ static int router_get_servercount( BACKEND** b = inst->servers; /** count servers */ while (*(b++) != NULL) router_nservers++; - + return router_nservers; } @@ -5012,10 +5012,10 @@ static bool have_enough_servers( ROUTER_INSTANCE* router) { bool succp; - + /** With too few servers session is not created */ - if (router_nsrv < min_nsrv || - MAX((*p_rses)->rses_config.rw_max_slave_conn_count, + if (router_nsrv < min_nsrv || + MAX((*p_rses)->rses_config.rw_max_slave_conn_count, (router_nsrv*(*p_rses)->rses_config.rw_max_slave_conn_percent)/100) < min_nsrv) { @@ -5032,7 +5032,7 @@ static bool have_enough_servers( { int pct = (*p_rses)->rses_config.rw_max_slave_conn_percent/100; int nservers = router_nsrv*pct; - + if ((*p_rses)->rses_config.rw_max_slave_conn_count < min_nsrv) { MXS_ERROR("Unable to start %s service. There are " @@ -5065,9 +5065,9 @@ static bool have_enough_servers( return succp; } -/** +/** * Find out the number of read backend servers. - * Depending on the configuration value type, either copy direct count + * Depending on the configuration value type, either copy direct count * of slave connections or calculate the count from percentage value. */ static int rses_get_max_slavecount( @@ -5076,20 +5076,20 @@ static int rses_get_max_slavecount( { int conf_max_nslaves; int max_nslaves; - + CHK_CLIENT_RSES(rses); - + if (rses->rses_config.rw_max_slave_conn_count > 0) { conf_max_nslaves = rses->rses_config.rw_max_slave_conn_count; } else { - conf_max_nslaves = + conf_max_nslaves = (router_nservers*rses->rses_config.rw_max_slave_conn_percent)/100; } max_nslaves = MIN(router_nservers-1, MAX(1, conf_max_nslaves)); - + return max_nslaves; } @@ -5098,9 +5098,9 @@ static int rses_get_max_replication_lag( ROUTER_CLIENT_SES* rses) { int conf_max_rlag; - + CHK_CLIENT_RSES(rses); - + /** if there is no configured value, then longest possible int is used */ if (rses->rses_config.rw_max_slave_replication_lag > 0) { @@ -5110,16 +5110,16 @@ static int rses_get_max_replication_lag( { conf_max_rlag = ~(1<<31); } - + return conf_max_rlag; } /** - * Finds out if there is a backend reference pointing at the DCB given as - * parameter. + * Finds out if there is a backend reference pointing at the DCB given as + * parameter. * @param rses router client session * @param dcb DCB - * + * * @return backend reference pointer if succeed or NULL */ static backend_ref_t* get_bref_from_dcb( @@ -5130,9 +5130,9 @@ static backend_ref_t* get_bref_from_dcb( int i = 0; CHK_DCB(dcb); CHK_CLIENT_RSES(rses); - + bref = rses->rses_backend_ref; - + while (irses_nbackends) { if (bref->bref_dcb == dcb) @@ -5142,7 +5142,7 @@ static backend_ref_t* get_bref_from_dcb( bref++; i += 1; } - + if (i == rses->rses_nbackends) { bref = NULL; @@ -5151,7 +5151,7 @@ static backend_ref_t* get_bref_from_dcb( } /** - * Calls hang-up function for DCB if it is not both running and in + * Calls hang-up function for DCB if it is not both running and in * master/slave/joined/ndb role. Called by DCB's callback routine. */ static int router_handle_state_switch( @@ -5174,14 +5174,14 @@ static int router_handle_state_switch( } bref = (backend_ref_t *)data; CHK_BACKEND_REF(bref); - + srv = bref->bref_backend->backend_server; - + if (SERVER_IS_RUNNING(srv) && SERVER_IS_IN_CLUSTER(srv)) { goto return_rc; } - + MXS_DEBUG("%lu [router_handle_state_switch] %s %s:%d in state %s", pthread_self(), STRDCBREASON(reason), @@ -5198,11 +5198,11 @@ static int router_handle_state_switch( case DCB_REASON_NOT_RESPONDING: dcb->func.hangup(dcb); break; - + default: break; } - + return_rc: return rc; } @@ -5213,10 +5213,10 @@ static sescmd_cursor_t* backend_ref_get_sescmd_cursor ( { sescmd_cursor_t* scur; CHK_BACKEND_REF(bref); - + scur = &bref->bref_sescmd_cur; CHK_SESCMD_CUR(scur); - + return scur; } @@ -5228,9 +5228,9 @@ static prep_stmt_t* prep_stmt_init( void* id) { prep_stmt_t* pstmt; - + pstmt = (prep_stmt_t *)calloc(1, sizeof(prep_stmt_t)); - + if (pstmt != NULL) { #if defined(SS_DEBUG) @@ -5239,7 +5239,7 @@ static prep_stmt_t* prep_stmt_init( #endif pstmt->pstmt_state = PREP_STMT_ALLOC; pstmt->pstmt_type = type; - + if (type == PREP_STMT_NAME) { pstmt->pstmt_id.name = strndup((char *)id, MAX_STMT_LEN); @@ -5257,7 +5257,7 @@ static void prep_stmt_done( prep_stmt_t* pstmt) { CHK_PREP_STMT(pstmt); - + if (pstmt->pstmt_type == PREP_STMT_NAME) { free(pstmt->pstmt_id.name); @@ -5269,7 +5269,7 @@ static bool prep_stmt_drop( prep_stmt_t* pstmt) { CHK_PREP_STMT(pstmt); - + pstmt->pstmt_state = PREP_STMT_DROPPED; return true; } @@ -5289,27 +5289,27 @@ static bool prep_stmt_drop( * */ static BACKEND *get_root_master( - backend_ref_t *servers, - int router_nservers) + backend_ref_t *servers, + int router_nservers) { int i = 0; BACKEND * master_host = NULL; - for (i = 0; i< router_nservers; i++) + for (i = 0; i< router_nservers; i++) { BACKEND* b; - + if (servers[i].bref_backend == NULL) { continue; } - + b = servers[i].bref_backend; - if ((b->backend_server->status & - (SERVER_MASTER|SERVER_MAINT)) == SERVER_MASTER) + if ((b->backend_server->status & + (SERVER_MASTER|SERVER_MAINT)) == SERVER_MASTER) { - if (master_host == NULL || + if (master_host == NULL || (b->backend_server->depth < master_host->backend_server->depth)) { master_host = b; @@ -5338,9 +5338,9 @@ static backend_ref_t* get_root_master_bref( backend_ref_t* bref; backend_ref_t* candidate_bref = NULL; int i = 0; - + bref = rses->rses_backend_ref; - + while (irses_nbackends) { if ((bref->bref_backend->backend_server->status & diff --git a/server/modules/routing/schemarouter/schemarouter.c b/server/modules/routing/schemarouter/schemarouter.c index 29206c0ab..6ac09eae7 100644 --- a/server/modules/routing/schemarouter/schemarouter.c +++ b/server/modules/routing/schemarouter/schemarouter.c @@ -985,8 +985,8 @@ static void* newSession(ROUTER* router_inst, SESSION* session) int router_nservers = 0; /*< # of servers in total */ int i; char db[MYSQL_DATABASE_MAXLEN+1]; - MySQLProtocol* protocol = session->client->protocol; - MYSQL_session* data = session->data; + MySQLProtocol* protocol = session->client_dcb->protocol; + MYSQL_session* data = session->client_dcb->data; bool using_db = false; bool have_db = false; @@ -1027,12 +1027,12 @@ static void* newSession(ROUTER* router_inst, SESSION* session) #endif client_rses->router = router; - client_rses->rses_mysql_session = (MYSQL_session*)session->data; - client_rses->rses_client_dcb = (DCB*)session->client; + client_rses->rses_mysql_session = (MYSQL_session*)session->client_dcb->data; + client_rses->rses_client_dcb = (DCB*)session->client_dcb; spinlock_acquire(&router->lock); - shard_map_t *map = hashtable_fetch(router->shard_maps, session->client->user); + shard_map_t *map = hashtable_fetch(router->shard_maps, session->client_dcb->user); enum shard_map_state state; if (map) @@ -2496,7 +2496,7 @@ static void clientReply(ROUTER* instance, } /** Holding lock ensures that router session remains open */ ss_dassert(backend_dcb->session != NULL); - client_dcb = backend_dcb->session->client; + client_dcb = backend_dcb->session->client_dcb; /** Unlock */ rses_end_locked_router_action(router_cli_ses); @@ -3978,7 +3978,7 @@ static void handle_error_reply_client(SESSION* ses, spinlock_acquire(&ses->ses_lock); sesstate = ses->state; - client_dcb = ses->client; + client_dcb = ses->client_dcb; spinlock_release(&ses->ses_lock); /** @@ -4069,7 +4069,7 @@ static bool handle_error_new_connection(ROUTER_INSTANCE* inst, if (BREF_IS_WAITING_RESULT(bref)) { DCB* client_dcb; - client_dcb = ses->client; + client_dcb = ses->client_dcb; client_dcb->func.write(client_dcb, gwbuf_clone(errmsg)); bref_clear_state(bref, BREF_WAITING_RESULT); } diff --git a/server/modules/routing/schemarouter/shardrouter.c b/server/modules/routing/schemarouter/shardrouter.c index 2c120d8a5..65918ea31 100644 --- a/server/modules/routing/schemarouter/shardrouter.c +++ b/server/modules/routing/schemarouter/shardrouter.c @@ -47,7 +47,7 @@ MODULE_INFO info = { /** - * @file shardrouter.c + * @file shardrouter.c * * This is the sharding router that uses MaxScale's services to abstract * the actual implementation of the backend database. Queries are routed based on @@ -364,13 +364,13 @@ parse_mapping_response(ROUTER_CLIENT_SES* rses, char* target, GWBUF* buf) */ bool subsvc_is_valid(SUBSERVICE* sub) { - - if(sub->session == NULL || + + if(sub->session == NULL || sub->service->router == NULL) { return false; } - + spinlock_acquire(&sub->session->ses_lock); session_state_t ses_state = sub->session->state; spinlock_release(&sub->session->ses_lock); @@ -420,7 +420,7 @@ gen_subsvc_dblist(ROUTER_INSTANCE* inst, ROUTER_CLIENT_SES* session) if(SUBSVC_IS_OK(session->subservice[i])) { clone = gwbuf_clone(buffer); - + rval |= !SESSION_ROUTE_QUERY(session->subservice[i]->session,clone); subsvc_set_state(session->subservice[i],SUBSVC_WAITING_RESULT|SUBSVC_QUERY_ACTIVE); } @@ -485,7 +485,7 @@ get_shard_target_name(ROUTER_INSTANCE* router, ROUTER_CLIENT_SES* client, GWBUF* MXS_INFO("shardrouter: SHOW TABLES with specific database '%s' on server '%s'", tok, tmp); } free(query); - + if(tmp == NULL) { rval = (char*) hashtable_fetch(ht, client->rses_mysql_session->db); @@ -494,12 +494,12 @@ get_shard_target_name(ROUTER_INSTANCE* router, ROUTER_CLIENT_SES* client, GWBUF* } else { - rval = tmp; + rval = tmp; has_dbs = true; } } - - + + if(buffer->hint && buffer->hint->type == HINT_ROUTE_TO_NAMED_SERVER) { for(i = 0; i < client->n_subservice; i++) @@ -512,14 +512,14 @@ get_shard_target_name(ROUTER_INSTANCE* router, ROUTER_CLIENT_SES* client, GWBUF* { rval = srvrf->server->unique_name; MXS_INFO("shardrouter: Routing hint found (%s)",rval); - + } srvrf = srvrf->next; } } } - - + + if(rval == NULL && !has_dbs && client->rses_mysql_session->db[0] != '\0') { /** @@ -533,7 +533,7 @@ get_shard_target_name(ROUTER_INSTANCE* router, ROUTER_CLIENT_SES* client, GWBUF* MXS_INFO("shardrouter: Using active database '%s'",client->rses_mysql_session->db); } } - + return rval; } @@ -730,7 +730,7 @@ filterReply(FILTER* instance, void *session, GWBUF *reply) /** * This function reads the DCB's readqueue and sends it as a reply to the session - * who owns the DCB. + * who owns the DCB. * @param dcb The dummy DCB * @return 1 on success, 0 on failure */ @@ -760,7 +760,7 @@ int fakeQuery(DCB* dcb) GWBUF* tmp = dcb->dcb_readqueue; void* rinst = dcb->session->service->router_instance; void *rses = dcb->session->router_session; - + dcb->dcb_readqueue = NULL; return dcb->session->service->router->routeQuery(rinst,rses,tmp); } @@ -841,8 +841,8 @@ refreshInstance( } /*else if (paramtype == STRING_TYPE) { - if (strncmp(param->name, - "ignore_databases", + if (strncmp(param->name, + "ignore_databases", MAX_PARAM_LEN) == 0) { router->ignore_list = tokenize_string(param->qfd.valstr); @@ -862,7 +862,7 @@ refreshInstance( /** * Create an instance of shardrouter statement router within the MaxScale. * - * + * * @param service The service this router is being create for * @param options The options for this query router * @@ -964,7 +964,7 @@ createInstance(SERVICE *service, char **options) router->bitvalue = 0; /** - * Read config version number from service to inform what configuration + * Read config version number from service to inform what configuration * is used if any. */ router->shardrouter_version = service->svc_config_version; @@ -1018,8 +1018,8 @@ newSession( #endif client_rses->router = router; - client_rses->rses_mysql_session = (MYSQL_session*) session->data; - client_rses->rses_client_dcb = (DCB*) session->client; + client_rses->rses_mysql_session = (MYSQL_session*) session->client_dcb->data; + client_rses->rses_client_dcb = (DCB*) session->client_dcb; client_rses->rses_autocommit_enabled = true; client_rses->rses_transaction_active = false; client_rses->session = session; @@ -1027,12 +1027,12 @@ newSession( client_rses->replydcb->func.read = fakeReply; client_rses->replydcb->state = DCB_STATE_POLLING; client_rses->replydcb->session = session; - + client_rses->routedcb = dcb_alloc(DCB_ROLE_REQUEST_HANDLER); client_rses->routedcb->func.read = fakeQuery; client_rses->routedcb->state = DCB_STATE_POLLING; client_rses->routedcb->session = session; - + spinlock_init(&client_rses->rses_lock); client_rses->subservice = calloc(router->n_services, sizeof(SUBSERVICE*)); @@ -1051,10 +1051,10 @@ newSession( { goto errorblock; } - + /* TODO: add NULL value checks */ client_rses->subservice[i] = subsvc; - + subsvc->scur = calloc(1,sizeof(sescmd_cursor_t)); if(subsvc->scur == NULL) { @@ -1066,15 +1066,15 @@ newSession( subsvc->scur->scmd_cur_ptr_property = client_rses->rses_properties; subsvc->service = router->services[i]; subsvc->dcb = dcb_clone(client_rses->rses_client_dcb); - + if(subsvc->dcb == NULL){ subsvc_set_state(subsvc,SUBSVC_FAILED); MXS_ERROR("Failed to clone client DCB in shardrouter."); continue; } - + subsvc->session = session_alloc(subsvc->service,subsvc->dcb); - + if(subsvc->session == NULL){ dcb_close(subsvc->dcb); subsvc->dcb = NULL; @@ -1082,9 +1082,9 @@ newSession( MXS_ERROR("Failed to create subsession for service %s in shardrouter.",subsvc->service->name); continue; } - + dummy_filterdef = filter_alloc("tee_dummy","tee_dummy"); - + if(dummy_filterdef == NULL) { subsvc_set_state(subsvc,SUBSVC_FAILED); @@ -1092,21 +1092,21 @@ newSession( continue; } dummy_filterdef->obj = &dummyObject; - dummy_filterdef->filter = (FILTER*)client_rses; + dummy_filterdef->filter = (FILTER*)client_rses; dummy_upstream = filterUpstream(dummy_filterdef,subsvc->session,&subsvc->session->tail); - + if(dummy_upstream == NULL) { subsvc_set_state(subsvc,SUBSVC_FAILED); MXS_ERROR("Failed to set filterUpstream in shardrouter."); - continue; + continue; } - + subsvc->session->tail = *dummy_upstream; - - + + subsvc_set_state(subsvc,SUBSVC_OK); - + free(dummy_upstream); } @@ -1171,9 +1171,9 @@ closeSession( int i; MXS_DEBUG("%lu [RWSplit:closeSession]", pthread_self()); - /** + /** * router session can be NULL if newSession failed and it is discarding - * its connections and DCB's. + * its connections and DCB's. */ if(router_session == NULL) { @@ -1191,17 +1191,17 @@ closeSession( ROUTER_OBJECT* rtr; ROUTER* rinst; void *rses; - SESSION *ses; - + SESSION *one_session; + for(i = 0;in_subservice;i++) { rtr = router_cli_ses->subservice[i]->service->router; rinst = router_cli_ses->subservice[i]->service->router_instance; - ses = router_cli_ses->subservice[i]->session; - if(ses != NULL) + one_session = router_cli_ses->subservice[i]->session; + if(one_session != NULL) { - rses = ses->router_session; - ses->state = SESSION_STATE_STOPPING; + rses = one_session->router_session; + one_session->state = SESSION_STATE_STOPPING; rtr->closeSession(rinst,rses); } router_cli_ses->subservice[i]->state = SUBSVC_CLOSED; @@ -1210,7 +1210,7 @@ closeSession( router_cli_ses->routedcb->session = NULL; dcb_close(router_cli_ses->replydcb); dcb_close(router_cli_ses->routedcb); - + /** Unlock */ rses_end_locked_router_action(router_cli_ses); } @@ -1226,10 +1226,10 @@ freeSession( router_cli_ses = (ROUTER_CLIENT_SES *) router_client_session; - - /** - * For each property type, walk through the list, finalize properties - * and free the allocated memory. + + /** + * For each property type, walk through the list, finalize properties + * and free the allocated memory. */ for(i = RSES_PROP_TYPE_FIRST; i < RSES_PROP_TYPE_COUNT; i++) { @@ -1243,10 +1243,10 @@ freeSession( p = q; } } - + for(i = 0;in_subservice;i++) { - + /* TODO: free router client session */ free(router_cli_ses->subservice[i]); } @@ -1258,7 +1258,7 @@ freeSession( * all the memory and other resources associated * to the client session. */ - hashtable_free(router_cli_ses->dbhash); + hashtable_free(router_cli_ses->dbhash); free(router_cli_ses); return; } @@ -1266,12 +1266,12 @@ freeSession( /** * Examine the query type, transaction state and routing hints. Find out the * target for query routing. - * - * @param qtype Type of query + * + * @param qtype Type of query * @param trx_active Is transcation active or not * @param hint Pointer to list of hints attached to the query buffer - * - * @return bitfield including the routing target, or the target server name + * + * @return bitfield including the routing target, or the target server name * if the query would otherwise be routed to slave. */ static route_target_t @@ -1556,14 +1556,14 @@ routeQuery(ROUTER* instance, } rses_end_locked_router_action(router_cli_ses); - + packet = GWBUF_DATA(querybuf); packet_type = packet[4]; if(rses_is_closed) { - /** - * MYSQL_COM_QUIT may have sent by client and as a part of backend + /** + * MYSQL_COM_QUIT may have sent by client and as a part of backend * closing procedure. */ if(packet_type != MYSQL_COM_QUIT) @@ -1631,7 +1631,7 @@ routeQuery(ROUTER* instance, default: break; } /**< switch by packet type */ - + if(packet_type == MYSQL_COM_INIT_DB) { if(!(change_successful = change_current_db(router_cli_ses->current_db, @@ -1647,15 +1647,15 @@ routeQuery(ROUTER* instance, } /** - * Find out whether the query should be routed to single server or to + * Find out whether the query should be routed to single server or to * all of them. */ if(QUERY_IS_TYPE(qtype, QUERY_TYPE_SHOW_DATABASES)) { /** - * Generate custom response that contains all the databases + * Generate custom response that contains all the databases */ - + GWBUF* dbres = gen_show_dbs_response(inst,router_cli_ses); poll_add_epollin_event_to_dcb(router_cli_ses->replydcb,dbres); ret = 1; @@ -1670,7 +1670,7 @@ routeQuery(ROUTER* instance, { tname = hashtable_fetch(router_cli_ses->dbhash, router_cli_ses->rses_mysql_session->db); route_target = TARGET_NAMED_SERVER; - + } else if(route_target != TARGET_ALL && (tname = get_shard_target_name(inst, router_cli_ses, querybuf, qtype)) != NULL) @@ -1697,7 +1697,7 @@ routeQuery(ROUTER* instance, * No current database and no databases in query or * the database is ignored, route to first available backend. */ - + route_target = TARGET_ANY; } @@ -1706,7 +1706,7 @@ routeQuery(ROUTER* instance, if(!change_successful) { /** - * Bad shard status. The changing of the database + * Bad shard status. The changing of the database * was not successful and the error message was already sent. */ @@ -1777,12 +1777,12 @@ routeQuery(ROUTER* instance, if(TARGET_IS_NAMED_SERVER(route_target)) { /** - * Search backend server by name or replication lag. + * Search backend server by name or replication lag. * If it fails, then try to find valid slave or master. */ succp = get_shard_subsvc(&target_subsvc,router_cli_ses,tname); - + if(!succp) { MXS_INFO("Was supposed to route to named server " @@ -1796,12 +1796,12 @@ routeQuery(ROUTER* instance, { sescmd_cursor_t* scur; scur = target_subsvc->scur; - /** - * Store current stmt if execution of previous session command + /** + * Store current stmt if execution of previous session command * haven't completed yet. Note that according to MySQL protocol * there can only be one such non-sescmd stmt at the time. */ - if(scur && sescmd_cursor_is_active(scur)) + if(scur && sescmd_cursor_is_active(scur)) { target_subsvc->pending_cmd = gwbuf_clone(querybuf); rses_end_locked_router_action(router_cli_ses); @@ -1811,16 +1811,16 @@ routeQuery(ROUTER* instance, if(SESSION_ROUTE_QUERY(target_subsvc->session,querybuf) == 1) { - + atomic_add(&inst->stats.n_queries, 1); /** * Add one query response waiter to backend reference */ subsvc_set_state(target_subsvc,SUBSVC_QUERY_ACTIVE|SUBSVC_WAITING_RESULT); - + atomic_add(&target_subsvc->n_res_waiting, 1); - + } else { @@ -1839,18 +1839,18 @@ retblock: return ret; } -/** +/** * @node Acquires lock to router client session if it is not closed. * * Parameters: * @param rses - in, use - * + * * * @return true if router session was not closed. If return value is true * it means that router is locked, and must be unlocked later. False, if * router was closed before lock was acquired. * - * + * * @details (write detailed description here) * */ @@ -1881,7 +1881,7 @@ return_succp: /** to be inline'd */ -/** +/** * @node Releases router client session lock. * * Parameters: @@ -1890,7 +1890,7 @@ return_succp: * * @return void * - * + * * @details (write detailed description here) * */ @@ -1955,7 +1955,7 @@ diagnostic(ROUTER *instance, DCB *dcb) "Operations\n"); dcb_printf(dcb, "\t\t Global Router\n"); - + } @@ -1967,7 +1967,7 @@ diagnostic(ROUTER *instance, DCB *dcb) * The routine will reply to client for session change with master server data * * @param instance The router instance - * @param router_session The router session + * @param router_session The router session * @param backend_dcb The backend DCB * @param queue The GWBUF with reply data */ @@ -1978,12 +1978,12 @@ clientReply( GWBUF* writebuf, DCB* backend_dcb) { - + SESSION_ROUTE_REPLY(backend_dcb->session, writebuf); return; } -/** +/** * Create a generic router session property strcture. */ static rses_property_t* @@ -2044,7 +2044,7 @@ rses_property_done( * Add property to the router_client_ses structure's rses_properties * array. The slot is determined by the type of property. * In each slot there is a list of properties of similar type. - * + * * Router client session must be locked. */ static void @@ -2075,7 +2075,7 @@ rses_property_add( } } -/** +/** * Router session must be locked. * Return session command pointer if succeed, NULL if failed. */ @@ -2139,14 +2139,14 @@ mysql_sescmd_done( * command are handled here. * Read session commands from property list. If command is already replied, * discard packet. Else send reply to client. In both cases move cursor forward - * until all session command replies are handled. - * + * until all session command replies are handled. + * * Cases that are expected to happen and which are handled: * s = response not yet replied to client, S = already replied response, * q = query * 1. q+ for example : select * from mysql.user * 2. s+ for example : set autocommit=1 - * 3. S+ + * 3. S+ * 4. sq+ * 5. Sq+ * 6. Ss+ @@ -2168,9 +2168,9 @@ sescmd_cursor_process_replies( CHK_GWBUF(replybuf); - /** - * Walk through packets in the message and the list of session - * commands. + /** + * Walk through packets in the message and the list of session + * commands. */ while(scmd != NULL && replybuf != NULL) { @@ -2191,7 +2191,7 @@ sescmd_cursor_process_replies( replybuf = gwbuf_consume(replybuf, buflen); } /** Set response status received */ - + subsvc_clear_state(subsvc, SUBSVC_WAITING_RESULT); } /** Response is in the buffer and it will be sent to client. */ @@ -2219,7 +2219,7 @@ sescmd_cursor_process_replies( /** * Get the address of current session command. - * + * * Router session must be locked */ static mysql_sescmd_t* sescmd_cursor_get_command( @@ -2261,9 +2261,9 @@ sescmd_cursor_set_active( sescmd_cursor->scmd_cur_active = value; } -/** - * Clone session command's command buffer. - * Router session must be locked +/** + * Clone session command's command buffer. + * Router session must be locked */ static GWBUF* sescmd_cursor_clone_querybuf( @@ -2339,12 +2339,12 @@ execute_sescmd_history( /** * If session command cursor is passive, sends the command to backend for - * execution. - * + * execution. + * * Returns true if command was sent or added successfully to the queue. * Returns false if command sending failed or if there are no pending session * commands. - * + * * Router session must be locked. */ static bool @@ -2353,21 +2353,21 @@ execute_sescmd_in_backend(SUBSERVICE* subsvc) bool succp; int rc = 0; sescmd_cursor_t* scur; - + if(SUBSVC_IS_CLOSED(subsvc) || !SUBSVC_IS_OK(subsvc)) { succp = false; goto return_succp; } - + if(!subsvc_is_valid(subsvc)) { succp = false; goto return_succp; } - - /** + + /** * Get cursor pointer and copy of command buffer to cursor. */ scur = subsvc->scur; @@ -2397,8 +2397,8 @@ execute_sescmd_in_backend(SUBSERVICE* subsvc) case MYSQL_COM_QUERY: default: - /** - * Mark session command buffer, it triggers writing + /** + * Mark session command buffer, it triggers writing * MySQL command to protocol */ gwbuf_set_type(scur->scmd_cur_cmd->my_sescmd_buf, GWBUF_TYPE_SESCMD); @@ -2422,8 +2422,8 @@ return_succp: * Moves cursor to next property and copied address of its sescmd to cursor. * Current propery must be non-null. * If current property is the last on the list, *scur->scmd_ptr_property == NULL - * - * Router session must be locked + * + * Router session must be locked */ static bool sescmd_cursor_next( @@ -2509,11 +2509,11 @@ getCapabilities() /** * Execute in backends used by current router session. * Save session variable commands to router session property - * struct. Thus, they can be replayed in backends which are + * struct. Thus, they can be replayed in backends which are * started and joined later. - * + * * Suppress redundant OK packets sent by backends. - * + * * The first OK packet is replied to the client. * Return true if succeed, false is returned if router session was closed or * if execute_sescmd_in_backend failed. @@ -2535,7 +2535,7 @@ route_session_write( /** * These are one-way messages and server doesn't respond to them. - * Therefore reply processing is unnecessary and session + * Therefore reply processing is unnecessary and session * command property is not needed. It is just routed to all available * backends. */ @@ -2592,8 +2592,8 @@ route_session_write( succp = false; goto return_succp; } - /** - * Additional reference is created to querybuf to + /** + * Additional reference is created to querybuf to * prevent it from being released before properties * are cleaned up as a part of router sessionclean-up. */ @@ -2606,7 +2606,7 @@ route_session_write( for(i = 0; i < router_cli_ses->n_subservice; i++) { subsvc = router_cli_ses->subservice[i]; - + if(!SUBSVC_IS_CLOSED(subsvc)) { sescmd_cursor_t* scur; @@ -2619,18 +2619,18 @@ route_session_write( i+1 >= router_cli_ses->n_subservice ? "<" : ""); } - - - + + + scur = subsvc->scur; - - /** + + /** * Add one waiter to backend reference. */ subsvc_set_state(subsvc,SUBSVC_WAITING_RESULT); - - /** + + /** * Start execution if cursor is not already executing. * Otherwise, cursor will execute pending commands * when it completes with previous commands. @@ -2668,7 +2668,7 @@ return_succp: /** * Error Handler routine to resolve _backend_ failures. If it succeeds then there - * are enough operative backends available and connected. Otherwise it fails, + * are enough operative backends available and connected. Otherwise it fails, * and session is terminated. * * @param instance The router instance @@ -2677,7 +2677,7 @@ return_succp: * @param backend_dcb The backend DCB * @param action The action: ERRACT_NEW_CONNECTION or ERRACT_REPLY_CLIENT * @param succp Result of action: true if router can continue - * + * * Even if succp == true connecting to new slave may have failed. succp is to * tell whether router has enough master/slave connections to continue work. */ @@ -2694,11 +2694,11 @@ handleError( ROUTER_CLIENT_SES* rses = (ROUTER_CLIENT_SES *) router_session; CHK_DCB(backend_dcb); - + /** Don't handle same error twice on same DCB */ if(backend_dcb->dcb_errhandle_called) { - /** we optimistically assume that previous call succeed */ + /** we optimistically assume that previous call succeed */ *succp = true; return; } @@ -2763,13 +2763,13 @@ static SUBSERVICE* get_subsvc_from_ses(ROUTER_CLIENT_SES* rses, SESSION* ses) return rses->subservice[i]; } } - + return NULL; } /** - * Calls hang-up function for DCB if it is not both running and in + * Calls hang-up function for DCB if it is not both running and in * master/slave/joined/ndb role. Called by DCB's callback routine. * * TODO: See if there's a way to inject this into the subservices diff --git a/server/modules/routing/webserver.c b/server/modules/routing/webserver.c index 3b94fa41b..fe7f9471c 100644 --- a/server/modules/routing/webserver.c +++ b/server/modules/routing/webserver.c @@ -139,7 +139,7 @@ GetModuleObject() /** * Create an instance of the router for a particular service * within the gateway. - * + * * @param service The service this router is being create for * @param options The options for this query router * @@ -183,7 +183,7 @@ WEB_SESSION *wsession; * @param instance The router instance data * @param session The session being closed */ -static void +static void closeSession(ROUTER *instance, void *session) { free(session); @@ -196,7 +196,7 @@ static void freeSession( return; } -static int +static int routeQuery(ROUTER *instance, void *session, GWBUF *queue) { WEB_SESSION *wsession = (WEB_SESSION *)session; @@ -367,7 +367,7 @@ send_static_html(DCB *dcb, char *html) static void send_index(WEB_SESSION *session) { -DCB *dcb = session->session->client; +DCB *dcb = session->session->client_dcb; send_html_header(dcb); send_static_html(dcb, index_page); @@ -382,7 +382,7 @@ DCB *dcb = session->session->client; static void send_css(WEB_SESSION *session) { -DCB *dcb = session->session->client; +DCB *dcb = session->session->client_dcb; send_html_header(dcb); send_static_html(dcb, css); @@ -397,7 +397,7 @@ DCB *dcb = session->session->client; static void send_title(WEB_SESSION *session) { -DCB *dcb = session->session->client; +DCB *dcb = session->session->client_dcb; send_html_header(dcb); send_static_html(dcb, title_page); @@ -412,7 +412,7 @@ DCB *dcb = session->session->client; static void send_frame1(WEB_SESSION *session) { -DCB *dcb = session->session->client; +DCB *dcb = session->session->client_dcb; send_html_header(dcb); send_static_html(dcb, frame1_page); @@ -427,7 +427,7 @@ DCB *dcb = session->session->client; static void send_menu(WEB_SESSION *session) { -DCB *dcb = session->session->client; +DCB *dcb = session->session->client_dcb; send_html_header(dcb); send_static_html(dcb, menu_page); @@ -442,7 +442,7 @@ DCB *dcb = session->session->client; static void send_blank(WEB_SESSION *session) { -DCB *dcb = session->session->client; +DCB *dcb = session->session->client_dcb; send_html_header(dcb); send_static_html(dcb, blank_page); @@ -465,7 +465,7 @@ service_row(SERVICE *service, DCB *dcb) } /** - * Send the services page. This produces a table by means of the + * Send the services page. This produces a table by means of the * serviceIterate call. * * @param session The router session @@ -473,7 +473,7 @@ service_row(SERVICE *service, DCB *dcb) static void send_services(WEB_SESSION *session) { -DCB *dcb = session->session->client; +DCB *dcb = session->session->client_dcb; send_html_header(dcb); dcb_printf(dcb, ""); @@ -497,8 +497,8 @@ static void session_row(SESSION *session, DCB *dcb) { dcb_printf(dcb, "%-16p%s%s%s\n", - session, ((session->client && session->client->remote) - ? session->client->remote : ""), + session, ((session->client_dcb && session->client_dcb->remote) + ? session->client_dcb->remote : ""), (session->service && session->service->name ? session->service->name : ""), session_state(session->state)); @@ -514,7 +514,7 @@ session_row(SESSION *session, DCB *dcb) static void send_sessions(WEB_SESSION *session) { -DCB *dcb = session->session->client; +DCB *dcb = session->session->client_dcb; send_html_header(dcb); dcb_printf(dcb, ""); @@ -550,7 +550,7 @@ server_row(SERVER *server, DCB *dcb) static void send_servers(WEB_SESSION *session) { -DCB *dcb = session->session->client; +DCB *dcb = session->session->client_dcb; send_html_header(dcb); dcb_printf(dcb, ""); @@ -586,7 +586,7 @@ monitor_row(MONITOR *monitor, DCB *dcb) static void send_monitors(WEB_SESSION *session) { -DCB *dcb = session->session->client; +DCB *dcb = session->session->client_dcb; send_html_header(dcb); dcb_printf(dcb, ""); @@ -608,7 +608,7 @@ DCB *dcb = session->session->client; static void respond_error(WEB_SESSION *session, int err, char *msg) { -DCB *dcb = session->session->client; +DCB *dcb = session->session->client_dcb; dcb_printf(dcb, "HTTP/1.1 %d %s\n", err, msg); dcb_printf(dcb, "Content-Type: text/html\n"); From 5122777829c33b3334fd41d63fe505124e7ca03e Mon Sep 17 00:00:00 2001 From: counterpoint Date: Wed, 24 Feb 2016 10:00:45 +0000 Subject: [PATCH 09/26] Try to fix problem if balancing free client DCB and free session so that auth data is always available and client DCB is not freed until session is ready to be freed. Also fix problem in auth logic. --- server/core/dcb.c | 43 ++++++++++------------- server/core/session.c | 45 +++++++++++++++---------- server/include/dcb.h | 1 + server/modules/protocol/mysql_backend.c | 2 +- server/modules/protocol/mysql_client.c | 4 +-- 5 files changed, 50 insertions(+), 45 deletions(-) diff --git a/server/core/dcb.c b/server/core/dcb.c index 05ab7de6a..e6006da36 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -126,8 +126,6 @@ static int gw_write(DCB *dcb, bool *stop_writing); static int gw_write_SSL(DCB *dcb, bool *stop_writing); static void dcb_log_errors_SSL (DCB *dcb, const char *called_by, int ret); -static void mysql_auth_free_client_data(DCB *dcb); - size_t dcb_get_session_id( DCB *dcb) { @@ -320,8 +318,6 @@ dcb_clone(DCB *orig) static void dcb_final_free(DCB *dcb) { - DCB_CALLBACK *cb; - CHK_DCB(dcb); ss_info_dassert(dcb->state == DCB_STATE_DISCONNECTED || dcb->state == DCB_STATE_ALLOC, @@ -384,10 +380,24 @@ dcb_final_free(DCB *dcb) if (SESSION_STATE_DUMMY != local_session->state) { session_free(local_session); + return; } } + dcb_free_all_memory(dcb); +} - mysql_auth_free_client_data(dcb); +/** + * Free the memory belonging to a DCB + * + * NB The DCB is fully detached from all links except perhaps the session + * dcb_client link. + * + * @param dcb The DCB to free + */ +void +dcb_free_all_memory(DCB *dcb) +{ + DCB_CALLBACK *cb_dcb; if (dcb->protocol && (!DCB_IS_CLONE(dcb))) { @@ -424,10 +434,10 @@ dcb_final_free(DCB *dcb) } spinlock_acquire(&dcb->cb_lock); - while ((cb = dcb->callbacks) != NULL) + while ((cb_dcb = dcb->callbacks) != NULL) { - dcb->callbacks = cb->next; - free(cb); + dcb->callbacks = cb_dcb->next; + free(cb_dcb); } spinlock_release(&dcb->cb_lock); if (dcb->ssl) @@ -3025,20 +3035,3 @@ dcb_role_name(DCB *dcb) return name; } -/** - * @brief Free the client data pointed to by the passed DCB. - * - * Currently all that is required is to free the storage pointed to by - * dcb->data. But this is intended to be implemented as part of the - * authentication API at which time this code will be moved into the - * MySQL authenticator. If the data structure were to become more complex - * the mechanism would still work and be the responsibility of the authenticator. - * The DCB should not know authenticator implementation details. - * - * @param dcb Request handler DCB connected to the client - */ -static void -mysql_auth_free_client_data(DCB *dcb) -{ - free(dcb->data); -} \ No newline at end of file diff --git a/server/core/session.c b/server/core/session.c index 69ba9c9c0..e1f927b04 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -65,6 +65,8 @@ static SPINLOCK timeout_lock = SPINLOCK_INIT; static int session_setup_filters(SESSION *session); static void session_simple_free(SESSION *session, DCB *dcb); +static void mysql_auth_free_client_data(DCB *dcb); + /** * Allocate a new session for a new client of the specified service. * @@ -91,23 +93,6 @@ session_alloc(SERVICE *service, DCB *client_dcb) "session object due error %d, %s.", errno, strerror_r(errno, errbuf, sizeof(errbuf))); - /* Does this possibly need a lock? */ - /* - * This is really not the right way to do this. The data in a DCB is - * router specific and should be freed by a function in the relevant - * router. This would be better achieved by placing a function reference - * in the DCB and having dcb_final_free call it to dispose of the data - * at the final destruction of the DCB. However, this piece of code is - * only run following a calloc failure, so the system is probably on - * the point of crashing anyway. - * - */ - if (client_dcb->data && !DCB_IS_CLONE(client_dcb)) - { - void * clientdata = client_dcb->data; - client_dcb->data = NULL; - free(clientdata); - } return NULL; } #if defined(SS_DEBUG) @@ -437,6 +422,14 @@ session_free(SESSION *session) spinlock_release(&session_spin); atomic_add(&session->service->stats.n_current, -1); + /*** + * + */ + if (session->client_dcb) + { + mysql_auth_free_client_data(session->client_dcb); + dcb_free_all_memory(session->client_dcb); + } /** * If session is not child of some other session, free router_session. * Otherwise let the parent free it. @@ -1108,3 +1101,21 @@ sessionGetList(SESSIONLISTFILTER filter) return set; } + +/** + * @brief Free the client data pointed to by the passed DCB. + * + * Currently all that is required is to free the storage pointed to by + * dcb->data. But this is intended to be implemented as part of the + * authentication API at which time this code will be moved into the + * MySQL authenticator. If the data structure were to become more complex + * the mechanism would still work and be the responsibility of the authenticator. + * The DCB should not know authenticator implementation details. + * + * @param dcb Request handler DCB connected to the client + */ +static void +mysql_auth_free_client_data(DCB *dcb) +{ + free(dcb->data); +} \ No newline at end of file diff --git a/server/include/dcb.h b/server/include/dcb.h index d069d277b..6a636b3df 100644 --- a/server/include/dcb.h +++ b/server/include/dcb.h @@ -294,6 +294,7 @@ DCB *dcb_get_zombies(void); int dcb_write(DCB *, GWBUF *); DCB *dcb_alloc(dcb_role_t); void dcb_free(DCB *); +void dcb_free_all_memory(DCB *dcb); DCB *dcb_connect(struct server *, struct session *, const char *); DCB *dcb_clone(DCB *); int dcb_read(DCB *, GWBUF **, int); diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index 7a7b86bb4..8f4e9bf97 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -172,7 +172,7 @@ static int gw_read_backend_event(DCB *dcb) goto return_rc; } - if (dcb->session == NULL) + if (dcb->dcb_is_zombie || dcb->session == NULL) { goto return_rc; } diff --git a/server/modules/protocol/mysql_client.c b/server/modules/protocol/mysql_client.c index b0dbd4aef..f68e01f6c 100644 --- a/server/modules/protocol/mysql_client.c +++ b/server/modules/protocol/mysql_client.c @@ -537,12 +537,12 @@ int gw_read_client_event(DCB* dcb) auth_val = MYSQL_AUTH_NO_SESSION; } } - if (MYSQL_AUTH_SUCCEEDED != auth_val) + if (MYSQL_AUTH_SUCCEEDED != auth_val && MYSQL_AUTH_SSL_INCOMPLETE != auth_val) { protocol->protocol_auth_state = MYSQL_AUTH_FAILED; mysql_client_auth_error_handling(dcb, auth_val); /** - * Close DCB and which will release MYSQL_session + * Close DCB and which will release MYSQL_session */ dcb_close(dcb); } From a61b70a810b0be600fe59da71980955bc670d684 Mon Sep 17 00:00:00 2001 From: counterpoint Date: Wed, 24 Feb 2016 15:18:44 +0000 Subject: [PATCH 10/26] Quick fix to see if we can overcome crash. --- server/core/dcb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/core/dcb.c b/server/core/dcb.c index e6006da36..04618451a 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -371,12 +371,14 @@ dcb_final_free(DCB *dcb) * won't try to call dcb_close for client DCB * after this call. */ + /* if (local_session->client_dcb == dcb) { spinlock_acquire(&local_session->ses_lock); local_session->client_dcb = NULL; spinlock_release(&local_session->ses_lock); } + */ if (SESSION_STATE_DUMMY != local_session->state) { session_free(local_session); From c26b5a3dd0c444fcd37eda8eec515d48082a58ae Mon Sep 17 00:00:00 2001 From: counterpoint Date: Wed, 24 Feb 2016 15:34:02 +0000 Subject: [PATCH 11/26] Tidy earlier fix and apply another - both follow the principle of not removing the link from session to client DCB when dcb_close is called for client DCB. The close will not fully complete, but will be finalised when the session is freed. At this time, no DCBs will be referring to the session. These measures protect the dcb->data in the client DCB from being lost until the session is no longer needed. --- server/core/dcb.c | 13 ------------- server/modules/protocol/mysql_backend.c | 4 +--- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/server/core/dcb.c b/server/core/dcb.c index 04618451a..7d4481839 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -366,19 +366,6 @@ dcb_final_free(DCB *dcb) SESSION *local_session = dcb->session; dcb->session = NULL; CHK_SESSION(local_session); - /** - * Set session's client pointer NULL so that other threads - * won't try to call dcb_close for client DCB - * after this call. - */ - /* - if (local_session->client_dcb == dcb) - { - spinlock_acquire(&local_session->ses_lock); - local_session->client_dcb = NULL; - spinlock_release(&local_session->ses_lock); - } - */ if (SESSION_STATE_DUMMY != local_session->state) { session_free(local_session); diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index 8f4e9bf97..5985e4b83 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -1166,9 +1166,7 @@ static int gw_backend_close(DCB *dcb) spinlock_release(&session->ses_lock); /** Close client DCB */ - temp = session->client_dcb; - session->client_dcb = NULL; - dcb_close(temp); + dcb_close(session->client_dcb); } else { From b5b27740e3c684853275295ba21c86472dc5d458 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 22 Feb 2016 11:05:34 +0200 Subject: [PATCH 12/26] Fixed wrong comparison being made when setting readwritesplit parameters The comparison was done with strcmp but it was expected to return true on success when in reality it returns 0 on success. --- server/core/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/core/config.c b/server/core/config.c index 932237278..759e77eab 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -2383,7 +2383,7 @@ int create_new_service(CONFIG_CONTEXT *obj) */ /** Parameters for rwsplit router only */ - if (strcmp(router, "readwritesplit")) + if (strcmp(router, "readwritesplit") == 0) { if ((param = config_get_param(obj->parameters, "max_slave_connections"))) { From f77afc63744cfbc4cba846da2b981aa35f252382 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 26 Feb 2016 20:45:41 +0200 Subject: [PATCH 13/26] Fixed possible crash when draining writequeue The write queue spinlock was released in dcb_write_tidy_up without first acquiring it. This caused a crash when two threads try to interact with the write queue at the same time. --- server/core/dcb.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/core/dcb.c b/server/core/dcb.c index 7d4481839..87189e143 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -1474,8 +1474,6 @@ dcb_log_write_failure(DCB *dcb, GWBUF *queue, int eno) static inline void dcb_write_tidy_up(DCB *dcb, bool below_water) { - spinlock_release(&dcb->writeqlock); - if (dcb->high_water && dcb->writeqlen > dcb->high_water && below_water) { atomic_add(&dcb->stats.n_high_water, 1); From f03865f32ac664f46fc883a87e3c8610a8ddb60a Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sat, 27 Feb 2016 13:05:44 +0200 Subject: [PATCH 14/26] Cloned DCB authentication data was freed twice This caused all Tee filter sessions to crash when the branch DCB was closed. --- server/core/dcb.c | 10 +++++++++- server/core/session.c | 5 ++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/server/core/dcb.c b/server/core/dcb.c index 87189e143..39f582f84 100644 --- a/server/core/dcb.c +++ b/server/core/dcb.c @@ -369,7 +369,15 @@ dcb_final_free(DCB *dcb) if (SESSION_STATE_DUMMY != local_session->state) { session_free(local_session); - return; + + if (local_session->client_dcb == dcb) + { + /** The client DCB is freed once all other DCBs that the session + * uses have been freed. This will guarantee that the authentication + * data will be usable for all DCBs even if the client DCB has already + * been closed. */ + return; + } } } dcb_free_all_memory(dcb); diff --git a/server/core/session.c b/server/core/session.c index e1f927b04..b7f27f3b4 100644 --- a/server/core/session.c +++ b/server/core/session.c @@ -427,7 +427,10 @@ session_free(SESSION *session) */ if (session->client_dcb) { - mysql_auth_free_client_data(session->client_dcb); + if (!DCB_IS_CLONE(session->client_dcb)) + { + mysql_auth_free_client_data(session->client_dcb); + } dcb_free_all_memory(session->client_dcb); } /** From 1ebb998056630b7406c6c29502bac3e619c66f40 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Mon, 29 Feb 2016 16:53:39 +0200 Subject: [PATCH 15/26] Fixed wrong DCB being used in gw_get_shared_session_auth_info The caller DCB was used instead of the session client DCB. This caused a crash when a backend DCB was getting the shared data since those DCBs have a NULL data pointer. --- server/modules/protocol/mysql_backend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/modules/protocol/mysql_backend.c b/server/modules/protocol/mysql_backend.c index db9469ccb..444f6c168 100644 --- a/server/modules/protocol/mysql_backend.c +++ b/server/modules/protocol/mysql_backend.c @@ -144,7 +144,7 @@ static bool gw_get_shared_session_auth_info(DCB* dcb, MYSQL_session* session) if (dcb->session->state != SESSION_STATE_ALLOC && dcb->session->state != SESSION_STATE_DUMMY) { - memcpy(session, dcb->data, sizeof(MYSQL_session)); + memcpy(session, dcb->session->client_dcb->data, sizeof(MYSQL_session)); } else { From ab335152828af76dcea260059ffa21225935021a Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 4 Mar 2016 07:06:53 +0200 Subject: [PATCH 16/26] Fixed undefined servers being ignored The missing server was logged but it was not considered an error. --- server/core/config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/server/core/config.c b/server/core/config.c index c5a7dda5f..1b8ca0e4f 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -2560,6 +2560,7 @@ int configure_new_service(CONFIG_CONTEXT *context, CONFIG_CONTEXT *obj) { MXS_ERROR("Unable to find server '%s' that is " "configured as part of service '%s'.", s, obj->object); + error_count++; } s = strtok_r(NULL, ",", &lasts); } From d1837e0e4ae32a638616551b7eb1f6c40ce1d055 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 4 Mar 2016 07:55:03 +0200 Subject: [PATCH 17/26] Added more error logging and fixed parsing of packets in readwritesplit Readwritesplit tried to parse COM_PING packets which only have one byte of payload. This would cause the queries to fail to parsed. --- query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc | 5 +++++ server/modules/routing/readwritesplit/readwritesplit.c | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc index d8af03c79..99316a18d 100644 --- a/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc +++ b/query_classifier/qc_mysqlembedded/qc_mysqlembedded.cc @@ -195,6 +195,7 @@ static bool parse_query(GWBUF* querybuf) if (querybuf == NULL || query_is_parsed(querybuf)) { + MXS_ERROR("Query is NULL (%p) or query is already parsed.", querybuf); return false; } @@ -203,6 +204,7 @@ static bool parse_query(GWBUF* querybuf) if (pi == NULL) { + MXS_ERROR("Parsing info initialization failed."); succp = false; goto retblock; } @@ -215,6 +217,8 @@ static bool parse_query(GWBUF* querybuf) if (len < 1 || len >= ~((size_t) 0) - 1 || (query_str = (char *) malloc(len + 1)) == NULL) { /** Free parsing info data */ + MXS_ERROR("Length (%lu) is 0 or query string allocation failed (%p). Buffer is %lu bytes.", + len, query_str, GWBUF_LENGTH(querybuf)); parsing_info_done(pi); succp = false; goto retblock; @@ -229,6 +233,7 @@ static bool parse_query(GWBUF* querybuf) if (thd == NULL) { + MXS_ERROR("THD creation failed."); /** Free parsing info data */ parsing_info_done(pi); succp = false; diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index cca6acc3c..5c0ae9509 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -2178,7 +2178,7 @@ static bool route_single_stmt( * Check if this is a LOAD DATA LOCAL INFILE query. If so, send all queries * to the master until the last, empty packet arrives. */ - if (!rses->rses_load_active) + if (!rses->rses_load_active && packet_type == MYSQL_COM_QUERY) { qc_query_op_t queryop = qc_get_operation(querybuf); if (queryop == QUERY_OP_LOAD) From dd9f7fbbb792b0428a63bb3516a1bd18d353c4f5 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 4 Mar 2016 09:54:17 +0200 Subject: [PATCH 18/26] Fixed rwsplit length calculations for multi-statements The calculations used the amount of bytes read instead of amount of bytes available and also read one byte too much. --- server/modules/routing/readwritesplit/readwritesplit.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/modules/routing/readwritesplit/readwritesplit.c b/server/modules/routing/readwritesplit/readwritesplit.c index 5c0ae9509..bcaf33b49 100644 --- a/server/modules/routing/readwritesplit/readwritesplit.c +++ b/server/modules/routing/readwritesplit/readwritesplit.c @@ -5368,9 +5368,9 @@ static void check_for_multi_stmt(ROUTER_CLIENT_SES* rses, GWBUF *buf, if ((ptr = strnchr_esc_mysql(data, ';', buflen))) { /** Skip stored procedures etc. */ - while (ptr && is_mysql_sp_end(ptr, ptr - data)) + while (ptr && is_mysql_sp_end(ptr, buflen - (ptr - data))) { - ptr = strnchr_esc_mysql(ptr + 1, ';', ptr - data); + ptr = strnchr_esc_mysql(ptr + 1, ';', buflen - (ptr - data) - 1); } if (ptr) From 9dc55735e7b1595d9ae845aacd43d6b10286bc35 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Fri, 4 Mar 2016 10:58:12 +0200 Subject: [PATCH 19/26] Missing listeners are treated as warnings instead of errors Missing listeners are no longer a cause for shutdown. --- server/core/config.c | 2 ++ server/core/service.c | 53 ++++++++++++++++++++++++++----------------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/server/core/config.c b/server/core/config.c index 1b8ca0e4f..1a9f573cb 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -582,10 +582,12 @@ process_config_context(CONFIG_CONTEXT *context) * error_count += consistency_checks(); */ +#ifdef REQUIRE_LISTENERS if (!service_all_services_have_listeners()) { error_count++; } +#endif if (error_count) { diff --git a/server/core/service.c b/server/core/service.c index 92eadc0e0..a112917f6 100644 --- a/server/core/service.c +++ b/server/core/service.c @@ -429,30 +429,41 @@ int serviceStartAllPorts(SERVICE* service) { SERV_LISTENER *port = service->ports; int listeners = 0; - while (!service->svc_do_shutdown && port) + + if (port) { - listeners += serviceStartPort(service, port); - port = port->next; + while (!service->svc_do_shutdown && port) + { + listeners += serviceStartPort(service, port); + port = port->next; + } + + if (listeners) + { + service->state = SERVICE_STATE_STARTED; + service->stats.started = time(0); + } + else if (service->retry_start) + { + /** Service failed to start any ports. Try again later. */ + service->stats.n_failed_starts++; + char taskname[strlen(service->name) + strlen("_start_retry_") + + (int) ceil(log10(INT_MAX)) + 1]; + int retry_after = MIN(service->stats.n_failed_starts * 10, SERVICE_MAX_RETRY_INTERVAL); + snprintf(taskname, sizeof(taskname), "%s_start_retry_%d", + service->name, service->stats.n_failed_starts); + hktask_oneshot(taskname, service_internal_restart, + (void*) service, retry_after); + MXS_NOTICE("Failed to start service %s, retrying in %d seconds.", + service->name, retry_after); + } + } + else + { + MXS_WARNING("Service '%s' has no listeners defined.", service->name); + listeners = 1; /** Set this to one to suppress errors */ } - if (listeners) - { - service->state = SERVICE_STATE_STARTED; - service->stats.started = time(0); - } - else if (service->retry_start) - { - /** Service failed to start any ports. Try again later. */ - service->stats.n_failed_starts++; - char taskname[strlen(service->name) + strlen("_start_retry_") + (int)ceil(log10(INT_MAX)) + 1]; - int retry_after = MIN(service->stats.n_failed_starts * 10, SERVICE_MAX_RETRY_INTERVAL); - snprintf(taskname, sizeof (taskname), "%s_start_retry_%d", - service->name, service->stats.n_failed_starts); - hktask_oneshot(taskname, service_internal_restart, - (void*) service, retry_after); - MXS_NOTICE("Failed to start service %s, retrying in %d seconds.", - service->name, retry_after); - } return listeners; } From b7904bffb08b7f715ef30d58520bb9fde7e35e85 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 4 Mar 2016 11:41:19 +0200 Subject: [PATCH 20/26] Expose maxscale starttime. Maxinfo needs access to the time MaxScale was started. Now exposed in a way similar to the uptime. --- server/core/gateway.c | 2 +- server/core/misc.c | 16 ++++++++++++---- server/include/maxscale.h | 6 +++++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/server/core/gateway.c b/server/core/gateway.c index ee9336953..9dc266aef 100644 --- a/server/core/gateway.c +++ b/server/core/gateway.c @@ -1291,7 +1291,7 @@ int main(int argc, char **argv) *maxlog_enabled = 1; *log_to_shm = 0; - maxscale_reset_uptime(); + maxscale_reset_starttime(); sigemptyset(&sigpipe_mask); sigaddset(&sigpipe_mask, SIGPIPE); diff --git a/server/core/misc.c b/server/core/misc.c index 9cf681f59..a52ffdaea 100644 --- a/server/core/misc.c +++ b/server/core/misc.c @@ -20,14 +20,22 @@ #include #include -static time_t maxscale_started; +static time_t started; /** * Reset the start time from which the uptime is calculated. */ -void maxscale_reset_uptime(void) +void maxscale_reset_starttime(void) { - maxscale_started = time(0); + started = time(0); +} + +/** + * Return the time when MaxScale was started. + */ +time_t maxscale_started(void) +{ + return started; } /** @@ -37,5 +45,5 @@ void maxscale_reset_uptime(void) */ int maxscale_uptime() { - return time(0) - maxscale_started; + return time(0) - started; } diff --git a/server/include/maxscale.h b/server/include/maxscale.h index ad790cd00..3f5c78b6e 100644 --- a/server/include/maxscale.h +++ b/server/include/maxscale.h @@ -32,6 +32,9 @@ * @endverbatim */ +#include + + /* Exit status for MaxScale */ #define MAXSCALE_SHUTDOWN 0 /* Good shutdown */ #define MAXSCALE_BADCONFIG 1 /* Configuration fiel error */ @@ -41,7 +44,8 @@ #define MAXSCALE_BADARG 5 /* Bad command line argument */ #define MAXSCALE_INTERNALERROR 6 /* Internal error, see error log */ -void maxscale_reset_uptime(void); +void maxscale_reset_starttime(void); +time_t maxscale_started(void); int maxscale_uptime(void); #endif From 739fe7039caa23fbfabd2d7fb42dc791a3cbaa03 Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 4 Mar 2016 11:47:12 +0200 Subject: [PATCH 21/26] Use new functions for accessing start- and uptime. --- server/modules/routing/maxinfo/maxinfo.c | 6 +++--- server/modules/routing/maxinfo/maxinfo_exec.c | 9 ++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/server/modules/routing/maxinfo/maxinfo.c b/server/modules/routing/maxinfo/maxinfo.c index 797b47403..45e90382c 100644 --- a/server/modules/routing/maxinfo/maxinfo.c +++ b/server/modules/routing/maxinfo/maxinfo.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -430,11 +431,10 @@ maxinfo_statistics(INFO_INSTANCE *router, INFO_SESSION *session, GWBUF *queue) char result[1000], *ptr; GWBUF *ret; int len; -extern int MaxScaleUptime(); snprintf(result, 1000, "Uptime: %u Threads: %u Sessions: %u ", - MaxScaleUptime(), + maxscale_uptime(), config_threadcount(), serviceSessionCountAll()); if ((ret = gwbuf_alloc(4 + strlen(result))) == NULL) @@ -540,7 +540,7 @@ static char buf[40]; { (*context)++; row = resultset_make_row(result); - sprintf(buf, "%u", (unsigned int)MaxScaleStarted); + sprintf(buf, "%u", (unsigned int)maxscale_started()); resultset_row_set(row, 0, buf); return row; } diff --git a/server/modules/routing/maxinfo/maxinfo_exec.c b/server/modules/routing/maxinfo/maxinfo_exec.c index 658aa45b6..b1521c080 100644 --- a/server/modules/routing/maxinfo/maxinfo_exec.c +++ b/server/modules/routing/maxinfo/maxinfo_exec.c @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -776,8 +777,6 @@ getMaxScaleHome() #define VT_STRING 1 #define VT_INT 2 -extern int MaxScaleUptime(); - typedef void *(*STATSFUNC)(); /** * Variables that may be sent in a show variables @@ -794,7 +793,7 @@ static struct { { "MAXSCALE_THREADS", VT_INT, (STATSFUNC)config_threadcount }, { "MAXSCALE_NBPOLLS", VT_INT, (STATSFUNC)config_nbpolls }, { "MAXSCALE_POLLSLEEP", VT_INT, (STATSFUNC)config_pollsleep }, - { "MAXSCALE_UPTIME", VT_INT, (STATSFUNC)MaxScaleUptime }, + { "MAXSCALE_UPTIME", VT_INT, (STATSFUNC)maxscale_uptime }, { "MAXSCALE_SESSIONS", VT_INT, (STATSFUNC)serviceSessionCountAll }, { NULL, 0, NULL } }; @@ -1051,8 +1050,8 @@ static struct { int type; STATSFUNC func; } status[] = { - { "Uptime", VT_INT, (STATSFUNC)MaxScaleUptime }, - { "Uptime_since_flush_status", VT_INT, (STATSFUNC)MaxScaleUptime }, + { "Uptime", VT_INT, (STATSFUNC)maxscale_uptime }, + { "Uptime_since_flush_status", VT_INT, (STATSFUNC)maxscale_uptime }, { "Threads_created", VT_INT, (STATSFUNC)config_threadcount }, { "Threads_running", VT_INT, (STATSFUNC)config_threadcount }, { "Threadpool_threads", VT_INT, (STATSFUNC)config_threadcount }, From 768ff2442852e5cccceb79929996ad94572e97ee Mon Sep 17 00:00:00 2001 From: counterpoint Date: Fri, 4 Mar 2016 11:53:54 +0000 Subject: [PATCH 22/26] Ensure SSL_LISTENER structure is intialised to binary zero when allocated in configuration processing by using calloc instead of malloc. --- server/core/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/core/config.c b/server/core/config.c index 1a9f573cb..c8c9f81a2 100644 --- a/server/core/config.c +++ b/server/core/config.c @@ -1038,7 +1038,7 @@ make_ssl_structure (CONFIG_CONTEXT *obj, bool require_cert, int *error_count) ssl = config_get_value(obj->parameters, "ssl"); if (ssl && !strcmp(ssl, "required")) { - if ((new_ssl = malloc(sizeof(SSL_LISTENER))) == NULL) + if ((new_ssl = calloc(1, sizeof(SSL_LISTENER))) == NULL) { return NULL; } From a86903f3d020e112a1d09a72281909623a5a547e Mon Sep 17 00:00:00 2001 From: Johan Wikman Date: Fri, 4 Mar 2016 13:10:05 +0200 Subject: [PATCH 23/26] Remove obsolete declarations. And dummy definitions as well. --- server/modules/routing/binlog/blr.c | 1 - server/modules/routing/binlog/blr_file.c | 1 - server/modules/routing/binlog/maxbinlogcheck.c | 6 ------ server/modules/routing/binlog/test/testbinlog.c | 6 ------ server/modules/routing/maxinfo/maxinfo.c | 1 - 5 files changed, 15 deletions(-) diff --git a/server/modules/routing/binlog/blr.c b/server/modules/routing/binlog/blr.c index a67a13fd6..cc777146f 100644 --- a/server/modules/routing/binlog/blr.c +++ b/server/modules/routing/binlog/blr.c @@ -113,7 +113,6 @@ extern char *create_hex_sha1_sha1_passwd(char *passwd); extern int blr_read_events_all_events(ROUTER_INSTANCE *router, int fix, int debug); void blr_master_close(ROUTER_INSTANCE *); char * blr_last_event_description(ROUTER_INSTANCE *router); -extern int MaxScaleUptime(); char *blr_get_event_description(ROUTER_INSTANCE *router, uint8_t event); /** The module object definition */ diff --git a/server/modules/routing/binlog/blr_file.c b/server/modules/routing/binlog/blr_file.c index 0331b45c9..89c4caa91 100644 --- a/server/modules/routing/binlog/blr_file.c +++ b/server/modules/routing/binlog/blr_file.c @@ -73,7 +73,6 @@ void blr_file_use_binlog(ROUTER_INSTANCE *router, char *file); int blr_file_write_master_config(ROUTER_INSTANCE *router, char *error); extern uint32_t extract_field(uint8_t *src, int bits); static void blr_format_event_size(double *event_size, char *label); -extern int MaxScaleUptime(); extern char *blr_get_event_description(ROUTER_INSTANCE *router, uint8_t event); typedef struct binlog_event_desc { diff --git a/server/modules/routing/binlog/maxbinlogcheck.c b/server/modules/routing/binlog/maxbinlogcheck.c index e8c1c6e25..6ae35e1e4 100644 --- a/server/modules/routing/binlog/maxbinlogcheck.c +++ b/server/modules/routing/binlog/maxbinlogcheck.c @@ -79,12 +79,6 @@ static struct option long_options[] = { char *binlog_check_version = "1.1.0"; -int -MaxScaleUptime() -{ -return 1; -} - int main(int argc, char **argv) { ROUTER_INSTANCE *inst; int fd; diff --git a/server/modules/routing/binlog/test/testbinlog.c b/server/modules/routing/binlog/test/testbinlog.c index 4d6687231..0e6d658d7 100644 --- a/server/modules/routing/binlog/test/testbinlog.c +++ b/server/modules/routing/binlog/test/testbinlog.c @@ -57,12 +57,6 @@ extern int blr_test_parse_change_master_command(char *input, char *error_string, extern char *blr_test_set_master_logfile(ROUTER_INSTANCE *router, char *filename, char *error); extern int blr_test_handle_change_master(ROUTER_INSTANCE* router, char *command, char *error); -int -MaxScaleUptime() -{ -return 1; -} - static struct option long_options[] = { {"debug", no_argument, 0, 'd'}, {"verbose", no_argument, 0, 'v'}, diff --git a/server/modules/routing/maxinfo/maxinfo.c b/server/modules/routing/maxinfo/maxinfo.c index 45e90382c..f00357d7c 100644 --- a/server/modules/routing/maxinfo/maxinfo.c +++ b/server/modules/routing/maxinfo/maxinfo.c @@ -532,7 +532,6 @@ starttime_row(RESULTSET *result, void *data) { int *context = (int *)data; RESULT_ROW *row; -extern time_t MaxScaleStarted; struct tm tm; static char buf[40]; From f0956276aee98cde22dc74feb27c0913da2268d7 Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sat, 5 Mar 2016 06:04:36 +0200 Subject: [PATCH 24/26] Fixed FindMariaDBConnector.cmake failing even if connector is installed This will remove the need to build the connector if it already exists as a system library. --- cmake/FindMariaDBConnector.cmake | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cmake/FindMariaDBConnector.cmake b/cmake/FindMariaDBConnector.cmake index 787bd90fa..31d9e1e24 100644 --- a/cmake/FindMariaDBConnector.cmake +++ b/cmake/FindMariaDBConnector.cmake @@ -6,7 +6,7 @@ # MARIADB_CONNECTOR_STATIC_LIBRARIES - The static connector libraries # MARIADB_CONNECTOR_INCLUDE_DIR - The connector headers -find_library(MARIADB_CONNECTOR_LIBRARIES NAMES mysqlclient PATH_SUFFIXES mysql mariadb) +find_library(MARIADB_CONNECTOR_LIBRARIES NAMES mysqlclient PATH_SUFFIXES mariadb mysql) if(${MARIADB_CONNECTOR_LIBRARIES} MATCHES "NOTFOUND") set(MARIADB_CONNECTOR_FOUND FALSE CACHE INTERNAL "") message(STATUS "Dynamic MySQL client library not found.") @@ -18,7 +18,7 @@ endif() set(OLD_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") -find_library(MARIADB_CONNECTOR_STATIC_LIBRARIES NAMES mysqlclient PATH_SUFFIXES mysql mariadb) +find_library(MARIADB_CONNECTOR_STATIC_LIBRARIES NAMES mysqlclient PATH_SUFFIXES mariadb mysql) set(CMAKE_FIND_LIBRARY_SUFFIXES ${OLD_SUFFIXES}) if(${MARIADB_CONNECTOR_STATIC_LIBRARIES} MATCHES "NOTFOUND") @@ -30,10 +30,13 @@ else() message(STATUS "Found statc MySQL client library: ${MARIADB_CONNECTOR_STATIC_LIBRARIES}") endif() -find_path(MARIADB_CONNECTOR_INCLUDE_DIR mysql.h PATH_SUFFIXES mysql mariadb) +find_path(MARIADB_CONNECTOR_INCLUDE_DIR mysql.h PATH_SUFFIXES mariadb mysql) -include(CheckSymbolExists) -check_symbol_exists(LIBMARIADB ${MARIADB_CONNECTOR_INCLUDE_DIR}/mysql.h HAVE_MARIADB_CONNECTOR) +if(NOT (${MARIADB_CONNECTOR_INCLUDE_DIR} MATCHES "NOTFOUND")) + include(CheckSymbolExists) + set(CMAKE_REQUIRED_INCLUDES ${MARIADB_CONNECTOR_INCLUDE_DIR}) + check_symbol_exists(LIBMARIADB mysql.h HAVE_MARIADB_CONNECTOR) +endif() if(HAVE_MARIADB_CONNECTOR) message(STATUS "Found MariaDB Connector-C") From 7bae03f3b88bfb58c847ba95a0a4e8929418dc4b Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sat, 5 Mar 2016 06:23:35 +0200 Subject: [PATCH 25/26] Added missing PCRE link flags to binlogrouter MariaDB 10.0 might require it to be linked against system PCRE. This will allow the binlogrouter to be built with MariaDB 10.0 embedded libraries. --- server/modules/routing/binlog/CMakeLists.txt | 4 ++-- server/modules/routing/binlog/test/CMakeLists.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/modules/routing/binlog/CMakeLists.txt b/server/modules/routing/binlog/CMakeLists.txt index 2250ca0ea..851c71776 100644 --- a/server/modules/routing/binlog/CMakeLists.txt +++ b/server/modules/routing/binlog/CMakeLists.txt @@ -3,13 +3,13 @@ set_target_properties(binlogrouter PROPERTIES INSTALL_RPATH ${CMAKE_INSTALL_RPAT set_target_properties(binlogrouter PROPERTIES LINK_FLAGS -Wl,-z,defs) # libbinlogrouter refers to my_uuid_init and my_uuid. They are non-public functions and # should not be used. They are found only from the embedded lib. -target_link_libraries(binlogrouter maxscale-common ${MYSQL_EMBEDDED_LIBRARIES}) +target_link_libraries(binlogrouter maxscale-common ${MYSQL_EMBEDDED_LIBRARIES} ${PCRE_LINK_FLAGS}) install(TARGETS binlogrouter DESTINATION ${MAXSCALE_LIBDIR}) add_executable(maxbinlogcheck maxbinlogcheck.c blr_file.c blr_cache.c blr_master.c blr_slave.c blr.c) # maxbinlogcheck refers to my_uuid_init and my_uuid. They are non-public functions and # should not be used. They are found only from the embedded lib. -target_link_libraries(maxbinlogcheck maxscale-common ${MYSQL_EMBEDDED_LIBRARIES}) +target_link_libraries(maxbinlogcheck maxscale-common ${MYSQL_EMBEDDED_LIBRARIES} ${PCRE_LINK_FLAGS}) install(TARGETS maxbinlogcheck DESTINATION bin) diff --git a/server/modules/routing/binlog/test/CMakeLists.txt b/server/modules/routing/binlog/test/CMakeLists.txt index 7427a184c..7cddb1c0b 100644 --- a/server/modules/routing/binlog/test/CMakeLists.txt +++ b/server/modules/routing/binlog/test/CMakeLists.txt @@ -2,6 +2,6 @@ if(BUILD_TESTS) add_executable(testbinlogrouter testbinlog.c ../blr.c ../blr_slave.c ../blr_master.c ../blr_file.c ../blr_cache.c) # testbinlogrouter refers to my_uuid_init and my_uuin. They are non-public functions and # should not be used. They are found only from the embedded lib. - target_link_libraries(testbinlogrouter maxscale-common ${MYSQL_EMBEDDED_LIBRARIES}) + target_link_libraries(testbinlogrouter maxscale-common ${MYSQL_EMBEDDED_LIBRARIES} ${PCRE_LINK_FLAGS}) add_test(TestBinlogRouter ${CMAKE_CURRENT_BINARY_DIR}/testbinlogrouter) endif() From 9b8b7ee576c8d8275147fd53744a4d488b719cfb Mon Sep 17 00:00:00 2001 From: Markus Makela Date: Sat, 5 Mar 2016 06:25:14 +0200 Subject: [PATCH 26/26] Added support for system PCRE2 This will allow system PCRE2 to be used instead of always using the bundled one. --- CMakeLists.txt | 10 ++++++++-- cmake/BuildPCRE2.cmake | 5 +++-- cmake/FindPCRE2.cmake | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 cmake/FindPCRE2.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 8476fdd12..b1072e24a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,9 +42,15 @@ find_package(Git) find_package(CURL) find_package(RabbitMQ) -# Build PCRE2 +# Find or build PCRE2 # Read BuildPCRE2 for details about how to add pcre2 as a dependency to a target -include(cmake/BuildPCRE2.cmake) +find_package(PCRE2) +if(NOT PCRE2_FOUND) + message(STATUS "Using bundled PCRE2 library") + include(cmake/BuildPCRE2.cmake) +endif() + +include_directories(${PCRE2_INCLUDE_DIRS}) # If the connector was not found, download and build it from source if(NOT MARIADB_CONNECTOR_FOUND) diff --git a/cmake/BuildPCRE2.cmake b/cmake/BuildPCRE2.cmake index f8a16f176..dcf3840e8 100644 --- a/cmake/BuildPCRE2.cmake +++ b/cmake/BuildPCRE2.cmake @@ -14,5 +14,6 @@ ExternalProject_Add(pcre2 SOURCE_DIR ${CMAKE_SOURCE_DIR}/pcre2/ BUILD_COMMAND make INSTALL_COMMAND "") -include_directories(${CMAKE_BINARY_DIR}/pcre2/) -set(PCRE2_LIBRARIES ${CMAKE_BINARY_DIR}/pcre2/libpcre2-8.a CACHE INTERNAL "") +set(PCRE2_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/pcre2/ CACHE PATH "PCRE2 headers" FORCE) +set(PCRE2_LIBRARIES ${CMAKE_BINARY_DIR}/pcre2/libpcre2-8.a CACHE PATH "PCRE2 libraries" FORCE) +set(PCRE2_FOUND TRUE CACHE BOOL "Found PCRE2 libraries" FORCE) diff --git a/cmake/FindPCRE2.cmake b/cmake/FindPCRE2.cmake new file mode 100644 index 000000000..38133d4cf --- /dev/null +++ b/cmake/FindPCRE2.cmake @@ -0,0 +1,17 @@ +# This CMake file tries to find the Perl regular expression libraries +# The following variables are set: +# PCRE2_FOUND - System has the PCRE library +# PCRE2_LIBRARIES - The PCRE library file +# PCRE2_INCLUDE_DIRS - The folder with the PCRE headers + +find_library(PCRE2_LIBRARIES NAMES pcre2 pcre2-8) +find_path(PCRE2_INCLUDE_DIRS pcre2.h) +if(PCRE2_LIBRARIES AND PCRE2_INCLUDE_DIRS) + message(STATUS "PCRE2 libs: ${PCRE2_LIBRARIES}") + message(STATUS "PCRE2 include directory: ${PCRE2_INCLUDE_DIRS}") + set(PCRE2_FOUND TRUE CACHE BOOL "Found PCRE2 libraries" FORCE) + add_custom_target(pcre2) +else() + set(PCRE2_FOUND FALSE CACHE BOOL "Found PCRE2 libraries" FORCE) + message(STATUS "PCRE2 library not found.") +endif()