/* * Copyright (C) 1996-2023 The Squid Software Foundation and contributors * * Squid software is distributed under GPLv2+ license and includes * contributions from numerous individuals and organizations. * Please see the COPYING and CONTRIBUTORS files for details. */ /* DEBUG: section 31 Hypertext Caching Protocol */ #include "squid.h" #include "AccessLogEntry.h" #include "acl/Acl.h" #include "acl/FilledChecklist.h" #include "base/AsyncCallbacks.h" #include "CachePeer.h" #include "CachePeers.h" #include "comm.h" #include "comm/Connection.h" #include "comm/Loops.h" #include "compat/xalloc.h" #include "debug/Messages.h" #include "globals.h" #include "htcp.h" #include "http.h" #include "http/ContentLengthInterpreter.h" #include "HttpRequest.h" #include "icmp/net_db.h" #include "ip/tools.h" #include "ipc/StartListening.h" #include "md5.h" #include "mem/forward.h" #include "MemBuf.h" #include "refresh.h" #include "SquidConfig.h" #include "StatCounters.h" #include "Store.h" #include "store_key_md5.h" #include "StoreClient.h" #include "tools.h" typedef struct _Countstr Countstr; typedef struct _htcpHeader htcpHeader; typedef struct _htcpDataHeader htcpDataHeader; typedef struct _htcpDataHeaderSquid htcpDataHeaderSquid; typedef struct _htcpAuthHeader htcpAuthHeader; struct _Countstr { uint16_t length; char *text; }; struct _htcpHeader { uint16_t length; u_char major; u_char minor; }; struct _htcpDataHeaderSquid { uint16_t length; #if !WORDS_BIGENDIAN unsigned int opcode:4; unsigned int response:4; #else unsigned int response:4; unsigned int opcode:4; #endif #if !WORDS_BIGENDIAN unsigned int reserved:6; unsigned int F1:1; unsigned int RR:1; #else unsigned int RR:1; unsigned int F1:1; unsigned int reserved:6; #endif uint32_t msg_id; }; struct _htcpDataHeader { uint16_t length; #if WORDS_BIGENDIAN uint8_t opcode:4; uint8_t response:4; #else uint8_t response:4; uint8_t opcode:4; #endif #if WORDS_BIGENDIAN uint8_t reserved:6; uint8_t F1:1; uint8_t RR:1; #else uint8_t RR:1; uint8_t F1:1; uint8_t reserved:6; #endif uint32_t msg_id; }; /* RR == 0 --> F1 = RESPONSE DESIRED FLAG */ /* RR == 1 --> F1 = MESSAGE OVERALL FLAG */ /* RR == 0 --> REQUEST */ /* RR == 1 --> RESPONSE */ struct _htcpAuthHeader { uint16_t length; time_t sig_time; time_t sig_expire; Countstr key_name; Countstr signature; }; class htcpSpecifier: public CodeContext, public StoreClient { MEMPROXY_CLASS(htcpSpecifier); public: typedef RefCount Pointer; void checkHit(); void checkedHit(StoreEntry *); void setFrom(Ip::Address &anIp) { from = anIp; } void setDataHeader(htcpDataHeader *aDataHeader) { dhdr = aDataHeader; } /* CodeContext API */ ScopedId codeContextGist() const override; // override std::ostream &detailCodeContext(std::ostream &os) const override; // override /* StoreClient API */ LogTags *loggingTags() const override; void fillChecklist(ACLFilledChecklist &) const override; public: const char *method = nullptr; const char *uri = nullptr; char *version = nullptr; char *req_hdrs = nullptr; size_t reqHdrsSz = 0; ///< size of the req_hdrs content HttpRequest::Pointer request; /// optimization: nil until needed mutable AccessLogEntryPointer al; private: HttpRequest::Pointer checkHitRequest; Ip::Address from; htcpDataHeader *dhdr = nullptr; }; class htcpDetail { MEMPROXY_CLASS(htcpDetail); public: char *resp_hdrs = nullptr; size_t respHdrsSz = 0; char *entity_hdrs = nullptr; size_t entityHdrsSz = 0; char *cache_hdrs = nullptr; size_t cacheHdrsSz = 0; }; class htcpStuff { public: htcpStuff(uint32_t id, int o, int r, int f) : op(o), rr(r), f1(f), msg_id(id) {} int op = 0; int rr = 0; int f1 = 0; int response = 0; int reason = 0; uint32_t msg_id; htcpSpecifier S; htcpDetail D; }; enum { HTCP_NOP, HTCP_TST, HTCP_MON, HTCP_SET, HTCP_CLR, HTCP_END }; static const char *const htcpOpcodeStr[] = { "HTCP_NOP", "HTCP_TST", "HTCP_MON", "HTCP_SET", "HTCP_CLR", "HTCP_END" }; /* * values for htcpDataHeader->response */ enum { AUTH_REQUIRED, AUTH_FAILURE, OPCODE_UNIMPLEMENTED, MAJOR_VERSION_UNSUPPORTED, MINOR_VERSION_UNSUPPORTED, INVALID_OPCODE }; /* * values for htcpDataHeader->RR */ enum { RR_REQUEST, RR_RESPONSE }; static void htcpIncomingConnectionOpened(Ipc::StartListeningAnswer&); static uint32_t msg_id_counter = 0; static Comm::ConnectionPointer htcpOutgoingConn = nullptr; static Comm::ConnectionPointer htcpIncomingConn = nullptr; #define N_QUERIED_KEYS 8192 static uint32_t queried_id[N_QUERIED_KEYS]; static cache_key queried_keys[N_QUERIED_KEYS][SQUID_MD5_DIGEST_LENGTH]; static Ip::Address queried_addr[N_QUERIED_KEYS]; static int old_squid_format = 0; static ssize_t htcpBuildPacket(char *buf, size_t buflen, htcpStuff * stuff); static htcpDetail *htcpUnpackDetail(char *buf, int sz); static ssize_t htcpBuildAuth(char *buf, size_t buflen); static ssize_t htcpBuildCountstr(char *buf, size_t buflen, const char *s, size_t len); static ssize_t htcpBuildData(char *buf, size_t buflen, htcpStuff * stuff); static ssize_t htcpBuildDetail(char *buf, size_t buflen, htcpStuff * stuff); static ssize_t htcpBuildOpData(char *buf, size_t buflen, htcpStuff * stuff); static ssize_t htcpBuildSpecifier(char *buf, size_t buflen, htcpStuff * stuff); static ssize_t htcpBuildTstOpData(char *buf, size_t buflen, htcpStuff * stuff); static void htcpHandleMsg(char *buf, int sz, Ip::Address &from); static void htcpLogHtcp(Ip::Address &, const int, const LogTags_ot, const char *, AccessLogEntryPointer); static void htcpHandleTst(htcpDataHeader *, char *buf, int sz, Ip::Address &from); static void htcpRecv(int fd, void *data); static void htcpSend(const char *buf, int len, Ip::Address &to); static void htcpTstReply(htcpDataHeader *, StoreEntry *, htcpSpecifier *, Ip::Address &); static void htcpHandleTstRequest(htcpDataHeader *, char *buf, int sz, Ip::Address &from); static void htcpHandleTstResponse(htcpDataHeader *, char *, int, Ip::Address &); static void htcpSyncAle(AccessLogEntryPointer &al, const Ip::Address &caddr, const int opcode, const char *url) { if (!al) al = new AccessLogEntry(); al->cache.caddr = caddr; al->htcp.opcode = htcpOpcodeStr[opcode]; al->url = url; al->setVirginUrlForMissingRequest(al->url); // HTCP transactions do not wait al->cache.start_time = current_time; al->cache.trTime.tv_sec = 0; al->cache.trTime.tv_usec = 0; } static void htcpHexdump(const char *tag, const char *s, int sz) { #if USE_HEXDUMP char hex[80]; debugs(31, 3, "htcpHexdump " << tag); memset(hex, '\0', sizeof(hex)); for (int i = 0; i < sz; ++i) { int k = i % 16; snprintf(&hex[k * 3], 4, " %02x", (int) *(s + i)); if (k < 15 && i < (sz - 1)) continue; debugs(31, 3, "\t" << hex); memset(hex, '\0', sizeof(hex)); } #else (void)tag; (void)s; (void)sz; #endif } /* * STUFF FOR SENDING HTCP MESSAGES */ static ssize_t htcpBuildAuth(char *buf, size_t buflen) { htcpAuthHeader auth; size_t copy_sz = 0; assert(2 == sizeof(uint16_t)); auth.length = htons(2); copy_sz += 2; if (buflen < copy_sz) return -1; memcpy(buf, &auth, copy_sz); return copy_sz; } static ssize_t htcpBuildCountstr(char *buf, size_t buflen, const char *s, size_t len) { int off = 0; if (buflen - off < 2) return -1; debugs(31, 3, "htcpBuildCountstr: LENGTH = " << len); debugs(31, 3, "htcpBuildCountstr: TEXT = {" << (s ? s : "") << "}"); uint16_t length = htons((uint16_t) len); memcpy(buf + off, &length, 2); off += 2; if (buflen - off < len) return -1; if (len) memcpy(buf + off, s, len); off += len; return off; } static ssize_t htcpBuildSpecifier(char *buf, size_t buflen, htcpStuff * stuff) { ssize_t off = 0; ssize_t s; s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.method, (stuff->S.method?strlen(stuff->S.method):0)); if (s < 0) return s; off += s; s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.uri, (stuff->S.uri?strlen(stuff->S.uri):0)); if (s < 0) return s; off += s; s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.version, (stuff->S.version?strlen(stuff->S.version):0)); if (s < 0) return s; off += s; s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.req_hdrs, stuff->S.reqHdrsSz); if (s < 0) return s; off += s; debugs(31, 3, "htcpBuildSpecifier: size " << off); return off; } static ssize_t htcpBuildDetail(char *buf, size_t buflen, htcpStuff * stuff) { ssize_t off = 0; ssize_t s; s = htcpBuildCountstr(buf + off, buflen - off, stuff->D.resp_hdrs, stuff->D.respHdrsSz); if (s < 0) return s; off += s; s = htcpBuildCountstr(buf + off, buflen - off, stuff->D.entity_hdrs, stuff->D.entityHdrsSz); if (s < 0) return s; off += s; s = htcpBuildCountstr(buf + off, buflen - off, stuff->D.cache_hdrs, stuff->D.cacheHdrsSz); if (s < 0) return s; off += s; return off; } static ssize_t htcpBuildTstOpData(char *buf, size_t buflen, htcpStuff * stuff) { switch (stuff->rr) { case RR_REQUEST: debugs(31, 3, "htcpBuildTstOpData: RR_REQUEST"); return htcpBuildSpecifier(buf, buflen, stuff); case RR_RESPONSE: debugs(31, 3, "htcpBuildTstOpData: RR_RESPONSE"); debugs(31, 3, "htcpBuildTstOpData: F1 = " << stuff->f1); if (stuff->f1) /* cache miss */ return 0; else /* cache hit */ return htcpBuildDetail(buf, buflen, stuff); default: fatal_dump("htcpBuildTstOpData: bad RR value"); } return 0; } static ssize_t htcpBuildClrOpData(char *buf, size_t buflen, htcpStuff * stuff) { unsigned short reason; switch (stuff->rr) { case RR_REQUEST: debugs(31, 3, "htcpBuildClrOpData: RR_REQUEST"); reason = htons((unsigned short)stuff->reason); memcpy(buf, &reason, 2); return htcpBuildSpecifier(buf + 2, buflen - 2, stuff) + 2; case RR_RESPONSE: break; default: fatal_dump("htcpBuildClrOpData: bad RR value"); } return 0; } static ssize_t htcpBuildOpData(char *buf, size_t buflen, htcpStuff * stuff) { ssize_t off = 0; debugs(31, 3, "htcpBuildOpData: opcode " << htcpOpcodeStr[stuff->op]); switch (stuff->op) { case HTCP_TST: off = htcpBuildTstOpData(buf + off, buflen, stuff); break; case HTCP_CLR: off = htcpBuildClrOpData(buf + off, buflen, stuff); break; default: assert(0); break; } return off; } static ssize_t htcpBuildData(char *buf, size_t buflen, htcpStuff * stuff) { ssize_t off = 0; ssize_t op_data_sz; size_t hdr_sz = sizeof(htcpDataHeader); if (buflen < hdr_sz) return -1; off += hdr_sz; /* skip! */ op_data_sz = htcpBuildOpData(buf + off, buflen - off, stuff); if (op_data_sz < 0) return op_data_sz; off += op_data_sz; debugs(31, 3, "htcpBuildData: hdr.length = " << off); if (!old_squid_format) { htcpDataHeader hdr; memset(&hdr, 0, sizeof(hdr)); /* convert multi-byte fields */ hdr.msg_id = htonl(stuff->msg_id); hdr.length = htons(static_cast(off)); hdr.opcode = stuff->op; hdr.response = stuff->response; hdr.RR = stuff->rr; hdr.F1 = stuff->f1; memcpy(buf, &hdr, hdr_sz); } else { htcpDataHeaderSquid hdrSquid; memset(&hdrSquid, 0, sizeof(hdrSquid)); hdrSquid.length = htons(static_cast(off)); hdrSquid.opcode = stuff->op; hdrSquid.response = stuff->response; hdrSquid.F1 = stuff->f1; hdrSquid.RR = stuff->rr; memcpy(buf, &hdrSquid, hdr_sz); } debugs(31, 3, "htcpBuildData: size " << off); return off; } /* * Build an HTCP packet into buf, maximum length buflen. * Returns the packet length, or zero on failure. */ static ssize_t htcpBuildPacket(char *buf, size_t buflen, htcpStuff * stuff) { ssize_t s; ssize_t off = 0; size_t hdr_sz = sizeof(htcpHeader); htcpHeader hdr; /* skip the header -- we don't know the overall length */ if (buflen < hdr_sz) { return 0; } off += hdr_sz; s = htcpBuildData(buf + off, buflen - off, stuff); if (s < 0) { return 0; } off += s; s = htcpBuildAuth(buf + off, buflen - off); if (s < 0) { return 0; } off += s; hdr.length = htons((uint16_t) off); hdr.major = 0; if (old_squid_format) hdr.minor = 0; else hdr.minor = 1; memcpy(buf, &hdr, hdr_sz); debugs(31, 3, "htcpBuildPacket: size " << off); return off; } static void htcpSend(const char *buf, int len, Ip::Address &to) { debugs(31, 3, to); htcpHexdump("htcpSend", buf, len); if (comm_udp_sendto(htcpOutgoingConn->fd, to, buf, len) < 0) { int xerrno = errno; debugs(31, 3, htcpOutgoingConn << " sendto: " << xstrerr(xerrno)); } else ++statCounter.htcp.pkts_sent; } /* * Unpack an HTCP SPECIFIER in place * This will overwrite any following AUTH block */ // XXX: this needs to be turned into an Htcp1::Parser inheriting from Http1::RequestParser // but with different first-line and block unpacking logic. static htcpSpecifier::Pointer htcpUnpackSpecifier(char *buf, int sz) { static const htcpSpecifier::Pointer nil; htcpSpecifier::Pointer s(new htcpSpecifier); HttpRequestMethod method; /* Find length of METHOD */ uint16_t l = ntohs(*(uint16_t *) buf); sz -= 2; buf += 2; if (l > sz) { debugs(31, 3, "htcpUnpackSpecifier: failed to unpack METHOD"); return nil; } /* Set METHOD */ s->method = buf; buf += l; sz -= l; debugs(31, 6, "htcpUnpackSpecifier: METHOD (" << l << "/" << sz << ") '" << s->method << "'"); /* Find length of URI */ l = ntohs(*(uint16_t *) buf); sz -= 2; if (l > sz) { debugs(31, 3, "htcpUnpackSpecifier: failed to unpack URI"); return nil; } /* Add terminating null to METHOD */ *buf = '\0'; buf += 2; /* Set URI */ s->uri = buf; buf += l; sz -= l; debugs(31, 6, "htcpUnpackSpecifier: URI (" << l << "/" << sz << ") '" << s->uri << "'"); /* Find length of VERSION */ l = ntohs(*(uint16_t *) buf); sz -= 2; if (l > sz) { debugs(31, 3, "htcpUnpackSpecifier: failed to unpack VERSION"); return nil; } /* Add terminating null to URI */ *buf = '\0'; buf += 2; /* Set VERSION */ s->version = buf; buf += l; sz -= l; debugs(31, 6, "htcpUnpackSpecifier: VERSION (" << l << "/" << sz << ") '" << s->version << "'"); /* Find length of REQ-HDRS */ l = ntohs(*(uint16_t *) buf); sz -= 2; if (l > sz) { debugs(31, 3, "htcpUnpackSpecifier: failed to unpack REQ-HDRS"); return nil; } /* Add terminating null to URI */ *buf = '\0'; buf += 2; /* Set REQ-HDRS */ s->req_hdrs = buf; buf += l; sz -= l; s->reqHdrsSz = l; debugs(31, 6, "htcpUnpackSpecifier: REQ-HDRS (" << l << "/" << sz << ") '" << s->req_hdrs << "'"); debugs(31, 3, "htcpUnpackSpecifier: " << sz << " bytes left"); /* * Add terminating null to REQ-HDRS. This is possible because we allocated * an extra byte when we received the packet. This will overwrite any following * AUTH block. */ *buf = '\0'; // Parse the request method.HttpRequestMethodXXX(s->method); const auto mx = MasterXaction::MakePortless(); s->request = HttpRequest::FromUrlXXX(s->uri, mx, method == Http::METHOD_NONE ? HttpRequestMethod(Http::METHOD_GET) : method); if (!s->request) { debugs(31, 3, "failed to create request. Invalid URI?"); return nil; } return s; } /* * Unpack an HTCP DETAIL in place * This will overwrite any following AUTH block */ static htcpDetail * htcpUnpackDetail(char *buf, int sz) { htcpDetail *d = new htcpDetail; /* Find length of RESP-HDRS */ uint16_t l = ntohs(*(uint16_t *) buf); sz -= 2; buf += 2; if (l > sz) { debugs(31, 3, "htcpUnpackDetail: failed to unpack RESP_HDRS"); delete d; return nullptr; } /* Set RESP-HDRS */ d->resp_hdrs = buf; buf += l; d->respHdrsSz = l; sz -= l; /* Find length of ENTITY-HDRS */ l = ntohs(*(uint16_t *) buf); sz -= 2; if (l > sz) { debugs(31, 3, "htcpUnpackDetail: failed to unpack ENTITY_HDRS"); delete d; return nullptr; } /* Add terminating null to RESP-HDRS */ *buf = '\0'; /* Set ENTITY-HDRS */ buf += 2; d->entity_hdrs = buf; buf += l; d->entityHdrsSz = l; sz -= l; /* Find length of CACHE-HDRS */ l = ntohs(*(uint16_t *) buf); sz -= 2; if (l > sz) { debugs(31, 3, "htcpUnpackDetail: failed to unpack CACHE_HDRS"); delete d; return nullptr; } /* Add terminating null to ENTITY-HDRS */ *buf = '\0'; /* Set CACHE-HDRS */ buf += 2; d->cache_hdrs = buf; buf += l; d->cacheHdrsSz = l; sz -= l; debugs(31, 3, "htcpUnpackDetail: " << sz << " bytes left"); /* * Add terminating null to CACHE-HDRS. This is possible because we allocated * an extra byte when we received the packet. This will overwrite any following * AUTH block. */ *buf = '\0'; return d; } static bool htcpAccessAllowed(acl_access * acl, const htcpSpecifier::Pointer &s, Ip::Address &from) { /* default deny if no access list present */ if (!acl) return false; ACLFilledChecklist checklist(acl, s->request.getRaw()); checklist.src_addr = from; checklist.my_addr.setNoAddr(); return checklist.fastCheck().allowed(); } static void htcpTstReply(htcpDataHeader * dhdr, StoreEntry * e, htcpSpecifier * spec, Ip::Address &from) { static char pkt[8192]; HttpHeader hdr(hoHtcpReply); ssize_t pktlen; htcpStuff stuff(dhdr->msg_id, HTCP_TST, RR_RESPONSE, 0); stuff.response = e ? 0 : 1; debugs(31, 3, "htcpTstReply: response = " << stuff.response); if (spec) { stuff.S.method = spec->method; stuff.S.request = spec->request; stuff.S.uri = spec->uri; stuff.S.version = spec->version; stuff.S.req_hdrs = spec->req_hdrs; stuff.S.reqHdrsSz = spec->reqHdrsSz; if (e) hdr.putInt(Http::HdrType::AGE, (e->timestamp <= squid_curtime ? (squid_curtime - e->timestamp) : 0) ); else hdr.putInt(Http::HdrType::AGE, 0); MemBuf mb; mb.init(); hdr.packInto(&mb); stuff.D.resp_hdrs = xstrdup(mb.buf); stuff.D.respHdrsSz = mb.contentSize(); debugs(31, 3, "htcpTstReply: resp_hdrs = {" << stuff.D.resp_hdrs << "}"); mb.reset(); hdr.clean(); if (e && e->expires > -1) hdr.putTime(Http::HdrType::EXPIRES, e->expires); if (e && e->lastModified() > -1) hdr.putTime(Http::HdrType::LAST_MODIFIED, e->lastModified()); hdr.packInto(&mb); stuff.D.entity_hdrs = xstrdup(mb.buf); stuff.D.entityHdrsSz = mb.contentSize(); debugs(31, 3, "htcpTstReply: entity_hdrs = {" << stuff.D.entity_hdrs << "}"); mb.reset(); hdr.clean(); #if USE_ICMP if (const char *host = spec->request->url.host()) { int rtt = 0; int hops = 0; int samp = 0; netdbHostData(host, &samp, &rtt, &hops); if (rtt || hops) { char cto_buf[SQUIDHOSTNAMELEN+128]; snprintf(cto_buf, sizeof(cto_buf), "%s %d %f %d", host, samp, 0.001 * rtt, hops); hdr.putExt("Cache-to-Origin", cto_buf); } } #endif /* USE_ICMP */ hdr.packInto(&mb); stuff.D.cache_hdrs = xstrdup(mb.buf); stuff.D.cacheHdrsSz = mb.contentSize(); debugs(31, 3, "htcpTstReply: cache_hdrs = {" << stuff.D.cache_hdrs << "}"); mb.clean(); hdr.clean(); } pktlen = htcpBuildPacket(pkt, sizeof(pkt), &stuff); safe_free(stuff.D.resp_hdrs); stuff.D.respHdrsSz = 0; safe_free(stuff.D.entity_hdrs); stuff.D.entityHdrsSz = 0; safe_free(stuff.D.cache_hdrs); stuff.D.cacheHdrsSz = 0; if (!pktlen) { debugs(31, 3, "htcpTstReply: htcpBuildPacket() failed"); return; } htcpSend(pkt, (int) pktlen, from); } static void htcpClrReply(htcpDataHeader * dhdr, int purgeSucceeded, Ip::Address &from) { static char pkt[8192]; ssize_t pktlen; /* If dhdr->F1 == 0, no response desired */ if (dhdr->F1 == 0) return; htcpStuff stuff(dhdr->msg_id, HTCP_CLR, RR_RESPONSE, 0); stuff.response = purgeSucceeded ? 0 : 2; debugs(31, 3, "htcpClrReply: response = " << stuff.response); pktlen = htcpBuildPacket(pkt, sizeof(pkt), &stuff); if (pktlen == 0) { debugs(31, 3, "htcpClrReply: htcpBuildPacket() failed"); return; } htcpSend(pkt, (int) pktlen, from); } ScopedId htcpSpecifier::codeContextGist() const { if (al) { const auto gist = al->codeContextGist(); if (gist.value) return gist; } if (request) { if (const auto &mx = request->masterXaction) return mx->id.detach(); } return ScopedId("HTCP w/o master"); } std::ostream & htcpSpecifier::detailCodeContext(std::ostream &os) const { if (al) return al->detailCodeContext(os); if (request) { if (const auto &mx = request->masterXaction) return os << Debug::Extra << "current master transaction: " << mx->id; } // TODO: Report method, uri, and version if they have been set return os; } void htcpSpecifier::checkHit() { checkHitRequest = request; if (!checkHitRequest) { debugs(31, 3, "htcpCheckHit: NO; failed to parse URL"); checkedHit(nullptr); return; } if (!checkHitRequest->parseHeader(req_hdrs, reqHdrsSz)) { debugs(31, 3, "htcpCheckHit: NO; failed to parse request headers"); checkHitRequest = nullptr; checkedHit(nullptr); return; } const auto e = storeGetPublicByRequest(checkHitRequest.getRaw()); StoreEntry *hit = nullptr; if (!e) { debugs(31, 3, "NO; public object not found"); } else if (!e->validToSend()) { debugs(31, 3, "NO; entry not valid to send" ); } else if (refreshCheckHTCP(e, checkHitRequest.getRaw())) { debugs(31, 3, "NO; cached response is stale"); } else if (e->hittingRequiresCollapsing() && !startCollapsingOn(*e, false)) { debugs(31, 3, "NO; prohibited CF hit: " << *e); } else { debugs(31, 3, "YES!?"); hit = e; } checkedHit(hit); // TODO: StoreClients must either store/lock or abandon found entries. //if (e) // e->abandon(); } LogTags * htcpSpecifier::loggingTags() const { // calling htcpSyncAle() here would not change cache.code if (!al) al = new AccessLogEntry(); return &al->cache.code; } void htcpSpecifier::fillChecklist(ACLFilledChecklist &checklist) const { checklist.setRequest(request.getRaw()); htcpSyncAle(al, from, dhdr->opcode, uri); checklist.al = al; } static void htcpClrStoreEntry(StoreEntry * e) { debugs(31, 4, "htcpClrStoreEntry: Clearing store for entry: " << e->url() ); e->releaseRequest(); } static int htcpClrStore(const htcpSpecifier::Pointer &s) { HttpRequestPointer request(s->request); if (!request) { debugs(31, 3, "htcpClrStore: failed to parse URL"); return -1; } /* Parse request headers */ if (!request->parseHeader(s->req_hdrs, s->reqHdrsSz)) { debugs(31, 2, "htcpClrStore: failed to parse request headers"); return -1; } StoreEntry *e = nullptr; int released = 0; /* Lookup matching entries. This matches both GET and HEAD */ while ((e = storeGetPublicByRequest(request.getRaw()))) { htcpClrStoreEntry(e); ++released; } if (released) { debugs(31, 4, "htcpClrStore: Cleared " << released << " matching entries"); return 1; } else { debugs(31, 4, "htcpClrStore: No matching entry found"); return 0; } } static void htcpHandleTst(htcpDataHeader * hdr, char *buf, int sz, Ip::Address &from) { debugs(31, 3, "htcpHandleTst: sz = " << sz); if (hdr->RR == RR_REQUEST) htcpHandleTstRequest(hdr, buf, sz, from); else htcpHandleTstResponse(hdr, buf, sz, from); } HtcpReplyData::HtcpReplyData() : hit(0), hdr(hoHtcpReply), msg_id(0), version(0.0) { memset(&cto, 0, sizeof(cto)); } bool HtcpReplyData::parseHeader(const char *buffer, const size_t size) { Http::ContentLengthInterpreter interpreter; // no applyStatusCodeRules() -- HTCP replies lack cached HTTP status code return hdr.parse(buffer, size, interpreter); } static void htcpHandleTstResponse(htcpDataHeader * hdr, char *buf, int sz, Ip::Address &from) { HtcpReplyData htcpReply; cache_key *key = nullptr; Ip::Address *peer; htcpDetail *d = nullptr; char *t; if (queried_id[hdr->msg_id % N_QUERIED_KEYS] != hdr->msg_id) { debugs(31, 2, "htcpHandleTstResponse: No matching query id '" << hdr->msg_id << "' (expected " << queried_id[hdr->msg_id % N_QUERIED_KEYS] << ") from '" << from << "'"); return; } key = queried_keys[hdr->msg_id % N_QUERIED_KEYS]; if (!key) { debugs(31, 3, "htcpHandleTstResponse: No query key for response id '" << hdr->msg_id << "' from '" << from << "'"); return; } peer = &queried_addr[hdr->msg_id % N_QUERIED_KEYS]; if ( *peer != from || peer->port() != from.port() ) { debugs(31, 3, "htcpHandleTstResponse: Unexpected response source " << from ); return; } if (hdr->F1 == 1) { debugs(31, 2, "htcpHandleTstResponse: error condition, F1/MO == 1"); return; } htcpReply.msg_id = hdr->msg_id; debugs(31, 3, "htcpHandleTstResponse: msg_id = " << htcpReply.msg_id); htcpReply.hit = hdr->response ? 0 : 1; if (hdr->F1) { debugs(31, 3, "htcpHandleTstResponse: MISS"); } else { debugs(31, 3, "htcpHandleTstResponse: HIT"); d = htcpUnpackDetail(buf, sz); if (d == nullptr) { debugs(31, 3, "htcpHandleTstResponse: bad DETAIL"); return; } if ((t = d->resp_hdrs)) htcpReply.parseHeader(t, d->respHdrsSz); if ((t = d->entity_hdrs)) htcpReply.parseHeader(t, d->entityHdrsSz); if ((t = d->cache_hdrs)) htcpReply.parseHeader(t, d->cacheHdrsSz); } debugs(31, 3, "htcpHandleTstResponse: key (" << key << ") " << storeKeyText(key)); neighborsHtcpReply(key, &htcpReply, from); htcpReply.hdr.clean(); delete d; } static void htcpHandleTstRequest(htcpDataHeader * dhdr, char *buf, int sz, Ip::Address &from) { if (sz == 0) { debugs(31, 3, "htcpHandleTst: nothing to do"); return; } if (dhdr->F1 == 0) return; /* buf should be a SPECIFIER */ htcpSpecifier::Pointer s(htcpUnpackSpecifier(buf, sz)); if (!s) { debugs(31, 3, "htcpHandleTstRequest: htcpUnpackSpecifier failed"); htcpLogHtcp(from, dhdr->opcode, LOG_UDP_INVALID, dash_str, nullptr); return; } else { s->setFrom(from); s->setDataHeader(dhdr); } if (!s->request) { debugs(31, 3, "htcpHandleTstRequest: failed to parse request"); htcpLogHtcp(from, dhdr->opcode, LOG_UDP_INVALID, dash_str, s->al); return; } if (!htcpAccessAllowed(Config.accessList.htcp, s, from)) { debugs(31, 3, "htcpHandleTstRequest: Access denied"); htcpLogHtcp(from, dhdr->opcode, LOG_UDP_DENIED, s->uri, s->al); return; } debugs(31, 2, "HTCP TST request: " << s->method << " " << s->uri << " " << s->version); debugs(31, 2, "HTCP TST headers: " << s->req_hdrs); s->checkHit(); } void htcpSpecifier::checkedHit(StoreEntry *e) { if (e) { htcpTstReply(dhdr, e, this, from); /* hit */ htcpLogHtcp(from, dhdr->opcode, LOG_UDP_HIT, uri, al); } else { htcpTstReply(dhdr, nullptr, nullptr, from); /* cache miss */ htcpLogHtcp(from, dhdr->opcode, LOG_UDP_MISS, uri, al); } } static void htcpHandleClr(htcpDataHeader * hdr, char *buf, int sz, Ip::Address &from) { /* buf[0/1] is reserved and reason */ int reason = buf[1] << 4; debugs(31, 2, "HTCP CLR reason: " << reason); buf += 2; sz -= 2; /* buf should be a SPECIFIER */ if (sz == 0) { debugs(31, 4, "htcpHandleClr: nothing to do"); htcpLogHtcp(from, hdr->opcode, LOG_UDP_INVALID, dash_str, nullptr); return; } htcpSpecifier::Pointer s(htcpUnpackSpecifier(buf, sz)); if (!s) { debugs(31, 3, "htcpHandleClr: htcpUnpackSpecifier failed"); htcpLogHtcp(from, hdr->opcode, LOG_UDP_INVALID, dash_str, nullptr); return; } else { s->setFrom(from); s->setDataHeader(hdr); } if (!s->request) { debugs(31, 3, "htcpHandleTstRequest: failed to parse request"); htcpLogHtcp(from, hdr->opcode, LOG_UDP_INVALID, dash_str, s->al); return; } if (!htcpAccessAllowed(Config.accessList.htcp_clr, s, from)) { debugs(31, 3, "htcpHandleClr: Access denied"); htcpLogHtcp(from, hdr->opcode, LOG_UDP_DENIED, s->uri, s->al); return; } debugs(31, 2, "HTCP CLR request: " << s->method << " " << s->uri << " " << s->version); debugs(31, 2, "HTCP CLR headers: " << s->req_hdrs); /* Release objects from cache * analog to clientPurgeRequest in client_side.c */ switch (htcpClrStore(s)) { case 1: htcpClrReply(hdr, 1, from); /* hit */ htcpLogHtcp(from, hdr->opcode, LOG_UDP_HIT, s->uri, s->al); break; case 0: htcpClrReply(hdr, 0, from); /* miss */ htcpLogHtcp(from, hdr->opcode, LOG_UDP_MISS, s->uri, s->al); break; default: break; } } /* * Forward a CLR request to all peers who have requested that CLRs be * forwarded to them. */ static void htcpForwardClr(char *buf, int sz) { for (const auto &p: CurrentCachePeers()) { if (!p->options.htcp) { continue; } if (!p->options.htcp_forward_clr) { continue; } htcpSend(buf, sz, p->in_addr); } } /* * Do the first pass of handling an HTCP message. This used to be two * separate functions, htcpHandle and htcpHandleData. They were merged to * allow for forwarding HTCP packets easily to other peers if desired. * * This function now works out what type of message we have received and then * hands it off to other functions to break apart message-specific data. */ static void htcpHandleMsg(char *buf, int sz, Ip::Address &from) { // TODO: function-scoped CodeContext::Reset(...("HTCP message from", from)) htcpHeader htcpHdr; htcpDataHeader hdr; char *hbuf; int hsz; if (sz < 0 || (size_t)sz < sizeof(htcpHeader)) { // These are highly likely to be attack packets. Should probably get a bigger warning. debugs(31, 2, "htcpHandle: msg size less than htcpHeader size from " << from); return; } htcpHexdump("htcpHandle", buf, sz); memcpy(&htcpHdr, buf, sizeof(htcpHeader)); htcpHdr.length = ntohs(htcpHdr.length); if (htcpHdr.minor == 0) old_squid_format = 1; else old_squid_format = 0; debugs(31, 3, "htcpHandle: htcpHdr.length = " << htcpHdr.length); debugs(31, 3, "htcpHandle: htcpHdr.major = " << htcpHdr.major); debugs(31, 3, "htcpHandle: htcpHdr.minor = " << htcpHdr.minor); if (sz != htcpHdr.length) { debugs(31, 3, "htcpHandle: sz/" << sz << " != htcpHdr.length/" << htcpHdr.length << " from " << from ); return; } if (htcpHdr.major != 0) { debugs(31, 3, "htcpHandle: Unknown major version " << htcpHdr.major << " from " << from ); return; } hbuf = buf + sizeof(htcpHeader); hsz = sz - sizeof(htcpHeader); if ((size_t)hsz < sizeof(htcpDataHeader)) { debugs(31, 3, "htcpHandleData: msg size less than htcpDataHeader size"); return; } if (!old_squid_format) { memcpy(&hdr, hbuf, sizeof(hdr)); } else { htcpDataHeaderSquid hdrSquid; memcpy(&hdrSquid, hbuf, sizeof(hdrSquid)); hdr.length = hdrSquid.length; hdr.opcode = hdrSquid.opcode; hdr.response = hdrSquid.response; hdr.F1 = hdrSquid.F1; hdr.RR = hdrSquid.RR; hdr.reserved = 0; hdr.msg_id = hdrSquid.msg_id; } hdr.length = ntohs(hdr.length); hdr.msg_id = ntohl(hdr.msg_id); debugs(31, 3, "htcpHandleData: hsz = " << hsz); debugs(31, 3, "htcpHandleData: length = " << hdr.length); if (hdr.opcode >= HTCP_END) { debugs(31, 3, "htcpHandleData: client " << from << ", opcode " << hdr.opcode << " out of range"); return; } debugs(31, 3, "htcpHandleData: opcode = " << hdr.opcode << " " << htcpOpcodeStr[hdr.opcode]); debugs(31, 3, "htcpHandleData: response = " << hdr.response); debugs(31, 3, "htcpHandleData: F1 = " << hdr.F1); debugs(31, 3, "htcpHandleData: RR = " << hdr.RR); debugs(31, 3, "htcpHandleData: msg_id = " << hdr.msg_id); if (hsz < hdr.length) { debugs(31, 3, "htcpHandleData: sz < hdr.length"); return; } /* * set sz = hdr.length so we ignore any AUTH fields following * the DATA. */ hsz = (int) hdr.length; hbuf += sizeof(htcpDataHeader); hsz -= sizeof(htcpDataHeader); debugs(31, 3, "htcpHandleData: hsz = " << hsz); htcpHexdump("htcpHandleData", hbuf, hsz); switch (hdr.opcode) { case HTCP_NOP: debugs(31, 3, "HTCP NOP not implemented"); break; case HTCP_TST: htcpHandleTst(&hdr, hbuf, hsz, from); break; case HTCP_MON: debugs(31, 3, "HTCP MON not implemented"); break; case HTCP_SET: debugs(31, 3, "HTCP SET not implemented"); break; case HTCP_CLR: htcpHandleClr(&hdr, hbuf, hsz, from); htcpForwardClr(buf, sz); break; default: break; } } static void htcpRecv(int fd, void *) { static char buf[8192]; int len; static Ip::Address from; /* Receive up to 8191 bytes, leaving room for a null */ len = comm_udp_recvfrom(fd, buf, sizeof(buf) - 1, 0, from); debugs(31, 3, "htcpRecv: FD " << fd << ", " << len << " bytes from " << from ); if (len) ++statCounter.htcp.pkts_recv; htcpHandleMsg(buf, len, from); Comm::SetSelect(fd, COMM_SELECT_READ, htcpRecv, nullptr, 0); } /* * ====================================================================== * PUBLIC FUNCTIONS * ====================================================================== */ void htcpOpenPorts(void) { if (Config.Port.htcp <= 0) { debugs(31, Important(21), "HTCP Disabled."); return; } htcpIncomingConn = new Comm::Connection; htcpIncomingConn->local = Config.Addrs.udp_incoming; htcpIncomingConn->local.port(Config.Port.htcp); if (!Ip::EnableIpv6 && !htcpIncomingConn->local.setIPv4()) { debugs(31, DBG_CRITICAL, "ERROR: IPv6 is disabled. " << htcpIncomingConn->local << " is not an IPv4 address."); fatal("HTCP port cannot be opened."); } /* split-stack for now requires default IPv4-only HTCP */ if (Ip::EnableIpv6&IPV6_SPECIAL_SPLITSTACK && htcpIncomingConn->local.isAnyAddr()) { htcpIncomingConn->local.setIPv4(); } auto call = asyncCallbackFun(31, 2, htcpIncomingConnectionOpened); Ipc::StartListening(SOCK_DGRAM, IPPROTO_UDP, htcpIncomingConn, Ipc::fdnInHtcpSocket, call); if (!Config.Addrs.udp_outgoing.isNoAddr()) { htcpOutgoingConn = new Comm::Connection; htcpOutgoingConn->local = Config.Addrs.udp_outgoing; htcpOutgoingConn->local.port(Config.Port.htcp); if (!Ip::EnableIpv6 && !htcpOutgoingConn->local.setIPv4()) { debugs(31, DBG_CRITICAL, "ERROR: IPv6 is disabled. " << htcpOutgoingConn->local << " is not an IPv4 address."); fatal("HTCP port cannot be opened."); } /* split-stack for now requires default IPv4-only HTCP */ if (Ip::EnableIpv6&IPV6_SPECIAL_SPLITSTACK && htcpOutgoingConn->local.isAnyAddr()) { htcpOutgoingConn->local.setIPv4(); } enter_suid(); comm_open_listener(SOCK_DGRAM, IPPROTO_UDP, htcpOutgoingConn, "Outgoing HTCP Socket"); leave_suid(); if (!Comm::IsConnOpen(htcpOutgoingConn)) fatal("Cannot open Outgoing HTCP Socket"); Comm::SetSelect(htcpOutgoingConn->fd, COMM_SELECT_READ, htcpRecv, nullptr, 0); debugs(31, DBG_IMPORTANT, "Sending HTCP messages from " << htcpOutgoingConn->local); } } static void htcpIncomingConnectionOpened(Ipc::StartListeningAnswer &answer) { const auto &conn = answer.conn; if (!Comm::IsConnOpen(conn)) fatal("Cannot open HTCP Socket"); Comm::SetSelect(conn->fd, COMM_SELECT_READ, htcpRecv, nullptr, 0); debugs(31, DBG_CRITICAL, "Accepting HTCP messages on " << conn->local); if (Config.Addrs.udp_outgoing.isNoAddr()) { htcpOutgoingConn = conn; debugs(31, DBG_IMPORTANT, "Sending HTCP messages from " << htcpOutgoingConn->local); } } int htcpQuery(StoreEntry * e, HttpRequest * req, CachePeer * p) { cache_key *save_key; static char pkt[8192]; ssize_t pktlen; char vbuf[32]; HttpHeader hdr(hoRequest); Http::StateFlags flags; if (!Comm::IsConnOpen(htcpIncomingConn)) return 0; old_squid_format = p->options.htcp_oldsquid; snprintf(vbuf, sizeof(vbuf), "%d/%d", req->http_ver.major, req->http_ver.minor); htcpStuff stuff(++msg_id_counter, HTCP_TST, RR_REQUEST, 1); SBuf sb = req->method.image(); stuff.S.method = sb.c_str(); stuff.S.uri = (char *) e->url(); stuff.S.version = vbuf; HttpStateData::httpBuildRequestHeader(req, e, nullptr, &hdr, flags); MemBuf mb; mb.init(); hdr.packInto(&mb); hdr.clean(); stuff.S.req_hdrs = mb.buf; pktlen = htcpBuildPacket(pkt, sizeof(pkt), &stuff); mb.clean(); if (!pktlen) { debugs(31, 3, "htcpQuery: htcpBuildPacket() failed"); return -1; } htcpSend(pkt, (int) pktlen, p->in_addr); queried_id[stuff.msg_id % N_QUERIED_KEYS] = stuff.msg_id; save_key = queried_keys[stuff.msg_id % N_QUERIED_KEYS]; storeKeyCopy(save_key, (const cache_key *)e->key); queried_addr[stuff.msg_id % N_QUERIED_KEYS] = p->in_addr; debugs(31, 3, "htcpQuery: key (" << save_key << ") " << storeKeyText(save_key)); return 1; } /* * Send an HTCP CLR message for a specified item to a given CachePeer. */ void htcpClear(StoreEntry * e, HttpRequest * req, const HttpRequestMethod &, CachePeer * p, htcp_clr_reason reason) { static char pkt[8192]; ssize_t pktlen; char vbuf[32]; HttpHeader hdr(hoRequest); MemBuf mb; Http::StateFlags flags; if (!Comm::IsConnOpen(htcpIncomingConn)) return; old_squid_format = p->options.htcp_oldsquid; snprintf(vbuf, sizeof(vbuf), "%d/%d", req->http_ver.major, req->http_ver.minor); htcpStuff stuff(++msg_id_counter, HTCP_CLR, RR_REQUEST, 0); if (reason == HTCP_CLR_INVALIDATION) stuff.reason = 1; SBuf sb = req->method.image(); stuff.S.method = sb.c_str(); stuff.S.request = req; SBuf uri = req->effectiveRequestUri(); stuff.S.uri = uri.c_str(); stuff.S.version = vbuf; if (reason != HTCP_CLR_INVALIDATION) { HttpStateData::httpBuildRequestHeader(req, e, nullptr, &hdr, flags); mb.init(); hdr.packInto(&mb); hdr.clean(); stuff.S.req_hdrs = mb.buf; } else { stuff.S.req_hdrs = nullptr; } pktlen = htcpBuildPacket(pkt, sizeof(pkt), &stuff); if (reason != HTCP_CLR_INVALIDATION) { mb.clean(); } if (!pktlen) { debugs(31, 3, "htcpClear: htcpBuildPacket() failed"); return; } htcpSend(pkt, (int) pktlen, p->in_addr); } /* * htcpSocketShutdown only closes the 'in' socket if it is * different than the 'out' socket. */ void htcpSocketShutdown(void) { if (!Comm::IsConnOpen(htcpIncomingConn)) return; debugs(12, DBG_IMPORTANT, "Stop accepting HTCP on " << htcpIncomingConn->local); /* * Here we just unlink htcpIncomingConn because the HTCP 'in' * and 'out' sockets might be just one FD. This prevents this * function from executing repeatedly. When we are really ready to * exit or restart, main will comm_close the 'out' descriptor. */ htcpIncomingConn = nullptr; /* * Normally we only write to the outgoing HTCP socket, but * we also have a read handler there to catch messages sent * to that specific interface. During shutdown, we must * disable reading on the outgoing socket. */ /* XXX Don't we need this handler to read replies while shutting down? * I think there should be a separate handler for reading replies.. */ assert(Comm::IsConnOpen(htcpOutgoingConn)); Comm::SetSelect(htcpOutgoingConn->fd, COMM_SELECT_READ, nullptr, nullptr, 0); } void htcpClosePorts(void) { htcpSocketShutdown(); if (htcpOutgoingConn != nullptr) { debugs(12, DBG_IMPORTANT, "Stop sending HTCP from " << htcpOutgoingConn->local); htcpOutgoingConn = nullptr; } } static void htcpLogHtcp(Ip::Address &caddr, const int opcode, const LogTags_ot logcode, const char *url, AccessLogEntryPointer al) { if (!Config.onoff.log_udp) return; htcpSyncAle(al, caddr, opcode, url); assert(logcode != LOG_TAG_NONE); al->cache.code.update(logcode); accessLogLog(al, nullptr); }