]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ipcache.cc
Merge from trunk
[thirdparty/squid.git] / src / ipcache.cc
1 /*
2 * Copyright (C) 1996-2016 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 seperate 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 {
99 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
153 inline int ipcacheCount() { return ip_table ? ip_table->count : 0; }
154
155 int
156 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
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 *
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
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
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
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
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
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
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
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
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
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
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
573 ipcache_init(void)
574 {
575 int n;
576 debugs(14, DBG_IMPORTANT, "Initializing IP Cache...");
577 memset(&IpcacheStats, '\0', sizeof(IpcacheStats));
578 memset(&lru_list, '\0', sizeof(lru_list));
579 memset(&static_addrs, '\0', sizeof(ipcache_addrs));
580
581 static_addrs.in_addrs = static_cast<Ip::Address *>(xcalloc(1, sizeof(Ip::Address)));
582 static_addrs.in_addrs->setEmpty(); // properly setup the Ip::Address!
583 static_addrs.bad_mask = (unsigned char *)xcalloc(1, sizeof(unsigned char));
584 ipcache_high = (long) (((float) Config.ipcache.size *
585 (float) Config.ipcache.high) / (float) 100);
586 ipcache_low = (long) (((float) Config.ipcache.size *
587 (float) Config.ipcache.low) / (float) 100);
588 n = hashPrime(ipcache_high / 4);
589 ip_table = hash_create((HASHCMP *) strcmp, n, hash4);
590
591 ipcacheRegisterWithCacheManager();
592 }
593
594 /**
595 \ingroup IPCacheAPI
596 *
597 * Is different from ipcache_nbgethostbyname in that it only checks
598 * if an entry exists in the cache and does not by default contact the DNS,
599 * unless this is requested, by setting the flags.
600 *
601 \param name Host name to resolve.
602 \param flags Default is NULL, set to IP_LOOKUP_IF_MISS
603 * to explicitly perform DNS lookups.
604 *
605 \retval NULL An error occured during lookup
606 \retval NULL No results available in cache and no lookup specified
607 \retval * Pointer to the ipcahce_addrs structure containing the lookup results
608 */
609 const ipcache_addrs *
610 ipcache_gethostbyname(const char *name, int flags)
611 {
612 ipcache_entry *i = NULL;
613 ipcache_addrs *addrs;
614 assert(name);
615 debugs(14, 3, "ipcache_gethostbyname: '" << name << "', flags=" << std::hex << flags);
616 ++IpcacheStats.requests;
617 i = ipcache_get(name);
618
619 if (NULL == i) {
620 (void) 0;
621 } else if (ipcacheExpiredEntry(i)) {
622 ipcacheRelease(i);
623 i = NULL;
624 } else if (i->flags.negcached) {
625 ++IpcacheStats.negative_hits;
626 // ignore i->error_message: the caller just checks IP cache presence
627 return NULL;
628 } else {
629 ++IpcacheStats.hits;
630 i->lastref = squid_curtime;
631 // ignore i->error_message: the caller just checks IP cache presence
632 return &i->addrs;
633 }
634
635 /* no entry [any more] */
636
637 if ((addrs = ipcacheCheckNumeric(name))) {
638 ++IpcacheStats.numeric_hits;
639 return addrs;
640 }
641
642 ++IpcacheStats.misses;
643
644 if (flags & IP_LOOKUP_IF_MISS)
645 ipcache_nbgethostbyname(name, NULL, NULL);
646
647 return NULL;
648 }
649
650 /// \ingroup IPCacheInternal
651 static void
652 ipcacheStatPrint(ipcache_entry * i, StoreEntry * sentry)
653 {
654 int k;
655 char buf[MAX_IPSTRLEN];
656
657 if (!sentry) {
658 debugs(14, DBG_CRITICAL, HERE << "CRITICAL: sentry is NULL!");
659 return;
660 }
661
662 if (!i) {
663 debugs(14, DBG_CRITICAL, HERE << "CRITICAL: ipcache_entry is NULL!");
664 storeAppendPrintf(sentry, "CRITICAL ERROR\n");
665 return;
666 }
667
668 int count = i->addrs.count;
669
670 storeAppendPrintf(sentry, " %-32.32s %c%c %6d %6d %2d(%2d)",
671 hashKeyStr(&i->hash),
672 i->flags.fromhosts ? 'H' : ' ',
673 i->flags.negcached ? 'N' : ' ',
674 (int) (squid_curtime - i->lastref),
675 (int) ((i->flags.fromhosts ? -1 : i->expires - squid_curtime)),
676 (int) i->addrs.count,
677 (int) i->addrs.badcount);
678
679 /** \par
680 * Negative-cached entries have no IPs listed. */
681 if (i->flags.negcached) {
682 storeAppendPrintf(sentry, "\n");
683 return;
684 }
685
686 /** \par
687 * Cached entries have IPs listed with a BNF of: ip-address '-' ('OK'|'BAD') */
688 for (k = 0; k < count; ++k) {
689 /* Display tidy-up: IPv6 are so big make the list vertical */
690 if (k == 0)
691 storeAppendPrintf(sentry, " %45.45s-%3s\n",
692 i->addrs.in_addrs[k].toStr(buf,MAX_IPSTRLEN),
693 i->addrs.bad_mask[k] ? "BAD" : "OK ");
694 else
695 storeAppendPrintf(sentry, "%s %45.45s-%3s\n",
696 " ", /* blank-space indenting IP list */
697 i->addrs.in_addrs[k].toStr(buf,MAX_IPSTRLEN),
698 i->addrs.bad_mask[k] ? "BAD" : "OK ");
699 }
700 }
701
702 /**
703 \ingroup IPCacheInternal
704 *
705 * process objects list
706 */
707 void
708 stat_ipcache_get(StoreEntry * sentry)
709 {
710 dlink_node *m;
711 assert(ip_table != NULL);
712 storeAppendPrintf(sentry, "IP Cache Statistics:\n");
713 storeAppendPrintf(sentry, "IPcache Entries In Use: %d\n",
714 ipcache_entry::UseCount());
715 storeAppendPrintf(sentry, "IPcache Entries Cached: %d\n",
716 ipcacheCount());
717 storeAppendPrintf(sentry, "IPcache Requests: %d\n",
718 IpcacheStats.requests);
719 storeAppendPrintf(sentry, "IPcache Hits: %d\n",
720 IpcacheStats.hits);
721 storeAppendPrintf(sentry, "IPcache Negative Hits: %d\n",
722 IpcacheStats.negative_hits);
723 storeAppendPrintf(sentry, "IPcache Numeric Hits: %d\n",
724 IpcacheStats.numeric_hits);
725 storeAppendPrintf(sentry, "IPcache Misses: %d\n",
726 IpcacheStats.misses);
727 storeAppendPrintf(sentry, "IPcache Retrieved A: %d\n",
728 IpcacheStats.rr_a);
729 storeAppendPrintf(sentry, "IPcache Retrieved AAAA: %d\n",
730 IpcacheStats.rr_aaaa);
731 storeAppendPrintf(sentry, "IPcache Retrieved CNAME: %d\n",
732 IpcacheStats.rr_cname);
733 storeAppendPrintf(sentry, "IPcache CNAME-Only Response: %d\n",
734 IpcacheStats.cname_only);
735 storeAppendPrintf(sentry, "IPcache Invalid Request: %d\n",
736 IpcacheStats.invalid);
737 storeAppendPrintf(sentry, "\n\n");
738 storeAppendPrintf(sentry, "IP Cache Contents:\n\n");
739 storeAppendPrintf(sentry, " %-31.31s %3s %6s %6s %4s\n",
740 "Hostname",
741 "Flg",
742 "lstref",
743 "TTL",
744 "N(b)");
745
746 for (m = lru_list.head; m; m = m->next) {
747 assert( m->next != m );
748 ipcacheStatPrint((ipcache_entry *)m->data, sentry);
749 }
750 }
751
752 /// \ingroup IPCacheAPI
753 void
754 ipcacheInvalidate(const char *name)
755 {
756 ipcache_entry *i;
757
758 if ((i = ipcache_get(name)) == NULL)
759 return;
760
761 i->expires = squid_curtime;
762
763 /*
764 * NOTE, don't call ipcacheRelease here because we might be here due
765 * to a thread started from a callback.
766 */
767 }
768
769 /// \ingroup IPCacheAPI
770 void
771 ipcacheInvalidateNegative(const char *name)
772 {
773 ipcache_entry *i;
774
775 if ((i = ipcache_get(name)) == NULL)
776 return;
777
778 if (i->flags.negcached)
779 i->expires = squid_curtime;
780
781 /*
782 * NOTE, don't call ipcacheRelease here because we might be here due
783 * to a thread started from a callback.
784 */
785 }
786
787 /// \ingroup IPCacheAPI
788 ipcache_addrs *
789 ipcacheCheckNumeric(const char *name)
790 {
791 Ip::Address ip;
792 /* check if it's already a IP address in text form. */
793
794 /* it may be IPv6-wrapped */
795 if (name[0] == '[') {
796 char *tmp = xstrdup(&name[1]);
797 tmp[strlen(tmp)-1] = '\0';
798 if (!(ip = tmp)) {
799 delete tmp;
800 return NULL;
801 }
802 delete tmp;
803 } else if (!(ip = name))
804 return NULL;
805
806 debugs(14, 4, "ipcacheCheckNumeric: HIT_BYPASS for '" << name << "' == " << ip );
807
808 static_addrs.count = 1;
809
810 static_addrs.cur = 0;
811
812 static_addrs.in_addrs[0] = ip;
813
814 static_addrs.bad_mask[0] = FALSE;
815
816 static_addrs.badcount = 0;
817
818 return &static_addrs;
819 }
820
821 /// \ingroup IPCacheInternal
822 static void
823 ipcacheLockEntry(ipcache_entry * i)
824 {
825 if (i->locks++ == 0) {
826 dlinkDelete(&i->lru, &lru_list);
827 dlinkAdd(i, &i->lru, &lru_list);
828 }
829 }
830
831 /// \ingroup IPCacheInternal
832 static void
833 ipcacheUnlockEntry(ipcache_entry * i)
834 {
835 if (i->locks < 1) {
836 debugs(14, DBG_IMPORTANT, "WARNING: ipcacheEntry unlocked with no lock! locks=" << i->locks);
837 return;
838 }
839
840 -- i->locks;
841
842 if (ipcacheExpiredEntry(i))
843 ipcacheRelease(i);
844 }
845
846 /// \ingroup IPCacheAPI
847 void
848 ipcacheCycleAddr(const char *name, ipcache_addrs * ia)
849 {
850 ipcache_entry *i;
851 unsigned char k;
852 assert(name || ia);
853
854 if (NULL == ia) {
855 if ((i = ipcache_get(name)) == NULL)
856 return;
857
858 if (i->flags.negcached)
859 return;
860
861 ia = &i->addrs;
862 }
863
864 for (k = 0; k < ia->count; ++k) {
865 if (++ia->cur == ia->count)
866 ia->cur = 0;
867
868 if (!ia->bad_mask[ia->cur])
869 break;
870 }
871
872 if (k == ia->count) {
873 /* All bad, reset to All good */
874 debugs(14, 3, "ipcacheCycleAddr: Changing ALL " << name << " addrs from BAD to OK");
875
876 for (k = 0; k < ia->count; ++k)
877 ia->bad_mask[k] = 0;
878
879 ia->badcount = 0;
880
881 ia->cur = 0;
882 }
883
884 /* NP: zero-based so we increase the human-readable number of our position */
885 debugs(14, 3, "ipcacheCycleAddr: " << name << " now at " << ia->in_addrs[ia->cur] << " (" << (ia->cur+1) << " of " << ia->count << ")");
886 }
887
888 /**
889 \ingroup IPCacheAPI
890 *
891 \param name domain name to have an IP marked bad
892 \param addr specific addres to be marked bad
893 */
894 void
895 ipcacheMarkBadAddr(const char *name, const Ip::Address &addr)
896 {
897 ipcache_entry *i;
898 ipcache_addrs *ia;
899 int k;
900
901 /** Does nothing if the domain name does not exist. */
902 if ((i = ipcache_get(name)) == NULL)
903 return;
904
905 ia = &i->addrs;
906
907 for (k = 0; k < (int) ia->count; ++k) {
908 if (addr == ia->in_addrs[k] )
909 break;
910 }
911
912 /** Does nothing if the IP does not exist for the doamin. */
913 if (k == (int) ia->count)
914 return;
915
916 /** Marks the given address as BAD */
917 if (!ia->bad_mask[k]) {
918 ia->bad_mask[k] = TRUE;
919 ++ia->badcount;
920 debugs(14, 2, "ipcacheMarkBadAddr: " << name << " " << addr );
921 }
922
923 /** then calls ipcacheCycleAddr() to advance the current pointer to the next OK address. */
924 ipcacheCycleAddr(name, ia);
925 }
926
927 /// \ingroup IPCacheAPI
928 void
929 ipcacheMarkAllGood(const char *name)
930 {
931 ipcache_entry *i;
932 ipcache_addrs *ia;
933 int k;
934
935 if ((i = ipcache_get(name)) == NULL)
936 return;
937
938 ia = &i->addrs;
939
940 /* All bad, reset to All good */
941 debugs(14, 3, "ipcacheMarkAllGood: Changing ALL " << name << " addrs to OK (" << ia->badcount << "/" << ia->count << " bad)");
942
943 for (k = 0; k < ia->count; ++k)
944 ia->bad_mask[k] = 0;
945
946 ia->badcount = 0;
947 }
948
949 /// \ingroup IPCacheAPI
950 void
951 ipcacheMarkGoodAddr(const char *name, const Ip::Address &addr)
952 {
953 ipcache_entry *i;
954 ipcache_addrs *ia;
955 int k;
956
957 if ((i = ipcache_get(name)) == NULL)
958 return;
959
960 ia = &i->addrs;
961
962 for (k = 0; k < (int) ia->count; ++k) {
963 if (addr == ia->in_addrs[k])
964 break;
965 }
966
967 if (k == (int) ia->count) /* not found */
968 return;
969
970 if (!ia->bad_mask[k]) /* already OK */
971 return;
972
973 ia->bad_mask[k] = FALSE;
974
975 -- ia->badcount;
976
977 debugs(14, 2, "ipcacheMarkGoodAddr: " << name << " " << addr );
978 }
979
980 /// \ingroup IPCacheInternal
981 static void
982 ipcacheFreeEntry(void *data)
983 {
984 ipcache_entry *i = (ipcache_entry *)data;
985 delete i;
986 }
987
988 ipcache_entry::~ipcache_entry()
989 {
990 xfree(addrs.in_addrs);
991 xfree(addrs.bad_mask);
992 xfree(error_message);
993 xfree(hash.key);
994 }
995
996 /// \ingroup IPCacheAPI
997 void
998 ipcacheFreeMemory(void)
999 {
1000 hashFreeItems(ip_table, ipcacheFreeEntry);
1001 hashFreeMemory(ip_table);
1002 ip_table = NULL;
1003 }
1004
1005 /**
1006 \ingroup IPCacheAPI
1007 *
1008 * Recalculate IP cache size upon reconfigure.
1009 * Is called to clear the IPCache's data structures,
1010 * cancel all pending requests.
1011 */
1012 void
1013 ipcache_restart(void)
1014 {
1015 ipcache_high = (long) (((float) Config.ipcache.size *
1016 (float) Config.ipcache.high) / (float) 100);
1017 ipcache_low = (long) (((float) Config.ipcache.size *
1018 (float) Config.ipcache.low) / (float) 100);
1019 purge_entries_fromhosts();
1020 }
1021
1022 /**
1023 \ingroup IPCacheAPI
1024 *
1025 * Adds a "static" entry from /etc/hosts
1026 *
1027 \param name Hostname to be linked with IP
1028 \param ipaddr IP Address to be cached.
1029 *
1030 \retval 0 Success.
1031 \retval 1 IP address is invalid or other error.
1032 */
1033 int
1034 ipcacheAddEntryFromHosts(const char *name, const char *ipaddr)
1035 {
1036 ipcache_entry *i;
1037
1038 Ip::Address ip;
1039
1040 if (!(ip = ipaddr)) {
1041 if (strchr(ipaddr, ':') && strspn(ipaddr, "0123456789abcdefABCDEF:") == strlen(ipaddr)) {
1042 debugs(14, 3, "ipcacheAddEntryFromHosts: Skipping IPv6 address '" << ipaddr << "'");
1043 } else {
1044 debugs(14, DBG_IMPORTANT, "ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr << "'");
1045 }
1046
1047 return 1;
1048 }
1049
1050 if ((i = ipcache_get(name))) {
1051 if (1 == i->flags.fromhosts) {
1052 ipcacheUnlockEntry(i);
1053 } else if (i->locks > 0) {
1054 debugs(14, DBG_IMPORTANT, "ipcacheAddEntryFromHosts: can't add static entry for locked name '" << name << "'");
1055 return 1;
1056 } else {
1057 ipcacheRelease(i);
1058 }
1059 }
1060
1061 i = new ipcache_entry(name);
1062 i->addrs.count = 1;
1063 i->addrs.cur = 0;
1064 i->addrs.badcount = 0;
1065
1066 i->addrs.in_addrs = static_cast<Ip::Address *>(xcalloc(1, sizeof(Ip::Address)));
1067 i->addrs.bad_mask = (unsigned char *)xcalloc(1, sizeof(unsigned char));
1068 i->addrs.in_addrs[0] = ip;
1069 i->addrs.bad_mask[0] = FALSE;
1070 i->flags.fromhosts = true;
1071 ipcacheAddEntry(i);
1072 ipcacheLockEntry(i);
1073 return 0;
1074 }
1075
1076 #if SQUID_SNMP
1077 /**
1078 \ingroup IPCacheAPI
1079 *
1080 * The function to return the ip cache statistics to via SNMP
1081 */
1082 variable_list *
1083 snmp_netIpFn(variable_list * Var, snint * ErrP)
1084 {
1085 variable_list *Answer = NULL;
1086 MemBuf tmp;
1087 debugs(49, 5, "snmp_netIpFn: Processing request:" << snmpDebugOid(Var->name, Var->name_length, tmp));
1088 *ErrP = SNMP_ERR_NOERROR;
1089
1090 switch (Var->name[LEN_SQ_NET + 1]) {
1091
1092 case IP_ENT:
1093 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1094 ipcacheCount(),
1095 SMI_GAUGE32);
1096 break;
1097
1098 case IP_REQ:
1099 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1100 IpcacheStats.requests,
1101 SMI_COUNTER32);
1102 break;
1103
1104 case IP_HITS:
1105 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1106 IpcacheStats.hits,
1107 SMI_COUNTER32);
1108 break;
1109
1110 case IP_PENDHIT:
1111 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1112 0,
1113 SMI_GAUGE32);
1114 break;
1115
1116 case IP_NEGHIT:
1117 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1118 IpcacheStats.negative_hits,
1119 SMI_COUNTER32);
1120 break;
1121
1122 case IP_MISS:
1123 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1124 IpcacheStats.misses,
1125 SMI_COUNTER32);
1126 break;
1127
1128 case IP_GHBN:
1129 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1130 0, /* deprecated */
1131 SMI_COUNTER32);
1132 break;
1133
1134 case IP_LOC:
1135 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1136 0, /* deprecated */
1137 SMI_COUNTER32);
1138 break;
1139
1140 default:
1141 *ErrP = SNMP_ERR_NOSUCHNAME;
1142 snmp_var_free(Answer);
1143 return (NULL);
1144 }
1145
1146 return Answer;
1147 }
1148
1149 #endif /*SQUID_SNMP */
1150