2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 44 Peer Selection Algorithm */
12 #include "acl/FilledChecklist.h"
13 #include "base/InstanceId.h"
14 #include "CachePeer.h"
16 #include "client_side.h"
17 #include "dns/LookupDetails.h"
18 #include "errorpage.h"
22 #include "hier_code.h"
24 #include "http/Stream.h"
25 #include "HttpRequest.h"
26 #include "icmp/net_db.h"
30 #include "neighbors.h"
31 #include "peer_sourcehash.h"
32 #include "peer_userhash.h"
33 #include "PeerSelectState.h"
34 #include "SquidConfig.h"
35 #include "SquidTime.h"
39 * A CachePeer which has been selected as a possible destination.
40 * Listed as pointers here so as to prevent duplicates being added but will
41 * be converted to a set of IP address path options before handing back out
44 * Certain connection flags and outgoing settings will also be looked up and
45 * set based on the received request and CachePeer settings before handing back.
49 MEMPROXY_CLASS(FwdServer
);
52 FwdServer(CachePeer
*p
, hier_code c
) :
58 CbcPointer
<CachePeer
> _peer
; /* NULL --> origin server */
67 static const char *DirectStr
[] = {
74 /// a helper class to report a selected destination (for debugging)
75 class PeerSelectionDumper
78 PeerSelectionDumper(const PeerSelector
* const aSelector
, const CachePeer
* const aPeer
, const hier_code aCode
):
79 selector(aSelector
), peer(aPeer
), code(aCode
) {}
81 const PeerSelector
* const selector
; ///< selection parameters
82 const CachePeer
* const peer
; ///< successful selection info
83 const hier_code code
; ///< selection algorithm
86 CBDATA_CLASS_INIT(PeerSelector
);
88 /// prints PeerSelectionDumper (for debugging)
90 operator <<(std::ostream
&os
, const PeerSelectionDumper
&fsd
)
92 os
<< hier_code_str
[fsd
.code
];
95 os
<< '/' << fsd
.peer
->host
;
96 else if (fsd
.selector
) // useful for DIRECT and gone PINNED destinations
97 os
<< '#' << fsd
.selector
->request
->url
.host();
102 PeerSelector::~PeerSelector()
105 FwdServer
*next
= servers
->next
;
111 debugs(44, 3, entry
->url());
113 if (entry
->ping_status
== PING_WAITING
)
114 eventDelete(HandlePingTimeout
, this);
116 entry
->ping_status
= PING_DONE
;
120 debugs(44, DBG_IMPORTANT
, "BUG: peer selector gone while waiting for a slow ACL");
121 delete acl_checklist
;
124 HTTPMSGUNLOCK(request
);
127 assert(entry
->ping_status
!= PING_WAITING
);
128 entry
->unlock("peerSelect");
136 peerSelectIcpPing(PeerSelector
*ps
, int direct
, StoreEntry
* entry
)
139 HttpRequest
*request
= ps
->request
;
143 assert(entry
->ping_status
== PING_NONE
);
144 assert(direct
!= DIRECT_YES
);
145 debugs(44, 3, entry
->url());
147 if (!request
->flags
.hierarchical
&& direct
!= DIRECT_NO
)
150 if (EBIT_TEST(entry
->flags
, KEY_PRIVATE
) && !neighbors_do_private_keys
)
151 if (direct
!= DIRECT_NO
)
154 n
= neighborsCount(ps
);
156 debugs(44, 3, "counted " << n
<< " neighbors");
162 peerSelect(PeerSelectionInitiator
*initiator
,
163 HttpRequest
* request
,
164 AccessLogEntry::Pointer
const &al
,
168 debugs(44, 3, *entry
<< ' ' << entry
->url());
170 debugs(44, 3, request
->method
);
172 const auto selector
= new PeerSelector(initiator
);
174 selector
->request
= request
;
175 HTTPMSGLOCK(selector
->request
);
178 selector
->entry
= entry
;
180 #if USE_CACHE_DIGESTS
182 request
->hier
.peer_select_start
= current_time
;
187 selector
->entry
->lock("peerSelect");
189 selector
->selectMore();
193 PeerSelectionInitiator::startSelectingDestinations(HttpRequest
*request
, const AccessLogEntry::Pointer
&ale
, StoreEntry
*entry
)
196 peerSelect(this, request
, ale
, entry
);
197 // and wait for noteDestination() and/or noteDestinationsEnd() calls
201 PeerSelector::checkNeverDirectDone(const Acl::Answer answer
)
203 acl_checklist
= nullptr;
204 debugs(44, 3, answer
);
205 never_direct
= answer
;
208 /** if never_direct says YES, do that. */
210 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (never_direct allow)");
212 case ACCESS_DENIED
: // not relevant.
213 case ACCESS_DUNNO
: // not relevant.
215 case ACCESS_AUTH_REQUIRED
:
216 debugs(44, DBG_IMPORTANT
, "WARNING: never_direct resulted in " << answer
<< ". Username ACLs are not reliable here.");
223 PeerSelector::CheckNeverDirectDone(Acl::Answer answer
, void *data
)
225 static_cast<PeerSelector
*>(data
)->checkNeverDirectDone(answer
);
229 PeerSelector::checkAlwaysDirectDone(const Acl::Answer answer
)
231 acl_checklist
= nullptr;
232 debugs(44, 3, answer
);
233 always_direct
= answer
;
236 /** if always_direct says YES, do that. */
238 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (always_direct allow)");
240 case ACCESS_DENIED
: // not relevant.
241 case ACCESS_DUNNO
: // not relevant.
243 case ACCESS_AUTH_REQUIRED
:
244 debugs(44, DBG_IMPORTANT
, "WARNING: always_direct resulted in " << answer
<< ". Username ACLs are not reliable here.");
251 PeerSelector::CheckAlwaysDirectDone(Acl::Answer answer
, void *data
)
253 static_cast<PeerSelector
*>(data
)->checkAlwaysDirectDone(answer
);
256 /// \returns true (after destroying "this") if the peer initiator is gone
257 /// \returns false (without side effects) otherwise
259 PeerSelector::selectionAborted()
261 if (interestedInitiator())
264 debugs(44, 3, "Aborting peer selection: Initiator gone or lost interest.");
269 /// A single DNS resolution loop iteration: Converts selected FwdServer to IPs.
271 PeerSelector::resolveSelected()
273 if (selectionAborted())
276 FwdServer
*fs
= servers
;
278 // Bug 3243: CVE 2009-0801
279 // Bypass of browser same-origin access control in intercepted communication
280 // To resolve this we must use only the original client destination when going DIRECT
281 // on intercepted traffic which failed Host verification
282 const HttpRequest
*req
= request
;
283 const bool isIntercepted
= !req
->flags
.redirected
&&
284 (req
->flags
.intercepted
|| req
->flags
.interceptTproxy
);
285 const bool useOriginalDst
= Config
.onoff
.client_dst_passthru
|| !req
->flags
.hostVerified
;
286 const bool choseDirect
= fs
&& fs
->code
== HIER_DIRECT
;
287 if (isIntercepted
&& useOriginalDst
&& choseDirect
) {
288 // check the client is still around before using any of its details
289 if (req
->clientConnectionManager
.valid()) {
290 // construct a "result" adding the ORIGINAL_DST to the set instead of DIRECT
291 Comm::ConnectionPointer p
= new Comm::Connection();
292 p
->remote
= req
->clientConnectionManager
->clientConnection
->local
;
293 fs
->code
= ORIGINAL_DST
; // fs->code is DIRECT. This fixes the display.
297 // clear the used fs and continue
304 if (fs
&& fs
->code
== PINNED
) {
305 // Nil path signals a PINNED destination selection. Our initiator should
306 // borrow and use clientConnectionManager's pinned connection object
307 // (regardless of that connection destination).
308 handlePath(nullptr, *fs
);
315 // convert the list of FwdServer destinations into destinations IP addresses
316 if (fs
&& wantsMoreDestinations()) {
317 // send the next one off for DNS lookup.
318 const char *host
= fs
->_peer
.valid() ? fs
->_peer
->host
: request
->url
.host();
319 debugs(44, 2, "Find IP destination for: " << url() << "' via " << host
);
320 Dns::nbgethostbyname(host
, this);
324 // Bug 3605: clear any extra listed FwdServer destinations, when the options exceeds max_foward_tries.
325 // due to the allocation method of fs, we must deallocate each manually.
326 // TODO: use a std::list so we can get the size and abort adding whenever the selection loops reach Config.forward_max_tries
328 assert(fs
== servers
);
336 // done with DNS lookups. pass back to caller
338 debugs(44, 2, id
<< " found all " << foundPaths
<< " destinations for " << url());
339 debugs(44, 2, " always_direct = " << always_direct
);
340 debugs(44, 2, " never_direct = " << never_direct
);
341 debugs(44, 2, " timedout = " << ping
.timedout
);
343 ping
.stop
= current_time
;
344 request
->hier
.ping
= ping
; // final result
346 if (lastError
&& foundPaths
) {
347 // nobody cares about errors if we found destinations despite them
348 debugs(44, 3, "forgetting the last error");
353 if (const auto initiator
= interestedInitiator())
354 initiator
->noteDestinationsEnd(lastError
);
355 lastError
= nullptr; // initiator owns the ErrorState object now
360 PeerSelector::noteLookup(const Dns::LookupDetails
&details
)
362 /* ignore lookup delays that occurred after the initiator moved on */
364 if (selectionAborted())
367 if (!wantsMoreDestinations())
370 request
->recordLookup(details
);
374 PeerSelector::noteIp(const Ip::Address
&ip
)
376 if (selectionAborted())
379 if (!wantsMoreDestinations())
382 const auto peer
= servers
->_peer
.valid();
384 // for TPROXY spoofing, we must skip unusable addresses
385 if (request
->flags
.spoofClientIp
&& !(peer
&& peer
->options
.no_tproxy
) ) {
386 if (ip
.isIPv4() != request
->client_addr
.isIPv4())
387 return; // cannot spoof the client address on this link
390 Comm::ConnectionPointer p
= new Comm::Connection();
392 p
->remote
.port(peer
? peer
->http_port
: request
->url
.port());
393 handlePath(p
, *servers
);
397 PeerSelector::noteIps(const Dns::CachedIps
*ia
, const Dns::LookupDetails
&details
)
399 if (selectionAborted())
402 FwdServer
*fs
= servers
;
404 debugs(44, 3, "Unknown host: " << (fs
->_peer
.valid() ? fs
->_peer
->host
: request
->url
.host()));
405 // discard any previous error.
408 if (fs
->code
== HIER_DIRECT
) {
409 lastError
= new ErrorState(ERR_DNS_FAIL
, Http::scServiceUnavailable
, request
, al
);
410 lastError
->dnsError
= details
.error
;
413 // else noteIp() calls have already processed all IPs in *ia
418 // continue resolving selected peers
423 PeerSelector::checkNetdbDirect()
430 if (direct
== DIRECT_NO
)
433 /* base lookup on RTT and Hops if ICMP NetDB is enabled. */
435 myrtt
= netdbHostRtt(request
->url
.host());
436 debugs(44, 3, "MY RTT = " << myrtt
<< " msec");
437 debugs(44, 3, "minimum_direct_rtt = " << Config
.minDirectRtt
<< " msec");
439 if (myrtt
&& myrtt
<= Config
.minDirectRtt
)
442 myhops
= netdbHostHops(request
->url
.host());
444 debugs(44, 3, "MY hops = " << myhops
);
445 debugs(44, 3, "minimum_direct_hops = " << Config
.minDirectHops
);
447 if (myhops
&& myhops
<= Config
.minDirectHops
)
450 p
= whichPeer(closest_parent_miss
);
455 debugs(44, 3, "closest_parent_miss RTT = " << ping
.p_rtt
<< " msec");
457 if (myrtt
&& myrtt
<= ping
.p_rtt
)
460 #endif /* USE_ICMP */
466 PeerSelector::selectMore()
468 if (selectionAborted())
471 debugs(44, 3, request
->method
<< ' ' << request
->url
.host());
473 /** If we don't know whether DIRECT is permitted ... */
474 if (direct
== DIRECT_UNKNOWN
) {
475 if (always_direct
== ACCESS_DUNNO
) {
476 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (always_direct to be checked)");
477 /** check always_direct; */
478 ACLFilledChecklist
*ch
= new ACLFilledChecklist(Config
.accessList
.AlwaysDirect
, request
, NULL
);
481 acl_checklist
->syncAle(request
, nullptr);
482 acl_checklist
->nonBlockingCheck(CheckAlwaysDirectDone
, this);
484 } else if (never_direct
== ACCESS_DUNNO
) {
485 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (never_direct to be checked)");
486 /** check never_direct; */
487 ACLFilledChecklist
*ch
= new ACLFilledChecklist(Config
.accessList
.NeverDirect
, request
, NULL
);
490 acl_checklist
->syncAle(request
, nullptr);
491 acl_checklist
->nonBlockingCheck(CheckNeverDirectDone
, this);
493 } else if (request
->flags
.noDirect
) {
494 /** if we are accelerating, direct is not an option. */
496 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (forced non-direct)");
497 } else if (request
->flags
.loopDetected
) {
498 /** if we are in a forwarding-loop, direct is not an option. */
500 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (forwarding loop detected)");
501 } else if (checkNetdbDirect()) {
503 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (checkNetdbDirect)");
505 direct
= DIRECT_MAYBE
;
506 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (default)");
509 debugs(44, 3, "direct = " << DirectStr
[direct
]);
512 if (!entry
|| entry
->ping_status
== PING_NONE
)
516 } else if (entry
->ping_status
== PING_NONE
) {
517 selectSomeNeighbor();
519 if (entry
->ping_status
== PING_WAITING
)
521 } else if (entry
->ping_status
== PING_WAITING
) {
522 selectSomeNeighborReplies();
523 entry
->ping_status
= PING_DONE
;
539 if (Config
.onoff
.prefer_direct
)
542 if (request
->flags
.hierarchical
|| !Config
.onoff
.nonhierarchical_direct
) {
547 if (!Config
.onoff
.prefer_direct
)
553 // end peer selection; start resolving selected peers
557 bool peerAllowedToUse(const CachePeer
*, PeerSelector
*);
559 /// Selects a pinned connection if it exists, is valid, and is allowed.
561 PeerSelector::selectPinned()
563 // TODO: Avoid all repeated calls. Relying on PING_DONE is not enough.
564 if (!request
->pinnedConnection())
567 const auto peer
= request
->pinnedConnection()->pinnedPeer();
568 const auto usePinned
= peer
? peerAllowedToUse(peer
, this) : (direct
!= DIRECT_NO
);
569 // If the pinned connection is prohibited (for this request) then
570 // the initiator must decide whether it is OK to open a new one instead.
571 request
->pinnedConnection()->pinning
.peerAccessDenied
= !usePinned
;
573 addSelection(peer
, PINNED
);
575 entry
->ping_status
= PING_DONE
; // skip ICP
579 * Selects a neighbor (parent or sibling) based on one of the
583 * ICMP Netdb RTT estimates
587 PeerSelector::selectSomeNeighbor()
590 hier_code code
= HIER_NONE
;
591 assert(entry
->ping_status
== PING_NONE
);
593 if (direct
== DIRECT_YES
) {
594 entry
->ping_status
= PING_DONE
;
598 #if USE_CACHE_DIGESTS
599 if ((p
= neighborsDigestSelect(this))) {
600 if (neighborType(p
, request
->url
) == PEER_PARENT
)
601 code
= CD_PARENT_HIT
;
603 code
= CD_SIBLING_HIT
;
606 if ((p
= netdbClosestParent(this))) {
607 code
= CLOSEST_PARENT
;
608 } else if (peerSelectIcpPing(this, direct
, entry
)) {
609 debugs(44, 3, "Doing ICP pings");
610 ping
.start
= current_time
;
611 ping
.n_sent
= neighborsUdpPing(request
,
615 &ping
.n_replies_expected
,
618 if (ping
.n_sent
== 0)
619 debugs(44, DBG_CRITICAL
, "WARNING: neighborsUdpPing returned 0");
620 debugs(44, 3, ping
.n_replies_expected
<<
621 " ICP replies expected, RTT " << ping
.timeout
<<
624 if (ping
.n_replies_expected
> 0) {
625 entry
->ping_status
= PING_WAITING
;
626 eventAdd("PeerSelector::HandlePingTimeout",
629 0.001 * ping
.timeout
,
635 if (code
!= HIER_NONE
) {
637 addSelection(p
, code
);
640 entry
->ping_status
= PING_DONE
;
643 /// Selects a neighbor (parent or sibling) based on ICP/HTCP replies.
645 PeerSelector::selectSomeNeighborReplies()
648 hier_code code
= HIER_NONE
;
649 assert(entry
->ping_status
== PING_WAITING
);
650 assert(direct
!= DIRECT_YES
);
652 if (checkNetdbDirect()) {
653 code
= CLOSEST_DIRECT
;
654 addSelection(nullptr, code
);
659 code
= hit_type
== PEER_PARENT
? PARENT_HIT
: SIBLING_HIT
;
661 if (!closest_parent_miss
.isAnyAddr()) {
662 p
= whichPeer(closest_parent_miss
);
663 code
= CLOSEST_PARENT_MISS
;
664 } else if (!first_parent_miss
.isAnyAddr()) {
665 p
= whichPeer(first_parent_miss
);
666 code
= FIRST_PARENT_MISS
;
669 if (p
&& code
!= HIER_NONE
) {
670 addSelection(p
, code
);
674 /// Adds a "direct" entry if the request can be forwarded to the origin server.
676 PeerSelector::selectSomeDirect()
678 if (direct
== DIRECT_NO
)
681 /* WAIS is not implemented natively */
682 if (request
->url
.getScheme() == AnyP::PROTO_WAIS
)
685 addSelection(nullptr, HIER_DIRECT
);
689 PeerSelector::selectSomeParent()
692 hier_code code
= HIER_NONE
;
693 debugs(44, 3, request
->method
<< ' ' << request
->url
.host());
695 if (direct
== DIRECT_YES
)
698 if ((p
= peerSourceHashSelectParent(this))) {
699 code
= SOURCEHASH_PARENT
;
701 } else if ((p
= peerUserHashSelectParent(this))) {
702 code
= USERHASH_PARENT
;
704 } else if ((p
= carpSelectParent(this))) {
706 } else if ((p
= getRoundRobinParent(this))) {
707 code
= ROUNDROBIN_PARENT
;
708 } else if ((p
= getWeightedRoundRobinParent(this))) {
709 code
= ROUNDROBIN_PARENT
;
710 } else if ((p
= getFirstUpParent(this))) {
711 code
= FIRSTUP_PARENT
;
712 } else if ((p
= getDefaultParent(this))) {
713 code
= DEFAULT_PARENT
;
716 if (code
!= HIER_NONE
) {
717 addSelection(p
, code
);
721 /// Adds alive parents. Used as a last resort for never_direct.
723 PeerSelector::selectAllParents()
726 /* Add all alive parents */
728 for (p
= Config
.peers
; p
; p
= p
->next
) {
729 /* XXX: neighbors.c lacks a public interface for enumerating
730 * parents to a request so we have to dig some here..
733 if (neighborType(p
, request
->url
) != PEER_PARENT
)
736 if (!peerHTTPOkay(p
, this))
739 addSelection(p
, ANY_OLD_PARENT
);
742 /* XXX: should add dead parents here, but it is currently
743 * not possible to find out which parents are dead or which
744 * simply are not configured to handle the request.
746 /* Add default parent as a last resort */
747 if ((p
= getDefaultParent(this))) {
748 addSelection(p
, DEFAULT_PARENT
);
753 PeerSelector::handlePingTimeout()
755 debugs(44, 3, url());
758 entry
->ping_status
= PING_DONE
;
760 if (selectionAborted())
763 ++PeerStats
.timeouts
;
769 PeerSelector::HandlePingTimeout(void *data
)
771 static_cast<PeerSelector
*>(data
)->handlePingTimeout();
777 memset(&PeerStats
, '\0', sizeof(PeerStats
));
781 PeerSelector::handleIcpParentMiss(CachePeer
*p
, icp_common_t
*header
)
786 if (Config
.onoff
.query_icmp
) {
787 if (header
->flags
& ICP_FLAG_SRC_RTT
) {
788 rtt
= header
->pad
& 0xFFFF;
789 int hops
= (header
->pad
>> 16) & 0xFFFF;
791 if (rtt
> 0 && rtt
< 0xFFFF)
792 netdbUpdatePeer(request
->url
, p
, rtt
, hops
);
794 if (rtt
&& (ping
.p_rtt
== 0 || rtt
< ping
.p_rtt
)) {
795 closest_parent_miss
= p
->in_addr
;
800 #endif /* USE_ICMP */
802 /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
803 if (p
->options
.closest_only
)
806 /* set FIRST_MISS if there is no CLOSEST parent */
807 if (!closest_parent_miss
.isAnyAddr())
810 rtt
= (tvSubMsec(ping
.start
, current_time
) - p
->basetime
) / p
->weight
;
815 if (first_parent_miss
.isAnyAddr() || rtt
< ping
.w_rtt
) {
816 first_parent_miss
= p
->in_addr
;
822 PeerSelector::handleIcpReply(CachePeer
*p
, const peer_t type
, icp_common_t
*header
)
824 icp_opcode op
= header
->getOpCode();
825 debugs(44, 3, icp_opcode_str
[op
] << ' ' << url());
826 #if USE_CACHE_DIGESTS && 0
827 /* do cd lookup to count false misses */
830 peerNoteDigestLookup(request
, p
,
831 peerDigestLookup(p
, this));
837 if (op
== ICP_MISS
|| op
== ICP_DECHO
) {
838 if (type
== PEER_PARENT
)
839 handleIcpParentMiss(p
, header
);
840 } else if (op
== ICP_HIT
) {
847 if (ping
.n_recv
< ping
.n_replies_expected
)
855 PeerSelector::handleHtcpReply(CachePeer
*p
, const peer_t type
, HtcpReplyData
*htcp
)
857 debugs(44, 3, (htcp
->hit
? "HIT" : "MISS") << ' ' << url());
867 if (type
== PEER_PARENT
)
868 handleHtcpParentMiss(p
, htcp
);
870 if (ping
.n_recv
< ping
.n_replies_expected
)
877 PeerSelector::handleHtcpParentMiss(CachePeer
*p
, HtcpReplyData
*htcp
)
882 if (Config
.onoff
.query_icmp
) {
883 if (htcp
->cto
.rtt
> 0) {
884 rtt
= (int) htcp
->cto
.rtt
* 1000;
885 int hops
= (int) htcp
->cto
.hops
* 1000;
886 netdbUpdatePeer(request
->url
, p
, rtt
, hops
);
888 if (rtt
&& (ping
.p_rtt
== 0 || rtt
< ping
.p_rtt
)) {
889 closest_parent_miss
= p
->in_addr
;
894 #endif /* USE_ICMP */
896 /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
897 if (p
->options
.closest_only
)
900 /* set FIRST_MISS if there is no CLOSEST parent */
901 if (!closest_parent_miss
.isAnyAddr())
904 rtt
= (tvSubMsec(ping
.start
, current_time
) - p
->basetime
) / p
->weight
;
909 if (first_parent_miss
.isAnyAddr() || rtt
< ping
.w_rtt
) {
910 first_parent_miss
= p
->in_addr
;
918 PeerSelector::HandlePingReply(CachePeer
* p
, peer_t type
, AnyP::ProtocolType proto
, void *pingdata
, void *data
)
920 if (proto
== AnyP::PROTO_ICP
)
921 static_cast<PeerSelector
*>(data
)->handleIcpReply(p
, type
, static_cast<icp_common_t
*>(pingdata
));
925 else if (proto
== AnyP::PROTO_HTCP
)
926 static_cast<PeerSelector
*>(data
)->handleHtcpReply(p
, type
, static_cast<HtcpReplyData
*>(pingdata
));
931 debugs(44, DBG_IMPORTANT
, "ERROR: ignoring an ICP reply with unknown protocol " << proto
);
935 PeerSelector::addSelection(CachePeer
*peer
, const hier_code code
)
937 // Find the end of the servers list. Bail on a duplicate destination.
938 auto **serversTail
= &servers
;
939 while (const auto server
= *serversTail
) {
940 // There can be at most one PINNED destination.
941 // Non-PINNED destinations are uniquely identified by their CachePeer
942 // (even though a DIRECT destination might match a cache_peer address).
943 const bool duplicate
= (server
->code
== PINNED
) ?
944 (code
== PINNED
) : (server
->_peer
== peer
);
946 debugs(44, 3, "skipping " << PeerSelectionDumper(this, peer
, code
) <<
947 "; have " << PeerSelectionDumper(this, server
->_peer
.get(), server
->code
));
950 serversTail
= &server
->next
;
953 debugs(44, 3, "adding " << PeerSelectionDumper(this, peer
, code
));
954 *serversTail
= new FwdServer(peer
, code
);
957 PeerSelector::PeerSelector(PeerSelectionInitiator
*initiator
):
960 always_direct(Config
.accessList
.AlwaysDirect
?ACCESS_DUNNO
:ACCESS_DENIED
),
961 never_direct(Config
.accessList
.NeverDirect
?ACCESS_DUNNO
:ACCESS_DENIED
),
962 direct(DIRECT_UNKNOWN
),
966 closest_parent_miss(),
969 acl_checklist (NULL
),
970 initiator_(initiator
)
972 ; // no local defaults.
976 PeerSelector::url() const
979 return SBuf(entry
->url());
982 return request
->effectiveRequestUri();
984 static const SBuf
noUrl("[no URL]");
988 /// \returns valid/interested peer initiator or nil
989 PeerSelectionInitiator
*
990 PeerSelector::interestedInitiator()
992 const auto initiator
= initiator_
.valid();
995 debugs(44, 3, id
<< " initiator gone");
999 if (!initiator
->subscribed
) {
1000 debugs(44, 3, id
<< " initiator lost interest");
1009 PeerSelector::wantsMoreDestinations() const {
1010 const auto maxCount
= Config
.forward_max_tries
;
1011 return maxCount
>= 0 && foundPaths
<
1012 static_cast<std::make_unsigned
<decltype(maxCount
)>::type
>(maxCount
);
1016 PeerSelector::handlePath(const Comm::ConnectionPointer
&path
, FwdServer
&fs
)
1021 path
->peerType
= fs
.code
;
1022 path
->setPeer(fs
._peer
.get());
1024 // check for a configured outgoing address for this destination...
1025 getOutgoingAddress(request
, path
);
1026 debugs(44, 2, id
<< " found " << path
<< ", destination #" << foundPaths
<< " for " << url());
1028 debugs(44, 2, id
<< " found pinned, destination #" << foundPaths
<< " for " << url());
1030 request
->hier
.ping
= ping
; // may be updated later
1032 debugs(44, 2, " always_direct = " << always_direct
);
1033 debugs(44, 2, " never_direct = " << never_direct
);
1034 debugs(44, 2, " timedout = " << ping
.timedout
);
1036 if (const auto initiator
= interestedInitiator())
1037 initiator
->noteDestination(path
);
1040 InstanceIdDefinitions(PeerSelector
, "PeerSelector");
1042 ping_data::ping_data() :
1045 n_replies_expected(0),