MXS-2546 Add DNS-resolving to topology detection

When matching hostnames between MaxScale server configuration and the
SHOW SLAVE STATUS-output, use DNS-resolution if a simple string comparison
doesn't find an answer. Results of the resolution are saved to avoid
repeating the operation for the same address.
This commit is contained in:
Esa Korhonen
2019-06-06 11:20:34 +03:00
parent 5df1f2561c
commit d4b712ae84
6 changed files with 140 additions and 7 deletions

View File

@ -109,6 +109,16 @@ inline bool operator!=(const Host& l, const Host& r)
return !(l == r); return !(l == r);
} }
/**
* Perform DNS resolution on a hostname or text-form IP address.
*
* @param host Hostname to convert.
* @param addr_out Output buffer. The output is in IPv6-form as returned by "inet_ntop(AF_INET6, ...)".
* @param error_out Error output
* @return True if successful
*/
bool name_lookup(const std::string& host, std::string* addr_out, std::string* error_out = nullptr);
/** /**
* Perform reverse DNS on an IP address. This may involve network communication so can be slow. * Perform reverse DNS on an IP address. This may involve network communication so can be slow.
* *
@ -116,5 +126,5 @@ inline bool operator!=(const Host& l, const Host& r)
* @param output Where to write the output. If operation fails, original IP is written. * @param output Where to write the output. If operation fails, original IP is written.
* @return True on success * @return True on success
*/ */
bool reverse_dns(const std::string& ip, std::string* output); bool reverse_name_lookup(const std::string& ip, std::string* output);
} }

View File

@ -11,13 +11,15 @@
* Public License. * Public License.
*/ */
#include <maxbase/host.hh> #include <maxbase/host.hh>
#include <maxbase/string.hh>
#include <ostream> #include <ostream>
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <netdb.h> #include <netdb.h>
#include <maxbase/assert.h>
#include <maxbase/format.hh>
#include <maxbase/string.hh>
namespace namespace
{ {
@ -225,7 +227,54 @@ std::istream& operator>>(std::istream& is, Host& host)
return is; return is;
} }
bool reverse_dns(const std::string& ip, std::string* output) bool name_lookup(const std::string& host, std::string* addr_out, std::string* error_out)
{
addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET6; /* Only return IPv6-addresses, possibly mapped from IPv4 */
hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
hints.ai_flags = AI_V4MAPPED; /* Mapped IPv4 */
hints.ai_protocol = 0; /* Any protocol */
hints.ai_canonname = NULL;
hints.ai_addr = NULL;
hints.ai_next = NULL;
addrinfo* results = nullptr;
bool success = false;
std::string error_msg;
int rv_addrinfo = getaddrinfo(host.c_str(), nullptr, &hints, &results);
if (rv_addrinfo == 0)
{
mxb_assert(results);
if (results)
{
// getaddrinfo may return multiple result addresses. Only consider the first.
char buf[INET6_ADDRSTRLEN];
if (inet_ntop(AF_INET6, results->ai_addr, buf, sizeof(buf)))
{
*addr_out = buf;
success = true;
}
else
{
error_msg = mxb::string_printf("inet_ntop() failed: '%s'.", strerror(errno));
}
freeaddrinfo(results);
}
}
else
{
error_msg = mxb::string_printf("getaddrinfo() failed: '%s'.", gai_strerror(rv_addrinfo));
}
if (error_out)
{
*error_out = error_msg;
}
return success;
}
bool reverse_name_lookup(const std::string& ip, std::string* output)
{ {
sockaddr_storage socket_address; sockaddr_storage socket_address;
memset(&socket_address, 0, sizeof(socket_address)); memset(&socket_address, 0, sizeof(socket_address));

View File

@ -766,7 +766,7 @@ json_t* session_json_data(const Session* session, const char* host, bool rdns)
auto remote = session->client_dcb->remote; auto remote = session->client_dcb->remote;
if (rdns) if (rdns)
{ {
maxbase::reverse_dns(remote, &result_address); maxbase::reverse_name_lookup(remote, &result_address);
} }
else else
{ {

View File

@ -12,11 +12,13 @@
*/ */
#include "mariadbmon.hh" #include "mariadbmon.hh"
#include <algorithm> #include <algorithm>
#include <inttypes.h> #include <inttypes.h>
#include <string> #include <string>
#include <queue> #include <queue>
#include <maxbase/format.hh> #include <maxbase/format.hh>
#include <maxbase/host.hh>
#include <maxscale/modutil.hh> #include <maxscale/modutil.hh>
#include <maxscale/mysql_utils.hh> #include <maxscale/mysql_utils.hh>
@ -1002,3 +1004,35 @@ bool MariaDBMonitor::is_candidate_valid(MariaDBServer* cand, RequireRunning req_
} }
return is_valid; return is_valid;
}; };
string MariaDBMonitor::DNSResolver::resolve_server(const string& host)
{
auto now = mxb::Clock::now();
const auto MAX_AGE = mxb::Duration((double)5*60); // Refresh interval for cache entries.
auto recent_time = now - MAX_AGE;
string rval;
auto iter = m_mapping.find(host);
if (iter == m_mapping.end() || iter->second.timestamp < recent_time)
{
// Map did not have a record, or it was too old. In either case, generate a new one.
string addr;
string error_msg;
bool dns_success = mxb::name_lookup(host, &addr, &error_msg);
if (!dns_success)
{
MXB_ERROR("Could not resolve host '%s'. %s", host.c_str(), error_msg.c_str());
}
// If dns failed, addr will be empty. Add the element anyway to prevent repeated lookups.
MapElement newelem = {addr, now};
m_mapping[host] = newelem;
rval = addr;
}
else
{
// Return recent value.
rval = iter->second.address;
}
return rval;
}

View File

@ -94,6 +94,8 @@ void MariaDBMonitor::reset_server_info()
{ {
m_servers.push_back(new MariaDBServer(mon_server, m_servers.size(), m_settings.shared)); m_servers.push_back(new MariaDBServer(mon_server, m_servers.size(), m_settings.shared));
} }
m_resolver = DNSResolver(); // Erases result cache.
} }
void MariaDBMonitor::reset_node_index_info() void MariaDBMonitor::reset_node_index_info()
@ -106,10 +108,9 @@ void MariaDBMonitor::reset_node_index_info()
MariaDBServer* MariaDBMonitor::get_server(const EndPoint& search_ep) MariaDBServer* MariaDBMonitor::get_server(const EndPoint& search_ep)
{ {
// TODO: Do this with a map lookup
// TODO: Add DNS check here.
MariaDBServer* found = NULL; MariaDBServer* found = NULL;
for (MariaDBServer* server : m_servers) // Phase 1: Direct string compare
for (auto server : m_servers)
{ {
EndPoint srv(server->m_server_base->server); EndPoint srv(server->m_server_base->server);
if (srv == search_ep) if (srv == search_ep)
@ -118,6 +119,28 @@ MariaDBServer* MariaDBMonitor::get_server(const EndPoint& search_ep)
break; break;
} }
} }
if (!found)
{
// Phase 2: Was not found with simple string compare. Try DNS resolving for endpoints with
// matching ports.
string target_addr = m_resolver.resolve_server(search_ep.host());
if (!target_addr.empty())
{
for (auto server : m_servers)
{
if (server->m_server_base->server->port == search_ep.port())
{
string server_addr = m_resolver.resolve_server(server->m_server_base->server->address);
if (server_addr == target_addr)
{
found = server;
break;
}
}
}
}
}
return found; return found;
} }

View File

@ -169,6 +169,21 @@ private:
bool result_waiting = false; /* Guard variable for has_result */ bool result_waiting = false; /* Guard variable for has_result */
}; };
class DNSResolver
{
public:
std::string resolve_server(const std::string& host);
private:
struct MapElement
{
std::string address;
mxb::TimePoint timestamp;
};
std::unordered_map<std::string, MapElement> m_mapping; // hostname -> address cache
};
ManualCommand m_manual_cmd; /* Communicates manual commands and results */ ManualCommand m_manual_cmd; /* Communicates manual commands and results */
// Server containers, mostly constant. // Server containers, mostly constant.
@ -185,6 +200,8 @@ private:
bool m_cluster_modified = false; /* Has a cluster operation been performed this loop? Prevents bool m_cluster_modified = false; /* Has a cluster operation been performed this loop? Prevents
* other operations during this tick. */ * other operations during this tick. */
DNSResolver m_resolver; /* DNS-resolver with cache */
/* Counter for temporary automatic cluster operation disabling. */ /* Counter for temporary automatic cluster operation disabling. */
int cluster_operation_disable_timer = 0; int cluster_operation_disable_timer = 0;