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