| /** @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); |