2 * Copyright (C) 1996-2025 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 31 Hypertext Caching Protocol */
12 #include "AccessLogEntry.h"
14 #include "acl/FilledChecklist.h"
15 #include "base/AsyncCallbacks.h"
16 #include "base/RunnersRegistry.h"
17 #include "CachePeer.h"
18 #include "CachePeers.h"
20 #include "comm/Connection.h"
21 #include "comm/Loops.h"
22 #include "compat/xalloc.h"
23 #include "debug/Messages.h"
27 #include "http/ContentLengthInterpreter.h"
28 #include "HttpRequest.h"
29 #include "icmp/net_db.h"
31 #include "ipc/StartListening.h"
33 #include "mem/forward.h"
36 #include "SquidConfig.h"
37 #include "StatCounters.h"
39 #include "store_key_md5.h"
40 #include "StoreClient.h"
44 typedef struct _Countstr Countstr
;
46 typedef struct _htcpHeader htcpHeader
;
48 typedef struct _htcpDataHeader htcpDataHeader
;
50 typedef struct _htcpDataHeaderSquid htcpDataHeaderSquid
;
52 typedef struct _htcpAuthHeader htcpAuthHeader
;
65 struct _htcpDataHeaderSquid
{
69 unsigned int opcode
:4;
70 unsigned int response
:4;
72 unsigned int response
:4;
73 unsigned int opcode
:4;
77 unsigned int reserved
:6;
83 unsigned int reserved
:6;
89 struct _htcpDataHeader
{
113 /* RR == 0 --> F1 = RESPONSE DESIRED FLAG */
114 /* RR == 1 --> F1 = MESSAGE OVERALL FLAG */
115 /* RR == 0 --> REQUEST */
116 /* RR == 1 --> RESPONSE */
118 struct _htcpAuthHeader
{
126 class htcpSpecifier
: public CodeContext
, public StoreClient
128 MEMPROXY_CLASS(htcpSpecifier
);
131 typedef RefCount
<htcpSpecifier
> Pointer
;
134 void checkedHit(StoreEntry
*);
136 void setFrom(Ip::Address
&anIp
) { from
= anIp
; }
137 void setDataHeader(htcpDataHeader
*aDataHeader
) {
141 /* CodeContext API */
142 ScopedId
codeContextGist() const override
; // override
143 std::ostream
&detailCodeContext(std::ostream
&os
) const override
; // override
145 /* StoreClient API */
146 LogTags
*loggingTags() const override
;
147 void fillChecklist(ACLFilledChecklist
&) const override
;
150 const char *method
= nullptr;
151 const char *uri
= nullptr;
152 char *version
= nullptr;
153 char *req_hdrs
= nullptr;
154 size_t reqHdrsSz
= 0; ///< size of the req_hdrs content
155 HttpRequest::Pointer request
;
157 /// optimization: nil until needed
158 mutable AccessLogEntryPointer al
;
161 HttpRequest::Pointer checkHitRequest
;
164 htcpDataHeader
*dhdr
= nullptr;
169 MEMPROXY_CLASS(htcpDetail
);
171 char *resp_hdrs
= nullptr;
172 size_t respHdrsSz
= 0;
174 char *entity_hdrs
= nullptr;
175 size_t entityHdrsSz
= 0;
177 char *cache_hdrs
= nullptr;
178 size_t cacheHdrsSz
= 0;
184 htcpStuff(uint32_t id
, int o
, int r
, int f
) :
210 static const char *const htcpOpcodeStr
[] = {
220 * values for htcpDataHeader->response
225 OPCODE_UNIMPLEMENTED
,
226 MAJOR_VERSION_UNSUPPORTED
,
227 MINOR_VERSION_UNSUPPORTED
,
232 * values for htcpDataHeader->RR
239 static void htcpIncomingConnectionOpened(Ipc::StartListeningAnswer
&);
240 static uint32_t msg_id_counter
= 0;
242 static Comm::ConnectionPointer htcpOutgoingConn
= nullptr;
243 static Comm::ConnectionPointer htcpIncomingConn
= nullptr;
244 #define N_QUERIED_KEYS 8192
245 static uint32_t queried_id
[N_QUERIED_KEYS
];
246 static cache_key queried_keys
[N_QUERIED_KEYS
][SQUID_MD5_DIGEST_LENGTH
];
248 static Ip::Address queried_addr
[N_QUERIED_KEYS
];
250 static int old_squid_format
= 0;
252 static ssize_t
htcpBuildPacket(char *buf
, size_t buflen
, htcpStuff
* stuff
);
253 static htcpDetail
*htcpUnpackDetail(char *buf
, int sz
);
254 static ssize_t
htcpBuildAuth(char *buf
, size_t buflen
);
255 static ssize_t
htcpBuildCountstr(char *buf
, size_t buflen
, const char *s
, size_t len
);
256 static ssize_t
htcpBuildData(char *buf
, size_t buflen
, htcpStuff
* stuff
);
257 static ssize_t
htcpBuildDetail(char *buf
, size_t buflen
, htcpStuff
* stuff
);
258 static ssize_t
htcpBuildOpData(char *buf
, size_t buflen
, htcpStuff
* stuff
);
259 static ssize_t
htcpBuildSpecifier(char *buf
, size_t buflen
, htcpStuff
* stuff
);
260 static ssize_t
htcpBuildTstOpData(char *buf
, size_t buflen
, htcpStuff
* stuff
);
262 static void htcpHandleMsg(char *buf
, int sz
, Ip::Address
&from
);
264 static void htcpLogHtcp(Ip::Address
&, const int, const LogTags_ot
, const char *, AccessLogEntryPointer
);
265 static void htcpHandleTst(htcpDataHeader
*, char *buf
, int sz
, Ip::Address
&from
);
267 static void htcpRecv(int fd
, void *data
);
269 static void htcpSend(const char *buf
, int len
, Ip::Address
&to
);
271 static void htcpTstReply(htcpDataHeader
*, StoreEntry
*, htcpSpecifier
*, Ip::Address
&);
273 static void htcpHandleTstRequest(htcpDataHeader
*, char *buf
, int sz
, Ip::Address
&from
);
275 static void htcpHandleTstResponse(htcpDataHeader
*, char *, int, Ip::Address
&);
278 htcpSyncAle(AccessLogEntryPointer
&al
, const Ip::Address
&caddr
, const int opcode
, const char *url
)
281 al
= new AccessLogEntry();
282 al
->cache
.caddr
= caddr
;
283 al
->htcp
.opcode
= htcpOpcodeStr
[opcode
];
285 al
->setVirginUrlForMissingRequest(al
->url
);
286 // HTCP transactions do not wait
287 al
->cache
.start_time
= current_time
;
288 al
->cache
.trTime
.tv_sec
= 0;
289 al
->cache
.trTime
.tv_usec
= 0;
293 htcpHexdump(const char *tag
, const char *s
, int sz
)
297 debugs(31, 3, "htcpHexdump " << tag
);
298 memset(hex
, '\0', sizeof(hex
));
300 for (int i
= 0; i
< sz
; ++i
) {
302 snprintf(&hex
[k
* 3], 4, " %02x", (int) *(s
+ i
));
304 if (k
< 15 && i
< (sz
- 1))
307 debugs(31, 3, "\t" << hex
);
309 memset(hex
, '\0', sizeof(hex
));
319 parseUint16(const char * const buf
, const int sz
, uint16_t &out
, const char * const field
)
322 debugs(31, 3, "too short for " << field
);
326 memcpy(&out
, buf
, 2);
332 * STUFF FOR SENDING HTCP MESSAGES
336 htcpBuildAuth(char *buf
, size_t buflen
)
340 assert(2 == sizeof(uint16_t));
341 auth
.length
= htons(2);
343 if (buflen
< copy_sz
)
345 memcpy(buf
, &auth
, copy_sz
);
350 htcpBuildCountstr(char *buf
, size_t buflen
, const char *s
, size_t len
)
354 if (buflen
- off
< 2)
357 debugs(31, 3, "htcpBuildCountstr: LENGTH = " << len
);
359 debugs(31, 3, "htcpBuildCountstr: TEXT = {" << (s
? s
: "<NULL>") << "}");
361 uint16_t length
= htons((uint16_t) len
);
363 memcpy(buf
+ off
, &length
, 2);
367 if (buflen
- off
< len
)
371 memcpy(buf
+ off
, s
, len
);
379 htcpBuildSpecifier(char *buf
, size_t buflen
, htcpStuff
* stuff
)
383 s
= htcpBuildCountstr(buf
+ off
, buflen
- off
, stuff
->S
.method
, (stuff
->S
.method
?strlen(stuff
->S
.method
):0));
390 s
= htcpBuildCountstr(buf
+ off
, buflen
- off
, stuff
->S
.uri
, (stuff
->S
.uri
?strlen(stuff
->S
.uri
):0));
397 s
= htcpBuildCountstr(buf
+ off
, buflen
- off
, stuff
->S
.version
, (stuff
->S
.version
?strlen(stuff
->S
.version
):0));
404 s
= htcpBuildCountstr(buf
+ off
, buflen
- off
, stuff
->S
.req_hdrs
, stuff
->S
.reqHdrsSz
);
411 debugs(31, 3, "htcpBuildSpecifier: size " << off
);
417 htcpBuildDetail(char *buf
, size_t buflen
, htcpStuff
* stuff
)
421 s
= htcpBuildCountstr(buf
+ off
, buflen
- off
, stuff
->D
.resp_hdrs
, stuff
->D
.respHdrsSz
);
428 s
= htcpBuildCountstr(buf
+ off
, buflen
- off
, stuff
->D
.entity_hdrs
, stuff
->D
.entityHdrsSz
);
435 s
= htcpBuildCountstr(buf
+ off
, buflen
- off
, stuff
->D
.cache_hdrs
, stuff
->D
.cacheHdrsSz
);
446 htcpBuildTstOpData(char *buf
, size_t buflen
, htcpStuff
* stuff
)
451 debugs(31, 3, "htcpBuildTstOpData: RR_REQUEST");
452 return htcpBuildSpecifier(buf
, buflen
, stuff
);
455 debugs(31, 3, "htcpBuildTstOpData: RR_RESPONSE");
456 debugs(31, 3, "htcpBuildTstOpData: F1 = " << stuff
->f1
);
458 if (stuff
->f1
) /* cache miss */
461 return htcpBuildDetail(buf
, buflen
, stuff
);
464 fatal_dump("htcpBuildTstOpData: bad RR value");
471 htcpBuildClrOpData(char *buf
, size_t buflen
, htcpStuff
* stuff
)
473 unsigned short reason
;
477 debugs(31, 3, "htcpBuildClrOpData: RR_REQUEST");
478 reason
= htons((unsigned short)stuff
->reason
);
481 memcpy(buf
, &reason
, 2);
483 const auto s
= htcpBuildSpecifier(buf
+ 2, buflen
- 2, stuff
);
491 fatal_dump("htcpBuildClrOpData: bad RR value");
498 htcpBuildOpData(char *buf
, size_t buflen
, htcpStuff
* stuff
)
501 debugs(31, 3, "htcpBuildOpData: opcode " << htcpOpcodeStr
[stuff
->op
]);
506 off
= htcpBuildTstOpData(buf
+ off
, buflen
, stuff
);
510 off
= htcpBuildClrOpData(buf
+ off
, buflen
, stuff
);
522 htcpBuildData(char *buf
, size_t buflen
, htcpStuff
* stuff
)
526 size_t hdr_sz
= sizeof(htcpDataHeader
);
531 off
+= hdr_sz
; /* skip! */
533 op_data_sz
= htcpBuildOpData(buf
+ off
, buflen
- off
, stuff
);
540 debugs(31, 3, "htcpBuildData: hdr.length = " << off
);
542 if (!old_squid_format
) {
544 memset(&hdr
, 0, sizeof(hdr
));
545 /* convert multi-byte fields */
546 hdr
.msg_id
= htonl(stuff
->msg_id
);
547 hdr
.length
= htons(static_cast<uint16_t>(off
));
548 hdr
.opcode
= stuff
->op
;
549 hdr
.response
= stuff
->response
;
552 memcpy(buf
, &hdr
, hdr_sz
);
554 htcpDataHeaderSquid hdrSquid
;
555 memset(&hdrSquid
, 0, sizeof(hdrSquid
));
556 hdrSquid
.length
= htons(static_cast<uint16_t>(off
));
557 hdrSquid
.opcode
= stuff
->op
;
558 hdrSquid
.response
= stuff
->response
;
559 hdrSquid
.F1
= stuff
->f1
;
560 hdrSquid
.RR
= stuff
->rr
;
561 memcpy(buf
, &hdrSquid
, hdr_sz
);
564 debugs(31, 3, "htcpBuildData: size " << off
);
570 * Build an HTCP packet into buf, maximum length buflen.
571 * Returns the packet length, or zero on failure.
574 htcpBuildPacket(char *buf
, size_t buflen
, htcpStuff
* stuff
)
578 size_t hdr_sz
= sizeof(htcpHeader
);
580 /* skip the header -- we don't know the overall length */
582 if (buflen
< hdr_sz
) {
587 s
= htcpBuildData(buf
+ off
, buflen
- off
, stuff
);
594 s
= htcpBuildAuth(buf
+ off
, buflen
- off
);
601 hdr
.length
= htons((uint16_t) off
);
604 if (old_squid_format
)
609 memcpy(buf
, &hdr
, hdr_sz
);
611 debugs(31, 3, "htcpBuildPacket: size " << off
);
617 htcpSend(const char *buf
, int len
, Ip::Address
&to
)
620 htcpHexdump("htcpSend", buf
, len
);
622 if (comm_udp_sendto(htcpOutgoingConn
->fd
, to
, buf
, len
) < 0) {
624 debugs(31, 3, htcpOutgoingConn
<< " sendto: " << xstrerr(xerrno
));
626 ++statCounter
.htcp
.pkts_sent
;
630 * Unpack an HTCP SPECIFIER in place
631 * This will overwrite any following AUTH block
633 // XXX: this needs to be turned into an Htcp1::Parser inheriting from Http1::RequestParser
634 // but with different first-line and block unpacking logic.
635 static htcpSpecifier::Pointer
636 htcpUnpackSpecifier(char *buf
, int sz
)
638 static const htcpSpecifier::Pointer nil
;
639 htcpSpecifier::Pointer
s(new htcpSpecifier
);
640 HttpRequestMethod method
;
642 /* Find length of METHOD */
644 if (!parseUint16(buf
, sz
, l
, "METHOD length"))
651 debugs(31, 3, "htcpUnpackSpecifier: failed to unpack METHOD");
659 debugs(31, 6, "htcpUnpackSpecifier: METHOD (" << l
<< "/" << sz
<< ") '" << s
->method
<< "'");
661 /* Find length of URI */
662 if (!parseUint16(buf
, sz
, l
, "URI length"))
668 debugs(31, 3, "htcpUnpackSpecifier: failed to unpack URI");
672 /* Add terminating null to METHOD */
680 debugs(31, 6, "htcpUnpackSpecifier: URI (" << l
<< "/" << sz
<< ") '" << s
->uri
<< "'");
682 /* Find length of VERSION */
683 if (!parseUint16(buf
, sz
, l
, "VERSION length"))
689 debugs(31, 3, "htcpUnpackSpecifier: failed to unpack VERSION");
693 /* Add terminating null to URI */
701 debugs(31, 6, "htcpUnpackSpecifier: VERSION (" << l
<< "/" << sz
<< ") '" << s
->version
<< "'");
703 /* Find length of REQ-HDRS */
704 if (!parseUint16(buf
, sz
, l
, "REQ-HDRS length"))
710 debugs(31, 3, "htcpUnpackSpecifier: failed to unpack REQ-HDRS");
714 /* Add terminating null to URI */
723 debugs(31, 6, "htcpUnpackSpecifier: REQ-HDRS (" << l
<< "/" << sz
<< ") '" << s
->req_hdrs
<< "'");
725 debugs(31, 3, "htcpUnpackSpecifier: " << sz
<< " bytes left");
728 * Add terminating null to REQ-HDRS. This is possible because we allocated
729 * an extra byte when we received the packet. This will overwrite any following
735 method
.HttpRequestMethodXXX(s
->method
);
737 const auto mx
= MasterXaction::MakePortless
<XactionInitiator::initHtcp
>();
738 s
->request
= HttpRequest::FromUrlXXX(s
->uri
, mx
, method
== Http::METHOD_NONE
? HttpRequestMethod(Http::METHOD_GET
) : method
);
740 debugs(31, 3, "failed to create request. Invalid URI?");
748 * Unpack an HTCP DETAIL in place
749 * This will overwrite any following AUTH block
752 htcpUnpackDetail(char *buf
, int sz
)
754 auto d
= std::make_unique
<htcpDetail
>();
756 /* Find length of RESP-HDRS */
758 if (!parseUint16(buf
, sz
, l
, "RESP-HDRS length"))
765 debugs(31, 3, "htcpUnpackDetail: failed to unpack RESP_HDRS");
775 /* Find length of ENTITY-HDRS */
776 if (!parseUint16(buf
, sz
, l
, "ENTITY-HDRS length"))
782 debugs(31, 3, "htcpUnpackDetail: failed to unpack ENTITY_HDRS");
786 /* Add terminating null to RESP-HDRS */
789 /* Set ENTITY-HDRS */
792 d
->entity_hdrs
= buf
;
797 /* Find length of CACHE-HDRS */
798 if (!parseUint16(buf
, sz
, l
, "CACHE-HDRS length"))
804 debugs(31, 3, "htcpUnpackDetail: failed to unpack CACHE_HDRS");
808 /* Add terminating null to ENTITY-HDRS */
819 debugs(31, 3, "htcpUnpackDetail: " << sz
<< " bytes left");
822 * Add terminating null to CACHE-HDRS. This is possible because we allocated
823 * an extra byte when we received the packet. This will overwrite any following
832 htcpAccessAllowed(acl_access
* acl
, const htcpSpecifier::Pointer
&s
, Ip::Address
&from
)
834 /* default deny if no access list present */
838 ACLFilledChecklist
checklist(acl
, s
->request
.getRaw());
839 checklist
.src_addr
= from
;
840 checklist
.my_addr
.setNoAddr();
841 return checklist
.fastCheck().allowed();
845 htcpTstReply(htcpDataHeader
* dhdr
, StoreEntry
* e
, htcpSpecifier
* spec
, Ip::Address
&from
)
847 static char pkt
[8192];
848 HttpHeader
hdr(hoHtcpReply
);
851 htcpStuff
stuff(dhdr
->msg_id
, HTCP_TST
, RR_RESPONSE
, 0);
852 stuff
.response
= e
? 0 : 1;
853 debugs(31, 3, "htcpTstReply: response = " << stuff
.response
);
856 stuff
.S
.method
= spec
->method
;
857 stuff
.S
.request
= spec
->request
;
858 stuff
.S
.uri
= spec
->uri
;
859 stuff
.S
.version
= spec
->version
;
860 stuff
.S
.req_hdrs
= spec
->req_hdrs
;
861 stuff
.S
.reqHdrsSz
= spec
->reqHdrsSz
;
863 hdr
.putInt(Http::HdrType::AGE
, (e
->timestamp
<= squid_curtime
? (squid_curtime
- e
->timestamp
) : 0) );
865 hdr
.putInt(Http::HdrType::AGE
, 0);
869 stuff
.D
.resp_hdrs
= xstrdup(mb
.buf
);
870 stuff
.D
.respHdrsSz
= mb
.contentSize();
871 debugs(31, 3, "htcpTstReply: resp_hdrs = {" << stuff
.D
.resp_hdrs
<< "}");
875 if (e
&& e
->expires
> -1)
876 hdr
.putTime(Http::HdrType::EXPIRES
, e
->expires
);
878 if (e
&& e
->lastModified() > -1)
879 hdr
.putTime(Http::HdrType::LAST_MODIFIED
, e
->lastModified());
883 stuff
.D
.entity_hdrs
= xstrdup(mb
.buf
);
884 stuff
.D
.entityHdrsSz
= mb
.contentSize();
886 debugs(31, 3, "htcpTstReply: entity_hdrs = {" << stuff
.D
.entity_hdrs
<< "}");
892 if (const char *host
= spec
->request
->url
.host()) {
896 netdbHostData(host
, &samp
, &rtt
, &hops
);
899 char cto_buf
[SQUIDHOSTNAMELEN
+128];
900 snprintf(cto_buf
, sizeof(cto_buf
), "%s %d %f %d",
901 host
, samp
, 0.001 * rtt
, hops
);
902 hdr
.putExt("Cache-to-Origin", cto_buf
);
905 #endif /* USE_ICMP */
908 stuff
.D
.cache_hdrs
= xstrdup(mb
.buf
);
909 stuff
.D
.cacheHdrsSz
= mb
.contentSize();
910 debugs(31, 3, "htcpTstReply: cache_hdrs = {" << stuff
.D
.cache_hdrs
<< "}");
915 pktlen
= htcpBuildPacket(pkt
, sizeof(pkt
), &stuff
);
917 safe_free(stuff
.D
.resp_hdrs
);
918 stuff
.D
.respHdrsSz
= 0;
919 safe_free(stuff
.D
.entity_hdrs
);
920 stuff
.D
.entityHdrsSz
= 0;
921 safe_free(stuff
.D
.cache_hdrs
);
922 stuff
.D
.cacheHdrsSz
= 0;
925 debugs(31, 3, "htcpTstReply: htcpBuildPacket() failed");
929 htcpSend(pkt
, (int) pktlen
, from
);
934 htcpClrReply(htcpDataHeader
* dhdr
, int purgeSucceeded
, Ip::Address
&from
)
936 static char pkt
[8192];
939 /* If dhdr->F1 == 0, no response desired */
944 htcpStuff
stuff(dhdr
->msg_id
, HTCP_CLR
, RR_RESPONSE
, 0);
946 stuff
.response
= purgeSucceeded
? 0 : 2;
948 debugs(31, 3, "htcpClrReply: response = " << stuff
.response
);
950 pktlen
= htcpBuildPacket(pkt
, sizeof(pkt
), &stuff
);
953 debugs(31, 3, "htcpClrReply: htcpBuildPacket() failed");
957 htcpSend(pkt
, (int) pktlen
, from
);
961 htcpSpecifier::codeContextGist() const
964 const auto gist
= al
->codeContextGist();
970 if (const auto &mx
= request
->masterXaction
)
971 return mx
->id
.detach();
974 return ScopedId("HTCP w/o master");
978 htcpSpecifier::detailCodeContext(std::ostream
&os
) const
981 return al
->detailCodeContext(os
);
984 if (const auto &mx
= request
->masterXaction
)
985 return os
<< Debug::Extra
<< "current master transaction: " << mx
->id
;
988 // TODO: Report method, uri, and version if they have been set
993 htcpSpecifier::checkHit()
995 checkHitRequest
= request
;
997 if (!checkHitRequest
) {
998 debugs(31, 3, "htcpCheckHit: NO; failed to parse URL");
1003 if (!checkHitRequest
->parseHeader(req_hdrs
, reqHdrsSz
)) {
1004 debugs(31, 3, "htcpCheckHit: NO; failed to parse request headers");
1005 checkHitRequest
= nullptr;
1006 checkedHit(nullptr);
1010 const auto e
= storeGetPublicByRequest(checkHitRequest
.getRaw());
1011 StoreEntry
*hit
= nullptr;
1014 debugs(31, 3, "NO; public object not found");
1015 } else if (!e
->validToSend()) {
1016 debugs(31, 3, "NO; entry not valid to send" );
1017 } else if (e
->hittingRequiresCollapsing() && !startCollapsingOn(*e
, false)) {
1018 debugs(31, 3, "NO; prohibited CF hit: " << *e
);
1019 } else if (!didCollapse
&& refreshCheckHTCP(e
, checkHitRequest
.getRaw())) {
1020 debugs(31, 3, "NO; cached response is stale");
1022 debugs(31, 3, "YES!?");
1028 // TODO: StoreClients must either store/lock or abandon found entries.
1034 htcpSpecifier::loggingTags() const
1036 // calling htcpSyncAle() here would not change cache.code
1038 al
= new AccessLogEntry();
1039 return &al
->cache
.code
;
1043 htcpSpecifier::fillChecklist(ACLFilledChecklist
&checklist
) const
1045 checklist
.setRequest(request
.getRaw());
1046 htcpSyncAle(al
, from
, dhdr
->opcode
, uri
);
1051 htcpClrStoreEntry(StoreEntry
* e
)
1053 debugs(31, 4, "htcpClrStoreEntry: Clearing store for entry: " << e
->url() );
1054 e
->releaseRequest();
1058 htcpClrStore(const htcpSpecifier::Pointer
&s
)
1060 HttpRequestPointer
request(s
->request
);
1062 debugs(31, 3, "htcpClrStore: failed to parse URL");
1066 /* Parse request headers */
1067 if (!request
->parseHeader(s
->req_hdrs
, s
->reqHdrsSz
)) {
1068 debugs(31, 2, "htcpClrStore: failed to parse request headers");
1072 StoreEntry
*e
= nullptr;
1074 /* Lookup matching entries. This matches both GET and HEAD */
1075 while ((e
= storeGetPublicByRequest(request
.getRaw()))) {
1076 htcpClrStoreEntry(e
);
1081 debugs(31, 4, "htcpClrStore: Cleared " << released
<< " matching entries");
1084 debugs(31, 4, "htcpClrStore: No matching entry found");
1091 htcpHandleTst(htcpDataHeader
* hdr
, char *buf
, int sz
, Ip::Address
&from
)
1093 debugs(31, 3, "htcpHandleTst: sz = " << sz
);
1095 if (hdr
->RR
== RR_REQUEST
)
1096 htcpHandleTstRequest(hdr
, buf
, sz
, from
);
1098 htcpHandleTstResponse(hdr
, buf
, sz
, from
);
1101 HtcpReplyData::HtcpReplyData() :
1102 hit(0), hdr(hoHtcpReply
), msg_id(0), version(0.0)
1104 memset(&cto
, 0, sizeof(cto
));
1108 HtcpReplyData::parseHeader(const char *buffer
, const size_t size
)
1110 Http::ContentLengthInterpreter interpreter
;
1111 // no applyStatusCodeRules() -- HTCP replies lack cached HTTP status code
1112 return hdr
.parse(buffer
, size
, interpreter
);
1117 htcpHandleTstResponse(htcpDataHeader
* hdr
, char *buf
, int sz
, Ip::Address
&from
)
1119 HtcpReplyData htcpReply
;
1120 cache_key
*key
= nullptr;
1123 htcpDetail
*d
= nullptr;
1126 if (queried_id
[hdr
->msg_id
% N_QUERIED_KEYS
] != hdr
->msg_id
) {
1127 debugs(31, 2, "htcpHandleTstResponse: No matching query id '" <<
1128 hdr
->msg_id
<< "' (expected " <<
1129 queried_id
[hdr
->msg_id
% N_QUERIED_KEYS
] << ") from '" <<
1135 key
= queried_keys
[hdr
->msg_id
% N_QUERIED_KEYS
];
1138 debugs(31, 3, "htcpHandleTstResponse: No query key for response id '" << hdr
->msg_id
<< "' from '" << from
<< "'");
1142 peer
= &queried_addr
[hdr
->msg_id
% N_QUERIED_KEYS
];
1144 if ( *peer
!= from
|| peer
->port() != from
.port() ) {
1145 debugs(31, 3, "htcpHandleTstResponse: Unexpected response source " << from
);
1150 debugs(31, 2, "htcpHandleTstResponse: error condition, F1/MO == 1");
1154 htcpReply
.msg_id
= hdr
->msg_id
;
1155 debugs(31, 3, "htcpHandleTstResponse: msg_id = " << htcpReply
.msg_id
);
1156 htcpReply
.hit
= hdr
->response
? 0 : 1;
1159 debugs(31, 3, "htcpHandleTstResponse: MISS");
1161 debugs(31, 3, "htcpHandleTstResponse: HIT");
1162 d
= htcpUnpackDetail(buf
, sz
);
1165 debugs(31, 3, "htcpHandleTstResponse: bad DETAIL");
1169 if ((t
= d
->resp_hdrs
))
1170 htcpReply
.parseHeader(t
, d
->respHdrsSz
);
1172 if ((t
= d
->entity_hdrs
))
1173 htcpReply
.parseHeader(t
, d
->entityHdrsSz
);
1175 if ((t
= d
->cache_hdrs
))
1176 htcpReply
.parseHeader(t
, d
->cacheHdrsSz
);
1179 debugs(31, 3, "htcpHandleTstResponse: key (" << key
<< ") " << storeKeyText(key
));
1180 neighborsHtcpReply(key
, &htcpReply
, from
);
1181 htcpReply
.hdr
.clean();
1187 htcpHandleTstRequest(htcpDataHeader
* dhdr
, char *buf
, int sz
, Ip::Address
&from
)
1190 debugs(31, 3, "htcpHandleTst: nothing to do");
1197 /* buf should be a SPECIFIER */
1198 htcpSpecifier::Pointer
s(htcpUnpackSpecifier(buf
, sz
));
1201 debugs(31, 3, "htcpHandleTstRequest: htcpUnpackSpecifier failed");
1202 htcpLogHtcp(from
, dhdr
->opcode
, LOG_UDP_INVALID
, dash_str
, nullptr);
1206 s
->setDataHeader(dhdr
);
1210 debugs(31, 3, "htcpHandleTstRequest: failed to parse request");
1211 htcpLogHtcp(from
, dhdr
->opcode
, LOG_UDP_INVALID
, dash_str
, s
->al
);
1215 if (!htcpAccessAllowed(Config
.accessList
.htcp
, s
, from
)) {
1216 debugs(31, 3, "htcpHandleTstRequest: Access denied");
1217 htcpLogHtcp(from
, dhdr
->opcode
, LOG_UDP_DENIED
, s
->uri
, s
->al
);
1221 debugs(31, 2, "HTCP TST request: " << s
->method
<< " " << s
->uri
<< " " << s
->version
);
1222 debugs(31, 2, "HTCP TST headers: " << s
->req_hdrs
);
1227 htcpSpecifier::checkedHit(StoreEntry
*e
)
1230 htcpTstReply(dhdr
, e
, this, from
); /* hit */
1231 htcpLogHtcp(from
, dhdr
->opcode
, LOG_UDP_HIT
, uri
, al
);
1233 htcpTstReply(dhdr
, nullptr, nullptr, from
); /* cache miss */
1234 htcpLogHtcp(from
, dhdr
->opcode
, LOG_UDP_MISS
, uri
, al
);
1239 htcpHandleClr(htcpDataHeader
* hdr
, char *buf
, int sz
, Ip::Address
&from
)
1241 /* buf[0/1] is reserved and reason */
1243 debugs(31, 4, "too short for reserved+reason fields (sz=" << sz
<< ")");
1244 htcpLogHtcp(from
, hdr
->opcode
, LOG_UDP_INVALID
, dash_str
, nullptr);
1247 int reason
= static_cast<unsigned char>(buf
[1]) << 4;
1248 debugs(31, 2, "HTCP CLR reason: " << reason
);
1252 /* buf should be a SPECIFIER */
1255 debugs(31, 4, "nothing to do");
1256 htcpLogHtcp(from
, hdr
->opcode
, LOG_UDP_INVALID
, dash_str
, nullptr);
1260 htcpSpecifier::Pointer
s(htcpUnpackSpecifier(buf
, sz
));
1263 debugs(31, 3, "htcpUnpackSpecifier failed");
1264 htcpLogHtcp(from
, hdr
->opcode
, LOG_UDP_INVALID
, dash_str
, nullptr);
1268 s
->setDataHeader(hdr
);
1272 debugs(31, 3, "failed to parse request");
1273 htcpLogHtcp(from
, hdr
->opcode
, LOG_UDP_INVALID
, dash_str
, s
->al
);
1277 if (!htcpAccessAllowed(Config
.accessList
.htcp_clr
, s
, from
)) {
1278 debugs(31, 3, "Access denied");
1279 htcpLogHtcp(from
, hdr
->opcode
, LOG_UDP_DENIED
, s
->uri
, s
->al
);
1283 debugs(31, 2, "HTCP CLR request: " << s
->method
<< " " << s
->uri
<< " " << s
->version
);
1284 debugs(31, 2, "HTCP CLR headers: " << s
->req_hdrs
);
1286 /* Release objects from cache
1287 * analog to clientPurgeRequest in client_side.c
1290 switch (htcpClrStore(s
)) {
1293 htcpClrReply(hdr
, 1, from
); /* hit */
1294 htcpLogHtcp(from
, hdr
->opcode
, LOG_UDP_HIT
, s
->uri
, s
->al
);
1298 htcpClrReply(hdr
, 0, from
); /* miss */
1299 htcpLogHtcp(from
, hdr
->opcode
, LOG_UDP_MISS
, s
->uri
, s
->al
);
1308 * Forward a CLR request to all peers who have requested that CLRs be
1309 * forwarded to them.
1312 htcpForwardClr(char *buf
, int sz
)
1314 for (const auto &p
: CurrentCachePeers()) {
1315 if (!p
->options
.htcp
) {
1318 if (!p
->options
.htcp_forward_clr
) {
1322 htcpSend(buf
, sz
, p
->in_addr
);
1327 * Do the first pass of handling an HTCP message. This used to be two
1328 * separate functions, htcpHandle and htcpHandleData. They were merged to
1329 * allow for forwarding HTCP packets easily to other peers if desired.
1331 * This function now works out what type of message we have received and then
1332 * hands it off to other functions to break apart message-specific data.
1335 htcpHandleMsg(char *buf
, int sz
, Ip::Address
&from
)
1337 // TODO: function-scoped CodeContext::Reset(...("HTCP message from", from))
1344 if (sz
< 0 || (size_t)sz
< sizeof(htcpHeader
)) {
1345 // These are highly likely to be attack packets. Should probably get a bigger warning.
1346 debugs(31, 2, "htcpHandle: msg size less than htcpHeader size from " << from
);
1350 htcpHexdump("htcpHandle", buf
, sz
);
1351 memcpy(&htcpHdr
, buf
, sizeof(htcpHeader
));
1352 htcpHdr
.length
= ntohs(htcpHdr
.length
);
1354 if (htcpHdr
.minor
== 0)
1355 old_squid_format
= 1;
1357 old_squid_format
= 0;
1359 debugs(31, 3, "htcpHandle: htcpHdr.length = " << htcpHdr
.length
);
1360 debugs(31, 3, "htcpHandle: htcpHdr.major = " << htcpHdr
.major
);
1361 debugs(31, 3, "htcpHandle: htcpHdr.minor = " << htcpHdr
.minor
);
1363 if (sz
!= htcpHdr
.length
) {
1364 debugs(31, 3, "htcpHandle: sz/" << sz
<< " != htcpHdr.length/" <<
1365 htcpHdr
.length
<< " from " << from
);
1370 if (htcpHdr
.major
!= 0) {
1371 debugs(31, 3, "htcpHandle: Unknown major version " << htcpHdr
.major
<< " from " << from
);
1376 hbuf
= buf
+ sizeof(htcpHeader
);
1377 hsz
= sz
- sizeof(htcpHeader
);
1379 if ((size_t)hsz
< sizeof(htcpDataHeader
)) {
1380 debugs(31, 3, "htcpHandleData: msg size less than htcpDataHeader size");
1384 if (!old_squid_format
) {
1385 memcpy(&hdr
, hbuf
, sizeof(hdr
));
1387 // Old Squid format (minor==0) uses a wider struct due to bitfield layout.
1388 // Never read more than available; zero-init then copy the safe prefix.
1389 htcpDataHeaderSquid hdrSquid
;
1390 memset(&hdrSquid
, 0, sizeof(hdrSquid
));
1391 if (static_cast<size_t>(hsz
) >= sizeof(htcpDataHeaderSquid
)) {
1392 memcpy(&hdrSquid
, hbuf
, sizeof(htcpDataHeaderSquid
));
1394 // Guaranteed earlier: hsz >= sizeof(htcpDataHeader) (compact prefix).
1395 memcpy(&hdrSquid
, hbuf
, sizeof(htcpDataHeader
));
1398 hdr
.length
= hdrSquid
.length
;
1399 hdr
.opcode
= hdrSquid
.opcode
;
1400 hdr
.response
= hdrSquid
.response
;
1401 hdr
.F1
= hdrSquid
.F1
;
1402 hdr
.RR
= hdrSquid
.RR
;
1404 hdr
.msg_id
= hdrSquid
.msg_id
;
1407 hdr
.length
= ntohs(hdr
.length
);
1408 hdr
.msg_id
= ntohl(hdr
.msg_id
);
1409 debugs(31, 3, "htcpHandleData: hsz = " << hsz
);
1410 debugs(31, 3, "htcpHandleData: length = " << hdr
.length
);
1412 if (hdr
.opcode
>= HTCP_END
) {
1413 debugs(31, 3, "htcpHandleData: client " << from
<< ", opcode " << hdr
.opcode
<< " out of range");
1417 debugs(31, 3, "htcpHandleData: opcode = " << hdr
.opcode
<< " " << htcpOpcodeStr
[hdr
.opcode
]);
1418 debugs(31, 3, "htcpHandleData: response = " << hdr
.response
);
1419 debugs(31, 3, "htcpHandleData: F1 = " << hdr
.F1
);
1420 debugs(31, 3, "htcpHandleData: RR = " << hdr
.RR
);
1421 debugs(31, 3, "htcpHandleData: msg_id = " << hdr
.msg_id
);
1423 // DATA length must include at least the data header itself
1424 if (hdr
.length
< sizeof(htcpDataHeader
)) {
1425 debugs(31, 3, "invalid hdr.length " << hdr
.length
<< " (< " << sizeof(htcpDataHeader
) << ")");
1429 if (hsz
< hdr
.length
) {
1430 debugs(31, 3, "htcpHandleData: sz < hdr.length");
1435 * set sz = hdr.length so we ignore any AUTH fields following
1438 hsz
= (int) hdr
.length
;
1439 hbuf
+= sizeof(htcpDataHeader
);
1440 hsz
-= sizeof(htcpDataHeader
);
1441 debugs(31, 3, "htcpHandleData: hsz = " << hsz
);
1443 htcpHexdump("htcpHandleData", hbuf
, hsz
);
1445 switch (hdr
.opcode
) {
1447 debugs(31, 3, "HTCP NOP not implemented");
1450 htcpHandleTst(&hdr
, hbuf
, hsz
, from
);
1453 debugs(31, 3, "HTCP MON not implemented");
1456 debugs(31, 3, "HTCP SET not implemented");
1459 htcpHandleClr(&hdr
, hbuf
, hsz
, from
);
1460 htcpForwardClr(buf
, sz
);
1468 htcpRecv(int fd
, void *)
1470 static char buf
[8192];
1472 static Ip::Address from
;
1474 /* Receive up to 8191 bytes, leaving room for a null */
1476 len
= comm_udp_recvfrom(fd
, buf
, sizeof(buf
) - 1, 0, from
);
1478 debugs(31, 3, "htcpRecv: FD " << fd
<< ", " << len
<< " bytes from " << from
);
1481 ++statCounter
.htcp
.pkts_recv
;
1483 htcpHandleMsg(buf
, len
, from
);
1485 Comm::SetSelect(fd
, COMM_SELECT_READ
, htcpRecv
, nullptr, 0);
1491 if (!IamWorkerProcess())
1494 if (Config
.Port
.htcp
<= 0) {
1495 debugs(31, Important(21), "HTCP Disabled.");
1499 htcpIncomingConn
= new Comm::Connection
;
1500 htcpIncomingConn
->local
= Config
.Addrs
.udp_incoming
;
1501 htcpIncomingConn
->local
.port(Config
.Port
.htcp
);
1503 if (!Ip::EnableIpv6
&& !htcpIncomingConn
->local
.setIPv4()) {
1504 debugs(31, DBG_CRITICAL
, "ERROR: IPv6 is disabled. " << htcpIncomingConn
->local
<< " is not an IPv4 address.");
1505 fatal("HTCP port cannot be opened.");
1507 /* split-stack for now requires default IPv4-only HTCP */
1508 if (Ip::EnableIpv6
&IPV6_SPECIAL_SPLITSTACK
&& htcpIncomingConn
->local
.isAnyAddr()) {
1509 htcpIncomingConn
->local
.setIPv4();
1512 auto call
= asyncCallbackFun(31, 2, htcpIncomingConnectionOpened
);
1513 Ipc::StartListening(SOCK_DGRAM
,
1516 Ipc::fdnInHtcpSocket
, call
);
1518 if (!Config
.Addrs
.udp_outgoing
.isNoAddr()) {
1519 htcpOutgoingConn
= new Comm::Connection
;
1520 htcpOutgoingConn
->local
= Config
.Addrs
.udp_outgoing
;
1521 htcpOutgoingConn
->local
.port(Config
.Port
.htcp
);
1523 if (!Ip::EnableIpv6
&& !htcpOutgoingConn
->local
.setIPv4()) {
1524 debugs(31, DBG_CRITICAL
, "ERROR: IPv6 is disabled. " << htcpOutgoingConn
->local
<< " is not an IPv4 address.");
1525 fatal("HTCP port cannot be opened.");
1527 /* split-stack for now requires default IPv4-only HTCP */
1528 if (Ip::EnableIpv6
&IPV6_SPECIAL_SPLITSTACK
&& htcpOutgoingConn
->local
.isAnyAddr()) {
1529 htcpOutgoingConn
->local
.setIPv4();
1533 comm_open_listener(SOCK_DGRAM
, IPPROTO_UDP
, htcpOutgoingConn
, "Outgoing HTCP Socket");
1536 if (!Comm::IsConnOpen(htcpOutgoingConn
))
1537 fatal("Cannot open Outgoing HTCP Socket");
1539 Comm::SetSelect(htcpOutgoingConn
->fd
, COMM_SELECT_READ
, htcpRecv
, nullptr, 0);
1541 debugs(31, DBG_IMPORTANT
, "Sending HTCP messages from " << htcpOutgoingConn
->local
);
1547 htcpIncomingConnectionOpened(Ipc::StartListeningAnswer
&answer
)
1549 const auto &conn
= answer
.conn
;
1551 if (!Comm::IsConnOpen(conn
))
1552 fatal("Cannot open HTCP Socket");
1554 Comm::SetSelect(conn
->fd
, COMM_SELECT_READ
, htcpRecv
, nullptr, 0);
1556 debugs(31, DBG_CRITICAL
, "Accepting HTCP messages on " << conn
->local
);
1558 if (Config
.Addrs
.udp_outgoing
.isNoAddr()) {
1559 htcpOutgoingConn
= conn
;
1560 debugs(31, DBG_IMPORTANT
, "Sending HTCP messages from " << htcpOutgoingConn
->local
);
1565 htcpQuery(StoreEntry
* e
, HttpRequest
* req
, CachePeer
* p
)
1567 cache_key
*save_key
;
1568 static char pkt
[8192];
1571 HttpHeader
hdr(hoRequest
);
1572 Http::StateFlags flags
;
1574 if (!Comm::IsConnOpen(htcpIncomingConn
))
1577 old_squid_format
= p
->options
.htcp_oldsquid
;
1578 snprintf(vbuf
, sizeof(vbuf
), "%d/%d",
1579 req
->http_ver
.major
, req
->http_ver
.minor
);
1581 htcpStuff
stuff(++msg_id_counter
, HTCP_TST
, RR_REQUEST
, 1);
1582 SBuf sb
= req
->method
.image();
1583 stuff
.S
.method
= sb
.c_str();
1584 stuff
.S
.uri
= (char *) e
->url();
1585 stuff
.S
.version
= vbuf
;
1586 HttpStateData::httpBuildRequestHeader(req
, e
, nullptr, &hdr
, p
, flags
);
1591 stuff
.S
.req_hdrs
= mb
.buf
;
1592 pktlen
= htcpBuildPacket(pkt
, sizeof(pkt
), &stuff
);
1595 debugs(31, 3, "htcpQuery: htcpBuildPacket() failed");
1599 htcpSend(pkt
, (int) pktlen
, p
->in_addr
);
1601 queried_id
[stuff
.msg_id
% N_QUERIED_KEYS
] = stuff
.msg_id
;
1602 save_key
= queried_keys
[stuff
.msg_id
% N_QUERIED_KEYS
];
1603 storeKeyCopy(save_key
, (const cache_key
*)e
->key
);
1604 queried_addr
[stuff
.msg_id
% N_QUERIED_KEYS
] = p
->in_addr
;
1605 debugs(31, 3, "htcpQuery: key (" << save_key
<< ") " << storeKeyText(save_key
));
1611 * Send an HTCP CLR message for a specified item to a given CachePeer.
1614 htcpClear(StoreEntry
* e
, HttpRequest
* req
, const HttpRequestMethod
&, CachePeer
* p
, htcp_clr_reason reason
)
1616 static char pkt
[8192];
1619 HttpHeader
hdr(hoRequest
);
1621 Http::StateFlags flags
;
1623 if (!Comm::IsConnOpen(htcpIncomingConn
))
1626 old_squid_format
= p
->options
.htcp_oldsquid
;
1627 snprintf(vbuf
, sizeof(vbuf
), "%d/%d",
1628 req
->http_ver
.major
, req
->http_ver
.minor
);
1630 htcpStuff
stuff(++msg_id_counter
, HTCP_CLR
, RR_REQUEST
, 0);
1631 if (reason
== HTCP_CLR_INVALIDATION
)
1634 SBuf sb
= req
->method
.image();
1635 stuff
.S
.method
= sb
.c_str();
1636 stuff
.S
.request
= req
;
1637 SBuf uri
= req
->effectiveRequestUri();
1638 stuff
.S
.uri
= uri
.c_str();
1639 stuff
.S
.version
= vbuf
;
1640 if (reason
!= HTCP_CLR_INVALIDATION
) {
1641 HttpStateData::httpBuildRequestHeader(req
, e
, nullptr, &hdr
, p
, flags
);
1645 stuff
.S
.req_hdrs
= mb
.buf
;
1647 stuff
.S
.req_hdrs
= nullptr;
1649 pktlen
= htcpBuildPacket(pkt
, sizeof(pkt
), &stuff
);
1650 if (reason
!= HTCP_CLR_INVALIDATION
) {
1654 debugs(31, 3, "htcpClear: htcpBuildPacket() failed");
1658 htcpSend(pkt
, (int) pktlen
, p
->in_addr
);
1662 * htcpSocketShutdown only closes the 'in' socket if it is
1663 * different than the 'out' socket.
1666 htcpSocketShutdown()
1668 if (!Comm::IsConnOpen(htcpIncomingConn
))
1671 debugs(12, DBG_IMPORTANT
, "Stop accepting HTCP on " << htcpIncomingConn
->local
);
1673 * Here we just unlink htcpIncomingConn because the HTCP 'in'
1674 * and 'out' sockets might be just one FD. This prevents this
1675 * function from executing repeatedly. When we are really ready to
1676 * exit or restart, main will comm_close the 'out' descriptor.
1678 htcpIncomingConn
= nullptr;
1681 * Normally we only write to the outgoing HTCP socket, but
1682 * we also have a read handler there to catch messages sent
1683 * to that specific interface. During shutdown, we must
1684 * disable reading on the outgoing socket.
1686 /* XXX Don't we need this handler to read replies while shutting down?
1687 * I think there should be a separate handler for reading replies..
1689 assert(Comm::IsConnOpen(htcpOutgoingConn
));
1691 Comm::SetSelect(htcpOutgoingConn
->fd
, COMM_SELECT_READ
, nullptr, nullptr, 0);
1697 if (!IamWorkerProcess())
1700 htcpSocketShutdown();
1702 if (htcpOutgoingConn
!= nullptr) {
1703 debugs(12, DBG_IMPORTANT
, "Stop sending HTCP from " << htcpOutgoingConn
->local
);
1704 htcpOutgoingConn
= nullptr;
1708 class HtcpRr
: public RegisteredRunner
1711 void useConfig() override
{ htcpOpenPorts(); }
1712 void startReconfigure() override
{ htcpClosePorts(); }
1713 void syncConfig() override
{ htcpOpenPorts(); }
1714 void startShutdown() override
{ htcpClosePorts(); }
1716 DefineRunnerRegistrator(HtcpRr
);
1719 htcpLogHtcp(Ip::Address
&caddr
, const int opcode
, const LogTags_ot logcode
, const char *url
, AccessLogEntryPointer al
)
1721 if (!Config
.onoff
.log_udp
)
1724 htcpSyncAle(al
, caddr
, opcode
, url
);
1726 assert(logcode
!= LOG_TAG_NONE
);
1727 al
->cache
.code
.update(logcode
);
1729 accessLogLog(al
, nullptr);