]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/sendmail/src/ratectrl.c
Merge commit '850ef5ae11d69ea3381bd310f564f025fc8caea3'
[FreeBSD/FreeBSD.git] / contrib / sendmail / src / ratectrl.c
1 /*
2  * Copyright (c) 2003 Proofpoint, Inc. and its suppliers.
3  *      All rights reserved.
4  *
5  * By using this file, you agree to the terms and conditions set
6  * forth in the LICENSE file which can be found at the top level of
7  * the sendmail distribution.
8  *
9  * Contributed by Jose Marcio Martins da Cruz - Ecole des Mines de Paris
10  *   Jose-Marcio.Martins@ensmp.fr
11  */
12
13 /* a part of this code is based on inetd.c for which this copyright applies: */
14 /*
15  * Copyright (c) 1983, 1991, 1993, 1994
16  *      The Regents of the University of California.  All rights reserved.
17  *
18  * Redistribution and use in source and binary forms, with or without
19  * modification, are permitted provided that the following conditions
20  * are met:
21  * 1. Redistributions of source code must retain the above copyright
22  *    notice, this list of conditions and the following disclaimer.
23  * 2. Redistributions in binary form must reproduce the above copyright
24  *    notice, this list of conditions and the following disclaimer in the
25  *    documentation and/or other materials provided with the distribution.
26  * 3. All advertising materials mentioning features or use of this software
27  *    must display the following acknowledgement:
28  *      This product includes software developed by the University of
29  *      California, Berkeley and its contributors.
30  * 4. Neither the name of the University nor the names of its contributors
31  *    may be used to endorse or promote products derived from this software
32  *    without specific prior written permission.
33  *
34  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
35  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
36  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
37  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
38  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
39  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
40  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
41  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
42  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
43  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44  * SUCH DAMAGE.
45  */
46
47 #include <ratectrl.h>
48 SM_RCSID("@(#)$Id: ratectrl.c,v 8.14 2013-11-22 20:51:56 ca Exp $")
49
50 static int client_rate __P((time_t, SOCKADDR *, int));
51 static int total_rate __P((time_t, bool));
52 static unsigned int gen_hash __P((SOCKADDR *));
53 static void rate_init __P((void));
54
55 /*
56 **  CONNECTION_RATE_CHECK - updates connection history data
57 **      and computes connection rate for the given host
58 **
59 **      Parameters:
60 **              hostaddr -- IP address of SMTP client
61 **              e -- envelope
62 **
63 **      Returns:
64 **              none
65 **
66 **      Side Effects:
67 **              updates connection history
68 **
69 **      Warnings:
70 **              For each connection, this call shall be
71 **              done only once with the value true for the
72 **              update parameter.
73 **              Typically, this call is done with the value
74 **              true by the father, and once again with
75 **              the value false by the children.
76 */
77
78 void
79 connection_rate_check(hostaddr, e)
80         SOCKADDR *hostaddr;
81         ENVELOPE *e;
82 {
83         time_t now;
84         int totalrate, clientrate;
85         static int clientconn = 0;
86
87         now = time(NULL);
88 #if RATECTL_DEBUG
89         sm_syslog(LOG_INFO, NOQID, "connection_rate_check entering...");
90 #endif
91
92         /* update server connection rate */
93         totalrate = total_rate(now, e == NULL);
94 #if RATECTL_DEBUG
95         sm_syslog(LOG_INFO, NOQID, "global connection rate: %d", totalrate);
96 #endif
97
98         /* update client connection rate */
99         clientrate = client_rate(now, hostaddr, e == NULL ? SM_CLFL_UPDATE : SM_CLFL_NONE);
100
101         if (e == NULL)
102                 clientconn = count_open_connections(hostaddr);
103
104         if (e != NULL)
105         {
106                 char s[16];
107
108                 sm_snprintf(s, sizeof(s), "%d", clientrate);
109                 macdefine(&e->e_macro, A_TEMP, macid("{client_rate}"), s);
110                 sm_snprintf(s, sizeof(s), "%d", totalrate);
111                 macdefine(&e->e_macro, A_TEMP, macid("{total_rate}"), s);
112                 sm_snprintf(s, sizeof(s), "%d", clientconn);
113                 macdefine(&e->e_macro, A_TEMP, macid("{client_connections}"),
114                                 s);
115         }
116         return;
117 }
118
119 /*
120 **  Data declarations needed to evaluate connection rate
121 */
122
123 static int CollTime = 60;
124
125 /*
126 **  time granularity: 10s (that's one "tick")
127 **  will be initialised to ConnectionRateWindowSize/CHTSIZE
128 **  before being used the first time
129 */
130
131 static int ChtGran = -1;
132 static CHash_T CHashAry[CPMHSIZE];
133 static CTime_T srv_Times[CHTSIZE];
134
135 #ifndef MAX_CT_STEPS
136 # define MAX_CT_STEPS   10
137 #endif
138
139 /*
140 **  RATE_INIT - initialize local data
141 **
142 **      Parameters:
143 **              none
144 **
145 **      Returns:
146 **              none
147 **
148 **      Side effects:
149 **              initializes static global data
150 */
151
152 static void
153 rate_init()
154 {
155         if (ChtGran > 0)
156                 return;
157         ChtGran = ConnectionRateWindowSize / CHTSIZE;
158         if (ChtGran <= 0)
159                 ChtGran = 10;
160         memset(CHashAry, 0, sizeof(CHashAry));
161         memset(srv_Times, 0, sizeof(srv_Times));
162         return;
163 }
164
165 /*
166 **  GEN_HASH - calculate a hash value
167 **
168 **      Parameters:
169 **              saddr - client address
170 **
171 **      Returns:
172 **              hash value
173 */
174
175 static unsigned int
176 gen_hash(saddr)
177         SOCKADDR *saddr;
178 {
179         unsigned int hv;
180         int i;
181         int addrlen;
182         char *p;
183 #if HASH_ALG != 1
184         int c, d;
185 #endif
186
187         hv = 0xABC3D20F;
188         switch (saddr->sa.sa_family)
189         {
190 #if NETINET
191           case AF_INET:
192                 p = (char *)&saddr->sin.sin_addr;
193                 addrlen = sizeof(struct in_addr);
194                 break;
195 #endif /* NETINET */
196 #if NETINET6
197           case AF_INET6:
198                 p = (char *)&saddr->sin6.sin6_addr;
199                 addrlen = sizeof(struct in6_addr);
200                 break;
201 #endif /* NETINET6 */
202           default:
203                 /* should not happen */
204                 return -1;
205         }
206
207         /* compute hash value */
208         for (i = 0; i < addrlen; ++i, ++p)
209 #if HASH_ALG == 1
210                 hv = (hv << 5) ^ (hv >> 23) ^ *p;
211         hv = (hv ^ (hv >> 16));
212 #elif HASH_ALG == 2
213         {
214                 d = *p;
215                 c = d;
216                 c ^= c<<6;
217                 hv += (c<<11) ^ (c>>1);
218                 hv ^= (d<<14) + (d<<7) + (d<<4) + d;
219         }
220 #elif HASH_ALG == 3
221         {
222                 hv = (hv << 4) + *p;
223                 d = hv & 0xf0000000;
224                 if (d != 0)
225                 {
226                         hv ^= (d >> 24);
227                         hv ^= d;
228                 }
229         }
230 #else /* HASH_ALG == 1 */
231 # error "unsupported HASH_ALG"
232         hv = ((hv << 1) ^ (*p & 0377)) % cctx->cc_size; ???
233 #endif /* HASH_ALG == 1 */
234
235         return hv;
236 }
237
238 /*
239 **  CONN_LIMIT - Evaluate connection limits
240 **
241 **      Parameters:
242 **              e -- envelope (_FFR_OCC, for logging only)
243 **              now - current time in secs
244 **              saddr - client address
245 **              clflags - update data / check only / ...
246 **              hashary - hash array
247 **              ratelimit - rate limit (_FFR_OCC only)
248 **              conclimit - concurrency limit (_FFR_OCC only)
249 **
250 **      Returns:
251 #if _FFR_OCC
252 **              outgoing: limit exceeded?
253 #endif
254 **              incoming:
255 **                connection rate (connections / ConnectionRateWindowSize)
256 */
257
258 int
259 conn_limits(e, now, saddr, clflags, hashary, ratelimit, conclimit)
260         ENVELOPE *e;
261         time_t now;
262         SOCKADDR *saddr;
263         int clflags;
264         CHash_T hashary[];
265         int ratelimit;
266         int conclimit;
267 {
268         int i;
269         int cnt;
270         bool coll;
271         CHash_T *chBest = NULL;
272         CTime_T *ct = NULL;
273         unsigned int ticks;
274         unsigned int hv;
275 #if _FFR_OCC
276         bool exceeded = false;
277         int *prv, *pcv;
278 #endif
279 #if RATECTL_DEBUG || _FFR_OCC
280         bool logit = false;
281 #endif
282
283         cnt = 0;
284         hv = gen_hash(saddr);
285         ticks = now / ChtGran;
286
287         coll = true;
288         for (i = 0; i < MAX_CT_STEPS; ++i)
289         {
290                 CHash_T *ch = &hashary[(hv + i) & CPMHMASK];
291
292 #if NETINET
293                 if (saddr->sa.sa_family == AF_INET &&
294                     ch->ch_Family == AF_INET &&
295                     (saddr->sin.sin_addr.s_addr == ch->ch_Addr4.s_addr ||
296                      ch->ch_Addr4.s_addr == 0))
297                 {
298                         chBest = ch;
299                         coll = false;
300                         break;
301                 }
302 #endif /* NETINET */
303 #if NETINET6
304                 if (saddr->sa.sa_family == AF_INET6 &&
305                     ch->ch_Family == AF_INET6 &&
306                     (IN6_ARE_ADDR_EQUAL(&saddr->sin6.sin6_addr,
307                                        &ch->ch_Addr6) != 0 ||
308                      IN6_IS_ADDR_UNSPECIFIED(&ch->ch_Addr6)))
309                 {
310                         chBest = ch;
311                         coll = false;
312                         break;
313                 }
314 #endif /* NETINET6 */
315                 if (chBest == NULL || ch->ch_LTime == 0 ||
316                     ch->ch_LTime < chBest->ch_LTime)
317                         chBest = ch;
318         }
319
320         /* Let's update data... */
321         if ((clflags & (SM_CLFL_UPDATE|SM_CLFL_EXC)) != 0)
322         {
323                 if (coll && (now - chBest->ch_LTime < CollTime))
324                 {
325                         /*
326                         **  increment the number of collisions last
327                         **  CollTime for this client
328                         */
329
330                         chBest->ch_colls++;
331
332                         /*
333                         **  Maybe shall log if collision rate is too high...
334                         **  and take measures to resize tables
335                         **  if this is the case
336                         */
337                 }
338
339                 /*
340                 **  If it's not a match, then replace the data.
341                 **  Note: this purges the history of a colliding entry,
342                 **  which may cause "overruns", i.e., if two entries are
343                 **  "cancelling" each other out, then they may exceed
344                 **  the limits that are set. This might be mitigated a bit
345                 **  by the above "best of 5" function however.
346                 **
347                 **  Alternative approach: just use the old data, which may
348                 **  cause false positives however.
349                 **  To activate this, deactivate the memset() call.
350                 */
351
352                 if (coll)
353                 {
354 #if NETINET
355                         if (saddr->sa.sa_family == AF_INET)
356                         {
357                                 chBest->ch_Family = AF_INET;
358                                 chBest->ch_Addr4 = saddr->sin.sin_addr;
359                         }
360 #endif /* NETINET */
361 #if NETINET6
362                         if (saddr->sa.sa_family == AF_INET6)
363                         {
364                                 chBest->ch_Family = AF_INET6;
365                                 chBest->ch_Addr6 = saddr->sin6.sin6_addr;
366                         }
367 #endif /* NETINET6 */
368                         memset(chBest->ch_Times, '\0',
369                                sizeof(chBest->ch_Times));
370                 }
371
372                 chBest->ch_LTime = now;
373                 ct = &chBest->ch_Times[ticks % CHTSIZE];
374
375                 if (ct->ct_Ticks != ticks)
376                 {
377                         ct->ct_Ticks = ticks;
378                         ct->ct_Count = 0;
379                 }
380                 if ((clflags & SM_CLFL_UPDATE) != 0)
381                         ++ct->ct_Count;
382         }
383
384         /* Now let's count connections on the window */
385         for (i = 0; i < CHTSIZE; ++i)
386         {
387                 CTime_T *cth;
388
389                 cth = &chBest->ch_Times[i];
390                 if (cth->ct_Ticks <= ticks && cth->ct_Ticks >= ticks - CHTSIZE)
391                         cnt += cth->ct_Count;
392         }
393 #if _FFR_OCC
394         prv = pcv = NULL;
395         if (ct != NULL && ((clflags & SM_CLFL_EXC) != 0))
396         {
397                 if (ratelimit > 0)
398                 {
399                         if (cnt < ratelimit)
400                                 prv = &(ct->ct_Count);
401                         else
402                                 exceeded = true;
403                 }
404                 else if (ratelimit < 0 && ct->ct_Count > 0)
405                         --ct->ct_Count;
406         }
407
408         if (chBest != NULL && ((clflags & SM_CLFL_EXC) != 0))
409         {
410                 if (conclimit > 0)
411                 {
412                         if (chBest->ch_oc < conclimit)
413                                 pcv = &(chBest->ch_oc);
414                         else
415                                 exceeded = true;
416                 }
417                 else if (conclimit < 0 && chBest->ch_oc > 0)
418                         --chBest->ch_oc;
419         }
420 #endif
421
422 #if RATECTL_DEBUG
423         logit = true;
424 #endif
425 #if RATECTL_DEBUG || _FFR_OCC
426 # if _FFR_OCC
427         if (!exceeded)
428         {
429                 if (prv != NULL)
430                         ++*prv, ++cnt;
431                 if (pcv != NULL)
432                         ++*pcv;
433         }
434         logit = exceeded || LogLevel > 11;
435 # endif
436         if (logit)
437                 sm_syslog(LOG_DEBUG, e != NULL ? e->e_id : NOQID,
438                         "conn_limits: addr=%s, flags=0x%x, rate=%d/%d, conc=%d/%d, exc=%d",
439                         saddr->sa.sa_family == AF_INET
440                                 ? inet_ntoa(saddr->sin.sin_addr) : "???",
441                         clflags, cnt, ratelimit,
442 # if _FFR_OCC
443                         chBest != NULL ? chBest->ch_oc : -1
444 # else
445                         -2
446 # endif
447                         , conclimit
448 # if _FFR_OCC
449                         , exceeded
450 # else
451                         , 0
452 # endif
453                         );
454 #endif
455 #if _FFR_OCC
456         if ((clflags & SM_CLFL_EXC) != 0)
457                 return exceeded;
458 #endif
459         return cnt;
460 }
461
462 /*
463 **  CLIENT_RATE - Evaluate connection rate per SMTP client
464 **
465 **      Parameters:
466 **              now - current time in secs
467 **              saddr - client address
468 **              clflags - update data / check only
469 **
470 **      Returns:
471 **              connection rate (connections / ConnectionRateWindowSize)
472 **
473 **      Side effects:
474 **              update static global data
475 */
476
477 static int
478 client_rate(now, saddr, clflags)
479         time_t now;
480         SOCKADDR *saddr;
481         int clflags;
482 {
483         rate_init();
484         return conn_limits(NULL, now, saddr, clflags, CHashAry, 0, 0);
485 }
486
487 /*
488 **  TOTAL_RATE - Evaluate global connection rate
489 **
490 **      Parameters:
491 **              now - current time in secs
492 **              update - update data / check only
493 **
494 **      Returns:
495 **              connection rate (connections / ConnectionRateWindowSize)
496 */
497
498 static int
499 total_rate(now, update)
500         time_t now;
501         bool update;
502 {
503         int i;
504         int cnt = 0;
505         CTime_T *ct;
506         unsigned int ticks;
507
508         rate_init();
509         ticks = now / ChtGran;
510
511         /* Let's update data */
512         if (update)
513         {
514                 ct = &srv_Times[ticks % CHTSIZE];
515
516                 if (ct->ct_Ticks != ticks)
517                 {
518                         ct->ct_Ticks = ticks;
519                         ct->ct_Count = 0;
520                 }
521                 ++ct->ct_Count;
522         }
523
524         /* Let's count connections on the window */
525         for (i = 0; i < CHTSIZE; ++i)
526         {
527                 ct = &srv_Times[i];
528
529                 if (ct->ct_Ticks <= ticks && ct->ct_Ticks >= ticks - CHTSIZE)
530                         cnt += ct->ct_Count;
531         }
532
533 #if RATECTL_DEBUG
534         sm_syslog(LOG_WARNING, NOQID,
535                 "total: cnt=%d, CHTSIZE=%d, ChtGran=%d",
536                 cnt, CHTSIZE, ChtGran);
537 #endif
538
539         return cnt;
540 }
541
542 #if RATECTL_DEBUG || _FFR_OCC
543 void
544 dump_ch(fp)
545         SM_FILE_T *fp;
546 {
547         int i, j, cnt;
548         unsigned int ticks;
549
550         ticks = time(NULL) / ChtGran;
551         sm_io_fprintf(fp, SM_TIME_DEFAULT, "dump_ch\n");
552         for (i = 0; i < CPMHSIZE; i++)
553         {
554                 CHash_T *ch = &CHashAry[i];
555                 bool valid;
556
557                 valid = false;
558 # if NETINET
559                 valid = (ch->ch_Family == AF_INET);
560                 if (valid)
561                         sm_io_fprintf(fp, SM_TIME_DEFAULT, "ip=%s ",
562                                 inet_ntoa(ch->ch_Addr4));
563 # endif /* NETINET */
564 # if NETINET6
565                 if (ch->ch_Family == AF_INET6)
566                 {
567                         char buf[64], *str;
568
569                         valid = true;
570                         str = anynet_ntop(&ch->ch_Addr6, buf, sizeof(buf));
571                         if (str != NULL)
572                                 sm_io_fprintf(fp, SM_TIME_DEFAULT, "ip=%s ",
573                                         str);
574                 }
575 # endif /* NETINET6 */
576                 if (!valid)
577                         continue;
578
579                 cnt = 0;
580                 for (j = 0; j < CHTSIZE; ++j)
581                 {
582                         CTime_T *cth;
583
584                         cth = &ch->ch_Times[j];
585                         if (cth->ct_Ticks <= ticks && cth->ct_Ticks >= ticks - CHTSIZE)
586                                 cnt += cth->ct_Count;
587                 }
588
589                 sm_io_fprintf(fp, SM_TIME_DEFAULT, "time=%ld cnt=%d ",
590                         (long) ch->ch_LTime, cnt);
591 # if _FFR_OCC
592                 sm_io_fprintf(fp, SM_TIME_DEFAULT, "oc=%d", ch->ch_oc);
593 # endif
594                 sm_io_fprintf(fp, SM_TIME_DEFAULT, "\n");
595         }
596         sm_io_flush(fp, SM_TIME_DEFAULT);
597 }
598
599 #endif /* RATECTL_DEBUG || _FFR_OCC */