2 * DEBUG: section 35 FQDN Cache
3 * AUTHOR: Harvest Derived
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
35 #include "DnsLookupDetails.h"
38 #include "HelperReply.h"
40 #include "mgr/Registration.h"
41 #include "SquidConfig.h"
43 #include "SquidTime.h"
44 #include "StatCounters.h"
49 #include "snmp_core.h"
53 \defgroup FQDNCacheAPI FQDN Cache API
55 \section Introduction Introduction
57 * The FQDN cache is a built-in component of squid providing
58 * Hostname to IP-Number translation functionality and managing
59 * the involved data-structures. Efficiency concerns require
60 * mechanisms that allow non-blocking access to these mappings.
61 * The FQDN cache usually doesn't block on a request except for
62 * special cases where this is desired (see below).
64 \todo FQDN Cache should have its own API *.h file.
68 \defgroup FQDNCacheInternal FQDN Cache Internals
71 * Internally, the execution flow is as follows:
72 * On a miss, fqdncache_nbgethostbyaddr() checks whether a request
73 * for this name is already pending, and if positive, it creates a
74 * new entry using fqdncacheAddEntry(). Then it calls
75 * fqdncacheAddPending() to add a request to the queue together
76 * with data and handler. Else, ifqdncache_dnsDispatch() is called
77 * to directly create a DNS query or to fqdncacheEnqueue() if all
78 * no DNS port is free.
81 * fqdncacheCallback() is called regularly to walk down the pending
82 * list and call handlers.
85 * LRU clean-up is performed through fqdncache_purgelru() according
86 * to the fqdncache_high threshold.
89 /// \ingroup FQDNCacheInternal
90 #define FQDN_LOW_WATER 90
92 /// \ingroup FQDNCacheInternal
93 #define FQDN_HIGH_WATER 95
97 * The data structure used for storing name-address mappings
98 * is a small hashtable (static hash_table *fqdn_table),
99 * where structures of type fqdncache_entry whose most
100 * interesting members are:
102 class fqdncache_entry
105 hash_link hash
; /* must be first */
108 unsigned char name_count
;
109 char *names
[FQDN_MAX_NAMES
+ 1];
114 struct timeval request_time
;
116 unsigned short locks
;
123 int age() const; ///< time passed since request_time or -1 if unknown
126 /// \ingroup FQDNCacheInternal
127 static struct _fqdn_cache_stats
{
135 /// \ingroup FQDNCacheInternal
136 static dlink_list lru_list
;
138 static IDNSCB fqdncacheHandleReply
;
139 static int fqdncacheParse(fqdncache_entry
*, const rfc1035_rr
*, int, const char *error_message
);
140 static void fqdncacheRelease(fqdncache_entry
*);
141 static fqdncache_entry
*fqdncacheCreateEntry(const char *name
);
142 static void fqdncacheCallback(fqdncache_entry
*, int wait
);
143 static fqdncache_entry
*fqdncache_get(const char *);
144 static int fqdncacheExpiredEntry(const fqdncache_entry
*);
145 static void fqdncacheLockEntry(fqdncache_entry
* f
);
146 static void fqdncacheUnlockEntry(fqdncache_entry
* f
);
147 static FREE fqdncacheFreeEntry
;
148 static void fqdncacheAddEntry(fqdncache_entry
* f
);
150 /// \ingroup FQDNCacheInternal
151 static hash_table
*fqdn_table
= NULL
;
153 /// \ingroup FQDNCacheInternal
154 static long fqdncache_low
= 180;
156 /// \ingroup FQDNCacheInternal
157 static long fqdncache_high
= 200;
159 /// \ingroup FQDNCacheInternal
160 inline int fqdncacheCount() { return fqdn_table
? fqdn_table
->count
: 0; }
163 fqdncache_entry::age() const
165 return request_time
.tv_sec
? tvSubMsec(request_time
, current_time
) : -1;
169 \ingroup FQDNCacheInternal
170 * Removes the given fqdncache entry
173 fqdncacheRelease(fqdncache_entry
* f
)
176 hash_remove_link(fqdn_table
, (hash_link
*) f
);
178 for (k
= 0; k
< (int) f
->name_count
; ++k
)
179 safe_free(f
->names
[k
]);
181 debugs(35, 5, "fqdncacheRelease: Released FQDN record for '" << hashKeyStr(&f
->hash
) << "'.");
183 dlinkDelete(&f
->lru
, &lru_list
);
185 safe_free(f
->hash
.key
);
187 safe_free(f
->error_message
);
189 memFree(f
, MEM_FQDNCACHE_ENTRY
);
193 \ingroup FQDNCacheInternal
194 \param name FQDN hash string.
195 \retval Match for given name
197 static fqdncache_entry
*
198 fqdncache_get(const char *name
)
201 static fqdncache_entry
*f
;
205 if ((e
= (hash_link
*)hash_lookup(fqdn_table
, name
)) != NULL
)
206 f
= (fqdncache_entry
*) e
;
212 /// \ingroup FQDNCacheInternal
214 fqdncacheExpiredEntry(const fqdncache_entry
* f
)
216 /* all static entries are locked, so this takes care of them too */
221 if (f
->expires
> squid_curtime
)
227 /// \ingroup FQDNCacheAPI
229 fqdncache_purgelru(void *notused
)
232 dlink_node
*prev
= NULL
;
235 eventAdd("fqdncache_purgelru", fqdncache_purgelru
, NULL
, 10.0, 1);
237 for (m
= lru_list
.tail
; m
; m
= prev
) {
238 if (fqdncacheCount() < fqdncache_low
)
243 f
= (fqdncache_entry
*)m
->data
;
253 debugs(35, 9, "fqdncache_purgelru: removed " << removed
<< " entries");
256 /// \ingroup FQDNCacheAPI
258 purge_entries_fromhosts(void)
260 dlink_node
*m
= lru_list
.head
;
261 fqdncache_entry
*i
= NULL
;
265 if (i
!= NULL
) { /* need to delay deletion */
266 fqdncacheRelease(i
); /* we just override locks */
270 t
= (fqdncache_entry
*)m
->data
;
272 if (t
->flags
.fromhosts
)
283 \ingroup FQDNCacheInternal
285 * Create blank fqdncache_entry
287 static fqdncache_entry
*
288 fqdncacheCreateEntry(const char *name
)
290 static fqdncache_entry
*f
;
291 f
= (fqdncache_entry
*)memAllocate(MEM_FQDNCACHE_ENTRY
);
292 f
->hash
.key
= xstrdup(name
);
293 f
->expires
= squid_curtime
+ Config
.negativeDnsTtl
;
297 /// \ingroup FQDNCacheInternal
299 fqdncacheAddEntry(fqdncache_entry
* f
)
301 hash_link
*e
= (hash_link
*)hash_lookup(fqdn_table
, f
->hash
.key
);
304 /* avoid colission */
305 fqdncache_entry
*q
= (fqdncache_entry
*) e
;
309 hash_join(fqdn_table
, &f
->hash
);
310 dlinkAdd(f
, &f
->lru
, &lru_list
);
311 f
->lastref
= squid_curtime
;
315 \ingroup FQDNCacheInternal
317 * Walks down the pending list, calling handlers
320 fqdncacheCallback(fqdncache_entry
* f
, int wait
)
324 f
->lastref
= squid_curtime
;
329 fqdncacheLockEntry(f
);
331 callback
= f
->handler
;
335 if (cbdataReferenceValidDone(f
->handlerData
, &cbdata
)) {
336 const DnsLookupDetails
details(f
->error_message
, wait
);
337 callback(f
->name_count
? f
->names
[0] : NULL
, details
, cbdata
);
340 fqdncacheUnlockEntry(f
);
343 /// \ingroup FQDNCacheInternal
345 fqdncacheParse(fqdncache_entry
*f
, const rfc1035_rr
* answers
, int nr
, const char *error_message
)
349 const char *name
= (const char *)f
->hash
.key
;
350 f
->expires
= squid_curtime
+ Config
.negativeDnsTtl
;
351 f
->flags
.negcached
= true;
354 debugs(35, 3, "fqdncacheParse: Lookup of '" << name
<< "' failed (" << error_message
<< ")");
355 f
->error_message
= xstrdup(error_message
);
360 debugs(35, 3, "fqdncacheParse: No DNS records for '" << name
<< "'");
361 f
->error_message
= xstrdup("No DNS records");
365 debugs(35, 3, "fqdncacheParse: " << nr
<< " answers for '" << name
<< "'");
368 for (k
= 0; k
< nr
; ++k
) {
369 if (answers
[k
]._class
!= RFC1035_CLASS_IN
)
372 if (answers
[k
].type
== RFC1035_TYPE_PTR
) {
373 if (!answers
[k
].rdata
[0]) {
374 debugs(35, 2, "fqdncacheParse: blank PTR record for '" << name
<< "'");
378 if (strchr(answers
[k
].rdata
, ' ')) {
379 debugs(35, 2, "fqdncacheParse: invalid PTR record '" << answers
[k
].rdata
<< "' for '" << name
<< "'");
383 f
->names
[f
->name_count
] = xstrdup(answers
[k
].rdata
);
385 } else if (answers
[k
].type
!= RFC1035_TYPE_CNAME
)
388 if (ttl
== 0 || (int) answers
[k
].ttl
< ttl
)
389 ttl
= answers
[k
].ttl
;
391 if (f
->name_count
>= FQDN_MAX_NAMES
)
395 if (f
->name_count
== 0) {
396 debugs(35, DBG_IMPORTANT
, "fqdncacheParse: No PTR record for '" << name
<< "'");
400 if (ttl
> Config
.positiveDnsTtl
)
401 ttl
= Config
.positiveDnsTtl
;
403 if (ttl
< Config
.negativeDnsTtl
)
404 ttl
= Config
.negativeDnsTtl
;
406 f
->expires
= squid_curtime
+ ttl
;
408 f
->flags
.negcached
= false;
410 return f
->name_count
;
414 \ingroup FQDNCacheAPI
416 * Callback for handling DNS results.
419 fqdncacheHandleReply(void *data
, const rfc1035_rr
* answers
, int na
, const char *error_message
)
422 static_cast<generic_cbdata
*>(data
)->unwrap(&f
);
423 ++FqdncacheStats
.replies
;
424 const int age
= f
->age();
425 statCounter
.dns
.svcTime
.count(age
);
426 fqdncacheParse(f
, answers
, na
, error_message
);
427 fqdncacheAddEntry(f
);
428 fqdncacheCallback(f
, age
);
432 \ingroup FQDNCacheAPI
434 \param addr IP address of domain to resolve.
435 \param handler A pointer to the function to be called when
436 * the reply from the FQDN cache
437 * (or the DNS if the FQDN cache misses)
438 \param handlerData Information that is passed to the handler
439 * and does not affect the FQDN cache.
442 fqdncache_nbgethostbyaddr(const Ip::Address
&addr
, FQDNH
* handler
, void *handlerData
)
444 fqdncache_entry
*f
= NULL
;
445 char name
[MAX_IPSTRLEN
];
447 addr
.toStr(name
,MAX_IPSTRLEN
);
448 debugs(35, 4, "fqdncache_nbgethostbyaddr: Name '" << name
<< "'.");
449 ++FqdncacheStats
.requests
;
451 if (name
[0] == '\0') {
452 debugs(35, 4, "fqdncache_nbgethostbyaddr: Invalid name!");
453 const DnsLookupDetails
details("Invalid hostname", -1); // error, no lookup
455 handler(NULL
, details
, handlerData
);
459 f
= fqdncache_get(name
);
464 } else if (fqdncacheExpiredEntry(f
)) {
465 /* hit, but expired -- bummer */
470 debugs(35, 4, "fqdncache_nbgethostbyaddr: HIT for '" << name
<< "'");
472 if (f
->flags
.negcached
)
473 ++ FqdncacheStats
.negative_hits
;
475 ++ FqdncacheStats
.hits
;
477 f
->handler
= handler
;
479 f
->handlerData
= cbdataReference(handlerData
);
481 fqdncacheCallback(f
, -1); // no lookup
486 debugs(35, 5, "fqdncache_nbgethostbyaddr: MISS for '" << name
<< "'");
487 ++ FqdncacheStats
.misses
;
488 f
= fqdncacheCreateEntry(name
);
489 f
->handler
= handler
;
490 f
->handlerData
= cbdataReference(handlerData
);
491 f
->request_time
= current_time
;
492 c
= new generic_cbdata(f
);
493 idnsPTRLookup(addr
, fqdncacheHandleReply
, c
);
497 \ingroup FQDNCacheAPI
499 * Is different in that it only checks if an entry exists in
500 * it's data-structures and does not by default contact the
501 * DNS, unless this is requested, by setting the flags
502 * to FQDN_LOOKUP_IF_MISS.
504 \param addr address of the FQDN being resolved
505 \param flags values are NULL or FQDN_LOOKUP_IF_MISS. default is NULL.
509 fqdncache_gethostbyaddr(const Ip::Address
&addr
, int flags
)
511 char name
[MAX_IPSTRLEN
];
512 fqdncache_entry
*f
= NULL
;
514 if (addr
.isAnyAddr() || addr
.isNoAddr()) {
518 addr
.toStr(name
,MAX_IPSTRLEN
);
519 ++ FqdncacheStats
.requests
;
520 f
= fqdncache_get(name
);
524 } else if (fqdncacheExpiredEntry(f
)) {
527 } else if (f
->flags
.negcached
) {
528 ++ FqdncacheStats
.negative_hits
;
529 // ignore f->error_message: the caller just checks FQDN cache presence
532 ++ FqdncacheStats
.hits
;
533 f
->lastref
= squid_curtime
;
534 // ignore f->error_message: the caller just checks FQDN cache presence
538 /* no entry [any more] */
540 ++ FqdncacheStats
.misses
;
542 if (flags
& FQDN_LOOKUP_IF_MISS
) {
543 fqdncache_nbgethostbyaddr(addr
, NULL
, NULL
);
550 \ingroup FQDNCacheInternal
552 * Process objects list
555 fqdnStats(StoreEntry
* sentry
)
557 fqdncache_entry
*f
= NULL
;
561 if (fqdn_table
== NULL
)
564 storeAppendPrintf(sentry
, "FQDN Cache Statistics:\n");
566 storeAppendPrintf(sentry
, "FQDNcache Entries In Use: %d\n",
567 memInUse(MEM_FQDNCACHE_ENTRY
));
569 storeAppendPrintf(sentry
, "FQDNcache Entries Cached: %d\n",
572 storeAppendPrintf(sentry
, "FQDNcache Requests: %d\n",
573 FqdncacheStats
.requests
);
575 storeAppendPrintf(sentry
, "FQDNcache Hits: %d\n",
576 FqdncacheStats
.hits
);
578 storeAppendPrintf(sentry
, "FQDNcache Negative Hits: %d\n",
579 FqdncacheStats
.negative_hits
);
581 storeAppendPrintf(sentry
, "FQDNcache Misses: %d\n",
582 FqdncacheStats
.misses
);
584 storeAppendPrintf(sentry
, "FQDN Cache Contents:\n\n");
586 storeAppendPrintf(sentry
, "%-45.45s %3s %3s %3s %s\n",
587 "Address", "Flg", "TTL", "Cnt", "Hostnames");
589 hash_first(fqdn_table
);
591 while ((f
= (fqdncache_entry
*) hash_next(fqdn_table
))) {
592 ttl
= (f
->flags
.fromhosts
? -1 : (f
->expires
- squid_curtime
));
593 storeAppendPrintf(sentry
, "%-45.45s %c%c %3.3d % 3d",
594 hashKeyStr(&f
->hash
),
595 f
->flags
.negcached
? 'N' : ' ',
596 f
->flags
.fromhosts
? 'H' : ' ',
598 (int) f
->name_count
);
600 for (k
= 0; k
< (int) f
->name_count
; ++k
)
601 storeAppendPrintf(sentry
, " %s", f
->names
[k
]);
603 storeAppendPrintf(sentry
, "\n");
607 /// \ingroup FQDNCacheInternal
609 fqdncacheLockEntry(fqdncache_entry
* f
)
611 if (f
->locks
++ == 0) {
612 dlinkDelete(&f
->lru
, &lru_list
);
613 dlinkAdd(f
, &f
->lru
, &lru_list
);
617 /// \ingroup FQDNCacheInternal
619 fqdncacheUnlockEntry(fqdncache_entry
* f
)
621 assert(f
->locks
> 0);
624 if (fqdncacheExpiredEntry(f
))
628 /// \ingroup FQDNCacheInternal
630 fqdncacheFreeEntry(void *data
)
632 fqdncache_entry
*f
= (fqdncache_entry
*)data
;
635 for (k
= 0; k
< (int) f
->name_count
; ++k
)
636 safe_free(f
->names
[k
]);
638 safe_free(f
->hash
.key
);
640 safe_free(f
->error_message
);
642 memFree(f
, MEM_FQDNCACHE_ENTRY
);
645 /// \ingroup FQDNCacheAPI
647 fqdncacheFreeMemory(void)
649 hashFreeItems(fqdn_table
, fqdncacheFreeEntry
);
650 hashFreeMemory(fqdn_table
);
655 \ingroup FQDNCacheAPI
657 * Recalculate FQDN cache size upon reconfigure.
658 * Is called to clear the FQDN cache's data structures,
659 * cancel all pending requests.
662 fqdncache_restart(void)
664 fqdncache_high
= (long) (((float) Config
.fqdncache
.size
*
665 (float) FQDN_HIGH_WATER
) / (float) 100);
666 fqdncache_low
= (long) (((float) Config
.fqdncache
.size
*
667 (float) FQDN_LOW_WATER
) / (float) 100);
668 purge_entries_fromhosts();
672 \ingroup FQDNCacheAPI
674 * Adds a "static" entry from /etc/hosts.
676 * The worldist is to be managed by the caller,
677 * including pointed-to strings
679 \param addr FQDN name to be added.
683 fqdncacheAddEntryFromHosts(char *addr
, wordlist
* hostnames
)
685 fqdncache_entry
*fce
;
688 if ((fce
= fqdncache_get(addr
))) {
689 if (1 == fce
->flags
.fromhosts
) {
690 fqdncacheUnlockEntry(fce
);
691 } else if (fce
->locks
> 0) {
692 debugs(35, DBG_IMPORTANT
, "fqdncacheAddEntryFromHosts: can't add static entry for locked address '" << addr
<< "'");
695 fqdncacheRelease(fce
);
699 fce
= fqdncacheCreateEntry(addr
);
702 fce
->names
[j
] = xstrdup(hostnames
->key
);
703 Tolower(fce
->names
[j
]);
705 hostnames
= hostnames
->next
;
707 if (j
>= FQDN_MAX_NAMES
)
712 fce
->names
[j
] = NULL
; /* it's safe */
713 fce
->flags
.fromhosts
= true;
714 fqdncacheAddEntry(fce
);
715 fqdncacheLockEntry(fce
);
718 /// \ingroup FQDNCacheInternal
720 fqdncacheRegisterWithCacheManager(void)
722 Mgr::RegisterAction("fqdncache", "FQDN Cache Stats and Contents",
728 \ingroup FQDNCacheAPI
730 * Initialize the fqdncache.
731 * Called after IP cache initialization.
738 fqdncacheRegisterWithCacheManager();
743 debugs(35, 3, "Initializing FQDN Cache...");
745 memset(&FqdncacheStats
, '\0', sizeof(FqdncacheStats
));
747 memset(&lru_list
, '\0', sizeof(lru_list
));
749 fqdncache_high
= (long) (((float) Config
.fqdncache
.size
*
750 (float) FQDN_HIGH_WATER
) / (float) 100);
752 fqdncache_low
= (long) (((float) Config
.fqdncache
.size
*
753 (float) FQDN_LOW_WATER
) / (float) 100);
755 n
= hashPrime(fqdncache_high
/ 4);
757 fqdn_table
= hash_create((HASHCMP
*) strcmp
, n
, hash4
);
759 memDataInit(MEM_FQDNCACHE_ENTRY
, "fqdncache_entry",
760 sizeof(fqdncache_entry
), 0);
765 * \ingroup FQDNCacheAPI
766 * The function to return the FQDN statistics via SNMP
769 snmp_netFqdnFn(variable_list
* Var
, snint
* ErrP
)
771 variable_list
*Answer
= NULL
;
773 debugs(49, 5, "snmp_netFqdnFn: Processing request:" << snmpDebugOid(Var
->name
, Var
->name_length
, tmp
));
774 *ErrP
= SNMP_ERR_NOERROR
;
776 switch (Var
->name
[LEN_SQ_NET
+ 1]) {
779 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
785 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
786 FqdncacheStats
.requests
,
791 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
797 /* this is now worthless */
798 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
804 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
805 FqdncacheStats
.negative_hits
,
810 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
811 FqdncacheStats
.misses
,
816 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
822 *ErrP
= SNMP_ERR_NOSUCHNAME
;
829 #endif /*SQUID_SNMP */