You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
proxysql/lib/DNS_Cache.cpp

350 lines
10 KiB

#include "DNS_Cache.hpp"
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <cstdlib>
#include <cstring>
#include "proxysql.h"
#include "proxysql_debug.h"
#include "proxysql_utils.h"
#include "proxysql_glovars.hpp"
#include "gen_utils.h"
#include <random>
bool validate_ip(const std::string& ip) {
// inet_pton returns 1 on success, 0 if input is not a valid address for
// the family, -1 on error (e.g. unsupported family). Treating != 0 as
// success would misclassify -1 errors as valid IPs.
struct sockaddr_in sa4;
if (inet_pton(AF_INET, ip.c_str(), &(sa4.sin_addr)) == 1)
return true;
struct sockaddr_in6 sa6;
if (inet_pton(AF_INET6, ip.c_str(), &(sa6.sin6_addr)) == 1)
return true;
return false;
}
std::string get_connected_peer_ip_from_socket(int socket_fd) {
std::string result;
char ip_addr[INET6_ADDRSTRLEN];
struct sockaddr_storage custom_sockaddr;
socklen_t addrlen = sizeof(custom_sockaddr);
memset(&custom_sockaddr, 0, sizeof(custom_sockaddr));
if (getpeername(socket_fd, (struct sockaddr*)&custom_sockaddr, &addrlen) != 0)
return result;
// Only assign to result when sa_family is one we know how to format.
// Other families (AF_UNIX, AF_NETLINK, ...) shouldn't happen for a TCP
// peer fd, but if they ever do we'd previously emit uninitialized memory
// from ip_addr.
if (custom_sockaddr.ss_family == AF_INET) {
const struct sockaddr_in* ipv4 = (const struct sockaddr_in*)&custom_sockaddr;
if (inet_ntop(AF_INET, &ipv4->sin_addr, ip_addr, INET_ADDRSTRLEN))
result = ip_addr;
}
else if (custom_sockaddr.ss_family == AF_INET6) {
const struct sockaddr_in6* ipv6 = (const struct sockaddr_in6*)&custom_sockaddr;
if (inet_ntop(AF_INET6, &ipv6->sin6_addr, ip_addr, INET6_ADDRSTRLEN))
result = ip_addr;
}
return result;
}
void* monitor_dns_resolver_thread(const std::vector<DNS_Resolve_Data*>& dns_resolve_data_list) {
assert(!dns_resolve_data_list.empty());
DNS_Resolve_Data* dns_resolve_data = dns_resolve_data_list.front();
struct addrinfo hints, *res = NULL;
memset(&hints, 0, sizeof(hints));
hints.ai_protocol = IPPROTO_TCP;
hints.ai_socktype = SOCK_STREAM;
// AI_ADDRCONFIG: only return AF_INET addrs if the local system has at least
// one IPv4 address configured, and AF_INET6 only if it has at least one
// IPv6 address configured. Loopback is not considered configured for this
// purpose. Useful on IPv4-only hosts so getaddrinfo() doesn't return IPv6
// addresses that connect/bind would always fail on.
hints.ai_flags = AI_ADDRCONFIG;
hints.ai_family = dns_resolve_data->ai_family;
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5,
"Resolving hostname:[%s] to its mapped IP address.\n",
dns_resolve_data->hostname.c_str());
int gai_rc = getaddrinfo(dns_resolve_data->hostname.c_str(), NULL, &hints, &res);
if (gai_rc != 0 || !res) {
proxy_error("An error occurred while resolving hostname: %s [%d]\n",
dns_resolve_data->hostname.c_str(), gai_rc);
goto __error;
}
try {
std::vector<std::string> ips;
ips.reserve(64);
char ip_addr[INET6_ADDRSTRLEN];
for (auto p = res; p != NULL; p = p->ai_next) {
if (p->ai_family == AF_INET) {
struct sockaddr_in* ipv4 = (struct sockaddr_in*)p->ai_addr;
inet_ntop(p->ai_addr->sa_family, &ipv4->sin_addr, ip_addr, INET_ADDRSTRLEN);
ips.push_back(ip_addr);
}
else {
struct sockaddr_in6* ipv6 = (struct sockaddr_in6*)p->ai_addr;
inet_ntop(p->ai_addr->sa_family, &ipv6->sin6_addr, ip_addr, INET6_ADDRSTRLEN);
ips.push_back(ip_addr);
}
}
freeaddrinfo(res);
if (!ips.empty()) {
bool to_update_cache = false;
int cache_ttl = dns_resolve_data->ttl;
if (dns_resolve_data->ttl > dns_resolve_data->refresh_intv) {
// NOSONAR cpp:S2245 — mt19937 used here only as a DNS-cache
// TTL jitter source (non-cryptographic timing tweak); no
// security boundary. Inline annotation on the construction
// line because Sonar attributes the hotspot to it.
thread_local std::mt19937 gen(std::random_device{}()); // NOSONAR cpp:S2245
const int jitter = static_cast<int>(dns_resolve_data->ttl * 0.025);
std::uniform_int_distribution<int> dis(-jitter, jitter);
cache_ttl += dis(gen);
}
if (!dns_resolve_data->cached_ips.empty()) {
if (dns_resolve_data->cached_ips.size() == ips.size()) {
for (const std::string& ip : ips) {
if (dns_resolve_data->cached_ips.find(ip) == dns_resolve_data->cached_ips.end()) {
to_update_cache = true;
break;
}
}
}
else
to_update_cache = true;
if (!to_update_cache) {
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5,
"DNS cache record already up-to-date. (Hostname:[%s] IP:[%s])\n",
dns_resolve_data->hostname.c_str(),
debug_iplisttostring(ips).c_str());
dns_resolve_data->result.set_value(std::make_tuple<>(true,
DNS_Cache_Record(dns_resolve_data->hostname,
std::move(dns_resolve_data->cached_ips),
monotonic_time() + (1000ULL * static_cast<unsigned long long>(cache_ttl)))));
}
}
else
to_update_cache = true;
if (to_update_cache) {
dns_resolve_data->result.set_value(std::make_tuple<>(true,
DNS_Cache_Record(dns_resolve_data->hostname, ips,
monotonic_time() + (1000ULL * static_cast<unsigned long long>(cache_ttl)))));
dns_resolve_data->dns_cache->add(dns_resolve_data->hostname, std::move(ips));
}
return NULL;
}
}
catch (std::exception& ex) {
proxy_error("An exception occurred while resolving hostname: %s [%s]\n",
dns_resolve_data->hostname.c_str(), ex.what());
}
catch (...) {
proxy_error("An unknown exception has occurred while resolving hostname: %s\n",
dns_resolve_data->hostname.c_str());
}
__error:
dns_resolve_data->result.set_value(std::make_tuple<>(false, DNS_Cache_Record()));
return NULL;
}
void* DNSResolverWorker::run() {
set_thread_name(thr_name_, GloVars.set_thread_name);
while (true) {
DNS_Resolve_Data* item = static_cast<DNS_Resolve_Data*>(queue_.remove());
if (item == nullptr) {
// Sentinel: caller pushed NULL to ask the worker to exit.
break;
}
std::vector<DNS_Resolve_Data*> list { item };
try {
monitor_dns_resolver_thread(list);
} catch (...) {
// monitor_dns_resolver_thread() can in principle throw
// (allocations inside the try/catch in there are guarded, but
// other paths — set_thread_name, vector growth, etc. — aren't).
// If it did and the promise was not satisfied, future::get() on
// the producer side would block forever and hang shutdown.
// Forcibly satisfy the promise with a failure result. set_value
// throws if the promise is already satisfied, which we also
// swallow here.
try {
item->result.set_value(std::make_tuple<>(false, DNS_Cache_Record()));
} catch (...) { }
}
delete item;
}
return nullptr;
}
bool DNS_Cache::add(const std::string& hostname, std::vector<std::string>&& ips) {
if (!enabled) return false;
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5,
"Updating DNS cache. (Hostname:[%s] IP:[%s])\n",
hostname.c_str(), debug_iplisttostring(ips).c_str());
int rc = pthread_rwlock_wrlock(&rwlock_);
assert(rc == 0);
auto& ip_addr = records[hostname];
ip_addr.ips = std::move(ips);
__sync_fetch_and_and(&ip_addr.counter, 0);
rc = pthread_rwlock_unlock(&rwlock_);
assert(rc == 0);
if (counter_record_updated_)
counter_record_updated_->fetch_add(1, std::memory_order_relaxed);
return true;
}
bool DNS_Cache::add_if_not_exist(const std::string& hostname, std::vector<std::string>&& ips) {
if (!enabled) return false;
bool inserted = false;
int rc = pthread_rwlock_wrlock(&rwlock_);
assert(rc == 0);
if (records.find(hostname) == records.end()) {
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5,
"Updating DNS cache. (Hostname:[%s] IP:[%s])\n",
hostname.c_str(), debug_iplisttostring(ips).c_str());
auto& ip_addr = records[hostname];
ip_addr.ips = std::move(ips);
__sync_fetch_and_and(&ip_addr.counter, 0);
inserted = true;
}
rc = pthread_rwlock_unlock(&rwlock_);
assert(rc == 0);
if (inserted && counter_record_updated_)
counter_record_updated_->fetch_add(1, std::memory_order_relaxed);
return inserted;
}
std::string DNS_Cache::get_next_ip(const IP_ADDR& ip_addr) const {
if (ip_addr.ips.empty())
return "";
const auto counter_val = __sync_fetch_and_add(&ip_addr.counter, 1);
return ip_addr.ips[counter_val % ip_addr.ips.size()];
}
std::string DNS_Cache::lookup(const std::string& hostname, size_t* ip_count) const {
if (!enabled) {
if (ip_count)
*ip_count = 0;
return "";
}
std::string ip;
if (counter_queried_)
counter_queried_->fetch_add(1, std::memory_order_relaxed);
int rc = pthread_rwlock_rdlock(&rwlock_);
assert(rc == 0);
auto itr = records.find(hostname);
if (itr != records.end()) {
ip = get_next_ip(itr->second);
if (ip_count)
*ip_count = itr->second.ips.size();
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5,
"DNS cache lookup success. (Hostname:[%s] IP returned:[%s])\n",
hostname.c_str(), ip.c_str());
}
else {
if (ip_count)
*ip_count = 0;
}
rc = pthread_rwlock_unlock(&rwlock_);
assert(rc == 0);
if (!ip.empty() && counter_lookup_success_)
counter_lookup_success_->fetch_add(1, std::memory_order_relaxed);
return ip;
}
void DNS_Cache::remove(const std::string& hostname) {
bool item_removed = false;
int rc = pthread_rwlock_wrlock(&rwlock_);
assert(rc == 0);
auto itr = records.find(hostname);
if (itr != records.end()) {
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5,
"Removing DNS cache record. (Hostname:[%s] IP:[%s])\n",
hostname.c_str(), debug_iplisttostring(itr->second.ips).c_str());
records.erase(itr);
item_removed = true;
}
rc = pthread_rwlock_unlock(&rwlock_);
if (item_removed && counter_record_updated_)
counter_record_updated_->fetch_add(1, std::memory_order_relaxed);
assert(rc == 0);
}
void DNS_Cache::clear() {
size_t records_removed = 0;
int rc = pthread_rwlock_wrlock(&rwlock_);
assert(rc == 0);
records_removed = records.size();
records.clear();
rc = pthread_rwlock_unlock(&rwlock_);
assert(rc == 0);
if (records_removed && counter_record_updated_)
counter_record_updated_->fetch_add(records_removed, std::memory_order_relaxed);
proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "DNS cache was cleared.\n");
}
bool DNS_Cache::empty() const {
bool result = true;
int rc = pthread_rwlock_rdlock(&rwlock_);
assert(rc == 0);
result = records.empty();
rc = pthread_rwlock_unlock(&rwlock_);
assert(rc == 0);
return result;
}