]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ipcache.cc
merge from trunk r12441
[thirdparty/squid.git] / src / ipcache.cc
1 /*
2 * DEBUG: section 14 IP Cache
3 * AUTHOR: Harvest Derived
4 *
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
7 *
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
16 *
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
30 *
31 */
32
33 #include "squid.h"
34 #include "cbdata.h"
35 #include "CacheManager.h"
36 #include "DnsLookupDetails.h"
37 #include "event.h"
38 #include "ip/Address.h"
39 #include "ip/tools.h"
40 #include "ipcache.h"
41 #include "Mem.h"
42 #include "mgr/Registration.h"
43 #include "rfc3596.h"
44 #include "SquidConfig.h"
45 #include "SquidDns.h"
46 #include "SquidTime.h"
47 #include "StatCounters.h"
48 #include "Store.h"
49 #include "wordlist.h"
50
51 #if SQUID_SNMP
52 #include "snmp_core.h"
53 #endif
54
55 /**
56 \defgroup IPCacheAPI IP Cache API
57 \ingroup Components
58 \section Introduction Introduction
59 \par
60 * The IP cache is a built-in component of squid providing
61 * Hostname to IP-Number translation functionality and managing
62 * the involved data-structures. Efficiency concerns require
63 * mechanisms that allow non-blocking access to these mappings.
64 * The IP cache usually doesn't block on a request except for
65 * special cases where this is desired (see below).
66 *
67 \todo IP Cache should have its own API *.h header file.
68 */
69
70 /**
71 \defgroup IPCacheInternal IP Cache Internals
72 \ingroup IPCacheAPI
73 \todo when IP cache is provided as a class. These sub-groups will be obsolete
74 * for now they are used to seperate the public and private functions.
75 * with the private ones all being in IPCachInternal and public in IPCacheAPI
76 *
77 \section InternalOperation Internal Operation
78 *
79 * Internally, the execution flow is as follows: On a miss,
80 * ipcache_getnbhostbyname checks whether a request for
81 * this name is already pending, and if positive, it creates
82 * a new entry using ipcacheAddNew with the IP_PENDING
83 * flag set . Then it calls ipcacheAddPending to add a
84 * request to the queue together with data and handler. Else,
85 * ipcache_dnsDispatch() is called to directly create a
86 * DNS query or to ipcacheEnqueue() if all no DNS port
87 * is free. ipcache_call_pending() is called regularly
88 * to walk down the pending list and call handlers. LRU clean-up
89 * is performed through ipcache_purgelru() according to
90 * the ipcache_high threshold.
91 */
92
93 /**
94 \ingroup IPCacheAPI
95 *
96 * The data structure used for storing name-address mappings
97 * is a small hashtable (static hash_table *ip_table),
98 * where structures of type ipcache_entry whose most
99 * interesting members are:
100 */
101 class ipcache_entry
102 {
103 public:
104 hash_link hash; /* must be first */
105 time_t lastref;
106 time_t expires;
107 ipcache_addrs addrs;
108 IPH *handler;
109 void *handlerData;
110 char *error_message;
111
112 struct timeval request_time;
113 dlink_node lru;
114 unsigned short locks;
115 struct {
116 unsigned int negcached:1;
117 unsigned int fromhosts:1;
118 } flags;
119
120 int age() const; ///< time passed since request_time or -1 if unknown
121 };
122
123 /// \ingroup IPCacheInternal
124 static struct _ipcache_stats {
125 int requests;
126 int replies;
127 int hits;
128 int misses;
129 int negative_hits;
130 int numeric_hits;
131 int rr_a;
132 int rr_aaaa;
133 int rr_cname;
134 int cname_only;
135 int invalid;
136 } IpcacheStats;
137
138 /// \ingroup IPCacheInternal
139 static dlink_list lru_list;
140
141 // forward-decls
142 static void stat_ipcache_get(StoreEntry *);
143
144 static FREE ipcacheFreeEntry;
145 #if USE_DNSHELPER
146 static HLPCB ipcacheHandleReply;
147 #else
148 static IDNSCB ipcacheHandleReply;
149 #endif
150 static int ipcacheExpiredEntry(ipcache_entry *);
151 #if USE_DNSHELPER
152 static int ipcacheParse(ipcache_entry *, const char *buf);
153 #else
154 static int ipcacheParse(ipcache_entry *, const rfc1035_rr *, int, const char *error);
155 #endif
156 static ipcache_entry *ipcache_get(const char *);
157 static void ipcacheLockEntry(ipcache_entry *);
158 static void ipcacheStatPrint(ipcache_entry *, StoreEntry *);
159 static void ipcacheUnlockEntry(ipcache_entry *);
160 static void ipcacheRelease(ipcache_entry *, bool dofree = true);
161
162 /// \ingroup IPCacheInternal
163 static ipcache_addrs static_addrs;
164 /// \ingroup IPCacheInternal
165 static hash_table *ip_table = NULL;
166
167 /// \ingroup IPCacheInternal
168 static long ipcache_low = 180;
169 /// \ingroup IPCacheInternal
170 static long ipcache_high = 200;
171
172 #if LIBRESOLV_DNS_TTL_HACK
173 extern int _dns_ttl_;
174 #endif
175
176 /// \ingroup IPCacheInternal
177 inline int ipcacheCount() { return ip_table ? ip_table->count : 0; }
178
179 int
180 ipcache_entry::age() const
181 {
182 return request_time.tv_sec ? tvSubMsec(request_time, current_time) : -1;
183 }
184
185 /**
186 \ingroup IPCacheInternal
187 *
188 * removes the given ipcache entry
189 */
190 static void
191 ipcacheRelease(ipcache_entry * i, bool dofree)
192 {
193 if (!i) {
194 debugs(14, DBG_CRITICAL, "ipcacheRelease: Releasing entry with i=<NULL>");
195 return;
196 }
197
198 if (!i || !i->hash.key) {
199 debugs(14, DBG_CRITICAL, "ipcacheRelease: Releasing entry without hash link!");
200 return;
201 }
202
203 debugs(14, 3, "ipcacheRelease: Releasing entry for '" << (const char *) i->hash.key << "'");
204
205 hash_remove_link(ip_table, (hash_link *) i);
206 dlinkDelete(&i->lru, &lru_list);
207 if (dofree)
208 ipcacheFreeEntry(i);
209 }
210
211 /// \ingroup IPCacheInternal
212 static ipcache_entry *
213 ipcache_get(const char *name)
214 {
215 if (ip_table != NULL)
216 return (ipcache_entry *) hash_lookup(ip_table, name);
217 else
218 return NULL;
219 }
220
221 /// \ingroup IPCacheInternal
222 static int
223 ipcacheExpiredEntry(ipcache_entry * i)
224 {
225 /* all static entries are locked, so this takes care of them too */
226
227 if (i->locks != 0)
228 return 0;
229
230 if (i->addrs.count == 0)
231 if (0 == i->flags.negcached)
232 return 1;
233
234 if (i->expires > squid_curtime)
235 return 0;
236
237 return 1;
238 }
239
240 /// \ingroup IPCacheAPI
241 void
242 ipcache_purgelru(void *voidnotused)
243 {
244 dlink_node *m;
245 dlink_node *prev = NULL;
246 ipcache_entry *i;
247 int removed = 0;
248 eventAdd("ipcache_purgelru", ipcache_purgelru, NULL, 10.0, 1);
249
250 for (m = lru_list.tail; m; m = prev) {
251 if (ipcacheCount() < ipcache_low)
252 break;
253
254 prev = m->prev;
255
256 i = (ipcache_entry *)m->data;
257
258 if (i->locks != 0)
259 continue;
260
261 ipcacheRelease(i);
262
263 ++removed;
264 }
265
266 debugs(14, 9, "ipcache_purgelru: removed " << removed << " entries");
267 }
268
269 /**
270 \ingroup IPCacheInternal
271 *
272 * purges entries added from /etc/hosts (or whatever).
273 */
274 static void
275 purge_entries_fromhosts(void)
276 {
277 dlink_node *m = lru_list.head;
278 ipcache_entry *i = NULL, *t;
279
280 while (m) {
281 if (i != NULL) { /* need to delay deletion */
282 ipcacheRelease(i); /* we just override locks */
283 i = NULL;
284 }
285
286 t = (ipcache_entry*)m->data;
287
288 if (t->flags.fromhosts)
289 i = t;
290
291 m = m->next;
292 }
293
294 if (i != NULL)
295 ipcacheRelease(i);
296 }
297
298 /**
299 \ingroup IPCacheInternal
300 *
301 * create blank ipcache_entry
302 */
303 static ipcache_entry *
304 ipcacheCreateEntry(const char *name)
305 {
306 static ipcache_entry *i;
307 i = (ipcache_entry *)memAllocate(MEM_IPCACHE_ENTRY);
308 i->hash.key = xstrdup(name);
309 Tolower(static_cast<char*>(i->hash.key));
310 i->expires = squid_curtime + Config.negativeDnsTtl;
311 return i;
312 }
313
314 /// \ingroup IPCacheInternal
315 static void
316 ipcacheAddEntry(ipcache_entry * i)
317 {
318 hash_link *e = (hash_link *)hash_lookup(ip_table, i->hash.key);
319
320 if (NULL != e) {
321 /* avoid colission */
322 ipcache_entry *q = (ipcache_entry *) e;
323 ipcacheRelease(q);
324 }
325
326 hash_join(ip_table, &i->hash);
327 dlinkAdd(i, &i->lru, &lru_list);
328 i->lastref = squid_curtime;
329 }
330
331 /**
332 \ingroup IPCacheInternal
333 *
334 * walks down the pending list, calling handlers
335 */
336 static void
337 ipcacheCallback(ipcache_entry *i, int wait)
338 {
339 IPH *callback = i->handler;
340 void *cbdata = NULL;
341 i->lastref = squid_curtime;
342
343 if (!i->handler)
344 return;
345
346 ipcacheLockEntry(i);
347
348 callback = i->handler;
349
350 i->handler = NULL;
351
352 if (cbdataReferenceValidDone(i->handlerData, &cbdata)) {
353 const DnsLookupDetails details(i->error_message, wait);
354 callback((i->addrs.count ? &i->addrs : NULL), details, cbdata);
355 }
356
357 ipcacheUnlockEntry(i);
358 }
359
360 /// \ingroup IPCacheAPI
361 #if USE_DNSHELPER
362 static int
363 ipcacheParse(ipcache_entry *i, const char *inbuf)
364 {
365 LOCAL_ARRAY(char, buf, DNS_INBUF_SZ);
366 char *token;
367 int ipcount = 0;
368 int ttl;
369 char *A[32];
370 const char *name = (const char *)i->hash.key;
371 i->expires = squid_curtime + Config.negativeDnsTtl;
372 i->flags.negcached = 1;
373 safe_free(i->addrs.in_addrs);
374 safe_free(i->addrs.bad_mask);
375 safe_free(i->error_message);
376 i->addrs.count = 0;
377
378 if (inbuf == NULL) {
379 debugs(14, DBG_IMPORTANT, "ipcacheParse: Got <NULL> reply");
380 i->error_message = xstrdup("Internal Error");
381 return -1;
382 }
383
384 xstrncpy(buf, inbuf, DNS_INBUF_SZ);
385 debugs(14, 5, "ipcacheParse: parsing: {" << buf << "}");
386 token = strtok(buf, w_space);
387
388 if (NULL == token) {
389 debugs(14, DBG_IMPORTANT, "ipcacheParse: expecting result, got '" << inbuf << "'");
390
391 i->error_message = xstrdup("Internal Error");
392 return -1;
393 }
394
395 if (0 == strcmp(token, "$fail")) {
396 token = strtok(NULL, "\n");
397 assert(NULL != token);
398 i->error_message = xstrdup(token);
399 return 0;
400 }
401
402 if (0 != strcmp(token, "$addr")) {
403 debugs(14, DBG_IMPORTANT, "ipcacheParse: expecting '$addr', got '" << inbuf << "' in response to '" << name << "'");
404
405 i->error_message = xstrdup("Internal Error");
406 return -1;
407 }
408
409 token = strtok(NULL, w_space);
410
411 if (NULL == token) {
412 debugs(14, DBG_IMPORTANT, "ipcacheParse: expecting TTL, got '" << inbuf << "' in response to '" << name << "'");
413
414 i->error_message = xstrdup("Internal Error");
415 return -1;
416 }
417
418 ttl = atoi(token);
419
420 while (NULL != (token = strtok(NULL, w_space))) {
421 A[ipcount] = token;
422
423 if (++ipcount == 32)
424 break;
425 }
426
427 if (ipcount > 0) {
428 int j, k;
429
430 i->addrs.in_addrs = static_cast<Ip::Address *>(xcalloc(ipcount, sizeof(Ip::Address)));
431 for (int l = 0; l < ipcount; ++l)
432 i->addrs.in_addrs[l].SetEmpty(); // perform same init actions as constructor would.
433 i->addrs.bad_mask = (unsigned char *)xcalloc(ipcount, sizeof(unsigned char));
434 memset(i->addrs.bad_mask, 0, sizeof(unsigned char) * ipcount);
435
436 for (j = 0, k = 0; k < ipcount; ++k) {
437 if ((i->addrs.in_addrs[j] = A[k]))
438 ++j;
439 else
440 debugs(14, DBG_IMPORTANT, "ipcacheParse: Invalid IP address '" << A[k] << "' in response to '" << name << "'");
441 }
442
443 i->addrs.count = (unsigned char) j;
444 }
445
446 if (i->addrs.count <= 0) {
447 debugs(14, DBG_IMPORTANT, "ipcacheParse: No addresses in response to '" << name << "'");
448 return -1;
449 }
450
451 if (ttl == 0 || ttl > Config.positiveDnsTtl)
452 ttl = Config.positiveDnsTtl;
453
454 if (ttl < Config.negativeDnsTtl)
455 ttl = Config.negativeDnsTtl;
456
457 i->expires = squid_curtime + ttl;
458
459 i->flags.negcached = 0;
460
461 return i->addrs.count;
462 }
463
464 #else
465 static int
466 ipcacheParse(ipcache_entry *i, const rfc1035_rr * answers, int nr, const char *error_message)
467 {
468 int k;
469 int j = 0;
470 int na = 0;
471 int ttl = 0;
472 const char *name = (const char *)i->hash.key;
473 int cname_found = 0;
474
475 i->expires = squid_curtime + Config.negativeDnsTtl;
476 i->flags.negcached = 1;
477 safe_free(i->addrs.in_addrs);
478 assert(i->addrs.in_addrs == NULL);
479 safe_free(i->addrs.bad_mask);
480 assert(i->addrs.bad_mask == NULL);
481 safe_free(i->error_message);
482 assert(i->error_message == NULL);
483 i->addrs.count = 0;
484
485 if (nr < 0) {
486 debugs(14, 3, "ipcacheParse: Lookup failed '" << error_message << "' for '" << (const char *)i->hash.key << "'");
487 i->error_message = xstrdup(error_message);
488 return -1;
489 }
490
491 if (nr == 0) {
492 debugs(14, 3, "ipcacheParse: No DNS records in response to '" << name << "'");
493 i->error_message = xstrdup("No DNS records");
494 return -1;
495 }
496
497 debugs(14, 3, "ipcacheParse: " << nr << " answers for '" << name << "'");
498 assert(answers);
499
500 for (k = 0; k < nr; ++k) {
501
502 if (Ip::EnableIpv6 && answers[k].type == RFC1035_TYPE_AAAA) {
503 if (answers[k].rdlength != sizeof(struct in6_addr)) {
504 debugs(14, DBG_IMPORTANT, "ipcacheParse: Invalid IPv6 address in response to '" << name << "'");
505 continue;
506 }
507 ++na;
508 ++IpcacheStats.rr_aaaa;
509 continue;
510 }
511
512 if (answers[k].type == RFC1035_TYPE_A) {
513 if (answers[k].rdlength != sizeof(struct in_addr)) {
514 debugs(14, DBG_IMPORTANT, "ipcacheParse: Invalid IPv4 address in response to '" << name << "'");
515 continue;
516 }
517 ++na;
518 ++IpcacheStats.rr_a;
519 continue;
520 }
521
522 /* With A and AAAA, the CNAME does not necessarily come with additional records to use. */
523 if (answers[k].type == RFC1035_TYPE_CNAME) {
524 cname_found=1;
525 ++IpcacheStats.rr_cname;
526 continue;
527 }
528
529 // otherwise its an unknown RR. debug at level 9 since we usually want to ignore these and they are common.
530 debugs(14, 9, HERE << "Unknown RR type received: type=" << answers[k].type << " starting at " << &(answers[k]) );
531 }
532 if (na == 0) {
533 debugs(14, DBG_IMPORTANT, "ipcacheParse: No Address records in response to '" << name << "'");
534 i->error_message = xstrdup("No Address records");
535 if (cname_found)
536 ++IpcacheStats.cname_only;
537 return 0;
538 }
539
540 i->addrs.in_addrs = static_cast<Ip::Address *>(xcalloc(na, sizeof(Ip::Address)));
541 for (int l = 0; l < na; ++l)
542 i->addrs.in_addrs[l].SetEmpty(); // perform same init actions as constructor would.
543 i->addrs.bad_mask = (unsigned char *)xcalloc(na, sizeof(unsigned char));
544
545 for (j = 0, k = 0; k < nr; ++k) {
546
547 if (answers[k].type == RFC1035_TYPE_A) {
548 if (answers[k].rdlength != sizeof(struct in_addr))
549 continue;
550
551 struct in_addr temp;
552 memcpy(&temp, answers[k].rdata, sizeof(struct in_addr));
553 i->addrs.in_addrs[j] = temp;
554
555 debugs(14, 3, "ipcacheParse: " << name << " #" << j << " " << i->addrs.in_addrs[j]);
556 ++j;
557
558 } else if (Ip::EnableIpv6 && answers[k].type == RFC1035_TYPE_AAAA) {
559 if (answers[k].rdlength != sizeof(struct in6_addr))
560 continue;
561
562 struct in6_addr temp;
563 memcpy(&temp, answers[k].rdata, sizeof(struct in6_addr));
564 i->addrs.in_addrs[j] = temp;
565
566 debugs(14, 3, "ipcacheParse: " << name << " #" << j << " " << i->addrs.in_addrs[j] );
567 ++j;
568 }
569 if (ttl == 0 || (int) answers[k].ttl < ttl)
570 ttl = answers[k].ttl;
571 }
572
573 assert(j == na);
574
575 if (na < 256)
576 i->addrs.count = (unsigned char) na;
577 else
578 i->addrs.count = 255;
579
580 if (ttl > Config.positiveDnsTtl)
581 ttl = Config.positiveDnsTtl;
582
583 if (ttl < Config.negativeDnsTtl)
584 ttl = Config.negativeDnsTtl;
585
586 i->expires = squid_curtime + ttl;
587
588 i->flags.negcached = 0;
589
590 return i->addrs.count;
591 }
592
593 #endif
594
595 /// \ingroup IPCacheInternal
596 static void
597 #if USE_DNSHELPER
598 ipcacheHandleReply(void *data, const HelperReply &reply)
599 #else
600 ipcacheHandleReply(void *data, const rfc1035_rr * answers, int na, const char *error_message)
601 #endif
602 {
603 ipcache_entry *i;
604 static_cast<generic_cbdata *>(data)->unwrap(&i);
605 ++IpcacheStats.replies;
606 const int age = i->age();
607 statCounter.dns.svcTime.count(age);
608
609 #if USE_DNSHELPER
610 ipcacheParse(i, reply.other().content());
611 #else
612
613 int done = ipcacheParse(i, answers, na, error_message);
614
615 /* If we have not produced either IPs or Error immediately, wait for recursion to finish. */
616 if (done != 0 || error_message != NULL)
617 #endif
618
619 {
620 ipcacheAddEntry(i);
621 ipcacheCallback(i, age);
622 }
623 }
624
625 /**
626 \ingroup IPCacheAPI
627 *
628 \param name Host to resolve.
629 \param handler Pointer to the function to be called when the reply
630 * from the IP cache (or the DNS if the IP cache misses)
631 \param handlerData Information that is passed to the handler and does not affect the IP cache.
632 *
633 * XXX: on hits and some errors, the handler is called immediately instead
634 * of scheduling an async call. This reentrant behavior means that the
635 * user job must be extra careful after calling ipcache_nbgethostbyname,
636 * especially if the handler destroys the job. Moreover, the job has
637 * no way of knowing whether the reentrant call happened.
638 * Comm::Connection setup usually protects the job by scheduling an async call,
639 * but some user code calls ipcache_nbgethostbyname directly.
640 */
641 void
642 ipcache_nbgethostbyname(const char *name, IPH * handler, void *handlerData)
643 {
644 ipcache_entry *i = NULL;
645 const ipcache_addrs *addrs = NULL;
646 generic_cbdata *c;
647 debugs(14, 4, "ipcache_nbgethostbyname: Name '" << name << "'.");
648 ++IpcacheStats.requests;
649
650 if (name == NULL || name[0] == '\0') {
651 debugs(14, 4, "ipcache_nbgethostbyname: Invalid name!");
652 ++IpcacheStats.invalid;
653 const DnsLookupDetails details("Invalid hostname", -1); // error, no lookup
654 if (handler)
655 handler(NULL, details, handlerData);
656 return;
657 }
658
659 if ((addrs = ipcacheCheckNumeric(name))) {
660 debugs(14, 4, "ipcache_nbgethostbyname: BYPASS for '" << name << "' (already numeric)");
661 ++IpcacheStats.numeric_hits;
662 const DnsLookupDetails details(NULL, -1); // no error, no lookup
663 if (handler)
664 handler(addrs, details, handlerData);
665 return;
666 }
667
668 i = ipcache_get(name);
669
670 if (NULL == i) {
671 /* miss */
672 (void) 0;
673 } else if (ipcacheExpiredEntry(i)) {
674 /* hit, but expired -- bummer */
675 ipcacheRelease(i);
676 i = NULL;
677 } else {
678 /* hit */
679 debugs(14, 4, "ipcache_nbgethostbyname: HIT for '" << name << "'");
680
681 if (i->flags.negcached)
682 ++IpcacheStats.negative_hits;
683 else
684 ++IpcacheStats.hits;
685
686 i->handler = handler;
687
688 i->handlerData = cbdataReference(handlerData);
689
690 ipcacheCallback(i, -1); // no lookup
691
692 return;
693 }
694
695 debugs(14, 5, "ipcache_nbgethostbyname: MISS for '" << name << "'");
696 ++IpcacheStats.misses;
697 i = ipcacheCreateEntry(name);
698 i->handler = handler;
699 i->handlerData = cbdataReference(handlerData);
700 i->request_time = current_time;
701 c = new generic_cbdata(i);
702 #if USE_DNSHELPER
703 dnsSubmit(hashKeyStr(&i->hash), ipcacheHandleReply, c);
704 #else
705 idnsALookup(hashKeyStr(&i->hash), ipcacheHandleReply, c);
706 #endif
707 }
708
709 /// \ingroup IPCacheInternal
710 static void
711 ipcacheRegisterWithCacheManager(void)
712 {
713 Mgr::RegisterAction("ipcache",
714 "IP Cache Stats and Contents",
715 stat_ipcache_get, 0, 1);
716 }
717
718 /**
719 \ingroup IPCacheAPI
720 *
721 * Initialize the ipcache.
722 * Is called from mainInitialize() after disk initialization
723 * and prior to the reverse FQDNCache initialization
724 */
725 void
726 ipcache_init(void)
727 {
728 int n;
729 debugs(14, DBG_IMPORTANT, "Initializing IP Cache...");
730 memset(&IpcacheStats, '\0', sizeof(IpcacheStats));
731 memset(&lru_list, '\0', sizeof(lru_list));
732 memset(&static_addrs, '\0', sizeof(ipcache_addrs));
733
734 static_addrs.in_addrs = static_cast<Ip::Address *>(xcalloc(1, sizeof(Ip::Address)));
735 static_addrs.in_addrs->SetEmpty(); // properly setup the Ip::Address!
736 static_addrs.bad_mask = (unsigned char *)xcalloc(1, sizeof(unsigned char));
737 ipcache_high = (long) (((float) Config.ipcache.size *
738 (float) Config.ipcache.high) / (float) 100);
739 ipcache_low = (long) (((float) Config.ipcache.size *
740 (float) Config.ipcache.low) / (float) 100);
741 n = hashPrime(ipcache_high / 4);
742 ip_table = hash_create((HASHCMP *) strcmp, n, hash4);
743 memDataInit(MEM_IPCACHE_ENTRY, "ipcache_entry", sizeof(ipcache_entry), 0);
744
745 ipcacheRegisterWithCacheManager();
746 }
747
748 /**
749 \ingroup IPCacheAPI
750 *
751 * Is different from ipcache_nbgethostbyname in that it only checks
752 * if an entry exists in the cache and does not by default contact the DNS,
753 * unless this is requested, by setting the flags.
754 *
755 \param name Host name to resolve.
756 \param flags Default is NULL, set to IP_LOOKUP_IF_MISS
757 * to explicitly perform DNS lookups.
758 *
759 \retval NULL An error occured during lookup
760 \retval NULL No results available in cache and no lookup specified
761 \retval * Pointer to the ipcahce_addrs structure containing the lookup results
762 */
763 const ipcache_addrs *
764 ipcache_gethostbyname(const char *name, int flags)
765 {
766 ipcache_entry *i = NULL;
767 ipcache_addrs *addrs;
768 assert(name);
769 debugs(14, 3, "ipcache_gethostbyname: '" << name << "', flags=" << std::hex << flags);
770 ++IpcacheStats.requests;
771 i = ipcache_get(name);
772
773 if (NULL == i) {
774 (void) 0;
775 } else if (ipcacheExpiredEntry(i)) {
776 ipcacheRelease(i);
777 i = NULL;
778 } else if (i->flags.negcached) {
779 ++IpcacheStats.negative_hits;
780 // ignore i->error_message: the caller just checks IP cache presence
781 return NULL;
782 } else {
783 ++IpcacheStats.hits;
784 i->lastref = squid_curtime;
785 // ignore i->error_message: the caller just checks IP cache presence
786 return &i->addrs;
787 }
788
789 /* no entry [any more] */
790
791 if ((addrs = ipcacheCheckNumeric(name))) {
792 ++IpcacheStats.numeric_hits;
793 return addrs;
794 }
795
796 ++IpcacheStats.misses;
797
798 if (flags & IP_LOOKUP_IF_MISS)
799 ipcache_nbgethostbyname(name, NULL, NULL);
800
801 return NULL;
802 }
803
804 /// \ingroup IPCacheInternal
805 static void
806 ipcacheStatPrint(ipcache_entry * i, StoreEntry * sentry)
807 {
808 int k;
809 char buf[MAX_IPSTRLEN];
810
811 if (!sentry) {
812 debugs(14, DBG_CRITICAL, HERE << "CRITICAL: sentry is NULL!");
813 return;
814 }
815
816 if (!i) {
817 debugs(14, DBG_CRITICAL, HERE << "CRITICAL: ipcache_entry is NULL!");
818 storeAppendPrintf(sentry, "CRITICAL ERROR\n");
819 return;
820 }
821
822 int count = i->addrs.count;
823
824 storeAppendPrintf(sentry, " %-32.32s %c%c %6d %6d %2d(%2d)",
825 hashKeyStr(&i->hash),
826 i->flags.fromhosts ? 'H' : ' ',
827 i->flags.negcached ? 'N' : ' ',
828 (int) (squid_curtime - i->lastref),
829 (int) ((i->flags.fromhosts ? -1 : i->expires - squid_curtime)),
830 (int) i->addrs.count,
831 (int) i->addrs.badcount);
832
833 /** \par
834 * Negative-cached entries have no IPs listed. */
835 if (i->flags.negcached) {
836 storeAppendPrintf(sentry, "\n");
837 return;
838 }
839
840 /** \par
841 * Cached entries have IPs listed with a BNF of: ip-address '-' ('OK'|'BAD') */
842 for (k = 0; k < count; ++k) {
843 /* Display tidy-up: IPv6 are so big make the list vertical */
844 if (k == 0)
845 storeAppendPrintf(sentry, " %45.45s-%3s\n",
846 i->addrs.in_addrs[k].NtoA(buf,MAX_IPSTRLEN),
847 i->addrs.bad_mask[k] ? "BAD" : "OK ");
848 else
849 storeAppendPrintf(sentry, "%s %45.45s-%3s\n",
850 " ", /* blank-space indenting IP list */
851 i->addrs.in_addrs[k].NtoA(buf,MAX_IPSTRLEN),
852 i->addrs.bad_mask[k] ? "BAD" : "OK ");
853 }
854 }
855
856 /**
857 \ingroup IPCacheInternal
858 *
859 * process objects list
860 */
861 void
862 stat_ipcache_get(StoreEntry * sentry)
863 {
864 dlink_node *m;
865 assert(ip_table != NULL);
866 storeAppendPrintf(sentry, "IP Cache Statistics:\n");
867 storeAppendPrintf(sentry, "IPcache Entries In Use: %d\n",
868 memInUse(MEM_IPCACHE_ENTRY));
869 storeAppendPrintf(sentry, "IPcache Entries Cached: %d\n",
870 ipcacheCount());
871 storeAppendPrintf(sentry, "IPcache Requests: %d\n",
872 IpcacheStats.requests);
873 storeAppendPrintf(sentry, "IPcache Hits: %d\n",
874 IpcacheStats.hits);
875 storeAppendPrintf(sentry, "IPcache Negative Hits: %d\n",
876 IpcacheStats.negative_hits);
877 storeAppendPrintf(sentry, "IPcache Numeric Hits: %d\n",
878 IpcacheStats.numeric_hits);
879 storeAppendPrintf(sentry, "IPcache Misses: %d\n",
880 IpcacheStats.misses);
881 storeAppendPrintf(sentry, "IPcache Retrieved A: %d\n",
882 IpcacheStats.rr_a);
883 storeAppendPrintf(sentry, "IPcache Retrieved AAAA: %d\n",
884 IpcacheStats.rr_aaaa);
885 storeAppendPrintf(sentry, "IPcache Retrieved CNAME: %d\n",
886 IpcacheStats.rr_cname);
887 storeAppendPrintf(sentry, "IPcache CNAME-Only Response: %d\n",
888 IpcacheStats.cname_only);
889 storeAppendPrintf(sentry, "IPcache Invalid Request: %d\n",
890 IpcacheStats.invalid);
891 storeAppendPrintf(sentry, "\n\n");
892 storeAppendPrintf(sentry, "IP Cache Contents:\n\n");
893 storeAppendPrintf(sentry, " %-31.31s %3s %6s %6s %4s\n",
894 "Hostname",
895 "Flg",
896 "lstref",
897 "TTL",
898 "N(b)");
899
900 for (m = lru_list.head; m; m = m->next) {
901 assert( m->next != m );
902 ipcacheStatPrint((ipcache_entry *)m->data, sentry);
903 }
904 }
905
906 /// \ingroup IPCacheAPI
907 void
908 ipcacheInvalidate(const char *name)
909 {
910 ipcache_entry *i;
911
912 if ((i = ipcache_get(name)) == NULL)
913 return;
914
915 i->expires = squid_curtime;
916
917 /*
918 * NOTE, don't call ipcacheRelease here because we might be here due
919 * to a thread started from a callback.
920 */
921 }
922
923 /// \ingroup IPCacheAPI
924 void
925 ipcacheInvalidateNegative(const char *name)
926 {
927 ipcache_entry *i;
928
929 if ((i = ipcache_get(name)) == NULL)
930 return;
931
932 if (i->flags.negcached)
933 i->expires = squid_curtime;
934
935 /*
936 * NOTE, don't call ipcacheRelease here because we might be here due
937 * to a thread started from a callback.
938 */
939 }
940
941 /// \ingroup IPCacheAPI
942 ipcache_addrs *
943 ipcacheCheckNumeric(const char *name)
944 {
945 Ip::Address ip;
946 /* check if it's already a IP address in text form. */
947
948 /* it may be IPv6-wrapped */
949 if (name[0] == '[') {
950 char *tmp = xstrdup(&name[1]);
951 tmp[strlen(tmp)-1] = '\0';
952 if (!(ip = tmp)) {
953 delete tmp;
954 return NULL;
955 }
956 delete tmp;
957 } else if (!(ip = name))
958 return NULL;
959
960 debugs(14, 4, "ipcacheCheckNumeric: HIT_BYPASS for '" << name << "' == " << ip );
961
962 static_addrs.count = 1;
963
964 static_addrs.cur = 0;
965
966 static_addrs.in_addrs[0] = ip;
967
968 static_addrs.bad_mask[0] = FALSE;
969
970 static_addrs.badcount = 0;
971
972 return &static_addrs;
973 }
974
975 /// \ingroup IPCacheInternal
976 static void
977 ipcacheLockEntry(ipcache_entry * i)
978 {
979 if (i->locks++ == 0) {
980 dlinkDelete(&i->lru, &lru_list);
981 dlinkAdd(i, &i->lru, &lru_list);
982 }
983 }
984
985 /// \ingroup IPCacheInternal
986 static void
987 ipcacheUnlockEntry(ipcache_entry * i)
988 {
989 if (i->locks < 1) {
990 debugs(14, DBG_IMPORTANT, "WARNING: ipcacheEntry unlocked with no lock! locks=" << i->locks);
991 return;
992 }
993
994 -- i->locks;
995
996 if (ipcacheExpiredEntry(i))
997 ipcacheRelease(i);
998 }
999
1000 /// \ingroup IPCacheAPI
1001 void
1002 ipcacheCycleAddr(const char *name, ipcache_addrs * ia)
1003 {
1004 ipcache_entry *i;
1005 unsigned char k;
1006 assert(name || ia);
1007
1008 if (NULL == ia) {
1009 if ((i = ipcache_get(name)) == NULL)
1010 return;
1011
1012 if (i->flags.negcached)
1013 return;
1014
1015 ia = &i->addrs;
1016 }
1017
1018 for (k = 0; k < ia->count; ++k) {
1019 if (++ia->cur == ia->count)
1020 ia->cur = 0;
1021
1022 if (!ia->bad_mask[ia->cur])
1023 break;
1024 }
1025
1026 if (k == ia->count) {
1027 /* All bad, reset to All good */
1028 debugs(14, 3, "ipcacheCycleAddr: Changing ALL " << name << " addrs from BAD to OK");
1029
1030 for (k = 0; k < ia->count; ++k)
1031 ia->bad_mask[k] = 0;
1032
1033 ia->badcount = 0;
1034
1035 ia->cur = 0;
1036 }
1037
1038 /* NP: zero-based so we increase the human-readable number of our position */
1039 debugs(14, 3, "ipcacheCycleAddr: " << name << " now at " << ia->in_addrs[ia->cur] << " (" << (ia->cur+1) << " of " << ia->count << ")");
1040 }
1041
1042 /**
1043 \ingroup IPCacheAPI
1044 *
1045 \param name domain name to have an IP marked bad
1046 \param addr specific addres to be marked bad
1047 */
1048 void
1049 ipcacheMarkBadAddr(const char *name, const Ip::Address &addr)
1050 {
1051 ipcache_entry *i;
1052 ipcache_addrs *ia;
1053 int k;
1054
1055 /** Does nothing if the domain name does not exist. */
1056 if ((i = ipcache_get(name)) == NULL)
1057 return;
1058
1059 ia = &i->addrs;
1060
1061 for (k = 0; k < (int) ia->count; ++k) {
1062 if (addr == ia->in_addrs[k] )
1063 break;
1064 }
1065
1066 /** Does nothing if the IP does not exist for the doamin. */
1067 if (k == (int) ia->count)
1068 return;
1069
1070 /** Marks the given address as BAD */
1071 if (!ia->bad_mask[k]) {
1072 ia->bad_mask[k] = TRUE;
1073 ++ia->badcount;
1074 i->expires = min(squid_curtime + max((time_t)60, Config.negativeDnsTtl), i->expires);
1075 debugs(14, 2, "ipcacheMarkBadAddr: " << name << " " << addr );
1076 }
1077
1078 /** then calls ipcacheCycleAddr() to advance the current pointer to the next OK address. */
1079 ipcacheCycleAddr(name, ia);
1080 }
1081
1082 /// \ingroup IPCacheAPI
1083 void
1084 ipcacheMarkAllGood(const char *name)
1085 {
1086 ipcache_entry *i;
1087 ipcache_addrs *ia;
1088 int k;
1089
1090 if ((i = ipcache_get(name)) == NULL)
1091 return;
1092
1093 ia = &i->addrs;
1094
1095 /* All bad, reset to All good */
1096 debugs(14, 3, "ipcacheMarkAllGood: Changing ALL " << name << " addrs to OK (" << ia->badcount << "/" << ia->count << " bad)");
1097
1098 for (k = 0; k < ia->count; ++k)
1099 ia->bad_mask[k] = 0;
1100
1101 ia->badcount = 0;
1102 }
1103
1104 /// \ingroup IPCacheAPI
1105 void
1106 ipcacheMarkGoodAddr(const char *name, const Ip::Address &addr)
1107 {
1108 ipcache_entry *i;
1109 ipcache_addrs *ia;
1110 int k;
1111
1112 if ((i = ipcache_get(name)) == NULL)
1113 return;
1114
1115 ia = &i->addrs;
1116
1117 for (k = 0; k < (int) ia->count; ++k) {
1118 if (addr == ia->in_addrs[k])
1119 break;
1120 }
1121
1122 if (k == (int) ia->count) /* not found */
1123 return;
1124
1125 if (!ia->bad_mask[k]) /* already OK */
1126 return;
1127
1128 ia->bad_mask[k] = FALSE;
1129
1130 -- ia->badcount;
1131
1132 debugs(14, 2, "ipcacheMarkGoodAddr: " << name << " " << addr );
1133 }
1134
1135 /// \ingroup IPCacheInternal
1136 static void
1137 ipcacheFreeEntry(void *data)
1138 {
1139 ipcache_entry *i = (ipcache_entry *)data;
1140 safe_free(i->addrs.in_addrs);
1141 safe_free(i->addrs.bad_mask);
1142 safe_free(i->hash.key);
1143 safe_free(i->error_message);
1144 memFree(i, MEM_IPCACHE_ENTRY);
1145 }
1146
1147 /// \ingroup IPCacheAPI
1148 void
1149 ipcacheFreeMemory(void)
1150 {
1151 hashFreeItems(ip_table, ipcacheFreeEntry);
1152 hashFreeMemory(ip_table);
1153 ip_table = NULL;
1154 }
1155
1156 /**
1157 \ingroup IPCacheAPI
1158 *
1159 * Recalculate IP cache size upon reconfigure.
1160 * Is called to clear the IPCache's data structures,
1161 * cancel all pending requests.
1162 */
1163 void
1164 ipcache_restart(void)
1165 {
1166 ipcache_high = (long) (((float) Config.ipcache.size *
1167 (float) Config.ipcache.high) / (float) 100);
1168 ipcache_low = (long) (((float) Config.ipcache.size *
1169 (float) Config.ipcache.low) / (float) 100);
1170 purge_entries_fromhosts();
1171 }
1172
1173 /**
1174 \ingroup IPCacheAPI
1175 *
1176 * Adds a "static" entry from /etc/hosts
1177 *
1178 \param name Hostname to be linked with IP
1179 \param ipaddr IP Address to be cached.
1180 *
1181 \retval 0 Success.
1182 \retval 1 IP address is invalid or other error.
1183 */
1184 int
1185 ipcacheAddEntryFromHosts(const char *name, const char *ipaddr)
1186 {
1187 ipcache_entry *i;
1188
1189 Ip::Address ip;
1190
1191 if (!(ip = ipaddr)) {
1192 if (strchr(ipaddr, ':') && strspn(ipaddr, "0123456789abcdefABCDEF:") == strlen(ipaddr)) {
1193 debugs(14, 3, "ipcacheAddEntryFromHosts: Skipping IPv6 address '" << ipaddr << "'");
1194 } else {
1195 debugs(14, DBG_IMPORTANT, "ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr << "'");
1196 }
1197
1198 return 1;
1199 }
1200
1201 if ((i = ipcache_get(name))) {
1202 if (1 == i->flags.fromhosts) {
1203 ipcacheUnlockEntry(i);
1204 } else if (i->locks > 0) {
1205 debugs(14, DBG_IMPORTANT, "ipcacheAddEntryFromHosts: can't add static entry for locked name '" << name << "'");
1206 return 1;
1207 } else {
1208 ipcacheRelease(i);
1209 }
1210 }
1211
1212 i = ipcacheCreateEntry(name);
1213 i->addrs.count = 1;
1214 i->addrs.cur = 0;
1215 i->addrs.badcount = 0;
1216
1217 i->addrs.in_addrs = static_cast<Ip::Address *>(xcalloc(1, sizeof(Ip::Address)));
1218 i->addrs.bad_mask = (unsigned char *)xcalloc(1, sizeof(unsigned char));
1219 i->addrs.in_addrs[0] = ip;
1220 i->addrs.bad_mask[0] = FALSE;
1221 i->flags.fromhosts = 1;
1222 ipcacheAddEntry(i);
1223 ipcacheLockEntry(i);
1224 return 0;
1225 }
1226
1227 #if SQUID_SNMP
1228 /**
1229 \ingroup IPCacheAPI
1230 *
1231 * The function to return the ip cache statistics to via SNMP
1232 */
1233 variable_list *
1234 snmp_netIpFn(variable_list * Var, snint * ErrP)
1235 {
1236 variable_list *Answer = NULL;
1237 MemBuf tmp;
1238 debugs(49, 5, "snmp_netIpFn: Processing request:" << snmpDebugOid(Var->name, Var->name_length, tmp));
1239 *ErrP = SNMP_ERR_NOERROR;
1240
1241 switch (Var->name[LEN_SQ_NET + 1]) {
1242
1243 case IP_ENT:
1244 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1245 ipcacheCount(),
1246 SMI_GAUGE32);
1247 break;
1248
1249 case IP_REQ:
1250 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1251 IpcacheStats.requests,
1252 SMI_COUNTER32);
1253 break;
1254
1255 case IP_HITS:
1256 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1257 IpcacheStats.hits,
1258 SMI_COUNTER32);
1259 break;
1260
1261 case IP_PENDHIT:
1262 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1263 0,
1264 SMI_GAUGE32);
1265 break;
1266
1267 case IP_NEGHIT:
1268 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1269 IpcacheStats.negative_hits,
1270 SMI_COUNTER32);
1271 break;
1272
1273 case IP_MISS:
1274 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1275 IpcacheStats.misses,
1276 SMI_COUNTER32);
1277 break;
1278
1279 case IP_GHBN:
1280 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1281 0, /* deprecated */
1282 SMI_COUNTER32);
1283 break;
1284
1285 case IP_LOC:
1286 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1287 0, /* deprecated */
1288 SMI_COUNTER32);
1289 break;
1290
1291 default:
1292 *ErrP = SNMP_ERR_NOSUCHNAME;
1293 snmp_var_free(Answer);
1294 return (NULL);
1295 }
1296
1297 return Answer;
1298 }
1299
1300 #endif /*SQUID_SNMP */