From 69fbd03d42e0978c9cc6757b046366981413ff6a Mon Sep 17 00:00:00 2001 From: "Alan M. Carroll" Date: Tue, 11 May 2021 13:13:11 -0500 Subject: [PATCH 1/6] Rebase checkpoint for HostDB restructure. --- configs/body_factory/default/Makefile.am | 1 + configs/body_factory/default/connect#all_dead | 17 + .../core-architecture/HostDB-Data-Layout.svg | 3 + .../core-architecture/hostdb.en.rst | 191 ++ .../core-architecture/index.en.rst | 1 + doc/uml/host-resolve.plantuml | 24 + example/plugins/c-api/protocol/TxnSM.c | 12 +- include/ts/ts.h | 6 + include/tscore/BufferWriter.h | 4 +- include/tscore/BufferWriterForward.h | 7 + include/tscore/Diags.h | 9 + include/tscore/bwf_std_format.h | 16 +- include/tscore/ts_file.h | 7 + iocore/dns/P_SplitDNSProcessor.h | 36 +- iocore/dns/SRV.h | 1 - iocore/dns/SplitDNS.cc | 6 +- iocore/hostdb/HostDB.cc | 1570 +++++++++-------- iocore/hostdb/I_HostDBProcessor.h | 929 ++++++---- iocore/hostdb/P_HostDBProcessor.h | 283 +-- plugins/lua/ts_lua_misc.c | 10 +- proxy/ControlMatcher.h | 8 +- proxy/ParentSelection.cc | 30 +- proxy/http/HttpConfig.cc | 4 +- proxy/http/HttpConfig.h | 4 +- proxy/http/HttpConnectionCount.cc | 2 +- proxy/http/HttpSM.cc | 358 ++-- proxy/http/HttpSM.h | 7 +- proxy/http/HttpTransact.cc | 353 ++-- proxy/http/HttpTransact.h | 77 +- .../remap/unit-tests/nexthop_test_stubs.cc | 1 - src/traffic_server/InkAPI.cc | 52 +- src/traffic_server/InkAPITest.cc | 8 +- .../unit_tests/test_BufferWriterFormat.cc | 2 + .../strategies_ch2/strategies_ch2.test.py | 2 + .../tls/tls_verify_override_base.test.py | 4 +- 35 files changed, 2151 insertions(+), 1894 deletions(-) create mode 100644 configs/body_factory/default/connect#all_dead create mode 100644 doc/developer-guide/core-architecture/HostDB-Data-Layout.svg create mode 100644 doc/developer-guide/core-architecture/hostdb.en.rst create mode 100644 doc/uml/host-resolve.plantuml diff --git a/configs/body_factory/default/Makefile.am b/configs/body_factory/default/Makefile.am index a24d2e290dc..69eb6b6810f 100644 --- a/configs/body_factory/default/Makefile.am +++ b/configs/body_factory/default/Makefile.am @@ -28,6 +28,7 @@ dist_bodyfactory_DATA = \ connect\#dns_failed \ connect\#failed_connect \ connect\#hangup \ + connect\#all_dead \ default \ interception\#no_host \ README \ diff --git a/configs/body_factory/default/connect#all_dead b/configs/body_factory/default/connect#all_dead new file mode 100644 index 00000000000..7e18a62986f --- /dev/null +++ b/configs/body_factory/default/connect#all_dead @@ -0,0 +1,17 @@ + + +No Valid Host + + + +

No Valid Host

+
+ + +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 @@ + + +HostDBRecordHostDBType : record_typeunsigned: rr_offset

VLA<HostDBInfo>


HostDBInfo
.

.

.


VLA<HostDBInfo>...

SRV Names


Name
.

.

.

SRV Names...
HostDB Hash TableKeyHostDBRecordRecord 1Record 2Record 3

Optional
Optional

RR Data
RR Data

SRV only
SRV only
rr_offset indicates start of HostDBInfo VLA.
rr_offset indicates start o...
Each SRV record has an
offset to its name
Each SRV record has an...
Viewer does not support full SVG 1.1
\ 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 38f1160ca25..66af0983da1 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 bab84d80ba4..e9e050f60ac 100644 --- a/include/tscore/Diags.h +++ b/include/tscore/Diags.h @@ -188,6 +188,15 @@ is_dbg_ctl_enabled(DbgCtl const &ctl) } \ } while (false) +#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) + // printf-like debug output. First parameter must be tag (C-string literal, or otherwise // a constexpr returning char const pointer to null-terminated C-string). // 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 257f353479a..e9b8f957111 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,74 +662,58 @@ 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())) { HOSTDB_INCREMENT_DYN_STAT(hostdb_total_serve_stale_stat); 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; } // @@ -659,7 +764,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) { @@ -668,10 +773,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) : ""); } @@ -689,12 +794,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); @@ -727,7 +833,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); @@ -744,7 +850,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); @@ -783,7 +889,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; @@ -803,7 +909,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. @@ -838,150 +944,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) { @@ -990,65 +981,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 @@ -1078,28 +1042,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) { @@ -1144,38 +1087,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. @@ -1195,160 +1125,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 != nullptr) && (!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 @@ -1373,7 +1235,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; } @@ -1390,7 +1252,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); @@ -1433,7 +1295,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)); } @@ -1499,7 +1361,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); @@ -1545,7 +1407,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); @@ -1583,31 +1445,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 { @@ -1624,7 +1497,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; @@ -1644,44 +1517,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); } } @@ -1689,37 +1559,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; @@ -1785,41 +1678,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("\n")); - CHECK_SHOW(show("\n", "Total", rr_data->rrcount)); - CHECK_SHOW(show("\n", "Good", rr_data->good)); - CHECK_SHOW(show("\n", "Current", rr_data->current)); - CHECK_SHOW(show("
%s%d
%s%d
%s%d
\n")); - } else { - CHECK_SHOW(show(",\"%s\":\"%d\",", "rr_total", rr_data->rrcount)); - CHECK_SHOW(show("\"%s\":\"%d\",", "rr_good", rr_data->good)); - CHECK_SHOW(show("\"%s\":\"%d\",", "rr_current", rr_data->current)); - CHECK_SHOW(show("\"rr_records\":[")); - } + auto rr_info{r->rr_info()}; + if (rr_info.count()) { + if (!output_json) { + CHECK_SHOW(show("\n")); + CHECK_SHOW(show("\n", "Total", r->rr_count)); + CHECK_SHOW(show("\n", "Current", r->_rr_idx.load())); + CHECK_SHOW(show("\n", "Stale", r->is_ip_stale() ? "Yes" : "No")); + CHECK_SHOW(show("\n", "Timed-Out", r->is_ip_timeout() ? "Yes" : "No")); + CHECK_SHOW(show("
%s%d
%s%d
%s%s
%s%s
\n")); + } else { + CHECK_SHOW(show(",\"%s\":\"%d\",", "rr_total", r->rr_count)); + CHECK_SHOW(show("\"%s\":\"%d\",", "rr_current", r->_rr_idx.load())); + CHECK_SHOW(show("\"rr_records\":[")); + } + CHECK_SHOW(show("%s%d\n", "TTL", r->ip_time_remaining())); - for (int i = 0; i < rr_data->rrcount; i++) { - showOne(&rr_data->info(i), true, event, e, rr_data); - if (output_json) { - CHECK_SHOW(show("}")); // we need to separate records - if (i < (rr_data->rrcount - 1)) - CHECK_SHOW(show(",")); + bool need_separator = false; + for (auto &item : rr_info) { + showOne(&item, r->record_type, event, e); + if (output_json) { + CHECK_SHOW(show("}")); // we need to separate records + if (need_separator) { + CHECK_SHOW(show(",")); } + need_separator = true; } + } - if (!output_json) { - CHECK_SHOW(show("
\n
\n")); - } else { - CHECK_SHOW(show("]")); - } + if (!output_json) { + CHECK_SHOW(show("
\n
\n")); + } else { + CHECK_SHOW(show("]")); } } @@ -1841,67 +1735,47 @@ struct ShowHostDB : public ShowCont { } int - showOne(HostDBInfo *r, bool rr, int event, Event *e, HostDBRoundRobin *hostdb_rr = nullptr) + showOne(HostDBInfo *info, HostDBType record_type, int event, Event *e) { ip_text_buffer b; if (!output_json) { CHECK_SHOW(show("\n")); - CHECK_SHOW(show("\n", "Type", r->round_robin ? "Round-Robin" : "", - r->reverse_dns ? "Reverse DNS" : "", r->is_srv ? "SRV" : "DNS")); + CHECK_SHOW(show("\n", "Type", name_of(record_type))); - if (r->perm_hostname()) { - CHECK_SHOW(show("\n", "Hostname", r->perm_hostname())); - } else if (rr && r->is_srv && hostdb_rr) { - CHECK_SHOW(show("\n", "Hostname", r->srvname(hostdb_rr))); + if (HostDBType::SRV == record_type) { + CHECK_SHOW(show("\n", "Hostname", info->srvname())); } // Let's display the hash. - CHECK_SHOW(show("\n", "App1", r->app.allotment.application1)); - CHECK_SHOW(show("\n", "App2", r->app.allotment.application2)); - CHECK_SHOW(show("\n", "LastFailure", r->app.http_data.last_failure)); - if (!rr) { - CHECK_SHOW(show("\n", "Stale", r->is_ip_stale() ? "Yes" : "No")); - CHECK_SHOW(show("\n", "Timed-Out", r->is_ip_timeout() ? "Yes" : "No")); - CHECK_SHOW(show("\n", "TTL", r->ip_time_remaining())); - } + CHECK_SHOW(show("\n", "LastFailure", info->last_failure.load().time_since_epoch().count())); - if (rr && r->is_srv) { - CHECK_SHOW(show("\n", "Weight", r->data.srv.srv_weight)); - CHECK_SHOW(show("\n", "Priority", r->data.srv.srv_priority)); - CHECK_SHOW(show("\n", "Port", r->data.srv.srv_port)); - CHECK_SHOW(show("\n", "Key", r->data.srv.key)); - } else if (!r->is_srv) { - CHECK_SHOW(show("\n", "IP", ats_ip_ntop(r->ip(), b, sizeof b))); + if (HostDBType::SRV == record_type) { + CHECK_SHOW(show("\n", "Weight", info->data.srv.srv_weight)); + CHECK_SHOW(show("\n", "Priority", info->data.srv.srv_priority)); + CHECK_SHOW(show("\n", "Port", info->data.srv.srv_port)); + CHECK_SHOW(show("\n", "Key", info->data.srv.key)); + } else { + CHECK_SHOW(show("\n", "IP", info->data.ip.toString(b, sizeof b))); } CHECK_SHOW(show("
%s%s%s %s
%s%s
%s%s
%s%s
%s%s
%s%u
%s%u
%s%u
%s%s
%s%s
%s%d
%s%u
%s%d
%s%d
%s%d
%s%x
%s%s
%s%d
%s%d
%s%d
%s%x
%s%s
\n")); } else { CHECK_SHOW(show("{")); - CHECK_SHOW(show("\"%s\":\"%s%s%s\",", "type", (r->round_robin && !r->is_srv) ? "roundrobin" : "", - r->reverse_dns ? "reversedns" : "", r->is_srv ? "srv" : "dns")); + CHECK_SHOW(show("\"%s\":\"%s\",", "type", name_of(record_type))); - if (r->perm_hostname()) { - CHECK_SHOW(show("\"%s\":\"%s\",", "hostname", r->perm_hostname())); - } else if (rr && r->is_srv && hostdb_rr) { - CHECK_SHOW(show("\"%s\":\"%s\",", "hostname", r->srvname(hostdb_rr))); + if (HostDBType::SRV == record_type) { + CHECK_SHOW(show("\"%s\":\"%s\",", "hostname", info->srvname())); } - CHECK_SHOW(show("\"%s\":\"%u\",", "app1", r->app.allotment.application1)); - CHECK_SHOW(show("\"%s\":\"%u\",", "app2", r->app.allotment.application2)); - CHECK_SHOW(show("\"%s\":\"%u\",", "lastfailure", r->app.http_data.last_failure)); - if (!rr) { - CHECK_SHOW(show("\"%s\":\"%s\",", "stale", r->is_ip_stale() ? "yes" : "no")); - CHECK_SHOW(show("\"%s\":\"%s\",", "timedout", r->is_ip_timeout() ? "yes" : "no")); - CHECK_SHOW(show("\"%s\":\"%d\",", "ttl", r->ip_time_remaining())); - } + CHECK_SHOW(show("\"%s\":\"%u\",", "lastfailure", info->last_failure.load().time_since_epoch().count())); - if (rr && r->is_srv) { - CHECK_SHOW(show("\"%s\":\"%d\",", "weight", r->data.srv.srv_weight)); - CHECK_SHOW(show("\"%s\":\"%d\",", "priority", r->data.srv.srv_priority)); - CHECK_SHOW(show("\"%s\":\"%d\",", "port", r->data.srv.srv_port)); - CHECK_SHOW(show("\"%s\":\"%x\",", "key", r->data.srv.key)); - } else if (!r->is_srv) { - CHECK_SHOW(show("\"%s\":\"%s\"", "ip", ats_ip_ntop(r->ip(), b, sizeof b))); + if (HostDBType::SRV == record_type) { + CHECK_SHOW(show("\"%s\":\"%d\",", "weight", info->data.srv.srv_weight)); + CHECK_SHOW(show("\"%s\":\"%d\",", "priority", info->data.srv.srv_priority)); + CHECK_SHOW(show("\"%s\":\"%d\",", "port", info->data.srv.srv_port)); + CHECK_SHOW(show("\"%s\":\"%x\",", "key", info->data.srv.key)); + } else { + CHECK_SHOW(show("\"%s\":\"%s\"", "ip", info->data.ip.toString(b, sizeof b))); } } return EVENT_CONT; @@ -1910,7 +1784,7 @@ struct ShowHostDB : public ShowCont { int showLookupDone(int event, Event *e) { - HostDBInfo *r = reinterpret_cast(e); + auto *r = reinterpret_cast(e); CHECK_SHOW(begin("HostDB Lookup")); if (name) { @@ -1919,19 +1793,15 @@ struct ShowHostDB : public ShowCont { CHECK_SHOW(show("

%u.%u.%u.%u

\n", PRINT_IP(ip))); } if (r) { - showOne(r, false, event, e); - if (r->round_robin) { - HostDBRoundRobin *rr_data = r->rr(); - if (rr_data) { - CHECK_SHOW(show("\n")); - CHECK_SHOW(show("\n", "Total", rr_data->rrcount)); - CHECK_SHOW(show("\n", "Good", rr_data->good)); - CHECK_SHOW(show("\n", "Current", rr_data->current)); - CHECK_SHOW(show("
%s%d
%s%d
%s%d
\n")); - - for (int i = 0; i < rr_data->rrcount; i++) { - showOne(&rr_data->info(i), true, event, e, rr_data); - } + auto rr_data = r->rr_info(); + if (rr_data.count()) { + CHECK_SHOW(show("\n")); + CHECK_SHOW(show("\n", "Total", r->rr_count)); + CHECK_SHOW(show("\n", "Current", r->_rr_idx.load())); + CHECK_SHOW(show("
%s%d
%s%d
\n")); + + for (auto &item : rr_data) { + showOne(&item, r->record_type, event, e); } } } else { @@ -2027,9 +1897,9 @@ struct HostDBTestReverse : public Continuation { mainEvent(int event, Event *e) { if (event == EVENT_HOST_DB_LOOKUP) { - HostDBInfo *i = reinterpret_cast(e); + auto *i = reinterpret_cast(e); if (i) { - rprintf(test, "HostDBTestReverse: reversed %s\n", i->hostname()); + rprintf(test, "HostDBTestReverse: reversed %s\n", i->name()); } outstanding--; } @@ -2149,93 +2019,110 @@ HostDBFileContinuation::destroy() // We can't allow more than one update to be // proceeding at a time in any case so we might as well make these // globals. -int HostDBFileUpdateActive = 0; +std::atomic HostDBFileUpdateActive{false}; -static void -ParseHostLine(Ptr &map, char *l) +/* Container for temporarily holding data from the host file. For each FQDN there is a vector of IPv4 + * and IPv6 addresses. These are used to generate the HostDBRecord instances that are stored persistently. + */ +using HostAddrMap = std::unordered_map, std::vector>>; + +namespace { - Tokenizer elts(" \t"); - int n_elts = elts.Initialize(l, SHARE_TOKS); +constexpr unsigned IPV4_IDX = 0; +constexpr unsigned IPV6_IDX = 1; +} // namespace +static void +ParseHostLine(TextView line, HostAddrMap &map) +{ // Elements should be the address then a list of host names. + TextView addr_text = line.take_prefix_if(&isspace); + IpAddr addr; + // Don't use RecHttpLoadIp because the address *must* be literal. - IpAddr ip; - if (n_elts > 1 && 0 == ip.load(elts[0])) { - for (int i = 1; i < n_elts; ++i) { - ts::ConstBuffer name(elts[i], strlen(elts[i])); - // If we don't have an entry already (host files only support single IPs for a given name) - if (map->hosts_file_map.find(name) == map->hosts_file_map.end()) { - map->hosts_file_map[name] = ip; - } + if (TS_SUCCESS != addr.load(addr_text)) { + return; + } + + while (!line.ltrim_if(&isspace).empty()) { + TextView name = line.take_prefix_if(&isspace); + if (addr.isIp6()) { + std::get(map[name]).push_back(addr); + } else if (addr.isIp4()) { + std::get(map[name]).push_back(addr); } } } void -ParseHostFile(const char *path, unsigned int hostdb_hostfile_check_interval_parse) +ParseHostFile(ts::file::path const &path, ts_seconds hostdb_hostfile_check_interval_parse) { - Ptr parsed_hosts_file_ptr; + std::shared_ptr map; // Test and set for update in progress. - if (0 != ink_atomic_swap(&HostDBFileUpdateActive, 1)) { + bool flag = false; + if (!HostDBFileUpdateActive.compare_exchange_strong(flag, true)) { Debug("hostdb", "Skipped load of host file because update already in progress"); return; } - Debug("hostdb", "Loading host file '%s'", path); - - if (*path) { - ats_scoped_fd fd(open(path, O_RDONLY)); - if (fd >= 0) { - struct stat info; - if (0 == fstat(fd, &info)) { - // +1 in case no terminating newline - int64_t size = info.st_size + 1; - - parsed_hosts_file_ptr = new RefCountedHostsFileMap; - parsed_hosts_file_ptr->HostFileText = static_cast(ats_malloc(size)); - if (parsed_hosts_file_ptr->HostFileText) { - char *base = parsed_hosts_file_ptr->HostFileText; - char *limit; - - size = read(fd, parsed_hosts_file_ptr->HostFileText, info.st_size); - limit = parsed_hosts_file_ptr->HostFileText + size; - *limit = 0; - - // We need to get a list of all name/addr pairs so that we can - // group names for round robin records. Also note that the - // pairs have pointer back in to the text storage for the file - // so we need to keep that until we're done with @a pairs. - while (base < limit) { - char *spot = strchr(base, '\n'); - - // terminate the line. - if (nullptr == spot) { - spot = limit; // no trailing EOL, grab remaining - } else { - *spot = 0; - } - - while (base < spot && isspace(*base)) { - ++base; // skip leading ws - } - if (*base != '#' && base < spot) { // non-empty non-comment line - ParseHostLine(parsed_hosts_file_ptr, base); - } - base = spot + 1; - } - - hostdb_hostfile_update_timestamp = hostdb_current_interval; + Debug_bw("hostdb", R"(Loading host file "{}")", path); + + if (!path.empty()) { + std::error_code ec; + std::string content = ts::file::load(path, ec); + if (!ec) { + HostAddrMap addr_map; + TextView text{content}; + while (text) { + auto line = text.take_prefix_at('\n').ltrim_if(&isspace); + if (line.empty() || '#' == *line) { + continue; } + ParseHostLine(line, addr_map); } + // @a map should be loaded with all of the data, create the records. + map = std::make_shared(); + // Common loading function for creating a record from the address vector. + auto loader = [](TextView key, std::vector const &v) -> HostDBRecord::Handle { + HostDBRecord::Handle record{HostDBRecord::alloc(key, v.size())}; + record->af_family = v.front().family(); // @a v is presumed family homogenous + auto rr_info = record->rr_info(); + auto spot = v.begin(); + for (auto &item : rr_info) { + item.assign(*spot++); + } + return record; + }; + // Walk the temporary map and create the corresponding records for the persistent map. + for (auto const &[key, value] : addr_map) { + // Bit of subtlety to be able to search records with a view and not a string - the key + // must point at stable memory for the name, which is available in the record itself. + // Therefore the lookup for adding the record must be done using a view based in the record. + // It doesn't matter if it's the IPv4 or IPv6 record that's used, both are stable and equal + // to each other. + // IPv4 + if (auto const &v = std::get(value); v.size() > 0) { + auto r = loader(key, v); + (*map)[r->name_view()].record_4 = r; + } + // IPv6 + if (auto const &v = std::get(value); v.size() > 0) { + auto r = loader(key, v); + (*map)[r->name_view()].record_6 = r; + } + } + + hostdb_hostfile_update_timestamp = hostdb_current_interval; } } // Swap the pointer - if (parsed_hosts_file_ptr != nullptr) { - hostDB.hosts_file_ptr = parsed_hosts_file_ptr; + if (map) { + std::unique_lock lock(hostDB.host_file_mutex); + hostDB.host_file = map; } // Mark this one as completed, so we can allow another update to happen - HostDBFileUpdateActive = 0; + HostDBFileUpdateActive = false; } // @@ -2258,7 +2145,7 @@ struct HostDBRegressionContinuation : public Continuation { int i; int - mainEvent(int event, HostDBInfo *r) + mainEvent(int event, HostDBRecord *r) { (void)event; @@ -2267,27 +2154,15 @@ struct HostDBRegressionContinuation : public Continuation { } if (event == EVENT_HOST_DB_LOOKUP) { --outstanding; - // since this is a lookup done, data is either hostdbInfo or nullptr if (r) { - rprintf(test, "hostdbinfo r=%x\n", r); - char const *hname = r->perm_hostname(); - if (nullptr == hname) { - hname = "(null)"; - } - rprintf(test, "hostdbinfo hostname=%s\n", hname); - rprintf(test, "hostdbinfo rr %x\n", r->rr()); + rprintf(test, "HostDBRecord r=%x\n", r); + rprintf(test, "HostDBRecord hostname=%s\n", r->name()); // If RR, print all of the enclosed records - if (r->rr()) { - rprintf(test, "hostdbinfo good=%d\n", r->rr()->good); - for (int x = 0; x < r->rr()->good; x++) { - ip_port_text_buffer ip_buf; - ats_ip_ntop(r->rr()->info(x).ip(), ip_buf, sizeof(ip_buf)); - rprintf(test, "hostdbinfo RR%d ip=%s\n", x, ip_buf); - } - } else { // Otherwise, just the one will do + auto rr_info{r->rr_info()}; + for (int x = 0; x < r->rr_count; ++x) { ip_port_text_buffer ip_buf; - ats_ip_ntop(r->ip(), ip_buf, sizeof(ip_buf)); - rprintf(test, "hostdbinfo A ip=%s\n", ip_buf); + rr_info[x].data.ip.toString(ip_buf, sizeof(ip_buf)); + rprintf(test, "hostdbinfo RR%d ip=%s\n", x, ip_buf); } ++success; } else { @@ -2327,9 +2202,9 @@ struct HostDBRegressionContinuation : public Continuation { static const char *dns_test_hosts[] = { "www.apple.com", "www.ibm.com", "www.microsoft.com", - "www.coke.com", // RR record - "4.2.2.2", // An IP-- since we don't expect resolution - "127.0.0.1", // loopback since it has some special handling + "yahoo.com", // RR record + "4.2.2.2", // An IP-- since we don't expect resolution + "127.0.0.1", // loopback since it has some special handling }; REGRESSION_TEST(HostDBProcessor)(RegressionTest *t, int atype, int *pstatus) @@ -2338,3 +2213,214 @@ REGRESSION_TEST(HostDBProcessor)(RegressionTest *t, int atype, int *pstatus) } #endif +// ----- +void +HostDBRecord::free() +{ + if (_iobuffer_index > 0) { + Debug("hostdb", "freeing %d bytes at [%p]", (1 << (7 + _iobuffer_index)), this); + ioBufAllocator[_iobuffer_index].free_void(static_cast(this)); + } +} + +HostDBRecord * +HostDBRecord::alloc(TextView query_name, unsigned int rr_count, size_t srv_name_size) +{ + const ts::Scalar<8> qn_size = ts::round_up(query_name.size() + 1); + const ts::Scalar<8> r_size = ts::round_up(sizeof(self_type) + qn_size + rr_count * sizeof(HostDBInfo) + srv_name_size); + int iobuffer_index = iobuffer_size_to_index(r_size, hostdb_max_iobuf_index); + ink_release_assert(iobuffer_index >= 0); + auto ptr = ioBufAllocator[iobuffer_index].alloc_void(); + memset(ptr, 0, r_size); + auto self = static_cast(ptr); + new (self) self_type(); + self->_iobuffer_index = iobuffer_index; + self->_record_size = r_size; + + Debug("hostdb", "allocating %ld bytes for %.*s with %d RR records at [%p]", r_size.value(), int(query_name.size()), + query_name.data(), rr_count, self); + + // where in our block of memory we are + int offset = sizeof(self_type); + memcpy(self->apply_offset(offset), query_name); + offset += qn_size; + self->rr_offset = offset; + self->rr_count = rr_count; + // Construct the info instances to a valid state. + for (auto &info : self->rr_info()) { + new (&info) std::remove_reference_t; + } + + return self; +} + +HostDBRecord::self_type * +HostDBRecord::unmarshall(char *buff, unsigned size) +{ + if (size < sizeof(self_type)) { + return nullptr; + } + auto src = reinterpret_cast(buff); + ink_release_assert(size == src->_record_size); + auto ptr = ioBufAllocator[src->_iobuffer_index].alloc_void(); + auto self = static_cast(ptr); + new (self) self_type(); + auto delta = sizeof(RefCountObj); // skip the VFTP and ref count. + memcpy(static_cast(ptr) + delta, buff + delta, size - delta); + return self; +} + +bool +HostDBRecord::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 + ts_seconds(hostdb_serve_stale_but_revalidate)) > ip_interval()) { + Debug_bw("hostdb", "serving stale entry {} | {} | {} as requested by config", ip_timeout_interval, + hostdb_serve_stale_but_revalidate, ip_interval()); + return true; + } + + // otherwise, the entry is too old + return false; +} + +HostDBInfo * +HostDBRecord::select_best_srv(char *target, InkRand *rand, ts_time now, ts_seconds fail_window) +{ + ink_assert(rr_count <= 0 || static_cast(rr_count) > hostdb_round_robin_max_count); + + int i = 0; + int live_n = 0; + uint32_t weight = 0, p = INT32_MAX; + HostDBInfo *result = nullptr; + auto rr = this->rr_info(); + // Array of live targets, sized by @a live_n + HostDBInfo *live[rr.count()]; + for (auto &target : rr) { + // skip dead upstreams. + if (rr[i].is_dead(now, fail_window)) { + continue; + } + + if (target.data.srv.srv_priority <= p) { + p = target.data.srv.srv_priority; + weight += target.data.srv.srv_weight; + live[live_n++] = ⌖ + } else { + break; + } + }; + + if (live_n == 0 || weight == 0) { // no valid or weighted choice, use strict RR + result = this->select_next_rr(now, fail_window); + } else { + uint32_t xx = rand->random() % weight; + for (i = 0; i < live_n - 1 && xx >= live[i]->data.srv.srv_weight; ++i) + xx -= live[i]->data.srv.srv_weight; + + result = live[i]; + } + + if (result) { + ink_strlcpy(target, this->name(), MAXDNAME); + return result; + } + return nullptr; +} + +HostDBInfo * +HostDBRecord::select_next_rr(ts_time now, ts_seconds fail_window) +{ + auto rr_info = this->rr_info(); + for (unsigned idx = 0, limit = rr_info.count(); idx < limit; ++idx) { + auto &target = rr_info[this->next_rr()]; + if (target.select(now, fail_window)) { + return ⌖ + } + } + + return nullptr; +} + +unsigned +HostDBRecord::next_rr() +{ + auto raw_idx = ++_rr_idx; + // Modulus on an atomic is a bit tricky - need to make sure the value is always decremented by the + // modulus even if another thread incremented. Update to modulus value iff the value hasn't been + // incremented elsewhere. Eventually the "last" incrementer will do the update. + auto idx = raw_idx % rr_count; + _rr_idx.compare_exchange_weak(raw_idx, idx); + return idx; +} + +HostDBInfo * +HostDBRecord::find(sockaddr const *addr) +{ + for (auto &item : this->rr_info()) { + if (item.data.ip == addr) { + return &item; + } + } + return nullptr; +} + +bool +ResolveInfo::resolve_immediate() +{ + if (resolved_p) { + // nothing - already resolved. + } else if (IpAddr tmp; TS_SUCCESS == tmp.load(lookup_name)) { + ts::bwprint(ts::bw_dbg, "[resolve_immediate] success - FQDN '{}' is a valid IP address.", lookup_name); + Debug("hostdb", "%s", ts::bw_dbg.c_str()); + addr.assign(tmp); + resolved_p = true; + } + return resolved_p; +} + +bool +ResolveInfo::set_active(HostDBInfo *info) +{ + active = info; + if (info) { + addr.assign(active->data.ip); + resolved_p = true; + return true; + } + resolved_p = false; + return false; +} + +bool +ResolveInfo::select_next_rr() +{ + if (active) { + if (auto rr_info{this->record->rr_info()}; rr_info.count() > 1) { + unsigned limit = active - rr_info.data(), idx = (limit + 1) % rr_info.count(); + while ((idx = (idx + 1) % rr_info.count()) != limit && !rr_info[idx].is_alive()) + ; + active = &rr_info[idx]; + return idx != limit; // if the active record was actually changed. + } + } + return false; +} + +bool +ResolveInfo::set_upstream_address(IpAddr const &ip_addr) +{ + if (ip_addr.isValid()) { + addr.assign(ip_addr); + resolved_p = true; + return true; + } + return false; +} diff --git a/iocore/hostdb/I_HostDBProcessor.h b/iocore/hostdb/I_HostDBProcessor.h index 0be0a81b4a1..8b2669396c4 100644 --- a/iocore/hostdb/I_HostDBProcessor.h +++ b/iocore/hostdb/I_HostDBProcessor.h @@ -23,10 +23,14 @@ #pragma once +#include +#include + #include "tscore/HashFNV.h" #include "tscore/ink_time.h" #include "tscore/CryptoHash.h" #include "tscore/ink_align.h" +#include "tscore/ink_inet.h" #include "tscore/ink_resolver.h" #include "tscore/HTTPVersion.h" #include "I_EventSystem.h" @@ -46,6 +50,7 @@ // Data // struct HostDBContinuation; +struct ResolveInfo; // // The host database stores host information, most notably the @@ -56,7 +61,7 @@ struct HostDBContinuation; // disk representation to decrease # of seeks. // extern int hostdb_enable; -extern ink_time_t hostdb_current_interval; +extern ts_time 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; @@ -84,339 +89,415 @@ makeHostHash(const char *string) // Types // -/** Host information metadata used by various parts of HostDB. - * It is stored as generic data in the cache. - * - * As a @c union only one of the members is valid, Which one depends on context data in the - * @c HostDBInfo. This data is written literally to disk therefore if any change is made, - * the @c object_version for the cache must be updated by modifying @c HostDBInfo::version. - * - * @see HostDBInfo::version - */ -union HostDBApplicationInfo { - /// Generic storage. This is verified to be the size of the union. - struct application_data_allotment { - unsigned int application1; - unsigned int application2; - } allotment; - - ////////////////////////////////////////////////////////// - // http server attributes in the host database // - // // - // http_version - one of HTTPVersion // - // 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 { - uint32_t last_failure; - HTTPVersion http_version; - uint8_t fail_count; - http_server_attr() : http_version() {} - } http_data; - - struct application_data_rr { - unsigned int offset; - } rr; - HostDBApplicationInfo() : http_data() {} -}; - -struct HostDBRoundRobin; +class HostDBRecord; +/// Information for an SRV record. struct SRVInfo { - unsigned int srv_offset : 16; + unsigned int srv_offset : 16; ///< Memory offset from @c HostDBInfo to name. 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. - */ +/// Type of data stored. +enum class HostDBType : uint8_t { + UNSPEC, ///< No valid data. + ADDR, ///< IP address. + SRV, ///< SRV record. + HOST ///< Hostname (reverse DNS) +}; +char const *name_of(HostDBType t); - 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; - } +/** Information about a single target. + */ +struct HostDBInfo { + using self_type = HostDBInfo; ///< Self reference type. - 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)); - } + /// Default constructor. + HostDBInfo() = default; - /// Effectively the @c object_version for cache data. - /// This is used to indicate incompatible changes in the binary layout of HostDB records. - /// It must be updated if any such change is made, even if it is functionally equivalent. - static ts::VersionNumber - version() - { - /// - 1.0 Initial version. - /// - 1.1 tweak HostDBApplicationInfo::http_data. - return ts::VersionNumber(1, 1); - } + HostDBInfo &operator=(HostDBInfo const &that); - 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; - } + /// Absolute time of when this target failed. + /// A value of zero (@c TS_TIME_ZERO ) indicates no failure. + ts_time last_fail_time() const; - // return expiry time (in seconds since epoch) - ink_time_t - expiry_time() const - { - return ip_timestamp + ip_timeout_interval + hostdb_serve_stale_but_revalidate; - } + /// Target is alive - no known failure. + bool is_alive(); - sockaddr * - ip() - { - return &data.ip.sa; - } + /// Target has failed and is still in the blocked time window. + bool is_dead(ts_time now, ts_seconds fail_window); - sockaddr const * - ip() const - { - return &data.ip.sa; - } + /** Select this target. + * + * @param now Current time. + * @param fail_window Failure window. + * @return Status of the selection. + * + * If a zombie is selected the failure time is updated to make it look dead to other threads in a thread safe + * manner. The caller should check @c last_fail_time to see if a zombie was selected. + */ + bool select(ts_time now, ts_seconds fail_window); - char *hostname() const; - char *perm_hostname() const; - char *srvname(HostDBRoundRobin *rr) const; + /// Check if this info is valid. + bool is_valid() 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; - } + /// Mark this info as invalid. + void invalidate(); - HostDBRoundRobin *rr(); + /** Mark the entry as down. + * + * @param now Time of the failure. + * @return @c true if @a this was marked down, @c false if not. + * + * This can return @c false if the entry is already marked down, in which case the failure time is not updated. + */ + bool mark_down(ts_time now); - unsigned int - ip_interval() const - { - return (hostdb_current_interval - ip_timestamp) & 0x7FFFFFFF; - } + /** Mark the target as up / alive. + * + * @return Previous alive state of the target. + */ + bool mark_up(); - int - ip_time_remaining() const - { - return static_cast(ip_timeout_interval) - static_cast(this->ip_interval()); - } + char const *srvname() const; - bool - is_ip_stale() const - { - return ip_timeout_interval >= 2 * hostdb_ip_stale_interval && ip_interval() >= hostdb_ip_stale_interval; - } + /** Migrate data after a DNS update. + * + * @param that Source item. + * + * This moves only specific state information, it is not a generic copy. + */ + void migrate_from(self_type const &that); - bool - is_ip_timeout() const - { - return ip_interval() >= ip_timeout_interval; - } + /// A target is either an IP address or an SRV record. + /// The type should be indicated by @c flags.f.is_srv; + union { + IpAddr ip; ///< IP address / port data. + SRVInfo srv; ///< SRV record. + } data{IpAddr{}}; + + /// Data that migrates after updated DNS records are processed. + /// @see migrate_from + /// @{ + /// Last time a failure was recorded. + std::atomic last_failure{TS_TIME_ZERO}; + /// Count of connection failures + std::atomic fail_count{0}; + /// Expected HTTP version of the target based on earlier transactions. + HTTPVersion http_version = HTTP_INVALID; + /// @} + + self_type &assign(IpAddr const &addr); + +protected: + self_type &assign(sa_family_t af, void const *addr); + self_type &assign(SRV const *srv, char const *name); + + HostDBType type = HostDBType::UNSPEC; ///< Invalid data. + + friend HostDBContinuation; +}; - bool - is_ip_fail_timeout() const - { - return ip_interval() >= hostdb_ip_fail_timeout_interval; +inline HostDBInfo & +HostDBInfo::operator=(HostDBInfo const &that) +{ + if (this != &that) { + memcpy(static_cast(this), static_cast(&that), sizeof(*this)); } + return *this; +} - void - refresh_ip() - { - ip_timestamp = hostdb_current_interval; - } +inline ts_time +HostDBInfo::last_fail_time() const +{ + return last_failure; +} - bool - serve_stale_but_revalidate() const - { - // the option is disabled - if (hostdb_serve_stale_but_revalidate <= 0) { - return false; - } +inline bool +HostDBInfo::is_alive() +{ + return this->last_fail_time() == TS_TIME_ZERO; +} - // 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; - } +inline bool +HostDBInfo::is_dead(ts_time now, ts_seconds fail_window) +{ + auto last_fail = this->last_fail_time(); + return (last_fail != TS_TIME_ZERO) && (last_fail + fail_window < now); +} + +inline bool +HostDBInfo::mark_up() +{ + auto t = last_failure.exchange(TS_TIME_ZERO); + return t != TS_TIME_ZERO; +} + +inline bool +HostDBInfo::mark_down(ts_time now) +{ + auto t0{TS_TIME_ZERO}; + return last_failure.compare_exchange_strong(t0, now); +} - // otherwise, the entry is too old - return false; +inline bool +HostDBInfo::select(ts_time now, ts_seconds fail_window) +{ + auto t0 = this->last_fail_time(); + if (t0 == TS_TIME_ZERO) { + return true; // it's alive and so is valid for selection. } + // Success means this is a zombie and this thread updated the failure time. + return (t0 + fail_window < now) && last_failure.compare_exchange_strong(t0, now); +} + +inline void +HostDBInfo::migrate_from(HostDBInfo::self_type const &that) +{ + this->last_failure = that.last_failure.load(); + this->http_version = that.http_version; +} - /* - * Given the current time `now` and the fail_window, determine if this real is alive +inline bool +HostDBInfo::is_valid() const +{ + return type != HostDBType::UNSPEC; +} + +inline void +HostDBInfo::invalidate() +{ + type = HostDBType::UNSPEC; +} + +// ---- +/** Root item for HostDB. + * This is the container for HostDB data. It is always an array of @c HostDBInfo instances plus metadata. + * All strings are C-strings and therefore don't need a distinct size. + * + */ +class HostDBRecord : public RefCountObj +{ + friend struct HostDBContinuation; + friend struct ShowHostDB; + using self_type = HostDBRecord; + + /// Size of the IO buffer block owned by @a this. + /// If negative @a this is in not allocated memory. + int _iobuffer_index{-1}; + /// Actual size of the data. + unsigned _record_size = sizeof(self_type); + +public: + HostDBRecord() = default; + HostDBRecord(self_type const &that) = delete; + + using Handle = Ptr; ///< Shared pointer type to hold an instance. + + /** Allocate an instance from the IOBuffers. + * + * @param query_name Name of the query for the record. + * @param rr_count Number of info instances. + * @param srv_name_size Storage for SRV names, if any. + * @return An instance sufficient to hold the specified data. + * + * The query name will stored and initialized, and the info instances initialized. */ - 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; - } - } + static self_type *alloc(ts::TextView query_name, unsigned rr_count, size_t srv_name_size = 0); - bool - is_failed() const - { - return !((is_srv && data.srv.srv_offset) || (reverse_dns && data.hostname_offset) || ats_is_ip(ip())); - } + /// Type of data stored in this record. + HostDBType record_type = HostDBType::UNSPEC; - void - set_failed() - { - if (is_srv) { - data.srv.srv_offset = 0; - } else if (reverse_dns) { - data.hostname_offset = 0; - } else { - ats_ip_invalidate(ip()); - } - } + /// IP family of this record. + sa_family_t af_family = AF_UNSPEC; + + /// Offset from @a this to the VLA. + unsigned short rr_offset = 0; + + /// Number of @c HostDBInfo instances. + unsigned short rr_count = 0; + + /// Timing data for switch records in the RR. + std::atomic rr_ctime{TS_TIME_ZERO}; + /// Hash key. uint64_t key{0}; - // 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; + /// When the data was received. + ts_time ip_timestamp; - union { - IpEndpoint ip; ///< IP address / port data. - unsigned int hostname_offset; ///< Some hostname thing. - SRVInfo srv; - } data; + /// Valid duration of the data. + ts_seconds ip_timeout_interval; - unsigned int hostname_offset{0}; // always maintain a permanent copy of the hostname for non-rev dns records. + /** Atomically advance the round robin index. + * + * If multiple threads call this simultaneously each thread will get a distinct return value. + * + * @return The new round robin index. + */ + unsigned next_rr(); + + /** Pick the next round robin and update the record atomically. + * + * @note This may select a zombie server and reserve it for the caller, therefore the caller must + * attempt to connect to the selected target if possible. + * + * @param now Current time to use for aliveness calculations. + * @param fail_window Blackout time for dead servers. + * @return Status of the updated target. + * + * If the return value is @c HostDBInfo::Status::DEAD this means all targets are dead and there is + * no valid upstream. + * + * @note Concurrency - this is not done under lock and depends on the caller for correct use. + * For strict round robin, it is a feature that every call will get a distinct index. For + * timed round robin, the caller must arrange to have only one thread call this per time interval. + */ + HostDBInfo *select_next_rr(ts_time now, ts_seconds fail_window); - unsigned int ip_timestamp{0}; + /// Check if this record is of SRV targets. + bool is_srv() const; - unsigned int ip_timeout_interval{0}; // bounded between 1 and HOST_DB_MAX_TTL (0x1FFFFF, 24 days) + /** Query name for the record. + * @return A C-string. + * If this is a @c HOST record, this is the resolved named and the query was based on the IP address. + * Otherwise this is the name used in the DNS query. + */ + char const *name() const; + + /** Query name for the record. + * @return A view. + * If this is a @c HOST record, this is the resolved named and the query was based on the IP address. + * Otherwise this is the name used in the DNS query. + * @note Although not included in the view, the name is always nul terminated and the string can + * be used as a C-string. + */ + ts::TextView name_view() const; - unsigned int is_srv : 1; - unsigned int reverse_dns : 1; + /// Get the array of info instances. + ts::MemSpan rr_info(); - 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 + /** Find a host record by IP address. + * + * @param addr Address key. + * @return A pointer to the info instance if a match is found, @c nullptr if not. + */ + HostDBInfo *find(sockaddr const *addr); + + /** Select an upstream target. + * + * @param now Current time. + * @param fail_window Dead server blackout time. + * @param hash_addr Inbound remote IP address. + * @return A selected target, or @c nullptr if there are no valid targets. + * + * This accounts for the round robin setting. The default is to use "client affinity" in + * which case @a hash_addr is as a hash seed to select the target. + * + * This may select a zombie target, which can be detected by checking the target's last + * failure time. If it is not @c TS_TIME_ZERO the target is a zombie. Other transactions will + * be blocked from selecting that target until @a fail_window time has passed. + * + * In cases other than strict round robin, a base target is selected. If valid, that is returned, + * but if not then the targets in this record are searched until a valid one is found. The result + * is this can be called to select a target for failover when a previous target fails. + */ + HostDBInfo *select_best_http(ts_time now, ts_seconds fail_window, sockaddr const *hash_addr); + HostDBInfo *select_best_srv(char *target, InkRand *rand, ts_time now, ts_seconds fail_window); - HostDBInfo() : _iobuffer_index{-1} {} + bool is_failed() const; - HostDBInfo(HostDBInfo const &src) : RefCountObj() - { - memcpy(static_cast(this), static_cast(&src), sizeof(*this)); - _iobuffer_index = -1; - } + void set_failed(); - HostDBInfo & - operator=(HostDBInfo const &src) - { - if (this != &src) { - int iob_idx = _iobuffer_index; - memcpy(static_cast(this), static_cast(&src), sizeof(*this)); - _iobuffer_index = iob_idx; - } - return *this; - } + /// @return The time point when the item expires. + ts_time expiry_time() const; - bool - from_alloc() const - { - return _iobuffer_index >= 0; - } + ts_seconds ip_interval() const; -private: - // The value of this will be -1 for objects that are not created by the alloc() static member function. - int _iobuffer_index; -}; + ts_seconds ip_time_remaining() const; + + bool is_ip_stale() const; + + bool is_ip_timeout() const; + + bool is_ip_fail_timeout() const; + + void refresh_ip(); + + bool serve_stale_but_revalidate() const; + + /// Deallocate @a this. + void free() override; -struct HostDBRoundRobin { - /** Total number (to compute space used). */ - short rrcount = 0; + /** The current round robin index. + * + * @return The current index. + * + * @note The internal index may be out of range due to concurrency constraints - this insures the + * returned valu is in range. + */ + unsigned short rr_idx() const; - /** Number which have not failed a connect. */ - short good = 0; + /** Offset from the current round robin index. + * + * @param delta Distance from the current index. + * @return The effective index. + */ + unsigned short rr_idx(unsigned short delta) const; - unsigned short current = 0; - ink_time_t timed_rr_ctime = 0; + /// The index of @a target in this record. + int index_of(HostDBInfo const *target) const; - // 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) + /** Allocation and initialize an instance from a serialized buffer. + * + * @param buff Serialization data. + * @param size Size of @a buff. + * @return An instance initialized from @a buff. + */ + static self_type *unmarshall(char *buff, unsigned size); + + /// Database version. + static constexpr ts::VersionNumber Version{3, 0}; + +protected: + /// Current active info. + /// @note This value may be out of range due to the difficulty of synchronization, therefore + /// must always be taken modulus @c rr_count when used. Use the @c rr_idx() method unless + /// raw access is required. + std::atomic _rr_idx = 0; + + /** Access an internal object at @a offset. + * + * @tparam T Type of object. + * @param offset Offset of object. + * @return A pointer to the object of type @a T. + * + * @a offset is applied to @a this record and the result cast to a pointer to @a T. + * + * @note @a offset based at @a this. + */ + template + T * + apply_offset(unsigned offset) { - ink_assert(n < rrcount && n >= 0); - return *((HostDBInfo *)((char *)this + sizeof(HostDBRoundRobin)) + n); + return reinterpret_cast(reinterpret_cast(this) + offset); } - // Return the allocation size of a HostDBRoundRobin struct suitable for storing - // "count" HostDBInfo records. - static unsigned - size(unsigned count, unsigned srv_len = 0) + template + T const * + apply_offset(unsigned offset) const { - ink_assert(count > 0); - return INK_ALIGN((sizeof(HostDBRoundRobin) + (count * sizeof(HostDBInfo)) + srv_len), 8); + return reinterpret_cast(reinterpret_cast(this) + offset); } - /** 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() {} + union { + uint16_t all; + struct { + unsigned failed_p : 1; ///< DNS error. + } f; + } flags{0}; }; struct HostDBCache; @@ -424,10 +505,123 @@ struct HostDBHash; // Prototype for inline completion function or // getbyname_imm() -typedef void (Continuation::*cb_process_result_pfn)(HostDBInfo *r); +using cb_process_result_pfn = void (Continuation::*)(HostDBRecord *r); Action *iterate(Continuation *cont); +/** Information for doing host resolution for a request. + * + * This is effectively a state object for a request attempting to connect upstream. Information about its attempt + * that are local to the request are kept here, while shared data is accessed via the @c HostDBInfo pointers. + * + * A primitive version of the IP address generator concept. + */ +struct ResolveInfo { + using self_type = ResolveInfo; ///< Self reference type. + + /// Not quite sure what this is for. + enum UpstreamResolveStyle { UNDEFINED_LOOKUP, ORIGIN_SERVER, PARENT_PROXY, HOST_NONE }; + + /** Origin server address source selection. + + If config says to use CTA (client target addr) state is TRY_CLIENT, otherwise it + remains the default. If the connect fails then we switch to a USE. We go to USE_HOSTDB if (1) + the HostDB lookup is successful and (2) some address other than the CTA is available to try. + Otherwise we keep retrying on the CTA (USE_CLIENT) up to the max retry value. In essence we + try to treat the CTA as if it were another RR value in the HostDB record. + */ + enum class OS_Addr { + TRY_DEFAULT, ///< Initial state, use what config says. + TRY_HOSTDB, ///< Try HostDB data. + TRY_CLIENT, ///< Try client target addr. + USE_HOSTDB, ///< Force use of HostDB target address. + USE_CLIENT, ///< Force client target addr. + USE_API ///< Use the API provided address. + }; + + ResolveInfo() = default; + ~ResolveInfo() = default; + + /// Keep a reference to the base HostDB object, so it doesn't get GC'd. + Ptr record; + HostDBInfo *active = nullptr; ///< Active host record. + + /// Working address. The meaning / source of the value depends on other elements. + /// This is the "resolved" address if @a resolved_p is @c true. + IpEndpoint addr; + + int attempts = 0; ///< Number of connection attempts. + + char const *lookup_name = nullptr; + char srv_hostname[MAXDNAME] = {0}; + const sockaddr *inbound_remote_addr = nullptr; ///< Remote address of inbound client - used for hashing. + in_port_t srv_port = 0; ///< Port from SRV lookup or API call. + + OS_Addr os_addr_style = OS_Addr::TRY_DEFAULT; + HostResStyle host_res_style = HOST_RES_IPV4; + UpstreamResolveStyle looking_up = UNDEFINED_LOOKUP; + + HTTPVersion http_version = HTTP_INVALID; + + bool resolved_p = false; ///< If there is a valid, resolved address in @a addr. + + /// Flag for @a addr being set externally. + // bool api_addr_set_p = false; + + /*** Set to true by default. If use_client_target_address is set + * to 1, this value will be set to false if the client address is + * not in the DNS pool */ + bool cta_validated_p = true; + + bool set_active(HostDBInfo *info); + + bool set_active(sockaddr const *s); + + bool set_active(std::nullptr_t); + + /** Force a resolved address. + * + * @param sa Address to use for the upstream. + * @return @c true if successful, @c false if error. + * + * This fails if @a sa isn't a valid IP address. + */ + bool set_upstream_address(const sockaddr *sa); + + bool set_upstream_address(IpAddr const &ip_addr); + + void set_upstream_port(in_port_t port); + + /** Check and (if possible) immediately resolve the upstream address without consulting the HostDB. + * The cases where this is successful are + * - The address is already resolved (@a resolved_p is @c true). + * - The upstream was set explicitly. + * - The hostname is a valid IP address. + * + * @return @c true if the upstream address was resolved, @c false if not. + */ + bool resolve_immediate(); + + /** Mark the active target as dead. + * + * @param now Time of failure. + * @return @c true if the server was marked as dead, @c false if not. + * + */ + bool mark_active_server_dead(ts_time now); + + /** Mark the active target as alive. + * + * @return @c true if the target changed state. + */ + bool mark_active_server_alive(); + + /// Select / resolve to the next RR entry for the record. + bool select_next_rr(); + + bool is_srv() const; +}; + /** The Host Database access interface. */ struct HostDBProcessor : public Processor { friend struct HostDBSync; @@ -486,29 +680,6 @@ struct HostDBProcessor : public Processor { /** 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; @@ -524,21 +695,151 @@ struct HostDBProcessor : public Processor { 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. - ); +inline bool +HostDBRecord::is_srv() const +{ + return HostDBType::SRV == record_type; +} - void setby_srv(const char *hostname, int len, const char *target, HostDBApplicationInfo *app); -}; +inline char const * +HostDBRecord::name() const +{ + return this->apply_offset(sizeof(self_type)); +} + +inline ts::TextView +HostDBRecord::name_view() const +{ + return {this->name(), ts::TextView::npos}; +} + +inline ts_time +HostDBRecord::expiry_time() const +{ + return ip_timestamp + ip_timeout_interval + ts_seconds(hostdb_serve_stale_but_revalidate); +} + +inline ts_seconds +HostDBRecord::ip_interval() const +{ + static constexpr ts_seconds ZERO{0}; + static constexpr ts_seconds MAX{0x7FFFFFFF}; + return std::clamp(std::chrono::duration_cast((hostdb_current_interval - ip_timestamp)), ZERO, MAX); +} + +inline ts_seconds +HostDBRecord::ip_time_remaining() const +{ + return ip_timeout_interval - this->ip_interval(); +} + +inline bool +HostDBRecord::is_ip_stale() const +{ + return ip_timeout_interval >= ts_seconds(2 * hostdb_ip_stale_interval) && ip_interval() >= ts_seconds(hostdb_ip_stale_interval); +} + +inline bool +HostDBRecord::is_ip_timeout() const +{ + return ip_interval() >= ip_timeout_interval; +} + +inline bool +HostDBRecord::is_ip_fail_timeout() const +{ + return ip_interval() >= ts_seconds(hostdb_ip_fail_timeout_interval); +} + +inline void +HostDBRecord::refresh_ip() +{ + ip_timestamp = hostdb_current_interval; +} + +inline ts::MemSpan +HostDBRecord::rr_info() +{ + return {this->apply_offset(rr_offset), rr_count}; +} + +inline bool +HostDBRecord::is_failed() const +{ + return flags.f.failed_p; +} + +inline void +HostDBRecord::set_failed() +{ + flags.f.failed_p = true; +} + +inline unsigned short +HostDBRecord::rr_idx() const +{ + return _rr_idx % rr_count; +} + +inline unsigned short +HostDBRecord::rr_idx(unsigned short delta) const +{ + return (_rr_idx + delta) % rr_count; +} + +inline int +HostDBRecord::index_of(HostDBInfo const *target) const +{ + return target ? target - this->apply_offset(rr_offset) : -1; +} + +// -- + +inline bool +ResolveInfo::set_active(sockaddr const *s) +{ + return this->set_active(record->find(s)); +} + +inline bool +ResolveInfo::mark_active_server_alive() +{ + return active->mark_up(); +} + +inline bool +ResolveInfo::mark_active_server_dead(ts_time now) +{ + return active != nullptr && active->mark_down(now); +} + +inline bool ResolveInfo::set_active(std::nullptr_t) +{ + active = nullptr; + resolved_p = false; + return false; +} + +inline bool +ResolveInfo::set_upstream_address(sockaddr const *sa) +{ + return resolved_p = addr.assign(sa).isValid(); +} + +inline void +ResolveInfo::set_upstream_port(in_port_t port) +{ + srv_port = port; +} + +inline bool +ResolveInfo::is_srv() const +{ + return record && record->is_srv(); +} +// --- void run_HostDBTest(); diff --git a/iocore/hostdb/P_HostDBProcessor.h b/iocore/hostdb/P_HostDBProcessor.h index 7a05013c7b2..3ebd270a827 100644 --- a/iocore/hostdb/P_HostDBProcessor.h +++ b/iocore/hostdb/P_HostDBProcessor.h @@ -27,6 +27,8 @@ #pragma once +#include + #include "I_HostDBProcessor.h" #include "tscore/TsBuffer.h" @@ -51,7 +53,7 @@ extern int hostdb_ttl_mode; extern int hostdb_srv_enabled; // extern int hostdb_timestamp; -extern int hostdb_sync_frequency; +extern ts_seconds hostdb_sync_frequency; extern int hostdb_disable_reverse_lookup; // Static configuration information @@ -74,26 +76,6 @@ enum HostDBMark { */ extern const char *string_for(HostDBMark mark); -inline unsigned int -HOSTDB_CLIENT_IP_HASH(sockaddr const *lhs, sockaddr const *rhs) -{ - unsigned int zret = ~static_cast(0); - if (ats_ip_are_compatible(lhs, rhs)) { - if (ats_is_ip4(lhs)) { - in_addr_t ip1 = ats_ip4_addr_cast(lhs); - in_addr_t ip2 = ats_ip4_addr_cast(rhs); - zret = (ip1 >> 16) ^ ip1 ^ ip2 ^ (ip2 >> 16); - } else if (ats_is_ip6(lhs)) { - uint32_t const *ip1 = ats_ip_addr32_cast(lhs); - uint32_t const *ip2 = ats_ip_addr32_cast(rhs); - for (int i = 0; i < 4; ++i, ++ip1, ++ip2) { - zret ^= (*ip1 >> 16) ^ *ip1 ^ *ip2 ^ (*ip2 >> 16); - } - } - } - return zret & 0xFFFF; -} - // // Constants // @@ -170,21 +152,12 @@ extern RecRawStatBlock *hostdb_rsb; #define HOSTDB_DECREMENT_THREAD_DYN_STAT(_s, _t) RecIncrRawStatSum(hostdb_rsb, _t, (int)_s, -1); -struct CmpConstBuffferCaseInsensitive { - bool - operator()(ts::ConstBuffer a, ts::ConstBuffer b) const - { - return ptr_len_casecmp(a._ptr, a._size, b._ptr, b._size) < 0; - } +struct HostFileRecord { + HostDBRecord::Handle record_4; + HostDBRecord::Handle record_6; }; -// Our own typedef for the host file mapping -typedef std::map HostsFileMap; -// A to hold a ref-counted map -struct RefCountedHostsFileMap : public RefCountObj { - HostsFileMap hosts_file_map; - ats_scoped_str HostFileText; -}; +using HostFileMap = std::unordered_map>; // // HostDBCache (Private) @@ -192,9 +165,11 @@ struct RefCountedHostsFileMap : public RefCountObj { struct HostDBCache { int start(int flags = 0); // Map to contain all of the host file overrides, initialize it to empty - Ptr hosts_file_ptr; + std::shared_ptr host_file; + std::shared_mutex host_file_mutex; + // TODO: make ATS call a close() method or something on shutdown (it does nothing of the sort today) - RefCountCache *refcountcache = nullptr; + RefCountCache *refcountcache = nullptr; // TODO configurable number of items in the cache Queue *pending_dns = nullptr; @@ -202,189 +177,9 @@ struct HostDBCache { Queue *remoteHostDBQueue = nullptr; HostDBCache(); bool is_pending_dns_for_hash(const CryptoHash &hash); -}; - -inline int -HostDBRoundRobin::index_of(sockaddr const *ip) -{ - bool bad = (rrcount <= 0 || (unsigned int)rrcount > hostdb_round_robin_max_count || good <= 0 || - (unsigned int)good > hostdb_round_robin_max_count); - if (bad) { - ink_assert(!"bad round robin size"); - return -1; - } - - for (int i = 0; i < good; i++) { - if (ats_ip_addr_eq(ip, info(i).ip())) { - return i; - } - } - - return -1; -} - -inline HostDBInfo * -HostDBRoundRobin::find_ip(sockaddr const *ip) -{ - int idx = this->index_of(ip); - return idx < 0 ? nullptr : &info(idx); -} - -inline HostDBInfo * -HostDBRoundRobin::select_next(sockaddr const *ip) -{ - HostDBInfo *zret = nullptr; - if (good > 1) { - int idx = this->index_of(ip); - if (idx >= 0) { - idx = (idx + 1) % good; - zret = &info(idx); - } - } - return zret; -} - -inline HostDBInfo * -HostDBRoundRobin::find_target(const char *target) -{ - bool bad = (rrcount <= 0 || (unsigned int)rrcount > hostdb_round_robin_max_count || good <= 0 || - (unsigned int)good > hostdb_round_robin_max_count); - if (bad) { - ink_assert(!"bad round robin size"); - return nullptr; - } - - uint32_t key = makeHostHash(target); - for (int i = 0; i < good; i++) { - if (info(i).data.srv.key == key && !strcmp(target, info(i).srvname(this))) - return &info(i); - } - return nullptr; -} - -inline HostDBInfo * -HostDBRoundRobin::select_best_http(sockaddr const *client_ip, ink_time_t now, int32_t fail_window) -{ - bool bad = (rrcount <= 0 || (unsigned int)rrcount > hostdb_round_robin_max_count || good <= 0 || - (unsigned int)good > hostdb_round_robin_max_count); - - if (bad) { - ink_assert(!"bad round robin size"); - return nullptr; - } - - int best_any = 0; - int best_up = -1; - - // Basic round robin, increment current and mod with how many we have - if (HostDBProcessor::hostdb_strict_round_robin) { - Debug("hostdb", "Using strict round robin"); - // Check that the host we selected is alive - for (int i = 0; i < good; i++) { - best_any = current++ % good; - if (info(best_any).is_alive(now, fail_window)) { - best_up = best_any; - break; - } - } - } else if (HostDBProcessor::hostdb_timed_round_robin > 0) { - Debug("hostdb", "Using timed round-robin for HTTP"); - if ((now - timed_rr_ctime) > HostDBProcessor::hostdb_timed_round_robin) { - Debug("hostdb", "Timed interval expired.. rotating"); - ++current; - timed_rr_ctime = now; - } - for (int i = 0; i < good; i++) { - best_any = (current + i) % good; - if (info(best_any).is_alive(now, fail_window)) { - best_up = best_any; - break; - } - } - Debug("hostdb", "Using %d for best_up", best_up); - } else { - Debug("hostdb", "Using default round robin"); - unsigned int best_hash_any = 0; - unsigned int best_hash_up = 0; - for (int i = 0; i < good; i++) { - sockaddr const *ip = info(i).ip(); - unsigned int h = HOSTDB_CLIENT_IP_HASH(client_ip, ip); - if (best_hash_any <= h) { - best_any = i; - best_hash_any = h; - } - if (info(i).is_alive(now, fail_window)) { - if (best_hash_up <= h) { - best_up = i; - best_hash_up = h; - } - } - } - } - - if (best_up != -1) { - ink_assert(best_up >= 0 && best_up < good); - return &info(best_up); - } else { - ink_assert(best_any >= 0 && best_any < good); - return &info(best_any); - } -} - -inline HostDBInfo * -HostDBRoundRobin::select_best_srv(char *target, InkRand *rand, ink_time_t now, int32_t fail_window) -{ - bool bad = (rrcount <= 0 || (unsigned int)rrcount > hostdb_round_robin_max_count || good <= 0 || - (unsigned int)good > hostdb_round_robin_max_count); - - if (bad) { - ink_assert(!"bad round robin size"); - return nullptr; - } - -#ifdef DEBUG - for (int i = 1; i < good; ++i) { - ink_assert(info(i).data.srv.srv_priority >= info(i - 1).data.srv.srv_priority); - } -#endif - - int i = 0; - int len = 0; - uint32_t weight = 0, p = INT32_MAX; - HostDBInfo *result = nullptr; - HostDBInfo *infos[good]; - do { - // if the real isn't alive-- exclude it from selection - if (!info(i).is_alive(now, fail_window)) { - continue; - } - - if (info(i).data.srv.srv_priority <= p) { - p = info(i).data.srv.srv_priority; - weight += info(i).data.srv.srv_weight; - infos[len++] = &info(i); - } else - break; - } while (++i < good); - - if (len == 0) { // all failed - result = &info(current++ % good); - } else if (weight == 0) { // srv weight is 0 - result = infos[current++ % len]; - } else { - uint32_t xx = rand->random() % weight; - for (i = 0; i < len - 1 && xx >= infos[i]->data.srv.srv_weight; ++i) - xx -= infos[i]->data.srv.srv_weight; - - result = infos[i]; - } - if (result) { - ink_strlcpy(target, result->srvname(this), MAXDNAME); - return result; - } - return nullptr; -} + std::shared_ptr acquire_host_file(); +}; // // Types @@ -398,10 +193,9 @@ struct HostDBHash { CryptoHash hash; ///< The hash value. - const char *host_name = nullptr; ///< Host name. - int host_len = 0; ///< Length of @a _host_name - IpAddr ip; ///< IP address. - in_port_t port = 0; ///< IP port (host order). + ts::TextView host_name; ///< Name of the host for the query. + IpAddr ip; ///< IP address. + in_port_t port = 0; ///< IP port (host order). /// DNS server. Not strictly part of the hash data but /// it's both used by @c HostDBContinuation and provides access to /// hash data. It's just handier to store it here for both uses. @@ -418,20 +212,23 @@ struct HostDBHash { /** Assign a hostname. This updates the split DNS data as well. */ - self &set_host(const char *name, int len); + self &set_host(ts::TextView name); + self & + set_host(char const *name) + { + return this->set_host(ts::TextView{name, strlen(name)}); + } }; // // Handles a HostDB lookup request // -struct HostDBContinuation; -typedef int (HostDBContinuation::*HostDBContHandler)(int, void *); +using HostDBContHandler = int (HostDBContinuation::*)(int, void *); struct HostDBContinuation : public Continuation { Action action; HostDBHash hash; - // IpEndpoint ip; - unsigned int ttl = 0; + ts_seconds ttl{0}; // HostDBMark db_mark; ///< Target type. /// Original IP address family style. Note this will disagree with /// @a hash.db_mark when doing a retry on an alternate family. The retry @@ -440,9 +237,8 @@ struct HostDBContinuation : public Continuation { int dns_lookup_timeout = DEFAULT_OPTIONS.timeout; Event *timeout = nullptr; Continuation *from_cont = nullptr; - HostDBApplicationInfo app; - int probe_depth = 0; - size_t current_iterate_pos = 0; + int probe_depth = 0; + size_t current_iterate_pos = 0; // char name[MAXDNAME]; // int namelen; char hash_host_name_store[MAXDNAME + 1]; // used as backing store for @a hash @@ -452,7 +248,6 @@ struct HostDBContinuation : public Continuation { unsigned int missing : 1; unsigned int force_dns : 1; - unsigned int round_robin : 1; int probeEvent(int event, Event *e); int iterateEvent(int event, Event *e); @@ -475,19 +270,23 @@ struct HostDBContinuation : public Continuation { { return hash.db_mark == HOSTDB_MARK_SRV; } - HostDBInfo *lookup_done(IpAddr const &ip, const char *aname, bool round_robin, unsigned int attl, SRVHosts *s = nullptr, - HostDBInfo *r = nullptr); + + Ptr + lookup_done(const char *query_name, ts_seconds answer_ttl, SRVHosts *s = nullptr, Ptr record = Ptr{}) + { + return this->lookup_done(ts::TextView{query_name, strlen(query_name)}, answer_ttl, s, record); + } + + Ptr lookup_done(ts::TextView query_name, ts_seconds answer_ttl, SRVHosts *s = nullptr, + Ptr record = Ptr{}); + int key_partition(); - void remove_trigger_pending_dns(); + void remove_and_trigger_pending_dns(); int set_check_pending_dns(); - HostDBInfo *insert(unsigned int attl); - /** Optional values for @c init. */ struct Options { - typedef Options self; ///< Self reference type. - int timeout = 0; ///< Timeout value. Default 0 HostResStyle host_res_style = HOST_RES_NONE; ///< IP address family fallback. Default @c HOST_RES_NONE bool force_dns = false; ///< Force DNS lookup. Default @c false @@ -500,7 +299,7 @@ struct HostDBContinuation : public Continuation { int make_get_message(char *buf, int len); int make_put_message(HostDBInfo *r, Continuation *c, char *buf, int len); - HostDBContinuation() : missing(false), force_dns(DEFAULT_OPTIONS.force_dns), round_robin(false) + HostDBContinuation() : missing(false), force_dns(DEFAULT_OPTIONS.force_dns) { ink_zero(hash_host_name_store); ink_zero(hash.hash); @@ -514,12 +313,6 @@ master_hash(CryptoHash const &hash) return static_cast(hash[1] >> 32); } -inline bool -is_dotted_form_hostname(const char *c) -{ - return -1 != (int)ink_inet_addr(c); -} - inline Queue & HostDBCache::pending_dns_for_hash(const CryptoHash &hash) { diff --git a/plugins/lua/ts_lua_misc.c b/plugins/lua/ts_lua_misc.c index 8859a17bad5..f8848f04585 100644 --- a/plugins/lua/ts_lua_misc.c +++ b/plugins/lua/ts_lua_misc.c @@ -517,12 +517,14 @@ ts_lua_host_lookup_handler(TSCont contp, TSEvent event, void *edata) } else if (!edata) { lua_pushnil(L); } else { - TSHostLookupResult result = (TSHostLookupResult)edata; - struct sockaddr const *addr = TSHostLookupResultAddrGet(result); + TSHostLookupResult record = (TSHostLookupResult)edata; + struct sockaddr const *addr = TSHostLookupResultAddrGet(record); if (addr->sa_family == AF_INET) { - inet_ntop(AF_INET, (const void *)&((struct sockaddr_in *)addr)->sin_addr, cip, sizeof(cip)); + inet_ntop(AF_INET, &((struct sockaddr_in const *)addr)->sin_addr, cip, sizeof(cip)); + } else if (addr->sa_family == AF_INET6) { + inet_ntop(AF_INET6, &((struct sockaddr_in6 const *)addr)->sin6_addr, cip, sizeof(cip)); } else { - inet_ntop(AF_INET6, (const void *)&((struct sockaddr_in6 *)addr)->sin6_addr, cip, sizeof(cip)); + cip[0] = 0; } lua_pushstring(L, cip); } diff --git a/proxy/ControlMatcher.h b/proxy/ControlMatcher.h index 6c1d3624474..6365ee0ed49 100644 --- a/proxy/ControlMatcher.h +++ b/proxy/ControlMatcher.h @@ -144,10 +144,10 @@ class HttpRequestData : public RequestData ink_zero(dest_ip); } - HTTPHdr *hdr = nullptr; - char *hostname_str = nullptr; - HttpApiInfo *api_info = nullptr; - time_t xact_start = 0; + HTTPHdr *hdr = nullptr; + char const *hostname_str = nullptr; + HttpApiInfo *api_info = nullptr; + time_t xact_start = 0; IpEndpoint src_ip; IpEndpoint dest_ip; uint16_t incoming_port = 0; diff --git a/proxy/ParentSelection.cc b/proxy/ParentSelection.cc index 0f495f1af5f..9d5546e2e4b 100644 --- a/proxy/ParentSelection.cc +++ b/proxy/ParentSelection.cc @@ -1068,21 +1068,21 @@ EXCLUSIVE_REGRESSION_TEST(PARENTSELECTION)(RegressionTest * /* t ATS_UNUSED */, params = new ParentConfigParams(ParentTable); \ } while (0) -#define REINIT \ - do { \ - if (request != NULL) { \ - delete request->hdr; \ - ats_free(request->hostname_str); \ - delete request->api_info; \ - } \ - delete request; \ - delete result; \ - request = new HttpRequestData(); \ - result = new ParentResult(); \ - if (!result || !request) { \ - (void)printf("Allocation failed\n"); \ - return; \ - } \ +#define REINIT \ + do { \ + if (request != nullptr) { \ + delete request->hdr; \ + /* ats_free(request->hostname_str); */ \ + delete request->api_info; \ + } \ + delete request; \ + delete result; \ + request = new HttpRequestData(); \ + result = new ParentResult(); \ + if (!result || !request) { \ + (void)printf("Allocation failed\n"); \ + return; \ + } \ } while (0) #define ST(x) \ diff --git a/proxy/http/HttpConfig.cc b/proxy/http/HttpConfig.cc index d7798dbb1c0..7fe46cef783 100644 --- a/proxy/http/HttpConfig.cc +++ b/proxy/http/HttpConfig.cc @@ -35,6 +35,8 @@ #include #include "HttpSessionManager.h" +extern void HostDB_Config_Init(); + #define HttpEstablishStaticConfigStringAlloc(_ix, _n) \ REC_EstablishStaticConfigStringAlloc(_ix, _n); \ REC_RegisterConfigUpdateFunc(_n, http_config_cb, NULL) @@ -1372,7 +1374,7 @@ HttpConfig::startup() HttpEstablishStaticConfigByte(c.referer_filter_enabled, "proxy.config.http.referer_filter"); HttpEstablishStaticConfigByte(c.referer_format_redirect, "proxy.config.http.referer_format_redirect"); - HttpEstablishStaticConfigLongLong(c.oride.down_server_timeout, "proxy.config.http.down_server.cache_time"); + HostDB_Config_Init(); // Negative caching and revalidation HttpEstablishStaticConfigByte(c.oride.negative_caching_enabled, "proxy.config.http.negative_caching_enabled"); diff --git a/proxy/http/HttpConfig.h b/proxy/http/HttpConfig.h index 03c4904a02c..cc6b3e2e91d 100644 --- a/proxy/http/HttpConfig.h +++ b/proxy/http/HttpConfig.h @@ -40,6 +40,8 @@ #include #include +#include + #include "tscore/ink_platform.h" #include "tscore/ink_inet.h" #include "tscore/ink_resolver.h" @@ -688,7 +690,7 @@ struct OverridableHttpConfigParams { MgmtByte enable_parent_timeout_markdowns = 0; MgmtByte disable_parent_markdowns = 0; - MgmtInt down_server_timeout = 300; + ts_seconds down_server_timeout{300}; MgmtInt client_abort_threshold = 1000; // open read failure retries. diff --git a/proxy/http/HttpConnectionCount.cc b/proxy/http/HttpConnectionCount.cc index cd64e8d38d5..009ea8f3390 100644 --- a/proxy/http/HttpConnectionCount.cc +++ b/proxy/http/HttpConnectionCount.cc @@ -31,7 +31,7 @@ using namespace std::literals; -extern int http_config_cb(const char *, RecDataT, RecData, void *); +extern void Enable_Config_Var(std::string_view const &name, bool (*cb)(const char *, RecDataT, RecData, void *), void *cookie); OutboundConnTrack::Imp OutboundConnTrack::_imp; diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc index ded6448013d..71fcb46e7ba 100644 --- a/proxy/http/HttpSM.cc +++ b/proxy/http/HttpSM.cc @@ -1867,7 +1867,7 @@ HttpSM::state_http_server_open(int event, void *data) NetVConnection *netvc = static_cast(data); UnixNetVConnection *vc = static_cast(data); PoolableSession *new_session = this->create_server_session(netvc); - if (t_state.current.request_to == HttpTransact::PARENT_PROXY) { + if (t_state.current.request_to == ResolveInfo::PARENT_PROXY) { new_session->to_parent_proxy = true; HTTP_INCREMENT_DYN_STAT(http_current_parent_proxy_connections_stat); HTTP_INCREMENT_DYN_STAT(http_total_parent_proxy_connections_stat); @@ -2230,34 +2230,28 @@ HttpSM::state_send_server_request_header(int event, void *data) } void -HttpSM::process_srv_info(HostDBInfo *r) +HttpSM::process_srv_info(HostDBRecord *record) { SMDebug("dns_srv", "beginning process_srv_info"); - t_state.hostdb_entry = Ptr(r); + t_state.dns_info.record = record; /* we didn't get any SRV records, continue w normal lookup */ - if (!r || !r->is_srv || !r->round_robin) { - t_state.dns_info.srv_hostname[0] = '\0'; - t_state.dns_info.srv_lookup_success = false; - t_state.my_txn_conf().srv_enabled = false; + if (!record || !record->is_srv()) { + t_state.dns_info.srv_hostname[0] = '\0'; + t_state.dns_info.resolved_p = false; + t_state.my_txn_conf().srv_enabled = false; SMDebug("dns_srv", "No SRV records were available, continuing to lookup %s", t_state.dns_info.lookup_name); } else { - HostDBRoundRobin *rr = r->rr(); - HostDBInfo *srv = nullptr; - if (rr) { - srv = rr->select_best_srv(t_state.dns_info.srv_hostname, &mutex->thread_holding->generator, ink_local_time(), - static_cast(t_state.txn_conf->down_server_timeout)); - } + HostDBInfo *srv = record->select_best_srv(t_state.dns_info.srv_hostname, &mutex->thread_holding->generator, ts_clock::now(), + t_state.txn_conf->down_server_timeout); if (!srv) { - t_state.dns_info.srv_lookup_success = false; - t_state.dns_info.srv_hostname[0] = '\0'; - t_state.my_txn_conf().srv_enabled = false; + // t_state.dns_info.srv_lookup_success = false; + t_state.dns_info.srv_hostname[0] = '\0'; + t_state.my_txn_conf().srv_enabled = false; SMDebug("dns_srv", "SRV records empty for %s", t_state.dns_info.lookup_name); } else { - t_state.dns_info.srv_lookup_success = true; - t_state.dns_info.srv_port = srv->data.srv.srv_port; - t_state.dns_info.srv_app = srv->app; - // t_state.dns_info.single_srv = (rr->good == 1); + t_state.dns_info.resolved_p = false; + t_state.dns_info.srv_port = srv->data.srv.srv_port; ink_assert(srv->data.srv.key == makeHostHash(t_state.dns_info.srv_hostname)); SMDebug("dns_srv", "select SRV records %s", t_state.dns_info.srv_hostname); } @@ -2266,87 +2260,43 @@ HttpSM::process_srv_info(HostDBInfo *r) } void -HttpSM::process_hostdb_info(HostDBInfo *r) +HttpSM::process_hostdb_info(HostDBRecord *record) { - // Increment the refcount to our item, since we are pointing at it - t_state.hostdb_entry = Ptr(r); + t_state.dns_info.record = record; // protect record. + + bool use_client_addr = t_state.http_config_param->use_client_target_addr == 1 && t_state.client_info.is_transparent && + t_state.dns_info.os_addr_style == ResolveInfo::OS_Addr::TRY_DEFAULT; + + t_state.dns_info.set_active(nullptr); - sockaddr const *client_addr = nullptr; - bool use_client_addr = t_state.http_config_param->use_client_target_addr == 1 && t_state.client_info.is_transparent && - t_state.dns_info.os_addr_style == HttpTransact::DNSLookupInfo::OS_Addr::OS_ADDR_TRY_DEFAULT; if (use_client_addr) { NetVConnection *vc = ua_txn ? ua_txn->get_netvc() : nullptr; if (vc) { - client_addr = vc->get_local_addr(); - // Regardless of whether the client address matches the DNS record or not, - // we want to use that address. Therefore, we copy over the client address - // info and skip the assignment from the DNS cache - ats_ip_copy(t_state.host_db_info.ip(), client_addr); - t_state.dns_info.os_addr_style = HttpTransact::DNSLookupInfo::OS_Addr::OS_ADDR_TRY_CLIENT; - t_state.dns_info.lookup_success = true; - // Leave ret unassigned, so we don't overwrite the host_db_info - } else { - use_client_addr = false; + t_state.dns_info.set_upstream_address(vc->get_local_addr()); + t_state.dns_info.os_addr_style = ResolveInfo::OS_Addr::TRY_CLIENT; } } - if (r && !r->is_failed()) { - ink_time_t now = ink_local_time(); - HostDBInfo *ret = nullptr; - t_state.dns_info.lookup_success = true; - t_state.dns_info.lookup_validated = true; - - HostDBRoundRobin *rr = r->round_robin ? r->rr() : nullptr; - if (rr) { + if (record && !record->is_failed()) { + t_state.dns_info.inbound_remote_addr = &t_state.client_info.src_addr.sa; + if (!use_client_addr) { + t_state.dns_info.set_active( + record->select_best_http(ts_clock::now(), t_state.txn_conf->down_server_timeout, t_state.dns_info.inbound_remote_addr)); + } else { // if use_client_target_addr is set, make sure the client addr is in the results pool - if (use_client_addr && rr->find_ip(client_addr) == nullptr) { + t_state.dns_info.cta_validated_p = true; + t_state.dns_info.record = record; // Cache this but do not make it active. + if (record->find(t_state.dns_info.addr) == nullptr) { SMDebug("http", "use_client_target_addr == 1. Client specified address is not in the pool, not validated."); - t_state.dns_info.lookup_validated = false; - } else { - // Since the time elapsed between current time and client_request_time - // may be very large, we cannot use client_request_time to approximate - // current time when calling select_best_http(). - ret = rr->select_best_http(&t_state.client_info.src_addr.sa, now, static_cast(t_state.txn_conf->down_server_timeout)); - // set the srv target`s last_failure - if (t_state.dns_info.srv_lookup_success) { - uint32_t last_failure = 0xFFFFFFFF; - for (int i = 0; i < rr->rrcount && last_failure != 0; ++i) { - if (last_failure > rr->info(i).app.http_data.last_failure) { - last_failure = rr->info(i).app.http_data.last_failure; - } - } - - if (last_failure != 0 && static_cast(now - t_state.txn_conf->down_server_timeout) < last_failure) { - HostDBApplicationInfo app; - app.allotment.application1 = 0; - app.allotment.application2 = 0; - app.http_data.last_failure = last_failure; - hostDBProcessor.setby_srv(t_state.dns_info.lookup_name, 0, t_state.dns_info.srv_hostname, &app); - } - } - } - } else { - if (use_client_addr && !ats_ip_addr_eq(client_addr, &r->data.ip.sa)) { - SMDebug("http", "use_client_target_addr == 1. Comparing single addresses failed, not validated."); - t_state.dns_info.lookup_validated = false; - } else { - ret = r; + t_state.dns_info.cta_validated_p = false; } } - if (ret) { - t_state.host_db_info = *ret; - ink_release_assert(!t_state.host_db_info.reverse_dns); - ink_release_assert(ats_is_ip(t_state.host_db_info.ip())); - } } else { SMDebug("http", "DNS lookup failed for '%s'", t_state.dns_info.lookup_name); + } - if (!use_client_addr) { - t_state.dns_info.lookup_success = false; - } - t_state.host_db_info.app.allotment.application1 = 0; - t_state.host_db_info.app.allotment.application2 = 0; - ink_assert(!t_state.host_db_info.round_robin); + if (!t_state.dns_info.resolved_p) { + SMDebug("http", "[%" PRId64 "] resolution failed for '%s'", sm_id, t_state.dns_info.lookup_name); } milestones[TS_MILESTONE_DNS_LOOKUP_END] = Thread::get_hrtime(); @@ -2359,6 +2309,13 @@ HttpSM::process_hostdb_info(HostDBInfo *r) } } +int +HttpSM::state_pre_resolve(int event, void *data) +{ + STATE_ENTER(&HttpSM::state_hostdb_lookup, event); + return 0; +} + ////////////////////////////////////////////////////////////////////////////// // // HttpSM::state_hostdb_lookup() @@ -2377,16 +2334,16 @@ HttpSM::state_hostdb_lookup(int event, void *data) switch (event) { case EVENT_HOST_DB_LOOKUP: pending_action = nullptr; - process_hostdb_info(static_cast(data)); + process_hostdb_info(static_cast(data)); call_transact_and_set_next_state(nullptr); break; case EVENT_SRV_LOOKUP: { pending_action = nullptr; - process_srv_info(static_cast(data)); + process_srv_info(static_cast(data)); - char *host_name = t_state.dns_info.srv_lookup_success ? t_state.dns_info.srv_hostname : t_state.dns_info.lookup_name; + char const *host_name = t_state.dns_info.is_srv() ? t_state.dns_info.record->name() : t_state.dns_info.lookup_name; HostDBProcessor::Options opt; - opt.port = t_state.dns_info.srv_lookup_success ? t_state.dns_info.srv_port : t_state.server_info.dst_addr.host_order_port(); + opt.port = t_state.dns_info.is_srv() ? t_state.dns_info.srv_port : t_state.server_info.dst_addr.host_order_port(); opt.flags = (t_state.cache_info.directives.does_client_permit_dns_storing) ? HostDBProcessor::HOSTDB_DO_NOT_FORCE_DNS : HostDBProcessor::HOSTDB_FORCE_DNS_RELOAD; opt.timeout = (t_state.api_txn_dns_timeout_value != -1) ? t_state.api_txn_dns_timeout_value : 0; @@ -2421,7 +2378,7 @@ HttpSM::state_hostdb_reverse_lookup(int event, void *data) case EVENT_HOST_DB_LOOKUP: pending_action = nullptr; if (data) { - t_state.request_data.hostname_str = (static_cast(data))->hostname(); + t_state.request_data.hostname_str = (static_cast(data))->name(); } else { SMDebug("http", "reverse DNS lookup failed for '%s'", t_state.dns_info.lookup_name); } @@ -2443,27 +2400,15 @@ int HttpSM::state_mark_os_down(int event, void *data) { STATE_ENTER(&HttpSM::state_mark_os_down, event); - HostDBInfo *mark_down = nullptr; if (event == EVENT_HOST_DB_LOOKUP && data) { - HostDBInfo *r = static_cast(data); - - if (r->round_robin) { - // Look for the entry we need mark down in the round robin - ink_assert(t_state.current.server != nullptr); - ink_assert(t_state.current.request_to == HttpTransact::ORIGIN_SERVER); - if (t_state.current.server) { - mark_down = r->rr()->find_ip(&t_state.current.server->dst_addr.sa); - } - } else { - // No longer a round robin, check to see if our address is the same - if (ats_ip_addr_eq(t_state.host_db_info.ip(), r->ip())) { - mark_down = r; - } - } + auto r = static_cast(data); - if (mark_down) { - mark_host_failure(mark_down, t_state.request_sent_time); + // Look for the entry we need mark down in the round robin + ink_assert(t_state.current.server != nullptr); + ink_assert(t_state.dns_info.looking_up == ResolveInfo::ORIGIN_SERVER); + if (auto *info = r->find(&t_state.dns_info.addr.sa); info != nullptr) { + info->mark_down(ts_clock::now()); } } // We either found our entry or we did not. Either way find @@ -4260,8 +4205,8 @@ HttpSM::do_hostdb_lookup() } pending_action = hostDBProcessor.getSRVbyname_imm(this, (cb_process_result_pfn)&HttpSM::process_srv_info, d, 0, opt); if (pending_action.empty()) { - char *host_name = t_state.dns_info.srv_lookup_success ? t_state.dns_info.srv_hostname : t_state.dns_info.lookup_name; - opt.port = t_state.dns_info.srv_lookup_success ? + char const *host_name = t_state.dns_info.resolved_p ? t_state.dns_info.srv_hostname : t_state.dns_info.lookup_name; + opt.port = t_state.dns_info.resolved_p ? t_state.dns_info.srv_port : t_state.server_info.dst_addr.isValid() ? t_state.server_info.dst_addr.host_order_port() : t_state.hdr_info.client_request.port_get(); @@ -4347,18 +4292,10 @@ HttpSM::track_connect_fail() const void HttpSM::do_hostdb_update_if_necessary() { - int issue_update = 0; - - if (t_state.current.server == nullptr || plugin_tunnel_type != HTTP_NO_PLUGIN_TUNNEL) { + if (t_state.current.server == nullptr || plugin_tunnel_type != HTTP_NO_PLUGIN_TUNNEL || t_state.dns_info.active == nullptr) { // No server, so update is not necessary return; } - // If we failed back over to the origin server, we don't have our - // hostdb information anymore which means we shouldn't update the hostdb - if (!ats_ip_addr_eq(&t_state.current.server->dst_addr.sa, t_state.host_db_info.ip())) { - SMDebug("http", "skipping hostdb update due to server failover"); - return; - } if (t_state.updated_server_version != HTTP_INVALID) { // we may have incorrectly assumed that the hostdb had the wrong version of @@ -4368,39 +4305,25 @@ HttpSM::do_hostdb_update_if_necessary() // // This test therefore just issues the update only if the hostdb version is // in fact different from the version we want the value to be updated to. - if (t_state.host_db_info.app.http_data.http_version != t_state.updated_server_version) { - t_state.host_db_info.app.http_data.http_version = t_state.updated_server_version; - issue_update |= 1; - } - - t_state.updated_server_version = HTTP_INVALID; + t_state.updated_server_version = HTTP_INVALID; + t_state.dns_info.active->http_version = t_state.updated_server_version; } + // Check to see if we need to report or clear a connection failure if (track_connect_fail()) { - issue_update |= 1; - mark_host_failure(&t_state.host_db_info, t_state.client_request_time); + this->mark_host_failure(&t_state.dns_info, ts_clock::from_time_t(t_state.client_request_time)); } else { - if (t_state.host_db_info.app.http_data.last_failure != 0) { - t_state.host_db_info.app.http_data.last_failure = 0; - t_state.host_db_info.app.http_data.fail_count = 0; - issue_update |= 1; - char addrbuf[INET6_ADDRPORTSTRLEN]; - SMDebug("http", "hostdb update marking IP: %s as up", - ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); - } - - if (t_state.dns_info.srv_lookup_success && t_state.dns_info.srv_app.http_data.last_failure != 0) { - t_state.dns_info.srv_app.http_data.last_failure = 0; - hostDBProcessor.setby_srv(t_state.dns_info.lookup_name, 0, t_state.dns_info.srv_hostname, &t_state.dns_info.srv_app); - SMDebug("http", "hostdb update marking SRV: %s as up", t_state.dns_info.srv_hostname); + if (t_state.dns_info.mark_active_server_alive()) { + if (t_state.dns_info.record->is_srv()) { + SMDebug("http", "[%" PRId64 "] hostdb update marking SRV: %s as up", sm_id, t_state.dns_info.record->name()); + } else { + char addrbuf[INET6_ADDRPORTSTRLEN]; + SMDebug("http", "[%" PRId64 "] hostdb update marking IP: %s as up", sm_id, + ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); + } } } - if (issue_update) { - hostDBProcessor.setby(t_state.current.server->name, strlen(t_state.current.server->name), &t_state.current.server->dst_addr.sa, - &t_state.host_db_info.app); - } - char addrbuf[INET6_ADDRPORTSTRLEN]; SMDebug("http", "server info = %s", ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); return; @@ -4898,7 +4821,7 @@ HttpSM::send_origin_throttled_response() // if the request is to a parent proxy, do not reset // t_state.current.attempts so that another parent or // NextHop may be tried. - if (t_state.current.request_to != HttpTransact::PARENT_PROXY) { + if (t_state.dns_info.looking_up != ResolveInfo::PARENT_PROXY) { t_state.current.attempts = t_state.txn_conf->connect_attempts_max_retries; } t_state.current.state = HttpTransact::OUTBOUND_CONGESTION; @@ -5537,42 +5460,44 @@ HttpSM::do_transform_open() } void -HttpSM::mark_host_failure(HostDBInfo *info, time_t time_down) +HttpSM::mark_host_failure(ResolveInfo *info, ts_time time_down) { char addrbuf[INET6_ADDRPORTSTRLEN]; - if (time_down) { - // Increment the fail_count - ++info->app.http_data.fail_count; - if (info->app.http_data.fail_count >= t_state.txn_conf->connect_attempts_rr_retries) { - if (info->app.http_data.last_failure == 0) { - char *url_str = t_state.hdr_info.client_request.url_string_get(&t_state.arena, nullptr); - int host_len; - const char *host_name_ptr = t_state.unmapped_url.host_get(&host_len); - std::string_view host_name{host_name_ptr, size_t(host_len)}; - ts::bwprint(error_bw_buffer, "CONNECT: {::s} connecting to {} for host='{}' url='{}' marking down", - ts::bwf::Errno(t_state.current.server->connect_result), t_state.current.server->dst_addr, host_name, - ts::bwf::FirstOf(url_str, "")); - Log::error("%s", error_bw_buffer.c_str()); - - if (url_str) { - t_state.arena.str_free(url_str); + if (info->active) { + if (time_down != TS_TIME_ZERO) { + // Increment the fail_count + if (++info->active->fail_count >= t_state.txn_conf->connect_attempts_rr_retries) { + if (info->active) { + if (info->active->last_failure.load() == TS_TIME_ZERO) { + char *url_str = t_state.hdr_info.client_request.url_string_get(&t_state.arena, nullptr); + int host_len; + const char *host_name_ptr = t_state.unmapped_url.host_get(&host_len); + std::string_view host_name{host_name_ptr, size_t(host_len)}; + ts::bwprint(error_bw_buffer,"CONNECT : {::s} connecting to {} for host='{}' url='{}' marking down", + ts::bwf::Errno(t_state.current.server->connect_result), t_state.current.server->dst_addr,host_name, + ts::bwf::FirstOf(url_str, "")); + Log::error("%s", error_bw_buffer.c_str()); + + if (url_str) { + t_state.arena.str_free(url_str); + } + } + info->active->last_failure = time_down; + SMDebug("http", "hostdb update marking IP: %s as down", + ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); + } else { + SMDebug("http", "hostdb increment IP failcount %s to %d", + ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf)), info->active->fail_count.load()); } + } else { // Clear the failure + info->active->fail_count = 0; + info->active->last_failure = time_down; } - info->app.http_data.last_failure = time_down; - SMDebug("http", "hostdb update marking IP: %s as down", - ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); - } else { - SMDebug("http", "hostdb increment IP failcount %s to %d", - ats_ip_nptop(&t_state.current.server->dst_addr.sa, addrbuf, sizeof(addrbuf)), info->app.http_data.fail_count); } - } else { // Clear the failure - info->app.http_data.fail_count = 0; - info->app.http_data.last_failure = time_down; } - #ifdef DEBUG - ink_assert(ink_local_time() + t_state.txn_conf->down_server_timeout > time_down); + ink_assert(std::chrono::system_clock::now() + t_state.txn_conf->down_server_timeout > time_down); #endif } @@ -5839,6 +5764,7 @@ HttpSM::handle_server_setup_error(int event, void *data) } } + [[maybe_unused]] UnixNetVConnection *dbg_vc = nullptr; switch (event) { case VC_EVENT_EOS: t_state.current.state = HttpTransact::CONNECTION_CLOSED; @@ -7589,63 +7515,47 @@ HttpSM::set_next_state() } case HttpTransact::SM_ACTION_DNS_LOOKUP: { - sockaddr const *addr; - - if (t_state.api_server_addr_set) { - /* If the API has set the server address before the OS DNS lookup - * then we can skip the lookup - */ - ip_text_buffer ipb; - SMDebug("dns", "Skipping DNS lookup for API supplied target %s.", - ats_ip_ntop(&t_state.server_info.dst_addr, ipb, sizeof(ipb))); - // this seems wasteful as we will just copy it right back - ats_ip_copy(t_state.host_db_info.ip(), &t_state.server_info.dst_addr); - t_state.dns_info.lookup_success = true; - call_transact_and_set_next_state(nullptr); - break; - } else if (0 == ats_ip_pton(t_state.dns_info.lookup_name, t_state.host_db_info.ip()) && - ats_is_ip_loopback(t_state.host_db_info.ip())) { - // If it's 127.0.0.1 or ::1 don't bother with hostdb - SMDebug("dns", "Skipping DNS lookup for %s because it's loopback", t_state.dns_info.lookup_name); - t_state.dns_info.lookup_success = true; - call_transact_and_set_next_state(nullptr); - break; - } else if (t_state.http_config_param->use_client_target_addr == 2 && !t_state.url_remap_success && - t_state.parent_result.result != PARENT_SPECIFIED && t_state.client_info.is_transparent && - t_state.dns_info.os_addr_style == HttpTransact::DNSLookupInfo::OS_Addr::OS_ADDR_TRY_DEFAULT && - ats_is_ip(addr = ua_txn->get_netvc()->get_local_addr())) { - /* If the connection is client side transparent and the URL - * was not remapped/directed to parent proxy, we can use the - * client destination IP address instead of doing a DNS - * lookup. This is controlled by the 'use_client_target_addr' - * configuration parameter. + if (sockaddr const *addr; t_state.http_config_param->use_client_target_addr == 2 && // no CTA verification + !t_state.url_remap_success && // wasn't remapped + t_state.parent_result.result != PARENT_SPECIFIED && // no parent. + t_state.client_info.is_transparent && // inbound transparent + t_state.dns_info.os_addr_style == ResolveInfo::OS_Addr::TRY_DEFAULT && // haven't tried anything yet. + ats_is_ip(addr = ua_txn->get_netvc()->get_local_addr())) // valid inbound remote address + { + /* If the connection is client side transparent and the URL was not remapped/directed to + * parent proxy, we can use the client destination IP address instead of doing a DNS lookup. + * This is controlled by the 'use_client_target_addr' configuration parameter. */ if (is_debug_tag_set("dns")) { ip_text_buffer ipb; SMDebug("dns", "Skipping DNS lookup for client supplied target %s.", ats_ip_ntop(addr, ipb, sizeof(ipb))); } - ats_ip_copy(t_state.host_db_info.ip(), addr); - t_state.host_db_info.app.http_data.http_version = t_state.hdr_info.client_request.version_get(); - t_state.dns_info.lookup_success = true; - // cache this result so we don't have to unreliably duplicate the - // logic later if the connect fails. - t_state.dns_info.os_addr_style = HttpTransact::DNSLookupInfo::OS_Addr::OS_ADDR_TRY_CLIENT; + t_state.dns_info.set_upstream_address(addr); + + // Make a note the CTA is being used - don't do this case again. + t_state.dns_info.os_addr_style = ResolveInfo::OS_Addr::TRY_CLIENT; + + if (t_state.hdr_info.client_request.version_get() == HTTPVersion(0, 9)) { + t_state.dns_info.http_version = HTTP_0_9; + } else if (t_state.hdr_info.client_request.version_get() == HTTPVersion(1, 0)) { + t_state.dns_info.http_version = HTTP_1_0; + } else { + t_state.dns_info.http_version = HTTP_1_1; + } + call_transact_and_set_next_state(nullptr); break; - } else if (t_state.parent_result.result == PARENT_UNDEFINED && t_state.dns_info.lookup_success) { - // Already set, and we don't have a parent proxy to lookup - ink_assert(ats_is_ip(t_state.host_db_info.ip())); - SMDebug("dns", "Skipping DNS lookup, provided by plugin"); + } else if (t_state.dns_info.looking_up == ResolveInfo::ORIGIN_SERVER && t_state.http_config_param->no_dns_forward_to_parent && + t_state.parent_result.result != PARENT_UNDEFINED) { + t_state.dns_info.resolved_p = true; // seems dangerous - where's the IP address? call_transact_and_set_next_state(nullptr); break; - } else if (t_state.dns_info.looking_up == HttpTransact::ORIGIN_SERVER && t_state.http_config_param->no_dns_forward_to_parent && - t_state.parent_result.result != PARENT_UNDEFINED) { - t_state.dns_info.lookup_success = true; + } else if (t_state.dns_info.resolve_immediate()) { call_transact_and_set_next_state(nullptr); break; } - + // else have to do DNS. HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_hostdb_lookup); // We need to close the previous attempt @@ -7673,7 +7583,7 @@ HttpSM::set_next_state() } } - ink_assert(t_state.dns_info.looking_up != HttpTransact::UNDEFINED_LOOKUP); + ink_assert(t_state.dns_info.looking_up != ResolveInfo::UNDEFINED_LOOKUP); do_hostdb_lookup(); break; } @@ -7858,7 +7768,7 @@ HttpSM::set_next_state() case HttpTransact::SM_ACTION_ORIGIN_SERVER_RR_MARK_DOWN: { HTTP_SM_SET_DEFAULT_HANDLER(&HttpSM::state_mark_os_down); - ink_assert(t_state.dns_info.looking_up == HttpTransact::ORIGIN_SERVER); + ink_assert(t_state.dns_info.looking_up == ResolveInfo::ORIGIN_SERVER); // TODO: This might not be optimal (or perhaps even correct), but it will // effectively mark the host as down. What's odd is that state_mark_os_down @@ -8126,13 +8036,13 @@ HttpSM::redirect_request(const char *arg_redirect_url, const int arg_redirect_le t_state.response_received_time = 0; t_state.next_action = HttpTransact::SM_ACTION_REDIRECT_READ; // we have a new OS and need to have DNS lookup the new OS - t_state.dns_info.lookup_success = false; - t_state.force_dns = false; + t_state.dns_info.resolved_p = false; + t_state.force_dns = false; t_state.server_info.clear(); t_state.parent_info.clear(); // Must reset whether the InkAPI has set the destination address - t_state.api_server_addr_set = false; + // t_state.dns_info.api_addr_set_p = false; if (t_state.txn_conf->cache_http) { t_state.cache_info.object_read = nullptr; diff --git a/proxy/http/HttpSM.h b/proxy/http/HttpSM.h index 0d66cab71b7..5726f4130b9 100644 --- a/proxy/http/HttpSM.h +++ b/proxy/http/HttpSM.h @@ -261,8 +261,8 @@ class HttpSM : public Continuation, public PluginUserArgs // Handles the setting of all state necessary before // calling transact to process the hostdb lookup // A NULL 'r' argument indicates the hostdb lookup failed - void process_hostdb_info(HostDBInfo *r); - void process_srv_info(HostDBInfo *r); + void process_hostdb_info(HostDBRecord *record); + void process_srv_info(HostDBRecord *record); // Called by transact. Synchronous. VConnection *do_transform_open(); @@ -405,6 +405,7 @@ class HttpSM : public Continuation, public PluginUserArgs int state_read_client_request_header(int event, void *data); int state_watch_for_client_abort(int event, void *data); int state_read_push_response_header(int event, void *data); + int state_pre_resolve(int event, void *data); int state_hostdb_lookup(int event, void *data); int state_hostdb_reverse_lookup(int event, void *data); int state_mark_os_down(int event, void *data); @@ -472,7 +473,7 @@ class HttpSM : public Continuation, public PluginUserArgs void handle_server_setup_error(int event, void *data); void handle_http_server_open(); void handle_post_failure(); - void mark_host_failure(HostDBInfo *info, time_t time_down); + void mark_host_failure(ResolveInfo *info, ts_time time_down); void release_server_session(bool serve_from_cache = false); void set_ua_abort(HttpTransact::AbortState_t ua_abort, int event); int write_header_into_buffer(HTTPHdr *h, MIOBuffer *b); diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc index 3dc23eddfb9..5c2cf3f5faf 100644 --- a/proxy/http/HttpTransact.cc +++ b/proxy/http/HttpTransact.cc @@ -368,7 +368,7 @@ HttpTransact::is_response_valid(State *s, HTTPHdr *incoming_response) inline static ParentRetry_t response_is_retryable(HttpTransact::State *s, HTTPStatus response_code) { - if (!HttpTransact::is_response_valid(s, &s->hdr_info.server_response) || s->current.request_to != HttpTransact::PARENT_PROXY) { + if (!HttpTransact::is_response_valid(s, &s->hdr_info.server_response) || s->current.request_to != ResolveInfo::PARENT_PROXY) { return PARENT_RETRY_NONE; } if (s->response_action.handled) { @@ -487,8 +487,8 @@ update_cache_control_information_from_config(HttpTransact::State *s) bool HttpTransact::is_server_negative_cached(State *s) { - if (s->host_db_info.app.http_data.last_failure != 0 && - s->host_db_info.app.http_data.last_failure + s->txn_conf->down_server_timeout > s->client_request_time) { + if (s->dns_info.active && s->dns_info.active->last_fail_time() != TS_TIME_ZERO && + s->dns_info.active->last_fail_time() + s->txn_conf->down_server_timeout > ts_clock::from_time_t(s->client_request_time)) { return true; } else { // Make sure some nasty clock skew has not happened @@ -496,9 +496,10 @@ HttpTransact::is_server_negative_cached(State *s) // future we should tolerate bogus last failure times. This sets // the upper bound to the time that we would ever consider a server // down to 2*down_server_timeout - if (s->client_request_time + s->txn_conf->down_server_timeout < s->host_db_info.app.http_data.last_failure) { - s->host_db_info.app.http_data.last_failure = 0; - s->host_db_info.app.http_data.fail_count = 0; + if (s->dns_info.active && + ts_clock::from_time_t(s->client_request_time) + s->txn_conf->down_server_timeout < s->dns_info.active->last_fail_time()) { + s->dns_info.active->last_failure = TS_TIME_ZERO; + s->dns_info.active->fail_count = 0; ink_assert(!"extreme clock skew"); return true; } @@ -507,8 +508,8 @@ HttpTransact::is_server_negative_cached(State *s) } inline static void -update_current_info(HttpTransact::CurrentInfo *into, HttpTransact::ConnectionAttributes *from, HttpTransact::LookingUp_t who, - int attempts) +update_current_info(HttpTransact::CurrentInfo *into, HttpTransact::ConnectionAttributes *from, + ResolveInfo::UpstreamResolveStyle who, int attempts) { into->request_to = who; into->server = from; @@ -516,7 +517,7 @@ update_current_info(HttpTransact::CurrentInfo *into, HttpTransact::ConnectionAtt } inline static void -update_dns_info(HttpTransact::DNSLookupInfo *dns, HttpTransact::CurrentInfo *from) +update_dns_info(ResolveInfo *dns, HttpTransact::CurrentInfo *from) { dns->looking_up = from->request_to; dns->lookup_name = from->server->name; @@ -558,7 +559,7 @@ is_negative_caching_appropriate(HttpTransact::State *s) } } -inline static HttpTransact::LookingUp_t +inline static ResolveInfo::UpstreamResolveStyle find_server_and_update_current_info(HttpTransact::State *s) { int host_len; @@ -639,16 +640,16 @@ find_server_and_update_current_info(HttpTransact::State *s) switch (s->parent_result.result) { case PARENT_SPECIFIED: s->parent_info.name = s->arena.str_store(s->parent_result.hostname, strlen(s->parent_result.hostname)); - update_current_info(&s->current, &s->parent_info, HttpTransact::PARENT_PROXY, (s->current.attempts)++); + update_current_info(&s->current, &s->parent_info, ResolveInfo::PARENT_PROXY, (s->current.attempts)++); update_dns_info(&s->dns_info, &s->current); - ink_assert(s->dns_info.looking_up == HttpTransact::PARENT_PROXY); + ink_assert(s->dns_info.looking_up == ResolveInfo::PARENT_PROXY); s->next_hop_scheme = URL_WKSIDX_HTTP; - return HttpTransact::PARENT_PROXY; + return ResolveInfo::PARENT_PROXY; case PARENT_FAIL: // No more parents - need to return an error message - s->current.request_to = HttpTransact::HOST_NONE; - return HttpTransact::HOST_NONE; + s->current.request_to = ResolveInfo::HOST_NONE; + return ResolveInfo::HOST_NONE; case PARENT_DIRECT: // if the configuration does not allow the origin to be dns'd @@ -656,15 +657,15 @@ find_server_and_update_current_info(HttpTransact::State *s) if (s->http_config_param->no_dns_forward_to_parent) { Warning("no available parents and the config proxy.config.http.no_dns_just_forward_to_parent, prevents origin lookups."); s->parent_result.result = PARENT_FAIL; - return HttpTransact::HOST_NONE; + return ResolveInfo::HOST_NONE; } /* fall through */ default: - update_current_info(&s->current, &s->server_info, HttpTransact::ORIGIN_SERVER, (s->current.attempts)++); + update_current_info(&s->current, &s->server_info, ResolveInfo::ORIGIN_SERVER, (s->current.attempts)++); update_dns_info(&s->dns_info, &s->current); - ink_assert(s->dns_info.looking_up == HttpTransact::ORIGIN_SERVER); + ink_assert(s->dns_info.looking_up == ResolveInfo::ORIGIN_SERVER); s->next_hop_scheme = s->scheme; - return HttpTransact::ORIGIN_SERVER; + return ResolveInfo::ORIGIN_SERVER; } } @@ -764,7 +765,7 @@ does_method_effect_cache(int method) inline static HttpTransact::StateMachineAction_t how_to_open_connection(HttpTransact::State *s) { - ink_assert((s->pending_work == nullptr) || (s->current.request_to == HttpTransact::PARENT_PROXY)); + ink_assert((s->pending_work == nullptr) || (s->current.request_to == ResolveInfo::PARENT_PROXY)); // Originally we returned which type of server to open // Now, however, we may want to issue a cache @@ -1675,13 +1676,13 @@ HttpTransact::setup_plugin_request_intercept(State *s) s->scheme = s->next_hop_scheme = URL_WKSIDX_HTTP; // Set up a "fake" server entry - update_current_info(&s->current, &s->server_info, HttpTransact::ORIGIN_SERVER, 0); + update_current_info(&s->current, &s->server_info, ResolveInfo::ORIGIN_SERVER, 0); // Also "fake" the info we'd normally get from // hostDB - s->server_info.http_version = HTTP_1_0; - s->server_info.keep_alive = HTTP_NO_KEEPALIVE; - s->host_db_info.app.http_data.http_version = HTTP_1_0; + s->server_info.http_version = HTTP_1_0; + s->server_info.keep_alive = HTTP_NO_KEEPALIVE; + s->server_info.http_version = HTTP_1_0; s->server_info.dst_addr.setToAnyAddr(AF_INET); // must set an address or we can't set the port. s->server_info.dst_addr.network_order_port() = htons(s->hdr_info.client_request.port_get()); // this is the info that matters. @@ -1783,19 +1784,19 @@ HttpTransact::PPDNSLookup(State *s) { TxnDebug("http_trans", "Entering HttpTransact::PPDNSLookup"); - ink_assert(s->dns_info.looking_up == PARENT_PROXY); - if (!s->dns_info.lookup_success) { + ink_assert(s->dns_info.looking_up == ResolveInfo::PARENT_PROXY); + if (!s->dns_info.resolved_p) { // Mark parent as down due to resolving failure markParentDown(s); // DNS lookup of parent failed, find next parent or o.s. - if (find_server_and_update_current_info(s) == HttpTransact::HOST_NONE) { - ink_assert(s->current.request_to == HOST_NONE); + if (find_server_and_update_current_info(s) == ResolveInfo::HOST_NONE) { + ink_assert(s->current.request_to == ResolveInfo::HOST_NONE); handle_parent_died(s); return; } if (!s->current.server->dst_addr.isValid()) { - if (s->current.request_to == PARENT_PROXY) { + if (s->current.request_to == ResolveInfo::PARENT_PROXY) { TRANSACT_RETURN(SM_ACTION_DNS_LOOKUP, PPDNSLookupAPICall); } else if (s->parent_result.result == PARENT_DIRECT && s->http_config_param->no_dns_forward_to_parent != 1) { // We ran out of parents but parent configuration allows us to go to Origin Server directly @@ -1803,16 +1804,16 @@ HttpTransact::PPDNSLookup(State *s) return; } else { // We could be out of parents here if all the parents failed DNS lookup - ink_assert(s->current.request_to == HOST_NONE); + ink_assert(s->current.request_to == ResolveInfo::HOST_NONE); handle_parent_died(s); } return; } } else { // lookup succeeded, open connection to p.p. - ats_ip_copy(&s->parent_info.dst_addr, s->host_db_info.ip()); + ats_ip_copy(&s->parent_info.dst_addr, s->dns_info.addr); s->parent_info.dst_addr.network_order_port() = htons(s->parent_result.port); - get_ka_info_from_host_db(s, &s->parent_info, &s->client_info, &s->host_db_info); + get_ka_info_from_host_db(s, &s->parent_info, &s->client_info, s->dns_info.active); char addrbuf[INET6_ADDRSTRLEN]; TxnDebug("http_trans", "DNS lookup for successful IP: %s", ats_ip_ntop(&s->parent_info.dst_addr.sa, addrbuf, sizeof(addrbuf))); @@ -1835,65 +1836,6 @@ HttpTransact::PPDNSLookup(State *s) s->next_action = how_to_open_connection(s); } -/////////////////////////////////////////////////////////////////////////////// -// -// Name : ReDNSRoundRobin -// Description: Called after we fail to contact part of a round-robin -// robin server set and we found a another ip address. -// -// Details : -// -// -// -// Possible Next States From Here: -// - HttpTransact::ORIGIN_SERVER_RAW_OPEN; -// - HttpTransact::ORIGIN_SERVER_OPEN; -// - HttpTransact::PROXY_INTERNAL_CACHE_NOOP; -// -/////////////////////////////////////////////////////////////////////////////// -void -HttpTransact::ReDNSRoundRobin(State *s) -{ - ink_assert(s->current.server == &s->server_info); - ink_assert(s->current.server->had_connect_fail()); - - if (s->dns_info.lookup_success) { - // We using a new server now so clear the connection - // failure mark - s->current.server->clear_connect_fail(); - - // Our ReDNS of the server succeeded so update the necessary - // information and try again. Need to preserve the current port value if possible. - in_port_t server_port = s->current.server->dst_addr.host_order_port(); - // Temporary check to make sure the port preservation can be depended upon. That should be the case - // because we get here only after trying a connection. Remove for 6.2. - ink_assert(s->current.server->dst_addr.isValid() && 0 != server_port); - - ats_ip_copy(&s->server_info.dst_addr, s->host_db_info.ip()); - s->server_info.dst_addr.network_order_port() = htons(server_port); - ats_ip_copy(&s->request_data.dest_ip, &s->server_info.dst_addr); - get_ka_info_from_host_db(s, &s->server_info, &s->client_info, &s->host_db_info); - - char addrbuf[INET6_ADDRSTRLEN]; - TxnDebug("http_trans", "DNS lookup for O.S. successful IP: %s", - ats_ip_ntop(&s->server_info.dst_addr.sa, addrbuf, sizeof(addrbuf))); - - s->next_action = how_to_open_connection(s); - } else { - // Our ReDNS failed so output the DNS failure error message - // Set to internal server error so later logging will pick up SQUID_LOG_ERR_DNS_FAIL - build_error_response(s, HTTP_STATUS_INTERNAL_SERVER_ERROR, "Cannot find server.", Dns_error_body); - s->cache_info.action = CACHE_DO_NO_ACTION; - s->next_action = SM_ACTION_SEND_ERROR_CACHE_NOOP; - // s->next_action = PROXY_INTERNAL_CACHE_NOOP; - char *url_str = s->hdr_info.client_request.url_string_get(&s->arena, nullptr); - ts::bwprint(error_bw_buffer, "DNS Error: looking up {}", ts::bwf::FirstOf(url_str, "")); - Log::error("%s", error_bw_buffer.c_str()); - } - - return; -} - /////////////////////////////////////////////////////////////////////////////// // Name : OSDNSLookup // Description: called after the DNS lookup of origin server name @@ -1923,29 +1865,22 @@ HttpTransact::ReDNSRoundRobin(State *s) void HttpTransact::OSDNSLookup(State *s) { - ink_assert(s->dns_info.looking_up == ORIGIN_SERVER); + ink_assert(s->dns_info.looking_up == ResolveInfo::UpstreamResolveStyle::ORIGIN_SERVER); TxnDebug("http_trans", "Entering HttpTransact::OSDNSLookup"); - // It's never valid to connect *to* INADDR_ANY, so let's reject the request now. - if (ats_is_ip_any(s->host_db_info.ip())) { - TxnDebug("http_trans", "Invalid request IP: INADDR_ANY"); - build_error_response(s, HTTP_STATUS_BAD_REQUEST, "Bad Destination Address", "request#syntax_error"); - SET_VIA_STRING(VIA_DETAIL_TUNNEL, VIA_DETAIL_TUNNEL_NO_FORWARD); - TRANSACT_RETURN(SM_ACTION_SEND_ERROR_CACHE_NOOP, nullptr); - } - - if (!s->dns_info.lookup_success) { - if (DNSLookupInfo::OS_Addr::OS_ADDR_TRY_HOSTDB == s->dns_info.os_addr_style) { - /* - * Transparent case: We tried to connect to client target address, failed and tried to use a different addr - * No HostDB data, just keep on with the CTA. + if (!s->dns_info.resolved_p) { + if (ResolveInfo::OS_Addr::TRY_HOSTDB == s->dns_info.os_addr_style) { + /* Transparent case: We tried to connect to client target address, failed and tried to use a different addr + * but that failed to resolve therefore keep on with the CTA. */ - s->dns_info.lookup_success = true; - s->dns_info.os_addr_style = DNSLookupInfo::OS_Addr::OS_ADDR_USE_CLIENT; + s->dns_info.addr.assign(s->state_machine->ua_txn->get_netvc()->get_local_addr()); // fetch CTA + s->dns_info.resolved_p = true; + s->dns_info.os_addr_style = ResolveInfo::OS_Addr::USE_CLIENT; TxnDebug("http_seq", "DNS lookup unsuccessful, using client target address"); } else { TxnDebug("http_seq", "DNS Lookup unsuccessful"); + char const *log_msg; // Even with unsuccessful DNS lookup, return stale object from cache if applicable if (is_cache_hit(s->cache_lookup_result) && is_stale_cache_response_returnable(s)) { @@ -1956,10 +1891,16 @@ HttpTransact::OSDNSLookup(State *s) } // output the DNS failure error message SET_VIA_STRING(VIA_DETAIL_TUNNEL, VIA_DETAIL_TUNNEL_NO_FORWARD); - // Set to internal server error so later logging will pick up SQUID_LOG_ERR_DNS_FAIL - build_error_response(s, HTTP_STATUS_INTERNAL_SERVER_ERROR, "Cannot find server.", "connect#dns_failed"); + if (!s->dns_info.record || s->dns_info.record->is_failed()) { + // Set to internal server error so later logging will pick up SQUID_LOG_ERR_DNS_FAIL + build_error_response(s, HTTP_STATUS_INTERNAL_SERVER_ERROR, "Cannot find server.", "connect#dns_failed"); + log_msg = "looking up"; + } else { + build_error_response(s, HTTP_STATUS_INTERNAL_SERVER_ERROR, "No valid server.", "connect#all_dead"); + log_msg = "no valid server"; + } char *url_str = s->hdr_info.client_request.url_string_get(&s->arena, nullptr); - ts::bwprint(error_bw_buffer, "DNS Error: looking up {}", ts::bwf::FirstOf(url_str, "")); + ts::bwprint(error_bw_buffer, "DNS Error: {} {}", log_msg, ts::bwf::FirstOf(url_str, "")); Log::error("%s", error_bw_buffer.c_str()); // s->cache_info.action = CACHE_DO_NO_ACTION; TRANSACT_RETURN(SM_ACTION_SEND_ERROR_CACHE_NOOP, nullptr); @@ -1968,43 +1909,43 @@ HttpTransact::OSDNSLookup(State *s) } // The dns lookup succeeded - ink_assert(s->dns_info.lookup_success); + ink_assert(s->dns_info.resolved_p); TxnDebug("http_seq", "DNS Lookup successful"); + // It's never valid to connect *to* INADDR_ANY, so let's reject the request now. + if (ats_is_ip_any(s->dns_info.addr)) { + TxnDebug("http_trans", "[OSDNSLookup] Invalid request IP: INADDR_ANY"); + build_error_response(s, HTTP_STATUS_BAD_REQUEST, "Bad Destination Address", "request#syntax_error"); + SET_VIA_STRING(VIA_DETAIL_TUNNEL, VIA_DETAIL_TUNNEL_NO_FORWARD); + TRANSACT_RETURN(SM_ACTION_SEND_ERROR_CACHE_NOOP, nullptr); + } + // For the transparent case, nail down the kind of address we are really using - if (DNSLookupInfo::OS_Addr::OS_ADDR_TRY_HOSTDB == s->dns_info.os_addr_style) { + if (ResolveInfo::OS_Addr::TRY_HOSTDB == s->dns_info.os_addr_style) { // We've backed off from a client supplied address and found some // HostDB addresses. We use those if they're different from the CTA. // In all cases we now commit to client or HostDB for our source. - if (s->host_db_info.round_robin) { - HostDBInfo *cta = s->host_db_info.rr()->select_next(&s->current.server->dst_addr.sa); - if (cta) { - // found another addr, lock in host DB. - s->host_db_info = *cta; - s->dns_info.os_addr_style = DNSLookupInfo::OS_Addr::OS_ADDR_USE_HOSTDB; - } else { - // nothing else there, continue with CTA. - s->dns_info.os_addr_style = DNSLookupInfo::OS_Addr::OS_ADDR_USE_CLIENT; - } - } else if (ats_ip_addr_eq(s->host_db_info.ip(), &s->server_info.dst_addr.sa)) { - s->dns_info.os_addr_style = DNSLookupInfo::OS_Addr::OS_ADDR_USE_CLIENT; + if (s->dns_info.set_active(&s->current.server->dst_addr.sa) && s->dns_info.select_next_rr()) { + s->dns_info.os_addr_style = ResolveInfo::OS_Addr::USE_HOSTDB; } else { - s->dns_info.os_addr_style = DNSLookupInfo::OS_Addr::OS_ADDR_USE_HOSTDB; + // nothing else there, continue with CTA. + s->dns_info.set_active(nullptr); + s->dns_info.set_upstream_address(&s->current.server->dst_addr.sa); + s->dns_info.os_addr_style = ResolveInfo::OS_Addr::USE_CLIENT; } } - // Check to see if can fullfill expect requests based on the cached - // update some state variables with hostdb information that has - // been provided. - ats_ip_copy(&s->server_info.dst_addr, s->host_db_info.ip()); + s->server_info.dst_addr.assign(s->dns_info.addr); // If the SRV response has a port number, we should honor it. Otherwise we do the port defined in remap - if (s->dns_info.srv_lookup_success) { + if (s->dns_info.resolved_p && s->dns_info.srv_port) { s->server_info.dst_addr.network_order_port() = htons(s->dns_info.srv_port); - } else if (!s->api_server_addr_set) { + } else if (s->dns_info.os_addr_style == ResolveInfo::OS_Addr::USE_API && 0 != ats_ip_port_cast(s->dns_info.addr)) { + // Nothing - port set via API and already copied over. + } else { s->server_info.dst_addr.network_order_port() = htons(s->hdr_info.client_request.port_get()); // now we can set the port. } ats_ip_copy(&s->request_data.dest_ip, &s->server_info.dst_addr); - get_ka_info_from_host_db(s, &s->server_info, &s->client_info, &s->host_db_info); + get_ka_info_from_host_db(s, &s->server_info, &s->client_info, s->dns_info.active); char addrbuf[INET6_ADDRSTRLEN]; TxnDebug("http_trans", "DNS lookup for O.S. successful IP: %s", @@ -2013,14 +1954,17 @@ HttpTransact::OSDNSLookup(State *s) if (s->redirect_info.redirect_in_process) { // If dns lookup was not successful, the code below will handle the error. RedirectEnabled::Action action = RedirectEnabled::Action::INVALID; - if (true == Machine::instance()->is_self(s->host_db_info.ip())) { + if (true == Machine::instance()->is_self(&s->dns_info.addr.sa)) { action = s->http_config_param->redirect_actions_self_action; + TxnDebug("http_trans", "[OSDNSLookup] Self action - %d.", int(action)); } else { // Make sure the return value from contains is big enough for a void*. intptr_t x{intptr_t(RedirectEnabled::Action::INVALID)}; ink_release_assert(s->http_config_param->redirect_actions_map != nullptr); - ink_release_assert(s->http_config_param->redirect_actions_map->contains(s->host_db_info.ip(), reinterpret_cast(&x))); + ink_release_assert(s->http_config_param->redirect_actions_map->contains(s->dns_info.addr, reinterpret_cast(&x))); action = static_cast(x); + TxnDebug("http_trans", "[OSDNSLookup] Mapped action - %d for family %d.", int(action), + int(s->dns_info.active->data.ip.family())); } if (action == RedirectEnabled::Action::FOLLOW) { @@ -2055,10 +1999,11 @@ HttpTransact::OSDNSLookup(State *s) // After SM_ACTION_DNS_LOOKUP, goto the saved action/state ORIGIN_SERVER_(RAW_)OPEN. // Should we skip the StartAccessControl()? why? - if (DNSLookupInfo::OS_Addr::OS_ADDR_USE_CLIENT == s->dns_info.os_addr_style || - DNSLookupInfo::OS_Addr::OS_ADDR_USE_HOSTDB == s->dns_info.os_addr_style) { - // we've come back after already trying the server to get a better address - // and finished with all backtracking - return to trying the server. + if (ResolveInfo::OS_Addr::USE_CLIENT == s->dns_info.os_addr_style || + ResolveInfo::OS_Addr::USE_HOSTDB == s->dns_info.os_addr_style) { + // we've come back after already trying the server to get a better address, + // or we're locked on a plugin supplied address. + // therefore no more backtracking - return to trying the server. TRANSACT_RETURN(how_to_open_connection(s), HttpTransact::HandleResponse); } else if (s->dns_info.lookup_name[0] <= '9' && s->dns_info.lookup_name[0] >= '0' && s->parent_params->parent_table->hostMatch && !s->http_config_param->no_dns_forward_to_parent) { @@ -2233,14 +2178,14 @@ HttpTransact::LookupSkipOpenServer(State *s) // to a parent proxy or to the origin server. find_server_and_update_current_info(s); - if (s->current.request_to == PARENT_PROXY) { + if (s->current.request_to == ResolveInfo::PARENT_PROXY) { TRANSACT_RETURN(SM_ACTION_DNS_LOOKUP, PPDNSLookupAPICall); } else if (s->parent_result.result == PARENT_FAIL) { handle_parent_died(s); return; } - ink_assert(s->current.request_to == ORIGIN_SERVER); + ink_assert(s->current.request_to == ResolveInfo::ORIGIN_SERVER); // ink_assert(s->current.server->ip != 0); build_request(s, &s->hdr_info.client_request, &s->hdr_info.server_request, s->current.server->http_version); @@ -2892,18 +2837,18 @@ HttpTransact::HandleCacheOpenReadHit(State *s) // scheme & 2) If we skip down parents, every page // we serve is potentially stale // - if (s->current.request_to == ORIGIN_SERVER && is_server_negative_cached(s) && response_returnable == true && + if (s->current.request_to == ResolveInfo::ORIGIN_SERVER && is_server_negative_cached(s) && response_returnable == true && is_stale_cache_response_returnable(s) == true) { server_up = false; - update_current_info(&s->current, nullptr, UNDEFINED_LOOKUP, 0); + update_current_info(&s->current, nullptr, ResolveInfo::UNDEFINED_LOOKUP, 0); TxnDebug("http_trans", "CacheOpenReadHit - server_down, returning stale document"); } // a parent lookup could come back as PARENT_FAIL if in parent.config, go_direct == false and // there are no available parents (all down). - else if (s->current.request_to == HOST_NONE && s->parent_result.result == PARENT_FAIL) { + else if (s->current.request_to == ResolveInfo::HOST_NONE && s->parent_result.result == PARENT_FAIL) { if (response_returnable == true && is_stale_cache_response_returnable(s) == true) { server_up = false; - update_current_info(&s->current, nullptr, UNDEFINED_LOOKUP, 0); + update_current_info(&s->current, nullptr, ResolveInfo::UNDEFINED_LOOKUP, 0); TxnDebug("http_trans", "CacheOpenReadHit - server_down, returning stale document"); } else { handle_parent_died(s); @@ -2926,14 +2871,14 @@ HttpTransact::HandleCacheOpenReadHit(State *s) // through. The request will fail because of the // missing ip but we won't take down the system // - if (s->current.request_to == PARENT_PROXY) { + if (s->current.request_to == ResolveInfo::PARENT_PROXY) { // Set ourselves up to handle pending revalidate issues // after the PP DNS lookup ink_assert(s->pending_work == nullptr); s->pending_work = issue_revalidate; TRANSACT_RETURN(SM_ACTION_DNS_LOOKUP, PPDNSLookupAPICall); - } else if (s->current.request_to == ORIGIN_SERVER) { + } else if (s->current.request_to == ResolveInfo::ORIGIN_SERVER) { return CallOSDNSLookup(s); } else { handle_parent_died(s); @@ -3352,12 +3297,12 @@ HttpTransact::HandleCacheOpenReadMiss(State *s) return; } if (!s->current.server->dst_addr.isValid()) { - ink_release_assert(s->parent_result.result == PARENT_DIRECT || s->current.request_to == PARENT_PROXY || + ink_release_assert(s->parent_result.result == PARENT_DIRECT || s->current.request_to == ResolveInfo::PARENT_PROXY || s->http_config_param->no_dns_forward_to_parent != 0); if (s->parent_result.result == PARENT_DIRECT && s->http_config_param->no_dns_forward_to_parent != 1) { return CallOSDNSLookup(s); } - if (s->current.request_to == PARENT_PROXY) { + if (s->current.request_to == ResolveInfo::PARENT_PROXY) { TRANSACT_RETURN(SM_ACTION_DNS_LOOKUP, HttpTransact::PPDNSLookupAPICall); } else { handle_parent_died(s); @@ -3466,7 +3411,7 @@ HttpTransact::HandleResponse(State *s) HTTP_INCREMENT_DYN_STAT(http_incoming_responses_stat); - ink_release_assert(s->current.request_to != UNDEFINED_LOOKUP); + ink_release_assert(s->current.request_to != ResolveInfo::UNDEFINED_LOOKUP); if (s->cache_info.action != CACHE_DO_WRITE) { ink_release_assert(s->cache_info.action != CACHE_DO_LOOKUP); ink_release_assert(s->cache_info.action != CACHE_DO_SERVE); @@ -3483,10 +3428,10 @@ HttpTransact::HandleResponse(State *s) } switch (s->current.request_to) { - case PARENT_PROXY: + case ResolveInfo::PARENT_PROXY: handle_response_from_parent(s); break; - case ORIGIN_SERVER: + case ResolveInfo::ORIGIN_SERVER: handle_response_from_server(s); break; default: @@ -3610,7 +3555,7 @@ HttpTransact::HandleStatPage(State *s) void HttpTransact::handle_response_from_parent(State *s) { - LookingUp_t next_lookup = UNDEFINED_LOOKUP; + auto next_lookup = ResolveInfo::UNDEFINED_LOOKUP; TxnDebug("http_trans", "(hrfp)"); HTTP_RELEASE_ASSERT(s->current.server == &s->parent_info); @@ -3717,7 +3662,7 @@ HttpTransact::handle_response_from_parent(State *s) markParentDown(s); } s->parent_result.result = PARENT_FAIL; - next_lookup = HOST_NONE; + next_lookup = ResolveInfo::HOST_NONE; } break; } @@ -3725,15 +3670,15 @@ HttpTransact::handle_response_from_parent(State *s) // We have either tried to find a new parent or failed over to the // origin server switch (next_lookup) { - case PARENT_PROXY: - ink_assert(s->current.request_to == PARENT_PROXY); + case ResolveInfo::PARENT_PROXY: + ink_assert(s->current.request_to == ResolveInfo::PARENT_PROXY); TRANSACT_RETURN(SM_ACTION_DNS_LOOKUP, PPDNSLookupAPICall); break; - case ORIGIN_SERVER: + case ResolveInfo::ORIGIN_SERVER: // Next lookup is Origin Server, try DNS for Origin Server return CallOSDNSLookup(s); break; - case HOST_NONE: + case ResolveInfo::HOST_NONE: // Check if content can be served from cache s->current.request_to = PARENT_PROXY; handle_server_connection_not_open(s); @@ -3809,38 +3754,25 @@ HttpTransact::handle_response_from_server(State *s) if (is_request_retryable(s) && s->current.attempts < max_connect_retries) { // If this is a round robin DNS entry & we're tried configured // number of times, we should try another node - if (DNSLookupInfo::OS_Addr::OS_ADDR_TRY_CLIENT == s->dns_info.os_addr_style) { - // attempt was based on client supplied server address. Try again - // using HostDB. + if (ResolveInfo::OS_Addr::TRY_CLIENT == s->dns_info.os_addr_style) { + // attempt was based on client supplied server address. Try again using HostDB. // Allow DNS attempt - s->dns_info.lookup_success = false; + s->dns_info.resolved_p = false; // See if we can get data from HostDB for this. - s->dns_info.os_addr_style = DNSLookupInfo::OS_Addr::OS_ADDR_TRY_HOSTDB; + s->dns_info.os_addr_style = ResolveInfo::OS_Addr::TRY_HOSTDB; // Force host resolution to have the same family as the client. // Because this is a transparent connection, we can't switch address // families - that is locked in by the client source address. ats_force_order_by_family(&s->current.server->dst_addr.sa, s->my_txn_conf().host_res_data.order); return CallOSDNSLookup(s); - } else if ((s->dns_info.srv_lookup_success || s->host_db_info.is_rr_elt()) && - (s->txn_conf->connect_attempts_rr_retries > 0) && - ((s->current.attempts + 1) % s->txn_conf->connect_attempts_rr_retries == 0)) { - delete_server_rr_entry(s, max_connect_retries); - return; } else { + if ((s->txn_conf->connect_attempts_rr_retries > 0) && + ((s->current.attempts + 1) % s->txn_conf->connect_attempts_rr_retries == 0)) { + s->dns_info.select_next_rr(); + } retry_server_connection_not_open(s, s->current.state, max_connect_retries); TxnDebug("http_trans", "Error. Retrying..."); s->next_action = how_to_open_connection(s); - - if (s->api_server_addr_set) { - // If the plugin set a server address, back up to the OS_DNS hook - // to let it try another one. Force OS_ADDR_USE_CLIENT so that - // in OSDNSLoopkup, we back up to how_to_open_connections which - // will tell HttpSM to connect the origin server. - - s->dns_info.os_addr_style = DNSLookupInfo::OS_Addr::OS_ADDR_USE_CLIENT; - TRANSACT_RETURN(SM_ACTION_API_OS_DNS, OSDNSLookup); - } - return; } } else { error_log_connection_failure(s, s->current.state); @@ -3863,35 +3795,6 @@ HttpTransact::handle_response_from_server(State *s) return; } -/////////////////////////////////////////////////////////////////////////////// -// Name : delete_server_rr_entry -// Description: -// -// Details : -// -// connection to server failed mark down the server round robin entry -// -// -// Possible Next States From Here: -// -/////////////////////////////////////////////////////////////////////////////// -void -HttpTransact::delete_server_rr_entry(State *s, int max_retries) -{ - char addrbuf[INET6_ADDRSTRLEN]; - - TxnDebug("http_trans", "[%d] failed to connect to %s", s->current.attempts, - ats_ip_ntop(&s->current.server->dst_addr.sa, addrbuf, sizeof(addrbuf))); - TxnDebug("http_trans", "marking rr entry down and finding next one"); - ink_assert(s->current.server->had_connect_fail()); - ink_assert(s->current.request_to == ORIGIN_SERVER); - ink_assert(s->current.server == &s->server_info); - update_dns_info(&s->dns_info, &s->current); - s->current.attempts++; - TxnDebug("http_trans", "attempts now: %d, max: %d", s->current.attempts, max_retries); - TRANSACT_RETURN(SM_ACTION_ORIGIN_SERVER_RR_MARK_DOWN, ReDNSRoundRobin); -} - void HttpTransact::error_log_connection_failure(State *s, ServerState_t conn_state) { @@ -4061,7 +3964,7 @@ HttpTransact::handle_forward_server_connection_open(State *s) ink_release_assert(s->current.state == CONNECTION_ALIVE); HTTPVersion real_version = s->state_machine->get_server_version(s->hdr_info.server_response); - if (real_version != s->host_db_info.app.http_data.http_version) { + if (real_version != s->dns_info.http_version) { // Need to update the hostdb s->updated_server_version = real_version; TxnDebug("http_trans", "Update hostdb history of server HTTP version 0x%x", s->updated_server_version.get_flat_version()); @@ -5291,22 +5194,22 @@ HttpTransact::get_ka_info_from_host_db(State *s, ConnectionAttributes *server_in break; } - if (force_http11 == true || (http11_if_hostdb == true && host_db_info->app.http_data.http_version == HTTP_1_1)) { + if (force_http11 == true || (http11_if_hostdb == true && host_db_info->http_version == HTTP_1_1)) { server_info->http_version = HTTP_1_1; server_info->keep_alive = HTTP_KEEPALIVE; - } else if (host_db_info->app.http_data.http_version == HTTP_1_0) { + } else if (host_db_info->http_version == HTTP_1_0) { server_info->http_version = HTTP_1_0; server_info->keep_alive = HTTP_KEEPALIVE; - } else if (host_db_info->app.http_data.http_version == HTTP_0_9) { + } else if (host_db_info->http_version == HTTP_0_9) { server_info->http_version = HTTP_0_9; server_info->keep_alive = HTTP_NO_KEEPALIVE; } else { ////////////////////////////////////////////// // not set yet for this host. set defaults. // ////////////////////////////////////////////// - server_info->http_version = HTTP_1_0; - server_info->keep_alive = HTTP_KEEPALIVE; - host_db_info->app.http_data.http_version = HTTP_1_0; + server_info->http_version = HTTP_1_0; + server_info->keep_alive = HTTP_KEEPALIVE; + host_db_info->http_version = HTTP_1_0; } ///////////////////////////// @@ -5852,7 +5755,7 @@ HttpTransact::initialize_state_variables_from_request(State *s, HTTPHdr *obsolet // the expanded host for cache lookup, and // // the host ip for reverse proxy. // ///////////////////////////////////////////// - s->dns_info.looking_up = ORIGIN_SERVER; + s->dns_info.looking_up = ResolveInfo::ORIGIN_SERVER; s->dns_info.lookup_name = s->server_info.name; } @@ -6248,7 +6151,7 @@ HttpTransact::is_response_cacheable(State *s, HTTPHdr *request, HTTPHdr *respons // host addresses, do not allow cache. This may cause DNS cache poisoning // of other trafficserver clients. The flag is set in the // process_host_db_info method - if (!s->dns_info.lookup_validated && s->client_info.is_transparent) { + if (!s->dns_info.cta_validated_p && s->client_info.is_transparent) { TxnDebug("http_trans", "Lookup not validated. Possible DNS cache poison. Don't cache"); return false; } @@ -6638,7 +6541,7 @@ HttpTransact::will_this_request_self_loop(State *s) //////////////////////////////////////// // check if we are about to self loop // //////////////////////////////////////// - if (s->dns_info.lookup_success) { + if (s->dns_info.active) { TxnDebug("http_transact", "max_proxy_cycles = %d", max_proxy_cycles); if (max_proxy_cycles == 0) { in_port_t dst_port = s->hdr_info.client_request.url_get()->port_get(); // going to this port. @@ -6646,13 +6549,13 @@ HttpTransact::will_this_request_self_loop(State *s) // It's a loop if connecting to the same port as it already connected to the proxy and // it's a proxy address or the same address it already connected to. TxnDebug("http_transact", "dst_port = %d local_port = %d", dst_port, local_port); - if (dst_port == local_port && (ats_ip_addr_eq(s->host_db_info.ip(), &Machine::instance()->ip.sa) || - ats_ip_addr_eq(s->host_db_info.ip(), s->client_info.dst_addr))) { + if (dst_port == local_port && ((s->dns_info.active->data.ip == &Machine::instance()->ip.sa) || + (s->dns_info.active->data.ip == s->client_info.dst_addr))) { switch (s->dns_info.looking_up) { - case ORIGIN_SERVER: + case ResolveInfo::ORIGIN_SERVER: TxnDebug("http_transact", "host ip and port same as local ip and port - bailing"); break; - case PARENT_PROXY: + case ResolveInfo::PARENT_PROXY: TxnDebug("http_transact", "parent proxy ip and port same as local ip and port - bailing"); break; default: @@ -6883,7 +6786,7 @@ HttpTransact::handle_request_keep_alive_headers(State *s, HTTPVersion ver, HTTPH case KA_CONNECTION: ink_assert(s->current.server->keep_alive != HTTP_NO_KEEPALIVE); if (ver == HTTP_1_0) { - if (s->current.request_to == PARENT_PROXY && parent_is_proxy(s)) { + if (s->current.request_to == ResolveInfo::PARENT_PROXY && parent_is_proxy(s)) { heads->value_set(MIME_FIELD_PROXY_CONNECTION, MIME_LEN_PROXY_CONNECTION, "keep-alive", 10); } else { heads->value_set(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION, "keep-alive", 10); @@ -6897,7 +6800,7 @@ HttpTransact::handle_request_keep_alive_headers(State *s, HTTPVersion ver, HTTPH if (s->current.server->keep_alive != HTTP_NO_KEEPALIVE || (ver == HTTP_1_1)) { /* Had keep-alive */ s->current.server->keep_alive = HTTP_NO_KEEPALIVE; - if (s->current.request_to == PARENT_PROXY && parent_is_proxy(s)) { + if (s->current.request_to == ResolveInfo::PARENT_PROXY && parent_is_proxy(s)) { heads->value_set(MIME_FIELD_PROXY_CONNECTION, MIME_LEN_PROXY_CONNECTION, "close", 5); } else { ProxyTransaction *svr = s->state_machine->get_server_txn(); @@ -7809,7 +7712,7 @@ HttpTransact::build_request(State *s, HTTPHdr *base_request, HTTPHdr *outgoing_r if (outgoing_request->method_get_wksidx() == HTTP_WKSIDX_CONNECT) { // CONNECT method requires a target in the URL, so always force it from the Host header. outgoing_request->set_url_target_from_host_field(); - } else if (s->current.request_to == PARENT_PROXY && parent_is_proxy(s)) { + } else if (s->current.request_to == ResolveInfo::PARENT_PROXY && parent_is_proxy(s)) { // If we have a parent proxy set the URL target field. if (!outgoing_request->is_target_in_url()) { TxnDebug("http_trans", "adding target to URL for parent proxy"); @@ -8836,7 +8739,7 @@ HttpTransact::update_size_and_time_stats(State *s, ink_hrtime total_time, ink_hr HTTP_SUM_DYN_STAT(http_user_agent_response_document_total_size_stat, user_agent_response_body_size); // proxy stats - if (s->current.request_to == HttpTransact::PARENT_PROXY) { + if (s->current.request_to == ResolveInfo::PARENT_PROXY) { HTTP_SUM_DYN_STAT(http_parent_proxy_request_total_bytes_stat, origin_server_request_header_size + origin_server_request_body_size); HTTP_SUM_DYN_STAT(http_parent_proxy_response_total_bytes_stat, diff --git a/proxy/http/HttpTransact.h b/proxy/http/HttpTransact.h index 7e7fc71ea20..e9700f83180 100644 --- a/proxy/http/HttpTransact.h +++ b/proxy/http/HttpTransact.h @@ -282,14 +282,6 @@ class HttpTransact HTTP_TRANSACT_MAGIC_SEPARATOR = 0x12345678 }; - enum LookingUp_t { - ORIGIN_SERVER, - UNDEFINED_LOOKUP, - PARENT_PROXY, - INCOMING_ROUTER, - HOST_NONE, - }; - enum ProxyMode_t { UNDEFINED_MODE, GENERIC_PROXY, @@ -574,57 +566,19 @@ class HttpTransact }; typedef struct _CurrentInfo { - ProxyMode_t mode = UNDEFINED_MODE; - LookingUp_t request_to = UNDEFINED_LOOKUP; - ConnectionAttributes *server = nullptr; - ink_time_t now = 0; - ServerState_t state = STATE_UNDEFINED; - unsigned attempts = 0; - unsigned simple_retry_attempts = 0; - unsigned unavailable_server_retry_attempts = 0; - ParentRetry_t retry_type = PARENT_RETRY_NONE; + ProxyMode_t mode = UNDEFINED_MODE; + ResolveInfo::UpstreamResolveStyle request_to = ResolveInfo::UNDEFINED_LOOKUP; + ConnectionAttributes *server = nullptr; + ink_time_t now = 0; + ServerState_t state = STATE_UNDEFINED; + unsigned attempts = 0; + unsigned simple_retry_attempts = 0; + unsigned unavailable_server_retry_attempts = 0; + ParentRetry_t retry_type = PARENT_RETRY_NONE; _CurrentInfo() {} } CurrentInfo; - typedef struct _DNSLookupInfo { - /** Origin server address source selection. - - If config says to use CTA (client target addr) state is - OS_ADDR_TRY_CLIENT, otherwise it remains the default. If the - connect fails then we switch to a USE. We go to USE_HOSTDB if - (1) the HostDB lookup is successful and (2) some address other - than the CTA is available to try. Otherwise we keep retrying - on the CTA (USE_CLIENT) up to the max retry value. In essence - we try to treat the CTA as if it were another RR value in the - HostDB record. - */ - enum class OS_Addr { - OS_ADDR_TRY_DEFAULT, ///< Initial state, use what config says. - OS_ADDR_TRY_HOSTDB, ///< Try HostDB data. - OS_ADDR_TRY_CLIENT, ///< Try client target addr. - OS_ADDR_USE_HOSTDB, ///< Force use of HostDB target address. - OS_ADDR_USE_CLIENT ///< Use client target addr, no fallback. - }; - - OS_Addr os_addr_style = OS_Addr::OS_ADDR_TRY_DEFAULT; - - bool lookup_success = false; - char *lookup_name = nullptr; - char srv_hostname[MAXDNAME] = {0}; - LookingUp_t looking_up = UNDEFINED_LOOKUP; - bool srv_lookup_success = false; - short srv_port = 0; - HostDBApplicationInfo srv_app; - - /*** Set to true by default. If use_client_target_address is set - * to 1, this value will be set to false if the client address is - * not in the DNS pool */ - bool lookup_validated = true; - - _DNSLookupInfo() {} - } DNSLookupInfo; - // Conversion handling for DNS host resolution type. static const MgmtConverter HOST_RES_CONV; @@ -672,7 +626,7 @@ class HttpTransact HttpConfigParams *http_config_param = nullptr; CacheLookupInfo cache_info; - DNSLookupInfo dns_info; + ResolveInfo dns_info; RedirectInfo redirect_info; OutboundConnTrack::TxnState outbound_conn_track_state; HTTPVersion updated_server_version = HTTP_INVALID; @@ -724,8 +678,6 @@ class HttpTransact int orig_scheme = scheme; // pre-mapped scheme int method = 0; int cause_of_death_errno = -UNKNOWN_INTERNAL_ERROR; // in - Ptr hostdb_entry; // Pointer to the entry we are referencing in hostdb-- to keep our ref - HostDBInfo host_db_info; // in ink_time_t client_request_time = UNDEFINED_TIME; // internal ink_time_t request_sent_time = UNDEFINED_TIME; // internal @@ -774,7 +726,6 @@ class HttpTransact bool api_server_request_body_set = false; bool api_req_cacheable = false; bool api_resp_cacheable = false; - bool api_server_addr_set = false; UpdateCachedObject_t api_update_cached_object = UPDATE_CACHED_OBJECT_NONE; StateMachineAction_t saved_update_next_action = SM_ACTION_UNDEFINED; CacheAction_t saved_update_cache_action = CACHE_DO_UNDEFINED; @@ -822,6 +773,7 @@ class HttpTransact init() { parent_params = ParentConfig::acquire(); + new (&dns_info) decltype(dns_info); // reset to default state. } // Constructor @@ -848,7 +800,8 @@ class HttpTransact via_string[VIA_DETAIL_SERVER_DESCRIPTOR] = VIA_DETAIL_SERVER_DESCRIPTOR_STRING; via_string[MAX_VIA_INDICES] = '\0'; - memset((void *)&host_db_info, 0, sizeof(host_db_info)); + // memset(user_args, 0, sizeof(user_args)); + // memset((void *)&host_db_info, 0, sizeof(host_db_info)); } void @@ -878,7 +831,7 @@ class HttpTransact url_map.clear(); arena.reset(); unmapped_url.clear(); - hostdb_entry.clear(); + dns_info.~ResolveInfo(); outbound_conn_track_state.clear(); delete[] ranges; @@ -922,6 +875,7 @@ class HttpTransact if (e != EIO) { this->cause_of_death_errno = e; } + Debug("http", "Setting upstream connection failure %d to %d", e, this->current.server->connect_result); } private: @@ -953,7 +907,6 @@ class HttpTransact static void CallOSDNSLookup(State *s); static void OSDNSLookup(State *s); - static void ReDNSRoundRobin(State *s); static void PPDNSLookup(State *s); static void PPDNSLookupAPICall(State *s); static void OriginServerRawOpen(State *s); diff --git a/proxy/http/remap/unit-tests/nexthop_test_stubs.cc b/proxy/http/remap/unit-tests/nexthop_test_stubs.cc index b72183b21a8..17cfdaec05f 100644 --- a/proxy/http/remap/unit-tests/nexthop_test_stubs.cc +++ b/proxy/http/remap/unit-tests/nexthop_test_stubs.cc @@ -86,7 +86,6 @@ br_destroy(HttpSM &sm) } delete h->hdr; delete h->api_info; - ats_free(h->hostname_str); } void diff --git a/src/traffic_server/InkAPI.cc b/src/traffic_server/InkAPI.cc index 8743e170141..1b19095e7b1 100644 --- a/src/traffic_server/InkAPI.cc +++ b/src/traffic_server/InkAPI.cc @@ -5893,12 +5893,11 @@ TSHttpTxnServerAddrSet(TSHttpTxn txnp, struct sockaddr const *addr) sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS); HttpSM *sm = reinterpret_cast(txnp); - if (ats_ip_copy(&sm->t_state.server_info.dst_addr.sa, addr)) { - sm->t_state.api_server_addr_set = true; + if (sm->t_state.dns_info.set_upstream_address(addr)) { + sm->t_state.dns_info.os_addr_style = ResolveInfo::OS_Addr::USE_API; return TS_SUCCESS; - } else { - return TS_ERROR; } + return TS_ERROR; } void @@ -7595,6 +7594,30 @@ TSNetAcceptNamedProtocol(TSCont contp, const char *protocol) } /* DNS Lookups */ +/// Context structure for the lookup callback to the plugin. +struct TSResolveInfo { + IpEndpoint addr; ///< Lookup result. + HostDBRecord *record = nullptr; ///< Record for the FQDN. +}; + +int +TSHostLookupTrampoline(TSCont contp, TSEvent ev, void *data) +{ + auto c = reinterpret_cast(contp); + // Set up the local context. + TSResolveInfo ri; + ri.record = static_cast(data); + if (ri.record) { + ri.record->rr_info()[0].data.ip.toSockAddr(ri.addr); + } + auto *target = reinterpret_cast(c->mdata); + // Deliver the message. + target->handleEvent(ev, &ri); + // Cleanup. + c->destroy(); + return TS_SUCCESS; +}; + TSAction TSHostLookup(TSCont contp, const char *hostname, size_t namelen) { @@ -7604,16 +7627,23 @@ TSHostLookup(TSCont contp, const char *hostname, size_t namelen) FORCE_PLUGIN_SCOPED_MUTEX(contp); - INKContInternal *i = (INKContInternal *)contp; - return (TSAction)hostDBProcessor.getbyname_re(i, hostname, namelen); + // There is no place to store the actual sockaddr to which a pointer should be returned. + // therefore an intermediate continuation is created to intercept the reply from HostDB. + // Its handler can create the required sockaddr context on the stack and then forward + // the event to the plugin continuation. The sockaddr cannot be placed in the HostDB + // record because that is a shared object. + auto bouncer = INKContAllocator.alloc(); + bouncer->init(&TSHostLookupTrampoline, reinterpret_cast(reinterpret_cast(contp)->mutex.get())); + bouncer->mdata = contp; + return (TSAction)hostDBProcessor.getbyname_re(bouncer, hostname, namelen); } sockaddr const * TSHostLookupResultAddrGet(TSHostLookupResult lookup_result) { sdk_assert(sdk_sanity_check_hostlookup_structure(lookup_result) == TS_SUCCESS); - HostDBInfo *di = reinterpret_cast(lookup_result); - return di->ip(); + auto ri{reinterpret_cast(lookup_result)}; + return ri->addr.isValid() ? &ri->addr.sa : nullptr; } /* @@ -8662,6 +8692,9 @@ _memberp_to_generic(MgmtFloat *ptr, MgmtConverter const *&conv) -> typename std: static void * _conf_to_memberp(TSOverridableConfigKey conf, OverridableHttpConfigParams *overridableHttpConfig, MgmtConverter const *&conv) { + // External converters. + extern MgmtConverter const &HostDBDownServerCacheTimeConv; + void *ret = nullptr; conv = nullptr; @@ -8820,7 +8853,8 @@ _conf_to_memberp(TSOverridableConfigKey conf, OverridableHttpConfigParams *overr ret = _memberp_to_generic(&overridableHttpConfig->post_connect_attempts_timeout, conv); break; case TS_CONFIG_HTTP_DOWN_SERVER_CACHE_TIME: - ret = _memberp_to_generic(&overridableHttpConfig->down_server_timeout, conv); + conv = &HostDBDownServerCacheTimeConv; + ret = &overridableHttpConfig->down_server_timeout; break; case TS_CONFIG_HTTP_DOWN_SERVER_ABORT_THRESHOLD: ret = _memberp_to_generic(&overridableHttpConfig->client_abort_threshold, conv); diff --git a/src/traffic_server/InkAPITest.cc b/src/traffic_server/InkAPITest.cc index f6b07008292..96526d9fa4f 100644 --- a/src/traffic_server/InkAPITest.cc +++ b/src/traffic_server/InkAPITest.cc @@ -8512,14 +8512,14 @@ EXCLUSIVE_REGRESSION_TEST(SDK_API_TSHttpConnectIntercept)(RegressionTest *test, /* ip and log do not matter as it is used for logging only */ sockaddr_in addr; ats_ip4_set(&addr, 1, 1); - data->vc = TSHttpConnect(ats_ip_sa_cast(&addr)); + data->vc = TSHttpConnectWithPluginId(ats_ip_sa_cast(&addr), "TSHttpConnectIntercept", 1); if (TSVConnClosedGet(data->vc)) { SDK_RPRINT(data->test, "TSHttpConnect", "TestCase 1", TC_FAIL, "Connect reported as closed immediately after open"); } synclient_txn_send_request_to_vc(data->browser, data->request, data->vc); /* Wait until transaction is done */ - TSContScheduleOnPool(cont_test, 25, TS_THREAD_POOL_NET); + TSContScheduleOnPool(cont_test, 100, TS_THREAD_POOL_NET); return; } @@ -8558,12 +8558,12 @@ EXCLUSIVE_REGRESSION_TEST(SDK_API_TSHttpConnectServerIntercept)(RegressionTest * /* ip and log do not matter as it is used for logging only */ sockaddr_in addr; ats_ip4_set(&addr, 2, 2); - data->vc = TSHttpConnect(ats_ip_sa_cast(&addr)); + data->vc = TSHttpConnectWithPluginId(ats_ip_sa_cast(&addr), "TSHttpConnectServerIntercept", 1); synclient_txn_send_request_to_vc(data->browser, data->request, data->vc); /* Wait until transaction is done */ - TSContScheduleOnPool(cont_test, 25, TS_THREAD_POOL_NET); + TSContScheduleOnPool(cont_test, 100, TS_THREAD_POOL_NET); return; } diff --git a/src/tscore/unit_tests/test_BufferWriterFormat.cc b/src/tscore/unit_tests/test_BufferWriterFormat.cc index 66004f97db7..76d97080e3d 100644 --- a/src/tscore/unit_tests/test_BufferWriterFormat.cc +++ b/src/tscore/unit_tests/test_BufferWriterFormat.cc @@ -284,9 +284,11 @@ TEST_CASE("bwstring", "[bwprint][bwstring]") ts::bwprint(s, fmt, 99999, text); REQUIRE(s == "99999 -- e99a18c428cb38d5f260853678922e03"); + REQUIRE(strlen(s.c_str()) == text.size() + 9); ts::bwprint(s, "{} .. |{:,20}|", 32767, text); REQUIRE(s == "32767 .. |e99a18c428cb38d5f260|"); + REQUIRE(strlen(s.c_str()) == 31); ts::LocalBufferWriter<128> bw; char buff[128]; diff --git a/tests/gold_tests/next_hop/strategies_ch2/strategies_ch2.test.py b/tests/gold_tests/next_hop/strategies_ch2/strategies_ch2.test.py index 9d7f9765298..0ad0c49868d 100644 --- a/tests/gold_tests/next_hop/strategies_ch2/strategies_ch2.test.py +++ b/tests/gold_tests/next_hop/strategies_ch2/strategies_ch2.test.py @@ -74,6 +74,7 @@ 'proxy.config.http.uncacheable_requests_bypass_parent': 0, 'proxy.config.http.no_dns_just_forward_to_parent': 1, 'proxy.config.http.parent_proxy.mark_down_hostdb': 0, + 'proxy.config.http.down_server.cache_time': 1, 'proxy.config.http.parent_proxy.self_detect': 0, }) @@ -90,6 +91,7 @@ # The health check URL does not seem to be used currently. # s.AddLine(f" health_check_url: http://next_hop{i}:{ts_nh[i].Variables.port}") s.AddLine(f" weight: 1.0") + s.AddLines([ "strategies:", " - strategy: the-strategy", diff --git a/tests/gold_tests/tls/tls_verify_override_base.test.py b/tests/gold_tests/tls/tls_verify_override_base.test.py index 0495ec54b78..00027f230b9 100644 --- a/tests/gold_tests/tls/tls_verify_override_base.test.py +++ b/tests/gold_tests/tls/tls_verify_override_base.test.py @@ -113,7 +113,9 @@ 'proxy.config.url_remap.pristine_host_hdr': 1, 'proxy.config.exec_thread.autoconfig.scale': 1.0, 'proxy.config.dns.nameservers': '127.0.0.1:{0}'.format(dns.Variables.Port), - 'proxy.config.dns.resolv_conf': 'NULL' + 'proxy.config.dns.resolv_conf': 'NULL', + 'proxy.config.ssl.client.verify.server.policy': 'PERMISSIVE', + 'proxy.config.http.connect.dead.policy': 1, # Don't count TLS failures for dead upstream. }) dns.addRecords(records={"foo.com.": ["127.0.0.1"]}) From fd33b2d82f4abd345a26b3ebaec1dce7169412bc Mon Sep 17 00:00:00 2001 From: "Alan M. Carroll" Date: Mon, 11 Apr 2022 13:45:45 -0500 Subject: [PATCH 2/6] Post rebase fixup. --- iocore/hostdb/HostDB.cc | 2 +- proxy/http/HttpTransact.cc | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/iocore/hostdb/HostDB.cc b/iocore/hostdb/HostDB.cc index e9b8f957111..d5ae031d63b 100644 --- a/iocore/hostdb/HostDB.cc +++ b/iocore/hostdb/HostDB.cc @@ -1061,7 +1061,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... diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc index 5c2cf3f5faf..a27af518e76 100644 --- a/proxy/http/HttpTransact.cc +++ b/proxy/http/HttpTransact.cc @@ -3680,7 +3680,7 @@ HttpTransact::handle_response_from_parent(State *s) break; case ResolveInfo::HOST_NONE: // Check if content can be served from cache - s->current.request_to = PARENT_PROXY; + s->current.request_to = ResolveInfo::PARENT_PROXY; handle_server_connection_not_open(s); break; default: @@ -3925,10 +3925,10 @@ HttpTransact::handle_server_connection_not_open(State *s) build_response_from_cache(s, HTTP_WARNING_CODE_REVALIDATION_FAILED); } else { switch (s->current.request_to) { - case PARENT_PROXY: + case ResolveInfo::PARENT_PROXY: handle_parent_died(s); break; - case ORIGIN_SERVER: + case ResolveInfo::ORIGIN_SERVER: handle_server_died(s); break; default: From 20dcbef27c4299c8e60790e130b86736e3c4ecbe Mon Sep 17 00:00:00 2001 From: "Alan M. Carroll" Date: Mon, 11 Apr 2022 13:45:53 -0500 Subject: [PATCH 3/6] Fix PreWarm --- proxy/http/PreWarmManager.cc | 46 ++++++++++++++++++++---------------- proxy/http/PreWarmManager.h | 4 ++-- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/proxy/http/PreWarmManager.cc b/proxy/http/PreWarmManager.cc index 930d71ed782..ffddf5d9364 100644 --- a/proxy/http/PreWarmManager.cc +++ b/proxy/http/PreWarmManager.cc @@ -27,6 +27,7 @@ #include "HttpConfig.h" #include "P_SSLSNI.h" +#include "tscore/ink_time.h" #include "tscpp/util/PostScript.h" #include @@ -41,8 +42,8 @@ namespace { using namespace std::literals; -constexpr int DOWN_SERVER_TIMEOUT = 300; -constexpr size_t STAT_NAME_BUF_LEN = 1024; +constexpr ts_seconds DOWN_SERVER_TIMEOUT = 300s; +constexpr size_t STAT_NAME_BUF_LEN = 1024; constexpr std::string_view SRV_TUNNEL_TCP = "_tunnel._tcp."sv; constexpr std::string_view CLIENT_SNI_POLICY_SERVER_NAME = "server_name"sv; @@ -265,22 +266,29 @@ PreWarmSM::state_init(int event, void *data) int PreWarmSM::state_dns_lookup(int event, void *data) { - HostDBInfo *info = static_cast(data); + HostDBRecord *record = static_cast(data); switch (event) { case EVENT_HOST_DB_LOOKUP: { _pending_action = nullptr; - if (info == nullptr || info->is_failed()) { + if (record == nullptr || record->is_failed()) { PreWarmSMVDebug("hostdb lookup is failed"); retry(); return EVENT_DONE; } + HostDBInfo *info = record->select_next_rr(ts_clock::now(), DOWN_SERVER_TIMEOUT); + + if (info == nullptr) { + PreWarmSMVDebug("hostdb lookup found no entry"); + retry(); + return EVENT_DONE; + } IpEndpoint addr; + addr.assign(info->data.ip); - ats_ip_copy(addr, info->ip()); addr.network_order_port() = htons(_dst->port); if (is_debug_tag_set("v_prewarm_sm")) { @@ -302,23 +310,19 @@ PreWarmSM::state_dns_lookup(int event, void *data) _pending_action = nullptr; std::string_view hostname; - if (info == nullptr || !info->is_srv || !info->round_robin) { + if (record == nullptr || !record->is_srv()) { // no SRV record, fallback to default lookup hostname = _dst->host; } else { - HostDBRoundRobin *rr = info->rr(); - HostDBInfo *srv = nullptr; - if (rr) { - char srv_hostname[MAXDNAME] = {0}; - - ink_hrtime now = Thread::get_hrtime(); - srv = rr->select_best_srv(srv_hostname, &mutex->thread_holding->generator, ink_hrtime_to_sec(now), DOWN_SERVER_TIMEOUT); - hostname = std::string_view(srv_hostname); - - if (srv == nullptr) { - // lookup SRV record failed, fallback to default lookup - hostname = _dst->host; - } + char srv_hostname[MAXDNAME] = {0}; + + hostname = std::string_view(srv_hostname); + + HostDBInfo *info = + record->select_best_srv(srv_hostname, &mutex->thread_holding->generator, ts_clock::now(), DOWN_SERVER_TIMEOUT); + if (info == nullptr) { + // lookup SRV record failed, fallback to default lookup + hostname = _dst->host; } } @@ -508,7 +512,7 @@ PreWarmSM::is_inactive_timeout_expired(ink_hrtime now) } void -PreWarmSM::process_hostdb_info(HostDBInfo *r) +PreWarmSM::process_hostdb_info(HostDBRecord *r) { ink_release_assert(this->handler == &PreWarmSM::state_dns_lookup); @@ -516,7 +520,7 @@ PreWarmSM::process_hostdb_info(HostDBInfo *r) } void -PreWarmSM::process_srv_info(HostDBInfo *r) +PreWarmSM::process_srv_info(HostDBRecord *r) { ink_release_assert(this->handler == &PreWarmSM::state_dns_lookup); diff --git a/proxy/http/PreWarmManager.h b/proxy/http/PreWarmManager.h index e7da819c267..ea350636571 100644 --- a/proxy/http/PreWarmManager.h +++ b/proxy/http/PreWarmManager.h @@ -192,8 +192,8 @@ class PreWarmSM : public Continuation bool is_inactive_timeout_expired(ink_hrtime now); // HostDB inline completion functions - void process_hostdb_info(HostDBInfo *r); - void process_srv_info(HostDBInfo *r); + void process_hostdb_info(HostDBRecord *r); + void process_srv_info(HostDBRecord *r); private: enum class Milestone { From 1ffd1eb0400795542a63e9f631aa9c7bbccd40ed Mon Sep 17 00:00:00 2001 From: "Alan M. Carroll" Date: Mon, 11 Apr 2022 18:47:44 -0500 Subject: [PATCH 4/6] Fix nexthop test. --- proxy/http/remap/unit-tests/nexthop_test_stubs.cc | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/proxy/http/remap/unit-tests/nexthop_test_stubs.cc b/proxy/http/remap/unit-tests/nexthop_test_stubs.cc index 17cfdaec05f..9056512617a 100644 --- a/proxy/http/remap/unit-tests/nexthop_test_stubs.cc +++ b/proxy/http/remap/unit-tests/nexthop_test_stubs.cc @@ -101,10 +101,7 @@ build_request(int64_t sm_id, HttpSM *sm, sockaddr_in *ip, const char *os_hostnam } sm->t_state.request_data.hdr = new HTTPHdr(); sm->t_state.request_data.hdr->create(HTTP_TYPE_REQUEST, myHeap); - - ats_free(sm->t_state.request_data.hostname_str); - - sm->t_state.request_data.hostname_str = ats_strdup(os_hostname); + sm->t_state.request_data.hostname_str = sm->t_state.arena.str_store(os_hostname, strlen(os_hostname)); sm->t_state.request_data.xact_start = time(nullptr); ink_zero(sm->t_state.request_data.src_ip); ink_zero(sm->t_state.request_data.dest_ip); From 4d4ea43cef438fff26735f9b21d76983336967df Mon Sep 17 00:00:00 2001 From: Brian Neradt Date: Wed, 22 Jun 2022 18:58:19 -0500 Subject: [PATCH 5/6] Timing fix for HostDB restructure (#34) The default duration of time_since_epoch() is std::chrono::high_resolution_clock::duration, which will not generally be seconds. hostDB.refcountcache->put expects the epoch count to be a number of seconds. This explicitly casts to seconds so we get that expected value. This also makes some other std::chrono time updates. Co-authored-by: bneradt --- include/tscore/ink_time.h | 1 + iocore/hostdb/HostDB.cc | 63 ++++++++++++++++--------------- iocore/hostdb/I_HostDBProcessor.h | 57 +++++++++++++++++++++------- 3 files changed, 78 insertions(+), 43 deletions(-) diff --git a/include/tscore/ink_time.h b/include/tscore/ink_time.h index f138bc3662b..1ac667c851e 100644 --- a/include/tscore/ink_time.h +++ b/include/tscore/ink_time.h @@ -53,6 +53,7 @@ using ts_hr_time = ts_hr_clock::time_point; using ts_seconds = std::chrono::seconds; using ts_milliseconds = std::chrono::milliseconds; +using ts_nanoseconds = std::chrono::nanoseconds; /// Equivalent of 0 for @c ts_time. This should be used as the default initializer. static constexpr ts_time TS_TIME_ZERO; diff --git a/iocore/hostdb/HostDB.cc b/iocore/hostdb/HostDB.cc index d5ae031d63b..0095fd70f80 100644 --- a/iocore/hostdb/HostDB.cc +++ b/iocore/hostdb/HostDB.cc @@ -38,6 +38,7 @@ #include using ts::TextView; +using std::chrono::duration_cast; HostDBProcessor hostDBProcessor; int HostDBProcessor::hostdb_strict_round_robin = 0; @@ -54,11 +55,12 @@ 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; -ts_seconds hostdb_hostfile_check_interval{std::chrono::hours(24)}; -// Epoch timestamp of the current hosts file check. -ts_time hostdb_current_interval{TS_TIME_ZERO}; +static ts_seconds hostdb_hostfile_check_interval{std::chrono::hours(24)}; +// Epoch timestamp of the current hosts file check. This also functions as a +// cached version of ts_clock::now(). +ts_time hostdb_current_timestamp{TS_TIME_ZERO}; // Epoch timestamp of the last time we actually checked for a hosts file update. -static ts_time hostdb_last_interval{TS_TIME_ZERO}; +static ts_time hostdb_last_timestamp{TS_TIME_ZERO}; // Epoch timestamp when we updated the hosts file last. static ts_time hostdb_hostfile_update_timestamp{TS_TIME_ZERO}; static char hostdb_filename[PATH_NAME_MAX] = DEFAULT_HOST_DB_FILENAME; @@ -420,7 +422,7 @@ HostDBBackgroundTask::wait_event(int, void *) SET_HANDLER(&HostDBBackgroundTask::sync_event); if (next_sync > ts_milliseconds{100}) { - eventProcessor.schedule_in(this, std::chrono::duration_cast(next_sync).count(), ET_TASK); + eventProcessor.schedule_in(this, duration_cast(next_sync).count(), ET_TASK); } else { eventProcessor.schedule_imm(this, ET_TASK); } @@ -552,9 +554,10 @@ HostDBProcessor::start(int, size_t) REC_EstablishStaticConfigInt32U(hostdb_round_robin_max_count, "proxy.config.hostdb.round_robin_max_count"); // - // Set up hostdb_current_interval + // Initialize hostdb_current_timestamp which is our cached version of + // ts_clock::now(). // - hostdb_current_interval = ts_clock::now(); + hostdb_current_timestamp = ts_clock::now(); HostDBContinuation *b = hostDBContAllocator.alloc(); SET_CONTINUATION_HANDLER(b, (HostDBContHandler)&HostDBContinuation::backgroundEvent); @@ -693,19 +696,19 @@ probe(const Ptr &mutex, HostDBHash const &hash, bool ignore_timeout) } // If the record is stale, but we want to revalidate-- lets start that up - if ((!ignore_timeout && record->is_ip_stale() && record->record_type != HostDBType::HOST) || + if ((!ignore_timeout && record->is_ip_configured_stale() && record->record_type != HostDBType::HOST) || (record->is_ip_timeout() && record->serve_stale_but_revalidate())) { HOSTDB_INCREMENT_DYN_STAT(hostdb_total_serve_stale_stat); if (hostDB.is_pending_dns_for_hash(hash.hash)) { Debug("hostdb", "%s", - ts::bwprint(ts::bw_dbg, "stale {} {} {}, using with pending refresh", record->ip_interval(), + ts::bwprint(ts::bw_dbg, "stale {} {} {}, using with pending refresh", record->ip_age(), 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) + ts::bwprint(ts::bw_dbg, "stale {} {} {}, using while refresh", record->ip_age(), record->ip_timestamp.time_since_epoch(), + record->ip_timeout_interval) .c_str()); HostDBContinuation *c = hostDBContAllocator.alloc(); HostDBContinuation::Options copt; @@ -962,7 +965,7 @@ HostDBContinuation::lookup_done(TextView query_name, ts_seconds answer_ttl, SRVH ip_text_buffer b; Debug("hostdb", "failed for %s", hash.ip.toString(b, sizeof b)); } - record->ip_timestamp = hostdb_current_interval; + record->ip_timestamp = hostdb_current_timestamp; record->ip_timeout_interval = ts_seconds(std::clamp(hostdb_ip_fail_timeout_interval, 1u, HOST_DB_MAX_TTL)); if (is_srv()) { @@ -997,7 +1000,7 @@ HostDBContinuation::lookup_done(TextView query_name, ts_seconds answer_ttl, SRVH HOSTDB_SUM_DYN_STAT(hostdb_ttl_stat, answer_ttl.count()); // update the TTL - record->ip_timestamp = hostdb_current_interval; + record->ip_timestamp = hostdb_current_timestamp; record->ip_timeout_interval = std::clamp(answer_ttl, ts_seconds(1), ts_seconds(HOST_DB_MAX_TTL)); if (is_byname()) { @@ -1206,9 +1209,9 @@ HostDBContinuation::dnsEvent(int event, HostEnt *e) } 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()); + auto const duration_till_revalidate = r->expiry_time().time_since_epoch(); + auto const seconds_till_revalidate = duration_cast(duration_till_revalidate).count(); + hostDB.refcountcache->put(r->key, r.get(), r->_record_size, seconds_till_revalidate); } else { Warning("Fallback to serving stale record, skip re-update of hostdb for %.*s", int(query_name.size()), query_name.data()); } @@ -1511,22 +1514,22 @@ HostDBContinuation::do_dns() // // Background event -// Just increment the current_interval. Might do other stuff -// here, like move records to the current position in the cluster. -// +// Increment the hostdb_current_timestamp which funcions as our cached version +// of ts_clock::now(). Might do other stuff here, like move records to the +// current position in the cluster. int HostDBContinuation::backgroundEvent(int /* event ATS_UNUSED */, Event * /* e ATS_UNUSED */) { std::string dbg; - // No nothing if hosts file checking is not enabled. + hostdb_current_timestamp = ts_clock::now(); + + // Do nothing if hosts file checking is not enabled. if (hostdb_hostfile_check_interval.count() == 0) { return EVENT_CONT; } - hostdb_current_interval = ts_clock::now(); - - if ((hostdb_current_interval - hostdb_last_interval) > hostdb_hostfile_check_interval) { + if ((hostdb_current_timestamp - hostdb_last_timestamp) > hostdb_hostfile_check_interval) { bool update_p = false; // do we need to reparse the file and update? char path[PATH_NAME_MAX]; @@ -1539,7 +1542,7 @@ HostDBContinuation::backgroundEvent(int /* event ATS_UNUSED */, Event * /* e ATS hostdb_hostfile_path = path; update_p = true; } else if (!hostdb_hostfile_path.empty()) { - hostdb_last_interval = hostdb_current_interval; + hostdb_last_timestamp = hostdb_current_timestamp; std::error_code ec; auto stat{ts::file::status(hostdb_hostfile_path, ec)}; if (!ec) { @@ -1688,7 +1691,7 @@ struct ShowHostDB : public ShowCont { CHECK_SHOW(show("\n")); CHECK_SHOW(show("\n", "Total", r->rr_count)); CHECK_SHOW(show("\n", "Current", r->_rr_idx.load())); - CHECK_SHOW(show("\n", "Stale", r->is_ip_stale() ? "Yes" : "No")); + CHECK_SHOW(show("\n", "Stale", r->is_ip_configured_stale() ? "Yes" : "No")); CHECK_SHOW(show("\n", "Timed-Out", r->is_ip_timeout() ? "Yes" : "No")); CHECK_SHOW(show("
%s%d
%s%d
%s%s
%s%s
%s%s
\n")); } else { @@ -2112,7 +2115,7 @@ ParseHostFile(ts::file::path const &path, ts_seconds hostdb_hostfile_check_inter } } - hostdb_hostfile_update_timestamp = hostdb_current_interval; + hostdb_hostfile_update_timestamp = hostdb_current_timestamp; } } @@ -2280,10 +2283,10 @@ HostDBRecord::serve_stale_but_revalidate() const // 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 + ts_seconds(hostdb_serve_stale_but_revalidate)) > ip_interval()) { - Debug_bw("hostdb", "serving stale entry {} | {} | {} as requested by config", ip_timeout_interval, - hostdb_serve_stale_but_revalidate, ip_interval()); + // ip_age() is the number of seconds between now() and when the entry was inserted + if ((ip_timeout_interval + ts_seconds(hostdb_serve_stale_but_revalidate)) > ip_age()) { + Debug_bw("hostdb", "serving stale entry for {}, TTL: {}, serve_stale_for: {}, age: {} as requested by config", name(), + ip_timeout_interval, hostdb_serve_stale_but_revalidate, ip_age()); return true; } diff --git a/iocore/hostdb/I_HostDBProcessor.h b/iocore/hostdb/I_HostDBProcessor.h index 8b2669396c4..6c34082c11f 100644 --- a/iocore/hostdb/I_HostDBProcessor.h +++ b/iocore/hostdb/I_HostDBProcessor.h @@ -61,7 +61,17 @@ struct ResolveInfo; // disk representation to decrease # of seeks. // extern int hostdb_enable; -extern ts_time hostdb_current_interval; +/** Epoch timestamp of the current hosts file check. + * + * This also functions as a cached version of ts_clock::now(). Since it is + * updated in backgroundEvent which runs every second, it should be + * approximately accurate to a second. + */ +extern ts_time hostdb_current_timestamp; + +/** How long before any DNS response is consided stale, regardless of DNS TTL. + * This corresponds to proxy.config.hostdb.verify_after. + */ extern unsigned int hostdb_ip_stale_interval; extern unsigned int hostdb_ip_timeout_interval; extern unsigned int hostdb_ip_fail_timeout_interval; @@ -325,10 +335,13 @@ class HostDBRecord : public RefCountObj /// Hash key. uint64_t key{0}; - /// When the data was received. + /// When the DNS response was received. ts_time ip_timestamp; - /// Valid duration of the data. + /// Valid duration of the DNS response data. + /// In the code this functions as the TTL in HostDB calcuations, but may not + /// be the response's TTL based upon configuration such as + /// proxy.config.hostdb.ttl_mode. ts_seconds ip_timeout_interval; /** Atomically advance the round robin index. @@ -414,18 +427,33 @@ class HostDBRecord : public RefCountObj /// @return The time point when the item expires. ts_time expiry_time() const; - ts_seconds ip_interval() const; + /// @return The age of the DNS response. + ts_seconds ip_age() const; + /// @return How long before the DNS response becomes stale (i.e., exceeds @a + /// ip_timeout_interval from the time of the response). ts_seconds ip_time_remaining() const; - bool is_ip_stale() const; + /// @return Whether the age of the DNS response exceeds @a the user's + /// configured proxy.config.hostdb.verify_after value. + bool is_ip_configured_stale() const; + /** Whether we have exceeded the DNS response's TTL (i.e., whether the DNS + * response is stale). */ bool is_ip_timeout() const; bool is_ip_fail_timeout() const; void refresh_ip(); + /** Whether the DNS response can still be used per + * proxy.config.hostdb.serve_stale_for configuration. + * + * @return False if serve_stale_for is not configured or if the DNS + * response's age is less than TTL + the serve_stale_for value. Note that + * this function will return true for DNS responses whose age is less than + * TTL (i.e., for responses that are not yet stale). + */ bool serve_stale_but_revalidate() const; /// Deallocate @a this. @@ -722,41 +750,44 @@ HostDBRecord::expiry_time() const } inline ts_seconds -HostDBRecord::ip_interval() const +HostDBRecord::ip_age() const { static constexpr ts_seconds ZERO{0}; static constexpr ts_seconds MAX{0x7FFFFFFF}; - return std::clamp(std::chrono::duration_cast((hostdb_current_interval - ip_timestamp)), ZERO, MAX); + return std::clamp(std::chrono::duration_cast(hostdb_current_timestamp - ip_timestamp), ZERO, MAX); } inline ts_seconds HostDBRecord::ip_time_remaining() const { - return ip_timeout_interval - this->ip_interval(); + static constexpr ts_seconds ZERO{0}; + static constexpr ts_seconds MAX{0x7FFFFFFF}; + return std::clamp(std::chrono::duration_cast(ip_timeout_interval - this->ip_age()), ZERO, MAX); } inline bool -HostDBRecord::is_ip_stale() const +HostDBRecord::is_ip_configured_stale() const { - return ip_timeout_interval >= ts_seconds(2 * hostdb_ip_stale_interval) && ip_interval() >= ts_seconds(hostdb_ip_stale_interval); + return ( + ((ip_timeout_interval >= ts_seconds(2 * hostdb_ip_stale_interval)) && (ip_age() >= ts_seconds(hostdb_ip_stale_interval)))); } inline bool HostDBRecord::is_ip_timeout() const { - return ip_interval() >= ip_timeout_interval; + return ip_age() >= ip_timeout_interval; } inline bool HostDBRecord::is_ip_fail_timeout() const { - return ip_interval() >= ts_seconds(hostdb_ip_fail_timeout_interval); + return ip_age() >= ts_seconds(hostdb_ip_fail_timeout_interval); } inline void HostDBRecord::refresh_ip() { - ip_timestamp = hostdb_current_interval; + ip_timestamp = hostdb_current_timestamp; } inline ts::MemSpan From 73e0abd45dce6028e26bbf44104750c94220b823 Mon Sep 17 00:00:00 2001 From: Brian Neradt Date: Fri, 8 Jul 2022 14:16:52 -0500 Subject: [PATCH 6/6] Debug_bw updates. And clang-format fix. (#35) * Debug_bw updates. And clang-format fix. * If HostDB returns only failed parents, try serving from cache. Before this change, the parent cache logic would give up if it couldn't resolve the origin name for a request. This will attempt to retrieve a cached response if the resolution fails. --- include/tscore/Diags.h | 18 ++++++++++-------- proxy/http/HttpSM.cc | 8 ++++---- proxy/http/HttpTransact.cc | 6 ++++++ .../proxy_protocol/proxy_serve_stale.test.py | 2 +- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/include/tscore/Diags.h b/include/tscore/Diags.h index e9e050f60ac..d87baec412c 100644 --- a/include/tscore/Diags.h +++ b/include/tscore/Diags.h @@ -188,14 +188,16 @@ is_dbg_ctl_enabled(DbgCtl const &ctl) } \ } while (false) -#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) +// A BufferWriter version of Debug(). +#define Debug_bw(tag__, fmt, ...) \ + do { \ + if (unlikely(diags()->on())) { \ + static DbgCtl Debug_bw_ctl(tag__); \ + if (Debug_bw_ctl.ptr()->on) { \ + DbgPrint(Debug_bw_ctl, "%s", ts::bwprint(ts::bw_dbg, fmt, __VA_ARGS__).c_str()); \ + } \ + } \ + } while (false) // printf-like debug output. First parameter must be tag (C-string literal, or otherwise // a constexpr returning char const pointer to null-terminated C-string). diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc index 71fcb46e7ba..813b6032cde 100644 --- a/proxy/http/HttpSM.cc +++ b/proxy/http/HttpSM.cc @@ -5474,10 +5474,10 @@ HttpSM::mark_host_failure(ResolveInfo *info, ts_time time_down) int host_len; const char *host_name_ptr = t_state.unmapped_url.host_get(&host_len); std::string_view host_name{host_name_ptr, size_t(host_len)}; - ts::bwprint(error_bw_buffer,"CONNECT : {::s} connecting to {} for host='{}' url='{}' marking down", - ts::bwf::Errno(t_state.current.server->connect_result), t_state.current.server->dst_addr,host_name, - ts::bwf::FirstOf(url_str, "")); - Log::error("%s", error_bw_buffer.c_str()); + ts::bwprint(error_bw_buffer, "CONNECT : {::s} connecting to {} for host='{}' url='{}' marking down", + ts::bwf::Errno(t_state.current.server->connect_result), t_state.current.server->dst_addr, host_name, + ts::bwf::FirstOf(url_str, "")); + Log::error("%s", error_bw_buffer.c_str()); if (url_str) { t_state.arena.str_free(url_str); diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc index a27af518e76..9f660d4207c 100644 --- a/proxy/http/HttpTransact.cc +++ b/proxy/http/HttpTransact.cc @@ -1790,6 +1790,12 @@ HttpTransact::PPDNSLookup(State *s) markParentDown(s); // DNS lookup of parent failed, find next parent or o.s. if (find_server_and_update_current_info(s) == ResolveInfo::HOST_NONE) { + if (is_cache_hit(s->cache_lookup_result) && is_stale_cache_response_returnable(s)) { + s->source = SOURCE_CACHE; + TxnDebug("http_trans", "All parents are down, serving stale doc to client"); + build_response_from_cache(s, HTTP_WARNING_CODE_REVALIDATION_FAILED); + return; + } ink_assert(s->current.request_to == ResolveInfo::HOST_NONE); handle_parent_died(s); return; diff --git a/tests/gold_tests/proxy_protocol/proxy_serve_stale.test.py b/tests/gold_tests/proxy_protocol/proxy_serve_stale.test.py index e012d394f74..e799884c6d8 100644 --- a/tests/gold_tests/proxy_protocol/proxy_serve_stale.test.py +++ b/tests/gold_tests/proxy_protocol/proxy_serve_stale.test.py @@ -47,7 +47,7 @@ def _configure_ts(self): 'proxy.config.http.cache.max_stale_age': 10, 'proxy.config.http.parent_proxy.self_detect': 0, 'proxy.config.diags.debug.enabled': 1, - 'proxy.config.diags.debug.tags': 'http|dns|parent_proxy', + 'proxy.config.diags.debug.tags': 'cache|http|dns|hostdb|parent_proxy', }) self.ts_child.Disk.parent_config.AddLine( f'dest_domain=. parent="{self.ts_parent_hostname}" round_robin=consistent_hash go_direct=false'