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