From ad87126c1611b3d02f19488cf0a999f30f498915 Mon Sep 17 00:00:00 2001 From: Massimiliano Pinto Date: Wed, 3 Jul 2013 18:10:10 +0200 Subject: [PATCH 01/13] Added support for delay queue and authlock --- core/dcb.c | 7 +++++++ include/dcb.h | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/core/dcb.c b/core/dcb.c index 6db392d14..51fbef92b 100644 --- a/core/dcb.c +++ b/core/dcb.c @@ -35,6 +35,10 @@ * 28/06/13 Mark Riddoch Changed the free mechanism ti * introduce a zombie state for the * dcb + * 02/07/2013 Massimiliano Pinto Addition of delayqlock, delayq and authlock + * for handling backend asynchronous protocol connection + * and a generic lock for backend authentication + * * @endverbatim */ #include @@ -77,7 +81,10 @@ DCB *rval; return NULL; } spinlock_init(&rval->writeqlock); + spinlock_init(&rval->delayqlock); + spinlock_init(&rval->authlock); rval->writeq = NULL; + rval->delayq = NULL; rval->remote = NULL; rval->state = DCB_STATE_ALLOC; rval->next = NULL; diff --git a/include/dcb.h b/include/dcb.h index fe9047397..4f818039f 100644 --- a/include/dcb.h +++ b/include/dcb.h @@ -39,6 +39,9 @@ struct service; * 11/06/13 Mark Riddoch Updated GWPROTOCOL structure with new * entry points * 18/06/13 Mark Riddoch Addition of the listener entry point + * 02/06/2013 Massimiliano Pinto Addition of delayqlock, delayq and authlock + * for handling backend asynchronous protocol connection + * and a generic lock for backend authentication * * @endverbatim */ @@ -134,6 +137,7 @@ typedef struct dcb { GWBUF *writeq; /**< Write Data Queue */ SPINLOCK delayqlock; /**< Delay Backend Write Queue spinlock */ GWBUF *delayq; /**< Delay Backend Write Data Queue */ + SPINLOCK authlock; /**< Generic Authorization spinlock */ DCBSTATS stats; /**< DCB related statistics */ From b4f5889a3a939c9b1320c5d884a3830db9c3b224 Mon Sep 17 00:00:00 2001 From: Mark Riddoch Date: Wed, 3 Jul 2013 18:15:02 +0200 Subject: [PATCH 02/13] Fix in poll.c to prevent further processing when an error makes a DCBV into a zombie Fix in laod_config for issues when not being able to load router modules --- core/config.c | 4 ++-- core/dcb.c | 6 ++++-- core/poll.c | 6 ++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/core/config.c b/core/config.c index 7e56baaf2..524d27fd6 100644 --- a/core/config.c +++ b/core/config.c @@ -176,7 +176,7 @@ CONFIG_CONTEXT *obj; { char *servers = config_get_value(obj->parameters, "servers"); char *roptions = config_get_value(obj->parameters, "router_options"); - if (servers) + if (servers && obj->element) { char *s = strtok(servers, ","); while (s) @@ -191,7 +191,7 @@ CONFIG_CONTEXT *obj; s = strtok(NULL, ","); } } - if (roptions) + if (roptions && obj->element) { char *s = strtok(roptions, ","); while (s) diff --git a/core/dcb.c b/core/dcb.c index 51fbef92b..59d5aa7d0 100644 --- a/core/dcb.c +++ b/core/dcb.c @@ -258,7 +258,8 @@ GWPROTOCOL *funcs; } if ((funcs = (GWPROTOCOL *)load_module(protocol, MODULE_PROTOCOL)) == NULL) { - dcb_free(dcb); + dcb_final_free(dcb); + fprintf(stderr, "Failed to load protocol module for %s, feee dcb %p\n", protocol, dcb); return NULL; } memcpy(&(dcb->func), funcs, sizeof(GWPROTOCOL)); @@ -266,7 +267,8 @@ GWPROTOCOL *funcs; if ((dcb->fd = dcb->func.connect(dcb, server, session)) == -1) { - dcb_free(dcb); + dcb_final_free(dcb); + fprintf(stderr, "Failed to connect to server, feee dcb %p\n", dcb); return NULL; } atomic_add(&server->stats.n_connections, 1); diff --git a/core/poll.c b/core/poll.c index 32154c239..b911176c7 100644 --- a/core/poll.c +++ b/core/poll.c @@ -141,6 +141,7 @@ struct epoll_event events[MAX_EVENTS]; int i, nfds; int thread_id = (int)arg; + /* Add this thread to the bitmask of running polling threads */ bitmask_set(&poll_mask, thread_id); while (1) { @@ -174,11 +175,15 @@ int thread_id = (int)arg; { atomic_add(&pollStats.n_error, 1); dcb->func.error(dcb); + if (DCB_ISZOMBIE(dcb)) + continue; } if (ev & EPOLLHUP) { atomic_add(&pollStats.n_hup, 1); dcb->func.hangup(dcb); + if (DCB_ISZOMBIE(dcb)) + continue; } if (ev & EPOLLOUT) { @@ -203,6 +208,7 @@ int thread_id = (int)arg; dcb_process_zombies(thread_id); if (shutdown) { + /* Remove this thread from the bitmask of running polling threads */ bitmask_clear(&poll_mask, thread_id); return; } From 7a8b65aa0af18d8403d768b139c8a31101cd90be Mon Sep 17 00:00:00 2001 From: Massimiliano Pinto Date: Thu, 4 Jul 2013 11:18:02 +0200 Subject: [PATCH 03/13] Added MYSQL_PENDING_CONNECT for handling EAGAIN status in connect. MYSQL_FAILED_AUTHENTICATION and MYSQL_SUCCESFUL_AUTHENTICATION are for backend server auth reply --- .../include/mysql_client_server_protocol.h | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/modules/include/mysql_client_server_protocol.h b/modules/include/mysql_client_server_protocol.h index 167511ff3..b276ff4a5 100644 --- a/modules/include/mysql_client_server_protocol.h +++ b/modules/include/mysql_client_server_protocol.h @@ -104,13 +104,18 @@ typedef struct mysql_session { /* MySQL Protocol States */ #define MYSQL_ALLOC 0 /* Allocate data */ -#define MYSQL_AUTH_SENT 1 /* Authentication handshake has been sent */ -#define MYSQL_AUTH_RECV 2 /* Received user, password, db and capabilities */ -#define MYSQL_AUTH_FAILED 3 /* Auth failed, return error packet */ -#define MYSQL_IDLE 4 /* Auth done. Protocol is idle, waiting for statements */ -#define MYSQL_ROUTING 5 /* The received command has been routed to backend(s) */ -#define MYSQL_WAITING_RESULT 6 /* Waiting for result set */ -#define MYSQL_CONNECTED 7 /* Backend socket Connected */ +#define MYSQL_CONNECTED 1 /* Backend socket Connected */ +#define MYSQL_PENDING_CONNECT 2 /* Backend socket pending connect */ +#define MYSQL_AUTH_SENT 3 /* Authentication handshake has been sent */ +#define MYSQL_AUTH_RECV 4 /* Received user, password, db and capabilities */ +#define MYSQL_AUTH_FAILED 5 /* Auth failed, return error packet */ +#define MYSQL_IDLE 6 /* Auth done. Protocol is idle, waiting for statements */ +#define MYSQL_ROUTING 7 /* The received command has been routed to backend(s) */ +#define MYSQL_WAITING_RESULT 8 /* Waiting for result set */ + +/* MySQL states for authentication reply */ +#define MYSQL_FAILED_AUTHENTICATION 1 +#define MYSQL_SUCCESFUL_AUTHENTICATION 0 /* Protocol packing macros. */ #define gw_mysql_set_byte2(__buffer, __int) do { \ From b549ad1676bd89d5420003ed46c47a2cfb51029a Mon Sep 17 00:00:00 2001 From: Massimiliano Pinto Date: Thu, 4 Jul 2013 11:48:03 +0200 Subject: [PATCH 04/13] Changed value for MYSQL_PENDING_CONNECT and MYSQL_CONNECTED --- modules/include/mysql_client_server_protocol.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/include/mysql_client_server_protocol.h b/modules/include/mysql_client_server_protocol.h index b276ff4a5..955a92d30 100644 --- a/modules/include/mysql_client_server_protocol.h +++ b/modules/include/mysql_client_server_protocol.h @@ -104,8 +104,8 @@ typedef struct mysql_session { /* MySQL Protocol States */ #define MYSQL_ALLOC 0 /* Allocate data */ -#define MYSQL_CONNECTED 1 /* Backend socket Connected */ -#define MYSQL_PENDING_CONNECT 2 /* Backend socket pending connect */ +#define MYSQL_PENDING_CONNECT 1 /* Backend socket pending connect */ +#define MYSQL_CONNECTED 2 /* Backend socket Connected */ #define MYSQL_AUTH_SENT 3 /* Authentication handshake has been sent */ #define MYSQL_AUTH_RECV 4 /* Received user, password, db and capabilities */ #define MYSQL_AUTH_FAILED 5 /* Auth failed, return error packet */ From 272f7d86858f1a1c6c7358047367f3a9410037c9 Mon Sep 17 00:00:00 2001 From: Massimiliano Pinto Date: Thu, 4 Jul 2013 12:08:09 +0200 Subject: [PATCH 05/13] Added comments and revision history update --- .../include/mysql_client_server_protocol.h | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/modules/include/mysql_client_server_protocol.h b/modules/include/mysql_client_server_protocol.h index 955a92d30..7b551a692 100644 --- a/modules/include/mysql_client_server_protocol.h +++ b/modules/include/mysql_client_server_protocol.h @@ -25,6 +25,8 @@ * 01-06-2013 Mark Riddoch Initial implementation * 14-06-2013 Massimiliano Pinto Added specific data * for MySQL session + * 04-07-2013 Massimiliano Pinto Added new MySQL protocol status for asynchronous connection + * Added authentication reply status */ #include @@ -103,19 +105,19 @@ typedef struct mysql_session { } MYSQL_session; /* MySQL Protocol States */ -#define MYSQL_ALLOC 0 /* Allocate data */ -#define MYSQL_PENDING_CONNECT 1 /* Backend socket pending connect */ +#define MYSQL_ALLOC 0 /* Allocate data */ +#define MYSQL_PENDING_CONNECT 1 /* Backend socket pending connect */ #define MYSQL_CONNECTED 2 /* Backend socket Connected */ -#define MYSQL_AUTH_SENT 3 /* Authentication handshake has been sent */ -#define MYSQL_AUTH_RECV 4 /* Received user, password, db and capabilities */ -#define MYSQL_AUTH_FAILED 5 /* Auth failed, return error packet */ -#define MYSQL_IDLE 6 /* Auth done. Protocol is idle, waiting for statements */ -#define MYSQL_ROUTING 7 /* The received command has been routed to backend(s) */ -#define MYSQL_WAITING_RESULT 8 /* Waiting for result set */ +#define MYSQL_AUTH_SENT 3 /* Authentication handshake has been sent */ +#define MYSQL_AUTH_RECV 4 /* Received user, password, db and capabilities */ +#define MYSQL_AUTH_FAILED 5 /* Auth failed, return error packet */ +#define MYSQL_IDLE 6 /* Auth done. Protocol is idle, waiting for statements */ +#define MYSQL_ROUTING 7 /* The received command has been routed to backend(s) */ +#define MYSQL_WAITING_RESULT 8 /* Waiting for result set */ /* MySQL states for authentication reply */ -#define MYSQL_FAILED_AUTHENTICATION 1 -#define MYSQL_SUCCESFUL_AUTHENTICATION 0 +#define MYSQL_FAILED_AUTHENTICATION 1 +#define MYSQL_SUCCESFUL_AUTHENTICATION 0 /* Protocol packing macros. */ #define gw_mysql_set_byte2(__buffer, __int) do { \ From 2cb2a60a96789c9895c8529037760501b2866d90 Mon Sep 17 00:00:00 2001 From: Massimiliano Pinto Date: Thu, 4 Jul 2013 12:37:00 +0200 Subject: [PATCH 06/13] 2 routines added --- modules/include/mysql_client_server_protocol.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/include/mysql_client_server_protocol.h b/modules/include/mysql_client_server_protocol.h index 7b551a692..7a606ed98 100644 --- a/modules/include/mysql_client_server_protocol.h +++ b/modules/include/mysql_client_server_protocol.h @@ -217,6 +217,8 @@ int gw_receive_backend_auth(MySQLProtocol *conn); int gw_decode_mysql_server_handshake(MySQLProtocol *conn, uint8_t *payload); int gw_read_backend_handshake(MySQLProtocol *conn); int gw_send_authentication_to_backend(char *dbname, char *user, uint8_t *passwd, MySQLProtocol *conn); +const char *gw_mysql_protocol_state2string(int state); +int gw_do_connect_to_backend(char *host, int port, MySQLProtocol *conn); extern void gw_sha1_str(const uint8_t *in, int in_len, uint8_t *out); extern void gw_sha1_2_str(const uint8_t *in, int in_len, const uint8_t *in2, int in2_len, uint8_t *out); From 4adf121de90124f6b4dde62c4a7ee7fa354e7aa7 Mon Sep 17 00:00:00 2001 From: Massimiliano Pinto Date: Thu, 4 Jul 2013 12:43:16 +0200 Subject: [PATCH 07/13] Now the MySQL connect phase is fully asynchronous EINPROGRESS will set the MYSQL_PENDING_CONNECT protocol state --- modules/protocol/mysql_backend.c | 693 ++++++++----------------------- modules/protocol/mysql_common.c | 76 +++- 2 files changed, 253 insertions(+), 516 deletions(-) diff --git a/modules/protocol/mysql_backend.c b/modules/protocol/mysql_backend.c index 663aed9af..e9995671a 100644 --- a/modules/protocol/mysql_backend.c +++ b/modules/protocol/mysql_backend.c @@ -34,10 +34,10 @@ * and necessary headers. * 01/07/2013 Massimiliano Pinto Put Log Manager example code behind SS_DEBUG macros. * 03/07/2013 Massimiliano Pinto Added delayq for incoming data before mysql connection + * 04/07/2013 Massimiliano Pinto Added asyncrhronous MySQL protocol connection to backend */ static char *version_str = "V1.0.0"; -extern char *gw_strend(register const char *s); int gw_mysql_connect(char *host, int port, char *dbname, char *user, uint8_t *passwd, MySQLProtocol *conn); static int gw_create_backend_connection(DCB *client_dcb, SERVER *server, SESSION *in_session); static int gw_read_backend_event(DCB* dcb); @@ -45,15 +45,18 @@ static int gw_write_backend_event(DCB *dcb); static int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue); static int gw_error_backend_event(DCB *dcb); static int gw_backend_close(DCB *dcb); +static int gw_backend_hangup(DCB *dcb); static int backend_write_delayqueue(DCB *dcb); static void backend_set_delayqueue(DCB *dcb, GWBUF *queue); +extern char *gw_strend(register const char *s); + static GWPROTOCOL MyObject = { gw_read_backend_event, /* Read - EPOLLIN handler */ gw_MySQLWrite_backend, /* Write - data from gateway */ gw_write_backend_event, /* WriteReady - EPOLLOUT handler */ gw_error_backend_event, /* Error - EPOLLERR handler */ - NULL, /* HangUp - EPOLLHUP handler */ + gw_backend_hangup, /* HangUp - EPOLLHUP handler */ NULL, /* Accept */ gw_create_backend_connection, /* Connect */ gw_backend_close, /* Close */ @@ -101,56 +104,89 @@ GetModuleObject() } -////////////////////////////////////////// -//backend read event triggered by EPOLLIN -////////////////////////////////////////// +/** + * Backend Read Event for EPOLLIN on the MySQL backend protocol module + * @param dcb The backend Descriptor Control Block + * @return 1 on operation, 0 for no action + */ + static int gw_read_backend_event(DCB *dcb) { - int n; MySQLProtocol *client_protocol = NULL; + MySQLProtocol *backend_protocol = NULL; + MYSQL_session *current_session = NULL; - if (dcb) - if(dcb->session) + if (dcb) { + if(dcb->session) { client_protocol = SESSION_PROTOCOL(dcb->session, MySQLProtocol); + } -#ifdef GW_DEBUG_READ_EVENT - fprintf(stderr, "Backend ready! Read from Backend %i, write to client %i, client state %i\n", dcb->fd, dcb->session->client->fd, client_protocol->state); -#endif + backend_protocol = (MySQLProtocol *) dcb->protocol; + } + + current_session = (MYSQL_session *)dcb->session->data; + + // backend is not yet ready + if ( (backend_protocol->state == MYSQL_ALLOC) || (backend_protocol->state == MYSQL_PENDING_CONNECT)) { + fprintf(stderr, ">>>> The backend %i is not ready\n", dcb->fd); + + return 0; + } + + // backend is conected: read server handshake and write auth request and return + if (backend_protocol->state == MYSQL_CONNECTED) { + + gw_read_backend_handshake(backend_protocol); + + gw_send_authentication_to_backend(current_session->db, current_session->user, current_session->client_sha1, backend_protocol); + return 1; + } + + // ready to check the authentication reply + if (backend_protocol->state == MYSQL_AUTH_RECV) { + int rv = -1; + rv = gw_receive_backend_auth(backend_protocol); + + switch (rv) { + case MYSQL_FAILED_AUTHENTICATION: + backend_protocol->state = MYSQL_AUTH_FAILED; + + fprintf(stderr, ">>>> BACKEND EPOLLIN %i , AUTH FAILED %i\n", dcb->fd, backend_protocol->state); + + dcb_close(dcb); + + return 1; + + case MYSQL_SUCCESFUL_AUTHENTICATION: + spinlock_acquire(&dcb->authlock); + + backend_protocol->state = MYSQL_IDLE; + + fprintf(stderr, ">>>> BACKEND EPOLLIN %i , auth is OK, %i\n", dcb->fd, backend_protocol->state); + + // check the delay queue + if(dcb->delayq) { + fprintf(stderr, ">>> Mysql Backend is ok, Force writing to the backend from delay queue. Backend Proto state is %i, Client Proto state is %i. Writing %i bytes\n", backend_protocol->state, client_protocol->state, gwbuf_length(dcb->delayq)); + backend_write_delayqueue(dcb); + spinlock_release(&dcb->authlock); + return 1; + } + spinlock_release(&dcb->authlock); + + return 1; + + default: + // no other authentication state here right now, so just return + return 0; + } + } + + // reading MySQL command output from backend and writing to the client if ((client_protocol->state == MYSQL_WAITING_RESULT) || (client_protocol->state == MYSQL_IDLE)) { - int b = -1; - GWBUF *buffer, *head; + GWBUF *head = NULL; - if (ioctl(dcb->fd, FIONREAD, &b)) { - fprintf(stderr, "Backend Ioctl FIONREAD error %i, %s\n", errno , strerror(errno)); - } else { - //fprintf(stderr, "Backend IOCTL FIONREAD bytes to read = %i\n", b); - } - - /* - * Read all the data that is available into a chain of buffers - */ - head = NULL; - while (b > 0) - { - int bufsize = b < MAX_BUFFER_SIZE ? b : MAX_BUFFER_SIZE; - if ((buffer = gwbuf_alloc(bufsize)) == NULL) - { - /* Bad news, we have run out of memory */ - return 0; - } - GW_NOINTR_CALL(n = read(dcb->fd, GWBUF_DATA(buffer), bufsize); dcb->stats.n_reads++); - if (n < 0) - { - // if eerno == EAGAIN || EWOULDBLOCK is missing - // do the right task, not just break - break; - } - - head = gwbuf_append(head, buffer); - - // how many bytes left - b -= n; - } + // read data + dcb_read(dcb, &head); // write the gwbuffer to client dcb->session->client->func.write(dcb->session->client, head); @@ -168,6 +204,23 @@ static int gw_read_backend_event(DCB *dcb) { * @return The number of bytes written */ static int gw_write_backend_event(DCB *dcb) { + MySQLProtocol *backend_protocol = dcb->protocol; + + //fprintf(stderr, ">>>> Backend %i, protocol state [%s]\n", backend_protocol->fd, gw_mysql_protocol_state2string(backend_protocol->state)); + + // spinlock_acquire(&dcb->connectlock); + + if (backend_protocol->state == MYSQL_PENDING_CONNECT) { + //fprintf(stderr, ">>>> Now the backend %i is CONNECTED\n", backend_protocol->fd); + backend_protocol->state = MYSQL_CONNECTED; + + // spinlock_release(&dcb->connectlock); + + return 1; + } + + // spinlock_release(&dcb->connectlock); + return dcb_drain_writeq(dcb); } @@ -181,9 +234,29 @@ static int gw_write_backend_event(DCB *dcb) { static int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) { + MySQLProtocol *backend_protocol = dcb->protocol; + + spinlock_acquire(&dcb->authlock); + + // put incoming data to the delay queue unless backend is connected with auth ok + if (backend_protocol->state != MYSQL_IDLE) { + fprintf(stderr, ">>> Writing in the backend %i delay queue\n", dcb->fd); + + backend_set_delayqueue(dcb, queue); + spinlock_release(&dcb->authlock); + return 1; + } + + spinlock_release(&dcb->authlock); + + // Normal flow of backend write; return dcb_write(dcb, queue); } +/** + * Backend Error Handling + * + */ static int gw_error_backend_event(DCB *dcb) { fprintf(stderr, "#### Handle Backend error function for %i\n", dcb->fd); @@ -193,9 +266,6 @@ static int gw_error_backend_event(DCB *dcb) { fprintf(stderr, "Backend poll_remove_dcb: from events check failed to delete %i, [%i]:[%s]\n", dcb->fd, errno, strerror(errno)); } -#ifdef GW_EVENT_DEBUG - fprintf(stderr, "Backend closing fd [%i]=%i, from events check\n", dcb->fd, protocol->fd); -#endif if (dcb->fd) { dcb->state = DCB_STATE_DISCONNECTED; fprintf(stderr, "Freeing backend MySQL conn %p, %p\n", dcb->protocol, &dcb->protocol); @@ -208,60 +278,91 @@ static int gw_error_backend_event(DCB *dcb) { } /* - * Create a new MySQL backend connection. + * Create a new ackend connection. * - * This routine performs the MySQL connection to the backend and fills the session->backends of the callier dcb - * with the new allocatetd dcb and adds the new socket to the poll set + * This routine will connect to a backend server * * - backend dcb allocation * - MySQL session data fetch * - backend connection using data in MySQL session * - * @param client_dcb The client DCB struct + * @param backend The Backend DCB allocated from dcb_connect + * @param server The selected server to connect to + * @param session The current session from Client DCB * @return 0 on Success or 1 on Failure. */ static int gw_create_backend_connection(DCB *backend, SERVER *server, SESSION *session) { - MySQLProtocol *ptr_proto = NULL; + MySQLProtocol *protocol = NULL; MYSQL_session *s_data = NULL; + int rv = -1; - fprintf(stderr, "HERE, the server to connect is [%s]:[%i]\n", server->name, server->port); + //fprintf(stderr, "HERE, the server to connect is [%s]:[%i]\n", server->name, server->port); - backend->protocol = (MySQLProtocol *) calloc(1, sizeof(MySQLProtocol)); + protocol = (MySQLProtocol *) calloc(1, sizeof(MySQLProtocol)); + protocol->state = MYSQL_ALLOC; + + backend->protocol = protocol; + + // put the backend dcb in the protocol struct + protocol->descriptor = backend; - ptr_proto = (MySQLProtocol *)backend->protocol; s_data = (MYSQL_session *)session->client->data; -// fprintf(stderr, "HERE before connect, s_data is [%p]\n", s_data); -// fprintf(stderr, "HERE before connect, username is [%s]\n", s_data->user); + // let's try to connecte to a backend server, only connect sys call + // The socket descriptor is in Non Blocking status, this is set in the function + rv = gw_do_connect_to_backend(server->name, server->port, protocol); - // this is blocking until auth done - if (gw_mysql_connect(server->name, server->port, s_data->db, s_data->user, s_data->client_sha1, backend->protocol) == 0) { - memcpy(&backend->fd, &ptr_proto->fd, sizeof(backend->fd)); + // we could also move later, this in to the gw_do_connect_to_backend using protocol->descriptor - setnonblocking(backend->fd); - fprintf(stderr, "Connected to backend mysql server. fd is %i\n", backend->fd); - } else { - fprintf(stderr, "<<<< NOT Connected to backend mysql server!!!\n"); - backend->fd = -1; - return -1; + memcpy(&backend->fd, &protocol->fd, sizeof(backend->fd)); + + switch (rv) { + + case 0: + fprintf(stderr, "Connected to backend mysql server. fd is %i\n", backend->fd); + protocol->state = MYSQL_CONNECTED; + + break; + + case 1: + fprintf(stderr, "Connection is PENDING to backend mysql server. fd is %i\n", backend->fd); + protocol->state = MYSQL_PENDING_CONNECT; + + break; + + default: + fprintf(stderr, "<<<< NOT Connected to backend mysql server!!!\n"); + backend->fd = -1; + + break; } - // if connected, it will be addeed to the epoll from the caller of connect() + fprintf(stderr, "--> Backend conn added [%i], in the client session [%i]\n", backend->fd, session->client->fd); - if (backend->fd <= 0) { - perror("ERROR: epoll_ctl: backend sock"); - backend->fd = -1; - return -1; - } else { - fprintf(stderr, "--> Backend conn added, bk_fd [%i], scramble [%s], is session with client_fd [%i]\n", backend->fd, ptr_proto->scramble, session->client->fd); - backend->state = DCB_STATE_POLLING; + backend->state = DCB_STATE_POLLING; - return backend->fd; - } - return -1; + return backend->fd; } +/** + * Hangup routine the backend dcb: it does nothing right now + * + * @param dcb The current Backend DCB + * @return 1 always + */ +static int +gw_backend_hangup(DCB *dcb) +{ + return 1; +} + +/** + * Close the backend dcb + * + * @param dcb The current Backend DCB + * @return 1 always + */ static int gw_backend_close(DCB *dcb) { @@ -269,444 +370,6 @@ gw_backend_close(DCB *dcb) return 1; } -/* - * Create a new MySQL connection. - * - * This routine performs the full MySQL connection to the specified server. - * It does - * - socket init - * - socket connect - * - server handshake parsing - * - authenticatio reply - * - the Auth ack receive - * - * Please note, all socket operation are in blocking state - * Status: work in progress. - * - * @param host The TCP/IP host address to connect to - * @param port The TCP/IP host port to connect to - * @param dbname The optional database name. Use NULL if not interested in - * @param user The MySQL database Username: required - * @param passwd The MySQL database Password: required - * @param conn The MySQLProtocol structure to be filled: must be preallocated with gw_mysql_init() - * @return 0 on Success or 1 on Failure. - */ -int gw_mysql_connect(char *host, int port, char *dbname, char *user, uint8_t *passwd, MySQLProtocol *conn) { - - struct sockaddr_in serv_addr; - int compress = 0; - int rv; - int so = 0; - int ciclo = 0; - uint8_t buffer[SMALL_CHUNK]; - uint8_t packet_buffer[SMALL_CHUNK]; - uint8_t *payload = NULL; - int server_protocol; - uint8_t *server_version_end = NULL; - uint16_t mysql_server_capabilities_one; - uint16_t mysql_server_capabilities_two; - unsigned long tid =0; - long bytes; - uint8_t scramble_data_1[8 + 1] = ""; - uint8_t scramble_data_2[12 + 1] = ""; - uint8_t capab_ptr[4]; - int scramble_len; - uint8_t scramble[GW_MYSQL_SCRAMBLE_SIZE + 1]; - uint8_t client_scramble[GW_MYSQL_SCRAMBLE_SIZE + 1]; - uint8_t client_capabilities[4]; - uint32_t server_capabilities; - uint32_t final_capabilities; - char dbpass[129]=""; - - char *curr_db = NULL; - uint8_t *curr_passwd = NULL; - - if (strlen(dbname)) - curr_db = dbname; - - if (strlen((char *)passwd)) - curr_passwd = passwd; - - conn->state = MYSQL_ALLOC; - conn->fd = -1; - - memset(&server_capabilities, '\0', sizeof(server_capabilities)); - memset(&final_capabilities, '\0', sizeof(final_capabilities)); - -#ifdef MYSQL_CONN_DEBUG - //fprintf(stderr, ")))) Connect to MySQL: user[%s], SHA1(passwd)[%s], db [%s]\n", user, passwd, dbname); -#endif - - memset(&serv_addr, 0, sizeof serv_addr); - serv_addr.sin_family = AF_INET; - - so = socket(AF_INET,SOCK_STREAM,0); - if (so < 0) { - fprintf(stderr, "Errore creazione socket: [%s] %i\n", strerror(errno), errno); - return 1; - } - - conn->fd = so; - - setipaddress(&serv_addr.sin_addr, host); - serv_addr.sin_port = htons(port); - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "Socket initialized\n"); - fflush(stderr); -#endif - - while(1) { - if ((rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) < 0) { - fprintf(stderr, "Errore connect %i, %s: RV = [%i]\n", errno, strerror(errno), rv); - - if (errno == EINPROGRESS) { - continue; - } else { - close(so); - return -1; - } - } else { - break; - } - } - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "CONNECT is DONE\n"); - fprintf(stderr, "Socket FD is %i\n", so); - fflush(stderr); -#endif - - - memset(&buffer, '\0', sizeof(buffer)); - - bytes = SMALL_CHUNK; - - rv = read(so, buffer, bytes); - - if ( rv >0 ) { -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "RESPONSE ciclo %i HO letto [%s] bytes %li\n",ciclo, buffer, bytes); - fflush(stderr); -#endif - ciclo++; - } else { - if (rv == 0 && errno == EOF) { -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "EOF reached. Bytes = %li\n", bytes); - fflush(stderr); -#endif - } else { -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "###### Receive error FINAL : connection not completed %i %s: RV = [%i]\n", errno, strerror(errno), rv); -#endif - close(so); - - return -1; - } - } - -#ifdef MYSQL_CONN_DEBUG - fwrite(buffer, bytes, 1, stderr); - fflush(stderr); -#endif - - //decode mysql handshake - - payload = buffer + 4; - server_protocol= payload[0]; - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "Server Protocol [%i]\n", server_protocol); - -#endif - payload++; - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "Protocol Version [%s]\n", payload); - fflush(stderr); -#endif - - server_version_end = (uint8_t *) gw_strend((char*) payload); - payload = server_version_end + 1; - - // TID - tid = gw_mysql_get_byte4(payload); - memcpy(&conn->tid, &tid, 4); - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "Thread ID is %lu\n", conn->tid); - fflush(stderr); -#endif - - payload +=4; - - // scramble_part 1 - memcpy(scramble_data_1, payload, 8); - payload += 8; - - // 1 filler - payload++; - - mysql_server_capabilities_one = gw_mysql_get_byte2(payload); - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "Capab_1[\n"); - fwrite(&mysql_server_capabilities_one, 2, 1, stderr); - fflush(stderr); -#endif - - //2 capab_part 1 + 1 language + 2 server_status - payload +=5; - - mysql_server_capabilities_two = gw_mysql_get_byte2(payload); - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "]Capab_2[\n"); - fwrite(&mysql_server_capabilities_two, 2, 1, stderr); - fprintf(stderr, "]\n"); - fflush(stderr); -#endif - - memcpy(&capab_ptr, &mysql_server_capabilities_one, 2); - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "Capab_1[\n"); - fwrite(capab_ptr, 2, 1, stderr); - fflush(stderr); -#endif - - memcpy(&(capab_ptr[2]), &mysql_server_capabilities_two, 2); - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "Capab_2[\n"); - fwrite(capab_ptr, 2, 1, stderr); - fflush(stderr); -#endif - - // 2 capab_part 2 - payload+=2; - - scramble_len = payload[0] -1; - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "Scramble_len [%i]\n", scramble_len); - fflush(stderr); -#endif - - payload += 11; - - memcpy(scramble_data_2, payload, scramble_len - 8); - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "Scramble_buff1["); - fwrite(scramble_data_1, 8, 1, stderr); - fprintf(stderr, "]\nScramble_buff2 ["); - fwrite(scramble_data_2, scramble_len - 8, 1, stderr); - fprintf(stderr, "]\n"); - fflush(stderr); -#endif - - memcpy(scramble, scramble_data_1, 8); - memcpy(scramble + 8, scramble_data_2, scramble_len - 8); - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "Full Scramble 20 bytes is [\n"); - fwrite(scramble, GW_MYSQL_SCRAMBLE_SIZE, 1, stderr); - fprintf(stderr, "\n]\n"); - fflush(stderr); -#endif - - memcpy(conn->scramble, scramble, GW_MYSQL_SCRAMBLE_SIZE); - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "Scramble from MYSQL_Conn is [\n"); - fwrite(scramble, GW_MYSQL_SCRAMBLE_SIZE, 1, stderr); - fprintf(stderr, "\n]\n"); - fflush(stderr); - fprintf(stderr, "Now sending user, pass & db\n["); - fwrite(&server_capabilities, 4, 1, stderr); - fprintf(stderr, "]\n"); -#endif - - final_capabilities = gw_mysql_get_byte4((uint8_t *)&server_capabilities); - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "CAPABS [%u]\n", final_capabilities); - fflush(stderr); -#endif - memset(packet_buffer, '\0', sizeof(packet_buffer)); - //packet_header(byte3 +1 pack#) - packet_buffer[3] = '\x01'; - - final_capabilities |= GW_MYSQL_CAPABILITIES_PROTOCOL_41; - final_capabilities |= GW_MYSQL_CAPABILITIES_CLIENT; - if (compress) { - final_capabilities |= GW_MYSQL_CAPABILITIES_COMPRESS; - fprintf(stderr, "Backend Connection with compression\n"); - fflush(stderr); - } - - if (curr_passwd != NULL) { - uint8_t hash1[GW_MYSQL_SCRAMBLE_SIZE]=""; - uint8_t hash2[GW_MYSQL_SCRAMBLE_SIZE]=""; - uint8_t new_sha[GW_MYSQL_SCRAMBLE_SIZE]=""; - - - memcpy(hash1, passwd, GW_MYSQL_SCRAMBLE_SIZE); - gw_sha1_str(hash1, GW_MYSQL_SCRAMBLE_SIZE, hash2); - gw_bin2hex(dbpass, hash2, GW_MYSQL_SCRAMBLE_SIZE); - gw_sha1_2_str(scramble, GW_MYSQL_SCRAMBLE_SIZE, hash2, GW_MYSQL_SCRAMBLE_SIZE, new_sha); - gw_str_xor(client_scramble, new_sha, hash1, GW_MYSQL_SCRAMBLE_SIZE); - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "Hash1 [%s]\n", hash1); - fprintf(stderr, "Hash2 [%s]\n", hash2); - fprintf(stderr, "SHA1(SHA1(password in hex)\n"); - fprintf(stderr, "PAss [%s]\n", dbpass); - fflush(stderr); - fprintf(stderr, "newsha [%s]\n", new_sha); - fprintf(stderr, "Client send scramble 20 [\n"); - fwrite(client_scramble, GW_MYSQL_SCRAMBLE_SIZE, 1, stderr); - fprintf(stderr, "\n]\n"); - fflush(stderr); -#endif - } - - if (curr_db == NULL) { - // now without db!! - final_capabilities &= ~GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB; - } else { - final_capabilities |= GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB; - } - - payload = packet_buffer + 4; - - final_capabilities |= GW_MYSQL_CAPABILITIES_PLUGIN_AUTH; - - gw_mysql_set_byte4(client_capabilities, final_capabilities); - memcpy(payload, client_capabilities, 4); - - //packet_buffer[4] = '\x8d'; - //packet_buffer[5] = '\xa6'; - //packet_buffer[6] = '\x0f'; - //packet_buffer[7] = '\x00'; - - // set now the max-packet size - payload += 4; - gw_mysql_set_byte4(payload, 16777216); - - // set the charset - payload += 4; - *payload = '\x08'; - - payload++; - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "User is [%s]\n", user); - fflush(stderr); -#endif - - - // 4 + 4 + 4 + 1 + 23 = 36 - payload += 23; - memcpy(payload, user, strlen(user)); - - // 4 + 4 + 1 + 23 = 32 + 1 (scramble_len) + 20 (fixed_scramble) + 1 (user NULL term) + 1 (db NULL term) = 55 - bytes = 32; - - bytes += strlen(user); - // the NULL - bytes++; - - payload += strlen(user); - payload++; - - if (curr_passwd != NULL) { - // set the auth-length - *payload = GW_MYSQL_SCRAMBLE_SIZE; - payload++; - bytes++; - - //copy the 20 bytes scramble data after packet_buffer+36+user+NULL+1 (byte of auth-length) - memcpy(payload, client_scramble, GW_MYSQL_SCRAMBLE_SIZE); - - payload += GW_MYSQL_SCRAMBLE_SIZE; - bytes += GW_MYSQL_SCRAMBLE_SIZE; - - } else { - // skip the auth-length and write a NULL - payload++; - bytes++; - } - - // if the db is not NULL append it - if (curr_db) { - memcpy(payload, curr_db, strlen(curr_db)); - payload += strlen(curr_db); - payload++; - bytes += strlen(curr_db); - // the NULL - bytes++; - } - - memcpy(payload, "mysql_native_password", strlen("mysql_native_password")); - - payload += strlen("mysql_native_password"); - payload++; - - bytes +=strlen("mysql_native_password"); - bytes++; - - gw_mysql_set_byte3(packet_buffer, bytes); - - // the packet header - bytes += 4; - - rv = write(so, packet_buffer, bytes); - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "Sent [%s], [%i] bytes\n", packet_buffer, bytes); - fflush(stderr); -#endif - - if (rv == -1) { - fprintf(stderr, "CONNECT Error in send auth\n"); - } - - bytes = SMALL_CHUNK; - - memset(buffer, '\0', sizeof (buffer)); - - rv = read(so, buffer, SMALL_CHUNK); - - if (rv == -1) { - fprintf(stderr, "CONNCET Error in recv OK for auth\n"); - } - -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "ok packet\["); - fwrite(buffer, bytes, 1, stderr); - fprintf(stderr, "]\n"); - fflush(stderr); -#endif - if (buffer[4] == '\x00') { -#ifdef MYSQL_CONN_DEBUG - fprintf(stderr, "OK packet received, packet # %i\n", buffer[3]); - fflush(stderr); -#endif - conn->state = MYSQL_IDLE; - - return 0; - } else { - - close(so); - } - - return 1; - -} - /** * This routine put into the delay queue the input queue * The input is what backend DCB is receiving diff --git a/modules/protocol/mysql_common.c b/modules/protocol/mysql_common.c index 18d935d8e..b522d24fa 100644 --- a/modules/protocol/mysql_common.c +++ b/modules/protocol/mysql_common.c @@ -22,7 +22,9 @@ * Revision History * Date Who Description * 17/06/2013 Massimiliano Pinto Common MySQL protocol routines - * 02/06/2013 Massimiliano Pinto MySQL connect asynchronous phases + * 02/07/2013 Massimiliano Pinto MySQL connect asynchronous phases + * 04/07/2013 Massimiliano Pinto MySQL connect routine supports EAGAIN + * Added gw_mysql_protocol_state2string for printing MySQL the protocol status */ #include "mysql_client_server_protocol.h" @@ -432,4 +434,76 @@ int gw_send_authentication_to_backend(char *dbname, char *user, uint8_t *passwd, else return 0; } + +/** + * Only backend connect syscall + */ +int gw_do_connect_to_backend(char *host, int port, MySQLProtocol *conn) { + struct sockaddr_in serv_addr; + int rv; + int so = 0; + + memset(&serv_addr, 0, sizeof serv_addr); + serv_addr.sin_family = AF_INET; + + so = socket(AF_INET,SOCK_STREAM,0); + + conn->fd = so; + + if (so < 0) { + fprintf(stderr, "Errore creazione socket: [%s] %i\n", strerror(errno), errno); + return -1; + } + + setipaddress(&serv_addr.sin_addr, host); + serv_addr.sin_port = htons(port); + + setnonblocking(so); + + if ((rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) < 0) { + fprintf(stderr, "Errore connect %i, %s: RV = [%i]\n", errno, strerror(errno), rv); + + if (errno == EINPROGRESS) { + return 1; + } else { + close(so); + return -1; + } + } + + return 0; +} + +/** + * Return a string representation of a MySQL protocol state. + * + * @param state The protocol state + * @return String representation of the state + * + */ +const char * +gw_mysql_protocol_state2string (int state) { + switch(state) { + case MYSQL_ALLOC: + return "MySQL Protocl struct allocated"; + case MYSQL_PENDING_CONNECT: + return "MySQL Backend socket PENDING connect"; + case MYSQL_CONNECTED: + return "MySQL Backend socket CONNECTED"; + case MYSQL_AUTH_SENT: + return "MySQL Authentication handshake has been sent"; + case MYSQL_AUTH_RECV: + return "MySQL Received user, password, db and capabilities"; + case MYSQL_AUTH_FAILED: + return "MySQL Authentication failed"; + case MYSQL_IDLE: + return "MySQL Auth done. Protocol is idle, waiting for statements"; + case MYSQL_ROUTING: + return "MySQL received command has been routed to backend(s)"; + case MYSQL_WAITING_RESULT: + return "MySQL Waiting for result set"; + default: + return "MySQL (unknown protocol state)"; + } +} ///// From e8ee2a3e9b44e567776b0681ab51e72aa1143315 Mon Sep 17 00:00:00 2001 From: Massimiliano Pinto Date: Thu, 4 Jul 2013 15:19:52 +0200 Subject: [PATCH 08/13] Removed useless code from mysql_backend.c and mysql_common.c --- modules/protocol/mysql_backend.c | 45 ++++++++------------------------ modules/protocol/mysql_common.c | 12 ++++++--- 2 files changed, 19 insertions(+), 38 deletions(-) diff --git a/modules/protocol/mysql_backend.c b/modules/protocol/mysql_backend.c index e9995671a..8c276779b 100644 --- a/modules/protocol/mysql_backend.c +++ b/modules/protocol/mysql_backend.c @@ -115,24 +115,16 @@ static int gw_read_backend_event(DCB *dcb) { MySQLProtocol *backend_protocol = NULL; MYSQL_session *current_session = NULL; - if (dcb) { - if(dcb->session) { - client_protocol = SESSION_PROTOCOL(dcb->session, MySQLProtocol); - } - - backend_protocol = (MySQLProtocol *) dcb->protocol; + if(dcb->session) { + client_protocol = SESSION_PROTOCOL(dcb->session, MySQLProtocol); } + backend_protocol = (MySQLProtocol *) dcb->protocol; current_session = (MYSQL_session *)dcb->session->data; - // backend is not yet ready - if ( (backend_protocol->state == MYSQL_ALLOC) || (backend_protocol->state == MYSQL_PENDING_CONNECT)) { - fprintf(stderr, ">>>> The backend %i is not ready\n", dcb->fd); + //fprintf(stderr, ">>> backend EPOLLIN from %i, protocol state [%s]\n", dcb->fd, gw_mysql_protocol_state2string(backend_protocol->state)); - return 0; - } - - // backend is conected: read server handshake and write auth request and return + // backend is connected: read server handshake and write auth request and return if (backend_protocol->state == MYSQL_CONNECTED) { gw_read_backend_handshake(backend_protocol); @@ -150,8 +142,7 @@ static int gw_read_backend_event(DCB *dcb) { case MYSQL_FAILED_AUTHENTICATION: backend_protocol->state = MYSQL_AUTH_FAILED; - fprintf(stderr, ">>>> BACKEND EPOLLIN %i , AUTH FAILED %i\n", dcb->fd, backend_protocol->state); - + // this will close the opened backend socket dcb_close(dcb); return 1; @@ -161,11 +152,8 @@ static int gw_read_backend_event(DCB *dcb) { backend_protocol->state = MYSQL_IDLE; - fprintf(stderr, ">>>> BACKEND EPOLLIN %i , auth is OK, %i\n", dcb->fd, backend_protocol->state); - // check the delay queue if(dcb->delayq) { - fprintf(stderr, ">>> Mysql Backend is ok, Force writing to the backend from delay queue. Backend Proto state is %i, Client Proto state is %i. Writing %i bytes\n", backend_protocol->state, client_protocol->state, gwbuf_length(dcb->delayq)); backend_write_delayqueue(dcb); spinlock_release(&dcb->authlock); return 1; @@ -206,7 +194,7 @@ static int gw_read_backend_event(DCB *dcb) { static int gw_write_backend_event(DCB *dcb) { MySQLProtocol *backend_protocol = dcb->protocol; - //fprintf(stderr, ">>>> Backend %i, protocol state [%s]\n", backend_protocol->fd, gw_mysql_protocol_state2string(backend_protocol->state)); + //fprintf(stderr, ">>> backend EPOLLOUT %i, protocol state [%s]\n", backend_protocol->fd, gw_mysql_protocol_state2string(backend_protocol->state)); // spinlock_acquire(&dcb->connectlock); @@ -261,18 +249,7 @@ static int gw_error_backend_event(DCB *dcb) { fprintf(stderr, "#### Handle Backend error function for %i\n", dcb->fd); - if (dcb->state != DCB_STATE_LISTENING) { - if (poll_remove_dcb(dcb) == -1) { - fprintf(stderr, "Backend poll_remove_dcb: from events check failed to delete %i, [%i]:[%s]\n", dcb->fd, errno, strerror(errno)); - } - - if (dcb->fd) { - dcb->state = DCB_STATE_DISCONNECTED; - fprintf(stderr, "Freeing backend MySQL conn %p, %p\n", dcb->protocol, &dcb->protocol); - gw_mysql_close((MySQLProtocol **)&dcb->protocol); - fprintf(stderr, "Freeing backend MySQL conn %p, %p\n", dcb->protocol, &dcb->protocol); - } - } + dcb_close(dcb); return 1; } @@ -320,19 +297,19 @@ static int gw_create_backend_connection(DCB *backend, SERVER *server, SESSION *s switch (rv) { case 0: - fprintf(stderr, "Connected to backend mysql server. fd is %i\n", backend->fd); + fprintf(stderr, "Connected to backend mysql server: fd is %i\n", backend->fd); protocol->state = MYSQL_CONNECTED; break; case 1: - fprintf(stderr, "Connection is PENDING to backend mysql server. fd is %i\n", backend->fd); + fprintf(stderr, ">>> Connection is PENDING to backend mysql server: fd is %i\n", backend->fd); protocol->state = MYSQL_PENDING_CONNECT; break; default: - fprintf(stderr, "<<<< NOT Connected to backend mysql server!!!\n"); + fprintf(stderr, ">>> ERROR: NOT Connected to the backend mysql server!!!\n"); backend->fd = -1; break; diff --git a/modules/protocol/mysql_common.c b/modules/protocol/mysql_common.c index b522d24fa..954a0fc28 100644 --- a/modules/protocol/mysql_common.c +++ b/modules/protocol/mysql_common.c @@ -451,7 +451,8 @@ int gw_do_connect_to_backend(char *host, int port, MySQLProtocol *conn) { conn->fd = so; if (so < 0) { - fprintf(stderr, "Errore creazione socket: [%s] %i\n", strerror(errno), errno); + fprintf(stderr, "Error creating backend socket: [%s] %i\n", strerror(errno), errno); + // this is an error return -1; } @@ -461,16 +462,19 @@ int gw_do_connect_to_backend(char *host, int port, MySQLProtocol *conn) { setnonblocking(so); if ((rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) < 0) { - fprintf(stderr, "Errore connect %i, %s: RV = [%i]\n", errno, strerror(errno), rv); - + // If connection is not yet completed just return 1 if (errno == EINPROGRESS) { + fprintf(stderr, ">>> Connection not yet completed for backend server [%s:%i]: errno %i, %s: RV = [%i]\n", host, port, errno, strerror(errno), rv); return 1; } else { - close(so); + // this is a real error + fprintf(stderr, ">>> ERROR connecting to backend server [%s:%i]: errno %i, %s: RV = [%i]\n", host, port, errno, strerror(errno), rv); return -1; } } + // connection succesfully completed + return 0; } From 740a001d019e2c29e0e164692bcd5becdb4b655b Mon Sep 17 00:00:00 2001 From: Massimiliano Pinto Date: Fri, 5 Jul 2013 09:38:52 +0200 Subject: [PATCH 09/13] moved mysql_send_custom_error to mysql_common.c The failed backend auth now forces the closeSession and also sets thre router session = NULL This will be changed introducing another state in the session struct --- modules/protocol/mysql_backend.c | 54 ++++++++++++++++---- modules/protocol/mysql_client.c | 85 +++++++++++++++---------------- modules/protocol/mysql_common.c | 86 ++++++++++++++++++++++++++++++-- 3 files changed, 164 insertions(+), 61 deletions(-) diff --git a/modules/protocol/mysql_backend.c b/modules/protocol/mysql_backend.c index 8c276779b..252985913 100644 --- a/modules/protocol/mysql_backend.c +++ b/modules/protocol/mysql_backend.c @@ -122,7 +122,7 @@ static int gw_read_backend_event(DCB *dcb) { backend_protocol = (MySQLProtocol *) dcb->protocol; current_session = (MYSQL_session *)dcb->session->data; - //fprintf(stderr, ">>> backend EPOLLIN from %i, protocol state [%s]\n", dcb->fd, gw_mysql_protocol_state2string(backend_protocol->state)); + fprintf(stderr, ">>> backend EPOLLIN from %i, protocol state [%s]\n", dcb->fd, gw_mysql_protocol_state2string(backend_protocol->state)); // backend is connected: read server handshake and write auth request and return if (backend_protocol->state == MYSQL_CONNECTED) { @@ -135,15 +135,36 @@ static int gw_read_backend_event(DCB *dcb) { // ready to check the authentication reply if (backend_protocol->state == MYSQL_AUTH_RECV) { + ROUTER_OBJECT *router = NULL; + ROUTER *router_instance = NULL; + void *rsession = NULL; int rv = -1; + SESSION *session = dcb->session; + + if (session) { + router = session->service->router; + router_instance = session->service->router_instance; + rsession = session->router_session; + } + rv = gw_receive_backend_auth(backend_protocol); switch (rv) { case MYSQL_FAILED_AUTHENTICATION: + fprintf(stderr, ">>>> Backend Auth failed for %i\n", dcb->fd); + backend_protocol->state = MYSQL_AUTH_FAILED; - // this will close the opened backend socket - dcb_close(dcb); + /* send an error to the client */ + mysql_send_custom_error(dcb->session->client, 1, 0, "Connection to backend lost right now"); + + /* close the active session */ + router->closeSession(router_instance, rsession); + + /* force the router_session to NULL + * Later we will implement a proper status for the session + */ + session->router_session = NULL; return 1; @@ -152,7 +173,7 @@ static int gw_read_backend_event(DCB *dcb) { backend_protocol->state = MYSQL_IDLE; - // check the delay queue + /* check the delay queue and flush the data */ if(dcb->delayq) { backend_write_delayqueue(dcb); spinlock_release(&dcb->authlock); @@ -163,20 +184,20 @@ static int gw_read_backend_event(DCB *dcb) { return 1; default: - // no other authentication state here right now, so just return + /* no other authentication state here right now, so just return */ return 0; } } - // reading MySQL command output from backend and writing to the client + /* reading MySQL command output from backend and writing to the client */ if ((client_protocol->state == MYSQL_WAITING_RESULT) || (client_protocol->state == MYSQL_IDLE)) { GWBUF *head = NULL; - // read data + /* read available backend data */ dcb_read(dcb, &head); - // write the gwbuffer to client + /* and write the gwbuffer to client */ dcb->session->client->func.write(dcb->session->client, head); return 1; @@ -194,7 +215,12 @@ static int gw_read_backend_event(DCB *dcb) { static int gw_write_backend_event(DCB *dcb) { MySQLProtocol *backend_protocol = dcb->protocol; - //fprintf(stderr, ">>> backend EPOLLOUT %i, protocol state [%s]\n", backend_protocol->fd, gw_mysql_protocol_state2string(backend_protocol->state)); + fprintf(stderr, ">>> backend EPOLLOUT %i, protocol state [%s]\n", backend_protocol->fd, gw_mysql_protocol_state2string(backend_protocol->state)); + + if (backend_protocol->state == MYSQL_AUTH_FAILED) { + fprintf(stderr, ">>> Backend epollout auth failed, EXIT\n"); + return 0; + } // spinlock_acquire(&dcb->connectlock); @@ -224,11 +250,17 @@ gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) { MySQLProtocol *backend_protocol = dcb->protocol; + if (backend_protocol->state == MYSQL_AUTH_FAILED) { + fprintf(stderr, ">>> backend %i auth failed, EXIT\n", dcb->fd); + dcb_close(dcb); + return 0; + } + spinlock_acquire(&dcb->authlock); // put incoming data to the delay queue unless backend is connected with auth ok if (backend_protocol->state != MYSQL_IDLE) { - fprintf(stderr, ">>> Writing in the backend %i delay queue\n", dcb->fd); + //fprintf(stderr, ">>> Writing in the backend %i delay queue\n", dcb->fd); backend_set_delayqueue(dcb, queue); spinlock_release(&dcb->authlock); @@ -315,7 +347,7 @@ static int gw_create_backend_connection(DCB *backend, SERVER *server, SESSION *s break; } - fprintf(stderr, "--> Backend conn added [%i], in the client session [%i]\n", backend->fd, session->client->fd); + fprintf(stderr, ">>> Backend [%s:%i] added [%i], in the client session [%i]\n", server->name, server->port, backend->fd, session->client->fd); backend->state = DCB_STATE_POLLING; diff --git a/modules/protocol/mysql_client.c b/modules/protocol/mysql_client.c index b3da2b733..568ffffdb 100644 --- a/modules/protocol/mysql_client.c +++ b/modules/protocol/mysql_client.c @@ -180,7 +180,7 @@ mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mys } /** - * mysql_send_auth_error + * mysql_send_custom_error * * Send a MySQL protocol Generic ERR message, to the dcb * Note the errno and state are still fixed now @@ -542,8 +542,6 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { fprintf(stderr, "<<< Client is NOT connected with db\n"); } - fprintf(stderr, "HERE auth token len is %i\n", auth_token_len); - // allocate memory for token only if auth_token_len > 0 if (auth_token_len) { auth_token = (uint8_t *)malloc(auth_token_len); @@ -558,10 +556,8 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) { if (auth_token) free(auth_token); - if (auth_ret == 0) { - fprintf(stderr, "<<< CLIENT AUTH is OK\n"); - } else { - fprintf(stderr, "<<< CLIENT AUTH FAILED\n"); + if (auth_ret != 0) { + fprintf(stderr, "<<< CLIENT AUTH FAILEDi for user [%s]\n", username); } return auth_ret; @@ -586,7 +582,6 @@ static int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_pas fprintf(stderr, ">>> MYSQL user NOT FOUND: %s\n", username); return 1; } - fprintf(stderr, ">>> MYSQL user FOUND !!!!: [%s]:[%s]\n", username, user_password); // convert hex data (40 bytes) to binary (20 bytes) // gateway_password represents the SHA1(SHA1(real_password)) @@ -618,16 +613,14 @@ static int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int t if (ret_val) { fprintf(stderr, "<<<< User [%s] was not found\n", username); return 1; - } else { - fprintf(stderr, "<<<< User [%s] OK\n", username); } if (token && token_len) { - fprintf(stderr, ">>> continue with auth\n"); // convert in hex format: this is the content of mysql.user table, field password without the '*' prefix - // an it is 40 bytes long + // 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 if (!strlen((char *)password)) { fprintf(stderr, ">>> continue WITHOUT auth, no password\n"); return 0; @@ -673,14 +666,12 @@ static int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int t gw_sha1_str(step2, SHA_DIGEST_LENGTH, check_hash); - fprintf(stderr, "<<<< Client_SHA1 [%20s]\n", stage1_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); + fprintf(stderr, "The CLIENT hex(SHA1(SHA1(password))) for \"%s\" is [%s]", username, inpass); } #endif @@ -865,6 +856,8 @@ int gw_read_client_event(DCB* dcb) { int ret = -1; session = dcb->session; + + // get the backend session, if available if (session) { router = session->service->router; router_instance = session->service->router_instance; @@ -877,65 +870,66 @@ int gw_read_client_event(DCB* dcb) { if ((ret = gw_read_gwbuff(dcb, &gw_buffer, b)) != 0) return ret; - // Now assuming in the first buffer there is the information form mysql command + /* Now, we are assuming in the first buffer there is the information form mysql command */ - // following code is only for debug now queue = gw_buffer; len = GWBUF_LENGTH(queue); ptr_buff = GWBUF_DATA(queue); - // get mysql commang + /* get mysql commang at fourth byte */ if (ptr_buff) mysql_command = ptr_buff[4]; if (mysql_command == '\x03') { - /// this is a query !!!! - //fprintf(stderr, "<<< MySQL Query from Client %i bytes: [%s]\n", len, ptr_buff+5); - //else - //fprintf(stderr, "<<< Reading from Client %i bytes: [%s]\n", len, ptr_buff); + /// this is a standard MySQL query !!!! } - + /** + * Routing Client input to Backend + */ + + /* Do not route the query without session! */ if(!rsession) { - if (mysql_command == '\x01') { - fprintf(stderr, "COM_QUIT received with no connected backends\n"); + /* COM_QUIT handling */ + fprintf(stderr, "COM_QUIT received with no connected backends from %i\n", dcb->fd); (dcb->func).close(dcb); + return 1; + } else { + /* Send a custom error as MySQL command reply */ + mysql_send_custom_error(dcb, 1, 0, "Connection to backend lost"); + + protocol->state = MYSQL_IDLE; + return 1; } - mysql_send_custom_error(dcb, 1, 0, "Connection to backend lost"); - protocol->state = MYSQL_IDLE; - - break; } - - /////////////////////////// - // Handling the COM_QUIT - ////////////////////////// - if (mysql_command == '\x01') { - fprintf(stderr, "COM_QUIT received\n"); - // uncomment the following lines for closing - // client and backend conns - // dcb still to be freed + + /* We can route the query */ - // this will propagate COM_QUIT to backends + /* COM_QUIT handling */ + if (mysql_command == '\x01') { + fprintf(stderr, "COM_QUIT received for %i\n", dcb->fd); + + /* this will propagate COM_QUIT to backend(s) */ + fprintf(stderr, "<<< Routing the COM_QUIT ...\n"); router->routeQuery(router_instance, rsession, queue); - // close client + + /* close client connection */ (dcb->func).close(dcb); - // call errors, it will be removed after tests - //(dcb->func).error(dcb); return 1; } + /* MySQL Command Routing */ + protocol->state = MYSQL_ROUTING; - /////////////////////////////////////// - // writing in the backend buffer queue, via routeQuery - /////////////////////////////////////// + /* writing in the backend buffer queue, via routeQuery */ + fprintf(stderr, "<<< Routing the Query ...\n"); router->routeQuery(router_instance, rsession, queue); protocol->state = MYSQL_WAITING_RESULT; @@ -1085,7 +1079,6 @@ int gw_MySQLAccept(DCB *listener) { if (c_sock == -1) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { - fprintf(stderr, ">>>> NO MORE conns for MySQL Listener: errno is %i for %i\n", errno, listener->fd); /* We have processed all incoming connections. */ break; } else { diff --git a/modules/protocol/mysql_common.c b/modules/protocol/mysql_common.c index 954a0fc28..06b2bdca1 100644 --- a/modules/protocol/mysql_common.c +++ b/modules/protocol/mysql_common.c @@ -22,9 +22,7 @@ * Revision History * Date Who Description * 17/06/2013 Massimiliano Pinto Common MySQL protocol routines - * 02/07/2013 Massimiliano Pinto MySQL connect asynchronous phases - * 04/07/2013 Massimiliano Pinto MySQL connect routine supports EAGAIN - * Added gw_mysql_protocol_state2string for printing MySQL the protocol status + * 02/06/2013 Massimiliano Pinto MySQL connect asynchronous phases */ #include "mysql_client_server_protocol.h" @@ -464,7 +462,7 @@ int gw_do_connect_to_backend(char *host, int port, MySQLProtocol *conn) { if ((rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) < 0) { // If connection is not yet completed just return 1 if (errno == EINPROGRESS) { - fprintf(stderr, ">>> Connection not yet completed for backend server [%s:%i]: errno %i, %s: RV = [%i]\n", host, port, errno, strerror(errno), rv); + fprintf(stderr, ">>> Connection is not yet completed for backend server [%s:%i]: errno %i, %s: RV = [%i]\n", host, port, errno, strerror(errno), rv); return 1; } else { // this is a real error @@ -510,4 +508,84 @@ gw_mysql_protocol_state2string (int state) { return "MySQL (unknown protocol state)"; } } + +/** + * mysql_send_custom_error + * + * Send a MySQL protocol Generic ERR message, to the dcb + * Note the errno and state are still fixed now + * + * @param dcb Descriptor Control Block for the connection to which the OK is sent + * @param packet_number + * @param in_affected_rows + * @param mysql_message + * @return packet length + * + */ +int +gw_backend_send_custom_error (DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message) { + uint8_t *outbuf = NULL; + uint8_t mysql_payload_size = 0; + uint8_t mysql_packet_header[4]; + uint8_t *mysql_payload = NULL; + uint8_t field_count = 0; + uint8_t mysql_err[2]; + uint8_t mysql_statemsg[6]; + unsigned int mysql_errno = 0; + const char *mysql_error_msg = NULL; + const char *mysql_state = NULL; + + GWBUF *buf; + + mysql_errno = 2003; + mysql_error_msg = "An errorr occurred ..."; + mysql_state = "HY000"; + + field_count = 0xff; + gw_mysql_set_byte2(mysql_err, mysql_errno); + mysql_statemsg[0]='#'; + memcpy(mysql_statemsg+1, mysql_state, 5); + + if (mysql_message != NULL) { + mysql_error_msg = mysql_message; + } + + mysql_payload_size = sizeof(field_count) + sizeof(mysql_err) + sizeof(mysql_statemsg) + strlen(mysql_error_msg); + + // allocate memory for packet header + payload + if ((buf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size)) == NULL) + { + return 0; + } + outbuf = GWBUF_DATA(buf); + + // write packet header with 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 err messg + memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg)); + + // writing data in the Client buffer queue + dcb->func.write(dcb, buf); + + return sizeof(mysql_packet_header) + mysql_payload_size; +} ///// From 9ab35a33488b27582318638e0bbbf13d510b2086 Mon Sep 17 00:00:00 2001 From: Massimiliano Pinto Date: Fri, 5 Jul 2013 09:51:13 +0200 Subject: [PATCH 10/13] Added mysql_send_custom_error, fixing the compile warning --- modules/include/mysql_client_server_protocol.h | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/include/mysql_client_server_protocol.h b/modules/include/mysql_client_server_protocol.h index 7a606ed98..fbd8f28b6 100644 --- a/modules/include/mysql_client_server_protocol.h +++ b/modules/include/mysql_client_server_protocol.h @@ -219,6 +219,7 @@ int gw_read_backend_handshake(MySQLProtocol *conn); int gw_send_authentication_to_backend(char *dbname, char *user, uint8_t *passwd, MySQLProtocol *conn); const char *gw_mysql_protocol_state2string(int state); int gw_do_connect_to_backend(char *host, int port, MySQLProtocol *conn); +int mysql_send_custom_error (DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message); extern void gw_sha1_str(const uint8_t *in, int in_len, uint8_t *out); extern void gw_sha1_2_str(const uint8_t *in, int in_len, const uint8_t *in2, int in2_len, uint8_t *out); From 5e06ba76ddfb3ce1d030f0158f3d156da0be8d5c Mon Sep 17 00:00:00 2001 From: Massimiliano Pinto Date: Fri, 5 Jul 2013 09:59:54 +0200 Subject: [PATCH 11/13] Fixed function name mysql_send_custom_error in mysql_common.c and removed from mysql_client.c. The revision 150 is then not working --- modules/protocol/mysql_client.c | 81 --------------------------------- modules/protocol/mysql_common.c | 2 +- 2 files changed, 1 insertion(+), 82 deletions(-) diff --git a/modules/protocol/mysql_client.c b/modules/protocol/mysql_client.c index 568ffffdb..d82fe22ae 100644 --- a/modules/protocol/mysql_client.c +++ b/modules/protocol/mysql_client.c @@ -179,87 +179,6 @@ mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mys return sizeof(mysql_packet_header) + mysql_payload_size; } -/** - * mysql_send_custom_error - * - * Send a MySQL protocol Generic ERR message, to the dcb - * Note the errno and state are still fixed now - * - * @param dcb Descriptor Control Block for the connection to which the OK is sent - * @param packet_number - * @param in_affected_rows - * @param mysql_message - * @return packet length - * - */ - -int -mysql_send_custom_error (DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message) { - uint8_t *outbuf = NULL; - uint8_t mysql_payload_size = 0; - uint8_t mysql_packet_header[4]; - uint8_t *mysql_payload = NULL; - uint8_t field_count = 0; - uint8_t mysql_err[2]; - uint8_t mysql_statemsg[6]; - unsigned int mysql_errno = 0; - const char *mysql_error_msg = NULL; - const char *mysql_state = NULL; - - GWBUF *buf; - - mysql_errno = 2003; - mysql_error_msg = "An errorr occurred ..."; - mysql_state = "HY000"; - - field_count = 0xff; - gw_mysql_set_byte2(mysql_err, mysql_errno); - mysql_statemsg[0]='#'; - memcpy(mysql_statemsg+1, mysql_state, 5); - - if (mysql_message != NULL) { - mysql_error_msg = mysql_message; - } - - mysql_payload_size = sizeof(field_count) + sizeof(mysql_err) + sizeof(mysql_statemsg) + strlen(mysql_error_msg); - - // allocate memory for packet header + payload - if ((buf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size)) == NULL) - { - return 0; - } - outbuf = GWBUF_DATA(buf); - - // write packet header with 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 err messg - memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg)); - - // writing data in the Client buffer queue - dcb->func.write(dcb, buf); - - return sizeof(mysql_packet_header) + mysql_payload_size; -} - /** * mysql_send_auth_error * diff --git a/modules/protocol/mysql_common.c b/modules/protocol/mysql_common.c index 06b2bdca1..4651f26aa 100644 --- a/modules/protocol/mysql_common.c +++ b/modules/protocol/mysql_common.c @@ -523,7 +523,7 @@ gw_mysql_protocol_state2string (int state) { * */ int -gw_backend_send_custom_error (DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message) { +mysql_send_custom_error (DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message) { uint8_t *outbuf = NULL; uint8_t mysql_payload_size = 0; uint8_t mysql_packet_header[4]; From 29d260e6bf419c7fad97dee18d88aa8942db1965 Mon Sep 17 00:00:00 2001 From: Massimiliano Pinto Date: Fri, 5 Jul 2013 10:36:59 +0200 Subject: [PATCH 12/13] Removed fprintf in backend operations. Some fprinf still available for gateway incoming connection and backend selection --- core/gw_utils.c | 2 +- modules/protocol/mysql_backend.c | 60 ++++++++++++++------------------ modules/protocol/mysql_client.c | 11 +++--- modules/protocol/mysql_common.c | 12 ++++--- 4 files changed, 41 insertions(+), 44 deletions(-) diff --git a/core/gw_utils.c b/core/gw_utils.c index 39e0cb1c6..f9ab33b82 100644 --- a/core/gw_utils.c +++ b/core/gw_utils.c @@ -87,7 +87,7 @@ int gw_read_gwbuff(DCB *dcb, GWBUF **head, int b) { int n = -1; if (b <= 0) { - fprintf(stderr, "||| read_gwbuff called with 0 bytes for %i, closing\n", dcb->fd); + //fprintf(stderr, "||| read_gwbuff called with 0 bytes for %i, closing\n", dcb->fd); dcb->func.close(dcb); return 1; } diff --git a/modules/protocol/mysql_backend.c b/modules/protocol/mysql_backend.c index 252985913..dbb0df332 100644 --- a/modules/protocol/mysql_backend.c +++ b/modules/protocol/mysql_backend.c @@ -35,9 +35,10 @@ * 01/07/2013 Massimiliano Pinto Put Log Manager example code behind SS_DEBUG macros. * 03/07/2013 Massimiliano Pinto Added delayq for incoming data before mysql connection * 04/07/2013 Massimiliano Pinto Added asyncrhronous MySQL protocol connection to backend + * 05/07/2013 Massimiliano Pinto Added closeSession if backend auth fails */ -static char *version_str = "V1.0.0"; +static char *version_str = "V2.0.0"; int gw_mysql_connect(char *host, int port, char *dbname, char *user, uint8_t *passwd, MySQLProtocol *conn); static int gw_create_backend_connection(DCB *client_dcb, SERVER *server, SESSION *in_session); static int gw_read_backend_event(DCB* dcb); @@ -109,7 +110,6 @@ GetModuleObject() * @param dcb The backend Descriptor Control Block * @return 1 on operation, 0 for no action */ - static int gw_read_backend_event(DCB *dcb) { MySQLProtocol *client_protocol = NULL; MySQLProtocol *backend_protocol = NULL; @@ -122,9 +122,14 @@ static int gw_read_backend_event(DCB *dcb) { backend_protocol = (MySQLProtocol *) dcb->protocol; current_session = (MYSQL_session *)dcb->session->data; - fprintf(stderr, ">>> backend EPOLLIN from %i, protocol state [%s]\n", dcb->fd, gw_mysql_protocol_state2string(backend_protocol->state)); + //fprintf(stderr, ">>> backend EPOLLIN from %i, protocol state [%s]\n", dcb->fd, gw_mysql_protocol_state2string(backend_protocol->state)); - // backend is connected: read server handshake and write auth request and return + /* backend is connected: + * + * 1. read server handshake + * 2. and write auth request + * 3. and return + */ if (backend_protocol->state == MYSQL_CONNECTED) { gw_read_backend_handshake(backend_protocol); @@ -133,7 +138,8 @@ static int gw_read_backend_event(DCB *dcb) { return 1; } - // ready to check the authentication reply + /* ready to check the authentication reply from backend */ + if (backend_protocol->state == MYSQL_AUTH_RECV) { ROUTER_OBJECT *router = NULL; ROUTER *router_instance = NULL; @@ -147,11 +153,12 @@ static int gw_read_backend_event(DCB *dcb) { rsession = session->router_session; } + /* read backed auth reply */ rv = gw_receive_backend_auth(backend_protocol); switch (rv) { case MYSQL_FAILED_AUTHENTICATION: - fprintf(stderr, ">>>> Backend Auth failed for %i\n", dcb->fd); + fprintf(stderr, ">>>> Backend Auth failed for user [%s], fd %i\n", current_session->user, dcb->fd); backend_protocol->state = MYSQL_AUTH_FAILED; @@ -215,17 +222,11 @@ static int gw_read_backend_event(DCB *dcb) { static int gw_write_backend_event(DCB *dcb) { MySQLProtocol *backend_protocol = dcb->protocol; - fprintf(stderr, ">>> backend EPOLLOUT %i, protocol state [%s]\n", backend_protocol->fd, gw_mysql_protocol_state2string(backend_protocol->state)); - - if (backend_protocol->state == MYSQL_AUTH_FAILED) { - fprintf(stderr, ">>> Backend epollout auth failed, EXIT\n"); - return 0; - } + //fprintf(stderr, ">>> backend EPOLLOUT %i, protocol state [%s]\n", backend_protocol->fd, gw_mysql_protocol_state2string(backend_protocol->state)); // spinlock_acquire(&dcb->connectlock); if (backend_protocol->state == MYSQL_PENDING_CONNECT) { - //fprintf(stderr, ">>>> Now the backend %i is CONNECTED\n", backend_protocol->fd); backend_protocol->state = MYSQL_CONNECTED; // spinlock_release(&dcb->connectlock); @@ -250,15 +251,11 @@ gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) { MySQLProtocol *backend_protocol = dcb->protocol; - if (backend_protocol->state == MYSQL_AUTH_FAILED) { - fprintf(stderr, ">>> backend %i auth failed, EXIT\n", dcb->fd); - dcb_close(dcb); - return 0; - } - spinlock_acquire(&dcb->authlock); - // put incoming data to the delay queue unless backend is connected with auth ok + /** + * Now put the incoming data to the delay queue unless backend is connected with auth ok + */ if (backend_protocol->state != MYSQL_IDLE) { //fprintf(stderr, ">>> Writing in the backend %i delay queue\n", dcb->fd); @@ -269,7 +266,6 @@ gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) spinlock_release(&dcb->authlock); - // Normal flow of backend write; return dcb_write(dcb, queue); } @@ -279,7 +275,7 @@ gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) */ static int gw_error_backend_event(DCB *dcb) { - fprintf(stderr, "#### Handle Backend error function for %i\n", dcb->fd); + fprintf(stderr, ">>> Handle Backend error function for %i\n", dcb->fd); dcb_close(dcb); @@ -287,13 +283,9 @@ static int gw_error_backend_event(DCB *dcb) { } /* - * Create a new ackend connection. + * Create a new backend connection. * - * This routine will connect to a backend server - * - * - backend dcb allocation - * - MySQL session data fetch - * - backend connection using data in MySQL session + * This routine will connect to a backend server and it is called by dbc_connect in router->newSession * * @param backend The Backend DCB allocated from dcb_connect * @param server The selected server to connect to @@ -313,13 +305,15 @@ static int gw_create_backend_connection(DCB *backend, SERVER *server, SESSION *s backend->protocol = protocol; - // put the backend dcb in the protocol struct + /* put the backend dcb in the protocol struct */ protocol->descriptor = backend; s_data = (MYSQL_session *)session->client->data; - // let's try to connecte to a backend server, only connect sys call - // The socket descriptor is in Non Blocking status, this is set in the function + /** + * let's try to connect to a backend server, only connect sys call + * The socket descriptor is in Non Blocking status, this is set in the function + */ rv = gw_do_connect_to_backend(server->name, server->port, protocol); // we could also move later, this in to the gw_do_connect_to_backend using protocol->descriptor @@ -329,13 +323,13 @@ static int gw_create_backend_connection(DCB *backend, SERVER *server, SESSION *s switch (rv) { case 0: - fprintf(stderr, "Connected to backend mysql server: fd is %i\n", backend->fd); + //fprintf(stderr, "Connected to backend mysql server: fd is %i\n", backend->fd); protocol->state = MYSQL_CONNECTED; break; case 1: - fprintf(stderr, ">>> Connection is PENDING to backend mysql server: fd is %i\n", backend->fd); + //fprintf(stderr, ">>> Connection is PENDING to backend mysql server: fd is %i\n", backend->fd); protocol->state = MYSQL_PENDING_CONNECT; break; diff --git a/modules/protocol/mysql_client.c b/modules/protocol/mysql_client.c index d82fe22ae..e15761391 100644 --- a/modules/protocol/mysql_client.c +++ b/modules/protocol/mysql_client.c @@ -812,7 +812,7 @@ int gw_read_client_event(DCB* dcb) { if(!rsession) { if (mysql_command == '\x01') { /* COM_QUIT handling */ - fprintf(stderr, "COM_QUIT received with no connected backends from %i\n", dcb->fd); + //fprintf(stderr, "COM_QUIT received with no connected backends from %i\n", dcb->fd); (dcb->func).close(dcb); return 1; @@ -830,10 +830,10 @@ int gw_read_client_event(DCB* dcb) { /* COM_QUIT handling */ if (mysql_command == '\x01') { - fprintf(stderr, "COM_QUIT received for %i\n", dcb->fd); + //fprintf(stderr, "COM_QUIT received from %i and passed to backed\n", dcb->fd); /* this will propagate COM_QUIT to backend(s) */ - fprintf(stderr, "<<< Routing the COM_QUIT ...\n"); + //fprintf(stderr, "<<< Routing the COM_QUIT ...\n"); router->routeQuery(router_instance, rsession, queue); /* close client connection */ @@ -848,7 +848,7 @@ int gw_read_client_event(DCB* dcb) { /* writing in the backend buffer queue, via routeQuery */ - fprintf(stderr, "<<< Routing the Query ...\n"); + //fprintf(stderr, "<<< Routing the Query ...\n"); router->routeQuery(router_instance, rsession, queue); protocol->state = MYSQL_WAITING_RESULT; @@ -1057,7 +1057,8 @@ int gw_MySQLAccept(DCB *listener) { /* */ static int gw_error_client_event(DCB *dcb) { - fprintf(stderr, "#### Handle error function gw_error_client_event, for [%i] is [%s]\n", dcb->fd, gw_dcb_state2string(dcb->state)); + //fprintf(stderr, "#### Handle error function gw_error_client_event, for [%i] is [%s]\n", dcb->fd, gw_dcb_state2string(dcb->state)); + //dcb_close(dcb); return 1; } diff --git a/modules/protocol/mysql_common.c b/modules/protocol/mysql_common.c index 4651f26aa..92c7bcf4e 100644 --- a/modules/protocol/mysql_common.c +++ b/modules/protocol/mysql_common.c @@ -450,28 +450,30 @@ int gw_do_connect_to_backend(char *host, int port, MySQLProtocol *conn) { if (so < 0) { fprintf(stderr, "Error creating backend socket: [%s] %i\n", strerror(errno), errno); - // this is an error + /* this is an error */ return -1; } setipaddress(&serv_addr.sin_addr, host); serv_addr.sin_port = htons(port); + /* set NON BLOCKING here */ setnonblocking(so); if ((rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) < 0) { - // If connection is not yet completed just return 1 + /* If connection is not yet completed just return 1 */ if (errno == EINPROGRESS) { - fprintf(stderr, ">>> Connection is not yet completed for backend server [%s:%i]: errno %i, %s: RV = [%i]\n", host, port, errno, strerror(errno), rv); + //fprintf(stderr, ">>> Connection is not yet completed for backend server [%s:%i]: errno %i, %s: RV = [%i]\n", host, port, errno, strerror(errno), rv); + return 1; } else { - // this is a real error + /* this is a real error */ fprintf(stderr, ">>> ERROR connecting to backend server [%s:%i]: errno %i, %s: RV = [%i]\n", host, port, errno, strerror(errno), rv); return -1; } } - // connection succesfully completed + /* The connection succesfully completed now */ return 0; } From 37ba2738ba75bd9d0565c9416c30f0f78623c769 Mon Sep 17 00:00:00 2001 From: Massimiliano Pinto Date: Mon, 8 Jul 2013 10:14:55 +0200 Subject: [PATCH 13/13] First implementation of HTTPD module, without router. Makefile in ./modules/protocol not added for now --- modules/protocol/httpd.c | 429 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 429 insertions(+) create mode 100644 modules/protocol/httpd.c diff --git a/modules/protocol/httpd.c b/modules/protocol/httpd.c new file mode 100644 index 000000000..93e886594 --- /dev/null +++ b/modules/protocol/httpd.c @@ -0,0 +1,429 @@ +/* + * This file is distributed as part of the SkySQL Gateway. 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 SkySQL Ab 2013 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @file httpd.c - HTTP daemon protocol module + * + * The httpd protocol module is intended as a mechanism to allow connections + * into the gateway for the purpose of accessing information within + * the gateway with a REST interface + * databases. + * + * In the first instance it is intended to allow a debug connection to access + * internal data structures, however it may also be used to manage the + * configuration of the gateway via REST interface. + * + * @verbatim + * Revision History + * Date Who Description + * 08/07/2013 Massimiliano Pinto Initial version + * + * @endverbatim + */ + +#define ISspace(x) isspace((int)(x)) +#define HTTP_SERVER_STRING "Gateway(c) v.1.0.0\r\n" +static char *version_str = "V1.0.0"; + +static int httpd_read_event(DCB* dcb); +static int httpd_write_event(DCB *dcb); +static int httpd_write(DCB *dcb, GWBUF *queue); +static int httpd_error(DCB *dcb); +static int httpd_hangup(DCB *dcb); +static int httpd_accept(DCB *dcb); +static int httpd_close(DCB *dcb); +static int httpd_listen(DCB *dcb, char *config); +static int httpd_get_line(int sock, char *buf, int size); +static void httpd_send_headers(int client, const char *filename); + +/** + * The "module object" for the httpd protocol module. + */ +static GWPROTOCOL MyObject = { + httpd_read_event, /**< Read - EPOLLIN handler */ + httpd_write, /**< Write - data from gateway */ + httpd_write_event, /**< WriteReady - EPOLLOUT handler */ + httpd_error, /**< Error - EPOLLERR handler */ + httpd_hangup, /**< HangUp - EPOLLHUP handler */ + httpd_accept, /**< Accept */ + NULL, /**< Connect */ + httpd_close, /**< Close */ + httpd_listen /**< Create a listener */ + }; + +static void +httpd_command(DCB *, char *cmd); + +/** + * Implementation of the mandatory version entry point + * + * @return version string of the module + */ +char * +version() +{ + return version_str; +} + +/** + * The module initialisation routine, called when the module + * is first loaded. + */ +void +ModuleInit() +{ + fprintf(stderr, "Initialise HTTPD Protocol module.\n"); +} + +/** + * The module entry point routine. It is this routine that + * must populate the structure that is referred to as the + * "module object", this is a structure with the set of + * external entry points for this module. + * + * @return The module object + */ +GWPROTOCOL * +GetModuleObject() +{ + return &MyObject; +} + +/** + * Read event for EPOLLIN on the httpd protocol module. + * + * @param dcb The descriptor control block + * @return + */ +static int +httpd_read_event(DCB* dcb) +{ +int n = -1; +GWBUF *head = NULL; +SESSION *session = dcb->session; +ROUTER_OBJECT *router = session->service->router; +ROUTER *router_instance = session->service->router_instance; +void *rsession = session->router_session; + +int numchars = 1; +char buf[1024]; +char *query_string = NULL; +char *path_info = NULL; +char method[255]; +char url[255]; +char path[512]; +int cgi = 0; +size_t i, j; +GWBUF *buffer=NULL; + + dcb->state = DCB_STATE_PROCESSING; + + numchars = httpd_get_line(dcb->fd, buf, sizeof(buf)); + i = 0; j = 0; + while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) { + method[i] = buf[j]; + i++; j++; + } + method[i] = '\0'; + + if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) { + //httpd_unimplemented(dcb->fd); + return 0; + } + + if ((buffer = gwbuf_alloc(1024)) == NULL) { + //httpd_error(dcb->fd); + return 0; + } + + if (strcasecmp(method, "POST") == 0) + cgi = 1; + + i = 0; + while (ISspace(buf[j]) && (j < sizeof(buf))) + j++; + while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) { + url[i] = buf[j]; + i++; j++; + } + url[i] = '\0'; + + if (strcasecmp(method, "GET") == 0) { + query_string = url; + while ((*query_string != '?') && (*query_string != '\0')) + query_string++; + if (*query_string == '?') { + cgi = 1; + *query_string = '\0'; + query_string++; + } + } + + while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ + numchars = httpd_get_line(dcb->fd, buf, sizeof(buf)); + + httpd_send_headers(dcb->fd, NULL); + + strcpy(GWBUF_DATA(buffer), "Welcome to HTTPD Gateway (c)\n"); + + dcb->func.write(dcb, buffer); + + dcb_close(dcb); + + dcb->state = DCB_STATE_POLLING; + + return n; +} + +/** + * EPOLLOUT handler for the HTTPD protocol module. + * + * @param dcb The descriptor control block + * @return + */ +static int +httpd_write_event(DCB *dcb) +{ + return dcb_drain_writeq(dcb); +} + +/** + * Write routine for the HTTPD protocol module. + * + * Writes the content of the buffer queue to the socket + * observing the non-blocking principles of the gateway. + * + * @param dcb Descriptor Control Block for the socket + * @param queue Linked list of buffes to write + */ +static int +httpd_write(DCB *dcb, GWBUF *queue) +{ + return dcb_write(dcb, queue); +} + +/** + * Handler for the EPOLLERR event. + * + * @param dcb The descriptor control block + */ +static int +httpd_error(DCB *dcb) +{ + return 0; +} + +/** + * Handler for the EPOLLHUP event. + * + * @param dcb The descriptor control block + */ +static int +httpd_hangup(DCB *dcb) +{ + return 0; +} + +/** + * Handler for the EPOLLIN event when the DCB refers to the listening + * socket for the protocol. + * + * @param dcb The descriptor control block + */ +static int +httpd_accept(DCB *dcb) +{ +int n_connect = 0; + + while (1) + { + int so; + struct sockaddr_in addr; + socklen_t addrlen; + DCB *client; + + if ((so = accept(dcb->fd, (struct sockaddr *)&addr, &addrlen)) == -1) + return n_connect; + else + { + atomic_add(&dcb->stats.n_accepts, 1); + client = dcb_alloc(); + client->fd = so; + client->remote = strdup(inet_ntoa(addr.sin_addr)); + memcpy(&client->func, &MyObject, sizeof(GWPROTOCOL)); + client->session = session_alloc(dcb->session->service, client); + + client->state = DCB_STATE_IDLE; + + if (poll_add_dcb(client) == -1) + { + return n_connect; + } + n_connect++; + + client->state = DCB_STATE_POLLING; + } + } + return n_connect; +} + +/** + * The close handler for the descriptor. Called by the gateway to + * explicitly close a connection. + * + * @param dcb The descriptor control block + */ + +static int +httpd_close(DCB *dcb) +{ + dcb_close(dcb); + return 0; +} + +/** + * HTTTP daemon listener entry point + * + * @param listener The Listener DCB + * @param config Configuration (ip:port) + */ +static int +httpd_listen(DCB *listener, char *config) +{ +struct sockaddr_in addr; +char *port; +int one = 1; +short pnum; + + memcpy(&listener->func, &MyObject, sizeof(GWPROTOCOL)); + + port = strrchr(config, ':'); + if (port) + port++; + else + port = "6442"; + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + pnum = atoi(port); + addr.sin_port = htons(pnum); + + if ((listener->fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) + { + return 0; + } + + /* socket options */ + setsockopt(listener->fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)); + + /* set NONBLOCKING mode */ + setnonblocking(listener->fd); + + /* bind address and port */ + if (bind(listener->fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) + { + return 0; + } + + listener->state = DCB_STATE_LISTENING; + listen(listener->fd, SOMAXCONN); + + if (poll_add_dcb(listener) == -1) + { + return 0; + } + return 1; +} + +/** + * HTTPD command implementation + * + * Called for each command in the HTTP stream. + * + * Currently we do no command execution + * + * @param dcb The client DCB + * @param cmd The command stream + */ +static void +httpd_command(DCB *dcb, char *cmd) +{ +} + +static int httpd_get_line(int sock, char *buf, int size) { + int i = 0; + char c = '\0'; + int n; + + while ((i < size - 1) && (c != '\n')) + { + n = recv(sock, &c, 1, 0); + /* DEBUG printf("%02X\n", c); */ + if (n > 0) + { + if (c == '\r') + { + n = recv(sock, &c, 1, MSG_PEEK); + /* DEBUG printf("%02X\n", c); */ + if ((n > 0) && (c == '\n')) + recv(sock, &c, 1, 0); + else + c = '\n'; + } + buf[i] = c; + i++; + } + else + c = '\n'; + } + buf[i] = '\0'; + + return(i); +} + +static void httpd_send_headers(int client, const char *filename) +{ + char buf[1024]; + (void)filename; /* could use filename to determine file type */ + + strcpy(buf, "HTTP/1.0 200 OK\r\n"); + send(client, buf, strlen(buf), 0); + strcpy(buf, HTTP_SERVER_STRING); + send(client, buf, strlen(buf), 0); + sprintf(buf, "Content-Type: text/html\r\n"); + send(client, buf, strlen(buf), 0); + strcpy(buf, "\r\n"); + send(client, buf, strlen(buf), 0); +} +///