]>
Commit | Line | Data |
---|---|---|
30a4f2a8 | 1 | /* |
b8ae064d | 2 | * Copyright (C) 1996-2023 The Squid Software Foundation and contributors |
e25c139f | 3 | * |
bbc27441 AJ |
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. | |
019dd986 | 7 | */ |
44a47c6e | 8 | |
bbc27441 AJ |
9 | /* DEBUG: section 14 IP Cache */ |
10 | ||
582c2af2 | 11 | #include "squid.h" |
a4dd5bfa | 12 | #include "base/IoManip.h" |
62ee09ca | 13 | #include "CacheManager.h" |
602d9612 | 14 | #include "cbdata.h" |
675b8408 | 15 | #include "debug/Messages.h" |
17852883 | 16 | #include "dlink.h" |
4a3b98d7 AJ |
17 | #include "dns/LookupDetails.h" |
18 | #include "dns/rfc3596.h" | |
cfd66529 AJ |
19 | #include "event.h" |
20 | #include "ip/Address.h" | |
055421ee | 21 | #include "ip/tools.h" |
714e68b7 | 22 | #include "ipcache.h" |
8822ebee | 23 | #include "mgr/Registration.h" |
8b082ed9 | 24 | #include "snmp_agent.h" |
4d5904f7 | 25 | #include "SquidConfig.h" |
e4f1fdae | 26 | #include "StatCounters.h" |
e6ccf245 | 27 | #include "Store.h" |
ed6e9fb9 | 28 | #include "util.h" |
d295d770 | 29 | #include "wordlist.h" |
44a47c6e | 30 | |
9c0a2256 FC |
31 | #if SQUID_SNMP |
32 | #include "snmp_core.h" | |
33 | #endif | |
34 | ||
63be0a78 | 35 | /** |
36 | \defgroup IPCacheAPI IP Cache API | |
37 | \ingroup Components | |
f439fbd2 | 38 | \section IpcacheIntroduction Introduction |
63be0a78 | 39 | \par |
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). | |
63be0a78 | 46 | */ |
47 | ||
48 | /** | |
49 | \defgroup IPCacheInternal IP Cache Internals | |
50 | \ingroup IPCacheAPI | |
9837567d | 51 | \note when IP cache is provided as a class. These sub-groups will be obsolete |
2b61af8e | 52 | * for now they are used to separate the public and private functions. |
f53969cc | 53 | * with the private ones all being in IPCachInternal and public in IPCacheAPI |
63be0a78 | 54 | * |
55 | \section InternalOperation Internal Operation | |
56 | * | |
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. | |
69 | */ | |
70 | ||
fd9c47d1 AR |
71 | /// metadata for parsing DNS A and AAAA records |
72 | template <class Content> | |
73 | class RrSpecs | |
74 | { | |
75 | public: | |
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) | |
79 | }; | |
80 | ||
81 | /// forwards non-blocking IP cache lookup results to either IPH or IpReciever | |
82 | class IpCacheLookupForwarder | |
83 | { | |
84 | public: | |
85 | IpCacheLookupForwarder() {} | |
86 | explicit IpCacheLookupForwarder(const CbcPointer<Dns::IpReceiver> &receiver); | |
87 | IpCacheLookupForwarder(IPH *fun, void *data); | |
88 | ||
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); | |
91 | ||
92 | /// forwards an IP notification | |
93 | /// \returns whether it may be possible to deliver more notifications | |
94 | bool forwardIp(const Ip::Address &ip); | |
95 | ||
96 | /// convenience wrapper to safely forwardIp() for each IP in the container | |
97 | void forwardHits(const Dns::CachedIps &ips); | |
98 | ||
99 | /// initialize lookup timestamps for Dns::LookupDetails delay calculation | |
100 | void lookupsStarting() { firstLookupStart = lastLookupEnd = current_time; } | |
101 | ||
102 | /// inform recipient of a finished lookup | |
103 | void forwardLookup(const char *error); | |
104 | ||
105 | /// \returns milliseconds since the first lookup start | |
106 | int totalResponseTime() const { return tvSubMsec(firstLookupStart, current_time); } | |
107 | ||
108 | protected: | |
109 | /// \returns not yet reported lookup delay in milliseconds | |
110 | int additionalLookupDelay() const { return tvSubMsec(lastLookupEnd, current_time); } | |
111 | ||
112 | private: | |
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) | |
117 | ||
118 | struct timeval firstLookupStart {0,0}; ///< time of the idnsALookup() call | |
119 | struct timeval lastLookupEnd {0,0}; ///< time of the last noteLookup() call | |
120 | }; | |
121 | ||
63be0a78 | 122 | /** |
123 | \ingroup IPCacheAPI | |
124 | * | |
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: | |
129 | */ | |
e1381638 AJ |
130 | class ipcache_entry |
131 | { | |
fd9c47d1 | 132 | CBDATA_CLASS(ipcache_entry); |
3c670b50 | 133 | |
3ff65596 | 134 | public: |
3c670b50 AJ |
135 | ipcache_entry(const char *); |
136 | ~ipcache_entry(); | |
137 | ||
f53969cc | 138 | hash_link hash; /* must be first */ |
ecc3091b | 139 | time_t lastref; |
140 | time_t expires; | |
141 | ipcache_addrs addrs; | |
fd9c47d1 | 142 | IpCacheLookupForwarder handler; |
ecc3091b | 143 | char *error_message; |
62e76326 | 144 | |
ecc3091b | 145 | dlink_node lru; |
ac06f720 | 146 | unsigned short locks; |
3c670b50 AJ |
147 | struct Flags { |
148 | Flags() : negcached(false), fromhosts(false) {} | |
149 | ||
be4d35dc FC |
150 | bool negcached; |
151 | bool fromhosts; | |
2fadd50d | 152 | } flags; |
3ff65596 | 153 | |
fd9c47d1 AR |
154 | bool sawCname = false; |
155 | ||
156 | const char *name() const { return static_cast<const char*>(hash.key); } | |
157 | ||
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; | |
162 | ||
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); | |
166 | ||
167 | /// remembers the last error seen, overwriting any previous errors | |
7efc7a7b | 168 | void latestError(const char *text); |
fd9c47d1 AR |
169 | |
170 | protected: | |
171 | void updateTtl(const unsigned int rrTtl); | |
ecc3091b | 172 | }; |
173 | ||
63be0a78 | 174 | /// \ingroup IPCacheInternal |
26ac0430 | 175 | static struct _ipcache_stats { |
30a4f2a8 | 176 | int requests; |
f88bb09c | 177 | int replies; |
30a4f2a8 | 178 | int hits; |
179 | int misses; | |
30a4f2a8 | 180 | int negative_hits; |
22b245f8 | 181 | int numeric_hits; |
bae9832d | 182 | int rr_a; |
183 | int rr_aaaa; | |
184 | int rr_cname; | |
185 | int cname_only; | |
22b245f8 | 186 | int invalid; |
2fadd50d | 187 | } IpcacheStats; |
090089c4 | 188 | |
63be0a78 | 189 | /// \ingroup IPCacheInternal |
ce75f381 | 190 | static dlink_list lru_list; |
7b04dad5 | 191 | |
59a09b98 FC |
192 | // forward-decls |
193 | static void stat_ipcache_get(StoreEntry *); | |
194 | ||
74addf6c | 195 | static FREE ipcacheFreeEntry; |
7b724b86 | 196 | static IDNSCB ipcacheHandleReply; |
74addf6c | 197 | static int ipcacheExpiredEntry(ipcache_entry *); |
f5b8bbc4 | 198 | static ipcache_entry *ipcache_get(const char *); |
74addf6c | 199 | static void ipcacheLockEntry(ipcache_entry *); |
f5b8bbc4 | 200 | static void ipcacheStatPrint(ipcache_entry *, StoreEntry *); |
201 | static void ipcacheUnlockEntry(ipcache_entry *); | |
cc192b50 | 202 | static void ipcacheRelease(ipcache_entry *, bool dofree = true); |
fd9c47d1 AR |
203 | static const Dns::CachedIps *ipcacheCheckNumeric(const char *name); |
204 | static void ipcache_nbgethostbyname_(const char *name, IpCacheLookupForwarder handler); | |
30a4f2a8 | 205 | |
63be0a78 | 206 | /// \ingroup IPCacheInternal |
aee3523a | 207 | static hash_table *ip_table = nullptr; |
090089c4 | 208 | |
63be0a78 | 209 | /// \ingroup IPCacheInternal |
24382924 | 210 | static long ipcache_low = 180; |
63be0a78 | 211 | /// \ingroup IPCacheInternal |
24382924 | 212 | static long ipcache_high = 200; |
f88bb09c | 213 | |
c021888f | 214 | #if LIBRESOLV_DNS_TTL_HACK |
215 | extern int _dns_ttl_; | |
216 | #endif | |
217 | ||
fd9c47d1 | 218 | CBDATA_CLASS_INIT(ipcache_entry); |
ac49890a | 219 | |
fd9c47d1 AR |
220 | IpCacheLookupForwarder::IpCacheLookupForwarder(const CbcPointer<Dns::IpReceiver> &receiver): |
221 | receiverObj(receiver) | |
222 | { | |
223 | } | |
224 | ||
225 | IpCacheLookupForwarder::IpCacheLookupForwarder(IPH *fun, void *data): | |
226 | receiverFun(fun), receiverData(data) | |
3ff65596 | 227 | { |
3ff65596 AR |
228 | } |
229 | ||
fd9c47d1 | 230 | void |
cef32d3e | 231 | IpCacheLookupForwarder::finalCallback(const Dns::CachedIps * const possiblyEmptyAddrs, const Dns::LookupDetails &details) |
fd9c47d1 | 232 | { |
cef32d3e AR |
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; | |
236 | ||
fd9c47d1 AR |
237 | debugs(14, 7, addrs << " " << details); |
238 | if (receiverObj.set()) { | |
239 | if (auto receiver = receiverObj.valid()) | |
240 | receiver->noteIps(addrs, details); | |
241 | receiverObj.clear(); | |
242 | } else if (receiverFun) { | |
cef32d3e AR |
243 | if (receiverData.valid()) |
244 | receiverFun(addrs, details, receiverData.validDone()); | |
fd9c47d1 AR |
245 | receiverFun = nullptr; |
246 | } | |
247 | } | |
248 | ||
249 | /// forwards an IP notification | |
250 | /// \returns whether it may be possible to deliver more notifications | |
251 | bool | |
252 | IpCacheLookupForwarder::forwardIp(const Ip::Address &ip) | |
253 | { | |
254 | debugs(14, 7, ip); | |
255 | if (receiverObj.set()) { | |
256 | if (auto receiver = receiverObj.valid()) { | |
257 | receiver->noteIp(ip); | |
258 | return true; | |
259 | } | |
260 | return false; | |
261 | } | |
262 | // else do nothing: ReceiverFun does not do incremental notifications | |
263 | return false; | |
264 | } | |
265 | ||
266 | /// convenience wrapper to safely forwardIp() for each IP in the container | |
267 | void | |
268 | IpCacheLookupForwarder::forwardHits(const Dns::CachedIps &ips) | |
269 | { | |
270 | if (receiverObj.set()) { | |
271 | for (const auto &ip: ips.good()) { | |
272 | if (!forwardIp(ip)) | |
273 | break; // receiver gone | |
274 | } | |
275 | } | |
276 | // else do nothing: ReceiverFun does not do incremental notifications | |
277 | } | |
278 | ||
279 | void | |
280 | IpCacheLookupForwarder::forwardLookup(const char *error) | |
281 | { | |
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()) { | |
8651e126 | 286 | receiver->noteLookup(Dns::LookupDetails(SBuf(error), additionalLookupDelay())); |
fd9c47d1 AR |
287 | lastLookupEnd = current_time; |
288 | } | |
289 | } | |
290 | // else do nothing: ReceiverFun gets no individual lookup notifications | |
291 | } | |
292 | ||
293 | /// \ingroup IPCacheInternal | |
294 | inline int ipcacheCount() { return ip_table ? ip_table->count : 0; } | |
295 | ||
63be0a78 | 296 | /** |
297 | \ingroup IPCacheInternal | |
298 | * | |
299 | * removes the given ipcache entry | |
300 | */ | |
b8d8561b | 301 | static void |
cc192b50 | 302 | ipcacheRelease(ipcache_entry * i, bool dofree) |
090089c4 | 303 | { |
26ac0430 | 304 | if (!i) { |
fa84c01d | 305 | debugs(14, DBG_CRITICAL, "ipcacheRelease: Releasing entry with i=<NULL>"); |
cc192b50 | 306 | return; |
307 | } | |
308 | ||
26ac0430 | 309 | if (!i || !i->hash.key) { |
fa84c01d | 310 | debugs(14, DBG_CRITICAL, "ipcacheRelease: Releasing entry without hash link!"); |
cc192b50 | 311 | return; |
312 | } | |
313 | ||
bf8fe701 | 314 | debugs(14, 3, "ipcacheRelease: Releasing entry for '" << (const char *) i->hash.key << "'"); |
315 | ||
ecc3091b | 316 | hash_remove_link(ip_table, (hash_link *) i); |
7b04dad5 | 317 | dlinkDelete(&i->lru, &lru_list); |
26ac0430 | 318 | if (dofree) |
cc192b50 | 319 | ipcacheFreeEntry(i); |
090089c4 | 320 | } |
321 | ||
63be0a78 | 322 | /// \ingroup IPCacheInternal |
b8d8561b | 323 | static ipcache_entry * |
0ee4272b | 324 | ipcache_get(const char *name) |
090089c4 | 325 | { |
aee3523a | 326 | if (ip_table != nullptr) |
62e76326 | 327 | return (ipcache_entry *) hash_lookup(ip_table, name); |
ecc3091b | 328 | else |
aee3523a | 329 | return nullptr; |
090089c4 | 330 | } |
331 | ||
63be0a78 | 332 | /// \ingroup IPCacheInternal |
b8d8561b | 333 | static int |
334 | ipcacheExpiredEntry(ipcache_entry * i) | |
30a4f2a8 | 335 | { |
0e70aa1e | 336 | /* all static entries are locked, so this takes care of them too */ |
62e76326 | 337 | |
620da955 | 338 | if (i->locks != 0) |
62e76326 | 339 | return 0; |
340 | ||
fd9c47d1 | 341 | if (i->addrs.empty()) |
62e76326 | 342 | if (0 == i->flags.negcached) |
343 | return 1; | |
344 | ||
af00901c | 345 | if (i->expires > squid_curtime) |
62e76326 | 346 | return 0; |
347 | ||
30a4f2a8 | 348 | return 1; |
349 | } | |
090089c4 | 350 | |
63be0a78 | 351 | /// \ingroup IPCacheAPI |
7b04dad5 | 352 | void |
ced8def3 | 353 | ipcache_purgelru(void *) |
7b04dad5 | 354 | { |
355 | dlink_node *m; | |
aee3523a | 356 | dlink_node *prev = nullptr; |
7b04dad5 | 357 | ipcache_entry *i; |
358 | int removed = 0; | |
aee3523a | 359 | eventAdd("ipcache_purgelru", ipcache_purgelru, nullptr, 10.0, 1); |
62e76326 | 360 | |
7b04dad5 | 361 | for (m = lru_list.tail; m; m = prev) { |
ac49890a | 362 | if (ipcacheCount() < ipcache_low) |
62e76326 | 363 | break; |
364 | ||
365 | prev = m->prev; | |
366 | ||
367 | i = (ipcache_entry *)m->data; | |
368 | ||
369 | if (i->locks != 0) | |
370 | continue; | |
371 | ||
372 | ipcacheRelease(i); | |
373 | ||
95dc7ff4 | 374 | ++removed; |
7b04dad5 | 375 | } |
62e76326 | 376 | |
bf8fe701 | 377 | debugs(14, 9, "ipcache_purgelru: removed " << removed << " entries"); |
7b04dad5 | 378 | } |
379 | ||
63be0a78 | 380 | /** |
381 | \ingroup IPCacheInternal | |
382 | * | |
383 | * purges entries added from /etc/hosts (or whatever). | |
384 | */ | |
0e70aa1e | 385 | static void |
386 | purge_entries_fromhosts(void) | |
387 | { | |
388 | dlink_node *m = lru_list.head; | |
aee3523a | 389 | ipcache_entry *i = nullptr, *t; |
62e76326 | 390 | |
0e70aa1e | 391 | while (m) { |
aee3523a | 392 | if (i != nullptr) { /* need to delay deletion */ |
f53969cc | 393 | ipcacheRelease(i); /* we just override locks */ |
aee3523a | 394 | i = nullptr; |
62e76326 | 395 | } |
396 | ||
397 | t = (ipcache_entry*)m->data; | |
398 | ||
399 | if (t->flags.fromhosts) | |
400 | i = t; | |
401 | ||
402 | m = m->next; | |
0e70aa1e | 403 | } |
62e76326 | 404 | |
aee3523a | 405 | if (i != nullptr) |
62e76326 | 406 | ipcacheRelease(i); |
0e70aa1e | 407 | } |
408 | ||
494269cd | 409 | ipcache_entry::ipcache_entry(const char *aName): |
3c670b50 AJ |
410 | lastref(0), |
411 | expires(0), | |
3c670b50 AJ |
412 | error_message(nullptr), |
413 | locks(0) // XXX: use Lock type ? | |
090089c4 | 414 | { |
494269cd | 415 | hash.key = xstrdup(aName); |
3c670b50 AJ |
416 | Tolower(static_cast<char*>(hash.key)); |
417 | expires = squid_curtime + Config.negativeDnsTtl; | |
090089c4 | 418 | } |
419 | ||
63be0a78 | 420 | /// \ingroup IPCacheInternal |
b8d8561b | 421 | static void |
ecc3091b | 422 | ipcacheAddEntry(ipcache_entry * i) |
dd7ad0a4 | 423 | { |
e6ccf245 | 424 | hash_link *e = (hash_link *)hash_lookup(ip_table, i->hash.key); |
62e76326 | 425 | |
aee3523a | 426 | if (nullptr != e) { |
2f8abb64 | 427 | /* avoid collision */ |
62e76326 | 428 | ipcache_entry *q = (ipcache_entry *) e; |
2b72648d | 429 | ipcacheRelease(q); |
ecc3091b | 430 | } |
62e76326 | 431 | |
186477c1 | 432 | hash_join(ip_table, &i->hash); |
ecc3091b | 433 | dlinkAdd(i, &i->lru, &lru_list); |
dd7ad0a4 | 434 | i->lastref = squid_curtime; |
090089c4 | 435 | } |
436 | ||
63be0a78 | 437 | /** |
438 | \ingroup IPCacheInternal | |
439 | * | |
440 | * walks down the pending list, calling handlers | |
441 | */ | |
b8d8561b | 442 | static void |
fd9c47d1 | 443 | ipcacheCallback(ipcache_entry *i, const bool hit, const int wait) |
090089c4 | 444 | { |
30a4f2a8 | 445 | i->lastref = squid_curtime; |
62e76326 | 446 | |
fa80a8ef | 447 | ipcacheLockEntry(i); |
62e76326 | 448 | |
fd9c47d1 AR |
449 | if (hit) |
450 | i->handler.forwardHits(i->addrs); | |
8651e126 | 451 | const Dns::LookupDetails details(SBuf(i->error_message), wait); |
fd9c47d1 | 452 | i->handler.finalCallback(&i->addrs, details); |
62e76326 | 453 | |
620da955 | 454 | ipcacheUnlockEntry(i); |
090089c4 | 455 | } |
456 | ||
fd9c47d1 | 457 | void |
7efc7a7b | 458 | ipcache_entry::latestError(const char *text) |
fd9c47d1 | 459 | { |
7efc7a7b | 460 | debugs(14, 3, "ERROR: DNS failure while resolving " << name() << ": " << text); |
fd9c47d1 AR |
461 | safe_free(error_message); |
462 | error_message = xstrdup(text); | |
463 | } | |
464 | ||
7eb648e2 | 465 | static void |
33ab4aaf | 466 | ipcacheParse(ipcache_entry *i, const rfc1035_rr * answers, int nr, const char *error_message) |
7b724b86 | 467 | { |
7cfc1c9a | 468 | int k; |
62e76326 | 469 | |
fd9c47d1 | 470 | // XXX: Callers use zero ancount instead of -1 on errors! |
7cfc1c9a | 471 | if (nr < 0) { |
fd9c47d1 | 472 | i->latestError(error_message); |
7eb648e2 | 473 | return; |
7cfc1c9a | 474 | } |
62e76326 | 475 | |
7cfc1c9a | 476 | if (nr == 0) { |
fd9c47d1 | 477 | i->latestError("No DNS records"); |
7eb648e2 | 478 | return; |
7cfc1c9a | 479 | } |
62e76326 | 480 | |
fd9c47d1 | 481 | debugs(14, 3, nr << " answers for " << i->name()); |
7cfc1c9a | 482 | assert(answers); |
62e76326 | 483 | |
95dc7ff4 | 484 | for (k = 0; k < nr; ++k) { |
62e76326 | 485 | |
055421ee | 486 | if (Ip::EnableIpv6 && answers[k].type == RFC1035_TYPE_AAAA) { |
fd9c47d1 AR |
487 | static const RrSpecs<struct in6_addr> QuadA = { "IPv6", IpcacheStats.rr_aaaa }; |
488 | i->addGood(answers[k], QuadA); | |
26ac0430 AJ |
489 | continue; |
490 | } | |
62e76326 | 491 | |
cc192b50 | 492 | if (answers[k].type == RFC1035_TYPE_A) { |
fd9c47d1 AR |
493 | static const RrSpecs<struct in_addr> SingleA = { "IPv4", IpcacheStats.rr_a }; |
494 | i->addGood(answers[k], SingleA); | |
26ac0430 AJ |
495 | continue; |
496 | } | |
cc192b50 | 497 | |
26ac0430 | 498 | /* With A and AAAA, the CNAME does not necessarily come with additional records to use. */ |
cc192b50 | 499 | if (answers[k].type == RFC1035_TYPE_CNAME) { |
fd9c47d1 | 500 | i->sawCname = true; |
95dc7ff4 | 501 | ++IpcacheStats.rr_cname; |
470cd749 | 502 | continue; |
503 | } | |
bae9832d | 504 | |
505 | // otherwise its an unknown RR. debug at level 9 since we usually want to ignore these and they are common. | |
7eb648e2 | 506 | debugs(14, 9, "Unknown RR type received: type=" << answers[k].type << " starting at " << &(answers[k]) ); |
cc192b50 | 507 | } |
fd9c47d1 AR |
508 | } |
509 | ||
510 | template <class Specs> | |
511 | void | |
512 | ipcache_entry::addGood(const rfc1035_rr &rr, Specs &specs) | |
513 | { | |
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()); | |
7eb648e2 | 517 | return; |
7cfc1c9a | 518 | } |
62e76326 | 519 | |
fd9c47d1 | 520 | ++specs.recordCounter; |
cc192b50 | 521 | |
fd9c47d1 AR |
522 | // Do not store more than 255 addresses (TODO: Why?) |
523 | if (addrs.raw().size() >= 255) | |
524 | return; | |
cc192b50 | 525 | |
fd9c47d1 AR |
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); | |
530 | return; | |
7b724b86 | 531 | } |
fd9c47d1 | 532 | addrs.pushUnique(address); |
62e76326 | 533 | |
fd9c47d1 | 534 | updateTtl(rr.ttl); |
7ba8d0b8 | 535 | |
fd9c47d1 AR |
536 | debugs(14, 3, name() << " #" << addrs.size() << " " << ip); |
537 | handler.forwardIp(ip); // we are only called with good IPs | |
538 | } | |
7ba8d0b8 | 539 | |
fd9c47d1 AR |
540 | void |
541 | ipcache_entry::updateTtl(const unsigned int rrTtl) | |
542 | { | |
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 | |
547 | ||
548 | const time_t rrExpires = squid_curtime + ttl; | |
eae95059 AR |
549 | if (addrs.size() <= 1) { |
550 | debugs(14, 5, "use first " << ttl << " from RR TTL " << rrTtl); | |
fd9c47d1 | 551 | expires = rrExpires; |
eae95059 AR |
552 | } else if (rrExpires < expires) { |
553 | debugs(14, 5, "use smaller " << ttl << " from RR TTL " << rrTtl << "; was: " << (expires - squid_curtime)); | |
554 | expires = rrExpires; | |
555 | } else { | |
556 | debugs(14, 7, "ignore " << ttl << " from RR TTL " << rrTtl << "; keep: " << (expires - squid_curtime)); | |
557 | } | |
7b724b86 | 558 | } |
62e76326 | 559 | |
63be0a78 | 560 | /// \ingroup IPCacheInternal |
a7e59001 | 561 | static void |
fd9c47d1 | 562 | ipcacheHandleReply(void *data, const rfc1035_rr * answers, int na, const char *error_message, const bool lastAnswer) |
090089c4 | 563 | { |
fd9c47d1 AR |
564 | ipcache_entry *i = static_cast<ipcache_entry*>(data); |
565 | ||
566 | i->handler.forwardLookup(error_message); | |
567 | ipcacheParse(i, answers, na, error_message); | |
568 | ||
569 | if (!lastAnswer) | |
570 | return; | |
571 | ||
95dc7ff4 | 572 | ++IpcacheStats.replies; |
fd9c47d1 | 573 | const auto age = i->handler.totalResponseTime(); |
e8baef82 | 574 | statCounter.dns.svcTime.count(age); |
e1381638 | 575 | |
fd9c47d1 AR |
576 | if (i->addrs.empty()) { |
577 | i->flags.negcached = true; | |
578 | i->expires = squid_curtime + Config.negativeDnsTtl; | |
579 | ||
580 | if (!i->error_message) { | |
7efc7a7b | 581 | i->latestError("No valid address records"); |
fd9c47d1 AR |
582 | if (i->sawCname) |
583 | ++IpcacheStats.cname_only; | |
584 | } | |
585 | } | |
586 | ||
587 | debugs(14, 3, "done with " << i->name() << ": " << i->addrs); | |
7eb648e2 | 588 | ipcacheAddEntry(i); |
fd9c47d1 | 589 | ipcacheCallback(i, false, age); |
30a4f2a8 | 590 | } |
591 | ||
63be0a78 | 592 | /** |
593 | \ingroup IPCacheAPI | |
594 | * | |
f53969cc SM |
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. | |
79300bcb AR |
599 | * |
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, | |
26ac0430 | 603 | * especially if the handler destroys the job. Moreover, the job has |
55cbb02b AJ |
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. | |
63be0a78 | 607 | */ |
b8d8561b | 608 | void |
8407afee | 609 | ipcache_nbgethostbyname(const char *name, IPH * handler, void *handlerData) |
fd9c47d1 AR |
610 | { |
611 | debugs(14, 4, name); | |
612 | ipcache_nbgethostbyname_(name, IpCacheLookupForwarder(handler, handlerData)); | |
613 | } | |
614 | ||
615 | void | |
616 | Dns::nbgethostbyname(const char *name, const CbcPointer<IpReceiver> &receiver) | |
617 | { | |
618 | debugs(14, 4, name); | |
619 | ipcache_nbgethostbyname_(name, IpCacheLookupForwarder(receiver)); | |
620 | } | |
621 | ||
622 | /// implements ipcache_nbgethostbyname() and Dns::nbgethostbyname() APIs | |
623 | static void | |
624 | ipcache_nbgethostbyname_(const char *name, IpCacheLookupForwarder handler) | |
090089c4 | 625 | { |
aee3523a AR |
626 | ipcache_entry *i = nullptr; |
627 | const ipcache_addrs *addrs = nullptr; | |
95dc7ff4 | 628 | ++IpcacheStats.requests; |
62e76326 | 629 | |
aee3523a | 630 | if (name == nullptr || name[0] == '\0') { |
bf8fe701 | 631 | debugs(14, 4, "ipcache_nbgethostbyname: Invalid name!"); |
95dc7ff4 | 632 | ++IpcacheStats.invalid; |
8651e126 | 633 | static const Dns::LookupDetails details(SBuf("Invalid hostname"), -1); // error, no lookup |
fd9c47d1 | 634 | handler.finalCallback(nullptr, details); |
62e76326 | 635 | return; |
af00901c | 636 | } |
62e76326 | 637 | |
e5f6c5c2 | 638 | if ((addrs = ipcacheCheckNumeric(name))) { |
cc192b50 | 639 | debugs(14, 4, "ipcache_nbgethostbyname: BYPASS for '" << name << "' (already numeric)"); |
8e44d1a4 | 640 | handler.forwardHits(*addrs); |
95dc7ff4 | 641 | ++IpcacheStats.numeric_hits; |
4a3b98d7 | 642 | const Dns::LookupDetails details; // no error, no lookup |
fd9c47d1 | 643 | handler.finalCallback(addrs, details); |
62e76326 | 644 | return; |
090089c4 | 645 | } |
62e76326 | 646 | |
ecc3091b | 647 | i = ipcache_get(name); |
62e76326 | 648 | |
aee3523a | 649 | if (nullptr == i) { |
62e76326 | 650 | /* miss */ |
651 | (void) 0; | |
ecc3091b | 652 | } else if (ipcacheExpiredEntry(i)) { |
62e76326 | 653 | /* hit, but expired -- bummer */ |
654 | ipcacheRelease(i); | |
aee3523a | 655 | i = nullptr; |
ecc3091b | 656 | } else { |
62e76326 | 657 | /* hit */ |
bf8fe701 | 658 | debugs(14, 4, "ipcache_nbgethostbyname: HIT for '" << name << "'"); |
62e76326 | 659 | |
660 | if (i->flags.negcached) | |
95dc7ff4 | 661 | ++IpcacheStats.negative_hits; |
62e76326 | 662 | else |
95dc7ff4 | 663 | ++IpcacheStats.hits; |
62e76326 | 664 | |
fd9c47d1 AR |
665 | i->handler = std::move(handler); |
666 | ipcacheCallback(i, true, -1); // no lookup | |
62e76326 | 667 | |
668 | return; | |
090089c4 | 669 | } |
62e76326 | 670 | |
bf8fe701 | 671 | debugs(14, 5, "ipcache_nbgethostbyname: MISS for '" << name << "'"); |
95dc7ff4 | 672 | ++IpcacheStats.misses; |
3c670b50 | 673 | i = new ipcache_entry(name); |
fd9c47d1 AR |
674 | i->handler = std::move(handler); |
675 | i->handler.lookupsStarting(); | |
676 | idnsALookup(hashKeyStr(&i->hash), ipcacheHandleReply, i); | |
090089c4 | 677 | } |
678 | ||
5f5e883f FC |
679 | /// \ingroup IPCacheInternal |
680 | static void | |
681 | ipcacheRegisterWithCacheManager(void) | |
682 | { | |
8822ebee | 683 | Mgr::RegisterAction("ipcache", |
d9fc6862 A |
684 | "IP Cache Stats and Contents", |
685 | stat_ipcache_get, 0, 1); | |
5f5e883f FC |
686 | } |
687 | ||
63be0a78 | 688 | /** |
689 | \ingroup IPCacheAPI | |
690 | * | |
691 | * Initialize the ipcache. | |
692 | * Is called from mainInitialize() after disk initialization | |
693 | * and prior to the reverse FQDNCache initialization | |
694 | */ | |
b8d8561b | 695 | void |
0673c0ba | 696 | ipcache_init(void) |
0ffd22bc | 697 | { |
aa9e2cab | 698 | int n; |
c59baaa8 | 699 | debugs(14, Important(24), "Initializing IP Cache..."); |
30a4f2a8 | 700 | memset(&IpcacheStats, '\0', sizeof(IpcacheStats)); |
b56b37cf | 701 | lru_list = dlink_list(); |
62e76326 | 702 | |
b15e6857 | 703 | ipcache_high = (long) (((float) Config.ipcache.size * |
62e76326 | 704 | (float) Config.ipcache.high) / (float) 100); |
b15e6857 | 705 | ipcache_low = (long) (((float) Config.ipcache.size * |
62e76326 | 706 | (float) Config.ipcache.low) / (float) 100); |
aa9e2cab | 707 | n = hashPrime(ipcache_high / 4); |
30abd221 | 708 | ip_table = hash_create((HASHCMP *) strcmp, n, hash4); |
d120ed12 FC |
709 | |
710 | ipcacheRegisterWithCacheManager(); | |
090089c4 | 711 | } |
712 | ||
63be0a78 | 713 | /** |
714 | \ingroup IPCacheAPI | |
715 | * | |
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. | |
719 | * | |
f53969cc SM |
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. | |
63be0a78 | 723 | * |
61beade2 | 724 | \retval NULL An error occurred during lookup |
f53969cc SM |
725 | \retval NULL No results available in cache and no lookup specified |
726 | \retval * Pointer to the ipcahce_addrs structure containing the lookup results | |
63be0a78 | 727 | */ |
0ee4272b | 728 | const ipcache_addrs * |
729 | ipcache_gethostbyname(const char *name, int flags) | |
090089c4 | 730 | { |
aee3523a | 731 | ipcache_entry *i = nullptr; |
ecc3091b | 732 | assert(name); |
a4dd5bfa | 733 | debugs(14, 3, "'" << name << "', flags=" << asHex(flags)); |
95dc7ff4 | 734 | ++IpcacheStats.requests; |
ecc3091b | 735 | i = ipcache_get(name); |
62e76326 | 736 | |
aee3523a | 737 | if (nullptr == i) { |
62e76326 | 738 | (void) 0; |
ecc3091b | 739 | } else if (ipcacheExpiredEntry(i)) { |
62e76326 | 740 | ipcacheRelease(i); |
aee3523a | 741 | i = nullptr; |
ecc3091b | 742 | } else if (i->flags.negcached) { |
95dc7ff4 | 743 | ++IpcacheStats.negative_hits; |
3ff65596 | 744 | // ignore i->error_message: the caller just checks IP cache presence |
aee3523a | 745 | return nullptr; |
ecc3091b | 746 | } else { |
95dc7ff4 | 747 | ++IpcacheStats.hits; |
62e76326 | 748 | i->lastref = squid_curtime; |
3ff65596 | 749 | // ignore i->error_message: the caller just checks IP cache presence |
62e76326 | 750 | return &i->addrs; |
30a4f2a8 | 751 | } |
62e76326 | 752 | |
3ff65596 | 753 | /* no entry [any more] */ |
2ffff82e | 754 | |
fd9c47d1 | 755 | if (const auto addrs = ipcacheCheckNumeric(name)) { |
95dc7ff4 | 756 | ++IpcacheStats.numeric_hits; |
62e76326 | 757 | return addrs; |
22b245f8 | 758 | } |
62e76326 | 759 | |
95dc7ff4 | 760 | ++IpcacheStats.misses; |
62e76326 | 761 | |
30a4f2a8 | 762 | if (flags & IP_LOOKUP_IF_MISS) |
aee3523a | 763 | ipcache_nbgethostbyname(name, nullptr, nullptr); |
62e76326 | 764 | |
aee3523a | 765 | return nullptr; |
090089c4 | 766 | } |
767 | ||
63be0a78 | 768 | /// \ingroup IPCacheInternal |
b8d8561b | 769 | static void |
770 | ipcacheStatPrint(ipcache_entry * i, StoreEntry * sentry) | |
af00901c | 771 | { |
cc192b50 | 772 | char buf[MAX_IPSTRLEN]; |
773 | ||
26ac0430 | 774 | if (!sentry) { |
d816f28d | 775 | debugs(14, DBG_CRITICAL, "ERROR: sentry is NULL!"); |
71aab4cc | 776 | return; |
cc192b50 | 777 | } |
778 | ||
26ac0430 | 779 | if (!i) { |
d816f28d | 780 | debugs(14, DBG_CRITICAL, "ERROR: ipcache_entry is NULL!"); |
cc192b50 | 781 | storeAppendPrintf(sentry, "CRITICAL ERROR\n"); |
782 | return; | |
783 | } | |
784 | ||
181b1adc | 785 | storeAppendPrintf(sentry, " %-32.32s %c%c %6d %6d %2d(%2d)", |
62e76326 | 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)), | |
fd9c47d1 AR |
791 | static_cast<int>(i->addrs.size()), |
792 | static_cast<int>(i->addrs.badCount())); | |
62e76326 | 793 | |
cc192b50 | 794 | /** \par |
795 | * Negative-cached entries have no IPs listed. */ | |
26ac0430 | 796 | if (i->flags.negcached) { |
cc192b50 | 797 | storeAppendPrintf(sentry, "\n"); |
798 | return; | |
52926044 | 799 | } |
62e76326 | 800 | |
cc192b50 | 801 | /** \par |
d85b8894 | 802 | * Cached entries have IPs listed with a BNF of: ip-address '-' ('OK'|'BAD') */ |
fd9c47d1 AR |
803 | bool firstLine = true; |
804 | for (const auto &addr: i->addrs.raw()) { | |
cc192b50 | 805 | /* Display tidy-up: IPv6 are so big make the list vertical */ |
fd9c47d1 AR |
806 | const char *indent = firstLine ? "" : " "; |
807 | storeAppendPrintf(sentry, "%s %45.45s-%3s\n", | |
808 | indent, | |
809 | addr.ip.toStr(buf, MAX_IPSTRLEN), | |
810 | addr.bad() ? "BAD" : "OK "); | |
811 | firstLine = false; | |
cc192b50 | 812 | } |
af00901c | 813 | } |
090089c4 | 814 | |
63be0a78 | 815 | /** |
816 | \ingroup IPCacheInternal | |
817 | * | |
818 | * process objects list | |
819 | */ | |
b8d8561b | 820 | void |
821 | stat_ipcache_get(StoreEntry * sentry) | |
090089c4 | 822 | { |
7b04dad5 | 823 | dlink_node *m; |
aee3523a | 824 | assert(ip_table != nullptr); |
15576b6a | 825 | storeAppendPrintf(sentry, "IP Cache Statistics:\n"); |
ac49890a CT |
826 | storeAppendPrintf(sentry, "IPcache Entries Cached: %d\n", |
827 | ipcacheCount()); | |
15576b6a | 828 | storeAppendPrintf(sentry, "IPcache Requests: %d\n", |
62e76326 | 829 | IpcacheStats.requests); |
22b245f8 | 830 | storeAppendPrintf(sentry, "IPcache Hits: %d\n", |
62e76326 | 831 | IpcacheStats.hits); |
22b245f8 | 832 | storeAppendPrintf(sentry, "IPcache Negative Hits: %d\n", |
62e76326 | 833 | IpcacheStats.negative_hits); |
22b245f8 | 834 | storeAppendPrintf(sentry, "IPcache Numeric Hits: %d\n", |
835 | IpcacheStats.numeric_hits); | |
836 | storeAppendPrintf(sentry, "IPcache Misses: %d\n", | |
62e76326 | 837 | IpcacheStats.misses); |
f2d71697 | 838 | storeAppendPrintf(sentry, "IPcache Retrieved A: %d\n", |
bae9832d | 839 | IpcacheStats.rr_a); |
f2d71697 | 840 | storeAppendPrintf(sentry, "IPcache Retrieved AAAA: %d\n", |
bae9832d | 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); | |
22b245f8 | 846 | storeAppendPrintf(sentry, "IPcache Invalid Request: %d\n", |
847 | IpcacheStats.invalid); | |
15576b6a | 848 | storeAppendPrintf(sentry, "\n\n"); |
849 | storeAppendPrintf(sentry, "IP Cache Contents:\n\n"); | |
cc192b50 | 850 | storeAppendPrintf(sentry, " %-31.31s %3s %6s %6s %4s\n", |
62e76326 | 851 | "Hostname", |
852 | "Flg", | |
853 | "lstref", | |
854 | "TTL", | |
cc192b50 | 855 | "N(b)"); |
62e76326 | 856 | |
cc192b50 | 857 | for (m = lru_list.head; m; m = m->next) { |
858 | assert( m->next != m ); | |
62e76326 | 859 | ipcacheStatPrint((ipcache_entry *)m->data, sentry); |
cc192b50 | 860 | } |
861 | } | |
862 | ||
63be0a78 | 863 | /// \ingroup IPCacheAPI |
b8d8561b | 864 | void |
0ee4272b | 865 | ipcacheInvalidate(const char *name) |
f900607e | 866 | { |
867 | ipcache_entry *i; | |
62e76326 | 868 | |
aee3523a | 869 | if ((i = ipcache_get(name)) == nullptr) |
62e76326 | 870 | return; |
871 | ||
6c11e193 | 872 | i->expires = squid_curtime; |
62e76326 | 873 | |
ecc3091b | 874 | /* |
63be0a78 | 875 | * NOTE, don't call ipcacheRelease here because we might be here due |
ecc3091b | 876 | * to a thread started from a callback. |
877 | */ | |
f900607e | 878 | } |
af00901c | 879 | |
63be0a78 | 880 | /// \ingroup IPCacheAPI |
a12a049a | 881 | void |
882 | ipcacheInvalidateNegative(const char *name) | |
883 | { | |
884 | ipcache_entry *i; | |
885 | ||
aee3523a | 886 | if ((i = ipcache_get(name)) == nullptr) |
a12a049a | 887 | return; |
888 | ||
889 | if (i->flags.negcached) | |
890 | i->expires = squid_curtime; | |
891 | ||
892 | /* | |
63be0a78 | 893 | * NOTE, don't call ipcacheRelease here because we might be here due |
a12a049a | 894 | * to a thread started from a callback. |
895 | */ | |
896 | } | |
897 | ||
63be0a78 | 898 | /// \ingroup IPCacheAPI |
fd9c47d1 | 899 | static const Dns::CachedIps * |
0ee4272b | 900 | ipcacheCheckNumeric(const char *name) |
af00901c | 901 | { |
b7ac5457 | 902 | Ip::Address ip; |
fd9c47d1 AR |
903 | if (!ip.fromHost(name)) |
904 | return nullptr; | |
62e76326 | 905 | |
fd9c47d1 AR |
906 | debugs(14, 4, "HIT_BYPASS for " << name << "=" << ip); |
907 | static Dns::CachedIps static_addrs; | |
908 | static_addrs.reset(ip); | |
e5f6c5c2 | 909 | return &static_addrs; |
af00901c | 910 | } |
8905d949 | 911 | |
63be0a78 | 912 | /// \ingroup IPCacheInternal |
b8d8561b | 913 | static void |
914 | ipcacheLockEntry(ipcache_entry * i) | |
620da955 | 915 | { |
7b04dad5 | 916 | if (i->locks++ == 0) { |
62e76326 | 917 | dlinkDelete(&i->lru, &lru_list); |
918 | dlinkAdd(i, &i->lru, &lru_list); | |
7b04dad5 | 919 | } |
620da955 | 920 | } |
921 | ||
63be0a78 | 922 | /// \ingroup IPCacheInternal |
b8d8561b | 923 | static void |
924 | ipcacheUnlockEntry(ipcache_entry * i) | |
620da955 | 925 | { |
26ac0430 | 926 | if (i->locks < 1) { |
e0236918 | 927 | debugs(14, DBG_IMPORTANT, "WARNING: ipcacheEntry unlocked with no lock! locks=" << i->locks); |
cc192b50 | 928 | return; |
929 | } | |
930 | ||
5e263176 | 931 | -- i->locks; |
62e76326 | 932 | |
620da955 | 933 | if (ipcacheExpiredEntry(i)) |
62e76326 | 934 | ipcacheRelease(i); |
620da955 | 935 | } |
e5f6c5c2 | 936 | |
fd9c47d1 AR |
937 | /// find the next good IP, wrapping if needed |
938 | /// \returns whether the search was successful | |
939 | bool | |
940 | Dns::CachedIps::seekNewGood(const char *name) | |
52926044 | 941 | { |
fd9c47d1 AR |
942 | // linear search! |
943 | for (size_t seen = 0; seen < ips.size(); ++seen) { | |
944 | if (++goodPosition >= ips.size()) | |
945 | goodPosition = 0; | |
946 | if (!ips[goodPosition].bad()) { | |
947 | debugs(14, 3, "succeeded for " << name << ": " << *this); | |
948 | return true; | |
949 | } | |
52926044 | 950 | } |
fd9c47d1 AR |
951 | goodPosition = ips.size(); |
952 | debugs(14, 3, "failed for " << name << ": " << *this); | |
953 | return false; | |
954 | } | |
62e76326 | 955 | |
fd9c47d1 AR |
956 | void |
957 | Dns::CachedIps::reset(const Ip::Address &ip) | |
958 | { | |
9770c5fe AR |
959 | ips.clear(); |
960 | ips.emplace_back(ip); | |
fd9c47d1 AR |
961 | goodPosition = 0; |
962 | // Assume that the given IP is good because CachedIps are designed to never | |
963 | // run out of good IPs. | |
964 | badCount_ = 0; | |
965 | } | |
62e76326 | 966 | |
fd9c47d1 AR |
967 | /// makes current() calls possible after a successful markAsBad() |
968 | void | |
969 | Dns::CachedIps::restoreGoodness(const char *name) | |
970 | { | |
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(); | |
976 | badCount_ = 0; | |
7d5be721 AR |
977 | debugs(14, 3, "cleared all " << size() << " bad IPs for " << name); |
978 | // fall through to reset goodPosition and report the current state | |
52926044 | 979 | } |
fd9c47d1 | 980 | Must(seekNewGood(name)); |
fd9c47d1 | 981 | } |
62e76326 | 982 | |
fd9c47d1 AR |
983 | bool |
984 | Dns::CachedIps::have(const Ip::Address &ip, size_t *positionOrNil) const | |
985 | { | |
986 | // linear search! | |
987 | size_t pos = 0; | |
988 | for (const auto &cachedIp: ips) { | |
989 | if (cachedIp.ip == ip) { | |
990 | if (auto position = positionOrNil) | |
991 | *position = pos; | |
9770c5fe | 992 | debugs(14, 7, ip << " at " << pos << " in " << *this); |
fd9c47d1 AR |
993 | return true; |
994 | } | |
e213ef43 | 995 | ++pos; // TODO: Replace with std::views::enumerate() after upgrading to C++23 |
52926044 | 996 | } |
fd9c47d1 | 997 | // no such address; leave *position as is |
9770c5fe | 998 | debugs(14, 7, " no " << ip << " in " << *this); |
fd9c47d1 | 999 | return false; |
52926044 | 1000 | } |
e5f6c5c2 | 1001 | |
1002 | void | |
fd9c47d1 | 1003 | Dns::CachedIps::pushUnique(const Ip::Address &ip) |
e5f6c5c2 | 1004 | { |
fd9c47d1 | 1005 | assert(!have(ip)); |
09835feb AR |
1006 | [[maybe_unused]] auto &cachedIp = ips.emplace_back(ip); |
1007 | assert(!cachedIp.bad()); | |
fd9c47d1 | 1008 | } |
62e76326 | 1009 | |
fd9c47d1 AR |
1010 | void |
1011 | Dns::CachedIps::reportCurrent(std::ostream &os) const | |
1012 | { | |
1013 | if (empty()) | |
1014 | os << "[no cached IPs]"; | |
1015 | else if (goodPosition == size()) | |
1016 | os << "[" << size() << " bad cached IPs]"; // could only be temporary | |
1017 | else | |
1018 | os << current() << " #" << (goodPosition+1) << "/" << ips.size() << "-" << badCount(); | |
1019 | } | |
62e76326 | 1020 | |
fd9c47d1 AR |
1021 | void |
1022 | Dns::CachedIps::markAsBad(const char *name, const Ip::Address &ip) | |
1023 | { | |
1024 | size_t badPosition = 0; | |
1025 | if (!have(ip, &badPosition)) | |
1026 | return; // no such address | |
62e76326 | 1027 | |
fd9c47d1 AR |
1028 | auto &cachedIp = ips[badPosition]; |
1029 | if (cachedIp.bad()) | |
1030 | return; // already marked correctly | |
62e76326 | 1031 | |
fd9c47d1 AR |
1032 | cachedIp.markAsBad(); |
1033 | ++badCount_; | |
1034 | debugs(14, 2, ip << " of " << name); | |
62e76326 | 1035 | |
fd9c47d1 AR |
1036 | if (goodPosition == badPosition) |
1037 | restoreGoodness(name); | |
1038 | // else nothing to do: goodPositon still points to a good IP | |
e5f6c5c2 | 1039 | } |
56e15c50 | 1040 | |
ec505200 | 1041 | void |
fd9c47d1 | 1042 | Dns::CachedIps::forgetMarking(const char *name, const Ip::Address &ip) |
ec505200 | 1043 | { |
fd9c47d1 AR |
1044 | if (!badCount_) |
1045 | return; // all IPs are already "good" | |
ec505200 | 1046 | |
fd9c47d1 AR |
1047 | size_t badPosition = 0; |
1048 | if (!have(ip, &badPosition)) | |
1049 | return; // no such address | |
ec505200 | 1050 | |
fd9c47d1 AR |
1051 | auto &cachedIp = ips[badPosition]; |
1052 | if (!cachedIp.bad()) | |
1053 | return; // already marked correctly | |
ec505200 | 1054 | |
fd9c47d1 AR |
1055 | cachedIp.forgetMarking(); |
1056 | assert(!cachedIp.bad()); | |
1057 | --badCount_; | |
1058 | debugs(14, 2, ip << " of " << name); | |
1059 | } | |
ec505200 | 1060 | |
fd9c47d1 AR |
1061 | /** |
1062 | * Marks the given address as BAD. | |
1063 | * Does nothing if the domain name does not exist. | |
1064 | * | |
1065 | \param name domain name to have an IP marked bad | |
2f8abb64 | 1066 | \param addr specific address to be marked bad |
fd9c47d1 AR |
1067 | */ |
1068 | void | |
1069 | ipcacheMarkBadAddr(const char *name, const Ip::Address &addr) | |
1070 | { | |
1071 | if (auto cached = ipcache_get(name)) | |
1072 | cached->addrs.markAsBad(name, addr); | |
ec505200 HN |
1073 | } |
1074 | ||
63be0a78 | 1075 | /// \ingroup IPCacheAPI |
22c653cd | 1076 | void |
b7ac5457 | 1077 | ipcacheMarkGoodAddr(const char *name, const Ip::Address &addr) |
22c653cd | 1078 | { |
fd9c47d1 AR |
1079 | if (auto cached = ipcache_get(name)) |
1080 | cached->addrs.forgetMarking(name, addr); | |
22c653cd | 1081 | } |
1082 | ||
63be0a78 | 1083 | /// \ingroup IPCacheInternal |
ec878047 | 1084 | static void |
1085 | ipcacheFreeEntry(void *data) | |
1086 | { | |
e6ccf245 | 1087 | ipcache_entry *i = (ipcache_entry *)data; |
3c670b50 AJ |
1088 | delete i; |
1089 | } | |
1090 | ||
1091 | ipcache_entry::~ipcache_entry() | |
1092 | { | |
3c670b50 AJ |
1093 | xfree(error_message); |
1094 | xfree(hash.key); | |
ec878047 | 1095 | } |
1096 | ||
63be0a78 | 1097 | /** |
1098 | \ingroup IPCacheAPI | |
1099 | * | |
1100 | * Recalculate IP cache size upon reconfigure. | |
1101 | * Is called to clear the IPCache's data structures, | |
1102 | * cancel all pending requests. | |
1103 | */ | |
429fdbec | 1104 | void |
1105 | ipcache_restart(void) | |
1106 | { | |
429fdbec | 1107 | ipcache_high = (long) (((float) Config.ipcache.size * |
62e76326 | 1108 | (float) Config.ipcache.high) / (float) 100); |
429fdbec | 1109 | ipcache_low = (long) (((float) Config.ipcache.size * |
62e76326 | 1110 | (float) Config.ipcache.low) / (float) 100); |
0e70aa1e | 1111 | purge_entries_fromhosts(); |
1112 | } | |
1113 | ||
63be0a78 | 1114 | /** |
1115 | \ingroup IPCacheAPI | |
1116 | * | |
1117 | * Adds a "static" entry from /etc/hosts | |
1118 | * | |
f53969cc SM |
1119 | \param name Hostname to be linked with IP |
1120 | \param ipaddr IP Address to be cached. | |
63be0a78 | 1121 | * |
f53969cc SM |
1122 | \retval 0 Success. |
1123 | \retval 1 IP address is invalid or other error. | |
0e70aa1e | 1124 | */ |
1125 | int | |
1126 | ipcacheAddEntryFromHosts(const char *name, const char *ipaddr) | |
1127 | { | |
1128 | ipcache_entry *i; | |
62e76326 | 1129 | |
b7ac5457 | 1130 | Ip::Address ip; |
62e76326 | 1131 | |
cc192b50 | 1132 | if (!(ip = ipaddr)) { |
62e76326 | 1133 | if (strchr(ipaddr, ':') && strspn(ipaddr, "0123456789abcdefABCDEF:") == strlen(ipaddr)) { |
bf8fe701 | 1134 | debugs(14, 3, "ipcacheAddEntryFromHosts: Skipping IPv6 address '" << ipaddr << "'"); |
62e76326 | 1135 | } else { |
d816f28d | 1136 | debugs(14, DBG_IMPORTANT, "ERROR: ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr << "'"); |
62e76326 | 1137 | } |
1138 | ||
1139 | return 1; | |
0e70aa1e | 1140 | } |
62e76326 | 1141 | |
fd9c47d1 AR |
1142 | if (!Ip::EnableIpv6 && ip.isIPv6()) { |
1143 | debugs(14, 2, "skips IPv6 address in /etc/hosts because IPv6 support was disabled: " << ip); | |
1144 | return 1; | |
1145 | } | |
1146 | ||
0e70aa1e | 1147 | if ((i = ipcache_get(name))) { |
62e76326 | 1148 | if (1 == i->flags.fromhosts) { |
1149 | ipcacheUnlockEntry(i); | |
1150 | } else if (i->locks > 0) { | |
d816f28d | 1151 | debugs(14, DBG_IMPORTANT, "ERROR: ipcacheAddEntryFromHosts: cannot add static entry for locked name '" << name << "'"); |
62e76326 | 1152 | return 1; |
1153 | } else { | |
1154 | ipcacheRelease(i); | |
1155 | } | |
0e70aa1e | 1156 | } |
62e76326 | 1157 | |
3c670b50 | 1158 | i = new ipcache_entry(name); |
fd9c47d1 | 1159 | i->addrs.pushUnique(ip); |
be4d35dc | 1160 | i->flags.fromhosts = true; |
0e70aa1e | 1161 | ipcacheAddEntry(i); |
1162 | ipcacheLockEntry(i); | |
1163 | return 0; | |
429fdbec | 1164 | } |
ce75f381 | 1165 | |
59ad6d31 | 1166 | #if SQUID_SNMP |
63be0a78 | 1167 | /** |
1168 | \ingroup IPCacheAPI | |
1169 | * | |
135171fe | 1170 | * The function to return the ip cache statistics to via SNMP |
1171 | */ | |
86115da5 | 1172 | variable_list * |
1f5b542b | 1173 | snmp_netIpFn(variable_list * Var, snint * ErrP) |
d60c11be | 1174 | { |
aee3523a | 1175 | variable_list *Answer = nullptr; |
6a644e75 AJ |
1176 | MemBuf tmp; |
1177 | debugs(49, 5, "snmp_netIpFn: Processing request:" << snmpDebugOid(Var->name, Var->name_length, tmp)); | |
86115da5 | 1178 | *ErrP = SNMP_ERR_NOERROR; |
62e76326 | 1179 | |
135171fe | 1180 | switch (Var->name[LEN_SQ_NET + 1]) { |
62e76326 | 1181 | |
1f5b542b | 1182 | case IP_ENT: |
62e76326 | 1183 | Answer = snmp_var_new_integer(Var->name, Var->name_length, |
ac49890a | 1184 | ipcacheCount(), |
62e76326 | 1185 | SMI_GAUGE32); |
1186 | break; | |
1187 | ||
1f5b542b | 1188 | case IP_REQ: |
62e76326 | 1189 | Answer = snmp_var_new_integer(Var->name, Var->name_length, |
1190 | IpcacheStats.requests, | |
1191 | SMI_COUNTER32); | |
1192 | break; | |
1193 | ||
1f5b542b | 1194 | case IP_HITS: |
62e76326 | 1195 | Answer = snmp_var_new_integer(Var->name, Var->name_length, |
1196 | IpcacheStats.hits, | |
1197 | SMI_COUNTER32); | |
1198 | break; | |
1199 | ||
1f5b542b | 1200 | case IP_PENDHIT: |
62e76326 | 1201 | Answer = snmp_var_new_integer(Var->name, Var->name_length, |
1202 | 0, | |
1203 | SMI_GAUGE32); | |
1204 | break; | |
1205 | ||
1f5b542b | 1206 | case IP_NEGHIT: |
62e76326 | 1207 | Answer = snmp_var_new_integer(Var->name, Var->name_length, |
1208 | IpcacheStats.negative_hits, | |
1209 | SMI_COUNTER32); | |
1210 | break; | |
1211 | ||
1f5b542b | 1212 | case IP_MISS: |
62e76326 | 1213 | Answer = snmp_var_new_integer(Var->name, Var->name_length, |
1214 | IpcacheStats.misses, | |
1215 | SMI_COUNTER32); | |
1216 | break; | |
1217 | ||
1f5b542b | 1218 | case IP_GHBN: |
62e76326 | 1219 | Answer = snmp_var_new_integer(Var->name, Var->name_length, |
1220 | 0, /* deprecated */ | |
1221 | SMI_COUNTER32); | |
1222 | break; | |
1223 | ||
1f5b542b | 1224 | case IP_LOC: |
62e76326 | 1225 | Answer = snmp_var_new_integer(Var->name, Var->name_length, |
1226 | 0, /* deprecated */ | |
1227 | SMI_COUNTER32); | |
1228 | break; | |
1229 | ||
ce75f381 | 1230 | default: |
62e76326 | 1231 | *ErrP = SNMP_ERR_NOSUCHNAME; |
ccea5452 | 1232 | assert(!Answer); |
86115da5 | 1233 | } |
62e76326 | 1234 | |
86115da5 | 1235 | return Answer; |
ce75f381 | 1236 | } |
1f5b542b | 1237 | |
135171fe | 1238 | #endif /*SQUID_SNMP */ |
f53969cc | 1239 |