4 * DEBUG: section 44 Peer Selection Algorithm
5 * AUTHOR: Duane Wessels
7 * SQUID Web Proxy Cache http://www.squid-cache.org/
8 * ----------------------------------------------------------
10 * Squid is the result of efforts by numerous individuals from
11 * the Internet community; see the CONTRIBUTORS file for full
12 * details. Many organizations have provided support for Squid's
13 * development; see the SPONSORS file for full details. Squid is
14 * Copyrighted (C) 2001 by the Regents of the University of
15 * California; see the COPYRIGHT file for full details. Squid
16 * incorporates software developed and/or copyrighted by other
17 * sources; see the CREDITS file for full details.
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
37 #include "PeerSelectState.h"
40 #include "HttpRequest.h"
41 #include "ACLChecklist.h"
44 #include "SquidTime.h"
45 #include "icmp/net_db.h"
47 const char *hier_strings
[] = {
56 "CLOSEST_PARENT_MISS",
77 static const char *DirectStr
[] = {
84 static void peerSelectFoo(ps_state
*);
85 static void peerPingTimeout(void *data
);
86 static void peerSelectCallback(ps_state
* psstate
);
87 static IRCB peerHandlePingReply
;
88 static void peerSelectStateFree(ps_state
* psstate
);
89 static void peerIcpParentMiss(peer
*, icp_common_t
*, ps_state
*);
91 static void peerHtcpParentMiss(peer
*, htcpReplyData
*, ps_state
*);
92 static void peerHandleHtcpReply(peer
*, peer_t
, htcpReplyData
*, void *);
94 static int peerCheckNetdbDirect(ps_state
* psstate
);
95 static void peerGetSomeNeighbor(ps_state
*);
96 static void peerGetSomeNeighborReplies(ps_state
*);
97 static void peerGetSomeDirect(ps_state
*);
98 static void peerGetSomeParent(ps_state
*);
99 static void peerGetAllParents(ps_state
*);
100 static void peerAddFwdServer(FwdServer
**, peer
*, hier_code
);
101 static void peerSelectPinned(ps_state
* ps
);
103 CBDATA_CLASS_INIT(ps_state
);
106 peerSelectStateFree(ps_state
* psstate
)
108 if (psstate
->acl_checklist
) {
109 debugs(44, 1, "calling aclChecklistFree() from peerSelectStateFree");
110 delete (psstate
->acl_checklist
);
113 HTTPMSGUNLOCK(psstate
->request
);
115 if (psstate
->entry
) {
116 assert(psstate
->entry
->ping_status
!= PING_WAITING
);
117 psstate
->entry
->unlock();
118 psstate
->entry
= NULL
;
125 peerSelectIcpPing(HttpRequest
* request
, int direct
, StoreEntry
* entry
)
129 assert(entry
->ping_status
== PING_NONE
);
130 assert(direct
!= DIRECT_YES
);
131 debugs(44, 3, "peerSelectIcpPing: " << entry
->url() );
133 if (!request
->flags
.hierarchical
&& direct
!= DIRECT_NO
)
136 if (EBIT_TEST(entry
->flags
, KEY_PRIVATE
) && !neighbors_do_private_keys
)
137 if (direct
!= DIRECT_NO
)
140 n
= neighborsCount(request
);
142 debugs(44, 3, "peerSelectIcpPing: counted " << n
<< " neighbors");
149 peerSelect(HttpRequest
* request
,
157 debugs(44, 3, "peerSelect: " << entry
->url() );
159 debugs(44, 3, "peerSelect: " << RequestMethodStr(request
->method
));
161 psstate
= new ps_state
;
163 psstate
->request
= HTTPMSGLOCK(request
);
165 psstate
->entry
= entry
;
167 psstate
->callback
= callback
;
169 psstate
->callback_data
= cbdataReference(callback_data
);
171 psstate
->direct
= DIRECT_UNKNOWN
;
173 #if USE_CACHE_DIGESTS
175 request
->hier
.peer_select_start
= current_time
;
180 psstate
->entry
->lock();
182 peerSelectFoo(psstate
);
186 peerCheckNeverDirectDone(int answer
, void *data
)
188 ps_state
*psstate
= (ps_state
*) data
;
189 psstate
->acl_checklist
= NULL
;
190 debugs(44, 3, "peerCheckNeverDirectDone: " << answer
);
191 psstate
->never_direct
= answer
? 1 : -1;
192 peerSelectFoo(psstate
);
196 peerCheckAlwaysDirectDone(int answer
, void *data
)
198 ps_state
*psstate
= (ps_state
*)data
;
199 psstate
->acl_checklist
= NULL
;
200 debugs(44, 3, "peerCheckAlwaysDirectDone: " << answer
);
201 psstate
->always_direct
= answer
? 1 : -1;
202 peerSelectFoo(psstate
);
206 peerSelectCallback(ps_state
* psstate
)
208 StoreEntry
*entry
= psstate
->entry
;
209 FwdServer
*fs
= psstate
->servers
;
214 debugs(44, 3, "peerSelectCallback: " << entry
->url() );
216 if (entry
->ping_status
== PING_WAITING
)
217 eventDelete(peerPingTimeout
, psstate
);
219 entry
->ping_status
= PING_DONE
;
223 debugs(44, 1, "Failed to select source for '" << entry
->url() << "'" );
224 debugs(44, 1, " always_direct = " << psstate
->always_direct
);
225 debugs(44, 1, " never_direct = " << psstate
->never_direct
);
226 debugs(44, 1, " timedout = " << psstate
->ping
.timedout
);
229 psstate
->ping
.stop
= current_time
;
230 psstate
->request
->hier
.ping
= psstate
->ping
;
231 callback
= psstate
->callback
;
232 psstate
->callback
= NULL
;
234 if (cbdataReferenceValidDone(psstate
->callback_data
, &cbdata
)) {
235 psstate
->servers
= NULL
;
236 callback(fs
, cbdata
);
239 peerSelectStateFree(psstate
);
243 peerCheckNetdbDirect(ps_state
* psstate
)
250 if (psstate
->direct
== DIRECT_NO
)
253 /* base lookup on RTT and Hops if ICMP NetDB is enabled. */
255 myrtt
= netdbHostRtt(psstate
->request
->GetHost());
257 debugs(44, 3, "peerCheckNetdbDirect: MY RTT = " << myrtt
<< " msec");
258 debugs(44, 3, "peerCheckNetdbDirect: minimum_direct_rtt = " << Config
.minDirectRtt
<< " msec");
260 if (myrtt
&& myrtt
<= Config
.minDirectRtt
)
263 myhops
= netdbHostHops(psstate
->request
->GetHost());
265 debugs(44, 3, "peerCheckNetdbDirect: MY hops = " << myhops
);
266 debugs(44, 3, "peerCheckNetdbDirect: minimum_direct_hops = " << Config
.minDirectHops
);
268 if (myhops
&& myhops
<= Config
.minDirectHops
)
271 p
= whichPeer(psstate
->closest_parent_miss
);
276 debugs(44, 3, "peerCheckNetdbDirect: closest_parent_miss RTT = " << psstate
->ping
.p_rtt
<< " msec");
278 if (myrtt
&& myrtt
<= psstate
->ping
.p_rtt
)
281 #endif /* USE_ICMP */
287 peerSelectFoo(ps_state
* ps
)
289 StoreEntry
*entry
= ps
->entry
;
290 HttpRequest
*request
= ps
->request
;
291 debugs(44, 3, "peerSelectFoo: '" << RequestMethodStr(request
->method
) << " " << request
->GetHost() << "'");
293 if (ps
->direct
== DIRECT_UNKNOWN
) {
294 if (ps
->always_direct
== 0 && Config
.accessList
.AlwaysDirect
) {
295 ps
->acl_checklist
= aclChecklistCreate(
296 Config
.accessList
.AlwaysDirect
,
299 ps
->acl_checklist
->nonBlockingCheck(peerCheckAlwaysDirectDone
,
302 } else if (ps
->always_direct
> 0) {
303 ps
->direct
= DIRECT_YES
;
304 } else if (ps
->never_direct
== 0 && Config
.accessList
.NeverDirect
) {
305 ps
->acl_checklist
= aclChecklistCreate(
306 Config
.accessList
.NeverDirect
,
309 ps
->acl_checklist
->nonBlockingCheck(peerCheckNeverDirectDone
,
312 } else if (ps
->never_direct
> 0) {
313 ps
->direct
= DIRECT_NO
;
314 } else if (request
->flags
.accelerated
) {
315 ps
->direct
= DIRECT_NO
;
316 } else if (request
->flags
.loopdetect
) {
317 ps
->direct
= DIRECT_YES
;
318 } else if (peerCheckNetdbDirect(ps
)) {
319 ps
->direct
= DIRECT_YES
;
321 ps
->direct
= DIRECT_MAYBE
;
324 debugs(44, 3, "peerSelectFoo: direct = " << DirectStr
[ps
->direct
]);
327 if (!entry
|| entry
->ping_status
== PING_NONE
)
328 peerSelectPinned(ps
);
331 } else if (entry
->ping_status
== PING_NONE
) {
332 peerGetSomeNeighbor(ps
);
334 if (entry
->ping_status
== PING_WAITING
)
336 } else if (entry
->ping_status
== PING_WAITING
) {
337 peerGetSomeNeighborReplies(ps
);
338 entry
->ping_status
= PING_DONE
;
341 switch (ps
->direct
) {
344 peerGetSomeDirect(ps
);
348 peerGetSomeParent(ps
);
349 peerGetAllParents(ps
);
354 if (Config
.onoff
.prefer_direct
)
355 peerGetSomeDirect(ps
);
357 if (request
->flags
.hierarchical
|| !Config
.onoff
.nonhierarchical_direct
)
358 peerGetSomeParent(ps
);
360 if (!Config
.onoff
.prefer_direct
)
361 peerGetSomeDirect(ps
);
366 peerSelectCallback(ps
);
372 * Selects a pinned connection
374 int peerAllowedToUse(const peer
* p
, HttpRequest
* request
);
376 peerSelectPinned(ps_state
* ps
)
378 HttpRequest
*request
= ps
->request
;
380 if (!request
->pinnedConnection())
382 if (request
->pinnedConnection()->validatePinnedConnection(request
) != -1) {
383 peer
= request
->pinnedConnection()->pinnedPeer();
384 if (peer
&& peerAllowedToUse(peer
, request
)) {
385 peerAddFwdServer(&ps
->servers
, peer
, PINNED
);
387 ps
->entry
->ping_status
= PING_DONE
; /* Skip ICP */
388 } else if (!peer
&& ps
->direct
!= DIRECT_NO
) {
389 peerAddFwdServer(&ps
->servers
, NULL
, PINNED
);
391 ps
->entry
->ping_status
= PING_DONE
; /* Skip ICP */
397 * peerGetSomeNeighbor
399 * Selects a neighbor (parent or sibling) based on one of the
403 * ICMP Netdb RTT estimates
407 peerGetSomeNeighbor(ps_state
* ps
)
409 StoreEntry
*entry
= ps
->entry
;
410 HttpRequest
*request
= ps
->request
;
412 hier_code code
= HIER_NONE
;
413 assert(entry
->ping_status
== PING_NONE
);
415 if (ps
->direct
== DIRECT_YES
) {
416 entry
->ping_status
= PING_DONE
;
420 #if USE_CACHE_DIGESTS
421 if ((p
= neighborsDigestSelect(request
))) {
422 if (neighborType(p
, request
) == PEER_PARENT
)
423 code
= CD_PARENT_HIT
;
425 code
= CD_SIBLING_HIT
;
428 if ((p
= netdbClosestParent(request
))) {
429 code
= CLOSEST_PARENT
;
430 } else if (peerSelectIcpPing(request
, ps
->direct
, entry
)) {
431 debugs(44, 3, "peerSelect: Doing ICP pings");
432 ps
->ping
.start
= current_time
;
433 ps
->ping
.n_sent
= neighborsUdpPing(request
,
437 &ps
->ping
.n_replies_expected
,
440 if (ps
->ping
.n_sent
== 0)
441 debugs(44, 0, "WARNING: neighborsUdpPing returned 0");
442 debugs(44, 3, "peerSelect: " << ps
->ping
.n_replies_expected
<<
443 " ICP replies expected, RTT " << ps
->ping
.timeout
<<
447 if (ps
->ping
.n_replies_expected
> 0) {
448 entry
->ping_status
= PING_WAITING
;
449 eventAdd("peerPingTimeout",
452 0.001 * ps
->ping
.timeout
,
458 if (code
!= HIER_NONE
) {
460 debugs(44, 3, "peerSelect: " << hier_strings
[code
] << "/" << p
->host
);
461 peerAddFwdServer(&ps
->servers
, p
, code
);
464 entry
->ping_status
= PING_DONE
;
468 * peerGetSomeNeighborReplies
470 * Selects a neighbor (parent or sibling) based on ICP/HTCP replies.
473 peerGetSomeNeighborReplies(ps_state
* ps
)
475 HttpRequest
*request
= ps
->request
;
477 hier_code code
= HIER_NONE
;
478 assert(ps
->entry
->ping_status
== PING_WAITING
);
479 assert(ps
->direct
!= DIRECT_YES
);
481 if (peerCheckNetdbDirect(ps
)) {
482 code
= CLOSEST_DIRECT
;
483 debugs(44, 3, "peerSelect: " << hier_strings
[code
] << "/" << request
->GetHost());
484 peerAddFwdServer(&ps
->servers
, NULL
, code
);
489 code
= ps
->hit_type
== PEER_PARENT
? PARENT_HIT
: SIBLING_HIT
;
491 if (!ps
->closest_parent_miss
.IsAnyAddr()) {
492 p
= whichPeer(ps
->closest_parent_miss
);
493 code
= CLOSEST_PARENT_MISS
;
494 } else if (!ps
->first_parent_miss
.IsAnyAddr()) {
495 p
= whichPeer(ps
->first_parent_miss
);
496 code
= FIRST_PARENT_MISS
;
499 if (p
&& code
!= HIER_NONE
) {
500 debugs(44, 3, "peerSelect: " << hier_strings
[code
] << "/" << p
->host
);
501 peerAddFwdServer(&ps
->servers
, p
, code
);
509 * Simply adds a 'direct' entry to the FwdServers list if this
510 * request can be forwarded directly to the origin server
513 peerGetSomeDirect(ps_state
* ps
)
515 if (ps
->direct
== DIRECT_NO
)
518 /* WAIS is not implemented natively */
519 if (ps
->request
->protocol
== PROTO_WAIS
)
522 peerAddFwdServer(&ps
->servers
, NULL
, HIER_DIRECT
);
526 peerGetSomeParent(ps_state
* ps
)
529 HttpRequest
*request
= ps
->request
;
530 hier_code code
= HIER_NONE
;
531 debugs(44, 3, "peerGetSomeParent: " << RequestMethodStr(request
->method
) << " " << request
->GetHost());
533 if (ps
->direct
== DIRECT_YES
)
536 if ((p
= getDefaultParent(request
))) {
537 code
= DEFAULT_PARENT
;
538 } else if ((p
= peerUserHashSelectParent(request
))) {
539 code
= USERHASH_PARENT
;
540 } else if ((p
= peerSourceHashSelectParent(request
))) {
541 code
= SOURCEHASH_PARENT
;
542 } else if ((p
= carpSelectParent(request
))) {
544 } else if ((p
= getRoundRobinParent(request
))) {
545 code
= ROUNDROBIN_PARENT
;
546 } else if ((p
= getWeightedRoundRobinParent(request
))) {
547 code
= ROUNDROBIN_PARENT
;
548 } else if ((p
= getFirstUpParent(request
))) {
549 code
= FIRSTUP_PARENT
;
550 } else if ((p
= getAnyParent(request
))) {
551 code
= ANY_OLD_PARENT
;
554 if (code
!= HIER_NONE
) {
555 debugs(44, 3, "peerSelect: " << hier_strings
[code
] << "/" << p
->host
);
556 peerAddFwdServer(&ps
->servers
, p
, code
);
560 /* Adds alive parents. Used as a last resort for never_direct.
563 peerGetAllParents(ps_state
* ps
)
566 HttpRequest
*request
= ps
->request
;
567 /* Add all alive parents */
569 for (p
= Config
.peers
; p
; p
= p
->next
) {
570 /* XXX: neighbors.c lacks a public interface for enumerating
571 * parents to a request so we have to dig some here..
574 if (neighborType(p
, request
) != PEER_PARENT
)
577 if (!peerHTTPOkay(p
, request
))
580 debugs(15, 3, "peerGetAllParents: adding alive parent " << p
->host
);
582 peerAddFwdServer(&ps
->servers
, p
, ANY_OLD_PARENT
);
585 /* XXX: should add dead parents here, but it is currently
586 * not possible to find out which parents are dead or which
587 * simply are not configured to handle the request.
589 /* Add default parent as a last resort */
590 if ((p
= getDefaultParent(request
))) {
591 peerAddFwdServer(&ps
->servers
, p
, DEFAULT_PARENT
);
596 peerPingTimeout(void *data
)
598 ps_state
*psstate
= (ps_state
*)data
;
599 StoreEntry
*entry
= psstate
->entry
;
602 debugs(44, 3, "peerPingTimeout: '" << entry
->url() << "'" );
604 if (!cbdataReferenceValid(psstate
->callback_data
)) {
605 /* request aborted */
606 entry
->ping_status
= PING_DONE
;
607 cbdataReferenceDone(psstate
->callback_data
);
608 peerSelectStateFree(psstate
);
612 PeerStats
.timeouts
++;
613 psstate
->ping
.timedout
= 1;
614 peerSelectFoo(psstate
);
620 memset(&PeerStats
, '\0', sizeof(PeerStats
));
621 assert(sizeof(hier_strings
) == (HIER_MAX
+ 1) * sizeof(char *));
625 peerIcpParentMiss(peer
* p
, icp_common_t
* header
, ps_state
* ps
)
630 if (Config
.onoff
.query_icmp
) {
631 if (header
->flags
& ICP_FLAG_SRC_RTT
) {
632 rtt
= header
->pad
& 0xFFFF;
633 int hops
= (header
->pad
>> 16) & 0xFFFF;
635 if (rtt
> 0 && rtt
< 0xFFFF)
636 netdbUpdatePeer(ps
->request
, p
, rtt
, hops
);
638 if (rtt
&& (ps
->ping
.p_rtt
== 0 || rtt
< ps
->ping
.p_rtt
)) {
639 ps
->closest_parent_miss
= p
->in_addr
;
640 ps
->ping
.p_rtt
= rtt
;
644 #endif /* USE_ICMP */
646 /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
647 if (p
->options
.closest_only
)
650 /* set FIRST_MISS if there is no CLOSEST parent */
651 if (!ps
->closest_parent_miss
.IsAnyAddr())
654 rtt
= (tvSubMsec(ps
->ping
.start
, current_time
) - p
->basetime
) / p
->weight
;
659 if (ps
->first_parent_miss
.IsAnyAddr() || rtt
< ps
->ping
.w_rtt
) {
660 ps
->first_parent_miss
= p
->in_addr
;
661 ps
->ping
.w_rtt
= rtt
;
666 peerHandleIcpReply(peer
* p
, peer_t type
, icp_common_t
* header
, void *data
)
668 ps_state
*psstate
= (ps_state
*)data
;
669 icp_opcode op
= header
->getOpCode();
670 debugs(44, 3, "peerHandleIcpReply: " << icp_opcode_str
[op
] << " " << psstate
->entry
->url() );
671 #if USE_CACHE_DIGESTS && 0
672 /* do cd lookup to count false misses */
675 peerNoteDigestLookup(request
, p
,
676 peerDigestLookup(p
, request
, psstate
->entry
));
680 psstate
->ping
.n_recv
++;
682 if (op
== ICP_MISS
|| op
== ICP_DECHO
) {
683 if (type
== PEER_PARENT
)
684 peerIcpParentMiss(p
, header
, psstate
);
685 } else if (op
== ICP_HIT
) {
687 psstate
->hit_type
= type
;
688 peerSelectFoo(psstate
);
692 if (psstate
->ping
.n_recv
< psstate
->ping
.n_replies_expected
)
695 peerSelectFoo(psstate
);
700 peerHandleHtcpReply(peer
* p
, peer_t type
, htcpReplyData
* htcp
, void *data
)
702 ps_state
*psstate
= (ps_state
*)data
;
703 debugs(44, 3, "peerHandleHtcpReply: " <<
704 (htcp
->hit
? "HIT" : "MISS") << " " <<
705 psstate
->entry
->url() );
706 psstate
->ping
.n_recv
++;
710 psstate
->hit_type
= type
;
711 peerSelectFoo(psstate
);
715 if (type
== PEER_PARENT
)
716 peerHtcpParentMiss(p
, htcp
, psstate
);
718 if (psstate
->ping
.n_recv
< psstate
->ping
.n_replies_expected
)
721 peerSelectFoo(psstate
);
725 peerHtcpParentMiss(peer
* p
, htcpReplyData
* htcp
, ps_state
* ps
)
730 if (Config
.onoff
.query_icmp
) {
731 if (htcp
->cto
.rtt
> 0) {
732 rtt
= (int) htcp
->cto
.rtt
* 1000;
733 int hops
= (int) htcp
->cto
.hops
* 1000;
734 netdbUpdatePeer(ps
->request
, p
, rtt
, hops
);
736 if (rtt
&& (ps
->ping
.p_rtt
== 0 || rtt
< ps
->ping
.p_rtt
)) {
737 ps
->closest_parent_miss
= p
->in_addr
;
738 ps
->ping
.p_rtt
= rtt
;
742 #endif /* USE_ICMP */
744 /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
745 if (p
->options
.closest_only
)
748 /* set FIRST_MISS if there is no CLOSEST parent */
749 if (!ps
->closest_parent_miss
.IsAnyAddr())
752 rtt
= (tvSubMsec(ps
->ping
.start
, current_time
) - p
->basetime
) / p
->weight
;
757 if (ps
->first_parent_miss
.IsAnyAddr() || rtt
< ps
->ping
.w_rtt
) {
758 ps
->first_parent_miss
= p
->in_addr
;
759 ps
->ping
.w_rtt
= rtt
;
766 peerHandlePingReply(peer
* p
, peer_t type
, protocol_t proto
, void *pingdata
, void *data
)
768 if (proto
== PROTO_ICP
)
769 peerHandleIcpReply(p
, type
, (icp_common_t
*)pingdata
, data
);
773 else if (proto
== PROTO_HTCP
)
774 peerHandleHtcpReply(p
, type
, (htcpReplyData
*)pingdata
, data
);
779 debugs(44, 1, "peerHandlePingReply: unknown protocol_t " << proto
);
783 peerAddFwdServer(FwdServer
** FSVR
, peer
* p
, hier_code code
)
785 FwdServer
*fs
= (FwdServer
*)memAllocate(MEM_FWD_SERVER
);
786 debugs(44, 5, "peerAddFwdServer: adding " <<
787 (p
? p
->host
: "DIRECT") << " " <<
788 hier_strings
[code
] );
789 fs
->_peer
= cbdataReference(p
);
793 FSVR
= &(*FSVR
)->next
;
799 ps_state::operator new(size_t)
801 CBDATA_INIT_TYPE(ps_state
);
802 return cbdataAlloc(ps_state
);
805 ps_state::ps_state() : request (NULL
),
811 callback_data (NULL
),
814 closest_parent_miss(),
819 ; // no local defaults.
822 ping_data::ping_data() :
825 n_replies_expected(0),