htcp.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2025 The Squid Software Foundation and contributors
3  *
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.
7  */
8 
9 /* DEBUG: section 31 Hypertext Caching Protocol */
10 
11 #include "squid.h"
12 #include "AccessLogEntry.h"
13 #include "acl/Acl.h"
14 #include "acl/FilledChecklist.h"
15 #include "base/AsyncCallbacks.h"
16 #include "base/RunnersRegistry.h"
17 #include "CachePeer.h"
18 #include "CachePeers.h"
19 #include "comm.h"
20 #include "comm/Connection.h"
21 #include "comm/Loops.h"
22 #include "compat/xalloc.h"
23 #include "debug/Messages.h"
24 #include "globals.h"
25 #include "htcp.h"
26 #include "http.h"
28 #include "HttpRequest.h"
29 #include "icmp/net_db.h"
30 #include "ip/tools.h"
31 #include "ipc/StartListening.h"
32 #include "md5.h"
33 #include "mem/forward.h"
34 #include "MemBuf.h"
35 #include "refresh.h"
36 #include "SquidConfig.h"
37 #include "StatCounters.h"
38 #include "Store.h"
39 #include "store_key_md5.h"
40 #include "StoreClient.h"
41 #include "tools.h"
42 
43 #include <memory>
44 typedef struct _Countstr Countstr;
45 
46 typedef struct _htcpHeader htcpHeader;
47 
49 
51 
53 
54 struct _Countstr {
55  uint16_t length;
56  char *text;
57 };
58 
59 struct _htcpHeader {
60  uint16_t length;
61  u_char major;
62  u_char minor;
63 };
64 
66  uint16_t length;
67 
68 #if !WORDS_BIGENDIAN
69  unsigned int opcode:4;
70  unsigned int response:4;
71 #else
72  unsigned int response:4;
73  unsigned int opcode:4;
74 #endif
75 
76 #if !WORDS_BIGENDIAN
77  unsigned int reserved:6;
78  unsigned int F1:1;
79  unsigned int RR:1;
80 #else
81  unsigned int RR:1;
82  unsigned int F1:1;
83  unsigned int reserved:6;
84 #endif
85 
86  uint32_t msg_id;
87 };
88 
90  uint16_t length;
91 
92 #if WORDS_BIGENDIAN
93  uint8_t opcode:4;
94  uint8_t response:4;
95 #else
96  uint8_t response:4;
97  uint8_t opcode:4;
98 #endif
99 
100 #if WORDS_BIGENDIAN
101  uint8_t reserved:6;
102  uint8_t F1:1;
103  uint8_t RR:1;
104 #else
105  uint8_t RR:1;
106  uint8_t F1:1;
107  uint8_t reserved:6;
108 #endif
109 
110  uint32_t msg_id;
111 };
112 
113 /* RR == 0 --> F1 = RESPONSE DESIRED FLAG */
114 /* RR == 1 --> F1 = MESSAGE OVERALL FLAG */
115 /* RR == 0 --> REQUEST */
116 /* RR == 1 --> RESPONSE */
117 
119  uint16_t length;
120  time_t sig_time;
121  time_t sig_expire;
124 };
125 
126 class htcpSpecifier: public CodeContext, public StoreClient
127 {
129 
130 public:
132 
133  void checkHit();
134  void checkedHit(StoreEntry *);
135 
136  void setFrom(Ip::Address &anIp) { from = anIp; }
137  void setDataHeader(htcpDataHeader *aDataHeader) {
138  dhdr = aDataHeader;
139  }
140 
141  /* CodeContext API */
142  ScopedId codeContextGist() const override; // override
143  std::ostream &detailCodeContext(std::ostream &os) const override; // override
144 
145  /* StoreClient API */
146  LogTags *loggingTags() const override;
147  void fillChecklist(ACLFilledChecklist &) const override;
148 
149 public:
150  const char *method = nullptr;
151  const char *uri = nullptr;
152  char *version = nullptr;
153  char *req_hdrs = nullptr;
154  size_t reqHdrsSz = 0;
156 
159 
160 private:
162 
164  htcpDataHeader *dhdr = nullptr;
165 };
166 
168 {
170 public:
171  char *resp_hdrs = nullptr;
172  size_t respHdrsSz = 0;
173 
174  char *entity_hdrs = nullptr;
175  size_t entityHdrsSz = 0;
176 
177  char *cache_hdrs = nullptr;
178  size_t cacheHdrsSz = 0;
179 };
180 
182 {
183 public:
184  htcpStuff(uint32_t id, int o, int r, int f) :
185  op(o),
186  rr(r),
187  f1(f),
188  msg_id(id)
189  {}
190 
191  int op = 0;
192  int rr = 0;
193  int f1 = 0;
194  int response = 0;
195  int reason = 0;
196  uint32_t msg_id;
199 };
200 
201 enum {
208 };
209 
210 static const char *const htcpOpcodeStr[] = {
211  "HTCP_NOP",
212  "HTCP_TST",
213  "HTCP_MON",
214  "HTCP_SET",
215  "HTCP_CLR",
216  "HTCP_END"
217 };
218 
219 /*
220  * values for htcpDataHeader->response
221  */
222 enum {
229 };
230 
231 /*
232  * values for htcpDataHeader->RR
233  */
234 enum {
237 };
238 
240 static uint32_t msg_id_counter = 0;
241 
244 #define N_QUERIED_KEYS 8192
245 static uint32_t queried_id[N_QUERIED_KEYS];
247 
249 
250 static int old_squid_format = 0;
251 
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);
261 
262 static void htcpHandleMsg(char *buf, int sz, Ip::Address &from);
263 
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);
266 
267 static void htcpRecv(int fd, void *data);
268 
269 static void htcpSend(const char *buf, int len, Ip::Address &to);
270 
272 
273 static void htcpHandleTstRequest(htcpDataHeader *, char *buf, int sz, Ip::Address &from);
274 
275 static void htcpHandleTstResponse(htcpDataHeader *, char *, int, Ip::Address &);
276 
277 static void
278 htcpSyncAle(AccessLogEntryPointer &al, const Ip::Address &caddr, const int opcode, const char *url)
279 {
280  if (!al)
281  al = new AccessLogEntry();
282  al->cache.caddr = caddr;
283  al->htcp.opcode = htcpOpcodeStr[opcode];
284  al->url = url;
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;
290 }
291 
292 static void
293 htcpHexdump(const char *tag, const char *s, int sz)
294 {
295 #if USE_HEXDUMP
296  char hex[80];
297  debugs(31, 3, "htcpHexdump " << tag);
298  memset(hex, '\0', sizeof(hex));
299 
300  for (int i = 0; i < sz; ++i) {
301  int k = i % 16;
302  snprintf(&hex[k * 3], 4, " %02x", (int) *(s + i));
303 
304  if (k < 15 && i < (sz - 1))
305  continue;
306 
307  debugs(31, 3, "\t" << hex);
308 
309  memset(hex, '\0', sizeof(hex));
310  }
311 #else
312  (void)tag;
313  (void)s;
314  (void)sz;
315 #endif
316 }
317 
318 static bool
319 parseUint16(const char * const buf, const int sz, uint16_t &out, const char * const field)
320 {
321  if (sz < 2) {
322  debugs(31, 3, "too short for " << field);
323  return false;
324  }
325 
326  memcpy(&out, buf, 2);
327  out = ntohs(out);
328  return true;
329 }
330 
331 /*
332  * STUFF FOR SENDING HTCP MESSAGES
333  */
334 
335 static ssize_t
336 htcpBuildAuth(char *buf, size_t buflen)
337 {
338  htcpAuthHeader auth;
339  size_t copy_sz = 0;
340  assert(2 == sizeof(uint16_t));
341  auth.length = htons(2);
342  copy_sz += 2;
343  if (buflen < copy_sz)
344  return -1;
345  memcpy(buf, &auth, copy_sz);
346  return copy_sz;
347 }
348 
349 static ssize_t
350 htcpBuildCountstr(char *buf, size_t buflen, const char *s, size_t len)
351 {
352  int off = 0;
353 
354  if (buflen - off < 2)
355  return -1;
356 
357  debugs(31, 3, "htcpBuildCountstr: LENGTH = " << len);
358 
359  debugs(31, 3, "htcpBuildCountstr: TEXT = {" << (s ? s : "<NULL>") << "}");
360 
361  uint16_t length = htons((uint16_t) len);
362 
363  memcpy(buf + off, &length, 2);
364 
365  off += 2;
366 
367  if (buflen - off < len)
368  return -1;
369 
370  if (len)
371  memcpy(buf + off, s, len);
372 
373  off += len;
374 
375  return off;
376 }
377 
378 static ssize_t
379 htcpBuildSpecifier(char *buf, size_t buflen, htcpStuff * stuff)
380 {
381  ssize_t off = 0;
382  ssize_t s;
383  s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.method, (stuff->S.method?strlen(stuff->S.method):0));
384 
385  if (s < 0)
386  return s;
387 
388  off += s;
389 
390  s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.uri, (stuff->S.uri?strlen(stuff->S.uri):0));
391 
392  if (s < 0)
393  return s;
394 
395  off += s;
396 
397  s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.version, (stuff->S.version?strlen(stuff->S.version):0));
398 
399  if (s < 0)
400  return s;
401 
402  off += s;
403 
404  s = htcpBuildCountstr(buf + off, buflen - off, stuff->S.req_hdrs, stuff->S.reqHdrsSz);
405 
406  if (s < 0)
407  return s;
408 
409  off += s;
410 
411  debugs(31, 3, "htcpBuildSpecifier: size " << off);
412 
413  return off;
414 }
415 
416 static ssize_t
417 htcpBuildDetail(char *buf, size_t buflen, htcpStuff * stuff)
418 {
419  ssize_t off = 0;
420  ssize_t s;
421  s = htcpBuildCountstr(buf + off, buflen - off, stuff->D.resp_hdrs, stuff->D.respHdrsSz);
422 
423  if (s < 0)
424  return s;
425 
426  off += s;
427 
428  s = htcpBuildCountstr(buf + off, buflen - off, stuff->D.entity_hdrs, stuff->D.entityHdrsSz);
429 
430  if (s < 0)
431  return s;
432 
433  off += s;
434 
435  s = htcpBuildCountstr(buf + off, buflen - off, stuff->D.cache_hdrs, stuff->D.cacheHdrsSz);
436 
437  if (s < 0)
438  return s;
439 
440  off += s;
441 
442  return off;
443 }
444 
445 static ssize_t
446 htcpBuildTstOpData(char *buf, size_t buflen, htcpStuff * stuff)
447 {
448  switch (stuff->rr) {
449 
450  case RR_REQUEST:
451  debugs(31, 3, "htcpBuildTstOpData: RR_REQUEST");
452  return htcpBuildSpecifier(buf, buflen, stuff);
453 
454  case RR_RESPONSE:
455  debugs(31, 3, "htcpBuildTstOpData: RR_RESPONSE");
456  debugs(31, 3, "htcpBuildTstOpData: F1 = " << stuff->f1);
457 
458  if (stuff->f1) /* cache miss */
459  return 0;
460  else /* cache hit */
461  return htcpBuildDetail(buf, buflen, stuff);
462 
463  default:
464  fatal_dump("htcpBuildTstOpData: bad RR value");
465  }
466 
467  return 0;
468 }
469 
470 static ssize_t
471 htcpBuildClrOpData(char *buf, size_t buflen, htcpStuff * stuff)
472 {
473  unsigned short reason;
474 
475  switch (stuff->rr) {
476  case RR_REQUEST:
477  debugs(31, 3, "htcpBuildClrOpData: RR_REQUEST");
478  reason = htons((unsigned short)stuff->reason);
479  if (buflen < 2)
480  return -1;
481  memcpy(buf, &reason, 2);
482  {
483  const auto s = htcpBuildSpecifier(buf + 2, buflen - 2, stuff);
484  if (s < 0)
485  return s;
486  return s + 2;
487  }
488  case RR_RESPONSE:
489  break;
490  default:
491  fatal_dump("htcpBuildClrOpData: bad RR value");
492  }
493 
494  return 0;
495 }
496 
497 static ssize_t
498 htcpBuildOpData(char *buf, size_t buflen, htcpStuff * stuff)
499 {
500  ssize_t off = 0;
501  debugs(31, 3, "htcpBuildOpData: opcode " << htcpOpcodeStr[stuff->op]);
502 
503  switch (stuff->op) {
504 
505  case HTCP_TST:
506  off = htcpBuildTstOpData(buf + off, buflen, stuff);
507  break;
508 
509  case HTCP_CLR:
510  off = htcpBuildClrOpData(buf + off, buflen, stuff);
511  break;
512 
513  default:
514  assert(0);
515  break;
516  }
517 
518  return off;
519 }
520 
521 static ssize_t
522 htcpBuildData(char *buf, size_t buflen, htcpStuff * stuff)
523 {
524  ssize_t off = 0;
525  ssize_t op_data_sz;
526  size_t hdr_sz = sizeof(htcpDataHeader);
527 
528  if (buflen < hdr_sz)
529  return -1;
530 
531  off += hdr_sz; /* skip! */
532 
533  op_data_sz = htcpBuildOpData(buf + off, buflen - off, stuff);
534 
535  if (op_data_sz < 0)
536  return op_data_sz;
537 
538  off += op_data_sz;
539 
540  debugs(31, 3, "htcpBuildData: hdr.length = " << off);
541 
542  if (!old_squid_format) {
543  htcpDataHeader hdr;
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;
550  hdr.RR = stuff->rr;
551  hdr.F1 = stuff->f1;
552  memcpy(buf, &hdr, hdr_sz);
553  } else {
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);
562  }
563 
564  debugs(31, 3, "htcpBuildData: size " << off);
565 
566  return off;
567 }
568 
569 /*
570  * Build an HTCP packet into buf, maximum length buflen.
571  * Returns the packet length, or zero on failure.
572  */
573 static ssize_t
574 htcpBuildPacket(char *buf, size_t buflen, htcpStuff * stuff)
575 {
576  ssize_t s;
577  ssize_t off = 0;
578  size_t hdr_sz = sizeof(htcpHeader);
579  htcpHeader hdr;
580  /* skip the header -- we don't know the overall length */
581 
582  if (buflen < hdr_sz) {
583  return 0;
584  }
585 
586  off += hdr_sz;
587  s = htcpBuildData(buf + off, buflen - off, stuff);
588 
589  if (s < 0) {
590  return 0;
591  }
592 
593  off += s;
594  s = htcpBuildAuth(buf + off, buflen - off);
595 
596  if (s < 0) {
597  return 0;
598  }
599 
600  off += s;
601  hdr.length = htons((uint16_t) off);
602  hdr.major = 0;
603 
604  if (old_squid_format)
605  hdr.minor = 0;
606  else
607  hdr.minor = 1;
608 
609  memcpy(buf, &hdr, hdr_sz);
610 
611  debugs(31, 3, "htcpBuildPacket: size " << off);
612 
613  return off;
614 }
615 
616 static void
617 htcpSend(const char *buf, int len, Ip::Address &to)
618 {
619  debugs(31, 3, to);
620  htcpHexdump("htcpSend", buf, len);
621 
622  if (comm_udp_sendto(htcpOutgoingConn->fd, to, buf, len) < 0) {
623  int xerrno = errno;
624  debugs(31, 3, htcpOutgoingConn << " sendto: " << xstrerr(xerrno));
625  } else
627 }
628 
629 /*
630  * Unpack an HTCP SPECIFIER in place
631  * This will overwrite any following AUTH block
632  */
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.
636 htcpUnpackSpecifier(char *buf, int sz)
637 {
638  static const htcpSpecifier::Pointer nil;
640  HttpRequestMethod method;
641 
642  /* Find length of METHOD */
643  uint16_t l = 0;
644  if (!parseUint16(buf, sz, l, "METHOD length"))
645  return nil;
646 
647  sz -= 2;
648  buf += 2;
649 
650  if (l > sz) {
651  debugs(31, 3, "htcpUnpackSpecifier: failed to unpack METHOD");
652  return nil;
653  }
654 
655  /* Set METHOD */
656  s->method = buf;
657  buf += l;
658  sz -= l;
659  debugs(31, 6, "htcpUnpackSpecifier: METHOD (" << l << "/" << sz << ") '" << s->method << "'");
660 
661  /* Find length of URI */
662  if (!parseUint16(buf, sz, l, "URI length"))
663  return nil;
664 
665  sz -= 2;
666 
667  if (l > sz) {
668  debugs(31, 3, "htcpUnpackSpecifier: failed to unpack URI");
669  return nil;
670  }
671 
672  /* Add terminating null to METHOD */
673  *buf = '\0';
674  buf += 2;
675 
676  /* Set URI */
677  s->uri = buf;
678  buf += l;
679  sz -= l;
680  debugs(31, 6, "htcpUnpackSpecifier: URI (" << l << "/" << sz << ") '" << s->uri << "'");
681 
682  /* Find length of VERSION */
683  if (!parseUint16(buf, sz, l, "VERSION length"))
684  return nil;
685 
686  sz -= 2;
687 
688  if (l > sz) {
689  debugs(31, 3, "htcpUnpackSpecifier: failed to unpack VERSION");
690  return nil;
691  }
692 
693  /* Add terminating null to URI */
694  *buf = '\0';
695  buf += 2;
696 
697  /* Set VERSION */
698  s->version = buf;
699  buf += l;
700  sz -= l;
701  debugs(31, 6, "htcpUnpackSpecifier: VERSION (" << l << "/" << sz << ") '" << s->version << "'");
702 
703  /* Find length of REQ-HDRS */
704  if (!parseUint16(buf, sz, l, "REQ-HDRS length"))
705  return nil;
706 
707  sz -= 2;
708 
709  if (l > sz) {
710  debugs(31, 3, "htcpUnpackSpecifier: failed to unpack REQ-HDRS");
711  return nil;
712  }
713 
714  /* Add terminating null to URI */
715  *buf = '\0';
716  buf += 2;
717 
718  /* Set REQ-HDRS */
719  s->req_hdrs = buf;
720  buf += l;
721  sz -= l;
722  s->reqHdrsSz = l;
723  debugs(31, 6, "htcpUnpackSpecifier: REQ-HDRS (" << l << "/" << sz << ") '" << s->req_hdrs << "'");
724 
725  debugs(31, 3, "htcpUnpackSpecifier: " << sz << " bytes left");
726 
727  /*
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
730  * AUTH block.
731  */
732  *buf = '\0';
733 
734  // Parse the request
735  method.HttpRequestMethodXXX(s->method);
736 
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);
739  if (!s->request) {
740  debugs(31, 3, "failed to create request. Invalid URI?");
741  return nil;
742  }
743 
744  return s;
745 }
746 
747 /*
748  * Unpack an HTCP DETAIL in place
749  * This will overwrite any following AUTH block
750  */
751 static htcpDetail *
752 htcpUnpackDetail(char *buf, int sz)
753 {
754  auto d = std::make_unique<htcpDetail>();
755 
756  /* Find length of RESP-HDRS */
757  uint16_t l = 0;
758  if (!parseUint16(buf, sz, l, "RESP-HDRS length"))
759  return nullptr;
760 
761  sz -= 2;
762  buf += 2;
763 
764  if (l > sz) {
765  debugs(31, 3, "htcpUnpackDetail: failed to unpack RESP_HDRS");
766  return nullptr;
767  }
768 
769  /* Set RESP-HDRS */
770  d->resp_hdrs = buf;
771  buf += l;
772  d->respHdrsSz = l;
773  sz -= l;
774 
775  /* Find length of ENTITY-HDRS */
776  if (!parseUint16(buf, sz, l, "ENTITY-HDRS length"))
777  return nullptr;
778 
779  sz -= 2;
780 
781  if (l > sz) {
782  debugs(31, 3, "htcpUnpackDetail: failed to unpack ENTITY_HDRS");
783  return nullptr;
784  }
785 
786  /* Add terminating null to RESP-HDRS */
787  *buf = '\0';
788 
789  /* Set ENTITY-HDRS */
790  buf += 2;
791 
792  d->entity_hdrs = buf;
793  buf += l;
794  d->entityHdrsSz = l;
795  sz -= l;
796 
797  /* Find length of CACHE-HDRS */
798  if (!parseUint16(buf, sz, l, "CACHE-HDRS length"))
799  return nullptr;
800 
801  sz -= 2;
802 
803  if (l > sz) {
804  debugs(31, 3, "htcpUnpackDetail: failed to unpack CACHE_HDRS");
805  return nullptr;
806  }
807 
808  /* Add terminating null to ENTITY-HDRS */
809  *buf = '\0';
810 
811  /* Set CACHE-HDRS */
812  buf += 2;
813 
814  d->cache_hdrs = buf;
815  buf += l;
816  d->cacheHdrsSz = l;
817  sz -= l;
818 
819  debugs(31, 3, "htcpUnpackDetail: " << sz << " bytes left");
820 
821  /*
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
824  * AUTH block.
825  */
826  *buf = '\0';
827 
828  return d.release();
829 }
830 
831 static bool
833 {
834  /* default deny if no access list present */
835  if (!acl)
836  return false;
837 
838  ACLFilledChecklist checklist(acl, s->request.getRaw());
839  checklist.src_addr = from;
840  checklist.my_addr.setNoAddr();
841  return checklist.fastCheck().allowed();
842 }
843 
844 static void
846 {
847  static char pkt[8192];
848  HttpHeader hdr(hoHtcpReply);
849  ssize_t pktlen;
850 
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);
854 
855  if (spec) {
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;
862  if (e)
864  else
865  hdr.putInt(Http::HdrType::AGE, 0);
866  MemBuf mb;
867  mb.init();
868  hdr.packInto(&mb);
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 << "}");
872  mb.reset();
873  hdr.clean();
874 
875  if (e && e->expires > -1)
877 
878  if (e && e->lastModified() > -1)
880 
881  hdr.packInto(&mb);
882 
883  stuff.D.entity_hdrs = xstrdup(mb.buf);
884  stuff.D.entityHdrsSz = mb.contentSize();
885 
886  debugs(31, 3, "htcpTstReply: entity_hdrs = {" << stuff.D.entity_hdrs << "}");
887 
888  mb.reset();
889  hdr.clean();
890 
891 #if USE_ICMP
892  if (const char *host = spec->request->url.host()) {
893  int rtt = 0;
894  int hops = 0;
895  int samp = 0;
896  netdbHostData(host, &samp, &rtt, &hops);
897 
898  if (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);
903  }
904  }
905 #endif /* USE_ICMP */
906 
907  hdr.packInto(&mb);
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 << "}");
911  mb.clean();
912  hdr.clean();
913  }
914 
915  pktlen = htcpBuildPacket(pkt, sizeof(pkt), &stuff);
916 
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;
923 
924  if (!pktlen) {
925  debugs(31, 3, "htcpTstReply: htcpBuildPacket() failed");
926  return;
927  }
928 
929  htcpSend(pkt, (int) pktlen, from);
930 }
931 
932 static void
933 
934 htcpClrReply(htcpDataHeader * dhdr, int purgeSucceeded, Ip::Address &from)
935 {
936  static char pkt[8192];
937  ssize_t pktlen;
938 
939  /* If dhdr->F1 == 0, no response desired */
940 
941  if (dhdr->F1 == 0)
942  return;
943 
944  htcpStuff stuff(dhdr->msg_id, HTCP_CLR, RR_RESPONSE, 0);
945 
946  stuff.response = purgeSucceeded ? 0 : 2;
947 
948  debugs(31, 3, "htcpClrReply: response = " << stuff.response);
949 
950  pktlen = htcpBuildPacket(pkt, sizeof(pkt), &stuff);
951 
952  if (pktlen == 0) {
953  debugs(31, 3, "htcpClrReply: htcpBuildPacket() failed");
954  return;
955  }
956 
957  htcpSend(pkt, (int) pktlen, from);
958 }
959 
960 ScopedId
962 {
963  if (al) {
964  const auto gist = al->codeContextGist();
965  if (gist.value)
966  return gist;
967  }
968 
969  if (request) {
970  if (const auto &mx = request->masterXaction)
971  return mx->id.detach();
972  }
973 
974  return ScopedId("HTCP w/o master");
975 }
976 
977 std::ostream &
978 htcpSpecifier::detailCodeContext(std::ostream &os) const
979 {
980  if (al)
981  return al->detailCodeContext(os);
982 
983  if (request) {
984  if (const auto &mx = request->masterXaction)
985  return os << Debug::Extra << "current master transaction: " << mx->id;
986  }
987 
988  // TODO: Report method, uri, and version if they have been set
989  return os;
990 }
991 
992 void
994 {
996 
997  if (!checkHitRequest) {
998  debugs(31, 3, "htcpCheckHit: NO; failed to parse URL");
999  checkedHit(nullptr);
1000  return;
1001  }
1002 
1004  debugs(31, 3, "htcpCheckHit: NO; failed to parse request headers");
1005  checkHitRequest = nullptr;
1006  checkedHit(nullptr);
1007  return;
1008  }
1009 
1011  StoreEntry *hit = nullptr;
1012 
1013  if (!e) {
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");
1021  } else {
1022  debugs(31, 3, "YES!?");
1023  hit = e;
1024  }
1025 
1026  checkedHit(hit);
1027 
1028  // TODO: StoreClients must either store/lock or abandon found entries.
1029  //if (e)
1030  // e->abandon();
1031 }
1032 
1033 LogTags *
1035 {
1036  // calling htcpSyncAle() here would not change cache.code
1037  if (!al)
1038  al = new AccessLogEntry();
1039  return &al->cache.code;
1040 }
1041 
1042 void
1044 {
1045  checklist.setRequest(request.getRaw());
1047  checklist.al = al;
1048 }
1049 
1050 static void
1052 {
1053  debugs(31, 4, "htcpClrStoreEntry: Clearing store for entry: " << e->url() );
1054  e->releaseRequest();
1055 }
1056 
1057 static int
1059 {
1060  HttpRequestPointer request(s->request);
1061  if (!request) {
1062  debugs(31, 3, "htcpClrStore: failed to parse URL");
1063  return -1;
1064  }
1065 
1066  /* Parse request headers */
1067  if (!request->parseHeader(s->req_hdrs, s->reqHdrsSz)) {
1068  debugs(31, 2, "htcpClrStore: failed to parse request headers");
1069  return -1;
1070  }
1071 
1072  StoreEntry *e = nullptr;
1073  int released = 0;
1074  /* Lookup matching entries. This matches both GET and HEAD */
1075  while ((e = storeGetPublicByRequest(request.getRaw()))) {
1076  htcpClrStoreEntry(e);
1077  ++released;
1078  }
1079 
1080  if (released) {
1081  debugs(31, 4, "htcpClrStore: Cleared " << released << " matching entries");
1082  return 1;
1083  } else {
1084  debugs(31, 4, "htcpClrStore: No matching entry found");
1085  return 0;
1086  }
1087 }
1088 
1089 static void
1090 
1091 htcpHandleTst(htcpDataHeader * hdr, char *buf, int sz, Ip::Address &from)
1092 {
1093  debugs(31, 3, "htcpHandleTst: sz = " << sz);
1094 
1095  if (hdr->RR == RR_REQUEST)
1096  htcpHandleTstRequest(hdr, buf, sz, from);
1097  else
1098  htcpHandleTstResponse(hdr, buf, sz, from);
1099 }
1100 
1102  hit(0), hdr(hoHtcpReply), msg_id(0), version(0.0)
1103 {
1104  memset(&cto, 0, sizeof(cto));
1105 }
1106 
1107 bool
1108 HtcpReplyData::parseHeader(const char *buffer, const size_t size)
1109 {
1110  Http::ContentLengthInterpreter interpreter;
1111  // no applyStatusCodeRules() -- HTCP replies lack cached HTTP status code
1112  return hdr.parse(buffer, size, interpreter);
1113 }
1114 
1115 static void
1116 
1117 htcpHandleTstResponse(htcpDataHeader * hdr, char *buf, int sz, Ip::Address &from)
1118 {
1119  HtcpReplyData htcpReply;
1120  cache_key *key = nullptr;
1121 
1122  Ip::Address *peer;
1123  htcpDetail *d = nullptr;
1124  char *t;
1125 
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 '" <<
1130  from << "'");
1131 
1132  return;
1133  }
1134 
1135  key = queried_keys[hdr->msg_id % N_QUERIED_KEYS];
1136 
1137  if (!key) {
1138  debugs(31, 3, "htcpHandleTstResponse: No query key for response id '" << hdr->msg_id << "' from '" << from << "'");
1139  return;
1140  }
1141 
1142  peer = &queried_addr[hdr->msg_id % N_QUERIED_KEYS];
1143 
1144  if ( *peer != from || peer->port() != from.port() ) {
1145  debugs(31, 3, "htcpHandleTstResponse: Unexpected response source " << from );
1146  return;
1147  }
1148 
1149  if (hdr->F1 == 1) {
1150  debugs(31, 2, "htcpHandleTstResponse: error condition, F1/MO == 1");
1151  return;
1152  }
1153 
1154  htcpReply.msg_id = hdr->msg_id;
1155  debugs(31, 3, "htcpHandleTstResponse: msg_id = " << htcpReply.msg_id);
1156  htcpReply.hit = hdr->response ? 0 : 1;
1157 
1158  if (hdr->F1) {
1159  debugs(31, 3, "htcpHandleTstResponse: MISS");
1160  } else {
1161  debugs(31, 3, "htcpHandleTstResponse: HIT");
1162  d = htcpUnpackDetail(buf, sz);
1163 
1164  if (d == nullptr) {
1165  debugs(31, 3, "htcpHandleTstResponse: bad DETAIL");
1166  return;
1167  }
1168 
1169  if ((t = d->resp_hdrs))
1170  htcpReply.parseHeader(t, d->respHdrsSz);
1171 
1172  if ((t = d->entity_hdrs))
1173  htcpReply.parseHeader(t, d->entityHdrsSz);
1174 
1175  if ((t = d->cache_hdrs))
1176  htcpReply.parseHeader(t, d->cacheHdrsSz);
1177  }
1178 
1179  debugs(31, 3, "htcpHandleTstResponse: key (" << key << ") " << storeKeyText(key));
1180  neighborsHtcpReply(key, &htcpReply, from);
1181  htcpReply.hdr.clean();
1182 
1183  delete d;
1184 }
1185 
1186 static void
1187 htcpHandleTstRequest(htcpDataHeader * dhdr, char *buf, int sz, Ip::Address &from)
1188 {
1189  if (sz == 0) {
1190  debugs(31, 3, "htcpHandleTst: nothing to do");
1191  return;
1192  }
1193 
1194  if (dhdr->F1 == 0)
1195  return;
1196 
1197  /* buf should be a SPECIFIER */
1199 
1200  if (!s) {
1201  debugs(31, 3, "htcpHandleTstRequest: htcpUnpackSpecifier failed");
1202  htcpLogHtcp(from, dhdr->opcode, LOG_UDP_INVALID, dash_str, nullptr);
1203  return;
1204  } else {
1205  s->setFrom(from);
1206  s->setDataHeader(dhdr);
1207  }
1208 
1209  if (!s->request) {
1210  debugs(31, 3, "htcpHandleTstRequest: failed to parse request");
1211  htcpLogHtcp(from, dhdr->opcode, LOG_UDP_INVALID, dash_str, s->al);
1212  return;
1213  }
1214 
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);
1218  return;
1219  }
1220 
1221  debugs(31, 2, "HTCP TST request: " << s->method << " " << s->uri << " " << s->version);
1222  debugs(31, 2, "HTCP TST headers: " << s->req_hdrs);
1223  s->checkHit();
1224 }
1225 
1226 void
1228 {
1229  if (e) {
1230  htcpTstReply(dhdr, e, this, from); /* hit */
1232  } else {
1233  htcpTstReply(dhdr, nullptr, nullptr, from); /* cache miss */
1235  }
1236 }
1237 
1238 static void
1239 htcpHandleClr(htcpDataHeader * hdr, char *buf, int sz, Ip::Address &from)
1240 {
1241  /* buf[0/1] is reserved and reason */
1242  if (sz < 2) {
1243  debugs(31, 4, "too short for reserved+reason fields (sz=" << sz << ")");
1244  htcpLogHtcp(from, hdr->opcode, LOG_UDP_INVALID, dash_str, nullptr);
1245  return;
1246  }
1247  int reason = static_cast<unsigned char>(buf[1]) << 4;
1248  debugs(31, 2, "HTCP CLR reason: " << reason);
1249  buf += 2;
1250  sz -= 2;
1251 
1252  /* buf should be a SPECIFIER */
1253 
1254  if (sz == 0) {
1255  debugs(31, 4, "nothing to do");
1256  htcpLogHtcp(from, hdr->opcode, LOG_UDP_INVALID, dash_str, nullptr);
1257  return;
1258  }
1259 
1261 
1262  if (!s) {
1263  debugs(31, 3, "htcpUnpackSpecifier failed");
1264  htcpLogHtcp(from, hdr->opcode, LOG_UDP_INVALID, dash_str, nullptr);
1265  return;
1266  } else {
1267  s->setFrom(from);
1268  s->setDataHeader(hdr);
1269  }
1270 
1271  if (!s->request) {
1272  debugs(31, 3, "failed to parse request");
1273  htcpLogHtcp(from, hdr->opcode, LOG_UDP_INVALID, dash_str, s->al);
1274  return;
1275  }
1276 
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);
1280  return;
1281  }
1282 
1283  debugs(31, 2, "HTCP CLR request: " << s->method << " " << s->uri << " " << s->version);
1284  debugs(31, 2, "HTCP CLR headers: " << s->req_hdrs);
1285 
1286  /* Release objects from cache
1287  * analog to clientPurgeRequest in client_side.c
1288  */
1289 
1290  switch (htcpClrStore(s)) {
1291 
1292  case 1:
1293  htcpClrReply(hdr, 1, from); /* hit */
1294  htcpLogHtcp(from, hdr->opcode, LOG_UDP_HIT, s->uri, s->al);
1295  break;
1296 
1297  case 0:
1298  htcpClrReply(hdr, 0, from); /* miss */
1299  htcpLogHtcp(from, hdr->opcode, LOG_UDP_MISS, s->uri, s->al);
1300  break;
1301 
1302  default:
1303  break;
1304  }
1305 }
1306 
1307 /*
1308  * Forward a CLR request to all peers who have requested that CLRs be
1309  * forwarded to them.
1310  */
1311 static void
1312 htcpForwardClr(char *buf, int sz)
1313 {
1314  for (const auto &p: CurrentCachePeers()) {
1315  if (!p->options.htcp) {
1316  continue;
1317  }
1318  if (!p->options.htcp_forward_clr) {
1319  continue;
1320  }
1321 
1322  htcpSend(buf, sz, p->in_addr);
1323  }
1324 }
1325 
1326 /*
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.
1330  *
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.
1333  */
1334 static void
1335 htcpHandleMsg(char *buf, int sz, Ip::Address &from)
1336 {
1337  // TODO: function-scoped CodeContext::Reset(...("HTCP message from", from))
1338 
1339  htcpHeader htcpHdr;
1340  htcpDataHeader hdr;
1341  char *hbuf;
1342  int hsz;
1343 
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);
1347  return;
1348  }
1349 
1350  htcpHexdump("htcpHandle", buf, sz);
1351  memcpy(&htcpHdr, buf, sizeof(htcpHeader));
1352  htcpHdr.length = ntohs(htcpHdr.length);
1353 
1354  if (htcpHdr.minor == 0)
1355  old_squid_format = 1;
1356  else
1357  old_squid_format = 0;
1358 
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);
1362 
1363  if (sz != htcpHdr.length) {
1364  debugs(31, 3, "htcpHandle: sz/" << sz << " != htcpHdr.length/" <<
1365  htcpHdr.length << " from " << from );
1366 
1367  return;
1368  }
1369 
1370  if (htcpHdr.major != 0) {
1371  debugs(31, 3, "htcpHandle: Unknown major version " << htcpHdr.major << " from " << from );
1372 
1373  return;
1374  }
1375 
1376  hbuf = buf + sizeof(htcpHeader);
1377  hsz = sz - sizeof(htcpHeader);
1378 
1379  if ((size_t)hsz < sizeof(htcpDataHeader)) {
1380  debugs(31, 3, "htcpHandleData: msg size less than htcpDataHeader size");
1381  return;
1382  }
1383 
1384  if (!old_squid_format) {
1385  memcpy(&hdr, hbuf, sizeof(hdr));
1386  } else {
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));
1393  } else {
1394  // Guaranteed earlier: hsz >= sizeof(htcpDataHeader) (compact prefix).
1395  memcpy(&hdrSquid, hbuf, sizeof(htcpDataHeader));
1396  }
1397 
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;
1403  hdr.reserved = 0;
1404  hdr.msg_id = hdrSquid.msg_id;
1405  }
1406 
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);
1411 
1412  if (hdr.opcode >= HTCP_END) {
1413  debugs(31, 3, "htcpHandleData: client " << from << ", opcode " << hdr.opcode << " out of range");
1414  return;
1415  }
1416 
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);
1422 
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) << ")");
1426  return;
1427  }
1428 
1429  if (hsz < hdr.length) {
1430  debugs(31, 3, "htcpHandleData: sz < hdr.length");
1431  return;
1432  }
1433 
1434  /*
1435  * set sz = hdr.length so we ignore any AUTH fields following
1436  * the DATA.
1437  */
1438  hsz = (int) hdr.length;
1439  hbuf += sizeof(htcpDataHeader);
1440  hsz -= sizeof(htcpDataHeader);
1441  debugs(31, 3, "htcpHandleData: hsz = " << hsz);
1442 
1443  htcpHexdump("htcpHandleData", hbuf, hsz);
1444 
1445  switch (hdr.opcode) {
1446  case HTCP_NOP:
1447  debugs(31, 3, "HTCP NOP not implemented");
1448  break;
1449  case HTCP_TST:
1450  htcpHandleTst(&hdr, hbuf, hsz, from);
1451  break;
1452  case HTCP_MON:
1453  debugs(31, 3, "HTCP MON not implemented");
1454  break;
1455  case HTCP_SET:
1456  debugs(31, 3, "HTCP SET not implemented");
1457  break;
1458  case HTCP_CLR:
1459  htcpHandleClr(&hdr, hbuf, hsz, from);
1460  htcpForwardClr(buf, sz);
1461  break;
1462  default:
1463  break;
1464  }
1465 }
1466 
1467 static void
1468 htcpRecv(int fd, void *)
1469 {
1470  static char buf[8192];
1471  int len;
1472  static Ip::Address from;
1473 
1474  /* Receive up to 8191 bytes, leaving room for a null */
1475 
1476  len = comm_udp_recvfrom(fd, buf, sizeof(buf) - 1, 0, from);
1477 
1478  debugs(31, 3, "htcpRecv: FD " << fd << ", " << len << " bytes from " << from );
1479 
1480  if (len)
1482 
1483  htcpHandleMsg(buf, len, from);
1484 
1485  Comm::SetSelect(fd, COMM_SELECT_READ, htcpRecv, nullptr, 0);
1486 }
1487 
1488 static void
1490 {
1491  if (!IamWorkerProcess())
1492  return;
1493 
1494  if (Config.Port.htcp <= 0) {
1495  debugs(31, Important(21), "HTCP Disabled.");
1496  return;
1497  }
1498 
1501  htcpIncomingConn->local.port(Config.Port.htcp);
1502 
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.");
1506  }
1507  /* split-stack for now requires default IPv4-only HTCP */
1508  if (Ip::EnableIpv6&IPV6_SPECIAL_SPLITSTACK && htcpIncomingConn->local.isAnyAddr()) {
1509  htcpIncomingConn->local.setIPv4();
1510  }
1511 
1512  auto call = asyncCallbackFun(31, 2, htcpIncomingConnectionOpened);
1513  Ipc::StartListening(SOCK_DGRAM,
1514  IPPROTO_UDP,
1516  Ipc::fdnInHtcpSocket, call);
1517 
1518  if (!Config.Addrs.udp_outgoing.isNoAddr()) {
1521  htcpOutgoingConn->local.port(Config.Port.htcp);
1522 
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.");
1526  }
1527  /* split-stack for now requires default IPv4-only HTCP */
1528  if (Ip::EnableIpv6&IPV6_SPECIAL_SPLITSTACK && htcpOutgoingConn->local.isAnyAddr()) {
1529  htcpOutgoingConn->local.setIPv4();
1530  }
1531 
1532  enter_suid();
1533  comm_open_listener(SOCK_DGRAM, IPPROTO_UDP, htcpOutgoingConn, "Outgoing HTCP Socket");
1534  leave_suid();
1535 
1537  fatal("Cannot open Outgoing HTCP Socket");
1538 
1540 
1541  debugs(31, DBG_IMPORTANT, "Sending HTCP messages from " << htcpOutgoingConn->local);
1542  }
1543 
1544 }
1545 
1546 static void
1548 {
1549  const auto &conn = answer.conn;
1550 
1551  if (!Comm::IsConnOpen(conn))
1552  fatal("Cannot open HTCP Socket");
1553 
1554  Comm::SetSelect(conn->fd, COMM_SELECT_READ, htcpRecv, nullptr, 0);
1555 
1556  debugs(31, DBG_CRITICAL, "Accepting HTCP messages on " << conn->local);
1557 
1559  htcpOutgoingConn = conn;
1560  debugs(31, DBG_IMPORTANT, "Sending HTCP messages from " << htcpOutgoingConn->local);
1561  }
1562 }
1563 
1564 int
1566 {
1567  cache_key *save_key;
1568  static char pkt[8192];
1569  ssize_t pktlen;
1570  char vbuf[32];
1571  HttpHeader hdr(hoRequest);
1572  Http::StateFlags flags;
1573 
1575  return 0;
1576 
1578  snprintf(vbuf, sizeof(vbuf), "%d/%d",
1579  req->http_ver.major, req->http_ver.minor);
1580 
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);
1587  MemBuf mb;
1588  mb.init();
1589  hdr.packInto(&mb);
1590  hdr.clean();
1591  stuff.S.req_hdrs = mb.buf;
1592  pktlen = htcpBuildPacket(pkt, sizeof(pkt), &stuff);
1593  mb.clean();
1594  if (!pktlen) {
1595  debugs(31, 3, "htcpQuery: htcpBuildPacket() failed");
1596  return -1;
1597  }
1598 
1599  htcpSend(pkt, (int) pktlen, p->in_addr);
1600 
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));
1606 
1607  return 1;
1608 }
1609 
1610 /*
1611  * Send an HTCP CLR message for a specified item to a given CachePeer.
1612  */
1613 void
1615 {
1616  static char pkt[8192];
1617  ssize_t pktlen;
1618  char vbuf[32];
1619  HttpHeader hdr(hoRequest);
1620  MemBuf mb;
1621  Http::StateFlags flags;
1622 
1624  return;
1625 
1627  snprintf(vbuf, sizeof(vbuf), "%d/%d",
1628  req->http_ver.major, req->http_ver.minor);
1629 
1631  if (reason == HTCP_CLR_INVALIDATION)
1632  stuff.reason = 1;
1633 
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);
1642  mb.init();
1643  hdr.packInto(&mb);
1644  hdr.clean();
1645  stuff.S.req_hdrs = mb.buf;
1646  } else {
1647  stuff.S.req_hdrs = nullptr;
1648  }
1649  pktlen = htcpBuildPacket(pkt, sizeof(pkt), &stuff);
1650  if (reason != HTCP_CLR_INVALIDATION) {
1651  mb.clean();
1652  }
1653  if (!pktlen) {
1654  debugs(31, 3, "htcpClear: htcpBuildPacket() failed");
1655  return;
1656  }
1657 
1658  htcpSend(pkt, (int) pktlen, p->in_addr);
1659 }
1660 
1661 /*
1662  * htcpSocketShutdown only closes the 'in' socket if it is
1663  * different than the 'out' socket.
1664  */
1665 static void
1667 {
1669  return;
1670 
1671  debugs(12, DBG_IMPORTANT, "Stop accepting HTCP on " << htcpIncomingConn->local);
1672  /*
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.
1677  */
1678  htcpIncomingConn = nullptr;
1679 
1680  /*
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.
1685  */
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..
1688  */
1690 
1691  Comm::SetSelect(htcpOutgoingConn->fd, COMM_SELECT_READ, nullptr, nullptr, 0);
1692 }
1693 
1694 static void
1696 {
1697  if (!IamWorkerProcess())
1698  return;
1699 
1701 
1702  if (htcpOutgoingConn != nullptr) {
1703  debugs(12, DBG_IMPORTANT, "Stop sending HTCP from " << htcpOutgoingConn->local);
1704  htcpOutgoingConn = nullptr;
1705  }
1706 }
1707 
1708 class HtcpRr : public RegisteredRunner
1709 {
1710 public:
1711  void useConfig() override { htcpOpenPorts(); }
1712  void startReconfigure() override { htcpClosePorts(); }
1713  void syncConfig() override { htcpOpenPorts(); }
1714  void startShutdown() override { htcpClosePorts(); }
1715 };
1717 
1718 static void
1719 htcpLogHtcp(Ip::Address &caddr, const int opcode, const LogTags_ot logcode, const char *url, AccessLogEntryPointer al)
1720 {
1721  if (!Config.onoff.log_udp)
1722  return;
1723 
1724  htcpSyncAle(al, caddr, opcode, url);
1725 
1726  assert(logcode != LOG_TAG_NONE);
1727  al->cache.code.update(logcode);
1728 
1729  accessLogLog(al, nullptr);
1730 }
1731 
@ AUTH_REQUIRED
Definition: htcp.cc:223
void fatal(const char *message)
Definition: fatal.cc:28
const char * xstrerr(int error)
Definition: xstrerror.cc:83
char * resp_hdrs
Definition: htcp.cc:171
static ssize_t htcpBuildDetail(char *buf, size_t buflen, htcpStuff *stuff)
Definition: htcp.cc:417
size_t respHdrsSz
Definition: htcp.cc:172
@ MINOR_VERSION_UNSUPPORTED
Definition: htcp.cc:227
char * buf
Definition: MemBuf.h:134
size_t reqHdrsSz
size of the req_hdrs content
Definition: htcp.cc:154
uint16_t length
Definition: htcp.cc:55
unsigned int major
major version number
std::ostream & detailCodeContext(std::ostream &os) const override
appends human-friendly context description line(s) to a cache.log record
Definition: htcp.cc:978
static ssize_t htcpBuildClrOpData(char *buf, size_t buflen, htcpStuff *stuff)
Definition: htcp.cc:471
unsigned int RR
Definition: htcp.cc:79
time_t timestamp
Definition: Store.h:223
AnyP::ProtocolVersion http_ver
Definition: Message.h:72
#define DBG_CRITICAL
Definition: Stream.h:37
char * version
Definition: htcp.cc:152
AnyP::Uri url
the request URI
Definition: HttpRequest.h:115
@ HTCP_MON
Definition: htcp.cc:204
bool parseHeader(Http1::Parser &hp)
Definition: HttpRequest.cc:708
static void htcpSend(const char *buf, int len, Ip::Address &to)
Definition: htcp.cc:617
Ip::Address udp_incoming
Definition: SquidConfig.h:235
void releaseRequest(const bool shareable=false)
Definition: store.cc:458
static ssize_t htcpBuildData(char *buf, size_t buflen, htcpStuff *stuff)
Definition: htcp.cc:522
Ip::Address in_addr
Definition: CachePeer.h:70
Ip::Address src_addr
unsigned char cache_key
Store key.
Definition: forward.h:29
@ OPCODE_UNIMPLEMENTED
Definition: htcp.cc:225
AccessLogEntryPointer al
optimization: nil until needed
Definition: htcp.cc:158
static bool parseUint16(const char *const buf, const int sz, uint16_t &out, const char *const field)
Definition: htcp.cc:319
const char * uri
Definition: htcp.cc:151
htcpStuff(uint32_t id, int o, int r, int f)
Definition: htcp.cc:184
const char * url() const
Definition: store.cc:1566
RefCount< htcpSpecifier > Pointer
Definition: htcp.cc:131
int comm_udp_sendto(int fd, const Ip::Address &to_addr, const void *buf, int len)
Definition: comm.cc:911
unsigned int minor
minor version number
@ AUTH_FAILURE
Definition: htcp.cc:224
struct SquidConfig::@78 Port
int parse(const char *header_start, size_t len, Http::ContentLengthInterpreter &interpreter)
Definition: HttpHeader.cc:482
struct SquidConfig::@85 Addrs
void init(mb_size_t szInit, mb_size_t szMax)
Definition: MemBuf.cc:93
static uint32_t queried_id[N_QUERIED_KEYS]
Definition: htcp.cc:245
Definition: SBuf.h:93
uint16_t length
Definition: htcp.cc:119
time_t sig_expire
Definition: htcp.cc:121
Ip::Address from
Definition: htcp.cc:163
#define xstrdup
static ssize_t htcpBuildAuth(char *buf, size_t buflen)
Definition: htcp.cc:336
static ssize_t htcpBuildOpData(char *buf, size_t buflen, htcpStuff *stuff)
Definition: htcp.cc:498
int refreshCheckHTCP(const StoreEntry *entry, HttpRequest *request)
Definition: refresh.cc:604
C * getRaw() const
Definition: RefCount.h:89
AccessLogEntry::Pointer al
info for the future access.log, and external ACL
const SBuf & image() const
bool IsConnOpen(const Comm::ConnectionPointer &conn)
Definition: Connection.cc:27
@ INVALID_OPCODE
Definition: htcp.cc:228
time_t expires
Definition: Store.h:225
void accessLogLog(const AccessLogEntryPointer &, ACLChecklist *)
Definition: access_log.cc:136
bool IamWorkerProcess()
whether the current process handles HTTP transactions and such
Definition: stub_tools.cc:47
struct CachePeer::@20 options
unsigned int opcode
Definition: htcp.cc:69
acl_access * htcp_clr
Definition: SquidConfig.h:384
@ HTCP_TST
Definition: htcp.cc:203
int hit
Definition: htcp.h:30
char * entity_hdrs
Definition: htcp.cc:174
static void htcpRecv(int fd, void *data)
Definition: htcp.cc:1468
StoreEntry * storeGetPublicByRequest(HttpRequest *req, const KeyScope keyScope)
Definition: store.cc:516
void htcpClear(StoreEntry *e, HttpRequest *req, const HttpRequestMethod &, CachePeer *p, htcp_clr_reason reason)
Definition: htcp.cc:1614
uint8_t reserved
Definition: htcp.cc:107
HtcpReplyData()
Definition: htcp.cc:1101
void neighborsHtcpReply(const cache_key *, HtcpReplyData *, const Ip::Address &)
Definition: neighbors.cc:1601
uint16_t length
Definition: htcp.cc:90
static Comm::ConnectionPointer htcpOutgoingConn
Definition: htcp.cc:242
@ HTCP_END
Definition: htcp.cc:207
bool parseHeader(const char *buffer, const size_t size)
parses request header from the buffer
Definition: htcp.cc:1108
void comm_open_listener(int sock_type, int proto, Comm::ConnectionPointer &conn, const char *note)
Definition: comm.cc:259
uint16_t length
Definition: htcp.cc:66
static void htcpClrReply(htcpDataHeader *dhdr, int purgeSucceeded, Ip::Address &from)
Definition: htcp.cc:934
int response
Definition: htcp.cc:194
StartListening() result.
a storeGetPublic*() caller
Definition: StoreClient.h:40
u_char minor
Definition: htcp.cc:62
@ HTCP_CLR_INVALIDATION
Definition: enums.h:236
uint8_t response
Definition: htcp.cc:96
void leave_suid(void)
Definition: tools.cc:560
void StartListening(int sock_type, int proto, const Comm::ConnectionPointer &listenConn, FdNoteId, StartListeningCallback &)
#define SQUID_MD5_DIGEST_LENGTH
Definition: md5.h:66
static void httpBuildRequestHeader(HttpRequest *request, StoreEntry *entry, const AccessLogEntryPointer &al, HttpHeader *hdr_out, const CachePeer *peer, const Http::StateFlags &flags)
Definition: http.cc:1909
@ HTCP_SET
Definition: htcp.cc:205
unsigned int response
Definition: htcp.cc:70
mb_size_t contentSize() const
available data size
Definition: MemBuf.h:47
int size
Definition: ModDevPoll.cc:70
struct timeval current_time
the current UNIX time in timeval {seconds, microseconds} format
Definition: gadgets.cc:18
static void htcpHandleClr(htcpDataHeader *hdr, char *buf, int sz, Ip::Address &from)
Definition: htcp.cc:1239
Comm::ConnectionPointer conn
opened listening socket
@ LOG_UDP_HIT
Definition: LogTags.h:60
void checkHit()
Definition: htcp.cc:993
static Ip::Address queried_addr[N_QUERIED_KEYS]
Definition: htcp.cc:248
static void htcpHandleMsg(char *buf, int sz, Ip::Address &from)
Definition: htcp.cc:1335
void setNoAddr()
Definition: Address.cc:312
static ssize_t htcpBuildCountstr(char *buf, size_t buflen, const char *s, size_t len)
Definition: htcp.cc:350
static int old_squid_format
Definition: htcp.cc:250
const Acl::Answer & fastCheck()
Definition: Checklist.cc:298
size_t cacheHdrsSz
Definition: htcp.cc:178
@ LOG_TAG_NONE
Definition: LogTags.h:41
u_char major
Definition: htcp.cc:61
@ HTCP_CLR
Definition: htcp.cc:206
static Comm::ConnectionPointer htcpIncomingConn
Definition: htcp.cc:243
unsigned short port() const
Definition: Address.cc:790
Definition: MemBuf.h:23
static void htcpHexdump(const char *tag, const char *s, int sz)
Definition: htcp.cc:293
int reason
Definition: htcp.cc:195
void clean()
Definition: MemBuf.cc:110
MEMPROXY_CLASS(htcpSpecifier)
uint32_t msg_id
Definition: htcp.cc:196
Ip::Address udp_outgoing
Definition: SquidConfig.h:236
static ssize_t htcpBuildPacket(char *buf, size_t buflen, htcpStuff *stuff)
Definition: htcp.cc:574
HttpHeader hdr
Definition: htcp.h:31
void putExt(const char *name, const char *value)
Definition: HttpHeader.cc:1208
static void htcpClosePorts()
Definition: htcp.cc:1695
MEMPROXY_CLASS(htcpDetail)
char * req_hdrs
Definition: htcp.cc:153
#define safe_free(x)
Definition: xalloc.h:73
uint8_t F1
Definition: htcp.cc:106
MasterXaction::Pointer masterXaction
the master transaction this request belongs to. Never nil.
Definition: HttpRequest.h:236
void putTime(Http::HdrType id, time_t htime)
Definition: HttpHeader.cc:1119
cache_key * storeKeyCopy(cache_key *dst, const cache_key *src)
const char * dash_str
@ LOG_UDP_INVALID
Definition: LogTags.h:63
#define assert(EX)
Definition: assert.h:17
struct HtcpReplyData::cto_t cto
static void htcpLogHtcp(Ip::Address &, const int, const LogTags_ot, const char *, AccessLogEntryPointer)
Definition: htcp.cc:1719
char * text
Definition: htcp.cc:56
SSL Connection
Definition: Session.h:49
static void htcpIncomingConnectionOpened(Ipc::StartListeningAnswer &)
Definition: htcp.cc:1547
class AccessLogEntry::CacheDetails cache
#define IPV6_SPECIAL_SPLITSTACK
Definition: tools.h:22
static htcpDetail * htcpUnpackDetail(char *buf, int sz)
Definition: htcp.cc:752
void startShutdown() override
Definition: htcp.cc:1714
static int version
#define COMM_SELECT_READ
Definition: defines.h:24
void HttpRequestMethodXXX(char const *)
static void htcpHandleTst(htcpDataHeader *, char *buf, int sz, Ip::Address &from)
Definition: htcp.cc:1091
void packInto(Packable *p, bool mask_sensitive_info=false) const
Definition: HttpHeader.cc:672
int f1
Definition: htcp.cc:193
struct StatCounters::@107 htcp
htcpSpecifier S
Definition: htcp.cc:197
HttpRequest::Pointer checkHitRequest
Definition: htcp.cc:161
const char * c_str()
Definition: SBuf.cc:516
HttpRequest::Pointer request
Definition: htcp.cc:155
void fatal_dump(const char *message)
Definition: fatal.cc:78
time_t squid_curtime
Definition: stub_libtime.cc:20
static std::ostream & Extra(std::ostream &)
Definition: debug.cc:1316
bool isNoAddr() const
Definition: Address.cc:304
static void htcpForwardClr(char *buf, int sz)
Definition: htcp.cc:1312
void useConfig() override
Definition: htcp.cc:1711
uint32_t msg_id
Definition: htcp.h:32
htcpDataHeader * dhdr
Definition: htcp.cc:164
static void htcpHandleTstRequest(htcpDataHeader *, char *buf, int sz, Ip::Address &from)
Definition: htcp.cc:1187
static ssize_t htcpBuildTstOpData(char *buf, size_t buflen, htcpStuff *stuff)
Definition: htcp.cc:446
void syncConfig() override
Definition: htcp.cc:1713
@ hoHtcpReply
Definition: HttpHeader.h:34
bool startCollapsingOn(const StoreEntry &, const bool doingRevalidation) const
Definition: store_client.cc:66
std::ostream & detailCodeContext(std::ostream &os) const override
appends human-friendly context description line(s) to a cache.log record
htcpDetail D
Definition: htcp.cc:198
bool didCollapse
whether startCollapsingOn() was called and returned true
Definition: StoreClient.h:64
LogTags * loggingTags() const override
Definition: htcp.cc:1034
ScopedId codeContextGist() const override
Definition: htcp.cc:961
char * cache_hdrs
Definition: htcp.cc:177
void putInt(Http::HdrType id, int number)
Definition: HttpHeader.cc:1101
HttpRequestMethod method
Definition: HttpRequest.h:114
@ RR_REQUEST
Definition: htcp.cc:235
void fillChecklist(ACLFilledChecklist &) const override
configure the given checklist (to reflect the current transaction state)
Definition: htcp.cc:1043
bool allowed() const
Definition: Acl.h:82
int htcpQuery(StoreEntry *e, HttpRequest *req, CachePeer *p)
Definition: htcp.cc:1565
static const char *const htcpOpcodeStr[]
Definition: htcp.cc:210
size_t entityHdrsSz
Definition: htcp.cc:175
bool htcp_oldsquid
Definition: CachePeer.h:120
void checkedHit(StoreEntry *)
Definition: htcp.cc:1227
void setFrom(Ip::Address &anIp)
Definition: htcp.cc:136
static void htcpSocketShutdown()
Definition: htcp.cc:1666
Countstr key_name
Definition: htcp.cc:122
void SetSelect(int, unsigned int, PF *, void *, time_t)
Mark an FD to be watched for its IO status.
Definition: ModDevPoll.cc:220
#define N_QUERIED_KEYS
Definition: htcp.cc:244
int op
Definition: htcp.cc:191
struct _htcpHeader htcpHeader
Definition: htcp.cc:46
void setRequest(HttpRequest *)
configure client request-related fields for the first time
struct SquidConfig::@91 accessList
static void htcpSyncAle(AccessLogEntryPointer &al, const Ip::Address &caddr, const int opcode, const char *url)
Definition: htcp.cc:278
@ METHOD_NONE
Definition: MethodType.h:22
uint32_t msg_id
Definition: htcp.cc:110
static HttpRequest * FromUrlXXX(const char *url, const MasterXaction::Pointer &, const HttpRequestMethod &method=Http::METHOD_GET)
Definition: HttpRequest.cc:525
#define Important(id)
Definition: Messages.h:93
@ fdnInHtcpSocket
Definition: FdNotes.h:24
void enter_suid(void)
Definition: tools.cc:624
static int htcpClrStore(const htcpSpecifier::Pointer &s)
Definition: htcp.cc:1058
LogTags_ot
Definition: LogTags.h:40
void setDataHeader(htcpDataHeader *aDataHeader)
Definition: htcp.cc:137
#define DBG_IMPORTANT
Definition: Stream.h:38
const char * storeKeyText(const cache_key *key)
unsigned int F1
Definition: htcp.cc:78
static cache_key queried_keys[N_QUERIED_KEYS][SQUID_MD5_DIGEST_LENGTH]
Definition: htcp.cc:246
DefineRunnerRegistrator(HtcpRr)
@ MAJOR_VERSION_UNSUPPORTED
Definition: htcp.cc:226
void reset()
Definition: MemBuf.cc:129
static void htcpClrStoreEntry(StoreEntry *e)
Definition: htcp.cc:1051
ScopedId codeContextGist() const override
Countstr signature
Definition: htcp.cc:123
time_t sig_time
Definition: htcp.cc:120
int comm_udp_recvfrom(int fd, void *buf, size_t len, int flags, Ip::Address &from)
Definition: comm.cc:128
uint8_t opcode
Definition: htcp.cc:97
static bool htcpAccessAllowed(acl_access *acl, const htcpSpecifier::Pointer &s, Ip::Address &from)
Definition: htcp.cc:832
void netdbHostData(const char *host, int *samp, int *rtt, int *hops)
Definition: net_db.cc:961
void startReconfigure() override
Definition: htcp.cc:1712
static uint32_t msg_id_counter
Definition: htcp.cc:240
void lastModified(const time_t when)
Definition: Store.h:175
const char * method
Definition: htcp.cc:150
const CachePeers & CurrentCachePeers()
Definition: CachePeers.cc:43
uint16_t length
Definition: htcp.cc:60
#define asyncCallbackFun(dbgSection, dbgLevel, function)
AsyncCall for calling back a function.
@ METHOD_GET
Definition: MethodType.h:25
struct SquidConfig::@90 onoff
@ LOG_UDP_DENIED
Definition: LogTags.h:62
int EnableIpv6
Whether IPv6 is supported and type of support.
Definition: tools.h:25
const SBuf & effectiveRequestUri() const
RFC 7230 section 5.5 - Effective Request URI.
Definition: HttpRequest.cc:741
static void htcpOpenPorts()
Definition: htcp.cc:1489
@ RR_RESPONSE
Definition: htcp.cc:236
void host(const char *src)
Definition: Uri.cc:142
Definition: htcp.cc:1708
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
htcp_clr_reason
Definition: enums.h:234
@ hoRequest
Definition: HttpHeader.h:36
uint8_t RR
Definition: htcp.cc:105
static void htcpTstReply(htcpDataHeader *, StoreEntry *, htcpSpecifier *, Ip::Address &)
Definition: htcp.cc:845
static void htcpHandleTstResponse(htcpDataHeader *, char *, int, Ip::Address &)
Definition: htcp.cc:1117
void clean()
Definition: HttpHeader.cc:318
@ HTCP_NOP
Definition: htcp.cc:202
unsigned short htcp
Definition: SquidConfig.h:144
#define SQUIDHOSTNAMELEN
Definition: rfc2181.h:30
static ssize_t htcpBuildSpecifier(char *buf, size_t buflen, htcpStuff *stuff)
Definition: htcp.cc:379
class SquidConfig Config
Definition: SquidConfig.cc:12
int unsigned int
Definition: stub_fd.cc:19
struct _htcpDataHeader htcpDataHeader
Definition: htcp.cc:48
StatCounters statCounter
Definition: StatCounters.cc:12
@ LOG_UDP_MISS
Definition: LogTags.h:61
uint32_t msg_id
Definition: htcp.cc:86
unsigned int reserved
Definition: htcp.cc:77
int rr
Definition: htcp.cc:192
static htcpSpecifier::Pointer htcpUnpackSpecifier(char *buf, int sz)
Definition: htcp.cc:636

 

Introduction

Documentation

Support

Miscellaneous