#include "DNS_Cache.hpp" #include #include #include #include #include #include #include "proxysql.h" #include "proxysql_debug.h" #include "proxysql_utils.h" #include "proxysql_glovars.hpp" #include "gen_utils.h" #include 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_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 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(dns_resolve_data->ttl * 0.025); std::uniform_int_distribution 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(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(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(queue_.remove()); if (item == nullptr) { // Sentinel: caller pushed NULL to ask the worker to exit. break; } std::vector 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&& 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&& 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; }