]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/client_db.cc
2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 00 Client Database */
12 #include "base/RunnersRegistry.h"
13 #include "client_db.h"
14 #include "ClientInfo.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"
28 #include "snmp_core.h"
31 static hash_table
*client_table
= nullptr;
33 static ClientInfo
*clientdbAdd(const Ip::Address
&addr
);
34 static FREE clientdbFreeItem
;
35 static void clientdbStartGC(void);
36 static void clientdbScheduledGC(void *);
39 static int max_clients
= 32768;
41 static int max_clients
= 32;
44 static int cleanup_running
= 0;
45 static int cleanup_scheduled
= 0;
46 static int cleanup_removed
;
49 #define CLIENT_DB_HASH_SIZE 65357
51 #define CLIENT_DB_HASH_SIZE 467
54 ClientInfo::ClientInfo(const Ip::Address
&ip
) :
56 BandwidthBucket(0, 0, 0),
62 , writeLimitingActive(false),
63 firstTimeConnection(true),
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
);
76 clientdbAdd(const Ip::Address
&addr
)
78 ClientInfo
*c
= new ClientInfo(addr
);
79 hash_join(client_table
, static_cast<hash_link
*>(c
));
80 ++statCounter
.client_http
.clients
;
82 if ((statCounter
.client_http
.clients
> max_clients
) && !cleanup_running
&& cleanup_scheduled
< 2) {
84 eventAdd("client_db garbage collector", clientdbScheduledGC
, nullptr, 90, 0);
96 client_table
= hash_create((HASHCMP
*) strcmp
, CLIENT_DB_HASH_SIZE
, hash_string
);
99 class ClientDbRr
: public RegisteredRunner
102 /* RegisteredRunner API */
103 void useConfig() override
;
105 RunnerRegistrationEntry(ClientDbRr
);
108 ClientDbRr::useConfig()
111 Mgr::RegisterAction("client_list", "Cache Client List", clientdbDump
, 0, 1);
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)
119 ClientInfo
* clientdbGetInfo(const Ip::Address
&addr
)
121 char key
[MAX_IPSTRLEN
];
124 if (!Config
.onoff
.client_db
)
127 addr
.toStr(key
,MAX_IPSTRLEN
);
129 c
= (ClientInfo
*) hash_lookup(client_table
, key
);
131 debugs(77, DBG_IMPORTANT
,"Client db does not contain information for given IP address "<<(const char*)key
);
138 clientdbUpdate(const Ip::Address
&addr
, const LogTags
<ype
, AnyP::ProtocolType p
, size_t size
)
140 char key
[MAX_IPSTRLEN
];
143 if (!Config
.onoff
.client_db
)
146 addr
.toStr(key
,MAX_IPSTRLEN
);
148 c
= (ClientInfo
*) hash_lookup(client_table
, key
);
151 c
= clientdbAdd(addr
);
154 debug_trap("clientdbUpdate: Failed to add entry");
156 if (p
== AnyP::PROTO_HTTP
) {
157 ++ c
->Http
.n_requests
;
158 ++ c
->Http
.result_hist
[ltype
.oldType
];
159 c
->Http
.kbytes_out
+= size
;
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
;
168 if (LOG_UDP_HIT
== ltype
.oldType
)
169 c
->Icp
.hit_kbytes_out
+= size
;
172 c
->last_seen
= squid_curtime
;
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.
182 clientdbEstablished(const Ip::Address
&addr
, int delta
)
184 char key
[MAX_IPSTRLEN
];
187 if (!Config
.onoff
.client_db
)
190 addr
.toStr(key
,MAX_IPSTRLEN
);
192 c
= (ClientInfo
*) hash_lookup(client_table
, key
);
195 c
= clientdbAdd(addr
);
199 debug_trap("clientdbUpdate: Failed to add entry");
201 c
->n_established
+= delta
;
203 return c
->n_established
;
206 #define CUTOFF_SECONDS 3600
209 clientdbCutoffDenied(const Ip::Address
&addr
)
211 char key
[MAX_IPSTRLEN
];
217 if (!Config
.onoff
.client_db
)
220 addr
.toStr(key
,MAX_IPSTRLEN
);
222 c
= (ClientInfo
*) hash_lookup(client_table
, key
);
228 * If we are in a cutoff window, we don't send a reply
230 if (squid_curtime
- c
->cutoff
.time
< CUTOFF_SECONDS
)
234 * Calculate the percent of DENIED replies since the last
237 NR
= c
->Icp
.n_requests
- c
->cutoff
.n_req
;
242 ND
= c
->Icp
.result_hist
[LOG_UDP_DENIED
] - c
->cutoff
.n_denied
;
249 debugs(1, DBG_CRITICAL
, "WARNING: Probable misconfigured neighbor at " << key
);
251 debugs(1, DBG_CRITICAL
, "WARNING: " << ND
<< " of the last " << NR
<<
252 " ICP replies are DENIED");
254 debugs(1, DBG_CRITICAL
, "WARNING: No replies will be sent for the next " <<
255 CUTOFF_SECONDS
<< " seconds");
257 c
->cutoff
.time
= squid_curtime
;
259 c
->cutoff
.n_req
= c
->Icp
.n_requests
;
261 c
->cutoff
.n_denied
= c
->Icp
.result_hist
[LOG_UDP_DENIED
];
267 clientdbDump(StoreEntry
* sentry
)
274 storeAppendPrintf(sentry
, "Cache Clients:\n");
275 hash_first(client_table
);
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
);
283 storeAppendPrintf(sentry
, "Currently established connections: %d\n",
285 storeAppendPrintf(sentry
, " ICP Requests %d\n",
288 for (LogTags_ot l
= LOG_TAG_NONE
; l
< LOG_TYPE_MAX
; ++l
) {
289 if (c
->Icp
.result_hist
[l
] == 0)
292 icp_total
+= c
->Icp
.result_hist
[l
];
294 if (LOG_UDP_HIT
== l
)
295 icp_hits
+= c
->Icp
.result_hist
[l
];
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
));
300 storeAppendPrintf(sentry
, " HTTP Requests %d\n", c
->Http
.n_requests
);
302 for (LogTags_ot l
= LOG_TAG_NONE
; l
< LOG_TYPE_MAX
; ++l
) {
303 if (c
->Http
.result_hist
[l
] == 0)
306 http_total
+= c
->Http
.result_hist
[l
];
308 if (LogTags(l
).isTcpHit())
309 http_hits
+= c
->Http
.result_hist
[l
];
311 storeAppendPrintf(sentry
,
312 " %-20.20s %7d %3d%%\n",
314 c
->Http
.result_hist
[l
],
315 Math::intPercent(c
->Http
.result_hist
[l
], c
->Http
.n_requests
));
318 storeAppendPrintf(sentry
, "\n");
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
));
329 clientdbFreeItem(void *data
)
331 ClientInfo
*c
= (ClientInfo
*)data
;
335 ClientInfo::~ClientInfo()
340 if (CommQuotaQueue
*q
= quotaQueue
) {
341 q
->clientInfo
= nullptr;
342 delete q
; // invalidates cbdata, cancelling any pending kicks
346 debugs(77, 9, "ClientInfo destructed, this=" << static_cast<void*>(this));
350 clientdbScheduledGC(void *)
352 cleanup_scheduled
= 0;
359 static int bucket
= 0;
360 hash_link
*link_next
;
362 link_next
= hash_get_bucket(client_table
, bucket
++);
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
;
369 if (c
->n_established
)
372 if (age
< 24 * 3600 && c
->Http
.n_requests
> 100)
375 if (age
< 4 * 3600 && (c
->Http
.n_requests
> 10 || c
->Icp
.n_requests
> 10))
378 if (age
< 5 * 60 && (c
->Http
.n_requests
> 1 || c
->Icp
.n_requests
> 1))
384 hash_remove_link(client_table
, static_cast<hash_link
*>(c
));
388 --statCounter
.client_http
.clients
;
393 if (bucket
< CLIENT_DB_HASH_SIZE
)
394 eventAdd("client_db garbage collector", clientdbGC
, nullptr, 0.15, 0);
398 max_clients
= statCounter
.client_http
.clients
* 3 / 2;
400 if (!cleanup_scheduled
) {
401 cleanup_scheduled
= 1;
402 eventAdd("client_db garbage collector", clientdbScheduledGC
, nullptr, 6 * 3600, 0);
405 debugs(49, 2, "clientdbGC: Removed " << cleanup_removed
<< " entries");
410 clientdbStartGC(void)
412 max_clients
= statCounter
.client_http
.clients
;
421 client_entry(Ip::Address
*current
)
423 char key
[MAX_IPSTRLEN
];
424 hash_first(client_table
);
427 current
->toStr(key
,MAX_IPSTRLEN
);
428 while (hash_link
*hash
= hash_next(client_table
)) {
429 if (!strcmp(key
, hashKeyStr(hash
)))
434 ClientInfo
*c
= static_cast<ClientInfo
*>(hash_next(client_table
));
436 hash_last(client_table
);
438 return c
? &c
->addr
: nullptr;
442 snmp_meshCtblFn(variable_list
* Var
, snint
* ErrP
)
444 char key
[MAX_IPSTRLEN
];
445 ClientInfo
*c
= nullptr;
448 *ErrP
= SNMP_ERR_NOERROR
;
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);
456 *ErrP
= SNMP_ERR_NOSUCHNAME
;
460 keyIp
.toStr(key
, sizeof(key
));
461 debugs(49, 5, "[" << key
<< "] requested!");
462 c
= (ClientInfo
*) hash_lookup(client_table
, key
);
465 debugs(49, 5, "not found.");
466 *ErrP
= SNMP_ERR_NOSUCHNAME
;
470 variable_list
*Answer
= nullptr;
473 switch (Var
->name
[LEN_SQ_NET
+ 2]) {
475 case MESH_CTBL_ADDR_TYPE
: {
477 ival
= c
->addr
.isIPv4() ? INETADDRESSTYPE_IPV4
: INETADDRESSTYPE_IPV6
;
478 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
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)
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
);
495 case MESH_CTBL_HTBYTES
:
496 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
497 (snint
) c
->Http
.kbytes_out
.kb
,
501 case MESH_CTBL_HTREQ
:
502 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
503 (snint
) c
->Http
.n_requests
,
507 case MESH_CTBL_HTHITS
:
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
];
515 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
520 case MESH_CTBL_HTHITBYTES
:
521 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
522 (snint
) c
->Http
.hit_kbytes_out
.kb
,
526 case MESH_CTBL_ICPBYTES
:
527 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
528 (snint
) c
->Icp
.kbytes_out
.kb
,
532 case MESH_CTBL_ICPREQ
:
533 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
534 (snint
) c
->Icp
.n_requests
,
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
,
545 case MESH_CTBL_ICPHITBYTES
:
546 Answer
= snmp_var_new_integer(Var
->name
, Var
->name_length
,
547 (snint
) c
->Icp
.hit_kbytes_out
.kb
,
552 *ErrP
= SNMP_ERR_NOSUCHNAME
;
553 debugs(49, 5, "snmp_meshCtblFn: illegal column.");
560 #endif /*SQUID_SNMP */