/** * Convert a chain of GWBUF structures into a single GWBUF structure * * @param orig The chain to convert * @return The contiguous buffer */ GWBUF * gwbuf_make_contiguous(GWBUF *orig) { GWBUF *newbuf; char *ptr; int len; if (orig->next == NULL) return orig; if ((newbuf = gwbuf_alloc(gwbuf_length(orig))) != NULL) { newbuf->gwbuf_type = orig->gwbuf_type; newbuf->hint = hint_dup(orig->hint); ptr = GWBUF_DATA(newbuf); while (orig) { len = GWBUF_LENGTH(orig); memcpy(ptr, GWBUF_DATA(orig), len); ptr += len; orig = gwbuf_consume(orig, len); } } return newbuf; }
/** * Trim bytes form the end of a GWBUF structure. If the * buffer has n_bytes or less then it will be freed and * NULL will be returned. * * This routine assumes the buffer is not part of a chain * * @param buf The buffer to trim * @param n_bytes The number of bytes to trim off * @return The buffer chain or NULL if buffer has <= n_bytes */ GWBUF * gwbuf_trim(GWBUF *buf, unsigned int n_bytes) { ss_dassert(buf->next == NULL); if (GWBUF_LENGTH(buf) <= n_bytes) { gwbuf_consume(buf, GWBUF_LENGTH(buf)); return NULL; } buf->end = (void *)((char *)buf->end - n_bytes); return buf; }
/** * We have data from the client, we must route it to the backend. * This is simply a case of sending it to the connection that was * chosen when we started the client session. * * @param instance The router instance * @param router_session The router session returned from the newSession call * @param queue The queue of data buffers to route * @return The number of bytes sent */ static int execute(ROUTER *instance, void *router_session, GWBUF *queue) { CLI_SESSION *session = (CLI_SESSION *)router_session; /* Extract the characters */ while (queue) { strncat(session->cmdbuf, GWBUF_DATA(queue), MIN(GWBUF_LENGTH(queue),cmdbuflen-1)); queue = gwbuf_consume(queue, GWBUF_LENGTH(queue)); } execute_cmd(session); return 1; }
/** * Process a request packet from the slave server. * * The router can handle a limited subset of requests from the slave, these * include a subset of general SQL queries, a slave registeration command and * the binlog dump command. * * The strategy for responding to these commands is to use caches responses * for the the same commands that have previously been made to the real master * if this is possible, if it is not then the router itself will synthesize a * response. * * @param router The router instance this defines the master for this replication chain * @param slave The slave specific data * @param queue The incoming request packet */ int blr_slave_request(ROUTER_INSTANCE *router, ROUTER_SLAVE *slave, GWBUF *queue) { if (slave->state < 0 || slave->state > BLRS_MAXSTATE) { LOGIF(LE, (skygw_log_write( LOGFILE_ERROR, "Invalid slave state machine state (%d) for binlog router.", slave->state))); gwbuf_consume(queue, gwbuf_length(queue)); return 0; } slave->stats.n_requests++; switch (MYSQL_COMMAND(queue)) { case COM_QUERY: return blr_slave_query(router, slave, queue); break; case COM_REGISTER_SLAVE: return blr_slave_register(router, slave, queue); break; case COM_BINLOG_DUMP: return blr_slave_binlog_dump(router, slave, queue); break; case COM_STATISTICS: return blr_statistics(router, slave, queue); break; case COM_PING: return blr_ping(router, slave, queue); break; case COM_QUIT: LOGIF(LD, (skygw_log_write(LOGFILE_DEBUG, "COM_QUIT received from slave with server_id %d", slave->serverid))); break; default: blr_send_custom_error(slave->dcb, 1, 0, "MySQL command not supported by the binlog router."); LOGIF(LE, (skygw_log_write( LOGFILE_ERROR, "Unexpected MySQL Command (%d) received from slave", MYSQL_COMMAND(queue)))); break; } return 0; }
/** * Remove the first mysql statement from buffer. Return pointer to the removed * statement or NULL if buffer is empty. * * Clone buf, calculate the length of included mysql stmt, and point the * statement with cloned buffer. Move the start pointer of buf accordingly * so that it only cover the remaining buffer. * */ GWBUF* gw_MySQL_get_next_stmt( GWBUF** p_readbuf) { GWBUF* stmtbuf; size_t buflen; size_t strlen; uint8_t* packet; if (*p_readbuf == NULL) { stmtbuf = NULL; goto return_stmtbuf; } CHK_GWBUF(*p_readbuf); if (GWBUF_EMPTY(*p_readbuf)) { stmtbuf = NULL; goto return_stmtbuf; } buflen = GWBUF_LENGTH((*p_readbuf)); packet = GWBUF_DATA((*p_readbuf)); strlen = MYSQL_GET_PACKET_LEN(packet); if (strlen+4 == buflen) { stmtbuf = *p_readbuf; *p_readbuf = NULL; goto return_stmtbuf; } /** vraa :Multi-packet stmt is not supported as of 7.3.14 */ if (strlen-1 > buflen-5) { stmtbuf = NULL; goto return_stmtbuf; } stmtbuf = gwbuf_clone_portion(*p_readbuf, 0, strlen+4); *p_readbuf = gwbuf_consume(*p_readbuf, strlen+4); return_stmtbuf: return stmtbuf; }
/** * We have data from the client, we must route it to the backend. * This is simply a case of sending it to the connection that was * chosen when we started the client session. * * @param instance The router instance * @param router_session The router session returned from the newSession call * @param queue The queue of data buffers to route * @return The number of bytes sent */ static int execute(ROUTER *instance, void *router_session, GWBUF *queue) { CLI_SESSION *session = (CLI_SESSION *)router_session; /* Extract the characters */ while (queue) { strncat(session->cmdbuf, GWBUF_DATA(queue), GWBUF_LENGTH(queue)); queue = gwbuf_consume(queue, GWBUF_LENGTH(queue)); } if (strrchr(session->cmdbuf, '\n')) { if (execute_cmd(session)) dcb_printf(session->session->client, "MaxScale> "); else session->session->client->func.close(session->session->client); } return 1; }
/** * Read event for EPOLLIN on the telnetd protocol module. * * @param dcb The descriptor control block * @return */ static int telnetd_read_event(DCB* dcb) { int n; 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; TELNETD *telnetd = (TELNETD *)dcb->protocol; char *password, *t; if ((n = dcb_read(dcb, &head)) != -1) { if (head) { unsigned char *ptr = GWBUF_DATA(head); ptr = GWBUF_DATA(head); while (GWBUF_LENGTH(head) && *ptr == TELNET_IAC) { telnetd_command(dcb, ptr + 1); GWBUF_CONSUME(head, 3); ptr = GWBUF_DATA(head); } if (GWBUF_LENGTH(head)) { switch (telnetd->state) { case TELNETD_STATE_LOGIN: telnetd->username = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head)); /* Strip the cr/lf from the username */ t = strstr(telnetd->username, "\r\n"); if (t) *t = 0; telnetd->state = TELNETD_STATE_PASSWD; dcb_printf(dcb, "Password: "******"\r\n"); if (t) *t = 0; if (admin_verify(telnetd->username, password)) { telnetd_echo(dcb, 1); telnetd->state = TELNETD_STATE_DATA; dcb_printf(dcb, "\n\nMaxScale> "); } else { dcb_printf(dcb, "\n\rLogin incorrect\n\rLogin: "); telnetd_echo(dcb, 1); telnetd->state = TELNETD_STATE_LOGIN; free(telnetd->username); } gwbuf_consume(head, GWBUF_LENGTH(head)); free(password); break; case TELNETD_STATE_DATA: router->routeQuery(router_instance, rsession, head); break; } } else { // Force the free of the buffer header gwbuf_consume(head, 0); } } } return n; }
/** * Receive the MySQL authentication packet from backend, packet # is 2 * * @param protocol The MySQL protocol structure * @return -1 in case of failure, 0 if there was nothing to read, 1 if read * was successful. */ int gw_receive_backend_auth( MySQLProtocol *protocol) { int n = -1; GWBUF *head = NULL; DCB *dcb = protocol->owner_dcb; uint8_t *ptr = NULL; int rc = 0; n = dcb_read(dcb, &head); /*< * Read didn't fail and there is enough data for mysql packet. */ if (n != -1 && head != NULL && GWBUF_LENGTH(head) >= 5) { ptr = GWBUF_DATA(head); /*< * 5th byte is 0x0 if successful. */ if (ptr[4] == '\x00') { rc = 1; } else { uint8_t* tmpbuf = (uint8_t *)calloc(1, GWBUF_LENGTH(head)+1); memcpy(tmpbuf, ptr, GWBUF_LENGTH(head)); LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [gw_receive_backend_auth] Invalid " "authentication message from backend dcb %p " "fd %d, ptr[4] = %p, msg %s.", pthread_self(), dcb, dcb->fd, tmpbuf[4], tmpbuf))); free(tmpbuf); rc = -1; } /*< * Remove data from buffer. */ head = gwbuf_consume(head, GWBUF_LENGTH(head)); } else if (n == 0) { /*< * This is considered as success because call didn't fail, * although no bytes was read. */ rc = 0; LOGIF(LD, (skygw_log_write( LOGFILE_DEBUG, "%lu [gw_receive_backend_auth] Read zero bytes from " "backend dcb %p fd %d in state %s. n %d, head %p, len %d", pthread_self(), dcb, dcb->fd, STRDCBSTATE(dcb->state), n, head, (head == NULL) ? 0 : GWBUF_LENGTH(head)))); } else { ss_dassert(n < 0 && head == NULL); rc = -1; LOGIF(LD, (skygw_log_write_flush( LOGFILE_DEBUG, "%lu [gw_receive_backend_auth] Reading from backend dcb %p " "fd %d in state %s failed. n %d, head %p, len %d", pthread_self(), dcb, dcb->fd, STRDCBSTATE(dcb->state), n, head, (head == NULL) ? 0 : GWBUF_LENGTH(head)))); } return rc; }
/** * Read the backend server MySQL handshake * * @param conn MySQL protocol structure * @return 0 on success, 1 on failure */ int gw_read_backend_handshake(MySQLProtocol *conn) { GWBUF *head = NULL; DCB *dcb = conn->owner_dcb; int n = -1; uint8_t *payload = NULL; int h_len = 0; int success = 0; int packet_len = 0; if ((n = dcb_read(dcb, &head)) != -1) { if (head) { payload = GWBUF_DATA(head); h_len = gwbuf_length(head); /* * The mysql packets content starts at byte fifth * just return with less bytes */ if (h_len <= 4) { /* log error this exit point */ conn->state = MYSQL_AUTH_FAILED; return 1; } //get mysql packet size, 3 bytes packet_len = gw_mysql_get_byte3(payload); if (h_len < (packet_len + 4)) { /* * data in buffer less than expected in the * packet. Log error this exit point */ conn->state = MYSQL_AUTH_FAILED; return 1; } // skip the 4 bytes header payload += 4; //Now decode mysql handshake success = gw_decode_mysql_server_handshake(conn, payload); if (success < 0) { /* MySQL handshake has not been properly decoded * we cannot continue * log error this exit point */ conn->state = MYSQL_AUTH_FAILED; return 1; } conn->state = MYSQL_AUTH_SENT; // consume all the data here head = gwbuf_consume(head, GWBUF_LENGTH(head)); return 0; } } // Nothing done here, log error this return 1; }