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