blob: b79f74a4bdc8e93d99bdda7ce9321a2ae22a730d [file] [log] [blame]
/** @file
A brief file description
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "Main.h"
#include "P_HostDB.h"
#include "P_RefCountCacheSerializer.h"
#include "tscore/I_Layout.h"
#include "Show.h"
#include "tscore/Tokenizer.h"
#include "tscore/ink_apidefs.h"
#include <utility>
#include <vector>
#include <algorithm>
#include <random>
#include <chrono>
HostDBProcessor hostDBProcessor;
int HostDBProcessor::hostdb_strict_round_robin = 0;
int HostDBProcessor::hostdb_timed_round_robin = 0;
HostDBProcessor::Options const HostDBProcessor::DEFAULT_OPTIONS;
HostDBContinuation::Options const HostDBContinuation::DEFAULT_OPTIONS;
int hostdb_enable = true;
int hostdb_migrate_on_demand = true;
int hostdb_lookup_timeout = 30;
int hostdb_re_dns_on_reload = false;
int hostdb_ttl_mode = TTL_OBEY;
unsigned int hostdb_round_robin_max_count = 16;
unsigned int hostdb_ip_stale_interval = HOST_DB_IP_STALE;
unsigned int hostdb_ip_timeout_interval = HOST_DB_IP_TIMEOUT;
unsigned int hostdb_ip_fail_timeout_interval = HOST_DB_IP_FAIL_TIMEOUT;
unsigned int hostdb_serve_stale_but_revalidate = 0;
unsigned int hostdb_hostfile_check_interval = 86400; // 1 day
// Epoch timestamp of the current hosts file check.
ink_time_t hostdb_current_interval = 0;
// Epoch timestamp of the last time we actually checked for a hosts file update.
static ink_time_t hostdb_last_interval = 0;
// Epoch timestamp when we updated the hosts file last.
static ink_time_t hostdb_hostfile_update_timestamp = 0;
static char hostdb_filename[PATH_NAME_MAX] = DEFAULT_HOST_DB_FILENAME;
int hostdb_max_count = DEFAULT_HOST_DB_SIZE;
char hostdb_hostfile_path[PATH_NAME_MAX] = "";
int hostdb_sync_frequency = 0;
int hostdb_disable_reverse_lookup = 0;
int hostdb_max_iobuf_index = BUFFER_SIZE_INDEX_32K;
ClassAllocator<HostDBContinuation> hostDBContAllocator("hostDBContAllocator");
// Static configuration information
HostDBCache hostDB;
void ParseHostFile(const char *path, unsigned int interval);
char *
HostDBInfo::srvname(HostDBRoundRobin *rr) const
{
if (!is_srv || !data.srv.srv_offset) {
return nullptr;
}
return reinterpret_cast<char *>(rr) + data.srv.srv_offset;
}
static inline bool
is_addr_valid(uint8_t af, ///< Address family (format of data)
void *ptr ///< Raw address data (not a sockaddr variant!)
)
{
return (AF_INET == af && INADDR_ANY != *(reinterpret_cast<in_addr_t *>(ptr))) ||
(AF_INET6 == af && !IN6_IS_ADDR_UNSPECIFIED(reinterpret_cast<in6_addr *>(ptr)));
}
static inline void
ip_addr_set(sockaddr *ip, ///< Target storage, sockaddr compliant.
uint8_t af, ///< Address format.
void *ptr ///< Raw address data
)
{
if (AF_INET6 == af) {
ats_ip6_set(ip, *static_cast<in6_addr *>(ptr));
} else if (AF_INET == af) {
ats_ip4_set(ip, *static_cast<in_addr_t *>(ptr));
} else {
ats_ip_invalidate(ip);
}
}
static inline void
ip_addr_set(IpAddr &ip, ///< Target storage.
uint8_t af, ///< Address format.
void *ptr ///< Raw address data
)
{
if (AF_INET6 == af) {
ip = *static_cast<in6_addr *>(ptr);
} else if (AF_INET == af) {
ip = *static_cast<in_addr_t *>(ptr);
} else {
ip.invalidate();
}
}
inline void
hostdb_cont_free(HostDBContinuation *cont)
{
if (cont->pending_action) {
cont->pending_action->cancel();
}
if (cont->timeout) {
cont->timeout->cancel();
}
cont->mutex = nullptr;
cont->action.mutex = nullptr;
hostDBContAllocator.free(cont);
}
/* Check whether a resolution fail should lead to a retry.
The @a mark argument is updated if appropriate.
@return @c true if @a mark was updated, @c false if no retry should be done.
*/
static inline bool
check_for_retry(HostDBMark &mark, HostResStyle style)
{
bool zret = true;
if (HOSTDB_MARK_IPV4 == mark && HOST_RES_IPV4 == style) {
mark = HOSTDB_MARK_IPV6;
} else if (HOSTDB_MARK_IPV6 == mark && HOST_RES_IPV6 == style) {
mark = HOSTDB_MARK_IPV4;
} else {
zret = false;
}
return zret;
}
const char *
string_for(HostDBMark mark)
{
static const char *STRING[] = {"Generic", "IPv4", "IPv6", "SRV"};
return STRING[mark];
}
//
// Function Prototypes
//
static Action *register_ShowHostDB(Continuation *c, HTTPHdr *h);
HostDBHash &
HostDBHash::set_host(const char *name, int len)
{
host_name = name;
host_len = len;
if (host_name && SplitDNSConfig::isSplitDNSEnabled()) {
const char *scan;
// I think this is checking for a hostname that is just an address.
for (scan = host_name; *scan != '\0' && (ParseRules::is_digit(*scan) || '.' == *scan || ':' == *scan); ++scan) {
;
}
if ('\0' != *scan) {
// config is released in the destructor, because we must make sure values we
// get out of it don't evaporate while @a this is still around.
if (!pSD) {
pSD = SplitDNSConfig::acquire();
}
if (pSD) {
dns_server = static_cast<DNSServer *>(pSD->getDNSRecord(host_name));
}
} else {
dns_server = nullptr;
}
}
return *this;
}
void
HostDBHash::refresh()
{
CryptoContext ctx;
if (host_name) {
const char *server_line = dns_server ? dns_server->x_dns_ip_line : nullptr;
uint8_t m = static_cast<uint8_t>(db_mark); // be sure of the type.
ctx.update(host_name, host_len);
ctx.update(reinterpret_cast<uint8_t *>(&port), sizeof(port));
ctx.update(&m, sizeof(m));
if (server_line) {
ctx.update(server_line, strlen(server_line));
}
} else {
// CryptoHash the ip, pad on both sizes with 0's
// so that it does not intersect the string space
//
char buff[TS_IP6_SIZE + 4];
int n = ip.isIp6() ? sizeof(in6_addr) : sizeof(in_addr_t);
memset(buff, 0, 2);
memcpy(buff + 2, ip._addr._byte, n);
memset(buff + 2 + n, 0, 2);
ctx.update(buff, n + 4);
}
ctx.finalize(hash);
}
HostDBHash::HostDBHash() {}
HostDBHash::~HostDBHash()
{
if (pSD) {
SplitDNSConfig::release(pSD);
}
}
HostDBCache::HostDBCache()
{
hosts_file_ptr = new RefCountedHostsFileMap();
}
bool
HostDBCache::is_pending_dns_for_hash(const CryptoHash &hash)
{
Queue<HostDBContinuation> &q = pending_dns_for_hash(hash);
for (HostDBContinuation *c = q.head; c; c = static_cast<HostDBContinuation *>(c->link.next)) {
if (hash == c->hash.hash) {
return true;
}
}
return false;
}
HostDBCache *
HostDBProcessor::cache()
{
return &hostDB;
}
struct HostDBBackgroundTask : public Continuation {
int frequency;
ink_hrtime start_time;
virtual int sync_event(int event, void *edata) = 0;
int wait_event(int event, void *edata);
HostDBBackgroundTask(int frequency);
};
HostDBBackgroundTask::HostDBBackgroundTask(int frequency) : Continuation(new_ProxyMutex()), frequency(frequency), start_time(0)
{
SET_HANDLER(&HostDBBackgroundTask::sync_event);
}
int
HostDBBackgroundTask::wait_event(int, void *)
{
ink_hrtime next_sync = HRTIME_SECONDS(this->frequency) - (Thread::get_hrtime() - start_time);
SET_HANDLER(&HostDBBackgroundTask::sync_event);
if (next_sync > HRTIME_MSECONDS(100)) {
eventProcessor.schedule_in(this, next_sync, ET_TASK);
} else {
eventProcessor.schedule_imm(this, ET_TASK);
}
return EVENT_DONE;
}
struct HostDBSync : public HostDBBackgroundTask {
std::string storage_path;
std::string full_path;
HostDBSync(int frequency, const std::string &storage_path, const std::string &full_path)
: HostDBBackgroundTask(frequency), storage_path(std::move(storage_path)), full_path(std::move(full_path)){};
int
sync_event(int, void *) override
{
SET_HANDLER(&HostDBSync::wait_event);
start_time = Thread::get_hrtime();
new RefCountCacheSerializer<HostDBInfo>(this, hostDBProcessor.cache()->refcountcache, this->frequency, this->storage_path,
this->full_path);
return EVENT_DONE;
}
};
int
HostDBCache::start(int flags)
{
(void)flags; // unused
char storage_path[PATH_NAME_MAX];
MgmtInt hostdb_max_size = 0;
int hostdb_partitions = 64;
storage_path[0] = '\0';
// Read configuration
// Command line overrides manager configuration.
//
REC_ReadConfigInt32(hostdb_enable, "proxy.config.hostdb");
REC_ReadConfigString(storage_path, "proxy.config.hostdb.storage_path", sizeof(storage_path));
REC_ReadConfigString(hostdb_filename, "proxy.config.hostdb.filename", sizeof(hostdb_filename));
// Max number of items
REC_ReadConfigInt32(hostdb_max_count, "proxy.config.hostdb.max_count");
// max size allowed to use
REC_ReadConfigInteger(hostdb_max_size, "proxy.config.hostdb.max_size");
// number of partitions
REC_ReadConfigInt32(hostdb_partitions, "proxy.config.hostdb.partitions");
// how often to sync hostdb to disk
REC_EstablishStaticConfigInt32(hostdb_sync_frequency, "proxy.config.cache.hostdb.sync_frequency");
REC_EstablishStaticConfigInt32(hostdb_max_iobuf_index, "proxy.config.hostdb.io.max_buffer_index");
if (hostdb_max_size == 0) {
Fatal("proxy.config.hostdb.max_size must be a non-zero number");
}
// Setup the ref-counted cache (this must be done regardless of syncing or not).
this->refcountcache = new RefCountCache<HostDBInfo>(hostdb_partitions, hostdb_max_size, hostdb_max_count, HostDBInfo::version(),
"proxy.process.hostdb.cache.");
//
// Load and sync HostDB, if we've asked for it.
//
if (hostdb_sync_frequency > 0) {
// If proxy.config.hostdb.storage_path is not set, use the local state dir. If it is set to
// a relative path, make it relative to the prefix.
if (storage_path[0] == '\0') {
ats_scoped_str rundir(RecConfigReadRuntimeDir());
ink_strlcpy(storage_path, rundir, sizeof(storage_path));
} else if (storage_path[0] != '/') {
Layout::relative_to(storage_path, sizeof(storage_path), Layout::get()->prefix, storage_path);
}
Debug("hostdb", "Storage path is %s", storage_path);
if (access(storage_path, W_OK | R_OK) == -1) {
Warning("Unable to access() directory '%s': %d, %s", storage_path, errno, strerror(errno));
Warning("Please set 'proxy.config.hostdb.storage_path' or 'proxy.config.local_state_dir'");
}
// Combine the path and name
char full_path[2 * PATH_NAME_MAX];
ink_filepath_make(full_path, 2 * PATH_NAME_MAX, storage_path, hostdb_filename);
Debug("hostdb", "Opening %s, partitions=%d storage_size=%" PRIu64 " items=%d", full_path, hostdb_partitions, hostdb_max_size,
hostdb_max_count);
int load_ret = LoadRefCountCacheFromPath<HostDBInfo>(*this->refcountcache, storage_path, full_path, HostDBInfo::unmarshall);
if (load_ret != 0) {
Warning("Error loading cache from %s: %d", full_path, load_ret);
}
eventProcessor.schedule_imm(new HostDBSync(hostdb_sync_frequency, storage_path, full_path), ET_TASK);
}
this->pending_dns = new Queue<HostDBContinuation, Continuation::Link_link>[hostdb_partitions];
this->remoteHostDBQueue = new Queue<HostDBContinuation, Continuation::Link_link>[hostdb_partitions];
return 0;
}
// Start up the Host Database processor.
// Load configuration, register configuration and statistics and
// open the cache. This doesn't create any threads, so those
// parameters are ignored.
//
int
HostDBProcessor::start(int, size_t)
{
if (hostDB.start(0) < 0) {
return -1;
}
if (auto_clear_hostdb_flag) {
hostDB.refcountcache->clear();
}
statPagesManager.register_http("hostdb", register_ShowHostDB);
//
// Register configuration callback, and establish configuration links
//
REC_EstablishStaticConfigInt32(hostdb_ttl_mode, "proxy.config.hostdb.ttl_mode");
REC_EstablishStaticConfigInt32(hostdb_disable_reverse_lookup, "proxy.config.cache.hostdb.disable_reverse_lookup");
REC_EstablishStaticConfigInt32(hostdb_re_dns_on_reload, "proxy.config.hostdb.re_dns_on_reload");
REC_EstablishStaticConfigInt32(hostdb_migrate_on_demand, "proxy.config.hostdb.migrate_on_demand");
REC_EstablishStaticConfigInt32(hostdb_strict_round_robin, "proxy.config.hostdb.strict_round_robin");
REC_EstablishStaticConfigInt32(hostdb_timed_round_robin, "proxy.config.hostdb.timed_round_robin");
REC_EstablishStaticConfigInt32(hostdb_lookup_timeout, "proxy.config.hostdb.lookup_timeout");
REC_EstablishStaticConfigInt32U(hostdb_ip_timeout_interval, "proxy.config.hostdb.timeout");
REC_EstablishStaticConfigInt32U(hostdb_ip_stale_interval, "proxy.config.hostdb.verify_after");
REC_EstablishStaticConfigInt32U(hostdb_ip_fail_timeout_interval, "proxy.config.hostdb.fail.timeout");
REC_EstablishStaticConfigInt32U(hostdb_serve_stale_but_revalidate, "proxy.config.hostdb.serve_stale_for");
REC_EstablishStaticConfigInt32U(hostdb_hostfile_check_interval, "proxy.config.hostdb.host_file.interval");
REC_EstablishStaticConfigInt32U(hostdb_round_robin_max_count, "proxy.config.hostdb.round_robin_max_count");
//
// Set up hostdb_current_interval
//
hostdb_current_interval = ink_time();
HostDBContinuation *b = hostDBContAllocator.alloc();
SET_CONTINUATION_HANDLER(b, (HostDBContHandler)&HostDBContinuation::backgroundEvent);
b->mutex = new_ProxyMutex();
eventProcessor.schedule_every(b, HRTIME_SECONDS(1), ET_DNS);
return 0;
}
void
HostDBContinuation::init(HostDBHash const &the_hash, Options const &opt)
{
hash = the_hash;
if (hash.host_name) {
// copy to backing store.
if (hash.host_len > static_cast<int>(sizeof(hash_host_name_store) - 1)) {
hash.host_len = sizeof(hash_host_name_store) - 1;
}
memcpy(hash_host_name_store, hash.host_name, hash.host_len);
} else {
hash.host_len = 0;
}
hash_host_name_store[hash.host_len] = 0;
hash.host_name = hash_host_name_store;
host_res_style = opt.host_res_style;
dns_lookup_timeout = opt.timeout;
mutex = hostDB.refcountcache->lock_for_key(hash.hash.fold());
if (opt.cont) {
action = opt.cont;
} else {
// ink_assert(!"this sucks");
ink_zero(action);
action.mutex = mutex;
}
}
void
HostDBContinuation::refresh_hash()
{
Ptr<ProxyMutex> old_bucket_mutex = hostDB.refcountcache->lock_for_key(hash.hash.fold());
// We're not pending DNS anymore.
remove_trigger_pending_dns();
hash.refresh();
// Update the mutex if it's from the bucket.
// Some call sites modify this after calling @c init so need to check.
if (mutex == old_bucket_mutex) {
mutex = hostDB.refcountcache->lock_for_key(hash.hash.fold());
}
}
static bool
reply_to_cont(Continuation *cont, HostDBInfo *r, bool is_srv = false)
{
if (r == nullptr || r->is_srv != is_srv || r->is_failed()) {
cont->handleEvent(is_srv ? EVENT_SRV_LOOKUP : EVENT_HOST_DB_LOOKUP, nullptr);
return false;
}
if (r->reverse_dns) {
if (!r->hostname()) {
ink_assert(!"missing hostname");
cont->handleEvent(is_srv ? EVENT_SRV_LOOKUP : EVENT_HOST_DB_LOOKUP, nullptr);
Warning("bogus entry deleted from HostDB: missing hostname");
hostDB.refcountcache->erase(r->key);
return false;
}
Debug("hostdb", "hostname = %s", r->hostname());
}
if (!r->is_srv && r->round_robin) {
if (!r->rr()) {
ink_assert(!"missing round-robin");
cont->handleEvent(is_srv ? EVENT_SRV_LOOKUP : EVENT_HOST_DB_LOOKUP, nullptr);
Warning("bogus entry deleted from HostDB: missing round-robin");
hostDB.refcountcache->erase(r->key);
return false;
}
ip_text_buffer ipb;
Debug("hostdb", "RR of %d with %d good, 1st IP = %s", r->rr()->rrcount, r->rr()->good, ats_ip_ntop(r->ip(), ipb, sizeof ipb));
}
cont->handleEvent(is_srv ? EVENT_SRV_LOOKUP : EVENT_HOST_DB_LOOKUP, r);
return true;
}
inline HostResStyle
host_res_style_for(sockaddr const *ip)
{
return ats_is_ip6(ip) ? HOST_RES_IPV6_ONLY : HOST_RES_IPV4_ONLY;
}
inline HostResStyle
host_res_style_for(HostDBMark mark)
{
return HOSTDB_MARK_IPV4 == mark ? HOST_RES_IPV4_ONLY : HOSTDB_MARK_IPV6 == mark ? HOST_RES_IPV6_ONLY : HOST_RES_NONE;
}
inline HostDBMark
db_mark_for(HostResStyle style)
{
HostDBMark zret = HOSTDB_MARK_GENERIC;
if (HOST_RES_IPV4 == style || HOST_RES_IPV4_ONLY == style) {
zret = HOSTDB_MARK_IPV4;
} else if (HOST_RES_IPV6 == style || HOST_RES_IPV6_ONLY == style) {
zret = HOSTDB_MARK_IPV6;
}
return zret;
}
inline HostDBMark
db_mark_for(sockaddr const *ip)
{
return ats_is_ip6(ip) ? HOSTDB_MARK_IPV6 : HOSTDB_MARK_IPV4;
}
inline HostDBMark
db_mark_for(IpAddr const &ip)
{
return ip.isIp6() ? HOSTDB_MARK_IPV6 : HOSTDB_MARK_IPV4;
}
Ptr<HostDBInfo>
probe(const Ptr<ProxyMutex> &mutex, HostDBHash const &hash, bool ignore_timeout)
{
// If hostdb is disabled, don't return anything
if (!hostdb_enable) {
return Ptr<HostDBInfo>();
}
// Otherwise HostDB is enabled, so we'll do our thing
ink_assert(this_ethread() == hostDB.refcountcache->lock_for_key(hash.hash.fold())->thread_holding);
uint64_t folded_hash = hash.hash.fold();
// get the item from cache
Ptr<HostDBInfo> r = hostDB.refcountcache->get(folded_hash);
// If there was nothing in the cache-- this is a miss
if (r.get() == nullptr) {
return r;
}
// If the dns response was failed, and we've hit the failed timeout, lets stop returning it
if (r->is_failed() && r->is_ip_fail_timeout()) {
return make_ptr((HostDBInfo *)nullptr);
// if we aren't ignoring timeouts, and we are past it-- then remove the item
} else if (!ignore_timeout && r->is_ip_timeout() && !r->serve_stale_but_revalidate()) {
HOSTDB_INCREMENT_DYN_STAT(hostdb_ttl_expires_stat);
return make_ptr((HostDBInfo *)nullptr);
}
// If the record is stale, but we want to revalidate-- lets start that up
if ((!ignore_timeout && r->is_ip_stale() && !r->reverse_dns) || (r->is_ip_timeout() && r->serve_stale_but_revalidate())) {
if (hostDB.is_pending_dns_for_hash(hash.hash)) {
Debug("hostdb", "stale %u %u %u, using it and pending to refresh it", r->ip_interval(), r->ip_timestamp,
r->ip_timeout_interval);
return r;
}
Debug("hostdb", "stale %u %u %u, using it and refreshing it", r->ip_interval(), r->ip_timestamp, r->ip_timeout_interval);
HostDBContinuation *c = hostDBContAllocator.alloc();
HostDBContinuation::Options copt;
copt.host_res_style = host_res_style_for(r->ip());
c->init(hash, copt);
c->do_dns();
}
return r;
}
//
// Insert a HostDBInfo into the database
// A null value indicates that the block is empty.
//
HostDBInfo *
HostDBContinuation::insert(unsigned int attl)
{
uint64_t folded_hash = hash.hash.fold();
ink_assert(this_ethread() == hostDB.refcountcache->lock_for_key(folded_hash)->thread_holding);
HostDBInfo *r = HostDBInfo::alloc();
r->key = folded_hash;
r->ip_timestamp = hostdb_current_interval;
r->ip_timeout_interval = std::clamp(attl, 1u, HOST_DB_MAX_TTL);
Debug("hostdb", "inserting for: %.*s: (hash: %" PRIx64 ") now: %u timeout: %u ttl: %u", hash.host_len, hash.host_name,
folded_hash, r->ip_timestamp, r->ip_timeout_interval, attl);
hostDB.refcountcache->put(folded_hash, r, 0, r->expiry_time());
return r;
}
//
// Get an entry by either name or IP
//
Action *
HostDBProcessor::getby(Continuation *cont, cb_process_result_pfn cb_process_result, HostDBHash &hash, Options const &opt)
{
bool force_dns = false;
EThread *thread = this_ethread();
Ptr<ProxyMutex> mutex = thread->mutex;
ip_text_buffer ipb;
if (opt.flags & HOSTDB_FORCE_DNS_ALWAYS) {
force_dns = true;
} else if (opt.flags & HOSTDB_FORCE_DNS_RELOAD) {
force_dns = hostdb_re_dns_on_reload;
if (force_dns) {
HOSTDB_INCREMENT_DYN_STAT(hostdb_re_dns_on_reload_stat);
}
}
HOSTDB_INCREMENT_DYN_STAT(hostdb_total_lookups_stat);
if (!hostdb_enable || // if the HostDB is disabled,
(hash.host_name && !*hash.host_name) || // or host_name is empty string
(hostdb_disable_reverse_lookup && hash.ip.isValid())) { // or try to lookup by ip address when the reverse lookup disabled
if (cb_process_result) {
(cont->*cb_process_result)(nullptr);
} else {
MUTEX_TRY_LOCK(lock, cont->mutex, thread);
if (!lock.is_locked()) {
goto Lretry;
}
cont->handleEvent(EVENT_HOST_DB_LOOKUP, nullptr);
}
return ACTION_RESULT_DONE;
}
// Attempt to find the result in-line, for level 1 hits
if (!force_dns) {
MUTEX_TRY_LOCK(lock, cont->mutex, thread);
bool loop = lock.is_locked();
while (loop) {
loop = false; // Only loop on explicit set for retry.
// find the partition lock
Ptr<ProxyMutex> bucket_mutex = hostDB.refcountcache->lock_for_key(hash.hash.fold());
MUTEX_TRY_LOCK(lock2, bucket_mutex, thread);
if (lock2.is_locked()) {
// If we can get the lock and a level 1 probe succeeds, return
Ptr<HostDBInfo> r = probe(bucket_mutex, hash, false);
if (r) {
// fail, see if we should retry with alternate
if (hash.db_mark != HOSTDB_MARK_SRV && r->is_failed() && hash.host_name) {
loop = check_for_retry(hash.db_mark, opt.host_res_style);
}
if (!loop) {
// No retry -> final result. Return it.
if (hash.db_mark == HOSTDB_MARK_SRV) {
Debug("hostdb", "immediate SRV answer for %.*s from hostdb", hash.host_len, hash.host_name);
Debug("dns_srv", "immediate SRV answer for %.*s from hostdb", hash.host_len, hash.host_name);
} else if (hash.host_name) {
Debug("hostdb", "immediate answer for %.*s", hash.host_len, hash.host_name);
} else {
Debug("hostdb", "immediate answer for %s", hash.ip.isValid() ? hash.ip.toString(ipb, sizeof ipb) : "<null>");
}
HOSTDB_INCREMENT_DYN_STAT(hostdb_total_hits_stat);
if (cb_process_result) {
(cont->*cb_process_result)(r.get());
} else {
reply_to_cont(cont, r.get());
}
return ACTION_RESULT_DONE;
}
hash.refresh(); // only on reloop, because we've changed the family.
}
}
}
}
if (hash.db_mark == HOSTDB_MARK_SRV) {
Debug("hostdb", "delaying (force=%d) SRV answer for %.*s [timeout = %d]", force_dns, hash.host_len, hash.host_name,
opt.timeout);
Debug("dns_srv", "delaying (force=%d) SRV answer for %.*s [timeout = %d]", force_dns, hash.host_len, hash.host_name,
opt.timeout);
} else if (hash.host_name) {
Debug("hostdb", "delaying (force=%d) answer for %.*s [timeout %d]", force_dns, hash.host_len, hash.host_name, opt.timeout);
} else {
Debug("hostdb", "delaying (force=%d) answer for %s [timeout %d]", force_dns,
hash.ip.isValid() ? hash.ip.toString(ipb, sizeof ipb) : "<null>", opt.timeout);
}
Lretry:
// Otherwise, create a continuation to do a deeper probe in the background
//
HostDBContinuation *c = hostDBContAllocator.alloc();
HostDBContinuation::Options copt;
copt.timeout = opt.timeout;
copt.force_dns = force_dns;
copt.cont = cont;
copt.host_res_style = (hash.db_mark == HOSTDB_MARK_SRV) ? HOST_RES_NONE : opt.host_res_style;
c->init(hash, copt);
SET_CONTINUATION_HANDLER(c, (HostDBContHandler)&HostDBContinuation::probeEvent);
thread->schedule_in(c, MUTEX_RETRY_DELAY);
return &c->action;
}
// Wrapper from getbyname to getby
//
Action *
HostDBProcessor::getbyname_re(Continuation *cont, const char *ahostname, int len, Options const &opt)
{
HostDBHash hash;
ink_assert(nullptr != ahostname);
// Load the hash data.
hash.set_host(ahostname, ahostname ? (len ? len : strlen(ahostname)) : 0);
// Leave hash.ip invalid
hash.port = 0;
hash.db_mark = db_mark_for(opt.host_res_style);
hash.refresh();
return getby(cont, nullptr, hash, opt);
}
Action *
HostDBProcessor::getbynameport_re(Continuation *cont, const char *ahostname, int len, Options const &opt)
{
HostDBHash hash;
ink_assert(nullptr != ahostname);
// Load the hash data.
hash.set_host(ahostname, ahostname ? (len ? len : strlen(ahostname)) : 0);
// Leave hash.ip invalid
hash.port = opt.port;
hash.db_mark = db_mark_for(opt.host_res_style);
hash.refresh();
return getby(cont, nullptr, hash, opt);
}
// Lookup Hostinfo by addr
Action *
HostDBProcessor::getbyaddr_re(Continuation *cont, sockaddr const *aip)
{
HostDBHash hash;
ink_assert(nullptr != aip);
HostDBProcessor::Options opt;
opt.host_res_style = HOST_RES_NONE;
// Leave hash.host_name as nullptr
hash.ip.assign(aip);
hash.port = ats_ip_port_host_order(aip);
hash.db_mark = db_mark_for(opt.host_res_style);
hash.refresh();
return getby(cont, nullptr, hash, opt);
}
/* Support SRV records */
Action *
HostDBProcessor::getSRVbyname_imm(Continuation *cont, cb_process_result_pfn process_srv_info, const char *hostname, int len,
Options const &opt)
{
ink_assert(cont->mutex->thread_holding == this_ethread());
HostDBHash hash;
ink_assert(nullptr != hostname);
hash.set_host(hostname, len ? len : strlen(hostname));
// Leave hash.ip invalid
hash.port = 0;
hash.db_mark = HOSTDB_MARK_SRV;
hash.refresh();
return getby(cont, process_srv_info, hash, opt);
}
// Wrapper from getbyname to getby
//
Action *
HostDBProcessor::getbyname_imm(Continuation *cont, cb_process_result_pfn process_hostdb_info, const char *hostname, int len,
Options const &opt)
{
ink_assert(cont->mutex->thread_holding == this_ethread());
HostDBHash hash;
ink_assert(nullptr != hostname);
hash.set_host(hostname, len ? len : strlen(hostname));
// Leave hash.ip invalid
// TODO: May I rename the wrapper name to getbynameport_imm ? - oknet
// By comparing getbyname_re and getbynameport_re, the hash.port should be 0 if only get hostinfo by name.
hash.port = opt.port;
hash.db_mark = db_mark_for(opt.host_res_style);
hash.refresh();
return getby(cont, process_hostdb_info, hash, opt);
}
Action *
HostDBProcessor::iterate(Continuation *cont)
{
ink_assert(cont->mutex->thread_holding == this_ethread());
EThread *thread = cont->mutex->thread_holding;
ProxyMutex *mutex = thread->mutex.get();
HOSTDB_INCREMENT_DYN_STAT(hostdb_total_lookups_stat);
HostDBContinuation *c = hostDBContAllocator.alloc();
HostDBContinuation::Options copt;
copt.cont = cont;
copt.force_dns = false;
copt.timeout = 0;
copt.host_res_style = HOST_RES_NONE;
c->init(HostDBHash(), copt);
c->current_iterate_pos = 0;
SET_CONTINUATION_HANDLER(c, (HostDBContHandler)&HostDBContinuation::iterateEvent);
thread->schedule_in(c, HOST_DB_RETRY_PERIOD);
return &c->action;
}
static void
do_setby(HostDBInfo *r, HostDBApplicationInfo *app, const char *hostname, IpAddr const &ip, bool is_srv = false)
{
HostDBRoundRobin *rr = r->rr();
if (is_srv && (!r->is_srv || !rr)) {
return;
}
if (rr) {
if (is_srv) {
uint32_t key = makeHostHash(hostname);
for (int i = 0; i < rr->rrcount; i++) {
if (key == rr->info(i).data.srv.key && !strcmp(hostname, rr->info(i).srvname(rr))) {
Debug("hostdb", "immediate setby for %s", hostname);
rr->info(i).app.allotment.application1 = app->allotment.application1;
rr->info(i).app.allotment.application2 = app->allotment.application2;
return;
}
}
} else {
for (int i = 0; i < rr->rrcount; i++) {
if (rr->info(i).ip() == ip) {
Debug("hostdb", "immediate setby for %s", hostname ? hostname : "<addr>");
rr->info(i).app.allotment.application1 = app->allotment.application1;
rr->info(i).app.allotment.application2 = app->allotment.application2;
return;
}
}
}
} else {
if (r->reverse_dns || (!r->round_robin && ip == r->ip())) {
Debug("hostdb", "immediate setby for %s", hostname ? hostname : "<addr>");
r->app.allotment.application1 = app->allotment.application1;
r->app.allotment.application2 = app->allotment.application2;
}
}
}
void
HostDBProcessor::setby(const char *hostname, int len, sockaddr const *ip, HostDBApplicationInfo *app)
{
if (!hostdb_enable) {
return;
}
HostDBHash hash;
hash.set_host(hostname, hostname ? (len ? len : strlen(hostname)) : 0);
hash.ip.assign(ip);
hash.port = ip ? ats_ip_port_host_order(ip) : 0;
hash.db_mark = db_mark_for(ip);
hash.refresh();
// Attempt to find the result in-line, for level 1 hits
Ptr<ProxyMutex> mutex = hostDB.refcountcache->lock_for_key(hash.hash.fold());
EThread *thread = this_ethread();
MUTEX_TRY_LOCK(lock, mutex, thread);
if (lock.is_locked()) {
Ptr<HostDBInfo> r = probe(mutex, hash, false);
if (r) {
do_setby(r.get(), app, hostname, hash.ip);
}
return;
}
// Create a continuation to do a deeper probe in the background
HostDBContinuation *c = hostDBContAllocator.alloc();
c->init(hash);
c->app.allotment.application1 = app->allotment.application1;
c->app.allotment.application2 = app->allotment.application2;
SET_CONTINUATION_HANDLER(c, (HostDBContHandler)&HostDBContinuation::setbyEvent);
thread->schedule_in(c, MUTEX_RETRY_DELAY);
}
void
HostDBProcessor::setby_srv(const char *hostname, int len, const char *target, HostDBApplicationInfo *app)
{
if (!hostdb_enable || !hostname || !target) {
return;
}
HostDBHash hash;
hash.set_host(hostname, len ? len : strlen(hostname));
hash.port = 0;
hash.db_mark = HOSTDB_MARK_SRV;
hash.refresh();
// Create a continuation to do a deeper probe in the background
HostDBContinuation *c = hostDBContAllocator.alloc();
c->init(hash);
ink_strlcpy(c->srv_target_name, target, MAXDNAME);
c->app.allotment.application1 = app->allotment.application1;
c->app.allotment.application2 = app->allotment.application2;
SET_CONTINUATION_HANDLER(c, (HostDBContHandler)&HostDBContinuation::setbyEvent);
eventProcessor.schedule_imm(c);
}
int
HostDBContinuation::setbyEvent(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
{
Ptr<HostDBInfo> r = probe(mutex, hash, false);
if (r) {
do_setby(r.get(), &app, hash.host_name, hash.ip, is_srv());
}
hostdb_cont_free(this);
return EVENT_DONE;
}
static bool
remove_round_robin(HostDBInfo *r, const char *hostname, IpAddr const &ip)
{
if (r) {
if (!r->round_robin) {
return false;
}
HostDBRoundRobin *rr = r->rr();
if (!rr) {
return false;
}
for (int i = 0; i < rr->good; i++) {
if (ip == rr->info(i).ip()) {
ip_text_buffer b;
Debug("hostdb", "Deleting %s from '%s' round robin DNS entry", ip.toString(b, sizeof b), hostname);
HostDBInfo tmp = rr->info(i);
rr->info(i) = rr->info(rr->good - 1);
rr->info(rr->good - 1) = tmp;
rr->good--;
if (rr->good <= 0) {
hostDB.refcountcache->erase(r->key);
return false;
} else {
if (is_debug_tag_set("hostdb")) {
int bufsize = rr->good * INET6_ADDRSTRLEN;
char *rr_ip_list = static_cast<char *>(alloca(bufsize));
char *p = rr_ip_list;
for (int n = 0; n < rr->good; ++n) {
ats_ip_ntop(rr->info(n).ip(), p, bufsize);
int nbytes = strlen(p);
p += nbytes;
bufsize -= nbytes;
}
Note("'%s' round robin DNS entry updated, entries=%d, IP list: %s", hostname, rr->good, rr_ip_list);
}
}
return true;
}
}
}
return false;
}
int
HostDBContinuation::removeEvent(int /* event ATS_UNUSED */, Event *e)
{
Continuation *cont = action.continuation;
Ptr<ProxyMutex> proxy_mutex;
if (cont) {
proxy_mutex = cont->mutex;
}
WEAK_MUTEX_TRY_LOCK(lock, proxy_mutex, e->ethread);
if (!lock.is_locked()) {
e->schedule_in(HOST_DB_RETRY_PERIOD);
return EVENT_CONT;
}
if (!action.cancelled) {
if (!hostdb_enable) {
if (cont) {
cont->handleEvent(EVENT_HOST_DB_IP_REMOVED, (void *)nullptr);
}
} else {
Ptr<HostDBInfo> r = probe(mutex, hash, false);
bool res = remove_round_robin(r.get(), hash.host_name, hash.ip);
if (cont) {
cont->handleEvent(EVENT_HOST_DB_IP_REMOVED, res ? static_cast<void *>(&hash.ip) : static_cast<void *>(nullptr));
}
}
}
hostdb_cont_free(this);
return EVENT_DONE;
}
// Lookup done, insert into the local table, return data to the
// calling continuation.
// NOTE: if "i" exists it means we already allocated the space etc, just return
//
HostDBInfo *
HostDBContinuation::lookup_done(IpAddr const &ip, const char *aname, bool around_robin, unsigned int ttl_seconds, SRVHosts *srv,
HostDBInfo *r)
{
ink_assert(this_ethread() == hostDB.refcountcache->lock_for_key(hash.hash.fold())->thread_holding);
if (!ip.isValid() || !aname || !aname[0]) {
if (is_byname()) {
Debug("hostdb", "lookup_done() failed for '%.*s'", hash.host_len, hash.host_name);
} else if (is_srv()) {
Debug("dns_srv", "SRV failed for '%.*s'", hash.host_len, hash.host_name);
} else {
ip_text_buffer b;
Debug("hostdb", "failed for %s", hash.ip.toString(b, sizeof b));
}
if (r == nullptr) {
r = insert(hostdb_ip_fail_timeout_interval);
} else {
r->ip_timestamp = hostdb_current_interval;
r->ip_timeout_interval = std::clamp(hostdb_ip_fail_timeout_interval, 1u, HOST_DB_MAX_TTL);
}
r->round_robin = false;
r->round_robin_elt = false;
r->is_srv = is_srv();
r->reverse_dns = !is_byname() && !is_srv();
r->set_failed();
return r;
} else {
switch (hostdb_ttl_mode) {
default:
ink_assert(!"bad TTL mode");
case TTL_OBEY:
break;
case TTL_IGNORE:
ttl_seconds = hostdb_ip_timeout_interval;
break;
case TTL_MIN:
if (hostdb_ip_timeout_interval < ttl_seconds) {
ttl_seconds = hostdb_ip_timeout_interval;
}
break;
case TTL_MAX:
if (hostdb_ip_timeout_interval > ttl_seconds) {
ttl_seconds = hostdb_ip_timeout_interval;
}
break;
}
HOSTDB_SUM_DYN_STAT(hostdb_ttl_stat, ttl_seconds);
if (r == nullptr) {
r = insert(ttl_seconds);
} else {
// update the TTL
r->ip_timestamp = hostdb_current_interval;
r->ip_timeout_interval = std::clamp(ttl_seconds, 1u, HOST_DB_MAX_TTL);
}
r->round_robin_elt = false; // only true for elements explicitly added as RR elements.
if (is_byname()) {
ip_text_buffer b;
Debug("hostdb", "done %s TTL %d", ip.toString(b, sizeof b), ttl_seconds);
ats_ip_set(r->ip(), ip);
r->round_robin = around_robin;
r->reverse_dns = false;
if (hash.host_name != aname) {
ink_strlcpy(hash_host_name_store, aname, sizeof(hash_host_name_store));
}
r->is_srv = false;
} else if (is_srv()) {
ink_assert(srv && srv->hosts.size() && srv->hosts.size() <= hostdb_round_robin_max_count && around_robin);
r->data.srv.srv_offset = srv->hosts.size();
r->reverse_dns = false;
r->is_srv = true;
r->round_robin = around_robin;
if (hash.host_name != aname) {
ink_strlcpy(hash_host_name_store, aname, sizeof(hash_host_name_store));
}
} else {
Debug("hostdb", "done '%s' TTL %d", aname, ttl_seconds);
// TODO: check that this is right, it seems that the 2 hostnames are always the same
r->data.hostname_offset = r->hostname_offset;
// TODO: consolidate into a single "item type" field?
r->round_robin = false;
r->reverse_dns = true;
r->is_srv = false;
}
}
ink_assert(!r->round_robin || !r->reverse_dns);
return r;
}
int
HostDBContinuation::dnsPendingEvent(int event, Event *e)
{
ink_assert(this_ethread() == hostDB.refcountcache->lock_for_key(hash.hash.fold())->thread_holding);
if (timeout) {
timeout->cancel(this);
timeout = nullptr;
}
if (event == EVENT_INTERVAL) {
// we timed out, return a failure to the user
MUTEX_TRY_LOCK(lock, action.mutex, ((Event *)e)->ethread);
if (!lock.is_locked()) {
timeout = eventProcessor.schedule_in(this, HOST_DB_RETRY_PERIOD);
return EVENT_CONT;
}
if (!action.cancelled && action.continuation) {
action.continuation->handleEvent(EVENT_HOST_DB_LOOKUP, nullptr);
}
hostDB.pending_dns_for_hash(hash.hash).remove(this);
hostdb_cont_free(this);
return EVENT_DONE;
} else {
SET_HANDLER((HostDBContHandler)&HostDBContinuation::probeEvent);
return probeEvent(EVENT_INTERVAL, nullptr);
}
}
// for a new HostDBInfo `r`, "inherit" from the old version of yourself if it exists in `old_rr_data`
static int
restore_info(HostDBInfo *r, HostDBInfo *old_r, HostDBInfo &old_info, HostDBRoundRobin *old_rr_data)
{
if (old_rr_data) {
for (int j = 0; j < old_rr_data->rrcount; j++) {
if (ats_ip_addr_eq(old_rr_data->info(j).ip(), r->ip())) {
r->app = old_rr_data->info(j).app;
return true;
}
}
} else if (old_r) {
if (ats_ip_addr_eq(old_info.ip(), r->ip())) {
r->app = old_info.app;
return true;
}
}
return false;
}
// DNS lookup result state
//
int
HostDBContinuation::dnsEvent(int event, HostEnt *e)
{
ink_assert(this_ethread() == hostDB.refcountcache->lock_for_key(hash.hash.fold())->thread_holding);
if (timeout) {
timeout->cancel(this);
timeout = nullptr;
}
EThread *thread = mutex->thread_holding;
if (event != DNS_EVENT_LOOKUP) {
// This was an event_interval or an event_immediate
// Either we timed out, or remove_trigger_pending gave up on us
if (!action.continuation) {
// give up on insert, it has been too long
hostDB.pending_dns_for_hash(hash.hash).remove(this);
hostdb_cont_free(this);
return EVENT_DONE;
}
MUTEX_TRY_LOCK(lock, action.mutex, thread);
if (!lock.is_locked()) {
timeout = thread->schedule_in(this, HOST_DB_RETRY_PERIOD);
return EVENT_CONT;
}
// [amc] Callback to client to indicate a failure due to timeout.
// We don't try a different family here because a timeout indicates
// a server issue that won't be fixed by asking for a different
// address family.
if (!action.cancelled && action.continuation) {
action.continuation->handleEvent(EVENT_HOST_DB_LOOKUP, nullptr);
}
action = nullptr;
return EVENT_DONE;
} else {
bool failed = !e || !e->good;
bool is_rr = false;
pending_action = nullptr;
if (is_srv()) {
is_rr = !failed && (e->srv_hosts.hosts.size() > 0);
} else if (!failed) {
is_rr = nullptr != e->ent.h_addr_list[1];
} else {
}
ttl = failed ? 0 : e->ttl / 60;
int ttl_seconds = failed ? 0 : e->ttl; // ebalsa: moving to second accuracy
Ptr<HostDBInfo> old_r = probe(mutex, hash, false);
// If the DNS lookup failed with NXDOMAIN, remove the old record
if (e && e->isNameError() && old_r) {
hostDB.refcountcache->erase(old_r->key);
old_r = nullptr;
Debug("hostdb", "Removing the old record when the DNS lookup failed with NXDOMAIN");
}
HostDBInfo old_info;
if (old_r) {
old_info = *old_r.get();
}
HostDBRoundRobin *old_rr_data = old_r ? old_r->rr() : nullptr;
int valid_records = 0;
void *first_record = nullptr;
uint8_t af = e ? e->ent.h_addrtype : AF_UNSPEC; // address family
// if this is an RR response, we need to find the first record, as well as the
// total number of records
if (is_rr) {
if (is_srv() && !failed) {
valid_records = e->srv_hosts.hosts.size();
} else {
void *ptr; // tmp for current entry.
for (int total_records = 0;
total_records < static_cast<int>(hostdb_round_robin_max_count) && nullptr != (ptr = e->ent.h_addr_list[total_records]);
++total_records) {
if (is_addr_valid(af, ptr)) {
if (!first_record) {
first_record = ptr;
}
// If we have found some records which are invalid, lets just shuffle around them.
// This way we'll end up with e->ent.h_addr_list with all the valid responses at
// the first `valid_records` slots
if (valid_records != total_records) {
e->ent.h_addr_list[valid_records] = e->ent.h_addr_list[total_records];
}
++valid_records;
} else {
Warning("Zero address removed from round-robin list for '%s'", hash.host_name);
}
}
if (!first_record) {
failed = true;
is_rr = false;
}
}
} else if (!failed) {
first_record = e->ent.h_addr_list[0];
} // else first is 0.
IpAddr tip; // temp storage if needed.
// In the event that the lookup failed (SOA response-- for example) we want to use hash.host_name, since it'll be ""
const char *aname = (failed || strlen(hash.host_name)) ? hash.host_name : e->ent.h_name;
const size_t s_size = strlen(aname) + 1;
const size_t rrsize = is_rr ? HostDBRoundRobin::size(valid_records, e->srv_hosts.srv_hosts_length) : 0;
// where in our block of memory we are
int offset = sizeof(HostDBInfo);
int allocSize = s_size + rrsize; // The extra space we need for the rest of the things
HostDBInfo *r = HostDBInfo::alloc(allocSize);
Debug("hostdb", "allocating %d bytes for %s with %d RR records at [%p]", allocSize, aname, valid_records, r);
// set up the record
r->key = hash.hash.fold(); // always set the key
r->hostname_offset = offset;
ink_strlcpy(r->perm_hostname(), aname, s_size);
offset += s_size;
// If the DNS lookup failed (errors such as SERVFAIL, etc.) but we have an old record
// which is okay with being served stale-- lets continue to serve the stale record as long as
// the record is willing to be served.
bool serve_stale = false;
if (failed && old_r && old_r->serve_stale_but_revalidate()) {
r->free();
r = old_r.get();
serve_stale = true;
} else if (is_byname()) {
if (first_record) {
ip_addr_set(tip, af, first_record);
}
r = lookup_done(tip, hash.host_name, is_rr, ttl_seconds, failed ? nullptr : &e->srv_hosts, r);
} else if (is_srv()) {
if (!failed) {
tip._family = AF_INET; // force the tip valid, or else the srv will fail
}
r = lookup_done(tip, /* junk: FIXME: is the code in lookup_done() wrong to NEED this? */
hash.host_name, /* hostname */
is_rr, /* is round robin, doesnt matter for SRV since we recheck getCount() inside lookup_done() */
ttl_seconds, /* ttl in seconds */
failed ? nullptr : &e->srv_hosts, r);
} else if (failed) {
r = lookup_done(tip, hash.host_name, false, ttl_seconds, nullptr, r);
} else {
r = lookup_done(hash.ip, e->ent.h_name, false, ttl_seconds, &e->srv_hosts, r);
}
// Conditionally make rr record entries
if (is_rr) {
r->app.rr.offset = offset;
// This will only be set if is_rr
HostDBRoundRobin *rr_data = static_cast<HostDBRoundRobin *>(r->rr());
;
if (is_srv()) {
int skip = 0;
char *pos = reinterpret_cast<char *>(rr_data) + sizeof(HostDBRoundRobin) + valid_records * sizeof(HostDBInfo);
SRV *q[valid_records];
ink_assert(valid_records <= (int)hostdb_round_robin_max_count);
// sort
for (int i = 0; i < valid_records; ++i) {
q[i] = &e->srv_hosts.hosts[i];
}
for (int i = 0; i < valid_records; ++i) {
for (int ii = i + 1; ii < valid_records; ++ii) {
if (*q[ii] < *q[i]) {
SRV *tmp = q[i];
q[i] = q[ii];
q[ii] = tmp;
}
}
}
rr_data->good = rr_data->rrcount = valid_records;
rr_data->current = 0;
for (int i = 0; i < valid_records; ++i) {
SRV *t = q[i];
HostDBInfo &item = rr_data->info(i);
item.round_robin = 0;
item.round_robin_elt = 1;
item.reverse_dns = 0;
item.is_srv = 1;
item.data.srv.srv_weight = t->weight;
item.data.srv.srv_priority = t->priority;
item.data.srv.srv_port = t->port;
item.data.srv.key = t->key;
ink_assert((skip + t->host_len) <= e->srv_hosts.srv_hosts_length);
memcpy(pos + skip, t->host, t->host_len);
item.data.srv.srv_offset = (pos - reinterpret_cast<char *>(rr_data)) + skip;
skip += t->host_len;
item.app.allotment.application1 = 0;
item.app.allotment.application2 = 0;
Debug("dns_srv", "inserted SRV RR record [%s] into HostDB with TTL: %d seconds", t->host, ttl_seconds);
}
// restore
if (old_rr_data) {
for (int i = 0; i < rr_data->rrcount; ++i) {
for (int ii = 0; ii < old_rr_data->rrcount; ++ii) {
if (rr_data->info(i).data.srv.key == old_rr_data->info(ii).data.srv.key) {
char *new_host = rr_data->info(i).srvname(rr_data);
char *old_host = old_rr_data->info(ii).srvname(old_rr_data);
if (!strcmp(new_host, old_host)) {
rr_data->info(i).app = old_rr_data->info(ii).app;
}
}
}
}
}
} else { // Otherwise this is a regular dns response
rr_data->good = rr_data->rrcount = valid_records;
rr_data->current = 0;
for (int i = 0; i < valid_records; ++i) {
HostDBInfo &item = rr_data->info(i);
ip_addr_set(item.ip(), af, e->ent.h_addr_list[i]);
item.round_robin = 0;
item.round_robin_elt = 1;
item.reverse_dns = 0;
item.is_srv = 0;
if (!restore_info(&item, old_r.get(), old_info, old_rr_data)) {
item.app.allotment.application1 = 0;
item.app.allotment.application2 = 0;
}
}
}
}
if (!failed && !is_rr && !is_srv()) {
restore_info(r, old_r.get(), old_info, old_rr_data);
}
ink_assert(!r || !r->round_robin || !r->reverse_dns);
ink_assert(failed || !r->round_robin || r->app.rr.offset);
if (!serve_stale) {
hostDB.refcountcache->put(hash.hash.fold(), r, allocSize, r->expiry_time());
} else {
Warning("Fallback to serving stale record, skip re-update of hostdb for %s", aname);
}
// try to callback the user
//
if (action.continuation) {
// Check for IP family failover
if (failed && check_for_retry(hash.db_mark, host_res_style)) {
this->refresh_hash(); // family changed if we're doing a retry.
SET_CONTINUATION_HANDLER(this, (HostDBContHandler)&HostDBContinuation::probeEvent);
thread->schedule_in(this, MUTEX_RETRY_DELAY);
return EVENT_CONT;
}
// We have seen cases were the action.mutex != action.continuation.mutex. However, it seems that case
// is likely a memory corruption... Thus the introduction of the assert.
// Since reply_to_cont will call the handler on the action.continuation, it is important that we hold
// that mutex.
bool need_to_reschedule = true;
MUTEX_TRY_LOCK(lock, action.mutex, thread);
if (lock.is_locked()) {
if (!action.cancelled) {
if (action.continuation->mutex) {
ink_release_assert(action.continuation->mutex == action.mutex);
}
reply_to_cont(action.continuation, r, is_srv());
}
need_to_reschedule = false;
}
if (need_to_reschedule) {
SET_HANDLER((HostDBContHandler)&HostDBContinuation::probeEvent);
// Will reschedule on affinity thread or current thread
timeout = eventProcessor.schedule_in(this, HOST_DB_RETRY_PERIOD);
return EVENT_CONT;
}
}
// Clean ourselves up
hostDB.pending_dns_for_hash(hash.hash).remove(this);
// wake up everyone else who is waiting
remove_trigger_pending_dns();
hostdb_cont_free(this);
// all done, or at least scheduled to be all done
//
return EVENT_DONE;
}
}
int
HostDBContinuation::iterateEvent(int event, Event *e)
{
Debug("hostdb", "iterateEvent event=%d eventp=%p", event, e);
ink_assert(!link.prev && !link.next);
EThread *t = e ? e->ethread : this_ethread();
MUTEX_TRY_LOCK(lock, action.mutex, t);
if (!lock.is_locked()) {
Debug("hostdb", "iterateEvent event=%d eventp=%p: reschedule due to not getting action mutex", event, e);
mutex->thread_holding->schedule_in(this, HOST_DB_RETRY_PERIOD);
return EVENT_CONT;
}
if (action.cancelled) {
hostdb_cont_free(this);
return EVENT_DONE;
}
// let's iterate through another record and then reschedule ourself.
if (current_iterate_pos < hostDB.refcountcache->partition_count()) {
// TODO: configurable number at a time?
Ptr<ProxyMutex> bucket_mutex = hostDB.refcountcache->get_partition(current_iterate_pos).lock;
MUTEX_TRY_LOCK(lock_bucket, bucket_mutex, t);
if (!lock_bucket.is_locked()) {
// we couldn't get the bucket lock, let's just reschedule and try later.
Debug("hostdb", "iterateEvent event=%d eventp=%p: reschedule due to not getting bucket mutex", event, e);
mutex->thread_holding->schedule_in(this, HOST_DB_RETRY_PERIOD);
return EVENT_CONT;
}
IntrusiveHashMap<RefCountCacheLinkage> &partMap = hostDB.refcountcache->get_partition(current_iterate_pos).get_map();
for (const auto &it : partMap) {
HostDBInfo *r = static_cast<HostDBInfo *>(it.item.get());
if (r && !r->is_failed()) {
action.continuation->handleEvent(EVENT_INTERVAL, static_cast<void *>(r));
}
}
current_iterate_pos++;
}
if (current_iterate_pos < hostDB.refcountcache->partition_count()) {
// And reschedule ourselves to pickup the next bucket after HOST_DB_RETRY_PERIOD.
Debug("hostdb", "iterateEvent event=%d eventp=%p: completed current iteration %ld of %ld", event, e, current_iterate_pos,
hostDB.refcountcache->partition_count());
mutex->thread_holding->schedule_in(this, HOST_DB_ITERATE_PERIOD);
return EVENT_CONT;
} else {
Debug("hostdb", "iterateEvent event=%d eventp=%p: completed FINAL iteration %ld", event, e, current_iterate_pos);
// if there are no more buckets, then we're done.
action.continuation->handleEvent(EVENT_DONE, nullptr);
hostdb_cont_free(this);
}
return EVENT_DONE;
}
//
// Probe state
//
int
HostDBContinuation::probeEvent(int /* event ATS_UNUSED */, Event *e)
{
ink_assert(!link.prev && !link.next);
EThread *t = e ? e->ethread : this_ethread();
if (timeout) {
timeout->cancel(this);
timeout = nullptr;
}
MUTEX_TRY_LOCK(lock, action.mutex, t);
// Separating lock checks here to make sure things don't break
// when we check if the action is cancelled.
if (!lock.is_locked()) {
timeout = mutex->thread_holding->schedule_in(this, HOST_DB_RETRY_PERIOD);
return EVENT_CONT;
}
if (action.cancelled) {
hostdb_cont_free(this);
return EVENT_DONE;
}
// If the action.continuation->mutex != action.mutex, we have a use after free/realloc
ink_release_assert(!action.continuation || action.continuation->mutex == action.mutex);
if (!hostdb_enable || (!*hash.host_name && !hash.ip.isValid())) {
if (action.continuation) {
action.continuation->handleEvent(EVENT_HOST_DB_LOOKUP, nullptr);
}
hostdb_cont_free(this);
return EVENT_DONE;
}
if (!force_dns) {
// Do the probe
//
Ptr<HostDBInfo> r = probe(mutex, hash, false);
if (r) {
HOSTDB_INCREMENT_DYN_STAT(hostdb_total_hits_stat);
}
if (action.continuation && r) {
reply_to_cont(action.continuation, r.get(), is_srv());
}
// If it succeeds or it was a remote probe, we are done
//
if (r) {
hostdb_cont_free(this);
return EVENT_DONE;
}
}
// If there are no remote nodes to probe, do a DNS lookup
//
do_dns();
return EVENT_DONE;
}
int
HostDBContinuation::set_check_pending_dns()
{
Queue<HostDBContinuation> &q = hostDB.pending_dns_for_hash(hash.hash);
this->setThreadAffinity(this_ethread());
HostDBContinuation *c = q.head;
for (; c; c = static_cast<HostDBContinuation *>(c->link.next)) {
if (hash.hash == c->hash.hash) {
Debug("hostdb", "enqueuing additional request");
q.enqueue(this);
return false;
}
}
q.enqueue(this);
return true;
}
void
HostDBContinuation::remove_trigger_pending_dns()
{
Queue<HostDBContinuation> &q = hostDB.pending_dns_for_hash(hash.hash);
q.remove(this);
HostDBContinuation *c = q.head;
Queue<HostDBContinuation> qq;
while (c) {
HostDBContinuation *n = static_cast<HostDBContinuation *>(c->link.next);
if (hash.hash == c->hash.hash) {
Debug("hostdb", "dequeuing additional request");
q.remove(c);
qq.enqueue(c);
}
c = n;
}
EThread *thread = this_ethread();
while ((c = qq.dequeue())) {
// resume all queued HostDBCont in the thread associated with the netvc to avoid nethandler locking issues.
EThread *affinity_thread = c->getThreadAffinity();
if (!affinity_thread || affinity_thread == thread) {
c->handleEvent(EVENT_IMMEDIATE, nullptr);
} else {
if (c->timeout) {
c->timeout->cancel();
}
c->timeout = eventProcessor.schedule_imm(c);
}
}
}
//
// Query the DNS processor
//
void
HostDBContinuation::do_dns()
{
ink_assert(!action.cancelled);
if (is_byname()) {
Debug("hostdb", "DNS %s", hash.host_name);
IpAddr tip;
if (0 == tip.load(hash.host_name)) {
// check 127.0.0.1 format // What the heck does that mean? - AMC
if (action.continuation) {
HostDBInfo *r = lookup_done(tip, hash.host_name, false, HOST_DB_MAX_TTL, nullptr);
reply_to_cont(action.continuation, r);
}
hostdb_cont_free(this);
return;
}
ts::ConstBuffer hname(hash.host_name, hash.host_len);
Ptr<RefCountedHostsFileMap> current_host_file_map = hostDB.hosts_file_ptr;
HostsFileMap::iterator find_result = current_host_file_map->hosts_file_map.find(hname);
if (find_result != current_host_file_map->hosts_file_map.end()) {
if (action.continuation) {
// Set the TTL based on how often we stat() the host file
HostDBInfo *r = lookup_done(IpAddr(find_result->second), hash.host_name, false, hostdb_hostfile_check_interval, nullptr);
reply_to_cont(action.continuation, r);
}
hostdb_cont_free(this);
return;
}
}
if (hostdb_lookup_timeout) {
timeout = mutex->thread_holding->schedule_in(this, HRTIME_SECONDS(hostdb_lookup_timeout));
} else {
timeout = nullptr;
}
if (set_check_pending_dns()) {
DNSProcessor::Options opt;
opt.timeout = dns_lookup_timeout;
opt.host_res_style = host_res_style_for(hash.db_mark);
SET_HANDLER((HostDBContHandler)&HostDBContinuation::dnsEvent);
if (is_byname()) {
if (hash.dns_server) {
opt.handler = hash.dns_server->x_dnsH;
}
pending_action = dnsProcessor.gethostbyname(this, hash.host_name, opt);
} else if (is_srv()) {
Debug("dns_srv", "SRV lookup of %s", hash.host_name);
pending_action = dnsProcessor.getSRVbyname(this, hash.host_name, opt);
} else {
ip_text_buffer ipb;
Debug("hostdb", "DNS IP %s", hash.ip.toString(ipb, sizeof ipb));
pending_action = dnsProcessor.gethostbyaddr(this, &hash.ip, opt);
}
} else {
SET_HANDLER((HostDBContHandler)&HostDBContinuation::dnsPendingEvent);
}
}
//
// Background event
// Just increment the current_interval. Might do other stuff
// here, like move records to the current position in the cluster.
//
int
HostDBContinuation::backgroundEvent(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
{
// No nothing if hosts file checking is not enabled.
if (hostdb_hostfile_check_interval == 0) {
return EVENT_CONT;
}
hostdb_current_interval = ink_time();
if ((hostdb_current_interval - hostdb_last_interval) > hostdb_hostfile_check_interval) {
bool update_p = false; // do we need to reparse the file and update?
struct stat info;
char path[sizeof(hostdb_hostfile_path)];
REC_ReadConfigString(path, "proxy.config.hostdb.host_file.path", sizeof(path));
if (0 != strcasecmp(hostdb_hostfile_path, path)) {
Debug("hostdb", "Update host file '%s' -> '%s'", (*hostdb_hostfile_path ? hostdb_hostfile_path : "*-none-*"),
(*path ? path : "*-none-*"));
// path to hostfile changed
hostdb_hostfile_update_timestamp = 0; // never updated from this file
if ('\0' != *path) {
memcpy(hostdb_hostfile_path, path, sizeof(hostdb_hostfile_path));
} else {
hostdb_hostfile_path[0] = 0; // mark as not there
}
update_p = true;
} else {
hostdb_last_interval = hostdb_current_interval;
if (*hostdb_hostfile_path) {
if (0 == stat(hostdb_hostfile_path, &info)) {
if (info.st_mtime > static_cast<time_t>(hostdb_hostfile_update_timestamp)) {
update_p = true; // same file but it's changed.
}
} else {
Debug("hostdb", "Failed to stat host file '%s'", hostdb_hostfile_path);
}
}
}
if (update_p) {
Debug("hostdb", "Updating from host file");
ParseHostFile(hostdb_hostfile_path, hostdb_hostfile_check_interval);
}
}
return EVENT_CONT;
}
char *
HostDBInfo::hostname() const
{
if (!reverse_dns) {
return nullptr;
}
return (char *)this + data.hostname_offset;
}
/*
* The perm_hostname exists for all records not just reverse dns records.
*/
char *
HostDBInfo::perm_hostname() const
{
if (hostname_offset == 0) {
return nullptr;
}
return (char *)this + hostname_offset;
}
HostDBRoundRobin *
HostDBInfo::rr()
{
if (!round_robin) {
return nullptr;
}
return reinterpret_cast<HostDBRoundRobin *>(reinterpret_cast<char *>(this) + this->app.rr.offset);
}
struct ShowHostDB;
using ShowHostDBEventHandler = int (ShowHostDB::*)(int, Event *);
struct ShowHostDB : public ShowCont {
char *name;
uint16_t port;
IpEndpoint ip;
bool force;
bool output_json;
int records_seen;
int
showMain(int event, Event *e)
{
CHECK_SHOW(begin("HostDB"));
CHECK_SHOW(show("<a href=\"./showall\">Show all HostDB records<a/><hr>"));
CHECK_SHOW(show("<form method = GET action = \"./name\">\n"
"Lookup by name (e.g. trafficserver.apache.org):<br>\n"
"<input type=text name=name size=64 maxlength=256>\n"
"</form>\n"
"<form method = GET action = \"./ip\">\n"
"Lookup by IP (e.g. 127.0.0.1):<br>\n"
"<input type=text name=ip size=64 maxlength=256>\n"
"</form>\n"
"<form method = GET action = \"./nameforce\">\n"
"Force DNS by name (e.g. trafficserver.apache.org):<br>\n"
"<input type=text name=name size=64 maxlength=256>\n"
"</form>\n"));
return complete(event, e);
}
int
showLookup(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
{
SET_HANDLER(&ShowHostDB::showLookupDone);
if (name) {
HostDBProcessor::Options opts;
opts.port = port;
opts.flags = HostDBProcessor::HOSTDB_DO_NOT_FORCE_DNS;
hostDBProcessor.getbynameport_re(this, name, strlen(name), opts);
} else {
hostDBProcessor.getbyaddr_re(this, &ip.sa);
}
return EVENT_CONT;
}
int
showAll(int event, Event *e)
{
if (!output_json) {
CHECK_SHOW(begin("HostDB All Records"));
CHECK_SHOW(show("<hr>"));
} else {
CHECK_SHOW(show("["));
}
SET_HANDLER(&ShowHostDB::showAllEvent);
hostDBProcessor.iterate(this);
return EVENT_CONT;
}
int
showAllEvent(int event, Event *e)
{
if (event == EVENT_INTERVAL) {
HostDBInfo *r = reinterpret_cast<HostDBInfo *>(e);
if (output_json && records_seen++ > 0) {
CHECK_SHOW(show(",")); // we need to separate records
}
showOne(r, false, event, e);
if (r->round_robin) {
HostDBRoundRobin *rr_data = r->rr();
if (rr_data) {
if (!output_json) {
CHECK_SHOW(show("<table border=1>\n"));
CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Total", rr_data->rrcount));
CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Good", rr_data->good));
CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Current", rr_data->current));
CHECK_SHOW(show("</table>\n"));
} else {
CHECK_SHOW(show(",\"%s\":\"%d\",", "rr_total", rr_data->rrcount));
CHECK_SHOW(show("\"%s\":\"%d\",", "rr_good", rr_data->good));
CHECK_SHOW(show("\"%s\":\"%d\",", "rr_current", rr_data->current));
CHECK_SHOW(show("\"rr_records\":["));
}
for (int i = 0; i < rr_data->rrcount; i++) {
showOne(&rr_data->info(i), true, event, e, rr_data);
if (output_json) {
CHECK_SHOW(show("}")); // we need to separate records
if (i < (rr_data->rrcount - 1))
CHECK_SHOW(show(","));
}
}
if (!output_json) {
CHECK_SHOW(show("<br />\n<br />\n"));
} else {
CHECK_SHOW(show("]"));
}
}
}
if (output_json) {
CHECK_SHOW(show("}"));
}
} else if (event == EVENT_DONE) {
if (output_json) {
CHECK_SHOW(show("]"));
return completeJson(event, e);
} else {
return complete(event, e);
}
} else {
ink_assert(!"unexpected event");
}
return EVENT_CONT;
}
int
showOne(HostDBInfo *r, bool rr, int event, Event *e, HostDBRoundRobin *hostdb_rr = nullptr)
{
ip_text_buffer b;
if (!output_json) {
CHECK_SHOW(show("<table border=1>\n"));
CHECK_SHOW(show("<tr><td>%s</td><td>%s%s %s</td></tr>\n", "Type", r->round_robin ? "Round-Robin" : "",
r->reverse_dns ? "Reverse DNS" : "", r->is_srv ? "SRV" : "DNS"));
if (r->perm_hostname()) {
CHECK_SHOW(show("<tr><td>%s</td><td>%s</td></tr>\n", "Hostname", r->perm_hostname()));
} else if (rr && r->is_srv && hostdb_rr) {
CHECK_SHOW(show("<tr><td>%s</td><td>%s</td></tr>\n", "Hostname", r->srvname(hostdb_rr)));
}
// Let's display the hash.
CHECK_SHOW(show("<tr><td>%s</td><td>%u</td></tr>\n", "App1", r->app.allotment.application1));
CHECK_SHOW(show("<tr><td>%s</td><td>%u</td></tr>\n", "App2", r->app.allotment.application2));
CHECK_SHOW(show("<tr><td>%s</td><td>%u</td></tr>\n", "LastFailure", r->app.http_data.last_failure));
if (!rr) {
CHECK_SHOW(show("<tr><td>%s</td><td>%s</td></tr>\n", "Stale", r->is_ip_stale() ? "Yes" : "No"));
CHECK_SHOW(show("<tr><td>%s</td><td>%s</td></tr>\n", "Timed-Out", r->is_ip_timeout() ? "Yes" : "No"));
CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "TTL", r->ip_time_remaining()));
}
if (rr && r->is_srv) {
CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Weight", r->data.srv.srv_weight));
CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Priority", r->data.srv.srv_priority));
CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Port", r->data.srv.srv_port));
CHECK_SHOW(show("<tr><td>%s</td><td>%x</td></tr>\n", "Key", r->data.srv.key));
} else if (!r->is_srv) {
CHECK_SHOW(show("<tr><td>%s</td><td>%s</td></tr>\n", "IP", ats_ip_ntop(r->ip(), b, sizeof b)));
}
CHECK_SHOW(show("</table>\n"));
} else {
CHECK_SHOW(show("{"));
CHECK_SHOW(show("\"%s\":\"%s%s%s\",", "type", (r->round_robin && !r->is_srv) ? "roundrobin" : "",
r->reverse_dns ? "reversedns" : "", r->is_srv ? "srv" : "dns"));
if (r->perm_hostname()) {
CHECK_SHOW(show("\"%s\":\"%s\",", "hostname", r->perm_hostname()));
} else if (rr && r->is_srv && hostdb_rr) {
CHECK_SHOW(show("\"%s\":\"%s\",", "hostname", r->srvname(hostdb_rr)));
}
CHECK_SHOW(show("\"%s\":\"%u\",", "app1", r->app.allotment.application1));
CHECK_SHOW(show("\"%s\":\"%u\",", "app2", r->app.allotment.application2));
CHECK_SHOW(show("\"%s\":\"%u\",", "lastfailure", r->app.http_data.last_failure));
if (!rr) {
CHECK_SHOW(show("\"%s\":\"%s\",", "stale", r->is_ip_stale() ? "yes" : "no"));
CHECK_SHOW(show("\"%s\":\"%s\",", "timedout", r->is_ip_timeout() ? "yes" : "no"));
CHECK_SHOW(show("\"%s\":\"%d\",", "ttl", r->ip_time_remaining()));
}
if (rr && r->is_srv) {
CHECK_SHOW(show("\"%s\":\"%d\",", "weight", r->data.srv.srv_weight));
CHECK_SHOW(show("\"%s\":\"%d\",", "priority", r->data.srv.srv_priority));
CHECK_SHOW(show("\"%s\":\"%d\",", "port", r->data.srv.srv_port));
CHECK_SHOW(show("\"%s\":\"%x\",", "key", r->data.srv.key));
} else if (!r->is_srv) {
CHECK_SHOW(show("\"%s\":\"%s\"", "ip", ats_ip_ntop(r->ip(), b, sizeof b)));
}
}
return EVENT_CONT;
}
int
showLookupDone(int event, Event *e)
{
HostDBInfo *r = reinterpret_cast<HostDBInfo *>(e);
CHECK_SHOW(begin("HostDB Lookup"));
if (name) {
CHECK_SHOW(show("<H2>%s</H2>\n", name));
} else {
CHECK_SHOW(show("<H2>%u.%u.%u.%u</H2>\n", PRINT_IP(ip)));
}
if (r) {
showOne(r, false, event, e);
if (r->round_robin) {
HostDBRoundRobin *rr_data = r->rr();
if (rr_data) {
CHECK_SHOW(show("<table border=1>\n"));
CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Total", rr_data->rrcount));
CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Good", rr_data->good));
CHECK_SHOW(show("<tr><td>%s</td><td>%d</td></tr>\n", "Current", rr_data->current));
CHECK_SHOW(show("</table>\n"));
for (int i = 0; i < rr_data->rrcount; i++) {
showOne(&rr_data->info(i), true, event, e, rr_data);
}
}
}
} else {
if (!name) {
ip_text_buffer b;
CHECK_SHOW(show("<H2>%s Not Found</H2>\n", ats_ip_ntop(&ip.sa, b, sizeof b)));
} else {
CHECK_SHOW(show("<H2>%s Not Found</H2>\n", name));
}
}
return complete(event, e);
}
ShowHostDB(Continuation *c, HTTPHdr *h)
: ShowCont(c, h), name(nullptr), port(0), force(false), output_json(false), records_seen(0)
{
ats_ip_invalidate(&ip);
SET_HANDLER(&ShowHostDB::showMain);
}
};
#define STR_LEN_EQ_PREFIX(_x, _l, _s) (!ptr_len_ncasecmp(_x, _l, _s, sizeof(_s) - 1))
static Action *
register_ShowHostDB(Continuation *c, HTTPHdr *h)
{
ShowHostDB *s = new ShowHostDB(c, h);
int path_len;
const char *path = h->url_get()->path_get(&path_len);
SET_CONTINUATION_HANDLER(s, &ShowHostDB::showMain);
if (STR_LEN_EQ_PREFIX(path, path_len, "ip")) {
s->force = !ptr_len_ncasecmp(path + 3, path_len - 3, "force", 5);
int query_len;
const char *query = h->url_get()->query_get(&query_len);
s->sarg = ats_strndup(query, query_len);
char *gn = nullptr;
if (s->sarg) {
gn = static_cast<char *>(memchr(s->sarg, '=', strlen(s->sarg)));
}
if (gn) {
ats_ip_pton(gn + 1, &s->ip); // hope that's null terminated.
}
SET_CONTINUATION_HANDLER(s, &ShowHostDB::showLookup);
} else if (STR_LEN_EQ_PREFIX(path, path_len, "name")) {
s->force = !ptr_len_ncasecmp(path + 5, path_len - 5, "force", 5);
int query_len;
const char *query = h->url_get()->query_get(&query_len);
s->sarg = ats_strndup(query, query_len);
char *gn = nullptr;
if (s->sarg) {
gn = static_cast<char *>(memchr(s->sarg, '=', strlen(s->sarg)));
}
if (gn) {
s->name = gn + 1;
char *pos = strstr(s->name, "%3A");
if (pos != nullptr) {
s->port = atoi(pos + 3);
*pos = '\0'; // Null terminate name
} else {
s->port = 0;
}
}
SET_CONTINUATION_HANDLER(s, &ShowHostDB::showLookup);
} else if (STR_LEN_EQ_PREFIX(path, path_len, "showall")) {
int query_len = 0;
const char *query = h->url_get()->query_get(&query_len);
if (query && query_len && strstr(query, "json")) {
s->output_json = true;
}
Debug("hostdb", "dumping all hostdb records");
SET_CONTINUATION_HANDLER(s, &ShowHostDB::showAll);
}
this_ethread()->schedule_imm(s);
return &s->action;
}
static constexpr int HOSTDB_TEST_MAX_OUTSTANDING = 20;
static constexpr int HOSTDB_TEST_LENGTH = 200;
struct HostDBTestReverse;
using HostDBTestReverseHandler = int (HostDBTestReverse::*)(int, void *);
struct HostDBTestReverse : public Continuation {
RegressionTest *test;
int type;
int *status;
int outstanding = 0;
int total = 0;
std::ranlux48 randu;
int
mainEvent(int event, Event *e)
{
if (event == EVENT_HOST_DB_LOOKUP) {
HostDBInfo *i = reinterpret_cast<HostDBInfo *>(e);
if (i) {
rprintf(test, "HostDBTestReverse: reversed %s\n", i->hostname());
}
outstanding--;
}
while (outstanding < HOSTDB_TEST_MAX_OUTSTANDING && total < HOSTDB_TEST_LENGTH) {
IpEndpoint ip;
ip.assign(IpAddr(static_cast<in_addr_t>(randu())));
outstanding++;
total++;
if (!(outstanding % 100)) {
rprintf(test, "HostDBTestReverse: %d\n", total);
}
hostDBProcessor.getbyaddr_re(this, &ip.sa);
}
if (!outstanding) {
rprintf(test, "HostDBTestReverse: done\n");
*status = REGRESSION_TEST_PASSED; // TODO: actually verify it passed
delete this;
}
return EVENT_CONT;
}
HostDBTestReverse(RegressionTest *t, int atype, int *astatus)
: Continuation(new_ProxyMutex()), test(t), type(atype), status(astatus)
{
SET_HANDLER((HostDBTestReverseHandler)&HostDBTestReverse::mainEvent);
randu.seed(std::chrono::system_clock::now().time_since_epoch().count());
}
};
#if TS_HAS_TESTS
REGRESSION_TEST(HostDBTests)(RegressionTest *t, int atype, int *pstatus)
{
eventProcessor.schedule_imm(new HostDBTestReverse(t, atype, pstatus), ET_CACHE);
}
#endif
RecRawStatBlock *hostdb_rsb;
void
ink_hostdb_init(ts::ModuleVersion v)
{
static int init_called = 0;
ink_release_assert(v.check(HOSTDB_MODULE_INTERNAL_VERSION));
if (init_called) {
return;
}
init_called = 1;
// do one time stuff
// create a stat block for HostDBStats
hostdb_rsb = RecAllocateRawStatBlock(static_cast<int>(HostDB_Stat_Count));
//
// Register stats
//
RecRegisterRawStat(hostdb_rsb, RECT_PROCESS, "proxy.process.hostdb.total_lookups", RECD_INT, RECP_PERSISTENT,
(int)hostdb_total_lookups_stat, RecRawStatSyncSum);
RecRegisterRawStat(hostdb_rsb, RECT_PROCESS, "proxy.process.hostdb.total_hits", RECD_INT, RECP_PERSISTENT,
(int)hostdb_total_hits_stat, RecRawStatSyncSum);
RecRegisterRawStat(hostdb_rsb, RECT_PROCESS, "proxy.process.hostdb.ttl", RECD_FLOAT, RECP_PERSISTENT, (int)hostdb_ttl_stat,
RecRawStatSyncAvg);
RecRegisterRawStat(hostdb_rsb, RECT_PROCESS, "proxy.process.hostdb.ttl_expires", RECD_INT, RECP_PERSISTENT,
(int)hostdb_ttl_expires_stat, RecRawStatSyncSum);
RecRegisterRawStat(hostdb_rsb, RECT_PROCESS, "proxy.process.hostdb.re_dns_on_reload", RECD_INT, RECP_PERSISTENT,
(int)hostdb_re_dns_on_reload_stat, RecRawStatSyncSum);
ts_host_res_global_init();
}
/// Pair of IP address and host name from a host file.
struct HostFilePair {
using self = HostFilePair;
IpAddr ip;
const char *name;
};
struct HostDBFileContinuation : public Continuation {
using self = HostDBFileContinuation;
using Keys = std::vector<CryptoHash>;
int idx = 0; ///< Working index.
const char *name = nullptr; ///< Host name (just for debugging)
Keys *keys = nullptr; ///< Entries from file.
CryptoHash hash; ///< Key for entry.
ats_scoped_str path; ///< Used to keep the host file name around.
HostDBFileContinuation() : Continuation(nullptr) {}
/// Finish update
static void finish(Keys *keys ///< Valid keys from update.
);
/// Clean up this instance.
void destroy();
};
ClassAllocator<HostDBFileContinuation> hostDBFileContAllocator("hostDBFileContAllocator");
void
HostDBFileContinuation::destroy()
{
this->~HostDBFileContinuation();
hostDBFileContAllocator.free(this);
}
// Host file processing globals.
// We can't allow more than one update to be
// proceeding at a time in any case so we might as well make these
// globals.
int HostDBFileUpdateActive = 0;
static void
ParseHostLine(Ptr<RefCountedHostsFileMap> &map, char *l)
{
Tokenizer elts(" \t");
int n_elts = elts.Initialize(l, SHARE_TOKS);
// Elements should be the address then a list of host names.
// Don't use RecHttpLoadIp because the address *must* be literal.
IpAddr ip;
if (n_elts > 1 && 0 == ip.load(elts[0])) {
for (int i = 1; i < n_elts; ++i) {
ts::ConstBuffer name(elts[i], strlen(elts[i]));
// If we don't have an entry already (host files only support single IPs for a given name)
if (map->hosts_file_map.find(name) == map->hosts_file_map.end()) {
map->hosts_file_map[name] = ip;
}
}
}
}
void
ParseHostFile(const char *path, unsigned int hostdb_hostfile_check_interval_parse)
{
Ptr<RefCountedHostsFileMap> parsed_hosts_file_ptr;
// Test and set for update in progress.
if (0 != ink_atomic_swap(&HostDBFileUpdateActive, 1)) {
Debug("hostdb", "Skipped load of host file because update already in progress");
return;
}
Debug("hostdb", "Loading host file '%s'", path);
if (*path) {
ats_scoped_fd fd(open(path, O_RDONLY));
if (fd >= 0) {
struct stat info;
if (0 == fstat(fd, &info)) {
// +1 in case no terminating newline
int64_t size = info.st_size + 1;
parsed_hosts_file_ptr = new RefCountedHostsFileMap;
parsed_hosts_file_ptr->HostFileText = static_cast<char *>(ats_malloc(size));
if (parsed_hosts_file_ptr->HostFileText) {
char *base = parsed_hosts_file_ptr->HostFileText;
char *limit;
size = read(fd, parsed_hosts_file_ptr->HostFileText, info.st_size);
limit = parsed_hosts_file_ptr->HostFileText + size;
*limit = 0;
// We need to get a list of all name/addr pairs so that we can
// group names for round robin records. Also note that the
// pairs have pointer back in to the text storage for the file
// so we need to keep that until we're done with @a pairs.
while (base < limit) {
char *spot = strchr(base, '\n');
// terminate the line.
if (nullptr == spot) {
spot = limit; // no trailing EOL, grab remaining
} else {
*spot = 0;
}
while (base < spot && isspace(*base)) {
++base; // skip leading ws
}
if (*base != '#' && base < spot) { // non-empty non-comment line
ParseHostLine(parsed_hosts_file_ptr, base);
}
base = spot + 1;
}
hostdb_hostfile_update_timestamp = hostdb_current_interval;
}
}
}
}
// Swap the pointer
if (parsed_hosts_file_ptr != nullptr) {
hostDB.hosts_file_ptr = parsed_hosts_file_ptr;
}
// Mark this one as completed, so we can allow another update to happen
HostDBFileUpdateActive = 0;
}
//
// Regression tests
//
// Take a started hostDB and fill it up and make sure it doesn't explode
#if TS_HAS_TESTS
struct HostDBRegressionContinuation;
struct HostDBRegressionContinuation : public Continuation {
int hosts;
const char **hostnames;
RegressionTest *test;
int type;
int *status;
int success;
int failure;
int outstanding;
int i;
int
mainEvent(int event, HostDBInfo *r)
{
(void)event;
if (event == EVENT_INTERVAL) {
rprintf(test, "hosts=%d success=%d failure=%d outstanding=%d i=%d\n", hosts, success, failure, outstanding, i);
}
if (event == EVENT_HOST_DB_LOOKUP) {
--outstanding;
// since this is a lookup done, data is either hostdbInfo or nullptr
if (r) {
rprintf(test, "hostdbinfo r=%x\n", r);
rprintf(test, "hostdbinfo hostname=%s\n", r->perm_hostname());
rprintf(test, "hostdbinfo rr %x\n", r->rr());
// If RR, print all of the enclosed records
if (r->rr()) {
rprintf(test, "hostdbinfo good=%d\n", r->rr()->good);
for (int x = 0; x < r->rr()->good; x++) {
ip_port_text_buffer ip_buf;
ats_ip_ntop(r->rr()->info(x).ip(), ip_buf, sizeof(ip_buf));
rprintf(test, "hostdbinfo RR%d ip=%s\n", x, ip_buf);
}
} else { // Otherwise, just the one will do
ip_port_text_buffer ip_buf;
ats_ip_ntop(r->ip(), ip_buf, sizeof(ip_buf));
rprintf(test, "hostdbinfo A ip=%s\n", ip_buf);
}
++success;
} else {
++failure;
}
}
if (i < hosts) {
hostDBProcessor.getbyname_re(this, hostnames[i++], 0);
return EVENT_CONT;
} else {
rprintf(test, "HostDBTestRR: %d outstanding %d success %d failure\n", outstanding, success, failure);
if (success == hosts) {
*status = REGRESSION_TEST_PASSED;
} else {
*status = REGRESSION_TEST_FAILED;
}
return EVENT_DONE;
}
}
HostDBRegressionContinuation(int ahosts, const char **ahostnames, RegressionTest *t, int atype, int *astatus)
: Continuation(new_ProxyMutex()),
hosts(ahosts),
hostnames(ahostnames),
test(t),
type(atype),
status(astatus),
success(0),
failure(0),
i(0)
{
outstanding = ahosts;
SET_HANDLER(&HostDBRegressionContinuation::mainEvent);
}
};
static const char *dns_test_hosts[] = {
"www.apple.com", "www.ibm.com", "www.microsoft.com",
"www.coke.com", // RR record
"4.2.2.2", // An IP-- since we don't expect resolution
"127.0.0.1", // loopback since it has some special handling
};
REGRESSION_TEST(HostDBProcessor)(RegressionTest *t, int atype, int *pstatus)
{
eventProcessor.schedule_in(new HostDBRegressionContinuation(6, dns_test_hosts, t, atype, pstatus), HRTIME_SECONDS(1));
}
#endif