3 * $Id: peer_select.cc,v 1.90 1998/11/12 06:33:32 wessels Exp $
5 * DEBUG: section 44 Peer Selection Algorithm
6 * AUTHOR: Duane Wessels
8 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
9 * ----------------------------------------------------------
11 * Squid is the result of efforts by numerous individuals from the
12 * Internet community. Development is led by Duane Wessels of the
13 * National Laboratory for Applied Network Research and funded by the
14 * National Science Foundation. Squid is Copyrighted (C) 1998 by
15 * Duane Wessels and the University of California San Diego. Please
16 * see the COPYRIGHT file for full details. Squid incorporates
17 * software developed and/or copyrighted by other sources. Please see
18 * the CREDITS file for full details.
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
38 const char *hier_strings
[] =
49 "CLOSEST_PARENT_MISS",
57 "NO_CACHE_DIGEST_DIRECT",
70 static char *DirectStr
[] =
77 static void peerSelectFoo(ps_state
*);
78 static void peerPingTimeout(void *data
);
79 static void peerSelectCallbackFail(ps_state
* psstate
);
80 static IRCB peerHandlePingReply
;
81 static void peerSelectStateFree(ps_state
* psstate
);
82 static void peerIcpParentMiss(peer
*, icp_common_t
*, ps_state
*);
84 static void peerHtcpParentMiss(peer
*, htcpReplyData
*, ps_state
*);
85 static void peerHandleHtcpReply(peer
*, peer_t
, htcpReplyData
*, void *);
87 static int peerCheckNetdbDirect(ps_state
* psstate
);
90 peerSelectStateFree(ps_state
* psstate
)
92 if (psstate
->acl_checklist
) {
93 debug(44, 1) ("calling aclChecklistFree() from peerSelectStateFree\n");
94 aclChecklistFree(psstate
->acl_checklist
);
96 requestUnlink(psstate
->request
);
97 psstate
->request
= NULL
;
99 assert(psstate
->entry
->ping_status
!= PING_WAITING
);
100 storeUnlockObject(psstate
->entry
);
101 psstate
->entry
= NULL
;
107 peerSelectIcpPing(request_t
* request
, int direct
, StoreEntry
* entry
)
112 debug(44, 3) ("peerSelectIcpPing: %s\n", storeUrl(entry
));
113 if (entry
->ping_status
!= PING_NONE
)
115 assert(direct
!= DIRECT_YES
);
116 if (!request
->flags
.hierarchical
&& direct
!= DIRECT_NO
)
118 if (EBIT_TEST(entry
->flags
, KEY_PRIVATE
) && !neighbors_do_private_keys
)
119 if (direct
!= DIRECT_NO
)
121 n
= neighborsCount(request
);
122 debug(44, 3) ("peerSelectIcpPing: counted %d neighbors\n", n
);
128 peerGetSomeParent(request_t
* request
, hier_code
* code
)
131 debug(44, 3) ("peerGetSomeParent: %s %s\n",
132 RequestMethodStr
[request
->method
],
134 if ((p
= getDefaultParent(request
))) {
135 *code
= DEFAULT_PARENT
;
138 if ((p
= getRoundRobinParent(request
))) {
139 *code
= ROUNDROBIN_PARENT
;
142 if ((p
= getFirstUpParent(request
))) {
143 *code
= FIRSTUP_PARENT
;
146 if ((p
= getAnyParent(request
))) {
147 *code
= ANY_OLD_PARENT
;
154 peerSelect(request_t
* request
,
160 ps_state
*psstate
= xcalloc(1, sizeof(ps_state
));
162 debug(44, 3) ("peerSelect: %s\n", storeUrl(entry
));
164 debug(44, 3) ("peerSelect: %s\n", RequestMethodStr
[request
->method
]);
165 cbdataAdd(psstate
, MEM_NONE
);
166 psstate
->request
= requestLink(request
);
167 psstate
->entry
= entry
;
168 psstate
->callback
= callback
;
169 psstate
->fail_callback
= fail_callback
;
170 psstate
->callback_data
= callback_data
;
171 #if USE_CACHE_DIGESTS
172 request
->hier
.peer_select_start
= current_time
;
175 storeLockObject(psstate
->entry
);
176 cbdataLock(callback_data
);
177 peerSelectFoo(psstate
);
181 peerCheckNeverDirectDone(int answer
, void *data
)
183 ps_state
*psstate
= data
;
184 psstate
->acl_checklist
= NULL
;
185 debug(44, 3) ("peerCheckNeverDirectDone: %d\n", answer
);
186 psstate
->never_direct
= answer
? 1 : -1;
187 peerSelectFoo(psstate
);
191 peerCheckAlwaysDirectDone(int answer
, void *data
)
193 ps_state
*psstate
= data
;
194 psstate
->acl_checklist
= NULL
;
195 debug(44, 3) ("peerCheckAlwaysDirectDone: %d\n", answer
);
196 psstate
->always_direct
= answer
? 1 : -1;
197 peerSelectFoo(psstate
);
201 peerSelectCallback(ps_state
* psstate
, peer
* p
)
203 StoreEntry
*entry
= psstate
->entry
;
204 void *data
= psstate
->callback_data
;
206 debug(44, 3) ("peerSelectCallback: %s\n", storeUrl(entry
));
207 if (entry
->ping_status
== PING_WAITING
)
208 eventDelete(peerPingTimeout
, psstate
);
209 entry
->ping_status
= PING_DONE
;
211 psstate
->ping
.stop
= current_time
;
212 if (cbdataValid(data
))
213 psstate
->callback(p
, data
);
215 peerSelectStateFree(psstate
);
219 peerSelectCallbackFail(ps_state
* psstate
)
221 request_t
*request
= psstate
->request
;
222 void *data
= psstate
->callback_data
;
223 const char *url
= psstate
->entry
? storeUrl(psstate
->entry
) : urlCanonical(request
);
225 psstate
->entry
->ping_status
= PING_DONE
;
226 debug(44, 1) ("Failed to select source for '%s'\n", url
);
227 debug(44, 1) (" always_direct = %d\n", psstate
->always_direct
);
228 debug(44, 1) (" never_direct = %d\n", psstate
->never_direct
);
229 debug(44, 1) (" timedout = %d\n", psstate
->ping
.timedout
);
230 if (cbdataValid(data
))
231 psstate
->fail_callback(NULL
, data
);
233 peerSelectStateFree(psstate
);
237 peerCheckNetdbDirect(ps_state
* psstate
)
239 peer
*p
= whichPeer(&psstate
->closest_parent_miss
);
244 myrtt
= netdbHostRtt(psstate
->request
->host
);
245 debug(44, 3) ("peerCheckNetdbDirect: MY RTT = %d msec\n", myrtt
);
246 debug(44, 3) ("peerCheckNetdbDirect: closest_parent_miss RTT = %d msec\n",
247 psstate
->ping
.p_rtt
);
248 if (myrtt
&& myrtt
< psstate
->ping
.p_rtt
)
250 myhops
= netdbHostHops(psstate
->request
->host
);
251 debug(44, 3) ("peerCheckNetdbDirect: MY hops = %d\n", myhops
);
252 debug(44, 3) ("peerCheckNetdbDirect: minimum_direct_hops = %d\n",
253 Config
.minDirectHops
);
254 if (myhops
&& myhops
<= Config
.minDirectHops
)
260 peerSelectFoo(ps_state
* psstate
)
264 StoreEntry
*entry
= psstate
->entry
;
265 request_t
*request
= psstate
->request
;
267 debug(44, 3) ("peerSelectFoo: '%s %s'\n",
268 RequestMethodStr
[request
->method
],
270 if (psstate
->always_direct
== 0 && Config
.accessList
.AlwaysDirect
) {
271 psstate
->acl_checklist
= aclChecklistCreate(
272 Config
.accessList
.AlwaysDirect
,
274 request
->client_addr
,
275 NULL
, /* user agent */
277 aclNBCheck(psstate
->acl_checklist
,
278 peerCheckAlwaysDirectDone
,
281 } else if (psstate
->always_direct
> 0) {
283 } else if (psstate
->never_direct
== 0 && Config
.accessList
.NeverDirect
) {
284 psstate
->acl_checklist
= aclChecklistCreate(
285 Config
.accessList
.NeverDirect
,
287 request
->client_addr
,
288 NULL
, /* user agent */
290 aclNBCheck(psstate
->acl_checklist
,
291 peerCheckNeverDirectDone
,
294 } else if (psstate
->never_direct
> 0) {
296 } else if (request
->flags
.loopdetect
) {
299 direct
= DIRECT_MAYBE
;
301 debug(44, 3) ("peerSelectFoo: direct = %s\n", DirectStr
[direct
]);
302 if (direct
== DIRECT_YES
) {
303 debug(44, 3) ("peerSelectFoo: DIRECT\n");
304 hierarchyNote(&request
->hier
, DIRECT
, &psstate
->ping
, request
->host
);
305 peerSelectCallback(psstate
, NULL
);
308 if ((p
= getSingleParent(request
))) {
309 code
= SINGLE_PARENT
;
310 debug(44, 3) ("peerSelectFoo: %s/%s\n", hier_strings
[code
], p
->host
);
311 hierarchyNote(&request
->hier
, code
, &psstate
->ping
, p
->host
);
312 peerSelectCallback(psstate
, p
);
315 if (!request
->flags
.hierarchical
&& direct
!= DIRECT_NO
) {
316 debug(44, 3) ("peerSelectFoo: DIRECT for non-hierarchical request\n");
317 hierarchyNote(&request
->hier
, DIRECT
, &psstate
->ping
, request
->host
);
318 peerSelectCallback(psstate
, NULL
);
321 #if USE_CACHE_DIGESTS
322 else if ((p
= neighborsDigestSelect(request
, entry
))) {
323 debug(44, 2) ("peerSelect: Using Cache Digest\n");
324 request
->hier
.alg
= PEER_SA_DIGEST
;
325 code
= CACHE_DIGEST_HIT
;
326 debug(44, 2) ("peerSelect: %s/%s\n", hier_strings
[code
], p
->host
);
327 hierarchyNote(&request
->hier
, code
, &psstate
->ping
, p
->host
);
328 peerSelectCallback(psstate
, p
);
333 else if ((p
= carpSelectParent(request
))) {
334 hierarchyNote(&request
->hier
, CARP
, &psstate
->ping
, p
->host
);
335 peerSelectCallback(psstate
, p
);
339 else if ((p
= netdbClosestParent(request
))) {
340 request
->hier
.alg
= PEER_SA_NETDB
;
341 code
= CLOSEST_PARENT
;
342 debug(44, 2) ("peerSelect: %s/%s\n", hier_strings
[code
], p
->host
);
343 hierarchyNote(&request
->hier
, code
, &psstate
->ping
, p
->host
);
344 peerSelectCallback(psstate
, p
);
346 } else if (peerSelectIcpPing(request
, direct
, entry
)) {
347 assert(entry
->ping_status
== PING_NONE
);
348 request
->hier
.alg
= PEER_SA_ICP
;
349 debug(44, 3) ("peerSelect: Doing ICP pings\n");
350 psstate
->ping
.start
= current_time
;
351 psstate
->ping
.n_sent
= neighborsUdpPing(request
,
355 &psstate
->ping
.n_replies_expected
,
356 &psstate
->ping
.timeout
);
357 if (psstate
->ping
.n_sent
== 0)
358 debug(44, 0) ("WARNING: neighborsUdpPing returned 0\n");
359 debug(44, 3) ("peerSelectFoo: %d ICP replies expected, RTT %d msec\n",
360 psstate
->ping
.n_replies_expected
, psstate
->ping
.timeout
);
361 if (psstate
->ping
.n_replies_expected
> 0) {
362 entry
->ping_status
= PING_WAITING
;
363 eventAdd("peerPingTimeout",
366 0.001 * psstate
->ping
.timeout
,
371 debug(44, 3) ("peerSelectFoo: After peerSelectIcpPing.\n");
372 if (peerCheckNetdbDirect(psstate
)) {
373 code
= CLOSEST_DIRECT
;
374 debug(44, 3) ("peerSelect: %s/%s\n", hier_strings
[code
], request
->host
);
375 hierarchyNote(&request
->hier
, code
, &psstate
->ping
, request
->host
);
376 peerSelectCallback(psstate
, NULL
);
377 } else if ((p
= whichPeer(&psstate
->closest_parent_miss
))) {
378 code
= CLOSEST_PARENT_MISS
;
379 debug(44, 3) ("peerSelect: %s/%s\n", hier_strings
[code
], p
->host
);
380 hierarchyNote(&request
->hier
, code
, &psstate
->ping
, p
->host
);
381 peerSelectCallback(psstate
, p
);
382 } else if ((p
= whichPeer(&psstate
->first_parent_miss
))) {
383 code
= FIRST_PARENT_MISS
;
384 debug(44, 3) ("peerSelect: %s/%s\n", hier_strings
[code
], p
->host
);
385 hierarchyNote(&request
->hier
, code
, &psstate
->ping
, p
->host
);
386 peerSelectCallback(psstate
, p
);
387 } else if (Config
.onoff
.prefer_direct
&& direct
!= DIRECT_NO
) {
389 debug(44, 3) ("peerSelect: %s/%s\n", hier_strings
[code
], request
->host
);
390 hierarchyNote(&request
->hier
, code
, &psstate
->ping
, request
->host
);
391 peerSelectCallback(psstate
, NULL
);
392 } else if ((p
= peerGetSomeParent(request
, &code
))) {
393 debug(44, 3) ("peerSelect: %s/%s\n", hier_strings
[code
], p
->host
);
394 hierarchyNote(&request
->hier
, code
, &psstate
->ping
, p
->host
);
395 peerSelectCallback(psstate
, p
);
396 } else if (direct
!= DIRECT_NO
) {
398 debug(44, 3) ("peerSelect: %s/%s\n", hier_strings
[code
], request
->host
);
399 hierarchyNote(&request
->hier
, code
, &psstate
->ping
, request
->host
);
400 peerSelectCallback(psstate
, NULL
);
402 code
= NO_DIRECT_FAIL
;
403 hierarchyNote(&request
->hier
, code
, &psstate
->ping
, NULL
);
404 peerSelectCallbackFail(psstate
);
409 peerPingTimeout(void *data
)
411 ps_state
*psstate
= data
;
412 StoreEntry
*entry
= psstate
->entry
;
414 debug(44, 3) ("peerPingTimeout: '%s'\n", storeUrl(entry
));
415 entry
->ping_status
= PING_TIMEOUT
;
416 if (!cbdataValid(psstate
->callback_data
)) {
417 /* request aborted */
418 cbdataUnlock(psstate
->callback_data
);
419 peerSelectStateFree(psstate
);
422 PeerStats
.timeouts
++;
423 psstate
->ping
.timedout
= 1;
424 peerSelectFoo(psstate
);
430 memset(&PeerStats
, '\0', sizeof(PeerStats
));
431 assert(sizeof(hier_strings
) == (HIER_MAX
+ 1) * sizeof(char *));
435 peerIcpParentMiss(peer
* p
, icp_common_t
* header
, ps_state
* ps
)
439 if (Config
.onoff
.query_icmp
) {
440 if (header
->flags
& ICP_FLAG_SRC_RTT
) {
441 rtt
= header
->pad
& 0xFFFF;
442 hops
= (header
->pad
>> 16) & 0xFFFF;
443 if (rtt
> 0 && rtt
< 0xFFFF)
444 netdbUpdatePeer(ps
->request
, p
, rtt
, hops
);
445 if (rtt
&& (ps
->ping
.p_rtt
== 0 || rtt
< ps
->ping
.p_rtt
)) {
446 ps
->closest_parent_miss
= p
->in_addr
;
447 ps
->ping
.p_rtt
= rtt
;
451 /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
452 if (p
->options
.closest_only
)
454 /* set FIRST_MISS if there is no CLOSEST parent */
455 if (ps
->closest_parent_miss
.sin_addr
.s_addr
!= any_addr
.s_addr
)
457 rtt
= tvSubMsec(ps
->ping
.start
, current_time
) / p
->weight
;
458 if (ps
->ping
.w_rtt
== 0 || rtt
< ps
->ping
.w_rtt
) {
459 ps
->first_parent_miss
= p
->in_addr
;
460 ps
->ping
.w_rtt
= rtt
;
465 peerHandleIcpReply(peer
* p
, peer_t type
, icp_common_t
* header
, void *data
)
467 ps_state
*psstate
= data
;
468 icp_opcode op
= header
->opcode
;
469 request_t
*request
= psstate
->request
;
470 debug(44, 3) ("peerHandleIcpReply: %s %s\n",
472 storeUrl(psstate
->entry
));
473 #if USE_CACHE_DIGESTS && 0
474 /* do cd lookup to count false misses */
476 peerNoteDigestLookup(request
, p
,
477 peerDigestLookup(p
, request
, psstate
->entry
));
479 psstate
->ping
.n_recv
++;
480 if (op
== ICP_MISS
|| op
== ICP_DECHO
) {
481 if (type
== PEER_PARENT
)
482 peerIcpParentMiss(p
, header
, psstate
);
483 } else if (op
== ICP_HIT
) {
484 hierarchyNote(&request
->hier
,
485 type
== PEER_PARENT
? PARENT_HIT
: SIBLING_HIT
,
488 peerSelectCallback(psstate
, p
);
490 } else if (op
== ICP_SECHO
) {
491 hierarchyNote(&request
->hier
,
495 peerSelectCallback(psstate
, NULL
);
498 if (psstate
->ping
.n_recv
< psstate
->ping
.n_replies_expected
)
500 peerSelectFoo(psstate
);
505 peerHandleHtcpReply(peer
* p
, peer_t type
, htcpReplyData
* htcp
, void *data
)
507 ps_state
*psstate
= data
;
508 request_t
*request
= psstate
->request
;
509 debug(44, 3) ("peerHandleIcpReply: %s %s\n",
510 htcp
->hit
? "HIT" : "MISS",
511 storeUrl(psstate
->entry
));
512 psstate
->ping
.n_recv
++;
514 hierarchyNote(&request
->hier
,
515 type
== PEER_PARENT
? PARENT_HIT
: SIBLING_HIT
,
518 peerSelectCallback(psstate
, p
);
521 if (type
== PEER_PARENT
)
522 peerHtcpParentMiss(p
, htcp
, psstate
);
523 if (psstate
->ping
.n_recv
< psstate
->ping
.n_replies_expected
)
525 peerSelectFoo(psstate
);
529 peerHtcpParentMiss(peer
* p
, htcpReplyData
* htcp
, ps_state
* ps
)
533 if (Config
.onoff
.query_icmp
) {
534 if (htcp
->cto
.rtt
> 0) {
535 rtt
= (int) htcp
->cto
.rtt
* 1000;
536 hops
= (int) htcp
->cto
.hops
* 1000;
537 netdbUpdatePeer(ps
->request
, p
, rtt
, hops
);
538 if (rtt
&& (ps
->ping
.p_rtt
== 0 || rtt
< ps
->ping
.p_rtt
)) {
539 ps
->closest_parent_miss
= p
->in_addr
;
540 ps
->ping
.p_rtt
= rtt
;
544 /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
545 if (p
->options
.closest_only
)
547 /* set FIRST_MISS if there is no CLOSEST parent */
548 if (ps
->closest_parent_miss
.sin_addr
.s_addr
!= any_addr
.s_addr
)
550 rtt
= tvSubMsec(ps
->ping
.start
, current_time
) / p
->weight
;
551 if (ps
->ping
.w_rtt
== 0 || rtt
< ps
->ping
.w_rtt
) {
552 ps
->first_parent_miss
= p
->in_addr
;
553 ps
->ping
.w_rtt
= rtt
;
559 peerHandlePingReply(peer
* p
, peer_t type
, protocol_t proto
, void *pingdata
, void *data
)
561 if (proto
== PROTO_ICP
)
562 peerHandleIcpReply(p
, type
, pingdata
, data
);
564 else if (proto
== PROTO_HTCP
)
565 peerHandleHtcpReply(p
, type
, pingdata
, data
);
568 debug(44, 1) ("peerHandlePingReply: unknown protocol_t %d\n", (int) proto
);