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