diff --git a/README.md b/README.md
index f417e30d4..4c5329666 100644
--- a/README.md
+++ b/README.md
@@ -164,6 +164,7 @@ option and which are reproduced below:
 	  -T, --tos=INT                    TOS value to set on streams
 	  -o, --timeout=SECS               RTP timeout
 	  -s, --silent-timeout=SECS        RTP timeout for muted
+	  -a, --final-timeout=SECS         Call timeout
 	  -p, --pidfile=FILE               Write PID to file
 	  -f, --foreground                 Don't fork to background
 	  -m, --port-min=INT               Lowest port to use for RTP
@@ -293,6 +294,11 @@ The options are described in more detail below.
 	Ditto as the `--timeout` option, but applies to muted or inactive media streams. Defaults to 3600
 	(one hour).
 
+* -a, --final-timeout
+
+	The number of seconds since call creation, after call is deleted. Useful for limiting the lifetime of a call.
+	This feature can be disabled by setting the parameter to 0. By default this timeout is disabled.
+
 * -p, --pidfile
 
 	Specifies a path and file name to write the daemon's PID number to.
@@ -718,6 +724,10 @@ Optionally included keys are:
 		Corresponds to the *rtpproxy* `a` flag. Advertises an RTP endpoint which uses asymmetric
 		RTP, which disables learning of endpoint addresses (see below).
 
+	- `unidirectional`
+
+		When this flag is present, kernelize also one-way rtp media.
+
 	- `strict source`
 
 		Normally, *rtpengine* attempts to learn the correct endpoint address for every stream during
diff --git a/daemon/aux.h b/daemon/aux.h
index 91667b7d3..f3468a180 100644
--- a/daemon/aux.h
+++ b/daemon/aux.h
@@ -569,6 +569,11 @@ INLINE void timeval_multiply(struct timeval *result, const struct timeval *a, co
 	timeval_from_us(result, timeval_us(a) * multiplier);
 }
 INLINE void timeval_divide(struct timeval *result, const struct timeval *a, const long divisor) {
+	if (divisor == 0) {
+		result->tv_sec = 0;
+		result->tv_usec = 0;
+		return ;
+	}
 	timeval_from_us(result, timeval_us(a) / divisor);
 }
 INLINE void timeval_add(struct timeval *result, const struct timeval *a, const struct timeval *b) {
diff --git a/daemon/call.c b/daemon/call.c
index 3d9f8c96e..42dffed61 100644
--- a/daemon/call.c
+++ b/daemon/call.c
@@ -108,6 +108,7 @@ static const char * const __term_reason_texts[] = {
 	[REGULAR] = "REGULAR",
 	[FORCED] = "FORCED",
 	[SILENT_TIMEOUT] = "SILENT_TIMEOUT",
+	[FINAL_TIMEOUT] = "FINAL_TIMEOUT",
 };
 static const char * const __tag_type_texts[] = {
 	[FROM_TAG] = "FROM_TAG",
@@ -179,7 +180,7 @@ static void call_timer_iterator(void *key, void *val, void *ptr) {
 	int good = 0;
 	struct packet_stream *ps;
 	struct stream_fd *sfd;
-	int tmp_t_reason=0;
+	int tmp_t_reason = UNKNOWN;
 	struct call_monologue *ml;
 	enum call_stream_state css;
 	atomic64 *timestamp;
@@ -188,6 +189,19 @@ static void call_timer_iterator(void *key, void *val, void *ptr) {
 	log_info_call(c);
 
 	cm = c->callmaster;
+	rwlock_lock_r(&cm->conf.config_lock);
+
+	if (cm->conf.final_timeout && poller_now >= (c->created + cm->conf.final_timeout)) {
+		ilog(LOG_INFO, "Closing call due to final timeout");
+		tmp_t_reason = FINAL_TIMEOUT;
+		for (it = c->monologues.head; it; it = it->next) {
+			ml = it->data;
+			gettimeofday(&(ml->terminated),NULL);
+			ml->term_reason = tmp_t_reason;
+		}
+
+		goto delete;
+	}
 
 	if (c->redis_foreign_call) {
 		ilog(LOG_DEBUG, "Redis-Notification: Timeout resets the deletion timers for a call where I am not responsible.");
@@ -234,10 +248,10 @@ no_sfd:
 			goto next;
 
 		check = cm->conf.timeout;
-		tmp_t_reason = 1;
+		tmp_t_reason = TIMEOUT;
 		if (!MEDIA_ISSET(ps->media, RECV) || !sfd || !PS_ISSET(ps, FILLED)) {
 			check = cm->conf.silent_timeout;
-			tmp_t_reason = 2;
+			tmp_t_reason = SILENT_TIMEOUT;
 		}
 
 		if (poller_now - atomic64_get(timestamp) < check)
@@ -257,13 +271,7 @@ next:
 	for (it = c->monologues.head; it; it = it->next) {
 		ml = it->data;
 		gettimeofday(&(ml->terminated),NULL);
-		if (tmp_t_reason==1) {
-			ml->term_reason = TIMEOUT;
-		} else if (tmp_t_reason==2) {
-			ml->term_reason = SILENT_TIMEOUT;
-		} else {
-			ml->term_reason = UNKNOWN;
-		}
+		ml->term_reason = tmp_t_reason;
 	}
 
 	ilog(LOG_INFO, "Closing call due to timeout");
@@ -277,6 +285,7 @@ delete:
 	goto out;
 
 out:
+	rwlock_unlock_r(&cm->conf.config_lock);
 	rwlock_unlock_r(&c->master_lock);
 	log_info_clear();
 }
@@ -1561,8 +1570,8 @@ int monologue_offer_answer(struct call_monologue *other_ml, GQueue *streams,
 		if (sp->rtp_endpoint.port) {
 			/* copy parameters advertised by the sender of this message */
 			bf_copy_same(&other_media->media_flags, &sp->sp_flags,
-					SHARED_FLAG_RTCP_MUX | SHARED_FLAG_ASYMMETRIC | SHARED_FLAG_ICE
-					| SHARED_FLAG_TRICKLE_ICE | SHARED_FLAG_ICE_LITE);
+					SHARED_FLAG_RTCP_MUX | SHARED_FLAG_ASYMMETRIC | SHARED_FLAG_UNIDIRECTIONAL |
+					SHARED_FLAG_ICE | SHARED_FLAG_TRICKLE_ICE | SHARED_FLAG_ICE_LITE);
 
 			crypto_params_copy(&other_media->sdes_in.params, &sp->crypto, 1);
 			other_media->sdes_in.tag = sp->sdes_tag;
@@ -2168,6 +2177,11 @@ void call_destroy(struct call *c) {
 					&m->latest_graphite_interval_start,
 					m->conf.graphite_interval);
 		}
+
+		if (ml->term_reason==FINAL_TIMEOUT) {
+			atomic64_inc(&m->totalstats.total_final_timeout_sess);
+			atomic64_inc(&m->totalstats_interval.total_final_timeout_sess);
+		}
 	}
 
 
diff --git a/daemon/call.h b/daemon/call.h
index b73289486..b8edec02a 100644
--- a/daemon/call.h
+++ b/daemon/call.h
@@ -35,7 +35,8 @@ enum termination_reason {
 	REGULAR=1,
 	FORCED=2,
 	TIMEOUT=3,
-	SILENT_TIMEOUT=4
+	SILENT_TIMEOUT=4,
+	FINAL_TIMEOUT=5
 };
 
 enum tag_type {
@@ -128,6 +129,7 @@ enum call_type {
 #define SHARED_FLAG_MEDIA_HANDOVER		0x00000200
 #define SHARED_FLAG_TRICKLE_ICE			0x00000400
 #define SHARED_FLAG_ICE_LITE			0x00000800
+#define SHARED_FLAG_UNIDIRECTIONAL		0x00001000
 
 /* struct stream_params */
 #define SP_FLAG_NO_RTCP				0x00010000
@@ -136,6 +138,7 @@ enum call_type {
 #define SP_FLAG_SEND				SHARED_FLAG_SEND
 #define SP_FLAG_RECV				SHARED_FLAG_RECV
 #define SP_FLAG_ASYMMETRIC			SHARED_FLAG_ASYMMETRIC
+#define SP_FLAG_UNIDIRECTIONAL			SHARED_FLAG_UNIDIRECTIONAL
 #define SP_FLAG_SETUP_ACTIVE			SHARED_FLAG_SETUP_ACTIVE
 #define SP_FLAG_SETUP_PASSIVE			SHARED_FLAG_SETUP_PASSIVE
 #define SP_FLAG_ICE				SHARED_FLAG_ICE
@@ -163,6 +166,7 @@ enum call_type {
 /* struct call_media */
 #define MEDIA_FLAG_INITIALIZED			0x00010000
 #define MEDIA_FLAG_ASYMMETRIC			SHARED_FLAG_ASYMMETRIC
+#define MEDIA_FLAG_UNIDIRECTIONAL		SHARED_FLAG_UNIDIRECTIONAL
 #define MEDIA_FLAG_SEND				SHARED_FLAG_SEND
 #define MEDIA_FLAG_RECV				SHARED_FLAG_RECV
 #define MEDIA_FLAG_RTCP_MUX			SHARED_FLAG_RTCP_MUX
@@ -236,12 +240,19 @@ struct stats {
 	atomic64			foreign_sessions; // unresponsible via redis notification
 };
 
+struct request_time {
+	mutex_t lock;
+	u_int64_t count;
+	struct timeval time_min, time_max, time_avg;
+};
+
 struct totalstats {
 	time_t 				started;
 	atomic64			total_timeout_sess;
 	atomic64			total_foreign_sessions;
 	atomic64			total_rejected_sess;
 	atomic64			total_silent_timeout_sess;
+	atomic64			total_final_timeout_sess;
 	atomic64			total_regular_term_sess;
 	atomic64			total_forced_term_sess;
 	atomic64			total_relayed_packets;
@@ -259,6 +270,8 @@ struct totalstats {
 
 	mutex_t				total_calls_duration_lock; /* for these two below */
 	struct timeval		total_calls_duration_interval;
+
+	struct request_time		offer, answer, delete;
 };
 
 struct stream_params {
@@ -428,9 +441,14 @@ struct call {
 struct callmaster_config {
 	int			kernelfd;
 	int			kernelid;
+
+	/* everything below protected by config_lock */
+	rwlock_t		config_lock;
 	int			max_sessions;
 	unsigned int		timeout;
 	unsigned int		silent_timeout;
+	unsigned int		final_timeout;
+
 	unsigned int		delete_delay;
 	struct redis		*redis;
 	struct redis		*redis_write;
diff --git a/daemon/call_interfaces.c b/daemon/call_interfaces.c
index 8c0812f85..0353379bf 100644
--- a/daemon/call_interfaces.c
+++ b/daemon/call_interfaces.c
@@ -388,10 +388,12 @@ str *call_query_udp(char **out, struct callmaster *m) {
 
 	rwlock_unlock_w(&c->master_lock);
 
+	rwlock_lock_r(&m->conf.config_lock);
 	ret = str_sprintf("%s %lld "UINT64F" "UINT64F" "UINT64F" "UINT64F"\n", out[RE_UDP_COOKIE],
 		(long long int) m->conf.silent_timeout - (poller_now - stats.last_packet),
 		atomic64_get_na(&stats.totals[0].packets), atomic64_get_na(&stats.totals[1].packets),
 		atomic64_get_na(&stats.totals[2].packets), atomic64_get_na(&stats.totals[3].packets));
+	rwlock_unlock_r(&m->conf.config_lock);
 	goto out;
 
 err:
@@ -541,6 +543,8 @@ static void call_ng_process_flags(struct sdp_ng_flags *out, bencode_item_t *inpu
 				out->trust_address = 0;
 			else if (!bencode_strcmp(it, "asymmetric"))
 				out->asymmetric = 1;
+			else if (!bencode_strcmp(it, "unidirectional"))
+				out->unidirectional = 1;
 			else if (!bencode_strcmp(it, "strict-source"))
 				out->strict_source = 1;
 			else if (!bencode_strcmp(it, "media-handover"))
@@ -741,6 +745,7 @@ out:
 const char *call_offer_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output, const char* addr,
 		const endpoint_t *sin)
 {
+	rwlock_lock_r(&m->conf.config_lock);
 	if (m->conf.max_sessions>=0) {
 		rwlock_lock_r(&m->hashlock);
 		if (g_hash_table_size(m->callhash) -
@@ -751,10 +756,14 @@ const char *call_offer_ng(bencode_item_t *input, struct callmaster *m, bencode_i
 			atomic64_inc(&m->totalstats.total_rejected_sess);
 			atomic64_inc(&m->totalstats_interval.total_rejected_sess);
 			ilog(LOG_ERROR, "Parallel session limit reached (%i)",m->conf.max_sessions);
+
+			rwlock_unlock_r(&m->conf.config_lock);
 			return "Parallel session limit reached";
 		}
 		rwlock_unlock_r(&m->hashlock);
 	}
+
+	rwlock_unlock_r(&m->conf.config_lock);
 	return call_offer_answer_ng(input, m, output, OP_OFFER, addr, sin);
 }
 
diff --git a/daemon/call_interfaces.h b/daemon/call_interfaces.h
index 23ee7bea8..214861ec0 100644
--- a/daemon/call_interfaces.h
+++ b/daemon/call_interfaces.h
@@ -31,6 +31,7 @@ struct sdp_ng_flags {
 	sockfamily_t *address_family;
 	int tos;
 	int asymmetric:1,
+	    unidirectional:1,
 	    trust_address:1,
 	    port_latching:1,
 	    replace_origin:1,
diff --git a/daemon/cli.c b/daemon/cli.c
index 80f25f93f..15b1b9ec7 100644
--- a/daemon/cli.c
+++ b/daemon/cli.c
@@ -22,6 +22,7 @@ static void cli_incoming_list_totals(char* buffer, int len, struct callmaster* m
 	int printlen=0;
 	struct timeval avg, calls_dur_iv;
 	u_int64_t num_sessions, min_sess_iv, max_sess_iv;
+	struct request_time offer_iv, answer_iv, delete_iv;
 
 	mutex_lock(&m->totalstats.total_average_lock);
 	avg = m->totalstats.total_average_call_dur;
@@ -40,6 +41,8 @@ static void cli_incoming_list_totals(char* buffer, int len, struct callmaster* m
 	ADJUSTLEN(printlen,outbufend,replybuffer);
 	printlen = snprintf(replybuffer,(outbufend-replybuffer), " Total timed-out sessions via SILENT_TIMEOUT     :"UINT64F"\n",atomic64_get(&m->totalstats.total_silent_timeout_sess));
 	ADJUSTLEN(printlen,outbufend,replybuffer);
+	printlen = snprintf(replybuffer,(outbufend-replybuffer), " Total timed-out sessions via FINAL_TIMEOUT      :"UINT64F"\n",atomic64_get(&m->totalstats.total_final_timeout_sess));
+	ADJUSTLEN(printlen,outbufend,replybuffer);
 	printlen = snprintf(replybuffer,(outbufend-replybuffer), " Total regular terminated sessions               :"UINT64F"\n",atomic64_get(&m->totalstats.total_regular_term_sess));
 	ADJUSTLEN(printlen,outbufend,replybuffer);
 	printlen = snprintf(replybuffer,(outbufend-replybuffer), " Total forced terminated sessions                :"UINT64F"\n",atomic64_get(&m->totalstats.total_forced_term_sess));
@@ -59,7 +62,15 @@ static void cli_incoming_list_totals(char* buffer, int len, struct callmaster* m
 	calls_dur_iv = m->totalstats_lastinterval.total_calls_duration_interval;
 	min_sess_iv = m->totalstats_lastinterval.managed_sess_min;
 	max_sess_iv = m->totalstats_lastinterval.managed_sess_max;
-	mutex_unlock(&m->totalstats_lastinterval_lock);
+        offer_iv = m->totalstats_lastinterval.offer;
+        answer_iv = m->totalstats_lastinterval.answer;
+        delete_iv = m->totalstats_lastinterval.delete;
+        mutex_unlock(&m->totalstats_lastinterval_lock);
+
+        // compute average offer/answer/delete time
+        timeval_divide(&offer_iv.time_avg, &offer_iv.time_avg, offer_iv.count);
+        timeval_divide(&answer_iv.time_avg, &answer_iv.time_avg, answer_iv.count);
+        timeval_divide(&delete_iv.time_avg, &delete_iv.time_avg, delete_iv.count);
 
 	printlen = snprintf(replybuffer,(outbufend-replybuffer), "\nGraphite interval statistics (last reported values to graphite):\n");
 	ADJUSTLEN(printlen,outbufend,replybuffer);
@@ -69,6 +80,21 @@ static void cli_incoming_list_totals(char* buffer, int len, struct callmaster* m
 	ADJUSTLEN(printlen,outbufend,replybuffer);
 	printlen = snprintf(replybuffer,(outbufend-replybuffer), " Max managed sessions                            :"UINT64F"\n", max_sess_iv);
 	ADJUSTLEN(printlen,outbufend,replybuffer);
+	printlen = snprintf(replybuffer,(outbufend-replybuffer), " Min/Max/Avg offer processing delay              :%llu.%06llu/%llu.%06llu/%llu.%06llu sec\n",
+		(unsigned long long)offer_iv.time_min.tv_sec,(unsigned long long)offer_iv.time_min.tv_usec,
+		(unsigned long long)offer_iv.time_max.tv_sec,(unsigned long long)offer_iv.time_max.tv_usec,
+		(unsigned long long)offer_iv.time_avg.tv_sec,(unsigned long long)offer_iv.time_avg.tv_usec);
+	ADJUSTLEN(printlen,outbufend,replybuffer);
+	printlen = snprintf(replybuffer,(outbufend-replybuffer), " Min/Max/Avg answer processing delay             :%llu.%06llu/%llu.%06llu/%llu.%06llu sec\n",
+		(unsigned long long)answer_iv.time_min.tv_sec,(unsigned long long)answer_iv.time_min.tv_usec,
+		(unsigned long long)answer_iv.time_max.tv_sec,(unsigned long long)answer_iv.time_max.tv_usec,
+		(unsigned long long)answer_iv.time_avg.tv_sec,(unsigned long long)answer_iv.time_avg.tv_usec);
+	ADJUSTLEN(printlen,outbufend,replybuffer);
+	printlen = snprintf(replybuffer,(outbufend-replybuffer), " Min/Max/Avg delete processing delay             :%llu.%06llu/%llu.%06llu/%llu.%06llu sec\n",
+		(unsigned long long)delete_iv.time_min.tv_sec,(unsigned long long)delete_iv.time_min.tv_usec,
+		(unsigned long long)delete_iv.time_max.tv_sec,(unsigned long long)delete_iv.time_max.tv_usec,
+		(unsigned long long)delete_iv.time_avg.tv_sec,(unsigned long long)delete_iv.time_avg.tv_usec);
+	ADJUSTLEN(printlen,outbufend,replybuffer);
 
 	printlen = snprintf(replybuffer,(outbufend-replybuffer), "\n\n");
 	ADJUSTLEN(printlen,outbufend,replybuffer);
@@ -137,6 +163,24 @@ static void cli_incoming_list_maxopenfiles(char* buffer, int len, struct callmas
 	return ;
 }
 
+static void cli_incoming_list_timeout(char* buffer, int len, struct callmaster* m, char* replybuffer, const char* outbufend) {
+	int printlen=0;
+
+	rwlock_lock_r(&m->conf.config_lock);
+
+	/* don't lock anything while reading the value */
+	printlen = snprintf(replybuffer,(outbufend-replybuffer), "TIMEOUT=%u\n", m->conf.timeout);
+	ADJUSTLEN(printlen,outbufend,replybuffer);
+	printlen = snprintf(replybuffer,(outbufend-replybuffer), "SILENT_TIMEOUT=%u\n", m->conf.silent_timeout);
+	ADJUSTLEN(printlen,outbufend,replybuffer);
+	printlen = snprintf(replybuffer,(outbufend-replybuffer), "FINAL_TIMEOUT=%u\n", m->conf.final_timeout);
+	ADJUSTLEN(printlen,outbufend,replybuffer);
+
+	rwlock_unlock_r(&m->conf.config_lock);
+
+	return ;
+}
+
 static void cli_incoming_list_callid(char* buffer, int len, struct callmaster* m, char* replybuffer, const char* outbufend) {
    str callid;
    struct call* c=0;
@@ -250,11 +294,12 @@ static void cli_incoming_set_maxopenfiles(char* buffer, int len, struct callmast
 	unsigned int open_files_num;
 	str open_files;
 	pid_t pid;
+	char *endptr;
 
 	// limit the minimum number of open files to avoid rtpengine freeze for low open_files_num values
 	unsigned int min_open_files_num = (1 << 16);
 
-	if (len<=1) {
+	if (len <= 1) {
 		printlen = snprintf(replybuffer,(outbufend-replybuffer), "%s\n", "More parameters required.");
 		ADJUSTLEN(printlen,outbufend,replybuffer);
 		return;
@@ -263,23 +308,27 @@ static void cli_incoming_set_maxopenfiles(char* buffer, int len, struct callmast
 	++buffer; --len; // one space
 	open_files.s = buffer;
 	open_files.len = len;
-	open_files_num = str_to_ui(&open_files, -1);
+	open_files_num = strtol(open_files.s, &endptr, 10);
 
-	if (open_files_num == -1) {
-		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Fail setting open_files to %.*s; not an unsigned integer\n", open_files.len, open_files.s);
+	if ((errno == ERANGE && (open_files_num == LONG_MAX || open_files_num == LONG_MIN)) || (errno != 0 && open_files_num == 0)) {
+		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Fail setting open_files to %.*s; errno=%d\n", open_files.len, open_files.s, errno);
+		ADJUSTLEN(printlen,outbufend,replybuffer);
+		return;
+	} else if (endptr == open_files.s) {
+		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Fail setting open_files to %.*s; no digists found\n", open_files.len, open_files.s);
 		ADJUSTLEN(printlen,outbufend,replybuffer);
 		return;
 	} else if (open_files_num < min_open_files_num) {
-		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Fail setting open_files to %.*s; can't set it under %u\n", open_files.len, open_files.s, min_open_files_num);
+		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Fail setting open_files to %u; can't set it under %u\n", open_files_num, min_open_files_num);
 		ADJUSTLEN(printlen,outbufend,replybuffer);
 		return;
 	} else if (rlim(RLIMIT_NOFILE, open_files_num) == -1){
-		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Fail setting open_files to %.*s; errno = %d\n", open_files.len, open_files.s, errno);
+		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Fail setting open_files to %u; errno = %d\n", open_files_num, errno);
 		ADJUSTLEN(printlen,outbufend,replybuffer);
 		return;
 	} else {
 		pid = getpid();
-		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Success setting open_files to %.*s; cat /proc/%u/limits\n", open_files.len, open_files.s, pid);
+		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Success setting open_files to %u; cat /proc/%u/limits\n", open_files_num, pid);
 		ADJUSTLEN(printlen,outbufend,replybuffer);
 	}
 }
@@ -287,11 +336,11 @@ static void cli_incoming_set_maxopenfiles(char* buffer, int len, struct callmast
 static void cli_incoming_set_maxsessions(char* buffer, int len, struct callmaster* m, char* replybuffer, const char* outbufend) {
 	int printlen = 0;
 	int maxsessions_num;
-	int err = 0x80000000;
 	int disabled = -1;
 	str maxsessions;
+	char *endptr;
 
-	if (len<=1) {
+	if (len <= 1) {
 		printlen = snprintf(replybuffer,(outbufend-replybuffer), "%s\n", "More parameters required.");
 		ADJUSTLEN(printlen,outbufend,replybuffer);
 		return;
@@ -300,22 +349,29 @@ static void cli_incoming_set_maxsessions(char* buffer, int len, struct callmaste
 	++buffer; --len; // one space
 	maxsessions.s = buffer;
 	maxsessions.len = len;
-	maxsessions_num = str_to_i(&maxsessions, err);
+	maxsessions_num = strtol(maxsessions.s, &endptr, 10);
 
-	if (maxsessions_num == err) {
-		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Fail setting maxsessions to %.*s; not an integer\n", maxsessions.len, maxsessions.s);
+	if ((errno == ERANGE && (maxsessions_num == LONG_MAX || maxsessions_num == LONG_MIN)) || (errno != 0 && maxsessions_num == 0)) {
+		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Fail setting maxsessions to %.*s; errno=%d\n", maxsessions.len, maxsessions.s, errno);
+		ADJUSTLEN(printlen,outbufend,replybuffer);
+		return;
+	} else if (endptr == maxsessions.s) {
+		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Fail setting maxsessions to %.*s; no digists found\n", maxsessions.len, maxsessions.s);
 		ADJUSTLEN(printlen,outbufend,replybuffer);
+		return;
 	} else if (maxsessions_num < disabled) {
 		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Fail setting maxsessions to %d; either positive or -1 values allowed\n", maxsessions_num);
 		ADJUSTLEN(printlen,outbufend,replybuffer);
 	} else if (maxsessions_num == disabled) {
-		/* don't lock anything while writing the value - only this command modifies its value */
+		rwlock_lock_w(&m->conf.config_lock);
 		m->conf.max_sessions = maxsessions_num;
+		rwlock_unlock_w(&m->conf.config_lock);
 		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Success setting maxsessions to %d; disable feature\n", maxsessions_num);
 		ADJUSTLEN(printlen,outbufend,replybuffer);
 	} else {
-		/* don't lock anything while writing the value - only this command modifies its value */
+		rwlock_lock_w(&m->conf.config_lock);
 		m->conf.max_sessions = maxsessions_num;
+		rwlock_unlock_w(&m->conf.config_lock);
 		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Success setting maxsessions to %d\n", maxsessions_num);
 		ADJUSTLEN(printlen,outbufend,replybuffer);
 	}
@@ -323,6 +379,41 @@ static void cli_incoming_set_maxsessions(char* buffer, int len, struct callmaste
 	return;
 }
 
+static void cli_incoming_set_timeout(char* buffer, int len, struct callmaster* m, char* replybuffer, const char* outbufend, unsigned int *conf_timeout) {
+	int printlen = 0;
+	unsigned int timeout_num;
+	str timeout;
+	char *endptr;
+
+	if (len <= 1) {
+		printlen = snprintf(replybuffer,(outbufend-replybuffer), "%s\n", "More parameters required.");
+		ADJUSTLEN(printlen,outbufend,replybuffer);
+		return;
+	}
+
+	++buffer; --len; // one space
+	timeout.s = buffer;
+	timeout.len = len;
+	timeout_num = strtol(timeout.s, &endptr, 10);
+
+	if ((errno == ERANGE && (timeout_num == LONG_MAX || timeout_num == LONG_MIN)) || (errno != 0 && timeout_num == 0)) {
+		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Fail setting timeout to %.*s; errno=%d\n", timeout.len, timeout.s, errno);
+		ADJUSTLEN(printlen,outbufend,replybuffer);
+		return;
+	} else if (endptr == timeout.s) {
+		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Fail setting timeout to %.*s; no digists found\n", timeout.len, timeout.s);
+		ADJUSTLEN(printlen,outbufend,replybuffer);
+		return;
+	} else {
+		/* don't lock anything while writing the value - only this command modifies its value */
+		rwlock_lock_w(&m->conf.config_lock);
+		*conf_timeout = timeout_num;
+		rwlock_unlock_w(&m->conf.config_lock);
+		printlen = snprintf (replybuffer,(outbufend-replybuffer), "Success setting timeout to %u\n", timeout_num);
+		ADJUSTLEN(printlen,outbufend,replybuffer);
+	}
+}
+
 static void cli_incoming_list(char* buffer, int len, struct callmaster* m, char* replybuffer, const char* outbufend) {
    GHashTableIter iter;
    gpointer key, value;
@@ -336,6 +427,7 @@ static void cli_incoming_list(char* buffer, int len, struct callmaster* m, char*
    static const char* LIST_TOTALS = "totals";
    static const char* LIST_MAX_OPEN_FILES = "maxopenfiles";
    static const char* LIST_MAX_SESSIONS = "maxsessions";
+   static const char* LIST_TIMEOUT = "timeout";
 
    if (len<=1) {
        printlen = snprintf(replybuffer, outbufend-replybuffer, "%s\n", "More parameters required.");
@@ -377,6 +469,8 @@ static void cli_incoming_list(char* buffer, int len, struct callmaster* m, char*
        cli_incoming_list_maxsessions(buffer+strlen(LIST_MAX_SESSIONS), len-strlen(LIST_MAX_SESSIONS), m, replybuffer, outbufend);
    } else if (len>=strlen(LIST_MAX_OPEN_FILES) && strncmp(buffer,LIST_MAX_OPEN_FILES,strlen(LIST_MAX_OPEN_FILES)) == 0) {
        cli_incoming_list_maxopenfiles(buffer+strlen(LIST_MAX_OPEN_FILES), len-strlen(LIST_MAX_OPEN_FILES), m, replybuffer, outbufend);
+   } else if (len>=strlen(LIST_TIMEOUT) && strncmp(buffer,LIST_TIMEOUT,strlen(LIST_TIMEOUT)) == 0) {
+       cli_incoming_list_timeout(buffer+strlen(LIST_TIMEOUT), len-strlen(LIST_TIMEOUT), m, replybuffer, outbufend);
    } else {
        printlen = snprintf(replybuffer, outbufend-replybuffer, "%s:%s\n", "Unknown 'list' command", buffer);
        ADJUSTLEN(printlen,outbufend,replybuffer);
@@ -388,6 +482,9 @@ static void cli_incoming_set(char* buffer, int len, struct callmaster* m, char*
 
 	static const char* SET_MAX_OPEN_FILES = "maxopenfiles";
 	static const char* SET_MAX_SESSIONS = "maxsessions";
+	static const char* SET_TIMEOUT = "timeout";
+	static const char* SET_SILENT_TIMEOUT = "silenttimeout";
+	static const char* SET_FINAL_TIMEOUT = "finaltimeout";
 
 	if (len<=1) {
 		printlen = snprintf(replybuffer, outbufend-replybuffer, "%s\n", "More parameters required.");
@@ -400,6 +497,12 @@ static void cli_incoming_set(char* buffer, int len, struct callmaster* m, char*
 		cli_incoming_set_maxopenfiles(buffer+strlen(SET_MAX_OPEN_FILES), len-strlen(SET_MAX_OPEN_FILES), m, replybuffer, outbufend);
 	} else if (len>=strlen(SET_MAX_SESSIONS) && strncmp(buffer,SET_MAX_SESSIONS,strlen(SET_MAX_SESSIONS)) == 0) {
 		cli_incoming_set_maxsessions(buffer+strlen(SET_MAX_SESSIONS), len-strlen(SET_MAX_SESSIONS), m, replybuffer, outbufend);
+	} else if (len>=strlen(SET_TIMEOUT) && strncmp(buffer,SET_TIMEOUT,strlen(SET_TIMEOUT)) == 0) {
+		cli_incoming_set_timeout(buffer+strlen(SET_TIMEOUT), len-strlen(SET_TIMEOUT), m, replybuffer, outbufend, &m->conf.timeout);
+	} else if (len>=strlen(SET_SILENT_TIMEOUT) && strncmp(buffer,SET_SILENT_TIMEOUT,strlen(SET_SILENT_TIMEOUT)) == 0) {
+		cli_incoming_set_timeout(buffer+strlen(SET_SILENT_TIMEOUT), len-strlen(SET_SILENT_TIMEOUT), m, replybuffer, outbufend, &m->conf.silent_timeout);
+	} else if (len>=strlen(SET_FINAL_TIMEOUT) && strncmp(buffer,SET_FINAL_TIMEOUT,strlen(SET_FINAL_TIMEOUT)) == 0) {
+		cli_incoming_set_timeout(buffer+strlen(SET_FINAL_TIMEOUT), len-strlen(SET_FINAL_TIMEOUT), m, replybuffer, outbufend, &m->conf.final_timeout);
 	} else {
 		printlen = snprintf(replybuffer, outbufend-replybuffer, "%s:%s\n", "Unknown 'set' command", buffer);
 		ADJUSTLEN(printlen,outbufend,replybuffer);
diff --git a/daemon/control_ng.c b/daemon/control_ng.c
index 1f9735a47..31d924c11 100644
--- a/daemon/control_ng.c
+++ b/daemon/control_ng.c
@@ -13,6 +13,30 @@
 #include "call_interfaces.h"
 #include "socket.h"
 
+static void timeval_update_request_time(struct request_time *request, const struct timeval *offer_diff) {
+	// lock offers
+	mutex_lock(&request->lock);
+
+	// update min value
+	if (timeval_us(&request->time_min) == 0 ||
+	    timeval_cmp(&request->time_min, offer_diff) > 0) {
+		timeval_from_us(&request->time_min, timeval_us(offer_diff));
+	}
+
+	// update max value
+	if (timeval_us(&request->time_max) == 0 ||
+	    timeval_cmp(&request->time_max, offer_diff) < 0) {
+		timeval_from_us(&request->time_max, timeval_us(offer_diff));
+	}
+
+	// update avg value
+	timeval_add(&request->time_avg, &request->time_avg, offer_diff);
+	request->count++;
+
+	// unlock offers
+	mutex_unlock(&request->lock);
+}
+
 
 static void pretty_print(bencode_item_t *el, GString *s) {
 	bencode_item_t *chld;
@@ -86,6 +110,9 @@ static void control_ng_incoming(struct obj *obj, str *buf, const endpoint_t *sin
 	struct iovec iov[3];
 	unsigned int iovlen;
 	GString *log_str;
+	struct timeval offer_start, offer_stop;
+	struct timeval answer_start, answer_stop;
+	struct timeval delete_start, delete_stop;
 
 	struct control_ng_stats* cur = get_control_ng_stats(c,&sin->address);
 
@@ -143,16 +170,46 @@ static void control_ng_incoming(struct obj *obj, str *buf, const endpoint_t *sin
 		g_atomic_int_inc(&cur->ping);
 	}
 	else if (!str_cmp(&cmd, "offer")) {
+		// start offer timer
+		gettimeofday(&offer_start, NULL);
+
 		errstr = call_offer_ng(dict, c->callmaster, resp, addr, sin);
 		g_atomic_int_inc(&cur->offer);
+
+		// stop offer timer
+		gettimeofday(&offer_stop, NULL);
+
+		// print offer duration
+		timeval_from_us(&offer_stop, timeval_diff(&offer_stop, &offer_start));
+		ilog(LOG_INFO, "offer time = %llu.%06llu sec", (unsigned long long)offer_stop.tv_sec, (unsigned long long)offer_stop.tv_usec);
 	}
 	else if (!str_cmp(&cmd, "answer")) {
+		// start answer timer
+		gettimeofday(&answer_start, NULL);
+
 		errstr = call_answer_ng(dict, c->callmaster, resp);
 		g_atomic_int_inc(&cur->answer);
+
+		// stop answer timer
+		gettimeofday(&answer_stop, NULL);
+
+		// print answer duration
+		timeval_from_us(&answer_stop, timeval_diff(&answer_stop, &answer_start));
+		ilog(LOG_INFO, "answer time = %llu.%06llu sec", (unsigned long long)answer_stop.tv_sec, (unsigned long long)answer_stop.tv_usec);
 	}
 	else if (!str_cmp(&cmd, "delete")) {
+		// start delete timer
+		gettimeofday(&delete_start, NULL);
+
 		errstr = call_delete_ng(dict, c->callmaster, resp);
 		g_atomic_int_inc(&cur->delete);
+
+		// stop delete timer
+		gettimeofday(&delete_stop, NULL);
+
+		// print delete duration
+		timeval_from_us(&delete_stop, timeval_diff(&delete_stop, &delete_start));
+		ilog(LOG_INFO, "delete time = %llu.%06llu sec", (unsigned long long)delete_stop.tv_sec, (unsigned long long)delete_stop.tv_usec);
 	}
 	else if (!str_cmp(&cmd, "query")) {
 		errstr = call_query_ng(dict, c->callmaster, resp);
@@ -168,6 +225,15 @@ static void control_ng_incoming(struct obj *obj, str *buf, const endpoint_t *sin
 	if (errstr)
 		goto err_send;
 
+	// update interval statistics
+	if (!str_cmp(&cmd, "offer")) {
+		timeval_update_request_time(&c->callmaster->totalstats_interval.offer, &offer_stop);
+	} else if (!str_cmp(&cmd, "answer")) {
+		timeval_update_request_time(&c->callmaster->totalstats_interval.answer, &answer_stop);
+	} else if (!str_cmp(&cmd, "delete")) {
+		timeval_update_request_time(&c->callmaster->totalstats_interval.delete, &delete_stop);
+	}
+
 	goto send_resp;
 
 err_send:
diff --git a/daemon/graphite.c b/daemon/graphite.c
index 3dd65d2ab..3f0c721d5 100644
--- a/daemon/graphite.c
+++ b/daemon/graphite.c
@@ -37,6 +37,23 @@ void set_prefix(char* prefix) {
 	graphite_prefix = prefix;
 }
 
+static struct request_time timeval_clear_request_time(struct request_time *request) {
+	struct request_time ret;
+
+        mutex_lock(&request->lock);
+        ret = *request;
+        request->time_min.tv_sec = 0;
+        request->time_min.tv_usec = 0;
+        request->time_max.tv_sec = 0;
+        request->time_max.tv_usec = 0;
+        request->time_avg.tv_sec = 0;
+        request->time_avg.tv_usec = 0;
+        request->count = 0;
+        mutex_unlock(&request->lock);
+
+	return ret;
+}
+
 int connect_to_graphite_server(const endpoint_t *graphite_ep) {
 	int rc;
 
@@ -87,6 +104,7 @@ int send_graphite_data(struct callmaster *cm, struct totalstats *sent_data) {
 	atomic64_local_copy_zero_struct(ts, &cm->totalstats_interval, total_timeout_sess);
 	atomic64_local_copy_zero_struct(ts, &cm->totalstats_interval, total_rejected_sess);
 	atomic64_local_copy_zero_struct(ts, &cm->totalstats_interval, total_silent_timeout_sess);
+	atomic64_local_copy_zero_struct(ts, &cm->totalstats_interval, total_final_timeout_sess);
 	atomic64_local_copy_zero_struct(ts, &cm->totalstats_interval, total_regular_term_sess);
 	atomic64_local_copy_zero_struct(ts, &cm->totalstats_interval, total_forced_term_sess);
 	atomic64_local_copy_zero_struct(ts, &cm->totalstats_interval, total_relayed_packets);
@@ -105,10 +123,13 @@ int send_graphite_data(struct callmaster *cm, struct totalstats *sent_data) {
 	ts->total_calls_duration_interval = cm->totalstats_interval.total_calls_duration_interval;
 	cm->totalstats_interval.total_calls_duration_interval.tv_sec = 0;
 	cm->totalstats_interval.total_calls_duration_interval.tv_usec = 0;
- 
 	//ZERO(cm->totalstats_interval.total_calls_duration_interval);
 	mutex_unlock(&cm->totalstats_interval.total_calls_duration_lock);
 
+	ts->offer = timeval_clear_request_time(&cm->totalstats_interval.offer);
+	ts->answer = timeval_clear_request_time(&cm->totalstats_interval.answer);
+	ts->delete = timeval_clear_request_time(&cm->totalstats_interval.delete);
+
 	rwlock_lock_r(&cm->hashlock);
 	mutex_lock(&cm->totalstats_interval.managed_sess_lock);
 	ts->managed_sess_max = cm->totalstats_interval.managed_sess_max;
@@ -119,6 +140,32 @@ int send_graphite_data(struct callmaster *cm, struct totalstats *sent_data) {
 	mutex_unlock(&cm->totalstats_interval.managed_sess_lock);
 	rwlock_unlock_r(&cm->hashlock);
 
+	// compute average offer/answer/delete time
+	timeval_divide(&ts->offer.time_avg, &ts->offer.time_avg, ts->offer.count);
+	timeval_divide(&ts->answer.time_avg, &ts->answer.time_avg, ts->answer.count);
+	timeval_divide(&ts->delete.time_avg, &ts->delete.time_avg, ts->delete.count);
+
+	if (graphite_prefix!=NULL) { rc = sprintf(ptr,"%s",graphite_prefix); ptr += rc; }
+	rc = sprintf(ptr,"offer_time_min %llu.%06llu %llu\n",(unsigned long long)ts->offer.time_min.tv_sec,(unsigned long long)ts->offer.time_min.tv_usec,(unsigned long long)g_now.tv_sec); ptr += rc;
+	if (graphite_prefix!=NULL) { rc = sprintf(ptr,"%s",graphite_prefix); ptr += rc; }
+	rc = sprintf(ptr,"offer_time_max %llu.%06llu %llu\n",(unsigned long long)ts->offer.time_max.tv_sec,(unsigned long long)ts->offer.time_max.tv_usec,(unsigned long long)g_now.tv_sec); ptr += rc;
+	if (graphite_prefix!=NULL) { rc = sprintf(ptr,"%s",graphite_prefix); ptr += rc; }
+	rc = sprintf(ptr,"offer_time_avg %llu.%06llu %llu\n",(unsigned long long)ts->offer.time_avg.tv_sec,(unsigned long long)ts->offer.time_avg.tv_usec,(unsigned long long)g_now.tv_sec); ptr += rc;
+
+	if (graphite_prefix!=NULL) { rc = sprintf(ptr,"%s",graphite_prefix); ptr += rc; }
+	rc = sprintf(ptr,"answer_time_min %llu.%06llu %llu\n",(unsigned long long)ts->answer.time_min.tv_sec,(unsigned long long)ts->answer.time_min.tv_usec,(unsigned long long)g_now.tv_sec); ptr += rc;
+	if (graphite_prefix!=NULL) { rc = sprintf(ptr,"%s",graphite_prefix); ptr += rc; }
+	rc = sprintf(ptr,"answer_time_max %llu.%06llu %llu\n",(unsigned long long)ts->answer.time_max.tv_sec,(unsigned long long)ts->answer.time_max.tv_usec,(unsigned long long)g_now.tv_sec); ptr += rc;
+	if (graphite_prefix!=NULL) { rc = sprintf(ptr,"%s",graphite_prefix); ptr += rc; }
+	rc = sprintf(ptr,"answer_time_avg %llu.%06llu %llu\n",(unsigned long long)ts->answer.time_avg.tv_sec,(unsigned long long)ts->answer.time_avg.tv_usec,(unsigned long long)g_now.tv_sec); ptr += rc;
+
+	if (graphite_prefix!=NULL) { rc = sprintf(ptr,"%s",graphite_prefix); ptr += rc; }
+	rc = sprintf(ptr,"delete_time_min %llu.%06llu %llu\n",(unsigned long long)ts->delete.time_min.tv_sec,(unsigned long long)ts->delete.time_min.tv_usec,(unsigned long long)g_now.tv_sec); ptr += rc;
+	if (graphite_prefix!=NULL) { rc = sprintf(ptr,"%s",graphite_prefix); ptr += rc; }
+	rc = sprintf(ptr,"delete_time_max %llu.%06llu %llu\n",(unsigned long long)ts->delete.time_max.tv_sec,(unsigned long long)ts->delete.time_max.tv_usec,(unsigned long long)g_now.tv_sec); ptr += rc;
+	if (graphite_prefix!=NULL) { rc = sprintf(ptr,"%s",graphite_prefix); ptr += rc; }
+	rc = sprintf(ptr,"delete_time_avg %llu.%06llu %llu\n",(unsigned long long)ts->delete.time_avg.tv_sec,(unsigned long long)ts->delete.time_avg.tv_usec,(unsigned long long)g_now.tv_sec); ptr += rc;
+
 	if (graphite_prefix!=NULL) { rc = sprintf(ptr,"%s",graphite_prefix); ptr += rc; }
 	rc = sprintf(ptr, "call_dur %llu.%06llu %llu\n",(unsigned long long)ts->total_calls_duration_interval.tv_sec,(unsigned long long)ts->total_calls_duration_interval.tv_usec,(unsigned long long)g_now.tv_sec); ptr += rc;
 	if (graphite_prefix!=NULL) { rc = sprintf(ptr,"%s",graphite_prefix); ptr += rc; }
@@ -144,6 +191,8 @@ int send_graphite_data(struct callmaster *cm, struct totalstats *sent_data) {
 	if (graphite_prefix!=NULL) { rc = sprintf(ptr,"%s",graphite_prefix); ptr += rc; }
 	rc = sprintf(ptr,"silent_timeout_sess "UINT64F" %llu\n", atomic64_get_na(&ts->total_silent_timeout_sess),(unsigned long long)g_now.tv_sec); ptr += rc;
 	if (graphite_prefix!=NULL) { rc = sprintf(ptr,"%s",graphite_prefix); ptr += rc; }
+	rc = sprintf(ptr,"final_timeout_sess "UINT64F" %llu\n", atomic64_get_na(&ts->total_final_timeout_sess),(unsigned long long)g_now.tv_sec); ptr += rc;
+	if (graphite_prefix!=NULL) { rc = sprintf(ptr,"%s",graphite_prefix); ptr += rc; }
 	rc = sprintf(ptr,"timeout_sess "UINT64F" %llu\n", atomic64_get_na(&ts->total_timeout_sess),(unsigned long long)g_now.tv_sec); ptr += rc;
 	if (graphite_prefix!=NULL) { rc = sprintf(ptr,"%s",graphite_prefix); ptr += rc; }
 	rc = sprintf(ptr,"reject_sess "UINT64F" %llu\n", atomic64_get_na(&ts->total_rejected_sess),(unsigned long long)g_now.tv_sec); ptr += rc;
@@ -155,6 +204,19 @@ int send_graphite_data(struct callmaster *cm, struct totalstats *sent_data) {
 			(unsigned long long ) ts->total_calls_duration_interval.tv_usec,
 			(unsigned long long ) g_now.tv_sec);
 
+	ilog(LOG_DEBUG, "Min/Max/Avg offer processing delay: %llu.%06llu/%llu.%06llu/%llu.%06llu sec",
+		(unsigned long long)ts->offer.time_min.tv_sec,(unsigned long long)ts->offer.time_min.tv_usec,
+		(unsigned long long)ts->offer.time_max.tv_sec,(unsigned long long)ts->offer.time_max.tv_usec,
+		(unsigned long long)ts->offer.time_avg.tv_sec,(unsigned long long)ts->offer.time_avg.tv_usec);
+	ilog(LOG_DEBUG, "Min/Max/Avg answer processing delay: %llu.%06llu/%llu.%06llu/%llu.%06llu sec",
+		(unsigned long long)ts->answer.time_min.tv_sec,(unsigned long long)ts->answer.time_min.tv_usec,
+		(unsigned long long)ts->answer.time_max.tv_sec,(unsigned long long)ts->answer.time_max.tv_usec,
+		(unsigned long long)ts->answer.time_avg.tv_sec,(unsigned long long)ts->answer.time_avg.tv_usec);
+	ilog(LOG_DEBUG, "Min/Max/Avg delete processing delay: %llu.%06llu/%llu.%06llu/%llu.%06llu sec",
+		(unsigned long long)ts->delete.time_min.tv_sec,(unsigned long long)ts->delete.time_min.tv_usec,
+		(unsigned long long)ts->delete.time_max.tv_sec,(unsigned long long)ts->delete.time_max.tv_usec,
+		(unsigned long long)ts->delete.time_avg.tv_sec,(unsigned long long)ts->delete.time_avg.tv_usec);
+
 	rc = write(graphite_sock.fd, data_to_send, ptr - data_to_send);
 	if (rc<0) {
 		ilog(LOG_ERROR,"Could not write to graphite socket. Disconnecting graphite server.");
diff --git a/daemon/main.c b/daemon/main.c
index fb859c2ef..7aa2765d3 100644
--- a/daemon/main.c
+++ b/daemon/main.c
@@ -65,8 +65,9 @@ endpoint_t redis_write_ep;
 static int tos;
 static int table = -1;
 static int no_fallback;
-static int timeout;
-static int silent_timeout;
+static unsigned int timeout;
+static unsigned int silent_timeout;
+static unsigned int final_timeout;
 static int port_min = 30000;
 static int port_max = 40000;
 static int max_sessions = -1;
@@ -290,6 +291,7 @@ static void options(int *argc, char ***argv) {
 		{ "tos",	'T', 0, G_OPTION_ARG_INT,	&tos,		"Default TOS value to set on streams",	"INT"		},
 		{ "timeout",	'o', 0, G_OPTION_ARG_INT,	&timeout,	"RTP timeout",			"SECS"		},
 		{ "silent-timeout",'s',0,G_OPTION_ARG_INT,	&silent_timeout,"RTP timeout for muted",	"SECS"		},
+		{ "final-timeout",'a',0,G_OPTION_ARG_INT,	&final_timeout,	"Call timeout",			"SECS"		},
 		{ "pidfile",	'p', 0, G_OPTION_ARG_FILENAME,	&pidfile,	"Write PID to file",		"FILE"		},
 		{ "foreground",	'f', 0, G_OPTION_ARG_NONE,	&foreground,	"Don't fork to background",	NULL		},
 		{ "port-min",	'm', 0, G_OPTION_ARG_INT,	&port_min,	"Lowest port to use for RTP",	"INT"		},
@@ -377,9 +379,13 @@ static void options(int *argc, char ***argv) {
 
 	if (timeout <= 0)
 		timeout = 60;
+
 	if (silent_timeout <= 0)
 		silent_timeout = 3600;
 
+	if (final_timeout <= 0)
+		final_timeout = 0;
+
 	if (redisps)
 		if (redis_ep_parse(&redis_ep, &redis_db, &redis_auth, "RTPENGINE_REDIS_AUTH_PW", redisps))
 			die("Invalid Redis endpoint [IP:PORT/INT] (--redis)");
@@ -545,6 +551,7 @@ no_kernel:
 	dtls_timer(ctx->p);
 
 	ZERO(mc);
+        rwlock_init(&mc.config_lock);
 	mc.kernelfd = kfd;
 	mc.kernelid = table;
 	if (max_sessions < -1) {
@@ -553,6 +560,7 @@ no_kernel:
 	mc.max_sessions = max_sessions;
 	mc.timeout = timeout;
 	mc.silent_timeout = silent_timeout;
+	mc.final_timeout = final_timeout;
 	mc.delete_delay = delete_delay;
 	mc.default_tos = tos;
 	mc.b2b_url = b2b_url;
diff --git a/daemon/media_socket.c b/daemon/media_socket.c
index b9e1e7328..4d719171b 100644
--- a/daemon/media_socket.c
+++ b/daemon/media_socket.c
@@ -1204,6 +1204,11 @@ loop_ok:
 	if (MEDIA_ISSET(media, ASYMMETRIC))
 		PS_SET(stream, CONFIRMED);
 
+	/* confirm sink for unidirectional streams in order to kernelize */
+	if (MEDIA_ISSET(media, UNIDIRECTIONAL)) {
+		PS_SET(sink, CONFIRMED);
+	}
+
 	/* if we have already updated the endpoint in the past ... */
 	if (PS_ISSET(stream, CONFIRMED)) {
 		/* see if we need to compare the source address with the known endpoint */
diff --git a/daemon/sdp.c b/daemon/sdp.c
index 454fac211..4b03276fe 100644
--- a/daemon/sdp.c
+++ b/daemon/sdp.c
@@ -1188,6 +1188,7 @@ int sdp_streams(const GQueue *sessions, GQueue *streams, struct sdp_ng_flags *fl
 			memcpy(sp->direction, flags->direction, sizeof(sp->direction));
 			sp->desired_family = flags->address_family;
 			bf_set_clear(&sp->sp_flags, SP_FLAG_ASYMMETRIC, flags->asymmetric);
+			bf_set_clear(&sp->sp_flags, SP_FLAG_UNIDIRECTIONAL, flags->unidirectional);
 			bf_set_clear(&sp->sp_flags, SP_FLAG_STRICT_SOURCE, flags->strict_source);
 			bf_set_clear(&sp->sp_flags, SP_FLAG_MEDIA_HANDOVER, flags->media_handover);
 
diff --git a/debian/ngcp-rtpengine-daemon.default b/debian/ngcp-rtpengine-daemon.default
index 161088ed8..a296a9c88 100644
--- a/debian/ngcp-rtpengine-daemon.default
+++ b/debian/ngcp-rtpengine-daemon.default
@@ -8,6 +8,7 @@ LISTEN_CLI=9900
 # INTERFACES="12.23.34.45!23.34.45.56"
 TIMEOUT=60
 SILENT_TIMEOUT=3600
+# FINAL_TIMEOUT=10800
 PIDFILE=/var/run/ngcp-rtpengine-daemon.pid
 FORK=yes
 # TOS=184
diff --git a/debian/ngcp-rtpengine-daemon.init b/debian/ngcp-rtpengine-daemon.init
index 688dc4b36..2f9426423 100755
--- a/debian/ngcp-rtpengine-daemon.init
+++ b/debian/ngcp-rtpengine-daemon.init
@@ -65,6 +65,7 @@ fi
 [ -z "$LISTEN_CLI" ] || OPTIONS="$OPTIONS --listen-cli=$LISTEN_CLI"
 [ -z "$TIMEOUT" ] || OPTIONS="$OPTIONS --timeout=$TIMEOUT"
 [ -z "$SILENT_TIMEOUT" ] || OPTIONS="$OPTIONS --silent-timeout=$SILENT_TIMEOUT"
+[ -z "$FINAL_TIMEOUT" ] || OPTIONS="$OPTIONS --final-timeout=$FINAL_TIMEOUT"
 [ -z "$PIDFILE" ] || OPTIONS="$OPTIONS --pidfile=$PIDFILE"
 [ -z "$TOS" ] || OPTIONS="$OPTIONS --tos=$TOS"
 [ -z "$PORT_MIN" ] || OPTIONS="$OPTIONS --port-min=$PORT_MIN"
diff --git a/utils/rtpengine-ctl b/utils/rtpengine-ctl
index a9a21a74b..b93204868 100755
--- a/utils/rtpengine-ctl
+++ b/utils/rtpengine-ctl
@@ -62,21 +62,25 @@ sub showusage {
     print "\n";
     print "    Supported commands are:\n";
     print "\n";
-    print "    list [ numsessions | maxsessions | maxopenfiles | sessions | session <callid> | totals ]\n";
+    print "    list [ numsessions | maxsessions | maxopenfiles | sessions | session <callid> | totals | timeout ]\n";
     print "         numsessions           : print the number of sessions\n";
     print "         maxsessions           : print the number of allowed sessions\n";
     print "         maxopenfiles          : print the number of allowed open files\n";
     print "         sessions              : print one-liner session information\n";
     print "         session <callid>      : print detail about one session\n";
     print "         totals                : print total statistics\n";
+    print "         timeout               : print timout parameters\n";
     print "\n";
     print "    terminate [ all | <callid> ]\n";
     print "         all                   : terminates all current sessions\n";
     print "         <callid>              : session is immediately terminated\n";
     print "\n";
-    print "    set [ maxopenfiles <uint> | maxsessions <int> ]\n";
+    print "    set [ maxsessions <int> | maxopenfiles <uint> | timeout <uint> | silent_timeout <uint> | final_timeout <uint>]\n";
     print "         maxsessions  <int>    : set the max nr of allowed sessions\n";
     print "         maxopenfiles <uint>   : set the max nr of allowed open files\n";
+    print "         timeout <uint>        : set the --timeout parameter \n";
+    print "         silenttimeout <uint>  : set the --silent-timeout parameter \n";
+    print "         finaltimeout <uint>   : set the --final-timeout parameter \n";
     print "\n";
     print "    ksadd [ keyspace <uint>]\n";
     print "         keyspace <uint>       : subscribe to 'keyspace' database\n";