From 0a336de04841908e68d24fa581ca07c7eae6e7dd Mon Sep 17 00:00:00 2001 From: Wazir Ahmed Date: Mon, 8 Sep 2025 13:36:22 +0530 Subject: [PATCH] Add validation for `LOAD * FROM CONFIG` - Add validation methods for `mysql_users`, `pgsql_users`, `mysql_servers`, `pgsql_servers` and `proxysql_servers` - Check for duplicates and mandatory fields - Return descriptive error messages to clients when validation fails Signed-off-by: Wazir Ahmed Co-authored-by: takaidohigasi --- include/proxysql_config.h | 24 +++- lib/Admin_Bootstrap.cpp | 13 ++- lib/Admin_Handler.cpp | 61 +++++++--- lib/ProxySQL_Config.cpp | 227 +++++++++++++++++++++++++++++--------- 4 files changed, 249 insertions(+), 76 deletions(-) diff --git a/include/proxysql_config.h b/include/proxysql_config.h index cf0d31b3d..6dffec6f6 100644 --- a/include/proxysql_config.h +++ b/include/proxysql_config.h @@ -2,10 +2,19 @@ #define __PROXYSQL_CONFIG_H__ #include +#include class SQLite3DB; extern const char* config_header; +enum proxysql_config_type { + PROXYSQL_CONFIG_MYSQL_USERS, + PROXYSQL_CONFIG_PGSQL_USERS, + PROXYSQL_CONFIG_MYSQL_SERVERS, + PROXYSQL_CONFIG_PGSQL_SERVERS, + PROXYSQL_CONFIG_PROXY_SERVERS, +}; + class ProxySQL_Config { public: SQLite3DB* admindb; @@ -13,15 +22,15 @@ public: virtual ~ProxySQL_Config(); int Read_Global_Variables_from_configfile(const char *prefix); - int Read_MySQL_Users_from_configfile(); + int Read_MySQL_Users_from_configfile(std::string& error); int Read_MySQL_Query_Rules_from_configfile(); - int Read_MySQL_Servers_from_configfile(); - int Read_PgSQL_Servers_from_configfile(); - int Read_PgSQL_Users_from_configfile(); + int Read_MySQL_Servers_from_configfile(std::string& error); + int Read_PgSQL_Servers_from_configfile(std::string& error); + int Read_PgSQL_Users_from_configfile(std::string& error); int Read_PgSQL_Query_Rules_from_configfile(); int Read_Scheduler_from_configfile(); int Read_Restapi_from_configfile(); - int Read_ProxySQL_Servers_from_configfile(); + int Read_ProxySQL_Servers_from_configfile(std::string& error); void addField(std::string& data, const char* name, const char* value, const char* dq="\""); int Write_Global_Variables_to_configfile(std::string& data); @@ -34,6 +43,11 @@ public: int Write_Scheduler_to_configfile(std::string& data); int Write_Restapi_to_configfile(std::string& data); int Write_ProxySQL_Servers_to_configfile(std::string& data); + +private: + bool validate_backend_users(proxysql_config_type type, const libconfig::Setting& config, std::string& error); + bool validate_backend_servers(proxysql_config_type type, const libconfig::Setting& config, std::string& error); + bool validate_proxysql_servers(const libconfig::Setting& config, std::string& error); }; #endif diff --git a/lib/Admin_Bootstrap.cpp b/lib/Admin_Bootstrap.cpp index 66ef33f20..3ea73a1cc 100644 --- a/lib/Admin_Bootstrap.cpp +++ b/lib/Admin_Bootstrap.cpp @@ -812,20 +812,23 @@ bool ProxySQL_Admin::init(const bootstrap_info_t& bootstrap_info) { if (GloVars.__cmd_proxysql_reload || GloVars.__cmd_proxysql_initial || admindb_file_exists==false) { // see #617 if (GloVars.configfile_open) { - proxysql_config().Read_MySQL_Servers_from_configfile(); - proxysql_config().Read_MySQL_Users_from_configfile(); + // ignore validation errors during init + std::string e; + + proxysql_config().Read_MySQL_Servers_from_configfile(e); + proxysql_config().Read_MySQL_Users_from_configfile(e); proxysql_config().Read_MySQL_Query_Rules_from_configfile(); proxysql_config().Read_Global_Variables_from_configfile("admin"); proxysql_config().Read_Global_Variables_from_configfile("mysql"); - proxysql_config().Read_PgSQL_Servers_from_configfile(); - proxysql_config().Read_PgSQL_Users_from_configfile(); + proxysql_config().Read_PgSQL_Servers_from_configfile(e); + proxysql_config().Read_PgSQL_Users_from_configfile(e); proxysql_config().Read_PgSQL_Query_Rules_from_configfile(); proxysql_config().Read_Global_Variables_from_configfile("pgsql"); proxysql_config().Read_Scheduler_from_configfile(); proxysql_config().Read_Restapi_from_configfile(); - proxysql_config().Read_ProxySQL_Servers_from_configfile(); + proxysql_config().Read_ProxySQL_Servers_from_configfile(e); __insert_or_replace_disktable_select_maintable(); } } diff --git a/lib/Admin_Handler.cpp b/lib/Admin_Handler.cpp index b653f0bef..be1ccc415 100644 --- a/lib/Admin_Handler.cpp +++ b/lib/Admin_Handler.cpp @@ -1292,14 +1292,28 @@ bool admin_handler_command_load_or_save(char *query_no_space, unsigned int query if (GloVars.confFile->OpenFile(NULL)==true) { ProxySQL_Admin *SPA=(ProxySQL_Admin *)pa; int rows=0; + std::string msg, validation_err; if (query_no_space[5] == 'P' || query_no_space[5] == 'p') { - rows=SPA->proxysql_config().Read_PgSQL_Users_from_configfile(); - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Loaded pgsql users from CONFIG\n"); + rows = SPA->proxysql_config().Read_PgSQL_Users_from_configfile(validation_err); + if (rows < 0) { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Failed to load pgsql users from CONFIG due to validation error\n"); + msg = "Configuration validation failed - " + validation_err; + SPA->send_error_msg_to_client(sess, msg.c_str()); + } else { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Loaded pgsql users from CONFIG\n"); + SPA->send_ok_msg_to_client(sess, NULL, rows, query_no_space); + } } else { - rows=SPA->proxysql_config().Read_MySQL_Users_from_configfile(); - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Loaded mysql users from CONFIG\n"); + rows = SPA->proxysql_config().Read_MySQL_Users_from_configfile(validation_err); + if (rows < 0) { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Failed to load mysql users from CONFIG due to validation error\n"); + msg = "Configuration validation failed - " + validation_err; + SPA->send_error_msg_to_client(sess, msg.c_str()); + } else { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Loaded mysql users from CONFIG\n"); + SPA->send_ok_msg_to_client(sess, NULL, rows, query_no_space); + } } - SPA->send_ok_msg_to_client(sess, NULL, rows, query_no_space); GloVars.confFile->CloseFile(); } else { proxy_debug(PROXY_DEBUG_ADMIN, 4, "Unable to open or parse config file %s\n", GloVars.config_file); @@ -1693,14 +1707,28 @@ bool admin_handler_command_load_or_save(char *query_no_space, unsigned int query if (GloVars.confFile->OpenFile(NULL)==true) { ProxySQL_Admin *SPA=(ProxySQL_Admin *)pa; int rows=0; + std::string msg, validation_err; if (is_pgsql) { - rows=SPA->proxysql_config().Read_PgSQL_Servers_from_configfile(); - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Loaded pgsql servers from CONFIG\n"); + rows = SPA->proxysql_config().Read_PgSQL_Servers_from_configfile(validation_err); + if (rows < 0) { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Failed to load pgsql servers from CONFIG due to validation error\n"); + msg = "Configuration validation failed - " + validation_err; + SPA->send_error_msg_to_client(sess, msg.c_str()); + } else { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Loaded pgsql servers from CONFIG\n"); + SPA->send_ok_msg_to_client(sess, NULL, rows, query_no_space); + } } else { - rows=SPA->proxysql_config().Read_MySQL_Servers_from_configfile(); - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Loaded mysql servers from CONFIG\n"); + rows = SPA->proxysql_config().Read_MySQL_Servers_from_configfile(validation_err); + if (rows < 0) { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Failed to load mysql servers from CONFIG due to validation error\n"); + msg = "Configuration validation failed - " + validation_err; + SPA->send_error_msg_to_client(sess, msg.c_str()); + } else { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Loaded mysql servers from CONFIG\n"); + SPA->send_ok_msg_to_client(sess, NULL, rows, query_no_space); + } } - SPA->send_ok_msg_to_client(sess, NULL, rows, query_no_space); GloVars.confFile->CloseFile(); } else { proxy_debug(PROXY_DEBUG_ADMIN, 4, "Unable to open or parse config file %s\n", GloVars.config_file); @@ -1815,9 +1843,16 @@ bool admin_handler_command_load_or_save(char *query_no_space, unsigned int query if (GloVars.confFile->OpenFile(NULL)==true) { ProxySQL_Admin *SPA=(ProxySQL_Admin *)pa; int rows=0; - rows=SPA->proxysql_config().Read_ProxySQL_Servers_from_configfile(); - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Loaded ProxySQL servers from CONFIG\n"); - SPA->send_ok_msg_to_client(sess, NULL, rows, query_no_space); + std::string msg, validation_err; + rows = SPA->proxysql_config().Read_ProxySQL_Servers_from_configfile(validation_err); + if (rows < 0) { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Failed to load ProxySQL servers from CONFIG due to validation error\n"); + msg = "Configuration validation failed - " + validation_err; + SPA->send_error_msg_to_client(sess, msg.c_str()); + } else { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Loaded ProxySQL servers from CONFIG\n"); + SPA->send_ok_msg_to_client(sess, NULL, rows, query_no_space); + } GloVars.confFile->CloseFile(); } else { proxy_debug(PROXY_DEBUG_ADMIN, 4, "Unable to open or parse config file %s\n", GloVars.config_file); diff --git a/lib/ProxySQL_Config.cpp b/lib/ProxySQL_Config.cpp index 347cf3bdf..758f60c66 100644 --- a/lib/ProxySQL_Config.cpp +++ b/lib/ProxySQL_Config.cpp @@ -4,6 +4,8 @@ #include "cpp.h" #include +#include +#include const char* config_header = "########################################################################################\n" "# This config file is parsed using libconfig , and its grammar is described in:\n" @@ -144,17 +146,21 @@ int ProxySQL_Config::Write_MySQL_Users_to_configfile(std::string& data) { return 0; } -int ProxySQL_Config::Read_MySQL_Users_from_configfile() { +int ProxySQL_Config::Read_MySQL_Users_from_configfile(std::string& error) { const Setting& root = GloVars.confFile->cfg.getRoot(); if (root.exists("mysql_users")==false) return 0; const Setting &mysql_users = root["mysql_users"]; int count = mysql_users.getLength(); - //fprintf(stderr, "Found %d users\n",count); - int i; + + if (!validate_backend_users(PROXYSQL_CONFIG_MYSQL_USERS, mysql_users, error)) { + proxy_error("Admin: %s\n", error.c_str()); + return -1; + } + int rows=0; admindb->execute("PRAGMA foreign_keys = OFF"); char *q=(char *)"INSERT OR REPLACE INTO mysql_users (username, password, active, use_ssl, default_hostgroup, default_schema, schema_locked, transaction_persistent, fast_forward, max_connections, attributes, comment) VALUES ('%s', '%s', %d, %d, %d, '%s', %d, %d, %d, %d, '%s','%s')"; - for (i=0; i< count; i++) { + for (int i=0; i< count; i++) { const Setting &user = mysql_users[i]; std::string username; std::string password=""; @@ -168,10 +174,8 @@ int ProxySQL_Config::Read_MySQL_Users_from_configfile() { int max_connections=10000; std::string comment=""; std::string attributes=""; - if (user.lookupValue("username", username)==false) { - proxy_error("Admin: detected a mysql_users in config file without a mandatory username\n"); - continue; - } + + user.lookupValue("username", username); user.lookupValue("password", password); user.lookupValue("default_hostgroup", default_hostgroup); user.lookupValue("active", active); @@ -1014,7 +1018,7 @@ int ProxySQL_Config::Write_MySQL_Servers_to_configfile(std::string& data) { return 0; } -int ProxySQL_Config::Read_MySQL_Servers_from_configfile() { +int ProxySQL_Config::Read_MySQL_Servers_from_configfile(std::string& error) { const Setting& root = GloVars.confFile->cfg.getRoot(); int i; int rows=0; @@ -1022,7 +1026,12 @@ int ProxySQL_Config::Read_MySQL_Servers_from_configfile() { if (root.exists("mysql_servers")==true) { const Setting &mysql_servers = root["mysql_servers"]; int count = mysql_servers.getLength(); - //fprintf(stderr, "Found %d servers\n",count); + + if (!validate_backend_servers(PROXYSQL_CONFIG_MYSQL_SERVERS, mysql_servers, error)) { + proxy_error("Admin: %s\n", error.c_str()); + return -1; + } + char *q=(char *)"INSERT OR REPLACE INTO mysql_servers (hostname, port, gtid_port, hostgroup_id, compression, weight, status, max_connections, max_replication_lag, use_ssl, max_latency_ms, comment) VALUES (\"%s\", %d, %d, %d, %d, %d, \"%s\", %d, %d, %d, %d, '%s')"; for (i=0; i< count; i++) { const Setting &server = mysql_servers[i]; @@ -1038,20 +1047,15 @@ int ProxySQL_Config::Read_MySQL_Servers_from_configfile() { int use_ssl=0; int max_latency_ms=0; std::string comment=""; - if (server.lookupValue("address", address)==false) { - if (server.lookupValue("hostname", address)==false) { - proxy_error("Admin: detected a mysql_servers in config file without a mandatory hostname\n"); - continue; - } + + if (!server.lookupValue("address", address)) { + server.lookupValue("hostname", address); + } + if (!server.lookupValue("hostgroup", hostgroup)) { + server.lookupValue("hostgroup_id", hostgroup); } server.lookupValue("port", port); server.lookupValue("gtid_port", gtid_port); - if (server.lookupValue("hostgroup", hostgroup)==false) { - if (server.lookupValue("hostgroup_id", hostgroup)==false) { - proxy_error("Admin: detected a mysql_servers in config file without a mandatory hostgroup_id\n"); - continue; - } - } server.lookupValue("status", status); if ( (strcasecmp(status.c_str(),(char *)"ONLINE")) @@ -1080,6 +1084,8 @@ int ProxySQL_Config::Read_MySQL_Servers_from_configfile() { rows++; } } + + // TODO: Add validation to mysql_replication_hostgroups similar to mysql_servers if (root.exists("mysql_replication_hostgroups")==true) { const Setting &mysql_replication_hostgroups = root["mysql_replication_hostgroups"]; int count = mysql_replication_hostgroups.getLength(); @@ -1457,7 +1463,7 @@ int ProxySQL_Config::Write_ProxySQL_Servers_to_configfile(std::string& data) { return 0; } -int ProxySQL_Config::Read_ProxySQL_Servers_from_configfile() { +int ProxySQL_Config::Read_ProxySQL_Servers_from_configfile(std::string& error) { const Setting& root = GloVars.confFile->cfg.getRoot(); int i; int rows=0; @@ -1465,7 +1471,12 @@ int ProxySQL_Config::Read_ProxySQL_Servers_from_configfile() { if (root.exists("proxysql_servers")==true) { const Setting & proxysql_servers = root["proxysql_servers"]; int count = proxysql_servers.getLength(); - //fprintf(stderr, "Found %d servers\n",count); + + if (!validate_proxysql_servers(proxysql_servers, error)) { + proxy_error("Admin: %s\n", error.c_str()); + return -1; + } + char *q=(char *)"INSERT OR REPLACE INTO proxysql_servers (hostname, port, weight, comment) VALUES (\"%s\", %d, %d, '%s')"; for (i=0; i< count; i++) { const Setting &server = proxysql_servers[i]; @@ -1473,16 +1484,11 @@ int ProxySQL_Config::Read_ProxySQL_Servers_from_configfile() { int port; int weight=0; std::string comment=""; - if (server.lookupValue("address", address)==false) { - if (server.lookupValue("hostname", address)==false) { - proxy_error("Admin: detected a proxysql_servers in config file without a mandatory hostname\n"); - continue; - } - } - if (server.lookupValue("port", port)==false) { - proxy_error("Admin: detected a proxysql_servers in config file without a mandatory port\n"); - continue; + + if (!server.lookupValue("address", address)) { + server.lookupValue("hostname", address); } + server.lookupValue("port", port); server.lookupValue("weight", weight); server.lookupValue("comment", comment); char *o1=strdup(comment.c_str()); @@ -1621,7 +1627,7 @@ int ProxySQL_Config::Write_PgSQL_Servers_to_configfile(std::string& data) { return 0; } -int ProxySQL_Config::Read_PgSQL_Servers_from_configfile() { +int ProxySQL_Config::Read_PgSQL_Servers_from_configfile(std::string& error) { const Setting& root = GloVars.confFile->cfg.getRoot(); int i; int rows = 0; @@ -1629,7 +1635,12 @@ int ProxySQL_Config::Read_PgSQL_Servers_from_configfile() { if (root.exists("pgsql_servers") == true) { const Setting& pgsql_servers = root["pgsql_servers"]; int count = pgsql_servers.getLength(); - //fprintf(stderr, "Found %d servers\n",count); + + if (!validate_backend_servers(PROXYSQL_CONFIG_PGSQL_SERVERS, pgsql_servers, error)) { + proxy_error("Admin: %s\n", error.c_str()); + return -1; + } + char* q = (char*)"INSERT OR REPLACE INTO pgsql_servers (hostname, port, hostgroup_id, compression, weight, status, max_connections, max_replication_lag, use_ssl, max_latency_ms, comment) VALUES (\"%s\", %d, %d, %d, %d, \"%s\", %d, %d, %d, %d, '%s')"; for (i = 0; i < count; i++) { const Setting& server = pgsql_servers[i]; @@ -1644,19 +1655,14 @@ int ProxySQL_Config::Read_PgSQL_Servers_from_configfile() { int use_ssl = 0; int max_latency_ms = 0; std::string comment = ""; - if (server.lookupValue("address", address) == false) { - if (server.lookupValue("hostname", address) == false) { - proxy_error("Admin: detected a pgsql_servers in config file without a mandatory hostname\n"); - continue; - } + + if (!server.lookupValue("address", address)) { + server.lookupValue("hostname", address); } - server.lookupValue("port", port); - if (server.lookupValue("hostgroup", hostgroup) == false) { - if (server.lookupValue("hostgroup_id", hostgroup) == false) { - proxy_error("Admin: detected a pgsql_servers in config file without a mandatory hostgroup_id\n"); - continue; - } + if (!server.lookupValue("hostgroup", hostgroup)) { + server.lookupValue("hostgroup_id", hostgroup); } + server.lookupValue("port", port); server.lookupValue("status", status); if ( (strcasecmp(status.c_str(), (char*)"ONLINE")) @@ -1685,6 +1691,8 @@ int ProxySQL_Config::Read_PgSQL_Servers_from_configfile() { rows++; } } + + // TODO: Add validation to pgsql_replication_hostgroups similar to pgsql_servers if (root.exists("pgsql_replication_hostgroups") == true) { const Setting& pgsql_replication_hostgroups = root["pgsql_replication_hostgroups"]; int count = pgsql_replication_hostgroups.getLength(); @@ -1777,17 +1785,21 @@ int ProxySQL_Config::Write_PgSQL_Users_to_configfile(std::string& data) { return 0; } -int ProxySQL_Config::Read_PgSQL_Users_from_configfile() { +int ProxySQL_Config::Read_PgSQL_Users_from_configfile(std::string& error) { const Setting& root = GloVars.confFile->cfg.getRoot(); if (root.exists("pgsql_users") == false) return 0; const Setting& pgsql_users = root["pgsql_users"]; int count = pgsql_users.getLength(); - //fprintf(stderr, "Found %d users\n",count); - int i; + + if (!validate_backend_users(PROXYSQL_CONFIG_PGSQL_USERS, pgsql_users, error)) { + proxy_error("Admin: %s\n", error.c_str()); + return -1; + } + int rows = 0; admindb->execute("PRAGMA foreign_keys = OFF"); char* q = (char*)"INSERT OR REPLACE INTO pgsql_users (username, password, active, use_ssl, default_hostgroup, transaction_persistent, fast_forward, max_connections, attributes, comment) VALUES ('%s', '%s', %d, %d, %d, %d, %d, %d, '%s','%s')"; - for (i = 0; i < count; i++) { + for (int i = 0; i < count; i++) { const Setting& user = pgsql_users[i]; std::string username; std::string password = ""; @@ -1799,10 +1811,7 @@ int ProxySQL_Config::Read_PgSQL_Users_from_configfile() { int max_connections = 10000; std::string comment = ""; std::string attributes = ""; - if (user.lookupValue("username", username) == false) { - proxy_error("Admin: detected a pgsql_users in config file without a mandatory username\n"); - continue; - } + user.lookupValue("username", username); user.lookupValue("password", password); user.lookupValue("default_hostgroup", default_hostgroup); user.lookupValue("active", active); @@ -2166,3 +2175,115 @@ int ProxySQL_Config::Read_PgSQL_Query_Rules_from_configfile() { admindb->execute("PRAGMA foreign_keys = ON"); return rows; } + +bool ProxySQL_Config::validate_backend_users(proxysql_config_type type, const Setting& config, std::string& error) { + std::set> pk_set; + int count = config.getLength(); + + for (int i = 0; i < count; i++) { + const Setting& user = config[i]; + + std::string config; + std::string username; + int backend = 1; // default backend value + + config = (type == PROXYSQL_CONFIG_MYSQL_USERS) ? "mysql_users" : "pgsql_users"; + + if (user.lookupValue("username", username) == false) { + error = "mandatory field username missing from a " + config + " entry in config file"; + return false; + } + + user.lookupValue("backend", backend); + + auto key = std::make_tuple(username, backend); + + if (pk_set.find(key) != pk_set.end()) { + error = "duplicate entries found in " + config + " in config file"; + return false; + } + + pk_set.insert(key); + } + + return true; +} + +bool ProxySQL_Config::validate_backend_servers(proxysql_config_type type, const Setting& config, std::string& error) { + std::set> pk_set; + int count = config.getLength(); + + for (int i = 0; i < count; i++) { + const Setting& server = config[i]; + + std::string config; + std::string address; + int port; + int hostgroup; + + config = (type == PROXYSQL_CONFIG_MYSQL_SERVERS) ? "mysql_servers" : "pgsql_servers"; + port = (type == PROXYSQL_CONFIG_MYSQL_SERVERS) ? 3306 : 5432; + + if (server.lookupValue("hostgroup", hostgroup) == false) { + if (server.lookupValue("hostgroup_id", hostgroup) == false) { + error = "mandatory field hostgroup_id missing from a " + config + " entry in config file"; + return false; + } + } + + if (server.lookupValue("address", address) == false) { + if (server.lookupValue("hostname", address) == false) { + error = "mandatory field hostname missing from a " + config + " entry in config file"; + return false; + } + } + + server.lookupValue("port", port); + + auto key = std::make_tuple(hostgroup, address, port); + + if (pk_set.find(key) != pk_set.end()) { + error = "duplicate entries found in " + config + " in config file"; + return false; + } + + pk_set.insert(key); + } + + return true; +} + +bool ProxySQL_Config::validate_proxysql_servers(const Setting& config, std::string& error) { + std::set> pk_set; + int count = config.getLength(); + + for (int i = 0; i < count; i++) { + const Setting& server = config[i]; + + std::string address; + int port; + + if (server.lookupValue("address", address) == false) { + if (server.lookupValue("hostname", address) == false) { + error = "mandatory field hostname missing from a proxysql_servers entry in config file"; + return false; + } + } + + if (server.lookupValue("port", port) == false) { + error = "mandatory field port missing from a proxysql_servers entry in config file"; + return false; + } + + auto key = std::make_tuple(address, port); + + if (pk_set.find(key) != pk_set.end()) { + error = "duplicate entries found in proxysql_servers in config file"; + return false; + } + + pk_set.insert(key); + } + + return true; +} \ No newline at end of file