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