dst_blacklist.c
dcb59e67
 /*
  * $Id$
  *
  * resolver related functions
  *
  * Copyright (C) 2006 iptelorg GmbH
  *
  * This file is part of ser, a free SIP server.
  *
  * ser is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version
  *
  * For a license to use the ser software under conditions
  * other than those described here, or to purchase support for this
  * software, please contact iptel.org by e-mail at the following addresses:
  *    info@iptel.org
  *
  * ser is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License 
  * along with this program; if not, write to the Free Software 
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 /* History:
  * --------
  *  2006-07-29  created by andrei
  */
 
 
 #ifdef USE_DST_BLACKLIST
 
 #include "dst_blacklist.h"
82e7e3ce
 #include "globals.h"
dcb59e67
 #include "mem/shm_mem.h"
 #include "hashes.h"
 #include "locking.h"
 #include "timer.h"
 #include "timer_ticks.h"
 #include "ip_addr.h"
 #include "error.h"
 #include "rpc.h"
 
 
 
 
 struct dst_blst_entry{
 	struct dst_blst_entry* next;
 	ticks_t expire;
 	unsigned short port;
 	unsigned char proto;
 	unsigned char flags; /* contains the address type + error flags */
 	unsigned char ip[4]; /* 4 for ipv4, 16 for ipv6 */ 
 };
 
 #define DST_BLST_ENTRY_SIZE(b) \
 		(sizeof(struct dst_blst_entry)+((b).flags&BLST_IS_IPV6)*12)
 
 
 #define DST_BLST_HASH_SIZE		1024
 #define DEFAULT_BLST_TIMEOUT		60  /* 1 min. */
 #define DEFAULT_BLST_MAX_MEM	250 /* 1 Kb FIXME (debugging)*/
 #define DEFAULT_BLST_TIMER_INTERVAL		60 /* 1 min */
 
 
 static gen_lock_t* blst_lock=0;
 static struct timer_ln* blst_timer_h=0;
 
 static volatile unsigned int* blst_mem_used=0;
 unsigned int  blst_max_mem=DEFAULT_BLST_MAX_MEM; /* maximum memory used
 													for the blacklist entries*/
 unsigned int blst_timeout=DEFAULT_BLST_TIMEOUT;
 unsigned int blst_timer_interval=DEFAULT_BLST_TIMER_INTERVAL;
 struct dst_blst_entry** dst_blst_hash=0;
 
 
 #define LOCK_BLST()		lock_get(blst_lock)
 #define UNLOCK_BLST()	lock_release(blst_lock)
 
 
 inline static void blst_destroy_entry(struct dst_blst_entry* e)
 {
 	shm_free(e);
 }
 
 
 static ticks_t blst_timer(ticks_t ticks, struct timer_ln* tl, void* data);
 
 
 inline static void dst_blst_entry2ip(struct ip_addr* ip,
 										struct dst_blst_entry* e)
 {
 	if (e->flags & BLST_IS_IPV6){
 		ip->af=AF_INET6;
 		ip->len=16;
 	}else{
 		ip->af=AF_INET;
 		ip->len=4;
 	}
 	memcpy(ip->u.addr, e->ip, ip->len);
 }
 
 
 
 inline static unsigned short dst_blst_hash_no(unsigned char proto,
 											  struct ip_addr* ip,
 											  unsigned short port)
 {
 	str s1;
 	str s2;
 	
 	s1.s=(char*)ip->u.addr;
 	s1.len=ip->len;
 	s2.s=(char*)&port;
 	s2.len=sizeof(unsigned short);
 	return get_hash2_raw(&s1, &s2)%DST_BLST_HASH_SIZE;
 }
 
 
 
 void destroy_dst_blacklist()
 {
3e227634
 	int r;
 	struct dst_blst_entry** crt;
 	struct dst_blst_entry* e;
 	
dcb59e67
 	if (blst_timer_h){
 		timer_del(blst_timer_h);
 		timer_free(blst_timer_h);
 		blst_timer_h=0;
 	}
 	if (blst_lock){
 		lock_destroy(blst_lock);
 		lock_dealloc(blst_lock);
 		blst_lock=0;
 	}
 	if (dst_blst_hash){
3e227634
 		for(r=0; r<DST_BLST_HASH_SIZE; r++){
4be0dabc
 			crt=&dst_blst_hash[r];
 			while(*crt){
 				e=*crt;
 				*crt=(*crt)->next;
 				blst_destroy_entry(e);
3e227634
 			}
 		}
dcb59e67
 		shm_free(dst_blst_hash);
 		dst_blst_hash=0;
 	}
 	if (blst_mem_used){
 		shm_free((void*)blst_mem_used);
 		blst_mem_used=0;
 	}
 }
 
 
 
 int init_dst_blacklist()
 {
 	int ret;
 	
 	ret=-1;
 	blst_mem_used=shm_malloc(sizeof(*blst_mem_used));
 	if (blst_mem_used==0){
 		ret=E_OUT_OF_MEM;
 		goto error;
 	}
3e227634
 	*blst_mem_used=0;
dcb59e67
 	dst_blst_hash=shm_malloc(sizeof(struct dst_blst_entry*) *
 											DST_BLST_HASH_SIZE);
 	if (dst_blst_hash==0){
 		ret=E_OUT_OF_MEM;
 		goto error;
 	}
3e227634
 	memset(dst_blst_hash, 0, sizeof(struct dst_blst_entry*) *
 								DST_BLST_HASH_SIZE);
dcb59e67
 	blst_lock=lock_alloc();
 	if (blst_lock==0){
 		ret=E_OUT_OF_MEM;
 		goto error;
 	}
 	if (lock_init(blst_lock)==0){
 		lock_dealloc(blst_lock);
 		blst_lock=0;
 		ret=-1;
 		goto error;
 	}
 	blst_timer_h=timer_alloc();
 	if (blst_timer_h==0){
 		ret=E_OUT_OF_MEM;
 		goto error;
 	}
 	/* fix options */
 	blst_max_mem<<=10; /* in Kb */ /* TODO: test with 0 */
 	if (blst_timer_interval){
 		timer_init(blst_timer_h, blst_timer, 0 ,0); /* slow timer */
 		if (timer_add(blst_timer_h, S_TO_TICKS(blst_timer_interval))<0){
 			LOG(L_CRIT, "BUG: init_dst_blacklist: failed to add the timer\n");
 			timer_free(blst_timer_h);
 			blst_timer_h=0;
 			goto error;
 		}
 	}
 	return 0;
 error:
 	destroy_dst_blacklist();
 	return ret;
 }
 
 
 /* must be called with the lock held
  * struct dst_blst_entry** head, struct dst_blst_entry* e */
 #define dst_blacklist_lst_add(head, e)\
 do{ \
 	(e)->next=*(head); \
 	*(head)=(e); \
 }while(0)
 
 
 
 /* must be called with the lock held
  * returns a pointer to the blacklist entry if found, 0 otherwise
  * it also deletes expired elements (expire<=now) as it searches
  * proto==PROTO_NONE = wildcard */
 inline static struct dst_blst_entry* _dst_blacklist_lst_find(
 												struct dst_blst_entry** head,
 												struct ip_addr* ip,
 												unsigned char proto,
 												unsigned short port,
 												ticks_t now)
 {
 	struct dst_blst_entry** crt;
 	struct dst_blst_entry** tmp;
 	struct dst_blst_entry* e;
 	unsigned char type;
 	
 	type=(ip->af==AF_INET6)*BLST_IS_IPV6;
 	for (crt=head, tmp=&(*head)->next; *crt; crt=tmp, tmp=&(*crt)->next){
 		e=*crt;
 		/* remove old expired entries */
 		if ((s_ticks_t)(now-(*crt)->expire)>=0){
 			*crt=(*crt)->next;
4be0dabc
 			tmp=crt;
dcb59e67
 			*blst_mem_used-=DST_BLST_ENTRY_SIZE(*e);
 			blst_destroy_entry(e);
 		}else if ((e->port==port) && ((e->flags & BLST_IS_IPV6)==type) &&
 				((e->proto==PROTO_NONE) || (proto==PROTO_NONE) ||
 					(e->proto==proto)) && 
 					(memcmp(ip->u.addr, e->ip, ip->len)==0)){
 			return e;
 		}
 	}
 	return 0;
 }
 
 
 
 /* frees all the expired entries until either there are no more of them
  *  or the total memory used is <= target (to free all of them use -1 for 
  *  targer)
  *  It must be called with LOCK_BLST held (or call dst_blacklist_clean_expired
  *   instead).
  *  params:   target  - free expired entries until no more then taget memory 
  *                      is used  (use 0 to free all of them)
  *            delta   - consider an entry expired if it expires after delta
  *                      ticks from now
  *            timeout - exit after timeout ticks
  *
  *  returns: number of deleted entries
  *  This function should be called periodically from a timer
  */
 inline static int _dst_blacklist_clean_expired_unsafe(unsigned int target,
 												ticks_t delta,
 												ticks_t timeout)
 {
f30f9b7e
 	static unsigned int start=0;
 	unsigned int h;
dcb59e67
 	struct dst_blst_entry** crt;
 	struct dst_blst_entry** tmp;
 	struct dst_blst_entry* e;
 	ticks_t start_time;
 	ticks_t now;
 	int no=0;
 	
 	now=start_time=get_ticks_raw();
 	for(h=start; h!=(start+DST_BLST_HASH_SIZE); h++){
 		for (crt=&dst_blst_hash[h%DST_BLST_HASH_SIZE], tmp=&(*crt)->next;
 				*crt; crt=tmp, tmp=&(*crt)->next){
 			e=*crt;
 			if ((s_ticks_t)(now+delta-(*crt)->expire)>=0){
 				*crt=(*crt)->next;
4be0dabc
 				tmp=crt;
dcb59e67
 				*blst_mem_used-=DST_BLST_ENTRY_SIZE(*e);
 				blst_destroy_entry(e);
 				no++;
 				if (*blst_mem_used<=target)
 					goto skip;
 			}
 		}
 		/* check for timeout only "between" hash cells */
 		now=get_ticks_raw();
 		if ((now-start_time)>=timeout){
 			DBG("_dst_blacklist_clean_expired_unsafe: timeout: %d > %d\n",
 					TICKS_TO_MS(now-start_time), TICKS_TO_MS(timeout));
 			goto skip;
 		}
 	}
 skip:
 	start=h; /* next time we start where we left */
 	return no;
 }
 
 
 
 /* frees all the expired entries until either there are no more of them
  *  or the total memory used is <= target (to free all of them use -1 for 
  *  targer)
  *  params:   target  - free expired entries until no more then taget memory 
  *                      is used  (use 0 to free all of them)
  *            delta   - consider an entry expired if it expires after delta
  *                      ticks from now
  *            timeout - exit after timeout ticks
  *
  *  returns: number of deleted entries
  *  This function should be called periodically from a timer
  */
 inline static int dst_blacklist_clean_expired(unsigned int target,
 												ticks_t delta,
 												ticks_t timeout)
 {
 	int no;
 	
 	LOCK_BLST();
 		no=_dst_blacklist_clean_expired_unsafe(target, delta, timeout);
 	UNLOCK_BLST();
 	if (no){
 		DBG("dst_blacklist_clean_expired, %d entries removed\n", no);
 	}
 	return no;
 }
 
 
 
 /* timer */
 static ticks_t blst_timer(ticks_t ticks, struct timer_ln* tl, void* data)
 {
 	dst_blacklist_clean_expired(0, 0, 2); /*spend max. 2 ticks*/
 	return (ticks_t)(-1);
 }
 
 
 
 /* adds a proto ip:port combination to the blacklist
  * returns 0 on success, -1 on error (blacklist full -- would use more then
  *  blst:_max_mem, or out of shm. mem.)
  */
 inline static int dst_blacklist_add_ip(unsigned char err_flags, 
 									unsigned char proto,
 									struct ip_addr* ip, unsigned short port)
 {
 	int size;
 	struct dst_blst_entry* e;
 	unsigned short hash;
 	ticks_t now;
 	int ret;
 	
 	ret=0;
 	if (ip->af==AF_INET){
 		err_flags&=~BLST_IS_IPV6; /* make sure the ipv6 flag is reset */
 		size=sizeof(struct dst_blst_entry);
 	}else{
 		err_flags|=BLST_IS_IPV6;
 		size=sizeof(struct dst_blst_entry)+12 /* ipv6 addr - 4 */;
 	}
 	now=get_ticks_raw();
 	hash=dst_blst_hash_no(proto, ip, port);
 	/* check if the entry already exists */
 	LOCK_BLST();
 		e=_dst_blacklist_lst_find(&dst_blst_hash[hash], ip, proto, port, now);
 		if (e){
 			e->flags|=err_flags;
 			e->expire=now+S_TO_TICKS(blst_timeout); /* update the timeout */
 		}else{
 			if ((*blst_mem_used+size)>=blst_max_mem){
 				/* first try to free some memory  (~ 12%), but don't
 				 * spend more then 250 ms*/
 				_dst_blacklist_clean_expired_unsafe(*blst_mem_used/16*14, 0, 
 															MS_TO_TICKS(250));
 				if (*blst_mem_used+size>=blst_max_mem){
 					ret=-1;
 					goto error;
 				}
 			}
 			e=shm_malloc(size);
 			if (e==0){
 				ret=E_OUT_OF_MEM;
 				goto error;
 			}
 			*blst_mem_used+=size;
 			e->flags=err_flags;
 			e->proto=proto;
 			e->port=port;
 			memcpy(e->ip, ip->u.addr, ip->len);
 			e->expire=now+S_TO_TICKS(blst_timeout); /* update the timeout */
 			e->next=0;
 			dst_blacklist_lst_add(&dst_blst_hash[hash], e);
 		}
 error:
 	UNLOCK_BLST();
 	return ret;
 }
 
 
 
 /* if no blacklisted returns 0, else returns the blacklist flags */
 inline static int dst_is_blacklisted_ip(unsigned char proto,
 										struct ip_addr* ip,
 										unsigned short port)
 {
 	struct dst_blst_entry* e;
 	unsigned short hash;
 	ticks_t now;
 	int ret=0;
 	
 	ret=0;
 	now=get_ticks_raw();
 	hash=dst_blst_hash_no(proto, ip, port);
 	LOCK_BLST();
 		e=_dst_blacklist_lst_find(&dst_blst_hash[hash], ip, proto, port, now);
 		if (e){
 			ret=e->flags;
 		}
 	UNLOCK_BLST();
 	return ret;
 }
 
 
 
 int dst_blacklist_add(unsigned char err_flags,  struct dest_info* si)
 {
 	struct ip_addr ip;
 	su2ip_addr(&ip, &si->to);
 	return dst_blacklist_add_ip(err_flags, si->proto, &ip,
 								su_getport(&si->to));
 }
 
 
 
 int dst_is_blacklisted(struct dest_info* si)
 {
 	struct ip_addr ip;
 	su2ip_addr(&ip, &si->to);
 	return dst_is_blacklisted_ip(si->proto, &ip, su_getport(&si->to));
 }
 
 
 
 /* rpc functions */
 void dst_blst_mem_info(rpc_t* rpc, void* ctx)
 {
82e7e3ce
 	if (!use_dst_blacklist){
 		rpc->fault(ctx, 500, "dst blacklist support disabled");
 		return;
 	}
dcb59e67
 	rpc->add(ctx, "dd",  *blst_mem_used, blst_max_mem);
 }
 
 
 
 static char* get_proto_name(unsigned char proto)
 {
 	switch(proto){
 		case PROTO_NONE:
 			return "*";
 		case PROTO_UDP:
 			return "udp";
 		case PROTO_TCP:
 			return "tcp";
 		case PROTO_TLS:
 			return "tls";
 		default:
 			return "unknown";
 	}
 }
 
 
 
 /* only for debugging, it helds the lock too long for "production" use */
 void dst_blst_debug(rpc_t* rpc, void* ctx)
 {
 	int h;
 	struct dst_blst_entry* e;
 	ticks_t now;
 	struct ip_addr ip;
 	
82e7e3ce
 	if (!use_dst_blacklist){
 		rpc->fault(ctx, 500, "dst blacklist support disabled");
 		return;
 	}
dcb59e67
 	now=get_ticks_raw();
 	LOCK_BLST();
 		for(h=0; h<DST_BLST_HASH_SIZE; h++){
 			for(e=dst_blst_hash[h]; e; e=e->next){
 				dst_blst_entry2ip(&ip, e);
 				rpc->add(ctx, "ssddd", get_proto_name(e->proto), 
 										ip_addr2a(&ip), e->port, 
 										(s_ticks_t)(now-e->expire)<=0?
 										TICKS_TO_S(e->expire-now):
 										-TICKS_TO_S(now-e->expire) ,
 										e->flags);
 			}
 		}
 	UNLOCK_BLST();
 }
 
 #endif /* USE_DST_BLACKLIST */