2 * Copyright (C) 1996-2021 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"
14 #include "DebugMessages.h"
16 #include "dns/LookupDetails.h"
17 #include "dns/rfc3596.h"
19 #include "ip/Address.h"
22 #include "mgr/Registration.h"
23 #include "SquidConfig.h"
24 #include "SquidTime.h"
25 #include "StatCounters.h"
31 #include "snmp_core.h"
35 \defgroup IPCacheAPI IP Cache API
37 \section IpcacheIntroduction Introduction
39 * The IP cache is a built-in component of squid providing
40 * Hostname to IP-Number translation functionality and managing
41 * the involved data-structures. Efficiency concerns require
42 * mechanisms that allow non-blocking access to these mappings.
43 * The IP cache usually doesn't block on a request except for
44 * special cases where this is desired (see below).
48 \defgroup IPCacheInternal IP Cache Internals
50 \note when IP cache is provided as a class. These sub-groups will be obsolete
51 * for now they are used to separate the public and private functions.
52 * with the private ones all being in IPCachInternal and public in IPCacheAPI
54 \section InternalOperation Internal Operation
56 * Internally, the execution flow is as follows: On a miss,
57 * ipcache_getnbhostbyname checks whether a request for
58 * this name is already pending, and if positive, it creates
59 * a new entry using ipcacheAddNew with the IP_PENDING
60 * flag set . Then it calls ipcacheAddPending to add a
61 * request to the queue together with data and handler. Else,
62 * ipcache_dnsDispatch() is called to directly create a
63 * DNS query or to ipcacheEnqueue() if all no DNS port
64 * is free. ipcache_call_pending() is called regularly
65 * to walk down the pending list and call handlers. LRU clean-up
66 * is performed through ipcache_purgelru() according to
67 * the ipcache_high threshold.
70 /// metadata for parsing DNS A and AAAA records
71 template <class Content
>
75 typedef Content DataType
; ///< actual RR DATA type
76 const char *kind
; ///< human-friendly record type description
77 int &recordCounter
; ///< where this kind of records are counted (for stats)
80 /// forwards non-blocking IP cache lookup results to either IPH or IpReciever
81 class IpCacheLookupForwarder
84 IpCacheLookupForwarder() {}
85 explicit IpCacheLookupForwarder(const CbcPointer
<Dns::IpReceiver
> &receiver
);
86 IpCacheLookupForwarder(IPH
*fun
, void *data
);
88 /// forwards notification about the end of the lookup; last method to be called
89 void finalCallback(const Dns::CachedIps
*addrs
, const Dns::LookupDetails
&details
);
91 /// forwards an IP notification
92 /// \returns whether it may be possible to deliver more notifications
93 bool forwardIp(const Ip::Address
&ip
);
95 /// convenience wrapper to safely forwardIp() for each IP in the container
96 void forwardHits(const Dns::CachedIps
&ips
);
98 /// initialize lookup timestamps for Dns::LookupDetails delay calculation
99 void lookupsStarting() { firstLookupStart
= lastLookupEnd
= current_time
; }
101 /// inform recipient of a finished lookup
102 void forwardLookup(const char *error
);
104 /// \returns milliseconds since the first lookup start
105 int totalResponseTime() const { return tvSubMsec(firstLookupStart
, current_time
); }
108 /// \returns not yet reported lookup delay in milliseconds
109 int additionalLookupDelay() const { return tvSubMsec(lastLookupEnd
, current_time
); }
112 /* receiverObj and receiverFun are mutually exclusive */
113 CbcPointer
<Dns::IpReceiver
> receiverObj
; ///< gets incremental and final results
114 IPH
*receiverFun
= nullptr; ///< gets final results
115 CallbackData receiverData
; ///< caller-specific data for the handler (optional)
117 struct timeval firstLookupStart
{0,0}; ///< time of the idnsALookup() call
118 struct timeval lastLookupEnd
{0,0}; ///< time of the last noteLookup() call
124 * The data structure used for storing name-address mappings
125 * is a small hashtable (static hash_table *ip_table),
126 * where structures of type ipcache_entry whose most
127 * interesting members are:
131 CBDATA_CLASS(ipcache_entry
);
134 ipcache_entry(const char *);
137 hash_link hash
; /* must be first */
141 IpCacheLookupForwarder handler
;
145 unsigned short locks
;
147 Flags() : negcached(false), fromhosts(false) {}
153 bool sawCname
= false;
155 const char *name() const { return static_cast<const char*>(hash
.key
); }
157 /// milliseconds since the first lookup start or -1 if there were no lookups
158 int totalResponseTime() const;
159 /// milliseconds since the last lookup start or -1 if there were no lookups
160 int additionalLookupDelay() const;
162 /// adds the contents of a "good" DNS A or AAAA record to stored IPs
163 template <class Specs
>
164 void addGood(const rfc1035_rr
&rr
, Specs
&specs
);
166 /// remembers the last error seen, overwriting any previous errors
167 void latestError(const char *text
, const int debugLevel
= 3);
170 void updateTtl(const unsigned int rrTtl
);
173 /// \ingroup IPCacheInternal
174 static struct _ipcache_stats
{
188 /// \ingroup IPCacheInternal
189 static dlink_list lru_list
;
192 static void stat_ipcache_get(StoreEntry
*);
194 static FREE ipcacheFreeEntry
;
195 static IDNSCB ipcacheHandleReply
;
196 static int ipcacheExpiredEntry(ipcache_entry
*);
197 static ipcache_entry
*ipcache_get(const char *);
198 static void ipcacheLockEntry(ipcache_entry
*);
199 static void ipcacheStatPrint(ipcache_entry
*, StoreEntry
*);
200 static void ipcacheUnlockEntry(ipcache_entry
*);
201 static void ipcacheRelease(ipcache_entry
*, bool dofree
= true);
202 static const Dns::CachedIps
*ipcacheCheckNumeric(const char *name
);
203 static void ipcache_nbgethostbyname_(const char *name
, IpCacheLookupForwarder handler
);
205 /// \ingroup IPCacheInternal
206 static hash_table
*ip_table
= NULL
;
208 /// \ingroup IPCacheInternal
209 static long ipcache_low
= 180;
210 /// \ingroup IPCacheInternal
211 static long ipcache_high
= 200;
213 #if LIBRESOLV_DNS_TTL_HACK
214 extern int _dns_ttl_
;
217 CBDATA_CLASS_INIT(ipcache_entry
);
219 IpCacheLookupForwarder::IpCacheLookupForwarder(const CbcPointer
<Dns::IpReceiver
> &receiver
):
220 receiverObj(receiver
)
224 IpCacheLookupForwarder::IpCacheLookupForwarder(IPH
*fun
, void *data
):
225 receiverFun(fun
), receiverData(data
)
230 IpCacheLookupForwarder::finalCallback(const Dns::CachedIps
*addrs
, const Dns::LookupDetails
&details
)
232 debugs(14, 7, addrs
<< " " << details
);
233 if (receiverObj
.set()) {
234 if (auto receiver
= receiverObj
.valid())
235 receiver
->noteIps(addrs
, details
);
237 } else if (receiverFun
) {
238 if (receiverData
.valid()) {
239 const Dns::CachedIps
*emptyIsNil
= (addrs
&& !addrs
->empty()) ? addrs
: nullptr;
240 receiverFun(emptyIsNil
, details
, receiverData
.validDone());
242 receiverFun
= nullptr;
246 /// forwards an IP notification
247 /// \returns whether it may be possible to deliver more notifications
249 IpCacheLookupForwarder::forwardIp(const Ip::Address
&ip
)
252 if (receiverObj
.set()) {
253 if (auto receiver
= receiverObj
.valid()) {
254 receiver
->noteIp(ip
);
259 // else do nothing: ReceiverFun does not do incremental notifications
263 /// convenience wrapper to safely forwardIp() for each IP in the container
265 IpCacheLookupForwarder::forwardHits(const Dns::CachedIps
&ips
)
267 if (receiverObj
.set()) {
268 for (const auto &ip
: ips
.good()) {
270 break; // receiver gone
273 // else do nothing: ReceiverFun does not do incremental notifications
277 IpCacheLookupForwarder::forwardLookup(const char *error
)
279 // Lookups run concurrently, but HttpRequest::recordLookup() thinks they
280 // are sequential. Give it just the new, yet-unaccounted-for delay.
281 if (receiverObj
.set()) {
282 if (auto receiver
= receiverObj
.valid()) {
283 receiver
->noteLookup(Dns::LookupDetails(error
, additionalLookupDelay()));
284 lastLookupEnd
= current_time
;
287 // else do nothing: ReceiverFun gets no individual lookup notifications
290 /// \ingroup IPCacheInternal
291 inline int ipcacheCount() { return ip_table
? ip_table
->count
: 0; }
294 \ingroup IPCacheInternal
296 * removes the given ipcache entry
299 ipcacheRelease(ipcache_entry
* i
, bool dofree
)
302 debugs(14, DBG_CRITICAL
, "ipcacheRelease: Releasing entry with i=<NULL>");
306 if (!i
|| !i
->hash
.key
) {
307 debugs(14, DBG_CRITICAL
, "ipcacheRelease: Releasing entry without hash link!");
311 debugs(14, 3, "ipcacheRelease: Releasing entry for '" << (const char *) i
->hash
.key
<< "'");
313 hash_remove_link(ip_table
, (hash_link
*) i
);
314 dlinkDelete(&i
->lru
, &lru_list
);
319 /// \ingroup IPCacheInternal
320 static ipcache_entry
*
321 ipcache_get(const char *name
)
323 if (ip_table
!= NULL
)
324 return (ipcache_entry
*) hash_lookup(ip_table
, name
);
329 /// \ingroup IPCacheInternal
331 ipcacheExpiredEntry(ipcache_entry
* i
)
333 /* all static entries are locked, so this takes care of them too */
338 if (i
->addrs
.empty())
339 if (0 == i
->flags
.negcached
)
342 if (i
->expires
> squid_curtime
)
348 /// \ingroup IPCacheAPI
350 ipcache_purgelru(void *)
353 dlink_node
*prev
= NULL
;
356 eventAdd("ipcache_purgelru", ipcache_purgelru
, NULL
, 10.0, 1);
358 for (m
= lru_list
.tail
; m
; m
= prev
) {
359 if (ipcacheCount() < ipcache_low
)
364 i
= (ipcache_entry
*)m
->data
;
374 debugs(14, 9, "ipcache_purgelru: removed " << removed
<< " entries");
378 \ingroup IPCacheInternal
380 * purges entries added from /etc/hosts (or whatever).
383 purge_entries_fromhosts(void)
385 dlink_node
*m
= lru_list
.head
;
386 ipcache_entry
*i
= NULL
, *t
;
389 if (i
!= NULL
) { /* need to delay deletion */
390 ipcacheRelease(i
); /* we just override locks */
394 t
= (ipcache_entry
*)m
->data
;
396 if (t
->flags
.fromhosts
)
406 ipcache_entry::ipcache_entry(const char *aName
):
409 error_message(nullptr),
410 locks(0) // XXX: use Lock type ?
412 hash
.key
= xstrdup(aName
);
413 Tolower(static_cast<char*>(hash
.key
));
414 expires
= squid_curtime
+ Config
.negativeDnsTtl
;
417 /// \ingroup IPCacheInternal
419 ipcacheAddEntry(ipcache_entry
* i
)
421 hash_link
*e
= (hash_link
*)hash_lookup(ip_table
, i
->hash
.key
);
424 /* avoid collision */
425 ipcache_entry
*q
= (ipcache_entry
*) e
;
429 hash_join(ip_table
, &i
->hash
);
430 dlinkAdd(i
, &i
->lru
, &lru_list
);
431 i
->lastref
= squid_curtime
;
435 \ingroup IPCacheInternal
437 * walks down the pending list, calling handlers
440 ipcacheCallback(ipcache_entry
*i
, const bool hit
, const int wait
)
442 i
->lastref
= squid_curtime
;
447 i
->handler
.forwardHits(i
->addrs
);
448 const Dns::LookupDetails
details(i
->error_message
, wait
);
449 i
->handler
.finalCallback(&i
->addrs
, details
);
451 ipcacheUnlockEntry(i
);
455 ipcache_entry::latestError(const char *text
, const int debugLevel
)
457 debugs(14, debugLevel
, "DNS error while resolving " << name() << ": " << text
);
458 safe_free(error_message
);
459 error_message
= xstrdup(text
);
463 ipcacheParse(ipcache_entry
*i
, const rfc1035_rr
* answers
, int nr
, const char *error_message
)
467 // XXX: Callers use zero ancount instead of -1 on errors!
469 i
->latestError(error_message
);
474 i
->latestError("No DNS records");
478 debugs(14, 3, nr
<< " answers for " << i
->name());
481 for (k
= 0; k
< nr
; ++k
) {
483 if (Ip::EnableIpv6
&& answers
[k
].type
== RFC1035_TYPE_AAAA
) {
484 static const RrSpecs
<struct in6_addr
> QuadA
= { "IPv6", IpcacheStats
.rr_aaaa
};
485 i
->addGood(answers
[k
], QuadA
);
489 if (answers
[k
].type
== RFC1035_TYPE_A
) {
490 static const RrSpecs
<struct in_addr
> SingleA
= { "IPv4", IpcacheStats
.rr_a
};
491 i
->addGood(answers
[k
], SingleA
);
495 /* With A and AAAA, the CNAME does not necessarily come with additional records to use. */
496 if (answers
[k
].type
== RFC1035_TYPE_CNAME
) {
498 ++IpcacheStats
.rr_cname
;
502 // otherwise its an unknown RR. debug at level 9 since we usually want to ignore these and they are common.
503 debugs(14, 9, "Unknown RR type received: type=" << answers
[k
].type
<< " starting at " << &(answers
[k
]) );
507 template <class Specs
>
509 ipcache_entry::addGood(const rfc1035_rr
&rr
, Specs
&specs
)
511 typename
Specs::DataType address
;
512 if (rr
.rdlength
!= sizeof(address
)) {
513 debugs(14, DBG_IMPORTANT
, "ERROR: Ignoring invalid " << specs
.kind
<< " address record while resolving " << name());
517 ++specs
.recordCounter
;
519 // Do not store more than 255 addresses (TODO: Why?)
520 if (addrs
.raw().size() >= 255)
523 memcpy(&address
, rr
.rdata
, sizeof(address
));
524 const Ip::Address ip
= address
;
525 if (addrs
.have(ip
)) {
526 debugs(14, 3, "refusing to add duplicate " << ip
);
529 addrs
.pushUnique(address
);
533 debugs(14, 3, name() << " #" << addrs
.size() << " " << ip
);
534 handler
.forwardIp(ip
); // we are only called with good IPs
538 ipcache_entry::updateTtl(const unsigned int rrTtl
)
540 const time_t ttl
= std::min(std::max(
541 Config
.negativeDnsTtl
, // smallest value allowed
542 static_cast<time_t>(rrTtl
)),
543 Config
.positiveDnsTtl
); // largest value allowed
545 const time_t rrExpires
= squid_curtime
+ ttl
;
546 if (rrExpires
< expires
)
550 /// \ingroup IPCacheInternal
552 ipcacheHandleReply(void *data
, const rfc1035_rr
* answers
, int na
, const char *error_message
, const bool lastAnswer
)
554 ipcache_entry
*i
= static_cast<ipcache_entry
*>(data
);
556 i
->handler
.forwardLookup(error_message
);
557 ipcacheParse(i
, answers
, na
, error_message
);
562 ++IpcacheStats
.replies
;
563 const auto age
= i
->handler
.totalResponseTime();
564 statCounter
.dns
.svcTime
.count(age
);
566 if (i
->addrs
.empty()) {
567 i
->flags
.negcached
= true;
568 i
->expires
= squid_curtime
+ Config
.negativeDnsTtl
;
570 if (!i
->error_message
) {
571 i
->latestError("No valid address records", DBG_IMPORTANT
);
573 ++IpcacheStats
.cname_only
;
577 debugs(14, 3, "done with " << i
->name() << ": " << i
->addrs
);
579 ipcacheCallback(i
, false, age
);
585 \param name Host to resolve.
586 \param handler Pointer to the function to be called when the reply
587 * from the IP cache (or the DNS if the IP cache misses)
588 \param handlerData Information that is passed to the handler and does not affect the IP cache.
590 * XXX: on hits and some errors, the handler is called immediately instead
591 * of scheduling an async call. This reentrant behavior means that the
592 * user job must be extra careful after calling ipcache_nbgethostbyname,
593 * especially if the handler destroys the job. Moreover, the job has
594 * no way of knowing whether the reentrant call happened.
595 * Comm::Connection setup usually protects the job by scheduling an async call,
596 * but some user code calls ipcache_nbgethostbyname directly.
599 ipcache_nbgethostbyname(const char *name
, IPH
* handler
, void *handlerData
)
602 ipcache_nbgethostbyname_(name
, IpCacheLookupForwarder(handler
, handlerData
));
606 Dns::nbgethostbyname(const char *name
, const CbcPointer
<IpReceiver
> &receiver
)
609 ipcache_nbgethostbyname_(name
, IpCacheLookupForwarder(receiver
));
612 /// implements ipcache_nbgethostbyname() and Dns::nbgethostbyname() APIs
614 ipcache_nbgethostbyname_(const char *name
, IpCacheLookupForwarder handler
)
616 ipcache_entry
*i
= NULL
;
617 const ipcache_addrs
*addrs
= NULL
;
618 ++IpcacheStats
.requests
;
620 if (name
== NULL
|| name
[0] == '\0') {
621 debugs(14, 4, "ipcache_nbgethostbyname: Invalid name!");
622 ++IpcacheStats
.invalid
;
623 const Dns::LookupDetails
details("Invalid hostname", -1); // error, no lookup
624 handler
.finalCallback(nullptr, details
);
628 if ((addrs
= ipcacheCheckNumeric(name
))) {
629 debugs(14, 4, "ipcache_nbgethostbyname: BYPASS for '" << name
<< "' (already numeric)");
630 handler
.forwardHits(*addrs
);
631 ++IpcacheStats
.numeric_hits
;
632 const Dns::LookupDetails details
; // no error, no lookup
633 handler
.finalCallback(addrs
, details
);
637 i
= ipcache_get(name
);
642 } else if (ipcacheExpiredEntry(i
)) {
643 /* hit, but expired -- bummer */
648 debugs(14, 4, "ipcache_nbgethostbyname: HIT for '" << name
<< "'");
650 if (i
->flags
.negcached
)
651 ++IpcacheStats
.negative_hits
;
655 i
->handler
= std::move(handler
);
656 ipcacheCallback(i
, true, -1); // no lookup
661 debugs(14, 5, "ipcache_nbgethostbyname: MISS for '" << name
<< "'");
662 ++IpcacheStats
.misses
;
663 i
= new ipcache_entry(name
);
664 i
->handler
= std::move(handler
);
665 i
->handler
.lookupsStarting();
666 idnsALookup(hashKeyStr(&i
->hash
), ipcacheHandleReply
, i
);
669 /// \ingroup IPCacheInternal
671 ipcacheRegisterWithCacheManager(void)
673 Mgr::RegisterAction("ipcache",
674 "IP Cache Stats and Contents",
675 stat_ipcache_get
, 0, 1);
681 * Initialize the ipcache.
682 * Is called from mainInitialize() after disk initialization
683 * and prior to the reverse FQDNCache initialization
689 debugs(14, Important(24), "Initializing IP Cache...");
690 memset(&IpcacheStats
, '\0', sizeof(IpcacheStats
));
691 lru_list
= dlink_list();
693 ipcache_high
= (long) (((float) Config
.ipcache
.size
*
694 (float) Config
.ipcache
.high
) / (float) 100);
695 ipcache_low
= (long) (((float) Config
.ipcache
.size
*
696 (float) Config
.ipcache
.low
) / (float) 100);
697 n
= hashPrime(ipcache_high
/ 4);
698 ip_table
= hash_create((HASHCMP
*) strcmp
, n
, hash4
);
700 ipcacheRegisterWithCacheManager();
706 * Is different from ipcache_nbgethostbyname in that it only checks
707 * if an entry exists in the cache and does not by default contact the DNS,
708 * unless this is requested, by setting the flags.
710 \param name Host name to resolve.
711 \param flags Default is NULL, set to IP_LOOKUP_IF_MISS
712 * to explicitly perform DNS lookups.
714 \retval NULL An error occurred during lookup
715 \retval NULL No results available in cache and no lookup specified
716 \retval * Pointer to the ipcahce_addrs structure containing the lookup results
718 const ipcache_addrs
*
719 ipcache_gethostbyname(const char *name
, int flags
)
721 ipcache_entry
*i
= NULL
;
723 debugs(14, 3, "ipcache_gethostbyname: '" << name
<< "', flags=" << std::hex
<< flags
);
724 ++IpcacheStats
.requests
;
725 i
= ipcache_get(name
);
729 } else if (ipcacheExpiredEntry(i
)) {
732 } else if (i
->flags
.negcached
) {
733 ++IpcacheStats
.negative_hits
;
734 // ignore i->error_message: the caller just checks IP cache presence
738 i
->lastref
= squid_curtime
;
739 // ignore i->error_message: the caller just checks IP cache presence
743 /* no entry [any more] */
745 if (const auto addrs
= ipcacheCheckNumeric(name
)) {
746 ++IpcacheStats
.numeric_hits
;
750 ++IpcacheStats
.misses
;
752 if (flags
& IP_LOOKUP_IF_MISS
)
753 ipcache_nbgethostbyname(name
, NULL
, NULL
);
758 /// \ingroup IPCacheInternal
760 ipcacheStatPrint(ipcache_entry
* i
, StoreEntry
* sentry
)
762 char buf
[MAX_IPSTRLEN
];
765 debugs(14, DBG_CRITICAL
, HERE
<< "CRITICAL: sentry is NULL!");
770 debugs(14, DBG_CRITICAL
, HERE
<< "CRITICAL: ipcache_entry is NULL!");
771 storeAppendPrintf(sentry
, "CRITICAL ERROR\n");
775 storeAppendPrintf(sentry
, " %-32.32s %c%c %6d %6d %2d(%2d)",
776 hashKeyStr(&i
->hash
),
777 i
->flags
.fromhosts
? 'H' : ' ',
778 i
->flags
.negcached
? 'N' : ' ',
779 (int) (squid_curtime
- i
->lastref
),
780 (int) ((i
->flags
.fromhosts
? -1 : i
->expires
- squid_curtime
)),
781 static_cast<int>(i
->addrs
.size()),
782 static_cast<int>(i
->addrs
.badCount()));
785 * Negative-cached entries have no IPs listed. */
786 if (i
->flags
.negcached
) {
787 storeAppendPrintf(sentry
, "\n");
792 * Cached entries have IPs listed with a BNF of: ip-address '-' ('OK'|'BAD') */
793 bool firstLine
= true;
794 for (const auto &addr
: i
->addrs
.raw()) {
795 /* Display tidy-up: IPv6 are so big make the list vertical */
796 const char *indent
= firstLine
? "" : " ";
797 storeAppendPrintf(sentry
, "%s %45.45s-%3s\n",
799 addr
.ip
.toStr(buf
, MAX_IPSTRLEN
),
800 addr
.bad() ? "BAD" : "OK ");
806 \ingroup IPCacheInternal
808 * process objects list
811 stat_ipcache_get(StoreEntry
* sentry
)
814 assert(ip_table
!= NULL
);
815 storeAppendPrintf(sentry
, "IP Cache Statistics:\n");
816 storeAppendPrintf(sentry
, "IPcache Entries Cached: %d\n",
818 storeAppendPrintf(sentry
, "IPcache Requests: %d\n",
819 IpcacheStats
.requests
);
820 storeAppendPrintf(sentry
, "IPcache Hits: %d\n",
822 storeAppendPrintf(sentry
, "IPcache Negative Hits: %d\n",
823 IpcacheStats
.negative_hits
);
824 storeAppendPrintf(sentry
, "IPcache Numeric Hits: %d\n",
825 IpcacheStats
.numeric_hits
);
826 storeAppendPrintf(sentry
, "IPcache Misses: %d\n",
827 IpcacheStats
.misses
);
828 storeAppendPrintf(sentry
, "IPcache Retrieved A: %d\n",
830 storeAppendPrintf(sentry
, "IPcache Retrieved AAAA: %d\n",
831 IpcacheStats
.rr_aaaa
);
832 storeAppendPrintf(sentry
, "IPcache Retrieved CNAME: %d\n",
833 IpcacheStats
.rr_cname
);
834 storeAppendPrintf(sentry
, "IPcache CNAME-Only Response: %d\n",
835 IpcacheStats
.cname_only
);
836 storeAppendPrintf(sentry
, "IPcache Invalid Request: %d\n",
837 IpcacheStats
.invalid
);
838 storeAppendPrintf(sentry
, "\n\n");
839 storeAppendPrintf(sentry
, "IP Cache Contents:\n\n");
840 storeAppendPrintf(sentry
, " %-31.31s %3s %6s %6s %4s\n",
847 for (m
= lru_list
.head
; m
; m
= m
->next
) {
848 assert( m
->next
!= m
);
849 ipcacheStatPrint((ipcache_entry
*)m
->data
, sentry
);
853 /// \ingroup IPCacheAPI
855 ipcacheInvalidate(const char *name
)
859 if ((i
= ipcache_get(name
)) == NULL
)
862 i
->expires
= squid_curtime
;
865 * NOTE, don't call ipcacheRelease here because we might be here due
866 * to a thread started from a callback.
870 /// \ingroup IPCacheAPI
872 ipcacheInvalidateNegative(const char *name
)
876 if ((i
= ipcache_get(name
)) == NULL
)
879 if (i
->flags
.negcached
)
880 i
->expires
= squid_curtime
;
883 * NOTE, don't call ipcacheRelease here because we might be here due
884 * to a thread started from a callback.
888 /// \ingroup IPCacheAPI
889 static const Dns::CachedIps
*
890 ipcacheCheckNumeric(const char *name
)
893 if (!ip
.fromHost(name
))
896 debugs(14, 4, "HIT_BYPASS for " << name
<< "=" << ip
);
897 static Dns::CachedIps static_addrs
;
898 static_addrs
.reset(ip
);
899 return &static_addrs
;
902 /// \ingroup IPCacheInternal
904 ipcacheLockEntry(ipcache_entry
* i
)
906 if (i
->locks
++ == 0) {
907 dlinkDelete(&i
->lru
, &lru_list
);
908 dlinkAdd(i
, &i
->lru
, &lru_list
);
912 /// \ingroup IPCacheInternal
914 ipcacheUnlockEntry(ipcache_entry
* i
)
917 debugs(14, DBG_IMPORTANT
, "WARNING: ipcacheEntry unlocked with no lock! locks=" << i
->locks
);
923 if (ipcacheExpiredEntry(i
))
927 /// find the next good IP, wrapping if needed
928 /// \returns whether the search was successful
930 Dns::CachedIps::seekNewGood(const char *name
)
933 for (size_t seen
= 0; seen
< ips
.size(); ++seen
) {
934 if (++goodPosition
>= ips
.size())
936 if (!ips
[goodPosition
].bad()) {
937 debugs(14, 3, "succeeded for " << name
<< ": " << *this);
941 goodPosition
= ips
.size();
942 debugs(14, 3, "failed for " << name
<< ": " << *this);
947 Dns::CachedIps::reset(const Ip::Address
&ip
)
950 ips
.emplace_back(ip
);
952 // Assume that the given IP is good because CachedIps are designed to never
953 // run out of good IPs.
957 /// makes current() calls possible after a successful markAsBad()
959 Dns::CachedIps::restoreGoodness(const char *name
)
961 if (badCount() >= size()) {
962 // There are no good IPs left. Clear all bad marks. This must help
963 // because we are called only after a good address was tested as bad.
964 for (auto &cachedIp
: ips
)
965 cachedIp
.forgetMarking();
968 Must(seekNewGood(name
));
969 debugs(14, 3, "cleared all IPs for " << name
<< "; now back to " << *this);
973 Dns::CachedIps::have(const Ip::Address
&ip
, size_t *positionOrNil
) const
977 for (const auto &cachedIp
: ips
) {
978 if (cachedIp
.ip
== ip
) {
979 if (auto position
= positionOrNil
)
981 debugs(14, 7, ip
<< " at " << pos
<< " in " << *this);
985 // no such address; leave *position as is
986 debugs(14, 7, " no " << ip
<< " in " << *this);
991 Dns::CachedIps::pushUnique(const Ip::Address
&ip
)
994 ips
.emplace_back(ip
);
995 assert(!raw().back().bad());
999 Dns::CachedIps::reportCurrent(std::ostream
&os
) const
1002 os
<< "[no cached IPs]";
1003 else if (goodPosition
== size())
1004 os
<< "[" << size() << " bad cached IPs]"; // could only be temporary
1006 os
<< current() << " #" << (goodPosition
+1) << "/" << ips
.size() << "-" << badCount();
1010 Dns::CachedIps::markAsBad(const char *name
, const Ip::Address
&ip
)
1012 size_t badPosition
= 0;
1013 if (!have(ip
, &badPosition
))
1014 return; // no such address
1016 auto &cachedIp
= ips
[badPosition
];
1018 return; // already marked correctly
1020 cachedIp
.markAsBad();
1022 debugs(14, 2, ip
<< " of " << name
);
1024 if (goodPosition
== badPosition
)
1025 restoreGoodness(name
);
1026 // else nothing to do: goodPositon still points to a good IP
1030 Dns::CachedIps::forgetMarking(const char *name
, const Ip::Address
&ip
)
1033 return; // all IPs are already "good"
1035 size_t badPosition
= 0;
1036 if (!have(ip
, &badPosition
))
1037 return; // no such address
1039 auto &cachedIp
= ips
[badPosition
];
1040 if (!cachedIp
.bad())
1041 return; // already marked correctly
1043 cachedIp
.forgetMarking();
1044 assert(!cachedIp
.bad());
1046 debugs(14, 2, ip
<< " of " << name
);
1050 * Marks the given address as BAD.
1051 * Does nothing if the domain name does not exist.
1053 \param name domain name to have an IP marked bad
1054 \param addr specific address to be marked bad
1057 ipcacheMarkBadAddr(const char *name
, const Ip::Address
&addr
)
1059 if (auto cached
= ipcache_get(name
))
1060 cached
->addrs
.markAsBad(name
, addr
);
1063 /// \ingroup IPCacheAPI
1065 ipcacheMarkGoodAddr(const char *name
, const Ip::Address
&addr
)
1067 if (auto cached
= ipcache_get(name
))
1068 cached
->addrs
.forgetMarking(name
, addr
);
1071 /// \ingroup IPCacheInternal
1073 ipcacheFreeEntry(void *data
)
1075 ipcache_entry
*i
= (ipcache_entry
*)data
;
1079 ipcache_entry::~ipcache_entry()
1081 xfree(error_message
);
1085 /// \ingroup IPCacheAPI
1087 ipcacheFreeMemory(void)
1089 hashFreeItems(ip_table
, ipcacheFreeEntry
);
1090 hashFreeMemory(ip_table
);
1097 * Recalculate IP cache size upon reconfigure.
1098 * Is called to clear the IPCache's data structures,
1099 * cancel all pending requests.
1102 ipcache_restart(void)
1104 ipcache_high
= (long) (((float) Config
.ipcache
.size
*
1105 (float) Config
.ipcache
.high
) / (float) 100);
1106 ipcache_low
= (long) (((float) Config
.ipcache
.size
*
1107 (float) Config
.ipcache
.low
) / (float) 100);
1108 purge_entries_fromhosts();
1114 * Adds a "static" entry from /etc/hosts
1116 \param name Hostname to be linked with IP
1117 \param ipaddr IP Address to be cached.
1120 \retval 1 IP address is invalid or other error.
1123 ipcacheAddEntryFromHosts(const char *name
, const char *ipaddr
)
1129 if (!(ip
= ipaddr
)) {
1130 if (strchr(ipaddr
, ':') && strspn(ipaddr
, "0123456789abcdefABCDEF:") == strlen(ipaddr
)) {
1131 debugs(14, 3, "ipcacheAddEntryFromHosts: Skipping IPv6 address '" << ipaddr
<< "'");
1133 debugs(14, DBG_IMPORTANT
, "ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr
<< "'");
1139 if (!Ip::EnableIpv6
&& ip
.isIPv6()) {
1140 debugs(14, 2, "skips IPv6 address in /etc/hosts because IPv6 support was disabled: " << ip
);
1144 if ((i
= ipcache_get(name
))) {
1145 if (1 == i
->flags
.fromhosts
) {
1146 ipcacheUnlockEntry(i
);
1147 } else if (i
->locks
> 0) {
1148 debugs(14, DBG_IMPORTANT
, "ipcacheAddEntryFromHosts: can't add static entry for locked name '" << name
<< "'");
1155 i
= new ipcache_entry(name
);
1156 i
->addrs
.pushUnique(ip
);
1157 i
->flags
.fromhosts
= true;
1159 ipcacheLockEntry(i
);
1167 * The function to return the ip cache statistics to via SNMP
1170 snmp_netIpFn(variable_list
* Var
, snint
* ErrP
)
1172 variable_list
*Answer
= NULL
;
1174 debugs(49, 5, "snmp_netIpFn: Processing request:" << snmpDebugOid(Var
->name
, Var
->name_length
, tmp
));
1175 *ErrP
= SNMP_ERR_NOERROR
;
1177 switch (Var
->name
[LEN_SQ_NET
+ 1]) {
1180 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1186 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1187 IpcacheStats
.requests
,
1192 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1198 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1204 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1205 IpcacheStats
.negative_hits
,
1210 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1211 IpcacheStats
.misses
,
1216 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1222 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
1228 *ErrP
= SNMP_ERR_NOSUCHNAME
;
1229 snmp_var_free(Answer
);
1236 #endif /*SQUID_SNMP */