mirror of https://github.com/sysown/proxysql
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.
350 lines
10 KiB
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;
|
|
}
|