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