diff --git a/mysql-test/suite/galera/r/galera_defaults.result b/mysql-test/suite/galera/r/galera_defaults.result index 04f45a7c770..e32557f578e 100644 --- a/mysql-test/suite/galera/r/galera_defaults.result +++ b/mysql-test/suite/galera/r/galera_defaults.result @@ -20,6 +20,7 @@ AND VARIABLE_NAME NOT IN ( ) ORDER BY VARIABLE_NAME; VARIABLE_NAME VARIABLE_VALUE +WSREP_ALLOWLIST WSREP_AUTO_INCREMENT_CONTROL ON WSREP_CAUSAL_READS ON WSREP_CERTIFICATION_RULES strict diff --git a/mysql-test/suite/galera/t/galera_defaults.test b/mysql-test/suite/galera/t/galera_defaults.test index 6b76473d6a6..ff08151327a 100644 --- a/mysql-test/suite/galera/t/galera_defaults.test +++ b/mysql-test/suite/galera/t/galera_defaults.test @@ -13,13 +13,11 @@ --source include/force_restart.inc # Make sure that the test is operating on the right version of galera library. ---let $galera_version=26.4.7 +--let $galera_version=26.4.11 source ../wsrep/include/check_galera_version.inc; # Global Variables -SELECT COUNT(*) `expect 50` FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME LIKE 'wsrep_%'; - SELECT VARIABLE_NAME, VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_VARIABLES WHERE VARIABLE_NAME LIKE 'wsrep_%' diff --git a/mysql-test/suite/galera_3nodes/r/galera_allowlist.result b/mysql-test/suite/galera_3nodes/r/galera_allowlist.result new file mode 100644 index 00000000000..471444d8c08 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/r/galera_allowlist.result @@ -0,0 +1,35 @@ +connection node_2; +connection node_1; +SELECT COUNT(*) = 3 FROM mysql.wsrep_allowlist; +COUNT(*) = 3 +1 +connection node_2; +SELECT COUNT(*) = 3 FROM mysql.wsrep_allowlist; +COUNT(*) = 3 +1 +connection node_3; +SET @@global.wsrep_desync = 1; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_provider_options = 'gmcast.isolate=1'; +connection node_1; +DELETE FROM mysql.wsrep_allowlist WHERE ip LIKE '127.0.0.3'; +SELECT COUNT(*) = 2 FROM mysql.wsrep_allowlist; +COUNT(*) = 2 +1 +connection node_2; +SELECT COUNT(*) = 2 FROM mysql.wsrep_allowlist; +COUNT(*) = 2 +1 +connection node_3; +SET GLOBAL wsrep_provider_options = 'gmcast.isolate=0'; +SET @@global.wsrep_desync = 0; +connection node_1; +INSERT INTO mysql.wsrep_allowlist(ip) VALUES ('127.0.0.3'); +connection node_3; +# restart +connection node_1; +CALL mtr.add_suppression('WSREP: Connection not allowed'); +connection node_2; +CALL mtr.add_suppression('WSREP: Connection not allowed'); +connection node_3; +CALL mtr.add_suppression('WSREP: Ignoring lack of quorum'); diff --git a/mysql-test/suite/galera_3nodes/t/galera_allowlist.cnf b/mysql-test/suite/galera_3nodes/t/galera_allowlist.cnf new file mode 100644 index 00000000000..62f24c172af --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/galera_allowlist.cnf @@ -0,0 +1,26 @@ +!include ../galera_3nodes.cnf + +[mysqld] +wsrep_sst_method=rsync + +[mysqld.1] +wsrep_allowlist="127.0.0.1,127.0.0.2,127.0.0.3" + +[mysqld.2] +wsrep_provider_options='repl.causal_read_timeout=PT90S;base_port=@mysqld.2.#galera_port;gmcast.listen_addr=127.0.0.2;evs.suspect_timeout=PT10S;evs.inactive_timeout=PT30S;evs.install_timeout=PT15S' + +# Variable is only used on bootstrap node, so this will be ignored +wsrep_allowlist="127.0.0.1,127.0.0.2,127.0.0.3,127.0.0.4,127.0.0.5" + +wsrep_node_address=127.0.0.2 +wsrep_sst_receive_address=127.0.0.2:@mysqld.2.#sst_port +wsrep_node_incoming_address=127.0.0.2:@mysqld.2.port +wsrep_sst_receive_address='127.0.0.2:@mysqld.2.#sst_port' + +[mysqld.3] +wsrep_provider_options='repl.causal_read_timeout=PT90S;base_port=@mysqld.3.#galera_port;gmcast.listen_addr=127.0.0.3;evs.suspect_timeout=PT10S;evs.inactive_timeout=PT30S;evs.install_timeout=PT15S;pc.ignore_quorum=TRUE;pc.wait_prim=FALSE' + +wsrep_node_address=127.0.0.3 +wsrep_sst_receive_address=127.0.0.3:@mysqld.3.#sst_port +wsrep_node_incoming_address=127.0.0.3:@mysqld.3.port +wsrep_sst_receive_address='127.0.0.3:@mysqld.3.#sst_port' \ No newline at end of file diff --git a/mysql-test/suite/galera_3nodes/t/galera_allowlist.test b/mysql-test/suite/galera_3nodes/t/galera_allowlist.test new file mode 100644 index 00000000000..74fff61c4f8 --- /dev/null +++ b/mysql-test/suite/galera_3nodes/t/galera_allowlist.test @@ -0,0 +1,66 @@ +--source include/galera_cluster.inc +--source include/have_innodb.inc + +# Check that `wsrep_allowlist` variable is loaded +SELECT COUNT(*) = 3 FROM mysql.wsrep_allowlist; + +--connection node_2 +# Check that non-bootstrap nodes doesn't populate `mysql.wsrep_allowlist` +SELECT COUNT(*) = 3 FROM mysql.wsrep_allowlist; + +--let $galera_connection_name = node_3 +--let $galera_server_number = 3 +--source include/galera_connect.inc + +--connection node_3 +# Desync and disconnect node 3 from the PC: +SET @@global.wsrep_desync = 1; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_provider_options = 'gmcast.isolate=1'; + +--connection node_1 +# Wait until node 3 disappears from the PC: +--let $wait_condition = SELECT VARIABLE_VALUE = 2 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +--source include/wait_condition.inc + +# Delete node ip (127.0.0.3) from allowlist +DELETE FROM mysql.wsrep_allowlist WHERE ip LIKE '127.0.0.3'; + +SELECT COUNT(*) = 2 FROM mysql.wsrep_allowlist; + +--connection node_2 +SELECT COUNT(*) = 2 FROM mysql.wsrep_allowlist; + +--connection node_3 +# Reconnect node 2 to the PC: +SET GLOBAL wsrep_provider_options = 'gmcast.isolate=0'; + +# We should reach Primary with cluster size = 1 because of `pc.ignore_quorum=TRUE and pc.wait_prim=FALSE` used in configuration +--let $wait_condition = SELECT VARIABLE_VALUE = 1 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +--source include/wait_condition.inc + +# Resync should pass: +SET @@global.wsrep_desync = 0; + +# Shutdown node +--source include/shutdown_mysqld.inc + +--connection node_1 +# Allow node 3 could be reconnected to cluster +INSERT INTO mysql.wsrep_allowlist(ip) VALUES ('127.0.0.3'); + +--connection node_3 +--source include/start_mysqld.inc +--source include/wait_until_connected_again.inc + +--connection node_1 +--let $wait_condition = SELECT VARIABLE_VALUE = 3 FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_cluster_size'; +--source include/wait_condition.inc + +CALL mtr.add_suppression('WSREP: Connection not allowed'); + +--connection node_2 +CALL mtr.add_suppression('WSREP: Connection not allowed'); + +--connection node_3 +CALL mtr.add_suppression('WSREP: Ignoring lack of quorum'); diff --git a/mysql-test/suite/sys_vars/r/sysvars_wsrep.result b/mysql-test/suite/sys_vars/r/sysvars_wsrep.result index 693af55ed66..305c0e88e5e 100644 --- a/mysql-test/suite/sys_vars/r/sysvars_wsrep.result +++ b/mysql-test/suite/sys_vars/r/sysvars_wsrep.result @@ -1,6 +1,21 @@ select * from information_schema.system_variables where variable_name like 'wsrep%' order by variable_name; +VARIABLE_NAME WSREP_ALLOWLIST +SESSION_VALUE NULL +GLOBAL_VALUE +GLOBAL_VALUE_ORIGIN COMPILE-TIME +DEFAULT_VALUE +VARIABLE_SCOPE GLOBAL +VARIABLE_TYPE VARCHAR +VARIABLE_COMMENT Allowed IP addresses split by comma delimiter +NUMERIC_MIN_VALUE NULL +NUMERIC_MAX_VALUE NULL +NUMERIC_BLOCK_SIZE NULL +ENUM_VALUE_LIST NULL +READ_ONLY YES +COMMAND_LINE_ARGUMENT REQUIRED +GLOBAL_VALUE_PATH NULL VARIABLE_NAME WSREP_AUTO_INCREMENT_CONTROL SESSION_VALUE NULL GLOBAL_VALUE ON diff --git a/mysql-test/suite/wsrep/r/variables.result b/mysql-test/suite/wsrep/r/variables.result index 8df0210b2d1..2d5d5a66232 100644 --- a/mysql-test/suite/wsrep/r/variables.result +++ b/mysql-test/suite/wsrep/r/variables.result @@ -88,6 +88,7 @@ wsrep_thread_count 2 # variables SELECT VARIABLE_NAME FROM INFORMATION_SCHEMA.SESSION_VARIABLES WHERE VARIABLE_NAME LIKE "wsrep%" ORDER BY VARIABLE_NAME; VARIABLE_NAME +WSREP_ALLOWLIST WSREP_AUTO_INCREMENT_CONTROL WSREP_CAUSAL_READS WSREP_CERTIFICATION_RULES diff --git a/mysql-test/suite/wsrep/r/variables_debug.result b/mysql-test/suite/wsrep/r/variables_debug.result index 109b25cd898..2ce69827911 100644 --- a/mysql-test/suite/wsrep/r/variables_debug.result +++ b/mysql-test/suite/wsrep/r/variables_debug.result @@ -89,6 +89,7 @@ wsrep_thread_count 2 # variables SELECT VARIABLE_NAME FROM INFORMATION_SCHEMA.SESSION_VARIABLES WHERE VARIABLE_NAME LIKE "wsrep%" ORDER BY VARIABLE_NAME; VARIABLE_NAME +WSREP_ALLOWLIST WSREP_AUTO_INCREMENT_CONTROL WSREP_CAUSAL_READS WSREP_CERTIFICATION_RULES diff --git a/mysql-test/suite/wsrep/t/variables.test b/mysql-test/suite/wsrep/t/variables.test index e40ac7b8772..c28638e78f1 100644 --- a/mysql-test/suite/wsrep/t/variables.test +++ b/mysql-test/suite/wsrep/t/variables.test @@ -3,7 +3,7 @@ --source include/have_innodb.inc --source include/galera_no_debug_sync.inc ---let $galera_version=26.4.9 +--let $galera_version=26.4.11 source include/check_galera_version.inc; source include/galera_variables_ok.inc; diff --git a/mysql-test/suite/wsrep/t/variables_debug.test b/mysql-test/suite/wsrep/t/variables_debug.test index 29747e48f18..5e90d61c84e 100644 --- a/mysql-test/suite/wsrep/t/variables_debug.test +++ b/mysql-test/suite/wsrep/t/variables_debug.test @@ -5,7 +5,7 @@ --source include/have_debug_sync.inc --source include/galera_have_debug_sync.inc ---let $galera_version=26.4.9 +--let $galera_version=26.4.11 source include/check_galera_version.inc; source include/galera_variables_ok.inc; diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 8d83ecb32de..4c52bc2858d 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -23,6 +23,7 @@ IF(WITH_WSREP AND NOT EMBEDDED_LIBRARY) wsrep_storage_service.cc wsrep_server_state.cc wsrep_status.cc + wsrep_allowlist_service.cc wsrep_utils.cc wsrep_xid.cc wsrep_check_opts.cc diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index e5b508c1719..0183fe21591 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -6236,6 +6236,12 @@ static Sys_var_charptr Sys_wsrep_patch_version( READ_ONLY GLOBAL_VAR(wsrep_patch_version_ptr), CMD_LINE_HELP_ONLY, DEFAULT(WSREP_PATCH_VERSION)); + +static Sys_var_charptr Sys_wsrep_allowlist( + "wsrep_allowlist", "Allowed IP addresses split by comma delimiter", + READ_ONLY GLOBAL_VAR(wsrep_allowlist), CMD_LINE(REQUIRED_ARG), + DEFAULT("")); + #endif /* WITH_WSREP */ static bool fix_host_cache_size(sys_var *, THD *, enum_var_type) diff --git a/sql/wsrep_allowlist_service.cc b/sql/wsrep_allowlist_service.cc new file mode 100644 index 00000000000..22b7edfd23b --- /dev/null +++ b/sql/wsrep_allowlist_service.cc @@ -0,0 +1,54 @@ +/* Copyright 2021 Codership Oy + + This program 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 of the License. + + 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "wsrep_allowlist_service.h" + +#include "my_global.h" +#include "wsrep_mysqld.h" +#include "wsrep_priv.h" + +#include +#include +#include + +class Wsrep_allowlist_service : public wsrep::allowlist_service +{ +public: + bool allowlist_cb(wsrep::allowlist_service::allowlist_key key, + const wsrep::const_buffer& value) WSREP_NOEXCEPT override; +}; + +bool Wsrep_allowlist_service::allowlist_cb ( + wsrep::allowlist_service::allowlist_key key, + const wsrep::const_buffer& value) + WSREP_NOEXCEPT +{ + std::string string_value(value.data()); + return (wsrep_schema->allowlist_check(key, string_value)); +} + +std::unique_ptr entrypoint; + +wsrep::allowlist_service* wsrep_allowlist_service_init() +{ + entrypoint = std::unique_ptr(new Wsrep_allowlist_service); + return entrypoint.get(); +} + +void wsrep_allowlist_service_deinit() +{ + entrypoint.reset(); +} + diff --git a/sql/wsrep_allowlist_service.h b/sql/wsrep_allowlist_service.h new file mode 100644 index 00000000000..2d96139b5c6 --- /dev/null +++ b/sql/wsrep_allowlist_service.h @@ -0,0 +1,29 @@ +/* Copyright 2021 Codership Oy + + This program 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 of the License. + + 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/* + Implementation of wsrep provider threads instrumentation. + */ + +#ifndef WSREP_PROVIDER_ALLOWLIST_H +#define WSREP_PROVIDER_ALLOWLIST_H + +#include "wsrep/allowlist_service.hpp" + +wsrep::allowlist_service* wsrep_allowlist_service_init(); + +void wsrep_allowlist_service_deinit(); + +#endif /* WSREP_PROVIDER_ALLOWLIST_H */ diff --git a/sql/wsrep_mysqld.cc b/sql/wsrep_mysqld.cc index a3f875a394e..af41e150256 100644 --- a/sql/wsrep_mysqld.cc +++ b/sql/wsrep_mysqld.cc @@ -84,6 +84,7 @@ const char *wsrep_data_home_dir; const char *wsrep_dbug_option; const char *wsrep_notify_cmd; const char *wsrep_status_file; +const char *wsrep_allowlist; ulong wsrep_debug; // Debug level logging my_bool wsrep_convert_LOCK_to_trx; // Convert locking sessions to trx @@ -454,6 +455,16 @@ void wsrep_init_schema() WSREP_ERROR("Failed to init wsrep schema"); unireg_abort(1); } + // If we are bootstraping new cluster we should + // populate allowlist from variable + if (wsrep_new_cluster) + { + std::vector ip_allowlist; + if (wsrep_split_allowlist(ip_allowlist)) + { + wsrep_schema->store_allowlist(ip_allowlist); + } + } } } @@ -876,10 +887,14 @@ int wsrep_init() if (!wsrep_data_home_dir || strlen(wsrep_data_home_dir) == 0) wsrep_data_home_dir= mysql_real_data_home; - if (Wsrep_server_state::instance().load_provider(wsrep_provider, - wsrep_provider_options)) + Wsrep_server_state::init_provider_services(); + if (Wsrep_server_state::instance().load_provider( + wsrep_provider, + wsrep_provider_options, + Wsrep_server_state::instance().provider_services())) { WSREP_ERROR("Failed to load provider"); + Wsrep_server_state::deinit_provider_services(); return 1; } @@ -893,6 +908,7 @@ int wsrep_init() "supports streaming replication.", wsrep_provider, global_system_variables.wsrep_trx_fragment_size); Wsrep_server_state::instance().unload_provider(); + Wsrep_server_state::deinit_provider_services(); return 1; } @@ -1008,6 +1024,8 @@ void wsrep_deinit(bool free_options) WSREP_DEBUG("wsrep_deinit"); Wsrep_server_state::instance().unload_provider(); + Wsrep_server_state::deinit_provider_services(); + provider_name[0]= '\0'; provider_version[0]= '\0'; provider_vendor[0]= '\0'; @@ -1157,8 +1175,9 @@ bool wsrep_start_replication(const char *wsrep_cluster_address) // --wsrep-new-cluster flag is not used, checking wsrep_cluster_address // it should match gcomm:// only to be considered as bootstrap node. // This logic is used in galera. - if (!wsrep_new_cluster && (strlen(wsrep_cluster_address) == 8) && - !strncmp(wsrep_cluster_address, "gcomm://", 8)) + if (!wsrep_new_cluster && + (strlen(wsrep_cluster_address) == 8) && + !strncmp(wsrep_cluster_address, "gcomm://", 8)) { wsrep_new_cluster= true; } @@ -1789,6 +1808,34 @@ bool wsrep_reload_ssl() } } +bool wsrep_split_allowlist(std::vector& allowlist) +{ + if (!wsrep_allowlist || 0 == strlen(wsrep_allowlist)) + { + return false; + } + std::istringstream ss{wsrep_allowlist}; + std::string token; + while (std::getline(ss, token, ',')) + { + if (!token.empty()) + { + struct sockaddr_in sa_4; + struct sockaddr_in6 sa_6; + if ((inet_pton(AF_INET, token.c_str(), &(sa_4.sin_addr)) != 0) || + (inet_pton(AF_INET6, token.c_str(), &(sa_6.sin6_addr)) != 0)) + { + allowlist.push_back(token); + } + else + { + WSREP_WARN("Invalid IP address %s provided in `wsrep_allowlist` variable", token.c_str()); + } + } + } + return allowlist.size(); +} + /*! * @param db Database string * @param table Table string @@ -3324,7 +3371,6 @@ void wsrep_wait_appliers_close(THD *thd) is also applier, we are still running... */ } - int wsrep_must_ignore_error(THD* thd) { const int error= thd->get_stmt_da()->sql_errno(); diff --git a/sql/wsrep_mysqld.h b/sql/wsrep_mysqld.h index 6d01dc09bfb..dabf1cb9701 100644 --- a/sql/wsrep_mysqld.h +++ b/sql/wsrep_mysqld.h @@ -80,6 +80,7 @@ extern ulong wsrep_max_ws_size; extern ulong wsrep_max_ws_rows; extern const char* wsrep_notify_cmd; extern const char* wsrep_status_file; +extern const char* wsrep_allowlist; extern my_bool wsrep_certify_nonPK; extern long int wsrep_protocol_version; extern ulong wsrep_forced_binlog_format; @@ -233,6 +234,7 @@ extern int wsrep_check_opts(); extern void wsrep_prepend_PATH (const char* path); extern bool wsrep_append_fk_parent_table(THD* thd, TABLE_LIST* table, wsrep::key_array* keys); extern bool wsrep_reload_ssl(); +extern bool wsrep_split_allowlist(std::vector& allowlist); /* Other global variables */ extern wsrep_seqno_t wsrep_locked_seqno; diff --git a/sql/wsrep_schema.cc b/sql/wsrep_schema.cc index 3d90f425ec8..e62fbd788f4 100644 --- a/sql/wsrep_schema.cc +++ b/sql/wsrep_schema.cc @@ -38,6 +38,7 @@ #define WSREP_STREAMING_TABLE "wsrep_streaming_log" #define WSREP_CLUSTER_TABLE "wsrep_cluster" #define WSREP_MEMBERS_TABLE "wsrep_cluster_members" +#define WSREP_ALLOWLIST_TABLE "wsrep_allowlist" const char* wsrep_sr_table_name_full= WSREP_SCHEMA "/" WSREP_STREAMING_TABLE; @@ -45,6 +46,7 @@ static const std::string wsrep_schema_str= WSREP_SCHEMA; static const std::string sr_table_str= WSREP_STREAMING_TABLE; static const std::string cluster_table_str= WSREP_CLUSTER_TABLE; static const std::string members_table_str= WSREP_MEMBERS_TABLE; +static const std::string allowlist_table_str= WSREP_ALLOWLIST_TABLE; static const std::string create_cluster_table_str= "CREATE TABLE IF NOT EXISTS " + wsrep_schema_str + "." + cluster_table_str + @@ -90,6 +92,13 @@ static const std::string create_frag_table_str= "PRIMARY KEY (node_uuid, trx_id, seqno)" ") ENGINE=InnoDB STATS_PERSISTENT=0"; +static const std::string create_allowlist_table_str= + "CREATE TABLE IF NOT EXISTS " + wsrep_schema_str + "." + allowlist_table_str + + "(" + "ip CHAR(64) NOT NULL," + "PRIMARY KEY (ip)" + ") ENGINE=InnoDB STATS_PERSISTENT=0"; + static const std::string delete_from_cluster_table= "DELETE FROM " + wsrep_schema_str + "." + cluster_table_str; @@ -439,11 +448,18 @@ static int insert(TABLE* table) { } if ((error= table->file->ha_write_row(table->record[0]))) { - WSREP_ERROR("Error writing into %s.%s: %d", - table->s->db.str, - table->s->table_name.str, - error); - ret= 1; + if (error == HA_ERR_FOUND_DUPP_KEY) { + WSREP_WARN("Duplicate key found when writing into %s.%s", + table->s->db.str, + table->s->table_name.str); + ret= HA_ERR_FOUND_DUPP_KEY; + } else { + WSREP_ERROR("Error writing into %s.%s: %d", + table->s->db.str, + table->s->table_name.str, + error); + ret= 1; + } } DBUG_RETURN(ret); @@ -684,6 +700,8 @@ static void wsrep_init_thd_for_schema(THD *thd) wsrep_store_threadvars(thd); } +static bool wsrep_schema_ready= false; + int Wsrep_schema::init() { DBUG_ENTER("Wsrep_schema::init()"); @@ -719,12 +737,16 @@ int Wsrep_schema::init() alter_members_table.size()) || Wsrep_schema_impl::execute_SQL(thd, alter_frag_table.c_str(), - alter_frag_table.size())) + alter_frag_table.size()) || + Wsrep_schema_impl::execute_SQL(thd, + create_allowlist_table_str.c_str(), + create_allowlist_table_str.size())) { ret= 1; } else { + wsrep_schema_ready= true; ret= 0; } @@ -1495,3 +1517,121 @@ int Wsrep_schema::recover_sr_transactions(THD *orig_thd) out: DBUG_RETURN(ret); } + +void Wsrep_schema::store_allowlist(std::vector& ip_allowlist) +{ + THD* thd= new THD(next_thread_id()); + if (!thd) + { + WSREP_ERROR("Unable to get thd"); + return; + } + thd->thread_stack= (char*)&thd; + wsrep_init_thd_for_schema(thd); + TABLE* allowlist_table= 0; + int error; + Wsrep_schema_impl::init_stmt(thd); + if (Wsrep_schema_impl::open_for_write(thd, allowlist_table_str.c_str(), + &allowlist_table)) + { + WSREP_ERROR("Failed to open mysql.wsrep_allowlist table"); + goto out; + } + for (size_t i= 0; i < ip_allowlist.size(); ++i) + { + Wsrep_schema_impl::store(allowlist_table, 0, ip_allowlist[i]); + if ((error= Wsrep_schema_impl::insert(allowlist_table))) + { + if (error == HA_ERR_FOUND_DUPP_KEY) + { + WSREP_WARN("Duplicate entry (%s) found in `wsrep_allowlist` list", ip_allowlist[i].c_str()); + } + else + { + WSREP_ERROR("Failed to write mysql.wsrep_allowlist table: %d", error); + goto out; + } + } + } + Wsrep_schema_impl::finish_stmt(thd); +out: + delete thd; +} + +bool Wsrep_schema::allowlist_check(Wsrep_allowlist_key key, + const std::string& value) +{ + // We don't have wsrep schema initialized at this point + if (wsrep_schema_ready == false) + { + return true; + } + my_thread_init(); + THD *thd = new THD(next_thread_id()); + if (!thd) + { + my_thread_end(); + WSREP_ERROR("Unable to get thd"); + return false; + } + thd->thread_stack= (char*)&thd; + int error; + TABLE *allowlist_table= 0; + bool match_found_or_empty= false; + bool table_have_rows= false; + char row[64]= { 0, }; + wsrep_init_thd_for_schema(thd); + + /* + * Read allowlist table + */ + Wsrep_schema_impl::init_stmt(thd); + if (Wsrep_schema_impl::open_for_read(thd, + allowlist_table_str.c_str(), + &allowlist_table) || + Wsrep_schema_impl::init_for_scan(allowlist_table)) + + { + goto out; + } + while (true) + { + if ((error= Wsrep_schema_impl::next_record(allowlist_table)) == 0) + { + if (Wsrep_schema_impl::scan(allowlist_table, 0, row, sizeof(row))) + { + goto out; + } + table_have_rows= true; + if (!value.compare(row)) + { + match_found_or_empty= true; + break; + } + } + else if (error == HA_ERR_END_OF_FILE) + { + if (!table_have_rows) + { + WSREP_DEBUG("allowlist table empty, allowing all connections."); + // If table is empty we are allowing all connections + match_found_or_empty= true; + } + break; + } + else + { + goto out; + } + } + if (Wsrep_schema_impl::end_scan(allowlist_table)) + { + goto out; + } + Wsrep_schema_impl::finish_stmt(thd); + (void)trans_commit(thd); +out: + delete thd; + my_thread_end(); + return match_found_or_empty; +} diff --git a/sql/wsrep_schema.h b/sql/wsrep_schema.h index 36e23998d19..943fe8759c0 100644 --- a/sql/wsrep_schema.h +++ b/sql/wsrep_schema.h @@ -133,6 +133,22 @@ class Wsrep_schema */ int recover_sr_transactions(THD* orig_thd); + /** + Store allowlist ip on bootstrap from `wsrep_allowlist` variable + */ + void store_allowlist(std::vector& ip_allowlist); + + /** + Scan white list table against accepted connection. Allow if ip + is found in table or if table is empty. + + @param key Which allowlist column to compare + @param value Value to be checked against allowlist + + @return True if found or empty table, false on not found + */ + bool allowlist_check(Wsrep_allowlist_key key, const std::string& val); + private: /* Non-copyable */ Wsrep_schema(const Wsrep_schema&); diff --git a/sql/wsrep_server_state.cc b/sql/wsrep_server_state.cc index 973850871b1..8e5f20c8b96 100644 --- a/sql/wsrep_server_state.cc +++ b/sql/wsrep_server_state.cc @@ -16,6 +16,7 @@ #include "my_global.h" #include "wsrep_api.h" #include "wsrep_server_state.h" +#include "wsrep_allowlist_service.h" #include "wsrep_binlog.h" /* init/deinit group commit */ mysql_mutex_t LOCK_wsrep_server_state; @@ -26,6 +27,8 @@ PSI_mutex_key key_LOCK_wsrep_server_state; PSI_cond_key key_COND_wsrep_server_state; #endif +wsrep::provider::services Wsrep_server_state::m_provider_services; + Wsrep_server_state::Wsrep_server_state(const std::string& name, const std::string& incoming_address, const std::string& address, @@ -74,7 +77,6 @@ void Wsrep_server_state::init_once(const std::string& name, void Wsrep_server_state::destroy() { - if (m_instance) { delete m_instance; @@ -83,3 +85,16 @@ void Wsrep_server_state::destroy() mysql_cond_destroy(&COND_wsrep_server_state); } } + +void Wsrep_server_state::init_provider_services() +{ + m_provider_services.allowlist_service= wsrep_allowlist_service_init(); +} + +void Wsrep_server_state::deinit_provider_services() +{ + if (m_provider_services.allowlist_service) + wsrep_allowlist_service_deinit(); + m_provider_services= wsrep::provider::services(); +} + diff --git a/sql/wsrep_server_state.h b/sql/wsrep_server_state.h index 1ef937300f6..8759f7a9d84 100644 --- a/sql/wsrep_server_state.h +++ b/sql/wsrep_server_state.h @@ -55,6 +55,14 @@ public: { return (get_provider().capabilities() & capability); } + + static void init_provider_services(); + static void deinit_provider_services(); + + static const wsrep::provider::services& provider_services() + { + return m_provider_services; + } private: Wsrep_server_state(const std::string& name, @@ -67,6 +75,7 @@ private: Wsrep_mutex m_mutex; Wsrep_condition_variable m_cond; Wsrep_server_service m_service; + static wsrep::provider::services m_provider_services; static Wsrep_server_state* m_instance; }; diff --git a/sql/wsrep_types.h b/sql/wsrep_types.h index 9da00e305a7..cd53ab95d0c 100644 --- a/sql/wsrep_types.h +++ b/sql/wsrep_types.h @@ -21,9 +21,11 @@ #include "wsrep/seqno.hpp" #include "wsrep/view.hpp" +#include "wsrep/allowlist_service.hpp" typedef wsrep::id Wsrep_id; typedef wsrep::seqno Wsrep_seqno; typedef wsrep::view Wsrep_view; +typedef enum wsrep::allowlist_service::allowlist_key Wsrep_allowlist_key; #endif /* WSREP_TYPES_H */