]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ipcache.cc
Import IPv6 support from squid3-ipv6 branch to 3-HEAD.
[thirdparty/squid.git] / src / ipcache.cc
1
2 /*
3 * $Id: ipcache.cc,v 1.265 2007/12/14 23:11:47 amosjeffries Exp $
4 *
5 * DEBUG: section 14 IP Cache
6 * AUTHOR: Harvest Derived
7 *
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
10 *
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
33 *
34 */
35
36 #include "squid.h"
37 #include "cbdata.h"
38 #include "event.h"
39 #include "CacheManager.h"
40 #include "SquidTime.h"
41 #include "Store.h"
42 #include "wordlist.h"
43 #include "IPAddress.h"
44
45 typedef struct _ipcache_entry ipcache_entry;
46
47 struct _ipcache_entry
48 {
49 hash_link hash; /* must be first */
50 time_t lastref;
51 time_t expires;
52 ipcache_addrs addrs;
53 IPH *handler;
54 void *handlerData;
55 char *error_message;
56
57 struct timeval request_time;
58 dlink_node lru;
59 unsigned short locks;
60 unsigned short cname_wait;
61
62 struct
63 {
64
65 unsigned int negcached:
66 1;
67
68 unsigned int fromhosts:
69 1;
70 }
71
72 flags;
73 };
74
75 static struct
76 {
77 int requests;
78 int replies;
79 int hits;
80 int misses;
81 int negative_hits;
82 int numeric_hits;
83 int invalid;
84 }
85 IpcacheStats;
86
87 static dlink_list lru_list;
88
89 static FREE ipcacheFreeEntry;
90 #if USE_DNSSERVERS
91 static HLPCB ipcacheHandleReply;
92 #else
93 static IDNSCB ipcacheHandleReply;
94 #endif
95 static IPH ipcacheHandleCnameRecurse;
96 static int ipcacheExpiredEntry(ipcache_entry *);
97 static int ipcache_testname(void);
98 #if USE_DNSSERVERS
99 static int ipcacheParse(ipcache_entry *, const char *buf);
100 #else
101 static int ipcacheParse(ipcache_entry *, rfc1035_rr *, int, const char *error);
102 #endif
103 static ipcache_entry *ipcache_get(const char *);
104 static void ipcacheLockEntry(ipcache_entry *);
105 static void ipcacheStatPrint(ipcache_entry *, StoreEntry *);
106 static void ipcacheUnlockEntry(ipcache_entry *);
107 static void ipcacheRelease(ipcache_entry *, bool dofree = true);
108
109 static ipcache_addrs static_addrs;
110 static hash_table *ip_table = NULL;
111
112 static long ipcache_low = 180;
113 static long ipcache_high = 200;
114
115 #if LIBRESOLV_DNS_TTL_HACK
116 extern int _dns_ttl_;
117 #endif
118
119 static int
120 ipcache_testname(void)
121 {
122 wordlist *w = NULL;
123 debugs(14, 1, "Performing DNS Tests...");
124
125 if ((w = Config.dns_testname_list) == NULL)
126 return 1;
127
128 for (; w; w = w->next) {
129 if (gethostbyname(w->key) != NULL)
130 return 1;
131 }
132
133 return 0;
134 }
135
136 /* removes the given ipcache entry */
137 static void
138 ipcacheRelease(ipcache_entry * i, bool dofree)
139 {
140 if(!i) {
141 debugs(14, 0, "ipcacheRelease: Releasing entry with i=<NULL>");
142 return;
143 }
144
145 if(!i || !i->hash.key) {
146 debugs(14, 0, "ipcacheRelease: Releasing entry without hash link!");
147 return;
148 }
149
150 debugs(14, 3, "ipcacheRelease: Releasing entry for '" << (const char *) i->hash.key << "'");
151
152 hash_remove_link(ip_table, (hash_link *) i);
153 dlinkDelete(&i->lru, &lru_list);
154 if(dofree)
155 ipcacheFreeEntry(i);
156 }
157
158 static ipcache_entry *
159 ipcache_get(const char *name)
160 {
161 if (ip_table != NULL)
162 return (ipcache_entry *) hash_lookup(ip_table, name);
163 else
164 return NULL;
165 }
166
167 static int
168 ipcacheExpiredEntry(ipcache_entry * i)
169 {
170 /* all static entries are locked, so this takes care of them too */
171
172 if (i->locks != 0)
173 return 0;
174
175 if (i->addrs.count == 0)
176 if (0 == i->flags.negcached)
177 return 1;
178
179 if (i->expires > squid_curtime)
180 return 0;
181
182 return 1;
183 }
184
185 void
186 ipcache_purgelru(void *voidnotused)
187 {
188 dlink_node *m;
189 dlink_node *prev = NULL;
190 ipcache_entry *i;
191 int removed = 0;
192 eventAdd("ipcache_purgelru", ipcache_purgelru, NULL, 10.0, 1);
193
194 for (m = lru_list.tail; m; m = prev) {
195 if (memInUse(MEM_IPCACHE_ENTRY) < ipcache_low)
196 break;
197
198 prev = m->prev;
199
200 i = (ipcache_entry *)m->data;
201
202 if (i->locks != 0)
203 continue;
204
205 ipcacheRelease(i);
206
207 removed++;
208 }
209
210 debugs(14, 9, "ipcache_purgelru: removed " << removed << " entries");
211 }
212
213 /* purges entries added from /etc/hosts (or whatever). */
214 static void
215 purge_entries_fromhosts(void)
216 {
217 dlink_node *m = lru_list.head;
218 ipcache_entry *i = NULL, *t;
219
220 while (m) {
221 if (i != NULL) { /* need to delay deletion */
222 ipcacheRelease(i); /* we just override locks */
223 i = NULL;
224 }
225
226 t = (ipcache_entry*)m->data;
227
228 if (t->flags.fromhosts)
229 i = t;
230
231 m = m->next;
232 }
233
234 if (i != NULL)
235 ipcacheRelease(i);
236 }
237
238 /* create blank ipcache_entry */
239 static ipcache_entry *
240 ipcacheCreateEntry(const char *name)
241 {
242 static ipcache_entry *i;
243 i = (ipcache_entry *)memAllocate(MEM_IPCACHE_ENTRY);
244 i->hash.key = xstrdup(name);
245 i->expires = squid_curtime + Config.negativeDnsTtl;
246 return i;
247 }
248
249 static void
250 ipcacheAddEntry(ipcache_entry * i)
251 {
252 hash_link *e = (hash_link *)hash_lookup(ip_table, i->hash.key);
253
254 /* INET6 : should NOT be adding this entry until all CNAME have been received. */
255 assert(i->cname_wait == 0);
256
257 if (NULL != e) {
258 /* avoid colission */
259 ipcache_entry *q = (ipcache_entry *) e;
260 if(q == i) {
261 /* can occur with Multiple-depth CNAME Recursion if parent returned early with additional */
262 /* just need to drop from the hash without releasing actual memory */
263 ipcacheRelease(q, false);
264 }
265 else
266 ipcacheRelease(q);
267 }
268
269 hash_join(ip_table, &i->hash);
270 dlinkAdd(i, &i->lru, &lru_list);
271 i->lastref = squid_curtime;
272 }
273
274 /* walks down the pending list, calling handlers */
275 static void
276 ipcacheCallback(ipcache_entry * i)
277 {
278 IPH *callback = i->handler;
279 void *cbdata = NULL;
280 i->lastref = squid_curtime;
281
282 if (!i->handler)
283 return;
284
285 ipcacheLockEntry(i);
286
287 callback = i->handler;
288
289 i->handler = NULL;
290
291 if (cbdataReferenceValidDone(i->handlerData, &cbdata)) {
292 dns_error_message = i->error_message;
293 callback(i->addrs.count ? &i->addrs : NULL, cbdata);
294 }
295
296 ipcacheUnlockEntry(i);
297 }
298
299 #if USE_DNSSERVERS
300 static int
301 ipcacheParse(ipcache_entry *i, const char *inbuf)
302 {
303 LOCAL_ARRAY(char, buf, DNS_INBUF_SZ);
304 char *token;
305 int ipcount = 0;
306 int ttl;
307 char *A[32];
308 const char *name = (const char *)i->hash.key;
309 i->expires = squid_curtime + Config.negativeDnsTtl;
310 i->flags.negcached = 1;
311 safe_free(i->addrs.in_addrs);
312 safe_free(i->addrs.bad_mask);
313 safe_free(i->error_message);
314 i->addrs.count = 0;
315
316 if (inbuf == NULL) {
317 debugs(14, 1, "ipcacheParse: Got <NULL> reply");
318 i->error_message = xstrdup("Internal Error");
319 return -1;
320 }
321
322 xstrncpy(buf, inbuf, DNS_INBUF_SZ);
323 debugs(14, 5, "ipcacheParse: parsing: {" << buf << "}");
324 token = strtok(buf, w_space);
325
326 if (NULL == token) {
327 debugs(14, 1, "ipcacheParse: expecting result, got '" << inbuf << "'");
328
329 i->error_message = xstrdup("Internal Error");
330 return -1;
331 }
332
333 if (0 == strcmp(token, "$fail")) {
334 token = strtok(NULL, "\n");
335 assert(NULL != token);
336 i->error_message = xstrdup(token);
337 return 0;
338 }
339
340 if (0 != strcmp(token, "$addr")) {
341 debugs(14, 1, "ipcacheParse: expecting '$addr', got '" << inbuf << "' in response to '" << name << "'");
342
343 i->error_message = xstrdup("Internal Error");
344 return -1;
345 }
346
347 token = strtok(NULL, w_space);
348
349 if (NULL == token) {
350 debugs(14, 1, "ipcacheParse: expecting TTL, got '" << inbuf << "' in response to '" << name << "'");
351
352 i->error_message = xstrdup("Internal Error");
353 return -1;
354 }
355
356 ttl = atoi(token);
357
358 while (NULL != (token = strtok(NULL, w_space))) {
359 A[ipcount] = token;
360
361 if (++ipcount == 32)
362 break;
363 }
364
365 if (ipcount > 0) {
366 int j, k;
367
368 i->addrs.in_addrs = (IPAddress *)xcalloc(ipcount, sizeof(IPAddress));
369 for(int l = 0; l < ipcount; l++)
370 i->addrs.in_addrs[l].SetEmpty(); // perform same init actions as constructor would.
371 i->addrs.bad_mask = (unsigned char *)xcalloc(ipcount, sizeof(unsigned char));
372 memset(i->addrs.bad_mask, 0, sizeof(unsigned char) * ipcount);
373
374 for (j = 0, k = 0; k < ipcount; k++) {
375 if ( i->addrs.in_addrs[j] = A[k] )
376 j++;
377 else
378 debugs(14, 1, "ipcacheParse: Invalid IP address '" << A[k] << "' in response to '" << name << "'");
379 }
380
381 i->addrs.count = (unsigned char) j;
382 }
383
384 if (i->addrs.count <= 0) {
385 debugs(14, 1, "ipcacheParse: No addresses in response to '" << name << "'");
386 return -1;
387 }
388
389 if (ttl == 0 || ttl > Config.positiveDnsTtl)
390 ttl = Config.positiveDnsTtl;
391
392 if (ttl < Config.negativeDnsTtl)
393 ttl = Config.negativeDnsTtl;
394
395 i->expires = squid_curtime + ttl;
396
397 i->flags.negcached = 0;
398
399 return i->addrs.count;
400 }
401
402 #else
403 static int
404 ipcacheParse(ipcache_entry *i, rfc1035_rr * answers, int nr, const char *error_message)
405 {
406 int k;
407 int j = 0;
408 int na = 0;
409 int ttl = 0;
410 const char *name = (const char *)i->hash.key;
411 i->expires = squid_curtime + Config.negativeDnsTtl;
412 i->flags.negcached = 1;
413 safe_free(i->addrs.in_addrs);
414 assert(i->addrs.in_addrs == NULL);
415 safe_free(i->addrs.bad_mask);
416 assert(i->addrs.bad_mask == NULL);
417 safe_free(i->error_message);
418 assert(i->error_message == NULL);
419 i->addrs.count = 0;
420
421 if (nr < 0) {
422 debugs(14, 3, "ipcacheParse: Lookup failed '" << error_message << "' for '" << (const char *)i->hash.key << "'");
423 i->error_message = xstrdup(error_message);
424 return -1;
425 }
426
427 if (nr == 0) {
428 debugs(14, 3, "ipcacheParse: No DNS records in response to '" << name << "'");
429 i->error_message = xstrdup("No DNS records");
430 return -1;
431 }
432
433 assert(answers);
434
435 for (k = 0; k < nr; k++) {
436
437 #if USE_IPV6
438 if (answers[k].type == RFC1035_TYPE_AAAA) {
439 if (answers[k].rdlength != sizeof(struct in6_addr)) {
440 debugs(14, 1, "ipcacheParse: Invalid IPv6 address in response to '" << name << "'");
441 continue;
442 }
443 na++;
444 continue;
445 }
446 #endif
447
448 if (answers[k].type == RFC1035_TYPE_A) {
449 if (answers[k].rdlength != sizeof(struct in_addr)) {
450 debugs(14, 1, "ipcacheParse: Invalid IPv4 address in response to '" << name << "'");
451 continue;
452 }
453 na++;
454 continue;
455 }
456
457 /* With A and AAAA, the CNAME does not necessarily come with additional records to use. */
458 if (answers[k].type == RFC1035_TYPE_CNAME) {
459 debugs(14, 5, "ipcacheParse: " << name << " CNAME " << answers[k].rdata << " (checking destination: " << i << ").");
460 const ipcache_addrs *res = ipcache_gethostbyname(answers[k].rdata, 0);
461 if(res) {
462 na += res->count;
463 debugs(14, 5, "ipcacheParse: CNAME " << answers[k].rdata << " already has " << res->count << " IPs cached.");
464 }
465 else {
466 /* keep going on this, but flag the fact that we need to wait for a CNAME lookup to finish */
467 debugs(14, 5, "ipcacheParse: CNAME " << answers[k].rdata << " has no IPs! Recursing.");
468 ipcache_nbgethostbyname(answers[k].rdata, ipcacheHandleCnameRecurse, new generic_cbdata(i) );
469 i->cname_wait++;
470 }
471 continue;
472 }
473 }
474
475 if(na == 0 && i->cname_wait >0 ) {
476 /* don't set any error message (yet). Allow recursion to do its work first. */
477 return 0;
478 }
479
480 if (na == 0) {
481 debugs(14, 1, "ipcacheParse: No Address records in response to '" << name << "'");
482 i->error_message = xstrdup("No Address records");
483 return 0;
484 }
485
486 i->addrs.in_addrs = (IPAddress *)xcalloc(na, sizeof(IPAddress));
487 for(int l = 0; l < na; l++)
488 i->addrs.in_addrs[l].SetEmpty(); // perform same init actions as constructor would.
489 i->addrs.bad_mask = (unsigned char *)xcalloc(na, sizeof(unsigned char));
490
491 for (j = 0, k = 0; k < nr; k++) {
492
493 if (answers[k].type == RFC1035_TYPE_A) {
494 if (answers[k].rdlength != sizeof(struct in_addr))
495 continue;
496
497 struct in_addr temp;
498 xmemcpy(&temp, answers[k].rdata, sizeof(struct in_addr));
499 i->addrs.in_addrs[j] = temp;
500
501 debugs(14, 3, "ipcacheParse: " << name << " #" << j << " " << i->addrs.in_addrs[j]);
502 j++;
503
504 #if USE_IPV6
505 } else if (answers[k].type == RFC1035_TYPE_AAAA) {
506 if (answers[k].rdlength != sizeof(struct in6_addr))
507 continue;
508
509 struct in6_addr temp;
510 xmemcpy(&temp, answers[k].rdata, sizeof(struct in6_addr));
511 i->addrs.in_addrs[j] = temp;
512
513 debugs(14, 3, "ipcacheParse: " << name << " #" << j << " " << i->addrs.in_addrs[j] );
514 j++;
515 #endif
516 } else if (answers[k].type == RFC1035_TYPE_CNAME) {
517 debugs(14, 3, "ipcacheParse: " << name << " #x CNAME " << answers[k].rdata);
518 const ipcache_addrs *res = ipcache_gethostbyname(answers[k].rdata, 0);
519 if(res) {
520 /* NP: the results of *that* query need to be integrated in place of the CNAME */
521 /* Ideally we should also integrate the min TTL of the above IPA's into ttl. */
522 for(int l = 0; l < res->count; l++, j++) {
523 i->addrs.in_addrs[j] = res->in_addrs[l];
524 debugs(14, 3, "ipcacheParse: " << name << " #" << j << " " << i->addrs.in_addrs[j] );
525 }
526 }
527 else {
528 debugs(14, 9, "ipcacheParse: " << answers[k].rdata << " (CNAME) waiting on A/AAAA records.");
529 }
530 }
531
532 if (ttl == 0 || (int) answers[k].ttl < ttl)
533 ttl = answers[k].ttl;
534 }
535
536 assert(j == na);
537
538 if (na < 256)
539 i->addrs.count = (unsigned char) na;
540 else
541 i->addrs.count = 255;
542
543 if (ttl > Config.positiveDnsTtl)
544 ttl = Config.positiveDnsTtl;
545
546 if (ttl < Config.negativeDnsTtl)
547 ttl = Config.negativeDnsTtl;
548
549 i->expires = squid_curtime + ttl;
550
551 i->flags.negcached = 0;
552
553 /* SPECIAL CASE: may get here IFF CNAME received with Additional records */
554 /* reurn 0/'wait for further details' value. */
555 /* NP: 'No DNS Results' is a return -1 +msg */
556 if(i->cname_wait)
557 return 0;
558 else
559 return i->addrs.count;
560 }
561
562 #endif
563
564 static void
565 #if USE_DNSSERVERS
566 ipcacheHandleReply(void *data, char *reply)
567 #else
568 ipcacheHandleReply(void *data, rfc1035_rr * answers, int na, const char *error_message)
569 #endif
570 {
571 int done;
572 ipcache_entry *i;
573 static_cast<generic_cbdata *>(data)->unwrap(&i);
574 IpcacheStats.replies++;
575 statHistCount(&statCounter.dns.svc_time,
576 tvSubMsec(i->request_time, current_time));
577 #if USE_DNSSERVERS
578
579 done = ipcacheParse(i, reply);
580 #else
581
582 done = ipcacheParse(i, answers, na, error_message);
583
584 /* If we have not produced either IPs or Error immediately, wait for recursion to finish. */
585 if(done != 0 || error_message != NULL)
586 #endif
587
588 {
589 ipcacheAddEntry(i);
590 ipcacheCallback(i);
591 }
592 }
593
594 void
595 ipcache_nbgethostbyname(const char *name, IPH * handler, void *handlerData)
596 {
597 ipcache_entry *i = NULL;
598 const ipcache_addrs *addrs = NULL;
599 generic_cbdata *c;
600 assert(handler != NULL);
601 debugs(14, 4, "ipcache_nbgethostbyname: Name '" << name << "'.");
602 IpcacheStats.requests++;
603
604 if (name == NULL || name[0] == '\0') {
605 debugs(14, 4, "ipcache_nbgethostbyname: Invalid name!");
606 IpcacheStats.invalid++;
607 dns_error_message = "Invalid hostname";
608 handler(NULL, handlerData);
609 return;
610 }
611
612 if ((addrs = ipcacheCheckNumeric(name))) {
613 debugs(14, 4, "ipcache_nbgethostbyname: BYPASS for '" << name << "' (already numeric)");
614 dns_error_message = NULL;
615 IpcacheStats.numeric_hits++;
616 handler(addrs, handlerData);
617 return;
618 }
619
620 i = ipcache_get(name);
621
622 if (NULL == i) {
623 /* miss */
624 (void) 0;
625 } else if (ipcacheExpiredEntry(i)) {
626 /* hit, but expired -- bummer */
627 ipcacheRelease(i);
628 i = NULL;
629 } else {
630 /* hit */
631 debugs(14, 4, "ipcache_nbgethostbyname: HIT for '" << name << "'");
632
633 if (i->flags.negcached)
634 IpcacheStats.negative_hits++;
635 else
636 IpcacheStats.hits++;
637
638 i->handler = handler;
639
640 i->handlerData = cbdataReference(handlerData);
641
642 ipcacheCallback(i);
643
644 return;
645 }
646
647 debugs(14, 5, "ipcache_nbgethostbyname: MISS for '" << name << "'");
648 IpcacheStats.misses++;
649 i = ipcacheCreateEntry(name);
650 i->handler = handler;
651 i->handlerData = cbdataReference(handlerData);
652 i->request_time = current_time;
653 c = new generic_cbdata(i);
654 #if USE_DNSSERVERS
655
656 dnsSubmit(hashKeyStr(&i->hash), ipcacheHandleReply, c);
657 #else
658
659 idnsALookup(hashKeyStr(&i->hash), ipcacheHandleReply, c);
660 #endif
661 }
662
663 /* initialize the ipcache */
664 void
665 ipcache_init(void)
666 {
667 int n;
668 debugs(14, 3, "Initializing IP Cache...");
669 memset(&IpcacheStats, '\0', sizeof(IpcacheStats));
670 memset(&lru_list, '\0', sizeof(lru_list));
671 /* test naming lookup */
672
673 if (!opt_dns_tests) {
674 debugs(14, 4, "ipcache_init: Skipping DNS name lookup tests.");
675 } else if (!ipcache_testname()) {
676 fatal("ipcache_init: DNS name lookup tests failed.");
677 } else {
678 debugs(14, 1, "Successful DNS name lookup tests...");
679 }
680
681 memset(&static_addrs, '\0', sizeof(ipcache_addrs));
682
683 static_addrs.in_addrs = (IPAddress *)xcalloc(1, sizeof(IPAddress));
684 static_addrs.in_addrs->SetEmpty(); // properly setup the IPAddress!
685 static_addrs.bad_mask = (unsigned char *)xcalloc(1, sizeof(unsigned char));
686 ipcache_high = (long) (((float) Config.ipcache.size *
687 (float) Config.ipcache.high) / (float) 100);
688 ipcache_low = (long) (((float) Config.ipcache.size *
689 (float) Config.ipcache.low) / (float) 100);
690 n = hashPrime(ipcache_high / 4);
691 ip_table = hash_create((HASHCMP *) strcmp, n, hash4);
692 memDataInit(MEM_IPCACHE_ENTRY, "ipcache_entry", sizeof(ipcache_entry), 0);
693 }
694
695 void
696 ipcacheRegisterWithCacheManager(CacheManager & manager)
697 {
698 manager.registerAction("ipcache",
699 "IP Cache Stats and Contents",
700 stat_ipcache_get, 0, 1);
701 }
702
703 const ipcache_addrs *
704 ipcache_gethostbyname(const char *name, int flags)
705 {
706 ipcache_entry *i = NULL;
707 ipcache_addrs *addrs;
708 assert(name);
709 debugs(14, 3, "ipcache_gethostbyname: '" << name << "', flags=" << std::hex << flags);
710 IpcacheStats.requests++;
711 i = ipcache_get(name);
712
713 if (NULL == i) {
714 (void) 0;
715 } else if (ipcacheExpiredEntry(i)) {
716 ipcacheRelease(i);
717 i = NULL;
718 } else if (i->flags.negcached) {
719 IpcacheStats.negative_hits++;
720 dns_error_message = i->error_message;
721 return NULL;
722 } else {
723 IpcacheStats.hits++;
724 i->lastref = squid_curtime;
725 dns_error_message = i->error_message;
726 return &i->addrs;
727 }
728
729 dns_error_message = NULL;
730
731 if ((addrs = ipcacheCheckNumeric(name))) {
732 IpcacheStats.numeric_hits++;
733 return addrs;
734 }
735
736 IpcacheStats.misses++;
737
738 if (flags & IP_LOOKUP_IF_MISS)
739 ipcache_nbgethostbyname(name, ipcacheHandleCnameRecurse, NULL);
740
741 return NULL;
742 }
743
744 static void
745 ipcacheStatPrint(ipcache_entry * i, StoreEntry * sentry)
746 {
747 int k;
748 int count = i->addrs.count;
749 char buf[MAX_IPSTRLEN];
750
751 if(!sentry) {
752 debugs(14, 0, HERE << "CRITICAL: sentry is NULL!");
753 }
754
755 if(!i) {
756 debugs(14, 0, HERE << "CRITICAL: ipcache_entry is NULL!");
757 storeAppendPrintf(sentry, "CRITICAL ERROR\n");
758 return;
759 }
760
761 storeAppendPrintf(sentry, " %-32.32s %c%c %6d %6d %2d(%2d)",
762 hashKeyStr(&i->hash),
763 i->flags.fromhosts ? 'H' : ' ',
764 i->flags.negcached ? 'N' : ' ',
765 (int) (squid_curtime - i->lastref),
766 (int) ((i->flags.fromhosts ? -1 : i->expires - squid_curtime)),
767 (int) i->addrs.count,
768 (int) i->addrs.badcount);
769
770 /** \par
771 * Negative-cached entries have no IPs listed. */
772 if(i->flags.negcached) {
773 storeAppendPrintf(sentry, "\n");
774 return;
775 }
776
777 /** \par
778 * Cached entries have IPs listed with a BNF of: <IP> '-' ('OK'|'BAD') */
779 for (k = 0; k < count; k++) {
780 /* Display tidy-up: IPv6 are so big make the list vertical */
781 if(k == 0)
782 storeAppendPrintf(sentry, " %45.45s-%3s\n",
783 i->addrs.in_addrs[k].NtoA(buf,MAX_IPSTRLEN),
784 i->addrs.bad_mask[k] ? "BAD" : "OK ");
785 else
786 storeAppendPrintf(sentry, "%s %45.45s-%3s\n",
787 " ", /* blank-space indenting IP list */
788 i->addrs.in_addrs[k].NtoA(buf,MAX_IPSTRLEN),
789 i->addrs.bad_mask[k] ? "BAD" : "OK ");
790 }
791 }
792
793 /* process objects list */
794 void
795 stat_ipcache_get(StoreEntry * sentry)
796 {
797 dlink_node *m;
798 assert(ip_table != NULL);
799 storeAppendPrintf(sentry, "IP Cache Statistics:\n");
800 storeAppendPrintf(sentry, "IPcache Entries: %d\n",
801 memInUse(MEM_IPCACHE_ENTRY));
802 storeAppendPrintf(sentry, "IPcache Requests: %d\n",
803 IpcacheStats.requests);
804 storeAppendPrintf(sentry, "IPcache Hits: %d\n",
805 IpcacheStats.hits);
806 storeAppendPrintf(sentry, "IPcache Negative Hits: %d\n",
807 IpcacheStats.negative_hits);
808 storeAppendPrintf(sentry, "IPcache Numeric Hits: %d\n",
809 IpcacheStats.numeric_hits);
810 storeAppendPrintf(sentry, "IPcache Misses: %d\n",
811 IpcacheStats.misses);
812 storeAppendPrintf(sentry, "IPcache Invalid Request: %d\n",
813 IpcacheStats.invalid);
814 storeAppendPrintf(sentry, "\n\n");
815 storeAppendPrintf(sentry, "IP Cache Contents:\n\n");
816 storeAppendPrintf(sentry, " %-31.31s %3s %6s %6s %4s\n",
817 "Hostname",
818 "Flg",
819 "lstref",
820 "TTL",
821 "N(b)");
822
823 for (m = lru_list.head; m; m = m->next) {
824 assert( m->next != m );
825 ipcacheStatPrint((ipcache_entry *)m->data, sentry);
826 }
827 }
828
829 /**
830 * Takes two IPAddress arrays and merges them into a single array
831 * which is allocated dynamically to fit the number of unique addresses
832 *
833 \param aaddrs One list to merge
834 \param alen Size of list aaddrs
835 \param baddrs Other list to merge
836 \param alen Size of list baddrs
837 \param out Combined list of unique addresses (sorted with IPv6 first in IPv6-mode)
838 \param outlen Size of list out
839 */
840 void
841 ipcacheMergeIPLists(IPAddress *aaddrs, int alen,
842 IPAddress *baddrs, int blen,
843 IPAddress **out, int &outlen )
844 {
845 int fc=0, t=0, c=0;
846
847 IPAddress *ip4ptrs[255];
848 #if USE_IPV6
849 IPAddress *ip6ptrs[255];
850 #endif
851 int num_ip4 = 0;
852 int num_ip6 = 0;
853
854 memset(ip4ptrs, 0, sizeof(IPAddress*)*255);
855 #if USE_IPV6
856 memset(ip6ptrs, 0, sizeof(IPAddress*)*255);
857 #endif
858
859 // for each unique address in list A - grab ptr
860 for(t = 0; t < alen; t++) {
861 if(aaddrs[t].IsIPv4()) {
862 // check against IPv4 pruned list
863 for(c = 0; c <= num_ip4; c++) {
864 if(ip4ptrs[c] && aaddrs[t] == *(ip4ptrs[c]) ) break; // duplicate.
865 }
866 if(c > num_ip4) {
867 ip4ptrs[num_ip4] = &aaddrs[t];
868 num_ip4++;
869 }
870 }
871 #if USE_IPV6
872 else if(aaddrs[t].IsIPv6()) {
873 debugs(14,8, HERE << "A[" << t << "]=IPv6 " << aaddrs[t]);
874 // check against IPv6 pruned list
875 for(c = 0; c <= num_ip6; c++) {
876 if(ip6ptrs[c] && aaddrs[t] == *ip6ptrs[c]) break; // duplicate.
877 }
878 if(c > num_ip6) {
879 ip6ptrs[num_ip6] = &aaddrs[t];
880 num_ip6++;
881 }
882 }
883 #endif
884 }
885
886 // for each unique address in list B - grab ptr
887 for(t = 0; t < blen; t++) {
888 if(baddrs[t].IsIPv4()) {
889 // check against IPv4 pruned list
890 for(c = 0; c <= num_ip4; c++) {
891 if(ip4ptrs[c] && baddrs[t] == *ip4ptrs[c]) break; // duplicate.
892 }
893 if(c > num_ip4) {
894 ip4ptrs[num_ip4] = &baddrs[t];
895 num_ip4++;
896 }
897 }
898 #if USE_IPV6
899 else if(baddrs[t].IsIPv6()) {
900 // check against IPv6 pruned list
901 for(c = 0; c <= num_ip6; c++) {
902 if(ip6ptrs[c] && baddrs[t] == *ip6ptrs[c]) break; // duplicate.
903 }
904 if(c > num_ip6) {
905 ip6ptrs[num_ip6] = &baddrs[t];
906 num_ip6++;
907 }
908 }
909 #endif
910 }
911
912 fc = num_ip6 + num_ip4;
913
914 assert(fc > 0);
915
916 debugs(14, 5, "ipcacheMergeIPLists: Merge " << alen << "+" << blen << " into " << fc << " unique IPs.");
917
918 // copy the old IPs into the new list buffer.
919 (*out) = (IPAddress*)xcalloc(fc, sizeof(IPAddress));
920 outlen=0;
921
922 assert(out != NULL);
923
924 #if USE_IPV6
925 /* IPv6 are preferred (tried first) over IPv4 */
926
927 for(int l = 0; outlen < num_ip6; l++, outlen++) {
928 (*out)[outlen] = *ip6ptrs[l];
929 debugs(14, 5, "ipcacheMergeIPLists: #" << outlen << " " << (*out)[outlen] );
930 }
931 #endif /* USE_IPV6 */
932
933 for(int l = 0; outlen < num_ip4; l++, outlen++) {
934 (*out)[outlen] = *ip4ptrs[l];
935 debugs(14, 5, "ipcacheMergeIPLists: #" << outlen << " " << (*out)[outlen] );
936 }
937
938 assert(outlen == fc); // otherwise something broke badly!
939 }
940
941 static void
942 ipcacheHandleCnameRecurse(const ipcache_addrs *addrs, void *cbdata)
943 {
944 ipcache_entry *i = NULL;
945 char *pname = NULL;
946 IPAddress *tmpbuf = NULL;
947 int fc = 0;
948 int ttl = 0;
949 generic_cbdata* gcb = (generic_cbdata*)cbdata;
950 // count of addrs at parent and child (REQ as .count is a char type!)
951 int ccount = 0, pcount = 0;
952
953 debugs(14, 5, "ipcacheHandleCnameRecurse: Handling basic A/AAAA response.");
954
955 /* IFF no CNAME recursion being processed. do nothing. */
956 if(cbdata == NULL)
957 return;
958
959 gcb->unwrap(&i);
960 assert(i != NULL);
961
962 // make sure we are actualy waiting for a CNAME callback to be run.
963 assert(i->cname_wait > 0);
964 // count this event. its being handled.
965 i->cname_wait--;
966
967 pname = (char*)i->hash.key;
968 assert(pname != NULL);
969
970 debugs(14, 5, "ipcacheHandleCnameRecurse: Handling CNAME recursion. CBDATA('" << gcb->data << "')='" << pname << "' -> " << std::hex << i);
971
972 if(i == NULL) {
973 return; // Parent has expired. Don't merge, just leave for future Ref:
974 }
975
976 /* IFF addrs is NULL (Usually an Error or Timeout occured on lookup.) */
977 /* Ignore it and HOPE that we got some Additional records to use. */
978 if(addrs == NULL)
979 return;
980
981 ccount = (0+ addrs->count);
982 pcount = (0+ i->addrs.count);
983 ttl = i->expires;
984
985 /* IFF no CNAME results. do none of the processing BUT finish anyway. */
986 if(addrs) {
987
988 debugs(14, 5, "ipcacheHandleCnameRecurse: Merge IP Lists for " << pname << " (" << pcount << "+" << ccount << ")");
989
990 /* add new IP records to entry */
991 tmpbuf = i->addrs.in_addrs;
992 i->addrs.in_addrs = NULL;
993 ipcacheMergeIPLists(tmpbuf, pcount, addrs->in_addrs, ccount, &(i->addrs.in_addrs), fc);
994 debugs(14,8, HERE << "in=" << tmpbuf << ", out=" << i->addrs.in_addrs );
995 assert(tmpbuf);
996 safe_free(tmpbuf);
997
998 if( pcount > 0) {
999 /* IFF the parent initial lookup was given Additional records with A */
1000 // clear the 'bad IP mask'
1001 safe_free(i->addrs.bad_mask);
1002 }
1003 // create a new bad IP mask to fit the new size needed.
1004 if(fc > 0) {
1005 i->addrs.bad_mask = (unsigned char*)xcalloc(fc, sizeof(unsigned char));
1006 memset(i->addrs.bad_mask, 0, sizeof(unsigned char)*fc);
1007 }
1008
1009 if (fc < 256)
1010 i->addrs.count = (unsigned char) fc;
1011 else
1012 i->addrs.count = 255;
1013
1014 if (ttl == 0 || ttl > Config.positiveDnsTtl)
1015 ttl = Config.positiveDnsTtl;
1016
1017 if (ttl < Config.negativeDnsTtl)
1018 ttl = Config.negativeDnsTtl;
1019
1020 i->expires = squid_curtime + ttl;
1021
1022 i->flags.negcached = 0;
1023
1024 i->addrs.cur = 0;
1025
1026 i->addrs.badcount = 0;
1027 }
1028
1029 if(fc == 0) {
1030 i->error_message = xstrdup("No DNS Records");
1031 }
1032
1033 /* finish the lookup we were doing on parent when we got side-tracked for CNAME loop */
1034 if(i->cname_wait == 0) {
1035 ipcacheAddEntry(i);
1036 ipcacheCallback(i);
1037 }
1038 // else still more CNAME to be found.
1039 }
1040
1041 void
1042 ipcacheInvalidate(const char *name)
1043 {
1044 ipcache_entry *i;
1045
1046 if ((i = ipcache_get(name)) == NULL)
1047 return;
1048
1049 i->expires = squid_curtime;
1050
1051 /*
1052 * NOTE, don't call ipcacheRelease here becuase we might be here due
1053 * to a thread started from a callback.
1054 */
1055 }
1056
1057 void
1058 ipcacheInvalidateNegative(const char *name)
1059 {
1060 ipcache_entry *i;
1061
1062 if ((i = ipcache_get(name)) == NULL)
1063 return;
1064
1065 if (i->flags.negcached)
1066 i->expires = squid_curtime;
1067
1068 /*
1069 * NOTE, don't call ipcacheRelease here becuase we might be here due
1070 * to a thread started from a callback.
1071 */
1072 }
1073
1074 ipcache_addrs *
1075 ipcacheCheckNumeric(const char *name)
1076 {
1077
1078 IPAddress ip;
1079 /* check if it's already a IP address in text form. */
1080
1081 /* it may be IPv6-wrapped */
1082 if(name[0] == '[') {
1083 char *tmp = xstrdup(&name[1]);
1084 tmp[strlen(tmp)-1] = '\0';
1085 if (!(ip = tmp)) {
1086 delete tmp;
1087 return NULL;
1088 }
1089 delete tmp;
1090 }
1091 else if (!(ip = name))
1092 return NULL;
1093
1094 debugs(14, 4, "ipcacheCheckNumeric: HIT_BYPASS for '" << name << "' == " << ip );
1095
1096 static_addrs.count = 1;
1097
1098 static_addrs.cur = 0;
1099
1100 static_addrs.in_addrs[0] = ip;
1101
1102 static_addrs.bad_mask[0] = FALSE;
1103
1104 static_addrs.badcount = 0;
1105
1106 return &static_addrs;
1107 }
1108
1109 static void
1110 ipcacheLockEntry(ipcache_entry * i)
1111 {
1112 if (i->locks++ == 0) {
1113 dlinkDelete(&i->lru, &lru_list);
1114 dlinkAdd(i, &i->lru, &lru_list);
1115 }
1116 }
1117
1118 static void
1119 ipcacheUnlockEntry(ipcache_entry * i)
1120 {
1121 if(i->locks < 1) {
1122 debugs(14, 1, "WARNING: ipcacheEntry unlocked with no lock! locks=" << i->locks);
1123 return;
1124 }
1125
1126 i->locks--;
1127
1128 if (ipcacheExpiredEntry(i))
1129 ipcacheRelease(i);
1130 }
1131
1132 void
1133 ipcacheCycleAddr(const char *name, ipcache_addrs * ia)
1134 {
1135 ipcache_entry *i;
1136 unsigned char k;
1137 assert(name || ia);
1138
1139 if (NULL == ia) {
1140 if ((i = ipcache_get(name)) == NULL)
1141 return;
1142
1143 if (i->flags.negcached)
1144 return;
1145
1146 ia = &i->addrs;
1147 }
1148
1149 for (k = 0; k < ia->count; k++) {
1150 if (++ia->cur == ia->count)
1151 ia->cur = 0;
1152
1153 if (!ia->bad_mask[ia->cur])
1154 break;
1155 }
1156
1157 if (k == ia->count) {
1158 /* All bad, reset to All good */
1159 debugs(14, 3, "ipcacheCycleAddr: Changing ALL " << name << " addrs from BAD to OK");
1160
1161 for (k = 0; k < ia->count; k++)
1162 ia->bad_mask[k] = 0;
1163
1164 ia->badcount = 0;
1165
1166 ia->cur = 0;
1167 }
1168
1169 debugs(14, 3, "ipcacheCycleAddr: " << name << " now at " << ia->in_addrs[ia->cur] << " (" << ia->cur << " of " << ia->count << ")");
1170 }
1171
1172 /*
1173 * Marks the given address as BAD and calls ipcacheCycleAddr to
1174 * advance the current pointer to the next OK address.
1175 */
1176 void
1177 ipcacheMarkBadAddr(const char *name, IPAddress &addr)
1178 {
1179 ipcache_entry *i;
1180 ipcache_addrs *ia;
1181 int k;
1182
1183 if ((i = ipcache_get(name)) == NULL)
1184 return;
1185
1186 ia = &i->addrs;
1187
1188 for (k = 0; k < (int) ia->count; k++)
1189 {
1190 if (addr == ia->in_addrs[k] )
1191 break;
1192 }
1193
1194 if (k == (int) ia->count) /* not found */
1195 return;
1196
1197 if (!ia->bad_mask[k])
1198 {
1199 ia->bad_mask[k] = TRUE;
1200 ia->badcount++;
1201 i->expires = XMIN(squid_curtime + XMAX((time_t)60, Config.negativeDnsTtl), i->expires);
1202 debugs(14, 2, "ipcacheMarkBadAddr: " << name << " " << addr );
1203 }
1204
1205 ipcacheCycleAddr(name, ia);
1206 }
1207
1208 void
1209
1210 ipcacheMarkGoodAddr(const char *name, IPAddress &addr)
1211 {
1212 ipcache_entry *i;
1213 ipcache_addrs *ia;
1214 int k;
1215
1216 if ((i = ipcache_get(name)) == NULL)
1217 return;
1218
1219 ia = &i->addrs;
1220
1221 for (k = 0; k < (int) ia->count; k++)
1222 {
1223 if (addr == ia->in_addrs[k])
1224 break;
1225 }
1226
1227 if (k == (int) ia->count) /* not found */
1228 return;
1229
1230 if (!ia->bad_mask[k]) /* already OK */
1231 return;
1232
1233 ia->bad_mask[k] = FALSE;
1234
1235 ia->badcount--;
1236
1237 debugs(14, 2, "ipcacheMarkGoodAddr: " << name << " " << addr );
1238 }
1239
1240 static void
1241 ipcacheFreeEntry(void *data)
1242 {
1243 ipcache_entry *i = (ipcache_entry *)data;
1244 safe_free(i->addrs.in_addrs);
1245 safe_free(i->addrs.bad_mask);
1246 safe_free(i->hash.key);
1247 safe_free(i->error_message);
1248 memFree(i, MEM_IPCACHE_ENTRY);
1249 }
1250
1251 void
1252 ipcacheFreeMemory(void)
1253 {
1254 hashFreeItems(ip_table, ipcacheFreeEntry);
1255 hashFreeMemory(ip_table);
1256 ip_table = NULL;
1257 }
1258
1259 /* Recalculate IP cache size upon reconfigure */
1260 void
1261 ipcache_restart(void)
1262 {
1263 ipcache_high = (long) (((float) Config.ipcache.size *
1264 (float) Config.ipcache.high) / (float) 100);
1265 ipcache_low = (long) (((float) Config.ipcache.size *
1266 (float) Config.ipcache.low) / (float) 100);
1267 purge_entries_fromhosts();
1268 }
1269
1270 /*
1271 * adds a "static" entry from /etc/hosts.
1272 * returns 0 upon success, 1 if the ip address is invalid
1273 */
1274 int
1275 ipcacheAddEntryFromHosts(const char *name, const char *ipaddr)
1276 {
1277 ipcache_entry *i;
1278
1279 IPAddress ip;
1280
1281 if (!(ip = ipaddr)) {
1282 #if USE_IPV6
1283 if (strchr(ipaddr, ':') && strspn(ipaddr, "0123456789abcdefABCDEF:") == strlen(ipaddr)) {
1284 debugs(14, 3, "ipcacheAddEntryFromHosts: Skipping IPv6 address '" << ipaddr << "'");
1285 } else {
1286 debugs(14, 1, "ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr << "'");
1287 }
1288 #else
1289 debugs(14, 1, "ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr << "'");
1290 #endif
1291
1292 return 1;
1293 }
1294
1295 if ((i = ipcache_get(name))) {
1296 if (1 == i->flags.fromhosts) {
1297 ipcacheUnlockEntry(i);
1298 } else if (i->locks > 0) {
1299 debugs(14, 1, "ipcacheAddEntryFromHosts: can't add static entry for locked name '" << name << "'");
1300 return 1;
1301 } else {
1302 ipcacheRelease(i);
1303 }
1304 }
1305
1306 i = ipcacheCreateEntry(name);
1307 i->addrs.count = 1;
1308 i->addrs.cur = 0;
1309 i->addrs.badcount = 0;
1310
1311 i->addrs.in_addrs = (IPAddress *)xcalloc(1, sizeof(IPAddress));
1312 i->addrs.bad_mask = (unsigned char *)xcalloc(1, sizeof(unsigned char));
1313 i->addrs.in_addrs[0] = ip;
1314 i->addrs.bad_mask[0] = FALSE;
1315 i->flags.fromhosts = 1;
1316 ipcacheAddEntry(i);
1317 ipcacheLockEntry(i);
1318 return 0;
1319 }
1320
1321 #ifdef SQUID_SNMP
1322 /*
1323 * The function to return the ip cache statistics to via SNMP
1324 */
1325
1326 variable_list *
1327 snmp_netIpFn(variable_list * Var, snint * ErrP)
1328 {
1329 variable_list *Answer = NULL;
1330 debugs(49, 5, "snmp_netIpFn: Processing request:");
1331 snmpDebugOid(5, Var->name, Var->name_length);
1332 *ErrP = SNMP_ERR_NOERROR;
1333
1334 switch (Var->name[LEN_SQ_NET + 1]) {
1335
1336 case IP_ENT:
1337 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1338 memInUse(MEM_IPCACHE_ENTRY),
1339 SMI_GAUGE32);
1340 break;
1341
1342 case IP_REQ:
1343 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1344 IpcacheStats.requests,
1345 SMI_COUNTER32);
1346 break;
1347
1348 case IP_HITS:
1349 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1350 IpcacheStats.hits,
1351 SMI_COUNTER32);
1352 break;
1353
1354 case IP_PENDHIT:
1355 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1356 0,
1357 SMI_GAUGE32);
1358 break;
1359
1360 case IP_NEGHIT:
1361 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1362 IpcacheStats.negative_hits,
1363 SMI_COUNTER32);
1364 break;
1365
1366 case IP_MISS:
1367 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1368 IpcacheStats.misses,
1369 SMI_COUNTER32);
1370 break;
1371
1372 case IP_GHBN:
1373 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1374 0, /* deprecated */
1375 SMI_COUNTER32);
1376 break;
1377
1378 case IP_LOC:
1379 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1380 0, /* deprecated */
1381 SMI_COUNTER32);
1382 break;
1383
1384 default:
1385 *ErrP = SNMP_ERR_NOSUCHNAME;
1386 snmp_var_free(Answer);
1387 return (NULL);
1388 }
1389
1390 return Answer;
1391 }
1392
1393 #endif /*SQUID_SNMP */