Updated plain protocol.
This commit is contained in:
260
server/modules/include/plainprotocol.h
Normal file
260
server/modules/include/plainprotocol.h
Normal file
@ -0,0 +1,260 @@
|
||||
#ifndef _MYSQL_PROTOCOL_H
|
||||
#define _MYSQL_PROTOCOL_H
|
||||
/*
|
||||
* This file is distributed as part of the MariaDB Corporation MaxScale. It is free
|
||||
* software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation,
|
||||
* version 2.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc., 51
|
||||
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright MariaDB Corporation Ab 2013-2015
|
||||
*/
|
||||
|
||||
/*
|
||||
* Revision History
|
||||
*
|
||||
* Date Who Description
|
||||
* 24-03-2015 Markus Makela Initial implementation
|
||||
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <stdbool.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <service.h>
|
||||
#include <router.h>
|
||||
#include <poll.h>
|
||||
#include <users.h>
|
||||
#include <dbusers.h>
|
||||
#include <version.h>
|
||||
#include <housekeeper.h>
|
||||
|
||||
#define GW_MYSQL_VERSION "MaxScale " MAXSCALE_VERSION
|
||||
#define GW_MYSQL_LOOP_TIMEOUT 300000000
|
||||
#define GW_MYSQL_READ 0
|
||||
#define GW_MYSQL_WRITE 1
|
||||
#define MYSQL_HEADER_LEN 4L
|
||||
|
||||
#define GW_MYSQL_PROTOCOL_VERSION 10 // version is 10
|
||||
#define GW_MYSQL_HANDSHAKE_FILLER 0x00
|
||||
#define GW_MYSQL_SERVER_CAPABILITIES_BYTE1 0xff
|
||||
#define GW_MYSQL_SERVER_CAPABILITIES_BYTE2 0xf7
|
||||
#define GW_MYSQL_SERVER_LANGUAGE 0x08
|
||||
#define GW_MYSQL_MAX_PACKET_LEN 0xffffffL;
|
||||
#define GW_MYSQL_SCRAMBLE_SIZE 20
|
||||
#define GW_SCRAMBLE_LENGTH_323 8
|
||||
|
||||
#ifndef MYSQL_SCRAMBLE_LEN
|
||||
# define MYSQL_SCRAMBLE_LEN GW_MYSQL_SCRAMBLE_SIZE
|
||||
#endif
|
||||
|
||||
#define GW_NOINTR_CALL(A) do { errno = 0; A; } while (errno == EINTR)
|
||||
#define SMALL_CHUNK 1024
|
||||
#define MAX_CHUNK SMALL_CHUNK * 8 * 4
|
||||
#define ToHex(Y) (Y>='0'&&Y<='9'?Y-'0':Y-'A'+10)
|
||||
struct dcb;
|
||||
|
||||
/** Protocol packing macros. */
|
||||
#define gw_mysql_set_byte2(__buffer, __int) do { \
|
||||
(__buffer)[0]= (uint8_t)((__int) & 0xFF); \
|
||||
(__buffer)[1]= (uint8_t)(((__int) >> 8) & 0xFF); } while (0)
|
||||
#define gw_mysql_set_byte3(__buffer, __int) do { \
|
||||
(__buffer)[0]= (uint8_t)((__int) & 0xFF); \
|
||||
(__buffer)[1]= (uint8_t)(((__int) >> 8) & 0xFF); \
|
||||
(__buffer)[2]= (uint8_t)(((__int) >> 16) & 0xFF); } while (0)
|
||||
#define gw_mysql_set_byte4(__buffer, __int) do { \
|
||||
(__buffer)[0]= (uint8_t)((__int) & 0xFF); \
|
||||
(__buffer)[1]= (uint8_t)(((__int) >> 8) & 0xFF); \
|
||||
(__buffer)[2]= (uint8_t)(((__int) >> 16) & 0xFF); \
|
||||
(__buffer)[3]= (uint8_t)(((__int) >> 24) & 0xFF); } while (0)
|
||||
|
||||
/** Protocol unpacking macros. */
|
||||
#define gw_mysql_get_byte2(__buffer) \
|
||||
(uint16_t)((__buffer)[0] | \
|
||||
((__buffer)[1] << 8))
|
||||
#define gw_mysql_get_byte3(__buffer) \
|
||||
(uint32_t)((__buffer)[0] | \
|
||||
((__buffer)[1] << 8) | \
|
||||
((__buffer)[2] << 16))
|
||||
#define gw_mysql_get_byte4(__buffer) \
|
||||
(uint32_t)((__buffer)[0] | \
|
||||
((__buffer)[1] << 8) | \
|
||||
((__buffer)[2] << 16) | \
|
||||
((__buffer)[3] << 24))
|
||||
#define gw_mysql_get_byte8(__buffer) \
|
||||
((uint64_t)(__buffer)[0] | \
|
||||
((uint64_t)(__buffer)[1] << 8) | \
|
||||
((uint64_t)(__buffer)[2] << 16) | \
|
||||
((uint64_t)(__buffer)[3] << 24) | \
|
||||
((uint64_t)(__buffer)[4] << 32) | \
|
||||
((uint64_t)(__buffer)[5] << 40) | \
|
||||
((uint64_t)(__buffer)[6] << 48) | \
|
||||
((uint64_t)(__buffer)[7] << 56))
|
||||
|
||||
/** MySQL protocol constants */
|
||||
typedef enum
|
||||
{
|
||||
GW_MYSQL_CAPABILITIES_NONE= 0,
|
||||
GW_MYSQL_CAPABILITIES_LONG_PASSWORD= (1 << 0),
|
||||
GW_MYSQL_CAPABILITIES_FOUND_ROWS= (1 << 1),
|
||||
GW_MYSQL_CAPABILITIES_LONG_FLAG= (1 << 2),
|
||||
GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB= (1 << 3),
|
||||
GW_MYSQL_CAPABILITIES_NO_SCHEMA= (1 << 4),
|
||||
GW_MYSQL_CAPABILITIES_COMPRESS= (1 << 5),
|
||||
GW_MYSQL_CAPABILITIES_ODBC= (1 << 6),
|
||||
GW_MYSQL_CAPABILITIES_LOCAL_FILES= (1 << 7),
|
||||
GW_MYSQL_CAPABILITIES_IGNORE_SPACE= (1 << 8),
|
||||
GW_MYSQL_CAPABILITIES_PROTOCOL_41= (1 << 9),
|
||||
GW_MYSQL_CAPABILITIES_INTERACTIVE= (1 << 10),
|
||||
GW_MYSQL_CAPABILITIES_SSL= (1 << 11),
|
||||
GW_MYSQL_CAPABILITIES_IGNORE_SIGPIPE= (1 << 12),
|
||||
GW_MYSQL_CAPABILITIES_TRANSACTIONS= (1 << 13),
|
||||
GW_MYSQL_CAPABILITIES_RESERVED= (1 << 14),
|
||||
GW_MYSQL_CAPABILITIES_SECURE_CONNECTION= (1 << 15),
|
||||
GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS= (1 << 16),
|
||||
GW_MYSQL_CAPABILITIES_MULTI_RESULTS= (1 << 17),
|
||||
GW_MYSQL_CAPABILITIES_PS_MULTI_RESULTS= (1 << 18),
|
||||
GW_MYSQL_CAPABILITIES_PLUGIN_AUTH= (1 << 19),
|
||||
GW_MYSQL_CAPABILITIES_SSL_VERIFY_SERVER_CERT= (1 << 30),
|
||||
GW_MYSQL_CAPABILITIES_REMEMBER_OPTIONS= (1 << 31),
|
||||
GW_MYSQL_CAPABILITIES_CLIENT= (GW_MYSQL_CAPABILITIES_LONG_PASSWORD |
|
||||
GW_MYSQL_CAPABILITIES_FOUND_ROWS |
|
||||
GW_MYSQL_CAPABILITIES_LONG_FLAG |
|
||||
GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB |
|
||||
GW_MYSQL_CAPABILITIES_LOCAL_FILES |
|
||||
GW_MYSQL_CAPABILITIES_PLUGIN_AUTH |
|
||||
GW_MYSQL_CAPABILITIES_TRANSACTIONS |
|
||||
GW_MYSQL_CAPABILITIES_PROTOCOL_41 |
|
||||
GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS |
|
||||
GW_MYSQL_CAPABILITIES_MULTI_RESULTS |
|
||||
GW_MYSQL_CAPABILITIES_PS_MULTI_RESULTS |
|
||||
GW_MYSQL_CAPABILITIES_SECURE_CONNECTION),
|
||||
GW_MYSQL_CAPABILITIES_CLIENT_COMPRESS= (GW_MYSQL_CAPABILITIES_LONG_PASSWORD |
|
||||
GW_MYSQL_CAPABILITIES_FOUND_ROWS |
|
||||
GW_MYSQL_CAPABILITIES_LONG_FLAG |
|
||||
GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB |
|
||||
GW_MYSQL_CAPABILITIES_LOCAL_FILES |
|
||||
GW_MYSQL_CAPABILITIES_PLUGIN_AUTH |
|
||||
GW_MYSQL_CAPABILITIES_TRANSACTIONS |
|
||||
GW_MYSQL_CAPABILITIES_PROTOCOL_41 |
|
||||
GW_MYSQL_CAPABILITIES_MULTI_STATEMENTS |
|
||||
GW_MYSQL_CAPABILITIES_MULTI_RESULTS |
|
||||
GW_MYSQL_CAPABILITIES_PS_MULTI_RESULTS |
|
||||
GW_MYSQL_CAPABILITIES_COMPRESS
|
||||
),
|
||||
} gw_mysql_capabilities_t;
|
||||
|
||||
/** Copy from enum in mariadb-5.5 mysql_com.h */
|
||||
typedef enum mysql_server_cmd {
|
||||
MYSQL_COM_UNDEFINED = -1,
|
||||
MYSQL_COM_SLEEP = 0,
|
||||
MYSQL_COM_QUIT,
|
||||
MYSQL_COM_INIT_DB,
|
||||
MYSQL_COM_QUERY,
|
||||
MYSQL_COM_FIELD_LIST,
|
||||
MYSQL_COM_CREATE_DB,
|
||||
MYSQL_COM_DROP_DB,
|
||||
MYSQL_COM_REFRESH,
|
||||
MYSQL_COM_SHUTDOWN,
|
||||
MYSQL_COM_STATISTICS,
|
||||
MYSQL_COM_PROCESS_INFO,
|
||||
MYSQL_COM_CONNECT,
|
||||
MYSQL_COM_PROCESS_KILL,
|
||||
MYSQL_COM_DEBUG,
|
||||
MYSQL_COM_PING,
|
||||
MYSQL_COM_TIME,
|
||||
MYSQL_COM_DELAYED_INSERT,
|
||||
MYSQL_COM_CHANGE_USER,
|
||||
MYSQL_COM_BINLOG_DUMP,
|
||||
MYSQL_COM_TABLE_DUMP,
|
||||
MYSQL_COM_CONNECT_OUT,
|
||||
MYSQL_COM_REGISTER_SLAVE,
|
||||
MYSQL_COM_STMT_PREPARE,
|
||||
MYSQL_COM_STMT_EXECUTE,
|
||||
MYSQL_COM_STMT_SEND_LONG_DATA,
|
||||
MYSQL_COM_STMT_CLOSE,
|
||||
MYSQL_COM_STMT_RESET,
|
||||
MYSQL_COM_SET_OPTION,
|
||||
MYSQL_COM_STMT_FETCH,
|
||||
MYSQL_COM_DAEMON,
|
||||
MYSQL_COM_END /*< Must be the last */
|
||||
} mysql_server_cmd_t;
|
||||
|
||||
/**
|
||||
* MySQL Protocol specific state data.
|
||||
*
|
||||
* Protocol carries information from client side to backend side, such as
|
||||
* MySQL session command information and history of earlier session commands.
|
||||
*/
|
||||
typedef struct {
|
||||
#if defined(SS_DEBUG)
|
||||
skygw_chk_t protocol_chk_top;
|
||||
#endif
|
||||
int fd; /*< The socket descriptor */
|
||||
struct dcb *owner_dcb; /*< The DCB of the socket
|
||||
* we are running on */
|
||||
SPINLOCK protocol_lock;
|
||||
uint8_t scramble[MYSQL_SCRAMBLE_LEN]; /*< server scramble,
|
||||
* created or received */
|
||||
uint32_t server_capabilities; /*< server capabilities,
|
||||
* created or received */
|
||||
uint32_t client_capabilities; /*< client capabilities,
|
||||
* created or received */
|
||||
unsigned long tid; /*< MySQL Thread ID, in
|
||||
* handshake */
|
||||
unsigned int charset; /*< MySQL character set at connect time */
|
||||
#if defined(SS_DEBUG)
|
||||
skygw_chk_t protocol_chk_tail;
|
||||
#endif
|
||||
} PlainProtocol;
|
||||
|
||||
|
||||
|
||||
#define MYSQL_GET_COMMAND(payload) (payload[4])
|
||||
#define MYSQL_GET_PACKET_NO(payload) (payload[3])
|
||||
#define MYSQL_GET_PACKET_LEN(payload) (gw_mysql_get_byte3(payload))
|
||||
#define MYSQL_GET_ERRCODE(payload) (gw_mysql_get_byte2(&payload[5]))
|
||||
#define MYSQL_GET_STMTOK_NPARAM(payload) (gw_mysql_get_byte2(&payload[9]))
|
||||
#define MYSQL_GET_STMTOK_NATTR(payload) (gw_mysql_get_byte2(&payload[11]))
|
||||
#define MYSQL_IS_ERROR_PACKET(payload) (MYSQL_GET_COMMAND(payload)==0xff)
|
||||
#define MYSQL_IS_COM_QUIT(payload) (MYSQL_GET_COMMAND(payload)==0x01)
|
||||
#define MYSQL_IS_COM_INIT_DB(payload) (MYSQL_GET_COMMAND(payload)==0x02)
|
||||
#define MYSQL_IS_CHANGE_USER(payload) (MYSQL_GET_COMMAND(payload)==0x11)
|
||||
#define MYSQL_GET_NATTR(payload) ((int)payload[4])
|
||||
|
||||
#endif /** _MYSQL_PROTOCOL_H */
|
||||
|
||||
PlainProtocol* mysql_protocol_init(DCB* dcb, int fd);
|
||||
void mysql_protocol_done (DCB* dcb);
|
||||
PlainProtocol *gw_mysql_init(PlainProtocol *data);
|
||||
int gw_receive_backend_auth(PlainProtocol *protocol);
|
||||
int gw_decode_mysql_server_handshake(PlainProtocol *protocol, uint8_t *payload);
|
||||
int gw_read_backend_handshake(PlainProtocol *protocol);
|
||||
int gw_send_authentication_to_backend(
|
||||
char *dbname,
|
||||
char *user,
|
||||
uint8_t *passwd,
|
||||
PlainProtocol *protocol);
|
||||
|
||||
int plain_do_connect_to_backend(char *host, int port, int* fd);
|
@ -16,12 +16,14 @@
|
||||
* Copyright MariaDB Corporation Ab 2013-2014
|
||||
*/
|
||||
|
||||
#include "mysql_client_server_protocol.h"
|
||||
#include <plainprotocol.h>
|
||||
#include <skygw_types.h>
|
||||
#include <skygw_utils.h>
|
||||
#include <log_manager.h>
|
||||
#include <modutil.h>
|
||||
|
||||
#define PLAIN_BACKEND_SO_SNDBUF (128 * 1024)
|
||||
#define PLAIN_BACKEND_SO_RCVBUF (128 * 1024)
|
||||
/*
|
||||
* MySQL Protocol module for handling the protocol between the gateway
|
||||
* and the backend MySQL database.
|
||||
@ -51,7 +53,7 @@ MODULE_INFO info = {
|
||||
MODULE_API_PROTOCOL,
|
||||
MODULE_GA,
|
||||
GWPROTOCOL_VERSION,
|
||||
"The MySQL to backend server protocol"
|
||||
"The plain protocol"
|
||||
};
|
||||
|
||||
/** Defined in log_manager.cc */
|
||||
@ -60,38 +62,32 @@ extern size_t log_ses_count[];
|
||||
extern __thread log_info_t tls_log_info;
|
||||
|
||||
static char *version_str = "V2.0.0";
|
||||
static int gw_create_backend_connection(DCB *backend, 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 plain_create_backend_connection(DCB *backend, SERVER *server, SESSION *in_session);
|
||||
static int plain_read_backend_event(DCB* dcb);
|
||||
static int plain_write_ready_backend_event(DCB *dcb);
|
||||
static int plain_write_backend(DCB *dcb, GWBUF *queue);
|
||||
static int plain_error_backend_event(DCB *dcb);
|
||||
static int plain_backend_close(DCB *dcb);
|
||||
static int plain_backend_hangup(DCB *dcb);
|
||||
static int backend_write_delayqueue(DCB *dcb);
|
||||
static void backend_set_delayqueue(DCB *dcb, GWBUF *queue);
|
||||
static int gw_change_user(DCB *backend_dcb, SERVER *server, SESSION *in_session, GWBUF *queue);
|
||||
static int plain_change_user(DCB *backend_dcb, SERVER *server, SESSION *in_session, GWBUF *queue);
|
||||
static GWBUF* process_response_data (DCB* dcb, GWBUF* readbuf, int nbytes_to_process);
|
||||
extern char* create_auth_failed_msg( GWBUF* readbuf, char* hostaddr, uint8_t* sha1);
|
||||
extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db);
|
||||
static bool sescmd_response_complete(DCB* dcb);
|
||||
|
||||
|
||||
#if defined(NOT_USED)
|
||||
static int gw_session(DCB *backend_dcb, void *data);
|
||||
#endif
|
||||
static MYSQL_session* gw_get_shared_session_auth_info(DCB* dcb);
|
||||
|
||||
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 */
|
||||
plain_read_backend_event, /* Read - EPOLLIN handler */
|
||||
plain_write_backend, /* Write - data from gateway */
|
||||
plain_write_ready_backend_event, /* WriteReady - EPOLLOUT handler */
|
||||
plain_error_backend_event, /* Error - EPOLLERR handler */
|
||||
plain_backend_hangup, /* HangUp - EPOLLHUP handler */
|
||||
NULL, /* Accept */
|
||||
gw_create_backend_connection, /* Connect */
|
||||
gw_backend_close, /* Close */
|
||||
plain_create_backend_connection, /* Connect */
|
||||
plain_backend_close, /* Close */
|
||||
NULL, /* Listen */
|
||||
gw_change_user, /* Authentication */
|
||||
NULL, /* Authentication */
|
||||
NULL /* Session */
|
||||
};
|
||||
|
||||
@ -130,43 +126,63 @@ GetModuleObject()
|
||||
}
|
||||
|
||||
|
||||
static MYSQL_session* gw_get_shared_session_auth_info(
|
||||
DCB* dcb)
|
||||
/**
|
||||
* Creates MySQL protocol structure
|
||||
*
|
||||
* @param dcb * Must be non-NULL.
|
||||
* @param fd
|
||||
*
|
||||
* @return
|
||||
*
|
||||
*
|
||||
* @details Protocol structure does not have fd because dcb is not
|
||||
* connected yet.
|
||||
*
|
||||
*/
|
||||
PlainProtocol* plain_protocol_init(
|
||||
DCB* dcb,
|
||||
int fd)
|
||||
{
|
||||
MYSQL_session* auth_info = NULL;
|
||||
CHK_DCB(dcb);
|
||||
CHK_SESSION(dcb->session);
|
||||
PlainProtocol* p;
|
||||
|
||||
spinlock_acquire(&dcb->session->ses_lock);
|
||||
p = (PlainProtocol *) calloc(1, sizeof(PlainProtocol));
|
||||
ss_dassert(p != NULL);
|
||||
|
||||
if (dcb->session->state != SESSION_STATE_ALLOC) {
|
||||
auth_info = dcb->session->data;
|
||||
} else {
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"%lu [gw_get_shared_session_auth_info] Couldn't get "
|
||||
"session authentication info. Session in a wrong state %d.",
|
||||
pthread_self(),
|
||||
dcb->session->state)));
|
||||
if (p == NULL) {
|
||||
int eno = errno;
|
||||
errno = 0;
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"%lu [mysql_init_protocol] MySQL protocol init failed : "
|
||||
"memory allocation due error %d, %s.",
|
||||
pthread_self(),
|
||||
eno,
|
||||
strerror(eno))));
|
||||
goto return_p;
|
||||
}
|
||||
spinlock_release(&dcb->session->ses_lock);
|
||||
|
||||
return auth_info;
|
||||
/*< Assign fd with protocol */
|
||||
p->fd = fd;
|
||||
p->owner_dcb = dcb;
|
||||
|
||||
return_p:
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
static int plain_read_backend_event(DCB *dcb) {
|
||||
PlainProtocol *client_protocol = NULL;
|
||||
PlainProtocol *backend_protocol = NULL;
|
||||
|
||||
int rc = 0;
|
||||
|
||||
|
||||
backend_protocol = (MySQLProtocol *) dcb->protocol;
|
||||
backend_protocol = (PlainProtocol *) dcb->protocol;
|
||||
CHK_PROTOCOL(backend_protocol);
|
||||
|
||||
|
||||
@ -226,58 +242,12 @@ static int gw_read_backend_event(DCB *dcb) {
|
||||
ss_dassert(read_buffer != NULL || dcb->dcb_readqueue != NULL);
|
||||
}
|
||||
|
||||
/** Packet prefix was read earlier */
|
||||
if (dcb->dcb_readqueue)
|
||||
{
|
||||
if (read_buffer != NULL)
|
||||
{
|
||||
read_buffer = gwbuf_append(dcb->dcb_readqueue, read_buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
read_buffer = dcb->dcb_readqueue;
|
||||
}
|
||||
nbytes_read = gwbuf_length(read_buffer);
|
||||
|
||||
if (nbytes_read < 5) /*< read at least command type */
|
||||
{
|
||||
rc = 0;
|
||||
LOGIF(LD, (skygw_log_write_flush(
|
||||
LOGFILE_DEBUG,
|
||||
"%p [gw_read_backend_event] Read %d bytes "
|
||||
"from DCB %p, fd %d, session %s. "
|
||||
"Returning to poll wait.\n",
|
||||
pthread_self(),
|
||||
nbytes_read,
|
||||
dcb,
|
||||
dcb->fd,
|
||||
dcb->session)));
|
||||
goto return_rc;
|
||||
}
|
||||
/** There is at least length and command type. */
|
||||
else
|
||||
{
|
||||
dcb->dcb_readqueue = NULL;
|
||||
}
|
||||
}
|
||||
/** This may be either short prefix of a packet, or the tail of it. */
|
||||
else
|
||||
{
|
||||
if (nbytes_read < 5)
|
||||
{
|
||||
dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, read_buffer);
|
||||
rc = 0;
|
||||
goto return_rc;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (dcb->session->state == SESSION_STATE_ROUTER_READY &&
|
||||
dcb->session->client != NULL &&
|
||||
dcb->session->client->state == DCB_STATE_POLLING)
|
||||
{
|
||||
client_protocol = SESSION_PROTOCOL(dcb->session,
|
||||
MySQLProtocol);
|
||||
PlainProtocol);
|
||||
|
||||
{
|
||||
gwbuf_set_type(read_buffer, GWBUF_TYPE_MYSQL);
|
||||
@ -305,9 +275,9 @@ return_with_lock:
|
||||
* @param dcb The descriptor control block
|
||||
* @return 1 in success, 0 in case of failure,
|
||||
*/
|
||||
static int gw_write_backend_event(DCB *dcb) {
|
||||
static int plain_write_ready_backend_event(DCB *dcb) {
|
||||
int rc = 0;
|
||||
MySQLProtocol *backend_protocol = dcb->protocol;
|
||||
PlainProtocol *backend_protocol = dcb->protocol;
|
||||
|
||||
/*<
|
||||
* Don't write to backend if backend_dcb is not in poll set anymore.
|
||||
@ -343,6 +313,138 @@ return_rc:
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* plain_do_connect_to_backend
|
||||
*
|
||||
* This routine creates socket and connects to a backend server.
|
||||
* Connect it non-blocking operation. If connect fails, socket is closed.
|
||||
*
|
||||
* @param host The host to connect to
|
||||
* @param port The host TCP/IP port
|
||||
* @param *fd where connected fd is copied
|
||||
* @return 0/1 on success and -1 on failure
|
||||
* If successful, fd has file descriptor to socket which is connected to
|
||||
* backend server. In failure, fd == -1 and socket is closed.
|
||||
*
|
||||
*/
|
||||
int plain_do_connect_to_backend(
|
||||
char *host,
|
||||
int port,
|
||||
int *fd)
|
||||
{
|
||||
struct sockaddr_in serv_addr;
|
||||
int rv;
|
||||
int so = 0;
|
||||
int bufsize;
|
||||
|
||||
memset(&serv_addr, 0, sizeof serv_addr);
|
||||
serv_addr.sin_family = AF_INET;
|
||||
so = socket(AF_INET,SOCK_STREAM,0);
|
||||
|
||||
if (so < 0) {
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error: Establishing connection to backend server "
|
||||
"%s:%d failed.\n\t\t Socket creation failed "
|
||||
"due %d, %s.",
|
||||
host,
|
||||
port,
|
||||
errno,
|
||||
strerror(errno))));
|
||||
rv = -1;
|
||||
goto return_rv;
|
||||
}
|
||||
/* prepare for connect */
|
||||
setipaddress(&serv_addr.sin_addr, host);
|
||||
serv_addr.sin_port = htons(port);
|
||||
bufsize = PLAIN_BACKEND_SO_SNDBUF;
|
||||
|
||||
if(setsockopt(so, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)) != 0)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error: Failed to set socket options "
|
||||
"%s:%d failed.\n\t\t Socket configuration failed "
|
||||
"due %d, %s.",
|
||||
host,
|
||||
port,
|
||||
errno,
|
||||
strerror(errno))));
|
||||
rv = -1;
|
||||
/** Close socket */
|
||||
goto close_so;
|
||||
}
|
||||
bufsize = PLAIN_BACKEND_SO_RCVBUF;
|
||||
|
||||
if(setsockopt(so, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) != 0)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error: Failed to set socket options "
|
||||
"%s:%d failed.\n\t\t Socket configuration failed "
|
||||
"due %d, %s.",
|
||||
host,
|
||||
port,
|
||||
errno,
|
||||
strerror(errno))));
|
||||
rv = -1;
|
||||
/** Close socket */
|
||||
goto close_so;
|
||||
}
|
||||
|
||||
/* set socket to as non-blocking here */
|
||||
setnonblocking(so);
|
||||
rv = connect(so, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
|
||||
|
||||
if (rv != 0)
|
||||
{
|
||||
if (errno == EINPROGRESS)
|
||||
{
|
||||
rv = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error: Failed to connect backend server %s:%d, "
|
||||
"due %d, %s.",
|
||||
host,
|
||||
port,
|
||||
errno,
|
||||
strerror(errno))));
|
||||
/** Close socket */
|
||||
goto close_so;
|
||||
}
|
||||
}
|
||||
*fd = so;
|
||||
LOGIF(LD, (skygw_log_write_flush(
|
||||
LOGFILE_DEBUG,
|
||||
"%lu [plain_do_connect_to_backend] Connected to backend server "
|
||||
"%s:%d, fd %d.",
|
||||
pthread_self(),
|
||||
host,
|
||||
port,
|
||||
so)));
|
||||
|
||||
return_rv:
|
||||
return rv;
|
||||
|
||||
close_so:
|
||||
/*< Close newly created socket. */
|
||||
if (close(so) != 0)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error: Failed to "
|
||||
"close socket %d due %d, %s.",
|
||||
so,
|
||||
errno,
|
||||
strerror(errno))));
|
||||
}
|
||||
goto return_rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write function for backend DCB. Store command to protocol.
|
||||
*
|
||||
@ -351,15 +453,13 @@ return_rc:
|
||||
* @return 0 on failure, 1 on success
|
||||
*/
|
||||
static int
|
||||
gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue)
|
||||
plain_write_backend(DCB *dcb, GWBUF *queue)
|
||||
{
|
||||
MySQLProtocol *backend_protocol = dcb->protocol;
|
||||
PlainProtocol *backend_protocol = dcb->protocol;
|
||||
int rc = 0;
|
||||
|
||||
rc = dcb_write(dcb, queue);
|
||||
|
||||
|
||||
return_rc:
|
||||
return rc;
|
||||
}
|
||||
|
||||
@ -370,7 +470,7 @@ return_rc:
|
||||
* closed and call DCB close function which triggers closing router session
|
||||
* and related backends (if any exists.
|
||||
*/
|
||||
static int gw_error_backend_event(DCB *dcb)
|
||||
static int plain_error_backend_event(DCB *dcb)
|
||||
{
|
||||
SESSION* session;
|
||||
void* rsession;
|
||||
@ -414,10 +514,6 @@ static int gw_error_backend_event(DCB *dcb)
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
errbuf = mysql_create_custom_error(
|
||||
1,
|
||||
0,
|
||||
"Lost connection to backend server.");
|
||||
|
||||
spinlock_acquire(&session->ses_lock);
|
||||
ses_state = session->state;
|
||||
@ -501,12 +597,12 @@ retblock:
|
||||
* backend server. Positive fd is copied to protocol and to dcb.
|
||||
* If fails, fd == -1 and socket is closed.
|
||||
*/
|
||||
static int gw_create_backend_connection(
|
||||
static int plain_create_backend_connection(
|
||||
DCB *backend_dcb,
|
||||
SERVER *server,
|
||||
SESSION *session)
|
||||
{
|
||||
MySQLProtocol *protocol = NULL;
|
||||
PlainProtocol *protocol = NULL;
|
||||
int rv = -1;
|
||||
int fd = -1;
|
||||
|
||||
@ -527,7 +623,8 @@ static int gw_create_backend_connection(
|
||||
}
|
||||
|
||||
/*< if succeed, fd > 0, -1 otherwise */
|
||||
rv = gw_do_connect_to_backend(server->name, server->port, &fd);
|
||||
|
||||
rv = plain_do_connect_to_backend(server->name, server->port, &fd);
|
||||
/*< Assign protocol with backend_dcb */
|
||||
backend_dcb->protocol = protocol;
|
||||
|
||||
@ -536,7 +633,7 @@ static int gw_create_backend_connection(
|
||||
case 0:
|
||||
ss_dassert(fd > 0);
|
||||
protocol->fd = fd;
|
||||
protocol->protocol_auth_state = MYSQL_CONNECTED;
|
||||
|
||||
LOGIF(LD, (skygw_log_write(
|
||||
LOGFILE_DEBUG,
|
||||
"%lu [gw_create_backend_connection] Established "
|
||||
@ -551,7 +648,7 @@ static int gw_create_backend_connection(
|
||||
|
||||
case 1:
|
||||
ss_dassert(fd > 0);
|
||||
protocol->protocol_auth_state = MYSQL_PENDING_CONNECT;
|
||||
|
||||
protocol->fd = fd;
|
||||
LOGIF(LD, (skygw_log_write(
|
||||
LOGFILE_DEBUG,
|
||||
@ -566,7 +663,7 @@ static int gw_create_backend_connection(
|
||||
|
||||
default:
|
||||
ss_dassert(fd == -1);
|
||||
ss_dassert(protocol->protocol_auth_state == MYSQL_ALLOC);
|
||||
|
||||
LOGIF(LD, (skygw_log_write(
|
||||
LOGFILE_DEBUG,
|
||||
"%lu [gw_create_backend_connection] Connection "
|
||||
@ -595,7 +692,7 @@ return_fd:
|
||||
* @return 1 always
|
||||
*/
|
||||
static int
|
||||
gw_backend_hangup(DCB *dcb)
|
||||
plain_backend_hangup(DCB *dcb)
|
||||
{
|
||||
SESSION* session;
|
||||
void* rsession;
|
||||
@ -613,11 +710,6 @@ gw_backend_hangup(DCB *dcb)
|
||||
router = session->service->router;
|
||||
router_instance = session->service->router_instance;
|
||||
|
||||
errbuf = mysql_create_custom_error(
|
||||
1,
|
||||
0,
|
||||
"Lost connection to backend server.");
|
||||
|
||||
spinlock_acquire(&session->ses_lock);
|
||||
ses_state = session->state;
|
||||
spinlock_release(&session->ses_lock);
|
||||
@ -695,7 +787,7 @@ retblock:
|
||||
* @return 1 always
|
||||
*/
|
||||
static int
|
||||
gw_backend_close(DCB *dcb)
|
||||
plain_backend_close(DCB *dcb)
|
||||
{
|
||||
DCB* client_dcb;
|
||||
SESSION* session;
|
||||
@ -705,17 +797,6 @@ gw_backend_close(DCB *dcb)
|
||||
session = dcb->session;
|
||||
CHK_SESSION(session);
|
||||
|
||||
LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG,
|
||||
"%lu [gw_backend_close]",
|
||||
pthread_self())));
|
||||
|
||||
quitbuf = mysql_create_com_quit(NULL, 0);
|
||||
gwbuf_set_type(quitbuf, GWBUF_TYPE_MYSQL);
|
||||
|
||||
/** Send COM_QUIT to the backend being closed */
|
||||
mysql_send_com_quit(dcb, 0, quitbuf);
|
||||
|
||||
mysql_protocol_done(dcb);
|
||||
/**
|
||||
* The lock is needed only to protect the read of session->state and
|
||||
* session->client values. Client's state may change by other thread
|
||||
@ -796,409 +877,18 @@ static int backend_write_delayqueue(DCB *dcb)
|
||||
}
|
||||
else
|
||||
{
|
||||
localq = dcb->delayq;
|
||||
dcb->delayq = NULL;
|
||||
spinlock_release(&dcb->delayqlock);
|
||||
|
||||
if (MYSQL_IS_CHANGE_USER(((uint8_t *)GWBUF_DATA(localq))))
|
||||
{
|
||||
MYSQL_session* mses;
|
||||
GWBUF* new_packet;
|
||||
|
||||
mses = (MYSQL_session *)dcb->session->client->data;
|
||||
new_packet = gw_create_change_user_packet(
|
||||
mses,
|
||||
(MySQLProtocol *)dcb->protocol);
|
||||
/**
|
||||
* Remove previous packet which lacks scramble
|
||||
* and append the new.
|
||||
*/
|
||||
localq = gwbuf_consume(localq, GWBUF_LENGTH(localq));
|
||||
localq = gwbuf_append(localq, new_packet);
|
||||
}
|
||||
rc = dcb_write(dcb, localq);
|
||||
}
|
||||
|
||||
if (rc == 0)
|
||||
{
|
||||
GWBUF* errbuf;
|
||||
bool succp;
|
||||
ROUTER_OBJECT *router = NULL;
|
||||
ROUTER *router_instance = NULL;
|
||||
void *rsession = NULL;
|
||||
SESSION *session = dcb->session;
|
||||
|
||||
CHK_SESSION(session);
|
||||
|
||||
if (session != NULL)
|
||||
{
|
||||
router = session->service->router;
|
||||
router_instance = session->service->router_instance;
|
||||
rsession = session->router_session;
|
||||
#if defined(SS_DEBUG)
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Backend write delayqueue error handling.")));
|
||||
#endif
|
||||
errbuf = mysql_create_custom_error(
|
||||
1,
|
||||
0,
|
||||
"Failed to write buffered data to back-end server. "
|
||||
"Buffer was empty or back-end was disconnected during "
|
||||
"operation. Attempting to find a new backend.");
|
||||
|
||||
router->handleError(router_instance,
|
||||
rsession,
|
||||
errbuf,
|
||||
dcb,
|
||||
ERRACT_NEW_CONNECTION,
|
||||
&succp);
|
||||
gwbuf_free(errbuf);
|
||||
|
||||
if (!succp)
|
||||
{
|
||||
spinlock_acquire(&session->ses_lock);
|
||||
session->state = SESSION_STATE_STOPPING;
|
||||
spinlock_release(&session->ses_lock);
|
||||
ss_dassert(dcb->dcb_errhandle_called);
|
||||
dcb_close(dcb);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* This routine handles the COM_CHANGE_USER command
|
||||
*
|
||||
* @param dcb The current backend DCB
|
||||
* @param server The backend server pointer
|
||||
* @param in_session The current session data (MYSQL_session)
|
||||
* @param queue The GWBUF containing the COM_CHANGE_USER receveid
|
||||
* @return 1 on success and 0 on failure
|
||||
*/
|
||||
static int gw_change_user(
|
||||
DCB *backend,
|
||||
SERVER *server,
|
||||
SESSION *in_session,
|
||||
GWBUF *queue)
|
||||
{
|
||||
MYSQL_session *current_session = NULL;
|
||||
MySQLProtocol *backend_protocol = NULL;
|
||||
MySQLProtocol *client_protocol = NULL;
|
||||
char username[MYSQL_USER_MAXLEN+1]="";
|
||||
char database[MYSQL_DATABASE_MAXLEN+1]="";
|
||||
char current_database[MYSQL_DATABASE_MAXLEN+1]="";
|
||||
uint8_t client_sha1[MYSQL_SCRAMBLE_LEN]="";
|
||||
uint8_t *client_auth_packet = GWBUF_DATA(queue);
|
||||
unsigned int auth_token_len = 0;
|
||||
uint8_t *auth_token = NULL;
|
||||
int rv = -1;
|
||||
int auth_ret = 1;
|
||||
|
||||
current_session = (MYSQL_session *)in_session->client->data;
|
||||
backend_protocol = backend->protocol;
|
||||
client_protocol = in_session->client->protocol;
|
||||
|
||||
/* now get the user, after 4 bytes header and 1 byte command */
|
||||
client_auth_packet += 5;
|
||||
strncpy(username, (char *)client_auth_packet,MYSQL_USER_MAXLEN);
|
||||
client_auth_packet += strlen(username) + 1;
|
||||
|
||||
/* get the auth token len */
|
||||
memcpy(&auth_token_len, client_auth_packet, 1);
|
||||
|
||||
client_auth_packet++;
|
||||
|
||||
/* allocate memory for token only if auth_token_len > 0 */
|
||||
if (auth_token_len > 0) {
|
||||
auth_token = (uint8_t *)malloc(auth_token_len);
|
||||
ss_dassert(auth_token != NULL);
|
||||
|
||||
if (auth_token == NULL)
|
||||
return rv;
|
||||
memcpy(auth_token, client_auth_packet, auth_token_len);
|
||||
client_auth_packet += auth_token_len;
|
||||
}
|
||||
|
||||
/* get new database name */
|
||||
strncpy(database, (char *)client_auth_packet,MYSQL_DATABASE_MAXLEN);
|
||||
|
||||
/* get character set */
|
||||
if (strlen(database)) {
|
||||
client_auth_packet += strlen(database) + 1;
|
||||
} else {
|
||||
client_auth_packet++;
|
||||
}
|
||||
|
||||
if (client_auth_packet && *client_auth_packet)
|
||||
memcpy(&backend_protocol->charset, client_auth_packet, sizeof(int));
|
||||
|
||||
/* save current_database name */
|
||||
strncpy(current_database, current_session->db,MYSQL_DATABASE_MAXLEN);
|
||||
|
||||
/*
|
||||
* Now clear database name in dcb as we don't do local authentication on db name for change user.
|
||||
* Local authentication only for user@host and if successful the database name change is sent to backend.
|
||||
*/
|
||||
strcpy(current_session->db, "");
|
||||
|
||||
/*
|
||||
* decode the token and check the password.
|
||||
* Note: if auth_token_len == 0 && auth_token == NULL, user is without password
|
||||
*/
|
||||
auth_ret = gw_check_mysql_scramble_data(backend->session->client,
|
||||
auth_token,
|
||||
auth_token_len,
|
||||
client_protocol->scramble,
|
||||
sizeof(client_protocol->scramble),
|
||||
username,
|
||||
client_sha1);
|
||||
|
||||
if (auth_ret != 0) {
|
||||
if (!service_refresh_users(backend->session->client->service)) {
|
||||
/* Try authentication again with new repository data */
|
||||
/* Note: if no auth client authentication will fail */
|
||||
auth_ret = gw_check_mysql_scramble_data(
|
||||
backend->session->client,
|
||||
auth_token, auth_token_len,
|
||||
client_protocol->scramble,
|
||||
sizeof(client_protocol->scramble),
|
||||
username,
|
||||
client_sha1);
|
||||
}
|
||||
}
|
||||
|
||||
/* copy back current datbase to client session */
|
||||
strcpy(current_session->db, current_database);
|
||||
|
||||
/* let's free the auth_token now */
|
||||
if (auth_token)
|
||||
free(auth_token);
|
||||
|
||||
if (auth_ret != 0) {
|
||||
char *password_set = NULL;
|
||||
char *message = NULL;
|
||||
GWBUF* buf;
|
||||
|
||||
if (auth_token_len > 0)
|
||||
password_set = (char *)client_sha1;
|
||||
else
|
||||
password_set = "";
|
||||
|
||||
/**
|
||||
* Create an error message and make it look like legit reply
|
||||
* from backend server. Then make it look like an incoming event
|
||||
* so that thread gets new task of it, calls clientReply
|
||||
* which filters out duplicate errors from same cause and forward
|
||||
* reply to the client.
|
||||
*/
|
||||
message = create_auth_fail_str(username,
|
||||
backend->session->client->remote,
|
||||
password_set,
|
||||
"");
|
||||
if (message == NULL)
|
||||
{
|
||||
LOGIF(LE, (skygw_log_write_flush(
|
||||
LOGFILE_ERROR,
|
||||
"Error : Creating error message failed.")));
|
||||
rv = 0;
|
||||
goto retblock;
|
||||
}
|
||||
/**
|
||||
* Add command to backend's protocol, create artificial reply
|
||||
* packet and add it to client's read buffer.
|
||||
*/
|
||||
protocol_add_srv_command((MySQLProtocol*)backend->protocol,
|
||||
MYSQL_COM_CHANGE_USER);
|
||||
modutil_reply_auth_error(backend, message, 0);
|
||||
rv = 1;
|
||||
} else {
|
||||
rv = gw_send_change_user_to_backend(database, username, client_sha1, backend_protocol);
|
||||
/*
|
||||
* Now copy new data into user session
|
||||
*/
|
||||
strcpy(current_session->user, username);
|
||||
strcpy(current_session->db, database);
|
||||
memcpy(current_session->client_sha1, client_sha1, sizeof(current_session->client_sha1));
|
||||
}
|
||||
|
||||
retblock:
|
||||
gwbuf_free(queue);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Move packets or parts of packets from readbuf to outbuf as the packet headers
|
||||
* and lengths have been noticed and counted.
|
||||
* Session commands need to be marked so that they can be handled properly in
|
||||
* the router's clientReply.
|
||||
*
|
||||
* @param dcb Backend's DCB where data was read from
|
||||
* @param readbuf GWBUF where data was read to
|
||||
* @param nbytes_to_process Number of bytes that has been read and need to be processed
|
||||
*
|
||||
* @return GWBUF which includes complete MySQL packet
|
||||
*/
|
||||
static GWBUF* process_response_data (
|
||||
DCB* dcb,
|
||||
GWBUF* readbuf,
|
||||
int nbytes_to_process)
|
||||
{
|
||||
int npackets_left = 0; /*< response's packet count */
|
||||
ssize_t nbytes_left = 0; /*< nbytes to be read for the packet */
|
||||
MySQLProtocol* p;
|
||||
GWBUF* outbuf = NULL;
|
||||
|
||||
/** Get command which was stored in gw_MySQLWrite_backend */
|
||||
p = DCB_PROTOCOL(dcb, MySQLProtocol);
|
||||
if (!DCB_IS_CLONE(dcb)) CHK_PROTOCOL(p);
|
||||
|
||||
/** All buffers processed here are sescmd responses */
|
||||
gwbuf_set_type(readbuf, GWBUF_TYPE_SESCMD_RESPONSE);
|
||||
|
||||
/**
|
||||
* Now it is known how many packets there should be and how much
|
||||
* is read earlier.
|
||||
*/
|
||||
while (nbytes_to_process != 0)
|
||||
{
|
||||
mysql_server_cmd_t srvcmd;
|
||||
bool succp;
|
||||
|
||||
srvcmd = protocol_get_srv_command(p, false);
|
||||
|
||||
LOGIF(LD, (skygw_log_write(
|
||||
LOGFILE_DEBUG,
|
||||
"%lu [process_response_data] Read command %s for DCB %p fd %d.",
|
||||
pthread_self(),
|
||||
STRPACKETTYPE(srvcmd),
|
||||
dcb,
|
||||
dcb->fd)));
|
||||
/**
|
||||
* Read values from protocol structure, fails if values are
|
||||
* uninitialized.
|
||||
*/
|
||||
if (npackets_left == 0)
|
||||
{
|
||||
succp = protocol_get_response_status(p, &npackets_left, &nbytes_left);
|
||||
|
||||
if (!succp || npackets_left == 0)
|
||||
{
|
||||
/**
|
||||
* Examine command type and the readbuf. Conclude response
|
||||
* packet count from the command type or from the first
|
||||
* packet content. Fails if read buffer doesn't include
|
||||
* enough data to read the packet length.
|
||||
*/
|
||||
init_response_status(readbuf, srvcmd, &npackets_left, &nbytes_left);
|
||||
}
|
||||
}
|
||||
/** Only session commands with responses should be processed */
|
||||
ss_dassert(npackets_left > 0);
|
||||
|
||||
/** Read incomplete packet. */
|
||||
if (nbytes_left > nbytes_to_process)
|
||||
{
|
||||
/** Includes length info so it can be processed */
|
||||
if (nbytes_to_process >= 5)
|
||||
{
|
||||
/** discard source buffer */
|
||||
readbuf = gwbuf_consume(readbuf, GWBUF_LENGTH(readbuf));
|
||||
nbytes_left -= nbytes_to_process;
|
||||
}
|
||||
nbytes_to_process = 0;
|
||||
}
|
||||
/** Packet was read. All bytes belonged to the last packet. */
|
||||
else if (nbytes_left == nbytes_to_process)
|
||||
{
|
||||
nbytes_left = 0;
|
||||
nbytes_to_process = 0;
|
||||
ss_dassert(npackets_left > 0);
|
||||
npackets_left -= 1;
|
||||
outbuf = gwbuf_append(outbuf, readbuf);
|
||||
readbuf = NULL;
|
||||
}
|
||||
/**
|
||||
* Packet was read. There should be more since bytes were
|
||||
* left over.
|
||||
* Move the next packet to its own buffer and add that next
|
||||
* to the prev packet's buffer.
|
||||
*/
|
||||
else /*< nbytes_left < nbytes_to_process */
|
||||
{
|
||||
ss_dassert(nbytes_left >= 0);
|
||||
nbytes_to_process -= nbytes_left;
|
||||
|
||||
/** Move the prefix of the buffer to outbuf from redbuf */
|
||||
outbuf = gwbuf_append(outbuf,
|
||||
gwbuf_clone_portion(readbuf, 0, (size_t)nbytes_left));
|
||||
readbuf = gwbuf_consume(readbuf, (size_t)nbytes_left);
|
||||
ss_dassert(npackets_left > 0);
|
||||
npackets_left -= 1;
|
||||
nbytes_left = 0;
|
||||
}
|
||||
|
||||
/** Store new status to protocol structure */
|
||||
protocol_set_response_status(p, npackets_left, nbytes_left);
|
||||
|
||||
/** A complete packet was read */
|
||||
if (nbytes_left == 0)
|
||||
{
|
||||
/** No more packets in this response */
|
||||
if (npackets_left == 0 && outbuf != NULL)
|
||||
{
|
||||
GWBUF* b = outbuf;
|
||||
|
||||
while (b->next != NULL)
|
||||
{
|
||||
b = b->next;
|
||||
}
|
||||
/** Mark last as end of response */
|
||||
gwbuf_set_type(b, GWBUF_TYPE_RESPONSE_END);
|
||||
|
||||
/** Archive the command */
|
||||
protocol_archive_srv_command(p);
|
||||
}
|
||||
/** Read next packet */
|
||||
else
|
||||
{
|
||||
uint8_t* data;
|
||||
|
||||
/** Read next packet length */
|
||||
data = GWBUF_DATA(readbuf);
|
||||
nbytes_left = MYSQL_GET_PACKET_LEN(data)+MYSQL_HEADER_LEN;
|
||||
/** Store new status to protocol structure */
|
||||
protocol_set_response_status(p, npackets_left, nbytes_left);
|
||||
}
|
||||
}
|
||||
}
|
||||
return outbuf;
|
||||
}
|
||||
|
||||
|
||||
static bool sescmd_response_complete(
|
||||
DCB* dcb)
|
||||
{
|
||||
int npackets_left;
|
||||
ssize_t nbytes_left;
|
||||
MySQLProtocol* p;
|
||||
bool succp;
|
||||
|
||||
p = DCB_PROTOCOL(dcb, MySQLProtocol);
|
||||
if (!DCB_IS_CLONE(dcb)) CHK_PROTOCOL(p);
|
||||
|
||||
protocol_get_response_status(p, &npackets_left, &nbytes_left);
|
||||
|
||||
if (npackets_left == 0)
|
||||
{
|
||||
succp = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
succp = false;
|
||||
}
|
||||
return succp;
|
||||
}
|
||||
|
@ -30,14 +30,13 @@
|
||||
#include <modinfo.h>
|
||||
#include <sys/stat.h>
|
||||
#include <modutil.h>
|
||||
|
||||
#include <mysql_client_server_protocol.h>
|
||||
#include <plainprotocol.h>
|
||||
|
||||
MODULE_INFO info = {
|
||||
MODULE_API_PROTOCOL,
|
||||
MODULE_GA,
|
||||
GWPROTOCOL_VERSION,
|
||||
"The client to plain protocol implementation"
|
||||
"The plain client protocol"
|
||||
};
|
||||
|
||||
/** Defined in log_manager.cc */
|
||||
@ -107,201 +106,6 @@ GetModuleObject()
|
||||
return &MyObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* mysql_send_ok
|
||||
*
|
||||
* Send a MySQL protocol OK message to the dcb (client)
|
||||
*
|
||||
* @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_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* MySQLSendHandshake
|
||||
*
|
||||
* @param dcb The descriptor control block to use for sending the handshake request
|
||||
* @return The packet length sent
|
||||
*/
|
||||
int
|
||||
MySQLSendHandshake(DCB* dcb)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* gw_mysql_do_authentication
|
||||
*
|
||||
* Performs the MySQL protocol 4.1 authentication, using data in GWBUF *queue
|
||||
*
|
||||
* (MYSQL_session*)client_data including: user, db, client_sha1 are copied into
|
||||
* the dcb->data and later to dcb->session->data.
|
||||
*
|
||||
* client_capabilitiesa are copied into the dcb->protocol
|
||||
*
|
||||
* @param dcb Descriptor Control Block of the client
|
||||
* @param queue The GWBUF with data from client
|
||||
* @return 0 If succeed, otherwise non-zero value
|
||||
*
|
||||
* @note in case of failure, dcb->data is freed before returning. If succeed,
|
||||
* dcb->data is freed in session.c:session_free.
|
||||
*/
|
||||
static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
|
||||
MySQLProtocol *protocol = NULL;
|
||||
/* int compress = -1; */
|
||||
int connect_with_db = -1;
|
||||
uint8_t *client_auth_packet = GWBUF_DATA(queue);
|
||||
int client_auth_packet_size = 0;
|
||||
char *username = NULL;
|
||||
char *database = NULL;
|
||||
unsigned int auth_token_len = 0;
|
||||
uint8_t *auth_token = NULL;
|
||||
uint8_t *stage1_hash = NULL;
|
||||
int auth_ret = -1;
|
||||
MYSQL_session *client_data = NULL;
|
||||
|
||||
CHK_DCB(dcb);
|
||||
|
||||
protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
|
||||
CHK_PROTOCOL(protocol);
|
||||
client_data = (MYSQL_session *)calloc(1, sizeof(MYSQL_session));
|
||||
#if defined(SS_DEBUG)
|
||||
client_data->myses_chk_top = CHK_NUM_MYSQLSES;
|
||||
client_data->myses_chk_tail = CHK_NUM_MYSQLSES;
|
||||
#endif
|
||||
/**
|
||||
* Assign authentication structure with client DCB.
|
||||
*/
|
||||
dcb->data = client_data;
|
||||
|
||||
stage1_hash = client_data->client_sha1;
|
||||
username = client_data->user;
|
||||
|
||||
client_auth_packet_size = gwbuf_length(queue);
|
||||
|
||||
/* For clients supporting CLIENT_PROTOCOL_41
|
||||
* the Handshake Response Packet is:
|
||||
*
|
||||
* 4 bytes mysql protocol heade
|
||||
* 4 bytes capability flags
|
||||
* 4 max-packet size
|
||||
* 1 byte character set
|
||||
* string[23] reserved (all [0])
|
||||
* ...
|
||||
* ...
|
||||
*/
|
||||
|
||||
/* Detect now if there are enough bytes to continue */
|
||||
if (client_auth_packet_size < (4 + 4 + 4 + 1 + 23))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
memcpy(&protocol->client_capabilities, client_auth_packet + 4, 4);
|
||||
|
||||
connect_with_db =
|
||||
GW_MYSQL_CAPABILITIES_CONNECT_WITH_DB & gw_mysql_get_byte4(
|
||||
(uint32_t *)&protocol->client_capabilities);
|
||||
/*
|
||||
compress =
|
||||
GW_MYSQL_CAPABILITIES_COMPRESS & gw_mysql_get_byte4(
|
||||
&protocol->client_capabilities);
|
||||
*/
|
||||
|
||||
username = get_username_from_auth(username, client_auth_packet);
|
||||
|
||||
if (username == NULL)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* get charset */
|
||||
memcpy(&protocol->charset, client_auth_packet + 4 + 4 + 4, sizeof (int));
|
||||
|
||||
/* get the auth token len */
|
||||
memcpy(&auth_token_len,
|
||||
client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + 1,
|
||||
1);
|
||||
|
||||
/*
|
||||
* Note: some clients may pass empty database, connect_with_db !=0 but database =""
|
||||
*/
|
||||
if (connect_with_db) {
|
||||
database = client_data->db;
|
||||
strncpy(database,
|
||||
(char *)(client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) +
|
||||
1 + 1 + auth_token_len), MYSQL_DATABASE_MAXLEN);
|
||||
}
|
||||
|
||||
/* allocate memory for token only if auth_token_len > 0 */
|
||||
if (auth_token_len) {
|
||||
auth_token = (uint8_t *)malloc(auth_token_len);
|
||||
memcpy(auth_token,
|
||||
client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + 1 + 1,
|
||||
auth_token_len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode the token and check the password
|
||||
* Note: if auth_token_len == 0 && auth_token == NULL, user is without password
|
||||
*/
|
||||
|
||||
auth_ret = gw_check_mysql_scramble_data(dcb,
|
||||
auth_token,
|
||||
auth_token_len,
|
||||
protocol->scramble,
|
||||
sizeof(protocol->scramble),
|
||||
username,
|
||||
stage1_hash);
|
||||
|
||||
/* check for database name match in resource hashtable */
|
||||
auth_ret = check_db_name_after_auth(dcb, database, auth_ret);
|
||||
|
||||
/* On failed auth try to load users' table from backend database */
|
||||
if (auth_ret != 0) {
|
||||
if (!service_refresh_users(dcb->service)) {
|
||||
/* Try authentication again with new repository data */
|
||||
/* Note: if no auth client authentication will fail */
|
||||
auth_ret = gw_check_mysql_scramble_data(
|
||||
dcb,
|
||||
auth_token,
|
||||
auth_token_len,
|
||||
protocol->scramble,
|
||||
sizeof(protocol->scramble),
|
||||
username,
|
||||
stage1_hash);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGIF(LM, (skygw_log_write(LOGFILE_MESSAGE,
|
||||
"%s: login attempt for user %s, user not "
|
||||
"found.",
|
||||
dcb->service->name, username)));
|
||||
}
|
||||
}
|
||||
|
||||
/* Do again the database check */
|
||||
auth_ret = check_db_name_after_auth(dcb, database, auth_ret);
|
||||
|
||||
/* on succesful auth set user into dcb field */
|
||||
if (auth_ret == 0) {
|
||||
dcb->user = strdup(client_data->user);
|
||||
}
|
||||
|
||||
/* let's free the auth_token now */
|
||||
if (auth_token) {
|
||||
free(auth_token);
|
||||
}
|
||||
|
||||
return auth_ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write function for client DCB: writes data from MaxScale to Client
|
||||
@ -328,7 +132,7 @@ int plain_read(
|
||||
ROUTER_OBJECT *router = NULL;
|
||||
ROUTER *router_instance = NULL;
|
||||
void *rsession = NULL;
|
||||
MySQLProtocol *protocol = NULL;
|
||||
PlainProtocol *protocol = NULL;
|
||||
GWBUF *read_buffer = NULL;
|
||||
int rc = 0;
|
||||
int nbytes_read = 0;
|
||||
@ -336,7 +140,7 @@ int plain_read(
|
||||
bool stmt_input = false; /*< router input type */
|
||||
|
||||
CHK_DCB(dcb);
|
||||
protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
|
||||
protocol = DCB_PROTOCOL(dcb, PlainProtocol);
|
||||
CHK_PROTOCOL(protocol);
|
||||
rc = dcb_read(dcb, &read_buffer);
|
||||
|
||||
@ -383,7 +187,7 @@ return_rc:
|
||||
*/
|
||||
int plain_write_ready(DCB *dcb)
|
||||
{
|
||||
MySQLProtocol *protocol = NULL;
|
||||
PlainProtocol *protocol = NULL;
|
||||
|
||||
CHK_DCB(dcb);
|
||||
|
||||
@ -400,7 +204,7 @@ int plain_write_ready(DCB *dcb)
|
||||
if (dcb->protocol == NULL) {
|
||||
goto return_1;
|
||||
}
|
||||
protocol = (MySQLProtocol *)dcb->protocol;
|
||||
protocol = (PlainProtocol *)dcb->protocol;
|
||||
|
||||
|
||||
dcb_drain_writeq(dcb);
|
||||
@ -575,7 +379,7 @@ int plain_accept(DCB *listener)
|
||||
{
|
||||
int rc = 0;
|
||||
DCB *client_dcb;
|
||||
MySQLProtocol *protocol;
|
||||
PlainProtocol *protocol;
|
||||
int c_sock;
|
||||
struct sockaddr client_conn;
|
||||
socklen_t client_len = sizeof(struct sockaddr_storage);
|
||||
@ -591,24 +395,12 @@ int plain_accept(DCB *listener)
|
||||
|
||||
retry_accept:
|
||||
|
||||
#if defined(FAKE_CODE)
|
||||
if (fail_next_accept > 0)
|
||||
{
|
||||
c_sock = -1;
|
||||
eno = fail_accept_errno;
|
||||
fail_next_accept -= 1;
|
||||
} else {
|
||||
fail_accept_errno = 0;
|
||||
#endif /* FAKE_CODE */
|
||||
// new connection from client
|
||||
c_sock = accept(listener->fd,
|
||||
(struct sockaddr *) &client_conn,
|
||||
&client_len);
|
||||
eno = errno;
|
||||
errno = 0;
|
||||
#if defined(FAKE_CODE)
|
||||
}
|
||||
#endif /* FAKE_CODE */
|
||||
|
||||
if (c_sock == -1) {
|
||||
|
||||
@ -687,9 +479,7 @@ int plain_accept(DCB *listener)
|
||||
pthread_self(),
|
||||
c_sock)));
|
||||
#endif /* SS_DEBUG */
|
||||
#if defined(FAKE_CODE)
|
||||
conn_open[c_sock] = true;
|
||||
#endif /* FAKE_CODE */
|
||||
|
||||
/* set nonblocking */
|
||||
sendbuf = GW_CLIENT_SO_SNDBUF;
|
||||
|
||||
@ -762,8 +552,6 @@ int plain_accept(DCB *listener)
|
||||
// assign function poiters to "func" field
|
||||
memcpy(&client_dcb->func, &MyObject, sizeof(GWPROTOCOL));
|
||||
|
||||
// client protocol state change
|
||||
protocol->protocol_auth_state = MYSQL_IDLE;
|
||||
|
||||
/**
|
||||
* Set new descriptor to event set. At the same time,
|
||||
@ -773,11 +561,6 @@ int plain_accept(DCB *listener)
|
||||
if (poll_add_dcb(client_dcb) == -1)
|
||||
{
|
||||
/* Send a custom error as MySQL command reply */
|
||||
mysql_send_custom_error(
|
||||
client_dcb,
|
||||
1,
|
||||
0,
|
||||
"MaxScale internal error.");
|
||||
|
||||
/** close client_dcb */
|
||||
dcb_close(client_dcb);
|
||||
@ -807,7 +590,7 @@ int plain_accept(DCB *listener)
|
||||
#if defined(SS_DEBUG)
|
||||
if (rc == 0) {
|
||||
CHK_DCB(client_dcb);
|
||||
CHK_PROTOCOL(((MySQLProtocol *)client_dcb->protocol));
|
||||
CHK_PROTOCOL(((PlainProtocol *)client_dcb->protocol));
|
||||
}
|
||||
#endif
|
||||
return_rc:
|
||||
@ -851,7 +634,7 @@ plain_client_close(DCB *dcb)
|
||||
ROUTER_OBJECT* router;
|
||||
void* router_instance;
|
||||
#if defined(SS_DEBUG)
|
||||
MySQLProtocol* protocol = (MySQLProtocol *)dcb->protocol;
|
||||
PlainProtocol* protocol = (PlainProtocol *)dcb->protocol;
|
||||
if (dcb->state == DCB_STATE_POLLING ||
|
||||
dcb->state == DCB_STATE_NOPOLLING ||
|
||||
dcb->state == DCB_STATE_ZOMBIE)
|
||||
@ -929,76 +712,3 @@ retblock:
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Detect if buffer includes partial mysql packet or multiple packets.
|
||||
* Store partial packet to dcb_readqueue. Send complete packets one by one
|
||||
* to router.
|
||||
*
|
||||
* It is assumed readbuf includes at least one complete packet.
|
||||
* Return 1 in success. If the last packet is incomplete return success but
|
||||
* leave incomplete packet to readbuf.
|
||||
*
|
||||
* @param session Session pointer
|
||||
* @param p_readbuf Pointer to the address of GWBUF including the query
|
||||
*
|
||||
* @return 1 if succeed,
|
||||
*/
|
||||
static int route_by_statement(
|
||||
SESSION* session,
|
||||
GWBUF** p_readbuf)
|
||||
{
|
||||
int rc;
|
||||
GWBUF* packetbuf;
|
||||
#if defined(SS_DEBUG)
|
||||
GWBUF* tmpbuf;
|
||||
|
||||
tmpbuf = *p_readbuf;
|
||||
while (tmpbuf != NULL)
|
||||
{
|
||||
ss_dassert(GWBUF_IS_TYPE_MYSQL(tmpbuf));
|
||||
tmpbuf=tmpbuf->next;
|
||||
}
|
||||
#endif
|
||||
do
|
||||
{
|
||||
ss_dassert(GWBUF_IS_TYPE_MYSQL((*p_readbuf)));
|
||||
|
||||
/**
|
||||
* Collect incoming bytes to a buffer until complete packet has
|
||||
* arrived and then return the buffer.
|
||||
*/
|
||||
packetbuf = gw_MySQL_get_next_packet(p_readbuf);
|
||||
|
||||
if (packetbuf != NULL)
|
||||
{
|
||||
CHK_GWBUF(packetbuf);
|
||||
ss_dassert(GWBUF_IS_TYPE_MYSQL(packetbuf));
|
||||
/**
|
||||
* This means that buffer includes exactly one MySQL
|
||||
* statement.
|
||||
* backend func.write uses the information. MySQL backend
|
||||
* protocol, for example, stores the command identifier
|
||||
* to protocol structure. When some other thread reads
|
||||
* the corresponding response the command tells how to
|
||||
* handle response.
|
||||
*
|
||||
* Set it here instead of plain_read to make
|
||||
* sure it is set to each (MySQL) packet.
|
||||
*/
|
||||
gwbuf_set_type(packetbuf, GWBUF_TYPE_SINGLE_STMT);
|
||||
/** Route query */
|
||||
rc = SESSION_ROUTE_QUERY(session, packetbuf);
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = 1;
|
||||
goto return_rc;
|
||||
}
|
||||
}
|
||||
while (rc == 1 && *p_readbuf != NULL);
|
||||
|
||||
return_rc:
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user