mirror of https://github.com/sysown/proxysql
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
186 lines
4.6 KiB
186 lines
4.6 KiB
/**
|
|
* @file res_3404-mysql_close_fd_leak.cpp
|
|
* @brief This file is not a test to test to be executed,
|
|
* it contains an isolated reproduction of issue #3404.
|
|
* @details This test contains and isolated reproduction of
|
|
* issue #3404. For this, it uses the async 'libmariadbclient'
|
|
* API in a fashion in which a file descriptor leak is created.
|
|
*
|
|
* # Test usage
|
|
*
|
|
* As the rest of tap tests this file requires serveral constants to
|
|
* be supplied, for making use of this file it's required to execute
|
|
* the following commands:
|
|
*
|
|
* ```
|
|
* $ source constants
|
|
* $ sudo iptables -A OUTPUT -p tcp --dport $TAP_PORT -j DROP
|
|
* ```
|
|
*
|
|
* This way we make sure that the connection is going to try to
|
|
* be performed and that the MySQL connection attempt is going
|
|
* to fail with a timeout. Finally we launch the test file,
|
|
* supplying the 'leak' parameter, to instruct the test to
|
|
* use the flow that creates the leak:
|
|
*
|
|
* ```
|
|
* $ ./tests/repro_3404-mysql_close_fd_leak-t leak
|
|
* ```
|
|
*
|
|
* Check that the file descriptors are being leak:
|
|
*
|
|
* ```
|
|
* sudo lsof -p $(pgrep -f res_3404-socket_fd_not_closed) | wc -l
|
|
* ```
|
|
*
|
|
* We can also verify that nothing is being leak if we don't supply
|
|
* the 'leak' parameter to the executable.
|
|
*
|
|
* # Implementation details
|
|
*
|
|
* For creating the leak this test creates the following flow:
|
|
*
|
|
* 1. Initialize the 'MYSQL' object using 'mysql_init'.
|
|
* 2. Attempt to start a connection with 'mysql_real_connect_start'.
|
|
* 3. Connection should timeout due to the server being unreachable.
|
|
* Check usage.
|
|
* 4. We immediately call 'mysql_close'. Leaking a file descriptor.
|
|
*
|
|
*/
|
|
|
|
#include <cstring>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <poll.h>
|
|
#include <sys/epoll.h>
|
|
|
|
#include "mysql.h"
|
|
#include <thread>
|
|
|
|
#include "tap.h"
|
|
#include "command_line.h"
|
|
#include "utils.h"
|
|
|
|
|
|
/* Helper function to do the waiting for events on the socket. */
|
|
static int wait_for_mysql(MYSQL *mysql, int status) {
|
|
struct pollfd pfd;
|
|
int timeout, res;
|
|
|
|
pfd.fd = mysql_get_socket(mysql);
|
|
pfd.events =
|
|
(status & MYSQL_WAIT_READ ? POLLIN : 0) |
|
|
(status & MYSQL_WAIT_WRITE ? POLLOUT : 0) |
|
|
(status & MYSQL_WAIT_EXCEPT ? POLLPRI : 0);
|
|
if (status & MYSQL_WAIT_TIMEOUT)
|
|
timeout = 1000*mysql_get_timeout_value(mysql);
|
|
else
|
|
timeout = -1;
|
|
res = poll(&pfd, 1, timeout);
|
|
if (res == 0)
|
|
return MYSQL_WAIT_TIMEOUT;
|
|
else if (res < 0)
|
|
return MYSQL_WAIT_TIMEOUT;
|
|
else {
|
|
int status = 0;
|
|
if (pfd.revents & POLLIN) status |= MYSQL_WAIT_READ;
|
|
if (pfd.revents & POLLOUT) status |= MYSQL_WAIT_WRITE;
|
|
if (pfd.revents & POLLPRI) status |= MYSQL_WAIT_EXCEPT;
|
|
return status;
|
|
}
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
CommandLine cl;
|
|
|
|
if (cl.getEnv()) {
|
|
diag("Failed to get the required environmental variables.");
|
|
return -1;
|
|
}
|
|
|
|
bool create_leak = false;
|
|
|
|
if (argc == 2) {
|
|
std::string param { argv[1] };
|
|
|
|
if (param == "leak") {
|
|
create_leak = true;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 1000; i++) {
|
|
MYSQL* proxysql = mysql_init(NULL);
|
|
MYSQL* ret = NULL;
|
|
unsigned int timeout = 1;
|
|
|
|
mysql_options(proxysql, MYSQL_OPT_NONBLOCK, 0);
|
|
mysql_options(proxysql, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&timeout);
|
|
mysql_ssl_set(proxysql, NULL, NULL, NULL, NULL, NULL);
|
|
|
|
int status = 0;
|
|
|
|
diag("Openning connection number: %d", i);
|
|
|
|
status =
|
|
mysql_real_connect_start(
|
|
&ret,
|
|
proxysql,
|
|
cl.host,
|
|
cl.username,
|
|
cl.password,
|
|
NULL,
|
|
cl.port,
|
|
NULL,
|
|
CLIENT_SSL
|
|
);
|
|
|
|
if (status == 0) {
|
|
fprintf(
|
|
stderr, "File %s, line %d, Error: %s\n",
|
|
__FILE__, __LINE__, mysql_error(proxysql)
|
|
);
|
|
return -1;
|
|
}
|
|
|
|
my_socket sockt = mysql_get_socket(proxysql);
|
|
|
|
int state = 0;
|
|
MYSQL* ret_mysql = NULL;
|
|
|
|
while (status) {
|
|
diag(":: Waiting for MySQL server on connection '%d'", i);
|
|
status = wait_for_mysql(proxysql, status);
|
|
|
|
// don't do finalize the connect! We directly call 'mysql_close'
|
|
// creating a leak in the already internally initalized 'fd'
|
|
// created by 'libmariadbclient'.
|
|
if (status == MYSQL_WAIT_TIMEOUT && create_leak) {
|
|
diag(":: Premature close in connection '%d', leaking 'fd'...", i);
|
|
break;
|
|
}
|
|
|
|
// even if we timeout, we call continue
|
|
diag(":: Calling 'mysql_real_connect_cont' with status: %d", status);
|
|
status = mysql_real_connect_cont(&ret_mysql, proxysql, status);
|
|
}
|
|
|
|
if (ret_mysql) {
|
|
diag(":: This is not expected, make sure the server is not reachable.");
|
|
} else {
|
|
// now we close
|
|
diag(
|
|
":: Calling 'mysql_close' after 'mysql_real_connect_cont' returned status: %d",
|
|
status
|
|
);
|
|
mysql_close(proxysql);
|
|
}
|
|
|
|
sleep(1);
|
|
}
|
|
|
|
return exit_status();
|
|
}
|
|
|