/*
- * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
#include "squid.h"
#include "acl/FilledChecklist.h"
+#include "base/AsyncCbdataCalls.h"
+#include "base/InstanceId.h"
+#include "base/TypeTraits.h"
#include "CachePeer.h"
+#include "CachePeers.h"
#include "carp.h"
#include "client_side.h"
-#include "DnsLookupDetails.h"
+#include "dns/LookupDetails.h"
#include "errorpage.h"
#include "event.h"
#include "FwdState.h"
#include "globals.h"
#include "hier_code.h"
#include "htcp.h"
+#include "http/Stream.h"
#include "HttpRequest.h"
#include "icmp/net_db.h"
#include "ICP.h"
#include "peer_userhash.h"
#include "PeerSelectState.h"
#include "SquidConfig.h"
-#include "SquidTime.h"
#include "Store.h"
-#include "URL.h"
+#include "time/gadgets.h"
+
+/**
+ * A CachePeer which has been selected as a possible destination.
+ * Listed as pointers here so as to prevent duplicates being added but will
+ * be converted to a set of IP address path options before handing back out
+ * to the caller.
+ *
+ * Certain connection flags and outgoing settings will also be looked up and
+ * set based on the received request and CachePeer settings before handing back.
+ */
+class FwdServer
+{
+ MEMPROXY_CLASS(FwdServer);
+
+public:
+ FwdServer(CachePeer *p, hier_code c) :
+ _peer(p),
+ code(c),
+ next(nullptr)
+ {}
+
+ CbcPointer<CachePeer> _peer; /* NULL --> origin server */
+ hier_code code;
+ FwdServer *next;
+};
static struct {
int timeouts;
"DIRECT_YES"
};
-static void peerSelectFoo(ps_state *);
-static void peerPingTimeout(void *data);
-static IRCB peerHandlePingReply;
-static void peerIcpParentMiss(CachePeer *, icp_common_t *, ps_state *);
-#if USE_HTCP
-static void peerHtcpParentMiss(CachePeer *, HtcpReplyData *, ps_state *);
-static void peerHandleHtcpReply(CachePeer *, peer_t, HtcpReplyData *, void *);
-#endif
-static int peerCheckNetdbDirect(ps_state * psstate);
-static void peerGetSomeNeighbor(ps_state *);
-static void peerGetSomeNeighborReplies(ps_state *);
-static void peerGetSomeDirect(ps_state *);
-static void peerGetSomeParent(ps_state *);
-static void peerGetAllParents(ps_state *);
-static void peerAddFwdServer(FwdServer **, CachePeer *, hier_code);
-static void peerSelectPinned(ps_state * ps);
-static void peerSelectDnsResults(const ipcache_addrs *ia, const DnsLookupDetails &details, void *data);
+/// a helper class to report a selected destination (for debugging)
+class PeerSelectionDumper
+{
+public:
+ PeerSelectionDumper(const PeerSelector * const aSelector, const CachePeer * const aPeer, const hier_code aCode):
+ selector(aSelector), peer(aPeer), code(aCode) {}
+
+ const PeerSelector * const selector; ///< selection parameters
+ const CachePeer * const peer; ///< successful selection info
+ const hier_code code; ///< selection algorithm
+};
-CBDATA_CLASS_INIT(ps_state);
+CBDATA_CLASS_INIT(PeerSelector);
-ps_state::~ps_state()
+/// prints PeerSelectionDumper (for debugging)
+static std::ostream &
+operator <<(std::ostream &os, const PeerSelectionDumper &fsd)
+{
+ os << hier_code_str[fsd.code];
+
+ if (fsd.peer)
+ os << '/' << *fsd.peer;
+ else if (fsd.selector) // useful for DIRECT and gone PINNED destinations
+ os << '#' << fsd.selector->request->url.host();
+
+ return os;
+}
+
+/// An ICP ping timeout service.
+/// Protects event.cc (which is designed to handle a few unrelated timeouts)
+/// from exposure to thousands of ping-related timeouts on busy proxies.
+class PeerSelectorPingMonitor
+{
+public:
+ /// registers the given selector to be notified about the IPC ping timeout
+ void monitor(PeerSelector *);
+
+ /// removes a PeerSelector from the waiting list
+ void forget(PeerSelector *);
+
+ /// \returns a (nil) registration of a non-waiting peer selector
+ WaitingPeerSelectorPosition npos() { return selectors.end(); }
+
+private:
+ static void NoteWaitOver(void *monitor);
+
+ void startWaiting();
+ void abortWaiting();
+ void noteWaitOver();
+
+ WaitingPeerSelectors selectors; ///< \see WaitingPeerSelectors
+};
+
+/// monitors all PeerSelector ICP ping timeouts
+static PeerSelectorPingMonitor &
+PingMonitor()
+{
+ static const auto Instance = new PeerSelectorPingMonitor();
+ return *Instance;
+}
+
+/* PeerSelectorPingMonitor */
+
+/// PeerSelectorPingMonitor::noteWaitOver() wrapper
+void
+PeerSelectorPingMonitor::NoteWaitOver(void *raw)
+{
+ assert(raw);
+ static_cast<PeerSelectorPingMonitor*>(raw)->noteWaitOver();
+}
+
+/// schedules a single event to represent all waiting selectors
+void
+PeerSelectorPingMonitor::startWaiting()
+{
+ assert(!selectors.empty());
+ const auto interval = tvSubDsec(current_time, selectors.begin()->first);
+ eventAdd("PeerSelectorPingMonitor::NoteWaitOver", &PeerSelectorPingMonitor::NoteWaitOver, this, interval, 0, false);
+}
+
+/// undoes an earlier startWaiting() call
+void
+PeerSelectorPingMonitor::abortWaiting()
+{
+ // our event may be already in the AsyncCallQueue but that is OK:
+ // such queued calls cannot accumulate, and we ignore any stale ones
+ eventDelete(&PeerSelectorPingMonitor::NoteWaitOver, nullptr);
+}
+
+/// calls back all ready PeerSelectors and continues to wait for others
+void
+PeerSelectorPingMonitor::noteWaitOver()
+{
+ while (!selectors.empty() && current_time >= selectors.begin()->first) {
+ const auto selector = selectors.begin()->second;
+ CallBack(selector->al, [selector,this] {
+ selector->ping.monitorRegistration = npos();
+ AsyncCall::Pointer callback = asyncCall(44, 4, "PeerSelector::HandlePingTimeout",
+ cbdataDialer(PeerSelector::HandlePingTimeout, selector));
+ ScheduleCallHere(callback);
+ });
+ selectors.erase(selectors.begin());
+ }
+
+ if (!selectors.empty()) {
+ // Since abortWaiting() is unreliable, we may have been awakened by a
+ // stale event A after event B has been scheduled. Now we are going to
+ // schedule event C. Prevent event accumulation by deleting B (if any).
+ abortWaiting();
+
+ startWaiting();
+ }
+}
+
+void
+PeerSelectorPingMonitor::monitor(PeerSelector *selector)
+{
+ assert(selector);
+
+ const auto deadline = selector->ping.deadline();
+ const auto position = selectors.emplace(deadline, selector);
+ selector->ping.monitorRegistration = position;
+
+ if (position == selectors.begin()) {
+ if (selectors.size() > 1)
+ abortWaiting(); // remove the previously scheduled earlier event
+ startWaiting();
+ } // else the already scheduled event is still the earliest one
+}
+
+void
+PeerSelectorPingMonitor::forget(PeerSelector *selector)
+{
+ assert(selector);
+
+ if (selector->ping.monitorRegistration == npos())
+ return; // already forgotten
+
+ const auto wasFirst = selector->ping.monitorRegistration == selectors.begin();
+ selectors.erase(selector->ping.monitorRegistration);
+ selector->ping.monitorRegistration = npos();
+
+ if (wasFirst) {
+ // do not reschedule if there are still elements with the same deadline
+ if (!selectors.empty() && selectors.begin()->first == selector->ping.deadline())
+ return;
+ abortWaiting();
+ if (!selectors.empty())
+ startWaiting();
+ } // else do nothing since the old scheduled event is still the earliest one
+}
+
+/* PeerSelector */
+
+PeerSelector::~PeerSelector()
{
while (servers) {
FwdServer *next = servers->next;
- cbdataReferenceDone(servers->_peer);
- memFree(servers, MEM_FWD_SERVER);
+ delete servers;
servers = next;
}
+ cancelPingTimeoutMonitoring();
+
if (entry) {
debugs(44, 3, entry->url());
-
- if (entry->ping_status == PING_WAITING)
- eventDelete(peerPingTimeout, this);
-
entry->ping_status = PING_DONE;
}
if (acl_checklist) {
- debugs(44, DBG_IMPORTANT, "calling aclChecklistFree() from ps_state destructor");
+ debugs(44, DBG_IMPORTANT, "ERROR: Squid BUG: peer selector gone while waiting for a slow ACL");
delete acl_checklist;
}
if (entry) {
assert(entry->ping_status != PING_WAITING);
entry->unlock("peerSelect");
- entry = NULL;
+ entry = nullptr;
}
delete lastError;
}
+void
+PeerSelector::startPingWaiting()
+{
+ assert(entry);
+ assert(entry->ping_status != PING_WAITING);
+ PingMonitor().monitor(this);
+ entry->ping_status = PING_WAITING;
+}
+
+void
+PeerSelector::cancelPingTimeoutMonitoring()
+{
+ PingMonitor().forget(this);
+}
+
static int
-peerSelectIcpPing(HttpRequest * request, int direct, StoreEntry * entry)
+peerSelectIcpPing(PeerSelector *ps, int direct, StoreEntry * entry)
{
+ assert(ps);
+ HttpRequest *request = ps->request;
+
int n;
assert(entry);
assert(entry->ping_status == PING_NONE);
assert(direct != DIRECT_YES);
- debugs(44, 3, "peerSelectIcpPing: " << entry->url() );
+ debugs(44, 3, entry->url());
if (!request->flags.hierarchical && direct != DIRECT_NO)
return 0;
if (direct != DIRECT_NO)
return 0;
- n = neighborsCount(request);
+ n = neighborsCount(ps);
- debugs(44, 3, "peerSelectIcpPing: counted " << n << " neighbors");
+ debugs(44, 3, "counted " << n << " neighbors");
return n;
}
-void
-peerSelect(Comm::ConnectionList * paths,
+static void
+peerSelect(PeerSelectionInitiator *initiator,
HttpRequest * request,
AccessLogEntry::Pointer const &al,
- StoreEntry * entry,
- PSC * callback,
- void *callback_data)
+ StoreEntry * entry)
{
- ps_state *psstate;
-
if (entry)
debugs(44, 3, *entry << ' ' << entry->url());
else
debugs(44, 3, request->method);
- psstate = new ps_state;
-
- psstate->request = request;
- HTTPMSGLOCK(psstate->request);
- psstate->al = al;
+ const auto selector = new PeerSelector(initiator);
- psstate->entry = entry;
- psstate->paths = paths;
+ selector->request = request;
+ HTTPMSGLOCK(selector->request);
+ selector->al = al;
- psstate->callback = callback;
-
- psstate->callback_data = cbdataReference(callback_data);
+ selector->entry = entry;
#if USE_CACHE_DIGESTS
#endif
- if (psstate->entry)
- psstate->entry->lock("peerSelect");
+ if (selector->entry)
+ selector->entry->lock("peerSelect");
- peerSelectFoo(psstate);
+ selector->selectMore();
}
-static void
-peerCheckNeverDirectDone(allow_t answer, void *data)
+void
+PeerSelectionInitiator::startSelectingDestinations(HttpRequest *request, const AccessLogEntry::Pointer &ale, StoreEntry *entry)
{
- ps_state *psstate = (ps_state *) data;
- psstate->acl_checklist = NULL;
- debugs(44, 3, "peerCheckNeverDirectDone: " << answer);
- psstate->never_direct = answer;
+ subscribed = true;
+ peerSelect(this, request, ale, entry);
+ // and wait for noteDestination() and/or noteDestinationsEnd() calls
+}
+
+void
+PeerSelector::checkNeverDirectDone(const Acl::Answer answer)
+{
+ acl_checklist = nullptr;
+ debugs(44, 3, answer);
+ never_direct = answer;
switch (answer) {
case ACCESS_ALLOWED:
/** if never_direct says YES, do that. */
- psstate->direct = DIRECT_NO;
- debugs(44, 3, HERE << "direct = " << DirectStr[psstate->direct] << " (never_direct allow)");
+ direct = DIRECT_NO;
+ debugs(44, 3, "direct = " << DirectStr[direct] << " (never_direct allow)");
break;
case ACCESS_DENIED: // not relevant.
case ACCESS_DUNNO: // not relevant.
debugs(44, DBG_IMPORTANT, "WARNING: never_direct resulted in " << answer << ". Username ACLs are not reliable here.");
break;
}
- peerSelectFoo(psstate);
+ selectMore();
}
-static void
-peerCheckAlwaysDirectDone(allow_t answer, void *data)
+void
+PeerSelector::CheckNeverDirectDone(Acl::Answer answer, void *data)
+{
+ static_cast<PeerSelector*>(data)->checkNeverDirectDone(answer);
+}
+
+void
+PeerSelector::checkAlwaysDirectDone(const Acl::Answer answer)
{
- ps_state *psstate = (ps_state *)data;
- psstate->acl_checklist = NULL;
- debugs(44, 3, "peerCheckAlwaysDirectDone: " << answer);
- psstate->always_direct = answer;
+ acl_checklist = nullptr;
+ debugs(44, 3, answer);
+ always_direct = answer;
switch (answer) {
case ACCESS_ALLOWED:
/** if always_direct says YES, do that. */
- psstate->direct = DIRECT_YES;
- debugs(44, 3, HERE << "direct = " << DirectStr[psstate->direct] << " (always_direct allow)");
+ direct = DIRECT_YES;
+ debugs(44, 3, "direct = " << DirectStr[direct] << " (always_direct allow)");
break;
case ACCESS_DENIED: // not relevant.
case ACCESS_DUNNO: // not relevant.
debugs(44, DBG_IMPORTANT, "WARNING: always_direct resulted in " << answer << ". Username ACLs are not reliable here.");
break;
}
- peerSelectFoo(psstate);
+ selectMore();
}
void
-peerSelectDnsPaths(ps_state *psstate)
+PeerSelector::CheckAlwaysDirectDone(Acl::Answer answer, void *data)
{
- FwdServer *fs = psstate->servers;
+ static_cast<PeerSelector*>(data)->checkAlwaysDirectDone(answer);
+}
+
+/// \returns true (after destroying "this") if the peer initiator is gone
+/// \returns false (without side effects) otherwise
+bool
+PeerSelector::selectionAborted()
+{
+ if (interestedInitiator())
+ return false;
+
+ debugs(44, 3, "Aborting peer selection: Initiator gone or lost interest.");
+ delete this;
+ return true;
+}
- if (!cbdataReferenceValid(psstate->callback_data)) {
- debugs(44, 3, "Aborting peer selection. Parent Job went away.");
- delete psstate;
+/// A single DNS resolution loop iteration: Converts selected FwdServer to IPs.
+void
+PeerSelector::resolveSelected()
+{
+ if (selectionAborted())
return;
- }
+
+ FwdServer *fs = servers;
// Bug 3243: CVE 2009-0801
// Bypass of browser same-origin access control in intercepted communication
// To resolve this we must use only the original client destination when going DIRECT
// on intercepted traffic which failed Host verification
- const HttpRequest *req = psstate->request;
+ const HttpRequest *req = request;
const bool isIntercepted = !req->flags.redirected &&
(req->flags.intercepted || req->flags.interceptTproxy);
const bool useOriginalDst = Config.onoff.client_dst_passthru || !req->flags.hostVerified;
// construct a "result" adding the ORIGINAL_DST to the set instead of DIRECT
Comm::ConnectionPointer p = new Comm::Connection();
p->remote = req->clientConnectionManager->clientConnection->local;
- p->peerType = ORIGINAL_DST; // fs->code is DIRECT. This fixes the display.
- p->setPeer(fs->_peer);
-
- // check for a configured outgoing address for this destination...
- getOutgoingAddress(psstate->request, p);
- psstate->paths->push_back(p);
+ fs->code = ORIGINAL_DST; // fs->code is DIRECT. This fixes the display.
+ handlePath(p, *fs);
}
// clear the used fs and continue
- psstate->servers = fs->next;
- cbdataReferenceDone(fs->_peer);
- memFree(fs, MEM_FWD_SERVER);
- peerSelectDnsPaths(psstate);
+ servers = fs->next;
+ delete fs;
+ resolveSelected();
+ return;
+ }
+
+ if (fs && fs->code == PINNED) {
+ // Nil path signals a PINNED destination selection. Our initiator should
+ // borrow and use clientConnectionManager's pinned connection object
+ // (regardless of that connection destination).
+ handlePath(nullptr, *fs);
+ servers = fs->next;
+ delete fs;
+ resolveSelected();
return;
}
// convert the list of FwdServer destinations into destinations IP addresses
- if (fs && psstate->paths->size() < (unsigned int)Config.forward_max_tries) {
+ if (fs && wantsMoreDestinations()) {
// send the next one off for DNS lookup.
- const char *host = fs->_peer ? fs->_peer->host : psstate->request->GetHost();
- debugs(44, 2, "Find IP destination for: " << psstate->entry->url() << "' via " << host);
- ipcache_nbgethostbyname(host, peerSelectDnsResults, psstate);
+ const char *host = fs->_peer.valid() ? fs->_peer->host : request->url.host();
+ debugs(44, 2, "Find IP destination for: " << url() << "' via " << host);
+ Dns::nbgethostbyname(host, this);
return;
}
// Bug 3605: clear any extra listed FwdServer destinations, when the options exceeds max_foward_tries.
// due to the allocation method of fs, we must deallocate each manually.
// TODO: use a std::list so we can get the size and abort adding whenever the selection loops reach Config.forward_max_tries
- if (fs && psstate->paths->size() >= (unsigned int)Config.forward_max_tries) {
- assert(fs == psstate->servers);
+ if (fs) {
+ assert(fs == servers);
while (fs) {
- psstate->servers = fs->next;
- cbdataReferenceDone(fs->_peer);
- memFree(fs, MEM_FWD_SERVER);
- fs = psstate->servers;
+ servers = fs->next;
+ delete fs;
+ fs = servers;
}
}
// done with DNS lookups. pass back to caller
- PSC *callback = psstate->callback;
- psstate->callback = NULL;
-
- debugs(44, 2, (psstate->paths->size()<1?"Failed to select source":"Found sources") << " for '" << psstate->url() << "'");
- debugs(44, 2, " always_direct = " << psstate->always_direct);
- debugs(44, 2, " never_direct = " << psstate->never_direct);
- if (psstate->paths) {
- for (size_t i = 0; i < psstate->paths->size(); ++i) {
- if ((*psstate->paths)[i]->peerType == HIER_DIRECT)
- debugs(44, 2, " DIRECT = " << (*psstate->paths)[i]);
- else if ((*psstate->paths)[i]->peerType == ORIGINAL_DST)
- debugs(44, 2, " ORIGINAL_DST = " << (*psstate->paths)[i]);
- else if ((*psstate->paths)[i]->peerType == PINNED)
- debugs(44, 2, " PINNED = " << (*psstate->paths)[i]);
- else
- debugs(44, 2, " cache_peer = " << (*psstate->paths)[i]);
- }
- }
- debugs(44, 2, " timedout = " << psstate->ping.timedout);
- psstate->ping.stop = current_time;
- psstate->request->hier.ping = psstate->ping;
+ debugs(44, 2, id << " found all " << foundPaths << " destinations for " << url());
+ debugs(44, 2, " always_direct = " << always_direct);
+ debugs(44, 2, " never_direct = " << never_direct);
+ debugs(44, 2, " timedout = " << ping.timedout);
- void *cbdata;
- if (cbdataReferenceValidDone(psstate->callback_data, &cbdata)) {
- callback(psstate->paths, psstate->lastError, cbdata);
- psstate->lastError = NULL; // FwdState has taken control over the ErrorState object.
+ ping.stop = current_time;
+ request->hier.ping = ping; // final result
+
+ if (lastError && foundPaths) {
+ // nobody cares about errors if we found destinations despite them
+ debugs(44, 3, "forgetting the last error");
+ delete lastError;
+ lastError = nullptr;
}
- delete psstate;
+ if (const auto initiator = interestedInitiator())
+ initiator->noteDestinationsEnd(lastError);
+ lastError = nullptr; // initiator owns the ErrorState object now
+ delete this;
}
-static void
-peerSelectDnsResults(const ipcache_addrs *ia, const DnsLookupDetails &details, void *data)
+void
+PeerSelector::noteLookup(const Dns::LookupDetails &details)
{
- ps_state *psstate = (ps_state *)data;
+ /* ignore lookup delays that occurred after the initiator moved on */
- if (!cbdataReferenceValid(psstate->callback_data)) {
- debugs(44, 3, "Aborting peer selection. Parent Job went away.");
- delete psstate;
+ if (selectionAborted())
return;
- }
-
- psstate->request->recordLookup(details);
- FwdServer *fs = psstate->servers;
- if (ia != NULL) {
-
- assert(ia->cur < ia->count);
+ if (!wantsMoreDestinations())
+ return;
- // loop over each result address, adding to the possible destinations.
- int ip = ia->cur;
- for (int n = 0; n < ia->count; ++n, ++ip) {
- Comm::ConnectionPointer p;
+ request->recordLookup(details);
+}
- if (ip >= ia->count) ip = 0; // looped back to zero.
+void
+PeerSelector::noteIp(const Ip::Address &ip)
+{
+ if (selectionAborted())
+ return;
- // Enforce forward_max_tries configuration.
- if (psstate->paths->size() >= (unsigned int)Config.forward_max_tries)
- break;
+ if (!wantsMoreDestinations())
+ return;
- // for TPROXY spoofing we must skip unusable addresses.
- if (psstate->request->flags.spoofClientIp && !(fs->_peer && fs->_peer->options.no_tproxy) ) {
- if (ia->in_addrs[ip].isIPv4() != psstate->request->client_addr.isIPv4()) {
- // we CAN'T spoof the address on this link. find another.
- continue;
- }
- }
+ const auto peer = servers->_peer.valid();
- p = new Comm::Connection();
- p->remote = ia->in_addrs[ip];
+ // for TPROXY spoofing, we must skip unusable addresses
+ if (request->flags.spoofClientIp && !(peer && peer->options.no_tproxy) ) {
+ if (ip.isIPv4() != request->client_addr.isIPv4())
+ return; // cannot spoof the client address on this link
+ }
- // when IPv6 is disabled we cannot use it
- if (!Ip::EnableIpv6 && p->remote.isIPv6()) {
- const char *host = (fs->_peer ? fs->_peer->host : psstate->request->GetHost());
- ipcacheMarkBadAddr(host, p->remote);
- continue;
- }
+ Comm::ConnectionPointer p = new Comm::Connection();
+ p->remote = ip;
+ // XXX: We return a (non-peer) destination with a zero port if the selection
+ // initiator supplied a request target without a port. If there are no valid
+ // use cases for this behavior, stop _selecting_ such destinations.
+ p->remote.port(peer ? peer->http_port : request->url.port().value_or(0));
+ handlePath(p, *servers);
+}
- if (fs->_peer)
- p->remote.port(fs->_peer->http_port);
- else
- p->remote.port(psstate->request->port);
- p->peerType = fs->code;
- p->setPeer(fs->_peer);
+void
+PeerSelector::noteIps(const Dns::CachedIps *ia, const Dns::LookupDetails &details)
+{
+ if (selectionAborted())
+ return;
- // check for a configured outgoing address for this destination...
- getOutgoingAddress(psstate->request, p);
- psstate->paths->push_back(p);
- }
- } else {
- debugs(44, 3, HERE << "Unknown host: " << (fs->_peer ? fs->_peer->host : psstate->request->GetHost()));
+ FwdServer *fs = servers;
+ if (!ia) {
+ debugs(44, 3, "Unknown host: " << (fs->_peer.valid() ? fs->_peer->host : request->url.host()));
// discard any previous error.
- delete psstate->lastError;
- psstate->lastError = NULL;
+ delete lastError;
+ lastError = nullptr;
if (fs->code == HIER_DIRECT) {
- psstate->lastError = new ErrorState(ERR_DNS_FAIL, Http::scServiceUnavailable, psstate->request);
- psstate->lastError->dnsError = details.error;
+ lastError = new ErrorState(ERR_DNS_FAIL, Http::scServiceUnavailable, request, al);
+ lastError->dnsError = details.error;
}
}
+ // else noteIp() calls have already processed all IPs in *ia
- psstate->servers = fs->next;
- cbdataReferenceDone(fs->_peer);
- memFree(fs, MEM_FWD_SERVER);
+ servers = fs->next;
+ delete fs;
- // see if more paths can be found
- peerSelectDnsPaths(psstate);
+ // continue resolving selected peers
+ resolveSelected();
}
-static int
-peerCheckNetdbDirect(ps_state * psstate)
+int
+PeerSelector::checkNetdbDirect()
{
#if USE_ICMP
CachePeer *p;
int myrtt;
int myhops;
- if (psstate->direct == DIRECT_NO)
+ if (direct == DIRECT_NO)
return 0;
/* base lookup on RTT and Hops if ICMP NetDB is enabled. */
- myrtt = netdbHostRtt(psstate->request->GetHost());
-
- debugs(44, 3, "peerCheckNetdbDirect: MY RTT = " << myrtt << " msec");
- debugs(44, 3, "peerCheckNetdbDirect: minimum_direct_rtt = " << Config.minDirectRtt << " msec");
+ myrtt = netdbHostRtt(request->url.host());
+ debugs(44, 3, "MY RTT = " << myrtt << " msec");
+ debugs(44, 3, "minimum_direct_rtt = " << Config.minDirectRtt << " msec");
if (myrtt && myrtt <= Config.minDirectRtt)
return 1;
- myhops = netdbHostHops(psstate->request->GetHost());
+ myhops = netdbHostHops(request->url.host());
- debugs(44, 3, "peerCheckNetdbDirect: MY hops = " << myhops);
- debugs(44, 3, "peerCheckNetdbDirect: minimum_direct_hops = " << Config.minDirectHops);
+ debugs(44, 3, "MY hops = " << myhops);
+ debugs(44, 3, "minimum_direct_hops = " << Config.minDirectHops);
if (myhops && myhops <= Config.minDirectHops)
return 1;
- p = whichPeer(psstate->closest_parent_miss);
+ p = whichPeer(closest_parent_miss);
- if (p == NULL)
+ if (p == nullptr)
return 0;
- debugs(44, 3, "peerCheckNetdbDirect: closest_parent_miss RTT = " << psstate->ping.p_rtt << " msec");
+ debugs(44, 3, "closest_parent_miss RTT = " << ping.p_rtt << " msec");
- if (myrtt && myrtt <= psstate->ping.p_rtt)
+ if (myrtt && myrtt <= ping.p_rtt)
return 1;
#endif /* USE_ICMP */
return 0;
}
-static void
-peerSelectFoo(ps_state * ps)
+void
+PeerSelector::selectMore()
{
- if (!cbdataReferenceValid(ps->callback_data)) {
- debugs(44, 3, "Aborting peer selection. Parent Job went away.");
- delete ps;
+ if (selectionAborted())
return;
- }
- StoreEntry *entry = ps->entry;
- HttpRequest *request = ps->request;
- debugs(44, 3, request->method << ' ' << request->GetHost());
+ debugs(44, 3, request->method << ' ' << request->url.host());
/** If we don't know whether DIRECT is permitted ... */
- if (ps->direct == DIRECT_UNKNOWN) {
- if (ps->always_direct == ACCESS_DUNNO) {
- debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct] << " (always_direct to be checked)");
+ if (direct == DIRECT_UNKNOWN) {
+ if (always_direct == ACCESS_DUNNO) {
+ debugs(44, 3, "direct = " << DirectStr[direct] << " (always_direct to be checked)");
/** check always_direct; */
- ACLFilledChecklist *ch = new ACLFilledChecklist(Config.accessList.AlwaysDirect, request, NULL);
- ch->al = ps->al;
- ps->acl_checklist = ch;
- ps->acl_checklist->nonBlockingCheck(peerCheckAlwaysDirectDone, ps);
+ ACLFilledChecklist *ch = new ACLFilledChecklist(Config.accessList.AlwaysDirect, request, nullptr);
+ ch->al = al;
+ acl_checklist = ch;
+ acl_checklist->syncAle(request, nullptr);
+ acl_checklist->nonBlockingCheck(CheckAlwaysDirectDone, this);
return;
- } else if (ps->never_direct == ACCESS_DUNNO) {
- debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct] << " (never_direct to be checked)");
+ } else if (never_direct == ACCESS_DUNNO) {
+ debugs(44, 3, "direct = " << DirectStr[direct] << " (never_direct to be checked)");
/** check never_direct; */
- ACLFilledChecklist *ch = new ACLFilledChecklist(Config.accessList.NeverDirect, request, NULL);
- ch->al = ps->al;
- ps->acl_checklist = ch;
- ps->acl_checklist->nonBlockingCheck(peerCheckNeverDirectDone, ps);
+ ACLFilledChecklist *ch = new ACLFilledChecklist(Config.accessList.NeverDirect, request, nullptr);
+ ch->al = al;
+ acl_checklist = ch;
+ acl_checklist->syncAle(request, nullptr);
+ acl_checklist->nonBlockingCheck(CheckNeverDirectDone, this);
return;
} else if (request->flags.noDirect) {
/** if we are accelerating, direct is not an option. */
- ps->direct = DIRECT_NO;
- debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct] << " (forced non-direct)");
+ direct = DIRECT_NO;
+ debugs(44, 3, "direct = " << DirectStr[direct] << " (forced non-direct)");
} else if (request->flags.loopDetected) {
/** if we are in a forwarding-loop, direct is not an option. */
- ps->direct = DIRECT_YES;
- debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct] << " (forwarding loop detected)");
- } else if (peerCheckNetdbDirect(ps)) {
- ps->direct = DIRECT_YES;
- debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct] << " (checkNetdbDirect)");
+ direct = DIRECT_YES;
+ debugs(44, 3, "direct = " << DirectStr[direct] << " (forwarding loop detected)");
+ } else if (checkNetdbDirect()) {
+ direct = DIRECT_YES;
+ debugs(44, 3, "direct = " << DirectStr[direct] << " (checkNetdbDirect)");
} else {
- ps->direct = DIRECT_MAYBE;
- debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct] << " (default)");
+ direct = DIRECT_MAYBE;
+ debugs(44, 3, "direct = " << DirectStr[direct] << " (default)");
}
- debugs(44, 3, "peerSelectFoo: direct = " << DirectStr[ps->direct]);
+ debugs(44, 3, "direct = " << DirectStr[direct]);
}
if (!entry || entry->ping_status == PING_NONE)
- peerSelectPinned(ps);
- if (entry == NULL) {
+ selectPinned();
+ if (entry == nullptr) {
(void) 0;
} else if (entry->ping_status == PING_NONE) {
- peerGetSomeNeighbor(ps);
+ selectSomeNeighbor();
if (entry->ping_status == PING_WAITING)
return;
} else if (entry->ping_status == PING_WAITING) {
- peerGetSomeNeighborReplies(ps);
+ selectSomeNeighborReplies();
+ cancelPingTimeoutMonitoring();
entry->ping_status = PING_DONE;
}
- switch (ps->direct) {
+ switch (direct) {
case DIRECT_YES:
- peerGetSomeDirect(ps);
+ selectSomeDirect();
break;
case DIRECT_NO:
- peerGetSomeParent(ps);
- peerGetAllParents(ps);
+ selectSomeParent();
+ selectAllParents();
break;
default:
if (Config.onoff.prefer_direct)
- peerGetSomeDirect(ps);
+ selectSomeDirect();
if (request->flags.hierarchical || !Config.onoff.nonhierarchical_direct) {
- peerGetSomeParent(ps);
- peerGetAllParents(ps);
+ selectSomeParent();
+ selectAllParents();
}
if (!Config.onoff.prefer_direct)
- peerGetSomeDirect(ps);
+ selectSomeDirect();
break;
}
- // resolve the possible peers
- peerSelectDnsPaths(ps);
+ // end peer selection; start resolving selected peers
+ resolveSelected();
}
-bool peerAllowedToUse(const CachePeer * p, HttpRequest * request);
+bool peerAllowedToUse(const CachePeer *, PeerSelector*);
-/**
- * peerSelectPinned
- *
- * Selects a pinned connection.
- */
-static void
-peerSelectPinned(ps_state * ps)
+/// Selects a pinned connection if it exists, is valid, and is allowed.
+void
+PeerSelector::selectPinned()
{
- HttpRequest *request = ps->request;
+ // TODO: Avoid all repeated calls. Relying on PING_DONE is not enough.
if (!request->pinnedConnection())
return;
- CachePeer *pear = request->pinnedConnection()->pinnedPeer();
- if (Comm::IsConnOpen(request->pinnedConnection()->validatePinnedConnection(request, pear))) {
- if (pear && peerAllowedToUse(pear, request)) {
- peerAddFwdServer(&ps->servers, pear, PINNED);
- if (ps->entry)
- ps->entry->ping_status = PING_DONE; /* Skip ICP */
- } else if (!pear && ps->direct != DIRECT_NO) {
- peerAddFwdServer(&ps->servers, NULL, PINNED);
- if (ps->entry)
- ps->entry->ping_status = PING_DONE; /* Skip ICP */
- }
- }
+
+ const auto peer = request->pinnedConnection()->pinnedPeer();
+ const auto usePinned = peer ? peerAllowedToUse(peer, this) : (direct != DIRECT_NO);
+ // If the pinned connection is prohibited (for this request) then
+ // the initiator must decide whether it is OK to open a new one instead.
+ request->pinnedConnection()->pinning.peerAccessDenied = !usePinned;
+
+ addSelection(peer, PINNED);
+ if (entry)
+ entry->ping_status = PING_DONE; // skip ICP
}
/**
- * peerGetSomeNeighbor
- *
* Selects a neighbor (parent or sibling) based on one of the
* following methods:
* Cache Digests
* ICMP Netdb RTT estimates
* ICP/HTCP queries
*/
-static void
-peerGetSomeNeighbor(ps_state * ps)
+void
+PeerSelector::selectSomeNeighbor()
{
- StoreEntry *entry = ps->entry;
- HttpRequest *request = ps->request;
CachePeer *p;
hier_code code = HIER_NONE;
assert(entry->ping_status == PING_NONE);
- if (ps->direct == DIRECT_YES) {
+ if (direct == DIRECT_YES) {
entry->ping_status = PING_DONE;
return;
}
#if USE_CACHE_DIGESTS
- if ((p = neighborsDigestSelect(request))) {
- if (neighborType(p, request) == PEER_PARENT)
+ if ((p = neighborsDigestSelect(this))) {
+ if (neighborType(p, request->url) == PEER_PARENT)
code = CD_PARENT_HIT;
else
code = CD_SIBLING_HIT;
} else
#endif
- if ((p = netdbClosestParent(request))) {
+ if ((p = netdbClosestParent(this))) {
code = CLOSEST_PARENT;
- } else if (peerSelectIcpPing(request, ps->direct, entry)) {
- debugs(44, 3, "peerSelect: Doing ICP pings");
- ps->ping.start = current_time;
- ps->ping.n_sent = neighborsUdpPing(request,
- entry,
- peerHandlePingReply,
- ps,
- &ps->ping.n_replies_expected,
- &ps->ping.timeout);
-
- if (ps->ping.n_sent == 0)
+ } else if (peerSelectIcpPing(this, direct, entry)) {
+ debugs(44, 3, "Doing ICP pings");
+ ping.start = current_time;
+ ping.n_sent = neighborsUdpPing(request,
+ entry,
+ HandlePingReply,
+ this,
+ &ping.n_replies_expected,
+ &ping.timeout);
+ // TODO: Refactor neighborsUdpPing() to guarantee positive timeouts.
+ if (ping.timeout < 0)
+ ping.timeout = 0;
+
+ if (ping.n_sent == 0)
debugs(44, DBG_CRITICAL, "WARNING: neighborsUdpPing returned 0");
- debugs(44, 3, "peerSelect: " << ps->ping.n_replies_expected <<
- " ICP replies expected, RTT " << ps->ping.timeout <<
+ debugs(44, 3, ping.n_replies_expected <<
+ " ICP replies expected, RTT " << ping.timeout <<
" msec");
- if (ps->ping.n_replies_expected > 0) {
- entry->ping_status = PING_WAITING;
- eventAdd("peerPingTimeout",
- peerPingTimeout,
- ps,
- 0.001 * ps->ping.timeout,
- 0);
+ if (ping.n_replies_expected > 0) {
+ startPingWaiting();
return;
}
}
if (code != HIER_NONE) {
assert(p);
- debugs(44, 3, "peerSelect: " << hier_code_str[code] << "/" << p->host);
- peerAddFwdServer(&ps->servers, p, code);
+ addSelection(p, code);
}
entry->ping_status = PING_DONE;
}
-/*
- * peerGetSomeNeighborReplies
- *
- * Selects a neighbor (parent or sibling) based on ICP/HTCP replies.
- */
-static void
-peerGetSomeNeighborReplies(ps_state * ps)
+/// Selects a neighbor (parent or sibling) based on ICP/HTCP replies.
+void
+PeerSelector::selectSomeNeighborReplies()
{
- HttpRequest *request = ps->request;
- CachePeer *p = NULL;
+ CachePeer *p = nullptr;
hier_code code = HIER_NONE;
- assert(ps->entry->ping_status == PING_WAITING);
- assert(ps->direct != DIRECT_YES);
+ assert(entry->ping_status == PING_WAITING);
+ assert(direct != DIRECT_YES);
- if (peerCheckNetdbDirect(ps)) {
+ if (checkNetdbDirect()) {
code = CLOSEST_DIRECT;
- debugs(44, 3, "peerSelect: " << hier_code_str[code] << "/" << request->GetHost());
- peerAddFwdServer(&ps->servers, NULL, code);
+ addSelection(nullptr, code);
return;
}
- if ((p = ps->hit)) {
- code = ps->hit_type == PEER_PARENT ? PARENT_HIT : SIBLING_HIT;
+ if ((p = hit)) {
+ code = hit_type == PEER_PARENT ? PARENT_HIT : SIBLING_HIT;
} else {
- if (!ps->closest_parent_miss.isAnyAddr()) {
- p = whichPeer(ps->closest_parent_miss);
+ if (!closest_parent_miss.isAnyAddr()) {
+ p = whichPeer(closest_parent_miss);
code = CLOSEST_PARENT_MISS;
- } else if (!ps->first_parent_miss.isAnyAddr()) {
- p = whichPeer(ps->first_parent_miss);
+ } else if (!first_parent_miss.isAnyAddr()) {
+ p = whichPeer(first_parent_miss);
code = FIRST_PARENT_MISS;
}
}
if (p && code != HIER_NONE) {
- debugs(44, 3, "peerSelect: " << hier_code_str[code] << "/" << p->host);
- peerAddFwdServer(&ps->servers, p, code);
+ addSelection(p, code);
}
}
-/*
- * peerGetSomeDirect
- *
- * Simply adds a 'direct' entry to the FwdServers list if this
- * request can be forwarded directly to the origin server
- */
-static void
-peerGetSomeDirect(ps_state * ps)
+/// Adds a "direct" entry if the request can be forwarded to the origin server.
+void
+PeerSelector::selectSomeDirect()
{
- if (ps->direct == DIRECT_NO)
+ if (direct == DIRECT_NO)
return;
/* WAIS is not implemented natively */
- if (ps->request->url.getScheme() == AnyP::PROTO_WAIS)
+ if (request->url.getScheme() == AnyP::PROTO_WAIS)
return;
- peerAddFwdServer(&ps->servers, NULL, HIER_DIRECT);
+ addSelection(nullptr, HIER_DIRECT);
}
-static void
-peerGetSomeParent(ps_state * ps)
+void
+PeerSelector::selectSomeParent()
{
CachePeer *p;
- HttpRequest *request = ps->request;
hier_code code = HIER_NONE;
- debugs(44, 3, request->method << ' ' << request->GetHost());
+ debugs(44, 3, request->method << ' ' << request->url.host());
- if (ps->direct == DIRECT_YES)
+ if (direct == DIRECT_YES)
return;
- if ((p = peerSourceHashSelectParent(request))) {
+ if ((p = peerSourceHashSelectParent(this))) {
code = SOURCEHASH_PARENT;
#if USE_AUTH
- } else if ((p = peerUserHashSelectParent(request))) {
+ } else if ((p = peerUserHashSelectParent(this))) {
code = USERHASH_PARENT;
#endif
- } else if ((p = carpSelectParent(request))) {
+ } else if ((p = carpSelectParent(this))) {
code = CARP;
- } else if ((p = getRoundRobinParent(request))) {
+ } else if ((p = getRoundRobinParent(this))) {
code = ROUNDROBIN_PARENT;
- } else if ((p = getWeightedRoundRobinParent(request))) {
+ } else if ((p = getWeightedRoundRobinParent(this))) {
code = ROUNDROBIN_PARENT;
- } else if ((p = getFirstUpParent(request))) {
+ } else if ((p = getFirstUpParent(this))) {
code = FIRSTUP_PARENT;
- } else if ((p = getDefaultParent(request))) {
+ } else if ((p = getDefaultParent(this))) {
code = DEFAULT_PARENT;
}
if (code != HIER_NONE) {
- debugs(44, 3, "peerSelect: " << hier_code_str[code] << "/" << p->host);
- peerAddFwdServer(&ps->servers, p, code);
+ addSelection(p, code);
}
}
-/* Adds alive parents. Used as a last resort for never_direct.
- */
-static void
-peerGetAllParents(ps_state * ps)
+/// Adds alive parents. Used as a last resort for never_direct.
+void
+PeerSelector::selectAllParents()
{
- CachePeer *p;
- HttpRequest *request = ps->request;
/* Add all alive parents */
- for (p = Config.peers; p; p = p->next) {
+ for (const auto &peer: CurrentCachePeers()) {
+ const auto p = peer.get();
/* XXX: neighbors.c lacks a public interface for enumerating
* parents to a request so we have to dig some here..
*/
- if (neighborType(p, request) != PEER_PARENT)
+ if (neighborType(p, request->url) != PEER_PARENT)
continue;
- if (!peerHTTPOkay(p, request))
+ if (!peerHTTPOkay(p, this))
continue;
- debugs(15, 3, "peerGetAllParents: adding alive parent " << p->host);
-
- peerAddFwdServer(&ps->servers, p, ANY_OLD_PARENT);
+ addSelection(p, ANY_OLD_PARENT);
}
/* XXX: should add dead parents here, but it is currently
* simply are not configured to handle the request.
*/
/* Add default parent as a last resort */
- if ((p = getDefaultParent(request))) {
- peerAddFwdServer(&ps->servers, p, DEFAULT_PARENT);
+ if (const auto p = getDefaultParent(this)) {
+ addSelection(p, DEFAULT_PARENT);
}
}
-static void
-peerPingTimeout(void *data)
+void
+PeerSelector::handlePingTimeout()
{
- ps_state *psstate = (ps_state *)data;
- StoreEntry *entry = psstate->entry;
+ debugs(44, 3, url());
- if (entry)
- debugs(44, 3, "peerPingTimeout: '" << entry->url() << "'" );
+ // do nothing if ping reply came while handlePingTimeout() was queued
+ if (!entry || entry->ping_status != PING_WAITING)
+ return;
- if (!cbdataReferenceValid(psstate->callback_data)) {
- /* request aborted */
- entry->ping_status = PING_DONE;
- cbdataReferenceDone(psstate->callback_data);
- delete psstate;
+ entry->ping_status = PING_DONE;
+
+ if (selectionAborted())
return;
- }
++PeerStats.timeouts;
- psstate->ping.timedout = 1;
- peerSelectFoo(psstate);
+ ping.timedout = 1;
+ selectMore();
+}
+
+void
+PeerSelector::HandlePingTimeout(PeerSelector *selector)
+{
+ selector->handlePingTimeout();
}
void
peerSelectInit(void)
{
memset(&PeerStats, '\0', sizeof(PeerStats));
- memDataInit(MEM_FWD_SERVER, "FwdServer", sizeof(FwdServer), 0);
}
-static void
-peerIcpParentMiss(CachePeer * p, icp_common_t * header, ps_state * ps)
+void
+PeerSelector::handleIcpParentMiss(CachePeer *p, icp_common_t *header)
{
int rtt;
int hops = (header->pad >> 16) & 0xFFFF;
if (rtt > 0 && rtt < 0xFFFF)
- netdbUpdatePeer(ps->request, p, rtt, hops);
+ netdbUpdatePeer(request->url, p, rtt, hops);
- if (rtt && (ps->ping.p_rtt == 0 || rtt < ps->ping.p_rtt)) {
- ps->closest_parent_miss = p->in_addr;
- ps->ping.p_rtt = rtt;
+ if (rtt && (ping.p_rtt == 0 || rtt < ping.p_rtt)) {
+ closest_parent_miss = p->in_addr;
+ ping.p_rtt = rtt;
}
}
}
+#else
+ (void)header;
#endif /* USE_ICMP */
/* if closest-only is set, then don't allow FIRST_PARENT_MISS */
return;
/* set FIRST_MISS if there is no CLOSEST parent */
- if (!ps->closest_parent_miss.isAnyAddr())
+ if (!closest_parent_miss.isAnyAddr())
return;
- rtt = (tvSubMsec(ps->ping.start, current_time) - p->basetime) / p->weight;
+ rtt = (tvSubMsec(ping.start, current_time) - p->basetime) / p->weight;
if (rtt < 1)
rtt = 1;
- if (ps->first_parent_miss.isAnyAddr() || rtt < ps->ping.w_rtt) {
- ps->first_parent_miss = p->in_addr;
- ps->ping.w_rtt = rtt;
+ if (first_parent_miss.isAnyAddr() || rtt < ping.w_rtt) {
+ first_parent_miss = p->in_addr;
+ ping.w_rtt = rtt;
}
}
-static void
-peerHandleIcpReply(CachePeer * p, peer_t type, icp_common_t * header, void *data)
+void
+PeerSelector::handleIcpReply(CachePeer *p, const peer_t type, icp_common_t *header)
{
- ps_state *psstate = (ps_state *)data;
icp_opcode op = header->getOpCode();
- debugs(44, 3, "peerHandleIcpReply: " << icp_opcode_str[op] << " " << psstate->entry->url() );
+ debugs(44, 3, icp_opcode_str[op] << ' ' << url());
#if USE_CACHE_DIGESTS && 0
/* do cd lookup to count false misses */
if (p && request)
peerNoteDigestLookup(request, p,
- peerDigestLookup(p, request, psstate->entry));
+ peerDigestLookup(p, this));
#endif
- ++ psstate->ping.n_recv;
+ ++ping.n_recv;
if (op == ICP_MISS || op == ICP_DECHO) {
if (type == PEER_PARENT)
- peerIcpParentMiss(p, header, psstate);
+ handleIcpParentMiss(p, header);
} else if (op == ICP_HIT) {
- psstate->hit = p;
- psstate->hit_type = type;
- peerSelectFoo(psstate);
+ hit = p;
+ hit_type = type;
+ selectMore();
return;
}
- if (psstate->ping.n_recv < psstate->ping.n_replies_expected)
+ if (ping.n_recv < ping.n_replies_expected)
return;
- peerSelectFoo(psstate);
+ selectMore();
}
#if USE_HTCP
-static void
-peerHandleHtcpReply(CachePeer * p, peer_t type, HtcpReplyData * htcp, void *data)
+void
+PeerSelector::handleHtcpReply(CachePeer *p, const peer_t type, HtcpReplyData *htcp)
{
- ps_state *psstate = (ps_state *)data;
- debugs(44, 3, "peerHandleHtcpReply: " <<
- (htcp->hit ? "HIT" : "MISS") << " " <<
- psstate->entry->url() );
- ++ psstate->ping.n_recv;
+ debugs(44, 3, (htcp->hit ? "HIT" : "MISS") << ' ' << url());
+ ++ping.n_recv;
if (htcp->hit) {
- psstate->hit = p;
- psstate->hit_type = type;
- peerSelectFoo(psstate);
+ hit = p;
+ hit_type = type;
+ selectMore();
return;
}
if (type == PEER_PARENT)
- peerHtcpParentMiss(p, htcp, psstate);
+ handleHtcpParentMiss(p, htcp);
- if (psstate->ping.n_recv < psstate->ping.n_replies_expected)
+ if (ping.n_recv < ping.n_replies_expected)
return;
- peerSelectFoo(psstate);
+ selectMore();
}
-static void
-peerHtcpParentMiss(CachePeer * p, HtcpReplyData * htcp, ps_state * ps)
+void
+PeerSelector::handleHtcpParentMiss(CachePeer *p, HtcpReplyData *htcp)
{
int rtt;
if (htcp->cto.rtt > 0) {
rtt = (int) htcp->cto.rtt * 1000;
int hops = (int) htcp->cto.hops * 1000;
- netdbUpdatePeer(ps->request, p, rtt, hops);
+ netdbUpdatePeer(request->url, p, rtt, hops);
- if (rtt && (ps->ping.p_rtt == 0 || rtt < ps->ping.p_rtt)) {
- ps->closest_parent_miss = p->in_addr;
- ps->ping.p_rtt = rtt;
+ if (rtt && (ping.p_rtt == 0 || rtt < ping.p_rtt)) {
+ closest_parent_miss = p->in_addr;
+ ping.p_rtt = rtt;
}
}
}
+#else
+ (void)htcp;
#endif /* USE_ICMP */
/* if closest-only is set, then don't allow FIRST_PARENT_MISS */
return;
/* set FIRST_MISS if there is no CLOSEST parent */
- if (!ps->closest_parent_miss.isAnyAddr())
+ if (!closest_parent_miss.isAnyAddr())
return;
- rtt = (tvSubMsec(ps->ping.start, current_time) - p->basetime) / p->weight;
+ rtt = (tvSubMsec(ping.start, current_time) - p->basetime) / p->weight;
if (rtt < 1)
rtt = 1;
- if (ps->first_parent_miss.isAnyAddr() || rtt < ps->ping.w_rtt) {
- ps->first_parent_miss = p->in_addr;
- ps->ping.w_rtt = rtt;
+ if (first_parent_miss.isAnyAddr() || rtt < ping.w_rtt) {
+ first_parent_miss = p->in_addr;
+ ping.w_rtt = rtt;
}
}
#endif
-static void
-peerHandlePingReply(CachePeer * p, peer_t type, AnyP::ProtocolType proto, void *pingdata, void *data)
+void
+PeerSelector::HandlePingReply(CachePeer * p, peer_t type, AnyP::ProtocolType proto, void *pingdata, void *data)
{
if (proto == AnyP::PROTO_ICP)
- peerHandleIcpReply(p, type, (icp_common_t *)pingdata, data);
+ static_cast<PeerSelector*>(data)->handleIcpReply(p, type, static_cast<icp_common_t*>(pingdata));
#if USE_HTCP
else if (proto == AnyP::PROTO_HTCP)
- peerHandleHtcpReply(p, type, (HtcpReplyData *)pingdata, data);
+ static_cast<PeerSelector*>(data)->handleHtcpReply(p, type, static_cast<HtcpReplyData*>(pingdata));
#endif
else
- debugs(44, DBG_IMPORTANT, "peerHandlePingReply: unknown protocol " << proto);
+ debugs(44, DBG_IMPORTANT, "ERROR: ignoring an ICP reply with unknown protocol " << proto);
}
-static void
-peerAddFwdServer(FwdServer ** FSVR, CachePeer * p, hier_code code)
+void
+PeerSelector::addSelection(CachePeer *peer, const hier_code code)
{
- FwdServer *fs = (FwdServer *)memAllocate(MEM_FWD_SERVER);
- debugs(44, 5, "peerAddFwdServer: adding " <<
- (p ? p->host : "DIRECT") << " " <<
- hier_code_str[code] );
- fs->_peer = cbdataReference(p);
- fs->code = code;
-
- while (*FSVR)
- FSVR = &(*FSVR)->next;
+ // Find the end of the servers list. Bail on a duplicate destination.
+ auto **serversTail = &servers;
+ while (const auto server = *serversTail) {
+ // There can be at most one PINNED destination.
+ // Non-PINNED destinations are uniquely identified by their CachePeer
+ // (even though a DIRECT destination might match a cache_peer address).
+ // TODO: We may still add duplicates because the same peer could have
+ // been removed from `servers` already (and given to the requestor).
+ const bool duplicate = (server->code == PINNED) ?
+ (code == PINNED) : (server->_peer == peer);
+ if (duplicate) {
+ debugs(44, 3, "skipping " << PeerSelectionDumper(this, peer, code) <<
+ "; have " << PeerSelectionDumper(this, server->_peer.get(), server->code));
+ return;
+ }
+ serversTail = &server->next;
+ }
- *FSVR = fs;
+ debugs(44, 3, "adding " << PeerSelectionDumper(this, peer, code));
+ *serversTail = new FwdServer(peer, code);
}
-ps_state::ps_state() : request (NULL),
- entry (NULL),
+PeerSelector::PeerSelector(PeerSelectionInitiator *initiator):
+ request(nullptr),
+ entry (nullptr),
always_direct(Config.accessList.AlwaysDirect?ACCESS_DUNNO:ACCESS_DENIED),
never_direct(Config.accessList.NeverDirect?ACCESS_DUNNO:ACCESS_DENIED),
direct(DIRECT_UNKNOWN),
- callback (NULL),
- callback_data (NULL),
- lastError(NULL),
- servers (NULL),
+ lastError(nullptr),
+ servers (nullptr),
first_parent_miss(),
closest_parent_miss(),
- hit(NULL),
+ hit(nullptr),
hit_type(PEER_NONE),
- acl_checklist (NULL)
+ acl_checklist (nullptr),
+ initiator_(initiator)
{
; // no local defaults.
}
-const char *
-ps_state::url() const
+const SBuf
+PeerSelector::url() const
{
if (entry)
- return entry->url();
+ return SBuf(entry->url());
if (request)
- return urlCanonical(request);
+ return request->effectiveRequestUri();
+
+ static const SBuf noUrl("[no URL]");
+ return noUrl;
+}
+
+/// \returns valid/interested peer initiator or nil
+PeerSelectionInitiator *
+PeerSelector::interestedInitiator()
+{
+ const auto initiator = initiator_.valid();
+
+ if (!initiator) {
+ debugs(44, 3, id << " initiator gone");
+ return nullptr;
+ }
+
+ if (!initiator->subscribed) {
+ debugs(44, 3, id << " initiator lost interest");
+ return nullptr;
+ }
+
+ debugs(44, 7, id);
+ return initiator;
+}
+
+bool
+PeerSelector::wantsMoreDestinations() const {
+ const auto maxCount = Config.forward_max_tries;
+ return maxCount >= 0 && foundPaths < static_cast<size_t>(maxCount);
+}
+
+void
+PeerSelector::handlePath(const Comm::ConnectionPointer &path, FwdServer &fs)
+{
+ ++foundPaths;
+
+ if (path) {
+ path->peerType = fs.code;
+ path->setPeer(fs._peer.get());
+
+ // check for a configured outgoing address for this destination...
+ getOutgoingAddress(request, path);
+ debugs(44, 2, id << " found " << path << ", destination #" << foundPaths << " for " << url());
+ } else
+ debugs(44, 2, id << " found pinned, destination #" << foundPaths << " for " << url());
- return "[no URL]";
+ request->hier.ping = ping; // may be updated later
+
+ debugs(44, 2, " always_direct = " << always_direct);
+ debugs(44, 2, " never_direct = " << never_direct);
+ debugs(44, 2, " timedout = " << ping.timedout);
+
+ if (const auto initiator = interestedInitiator())
+ initiator->noteDestination(path);
}
+InstanceIdDefinitions(PeerSelector, "PeerSelector");
+
ping_data::ping_data() :
n_sent(0),
n_recv(0),
timeout(0),
timedout(0),
w_rtt(0),
- p_rtt(0)
+ p_rtt(0),
+ monitorRegistration(PingMonitor().npos())
{
start.tv_sec = 0;
start.tv_usec = 0;
stop.tv_usec = 0;
}
+timeval
+ping_data::deadline() const
+{
+ timeval timeInterval;
+ timeInterval.tv_sec = timeout / 1000;
+ timeInterval.tv_usec = (timeout % 1000) * 1000;
+
+ timeval result;
+ tvAdd(result, start, timeInterval);
+ return result;
+}
+