blob: d709bdf27bd5578f4e502f5d61862e24ead6f39a [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.
*/
#pragma once
#include "tscore/HashFNV.h"
#include "tscore/ink_time.h"
#include "tscore/CryptoHash.h"
#include "tscore/ink_align.h"
#include "tscore/ink_resolver.h"
#include "I_EventSystem.h"
#include "SRV.h"
#include "P_RefCountCache.h"
// Event returned on a lookup
#define EVENT_HOST_DB_LOOKUP (HOSTDB_EVENT_EVENTS_START + 0)
#define EVENT_HOST_DB_IP_REMOVED (HOSTDB_EVENT_EVENTS_START + 1)
#define EVENT_HOST_DB_GET_RESPONSE (HOSTDB_EVENT_EVENTS_START + 2)
#define EVENT_SRV_LOOKUP (SRV_EVENT_EVENTS_START + 0)
#define EVENT_SRV_IP_REMOVED (SRV_EVENT_EVENTS_START + 1)
#define EVENT_SRV_GET_RESPONSE (SRV_EVENT_EVENTS_START + 2)
//
// Data
//
struct HostDBContinuation;
//
// The host database stores host information, most notably the
// IP address.
//
// Since host information is relatively small, we can afford to have
// a reasonable size memory cache, and use a (relatively) sparse
// disk representation to decrease # of seeks.
//
extern int hostdb_enable;
extern ink_time_t hostdb_current_interval;
extern unsigned int hostdb_ip_stale_interval;
extern unsigned int hostdb_ip_timeout_interval;
extern unsigned int hostdb_ip_fail_timeout_interval;
extern unsigned int hostdb_serve_stale_but_revalidate;
extern unsigned int hostdb_round_robin_max_count;
extern int hostdb_max_iobuf_index;
static inline unsigned int
makeHostHash(const char *string)
{
ink_assert(string && *string);
if (string && *string) {
ATSHash32FNV1a fnv;
fnv.update(string, strlen(string), ATSHash::nocase());
fnv.final();
return fnv.get();
}
return 0;
}
//
// Types
//
//
// This structure contains the host information required by
// the application. Except for the initial fields it
// is treated as opaque by the database.
//
union HostDBApplicationInfo {
struct application_data_allotment {
unsigned int application1;
unsigned int application2;
} allotment;
//////////////////////////////////////////////////////////
// http server attributes in the host database //
// //
// http_version - one of HttpVersion_t //
// keep_alive_timeout - in seconds. (up to 63 seconds). //
// last_failure - UNIX time for the last time //
// we tried the server & failed //
// fail_count - Number of times we tried and //
// and failed to contact the host //
//////////////////////////////////////////////////////////
struct http_server_attr {
unsigned int http_version : 3;
unsigned int keepalive_timeout : 6;
unsigned int fail_count : 8;
unsigned int unused1 : 8;
unsigned int last_failure : 32;
} http_data;
enum HttpVersion_t {
HTTP_VERSION_UNDEFINED = 0,
HTTP_VERSION_09 = 1,
HTTP_VERSION_10 = 2,
HTTP_VERSION_11 = 3,
};
struct application_data_rr {
unsigned int offset;
} rr;
};
struct HostDBRoundRobin;
struct SRVInfo {
unsigned int srv_offset : 16;
unsigned int srv_weight : 16;
unsigned int srv_priority : 16;
unsigned int srv_port : 16;
unsigned int key;
};
struct HostDBInfo : public RefCountObj {
/** Internal IP address data.
This is at least large enough to hold an IPv6 address.
*/
static HostDBInfo *
alloc(int size = 0)
{
size += sizeof(HostDBInfo);
int iobuffer_index = iobuffer_size_to_index(size, hostdb_max_iobuf_index);
ink_release_assert(iobuffer_index >= 0);
void *ptr = ioBufAllocator[iobuffer_index].alloc_void();
memset(ptr, 0, size);
HostDBInfo *ret = new (ptr) HostDBInfo();
ret->_iobuffer_index = iobuffer_index;
return ret;
}
void
free() override
{
ink_release_assert(from_alloc());
Debug("hostdb", "freeing %d bytes at [%p]", (1 << (7 + _iobuffer_index)), this);
ioBufAllocator[_iobuffer_index].free_void((void *)(this));
}
// return a version number-- so we can manage compatibility with the marshal/unmarshal
static ts::VersionNumber
version()
{
return ts::VersionNumber(1, 0);
}
static HostDBInfo *
unmarshall(char *buf, unsigned int size)
{
if (size < sizeof(HostDBInfo)) {
return nullptr;
}
HostDBInfo *ret = HostDBInfo::alloc(size - sizeof(HostDBInfo));
int buf_index = ret->_iobuffer_index;
memcpy((void *)ret, buf, size);
// Reset the refcount back to 0, this is a bit ugly-- but I'm not sure we want to expose a method
// to mess with the refcount, since this is a fairly unique use case
ret = new (ret) HostDBInfo();
ret->_iobuffer_index = buf_index;
return ret;
}
// return expiry time (in seconds since epoch)
ink_time_t
expiry_time() const
{
return ip_timestamp + ip_timeout_interval + hostdb_serve_stale_but_revalidate;
}
sockaddr *
ip()
{
return &data.ip.sa;
}
sockaddr const *
ip() const
{
return &data.ip.sa;
}
char *hostname() const;
char *perm_hostname() const;
char *srvname(HostDBRoundRobin *rr) const;
/// Check if this entry is an element of a round robin entry.
/// If @c true then this entry is part of and was obtained from a round robin root. This is useful if the
/// address doesn't work - a retry can probably get a new address by doing another lookup and resolving to
/// a different element of the round robin.
bool
is_rr_elt() const
{
return 0 != round_robin_elt;
}
HostDBRoundRobin *rr();
unsigned int
ip_interval() const
{
return (hostdb_current_interval - ip_timestamp) & 0x7FFFFFFF;
}
int
ip_time_remaining() const
{
return static_cast<int>(ip_timeout_interval) - static_cast<int>(this->ip_interval());
}
bool
is_ip_stale() const
{
return ip_timeout_interval >= 2 * hostdb_ip_stale_interval && ip_interval() >= hostdb_ip_stale_interval;
}
bool
is_ip_timeout() const
{
return ip_interval() >= ip_timeout_interval;
}
bool
is_ip_fail_timeout() const
{
return ip_interval() >= hostdb_ip_fail_timeout_interval;
}
void
refresh_ip()
{
ip_timestamp = hostdb_current_interval;
}
bool
serve_stale_but_revalidate() const
{
// the option is disabled
if (hostdb_serve_stale_but_revalidate <= 0) {
return false;
}
// ip_timeout_interval == DNS TTL
// hostdb_serve_stale_but_revalidate == number of seconds
// ip_interval() is the number of seconds between now() and when the entry was inserted
if ((ip_timeout_interval + hostdb_serve_stale_but_revalidate) > ip_interval()) {
Debug("hostdb", "serving stale entry %d | %d | %d as requested by config", ip_timeout_interval,
hostdb_serve_stale_but_revalidate, ip_interval());
return true;
}
// otherwise, the entry is too old
return false;
}
/*
* Given the current time `now` and the fail_window, determine if this real is alive
*/
bool
is_alive(ink_time_t now, int32_t fail_window)
{
unsigned int last_failure = app.http_data.last_failure;
if (last_failure == 0 || (unsigned int)(now - fail_window) > last_failure) {
return true;
} else {
// Entry is marked down. Make sure some nasty clock skew
// did not occur. Use the retry time to set an upper bound
// as to how far in the future we should tolerate bogus last
// failure times. This sets the upper bound that we would ever
// consider a server down to 2*down_server_timeout
if ((unsigned int)(now + fail_window) < last_failure) {
app.http_data.last_failure = 0;
return false;
}
return false;
}
}
bool
is_failed() const
{
return !((is_srv && data.srv.srv_offset) || (reverse_dns && data.hostname_offset) || ats_is_ip(ip()));
}
void
set_failed()
{
if (is_srv) {
data.srv.srv_offset = 0;
} else if (reverse_dns) {
data.hostname_offset = 0;
} else {
ats_ip_invalidate(ip());
}
}
uint64_t key;
// Application specific data. NOTE: We need an integral number of
// these per block. This structure is 32 bytes. (at 200k hosts =
// 8 Meg). Which gives us 7 bytes of application information.
HostDBApplicationInfo app;
union {
IpEndpoint ip; ///< IP address / port data.
unsigned int hostname_offset; ///< Some hostname thing.
SRVInfo srv;
} data;
unsigned int hostname_offset; // always maintain a permanent copy of the hostname for non-rev dns records.
unsigned int ip_timestamp;
unsigned int ip_timeout_interval; // bounded between 1 and HOST_DB_MAX_TTL (0x1FFFFF, 24 days)
unsigned int is_srv : 1;
unsigned int reverse_dns : 1;
unsigned int round_robin : 1; // This is the root of a round robin block
unsigned int round_robin_elt : 1; // This is an address in a round robin block
HostDBInfo() : _iobuffer_index{-1} {}
HostDBInfo(HostDBInfo const &src) : RefCountObj()
{
memcpy(static_cast<void *>(this), static_cast<const void *>(&src), sizeof(*this));
_iobuffer_index = -1;
}
HostDBInfo &
operator=(HostDBInfo const &src)
{
if (this != &src) {
int iob_idx = _iobuffer_index;
memcpy(static_cast<void *>(this), static_cast<const void *>(&src), sizeof(*this));
_iobuffer_index = iob_idx;
}
return *this;
}
bool
from_alloc() const
{
return _iobuffer_index >= 0;
}
private:
// The value of this will be -1 for objects that are not created by the alloc() static member function.
int _iobuffer_index;
};
struct HostDBRoundRobin {
/** Total number (to compute space used). */
short rrcount = 0;
/** Number which have not failed a connect. */
short good = 0;
unsigned short current = 0;
ink_time_t timed_rr_ctime = 0;
// This is the equivalent of a variable length array, we can't use a VLA because
// HostDBInfo is a non-POD type-- so this is the best we can do.
HostDBInfo &
info(short n)
{
ink_assert(n < rrcount && n >= 0);
return *((HostDBInfo *)((char *)this + sizeof(HostDBRoundRobin)) + n);
}
// Return the allocation size of a HostDBRoundRobin struct suitable for storing
// "count" HostDBInfo records.
static unsigned
size(unsigned count, unsigned srv_len = 0)
{
ink_assert(count > 0);
return INK_ALIGN((sizeof(HostDBRoundRobin) + (count * sizeof(HostDBInfo)) + srv_len), 8);
}
/** Find the index of @a addr in member @a info.
@return The index if found, -1 if not found.
*/
int index_of(sockaddr const *addr);
HostDBInfo *find_ip(sockaddr const *addr);
// Find the srv target
HostDBInfo *find_target(const char *target);
/** Select the next entry after @a addr.
@note If @a addr isn't an address in the round robin nothing is updated.
@return The selected entry or @c nullptr if @a addr wasn't present.
*/
HostDBInfo *select_next(sockaddr const *addr);
HostDBInfo *select_best_http(sockaddr const *client_ip, ink_time_t now, int32_t fail_window);
HostDBInfo *select_best_srv(char *target, InkRand *rand, ink_time_t now, int32_t fail_window);
HostDBRoundRobin() {}
};
struct HostDBCache;
struct HostDBHash;
// Prototype for inline completion function or
// getbyname_imm()
typedef void (Continuation::*cb_process_result_pfn)(HostDBInfo *r);
Action *iterate(Continuation *cont);
/** The Host Database access interface. */
struct HostDBProcessor : public Processor {
friend struct HostDBSync;
// Public Interface
// Lookup Hostinfo by name
// cont->handleEvent( EVENT_HOST_DB_LOOKUP, HostDBInfo * ); on success
// cont->handleEVent( EVENT_HOST_DB_LOOKUP, 0); on failure
// Failure occurs when the host cannot be DNS-ed
// NOTE: Will call the continuation back before returning if data is in the
// cache. The HostDBInfo * becomes invalid when the callback returns.
// The HostDBInfo may be changed during the callback.
enum {
HOSTDB_DO_NOT_FORCE_DNS = 0,
HOSTDB_ROUND_ROBIN = 0,
HOSTDB_FORCE_DNS_RELOAD = 1,
HOSTDB_FORCE_DNS_ALWAYS = 2,
HOSTDB_DO_NOT_ROUND_ROBIN = 4
};
/// Optional parameters for getby...
struct Options {
typedef Options self; ///< Self reference type.
int port = 0; ///< Target service port (default 0 -> don't care)
int flags = HOSTDB_DO_NOT_FORCE_DNS; ///< Processing flags (default HOSTDB_DO_NOT_FORCE_DNS)
int timeout = 0; ///< Timeout value (default 0 -> default timeout)
HostResStyle host_res_style = HOST_RES_IPV4; ///< How to query host (default HOST_RES_IPV4)
Options() {}
/// Set the flags.
self &
setFlags(int f)
{
flags = f;
return *this;
}
};
/// Default options.
static Options const DEFAULT_OPTIONS;
HostDBProcessor() {}
inkcoreapi Action *getbyname_re(Continuation *cont, const char *hostname, int len, Options const &opt = DEFAULT_OPTIONS);
Action *getbynameport_re(Continuation *cont, const char *hostname, int len, Options const &opt = DEFAULT_OPTIONS);
Action *getSRVbyname_imm(Continuation *cont, cb_process_result_pfn process_srv_info, const char *hostname, int len,
Options const &opt = DEFAULT_OPTIONS);
Action *getbyname_imm(Continuation *cont, cb_process_result_pfn process_hostdb_info, const char *hostname, int len,
Options const &opt = DEFAULT_OPTIONS);
Action *iterate(Continuation *cont);
/** Lookup Hostinfo by addr */
Action *getbyaddr_re(Continuation *cont, sockaddr const *aip);
/** Set the application information (fire-and-forget). */
void
setbyname_appinfo(char *hostname, int len, int port, HostDBApplicationInfo *app)
{
sockaddr_in addr;
ats_ip4_set(&addr, INADDR_ANY, port);
setby(hostname, len, ats_ip_sa_cast(&addr), app);
}
void
setbyaddr_appinfo(sockaddr const *addr, HostDBApplicationInfo *app)
{
this->setby(nullptr, 0, addr, app);
}
void
setbyaddr_appinfo(in_addr_t ip, HostDBApplicationInfo *app)
{
sockaddr_in addr;
ats_ip4_set(&addr, ip);
this->setby(nullptr, 0, ats_ip_sa_cast(&addr), app);
}
/** Configuration. */
static int hostdb_strict_round_robin;
static int hostdb_timed_round_robin;
// Processor Interface
/* hostdb does not use any dedicated event threads
* currently. Dont pass any value to start
*/
int start(int no_of_additional_event_threads = 0, size_t stacksize = DEFAULT_STACKSIZE) override;
// Private
HostDBCache *cache();
private:
Action *getby(Continuation *cont, cb_process_result_pfn cb_process_result, HostDBHash &hash, Options const &opt);
public:
/** Set something.
@a aip can carry address and / or port information. If setting just
by a port value, the address should be set to INADDR_ANY which is of
type IPv4.
*/
void setby(const char *hostname, ///< Hostname.
int len, ///< Length of hostname.
sockaddr const *aip, ///< Address and/or port.
HostDBApplicationInfo *app ///< I don't know.
);
void setby_srv(const char *hostname, int len, const char *target, HostDBApplicationInfo *app);
};
void run_HostDBTest();
extern inkcoreapi HostDBProcessor hostDBProcessor;
void ink_hostdb_init(ts::ModuleVersion version);