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);
}
/**
* 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.
*
@ -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.
* @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.
*/
#include <maxbase/host.hh>
#include <maxbase/string.hh>
#include <ostream>
#include <vector>
#include <algorithm>
#include <arpa/inet.h>
#include <netdb.h>
#include <maxbase/assert.h>
#include <maxbase/format.hh>
#include <maxbase/string.hh>
namespace
{
@ -225,7 +227,54 @@ std::istream& operator>>(std::istream& is, Host& host)
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;
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;
if (rdns)
{
maxbase::reverse_dns(remote, &result_address);
maxbase::reverse_name_lookup(remote, &result_address);
}
else
{

View File

@ -12,11 +12,13 @@
*/
#include "mariadbmon.hh"
#include <algorithm>
#include <inttypes.h>
#include <string>
#include <queue>
#include <maxbase/format.hh>
#include <maxbase/host.hh>
#include <maxscale/modutil.hh>
#include <maxscale/mysql_utils.hh>
@ -1002,3 +1004,35 @@ bool MariaDBMonitor::is_candidate_valid(MariaDBServer* cand, RequireRunning req_
}
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_resolver = DNSResolver(); // Erases result cache.
}
void MariaDBMonitor::reset_node_index_info()
@ -106,10 +108,9 @@ void MariaDBMonitor::reset_node_index_info()
MariaDBServer* MariaDBMonitor::get_server(const EndPoint& search_ep)
{
// TODO: Do this with a map lookup
// TODO: Add DNS check here.
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);
if (srv == search_ep)
@ -118,6 +119,28 @@ MariaDBServer* MariaDBMonitor::get_server(const EndPoint& search_ep)
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;
}

View File

@ -169,6 +169,21 @@ private:
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 */
// Server containers, mostly constant.
@ -185,6 +200,8 @@ private:
bool m_cluster_modified = false; /* Has a cluster operation been performed this loop? Prevents
* other operations during this tick. */
DNSResolver m_resolver; /* DNS-resolver with cache */
/* Counter for temporary automatic cluster operation disabling. */
int cluster_operation_disable_timer = 0;