]>
Commit | Line | Data |
---|---|---|
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 "event.h" | |
37 | #include "format/Token.h" | |
38 | #include "ClientInfo.h" | |
39 | #include "ip/Address.h" | |
40 | #include "mgr/Registration.h" | |
41 | #include "protos.h" | |
42 | #include "SquidMath.h" | |
43 | #include "SquidTime.h" | |
44 | #include "StatCounters.h" | |
45 | #include "Store.h" | |
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, DBG_IMPORTANT,"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, DBG_CRITICAL, "WARNING: Probable misconfigured neighbor at " << key); | |
259 | ||
260 | debugs(1, DBG_CRITICAL, "WARNING: " << ND << " of the last " << NR << | |
261 | " ICP replies are DENIED"); | |
262 | ||
263 | debugs(1, DBG_CRITICAL, "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 */ |