1 /*
2 * Copyright (C) 1996-2021 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 /**
72 \ingroup IPCacheAPI
73 *
74 * The data structure used for storing name-address mappings
75 * is a small hashtable (static hash_table *ip_table),
76 * where structures of type ipcache_entry whose most
77 * interesting members are:
78 */
79 class ipcache_entry
80 {
81 MEMPROXY_CLASS(ipcache_entry);
82
83 public:
84 ipcache_entry(const char *);
85 ~ipcache_entry();
86
87 hash_link hash; /* must be first */
88 time_t lastref;
89 time_t expires;
90 ipcache_addrs addrs;
91 IPH *handler;
92 void *handlerData;
93 char *error_message;
94
95 struct timeval request_time;
96 dlink_node lru;
97 unsigned short locks;
98 struct Flags {
Flagsipcache_entry::Flags99 Flags() : negcached(false), fromhosts(false) {}
100
101 bool negcached;
102 bool fromhosts;
103 } flags;
104
105 int age() const; ///< time passed since request_time or -1 if unknown
106 };
107
108 /// \ingroup IPCacheInternal
109 static struct _ipcache_stats {
110 int requests;
111 int replies;
112 int hits;
113 int misses;
114 int negative_hits;
115 int numeric_hits;
116 int rr_a;
117 int rr_aaaa;
118 int rr_cname;
119 int cname_only;
120 int invalid;
121 } IpcacheStats;
122
123 /// \ingroup IPCacheInternal
124 static dlink_list lru_list;
125
126 // forward-decls
127 static void stat_ipcache_get(StoreEntry *);
128
129 static FREE ipcacheFreeEntry;
130 static IDNSCB ipcacheHandleReply;
131 static int ipcacheExpiredEntry(ipcache_entry *);
132 static ipcache_entry *ipcache_get(const char *);
133 static void ipcacheLockEntry(ipcache_entry *);
134 static void ipcacheStatPrint(ipcache_entry *, StoreEntry *);
135 static void ipcacheUnlockEntry(ipcache_entry *);
136 static void ipcacheRelease(ipcache_entry *, bool dofree = true);
137
138 /// \ingroup IPCacheInternal
139 static ipcache_addrs static_addrs;
140 /// \ingroup IPCacheInternal
141 static hash_table *ip_table = NULL;
142
143 /// \ingroup IPCacheInternal
144 static long ipcache_low = 180;
145 /// \ingroup IPCacheInternal
146 static long ipcache_high = 200;
147
148 #if LIBRESOLV_DNS_TTL_HACK
149 extern int _dns_ttl_;
150 #endif
151
152 /// \ingroup IPCacheInternal
ipcacheCount()153 inline int ipcacheCount() { return ip_table ? ip_table->count : 0; }
154
155 int
age() const156 ipcache_entry::age() const
157 {
158 return request_time.tv_sec ? tvSubMsec(request_time, current_time) : -1;
159 }
160
161 /**
162 \ingroup IPCacheInternal
163 *
164 * removes the given ipcache entry
165 */
166 static void
ipcacheRelease(ipcache_entry * i,bool dofree)167 ipcacheRelease(ipcache_entry * i, bool dofree)
168 {
169 if (!i) {
170 debugs(14, DBG_CRITICAL, "ipcacheRelease: Releasing entry with i=<NULL>");
171 return;
172 }
173
174 if (!i || !i->hash.key) {
175 debugs(14, DBG_CRITICAL, "ipcacheRelease: Releasing entry without hash link!");
176 return;
177 }
178
179 debugs(14, 3, "ipcacheRelease: Releasing entry for '" << (const char *) i->hash.key << "'");
180
181 hash_remove_link(ip_table, (hash_link *) i);
182 dlinkDelete(&i->lru, &lru_list);
183 if (dofree)
184 ipcacheFreeEntry(i);
185 }
186
187 /// \ingroup IPCacheInternal
188 static ipcache_entry *
ipcache_get(const char * name)189 ipcache_get(const char *name)
190 {
191 if (ip_table != NULL)
192 return (ipcache_entry *) hash_lookup(ip_table, name);
193 else
194 return NULL;
195 }
196
197 /// \ingroup IPCacheInternal
198 static int
ipcacheExpiredEntry(ipcache_entry * i)199 ipcacheExpiredEntry(ipcache_entry * i)
200 {
201 /* all static entries are locked, so this takes care of them too */
202
203 if (i->locks != 0)
204 return 0;
205
206 if (i->addrs.count == 0)
207 if (0 == i->flags.negcached)
208 return 1;
209
210 if (i->expires > squid_curtime)
211 return 0;
212
213 return 1;
214 }
215
216 /// \ingroup IPCacheAPI
217 void
ipcache_purgelru(void *)218 ipcache_purgelru(void *)
219 {
220 dlink_node *m;
221 dlink_node *prev = NULL;
222 ipcache_entry *i;
223 int removed = 0;
224 eventAdd("ipcache_purgelru", ipcache_purgelru, NULL, 10.0, 1);
225
226 for (m = lru_list.tail; m; m = prev) {
227 if (ipcacheCount() < ipcache_low)
228 break;
229
230 prev = m->prev;
231
232 i = (ipcache_entry *)m->data;
233
234 if (i->locks != 0)
235 continue;
236
237 ipcacheRelease(i);
238
239 ++removed;
240 }
241
242 debugs(14, 9, "ipcache_purgelru: removed " << removed << " entries");
243 }
244
245 /**
246 \ingroup IPCacheInternal
247 *
248 * purges entries added from /etc/hosts (or whatever).
249 */
250 static void
purge_entries_fromhosts(void)251 purge_entries_fromhosts(void)
252 {
253 dlink_node *m = lru_list.head;
254 ipcache_entry *i = NULL, *t;
255
256 while (m) {
257 if (i != NULL) { /* need to delay deletion */
258 ipcacheRelease(i); /* we just override locks */
259 i = NULL;
260 }
261
262 t = (ipcache_entry*)m->data;
263
264 if (t->flags.fromhosts)
265 i = t;
266
267 m = m->next;
268 }
269
270 if (i != NULL)
271 ipcacheRelease(i);
272 }
273
ipcache_entry(const char * name)274 ipcache_entry::ipcache_entry(const char *name) :
275 lastref(0),
276 expires(0),
277 handler(nullptr),
278 handlerData(nullptr),
279 error_message(nullptr),
280 locks(0) // XXX: use Lock type ?
281 {
282 hash.key = xstrdup(name);
283 Tolower(static_cast<char*>(hash.key));
284 expires = squid_curtime + Config.negativeDnsTtl;
285
286 memset(&request_time, 0, sizeof(request_time));
287 }
288
289 /// \ingroup IPCacheInternal
290 static void
ipcacheAddEntry(ipcache_entry * i)291 ipcacheAddEntry(ipcache_entry * i)
292 {
293 hash_link *e = (hash_link *)hash_lookup(ip_table, i->hash.key);
294
295 if (NULL != e) {
296 /* avoid colission */
297 ipcache_entry *q = (ipcache_entry *) e;
298 ipcacheRelease(q);
299 }
300
301 hash_join(ip_table, &i->hash);
302 dlinkAdd(i, &i->lru, &lru_list);
303 i->lastref = squid_curtime;
304 }
305
306 /**
307 \ingroup IPCacheInternal
308 *
309 * walks down the pending list, calling handlers
310 */
311 static void
ipcacheCallback(ipcache_entry * i,int wait)312 ipcacheCallback(ipcache_entry *i, int wait)
313 {
314 IPH *callback = i->handler;
315 void *cbdata = NULL;
316 i->lastref = squid_curtime;
317
318 if (!i->handler)
319 return;
320
321 ipcacheLockEntry(i);
322
323 callback = i->handler;
324
325 i->handler = NULL;
326
327 if (cbdataReferenceValidDone(i->handlerData, &cbdata)) {
328 const Dns::LookupDetails details(i->error_message, wait);
329 callback((i->addrs.count ? &i->addrs : NULL), details, cbdata);
330 }
331
332 ipcacheUnlockEntry(i);
333 }
334
335 static void
ipcacheParse(ipcache_entry * i,const rfc1035_rr * answers,int nr,const char * error_message)336 ipcacheParse(ipcache_entry *i, const rfc1035_rr * answers, int nr, const char *error_message)
337 {
338 int k;
339 int j = 0;
340 int na = 0;
341 int ttl = 0;
342 const char *name = (const char *)i->hash.key;
343 int cname_found = 0;
344
345 i->expires = squid_curtime + Config.negativeDnsTtl;
346 i->flags.negcached = true;
347 safe_free(i->addrs.in_addrs);
348 assert(i->addrs.in_addrs == NULL);
349 safe_free(i->addrs.bad_mask);
350 assert(i->addrs.bad_mask == NULL);
351 safe_free(i->error_message);
352 assert(i->error_message == NULL);
353 i->addrs.count = 0;
354
355 if (nr < 0) {
356 debugs(14, 3, "Lookup failed '" << error_message << "' for '" << (const char *)i->hash.key << "'");
357 i->error_message = xstrdup(error_message);
358 return;
359 }
360
361 if (nr == 0) {
362 debugs(14, 3, "No DNS records in response to '" << name << "'");
363 i->error_message = xstrdup("No DNS records");
364 return;
365 }
366
367 debugs(14, 3, nr << " answers for '" << name << "'");
368 assert(answers);
369
370 for (k = 0; k < nr; ++k) {
371
372 if (Ip::EnableIpv6 && answers[k].type == RFC1035_TYPE_AAAA) {
373 if (answers[k].rdlength != sizeof(struct in6_addr)) {
374 debugs(14, DBG_IMPORTANT, MYNAME << "Invalid IPv6 address in response to '" << name << "'");
375 continue;
376 }
377 ++na;
378 ++IpcacheStats.rr_aaaa;
379 continue;
380 }
381
382 if (answers[k].type == RFC1035_TYPE_A) {
383 if (answers[k].rdlength != sizeof(struct in_addr)) {
384 debugs(14, DBG_IMPORTANT, MYNAME << "Invalid IPv4 address in response to '" << name << "'");
385 continue;
386 }
387 ++na;
388 ++IpcacheStats.rr_a;
389 continue;
390 }
391
392 /* With A and AAAA, the CNAME does not necessarily come with additional records to use. */
393 if (answers[k].type == RFC1035_TYPE_CNAME) {
394 cname_found=1;
395 ++IpcacheStats.rr_cname;
396 continue;
397 }
398
399 // otherwise its an unknown RR. debug at level 9 since we usually want to ignore these and they are common.
400 debugs(14, 9, "Unknown RR type received: type=" << answers[k].type << " starting at " << &(answers[k]) );
401 }
402 if (na == 0) {
403 debugs(14, DBG_IMPORTANT, MYNAME << "No Address records in response to '" << name << "'");
404 i->error_message = xstrdup("No Address records");
405 if (cname_found)
406 ++IpcacheStats.cname_only;
407 return;
408 }
409
410 i->addrs.in_addrs = static_cast<Ip::Address *>(xcalloc(na, sizeof(Ip::Address)));
411 for (int l = 0; l < na; ++l)
412 i->addrs.in_addrs[l].setEmpty(); // perform same init actions as constructor would.
413 i->addrs.bad_mask = (unsigned char *)xcalloc(na, sizeof(unsigned char));
414
415 for (j = 0, k = 0; k < nr; ++k) {
416
417 if (answers[k].type == RFC1035_TYPE_A) {
418 if (answers[k].rdlength != sizeof(struct in_addr))
419 continue;
420
421 struct in_addr temp;
422 memcpy(&temp, answers[k].rdata, sizeof(struct in_addr));
423 i->addrs.in_addrs[j] = temp;
424
425 debugs(14, 3, name << " #" << j << " " << i->addrs.in_addrs[j]);
426 ++j;
427
428 } else if (Ip::EnableIpv6 && answers[k].type == RFC1035_TYPE_AAAA) {
429 if (answers[k].rdlength != sizeof(struct in6_addr))
430 continue;
431
432 struct in6_addr temp;
433 memcpy(&temp, answers[k].rdata, sizeof(struct in6_addr));
434 i->addrs.in_addrs[j] = temp;
435
436 debugs(14, 3, name << " #" << j << " " << i->addrs.in_addrs[j] );
437 ++j;
438 }
439 if (ttl == 0 || (int) answers[k].ttl < ttl)
440 ttl = answers[k].ttl;
441 }
442
443 assert(j == na);
444
445 if (na < 256)
446 i->addrs.count = (unsigned char) na;
447 else
448 i->addrs.count = 255;
449
450 if (ttl > Config.positiveDnsTtl)
451 ttl = Config.positiveDnsTtl;
452
453 if (ttl < Config.negativeDnsTtl)
454 ttl = Config.negativeDnsTtl;
455
456 i->expires = squid_curtime + ttl;
457
458 i->flags.negcached = false;
459 }
460
461 /// \ingroup IPCacheInternal
462 static void
ipcacheHandleReply(void * data,const rfc1035_rr * answers,int na,const char * error_message)463 ipcacheHandleReply(void *data, const rfc1035_rr * answers, int na, const char *error_message)
464 {
465 ipcache_entry *i;
466 static_cast<generic_cbdata *>(data)->unwrap(&i);
467 ++IpcacheStats.replies;
468 const int age = i->age();
469 statCounter.dns.svcTime.count(age);
470
471 ipcacheParse(i, answers, na, error_message);
472 ipcacheAddEntry(i);
473 ipcacheCallback(i, age);
474 }
475
476 /**
477 \ingroup IPCacheAPI
478 *
479 \param name Host to resolve.
480 \param handler Pointer to the function to be called when the reply
481 * from the IP cache (or the DNS if the IP cache misses)
482 \param handlerData Information that is passed to the handler and does not affect the IP cache.
483 *
484 * XXX: on hits and some errors, the handler is called immediately instead
485 * of scheduling an async call. This reentrant behavior means that the
486 * user job must be extra careful after calling ipcache_nbgethostbyname,
487 * especially if the handler destroys the job. Moreover, the job has
488 * no way of knowing whether the reentrant call happened.
489 * Comm::Connection setup usually protects the job by scheduling an async call,
490 * but some user code calls ipcache_nbgethostbyname directly.
491 */
492 void
ipcache_nbgethostbyname(const char * name,IPH * handler,void * handlerData)493 ipcache_nbgethostbyname(const char *name, IPH * handler, void *handlerData)
494 {
495 ipcache_entry *i = NULL;
496 const ipcache_addrs *addrs = NULL;
497 generic_cbdata *c;
498 debugs(14, 4, "ipcache_nbgethostbyname: Name '" << name << "'.");
499 ++IpcacheStats.requests;
500
501 if (name == NULL || name[0] == '\0') {
502 debugs(14, 4, "ipcache_nbgethostbyname: Invalid name!");
503 ++IpcacheStats.invalid;
504 const Dns::LookupDetails details("Invalid hostname", -1); // error, no lookup
505 if (handler)
506 handler(NULL, details, handlerData);
507 return;
508 }
509
510 if ((addrs = ipcacheCheckNumeric(name))) {
511 debugs(14, 4, "ipcache_nbgethostbyname: BYPASS for '" << name << "' (already numeric)");
512 ++IpcacheStats.numeric_hits;
513 const Dns::LookupDetails details; // no error, no lookup
514 if (handler)
515 handler(addrs, details, handlerData);
516 return;
517 }
518
519 i = ipcache_get(name);
520
521 if (NULL == i) {
522 /* miss */
523 (void) 0;
524 } else if (ipcacheExpiredEntry(i)) {
525 /* hit, but expired -- bummer */
526 ipcacheRelease(i);
527 i = NULL;
528 } else {
529 /* hit */
530 debugs(14, 4, "ipcache_nbgethostbyname: HIT for '" << name << "'");
531
532 if (i->flags.negcached)
533 ++IpcacheStats.negative_hits;
534 else
535 ++IpcacheStats.hits;
536
537 i->handler = handler;
538
539 i->handlerData = cbdataReference(handlerData);
540
541 ipcacheCallback(i, -1); // no lookup
542
543 return;
544 }
545
546 debugs(14, 5, "ipcache_nbgethostbyname: MISS for '" << name << "'");
547 ++IpcacheStats.misses;
548 i = new ipcache_entry(name);
549 i->handler = handler;
550 i->handlerData = cbdataReference(handlerData);
551 i->request_time = current_time;
552 c = new generic_cbdata(i);
553 idnsALookup(hashKeyStr(&i->hash), ipcacheHandleReply, c);
554 }
555
556 /// \ingroup IPCacheInternal
557 static void
ipcacheRegisterWithCacheManager(void)558 ipcacheRegisterWithCacheManager(void)
559 {
560 Mgr::RegisterAction("ipcache",
561 "IP Cache Stats and Contents",
562 stat_ipcache_get, 0, 1);
563 }
564
565 /**
566 \ingroup IPCacheAPI
567 *
568 * Initialize the ipcache.
569 * Is called from mainInitialize() after disk initialization
570 * and prior to the reverse FQDNCache initialization
571 */
572 void
ipcache_init(void)573 ipcache_init(void)
574 {
575 int n;
576 debugs(14, DBG_IMPORTANT, "Initializing IP Cache...");
577 memset(&IpcacheStats, '\0', sizeof(IpcacheStats));
578 lru_list = dlink_list();
579
580 static_addrs.in_addrs = static_cast<Ip::Address *>(xcalloc(1, sizeof(Ip::Address)));
581 static_addrs.in_addrs->setEmpty(); // properly setup the Ip::Address!
582 static_addrs.bad_mask = (unsigned char *)xcalloc(1, sizeof(unsigned char));
583 ipcache_high = (long) (((float) Config.ipcache.size *
584 (float) Config.ipcache.high) / (float) 100);
585 ipcache_low = (long) (((float) Config.ipcache.size *
586 (float) Config.ipcache.low) / (float) 100);
587 n = hashPrime(ipcache_high / 4);
588 ip_table = hash_create((HASHCMP *) strcmp, n, hash4);
589
590 ipcacheRegisterWithCacheManager();
591 }
592
593 /**
594 \ingroup IPCacheAPI
595 *
596 * Is different from ipcache_nbgethostbyname in that it only checks
597 * if an entry exists in the cache and does not by default contact the DNS,
598 * unless this is requested, by setting the flags.
599 *
600 \param name Host name to resolve.
601 \param flags Default is NULL, set to IP_LOOKUP_IF_MISS
602 * to explicitly perform DNS lookups.
603 *
604 \retval NULL An error occurred during lookup
605 \retval NULL No results available in cache and no lookup specified
606 \retval * Pointer to the ipcahce_addrs structure containing the lookup results
607 */
608 const ipcache_addrs *
ipcache_gethostbyname(const char * name,int flags)609 ipcache_gethostbyname(const char *name, int flags)
610 {
611 ipcache_entry *i = NULL;
612 ipcache_addrs *addrs;
613 assert(name);
614 debugs(14, 3, "ipcache_gethostbyname: '" << name << "', flags=" << std::hex << flags);
615 ++IpcacheStats.requests;
616 i = ipcache_get(name);
617
618 if (NULL == i) {
619 (void) 0;
620 } else if (ipcacheExpiredEntry(i)) {
621 ipcacheRelease(i);
622 i = NULL;
623 } else if (i->flags.negcached) {
624 ++IpcacheStats.negative_hits;
625 // ignore i->error_message: the caller just checks IP cache presence
626 return NULL;
627 } else {
628 ++IpcacheStats.hits;
629 i->lastref = squid_curtime;
630 // ignore i->error_message: the caller just checks IP cache presence
631 return &i->addrs;
632 }
633
634 /* no entry [any more] */
635
636 if ((addrs = ipcacheCheckNumeric(name))) {
637 ++IpcacheStats.numeric_hits;
638 return addrs;
639 }
640
641 ++IpcacheStats.misses;
642
643 if (flags & IP_LOOKUP_IF_MISS)
644 ipcache_nbgethostbyname(name, NULL, NULL);
645
646 return NULL;
647 }
648
649 /// \ingroup IPCacheInternal
650 static void
ipcacheStatPrint(ipcache_entry * i,StoreEntry * sentry)651 ipcacheStatPrint(ipcache_entry * i, StoreEntry * sentry)
652 {
653 int k;
654 char buf[MAX_IPSTRLEN];
655
656 if (!sentry) {
657 debugs(14, DBG_CRITICAL, HERE << "CRITICAL: sentry is NULL!");
658 return;
659 }
660
661 if (!i) {
662 debugs(14, DBG_CRITICAL, HERE << "CRITICAL: ipcache_entry is NULL!");
663 storeAppendPrintf(sentry, "CRITICAL ERROR\n");
664 return;
665 }
666
667 int count = i->addrs.count;
668
669 storeAppendPrintf(sentry, " %-32.32s %c%c %6d %6d %2d(%2d)",
670 hashKeyStr(&i->hash),
671 i->flags.fromhosts ? 'H' : ' ',
672 i->flags.negcached ? 'N' : ' ',
673 (int) (squid_curtime - i->lastref),
674 (int) ((i->flags.fromhosts ? -1 : i->expires - squid_curtime)),
675 (int) i->addrs.count,
676 (int) i->addrs.badcount);
677
678 /** \par
679 * Negative-cached entries have no IPs listed. */
680 if (i->flags.negcached) {
681 storeAppendPrintf(sentry, "\n");
682 return;
683 }
684
685 /** \par
686 * Cached entries have IPs listed with a BNF of: ip-address '-' ('OK'|'BAD') */
687 for (k = 0; k < count; ++k) {
688 /* Display tidy-up: IPv6 are so big make the list vertical */
689 if (k == 0)
690 storeAppendPrintf(sentry, " %45.45s-%3s\n",
691 i->addrs.in_addrs[k].toStr(buf,MAX_IPSTRLEN),
692 i->addrs.bad_mask[k] ? "BAD" : "OK ");
693 else
694 storeAppendPrintf(sentry, "%s %45.45s-%3s\n",
695 " ", /* blank-space indenting IP list */
696 i->addrs.in_addrs[k].toStr(buf,MAX_IPSTRLEN),
697 i->addrs.bad_mask[k] ? "BAD" : "OK ");
698 }
699 }
700
701 /**
702 \ingroup IPCacheInternal
703 *
704 * process objects list
705 */
706 void
stat_ipcache_get(StoreEntry * sentry)707 stat_ipcache_get(StoreEntry * sentry)
708 {
709 dlink_node *m;
710 assert(ip_table != NULL);
711 storeAppendPrintf(sentry, "IP Cache Statistics:\n");
712 storeAppendPrintf(sentry, "IPcache Entries In Use: %d\n",
713 ipcache_entry::UseCount());
714 storeAppendPrintf(sentry, "IPcache Entries Cached: %d\n",
715 ipcacheCount());
716 storeAppendPrintf(sentry, "IPcache Requests: %d\n",
717 IpcacheStats.requests);
718 storeAppendPrintf(sentry, "IPcache Hits: %d\n",
719 IpcacheStats.hits);
720 storeAppendPrintf(sentry, "IPcache Negative Hits: %d\n",
721 IpcacheStats.negative_hits);
722 storeAppendPrintf(sentry, "IPcache Numeric Hits: %d\n",
723 IpcacheStats.numeric_hits);
724 storeAppendPrintf(sentry, "IPcache Misses: %d\n",
725 IpcacheStats.misses);
726 storeAppendPrintf(sentry, "IPcache Retrieved A: %d\n",
727 IpcacheStats.rr_a);
728 storeAppendPrintf(sentry, "IPcache Retrieved AAAA: %d\n",
729 IpcacheStats.rr_aaaa);
730 storeAppendPrintf(sentry, "IPcache Retrieved CNAME: %d\n",
731 IpcacheStats.rr_cname);
732 storeAppendPrintf(sentry, "IPcache CNAME-Only Response: %d\n",
733 IpcacheStats.cname_only);
734 storeAppendPrintf(sentry, "IPcache Invalid Request: %d\n",
735 IpcacheStats.invalid);
736 storeAppendPrintf(sentry, "\n\n");
737 storeAppendPrintf(sentry, "IP Cache Contents:\n\n");
738 storeAppendPrintf(sentry, " %-31.31s %3s %6s %6s %4s\n",
739 "Hostname",
740 "Flg",
741 "lstref",
742 "TTL",
743 "N(b)");
744
745 for (m = lru_list.head; m; m = m->next) {
746 assert( m->next != m );
747 ipcacheStatPrint((ipcache_entry *)m->data, sentry);
748 }
749 }
750
751 /// \ingroup IPCacheAPI
752 void
ipcacheInvalidate(const char * name)753 ipcacheInvalidate(const char *name)
754 {
755 ipcache_entry *i;
756
757 if ((i = ipcache_get(name)) == NULL)
758 return;
759
760 i->expires = squid_curtime;
761
762 /*
763 * NOTE, don't call ipcacheRelease here because we might be here due
764 * to a thread started from a callback.
765 */
766 }
767
768 /// \ingroup IPCacheAPI
769 void
ipcacheInvalidateNegative(const char * name)770 ipcacheInvalidateNegative(const char *name)
771 {
772 ipcache_entry *i;
773
774 if ((i = ipcache_get(name)) == NULL)
775 return;
776
777 if (i->flags.negcached)
778 i->expires = squid_curtime;
779
780 /*
781 * NOTE, don't call ipcacheRelease here because we might be here due
782 * to a thread started from a callback.
783 */
784 }
785
786 /// \ingroup IPCacheAPI
787 ipcache_addrs *
ipcacheCheckNumeric(const char * name)788 ipcacheCheckNumeric(const char *name)
789 {
790 Ip::Address ip;
791 /* check if it's already a IP address in text form. */
792
793 /* it may be IPv6-wrapped */
794 if (name[0] == '[') {
795 char *tmp = xstrdup(&name[1]);
796 tmp[strlen(tmp)-1] = '\0';
797 if (!(ip = tmp)) {
798 delete tmp;
799 return NULL;
800 }
801 delete tmp;
802 } else if (!(ip = name))
803 return NULL;
804
805 debugs(14, 4, "ipcacheCheckNumeric: HIT_BYPASS for '" << name << "' == " << ip );
806
807 static_addrs.count = 1;
808
809 static_addrs.cur = 0;
810
811 static_addrs.in_addrs[0] = ip;
812
813 static_addrs.bad_mask[0] = FALSE;
814
815 static_addrs.badcount = 0;
816
817 return &static_addrs;
818 }
819
820 /// \ingroup IPCacheInternal
821 static void
ipcacheLockEntry(ipcache_entry * i)822 ipcacheLockEntry(ipcache_entry * i)
823 {
824 if (i->locks++ == 0) {
825 dlinkDelete(&i->lru, &lru_list);
826 dlinkAdd(i, &i->lru, &lru_list);
827 }
828 }
829
830 /// \ingroup IPCacheInternal
831 static void
ipcacheUnlockEntry(ipcache_entry * i)832 ipcacheUnlockEntry(ipcache_entry * i)
833 {
834 if (i->locks < 1) {
835 debugs(14, DBG_IMPORTANT, "WARNING: ipcacheEntry unlocked with no lock! locks=" << i->locks);
836 return;
837 }
838
839 -- i->locks;
840
841 if (ipcacheExpiredEntry(i))
842 ipcacheRelease(i);
843 }
844
845 /// \ingroup IPCacheAPI
846 void
ipcacheCycleAddr(const char * name,ipcache_addrs * ia)847 ipcacheCycleAddr(const char *name, ipcache_addrs * ia)
848 {
849 ipcache_entry *i;
850 unsigned char k;
851 assert(name || ia);
852
853 if (NULL == ia) {
854 if ((i = ipcache_get(name)) == NULL)
855 return;
856
857 if (i->flags.negcached)
858 return;
859
860 ia = &i->addrs;
861 }
862
863 for (k = 0; k < ia->count; ++k) {
864 if (++ia->cur == ia->count)
865 ia->cur = 0;
866
867 if (!ia->bad_mask[ia->cur])
868 break;
869 }
870
871 if (k == ia->count) {
872 /* All bad, reset to All good */
873 debugs(14, 3, "ipcacheCycleAddr: Changing ALL " << name << " addrs from BAD to OK");
874
875 for (k = 0; k < ia->count; ++k)
876 ia->bad_mask[k] = 0;
877
878 ia->badcount = 0;
879
880 ia->cur = 0;
881 }
882
883 /* NP: zero-based so we increase the human-readable number of our position */
884 debugs(14, 3, "ipcacheCycleAddr: " << name << " now at " << ia->in_addrs[ia->cur] << " (" << (ia->cur+1) << " of " << ia->count << ")");
885 }
886
887 /**
888 \ingroup IPCacheAPI
889 *
890 \param name domain name to have an IP marked bad
891 \param addr specific addres to be marked bad
892 */
893 void
ipcacheMarkBadAddr(const char * name,const Ip::Address & addr)894 ipcacheMarkBadAddr(const char *name, const Ip::Address &addr)
895 {
896 ipcache_entry *i;
897 ipcache_addrs *ia;
898 int k;
899
900 /** Does nothing if the domain name does not exist. */
901 if ((i = ipcache_get(name)) == NULL)
902 return;
903
904 ia = &i->addrs;
905
906 for (k = 0; k < (int) ia->count; ++k) {
907 if (addr == ia->in_addrs[k] )
908 break;
909 }
910
911 /** Does nothing if the IP does not exist for the doamin. */
912 if (k == (int) ia->count)
913 return;
914
915 /** Marks the given address as BAD */
916 if (!ia->bad_mask[k]) {
917 ia->bad_mask[k] = TRUE;
918 ++ia->badcount;
919 debugs(14, 2, "ipcacheMarkBadAddr: " << name << " " << addr );
920 }
921
922 /** then calls ipcacheCycleAddr() to advance the current pointer to the next OK address. */
923 ipcacheCycleAddr(name, ia);
924 }
925
926 /// \ingroup IPCacheAPI
927 void
ipcacheMarkAllGood(const char * name)928 ipcacheMarkAllGood(const char *name)
929 {
930 ipcache_entry *i;
931 ipcache_addrs *ia;
932 int k;
933
934 if ((i = ipcache_get(name)) == NULL)
935 return;
936
937 ia = &i->addrs;
938
939 /* All bad, reset to All good */
940 debugs(14, 3, "ipcacheMarkAllGood: Changing ALL " << name << " addrs to OK (" << ia->badcount << "/" << ia->count << " bad)");
941
942 for (k = 0; k < ia->count; ++k)
943 ia->bad_mask[k] = 0;
944
945 ia->badcount = 0;
946 }
947
948 /// \ingroup IPCacheAPI
949 void
ipcacheMarkGoodAddr(const char * name,const Ip::Address & addr)950 ipcacheMarkGoodAddr(const char *name, const Ip::Address &addr)
951 {
952 ipcache_entry *i;
953 ipcache_addrs *ia;
954 int k;
955
956 if ((i = ipcache_get(name)) == NULL)
957 return;
958
959 ia = &i->addrs;
960
961 for (k = 0; k < (int) ia->count; ++k) {
962 if (addr == ia->in_addrs[k])
963 break;
964 }
965
966 if (k == (int) ia->count) /* not found */
967 return;
968
969 if (!ia->bad_mask[k]) /* already OK */
970 return;
971
972 ia->bad_mask[k] = FALSE;
973
974 -- ia->badcount;
975
976 debugs(14, 2, "ipcacheMarkGoodAddr: " << name << " " << addr );
977 }
978
979 /// \ingroup IPCacheInternal
980 static void
ipcacheFreeEntry(void * data)981 ipcacheFreeEntry(void *data)
982 {
983 ipcache_entry *i = (ipcache_entry *)data;
984 delete i;
985 }
986
~ipcache_entry()987 ipcache_entry::~ipcache_entry()
988 {
989 xfree(addrs.in_addrs);
990 xfree(addrs.bad_mask);
991 xfree(error_message);
992 xfree(hash.key);
993 }
994
995 /// \ingroup IPCacheAPI
996 void
ipcacheFreeMemory(void)997 ipcacheFreeMemory(void)
998 {
999 hashFreeItems(ip_table, ipcacheFreeEntry);
1000 hashFreeMemory(ip_table);
1001 ip_table = NULL;
1002 }
1003
1004 /**
1005 \ingroup IPCacheAPI
1006 *
1007 * Recalculate IP cache size upon reconfigure.
1008 * Is called to clear the IPCache's data structures,
1009 * cancel all pending requests.
1010 */
1011 void
ipcache_restart(void)1012 ipcache_restart(void)
1013 {
1014 ipcache_high = (long) (((float) Config.ipcache.size *
1015 (float) Config.ipcache.high) / (float) 100);
1016 ipcache_low = (long) (((float) Config.ipcache.size *
1017 (float) Config.ipcache.low) / (float) 100);
1018 purge_entries_fromhosts();
1019 }
1020
1021 /**
1022 \ingroup IPCacheAPI
1023 *
1024 * Adds a "static" entry from /etc/hosts
1025 *
1026 \param name Hostname to be linked with IP
1027 \param ipaddr IP Address to be cached.
1028 *
1029 \retval 0 Success.
1030 \retval 1 IP address is invalid or other error.
1031 */
1032 int
ipcacheAddEntryFromHosts(const char * name,const char * ipaddr)1033 ipcacheAddEntryFromHosts(const char *name, const char *ipaddr)
1034 {
1035 ipcache_entry *i;
1036
1037 Ip::Address ip;
1038
1039 if (!(ip = ipaddr)) {
1040 if (strchr(ipaddr, ':') && strspn(ipaddr, "0123456789abcdefABCDEF:") == strlen(ipaddr)) {
1041 debugs(14, 3, "ipcacheAddEntryFromHosts: Skipping IPv6 address '" << ipaddr << "'");
1042 } else {
1043 debugs(14, DBG_IMPORTANT, "ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr << "'");
1044 }
1045
1046 return 1;
1047 }
1048
1049 if ((i = ipcache_get(name))) {
1050 if (1 == i->flags.fromhosts) {
1051 ipcacheUnlockEntry(i);
1052 } else if (i->locks > 0) {
1053 debugs(14, DBG_IMPORTANT, "ipcacheAddEntryFromHosts: can't add static entry for locked name '" << name << "'");
1054 return 1;
1055 } else {
1056 ipcacheRelease(i);
1057 }
1058 }
1059
1060 i = new ipcache_entry(name);
1061 i->addrs.count = 1;
1062 i->addrs.cur = 0;
1063 i->addrs.badcount = 0;
1064
1065 i->addrs.in_addrs = static_cast<Ip::Address *>(xcalloc(1, sizeof(Ip::Address)));
1066 i->addrs.bad_mask = (unsigned char *)xcalloc(1, sizeof(unsigned char));
1067 i->addrs.in_addrs[0] = ip;
1068 i->addrs.bad_mask[0] = FALSE;
1069 i->flags.fromhosts = true;
1070 ipcacheAddEntry(i);
1071 ipcacheLockEntry(i);
1072 return 0;
1073 }
1074
1075 #if SQUID_SNMP
1076 /**
1077 \ingroup IPCacheAPI
1078 *
1079 * The function to return the ip cache statistics to via SNMP
1080 */
1081 variable_list *
snmp_netIpFn(variable_list * Var,snint * ErrP)1082 snmp_netIpFn(variable_list * Var, snint * ErrP)
1083 {
1084 variable_list *Answer = NULL;
1085 MemBuf tmp;
1086 debugs(49, 5, "snmp_netIpFn: Processing request:" << snmpDebugOid(Var->name, Var->name_length, tmp));
1087 *ErrP = SNMP_ERR_NOERROR;
1088
1089 switch (Var->name[LEN_SQ_NET + 1]) {
1090
1091 case IP_ENT:
1092 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1093 ipcacheCount(),
1094 SMI_GAUGE32);
1095 break;
1096
1097 case IP_REQ:
1098 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1099 IpcacheStats.requests,
1100 SMI_COUNTER32);
1101 break;
1102
1103 case IP_HITS:
1104 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1105 IpcacheStats.hits,
1106 SMI_COUNTER32);
1107 break;
1108
1109 case IP_PENDHIT:
1110 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1111 0,
1112 SMI_GAUGE32);
1113 break;
1114
1115 case IP_NEGHIT:
1116 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1117 IpcacheStats.negative_hits,
1118 SMI_COUNTER32);
1119 break;
1120
1121 case IP_MISS:
1122 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1123 IpcacheStats.misses,
1124 SMI_COUNTER32);
1125 break;
1126
1127 case IP_GHBN:
1128 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1129 0, /* deprecated */
1130 SMI_COUNTER32);
1131 break;
1132
1133 case IP_LOC:
1134 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1135 0, /* deprecated */
1136 SMI_COUNTER32);
1137 break;
1138
1139 default:
1140 *ErrP = SNMP_ERR_NOSUCHNAME;
1141 snmp_var_free(Answer);
1142 return (NULL);
1143 }
1144
1145 return Answer;
1146 }
1147
1148 #endif /*SQUID_SNMP */
1149
1150