]> git.ipfire.org Git - thirdparty/squid.git/blame - src/client_db.cc
mkrelease: allow two digits for minor release numbers (#1837)
[thirdparty/squid.git] / src / client_db.cc
CommitLineData
516350ca 1/*
b8ae064d 2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
e25c139f 3 *
bbc27441
AJ
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.
516350ca 7 */
8
bbc27441
AJ
9/* DEBUG: section 00 Client Database */
10
582c2af2 11#include "squid.h"
38e50b3a 12#include "base/RunnersRegistry.h"
95e6d864 13#include "client_db.h"
602d9612 14#include "ClientInfo.h"
a553a5a3 15#include "event.h"
31971e6a 16#include "format/Token.h"
95e6d864 17#include "fqdncache.h"
96d89ea0 18#include "ip/Address.h"
1c7ae5ff 19#include "log/access_log.h"
8822ebee 20#include "mgr/Registration.h"
4d5904f7 21#include "SquidConfig.h"
a98bcbee 22#include "SquidMath.h"
e4f1fdae 23#include "StatCounters.h"
e6ccf245 24#include "Store.h"
4e540555 25#include "tools.h"
c41d4b6d 26
9c0a2256
FC
27#if SQUID_SNMP
28#include "snmp_core.h"
29#endif
30
aee3523a 31static hash_table *client_table = nullptr;
62e76326 32
b7ac5457 33static ClientInfo *clientdbAdd(const Ip::Address &addr);
ec878047 34static FREE clientdbFreeItem;
a0eba6bc 35static void clientdbStartGC(void);
36static void clientdbScheduledGC(void *);
37
9a0a18de 38#if USE_DELAY_POOLS
b4cd430a
CT
39static int max_clients = 32768;
40#else
a0eba6bc 41static int max_clients = 32;
b4cd430a
CT
42#endif
43
a0eba6bc 44static int cleanup_running = 0;
45static int cleanup_scheduled = 0;
46static int cleanup_removed;
47
9a0a18de 48#if USE_DELAY_POOLS
b4cd430a
CT
49#define CLIENT_DB_HASH_SIZE 65357
50#else
a0eba6bc 51#define CLIENT_DB_HASH_SIZE 467
b4cd430a 52#endif
c41d4b6d 53
3c670b50 54ClientInfo::ClientInfo(const Ip::Address &ip) :
b27668ec
EB
55#if USE_DELAY_POOLS
56 BandwidthBucket(0, 0, 0),
57#endif
3c670b50
AJ
58 addr(ip),
59 n_established(0),
60 last_seen(0)
61#if USE_DELAY_POOLS
b27668ec 62 , writeLimitingActive(false),
3c670b50
AJ
63 firstTimeConnection(true),
64 quotaQueue(nullptr),
65 rationedQuota(0),
66 rationedCount(0),
3c670b50
AJ
67 eventWaiting(false)
68#endif
c41d4b6d 69{
3c670b50 70 debugs(77, 9, "ClientInfo constructed, this=" << static_cast<void*>(this));
3c670b50 71 char *buf = static_cast<char*>(xmalloc(MAX_IPSTRLEN)); // becomes hash.key
b27668ec 72 key = addr.toStr(buf,MAX_IPSTRLEN);
3c670b50
AJ
73}
74
75static ClientInfo *
76clientdbAdd(const Ip::Address &addr)
77{
78 ClientInfo *c = new ClientInfo(addr);
b27668ec 79 hash_join(client_table, static_cast<hash_link*>(c));
e4f1fdae 80 ++statCounter.client_http.clients;
a0eba6bc 81
26ac0430 82 if ((statCounter.client_http.clients > max_clients) && !cleanup_running && cleanup_scheduled < 2) {
5086523e 83 ++cleanup_scheduled;
aee3523a 84 eventAdd("client_db garbage collector", clientdbScheduledGC, nullptr, 90, 0);
a0eba6bc 85 }
86
c41d4b6d 87 return c;
88}
89
5f5e883f 90static void
c41d4b6d 91clientdbInit(void)
92{
19054954 93 if (client_table)
62e76326 94 return;
95
30abd221 96 client_table = hash_create((HASHCMP *) strcmp, CLIENT_DB_HASH_SIZE, hash_string);
62ee09ca 97}
62e76326 98
38e50b3a
NH
99class ClientDbRr: public RegisteredRunner
100{
86c63190 101public:
21b7990f 102 /* RegisteredRunner API */
337b9aa4 103 void useConfig() override;
38e50b3a 104};
230d4410 105DefineRunnerRegistrator(ClientDbRr);
38e50b3a
NH
106
107void
21b7990f 108ClientDbRr::useConfig()
38e50b3a
NH
109{
110 clientdbInit();
111 Mgr::RegisterAction("client_list", "Cache Client List", clientdbDump, 0, 1);
112}
113
9a0a18de 114#if USE_DELAY_POOLS
b4cd430a
CT
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*/
119ClientInfo * clientdbGetInfo(const Ip::Address &addr)
120{
121 char key[MAX_IPSTRLEN];
122 ClientInfo *c;
123
124 if (!Config.onoff.client_db)
aee3523a 125 return nullptr;
b4cd430a 126
4dd643d5 127 addr.toStr(key,MAX_IPSTRLEN);
b4cd430a
CT
128
129 c = (ClientInfo *) hash_lookup(client_table, key);
aee3523a 130 if (c==nullptr) {
e0236918 131 debugs(77, DBG_IMPORTANT,"Client db does not contain information for given IP address "<<(const char*)key);
aee3523a 132 return nullptr;
b4cd430a
CT
133 }
134 return c;
135}
136#endif
62ee09ca 137void
91369933 138clientdbUpdate(const Ip::Address &addr, const LogTags &ltype, AnyP::ProtocolType p, size_t size)
c41d4b6d 139{
cc192b50 140 char key[MAX_IPSTRLEN];
429fdbec 141 ClientInfo *c;
62e76326 142
17a0a4ee 143 if (!Config.onoff.client_db)
62e76326 144 return;
145
4dd643d5 146 addr.toStr(key,MAX_IPSTRLEN);
62e76326 147
429fdbec 148 c = (ClientInfo *) hash_lookup(client_table, key);
62e76326 149
aee3523a 150 if (c == nullptr)
62e76326 151 c = clientdbAdd(addr);
152
aee3523a 153 if (c == nullptr)
62e76326 154 debug_trap("clientdbUpdate: Failed to add entry");
155
0c3d3f65 156 if (p == AnyP::PROTO_HTTP) {
5086523e 157 ++ c->Http.n_requests;
91369933 158 ++ c->Http.result_hist[ltype.oldType];
a0864754 159 c->Http.kbytes_out += size;
62e76326 160
91369933 161 if (ltype.isTcpHit())
a0864754 162 c->Http.hit_kbytes_out += size;
0c3d3f65 163 } else if (p == AnyP::PROTO_ICP) {
5086523e 164 ++ c->Icp.n_requests;
91369933 165 ++ c->Icp.result_hist[ltype.oldType];
a0864754 166 c->Icp.kbytes_out += size;
62e76326 167
91369933 168 if (LOG_UDP_HIT == ltype.oldType)
a0864754 169 c->Icp.hit_kbytes_out += size;
81d0c856 170 }
a0eba6bc 171
172 c->last_seen = squid_curtime;
c41d4b6d 173}
174
077fe581 175/**
9bc73deb 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 */
181int
b7ac5457 182clientdbEstablished(const Ip::Address &addr, int delta)
9bc73deb 183{
cc192b50 184 char key[MAX_IPSTRLEN];
9bc73deb 185 ClientInfo *c;
62e76326 186
9bc73deb 187 if (!Config.onoff.client_db)
62e76326 188 return 0;
189
4dd643d5 190 addr.toStr(key,MAX_IPSTRLEN);
62e76326 191
9bc73deb 192 c = (ClientInfo *) hash_lookup(client_table, key);
62e76326 193
aee3523a 194 if (c == nullptr) {
62e76326 195 c = clientdbAdd(addr);
cc192b50 196 }
62e76326 197
aee3523a 198 if (c == nullptr)
62e76326 199 debug_trap("clientdbUpdate: Failed to add entry");
200
9bc73deb 201 c->n_established += delta;
62e76326 202
9bc73deb 203 return c->n_established;
204}
205
e711a2ff 206#define CUTOFF_SECONDS 3600
c41d4b6d 207int
62e76326 208
b7ac5457 209clientdbCutoffDenied(const Ip::Address &addr)
c41d4b6d 210{
cc192b50 211 char key[MAX_IPSTRLEN];
e711a2ff 212 int NR;
213 int ND;
214 double p;
429fdbec 215 ClientInfo *c;
62e76326 216
59c4d35b 217 if (!Config.onoff.client_db)
62e76326 218 return 0;
219
4dd643d5 220 addr.toStr(key,MAX_IPSTRLEN);
62e76326 221
429fdbec 222 c = (ClientInfo *) hash_lookup(client_table, key);
62e76326 223
aee3523a 224 if (c == nullptr)
62e76326 225 return 0;
226
e711a2ff 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)
62e76326 231 return 1;
232
e711a2ff 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;
62e76326 238
e711a2ff 239 if (NR < 150)
62e76326 240 NR = 150;
241
e711a2ff 242 ND = c->Icp.result_hist[LOG_UDP_DENIED] - c->cutoff.n_denied;
62e76326 243
e711a2ff 244 p = 100.0 * ND / NR;
62e76326 245
e711a2ff 246 if (p < 95.0)
62e76326 247 return 0;
248
fa84c01d 249 debugs(1, DBG_CRITICAL, "WARNING: Probable misconfigured neighbor at " << key);
62e76326 250
fa84c01d 251 debugs(1, DBG_CRITICAL, "WARNING: " << ND << " of the last " << NR <<
bf8fe701 252 " ICP replies are DENIED");
62e76326 253
fa84c01d 254 debugs(1, DBG_CRITICAL, "WARNING: No replies will be sent for the next " <<
bf8fe701 255 CUTOFF_SECONDS << " seconds");
62e76326 256
e711a2ff 257 c->cutoff.time = squid_curtime;
62e76326 258
e711a2ff 259 c->cutoff.n_req = c->Icp.n_requests;
62e76326 260
e711a2ff 261 c->cutoff.n_denied = c->Icp.result_hist[LOG_UDP_DENIED];
62e76326 262
e711a2ff 263 return 1;
c41d4b6d 264}
265
8eb58c9c 266void
c41d4b6d 267clientdbDump(StoreEntry * sentry)
268{
c63e5f01 269 const char *name;
ac0963ac 270 int icp_total = 0;
271 int icp_hits = 0;
272 int http_total = 0;
273 int http_hits = 0;
15576b6a 274 storeAppendPrintf(sentry, "Cache Clients:\n");
0f6bebac 275 hash_first(client_table);
62e76326 276
b27668ec 277 while (hash_link *hash = hash_next(client_table)) {
3d452cb1 278 const ClientInfo *c = static_cast<const ClientInfo *>(hash);
b27668ec 279 storeAppendPrintf(sentry, "Address: %s\n", hashKeyStr(hash));
af6a12ee 280 if ( (name = fqdncache_gethostbyaddr(c->addr, 0)) ) {
c63e5f01
AJ
281 storeAppendPrintf(sentry, "Name: %s\n", name);
282 }
62e76326 283 storeAppendPrintf(sentry, "Currently established connections: %d\n",
284 c->n_established);
077fe581 285 storeAppendPrintf(sentry, " ICP Requests %d\n",
62e76326 286 c->Icp.n_requests);
287
91369933 288 for (LogTags_ot l = LOG_TAG_NONE; l < LOG_TYPE_MAX; ++l) {
62e76326 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
91369933 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));
62e76326 298 }
299
a98bcbee 300 storeAppendPrintf(sentry, " HTTP Requests %d\n", c->Http.n_requests);
62e76326 301
91369933 302 for (LogTags_ot l = LOG_TAG_NONE; l < LOG_TYPE_MAX; ++l) {
62e76326 303 if (c->Http.result_hist[l] == 0)
304 continue;
305
306 http_total += c->Http.result_hist[l];
307
91369933 308 if (LogTags(l).isTcpHit())
62e76326 309 http_hits += c->Http.result_hist[l];
310
311 storeAppendPrintf(sentry,
312 " %-20.20s %7d %3d%%\n",
91369933 313 LogTags(l).c_str(),
62e76326 314 c->Http.result_hist[l],
a98bcbee 315 Math::intPercent(c->Http.result_hist[l], c->Http.n_requests));
62e76326 316 }
317
318 storeAppendPrintf(sentry, "\n");
c41d4b6d 319 }
62e76326 320
ac0963ac 321 storeAppendPrintf(sentry, "TOTALS\n");
322 storeAppendPrintf(sentry, "ICP : %d Queries, %d Hits (%3d%%)\n",
a98bcbee 323 icp_total, icp_hits, Math::intPercent(icp_hits, icp_total));
ac0963ac 324 storeAppendPrintf(sentry, "HTTP: %d Requests, %d Hits (%3d%%)\n",
a98bcbee 325 http_total, http_hits, Math::intPercent(http_hits, http_total));
c41d4b6d 326}
83394596 327
ec878047 328static void
329clientdbFreeItem(void *data)
330{
e6ccf245 331 ClientInfo *c = (ClientInfo *)data;
3c670b50
AJ
332 delete c;
333}
334
335ClientInfo::~ClientInfo()
336{
b27668ec 337 safe_free(key);
b4cd430a 338
9a0a18de 339#if USE_DELAY_POOLS
3c670b50 340 if (CommQuotaQueue *q = quotaQueue) {
aee3523a 341 q->clientInfo = nullptr;
b4cd430a 342 delete q; // invalidates cbdata, cancelling any pending kicks
f33d34a8 343 }
b4cd430a
CT
344#endif
345
3c670b50 346 debugs(77, 9, "ClientInfo destructed, this=" << static_cast<void*>(this));
ec878047 347}
348
a0eba6bc 349static void
ced8def3 350clientdbScheduledGC(void *)
a0eba6bc 351{
352 cleanup_scheduled = 0;
353 clientdbStartGC();
354}
355
356static void
ced8def3 357clientdbGC(void *)
a0eba6bc 358{
359 static int bucket = 0;
360 hash_link *link_next;
361
362 link_next = hash_get_bucket(client_table, bucket++);
363
aee3523a 364 while (link_next != nullptr) {
a0eba6bc 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
b27668ec 384 hash_remove_link(client_table, static_cast<hash_link*>(c));
a0eba6bc 385
386 clientdbFreeItem(c);
387
e4f1fdae 388 --statCounter.client_http.clients;
a0eba6bc 389
5086523e 390 ++cleanup_removed;
a0eba6bc 391 }
392
393 if (bucket < CLIENT_DB_HASH_SIZE)
aee3523a 394 eventAdd("client_db garbage collector", clientdbGC, nullptr, 0.15, 0);
a0eba6bc 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;
aee3523a 402 eventAdd("client_db garbage collector", clientdbScheduledGC, nullptr, 6 * 3600, 0);
a0eba6bc 403 }
404
bf8fe701 405 debugs(49, 2, "clientdbGC: Removed " << cleanup_removed << " entries");
a0eba6bc 406 }
407}
408
409static void
410clientdbStartGC(void)
411{
412 max_clients = statCounter.client_http.clients;
413 cleanup_running = 1;
414 cleanup_removed = 0;
aee3523a 415 clientdbGC(nullptr);
a0eba6bc 416}
417
81d0c856 418#if SQUID_SNMP
62e76326 419
b7ac5457
AJ
420Ip::Address *
421client_entry(Ip::Address *current)
81d0c856 422{
cc192b50 423 char key[MAX_IPSTRLEN];
b27668ec 424 hash_first(client_table);
62e76326 425
26ac0430 426 if (current) {
4dd643d5 427 current->toStr(key,MAX_IPSTRLEN);
b27668ec
EB
428 while (hash_link *hash = hash_next(client_table)) {
429 if (!strcmp(key, hashKeyStr(hash)))
62e76326 430 break;
431 }
6f47fbc7 432 }
62e76326 433
0d5a3c3f 434 ClientInfo *c = static_cast<ClientInfo *>(hash_next(client_table));
62e76326 435
b27668ec 436 hash_last(client_table);
26ac0430 437
b27668ec 438 return c ? &c->addr : nullptr;
b6a2f15e 439}
81d0c856 440
441variable_list *
6f47fbc7 442snmp_meshCtblFn(variable_list * Var, snint * ErrP)
81d0c856 443{
6a644e75 444 char key[MAX_IPSTRLEN];
aee3523a 445 ClientInfo *c = nullptr;
b7ac5457 446 Ip::Address keyIp;
cc192b50 447
81d0c856 448 *ErrP = SNMP_ERR_NOERROR;
6a644e75 449 MemBuf tmp;
bf95c10a 450 debugs(49, 6, "Current : length=" << Var->name_length << ": " << snmpDebugOid(Var->name, Var->name_length, tmp));
055421ee 451 if (Var->name_length == 16) {
6a644e75 452 oid2addr(&(Var->name[12]), keyIp, 4);
055421ee 453 } else if (Var->name_length == 28) {
6a644e75 454 oid2addr(&(Var->name[12]), keyIp, 16);
6a644e75
AJ
455 } else {
456 *ErrP = SNMP_ERR_NOSUCHNAME;
aee3523a 457 return nullptr;
6a644e75
AJ
458 }
459
4dd643d5 460 keyIp.toStr(key, sizeof(key));
bf95c10a 461 debugs(49, 5, "[" << key << "] requested!");
81d0c856 462 c = (ClientInfo *) hash_lookup(client_table, key);
62e76326 463
aee3523a 464 if (c == nullptr) {
bf95c10a 465 debugs(49, 5, "not found.");
62e76326 466 *ErrP = SNMP_ERR_NOSUCHNAME;
aee3523a 467 return nullptr;
81d0c856 468 }
62e76326 469
aee3523a 470 variable_list *Answer = nullptr;
6a644e75 471 int aggr = 0;
6a644e75 472
135171fe 473 switch (Var->name[LEN_SQ_NET + 2]) {
62e76326 474
26ac0430
AJ
475 case MESH_CTBL_ADDR_TYPE: {
476 int ival;
4dd643d5 477 ival = c->addr.isIPv4() ? INETADDRESSTYPE_IPV4 : INETADDRESSTYPE_IPV6 ;
26ac0430
AJ
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];
4dd643d5 490 c->addr.toStr(client,MAX_IPSTRLEN);
26ac0430
AJ
491 Answer->val_len = strlen(client);
492 Answer->val.string = (u_char *) xstrdup(client);
493 }
494 break;
81d0c856 495 case MESH_CTBL_HTBYTES:
62e76326 496 Answer = snmp_var_new_integer(Var->name, Var->name_length,
497 (snint) c->Http.kbytes_out.kb,
498 SMI_COUNTER32);
499 break;
500
81d0c856 501 case MESH_CTBL_HTREQ:
62e76326 502 Answer = snmp_var_new_integer(Var->name, Var->name_length,
503 (snint) c->Http.n_requests,
504 SMI_COUNTER32);
505 break;
506
81d0c856 507 case MESH_CTBL_HTHITS:
62e76326 508 aggr = 0;
509
91369933
AJ
510 for (LogTags_ot l = LOG_TAG_NONE; l < LOG_TYPE_MAX; ++l) {
511 if (LogTags(l).isTcpHit())
62e76326 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
81d0c856 520 case MESH_CTBL_HTHITBYTES:
62e76326 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
81d0c856 526 case MESH_CTBL_ICPBYTES:
62e76326 527 Answer = snmp_var_new_integer(Var->name, Var->name_length,
528 (snint) c->Icp.kbytes_out.kb,
529 SMI_COUNTER32);
530 break;
531
81d0c856 532 case MESH_CTBL_ICPREQ:
62e76326 533 Answer = snmp_var_new_integer(Var->name, Var->name_length,
534 (snint) c->Icp.n_requests,
535 SMI_COUNTER32);
536 break;
537
81d0c856 538 case MESH_CTBL_ICPHITS:
62e76326 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
81d0c856 545 case MESH_CTBL_ICPHITBYTES:
62e76326 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
81d0c856 551 default:
62e76326 552 *ErrP = SNMP_ERR_NOSUCHNAME;
bf8fe701 553 debugs(49, 5, "snmp_meshCtblFn: illegal column.");
62e76326 554 break;
81d0c856 555 }
62e76326 556
81d0c856 557 return Answer;
558}
559
135171fe 560#endif /*SQUID_SNMP */
f53969cc 561