+
+
+
+Description: Unable to find a valid target host.
+
+The server was found but all of the addresses are marked dead and so there is
+no valid target address to which to connect. Please try again after a few minutes.
+
+
+
diff --git a/doc/developer-guide/core-architecture/HostDB-Data-Layout.svg b/doc/developer-guide/core-architecture/HostDB-Data-Layout.svg
new file mode 100644
index 00000000000..9c02674a826
--- /dev/null
+++ b/doc/developer-guide/core-architecture/HostDB-Data-Layout.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/doc/developer-guide/core-architecture/hostdb.en.rst b/doc/developer-guide/core-architecture/hostdb.en.rst
new file mode 100644
index 00000000000..33eef3c253a
--- /dev/null
+++ b/doc/developer-guide/core-architecture/hostdb.en.rst
@@ -0,0 +1,191 @@
+.. 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:: ../../common.defs
+
+.. highlight:: cpp
+.. default-domain:: cpp
+
+.. _developer-doc-hostdb:
+
+HostDB
+******
+
+HostDB is a cache of DNS results. It is used to increase performance by aggregating address
+resolution across transactions. HostDB also stores state information for specific IP addresses.
+
+Operation
+=========
+
+The primary operation for HostDB is to resolve a fully qualified domain name ("FQDN"). As noted each
+FQDN is associated with a single record. Each record has an array of items. When a resolution
+request is made the database is checked to see if the record is already present. If so, it is
+served. Otherwise a DNS request is made. When the nameserver replies a record is created, added
+to the database, and then returned to the requestor.
+
+Each info tracks several status values for its corresponding upstream. These are
+
+* HTTP version
+* Last failure time
+
+The HTTP version is tracked from responses and provides a mechanism to make intelligent guesses
+about the protocol to use to the upstream.
+
+The last failure time tracks when the last connection failure to the info occurred and doubles as
+a flag, where a value of ``TS_TIME_ZERO`` indicates a live target and any other value indicates a
+dead info.
+
+If an info is marked dead (has a non-zero last failure time) there is a "fail window" during which
+no connections are permitted. After this time the info is considered to be a "zombie". If all infos
+for a record are dead then a specific error message is generated (body factory tag
+"connect#all_dead"). Otherwise if the selected info is a zombie, a request is permitted but the
+zombie is immediately marked dead again, preventing any additional requests until either the fail
+window has passed or the single connection succeeds. A successful connection clears the last file
+time and the info becomes alive.
+
+Runtime Structure
+=================
+
+DNS results are stored in a global hash table as instances of ``HostDBRecord``. Each record stores
+the results of a single query. These records are not updated with new DNS results - instead a new
+record instance is created and replaces the previous instance in the table. The records are
+reference counted so such a replacement doesn't invalidate the old record if the latter is still
+being accessed. Some specific dynamic data is migrated from the old record to the new one, such as
+the failure status of the upstreams in the record.
+
+In each record is a variable length array of items, instances of ``HostDBInfo``, one for each
+IP address in the record. This is called the "round robin" data for historical reasons. For SRV
+records there is an additional storage area in the record that is used to store the SRV names.
+
+.. figure:: HostDB-Data-Layout.svg
+
+The round robin data is accessed by using an offset and count in the base record. For SRV records
+each record has an offset, relative to that ``HostDBInfo`` instance, for its own name in the name
+storage area.
+
+State information for the outbound connection has been moved to a refurbished ``DNSInfo`` class
+named ``ResolveInfo``. As much as possible relevant state information has been moved from the
+``HttpSM`` to this structure. This is intended for future work where the state machine deals only
+with upstream transactions and not sessions.
+
+``ResolveInfo`` may contain a reference to a HostDB record, which preserves the record even if it is
+replaced due to DNS queries in other transactions. The record is not required as the resolution
+information can be supplied directly without DNS or HostDB, e.g. a plugin sets the upstream address
+explicitly. The ``resolved_p`` flag indicates if the current information is valid and ready to be
+used or not. A result of this is there is no longer a specific holder for API provided addresses -
+the interface now puts the address in the ``ResolveInfo`` and marks it as resolved. This prevents
+further DNS / HostDB lookups and the address is used as is.
+
+The upstream port is a bit tricky and should be cleaned up. Currently value in ``srv_port``
+determines the port if set. If not, then the port in ``addr`` is used.
+
+Resolution Style
+----------------
+
+.. cpp:enum:: OS_Addr
+
+ Metadata about the source of the resolved address.'
+
+ .. cpp:enumerator:: TRY_DEFAULT
+
+ Use default resolution. This is the initial state.
+
+ .. cpp:enumerator:: TRY_HOSTDB
+
+ Use HostDB to resolve the target key.
+
+ .. cpp:enumerator:: TRY_CLIENT
+
+ Use the client supplied target address. This is used for transparent connections - the upstream
+ address is obtained from the inbound connection. May fail over to HostDB.
+
+ .. cpp:enumerator:: USE_HOSTDB
+
+ Use HostDB to resolve the target key.
+
+ .. cpp:enumerator:: USE_CLIENT
+
+ Use the client supplied target address.
+
+ .. cpp:enumerator:: USE_API
+
+ Use the address provided via the plugin API.
+
+ The parallel values for using HostDB and the client target address are to control fail over on
+ connection failure. The ``TRY_`` values can fail over to another style, but the ``USE_`` values
+ cannot. This prevents cycles of style changes by having any ``TRY_`` value fail over to a
+ ``USE_`` value, at which point it can no longer change. Note there is no ``TRY_API`` - if a
+ plugin sets the upstream address that is locked in.
+
+Issues
+======
+
+Currently if an upstream is marked down connections are still permitted, the only change is the
+number of retries. This has caused operational problems where dead systems are flooded with requests
+which, despite the timeouts, accumulate in ATS until ATS runs out of memory (there were instances of
+over 800K pending transactions). This also made it hard to bring the upstreams back online. With
+these changes requests to dead upstreams are strongly rate limited and other transactions are
+immediately terminated with a 502 response, protecting both the upstream and ATS.
+
+Future
+======
+
+There is still some work to be done in future PRs.
+
+* The fail window and the zombie window should be separate values. It is quite reasonable to want
+ to configure a very short fail window (possibly 0) with a moderately long zombie window so that
+ probing connections can immediately start going upstream at a low rate.
+
+* Failing an upstream should be more loosely connected to transactions. Currently there is a one
+ to one relationship where failure is defined as the failure of a specific transaction to connect.
+ There are situations where the number of connections attempts for mark a failure is should be
+ larger than the number of retries for a single transaction. For transiently busy upstreams and
+ low latency requests it can be reasonable to tune the per transaction timeout low with no retries
+ but this then risks marking down upstreams that were merely a bit slow at a given moment.
+
+* Parallel DNS requests should be supported. This is for both cross family requests and for split
+ DNS.
+
+* It would be nice to be able to do the probing connections to an upstream using synthetic requests
+ instead of burning actual user requests. What would be needed is a handoff from ATS to the probe
+ to indicate a particular upstream is considered down, at which point active health checks are done
+ until the upstream is once again alive, at which point this is handed off back to ATS.
+
+History
+=======
+
+This version has several major architectural changes from the previous version.
+
+* The data is split into records and info, not handled as a variant of a single data type. This
+ provides a noticeable simplification of the code.
+
+* Single and multiple address results are treated identically - a singleton is simply a multiple
+ of size 1. This yeilds a major simplification of the implementation.
+
+* Connections are throttled to dead upstreams, allowing only a single connection attempt per fail
+ window timing until a connection succeeds.
+
+* Timing information is stored in ``std::chrono`` data types instead of proprietary types.
+
+* State information has been promoted to atomics and updates are immediate rather than scheduled.
+ This also means the data in the state machine is a reference to a shared object, not a local copy.
+ The promotion was necessary to coordinate zombie connections to dead upstreams across transactions.
+
+* The "resolve key" is now a separate data object from the HTTP request. This is a subtle but
+ major change. The effect is requests can be routed to different upstreams without changing
+ the request. Parent selection can be greatly simplified as it become merely a matter of setting
+ the resolve key, rather than having a completely different code path.
diff --git a/doc/developer-guide/core-architecture/index.en.rst b/doc/developer-guide/core-architecture/index.en.rst
index e88e35fb74e..97f59712d72 100644
--- a/doc/developer-guide/core-architecture/index.en.rst
+++ b/doc/developer-guide/core-architecture/index.en.rst
@@ -26,5 +26,6 @@ Core Architecture
:maxdepth: 1
heap.en
+ hostdb.en
rpc.en
url_rewrite_architecture.en.rst
diff --git a/doc/uml/host-resolve.plantuml b/doc/uml/host-resolve.plantuml
new file mode 100644
index 00000000000..f3c6a6091e9
--- /dev/null
+++ b/doc/uml/host-resolve.plantuml
@@ -0,0 +1,24 @@
+' SPDX-License-Identifier: Apache-2.0
+' Licensed 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.
+
+@startuml
+
+hide empty description
+
+state HttpSM {
+ state do_http_server_open {
+ }
+}
+
+state HandleRequest #cyan
+state CallOSDNSLookup #cyan
+
+CallOSDNSLookup -> OSDNSLookup
+
+@enduml
+
diff --git a/example/plugins/c-api/protocol/TxnSM.c b/example/plugins/c-api/protocol/TxnSM.c
index 8f6ae5416a6..cb7f00f44ec 100644
--- a/example/plugins/c-api/protocol/TxnSM.c
+++ b/example/plugins/c-api/protocol/TxnSM.c
@@ -477,8 +477,6 @@ int
state_dns_lookup(TSCont contp, TSEvent event, TSHostLookupResult host_info)
{
TxnSM *txn_sm = (TxnSM *)TSContDataGet(contp);
- struct sockaddr const *q_server_addr;
- struct sockaddr_in ip_addr;
TSDebug(PLUGIN_NAME, "enter state_dns_lookup");
@@ -489,16 +487,16 @@ state_dns_lookup(TSCont contp, TSEvent event, TSHostLookupResult host_info)
txn_sm->q_pending_action = NULL;
/* Get the server IP from data structure TSHostLookupResult. */
- q_server_addr = TSHostLookupResultAddrGet(host_info);
+ struct sockaddr const *sa = TSHostLookupResultAddrGet(host_info);
/* Connect to the server using its IP. */
set_handler(txn_sm->q_current_handler, (TxnSMHandler)&state_connect_to_server);
TSAssert(txn_sm->q_pending_action == NULL);
- TSAssert(q_server_addr->sa_family == AF_INET); /* NO IPv6 in this plugin */
+ TSAssert(sa->sa_family == AF_INET); /* NO IPv6 in this plugin */
+ struct sockaddr_in *addr = (struct sockaddr_in *)(sa);
- memcpy(&ip_addr, q_server_addr, sizeof(ip_addr));
- ip_addr.sin_port = txn_sm->q_server_port;
- txn_sm->q_pending_action = TSNetConnect(contp, (struct sockaddr const *)&ip_addr);
+ addr->sin_port = txn_sm->q_server_port;
+ txn_sm->q_pending_action = TSNetConnect(contp, sa);
return TS_SUCCESS;
}
diff --git a/include/ts/ts.h b/include/ts/ts.h
index 4d35972e7e0..90b2b7ef68e 100644
--- a/include/ts/ts.h
+++ b/include/ts/ts.h
@@ -1950,7 +1950,13 @@ tsapi TSReturnCode TSPortDescriptorAccept(TSPortDescriptor, TSCont);
/* --------------------------------------------------------------------------
DNS Lookups */
tsapi TSAction TSHostLookup(TSCont contp, const char *hostname, size_t namelen);
+/** Retrieve an address from the host lookup.
+ *
+ * @param lookup_result Result handle passed to event callback.
+ * @return A @c sockaddr with the address if successful, a @c nullptr if not.
+ */
tsapi struct sockaddr const *TSHostLookupResultAddrGet(TSHostLookupResult lookup_result);
+
/* TODO: Eventually, we might want something like this as well, but it requires
support for building the HostDBInfo struct:
tsapi void TSHostLookupResultSet(TSHttpTxn txnp, TSHostLookupResult result);
diff --git a/include/tscore/BufferWriter.h b/include/tscore/BufferWriter.h
index 34e0b6bb541..85d0a74f83f 100644
--- a/include/tscore/BufferWriter.h
+++ b/include/tscore/BufferWriter.h
@@ -854,10 +854,10 @@ std::string &
bwprintv(std::string &s, ts::TextView fmt, std::tuple const &args)
{
auto len = s.size(); // remember initial size
- size_t n = ts::FixedBufferWriter(const_cast(s.data()), s.size()).printv(fmt, std::move(args)).extent();
+ size_t n = ts::FixedBufferWriter(const_cast(s.data()), s.size()).printv(fmt, args).extent();
s.resize(n); // always need to resize - if shorter, must clip pre-existing text.
if (n > len) { // dropped data, try again.
- ts::FixedBufferWriter(const_cast(s.data()), s.size()).printv(fmt, std::move(args));
+ ts::FixedBufferWriter(const_cast(s.data()), s.size()).printv(fmt, args);
}
return s;
}
diff --git a/include/tscore/BufferWriterForward.h b/include/tscore/BufferWriterForward.h
index 8da67c60b4c..7773486b92f 100644
--- a/include/tscore/BufferWriterForward.h
+++ b/include/tscore/BufferWriterForward.h
@@ -148,4 +148,11 @@ class BWFormat;
class BufferWriter;
+/// Storage for debug messages.
+/// If @c bwprint is used with this, the storage is reused which minimizes allocations.
+/// E.g.
+/// @code
+
+inline thread_local std::string bw_dbg;
+
} // namespace ts
diff --git a/include/tscore/Diags.h b/include/tscore/Diags.h
index 53cfd759f37..50a708efdfa 100644
--- a/include/tscore/Diags.h
+++ b/include/tscore/Diags.h
@@ -159,6 +159,15 @@ extern Diags *diags;
} \
} while (0)
+#define Debug_bw(tag, fmt, ...) \
+ do { \
+ if (unlikely(diags->on())) { \
+ static const SourceLocation loc = MakeSourceLocation(); \
+ static LogMessage log_message; \
+ log_message.debug(tag, loc, "%s", ts::bwprint(ts::bw_dbg, fmt, __VA_ARGS__).c_str()); \
+ } \
+ } while (0)
+
/** Same as Debug above, but this allows a positive override of the tag
* mechanism by a flag boolean.
*
diff --git a/include/tscore/bwf_std_format.h b/include/tscore/bwf_std_format.h
index e67c858fc0b..cb060edd170 100644
--- a/include/tscore/bwf_std_format.h
+++ b/include/tscore/bwf_std_format.h
@@ -26,6 +26,7 @@
#include
#include
#include
+#include
#include "tscpp/util/TextView.h"
#include "tscore/BufferWriterForward.h"
@@ -38,6 +39,20 @@ bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, atomic const &v)
return ts::bwformat(w, spec, v.load());
}
+template
+ts::BufferWriter &
+bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, chrono::duration const &d)
+{
+ return bwformat(w, spec, d.count());
+}
+
+template
+ts::BufferWriter &
+bwformat(ts::BufferWriter &w, ts::BWFSpec const &spec, chrono::time_point const &t)
+{
+ return bwformat(w, spec, t.time_since_epoch());
+}
+
} // end namespace std
namespace ts
@@ -130,5 +145,4 @@ namespace bwf
BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, bwf::Errno const &e);
BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, bwf::Date const &date);
BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, bwf::OptionalAffix const &opts);
-
} // namespace ts
diff --git a/include/tscore/ts_file.h b/include/tscore/ts_file.h
index c4389e948f6..8a9eff2befc 100644
--- a/include/tscore/ts_file.h
+++ b/include/tscore/ts_file.h
@@ -329,5 +329,12 @@ namespace file
/* ------------------------------------------------------------------- */
} // namespace file
+
+inline BufferWriter &
+bwformat(BufferWriter &w, BWFSpec const &spec, file::path const &path)
+{
+ return bwformat(w, spec, path.string());
+}
+
} // namespace ts
/* ------------------------------------------------------------------- */
diff --git a/iocore/dns/P_SplitDNSProcessor.h b/iocore/dns/P_SplitDNSProcessor.h
index 5fa119e5f5a..7424ccfdc7b 100644
--- a/iocore/dns/P_SplitDNSProcessor.h
+++ b/iocore/dns/P_SplitDNSProcessor.h
@@ -79,7 +79,7 @@ struct SplitDNS : public ConfigInfo {
SplitDNS();
~SplitDNS() override;
- void *getDNSRecord(const char *hostname);
+ void *getDNSRecord(ts::TextView hostname);
void findServer(RequestData *rdata, SplitDNSResult *result);
DNS_table *m_DNSSrvrTable = nullptr;
@@ -116,46 +116,34 @@ SplitDNSConfig::isSplitDNSEnabled()
class DNSRequestData : public RequestData
{
public:
- DNSRequestData();
-
- char *get_string() override;
+ DNSRequestData() = default;
+ char *
+ get_string() override
+ {
+ ink_release_assert(!"Do not get a writeable string from a DNS request");
+ };
const char *get_host() override;
sockaddr const *get_ip() override; // unused required virtual method.
sockaddr const *get_client_ip() override; // unused required virtual method.
- const char *m_pHost = nullptr;
+ ts::TextView m_pHost;
};
-/* --------------------------------------------------------------
- DNSRequestData::get_string()
- -------------------------------------------------------------- */
-TS_INLINE
-DNSRequestData::DNSRequestData() {}
-
-/* --------------------------------------------------------------
- DNSRequestData::get_string()
- -------------------------------------------------------------- */
-TS_INLINE char *
-DNSRequestData::get_string()
-{
- return ats_strdup((char *)m_pHost);
-}
-
/* --------------------------------------------------------------
DNSRequestData::get_host()
-------------------------------------------------------------- */
-TS_INLINE const char *
+inline const char *
DNSRequestData::get_host()
{
- return m_pHost;
+ return m_pHost.data();
}
/* --------------------------------------------------------------
DNSRequestData::get_ip()
-------------------------------------------------------------- */
-TS_INLINE sockaddr const *
+inline sockaddr const *
DNSRequestData::get_ip()
{
return nullptr;
@@ -164,7 +152,7 @@ DNSRequestData::get_ip()
/* --------------------------------------------------------------
DNSRequestData::get_client_ip()
-------------------------------------------------------------- */
-TS_INLINE sockaddr const *
+inline sockaddr const *
DNSRequestData::get_client_ip()
{
return nullptr;
diff --git a/iocore/dns/SRV.h b/iocore/dns/SRV.h
index ff75689e74e..560223636e4 100644
--- a/iocore/dns/SRV.h
+++ b/iocore/dns/SRV.h
@@ -25,7 +25,6 @@
#include
#include "tscore/ink_platform.h"
-#include "I_HostDBProcessor.h"
struct HostDBInfo;
diff --git a/iocore/dns/SplitDNS.cc b/iocore/dns/SplitDNS.cc
index 802e97981cd..b993f1e7f51 100644
--- a/iocore/dns/SplitDNS.cc
+++ b/iocore/dns/SplitDNS.cc
@@ -178,9 +178,9 @@ SplitDNSConfig::print()
SplitDNS::getDNSRecord()
-------------------------------------------------------------- */
void *
-SplitDNS::getDNSRecord(const char *hostname)
+SplitDNS::getDNSRecord(ts::TextView hostname)
{
- Debug("splitdns", "Called SplitDNS::getDNSRecord(%s)", hostname);
+ Debug("splitdns", "Called SplitDNS::getDNSRecord(%.*s)", int(hostname.size()), hostname.data());
DNSRequestData *pRD = DNSReqAllocator.alloc();
pRD->m_pHost = hostname;
@@ -191,7 +191,7 @@ SplitDNS::getDNSRecord(const char *hostname)
DNSReqAllocator.free(pRD);
if (DNS_SRVR_SPECIFIED == res.r) {
- return (void *)&(res.m_rec->m_servers);
+ return &(res.m_rec->m_servers);
}
Debug("splitdns", "Fail to match a valid splitdns rule, fallback to default dns resolver");
diff --git a/iocore/hostdb/HostDB.cc b/iocore/hostdb/HostDB.cc
index 89968f6ca03..9a1ab5cce30 100644
--- a/iocore/hostdb/HostDB.cc
+++ b/iocore/hostdb/HostDB.cc
@@ -26,14 +26,18 @@
#include "P_RefCountCacheSerializer.h"
#include "tscore/I_Layout.h"
#include "Show.h"
-#include "tscore/Tokenizer.h"
+#include "tscore/ts_file.h"
#include "tscore/ink_apidefs.h"
+#include "tscore/bwf_std_format.h"
#include
#include
#include
#include
#include
+#include
+
+using ts::TextView;
HostDBProcessor hostDBProcessor;
int HostDBProcessor::hostdb_strict_round_robin = 0;
@@ -50,78 +54,215 @@ 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
+ts_seconds hostdb_hostfile_check_interval{std::chrono::hours(24)};
// Epoch timestamp of the current hosts file check.
-ink_time_t hostdb_current_interval = 0;
+ts_time hostdb_current_interval{TS_TIME_ZERO};
// Epoch timestamp of the last time we actually checked for a hosts file update.
-static ink_time_t hostdb_last_interval = 0;
+static ts_time hostdb_last_interval{TS_TIME_ZERO};
// 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;
-
-// Verify the generic storage is sufficient to cover all alternate members.
-static_assert(sizeof(HostDBApplicationInfo::allotment) == sizeof(HostDBApplicationInfo),
- "Generic storage for HostDBApplicationInfo is smaller than the union storage.");
+static ts_time hostdb_hostfile_update_timestamp{TS_TIME_ZERO};
+static char hostdb_filename[PATH_NAME_MAX] = DEFAULT_HOST_DB_FILENAME;
+int hostdb_max_count = DEFAULT_HOST_DB_SIZE;
+static ts::file::path hostdb_hostfile_path;
+ts_seconds hostdb_sync_frequency{0};
+int hostdb_disable_reverse_lookup = 0;
+int hostdb_max_iobuf_index = BUFFER_SIZE_INDEX_32K;
ClassAllocator hostDBContAllocator("hostDBContAllocator");
+namespace
+{
+/** Assign raw storage to an @c IpAddr
+ *
+ * @param ip Destination.
+ * @param af IP family.
+ * @param ptr Raw data for an address of family @a af.
+ */
+void
+ip_addr_set(IpAddr &ip, ///< Target storage.
+ uint8_t af, ///< Address format.
+ void const *ptr ///< Raw address data
+)
+{
+ if (AF_INET6 == af) {
+ ip = *static_cast(ptr);
+ } else if (AF_INET == af) {
+ ip = *static_cast(ptr);
+ } else {
+ ip.invalidate();
+ }
+}
+
+unsigned int
+HOSTDB_CLIENT_IP_HASH(sockaddr const *lhs, IpAddr const &rhs)
+{
+ unsigned int zret = ~static_cast(0);
+ if (lhs->sa_family == rhs.family()) {
+ if (rhs.isIp4()) {
+ in_addr_t ip1 = ats_ip4_addr_cast(lhs);
+ in_addr_t ip2 = rhs._addr._ip4;
+ zret = (ip1 >> 16) ^ ip1 ^ ip2 ^ (ip2 >> 16);
+ } else if (rhs.isIp6()) {
+ uint32_t const *ip1 = ats_ip_addr32_cast(lhs);
+ uint32_t const *ip2 = rhs._addr._u32;
+ for (int i = 0; i < 4; ++i, ++ip1, ++ip2) {
+ zret ^= (*ip1 >> 16) ^ *ip1 ^ *ip2 ^ (*ip2 >> 16);
+ }
+ }
+ }
+ return zret & 0xFFFF;
+}
+
+} // namespace
+
+char const *
+name_of(HostDBType t)
+{
+ switch (t) {
+ case HostDBType::UNSPEC:
+ return "*";
+ case HostDBType::ADDR:
+ return "Address";
+ case HostDBType::SRV:
+ return "SRV";
+ case HostDBType::HOST:
+ return "Reverse DNS";
+ }
+ return "";
+}
+
+/** Template for creating conversions and initialization for @c std::chrono based configuration variables.
+ *
+ * @tparam V The exact type of the configuration variable.
+ *
+ * The tricky template code is to enable having a class instance for each configuration variable, instead of for each _type_ of
+ * configuration variable. This is required because the callback interface requires functions and so the actual storage must be
+ * accessible from that function. *
+ */
+template struct ConfigDuration {
+ using self_type = ConfigDuration;
+ V *_var; ///< Pointer to the variable to control.
+
+ /** Constructor.
+ *
+ * @param v The variable to update.
+ */
+ ConfigDuration(V &v) : _var(&v) {}
+
+ /// Convert to the mgmt (configuration) type.
+ static MgmtInt
+ to_mgmt(void const *data)
+ {
+ return static_cast(static_cast(data)->count());
+ }
+
+ /// Convert from the mgmt (configuration) type.
+ static void
+ from_mgmt(void *data, MgmtInt i)
+ {
+ *static_cast(data) = V{i};
+ }
+
+ /// The conversion structure, which handles @c MgmtInt.
+ static inline const MgmtConverter Conversions{&to_mgmt, &from_mgmt};
+
+ /** Process start up conversion from configuration.
+ *
+ * @param type The data type in the configuration.
+ * @param data The data in the configuration.
+ * @param var Pointer to the variable to update.
+ * @return @c true if @a data was successfully converted and stored, @c false if not.
+ *
+ * @note @a var is the target variable because it was explicitly set to be the value of @a _var in @c Enable.
+ */
+ static bool
+ callback(char const *, RecDataT type, RecData data, void *var)
+ {
+ if (RECD_INT == type) {
+ (*self_type::Conversions.store_int)(var, data.rec_int);
+ return true;
+ }
+ return false;
+ }
+
+ /** Enable.
+ *
+ * @param name Name of the configuration variable.
+ *
+ * This enables both reading from the configuration and handling the callback for dynamic
+ * updates of the variable.
+ */
+ void
+ Enable(std::string_view name)
+ {
+ Enable_Config_Var(name, &self_type::callback, _var);
+ }
+};
+
+ConfigDuration HostDBDownServerCacheTimeVar{HttpConfig::m_master.oride.down_server_timeout};
+// Make the conversions visible to the plugin API. This allows exporting just the conversions
+// without having to export the class definition. Again, the compiler doesn't allow doing this
+// in one line.
+extern MgmtConverter const &HostDBDownServerCacheTimeConv;
+MgmtConverter const &HostDBDownServerCacheTimeConv = HostDBDownServerCacheTimeVar.Conversions;
+
+// Not run time configurable, therefore no support beyond this class needed.
+ConfigDuration HostDBSyncFrequencyVar{hostdb_sync_frequency};
+
+void
+HostDB_Config_Init()
+{
+ HostDBDownServerCacheTimeVar.Enable("proxy.config.http.down_server.cache_time");
+ HostDBSyncFrequencyVar.Enable("proxy.config.cache.hostdb.sync_frequency");
+}
+
// Static configuration information
HostDBCache hostDB;
-void ParseHostFile(const char *path, unsigned int interval);
+void ParseHostFile(ts::file::path const &path, ts_seconds interval);
-char *
-HostDBInfo::srvname(HostDBRoundRobin *rr) const
+auto
+HostDBInfo::assign(sa_family_t af, void const *addr) -> self_type &
{
- if (!is_srv || !data.srv.srv_offset) {
- return nullptr;
- }
- return reinterpret_cast(rr) + data.srv.srv_offset;
+ type = HostDBType::ADDR;
+ ip_addr_set(data.ip, af, addr);
+ return *this;
}
-static inline bool
-is_addr_valid(uint8_t af, ///< Address family (format of data)
- void *ptr ///< Raw address data (not a sockaddr variant!)
-)
+auto
+HostDBInfo::assign(IpAddr const &addr) -> self_type &
{
- return (AF_INET == af && INADDR_ANY != *(reinterpret_cast(ptr))) ||
- (AF_INET6 == af && !IN6_IS_ADDR_UNSPECIFIED(reinterpret_cast(ptr)));
+ type = HostDBType::ADDR;
+ data.ip = addr;
+ return *this;
}
-static inline void
-ip_addr_set(sockaddr *ip, ///< Target storage, sockaddr compliant.
- uint8_t af, ///< Address format.
- void *ptr ///< Raw address data
-)
+auto
+HostDBInfo::assign(SRV const *srv, char const *name) -> self_type &
{
- if (AF_INET6 == af) {
- ats_ip6_set(ip, *static_cast(ptr));
- } else if (AF_INET == af) {
- ats_ip4_set(ip, *static_cast(ptr));
- } else {
- ats_ip_invalidate(ip);
- }
+ type = HostDBType::SRV;
+ data.srv.srv_weight = srv->weight;
+ data.srv.srv_priority = srv->priority;
+ data.srv.srv_port = srv->port;
+ data.srv.key = srv->key;
+ data.srv.srv_offset = reinterpret_cast(this) - name;
+ return *this;
+}
+
+char const *
+HostDBInfo::srvname() const
+{
+ return data.srv.srv_offset ? reinterpret_cast(this) + data.srv.srv_offset : nullptr;
}
-static inline void
-ip_addr_set(IpAddr &ip, ///< Target storage.
- uint8_t af, ///< Address format.
- void *ptr ///< Raw address data
+static inline bool
+is_addr_valid(uint8_t af, ///< Address family (format of data)
+ void *ptr ///< Raw address data (not a sockaddr variant!)
)
{
- if (AF_INET6 == af) {
- ip = *static_cast(ptr);
- } else if (AF_INET == af) {
- ip = *static_cast(ptr);
- } else {
- ip.invalidate();
- }
+ return (AF_INET == af && INADDR_ANY != *(reinterpret_cast(ptr))) ||
+ (AF_INET6 == af && !IN6_IS_ADDR_UNSPECIFIED(reinterpret_cast(ptr)));
}
inline void
@@ -169,18 +310,12 @@ string_for(HostDBMark mark)
static Action *register_ShowHostDB(Continuation *c, HTTPHdr *h);
HostDBHash &
-HostDBHash::set_host(const char *name, int len)
+HostDBHash::set_host(TextView name)
{
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) {
+ if (!host_name.empty() && SplitDNSConfig::isSplitDNSEnabled()) {
+ if (TS_SUCCESS != ip.load(host_name)) {
// 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) {
@@ -206,7 +341,7 @@ HostDBHash::refresh()
const char *server_line = dns_server ? dns_server->x_dns_ip_line : nullptr;
uint8_t m = static_cast(db_mark); // be sure of the type.
- ctx.update(host_name, host_len);
+ ctx.update(host_name.data(), host_name.size());
ctx.update(reinterpret_cast(&port), sizeof(port));
ctx.update(&m, sizeof(m));
if (server_line) {
@@ -235,10 +370,7 @@ HostDBHash::~HostDBHash()
}
}
-HostDBCache::HostDBCache()
-{
- hosts_file_ptr = new RefCountedHostsFileMap();
-}
+HostDBCache::HostDBCache() {}
bool
HostDBCache::is_pending_dns_for_hash(const CryptoHash &hash)
@@ -252,6 +384,14 @@ HostDBCache::is_pending_dns_for_hash(const CryptoHash &hash)
return false;
}
+std::shared_ptr
+HostDBCache::acquire_host_file()
+{
+ std::shared_lock lock(host_file_mutex);
+ auto zret = host_file;
+ return zret;
+}
+
HostDBCache *
HostDBProcessor::cache()
{
@@ -259,16 +399,16 @@ HostDBProcessor::cache()
}
struct HostDBBackgroundTask : public Continuation {
- int frequency;
- ink_hrtime start_time;
+ ts_seconds frequency;
+ ts_hr_time start_time;
virtual int sync_event(int event, void *edata) = 0;
int wait_event(int event, void *edata);
- HostDBBackgroundTask(int frequency);
+ HostDBBackgroundTask(ts_seconds frequency);
};
-HostDBBackgroundTask::HostDBBackgroundTask(int frequency) : Continuation(new_ProxyMutex()), frequency(frequency), start_time(0)
+HostDBBackgroundTask::HostDBBackgroundTask(ts_seconds frequency) : Continuation(new_ProxyMutex()), frequency(frequency)
{
SET_HANDLER(&HostDBBackgroundTask::sync_event);
}
@@ -276,11 +416,11 @@ HostDBBackgroundTask::HostDBBackgroundTask(int frequency) : Continuation(new_Pro
int
HostDBBackgroundTask::wait_event(int, void *)
{
- ink_hrtime next_sync = HRTIME_SECONDS(this->frequency) - (Thread::get_hrtime() - start_time);
+ auto next_sync = this->frequency - (ts_hr_clock::now() - start_time);
SET_HANDLER(&HostDBBackgroundTask::sync_event);
- if (next_sync > HRTIME_MSECONDS(100)) {
- eventProcessor.schedule_in(this, next_sync, ET_TASK);
+ if (next_sync > ts_milliseconds{100}) {
+ eventProcessor.schedule_in(this, std::chrono::duration_cast(next_sync).count(), ET_TASK);
} else {
eventProcessor.schedule_imm(this, ET_TASK);
}
@@ -290,16 +430,16 @@ HostDBBackgroundTask::wait_event(int, void *)
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)
+ HostDBSync(ts_seconds 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();
+ start_time = ts_hr_clock::now();
- new RefCountCacheSerializer(this, hostDBProcessor.cache()->refcountcache, this->frequency, this->storage_path,
- this->full_path);
+ new RefCountCacheSerializer(this, hostDBProcessor.cache()->refcountcache, this->frequency.count(),
+ this->storage_path, this->full_path);
return EVENT_DONE;
}
};
@@ -327,8 +467,6 @@ HostDBCache::start(int flags)
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");
@@ -337,13 +475,13 @@ HostDBCache::start(int flags)
}
// Setup the ref-counted cache (this must be done regardless of syncing or not).
- this->refcountcache = new RefCountCache(hostdb_partitions, hostdb_max_size, hostdb_max_count, HostDBInfo::version(),
- "proxy.process.hostdb.cache.");
+ this->refcountcache = new RefCountCache(hostdb_partitions, hostdb_max_size, hostdb_max_count, HostDBRecord::Version,
+ "proxy.process.hostdb.cache.");
//
// Load and sync HostDB, if we've asked for it.
//
- if (hostdb_sync_frequency > 0) {
+ if (hostdb_sync_frequency.count() > 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') {
@@ -366,7 +504,7 @@ HostDBCache::start(int flags)
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(*this->refcountcache, full_path, HostDBInfo::unmarshall);
+ int load_ret = LoadRefCountCacheFromPath(*this->refcountcache, full_path, HostDBRecord::unmarshall);
if (load_ret != 0) {
Warning("Error loading cache from %s: %d", full_path, load_ret);
}
@@ -411,13 +549,12 @@ HostDBProcessor::start(int, size_t)
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();
+ hostdb_current_interval = ts_clock::now();
HostDBContinuation *b = hostDBContAllocator.alloc();
SET_CONTINUATION_HANDLER(b, (HostDBContHandler)&HostDBContinuation::backgroundEvent);
@@ -430,18 +567,14 @@ HostDBProcessor::start(int, size_t)
void
HostDBContinuation::init(HostDBHash const &the_hash, Options const &opt)
{
- hash = the_hash;
- if (hash.host_name) {
+ hash = the_hash;
+ hash.host_name = hash.host_name.prefix(static_cast(sizeof(hash_host_name_store) - 1));
+ if (!hash.host_name.empty()) {
// copy to backing store.
- if (hash.host_len > static_cast(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;
+ memcpy(hash_host_name_store, hash.host_name);
}
- hash_host_name_store[hash.host_len] = 0;
- hash.host_name = hash_host_name_store;
+ hash_host_name_store[hash.host_name.size()] = 0;
+ hash.host_name.assign(hash_host_name_store, hash.host_name.size());
host_res_style = opt.host_res_style;
dns_lookup_timeout = opt.timeout;
@@ -460,7 +593,7 @@ HostDBContinuation::refresh_hash()
{
Ptr old_bucket_mutex = hostDB.refcountcache->lock_for_key(hash.hash.fold());
// We're not pending DNS anymore.
- remove_trigger_pending_dns();
+ remove_and_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.
@@ -470,34 +603,22 @@ HostDBContinuation::refresh_hash()
}
static bool
-reply_to_cont(Continuation *cont, HostDBInfo *r, bool is_srv = false)
+reply_to_cont(Continuation *cont, HostDBRecord *r, bool is_srv = false)
{
- if (r == nullptr || r->is_srv != is_srv || r->is_failed()) {
+ 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()) {
+ if (r->record_type != HostDBType::HOST) {
+ if (!r->name()) {
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));
+ Debug("hostdb", "hostname = %s", r->name());
}
cont->handleEvent(is_srv ? EVENT_SRV_LOOKUP : EVENT_HOST_DB_LOOKUP, r);
@@ -541,73 +662,57 @@ db_mark_for(IpAddr const &ip)
return ip.isIp6() ? HOSTDB_MARK_IPV6 : HOSTDB_MARK_IPV4;
}
-Ptr
+HostDBRecord::Handle
probe(const Ptr &mutex, HostDBHash const &hash, bool ignore_timeout)
{
+ static const Ptr NO_RECORD;
+
// If hostdb is disabled, don't return anything
if (!hostdb_enable) {
- return Ptr();
+ return NO_RECORD;
}
// 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 r = hostDB.refcountcache->get(folded_hash);
+ // get the record from cache
+ Ptr record = hostDB.refcountcache->get(folded_hash);
// If there was nothing in the cache-- this is a miss
- if (r.get() == nullptr) {
- return r;
+ if (record.get() == nullptr) {
+ return record;
}
// 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()) {
+ if (record->is_failed() && record->is_ip_fail_timeout()) {
+ return NO_RECORD;
+ // if we aren't ignoring timeouts, and we are past it-- then remove the record
+ } else if (!ignore_timeout && record->is_ip_timeout() && !record->serve_stale_but_revalidate()) {
HOSTDB_INCREMENT_DYN_STAT(hostdb_ttl_expires_stat);
- return make_ptr((HostDBInfo *)nullptr);
+ return NO_RECORD;
}
// 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 ((!ignore_timeout && record->is_ip_stale() && record->record_type != HostDBType::HOST) ||
+ (record->is_ip_timeout() && record->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);
+ Debug("hostdb", "%s",
+ ts::bwprint(ts::bw_dbg, "stale {} {} {}, using with pending refresh", record->ip_interval(),
+ record->ip_timestamp.time_since_epoch(), record->ip_timeout_interval)
+ .c_str());
+ return record;
+ }
+ Debug("hostdb", "%s",
+ ts::bwprint(ts::bw_dbg, "stale {} {} {}, using while refresh", record->ip_interval(),
+ record->ip_timestamp.time_since_epoch(), record->ip_timeout_interval)
+ .c_str());
HostDBContinuation *c = hostDBContAllocator.alloc();
HostDBContinuation::Options copt;
- copt.host_res_style = host_res_style_for(r->ip());
+ copt.host_res_style = record->af_family == AF_INET6 ? HOST_RES_IPV6_ONLY : HOST_RES_IPV4_ONLY;
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;
+ return record;
}
//
@@ -658,7 +763,7 @@ HostDBProcessor::getby(Continuation *cont, cb_process_result_pfn cb_process_resu
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 r = probe(bucket_mutex, hash, false);
+ HostDBRecord::Handle 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) {
@@ -667,10 +772,10 @@ HostDBProcessor::getby(Continuation *cont, cb_process_result_pfn cb_process_resu
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);
+ Debug("hostdb", "immediate SRV answer for %.*s from hostdb", int(hash.host_name.size()), hash.host_name.data());
+ Debug("dns_srv", "immediate SRV answer for %.*s from hostdb", int(hash.host_name.size()), hash.host_name.data());
} else if (hash.host_name) {
- Debug("hostdb", "immediate answer for %.*s", hash.host_len, hash.host_name);
+ Debug("hostdb", "immediate answer for %.*s", int(hash.host_name.size()), hash.host_name.data());
} else {
Debug("hostdb", "immediate answer for %s", hash.ip.isValid() ? hash.ip.toString(ipb, sizeof ipb) : "");
}
@@ -688,12 +793,13 @@ HostDBProcessor::getby(Continuation *cont, cb_process_result_pfn cb_process_resu
}
}
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);
+ Debug("hostdb", "delaying (force=%d) SRV answer for %.*s [timeout = %d]", force_dns, int(hash.host_name.size()),
+ hash.host_name.data(), opt.timeout);
+ Debug("dns_srv", "delaying (force=%d) SRV answer for %.*s [timeout = %d]", force_dns, int(hash.host_name.size()),
+ hash.host_name.data(), 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);
+ Debug("hostdb", "delaying (force=%d) answer for %.*s [timeout %d]", force_dns, int(hash.host_name.size()),
+ hash.host_name.data(), opt.timeout);
} else {
Debug("hostdb", "delaying (force=%d) answer for %s [timeout %d]", force_dns,
hash.ip.isValid() ? hash.ip.toString(ipb, sizeof ipb) : "", opt.timeout);
@@ -726,7 +832,7 @@ HostDBProcessor::getbyname_re(Continuation *cont, const char *ahostname, int len
ink_assert(nullptr != ahostname);
// Load the hash data.
- hash.set_host(ahostname, ahostname ? (len ? len : strlen(ahostname)) : 0);
+ 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);
@@ -743,7 +849,7 @@ HostDBProcessor::getbynameport_re(Continuation *cont, const char *ahostname, int
ink_assert(nullptr != ahostname);
// Load the hash data.
- hash.set_host(ahostname, ahostname ? (len ? len : strlen(ahostname)) : 0);
+ 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);
@@ -782,7 +888,7 @@ HostDBProcessor::getSRVbyname_imm(Continuation *cont, cb_process_result_pfn proc
ink_assert(nullptr != hostname);
- hash.set_host(hostname, len ? len : strlen(hostname));
+ hash.set_host({hostname, len ? len : strlen(hostname)});
// Leave hash.ip invalid
hash.port = 0;
hash.db_mark = HOSTDB_MARK_SRV;
@@ -802,7 +908,7 @@ HostDBProcessor::getbyname_imm(Continuation *cont, cb_process_result_pfn process
ink_assert(nullptr != hostname);
- hash.set_host(hostname, len ? len : strlen(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.
@@ -837,150 +943,35 @@ HostDBProcessor::iterate(Continuation *cont)
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 : "");
- 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 : "");
- 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 mutex = hostDB.refcountcache->lock_for_key(hash.hash.fold());
- EThread *thread = this_ethread();
- MUTEX_TRY_LOCK(lock, mutex, thread);
-
- if (lock.is_locked()) {
- Ptr 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 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;
-}
-
// 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)
+Ptr
+HostDBContinuation::lookup_done(TextView query_name, ts_seconds answer_ttl, SRVHosts *srv, Ptr record)
{
ink_assert(this_ethread() == hostDB.refcountcache->lock_for_key(hash.hash.fold())->thread_holding);
- if (!ip.isValid() || !aname || !aname[0]) {
+ ink_assert(record);
+ if (query_name.empty()) {
if (is_byname()) {
- Debug("hostdb", "lookup_done() failed for '%.*s'", hash.host_len, hash.host_name);
+ Debug("hostdb", "lookup_done() failed for '%.*s'", int(hash.host_name.size()), hash.host_name.data());
} else if (is_srv()) {
- Debug("dns_srv", "SRV failed for '%.*s'", hash.host_len, hash.host_name);
+ Debug("dns_srv", "SRV failed for '%.*s'", int(hash.host_name.size()), hash.host_name.data());
} 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);
- }
+ record->ip_timestamp = hostdb_current_interval;
+ record->ip_timeout_interval = ts_seconds(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();
+ if (is_srv()) {
+ record->record_type = HostDBType::SRV;
+ } else if (!is_byname()) {
+ record->record_type = HostDBType::HOST;
+ }
- r->set_failed();
- return r;
+ record->set_failed();
+ return record;
} else {
switch (hostdb_ttl_mode) {
@@ -989,65 +980,38 @@ HostDBContinuation::lookup_done(IpAddr const &ip, const char *aname, bool around
case TTL_OBEY:
break;
case TTL_IGNORE:
- ttl_seconds = hostdb_ip_timeout_interval;
+ answer_ttl = ts_seconds(hostdb_ip_timeout_interval);
break;
case TTL_MIN:
- if (hostdb_ip_timeout_interval < ttl_seconds) {
- ttl_seconds = hostdb_ip_timeout_interval;
+ if (ts_seconds(hostdb_ip_timeout_interval) < answer_ttl) {
+ answer_ttl = ts_seconds(hostdb_ip_timeout_interval);
}
break;
case TTL_MAX:
- if (hostdb_ip_timeout_interval > ttl_seconds) {
- ttl_seconds = hostdb_ip_timeout_interval;
+ if (ts_seconds(hostdb_ip_timeout_interval) > answer_ttl) {
+ answer_ttl = ts_seconds(hostdb_ip_timeout_interval);
}
break;
}
- HOSTDB_SUM_DYN_STAT(hostdb_ttl_stat, ttl_seconds);
+ HOSTDB_SUM_DYN_STAT(hostdb_ttl_stat, answer_ttl.count());
- 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);
- }
+ // update the TTL
+ record->ip_timestamp = hostdb_current_interval;
+ record->ip_timeout_interval = std::clamp(answer_ttl, ts_seconds(1), ts_seconds(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;
+ Debug_bw("hostdb", "done {} TTL {}", hash.host_name, answer_ttl);
} 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));
- }
+ ink_assert(srv && srv->hosts.size() && srv->hosts.size() <= hostdb_round_robin_max_count);
+ record->record_type = HostDBType::SRV;
} 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;
+ Debug_bw("hostdb", "done {} TTL {}", hash.host_name, answer_ttl);
+ record->record_type = HostDBType::HOST;
}
}
- ink_assert(!r->round_robin || !r->reverse_dns);
- return r;
+ return record;
}
int
@@ -1077,28 +1041,7 @@ HostDBContinuation::dnsPendingEvent(int event, Event *e)
}
}
-// 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)
{
@@ -1117,7 +1060,7 @@ HostDBContinuation::dnsEvent(int event, HostEnt *e)
// actual DNS query. If the request rate is high enough this can cause a persistent queue where the
// DNS query is never sent and all requests timeout, even if it was a transient error.
// See issue #8417.
- remove_trigger_pending_dns();
+ remove_and_trigger_pending_dns();
} else {
// "local" signal to give up, usually due this being one of those "other" queries.
// That generally means @a this has already been removed from the queue, but just in case...
@@ -1143,38 +1086,25 @@ HostDBContinuation::dnsEvent(int event, HostEnt *e)
} 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
+ ttl = ts_seconds(failed ? 0 : e->ttl);
- Ptr old_r = probe(mutex, hash, false);
+ Ptr 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) {
+
+ int valid_records = 0;
+ void *first_record = nullptr;
+ sa_family_t af = e ? e->ent.h_addrtype : AF_UNSPEC; // address family
+
+ // Find the first record and total number of records.
+ if (!failed) {
+ if (is_srv()) {
valid_records = e->srv_hosts.hosts.size();
} else {
void *ptr; // tmp for current entry.
@@ -1194,160 +1124,92 @@ HostDBContinuation::dnsEvent(int event, HostEnt *e)
++valid_records;
} else {
- Warning("Zero address removed from round-robin list for '%s'", hash.host_name);
+ Warning("Invalid address removed for '%.*s'", int(hash.host_name.size()), hash.host_name.data());
}
}
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.
+ } // else first is nullptr
// 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;
+ TextView query_name = (failed || !hash.host_name.empty()) ? hash.host_name : TextView{e->ent.h_name, strlen(e->ent.h_name)};
+ HostDBRecord::Handle r{HostDBRecord::alloc(query_name, valid_records, failed ? 0 : e->srv_hosts.srv_hosts_length)};
+ r->key = hash.hash.fold(); // always set the key
+ r->af_family = af;
+ r->flags.f.failed_p = failed;
// 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();
+ r = old_r;
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);
+ lookup_done(hash.host_name, ttl, 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);
+ lookup_done(hash.host_name, /* hostname */
+ ttl, /* ttl in seconds */
+ failed ? nullptr : &e->srv_hosts, r);
} else if (failed) {
- r = lookup_done(tip, hash.host_name, false, ttl_seconds, nullptr, r);
+ lookup_done(hash.host_name, ttl, nullptr, r);
} else {
- r = lookup_done(hash.ip, e->ent.h_name, false, ttl_seconds, &e->srv_hosts, r);
+ lookup_done(e->ent.h_name, ttl, &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(r->rr());
- ;
+ if (!failed) { // implies r != old_r
+ auto rr_info = r->rr_info();
+ // Fill in record type specific data.
if (is_srv()) {
- int skip = 0;
- char *pos = reinterpret_cast(rr_data) + sizeof(HostDBRoundRobin) + valid_records * sizeof(HostDBInfo);
+ char *pos = rr_info.rebind().end();
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(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;
- }
+ std::sort(q, q + valid_records, [](SRV *lhs, SRV *rhs) -> bool { return *lhs < *rhs; });
+
+ SRV **cur_srv = q;
+ for (auto &item : rr_info) {
+ auto t = *cur_srv++; // get next SRV record pointer.
+ memcpy(pos, t->host, t->host_len); // Append the name to the overall record.
+ item.assign(t, pos);
+ pos += t->host_len;
+ if (old_r) { // migrate as needed.
+ for (auto &old_item : old_r->rr_info()) {
+ if (item.data.srv.key == old_item.data.srv.key && 0 == strcmp(item.srvname(), old_item.srvname())) {
+ item.migrate_from(old_item);
+ break;
}
}
}
+ // Archetypical example - "%zd" doesn't work on FreeBSD, "%ld" doesn't work on Ubuntu, "%lld" doesn't work on Fedora.
+ Debug_bw("dns_srv", "inserted SRV RR record [{}] into HostDB with TTL: {} seconds", t->host, ttl);
}
} 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;
+ unsigned idx = 0;
+ for (auto &item : rr_info) {
+ item.assign(af, e->ent.h_addr_list[idx++]);
+ if (old_r) { // migrate as needed.
+ for (auto &old_item : old_r->rr_info()) {
+ if (item.data.ip == old_item.data.ip) {
+ item.migrate_from(old_item);
+ break;
+ }
+ }
}
}
}
}
- 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());
+ if (!serve_stale) { // implies r != old_r
+ hostDB.refcountcache->put(
+ r->key, r.get(), r->_record_size,
+ (r->ip_timestamp + r->ip_timeout_interval + ts_seconds(hostdb_serve_stale_but_revalidate)).time_since_epoch().count());
} else {
- Warning("Fallback to serving stale record, skip re-update of hostdb for %s", aname);
+ Warning("Fallback to serving stale record, skip re-update of hostdb for %.*s", int(query_name.size()), query_name.data());
}
// try to callback the user
@@ -1372,7 +1234,7 @@ HostDBContinuation::dnsEvent(int event, HostEnt *e)
if (action.continuation->mutex) {
ink_release_assert(action.continuation->mutex == action.mutex);
}
- reply_to_cont(action.continuation, r, is_srv());
+ reply_to_cont(action.continuation, r.get(), is_srv());
}
need_to_reschedule = false;
}
@@ -1389,7 +1251,7 @@ HostDBContinuation::dnsEvent(int event, HostEnt *e)
hostDB.pending_dns_for_hash(hash.hash).remove(this);
// wake up everyone else who is waiting
- remove_trigger_pending_dns();
+ remove_and_trigger_pending_dns();
hostdb_cont_free(this);
@@ -1432,7 +1294,7 @@ HostDBContinuation::iterateEvent(int event, Event *e)
IntrusiveHashMap &partMap = hostDB.refcountcache->get_partition(current_iterate_pos).get_map();
for (const auto &it : partMap) {
- HostDBInfo *r = static_cast(it.item.get());
+ auto *r = static_cast(it.item.get());
if (r && !r->is_failed()) {
action.continuation->handleEvent(EVENT_INTERVAL, static_cast(r));
}
@@ -1498,7 +1360,7 @@ HostDBContinuation::probeEvent(int /* event ATS_UNUSED */, Event *e)
if (!force_dns) {
// Do the probe
//
- Ptr r = probe(mutex, hash, false);
+ Ptr r = probe(mutex, hash, false);
if (r) {
HOSTDB_INCREMENT_DYN_STAT(hostdb_total_hits_stat);
@@ -1544,7 +1406,7 @@ HostDBContinuation::set_check_pending_dns()
}
void
-HostDBContinuation::remove_trigger_pending_dns()
+HostDBContinuation::remove_and_trigger_pending_dns()
{
Queue &q = hostDB.pending_dns_for_hash(hash.hash);
q.remove(this);
@@ -1582,31 +1444,42 @@ HostDBContinuation::do_dns()
{
ink_assert(!action.cancelled);
if (is_byname()) {
- Debug("hostdb", "DNS %s", hash.host_name);
+ Debug("hostdb", "DNS %.*s", int(hash.host_name.size()), hash.host_name.data());
IpAddr tip;
if (0 == tip.load(hash.host_name)) {
- // check 127.0.0.1 format // What the heck does that mean? - AMC
+ // Need to consider if this is necessary - could the record in ResolveInfo be left null and
+ // just the resolved address set?
if (action.continuation) {
- HostDBInfo *r = lookup_done(tip, hash.host_name, false, HOST_DB_MAX_TTL, nullptr);
-
- reply_to_cont(action.continuation, r);
+ HostDBRecord::Handle r{HostDBRecord::alloc(hash.host_name, 1)};
+ r->af_family = tip.family();
+ auto &info = r->rr_info()[0];
+ info.assign(tip);
+ // tricksy - @a reply_to_cont must use an intrusive pointer to @a r if it needs to persist
+ // @a r doesn't go out of scope until after this returns. This continuation shares the mutex
+ // of the target continuation therefore this is always dispatched synchronously.
+ reply_to_cont(action.continuation, r.get());
}
hostdb_cont_free(this);
return;
}
- ts::ConstBuffer hname(hash.host_name, hash.host_len);
- Ptr 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);
+
+ // If looking for an IPv4 or IPv6 address, check the host file.
+ if (hash.db_mark == HOSTDB_MARK_IPV6 || hash.db_mark == HOSTDB_MARK_IPV4) {
+ if (auto static_hosts = hostDB.acquire_host_file(); static_hosts) {
+ if (auto spot = static_hosts->find(hash.host_name); spot != static_hosts->end()) {
+ HostDBRecord::Handle r = (hash.db_mark == HOSTDB_MARK_IPV4) ? spot->second.record_4 : spot->second.record_6;
+ // Set the TTL based on how often we stat() the host file
+ if (r && action.continuation) {
+ r = lookup_done(hash.host_name, hostdb_hostfile_check_interval, nullptr, r);
+ reply_to_cont(action.continuation, r.get());
+ hostdb_cont_free(this);
+ return;
+ }
+ }
}
- hostdb_cont_free(this);
- return;
}
}
+
if (hostdb_lookup_timeout) {
timeout = mutex->thread_holding->schedule_in(this, HRTIME_SECONDS(hostdb_lookup_timeout));
} else {
@@ -1623,7 +1496,7 @@ HostDBContinuation::do_dns()
}
pending_action = dnsProcessor.gethostbyname(this, hash.host_name, opt);
} else if (is_srv()) {
- Debug("dns_srv", "SRV lookup of %s", hash.host_name);
+ Debug("dns_srv", "SRV lookup of %.*s", int(hash.host_name.size()), hash.host_name.data());
pending_action = dnsProcessor.getSRVbyname(this, hash.host_name, opt);
} else {
ip_text_buffer ipb;
@@ -1643,44 +1516,41 @@ HostDBContinuation::do_dns()
int
HostDBContinuation::backgroundEvent(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */)
{
+ std::string dbg;
+
// No nothing if hosts file checking is not enabled.
- if (hostdb_hostfile_check_interval == 0) {
+ if (hostdb_hostfile_check_interval.count() == 0) {
return EVENT_CONT;
}
- hostdb_current_interval = ink_time();
+ hostdb_current_interval = ts_clock::now();
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)];
+ char path[PATH_NAME_MAX];
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-*"));
+ if (0 != strcasecmp(hostdb_hostfile_path.string(), path)) {
+ Debug("hostdb", "%s",
+ ts::bwprint(dbg, R"(Updating hosts file from "{}" to "{}")", hostdb_hostfile_path, ts::bwf::FirstOf(path, "")).c_str());
// 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_hostfile_update_timestamp = TS_TIME_ZERO; // never updated from this file
+ hostdb_hostfile_path = path;
+ update_p = true;
+ } else if (!hostdb_hostfile_path.empty()) {
hostdb_last_interval = hostdb_current_interval;
- if (*hostdb_hostfile_path) {
- if (0 == stat(hostdb_hostfile_path, &info)) {
- if (info.st_mtime > static_cast(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);
+ std::error_code ec;
+ auto stat{ts::file::status(hostdb_hostfile_path, ec)};
+ if (!ec) {
+ if (ts_clock::from_time_t(modification_time(stat)) > hostdb_hostfile_update_timestamp) {
+ update_p = true; // same file but it's changed.
}
+ } else {
+ Debug("hostdb", "%s", ts::bwprint(dbg, R"(Failed to stat host file "{}" - {})", hostdb_hostfile_path, ec).c_str());
}
}
if (update_p) {
- Debug("hostdb", "Updating from host file");
+ Debug("hostdb", "%s", ts::bwprint(dbg, R"(Updating from host file "{}")", hostdb_hostfile_path).c_str());
ParseHostFile(hostdb_hostfile_path, hostdb_hostfile_check_interval);
}
}
@@ -1688,37 +1558,60 @@ HostDBContinuation::backgroundEvent(int /* event ATS_UNUSED */, Event * /* e ATS
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
+HostDBInfo *
+HostDBRecord::select_best_http(ts_time now, ts_seconds fail_window, sockaddr const *hash_addr)
{
- if (hostname_offset == 0) {
- return nullptr;
+ ink_assert(0 < rr_count && rr_count <= hostdb_round_robin_max_count);
+
+ // @a best_any is set to a base candidate, which may be dead.
+ HostDBInfo *best_any = nullptr;
+ // @a best_alive is set when a valid target has been selected and should be used.
+ HostDBInfo *best_alive = nullptr;
+
+ auto info{this->rr_info()};
+
+ if (HostDBProcessor::hostdb_strict_round_robin) {
+ // Always select the next viable target - select failure means no valid targets at all.
+ best_alive = best_any = this->select_next_rr(now, fail_window);
+ Debug("hostdb", "Using strict round robin - index %d", this->index_of(best_alive));
+ } else if (HostDBProcessor::hostdb_timed_round_robin > 0) {
+ auto ctime = rr_ctime.load(); // cache for atomic update.
+ auto ntime = ctime + ts_seconds(HostDBProcessor::hostdb_timed_round_robin);
+ // Check and update RR if it's time - this always yields a valid target if there is one.
+ if (now > ntime && rr_ctime.compare_exchange_strong(ctime, ntime)) {
+ best_alive = best_any = this->select_next_rr(now, fail_window);
+ Debug("hostdb", "Round robin timed interval expired - index %d", this->index_of(best_alive));
+ } else { // pick the current index, which may be dead.
+ best_any = &info[this->rr_idx()];
+ }
+ Debug("hostdb", "Using timed round robin - index %d", this->index_of(best_any));
+ } else {
+ // Walk the entries and find the best (largest) hash.
+ unsigned int best_hash = 0; // any hash is better than this.
+ for (auto &target : info) {
+ unsigned int h = HOSTDB_CLIENT_IP_HASH(hash_addr, target.data.ip);
+ if (best_hash <= h) {
+ best_any = ⌖
+ best_hash = h;
+ }
+ }
+ Debug("hostdb", "Using client affinity - index %d", this->index_of(best_any));
}
- return (char *)this + hostname_offset;
-}
-
-HostDBRoundRobin *
-HostDBInfo::rr()
-{
- if (!round_robin) {
- return nullptr;
+ // If there is a base choice, search for valid target starting there.
+ // Otherwise there is no valid target in the record.
+ if (best_any && !best_alive) {
+ // Starting at the current target, search for a valid one.
+ for (unsigned short i = 0; i < rr_count; i++) {
+ auto target = &info[this->rr_idx(i)];
+ if (target->select(now, fail_window)) {
+ best_alive = target;
+ break;
+ }
+ }
}
- return reinterpret_cast(reinterpret_cast(this) + this->app.rr.offset);
+ return best_alive;
}
struct ShowHostDB;
@@ -1784,41 +1677,42 @@ struct ShowHostDB : public ShowCont {
showAllEvent(int event, Event *e)
{
if (event == EVENT_INTERVAL) {
- HostDBInfo *r = reinterpret_cast(e);
+ auto *r = reinterpret_cast(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("