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