From beb1d1792134f61a252538d45af909ee50771017 Mon Sep 17 00:00:00 2001
From: Matteo Brancaleoni <mbrancaleoni@voismart.it>
Date: Mon, 22 Sep 2014 10:44:53 +0200
Subject: [PATCH] FS-6400 Improve sip ping generation by distributing them
 across an interval

---
 src/mod/endpoints/mod_sofia/mod_sofia.h  |  5 ++
 src/mod/endpoints/mod_sofia/sofia.c      | 19 ++++++++
 src/mod/endpoints/mod_sofia/sofia_glue.c |  3 ++
 src/mod/endpoints/mod_sofia/sofia_reg.c  | 62 +++++++++++++++++++++---
 4 files changed, 83 insertions(+), 6 deletions(-)

diff --git a/src/mod/endpoints/mod_sofia/mod_sofia.h b/src/mod/endpoints/mod_sofia/mod_sofia.h
index f4e361318c..c15cbdc14d 100644
--- a/src/mod/endpoints/mod_sofia/mod_sofia.h
+++ b/src/mod/endpoints/mod_sofia/mod_sofia.h
@@ -43,6 +43,8 @@
 #define SQL_CACHE_TIMEOUT 300
 #define DEFAULT_NONCE_TTL 60
 #define IREG_SECONDS 30
+#define IPING_SECONDS 30
+#define IPING_FREQUENCY 1
 #define GATEWAY_SECONDS 1
 #define SOFIA_QUEUE_SIZE 50000
 #define HAVE_APR
@@ -720,6 +722,8 @@ struct sofia_profile {
 	uint32_t sip_expires_late_margin;
 	uint32_t sip_subscription_max_deviation;
 	int ireg_seconds;
+	int iping_seconds;
+	int iping_freq;
 	sofia_paid_type_t paid_type;
 	uint32_t rtp_digit_delay;
 	switch_queue_t *event_queue;
@@ -976,6 +980,7 @@ void sofia_glue_actually_execute_sql_trans(sofia_profile_t *profile, char *sql,
 void sofia_glue_execute_sql_now(sofia_profile_t *profile, char **sqlp, switch_bool_t sql_already_dynamic);
 void sofia_glue_execute_sql_soon(sofia_profile_t *profile, char **sqlp, switch_bool_t sql_already_dynamic);
 void sofia_reg_check_expire(sofia_profile_t *profile, time_t now, int reboot);
+void sofia_reg_check_ping_expire(sofia_profile_t *profile, time_t now, int interval);
 void sofia_reg_check_gateway(sofia_profile_t *profile, time_t now);
 void sofia_sub_check_gateway(sofia_profile_t *profile, time_t now);
 void sofia_reg_unregister(sofia_profile_t *profile);
diff --git a/src/mod/endpoints/mod_sofia/sofia.c b/src/mod/endpoints/mod_sofia/sofia.c
index 3c92207ebe..054b656db6 100644
--- a/src/mod/endpoints/mod_sofia/sofia.c
+++ b/src/mod/endpoints/mod_sofia/sofia.c
@@ -2574,6 +2574,7 @@ void *SWITCH_THREAD_FUNC sofia_profile_worker_thread_run(switch_thread_t *thread
 {
 	sofia_profile_t *profile = (sofia_profile_t *) obj;
 	uint32_t ireg_loops = profile->ireg_seconds;					/* Number of loop iterations done when we haven't checked for registrations */
+	uint32_t iping_loops = profile->iping_freq;					/* Number of loop iterations done when we haven't checked for ping expires */
 	uint32_t gateway_loops = GATEWAY_SECONDS;			/* Number of loop iterations done when we haven't checked for gateways */
 
 	sofia_set_pflag_locked(profile, PFLAG_WORKER_RUNNING);
@@ -2618,6 +2619,12 @@ void *SWITCH_THREAD_FUNC sofia_profile_worker_thread_run(switch_thread_t *thread
 				sofia_reg_check_expire(profile, now, 0);
 				ireg_loops = 0;
 			}
+	
+			if(++iping_loops >= (uint32_t)profile->iping_freq) {
+				time_t now = switch_epoch_time_now(NULL);
+				sofia_reg_check_ping_expire(profile, now, profile->iping_seconds);
+				iping_loops = 0;
+			}
 
 			if (++gateway_loops >= GATEWAY_SECONDS) {
 				sofia_reg_check_gateway(profile, switch_epoch_time_now(NULL));
@@ -4099,6 +4106,8 @@ switch_status_t config_sofia(sofia_config_t reload, char *profile_name)
 					profile->mndlb |= SM_NDLB_ALLOW_NONDUP_SDP;
 					profile->te = 101;
 					profile->ireg_seconds = IREG_SECONDS;
+					profile->iping_seconds = IPING_SECONDS;
+					profile->iping_freq = IPING_FREQUENCY;
 					profile->paid_type = PAID_DEFAULT;
 					profile->bind_attempts = 2;
 					profile->bind_attempt_interval = 5;
@@ -4229,6 +4238,16 @@ switch_status_t config_sofia(sofia_config_t reload, char *profile_name)
 						if (profile->ireg_seconds < 0) {
 							profile->ireg_seconds = IREG_SECONDS;
 						}
+					} else if (!strcasecmp(var, "ping-mean-interval")) {
+						profile->iping_seconds = atoi(val);
+						if (profile->iping_seconds < 0) {
+							profile->iping_seconds = IPING_SECONDS;
+						}
+					} else if (!strcasecmp(var, "ping-thread-frequency")) {
+						profile->iping_freq = atoi(val);
+						if (profile->iping_freq < 0) {
+							profile->iping_freq = IPING_FREQUENCY;
+						}
 					} else if (!strcasecmp(var, "user-agent-string")) {
 						profile->user_agent = switch_core_strdup(profile->pool, val);
 					} else if (!strcasecmp(var, "auto-restart")) {
diff --git a/src/mod/endpoints/mod_sofia/sofia_glue.c b/src/mod/endpoints/mod_sofia/sofia_glue.c
index 0773e45b50..3ed3e42e14 100644
--- a/src/mod/endpoints/mod_sofia/sofia_glue.c
+++ b/src/mod/endpoints/mod_sofia/sofia_glue.c
@@ -1993,6 +1993,7 @@ int sofia_glue_init_sql(sofia_profile_t *profile)
 		"   ping_count       INTEGER,\n"
 		"   rpid             VARCHAR(255),\n"
 		"   expires          BIGINT,\n"
+		"   ping_expires     INTEGER not null default 0,\n"
 		"   user_agent       VARCHAR(255),\n"
 		"   server_user      VARCHAR(255),\n"
 		"   server_host      VARCHAR(255),\n"
@@ -2121,6 +2122,7 @@ int sofia_glue_init_sql(sofia_profile_t *profile)
 		"create index sr_presence_hosts on sip_registrations (presence_hosts)",
 		"create index sr_contact on sip_registrations (contact)",
 		"create index sr_expires on sip_registrations (expires)",
+		"create index sr_ping_expires on sip_registrations (ping_expires)",
 		"create index sr_hostname on sip_registrations (hostname)",
 		"create index sr_status on sip_registrations (status)",
 		"create index sr_ping_status on sip_registrations (ping_status)",
@@ -2205,6 +2207,7 @@ int sofia_glue_init_sql(sofia_profile_t *profile)
 
 	switch_cache_db_test_reactive(dbh, "select ping_count from sip_registrations", NULL, "alter table sip_registrations add column ping_count INTEGER default 0");
 	switch_cache_db_test_reactive(dbh, "select ping_status from sip_registrations", NULL, "alter table sip_registrations add column ping_status VARCHAR(255) default \"Reachable\"");
+	switch_cache_db_test_reactive(dbh, "select ping_expires from sip_registrations", NULL, "alter table sip_registrations add column ping_expires INTEGER not null default 0");
 	
 	test2 = switch_mprintf("%s;%s", test_sql, test_sql);
 			
diff --git a/src/mod/endpoints/mod_sofia/sofia_reg.c b/src/mod/endpoints/mod_sofia/sofia_reg.c
index d4177195d1..108aaf54d1 100644
--- a/src/mod/endpoints/mod_sofia/sofia_reg.c
+++ b/src/mod/endpoints/mod_sofia/sofia_reg.c
@@ -846,21 +846,45 @@ void sofia_reg_check_expire(sofia_profile_t *profile, time_t now, int reboot)
 
 	sofia_glue_execute_sql(profile, &sql, SWITCH_TRUE);
 
+}
+
+long sofia_reg_uniform_distribution(int max)
+{
+/* 
+ * Generate a random number following a uniform distribution between 0 and max
+ */
+	int result;
+	int range = max + 1;
+
+	srand((unsigned) switch_thread_self() + switch_micro_time_now());
+	result = (int)((double)rand() / (((double)RAND_MAX + (double)1) / range));
+
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG9, "Generated random %ld, max is %d\n", (long) result, max);
+	return (long) result;
+}
+
+void sofia_reg_check_ping_expire(sofia_profile_t *profile, time_t now, int interval)
+{
+	char *sql;
+	int mean = interval / 2;
+	long next, irand;
 
 	if (now) {
 		if (sofia_test_pflag(profile, PFLAG_ALL_REG_OPTIONS_PING)) {
 			sql = switch_mprintf("select call_id,sip_user,sip_host,contact,status,rpid,"
 							"expires,user_agent,server_user,server_host,profile_name"
  " from sip_registrations where hostname='%s' and " 
- "profile_name='%s' and orig_hostname='%s'", mod_sofia_globals.hostname, profile->name, mod_sofia_globals.hostname); 
+ "profile_name='%s' and orig_hostname='%s' and "
+ "ping_expires > 0 and ping_expires <= %ld", mod_sofia_globals.hostname, profile->name, mod_sofia_globals.hostname, (long) now); 
 			
 			sofia_glue_execute_sql_callback(profile, profile->dbh_mutex, sql, sofia_reg_nat_callback, profile);
 			switch_safe_free(sql);
 		} else if (sofia_test_pflag(profile, PFLAG_UDP_NAT_OPTIONS_PING)) {
-			sql = switch_mprintf("select call_id,sip_user,sip_host,contact,status,rpid,"
-							"expires,user_agent,server_user,server_host,profile_name"
-							" from sip_registrations where status like '%%UDP-NAT%%' "
- "and hostname='%s' and profile_name='%s'", mod_sofia_globals.hostname, profile->name); 
+			sql = switch_mprintf(	" select call_id,sip_user,sip_host,contact,status,rpid, "
+						" expires,user_agent,server_user,server_host,profile_name "
+						" from sip_registrations where status like '%%UDP-NAT%%' "
+ 						" and hostname='%s' and profile_name='%s' and ping_expires > 0 and ping_expires <= %ld ",
+						mod_sofia_globals.hostname, profile->name, (long) now); 
 			
 			sofia_glue_execute_sql_callback(profile, profile->dbh_mutex, sql, sofia_reg_nat_callback, profile);
 			switch_safe_free(sql);
@@ -869,11 +893,37 @@ void sofia_reg_check_expire(sofia_profile_t *profile, time_t now, int reboot)
 							"expires,user_agent,server_user,server_host,profile_name"
 							" from sip_registrations where (status like '%%NAT%%' "
  "or contact like '%%fs_nat=yes%%') and hostname='%s' " 
- "and profile_name='%s' and orig_hostname='%s'", mod_sofia_globals.hostname, profile->name, mod_sofia_globals.hostname); 
+ "and profile_name='%s' and orig_hostname='%s' and "
+ "ping_expires > 0 and ping_expires <= %ld", mod_sofia_globals.hostname, profile->name, mod_sofia_globals.hostname, (long) now); 
 			
 			sofia_glue_execute_sql_callback(profile, profile->dbh_mutex, sql, sofia_reg_nat_callback, profile);
 			switch_safe_free(sql);
 		}
+
+		if (sofia_test_pflag(profile, PFLAG_ALL_REG_OPTIONS_PING) ||
+			sofia_test_pflag(profile, PFLAG_UDP_NAT_OPTIONS_PING) || 
+			sofia_test_pflag(profile, PFLAG_NAT_OPTIONS_PING)) {
+			char buf[32] = "";
+			int count;
+
+			sql = switch_mprintf("select count(*) from sip_registrations where hostname='%q' and profile_name='%q' and ping_expires <= %ld",
+                                      		mod_sofia_globals.hostname, profile->name, (long) now);
+
+        		sofia_glue_execute_sql2str(profile, profile->dbh_mutex, sql, buf, sizeof(buf));
+        		switch_safe_free(sql);
+        		count = atoi(buf);
+
+			/* only update if needed */
+			if (count) {
+				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG9, "Updating ping expires for profile %s\n", profile->name);
+				irand = mean + sofia_reg_uniform_distribution(interval);
+				next = (long) now + irand;
+	
+				sql = switch_mprintf(" update sip_registrations set ping_expires = %ld where ping_expires <= %ld ",
+							next, (long) now);
+				sofia_glue_execute_sql(profile, &sql, SWITCH_TRUE);
+			}
+		}
 	}
 
 }