2 * Copyright (C) 1996-2014 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 35 FQDN Cache */
13 #include "DnsLookupDetails.h"
17 #include "mgr/Registration.h"
18 #include "SquidConfig.h"
20 #include "SquidTime.h"
21 #include "StatCounters.h"
26 #include "snmp_core.h"
30 \defgroup FQDNCacheAPI FQDN Cache API
32 \section Introduction Introduction
34 * The FQDN cache is a built-in component of squid providing
35 * Hostname to IP-Number translation functionality and managing
36 * the involved data-structures. Efficiency concerns require
37 * mechanisms that allow non-blocking access to these mappings.
38 * The FQDN cache usually doesn't block on a request except for
39 * special cases where this is desired (see below).
41 \todo FQDN Cache should have its own API *.h file.
45 \defgroup FQDNCacheInternal FQDN Cache Internals
48 * Internally, the execution flow is as follows:
49 * On a miss, fqdncache_nbgethostbyaddr() checks whether a request
50 * for this name is already pending, and if positive, it creates a
51 * new entry using fqdncacheAddEntry(). Then it calls
52 * fqdncacheAddPending() to add a request to the queue together
53 * with data and handler. Else, ifqdncache_dnsDispatch() is called
54 * to directly create a DNS query or to fqdncacheEnqueue() if all
55 * no DNS port is free.
58 * fqdncacheCallback() is called regularly to walk down the pending
59 * list and call handlers.
62 * LRU clean-up is performed through fqdncache_purgelru() according
63 * to the fqdncache_high threshold.
66 /// \ingroup FQDNCacheInternal
67 #define FQDN_LOW_WATER 90
69 /// \ingroup FQDNCacheInternal
70 #define FQDN_HIGH_WATER 95
74 * The data structure used for storing name-address mappings
75 * is a small hashtable (static hash_table *fqdn_table),
76 * where structures of type fqdncache_entry whose most
77 * interesting members are:
82 hash_link hash
; /* must be first */
85 unsigned char name_count
;
86 char *names
[FQDN_MAX_NAMES
+ 1];
91 struct timeval request_time
;
100 int age() const; ///< time passed since request_time or -1 if unknown
103 /// \ingroup FQDNCacheInternal
104 static struct _fqdn_cache_stats
{
112 /// \ingroup FQDNCacheInternal
113 static dlink_list lru_list
;
115 static IDNSCB fqdncacheHandleReply
;
116 static int fqdncacheParse(fqdncache_entry
*, const rfc1035_rr
*, int, const char *error_message
);
117 static void fqdncacheRelease(fqdncache_entry
*);
118 static fqdncache_entry
*fqdncacheCreateEntry(const char *name
);
119 static void fqdncacheCallback(fqdncache_entry
*, int wait
);
120 static fqdncache_entry
*fqdncache_get(const char *);
121 static int fqdncacheExpiredEntry(const fqdncache_entry
*);
122 static void fqdncacheLockEntry(fqdncache_entry
* f
);
123 static void fqdncacheUnlockEntry(fqdncache_entry
* f
);
124 static FREE fqdncacheFreeEntry
;
125 static void fqdncacheAddEntry(fqdncache_entry
* f
);
127 /// \ingroup FQDNCacheInternal
128 static hash_table
*fqdn_table
= NULL
;
130 /// \ingroup FQDNCacheInternal
131 static long fqdncache_low
= 180;
133 /// \ingroup FQDNCacheInternal
134 static long fqdncache_high
= 200;
136 /// \ingroup FQDNCacheInternal
137 inline int fqdncacheCount() { return fqdn_table
? fqdn_table
->count
: 0; }
140 fqdncache_entry::age() const
142 return request_time
.tv_sec
? tvSubMsec(request_time
, current_time
) : -1;
146 \ingroup FQDNCacheInternal
147 * Removes the given fqdncache entry
150 fqdncacheRelease(fqdncache_entry
* f
)
153 hash_remove_link(fqdn_table
, (hash_link
*) f
);
155 for (k
= 0; k
< (int) f
->name_count
; ++k
)
156 safe_free(f
->names
[k
]);
158 debugs(35, 5, "fqdncacheRelease: Released FQDN record for '" << hashKeyStr(&f
->hash
) << "'.");
160 dlinkDelete(&f
->lru
, &lru_list
);
162 safe_free(f
->hash
.key
);
164 safe_free(f
->error_message
);
166 memFree(f
, MEM_FQDNCACHE_ENTRY
);
170 \ingroup FQDNCacheInternal
171 \param name FQDN hash string.
172 \retval Match for given name
174 static fqdncache_entry
*
175 fqdncache_get(const char *name
)
178 static fqdncache_entry
*f
;
182 if ((e
= (hash_link
*)hash_lookup(fqdn_table
, name
)) != NULL
)
183 f
= (fqdncache_entry
*) e
;
189 /// \ingroup FQDNCacheInternal
191 fqdncacheExpiredEntry(const fqdncache_entry
* f
)
193 /* all static entries are locked, so this takes care of them too */
198 if (f
->expires
> squid_curtime
)
204 /// \ingroup FQDNCacheAPI
206 fqdncache_purgelru(void *notused
)
209 dlink_node
*prev
= NULL
;
212 eventAdd("fqdncache_purgelru", fqdncache_purgelru
, NULL
, 10.0, 1);
214 for (m
= lru_list
.tail
; m
; m
= prev
) {
215 if (fqdncacheCount() < fqdncache_low
)
220 f
= (fqdncache_entry
*)m
->data
;
230 debugs(35, 9, "fqdncache_purgelru: removed " << removed
<< " entries");
233 /// \ingroup FQDNCacheAPI
235 purge_entries_fromhosts(void)
237 dlink_node
*m
= lru_list
.head
;
238 fqdncache_entry
*i
= NULL
;
242 if (i
!= NULL
) { /* need to delay deletion */
243 fqdncacheRelease(i
); /* we just override locks */
247 t
= (fqdncache_entry
*)m
->data
;
249 if (t
->flags
.fromhosts
)
260 \ingroup FQDNCacheInternal
262 * Create blank fqdncache_entry
264 static fqdncache_entry
*
265 fqdncacheCreateEntry(const char *name
)
267 static fqdncache_entry
*f
;
268 f
= (fqdncache_entry
*)memAllocate(MEM_FQDNCACHE_ENTRY
);
269 f
->hash
.key
= xstrdup(name
);
270 f
->expires
= squid_curtime
+ Config
.negativeDnsTtl
;
274 /// \ingroup FQDNCacheInternal
276 fqdncacheAddEntry(fqdncache_entry
* f
)
278 hash_link
*e
= (hash_link
*)hash_lookup(fqdn_table
, f
->hash
.key
);
281 /* avoid colission */
282 fqdncache_entry
*q
= (fqdncache_entry
*) e
;
286 hash_join(fqdn_table
, &f
->hash
);
287 dlinkAdd(f
, &f
->lru
, &lru_list
);
288 f
->lastref
= squid_curtime
;
292 \ingroup FQDNCacheInternal
294 * Walks down the pending list, calling handlers
297 fqdncacheCallback(fqdncache_entry
* f
, int wait
)
301 f
->lastref
= squid_curtime
;
306 fqdncacheLockEntry(f
);
308 callback
= f
->handler
;
312 if (cbdataReferenceValidDone(f
->handlerData
, &cbdata
)) {
313 const DnsLookupDetails
details(f
->error_message
, wait
);
314 callback(f
->name_count
? f
->names
[0] : NULL
, details
, cbdata
);
317 fqdncacheUnlockEntry(f
);
320 /// \ingroup FQDNCacheInternal
322 fqdncacheParse(fqdncache_entry
*f
, const rfc1035_rr
* answers
, int nr
, const char *error_message
)
326 const char *name
= (const char *)f
->hash
.key
;
327 f
->expires
= squid_curtime
+ Config
.negativeDnsTtl
;
328 f
->flags
.negcached
= true;
331 debugs(35, 3, "fqdncacheParse: Lookup of '" << name
<< "' failed (" << error_message
<< ")");
332 f
->error_message
= xstrdup(error_message
);
337 debugs(35, 3, "fqdncacheParse: No DNS records for '" << name
<< "'");
338 f
->error_message
= xstrdup("No DNS records");
342 debugs(35, 3, "fqdncacheParse: " << nr
<< " answers for '" << name
<< "'");
345 for (k
= 0; k
< nr
; ++k
) {
346 if (answers
[k
]._class
!= RFC1035_CLASS_IN
)
349 if (answers
[k
].type
== RFC1035_TYPE_PTR
) {
350 if (!answers
[k
].rdata
[0]) {
351 debugs(35, 2, "fqdncacheParse: blank PTR record for '" << name
<< "'");
355 if (strchr(answers
[k
].rdata
, ' ')) {
356 debugs(35, 2, "fqdncacheParse: invalid PTR record '" << answers
[k
].rdata
<< "' for '" << name
<< "'");
360 f
->names
[f
->name_count
] = xstrdup(answers
[k
].rdata
);
362 } else if (answers
[k
].type
!= RFC1035_TYPE_CNAME
)
365 if (ttl
== 0 || (int) answers
[k
].ttl
< ttl
)
366 ttl
= answers
[k
].ttl
;
368 if (f
->name_count
>= FQDN_MAX_NAMES
)
372 if (f
->name_count
== 0) {
373 debugs(35, DBG_IMPORTANT
, "fqdncacheParse: No PTR record for '" << name
<< "'");
377 if (ttl
> Config
.positiveDnsTtl
)
378 ttl
= Config
.positiveDnsTtl
;
380 if (ttl
< Config
.negativeDnsTtl
)
381 ttl
= Config
.negativeDnsTtl
;
383 f
->expires
= squid_curtime
+ ttl
;
385 f
->flags
.negcached
= false;
387 return f
->name_count
;
391 \ingroup FQDNCacheAPI
393 * Callback for handling DNS results.
396 fqdncacheHandleReply(void *data
, const rfc1035_rr
* answers
, int na
, const char *error_message
)
399 static_cast<generic_cbdata
*>(data
)->unwrap(&f
);
400 ++FqdncacheStats
.replies
;
401 const int age
= f
->age();
402 statCounter
.dns
.svcTime
.count(age
);
403 fqdncacheParse(f
, answers
, na
, error_message
);
404 fqdncacheAddEntry(f
);
405 fqdncacheCallback(f
, age
);
409 \ingroup FQDNCacheAPI
411 \param addr IP address of domain to resolve.
412 \param handler A pointer to the function to be called when
413 * the reply from the FQDN cache
414 * (or the DNS if the FQDN cache misses)
415 \param handlerData Information that is passed to the handler
416 * and does not affect the FQDN cache.
419 fqdncache_nbgethostbyaddr(const Ip::Address
&addr
, FQDNH
* handler
, void *handlerData
)
421 fqdncache_entry
*f
= NULL
;
422 char name
[MAX_IPSTRLEN
];
424 addr
.toStr(name
,MAX_IPSTRLEN
);
425 debugs(35, 4, "fqdncache_nbgethostbyaddr: Name '" << name
<< "'.");
426 ++FqdncacheStats
.requests
;
428 if (name
[0] == '\0') {
429 debugs(35, 4, "fqdncache_nbgethostbyaddr: Invalid name!");
430 const DnsLookupDetails
details("Invalid hostname", -1); // error, no lookup
432 handler(NULL
, details
, handlerData
);
436 f
= fqdncache_get(name
);
441 } else if (fqdncacheExpiredEntry(f
)) {
442 /* hit, but expired -- bummer */
447 debugs(35, 4, "fqdncache_nbgethostbyaddr: HIT for '" << name
<< "'");
449 if (f
->flags
.negcached
)
450 ++ FqdncacheStats
.negative_hits
;
452 ++ FqdncacheStats
.hits
;
454 f
->handler
= handler
;
456 f
->handlerData
= cbdataReference(handlerData
);
458 fqdncacheCallback(f
, -1); // no lookup
463 debugs(35, 5, "fqdncache_nbgethostbyaddr: MISS for '" << name
<< "'");
464 ++ FqdncacheStats
.misses
;
465 f
= fqdncacheCreateEntry(name
);
466 f
->handler
= handler
;
467 f
->handlerData
= cbdataReference(handlerData
);
468 f
->request_time
= current_time
;
469 c
= new generic_cbdata(f
);
470 idnsPTRLookup(addr
, fqdncacheHandleReply
, c
);
474 \ingroup FQDNCacheAPI
476 * Is different in that it only checks if an entry exists in
477 * it's data-structures and does not by default contact the
478 * DNS, unless this is requested, by setting the flags
479 * to FQDN_LOOKUP_IF_MISS.
481 \param addr address of the FQDN being resolved
482 \param flags values are NULL or FQDN_LOOKUP_IF_MISS. default is NULL.
486 fqdncache_gethostbyaddr(const Ip::Address
&addr
, int flags
)
488 char name
[MAX_IPSTRLEN
];
489 fqdncache_entry
*f
= NULL
;
491 if (addr
.isAnyAddr() || addr
.isNoAddr()) {
495 addr
.toStr(name
,MAX_IPSTRLEN
);
496 ++ FqdncacheStats
.requests
;
497 f
= fqdncache_get(name
);
501 } else if (fqdncacheExpiredEntry(f
)) {
504 } else if (f
->flags
.negcached
) {
505 ++ FqdncacheStats
.negative_hits
;
506 // ignore f->error_message: the caller just checks FQDN cache presence
509 ++ FqdncacheStats
.hits
;
510 f
->lastref
= squid_curtime
;
511 // ignore f->error_message: the caller just checks FQDN cache presence
515 /* no entry [any more] */
517 ++ FqdncacheStats
.misses
;
519 if (flags
& FQDN_LOOKUP_IF_MISS
) {
520 fqdncache_nbgethostbyaddr(addr
, NULL
, NULL
);
527 \ingroup FQDNCacheInternal
529 * Process objects list
532 fqdnStats(StoreEntry
* sentry
)
534 fqdncache_entry
*f
= NULL
;
538 if (fqdn_table
== NULL
)
541 storeAppendPrintf(sentry
, "FQDN Cache Statistics:\n");
543 storeAppendPrintf(sentry
, "FQDNcache Entries In Use: %d\n",
544 memInUse(MEM_FQDNCACHE_ENTRY
));
546 storeAppendPrintf(sentry
, "FQDNcache Entries Cached: %d\n",
549 storeAppendPrintf(sentry
, "FQDNcache Requests: %d\n",
550 FqdncacheStats
.requests
);
552 storeAppendPrintf(sentry
, "FQDNcache Hits: %d\n",
553 FqdncacheStats
.hits
);
555 storeAppendPrintf(sentry
, "FQDNcache Negative Hits: %d\n",
556 FqdncacheStats
.negative_hits
);
558 storeAppendPrintf(sentry
, "FQDNcache Misses: %d\n",
559 FqdncacheStats
.misses
);
561 storeAppendPrintf(sentry
, "FQDN Cache Contents:\n\n");
563 storeAppendPrintf(sentry
, "%-45.45s %3s %3s %3s %s\n",
564 "Address", "Flg", "TTL", "Cnt", "Hostnames");
566 hash_first(fqdn_table
);
568 while ((f
= (fqdncache_entry
*) hash_next(fqdn_table
))) {
569 ttl
= (f
->flags
.fromhosts
? -1 : (f
->expires
- squid_curtime
));
570 storeAppendPrintf(sentry
, "%-45.45s %c%c %3.3d % 3d",
571 hashKeyStr(&f
->hash
),
572 f
->flags
.negcached
? 'N' : ' ',
573 f
->flags
.fromhosts
? 'H' : ' ',
575 (int) f
->name_count
);
577 for (k
= 0; k
< (int) f
->name_count
; ++k
)
578 storeAppendPrintf(sentry
, " %s", f
->names
[k
]);
580 storeAppendPrintf(sentry
, "\n");
584 /// \ingroup FQDNCacheInternal
586 fqdncacheLockEntry(fqdncache_entry
* f
)
588 if (f
->locks
++ == 0) {
589 dlinkDelete(&f
->lru
, &lru_list
);
590 dlinkAdd(f
, &f
->lru
, &lru_list
);
594 /// \ingroup FQDNCacheInternal
596 fqdncacheUnlockEntry(fqdncache_entry
* f
)
598 assert(f
->locks
> 0);
601 if (fqdncacheExpiredEntry(f
))
605 /// \ingroup FQDNCacheInternal
607 fqdncacheFreeEntry(void *data
)
609 fqdncache_entry
*f
= (fqdncache_entry
*)data
;
612 for (k
= 0; k
< (int) f
->name_count
; ++k
)
613 safe_free(f
->names
[k
]);
615 safe_free(f
->hash
.key
);
617 safe_free(f
->error_message
);
619 memFree(f
, MEM_FQDNCACHE_ENTRY
);
622 /// \ingroup FQDNCacheAPI
624 fqdncacheFreeMemory(void)
626 hashFreeItems(fqdn_table
, fqdncacheFreeEntry
);
627 hashFreeMemory(fqdn_table
);
632 \ingroup FQDNCacheAPI
634 * Recalculate FQDN cache size upon reconfigure.
635 * Is called to clear the FQDN cache's data structures,
636 * cancel all pending requests.
639 fqdncache_restart(void)
641 fqdncache_high
= (long) (((float) Config
.fqdncache
.size
*
642 (float) FQDN_HIGH_WATER
) / (float) 100);
643 fqdncache_low
= (long) (((float) Config
.fqdncache
.size
*
644 (float) FQDN_LOW_WATER
) / (float) 100);
645 purge_entries_fromhosts();
649 \ingroup FQDNCacheAPI
651 * Adds a "static" entry from /etc/hosts.
653 * The worldist is to be managed by the caller,
654 * including pointed-to strings
656 \param addr FQDN name to be added.
660 fqdncacheAddEntryFromHosts(char *addr
, wordlist
* hostnames
)
662 fqdncache_entry
*fce
;
665 if ((fce
= fqdncache_get(addr
))) {
666 if (1 == fce
->flags
.fromhosts
) {
667 fqdncacheUnlockEntry(fce
);
668 } else if (fce
->locks
> 0) {
669 debugs(35, DBG_IMPORTANT
, "fqdncacheAddEntryFromHosts: can't add static entry for locked address '" << addr
<< "'");
672 fqdncacheRelease(fce
);
676 fce
= fqdncacheCreateEntry(addr
);
679 fce
->names
[j
] = xstrdup(hostnames
->key
);
680 Tolower(fce
->names
[j
]);
682 hostnames
= hostnames
->next
;
684 if (j
>= FQDN_MAX_NAMES
)
689 fce
->names
[j
] = NULL
; /* it's safe */
690 fce
->flags
.fromhosts
= true;
691 fqdncacheAddEntry(fce
);
692 fqdncacheLockEntry(fce
);
695 /// \ingroup FQDNCacheInternal
697 fqdncacheRegisterWithCacheManager(void)
699 Mgr::RegisterAction("fqdncache", "FQDN Cache Stats and Contents",
705 \ingroup FQDNCacheAPI
707 * Initialize the fqdncache.
708 * Called after IP cache initialization.
715 fqdncacheRegisterWithCacheManager();
720 debugs(35, 3, "Initializing FQDN Cache...");
722 memset(&FqdncacheStats
, '\0', sizeof(FqdncacheStats
));
724 memset(&lru_list
, '\0', sizeof(lru_list
));
726 fqdncache_high
= (long) (((float) Config
.fqdncache
.size
*
727 (float) FQDN_HIGH_WATER
) / (float) 100);
729 fqdncache_low
= (long) (((float) Config
.fqdncache
.size
*
730 (float) FQDN_LOW_WATER
) / (float) 100);
732 n
= hashPrime(fqdncache_high
/ 4);
734 fqdn_table
= hash_create((HASHCMP
*) strcmp
, n
, hash4
);
736 memDataInit(MEM_FQDNCACHE_ENTRY
, "fqdncache_entry",
737 sizeof(fqdncache_entry
), 0);
742 * \ingroup FQDNCacheAPI
743 * The function to return the FQDN statistics via SNMP
746 snmp_netFqdnFn(variable_list
* Var
, snint
* ErrP
)
748 variable_list
*Answer
= NULL
;
750 debugs(49, 5, "snmp_netFqdnFn: Processing request:" << snmpDebugOid(Var
->name
, Var
->name_length
, tmp
));
751 *ErrP
= SNMP_ERR_NOERROR
;
753 switch (Var
->name
[LEN_SQ_NET
+ 1]) {
756 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
762 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
763 FqdncacheStats
.requests
,
768 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
774 /* this is now worthless */
775 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
781 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
782 FqdncacheStats
.negative_hits
,
787 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
788 FqdncacheStats
.misses
,
793 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
799 *ErrP
= SNMP_ERR_NOSUCHNAME
;
806 #endif /*SQUID_SNMP */