]> git.ipfire.org Git - thirdparty/squid.git/blob - src/client_db.cc
moved mem.cc prototypes to Mem.h
[thirdparty/squid.git] / src / client_db.cc
1 /*
2 * $Id$
3 *
4 * DEBUG: section 00 Client Database
5 * AUTHOR: Duane Wessels
6 *
7 * SQUID Web Proxy Cache http://www.squid-cache.org/
8 * ----------------------------------------------------------
9 *
10 * Squid is the result of efforts by numerous individuals from
11 * the Internet community; see the CONTRIBUTORS file for full
12 * details. Many organizations have provided support for Squid's
13 * development; see the SPONSORS file for full details. Squid is
14 * Copyrighted (C) 2001 by the Regents of the University of
15 * California; see the COPYRIGHT file for full details. Squid
16 * incorporates software developed and/or copyrighted by other
17 * sources; see the CREDITS file for full details.
18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
23 *
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
32 *
33 */
34
35 #include "squid.h"
36 #include "client_db.h"
37 #include "event.h"
38 #include "format/Token.h"
39 #include "ClientInfo.h"
40 #include "fqdncache.h"
41 #include "ip/Address.h"
42 #include "Mem.h"
43 #include "mgr/Registration.h"
44 #include "protos.h"
45 #include "SquidMath.h"
46 #include "SquidTime.h"
47 #include "StatCounters.h"
48 #include "Store.h"
49 #include "tools.h"
50
51 static hash_table *client_table = NULL;
52
53 static ClientInfo *clientdbAdd(const Ip::Address &addr);
54 static FREE clientdbFreeItem;
55 static void clientdbStartGC(void);
56 static void clientdbScheduledGC(void *);
57
58 #if USE_DELAY_POOLS
59 static int max_clients = 32768;
60 #else
61 static int max_clients = 32;
62 #endif
63
64 static int cleanup_running = 0;
65 static int cleanup_scheduled = 0;
66 static int cleanup_removed;
67
68 #if USE_DELAY_POOLS
69 #define CLIENT_DB_HASH_SIZE 65357
70 #else
71 #define CLIENT_DB_HASH_SIZE 467
72 #endif
73
74 static ClientInfo *
75
76 clientdbAdd(const Ip::Address &addr)
77 {
78 ClientInfo *c;
79 char *buf = new char[MAX_IPSTRLEN];
80 c = (ClientInfo *)memAllocate(MEM_CLIENT_INFO);
81 c->hash.key = addr.NtoA(buf,MAX_IPSTRLEN);
82 c->addr = addr;
83 #if USE_DELAY_POOLS
84 /* setup default values for client write limiter */
85 c->writeLimitingActive=false;
86 c->writeSpeedLimit=0;
87 c->bucketSize = 0;
88 c->firstTimeConnection=true;
89 c->quotaQueue = NULL;
90 c->rationedQuota = 0;
91 c->rationedCount = 0;
92 c->selectWaiting = false;
93 c->eventWaiting = false;
94
95 /* get current time */
96 getCurrentTime();
97 c->prevTime=current_dtime;/* put current time to have something sensible here */
98 #endif
99 hash_join(client_table, &c->hash);
100 ++statCounter.client_http.clients;
101
102 if ((statCounter.client_http.clients > max_clients) && !cleanup_running && cleanup_scheduled < 2) {
103 ++cleanup_scheduled;
104 eventAdd("client_db garbage collector", clientdbScheduledGC, NULL, 90, 0);
105 }
106
107 return c;
108 }
109
110 static void
111 clientdbRegisterWithCacheManager(void)
112 {
113 Mgr::RegisterAction("client_list", "Cache Client List", clientdbDump, 0, 1);
114 }
115
116 void
117 clientdbInit(void)
118 {
119 clientdbRegisterWithCacheManager();
120
121 if (client_table)
122 return;
123
124 client_table = hash_create((HASHCMP *) strcmp, CLIENT_DB_HASH_SIZE, hash_string);
125 }
126
127 #if USE_DELAY_POOLS
128 /* returns ClientInfo for given IP addr
129 Returns NULL if no such client (or clientdb turned off)
130 (it is assumed that clientdbEstablished will be called before and create client record if needed)
131 */
132 ClientInfo * clientdbGetInfo(const Ip::Address &addr)
133 {
134 char key[MAX_IPSTRLEN];
135 ClientInfo *c;
136
137 if (!Config.onoff.client_db)
138 return NULL;
139
140 addr.NtoA(key,MAX_IPSTRLEN);
141
142 c = (ClientInfo *) hash_lookup(client_table, key);
143 if (c==NULL) {
144 debugs(77, DBG_IMPORTANT,"Client db does not contain information for given IP address "<<(const char*)key);
145 return NULL;
146 }
147 return c;
148 }
149 #endif
150 void
151 clientdbUpdate(const Ip::Address &addr, log_type ltype, AnyP::ProtocolType p, size_t size)
152 {
153 char key[MAX_IPSTRLEN];
154 ClientInfo *c;
155
156 if (!Config.onoff.client_db)
157 return;
158
159 addr.NtoA(key,MAX_IPSTRLEN);
160
161 c = (ClientInfo *) hash_lookup(client_table, key);
162
163 if (c == NULL)
164 c = clientdbAdd(addr);
165
166 if (c == NULL)
167 debug_trap("clientdbUpdate: Failed to add entry");
168
169 if (p == AnyP::PROTO_HTTP) {
170 ++ c->Http.n_requests;
171 ++ c->Http.result_hist[ltype];
172 kb_incr(&c->Http.kbytes_out, size);
173
174 if (logTypeIsATcpHit(ltype))
175 kb_incr(&c->Http.hit_kbytes_out, size);
176 } else if (p == AnyP::PROTO_ICP) {
177 ++ c->Icp.n_requests;
178 ++ c->Icp.result_hist[ltype];
179 kb_incr(&c->Icp.kbytes_out, size);
180
181 if (LOG_UDP_HIT == ltype)
182 kb_incr(&c->Icp.hit_kbytes_out, size);
183 }
184
185 c->last_seen = squid_curtime;
186 }
187
188 /**
189 * This function tracks the number of currently established connections
190 * for a client IP address. When a connection is accepted, call this
191 * with delta = 1. When the connection is closed, call with delta =
192 * -1. To get the current value, simply call with delta = 0.
193 */
194 int
195 clientdbEstablished(const Ip::Address &addr, int delta)
196 {
197 char key[MAX_IPSTRLEN];
198 ClientInfo *c;
199
200 if (!Config.onoff.client_db)
201 return 0;
202
203 addr.NtoA(key,MAX_IPSTRLEN);
204
205 c = (ClientInfo *) hash_lookup(client_table, key);
206
207 if (c == NULL) {
208 c = clientdbAdd(addr);
209 }
210
211 if (c == NULL)
212 debug_trap("clientdbUpdate: Failed to add entry");
213
214 c->n_established += delta;
215
216 return c->n_established;
217 }
218
219 #define CUTOFF_SECONDS 3600
220 int
221
222 clientdbCutoffDenied(const Ip::Address &addr)
223 {
224 char key[MAX_IPSTRLEN];
225 int NR;
226 int ND;
227 double p;
228 ClientInfo *c;
229
230 if (!Config.onoff.client_db)
231 return 0;
232
233 addr.NtoA(key,MAX_IPSTRLEN);
234
235 c = (ClientInfo *) hash_lookup(client_table, key);
236
237 if (c == NULL)
238 return 0;
239
240 /*
241 * If we are in a cutoff window, we don't send a reply
242 */
243 if (squid_curtime - c->cutoff.time < CUTOFF_SECONDS)
244 return 1;
245
246 /*
247 * Calculate the percent of DENIED replies since the last
248 * cutoff time.
249 */
250 NR = c->Icp.n_requests - c->cutoff.n_req;
251
252 if (NR < 150)
253 NR = 150;
254
255 ND = c->Icp.result_hist[LOG_UDP_DENIED] - c->cutoff.n_denied;
256
257 p = 100.0 * ND / NR;
258
259 if (p < 95.0)
260 return 0;
261
262 debugs(1, DBG_CRITICAL, "WARNING: Probable misconfigured neighbor at " << key);
263
264 debugs(1, DBG_CRITICAL, "WARNING: " << ND << " of the last " << NR <<
265 " ICP replies are DENIED");
266
267 debugs(1, DBG_CRITICAL, "WARNING: No replies will be sent for the next " <<
268 CUTOFF_SECONDS << " seconds");
269
270 c->cutoff.time = squid_curtime;
271
272 c->cutoff.n_req = c->Icp.n_requests;
273
274 c->cutoff.n_denied = c->Icp.result_hist[LOG_UDP_DENIED];
275
276 return 1;
277 }
278
279 log_type &operator++ (log_type &aLogType)
280 {
281 int tmp = (int)aLogType;
282 aLogType = (log_type)(++tmp);
283 return aLogType;
284 }
285
286 void
287 clientdbDump(StoreEntry * sentry)
288 {
289 const char *name;
290 ClientInfo *c;
291 log_type l;
292 int icp_total = 0;
293 int icp_hits = 0;
294 int http_total = 0;
295 int http_hits = 0;
296 storeAppendPrintf(sentry, "Cache Clients:\n");
297 hash_first(client_table);
298
299 while ((c = (ClientInfo *) hash_next(client_table))) {
300 storeAppendPrintf(sentry, "Address: %s\n", hashKeyStr(&c->hash));
301 if ( (name = fqdncache_gethostbyaddr(c->addr, 0)) ) {
302 storeAppendPrintf(sentry, "Name: %s\n", name);
303 }
304 storeAppendPrintf(sentry, "Currently established connections: %d\n",
305 c->n_established);
306 storeAppendPrintf(sentry, " ICP Requests %d\n",
307 c->Icp.n_requests);
308
309 for (l = LOG_TAG_NONE; l < LOG_TYPE_MAX; ++l) {
310 if (c->Icp.result_hist[l] == 0)
311 continue;
312
313 icp_total += c->Icp.result_hist[l];
314
315 if (LOG_UDP_HIT == l)
316 icp_hits += c->Icp.result_hist[l];
317
318 storeAppendPrintf(sentry, " %-20.20s %7d %3d%%\n",Format::log_tags[l], c->Icp.result_hist[l], Math::intPercent(c->Icp.result_hist[l], c->Icp.n_requests));
319 }
320
321 storeAppendPrintf(sentry, " HTTP Requests %d\n", c->Http.n_requests);
322
323 for (l = LOG_TAG_NONE; l < LOG_TYPE_MAX; ++l) {
324 if (c->Http.result_hist[l] == 0)
325 continue;
326
327 http_total += c->Http.result_hist[l];
328
329 if (logTypeIsATcpHit(l))
330 http_hits += c->Http.result_hist[l];
331
332 storeAppendPrintf(sentry,
333 " %-20.20s %7d %3d%%\n",
334 Format::log_tags[l],
335 c->Http.result_hist[l],
336 Math::intPercent(c->Http.result_hist[l], c->Http.n_requests));
337 }
338
339 storeAppendPrintf(sentry, "\n");
340 }
341
342 storeAppendPrintf(sentry, "TOTALS\n");
343 storeAppendPrintf(sentry, "ICP : %d Queries, %d Hits (%3d%%)\n",
344 icp_total, icp_hits, Math::intPercent(icp_hits, icp_total));
345 storeAppendPrintf(sentry, "HTTP: %d Requests, %d Hits (%3d%%)\n",
346 http_total, http_hits, Math::intPercent(http_hits, http_total));
347 }
348
349 static void
350 clientdbFreeItem(void *data)
351 {
352 ClientInfo *c = (ClientInfo *)data;
353 safe_free(c->hash.key);
354
355 #if USE_DELAY_POOLS
356 if (CommQuotaQueue *q = c->quotaQueue) {
357 q->clientInfo = NULL;
358 delete q; // invalidates cbdata, cancelling any pending kicks
359 }
360 #endif
361
362 memFree(c, MEM_CLIENT_INFO);
363 }
364
365 void
366 clientdbFreeMemory(void)
367 {
368 hashFreeItems(client_table, clientdbFreeItem);
369 hashFreeMemory(client_table);
370 client_table = NULL;
371 }
372
373 static void
374 clientdbScheduledGC(void *unused)
375 {
376 cleanup_scheduled = 0;
377 clientdbStartGC();
378 }
379
380 static void
381 clientdbGC(void *unused)
382 {
383 static int bucket = 0;
384 hash_link *link_next;
385
386 link_next = hash_get_bucket(client_table, bucket++);
387
388 while (link_next != NULL) {
389 ClientInfo *c = (ClientInfo *)link_next;
390 int age = squid_curtime - c->last_seen;
391 link_next = link_next->next;
392
393 if (c->n_established)
394 continue;
395
396 if (age < 24 * 3600 && c->Http.n_requests > 100)
397 continue;
398
399 if (age < 4 * 3600 && (c->Http.n_requests > 10 || c->Icp.n_requests > 10))
400 continue;
401
402 if (age < 5 * 60 && (c->Http.n_requests > 1 || c->Icp.n_requests > 1))
403 continue;
404
405 if (age < 60)
406 continue;
407
408 hash_remove_link(client_table, &c->hash);
409
410 clientdbFreeItem(c);
411
412 --statCounter.client_http.clients;
413
414 ++cleanup_removed;
415 }
416
417 if (bucket < CLIENT_DB_HASH_SIZE)
418 eventAdd("client_db garbage collector", clientdbGC, NULL, 0.15, 0);
419 else {
420 bucket = 0;
421 cleanup_running = 0;
422 max_clients = statCounter.client_http.clients * 3 / 2;
423
424 if (!cleanup_scheduled) {
425 cleanup_scheduled = 1;
426 eventAdd("client_db garbage collector", clientdbScheduledGC, NULL, 6 * 3600, 0);
427 }
428
429 debugs(49, 2, "clientdbGC: Removed " << cleanup_removed << " entries");
430 }
431 }
432
433 static void
434 clientdbStartGC(void)
435 {
436 max_clients = statCounter.client_http.clients;
437 cleanup_running = 1;
438 cleanup_removed = 0;
439 clientdbGC(NULL);
440 }
441
442 #if SQUID_SNMP
443
444 Ip::Address *
445 client_entry(Ip::Address *current)
446 {
447 ClientInfo *c = NULL;
448 char key[MAX_IPSTRLEN];
449
450 if (current) {
451 current->NtoA(key,MAX_IPSTRLEN);
452 hash_first(client_table);
453 while ((c = (ClientInfo *) hash_next(client_table))) {
454 if (!strcmp(key, hashKeyStr(&c->hash)))
455 break;
456 }
457
458 c = (ClientInfo *) hash_next(client_table);
459 } else {
460 hash_first(client_table);
461 c = (ClientInfo *) hash_next(client_table);
462 }
463
464 hash_last(client_table);
465
466 if (c)
467 return (&c->addr);
468 else
469 return (NULL);
470
471 }
472
473 variable_list *
474 snmp_meshCtblFn(variable_list * Var, snint * ErrP)
475 {
476 char key[MAX_IPSTRLEN];
477 ClientInfo *c = NULL;
478 Ip::Address keyIp;
479
480 *ErrP = SNMP_ERR_NOERROR;
481 MemBuf tmp;
482 debugs(49, 6, HERE << "Current : length=" << Var->name_length << ": " << snmpDebugOid(Var->name, Var->name_length, tmp));
483 if (Var->name_length == 16) {
484 oid2addr(&(Var->name[12]), keyIp, 4);
485 } else if (Var->name_length == 28) {
486 oid2addr(&(Var->name[12]), keyIp, 16);
487 } else {
488 *ErrP = SNMP_ERR_NOSUCHNAME;
489 return NULL;
490 }
491
492 keyIp.NtoA(key, sizeof(key));
493 debugs(49, 5, HERE << "[" << key << "] requested!");
494 c = (ClientInfo *) hash_lookup(client_table, key);
495
496 if (c == NULL) {
497 debugs(49, 5, HERE << "not found.");
498 *ErrP = SNMP_ERR_NOSUCHNAME;
499 return NULL;
500 }
501
502 variable_list *Answer = NULL;
503 int aggr = 0;
504 log_type l;
505
506 switch (Var->name[LEN_SQ_NET + 2]) {
507
508 case MESH_CTBL_ADDR_TYPE: {
509 int ival;
510 ival = c->addr.IsIPv4() ? INETADDRESSTYPE_IPV4 : INETADDRESSTYPE_IPV6 ;
511 Answer = snmp_var_new_integer(Var->name, Var->name_length,
512 ival, SMI_INTEGER);
513 }
514 break;
515
516 case MESH_CTBL_ADDR: {
517 Answer = snmp_var_new(Var->name, Var->name_length);
518 // InetAddress doesn't have its own ASN.1 type,
519 // like IpAddr does (SMI_IPADDRESS)
520 // See: rfc4001.txt
521 Answer->type = ASN_OCTET_STR;
522 char client[MAX_IPSTRLEN];
523 c->addr.NtoA(client,MAX_IPSTRLEN);
524 Answer->val_len = strlen(client);
525 Answer->val.string = (u_char *) xstrdup(client);
526 }
527 break;
528 case MESH_CTBL_HTBYTES:
529 Answer = snmp_var_new_integer(Var->name, Var->name_length,
530 (snint) c->Http.kbytes_out.kb,
531 SMI_COUNTER32);
532 break;
533
534 case MESH_CTBL_HTREQ:
535 Answer = snmp_var_new_integer(Var->name, Var->name_length,
536 (snint) c->Http.n_requests,
537 SMI_COUNTER32);
538 break;
539
540 case MESH_CTBL_HTHITS:
541 aggr = 0;
542
543 for (l = LOG_TAG_NONE; l < LOG_TYPE_MAX; ++l) {
544 if (logTypeIsATcpHit(l))
545 aggr += c->Http.result_hist[l];
546 }
547
548 Answer = snmp_var_new_integer(Var->name, Var->name_length,
549 (snint) aggr,
550 SMI_COUNTER32);
551 break;
552
553 case MESH_CTBL_HTHITBYTES:
554 Answer = snmp_var_new_integer(Var->name, Var->name_length,
555 (snint) c->Http.hit_kbytes_out.kb,
556 SMI_COUNTER32);
557 break;
558
559 case MESH_CTBL_ICPBYTES:
560 Answer = snmp_var_new_integer(Var->name, Var->name_length,
561 (snint) c->Icp.kbytes_out.kb,
562 SMI_COUNTER32);
563 break;
564
565 case MESH_CTBL_ICPREQ:
566 Answer = snmp_var_new_integer(Var->name, Var->name_length,
567 (snint) c->Icp.n_requests,
568 SMI_COUNTER32);
569 break;
570
571 case MESH_CTBL_ICPHITS:
572 aggr = c->Icp.result_hist[LOG_UDP_HIT];
573 Answer = snmp_var_new_integer(Var->name, Var->name_length,
574 (snint) aggr,
575 SMI_COUNTER32);
576 break;
577
578 case MESH_CTBL_ICPHITBYTES:
579 Answer = snmp_var_new_integer(Var->name, Var->name_length,
580 (snint) c->Icp.hit_kbytes_out.kb,
581 SMI_COUNTER32);
582 break;
583
584 default:
585 *ErrP = SNMP_ERR_NOSUCHNAME;
586 debugs(49, 5, "snmp_meshCtblFn: illegal column.");
587 break;
588 }
589
590 return Answer;
591 }
592
593 #endif /*SQUID_SNMP */