fqdncache.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2022 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 35 FQDN Cache */
10
11#include "squid.h"
12#include "cbdata.h"
13#include "dns/forward.h"
14#include "dns/LookupDetails.h"
15#include "dns/rfc1035.h"
16#include "event.h"
17#include "fqdncache.h"
18#include "helper.h"
19#include "mgr/Registration.h"
20#include "snmp_agent.h"
21#include "SquidConfig.h"
22#include "StatCounters.h"
23#include "Store.h"
24#include "util.h"
25
26#if SQUID_SNMP
27#include "snmp_core.h"
28#endif
29
31
68#define FQDN_LOW_WATER 90
69
71#define FQDN_HIGH_WATER 95
72
80{
82
83public:
84 fqdncache_entry(const char *name);
86
87 hash_link hash; /* must be first */
88 time_t lastref;
89 time_t expires;
90 unsigned char name_count;
95
96 struct timeval request_time;
98 unsigned short locks;
99
100 struct Flags {
102
106
107 int age() const;
108};
109
111static struct _fqdn_cache_stats {
114 int hits;
118
121
123static int fqdncacheParse(fqdncache_entry *, const rfc1035_rr *, int, const char *error_message);
124static void fqdncacheRelease(fqdncache_entry *);
125static void fqdncacheCallback(fqdncache_entry *, int wait);
126static fqdncache_entry *fqdncache_get(const char *);
127static int fqdncacheExpiredEntry(const fqdncache_entry *);
128static void fqdncacheLockEntry(fqdncache_entry * f);
130static void fqdncacheAddEntry(fqdncache_entry * f);
131
133static hash_table *fqdn_table = nullptr;
134
136static long fqdncache_low = 180;
137
139static long fqdncache_high = 200;
140
142inline int fqdncacheCount() { return fqdn_table ? fqdn_table->count : 0; }
143
144int
146{
147 return request_time.tv_sec ? tvSubMsec(request_time, current_time) : -1;
148}
149
154static void
156{
158 debugs(35, 5, "fqdncacheRelease: Released FQDN record for '" << hashKeyStr(&f->hash) << "'.");
159 dlinkDelete(&f->lru, &lru_list);
160 delete f;
161}
162
168static fqdncache_entry *
169fqdncache_get(const char *name)
170{
171 hash_link *e;
172 static fqdncache_entry *f;
173 f = nullptr;
174
175 if (fqdn_table) {
176 if ((e = (hash_link *)hash_lookup(fqdn_table, name)) != nullptr)
177 f = (fqdncache_entry *) e;
178 }
179
180 return f;
181}
182
184static int
186{
187 /* all static entries are locked, so this takes care of them too */
188
189 if (f->locks != 0)
190 return 0;
191
192 if (f->expires > squid_curtime)
193 return 0;
194
195 return 1;
196}
197
199void
201{
202 dlink_node *m;
203 dlink_node *prev = nullptr;
205 int removed = 0;
206 eventAdd("fqdncache_purgelru", fqdncache_purgelru, nullptr, 10.0, 1);
207
208 for (m = lru_list.tail; m; m = prev) {
210 break;
211
212 prev = m->prev;
213
214 f = (fqdncache_entry *)m->data;
215
216 if (f->locks != 0)
217 continue;
218
220
221 ++removed;
222 }
223
224 debugs(35, 9, "fqdncache_purgelru: removed " << removed << " entries");
225}
226
228static void
230{
232 fqdncache_entry *i = nullptr;
234
235 while (m) {
236 if (i != nullptr) { /* need to delay deletion */
237 fqdncacheRelease(i); /* we just override locks */
238 i = nullptr;
239 }
240
241 t = (fqdncache_entry *)m->data;
242
243 if (t->flags.fromhosts)
244 i = t;
245
246 m = m->next;
247 }
248
249 if (i != nullptr)
251}
252
254 lastref(0),
255 expires(squid_curtime + Config.negativeDnsTtl),
256 name_count(0),
257 handler(nullptr),
258 handlerData(nullptr),
259 error_message(nullptr),
260 locks(0) // XXX: use Lock
261{
262 hash.key = xstrdup(name);
263
264 memset(&request_time, 0, sizeof(request_time));
265 memset(&names, 0, sizeof(names));
266}
267
269static void
271{
273
274 if (nullptr != e) {
275 /* avoid collision */
278 }
279
281 dlinkAdd(f, &f->lru, &lru_list);
283}
284
290static void
292{
293 FQDNH *callback;
294 void *cbdata;
296
297 if (!f->handler)
298 return;
299
301
302 callback = f->handler;
303
304 f->handler = nullptr;
305
307 const Dns::LookupDetails details(f->error_message, wait);
308 callback(f->name_count ? f->names[0] : nullptr, details, cbdata);
309 }
310
312}
313
315static int
316fqdncacheParse(fqdncache_entry *f, const rfc1035_rr * answers, int nr, const char *error_message)
317{
318 int k;
319 int ttl = 0;
320 const char *name = (const char *)f->hash.key;
322 f->flags.negcached = true;
323
324 if (nr < 0) {
325 debugs(35, 3, "fqdncacheParse: Lookup of '" << name << "' failed (" << error_message << ")");
326 f->error_message = xstrdup(error_message);
327 return -1;
328 }
329
330 if (nr == 0) {
331 debugs(35, 3, "fqdncacheParse: No DNS records for '" << name << "'");
332 f->error_message = xstrdup("No DNS records");
333 return 0;
334 }
335
336 debugs(35, 3, "fqdncacheParse: " << nr << " answers for '" << name << "'");
337 assert(answers);
338
339 for (k = 0; k < nr; ++k) {
340 if (answers[k]._class != RFC1035_CLASS_IN)
341 continue;
342
343 if (answers[k].type == RFC1035_TYPE_PTR) {
344 if (!answers[k].rdata[0]) {
345 debugs(35, 2, "fqdncacheParse: blank PTR record for '" << name << "'");
346 continue;
347 }
348
349 if (strchr(answers[k].rdata, ' ')) {
350 debugs(35, 2, "fqdncacheParse: invalid PTR record '" << answers[k].rdata << "' for '" << name << "'");
351 continue;
352 }
353
354 f->names[f->name_count] = xstrdup(answers[k].rdata);
355 ++ f->name_count;
356 } else if (answers[k].type != RFC1035_TYPE_CNAME)
357 continue;
358
359 if (ttl == 0 || (int) answers[k].ttl < ttl)
360 ttl = answers[k].ttl;
361
362 if (f->name_count >= FQDN_MAX_NAMES)
363 break;
364 }
365
366 if (f->name_count == 0) {
367 debugs(35, DBG_IMPORTANT, "fqdncacheParse: No PTR record for '" << name << "'");
368 return 0;
369 }
370
371 if (ttl > Config.positiveDnsTtl)
373
374 if (ttl < Config.negativeDnsTtl)
376
377 f->expires = squid_curtime + ttl;
378
379 f->flags.negcached = false;
380
381 return f->name_count;
382}
383
389static void
390fqdncacheHandleReply(void *data, const rfc1035_rr * answers, int na, const char *error_message, const bool lastAnswer)
391{
392 assert(lastAnswer); // reverse DNS lookups do not generate multiple queries
394 static_cast<generic_cbdata *>(data)->unwrap(&f);
396 const int age = f->age();
398 fqdncacheParse(f, answers, na, error_message);
400 fqdncacheCallback(f, age);
401}
402
413void
414fqdncache_nbgethostbyaddr(const Ip::Address &addr, FQDNH * handler, void *handlerData)
415{
416 fqdncache_entry *f = nullptr;
417 char name[MAX_IPSTRLEN];
419 addr.toStr(name,MAX_IPSTRLEN);
420 debugs(35, 4, "fqdncache_nbgethostbyaddr: Name '" << name << "'.");
422
423 if (name[0] == '\0') {
424 debugs(35, 4, "fqdncache_nbgethostbyaddr: Invalid name!");
425 const Dns::LookupDetails details("Invalid hostname", -1); // error, no lookup
426 if (handler)
427 handler(nullptr, details, handlerData);
428 return;
429 }
430
431 f = fqdncache_get(name);
432
433 if (nullptr == f) {
434 /* miss */
435 (void) 0;
436 } else if (fqdncacheExpiredEntry(f)) {
437 /* hit, but expired -- bummer */
439 f = nullptr;
440 } else {
441 /* hit */
442 debugs(35, 4, "fqdncache_nbgethostbyaddr: HIT for '" << name << "'");
443
444 if (f->flags.negcached)
446 else
448
449 f->handler = handler;
450
451 f->handlerData = cbdataReference(handlerData);
452
453 fqdncacheCallback(f, -1); // no lookup
454
455 return;
456 }
457
458 debugs(35, 5, "fqdncache_nbgethostbyaddr: MISS for '" << name << "'");
460 f = new fqdncache_entry(name);
461 f->handler = handler;
462 f->handlerData = cbdataReference(handlerData);
464 c = new generic_cbdata(f);
466}
467
480const char *
482{
483 char name[MAX_IPSTRLEN];
484 fqdncache_entry *f = nullptr;
485
486 if (addr.isAnyAddr() || addr.isNoAddr()) {
487 debugs(35, 7, "nothing to lookup: " << addr);
488 return nullptr;
489 }
490
491 addr.toStr(name,MAX_IPSTRLEN);
493 f = fqdncache_get(name);
494
495 if (nullptr == f) {
496 (void) 0;
497 } else if (fqdncacheExpiredEntry(f)) {
499 f = nullptr;
500 } else if (f->flags.negcached) {
501 debugs(35, 5, "negative HIT: " << addr);
503 // ignore f->error_message: the caller just checks FQDN cache presence
504 return nullptr;
505 } else {
506 debugs(35, 5, "HIT: " << addr);
509 // ignore f->error_message: the caller just checks FQDN cache presence
510 return f->names[0];
511 }
512
513 /* no entry [any more] */
514 debugs(35, 5, "MISS: " << addr);
516
517 if (flags & FQDN_LOOKUP_IF_MISS) {
518 fqdncache_nbgethostbyaddr(addr, nullptr, nullptr);
519 }
520
521 return nullptr;
522}
523
529void
531{
532 fqdncache_entry *f = nullptr;
533 int k;
534 int ttl;
535
536 if (fqdn_table == nullptr)
537 return;
538
539 storeAppendPrintf(sentry, "FQDN Cache Statistics:\n");
540
541 storeAppendPrintf(sentry, "FQDNcache Entries In Use: %d\n",
542 fqdncache_entry::UseCount());
543
544 storeAppendPrintf(sentry, "FQDNcache Entries Cached: %d\n",
546
547 storeAppendPrintf(sentry, "FQDNcache Requests: %d\n",
549
550 storeAppendPrintf(sentry, "FQDNcache Hits: %d\n",
552
553 storeAppendPrintf(sentry, "FQDNcache Negative Hits: %d\n",
555
556 storeAppendPrintf(sentry, "FQDNcache Misses: %d\n",
558
559 storeAppendPrintf(sentry, "FQDN Cache Contents:\n\n");
560
561 storeAppendPrintf(sentry, "%-45.45s %3s %3s %3s %s\n",
562 "Address", "Flg", "TTL", "Cnt", "Hostnames");
563
565
566 while ((f = (fqdncache_entry *) hash_next(fqdn_table))) {
567 ttl = (f->flags.fromhosts ? -1 : (f->expires - squid_curtime));
568 storeAppendPrintf(sentry, "%-45.45s %c%c %3.3d % 3d",
569 hashKeyStr(&f->hash),
570 f->flags.negcached ? 'N' : ' ',
571 f->flags.fromhosts ? 'H' : ' ',
572 ttl,
573 (int) f->name_count);
574
575 for (k = 0; k < (int) f->name_count; ++k)
576 storeAppendPrintf(sentry, " %s", f->names[k]);
577
578 storeAppendPrintf(sentry, "\n");
579 }
580}
581
583static void
585{
586 if (f->locks++ == 0) {
587 dlinkDelete(&f->lru, &lru_list);
588 dlinkAdd(f, &f->lru, &lru_list);
589 }
590}
591
593static void
595{
596 assert(f->locks > 0);
597 -- f->locks;
598
601}
602
604{
605 for (int k = 0; k < (int)name_count; ++k)
606 xfree(names[k]);
607
608 xfree(hash.key);
610}
611
619void
621{
622 fqdncache_high = (long) (((float) Config.fqdncache.size *
623 (float) FQDN_HIGH_WATER) / (float) 100);
624 fqdncache_low = (long) (((float) Config.fqdncache.size *
625 (float) FQDN_LOW_WATER) / (float) 100);
627}
628
635void
637{
638 fqdncache_entry *fce= fqdncache_get(addr);
639 if (fce) {
640 if (1 == fce->flags.fromhosts) {
642 } else if (fce->locks > 0) {
643 debugs(35, DBG_IMPORTANT, "WARNING: can't add static entry for locked address '" << addr << "'");
644 return;
645 } else {
646 fqdncacheRelease(fce);
647 }
648 }
649
650 fce = new fqdncache_entry(addr);
651
652 int j = 0;
653 for (auto &h : hostnames) {
654 fce->names[j] = xstrdup(h.c_str());
655 Tolower(fce->names[j]);
656 ++j;
657
658 if (j >= FQDN_MAX_NAMES)
659 break;
660 }
661
662 fce->name_count = j;
663 fce->names[j] = nullptr; /* it's safe */
664 fce->flags.fromhosts = true;
667}
668
670static void
672{
673 Mgr::RegisterAction("fqdncache", "FQDN Cache Stats and Contents",
674 fqdnStats, 0, 1);
675
676}
677
684void
686{
687 int n;
688
690
691 if (fqdn_table)
692 return;
693
694 debugs(35, 3, "Initializing FQDN Cache...");
695
696 memset(&FqdncacheStats, '\0', sizeof(FqdncacheStats));
698
699 fqdncache_high = (long) (((float) Config.fqdncache.size *
700 (float) FQDN_HIGH_WATER) / (float) 100);
701
702 fqdncache_low = (long) (((float) Config.fqdncache.size *
703 (float) FQDN_LOW_WATER) / (float) 100);
704
705 n = hashPrime(fqdncache_high / 4);
706
707 fqdn_table = hash_create((HASHCMP *) strcmp, n, hash4);
708}
709
710#if SQUID_SNMP
717{
718 variable_list *Answer = nullptr;
719 MemBuf tmp;
720 debugs(49, 5, "snmp_netFqdnFn: Processing request:" << snmpDebugOid(Var->name, Var->name_length, tmp));
721 *ErrP = SNMP_ERR_NOERROR;
722
723 switch (Var->name[LEN_SQ_NET + 1]) {
724
725 case FQDN_ENT:
726 Answer = snmp_var_new_integer(Var->name, Var->name_length,
729 break;
730
731 case FQDN_REQ:
732 Answer = snmp_var_new_integer(Var->name, Var->name_length,
735 break;
736
737 case FQDN_HITS:
738 Answer = snmp_var_new_integer(Var->name, Var->name_length,
741 break;
742
743 case FQDN_PENDHIT:
744 /* this is now worthless */
745 Answer = snmp_var_new_integer(Var->name, Var->name_length,
746 0,
748 break;
749
750 case FQDN_NEGHIT:
751 Answer = snmp_var_new_integer(Var->name, Var->name_length,
754 break;
755
756 case FQDN_MISS:
757 Answer = snmp_var_new_integer(Var->name, Var->name_length,
760 break;
761
762 case FQDN_GHBN:
763 Answer = snmp_var_new_integer(Var->name, Var->name_length,
764 0, /* deprecated */
766 break;
767
768 default:
769 *ErrP = SNMP_ERR_NOSUCHNAME;
770 break;
771 }
772
773 return Answer;
774}
775
776#endif /*SQUID_SNMP */
777
#define false
Definition: GnuRegex.c:240
time_t squid_curtime
Definition: stub_libtime.cc:20
class SquidConfig Config
Definition: SquidConfig.cc:12
StatCounters statCounter
Definition: StatCounters.cc:12
#define assert(EX)
Definition: assert.h:19
#define LEN_SQ_NET
Definition: cache_snmp.h:49
int64_t snint
Definition: cache_snmp.h:14
@ FQDN_PENDHIT
Definition: cache_snmp.h:206
@ FQDN_NEGHIT
Definition: cache_snmp.h:207
@ FQDN_GHBN
Definition: cache_snmp.h:209
@ FQDN_HITS
Definition: cache_snmp.h:205
@ FQDN_MISS
Definition: cache_snmp.h:208
@ FQDN_ENT
Definition: cache_snmp.h:203
@ FQDN_REQ
Definition: cache_snmp.h:204
#define cbdataReference(var)
Definition: cbdata.h:341
#define cbdataReferenceValidDone(var, ptr)
Definition: cbdata.h:256
encapsulates DNS lookup results
Definition: LookupDetails.h:21
char * toStr(char *buf, const unsigned int blen, int force=AF_UNSPEC) const
Definition: Address.cc:792
bool isNoAddr() const
Definition: Address.cc:284
bool isAnyAddr() const
Definition: Address.cc:170
Definition: MemBuf.h:24
time_t negativeDnsTtl
Definition: SquidConfig.h:103
struct SquidConfig::@108 fqdncache
time_t positiveDnsTtl
Definition: SquidConfig.h:104
StatHist svcTime
Definition: StatCounters.h:98
struct StatCounters::@133 dns
void count(double val)
Definition: StatHist.cc:55
Definition: cbdata.cc:60
Definition: fqdncache.cc:80
unsigned char name_count
Definition: fqdncache.cc:90
struct fqdncache_entry::Flags flags
dlink_node lru
Definition: fqdncache.cc:97
~fqdncache_entry()
Definition: fqdncache.cc:603
int age() const
time passed since request_time or -1 if unknown
Definition: fqdncache.cc:145
struct timeval request_time
Definition: fqdncache.cc:96
unsigned short locks
Definition: fqdncache.cc:98
void * handlerData
Definition: fqdncache.cc:93
time_t expires
Definition: fqdncache.cc:89
fqdncache_entry(const char *name)
Definition: fqdncache.cc:253
FQDNH * handler
Definition: fqdncache.cc:92
char * error_message
Definition: fqdncache.cc:94
hash_link hash
Definition: fqdncache.cc:87
time_t lastref
Definition: fqdncache.cc:88
MEMPROXY_CLASS(fqdncache_entry)
char * names[FQDN_MAX_NAMES+1]
Definition: fqdncache.cc:91
int count
Definition: hash.h:31
unsigned int ttl
Definition: rfc1035.h:42
#define DBG_IMPORTANT
Definition: Stream.h:41
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:196
#define FQDN_LOOKUP_IF_MISS
Definition: defines.h:34
#define FQDN_MAX_NAMES
Definition: defines.h:35
void IDNSCB(void *cbdata, const rfc1035_rr *answer, const int recordsInAnswer, const char *error, bool lastAnswer)
Definition: forward.h:16
void idnsPTRLookup(const Ip::Address &, IDNSCB *, void *)
int type
Definition: errorpage.cc:152
void eventAdd(const char *name, EVH *func, void *arg, double when, int weight, bool cbdata)
Definition: event.cc:107
void fqdncacheAddEntryFromHosts(char *addr, SBufList &hostnames)
Definition: fqdncache.cc:636
static IDNSCB fqdncacheHandleReply
Definition: fqdncache.cc:122
void FQDNH(const char *, const Dns::LookupDetails &details, void *)
Definition: fqdncache.h:26
variable_list * snmp_netFqdnFn(variable_list *Var, snint *ErrP)
Definition: fqdncache.cc:716
void fqdncache_init(void)
Definition: fqdncache.cc:685
void fqdncache_purgelru(void *)
Definition: fqdncache.cc:200
static void purge_entries_fromhosts(void)
Definition: fqdncache.cc:229
const char * fqdncache_gethostbyaddr(const Ip::Address &addr, int flags)
Definition: fqdncache.cc:481
void fqdncache_nbgethostbyaddr(const Ip::Address &addr, FQDNH *handler, void *handlerData)
Definition: fqdncache.cc:414
void fqdncache_restart(void)
Definition: fqdncache.cc:620
static void fqdncacheAddEntry(fqdncache_entry *f)
Definition: fqdncache.cc:270
static void fqdncacheRelease(fqdncache_entry *)
Definition: fqdncache.cc:155
static hash_table * fqdn_table
Definition: fqdncache.cc:133
static long fqdncache_low
Definition: fqdncache.cc:136
static void fqdncacheRegisterWithCacheManager(void)
Definition: fqdncache.cc:671
#define FQDN_LOW_WATER
Definition: fqdncache.cc:68
static void fqdncacheLockEntry(fqdncache_entry *f)
Definition: fqdncache.cc:584
static int fqdncacheExpiredEntry(const fqdncache_entry *)
Definition: fqdncache.cc:185
static long fqdncache_high
Definition: fqdncache.cc:139
static fqdncache_entry * fqdncache_get(const char *)
Definition: fqdncache.cc:169
static void fqdncacheUnlockEntry(fqdncache_entry *f)
Definition: fqdncache.cc:594
static int fqdncacheParse(fqdncache_entry *, const rfc1035_rr *, int, const char *error_message)
Definition: fqdncache.cc:316
void fqdnStats(StoreEntry *sentry)
Definition: fqdncache.cc:530
#define FQDN_HIGH_WATER
Definition: fqdncache.cc:71
int fqdncacheCount()
Definition: fqdncache.cc:142
static dlink_list lru_list
Definition: fqdncache.cc:120
static struct _fqdn_cache_stats FqdncacheStats
static void fqdncacheCallback(fqdncache_entry *, int wait)
Definition: fqdncache.cc:291
SQUIDCEXTERN hash_table * hash_create(HASHCMP *, int, HASHHASH *)
Definition: hash.cc:108
SQUIDCEXTERN void hash_join(hash_table *, hash_link *)
Definition: hash.cc:131
SQUIDCEXTERN void hash_first(hash_table *)
Definition: hash.cc:172
SQUIDCEXTERN int hashPrime(int n)
Definition: hash.cc:293
int HASHCMP(const void *, const void *)
Definition: hash.h:13
SQUIDCEXTERN hash_link * hash_next(hash_table *)
Definition: hash.cc:188
SQUIDCEXTERN void hash_remove_link(hash_table *, hash_link *)
Definition: hash.cc:220
SQUIDCEXTERN HASHHASH hash4
Definition: hash.h:46
SQUIDCEXTERN const char * hashKeyStr(const hash_link *)
Definition: hash.cc:313
SQUIDCEXTERN hash_link * hash_lookup(hash_table *, const void *)
Definition: hash.cc:146
#define MAX_IPSTRLEN
Length of buffer that needs to be allocated to old a null-terminated IP-string.
Definition: forward.h:25
bool ResolveClientAddressesAsap
whether to do reverse DNS lookups for source IPs of accepted connections
Definition: fqdncache.cc:30
void RegisterAction(char const *action, char const *desc, OBJH *handler, int pw_req_flag, int atomic)
Definition: Registration.cc:16
#define xfree
#define xstrdup
static void handler(int signo)
Definition: purge.cc:854
#define RFC1035_TYPE_PTR
Definition: rfc1035.h:95
#define RFC1035_CLASS_IN
Definition: rfc1035.h:96
#define RFC1035_TYPE_CNAME
Definition: rfc1035.h:94
std::list< SBuf > SBufList
Definition: forward.h:23
const char * snmpDebugOid(oid *Name, snint Len, MemBuf &outbuf)
Definition: snmp_core.cc:1052
#define SNMP_ERR_NOERROR
Definition: snmp_error.h:42
#define SNMP_ERR_NOSUCHNAME
Definition: snmp_error.h:44
#define SMI_GAUGE32
Definition: snmp_vars.h:77
#define SMI_COUNTER32
Definition: snmp_vars.h:76
struct variable_list * snmp_var_new_integer(oid *, int, int, unsigned char)
Definition: snmp_vars.c:151
void storeAppendPrintf(StoreEntry *e, const char *fmt,...)
Definition: store.cc:828
Definition: fqdncache.cc:100
bool negcached
Definition: fqdncache.cc:103
bool fromhosts
Definition: fqdncache.cc:104
Flags()
Definition: fqdncache.cc:101
int name_length
Definition: snmp_vars.h:47
oid * name
Definition: snmp_vars.h:46
int unsigned int
Definition: stub_fd.cc:19
struct timeval current_time
the current UNIX time in timeval {seconds, microseconds} format
Definition: gadgets.cc:17
int tvSubMsec(struct timeval t1, struct timeval t2)
Definition: gadgets.cc:51
SQUIDCEXTERN void Tolower(char *)
Definition: util.c:28

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors