]> git.ipfire.org Git - thirdparty/squid.git/blob - src/peer_select.cc
Andres Kroonmaa's MemPool tidyup, take 1. Take a whole bunch of
[thirdparty/squid.git] / src / peer_select.cc
1
2 /*
3 * $Id: peer_select.cc,v 1.109 2000/10/17 08:06:04 adrian Exp $
4 *
5 * DEBUG: section 44 Peer Selection Algorithm
6 * AUTHOR: Duane Wessels
7 *
8 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
9 * ----------------------------------------------------------
10 *
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 * the Regents of the University of California. Please see the
16 * COPYRIGHT file for full details. Squid incorporates software
17 * developed and/or copyrighted by other sources. Please see the
18 * CREDITS file for full details.
19 *
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.
24 *
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.
29 *
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.
33 *
34 */
35
36 #include "squid.h"
37
38 const char *hier_strings[] =
39 {
40 "NONE",
41 "DIRECT",
42 "SIBLING_HIT",
43 "PARENT_HIT",
44 "DEFAULT_PARENT",
45 "SINGLE_PARENT",
46 "FIRST_UP_PARENT",
47 "FIRST_PARENT_MISS",
48 "CLOSEST_PARENT_MISS",
49 "CLOSEST_PARENT",
50 "CLOSEST_DIRECT",
51 "NO_DIRECT_FAIL",
52 "SOURCE_FASTEST",
53 "ROUNDROBIN_PARENT",
54 #if USE_CACHE_DIGESTS
55 "CD_PARENT_HIT",
56 "CD_SIBLING_HIT",
57 #endif
58 #if USE_CARP
59 "CARP",
60 #endif
61 "ANY_PARENT",
62 "INVALID CODE"
63 };
64
65 static struct {
66 int timeouts;
67 } PeerStats;
68
69 static char *DirectStr[] =
70 {
71 "DIRECT_UNKNOWN",
72 "DIRECT_NO",
73 "DIRECT_MAYBE",
74 "DIRECT_YES"
75 };
76
77 static void peerSelectFoo(ps_state *);
78 static void peerPingTimeout(void *data);
79 static void peerSelectCallback(ps_state * psstate);
80 static IRCB peerHandlePingReply;
81 static void peerSelectStateFree(ps_state * psstate);
82 static void peerIcpParentMiss(peer *, icp_common_t *, ps_state *);
83 #if USE_HTCP
84 static void peerHtcpParentMiss(peer *, htcpReplyData *, ps_state *);
85 static void peerHandleHtcpReply(peer *, peer_t, htcpReplyData *, void *);
86 #endif
87 static int peerCheckNetdbDirect(ps_state * psstate);
88 static void peerGetSomeNeighbor(ps_state *);
89 static void peerGetSomeNeighborReplies(ps_state *);
90 static void peerGetSomeDirect(ps_state *);
91 static void peerGetSomeParent(ps_state *);
92 static void peerGetAllParents(ps_state *);
93 static void peerAddFwdServer(FwdServer **, peer *, hier_code);
94
95 static void
96 peerSelectStateFree(ps_state * psstate)
97 {
98 if (psstate->acl_checklist) {
99 debug(44, 1) ("calling aclChecklistFree() from peerSelectStateFree\n");
100 aclChecklistFree(psstate->acl_checklist);
101 }
102 requestUnlink(psstate->request);
103 psstate->request = NULL;
104 if (psstate->entry) {
105 assert(psstate->entry->ping_status != PING_WAITING);
106 storeUnlockObject(psstate->entry);
107 psstate->entry = NULL;
108 }
109 cbdataFree(psstate);
110 }
111
112 int
113 peerSelectIcpPing(request_t * request, int direct, StoreEntry * entry)
114 {
115 int n;
116 assert(entry);
117 assert(entry->ping_status == PING_NONE);
118 assert(direct != DIRECT_YES);
119 debug(44, 3) ("peerSelectIcpPing: %s\n", storeUrl(entry));
120 if (!request->flags.hierarchical && direct != DIRECT_NO)
121 return 0;
122 if (EBIT_TEST(entry->flags, KEY_PRIVATE) && !neighbors_do_private_keys)
123 if (direct != DIRECT_NO)
124 return 0;
125 n = neighborsCount(request);
126 debug(44, 3) ("peerSelectIcpPing: counted %d neighbors\n", n);
127 return n;
128 }
129
130
131 void
132 peerSelect(request_t * request,
133 StoreEntry * entry,
134 PSC * callback,
135 void *callback_data)
136 {
137 ps_state *psstate = memAllocate(MEM_PS_STATE);
138 if (entry)
139 debug(44, 3) ("peerSelect: %s\n", storeUrl(entry));
140 else
141 debug(44, 3) ("peerSelect: %s\n", RequestMethodStr[request->method]);
142 cbdataAdd(psstate, memFree, MEM_PS_STATE);
143 psstate->request = requestLink(request);
144 psstate->entry = entry;
145 psstate->callback = callback;
146 psstate->callback_data = callback_data;
147 psstate->direct = DIRECT_UNKNOWN;
148 #if USE_CACHE_DIGESTS
149 request->hier.peer_select_start = current_time;
150 #endif
151 if (psstate->entry)
152 storeLockObject(psstate->entry);
153 cbdataLock(callback_data);
154 peerSelectFoo(psstate);
155 }
156
157 static void
158 peerCheckNeverDirectDone(int answer, void *data)
159 {
160 ps_state *psstate = data;
161 psstate->acl_checklist = NULL;
162 debug(44, 3) ("peerCheckNeverDirectDone: %d\n", answer);
163 psstate->never_direct = answer ? 1 : -1;
164 peerSelectFoo(psstate);
165 }
166
167 static void
168 peerCheckAlwaysDirectDone(int answer, void *data)
169 {
170 ps_state *psstate = data;
171 psstate->acl_checklist = NULL;
172 debug(44, 3) ("peerCheckAlwaysDirectDone: %d\n", answer);
173 psstate->always_direct = answer ? 1 : -1;
174 peerSelectFoo(psstate);
175 }
176
177 static void
178 peerSelectCallback(ps_state * psstate)
179 {
180 StoreEntry *entry = psstate->entry;
181 FwdServer *fs = psstate->servers;
182 void *data = psstate->callback_data;
183 if (entry) {
184 debug(44, 3) ("peerSelectCallback: %s\n", storeUrl(entry));
185 if (entry->ping_status == PING_WAITING)
186 eventDelete(peerPingTimeout, psstate);
187 entry->ping_status = PING_DONE;
188 }
189 if (fs == NULL) {
190 debug(44, 1) ("Failed to select source for '%s'\n", storeUrl(entry));
191 debug(44, 1) (" always_direct = %d\n", psstate->always_direct);
192 debug(44, 1) (" never_direct = %d\n", psstate->never_direct);
193 debug(44, 1) (" timedout = %d\n", psstate->ping.timedout);
194 }
195 psstate->ping.stop = current_time;
196 psstate->request->hier.ping = psstate->ping;
197 if (cbdataValid(data)) {
198 psstate->servers = NULL;
199 psstate->callback(fs, data);
200 }
201 cbdataUnlock(data);
202 peerSelectStateFree(psstate);
203 }
204
205 static int
206 peerCheckNetdbDirect(ps_state * psstate)
207 {
208 peer *p = whichPeer(&psstate->closest_parent_miss);
209 int myrtt;
210 int myhops;
211 if (p == NULL)
212 return 0;
213 if (psstate->direct == DIRECT_NO)
214 return 0;
215 myrtt = netdbHostRtt(psstate->request->host);
216 debug(44, 3) ("peerCheckNetdbDirect: MY RTT = %d msec\n", myrtt);
217 debug(44, 3) ("peerCheckNetdbDirect: closest_parent_miss RTT = %d msec\n",
218 psstate->ping.p_rtt);
219 if (myrtt && myrtt < psstate->ping.p_rtt)
220 return 1;
221 myhops = netdbHostHops(psstate->request->host);
222 debug(44, 3) ("peerCheckNetdbDirect: MY hops = %d\n", myhops);
223 debug(44, 3) ("peerCheckNetdbDirect: minimum_direct_hops = %d\n",
224 Config.minDirectHops);
225 if (myhops && myhops <= Config.minDirectHops)
226 return 1;
227 return 0;
228 }
229
230 static void
231 peerSelectFoo(ps_state * ps)
232 {
233 StoreEntry *entry = ps->entry;
234 request_t *request = ps->request;
235 debug(44, 3) ("peerSelectFoo: '%s %s'\n",
236 RequestMethodStr[request->method],
237 request->host);
238 if (ps->direct == DIRECT_UNKNOWN) {
239 if (ps->always_direct == 0 && Config.accessList.AlwaysDirect) {
240 ps->acl_checklist = aclChecklistCreate(
241 Config.accessList.AlwaysDirect,
242 request,
243 NULL); /* ident */
244 aclNBCheck(ps->acl_checklist,
245 peerCheckAlwaysDirectDone,
246 ps);
247 return;
248 } else if (ps->always_direct > 0) {
249 ps->direct = DIRECT_YES;
250 } else if (ps->never_direct == 0 && Config.accessList.NeverDirect) {
251 ps->acl_checklist = aclChecklistCreate(
252 Config.accessList.NeverDirect,
253 request,
254 NULL); /* ident */
255 aclNBCheck(ps->acl_checklist,
256 peerCheckNeverDirectDone,
257 ps);
258 return;
259 } else if (ps->never_direct > 0) {
260 ps->direct = DIRECT_NO;
261 } else if (request->flags.loopdetect) {
262 ps->direct = DIRECT_YES;
263 } else {
264 ps->direct = DIRECT_MAYBE;
265 }
266 debug(44, 3) ("peerSelectFoo: direct = %s\n",
267 DirectStr[ps->direct]);
268 }
269 if (entry == NULL) {
270 (void) 0;
271 } else if (entry->ping_status == PING_NONE) {
272 peerGetSomeNeighbor(ps);
273 if (entry->ping_status == PING_WAITING)
274 return;
275 } else if (entry->ping_status == PING_WAITING) {
276 peerGetSomeNeighborReplies(ps);
277 entry->ping_status = PING_DONE;
278 }
279 switch (ps->direct) {
280 case DIRECT_YES:
281 peerGetSomeDirect(ps);
282 break;
283 case DIRECT_NO:
284 peerGetSomeParent(ps);
285 peerGetAllParents(ps);
286 break;
287 default:
288 if (Config.onoff.prefer_direct)
289 peerGetSomeDirect(ps);
290 if (request->flags.hierarchical || !Config.onoff.nonhierarchical_direct)
291 peerGetSomeParent(ps);
292 if (!Config.onoff.prefer_direct)
293 peerGetSomeDirect(ps);
294 break;
295 }
296 peerSelectCallback(ps);
297 }
298
299 /*
300 * peerGetSomeNeighbor
301 *
302 * Selects a neighbor (parent or sibling) based on one of the
303 * following methods:
304 * Cache Digests
305 * CARP
306 * Netdb RTT estimates
307 * ICP/HTCP queries
308 */
309 static void
310 peerGetSomeNeighbor(ps_state * ps)
311 {
312 StoreEntry *entry = ps->entry;
313 request_t *request = ps->request;
314 peer *p;
315 hier_code code = HIER_NONE;
316 assert(entry->ping_status == PING_NONE);
317 if (ps->direct == DIRECT_YES) {
318 entry->ping_status = PING_DONE;
319 return;
320 }
321 #if USE_CACHE_DIGESTS
322 if ((p = neighborsDigestSelect(request, entry))) {
323 if (neighborType(p, request) == PEER_PARENT)
324 code = CD_PARENT_HIT;
325 else
326 code = CD_SIBLING_HIT;
327 } else
328 #endif
329 #if USE_CARP
330 if ((p = carpSelectParent(request))) {
331 code = CARP;
332 } else
333 #endif
334 if ((p = netdbClosestParent(request))) {
335 code = CLOSEST_PARENT;
336 } else if (peerSelectIcpPing(request, ps->direct, entry)) {
337 debug(44, 3) ("peerSelect: Doing ICP pings\n");
338 ps->ping.start = current_time;
339 ps->ping.n_sent = neighborsUdpPing(request,
340 entry,
341 peerHandlePingReply,
342 ps,
343 &ps->ping.n_replies_expected,
344 &ps->ping.timeout);
345 if (ps->ping.n_sent == 0)
346 debug(44, 0) ("WARNING: neighborsUdpPing returned 0\n");
347 debug(44, 3) ("peerSelect: %d ICP replies expected, RTT %d msec\n",
348 ps->ping.n_replies_expected, ps->ping.timeout);
349 if (ps->ping.n_replies_expected > 0) {
350 entry->ping_status = PING_WAITING;
351 eventAdd("peerPingTimeout",
352 peerPingTimeout,
353 ps,
354 0.001 * ps->ping.timeout,
355 0);
356 return;
357 }
358 }
359 if (code != HIER_NONE) {
360 assert(p);
361 debug(44, 3) ("peerSelect: %s/%s\n", hier_strings[code], p->host);
362 peerAddFwdServer(&ps->servers, p, code);
363 }
364 entry->ping_status = PING_DONE;
365 }
366
367 /*
368 * peerGetSomeNeighborReplies
369 *
370 * Selects a neighbor (parent or sibling) based on ICP/HTCP replies.
371 */
372 static void
373 peerGetSomeNeighborReplies(ps_state * ps)
374 {
375 request_t *request = ps->request;
376 peer *p = NULL;
377 hier_code code = HIER_NONE;
378 assert(ps->entry->ping_status == PING_WAITING);
379 assert(ps->direct != DIRECT_YES);
380 if (peerCheckNetdbDirect(ps)) {
381 code = CLOSEST_DIRECT;
382 debug(44, 3) ("peerSelect: %s/%s\n", hier_strings[code], request->host);
383 peerAddFwdServer(&ps->servers, NULL, code);
384 return;
385 }
386 if ((p = ps->hit)) {
387 code = ps->hit_type == PEER_PARENT ? PARENT_HIT : SIBLING_HIT;
388 } else
389 #if ALLOW_SOURCE_PING
390 if ((p = ps->secho)) {
391 code = SOURCE_FASTEST;
392 } else
393 #endif
394 if (ps->closest_parent_miss.sin_addr.s_addr != any_addr.s_addr) {
395 p = whichPeer(&ps->closest_parent_miss);
396 code = CLOSEST_PARENT_MISS;
397 } else if (ps->first_parent_miss.sin_addr.s_addr != any_addr.s_addr) {
398 p = whichPeer(&ps->first_parent_miss);
399 code = FIRST_PARENT_MISS;
400 }
401 if (p && code != HIER_NONE) {
402 debug(44, 3) ("peerSelect: %s/%s\n", hier_strings[code], p->host);
403 peerAddFwdServer(&ps->servers, p, code);
404 }
405 }
406
407
408 /*
409 * peerGetSomeDirect
410 *
411 * Simply adds a 'direct' entry to the FwdServers list if this
412 * request can be forwarded directly to the origin server
413 */
414 static void
415 peerGetSomeDirect(ps_state * ps)
416 {
417 if (ps->direct == DIRECT_NO)
418 return;
419 if (ps->request->protocol == PROTO_WAIS)
420 /* Its not really DIRECT, now is it? */
421 peerAddFwdServer(&ps->servers, Config.Wais.peer, DIRECT);
422 else
423 peerAddFwdServer(&ps->servers, NULL, DIRECT);
424 }
425
426 static void
427 peerGetSomeParent(ps_state * ps)
428 {
429 peer *p;
430 request_t *request = ps->request;
431 hier_code code = HIER_NONE;
432 debug(44, 3) ("peerGetSomeParent: %s %s\n",
433 RequestMethodStr[request->method],
434 request->host);
435 if (ps->direct == DIRECT_YES)
436 return;
437 if ((p = getDefaultParent(request))) {
438 code = DEFAULT_PARENT;
439 } else if ((p = getRoundRobinParent(request))) {
440 code = ROUNDROBIN_PARENT;
441 } else if ((p = getFirstUpParent(request))) {
442 code = FIRSTUP_PARENT;
443 } else if ((p = getAnyParent(request))) {
444 code = ANY_OLD_PARENT;
445 }
446 if (code != HIER_NONE) {
447 debug(44, 3) ("peerSelect: %s/%s\n", hier_strings[code], p->host);
448 peerAddFwdServer(&ps->servers, p, code);
449 }
450 }
451
452 /* Adds alive parents. Used as a last resort for never_direct.
453 */
454 static void
455 peerGetAllParents(ps_state * ps)
456 {
457 peer *p;
458 request_t *request = ps->request;
459 /* Add all alive parents */
460 for (p = Config.peers; p; p = p->next) {
461 /* XXX: neighbors.c lacks a public interface for enumerating
462 * parents to a request so we have to dig some here..
463 */
464 if (neighborType(p, request) != PEER_PARENT)
465 continue;
466 if (!peerHTTPOkay(p, request))
467 continue;
468 debug(15, 3) ("peerGetAllParents: adding alive parent %s\n", p->host);
469 peerAddFwdServer(&ps->servers, p, ANY_OLD_PARENT);
470 }
471 /* XXX: should add dead parents here, but it is currently
472 * not possible to find out which parents are dead or which
473 * simply are not configured to handle the request.
474 */
475 /* Add default parent as a last resort */
476 if ((p = getDefaultParent(request))) {
477 peerAddFwdServer(&ps->servers, p, DEFAULT_PARENT);
478 }
479 }
480
481 static void
482 peerPingTimeout(void *data)
483 {
484 ps_state *psstate = data;
485 StoreEntry *entry = psstate->entry;
486 if (entry)
487 debug(44, 3) ("peerPingTimeout: '%s'\n", storeUrl(entry));
488 if (!cbdataValid(psstate->callback_data)) {
489 /* request aborted */
490 entry->ping_status = PING_DONE;
491 cbdataUnlock(psstate->callback_data);
492 peerSelectStateFree(psstate);
493 return;
494 }
495 PeerStats.timeouts++;
496 psstate->ping.timedout = 1;
497 peerSelectFoo(psstate);
498 }
499
500 void
501 peerSelectInit(void)
502 {
503 memset(&PeerStats, '\0', sizeof(PeerStats));
504 assert(sizeof(hier_strings) == (HIER_MAX + 1) * sizeof(char *));
505 }
506
507 static void
508 peerIcpParentMiss(peer * p, icp_common_t * header, ps_state * ps)
509 {
510 int rtt;
511 int hops;
512 if (Config.onoff.query_icmp) {
513 if (header->flags & ICP_FLAG_SRC_RTT) {
514 rtt = header->pad & 0xFFFF;
515 hops = (header->pad >> 16) & 0xFFFF;
516 if (rtt > 0 && rtt < 0xFFFF)
517 netdbUpdatePeer(ps->request, p, rtt, hops);
518 if (rtt && (ps->ping.p_rtt == 0 || rtt < ps->ping.p_rtt)) {
519 ps->closest_parent_miss = p->in_addr;
520 ps->ping.p_rtt = rtt;
521 }
522 }
523 }
524 /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
525 if (p->options.closest_only)
526 return;
527 /* set FIRST_MISS if there is no CLOSEST parent */
528 if (ps->closest_parent_miss.sin_addr.s_addr != any_addr.s_addr)
529 return;
530 rtt = tvSubMsec(ps->ping.start, current_time) / p->weight;
531 if (ps->ping.w_rtt == 0 || rtt < ps->ping.w_rtt) {
532 ps->first_parent_miss = p->in_addr;
533 ps->ping.w_rtt = rtt;
534 }
535 }
536
537 static void
538 peerHandleIcpReply(peer * p, peer_t type, icp_common_t * header, void *data)
539 {
540 ps_state *psstate = data;
541 icp_opcode op = header->opcode;
542 debug(44, 3) ("peerHandleIcpReply: %s %s\n",
543 icp_opcode_str[op],
544 storeUrl(psstate->entry));
545 #if USE_CACHE_DIGESTS && 0
546 /* do cd lookup to count false misses */
547 if (p && request)
548 peerNoteDigestLookup(request, p,
549 peerDigestLookup(p, request, psstate->entry));
550 #endif
551 psstate->ping.n_recv++;
552 if (op == ICP_MISS || op == ICP_DECHO) {
553 if (type == PEER_PARENT)
554 peerIcpParentMiss(p, header, psstate);
555 } else if (op == ICP_HIT) {
556 psstate->hit = p;
557 psstate->hit_type = type;
558 peerSelectFoo(psstate);
559 return;
560 }
561 #if ALLOW_SOURCE_PING
562 else if (op == ICP_SECHO) {
563 psstate->secho = p;
564 peerSelectFoo(psstate);
565 return;
566 }
567 #endif
568 if (psstate->ping.n_recv < psstate->ping.n_replies_expected)
569 return;
570 peerSelectFoo(psstate);
571 }
572
573 #if USE_HTCP
574 static void
575 peerHandleHtcpReply(peer * p, peer_t type, htcpReplyData * htcp, void *data)
576 {
577 ps_state *psstate = data;
578 debug(44, 3) ("peerHandleIcpReply: %s %s\n",
579 htcp->hit ? "HIT" : "MISS",
580 storeUrl(psstate->entry));
581 psstate->ping.n_recv++;
582 if (htcp->hit) {
583 psstate->hit = p;
584 psstate->hit_type = type;
585 peerSelectFoo(psstate);
586 return;
587 }
588 if (type == PEER_PARENT)
589 peerHtcpParentMiss(p, htcp, psstate);
590 if (psstate->ping.n_recv < psstate->ping.n_replies_expected)
591 return;
592 peerSelectFoo(psstate);
593 }
594
595 static void
596 peerHtcpParentMiss(peer * p, htcpReplyData * htcp, ps_state * ps)
597 {
598 int rtt;
599 int hops;
600 if (Config.onoff.query_icmp) {
601 if (htcp->cto.rtt > 0) {
602 rtt = (int) htcp->cto.rtt * 1000;
603 hops = (int) htcp->cto.hops * 1000;
604 netdbUpdatePeer(ps->request, p, rtt, hops);
605 if (rtt && (ps->ping.p_rtt == 0 || rtt < ps->ping.p_rtt)) {
606 ps->closest_parent_miss = p->in_addr;
607 ps->ping.p_rtt = rtt;
608 }
609 }
610 }
611 /* if closest-only is set, then don't allow FIRST_PARENT_MISS */
612 if (p->options.closest_only)
613 return;
614 /* set FIRST_MISS if there is no CLOSEST parent */
615 if (ps->closest_parent_miss.sin_addr.s_addr != any_addr.s_addr)
616 return;
617 rtt = tvSubMsec(ps->ping.start, current_time) / p->weight;
618 if (ps->ping.w_rtt == 0 || rtt < ps->ping.w_rtt) {
619 ps->first_parent_miss = p->in_addr;
620 ps->ping.w_rtt = rtt;
621 }
622 }
623 #endif
624
625 static void
626 peerHandlePingReply(peer * p, peer_t type, protocol_t proto, void *pingdata, void *data)
627 {
628 if (proto == PROTO_ICP)
629 peerHandleIcpReply(p, type, pingdata, data);
630 #if USE_HTCP
631 else if (proto == PROTO_HTCP)
632 peerHandleHtcpReply(p, type, pingdata, data);
633 #endif
634 else
635 debug(44, 1) ("peerHandlePingReply: unknown protocol_t %d\n", (int) proto);
636 }
637
638 static void
639 peerAddFwdServer(FwdServer ** FS, peer * p, hier_code code)
640 {
641 FwdServer *fs = memAllocate(MEM_FWD_SERVER);
642 debug(44, 5) ("peerAddFwdServer: adding %s %s\n",
643 p ? p->host : "DIRECT",
644 hier_strings[code]);
645 fs->peer = p;
646 fs->code = code;
647 cbdataLock(fs->peer);
648 while (*FS)
649 FS = &(*FS)->next;
650 *FS = fs;
651 }