3 * $Id: ipcache.cc,v 1.269 2008/02/26 21:49:35 amosjeffries Exp $
5 * DEBUG: section 14 IP Cache
6 * AUTHOR: Harvest Derived
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
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.
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.
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.
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.
39 #include "CacheManager.h"
40 #include "SquidTime.h"
43 #include "IPAddress.h"
46 \defgroup IPCacheAPI IP Cache API
48 \section Introduction Introduction
50 * The IP cache is a built-in component of squid providing
51 * Hostname to IP-Number translation functionality and managing
52 * the involved data-structures. Efficiency concerns require
53 * mechanisms that allow non-blocking access to these mappings.
54 * The IP cache usually doesn't block on a request except for
55 * special cases where this is desired (see below).
57 \todo IP Cache should have its own API *.h header file.
61 \defgroup IPCacheInternal IP Cache Internals
63 \todo when IP cache is provided as a class. These sub-groups will be obsolete
64 * for now they are used to seperate the public and private functions.
65 * with the private ones all being in IPCachInternal and public in IPCacheAPI
67 \section InternalOperation Internal Operation
69 * Internally, the execution flow is as follows: On a miss,
70 * ipcache_getnbhostbyname checks whether a request for
71 * this name is already pending, and if positive, it creates
72 * a new entry using ipcacheAddNew with the IP_PENDING
73 * flag set . Then it calls ipcacheAddPending to add a
74 * request to the queue together with data and handler. Else,
75 * ipcache_dnsDispatch() is called to directly create a
76 * DNS query or to ipcacheEnqueue() if all no DNS port
77 * is free. ipcache_call_pending() is called regularly
78 * to walk down the pending list and call handlers. LRU clean-up
79 * is performed through ipcache_purgelru() according to
80 * the ipcache_high threshold.
83 /// \ingroup IPCacheAPI
84 typedef struct _ipcache_entry ipcache_entry
;
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:
96 hash_link hash
; /* must be first */
104 struct timeval request_time
;
106 unsigned short locks
;
108 unsigned short cname_wait
;
113 unsigned int negcached
:1;
114 unsigned int fromhosts
:1;
118 /// \ingroup IPCacheInternal
119 static struct _ipcache_stats
134 /// \ingroup IPCacheInternal
135 static dlink_list lru_list
;
137 static FREE ipcacheFreeEntry
;
139 static HLPCB ipcacheHandleReply
;
141 static IDNSCB ipcacheHandleReply
;
143 static IPH ipcacheHandleCnameRecurse
;
144 static int ipcacheExpiredEntry(ipcache_entry
*);
145 static int ipcache_testname(void);
147 static int ipcacheParse(ipcache_entry
*, const char *buf
);
149 static int ipcacheParse(ipcache_entry
*, rfc1035_rr
*, int, const char *error
);
151 static ipcache_entry
*ipcache_get(const char *);
152 static void ipcacheLockEntry(ipcache_entry
*);
153 static void ipcacheStatPrint(ipcache_entry
*, StoreEntry
*);
154 static void ipcacheUnlockEntry(ipcache_entry
*);
155 static void ipcacheRelease(ipcache_entry
*, bool dofree
= true);
157 /// \ingroup IPCacheInternal
158 static ipcache_addrs static_addrs
;
159 /// \ingroup IPCacheInternal
160 static hash_table
*ip_table
= NULL
;
162 /// \ingroup IPCacheInternal
163 static long ipcache_low
= 180;
164 /// \ingroup IPCacheInternal
165 static long ipcache_high
= 200;
167 #if LIBRESOLV_DNS_TTL_HACK
168 extern int _dns_ttl_
;
171 /// \ingroup IPCacheInternal
173 ipcache_testname(void)
176 debugs(14, 1, "Performing DNS Tests...");
178 if ((w
= Config
.dns_testname_list
) == NULL
)
181 for (; w
; w
= w
->next
) {
182 if (gethostbyname(w
->key
) != NULL
)
190 \ingroup IPCacheInternal
192 * removes the given ipcache entry
195 ipcacheRelease(ipcache_entry
* i
, bool dofree
)
198 debugs(14, 0, "ipcacheRelease: Releasing entry with i=<NULL>");
202 if(!i
|| !i
->hash
.key
) {
203 debugs(14, 0, "ipcacheRelease: Releasing entry without hash link!");
207 debugs(14, 3, "ipcacheRelease: Releasing entry for '" << (const char *) i
->hash
.key
<< "'");
209 hash_remove_link(ip_table
, (hash_link
*) i
);
210 dlinkDelete(&i
->lru
, &lru_list
);
215 /// \ingroup IPCacheInternal
216 static ipcache_entry
*
217 ipcache_get(const char *name
)
219 if (ip_table
!= NULL
)
220 return (ipcache_entry
*) hash_lookup(ip_table
, name
);
225 /// \ingroup IPCacheInternal
227 ipcacheExpiredEntry(ipcache_entry
* i
)
229 /* all static entries are locked, so this takes care of them too */
234 if (i
->addrs
.count
== 0)
235 if (0 == i
->flags
.negcached
)
238 if (i
->expires
> squid_curtime
)
244 /// \ingroup IPCacheAPI
246 ipcache_purgelru(void *voidnotused
)
249 dlink_node
*prev
= NULL
;
252 eventAdd("ipcache_purgelru", ipcache_purgelru
, NULL
, 10.0, 1);
254 for (m
= lru_list
.tail
; m
; m
= prev
) {
255 if (memInUse(MEM_IPCACHE_ENTRY
) < ipcache_low
)
260 i
= (ipcache_entry
*)m
->data
;
270 debugs(14, 9, "ipcache_purgelru: removed " << removed
<< " entries");
274 \ingroup IPCacheInternal
276 * purges entries added from /etc/hosts (or whatever).
279 purge_entries_fromhosts(void)
281 dlink_node
*m
= lru_list
.head
;
282 ipcache_entry
*i
= NULL
, *t
;
285 if (i
!= NULL
) { /* need to delay deletion */
286 ipcacheRelease(i
); /* we just override locks */
290 t
= (ipcache_entry
*)m
->data
;
292 if (t
->flags
.fromhosts
)
303 \ingroup IPCacheInternal
305 * create blank ipcache_entry
307 static ipcache_entry
*
308 ipcacheCreateEntry(const char *name
)
310 static ipcache_entry
*i
;
311 i
= (ipcache_entry
*)memAllocate(MEM_IPCACHE_ENTRY
);
312 i
->hash
.key
= xstrdup(name
);
313 i
->expires
= squid_curtime
+ Config
.negativeDnsTtl
;
317 /// \ingroup IPCacheInternal
319 ipcacheAddEntry(ipcache_entry
* i
)
321 hash_link
*e
= (hash_link
*)hash_lookup(ip_table
, i
->hash
.key
);
324 /* INET6 : should NOT be adding this entry until all CNAME have been received. */
325 assert(i
->cname_wait
== 0);
329 /* avoid colission */
330 ipcache_entry
*q
= (ipcache_entry
*) e
;
333 /* can occur with Multiple-depth CNAME Recursion if parent returned early with additional */
334 /* just need to drop from the hash without releasing actual memory */
335 ipcacheRelease(q
, false);
342 hash_join(ip_table
, &i
->hash
);
343 dlinkAdd(i
, &i
->lru
, &lru_list
);
344 i
->lastref
= squid_curtime
;
348 \ingroup IPCacheInternal
350 * walks down the pending list, calling handlers
353 ipcacheCallback(ipcache_entry
* i
)
355 IPH
*callback
= i
->handler
;
357 i
->lastref
= squid_curtime
;
364 callback
= i
->handler
;
368 if (cbdataReferenceValidDone(i
->handlerData
, &cbdata
)) {
369 dns_error_message
= i
->error_message
;
370 callback(i
->addrs
.count
? &i
->addrs
: NULL
, cbdata
);
373 ipcacheUnlockEntry(i
);
376 /// \ingroup IPCacheAPI
379 ipcacheParse(ipcache_entry
*i
, const char *inbuf
)
381 LOCAL_ARRAY(char, buf
, DNS_INBUF_SZ
);
386 const char *name
= (const char *)i
->hash
.key
;
387 i
->expires
= squid_curtime
+ Config
.negativeDnsTtl
;
388 i
->flags
.negcached
= 1;
389 safe_free(i
->addrs
.in_addrs
);
390 safe_free(i
->addrs
.bad_mask
);
391 safe_free(i
->error_message
);
395 debugs(14, 1, "ipcacheParse: Got <NULL> reply");
396 i
->error_message
= xstrdup("Internal Error");
400 xstrncpy(buf
, inbuf
, DNS_INBUF_SZ
);
401 debugs(14, 5, "ipcacheParse: parsing: {" << buf
<< "}");
402 token
= strtok(buf
, w_space
);
405 debugs(14, 1, "ipcacheParse: expecting result, got '" << inbuf
<< "'");
407 i
->error_message
= xstrdup("Internal Error");
411 if (0 == strcmp(token
, "$fail")) {
412 token
= strtok(NULL
, "\n");
413 assert(NULL
!= token
);
414 i
->error_message
= xstrdup(token
);
418 if (0 != strcmp(token
, "$addr")) {
419 debugs(14, 1, "ipcacheParse: expecting '$addr', got '" << inbuf
<< "' in response to '" << name
<< "'");
421 i
->error_message
= xstrdup("Internal Error");
425 token
= strtok(NULL
, w_space
);
428 debugs(14, 1, "ipcacheParse: expecting TTL, got '" << inbuf
<< "' in response to '" << name
<< "'");
430 i
->error_message
= xstrdup("Internal Error");
436 while (NULL
!= (token
= strtok(NULL
, w_space
))) {
446 i
->addrs
.in_addrs
= (IPAddress
*)xcalloc(ipcount
, sizeof(IPAddress
));
447 for(int l
= 0; l
< ipcount
; l
++)
448 i
->addrs
.in_addrs
[l
].SetEmpty(); // perform same init actions as constructor would.
449 i
->addrs
.bad_mask
= (unsigned char *)xcalloc(ipcount
, sizeof(unsigned char));
450 memset(i
->addrs
.bad_mask
, 0, sizeof(unsigned char) * ipcount
);
452 for (j
= 0, k
= 0; k
< ipcount
; k
++) {
453 if ( i
->addrs
.in_addrs
[j
] = A
[k
] )
456 debugs(14, 1, "ipcacheParse: Invalid IP address '" << A
[k
] << "' in response to '" << name
<< "'");
459 i
->addrs
.count
= (unsigned char) j
;
462 if (i
->addrs
.count
<= 0) {
463 debugs(14, 1, "ipcacheParse: No addresses in response to '" << name
<< "'");
467 if (ttl
== 0 || ttl
> Config
.positiveDnsTtl
)
468 ttl
= Config
.positiveDnsTtl
;
470 if (ttl
< Config
.negativeDnsTtl
)
471 ttl
= Config
.negativeDnsTtl
;
473 i
->expires
= squid_curtime
+ ttl
;
475 i
->flags
.negcached
= 0;
477 return i
->addrs
.count
;
482 ipcacheParse(ipcache_entry
*i
, rfc1035_rr
* answers
, int nr
, const char *error_message
)
488 const char *name
= (const char *)i
->hash
.key
;
491 i
->expires
= squid_curtime
+ Config
.negativeDnsTtl
;
492 i
->flags
.negcached
= 1;
493 safe_free(i
->addrs
.in_addrs
);
494 assert(i
->addrs
.in_addrs
== NULL
);
495 safe_free(i
->addrs
.bad_mask
);
496 assert(i
->addrs
.bad_mask
== NULL
);
497 safe_free(i
->error_message
);
498 assert(i
->error_message
== NULL
);
502 debugs(14, 3, "ipcacheParse: Lookup failed '" << error_message
<< "' for '" << (const char *)i
->hash
.key
<< "'");
503 i
->error_message
= xstrdup(error_message
);
508 debugs(14, 3, "ipcacheParse: No DNS records in response to '" << name
<< "'");
509 i
->error_message
= xstrdup("No DNS records");
515 for (k
= 0; k
< nr
; k
++) {
518 if (answers
[k
].type
== RFC1035_TYPE_AAAA
) {
519 if (answers
[k
].rdlength
!= sizeof(struct in6_addr
)) {
520 debugs(14, 1, "ipcacheParse: Invalid IPv6 address in response to '" << name
<< "'");
524 IpcacheStats
.rr_aaaa
++;
529 if (answers
[k
].type
== RFC1035_TYPE_A
) {
530 if (answers
[k
].rdlength
!= sizeof(struct in_addr
)) {
531 debugs(14, 1, "ipcacheParse: Invalid IPv4 address in response to '" << name
<< "'");
539 /* With A and AAAA, the CNAME does not necessarily come with additional records to use. */
540 if (answers
[k
].type
== RFC1035_TYPE_CNAME
) {
542 IpcacheStats
.rr_cname
++;
545 debugs(14, 5, "ipcacheParse: " << name
<< " CNAME " << answers
[k
].rdata
<< " (checking destination: " << i
<< ").");
546 const ipcache_addrs
*res
= ipcache_gethostbyname(answers
[k
].rdata
, 0);
549 debugs(14, 5, "ipcacheParse: CNAME " << answers
[k
].rdata
<< " already has " << res
->count
<< " IPs cached.");
552 /* keep going on this, but flag the fact that we need to wait for a CNAME lookup to finish */
553 debugs(14, 5, "ipcacheParse: CNAME " << answers
[k
].rdata
<< " has no IPs! Recursing.");
554 ipcache_nbgethostbyname(answers
[k
].rdata
, ipcacheHandleCnameRecurse
, new generic_cbdata(i
) );
557 #endif /* DNS_CNAME */
562 // otherwise its an unknown RR. debug at level 9 since we usually want to ignore these and they are common.
563 debugs(14, 9, HERE
<< "Unknown RR type received: type=" << answers
[k
].type
<< " starting at " << &(answers
[k
]) );
567 if(na
== 0 && i
->cname_wait
>0 ) {
568 /* don't set any error message (yet). Allow recursion to do its work first. */
569 IpcacheStats
.cname_only
++;
572 #endif /* DNS_CNAME */
575 debugs(14, 1, "ipcacheParse: No Address records in response to '" << name
<< "'");
576 i
->error_message
= xstrdup("No Address records");
578 IpcacheStats
.cname_only
++;
582 i
->addrs
.in_addrs
= (IPAddress
*)xcalloc(na
, sizeof(IPAddress
));
583 for(int l
= 0; l
< na
; l
++)
584 i
->addrs
.in_addrs
[l
].SetEmpty(); // perform same init actions as constructor would.
585 i
->addrs
.bad_mask
= (unsigned char *)xcalloc(na
, sizeof(unsigned char));
587 for (j
= 0, k
= 0; k
< nr
; k
++) {
589 if (answers
[k
].type
== RFC1035_TYPE_A
) {
590 if (answers
[k
].rdlength
!= sizeof(struct in_addr
))
594 xmemcpy(&temp
, answers
[k
].rdata
, sizeof(struct in_addr
));
595 i
->addrs
.in_addrs
[j
] = temp
;
597 debugs(14, 3, "ipcacheParse: " << name
<< " #" << j
<< " " << i
->addrs
.in_addrs
[j
]);
601 } else if (answers
[k
].type
== RFC1035_TYPE_AAAA
) {
602 if (answers
[k
].rdlength
!= sizeof(struct in6_addr
))
605 struct in6_addr temp
;
606 xmemcpy(&temp
, answers
[k
].rdata
, sizeof(struct in6_addr
));
607 i
->addrs
.in_addrs
[j
] = temp
;
609 debugs(14, 3, "ipcacheParse: " << name
<< " #" << j
<< " " << i
->addrs
.in_addrs
[j
] );
614 else if (answers
[k
].type
== RFC1035_TYPE_CNAME
) {
615 debugs(14, 3, "ipcacheParse: " << name
<< " #x CNAME " << answers
[k
].rdata
);
616 const ipcache_addrs
*res
= ipcache_gethostbyname(answers
[k
].rdata
, 0);
618 /* NP: the results of *that* query need to be integrated in place of the CNAME */
619 /* Ideally we should also integrate the min TTL of the above IPA's into ttl. */
620 for(int l
= 0; l
< res
->count
; l
++, j
++) {
621 i
->addrs
.in_addrs
[j
] = res
->in_addrs
[l
];
622 debugs(14, 3, "ipcacheParse: " << name
<< " #" << j
<< " " << i
->addrs
.in_addrs
[j
] );
626 debugs(14, 9, "ipcacheParse: " << answers
[k
].rdata
<< " (CNAME) waiting on A/AAAA records.");
629 #endif /* DNS_CNAME */
631 if (ttl
== 0 || (int) answers
[k
].ttl
< ttl
)
632 ttl
= answers
[k
].ttl
;
638 i
->addrs
.count
= (unsigned char) na
;
640 i
->addrs
.count
= 255;
642 if (ttl
> Config
.positiveDnsTtl
)
643 ttl
= Config
.positiveDnsTtl
;
645 if (ttl
< Config
.negativeDnsTtl
)
646 ttl
= Config
.negativeDnsTtl
;
648 i
->expires
= squid_curtime
+ ttl
;
650 i
->flags
.negcached
= 0;
653 /* SPECIAL CASE: may get here IFF CNAME received with Additional records */
654 /* reurn 0/'wait for further details' value. */
655 /* NP: 'No DNS Results' is a return -1 +msg */
659 #endif /* DNS_CNAME */
660 return i
->addrs
.count
;
665 /// \ingroup IPCacheInternal
668 ipcacheHandleReply(void *data
, char *reply
)
670 ipcacheHandleReply(void *data
, rfc1035_rr
* answers
, int na
, const char *error_message
)
675 static_cast<generic_cbdata
*>(data
)->unwrap(&i
);
676 IpcacheStats
.replies
++;
677 statHistCount(&statCounter
.dns
.svc_time
,
678 tvSubMsec(i
->request_time
, current_time
));
681 done
= ipcacheParse(i
, reply
);
684 done
= ipcacheParse(i
, answers
, na
, error_message
);
686 /* If we have not produced either IPs or Error immediately, wait for recursion to finish. */
687 if(done
!= 0 || error_message
!= NULL
)
699 \param name Host to resolve.
700 \param handler Pointer to the function to be called when the reply
701 * from the IP cache (or the DNS if the IP cache misses)
702 \param handlerData Information that is passed to the handler and does not affect the IP cache.
705 ipcache_nbgethostbyname(const char *name
, IPH
* handler
, void *handlerData
)
707 ipcache_entry
*i
= NULL
;
708 const ipcache_addrs
*addrs
= NULL
;
710 assert(handler
!= NULL
);
711 debugs(14, 4, "ipcache_nbgethostbyname: Name '" << name
<< "'.");
712 IpcacheStats
.requests
++;
714 if (name
== NULL
|| name
[0] == '\0') {
715 debugs(14, 4, "ipcache_nbgethostbyname: Invalid name!");
716 IpcacheStats
.invalid
++;
717 dns_error_message
= "Invalid hostname";
718 handler(NULL
, handlerData
);
722 if ((addrs
= ipcacheCheckNumeric(name
))) {
723 debugs(14, 4, "ipcache_nbgethostbyname: BYPASS for '" << name
<< "' (already numeric)");
724 dns_error_message
= NULL
;
725 IpcacheStats
.numeric_hits
++;
726 handler(addrs
, handlerData
);
730 i
= ipcache_get(name
);
735 } else if (ipcacheExpiredEntry(i
)) {
736 /* hit, but expired -- bummer */
741 debugs(14, 4, "ipcache_nbgethostbyname: HIT for '" << name
<< "'");
743 if (i
->flags
.negcached
)
744 IpcacheStats
.negative_hits
++;
748 i
->handler
= handler
;
750 i
->handlerData
= cbdataReference(handlerData
);
757 debugs(14, 5, "ipcache_nbgethostbyname: MISS for '" << name
<< "'");
758 IpcacheStats
.misses
++;
759 i
= ipcacheCreateEntry(name
);
760 i
->handler
= handler
;
761 i
->handlerData
= cbdataReference(handlerData
);
762 i
->request_time
= current_time
;
763 c
= new generic_cbdata(i
);
766 dnsSubmit(hashKeyStr(&i
->hash
), ipcacheHandleReply
, c
);
769 idnsALookup(hashKeyStr(&i
->hash
), ipcacheHandleReply
, c
);
773 /// \ingroup IPCacheInternal
775 ipcacheRegisterWithCacheManager(void)
777 CacheManager::GetInstance()->
778 registerAction("ipcache",
779 "IP Cache Stats and Contents",
780 stat_ipcache_get
, 0, 1);
787 * Initialize the ipcache.
788 * Is called from mainInitialize() after disk initialization
789 * and prior to the reverse FQDNCache initialization
795 debugs(14, 3, "Initializing IP Cache...");
796 memset(&IpcacheStats
, '\0', sizeof(IpcacheStats
));
797 memset(&lru_list
, '\0', sizeof(lru_list
));
798 /* test naming lookup */
800 if (!opt_dns_tests
) {
801 debugs(14, 4, "ipcache_init: Skipping DNS name lookup tests.");
802 } else if (!ipcache_testname()) {
803 fatal("ipcache_init: DNS name lookup tests failed.");
805 debugs(14, 1, "Successful DNS name lookup tests...");
808 memset(&static_addrs
, '\0', sizeof(ipcache_addrs
));
810 static_addrs
.in_addrs
= (IPAddress
*)xcalloc(1, sizeof(IPAddress
));
811 static_addrs
.in_addrs
->SetEmpty(); // properly setup the IPAddress!
812 static_addrs
.bad_mask
= (unsigned char *)xcalloc(1, sizeof(unsigned char));
813 ipcache_high
= (long) (((float) Config
.ipcache
.size
*
814 (float) Config
.ipcache
.high
) / (float) 100);
815 ipcache_low
= (long) (((float) Config
.ipcache
.size
*
816 (float) Config
.ipcache
.low
) / (float) 100);
817 n
= hashPrime(ipcache_high
/ 4);
818 ip_table
= hash_create((HASHCMP
*) strcmp
, n
, hash4
);
819 memDataInit(MEM_IPCACHE_ENTRY
, "ipcache_entry", sizeof(ipcache_entry
), 0);
821 ipcacheRegisterWithCacheManager();
827 * Is different from ipcache_nbgethostbyname in that it only checks
828 * if an entry exists in the cache and does not by default contact the DNS,
829 * unless this is requested, by setting the flags.
831 \param name Host name to resolve.
832 \param flags Default is NULL, set to IP_LOOKUP_IF_MISS
833 * to explicitly perform DNS lookups.
835 \retval NULL An error occured during lookup
836 \retval NULL No results available in cache and no lookup specified
837 \retval * Pointer to the ipcahce_addrs structure containing the lookup results
839 const ipcache_addrs
*
840 ipcache_gethostbyname(const char *name
, int flags
)
842 ipcache_entry
*i
= NULL
;
843 ipcache_addrs
*addrs
;
845 debugs(14, 3, "ipcache_gethostbyname: '" << name
<< "', flags=" << std::hex
<< flags
);
846 IpcacheStats
.requests
++;
847 i
= ipcache_get(name
);
851 } else if (ipcacheExpiredEntry(i
)) {
854 } else if (i
->flags
.negcached
) {
855 IpcacheStats
.negative_hits
++;
856 dns_error_message
= i
->error_message
;
860 i
->lastref
= squid_curtime
;
861 dns_error_message
= i
->error_message
;
865 dns_error_message
= NULL
;
867 if ((addrs
= ipcacheCheckNumeric(name
))) {
868 IpcacheStats
.numeric_hits
++;
872 IpcacheStats
.misses
++;
874 if (flags
& IP_LOOKUP_IF_MISS
)
875 ipcache_nbgethostbyname(name
, ipcacheHandleCnameRecurse
, NULL
);
880 /// \ingroup IPCacheInternal
882 ipcacheStatPrint(ipcache_entry
* i
, StoreEntry
* sentry
)
885 int count
= i
->addrs
.count
;
886 char buf
[MAX_IPSTRLEN
];
889 debugs(14, 0, HERE
<< "CRITICAL: sentry is NULL!");
893 debugs(14, 0, HERE
<< "CRITICAL: ipcache_entry is NULL!");
894 storeAppendPrintf(sentry
, "CRITICAL ERROR\n");
898 storeAppendPrintf(sentry
, " %-32.32s %c%c %6d %6d %2d(%2d)",
899 hashKeyStr(&i
->hash
),
900 i
->flags
.fromhosts
? 'H' : ' ',
901 i
->flags
.negcached
? 'N' : ' ',
902 (int) (squid_curtime
- i
->lastref
),
903 (int) ((i
->flags
.fromhosts
? -1 : i
->expires
- squid_curtime
)),
904 (int) i
->addrs
.count
,
905 (int) i
->addrs
.badcount
);
908 * Negative-cached entries have no IPs listed. */
909 if(i
->flags
.negcached
) {
910 storeAppendPrintf(sentry
, "\n");
915 * Cached entries have IPs listed with a BNF of: <IP> '-' ('OK'|'BAD') */
916 for (k
= 0; k
< count
; k
++) {
917 /* Display tidy-up: IPv6 are so big make the list vertical */
919 storeAppendPrintf(sentry
, " %45.45s-%3s\n",
920 i
->addrs
.in_addrs
[k
].NtoA(buf
,MAX_IPSTRLEN
),
921 i
->addrs
.bad_mask
[k
] ? "BAD" : "OK ");
923 storeAppendPrintf(sentry
, "%s %45.45s-%3s\n",
924 " ", /* blank-space indenting IP list */
925 i
->addrs
.in_addrs
[k
].NtoA(buf
,MAX_IPSTRLEN
),
926 i
->addrs
.bad_mask
[k
] ? "BAD" : "OK ");
931 \ingroup IPCacheInternal
933 * process objects list
936 stat_ipcache_get(StoreEntry
* sentry
)
939 assert(ip_table
!= NULL
);
940 storeAppendPrintf(sentry
, "IP Cache Statistics:\n");
941 storeAppendPrintf(sentry
, "IPcache Entries: %d\n",
942 memInUse(MEM_IPCACHE_ENTRY
));
943 storeAppendPrintf(sentry
, "IPcache Requests: %d\n",
944 IpcacheStats
.requests
);
945 storeAppendPrintf(sentry
, "IPcache Hits: %d\n",
947 storeAppendPrintf(sentry
, "IPcache Negative Hits: %d\n",
948 IpcacheStats
.negative_hits
);
949 storeAppendPrintf(sentry
, "IPcache Numeric Hits: %d\n",
950 IpcacheStats
.numeric_hits
);
951 storeAppendPrintf(sentry
, "IPcache Misses: %d\n",
952 IpcacheStats
.misses
);
953 storeAppendPrintf(sentry
, "IPcache Retrieved A: %d\n",
955 storeAppendPrintf(sentry
, "IPcache Retrieved AAAA: %d\n",
956 IpcacheStats
.rr_aaaa
);
957 storeAppendPrintf(sentry
, "IPcache Retrieved CNAME: %d\n",
958 IpcacheStats
.rr_cname
);
959 storeAppendPrintf(sentry
, "IPcache CNAME-Only Response: %d\n",
960 IpcacheStats
.cname_only
);
961 storeAppendPrintf(sentry
, "IPcache Invalid Request: %d\n",
962 IpcacheStats
.invalid
);
963 storeAppendPrintf(sentry
, "\n\n");
964 storeAppendPrintf(sentry
, "IP Cache Contents:\n\n");
965 storeAppendPrintf(sentry
, " %-31.31s %3s %6s %6s %4s\n",
972 for (m
= lru_list
.head
; m
; m
= m
->next
) {
973 assert( m
->next
!= m
);
974 ipcacheStatPrint((ipcache_entry
*)m
->data
, sentry
);
980 * Takes two IPAddress arrays and merges them into a single array
981 * which is allocated dynamically to fit the number of unique addresses
983 \param aaddrs One list to merge
984 \param alen Size of list aaddrs
985 \param baddrs Other list to merge
986 \param alen Size of list baddrs
987 \param out Combined list of unique addresses (sorted with IPv6 first in IPv6-mode)
988 \param outlen Size of list out
991 ipcacheMergeIPLists(const IPAddress
*aaddrs
, const int alen
,
992 const IPAddress
*baddrs
, const int blen
,
993 IPAddress
**out
, int &outlen
)
997 IPAddress
const *ip4ptrs
[255];
999 IPAddress
const *ip6ptrs
[255];
1004 memset(ip4ptrs
, 0, sizeof(IPAddress
*)*255);
1006 memset(ip6ptrs
, 0, sizeof(IPAddress
*)*255);
1009 // for each unique address in list A - grab ptr
1010 for(t
= 0; t
< alen
; t
++) {
1011 if(aaddrs
[t
].IsIPv4()) {
1012 // check against IPv4 pruned list
1013 for(c
= 0; c
<= num_ip4
; c
++) {
1014 if(ip4ptrs
[c
] && aaddrs
[t
] == *(ip4ptrs
[c
]) ) break; // duplicate.
1017 ip4ptrs
[num_ip4
] = &aaddrs
[t
];
1022 else if(aaddrs
[t
].IsIPv6()) {
1023 debugs(14,8, HERE
<< "A[" << t
<< "]=IPv6 " << aaddrs
[t
]);
1024 // check against IPv6 pruned list
1025 for(c
= 0; c
<= num_ip6
; c
++) {
1026 if(ip6ptrs
[c
] && aaddrs
[t
] == *ip6ptrs
[c
]) break; // duplicate.
1029 ip6ptrs
[num_ip6
] = &aaddrs
[t
];
1036 // for each unique address in list B - grab ptr
1037 for(t
= 0; t
< blen
; t
++) {
1038 if(baddrs
[t
].IsIPv4()) {
1039 // check against IPv4 pruned list
1040 for(c
= 0; c
<= num_ip4
; c
++) {
1041 if(ip4ptrs
[c
] && baddrs
[t
] == *ip4ptrs
[c
]) break; // duplicate.
1044 ip4ptrs
[num_ip4
] = &baddrs
[t
];
1049 else if(baddrs
[t
].IsIPv6()) {
1050 // check against IPv6 pruned list
1051 for(c
= 0; c
<= num_ip6
; c
++) {
1052 if(ip6ptrs
[c
] && baddrs
[t
] == *ip6ptrs
[c
]) break; // duplicate.
1055 ip6ptrs
[num_ip6
] = &baddrs
[t
];
1062 fc
= num_ip6
+ num_ip4
;
1066 debugs(14, 5, "ipcacheMergeIPLists: Merge " << alen
<< "+" << blen
<< " into " << fc
<< " unique IPs.");
1068 // copy the old IPs into the new list buffer.
1069 (*out
) = (IPAddress
*)xcalloc(fc
, sizeof(IPAddress
));
1072 assert(out
!= NULL
);
1075 /* IPv6 are preferred (tried first) over IPv4 */
1077 for(int l
= 0; outlen
< num_ip6
; l
++, outlen
++) {
1078 (*out
)[outlen
] = *ip6ptrs
[l
];
1079 debugs(14, 5, "ipcacheMergeIPLists: #" << outlen
<< " " << (*out
)[outlen
] );
1081 #endif /* USE_IPV6 */
1083 for(int l
= 0; outlen
< num_ip4
; l
++, outlen
++) {
1084 (*out
)[outlen
] = *ip4ptrs
[l
];
1085 debugs(14, 5, "ipcacheMergeIPLists: #" << outlen
<< " " << (*out
)[outlen
] );
1088 assert(outlen
== fc
); // otherwise something broke badly!
1090 #endif /* DNS_CNAME */
1092 /// \ingroup IPCacheInternal
1095 ipcacheHandleCnameRecurse(const ipcache_addrs
*addrs
, void *cbdata
)
1098 ipcache_entry
*i
= NULL
;
1100 IPAddress
*tmpbuf
= NULL
;
1103 generic_cbdata
* gcb
= (generic_cbdata
*)cbdata
;
1104 // count of addrs at parent and child (REQ as .count is a char type!)
1105 int ccount
= 0, pcount
= 0;
1107 debugs(14, 5, "ipcacheHandleCnameRecurse: Handling basic A/AAAA response.");
1109 /* IFF no CNAME recursion being processed. do nothing. */
1116 // make sure we are actualy waiting for a CNAME callback to be run.
1117 assert(i
->cname_wait
> 0);
1118 // count this event. its being handled.
1121 pname
= (char*)i
->hash
.key
;
1122 assert(pname
!= NULL
);
1124 debugs(14, 5, "ipcacheHandleCnameRecurse: Handling CNAME recursion. CBDATA('" << gcb
->data
<< "')='" << pname
<< "' -> " << std::hex
<< i
);
1127 return; // Parent has expired. Don't merge, just leave for future Ref:
1130 /* IFF addrs is NULL (Usually an Error or Timeout occured on lookup.) */
1131 /* Ignore it and HOPE that we got some Additional records to use. */
1135 ccount
= (0+ addrs
->count
);
1136 pcount
= (0+ i
->addrs
.count
);
1139 /* IFF no CNAME results. do none of the processing BUT finish anyway. */
1142 debugs(14, 5, "ipcacheHandleCnameRecurse: Merge IP Lists for " << pname
<< " (" << pcount
<< "+" << ccount
<< ")");
1144 /* add new IP records to entry */
1145 tmpbuf
= i
->addrs
.in_addrs
;
1146 i
->addrs
.in_addrs
= NULL
;
1147 ipcacheMergeIPLists(tmpbuf
, pcount
, addrs
->in_addrs
, ccount
, &(i
->addrs
.in_addrs
), fc
);
1148 debugs(14,8, HERE
<< "in=" << tmpbuf
<< ", out=" << i
->addrs
.in_addrs
);
1149 assert( (pcount
>0 ? tmpbuf
!=NULL
: tmpbuf
==NULL
) );
1153 /* IFF the parent initial lookup was given Additional records with A */
1154 // clear the 'bad IP mask'
1155 safe_free(i
->addrs
.bad_mask
);
1157 // create a new bad IP mask to fit the new size needed.
1159 i
->addrs
.bad_mask
= (unsigned char*)xcalloc(fc
, sizeof(unsigned char));
1160 memset(i
->addrs
.bad_mask
, 0, sizeof(unsigned char)*fc
);
1164 i
->addrs
.count
= (unsigned char) fc
;
1166 i
->addrs
.count
= 255;
1168 if (ttl
== 0 || ttl
> Config
.positiveDnsTtl
)
1169 ttl
= Config
.positiveDnsTtl
;
1171 if (ttl
< Config
.negativeDnsTtl
)
1172 ttl
= Config
.negativeDnsTtl
;
1174 i
->expires
= squid_curtime
+ ttl
;
1176 i
->flags
.negcached
= 0;
1180 i
->addrs
.badcount
= 0;
1184 i
->error_message
= xstrdup("No DNS Records");
1187 /* finish the lookup we were doing on parent when we got side-tracked for CNAME loop */
1188 if(i
->cname_wait
== 0) {
1192 // else still more CNAME to be found.
1193 #endif /* DNS_CNAME */
1196 /// \ingroup IPCacheAPI
1198 ipcacheInvalidate(const char *name
)
1202 if ((i
= ipcache_get(name
)) == NULL
)
1205 i
->expires
= squid_curtime
;
1208 * NOTE, don't call ipcacheRelease here because we might be here due
1209 * to a thread started from a callback.
1213 /// \ingroup IPCacheAPI
1215 ipcacheInvalidateNegative(const char *name
)
1219 if ((i
= ipcache_get(name
)) == NULL
)
1222 if (i
->flags
.negcached
)
1223 i
->expires
= squid_curtime
;
1226 * NOTE, don't call ipcacheRelease here because we might be here due
1227 * to a thread started from a callback.
1231 /// \ingroup IPCacheAPI
1233 ipcacheCheckNumeric(const char *name
)
1237 /* check if it's already a IP address in text form. */
1239 /* it may be IPv6-wrapped */
1240 if(name
[0] == '[') {
1241 char *tmp
= xstrdup(&name
[1]);
1242 tmp
[strlen(tmp
)-1] = '\0';
1249 else if (!(ip
= name
))
1252 debugs(14, 4, "ipcacheCheckNumeric: HIT_BYPASS for '" << name
<< "' == " << ip
);
1254 static_addrs
.count
= 1;
1256 static_addrs
.cur
= 0;
1258 static_addrs
.in_addrs
[0] = ip
;
1260 static_addrs
.bad_mask
[0] = FALSE
;
1262 static_addrs
.badcount
= 0;
1264 return &static_addrs
;
1267 /// \ingroup IPCacheInternal
1269 ipcacheLockEntry(ipcache_entry
* i
)
1271 if (i
->locks
++ == 0) {
1272 dlinkDelete(&i
->lru
, &lru_list
);
1273 dlinkAdd(i
, &i
->lru
, &lru_list
);
1277 /// \ingroup IPCacheInternal
1279 ipcacheUnlockEntry(ipcache_entry
* i
)
1282 debugs(14, 1, "WARNING: ipcacheEntry unlocked with no lock! locks=" << i
->locks
);
1288 if (ipcacheExpiredEntry(i
))
1292 /// \ingroup IPCacheAPI
1294 ipcacheCycleAddr(const char *name
, ipcache_addrs
* ia
)
1301 if ((i
= ipcache_get(name
)) == NULL
)
1304 if (i
->flags
.negcached
)
1310 for (k
= 0; k
< ia
->count
; k
++) {
1311 if (++ia
->cur
== ia
->count
)
1314 if (!ia
->bad_mask
[ia
->cur
])
1318 if (k
== ia
->count
) {
1319 /* All bad, reset to All good */
1320 debugs(14, 3, "ipcacheCycleAddr: Changing ALL " << name
<< " addrs from BAD to OK");
1322 for (k
= 0; k
< ia
->count
; k
++)
1323 ia
->bad_mask
[k
] = 0;
1330 debugs(14, 3, "ipcacheCycleAddr: " << name
<< " now at " << ia
->in_addrs
[ia
->cur
] << " (" << ia
->cur
<< " of " << ia
->count
<< ")");
1336 \param name domain name to have an IP marked bad
1337 \param addr specific addres to be marked bad
1340 ipcacheMarkBadAddr(const char *name
, IPAddress
&addr
)
1346 /** Does nothing if the domain name does not exist. */
1347 if ((i
= ipcache_get(name
)) == NULL
)
1352 for (k
= 0; k
< (int) ia
->count
; k
++)
1354 if (addr
== ia
->in_addrs
[k
] )
1358 /** Does nothing if the IP does not exist for the doamin. */
1359 if (k
== (int) ia
->count
)
1362 /** Marks the given address as BAD */
1363 if (!ia
->bad_mask
[k
])
1365 ia
->bad_mask
[k
] = TRUE
;
1367 i
->expires
= XMIN(squid_curtime
+ XMAX((time_t)60, Config
.negativeDnsTtl
), i
->expires
);
1368 debugs(14, 2, "ipcacheMarkBadAddr: " << name
<< " " << addr
);
1371 /** then calls ipcacheCycleAddr() to advance the current pointer to the next OK address. */
1372 ipcacheCycleAddr(name
, ia
);
1375 /// \ingroup IPCacheAPI
1377 ipcacheMarkGoodAddr(const char *name
, IPAddress
&addr
)
1383 if ((i
= ipcache_get(name
)) == NULL
)
1388 for (k
= 0; k
< (int) ia
->count
; k
++)
1390 if (addr
== ia
->in_addrs
[k
])
1394 if (k
== (int) ia
->count
) /* not found */
1397 if (!ia
->bad_mask
[k
]) /* already OK */
1400 ia
->bad_mask
[k
] = FALSE
;
1404 debugs(14, 2, "ipcacheMarkGoodAddr: " << name
<< " " << addr
);
1407 /// \ingroup IPCacheInternal
1409 ipcacheFreeEntry(void *data
)
1411 ipcache_entry
*i
= (ipcache_entry
*)data
;
1412 safe_free(i
->addrs
.in_addrs
);
1413 safe_free(i
->addrs
.bad_mask
);
1414 safe_free(i
->hash
.key
);
1415 safe_free(i
->error_message
);
1416 memFree(i
, MEM_IPCACHE_ENTRY
);
1419 /// \ingroup IPCacheAPI
1421 ipcacheFreeMemory(void)
1423 hashFreeItems(ip_table
, ipcacheFreeEntry
);
1424 hashFreeMemory(ip_table
);
1431 * Recalculate IP cache size upon reconfigure.
1432 * Is called to clear the IPCache's data structures,
1433 * cancel all pending requests.
1436 ipcache_restart(void)
1438 ipcache_high
= (long) (((float) Config
.ipcache
.size
*
1439 (float) Config
.ipcache
.high
) / (float) 100);
1440 ipcache_low
= (long) (((float) Config
.ipcache
.size
*
1441 (float) Config
.ipcache
.low
) / (float) 100);
1442 purge_entries_fromhosts();
1448 * Adds a "static" entry from /etc/hosts
1450 \param name Hostname to be linked with IP
1451 \param ipaddr IP Address to be cached.
1454 \retval 1 IP address is invalid or other error.
1457 ipcacheAddEntryFromHosts(const char *name
, const char *ipaddr
)
1463 if (!(ip
= ipaddr
)) {
1465 if (strchr(ipaddr
, ':') && strspn(ipaddr
, "0123456789abcdefABCDEF:") == strlen(ipaddr
)) {
1466 debugs(14, 3, "ipcacheAddEntryFromHosts: Skipping IPv6 address '" << ipaddr
<< "'");
1468 debugs(14, 1, "ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr
<< "'");
1471 debugs(14, 1, "ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr
<< "'");
1477 if ((i
= ipcache_get(name
))) {
1478 if (1 == i
->flags
.fromhosts
) {
1479 ipcacheUnlockEntry(i
);
1480 } else if (i
->locks
> 0) {
1481 debugs(14, 1, "ipcacheAddEntryFromHosts: can't add static entry for locked name '" << name
<< "'");
1488 i
= ipcacheCreateEntry(name
);
1491 i
->addrs
.badcount
= 0;
1493 i
->addrs
.in_addrs
= (IPAddress
*)xcalloc(1, sizeof(IPAddress
));
1494 i
->addrs
.bad_mask
= (unsigned char *)xcalloc(1, sizeof(unsigned char));
1495 i
->addrs
.in_addrs
[0] = ip
;
1496 i
->addrs
.bad_mask
[0] = FALSE
;
1497 i
->flags
.fromhosts
= 1;
1499 ipcacheLockEntry(i
);
1507 * The function to return the ip cache statistics to via SNMP
1510 snmp_netIpFn(variable_list
* Var
, snint
* ErrP
)
1512 variable_list
*Answer
= NULL
;
1513 debugs(49, 5, "snmp_netIpFn: Processing request:");
1514 snmpDebugOid(5, Var
->name
, Var
->name_length
);
1515 *ErrP
= SNMP_ERR_NOERROR
;
1517 switch (Var
->name
[LEN_SQ_NET
+ 1]) {
1520 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1521 memInUse(MEM_IPCACHE_ENTRY
),
1526 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1527 IpcacheStats
.requests
,
1532 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1538 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1544 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1545 IpcacheStats
.negative_hits
,
1550 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1551 IpcacheStats
.misses
,
1556 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1562 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1568 *ErrP
= SNMP_ERR_NOSUCHNAME
;
1569 snmp_var_free(Answer
);
1576 #endif /*SQUID_SNMP */