xref: /freebsd/contrib/sendmail/src/ratectrl.c (revision 3c4ba5f55438f7afd4f4b0b56f88f2bb505fd6a6)
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 
423 #if RATECTL_DEBUG
424 	logit = true;
425 #endif
426 #if RATECTL_DEBUG || _FFR_OCC
427 # if _FFR_OCC
428 	if (!exceeded)
429 	{
430 		if (prv != NULL)
431 			++*prv, ++cnt;
432 		if (pcv != NULL)
433 			++*pcv;
434 	}
435 	logit = exceeded || LogLevel > 11;
436 # endif
437 	if (logit)
438 		sm_syslog(LOG_DEBUG, e != NULL ? e->e_id : NOQID,
439 			"conn_limits: addr=%s, flags=0x%x, rate=%d/%d, conc=%d/%d, exc=%d",
440 			saddr->sa.sa_family == AF_INET
441 				? inet_ntoa(saddr->sin.sin_addr) : "???",
442 			clflags, cnt, ratelimit,
443 # if _FFR_OCC
444 			chBest != NULL ? chBest->ch_oc : -1
445 # else
446 			-2
447 # endif
448 			, conclimit
449 # if _FFR_OCC
450 			, exceeded
451 # else
452 			, 0
453 # endif
454 			);
455 #endif
456 #if _FFR_OCC
457 	if ((clflags & SM_CLFL_EXC) != 0)
458 		return exceeded;
459 #endif
460 	return cnt;
461 }
462 
463 /*
464 **  CLIENT_RATE - Evaluate connection rate per SMTP client
465 **
466 **	Parameters:
467 **		now - current time in secs
468 **		saddr - client address
469 **		clflags - update data / check only
470 **
471 **	Returns:
472 **		connection rate (connections / ConnectionRateWindowSize)
473 **
474 **	Side effects:
475 **		update static global data
476 */
477 
478 static int
479 client_rate(now, saddr, clflags)
480 	time_t now;
481 	SOCKADDR *saddr;
482 	int clflags;
483 {
484 	rate_init();
485 	return conn_limits(NULL, now, saddr, clflags, CHashAry, 0, 0);
486 }
487 
488 /*
489 **  TOTAL_RATE - Evaluate global connection rate
490 **
491 **	Parameters:
492 **		now - current time in secs
493 **		update - update data / check only
494 **
495 **	Returns:
496 **		connection rate (connections / ConnectionRateWindowSize)
497 */
498 
499 static int
500 total_rate(now, update)
501 	time_t now;
502 	bool update;
503 {
504 	int i;
505 	int cnt = 0;
506 	CTime_T *ct;
507 	unsigned int ticks;
508 
509 	rate_init();
510 	ticks = now / ChtGran;
511 
512 	/* Let's update data */
513 	if (update)
514 	{
515 		ct = &srv_Times[ticks % CHTSIZE];
516 
517 		if (ct->ct_Ticks != ticks)
518 		{
519 			ct->ct_Ticks = ticks;
520 			ct->ct_Count = 0;
521 		}
522 		++ct->ct_Count;
523 	}
524 
525 	/* Let's count connections on the window */
526 	for (i = 0; i < CHTSIZE; ++i)
527 	{
528 		ct = &srv_Times[i];
529 
530 		if (ct->ct_Ticks <= ticks && ct->ct_Ticks >= ticks - CHTSIZE)
531 			cnt += ct->ct_Count;
532 	}
533 
534 #if RATECTL_DEBUG
535 	sm_syslog(LOG_WARNING, NOQID,
536 		"total: cnt=%d, CHTSIZE=%d, ChtGran=%d",
537 		cnt, CHTSIZE, ChtGran);
538 #endif
539 
540 	return cnt;
541 }
542 
543 #if RATECTL_DEBUG || _FFR_OCC
544 void
545 dump_ch(fp)
546 	SM_FILE_T *fp;
547 {
548 	int i, j, cnt;
549 	unsigned int ticks;
550 
551 	ticks = time(NULL) / ChtGran;
552 	sm_io_fprintf(fp, SM_TIME_DEFAULT, "dump_ch\n");
553 	for (i = 0; i < CPMHSIZE; i++)
554 	{
555 		CHash_T *ch = &CHashAry[i];
556 		bool valid;
557 
558 		valid = false;
559 # if NETINET
560 		valid = (ch->ch_Family == AF_INET);
561 		if (valid)
562 			sm_io_fprintf(fp, SM_TIME_DEFAULT, "ip=%s ",
563 				inet_ntoa(ch->ch_Addr4));
564 # endif /* NETINET */
565 # if NETINET6
566 		if (ch->ch_Family == AF_INET6)
567 		{
568 			char buf[64], *str;
569 
570 			valid = true;
571 			str = anynet_ntop(&ch->ch_Addr6, buf, sizeof(buf));
572 			if (str != NULL)
573 				sm_io_fprintf(fp, SM_TIME_DEFAULT, "ip=%s ",
574 					str);
575 		}
576 # endif /* NETINET6 */
577 		if (!valid)
578 			continue;
579 
580 		cnt = 0;
581 		for (j = 0; j < CHTSIZE; ++j)
582 		{
583 			CTime_T *cth;
584 
585 			cth = &ch->ch_Times[j];
586 			if (cth->ct_Ticks <= ticks && cth->ct_Ticks >= ticks - CHTSIZE)
587 				cnt += cth->ct_Count;
588 		}
589 
590 		sm_io_fprintf(fp, SM_TIME_DEFAULT, "time=%ld cnt=%d ",
591 			(long) ch->ch_LTime, cnt);
592 # if _FFR_OCC
593 		sm_io_fprintf(fp, SM_TIME_DEFAULT, "oc=%d", ch->ch_oc);
594 # endif
595 		sm_io_fprintf(fp, SM_TIME_DEFAULT, "\n");
596 	}
597 	sm_io_flush(fp, SM_TIME_DEFAULT);
598 }
599 
600 #endif /* RATECTL_DEBUG || _FFR_OCC */
601