From 5af70decca3f89ebf121ed70cf2ac3c12c285839 Mon Sep 17 00:00:00 2001 From: Yuchen Pei Date: Wed, 13 Sep 2023 11:49:57 +1000 Subject: [PATCH] MDEV-15935 Adding global/session system var redirect_url Adding a global/session var `redirect_url' of string type. The initial value is empty. Can be supplied in mysqld with --redirect-url or set in --init-connect. A valid redirect_url should be of the format {mysql,mariadb}://host[:port] where is an arbitrary string not containing colons, and is a number between 0 and 65535 inclusive. The variable will be used by the server to notify clients that they should connect to another server, specified by the value of the variable, if not empty. The notification is done by the inclusion of the variable in session_track_system_variable. --- mysql-test/main/mysqld--help.result | 5 +- mysql-test/suite/sys_vars/r/mdev_32254.result | 13 +++ mysql-test/suite/sys_vars/r/redirect.result | 81 +++++++++++++++++++ ...ession_track_system_variables_basic.result | 22 ++--- .../r/sysvars_server_notembedded.result | 10 +++ mysql-test/suite/sys_vars/t/mdev_32254.test | 17 ++++ mysql-test/suite/sys_vars/t/redirect.opt | 1 + mysql-test/suite/sys_vars/t/redirect.test | 74 +++++++++++++++++ sql/sql_class.h | 1 + sql/sql_plugin.cc | 9 +++ sql/sys_vars.cc | 62 +++++++++++++- sql/sys_vars.inl | 7 +- 12 files changed, 286 insertions(+), 16 deletions(-) create mode 100644 mysql-test/suite/sys_vars/r/mdev_32254.result create mode 100644 mysql-test/suite/sys_vars/r/redirect.result create mode 100644 mysql-test/suite/sys_vars/t/mdev_32254.test create mode 100644 mysql-test/suite/sys_vars/t/redirect.opt create mode 100644 mysql-test/suite/sys_vars/t/redirect.test diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result index 77d6d10b3e6..ac96220a805 100644 --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result @@ -1064,6 +1064,8 @@ The following specify which files/extra groups are read (specified before remain --read-rnd-buffer-size=# When reading rows in sorted order after a sort, the rows are read through this buffer to avoid a disk seeks + --redirect-url=name URL of another server to redirect clients to. Empty + string means no redirection --relay-log=name The location and name to use for relay logs. --relay-log-index=name The location and name to use for the file that keeps a @@ -1838,6 +1840,7 @@ read-binlog-speed-limit 0 read-buffer-size 131072 read-only FALSE read-rnd-buffer-size 262144 +redirect-url relay-log (No default value) relay-log-index (No default value) relay-log-info-file relay-log.info @@ -1869,7 +1872,7 @@ secure-timestamp NO server-id 1 session-track-schema TRUE session-track-state-change FALSE -session-track-system-variables autocommit,character_set_client,character_set_connection,character_set_results,time_zone +session-track-system-variables autocommit,character_set_client,character_set_connection,character_set_results,redirect_url,time_zone session-track-transaction-info OFF show-slave-auth-info FALSE silent-startup FALSE diff --git a/mysql-test/suite/sys_vars/r/mdev_32254.result b/mysql-test/suite/sys_vars/r/mdev_32254.result new file mode 100644 index 00000000000..5188c56e22f --- /dev/null +++ b/mysql-test/suite/sys_vars/r/mdev_32254.result @@ -0,0 +1,13 @@ +# +# MDEV-32254 Server crashes when adding records to table after setting redirect_url with empty variable +# +set @old_redirect_url=@@global.redirect_url; +set global redirect_url=@empty_value; +ERROR 42000: Variable 'redirect_url' can't be set to the value of 'NULL' +CREATE TABLE t (c1 INT) ENGINE=INNODB; +INSERT INTO t VALUES (1),(1); +drop table t; +set global redirect_url=@old_redirect_url; +# +# end of test mdev_32254 +# diff --git a/mysql-test/suite/sys_vars/r/redirect.result b/mysql-test/suite/sys_vars/r/redirect.result new file mode 100644 index 00000000000..7d704de3bad --- /dev/null +++ b/mysql-test/suite/sys_vars/r/redirect.result @@ -0,0 +1,81 @@ +# +# MDEV-15935 Connection Redirection Mechanism in MariaDB Client/Server Protocol +# +connect con,localhost,anyone_but_root; +select @@redirect_url; +@@redirect_url +mysql://foobar +connection default; +set @old_global_redirect_url=@@global.redirect_url; +set @old_session_redirect_url=@@session.redirect_url; +set @old_session_track_system_variables=@@session_track_system_variables; +set session_track_system_variables=""; +select @@global.redirect_url; +@@global.redirect_url + +set global redirect_url=default; +select @@global.redirect_url; +@@global.redirect_url + +set global redirect_url="mariadb.org"; +ERROR 42000: Variable 'redirect_url' can't be set to the value of 'mariadb.org' +set global redirect_url="https://mariadb.org"; +ERROR 42000: Variable 'redirect_url' can't be set to the value of 'https://mariadb.org' +set global redirect_url="mysql://mariadb.org:"; +ERROR 42000: Variable 'redirect_url' can't be set to the value of 'mysql://mariadb.org:' +set global redirect_url="mysql://mariadb.org:hello"; +ERROR 42000: Variable 'redirect_url' can't be set to the value of 'mysql://mariadb.org:hello' +set global redirect_url="mysql://"; +ERROR 42000: Variable 'redirect_url' can't be set to the value of 'mysql://' +set global redirect_url="mysql://mariadb.org"; +select @@global.redirect_url; +@@global.redirect_url +mysql://mariadb.org +set global redirect_url="mysql://mariadb.org:12a"; +ERROR 42000: Variable 'redirect_url' can't be set to the value of 'mysql://mariadb.org:12a' +set global redirect_url="mysql://mariadb.org:66666"; +ERROR 42000: Variable 'redirect_url' can't be set to the value of 'mysql://mariadb.org:66666' +set global redirect_url="mysql://mariadb.org:12345"; +select @@global.redirect_url; +@@global.redirect_url +mysql://mariadb.org:12345 +set global redirect_url="maria"; +ERROR 42000: Variable 'redirect_url' can't be set to the value of 'maria' +set global redirect_url="mariadb://mariadb.org:"; +ERROR 42000: Variable 'redirect_url' can't be set to the value of 'mariadb://mariadb.org:' +set global redirect_url="mariadb://mariadb.org:hello"; +ERROR 42000: Variable 'redirect_url' can't be set to the value of 'mariadb://mariadb.org:hello' +set global redirect_url="mariadb://"; +ERROR 42000: Variable 'redirect_url' can't be set to the value of 'mariadb://' +set global redirect_url="mariadb://mariadb.org"; +select @@global.redirect_url; +@@global.redirect_url +mariadb://mariadb.org +set global redirect_url="mariadb://mariadb.org:12a"; +ERROR 42000: Variable 'redirect_url' can't be set to the value of 'mariadb://mariadb.org:12a' +set global redirect_url="mariadb://mariadb.org:66666"; +ERROR 42000: Variable 'redirect_url' can't be set to the value of 'mariadb://mariadb.org:66666' +set global redirect_url="mariadb://mariadb.org:12345"; +select @@global.redirect_url; +@@global.redirect_url +mariadb://mariadb.org:12345 +select @@session.redirect_url; +@@session.redirect_url + +set session redirect_url=default; +select @@session.redirect_url; +@@session.redirect_url +mariadb://mariadb.org:12345 +set session redirect_url="mysql://localhost"; +select @@session.redirect_url; +@@session.redirect_url +mysql://localhost +select @@global.redirect_url; +@@global.redirect_url +mariadb://mariadb.org:12345 +set global redirect_url=@old_global_redirect_url; +set session redirect_url=@old_session_redirect_url; +set session session_track_system_variables=@old_session_track_system_variables; +# +# end of test MDEV-15935 +# diff --git a/mysql-test/suite/sys_vars/r/session_track_system_variables_basic.result b/mysql-test/suite/sys_vars/r/session_track_system_variables_basic.result index c3f4a4ee4eb..4208a2e3540 100644 --- a/mysql-test/suite/sys_vars/r/session_track_system_variables_basic.result +++ b/mysql-test/suite/sys_vars/r/session_track_system_variables_basic.result @@ -5,20 +5,20 @@ # Global - default SELECT @@global.session_track_system_variables; @@global.session_track_system_variables -autocommit,character_set_client,character_set_connection,character_set_results,time_zone +autocommit,character_set_client,character_set_connection,character_set_results,redirect_url,time_zone # Session - default SELECT @@session.session_track_system_variables; @@session.session_track_system_variables -autocommit,character_set_client,character_set_connection,character_set_results,time_zone +autocommit,character_set_client,character_set_connection,character_set_results,redirect_url,time_zone # via INFORMATION_SCHEMA.GLOBAL_VARIABLES SELECT * FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME LIKE 'session_track_system_variables' ORDER BY VARIABLE_NAME; VARIABLE_NAME VARIABLE_VALUE -SESSION_TRACK_SYSTEM_VARIABLES autocommit,character_set_client,character_set_connection,character_set_results,time_zone +SESSION_TRACK_SYSTEM_VARIABLES autocommit,character_set_client,character_set_connection,character_set_results,redirect_url,time_zone # via INFORMATION_SCHEMA.SESSION_VARIABLES SELECT * FROM INFORMATION_SCHEMA.SESSION_VARIABLES WHERE VARIABLE_NAME LIKE 'session_track_system_variables' ORDER BY VARIABLE_NAME; VARIABLE_NAME VARIABLE_VALUE -SESSION_TRACK_SYSTEM_VARIABLES autocommit,character_set_client,character_set_connection,character_set_results,time_zone +SESSION_TRACK_SYSTEM_VARIABLES autocommit,character_set_client,character_set_connection,character_set_results,redirect_url,time_zone SET @global_saved_tmp = @@global.session_track_system_variables; # Altering global variable's value @@ -28,7 +28,7 @@ SELECT @@global.session_track_system_variables; autocommit SELECT @@session.session_track_system_variables; @@session.session_track_system_variables -autocommit,character_set_client,character_set_connection,character_set_results,time_zone +autocommit,character_set_client,character_set_connection,character_set_results,redirect_url,time_zone # Altering session variable's value SET @@session.session_track_system_variables='autocommit'; @@ -66,25 +66,25 @@ SET @@session.session_track_system_variables = DEFAULT; SELECT @@global.session_track_system_variables; @@global.session_track_system_variables -autocommit,character_set_client,character_set_connection,character_set_results,time_zone +autocommit,character_set_client,character_set_connection,character_set_results,redirect_url,time_zone SELECT @@session.session_track_system_variables; @@session.session_track_system_variables -autocommit,character_set_client,character_set_connection,character_set_results,time_zone +autocommit,character_set_client,character_set_connection,character_set_results,redirect_url,time_zone # Variables' values in a new session (con2). connect con2,"127.0.0.1",root,,test,$MASTER_MYPORT,; SELECT @@global.session_track_system_variables; @@global.session_track_system_variables -autocommit,character_set_client,character_set_connection,character_set_results,time_zone +autocommit,character_set_client,character_set_connection,character_set_results,redirect_url,time_zone SELECT @@session.session_track_system_variables; @@session.session_track_system_variables -autocommit,character_set_client,character_set_connection,character_set_results,time_zone +autocommit,character_set_client,character_set_connection,character_set_results,redirect_url,time_zone # Altering session should not affect global. SET @@session.session_track_system_variables = 'sql_mode'; SELECT @@global.session_track_system_variables; @@global.session_track_system_variables -autocommit,character_set_client,character_set_connection,character_set_results,time_zone +autocommit,character_set_client,character_set_connection,character_set_results,redirect_url,time_zone SELECT @@session.session_track_system_variables; @@session.session_track_system_variables sql_mode @@ -98,7 +98,7 @@ SELECT @@global.session_track_system_variables; sql_mode SELECT @@session.session_track_system_variables; @@session.session_track_system_variables -autocommit,character_set_client,character_set_connection,character_set_results,time_zone +autocommit,character_set_client,character_set_connection,character_set_results,redirect_url,time_zone # Switching to the default connection. connection default; diff --git a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result index 1fb6b245ace..c8087432b88 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result +++ b/mysql-test/suite/sys_vars/r/sysvars_server_notembedded.result @@ -3412,6 +3412,16 @@ NUMERIC_BLOCK_SIZE 1 ENUM_VALUE_LIST NULL READ_ONLY NO COMMAND_LINE_ARGUMENT REQUIRED +VARIABLE_NAME REDIRECT_URL +VARIABLE_SCOPE SESSION +VARIABLE_TYPE VARCHAR +VARIABLE_COMMENT URL of another server to redirect clients to. Empty string means no redirection +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL +ENUM_VALUE_LIST NULL +READ_ONLY NO +COMMAND_LINE_ARGUMENT REQUIRED VARIABLE_NAME RELAY_LOG VARIABLE_SCOPE GLOBAL VARIABLE_TYPE VARCHAR diff --git a/mysql-test/suite/sys_vars/t/mdev_32254.test b/mysql-test/suite/sys_vars/t/mdev_32254.test new file mode 100644 index 00000000000..d907806f430 --- /dev/null +++ b/mysql-test/suite/sys_vars/t/mdev_32254.test @@ -0,0 +1,17 @@ +--echo # +--echo # MDEV-32254 Server crashes when adding records to table after setting redirect_url with empty variable +--echo # +--source include/have_innodb.inc +# redirect_url is undefined in embedded. +--source include/not_embedded.inc +set @old_redirect_url=@@global.redirect_url; +--error ER_WRONG_VALUE_FOR_VAR +set global redirect_url=@empty_value; +CREATE TABLE t (c1 INT) ENGINE=INNODB; +INSERT INTO t VALUES (1),(1); +drop table t; +set global redirect_url=@old_redirect_url; + +--echo # +--echo # end of test mdev_32254 +--echo # diff --git a/mysql-test/suite/sys_vars/t/redirect.opt b/mysql-test/suite/sys_vars/t/redirect.opt new file mode 100644 index 00000000000..4cf52749d3a --- /dev/null +++ b/mysql-test/suite/sys_vars/t/redirect.opt @@ -0,0 +1 @@ +--init-connect="set redirect_url='mysql://foobar'" diff --git a/mysql-test/suite/sys_vars/t/redirect.test b/mysql-test/suite/sys_vars/t/redirect.test new file mode 100644 index 00000000000..5a0cb442737 --- /dev/null +++ b/mysql-test/suite/sys_vars/t/redirect.test @@ -0,0 +1,74 @@ +--echo # +--echo # MDEV-15935 Connection Redirection Mechanism in MariaDB Client/Server Protocol +--echo # +# redirect_url is undefined in embedded. +--source include/not_embedded.inc + +# We need to connect as a non super user for the init-connect to take +# effect +--source include/add_anonymous_users.inc +connect (con,localhost,anyone_but_root); +select @@redirect_url; + +connection default; +--source include/delete_anonymous_users.inc +set @old_global_redirect_url=@@global.redirect_url; +set @old_session_redirect_url=@@session.redirect_url; +set @old_session_track_system_variables=@@session_track_system_variables; +set session_track_system_variables=""; + +select @@global.redirect_url; +set global redirect_url=default; +select @@global.redirect_url; +--error ER_WRONG_VALUE_FOR_VAR +set global redirect_url="mariadb.org"; +--error ER_WRONG_VALUE_FOR_VAR +set global redirect_url="https://mariadb.org"; + +--error ER_WRONG_VALUE_FOR_VAR +set global redirect_url="mysql://mariadb.org:"; +--error ER_WRONG_VALUE_FOR_VAR +set global redirect_url="mysql://mariadb.org:hello"; +--error ER_WRONG_VALUE_FOR_VAR +set global redirect_url="mysql://"; +set global redirect_url="mysql://mariadb.org"; +select @@global.redirect_url; +--error ER_WRONG_VALUE_FOR_VAR +set global redirect_url="mysql://mariadb.org:12a"; +--error ER_WRONG_VALUE_FOR_VAR +set global redirect_url="mysql://mariadb.org:66666"; +set global redirect_url="mysql://mariadb.org:12345"; +select @@global.redirect_url; + +--error ER_WRONG_VALUE_FOR_VAR +set global redirect_url="maria"; +--error ER_WRONG_VALUE_FOR_VAR +set global redirect_url="mariadb://mariadb.org:"; +--error ER_WRONG_VALUE_FOR_VAR +set global redirect_url="mariadb://mariadb.org:hello"; +--error ER_WRONG_VALUE_FOR_VAR +set global redirect_url="mariadb://"; +set global redirect_url="mariadb://mariadb.org"; +select @@global.redirect_url; +--error ER_WRONG_VALUE_FOR_VAR +set global redirect_url="mariadb://mariadb.org:12a"; +--error ER_WRONG_VALUE_FOR_VAR +set global redirect_url="mariadb://mariadb.org:66666"; +set global redirect_url="mariadb://mariadb.org:12345"; +select @@global.redirect_url; + +select @@session.redirect_url; +# Test that session default is global value +set session redirect_url=default; +select @@session.redirect_url; +set session redirect_url="mysql://localhost"; +select @@session.redirect_url; +select @@global.redirect_url; + +set global redirect_url=@old_global_redirect_url; +set session redirect_url=@old_session_redirect_url; +set session session_track_system_variables=@old_session_track_system_variables; + +--echo # +--echo # end of test MDEV-15935 +--echo # diff --git a/sql/sql_class.h b/sql/sql_class.h index 953fa9d4cfa..b28a1790030 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -883,6 +883,7 @@ typedef struct system_variables Time_zone *time_zone; char *session_track_system_variables; + char *redirect_url; /* Some wsrep variables */ ulonglong wsrep_trx_fragment_size; diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index 5343c01ecdb..8bab244c709 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -3278,6 +3278,8 @@ void plugin_thdvar_init(THD *thd) /* This and all other variable cleanups are here for COM_CHANGE_USER :( */ #ifndef EMBEDDED_LIBRARY thd->session_tracker.sysvars.deinit(thd); + my_free(thd->variables.redirect_url); + thd->variables.redirect_url= 0; #endif my_free((char*) thd->variables.default_master_connection.str); thd->variables.default_master_connection.str= 0; @@ -3311,7 +3313,12 @@ void plugin_thdvar_init(THD *thd) MYF(MY_WME | MY_THREAD_SPECIFIC)); #ifndef EMBEDDED_LIBRARY thd->session_tracker.sysvars.init(thd); + thd->variables.redirect_url= + my_strdup(key_memory_Sys_var_charptr_value, + global_system_variables.redirect_url, + MYF(MY_WME | MY_THREAD_SPECIFIC)); #endif + DBUG_VOID_RETURN; } @@ -3379,6 +3386,8 @@ void plugin_thdvar_cleanup(THD *thd) #ifndef EMBEDDED_LIBRARY thd->session_tracker.sysvars.deinit(thd); + my_free(thd->variables.redirect_url); + thd->variables.redirect_url= 0; #endif my_free((char*) thd->variables.default_master_connection.str); thd->variables.default_master_connection.str= 0; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 5eb11762c16..515dbb95886 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -6894,13 +6894,73 @@ static Sys_var_ulonglong Sys_max_session_mem_used( DEFAULT(LONGLONG_MAX), BLOCK_SIZE(1)); #ifndef EMBEDDED_LIBRARY +/** + Validate a redirect_url string. + + A valid string is either empty, or of the format mysql://host[:port], + where host is an arbitrary string without any colon ':'. + + @param str A string to validate + @param len Length of the string + @retval false The string is valid + @retval true The string is invalid +*/ +static bool sysvar_validate_redirect_url(sys_var *self, THD *thd, + set_var *var) +{ + /* NULL is invalid. */ + if (check_not_null(self, thd, var)) + return true; + char *str= var->save_result.string_value.str; + size_t len= var->save_result.string_value.length; + LEX_CSTRING mysql_prefix= {STRING_WITH_LEN("mysql://")}; + LEX_CSTRING maria_prefix= {STRING_WITH_LEN("mariadb://")}; + /* Empty string is valid */ + if (len == 0) + return false; + const char* end= str + len; + if (!strncmp(str, mysql_prefix.str, mysql_prefix.length)) + str+= mysql_prefix.length; + else if (!strncmp(str, maria_prefix.str, maria_prefix.length)) + str+= maria_prefix.length; + else + return true; + /* Host name cannot be empty */ + if (str == end) + return true; + /* Find the colon, if any */ + while (str < end && *str != ':') + str++; + /* Found colon */ + if (str < end) + { + /* Should have at least one number after the colon */ + if (str + 1 == end) + return true; + int p= 0; + while (str < end && isdigit(*++str)) + if ((p= p * 10 + (*str - '0')) > 65535) + return true; + /* Should be all numbers after the colon */ + if (str < end) + return true; + } + return false; +} + +static Sys_var_charptr Sys_redirect_url( + "redirect_url", + "URL of another server to redirect clients to. " + "Empty string means no redirection", + SESSION_VAR(redirect_url), CMD_LINE(REQUIRED_ARG), DEFAULT(""), + NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(sysvar_validate_redirect_url)); static Sys_var_sesvartrack Sys_track_session_sys_vars( "session_track_system_variables", "Track changes in registered system variables. ", CMD_LINE(REQUIRED_ARG), DEFAULT("autocommit,character_set_client,character_set_connection," - "character_set_results,time_zone")); + "character_set_results,redirect_url,time_zone")); static bool update_session_track_schema(sys_var *self, THD *thd, enum_var_type type) diff --git a/sql/sys_vars.inl b/sql/sys_vars.inl index e5e47215643..7b538c89f06 100644 --- a/sql/sys_vars.inl +++ b/sql/sys_vars.inl @@ -495,9 +495,10 @@ public: Note that the memory management for SESSION_VAR's is manual, the value must be strdup'ed in THD::init() and freed in - plugin_thdvar_cleanup(). TODO: it should be done automatically when - we'll have more session string variables to justify it. Maybe some - kind of a loop over all variables, like sys_var_end() in set_var.cc? + plugin_thdvar_cleanup(), see e.g. redirect_url. TODO: it should be + done automatically when we'll have more session string variables to + justify it. Maybe some kind of a loop over all variables, like + sys_var_end() in set_var.cc? */ class Sys_var_charptr: public sys_var {