changeset 717:571b3abaa5df

Support for Diameter Redirects through rt_redirect.fdx extension (EXPERIMENTAL)
author Sebastien Decugis <sdecugis@nict.go.jp>
date Fri, 18 Feb 2011 14:50:37 +0900
parents 4784f2cf325f
children 2d5e76dd1117
files contrib/debian/changelog extensions/CMakeLists.txt extensions/app_radgw/rgw_clients.c extensions/app_radgw/rgwx_sip.c extensions/rt_redirect/CMakeLists.txt extensions/rt_redirect/redir_entries.c extensions/rt_redirect/redir_expiry.c extensions/rt_redirect/redir_fwd.c extensions/rt_redirect/redir_out.c extensions/rt_redirect/rt_redir.c extensions/rt_redirect/rt_redir.h include/freeDiameter/libfdcore.h include/freeDiameter/libfdproto.h libfdcore/routing_dispatch.c libfdproto/ostr.c libfdproto/rt_data.c tests/testostr.c
diffstat 17 files changed, 1546 insertions(+), 22 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/debian/changelog	Thu Feb 17 16:37:15 2011 +0900
+++ b/contrib/debian/changelog	Fri Feb 18 14:50:37 2011 +0900
@@ -19,8 +19,9 @@
   * Removed fd_cpu_flush_cache(), replaced by more robust alternatives.
   * Improved peer state machine algorithm to counter SCTP multistream race 
     condition.
+  * New extension rt_redirect.fdx that handles the Diameter Redirect errors.
 
- -- Sebastien Decugis <sdecugis@nict.go.jp>  Mon, 07 Feb 2011 17:24:20 +0900
+ -- Sebastien Decugis <sdecugis@nict.go.jp>  Fri, 18 Feb 2011 14:47:24 +0900
 
 freediameter (1.0.4) UNRELEASED; urgency=low
 
--- a/extensions/CMakeLists.txt	Thu Feb 17 16:37:15 2011 +0900
+++ b/extensions/CMakeLists.txt	Fri Feb 18 14:50:37 2011 +0900
@@ -61,8 +61,9 @@
 ####
 # Routing extensions
 
-FD_EXTENSION_SUBDIR(rt_default "Configurable routing rules for freeDiameter" 		     ON)
-FD_EXTENSION_SUBDIR(rt_ereg    "Configurable routing based on regexp matching of AVP values" OFF)
+FD_EXTENSION_SUBDIR(rt_default   "Configurable routing rules for freeDiameter" 		     	ON)
+FD_EXTENSION_SUBDIR(rt_redirect  "Handling of Diameter Redirect messages" 			ON)
+FD_EXTENSION_SUBDIR(rt_ereg      "Configurable routing based on regexp matching of AVP values" OFF)
 
 
 ####
--- a/extensions/app_radgw/rgw_clients.c	Thu Feb 17 16:37:15 2011 +0900
+++ b/extensions/app_radgw/rgw_clients.c	Fri Feb 18 14:50:37 2011 +0900
@@ -214,7 +214,7 @@
 {
 	struct rgw_client *tmp = NULL;
 	DiamId_t fqdn;
-	size_t fqdn_len;
+	size_t fqdn_len = 0;
 	int ret, i;
 	int loc = 0;
 	
@@ -549,9 +549,9 @@
 	int valid_nas_info = 0;
 	struct radius_attr_hdr *nas_ip = NULL, *nas_ip6 = NULL, *nas_id = NULL;
 	size_t nas_id_len;
-	char * oh_str = NULL; size_t oh_strlen; int oh_free = 0;
-	char * or_str = NULL; size_t or_strlen;
-	char * rr_str = NULL; size_t rr_strlen;
+	char * oh_str = NULL; size_t oh_strlen = 0; int oh_free = 0;
+	char * or_str = NULL; size_t or_strlen = 0;
+	char * rr_str = NULL; size_t rr_strlen = 0;
 	char buf[REVERSE_DNS_SIZE_MAX]; /* to store DNS lookups results */
 	
 	struct avp *avp = NULL;
@@ -788,6 +788,7 @@
 						rr_strlen = cli->fqdn_len;
 					}
 					oh_str = &buf[0]; /* The canonname resolved */
+					oh_strlen = 0;
 					CHECK_FCT_DO( ret = fd_os_validate_DiameterIdentity(&oh_str, &oh_strlen, 1),
 						{
 							TRACE_DEBUG(INFO, "Unable to use resolved client name '%s' as DiameterIdentity: %s", buf, strerror(ret));
--- a/extensions/app_radgw/rgwx_sip.c	Thu Feb 17 16:37:15 2011 +0900
+++ b/extensions/app_radgw/rgwx_sip.c	Fri Feb 18 14:50:37 2011 +0900
@@ -594,7 +594,7 @@
 			{
 				//We extract Realm from Digest_URI
 				DiamId_t realm=NULL;
-				size_t realm_len;
+				size_t realm_len = 0;
 				os0_t temp;
 				
 				temp = (os0_t)(attr + 1);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extensions/rt_redirect/CMakeLists.txt	Fri Feb 18 14:50:37 2011 +0900
@@ -0,0 +1,22 @@
+# The rt_redirect extension
+PROJECT("Diameter Redirect messages support" C)
+
+SET(RT_REDIR_SRC
+	rt_redir.h
+	rt_redir.c
+	redir_entries.c
+	redir_expiry.c
+	redir_fwd.c
+	redir_out.c
+	)
+
+# Compile as a module
+FD_ADD_EXTENSION(rt_redirect ${RT_REDIR_SRC})
+
+
+####
+## INSTALL section ##
+
+INSTALL(TARGETS rt_redirect
+	LIBRARY DESTINATION ${INSTALL_EXTENSIONS_SUFFIX}
+	COMPONENT freeDiameter-daemon)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extensions/rt_redirect/redir_entries.c	Fri Feb 18 14:50:37 2011 +0900
@@ -0,0 +1,319 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License)                                                               *
+* Author: Sebastien Decugis <sdecugis@nict.go.jp>							 *
+*													 *
+* Copyright (c) 2011, WIDE Project and NICT								 *
+* All rights reserved.											 *
+* 													 *
+* Redistribution and use of this software in source and binary forms, with or without modification, are  *
+* permitted provided that the following conditions are met:						 *
+* 													 *
+* * Redistributions of source code must retain the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer.										 *
+*    													 *
+* * Redistributions in binary form must reproduce the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer in the documentation and/or other						 *
+*   materials provided with the distribution.								 *
+* 													 *
+* * Neither the name of the WIDE Project or NICT nor the 						 *
+*   names of its contributors may be used to endorse or 						 *
+*   promote products derived from this software without 						 *
+*   specific prior written permission of WIDE Project and 						 *
+*   NICT.												 *
+* 													 *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 	 *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 	 *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.								 *
+*********************************************************************************************************/
+
+#include "rt_redir.h"
+
+/* The array with all entries ordered by their data */
+struct redir_line redirects_usages[H_U_MAX + 1];
+
+/* Initialize the array */
+int redir_entry_init()
+{
+	int i;
+	
+	TRACE_ENTRY("");
+	
+	/* redirects_usages */
+	memset(&redirects_usages, 0, sizeof(redirects_usages));
+	
+	for (i = 0; i <= H_U_MAX; i++) {
+		CHECK_POSIX( pthread_rwlock_init( &redirects_usages[i].lock, NULL) );
+		fd_list_init( &redirects_usages[i].sentinel, &redirects_usages[i] );
+	}
+	
+	/* initialize the scores */
+	redirects_usages[ DONT_CACHE		].score = FD_SCORE_REDIR_ONCE;
+	redirects_usages[ ALL_SESSION		].score = FD_SCORE_REDIR_SESSION;
+	redirects_usages[ ALL_REALM		].score = FD_SCORE_REDIR_REALM;
+	redirects_usages[ REALM_AND_APPLICATION	].score = FD_SCORE_REDIR_REALM_APP;
+	redirects_usages[ ALL_APPLICATION	].score = FD_SCORE_REDIR_APP;
+	redirects_usages[ ALL_HOST		].score = FD_SCORE_REDIR_HOST;
+	redirects_usages[ ALL_USER		].score = FD_SCORE_REDIR_USER;
+
+	return 0;
+}
+
+
+/* Create a new redir_entry and add the correct data */
+int redir_entry_new(struct redir_entry ** e, struct fd_list * targets, uint32_t rhu, struct msg * qry, DiamId_t nh, size_t nhlen, os0_t oh, size_t ohlen)
+{
+	struct redir_entry * entry = NULL;
+	os0_t s;
+	size_t l;
+	
+	TRACE_ENTRY("%p %p %d %p %p %zd %p %zd", e, targets, rhu, qry, nh, nhlen, oh, ohlen)
+	ASSERT(e && targets && (rhu <= H_U_MAX) && qry && nh && nhlen && oh && ohlen);
+	
+	CHECK_MALLOC( entry = malloc(sizeof(struct redir_entry)) );
+	memset(entry, 0, sizeof(struct redir_entry));
+	
+	entry->eyec = REDIR_ENTRY_EYEC;
+	
+	CHECK_MALLOC( entry->from.s = os0dup(nh, nhlen) );
+	entry->from.l = nhlen;
+	
+	fd_list_init(&entry->target_peers_list, entry);
+	fd_list_move_end(&entry->target_peers_list, targets);
+	
+	fd_list_init(&entry->exp_list, entry);
+	
+	entry->type = rhu;
+	fd_list_init(&entry->redir_list, entry);
+	/* finally initialize the data */
+	switch (rhu) {
+		case DONT_CACHE:
+			entry->data.message.msg = qry;
+			break;
+			
+		case ALL_SESSION:
+			{
+				/* There is a good chance that the session is already cached in the message, so retrieve it */
+				struct session * sess;
+				CHECK_FCT( fd_msg_sess_get(fd_g_config->cnf_dict, qry, &sess, NULL) );
+				if (!sess) {
+					TRACE_DEBUG(INFO, "Received a Redirect indication with usage ALL_SESSION but no Session-Id AVP in the message, defaulting to DONT_CACHE");
+					entry->type = DONT_CACHE;
+					entry->data.message.msg = qry;
+					break;
+				}
+				CHECK_FCT( fd_sess_getsid(sess, &s, &l) );
+				CHECK_MALLOC( entry->data.session.s = os0dup(s, l) );
+				entry->data.session.l = l;
+			}
+			break;
+		
+		case ALL_REALM:
+			{
+				/* Search the Destination-Realm of the message */
+				struct avp * dr;
+				struct avp_hdr * ahdr;
+				CHECK_FCT( fd_msg_search_avp(qry, redir_dict_dr, &dr) );
+				if (!dr) {
+					TRACE_DEBUG(INFO, "Received a Redirect indication with usage ALL_REALM but no Destination-Realm AVP in the message, defaulting to DONT_CACHE");
+					entry->type = DONT_CACHE;
+					entry->data.message.msg = qry;
+					break;
+				}
+				CHECK_FCT(  fd_msg_avp_hdr( dr, &ahdr )  );
+				CHECK_MALLOC( entry->data.realm.s = os0dup(ahdr->avp_value->os.data, ahdr->avp_value->os.len) );
+				entry->data.realm.l = ahdr->avp_value->os.len;
+			}
+			break;
+				
+		case REALM_AND_APPLICATION:
+			{
+				/* Search the Destination-Realm of the message */
+				struct avp * dr;
+				struct avp_hdr * ahdr;
+				CHECK_FCT( fd_msg_search_avp(qry, redir_dict_dr, &dr) );
+				if (!dr) {
+					TRACE_DEBUG(INFO, "Received a Redirect indication with usage REALM_AND_APPLICATION but no Destination-Realm AVP in the message, defaulting to DONT_CACHE");
+					entry->type = DONT_CACHE;
+					entry->data.message.msg = qry;
+					break;
+				}
+				CHECK_FCT(  fd_msg_avp_hdr( dr, &ahdr )  );
+				CHECK_MALLOC( entry->data.realm_app.s = os0dup(ahdr->avp_value->os.data, ahdr->avp_value->os.len) );
+				entry->data.realm_app.l = ahdr->avp_value->os.len;
+			}
+			/* and then the application */
+			{
+				struct msg_hdr * hdr;
+				CHECK_FCT( fd_msg_hdr(qry, &hdr) );
+				entry->data.realm_app.a = hdr->msg_appl;
+			}
+			break;
+				
+		case ALL_APPLICATION:
+			{
+				struct msg_hdr * hdr;
+				CHECK_FCT( fd_msg_hdr(qry, &hdr) );
+				entry->data.app.a = hdr->msg_appl;
+			}
+			break;
+			
+		case ALL_HOST:
+			CHECK_MALLOC( entry->data.host.s = os0dup(oh, ohlen) );
+			entry->data.host.l = ohlen;
+			break;
+			
+		case ALL_USER:
+			{
+				/* Search the User-Name of the message */
+				struct avp * un;
+				struct avp_hdr * ahdr;
+				CHECK_FCT( fd_msg_search_avp(qry, redir_dict_un, &un) );
+				if (!un) {
+					TRACE_DEBUG(INFO, "Received a Redirect indication with usage ALL_USER but no User-Name AVP in the message, defaulting to DONT_CACHE");
+					entry->type = DONT_CACHE;
+					entry->data.message.msg = qry;
+					break;
+				}
+				CHECK_FCT(  fd_msg_avp_hdr( un, &ahdr )  );
+				CHECK_MALLOC( entry->data.user.s = os0dup(ahdr->avp_value->os.data, ahdr->avp_value->os.len) );
+				entry->data.user.l = ahdr->avp_value->os.len;
+			}
+			break;
+		
+		default:
+			ASSERT(0);
+			return EINVAL;
+	}
+	
+	/* We're done */
+	*e = entry;
+	return 0;
+}
+
+
+/* Compares two pointers (DONT_CACHE) */
+static int compare_entries_ptr(union matchdata * d1, union matchdata * d2) {
+	unsigned long v1 = (unsigned long) d1->message.msg;
+	unsigned long v2 = (unsigned long) d2->message.msg;
+	if (v1 > v2) 
+		return 1;
+	if (v1 < v2)
+		return -1;
+	return 0;
+}
+/* Compare two applications (REALM_AND_APPLICATION and ALL_APPLICATION) */
+static int compare_entries_appl(union matchdata * d1, union matchdata * d2) {
+	if (d1->app.a > d2->app.a) 
+		return 1;
+	if (d1->app.a < d2->app.a) 
+		return -1;
+	return 0;
+}
+
+/* Compare two strings (ALL_SESSION, ALL_REALM, ALL_HOST, ALL_USER) */
+static int compare_entries_ostr(union matchdata * d1, union matchdata * d2) {
+	return fd_os_cmp(d1->session.s, d1->session.l, d2->session.s, d2->session.l);
+}
+
+/* The array of callbacks */
+int (*redir_entry_cmp_key[H_U_MAX +1])(union matchdata * , union matchdata * ) = {
+	compare_entries_ptr,  /* DONT_CACHE */
+	compare_entries_ostr, /* ALL_SESSION */
+	compare_entries_ostr, /* ALL_REALM */
+	compare_entries_appl, /* REALM_AND_APPLICATION */
+	compare_entries_appl, /* ALL_APPLICATION */
+	compare_entries_ostr, /* ALL_HOST */
+	compare_entries_ostr  /* ALL_USER */
+};
+
+/* Link the newly created entry into the correct redirects_usages list. The mutex must be held */
+int redir_entry_insert(struct redir_entry * e)
+{
+	struct fd_list * li;
+	
+	TRACE_ENTRY("%p", e);
+	CHECK_PARAMS(e && (e->eyec == REDIR_ENTRY_EYEC));
+	
+	/* Write-Lock the line */
+	CHECK_POSIX( pthread_rwlock_wrlock( RWLOCK_REDIR(e) ) );
+	
+	for (li = redirects_usages[e->type].sentinel.next; li != &redirects_usages[e->type].sentinel; li = li->next) {
+		struct redir_entry * n = li->o;
+		int cmp = redir_entry_cmp_key[e->type](&e->data, &n->data);
+		if (cmp <= 0)
+			break;
+	}
+	
+	fd_list_insert_before(li, &e->redir_list);
+	
+	/* unLock the line */
+	CHECK_POSIX( pthread_rwlock_unlock( RWLOCK_REDIR(e) ) );
+
+	return 0;
+}
+
+/* Destroy -- the exp_peer_lock must be held when this function is called */
+int redir_entry_destroy(struct redir_entry * e)
+{
+	TRACE_ENTRY("%p", e);
+	CHECK_PARAMS(e && (e->eyec == REDIR_ENTRY_EYEC));
+	
+	/* If the entry is linked, lock the rwlock also */
+	if (!FD_IS_LIST_EMPTY(&e->redir_list)) {
+		CHECK_POSIX( pthread_rwlock_wrlock( RWLOCK_REDIR(e) ) );
+		fd_list_unlink(&e->redir_list);
+		CHECK_POSIX( pthread_rwlock_unlock( RWLOCK_REDIR(e) ) );
+	}
+	
+	/* Now unlink from other list */
+	fd_list_unlink(&e->exp_list);
+	
+	/* Empty the targets list */
+	while (!FD_IS_LIST_EMPTY(&e->target_peers_list)) {
+		struct redir_host * h = (struct redir_host *)e->target_peers_list.next->o;
+		
+		fd_list_unlink(&h->chain);
+		free(h->id);
+		free(h);
+	}
+	
+	/* Now we can destroy the data safely */
+	switch (e->type) {
+		case DONT_CACHE:
+			/* nothing special */
+			break;
+		case ALL_SESSION:
+			free(e->data.session.s);
+			break;
+		case ALL_REALM:
+			free(e->data.realm.s);
+			break;
+		case REALM_AND_APPLICATION:
+			free(e->data.realm_app.s);
+			break;
+		case ALL_APPLICATION:
+			break;
+		case ALL_HOST:
+			free(e->data.host.s);
+			break;
+		case ALL_USER:
+			free(e->data.user.s);
+			break;
+		default:
+			TRACE_DEBUG(INFO, "Invalid redirect type was saved");
+			ASSERT(0);
+			return EINVAL;
+	}
+	
+	free(e->from.s);
+	
+	free(e);
+	return 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extensions/rt_redirect/redir_expiry.c	Fri Feb 18 14:50:37 2011 +0900
@@ -0,0 +1,136 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License)                                                               *
+* Author: Sebastien Decugis <sdecugis@nict.go.jp>							 *
+*													 *
+* Copyright (c) 2011, WIDE Project and NICT								 *
+* All rights reserved.											 *
+* 													 *
+* Redistribution and use of this software in source and binary forms, with or without modification, are  *
+* permitted provided that the following conditions are met:						 *
+* 													 *
+* * Redistributions of source code must retain the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer.										 *
+*    													 *
+* * Redistributions in binary form must reproduce the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer in the documentation and/or other						 *
+*   materials provided with the distribution.								 *
+* 													 *
+* * Neither the name of the WIDE Project or NICT nor the 						 *
+*   names of its contributors may be used to endorse or 						 *
+*   promote products derived from this software without 						 *
+*   specific prior written permission of WIDE Project and 						 *
+*   NICT.												 *
+* 													 *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 	 *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 	 *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.								 *
+*********************************************************************************************************/
+
+#include "rt_redir.h"
+
+/* Expiration management */
+
+
+/* Entries by their ascending expiration date, to accelerate the work of the expire thread */
+static struct fd_list  expire_list = FD_LIST_INITIALIZER(expire_list);
+static pthread_cond_t  exp_cnd  = PTHREAD_COND_INITIALIZER; 
+
+pthread_mutex_t redir_exp_peer_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* The thread that handles expired entries cleanup. */
+void * redir_exp_thr_fct(void * arg) 
+{
+	fd_log_threadname ( "Redirects/expire" );
+	TRACE_ENTRY( "" );
+
+	CHECK_POSIX_DO( pthread_mutex_lock(&redir_exp_peer_lock),  goto fatal_error );
+	pthread_cleanup_push( fd_cleanup_mutex, &redir_exp_peer_lock );
+	
+	do {
+		struct timespec	now;
+		struct redir_entry * first;
+again:		
+		/* Check if there are expiring entries available */
+		if (FD_IS_LIST_EMPTY(&expire_list)) {
+			/* Just wait for a change or cancelation */
+			CHECK_POSIX_DO( pthread_cond_wait( &exp_cnd, &redir_exp_peer_lock ), break /* this might not pop the cleanup handler, but since we ASSERT(0), it is not the big issue... */ );
+			/* Restart the loop on wakeup */
+			goto again;
+		}
+		
+		/* Get the pointer to the entry that expires first */
+		first = (struct redir_entry *)(expire_list.next->o);
+		
+		/* Get the current time */
+		CHECK_SYS_DO(  clock_gettime(CLOCK_REALTIME, &now),  break  );
+
+		/* If first session is not expired, we just wait until it happens */
+		if ( TS_IS_INFERIOR( &now, &first->timeout ) ) {
+			
+			CHECK_POSIX_DO2(  pthread_cond_timedwait( &exp_cnd, &redir_exp_peer_lock, &first->timeout ),  
+					ETIMEDOUT, /* ETIMEDOUT is a normal error, continue */,
+					/* on other error, */ break );
+	
+			/* on wakeup, loop */
+			goto again;
+		}
+		
+		/* Now, the first entry in the list is expired; destroy it */
+		
+		CHECK_FCT_DO( redir_entry_destroy( first ), break );
+		
+	} while (1);
+	
+	pthread_cleanup_pop( 0 );
+	CHECK_POSIX_DO( pthread_mutex_unlock(&redir_exp_peer_lock),  );
+	
+fatal_error:
+	TRACE_DEBUG(INFO, "A system error occurred in redirect module! Expiry thread is terminating...");
+	ASSERT(0);
+	return NULL;
+}
+
+/* Sets the timeout value & link in expiry list. The mutex must be held on calling */
+int redir_exp_set(struct redir_entry * e, uint32_t duration)
+{
+	struct fd_list * li;
+	TRACE_ENTRY("%p %d", e, duration);
+	CHECK_PARAMS(e && (e->eyec == REDIR_ENTRY_EYEC) && duration );
+	
+	/* Unlink in case it was already set before */
+	fd_list_unlink(&e->exp_list);
+	
+	/* Get current time */
+	CHECK_SYS(  clock_gettime(CLOCK_REALTIME, &e->timeout)  );
+	
+	/* Add the duration */
+	e->timeout.tv_sec += duration;
+	
+	/* now search the next element in the list */
+	for (li = expire_list.next; li != &expire_list; li = li->next) {
+		struct redir_entry * n = li->o;
+		
+		if ( TS_IS_INFERIOR( &e->timeout, &n->timeout ) )
+			break;
+	
+	}
+	
+	/* Insert before this element */
+	fd_list_insert_before(li, &e->exp_list);
+	
+	/* Signal the expiry thread if needed */
+	if (e->exp_list.prev == &expire_list) { /* it is the first element */
+		CHECK_POSIX( pthread_cond_signal(&exp_cnd) );
+	}
+	
+	/* Done */
+	return 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extensions/rt_redirect/redir_fwd.c	Fri Feb 18 14:50:37 2011 +0900
@@ -0,0 +1,272 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License)                                                               *
+* Author: Sebastien Decugis <sdecugis@nict.go.jp>							 *
+*													 *
+* Copyright (c) 2011, WIDE Project and NICT								 *
+* All rights reserved.											 *
+* 													 *
+* Redistribution and use of this software in source and binary forms, with or without modification, are  *
+* permitted provided that the following conditions are met:						 *
+* 													 *
+* * Redistributions of source code must retain the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer.										 *
+*    													 *
+* * Redistributions in binary form must reproduce the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer in the documentation and/or other						 *
+*   materials provided with the distribution.								 *
+* 													 *
+* * Neither the name of the WIDE Project or NICT nor the 						 *
+*   names of its contributors may be used to endorse or 						 *
+*   promote products derived from this software without 						 *
+*   specific prior written permission of WIDE Project and 						 *
+*   NICT.												 *
+* 													 *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 	 *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 	 *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.								 *
+*********************************************************************************************************/
+
+#include "rt_redir.h"
+
+/* This structure contains the data to keep when a new peer's connection is attempted (for later) */
+struct redir_task {
+	struct msg * answer; /* the message that was being processed */
+	uint32_t     rhu;    /* Redirect-Host-Usage value */
+	uint32_t     rmct;   /* Redirect-Max-Cache-Time value */
+	struct fd_list rh;   /* the list of Redirect-Hosts */
+};
+
+/* Received answers FWD callback */
+int redir_fwd_cb(void * cbdata, struct msg ** msg)
+{
+	struct msg * m, * q;
+	struct rt_data *rtd;
+	struct msg_hdr * hdr;
+	union avp_value *a_rc = NULL, *a_rhu = NULL, *a_rmct = NULL, *a_oh = NULL;
+	int known = 0, actives = 0;
+	struct fd_list * li;
+	struct avp * avp;
+	struct redir_task task = { .answer = NULL, .rhu = 0, .rmct = 0, .rh = FD_LIST_INITIALIZER(task.rh) };
+	DiamId_t nh;
+	size_t   nhlen;
+	int nbrh = 0;
+	struct redir_entry * entry;
+	
+	TRACE_ENTRY("%p %p", cbdata, msg);
+	
+	CHECK_PARAMS(msg && *msg);
+	
+	m = *msg;
+	
+	/* First get the header */
+	CHECK_FCT( fd_msg_hdr(m, &hdr) );
+	
+	/* Check if we have an error */
+	ASSERT(!(hdr->msg_flags & CMD_FLAG_REQUEST));
+	if (!(hdr->msg_flags & CMD_FLAG_ERROR)) {
+		/* This answer does not have the E flag, no need to process further */
+		return 0;
+	}
+	
+	/* Now get the AVPs we are interested in */
+	CHECK_FCT(  fd_msg_browse(m, MSG_BRW_FIRST_CHILD, &avp, NULL)  );
+	while (avp) {
+		struct avp_hdr * ahdr;
+			
+		CHECK_FCT(  fd_msg_avp_hdr( avp, &ahdr )  );
+		if (! (ahdr->avp_flags & AVP_FLAG_VENDOR)) {
+			switch (ahdr->avp_code) {
+				case AC_ORIGIN_HOST:
+					/* Parse this AVP */
+					CHECK_FCT( fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, NULL ) );
+					ASSERT( ahdr->avp_value );
+					a_oh = ahdr->avp_value;
+					break;
+					
+				case AC_RESULT_CODE:
+					/* Parse this AVP */
+					CHECK_FCT( fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, NULL ) );
+					ASSERT( ahdr->avp_value );
+					a_rc = ahdr->avp_value;
+					
+					if (a_rc->u32 != ER_DIAMETER_REDIRECT_INDICATION) {
+						/* It is not a REDIRECT error, we don't do anything */
+						goto out;
+					}
+					break;
+					
+				case AC_REDIRECT_HOST:
+					{
+						struct redir_host * h = NULL;
+						DiamId_t id = NULL;
+						size_t	 len = 0;
+						int 	 secure = 0;
+						uint16_t port = 0;
+						int	 l4 = 0;
+						char	 proto = 0;
+						
+						/* Parse this AVP */
+						CHECK_FCT( fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, NULL ) );
+						ASSERT( ahdr->avp_value );
+						
+						nbrh++;
+						
+						CHECK_FCT_DO( fd_os_parse_DiameterURI(ahdr->avp_value->os.data, ahdr->avp_value->os.len, 
+									&id, &len, &secure, &port, &l4, &proto),
+							{
+								TRACE_DEBUG(INFO, "Received an invalid Redirect-Host AVP value ('%.*s'), ignored", ahdr->avp_value->os.len, ahdr->avp_value->os.data);
+								break;
+							} );
+						
+						/* Now check if the transport & protocol are supported */
+						if (proto && (proto != 'd')) {
+							TRACE_DEBUG(FULL, "Ignored unsupported non-Diameter Redirect-Host AVP (%.*s)", ahdr->avp_value->os.len, ahdr->avp_value->os.data);
+							free(id);
+							break;
+						}
+						if (l4 && (l4 == IPPROTO_UDP)) {
+							TRACE_DEBUG(FULL, "Ignored unsupported UDP Redirect-Host AVP (%.*s)", ahdr->avp_value->os.len, ahdr->avp_value->os.data);
+							free(id);
+							break;
+						}
+						
+						/* It looks OK, save this entry. */
+						
+						CHECK_MALLOC( h = malloc(sizeof(struct redir_host)) );
+						memset(h, 0, sizeof(struct redir_host));
+						fd_list_init(&h->chain, h);
+						h->id = id;
+						h->len = len;
+						/* later: secure, port */
+						
+						/* The list is kept ordered by id so that it is faster to compare to candidates later */
+						for (li = task.rh.next; li != &task.rh; li = li->next) {
+							struct redir_host * nhost = li->o;
+							if ( fd_os_almostcasecmp(id, len, nhost->id, nhost->len) <= 0 )
+								break;
+						}
+						fd_list_insert_before(li, &h->chain);
+					}
+					break;
+
+				case AC_REDIRECT_HOST_USAGE:
+					/* Parse this AVP */
+					CHECK_FCT( fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, NULL ) );
+					ASSERT( ahdr->avp_value );
+					a_rhu = ahdr->avp_value;
+					if (a_rhu->u32 > H_U_MAX) {
+						TRACE_DEBUG(INFO, "Received unsupported Redirect-Host-Usage value (%d), defaulting to DONT_CACHE", a_rhu->u32);
+					} else {
+						task.rhu = a_rhu->u32;
+					}
+					break;
+
+				case AC_REDIRECT_MAX_CACHE_TIME:
+					/* Parse this AVP */
+					CHECK_FCT( fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, NULL ) );
+					ASSERT( ahdr->avp_value );
+					a_rmct = ahdr->avp_value;
+					task.rmct = a_rmct->u32;
+					break;
+
+			}
+		}
+
+		/* Go to next AVP */
+		CHECK_FCT(  fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL)  );
+	}
+	
+	/* Check we have received the necessary information */
+	if (!a_rc) {
+		TRACE_DEBUG(FULL, "Invalid Diameter answer without a Result-Code AVP, Redirect module gave up");
+		goto out;
+	}
+	
+	if (!a_oh) {
+		TRACE_DEBUG(FULL, "Invalid Diameter answer without an Origin-Host AVP, Redirect module gave up");
+		goto out;
+	}
+	
+	if (FD_IS_LIST_EMPTY(&task.rh)) {
+		TRACE_DEBUG(FULL, "Diameter answer with a DIAMETER_REDIRECT_INDICATION Result-Code AVP but no valid/supported Redirect-Host AVP, Redirect module gave up");
+		goto out;
+	}
+
+	if (a_rhu && (task.rhu != DONT_CACHE) && !a_rmct) {
+		TRACE_DEBUG(FULL, "Invalid Diameter Redirect answer with a Redirect-Host-Usage AVP but no Redirect-Max-Cache-Time, Redirect module gave up");
+		goto out;
+	}
+	
+	/* It looks like we can process the Redirect indication */
+	
+	/* Search for the peers we already know */
+	for (li = task.rh.next; li != &task.rh; li = li->next) {
+		struct redir_host * h = li->o;
+		struct peer_hdr * peer;
+		
+		CHECK_FCT( fd_peer_getbyid( h->id, h->len, 1, &peer ) );
+		if (peer) {
+			known ++;
+			memcpy(h->id, peer->info.pi_diamid, h->len); /* Overwrite the case so we can search case-sensitive from here on */
+			if (fd_peer_get_state(peer) == STATE_OPEN) {
+				actives ++;
+			}
+		}
+	}
+	
+	TRACE_DEBUG(FULL, "Redirect module: received %d Redirect-Hosts, %d are known peers, %d have an OPEN connection", nbrh, known, actives);
+	
+	/* in this version, we only redirect when there are known active peers. TODO: add new peers via fd_peer_add when no active peer is available */
+	
+	if (!actives) {
+		TRACE_DEBUG(INFO, "Unable to comply to Redirect indication: none of the peers included is in OPEN state");
+		goto out;
+	}
+	
+	/* From this point, we will re-send the query to a different peer, so stop forwarding the answer here */
+	*msg = NULL;
+	
+	/* Get the query's routing data & add the new error */
+	CHECK_FCT( fd_msg_answ_getq(m, &q) );
+	CHECK_FCT( fd_msg_rt_get(q, &rtd) );
+	CHECK_FCT( fd_msg_source_get( m, &nh, &nhlen ) );
+	CHECK_FCT( fd_rtd_error_add(rtd, nh, nhlen, a_oh->os.data, a_oh->os.len, a_rc->u32) );
+	
+	/* Create a redir_rule  */
+	CHECK_FCT( redir_entry_new(&entry, &task.rh, task.rhu, q, nh, nhlen, a_oh->os.data, a_oh->os.len) );
+		
+	CHECK_POSIX(  pthread_mutex_lock(&redir_exp_peer_lock)  );
+	/* Insert in the split list */
+	CHECK_FCT( redir_entry_insert(entry) );
+	/* Set the expiry */
+	CHECK_FCT( redir_exp_set(entry, task.rmct ?: DEFAULT_EXPIRE_TIME) );
+	CHECK_POSIX(  pthread_mutex_unlock(&redir_exp_peer_lock)  );
+
+	/* Now we can get rid of the received answer and send again the query. */
+	CHECK_FCT( fd_msg_answ_detach(m) );
+	CHECK_FCT( fd_msg_free(m) );
+	
+	/* Send it */
+	CHECK_FCT( fd_msg_send(&q, NULL, NULL) );
+	
+	/* Done! */
+	
+out:
+	while (!FD_IS_LIST_EMPTY(&task.rh)) {
+		struct redir_host * h = task.rh.next->o;
+		fd_list_unlink(&h->chain);
+		free(h->id);
+		free(h);
+	}
+		
+	return 0;
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extensions/rt_redirect/redir_out.c	Fri Feb 18 14:50:37 2011 +0900
@@ -0,0 +1,280 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License)                                                               *
+* Author: Sebastien Decugis <sdecugis@nict.go.jp>							 *
+*													 *
+* Copyright (c) 2011, WIDE Project and NICT								 *
+* All rights reserved.											 *
+* 													 *
+* Redistribution and use of this software in source and binary forms, with or without modification, are  *
+* permitted provided that the following conditions are met:						 *
+* 													 *
+* * Redistributions of source code must retain the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer.										 *
+*    													 *
+* * Redistributions in binary form must reproduce the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer in the documentation and/or other						 *
+*   materials provided with the distribution.								 *
+* 													 *
+* * Neither the name of the WIDE Project or NICT nor the 						 *
+*   names of its contributors may be used to endorse or 						 *
+*   promote products derived from this software without 						 *
+*   specific prior written permission of WIDE Project and 						 *
+*   NICT.												 *
+* 													 *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 	 *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 	 *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.								 *
+*********************************************************************************************************/
+
+#include "rt_redir.h"
+
+
+/* Find the data pertinent to a type in the input data */
+static int get_data_to_match(enum redir_h_u type, struct msg * msg, union matchdata * data, int * nodata)
+{
+	TRACE_ENTRY("%d %p %p %p", type, msg, data, nodata);
+	
+	/* Initialize the data area */
+	memset(data, 0, sizeof(union matchdata));
+	*nodata = 0;
+	
+	/* Now, find the appropriate information, depending on type */
+	switch (type) {
+		case DONT_CACHE:
+			data->message.msg = msg;
+			break;
+			
+		case ALL_SESSION:
+			{
+				/* Get the sid from the message */
+				struct session * sess;
+				CHECK_FCT( fd_msg_sess_get(fd_g_config->cnf_dict, msg, &sess, NULL) );
+				if (!sess) {
+					TRACE_DEBUG(ANNOYING, "Message %p cannot match any ALL_SESSION rule since it does not have a Session-Id", msg);
+					*nodata = 1;
+				} else {
+					CHECK_FCT( fd_sess_getsid(sess, &data->session.s, &data->session.l) );
+				}
+			}
+			break;
+		
+		case ALL_REALM:
+			{
+				/* Search the Destination-Realm in the message */
+				struct avp * dr;
+				CHECK_FCT( fd_msg_search_avp(msg, redir_dict_dr, &dr) );
+				if (!dr) {
+					TRACE_DEBUG(ANNOYING, "Message %p cannot match any ALL_REALM rule since it does not have a Destination-Realm", msg);
+					*nodata = 1;
+				} else {
+					struct avp_hdr * ahdr;
+					CHECK_FCT(  fd_msg_avp_hdr( dr, &ahdr )  );
+					data->realm.s = ahdr->avp_value->os.data;
+					data->realm.l = ahdr->avp_value->os.len;
+				}
+			}
+			break;
+				
+		case REALM_AND_APPLICATION:
+			{
+				/* Search the Destination-Realm of the message */
+				struct avp * dr;
+				CHECK_FCT( fd_msg_search_avp(msg, redir_dict_dr, &dr) );
+				if (!dr) {
+					TRACE_DEBUG(ANNOYING, "Message %p cannot match any REALM_AND_APPLICATION rule since it does not have a Destination-Realm", msg);
+					*nodata = 1;
+				} else {
+					struct avp_hdr * ahdr;
+					CHECK_FCT(  fd_msg_avp_hdr( dr, &ahdr )  );
+					data->realm_app.s = ahdr->avp_value->os.data;
+					data->realm_app.l = ahdr->avp_value->os.len;
+					
+					/* and then the application */
+					{
+						struct msg_hdr * hdr;
+						CHECK_FCT( fd_msg_hdr(msg, &hdr) );
+						data->realm_app.a = hdr->msg_appl;
+						/* Should we forbid application 0? */
+					}
+				}
+			}
+			break;
+				
+		case ALL_APPLICATION:
+			{
+				/* Retrieve the application from the message */
+				struct msg_hdr * hdr;
+				CHECK_FCT( fd_msg_hdr(msg, &hdr) );
+				data->app.a = hdr->msg_appl;
+			}
+			break;
+			
+		case ALL_HOST:
+				/* This is more complex, we need to match with all candidates in each rule, it'll be done later */
+			break;
+			
+		case ALL_USER:
+			{
+				/* Search the User-Name of the message */
+				struct avp * un;
+				CHECK_FCT( fd_msg_search_avp(msg, redir_dict_un, &un) );
+				if (!un) {
+					TRACE_DEBUG(ANNOYING, "Message %p cannot match any ALL_USER rule since it does not have a User-Name", msg);
+					*nodata = 1;
+				} else {
+					struct avp_hdr * ahdr;
+					CHECK_FCT(  fd_msg_avp_hdr( un, &ahdr )  );
+					data->user.s = ahdr->avp_value->os.data;
+					data->user.l = ahdr->avp_value->os.len;
+				}
+			}
+			break;
+		
+		default:
+			ASSERT(0);
+			return EINVAL;
+	}
+
+	return 0;
+}
+
+
+/* Apply the score from a rule if the candidate list is appropriate */
+static int apply_rule(struct redir_entry * e, struct msg * msg, struct fd_list * candidates)
+{
+	struct fd_list * lic, *lirh;
+	struct rtd_candidate * c_oh = NULL;
+	int cmp;
+	
+	TRACE_ENTRY("%p %p %p", e, msg, candidates);
+	ASSERT( e && msg && candidates );
+	
+	if (FD_IS_LIST_EMPTY(candidates)) {
+		TRACE_DEBUG(ANNOYING, "Skip Redirect rule since candidates list is empty");
+		return 0;
+	}
+	
+	/* Now search common peers between e->target_peers_list and candidates */
+	TRACE_DEBUG(ANNOYING, "Message %p matches a Redirect rule (t:%d, @%p), processing candidates list", msg, e->type, e);
+	
+	/* First, decrease the score of the host that we received the previous Redirect from, in case it is in the list */
+	for (lic = candidates->next; lic != candidates; lic = lic->next) {
+		struct rtd_candidate * cand = (struct rtd_candidate *) lic;
+		
+		/* Special case: ALL_HOST rules: we decrease the score of the Origin-Host if present */
+		if (e->type == ALL_HOST) {
+			cmp = fd_os_almostcasecmp(cand->diamid, cand->diamidlen, e->data.host.s, e->data.host.l);
+			if (!cmp) {
+				TRACE_DEBUG(FULL, "Redirect msg %p: peer '%.*s' += %d (previous ALL_HOST Redirect originated from this peer)", msg, cand->diamidlen, cand->diamid, FD_SCORE_SENT_REDIRECT);
+				cand->score += FD_SCORE_SENT_REDIRECT;
+				c_oh = cand;
+				continue;
+			}
+		}
+		
+		cmp = fd_os_cmp(cand->diamid, cand->diamidlen, e->from.s, e->from.l);
+		if (!cmp) {
+			TRACE_DEBUG(FULL, "Redirect msg %p: peer '%.*s' += %d (previous Redirect received from this peer)", msg, cand->diamidlen, cand->diamid, FD_SCORE_SENT_REDIRECT);
+			cand->score += FD_SCORE_SENT_REDIRECT;
+		}
+		
+	}
+	
+	if ((e->type == ALL_HOST) && (c_oh == NULL)) {
+		/* The rule does not apply, we're done */
+		return 0;
+	}
+	
+	/* for each candidate, if it is found in the target_peers list, we add the rule's score to this candidate */
+	for (lic = candidates->next; lic != candidates; lic = lic->next) {
+		/* the candidates list is not guaranteed to be ordered at this time, so we cannot avoid the two imbricated loops */
+		struct rtd_candidate * cand = (struct rtd_candidate *) lic;
+		
+		/* Is this candidate in the "Redirect-Host" list ? */
+		for (lirh = e->target_peers_list.next; lirh != &e->target_peers_list; lirh = lirh->next) {
+			struct redir_host * host = lirh->o;
+			
+			cmp = fd_os_cmp( cand->diamid, cand->diamidlen, host->id, host->len );
+			
+			if (cmp < 0)
+				break; /* targets are ordered */
+			
+			if (cmp == 0) {
+				TRACE_DEBUG(FULL, "Redirect msg %p: peer '%.*s' += %d (rule t:%d @%p)", msg, cand->diamidlen, cand->diamid, redirects_usages[e->type].score, e->type, e);
+				cand->score += redirects_usages[e->type].score;
+			}
+		}
+	}
+	
+	return 0;
+}
+
+
+/* OUT callback */
+int redir_out_cb(void * cbdata, struct msg * msg, struct fd_list * candidates)
+{
+	int i, ret = 0;
+	
+	TRACE_ENTRY("%p %p %p", cbdata, msg, candidates);
+	
+	for (i = 0; i <= H_U_MAX; i++) {
+		/* Lock the line. We write lock in case of DONT_CACHE so we can directly unlink the entry. read in other cases is sufficient */
+		if (i == DONT_CACHE) {
+			CHECK_POSIX( pthread_rwlock_wrlock( &redirects_usages[i].lock ) );
+		} else {
+			CHECK_POSIX( pthread_rwlock_rdlock( &redirects_usages[i].lock ) );
+		}
+		
+		if (!FD_IS_LIST_EMPTY(&redirects_usages[i].sentinel)) {
+			union matchdata data;
+			int nodata; /* The message does not allow to apply this rule, skip */
+			
+			/* Retrieve the data that may match in the message */
+			CHECK_FCT_DO( ret = get_data_to_match(i, msg, &data, &nodata), goto out );
+			
+			/* If this message may match some of our rules */
+			if (!nodata) {
+				struct fd_list * li;
+				/* Attempt each rule we have stored */
+				for (li = redirects_usages[i].sentinel.next; li != &redirects_usages[i].sentinel; li = li->next) {
+					struct redir_entry * e = li->o;
+					
+					/* Does it match ? */
+					if (i != ALL_HOST) { /* this one is an exception, we handle it separately */
+						int cmp = redir_entry_cmp_key[i](&data, &e->data);
+						if (cmp > 0)
+							continue;
+						if (cmp < 0)
+							break;
+					}
+					
+					/* This rule matches (or we are in ALL_HOST), apply */
+					CHECK_FCT_DO( ret = apply_rule(e, msg, candidates), goto out );
+					
+					/* If this was a DONT_CACHE rule, we unlink it, so that it will not be used again */
+					if (i == DONT_CACHE) {
+						li=li->prev;
+						fd_list_unlink( li->next );
+						/* We cannot delete here without taking the mutex, which would mean we have first to release the lock... 
+							just let expiry garbage collet the rule */
+					}
+				}
+			}
+		
+		}
+out:		
+		CHECK_POSIX( pthread_rwlock_unlock( &redirects_usages[i].lock ) );
+		if (ret)
+			return ret;
+	}
+	
+	return 0;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extensions/rt_redirect/rt_redir.c	Fri Feb 18 14:50:37 2011 +0900
@@ -0,0 +1,102 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License)                                                               *
+* Author: Sebastien Decugis <sdecugis@nict.go.jp>							 *
+*													 *
+* Copyright (c) 2011, WIDE Project and NICT								 *
+* All rights reserved.											 *
+* 													 *
+* Redistribution and use of this software in source and binary forms, with or without modification, are  *
+* permitted provided that the following conditions are met:						 *
+* 													 *
+* * Redistributions of source code must retain the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer.										 *
+*    													 *
+* * Redistributions in binary form must reproduce the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer in the documentation and/or other						 *
+*   materials provided with the distribution.								 *
+* 													 *
+* * Neither the name of the WIDE Project or NICT nor the 						 *
+*   names of its contributors may be used to endorse or 						 *
+*   promote products derived from this software without 						 *
+*   specific prior written permission of WIDE Project and 						 *
+*   NICT.												 *
+* 													 *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 	 *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 	 *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.								 *
+*********************************************************************************************************/
+
+#include "rt_redir.h"
+
+struct dict_object * redir_dict_dr = NULL;
+struct dict_object * redir_dict_un = NULL;
+
+static struct fd_rt_fwd_hdl * fwd_hdl = NULL;
+static struct fd_rt_out_hdl * out_hdl = NULL;
+static pthread_t exp_thr  = (pthread_t)NULL;
+
+/* Initialize the module */
+static int redir_entry(char * conffile)
+{
+	TRACE_ENTRY("");
+	
+	/* Dictionary objects */
+	CHECK_FCT(  fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Realm", &redir_dict_dr, ENOENT)  );
+	CHECK_FCT(  fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "User-Name", &redir_dict_un, ENOENT)  );
+
+	/* Initialize the entries array */
+	CHECK_FCT( redir_entry_init() );
+	
+	/* Start the expire thread */
+	CHECK_POSIX( pthread_create( &exp_thr, NULL, redir_exp_thr_fct, NULL ) );
+	
+	/* Register the callback that receives the answers and processes when it contains a Redirect indication. */
+	CHECK_FCT( fd_rt_fwd_register ( redir_fwd_cb, NULL, RT_FWD_ANS, &fwd_hdl ) );
+
+	/* Register the callback that applies the saved Redirect rules to outgoing messages. */
+	CHECK_FCT( fd_rt_out_register ( redir_out_cb, NULL, 10, &out_hdl ) );
+
+	return 0;
+}
+
+EXTENSION_ENTRY("rt_redirect", redir_entry);
+
+/* And terminate it */
+void fd_ext_fini(void)
+{
+	int i;
+	
+	/* Unregister the callbacks */
+	if (fwd_hdl) {
+		CHECK_FCT_DO( fd_rt_fwd_unregister(fwd_hdl, NULL), );
+	}
+	if (out_hdl) {
+		CHECK_FCT_DO( fd_rt_out_unregister(out_hdl, NULL), );
+	}
+	
+	/* Stop the expiry thread */
+	CHECK_FCT_DO( fd_thr_term(&exp_thr), );
+	
+	/* Empty all entries */
+	CHECK_POSIX_DO( pthread_mutex_lock(&redir_exp_peer_lock),   );
+	for (i = 0; i <= H_U_MAX; i++) {
+		CHECK_POSIX_DO( pthread_rwlock_wrlock( &redirects_usages[i].lock), );
+		while (!FD_IS_LIST_EMPTY(&redirects_usages[i].sentinel)) {
+			struct redir_entry * e = redirects_usages[i].sentinel.next->o;
+			fd_list_unlink(&e->redir_list);
+			CHECK_FCT_DO( redir_entry_destroy(e), );
+		}
+		CHECK_POSIX_DO( pthread_rwlock_unlock( &redirects_usages[i].lock), );
+		CHECK_POSIX_DO( pthread_rwlock_destroy( &redirects_usages[i].lock), );
+	}
+	CHECK_POSIX_DO( pthread_mutex_unlock(&redir_exp_peer_lock),   );
+	
+	return;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/extensions/rt_redirect/rt_redir.h	Fri Feb 18 14:50:37 2011 +0900
@@ -0,0 +1,173 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License)                                                               *
+* Author: Sebastien Decugis <sdecugis@nict.go.jp>							 *
+*													 *
+* Copyright (c) 2011, WIDE Project and NICT								 *
+* All rights reserved.											 *
+* 													 *
+* Redistribution and use of this software in source and binary forms, with or without modification, are  *
+* permitted provided that the following conditions are met:						 *
+* 													 *
+* * Redistributions of source code must retain the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer.										 *
+*    													 *
+* * Redistributions in binary form must reproduce the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer in the documentation and/or other						 *
+*   materials provided with the distribution.								 *
+* 													 *
+* * Neither the name of the WIDE Project or NICT nor the 						 *
+*   names of its contributors may be used to endorse or 						 *
+*   promote products derived from this software without 						 *
+*   specific prior written permission of WIDE Project and 						 *
+*   NICT.												 *
+* 													 *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 	 *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 	 *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.								 *
+*********************************************************************************************************/
+
+/* Diameter Redirect management */
+#include <freeDiameter/extension.h>
+
+/* There are 2 locks in this module. The priority is established as follow to avoid deadlocks:
+exp_peer mutex > usages rwlock.
+(e.g., the rwlock can be taken while holding the mutex, but not the other way) 
+*/
+
+/* The types of redirects (from Redirect-Host-Usage AVP value) */
+enum redir_h_u {
+	DONT_CACHE = 0,
+	ALL_SESSION,
+	ALL_REALM,
+	REALM_AND_APPLICATION,
+	ALL_APPLICATION,
+	ALL_HOST,
+	ALL_USER
+};
+#define H_U_MAX	ALL_USER
+
+/* Eye catcher */
+#define REDIR_ENTRY_EYEC  0x43D14E74
+
+/* Expiration time set for DONT_CACHE tasks, so that the entry is found when the code is called back */
+#define DEFAULT_EXPIRE_TIME 10 /* seconds */
+	
+/* Structure to store a parsed Redirect-Host */
+struct redir_host {
+	struct fd_list chain; 
+	
+	DiamId_t id;	/* malloc'd */
+	size_t	 len;
+	/* We don't use the following yet because we don't support dynamic new connections 
+	int 	 secure;
+	uint16_t port;
+	int	 l4;
+	char	 proto;
+	*/
+};
+
+/* Rule data that is stored depending on Redirect-Host-Usage value */
+union matchdata {
+	/* DONT_CACHE */
+	struct {
+		struct msg * msg; /* The query message for which this rule was created */
+	} message;
+
+	/* ALL_SESSION */
+	struct {
+		os0_t s;  /* sid */
+		size_t l;
+	} session;
+
+	/* ALL_REALM */
+	struct {
+		os0_t s;  /* Destination-Realm AVP data */
+		size_t l;
+	} realm;
+
+	/* REALM_AND_APPLICATION */
+	struct {
+		application_id_t a; /* message's application */
+		os0_t s;  /* Destination-Realm AVP data */
+		size_t l;
+	} realm_app;
+
+	/* ALL_APPLICATION */
+	struct {
+		application_id_t a; /* message's application */
+	} app;
+
+	/* ALL_HOST */
+	struct {
+		os0_t s;  /* the Origin-Host of the Redirect message (which may be the next hop or not) */
+		size_t l;
+	} host;
+
+	/* ALL_USER */
+	struct {
+		os0_t s;  /* User-Name AVP data */
+		size_t l;
+	} user;
+};
+	
+
+/* Structure to store a Redirect indication */
+struct redir_entry {
+	uint32_t eyec; /* must be REDIR_ENTRY_EYEC, used for debug only */
+	
+	struct {
+		os0_t s; /* alloc'd, must be freed */
+		size_t l;
+	} from; /* whom this rule was received from (next hop) ? */
+
+	struct fd_list	 target_peers_list; /* The list of Redirect-Hosts for this entry */
+
+	struct timespec  timeout;  /* When does this entry expires? */
+	struct fd_list   exp_list; /* chain in the expire_list list, ordered by expiration date, protected by exp_peer_lock */
+	
+	enum redir_h_u type;  /* Type of this entry */
+	struct fd_list redir_list; /* link in redirects_usages lists. Lists are ordered by the data value. Protected by rw locks */
+	union matchdata	data;	/* The strings are duplicated & must be freed in this structure */
+};
+
+/* The array where the redir_entries are stored */
+struct redir_line {
+	enum fd_rt_out_score 	score;
+	pthread_rwlock_t	lock; /* protect the list */
+	struct fd_list		sentinel; /* list of redir_entry, the "o" field of the sentinel points to the redir_line entry */
+};
+extern struct redir_line redirects_usages[];
+
+/* Accelerator to the line lock */
+#define RWLOCK_REDIR( _entry ) ( &(redirects_usages[(_entry)->type].lock) )
+
+/* Lock that must be owned before calling some functions */
+extern pthread_mutex_t redir_exp_peer_lock;
+
+/* Dictionary cached objects */
+extern struct dict_object * redir_dict_dr;
+extern struct dict_object * redir_dict_un;
+
+/* Functions on redir_entry */
+int redir_entry_init();
+int redir_entry_new(struct redir_entry ** e, struct fd_list * targets, uint32_t rhu, struct msg * qry, DiamId_t nh, size_t nhlen, os0_t oh, size_t ohlen);
+extern int (*redir_entry_cmp_key[])(union matchdata * , union matchdata *); /* compare functions */
+int redir_entry_insert(struct redir_entry * e);
+int redir_entry_destroy(struct redir_entry * e);
+
+/* Functions for expiry */
+void * redir_exp_thr_fct(void * arg);
+int redir_exp_set(struct redir_entry * e, uint32_t duration);
+
+/* Forward cb */
+int redir_fwd_cb(void * cbdata, struct msg ** msg);
+
+/* Out callback */
+int redir_out_cb(void * cbdata, struct msg * msg, struct fd_list * candidates);
--- a/include/freeDiameter/libfdcore.h	Thu Feb 17 16:37:15 2011 +0900
+++ b/include/freeDiameter/libfdcore.h	Fri Feb 18 14:50:37 2011 +0900
@@ -657,6 +657,7 @@
 
 enum fd_rt_out_score {
 	FD_SCORE_NO_DELIVERY	 = -70,	/* We should not send this message to this candidate */
+	FD_SCORE_SENT_REDIRECT	 = -60, /* If this peer previously sent a Redirect indication that applies to this message */
 	FD_SCORE_INI		 =  -2, /* All candidates are initialized with this value */
 	FD_SCORE_LOAD_BALANCE	 =   1,	/* Use this to differentiate between several peers with the same score */
 	FD_SCORE_DEFAULT	 =   5,	/* The peer is a default route for all messages */
@@ -668,6 +669,7 @@
 	FD_SCORE_REDIR_REALM_APP =  40,	/* If there is a redirect rule with REALM_AND_APPLICATION for these message and peer */
 	FD_SCORE_REDIR_USER	 =  45,	/* If there is a redirect rule with ALL_USER for these message and peer */
 	FD_SCORE_REDIR_SESSION	 =  50,	/* If there is a redirect rule with ALL_SESSION for these message and peer */
+	FD_SCORE_REDIR_ONCE	 =  55,	/* If there is a redirect rule with DONT_CACHE for these message and peer */
 	FD_SCORE_FINALDEST	 = 100	/* If the peer is the final recipient of the message (i.e. matching Destination-Host), it receives a big score. */
 };
 
--- a/include/freeDiameter/libfdproto.h	Thu Feb 17 16:37:15 2011 +0900
+++ b/include/freeDiameter/libfdproto.h	Fri Feb 18 14:50:37 2011 +0900
@@ -606,8 +606,11 @@
   rfc3588 states explicitely that such a Diameter Identity consists only of ASCII characters. */
 int fd_os_is_valid_DiameterIdentity(uint8_t * os, size_t ossz);
 
-/* This checks a string is a valid DiameterIdentity and applies IDNA transformations otherwise (xn--...) */
-int fd_os_validate_DiameterIdentity(char ** id, size_t * outsz, int memory /* 0: *id can be realloc'd. 1: *id must be malloc'd on output (was static) */ );
+/* The following function validates a string as a Diameter Identity or applies the IDNA transformation on it 
+ if *inoutsz is != 0 on entry, *id may not be \0-terminated.
+ memory has the following meaning: 0: *id can be realloc'd. 1: *id must be malloc'd on output (was static)
+*/
+int fd_os_validate_DiameterIdentity(char ** id, size_t * inoutsz, int memory);
 
 /* Create an order relationship for binary strings (not needed to be \0 terminated). 
    It does NOT mimic strings relationships so that it is more efficient. It is case sensitive.
@@ -626,6 +629,15 @@
 int fd_os_almostcasecmp_int(uint8_t * os1, size_t os1sz, uint8_t * os2, size_t os2sz);
 #define fd_os_almostcasecmp(_o1, _l1, _o2, _l2)  fd_os_almostcasecmp_int((os0_t)(_o1), _l1, (os0_t)(_o2), _l2)
 
+/* Analyze a DiameterURI and return its components. 
+  Return EINVAL if the URI is not valid. 
+  *diamid is malloc'd on function return and must be freed (it is processed by fd_os_validate_DiameterIdentity).
+  *secure is 0 (no security) or 1 (security enabled) on return.
+  *port is 0 (default) or a value in host byte order on return.
+  *transport is 0 (default) or IPPROTO_* on return.
+  *proto is 0 (default) or 'd' (diameter), 'r' (radius), or 't' (tacacs+) on return.
+  */
+int fd_os_parse_DiameterURI(uint8_t * uri, size_t urisz, DiamId_t * diamid, size_t * diamidlen, int * secure, uint16_t * port, int * transport, char *proto);
 
 /*============================================================*/
 /*                          THREADS                           */
--- a/libfdcore/routing_dispatch.c	Thu Feb 17 16:37:15 2011 +0900
+++ b/libfdcore/routing_dispatch.c	Fri Feb 18 14:50:37 2011 +0900
@@ -721,7 +721,7 @@
 			CHECK_FCT(fd_fifo_post(fd_g_local, pmsg) );
 			return 0;
 		}
-
+		
 		/* From that point, for answers, we will call the registered callbacks, then pass it to the dispatch module or forward it */
 	}
 
@@ -1105,8 +1105,6 @@
 	CHECK_FCT( fd_rt_out_register( dont_send_if_no_common_app, NULL, 10, NULL ) );
 	CHECK_FCT( fd_rt_out_register( score_destination_avp, NULL, 10, NULL ) );
 	
-	TODO("built-in callbacks for Redirect messages?");
-	
 	return 0;
 }
 
--- a/libfdproto/ostr.c	Thu Feb 17 16:37:15 2011 +0900
+++ b/libfdproto/ostr.c	Fri Feb 18 14:50:37 2011 +0900
@@ -166,16 +166,25 @@
 	return 1;
 }
 
-/* The following function validates a string as a Diameter Identity or applies the IDNA transformation on it */
-int fd_os_validate_DiameterIdentity(char ** id, size_t * outsz, int memory /* 0: *id can be realloc'd. 1: *id must be malloc'd on output (was static) */)
+/* The following function validates a string as a Diameter Identity or applies the IDNA transformation on it 
+ if *inoutsz is != 0 on entry, *id may not be \0-terminated.
+ memory has the following meaning: 0: *id can be realloc'd. 1: *id must be malloc'd on output (was static)
+*/
+int fd_os_validate_DiameterIdentity(char ** id, size_t * inoutsz, int memory)
 {
-	TRACE_ENTRY("%p %p", id, outsz);
+	int gotsize = 0;
+	
+	TRACE_ENTRY("%p %p", id, inoutsz);
+	CHECK_PARAMS( id && *id && inoutsz );
 	
-	*outsz = strlen(*id);
+	if (!*inoutsz)
+		*inoutsz = strlen(*id);
+	else
+		gotsize = 1;
 	
 #ifndef DIAMID_IDNA_IGNORE
 	
-	if (!fd_os_is_valid_DiameterIdentity((os0_t)*id, *outsz)) {
+	if (!fd_os_is_valid_DiameterIdentity((os0_t)*id, *inoutsz)) {
 	
 #ifdef DIAMID_IDNA_REJECT
 		
@@ -188,13 +197,23 @@
 		char *processed;
 		int ret;
 		
+		if (gotsize) { /* make it \0-terminated */
+			if (memory) {
+				CHECK_MALLOC( *id = os0dup(*id, *inoutsz) );
+				memory = 0;
+			} else {
+				CHECK_MALLOC( *id = realloc(*id, *inoutsz + 1) );
+				(*id)[*inoutsz] = '0';
+			}
+		}
+		
 		ret = idna_to_ascii_8z ( *id, &processed, IDNA_USE_STD3_ASCII_RULES );
 		if (ret == IDNA_SUCCESS) {
 			TRACE_DEBUG(INFO, "The string '%s' is not a valid DiameterIdentity, it was changed to '%s'", *id, processed);
 			if (memory == 0)
 				free(*id);
 			*id = processed;
-			*outsz = strlen(processed);
+			*inoutsz = strlen(processed);
 			/* Done! */
 		} else {
 			TRACE_DEBUG(INFO, "The string '%s' is not a valid DiameterIdentity and cannot be sanitanized: %s", *id, idna_strerror (ret));
@@ -206,12 +225,197 @@
 #endif /* ! DIAMID_IDNA_IGNORE */
 	{
 		if (memory == 1) {
-			CHECK_MALLOC( *id = os0dup(*id, *outsz) );
+			CHECK_MALLOC( *id = os0dup(*id, *inoutsz) );
 		}
 	}
 	return 0;
 }
 
+/* Analyze a DiameterURI and return its components. 
+  Return EINVAL if the URI is not valid. 
+  *diamid is malloc'd on function return and must be freed (it is processed by fd_os_validate_DiameterIdentity).
+  *secure is 0 (no security) or 1 (security enabled) on return.
+  *port is 0 (default) or a value in host byte order on return.
+  *transport is 0 (default) or IPPROTO_* on return.
+  *proto is 0 (default) or 'd' (diameter), 'r' (radius), or 't' (tacacs+) on return.
+  */
+int fd_os_parse_DiameterURI(uint8_t * uri, size_t urisz, DiamId_t * diamid, size_t * diamidlen, int * secure, uint16_t * port, int * transport, char *proto)
+{
+	size_t offset = 0;
+	DiamId_t fqdn = NULL;
+	size_t   fqdnlen;
+	TRACE_ENTRY("%p %zd %p %p %p %p %p %p", uri, urisz, diamid, diamidlen, secure, port, transport, proto);
+	CHECK_PARAMS( uri && urisz );
+	
+	CHECK_PARAMS( urisz > 7 ); /* "aaa" + "://" + something else at least */
+	
+	/* Initialize values */
+	if (secure)
+		*secure = 0;
+	if (port)
+		*port = 0;
+	if (transport)
+		*transport = 0;
+	if (proto)
+		*proto = 0;
+	
+	/* Check the beginning */
+	if (memcmp( uri, "aaa", 3)) {
+		TRACE_DEBUG(INFO, "Invalid DiameterURI prefix: got '%.*s', expected 'aaa'", 3, uri);
+		return EINVAL;
+	}
+	offset += 3;
+	
+	/* Secure? */
+	if (uri[offset] == (uint8_t)'s') {
+		if (secure)
+			*secure = 1;
+		offset += 1;
+	}
+	
+	/* Remaining of URI marker */
+	if (memcmp( uri + offset, "://", 3)) {
+		TRACE_DEBUG(INFO, "Invalid DiameterURI prefix: got '%.*s', expected 'aaa://' or 'aaas://'", offset + 3, uri);
+		return EINVAL;
+	}
+	offset += 3;
+	
+	/* This is the start of the FQDN */
+	fqdn = (DiamId_t)uri + offset;
+	for ( ; offset < urisz ; offset++ ) {
+		/* Stop only when we find ':' or ';' */
+		if ((uri[offset] == (uint8_t)':') || (uri[offset] == (uint8_t)';'))
+			break;
+	}
+	fqdnlen = offset - (fqdn - (DiamId_t)uri);
+	CHECK_FCT(fd_os_validate_DiameterIdentity(&fqdn, &fqdnlen, 1));
+	if (diamid)
+		*diamid = fqdn;
+	else
+		free(fqdn);
+	if (diamidlen)
+		*diamidlen = fqdnlen;
+	
+	if (offset == urisz)
+		return 0; /* Finished */
+	
+	/* Is there a port ? */
+	if (uri[offset] == ':') {
+		uint16_t p = 0;
+		do {
+			offset++;
+
+			if (offset == urisz)
+				break;
+
+			uint32_t t = (uint32_t)((char)uri[offset] - '0');
+			if (t > 9)
+				break; /* we did not get a digit */
+
+			t += p * 10; /* the port is specified in decimal base */
+			
+			if (t >= (1<<16)) {
+				TRACE_DEBUG(INFO, "Invalid DiameterURI: port value is too big, ignored.");
+				p = 0;
+				break;
+			}
+
+			p = t;
+		} while (1);
+
+		if (port)
+			*port = p;
+	}
+	
+	if (offset == urisz)
+		return 0; /* Finished */
+	
+	/* Is there a transport? */
+	if ( (urisz - offset > CONSTSTRLEN(";transport=")) 
+		&& !strncasecmp((char *)uri + offset, ";transport=", CONSTSTRLEN(";transport=")) ) {
+	
+		offset += CONSTSTRLEN(";transport=");
+
+		if (urisz - offset < 3) {
+			TRACE_DEBUG(INFO, "Invalid DiameterURI: transport string is too short, ignored.");
+			return 0;
+		}		
+		if (!strncasecmp((char *)uri + offset, "tcp", CONSTSTRLEN("tcp"))) {
+			if (transport)
+				*transport = IPPROTO_TCP;
+			offset += CONSTSTRLEN("tcp");
+			goto after_transport;
+		}
+		if (!strncasecmp((char *)uri + offset, "udp", CONSTSTRLEN("udp"))) {
+			if (transport)
+				*transport = IPPROTO_UDP;
+			offset += CONSTSTRLEN("udp");
+			goto after_transport;
+		}
+		if ((urisz - offset > 3) && !strncasecmp((char *)uri + offset, "sctp", CONSTSTRLEN("sctp"))) {
+			if (transport) {
+#ifndef DISABLE_SCTP
+				*transport = IPPROTO_SCTP;
+#else /* DISABLE_SCTP */
+				TRACE_DEBUG(INFO, "Received DiameterURI with 'transport=sctp' but DISABLE_SCTP was selected");
+				*transport = 0;
+#endif /* DISABLE_SCTP */
+			}
+			offset += CONSTSTRLEN("sctp");
+			goto after_transport;
+		}
+		
+		TRACE_DEBUG(INFO, "Invalid DiameterURI: transport string is not recognized ('%.*s'), ignored.", urisz - offset, uri + offset);
+		
+		/* eat the remaining of invalid transport until a ';' */
+		for (; (offset < urisz) && ((char)uri[offset] != ';'); offset++);
+	}
+after_transport:
+	if (offset == urisz)
+		return 0; /* Finished */
+	
+	/* Is there a protocol? */
+	if ( ((urisz - offset) > CONSTSTRLEN(";protocol=")) 
+		&& (!strncasecmp((char *)uri + offset, ";protocol=", CONSTSTRLEN(";protocol="))) ) {
+	
+		offset += CONSTSTRLEN(";protocol=");
+
+		if ( ((urisz - offset) >= CONSTSTRLEN("diameter")) 
+		    && (!strncasecmp((char *)uri + offset, "diameter", CONSTSTRLEN("diameter"))) ) {
+			if (proto)
+				*proto = 'd';
+			offset += CONSTSTRLEN("diameter");
+			goto after_proto;
+		}
+		
+		if ( ((urisz - offset) >= CONSTSTRLEN("radius")) 
+		    && (!strncasecmp((char *)uri + offset, "radius", CONSTSTRLEN("radius"))) ) {
+			if (proto)
+				*proto = 'r';
+			offset += CONSTSTRLEN("radius");
+			goto after_proto;
+		}
+		
+		if ( ((urisz - offset) >= CONSTSTRLEN("tacacs+")) 
+		    && (!strncasecmp((char *)uri + offset, "tacacs+", CONSTSTRLEN("tacacs+"))) ) {
+			if (proto)
+				*proto = 't';
+			offset += CONSTSTRLEN("tacacs+");
+			goto after_proto;
+		}
+		
+		TRACE_DEBUG(INFO, "Invalid DiameterURI: protocol string is not recognized ('%.*s'), ignored.", urisz - offset, uri + offset);
+		
+		/* eat the remaining of invalid transport until a ';' */
+		for (; (offset < urisz) && ((char)uri[offset] != ';'); offset++);
+	}
+after_proto:
+	if (offset == urisz)
+		return 0; /* Finished */
+	
+	TRACE_DEBUG(INFO, "Invalid DiameterURI: string is not recognized ('%.*s'), ignored.", urisz - offset, uri + offset);
+	return 0;
+}
 
 
 /********************************************************************************************************/
--- a/libfdproto/rt_data.c	Thu Feb 17 16:37:15 2011 +0900
+++ b/libfdproto/rt_data.c	Fri Feb 18 14:50:37 2011 +0900
@@ -131,7 +131,7 @@
 	/* Create the new entry */
 	CHECK_MALLOC( new = malloc(sizeof(struct rtd_candidate)) );
 	memset(new, 0, sizeof(struct rtd_candidate) );
-	fd_list_init(&new->chain, NULL);
+	fd_list_init(&new->chain, new);
 	CHECK_MALLOC( new->diamid = os0dup(peerid, peeridlen) )
 	new->diamidlen = peeridlen;
 	if (realm) {
--- a/tests/testostr.c	Thu Feb 17 16:37:15 2011 +0900
+++ b/tests/testostr.c	Fri Feb 18 14:50:37 2011 +0900
@@ -74,7 +74,7 @@
 	/* Check the Diameter Identity functions */
 	{
 		char * res;
-		size_t len;
+		size_t len=0;
 		
 		/* A valid ASCII domain name */
 		res = TEST_IDN_CONV;
@@ -87,6 +87,7 @@
 		
 		/* Now, an invalid string */
 		res = TEST_IDN_UTF8;
+		len = 0;
 		
 		#ifdef DIAMID_IDNA_IGNORE
 		
"Welcome to our mercurial repository"