2 * Copyright (C) 1996-2022 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/AsyncCbdataCalls.h"
14 #include "base/InstanceId.h"
15 #include "base/TypeTraits.h"
16 #include "CachePeer.h"
18 #include "client_side.h"
19 #include "dns/LookupDetails.h"
20 #include "errorpage.h"
24 #include "hier_code.h"
26 #include "http/Stream.h"
27 #include "HttpRequest.h"
28 #include "icmp/net_db.h"
32 #include "neighbors.h"
33 #include "peer_sourcehash.h"
34 #include "peer_userhash.h"
35 #include "PeerSelectState.h"
36 #include "SquidConfig.h"
37 #include "SquidTime.h"
39 #include "util.h" // for tvSubDsec() which should be in SquidTime.h
42 * A CachePeer which has been selected as a possible destination.
43 * Listed as pointers here so as to prevent duplicates being added but will
44 * be converted to a set of IP address path options before handing back out
47 * Certain connection flags and outgoing settings will also be looked up and
48 * set based on the received request and CachePeer settings before handing back.
52 MEMPROXY_CLASS(FwdServer
);
55 FwdServer(CachePeer
*p
, hier_code c
) :
61 CbcPointer
<CachePeer
> _peer
; /* NULL --> origin server */
70 static const char *DirectStr
[] = {
77 /// a helper class to report a selected destination (for debugging)
78 class PeerSelectionDumper
81 PeerSelectionDumper(const PeerSelector
* const aSelector
, const CachePeer
* const aPeer
, const hier_code aCode
):
82 selector(aSelector
), peer(aPeer
), code(aCode
) {}
84 const PeerSelector
* const selector
; ///< selection parameters
85 const CachePeer
* const peer
; ///< successful selection info
86 const hier_code code
; ///< selection algorithm
89 CBDATA_CLASS_INIT(PeerSelector
);
91 /// prints PeerSelectionDumper (for debugging)
93 operator <<(std::ostream
&os
, const PeerSelectionDumper
&fsd
)
95 os
<< hier_code_str
[fsd
.code
];
98 os
<< '/' << fsd
.peer
->host
;
99 else if (fsd
.selector
) // useful for DIRECT and gone PINNED destinations
100 os
<< '#' << fsd
.selector
->request
->url
.host();
105 /// An ICP ping timeout service.
106 /// Protects event.cc (which is designed to handle a few unrelated timeouts)
107 /// from exposure to thousands of ping-related timeouts on busy proxies.
108 class PeerSelectorPingMonitor
111 /// registers the given selector to be notified about the IPC ping timeout
112 void monitor(PeerSelector
*);
114 /// removes a PeerSelector from the waiting list
115 void forget(PeerSelector
*);
117 /// \returns a (nil) registration of a non-waiting peer selector
118 WaitingPeerSelectorPosition
npos() { return selectors
.end(); }
121 static void NoteWaitOver(void *monitor
);
127 WaitingPeerSelectors selectors
; ///< \see WaitingPeerSelectors
130 /// monitors all PeerSelector ICP ping timeouts
131 static PeerSelectorPingMonitor
&
134 static const auto Instance
= new PeerSelectorPingMonitor();
138 /* PeerSelectorPingMonitor */
140 /// PeerSelectorPingMonitor::noteWaitOver() wrapper
142 PeerSelectorPingMonitor::NoteWaitOver(void *raw
)
145 static_cast<PeerSelectorPingMonitor
*>(raw
)->noteWaitOver();
148 /// schedules a single event to represent all waiting selectors
150 PeerSelectorPingMonitor::startWaiting()
152 assert(!selectors
.empty());
153 const auto interval
= tvSubDsec(current_time
, selectors
.begin()->first
);
154 eventAdd("PeerSelectorPingMonitor::NoteWaitOver", &PeerSelectorPingMonitor::NoteWaitOver
, this, interval
, 0, false);
157 /// undoes an earlier startWaiting() call
159 PeerSelectorPingMonitor::abortWaiting()
161 // our event may be already in the AsyncCallQueue but that is OK:
162 // such queued calls cannot accumulate, and we ignore any stale ones
163 eventDelete(&PeerSelectorPingMonitor::NoteWaitOver
, nullptr);
166 /// calls back all ready PeerSelectors and continues to wait for others
168 PeerSelectorPingMonitor::noteWaitOver()
170 while (!selectors
.empty() && current_time
>= selectors
.begin()->first
) {
171 const auto selector
= selectors
.begin()->second
;
172 CallBack(selector
->al
, [selector
,this] {
173 selector
->ping
.monitorRegistration
= npos();
174 AsyncCall::Pointer callback
= asyncCall(44, 4, "PeerSelector::HandlePingTimeout",
175 cbdataDialer(PeerSelector::HandlePingTimeout
, selector
));
176 ScheduleCallHere(callback
);
178 selectors
.erase(selectors
.begin());
181 if (!selectors
.empty()) {
182 // Since abortWaiting() is unreliable, we may have been awakened by a
183 // stale event A after event B has been scheduled. Now we are going to
184 // schedule event C. Prevent event accumulation by deleting B (if any).
192 PeerSelectorPingMonitor::monitor(PeerSelector
*selector
)
196 const auto deadline
= selector
->ping
.deadline();
197 const auto position
= selectors
.emplace(deadline
, selector
);
198 selector
->ping
.monitorRegistration
= position
;
200 if (position
== selectors
.begin()) {
201 if (selectors
.size() > 1)
202 abortWaiting(); // remove the previously scheduled earlier event
204 } // else the already scheduled event is still the earliest one
208 PeerSelectorPingMonitor::forget(PeerSelector
*selector
)
212 if (selector
->ping
.monitorRegistration
== npos())
213 return; // already forgotten
215 const auto wasFirst
= selector
->ping
.monitorRegistration
== selectors
.begin();
216 selectors
.erase(selector
->ping
.monitorRegistration
);
217 selector
->ping
.monitorRegistration
= npos();
220 // do not reschedule if there are still elements with the same deadline
221 if (!selectors
.empty() && selectors
.begin()->first
== selector
->ping
.deadline())
224 if (!selectors
.empty())
226 } // else do nothing since the old scheduled event is still the earliest one
231 PeerSelector::~PeerSelector()
234 FwdServer
*next
= servers
->next
;
239 cancelPingTimeoutMonitoring();
242 debugs(44, 3, entry
->url());
243 entry
->ping_status
= PING_DONE
;
247 debugs(44, DBG_IMPORTANT
, "ERROR: Squid BUG: peer selector gone while waiting for a slow ACL");
248 delete acl_checklist
;
251 HTTPMSGUNLOCK(request
);
254 assert(entry
->ping_status
!= PING_WAITING
);
255 entry
->unlock("peerSelect");
263 PeerSelector::startPingWaiting()
266 assert(entry
->ping_status
!= PING_WAITING
);
267 PingMonitor().monitor(this);
268 entry
->ping_status
= PING_WAITING
;
272 PeerSelector::cancelPingTimeoutMonitoring()
274 PingMonitor().forget(this);
278 peerSelectIcpPing(PeerSelector
*ps
, int direct
, StoreEntry
* entry
)
281 HttpRequest
*request
= ps
->request
;
285 assert(entry
->ping_status
== PING_NONE
);
286 assert(direct
!= DIRECT_YES
);
287 debugs(44, 3, entry
->url());
289 if (!request
->flags
.hierarchical
&& direct
!= DIRECT_NO
)
292 if (EBIT_TEST(entry
->flags
, KEY_PRIVATE
) && !neighbors_do_private_keys
)
293 if (direct
!= DIRECT_NO
)
296 n
= neighborsCount(ps
);
298 debugs(44, 3, "counted " << n
<< " neighbors");
304 peerSelect(PeerSelectionInitiator
*initiator
,
305 HttpRequest
* request
,
306 AccessLogEntry::Pointer
const &al
,
310 debugs(44, 3, *entry
<< ' ' << entry
->url());
312 debugs(44, 3, request
->method
);
314 const auto selector
= new PeerSelector(initiator
);
316 selector
->request
= request
;
317 HTTPMSGLOCK(selector
->request
);
320 selector
->entry
= entry
;
322 #if USE_CACHE_DIGESTS
324 request
->hier
.peer_select_start
= current_time
;
329 selector
->entry
->lock("peerSelect");
331 selector
->selectMore();
335 PeerSelectionInitiator::startSelectingDestinations(HttpRequest
*request
, const AccessLogEntry::Pointer
&ale
, StoreEntry
*entry
)
338 peerSelect(this, request
, ale
, entry
);
339 // and wait for noteDestination() and/or noteDestinationsEnd() calls
343 PeerSelector::checkNeverDirectDone(const Acl::Answer answer
)
345 acl_checklist
= nullptr;
346 debugs(44, 3, answer
);
347 never_direct
= answer
;
350 /** if never_direct says YES, do that. */
352 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (never_direct allow)");
354 case ACCESS_DENIED
: // not relevant.
355 case ACCESS_DUNNO
: // not relevant.
357 case ACCESS_AUTH_REQUIRED
:
358 debugs(44, DBG_IMPORTANT
, "WARNING: never_direct resulted in " << answer
<< ". Username ACLs are not reliable here.");
365 PeerSelector::CheckNeverDirectDone(Acl::Answer answer
, void *data
)
367 static_cast<PeerSelector
*>(data
)->checkNeverDirectDone(answer
);
371 PeerSelector::checkAlwaysDirectDone(const Acl::Answer answer
)
373 acl_checklist
= nullptr;
374 debugs(44, 3, answer
);
375 always_direct
= answer
;
378 /** if always_direct says YES, do that. */
380 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (always_direct allow)");
382 case ACCESS_DENIED
: // not relevant.
383 case ACCESS_DUNNO
: // not relevant.
385 case ACCESS_AUTH_REQUIRED
:
386 debugs(44, DBG_IMPORTANT
, "WARNING: always_direct resulted in " << answer
<< ". Username ACLs are not reliable here.");
393 PeerSelector::CheckAlwaysDirectDone(Acl::Answer answer
, void *data
)
395 static_cast<PeerSelector
*>(data
)->checkAlwaysDirectDone(answer
);
398 /// \returns true (after destroying "this") if the peer initiator is gone
399 /// \returns false (without side effects) otherwise
401 PeerSelector::selectionAborted()
403 if (interestedInitiator())
406 debugs(44, 3, "Aborting peer selection: Initiator gone or lost interest.");
411 /// A single DNS resolution loop iteration: Converts selected FwdServer to IPs.
413 PeerSelector::resolveSelected()
415 if (selectionAborted())
418 FwdServer
*fs
= servers
;
420 // Bug 3243: CVE 2009-0801
421 // Bypass of browser same-origin access control in intercepted communication
422 // To resolve this we must use only the original client destination when going DIRECT
423 // on intercepted traffic which failed Host verification
424 const HttpRequest
*req
= request
;
425 const bool isIntercepted
= !req
->flags
.redirected
&&
426 (req
->flags
.intercepted
|| req
->flags
.interceptTproxy
);
427 const bool useOriginalDst
= Config
.onoff
.client_dst_passthru
|| !req
->flags
.hostVerified
;
428 const bool choseDirect
= fs
&& fs
->code
== HIER_DIRECT
;
429 if (isIntercepted
&& useOriginalDst
&& choseDirect
) {
430 // check the client is still around before using any of its details
431 if (req
->clientConnectionManager
.valid()) {
432 // construct a "result" adding the ORIGINAL_DST to the set instead of DIRECT
433 Comm::ConnectionPointer p
= new Comm::Connection();
434 p
->remote
= req
->clientConnectionManager
->clientConnection
->local
;
435 fs
->code
= ORIGINAL_DST
; // fs->code is DIRECT. This fixes the display.
439 // clear the used fs and continue
446 if (fs
&& fs
->code
== PINNED
) {
447 // Nil path signals a PINNED destination selection. Our initiator should
448 // borrow and use clientConnectionManager's pinned connection object
449 // (regardless of that connection destination).
450 handlePath(nullptr, *fs
);
457 // convert the list of FwdServer destinations into destinations IP addresses
458 if (fs
&& wantsMoreDestinations()) {
459 // send the next one off for DNS lookup.
460 const char *host
= fs
->_peer
.valid() ? fs
->_peer
->host
: request
->url
.host();
461 debugs(44, 2, "Find IP destination for: " << url() << "' via " << host
);
462 Dns::nbgethostbyname(host
, this);
466 // Bug 3605: clear any extra listed FwdServer destinations, when the options exceeds max_foward_tries.
467 // due to the allocation method of fs, we must deallocate each manually.
468 // TODO: use a std::list so we can get the size and abort adding whenever the selection loops reach Config.forward_max_tries
470 assert(fs
== servers
);
478 // done with DNS lookups. pass back to caller
480 debugs(44, 2, id
<< " found all " << foundPaths
<< " destinations for " << url());
481 debugs(44, 2, " always_direct = " << always_direct
);
482 debugs(44, 2, " never_direct = " << never_direct
);
483 debugs(44, 2, " timedout = " << ping
.timedout
);
485 ping
.stop
= current_time
;
486 request
->hier
.ping
= ping
; // final result
488 if (lastError
&& foundPaths
) {
489 // nobody cares about errors if we found destinations despite them
490 debugs(44, 3, "forgetting the last error");
495 if (const auto initiator
= interestedInitiator())
496 initiator
->noteDestinationsEnd(lastError
);
497 lastError
= nullptr; // initiator owns the ErrorState object now
502 PeerSelector::noteLookup(const Dns::LookupDetails
&details
)
504 /* ignore lookup delays that occurred after the initiator moved on */
506 if (selectionAborted())
509 if (!wantsMoreDestinations())
512 request
->recordLookup(details
);
516 PeerSelector::noteIp(const Ip::Address
&ip
)
518 if (selectionAborted())
521 if (!wantsMoreDestinations())
524 const auto peer
= servers
->_peer
.valid();
526 // for TPROXY spoofing, we must skip unusable addresses
527 if (request
->flags
.spoofClientIp
&& !(peer
&& peer
->options
.no_tproxy
) ) {
528 if (ip
.isIPv4() != request
->client_addr
.isIPv4())
529 return; // cannot spoof the client address on this link
532 Comm::ConnectionPointer p
= new Comm::Connection();
534 p
->remote
.port(peer
? peer
->http_port
: request
->url
.port());
535 handlePath(p
, *servers
);
539 PeerSelector::noteIps(const Dns::CachedIps
*ia
, const Dns::LookupDetails
&details
)
541 if (selectionAborted())
544 FwdServer
*fs
= servers
;
546 debugs(44, 3, "Unknown host: " << (fs
->_peer
.valid() ? fs
->_peer
->host
: request
->url
.host()));
547 // discard any previous error.
550 if (fs
->code
== HIER_DIRECT
) {
551 lastError
= new ErrorState(ERR_DNS_FAIL
, Http::scServiceUnavailable
, request
, al
);
552 lastError
->dnsError
= details
.error
;
555 // else noteIp() calls have already processed all IPs in *ia
560 // continue resolving selected peers
565 PeerSelector::checkNetdbDirect()
572 if (direct
== DIRECT_NO
)
575 /* base lookup on RTT and Hops if ICMP NetDB is enabled. */
577 myrtt
= netdbHostRtt(request
->url
.host());
578 debugs(44, 3, "MY RTT = " << myrtt
<< " msec");
579 debugs(44, 3, "minimum_direct_rtt = " << Config
.minDirectRtt
<< " msec");
581 if (myrtt
&& myrtt
<= Config
.minDirectRtt
)
584 myhops
= netdbHostHops(request
->url
.host());
586 debugs(44, 3, "MY hops = " << myhops
);
587 debugs(44, 3, "minimum_direct_hops = " << Config
.minDirectHops
);
589 if (myhops
&& myhops
<= Config
.minDirectHops
)
592 p
= whichPeer(closest_parent_miss
);
597 debugs(44, 3, "closest_parent_miss RTT = " << ping
.p_rtt
<< " msec");
599 if (myrtt
&& myrtt
<= ping
.p_rtt
)
602 #endif /* USE_ICMP */
608 PeerSelector::selectMore()
610 if (selectionAborted())
613 debugs(44, 3, request
->method
<< ' ' << request
->url
.host());
615 /** If we don't know whether DIRECT is permitted ... */
616 if (direct
== DIRECT_UNKNOWN
) {
617 if (always_direct
== ACCESS_DUNNO
) {
618 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (always_direct to be checked)");
619 /** check always_direct; */
620 ACLFilledChecklist
*ch
= new ACLFilledChecklist(Config
.accessList
.AlwaysDirect
, request
, NULL
);
623 acl_checklist
->syncAle(request
, nullptr);
624 acl_checklist
->nonBlockingCheck(CheckAlwaysDirectDone
, this);
626 } else if (never_direct
== ACCESS_DUNNO
) {
627 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (never_direct to be checked)");
628 /** check never_direct; */
629 ACLFilledChecklist
*ch
= new ACLFilledChecklist(Config
.accessList
.NeverDirect
, request
, NULL
);
632 acl_checklist
->syncAle(request
, nullptr);
633 acl_checklist
->nonBlockingCheck(CheckNeverDirectDone
, this);
635 } else if (request
->flags
.noDirect
) {
636 /** if we are accelerating, direct is not an option. */
638 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (forced non-direct)");
639 } else if (request
->flags
.loopDetected
) {
640 /** if we are in a forwarding-loop, direct is not an option. */
642 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (forwarding loop detected)");
643 } else if (checkNetdbDirect()) {
645 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (checkNetdbDirect)");
647 direct
= DIRECT_MAYBE
;
648 debugs(44, 3, "direct = " << DirectStr
[direct
] << " (default)");
651 debugs(44, 3, "direct = " << DirectStr
[direct
]);
654 if (!entry
|| entry
->ping_status
== PING_NONE
)
658 } else if (entry
->ping_status
== PING_NONE
) {
659 selectSomeNeighbor();
661 if (entry
->ping_status
== PING_WAITING
)
663 } else if (entry
->ping_status
== PING_WAITING
) {
664 selectSomeNeighborReplies();
665 cancelPingTimeoutMonitoring();
666 entry
->ping_status
= PING_DONE
;
682 if (Config
.onoff
.prefer_direct
)
685 if (request
->flags
.hierarchical
|| !Config
.onoff
.nonhierarchical_direct
) {
690 if (!Config
.onoff
.prefer_direct
)
696 // end peer selection; start resolving selected peers
700 bool peerAllowedToUse(const CachePeer
*, PeerSelector
*);
702 /// Selects a pinned connection if it exists, is valid, and is allowed.
704 PeerSelector::selectPinned()
706 // TODO: Avoid all repeated calls. Relying on PING_DONE is not enough.
707 if (!request
->pinnedConnection())
710 const auto peer
= request
->pinnedConnection()->pinnedPeer();
711 const auto usePinned
= peer
? peerAllowedToUse(peer
, this) : (direct
!= DIRECT_NO
);
712 // If the pinned connection is prohibited (for this request) then
713 // the initiator must decide whether it is OK to open a new one instead.
714 request
->pinnedConnection()->pinning
.peerAccessDenied
= !usePinned
;
716 addSelection(peer
, PINNED
);
718 entry
->ping_status
= PING_DONE
; // skip ICP
722 * Selects a neighbor (parent or sibling) based on one of the
726 * ICMP Netdb RTT estimates
730 PeerSelector::selectSomeNeighbor()
733 hier_code code
= HIER_NONE
;
734 assert(entry
->ping_status
== PING_NONE
);
736 if (direct
== DIRECT_YES
) {
737 entry
->ping_status
= PING_DONE
;
741 #if USE_CACHE_DIGESTS
742 if ((p
= neighborsDigestSelect(this))) {
743 if (neighborType(p
, request
->url
) == PEER_PARENT
)
744 code
= CD_PARENT_HIT
;
746 code
= CD_SIBLING_HIT
;
749 if ((p
= netdbClosestParent(this))) {
750 code
= CLOSEST_PARENT
;
751 } else if (peerSelectIcpPing(this, direct
, entry
)) {
752 debugs(44, 3, "Doing ICP pings");
753 ping
.start
= current_time
;
754 ping
.n_sent
= neighborsUdpPing(request
,
758 &ping
.n_replies_expected
,
760 // TODO: Refactor neighborsUdpPing() to guarantee positive timeouts.
761 if (ping
.timeout
< 0)
764 if (ping
.n_sent
== 0)
765 debugs(44, DBG_CRITICAL
, "WARNING: neighborsUdpPing returned 0");
766 debugs(44, 3, ping
.n_replies_expected
<<
767 " ICP replies expected, RTT " << ping
.timeout
<<
770 if (ping
.n_replies_expected
> 0) {
776 if (code
!= HIER_NONE
) {
778 addSelection(p
, code
);
781 entry
->ping_status
= PING_DONE
;
784 /// Selects a neighbor (parent or sibling) based on ICP/HTCP replies.
786 PeerSelector::selectSomeNeighborReplies()
789 hier_code code
= HIER_NONE
;
790 assert(entry
->ping_status
== PING_WAITING
);
791 assert(direct
!= DIRECT_YES
);
793 if (checkNetdbDirect()) {
794 code
= CLOSEST_DIRECT
;
795 addSelection(nullptr, code
);
800 code
= hit_type
== PEER_PARENT
? PARENT_HIT
: SIBLING_HIT
;
802 if (!closest_parent_miss
.isAnyAddr()) {
803 p
= whichPeer(closest_parent_miss
);
804 code
= CLOSEST_PARENT_MISS
;
805 } else if (!first_parent_miss
.isAnyAddr()) {
806 p
= whichPeer(first_parent_miss
);
807 code
= FIRST_PARENT_MISS
;
810 if (p
&& code
!= HIER_NONE
) {
811 addSelection(p
, code
);
815 /// Adds a "direct" entry if the request can be forwarded to the origin server.
817 PeerSelector::selectSomeDirect()
819 if (direct
== DIRECT_NO
)
822 /* WAIS is not implemented natively */
823 if (request
->url
.getScheme() == AnyP::PROTO_WAIS
)
826 addSelection(nullptr, HIER_DIRECT
);
830 PeerSelector::selectSomeParent()
833 hier_code code
= HIER_NONE
;
834 debugs(44, 3, request
->method
<< ' ' << request
->url
.host());
836 if (direct
== DIRECT_YES
)
839 if ((p
= peerSourceHashSelectParent(this))) {
840 code
= SOURCEHASH_PARENT
;
842 } else if ((p
= peerUserHashSelectParent(this))) {
843 code
= USERHASH_PARENT
;
845 } else if ((p
= carpSelectParent(this))) {
847 } else if ((p
= getRoundRobinParent(this))) {
848 code
= ROUNDROBIN_PARENT
;
849 } else if ((p
= getWeightedRoundRobinParent(this))) {
850 code
= ROUNDROBIN_PARENT
;
851 } else if ((p
= getFirstUpParent(this))) {
852 code
= FIRSTUP_PARENT
;
853 } else if ((p
= getDefaultParent(this))) {
854 code
= DEFAULT_PARENT
;
857 if (code
!= HIER_NONE
) {
858 addSelection(p
, code
);
862 /// Adds alive parents. Used as a last resort for never_direct.
864 PeerSelector::selectAllParents()
867 /* Add all alive parents */
869 for (p
= Config
.peers
; p
; p
= p
->next
) {
870 /* XXX: neighbors.c lacks a public interface for enumerating
871 * parents to a request so we have to dig some here..
874 if (neighborType(p
, request
->url
) != PEER_PARENT
)
877 if (!peerHTTPOkay(p
, this))
880 addSelection(p
, ANY_OLD_PARENT
);
883 /* XXX: should add dead parents here, but it is currently
884 * not possible to find out which parents are dead or which
885 * simply are not configured to handle the request.
887 /* Add default parent as a last resort */
888 if ((p
= getDefaultParent(this))) {
889 addSelection(p
, DEFAULT_PARENT
);
894 PeerSelector::handlePingTimeout()
896 debugs(44, 3, url());
898 // do nothing if ping reply came while handlePingTimeout() was queued
899 if (!entry
|| entry
->ping_status
!= PING_WAITING
)
902 entry
->ping_status
= PING_DONE
;
904 if (selectionAborted())
907 ++PeerStats
.timeouts
;
913 PeerSelector::HandlePingTimeout(PeerSelector
*selector
)
915 selector
->handlePingTimeout();
921 memset(&PeerStats
, '\0', sizeof(PeerStats
));
925 PeerSelector::handleIcpParentMiss(CachePeer
*p
, icp_common_t
*header
)
930 if (Config
.onoff
.query_icmp
) {
931 if (header
->flags
& ICP_FLAG_SRC_RTT
) {
932 rtt
= header
->pad
& 0xFFFF;
933 int hops
= (header
->pad
>> 16) & 0xFFFF;
935 if (rtt
> 0 && rtt
< 0xFFFF)
936 netdbUpdatePeer(request
->url
, p
, rtt
, hops
);
938 if (rtt
&& (ping
.p_rtt
== 0 || rtt
< ping
.p_rtt
)) {
939 closest_parent_miss
= p
->in_addr
;
946 #endif /* USE_ICMP */
948 /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
949 if (p
->options
.closest_only
)
952 /* set FIRST_MISS if there is no CLOSEST parent */
953 if (!closest_parent_miss
.isAnyAddr())
956 rtt
= (tvSubMsec(ping
.start
, current_time
) - p
->basetime
) / p
->weight
;
961 if (first_parent_miss
.isAnyAddr() || rtt
< ping
.w_rtt
) {
962 first_parent_miss
= p
->in_addr
;
968 PeerSelector::handleIcpReply(CachePeer
*p
, const peer_t type
, icp_common_t
*header
)
970 icp_opcode op
= header
->getOpCode();
971 debugs(44, 3, icp_opcode_str
[op
] << ' ' << url());
972 #if USE_CACHE_DIGESTS && 0
973 /* do cd lookup to count false misses */
976 peerNoteDigestLookup(request
, p
,
977 peerDigestLookup(p
, this));
983 if (op
== ICP_MISS
|| op
== ICP_DECHO
) {
984 if (type
== PEER_PARENT
)
985 handleIcpParentMiss(p
, header
);
986 } else if (op
== ICP_HIT
) {
993 if (ping
.n_recv
< ping
.n_replies_expected
)
1001 PeerSelector::handleHtcpReply(CachePeer
*p
, const peer_t type
, HtcpReplyData
*htcp
)
1003 debugs(44, 3, (htcp
->hit
? "HIT" : "MISS") << ' ' << url());
1013 if (type
== PEER_PARENT
)
1014 handleHtcpParentMiss(p
, htcp
);
1016 if (ping
.n_recv
< ping
.n_replies_expected
)
1023 PeerSelector::handleHtcpParentMiss(CachePeer
*p
, HtcpReplyData
*htcp
)
1028 if (Config
.onoff
.query_icmp
) {
1029 if (htcp
->cto
.rtt
> 0) {
1030 rtt
= (int) htcp
->cto
.rtt
* 1000;
1031 int hops
= (int) htcp
->cto
.hops
* 1000;
1032 netdbUpdatePeer(request
->url
, p
, rtt
, hops
);
1034 if (rtt
&& (ping
.p_rtt
== 0 || rtt
< ping
.p_rtt
)) {
1035 closest_parent_miss
= p
->in_addr
;
1042 #endif /* USE_ICMP */
1044 /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
1045 if (p
->options
.closest_only
)
1048 /* set FIRST_MISS if there is no CLOSEST parent */
1049 if (!closest_parent_miss
.isAnyAddr())
1052 rtt
= (tvSubMsec(ping
.start
, current_time
) - p
->basetime
) / p
->weight
;
1057 if (first_parent_miss
.isAnyAddr() || rtt
< ping
.w_rtt
) {
1058 first_parent_miss
= p
->in_addr
;
1066 PeerSelector::HandlePingReply(CachePeer
* p
, peer_t type
, AnyP::ProtocolType proto
, void *pingdata
, void *data
)
1068 if (proto
== AnyP::PROTO_ICP
)
1069 static_cast<PeerSelector
*>(data
)->handleIcpReply(p
, type
, static_cast<icp_common_t
*>(pingdata
));
1073 else if (proto
== AnyP::PROTO_HTCP
)
1074 static_cast<PeerSelector
*>(data
)->handleHtcpReply(p
, type
, static_cast<HtcpReplyData
*>(pingdata
));
1079 debugs(44, DBG_IMPORTANT
, "ERROR: ignoring an ICP reply with unknown protocol " << proto
);
1083 PeerSelector::addSelection(CachePeer
*peer
, const hier_code code
)
1085 // Find the end of the servers list. Bail on a duplicate destination.
1086 auto **serversTail
= &servers
;
1087 while (const auto server
= *serversTail
) {
1088 // There can be at most one PINNED destination.
1089 // Non-PINNED destinations are uniquely identified by their CachePeer
1090 // (even though a DIRECT destination might match a cache_peer address).
1091 // TODO: We may still add duplicates because the same peer could have
1092 // been removed from `servers` already (and given to the requestor).
1093 const bool duplicate
= (server
->code
== PINNED
) ?
1094 (code
== PINNED
) : (server
->_peer
== peer
);
1096 debugs(44, 3, "skipping " << PeerSelectionDumper(this, peer
, code
) <<
1097 "; have " << PeerSelectionDumper(this, server
->_peer
.get(), server
->code
));
1100 serversTail
= &server
->next
;
1103 debugs(44, 3, "adding " << PeerSelectionDumper(this, peer
, code
));
1104 *serversTail
= new FwdServer(peer
, code
);
1107 PeerSelector::PeerSelector(PeerSelectionInitiator
*initiator
):
1110 always_direct(Config
.accessList
.AlwaysDirect
?ACCESS_DUNNO
:ACCESS_DENIED
),
1111 never_direct(Config
.accessList
.NeverDirect
?ACCESS_DUNNO
:ACCESS_DENIED
),
1112 direct(DIRECT_UNKNOWN
),
1115 first_parent_miss(),
1116 closest_parent_miss(),
1118 hit_type(PEER_NONE
),
1119 acl_checklist (NULL
),
1120 initiator_(initiator
)
1122 ; // no local defaults.
1126 PeerSelector::url() const
1129 return SBuf(entry
->url());
1132 return request
->effectiveRequestUri();
1134 static const SBuf
noUrl("[no URL]");
1138 /// \returns valid/interested peer initiator or nil
1139 PeerSelectionInitiator
*
1140 PeerSelector::interestedInitiator()
1142 const auto initiator
= initiator_
.valid();
1145 debugs(44, 3, id
<< " initiator gone");
1149 if (!initiator
->subscribed
) {
1150 debugs(44, 3, id
<< " initiator lost interest");
1159 PeerSelector::wantsMoreDestinations() const {
1160 const auto maxCount
= Config
.forward_max_tries
;
1161 return maxCount
>= 0 && foundPaths
< static_cast<size_t>(maxCount
);
1165 PeerSelector::handlePath(const Comm::ConnectionPointer
&path
, FwdServer
&fs
)
1170 path
->peerType
= fs
.code
;
1171 path
->setPeer(fs
._peer
.get());
1173 // check for a configured outgoing address for this destination...
1174 getOutgoingAddress(request
, path
);
1175 debugs(44, 2, id
<< " found " << path
<< ", destination #" << foundPaths
<< " for " << url());
1177 debugs(44, 2, id
<< " found pinned, destination #" << foundPaths
<< " for " << url());
1179 request
->hier
.ping
= ping
; // may be updated later
1181 debugs(44, 2, " always_direct = " << always_direct
);
1182 debugs(44, 2, " never_direct = " << never_direct
);
1183 debugs(44, 2, " timedout = " << ping
.timedout
);
1185 if (const auto initiator
= interestedInitiator())
1186 initiator
->noteDestination(path
);
1189 InstanceIdDefinitions(PeerSelector
, "PeerSelector");
1191 ping_data::ping_data() :
1194 n_replies_expected(0),
1199 monitorRegistration(PingMonitor().npos())
1208 ping_data::deadline() const
1210 timeval timeInterval
;
1211 timeInterval
.tv_sec
= timeout
/ 1000;
1212 timeInterval
.tv_usec
= (timeout
% 1000) * 1000;
1215 tvAdd(result
, start
, timeInterval
);