2 * Copyright (C) 1996-2023 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 "base/IoManip.h"
13 #include "CacheManager.h"
15 #include "debug/Messages.h"
17 #include "dns/LookupDetails.h"
18 #include "dns/rfc3596.h"
20 #include "ip/Address.h"
23 #include "mgr/Registration.h"
24 #include "snmp_agent.h"
25 #include "SquidConfig.h"
26 #include "StatCounters.h"
32 #include "snmp_core.h"
36 \defgroup IPCacheAPI IP Cache API
38 \section IpcacheIntroduction Introduction
40 * The IP cache is a built-in component of squid providing
41 * Hostname to IP-Number translation functionality and managing
42 * the involved data-structures. Efficiency concerns require
43 * mechanisms that allow non-blocking access to these mappings.
44 * The IP cache usually doesn't block on a request except for
45 * special cases where this is desired (see below).
49 \defgroup IPCacheInternal IP Cache Internals
51 \note 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
);
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
= nullptr;
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
* const possiblyEmptyAddrs
, const Dns::LookupDetails
&details
)
233 // TODO: Consider removing nil-supplying IpcacheStats.invalid code and refactoring accordingly.
234 // may be nil but is never empty
235 const auto addrs
= (possiblyEmptyAddrs
&& possiblyEmptyAddrs
->empty()) ? nullptr : possiblyEmptyAddrs
;
237 debugs(14, 7, addrs
<< " " << details
);
238 if (receiverObj
.set()) {
239 if (auto receiver
= receiverObj
.valid())
240 receiver
->noteIps(addrs
, details
);
242 } else if (receiverFun
) {
243 if (receiverData
.valid())
244 receiverFun(addrs
, details
, receiverData
.validDone());
245 receiverFun
= nullptr;
249 /// forwards an IP notification
250 /// \returns whether it may be possible to deliver more notifications
252 IpCacheLookupForwarder::forwardIp(const Ip::Address
&ip
)
255 if (receiverObj
.set()) {
256 if (auto receiver
= receiverObj
.valid()) {
257 receiver
->noteIp(ip
);
262 // else do nothing: ReceiverFun does not do incremental notifications
266 /// convenience wrapper to safely forwardIp() for each IP in the container
268 IpCacheLookupForwarder::forwardHits(const Dns::CachedIps
&ips
)
270 if (receiverObj
.set()) {
271 for (const auto &ip
: ips
.good()) {
273 break; // receiver gone
276 // else do nothing: ReceiverFun does not do incremental notifications
280 IpCacheLookupForwarder::forwardLookup(const char *error
)
282 // Lookups run concurrently, but HttpRequest::recordLookup() thinks they
283 // are sequential. Give it just the new, yet-unaccounted-for delay.
284 if (receiverObj
.set()) {
285 if (auto receiver
= receiverObj
.valid()) {
286 receiver
->noteLookup(Dns::LookupDetails(SBuf(error
), additionalLookupDelay()));
287 lastLookupEnd
= current_time
;
290 // else do nothing: ReceiverFun gets no individual lookup notifications
293 /// \ingroup IPCacheInternal
294 inline int ipcacheCount() { return ip_table
? ip_table
->count
: 0; }
297 \ingroup IPCacheInternal
299 * removes the given ipcache entry
302 ipcacheRelease(ipcache_entry
* i
, bool dofree
)
305 debugs(14, DBG_CRITICAL
, "ipcacheRelease: Releasing entry with i=<NULL>");
309 if (!i
|| !i
->hash
.key
) {
310 debugs(14, DBG_CRITICAL
, "ipcacheRelease: Releasing entry without hash link!");
314 debugs(14, 3, "ipcacheRelease: Releasing entry for '" << (const char *) i
->hash
.key
<< "'");
316 hash_remove_link(ip_table
, (hash_link
*) i
);
317 dlinkDelete(&i
->lru
, &lru_list
);
322 /// \ingroup IPCacheInternal
323 static ipcache_entry
*
324 ipcache_get(const char *name
)
326 if (ip_table
!= nullptr)
327 return (ipcache_entry
*) hash_lookup(ip_table
, name
);
332 /// \ingroup IPCacheInternal
334 ipcacheExpiredEntry(ipcache_entry
* i
)
336 /* all static entries are locked, so this takes care of them too */
341 if (i
->addrs
.empty())
342 if (0 == i
->flags
.negcached
)
345 if (i
->expires
> squid_curtime
)
351 /// \ingroup IPCacheAPI
353 ipcache_purgelru(void *)
356 dlink_node
*prev
= nullptr;
359 eventAdd("ipcache_purgelru", ipcache_purgelru
, nullptr, 10.0, 1);
361 for (m
= lru_list
.tail
; m
; m
= prev
) {
362 if (ipcacheCount() < ipcache_low
)
367 i
= (ipcache_entry
*)m
->data
;
377 debugs(14, 9, "ipcache_purgelru: removed " << removed
<< " entries");
381 \ingroup IPCacheInternal
383 * purges entries added from /etc/hosts (or whatever).
386 purge_entries_fromhosts(void)
388 dlink_node
*m
= lru_list
.head
;
389 ipcache_entry
*i
= nullptr, *t
;
392 if (i
!= nullptr) { /* need to delay deletion */
393 ipcacheRelease(i
); /* we just override locks */
397 t
= (ipcache_entry
*)m
->data
;
399 if (t
->flags
.fromhosts
)
409 ipcache_entry::ipcache_entry(const char *aName
):
412 error_message(nullptr),
413 locks(0) // XXX: use Lock type ?
415 hash
.key
= xstrdup(aName
);
416 Tolower(static_cast<char*>(hash
.key
));
417 expires
= squid_curtime
+ Config
.negativeDnsTtl
;
420 /// \ingroup IPCacheInternal
422 ipcacheAddEntry(ipcache_entry
* i
)
424 hash_link
*e
= (hash_link
*)hash_lookup(ip_table
, i
->hash
.key
);
427 /* avoid collision */
428 ipcache_entry
*q
= (ipcache_entry
*) e
;
432 hash_join(ip_table
, &i
->hash
);
433 dlinkAdd(i
, &i
->lru
, &lru_list
);
434 i
->lastref
= squid_curtime
;
438 \ingroup IPCacheInternal
440 * walks down the pending list, calling handlers
443 ipcacheCallback(ipcache_entry
*i
, const bool hit
, const int wait
)
445 i
->lastref
= squid_curtime
;
450 i
->handler
.forwardHits(i
->addrs
);
451 const Dns::LookupDetails
details(SBuf(i
->error_message
), wait
);
452 i
->handler
.finalCallback(&i
->addrs
, details
);
454 ipcacheUnlockEntry(i
);
458 ipcache_entry::latestError(const char *text
)
460 debugs(14, 3, "ERROR: DNS failure while resolving " << name() << ": " << text
);
461 safe_free(error_message
);
462 error_message
= xstrdup(text
);
466 ipcacheParse(ipcache_entry
*i
, const rfc1035_rr
* answers
, int nr
, const char *error_message
)
470 // XXX: Callers use zero ancount instead of -1 on errors!
472 i
->latestError(error_message
);
477 i
->latestError("No DNS records");
481 debugs(14, 3, nr
<< " answers for " << i
->name());
484 for (k
= 0; k
< nr
; ++k
) {
486 if (Ip::EnableIpv6
&& answers
[k
].type
== RFC1035_TYPE_AAAA
) {
487 static const RrSpecs
<struct in6_addr
> QuadA
= { "IPv6", IpcacheStats
.rr_aaaa
};
488 i
->addGood(answers
[k
], QuadA
);
492 if (answers
[k
].type
== RFC1035_TYPE_A
) {
493 static const RrSpecs
<struct in_addr
> SingleA
= { "IPv4", IpcacheStats
.rr_a
};
494 i
->addGood(answers
[k
], SingleA
);
498 /* With A and AAAA, the CNAME does not necessarily come with additional records to use. */
499 if (answers
[k
].type
== RFC1035_TYPE_CNAME
) {
501 ++IpcacheStats
.rr_cname
;
505 // otherwise its an unknown RR. debug at level 9 since we usually want to ignore these and they are common.
506 debugs(14, 9, "Unknown RR type received: type=" << answers
[k
].type
<< " starting at " << &(answers
[k
]) );
510 template <class Specs
>
512 ipcache_entry::addGood(const rfc1035_rr
&rr
, Specs
&specs
)
514 typename
Specs::DataType address
;
515 if (rr
.rdlength
!= sizeof(address
)) {
516 debugs(14, DBG_IMPORTANT
, "ERROR: Ignoring invalid " << specs
.kind
<< " address record while resolving " << name());
520 ++specs
.recordCounter
;
522 // Do not store more than 255 addresses (TODO: Why?)
523 if (addrs
.raw().size() >= 255)
526 memcpy(&address
, rr
.rdata
, sizeof(address
));
527 const Ip::Address ip
= address
;
528 if (addrs
.have(ip
)) {
529 debugs(14, 3, "refusing to add duplicate " << ip
);
532 addrs
.pushUnique(address
);
536 debugs(14, 3, name() << " #" << addrs
.size() << " " << ip
);
537 handler
.forwardIp(ip
); // we are only called with good IPs
541 ipcache_entry::updateTtl(const unsigned int rrTtl
)
543 const time_t ttl
= std::min(std::max(
544 Config
.negativeDnsTtl
, // smallest value allowed
545 static_cast<time_t>(rrTtl
)),
546 Config
.positiveDnsTtl
); // largest value allowed
548 const time_t rrExpires
= squid_curtime
+ ttl
;
549 if (addrs
.size() <= 1) {
550 debugs(14, 5, "use first " << ttl
<< " from RR TTL " << rrTtl
);
552 } else if (rrExpires
< expires
) {
553 debugs(14, 5, "use smaller " << ttl
<< " from RR TTL " << rrTtl
<< "; was: " << (expires
- squid_curtime
));
556 debugs(14, 7, "ignore " << ttl
<< " from RR TTL " << rrTtl
<< "; keep: " << (expires
- squid_curtime
));
560 /// \ingroup IPCacheInternal
562 ipcacheHandleReply(void *data
, const rfc1035_rr
* answers
, int na
, const char *error_message
, const bool lastAnswer
)
564 ipcache_entry
*i
= static_cast<ipcache_entry
*>(data
);
566 i
->handler
.forwardLookup(error_message
);
567 ipcacheParse(i
, answers
, na
, error_message
);
572 ++IpcacheStats
.replies
;
573 const auto age
= i
->handler
.totalResponseTime();
574 statCounter
.dns
.svcTime
.count(age
);
576 if (i
->addrs
.empty()) {
577 i
->flags
.negcached
= true;
578 i
->expires
= squid_curtime
+ Config
.negativeDnsTtl
;
580 if (!i
->error_message
) {
581 i
->latestError("No valid address records");
583 ++IpcacheStats
.cname_only
;
587 debugs(14, 3, "done with " << i
->name() << ": " << i
->addrs
);
589 ipcacheCallback(i
, false, age
);
595 \param name Host to resolve.
596 \param handler Pointer to the function to be called when the reply
597 * from the IP cache (or the DNS if the IP cache misses)
598 \param handlerData Information that is passed to the handler and does not affect the IP cache.
600 * XXX: on hits and some errors, the handler is called immediately instead
601 * of scheduling an async call. This reentrant behavior means that the
602 * user job must be extra careful after calling ipcache_nbgethostbyname,
603 * especially if the handler destroys the job. Moreover, the job has
604 * no way of knowing whether the reentrant call happened.
605 * Comm::Connection setup usually protects the job by scheduling an async call,
606 * but some user code calls ipcache_nbgethostbyname directly.
609 ipcache_nbgethostbyname(const char *name
, IPH
* handler
, void *handlerData
)
612 ipcache_nbgethostbyname_(name
, IpCacheLookupForwarder(handler
, handlerData
));
616 Dns::nbgethostbyname(const char *name
, const CbcPointer
<IpReceiver
> &receiver
)
619 ipcache_nbgethostbyname_(name
, IpCacheLookupForwarder(receiver
));
622 /// implements ipcache_nbgethostbyname() and Dns::nbgethostbyname() APIs
624 ipcache_nbgethostbyname_(const char *name
, IpCacheLookupForwarder handler
)
626 ipcache_entry
*i
= nullptr;
627 const ipcache_addrs
*addrs
= nullptr;
628 ++IpcacheStats
.requests
;
630 if (name
== nullptr || name
[0] == '\0') {
631 debugs(14, 4, "ipcache_nbgethostbyname: Invalid name!");
632 ++IpcacheStats
.invalid
;
633 static const Dns::LookupDetails
details(SBuf("Invalid hostname"), -1); // error, no lookup
634 handler
.finalCallback(nullptr, details
);
638 if ((addrs
= ipcacheCheckNumeric(name
))) {
639 debugs(14, 4, "ipcache_nbgethostbyname: BYPASS for '" << name
<< "' (already numeric)");
640 handler
.forwardHits(*addrs
);
641 ++IpcacheStats
.numeric_hits
;
642 const Dns::LookupDetails details
; // no error, no lookup
643 handler
.finalCallback(addrs
, details
);
647 i
= ipcache_get(name
);
652 } else if (ipcacheExpiredEntry(i
)) {
653 /* hit, but expired -- bummer */
658 debugs(14, 4, "ipcache_nbgethostbyname: HIT for '" << name
<< "'");
660 if (i
->flags
.negcached
)
661 ++IpcacheStats
.negative_hits
;
665 i
->handler
= std::move(handler
);
666 ipcacheCallback(i
, true, -1); // no lookup
671 debugs(14, 5, "ipcache_nbgethostbyname: MISS for '" << name
<< "'");
672 ++IpcacheStats
.misses
;
673 i
= new ipcache_entry(name
);
674 i
->handler
= std::move(handler
);
675 i
->handler
.lookupsStarting();
676 idnsALookup(hashKeyStr(&i
->hash
), ipcacheHandleReply
, i
);
679 /// \ingroup IPCacheInternal
681 ipcacheRegisterWithCacheManager(void)
683 Mgr::RegisterAction("ipcache",
684 "IP Cache Stats and Contents",
685 stat_ipcache_get
, 0, 1);
691 * Initialize the ipcache.
692 * Is called from mainInitialize() after disk initialization
693 * and prior to the reverse FQDNCache initialization
699 debugs(14, Important(24), "Initializing IP Cache...");
700 memset(&IpcacheStats
, '\0', sizeof(IpcacheStats
));
701 lru_list
= dlink_list();
703 ipcache_high
= (long) (((float) Config
.ipcache
.size
*
704 (float) Config
.ipcache
.high
) / (float) 100);
705 ipcache_low
= (long) (((float) Config
.ipcache
.size
*
706 (float) Config
.ipcache
.low
) / (float) 100);
707 n
= hashPrime(ipcache_high
/ 4);
708 ip_table
= hash_create((HASHCMP
*) strcmp
, n
, hash4
);
710 ipcacheRegisterWithCacheManager();
716 * Is different from ipcache_nbgethostbyname in that it only checks
717 * if an entry exists in the cache and does not by default contact the DNS,
718 * unless this is requested, by setting the flags.
720 \param name Host name to resolve.
721 \param flags Default is NULL, set to IP_LOOKUP_IF_MISS
722 * to explicitly perform DNS lookups.
724 \retval NULL An error occurred during lookup
725 \retval NULL No results available in cache and no lookup specified
726 \retval * Pointer to the ipcahce_addrs structure containing the lookup results
728 const ipcache_addrs
*
729 ipcache_gethostbyname(const char *name
, int flags
)
731 ipcache_entry
*i
= nullptr;
733 debugs(14, 3, "'" << name
<< "', flags=" << asHex(flags
));
734 ++IpcacheStats
.requests
;
735 i
= ipcache_get(name
);
739 } else if (ipcacheExpiredEntry(i
)) {
742 } else if (i
->flags
.negcached
) {
743 ++IpcacheStats
.negative_hits
;
744 // ignore i->error_message: the caller just checks IP cache presence
748 i
->lastref
= squid_curtime
;
749 // ignore i->error_message: the caller just checks IP cache presence
753 /* no entry [any more] */
755 if (const auto addrs
= ipcacheCheckNumeric(name
)) {
756 ++IpcacheStats
.numeric_hits
;
760 ++IpcacheStats
.misses
;
762 if (flags
& IP_LOOKUP_IF_MISS
)
763 ipcache_nbgethostbyname(name
, nullptr, nullptr);
768 /// \ingroup IPCacheInternal
770 ipcacheStatPrint(ipcache_entry
* i
, StoreEntry
* sentry
)
772 char buf
[MAX_IPSTRLEN
];
775 debugs(14, DBG_CRITICAL
, "ERROR: sentry is NULL!");
780 debugs(14, DBG_CRITICAL
, "ERROR: ipcache_entry is NULL!");
781 storeAppendPrintf(sentry
, "CRITICAL ERROR\n");
785 storeAppendPrintf(sentry
, " %-32.32s %c%c %6d %6d %2d(%2d)",
786 hashKeyStr(&i
->hash
),
787 i
->flags
.fromhosts
? 'H' : ' ',
788 i
->flags
.negcached
? 'N' : ' ',
789 (int) (squid_curtime
- i
->lastref
),
790 (int) ((i
->flags
.fromhosts
? -1 : i
->expires
- squid_curtime
)),
791 static_cast<int>(i
->addrs
.size()),
792 static_cast<int>(i
->addrs
.badCount()));
795 * Negative-cached entries have no IPs listed. */
796 if (i
->flags
.negcached
) {
797 storeAppendPrintf(sentry
, "\n");
802 * Cached entries have IPs listed with a BNF of: ip-address '-' ('OK'|'BAD') */
803 bool firstLine
= true;
804 for (const auto &addr
: i
->addrs
.raw()) {
805 /* Display tidy-up: IPv6 are so big make the list vertical */
806 const char *indent
= firstLine
? "" : " ";
807 storeAppendPrintf(sentry
, "%s %45.45s-%3s\n",
809 addr
.ip
.toStr(buf
, MAX_IPSTRLEN
),
810 addr
.bad() ? "BAD" : "OK ");
816 \ingroup IPCacheInternal
818 * process objects list
821 stat_ipcache_get(StoreEntry
* sentry
)
824 assert(ip_table
!= nullptr);
825 storeAppendPrintf(sentry
, "IP Cache Statistics:\n");
826 storeAppendPrintf(sentry
, "IPcache Entries Cached: %d\n",
828 storeAppendPrintf(sentry
, "IPcache Requests: %d\n",
829 IpcacheStats
.requests
);
830 storeAppendPrintf(sentry
, "IPcache Hits: %d\n",
832 storeAppendPrintf(sentry
, "IPcache Negative Hits: %d\n",
833 IpcacheStats
.negative_hits
);
834 storeAppendPrintf(sentry
, "IPcache Numeric Hits: %d\n",
835 IpcacheStats
.numeric_hits
);
836 storeAppendPrintf(sentry
, "IPcache Misses: %d\n",
837 IpcacheStats
.misses
);
838 storeAppendPrintf(sentry
, "IPcache Retrieved A: %d\n",
840 storeAppendPrintf(sentry
, "IPcache Retrieved AAAA: %d\n",
841 IpcacheStats
.rr_aaaa
);
842 storeAppendPrintf(sentry
, "IPcache Retrieved CNAME: %d\n",
843 IpcacheStats
.rr_cname
);
844 storeAppendPrintf(sentry
, "IPcache CNAME-Only Response: %d\n",
845 IpcacheStats
.cname_only
);
846 storeAppendPrintf(sentry
, "IPcache Invalid Request: %d\n",
847 IpcacheStats
.invalid
);
848 storeAppendPrintf(sentry
, "\n\n");
849 storeAppendPrintf(sentry
, "IP Cache Contents:\n\n");
850 storeAppendPrintf(sentry
, " %-31.31s %3s %6s %6s %4s\n",
857 for (m
= lru_list
.head
; m
; m
= m
->next
) {
858 assert( m
->next
!= m
);
859 ipcacheStatPrint((ipcache_entry
*)m
->data
, sentry
);
863 /// \ingroup IPCacheAPI
865 ipcacheInvalidate(const char *name
)
869 if ((i
= ipcache_get(name
)) == nullptr)
872 i
->expires
= squid_curtime
;
875 * NOTE, don't call ipcacheRelease here because we might be here due
876 * to a thread started from a callback.
880 /// \ingroup IPCacheAPI
882 ipcacheInvalidateNegative(const char *name
)
886 if ((i
= ipcache_get(name
)) == nullptr)
889 if (i
->flags
.negcached
)
890 i
->expires
= squid_curtime
;
893 * NOTE, don't call ipcacheRelease here because we might be here due
894 * to a thread started from a callback.
898 /// \ingroup IPCacheAPI
899 static const Dns::CachedIps
*
900 ipcacheCheckNumeric(const char *name
)
903 if (!ip
.fromHost(name
))
906 debugs(14, 4, "HIT_BYPASS for " << name
<< "=" << ip
);
907 static Dns::CachedIps static_addrs
;
908 static_addrs
.reset(ip
);
909 return &static_addrs
;
912 /// \ingroup IPCacheInternal
914 ipcacheLockEntry(ipcache_entry
* i
)
916 if (i
->locks
++ == 0) {
917 dlinkDelete(&i
->lru
, &lru_list
);
918 dlinkAdd(i
, &i
->lru
, &lru_list
);
922 /// \ingroup IPCacheInternal
924 ipcacheUnlockEntry(ipcache_entry
* i
)
927 debugs(14, DBG_IMPORTANT
, "WARNING: ipcacheEntry unlocked with no lock! locks=" << i
->locks
);
933 if (ipcacheExpiredEntry(i
))
937 /// find the next good IP, wrapping if needed
938 /// \returns whether the search was successful
940 Dns::CachedIps::seekNewGood(const char *name
)
943 for (size_t seen
= 0; seen
< ips
.size(); ++seen
) {
944 if (++goodPosition
>= ips
.size())
946 if (!ips
[goodPosition
].bad()) {
947 debugs(14, 3, "succeeded for " << name
<< ": " << *this);
951 goodPosition
= ips
.size();
952 debugs(14, 3, "failed for " << name
<< ": " << *this);
957 Dns::CachedIps::reset(const Ip::Address
&ip
)
960 ips
.emplace_back(ip
);
962 // Assume that the given IP is good because CachedIps are designed to never
963 // run out of good IPs.
967 /// makes current() calls possible after a successful markAsBad()
969 Dns::CachedIps::restoreGoodness(const char *name
)
971 if (badCount() >= size()) {
972 // There are no good IPs left. Clear all bad marks. This must help
973 // because we are called only after a good address was tested as bad.
974 for (auto &cachedIp
: ips
)
975 cachedIp
.forgetMarking();
977 debugs(14, 3, "cleared all " << size() << " bad IPs for " << name
);
978 // fall through to reset goodPosition and report the current state
980 Must(seekNewGood(name
));
984 Dns::CachedIps::have(const Ip::Address
&ip
, size_t *positionOrNil
) const
988 for (const auto &cachedIp
: ips
) {
989 if (cachedIp
.ip
== ip
) {
990 if (auto position
= positionOrNil
)
992 debugs(14, 7, ip
<< " at " << pos
<< " in " << *this);
995 ++pos
; // TODO: Replace with std::views::enumerate() after upgrading to C++23
997 // no such address; leave *position as is
998 debugs(14, 7, " no " << ip
<< " in " << *this);
1003 Dns::CachedIps::pushUnique(const Ip::Address
&ip
)
1006 [[maybe_unused
]] auto &cachedIp
= ips
.emplace_back(ip
);
1007 assert(!cachedIp
.bad());
1011 Dns::CachedIps::reportCurrent(std::ostream
&os
) const
1014 os
<< "[no cached IPs]";
1015 else if (goodPosition
== size())
1016 os
<< "[" << size() << " bad cached IPs]"; // could only be temporary
1018 os
<< current() << " #" << (goodPosition
+1) << "/" << ips
.size() << "-" << badCount();
1022 Dns::CachedIps::markAsBad(const char *name
, const Ip::Address
&ip
)
1024 size_t badPosition
= 0;
1025 if (!have(ip
, &badPosition
))
1026 return; // no such address
1028 auto &cachedIp
= ips
[badPosition
];
1030 return; // already marked correctly
1032 cachedIp
.markAsBad();
1034 debugs(14, 2, ip
<< " of " << name
);
1036 if (goodPosition
== badPosition
)
1037 restoreGoodness(name
);
1038 // else nothing to do: goodPositon still points to a good IP
1042 Dns::CachedIps::forgetMarking(const char *name
, const Ip::Address
&ip
)
1045 return; // all IPs are already "good"
1047 size_t badPosition
= 0;
1048 if (!have(ip
, &badPosition
))
1049 return; // no such address
1051 auto &cachedIp
= ips
[badPosition
];
1052 if (!cachedIp
.bad())
1053 return; // already marked correctly
1055 cachedIp
.forgetMarking();
1056 assert(!cachedIp
.bad());
1058 debugs(14, 2, ip
<< " of " << name
);
1062 * Marks the given address as BAD.
1063 * Does nothing if the domain name does not exist.
1065 \param name domain name to have an IP marked bad
1066 \param addr specific address to be marked bad
1069 ipcacheMarkBadAddr(const char *name
, const Ip::Address
&addr
)
1071 if (auto cached
= ipcache_get(name
))
1072 cached
->addrs
.markAsBad(name
, addr
);
1075 /// \ingroup IPCacheAPI
1077 ipcacheMarkGoodAddr(const char *name
, const Ip::Address
&addr
)
1079 if (auto cached
= ipcache_get(name
))
1080 cached
->addrs
.forgetMarking(name
, addr
);
1083 /// \ingroup IPCacheInternal
1085 ipcacheFreeEntry(void *data
)
1087 ipcache_entry
*i
= (ipcache_entry
*)data
;
1091 ipcache_entry::~ipcache_entry()
1093 xfree(error_message
);
1100 * Recalculate IP cache size upon reconfigure.
1101 * Is called to clear the IPCache's data structures,
1102 * cancel all pending requests.
1105 ipcache_restart(void)
1107 ipcache_high
= (long) (((float) Config
.ipcache
.size
*
1108 (float) Config
.ipcache
.high
) / (float) 100);
1109 ipcache_low
= (long) (((float) Config
.ipcache
.size
*
1110 (float) Config
.ipcache
.low
) / (float) 100);
1111 purge_entries_fromhosts();
1117 * Adds a "static" entry from /etc/hosts
1119 \param name Hostname to be linked with IP
1120 \param ipaddr IP Address to be cached.
1123 \retval 1 IP address is invalid or other error.
1126 ipcacheAddEntryFromHosts(const char *name
, const char *ipaddr
)
1132 if (!(ip
= ipaddr
)) {
1133 if (strchr(ipaddr
, ':') && strspn(ipaddr
, "0123456789abcdefABCDEF:") == strlen(ipaddr
)) {
1134 debugs(14, 3, "ipcacheAddEntryFromHosts: Skipping IPv6 address '" << ipaddr
<< "'");
1136 debugs(14, DBG_IMPORTANT
, "ERROR: ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr
<< "'");
1142 if (!Ip::EnableIpv6
&& ip
.isIPv6()) {
1143 debugs(14, 2, "skips IPv6 address in /etc/hosts because IPv6 support was disabled: " << ip
);
1147 if ((i
= ipcache_get(name
))) {
1148 if (1 == i
->flags
.fromhosts
) {
1149 ipcacheUnlockEntry(i
);
1150 } else if (i
->locks
> 0) {
1151 debugs(14, DBG_IMPORTANT
, "ERROR: ipcacheAddEntryFromHosts: cannot add static entry for locked name '" << name
<< "'");
1158 i
= new ipcache_entry(name
);
1159 i
->addrs
.pushUnique(ip
);
1160 i
->flags
.fromhosts
= true;
1162 ipcacheLockEntry(i
);
1170 * The function to return the ip cache statistics to via SNMP
1173 snmp_netIpFn(variable_list
* Var
, snint
* ErrP
)
1175 variable_list
*Answer
= nullptr;
1177 debugs(49, 5, "snmp_netIpFn: Processing request:" << snmpDebugOid(Var
->name
, Var
->name_length
, tmp
));
1178 *ErrP
= SNMP_ERR_NOERROR
;
1180 switch (Var
->name
[LEN_SQ_NET
+ 1]) {
1183 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1189 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1190 IpcacheStats
.requests
,
1195 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1201 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1207 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1208 IpcacheStats
.negative_hits
,
1213 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1214 IpcacheStats
.misses
,
1219 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1225 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1231 *ErrP
= SNMP_ERR_NOSUCHNAME
;
1238 #endif /*SQUID_SNMP */