/** * @file bug601.cpp regression case for bug 601 ("COM_CHANGE_USER fails with correct user/pwd if executed * during authentication") * - configure Maxscale.cnf to use only one thread * - in 100 parallel threads start to open/close session * - do change_user 2000 times * - check all change_user are ok * - check Mascale is alive */ /* * Vilho Raatikka 2014-10-30 14:30:57 UTC * If COM_CHANGE_USER is executed while backend protocol's state is not yet MYSQL_AUTH_RECV it will fail in * the backend. * * If MaxScale uses multiple worked threads this occurs rarely and it would be possible to easily suspend * execution of COM_CHANGE_USER. * * If MaxScale uses one worker thread then there's currently no way to suspend execution. It would require * thread to put current task on hold, complete authentication task and return to COM_CHANGE_USER execution. * * In theory it is possible to add an event to client's DCB and let it become notified in the same way than * events that occur in sockets. It would have to be added first (not last) and ensure that no other command * is executed before it. * * Since this is the only case known where this would be necessary, it could be enough to add a "pending auth * change" pointer in client's protocol object which would be checked before thread returns to epoll_wait * after completing the authentication. * Comment 1 Massimiliano 2014-11-07 17:01:29 UTC * Current code in develop branch let COM_CHANGE_USER work fine. * * I noticed sometime a failed authentication issue using only. * This because backend protocol's state is not yet MYSQL_AUTH_RECV and necessary data for succesfull backend * change user (such as scramble data from handshake) may be not available. * * * I put a query before change_user and the issue doesn't appear: that's another proof. * Comment 2 Vilho Raatikka 2014-11-13 15:54:15 UTC * In gw_change_user->gw_send_change_user_to_backend authentication message was sent to backend server before * backend had its scramble data. That caused authentication to fail. * Comment 3 Vilho Raatikka 2014-11-13 15:58:34 UTC * if (func.auth ==)gw_change_user->gw_send_change_user_to_backend is called before backend has its scramble, * auth packet is set to backend's delauqueue instead of writing it to backend. When backend_write_delayqueue * is called COM_CHANGE_USER packets are rewritten with backend's current data. */ #include #include "testconnections.h" using namespace std; pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; int exit_flag = 0; TestConnections* Test; void* parall_traffic(void* ptr); int main(int argc, char* argv[]) { int iterations = 1000; Test = new TestConnections(argc, argv); if (Test->smoke) { iterations = 100; } pthread_t parall_traffic1[100]; Test->set_timeout(60); Test->repl->connect(); Test->repl->execute_query_all_nodes((char*) "set global max_connect_errors=1000;"); Test->repl->execute_query_all_nodes((char*) "set global max_connections=1000;"); Test->maxscales->connect_maxscale(0); Test->tprintf("Creating one user 'user@%%'"); execute_query_silent(Test->maxscales->conn_rwsplit[0], (char*) "DROP USER user@'%'"); Test->try_query(Test->maxscales->conn_rwsplit[0], (char*) "CREATE USER user@'%%' identified by 'pass2'"); Test->try_query(Test->maxscales->conn_rwsplit[0], (char*) "GRANT SELECT ON test.* TO user@'%%';"); Test->try_query(Test->maxscales->conn_rwsplit[0], (char*) "FLUSH PRIVILEGES;"); Test->tprintf("Starting parallel thread which opens/closes session in the loop"); for (int j = 0; j < 25; j++) { pthread_create(¶ll_traffic1[j], NULL, parall_traffic, NULL); } Test->tprintf("Doing change_user in the loop"); for (int i = 0; i < iterations; i++) { Test->set_timeout(15); Test->add_result(mysql_change_user(Test->maxscales->conn_rwsplit[0], "user", "pass2", (char*) "test"), "change_user failed! %s", mysql_error(Test->maxscales->conn_rwsplit[0])); Test->add_result(mysql_change_user(Test->maxscales->conn_rwsplit[0], Test->maxscales->user_name, Test->maxscales->password, (char*) "test"), "change_user failed! %s", mysql_error(Test->maxscales->conn_rwsplit[0])); } Test->tprintf("Waiting for all threads to finish"); exit_flag = 1; for (int j = 0; j < 25; j++) { Test->set_timeout(30); pthread_join(parall_traffic1[j], NULL); } Test->tprintf("All threads are finished"); Test->repl->flush_hosts(); Test->tprintf("Change user to '%s' in order to be able to DROP user", Test->maxscales->user_name); Test->set_timeout(30); mysql_change_user(Test->maxscales->conn_rwsplit[0], Test->maxscales->user_name, Test->maxscales->password, NULL); Test->tprintf("Dropping user", Test->maxscales->user_name); Test->try_query(Test->maxscales->conn_rwsplit[0], (char*) "DROP USER user@'%%';"); Test->check_maxscale_alive(0); int rval = Test->global_result; delete Test; return rval; } void* parall_traffic(void* ptr) { MYSQL* conn; while (exit_flag == 0) { conn = Test->maxscales->open_rwsplit_connection(0); mysql_close(conn); if (Test->backend_ssl) { sleep(1); } } return NULL; }