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/include/DNS_Cache.hpp

168 lines
5.6 KiB

#ifndef __CLASS_DNS_CACHE_H
#define __CLASS_DNS_CACHE_H
// Shared DNS cache infrastructure used by both MySQL_Monitor and PgSQL_Monitor.
// The cache itself is just a hostname -> [IP] map with TTL; the resolver thread
// does getaddrinfo and feeds the cache. Each monitor instantiates its own
// DNS_Cache and supplies its own counters, so the two protocols share code but
// have independent cache state.
#include <pthread.h>
#include <sys/socket.h>
#include <algorithm>
#include <atomic>
#include <cassert>
#include <future>
#include <iterator>
#include <memory>
#include <set>
#include <sstream>
#include <string>
#include <tuple>
#include <unordered_map>
#include <vector>
#include "thread.h"
#include "wqueue.h"
struct DNS_Cache_Record {
DNS_Cache_Record() = default;
DNS_Cache_Record(DNS_Cache_Record&&) = default;
DNS_Cache_Record(const DNS_Cache_Record&) = default;
DNS_Cache_Record& operator=(DNS_Cache_Record&&) = default;
DNS_Cache_Record& operator=(const DNS_Cache_Record&) = default;
DNS_Cache_Record(const std::string& hostname, const std::vector<std::string>& ips, unsigned long long ttl = 0)
: hostname_(hostname), ttl_(ttl) {
std::copy(ips.begin(), ips.end(), std::inserter(ips_, ips_.end()));
}
DNS_Cache_Record(const std::string& hostname, std::set<std::string>&& ips, unsigned long long ttl = 0)
: hostname_(hostname), ips_(std::move(ips)), ttl_(ttl) { }
~DNS_Cache_Record() = default;
std::string hostname_;
std::set<std::string> ips_;
unsigned long long ttl_ = 0;
};
class DNS_Cache {
public:
DNS_Cache() : enabled(true) {
int rc = pthread_rwlock_init(&rwlock_, nullptr);
assert(rc == 0);
}
~DNS_Cache() {
pthread_rwlock_destroy(&rwlock_);
}
inline
void set_enabled_flag(bool value) {
enabled = value;
}
// Wire the cache up to per-instance counters maintained by the owning
// monitor. Pass nullptr to leave any counter unincremented; this is what
// keeps MySQL_Monitor and PgSQL_Monitor cache state independent. The
// counters are atomic because resolver workers increment them while
// p_update_metrics() reads them from another thread.
inline
void set_counters(std::atomic<unsigned long long>* queried,
std::atomic<unsigned long long>* lookup_success,
std::atomic<unsigned long long>* record_updated) {
counter_queried_ = queried;
counter_lookup_success_ = lookup_success;
counter_record_updated_ = record_updated;
}
bool add(const std::string& hostname, std::vector<std::string>&& ips);
bool add_if_not_exist(const std::string& hostname, std::vector<std::string>&& ips);
void remove(const std::string& hostname);
void clear();
bool empty() const;
std::string lookup(const std::string& hostname, size_t* ip_count) const;
private:
struct IP_ADDR {
std::vector<std::string> ips;
// 'counter' is bumped by get_next_ip() (a const method) for
// round-robin selection; the logical state of the cache record is
// unchanged, so mutable is the right tool here and lets us drop a
// const_cast at the call site.
mutable unsigned long counter = 0;
};
std::string get_next_ip(const IP_ADDR& ip_addr) const;
std::unordered_map<std::string, IP_ADDR> records;
std::atomic_bool enabled;
mutable pthread_rwlock_t rwlock_;
std::atomic<unsigned long long>* counter_queried_ { nullptr };
std::atomic<unsigned long long>* counter_lookup_success_ { nullptr };
std::atomic<unsigned long long>* counter_record_updated_ { nullptr };
};
struct DNS_Resolve_Data {
std::promise<std::tuple<bool, DNS_Cache_Record>> result;
std::shared_ptr<DNS_Cache> dns_cache;
std::string hostname;
std::set<std::string> cached_ips;
unsigned int ttl = 0;
unsigned int refresh_intv = 0;
// Address family for getaddrinfo, set by the caller from its protocol's
// resolution_family setting. AF_UNSPEC keeps the OS default behavior.
int ai_family = AF_UNSPEC;
};
// Worker function for the DNS resolver thread pool. Performs getaddrinfo
// on dns_resolve_data->hostname, populates the cache, and fulfils the
// future on dns_resolve_data->result.
void* monitor_dns_resolver_thread(const std::vector<DNS_Resolve_Data*>& dns_resolve_data_list);
// Minimal worker thread that drains a wqueue<DNS_Resolve_Data*> and calls
// monitor_dns_resolver_thread() on each item. Independent of any monitor
// singleton so it can back PgSQL_Monitor's DNS cache loop alongside (or
// instead of) MySQL_Monitor's existing ConsumerThread-based pool.
//
// Each item ownership is transferred to this worker: the worker frees it
// after running monitor_dns_resolver_thread. A NULL dequeue is the exit
// signal — the loop terminates cleanly so callers can shut the pool down by
// pushing one NULL per worker.
class DNSResolverWorker : public Thread {
public:
DNSResolverWorker(wqueue<DNS_Resolve_Data*>& q, const char* name = nullptr)
: queue_(q) {
if (name && name[0])
snprintf(thr_name_, sizeof(thr_name_), "%.15s", name);
else
snprintf(thr_name_, sizeof(thr_name_), "DNSResolver");
}
void* run() override;
private:
wqueue<DNS_Resolve_Data*>& queue_;
char thr_name_[16] {};
};
// Return true if 'ip' is a valid IPv4 or IPv6 address string.
bool validate_ip(const std::string& ip);
// Return the IP address of the peer connected to 'socket_fd', or "" on
// failure / non-IP families.
std::string get_connected_peer_ip_from_socket(int socket_fd);
// Helper: stringify a list of IPs for debug logging. Defined inline because
// it's templated over the iterable type used by the various call sites.
template<class T>
std::string debug_iplisttostring(const T& ips) {
std::stringstream sstr;
for (const std::string& ip : ips)
sstr << ip << " ";
return sstr.str();
}
#endif // __CLASS_DNS_CACHE_H