]> git.ipfire.org Git - thirdparty/squid.git/blob - src/peer_digest.cc
Fix authentication headers sent on peer digest requests
[thirdparty/squid.git] / src / peer_digest.cc
1
2 /*
3 * DEBUG: section 72 Peer Digest Routines
4 * AUTHOR: Alex Rousskov
5 *
6 * SQUID Web Proxy Cache http://www.squid-cache.org/
7 * ----------------------------------------------------------
8 *
9 * Squid is the result of efforts by numerous individuals from
10 * the Internet community; see the CONTRIBUTORS file for full
11 * details. Many organizations have provided support for Squid's
12 * development; see the SPONSORS file for full details. Squid is
13 * Copyrighted (C) 2001 by the Regents of the University of
14 * California; see the COPYRIGHT file for full details. Squid
15 * incorporates software developed and/or copyrighted by other
16 * sources; see the CREDITS file for full details.
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
22 *
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
31 *
32 */
33
34 #include "squid.h"
35 #if USE_CACHE_DIGESTS
36 #include "CacheDigest.h"
37 #include "CachePeer.h"
38 #include "event.h"
39 #include "forward.h"
40 #include "globals.h"
41 #include "HttpReply.h"
42 #include "HttpRequest.h"
43 #include "internal.h"
44 #include "MemObject.h"
45 #include "neighbors.h"
46 #include "mime_header.h"
47 #include "PeerDigest.h"
48 #include "SquidTime.h"
49 #include "Store.h"
50 #include "store_key_md5.h"
51 #include "StoreClient.h"
52 #include "tools.h"
53
54 /* local types */
55
56 /* local prototypes */
57 static time_t peerDigestIncDelay(const PeerDigest * pd);
58 static time_t peerDigestNewDelay(const StoreEntry * e);
59 static void peerDigestSetCheck(PeerDigest * pd, time_t delay);
60 static void peerDigestClean(PeerDigest *);
61 static EVH peerDigestCheck;
62 static void peerDigestRequest(PeerDigest * pd);
63 static STCB peerDigestHandleReply;
64 static int peerDigestFetchReply(void *, char *, ssize_t);
65 int peerDigestSwapInHeaders(void *, char *, ssize_t);
66 int peerDigestSwapInCBlock(void *, char *, ssize_t);
67 int peerDigestSwapInMask(void *, char *, ssize_t);
68 static int peerDigestFetchedEnough(DigestFetchState * fetch, char *buf, ssize_t size, const char *step_name);
69 static void peerDigestFetchStop(DigestFetchState * fetch, char *buf, const char *reason);
70 static void peerDigestFetchAbort(DigestFetchState * fetch, char *buf, const char *reason);
71 static void peerDigestReqFinish(DigestFetchState * fetch, char *buf, int, int, int, const char *reason, int err);
72 static void peerDigestPDFinish(DigestFetchState * fetch, int pcb_valid, int err);
73 static void peerDigestFetchFinish(DigestFetchState * fetch, int err);
74 static void peerDigestFetchSetStats(DigestFetchState * fetch);
75 static int peerDigestSetCBlock(PeerDigest * pd, const char *buf);
76 static int peerDigestUseful(const PeerDigest * pd);
77
78 /* local constants */
79 Version const CacheDigestVer = { 5, 3 };
80
81 #define StoreDigestCBlockSize sizeof(StoreDigestCBlock)
82
83 /* min interval for requesting digests from a given peer */
84 static const time_t PeerDigestReqMinGap = 5 * 60; /* seconds */
85 /* min interval for requesting digests (cumulative request stream) */
86 static const time_t GlobDigestReqMinGap = 1 * 60; /* seconds */
87
88 /* local vars */
89
90 static time_t pd_last_req_time = 0; /* last call to Check */
91
92 /* initialize peer digest */
93 static void
94 peerDigestInit(PeerDigest * pd, CachePeer * p)
95 {
96 assert(pd && p);
97
98 memset(pd, 0, sizeof(*pd));
99 /*
100 * DPW 2007-04-12
101 * Lock on to the peer here. The corresponding cbdataReferenceDone()
102 * is in peerDigestDestroy().
103 */
104 pd->peer = cbdataReference(p);
105 /* if peer disappears, we will know it's name */
106 pd->host = p->host;
107
108 pd->times.initialized = squid_curtime;
109 }
110
111 static void
112 peerDigestClean(PeerDigest * pd)
113 {
114 assert(pd);
115
116 if (pd->cd)
117 cacheDigestDestroy(pd->cd);
118
119 pd->host.clean();
120 }
121
122 CBDATA_CLASS_INIT(PeerDigest);
123
124 void *
125 PeerDigest::operator new (size_t)
126 {
127 CBDATA_INIT_TYPE(PeerDigest);
128 PeerDigest *result = cbdataAlloc(PeerDigest);
129 return result;
130 }
131
132 void
133 PeerDigest::operator delete (void *address)
134 {
135 PeerDigest *t = static_cast<PeerDigest *>(address);
136 cbdataFree(t);
137 }
138
139 /* allocate new peer digest, call Init, and lock everything */
140 PeerDigest *
141 peerDigestCreate(CachePeer * p)
142 {
143 PeerDigest *pd;
144 assert(p);
145
146 pd = new PeerDigest;
147 peerDigestInit(pd, p);
148
149 /* XXX This does not look right, and the same thing again in the caller */
150 return cbdataReference(pd);
151 }
152
153 /* call Clean and free/unlock everything */
154 static void
155 peerDigestDestroy(PeerDigest * pd)
156 {
157 void *p;
158 assert(pd);
159 void * peerTmp = pd->peer;
160
161 /*
162 * DPW 2007-04-12
163 * We locked the peer in peerDigestInit(), this is
164 * where we unlock it. If the peer is still valid,
165 * tell it that the digest is gone.
166 */
167 if (cbdataReferenceValidDone(peerTmp, &p))
168 peerNoteDigestGone((CachePeer *)p);
169
170 peerDigestClean(pd);
171
172 delete pd;
173 }
174
175 /* called by peer to indicate that somebody actually needs this digest */
176 void
177 peerDigestNeeded(PeerDigest * pd)
178 {
179 assert(pd);
180 assert(!pd->flags.needed);
181 assert(!pd->cd);
182
183 pd->flags.needed = true;
184 pd->times.needed = squid_curtime;
185 peerDigestSetCheck(pd, 0); /* check asap */
186 }
187
188 /* currently we do not have a reason to disable without destroying */
189 #if FUTURE_CODE
190 /* disables peer for good */
191 static void
192 peerDigestDisable(PeerDigest * pd)
193 {
194 debugs(72, 2, "peerDigestDisable: peer " << pd->host.buf() << " disabled for good");
195 pd->times.disabled = squid_curtime;
196 pd->times.next_check = -1; /* never */
197 pd->flags.usable = 0;
198
199 if (pd->cd) {
200 cacheDigestDestroy(pd->cd);
201 pd->cd = NULL;
202 }
203
204 /* we do not destroy the pd itself to preserve its "history" and stats */
205 }
206
207 #endif
208
209 /* increment retry delay [after an unsuccessful attempt] */
210 static time_t
211 peerDigestIncDelay(const PeerDigest * pd)
212 {
213 assert(pd);
214 return pd->times.retry_delay > 0 ?
215 2 * pd->times.retry_delay : /* exponential backoff */
216 PeerDigestReqMinGap; /* minimal delay */
217 }
218
219 /* artificially increases Expires: setting to avoid race conditions
220 * returns the delay till that [increased] expiration time */
221 static time_t
222 peerDigestNewDelay(const StoreEntry * e)
223 {
224 assert(e);
225
226 if (e->expires > 0)
227 return e->expires + PeerDigestReqMinGap - squid_curtime;
228
229 return PeerDigestReqMinGap;
230 }
231
232 /* registers next digest verification */
233 static void
234 peerDigestSetCheck(PeerDigest * pd, time_t delay)
235 {
236 eventAdd("peerDigestCheck", peerDigestCheck, pd, (double) delay, 1);
237 pd->times.next_check = squid_curtime + delay;
238 debugs(72, 3, "peerDigestSetCheck: will check peer " << pd->host << " in " << delay << " secs");
239 }
240
241 /*
242 * called when peer is about to disappear or have already disappeared
243 */
244 void
245 peerDigestNotePeerGone(PeerDigest * pd)
246 {
247 if (pd->flags.requested) {
248 debugs(72, 2, "peerDigest: peer " << pd->host << " gone, will destroy after fetch.");
249 /* do nothing now, the fetching chain will notice and take action */
250 } else {
251 debugs(72, 2, "peerDigest: peer " << pd->host << " is gone, destroying now.");
252 peerDigestDestroy(pd);
253 }
254 }
255
256 /* callback for eventAdd() (with peer digest locked)
257 * request new digest if our copy is too old or if we lack one;
258 * schedule next check otherwise */
259 static void
260 peerDigestCheck(void *data)
261 {
262 PeerDigest *pd = (PeerDigest *)data;
263 time_t req_time;
264
265 assert(!pd->flags.requested);
266
267 pd->times.next_check = 0; /* unknown */
268
269 if (!cbdataReferenceValid(pd->peer)) {
270 peerDigestNotePeerGone(pd);
271 return;
272 }
273
274 debugs(72, 3, "peerDigestCheck: peer " << pd->peer->host << ":" << pd->peer->http_port);
275 debugs(72, 3, "peerDigestCheck: time: " << squid_curtime <<
276 ", last received: " << (long int) pd->times.received << " (" <<
277 std::showpos << (int) (squid_curtime - pd->times.received) << ")");
278
279 /* decide when we should send the request:
280 * request now unless too close to other requests */
281 req_time = squid_curtime;
282
283 /* per-peer limit */
284
285 if (req_time - pd->times.received < PeerDigestReqMinGap) {
286 debugs(72, 2, "peerDigestCheck: " << pd->host <<
287 ", avoiding close peer requests (" <<
288 (int) (req_time - pd->times.received) << " < " <<
289 (int) PeerDigestReqMinGap << " secs).");
290
291 req_time = pd->times.received + PeerDigestReqMinGap;
292 }
293
294 /* global limit */
295 if (req_time - pd_last_req_time < GlobDigestReqMinGap) {
296 debugs(72, 2, "peerDigestCheck: " << pd->host <<
297 ", avoiding close requests (" <<
298 (int) (req_time - pd_last_req_time) << " < " <<
299 (int) GlobDigestReqMinGap << " secs).");
300
301 req_time = pd_last_req_time + GlobDigestReqMinGap;
302 }
303
304 if (req_time <= squid_curtime)
305 peerDigestRequest(pd); /* will set pd->flags.requested */
306 else
307 peerDigestSetCheck(pd, req_time - squid_curtime);
308 }
309
310 CBDATA_TYPE(DigestFetchState);
311
312 /* ask store for a digest */
313 static void
314 peerDigestRequest(PeerDigest * pd)
315 {
316 CachePeer *p = pd->peer;
317 StoreEntry *e, *old_e;
318 char *url;
319 const cache_key *key;
320 HttpRequest *req;
321 DigestFetchState *fetch = NULL;
322 StoreIOBuffer tempBuffer;
323
324 pd->req_result = NULL;
325 pd->flags.requested = true;
326
327 /* compute future request components */
328
329 if (p->digest_url)
330 url = xstrdup(p->digest_url);
331 else
332 url = internalRemoteUri(p->host, p->http_port,
333 "/squid-internal-periodic/", StoreDigestFileName);
334
335 req = HttpRequest::CreateFromUrl(url);
336
337 assert(req);
338
339 key = storeKeyPublicByRequest(req);
340
341 debugs(72, 2, "peerDigestRequest: " << url << " key: " << storeKeyText(key));
342
343 /* add custom headers */
344 assert(!req->header.len);
345
346 req->header.putStr(HDR_ACCEPT, StoreDigestMimeStr);
347
348 req->header.putStr(HDR_ACCEPT, "text/html");
349
350 if (p->login &&
351 p->login[0] != '*' &&
352 strcmp(p->login, "PASS") != 0 &&
353 strcmp(p->login, "PASSTHRU") != 0 &&
354 strcmp(p->login, "NEGOTIATE") != 0 &&
355 strcmp(p->login, "PROXYPASS") != 0) {
356 xstrncpy(req->login, p->login, MAX_LOGIN_SZ);
357 }
358 /* create fetch state structure */
359 CBDATA_INIT_TYPE(DigestFetchState);
360
361 fetch = cbdataAlloc(DigestFetchState);
362
363 fetch->request = req;
364 HTTPMSGLOCK(fetch->request);
365
366 fetch->pd = cbdataReference(pd);
367
368 fetch->offset = 0;
369
370 fetch->state = DIGEST_READ_REPLY;
371
372 /* update timestamps */
373 fetch->start_time = squid_curtime;
374
375 pd->times.requested = squid_curtime;
376
377 pd_last_req_time = squid_curtime;
378
379 req->flags.cachable = true;
380
381 /* the rest is based on clientProcessExpired() */
382 req->flags.refresh = true;
383
384 old_e = fetch->old_entry = Store::Root().get(key);
385
386 if (old_e) {
387 debugs(72, 5, "peerDigestRequest: found old entry");
388
389 old_e->lock();
390 old_e->createMemObject(url, url);
391
392 fetch->old_sc = storeClientListAdd(old_e, fetch);
393 }
394
395 e = fetch->entry = storeCreateEntry(url, url, req->flags, req->method);
396 assert(EBIT_TEST(e->flags, KEY_PRIVATE));
397 fetch->sc = storeClientListAdd(e, fetch);
398 /* set lastmod to trigger IMS request if possible */
399
400 if (old_e)
401 e->lastmod = old_e->lastmod;
402
403 /* push towards peer cache */
404 debugs(72, 3, "peerDigestRequest: forwarding to fwdStart...");
405
406 FwdState::fwdStart(Comm::ConnectionPointer(), e, req);
407
408 tempBuffer.offset = 0;
409
410 tempBuffer.length = SM_PAGE_SIZE;
411
412 tempBuffer.data = fetch->buf;
413
414 storeClientCopy(fetch->sc, e, tempBuffer,
415 peerDigestHandleReply, fetch);
416 }
417
418 /* Handle the data copying .. */
419
420 /*
421 * This routine handles the copy data and then redirects the
422 * copy to a bunch of subfunctions depending upon the copy state.
423 * It also tracks the buffer offset and "seen", since I'm actually
424 * not interested in rewriting everything to suit my little idea.
425 */
426 static void
427 peerDigestHandleReply(void *data, StoreIOBuffer receivedData)
428 {
429 DigestFetchState *fetch = (DigestFetchState *)data;
430 int retsize = -1;
431 digest_read_state_t prevstate;
432 int newsize;
433
434 assert(fetch->pd && receivedData.data);
435 /* The existing code assumes that the received pointer is
436 * where we asked the data to be put
437 */
438 assert(fetch->buf + fetch->bufofs == receivedData.data);
439
440 /* Update the buffer size */
441 fetch->bufofs += receivedData.length;
442
443 assert(fetch->bufofs <= SM_PAGE_SIZE);
444
445 /* If we've fetched enough, return */
446
447 if (peerDigestFetchedEnough(fetch, fetch->buf, fetch->bufofs, "peerDigestHandleReply"))
448 return;
449
450 /* Call the right function based on the state */
451 /* (Those functions will update the state if needed) */
452
453 /* Give us a temporary reference. Some of the calls we make may
454 * try to destroy the fetch structure, and we like to know if they
455 * do
456 */
457 fetch = cbdataReference(fetch);
458
459 /* Repeat this loop until we're out of data OR the state changes */
460 /* (So keep going if the state has changed and we still have data */
461 do {
462 prevstate = fetch->state;
463
464 switch (fetch->state) {
465
466 case DIGEST_READ_REPLY:
467 retsize = peerDigestFetchReply(fetch, fetch->buf, fetch->bufofs);
468 break;
469
470 case DIGEST_READ_HEADERS:
471 retsize = peerDigestSwapInHeaders(fetch, fetch->buf, fetch->bufofs);
472 break;
473
474 case DIGEST_READ_CBLOCK:
475 retsize = peerDigestSwapInCBlock(fetch, fetch->buf, fetch->bufofs);
476 break;
477
478 case DIGEST_READ_MASK:
479 retsize = peerDigestSwapInMask(fetch, fetch->buf, fetch->bufofs);
480 break;
481
482 case DIGEST_READ_NONE:
483 break;
484
485 case DIGEST_READ_DONE:
486 goto finish;
487 break;
488
489 default:
490 fatal("Bad digest transfer mode!\n");
491 }
492
493 if (retsize < 0)
494 goto finish;
495
496 /*
497 * The returned size indicates how much of the buffer was read -
498 * so move the remainder of the buffer to the beginning
499 * and update the bufofs / bufsize
500 */
501 newsize = fetch->bufofs - retsize;
502
503 memmove(fetch->buf, fetch->buf + retsize, fetch->bufofs - newsize);
504
505 fetch->bufofs = newsize;
506
507 } while (cbdataReferenceValid(fetch) && prevstate != fetch->state && fetch->bufofs > 0);
508
509 /* Update the copy offset */
510 fetch->offset += receivedData.length;
511
512 /* Schedule another copy */
513 if (cbdataReferenceValid(fetch)) {
514 StoreIOBuffer tempBuffer;
515 tempBuffer.offset = fetch->offset;
516 tempBuffer.length = SM_PAGE_SIZE - fetch->bufofs;
517 tempBuffer.data = fetch->buf + fetch->bufofs;
518 storeClientCopy(fetch->sc, fetch->entry, tempBuffer,
519 peerDigestHandleReply, fetch);
520 }
521
522 finish:
523 /* Get rid of our reference, we've finished with it for now */
524 cbdataReferenceDone(fetch);
525 }
526
527 /* wait for full http headers to be received then parse them */
528 /*
529 * This routine handles parsing the reply line.
530 * If the reply line indicates an OK, the same data is thrown
531 * to SwapInHeaders(). If the reply line is a NOT_MODIFIED,
532 * we simply stop parsing.
533 */
534 static int
535 peerDigestFetchReply(void *data, char *buf, ssize_t size)
536 {
537 DigestFetchState *fetch = (DigestFetchState *)data;
538 PeerDigest *pd = fetch->pd;
539 size_t hdr_size;
540 assert(pd && buf);
541 assert(!fetch->offset);
542
543 assert(fetch->state == DIGEST_READ_REPLY);
544
545 if (peerDigestFetchedEnough(fetch, buf, size, "peerDigestFetchReply"))
546 return -1;
547
548 if ((hdr_size = headersEnd(buf, size))) {
549 http_status status;
550 HttpReply const *reply = fetch->entry->getReply();
551 assert(reply);
552 assert (reply->sline.status != 0);
553 status = reply->sline.status;
554 debugs(72, 3, "peerDigestFetchReply: " << pd->host << " status: " << status <<
555 ", expires: " << (long int) reply->expires << " (" << std::showpos <<
556 (int) (reply->expires - squid_curtime) << ")");
557
558 /* this "if" is based on clientHandleIMSReply() */
559
560 if (status == HTTP_NOT_MODIFIED) {
561 /* our old entry is fine */
562 assert(fetch->old_entry);
563
564 if (!fetch->old_entry->mem_obj->request) {
565 fetch->old_entry->mem_obj->request = fetch->entry->mem_obj->request;
566 HTTPMSGLOCK(fetch->old_entry->mem_obj->request);
567 }
568
569 assert(fetch->old_entry->mem_obj->request);
570
571 HttpReply *old_rep = (HttpReply *) fetch->old_entry->getReply();
572
573 old_rep->updateOnNotModified(reply);
574
575 fetch->old_entry->timestampsSet();
576
577 /* get rid of 304 reply */
578 storeUnregister(fetch->sc, fetch->entry, fetch);
579
580 fetch->entry->unlock();
581
582 fetch->entry = fetch->old_entry;
583
584 fetch->old_entry = NULL;
585
586 /* preserve request -- we need its size to update counters */
587 /* requestUnlink(r); */
588 /* fetch->entry->mem_obj->request = NULL; */
589 } else if (status == HTTP_OK) {
590 /* get rid of old entry if any */
591
592 if (fetch->old_entry) {
593 debugs(72, 3, "peerDigestFetchReply: got new digest, releasing old one");
594 storeUnregister(fetch->old_sc, fetch->old_entry, fetch);
595 fetch->old_entry->releaseRequest();
596 fetch->old_entry->unlock();
597 fetch->old_entry = NULL;
598 }
599 } else {
600 /* some kind of a bug */
601 peerDigestFetchAbort(fetch, buf, httpStatusLineReason(&reply->sline));
602 return -1; /* XXX -1 will abort stuff in ReadReply! */
603 }
604
605 /* must have a ready-to-use store entry if we got here */
606 /* can we stay with the old in-memory digest? */
607 if (status == HTTP_NOT_MODIFIED && fetch->pd->cd) {
608 peerDigestFetchStop(fetch, buf, "Not modified");
609 fetch->state = DIGEST_READ_DONE;
610 } else {
611 fetch->state = DIGEST_READ_HEADERS;
612 }
613 } else {
614 /* need more data, do we have space? */
615
616 if (size >= SM_PAGE_SIZE)
617 peerDigestFetchAbort(fetch, buf, "reply header too big");
618 }
619
620 /* We don't want to actually ack that we've handled anything,
621 * otherwise SwapInHeaders() won't get the reply line .. */
622 return 0;
623 }
624
625 /* fetch headers from disk, pass on to SwapInCBlock */
626 int
627 peerDigestSwapInHeaders(void *data, char *buf, ssize_t size)
628 {
629 DigestFetchState *fetch = (DigestFetchState *)data;
630 size_t hdr_size;
631
632 assert(fetch->state == DIGEST_READ_HEADERS);
633
634 if (peerDigestFetchedEnough(fetch, buf, size, "peerDigestSwapInHeaders"))
635 return -1;
636
637 assert(!fetch->offset);
638
639 if ((hdr_size = headersEnd(buf, size))) {
640 assert(fetch->entry->getReply());
641 assert (fetch->entry->getReply()->sline.status != 0);
642
643 if (fetch->entry->getReply()->sline.status != HTTP_OK) {
644 debugs(72, DBG_IMPORTANT, "peerDigestSwapInHeaders: " << fetch->pd->host <<
645 " status " << fetch->entry->getReply()->sline.status <<
646 " got cached!");
647
648 peerDigestFetchAbort(fetch, buf, "internal status error");
649 return -1;
650 }
651
652 fetch->state = DIGEST_READ_CBLOCK;
653 return hdr_size; /* Say how much data we read */
654 } else {
655 /* need more data, do we have space? */
656
657 if (size >= SM_PAGE_SIZE) {
658 peerDigestFetchAbort(fetch, buf, "stored header too big");
659 return -1;
660 } else {
661 return 0; /* We need to read more to parse .. */
662 }
663 }
664
665 fatal("peerDigestSwapInHeaders() - shouldn't get here!\n");
666 return 0; /* keep gcc happy */
667 }
668
669 int
670 peerDigestSwapInCBlock(void *data, char *buf, ssize_t size)
671 {
672 DigestFetchState *fetch = (DigestFetchState *)data;
673
674 assert(fetch->state == DIGEST_READ_CBLOCK);
675
676 if (peerDigestFetchedEnough(fetch, buf, size, "peerDigestSwapInCBlock"))
677 return -1;
678
679 if (size >= (ssize_t)StoreDigestCBlockSize) {
680 PeerDigest *pd = fetch->pd;
681
682 assert(pd && fetch->entry->getReply());
683
684 if (peerDigestSetCBlock(pd, buf)) {
685 /* XXX: soon we will have variable header size */
686 /* switch to CD buffer and fetch digest guts */
687 buf = NULL;
688 assert(pd->cd->mask);
689 fetch->state = DIGEST_READ_MASK;
690 return StoreDigestCBlockSize;
691 } else {
692 peerDigestFetchAbort(fetch, buf, "invalid digest cblock");
693 return -1;
694 }
695 } else {
696 /* need more data, do we have space? */
697
698 if (size >= SM_PAGE_SIZE) {
699 peerDigestFetchAbort(fetch, buf, "digest cblock too big");
700 return -1;
701 } else {
702 return 0; /* We need more data */
703 }
704 }
705
706 fatal("peerDigestSwapInCBlock(): shouldn't get here!\n");
707 return 0; /* keep gcc happy */
708 }
709
710 int
711 peerDigestSwapInMask(void *data, char *buf, ssize_t size)
712 {
713 DigestFetchState *fetch = (DigestFetchState *)data;
714 PeerDigest *pd;
715
716 pd = fetch->pd;
717 assert(pd->cd && pd->cd->mask);
718
719 /*
720 * NOTENOTENOTENOTENOTE: buf doesn't point to pd->cd->mask anymore!
721 * we need to do the copy ourselves!
722 */
723 memcpy(pd->cd->mask + fetch->mask_offset, buf, size);
724
725 /* NOTE! buf points to the middle of pd->cd->mask! */
726
727 if (peerDigestFetchedEnough(fetch, NULL, size, "peerDigestSwapInMask"))
728 return -1;
729
730 fetch->mask_offset += size;
731
732 if (fetch->mask_offset >= pd->cd->mask_size) {
733 debugs(72, 2, "peerDigestSwapInMask: Done! Got " <<
734 fetch->mask_offset << ", expected " << pd->cd->mask_size);
735 assert(fetch->mask_offset == pd->cd->mask_size);
736 assert(peerDigestFetchedEnough(fetch, NULL, 0, "peerDigestSwapInMask"));
737 return -1; /* XXX! */
738 } else {
739 /* We always read everything, so return so */
740 return size;
741 }
742
743 fatal("peerDigestSwapInMask(): shouldn't get here!\n");
744 return 0; /* keep gcc happy */
745 }
746
747 static int
748 peerDigestFetchedEnough(DigestFetchState * fetch, char *buf, ssize_t size, const char *step_name)
749 {
750 PeerDigest *pd = NULL;
751 const char *host = "<unknown>"; /* peer host */
752 const char *reason = NULL; /* reason for completion */
753 const char *no_bug = NULL; /* successful completion if set */
754 const int pdcb_valid = cbdataReferenceValid(fetch->pd);
755 const int pcb_valid = cbdataReferenceValid(fetch->pd->peer);
756
757 /* test possible exiting conditions (the same for most steps!)
758 * cases marked with '?!' should not happen */
759
760 if (!reason) {
761 if (!(pd = fetch->pd))
762 reason = "peer digest disappeared?!";
763
764 #if DONT /* WHY NOT? /HNO */
765
766 else if (!cbdataReferenceValid(pd))
767 reason = "invalidated peer digest?!";
768
769 #endif
770
771 else
772 host = pd->host.termedBuf();
773 }
774
775 debugs(72, 6, step_name << ": peer " << host << ", offset: " <<
776 fetch->offset << " size: " << size << ".");
777
778 /* continue checking (with pd and host known and valid) */
779
780 if (!reason) {
781 if (!cbdataReferenceValid(pd->peer))
782 reason = "peer disappeared";
783 else if (size < 0)
784 reason = "swap failure";
785 else if (!fetch->entry)
786 reason = "swap aborted?!";
787 else if (EBIT_TEST(fetch->entry->flags, ENTRY_ABORTED))
788 reason = "swap aborted";
789 }
790
791 /* continue checking (maybe-successful eof case) */
792 if (!reason && !size) {
793 if (!pd->cd)
794 reason = "null digest?!";
795 else if (fetch->mask_offset != (int)pd->cd->mask_size)
796 reason = "premature end of digest?!";
797 else if (!peerDigestUseful(pd))
798 reason = "useless digest";
799 else
800 reason = no_bug = "success";
801 }
802
803 /* finish if we have a reason */
804 if (reason) {
805 const int level = strstr(reason, "?!") ? 1 : 3;
806 debugs(72, level, "" << step_name << ": peer " << host << ", exiting after '" << reason << "'");
807 peerDigestReqFinish(fetch, buf,
808 1, pdcb_valid, pcb_valid, reason, !no_bug);
809 } else {
810 /* paranoid check */
811 assert(pdcb_valid && pcb_valid);
812 }
813
814 return reason != NULL;
815 }
816
817 /* call this when all callback data is valid and fetch must be stopped but
818 * no error has occurred (e.g. we received 304 reply and reuse old digest) */
819 static void
820 peerDigestFetchStop(DigestFetchState * fetch, char *buf, const char *reason)
821 {
822 assert(reason);
823 debugs(72, 2, "peerDigestFetchStop: peer " << fetch->pd->host << ", reason: " << reason);
824 peerDigestReqFinish(fetch, buf, 1, 1, 1, reason, 0);
825 }
826
827 /* call this when all callback data is valid but something bad happened */
828 static void
829 peerDigestFetchAbort(DigestFetchState * fetch, char *buf, const char *reason)
830 {
831 assert(reason);
832 debugs(72, 2, "peerDigestFetchAbort: peer " << fetch->pd->host << ", reason: " << reason);
833 peerDigestReqFinish(fetch, buf, 1, 1, 1, reason, 1);
834 }
835
836 /* complete the digest transfer, update stats, unlock/release everything */
837 static void
838 peerDigestReqFinish(DigestFetchState * fetch, char *buf,
839 int fcb_valid, int pdcb_valid, int pcb_valid,
840 const char *reason, int err)
841 {
842 assert(reason);
843
844 /* must go before peerDigestPDFinish */
845
846 if (pdcb_valid) {
847 fetch->pd->flags.requested = false;
848 fetch->pd->req_result = reason;
849 }
850
851 /* schedule next check if peer is still out there */
852 if (pcb_valid) {
853 PeerDigest *pd = fetch->pd;
854
855 if (err) {
856 pd->times.retry_delay = peerDigestIncDelay(pd);
857 peerDigestSetCheck(pd, pd->times.retry_delay);
858 } else {
859 pd->times.retry_delay = 0;
860 peerDigestSetCheck(pd, peerDigestNewDelay(fetch->entry));
861 }
862 }
863
864 /* note: order is significant */
865 if (fcb_valid)
866 peerDigestFetchSetStats(fetch);
867
868 if (pdcb_valid)
869 peerDigestPDFinish(fetch, pcb_valid, err);
870
871 if (fcb_valid)
872 peerDigestFetchFinish(fetch, err);
873 }
874
875 /* destroys digest if peer disappeared
876 * must be called only when fetch and pd cbdata are valid */
877 static void
878 peerDigestPDFinish(DigestFetchState * fetch, int pcb_valid, int err)
879 {
880 PeerDigest *pd = fetch->pd;
881 const char *host = pd->host.termedBuf();
882
883 pd->times.received = squid_curtime;
884 pd->times.req_delay = fetch->resp_time;
885 kb_incr(&pd->stats.sent.kbytes, (size_t) fetch->sent.bytes);
886 kb_incr(&pd->stats.recv.kbytes, (size_t) fetch->recv.bytes);
887 pd->stats.sent.msgs += fetch->sent.msg;
888 pd->stats.recv.msgs += fetch->recv.msg;
889
890 if (err) {
891 debugs(72, DBG_IMPORTANT, "" << (pcb_valid ? "temporary " : "" ) << "disabling (" << pd->req_result << ") digest from " << host);
892
893 if (pd->cd) {
894 cacheDigestDestroy(pd->cd);
895 pd->cd = NULL;
896 }
897
898 pd->flags.usable = false;
899
900 if (!pcb_valid)
901 peerDigestNotePeerGone(pd);
902 } else {
903 assert(pcb_valid);
904
905 pd->flags.usable = true;
906
907 /* XXX: ugly condition, but how? */
908
909 if (fetch->entry->store_status == STORE_OK)
910 debugs(72, 2, "re-used old digest from " << host);
911 else
912 debugs(72, 2, "received valid digest from " << host);
913 }
914
915 cbdataReferenceDone(fetch->pd);
916 }
917
918 /* free fetch state structures
919 * must be called only when fetch cbdata is valid */
920 static void
921 peerDigestFetchFinish(DigestFetchState * fetch, int err)
922 {
923 assert(fetch->entry && fetch->request);
924
925 if (fetch->old_entry) {
926 debugs(72, 3, "peerDigestFetchFinish: deleting old entry");
927 storeUnregister(fetch->old_sc, fetch->old_entry, fetch);
928 fetch->old_entry->releaseRequest();
929 fetch->old_entry->unlock();
930 fetch->old_entry = NULL;
931 }
932
933 /* update global stats */
934 kb_incr(&statCounter.cd.kbytes_sent, (size_t) fetch->sent.bytes);
935
936 kb_incr(&statCounter.cd.kbytes_recv, (size_t) fetch->recv.bytes);
937
938 statCounter.cd.msgs_sent += fetch->sent.msg;
939
940 statCounter.cd.msgs_recv += fetch->recv.msg;
941
942 /* unlock everything */
943 storeUnregister(fetch->sc, fetch->entry, fetch);
944
945 fetch->entry->unlock();
946
947 HTTPMSGUNLOCK(fetch->request);
948
949 fetch->entry = NULL;
950
951 assert(fetch->pd == NULL);
952
953 cbdataFree(fetch);
954 }
955
956 /* calculate fetch stats after completion */
957 static void
958 peerDigestFetchSetStats(DigestFetchState * fetch)
959 {
960 MemObject *mem;
961 assert(fetch->entry && fetch->request);
962
963 mem = fetch->entry->mem_obj;
964 assert(mem);
965
966 /* XXX: outgoing numbers are not precise */
967 /* XXX: we must distinguish between 304 hits and misses here */
968 fetch->sent.bytes = fetch->request->prefixLen();
969 /* XXX: this is slightly wrong: we don't KNOW that the entire memobject
970 * was fetched. We only know how big it is
971 */
972 fetch->recv.bytes = mem->size();
973 fetch->sent.msg = fetch->recv.msg = 1;
974 fetch->expires = fetch->entry->expires;
975 fetch->resp_time = squid_curtime - fetch->start_time;
976
977 debugs(72, 3, "peerDigestFetchFinish: recv " << fetch->recv.bytes <<
978 " bytes in " << (int) fetch->resp_time << " secs");
979
980 debugs(72, 3, "peerDigestFetchFinish: expires: " <<
981 (long int) fetch->expires << " (" << std::showpos <<
982 (int) (fetch->expires - squid_curtime) << "), lmt: " <<
983 std::noshowpos << (long int) fetch->entry->lastmod << " (" <<
984 std::showpos << (int) (fetch->entry->lastmod - squid_curtime) <<
985 ")");
986
987 }
988
989 static int
990 peerDigestSetCBlock(PeerDigest * pd, const char *buf)
991 {
992 StoreDigestCBlock cblock;
993 int freed_size = 0;
994 const char *host = pd->host.termedBuf();
995
996 memcpy(&cblock, buf, sizeof(cblock));
997 /* network -> host conversions */
998 cblock.ver.current = ntohs(cblock.ver.current);
999 cblock.ver.required = ntohs(cblock.ver.required);
1000 cblock.capacity = ntohl(cblock.capacity);
1001 cblock.count = ntohl(cblock.count);
1002 cblock.del_count = ntohl(cblock.del_count);
1003 cblock.mask_size = ntohl(cblock.mask_size);
1004 debugs(72, 2, "got digest cblock from " << host << "; ver: " <<
1005 (int) cblock.ver.current << " (req: " << (int) cblock.ver.required <<
1006 ")");
1007
1008 debugs(72, 2, "\t size: " <<
1009 cblock.mask_size << " bytes, e-cnt: " <<
1010 cblock.count << ", e-util: " <<
1011 xpercentInt(cblock.count, cblock.capacity) << "%" );
1012 /* check version requirements (both ways) */
1013
1014 if (cblock.ver.required > CacheDigestVer.current) {
1015 debugs(72, DBG_IMPORTANT, "" << host << " digest requires version " <<
1016 cblock.ver.required << "; have: " << CacheDigestVer.current);
1017
1018 return 0;
1019 }
1020
1021 if (cblock.ver.current < CacheDigestVer.required) {
1022 debugs(72, DBG_IMPORTANT, "" << host << " digest is version " <<
1023 cblock.ver.current << "; we require: " <<
1024 CacheDigestVer.required);
1025
1026 return 0;
1027 }
1028
1029 /* check consistency */
1030 if (cblock.ver.required > cblock.ver.current ||
1031 cblock.mask_size <= 0 || cblock.capacity <= 0 ||
1032 cblock.bits_per_entry <= 0 || cblock.hash_func_count <= 0) {
1033 debugs(72, DBG_CRITICAL, "" << host << " digest cblock is corrupted.");
1034 return 0;
1035 }
1036
1037 /* check consistency further */
1038 if ((size_t)cblock.mask_size != cacheDigestCalcMaskSize(cblock.capacity, cblock.bits_per_entry)) {
1039 debugs(72, DBG_CRITICAL, host << " digest cblock is corrupted " <<
1040 "(mask size mismatch: " << cblock.mask_size << " ? " <<
1041 cacheDigestCalcMaskSize(cblock.capacity, cblock.bits_per_entry)
1042 << ").");
1043 return 0;
1044 }
1045
1046 /* there are some things we cannot do yet */
1047 if (cblock.hash_func_count != CacheDigestHashFuncCount) {
1048 debugs(72, DBG_CRITICAL, "" << host << " digest: unsupported #hash functions: " <<
1049 cblock.hash_func_count << " ? " << CacheDigestHashFuncCount << ".");
1050 return 0;
1051 }
1052
1053 /*
1054 * no cblock bugs below this point
1055 */
1056 /* check size changes */
1057 if (pd->cd && cblock.mask_size != (ssize_t)pd->cd->mask_size) {
1058 debugs(72, 2, host << " digest changed size: " << cblock.mask_size <<
1059 " -> " << pd->cd->mask_size);
1060 freed_size = pd->cd->mask_size;
1061 cacheDigestDestroy(pd->cd);
1062 pd->cd = NULL;
1063 }
1064
1065 if (!pd->cd) {
1066 debugs(72, 2, "creating " << host << " digest; size: " << cblock.mask_size << " (" <<
1067 std::showpos << (int) (cblock.mask_size - freed_size) << ") bytes");
1068 pd->cd = cacheDigestCreate(cblock.capacity, cblock.bits_per_entry);
1069
1070 if (cblock.mask_size >= freed_size)
1071 kb_incr(&statCounter.cd.memory, cblock.mask_size - freed_size);
1072 }
1073
1074 assert(pd->cd);
1075 /* these assignments leave us in an inconsistent state until we finish reading the digest */
1076 pd->cd->count = cblock.count;
1077 pd->cd->del_count = cblock.del_count;
1078 return 1;
1079 }
1080
1081 static int
1082 peerDigestUseful(const PeerDigest * pd)
1083 {
1084 /* TODO: we should calculate the prob of a false hit instead of bit util */
1085 const int bit_util = cacheDigestBitUtil(pd->cd);
1086
1087 if (bit_util > 65) {
1088 debugs(72, DBG_CRITICAL, "Warning: " << pd->host <<
1089 " peer digest has too many bits on (" << bit_util << "%%).");
1090
1091 return 0;
1092 }
1093
1094 return 1;
1095 }
1096
1097 static int
1098 saneDiff(time_t diff)
1099 {
1100 return abs((int) diff) > squid_curtime / 2 ? 0 : diff;
1101 }
1102
1103 void
1104 peerDigestStatsReport(const PeerDigest * pd, StoreEntry * e)
1105 {
1106 #define f2s(flag) (pd->flags.flag ? "yes" : "no")
1107 #define appendTime(tm) storeAppendPrintf(e, "%s\t %10ld\t %+d\t %+d\n", \
1108 ""#tm, (long int)pd->times.tm, \
1109 saneDiff(pd->times.tm - squid_curtime), \
1110 saneDiff(pd->times.tm - pd->times.initialized))
1111
1112 assert(pd);
1113
1114 const char *host = pd->host.termedBuf();
1115 storeAppendPrintf(e, "\npeer digest from %s\n", host);
1116
1117 cacheDigestGuessStatsReport(&pd->stats.guess, e, host);
1118
1119 storeAppendPrintf(e, "\nevent\t timestamp\t secs from now\t secs from init\n");
1120 appendTime(initialized);
1121 appendTime(needed);
1122 appendTime(requested);
1123 appendTime(received);
1124 appendTime(next_check);
1125
1126 storeAppendPrintf(e, "peer digest state:\n");
1127 storeAppendPrintf(e, "\tneeded: %3s, usable: %3s, requested: %3s\n",
1128 f2s(needed), f2s(usable), f2s(requested));
1129 storeAppendPrintf(e, "\n\tlast retry delay: %d secs\n",
1130 (int) pd->times.retry_delay);
1131 storeAppendPrintf(e, "\tlast request response time: %d secs\n",
1132 (int) pd->times.req_delay);
1133 storeAppendPrintf(e, "\tlast request result: %s\n",
1134 pd->req_result ? pd->req_result : "(none)");
1135
1136 storeAppendPrintf(e, "\npeer digest traffic:\n");
1137 storeAppendPrintf(e, "\trequests sent: %d, volume: %d KB\n",
1138 pd->stats.sent.msgs, (int) pd->stats.sent.kbytes.kb);
1139 storeAppendPrintf(e, "\treplies recv: %d, volume: %d KB\n",
1140 pd->stats.recv.msgs, (int) pd->stats.recv.kbytes.kb);
1141
1142 storeAppendPrintf(e, "\npeer digest structure:\n");
1143
1144 if (pd->cd)
1145 cacheDigestReport(pd->cd, host, e);
1146 else
1147 storeAppendPrintf(e, "\tno in-memory copy\n");
1148 }
1149
1150 #endif