]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ipcache.cc
Merged from trunk
[thirdparty/squid.git] / src / ipcache.cc
1 /*
2 * Copyright (C) 1996-2014 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 "DnsLookupDetails.h"
16 #include "event.h"
17 #include "ip/Address.h"
18 #include "ip/tools.h"
19 #include "ipcache.h"
20 #include "mgr/Registration.h"
21 #include "rfc3596.h"
22 #include "SquidConfig.h"
23 #include "SquidDns.h"
24 #include "SquidTime.h"
25 #include "StatCounters.h"
26 #include "Store.h"
27 #include "util.h"
28 #include "wordlist.h"
29
30 #if SQUID_SNMP
31 #include "snmp_core.h"
32 #endif
33
34 /**
35 \defgroup IPCacheAPI IP Cache API
36 \ingroup Components
37 \section Introduction Introduction
38 \par
39 * The IP cache is a built-in component of squid providing
40 * Hostname to IP-Number translation functionality and managing
41 * the involved data-structures. Efficiency concerns require
42 * mechanisms that allow non-blocking access to these mappings.
43 * The IP cache usually doesn't block on a request except for
44 * special cases where this is desired (see below).
45 *
46 \todo IP Cache should have its own API *.h header file.
47 */
48
49 /**
50 \defgroup IPCacheInternal IP Cache Internals
51 \ingroup IPCacheAPI
52 \todo when IP cache is provided as a class. These sub-groups will be obsolete
53 * for now they are used to seperate the public and private functions.
54 * with the private ones all being in IPCachInternal and public in IPCacheAPI
55 *
56 \section InternalOperation Internal Operation
57 *
58 * Internally, the execution flow is as follows: On a miss,
59 * ipcache_getnbhostbyname checks whether a request for
60 * this name is already pending, and if positive, it creates
61 * a new entry using ipcacheAddNew with the IP_PENDING
62 * flag set . Then it calls ipcacheAddPending to add a
63 * request to the queue together with data and handler. Else,
64 * ipcache_dnsDispatch() is called to directly create a
65 * DNS query or to ipcacheEnqueue() if all no DNS port
66 * is free. ipcache_call_pending() is called regularly
67 * to walk down the pending list and call handlers. LRU clean-up
68 * is performed through ipcache_purgelru() according to
69 * the ipcache_high threshold.
70 */
71
72 /**
73 \ingroup IPCacheAPI
74 *
75 * The data structure used for storing name-address mappings
76 * is a small hashtable (static hash_table *ip_table),
77 * where structures of type ipcache_entry whose most
78 * interesting members are:
79 */
80 class ipcache_entry
81 {
82 public:
83 hash_link hash; /* must be first */
84 time_t lastref;
85 time_t expires;
86 ipcache_addrs addrs;
87 IPH *handler;
88 void *handlerData;
89 char *error_message;
90
91 struct timeval request_time;
92 dlink_node lru;
93 unsigned short locks;
94 struct {
95 bool negcached;
96 bool fromhosts;
97 } flags;
98
99 int age() const; ///< time passed since request_time or -1 if unknown
100 };
101
102 /// \ingroup IPCacheInternal
103 static struct _ipcache_stats {
104 int requests;
105 int replies;
106 int hits;
107 int misses;
108 int negative_hits;
109 int numeric_hits;
110 int rr_a;
111 int rr_aaaa;
112 int rr_cname;
113 int cname_only;
114 int invalid;
115 } IpcacheStats;
116
117 /// \ingroup IPCacheInternal
118 static dlink_list lru_list;
119
120 // forward-decls
121 static void stat_ipcache_get(StoreEntry *);
122
123 static FREE ipcacheFreeEntry;
124 static IDNSCB ipcacheHandleReply;
125 static int ipcacheExpiredEntry(ipcache_entry *);
126 static int ipcacheParse(ipcache_entry *, const rfc1035_rr *, int, const char *error);
127 static ipcache_entry *ipcache_get(const char *);
128 static void ipcacheLockEntry(ipcache_entry *);
129 static void ipcacheStatPrint(ipcache_entry *, StoreEntry *);
130 static void ipcacheUnlockEntry(ipcache_entry *);
131 static void ipcacheRelease(ipcache_entry *, bool dofree = true);
132
133 /// \ingroup IPCacheInternal
134 static ipcache_addrs static_addrs;
135 /// \ingroup IPCacheInternal
136 static hash_table *ip_table = NULL;
137
138 /// \ingroup IPCacheInternal
139 static long ipcache_low = 180;
140 /// \ingroup IPCacheInternal
141 static long ipcache_high = 200;
142
143 #if LIBRESOLV_DNS_TTL_HACK
144 extern int _dns_ttl_;
145 #endif
146
147 /// \ingroup IPCacheInternal
148 inline int ipcacheCount() { return ip_table ? ip_table->count : 0; }
149
150 int
151 ipcache_entry::age() const
152 {
153 return request_time.tv_sec ? tvSubMsec(request_time, current_time) : -1;
154 }
155
156 /**
157 \ingroup IPCacheInternal
158 *
159 * removes the given ipcache entry
160 */
161 static void
162 ipcacheRelease(ipcache_entry * i, bool dofree)
163 {
164 if (!i) {
165 debugs(14, DBG_CRITICAL, "ipcacheRelease: Releasing entry with i=<NULL>");
166 return;
167 }
168
169 if (!i || !i->hash.key) {
170 debugs(14, DBG_CRITICAL, "ipcacheRelease: Releasing entry without hash link!");
171 return;
172 }
173
174 debugs(14, 3, "ipcacheRelease: Releasing entry for '" << (const char *) i->hash.key << "'");
175
176 hash_remove_link(ip_table, (hash_link *) i);
177 dlinkDelete(&i->lru, &lru_list);
178 if (dofree)
179 ipcacheFreeEntry(i);
180 }
181
182 /// \ingroup IPCacheInternal
183 static ipcache_entry *
184 ipcache_get(const char *name)
185 {
186 if (ip_table != NULL)
187 return (ipcache_entry *) hash_lookup(ip_table, name);
188 else
189 return NULL;
190 }
191
192 /// \ingroup IPCacheInternal
193 static int
194 ipcacheExpiredEntry(ipcache_entry * i)
195 {
196 /* all static entries are locked, so this takes care of them too */
197
198 if (i->locks != 0)
199 return 0;
200
201 if (i->addrs.count == 0)
202 if (0 == i->flags.negcached)
203 return 1;
204
205 if (i->expires > squid_curtime)
206 return 0;
207
208 return 1;
209 }
210
211 /// \ingroup IPCacheAPI
212 void
213 ipcache_purgelru(void *)
214 {
215 dlink_node *m;
216 dlink_node *prev = NULL;
217 ipcache_entry *i;
218 int removed = 0;
219 eventAdd("ipcache_purgelru", ipcache_purgelru, NULL, 10.0, 1);
220
221 for (m = lru_list.tail; m; m = prev) {
222 if (ipcacheCount() < ipcache_low)
223 break;
224
225 prev = m->prev;
226
227 i = (ipcache_entry *)m->data;
228
229 if (i->locks != 0)
230 continue;
231
232 ipcacheRelease(i);
233
234 ++removed;
235 }
236
237 debugs(14, 9, "ipcache_purgelru: removed " << removed << " entries");
238 }
239
240 /**
241 \ingroup IPCacheInternal
242 *
243 * purges entries added from /etc/hosts (or whatever).
244 */
245 static void
246 purge_entries_fromhosts(void)
247 {
248 dlink_node *m = lru_list.head;
249 ipcache_entry *i = NULL, *t;
250
251 while (m) {
252 if (i != NULL) { /* need to delay deletion */
253 ipcacheRelease(i); /* we just override locks */
254 i = NULL;
255 }
256
257 t = (ipcache_entry*)m->data;
258
259 if (t->flags.fromhosts)
260 i = t;
261
262 m = m->next;
263 }
264
265 if (i != NULL)
266 ipcacheRelease(i);
267 }
268
269 /**
270 \ingroup IPCacheInternal
271 *
272 * create blank ipcache_entry
273 */
274 static ipcache_entry *
275 ipcacheCreateEntry(const char *name)
276 {
277 static ipcache_entry *i;
278 i = (ipcache_entry *)memAllocate(MEM_IPCACHE_ENTRY);
279 i->hash.key = xstrdup(name);
280 Tolower(static_cast<char*>(i->hash.key));
281 i->expires = squid_curtime + Config.negativeDnsTtl;
282 return i;
283 }
284
285 /// \ingroup IPCacheInternal
286 static void
287 ipcacheAddEntry(ipcache_entry * i)
288 {
289 hash_link *e = (hash_link *)hash_lookup(ip_table, i->hash.key);
290
291 if (NULL != e) {
292 /* avoid colission */
293 ipcache_entry *q = (ipcache_entry *) e;
294 ipcacheRelease(q);
295 }
296
297 hash_join(ip_table, &i->hash);
298 dlinkAdd(i, &i->lru, &lru_list);
299 i->lastref = squid_curtime;
300 }
301
302 /**
303 \ingroup IPCacheInternal
304 *
305 * walks down the pending list, calling handlers
306 */
307 static void
308 ipcacheCallback(ipcache_entry *i, int wait)
309 {
310 IPH *callback = i->handler;
311 void *cbdata = NULL;
312 i->lastref = squid_curtime;
313
314 if (!i->handler)
315 return;
316
317 ipcacheLockEntry(i);
318
319 callback = i->handler;
320
321 i->handler = NULL;
322
323 if (cbdataReferenceValidDone(i->handlerData, &cbdata)) {
324 const DnsLookupDetails details(i->error_message, wait);
325 callback((i->addrs.count ? &i->addrs : NULL), details, cbdata);
326 }
327
328 ipcacheUnlockEntry(i);
329 }
330
331 /// \ingroup IPCacheAPI
332 static int
333 ipcacheParse(ipcache_entry *i, const rfc1035_rr * answers, int nr, const char *error_message)
334 {
335 int k;
336 int j = 0;
337 int na = 0;
338 int ttl = 0;
339 const char *name = (const char *)i->hash.key;
340 int cname_found = 0;
341
342 i->expires = squid_curtime + Config.negativeDnsTtl;
343 i->flags.negcached = true;
344 safe_free(i->addrs.in_addrs);
345 assert(i->addrs.in_addrs == NULL);
346 safe_free(i->addrs.bad_mask);
347 assert(i->addrs.bad_mask == NULL);
348 safe_free(i->error_message);
349 assert(i->error_message == NULL);
350 i->addrs.count = 0;
351
352 if (nr < 0) {
353 debugs(14, 3, "ipcacheParse: Lookup failed '" << error_message << "' for '" << (const char *)i->hash.key << "'");
354 i->error_message = xstrdup(error_message);
355 return -1;
356 }
357
358 if (nr == 0) {
359 debugs(14, 3, "ipcacheParse: No DNS records in response to '" << name << "'");
360 i->error_message = xstrdup("No DNS records");
361 return -1;
362 }
363
364 debugs(14, 3, "ipcacheParse: " << nr << " answers for '" << name << "'");
365 assert(answers);
366
367 for (k = 0; k < nr; ++k) {
368
369 if (Ip::EnableIpv6 && answers[k].type == RFC1035_TYPE_AAAA) {
370 if (answers[k].rdlength != sizeof(struct in6_addr)) {
371 debugs(14, DBG_IMPORTANT, "ipcacheParse: Invalid IPv6 address in response to '" << name << "'");
372 continue;
373 }
374 ++na;
375 ++IpcacheStats.rr_aaaa;
376 continue;
377 }
378
379 if (answers[k].type == RFC1035_TYPE_A) {
380 if (answers[k].rdlength != sizeof(struct in_addr)) {
381 debugs(14, DBG_IMPORTANT, "ipcacheParse: Invalid IPv4 address in response to '" << name << "'");
382 continue;
383 }
384 ++na;
385 ++IpcacheStats.rr_a;
386 continue;
387 }
388
389 /* With A and AAAA, the CNAME does not necessarily come with additional records to use. */
390 if (answers[k].type == RFC1035_TYPE_CNAME) {
391 cname_found=1;
392 ++IpcacheStats.rr_cname;
393 continue;
394 }
395
396 // otherwise its an unknown RR. debug at level 9 since we usually want to ignore these and they are common.
397 debugs(14, 9, HERE << "Unknown RR type received: type=" << answers[k].type << " starting at " << &(answers[k]) );
398 }
399 if (na == 0) {
400 debugs(14, DBG_IMPORTANT, "ipcacheParse: No Address records in response to '" << name << "'");
401 i->error_message = xstrdup("No Address records");
402 if (cname_found)
403 ++IpcacheStats.cname_only;
404 return 0;
405 }
406
407 i->addrs.in_addrs = static_cast<Ip::Address *>(xcalloc(na, sizeof(Ip::Address)));
408 for (int l = 0; l < na; ++l)
409 i->addrs.in_addrs[l].setEmpty(); // perform same init actions as constructor would.
410 i->addrs.bad_mask = (unsigned char *)xcalloc(na, sizeof(unsigned char));
411
412 for (j = 0, k = 0; k < nr; ++k) {
413
414 if (answers[k].type == RFC1035_TYPE_A) {
415 if (answers[k].rdlength != sizeof(struct in_addr))
416 continue;
417
418 struct in_addr temp;
419 memcpy(&temp, answers[k].rdata, sizeof(struct in_addr));
420 i->addrs.in_addrs[j] = temp;
421
422 debugs(14, 3, "ipcacheParse: " << name << " #" << j << " " << i->addrs.in_addrs[j]);
423 ++j;
424
425 } else if (Ip::EnableIpv6 && answers[k].type == RFC1035_TYPE_AAAA) {
426 if (answers[k].rdlength != sizeof(struct in6_addr))
427 continue;
428
429 struct in6_addr temp;
430 memcpy(&temp, answers[k].rdata, sizeof(struct in6_addr));
431 i->addrs.in_addrs[j] = temp;
432
433 debugs(14, 3, "ipcacheParse: " << name << " #" << j << " " << i->addrs.in_addrs[j] );
434 ++j;
435 }
436 if (ttl == 0 || (int) answers[k].ttl < ttl)
437 ttl = answers[k].ttl;
438 }
439
440 assert(j == na);
441
442 if (na < 256)
443 i->addrs.count = (unsigned char) na;
444 else
445 i->addrs.count = 255;
446
447 if (ttl > Config.positiveDnsTtl)
448 ttl = Config.positiveDnsTtl;
449
450 if (ttl < Config.negativeDnsTtl)
451 ttl = Config.negativeDnsTtl;
452
453 i->expires = squid_curtime + ttl;
454
455 i->flags.negcached = false;
456
457 return i->addrs.count;
458 }
459
460 /// \ingroup IPCacheInternal
461 static void
462 ipcacheHandleReply(void *data, const rfc1035_rr * answers, int na, const char *error_message)
463 {
464 ipcache_entry *i;
465 static_cast<generic_cbdata *>(data)->unwrap(&i);
466 ++IpcacheStats.replies;
467 const int age = i->age();
468 statCounter.dns.svcTime.count(age);
469
470 int done = ipcacheParse(i, answers, na, error_message);
471
472 /* If we have not produced either IPs or Error immediately, wait for recursion to finish. */
473 if (done != 0 || error_message != NULL) {
474 ipcacheAddEntry(i);
475 ipcacheCallback(i, age);
476 }
477 }
478
479 /**
480 \ingroup IPCacheAPI
481 *
482 \param name Host to resolve.
483 \param handler Pointer to the function to be called when the reply
484 * from the IP cache (or the DNS if the IP cache misses)
485 \param handlerData Information that is passed to the handler and does not affect the IP cache.
486 *
487 * XXX: on hits and some errors, the handler is called immediately instead
488 * of scheduling an async call. This reentrant behavior means that the
489 * user job must be extra careful after calling ipcache_nbgethostbyname,
490 * especially if the handler destroys the job. Moreover, the job has
491 * no way of knowing whether the reentrant call happened.
492 * Comm::Connection setup usually protects the job by scheduling an async call,
493 * but some user code calls ipcache_nbgethostbyname directly.
494 */
495 void
496 ipcache_nbgethostbyname(const char *name, IPH * handler, void *handlerData)
497 {
498 ipcache_entry *i = NULL;
499 const ipcache_addrs *addrs = NULL;
500 generic_cbdata *c;
501 debugs(14, 4, "ipcache_nbgethostbyname: Name '" << name << "'.");
502 ++IpcacheStats.requests;
503
504 if (name == NULL || name[0] == '\0') {
505 debugs(14, 4, "ipcache_nbgethostbyname: Invalid name!");
506 ++IpcacheStats.invalid;
507 const DnsLookupDetails details("Invalid hostname", -1); // error, no lookup
508 if (handler)
509 handler(NULL, details, handlerData);
510 return;
511 }
512
513 if ((addrs = ipcacheCheckNumeric(name))) {
514 debugs(14, 4, "ipcache_nbgethostbyname: BYPASS for '" << name << "' (already numeric)");
515 ++IpcacheStats.numeric_hits;
516 const DnsLookupDetails details(NULL, -1); // no error, no lookup
517 if (handler)
518 handler(addrs, details, handlerData);
519 return;
520 }
521
522 i = ipcache_get(name);
523
524 if (NULL == i) {
525 /* miss */
526 (void) 0;
527 } else if (ipcacheExpiredEntry(i)) {
528 /* hit, but expired -- bummer */
529 ipcacheRelease(i);
530 i = NULL;
531 } else {
532 /* hit */
533 debugs(14, 4, "ipcache_nbgethostbyname: HIT for '" << name << "'");
534
535 if (i->flags.negcached)
536 ++IpcacheStats.negative_hits;
537 else
538 ++IpcacheStats.hits;
539
540 i->handler = handler;
541
542 i->handlerData = cbdataReference(handlerData);
543
544 ipcacheCallback(i, -1); // no lookup
545
546 return;
547 }
548
549 debugs(14, 5, "ipcache_nbgethostbyname: MISS for '" << name << "'");
550 ++IpcacheStats.misses;
551 i = ipcacheCreateEntry(name);
552 i->handler = handler;
553 i->handlerData = cbdataReference(handlerData);
554 i->request_time = current_time;
555 c = new generic_cbdata(i);
556 idnsALookup(hashKeyStr(&i->hash), ipcacheHandleReply, c);
557 }
558
559 /// \ingroup IPCacheInternal
560 static void
561 ipcacheRegisterWithCacheManager(void)
562 {
563 Mgr::RegisterAction("ipcache",
564 "IP Cache Stats and Contents",
565 stat_ipcache_get, 0, 1);
566 }
567
568 /**
569 \ingroup IPCacheAPI
570 *
571 * Initialize the ipcache.
572 * Is called from mainInitialize() after disk initialization
573 * and prior to the reverse FQDNCache initialization
574 */
575 void
576 ipcache_init(void)
577 {
578 int n;
579 debugs(14, DBG_IMPORTANT, "Initializing IP Cache...");
580 memset(&IpcacheStats, '\0', sizeof(IpcacheStats));
581 memset(&lru_list, '\0', sizeof(lru_list));
582 memset(&static_addrs, '\0', sizeof(ipcache_addrs));
583
584 static_addrs.in_addrs = static_cast<Ip::Address *>(xcalloc(1, sizeof(Ip::Address)));
585 static_addrs.in_addrs->setEmpty(); // properly setup the Ip::Address!
586 static_addrs.bad_mask = (unsigned char *)xcalloc(1, sizeof(unsigned char));
587 ipcache_high = (long) (((float) Config.ipcache.size *
588 (float) Config.ipcache.high) / (float) 100);
589 ipcache_low = (long) (((float) Config.ipcache.size *
590 (float) Config.ipcache.low) / (float) 100);
591 n = hashPrime(ipcache_high / 4);
592 ip_table = hash_create((HASHCMP *) strcmp, n, hash4);
593 memDataInit(MEM_IPCACHE_ENTRY, "ipcache_entry", sizeof(ipcache_entry), 0);
594
595 ipcacheRegisterWithCacheManager();
596 }
597
598 /**
599 \ingroup IPCacheAPI
600 *
601 * Is different from ipcache_nbgethostbyname in that it only checks
602 * if an entry exists in the cache and does not by default contact the DNS,
603 * unless this is requested, by setting the flags.
604 *
605 \param name Host name to resolve.
606 \param flags Default is NULL, set to IP_LOOKUP_IF_MISS
607 * to explicitly perform DNS lookups.
608 *
609 \retval NULL An error occured during lookup
610 \retval NULL No results available in cache and no lookup specified
611 \retval * Pointer to the ipcahce_addrs structure containing the lookup results
612 */
613 const ipcache_addrs *
614 ipcache_gethostbyname(const char *name, int flags)
615 {
616 ipcache_entry *i = NULL;
617 ipcache_addrs *addrs;
618 assert(name);
619 debugs(14, 3, "ipcache_gethostbyname: '" << name << "', flags=" << std::hex << flags);
620 ++IpcacheStats.requests;
621 i = ipcache_get(name);
622
623 if (NULL == i) {
624 (void) 0;
625 } else if (ipcacheExpiredEntry(i)) {
626 ipcacheRelease(i);
627 i = NULL;
628 } else if (i->flags.negcached) {
629 ++IpcacheStats.negative_hits;
630 // ignore i->error_message: the caller just checks IP cache presence
631 return NULL;
632 } else {
633 ++IpcacheStats.hits;
634 i->lastref = squid_curtime;
635 // ignore i->error_message: the caller just checks IP cache presence
636 return &i->addrs;
637 }
638
639 /* no entry [any more] */
640
641 if ((addrs = ipcacheCheckNumeric(name))) {
642 ++IpcacheStats.numeric_hits;
643 return addrs;
644 }
645
646 ++IpcacheStats.misses;
647
648 if (flags & IP_LOOKUP_IF_MISS)
649 ipcache_nbgethostbyname(name, NULL, NULL);
650
651 return NULL;
652 }
653
654 /// \ingroup IPCacheInternal
655 static void
656 ipcacheStatPrint(ipcache_entry * i, StoreEntry * sentry)
657 {
658 int k;
659 char buf[MAX_IPSTRLEN];
660
661 if (!sentry) {
662 debugs(14, DBG_CRITICAL, HERE << "CRITICAL: sentry is NULL!");
663 return;
664 }
665
666 if (!i) {
667 debugs(14, DBG_CRITICAL, HERE << "CRITICAL: ipcache_entry is NULL!");
668 storeAppendPrintf(sentry, "CRITICAL ERROR\n");
669 return;
670 }
671
672 int count = i->addrs.count;
673
674 storeAppendPrintf(sentry, " %-32.32s %c%c %6d %6d %2d(%2d)",
675 hashKeyStr(&i->hash),
676 i->flags.fromhosts ? 'H' : ' ',
677 i->flags.negcached ? 'N' : ' ',
678 (int) (squid_curtime - i->lastref),
679 (int) ((i->flags.fromhosts ? -1 : i->expires - squid_curtime)),
680 (int) i->addrs.count,
681 (int) i->addrs.badcount);
682
683 /** \par
684 * Negative-cached entries have no IPs listed. */
685 if (i->flags.negcached) {
686 storeAppendPrintf(sentry, "\n");
687 return;
688 }
689
690 /** \par
691 * Cached entries have IPs listed with a BNF of: ip-address '-' ('OK'|'BAD') */
692 for (k = 0; k < count; ++k) {
693 /* Display tidy-up: IPv6 are so big make the list vertical */
694 if (k == 0)
695 storeAppendPrintf(sentry, " %45.45s-%3s\n",
696 i->addrs.in_addrs[k].toStr(buf,MAX_IPSTRLEN),
697 i->addrs.bad_mask[k] ? "BAD" : "OK ");
698 else
699 storeAppendPrintf(sentry, "%s %45.45s-%3s\n",
700 " ", /* blank-space indenting IP list */
701 i->addrs.in_addrs[k].toStr(buf,MAX_IPSTRLEN),
702 i->addrs.bad_mask[k] ? "BAD" : "OK ");
703 }
704 }
705
706 /**
707 \ingroup IPCacheInternal
708 *
709 * process objects list
710 */
711 void
712 stat_ipcache_get(StoreEntry * sentry)
713 {
714 dlink_node *m;
715 assert(ip_table != NULL);
716 storeAppendPrintf(sentry, "IP Cache Statistics:\n");
717 storeAppendPrintf(sentry, "IPcache Entries In Use: %d\n",
718 memInUse(MEM_IPCACHE_ENTRY));
719 storeAppendPrintf(sentry, "IPcache Entries Cached: %d\n",
720 ipcacheCount());
721 storeAppendPrintf(sentry, "IPcache Requests: %d\n",
722 IpcacheStats.requests);
723 storeAppendPrintf(sentry, "IPcache Hits: %d\n",
724 IpcacheStats.hits);
725 storeAppendPrintf(sentry, "IPcache Negative Hits: %d\n",
726 IpcacheStats.negative_hits);
727 storeAppendPrintf(sentry, "IPcache Numeric Hits: %d\n",
728 IpcacheStats.numeric_hits);
729 storeAppendPrintf(sentry, "IPcache Misses: %d\n",
730 IpcacheStats.misses);
731 storeAppendPrintf(sentry, "IPcache Retrieved A: %d\n",
732 IpcacheStats.rr_a);
733 storeAppendPrintf(sentry, "IPcache Retrieved AAAA: %d\n",
734 IpcacheStats.rr_aaaa);
735 storeAppendPrintf(sentry, "IPcache Retrieved CNAME: %d\n",
736 IpcacheStats.rr_cname);
737 storeAppendPrintf(sentry, "IPcache CNAME-Only Response: %d\n",
738 IpcacheStats.cname_only);
739 storeAppendPrintf(sentry, "IPcache Invalid Request: %d\n",
740 IpcacheStats.invalid);
741 storeAppendPrintf(sentry, "\n\n");
742 storeAppendPrintf(sentry, "IP Cache Contents:\n\n");
743 storeAppendPrintf(sentry, " %-31.31s %3s %6s %6s %4s\n",
744 "Hostname",
745 "Flg",
746 "lstref",
747 "TTL",
748 "N(b)");
749
750 for (m = lru_list.head; m; m = m->next) {
751 assert( m->next != m );
752 ipcacheStatPrint((ipcache_entry *)m->data, sentry);
753 }
754 }
755
756 /// \ingroup IPCacheAPI
757 void
758 ipcacheInvalidate(const char *name)
759 {
760 ipcache_entry *i;
761
762 if ((i = ipcache_get(name)) == NULL)
763 return;
764
765 i->expires = squid_curtime;
766
767 /*
768 * NOTE, don't call ipcacheRelease here because we might be here due
769 * to a thread started from a callback.
770 */
771 }
772
773 /// \ingroup IPCacheAPI
774 void
775 ipcacheInvalidateNegative(const char *name)
776 {
777 ipcache_entry *i;
778
779 if ((i = ipcache_get(name)) == NULL)
780 return;
781
782 if (i->flags.negcached)
783 i->expires = squid_curtime;
784
785 /*
786 * NOTE, don't call ipcacheRelease here because we might be here due
787 * to a thread started from a callback.
788 */
789 }
790
791 /// \ingroup IPCacheAPI
792 ipcache_addrs *
793 ipcacheCheckNumeric(const char *name)
794 {
795 Ip::Address ip;
796 /* check if it's already a IP address in text form. */
797
798 /* it may be IPv6-wrapped */
799 if (name[0] == '[') {
800 char *tmp = xstrdup(&name[1]);
801 tmp[strlen(tmp)-1] = '\0';
802 if (!(ip = tmp)) {
803 delete tmp;
804 return NULL;
805 }
806 delete tmp;
807 } else if (!(ip = name))
808 return NULL;
809
810 debugs(14, 4, "ipcacheCheckNumeric: HIT_BYPASS for '" << name << "' == " << ip );
811
812 static_addrs.count = 1;
813
814 static_addrs.cur = 0;
815
816 static_addrs.in_addrs[0] = ip;
817
818 static_addrs.bad_mask[0] = FALSE;
819
820 static_addrs.badcount = 0;
821
822 return &static_addrs;
823 }
824
825 /// \ingroup IPCacheInternal
826 static void
827 ipcacheLockEntry(ipcache_entry * i)
828 {
829 if (i->locks++ == 0) {
830 dlinkDelete(&i->lru, &lru_list);
831 dlinkAdd(i, &i->lru, &lru_list);
832 }
833 }
834
835 /// \ingroup IPCacheInternal
836 static void
837 ipcacheUnlockEntry(ipcache_entry * i)
838 {
839 if (i->locks < 1) {
840 debugs(14, DBG_IMPORTANT, "WARNING: ipcacheEntry unlocked with no lock! locks=" << i->locks);
841 return;
842 }
843
844 -- i->locks;
845
846 if (ipcacheExpiredEntry(i))
847 ipcacheRelease(i);
848 }
849
850 /// \ingroup IPCacheAPI
851 void
852 ipcacheCycleAddr(const char *name, ipcache_addrs * ia)
853 {
854 ipcache_entry *i;
855 unsigned char k;
856 assert(name || ia);
857
858 if (NULL == ia) {
859 if ((i = ipcache_get(name)) == NULL)
860 return;
861
862 if (i->flags.negcached)
863 return;
864
865 ia = &i->addrs;
866 }
867
868 for (k = 0; k < ia->count; ++k) {
869 if (++ia->cur == ia->count)
870 ia->cur = 0;
871
872 if (!ia->bad_mask[ia->cur])
873 break;
874 }
875
876 if (k == ia->count) {
877 /* All bad, reset to All good */
878 debugs(14, 3, "ipcacheCycleAddr: Changing ALL " << name << " addrs from BAD to OK");
879
880 for (k = 0; k < ia->count; ++k)
881 ia->bad_mask[k] = 0;
882
883 ia->badcount = 0;
884
885 ia->cur = 0;
886 }
887
888 /* NP: zero-based so we increase the human-readable number of our position */
889 debugs(14, 3, "ipcacheCycleAddr: " << name << " now at " << ia->in_addrs[ia->cur] << " (" << (ia->cur+1) << " of " << ia->count << ")");
890 }
891
892 /**
893 \ingroup IPCacheAPI
894 *
895 \param name domain name to have an IP marked bad
896 \param addr specific addres to be marked bad
897 */
898 void
899 ipcacheMarkBadAddr(const char *name, const Ip::Address &addr)
900 {
901 ipcache_entry *i;
902 ipcache_addrs *ia;
903 int k;
904
905 /** Does nothing if the domain name does not exist. */
906 if ((i = ipcache_get(name)) == NULL)
907 return;
908
909 ia = &i->addrs;
910
911 for (k = 0; k < (int) ia->count; ++k) {
912 if (addr == ia->in_addrs[k] )
913 break;
914 }
915
916 /** Does nothing if the IP does not exist for the doamin. */
917 if (k == (int) ia->count)
918 return;
919
920 /** Marks the given address as BAD */
921 if (!ia->bad_mask[k]) {
922 ia->bad_mask[k] = TRUE;
923 ++ia->badcount;
924 debugs(14, 2, "ipcacheMarkBadAddr: " << name << " " << addr );
925 }
926
927 /** then calls ipcacheCycleAddr() to advance the current pointer to the next OK address. */
928 ipcacheCycleAddr(name, ia);
929 }
930
931 /// \ingroup IPCacheAPI
932 void
933 ipcacheMarkAllGood(const char *name)
934 {
935 ipcache_entry *i;
936 ipcache_addrs *ia;
937 int k;
938
939 if ((i = ipcache_get(name)) == NULL)
940 return;
941
942 ia = &i->addrs;
943
944 /* All bad, reset to All good */
945 debugs(14, 3, "ipcacheMarkAllGood: Changing ALL " << name << " addrs to OK (" << ia->badcount << "/" << ia->count << " bad)");
946
947 for (k = 0; k < ia->count; ++k)
948 ia->bad_mask[k] = 0;
949
950 ia->badcount = 0;
951 }
952
953 /// \ingroup IPCacheAPI
954 void
955 ipcacheMarkGoodAddr(const char *name, const Ip::Address &addr)
956 {
957 ipcache_entry *i;
958 ipcache_addrs *ia;
959 int k;
960
961 if ((i = ipcache_get(name)) == NULL)
962 return;
963
964 ia = &i->addrs;
965
966 for (k = 0; k < (int) ia->count; ++k) {
967 if (addr == ia->in_addrs[k])
968 break;
969 }
970
971 if (k == (int) ia->count) /* not found */
972 return;
973
974 if (!ia->bad_mask[k]) /* already OK */
975 return;
976
977 ia->bad_mask[k] = FALSE;
978
979 -- ia->badcount;
980
981 debugs(14, 2, "ipcacheMarkGoodAddr: " << name << " " << addr );
982 }
983
984 /// \ingroup IPCacheInternal
985 static void
986 ipcacheFreeEntry(void *data)
987 {
988 ipcache_entry *i = (ipcache_entry *)data;
989 safe_free(i->addrs.in_addrs);
990 safe_free(i->addrs.bad_mask);
991 safe_free(i->hash.key);
992 safe_free(i->error_message);
993 memFree(i, MEM_IPCACHE_ENTRY);
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 = ipcacheCreateEntry(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