ratectrl.c revision 168515
1/*
2 * Copyright (c) 2003 Sendmail, 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 <sendmail.h>
48SM_RCSID("@(#)$Id: ratectrl.c,v 8.11 2006/08/15 23:24:57 ca Exp $")
49
50/*
51**  stuff included - given some warnings (inet_ntoa)
52**	- surely not everything is needed
53*/
54
55#if NETINET || NETINET6
56# include <arpa/inet.h>
57#endif	/* NETINET || NETINET6 */
58
59#include <sm/time.h>
60
61#ifndef HASH_ALG
62# define HASH_ALG	2
63#endif /* HASH_ALG */
64
65#ifndef RATECTL_DEBUG
66# define RATECTL_DEBUG  0
67#endif /* RATECTL_DEBUG */
68
69/* forward declarations */
70static int client_rate __P((time_t, SOCKADDR *, bool));
71static int total_rate __P((time_t, bool));
72#if 0
73static int sockaddrcmp __P((SOCKADDR *, SOCKADDR *));
74#endif /* 0 */
75
76/*
77**  CONNECTION_RATE_CHECK - updates connection history data
78**      and computes connection rate for the given host
79**
80**    Parameters:
81**      hostaddr -- ip address of smtp client
82**      e -- envelope
83**
84**    Returns:
85**      true (always)
86**
87**    Side Effects:
88**      updates connection history
89**
90**    Warnings:
91**      For each connection, this call shall be
92**      done only once with the value true for the
93**      update parameter.
94**      Typically, this call is done with the value
95**      true by the father, and once again with
96**      the value false by the children.
97**
98*/
99
100bool
101connection_rate_check(hostaddr, e)
102	SOCKADDR *hostaddr;
103	ENVELOPE *e;
104{
105	time_t now;
106	int totalrate, clientrate;
107	static int clientconn = 0;
108
109	now = time(NULL);
110#if RATECTL_DEBUG
111	sm_syslog(LOG_INFO, NOQID, "connection_rate_check entering...");
112#endif /* RATECTL_DEBUG */
113
114	/* update server connection rate */
115	totalrate = total_rate(now, e == NULL);
116#if RATECTL_DEBUG
117	sm_syslog(LOG_INFO, NOQID, "global connection rate: %d", globalRate);
118#endif /* RATECTL_DEBUG */
119
120	/* update client connection rate */
121	clientrate = client_rate(now, hostaddr, e == NULL);
122
123	if (e == NULL)
124		clientconn = count_open_connections(hostaddr);
125
126	if (e != NULL)
127	{
128		char s[16];
129
130		sm_snprintf(s, sizeof(s), "%d", clientrate);
131		macdefine(&e->e_macro, A_TEMP, macid("{client_rate}"), s);
132		sm_snprintf(s, sizeof(s), "%d", totalrate);
133		macdefine(&e->e_macro, A_TEMP, macid("{total_rate}"), s);
134		sm_snprintf(s, sizeof(s), "%d", clientconn);
135		macdefine(&e->e_macro, A_TEMP, macid("{client_connections}"),
136				s);
137	}
138	return true;
139}
140
141/*
142**  Data declarations needed to evaluate connection rate
143*/
144
145static int CollTime = 60;
146
147/* this should be a power of 2, otherwise CPMHMASK doesn't work well */
148#ifndef CPMHSIZE
149# define CPMHSIZE	1024
150#endif /* CPMHSIZE */
151
152#define CPMHMASK	(CPMHSIZE-1)
153
154#ifndef MAX_CT_STEPS
155# define MAX_CT_STEPS	10
156#endif /* MAX_CT_STEPS */
157
158/*
159**  time granularity: 10s (that's one "tick")
160**  will be initialised to ConnectionRateWindowSize/CHTSIZE
161**  before being used the first time
162*/
163
164static int ChtGran = -1;
165
166#define CHTSIZE		6
167
168/* Number of connections for a certain "tick" */
169typedef struct CTime
170{
171	unsigned long	ct_Ticks;
172	int		ct_Count;
173}
174CTime_T;
175
176typedef struct CHash
177{
178#if NETINET6 && NETINET
179	union
180	{
181		struct in_addr	c4_Addr;
182		struct in6_addr	c6_Addr;
183	} cu_Addr;
184# define ch_Addr4	cu_Addr.c4_Addr
185# define ch_Addr6	cu_Addr.c6_Addr
186#else /* NETINET6 && NETINET */
187# if NETINET6
188	struct in6_addr	ch_Addr;
189#  define ch_Addr6	ch_Addr
190# else /* NETINET6 */
191	struct in_addr ch_Addr;
192#  define ch_Addr4	ch_Addr
193# endif /* NETINET6 */
194#endif /* NETINET6 && NETINET */
195
196	int		ch_Family;
197	time_t		ch_LTime;
198	unsigned long	ch_colls;
199
200	/* 6 buckets for ticks: 60s */
201	CTime_T		ch_Times[CHTSIZE];
202}
203CHash_T;
204
205static CHash_T CHashAry[CPMHSIZE];
206static bool CHashAryOK = false;
207
208/*
209**  CLIENT_RATE - Evaluate connection rate per smtp client
210**
211**	Parameters:
212**		now - current time in secs
213**		saddr - client address
214**		update - update data / check only
215**
216**	Returns:
217**		connection rate (connections / ConnectionRateWindowSize)
218**
219**	Side effects:
220**		update static global data
221**
222*/
223
224static int
225client_rate(now, saddr, update)
226	 time_t now;
227	 SOCKADDR *saddr;
228	 bool update;
229{
230	unsigned int hv;
231	int i;
232	int cnt;
233	bool coll;
234	CHash_T *chBest = NULL;
235	unsigned int ticks;
236
237	cnt = 0;
238	hv = 0xABC3D20F;
239	if (ChtGran < 0)
240		ChtGran = ConnectionRateWindowSize / CHTSIZE;
241	if (ChtGran <= 0)
242		ChtGran = 10;
243
244	ticks = now / ChtGran;
245
246	if (!CHashAryOK)
247	{
248		memset(CHashAry, 0, sizeof(CHashAry));
249		CHashAryOK = true;
250	}
251
252	{
253		char *p;
254		int addrlen;
255#if HASH_ALG != 1
256		int c, d;
257#endif /* HASH_ALG != 1 */
258
259		switch (saddr->sa.sa_family)
260		{
261#if NETINET
262		  case AF_INET:
263			p = (char *)&saddr->sin.sin_addr;
264			addrlen = sizeof(struct in_addr);
265			break;
266#endif /* NETINET */
267#if NETINET6
268		  case AF_INET6:
269			p = (char *)&saddr->sin6.sin6_addr;
270			addrlen = sizeof(struct in6_addr);
271			break;
272#endif /* NETINET6 */
273		  default:
274			/* should not happen */
275			return -1;
276		}
277
278		/* compute hash value */
279		for (i = 0; i < addrlen; ++i, ++p)
280#if HASH_ALG == 1
281			hv = (hv << 5) ^ (hv >> 23) ^ *p;
282		hv = (hv ^ (hv >> 16));
283#elif HASH_ALG == 2
284		{
285			d = *p;
286			c = d;
287			c ^= c<<6;
288			hv += (c<<11) ^ (c>>1);
289			hv ^= (d<<14) + (d<<7) + (d<<4) + d;
290		}
291#elif HASH_ALG == 3
292		{
293			hv = (hv << 4) + *p;
294			d = hv & 0xf0000000;
295			if (d != 0)
296			{
297				hv ^= (d >> 24);
298				hv ^= d;
299			}
300		}
301#else /* HASH_ALG == 1 */
302			hv = ((hv << 1) ^ (*p & 0377)) % cctx->cc_size;
303#endif /* HASH_ALG == 1 */
304	}
305
306	coll = true;
307	for (i = 0; i < MAX_CT_STEPS; ++i)
308	{
309		CHash_T *ch = &CHashAry[(hv + i) & CPMHMASK];
310
311#if NETINET
312		if (saddr->sa.sa_family == AF_INET &&
313		    ch->ch_Family == AF_INET &&
314		    (saddr->sin.sin_addr.s_addr == ch->ch_Addr4.s_addr ||
315		     ch->ch_Addr4.s_addr == 0))
316		{
317			chBest = ch;
318			coll = false;
319			break;
320		}
321#endif /* NETINET */
322#if NETINET6
323		if (saddr->sa.sa_family == AF_INET6 &&
324		    ch->ch_Family == AF_INET6 &&
325		    (IN6_ARE_ADDR_EQUAL(&saddr->sin6.sin6_addr,
326				       &ch->ch_Addr6) != 0 ||
327		     IN6_IS_ADDR_UNSPECIFIED(&ch->ch_Addr6)))
328		{
329			chBest = ch;
330			coll = false;
331			break;
332		}
333#endif /* NETINET6 */
334		if (chBest == NULL || ch->ch_LTime == 0 ||
335		    ch->ch_LTime < chBest->ch_LTime)
336			chBest = ch;
337	}
338
339	/* Let's update data... */
340	if (update)
341	{
342		if (coll && (now - chBest->ch_LTime < CollTime))
343		{
344			/*
345			**  increment the number of collisions last
346			**  CollTime for this client
347			*/
348
349			chBest->ch_colls++;
350
351			/*
352			**  Maybe shall log if collision rate is too high...
353			**  and take measures to resize tables
354			**  if this is the case
355			*/
356		}
357
358		/*
359		**  If it's not a match, then replace the data.
360		**  Note: this purges the history of a colliding entry,
361		**  which may cause "overruns", i.e., if two entries are
362		**  "cancelling" each other out, then they may exceed
363		**  the limits that are set. This might be mitigated a bit
364		**  by the above "best of 5" function however.
365		**
366		**  Alternative approach: just use the old data, which may
367		**  cause false positives however.
368		**  To activate this, change deactivate following memset call.
369		*/
370
371		if (coll)
372		{
373#if NETINET
374			if (saddr->sa.sa_family == AF_INET)
375			{
376				chBest->ch_Family = AF_INET;
377				chBest->ch_Addr4 = saddr->sin.sin_addr;
378			}
379#endif /* NETINET */
380#if NETINET6
381			if (saddr->sa.sa_family == AF_INET6)
382			{
383				chBest->ch_Family = AF_INET6;
384				chBest->ch_Addr6 = saddr->sin6.sin6_addr;
385			}
386#endif /* NETINET6 */
387#if 1
388			memset(chBest->ch_Times, '\0',
389			       sizeof(chBest->ch_Times));
390#endif /* 1 */
391		}
392
393		chBest->ch_LTime = now;
394		{
395			CTime_T *ct = &chBest->ch_Times[ticks % CHTSIZE];
396
397			if (ct->ct_Ticks != ticks)
398			{
399				ct->ct_Ticks = ticks;
400				ct->ct_Count = 0;
401			}
402			++ct->ct_Count;
403		}
404	}
405
406	/* Now let's count connections on the window */
407	for (i = 0; i < CHTSIZE; ++i)
408	{
409		CTime_T *ct = &chBest->ch_Times[i];
410
411		if (ct->ct_Ticks <= ticks && ct->ct_Ticks >= ticks - CHTSIZE)
412			cnt += ct->ct_Count;
413	}
414
415#if RATECTL_DEBUG
416	sm_syslog(LOG_WARNING, NOQID,
417		"cln: cnt=(%d), CHTSIZE=(%d), ChtGran=(%d)",
418		cnt, CHTSIZE, ChtGran);
419#endif /* RATECTL_DEBUG */
420	return cnt;
421}
422
423/*
424**  TOTAL_RATE - Evaluate global connection rate
425**
426**	Parameters:
427**		now - current time in secs
428**		update - update data / check only
429**
430**	Returns:
431**		connection rate (connections / ConnectionRateWindowSize)
432*/
433
434static CTime_T srv_Times[CHTSIZE];
435static bool srv_Times_OK = false;
436
437static int
438total_rate(now, update)
439	 time_t now;
440	 bool update;
441{
442	int i;
443	int cnt = 0;
444	CTime_T *ct;
445	unsigned int ticks;
446
447	if (ChtGran < 0)
448		ChtGran = ConnectionRateWindowSize / CHTSIZE;
449	if (ChtGran == 0)
450		ChtGran = 10;
451	ticks = now / ChtGran;
452	if (!srv_Times_OK)
453	{
454		memset(srv_Times, 0, sizeof(srv_Times));
455		srv_Times_OK = true;
456	}
457
458	/* Let's update data */
459	if (update)
460	{
461		ct = &srv_Times[ticks % CHTSIZE];
462
463		if (ct->ct_Ticks != ticks)
464		{
465			ct->ct_Ticks = ticks;
466			ct->ct_Count = 0;
467		}
468		++ct->ct_Count;
469	}
470
471	/* Let's count connections on the window */
472	for (i = 0; i < CHTSIZE; ++i)
473	{
474		ct = &srv_Times[i];
475
476		if (ct->ct_Ticks <= ticks && ct->ct_Ticks >= ticks - CHTSIZE)
477			cnt += ct->ct_Count;
478	}
479
480#if RATECTL_DEBUG
481	sm_syslog(LOG_WARNING, NOQID,
482		"srv: cnt=(%d), CHTSIZE=(%d), ChtGran=(%d)",
483		 cnt, CHTSIZE, ChtGran);
484#endif /* RATECTL_DEBUG */
485
486	return cnt;
487}
488
489#if 0
490/*
491** SOCKADDRCMP - compare two SOCKADDR structures
492**   this function may be used to compare SOCKADDR
493**   structures when using bsearch and qsort functions
494**   in the same way we do with strcmp
495**
496** Parameters:
497**   a, b - addresses
498**
499** Returns:
500**   1 if a > b
501**  -1 if a < b
502**   0 if a = b
503**
504** OBS: This call isn't used at the moment, it will
505** be used when code will be extended to work with IPV6
506*/
507
508static int
509sockaddrcmp(a, b)
510	 SOCKADDR *a;
511	 SOCKADDR *b;
512{
513	if (a->sa.sa_family > b->sa.sa_family)
514		return 1;
515	if (a->sa.sa_family < b->sa.sa_family)
516		return -1;
517
518	switch (a->sa.sa_family)
519	{
520	  case AF_INET:
521		if (a->sin.sin_addr.s_addr > b->sin.sin_addr.s_addr)
522			return 1;
523		if (a->sin.sin_addr.s_addr < b->sin.sin_addr.s_addr)
524			return -1;
525		return 0;
526		break;
527
528	  case AF_INET6:
529		/* TO BE DONE */
530		break;
531	}
532	return 0;
533}
534#endif /* 0 */
535