]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ipcache.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / ipcache.cc
CommitLineData
30a4f2a8 1/*
bde978a6 2 * Copyright (C) 1996-2015 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"
62ee09ca 12#include "CacheManager.h"
602d9612 13#include "cbdata.h"
17852883 14#include "dlink.h"
cfd66529
AJ
15#include "DnsLookupDetails.h"
16#include "event.h"
17#include "ip/Address.h"
055421ee 18#include "ip/tools.h"
714e68b7 19#include "ipcache.h"
8822ebee 20#include "mgr/Registration.h"
582c2af2 21#include "rfc3596.h"
4d5904f7 22#include "SquidConfig.h"
f64091a7 23#include "SquidDns.h"
985c86bc 24#include "SquidTime.h"
e4f1fdae 25#include "StatCounters.h"
e6ccf245 26#include "Store.h"
ed6e9fb9 27#include "util.h"
d295d770 28#include "wordlist.h"
44a47c6e 29
9c0a2256
FC
30#if SQUID_SNMP
31#include "snmp_core.h"
32#endif
33
63be0a78 34/**
35 \defgroup IPCacheAPI IP Cache API
36 \ingroup Components
37 \section Introduction Introduction
38 \par
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).
45 *
46 \todo IP Cache should have its own API *.h header file.
47 */
48
49/**
50 \defgroup IPCacheInternal IP Cache Internals
51 \ingroup IPCacheAPI
52 \todo when IP cache is provided as a class. These sub-groups will be obsolete
f53969cc
SM
53 * for now they are used to seperate the public and private functions.
54 * with the private ones all being in IPCachInternal and public in IPCacheAPI
63be0a78 55 *
56 \section InternalOperation Internal Operation
57 *
58 * Internally, the execution flow is as follows: On a miss,
59 * ipcache_getnbhostbyname checks whether a request for
60 * this name is already pending, and if positive, it creates
61 * a new entry using ipcacheAddNew with the IP_PENDING
62 * flag set . Then it calls ipcacheAddPending to add a
63 * request to the queue together with data and handler. Else,
64 * ipcache_dnsDispatch() is called to directly create a
65 * DNS query or to ipcacheEnqueue() if all no DNS port
66 * is free. ipcache_call_pending() is called regularly
67 * to walk down the pending list and call handlers. LRU clean-up
68 * is performed through ipcache_purgelru() according to
69 * the ipcache_high threshold.
70 */
71
63be0a78 72/**
73 \ingroup IPCacheAPI
74 *
75 * The data structure used for storing name-address mappings
76 * is a small hashtable (static hash_table *ip_table),
77 * where structures of type ipcache_entry whose most
78 * interesting members are:
79 */
e1381638
AJ
80class ipcache_entry
81{
3ff65596 82public:
f53969cc 83 hash_link hash; /* must be first */
ecc3091b 84 time_t lastref;
85 time_t expires;
86 ipcache_addrs addrs;
87 IPH *handler;
88 void *handlerData;
89 char *error_message;
62e76326 90
ecc3091b 91 struct timeval request_time;
92 dlink_node lru;
ac06f720 93 unsigned short locks;
26ac0430 94 struct {
be4d35dc
FC
95 bool negcached;
96 bool fromhosts;
2fadd50d 97 } flags;
3ff65596
AR
98
99 int age() const; ///< time passed since request_time or -1 if unknown
ecc3091b 100};
101
63be0a78 102/// \ingroup IPCacheInternal
26ac0430 103static struct _ipcache_stats {
30a4f2a8 104 int requests;
f88bb09c 105 int replies;
30a4f2a8 106 int hits;
107 int misses;
30a4f2a8 108 int negative_hits;
22b245f8 109 int numeric_hits;
bae9832d 110 int rr_a;
111 int rr_aaaa;
112 int rr_cname;
113 int cname_only;
22b245f8 114 int invalid;
2fadd50d 115} IpcacheStats;
090089c4 116
63be0a78 117/// \ingroup IPCacheInternal
ce75f381 118static dlink_list lru_list;
7b04dad5 119
59a09b98
FC
120// forward-decls
121static void stat_ipcache_get(StoreEntry *);
122
74addf6c 123static FREE ipcacheFreeEntry;
7b724b86 124static IDNSCB ipcacheHandleReply;
74addf6c 125static int ipcacheExpiredEntry(ipcache_entry *);
33ab4aaf 126static int ipcacheParse(ipcache_entry *, const rfc1035_rr *, int, const char *error);
f5b8bbc4 127static ipcache_entry *ipcache_get(const char *);
74addf6c 128static void ipcacheLockEntry(ipcache_entry *);
f5b8bbc4 129static void ipcacheStatPrint(ipcache_entry *, StoreEntry *);
130static void ipcacheUnlockEntry(ipcache_entry *);
cc192b50 131static void ipcacheRelease(ipcache_entry *, bool dofree = true);
30a4f2a8 132
63be0a78 133/// \ingroup IPCacheInternal
e5f6c5c2 134static ipcache_addrs static_addrs;
63be0a78 135/// \ingroup IPCacheInternal
365e5b34 136static hash_table *ip_table = NULL;
090089c4 137
63be0a78 138/// \ingroup IPCacheInternal
24382924 139static long ipcache_low = 180;
63be0a78 140/// \ingroup IPCacheInternal
24382924 141static long ipcache_high = 200;
f88bb09c 142
c021888f 143#if LIBRESOLV_DNS_TTL_HACK
144extern int _dns_ttl_;
145#endif
146
ac49890a
CT
147/// \ingroup IPCacheInternal
148inline int ipcacheCount() { return ip_table ? ip_table->count : 0; }
149
3ff65596
AR
150int
151ipcache_entry::age() const
152{
153 return request_time.tv_sec ? tvSubMsec(request_time, current_time) : -1;
154}
155
63be0a78 156/**
157 \ingroup IPCacheInternal
158 *
159 * removes the given ipcache entry
160 */
b8d8561b 161static void
cc192b50 162ipcacheRelease(ipcache_entry * i, bool dofree)
090089c4 163{
26ac0430 164 if (!i) {
fa84c01d 165 debugs(14, DBG_CRITICAL, "ipcacheRelease: Releasing entry with i=<NULL>");
cc192b50 166 return;
167 }
168
26ac0430 169 if (!i || !i->hash.key) {
fa84c01d 170 debugs(14, DBG_CRITICAL, "ipcacheRelease: Releasing entry without hash link!");
cc192b50 171 return;
172 }
173
bf8fe701 174 debugs(14, 3, "ipcacheRelease: Releasing entry for '" << (const char *) i->hash.key << "'");
175
ecc3091b 176 hash_remove_link(ip_table, (hash_link *) i);
7b04dad5 177 dlinkDelete(&i->lru, &lru_list);
26ac0430 178 if (dofree)
cc192b50 179 ipcacheFreeEntry(i);
090089c4 180}
181
63be0a78 182/// \ingroup IPCacheInternal
b8d8561b 183static ipcache_entry *
0ee4272b 184ipcache_get(const char *name)
090089c4 185{
ecc3091b 186 if (ip_table != NULL)
62e76326 187 return (ipcache_entry *) hash_lookup(ip_table, name);
ecc3091b 188 else
62e76326 189 return NULL;
090089c4 190}
191
63be0a78 192/// \ingroup IPCacheInternal
b8d8561b 193static int
194ipcacheExpiredEntry(ipcache_entry * i)
30a4f2a8 195{
0e70aa1e 196 /* all static entries are locked, so this takes care of them too */
62e76326 197
620da955 198 if (i->locks != 0)
62e76326 199 return 0;
200
7c63efed 201 if (i->addrs.count == 0)
62e76326 202 if (0 == i->flags.negcached)
203 return 1;
204
af00901c 205 if (i->expires > squid_curtime)
62e76326 206 return 0;
207
30a4f2a8 208 return 1;
209}
090089c4 210
63be0a78 211/// \ingroup IPCacheAPI
7b04dad5 212void
ced8def3 213ipcache_purgelru(void *)
7b04dad5 214{
215 dlink_node *m;
216 dlink_node *prev = NULL;
217 ipcache_entry *i;
218 int removed = 0;
52040193 219 eventAdd("ipcache_purgelru", ipcache_purgelru, NULL, 10.0, 1);
62e76326 220
7b04dad5 221 for (m = lru_list.tail; m; m = prev) {
ac49890a 222 if (ipcacheCount() < ipcache_low)
62e76326 223 break;
224
225 prev = m->prev;
226
227 i = (ipcache_entry *)m->data;
228
229 if (i->locks != 0)
230 continue;
231
232 ipcacheRelease(i);
233
95dc7ff4 234 ++removed;
7b04dad5 235 }
62e76326 236
bf8fe701 237 debugs(14, 9, "ipcache_purgelru: removed " << removed << " entries");
7b04dad5 238}
239
63be0a78 240/**
241 \ingroup IPCacheInternal
242 *
243 * purges entries added from /etc/hosts (or whatever).
244 */
0e70aa1e 245static void
246purge_entries_fromhosts(void)
247{
248 dlink_node *m = lru_list.head;
249 ipcache_entry *i = NULL, *t;
62e76326 250
0e70aa1e 251 while (m) {
f53969cc
SM
252 if (i != NULL) { /* need to delay deletion */
253 ipcacheRelease(i); /* we just override locks */
62e76326 254 i = NULL;
255 }
256
257 t = (ipcache_entry*)m->data;
258
259 if (t->flags.fromhosts)
260 i = t;
261
262 m = m->next;
0e70aa1e 263 }
62e76326 264
0e70aa1e 265 if (i != NULL)
62e76326 266 ipcacheRelease(i);
0e70aa1e 267}
268
63be0a78 269/**
270 \ingroup IPCacheInternal
271 *
272 * create blank ipcache_entry
273 */
b8d8561b 274static ipcache_entry *
ecc3091b 275ipcacheCreateEntry(const char *name)
090089c4 276{
7b04dad5 277 static ipcache_entry *i;
e6ccf245 278 i = (ipcache_entry *)memAllocate(MEM_IPCACHE_ENTRY);
186477c1 279 i->hash.key = xstrdup(name);
7176768b 280 Tolower(static_cast<char*>(i->hash.key));
7b04dad5 281 i->expires = squid_curtime + Config.negativeDnsTtl;
7b04dad5 282 return i;
090089c4 283}
284
63be0a78 285/// \ingroup IPCacheInternal
b8d8561b 286static void
ecc3091b 287ipcacheAddEntry(ipcache_entry * i)
dd7ad0a4 288{
e6ccf245 289 hash_link *e = (hash_link *)hash_lookup(ip_table, i->hash.key);
62e76326 290
ecc3091b 291 if (NULL != e) {
62e76326 292 /* avoid colission */
293 ipcache_entry *q = (ipcache_entry *) e;
2b72648d 294 ipcacheRelease(q);
ecc3091b 295 }
62e76326 296
186477c1 297 hash_join(ip_table, &i->hash);
ecc3091b 298 dlinkAdd(i, &i->lru, &lru_list);
dd7ad0a4 299 i->lastref = squid_curtime;
090089c4 300}
301
63be0a78 302/**
303 \ingroup IPCacheInternal
304 *
305 * walks down the pending list, calling handlers
306 */
b8d8561b 307static void
3ff65596 308ipcacheCallback(ipcache_entry *i, int wait)
090089c4 309{
fa80a8ef 310 IPH *callback = i->handler;
cc192b50 311 void *cbdata = NULL;
30a4f2a8 312 i->lastref = squid_curtime;
62e76326 313
fa80a8ef 314 if (!i->handler)
62e76326 315 return;
316
fa80a8ef 317 ipcacheLockEntry(i);
62e76326 318
fa80a8ef 319 callback = i->handler;
62e76326 320
ecc3091b 321 i->handler = NULL;
62e76326 322
fa80a8ef 323 if (cbdataReferenceValidDone(i->handlerData, &cbdata)) {
3ff65596
AR
324 const DnsLookupDetails details(i->error_message, wait);
325 callback((i->addrs.count ? &i->addrs : NULL), details, cbdata);
090089c4 326 }
62e76326 327
620da955 328 ipcacheUnlockEntry(i);
090089c4 329}
330
63be0a78 331/// \ingroup IPCacheAPI
7ba8d0b8 332static int
33ab4aaf 333ipcacheParse(ipcache_entry *i, const rfc1035_rr * answers, int nr, const char *error_message)
7b724b86 334{
7cfc1c9a 335 int k;
cc192b50 336 int j = 0;
7cfc1c9a 337 int na = 0;
7ba8d0b8 338 int ttl = 0;
339 const char *name = (const char *)i->hash.key;
bae9832d 340 int cname_found = 0;
341
7ba8d0b8 342 i->expires = squid_curtime + Config.negativeDnsTtl;
be4d35dc 343 i->flags.negcached = true;
7ba8d0b8 344 safe_free(i->addrs.in_addrs);
cc192b50 345 assert(i->addrs.in_addrs == NULL);
7ba8d0b8 346 safe_free(i->addrs.bad_mask);
cc192b50 347 assert(i->addrs.bad_mask == NULL);
7ba8d0b8 348 safe_free(i->error_message);
cc192b50 349 assert(i->error_message == NULL);
7ba8d0b8 350 i->addrs.count = 0;
62e76326 351
7cfc1c9a 352 if (nr < 0) {
bf8fe701 353 debugs(14, 3, "ipcacheParse: Lookup failed '" << error_message << "' for '" << (const char *)i->hash.key << "'");
7ba8d0b8 354 i->error_message = xstrdup(error_message);
355 return -1;
7cfc1c9a 356 }
62e76326 357
7cfc1c9a 358 if (nr == 0) {
bf8fe701 359 debugs(14, 3, "ipcacheParse: No DNS records in response to '" << name << "'");
7ba8d0b8 360 i->error_message = xstrdup("No DNS records");
cc192b50 361 return -1;
7cfc1c9a 362 }
62e76326 363
48e7baac 364 debugs(14, 3, "ipcacheParse: " << nr << " answers for '" << name << "'");
7cfc1c9a 365 assert(answers);
62e76326 366
95dc7ff4 367 for (k = 0; k < nr; ++k) {
62e76326 368
055421ee 369 if (Ip::EnableIpv6 && answers[k].type == RFC1035_TYPE_AAAA) {
26ac0430 370 if (answers[k].rdlength != sizeof(struct in6_addr)) {
e0236918 371 debugs(14, DBG_IMPORTANT, "ipcacheParse: Invalid IPv6 address in response to '" << name << "'");
26ac0430
AJ
372 continue;
373 }
95dc7ff4
FC
374 ++na;
375 ++IpcacheStats.rr_aaaa;
26ac0430
AJ
376 continue;
377 }
62e76326 378
cc192b50 379 if (answers[k].type == RFC1035_TYPE_A) {
26ac0430 380 if (answers[k].rdlength != sizeof(struct in_addr)) {
e0236918 381 debugs(14, DBG_IMPORTANT, "ipcacheParse: Invalid IPv4 address in response to '" << name << "'");
26ac0430
AJ
382 continue;
383 }
95dc7ff4
FC
384 ++na;
385 ++IpcacheStats.rr_a;
26ac0430
AJ
386 continue;
387 }
cc192b50 388
26ac0430 389 /* With A and AAAA, the CNAME does not necessarily come with additional records to use. */
cc192b50 390 if (answers[k].type == RFC1035_TYPE_CNAME) {
bae9832d 391 cname_found=1;
95dc7ff4 392 ++IpcacheStats.rr_cname;
470cd749 393 continue;
394 }
bae9832d 395
396 // otherwise its an unknown RR. debug at level 9 since we usually want to ignore these and they are common.
397 debugs(14, 9, HERE << "Unknown RR type received: type=" << answers[k].type << " starting at " << &(answers[k]) );
cc192b50 398 }
7cfc1c9a 399 if (na == 0) {
e0236918 400 debugs(14, DBG_IMPORTANT, "ipcacheParse: No Address records in response to '" << name << "'");
7ba8d0b8 401 i->error_message = xstrdup("No Address records");
26ac0430 402 if (cname_found)
95dc7ff4 403 ++IpcacheStats.cname_only;
7ba8d0b8 404 return 0;
7cfc1c9a 405 }
62e76326 406
b7ac5457 407 i->addrs.in_addrs = static_cast<Ip::Address *>(xcalloc(na, sizeof(Ip::Address)));
95dc7ff4 408 for (int l = 0; l < na; ++l)
4dd643d5 409 i->addrs.in_addrs[l].setEmpty(); // perform same init actions as constructor would.
7ba8d0b8 410 i->addrs.bad_mask = (unsigned char *)xcalloc(na, sizeof(unsigned char));
62e76326 411
95dc7ff4 412 for (j = 0, k = 0; k < nr; ++k) {
62e76326 413
2d320a3a 414 if (answers[k].type == RFC1035_TYPE_A) {
cc192b50 415 if (answers[k].rdlength != sizeof(struct in_addr))
2d320a3a 416 continue;
417
cc192b50 418 struct in_addr temp;
41d00cd3 419 memcpy(&temp, answers[k].rdata, sizeof(struct in_addr));
cc192b50 420 i->addrs.in_addrs[j] = temp;
2d320a3a 421
cc192b50 422 debugs(14, 3, "ipcacheParse: " << name << " #" << j << " " << i->addrs.in_addrs[j]);
95dc7ff4 423 ++j;
bf8fe701 424
055421ee 425 } else if (Ip::EnableIpv6 && answers[k].type == RFC1035_TYPE_AAAA) {
cc192b50 426 if (answers[k].rdlength != sizeof(struct in6_addr))
427 continue;
428
429 struct in6_addr temp;
41d00cd3 430 memcpy(&temp, answers[k].rdata, sizeof(struct in6_addr));
cc192b50 431 i->addrs.in_addrs[j] = temp;
432
433 debugs(14, 3, "ipcacheParse: " << name << " #" << j << " " << i->addrs.in_addrs[j] );
95dc7ff4 434 ++j;
bae9832d 435 }
7ba8d0b8 436 if (ttl == 0 || (int) answers[k].ttl < ttl)
437 ttl = answers[k].ttl;
7b724b86 438 }
62e76326 439
7cfc1c9a 440 assert(j == na);
7ba8d0b8 441
be6e7af9 442 if (na < 256)
443 i->addrs.count = (unsigned char) na;
444 else
445 i->addrs.count = 255;
7ba8d0b8 446
3e8c4107 447 if (ttl > Config.positiveDnsTtl)
7ba8d0b8 448 ttl = Config.positiveDnsTtl;
449
450 if (ttl < Config.negativeDnsTtl)
451 ttl = Config.negativeDnsTtl;
452
453 i->expires = squid_curtime + ttl;
454
be4d35dc 455 i->flags.negcached = false;
7ba8d0b8 456
7c16f24c 457 return i->addrs.count;
7b724b86 458}
62e76326 459
63be0a78 460/// \ingroup IPCacheInternal
a7e59001 461static void
33ab4aaf 462ipcacheHandleReply(void *data, const rfc1035_rr * answers, int na, const char *error_message)
090089c4 463{
aa839030 464 ipcache_entry *i;
465 static_cast<generic_cbdata *>(data)->unwrap(&i);
95dc7ff4 466 ++IpcacheStats.replies;
3ff65596 467 const int age = i->age();
e8baef82 468 statCounter.dns.svcTime.count(age);
e1381638 469
12f3560e 470 int done = ipcacheParse(i, answers, na, error_message);
cc192b50 471
472 /* If we have not produced either IPs or Error immediately, wait for recursion to finish. */
17852883 473 if (done != 0 || error_message != NULL) {
cc192b50 474 ipcacheAddEntry(i);
3ff65596 475 ipcacheCallback(i, age);
cc192b50 476 }
30a4f2a8 477}
478
63be0a78 479/**
480 \ingroup IPCacheAPI
481 *
f53969cc
SM
482 \param name Host to resolve.
483 \param handler Pointer to the function to be called when the reply
484 * from the IP cache (or the DNS if the IP cache misses)
485 \param handlerData Information that is passed to the handler and does not affect the IP cache.
79300bcb
AR
486 *
487 * XXX: on hits and some errors, the handler is called immediately instead
488 * of scheduling an async call. This reentrant behavior means that the
489 * user job must be extra careful after calling ipcache_nbgethostbyname,
26ac0430 490 * especially if the handler destroys the job. Moreover, the job has
55cbb02b
AJ
491 * no way of knowing whether the reentrant call happened.
492 * Comm::Connection setup usually protects the job by scheduling an async call,
493 * but some user code calls ipcache_nbgethostbyname directly.
63be0a78 494 */
b8d8561b 495void
8407afee 496ipcache_nbgethostbyname(const char *name, IPH * handler, void *handlerData)
090089c4 497{
30a4f2a8 498 ipcache_entry *i = NULL;
429fdbec 499 const ipcache_addrs *addrs = NULL;
74addf6c 500 generic_cbdata *c;
bf8fe701 501 debugs(14, 4, "ipcache_nbgethostbyname: Name '" << name << "'.");
95dc7ff4 502 ++IpcacheStats.requests;
62e76326 503
090089c4 504 if (name == NULL || name[0] == '\0') {
bf8fe701 505 debugs(14, 4, "ipcache_nbgethostbyname: Invalid name!");
95dc7ff4 506 ++IpcacheStats.invalid;
3ff65596 507 const DnsLookupDetails details("Invalid hostname", -1); // error, no lookup
7fd65651
A
508 if (handler)
509 handler(NULL, details, handlerData);
62e76326 510 return;
af00901c 511 }
62e76326 512
e5f6c5c2 513 if ((addrs = ipcacheCheckNumeric(name))) {
cc192b50 514 debugs(14, 4, "ipcache_nbgethostbyname: BYPASS for '" << name << "' (already numeric)");
95dc7ff4 515 ++IpcacheStats.numeric_hits;
3ff65596 516 const DnsLookupDetails details(NULL, -1); // no error, no lookup
7fd65651
A
517 if (handler)
518 handler(addrs, details, handlerData);
62e76326 519 return;
090089c4 520 }
62e76326 521
ecc3091b 522 i = ipcache_get(name);
62e76326 523
ecc3091b 524 if (NULL == i) {
62e76326 525 /* miss */
526 (void) 0;
ecc3091b 527 } else if (ipcacheExpiredEntry(i)) {
62e76326 528 /* hit, but expired -- bummer */
529 ipcacheRelease(i);
530 i = NULL;
ecc3091b 531 } else {
62e76326 532 /* hit */
bf8fe701 533 debugs(14, 4, "ipcache_nbgethostbyname: HIT for '" << name << "'");
62e76326 534
535 if (i->flags.negcached)
95dc7ff4 536 ++IpcacheStats.negative_hits;
62e76326 537 else
95dc7ff4 538 ++IpcacheStats.hits;
62e76326 539
540 i->handler = handler;
541
542 i->handlerData = cbdataReference(handlerData);
543
3ff65596 544 ipcacheCallback(i, -1); // no lookup
62e76326 545
546 return;
090089c4 547 }
62e76326 548
bf8fe701 549 debugs(14, 5, "ipcache_nbgethostbyname: MISS for '" << name << "'");
95dc7ff4 550 ++IpcacheStats.misses;
ecc3091b 551 i = ipcacheCreateEntry(name);
552 i->handler = handler;
fa80a8ef 553 i->handlerData = cbdataReference(handlerData);
ecc3091b 554 i->request_time = current_time;
aa839030 555 c = new generic_cbdata(i);
186477c1 556 idnsALookup(hashKeyStr(&i->hash), ipcacheHandleReply, c);
090089c4 557}
558
5f5e883f
FC
559/// \ingroup IPCacheInternal
560static void
561ipcacheRegisterWithCacheManager(void)
562{
8822ebee 563 Mgr::RegisterAction("ipcache",
d9fc6862
A
564 "IP Cache Stats and Contents",
565 stat_ipcache_get, 0, 1);
5f5e883f
FC
566}
567
63be0a78 568/**
569 \ingroup IPCacheAPI
570 *
571 * Initialize the ipcache.
572 * Is called from mainInitialize() after disk initialization
573 * and prior to the reverse FQDNCache initialization
574 */
b8d8561b 575void
0673c0ba 576ipcache_init(void)
0ffd22bc 577{
aa9e2cab 578 int n;
8eb28163 579 debugs(14, DBG_IMPORTANT, "Initializing IP Cache...");
30a4f2a8 580 memset(&IpcacheStats, '\0', sizeof(IpcacheStats));
3eb55834 581 memset(&lru_list, '\0', sizeof(lru_list));
e5f6c5c2 582 memset(&static_addrs, '\0', sizeof(ipcache_addrs));
62e76326 583
b7ac5457 584 static_addrs.in_addrs = static_cast<Ip::Address *>(xcalloc(1, sizeof(Ip::Address)));
4dd643d5 585 static_addrs.in_addrs->setEmpty(); // properly setup the Ip::Address!
e6ccf245 586 static_addrs.bad_mask = (unsigned char *)xcalloc(1, sizeof(unsigned char));
b15e6857 587 ipcache_high = (long) (((float) Config.ipcache.size *
62e76326 588 (float) Config.ipcache.high) / (float) 100);
b15e6857 589 ipcache_low = (long) (((float) Config.ipcache.size *
62e76326 590 (float) Config.ipcache.low) / (float) 100);
aa9e2cab 591 n = hashPrime(ipcache_high / 4);
30abd221 592 ip_table = hash_create((HASHCMP *) strcmp, n, hash4);
ecc3091b 593 memDataInit(MEM_IPCACHE_ENTRY, "ipcache_entry", sizeof(ipcache_entry), 0);
d120ed12
FC
594
595 ipcacheRegisterWithCacheManager();
090089c4 596}
597
63be0a78 598/**
599 \ingroup IPCacheAPI
600 *
601 * Is different from ipcache_nbgethostbyname in that it only checks
602 * if an entry exists in the cache and does not by default contact the DNS,
603 * unless this is requested, by setting the flags.
604 *
f53969cc
SM
605 \param name Host name to resolve.
606 \param flags Default is NULL, set to IP_LOOKUP_IF_MISS
607 * to explicitly perform DNS lookups.
63be0a78 608 *
f53969cc
SM
609 \retval NULL An error occured during lookup
610 \retval NULL No results available in cache and no lookup specified
611 \retval * Pointer to the ipcahce_addrs structure containing the lookup results
63be0a78 612 */
0ee4272b 613const ipcache_addrs *
614ipcache_gethostbyname(const char *name, int flags)
090089c4 615{
30a4f2a8 616 ipcache_entry *i = NULL;
e5f6c5c2 617 ipcache_addrs *addrs;
ecc3091b 618 assert(name);
bf8fe701 619 debugs(14, 3, "ipcache_gethostbyname: '" << name << "', flags=" << std::hex << flags);
95dc7ff4 620 ++IpcacheStats.requests;
ecc3091b 621 i = ipcache_get(name);
62e76326 622
ecc3091b 623 if (NULL == i) {
62e76326 624 (void) 0;
ecc3091b 625 } else if (ipcacheExpiredEntry(i)) {
62e76326 626 ipcacheRelease(i);
627 i = NULL;
ecc3091b 628 } else if (i->flags.negcached) {
95dc7ff4 629 ++IpcacheStats.negative_hits;
3ff65596 630 // ignore i->error_message: the caller just checks IP cache presence
62e76326 631 return NULL;
ecc3091b 632 } else {
95dc7ff4 633 ++IpcacheStats.hits;
62e76326 634 i->lastref = squid_curtime;
3ff65596 635 // ignore i->error_message: the caller just checks IP cache presence
62e76326 636 return &i->addrs;
30a4f2a8 637 }
62e76326 638
3ff65596 639 /* no entry [any more] */
2ffff82e 640
22b245f8 641 if ((addrs = ipcacheCheckNumeric(name))) {
95dc7ff4 642 ++IpcacheStats.numeric_hits;
62e76326 643 return addrs;
22b245f8 644 }
62e76326 645
95dc7ff4 646 ++IpcacheStats.misses;
62e76326 647
30a4f2a8 648 if (flags & IP_LOOKUP_IF_MISS)
7c16f24c 649 ipcache_nbgethostbyname(name, NULL, NULL);
62e76326 650
30a4f2a8 651 return NULL;
090089c4 652}
653
63be0a78 654/// \ingroup IPCacheInternal
b8d8561b 655static void
656ipcacheStatPrint(ipcache_entry * i, StoreEntry * sentry)
af00901c 657{
658 int k;
cc192b50 659 char buf[MAX_IPSTRLEN];
660
26ac0430 661 if (!sentry) {
fa84c01d 662 debugs(14, DBG_CRITICAL, HERE << "CRITICAL: sentry is NULL!");
71aab4cc 663 return;
cc192b50 664 }
665
26ac0430 666 if (!i) {
fa84c01d 667 debugs(14, DBG_CRITICAL, HERE << "CRITICAL: ipcache_entry is NULL!");
cc192b50 668 storeAppendPrintf(sentry, "CRITICAL ERROR\n");
669 return;
670 }
671
71aab4cc
AJ
672 int count = i->addrs.count;
673
181b1adc 674 storeAppendPrintf(sentry, " %-32.32s %c%c %6d %6d %2d(%2d)",
62e76326 675 hashKeyStr(&i->hash),
676 i->flags.fromhosts ? 'H' : ' ',
677 i->flags.negcached ? 'N' : ' ',
678 (int) (squid_curtime - i->lastref),
679 (int) ((i->flags.fromhosts ? -1 : i->expires - squid_curtime)),
680 (int) i->addrs.count,
681 (int) i->addrs.badcount);
682
cc192b50 683 /** \par
684 * Negative-cached entries have no IPs listed. */
26ac0430 685 if (i->flags.negcached) {
cc192b50 686 storeAppendPrintf(sentry, "\n");
687 return;
52926044 688 }
62e76326 689
cc192b50 690 /** \par
d85b8894 691 * Cached entries have IPs listed with a BNF of: ip-address '-' ('OK'|'BAD') */
95dc7ff4 692 for (k = 0; k < count; ++k) {
cc192b50 693 /* Display tidy-up: IPv6 are so big make the list vertical */
26ac0430 694 if (k == 0)
cc192b50 695 storeAppendPrintf(sentry, " %45.45s-%3s\n",
4dd643d5 696 i->addrs.in_addrs[k].toStr(buf,MAX_IPSTRLEN),
cc192b50 697 i->addrs.bad_mask[k] ? "BAD" : "OK ");
698 else
699 storeAppendPrintf(sentry, "%s %45.45s-%3s\n",
700 " ", /* blank-space indenting IP list */
4dd643d5 701 i->addrs.in_addrs[k].toStr(buf,MAX_IPSTRLEN),
cc192b50 702 i->addrs.bad_mask[k] ? "BAD" : "OK ");
703 }
af00901c 704}
090089c4 705
63be0a78 706/**
707 \ingroup IPCacheInternal
708 *
709 * process objects list
710 */
b8d8561b 711void
712stat_ipcache_get(StoreEntry * sentry)
090089c4 713{
7b04dad5 714 dlink_node *m;
715 assert(ip_table != NULL);
15576b6a 716 storeAppendPrintf(sentry, "IP Cache Statistics:\n");
ac49890a 717 storeAppendPrintf(sentry, "IPcache Entries In Use: %d\n",
62e76326 718 memInUse(MEM_IPCACHE_ENTRY));
ac49890a
CT
719 storeAppendPrintf(sentry, "IPcache Entries Cached: %d\n",
720 ipcacheCount());
15576b6a 721 storeAppendPrintf(sentry, "IPcache Requests: %d\n",
62e76326 722 IpcacheStats.requests);
22b245f8 723 storeAppendPrintf(sentry, "IPcache Hits: %d\n",
62e76326 724 IpcacheStats.hits);
22b245f8 725 storeAppendPrintf(sentry, "IPcache Negative Hits: %d\n",
62e76326 726 IpcacheStats.negative_hits);
22b245f8 727 storeAppendPrintf(sentry, "IPcache Numeric Hits: %d\n",
728 IpcacheStats.numeric_hits);
729 storeAppendPrintf(sentry, "IPcache Misses: %d\n",
62e76326 730 IpcacheStats.misses);
f2d71697 731 storeAppendPrintf(sentry, "IPcache Retrieved A: %d\n",
bae9832d 732 IpcacheStats.rr_a);
f2d71697 733 storeAppendPrintf(sentry, "IPcache Retrieved AAAA: %d\n",
bae9832d 734 IpcacheStats.rr_aaaa);
735 storeAppendPrintf(sentry, "IPcache Retrieved CNAME: %d\n",
736 IpcacheStats.rr_cname);
737 storeAppendPrintf(sentry, "IPcache CNAME-Only Response: %d\n",
738 IpcacheStats.cname_only);
22b245f8 739 storeAppendPrintf(sentry, "IPcache Invalid Request: %d\n",
740 IpcacheStats.invalid);
15576b6a 741 storeAppendPrintf(sentry, "\n\n");
742 storeAppendPrintf(sentry, "IP Cache Contents:\n\n");
cc192b50 743 storeAppendPrintf(sentry, " %-31.31s %3s %6s %6s %4s\n",
62e76326 744 "Hostname",
745 "Flg",
746 "lstref",
747 "TTL",
cc192b50 748 "N(b)");
62e76326 749
cc192b50 750 for (m = lru_list.head; m; m = m->next) {
751 assert( m->next != m );
62e76326 752 ipcacheStatPrint((ipcache_entry *)m->data, sentry);
cc192b50 753 }
754}
755
63be0a78 756/// \ingroup IPCacheAPI
b8d8561b 757void
0ee4272b 758ipcacheInvalidate(const char *name)
f900607e 759{
760 ipcache_entry *i;
62e76326 761
f900607e 762 if ((i = ipcache_get(name)) == NULL)
62e76326 763 return;
764
6c11e193 765 i->expires = squid_curtime;
62e76326 766
ecc3091b 767 /*
63be0a78 768 * NOTE, don't call ipcacheRelease here because we might be here due
ecc3091b 769 * to a thread started from a callback.
770 */
f900607e 771}
af00901c 772
63be0a78 773/// \ingroup IPCacheAPI
a12a049a 774void
775ipcacheInvalidateNegative(const char *name)
776{
777 ipcache_entry *i;
778
779 if ((i = ipcache_get(name)) == NULL)
780 return;
781
782 if (i->flags.negcached)
783 i->expires = squid_curtime;
784
785 /*
63be0a78 786 * NOTE, don't call ipcacheRelease here because we might be here due
a12a049a 787 * to a thread started from a callback.
788 */
789}
790
63be0a78 791/// \ingroup IPCacheAPI
4d650936 792ipcache_addrs *
0ee4272b 793ipcacheCheckNumeric(const char *name)
af00901c 794{
b7ac5457 795 Ip::Address ip;
af00901c 796 /* check if it's already a IP address in text form. */
62e76326 797
cc192b50 798 /* it may be IPv6-wrapped */
26ac0430 799 if (name[0] == '[') {
cc192b50 800 char *tmp = xstrdup(&name[1]);
801 tmp[strlen(tmp)-1] = '\0';
802 if (!(ip = tmp)) {
803 delete tmp;
804 return NULL;
805 }
806 delete tmp;
26ac0430 807 } else if (!(ip = name))
62e76326 808 return NULL;
809
cc192b50 810 debugs(14, 4, "ipcacheCheckNumeric: HIT_BYPASS for '" << name << "' == " << ip );
811
e5f6c5c2 812 static_addrs.count = 1;
62e76326 813
e5f6c5c2 814 static_addrs.cur = 0;
62e76326 815
cc192b50 816 static_addrs.in_addrs[0] = ip;
62e76326 817
22c653cd 818 static_addrs.bad_mask[0] = FALSE;
62e76326 819
22c653cd 820 static_addrs.badcount = 0;
62e76326 821
e5f6c5c2 822 return &static_addrs;
af00901c 823}
8905d949 824
63be0a78 825/// \ingroup IPCacheInternal
b8d8561b 826static void
827ipcacheLockEntry(ipcache_entry * i)
620da955 828{
7b04dad5 829 if (i->locks++ == 0) {
62e76326 830 dlinkDelete(&i->lru, &lru_list);
831 dlinkAdd(i, &i->lru, &lru_list);
7b04dad5 832 }
620da955 833}
834
63be0a78 835/// \ingroup IPCacheInternal
b8d8561b 836static void
837ipcacheUnlockEntry(ipcache_entry * i)
620da955 838{
26ac0430 839 if (i->locks < 1) {
e0236918 840 debugs(14, DBG_IMPORTANT, "WARNING: ipcacheEntry unlocked with no lock! locks=" << i->locks);
cc192b50 841 return;
842 }
843
5e263176 844 -- i->locks;
62e76326 845
620da955 846 if (ipcacheExpiredEntry(i))
62e76326 847 ipcacheRelease(i);
620da955 848}
e5f6c5c2 849
63be0a78 850/// \ingroup IPCacheAPI
52926044 851void
4b4cd312 852ipcacheCycleAddr(const char *name, ipcache_addrs * ia)
52926044 853{
854 ipcache_entry *i;
855 unsigned char k;
856 assert(name || ia);
62e76326 857
52926044 858 if (NULL == ia) {
62e76326 859 if ((i = ipcache_get(name)) == NULL)
860 return;
861
862 if (i->flags.negcached)
863 return;
864
865 ia = &i->addrs;
52926044 866 }
62e76326 867
95dc7ff4 868 for (k = 0; k < ia->count; ++k) {
62e76326 869 if (++ia->cur == ia->count)
870 ia->cur = 0;
871
872 if (!ia->bad_mask[ia->cur])
873 break;
52926044 874 }
62e76326 875
52926044 876 if (k == ia->count) {
62e76326 877 /* All bad, reset to All good */
bf8fe701 878 debugs(14, 3, "ipcacheCycleAddr: Changing ALL " << name << " addrs from BAD to OK");
62e76326 879
95dc7ff4 880 for (k = 0; k < ia->count; ++k)
62e76326 881 ia->bad_mask[k] = 0;
882
883 ia->badcount = 0;
884
885 ia->cur = 0;
52926044 886 }
62e76326 887
149b31df
AJ
888 /* NP: zero-based so we increase the human-readable number of our position */
889 debugs(14, 3, "ipcacheCycleAddr: " << name << " now at " << ia->in_addrs[ia->cur] << " (" << (ia->cur+1) << " of " << ia->count << ")");
52926044 890}
e5f6c5c2 891
63be0a78 892/**
893 \ingroup IPCacheAPI
894 *
f53969cc
SM
895 \param name domain name to have an IP marked bad
896 \param addr specific addres to be marked bad
22c653cd 897 */
e5f6c5c2 898void
b7ac5457 899ipcacheMarkBadAddr(const char *name, const Ip::Address &addr)
e5f6c5c2 900{
901 ipcache_entry *i;
902 ipcache_addrs *ia;
903 int k;
62e76326 904
63be0a78 905 /** Does nothing if the domain name does not exist. */
e5f6c5c2 906 if ((i = ipcache_get(name)) == NULL)
62e76326 907 return;
908
e5f6c5c2 909 ia = &i->addrs;
62e76326 910
95dc7ff4 911 for (k = 0; k < (int) ia->count; ++k) {
cc192b50 912 if (addr == ia->in_addrs[k] )
62e76326 913 break;
e5f6c5c2 914 }
62e76326 915
63be0a78 916 /** Does nothing if the IP does not exist for the doamin. */
917 if (k == (int) ia->count)
62e76326 918 return;
919
63be0a78 920 /** Marks the given address as BAD */
26ac0430 921 if (!ia->bad_mask[k]) {
62e76326 922 ia->bad_mask[k] = TRUE;
95dc7ff4 923 ++ia->badcount;
cc192b50 924 debugs(14, 2, "ipcacheMarkBadAddr: " << name << " " << addr );
22c653cd 925 }
62e76326 926
63be0a78 927 /** then calls ipcacheCycleAddr() to advance the current pointer to the next OK address. */
52926044 928 ipcacheCycleAddr(name, ia);
e5f6c5c2 929}
56e15c50 930
ec505200
HN
931/// \ingroup IPCacheAPI
932void
933ipcacheMarkAllGood(const char *name)
934{
935 ipcache_entry *i;
936 ipcache_addrs *ia;
937 int k;
938
939 if ((i = ipcache_get(name)) == NULL)
940 return;
941
942 ia = &i->addrs;
943
944 /* All bad, reset to All good */
945 debugs(14, 3, "ipcacheMarkAllGood: Changing ALL " << name << " addrs to OK (" << ia->badcount << "/" << ia->count << " bad)");
946
95dc7ff4 947 for (k = 0; k < ia->count; ++k)
ec505200
HN
948 ia->bad_mask[k] = 0;
949
950 ia->badcount = 0;
951}
952
63be0a78 953/// \ingroup IPCacheAPI
22c653cd 954void
b7ac5457 955ipcacheMarkGoodAddr(const char *name, const Ip::Address &addr)
22c653cd 956{
957 ipcache_entry *i;
958 ipcache_addrs *ia;
959 int k;
62e76326 960
22c653cd 961 if ((i = ipcache_get(name)) == NULL)
62e76326 962 return;
963
22c653cd 964 ia = &i->addrs;
62e76326 965
95dc7ff4 966 for (k = 0; k < (int) ia->count; ++k) {
cc192b50 967 if (addr == ia->in_addrs[k])
62e76326 968 break;
22c653cd 969 }
62e76326 970
f53969cc 971 if (k == (int) ia->count) /* not found */
62e76326 972 return;
973
f53969cc 974 if (!ia->bad_mask[k]) /* already OK */
62e76326 975 return;
976
52926044 977 ia->bad_mask[k] = FALSE;
62e76326 978
5e263176 979 -- ia->badcount;
62e76326 980
cc192b50 981 debugs(14, 2, "ipcacheMarkGoodAddr: " << name << " " << addr );
22c653cd 982}
983
63be0a78 984/// \ingroup IPCacheInternal
ec878047 985static void
986ipcacheFreeEntry(void *data)
987{
e6ccf245 988 ipcache_entry *i = (ipcache_entry *)data;
ec878047 989 safe_free(i->addrs.in_addrs);
990 safe_free(i->addrs.bad_mask);
186477c1 991 safe_free(i->hash.key);
ec878047 992 safe_free(i->error_message);
db1cd23c 993 memFree(i, MEM_IPCACHE_ENTRY);
ec878047 994}
995
63be0a78 996/// \ingroup IPCacheAPI
56e15c50 997void
998ipcacheFreeMemory(void)
999{
ec878047 1000 hashFreeItems(ip_table, ipcacheFreeEntry);
56e15c50 1001 hashFreeMemory(ip_table);
afe95a7e 1002 ip_table = NULL;
56e15c50 1003}
3fb036e8 1004
63be0a78 1005/**
1006 \ingroup IPCacheAPI
1007 *
1008 * Recalculate IP cache size upon reconfigure.
1009 * Is called to clear the IPCache's data structures,
1010 * cancel all pending requests.
1011 */
429fdbec 1012void
1013ipcache_restart(void)
1014{
429fdbec 1015 ipcache_high = (long) (((float) Config.ipcache.size *
62e76326 1016 (float) Config.ipcache.high) / (float) 100);
429fdbec 1017 ipcache_low = (long) (((float) Config.ipcache.size *
62e76326 1018 (float) Config.ipcache.low) / (float) 100);
0e70aa1e 1019 purge_entries_fromhosts();
1020}
1021
63be0a78 1022/**
1023 \ingroup IPCacheAPI
1024 *
1025 * Adds a "static" entry from /etc/hosts
1026 *
f53969cc
SM
1027 \param name Hostname to be linked with IP
1028 \param ipaddr IP Address to be cached.
63be0a78 1029 *
f53969cc
SM
1030 \retval 0 Success.
1031 \retval 1 IP address is invalid or other error.
0e70aa1e 1032 */
1033int
1034ipcacheAddEntryFromHosts(const char *name, const char *ipaddr)
1035{
1036 ipcache_entry *i;
62e76326 1037
b7ac5457 1038 Ip::Address ip;
62e76326 1039
cc192b50 1040 if (!(ip = ipaddr)) {
62e76326 1041 if (strchr(ipaddr, ':') && strspn(ipaddr, "0123456789abcdefABCDEF:") == strlen(ipaddr)) {
bf8fe701 1042 debugs(14, 3, "ipcacheAddEntryFromHosts: Skipping IPv6 address '" << ipaddr << "'");
62e76326 1043 } else {
e0236918 1044 debugs(14, DBG_IMPORTANT, "ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr << "'");
62e76326 1045 }
1046
1047 return 1;
0e70aa1e 1048 }
62e76326 1049
0e70aa1e 1050 if ((i = ipcache_get(name))) {
62e76326 1051 if (1 == i->flags.fromhosts) {
1052 ipcacheUnlockEntry(i);
1053 } else if (i->locks > 0) {
e0236918 1054 debugs(14, DBG_IMPORTANT, "ipcacheAddEntryFromHosts: can't add static entry for locked name '" << name << "'");
62e76326 1055 return 1;
1056 } else {
1057 ipcacheRelease(i);
1058 }
0e70aa1e 1059 }
62e76326 1060
0e70aa1e 1061 i = ipcacheCreateEntry(name);
1062 i->addrs.count = 1;
1063 i->addrs.cur = 0;
1064 i->addrs.badcount = 0;
62e76326 1065
b7ac5457 1066 i->addrs.in_addrs = static_cast<Ip::Address *>(xcalloc(1, sizeof(Ip::Address)));
e6ccf245 1067 i->addrs.bad_mask = (unsigned char *)xcalloc(1, sizeof(unsigned char));
cc192b50 1068 i->addrs.in_addrs[0] = ip;
0e70aa1e 1069 i->addrs.bad_mask[0] = FALSE;
be4d35dc 1070 i->flags.fromhosts = true;
0e70aa1e 1071 ipcacheAddEntry(i);
1072 ipcacheLockEntry(i);
1073 return 0;
429fdbec 1074}
ce75f381 1075
59ad6d31 1076#if SQUID_SNMP
63be0a78 1077/**
1078 \ingroup IPCacheAPI
1079 *
135171fe 1080 * The function to return the ip cache statistics to via SNMP
1081 */
86115da5 1082variable_list *
1f5b542b 1083snmp_netIpFn(variable_list * Var, snint * ErrP)
d60c11be 1084{
736eb6ad 1085 variable_list *Answer = NULL;
6a644e75
AJ
1086 MemBuf tmp;
1087 debugs(49, 5, "snmp_netIpFn: Processing request:" << snmpDebugOid(Var->name, Var->name_length, tmp));
86115da5 1088 *ErrP = SNMP_ERR_NOERROR;
62e76326 1089
135171fe 1090 switch (Var->name[LEN_SQ_NET + 1]) {
62e76326 1091
1f5b542b 1092 case IP_ENT:
62e76326 1093 Answer = snmp_var_new_integer(Var->name, Var->name_length,
ac49890a 1094 ipcacheCount(),
62e76326 1095 SMI_GAUGE32);
1096 break;
1097
1f5b542b 1098 case IP_REQ:
62e76326 1099 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1100 IpcacheStats.requests,
1101 SMI_COUNTER32);
1102 break;
1103
1f5b542b 1104 case IP_HITS:
62e76326 1105 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1106 IpcacheStats.hits,
1107 SMI_COUNTER32);
1108 break;
1109
1f5b542b 1110 case IP_PENDHIT:
62e76326 1111 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1112 0,
1113 SMI_GAUGE32);
1114 break;
1115
1f5b542b 1116 case IP_NEGHIT:
62e76326 1117 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1118 IpcacheStats.negative_hits,
1119 SMI_COUNTER32);
1120 break;
1121
1f5b542b 1122 case IP_MISS:
62e76326 1123 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1124 IpcacheStats.misses,
1125 SMI_COUNTER32);
1126 break;
1127
1f5b542b 1128 case IP_GHBN:
62e76326 1129 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1130 0, /* deprecated */
1131 SMI_COUNTER32);
1132 break;
1133
1f5b542b 1134 case IP_LOC:
62e76326 1135 Answer = snmp_var_new_integer(Var->name, Var->name_length,
1136 0, /* deprecated */
1137 SMI_COUNTER32);
1138 break;
1139
ce75f381 1140 default:
62e76326 1141 *ErrP = SNMP_ERR_NOSUCHNAME;
1142 snmp_var_free(Answer);
1143 return (NULL);
86115da5 1144 }
62e76326 1145
86115da5 1146 return Answer;
ce75f381 1147}
1f5b542b 1148
135171fe 1149#endif /*SQUID_SNMP */
f53969cc 1150