3 * $Id: ipcache.cc,v 1.232 2001/01/12 00:37:18 wessels Exp $
5 * DEBUG: section 14 IP Cache
6 * AUTHOR: Harvest Derived
8 * SQUID Web Proxy Cache http://www.squid-cache.org/
9 * ----------------------------------------------------------
11 * Squid is the result of efforts by numerous individuals from
12 * the Internet community; see the CONTRIBUTORS file for full
13 * details. Many organizations have provided support for Squid's
14 * development; see the SPONSORS file for full details. Squid is
15 * Copyrighted (C) 2001 by the Regents of the University of
16 * California; see the COPYRIGHT file for full details. Squid
17 * incorporates software developed and/or copyrighted by other
18 * sources; see the CREDITS file for full details.
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
38 typedef struct _ipcache_entry ipcache_entry
;
40 struct _ipcache_entry
{
41 hash_link hash
; /* must be first */
48 struct timeval request_time
;
52 unsigned int negcached
:1;
53 unsigned int fromhosts
:1;
64 int ghbn_calls
; /* # calls to blocking gethostbyname() */
68 static dlink_list lru_list
;
70 static FREE ipcacheFreeEntry
;
72 static HLPCB ipcacheHandleReply
;
74 static IDNSCB ipcacheHandleReply
;
76 static IPH dummy_handler
;
77 static int ipcacheExpiredEntry(ipcache_entry
*);
78 static int ipcache_testname(void);
80 static ipcache_entry
*ipcacheParse(const char *buf
);
82 static ipcache_entry
*ipcacheParse(rfc1035_rr
*, int);
84 static ipcache_entry
*ipcache_get(const char *);
85 static void ipcacheLockEntry(ipcache_entry
*);
86 static void ipcacheStatPrint(ipcache_entry
*, StoreEntry
*);
87 static void ipcacheUnlockEntry(ipcache_entry
*);
88 static void ipcacheRelease(ipcache_entry
*);
90 static ipcache_addrs static_addrs
;
91 static hash_table
*ip_table
= NULL
;
93 static long ipcache_low
= 180;
94 static long ipcache_high
= 200;
96 #if LIBRESOLV_DNS_TTL_HACK
101 ipcache_testname(void)
104 debug(14, 1) ("Performing DNS Tests...\n");
105 if ((w
= Config
.dns_testname_list
) == NULL
)
107 for (; w
; w
= w
->next
) {
108 IpcacheStats
.ghbn_calls
++;
109 if (gethostbyname(w
->key
) != NULL
)
115 /* removes the given ipcache entry */
117 ipcacheRelease(ipcache_entry
* i
)
119 hash_remove_link(ip_table
, (hash_link
*) i
);
120 dlinkDelete(&i
->lru
, &lru_list
);
124 static ipcache_entry
*
125 ipcache_get(const char *name
)
127 if (ip_table
!= NULL
)
128 return (ipcache_entry
*) hash_lookup(ip_table
, name
);
134 ipcacheExpiredEntry(ipcache_entry
* i
)
136 /* all static entries are locked, so this takes care of them too */
139 if (i
->addrs
.count
== 0)
140 if (0 == i
->flags
.negcached
)
142 if (i
->expires
> squid_curtime
)
148 ipcache_purgelru(void *voidnotused
)
151 dlink_node
*prev
= NULL
;
154 eventAdd("ipcache_purgelru", ipcache_purgelru
, NULL
, 10.0, 1);
155 for (m
= lru_list
.tail
; m
; m
= prev
) {
156 if (memInUse(MEM_IPCACHE_ENTRY
) < ipcache_low
)
165 debug(14, 9) ("ipcache_purgelru: removed %d entries\n", removed
);
168 /* purges entries added from /etc/hosts (or whatever). */
170 purge_entries_fromhosts(void)
172 dlink_node
*m
= lru_list
.head
;
173 ipcache_entry
*i
= NULL
, *t
;
175 if (i
!= NULL
) { /* need to delay deletion */
176 ipcacheRelease(i
); /* we just override locks */
180 if (t
->flags
.fromhosts
)
188 /* create blank ipcache_entry */
189 static ipcache_entry
*
190 ipcacheCreateEntry(const char *name
)
192 static ipcache_entry
*i
;
193 i
= memAllocate(MEM_IPCACHE_ENTRY
);
194 i
->hash
.key
= xstrdup(name
);
195 i
->expires
= squid_curtime
+ Config
.negativeDnsTtl
;
200 ipcacheAddEntry(ipcache_entry
* i
)
202 hash_link
*e
= hash_lookup(ip_table
, i
->hash
.key
);
204 /* avoid colission */
205 ipcache_entry
*q
= (ipcache_entry
*) e
;
208 hash_join(ip_table
, &i
->hash
);
209 dlinkAdd(i
, &i
->lru
, &lru_list
);
210 i
->lastref
= squid_curtime
;
213 /* walks down the pending list, calling handlers */
215 ipcacheCallback(ipcache_entry
* i
)
217 IPH
*handler
= i
->handler
;
218 void *handlerData
= i
->handlerData
;
219 i
->lastref
= squid_curtime
;
224 i
->handlerData
= NULL
;
225 if (cbdataValid(handlerData
)) {
226 dns_error_message
= i
->error_message
;
227 handler(i
->flags
.negcached
? NULL
: &i
->addrs
, handlerData
);
229 cbdataUnlock(handlerData
);
230 ipcacheUnlockEntry(i
);
233 static ipcache_entry
*
235 ipcacheParse(const char *inbuf
)
237 LOCAL_ARRAY(char, buf
, DNS_INBUF_SZ
);
239 static ipcache_entry i
;
245 memset(&i
, '\0', sizeof(i
));
246 i
.expires
= squid_curtime
;
247 i
.flags
.negcached
= 1;
249 debug(14, 1) ("ipcacheParse: Got <NULL> reply\n");
250 i
.error_message
= xstrdup("Internal Squid Error");
253 xstrncpy(buf
, inbuf
, DNS_INBUF_SZ
);
254 debug(14, 5) ("ipcacheParse: parsing: {%s}\n", buf
);
255 token
= strtok(buf
, w_space
);
257 debug(14, 1) ("ipcacheParse: Got <NULL>, expecting '$addr'\n");
260 if (0 == strcmp(token
, "$fail")) {
261 i
.expires
= squid_curtime
+ Config
.negativeDnsTtl
;
262 token
= strtok(NULL
, "\n");
263 assert(NULL
!= token
);
264 i
.error_message
= xstrdup(token
);
267 if (0 != strcmp(token
, "$addr")) {
268 debug(14, 1) ("ipcacheParse: Got '%s', expecting '$addr'\n", token
);
271 token
= strtok(NULL
, w_space
);
273 debug(14, 1) ("ipcacheParse: Got <NULL>, expecting TTL\n");
276 i
.flags
.negcached
= 0;
279 i
.expires
= squid_curtime
+ ttl
;
281 i
.expires
= squid_curtime
+ Config
.positiveDnsTtl
;
282 while (NULL
!= (token
= strtok(NULL
, w_space
))) {
283 xstrncpy(A
[ipcount
], token
, 16);
288 i
.addrs
.in_addrs
= NULL
;
289 i
.addrs
.bad_mask
= NULL
;
291 i
.addrs
.in_addrs
= xcalloc(ipcount
, sizeof(struct in_addr
));
292 i
.addrs
.bad_mask
= xcalloc(ipcount
, sizeof(unsigned char));
294 for (j
= 0, k
= 0; k
< ipcount
; k
++) {
295 if (safe_inet_addr(A
[k
], &i
.addrs
.in_addrs
[j
]))
298 debug(14, 1) ("ipcacheParse: Invalid IP address '%s'\n", A
[k
]);
300 i
.addrs
.count
= (unsigned char) j
;
304 ipcacheParse(rfc1035_rr
* answers
, int nr
)
306 static ipcache_entry i
;
310 memset(&i
, '\0', sizeof(i
));
311 i
.expires
= squid_curtime
+ Config
.negativeDnsTtl
;
312 i
.flags
.negcached
= 1;
314 debug(14, 3) ("ipcacheParse: Lookup failed (error %d)\n",
316 assert(rfc1035_error_message
);
317 i
.error_message
= xstrdup(rfc1035_error_message
);
321 debug(14, 3) ("ipcacheParse: No DNS records\n");
322 i
.error_message
= xstrdup("No DNS records");
326 for (j
= 0, k
= 0; k
< nr
; k
++) {
327 if (answers
[k
].type
!= RFC1035_TYPE_A
)
329 if (answers
[k
].class != RFC1035_CLASS_IN
)
334 debug(14, 1) ("ipcacheParse: No Address records\n");
335 i
.error_message
= xstrdup("No Address records");
338 i
.flags
.negcached
= 0;
339 i
.addrs
.in_addrs
= xcalloc(na
, sizeof(struct in_addr
));
340 i
.addrs
.bad_mask
= xcalloc(na
, sizeof(unsigned char));
341 i
.addrs
.count
= (unsigned char) na
;
342 for (j
= 0, k
= 0; k
< nr
; k
++) {
343 if (answers
[k
].type
!= RFC1035_TYPE_A
)
345 if (answers
[k
].class != RFC1035_CLASS_IN
)
348 i
.expires
= squid_curtime
+ answers
[k
].ttl
;
349 assert(answers
[k
].rdlength
== 4);
350 xmemcpy(&i
.addrs
.in_addrs
[j
++], answers
[k
].rdata
, 4);
351 debug(14, 3) ("ipcacheParse: #%d %s\n",
353 inet_ntoa(i
.addrs
.in_addrs
[j
- 1]));
362 ipcacheHandleReply(void *data
, char *reply
)
364 ipcacheHandleReply(void *data
, rfc1035_rr
* answers
, int na
)
368 generic_cbdata
*c
= data
;
369 ipcache_entry
*i
= c
->data
;
370 ipcache_entry
*x
= NULL
;
373 n
= ++IpcacheStats
.replies
;
374 statHistCount(&statCounter
.dns
.svc_time
,
375 tvSubMsec(i
->request_time
, current_time
));
377 x
= ipcacheParse(reply
);
379 x
= ipcacheParse(answers
, na
);
383 i
->error_message
= x
->error_message
;
384 i
->expires
= x
->expires
;
391 ipcache_nbgethostbyname(const char *name
, IPH
* handler
, void *handlerData
)
393 ipcache_entry
*i
= NULL
;
394 const ipcache_addrs
*addrs
= NULL
;
396 assert(handler
!= NULL
);
397 debug(14, 4) ("ipcache_nbgethostbyname: Name '%s'.\n", name
);
398 IpcacheStats
.requests
++;
399 if (name
== NULL
|| name
[0] == '\0') {
400 debug(14, 4) ("ipcache_nbgethostbyname: Invalid name!\n");
401 handler(NULL
, handlerData
);
404 if ((addrs
= ipcacheCheckNumeric(name
))) {
405 handler(addrs
, handlerData
);
408 i
= ipcache_get(name
);
412 } else if (ipcacheExpiredEntry(i
)) {
413 /* hit, but expired -- bummer */
418 debug(14, 4) ("ipcache_nbgethostbyname: HIT for '%s'\n", name
);
419 if (i
->flags
.negcached
)
420 IpcacheStats
.negative_hits
++;
423 i
->handler
= handler
;
424 i
->handlerData
= handlerData
;
425 cbdataLock(handlerData
);
429 debug(14, 5) ("ipcache_nbgethostbyname: MISS for '%s'\n", name
);
430 IpcacheStats
.misses
++;
431 i
= ipcacheCreateEntry(name
);
432 i
->handler
= handler
;
433 i
->handlerData
= handlerData
;
434 cbdataLock(handlerData
);
435 i
->request_time
= current_time
;
436 c
= CBDATA_ALLOC(generic_cbdata
, NULL
);
439 dnsSubmit(hashKeyStr(&i
->hash
), ipcacheHandleReply
, c
);
441 idnsALookup(hashKeyStr(&i
->hash
), ipcacheHandleReply
, c
);
445 /* initialize the ipcache */
450 debug(14, 3) ("Initializing IP Cache...\n");
451 memset(&IpcacheStats
, '\0', sizeof(IpcacheStats
));
452 memset(&lru_list
, '\0', sizeof(lru_list
));
453 /* test naming lookup */
454 if (!opt_dns_tests
) {
455 debug(14, 4) ("ipcache_init: Skipping DNS name lookup tests.\n");
456 } else if (!ipcache_testname()) {
457 fatal("ipcache_init: DNS name lookup tests failed.");
459 debug(14, 1) ("Successful DNS name lookup tests...\n");
461 memset(&static_addrs
, '\0', sizeof(ipcache_addrs
));
462 static_addrs
.in_addrs
= xcalloc(1, sizeof(struct in_addr
));
463 static_addrs
.bad_mask
= xcalloc(1, sizeof(unsigned char));
464 ipcache_high
= (long) (((float) Config
.ipcache
.size
*
465 (float) Config
.ipcache
.high
) / (float) 100);
466 ipcache_low
= (long) (((float) Config
.ipcache
.size
*
467 (float) Config
.ipcache
.low
) / (float) 100);
468 n
= hashPrime(ipcache_high
/ 4);
469 ip_table
= hash_create((HASHCMP
*) strcmp
, n
, hash4
);
470 cachemgrRegister("ipcache",
471 "IP Cache Stats and Contents",
472 stat_ipcache_get
, 0, 1);
473 memDataInit(MEM_IPCACHE_ENTRY
, "ipcache_entry", sizeof(ipcache_entry
), 0);
476 const ipcache_addrs
*
477 ipcache_gethostbyname(const char *name
, int flags
)
479 ipcache_entry
*i
= NULL
;
480 ipcache_addrs
*addrs
;
482 debug(14, 3) ("ipcache_gethostbyname: '%s', flags=%x\n", name
, flags
);
483 IpcacheStats
.requests
++;
484 i
= ipcache_get(name
);
487 } else if (ipcacheExpiredEntry(i
)) {
490 } else if (i
->flags
.negcached
) {
491 IpcacheStats
.negative_hits
++;
492 dns_error_message
= i
->error_message
;
496 i
->lastref
= squid_curtime
;
499 if ((addrs
= ipcacheCheckNumeric(name
)))
501 IpcacheStats
.misses
++;
502 if (flags
& IP_LOOKUP_IF_MISS
)
503 ipcache_nbgethostbyname(name
, dummy_handler
, NULL
);
508 ipcacheStatPrint(ipcache_entry
* i
, StoreEntry
* sentry
)
511 storeAppendPrintf(sentry
, " %-32.32s %c%c %6d %6d %2d(%2d)",
512 hashKeyStr(&i
->hash
),
513 i
->flags
.fromhosts
? 'H' : ' ',
514 i
->flags
.negcached
? 'N' : ' ',
515 (int) (squid_curtime
- i
->lastref
),
516 (int) ((i
->flags
.fromhosts
? -1 : i
->expires
- squid_curtime
)),
517 (int) i
->addrs
.count
,
518 (int) i
->addrs
.badcount
);
519 for (k
= 0; k
< (int) i
->addrs
.count
; k
++) {
520 storeAppendPrintf(sentry
, " %15s-%3s", inet_ntoa(i
->addrs
.in_addrs
[k
]),
521 i
->addrs
.bad_mask
[k
] ? "BAD" : "OK ");
523 storeAppendPrintf(sentry
, "\n");
526 /* process objects list */
528 stat_ipcache_get(StoreEntry
* sentry
)
531 assert(ip_table
!= NULL
);
532 storeAppendPrintf(sentry
, "IP Cache Statistics:\n");
533 storeAppendPrintf(sentry
, "IPcache Entries: %d\n",
534 memInUse(MEM_IPCACHE_ENTRY
));
535 storeAppendPrintf(sentry
, "IPcache Requests: %d\n",
536 IpcacheStats
.requests
);
537 storeAppendPrintf(sentry
, "IPcache Hits: %d\n",
539 storeAppendPrintf(sentry
, "IPcache Negative Hits: %d\n",
540 IpcacheStats
.negative_hits
);
541 storeAppendPrintf(sentry
, "IPcache Misses: %d\n",
542 IpcacheStats
.misses
);
543 storeAppendPrintf(sentry
, "Blocking calls to gethostbyname(): %d\n",
544 IpcacheStats
.ghbn_calls
);
545 storeAppendPrintf(sentry
, "Attempts to release locked entries: %d\n",
546 IpcacheStats
.release_locked
);
547 storeAppendPrintf(sentry
, "\n\n");
548 storeAppendPrintf(sentry
, "IP Cache Contents:\n\n");
549 storeAppendPrintf(sentry
, " %-29.29s %3s %6s %6s %1s\n",
555 for (m
= lru_list
.head
; m
; m
= m
->next
)
556 ipcacheStatPrint(m
->data
, sentry
);
560 dummy_handler(const ipcache_addrs
* addrsnotused
, void *datanotused
)
566 ipcacheInvalidate(const char *name
)
569 if ((i
= ipcache_get(name
)) == NULL
)
571 i
->expires
= squid_curtime
;
573 * NOTE, don't call ipcacheRelease here becuase we might be here due
574 * to a thread started from a callback.
579 ipcacheCheckNumeric(const char *name
)
582 /* check if it's already a IP address in text form. */
583 if (!safe_inet_addr(name
, &ip
))
585 static_addrs
.count
= 1;
586 static_addrs
.cur
= 0;
587 static_addrs
.in_addrs
[0].s_addr
= ip
.s_addr
;
588 static_addrs
.bad_mask
[0] = FALSE
;
589 static_addrs
.badcount
= 0;
590 return &static_addrs
;
594 ipcacheLockEntry(ipcache_entry
* i
)
596 if (i
->locks
++ == 0) {
597 dlinkDelete(&i
->lru
, &lru_list
);
598 dlinkAdd(i
, &i
->lru
, &lru_list
);
603 ipcacheUnlockEntry(ipcache_entry
* i
)
605 assert(i
->locks
> 0);
607 if (ipcacheExpiredEntry(i
))
612 ipcacheCycleAddr(const char *name
, ipcache_addrs
* ia
)
618 if ((i
= ipcache_get(name
)) == NULL
)
620 if (i
->flags
.negcached
)
624 for (k
= 0; k
< ia
->count
; k
++) {
625 if (++ia
->cur
== ia
->count
)
627 if (!ia
->bad_mask
[ia
->cur
])
630 if (k
== ia
->count
) {
631 /* All bad, reset to All good */
632 debug(14, 3) ("ipcacheCycleAddr: Changing ALL %s addrs from BAD to OK\n",
634 for (k
= 0; k
< ia
->count
; k
++)
639 debug(14, 3) ("ipcacheCycleAddr: %s now at %s\n", name
,
640 inet_ntoa(ia
->in_addrs
[ia
->cur
]));
644 * Marks the given address as BAD and calls ipcacheCycleAddr to
645 * advance the current pointer to the next OK address.
648 ipcacheMarkBadAddr(const char *name
, struct in_addr addr
)
653 if ((i
= ipcache_get(name
)) == NULL
)
656 for (k
= 0; k
< (int) ia
->count
; k
++) {
657 if (ia
->in_addrs
[k
].s_addr
== addr
.s_addr
)
660 if (k
== (int) ia
->count
) /* not found */
662 if (!ia
->bad_mask
[k
]) {
663 ia
->bad_mask
[k
] = TRUE
;
665 debug(14, 2) ("ipcacheMarkBadAddr: %s [%s]\n", name
, inet_ntoa(addr
));
667 ipcacheCycleAddr(name
, ia
);
671 ipcacheMarkGoodAddr(const char *name
, struct in_addr addr
)
676 if ((i
= ipcache_get(name
)) == NULL
)
679 for (k
= 0; k
< (int) ia
->count
; k
++) {
680 if (ia
->in_addrs
[k
].s_addr
== addr
.s_addr
)
683 if (k
== (int) ia
->count
) /* not found */
685 if (!ia
->bad_mask
[k
]) /* already OK */
687 ia
->bad_mask
[k
] = FALSE
;
689 debug(14, 2) ("ipcacheMarkGoodAddr: %s [%s]\n", name
, inet_ntoa(addr
));
693 ipcacheFreeEntry(void *data
)
695 ipcache_entry
*i
= data
;
696 safe_free(i
->addrs
.in_addrs
);
697 safe_free(i
->addrs
.bad_mask
);
698 safe_free(i
->hash
.key
);
699 safe_free(i
->error_message
);
700 memFree(i
, MEM_IPCACHE_ENTRY
);
704 ipcacheFreeMemory(void)
706 hashFreeItems(ip_table
, ipcacheFreeEntry
);
707 hashFreeMemory(ip_table
);
711 /* Recalculate IP cache size upon reconfigure */
713 ipcache_restart(void)
715 ipcache_high
= (long) (((float) Config
.ipcache
.size
*
716 (float) Config
.ipcache
.high
) / (float) 100);
717 ipcache_low
= (long) (((float) Config
.ipcache
.size
*
718 (float) Config
.ipcache
.low
) / (float) 100);
719 purge_entries_fromhosts();
723 * adds a "static" entry from /etc/hosts.
724 * returns 0 upon success, 1 if the ip address is invalid
727 ipcacheAddEntryFromHosts(const char *name
, const char *ipaddr
)
731 if (!safe_inet_addr(ipaddr
, &ip
)) {
732 debug(14, 1) ("ipcacheAddEntryFromHosts: bad IP address '%s'\n",
736 if ((i
= ipcache_get(name
))) {
737 if (1 == i
->flags
.fromhosts
) {
738 ipcacheUnlockEntry(i
);
739 } else if (i
->locks
> 0) {
740 debug(14, 1) ("ipcacheAddEntryFromHosts: can't add static entry"
741 " for locked name '%s'\n", name
);
747 i
= ipcacheCreateEntry(name
);
750 i
->addrs
.badcount
= 0;
751 i
->addrs
.in_addrs
= xcalloc(1, sizeof(struct in_addr
));
752 i
->addrs
.bad_mask
= xcalloc(1, sizeof(unsigned char));
753 i
->addrs
.in_addrs
[0].s_addr
= ip
.s_addr
;
754 i
->addrs
.bad_mask
[0] = FALSE
;
755 i
->flags
.fromhosts
= 1;
763 * The function to return the ip cache statistics to via SNMP
767 snmp_netIpFn(variable_list
* Var
, snint
* ErrP
)
769 variable_list
*Answer
= NULL
;
770 debug(49, 5) ("snmp_netIpFn: Processing request:\n", Var
->name
[LEN_SQ_NET
+ 1]);
771 snmpDebugOid(5, Var
->name
, Var
->name_length
);
772 *ErrP
= SNMP_ERR_NOERROR
;
773 switch (Var
->name
[LEN_SQ_NET
+ 1]) {
775 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
776 memInUse(MEM_IPCACHE_ENTRY
),
780 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
781 IpcacheStats
.requests
,
785 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
790 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
795 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
796 IpcacheStats
.negative_hits
,
800 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
805 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
806 IpcacheStats
.ghbn_calls
,
810 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
811 IpcacheStats
.release_locked
,
815 *ErrP
= SNMP_ERR_NOSUCHNAME
;
816 snmp_var_free(Answer
);
822 #endif /*SQUID_SNMP */