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:
94 struct _ipcache_entry
{
95 hash_link hash
; /* must be first */
103 struct timeval request_time
;
105 unsigned short locks
;
107 unsigned short cname_wait
;
111 unsigned int negcached
:1;
112 unsigned int fromhosts
:1;
116 /// \ingroup IPCacheInternal
117 static struct _ipcache_stats
{
131 /// \ingroup IPCacheInternal
132 static dlink_list lru_list
;
134 static FREE ipcacheFreeEntry
;
136 static HLPCB ipcacheHandleReply
;
138 static IDNSCB ipcacheHandleReply
;
140 static IPH ipcacheHandleCnameRecurse
;
141 static int ipcacheExpiredEntry(ipcache_entry
*);
143 static int ipcacheParse(ipcache_entry
*, const char *buf
);
145 static int ipcacheParse(ipcache_entry
*, rfc1035_rr
*, int, const char *error
);
147 static ipcache_entry
*ipcache_get(const char *);
148 static void ipcacheLockEntry(ipcache_entry
*);
149 static void ipcacheStatPrint(ipcache_entry
*, StoreEntry
*);
150 static void ipcacheUnlockEntry(ipcache_entry
*);
151 static void ipcacheRelease(ipcache_entry
*, bool dofree
= true);
153 /// \ingroup IPCacheInternal
154 static ipcache_addrs static_addrs
;
155 /// \ingroup IPCacheInternal
156 static hash_table
*ip_table
= NULL
;
158 /// \ingroup IPCacheInternal
159 static long ipcache_low
= 180;
160 /// \ingroup IPCacheInternal
161 static long ipcache_high
= 200;
163 #if LIBRESOLV_DNS_TTL_HACK
164 extern int _dns_ttl_
;
168 \ingroup IPCacheInternal
170 * removes the given ipcache entry
173 ipcacheRelease(ipcache_entry
* i
, bool dofree
)
176 debugs(14, 0, "ipcacheRelease: Releasing entry with i=<NULL>");
180 if (!i
|| !i
->hash
.key
) {
181 debugs(14, 0, "ipcacheRelease: Releasing entry without hash link!");
185 debugs(14, 3, "ipcacheRelease: Releasing entry for '" << (const char *) i
->hash
.key
<< "'");
187 hash_remove_link(ip_table
, (hash_link
*) i
);
188 dlinkDelete(&i
->lru
, &lru_list
);
193 /// \ingroup IPCacheInternal
194 static ipcache_entry
*
195 ipcache_get(const char *name
)
197 if (ip_table
!= NULL
)
198 return (ipcache_entry
*) hash_lookup(ip_table
, name
);
203 /// \ingroup IPCacheInternal
205 ipcacheExpiredEntry(ipcache_entry
* i
)
207 /* all static entries are locked, so this takes care of them too */
212 if (i
->addrs
.count
== 0)
213 if (0 == i
->flags
.negcached
)
216 if (i
->expires
> squid_curtime
)
222 /// \ingroup IPCacheAPI
224 ipcache_purgelru(void *voidnotused
)
227 dlink_node
*prev
= NULL
;
230 eventAdd("ipcache_purgelru", ipcache_purgelru
, NULL
, 10.0, 1);
232 for (m
= lru_list
.tail
; m
; m
= prev
) {
233 if (memInUse(MEM_IPCACHE_ENTRY
) < ipcache_low
)
238 i
= (ipcache_entry
*)m
->data
;
248 debugs(14, 9, "ipcache_purgelru: removed " << removed
<< " entries");
252 \ingroup IPCacheInternal
254 * purges entries added from /etc/hosts (or whatever).
257 purge_entries_fromhosts(void)
259 dlink_node
*m
= lru_list
.head
;
260 ipcache_entry
*i
= NULL
, *t
;
263 if (i
!= NULL
) { /* need to delay deletion */
264 ipcacheRelease(i
); /* we just override locks */
268 t
= (ipcache_entry
*)m
->data
;
270 if (t
->flags
.fromhosts
)
281 \ingroup IPCacheInternal
283 * create blank ipcache_entry
285 static ipcache_entry
*
286 ipcacheCreateEntry(const char *name
)
288 static ipcache_entry
*i
;
289 i
= (ipcache_entry
*)memAllocate(MEM_IPCACHE_ENTRY
);
290 i
->hash
.key
= xstrdup(name
);
291 i
->expires
= squid_curtime
+ Config
.negativeDnsTtl
;
295 /// \ingroup IPCacheInternal
297 ipcacheAddEntry(ipcache_entry
* i
)
299 hash_link
*e
= (hash_link
*)hash_lookup(ip_table
, i
->hash
.key
);
302 /* INET6 : should NOT be adding this entry until all CNAME have been received. */
303 assert(i
->cname_wait
== 0);
307 /* avoid colission */
308 ipcache_entry
*q
= (ipcache_entry
*) e
;
311 /* can occur with Multiple-depth CNAME Recursion if parent returned early with additional */
312 /* just need to drop from the hash without releasing actual memory */
313 ipcacheRelease(q
, false);
319 hash_join(ip_table
, &i
->hash
);
320 dlinkAdd(i
, &i
->lru
, &lru_list
);
321 i
->lastref
= squid_curtime
;
325 \ingroup IPCacheInternal
327 * walks down the pending list, calling handlers
330 ipcacheCallback(ipcache_entry
* i
)
332 IPH
*callback
= i
->handler
;
334 i
->lastref
= squid_curtime
;
341 callback
= i
->handler
;
345 if (cbdataReferenceValidDone(i
->handlerData
, &cbdata
)) {
346 dns_error_message
= i
->error_message
;
347 callback(i
->addrs
.count
? &i
->addrs
: NULL
, cbdata
);
350 ipcacheUnlockEntry(i
);
353 /// \ingroup IPCacheAPI
356 ipcacheParse(ipcache_entry
*i
, const char *inbuf
)
358 LOCAL_ARRAY(char, buf
, DNS_INBUF_SZ
);
363 const char *name
= (const char *)i
->hash
.key
;
364 i
->expires
= squid_curtime
+ Config
.negativeDnsTtl
;
365 i
->flags
.negcached
= 1;
366 safe_free(i
->addrs
.in_addrs
);
367 safe_free(i
->addrs
.bad_mask
);
368 safe_free(i
->error_message
);
372 debugs(14, 1, "ipcacheParse: Got <NULL> reply");
373 i
->error_message
= xstrdup("Internal Error");
377 xstrncpy(buf
, inbuf
, DNS_INBUF_SZ
);
378 debugs(14, 5, "ipcacheParse: parsing: {" << buf
<< "}");
379 token
= strtok(buf
, w_space
);
382 debugs(14, 1, "ipcacheParse: expecting result, got '" << inbuf
<< "'");
384 i
->error_message
= xstrdup("Internal Error");
388 if (0 == strcmp(token
, "$fail")) {
389 token
= strtok(NULL
, "\n");
390 assert(NULL
!= token
);
391 i
->error_message
= xstrdup(token
);
395 if (0 != strcmp(token
, "$addr")) {
396 debugs(14, 1, "ipcacheParse: expecting '$addr', got '" << inbuf
<< "' in response to '" << name
<< "'");
398 i
->error_message
= xstrdup("Internal Error");
402 token
= strtok(NULL
, w_space
);
405 debugs(14, 1, "ipcacheParse: expecting TTL, got '" << inbuf
<< "' in response to '" << name
<< "'");
407 i
->error_message
= xstrdup("Internal Error");
413 while (NULL
!= (token
= strtok(NULL
, w_space
))) {
423 i
->addrs
.in_addrs
= (IpAddress
*)xcalloc(ipcount
, sizeof(IPAddress
));
424 for (int l
= 0; l
< ipcount
; l
++)
425 i
->addrs
.in_addrs
[l
].SetEmpty(); // perform same init actions as constructor would.
426 i
->addrs
.bad_mask
= (unsigned char *)xcalloc(ipcount
, sizeof(unsigned char));
427 memset(i
->addrs
.bad_mask
, 0, sizeof(unsigned char) * ipcount
);
429 for (j
= 0, k
= 0; k
< ipcount
; k
++) {
430 if ( i
->addrs
.in_addrs
[j
] = A
[k
] )
433 debugs(14, 1, "ipcacheParse: Invalid IP address '" << A
[k
] << "' in response to '" << name
<< "'");
436 i
->addrs
.count
= (unsigned char) j
;
439 if (i
->addrs
.count
<= 0) {
440 debugs(14, 1, "ipcacheParse: No addresses in response to '" << name
<< "'");
444 if (ttl
== 0 || ttl
> Config
.positiveDnsTtl
)
445 ttl
= Config
.positiveDnsTtl
;
447 if (ttl
< Config
.negativeDnsTtl
)
448 ttl
= Config
.negativeDnsTtl
;
450 i
->expires
= squid_curtime
+ ttl
;
452 i
->flags
.negcached
= 0;
454 return i
->addrs
.count
;
459 ipcacheParse(ipcache_entry
*i
, rfc1035_rr
* answers
, int nr
, const char *error_message
)
465 const char *name
= (const char *)i
->hash
.key
;
468 i
->expires
= squid_curtime
+ Config
.negativeDnsTtl
;
469 i
->flags
.negcached
= 1;
470 safe_free(i
->addrs
.in_addrs
);
471 assert(i
->addrs
.in_addrs
== NULL
);
472 safe_free(i
->addrs
.bad_mask
);
473 assert(i
->addrs
.bad_mask
== NULL
);
474 safe_free(i
->error_message
);
475 assert(i
->error_message
== NULL
);
479 debugs(14, 3, "ipcacheParse: Lookup failed '" << error_message
<< "' for '" << (const char *)i
->hash
.key
<< "'");
480 i
->error_message
= xstrdup(error_message
);
485 debugs(14, 3, "ipcacheParse: No DNS records in response to '" << name
<< "'");
486 i
->error_message
= xstrdup("No DNS records");
492 for (k
= 0; k
< nr
; k
++) {
495 if (answers
[k
].type
== RFC1035_TYPE_AAAA
) {
496 if (answers
[k
].rdlength
!= sizeof(struct in6_addr
)) {
497 debugs(14, 1, "ipcacheParse: Invalid IPv6 address in response to '" << name
<< "'");
501 IpcacheStats
.rr_aaaa
++;
506 if (answers
[k
].type
== RFC1035_TYPE_A
) {
507 if (answers
[k
].rdlength
!= sizeof(struct in_addr
)) {
508 debugs(14, 1, "ipcacheParse: Invalid IPv4 address in response to '" << name
<< "'");
516 /* With A and AAAA, the CNAME does not necessarily come with additional records to use. */
517 if (answers
[k
].type
== RFC1035_TYPE_CNAME
) {
519 IpcacheStats
.rr_cname
++;
522 debugs(14, 5, "ipcacheParse: " << name
<< " CNAME " << answers
[k
].rdata
<< " (checking destination: " << i
<< ").");
523 const ipcache_addrs
*res
= ipcache_gethostbyname(answers
[k
].rdata
, 0);
526 debugs(14, 5, "ipcacheParse: CNAME " << answers
[k
].rdata
<< " already has " << res
->count
<< " IPs cached.");
528 /* keep going on this, but flag the fact that we need to wait for a CNAME lookup to finish */
529 debugs(14, 5, "ipcacheParse: CNAME " << answers
[k
].rdata
<< " has no IPs! Recursing.");
530 ipcache_nbgethostbyname(answers
[k
].rdata
, ipcacheHandleCnameRecurse
, new generic_cbdata(i
) );
533 #endif /* DNS_CNAME */
538 // otherwise its an unknown RR. debug at level 9 since we usually want to ignore these and they are common.
539 debugs(14, 9, HERE
<< "Unknown RR type received: type=" << answers
[k
].type
<< " starting at " << &(answers
[k
]) );
543 if (na
== 0 && i
->cname_wait
>0 ) {
544 /* don't set any error message (yet). Allow recursion to do its work first. */
545 IpcacheStats
.cname_only
++;
548 #endif /* DNS_CNAME */
551 debugs(14, 1, "ipcacheParse: No Address records in response to '" << name
<< "'");
552 i
->error_message
= xstrdup("No Address records");
554 IpcacheStats
.cname_only
++;
558 i
->addrs
.in_addrs
= (IpAddress
*)xcalloc(na
, sizeof(IPAddress
));
559 for (int l
= 0; l
< na
; l
++)
560 i
->addrs
.in_addrs
[l
].SetEmpty(); // perform same init actions as constructor would.
561 i
->addrs
.bad_mask
= (unsigned char *)xcalloc(na
, sizeof(unsigned char));
563 for (j
= 0, k
= 0; k
< nr
; k
++) {
565 if (answers
[k
].type
== RFC1035_TYPE_A
) {
566 if (answers
[k
].rdlength
!= sizeof(struct in_addr
))
570 xmemcpy(&temp
, answers
[k
].rdata
, sizeof(struct in_addr
));
571 i
->addrs
.in_addrs
[j
] = temp
;
573 debugs(14, 3, "ipcacheParse: " << name
<< " #" << j
<< " " << i
->addrs
.in_addrs
[j
]);
577 } else if (answers
[k
].type
== RFC1035_TYPE_AAAA
) {
578 if (answers
[k
].rdlength
!= sizeof(struct in6_addr
))
581 struct in6_addr temp
;
582 xmemcpy(&temp
, answers
[k
].rdata
, sizeof(struct in6_addr
));
583 i
->addrs
.in_addrs
[j
] = temp
;
585 debugs(14, 3, "ipcacheParse: " << name
<< " #" << j
<< " " << i
->addrs
.in_addrs
[j
] );
590 else if (answers
[k
].type
== RFC1035_TYPE_CNAME
) {
591 debugs(14, 3, "ipcacheParse: " << name
<< " #x CNAME " << answers
[k
].rdata
);
592 const ipcache_addrs
*res
= ipcache_gethostbyname(answers
[k
].rdata
, 0);
594 /* NP: the results of *that* query need to be integrated in place of the CNAME */
595 /* Ideally we should also integrate the min TTL of the above IPA's into ttl. */
596 for (int l
= 0; l
< res
->count
; l
++, j
++) {
597 i
->addrs
.in_addrs
[j
] = res
->in_addrs
[l
];
598 debugs(14, 3, "ipcacheParse: " << name
<< " #" << j
<< " " << i
->addrs
.in_addrs
[j
] );
601 debugs(14, 9, "ipcacheParse: " << answers
[k
].rdata
<< " (CNAME) waiting on A/AAAA records.");
604 #endif /* DNS_CNAME */
606 if (ttl
== 0 || (int) answers
[k
].ttl
< ttl
)
607 ttl
= answers
[k
].ttl
;
613 i
->addrs
.count
= (unsigned char) na
;
615 i
->addrs
.count
= 255;
617 if (ttl
> Config
.positiveDnsTtl
)
618 ttl
= Config
.positiveDnsTtl
;
620 if (ttl
< Config
.negativeDnsTtl
)
621 ttl
= Config
.negativeDnsTtl
;
623 i
->expires
= squid_curtime
+ ttl
;
625 i
->flags
.negcached
= 0;
628 /* SPECIAL CASE: may get here IFF CNAME received with Additional records */
629 /* reurn 0/'wait for further details' value. */
630 /* NP: 'No DNS Results' is a return -1 +msg */
634 #endif /* DNS_CNAME */
635 return i
->addrs
.count
;
640 /// \ingroup IPCacheInternal
643 ipcacheHandleReply(void *data
, char *reply
)
645 ipcacheHandleReply(void *data
, rfc1035_rr
* answers
, int na
, const char *error_message
)
650 static_cast<generic_cbdata
*>(data
)->unwrap(&i
);
651 IpcacheStats
.replies
++;
652 statHistCount(&statCounter
.dns
.svc_time
,
653 tvSubMsec(i
->request_time
, current_time
));
656 done
= ipcacheParse(i
, reply
);
659 done
= ipcacheParse(i
, answers
, na
, error_message
);
661 /* If we have not produced either IPs or Error immediately, wait for recursion to finish. */
662 if (done
!= 0 || error_message
!= NULL
)
674 \param name Host to resolve.
675 \param handler Pointer to the function to be called when the reply
676 * from the IP cache (or the DNS if the IP cache misses)
677 \param handlerData Information that is passed to the handler and does not affect the IP cache.
679 * XXX: on hits and some errors, the handler is called immediately instead
680 * of scheduling an async call. This reentrant behavior means that the
681 * user job must be extra careful after calling ipcache_nbgethostbyname,
682 * especially if the handler destroys the job. Moreover, the job has
683 * no way of knowing whether the reentrant call happened. commConnectStart
684 * protects the job by scheduling an async call, but some user code calls
685 * ipcache_nbgethostbyname directly.
688 ipcache_nbgethostbyname(const char *name
, IPH
* handler
, void *handlerData
)
690 ipcache_entry
*i
= NULL
;
691 const ipcache_addrs
*addrs
= NULL
;
693 assert(handler
!= NULL
);
694 debugs(14, 4, "ipcache_nbgethostbyname: Name '" << name
<< "'.");
695 IpcacheStats
.requests
++;
697 if (name
== NULL
|| name
[0] == '\0') {
698 debugs(14, 4, "ipcache_nbgethostbyname: Invalid name!");
699 IpcacheStats
.invalid
++;
700 dns_error_message
= "Invalid hostname";
701 handler(NULL
, handlerData
);
705 if ((addrs
= ipcacheCheckNumeric(name
))) {
706 debugs(14, 4, "ipcache_nbgethostbyname: BYPASS for '" << name
<< "' (already numeric)");
707 dns_error_message
= NULL
;
708 IpcacheStats
.numeric_hits
++;
709 handler(addrs
, handlerData
);
713 i
= ipcache_get(name
);
718 } else if (ipcacheExpiredEntry(i
)) {
719 /* hit, but expired -- bummer */
724 debugs(14, 4, "ipcache_nbgethostbyname: HIT for '" << name
<< "'");
726 if (i
->flags
.negcached
)
727 IpcacheStats
.negative_hits
++;
731 i
->handler
= handler
;
733 i
->handlerData
= cbdataReference(handlerData
);
740 debugs(14, 5, "ipcache_nbgethostbyname: MISS for '" << name
<< "'");
741 IpcacheStats
.misses
++;
742 i
= ipcacheCreateEntry(name
);
743 i
->handler
= handler
;
744 i
->handlerData
= cbdataReference(handlerData
);
745 i
->request_time
= current_time
;
746 c
= new generic_cbdata(i
);
749 dnsSubmit(hashKeyStr(&i
->hash
), ipcacheHandleReply
, c
);
752 idnsALookup(hashKeyStr(&i
->hash
), ipcacheHandleReply
, c
);
756 /// \ingroup IPCacheInternal
758 ipcacheRegisterWithCacheManager(void)
760 CacheManager::GetInstance()->
761 registerAction("ipcache",
762 "IP Cache Stats and Contents",
763 stat_ipcache_get
, 0, 1);
770 * Initialize the ipcache.
771 * Is called from mainInitialize() after disk initialization
772 * and prior to the reverse FQDNCache initialization
778 debugs(14, DBG_IMPORTANT
, "Initializing IP Cache...");
779 memset(&IpcacheStats
, '\0', sizeof(IpcacheStats
));
780 memset(&lru_list
, '\0', sizeof(lru_list
));
781 memset(&static_addrs
, '\0', sizeof(ipcache_addrs
));
783 static_addrs
.in_addrs
= (IpAddress
*)xcalloc(1, sizeof(IPAddress
));
784 static_addrs
.in_addrs
->SetEmpty(); // properly setup the IpAddress!
785 static_addrs
.bad_mask
= (unsigned char *)xcalloc(1, sizeof(unsigned char));
786 ipcache_high
= (long) (((float) Config
.ipcache
.size
*
787 (float) Config
.ipcache
.high
) / (float) 100);
788 ipcache_low
= (long) (((float) Config
.ipcache
.size
*
789 (float) Config
.ipcache
.low
) / (float) 100);
790 n
= hashPrime(ipcache_high
/ 4);
791 ip_table
= hash_create((HASHCMP
*) strcmp
, n
, hash4
);
792 memDataInit(MEM_IPCACHE_ENTRY
, "ipcache_entry", sizeof(ipcache_entry
), 0);
794 ipcacheRegisterWithCacheManager();
800 * Is different from ipcache_nbgethostbyname in that it only checks
801 * if an entry exists in the cache and does not by default contact the DNS,
802 * unless this is requested, by setting the flags.
804 \param name Host name to resolve.
805 \param flags Default is NULL, set to IP_LOOKUP_IF_MISS
806 * to explicitly perform DNS lookups.
808 \retval NULL An error occured during lookup
809 \retval NULL No results available in cache and no lookup specified
810 \retval * Pointer to the ipcahce_addrs structure containing the lookup results
812 const ipcache_addrs
*
813 ipcache_gethostbyname(const char *name
, int flags
)
815 ipcache_entry
*i
= NULL
;
816 ipcache_addrs
*addrs
;
818 debugs(14, 3, "ipcache_gethostbyname: '" << name
<< "', flags=" << std::hex
<< flags
);
819 IpcacheStats
.requests
++;
820 i
= ipcache_get(name
);
824 } else if (ipcacheExpiredEntry(i
)) {
827 } else if (i
->flags
.negcached
) {
828 IpcacheStats
.negative_hits
++;
829 dns_error_message
= i
->error_message
;
833 i
->lastref
= squid_curtime
;
834 dns_error_message
= i
->error_message
;
838 dns_error_message
= NULL
;
840 if ((addrs
= ipcacheCheckNumeric(name
))) {
841 IpcacheStats
.numeric_hits
++;
845 IpcacheStats
.misses
++;
847 if (flags
& IP_LOOKUP_IF_MISS
)
848 ipcache_nbgethostbyname(name
, ipcacheHandleCnameRecurse
, NULL
);
853 /// \ingroup IPCacheInternal
855 ipcacheStatPrint(ipcache_entry
* i
, StoreEntry
* sentry
)
858 int count
= i
->addrs
.count
;
859 char buf
[MAX_IPSTRLEN
];
862 debugs(14, 0, HERE
<< "CRITICAL: sentry is NULL!");
866 debugs(14, 0, HERE
<< "CRITICAL: ipcache_entry is NULL!");
867 storeAppendPrintf(sentry
, "CRITICAL ERROR\n");
871 storeAppendPrintf(sentry
, " %-32.32s %c%c %6d %6d %2d(%2d)",
872 hashKeyStr(&i
->hash
),
873 i
->flags
.fromhosts
? 'H' : ' ',
874 i
->flags
.negcached
? 'N' : ' ',
875 (int) (squid_curtime
- i
->lastref
),
876 (int) ((i
->flags
.fromhosts
? -1 : i
->expires
- squid_curtime
)),
877 (int) i
->addrs
.count
,
878 (int) i
->addrs
.badcount
);
881 * Negative-cached entries have no IPs listed. */
882 if (i
->flags
.negcached
) {
883 storeAppendPrintf(sentry
, "\n");
888 * Cached entries have IPs listed with a BNF of: <IP> '-' ('OK'|'BAD') */
889 for (k
= 0; k
< count
; k
++) {
890 /* Display tidy-up: IPv6 are so big make the list vertical */
892 storeAppendPrintf(sentry
, " %45.45s-%3s\n",
893 i
->addrs
.in_addrs
[k
].NtoA(buf
,MAX_IPSTRLEN
),
894 i
->addrs
.bad_mask
[k
] ? "BAD" : "OK ");
896 storeAppendPrintf(sentry
, "%s %45.45s-%3s\n",
897 " ", /* blank-space indenting IP list */
898 i
->addrs
.in_addrs
[k
].NtoA(buf
,MAX_IPSTRLEN
),
899 i
->addrs
.bad_mask
[k
] ? "BAD" : "OK ");
904 \ingroup IPCacheInternal
906 * process objects list
909 stat_ipcache_get(StoreEntry
* sentry
)
912 assert(ip_table
!= NULL
);
913 storeAppendPrintf(sentry
, "IP Cache Statistics:\n");
914 storeAppendPrintf(sentry
, "IPcache Entries: %d\n",
915 memInUse(MEM_IPCACHE_ENTRY
));
916 storeAppendPrintf(sentry
, "IPcache Requests: %d\n",
917 IpcacheStats
.requests
);
918 storeAppendPrintf(sentry
, "IPcache Hits: %d\n",
920 storeAppendPrintf(sentry
, "IPcache Negative Hits: %d\n",
921 IpcacheStats
.negative_hits
);
922 storeAppendPrintf(sentry
, "IPcache Numeric Hits: %d\n",
923 IpcacheStats
.numeric_hits
);
924 storeAppendPrintf(sentry
, "IPcache Misses: %d\n",
925 IpcacheStats
.misses
);
926 storeAppendPrintf(sentry
, "IPcache Retrieved A: %d\n",
928 storeAppendPrintf(sentry
, "IPcache Retrieved AAAA: %d\n",
929 IpcacheStats
.rr_aaaa
);
930 storeAppendPrintf(sentry
, "IPcache Retrieved CNAME: %d\n",
931 IpcacheStats
.rr_cname
);
932 storeAppendPrintf(sentry
, "IPcache CNAME-Only Response: %d\n",
933 IpcacheStats
.cname_only
);
934 storeAppendPrintf(sentry
, "IPcache Invalid Request: %d\n",
935 IpcacheStats
.invalid
);
936 storeAppendPrintf(sentry
, "\n\n");
937 storeAppendPrintf(sentry
, "IP Cache Contents:\n\n");
938 storeAppendPrintf(sentry
, " %-31.31s %3s %6s %6s %4s\n",
945 for (m
= lru_list
.head
; m
; m
= m
->next
) {
946 assert( m
->next
!= m
);
947 ipcacheStatPrint((ipcache_entry
*)m
->data
, sentry
);
953 * Takes two IpAddress arrays and merges them into a single array
954 * which is allocated dynamically to fit the number of unique addresses
956 \param aaddrs One list to merge
957 \param alen Size of list aaddrs
958 \param baddrs Other list to merge
959 \param alen Size of list baddrs
960 \param out Combined list of unique addresses (sorted with IPv6 first in IPv6-mode)
961 \param outlen Size of list out
964 ipcacheMergeIPLists(const IpAddress
*aaddrs
, const int alen
,
965 const IpAddress
*baddrs
, const int blen
,
966 IpAddress
**out
, int &outlen
)
970 IpAddress
const *ip4ptrs
[255];
972 IpAddress
const *ip6ptrs
[255];
977 memset(ip4ptrs
, 0, sizeof(IpAddress
*)*255);
979 memset(ip6ptrs
, 0, sizeof(IpAddress
*)*255);
982 // for each unique address in list A - grab ptr
983 for (t
= 0; t
< alen
; t
++) {
984 if (aaddrs
[t
].IsIPv4()) {
985 // check against IPv4 pruned list
986 for (c
= 0; c
<= num_ip4
; c
++) {
987 if (ip4ptrs
[c
] && aaddrs
[t
] == *(ip4ptrs
[c
]) ) break; // duplicate.
990 ip4ptrs
[num_ip4
] = &aaddrs
[t
];
995 else if (aaddrs
[t
].IsIPv6()) {
996 debugs(14,8, HERE
<< "A[" << t
<< "]=IPv6 " << aaddrs
[t
]);
997 // check against IPv6 pruned list
998 for (c
= 0; c
<= num_ip6
; c
++) {
999 if (ip6ptrs
[c
] && aaddrs
[t
] == *ip6ptrs
[c
]) break; // duplicate.
1002 ip6ptrs
[num_ip6
] = &aaddrs
[t
];
1009 // for each unique address in list B - grab ptr
1010 for (t
= 0; t
< blen
; t
++) {
1011 if (baddrs
[t
].IsIPv4()) {
1012 // check against IPv4 pruned list
1013 for (c
= 0; c
<= num_ip4
; c
++) {
1014 if (ip4ptrs
[c
] && baddrs
[t
] == *ip4ptrs
[c
]) break; // duplicate.
1017 ip4ptrs
[num_ip4
] = &baddrs
[t
];
1022 else if (baddrs
[t
].IsIPv6()) {
1023 // check against IPv6 pruned list
1024 for (c
= 0; c
<= num_ip6
; c
++) {
1025 if (ip6ptrs
[c
] && baddrs
[t
] == *ip6ptrs
[c
]) break; // duplicate.
1028 ip6ptrs
[num_ip6
] = &baddrs
[t
];
1035 fc
= num_ip6
+ num_ip4
;
1039 debugs(14, 5, "ipcacheMergeIPLists: Merge " << alen
<< "+" << blen
<< " into " << fc
<< " unique IPs.");
1041 // copy the old IPs into the new list buffer.
1042 (*out
) = (IpAddress
*)xcalloc(fc
, sizeof(IPAddress
));
1045 assert(out
!= NULL
);
1048 /* IPv6 are preferred (tried first) over IPv4 */
1050 for (int l
= 0; outlen
< num_ip6
; l
++, outlen
++) {
1051 (*out
)[outlen
] = *ip6ptrs
[l
];
1052 debugs(14, 5, "ipcacheMergeIPLists: #" << outlen
<< " " << (*out
)[outlen
] );
1054 #endif /* USE_IPV6 */
1056 for (int l
= 0; outlen
< num_ip4
; l
++, outlen
++) {
1057 (*out
)[outlen
] = *ip4ptrs
[l
];
1058 debugs(14, 5, "ipcacheMergeIPLists: #" << outlen
<< " " << (*out
)[outlen
] );
1061 assert(outlen
== fc
); // otherwise something broke badly!
1063 #endif /* DNS_CNAME */
1065 /// \ingroup IPCacheInternal
1068 ipcacheHandleCnameRecurse(const ipcache_addrs
*addrs
, void *cbdata
)
1071 ipcache_entry
*i
= NULL
;
1073 IpAddress
*tmpbuf
= NULL
;
1076 generic_cbdata
* gcb
= (generic_cbdata
*)cbdata
;
1077 // count of addrs at parent and child (REQ as .count is a char type!)
1078 int ccount
= 0, pcount
= 0;
1080 debugs(14, 5, "ipcacheHandleCnameRecurse: Handling basic A/AAAA response.");
1082 /* IFF no CNAME recursion being processed. do nothing. */
1089 // make sure we are actualy waiting for a CNAME callback to be run.
1090 assert(i
->cname_wait
> 0);
1091 // count this event. its being handled.
1094 pname
= (char*)i
->hash
.key
;
1095 assert(pname
!= NULL
);
1097 debugs(14, 5, "ipcacheHandleCnameRecurse: Handling CNAME recursion. CBDATA('" << gcb
->data
<< "')='" << pname
<< "' -> " << std::hex
<< i
);
1100 return; // Parent has expired. Don't merge, just leave for future Ref:
1103 /* IFF addrs is NULL (Usually an Error or Timeout occured on lookup.) */
1104 /* Ignore it and HOPE that we got some Additional records to use. */
1108 ccount
= (0+ addrs
->count
);
1109 pcount
= (0+ i
->addrs
.count
);
1112 /* IFF no CNAME results. do none of the processing BUT finish anyway. */
1115 debugs(14, 5, "ipcacheHandleCnameRecurse: Merge IP Lists for " << pname
<< " (" << pcount
<< "+" << ccount
<< ")");
1117 /* add new IP records to entry */
1118 tmpbuf
= i
->addrs
.in_addrs
;
1119 i
->addrs
.in_addrs
= NULL
;
1120 ipcacheMergeIPLists(tmpbuf
, pcount
, addrs
->in_addrs
, ccount
, &(i
->addrs
.in_addrs
), fc
);
1121 debugs(14,8, HERE
<< "in=" << tmpbuf
<< ", out=" << i
->addrs
.in_addrs
);
1122 assert( (pcount
>0 ? tmpbuf
!=NULL
: tmpbuf
==NULL
) );
1126 /* IFF the parent initial lookup was given Additional records with A */
1127 // clear the 'bad IP mask'
1128 safe_free(i
->addrs
.bad_mask
);
1130 // create a new bad IP mask to fit the new size needed.
1132 i
->addrs
.bad_mask
= (unsigned char*)xcalloc(fc
, sizeof(unsigned char));
1133 memset(i
->addrs
.bad_mask
, 0, sizeof(unsigned char)*fc
);
1137 i
->addrs
.count
= (unsigned char) fc
;
1139 i
->addrs
.count
= 255;
1141 if (ttl
== 0 || ttl
> Config
.positiveDnsTtl
)
1142 ttl
= Config
.positiveDnsTtl
;
1144 if (ttl
< Config
.negativeDnsTtl
)
1145 ttl
= Config
.negativeDnsTtl
;
1147 i
->expires
= squid_curtime
+ ttl
;
1149 i
->flags
.negcached
= 0;
1153 i
->addrs
.badcount
= 0;
1157 i
->error_message
= xstrdup("No DNS Records");
1160 /* finish the lookup we were doing on parent when we got side-tracked for CNAME loop */
1161 if (i
->cname_wait
== 0) {
1165 // else still more CNAME to be found.
1166 #endif /* DNS_CNAME */
1169 /// \ingroup IPCacheAPI
1171 ipcacheInvalidate(const char *name
)
1175 if ((i
= ipcache_get(name
)) == NULL
)
1178 i
->expires
= squid_curtime
;
1181 * NOTE, don't call ipcacheRelease here because we might be here due
1182 * to a thread started from a callback.
1186 /// \ingroup IPCacheAPI
1188 ipcacheInvalidateNegative(const char *name
)
1192 if ((i
= ipcache_get(name
)) == NULL
)
1195 if (i
->flags
.negcached
)
1196 i
->expires
= squid_curtime
;
1199 * NOTE, don't call ipcacheRelease here because we might be here due
1200 * to a thread started from a callback.
1204 /// \ingroup IPCacheAPI
1206 ipcacheCheckNumeric(const char *name
)
1210 /* check if it's already a IP address in text form. */
1212 /* it may be IPv6-wrapped */
1213 if (name
[0] == '[') {
1214 char *tmp
= xstrdup(&name
[1]);
1215 tmp
[strlen(tmp
)-1] = '\0';
1221 } else if (!(ip
= name
))
1224 debugs(14, 4, "ipcacheCheckNumeric: HIT_BYPASS for '" << name
<< "' == " << ip
);
1226 static_addrs
.count
= 1;
1228 static_addrs
.cur
= 0;
1230 static_addrs
.in_addrs
[0] = ip
;
1232 static_addrs
.bad_mask
[0] = FALSE
;
1234 static_addrs
.badcount
= 0;
1236 return &static_addrs
;
1239 /// \ingroup IPCacheInternal
1241 ipcacheLockEntry(ipcache_entry
* i
)
1243 if (i
->locks
++ == 0) {
1244 dlinkDelete(&i
->lru
, &lru_list
);
1245 dlinkAdd(i
, &i
->lru
, &lru_list
);
1249 /// \ingroup IPCacheInternal
1251 ipcacheUnlockEntry(ipcache_entry
* i
)
1254 debugs(14, 1, "WARNING: ipcacheEntry unlocked with no lock! locks=" << i
->locks
);
1260 if (ipcacheExpiredEntry(i
))
1264 /// \ingroup IPCacheAPI
1266 ipcacheCycleAddr(const char *name
, ipcache_addrs
* ia
)
1273 if ((i
= ipcache_get(name
)) == NULL
)
1276 if (i
->flags
.negcached
)
1282 for (k
= 0; k
< ia
->count
; k
++) {
1283 if (++ia
->cur
== ia
->count
)
1286 if (!ia
->bad_mask
[ia
->cur
])
1290 if (k
== ia
->count
) {
1291 /* All bad, reset to All good */
1292 debugs(14, 3, "ipcacheCycleAddr: Changing ALL " << name
<< " addrs from BAD to OK");
1294 for (k
= 0; k
< ia
->count
; k
++)
1295 ia
->bad_mask
[k
] = 0;
1302 debugs(14, 3, "ipcacheCycleAddr: " << name
<< " now at " << ia
->in_addrs
[ia
->cur
] << " (" << ia
->cur
<< " of " << ia
->count
<< ")");
1308 \param name domain name to have an IP marked bad
1309 \param addr specific addres to be marked bad
1312 ipcacheMarkBadAddr(const char *name
, IpAddress
&addr
)
1318 /** Does nothing if the domain name does not exist. */
1319 if ((i
= ipcache_get(name
)) == NULL
)
1324 for (k
= 0; k
< (int) ia
->count
; k
++) {
1325 if (addr
== ia
->in_addrs
[k
] )
1329 /** Does nothing if the IP does not exist for the doamin. */
1330 if (k
== (int) ia
->count
)
1333 /** Marks the given address as BAD */
1334 if (!ia
->bad_mask
[k
]) {
1335 ia
->bad_mask
[k
] = TRUE
;
1337 i
->expires
= XMIN(squid_curtime
+ XMAX((time_t)60, Config
.negativeDnsTtl
), i
->expires
);
1338 debugs(14, 2, "ipcacheMarkBadAddr: " << name
<< " " << addr
);
1341 /** then calls ipcacheCycleAddr() to advance the current pointer to the next OK address. */
1342 ipcacheCycleAddr(name
, ia
);
1345 /// \ingroup IPCacheAPI
1347 ipcacheMarkGoodAddr(const char *name
, IpAddress
&addr
)
1353 if ((i
= ipcache_get(name
)) == NULL
)
1358 for (k
= 0; k
< (int) ia
->count
; k
++) {
1359 if (addr
== ia
->in_addrs
[k
])
1363 if (k
== (int) ia
->count
) /* not found */
1366 if (!ia
->bad_mask
[k
]) /* already OK */
1369 ia
->bad_mask
[k
] = FALSE
;
1373 debugs(14, 2, "ipcacheMarkGoodAddr: " << name
<< " " << addr
);
1376 /// \ingroup IPCacheInternal
1378 ipcacheFreeEntry(void *data
)
1380 ipcache_entry
*i
= (ipcache_entry
*)data
;
1381 safe_free(i
->addrs
.in_addrs
);
1382 safe_free(i
->addrs
.bad_mask
);
1383 safe_free(i
->hash
.key
);
1384 safe_free(i
->error_message
);
1385 memFree(i
, MEM_IPCACHE_ENTRY
);
1388 /// \ingroup IPCacheAPI
1390 ipcacheFreeMemory(void)
1392 hashFreeItems(ip_table
, ipcacheFreeEntry
);
1393 hashFreeMemory(ip_table
);
1400 * Recalculate IP cache size upon reconfigure.
1401 * Is called to clear the IPCache's data structures,
1402 * cancel all pending requests.
1405 ipcache_restart(void)
1407 ipcache_high
= (long) (((float) Config
.ipcache
.size
*
1408 (float) Config
.ipcache
.high
) / (float) 100);
1409 ipcache_low
= (long) (((float) Config
.ipcache
.size
*
1410 (float) Config
.ipcache
.low
) / (float) 100);
1411 purge_entries_fromhosts();
1417 * Adds a "static" entry from /etc/hosts
1419 \param name Hostname to be linked with IP
1420 \param ipaddr IP Address to be cached.
1423 \retval 1 IP address is invalid or other error.
1426 ipcacheAddEntryFromHosts(const char *name
, const char *ipaddr
)
1432 if (!(ip
= ipaddr
)) {
1434 if (strchr(ipaddr
, ':') && strspn(ipaddr
, "0123456789abcdefABCDEF:") == strlen(ipaddr
)) {
1435 debugs(14, 3, "ipcacheAddEntryFromHosts: Skipping IPv6 address '" << ipaddr
<< "'");
1437 debugs(14, 1, "ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr
<< "'");
1440 debugs(14, 1, "ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr
<< "'");
1446 if ((i
= ipcache_get(name
))) {
1447 if (1 == i
->flags
.fromhosts
) {
1448 ipcacheUnlockEntry(i
);
1449 } else if (i
->locks
> 0) {
1450 debugs(14, 1, "ipcacheAddEntryFromHosts: can't add static entry for locked name '" << name
<< "'");
1457 i
= ipcacheCreateEntry(name
);
1460 i
->addrs
.badcount
= 0;
1462 i
->addrs
.in_addrs
= (IpAddress
*)xcalloc(1, sizeof(IPAddress
));
1463 i
->addrs
.bad_mask
= (unsigned char *)xcalloc(1, sizeof(unsigned char));
1464 i
->addrs
.in_addrs
[0] = ip
;
1465 i
->addrs
.bad_mask
[0] = FALSE
;
1466 i
->flags
.fromhosts
= 1;
1468 ipcacheLockEntry(i
);
1476 * The function to return the ip cache statistics to via SNMP
1479 snmp_netIpFn(variable_list
* Var
, snint
* ErrP
)
1481 variable_list
*Answer
= NULL
;
1482 debugs(49, 5, "snmp_netIpFn: Processing request:");
1483 snmpDebugOid(5, Var
->name
, Var
->name_length
);
1484 *ErrP
= SNMP_ERR_NOERROR
;
1486 switch (Var
->name
[LEN_SQ_NET
+ 1]) {
1489 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1490 memInUse(MEM_IPCACHE_ENTRY
),
1495 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1496 IpcacheStats
.requests
,
1501 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1507 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1513 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1514 IpcacheStats
.negative_hits
,
1519 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1520 IpcacheStats
.misses
,
1525 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1531 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1537 *ErrP
= SNMP_ERR_NOSUCHNAME
;
1538 snmp_var_free(Answer
);
1545 #endif /*SQUID_SNMP */