2 * Copyright (C) 1996-2018 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 14 IP Cache */
12 #include "CacheManager.h"
15 #include "dns/LookupDetails.h"
16 #include "dns/rfc3596.h"
18 #include "ip/Address.h"
21 #include "mgr/Registration.h"
22 #include "SquidConfig.h"
23 #include "SquidTime.h"
24 #include "StatCounters.h"
30 #include "snmp_core.h"
34 \defgroup IPCacheAPI IP Cache API
36 \section Introduction Introduction
38 * The IP cache is a built-in component of squid providing
39 * Hostname to IP-Number translation functionality and managing
40 * the involved data-structures. Efficiency concerns require
41 * mechanisms that allow non-blocking access to these mappings.
42 * The IP cache usually doesn't block on a request except for
43 * special cases where this is desired (see below).
45 \todo IP Cache should have its own API *.h header file.
49 \defgroup IPCacheInternal IP Cache Internals
51 \todo when IP cache is provided as a class. These sub-groups will be obsolete
52 * for now they are used to separate the public and private functions.
53 * with the private ones all being in IPCachInternal and public in IPCacheAPI
55 \section InternalOperation Internal Operation
57 * Internally, the execution flow is as follows: On a miss,
58 * ipcache_getnbhostbyname checks whether a request for
59 * this name is already pending, and if positive, it creates
60 * a new entry using ipcacheAddNew with the IP_PENDING
61 * flag set . Then it calls ipcacheAddPending to add a
62 * request to the queue together with data and handler. Else,
63 * ipcache_dnsDispatch() is called to directly create a
64 * DNS query or to ipcacheEnqueue() if all no DNS port
65 * is free. ipcache_call_pending() is called regularly
66 * to walk down the pending list and call handlers. LRU clean-up
67 * is performed through ipcache_purgelru() according to
68 * the ipcache_high threshold.
71 /// metadata for parsing DNS A and AAAA records
72 template <class Content
>
76 typedef Content DataType
; ///< actual RR DATA type
77 const char *kind
; ///< human-friendly record type description
78 int &recordCounter
; ///< where this kind of records are counted (for stats)
81 /// forwards non-blocking IP cache lookup results to either IPH or IpReciever
82 class IpCacheLookupForwarder
85 IpCacheLookupForwarder() {}
86 explicit IpCacheLookupForwarder(const CbcPointer
<Dns::IpReceiver
> &receiver
);
87 IpCacheLookupForwarder(IPH
*fun
, void *data
);
89 /// forwards notification about the end of the lookup; last method to be called
90 void finalCallback(const Dns::CachedIps
*addrs
, const Dns::LookupDetails
&details
);
92 /// forwards an IP notification
93 /// \returns whether it may be possible to deliver more notifications
94 bool forwardIp(const Ip::Address
&ip
);
96 /// convenience wrapper to safely forwardIp() for each IP in the container
97 void forwardHits(const Dns::CachedIps
&ips
);
99 /// initialize lookup timestamps for Dns::LookupDetails delay calculation
100 void lookupsStarting() { firstLookupStart
= lastLookupEnd
= current_time
; }
102 /// inform recipient of a finished lookup
103 void forwardLookup(const char *error
);
105 /// \returns milliseconds since the first lookup start
106 int totalResponseTime() const { return tvSubMsec(firstLookupStart
, current_time
); }
109 /// \returns not yet reported lookup delay in milliseconds
110 int additionalLookupDelay() const { return tvSubMsec(lastLookupEnd
, current_time
); }
113 /* receiverObj and receiverFun are mutually exclusive */
114 CbcPointer
<Dns::IpReceiver
> receiverObj
; ///< gets incremental and final results
115 IPH
*receiverFun
= nullptr; ///< gets final results
116 CallbackData receiverData
; ///< caller-specific data for the handler (optional)
118 struct timeval firstLookupStart
{0,0}; ///< time of the idnsALookup() call
119 struct timeval lastLookupEnd
{0,0}; ///< time of the last noteLookup() call
125 * The data structure used for storing name-address mappings
126 * is a small hashtable (static hash_table *ip_table),
127 * where structures of type ipcache_entry whose most
128 * interesting members are:
132 CBDATA_CLASS(ipcache_entry
);
135 ipcache_entry(const char *);
138 hash_link hash
; /* must be first */
142 IpCacheLookupForwarder handler
;
146 unsigned short locks
;
148 Flags() : negcached(false), fromhosts(false) {}
154 bool sawCname
= false;
156 const char *name() const { return static_cast<const char*>(hash
.key
); }
158 /// milliseconds since the first lookup start or -1 if there were no lookups
159 int totalResponseTime() const;
160 /// milliseconds since the last lookup start or -1 if there were no lookups
161 int additionalLookupDelay() const;
163 /// adds the contents of a "good" DNS A or AAAA record to stored IPs
164 template <class Specs
>
165 void addGood(const rfc1035_rr
&rr
, Specs
&specs
);
167 /// remembers the last error seen, overwriting any previous errors
168 void latestError(const char *text
, const int debugLevel
= 3);
171 void updateTtl(const unsigned int rrTtl
);
174 /// \ingroup IPCacheInternal
175 static struct _ipcache_stats
{
189 /// \ingroup IPCacheInternal
190 static dlink_list lru_list
;
193 static void stat_ipcache_get(StoreEntry
*);
195 static FREE ipcacheFreeEntry
;
196 static IDNSCB ipcacheHandleReply
;
197 static int ipcacheExpiredEntry(ipcache_entry
*);
198 static ipcache_entry
*ipcache_get(const char *);
199 static void ipcacheLockEntry(ipcache_entry
*);
200 static void ipcacheStatPrint(ipcache_entry
*, StoreEntry
*);
201 static void ipcacheUnlockEntry(ipcache_entry
*);
202 static void ipcacheRelease(ipcache_entry
*, bool dofree
= true);
203 static const Dns::CachedIps
*ipcacheCheckNumeric(const char *name
);
204 static void ipcache_nbgethostbyname_(const char *name
, IpCacheLookupForwarder handler
);
206 /// \ingroup IPCacheInternal
207 static hash_table
*ip_table
= NULL
;
209 /// \ingroup IPCacheInternal
210 static long ipcache_low
= 180;
211 /// \ingroup IPCacheInternal
212 static long ipcache_high
= 200;
214 #if LIBRESOLV_DNS_TTL_HACK
215 extern int _dns_ttl_
;
218 CBDATA_CLASS_INIT(ipcache_entry
);
220 IpCacheLookupForwarder::IpCacheLookupForwarder(const CbcPointer
<Dns::IpReceiver
> &receiver
):
221 receiverObj(receiver
)
225 IpCacheLookupForwarder::IpCacheLookupForwarder(IPH
*fun
, void *data
):
226 receiverFun(fun
), receiverData(data
)
231 IpCacheLookupForwarder::finalCallback(const Dns::CachedIps
*addrs
, const Dns::LookupDetails
&details
)
233 debugs(14, 7, addrs
<< " " << details
);
234 if (receiverObj
.set()) {
235 if (auto receiver
= receiverObj
.valid())
236 receiver
->noteIps(addrs
, details
);
238 } else if (receiverFun
) {
239 if (receiverData
.valid()) {
240 const Dns::CachedIps
*emptyIsNil
= (addrs
&& !addrs
->empty()) ? addrs
: nullptr;
241 receiverFun(emptyIsNil
, details
, receiverData
.validDone());
243 receiverFun
= nullptr;
247 /// forwards an IP notification
248 /// \returns whether it may be possible to deliver more notifications
250 IpCacheLookupForwarder::forwardIp(const Ip::Address
&ip
)
253 if (receiverObj
.set()) {
254 if (auto receiver
= receiverObj
.valid()) {
255 receiver
->noteIp(ip
);
260 // else do nothing: ReceiverFun does not do incremental notifications
264 /// convenience wrapper to safely forwardIp() for each IP in the container
266 IpCacheLookupForwarder::forwardHits(const Dns::CachedIps
&ips
)
268 if (receiverObj
.set()) {
269 for (const auto &ip
: ips
.good()) {
271 break; // receiver gone
274 // else do nothing: ReceiverFun does not do incremental notifications
278 IpCacheLookupForwarder::forwardLookup(const char *error
)
280 // Lookups run concurrently, but HttpRequest::recordLookup() thinks they
281 // are sequential. Give it just the new, yet-unaccounted-for delay.
282 if (receiverObj
.set()) {
283 if (auto receiver
= receiverObj
.valid()) {
284 receiver
->noteLookup(Dns::LookupDetails(error
, additionalLookupDelay()));
285 lastLookupEnd
= current_time
;
288 // else do nothing: ReceiverFun gets no individual lookup notifications
291 /// \ingroup IPCacheInternal
292 inline int ipcacheCount() { return ip_table
? ip_table
->count
: 0; }
295 \ingroup IPCacheInternal
297 * removes the given ipcache entry
300 ipcacheRelease(ipcache_entry
* i
, bool dofree
)
303 debugs(14, DBG_CRITICAL
, "ipcacheRelease: Releasing entry with i=<NULL>");
307 if (!i
|| !i
->hash
.key
) {
308 debugs(14, DBG_CRITICAL
, "ipcacheRelease: Releasing entry without hash link!");
312 debugs(14, 3, "ipcacheRelease: Releasing entry for '" << (const char *) i
->hash
.key
<< "'");
314 hash_remove_link(ip_table
, (hash_link
*) i
);
315 dlinkDelete(&i
->lru
, &lru_list
);
320 /// \ingroup IPCacheInternal
321 static ipcache_entry
*
322 ipcache_get(const char *name
)
324 if (ip_table
!= NULL
)
325 return (ipcache_entry
*) hash_lookup(ip_table
, name
);
330 /// \ingroup IPCacheInternal
332 ipcacheExpiredEntry(ipcache_entry
* i
)
334 /* all static entries are locked, so this takes care of them too */
339 if (i
->addrs
.empty())
340 if (0 == i
->flags
.negcached
)
343 if (i
->expires
> squid_curtime
)
349 /// \ingroup IPCacheAPI
351 ipcache_purgelru(void *)
354 dlink_node
*prev
= NULL
;
357 eventAdd("ipcache_purgelru", ipcache_purgelru
, NULL
, 10.0, 1);
359 for (m
= lru_list
.tail
; m
; m
= prev
) {
360 if (ipcacheCount() < ipcache_low
)
365 i
= (ipcache_entry
*)m
->data
;
375 debugs(14, 9, "ipcache_purgelru: removed " << removed
<< " entries");
379 \ingroup IPCacheInternal
381 * purges entries added from /etc/hosts (or whatever).
384 purge_entries_fromhosts(void)
386 dlink_node
*m
= lru_list
.head
;
387 ipcache_entry
*i
= NULL
, *t
;
390 if (i
!= NULL
) { /* need to delay deletion */
391 ipcacheRelease(i
); /* we just override locks */
395 t
= (ipcache_entry
*)m
->data
;
397 if (t
->flags
.fromhosts
)
407 ipcache_entry::ipcache_entry(const char *aName
):
410 error_message(nullptr),
411 locks(0) // XXX: use Lock type ?
413 hash
.key
= xstrdup(aName
);
414 Tolower(static_cast<char*>(hash
.key
));
415 expires
= squid_curtime
+ Config
.negativeDnsTtl
;
418 /// \ingroup IPCacheInternal
420 ipcacheAddEntry(ipcache_entry
* i
)
422 hash_link
*e
= (hash_link
*)hash_lookup(ip_table
, i
->hash
.key
);
425 /* avoid colission */
426 ipcache_entry
*q
= (ipcache_entry
*) e
;
430 hash_join(ip_table
, &i
->hash
);
431 dlinkAdd(i
, &i
->lru
, &lru_list
);
432 i
->lastref
= squid_curtime
;
436 \ingroup IPCacheInternal
438 * walks down the pending list, calling handlers
441 ipcacheCallback(ipcache_entry
*i
, const bool hit
, const int wait
)
443 i
->lastref
= squid_curtime
;
448 i
->handler
.forwardHits(i
->addrs
);
449 const Dns::LookupDetails
details(i
->error_message
, wait
);
450 i
->handler
.finalCallback(&i
->addrs
, details
);
452 ipcacheUnlockEntry(i
);
456 ipcache_entry::latestError(const char *text
, const int debugLevel
)
458 debugs(14, debugLevel
, "DNS error while resolving " << name() << ": " << text
);
459 safe_free(error_message
);
460 error_message
= xstrdup(text
);
464 ipcacheParse(ipcache_entry
*i
, const rfc1035_rr
* answers
, int nr
, const char *error_message
)
468 // XXX: Callers use zero ancount instead of -1 on errors!
470 i
->latestError(error_message
);
475 i
->latestError("No DNS records");
479 debugs(14, 3, nr
<< " answers for " << i
->name());
482 for (k
= 0; k
< nr
; ++k
) {
484 if (Ip::EnableIpv6
&& answers
[k
].type
== RFC1035_TYPE_AAAA
) {
485 static const RrSpecs
<struct in6_addr
> QuadA
= { "IPv6", IpcacheStats
.rr_aaaa
};
486 i
->addGood(answers
[k
], QuadA
);
490 if (answers
[k
].type
== RFC1035_TYPE_A
) {
491 static const RrSpecs
<struct in_addr
> SingleA
= { "IPv4", IpcacheStats
.rr_a
};
492 i
->addGood(answers
[k
], SingleA
);
496 /* With A and AAAA, the CNAME does not necessarily come with additional records to use. */
497 if (answers
[k
].type
== RFC1035_TYPE_CNAME
) {
499 ++IpcacheStats
.rr_cname
;
503 // otherwise its an unknown RR. debug at level 9 since we usually want to ignore these and they are common.
504 debugs(14, 9, "Unknown RR type received: type=" << answers
[k
].type
<< " starting at " << &(answers
[k
]) );
508 template <class Specs
>
510 ipcache_entry::addGood(const rfc1035_rr
&rr
, Specs
&specs
)
512 typename
Specs::DataType address
;
513 if (rr
.rdlength
!= sizeof(address
)) {
514 debugs(14, DBG_IMPORTANT
, "ERROR: Ignoring invalid " << specs
.kind
<< " address record while resolving " << name());
518 ++specs
.recordCounter
;
520 // Do not store more than 255 addresses (TODO: Why?)
521 if (addrs
.raw().size() >= 255)
524 memcpy(&address
, rr
.rdata
, sizeof(address
));
525 const Ip::Address ip
= address
;
526 if (addrs
.have(ip
)) {
527 debugs(14, 3, "refusing to add duplicate " << ip
);
530 addrs
.pushUnique(address
);
534 debugs(14, 3, name() << " #" << addrs
.size() << " " << ip
);
535 handler
.forwardIp(ip
); // we are only called with good IPs
539 ipcache_entry::updateTtl(const unsigned int rrTtl
)
541 const time_t ttl
= std::min(std::max(
542 Config
.negativeDnsTtl
, // smallest value allowed
543 static_cast<time_t>(rrTtl
)),
544 Config
.positiveDnsTtl
); // largest value allowed
546 const time_t rrExpires
= squid_curtime
+ ttl
;
547 if (rrExpires
< expires
)
551 /// \ingroup IPCacheInternal
553 ipcacheHandleReply(void *data
, const rfc1035_rr
* answers
, int na
, const char *error_message
, const bool lastAnswer
)
555 ipcache_entry
*i
= static_cast<ipcache_entry
*>(data
);
557 i
->handler
.forwardLookup(error_message
);
558 ipcacheParse(i
, answers
, na
, error_message
);
563 ++IpcacheStats
.replies
;
564 const auto age
= i
->handler
.totalResponseTime();
565 statCounter
.dns
.svcTime
.count(age
);
567 if (i
->addrs
.empty()) {
568 i
->flags
.negcached
= true;
569 i
->expires
= squid_curtime
+ Config
.negativeDnsTtl
;
571 if (!i
->error_message
) {
572 i
->latestError("No valid address records", DBG_IMPORTANT
);
574 ++IpcacheStats
.cname_only
;
578 debugs(14, 3, "done with " << i
->name() << ": " << i
->addrs
);
580 ipcacheCallback(i
, false, age
);
586 \param name Host to resolve.
587 \param handler Pointer to the function to be called when the reply
588 * from the IP cache (or the DNS if the IP cache misses)
589 \param handlerData Information that is passed to the handler and does not affect the IP cache.
591 * XXX: on hits and some errors, the handler is called immediately instead
592 * of scheduling an async call. This reentrant behavior means that the
593 * user job must be extra careful after calling ipcache_nbgethostbyname,
594 * especially if the handler destroys the job. Moreover, the job has
595 * no way of knowing whether the reentrant call happened.
596 * Comm::Connection setup usually protects the job by scheduling an async call,
597 * but some user code calls ipcache_nbgethostbyname directly.
600 ipcache_nbgethostbyname(const char *name
, IPH
* handler
, void *handlerData
)
603 ipcache_nbgethostbyname_(name
, IpCacheLookupForwarder(handler
, handlerData
));
607 Dns::nbgethostbyname(const char *name
, const CbcPointer
<IpReceiver
> &receiver
)
610 ipcache_nbgethostbyname_(name
, IpCacheLookupForwarder(receiver
));
613 /// implements ipcache_nbgethostbyname() and Dns::nbgethostbyname() APIs
615 ipcache_nbgethostbyname_(const char *name
, IpCacheLookupForwarder handler
)
617 ipcache_entry
*i
= NULL
;
618 const ipcache_addrs
*addrs
= NULL
;
619 ++IpcacheStats
.requests
;
621 if (name
== NULL
|| name
[0] == '\0') {
622 debugs(14, 4, "ipcache_nbgethostbyname: Invalid name!");
623 ++IpcacheStats
.invalid
;
624 const Dns::LookupDetails
details("Invalid hostname", -1); // error, no lookup
625 handler
.finalCallback(nullptr, details
);
629 if ((addrs
= ipcacheCheckNumeric(name
))) {
630 debugs(14, 4, "ipcache_nbgethostbyname: BYPASS for '" << name
<< "' (already numeric)");
631 handler
.forwardHits(*addrs
);
632 ++IpcacheStats
.numeric_hits
;
633 const Dns::LookupDetails details
; // no error, no lookup
634 handler
.finalCallback(addrs
, details
);
638 i
= ipcache_get(name
);
643 } else if (ipcacheExpiredEntry(i
)) {
644 /* hit, but expired -- bummer */
649 debugs(14, 4, "ipcache_nbgethostbyname: HIT for '" << name
<< "'");
651 if (i
->flags
.negcached
)
652 ++IpcacheStats
.negative_hits
;
656 i
->handler
= std::move(handler
);
657 ipcacheCallback(i
, true, -1); // no lookup
662 debugs(14, 5, "ipcache_nbgethostbyname: MISS for '" << name
<< "'");
663 ++IpcacheStats
.misses
;
664 i
= new ipcache_entry(name
);
665 i
->handler
= std::move(handler
);
666 i
->handler
.lookupsStarting();
667 idnsALookup(hashKeyStr(&i
->hash
), ipcacheHandleReply
, i
);
670 /// \ingroup IPCacheInternal
672 ipcacheRegisterWithCacheManager(void)
674 Mgr::RegisterAction("ipcache",
675 "IP Cache Stats and Contents",
676 stat_ipcache_get
, 0, 1);
682 * Initialize the ipcache.
683 * Is called from mainInitialize() after disk initialization
684 * and prior to the reverse FQDNCache initialization
690 debugs(14, DBG_IMPORTANT
, "Initializing IP Cache...");
691 memset(&IpcacheStats
, '\0', sizeof(IpcacheStats
));
692 memset(&lru_list
, '\0', sizeof(lru_list
));
694 ipcache_high
= (long) (((float) Config
.ipcache
.size
*
695 (float) Config
.ipcache
.high
) / (float) 100);
696 ipcache_low
= (long) (((float) Config
.ipcache
.size
*
697 (float) Config
.ipcache
.low
) / (float) 100);
698 n
= hashPrime(ipcache_high
/ 4);
699 ip_table
= hash_create((HASHCMP
*) strcmp
, n
, hash4
);
701 ipcacheRegisterWithCacheManager();
707 * Is different from ipcache_nbgethostbyname in that it only checks
708 * if an entry exists in the cache and does not by default contact the DNS,
709 * unless this is requested, by setting the flags.
711 \param name Host name to resolve.
712 \param flags Default is NULL, set to IP_LOOKUP_IF_MISS
713 * to explicitly perform DNS lookups.
715 \retval NULL An error occured during lookup
716 \retval NULL No results available in cache and no lookup specified
717 \retval * Pointer to the ipcahce_addrs structure containing the lookup results
719 const ipcache_addrs
*
720 ipcache_gethostbyname(const char *name
, int flags
)
722 ipcache_entry
*i
= NULL
;
724 debugs(14, 3, "ipcache_gethostbyname: '" << name
<< "', flags=" << std::hex
<< flags
);
725 ++IpcacheStats
.requests
;
726 i
= ipcache_get(name
);
730 } else if (ipcacheExpiredEntry(i
)) {
733 } else if (i
->flags
.negcached
) {
734 ++IpcacheStats
.negative_hits
;
735 // ignore i->error_message: the caller just checks IP cache presence
739 i
->lastref
= squid_curtime
;
740 // ignore i->error_message: the caller just checks IP cache presence
744 /* no entry [any more] */
746 if (const auto addrs
= ipcacheCheckNumeric(name
)) {
747 ++IpcacheStats
.numeric_hits
;
751 ++IpcacheStats
.misses
;
753 if (flags
& IP_LOOKUP_IF_MISS
)
754 ipcache_nbgethostbyname(name
, NULL
, NULL
);
759 /// \ingroup IPCacheInternal
761 ipcacheStatPrint(ipcache_entry
* i
, StoreEntry
* sentry
)
763 char buf
[MAX_IPSTRLEN
];
766 debugs(14, DBG_CRITICAL
, HERE
<< "CRITICAL: sentry is NULL!");
771 debugs(14, DBG_CRITICAL
, HERE
<< "CRITICAL: ipcache_entry is NULL!");
772 storeAppendPrintf(sentry
, "CRITICAL ERROR\n");
776 storeAppendPrintf(sentry
, " %-32.32s %c%c %6d %6d %2d(%2d)",
777 hashKeyStr(&i
->hash
),
778 i
->flags
.fromhosts
? 'H' : ' ',
779 i
->flags
.negcached
? 'N' : ' ',
780 (int) (squid_curtime
- i
->lastref
),
781 (int) ((i
->flags
.fromhosts
? -1 : i
->expires
- squid_curtime
)),
782 static_cast<int>(i
->addrs
.size()),
783 static_cast<int>(i
->addrs
.badCount()));
786 * Negative-cached entries have no IPs listed. */
787 if (i
->flags
.negcached
) {
788 storeAppendPrintf(sentry
, "\n");
793 * Cached entries have IPs listed with a BNF of: ip-address '-' ('OK'|'BAD') */
794 bool firstLine
= true;
795 for (const auto &addr
: i
->addrs
.raw()) {
796 /* Display tidy-up: IPv6 are so big make the list vertical */
797 const char *indent
= firstLine
? "" : " ";
798 storeAppendPrintf(sentry
, "%s %45.45s-%3s\n",
800 addr
.ip
.toStr(buf
, MAX_IPSTRLEN
),
801 addr
.bad() ? "BAD" : "OK ");
807 \ingroup IPCacheInternal
809 * process objects list
812 stat_ipcache_get(StoreEntry
* sentry
)
815 assert(ip_table
!= NULL
);
816 storeAppendPrintf(sentry
, "IP Cache Statistics:\n");
817 storeAppendPrintf(sentry
, "IPcache Entries Cached: %d\n",
819 storeAppendPrintf(sentry
, "IPcache Requests: %d\n",
820 IpcacheStats
.requests
);
821 storeAppendPrintf(sentry
, "IPcache Hits: %d\n",
823 storeAppendPrintf(sentry
, "IPcache Negative Hits: %d\n",
824 IpcacheStats
.negative_hits
);
825 storeAppendPrintf(sentry
, "IPcache Numeric Hits: %d\n",
826 IpcacheStats
.numeric_hits
);
827 storeAppendPrintf(sentry
, "IPcache Misses: %d\n",
828 IpcacheStats
.misses
);
829 storeAppendPrintf(sentry
, "IPcache Retrieved A: %d\n",
831 storeAppendPrintf(sentry
, "IPcache Retrieved AAAA: %d\n",
832 IpcacheStats
.rr_aaaa
);
833 storeAppendPrintf(sentry
, "IPcache Retrieved CNAME: %d\n",
834 IpcacheStats
.rr_cname
);
835 storeAppendPrintf(sentry
, "IPcache CNAME-Only Response: %d\n",
836 IpcacheStats
.cname_only
);
837 storeAppendPrintf(sentry
, "IPcache Invalid Request: %d\n",
838 IpcacheStats
.invalid
);
839 storeAppendPrintf(sentry
, "\n\n");
840 storeAppendPrintf(sentry
, "IP Cache Contents:\n\n");
841 storeAppendPrintf(sentry
, " %-31.31s %3s %6s %6s %4s\n",
848 for (m
= lru_list
.head
; m
; m
= m
->next
) {
849 assert( m
->next
!= m
);
850 ipcacheStatPrint((ipcache_entry
*)m
->data
, sentry
);
854 /// \ingroup IPCacheAPI
856 ipcacheInvalidate(const char *name
)
860 if ((i
= ipcache_get(name
)) == NULL
)
863 i
->expires
= squid_curtime
;
866 * NOTE, don't call ipcacheRelease here because we might be here due
867 * to a thread started from a callback.
871 /// \ingroup IPCacheAPI
873 ipcacheInvalidateNegative(const char *name
)
877 if ((i
= ipcache_get(name
)) == NULL
)
880 if (i
->flags
.negcached
)
881 i
->expires
= squid_curtime
;
884 * NOTE, don't call ipcacheRelease here because we might be here due
885 * to a thread started from a callback.
889 /// \ingroup IPCacheAPI
890 static const Dns::CachedIps
*
891 ipcacheCheckNumeric(const char *name
)
894 if (!ip
.fromHost(name
))
897 debugs(14, 4, "HIT_BYPASS for " << name
<< "=" << ip
);
898 static Dns::CachedIps static_addrs
;
899 static_addrs
.reset(ip
);
900 return &static_addrs
;
903 /// \ingroup IPCacheInternal
905 ipcacheLockEntry(ipcache_entry
* i
)
907 if (i
->locks
++ == 0) {
908 dlinkDelete(&i
->lru
, &lru_list
);
909 dlinkAdd(i
, &i
->lru
, &lru_list
);
913 /// \ingroup IPCacheInternal
915 ipcacheUnlockEntry(ipcache_entry
* i
)
918 debugs(14, DBG_IMPORTANT
, "WARNING: ipcacheEntry unlocked with no lock! locks=" << i
->locks
);
924 if (ipcacheExpiredEntry(i
))
928 /// find the next good IP, wrapping if needed
929 /// \returns whether the search was successful
931 Dns::CachedIps::seekNewGood(const char *name
)
934 for (size_t seen
= 0; seen
< ips
.size(); ++seen
) {
935 if (++goodPosition
>= ips
.size())
937 if (!ips
[goodPosition
].bad()) {
938 debugs(14, 3, "succeeded for " << name
<< ": " << *this);
942 goodPosition
= ips
.size();
943 debugs(14, 3, "failed for " << name
<< ": " << *this);
948 Dns::CachedIps::reset(const Ip::Address
&ip
)
951 ips
.emplace_back(ip
);
953 // Assume that the given IP is good because CachedIps are designed to never
954 // run out of good IPs.
958 /// makes current() calls possible after a successful markAsBad()
960 Dns::CachedIps::restoreGoodness(const char *name
)
962 if (badCount() >= size()) {
963 // There are no good IPs left. Clear all bad marks. This must help
964 // because we are called only after a good address was tested as bad.
965 for (auto &cachedIp
: ips
)
966 cachedIp
.forgetMarking();
969 Must(seekNewGood(name
));
970 debugs(14, 3, "cleared all IPs for " << name
<< "; now back to " << *this);
974 Dns::CachedIps::have(const Ip::Address
&ip
, size_t *positionOrNil
) const
978 for (const auto &cachedIp
: ips
) {
979 if (cachedIp
.ip
== ip
) {
980 if (auto position
= positionOrNil
)
982 debugs(14, 7, ip
<< " at " << pos
<< " in " << *this);
986 // no such address; leave *position as is
987 debugs(14, 7, " no " << ip
<< " in " << *this);
992 Dns::CachedIps::pushUnique(const Ip::Address
&ip
)
995 ips
.emplace_back(ip
);
996 assert(!raw().back().bad());
1000 Dns::CachedIps::reportCurrent(std::ostream
&os
) const
1003 os
<< "[no cached IPs]";
1004 else if (goodPosition
== size())
1005 os
<< "[" << size() << " bad cached IPs]"; // could only be temporary
1007 os
<< current() << " #" << (goodPosition
+1) << "/" << ips
.size() << "-" << badCount();
1011 Dns::CachedIps::markAsBad(const char *name
, const Ip::Address
&ip
)
1013 size_t badPosition
= 0;
1014 if (!have(ip
, &badPosition
))
1015 return; // no such address
1017 auto &cachedIp
= ips
[badPosition
];
1019 return; // already marked correctly
1021 cachedIp
.markAsBad();
1023 debugs(14, 2, ip
<< " of " << name
);
1025 if (goodPosition
== badPosition
)
1026 restoreGoodness(name
);
1027 // else nothing to do: goodPositon still points to a good IP
1031 Dns::CachedIps::forgetMarking(const char *name
, const Ip::Address
&ip
)
1034 return; // all IPs are already "good"
1036 size_t badPosition
= 0;
1037 if (!have(ip
, &badPosition
))
1038 return; // no such address
1040 auto &cachedIp
= ips
[badPosition
];
1041 if (!cachedIp
.bad())
1042 return; // already marked correctly
1044 cachedIp
.forgetMarking();
1045 assert(!cachedIp
.bad());
1047 debugs(14, 2, ip
<< " of " << name
);
1051 * Marks the given address as BAD.
1052 * Does nothing if the domain name does not exist.
1054 \param name domain name to have an IP marked bad
1055 \param addr specific addres to be marked bad
1058 ipcacheMarkBadAddr(const char *name
, const Ip::Address
&addr
)
1060 if (auto cached
= ipcache_get(name
))
1061 cached
->addrs
.markAsBad(name
, addr
);
1064 /// \ingroup IPCacheAPI
1066 ipcacheMarkGoodAddr(const char *name
, const Ip::Address
&addr
)
1068 if (auto cached
= ipcache_get(name
))
1069 cached
->addrs
.forgetMarking(name
, addr
);
1072 /// \ingroup IPCacheInternal
1074 ipcacheFreeEntry(void *data
)
1076 ipcache_entry
*i
= (ipcache_entry
*)data
;
1080 ipcache_entry::~ipcache_entry()
1082 xfree(error_message
);
1086 /// \ingroup IPCacheAPI
1088 ipcacheFreeMemory(void)
1090 hashFreeItems(ip_table
, ipcacheFreeEntry
);
1091 hashFreeMemory(ip_table
);
1098 * Recalculate IP cache size upon reconfigure.
1099 * Is called to clear the IPCache's data structures,
1100 * cancel all pending requests.
1103 ipcache_restart(void)
1105 ipcache_high
= (long) (((float) Config
.ipcache
.size
*
1106 (float) Config
.ipcache
.high
) / (float) 100);
1107 ipcache_low
= (long) (((float) Config
.ipcache
.size
*
1108 (float) Config
.ipcache
.low
) / (float) 100);
1109 purge_entries_fromhosts();
1115 * Adds a "static" entry from /etc/hosts
1117 \param name Hostname to be linked with IP
1118 \param ipaddr IP Address to be cached.
1121 \retval 1 IP address is invalid or other error.
1124 ipcacheAddEntryFromHosts(const char *name
, const char *ipaddr
)
1130 if (!(ip
= ipaddr
)) {
1131 if (strchr(ipaddr
, ':') && strspn(ipaddr
, "0123456789abcdefABCDEF:") == strlen(ipaddr
)) {
1132 debugs(14, 3, "ipcacheAddEntryFromHosts: Skipping IPv6 address '" << ipaddr
<< "'");
1134 debugs(14, DBG_IMPORTANT
, "ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr
<< "'");
1140 if (!Ip::EnableIpv6
&& ip
.isIPv6()) {
1141 debugs(14, 2, "skips IPv6 address in /etc/hosts because IPv6 support was disabled: " << ip
);
1145 if ((i
= ipcache_get(name
))) {
1146 if (1 == i
->flags
.fromhosts
) {
1147 ipcacheUnlockEntry(i
);
1148 } else if (i
->locks
> 0) {
1149 debugs(14, DBG_IMPORTANT
, "ipcacheAddEntryFromHosts: can't add static entry for locked name '" << name
<< "'");
1156 i
= new ipcache_entry(name
);
1157 i
->addrs
.pushUnique(ip
);
1158 i
->flags
.fromhosts
= true;
1160 ipcacheLockEntry(i
);
1168 * The function to return the ip cache statistics to via SNMP
1171 snmp_netIpFn(variable_list
* Var
, snint
* ErrP
)
1173 variable_list
*Answer
= NULL
;
1175 debugs(49, 5, "snmp_netIpFn: Processing request:" << snmpDebugOid(Var
->name
, Var
->name_length
, tmp
));
1176 *ErrP
= SNMP_ERR_NOERROR
;
1178 switch (Var
->name
[LEN_SQ_NET
+ 1]) {
1181 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1187 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1188 IpcacheStats
.requests
,
1193 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1199 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1205 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1206 IpcacheStats
.negative_hits
,
1211 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1212 IpcacheStats
.misses
,
1217 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1223 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1229 *ErrP
= SNMP_ERR_NOSUCHNAME
;
1230 snmp_var_free(Answer
);
1237 #endif /*SQUID_SNMP */