]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ipcache.cc
Source Format Enforcement (#1234)
[thirdparty/squid.git] / src / ipcache.cc
1 /*
2 * Copyright (C) 1996-2023 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 14 IP Cache */
10
11 #include "squid.h"
12 #include "CacheManager.h"
13 #include "cbdata.h"
14 #include "debug/Messages.h"
15 #include "dlink.h"
16 #include "dns/LookupDetails.h"
17 #include "dns/rfc3596.h"
18 #include "event.h"
19 #include "ip/Address.h"
20 #include "ip/tools.h"
21 #include "ipcache.h"
22 #include "mgr/Registration.h"
23 #include "snmp_agent.h"
24 #include "SquidConfig.h"
25 #include "StatCounters.h"
26 #include "Store.h"
27 #include "util.h"
28 #include "wordlist.h"
29
30 #if SQUID_SNMP
31 #include "snmp_core.h"
32 #endif
33
34 /**
35 \defgroup IPCacheAPI IP Cache API
36 \ingroup Components
37 \section IpcacheIntroduction Introduction
38 \par
39 * The IP cache is a built-in component of squid providing
40 * Hostname to IP-Number translation functionality and managing
41 * the involved data-structures. Efficiency concerns require
42 * mechanisms that allow non-blocking access to these mappings.
43 * The IP cache usually doesn't block on a request except for
44 * special cases where this is desired (see below).
45 */
46
47 /**
48 \defgroup IPCacheInternal IP Cache Internals
49 \ingroup IPCacheAPI
50 \note when IP cache is provided as a class. These sub-groups will be obsolete
51 * for now they are used to separate the public and private functions.
52 * with the private ones all being in IPCachInternal and public in IPCacheAPI
53 *
54 \section InternalOperation Internal Operation
55 *
56 * Internally, the execution flow is as follows: On a miss,
57 * ipcache_getnbhostbyname checks whether a request for
58 * this name is already pending, and if positive, it creates
59 * a new entry using ipcacheAddNew with the IP_PENDING
60 * flag set . Then it calls ipcacheAddPending to add a
61 * request to the queue together with data and handler. Else,
62 * ipcache_dnsDispatch() is called to directly create a
63 * DNS query or to ipcacheEnqueue() if all no DNS port
64 * is free. ipcache_call_pending() is called regularly
65 * to walk down the pending list and call handlers. LRU clean-up
66 * is performed through ipcache_purgelru() according to
67 * the ipcache_high threshold.
68 */
69
70 /// metadata for parsing DNS A and AAAA records
71 template <class Content>
72 class RrSpecs
73 {
74 public:
75 typedef Content DataType; ///< actual RR DATA type
76 const char *kind; ///< human-friendly record type description
77 int &recordCounter; ///< where this kind of records are counted (for stats)
78 };
79
80 /// forwards non-blocking IP cache lookup results to either IPH or IpReciever
81 class IpCacheLookupForwarder
82 {
83 public:
84 IpCacheLookupForwarder() {}
85 explicit IpCacheLookupForwarder(const CbcPointer<Dns::IpReceiver> &receiver);
86 IpCacheLookupForwarder(IPH *fun, void *data);
87
88 /// forwards notification about the end of the lookup; last method to be called
89 void finalCallback(const Dns::CachedIps *addrs, const Dns::LookupDetails &details);
90
91 /// forwards an IP notification
92 /// \returns whether it may be possible to deliver more notifications
93 bool forwardIp(const Ip::Address &ip);
94
95 /// convenience wrapper to safely forwardIp() for each IP in the container
96 void forwardHits(const Dns::CachedIps &ips);
97
98 /// initialize lookup timestamps for Dns::LookupDetails delay calculation
99 void lookupsStarting() { firstLookupStart = lastLookupEnd = current_time; }
100
101 /// inform recipient of a finished lookup
102 void forwardLookup(const char *error);
103
104 /// \returns milliseconds since the first lookup start
105 int totalResponseTime() const { return tvSubMsec(firstLookupStart, current_time); }
106
107 protected:
108 /// \returns not yet reported lookup delay in milliseconds
109 int additionalLookupDelay() const { return tvSubMsec(lastLookupEnd, current_time); }
110
111 private:
112 /* receiverObj and receiverFun are mutually exclusive */
113 CbcPointer<Dns::IpReceiver> receiverObj; ///< gets incremental and final results
114 IPH *receiverFun = nullptr; ///< gets final results
115 CallbackData receiverData; ///< caller-specific data for the handler (optional)
116
117 struct timeval firstLookupStart {0,0}; ///< time of the idnsALookup() call
118 struct timeval lastLookupEnd {0,0}; ///< time of the last noteLookup() call
119 };
120
121 /**
122 \ingroup IPCacheAPI
123 *
124 * The data structure used for storing name-address mappings
125 * is a small hashtable (static hash_table *ip_table),
126 * where structures of type ipcache_entry whose most
127 * interesting members are:
128 */
129 class ipcache_entry
130 {
131 CBDATA_CLASS(ipcache_entry);
132
133 public:
134 ipcache_entry(const char *);
135 ~ipcache_entry();
136
137 hash_link hash; /* must be first */
138 time_t lastref;
139 time_t expires;
140 ipcache_addrs addrs;
141 IpCacheLookupForwarder handler;
142 char *error_message;
143
144 dlink_node lru;
145 unsigned short locks;
146 struct Flags {
147 Flags() : negcached(false), fromhosts(false) {}
148
149 bool negcached;
150 bool fromhosts;
151 } flags;
152
153 bool sawCname = false;
154
155 const char *name() const { return static_cast<const char*>(hash.key); }
156
157 /// milliseconds since the first lookup start or -1 if there were no lookups
158 int totalResponseTime() const;
159 /// milliseconds since the last lookup start or -1 if there were no lookups
160 int additionalLookupDelay() const;
161
162 /// adds the contents of a "good" DNS A or AAAA record to stored IPs
163 template <class Specs>
164 void addGood(const rfc1035_rr &rr, Specs &specs);
165
166 /// remembers the last error seen, overwriting any previous errors
167 void latestError(const char *text, const int debugLevel = 3);
168
169 protected:
170 void updateTtl(const unsigned int rrTtl);
171 };
172
173 /// \ingroup IPCacheInternal
174 static struct _ipcache_stats {
175 int requests;
176 int replies;
177 int hits;
178 int misses;
179 int negative_hits;
180 int numeric_hits;
181 int rr_a;
182 int rr_aaaa;
183 int rr_cname;
184 int cname_only;
185 int invalid;
186 } IpcacheStats;
187
188 /// \ingroup IPCacheInternal
189 static dlink_list lru_list;
190
191 // forward-decls
192 static void stat_ipcache_get(StoreEntry *);
193
194 static FREE ipcacheFreeEntry;
195 static IDNSCB ipcacheHandleReply;
196 static int ipcacheExpiredEntry(ipcache_entry *);
197 static ipcache_entry *ipcache_get(const char *);
198 static void ipcacheLockEntry(ipcache_entry *);
199 static void ipcacheStatPrint(ipcache_entry *, StoreEntry *);
200 static void ipcacheUnlockEntry(ipcache_entry *);
201 static void ipcacheRelease(ipcache_entry *, bool dofree = true);
202 static const Dns::CachedIps *ipcacheCheckNumeric(const char *name);
203 static void ipcache_nbgethostbyname_(const char *name, IpCacheLookupForwarder handler);
204
205 /// \ingroup IPCacheInternal
206 static hash_table *ip_table = nullptr;
207
208 /// \ingroup IPCacheInternal
209 static long ipcache_low = 180;
210 /// \ingroup IPCacheInternal
211 static long ipcache_high = 200;
212
213 #if LIBRESOLV_DNS_TTL_HACK
214 extern int _dns_ttl_;
215 #endif
216
217 CBDATA_CLASS_INIT(ipcache_entry);
218
219 IpCacheLookupForwarder::IpCacheLookupForwarder(const CbcPointer<Dns::IpReceiver> &receiver):
220 receiverObj(receiver)
221 {
222 }
223
224 IpCacheLookupForwarder::IpCacheLookupForwarder(IPH *fun, void *data):
225 receiverFun(fun), receiverData(data)
226 {
227 }
228
229 void
230 IpCacheLookupForwarder::finalCallback(const Dns::CachedIps *addrs, const Dns::LookupDetails &details)
231 {
232 debugs(14, 7, addrs << " " << details);
233 if (receiverObj.set()) {
234 if (auto receiver = receiverObj.valid())
235 receiver->noteIps(addrs, details);
236 receiverObj.clear();
237 } else if (receiverFun) {
238 if (receiverData.valid()) {
239 const Dns::CachedIps *emptyIsNil = (addrs && !addrs->empty()) ? addrs : nullptr;
240 receiverFun(emptyIsNil, details, receiverData.validDone());
241 }
242 receiverFun = nullptr;
243 }
244 }
245
246 /// forwards an IP notification
247 /// \returns whether it may be possible to deliver more notifications
248 bool
249 IpCacheLookupForwarder::forwardIp(const Ip::Address &ip)
250 {
251 debugs(14, 7, ip);
252 if (receiverObj.set()) {
253 if (auto receiver = receiverObj.valid()) {
254 receiver->noteIp(ip);
255 return true;
256 }
257 return false;
258 }
259 // else do nothing: ReceiverFun does not do incremental notifications
260 return false;
261 }
262
263 /// convenience wrapper to safely forwardIp() for each IP in the container
264 void
265 IpCacheLookupForwarder::forwardHits(const Dns::CachedIps &ips)
266 {
267 if (receiverObj.set()) {
268 for (const auto &ip: ips.good()) {
269 if (!forwardIp(ip))
270 break; // receiver gone
271 }
272 }
273 // else do nothing: ReceiverFun does not do incremental notifications
274 }
275
276 void
277 IpCacheLookupForwarder::forwardLookup(const char *error)
278 {
279 // Lookups run concurrently, but HttpRequest::recordLookup() thinks they
280 // are sequential. Give it just the new, yet-unaccounted-for delay.
281 if (receiverObj.set()) {
282 if (auto receiver = receiverObj.valid()) {
283 receiver->noteLookup(Dns::LookupDetails(error, additionalLookupDelay()));
284 lastLookupEnd = current_time;
285 }
286 }
287 // else do nothing: ReceiverFun gets no individual lookup notifications
288 }
289
290 /// \ingroup IPCacheInternal
291 inline int ipcacheCount() { return ip_table ? ip_table->count : 0; }
292
293 /**
294 \ingroup IPCacheInternal
295 *
296 * removes the given ipcache entry
297 */
298 static void
299 ipcacheRelease(ipcache_entry * i, bool dofree)
300 {
301 if (!i) {
302 debugs(14, DBG_CRITICAL, "ipcacheRelease: Releasing entry with i=<NULL>");
303 return;
304 }
305
306 if (!i || !i->hash.key) {
307 debugs(14, DBG_CRITICAL, "ipcacheRelease: Releasing entry without hash link!");
308 return;
309 }
310
311 debugs(14, 3, "ipcacheRelease: Releasing entry for '" << (const char *) i->hash.key << "'");
312
313 hash_remove_link(ip_table, (hash_link *) i);
314 dlinkDelete(&i->lru, &lru_list);
315 if (dofree)
316 ipcacheFreeEntry(i);
317 }
318
319 /// \ingroup IPCacheInternal
320 static ipcache_entry *
321 ipcache_get(const char *name)
322 {
323 if (ip_table != nullptr)
324 return (ipcache_entry *) hash_lookup(ip_table, name);
325 else
326 return nullptr;
327 }
328
329 /// \ingroup IPCacheInternal
330 static int
331 ipcacheExpiredEntry(ipcache_entry * i)
332 {
333 /* all static entries are locked, so this takes care of them too */
334
335 if (i->locks != 0)
336 return 0;
337
338 if (i->addrs.empty())
339 if (0 == i->flags.negcached)
340 return 1;
341
342 if (i->expires > squid_curtime)
343 return 0;
344
345 return 1;
346 }
347
348 /// \ingroup IPCacheAPI
349 void
350 ipcache_purgelru(void *)
351 {
352 dlink_node *m;
353 dlink_node *prev = nullptr;
354 ipcache_entry *i;
355 int removed = 0;
356 eventAdd("ipcache_purgelru", ipcache_purgelru, nullptr, 10.0, 1);
357
358 for (m = lru_list.tail; m; m = prev) {
359 if (ipcacheCount() < ipcache_low)
360 break;
361
362 prev = m->prev;
363
364 i = (ipcache_entry *)m->data;
365
366 if (i->locks != 0)
367 continue;
368
369 ipcacheRelease(i);
370
371 ++removed;
372 }
373
374 debugs(14, 9, "ipcache_purgelru: removed " << removed << " entries");
375 }
376
377 /**
378 \ingroup IPCacheInternal
379 *
380 * purges entries added from /etc/hosts (or whatever).
381 */
382 static void
383 purge_entries_fromhosts(void)
384 {
385 dlink_node *m = lru_list.head;
386 ipcache_entry *i = nullptr, *t;
387
388 while (m) {
389 if (i != nullptr) { /* need to delay deletion */
390 ipcacheRelease(i); /* we just override locks */
391 i = nullptr;
392 }
393
394 t = (ipcache_entry*)m->data;
395
396 if (t->flags.fromhosts)
397 i = t;
398
399 m = m->next;
400 }
401
402 if (i != nullptr)
403 ipcacheRelease(i);
404 }
405
406 ipcache_entry::ipcache_entry(const char *aName):
407 lastref(0),
408 expires(0),
409 error_message(nullptr),
410 locks(0) // XXX: use Lock type ?
411 {
412 hash.key = xstrdup(aName);
413 Tolower(static_cast<char*>(hash.key));
414 expires = squid_curtime + Config.negativeDnsTtl;
415 }
416
417 /// \ingroup IPCacheInternal
418 static void
419 ipcacheAddEntry(ipcache_entry * i)
420 {
421 hash_link *e = (hash_link *)hash_lookup(ip_table, i->hash.key);
422
423 if (nullptr != e) {
424 /* avoid collision */
425 ipcache_entry *q = (ipcache_entry *) e;
426 ipcacheRelease(q);
427 }
428
429 hash_join(ip_table, &i->hash);
430 dlinkAdd(i, &i->lru, &lru_list);
431 i->lastref = squid_curtime;
432 }
433
434 /**
435 \ingroup IPCacheInternal
436 *
437 * walks down the pending list, calling handlers
438 */
439 static void
440 ipcacheCallback(ipcache_entry *i, const bool hit, const int wait)
441 {
442 i->lastref = squid_curtime;
443
444 ipcacheLockEntry(i);
445
446 if (hit)
447 i->handler.forwardHits(i->addrs);
448 const Dns::LookupDetails details(i->error_message, wait);
449 i->handler.finalCallback(&i->addrs, details);
450
451 ipcacheUnlockEntry(i);
452 }
453
454 void
455 ipcache_entry::latestError(const char *text, const int debugLevel)
456 {
457 debugs(14, debugLevel, "ERROR: DNS failure while resolving " << name() << ": " << text);
458 safe_free(error_message);
459 error_message = xstrdup(text);
460 }
461
462 static void
463 ipcacheParse(ipcache_entry *i, const rfc1035_rr * answers, int nr, const char *error_message)
464 {
465 int k;
466
467 // XXX: Callers use zero ancount instead of -1 on errors!
468 if (nr < 0) {
469 i->latestError(error_message);
470 return;
471 }
472
473 if (nr == 0) {
474 i->latestError("No DNS records");
475 return;
476 }
477
478 debugs(14, 3, nr << " answers for " << i->name());
479 assert(answers);
480
481 for (k = 0; k < nr; ++k) {
482
483 if (Ip::EnableIpv6 && answers[k].type == RFC1035_TYPE_AAAA) {
484 static const RrSpecs<struct in6_addr> QuadA = { "IPv6", IpcacheStats.rr_aaaa };
485 i->addGood(answers[k], QuadA);
486 continue;
487 }
488
489 if (answers[k].type == RFC1035_TYPE_A) {
490 static const RrSpecs<struct in_addr> SingleA = { "IPv4", IpcacheStats.rr_a };
491 i->addGood(answers[k], SingleA);
492 continue;
493 }
494
495 /* With A and AAAA, the CNAME does not necessarily come with additional records to use. */
496 if (answers[k].type == RFC1035_TYPE_CNAME) {
497 i->sawCname = true;
498 ++IpcacheStats.rr_cname;
499 continue;
500 }
501
502 // otherwise its an unknown RR. debug at level 9 since we usually want to ignore these and they are common.
503 debugs(14, 9, "Unknown RR type received: type=" << answers[k].type << " starting at " << &(answers[k]) );
504 }
505 }
506
507 template <class Specs>
508 void
509 ipcache_entry::addGood(const rfc1035_rr &rr, Specs &specs)
510 {
511 typename Specs::DataType address;
512 if (rr.rdlength != sizeof(address)) {
513 debugs(14, DBG_IMPORTANT, "ERROR: Ignoring invalid " << specs.kind << " address record while resolving " << name());
514 return;
515 }
516
517 ++specs.recordCounter;
518
519 // Do not store more than 255 addresses (TODO: Why?)
520 if (addrs.raw().size() >= 255)
521 return;
522
523 memcpy(&address, rr.rdata, sizeof(address));
524 const Ip::Address ip = address;
525 if (addrs.have(ip)) {
526 debugs(14, 3, "refusing to add duplicate " << ip);
527 return;
528 }
529 addrs.pushUnique(address);
530
531 updateTtl(rr.ttl);
532
533 debugs(14, 3, name() << " #" << addrs.size() << " " << ip);
534 handler.forwardIp(ip); // we are only called with good IPs
535 }
536
537 void
538 ipcache_entry::updateTtl(const unsigned int rrTtl)
539 {
540 const time_t ttl = std::min(std::max(
541 Config.negativeDnsTtl, // smallest value allowed
542 static_cast<time_t>(rrTtl)),
543 Config.positiveDnsTtl); // largest value allowed
544
545 const time_t rrExpires = squid_curtime + ttl;
546 if (rrExpires < expires)
547 expires = rrExpires;
548 }
549
550 /// \ingroup IPCacheInternal
551 static void
552 ipcacheHandleReply(void *data, const rfc1035_rr * answers, int na, const char *error_message, const bool lastAnswer)
553 {
554 ipcache_entry *i = static_cast<ipcache_entry*>(data);
555
556 i->handler.forwardLookup(error_message);
557 ipcacheParse(i, answers, na, error_message);
558
559 if (!lastAnswer)
560 return;
561
562 ++IpcacheStats.replies;
563 const auto age = i->handler.totalResponseTime();
564 statCounter.dns.svcTime.count(age);
565
566 if (i->addrs.empty()) {
567 i->flags.negcached = true;
568 i->expires = squid_curtime + Config.negativeDnsTtl;
569
570 if (!i->error_message) {
571 i->latestError("No valid address records", DBG_IMPORTANT);
572 if (i->sawCname)
573 ++IpcacheStats.cname_only;
574 }
575 }
576
577 debugs(14, 3, "done with " << i->name() << ": " << i->addrs);
578 ipcacheAddEntry(i);
579 ipcacheCallback(i, false, age);
580 }
581
582 /**
583 \ingroup IPCacheAPI
584 *
585 \param name Host to resolve.
586 \param handler Pointer to the function to be called when the reply
587 * from the IP cache (or the DNS if the IP cache misses)
588 \param handlerData Information that is passed to the handler and does not affect the IP cache.
589 *
590 * XXX: on hits and some errors, the handler is called immediately instead
591 * of scheduling an async call. This reentrant behavior means that the
592 * user job must be extra careful after calling ipcache_nbgethostbyname,
593 * especially if the handler destroys the job. Moreover, the job has
594 * no way of knowing whether the reentrant call happened.
595 * Comm::Connection setup usually protects the job by scheduling an async call,
596 * but some user code calls ipcache_nbgethostbyname directly.
597 */
598 void
599 ipcache_nbgethostbyname(const char *name, IPH * handler, void *handlerData)
600 {
601 debugs(14, 4, name);
602 ipcache_nbgethostbyname_(name, IpCacheLookupForwarder(handler, handlerData));
603 }
604
605 void
606 Dns::nbgethostbyname(const char *name, const CbcPointer<IpReceiver> &receiver)
607 {
608 debugs(14, 4, name);
609 ipcache_nbgethostbyname_(name, IpCacheLookupForwarder(receiver));
610 }
611
612 /// implements ipcache_nbgethostbyname() and Dns::nbgethostbyname() APIs
613 static void
614 ipcache_nbgethostbyname_(const char *name, IpCacheLookupForwarder handler)
615 {
616 ipcache_entry *i = nullptr;
617 const ipcache_addrs *addrs = nullptr;
618 ++IpcacheStats.requests;
619
620 if (name == nullptr || name[0] == '\0') {
621 debugs(14, 4, "ipcache_nbgethostbyname: Invalid name!");
622 ++IpcacheStats.invalid;
623 const Dns::LookupDetails details("Invalid hostname", -1); // error, no lookup
624 handler.finalCallback(nullptr, details);
625 return;
626 }
627
628 if ((addrs = ipcacheCheckNumeric(name))) {
629 debugs(14, 4, "ipcache_nbgethostbyname: BYPASS for '" << name << "' (already numeric)");
630 handler.forwardHits(*addrs);
631 ++IpcacheStats.numeric_hits;
632 const Dns::LookupDetails details; // no error, no lookup
633 handler.finalCallback(addrs, details);
634 return;
635 }
636
637 i = ipcache_get(name);
638
639 if (nullptr == i) {
640 /* miss */
641 (void) 0;
642 } else if (ipcacheExpiredEntry(i)) {
643 /* hit, but expired -- bummer */
644 ipcacheRelease(i);
645 i = nullptr;
646 } else {
647 /* hit */
648 debugs(14, 4, "ipcache_nbgethostbyname: HIT for '" << name << "'");
649
650 if (i->flags.negcached)
651 ++IpcacheStats.negative_hits;
652 else
653 ++IpcacheStats.hits;
654
655 i->handler = std::move(handler);
656 ipcacheCallback(i, true, -1); // no lookup
657
658 return;
659 }
660
661 debugs(14, 5, "ipcache_nbgethostbyname: MISS for '" << name << "'");
662 ++IpcacheStats.misses;
663 i = new ipcache_entry(name);
664 i->handler = std::move(handler);
665 i->handler.lookupsStarting();
666 idnsALookup(hashKeyStr(&i->hash), ipcacheHandleReply, i);
667 }
668
669 /// \ingroup IPCacheInternal
670 static void
671 ipcacheRegisterWithCacheManager(void)
672 {
673 Mgr::RegisterAction("ipcache",
674 "IP Cache Stats and Contents",
675 stat_ipcache_get, 0, 1);
676 }
677
678 /**
679 \ingroup IPCacheAPI
680 *
681 * Initialize the ipcache.
682 * Is called from mainInitialize() after disk initialization
683 * and prior to the reverse FQDNCache initialization
684 */
685 void
686 ipcache_init(void)
687 {
688 int n;
689 debugs(14, Important(24), "Initializing IP Cache...");
690 memset(&IpcacheStats, '\0', sizeof(IpcacheStats));
691 lru_list = dlink_list();
692
693 ipcache_high = (long) (((float) Config.ipcache.size *
694 (float) Config.ipcache.high) / (float) 100);
695 ipcache_low = (long) (((float) Config.ipcache.size *
696 (float) Config.ipcache.low) / (float) 100);
697 n = hashPrime(ipcache_high / 4);
698 ip_table = hash_create((HASHCMP *) strcmp, n, hash4);
699
700 ipcacheRegisterWithCacheManager();
701 }
702
703 /**
704 \ingroup IPCacheAPI
705 *
706 * Is different from ipcache_nbgethostbyname in that it only checks
707 * if an entry exists in the cache and does not by default contact the DNS,
708 * unless this is requested, by setting the flags.
709 *
710 \param name Host name to resolve.
711 \param flags Default is NULL, set to IP_LOOKUP_IF_MISS
712 * to explicitly perform DNS lookups.
713 *
714 \retval NULL An error occurred during lookup
715 \retval NULL No results available in cache and no lookup specified
716 \retval * Pointer to the ipcahce_addrs structure containing the lookup results
717 */
718 const ipcache_addrs *
719 ipcache_gethostbyname(const char *name, int flags)
720 {
721 ipcache_entry *i = nullptr;
722 assert(name);
723 debugs(14, 3, "ipcache_gethostbyname: '" << name << "', flags=" << std::hex << flags);
724 ++IpcacheStats.requests;
725 i = ipcache_get(name);
726
727 if (nullptr == i) {
728 (void) 0;
729 } else if (ipcacheExpiredEntry(i)) {
730 ipcacheRelease(i);
731 i = nullptr;
732 } else if (i->flags.negcached) {
733 ++IpcacheStats.negative_hits;
734 // ignore i->error_message: the caller just checks IP cache presence
735 return nullptr;
736 } else {
737 ++IpcacheStats.hits;
738 i->lastref = squid_curtime;
739 // ignore i->error_message: the caller just checks IP cache presence
740 return &i->addrs;
741 }
742
743 /* no entry [any more] */
744
745 if (const auto addrs = ipcacheCheckNumeric(name)) {
746 ++IpcacheStats.numeric_hits;
747 return addrs;
748 }
749
750 ++IpcacheStats.misses;
751
752 if (flags & IP_LOOKUP_IF_MISS)
753 ipcache_nbgethostbyname(name, nullptr, nullptr);
754
755 return nullptr;
756 }
757
758 /// \ingroup IPCacheInternal
759 static void
760 ipcacheStatPrint(ipcache_entry * i, StoreEntry * sentry)
761 {
762 char buf[MAX_IPSTRLEN];
763
764 if (!sentry) {
765 debugs(14, DBG_CRITICAL, "ERROR: sentry is NULL!");
766 return;
767 }
768
769 if (!i) {
770 debugs(14, DBG_CRITICAL, "ERROR: ipcache_entry is NULL!");
771 storeAppendPrintf(sentry, "CRITICAL ERROR\n");
772 return;
773 }
774
775 storeAppendPrintf(sentry, " %-32.32s %c%c %6d %6d %2d(%2d)",
776 hashKeyStr(&i->hash),
777 i->flags.fromhosts ? 'H' : ' ',
778 i->flags.negcached ? 'N' : ' ',
779 (int) (squid_curtime - i->lastref),
780 (int) ((i->flags.fromhosts ? -1 : i->expires - squid_curtime)),
781 static_cast<int>(i->addrs.size()),
782 static_cast<int>(i->addrs.badCount()));
783
784 /** \par
785 * Negative-cached entries have no IPs listed. */
786 if (i->flags.negcached) {
787 storeAppendPrintf(sentry, "\n");
788 return;
789 }
790
791 /** \par
792 * Cached entries have IPs listed with a BNF of: ip-address '-' ('OK'|'BAD') */
793 bool firstLine = true;
794 for (const auto &addr: i->addrs.raw()) {
795 /* Display tidy-up: IPv6 are so big make the list vertical */
796 const char *indent = firstLine ? "" : " ";
797 storeAppendPrintf(sentry, "%s %45.45s-%3s\n",
798 indent,
799 addr.ip.toStr(buf, MAX_IPSTRLEN),
800 addr.bad() ? "BAD" : "OK ");
801 firstLine = false;
802 }
803 }
804
805 /**
806 \ingroup IPCacheInternal
807 *
808 * process objects list
809 */
810 void
811 stat_ipcache_get(StoreEntry * sentry)
812 {
813 dlink_node *m;
814 assert(ip_table != nullptr);
815 storeAppendPrintf(sentry, "IP Cache Statistics:\n");
816 storeAppendPrintf(sentry, "IPcache Entries Cached: %d\n",
817 ipcacheCount());
818 storeAppendPrintf(sentry, "IPcache Requests: %d\n",
819 IpcacheStats.requests);
820 storeAppendPrintf(sentry, "IPcache Hits: %d\n",
821 IpcacheStats.hits);
822 storeAppendPrintf(sentry, "IPcache Negative Hits: %d\n",
823 IpcacheStats.negative_hits);
824 storeAppendPrintf(sentry, "IPcache Numeric Hits: %d\n",
825 IpcacheStats.numeric_hits);
826 storeAppendPrintf(sentry, "IPcache Misses: %d\n",
827 IpcacheStats.misses);
828 storeAppendPrintf(sentry, "IPcache Retrieved A: %d\n",
829 IpcacheStats.rr_a);
830 storeAppendPrintf(sentry, "IPcache Retrieved AAAA: %d\n",
831 IpcacheStats.rr_aaaa);
832 storeAppendPrintf(sentry, "IPcache Retrieved CNAME: %d\n",
833 IpcacheStats.rr_cname);
834 storeAppendPrintf(sentry, "IPcache CNAME-Only Response: %d\n",
835 IpcacheStats.cname_only);
836 storeAppendPrintf(sentry, "IPcache Invalid Request: %d\n",
837 IpcacheStats.invalid);
838 storeAppendPrintf(sentry, "\n\n");
839 storeAppendPrintf(sentry, "IP Cache Contents:\n\n");
840 storeAppendPrintf(sentry, " %-31.31s %3s %6s %6s %4s\n",
841 "Hostname",
842 "Flg",
843 "lstref",
844 "TTL",
845 "N(b)");
846
847 for (m = lru_list.head; m; m = m->next) {
848 assert( m->next != m );
849 ipcacheStatPrint((ipcache_entry *)m->data, sentry);
850 }
851 }
852
853 /// \ingroup IPCacheAPI
854 void
855 ipcacheInvalidate(const char *name)
856 {
857 ipcache_entry *i;
858
859 if ((i = ipcache_get(name)) == nullptr)
860 return;
861
862 i->expires = squid_curtime;
863
864 /*
865 * NOTE, don't call ipcacheRelease here because we might be here due
866 * to a thread started from a callback.
867 */
868 }
869
870 /// \ingroup IPCacheAPI
871 void
872 ipcacheInvalidateNegative(const char *name)
873 {
874 ipcache_entry *i;
875
876 if ((i = ipcache_get(name)) == nullptr)
877 return;
878
879 if (i->flags.negcached)
880 i->expires = squid_curtime;
881
882 /*
883 * NOTE, don't call ipcacheRelease here because we might be here due
884 * to a thread started from a callback.
885 */
886 }
887
888 /// \ingroup IPCacheAPI
889 static const Dns::CachedIps *
890 ipcacheCheckNumeric(const char *name)
891 {
892 Ip::Address ip;
893 if (!ip.fromHost(name))
894 return nullptr;
895
896 debugs(14, 4, "HIT_BYPASS for " << name << "=" << ip);
897 static Dns::CachedIps static_addrs;
898 static_addrs.reset(ip);
899 return &static_addrs;
900 }
901
902 /// \ingroup IPCacheInternal
903 static void
904 ipcacheLockEntry(ipcache_entry * i)
905 {
906 if (i->locks++ == 0) {
907 dlinkDelete(&i->lru, &lru_list);
908 dlinkAdd(i, &i->lru, &lru_list);
909 }
910 }
911
912 /// \ingroup IPCacheInternal
913 static void
914 ipcacheUnlockEntry(ipcache_entry * i)
915 {
916 if (i->locks < 1) {
917 debugs(14, DBG_IMPORTANT, "WARNING: ipcacheEntry unlocked with no lock! locks=" << i->locks);
918 return;
919 }
920
921 -- i->locks;
922
923 if (ipcacheExpiredEntry(i))
924 ipcacheRelease(i);
925 }
926
927 /// find the next good IP, wrapping if needed
928 /// \returns whether the search was successful
929 bool
930 Dns::CachedIps::seekNewGood(const char *name)
931 {
932 // linear search!
933 for (size_t seen = 0; seen < ips.size(); ++seen) {
934 if (++goodPosition >= ips.size())
935 goodPosition = 0;
936 if (!ips[goodPosition].bad()) {
937 debugs(14, 3, "succeeded for " << name << ": " << *this);
938 return true;
939 }
940 }
941 goodPosition = ips.size();
942 debugs(14, 3, "failed for " << name << ": " << *this);
943 return false;
944 }
945
946 void
947 Dns::CachedIps::reset(const Ip::Address &ip)
948 {
949 ips.clear();
950 ips.emplace_back(ip);
951 goodPosition = 0;
952 // Assume that the given IP is good because CachedIps are designed to never
953 // run out of good IPs.
954 badCount_ = 0;
955 }
956
957 /// makes current() calls possible after a successful markAsBad()
958 void
959 Dns::CachedIps::restoreGoodness(const char *name)
960 {
961 if (badCount() >= size()) {
962 // There are no good IPs left. Clear all bad marks. This must help
963 // because we are called only after a good address was tested as bad.
964 for (auto &cachedIp: ips)
965 cachedIp.forgetMarking();
966 badCount_ = 0;
967 }
968 Must(seekNewGood(name));
969 debugs(14, 3, "cleared all IPs for " << name << "; now back to " << *this);
970 }
971
972 bool
973 Dns::CachedIps::have(const Ip::Address &ip, size_t *positionOrNil) const
974 {
975 // linear search!
976 size_t pos = 0;
977 for (const auto &cachedIp: ips) {
978 if (cachedIp.ip == ip) {
979 if (auto position = positionOrNil)
980 *position = pos;
981 debugs(14, 7, ip << " at " << pos << " in " << *this);
982 return true;
983 }
984 }
985 // no such address; leave *position as is
986 debugs(14, 7, " no " << ip << " in " << *this);
987 return false;
988 }
989
990 void
991 Dns::CachedIps::pushUnique(const Ip::Address &ip)
992 {
993 assert(!have(ip));
994 [[maybe_unused]] auto &cachedIp = ips.emplace_back(ip);
995 assert(!cachedIp.bad());
996 }
997
998 void
999 Dns::CachedIps::reportCurrent(std::ostream &os) const
1000 {
1001 if (empty())
1002 os << "[no cached IPs]";
1003 else if (goodPosition == size())
1004 os << "[" << size() << " bad cached IPs]"; // could only be temporary
1005 else
1006 os << current() << " #" << (goodPosition+1) << "/" << ips.size() << "-" << badCount();
1007 }
1008
1009 void
1010 Dns::CachedIps::markAsBad(const char *name, const Ip::Address &ip)
1011 {
1012 size_t badPosition = 0;
1013 if (!have(ip, &badPosition))
1014 return; // no such address
1015
1016 auto &cachedIp = ips[badPosition];
1017 if (cachedIp.bad())
1018 return; // already marked correctly
1019
1020 cachedIp.markAsBad();
1021 ++badCount_;
1022 debugs(14, 2, ip << " of " << name);
1023
1024 if (goodPosition == badPosition)
1025 restoreGoodness(name);
1026 // else nothing to do: goodPositon still points to a good IP
1027 }
1028
1029 void
1030 Dns::CachedIps::forgetMarking(const char *name, const Ip::Address &ip)
1031 {
1032 if (!badCount_)
1033 return; // all IPs are already "good"
1034
1035 size_t badPosition = 0;
1036 if (!have(ip, &badPosition))
1037 return; // no such address
1038
1039 auto &cachedIp = ips[badPosition];
1040 if (!cachedIp.bad())
1041 return; // already marked correctly
1042
1043 cachedIp.forgetMarking();
1044 assert(!cachedIp.bad());
1045 --badCount_;
1046 debugs(14, 2, ip << " of " << name);
1047 }
1048
1049 /**
1050 * Marks the given address as BAD.
1051 * Does nothing if the domain name does not exist.
1052 *
1053 \param name domain name to have an IP marked bad
1054 \param addr specific address to be marked bad
1055 */
1056 void
1057 ipcacheMarkBadAddr(const char *name, const Ip::Address &addr)
1058 {
1059 if (auto cached = ipcache_get(name))
1060 cached->addrs.markAsBad(name, addr);
1061 }
1062
1063 /// \ingroup IPCacheAPI
1064 void
1065 ipcacheMarkGoodAddr(const char *name, const Ip::Address &addr)
1066 {
1067 if (auto cached = ipcache_get(name))
1068 cached->addrs.forgetMarking(name, addr);
1069 }
1070
1071 /// \ingroup IPCacheInternal
1072 static void
1073 ipcacheFreeEntry(void *data)
1074 {
1075 ipcache_entry *i = (ipcache_entry *)data;
1076 delete i;
1077 }
1078
1079 ipcache_entry::~ipcache_entry()
1080 {
1081 xfree(error_message);
1082 xfree(hash.key);
1083 }
1084
1085 /**
1086 \ingroup IPCacheAPI
1087 *
1088 * Recalculate IP cache size upon reconfigure.
1089 * Is called to clear the IPCache's data structures,
1090 * cancel all pending requests.
1091 */
1092 void
1093 ipcache_restart(void)
1094 {
1095 ipcache_high = (long) (((float) Config.ipcache.size *
1096 (float) Config.ipcache.high) / (float) 100);
1097 ipcache_low = (long) (((float) Config.ipcache.size *
1098 (float) Config.ipcache.low) / (float) 100);
1099 purge_entries_fromhosts();
1100 }
1101
1102 /**
1103 \ingroup IPCacheAPI
1104 *
1105 * Adds a "static" entry from /etc/hosts
1106 *
1107 \param name Hostname to be linked with IP
1108 \param ipaddr IP Address to be cached.
1109 *
1110 \retval 0 Success.
1111 \retval 1 IP address is invalid or other error.
1112 */
1113 int
1114 ipcacheAddEntryFromHosts(const char *name, const char *ipaddr)
1115 {
1116 ipcache_entry *i;
1117
1118 Ip::Address ip;
1119
1120 if (!(ip = ipaddr)) {
1121 if (strchr(ipaddr, ':') && strspn(ipaddr, "0123456789abcdefABCDEF:") == strlen(ipaddr)) {
1122 debugs(14, 3, "ipcacheAddEntryFromHosts: Skipping IPv6 address '" << ipaddr << "'");
1123 } else {
1124 debugs(14, DBG_IMPORTANT, "ERROR: ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr << "'");
1125 }
1126
1127 return 1;
1128 }
1129
1130 if (!Ip::EnableIpv6 && ip.isIPv6()) {
1131 debugs(14, 2, "skips IPv6 address in /etc/hosts because IPv6 support was disabled: " << ip);
1132 return 1;
1133 }
1134
1135 if ((i = ipcache_get(name))) {
1136 if (1 == i->flags.fromhosts) {
1137 ipcacheUnlockEntry(i);
1138 } else if (i->locks > 0) {
1139 debugs(14, DBG_IMPORTANT, "ERROR: ipcacheAddEntryFromHosts: cannot add static entry for locked name '" << name << "'");
1140 return 1;
1141 } else {
1142 ipcacheRelease(i);
1143 }
1144 }
1145
1146 i = new ipcache_entry(name);
1147 i->addrs.pushUnique(ip);
1148 i->flags.fromhosts = true;
1149 ipcacheAddEntry(i);
1150 ipcacheLockEntry(i);
1151 return 0;
1152 }
1153
1154 #if SQUID_SNMP
1155 /**
1156 \ingroup IPCacheAPI
1157 *
1158 * The function to return the ip cache statistics to via SNMP
1159 */
1160 variable_list *
1161 snmp_netIpFn(variable_list * Var, snint * ErrP)
1162 {
1163 variable_list *Answer = nullptr;
1164 MemBuf tmp;
1165 debugs(49, 5, "snmp_netIpFn: Processing request:" << snmpDebugOid(Var->name, Var->name_length, tmp));
1166 *ErrP = SNMP_ERR_NOERROR;
1167
1168 switch (Var->name[LEN_SQ_NET + 1]) {
1169
1170 case IP_ENT:
1171 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1172 ipcacheCount(),
1173 SMI_GAUGE32);
1174 break;
1175
1176 case IP_REQ:
1177 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1178 IpcacheStats.requests,
1179 SMI_COUNTER32);
1180 break;
1181
1182 case IP_HITS:
1183 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1184 IpcacheStats.hits,
1185 SMI_COUNTER32);
1186 break;
1187
1188 case IP_PENDHIT:
1189 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1190 0,
1191 SMI_GAUGE32);
1192 break;
1193
1194 case IP_NEGHIT:
1195 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1196 IpcacheStats.negative_hits,
1197 SMI_COUNTER32);
1198 break;
1199
1200 case IP_MISS:
1201 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1202 IpcacheStats.misses,
1203 SMI_COUNTER32);
1204 break;
1205
1206 case IP_GHBN:
1207 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1208 0, /* deprecated */
1209 SMI_COUNTER32);
1210 break;
1211
1212 case IP_LOC:
1213 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1214 0, /* deprecated */
1215 SMI_COUNTER32);
1216 break;
1217
1218 default:
1219 *ErrP = SNMP_ERR_NOSUCHNAME;
1220 assert(!Answer);
1221 }
1222
1223 return Answer;
1224 }
1225
1226 #endif /*SQUID_SNMP */
1227