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