/*
- * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2018 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/InstanceId.h"
#include "CachePeer.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 "ip/tools.h"
#include "ipcache.h"
-#include "Mem.h"
#include "neighbors.h"
#include "peer_sourcehash.h"
#include "peer_userhash.h"
#include "Store.h"
#include "URL.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;
} PeerStats;
"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) {}
-CBDATA_CLASS_INIT(ps_state);
+ const PeerSelector * const selector; ///< selection parameters
+ const CachePeer * const peer; ///< successful selection info
+ const hier_code code; ///< selection algorithm
+};
-ps_state::~ps_state()
+CBDATA_CLASS_INIT(PeerSelector);
+
+/// 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->host;
+ else if (fsd.selector) // useful for DIRECT and gone PINNED destinations
+ os << '#' << fsd.selector->request->url.host();
+
+ return os;
+}
+
+PeerSelector::~PeerSelector()
{
while (servers) {
FwdServer *next = servers->next;
- cbdataReferenceDone(servers->_peer);
- memFree(servers, MEM_FWD_SERVER);
+ delete servers;
servers = next;
}
debugs(44, 3, entry->url());
if (entry->ping_status == PING_WAITING)
- eventDelete(peerPingTimeout, this);
+ eventDelete(HandlePingTimeout, this);
entry->ping_status = PING_DONE;
}
if (acl_checklist) {
- debugs(44, DBG_IMPORTANT, "calling aclChecklistFree() from ps_state destructor");
+ debugs(44, DBG_IMPORTANT, "BUG: peer selector gone while waiting for a slow ACL");
delete acl_checklist;
}
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;
n = neighborsCount(request);
- 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;
+ const auto selector = new PeerSelector(initiator);
- psstate->request = request;
- HTTPMSGLOCK(psstate->request);
- psstate->al = al;
+ selector->request = request;
+ HTTPMSGLOCK(selector->request);
+ selector->al = al;
- psstate->entry = entry;
- psstate->paths = paths;
-
- 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 allow_t 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(allow_t answer, void *data)
+{
+ static_cast<PeerSelector*>(data)->checkNeverDirectDone(answer);
+}
+
+void
+PeerSelector::checkAlwaysDirectDone(const allow_t 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(allow_t 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;
- if (!cbdataReferenceValid(psstate->callback_data)) {
- debugs(44, 3, "Aborting peer selection. Parent Job went away.");
- delete psstate;
+ debugs(44, 3, "Aborting peer selection: Initiator gone or lost interest.");
+ delete this;
+ return true;
+}
+
+/// 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;
}
// 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) {
+ if (fs) {
+ assert(fs == servers);
while (fs) {
- FwdServer *next = fs->next;
- cbdataReferenceDone(fs->_peer);
- memFree(fs, MEM_FWD_SERVER);
- fs = next;
+ 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);
+
+ ping.stop = current_time;
+ request->hier.ping = ping; // final result
- 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.
+ 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;
+ p->remote.port(peer ? peer->http_port : request->url.port());
+ 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 = NULL;
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);
+ 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)
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);
+ ch->al = al;
+ acl_checklist = ch;
+ 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);
+ ch->al = al;
+ acl_checklist = ch;
+ 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);
+ selectPinned();
if (entry == NULL) {
(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();
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);
-/**
- * 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 bool usePinned = pear ? peerAllowedToUse(pear, request) : (direct != DIRECT_NO);
+ if (usePinned) {
+ addSelection(pear, PINNED);
+ if (entry)
+ entry->ping_status = PING_DONE; // skip ICP
}
}
+ // If the pinned connection is prohibited (for this request) or gone, then
+ // the initiator must decide whether it is OK to open a new one instead.
}
/**
- * 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 (neighborType(p, request->url) == PEER_PARENT)
code = CD_PARENT_HIT;
else
code = CD_SIBLING_HIT;
#endif
if ((p = netdbClosestParent(request))) {
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(request, 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);
+
+ 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) {
+ if (ping.n_replies_expected > 0) {
entry->ping_status = PING_WAITING;
- eventAdd("peerPingTimeout",
- peerPingTimeout,
- ps,
- 0.001 * ps->ping.timeout,
+ eventAdd("PeerSelector::HandlePingTimeout",
+ HandlePingTimeout,
+ this,
+ 0.001 * ping.timeout,
0);
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;
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 (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) {
* 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))
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
*/
/* Add default parent as a last resort */
if ((p = getDefaultParent(request))) {
- peerAddFwdServer(&ps->servers, p, DEFAULT_PARENT);
+ 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() << "'" );
-
- if (!cbdataReferenceValid(psstate->callback_data)) {
- /* request aborted */
entry->ping_status = PING_DONE;
- cbdataReferenceDone(psstate->callback_data);
- delete psstate;
+
+ if (selectionAborted())
return;
- }
++PeerStats.timeouts;
- psstate->ping.timedout = 1;
- peerSelectFoo(psstate);
+ ping.timedout = 1;
+ selectMore();
+}
+
+void
+PeerSelector::HandlePingTimeout(void *data)
+{
+ static_cast<PeerSelector*>(data)->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;
}
}
}
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, request, entry));
#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;
}
}
}
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)
-{
- 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;
-
- *FSVR = fs;
-}
-
-ps_state::ps_state() : request (NULL),
- entry (NULL),
- 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),
- first_parent_miss(),
- closest_parent_miss(),
- hit(NULL),
- hit_type(PEER_NONE),
- acl_checklist (NULL)
+void
+PeerSelector::addSelection(CachePeer *peer, const hier_code code)
+{
+ // 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).
+ 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;
+ }
+
+ debugs(44, 3, "adding " << PeerSelectionDumper(this, peer, code));
+ *serversTail = new FwdServer(peer, code);
+}
+
+PeerSelector::PeerSelector(PeerSelectionInitiator *initiator):
+ request(nullptr),
+ entry (NULL),
+ always_direct(Config.accessList.AlwaysDirect?ACCESS_DUNNO:ACCESS_DENIED),
+ never_direct(Config.accessList.NeverDirect?ACCESS_DUNNO:ACCESS_DENIED),
+ direct(DIRECT_UNKNOWN),
+ lastError(NULL),
+ servers (NULL),
+ first_parent_miss(),
+ closest_parent_miss(),
+ hit(NULL),
+ hit_type(PEER_NONE),
+ acl_checklist (NULL),
+ 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<std::make_unsigned<decltype(maxCount)>::type>(maxCount);
+}
- return "[no URL]";
+void
+PeerSelector::handlePath(Comm::ConnectionPointer &path, FwdServer &fs)
+{
+ ++foundPaths;
+
+ path->peerType = fs.code;
+ path->setPeer(fs._peer.get());
+
+ // check for a configured outgoing address for this destination...
+ getOutgoingAddress(request, path);
+
+ request->hier.ping = ping; // may be updated later
+
+ debugs(44, 2, id << " found " << path << ", destination #" << foundPaths << " for " << url());
+ 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),
- n_replies_expected(0),
- timeout(0),
- timedout(0),
- w_rtt(0),
- p_rtt(0)
+ n_sent(0),
+ n_recv(0),
+ n_replies_expected(0),
+ timeout(0),
+ timedout(0),
+ w_rtt(0),
+ p_rtt(0)
{
start.tv_sec = 0;
start.tv_usec = 0;
stop.tv_sec = 0;
stop.tv_usec = 0;
}
+