]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (C) 1996-2025 The Squid Software Foundation and contributors | |
3 | * | |
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. | |
7 | */ | |
8 | ||
9 | /* DEBUG: section 14 IP Cache */ | |
10 | ||
11 | #include "squid.h" | |
12 | #include "base/IoManip.h" | |
13 | #include "CacheManager.h" | |
14 | #include "cbdata.h" | |
15 | #include "debug/Messages.h" | |
16 | #include "dlink.h" | |
17 | #include "dns/LookupDetails.h" | |
18 | #include "dns/rfc3596.h" | |
19 | #include "event.h" | |
20 | #include "ip/Address.h" | |
21 | #include "ip/tools.h" | |
22 | #include "ipcache.h" | |
23 | #include "mgr/Registration.h" | |
24 | #include "snmp_agent.h" | |
25 | #include "SquidConfig.h" | |
26 | #include "StatCounters.h" | |
27 | #include "Store.h" | |
28 | #include "util.h" | |
29 | #include "wordlist.h" | |
30 | ||
31 | #if SQUID_SNMP | |
32 | #include "snmp_core.h" | |
33 | #endif | |
34 | ||
35 | /** | |
36 | \defgroup IPCacheAPI IP Cache API | |
37 | \ingroup Components | |
38 | \section IpcacheIntroduction Introduction | |
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). | |
46 | */ | |
47 | ||
48 | /** | |
49 | \defgroup IPCacheInternal IP Cache Internals | |
50 | \ingroup IPCacheAPI | |
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 | |
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 | ||
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 | ||
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 | */ | |
130 | class ipcache_entry | |
131 | { | |
132 | CBDATA_CLASS(ipcache_entry); | |
133 | ||
134 | public: | |
135 | ipcache_entry(const char *); | |
136 | ~ipcache_entry(); | |
137 | ||
138 | hash_link hash; /* must be first */ | |
139 | time_t lastref; | |
140 | time_t expires; | |
141 | ipcache_addrs addrs; | |
142 | IpCacheLookupForwarder handler; | |
143 | char *error_message; | |
144 | ||
145 | dlink_node lru; | |
146 | unsigned short locks; | |
147 | struct Flags { | |
148 | Flags() : negcached(false), fromhosts(false) {} | |
149 | ||
150 | bool negcached; | |
151 | bool fromhosts; | |
152 | } flags; | |
153 | ||
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 | |
168 | void latestError(const char *text); | |
169 | ||
170 | protected: | |
171 | void updateTtl(const unsigned int rrTtl); | |
172 | }; | |
173 | ||
174 | /// \ingroup IPCacheInternal | |
175 | static struct _ipcache_stats { | |
176 | int requests; | |
177 | int replies; | |
178 | int hits; | |
179 | int misses; | |
180 | int negative_hits; | |
181 | int numeric_hits; | |
182 | int rr_a; | |
183 | int rr_aaaa; | |
184 | int rr_cname; | |
185 | int cname_only; | |
186 | int invalid; | |
187 | } IpcacheStats; | |
188 | ||
189 | /// \ingroup IPCacheInternal | |
190 | static dlink_list lru_list; | |
191 | ||
192 | // forward-decls | |
193 | static void stat_ipcache_get(StoreEntry *); | |
194 | ||
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); | |
205 | ||
206 | /// \ingroup IPCacheInternal | |
207 | static hash_table *ip_table = nullptr; | |
208 | ||
209 | /// \ingroup IPCacheInternal | |
210 | static long ipcache_low = 180; | |
211 | /// \ingroup IPCacheInternal | |
212 | static long ipcache_high = 200; | |
213 | ||
214 | #if LIBRESOLV_DNS_TTL_HACK | |
215 | extern int _dns_ttl_; | |
216 | #endif | |
217 | ||
218 | CBDATA_CLASS_INIT(ipcache_entry); | |
219 | ||
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) | |
227 | { | |
228 | } | |
229 | ||
230 | void | |
231 | IpCacheLookupForwarder::finalCallback(const Dns::CachedIps * const possiblyEmptyAddrs, const Dns::LookupDetails &details) | |
232 | { | |
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 | ||
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) { | |
243 | if (receiverData.valid()) | |
244 | receiverFun(addrs, details, receiverData.validDone()); | |
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()) { | |
286 | receiver->noteLookup(Dns::LookupDetails(SBuf(error), additionalLookupDelay())); | |
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 | ||
296 | /** | |
297 | \ingroup IPCacheInternal | |
298 | * | |
299 | * removes the given ipcache entry | |
300 | */ | |
301 | static void | |
302 | ipcacheRelease(ipcache_entry * i, bool dofree) | |
303 | { | |
304 | if (!i) { | |
305 | debugs(14, DBG_CRITICAL, "ipcacheRelease: Releasing entry with i=<NULL>"); | |
306 | return; | |
307 | } | |
308 | ||
309 | if (!i || !i->hash.key) { | |
310 | debugs(14, DBG_CRITICAL, "ipcacheRelease: Releasing entry without hash link!"); | |
311 | return; | |
312 | } | |
313 | ||
314 | debugs(14, 3, "ipcacheRelease: Releasing entry for '" << (const char *) i->hash.key << "'"); | |
315 | ||
316 | hash_remove_link(ip_table, (hash_link *) i); | |
317 | dlinkDelete(&i->lru, &lru_list); | |
318 | if (dofree) | |
319 | ipcacheFreeEntry(i); | |
320 | } | |
321 | ||
322 | /// \ingroup IPCacheInternal | |
323 | static ipcache_entry * | |
324 | ipcache_get(const char *name) | |
325 | { | |
326 | if (ip_table != nullptr) | |
327 | return (ipcache_entry *) hash_lookup(ip_table, name); | |
328 | else | |
329 | return nullptr; | |
330 | } | |
331 | ||
332 | /// \ingroup IPCacheInternal | |
333 | static int | |
334 | ipcacheExpiredEntry(ipcache_entry * i) | |
335 | { | |
336 | /* all static entries are locked, so this takes care of them too */ | |
337 | ||
338 | if (i->locks != 0) | |
339 | return 0; | |
340 | ||
341 | if (i->addrs.empty()) | |
342 | if (0 == i->flags.negcached) | |
343 | return 1; | |
344 | ||
345 | if (i->expires > squid_curtime) | |
346 | return 0; | |
347 | ||
348 | return 1; | |
349 | } | |
350 | ||
351 | /// \ingroup IPCacheAPI | |
352 | void | |
353 | ipcache_purgelru(void *) | |
354 | { | |
355 | dlink_node *m; | |
356 | dlink_node *prev = nullptr; | |
357 | ipcache_entry *i; | |
358 | int removed = 0; | |
359 | eventAdd("ipcache_purgelru", ipcache_purgelru, nullptr, 10.0, 1); | |
360 | ||
361 | for (m = lru_list.tail; m; m = prev) { | |
362 | if (ipcacheCount() < ipcache_low) | |
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 | ||
374 | ++removed; | |
375 | } | |
376 | ||
377 | debugs(14, 9, "ipcache_purgelru: removed " << removed << " entries"); | |
378 | } | |
379 | ||
380 | /** | |
381 | \ingroup IPCacheInternal | |
382 | * | |
383 | * purges entries added from /etc/hosts (or whatever). | |
384 | */ | |
385 | static void | |
386 | purge_entries_fromhosts(void) | |
387 | { | |
388 | dlink_node *m = lru_list.head; | |
389 | ipcache_entry *i = nullptr, *t; | |
390 | ||
391 | while (m) { | |
392 | if (i != nullptr) { /* need to delay deletion */ | |
393 | ipcacheRelease(i); /* we just override locks */ | |
394 | i = nullptr; | |
395 | } | |
396 | ||
397 | t = (ipcache_entry*)m->data; | |
398 | ||
399 | if (t->flags.fromhosts) | |
400 | i = t; | |
401 | ||
402 | m = m->next; | |
403 | } | |
404 | ||
405 | if (i != nullptr) | |
406 | ipcacheRelease(i); | |
407 | } | |
408 | ||
409 | ipcache_entry::ipcache_entry(const char *aName): | |
410 | lastref(0), | |
411 | expires(0), | |
412 | error_message(nullptr), | |
413 | locks(0) // XXX: use Lock type ? | |
414 | { | |
415 | hash.key = xstrdup(aName); | |
416 | Tolower(static_cast<char*>(hash.key)); | |
417 | expires = squid_curtime + Config.negativeDnsTtl; | |
418 | } | |
419 | ||
420 | /// \ingroup IPCacheInternal | |
421 | static void | |
422 | ipcacheAddEntry(ipcache_entry * i) | |
423 | { | |
424 | hash_link *e = (hash_link *)hash_lookup(ip_table, i->hash.key); | |
425 | ||
426 | if (nullptr != e) { | |
427 | /* avoid collision */ | |
428 | ipcache_entry *q = (ipcache_entry *) e; | |
429 | ipcacheRelease(q); | |
430 | } | |
431 | ||
432 | hash_join(ip_table, &i->hash); | |
433 | dlinkAdd(i, &i->lru, &lru_list); | |
434 | i->lastref = squid_curtime; | |
435 | } | |
436 | ||
437 | /** | |
438 | \ingroup IPCacheInternal | |
439 | * | |
440 | * walks down the pending list, calling handlers | |
441 | */ | |
442 | static void | |
443 | ipcacheCallback(ipcache_entry *i, const bool hit, const int wait) | |
444 | { | |
445 | i->lastref = squid_curtime; | |
446 | ||
447 | ipcacheLockEntry(i); | |
448 | ||
449 | if (hit) | |
450 | i->handler.forwardHits(i->addrs); | |
451 | const Dns::LookupDetails details(SBuf(i->error_message), wait); | |
452 | i->handler.finalCallback(&i->addrs, details); | |
453 | ||
454 | ipcacheUnlockEntry(i); | |
455 | } | |
456 | ||
457 | void | |
458 | ipcache_entry::latestError(const char *text) | |
459 | { | |
460 | debugs(14, 3, "ERROR: DNS failure while resolving " << name() << ": " << text); | |
461 | safe_free(error_message); | |
462 | error_message = xstrdup(text); | |
463 | } | |
464 | ||
465 | static void | |
466 | ipcacheParse(ipcache_entry *i, const rfc1035_rr * answers, int nr, const char *error_message) | |
467 | { | |
468 | int k; | |
469 | ||
470 | // XXX: Callers use zero ancount instead of -1 on errors! | |
471 | if (nr < 0) { | |
472 | i->latestError(error_message); | |
473 | return; | |
474 | } | |
475 | ||
476 | if (nr == 0) { | |
477 | i->latestError("No DNS records"); | |
478 | return; | |
479 | } | |
480 | ||
481 | debugs(14, 3, nr << " answers for " << i->name()); | |
482 | assert(answers); | |
483 | ||
484 | for (k = 0; k < nr; ++k) { | |
485 | ||
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); | |
489 | continue; | |
490 | } | |
491 | ||
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); | |
495 | continue; | |
496 | } | |
497 | ||
498 | /* With A and AAAA, the CNAME does not necessarily come with additional records to use. */ | |
499 | if (answers[k].type == RFC1035_TYPE_CNAME) { | |
500 | i->sawCname = true; | |
501 | ++IpcacheStats.rr_cname; | |
502 | continue; | |
503 | } | |
504 | ||
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]) ); | |
507 | } | |
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()); | |
517 | return; | |
518 | } | |
519 | ||
520 | ++specs.recordCounter; | |
521 | ||
522 | // Do not store more than 255 addresses (TODO: Why?) | |
523 | if (addrs.raw().size() >= 255) | |
524 | return; | |
525 | ||
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; | |
531 | } | |
532 | addrs.pushUnique(address); | |
533 | ||
534 | updateTtl(rr.ttl); | |
535 | ||
536 | debugs(14, 3, name() << " #" << addrs.size() << " " << ip); | |
537 | handler.forwardIp(ip); // we are only called with good IPs | |
538 | } | |
539 | ||
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; | |
549 | if (addrs.size() <= 1) { | |
550 | debugs(14, 5, "use first " << ttl << " from RR TTL " << rrTtl); | |
551 | expires = rrExpires; | |
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 | } | |
558 | } | |
559 | ||
560 | /// \ingroup IPCacheInternal | |
561 | static void | |
562 | ipcacheHandleReply(void *data, const rfc1035_rr * answers, int na, const char *error_message, const bool lastAnswer) | |
563 | { | |
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 | ||
572 | ++IpcacheStats.replies; | |
573 | const auto age = i->handler.totalResponseTime(); | |
574 | statCounter.dns.svcTime.count(age); | |
575 | ||
576 | if (i->addrs.empty()) { | |
577 | i->flags.negcached = true; | |
578 | i->expires = squid_curtime + Config.negativeDnsTtl; | |
579 | ||
580 | if (!i->error_message) { | |
581 | i->latestError("No valid address records"); | |
582 | if (i->sawCname) | |
583 | ++IpcacheStats.cname_only; | |
584 | } | |
585 | } | |
586 | ||
587 | debugs(14, 3, "done with " << i->name() << ": " << i->addrs); | |
588 | ipcacheAddEntry(i); | |
589 | ipcacheCallback(i, false, age); | |
590 | } | |
591 | ||
592 | /** | |
593 | \ingroup IPCacheAPI | |
594 | * | |
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. | |
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, | |
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. | |
607 | */ | |
608 | void | |
609 | ipcache_nbgethostbyname(const char *name, IPH * handler, void *handlerData) | |
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) | |
625 | { | |
626 | ipcache_entry *i = nullptr; | |
627 | const ipcache_addrs *addrs = nullptr; | |
628 | ++IpcacheStats.requests; | |
629 | ||
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); | |
635 | return; | |
636 | } | |
637 | ||
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); | |
644 | return; | |
645 | } | |
646 | ||
647 | i = ipcache_get(name); | |
648 | ||
649 | if (nullptr == i) { | |
650 | /* miss */ | |
651 | (void) 0; | |
652 | } else if (ipcacheExpiredEntry(i)) { | |
653 | /* hit, but expired -- bummer */ | |
654 | ipcacheRelease(i); | |
655 | i = nullptr; | |
656 | } else { | |
657 | /* hit */ | |
658 | debugs(14, 4, "ipcache_nbgethostbyname: HIT for '" << name << "'"); | |
659 | ||
660 | if (i->flags.negcached) | |
661 | ++IpcacheStats.negative_hits; | |
662 | else | |
663 | ++IpcacheStats.hits; | |
664 | ||
665 | i->handler = std::move(handler); | |
666 | ipcacheCallback(i, true, -1); // no lookup | |
667 | ||
668 | return; | |
669 | } | |
670 | ||
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); | |
677 | } | |
678 | ||
679 | /// \ingroup IPCacheInternal | |
680 | static void | |
681 | ipcacheRegisterWithCacheManager(void) | |
682 | { | |
683 | Mgr::RegisterAction("ipcache", | |
684 | "IP Cache Stats and Contents", | |
685 | stat_ipcache_get, 0, 1); | |
686 | } | |
687 | ||
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 | */ | |
695 | void | |
696 | ipcache_init(void) | |
697 | { | |
698 | int n; | |
699 | debugs(14, Important(24), "Initializing IP Cache..."); | |
700 | memset(&IpcacheStats, '\0', sizeof(IpcacheStats)); | |
701 | lru_list = dlink_list(); | |
702 | ||
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); | |
709 | ||
710 | ipcacheRegisterWithCacheManager(); | |
711 | } | |
712 | ||
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 | * | |
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. | |
723 | * | |
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 | |
727 | */ | |
728 | const ipcache_addrs * | |
729 | ipcache_gethostbyname(const char *name, int flags) | |
730 | { | |
731 | ipcache_entry *i = nullptr; | |
732 | assert(name); | |
733 | debugs(14, 3, "'" << name << "', flags=" << asHex(flags)); | |
734 | ++IpcacheStats.requests; | |
735 | i = ipcache_get(name); | |
736 | ||
737 | if (nullptr == i) { | |
738 | (void) 0; | |
739 | } else if (ipcacheExpiredEntry(i)) { | |
740 | ipcacheRelease(i); | |
741 | i = nullptr; | |
742 | } else if (i->flags.negcached) { | |
743 | ++IpcacheStats.negative_hits; | |
744 | // ignore i->error_message: the caller just checks IP cache presence | |
745 | return nullptr; | |
746 | } else { | |
747 | ++IpcacheStats.hits; | |
748 | i->lastref = squid_curtime; | |
749 | // ignore i->error_message: the caller just checks IP cache presence | |
750 | return &i->addrs; | |
751 | } | |
752 | ||
753 | /* no entry [any more] */ | |
754 | ||
755 | if (const auto addrs = ipcacheCheckNumeric(name)) { | |
756 | ++IpcacheStats.numeric_hits; | |
757 | return addrs; | |
758 | } | |
759 | ||
760 | ++IpcacheStats.misses; | |
761 | ||
762 | if (flags & IP_LOOKUP_IF_MISS) | |
763 | ipcache_nbgethostbyname(name, nullptr, nullptr); | |
764 | ||
765 | return nullptr; | |
766 | } | |
767 | ||
768 | /// \ingroup IPCacheInternal | |
769 | static void | |
770 | ipcacheStatPrint(ipcache_entry * i, StoreEntry * sentry) | |
771 | { | |
772 | char buf[MAX_IPSTRLEN]; | |
773 | ||
774 | if (!sentry) { | |
775 | debugs(14, DBG_CRITICAL, "ERROR: sentry is NULL!"); | |
776 | return; | |
777 | } | |
778 | ||
779 | if (!i) { | |
780 | debugs(14, DBG_CRITICAL, "ERROR: ipcache_entry is NULL!"); | |
781 | storeAppendPrintf(sentry, "CRITICAL ERROR\n"); | |
782 | return; | |
783 | } | |
784 | ||
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())); | |
793 | ||
794 | /** \par | |
795 | * Negative-cached entries have no IPs listed. */ | |
796 | if (i->flags.negcached) { | |
797 | storeAppendPrintf(sentry, "\n"); | |
798 | return; | |
799 | } | |
800 | ||
801 | /** \par | |
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", | |
808 | indent, | |
809 | addr.ip.toStr(buf, MAX_IPSTRLEN), | |
810 | addr.bad() ? "BAD" : "OK "); | |
811 | firstLine = false; | |
812 | } | |
813 | } | |
814 | ||
815 | /** | |
816 | \ingroup IPCacheInternal | |
817 | * | |
818 | * process objects list | |
819 | */ | |
820 | void | |
821 | stat_ipcache_get(StoreEntry * sentry) | |
822 | { | |
823 | dlink_node *m; | |
824 | assert(ip_table != nullptr); | |
825 | storeAppendPrintf(sentry, "IP Cache Statistics:\n"); | |
826 | storeAppendPrintf(sentry, "IPcache Entries Cached: %d\n", | |
827 | ipcacheCount()); | |
828 | storeAppendPrintf(sentry, "IPcache Requests: %d\n", | |
829 | IpcacheStats.requests); | |
830 | storeAppendPrintf(sentry, "IPcache Hits: %d\n", | |
831 | IpcacheStats.hits); | |
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", | |
839 | IpcacheStats.rr_a); | |
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", | |
851 | "Hostname", | |
852 | "Flg", | |
853 | "lstref", | |
854 | "TTL", | |
855 | "N(b)"); | |
856 | ||
857 | for (m = lru_list.head; m; m = m->next) { | |
858 | assert( m->next != m ); | |
859 | ipcacheStatPrint((ipcache_entry *)m->data, sentry); | |
860 | } | |
861 | } | |
862 | ||
863 | /// \ingroup IPCacheAPI | |
864 | void | |
865 | ipcacheInvalidate(const char *name) | |
866 | { | |
867 | ipcache_entry *i; | |
868 | ||
869 | if ((i = ipcache_get(name)) == nullptr) | |
870 | return; | |
871 | ||
872 | i->expires = squid_curtime; | |
873 | ||
874 | /* | |
875 | * NOTE, don't call ipcacheRelease here because we might be here due | |
876 | * to a thread started from a callback. | |
877 | */ | |
878 | } | |
879 | ||
880 | /// \ingroup IPCacheAPI | |
881 | void | |
882 | ipcacheInvalidateNegative(const char *name) | |
883 | { | |
884 | ipcache_entry *i; | |
885 | ||
886 | if ((i = ipcache_get(name)) == nullptr) | |
887 | return; | |
888 | ||
889 | if (i->flags.negcached) | |
890 | i->expires = squid_curtime; | |
891 | ||
892 | /* | |
893 | * NOTE, don't call ipcacheRelease here because we might be here due | |
894 | * to a thread started from a callback. | |
895 | */ | |
896 | } | |
897 | ||
898 | /// \ingroup IPCacheAPI | |
899 | static const Dns::CachedIps * | |
900 | ipcacheCheckNumeric(const char *name) | |
901 | { | |
902 | Ip::Address ip; | |
903 | if (!ip.fromHost(name)) | |
904 | return nullptr; | |
905 | ||
906 | debugs(14, 4, "HIT_BYPASS for " << name << "=" << ip); | |
907 | static Dns::CachedIps static_addrs; | |
908 | static_addrs.reset(ip); | |
909 | return &static_addrs; | |
910 | } | |
911 | ||
912 | /// \ingroup IPCacheInternal | |
913 | static void | |
914 | ipcacheLockEntry(ipcache_entry * i) | |
915 | { | |
916 | if (i->locks++ == 0) { | |
917 | dlinkDelete(&i->lru, &lru_list); | |
918 | dlinkAdd(i, &i->lru, &lru_list); | |
919 | } | |
920 | } | |
921 | ||
922 | /// \ingroup IPCacheInternal | |
923 | static void | |
924 | ipcacheUnlockEntry(ipcache_entry * i) | |
925 | { | |
926 | if (i->locks < 1) { | |
927 | debugs(14, DBG_IMPORTANT, "WARNING: ipcacheEntry unlocked with no lock! locks=" << i->locks); | |
928 | return; | |
929 | } | |
930 | ||
931 | -- i->locks; | |
932 | ||
933 | if (ipcacheExpiredEntry(i)) | |
934 | ipcacheRelease(i); | |
935 | } | |
936 | ||
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) | |
941 | { | |
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 | } | |
950 | } | |
951 | goodPosition = ips.size(); | |
952 | debugs(14, 3, "failed for " << name << ": " << *this); | |
953 | return false; | |
954 | } | |
955 | ||
956 | void | |
957 | Dns::CachedIps::reset(const Ip::Address &ip) | |
958 | { | |
959 | ips.clear(); | |
960 | ips.emplace_back(ip); | |
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 | } | |
966 | ||
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; | |
977 | debugs(14, 3, "cleared all " << size() << " bad IPs for " << name); | |
978 | // fall through to reset goodPosition and report the current state | |
979 | } | |
980 | Must(seekNewGood(name)); | |
981 | } | |
982 | ||
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; | |
992 | debugs(14, 7, ip << " at " << pos << " in " << *this); | |
993 | return true; | |
994 | } | |
995 | ++pos; // TODO: Replace with std::views::enumerate() after upgrading to C++23 | |
996 | } | |
997 | // no such address; leave *position as is | |
998 | debugs(14, 7, " no " << ip << " in " << *this); | |
999 | return false; | |
1000 | } | |
1001 | ||
1002 | void | |
1003 | Dns::CachedIps::pushUnique(const Ip::Address &ip) | |
1004 | { | |
1005 | assert(!have(ip)); | |
1006 | [[maybe_unused]] auto &cachedIp = ips.emplace_back(ip); | |
1007 | assert(!cachedIp.bad()); | |
1008 | } | |
1009 | ||
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 | } | |
1020 | ||
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 | |
1027 | ||
1028 | auto &cachedIp = ips[badPosition]; | |
1029 | if (cachedIp.bad()) | |
1030 | return; // already marked correctly | |
1031 | ||
1032 | cachedIp.markAsBad(); | |
1033 | ++badCount_; | |
1034 | debugs(14, 2, ip << " of " << name); | |
1035 | ||
1036 | if (goodPosition == badPosition) | |
1037 | restoreGoodness(name); | |
1038 | // else nothing to do: goodPositon still points to a good IP | |
1039 | } | |
1040 | ||
1041 | void | |
1042 | Dns::CachedIps::forgetMarking(const char *name, const Ip::Address &ip) | |
1043 | { | |
1044 | if (!badCount_) | |
1045 | return; // all IPs are already "good" | |
1046 | ||
1047 | size_t badPosition = 0; | |
1048 | if (!have(ip, &badPosition)) | |
1049 | return; // no such address | |
1050 | ||
1051 | auto &cachedIp = ips[badPosition]; | |
1052 | if (!cachedIp.bad()) | |
1053 | return; // already marked correctly | |
1054 | ||
1055 | cachedIp.forgetMarking(); | |
1056 | assert(!cachedIp.bad()); | |
1057 | --badCount_; | |
1058 | debugs(14, 2, ip << " of " << name); | |
1059 | } | |
1060 | ||
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 | |
1066 | \param addr specific address to be marked bad | |
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); | |
1073 | } | |
1074 | ||
1075 | /// \ingroup IPCacheAPI | |
1076 | void | |
1077 | ipcacheMarkGoodAddr(const char *name, const Ip::Address &addr) | |
1078 | { | |
1079 | if (auto cached = ipcache_get(name)) | |
1080 | cached->addrs.forgetMarking(name, addr); | |
1081 | } | |
1082 | ||
1083 | /// \ingroup IPCacheInternal | |
1084 | static void | |
1085 | ipcacheFreeEntry(void *data) | |
1086 | { | |
1087 | ipcache_entry *i = (ipcache_entry *)data; | |
1088 | delete i; | |
1089 | } | |
1090 | ||
1091 | ipcache_entry::~ipcache_entry() | |
1092 | { | |
1093 | xfree(error_message); | |
1094 | xfree(hash.key); | |
1095 | } | |
1096 | ||
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 | */ | |
1104 | void | |
1105 | ipcache_restart(void) | |
1106 | { | |
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(); | |
1112 | } | |
1113 | ||
1114 | /** | |
1115 | \ingroup IPCacheAPI | |
1116 | * | |
1117 | * Adds a "static" entry from /etc/hosts | |
1118 | * | |
1119 | \param name Hostname to be linked with IP | |
1120 | \param ipaddr IP Address to be cached. | |
1121 | * | |
1122 | \retval 0 Success. | |
1123 | \retval 1 IP address is invalid or other error. | |
1124 | */ | |
1125 | int | |
1126 | ipcacheAddEntryFromHosts(const char *name, const char *ipaddr) | |
1127 | { | |
1128 | ipcache_entry *i; | |
1129 | ||
1130 | Ip::Address ip; | |
1131 | ||
1132 | if (!(ip = ipaddr)) { | |
1133 | if (strchr(ipaddr, ':') && strspn(ipaddr, "0123456789abcdefABCDEF:") == strlen(ipaddr)) { | |
1134 | debugs(14, 3, "ipcacheAddEntryFromHosts: Skipping IPv6 address '" << ipaddr << "'"); | |
1135 | } else { | |
1136 | debugs(14, DBG_IMPORTANT, "ERROR: ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr << "'"); | |
1137 | } | |
1138 | ||
1139 | return 1; | |
1140 | } | |
1141 | ||
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 | ||
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 << "'"); | |
1152 | return 1; | |
1153 | } else { | |
1154 | ipcacheRelease(i); | |
1155 | } | |
1156 | } | |
1157 | ||
1158 | i = new ipcache_entry(name); | |
1159 | i->addrs.pushUnique(ip); | |
1160 | i->flags.fromhosts = true; | |
1161 | ipcacheAddEntry(i); | |
1162 | ipcacheLockEntry(i); | |
1163 | return 0; | |
1164 | } | |
1165 | ||
1166 | #if SQUID_SNMP | |
1167 | /** | |
1168 | \ingroup IPCacheAPI | |
1169 | * | |
1170 | * The function to return the ip cache statistics to via SNMP | |
1171 | */ | |
1172 | variable_list * | |
1173 | snmp_netIpFn(variable_list * Var, snint * ErrP) | |
1174 | { | |
1175 | variable_list *Answer = nullptr; | |
1176 | MemBuf tmp; | |
1177 | debugs(49, 5, "snmp_netIpFn: Processing request:" << snmpDebugOid(Var->name, Var->name_length, tmp)); | |
1178 | *ErrP = SNMP_ERR_NOERROR; | |
1179 | ||
1180 | switch (Var->name[LEN_SQ_NET + 1]) { | |
1181 | ||
1182 | case IP_ENT: | |
1183 | Answer = snmp_var_new_integer(Var->name, Var->name_length, | |
1184 | ipcacheCount(), | |
1185 | SMI_GAUGE32); | |
1186 | break; | |
1187 | ||
1188 | case IP_REQ: | |
1189 | Answer = snmp_var_new_integer(Var->name, Var->name_length, | |
1190 | IpcacheStats.requests, | |
1191 | SMI_COUNTER32); | |
1192 | break; | |
1193 | ||
1194 | case IP_HITS: | |
1195 | Answer = snmp_var_new_integer(Var->name, Var->name_length, | |
1196 | IpcacheStats.hits, | |
1197 | SMI_COUNTER32); | |
1198 | break; | |
1199 | ||
1200 | case IP_PENDHIT: | |
1201 | Answer = snmp_var_new_integer(Var->name, Var->name_length, | |
1202 | 0, | |
1203 | SMI_GAUGE32); | |
1204 | break; | |
1205 | ||
1206 | case IP_NEGHIT: | |
1207 | Answer = snmp_var_new_integer(Var->name, Var->name_length, | |
1208 | IpcacheStats.negative_hits, | |
1209 | SMI_COUNTER32); | |
1210 | break; | |
1211 | ||
1212 | case IP_MISS: | |
1213 | Answer = snmp_var_new_integer(Var->name, Var->name_length, | |
1214 | IpcacheStats.misses, | |
1215 | SMI_COUNTER32); | |
1216 | break; | |
1217 | ||
1218 | case IP_GHBN: | |
1219 | Answer = snmp_var_new_integer(Var->name, Var->name_length, | |
1220 | 0, /* deprecated */ | |
1221 | SMI_COUNTER32); | |
1222 | break; | |
1223 | ||
1224 | case IP_LOC: | |
1225 | Answer = snmp_var_new_integer(Var->name, Var->name_length, | |
1226 | 0, /* deprecated */ | |
1227 | SMI_COUNTER32); | |
1228 | break; | |
1229 | ||
1230 | default: | |
1231 | *ErrP = SNMP_ERR_NOSUCHNAME; | |
1232 | assert(!Answer); | |
1233 | } | |
1234 | ||
1235 | return Answer; | |
1236 | } | |
1237 | ||
1238 | #endif /*SQUID_SNMP */ | |
1239 |