/* * 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 "mysql_client_server_protocol.h" #if defined(SS_DEBUG) #include #include #include #endif /* * MySQL Protocol module for handling the protocol between the gateway * and the backend MySQL database. * * Revision History * Date Who Description * 14/06/2013 Mark Riddoch Initial version * 17/06/2013 Massimiliano Pinto Added Gateway To Backends routines * 27/06/2013 Vilho Raatikka Added skygw_log_write command as an example * 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 * 05/07/2013 Massimiliano Pinto Added closeSession if backend auth fails */ 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); 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 */ gw_backend_hangup, /* HangUp - EPOLLHUP handler */ NULL, /* Accept */ gw_create_backend_connection, /* Connect */ gw_backend_close, /* Close */ NULL /* Listen */ }; /* * 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() { #if defined(SS_DEBUG) skygw_log_write(NULL, LOGFILE_MESSAGE, strdup("Initial MySQL Backend Protcol module.")); #endif fprintf(stderr, "Initial MySQL Backend Protcol 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; } /** * 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) { MySQLProtocol *client_protocol = NULL; MySQLProtocol *backend_protocol = NULL; MYSQL_session *current_session = NULL; if(dcb->session) { client_protocol = SESSION_PROTOCOL(dcb->session, MySQLProtocol); } 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)); /* 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); 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 from backend */ 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; } /* read backed auth reply */ rv = gw_receive_backend_auth(backend_protocol); switch (rv) { case MYSQL_FAILED_AUTHENTICATION: fprintf(stderr, ">>>> Backend Auth failed for user [%s], fd %i\n", current_session->user, dcb->fd); backend_protocol->state = MYSQL_AUTH_FAILED; /* 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; case MYSQL_SUCCESFUL_AUTHENTICATION: spinlock_acquire(&dcb->authlock); backend_protocol->state = MYSQL_IDLE; /* check the delay queue and flush the data */ if(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)) { GWBUF *head = NULL; /* read available backend data */ dcb_read(dcb, &head); /* and write the gwbuffer to client */ dcb->session->client->func.write(dcb->session->client, head); return 1; } return 0; } /* * EPOLLOUT handler for the MySQL Backend protocol module. * * @param dcb The descriptor control block * @return The number of bytes written */ 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)); // spinlock_acquire(&dcb->connectlock); if (backend_protocol->state == MYSQL_PENDING_CONNECT) { backend_protocol->state = MYSQL_CONNECTED; // spinlock_release(&dcb->connectlock); return 1; } // spinlock_release(&dcb->connectlock); return dcb_drain_writeq(dcb); } /* * Write function for backend DCB * * @param dcb The DCB of the backend * @param queue Queue of buffers to write * @return 0 on failure, 1 on success */ static int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue) { MySQLProtocol *backend_protocol = dcb->protocol; spinlock_acquire(&dcb->authlock); /** * 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); backend_set_delayqueue(dcb, queue); spinlock_release(&dcb->authlock); return 1; } spinlock_release(&dcb->authlock); 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); dcb_close(dcb); return 1; } /* * Create a new backend connection. * * 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 * @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 *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); protocol = (MySQLProtocol *) calloc(1, sizeof(MySQLProtocol)); protocol->state = MYSQL_ALLOC; backend->protocol = protocol; /* put the backend dcb in the protocol struct */ protocol->descriptor = backend; s_data = (MYSQL_session *)session->client->data; /** * 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 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, ">>> ERROR: NOT Connected to the backend mysql server!!!\n"); backend->fd = -1; break; } 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; 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) { dcb_close(dcb); return 1; } /** * This routine put into the delay queue the input queue * The input is what backend DCB is receiving * The routine is called from func.write() when mysql backend connection * is not yet complete buu there are inout data from client * * @param dcb The current backend DCB * @param queue Input data in the GWBUF struct */ static void backend_set_delayqueue(DCB *dcb, GWBUF *queue) { spinlock_acquire(&dcb->delayqlock); if (dcb->delayq) { /* Append data */ dcb->delayq = gwbuf_append(dcb->delayq, queue); } else { if (queue != NULL) { /* create the delay queue */ dcb->delayq = queue; } } spinlock_release(&dcb->delayqlock); } /** * This routine writes the delayq via dcb_write * The dcb->delayq contains data received from the client before * mysql backend authentication succeded * * @param dcb The current backend DCB * @return The dcb_write status */ static int backend_write_delayqueue(DCB *dcb) { GWBUF *localq = NULL; spinlock_acquire(&dcb->delayqlock); localq = dcb->delayq; dcb->delayq = NULL; spinlock_release(&dcb->delayqlock); return dcb_write(dcb, localq); } /////