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