Przeglądaj źródła

Merge branch 'master' of https://github.com/kamailio/kamailio.git

Robert Boisvert 4 lat temu
rodzic
commit
1d456523e7
100 zmienionych plików z 8371 dodań i 745 usunięć
  1. 2 2
      etc/kamailio.cfg
  2. 14 0
      misc/fuzz/README.md
  3. 8 0
      misc/fuzz/fuzz_uri.c
  4. 2 0
      pkg/kamailio/obs/kamailio.spec
  5. 15 1
      src/Makefile.groups
  6. 6 0
      src/core/cfg.lex
  7. 9 0
      src/core/cfg.y
  8. 5 0
      src/core/globals.h
  9. 27 10
      src/core/resolve.c
  10. 4 4
      src/core/str_list.h
  11. 3 3
      src/core/tcp_init.h
  12. 31 7
      src/core/tcp_main.c
  13. 57 2
      src/main.c
  14. 1 0
      src/modules/carrierroute/cr_data.c
  15. 13 34
      src/modules/carrierroute/cr_db.c
  16. 0 5
      src/modules/carrierroute/cr_db.h
  17. 1 0
      src/modules/carrierroute/cr_domain.h
  18. 39 2
      src/modules/corex/corex_mod.c
  19. 1 1
      src/modules/cplc/cpl_run.c
  20. 53 23
      src/modules/ctl/binrpc_run.c
  21. 38 19
      src/modules/ctl/fifo_server.c
  22. 2 2
      src/modules/ctl/io_listener.c
  23. 41 40
      src/modules/dialplan/dp_db.c
  24. 50 50
      src/modules/dispatcher/dispatch.c
  25. 5 5
      src/modules/dispatcher/dispatcher.c
  26. 4 0
      src/modules/dmq/README
  27. 30 13
      src/modules/dmq/dmq.c
  28. 19 2
      src/modules/dmq/dmq_funcs.c
  29. 17 2
      src/modules/dmq/dmqnode.c
  30. 4 0
      src/modules/dmq/doc/dmq_admin.xml
  31. 4 3
      src/modules/dmq/notification_peer.c
  32. 4 2
      src/modules/dmq/worker.c
  33. 2 2
      src/modules/htable/htable.c
  34. 6 6
      src/modules/ims_charging/Ro_data.h
  35. 12 3
      src/modules/ims_diameter_server/ims_diameter_server.c
  36. 25 0
      src/modules/jwt/Makefile
  37. 176 0
      src/modules/jwt/README
  38. 4 0
      src/modules/jwt/doc/Makefile
  39. 37 0
      src/modules/jwt/doc/jwt.xml
  40. 217 0
      src/modules/jwt/doc/jwt_admin.xml
  41. 514 0
      src/modules/jwt/jwt_mod.c
  42. 14 0
      src/modules/lrkproxy/Makefile
  43. 318 0
      src/modules/lrkproxy/README
  44. 4 0
      src/modules/lrkproxy/doc/Makefile
  45. 33 0
      src/modules/lrkproxy/doc/lrkproxy.xml
  46. 366 0
      src/modules/lrkproxy/doc/lrkproxy_admin.xml
  47. 1733 0
      src/modules/lrkproxy/lrkproxy.c
  48. 107 0
      src/modules/lrkproxy/lrkproxy.h
  49. 479 0
      src/modules/lrkproxy/lrkproxy_funcs.c
  50. 41 0
      src/modules/lrkproxy/lrkproxy_funcs.h
  51. 522 0
      src/modules/lrkproxy/lrkproxy_hash.c
  52. 75 0
      src/modules/lrkproxy/lrkproxy_hash.h
  53. 12 10
      src/modules/mangler/contact_ops.c
  54. 5 0
      src/modules/ndb_redis/redis_client.c
  55. 25 6
      src/modules/pipelimit/README
  56. 24 0
      src/modules/pipelimit/doc/pipelimit_admin.xml
  57. 38 0
      src/modules/pipelimit/pipelimit.c
  58. 2 2
      src/modules/pipelimit/pl_ht.c
  59. 6 1
      src/modules/presence/presence_dmq.c
  60. 63 22
      src/modules/pv/pv_branch.c
  61. 3 2
      src/modules/pv_headers/pvh_func.c
  62. 1 1
      src/modules/pv_headers/pvh_hash.c
  63. 42 8
      src/modules/pv_headers/pvh_str.c
  64. 3 2
      src/modules/pv_headers/pvh_str.h
  65. 66 47
      src/modules/registrar/README
  66. 25 1
      src/modules/registrar/doc/registrar_admin.xml
  67. 0 1
      src/modules/rtp_media_server/Makefile
  68. 6 3
      src/modules/siputils/contact_ops.c
  69. 25 1
      src/modules/smsops/smsops_impl.c
  70. 28 0
      src/modules/stirshaken/Makefile
  71. 489 0
      src/modules/stirshaken/README
  72. 4 0
      src/modules/stirshaken/doc/Makefile
  73. 42 0
      src/modules/stirshaken/doc/stirshaken.xml
  74. 471 0
      src/modules/stirshaken/doc/stirshaken_admin.xml
  75. 981 0
      src/modules/stirshaken/stirshaken_mod.c
  76. 302 278
      src/modules/textops/README
  77. 40 0
      src/modules/textops/doc/textops_admin.xml
  78. 64 0
      src/modules/textops/textops.c
  79. 3 1
      src/modules/tm/t_cancel.c
  80. 12 3
      src/modules/topos/README
  81. 9 0
      src/modules/topos/doc/topos.xml
  82. 5 2
      src/modules/topos/doc/topos_admin.xml
  83. 0 4
      src/modules/topos/topos_mod.c
  84. 5 5
      src/modules/topos/tps_msg.c
  85. 33 8
      src/modules/topos/tps_storage.c
  86. 2 0
      src/modules/topos/tps_storage.h
  87. 6 0
      src/modules/topos_redis/README
  88. 9 0
      src/modules/topos_redis/doc/topos_redis.xml
  89. 63 12
      src/modules/topos_redis/topos_redis_storage.c
  90. 113 66
      src/modules/uac/README
  91. 51 0
      src/modules/uac/doc/uac_admin.xml
  92. 2 0
      src/modules/uac/uac.c
  93. 27 9
      src/modules/uac_redirect/README
  94. 22 0
      src/modules/uac_redirect/doc/uac_redirect_admin.xml
  95. 2 2
      src/modules/uac_redirect/rd_funcs.c
  96. 4 0
      src/modules/uac_redirect/uac_redirect.c
  97. 16 0
      src/modules/usrloc/README
  98. 19 0
      src/modules/usrloc/doc/usrloc_admin.xml
  99. 4 4
      src/modules/usrloc/ul_rpc.c
  100. 3 1
      src/modules/usrloc/usrloc_mod.c

+ 2 - 2
etc/kamailio.cfg

@@ -851,9 +851,9 @@ route[NATMANAGE] {
 
 #!ifdef WITH_RTPENGINE
 	if(nat_uac_test("8")) {
-		rtpengine_manage("replace-origin replace-session-connection");
+		rtpengine_manage("SIP-source-address replace-origin replace-session-connection");
 	} else {
-		rtpengine_manage("trust-address replace-origin replace-session-connection");
+		rtpengine_manage("replace-origin replace-session-connection");
 	}
 #!else
 	if(nat_uac_test("8")) {

+ 14 - 0
misc/fuzz/README.md

@@ -0,0 +1,14 @@
+# Kamailio OSS Fuzz Integration #
+
+OSS-Fuzz is a free service run by Google that performs continuous fuzzing of
+various open source projects.
+
+  * https://github.com/google/oss-fuzz
+
+OSS-Fuzz pull request to integrate Kamailio:
+
+  * https://github.com/google/oss-fuzz/pull/5279
+
+Initial pull request in Kamailio, with additional details:
+
+  * https://github.com/kamailio/kamailio/pull/2660

+ 8 - 0
misc/fuzz/fuzz_uri.c

@@ -0,0 +1,8 @@
+#include "../parser/parse_uri.c"
+
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    struct sip_uri uri;
+    parse_uri(data, size, &uri);
+    return 0;
+}

+ 2 - 0
pkg/kamailio/obs/kamailio.spec

@@ -1448,6 +1448,7 @@ fi
 %doc %{_docdir}/kamailio/modules/README.ipops
 %doc %{_docdir}/kamailio/modules/README.kemix
 %doc %{_docdir}/kamailio/modules/README.kex
+%doc %{_docdir}/kamailio/modules/README.lrkproxy
 %doc %{_docdir}/kamailio/modules/README.malloc_test
 %doc %{_docdir}/kamailio/modules/README.mangler
 %doc %{_docdir}/kamailio/modules/README.matrix
@@ -1606,6 +1607,7 @@ fi
 %{_libdir}/kamailio/modules/ipops.so
 %{_libdir}/kamailio/modules/kemix.so
 %{_libdir}/kamailio/modules/kex.so
+%{_libdir}/kamailio/modules/lrkproxy.so
 %{_libdir}/kamailio/modules/malloc_test.so
 %{_libdir}/kamailio/modules/mangler.so
 %{_libdir}/kamailio/modules/matrix.so

+ 15 - 1
src/Makefile.groups

@@ -23,7 +23,7 @@ mod_list_extra=avp auth_diameter call_control call_obj dmq domainpolicy msrp \
 			carrierroute pdb qos sca seas sms sst timer tmrec uac_redirect \
 			xhttp xhttp_rpc xprint jsonrpcs nosip dmq_usrloc statsd rtjson \
 			log_custom keepalive ss7ops app_sqlang acc_diameter evrexec \
-			sipjson xhttp_prom
+			sipjson lrkproxy xhttp_prom
 
 # - common modules depending on database
 mod_list_db=acc alias_db auth_db avpops cfg_db db_text db_flatstore \
@@ -184,6 +184,12 @@ mod_list_uuid=uuid
 # - modules depending on ev library
 mod_list_ev=evapi
 
+# - modules depending on libjwt library
+mod_list_jwt=jwt
+
+# - modules depending on libstirshaken library
+mod_list_stirshaken=stirshaken
+
 # - modules depending on kazoo/rabbitmq
 mod_list_kazoo=kazoo
 
@@ -256,6 +262,8 @@ mod_list_all=$(sort $(mod_list_basic) $(mod_list_extra) \
 			   $(mod_list_kafka) \
 			   $(mod_list_mqtt) \
 			   $(mod_list_secsipid) \
+			   $(mod_list_jwt) \
+			   $(mod_list_stirshaken) \
 			   $(mod_list_rtp_media_server)
 
 
@@ -446,6 +454,12 @@ module_group_kuuid=$(mod_list_uuid)
 # pkg libev modules
 module_group_kev=$(mod_list_ev)
 
+# pkg jwt module
+module_group_kjwt=$(mod_list_jwt)
+
+# pkg stirshaken module
+module_group_kstirshaken=$(mod_list_stirshaken)
+
 # pkg kazoo module
 module_group_kkazoo=$(mod_list_kazoo)
 

+ 6 - 0
src/core/cfg.lex

@@ -467,6 +467,9 @@ VERBOSE_STARTUP		"verbose_startup"
 
 SERVER_ID     "server_id"
 ROUTE_LOCKS_SIZE     "route_locks_size"
+WAIT_WORKER1_MODE     "wait_worker1_mode"
+WAIT_WORKER1_TIME     "wait_worker1_time"
+WAIT_WORKER1_USLEEP   "wait_worker1_usleep"
 
 KEMI     "kemi"
 ONSEND_ROUTE_CALLBACK	"onsend_route_callback"
@@ -989,6 +992,9 @@ IMPORTFILE      "import_file"
 <INITIAL>{VERBOSE_STARTUP}		{	count(); yylval.strval=yytext;
 									return VERBOSE_STARTUP; }
 <INITIAL>{ROUTE_LOCKS_SIZE}  { count(); yylval.strval=yytext; return ROUTE_LOCKS_SIZE; }
+<INITIAL>{WAIT_WORKER1_MODE}  { count(); yylval.strval=yytext; return WAIT_WORKER1_MODE; }
+<INITIAL>{WAIT_WORKER1_TIME}  { count(); yylval.strval=yytext; return WAIT_WORKER1_TIME; }
+<INITIAL>{WAIT_WORKER1_USLEEP}  { count(); yylval.strval=yytext; return WAIT_WORKER1_USLEEP; }
 <INITIAL>{SERVER_ID}  { count(); yylval.strval=yytext; return SERVER_ID;}
 <INITIAL>{KEMI}  { count(); yylval.strval=yytext; return KEMI;}
 <INITIAL>{REPLY_ROUTE_CALLBACK}  { count(); yylval.strval=yytext; return REPLY_ROUTE_CALLBACK;}

+ 9 - 0
src/core/cfg.y

@@ -498,6 +498,9 @@ extern char *default_routename;
 %token VERSION_TABLE_CFG
 %token VERBOSE_STARTUP
 %token ROUTE_LOCKS_SIZE
+%token WAIT_WORKER1_MODE
+%token WAIT_WORKER1_TIME
+%token WAIT_WORKER1_USLEEP
 %token CFG_DESCRIPTION
 %token SERVER_ID
 %token KEMI
@@ -1702,6 +1705,12 @@ assign_stm:
 	| VERBOSE_STARTUP EQUAL error { yyerror("boolean value expected"); }
 	| ROUTE_LOCKS_SIZE EQUAL NUMBER { ksr_route_locks_size=$3; }
 	| ROUTE_LOCKS_SIZE EQUAL error { yyerror("number expected"); }
+	| WAIT_WORKER1_MODE EQUAL NUMBER { ksr_wait_worker1_mode=$3; }
+	| WAIT_WORKER1_MODE EQUAL error { yyerror("number expected"); }
+	| WAIT_WORKER1_TIME EQUAL NUMBER { ksr_wait_worker1_time=$3; }
+	| WAIT_WORKER1_TIME EQUAL error { yyerror("number expected"); }
+	| WAIT_WORKER1_USLEEP EQUAL NUMBER { ksr_wait_worker1_usleep=$3; }
+	| WAIT_WORKER1_USLEEP EQUAL error { yyerror("number expected"); }
     | SERVER_ID EQUAL NUMBER { server_id=$3; }
 	| SERVER_ID EQUAL error  { yyerror("number expected"); }
 	| KEMI DOT ONSEND_ROUTE_CALLBACK EQUAL STRING {

+ 5 - 0
src/core/globals.h

@@ -218,6 +218,11 @@ extern str _ksr_xavp_via_fields;
 extern int ksr_sip_parser_mode;
 extern int ksr_cfg_print_mode;
 
+extern int ksr_wait_worker1_mode;
+extern int ksr_wait_worker1_time;
+extern int ksr_wait_worker1_usleep;
+extern int *ksr_wait_worker1_done;
+
 extern char *_sr_uri_host_extra_chars;
 extern unsigned char *_ksr_hname_extra_chars;
 

+ 27 - 10
src/core/resolve.c

@@ -57,6 +57,20 @@
 #include "dns_cache.h"
 #endif
 
+#define KSR_IPADDR_LIST_SIZE 6
+static ip_addr_t _ksr_ipaddr_list[KSR_IPADDR_LIST_SIZE];
+static int _ksr_ipaddr_list_idx = 0;
+
+static ip_addr_t* get_next_ipaddr_buf(void)
+{
+	ip_addr_t *ipb;
+
+	ipb = &_ksr_ipaddr_list[_ksr_ipaddr_list_idx];
+	_ksr_ipaddr_list_idx = (_ksr_ipaddr_list_idx + 1) % KSR_IPADDR_LIST_SIZE;
+
+	return ipb;
+}
+
 /* counters framework */
 struct dns_counters_h dns_cnts_h;
 counter_def_t dns_cnt_defs[] =  {
@@ -1780,13 +1794,14 @@ error_dots:
    Warning: the result is a pointer to a statically allocated structure */
 ip_addr_t* str2ip(str* st)
 {
-	static ip_addr_t ip;
+	ip_addr_t *ipb;
 
-	if(str2ipbuf(st, &ip)<0) {
+	ipb = get_next_ipaddr_buf();
+	if(str2ipbuf(st, ipb)<0) {
 		return NULL;
 	}
 
-	return &ip;
+	return ipb;
 }
 
 /* converts a str to an ipv6 address struct stored in ipb
@@ -1895,13 +1910,14 @@ error_char:
  * the ip_addr struct is static, so subsequent calls will destroy its content*/
 ip_addr_t* str2ip6(str* st)
 {
-	static ip_addr_t ip;
+	ip_addr_t *ipb;
 
-	if(str2ip6buf(st, &ip)<0) {
+	ipb = get_next_ipaddr_buf();
+	if(str2ip6buf(st, ipb)<0) {
 		return NULL;
 	}
 
-	return &ip;
+	return ipb;
 }
 
 /* converts a str to an ipvv/6 address struct stored in ipb
@@ -1922,13 +1938,14 @@ int str2ipxbuf(str* st, ip_addr_t* ipb)
  * the ip_addr struct is static, so subsequent calls will destroy its content*/
 struct ip_addr* str2ipx(str* st)
 {
-	static ip_addr_t ip;
+	ip_addr_t *ipb;
 
-	if(str2ipbuf(st, &ip)<0) {
-		if(str2ip6buf(st, &ip)<0) {
+	ipb = get_next_ipaddr_buf();
+	if(str2ipbuf(st, ipb)<0) {
+		if(str2ip6buf(st, ipb)<0) {
 			return NULL;
 		}
 	}
 
-	return &ip;
+	return ipb;
 }

+ 4 - 4
src/core/str_list.h

@@ -20,7 +20,7 @@
  */
 
 /**
- * @file 
+ * @file
  * @brief Kamailio core :: Simple str type list and helper functions
  */
 
@@ -32,15 +32,15 @@
 /**
  * @brief Simple str type list
  */
-struct str_list {
+typedef struct str_list {
 	str s;
 	struct str_list *next;
-};
+} str_list_t;
 
 
 /**
  * @brief Add a new allocated list element to an existing list
- * 
+ *
  * Add a new allocated list element to an existing list, the allocation is done
  * from the private memory pool
  * @param s input character

+ 3 - 3
src/core/tcp_init.h

@@ -13,8 +13,8 @@
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
- * You should have received a copy of the GNU General Public License 
- * along with this program; if not, write to the Free Software 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
@@ -54,7 +54,7 @@ struct tcp_child{
 int init_tcp(void);
 void destroy_tcp(void);
 int tcp_init(struct socket_info* sock_info);
-int tcp_init_children(void);
+int tcp_init_children(int *woneinit);
 void tcp_main_loop(void);
 void tcp_receive_loop(int unix_sock);
 int tcp_fix_child_sockets(int* fd);

+ 31 - 7
src/core/tcp_main.c

@@ -5034,16 +5034,16 @@ int tcp_fix_child_sockets(int* fd)
 
 
 /* starts the tcp processes */
-int tcp_init_children()
+int tcp_init_children(int *woneinit)
 {
 	int r, i;
 	int reader_fd_1; /* for comm. with the tcp children read  */
 	pid_t pid;
 	char si_desc[MAX_PT_DESC];
 	struct socket_info *si;
-	
+
 	/* estimate max fd. no:
-	 * 1 tcp send unix socket/all_proc, 
+	 * 1 tcp send unix socket/all_proc,
 	 *  + 1 udp sock/udp proc + 1 tcp_child sock/tcp child*
 	 *  + no_listen_tcp */
 	for(r=0, si=tcp_listen; si; si=si->next, r++);
@@ -5051,12 +5051,12 @@ int tcp_init_children()
 	if (! tls_disable)
 		for (si=tls_listen; si; si=si->next, r++);
 #endif
-	
+
 	register_fds(r+tcp_max_connections+get_max_procs()-1 /* tcp main */);
 #if 0
 	tcp_max_fd_no=get_max_procs()*2 +r-1 /* timer */ +3; /* stdin/out/err*/
 	/* max connections can be temporarily exceeded with estimated_process_count
-	 * - tcp_main (tcpconn_connect called simultaneously in all all the 
+	 * - tcp_main (tcpconn_connect called simultaneously in all all the
 	 *  processes) */
 	tcp_max_fd_no+=tcp_max_connections+get_max_procs()-1 /* tcp main */;
 #endif
@@ -5095,7 +5095,7 @@ int tcp_init_children()
 
 	/* create the tcp sock_info structures */
 	/* copy the sockets --moved to main_loop*/
-	
+
 	/* fork children & create the socket pairs*/
 	for(r=0; r<tcp_children_no; r++){
 		child_rank++;
@@ -5107,11 +5107,35 @@ int tcp_init_children()
 			LM_ERR("fork failed: %s\n", strerror(errno));
 			goto error;
 		}else if (pid>0){
-			/* parent */
+			/* parent - main process */
+			if(*woneinit==0 && ksr_wait_worker1_mode!=0) {
+				int wcount=0;
+				while(*ksr_wait_worker1_done==0) {
+					sleep_us(ksr_wait_worker1_usleep);
+					wcount++;
+					if(ksr_wait_worker1_time<=wcount*ksr_wait_worker1_usleep) {
+						LM_ERR("waiting for child one too long - wait time: %d\n",
+								ksr_wait_worker1_time);
+						goto error;
+					}
+				}
+				LM_DBG("child one initialized after %d wait steps\n",
+							wcount);
+			}
+			*woneinit = 1;
 		}else{
 			/* child */
 			bind_address=0; /* force a SEGFAULT if someone uses a non-init.
 							   bind address on tcp */
+			if(*woneinit==0) {
+				if(run_child_one_init_route()<0)
+					goto error;
+			}
+			if(ksr_wait_worker1_mode!=0) {
+				*ksr_wait_worker1_done = 1;
+				LM_DBG("child one finished initialization\n");
+			}
+
 			tcp_receive_loop(reader_fd_1);
 		}
 	}

+ 57 - 2
src/main.c

@@ -156,7 +156,6 @@
 #endif
 
 
-
 static char help_msg[]= "\
 Usage: " NAME " [options]\n\
 Options:\n\
@@ -541,6 +540,11 @@ static int *_sr_instance_started = NULL;
 int ksr_cfg_print_mode = 0;
 int ksr_atexit_mode = 1;
 
+int ksr_wait_worker1_mode = 0;
+int ksr_wait_worker1_time = 4000000;
+int ksr_wait_worker1_usleep = 100000;
+int *ksr_wait_worker1_done = NULL;
+
 /**
  * return 1 if all child processes were forked
  * - note: they might still be in init phase (i.e., child init)
@@ -1651,6 +1655,14 @@ int main_loop(void)
 
 
 		woneinit = 0;
+		if(ksr_wait_worker1_mode!=0) {
+			ksr_wait_worker1_done=(int*)shm_malloc(sizeof(int));
+			if(ksr_wait_worker1_done==0) {
+				SHM_MEM_ERROR;
+				goto error;
+			}
+			*ksr_wait_worker1_done = 0;
+		}
 		/* udp processes */
 		for(si=udp_listen; si; si=si->next){
 			nrprocs = (si->workers>0)?si->workers:children_no;
@@ -1689,8 +1701,27 @@ int main_loop(void)
 						if(run_child_one_init_route()<0)
 							goto error;
 					}
+					if(ksr_wait_worker1_mode!=0) {
+						*ksr_wait_worker1_done = 1;
+						LM_DBG("child one finished initialization\n");
+					}
 					return udp_rcv_loop();
 				}
+				/* main process */
+				if(woneinit==0 && ksr_wait_worker1_mode!=0) {
+					int wcount=0;
+					while(*ksr_wait_worker1_done==0) {
+						sleep_us(ksr_wait_worker1_usleep);
+						wcount++;
+						if(ksr_wait_worker1_time<=wcount*ksr_wait_worker1_usleep) {
+							LM_ERR("waiting for child one too long - wait time: %d\n",
+									ksr_wait_worker1_time);
+							goto error;
+						}
+					}
+					LM_DBG("child one initialized after %d wait steps\n",
+							wcount);
+				}
 				woneinit = 1;
 			}
 			/*parent*/
@@ -1720,8 +1751,32 @@ int main_loop(void)
 						/* child */
 						bind_address=si; /* shortcut */
 
+						if(woneinit==0) {
+							if(run_child_one_init_route()<0)
+								goto error;
+						}
+						if(ksr_wait_worker1_mode!=0) {
+							*ksr_wait_worker1_done = 1;
+							LM_DBG("child one finished initialization\n");
+						}
 						return sctp_core_rcv_loop();
 					}
+					/* main process */
+					if(woneinit==0 && ksr_wait_worker1_mode!=0) {
+						int wcount=0;
+						while(*ksr_wait_worker1_done==0) {
+							sleep_us(ksr_wait_worker1_usleep);
+							wcount++;
+							if(ksr_wait_worker1_time<=wcount*ksr_wait_worker1_usleep) {
+								LM_ERR("waiting for child one too long - wait time: %d\n",
+										ksr_wait_worker1_time);
+								goto error;
+							}
+						}
+						LM_DBG("child one initialized after %d wait steps\n",
+								wcount);
+					}
+					woneinit = 1;
 				}
 			/*parent*/
 			/*close(sctp_sock)*/; /*if closed=>sendto invalid fd errors?*/
@@ -1777,7 +1832,7 @@ int main_loop(void)
 #ifdef USE_TCP
 		if (!tcp_disable){
 				/* start tcp  & tls receivers */
-			if (tcp_init_children()<0) goto error;
+			if (tcp_init_children(&woneinit)<0) goto error;
 				/* start tcp+tls main attendant proc */
 			pid = fork_process(PROC_TCP_MAIN, "tcp main process", 0);
 			if (pid<0){

+ 1 - 0
src/modules/carrierroute/cr_data.c

@@ -464,6 +464,7 @@ int add_route(struct route_data_t * rd, int carrier_id,
 		LM_ERR("could not retrieve domain data\n");
 		return -1;
 	}
+	domain_data->sum_prob = domain_data->sum_prob + prob;
 
 	LM_INFO("found carrier and domain, now adding route\n");
 	return add_route_to_tree(domain_data->tree, scan_prefix, flags, mask, scan_prefix, max_targets, prob, rewrite_hostpart,

+ 13 - 34
src/modules/carrierroute/cr_db.c

@@ -31,6 +31,7 @@
 #include "carrierroute.h"
 #include "cr_db.h"
 #include "cr_carrier.h"
+#include "cr_domain.h"
 #include "config.h"
 #include <stdio.h>
 #include <stdlib.h>
@@ -265,9 +266,8 @@ int load_user_carrier(str * user, str * domain) {
  */
 int load_route_data_db(struct route_data_t * rd) {
 	db1_res_t * res = NULL;
-	db1_res_t * prob_res = NULL;
 	db_row_t * row = NULL;
-	int i, ret;
+	int i, j, ret;
 	struct carrier_data_t * tmp_carrier_data;
 	static str query_str;
 	str tmp_scan_prefix, tmp_rewrite_host, tmp_rewrite_prefix,
@@ -353,7 +353,6 @@ int load_route_data_db(struct route_data_t * rd) {
 		}
 	}
 	int n = 0;
-	crboolean query_done = crfalse;
 	do {
 		LM_DBG("loading, cycle %d", n++);
 		for (i = 0; i < RES_ROW_N(res); ++i) {
@@ -379,6 +378,7 @@ int load_route_data_db(struct route_data_t * rd) {
 				p_tmp_comment = &tmp_comment;
 			}
 
+
 			if (add_route(rd,
 					row->values[COL_CARRIER].val.int_val,
 					row->values[COL_DOMAIN].val.int_val,
@@ -398,34 +398,6 @@ int load_route_data_db(struct route_data_t * rd) {
 					p_tmp_comment) == -1) {
 				goto errout;
 			}
-			if (row->values[COL_PROB].val.double_val == 0 && !query_done) {
-				int ret_tmp;
-				char query_tmp[QUERY_LEN];
-				str query_tmp_str;
-
-				memset(query_tmp, 0, QUERY_LEN);
-				ret_tmp = snprintf(query_tmp, QUERY_LEN, "SELECT * FROM %.*s WHERE %.*s=%d and %.*s=%d and %.*s>%d",
-						carrierroute_table.len, carrierroute_table.s, columns[COL_CARRIER]->len, columns[COL_CARRIER]->s, row->values[COL_CARRIER].val.int_val,
-						columns[COL_DOMAIN]->len, columns[COL_DOMAIN]->s, row->values[COL_DOMAIN].val.int_val, columns[COL_PROB]->len, columns[COL_PROB]->s, 0);
-
-				if (ret_tmp < 0) {
-					LM_ERR("error in snprintf while querying prob column");
-					goto errout;
-				}
-				query_tmp_str.s = query_tmp;
-				query_tmp_str.len = ret_tmp;
-
-				if (carrierroute_dbf.raw_query(carrierroute_dbh, &query_tmp_str, &prob_res) < 0) {
-					LM_ERR("Failed to query carrierroute db table based on prob column.\n");
-					goto errout;
-				}
-				if(RES_ROW_N(prob_res) == 0) {
-					LM_ERR("Carrierroute db table contains route(s) with only 0 probability.\n");
-					query_done = crtrue;
-				}
-				carrierroute_dbf.free_result(carrierroute_dbh, prob_res);
-				prob_res = NULL;
-			}
 
 		}
 		if (DB_CAPABILITY(carrierroute_dbf, DB_CAP_FETCH)) {
@@ -439,6 +411,16 @@ int load_route_data_db(struct route_data_t * rd) {
 		}
 	} while(RES_ROW_N(res) > 0);
 
+	for (i = 0; i < rd->carrier_num; ++i) {
+		for (j = 0; j < rd->carriers[i]->domain_num; ++j) {
+			if (rd->carriers[i]->domains[j]->sum_prob == 0.0) {
+				LM_ERR("All routes with carrier id %d (%.*s) and domain id %d (%.*s) have probability 0.\n",
+						rd->carriers[i]->id, rd->carriers[i]->name->len, rd->carriers[i]->name->s,
+						rd->carriers[i]->domains[j]->id, rd->carriers[i]->domains[j]->name->len, rd->carriers[i]->domains[j]->name->s);
+			}
+		}
+	}
+
 	carrierroute_dbf.free_result(carrierroute_dbh, res);
 	res = NULL;
 	
@@ -493,8 +475,5 @@ errout:
 	if (res) {
 		carrierroute_dbf.free_result(carrierroute_dbh, res);
 	}
-	if (prob_res) {
-		carrierroute_dbf.free_result(carrierroute_dbh, prob_res);
-	}
 	return -1;
 }

+ 0 - 5
src/modules/carrierroute/cr_db.h

@@ -88,9 +88,4 @@ int load_route_data_db (struct route_data_t * rd);
 
 int load_user_carrier(str * user, str * domain);
 
-typedef enum {
-	crfalse = 0,
-	crtrue = 1
-} crboolean;
-
 #endif

+ 1 - 0
src/modules/carrierroute/cr_domain.h

@@ -40,6 +40,7 @@
 struct domain_data_t {
 	int id; /*!< the numerical id of the routing tree */
 	str * name; /*!< the name of the routing tree. This points to the name in domain_map to avoid duplication. */
+	double sum_prob; /*!< sums the probabilities of all entries in the normal tree. Used to warn that (carrier, domain) has only routes with probability 0. */
 	struct dtrie_node_t * tree; /*!< the root node of the routing tree. Payload is of type (struct route_flags *) */
 	struct dtrie_node_t * failure_tree; /*!< the root node of the failure routing tree. Payload is of type (struct failure_route_rule *) */
 };

+ 39 - 2
src/modules/corex/corex_mod.c

@@ -31,6 +31,7 @@
 #include "../../core/pvar.h"
 #include "../../core/fmsg.h"
 #include "../../core/kemi.h"
+#include "../../core/str_list.h"
 #include "../../core/events.h"
 #include "../../core/onsend.h"
 #include "../../core/dns_cache.h"
@@ -83,6 +84,10 @@ static int  mod_init(void);
 static int  child_init(int);
 static void mod_destroy(void);
 
+static str_list_t *corex_dns_cache_list = NULL;
+
+static int corex_dns_cache_param_add(str *pval);
+
 static int corex_sip_reply_out(sr_event_param_t *evp);
 
 static pv_export_t mod_pvs[] = {
@@ -186,6 +191,8 @@ struct module_exports exports = {
  */
 static int mod_init(void)
 {
+	str_list_t *sit;
+
 	if(corex_init_rpc()<0)
 	{
 		LM_ERR("failed to register RPC commands\n");
@@ -204,6 +211,13 @@ static int mod_init(void)
 		return -1;
 	}
 
+	for(sit = corex_dns_cache_list; sit!=NULL; sit=sit->next) {
+		if(corex_dns_cache_param_add(&sit->s)<0) {
+			LM_ERR("failed to add record: %.*s\n", sit->s.len, sit->s.s);
+			return -1;
+		}
+	}
+
 	if((nio_intercept > 0) && (nio_intercept_init() < 0))
 	{
 		LM_ERR("failed to register network io intercept callback\n");
@@ -336,6 +350,29 @@ error:
 }
 
 int corex_dns_cache_param(modparam_t type, void *val)
+{
+	str_list_t *sit;
+
+	if(val==NULL || ((str*)val)->s==NULL || ((str*)val)->len==0) {
+		LM_ERR("invalid parameter\n");
+		return -1;
+	}
+
+	sit = (str_list_t*)pkg_mallocxz(sizeof(str_list_t));
+	if(sit==NULL) {
+		PKG_MEM_ERROR;
+		return -1;
+	}
+	sit->s = *((str*)val);
+	if(corex_dns_cache_list!=NULL) {
+		sit->next = corex_dns_cache_list;
+	}
+	corex_dns_cache_list = sit;
+
+	return 0;
+}
+
+static int corex_dns_cache_param_add(str *pval)
 {
 	str sval;
 	param_t* params_list = NULL;
@@ -347,11 +384,11 @@ int corex_dns_cache_param(modparam_t type, void *val)
 	int dns_ttl = 0;
 	int dns_flags = 0;
 
-	if(val==NULL) {
+	if(pval==NULL) {
 		LM_ERR("invalid parameter\n");
 		goto error;
 	}
-	sval = *((str*)val);
+	sval = *pval;
 	if(sval.s==NULL || sval.len<=0) {
 		LM_ERR("invalid parameter value\n");
 		goto error;

+ 1 - 1
src/modules/cplc/cpl_run.c

@@ -283,7 +283,7 @@ static inline char *run_lookup( struct cpl_interpreter *intr )
 		} else {
 			contact = r->contacts;
 			/* skip expired contacts */
-			while ((contact) && (contact->expires <= tc))
+			while ((contact) && (contact->expires > 0) && (contact->expires <= tc))
 				contact = contact->next;
 			/* any contacts left? */
 			if (contact) {

+ 53 - 23
src/modules/ctl/binrpc_run.c

@@ -961,10 +961,11 @@ static int rpc_add(struct binrpc_ctx* ctx, char* fmt, ...)
 {
 	va_list ap;
 	int err;
-	char* s;
-	str* st;
+	str st;
+	str* sp;
 	struct rpc_struct_l* rs;
-	
+	str null_value = str_init("<null string>");
+
 	va_start(ap, fmt);
 	for (;*fmt; fmt++){
 		switch(*fmt){
@@ -976,15 +977,24 @@ static int rpc_add(struct binrpc_ctx* ctx, char* fmt, ...)
 				if (err<0) goto error_add;
 				break;
 			case 's': /* asciiz */
-				s=va_arg(ap, char*);
-				if (s==0) /* fix null strings */
-					s="<null string>"; 
-				err=binrpc_addstr(&ctx->out.pkt, s, strlen(s));
+				st.s=va_arg(ap, char*);
+				if (st.s==0) {
+					/* fix null strings */
+					st=null_value;
+				} else {
+					st.len=strlen(st.s);
+				}
+				err=binrpc_addstr(&ctx->out.pkt, st.s, st.len);
 				if (err<0) goto error_add;
 				break;
 			case 'S': /* str */
-				st=va_arg(ap, str*);
-				err=binrpc_addstr(&ctx->out.pkt, st->s, st->len);
+				sp=va_arg(ap, str*);
+				if(sp!=NULL && sp->s!=NULL) {
+					st=*sp;
+				} else {
+					st=null_value;
+				}
+				err=binrpc_addstr(&ctx->out.pkt, st.s, st.len);
 				if (err<0) goto error_add;
 				break;
 			case '{':
@@ -999,11 +1009,11 @@ static int rpc_add(struct binrpc_ctx* ctx, char* fmt, ...)
 				clist_append(&ctx->out.structs, rs, next, prev);
 				*(va_arg(ap, void**))=rs;
 				break;
-			case 'f': 
+			case 'f':
 				err=binrpc_adddouble(&ctx->out.pkt, va_arg(ap, double));
 				if (err<0) goto error_add;
 				break;
-			default: 
+			default:
 				rpc_fault(ctx, 500, "Internal server error: "
 								"invalid formatting character \'%c\'", *fmt);
 				LOG(L_CRIT, "BUG: binrpc: rpc_add: formatting char \'%c\'"
@@ -1067,6 +1077,8 @@ static int rpc_struct_add(struct rpc_struct_l* s, char* fmt, ...)
 	int err;
 	struct binrpc_val avp;
 	struct rpc_struct_l* rs;
+	str *sp;
+	str null_value = str_init("<null string>");
 
 	va_start(ap, fmt);
 	for (;*fmt; fmt++){
@@ -1085,13 +1097,21 @@ static int rpc_struct_add(struct rpc_struct_l* s, char* fmt, ...)
 			case 's': /* asciiz */
 				avp.type=BINRPC_T_STR;
 				avp.u.strval.s=va_arg(ap, char*);
-				if (avp.u.strval.s==0) /* fix null strings */
-					avp.u.strval.s="<null string>";
-				avp.u.strval.len=strlen(avp.u.strval.s);
+				if (avp.u.strval.s==NULL) {
+					/* fix null strings */
+					avp.u.strval=null_value;
+				} else {
+					avp.u.strval.len=strlen(avp.u.strval.s);
+				}
 				break;
 			case 'S': /* str */
 				avp.type=BINRPC_T_STR;
-				avp.u.strval=*(va_arg(ap, str*));
+				sp = va_arg(ap, str*);
+				if(sp!=NULL && sp->s!=NULL) {
+					avp.u.strval=*sp;
+				} else {
+					avp.u.strval=null_value;
+				}
 				break;
 			case '{':
 			case '[':
@@ -1144,9 +1164,10 @@ static int rpc_array_add(struct rpc_struct_l* s, char* fmt, ...)
 {
 	va_list ap;
 	int err;
-	char* sv;
-	str* st;
+	str st;
+	str *sp;
 	struct rpc_struct_l* rs;
+	str null_value = str_init("<null string>");
 
 	va_start(ap, fmt);
 	for (;*fmt; fmt++){
@@ -1159,15 +1180,24 @@ static int rpc_array_add(struct rpc_struct_l* s, char* fmt, ...)
 				if (err<0) goto error_add;
 				break;
 			case 's': /* asciiz */
-				sv=va_arg(ap, char*);
-				if (sv==0) /* fix null strings */
-					sv="<null string>";
-				err=binrpc_addstr(&s->pkt, sv, strlen(sv));
+				st.s=va_arg(ap, char*);
+				if (st.s==0) {
+					/* fix null strings */
+					st=null_value;
+				} else {
+					st.len = strlen(st.s);
+				}
+				err=binrpc_addstr(&s->pkt, st.s, st.len);
 				if (err<0) goto error_add;
 				break;
 			case 'S': /* str */
-				st=va_arg(ap, str*);
-				err=binrpc_addstr(&s->pkt, st->s, st->len);
+				sp=va_arg(ap, str*);
+				if(sp!=NULL && sp->s!=NULL) {
+					st=*sp;
+				} else {
+					st=null_value;
+				}
+				err=binrpc_addstr(&s->pkt, st.s, st.len);
 				if (err<0) goto error_add;
 				break;
 			case '{':

+ 38 - 19
src/modules/ctl/fifo_server.c

@@ -1092,6 +1092,7 @@ static int print_value(rpc_ctx_t* ctx, char fmt, va_list* ap)
 	str str_val;
 	str* sp;
 	char buf[256];
+	str null_value = str_init("<null string>");
 
 	switch(fmt) {
 	case 'd':
@@ -1104,7 +1105,7 @@ static int print_value(rpc_ctx_t* ctx, char fmt, va_list* ap)
 			goto err;
 		}
 		break;
-		
+
 	case 'f':
 		str_val.s = buf;
 		str_val.len = snprintf(buf, 256, "%f", va_arg(*ap, double));
@@ -1120,39 +1121,48 @@ static int print_value(rpc_ctx_t* ctx, char fmt, va_list* ap)
 			goto err;
 		}
 		break;
-		
+
 	case 'b':
 		str_val.len = 1;
 		str_val.s = ((va_arg(*ap, int) == 0) ? "0" : "1");
 		l = new_chunk(&str_val);
 		if (!l) {
-			rpc_fault(ctx, 500, "Internal Server Error, line %d", 
+			rpc_fault(ctx, 500, "Internal Server Error, line %d",
 						ctx->line_no);
 			goto err;
 		}
 		break;
-				
+
 	case 's':
 		str_val.s = va_arg(*ap, char*);
-		str_val.len = strlen(str_val.s);
+		if(str_val.s!=NULL) {
+			str_val.len = strlen(str_val.s);
+		} else {
+			str_val = null_value;
+		}
 		l = new_chunk_escape(&str_val, 0);
 		if (!l) {
-			rpc_fault(ctx, 500, "Internal Server Error, line %d", 
+			rpc_fault(ctx, 500, "Internal Server Error, line %d",
 						ctx->line_no);
 			goto err;
 		}
 		break;
-		
+
 	case 'S':
 		sp = va_arg(*ap, str*);
-		l = new_chunk_escape(sp, 0);
+		if(sp!=NULL && sp->s!=NULL) {
+			str_val = *sp;
+		} else {
+			str_val = null_value;
+		}
+		l = new_chunk_escape(&str_val, 0);
 		if (!l) {
-			rpc_fault(ctx, 500, "Internal Server Error, line %d", 
+			rpc_fault(ctx, 500, "Internal Server Error, line %d",
 							ctx->line_no);
 			goto err;
 		}
 		break;
-		
+
 	default:
 		rpc_fault(ctx, 500, "Bug In SER (Invalid formatting character %c)", fmt);
 		ERR("Invalid formatting character\n");
@@ -1368,7 +1378,7 @@ static int rpc_scan(rpc_ctx_t* ctx, char* fmt, ...)
 		case 'd': /* Integer */
 			if (!line.len) {
 				if(nofault==0)
-					rpc_fault(ctx, 400, "Invalid parameter value on line %d", 
+					rpc_fault(ctx, 400, "Invalid parameter value on line %d",
 									ctx->line_no);
 				goto error;
 			}
@@ -1379,14 +1389,14 @@ static int rpc_scan(rpc_ctx_t* ctx, char* fmt, ...)
 		case 'f': /* double */
 			if (!line.len) {
 				if(nofault==0)
-					rpc_fault(ctx, 400, "Invalid parameter value on line %d", 
+					rpc_fault(ctx, 400, "Invalid parameter value on line %d",
 								ctx->line_no);
 				goto error;
 			}
 			double_ptr = va_arg(ap, double*);
 			*double_ptr = strtod(line.s, 0);
 			break;
-			
+
 		case 's': /* zero terminated string */
 		case 'S': /* str structure */
 			l = new_chunk_unescape(&line);
@@ -1444,6 +1454,7 @@ static int rpc_struct_add(struct text_chunk* s, char* fmt, ...)
 	va_list ap;
 	struct text_chunk* m, *c;
 	rpc_ctx_t* ctx;
+	str null_value = str_init("<null string>");
 
 	ctx=(rpc_ctx_t*)s->ctx;
 	va_start(ap, fmt);
@@ -1457,7 +1468,7 @@ static int rpc_struct_add(struct text_chunk* s, char* fmt, ...)
 			goto err;
 		}
 		m->flags |= CHUNK_MEMBER_NAME;
-		
+
 		if(*fmt=='{' || *fmt=='[') {
 			void_ptr = va_arg(ap, void**);
 			m->ctx=ctx;
@@ -1490,13 +1501,22 @@ static int rpc_struct_add(struct text_chunk* s, char* fmt, ...)
 
 			case 's':
 				st.s = va_arg(ap, char*);
-				st.len = strlen(st.s);
+				if(st.s==NULL) {
+					st = null_value;
+				} else {
+					st.len = strlen(st.s);
+				}
 				c = new_chunk_escape(&st, 1);
 				break;
 
 			case 'S':
 				sp = va_arg(ap, str*);
-				c = new_chunk_escape(sp, 1);
+				if(sp!=NULL && sp->s!=NULL) {
+					st = *sp;
+				} else {
+					st = null_value;
+				}
+				c = new_chunk_escape(&st, 1);
 				break;
 
 			default:
@@ -1576,7 +1596,7 @@ static int rpc_struct_scan(struct rpc_struct* s, char* fmt, ...)
 			va_end(ap);
 			return read;
 		}
-		
+
 		switch(*fmt) {
 		case 'b': /* Bool */
 		case 't': /* Date and time */
@@ -1599,7 +1619,7 @@ static int rpc_struct_scan(struct rpc_struct* s, char* fmt, ...)
 			     /* String in text_chunk is always zero terminated */
 			*double_ptr = strtod(val->s.s, 0);
 			break;
-			
+
 		case 's': /* zero terminated string */
 			char_ptr = va_arg(ap, char**);
 			     /* String in text_chunk is always zero terminated */
@@ -1608,7 +1628,6 @@ static int rpc_struct_scan(struct rpc_struct* s, char* fmt, ...)
 
 		case 'S': /* str structure */
 			str_ptr = va_arg(ap, str*);
-			str_ptr->len = strlen(str_ptr->s);
 			*str_ptr = val->s;
 			break;
 		default:

+ 2 - 2
src/modules/ctl/io_listener.c

@@ -495,7 +495,7 @@ again:
 		DBG("handle_stream read: eof on %s\n", s_c->parent->name);
 		goto close_connection;
 	}
-	LM_INFO("bytes read: %d\n", bytes_read);
+	LM_DBG("bytes read: %d\n", bytes_read);
 	r->end+=bytes_read;
 	if (bytes_read && (bytes_read<r->bytes_to_go)){
 		r->bytes_to_go-=bytes_read;
@@ -515,7 +515,7 @@ again:
 			/* error while processing the packet => close the connection */
 			goto close_connection;
 		}
-		LM_INFO("bytes processed: %d\n", bytes_processed);
+		LM_DBG("bytes processed: %d\n", bytes_processed);
 		r->proc+=bytes_processed;
 		r->bytes_to_go=bytes_needed;
 		if (bytes_needed>0){

+ 41 - 40
src/modules/dialplan/dp_db.c

@@ -13,8 +13,8 @@
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
- * You should have received a copy of the GNU General Public License 
- * along with this program; if not, write to the Free Software 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
@@ -46,7 +46,7 @@ str match_exp_column=   str_init(MATCH_EXP_COL);
 str match_len_column=   str_init(MATCH_LEN_COL);
 str subst_exp_column=   str_init(SUBST_EXP_COL);
 str repl_exp_column =   str_init(REPL_EXP_COL);
-str attrs_column    =   str_init(ATTRS_COL); 
+str attrs_column    =   str_init(ATTRS_COL);
 
 extern int dp_fetch_rows;
 extern int dp_match_dynamic;
@@ -75,8 +75,9 @@ void list_rule(dpl_node_t * );
 void list_hash(int h_index);
 
 
-dpl_id_p* rules_hash = NULL;
-int * crt_idx, *next_idx;
+static dpl_id_p* dp_rules_hash = NULL;
+static int *dp_crt_idx = NULL;
+static int *dp_next_idx = NULL;
 
 
 /**
@@ -197,21 +198,21 @@ int init_data(void)
 {
 	int *p;
 
-	rules_hash = (dpl_id_p *)shm_malloc(2*sizeof(dpl_id_p));
-	if(!rules_hash) {
+	dp_rules_hash = (dpl_id_p *)shm_malloc(2*sizeof(dpl_id_p));
+	if(!dp_rules_hash) {
 		LM_ERR("out of shm memory\n");
 		return -1;
 	}
-	rules_hash[0] = rules_hash[1] = 0;
+	dp_rules_hash[0] = dp_rules_hash[1] = 0;
 
 	p = (int *)shm_malloc(2*sizeof(int));
 	if(!p){
 		LM_ERR("out of shm memory\n");
 		return -1;
 	}
-	crt_idx = p;
-	next_idx = p+1;
-	*crt_idx = *next_idx = 0;
+	dp_crt_idx = p;
+	dp_next_idx = p+1;
+	*dp_crt_idx = *dp_next_idx = 0;
 
 	LM_DBG("trying to initialize data from db\n");
 	if(init_db_data() != 0)
@@ -223,15 +224,15 @@ int init_data(void)
 
 void destroy_data(void)
 {
-	if(rules_hash){
+	if(dp_rules_hash){
 		destroy_hash(0);
 		destroy_hash(1);
-		shm_free(rules_hash);
-		rules_hash = 0;
+		shm_free(dp_rules_hash);
+		dp_rules_hash = 0;
 	}
 
-	if(crt_idx)
-		shm_free(crt_idx);
+	if(dp_crt_idx)
+		shm_free(dp_crt_idx);
 }
 
 
@@ -252,7 +253,7 @@ int dp_load_db(void)
 	dpl_node_t *rule;
 
 	LM_DBG("init\n");
-	if( (*crt_idx) != (*next_idx)){
+	if( (*dp_crt_idx) != (*dp_next_idx)){
 		LM_WARN("a load command already generated, aborting reload...\n");
 		return 0;
 	}
@@ -263,7 +264,7 @@ int dp_load_db(void)
 	}
 
 	if (DB_CAPABILITY(dp_dbf, DB_CAP_FETCH)) {
-		if(dp_dbf.query(dp_db_handle,0,0,0,query_cols, 0, 
+		if(dp_dbf.query(dp_db_handle,0,0,0,query_cols, 0,
 					DP_TABLE_COL_NO, order, 0) < 0){
 			LM_ERR("failed to query database!\n");
 			return -1;
@@ -285,8 +286,8 @@ int dp_load_db(void)
 
 	nr_rows = RES_ROW_N(res);
 
-	*next_idx = ((*crt_idx) == 0)? 1:0;
-	destroy_hash(*next_idx);
+	*dp_next_idx = ((*dp_crt_idx) == 0)? 1:0;
+	destroy_hash(*dp_next_idx);
 
 	if(nr_rows == 0){
 		LM_WARN("no data in the db\n");
@@ -302,7 +303,7 @@ int dp_load_db(void)
 			if((rule = build_rule(values)) ==0 )
 				goto err2;
 
-			if(add_rule2hash(rule , *next_idx) != 0)
+			if(add_rule2hash(rule, *dp_next_idx) != 0)
 				goto err2;
 
 		}
@@ -321,16 +322,16 @@ int dp_load_db(void)
 
 end:
 	/*update data*/
-	*crt_idx = *next_idx;
-	list_hash(*crt_idx);
+	*dp_crt_idx = *dp_next_idx;
+	list_hash(*dp_crt_idx);
 	dp_dbf.free_result(dp_db_handle, res);
 	return 0;
 
 err2:
 	if(rule)	destroy_rule(rule);
-	destroy_hash(*next_idx);
+	destroy_hash(*dp_next_idx);
 	dp_dbf.free_result(dp_db_handle, res);
-	*next_idx = *crt_idx; 
+	*dp_next_idx = *dp_crt_idx;
 	return -1;
 }
 
@@ -550,7 +551,7 @@ int add_rule2hash(dpl_node_t * rule, int h_index)
 	dpl_index_p indexp, last_indexp, new_indexp;
 	int new_id;
 
-	if(!rules_hash){
+	if(!dp_rules_hash){
 		LM_ERR("data not allocated\n");
 		return -1;
 	}
@@ -558,7 +559,7 @@ int add_rule2hash(dpl_node_t * rule, int h_index)
 	new_id = 0;
 
 	/*search for the corresponding dpl_id*/
-	for(crt_idp = last_idp =rules_hash[h_index]; crt_idp!= NULL; 
+	for(crt_idp = last_idp =dp_rules_hash[h_index]; crt_idp!= NULL;
 			last_idp = crt_idp, crt_idp = crt_idp->next)
 		if(crt_idp->dp_id == rule->dpid)
 			break;
@@ -577,7 +578,7 @@ int add_rule2hash(dpl_node_t * rule, int h_index)
 	}
 
 	/*search for the corresponding dpl_index*/
-	for(indexp = last_indexp =crt_idp->first_index; indexp!=NULL; 
+	for(indexp = last_indexp =crt_idp->first_index; indexp!=NULL;
 			last_indexp = indexp, indexp = indexp->next){
 		if(indexp->len == rule->matchlen)
 			goto add_rule;
@@ -617,8 +618,8 @@ add_rule:
 	indexp->last_rule = rule;
 
 	if(new_id){
-		crt_idp->next = rules_hash[h_index];
-		rules_hash[h_index] = crt_idp;
+		crt_idp->next = dp_rules_hash[h_index];
+		dp_rules_hash[h_index] = crt_idp;
 	}
 	LM_DBG("added the rule id %i index %i pr %i next %p to the "
 			"index with %i len\n", rule->dpid, rule->matchlen,
@@ -639,10 +640,10 @@ void destroy_hash(int index)
 	dpl_index_p indexp;
 	dpl_node_p rulep;
 
-	if(!rules_hash[index])
+	if(!dp_rules_hash[index])
 		return;
 
-	for(crt_idp = rules_hash[index]; crt_idp != NULL;){
+	for(crt_idp = dp_rules_hash[index]; crt_idp != NULL;){
 
 		for(indexp = crt_idp->first_index; indexp != NULL;){
 
@@ -662,13 +663,13 @@ void destroy_hash(int index)
 
 		}
 
-		rules_hash[index] = crt_idp->next;
+		dp_rules_hash[index] = crt_idp->next;
 		shm_free(crt_idp);
 		crt_idp = 0;
-		crt_idp = rules_hash[index];
+		crt_idp = dp_rules_hash[index];
 	}
 
-	rules_hash[index] = 0;
+	dp_rules_hash[index] = 0;
 }
 
 
@@ -677,7 +678,7 @@ void destroy_rule(dpl_node_t * rule){
 	if(!rule)
 		return;
 
-	LM_DBG("destroying rule with priority %i\n", 
+	LM_DBG("destroying rule with priority %i\n",
 			rule->pr);
 
 	if(rule->match_comp)
@@ -708,10 +709,10 @@ dpl_id_p select_dpid(int id)
 {
 	dpl_id_p idp;
 
-	if(!rules_hash || !crt_idx)
+	if(!dp_rules_hash || !dp_crt_idx)
 		return NULL;
 
-	for(idp = rules_hash[*crt_idx]; idp!=NULL; idp = idp->next)
+	for(idp = dp_rules_hash[*dp_crt_idx]; idp!=NULL; idp = idp->next)
 		if(idp->dp_id == id)
 			return idp;
 
@@ -727,10 +728,10 @@ void list_hash(int h_index)
 	dpl_node_p rulep;
 
 
-	if(!rules_hash[h_index])
+	if(!dp_rules_hash[h_index])
 		return;
 
-	for(crt_idp=rules_hash[h_index]; crt_idp!=NULL; crt_idp = crt_idp->next){
+	for(crt_idp=dp_rules_hash[h_index]; crt_idp!=NULL; crt_idp = crt_idp->next){
 		LM_DBG("DPID: %i, pointer %p\n", crt_idp->dp_id, crt_idp);
 		for(indexp=crt_idp->first_index; indexp!=NULL;indexp= indexp->next){
 			LM_DBG("INDEX LEN: %i\n", indexp->len);

+ 50 - 50
src/modules/dispatcher/dispatch.c

@@ -113,13 +113,13 @@ extern int ds_load_mode;
 static db_func_t ds_dbf;
 static db1_con_t *ds_db_handle = NULL;
 
-ds_set_t **ds_lists = NULL;
+static ds_set_t **ds_lists = NULL;
 
-int *ds_list_nr = NULL;
-int *crt_idx = NULL;
-int *next_idx = NULL;
+static int *ds_list_nr = NULL;
+static int *ds_crt_idx = NULL;
+static int *ds_next_idx = NULL;
 
-#define _ds_list (ds_lists[*crt_idx])
+#define _ds_list (ds_lists[*ds_crt_idx])
 #define _ds_list_nr (*ds_list_nr)
 
 static void ds_run_route(struct sip_msg *msg, str *uri, char *route,
@@ -267,10 +267,10 @@ int ds_init_data(void)
 	}
 	memset(p, 0, 3 * sizeof(int));
 
-	crt_idx = p;
-	next_idx = p + 1;
+	ds_crt_idx = p;
+	ds_next_idx = p + 1;
 	ds_list_nr = p + 2;
-	*crt_idx = *next_idx = 0;
+	*ds_crt_idx = *ds_next_idx = 0;
 
 	return 0;
 }
@@ -784,7 +784,7 @@ int ds_load_list(char *lfile)
 	str uri;
 	str attrs;
 
-	if((*crt_idx) != (*next_idx)) {
+	if((*ds_crt_idx) != (*ds_next_idx)) {
 		LM_WARN("load command already generated, aborting reload...\n");
 		return 0;
 	}
@@ -802,8 +802,8 @@ int ds_load_list(char *lfile)
 
 	id = setn = flags = priority = 0;
 
-	*next_idx = (*crt_idx + 1) % 2;
-	ds_avl_destroy(&ds_lists[*next_idx]);
+	*ds_next_idx = (*ds_crt_idx + 1) % 2;
+	ds_avl_destroy(&ds_lists[*ds_next_idx]);
 
 	p = fgets(line, 1024, f);
 	while(p) {
@@ -878,7 +878,7 @@ int ds_load_list(char *lfile)
 		attrs.len = p - attrs.s;
 
 add_destination:
-		if(add_dest2list(id, uri, flags, priority, &attrs, *next_idx, &setn, 0)
+		if(add_dest2list(id, uri, flags, priority, &attrs, *ds_next_idx, &setn, 0)
 				!= 0) {
 			LM_WARN("unable to add destination %.*s to set %d -- skipping\n",
 					uri.len, uri.s, id);
@@ -890,7 +890,7 @@ next_line:
 		p = fgets(line, 1024, f);
 	}
 
-	if(reindex_dests(ds_lists[*next_idx]) != 0) {
+	if(reindex_dests(ds_lists[*ds_next_idx]) != 0) {
 		LM_ERR("error on reindex\n");
 		goto error;
 	}
@@ -899,7 +899,7 @@ next_line:
 	f = NULL;
 	/* Update list - should it be sync'ed? */
 	_ds_list_nr = setn;
-	*crt_idx = *next_idx;
+	*ds_crt_idx = *ds_next_idx;
 
 	LM_DBG("found [%d] dest sets\n", _ds_list_nr);
 
@@ -909,8 +909,8 @@ next_line:
 error:
 	if(f != NULL)
 		fclose(f);
-	ds_avl_destroy(&ds_lists[*next_idx]);
-	*next_idx = *crt_idx;
+	ds_avl_destroy(&ds_lists[*ds_next_idx]);
+	*ds_next_idx = *ds_crt_idx;
 	return -1;
 }
 
@@ -1048,7 +1048,7 @@ int ds_load_db(void)
 		}
 	}
 
-	if((*crt_idx) != (*next_idx)) {
+	if((*ds_crt_idx) != (*ds_next_idx)) {
 		LM_WARN("load command already generated, aborting reload...\n");
 		return 0;
 	}
@@ -1079,8 +1079,8 @@ int ds_load_db(void)
 	}
 
 	setn = 0;
-	*next_idx = (*crt_idx + 1) % 2;
-	ds_avl_destroy(&ds_lists[*next_idx]);
+	*ds_next_idx = (*ds_crt_idx + 1) % 2;
+	ds_avl_destroy(&ds_lists[*ds_next_idx]);
 
 	for(i = 0; i < nr_rows; i++) {
 		values = ROW_VALUES(rows + i);
@@ -1128,7 +1128,7 @@ int ds_load_db(void)
 			}
 		}
 		LM_DBG("attributes string: [%.*s]\n", attrs.len, (attrs.s)?attrs.s:"");
-		if(add_dest2list(id, uri, flags, priority, &attrs, *next_idx, &setn, 0)
+		if(add_dest2list(id, uri, flags, priority, &attrs, *ds_next_idx, &setn, 0)
 				!= 0) {
 			dest_errs++;
 			LM_WARN("unable to add destination %.*s to set %d -- skipping\n",
@@ -1138,7 +1138,7 @@ int ds_load_db(void)
 			}
 		}
 	}
-	if(reindex_dests(ds_lists[*next_idx]) != 0) {
+	if(reindex_dests(ds_lists[*ds_next_idx]) != 0) {
 		LM_ERR("error on reindex\n");
 		goto err2;
 	}
@@ -1147,7 +1147,7 @@ int ds_load_db(void)
 
 	/* update data - should it be sync'ed? */
 	_ds_list_nr = setn;
-	*crt_idx = *next_idx;
+	*ds_crt_idx = *ds_next_idx;
 
 	LM_DBG("found [%d] dest sets\n", _ds_list_nr);
 
@@ -1158,9 +1158,9 @@ int ds_load_db(void)
 	return 0;
 
 err2:
-	ds_avl_destroy(&ds_lists[*next_idx]);
+	ds_avl_destroy(&ds_lists[*ds_next_idx]);
 	ds_dbf.free_result(ds_db_handle, res);
-	*next_idx = *crt_idx;
+	*ds_next_idx = *ds_crt_idx;
 
 	return -1;
 }
@@ -1174,8 +1174,8 @@ int ds_destroy_list(void)
 		shm_free(ds_lists);
 	}
 
-	if(crt_idx)
-		shm_free(crt_idx);
+	if(ds_crt_idx)
+		shm_free(ds_crt_idx);
 
 	return 0;
 }
@@ -1615,7 +1615,7 @@ int ds_load_replace(struct sip_msg *msg, str *duid)
 	}
 	set = it->dset;
 	/* get the index of the set */
-	if(ds_get_index(set, *crt_idx, &idx) != 0) {
+	if(ds_get_index(set, *ds_crt_idx, &idx) != 0) {
 		ds_unlock_cell(_dsht_load, &msg->callid->body);
 		LM_ERR("destination set [%d] not found\n", set);
 		return -1;
@@ -1676,7 +1676,7 @@ int ds_load_remove_byid(int set, str *duid)
 	int i;
 
 	/* get the index of the set */
-	if(ds_get_index(set, *crt_idx, &idx) != 0) {
+	if(ds_get_index(set, *ds_crt_idx, &idx) != 0) {
 		LM_ERR("destination set [%d] not found\n", set);
 		return -1;
 	}
@@ -2276,7 +2276,7 @@ int ds_manage_routes(sip_msg_t *msg, ds_select_state_t *rstate)
 
 
 	/* get the index of the set */
-	if(ds_get_index(rstate->setid, *crt_idx, &idx) != 0) {
+	if(ds_get_index(rstate->setid, *ds_crt_idx, &idx) != 0) {
 		LM_ERR("destination set [%d] not found\n", rstate->setid);
 		return -1;
 	}
@@ -2579,7 +2579,7 @@ void ds_add_dest_cb(ds_set_t *node, int i, void *arg)
 	int setn;
 
 	if(add_dest2list(node->id, node->dlist[i].uri, node->dlist[i].flags,
-			node->dlist[i].priority, &node->dlist[i].attrs.body, *next_idx,
+			node->dlist[i].priority, &node->dlist[i].attrs.body, *ds_next_idx,
 			&setn, node->dlist[i].dload) != 0) {
 		LM_WARN("failed to add destination in group %d - %.*s\n",
 				node->id, node->dlist[i].uri.len, node->dlist[i].uri.s);
@@ -2595,35 +2595,35 @@ int ds_add_dst(int group, str *address, int flags, str *attrs)
 	setn = _ds_list_nr;
 	priority = 0;
 
-	*next_idx = (*crt_idx + 1) % 2;
-	ds_avl_destroy(&ds_lists[*next_idx]);
+	*ds_next_idx = (*ds_crt_idx + 1) % 2;
+	ds_avl_destroy(&ds_lists[*ds_next_idx]);
 
 	// add all existing destinations
 	ds_iter_set(_ds_list, &ds_add_dest_cb, NULL);
 
 	// add new destination
 	if(add_dest2list(group, *address, flags, priority, attrs,
-			*next_idx, &setn, 0) != 0) {
+			*ds_next_idx, &setn, 0) != 0) {
 		LM_WARN("unable to add destination %.*s to set %d", address->len, address->s, group);
 		if(ds_load_mode==1) {
 			goto error;
 		}
 	}
 
-	if(reindex_dests(ds_lists[*next_idx]) != 0) {
+	if(reindex_dests(ds_lists[*ds_next_idx]) != 0) {
 		LM_ERR("error on reindex\n");
 		goto error;
 	}
 
 	_ds_list_nr = setn;
-	*crt_idx = *next_idx;
+	*ds_crt_idx = *ds_next_idx;
 
 	ds_log_sets();
 	return 0;
 
 error:
-	ds_avl_destroy(&ds_lists[*next_idx]);
-	*next_idx = *crt_idx;
+	ds_avl_destroy(&ds_lists[*ds_next_idx]);
+	*ds_next_idx = *ds_crt_idx;
 	return -1;
 }
 
@@ -2637,7 +2637,7 @@ void ds_filter_dest_cb(ds_set_t *node, int i, void *arg)
 		return;
 
 	if(add_dest2list(node->id, node->dlist[i].uri, node->dlist[i].flags,
-			node->dlist[i].priority, &node->dlist[i].attrs.body, *next_idx,
+			node->dlist[i].priority, &node->dlist[i].attrs.body, *ds_next_idx,
 			filter_arg->setn, node->dlist[i].dload) != 0) {
 		LM_WARN("failed to add destination in group %d - %.*s\n",
 				node->id, node->dlist[i].uri.len, node->dlist[i].uri.s);
@@ -2659,26 +2659,26 @@ int ds_remove_dst(int group, str *address)
 	filter_arg.dest = dp;
 	filter_arg.setn = &setn;
 
-	*next_idx = (*crt_idx + 1) % 2;
-	ds_avl_destroy(&ds_lists[*next_idx]);
+	*ds_next_idx = (*ds_crt_idx + 1) % 2;
+	ds_avl_destroy(&ds_lists[*ds_next_idx]);
 
 	// add existing destinations except destination that matches group & address
 	ds_iter_set(_ds_list, &ds_filter_dest_cb, &filter_arg);
 
-	if(reindex_dests(ds_lists[*next_idx]) != 0) {
+	if(reindex_dests(ds_lists[*ds_next_idx]) != 0) {
 		LM_ERR("error on reindex\n");
 		goto error;
 	}
 
 	_ds_list_nr = setn;
-	*crt_idx = *next_idx;
+	*ds_crt_idx = *ds_next_idx;
 
 	ds_log_sets();
 	return 0;
 
 error:
-	ds_avl_destroy(&ds_lists[*next_idx]);
-	*next_idx = *crt_idx;
+	ds_avl_destroy(&ds_lists[*ds_next_idx]);
+	*ds_next_idx = *ds_crt_idx;
 	return -1;
 }
 
@@ -2833,7 +2833,7 @@ int ds_update_latency(int group, str *address, int code)
 	}
 
 	/* get the index of the set */
-	if(ds_get_index(group, *crt_idx, &idx) != 0) {
+	if(ds_get_index(group, *ds_crt_idx, &idx) != 0) {
 		LM_ERR("destination set [%d] not found\n", group);
 		return -1;
 	}
@@ -2927,7 +2927,7 @@ int ds_get_state(int group, str *address)
 	}
 
 	/* get the index of the set */
-	if(ds_get_index(group, *crt_idx, &idx) != 0) {
+	if(ds_get_index(group, *ds_crt_idx, &idx) != 0) {
 		LM_ERR("destination set [%d] not found\n", group);
 		return -1;
 	}
@@ -2961,7 +2961,7 @@ int ds_update_state(sip_msg_t *msg, int group, str *address, int state,
 	}
 
 	/* get the index of the set */
-	if(ds_get_index(group, *crt_idx, &idx) != 0) {
+	if(ds_get_index(group, *ds_crt_idx, &idx) != 0) {
 		LM_ERR("destination set [%d] not found\n", group);
 		return -1;
 	}
@@ -3162,7 +3162,7 @@ int ds_reinit_state(int group, str *address, int state)
 	}
 
 	/* get the index of the set */
-	if(ds_get_index(group, *crt_idx, &idx) != 0) {
+	if(ds_get_index(group, *ds_crt_idx, &idx) != 0) {
 		LM_ERR("destination set [%d] not found\n", group);
 		return -1;
 	}
@@ -3203,7 +3203,7 @@ int ds_reinit_duid_state(int group, str *vduid, int state)
 	}
 
 	/* get the index of the set */
-	if(ds_get_index(group, *crt_idx, &idx) != 0) {
+	if(ds_get_index(group, *ds_crt_idx, &idx) != 0) {
 		LM_ERR("destination set [%d] not found\n", group);
 		return -1;
 	}
@@ -3244,7 +3244,7 @@ int ds_reinit_state_all(int group, int state)
 	}
 
 	/* get the index of the set */
-	if(ds_get_index(group, *crt_idx, &idx) != 0) {
+	if(ds_get_index(group, *ds_crt_idx, &idx) != 0) {
 		LM_ERR("destination set [%d] not found\n", group);
 		return -1;
 	}

+ 5 - 5
src/modules/dispatcher/dispatcher.c

@@ -1635,10 +1635,10 @@ static void dispatcher_rpc_list(rpc_t *rpc, void *ctx)
 	void *th;
 	void *ih;
 
-	ds_set_t *ds_list = ds_get_list();
-	int ds_list_nr = ds_get_list_nr();
+	ds_set_t *dslist = ds_get_list();
+	int dslistnr = ds_get_list_nr();
 
-	if(ds_list == NULL || ds_list_nr <= 0) {
+	if(dslist == NULL || dslistnr <= 0) {
 		LM_DBG("no destination sets\n");
 		rpc->fault(ctx, 500, "No Destination Sets");
 		return;
@@ -1649,12 +1649,12 @@ static void dispatcher_rpc_list(rpc_t *rpc, void *ctx)
 		rpc->fault(ctx, 500, "Internal error root reply");
 		return;
 	}
-	if(rpc->struct_add(th, "d[", "NRSETS", ds_list_nr, "RECORDS", &ih) < 0) {
+	if(rpc->struct_add(th, "d[", "NRSETS", dslistnr, "RECORDS", &ih) < 0) {
 		rpc->fault(ctx, 500, "Internal error sets structure");
 		return;
 	}
 
-	ds_rpc_print_set(ds_list, rpc, ctx, ih);
+	ds_rpc_print_set(dslist, rpc, ctx, ih);
 
 	return;
 }

+ 4 - 0
src/modules/dmq/README

@@ -216,6 +216,8 @@ sip:192.168.40.17:5060;status=active
 ...
 modparam("dmq", "server_address", "sip:10.0.0.20:5060")
 ...
+modparam("dmq", "server_address", "sip:10.0.0.20:5061;transport=tls")
+...
 
 3.2. notification_address(str)
 
@@ -228,6 +230,8 @@ modparam("dmq", "server_address", "sip:10.0.0.20:5060")
 ...
 modparam("dmq", "notification_address", "sip:10.0.0.21:5060")
 ...
+modparam("dmq", "notification_address", "sip:10.0.0.21:5061;transport=tls")
+...
 
 3.3. notification_channel(str)
 

+ 30 - 13
src/modules/dmq/dmq.c

@@ -37,6 +37,7 @@
 #include "../../core/pt.h"
 #include "../../core/hashes.h"
 #include "../../core/mod_fix.h"
+#include "../../core/cfg/cfg_struct.h"
 #include "../../core/rpc_lookup.h"
 #include "../../core/kemi.h"
 
@@ -136,19 +137,29 @@ struct module_exports exports = {
 
 static int make_socket_str_from_uri(struct sip_uri *uri, str *socket)
 {
+	str sproto = STR_NULL;
+
 	if(!uri->host.s || !uri->host.len) {
 		LM_ERR("no host in uri\n");
 		return -1;
 	}
 
-	socket->len = uri->host.len + uri->port.len + 6;
+	socket->len = uri->host.len + uri->port.len + 7 /*sctp + : + : \0*/;
 	socket->s = pkg_malloc(socket->len);
 	if(socket->s == NULL) {
 		LM_ERR("no more pkg\n");
 		return -1;
 	}
-	memcpy(socket->s, "udp:", 4);
-	socket->len = 4;
+
+	if(get_valid_proto_string(uri->proto, 0, 0, &sproto)<0) {
+		LM_WARN("unknown transport protocol - fall back to udp\n");
+		sproto.s = "udp";
+		sproto.len = 3;
+	}
+
+	memcpy(socket->s, sproto.s, sproto.len);
+	socket->s[sproto.len] = ':';
+	socket->len = sproto.len + 1;
 
 	memcpy(socket->s + socket->len, uri->host.s, uri->host.len);
 	socket->len += uri->host.len;
@@ -275,6 +286,11 @@ static int child_init(int rank)
 {
 	int i, newpid;
 
+	if(rank == PROC_TCP_MAIN) {
+		/* do nothing for the tcp main process */
+		return 0;
+	}
+
 	if(rank == PROC_INIT) {
 		for(i = 0; i < dmq_num_workers; i++) {
 			if (init_worker(&dmq_workers[i]) < 0) {
@@ -289,17 +305,22 @@ static int child_init(int rank)
 		/* fork worker processes */
 		for(i = 0; i < dmq_num_workers; i++) {
 			LM_DBG("starting worker process %d\n", i);
-			newpid = fork_process(PROC_RPC, "DMQ WORKER", 0);
+			newpid = fork_process(PROC_RPC, "DMQ WORKER", 1);
 			if(newpid < 0) {
 				LM_ERR("failed to fork worker process %d\n", i);
 				return -1;
 			} else if(newpid == 0) {
+				if (cfg_child_init()) return -1;
 				/* child - this will loop forever */
 				worker_loop(i);
 			} else {
 				dmq_workers[i].pid = newpid;
 			}
 		}
+		return 0;
+	}
+
+	if(rank == PROC_SIPINIT) {
 		/* notification_node - the node from which the Kamailio instance
 		 * gets the server list on startup.
 		 * the address is given as a module parameter in dmq_notification_address
@@ -314,11 +335,6 @@ static int child_init(int rank)
 						STR_FMT(&dmq_notification_address));
 			}
 		}
-		return 0;
-	}
-	if(rank == PROC_TCP_MAIN) {
-		/* do nothing for the main process */
-		return 0;
 	}
 
 	dmq_pid = my_pid();
@@ -355,10 +371,11 @@ static void dmq_rpc_list_nodes(rpc_t *rpc, void *c)
 		ip_addr2sbuf(&cur->ip_address, ip, IP6_MAX_STR_SIZE);
 		if(rpc->add(c, "{", &h) < 0)
 			goto error;
-		if(rpc->struct_add(h, "SSsSdd", "host", &cur->uri.host, "port",
-				   &cur->uri.port, "resolved_ip", ip, "status",
-				   dmq_get_status_str(cur->status), "last_notification",
-				   cur->last_notification, "local", cur->local)
+		if(rpc->struct_add(h, "SSssSdd", "host", &cur->uri.host, "port",
+				   &cur->uri.port, "proto", get_proto_name(cur->uri.proto),
+				   "resolved_ip", ip, "status", dmq_get_status_str(cur->status),
+				   "last_notification", cur->last_notification,
+				   "local", cur->local)
 				< 0)
 			goto error;
 		cur = cur->next;

+ 19 - 2
src/modules/dmq/dmq_funcs.c

@@ -74,8 +74,9 @@ void dmq_tm_callback(struct cell *t, int type, struct tmcb_params *ps)
 
 int build_uri_str(str *username, struct sip_uri *uri, str *from)
 {
-	/* sip:user@host:port */
+	/* sip:user@host:port;transport=abcd */
 	int from_len;
+	str sproto = STR_NULL;
 
 	if(!uri->host.s || !uri->host.len) {
 		LM_ERR("no host in uri\n");
@@ -86,7 +87,8 @@ int build_uri_str(str *username, struct sip_uri *uri, str *from)
 		return -1;
 	}
 
-	from_len = username->len + uri->host.len + uri->port.len + 10;
+	from_len = username->len + uri->host.len + uri->port.len + 12
+				+ TRANSPORT_PARAM_LEN;
 	from->s = pkg_malloc(from_len);
 	if(from->s == NULL) {
 		LM_ERR("no more pkg\n");
@@ -112,6 +114,21 @@ int build_uri_str(str *username, struct sip_uri *uri, str *from)
 		memcpy(from->s + from->len, uri->port.s, uri->port.len);
 		from->len += uri->port.len;
 	}
+
+	if(uri->proto!=PROTO_NONE && uri->proto!=PROTO_UDP
+			&& uri->proto!=PROTO_OTHER) {
+		if(get_valid_proto_string(uri->proto, 1, 0, &sproto)<0) {
+			LM_WARN("unknown transport protocol - fall back to udp\n");
+			sproto.s = "udp";
+			sproto.len = 3;
+		}
+		memcpy(from->s + from->len, TRANSPORT_PARAM, TRANSPORT_PARAM_LEN);
+		from->len += TRANSPORT_PARAM_LEN;
+		memcpy(from->s + from->len, sproto.s, sproto.len);
+		from->len += sproto.len;
+	}
+	from->s[from->len] = '\0';
+
 	return 0;
 }
 

+ 17 - 2
src/modules/dmq/dmqnode.c

@@ -87,7 +87,8 @@ int cmp_dmq_node(dmq_node_t *node, dmq_node_t *cmpnode)
 		return -1;
 	}
 	return STR_EQ(node->uri.host, cmpnode->uri.host)
-		   && STR_EQ(node->uri.port, cmpnode->uri.port);
+		   && STR_EQ(node->uri.port, cmpnode->uri.port)
+		   && (node->uri.proto == cmpnode->uri.proto);
 }
 
 /**
@@ -397,8 +398,10 @@ int update_dmq_node_status(dmq_node_list_t *list, dmq_node_t *node, int status)
  */
 int build_node_str(dmq_node_t *node, char *buf, int buflen)
 {
-	/* sip:host:port;status=[status] */
+	/* sip:host:port;protocol=abcd;status=[status] */
 	int len = 0;
+	str sproto = STR_NULL;
+
 	if(buflen < node->orig_uri.len + 32) {
 		LM_ERR("no more space left for node string\n");
 		return -1;
@@ -411,6 +414,18 @@ int build_node_str(dmq_node_t *node, char *buf, int buflen)
 	len += 1;
 	memcpy(buf + len, node->uri.port.s, node->uri.port.len);
 	len += node->uri.port.len;
+	if(node->uri.proto!=PROTO_NONE && node->uri.proto!=PROTO_UDP
+			&& node->uri.proto!=PROTO_OTHER) {
+		if(get_valid_proto_string(node->uri.proto, 1, 0, &sproto)<0) {
+			LM_WARN("unknown transport protocol - fall back to udp\n");
+			sproto.s = "udp";
+			sproto.len = 3;
+		}
+		memcpy(buf + len, TRANSPORT_PARAM, TRANSPORT_PARAM_LEN);
+		len += TRANSPORT_PARAM_LEN;
+		memcpy(buf + len, sproto.s, sproto.len);
+		len += sproto.len;
+	}
 	memcpy(buf + len, ";", 1);
 	len += 1;
 	memcpy(buf + len, "status=", 7);

+ 4 - 0
src/modules/dmq/doc/dmq_admin.xml

@@ -132,6 +132,8 @@ sip:192.168.40.17:5060;status=active
 ...
 modparam("dmq", "server_address", "sip:10.0.0.20:5060")
 ...
+modparam("dmq", "server_address", "sip:10.0.0.20:5061;transport=tls")
+...
 </programlisting>
 		</example>
 	</section>
@@ -149,6 +151,8 @@ modparam("dmq", "server_address", "sip:10.0.0.20:5060")
 ...
 modparam("dmq", "notification_address", "sip:10.0.0.21:5060")
 ...
+modparam("dmq", "notification_address", "sip:10.0.0.21:5061;transport=tls")
+...
 </programlisting>
 		</example>
 	</section>

+ 4 - 3
src/modules/dmq/notification_peer.c

@@ -600,13 +600,14 @@ int notification_resp_callback_f(
 		}
 	} else if(code == 408) {
 		if(STR_EQ(node->orig_uri, dmq_notification_address)) {
-			LM_ERR("not deleting notification_peer\n");
-			update_dmq_node_status(dmq_node_list, node, DMQ_NODE_PENDING);	
+			LM_ERR("not deleting notification peer [%.*s]\n",
+					STR_FMT(&dmq_notification_address));
+			update_dmq_node_status(dmq_node_list, node, DMQ_NODE_PENDING);
 			return 0;
 		}
 		if (node->status == DMQ_NODE_DISABLED) {
 			/* deleting node - the server did not respond */
-			LM_ERR("deleting server %.*s because of failed request\n",
+			LM_ERR("deleting server node %.*s because of failed request\n",
 				STR_FMT(&node->orig_uri));
 			ret = del_dmq_node(dmq_node_list, node);
 			LM_DBG("del_dmq_node returned %d\n", ret);

+ 4 - 2
src/modules/dmq/worker.c

@@ -15,8 +15,8 @@
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
- * You should have received a copy of the GNU General Public License 
- * along with this program; if not, write to the Free Software 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
@@ -29,6 +29,7 @@
 #include "../../core/sip_msg_clone.h"
 #include "../../core/parser/parse_from.h"
 #include "../../core/parser/parse_to.h"
+#include "../../core/cfg/cfg_struct.h"
 
 /**
  * @brief set the body of a response
@@ -88,6 +89,7 @@ void worker_loop(int id)
 		} else {
 			sleep_us(dmq_worker_usleep);
 		}
+		cfg_update();
 
 		/* remove from queue until empty */
 		while(job_queue_size(worker->queue) > 0) {

+ 2 - 2
src/modules/htable/htable.c

@@ -484,7 +484,7 @@ static int ht_rm_items(sip_msg_t* msg, str* hname, str* op, str *val,
 		case 2:
 			if(strncmp(op->s, "re", 2)==0) {
 				isval.s = *val;
-				if (ht_dmq_replicate_action(HT_DMQ_RM_CELL_RE, &ht->name, NULL,
+				if ((ht->dmqreplicate > 0) && ht_dmq_replicate_action(HT_DMQ_RM_CELL_RE, &ht->name, NULL,
 							AVP_VAL_STR, &isval, mkey)!=0) {
 					LM_ERR("dmq relication failed (op %d)\n", mkey);
 				}
@@ -494,7 +494,7 @@ static int ht_rm_items(sip_msg_t* msg, str* hname, str* op, str *val,
 				return 1;
 			} else if(strncmp(op->s, "sw", 2)==0) {
 				isval.s = *val;
-				if (ht_dmq_replicate_action(HT_DMQ_RM_CELL_SW, &ht->name, NULL,
+				if ((ht->dmqreplicate > 0) &&ht_dmq_replicate_action(HT_DMQ_RM_CELL_SW, &ht->name, NULL,
 							AVP_VAL_STR, &isval, mkey)!=0) {
 					LM_ERR("dmq relication failed (op %d)\n", mkey);
 				}

+ 6 - 6
src/modules/ims_charging/Ro_data.h

@@ -131,9 +131,9 @@ typedef struct _str_list_t_slot {
     struct _str_list_t_slot *prev, *next;
 } str_list_slot_t;
 
-typedef struct {
+typedef struct str_htlist {
     str_list_slot_t *head, *tail;
-} str_list_t;
+} str_htlist_t;
 
 #define str_list_t_free(x,mem) \
 do{\
@@ -163,7 +163,7 @@ typedef struct {
 
 typedef struct {
     str *application_server;
-    str_list_t application_provided_called_party_address;
+    str_htlist_t application_provided_called_party_address;
 } as_info_t;
 
 typedef struct _as_info_list_t_slot {
@@ -194,7 +194,7 @@ do{\
 #define as_info_list_t_copy(dst,src,mem) \
 do {\
 	str_dup_ptr_ptr((dst)->info.application_server,(src)->info.application_server,mem);\
-	WL_DUP_ALL(&((dst)->info.application_provided_called_party_address),&((src)->info.application_provided_called_party_address),str_list_t,mem);\
+	WL_DUP_ALL(&((dst)->info.application_provided_called_party_address),&((src)->info.application_provided_called_party_address),str_htlist_t,mem);\
 } while(0)
 
 typedef struct {
@@ -268,9 +268,9 @@ typedef struct {
     str *user_session_id;
     str *outgoing_session_id;
 
-    str_list_t calling_party_address;
+    str_htlist_t calling_party_address;
     str *called_party_address;
-    str_list_t called_asserted_identity;
+    str_htlist_t called_asserted_identity;
     str *requested_party_address;
     str *access_network_info;
     str *app_provided_party;

+ 12 - 3
src/modules/ims_diameter_server/ims_diameter_server.c

@@ -280,6 +280,10 @@ int diameter_request(struct sip_msg * msg, char* peer, char* appid, char* comman
 		}
 		LM_DBG("Peer %.*s\n", s_peer.len, s_peer.s);
 	}
+	if (get_str_fparam(&s_message, msg, (fparam_t*)message) < 0) {
+		LM_ERR("failed to get Message\n");
+		return -1;
+	}
 	if (get_str_fparam(&s_appid, msg, (fparam_t*)appid) < 0) {
 		LM_ERR("failed to get App-ID\n");
 		return -1;
@@ -306,9 +310,14 @@ int diameter_request(struct sip_msg * msg, char* peer, char* appid, char* comman
 	session = cdpb.AAACreateSession(0);
 
 	req = cdpb.AAACreateRequest(i_appid, i_commandcode, Flag_Proxyable, session);
+        if (session) {
+	        cdpb.AAADropSession(session);
+                session = 0;
+        }
+
 	if (!req) goto error1;
 
-	if (addAVPsfromJSON(req, &s_message)) {
+	if (!addAVPsfromJSON(req, &s_message)) {
 		LM_ERR("Failed to parse JSON Request\n");
 		return -1;
 	}
@@ -322,7 +331,7 @@ int diameter_request(struct sip_msg * msg, char* peer, char* appid, char* comman
 		} else {
 			resp = cdpb.AAASendRecvMessageToPeer(req, &s_peer);
 			LM_DBG("Successfully sent diameter\n");
-			if (AAAmsg2json(resp, &responsejson) == 1) {
+			if (resp && AAAmsg2json(resp, &responsejson) == 1) {
 				return 1;
 			} else {
 				LM_ERR("Failed to convert response to JSON\n");
@@ -337,7 +346,7 @@ int diameter_request(struct sip_msg * msg, char* peer, char* appid, char* comman
 		} else {
 			resp = cdpb.AAASendRecvMessage(req);
 			LM_DBG("Successfully sent diameter\n");
-			if (AAAmsg2json(resp, &responsejson) == 1) {
+			if (resp && AAAmsg2json(resp, &responsejson) == 1) {
 				return 1;
 			} else {
 				LM_ERR("Failed to convert response to JSON\n");

+ 25 - 0
src/modules/jwt/Makefile

@@ -0,0 +1,25 @@
+#
+# 
+# WARNING: do not run this directly, it should be run by the main Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=jwt.so
+
+ifeq ($(CROSS_COMPILE),)
+JWT_BUILDER=$(shell \
+	if pkg-config --exists libjwt; then \
+		echo 'pkg-config libjwt'; \
+	fi)
+endif
+
+ifneq ($(JWT_BUILDER),)
+	DEFS += $(shell $(JWT_BUILDER) --cflags)
+	LIBS += $(shell $(JWT_BUILDER) --libs)
+else
+	DEFS += -I$(LOCALBASE)/include
+	LIBS += -L$(LOCALBASE)/lib -ljwt
+endif
+
+include ../../Makefile.modules
+

+ 176 - 0
src/modules/jwt/README

@@ -0,0 +1,176 @@
+JWT Module
+
+Daniel-Constantin Mierla
+
+   <[email protected]>
+
+Edited by
+
+Daniel-Constantin Mierla
+
+   <[email protected]>
+
+   Copyright © 2021 asipto.com
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+        2. Dependencies
+
+              2.1. Kamailio Modules
+              2.2. External Libraries or Applications
+
+        3. Parameters
+
+              3.1. key_mode (int)
+
+        4. Functions
+
+              4.1. jwt_generate(prvkey, alg, claims)
+              4.2. jwt_verify(pubkey, alg, claims, jwtval)
+
+        5. Variables
+
+              5.1. $jwt(key)
+
+   List of Examples
+
+   1.1. Set key_mode parameter
+   1.2. jwt_generate usage
+   1.3. jwt_verify usage
+   1.4. $jwt(name) usage
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+   2. Dependencies
+
+        2.1. Kamailio Modules
+        2.2. External Libraries or Applications
+
+   3. Parameters
+
+        3.1. key_mode (int)
+
+   4. Functions
+
+        4.1. jwt_generate(prvkey, alg, claims)
+        4.2. jwt_verify(pubkey, alg, claims, jwtval)
+
+   5. Variables
+
+        5.1. $jwt(key)
+
+1. Overview
+
+   This module provides JWT (JSON Web Token) functions to be used in
+   Kamailio configuration file.
+
+   It relies on libjwt (at least v1.12.0) library
+   (https://github.com/benmcollins/libjwt).
+
+2. Dependencies
+
+   2.1. Kamailio Modules
+   2.2. External Libraries or Applications
+
+2.1. Kamailio Modules
+
+   The following modules must be loaded before this module:
+     * none.
+
+2.2. External Libraries or Applications
+
+   The following libraries or applications must be installed before
+   running Kamailio with this module loaded:
+     * libjwt - minimum version 1.12.0.
+
+3. Parameters
+
+   3.1. key_mode (int)
+
+3.1. key_mode (int)
+
+   Mode to use the private and public keys. If set to 0, they are read
+   always from the disk. If set to 1, they are cached in memory with the
+   first use (no reload support yet).
+
+   Default value is 0.
+
+   Example 1.1. Set key_mode parameter
+...
+modparam("jwt", "key_mode", 1)
+...
+
+4. Functions
+
+   4.1. jwt_generate(prvkey, alg, claims)
+   4.2. jwt_verify(pubkey, alg, claims, jwtval)
+
+4.1.  jwt_generate(prvkey, alg, claims)
+
+   Generate the JWT, its value can be retrieved in the variable $jwt(val).
+
+   The parameters are:
+     * prvkey - path to private key
+     * alg - the algoritm to build the signature, as supported by the
+       libjwt (e.g., RS256, HS256, ES256, ...)
+     * claims - the list of claims to be added to JWT, in the format
+       "name1=value1;name2=value2;..." (same as the SIP parameters
+       format).
+
+   This function can be used from ANY_ROUTE.
+
+   Example 1.2. jwt_generate usage
+...
+  jwt_generate("/path/to/prvkey.pem", "RS256",
+        "caller=$fU;callee=$tU;callid=$ci");
+...
+
+4.2.  jwt_verify(pubkey, alg, claims, jwtval)
+
+   Verify the JWT.
+
+   The parameters are:
+     * pubkey - path to public key
+     * alg - the algoritm to build the signature, as supported by the
+       libjwt (e.g., RS256, HS256, ES256, ...)
+     * claims - the list of claims to be checked they are in the JWT, in
+       the format "name1=value1;name2=value2;..." (same as the SIP
+       parameters format).
+     * jwtval - the value of the JWT to verify
+
+   This function can be used from ANY_ROUTE.
+
+   Example 1.3. jwt_verify usage
+...
+  if(!jwt_verify("/path/to/pubkey.pem", "RS256",
+         "caller=$fU;callee=$tU;callid=$ci",
+        "$var(jwt)") {
+    xwarn("failed to verify jwt\n");
+  }
+...
+
+5. Variables
+
+   5.1. $jwt(key)
+
+5.1.  $jwt(key)
+
+   Get the values and attributes after using JWT functions.
+
+   The key can be:
+     * val - the value of JWT after a successful jwt_generate().
+     * status - the status of verification after a failed jwt_verify().
+
+   Example 1.4. $jwt(name) usage
+...
+  jwt_generate("/path/to/prvkey.pem", "RS256",
+        "caller=$fU;callee=$tU;callid=$ci");
+  xinfo("jwt is: $jwt(val)");
+...

+ 4 - 0
src/modules/jwt/doc/Makefile

@@ -0,0 +1,4 @@
+docs = jwt.xml
+
+docbook_dir = ../../../../doc/docbook
+include $(docbook_dir)/Makefile.module

+ 37 - 0
src/modules/jwt/doc/jwt.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+    <bookinfo>
+	<title>JWT Module</title>
+	<productname class="trade">kamailio.org</productname>
+	<authorgroup>
+	    <author>
+		<firstname>Daniel-Constantin</firstname>
+		<surname>Mierla</surname>
+		<email>[email protected]</email>
+	    </author>
+	    <editor>
+		<firstname>Daniel-Constantin</firstname>
+		<surname>Mierla</surname>
+		<email>[email protected]</email>
+	    </editor>
+	</authorgroup>
+	<copyright>
+	    <year>2021</year>
+	    <holder>asipto.com</holder>
+	</copyright>
+    </bookinfo>
+    <toc></toc>
+
+    <xi:include href="jwt_admin.xml"/>
+
+
+</book>

+ 217 - 0
src/modules/jwt/doc/jwt_admin.xml

@@ -0,0 +1,217 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module User's Guide -->
+
+<chapter>
+
+	<title>&adminguide;</title>
+
+	<section>
+	<title>Overview</title>
+	<para>
+		This module provides JWT (JSON Web Token) functions to be used
+		in &kamailio; configuration file.
+	</para>
+	<para>
+		It relies on libjwt (at least v1.12.0) library (https://github.com/benmcollins/libjwt).
+	</para>
+	</section>
+
+	<section>
+	<title>Dependencies</title>
+	<section>
+		<title>&kamailio; Modules</title>
+		<para>
+		The following modules must be loaded before this module:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>none</emphasis>.
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	<section>
+		<title>External Libraries or Applications</title>
+		<para>
+		The following libraries or applications must be installed before running
+		&kamailio; with this module loaded:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>libjwt</emphasis> - minimum version 1.12.0.
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	</section>
+
+	<section>
+	<title>Parameters</title>
+	<section id="jwt.p.key_mode">
+		<title><varname>key_mode</varname> (int)</title>
+		<para>
+			Mode to use the private and public keys. If set to 0, they are read
+			always from the disk. If set to 1, they are cached in memory with
+			the first use (no reload support yet).
+		</para>
+		<para>
+		<emphasis>
+			Default value is 0.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>key_mode</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("jwt", "key_mode", 1)
+...
+</programlisting>
+		</example>
+	</section>
+
+	</section>
+
+	<section>
+	<title>Functions</title>
+	<section id="jwt.f.jwt_generate">
+	    <title>
+		<function moreinfo="none">jwt_generate(prvkey, alg, claims)</function>
+	    </title>
+	    <para>
+	    Generate the JWT, its value can be retrieved in the variable $jwt(val).
+		</para>
+		<para>
+		The parameters are:
+		</para>
+		<itemizedlist>
+			<listitem>
+			<para>
+			prvkey - path to private key
+			</para>
+			</listitem>
+			<listitem>
+			<para>
+			alg - the algoritm to build the signature, as supported by the
+			libjwt (e.g., RS256, HS256, ES256, ...)
+			</para>
+			</listitem>
+			<listitem>
+			<para>
+			claims - the list of claims to be added to JWT, in the format
+			"name1=value1;name2=value2;..." (same as the SIP parameters format).
+			</para>
+			</listitem>
+		</itemizedlist>
+		<para>
+		This function can be used from ANY_ROUTE.
+		</para>
+		<example>
+		<title><function>jwt_generate</function> usage</title>
+		<programlisting format="linespecific">
+...
+  jwt_generate("/path/to/prvkey.pem", "RS256",
+        "caller=$fU;callee=$tU;callid=$ci");
+...
+</programlisting>
+	    </example>
+	</section>
+
+	<section id="jwt.f.jwt_verify">
+	    <title>
+		<function moreinfo="none">jwt_verify(pubkey, alg, claims, jwtval)</function>
+	    </title>
+	    <para>
+	    Verify the JWT.
+		</para>
+		<para>
+		The parameters are:
+		</para>
+		<itemizedlist>
+			<listitem>
+			<para>
+			pubkey - path to public key
+			</para>
+			</listitem>
+			<listitem>
+			<para>
+			alg - the algoritm to build the signature, as supported by the
+			libjwt (e.g., RS256, HS256, ES256, ...)
+			</para>
+			</listitem>
+			<listitem>
+			<para>
+			claims - the list of claims to be checked they are in the JWT, in the format
+			"name1=value1;name2=value2;..." (same as the SIP parameters format).
+			</para>
+			</listitem>
+			<listitem>
+			<para>
+			jwtval - the value of the JWT to verify
+			</para>
+			</listitem>
+		</itemizedlist>
+		<para>
+		This function can be used from ANY_ROUTE.
+		</para>
+		<example>
+		<title><function>jwt_verify</function> usage</title>
+		<programlisting format="linespecific">
+...
+  if(!jwt_verify("/path/to/pubkey.pem", "RS256",
+         "caller=$fU;callee=$tU;callid=$ci",
+        "$var(jwt)") {
+    xwarn("failed to verify jwt\n");
+  }
+...
+</programlisting>
+	    </example>
+	</section>
+	</section>
+	<section>
+	<title>Variables</title>
+	<section id="jwt.v.jwt">
+	    <title>
+		<function moreinfo="none">$jwt(key)</function>
+	    </title>
+	    <para>
+	    Get the values and attributes after using JWT functions.
+		</para>
+		<para>
+		The key can be:
+		</para>
+		<itemizedlist>
+			<listitem>
+			<para>
+			val - the value of JWT after a successful jwt_generate().
+			</para>
+			</listitem>
+			<listitem>
+			<para>
+			status - the status of verification after a failed jwt_verify().
+			</para>
+			</listitem>
+		</itemizedlist>
+		<example>
+		<title><function>$jwt(name)</function> usage</title>
+		<programlisting format="linespecific">
+...
+  jwt_generate("/path/to/prvkey.pem", "RS256",
+        "caller=$fU;callee=$tU;callid=$ci");
+  xinfo("jwt is: $jwt(val)");
+...
+</programlisting>
+	    </example>
+	</section>
+	</section>
+
+</chapter>

+ 514 - 0
src/modules/jwt/jwt_mod.c

@@ -0,0 +1,514 @@
+/**
+ * Copyright (C) 2021 Daniel-Constantin Mierla (asipto.com)
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <jwt.h>
+
+#include "../../core/sr_module.h"
+#include "../../core/dprint.h"
+#include "../../core/mod_fix.h"
+#include "../../core/lvalue.h"
+#include "../../core/kemi.h"
+#include "../../core/parser/parse_param.h"
+
+
+MODULE_VERSION
+
+static int  mod_init(void);
+static int  child_init(int);
+static void mod_destroy(void);
+
+static int w_jwt_generate(sip_msg_t* msg, char* pkey, char* palg, char* pclaims);
+static int w_jwt_verify(sip_msg_t* msg, char* pkey, char* palg, char* pclaims,
+		char *pjwtval);
+
+static int _jwt_key_mode = 0;
+
+static str _jwt_result = STR_NULL;
+static unsigned int _jwt_verify_status = 0;
+
+typedef struct jwt_fcache {
+	str fname;
+	str fdata;
+	struct jwt_fcache *next;
+} jwt_fcache_t;
+
+static jwt_fcache_t *_jwt_fcache_list = NULL;
+
+static cmd_export_t cmds[]={
+	{"jwt_generate", (cmd_function)w_jwt_generate, 3,
+		fixup_spve_all, 0, ANY_ROUTE},
+	{"jwt_verify", (cmd_function)w_jwt_verify, 4,
+		fixup_spve_all, 0, ANY_ROUTE},
+	{0, 0, 0, 0, 0, 0}
+};
+
+static param_export_t params[]={
+	{ "key_mode", PARAM_INT, &_jwt_key_mode },
+
+	{ 0, 0, 0 }
+};
+
+static int jwt_pv_get(sip_msg_t *msg, pv_param_t *param, pv_value_t *res);
+static int jwt_pv_parse_name(pv_spec_t *sp, str *in);
+static pv_export_t mod_pvs[] = {
+	{ {"jwt",  sizeof("jwt")-1}, PVT_OTHER,  jwt_pv_get,    0,
+			jwt_pv_parse_name, 0, 0, 0 },
+	{ {0, 0}, 0, 0, 0, 0, 0, 0, 0 }
+};
+
+struct module_exports exports = {
+	"jwt",          /* module name */
+	DEFAULT_DLFLAGS, /* dlopen flags */
+	cmds,            /* cmd (cfg function) exports */
+	params,          /* param exports */
+	0,               /* RPC method exports */
+	mod_pvs,         /* pseudo-variables exports */
+	0,               /* response handling function */
+	mod_init,        /* module init function */
+	child_init,      /* per-child init function */
+	mod_destroy      /* module destroy function */
+};
+
+
+/**
+ * @brief Initialize crypto module function
+ */
+static int mod_init(void)
+{
+	return 0;
+}
+
+/**
+ * @brief Initialize crypto module children
+ */
+static int child_init(int rank)
+{
+	return 0;
+}
+
+/**
+ * destroy module function
+ */
+static void mod_destroy(void)
+{
+	return;
+}
+
+/**
+ *
+ */
+static int jwt_fcache_get(str *key, str *kdata)
+{
+	jwt_fcache_t *fc = NULL;
+
+	if(_jwt_key_mode!=1) {
+		return -1;
+	}
+	for(fc=_jwt_fcache_list; fc!=NULL; fc=fc->next) {
+		if(fc->fname.len==key->len
+				&& strncmp(fc->fname.s, key->s, key->len)==0) {
+			LM_DBG("file found in cache: %.*s\n", key->len, key->s);
+			*kdata = fc->fdata;
+			break;
+		}
+	}
+	return 0;
+}
+
+/**
+ *
+ */
+static int jwt_fcache_add(str *key, str *kdata)
+{
+	jwt_fcache_t *fc = NULL;
+
+	if(_jwt_key_mode!=1) {
+		return -1;
+	}
+	fc = (jwt_fcache_t*)pkg_malloc(sizeof(jwt_fcache_t) + key->len
+			+ kdata->len + 2);
+	if(fc==NULL) {
+		PKG_MEM_ERROR;
+		return -1;
+	}
+	memset(fc, 0, sizeof(jwt_fcache_t) + key->len + kdata->len + 2);
+	fc->fname.s = (char*)fc + sizeof(jwt_fcache_t);
+	fc->fname.len = key->len;
+	memcpy(fc->fname.s, key->s, key->len);
+	fc->fdata.s = fc->fname.s + fc->fname.len + 1;
+	fc->fdata.len = kdata->len;
+	memcpy(fc->fdata.s, kdata->s, kdata->len);
+	fc->next = _jwt_fcache_list;
+	_jwt_fcache_list = fc;
+
+	return 0;
+}
+
+/**
+ *
+ */
+static int ki_jwt_generate(sip_msg_t* msg, str *key, str *alg, str *claims)
+{
+	str dupclaims = STR_NULL;
+	str sparams = STR_NULL;
+	str kdata = STR_NULL;
+	jwt_alg_t valg = JWT_ALG_NONE;
+	time_t iat;
+	FILE *fpk = NULL;
+	unsigned char keybuf[10240];
+	size_t keybuf_len = 0;
+	param_t* params_list = NULL;
+	param_hooks_t phooks;
+	param_t *pit = NULL;
+	int ret = 0;
+	jwt_t *jwt = NULL;
+
+	if(key==NULL || key->s==NULL || alg==NULL || alg->s==NULL
+			|| claims==NULL || claims->s==NULL || claims->len<=0) {
+		LM_ERR("invalid parameters\n");
+		return -1;
+	}
+	if(_jwt_result.s != NULL) {
+		jwt_free_str(_jwt_result.s);
+		_jwt_result.s = NULL;
+		_jwt_result.len = 0;
+	}
+	valg = jwt_str_alg(alg->s);
+	if (valg == JWT_ALG_INVAL) {
+		LM_ERR("not supported algorithm: %s\n", alg->s);
+		return -1;
+	}
+	if(pkg_str_dup(&dupclaims, claims)<0) {
+		LM_ERR("failed to duplicate claims\n");
+		return -1;
+	}
+	jwt_fcache_get(key, &kdata);
+	if(kdata.s==NULL) {
+		fpk= fopen(key->s, "r");
+		if(fpk==NULL) {
+			LM_ERR("failed to read key file: %s\n", key->s);
+			goto error;
+		}
+		keybuf_len = fread(keybuf, 1, sizeof(keybuf), fpk);
+		fclose(fpk);
+		if(keybuf_len==0) {
+			LM_ERR("unable to read key file content: %s\n", key->s);
+			goto error;
+		}
+		keybuf[keybuf_len] = '\0';
+		kdata.s = (char*)keybuf;
+		kdata.len = (int)keybuf_len;
+		jwt_fcache_add(key, &kdata);
+	}
+	sparams = dupclaims;
+	if(sparams.s[sparams.len-1]==';') {
+		sparams.len--;
+	}
+	if (parse_params(&sparams, CLASS_ANY, &phooks, &params_list)<0) {
+		LM_ERR("failed to parse claims\n");
+		goto error;
+	}
+
+	ret = jwt_new(&jwt);
+	if (ret != 0 || jwt == NULL) {
+		LM_ERR("failed to initialize jwt\n");
+		goto error;
+	}
+
+	iat = time(NULL);
+
+	ret = jwt_add_grant_int(jwt, "iat", iat);
+	for (pit = params_list; pit; pit=pit->next) {
+		if(pit->name.len>0 && pit->body.len>0) {
+			pit->name.s[pit->name.len] = '\0';
+			pit->body.s[pit->body.len] = '\0';
+			jwt_add_grant(jwt, pit->name.s, pit->body.s);
+		}
+	}
+
+	ret = jwt_set_alg(jwt, valg, (unsigned char*)kdata.s, (size_t)kdata.len);
+	if (ret < 0) {
+		LM_ERR("failed to set algorithm and key\n");
+		goto error;
+	}
+
+	_jwt_result.s = jwt_encode_str(jwt);
+	_jwt_result.len = strlen(_jwt_result.s);
+
+	free_params(params_list);
+	pkg_free(dupclaims.s);
+	jwt_free(jwt);
+
+	return 1;
+
+error:
+	if(params_list!=NULL) {
+		free_params(params_list);
+	}
+	if(dupclaims.s!=NULL) {
+		pkg_free(dupclaims.s);
+	}
+	if(jwt!=NULL) {
+		jwt_free(jwt);
+	}
+	return -1;
+}
+
+/**
+ *
+ */
+static int w_jwt_generate(sip_msg_t* msg, char* pkey, char* palg, char* pclaims)
+{
+	str skey = STR_NULL;
+	str salg = STR_NULL;
+	str sclaims = STR_NULL;
+
+	if (fixup_get_svalue(msg, (gparam_t*)pkey, &skey) != 0) {
+		LM_ERR("cannot get path to the key file\n");
+		return -1;
+	}
+	if (fixup_get_svalue(msg, (gparam_t*)palg, &salg) != 0) {
+		LM_ERR("cannot get algorithm value\n");
+		return -1;
+	}
+
+	if (fixup_get_svalue(msg, (gparam_t*)pclaims, &sclaims) != 0) {
+		LM_ERR("cannot get claims value\n");
+		return -1;
+	}
+
+	return ki_jwt_generate(msg, &skey, &salg, &sclaims);
+}
+
+/**
+ *
+ */
+static int ki_jwt_verify(sip_msg_t* msg, str *key, str *alg, str *claims,
+		str *jwtval)
+{
+	str dupclaims = STR_NULL;
+	jwt_alg_t valg = JWT_ALG_NONE;
+	str kdata = STR_NULL;
+	time_t iat;
+	FILE *fpk = NULL;
+	unsigned char keybuf[10240];
+	size_t keybuf_len = 0;
+	param_t* params_list = NULL;
+	param_hooks_t phooks;
+	param_t *pit = NULL;
+	int ret = 0;
+	jwt_t *jwt = NULL;
+	jwt_valid_t *jwt_valid = NULL;
+	str sparams = STR_NULL;
+
+	if(key==NULL || key->s==NULL || alg==NULL || alg->s==NULL
+			|| claims==NULL || claims->s==NULL || claims->len<=0
+			|| jwtval==NULL || jwtval->s==NULL || jwtval->len<=0) {
+		LM_ERR("invalid parameters\n");
+		return -1;
+	}
+
+	_jwt_verify_status = 0;
+
+	valg = jwt_str_alg(alg->s);
+	if (valg == JWT_ALG_INVAL) {
+		LM_ERR("not supported algorithm: %s\n", alg->s);
+		return -1;
+	}
+	if(pkg_str_dup(&dupclaims, claims)<0) {
+		LM_ERR("failed to duplicate claims\n");
+		return -1;
+	}
+	jwt_fcache_get(key, &kdata);
+	if(kdata.s==NULL) {
+		fpk= fopen(key->s, "r");
+		if(fpk==NULL) {
+			LM_ERR("failed to read key file: %s\n", key->s);
+			goto error;
+		}
+		keybuf_len = fread(keybuf, 1, sizeof(keybuf), fpk);
+		fclose(fpk);
+		if(keybuf_len==0) {
+			LM_ERR("unable to read key file content: %s\n", key->s);
+			goto error;
+		}
+		keybuf[keybuf_len] = '\0';
+		kdata.s = (char*)keybuf;
+		kdata.len = (int)keybuf_len;
+		jwt_fcache_add(key, &kdata);
+	}
+	sparams = dupclaims;
+	if(sparams.s[sparams.len-1]==';') {
+		sparams.len--;
+	}
+	if (parse_params(&sparams, CLASS_ANY, &phooks, &params_list)<0) {
+		LM_ERR("failed to parse claims\n");
+		goto error;
+	}
+
+	ret = jwt_valid_new(&jwt_valid, valg);
+	if (ret != 0 || jwt_valid == NULL) {
+		LM_ERR("failed to initialize jwt valid\n");
+		goto error;
+	}
+
+	iat = time(NULL);
+	jwt_valid_set_headers(jwt_valid, 1);
+	jwt_valid_set_now(jwt_valid, iat);
+
+	for (pit = params_list; pit; pit=pit->next) {
+		if(pit->name.len>0 && pit->body.len>0) {
+			pit->name.s[pit->name.len] = '\0';
+			pit->body.s[pit->body.len] = '\0';
+			jwt_valid_add_grant(jwt_valid, pit->name.s, pit->body.s);
+		}
+	}
+
+	ret = jwt_decode(&jwt, jwtval->s, (unsigned char*)kdata.s, (size_t)kdata.len);
+	if (ret!=0 || jwt==NULL) {
+		LM_ERR("failed to decode jwt value\n");
+		goto error;
+	}
+	if (jwt_validate(jwt, jwt_valid) != 0) {
+		_jwt_verify_status = jwt_valid_get_status(jwt_valid);
+		LM_ERR("failed to validate jwt: %08x\n", _jwt_verify_status);
+		goto error;
+	}
+
+	free_params(params_list);
+	pkg_free(dupclaims.s);
+	jwt_free(jwt);
+	jwt_valid_free(jwt_valid);
+
+	return 1;
+
+error:
+	if(params_list!=NULL) {
+		free_params(params_list);
+	}
+	if(dupclaims.s!=NULL) {
+		pkg_free(dupclaims.s);
+	}
+	if(jwt!=NULL) {
+		jwt_free(jwt);
+	}
+	if(jwt_valid!=NULL) {
+		jwt_valid_free(jwt_valid);
+	}
+	return -1;
+}
+
+/**
+ *
+ */
+static int w_jwt_verify(sip_msg_t* msg, char* pkey, char* palg, char* pclaims,
+		char *pjwtval)
+{
+	str skey = STR_NULL;
+	str salg = STR_NULL;
+	str sclaims = STR_NULL;
+	str sjwtval = STR_NULL;
+
+	if (fixup_get_svalue(msg, (gparam_t*)pkey, &skey) != 0) {
+		LM_ERR("cannot get path to the key file\n");
+		return -1;
+	}
+	if (fixup_get_svalue(msg, (gparam_t*)palg, &salg) != 0) {
+		LM_ERR("cannot get algorithm value\n");
+		return -1;
+	}
+	if (fixup_get_svalue(msg, (gparam_t*)pclaims, &sclaims) != 0) {
+		LM_ERR("cannot get claims value\n");
+		return -1;
+	}
+	if (fixup_get_svalue(msg, (gparam_t*)pjwtval, &sjwtval) != 0) {
+		LM_ERR("cannot get jwt value\n");
+		return -1;
+	}
+
+	return ki_jwt_verify(msg, &skey, &salg, &sclaims, &sjwtval);
+}
+
+/**
+ *
+ */
+static int jwt_pv_get(sip_msg_t *msg, pv_param_t *param, pv_value_t *res)
+{
+	switch(param->pvn.u.isname.name.n)
+	{
+		case 0:
+			if(_jwt_result.s==NULL)
+				return pv_get_null(msg, param, res);
+			return pv_get_strval(msg, param, res, &_jwt_result);
+		case 1:
+			return pv_get_uintval(msg, param, res, _jwt_verify_status);
+		default:
+			return pv_get_null(msg, param, res);
+	}
+}
+
+/**
+ *
+ */
+static int jwt_pv_parse_name(pv_spec_t *sp, str *in)
+{
+	if(in->len==3 && strncmp(in->s, "val", 3)==0) {
+		sp->pvp.pvn.u.isname.name.n = 0;
+	} else if(in->len==6 && strncmp(in->s, "status", 6)==0) {
+		sp->pvp.pvn.u.isname.name.n = 1;
+	} else {
+		LM_ERR("unknown inner name [%.*s]\n", in->len, in->s);
+		return -1;
+	}
+	return 0;
+}
+
+/**
+ *
+ */
+/* clang-format off */
+static sr_kemi_t sr_kemi_jwt_exports[] = {
+	{ str_init("jwt"), str_init("jwt_generate"),
+		SR_KEMIP_INT, ki_jwt_generate,
+		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
+			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+	},
+	{ str_init("jwt"), str_init("jwt_verify"),
+		SR_KEMIP_INT, ki_jwt_verify,
+		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
+			SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE }
+	},
+
+	{ {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
+};
+/* clang-format on */
+
+int mod_register(char *path, int *dlflags, void *p1, void *p2)
+{
+	sr_kemi_modules_add(sr_kemi_jwt_exports);
+	return 0;
+}

+ 14 - 0
src/modules/lrkproxy/Makefile

@@ -0,0 +1,14 @@
+#
+# lrkproxy module makefile
+#
+#
+# WARNING: do not run this directly, it should be run by the main Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=lrkproxy.so
+LIBS=
+
+SERLIBPATH=../../lib
+include ../../Makefile.modules
+

+ 318 - 0
src/modules/lrkproxy/README

@@ -0,0 +1,318 @@
+lrkproxy Module
+
+Mojtaba Esfandiari.S
+
+   Nasim Telecom
+
+   Copyright © 2020 Nasim Telecom Inc.
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+        2. LRKProxy Architecture
+
+              2.1. LRKP_Controlling Layer (LRKP_CL)
+              2.2. LRKP_Transport Stateful Layer (LRKP_TSL)
+
+        3. Multiple LRKProxy usage
+        4. Dependencies
+
+              4.1. Kamailio Modules
+              4.2. External Libraries or Applications
+              4.3. Parameters
+
+                    4.3.1. lrkproxy_sock (string)
+                    4.3.2. lrkproxy_disable_tout (integer)
+                    4.3.3. lrkproxy_tout (integer)
+                    4.3.4. lrkproxy_retr (integer)
+                    4.3.5. lrkp_alg (integer)
+                    4.3.6. hash_table_tout (integer)
+                    4.3.7. hash_table_size (integer)
+
+              4.4. Functions
+
+                    4.4.1. set_lrkproxy_set(setid)
+                    4.4.2. lrkproxy_manage([flags [, ip_address]])
+
+   List of Examples
+
+   1.1. Set lrkproxy_sock parameter
+   1.2. Set lrkproxy_disable_tout parameter
+   1.3. Set lrkproxy_tout parameter
+   1.4. Set lrkproxy_retr parameter
+   1.5. Set lrkp_alg parameter
+   1.6. Set hash_table_tout parameter
+   1.7. Set hash_table_size parameter
+   1.8. set_lrkproxy_set usage
+   1.9. lrkproxy_manage usage
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+   2. LRKProxy Architecture
+
+        2.1. LRKP_Controlling Layer (LRKP_CL)
+        2.2. LRKP_Transport Stateful Layer (LRKP_TSL)
+
+   3. Multiple LRKProxy usage
+   4. Dependencies
+
+        4.1. Kamailio Modules
+        4.2. External Libraries or Applications
+        4.3. Parameters
+
+              4.3.1. lrkproxy_sock (string)
+              4.3.2. lrkproxy_disable_tout (integer)
+              4.3.3. lrkproxy_tout (integer)
+              4.3.4. lrkproxy_retr (integer)
+              4.3.5. lrkp_alg (integer)
+              4.3.6. hash_table_tout (integer)
+              4.3.7. hash_table_size (integer)
+
+        4.4. Functions
+
+              4.4.1. set_lrkproxy_set(setid)
+              4.4.2. lrkproxy_manage([flags [, ip_address]])
+
+1. Overview
+
+   This is a module that enables media streams to be relayed via
+   pylrkproxy engine that exist in:
+   https://github.com/mojtabaesfandiari/pylrkproxy It does relaying audio
+   streams between peers in PREROUTING netfilter-hooking section in
+   kernel-space linux. The LRKProxy architecture is composed of two
+   different layers. These layers are independent of each other. For more
+   information about LRKProxy architecture, please visit our paper in
+   ieeexplore: https://ieeexplore.ieee.org/document/9303608
+
+2. LRKProxy Architecture
+
+   2.1. LRKP_Controlling Layer (LRKP_CL)
+   2.2. LRKP_Transport Stateful Layer (LRKP_TSL)
+
+2.1. LRKP_Controlling Layer (LRKP_CL)
+
+   The first layer is developed as User-Space application that allows
+   User-Space to directly access and manipulate cache data buffer and
+   packet buffer in Kernel-Space. This layer gets all information about
+   creating new sessions, active sessions and teardown sessions which is
+   gotten from SDP body during signaling plan and relay them to the
+   LRKP-Transport Stateful Layer (LRKP- TSL).
+
+2.2. LRKP_Transport Stateful Layer (LRKP_TSL)
+
+   The second layer is developed in Kernel-Space as a main decision point
+   for RTP admission controller and Quickpath selector to where a received
+   packet should be forwarded with power of packet mangling framework in
+   the network stack.
+
+   The LRKP_CL and LRKP-TSL could be run as independence functions on
+   different machines. We could have one LRKP_CL with multiple LRKP-TSL on
+   different machines. The LRKP_CL could works with all LRKP-TSL with
+   different strategies(lrkp_alg parameter).
+
+3. Multiple LRKProxy usage
+
+   The LRKP_CL Layer can support multiple LRKP_TSL Layer for
+   balancing/distribution and control/selection purposes.
+
+   The module allows definition of several sets of LRKP_TSL.
+   Load-balancing will be performed over predefine algorithm by setting
+   lrkp_alg parameter.
+
+   IMPORTANT: This module does not support balancing inside a set like as
+   is done RTPProxy module based on the weight of each rtpproxy from the
+   set. The balancing would be run on different machine
+
+4. Dependencies
+
+   4.1. Kamailio Modules
+   4.2. External Libraries or Applications
+   4.3. Parameters
+
+        4.3.1. lrkproxy_sock (string)
+        4.3.2. lrkproxy_disable_tout (integer)
+        4.3.3. lrkproxy_tout (integer)
+        4.3.4. lrkproxy_retr (integer)
+        4.3.5. lrkp_alg (integer)
+        4.3.6. hash_table_tout (integer)
+        4.3.7. hash_table_size (integer)
+
+   4.4. Functions
+
+        4.4.1. set_lrkproxy_set(setid)
+        4.4.2. lrkproxy_manage([flags [, ip_address]])
+
+4.1. Kamailio Modules
+
+   The following modules must be loaded before this module:
+     * tm module - (optional) if you want to have lrkproxy_manage() fully
+       functional
+
+4.2. External Libraries or Applications
+
+   The following libraries or applications must be installed before
+   running Kamailio with this module loaded:
+     * None.
+
+4.3. Parameters
+
+4.3.1. lrkproxy_sock (string)
+
+   Used to define the list of LRKP_TSL instances to connect to. These can
+   be UNIX sockets or IPv4/IPv6 UDP sockets. Each modparam entry will
+   insert sockets into a single set with default value set ID '0'. To
+   define multiple LRKP_TSL, just add the instances in each modparam.
+
+   Default value is “NONE” (disabled).
+
+   Example 1.1. Set lrkproxy_sock parameter
+...
+# single lrkproxy
+modparam("lrkproxy", "lrkproxy_sock", "udp:192.168.122.108:8080")
+
+# multiple lrkproxies for LB in diffenrent machine
+modparam("lrkproxy", "lrkproxy_sock", "udp:192.168.122.108:8080")
+modparam("lrkproxy", "lrkproxy_sock", "udp:192.168.122.109:8080")
+
+...
+
+4.3.2. lrkproxy_disable_tout (integer)
+
+   Once LRKP_TSL was found unreachable and marked as disabled, the LRKP_CL
+   module will not attempt to establish communication to LRKP_TSL for
+   lrkproxy_disable_tout seconds.
+
+   Default value is “60”.
+
+   Example 1.2. Set lrkproxy_disable_tout parameter
+...
+modparam("lrkproxy", "lrkproxy_disable_tout", 20)
+...
+
+4.3.3. lrkproxy_tout (integer)
+
+   Timeout value in waiting for reply from LRKP_TSL.
+
+   Default value is “1”.
+
+   Example 1.3. Set lrkproxy_tout parameter
+...
+modparam("lrkproxy", "lrkproxy_tout", 2)
+...
+
+4.3.4. lrkproxy_retr (integer)
+
+   How many times the LRKP_CL should retry to send and receive after
+   timeout was generated.
+
+   Default value is “5”.
+
+   Example 1.4. Set lrkproxy_retr parameter
+...
+modparam("lrkproxy", "lrkproxy_retr", 2)
+...
+
+4.3.5. lrkp_alg (integer)
+
+   This parameter set the algorithm of LRKP_TSL selection. lrk_LINER=0,
+   lrk_RR=1
+
+   Default value is “0”.
+
+   Example 1.5. Set lrkp_alg parameter
+...
+modparam("lrkproxy", "lrkp_alg", 1)
+...
+
+4.3.6. hash_table_tout (integer)
+
+   Number of seconds after an lrkproxy hash table entry is marked for
+   deletion. By default, this parameter is set to 3600 (seconds).
+
+   To maintain information about a selected rtp machine node, for a given
+   call, entries are added in a hashtable of (callid, viabranch) pairs.
+   When command comes, lookup callid, viabranch pairs. If found, return
+   chosen node. If not found, choose a new node, insert it in the hastable
+   and return the chosen node.
+
+   NOTE: In the current implementation, the actual deletion happens on the
+   fly, while insert/remove/lookup the hastable, only for the entries in
+   the insert/remove/lookup path.
+
+   NOTE: When configuring this parameter, one should consider maximum call
+   time VS share memory for unfinished calls.
+
+   Default value is “3600”.
+
+   Example 1.6. Set hash_table_tout parameter
+...
+modparam("lrkproxy", "hash_table_tout", "3600")
+...
+
+4.3.7. hash_table_size (integer)
+
+   Size of the hash table. Default value is 128.
+
+   Default value is “128”.
+
+   Example 1.7. Set hash_table_size parameter
+...
+modparam("lrkproxy", "hash_table_size", 256)
+...
+
+4.4. Functions
+
+4.4.1.  set_lrkproxy_set(setid)
+
+   Sets the Id of the lrkproxy set to be used for the next
+   lrkproxy_manage() command. The parameter can be an integer or a config
+   variable holding an integer.
+
+   This function can be used from REQUEST_ROUTE, ONREPLY_ROUTE,
+   BRANCH_ROUTE.
+
+   Example 1.8. set_lrkproxy_set usage
+...
+set_lrkproxy_set("0");
+lrkproxy_manage();
+...
+
+4.4.2.  lrkproxy_manage([flags [, ip_address]])
+
+   Manage the LRKProxy session - it combines the functionality of
+   lrkproxy_offer(), lrkproxy_answer() and unforce_lrkproxy(), detecting
+   internally based on message type and method which one to execute.
+
+   IMPORTANT:The LRKProxy just has one function relating rtp packets. It
+   does not support combination of functionality of lrkproxy_offer(),
+   lrkproxy_answer() and unforce_lrkproxy() and other etc. So you have to
+   just use lrkproxy_manage.
+
+   Meaning of the parameters is as follows:
+     * flags - flags to turn on some features.
+          + internal,external - The shorthand of this flag is "ie". This
+            can be used to relay media sessions between two different NIC
+            from internal to external path.
+          + external,internal - The shorthand of this flag is "ei". This
+            can be used to relay media sessions between two different NIC
+            from external to internal path.
+     * ip_address - new SDP IP address.This optional parameter is under
+       development.
+
+   This function can be used from ANY_ROUTE.
+
+   Example 1.9. lrkproxy_manage usage
+...
+lrkproxy_manage();
+//or
+lrkproxy_manage("ie");
+//or
+lrkproxy_manage("ei");
+
+...

+ 4 - 0
src/modules/lrkproxy/doc/Makefile

@@ -0,0 +1,4 @@
+docs = lrkproxy.xml
+
+docbook_dir = ../../../../doc/docbook
+include $(docbook_dir)/Makefile.module

+ 33 - 0
src/modules/lrkproxy/doc/lrkproxy.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+	<bookinfo>
+	<title>lrkproxy Module</title>
+	<productname class="trade">&kamailioname;</productname>
+	<authorgroup>
+		<author>
+		<firstname>Mojtaba</firstname>
+		<surname>Esfandiari.S</surname>
+		<affiliation><orgname>Nasim Telecom</orgname></affiliation>
+		<address>
+			<email>[email protected]</email>
+		</address>
+		</author>
+	</authorgroup>
+	<copyright>
+		<year>2020</year>
+		<holder><ulink url='https://www.nasimtelecom.com/en/'>Nasim Telecom Inc.</ulink></holder>
+	</copyright>
+	</bookinfo>
+	<toc></toc>
+	
+	<xi:include href="lrkproxy_admin.xml"/>
+</book>

+ 366 - 0
src/modules/lrkproxy/doc/lrkproxy_admin.xml

@@ -0,0 +1,366 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+
+<!-- Module User's Guide -->
+
+<chapter>
+
+	<title>&adminguide;</title>
+
+	<section>
+	<title>Overview</title>
+	<para>
+		This is a module that enables media streams to be relayed via
+ 		pylrkproxy engine that exist in:
+		https://github.com/mojtabaesfandiari/pylrkproxy
+		It does relaying audio streams between peers in
+		PREROUTING netfilter-hooking section in kernel-space linux.
+		The LRKProxy architecture is composed of two
+		different layers. These layers are independent of each
+		other.
+		For more information about LRKProxy architecture, please visit our paper in ieeexplore:
+		https://ieeexplore.ieee.org/document/9303608
+	</para>
+	</section>
+
+	<section>
+	<title>LRKProxy Architecture</title>
+	<section>
+		<title>LRKP_Controlling Layer (LRKP_CL)</title>
+		<para>
+			The first layer is developed as User-Space
+			application that allows User-Space to directly
+			access and manipulate cache data
+			buffer and packet buffer in Kernel-Space. This layer
+			gets all information about creating new sessions,
+			active sessions and teardown sessions which is
+	       		gotten from SDP body during signaling plan and relay
+			them to the LRKP-Transport Stateful Layer (LRKP-
+			TSL).
+		</para>
+	</section>
+	<section>
+		<title>LRKP_Transport Stateful Layer (LRKP_TSL)</title>
+		<para>
+			The second layer is developed in Kernel-Space as
+        		a main decision point for RTP admission controller
+        		and Quickpath selector to where a received packet
+        		should be forwarded with power of packet mangling
+        		framework in the network stack.
+		</para>
+	</section>
+	<para>
+		The LRKP_CL and LRKP-TSL could be run as
+		independence functions on different machines. We
+		could have one LRKP_CL with multiple LRKP-TSL
+		on different machines. The LRKP_CL could works
+		with all LRKP-TSL with different strategies(lrkp_alg parameter).
+	</para>
+	</section>
+	<section>
+	<title>Multiple LRKProxy usage</title>
+	<para>
+		The LRKP_CL Layer can support multiple LRKP_TSL Layer
+    		for balancing/distribution and control/selection purposes.
+	</para>
+	<para>
+		The module allows definition of several sets of LRKP_TSL.
+		Load-balancing will be performed over predefine algorithm by setting lrkp_alg parameter.
+
+	</para>
+	<para>
+		IMPORTANT: This module does not support balancing inside a set like as is done RTPProxy module based on
+		the weight of each rtpproxy from the set. The balancing would be run on different machine
+	</para>
+	</section>
+	
+	<section>
+	<title>Dependencies</title>
+	<section>
+		<title>&kamailio; Modules</title>
+		<para>
+		The following modules must be loaded before this module:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>tm module</emphasis> - (optional) if you want to
+				have lrkproxy_manage() fully functional
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	<section>
+		<title>External Libraries or Applications</title>
+		<para>
+		The following libraries or applications must be installed before
+		running &kamailio; with this module loaded:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>None</emphasis>.
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	<section>
+	<title>Parameters</title>
+	<section id="lrkproxy.p.lrkproxy_sock">
+	<title><varname>lrkproxy_sock</varname> (string)</title>
+			<para>
+			Used to define the list of LRKP_TSL instances to connect to. These can
+			be UNIX sockets or IPv4/IPv6 UDP sockets. Each modparam entry will
+   			insert sockets into a single set with default value set ID '0'.
+   			To define multiple LRKP_TSL, just add the instances in each modparam.
+			</para>
+			<para>
+			<emphasis>
+				Default value is <quote>NONE</quote> (disabled).
+			</emphasis>
+			</para>
+			<example>
+			<title>Set <varname>lrkproxy_sock</varname> parameter</title>
+			<programlisting format="linespecific">
+...
+# single lrkproxy
+modparam("lrkproxy", "lrkproxy_sock", "udp:192.168.122.108:8080")
+
+# multiple lrkproxies for LB in diffenrent machine
+modparam("lrkproxy", "lrkproxy_sock", "udp:192.168.122.108:8080")
+modparam("lrkproxy", "lrkproxy_sock", "udp:192.168.122.109:8080")
+
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="lrkproxy.p.lrkproxy_disable_tout">
+		<title><varname>lrkproxy_disable_tout</varname> (integer)</title>
+		<para>
+		Once LRKP_TSL was found unreachable and marked as disabled, the
+   		LRKP_CL module will not attempt to establish communication to LRKP_TSL
+   		for lrkproxy_disable_tout seconds.
+		</para>
+		<para>
+		<emphasis>
+			Default value is <quote>60</quote>.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>lrkproxy_disable_tout</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("lrkproxy", "lrkproxy_disable_tout", 20)
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="lrkproxy.p.lrkproxy_tout">
+		<title><varname>lrkproxy_tout</varname> (integer)</title>
+		<para>
+		Timeout value in waiting for reply from LRKP_TSL.
+		</para>
+		<para>
+		<emphasis>
+			Default value is <quote>1</quote>.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>lrkproxy_tout</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("lrkproxy", "lrkproxy_tout", 2)
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="lrkproxy.p.lrkproxy_retr">
+		<title><varname>lrkproxy_retr</varname> (integer)</title>
+		<para>
+		How many times the LRKP_CL should retry to send and receive after
+		timeout was generated.
+		</para>
+		<para>
+		<emphasis>
+			Default value is <quote>5</quote>.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>lrkproxy_retr</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("lrkproxy", "lrkproxy_retr", 2)
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="lrkproxy.p.lrkp_alg">
+		<title><varname>lrkp_alg</varname> (integer)</title>
+		<para>
+		This parameter set the algorithm of LRKP_TSL selection.
+    		lrk_LINER=0,
+    		lrk_RR=1
+		</para>
+		<para>
+		<emphasis>
+			Default value is <quote>0</quote>.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>lrkp_alg</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("lrkproxy", "lrkp_alg", 1)
+...
+</programlisting>
+		</example>
+	</section>
+
+	<section id="lrkproxy.p.hash_table_tout">
+		<title><varname>hash_table_tout</varname> (integer)</title>
+		<para>
+		Number of seconds after an lrkproxy hash table entry is marked for
+   		deletion. By default, this parameter is set to 3600 (seconds).
+		</para>
+		<para>
+		To maintain information about a selected rtp machine node, for a given
+		call, entries are added in a hashtable of (callid, viabranch) pairs. When
+   		command comes, lookup callid, viabranch pairs. If found, return chosen node. If not
+   		found, choose a new node, insert it in the hastable and return the
+   		chosen node.
+		</para>
+		<para>
+		NOTE: In the current implementation, the actual deletion happens on the
+		fly, while insert/remove/lookup the hastable, only for the entries in
+		the insert/remove/lookup path.
+		</para>
+		<para>
+		NOTE: When configuring this parameter, one should consider maximum call
+		time VS share memory for unfinished calls.
+		</para>
+		<para>
+		<emphasis>
+			Default value is <quote>3600</quote>.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>hash_table_tout</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("lrkproxy", "hash_table_tout", "3600")
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="lrkproxy.p.hash_table_size">
+		<title><varname>hash_table_size</varname> (integer)</title>
+		<para>
+		Size of the hash table. Default value is 128.
+		</para>
+		<para>
+		<emphasis>
+			Default value is <quote>128</quote>.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>hash_table_size</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("lrkproxy", "hash_table_size", 256)
+...
+</programlisting>
+		</example>
+	</section>
+	</section>
+	<section>
+	<title>Functions</title>
+	<section id="lrkproxy.f.set_lrkproxy_set">
+		<title>
+		<function moreinfo="none">set_lrkproxy_set(setid)</function>
+		</title>
+		<para>
+		Sets the Id of the lrkproxy set to be used for the next
+   		lrkproxy_manage() command. The parameter can be an integer or a config
+   		variable holding an integer.
+		</para>
+		<para>
+		This function can be used from REQUEST_ROUTE, ONREPLY_ROUTE,
+		BRANCH_ROUTE.
+		</para>
+		<example>
+		<title><function>set_lrkproxy_set</function> usage</title>
+		<programlisting format="linespecific">
+...
+set_lrkproxy_set("0");
+lrkproxy_manage();
+...
+</programlisting>
+		</example>
+	</section>
+        <section id="lrkproxy.f.lrkproxy_manage">
+                <title>
+                <function moreinfo="none">lrkproxy_manage([flags [, ip_address]])</function>
+                </title>
+                <para>
+                Manage the LRKProxy session - it combines the functionality of
+   		lrkproxy_offer(), lrkproxy_answer() and unforce_lrkproxy(), detecting
+   		internally based on message type and method which one to execute.
+                </para>
+		<para>
+		   IMPORTANT:The LRKProxy just has one function relating rtp packets. 
+		   It does not support combination of functionality of lrkproxy_offer(),
+		   lrkproxy_answer() and unforce_lrkproxy() and other etc.
+		   So you have to just use lrkproxy_manage.
+		</para>
+		<para>Meaning of the parameters is as follows:</para>
+		<itemizedlist>
+		<listitem>
+			<para>
+			<emphasis>flags</emphasis> - flags to turn on some features.
+			</para>
+			<itemizedlist>
+				<listitem><para>
+				<emphasis>internal,external</emphasis> - The shorthand of this flag is "ie".
+				This can be used to relay media sessions between two different NIC from internal to external path.
+				</para></listitem>
+			</itemizedlist>
+			<itemizedlist>
+				<listitem><para>
+				<emphasis>external,internal</emphasis> - The shorthand of this flag is "ei".
+				This can be used to relay media sessions between two different NIC from external to internal path.
+				</para></listitem>
+			</itemizedlist>
+		</listitem>
+		<listitem><para>
+		<emphasis>ip_address</emphasis> - new SDP IP address.This optional parameter is under development.
+		</para></listitem>
+		</itemizedlist>
+		<para>
+		This function can be used from ANY_ROUTE.
+                </para>
+		<example>
+		<title><function>lrkproxy_manage</function> usage</title>
+		<programlisting format="linespecific">
+...
+lrkproxy_manage();
+//or
+lrkproxy_manage("ie");
+//or
+lrkproxy_manage("ei");
+
+...
+</programlisting>
+                </example>
+	</section>
+
+
+	</section>
+	</section>
+</chapter>

+ 1733 - 0
src/modules/lrkproxy/lrkproxy.c

@@ -0,0 +1,1733 @@
+/*
+ * Copyright (C) 2003-2008 Sippy Software, Inc., http://www.sippysoft.com
+ * Copyright (C) 2020 Mojtaba Esfandiari.S, Nasim-Telecom
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#ifndef __USE_BSD
+#define  __USE_BSD
+#endif
+#include <netinet/ip.h>
+#ifndef __FAVOR_BSD
+#define __FAVOR_BSD
+#endif
+#include <netinet/udp.h>
+#include <arpa/inet.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../../core/flags.h"
+#include "../../core/sr_module.h"
+#include "../../core/dprint.h"
+#include "../../core/data_lump.h"
+#include "../../core/data_lump_rpl.h"
+#include "../../core/error.h"
+#include "../../core/forward.h"
+#include "../../core/mem/mem.h"
+#include "../../core/parser/parse_from.h"
+#include "../../core/parser/parse_to.h"
+#include "../../core/parser/parse_uri.h"
+#include "../../core/parser/parser_f.h"
+#include "../../core/parser/sdp/sdp.h"
+#include "../../core/resolve.h"
+#include "../../core/timer.h"
+#include "../../core/trim.h"
+#include "../../core/ut.h"
+#include "../../core/pt.h"
+#include "../../core/timer_proc.h"
+#include "../../core/rpc.h"
+#include "../../core/rpc_lookup.h"
+#include "../../core/pvar.h"
+#include "../../core/lvalue.h"
+#include "../../core/msg_translator.h"
+#include "../../core/usr_avp.h"
+#include "../../core/socket_info.h"
+#include "../../core/mod_fix.h"
+#include "../../core/dset.h"
+#include "../../core/route.h"
+#include "../../core/kemi.h"
+#include "../../modules/tm/tm_load.h"
+#include "lrkproxy.h"
+#include "lrkproxy_hash.h"
+#include "lrkproxy_funcs.h"
+
+MODULE_VERSION
+
+
+#if !defined(AF_LOCAL)
+#define	AF_LOCAL AF_UNIX
+#endif
+#if !defined(PF_LOCAL)
+#define	PF_LOCAL PF_UNIX
+#endif
+
+///* NAT UAC test constants */
+//#define	NAT_UAC_TEST_C_1918	0x01
+//#define	NAT_UAC_TEST_RCVD	0x02
+//#define	NAT_UAC_TEST_V_1918	0x04
+//#define	NAT_UAC_TEST_S_1918	0x08
+//#define	NAT_UAC_TEST_RPORT	0x10
+
+#define DEFAULT_LRKP_SET_ID		0
+static str DEFAULT_LRKP_SET_ID_STR = str_init("0");
+
+//#define RPC_DEFAULT_NATPING_STATE	1
+
+#define RPC_MIN_RECHECK_TICKS		0
+#define RPC_MAX_RECHECK_TICKS		(unsigned int)-1
+
+
+/* Supported version of the LRK proxy command protocol */
+#define	SUP_CPROTOVER	"20190708"
+/* Required additional version of the LRK proxy command protocol */
+#define	REQ_CPROTOVER	"20190709"
+/* Additional version necessary for re-packetization support */
+#define	REP_CPROTOVER	"20190708"
+#define	PTL_CPROTOVER	"20190708"
+
+#define	CPORT		"22333"
+#define HASH_SIZE   128
+
+static char *gencookie();
+static int lrkp_test(struct lrkp_node*);
+static int lrkp_get_config(struct lrkp_node *node);
+static int lrkp_set_conntrack_rule(struct lrkproxy_hash_entry *e);
+
+
+static int lrkproxy_force(struct sip_msg *msg, const char *flags, enum lrk_operation op, int more);
+static int lrkproxy_unforce(struct sip_msg *msg, const char *flags, enum lrk_operation op, int more);
+
+static int lrkproxy_manage0(struct sip_msg *msg, char *flags, char *ip);
+static int lrkproxy_manage1(struct sip_msg *msg, char *flags, char *ip);
+static int lrkproxy_manage2(struct sip_msg *msg, char *flags, char *ip);
+
+static int change_media_sdp(sip_msg_t *msg, struct lrkproxy_hash_entry *e, const char *flags, enum lrk_operation op);
+
+static int add_lrkproxy_socks(struct lrkp_set * lrkp_list, char * lrkproxy);
+static int fixup_set_id(void ** param, int param_no);
+static int set_lrkproxy_set_f(struct sip_msg * msg, char * str1, char * str2);
+
+static struct lrkp_set * select_lrkp_set(int id_set);
+
+static int lrkproxy_set_store(modparam_t type, void * val);
+static int lrkproxy_add_lrkproxy_set( char * lrk_proxies);
+
+static int mod_init(void);
+static int child_init(int);
+static void mod_destroy(void);
+
+/* Pseudo-Variables */
+//static int pv_get_lrkstat_f(struct sip_msg *, pv_param_t *, pv_value_t *);
+
+static int lrkproxy_disable_tout = 60;
+static int lrkproxy_retr = 5;
+static int lrkproxy_tout = 1;
+static pid_t mypid;
+static unsigned int myseqn = 0;
+//static str nolrkproxy_str = str_init("a=nolrkproxy:yes");
+//static str extra_id_pv_param = {NULL, 0};
+
+static char ** lrkp_strings=0;
+static int lrkp_sets=0; /*used in lrkproxy_set_store()*/
+static int lrkp_set_count = 0;
+static unsigned int current_msg_id = (unsigned int)-1;
+/* LRK proxy balancing list */
+struct lrkp_set_head * lrkp_set_list =0;
+struct lrkp_set * selected_lrkp_set =0;
+struct lrkp_set * default_lrkp_set=0;
+struct lrkp_node *selected_lrkp_node = 0;
+int lrkp_algorithm = LRK_LINER;
+static int hash_table_size = 0;
+static int hash_table_tout = 3600;
+
+
+
+//static char *ice_candidate_priority_avp_param = NULL;
+//static int ice_candidate_priority_avp_type;
+//static int_str ice_candidate_priority_avp;
+//static str lrk_inst_pv_param = {NULL, 0};
+//static pv_spec_t *lrk_inst_pvar = NULL;
+
+/* array with the sockets used by lrkproxy (per process)*/
+static unsigned int lrkp_no = 0;
+static int *lrkp_socks = 0;
+
+
+typedef struct lrkp_set_link {
+	struct lrkp_set *rset;
+	pv_spec_t *rpv;
+} lrkp_set_link_t;
+
+/* tm */
+static struct tm_binds tmb;
+
+/*0-> disabled, 1 ->enabled*/
+//unsigned int *natping_state=0;
+
+static cmd_export_t cmds[] = {
+
+        {"set_lrkproxy_set",  (cmd_function)set_lrkproxy_set_f,    1,
+                fixup_set_id, 0,
+                ANY_ROUTE},
+        {"lrkproxy_manage",	(cmd_function)lrkproxy_manage0,     0,
+                                                       0, 0,
+                   ANY_ROUTE},
+        {"lrkproxy_manage",	(cmd_function)lrkproxy_manage1,     1,
+                                                       fixup_spve_null, fixup_free_spve_null,
+                   ANY_ROUTE},
+        {"lrkproxy_manage",	(cmd_function)lrkproxy_manage2,     2,
+                                                       fixup_spve_spve, fixup_free_spve_spve,
+                   ANY_ROUTE},
+
+        {0, 0, 0, 0, 0, 0}
+};
+
+static param_export_t params[] = {
+        {"lrkproxy_sock",         PARAM_STRING|USE_FUNC_PARAM,
+                (void*)lrkproxy_set_store          },
+        {"lrkproxy_disable_tout", INT_PARAM, &lrkproxy_disable_tout },
+        {"lrkproxy_retr",         INT_PARAM, &lrkproxy_retr         },
+        {"lrkproxy_tout",         INT_PARAM, &lrkproxy_tout         },
+        {"lrkp_alg",         INT_PARAM, &lrkp_algorithm         },
+        {"hash_table_tout",       INT_PARAM, &hash_table_tout        },
+        {"hash_table_size",       INT_PARAM, &hash_table_size        },
+
+        {0, 0, 0}
+};
+
+/** module exports */
+struct module_exports exports= {
+        "lrkproxy",        /* module name */
+        DEFAULT_DLFLAGS, /* dlopen flags */
+        cmds,            /* cmd exports */
+        params,          /* param exports */
+        0,               /* RPC method exports */
+        0,         /* exported pseudo-variables */
+        0,               /* response handling function */
+        mod_init,        /* module initialization function */
+        child_init,               /* per-child init function */
+        mod_destroy                /* module destroy function */
+};
+
+
+static int lrkproxy_set_store(modparam_t type, void * val){
+
+    char * p;
+    int len;
+
+    p = (char* )val;
+
+    if(p==0 || *p=='\0'){
+        return 0;
+    }
+
+    if(lrkp_sets==0){
+        lrkp_strings = (char**)pkg_malloc(sizeof(char*));
+        if(!lrkp_strings){
+                    LM_ERR("no pkg memory left\n");
+            return -1;
+        }
+    } else {/*realloc to make room for the current set*/
+        lrkp_strings = (char**)pkg_reallocxf(lrkp_strings,
+                                             (lrkp_sets+1)* sizeof(char*));
+        if(!lrkp_strings){
+                    LM_ERR("no pkg memory left\n");
+            return -1;
+        }
+    }
+
+    /*allocate for the current set of urls*/
+    len = strlen(p);
+    lrkp_strings[lrkp_sets] = (char*)pkg_malloc((len+1)*sizeof(char));
+
+    if(!lrkp_strings[lrkp_sets]){
+                LM_ERR("no pkg memory left\n");
+        return -1;
+    }
+
+    memcpy(lrkp_strings[lrkp_sets], p, len);
+    lrkp_strings[lrkp_sets][len] = '\0';
+    lrkp_sets++;
+
+    return 0;
+}
+
+struct lrkp_set *get_lrkp_set(str *const set_name)
+{
+    unsigned int this_set_id;
+    struct lrkp_set *lrkp_list;
+    if (lrkp_set_list == NULL)
+    {
+                LM_ERR("lrkp set list not configured\n");
+        return NULL;
+    }
+    /* Only integer set_names are valid at the moment */
+    if ((set_name->s == NULL) || (set_name->len == 0))
+    {
+                LM_ERR("Invalid set name '%.*s'\n", set_name->len, set_name->s);
+        return NULL;
+    }
+    if (str2int(set_name, &this_set_id) < 0)
+    {
+                LM_ERR("Invalid set name '%.*s' - must be integer\n", set_name->len, set_name->s);
+        return NULL;
+    }
+
+    lrkp_list = select_lrkp_set(this_set_id);
+
+    if(lrkp_list==NULL){	/*if a new id_set : add a new set of lrkp*/
+        lrkp_list = shm_malloc(sizeof(struct lrkp_set));
+        if(!lrkp_list){
+                    LM_ERR("no shm memory left\n");
+            return NULL;
+        }
+        memset(lrkp_list, 0, sizeof(struct lrkp_set));
+        lrkp_list->id_set = this_set_id;
+        if (lrkp_set_list->lset_first == NULL)
+        {
+            lrkp_set_list->lset_first = lrkp_list;
+        } else {
+            lrkp_set_list->lset_last->lset_next = lrkp_list;
+        }
+        lrkp_set_list->lset_last = lrkp_list;
+        lrkp_set_count++;
+
+        if (this_set_id == DEFAULT_LRKP_SET_ID)
+        {
+            default_lrkp_set = lrkp_list;
+        }
+    }
+    return lrkp_list;
+}
+
+int insert_lrkp_node(struct lrkp_set *const lrkp_list, const str *const url, const int weight, const int enable)
+{
+    struct lrkp_node *pnode;
+
+    if ((pnode = shm_malloc(sizeof(struct lrkp_node) + url->len + 1)) == NULL)
+    {
+                LM_ERR("out of shm memory\n");
+        return -1;
+    }
+
+    memset(pnode, 0, sizeof(struct lrkp_node) + url->len + 1);
+
+
+    struct lrkp_node_conf *node_conf;
+    node_conf = shm_malloc(sizeof(struct lrkp_node_conf));
+    if (!node_conf)
+    {
+                LM_ERR("out of shm memory\n");
+        return -1;
+    }
+
+    memset(node_conf, 0, sizeof(struct lrkp_node_conf));
+    pnode->lrkp_n_c = node_conf;
+
+    pnode->idx = lrkp_no++;
+    pnode->ln_weight = weight;
+    pnode->ln_umode = 0;
+    pnode->ln_enable = enable;
+    /* Permanently disable if marked as disabled */
+//    pnode->ln_recheck_ticks = disabled ? RPC_MAX_RECHECK_TICKS : 0;
+    pnode->ln_url.s = (char*)(pnode + 1);
+    memcpy(pnode->ln_url.s, url->s, url->len);
+    pnode->ln_url.len = url->len;
+
+            LM_DBG("url is '%.*s'\n", pnode->ln_url.len, pnode->ln_url.s);
+
+    /* Find protocol and store address */
+    pnode->ln_address = pnode->ln_url.s;
+    if (strncasecmp(pnode->ln_address, "udp:", 4) == 0) {
+        pnode->ln_umode = 1;
+        pnode->ln_address += 4;
+    } else if (strncasecmp(pnode->ln_address, "udp6:", 5) == 0) {
+        pnode->ln_umode = 6;
+        pnode->ln_address += 5;
+    } else if (strncasecmp(pnode->ln_address, "unix:", 5) == 0) {
+        pnode->ln_umode = 0;
+        pnode->ln_address += 5;
+    }
+
+    if (lrkp_list->ln_first == NULL)
+    {
+        lrkp_list->ln_first = pnode;
+    } else {
+        lrkp_list->ln_last->ln_next = pnode;
+    }
+    lrkp_list->ln_last = pnode;
+    lrkp_list->lrkp_node_count++;
+
+    return 0;
+}
+
+static int add_lrkproxy_socks(struct lrkp_set * lrkp_list,
+                              char * lrkproxy){
+    /* Make lrk proxies list. */
+    char *p, *p1, *p2, *plim;
+    int weight;
+    str url;
+
+    p = lrkproxy;
+    plim = p + strlen(p);
+
+    for(;;) {
+        weight = 1;
+        while (*p && isspace((int)*p))
+            ++p;
+        if (p >= plim)
+            break;
+        p1 = p;
+        while (*p && !isspace((int)*p))
+            ++p;
+        if (p <= p1)
+            break; /* may happen??? */
+        /* Have weight specified? If yes, scan it */
+        p2 = memchr(p1, '=', p - p1);
+        if (p2 != NULL) {
+            weight = strtoul(p2 + 1, NULL, 10);
+        } else {
+            p2 = p;
+        }
+
+        url.s = p1;
+        url.len = (p2-p1);
+        insert_lrkp_node(lrkp_list, &url, weight, 0);
+    }
+    return 0;
+}
+
+/*	0-succes
+ *  -1 - erorr
+ * */
+static int lrkproxy_add_lrkproxy_set( char * lrk_proxies)
+{
+    char *p,*p2;
+    struct lrkp_set * lrkp_list;
+    str id_set;
+
+    /* empty definition? */
+    p= lrk_proxies;
+    if(!p || *p=='\0'){
+        return 0;
+    }
+
+    for(;*p && isspace(*p);p++);
+    if(*p=='\0'){
+        return 0;
+    }
+
+    lrk_proxies = strstr(p, "==");
+    if(lrk_proxies){
+        if(*(lrk_proxies +2)=='\0'){
+                    LM_ERR("script error -invalid lrk proxy list!\n");
+            return -1;
+        }
+
+        *lrk_proxies = '\0';
+        p2 = lrk_proxies-1;
+        for(;isspace(*p2); *p2 = '\0',p2--);
+        id_set.s = p;	id_set.len = p2 - p+1;
+
+        if(id_set.len <= 0){
+                    LM_ERR("script error -invalid set_id value!\n");
+            return -1;
+        }
+
+        lrk_proxies+=2;
+    }else{
+        lrk_proxies = p;
+        id_set = DEFAULT_LRKP_SET_ID_STR;
+    }
+
+    for(;*lrk_proxies && isspace(*lrk_proxies);lrk_proxies++);
+
+    if(!(*lrk_proxies)){
+                LM_ERR("script error -empty lrkproxy list\n");
+        return -1;;
+    }
+
+    lrkp_list = get_lrkp_set(&id_set);
+    if (lrkp_list == NULL)
+    {
+                LM_ERR("Failed to get or create lrkp_list for '%.*s'\n", id_set.len, id_set.s);
+        return -1;
+    }
+
+    if(add_lrkproxy_socks(lrkp_list, lrk_proxies)!= 0){
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static int fixup_set_id(void ** param, int param_no)
+{
+	int int_val, err;
+	struct lrkp_set* lrkp_list;
+	lrkp_set_link_t *lrkl = NULL;
+	str s;
+
+	lrkl = (lrkp_set_link_t*)pkg_malloc(sizeof(lrkp_set_link_t));
+	if(lrkl==NULL) {
+		LM_ERR("no more pkg memory\n");
+		return -1;
+	}
+	memset(lrkl, 0, sizeof(lrkp_set_link_t));
+	s.s = (char*)*param;
+	s.len = strlen(s.s);
+
+	if(s.s[0] == PV_MARKER) {
+		int_val = pv_locate_name(&s);
+		if(int_val<0 || int_val!=s.len) {
+			LM_ERR("invalid parameter %s\n", s.s);
+			pkg_free(lrkl);
+			return -1;
+		}
+		lrkl->rpv = pv_cache_get(&s);
+		if(lrkl->rpv == NULL) {
+			LM_ERR("invalid pv parameter %s\n", s.s);
+			pkg_free(lrkl);
+			return -1;
+		}
+	} else {
+		int_val = str2s(*param, strlen(*param), &err);
+		if (err == 0) {
+			pkg_free(*param);
+			if((lrkp_list = select_lrkp_set(int_val)) ==0){
+				LM_ERR("lrkp_proxy set %i not configured\n", int_val);
+				pkg_free(lrkl);
+				return E_CFG;
+			}
+			lrkl->rset = lrkp_list;
+		} else {
+			LM_ERR("bad number <%s>\n",	(char *)(*param));
+			pkg_free(lrkl);
+			return E_CFG;
+		}
+	}
+	*param = (void*)lrkl;
+	return 0;
+}
+
+
+static int
+mod_init(void)
+{
+    int i;
+//	pv_spec_t avp_spec;
+//	str s;
+//	unsigned short avp_flags;
+
+//	if(lrkproxy_rpc_init()<0)
+//	{
+//		LM_ERR("failed to register RPC commands\n");
+//		return -1;
+//	}
+
+    /* Configure the head of the lrkp_set_list */
+    lrkp_set_list = shm_malloc(sizeof(struct lrkp_set_head));
+    if (lrkp_set_list == NULL)
+    {
+                LM_ERR("no shm memory for lrkp_set_list\n");
+        return -1;
+    }
+    memset(lrkp_set_list, 0, sizeof(struct lrkp_set_head));
+
+
+    /* storing the list of lrk proxy sets in shared memory*/
+    for(i=0;i<lrkp_sets;i++){
+                LM_DBG("Adding LRK-Proxy set %d/%d: %s\n", i, lrkp_sets, lrkp_strings[i]);
+//        if ((lrkp_db_url.s == NULL) &&
+        if (lrkproxy_add_lrkproxy_set(lrkp_strings[i]) != 0) {
+            for(;i<lrkp_sets;i++)
+                if(lrkp_strings[i])
+                    pkg_free(lrkp_strings[i]);
+            pkg_free(lrkp_strings);
+                    LM_ERR("Failed to add LRK-Proxy from Config!\n");
+            return -1;
+        }
+        if(lrkp_strings[i])
+            pkg_free(lrkp_strings[i]);
+    }
+
+
+    if (lrkp_strings)
+        pkg_free(lrkp_strings);
+
+
+    /* init the hastable which keeps the all media address for both party and also the elected_node <--> callid& via-branch relation */
+    if (hash_table_size < 1){
+        hash_table_size = HASH_SIZE;    //the default size 128 entry.
+    }
+
+    if (!lrkproxy_hash_table_init(hash_table_size)) {
+                LM_ERR("lrkproxy_hash_table_init(%d) failed!\n", hash_table_size);
+        return -1;
+    } else {
+//                LM_DBG("lrkproxy_hash_table_init(%d) success!\n", hash_table_size);
+                LM_INFO(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>lrkproxy_hash_table_init(%d) success!\n", hash_table_size);
+    }
+
+
+
+    /* load tm module*/
+    if (load_tm_api( &tmb ) < 0)
+    {
+                LM_DBG("could not load the TM-functions - answer-offer model"
+                               " auto-detection is disabled\n");
+        memset(&tmb, 0, sizeof(struct tm_binds));
+    }
+
+    return 0;
+}
+
+
+static int
+child_init(int rank)
+{
+    int n;
+    char *cp;
+    struct addrinfo hints, *res;
+    struct lrkp_set  *lrkp_list;
+    struct lrkp_node *pnode;
+
+    if(lrkp_set_list==NULL )
+        return 0;
+
+    /* do not init sockets for PROC_INIT and main process when fork=yes */
+    if(rank==PROC_INIT || (rank==PROC_MAIN && dont_fork==0)) {
+        return 0;
+    }
+
+    /* Iterate known LRK proxies - create sockets */
+    mypid = getpid();
+
+    lrkp_socks = (int*)pkg_malloc( sizeof(int)*lrkp_no );
+    if (lrkp_socks==NULL) {
+                LM_ERR("no more pkg memory\n");
+        return -1;
+    }
+    memset(lrkp_socks, -1, sizeof(int)*lrkp_no);
+
+    for(lrkp_list = lrkp_set_list->lset_first; lrkp_list != 0;
+        lrkp_list = lrkp_list->lset_next){
+
+        for (pnode=lrkp_list->ln_first; pnode!=0; pnode = pnode->ln_next){
+            char *hostname;
+
+            if (pnode->ln_umode == 0) {
+                lrkp_socks[pnode->idx] = -1;
+                goto rptest;
+            }
+
+            /*
+             * This is UDP or UDP6. Detect host and port; lookup host;
+             * do connect() in order to specify peer address
+             */
+            hostname = (char*)pkg_malloc(sizeof(char) * (strlen(pnode->ln_address) + 1));
+            if (hostname==NULL) {
+                        LM_ERR("no more pkg memory\n");
+                return -1;
+            }
+            strcpy(hostname, pnode->ln_address);
+
+            cp = strrchr(hostname, ':');
+            if (cp != NULL) {
+                *cp = '\0';
+                cp++;
+            }
+            if (cp == NULL || *cp == '\0')
+                cp = CPORT;
+
+            memset(&hints, 0, sizeof(hints));
+            hints.ai_flags = 0;
+            hints.ai_family = (pnode->ln_umode == 6) ? AF_INET6 : AF_INET;
+            hints.ai_socktype = SOCK_DGRAM;
+            if ((n = getaddrinfo(hostname, cp, &hints, &res)) != 0) {
+                        LM_ERR("%s\n", gai_strerror(n));
+                pkg_free(hostname);
+                return -1;
+            }
+            pkg_free(hostname);
+
+            lrkp_socks[pnode->idx] = socket((pnode->ln_umode == 6)
+                                            ? AF_INET6 : AF_INET, SOCK_DGRAM, 0);
+            if ( lrkp_socks[pnode->idx] == -1) {
+                        LM_ERR("can't create socket\n");
+                freeaddrinfo(res);
+                return -1;
+            }
+
+            if (connect( lrkp_socks[pnode->idx], res->ai_addr, res->ai_addrlen) == -1) {
+                        LM_ERR("can't connect to a LRK proxy\n");
+                close( lrkp_socks[pnode->idx] );
+                lrkp_socks[pnode->idx] = -1;
+                freeaddrinfo(res);
+                return -1;
+            }
+            freeaddrinfo(res);
+rptest:
+            pnode->ln_enable = lrkp_test(pnode);
+            if (pnode->ln_enable) {       //get lrk proxy config if it is enable.
+//                LM_INFO("lrkp_test test is calling here\n"); //enable next line.
+                lrkp_get_config(pnode);
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+static void mod_destroy(void)
+{
+    struct lrkp_set * crt_list, * last_list;
+    struct lrkp_node * crt_lrkp, *last_lrkp;
+
+    /*free the shared memory*/
+//	if (natping_state)
+//		shm_free(natping_state);
+
+    if(lrkp_set_list == NULL)
+        return;
+
+    for(crt_list = lrkp_set_list->lset_first; crt_list != NULL; ){
+
+        for(crt_lrkp = crt_list->ln_first; crt_lrkp != NULL;  ){
+
+            last_lrkp = crt_lrkp;
+            crt_lrkp = last_lrkp->ln_next;
+            shm_free(last_lrkp);
+        }
+
+        last_list = crt_list;
+        crt_list = last_list->lset_next;
+        shm_free(last_list);
+    }
+
+    shm_free(lrkp_set_list);
+
+    /* destroy the hash table */
+    if (!lrkproxy_hash_table_destroy()) {
+                LM_ERR("lrkproxy_hash_table_destroy() failed!\n");
+    } else {
+                LM_DBG("lrkproxy_hash_table_destroy() success!\n");
+    }
+
+}
+
+
+static char * gencookie(void)
+{
+    static char cook[34];
+
+    sprintf(cook, "%d_%u ", (int)mypid, myseqn);
+    myseqn++;
+    return cook;
+}
+
+static int lrkp_test(struct lrkp_node *node)
+{
+    int buflen = 256;
+    char buf[buflen];
+    struct iovec v[2] = {{NULL, 0}, {"P", 1}};
+
+    memset(buf, 0, buflen);
+    memcpy(buf, send_lrkp_command(node, v, 2, 0), buflen);
+
+//    if (buf == NULL) {
+    if (!buf[0]) {
+        LM_ERR("can't ping the lrk proxy %s, Disable it right now.\n", node->ln_url.s);
+        return 0;
+    }
+
+    char *resp = buf + v[0].iov_len + v[1].iov_len + 1;
+    if (memcmp(resp, "PONG", 4) == 0)
+//                LM_DBG("Recieve PONG response from lrk proxy server %s, Enable it right now.\n", node->ln_url.s);
+            LM_INFO("Recieve PONG response from lrk proxy server %s, Enable it right now.\n", node->ln_url.s);
+
+    return 1;
+
+}
+
+static int lrkp_get_config(struct lrkp_node *node){
+
+    int buflen = 256;
+    char buf[buflen];
+    struct iovec v[2] = {{NULL, 0}, {"G", 1}};
+    struct lrkp_node_conf *lnconf = NULL;
+
+    memset(buf, 0, buflen);
+    memcpy(buf, send_lrkp_command(node, v, 2, 0), buflen);
+
+//    if (buf == NULL) {
+    if (!buf[0]) {
+        LM_ERR("can't get config of the lrk proxy %s, Disable it right now.\n", node->ln_url.s);
+        return 0;
+    }
+
+    lnconf = (struct lrkp_node_conf *)(buf + v[0].iov_len + v[1].iov_len + 1);
+
+    if (lnconf == NULL){
+        LM_ERR("can't get config of the lrk proxy %s, Disable it right now.\n", node->ln_url.s);
+        return 0;
+    }
+
+
+    memcpy(node->lrkp_n_c, lnconf, sizeof(struct lrkp_node_conf));
+
+//    node->lrkp_n_c = lnconf;
+    LM_INFO("the lrk proxy %s is configured successfully right now.\n", node->ln_url.s);
+    LM_INFO("buffer internal:%s\n", node->lrkp_n_c->internal_ip);
+    LM_INFO("buffer external:%s\n", node->lrkp_n_c->external_ip);
+    LM_INFO("buffer start_port:%d\n", node->lrkp_n_c->start_port);
+    LM_INFO("buffer end_port:%d\n", node->lrkp_n_c->end_port);
+    LM_INFO("buffer current_port:%d\n", node->lrkp_n_c->current_port);
+
+    return 1;
+
+
+}
+
+static int lrkp_set_conntrack_rule(struct lrkproxy_hash_entry *e) {
+    int buflen = 254;
+    char buf[buflen];
+    int v_len = 0;
+
+    char src_ipv4[20];
+    char src_port[20];
+    char dst_ipv4[20];
+    char dst_port[20];
+    char snat_ipv4[20];
+    char snat_port[20];
+    char dnat_ipv4[20];
+    char dnat_port[20];
+    char timeout[20];
+    char callid[50];
+
+    struct iovec v[] = {
+            {NULL, 0},  /* reserved (cookie) */
+            {"S",  1},   /* command & common options */
+            {NULL, 0},  /* src_ipv4 */
+            {NULL, 0},  /* dst_ipnv4 */
+            {NULL, 0},  /* snat_ipv4 */
+            {NULL, 0},  /* dnat_ipv4 */
+            {NULL, 0},  /* src_port */
+            {NULL, 0},  /* dst_port*/
+            {NULL, 0},  /* snat_port */
+            {NULL, 0},  /* dnat_port*/
+            {NULL, 0},  /* timeout to clear conntrack entry*/
+            {NULL, 0},  /* callid of session */
+    };
+
+    v_len += v[1].iov_len;
+
+    //set src_ipv4 to buffer.
+    sprintf(src_ipv4, " %.*s ", e->src_ipv4.len, e->src_ipv4.s);
+    v[2].iov_base = src_ipv4;
+    v[2].iov_len = strlen(v[2].iov_base);
+    v_len += v[2].iov_len;
+
+    //set dst_ipv4 to buffer.
+    sprintf(dst_ipv4, "%.*s ", e->dst_ipv4.len, e->dst_ipv4.s);
+    v[3].iov_base = dst_ipv4;
+    v[3].iov_len = strlen(v[3].iov_base);
+    v_len += v[3].iov_len;
+
+    //set snat_ipv4 to buffer.
+    sprintf(snat_ipv4, "%.*s ", e->snat_ipv4.len, e->snat_ipv4.s);
+    v[4].iov_base = snat_ipv4;
+    v[4].iov_len = strlen(v[4].iov_base);
+    v_len += v[4].iov_len;
+
+    //set dnat_ipv4 to buffer.
+    sprintf(dnat_ipv4, "%.*s ", e->dnat_ipv4.len, e->dnat_ipv4.s);
+    v[5].iov_base = dnat_ipv4;
+    v[5].iov_len = strlen(v[5].iov_base);
+    v_len += v[5].iov_len;
+
+    //set src_port to buffer.
+    sprintf(src_port, "%.*s ", e->src_port.len, e->src_port.s);
+    v[6].iov_base = src_port;
+    v[6].iov_len = strlen(v[6].iov_base);
+    v_len += v[6].iov_len;
+
+    //set dst_port to buffer.
+    sprintf(dst_port, "%.*s ", e->dst_port.len, e->dst_port.s);
+    v[7].iov_base = dst_port;
+    v[7].iov_len = strlen(v[7].iov_base);
+    v_len += v[7].iov_len;
+
+    //set snat_port to buffer.
+    sprintf(snat_port, "%.*s ", e->snat_port.len, e->snat_port.s);
+    v[8].iov_base = snat_port;
+    v[8].iov_len = strlen(v[8].iov_base);
+    v_len += v[8].iov_len;
+
+    //set dnat_port to buffer.
+    sprintf(dnat_port, "%.*s ", e->dnat_port.len, e->dnat_port.s);
+    v[9].iov_base = dnat_port;
+    v[9].iov_len = strlen(v[9].iov_base);
+    v_len += v[9].iov_len;
+
+    //set timeout to buffer. Set to 60 sec for default.
+    sprintf(timeout, "%d ", 60);
+    v[10].iov_base = timeout;
+    v[10].iov_len = strlen(v[10].iov_base);
+    v_len += v[10].iov_len;
+
+    //set callid to buffer.
+    sprintf(callid, "%.*s ", e->callid.len, e->callid.s);
+    v[11].iov_base = callid;
+    v[11].iov_len = strlen(v[11].iov_base);
+    v_len += v[11].iov_len;
+//    LM_ERR("e->callid.len is:%d right now.\n\n", e->callid.len);
+
+    memset(buf, 0, buflen);
+    memcpy(buf, send_lrkp_command(e->node, v, 12, v_len), buflen);
+//
+
+//    if (buf == NULL) {
+    if (!buf[0]) {
+                LM_ERR("can't ping the lrk proxy %s, Disable it right now.\n", e->node->ln_url.s);
+        return 0;
+    }
+
+    v_len += v[0].iov_len;
+
+
+//    char *resp = buf + v[0].iov_len + v[1].iov_len + v[2].iov_len;
+    char *resp = buf + v_len;
+    if (memcmp(resp, "OK", 2) == 0) {
+                LM_INFO("Recieve OK response from lrk proxy server %s, Rule set successfully.\n", e->node->ln_url.s);
+                LM_DBG("Recieve OK response from lrk proxy server %s, Rule set successfully.\n", e->node->ln_url.s);
+    }
+    return 1;
+
+}
+
+
+char *send_lrkp_command(struct lrkp_node *node, struct iovec *v, int vcnt, int more)
+{
+    struct sockaddr_un addr;
+    int fd, len, i;
+    int ret;
+//    char *cp;
+    static char buf[256];
+    struct pollfd fds[1];
+
+    memset(buf, 0, 256);
+    len = 0;
+//    cp = buf;
+    if (node->ln_umode == 0) {
+        memset(&addr, 0, sizeof(addr));
+        addr.sun_family = AF_LOCAL;
+        strncpy(addr.sun_path, node->ln_address,
+                sizeof(addr.sun_path) - 1);
+#ifdef HAVE_SOCKADDR_SA_LEN
+        addr.sun_len = strlen(addr.sun_path);
+#endif
+
+        fd = socket(AF_LOCAL, SOCK_STREAM, 0);
+        if (fd < 0) {
+                    LM_ERR("can't create socket\n");
+            goto badproxy;
+        }
+        if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+            close(fd);
+                    LM_ERR("can't connect to lrk proxy\n");
+            goto badproxy;
+        }
+
+        do {
+            len = writev(fd, v + 1, vcnt - 1);
+        } while (len == -1 && errno == EINTR);
+        if (len <= 0) {
+            close(fd);
+                    LM_ERR("can't send command to a lrk proxy %s\n", node->ln_url.s);
+            goto badproxy;
+        }
+        do {
+            len = read(fd, buf, sizeof(buf) - 1);
+        } while (len == -1 && errno == EINTR);
+        close(fd);
+        if (len <= 0) {
+                    LM_ERR("can't read reply from a lrk proxy %s\n", node->ln_url.s);
+            goto badproxy;
+        }
+    } else {
+        fds[0].fd = lrkp_socks[node->idx];
+        fds[0].events = POLLIN;
+        fds[0].revents = 0;
+        /* Drain input buffer */
+        ret = -1;
+        while ((poll(fds, 1, 0) == 1) &&
+               ((fds[0].revents & POLLIN) != 0) && ret != 0) {
+            ret = recv(lrkp_socks[node->idx], buf, sizeof(buf) - 1, 0);
+            fds[0].revents = 0;
+        }
+        v[0].iov_base = gencookie();
+        v[0].iov_len = strlen(v[0].iov_base);
+        for (i = 0; i < lrkproxy_retr; i++) {
+            do {
+                len = writev(lrkp_socks[node->idx], v, vcnt);
+            } while (len == -1 && (errno == EINTR || errno == ENOBUFS));
+            if (len <= 0) {
+                        LM_ERR("can't send command to a lrk proxy %s\n", node->ln_url.s);
+                goto badproxy;
+            }
+            while ((poll(fds, 1, lrkproxy_tout * 1000) == 1) &&
+                   (fds[0].revents & POLLIN) != 0) {
+                do {
+                    len = recv(lrkp_socks[node->idx], buf, sizeof(buf) - 1, 0);
+                } while (len == -1 && errno == EINTR);
+                if (len <= 0) {
+                            LM_ERR("can't read reply from a lrk proxy %s\n", node->ln_url.s);
+                    goto badproxy;
+                }
+                if (len >= (v[0].iov_len - 1) &&
+                    memcmp(buf, v[0].iov_base, (v[0].iov_len - 1)) == 0) {      //check coocke validation.
+                    char *command = buf + v[0].iov_len;
+                    switch (*command) {
+                        case 'P':
+                            if (len == v[0].iov_len + v[1].iov_len + 4 + 1)
+                                goto out;
+//                            break;
+                        case 'G':
+                            if (len == v[0].iov_len + v[1].iov_len + sizeof(struct lrkp_node_conf) + 1)
+                                goto out;
+//                            break;
+                        case 'S':
+                            if (len == more + v[0].iov_len + 2)
+                                goto out;
+//                            break;
+                    }
+
+                }
+                fds[0].revents = 0;
+            }
+        }
+        if (i == lrkproxy_tout) {
+                    LM_ERR("timeout waiting reply from a lrk proxy server %s\n", node->ln_url.s);
+            goto badproxy;
+
+        }
+    }
+    out:
+    return buf;
+    badproxy:
+            LM_ERR("lrk proxy <%s> does not respond, disable it\n", node->ln_url.s);
+    node->ln_enable = 0;
+//    node->ln_recheck_ticks = get_ticks() + lrkproxy_disable_tout;
+    return buf;
+}
+
+/*
+ * select the set with the id_set id
+ */
+static struct lrkp_set * select_lrkp_set(int id_set ){
+
+    struct lrkp_set * lrkp_list;
+    /*is it a valid set_id?*/
+
+    if(!lrkp_set_list)
+    {
+                LM_ERR("lrkproxy set list not initialised\n");
+        return NULL;
+    }
+
+    for(lrkp_list=lrkp_set_list->lset_first; lrkp_list!=NULL &&
+                                             lrkp_list->id_set!=id_set; lrkp_list=lrkp_list->lset_next);
+
+    return lrkp_list;
+}
+
+
+struct lrkp_node *select_lrkp_node(int do_test)
+{
+    struct lrkp_node* node;
+    int was_forced;
+    int was_forced2;
+
+    if(!selected_lrkp_set){
+        LM_ERR("script error -no valid set selected\n");
+        return NULL;
+    }
+    /* Most popular case: 1 proxy, nothing to calculate */
+    if (selected_lrkp_set->lrkp_node_count == 1) {
+        node = selected_lrkp_set->ln_first;
+//        if (node->rn_disabled && node->rn_recheck_ticks <= get_ticks())
+        if (!node->ln_enable) {
+            node->ln_enable = lrkp_test(node);
+            if (node->ln_enable) {       //get lrk proxy config if it is enable.
+                lrkp_get_config(node);
+                return node;
+            }
+        }
+        return node->ln_enable ? node : NULL;
+//        return NULL;
+    }
+
+
+    /* Check node is enable and test it again*/
+retry:
+    for (node=selected_lrkp_set->ln_first; node!=NULL; node=node->ln_next) {
+
+        if (!node->ln_enable) {
+            /* Try to enable if it's time to try. */
+            node->ln_enable = lrkp_test(node);
+            if (node->ln_enable)       //get lrk proxy config if it is enable.
+                lrkp_get_config(node);
+        }
+    }
+
+    if (lrkp_algorithm == LRK_LINER) {
+        was_forced = 0;
+        retry2:
+        for (node = selected_lrkp_set->ln_first; node != NULL; node = node->ln_next)
+            if (node->ln_enable)
+                goto found;
+        if (was_forced)
+            return NULL;
+
+        was_forced = 1;
+        //trying to enable all lrkproxy and check again.
+        for (node = selected_lrkp_set->ln_first; node != NULL; node = node->ln_next) {
+            /* Try to enable if it's time to try. */
+            node->ln_enable = lrkp_test(node);
+            if (node->ln_enable)       //get lrk proxy config if it is enable.
+                lrkp_get_config(node);
+        }
+
+
+        goto retry2;
+    }
+    else if(lrkp_algorithm == LRK_RR) {
+        was_forced2 = 0;
+retry3:
+        if (!selected_lrkp_node) {
+            selected_lrkp_node = selected_lrkp_set->ln_first;
+            node = selected_lrkp_set->ln_first;
+            if(node->ln_enable)
+                    goto found;
+//            was_forced2 = 1;
+        }
+        for (node = selected_lrkp_node->ln_next; node != NULL; node = node->ln_next)
+//        for (node = selected_lrkp_node; node != NULL; node = node->ln_next) {
+            if (node->ln_enable) {
+                selected_lrkp_node = node;
+                goto found;
+            }
+//            selected_lrkp_node = node->ln_next;
+//        if (sumcut < node->ln_weight)
+//        sumcut -= node->ln_weight;
+
+        if (was_forced2)
+            return NULL;
+
+        was_forced2 = 1;
+        selected_lrkp_node = NULL;
+
+        goto retry3;
+    }
+
+    found:
+    if (do_test) {
+//    //todo...
+        node->ln_enable = lrkp_test(node);
+        if (!node->ln_enable)
+            goto retry;
+    }
+    return node;
+}
+
+//static int change_media_sdp(sip_msg_t *msg, struct lrkp_node *n, const char *flags, int type) {
+static int change_media_sdp(sip_msg_t *msg, struct lrkproxy_hash_entry *e, const char *flags, enum lrk_operation op) {
+    struct lump *l;
+    str body;
+    str newbody;
+
+    int len;
+    char *start_sdp_o = NULL; //"o=";
+    char *start_sdp_s = NULL; //"s=";
+    char *start_sdp_c = NULL; //"c=IN IP4";
+    char *start_sdp_m = NULL; //"m=audio";
+    char *ip_selected = NULL;
+    char *sdp_param_start = NULL;
+    char *sdp_param_end = NULL;
+    char *off=NULL;
+    char sdp_new_o[128];
+    char sdp_new_s[128];
+    char sdp_new_c[128];
+    char sdp_new_m[128];
+
+    body.s = get_body(msg);
+    if (body.s == 0) {
+                LM_ERR("failed to get the message body\n");
+        return -1;
+    }
+
+    body.len = msg->len - (int) (body.s - msg->buf);
+    if (body.len == 0) {
+                LM_DBG("message body has zero length\n");
+        return -1;
+    }
+//            LM_INFO("body:<%.*s>\n", body.len, body.s);
+
+    //allocate new buffer to new sdp buffer.
+    newbody.len = 1024;
+    newbody.s = pkg_malloc(newbody.len);
+    if (newbody.s == NULL) {
+                LM_ERR("out of pkg memory\n");
+        return -1;
+    }
+    memset(newbody.s, 0, 1024);
+
+    off = body.s;
+    start_sdp_o = strstr(off, "o=");
+    start_sdp_s = strstr(off, "s=");
+    start_sdp_c = strstr(off, "c=IN IP4");
+    start_sdp_m = strstr(off, "m=audio");
+
+    //The external_ip should be set in config file for relaying RTP media between NIC.
+//    if (e->node->lrkp_n_c->external_ip && flags) {
+    if(flags) {
+        if (strstr(flags, "ei")) {
+            ip_selected = e->node->lrkp_n_c->internal_ip;// lrk_node->internal_ip;
+        } else if (strstr(flags, "ie")) {
+            ip_selected = e->node->lrkp_n_c->external_ip; //lrk_node->external_ip;
+        } else {
+            LM_INFO("unknown flags, use internal_ip\n");
+            ip_selected = e->node->lrkp_n_c->internal_ip;
+        }
+    }
+    else {
+        LM_INFO("no flags set, use internal_ip\n");
+        ip_selected = e->node->lrkp_n_c->internal_ip;
+    }
+
+    if (op == OP_OFFER) {
+        e->dst_ipv4.s = ip_selected;
+        e->dst_ipv4.len = strlen(e->dst_ipv4.s);
+
+        str current_port;
+        current_port.s = int2str(e->node->lrkp_n_c->current_port, &current_port.len);
+
+        if (shm_str_dup(&e->dst_port, &current_port) < 0) {
+                    LM_ERR("lrkproxy fail to insert dst_port, calllen=%d dst_port=%.*s\n",
+                           e->callid.len, current_port.len, current_port.s);
+            lrkproxy_hash_table_free_entry(e);
+            return 0;
+        }
+
+//        e->dst_port = e->node->lrkp_n_c->current_port;
+    }
+    else if (op == OP_ANSWER){
+        e->snat_ipv4.s = ip_selected;
+        e->snat_ipv4.len = strlen(e->snat_ipv4.s);
+
+        str current_port;
+        unsigned int snat_port;
+
+        str2int(&e->dst_port, &snat_port);
+        snat_port += 2;
+
+        current_port.s = int2str(snat_port, &current_port.len);
+
+        if (shm_str_dup(&e->snat_port, &current_port) < 0) {
+                    LM_ERR("lrkproxy fail to insert snat_port, calllen=%d snat_port=%.*s\n",
+                           e->callid.len, current_port.len, current_port.s);
+            lrkproxy_hash_table_free_entry(e);
+            return 0;
+        }
+
+//        e->snat_port = e->dst_port + 2;
+    }
+
+
+    while (*off != EOB)    //while end of body.
+    {
+        sdp_param_start = off;
+        sdp_param_end = sdp_param_start;
+        while (*sdp_param_end != CR && *sdp_param_end != LF && *sdp_param_end != EOB) sdp_param_end++;
+        len = (int) (sdp_param_end - sdp_param_start);
+        if ((int) (start_sdp_o - off) == 0) {
+            memset(sdp_new_o, 0, 128);
+            snprintf(sdp_new_o, 128, "o=lrkproxy %s %s IN IP4 %s\r", SUP_CPROTOVER, REQ_CPROTOVER, ip_selected);
+            strncat(newbody.s, sdp_new_o, strlen(sdp_new_o));
+            off += len + 1;
+            continue;
+        }
+        if ((int) (start_sdp_s - off) == 0) {
+            memset(sdp_new_s, 0, 128);
+            snprintf(sdp_new_s, 128, "s=lrkproxy Support only Audio Call\r");
+            strncat(newbody.s, sdp_new_s, strlen(sdp_new_s));
+            off += len + 1;
+            continue;
+        }
+        if ((int) (start_sdp_c - off) == 0) {
+            memset(sdp_new_c, 0, 128);
+            snprintf(sdp_new_c, 128, "c=IN IP4 %s\r", ip_selected);
+            strncat(newbody.s, sdp_new_c, strlen(sdp_new_c));
+            off += len + 1;
+            continue;
+        }
+        if ((int)(start_sdp_m - off) == 0){
+            memset(sdp_new_m, 0, 128);
+            char *avp_flags = off;
+//            int occure = 0;
+            for (;*avp_flags && !isspace(*avp_flags); avp_flags++);
+            for (avp_flags++;*avp_flags && !isspace(*avp_flags); avp_flags++);
+            avp_flags++;
+            if (op == OP_OFFER)
+                snprintf(sdp_new_m, 128, "m=audio %.*s %.*s\r",e->dst_port.len, e->dst_port.s, (int)(len - (avp_flags-off)), avp_flags);
+//                snprintf(sdp_new_m, 128, "m=audio %d %.*s\r",e->node->lrkp_n_c->current_port, (int)(len - (avp_flags-off)), avp_flags);
+            else if (op == OP_ANSWER)
+                snprintf(sdp_new_m, 128, "m=audio %.*s %.*s\r",e->snat_port.len, e->snat_port.s, (int)(len - (avp_flags-off)), avp_flags);
+//               snprintf(sdp_new_m, 128, "m=audio %d %.*s\r",e->node->lrkp_n_c->current_port, (int)(len - (avp_flags-off)), avp_flags);
+//            printf("%.*s\n\n", len - (avp_flags-off), avp_flags);
+            strncat(newbody.s,sdp_new_m, strlen(sdp_new_m));
+            off += len+1;
+            continue;
+        }
+
+        strncat(newbody.s, off, len + 1);
+        off += len + 1;
+    }
+
+
+//    LM_INFO("%.*s", (int)strlen(newbody.s), newbody.s);
+    l = del_lump(msg, body.s - msg->buf, body.len, 0);
+    if (!l) {
+                LM_ERR("del_lump failed\n");
+        return -1;
+    }
+
+
+    if (insert_new_lump_after(l, newbody.s, strlen(newbody.s), 0) == 0) {
+                LM_ERR("could not insert new lump\n");
+        pkg_free(newbody.s);
+        return -1;
+    }
+
+            LM_BUG("Insert_new_lump successfully\n");
+
+    return 1;
+}
+
+/* This function assumes p points to a line of requested type. */
+
+	static int
+set_lrkproxy_set_f(struct sip_msg * msg, char * str1, char * str2)
+{
+	lrkp_set_link_t *lrkl;
+	pv_value_t val;
+
+	lrkl = (lrkp_set_link_t*)str1;
+
+	current_msg_id = 0;
+	selected_lrkp_set = 0;
+
+	if(lrkl->rset != NULL) {
+		current_msg_id = msg->id;
+		selected_lrkp_set = lrkl->rset;
+	} else {
+		if(pv_get_spec_value(msg, lrkl->rpv, &val)<0) {
+			LM_ERR("cannot evaluate pv param\n");
+			return -1;
+		}
+		if(!(val.flags & PV_VAL_INT)) {
+			LM_ERR("pv param must hold an integer value\n");
+			return -1;
+		}
+		selected_lrkp_set = select_lrkp_set(val.ri);
+		if(selected_lrkp_set==NULL) {
+			LM_ERR("could not locate lrkproxy set %d\n", val.ri);
+			return -1;
+		}
+		current_msg_id = msg->id;
+	}
+	return 1;
+}
+
+static int
+lrkproxy_manage(struct sip_msg *msg, char *flags, char *ip)
+{
+    int method;
+    int nosdp;
+    tm_cell_t *t = NULL;
+
+    if (msg->cseq==NULL && ((parse_headers(msg, HDR_CSEQ_F, 0)==-1) ||
+                            (msg->cseq==NULL)))
+    {
+                LM_ERR("no CSEQ header\n");
+        return -1;
+    }
+
+    method = get_cseq(msg)->method_id;
+
+    if (!(method==METHOD_INVITE || method==METHOD_ACK || method==METHOD_CANCEL
+          || method==METHOD_BYE || method==METHOD_UPDATE))
+        return -1;
+
+    if (method==METHOD_CANCEL || method==METHOD_BYE)
+        return lrkproxy_unforce(msg, flags, OP_DELETE, 1);
+
+    if (msg->msg_flags & FL_SDP_BODY)
+        nosdp = 0;
+    else
+        nosdp = parse_sdp(msg);
+
+    if (msg->first_line.type == SIP_REQUEST) {
+        if(method==METHOD_ACK && nosdp==0)
+            return lrkproxy_force(msg, flags, OP_ANSWER, 1);
+        if(method==METHOD_UPDATE && nosdp==0)
+            return lrkproxy_force(msg, flags, OP_OFFER, 1);
+        if(method==METHOD_INVITE && nosdp==0) {
+            msg->msg_flags |= FL_SDP_BODY;
+            if(tmb.t_gett!=NULL) {
+                t = tmb.t_gett();
+                if(t!=NULL && t!=T_UNDEFINED && t->uas.request!=NULL) {
+                    t->uas.request->msg_flags |= FL_SDP_BODY;
+                }
+            }
+            if(route_type==FAILURE_ROUTE)
+                return lrkproxy_unforce(msg, flags, OP_DELETE, 1);
+            return lrkproxy_force(msg, flags, OP_OFFER, 1);
+        }
+    } else if (msg->first_line.type == SIP_REPLY) {
+        if (msg->first_line.u.reply.statuscode>=300)
+            return lrkproxy_unforce(msg, flags, OP_DELETE, 2);
+        if (nosdp==0) {
+            if (method==METHOD_UPDATE)
+                return lrkproxy_force(msg, flags, OP_ANSWER, 2);
+            if (tmb.t_gett==NULL || tmb.t_gett()==NULL
+                || tmb.t_gett()==T_UNDEFINED)
+                return lrkproxy_force(msg, flags, OP_ANSWER, 2);
+            if (tmb.t_gett()->uas.request->msg_flags & FL_SDP_BODY)
+                return lrkproxy_force(msg, flags, OP_ANSWER, 2);
+            return lrkproxy_force(msg, flags, OP_OFFER, 2);
+        }
+    }
+
+    return -1;
+}
+
+static int
+lrkproxy_manage0(struct sip_msg *msg, char *flags, char *ip)
+{
+    return lrkproxy_manage(msg, 0, 0);
+}
+
+static int
+lrkproxy_manage1(struct sip_msg *msg, char *flags, char *ip)
+{
+    str flag_str;
+    if(fixup_get_svalue(msg, (gparam_p)flags, &flag_str)<0)
+    {
+                LM_ERR("invalid flags parameter\n");
+        return -1;
+    }
+    return lrkproxy_manage(msg, flag_str.s, 0);
+}
+
+static int
+lrkproxy_manage2(struct sip_msg *msg, char *flags, char *ip)
+{
+    str flag_str;
+    str ip_str;
+    if(fixup_get_svalue(msg, (gparam_p)flags, &flag_str)<0)
+    {
+                LM_ERR("invalid flags parameter\n");
+        return -1;
+    }
+    if(fixup_get_svalue(msg, (gparam_p)ip, &ip_str)<0)
+    {
+                LM_ERR("invalid IP parameter\n");
+        return -1;
+    }
+    return lrkproxy_manage(msg, flag_str.s, ip_str.s);
+}
+
+
+static int lrkproxy_force(struct sip_msg *msg, const char *flags, enum lrk_operation op, int more) {
+
+//    lrk_sdp_info_t lrk_sdp_info;
+    struct lrkproxy_hash_entry *entry = NULL;
+    str viabranch = STR_NULL;
+    str call_id;
+    int via_id;
+
+    if (get_callid(msg, &call_id) == -1) {
+                LM_ERR("can't get Call-Id field\n");
+        return -1;
+    }
+
+    /*We have to choice VIA id,
+     * for SIP_REQUEST we use VIA1 and for SIP_REPLY we use VIA2 */
+    via_id = more;
+
+    if (get_via_branch(msg, via_id, &viabranch) == -1) {
+                LM_ERR("can't get Call-Id field\n");
+        return -1;
+    }
+
+    if (op == OP_OFFER) {
+                LM_INFO ("Here is SIP_REQUEST &  METHOD_INVITE\n");
+        int more_test = 1;
+
+        //select new node based on lrkp_algorithm param.
+        struct lrkp_node *node = select_lrkp_node(more_test);
+        if (!node) {
+                    LM_ERR("can't ping any lrk proxy right now.\n");
+            return -1;
+        }
+
+                LM_DBG("selected lrk proxy node: %s\n", node->ln_url.s);
+
+        //check if entry not exist.
+        entry = lrkproxy_hash_table_lookup(call_id, viabranch);
+        if (entry)
+            return -1;
+
+//        lrk_get_sdp_info(msg, &lrk_sdp_info);
+
+        //build new entry for hash table.
+//            struct lrkproxy_hash_entry *entry = shm_malloc(sizeof(struct lrkproxy_hash_entry));
+        entry = shm_malloc(sizeof(struct lrkproxy_hash_entry));
+        if (!entry) {
+                    LM_ERR("lrkproxy hash table fail to create entry for calllen=%d callid=%.*s viabranch=%.*s\n",
+                           call_id.len, call_id.len, call_id.s,
+                           viabranch.len, viabranch.s);
+            return 0;
+        }
+        memset(entry, 0, sizeof(struct lrkproxy_hash_entry));
+
+        // fill the entry
+        if (call_id.s && call_id.len > 0) {
+            if (shm_str_dup(&entry->callid, &call_id) < 0) {
+                        LM_ERR("lrkproxy hash table fail to instert call_id, calllen=%d callid=%.*s\n",
+                               call_id.len, call_id.len, call_id.s);
+                lrkproxy_hash_table_free_entry(entry);
+                return 0;
+            }
+        }
+
+        if (viabranch.s && viabranch.len > 0) {
+            if (shm_str_dup(&entry->viabranch, &viabranch) < 0) {
+                        LM_ERR("lrkproxy hash table fail to insert viabranch, calllen=%d viabranch=%.*s\n",
+                               call_id.len, viabranch.len, viabranch.s);
+                lrkproxy_hash_table_free_entry(entry);
+                return 0;
+            }
+        }
+
+        //fill src_ipv4 and src_port for entry.
+        str src_ipv4;
+        if (get_sdp_ipaddr_media(msg, &src_ipv4) == -1) {
+                    LM_ERR("can't get media src_ipv4 from sdp field\n");
+            return -1;
+        }
+
+        if (src_ipv4.s && src_ipv4.len > 0) {
+                    LM_DBG("src_ipv4 from sdp:%.*s\n", src_ipv4.len, src_ipv4.s);
+            if (shm_str_dup(&entry->src_ipv4, &src_ipv4) < 0) {
+                        LM_ERR("lrkproxy hash table fail to insert src_ipv4, calllen=%d src_ipv4=%.*s\n",
+                               call_id.len, src_ipv4.len, src_ipv4.s);
+                lrkproxy_hash_table_free_entry(entry);
+                return 0;
+            }
+        }
+
+        str src_port;
+        if (get_sdp_port_media(msg, &src_port) == -1) {
+                    LM_ERR("can't get media src_port from sdp field\n");
+            return -1;
+        }
+
+
+        if (src_port.s && src_port.len > 0) {
+                    LM_DBG("src_port from sdp:%.*s\n", src_port.len, src_port.s);
+            if (shm_str_dup(&entry->src_port, &src_port) < 0) {
+                        LM_ERR("lrkproxy hash table fail to insert src_port, calllen=%d src_port=%.*s\n",
+                               call_id.len, src_port.len, src_port.s);
+                lrkproxy_hash_table_free_entry(entry);
+                return 0;
+            }
+        }
+
+//            entry->
+        entry->node = node;
+        entry->next = NULL;
+        entry->tout = get_ticks() + hash_table_tout;
+
+        // insert the key<->entry from the hashtable
+        if (!lrkproxy_hash_table_insert(call_id, viabranch, entry)) {
+                    LM_ERR(
+                    "lrkproxy hash table fail to insert node=%.*s for calllen=%d callid=%.*s viabranch=%.*s\n",
+                    node->ln_url.len, node->ln_url.s, call_id.len,
+                    call_id.len, call_id.s, viabranch.len, viabranch.s);
+            lrkproxy_hash_table_free_entry(entry);
+            return 0;
+        } else {
+                    LM_INFO("lrkproxy hash table insert node=%.*s for calllen=%d callid=%.*s viabranch=%.*s\n",
+                            node->ln_url.len, node->ln_url.s, call_id.len,
+                            call_id.len, call_id.s, viabranch.len, viabranch.s);
+
+                    LM_DBG("lrkproxy hash table insert node=%.*s for calllen=%d callid=%.*s viabranch=%.*s\n",
+                           node->ln_url.len, node->ln_url.s, call_id.len,
+                           call_id.len, call_id.s, viabranch.len, viabranch.s);
+        }
+
+
+        if (flags)
+            change_media_sdp(msg, entry, flags, op);
+        else
+            change_media_sdp(msg, entry, NULL, op);
+
+        if (node->lrkp_n_c->current_port >= node->lrkp_n_c->end_port)
+            node->lrkp_n_c->current_port = node->lrkp_n_c->start_port;
+        else
+            node->lrkp_n_c->current_port += 4;
+
+    } else if (op == OP_ANSWER) {
+                LM_INFO ("Here is SIP_REPLY of METHOD_INVITE\n");
+
+
+        entry = lrkproxy_hash_table_lookup(call_id, viabranch);
+        if (!entry){
+                    LM_ERR("No found entry in hash table\n");
+                    //todo...
+            return 0;
+        }
+
+        //fill other data for entry
+        str dnat_ipv4;
+        if (get_sdp_ipaddr_media(msg, &dnat_ipv4) == -1) {
+                    LM_ERR("can't get media dnat_ipv4 from sdp field\n");
+            return -1;
+        }
+
+        if(dnat_ipv4.s && dnat_ipv4.len > 0) {
+                    LM_DBG("dnat_ipv4 from sdp:%.*s\n", dnat_ipv4.len, dnat_ipv4.s);
+            if (shm_str_dup(&entry->dnat_ipv4, &dnat_ipv4) < 0) {
+                        LM_ERR("lrkproxy hash table fail to insert dnat_ipv4, calllen=%d dnat_ipv4=%.*s\n",
+                               call_id.len, dnat_ipv4.len, dnat_ipv4.s);
+                lrkproxy_hash_table_free_entry(entry);
+                return 0;
+            }
+        }
+
+        str dnat_port;
+        if (get_sdp_port_media(msg, &dnat_port) == -1) {
+                    LM_ERR("can't get media port from sdp field\n");
+            return -1;
+        }
+
+
+        if(dnat_port.s && dnat_port.len > 0) {
+                    LM_DBG("port from sdp:%.*s\n", dnat_port.len, dnat_port.s);
+            if (shm_str_dup(&entry->dnat_port, &dnat_port) < 0) {
+                        LM_ERR("lrkproxy hash table fail to insert dnat_port, calllen=%d dnat_port=%.*s\n",
+                               call_id.len, dnat_port.len, dnat_port.s);
+                lrkproxy_hash_table_free_entry(entry);
+                return 0;
+            }
+        }
+
+
+        if (flags)
+            change_media_sdp(msg, entry, flags, op);
+        else
+            change_media_sdp(msg, entry, NULL, op);
+
+
+        LM_INFO("selected node: %s\n",entry->node->ln_url.s);
+        LM_INFO("call_is: %.*s\n",entry->callid.len, entry->callid.s);
+        LM_INFO("viabranch: %.*s\n",entry->viabranch.len, entry->viabranch.s);
+        LM_INFO("src_ipv4: %.*s\n",entry->src_ipv4.len, entry->src_ipv4.s);
+        LM_INFO("src_port: %.*s\n",entry->src_port.len, entry->src_port.s);
+        LM_INFO("dst_ipv4: %.*s\n",entry->dst_ipv4.len, entry->dst_ipv4.s);
+        LM_INFO("dst_port: %.*s\n",entry->dst_port.len, entry->dst_port.s);
+
+        LM_INFO("dnat_ipv4: %.*s\n",entry->dnat_ipv4.len, entry->dnat_ipv4.s);
+        LM_INFO("dnat_port: %.*s\n",entry->dnat_port.len, entry->dnat_port.s);
+        LM_INFO("snat_ipv4: %.*s\n",entry->snat_ipv4.len, entry->snat_ipv4.s);
+        LM_INFO("snat_port: %.*s\n",entry->snat_port.len, entry->snat_port.s);
+
+
+        lrkp_set_conntrack_rule(entry);
+
+    }
+    return 1;
+}
+
+static int lrkproxy_unforce(struct sip_msg *msg, const char *flags, enum lrk_operation op, int more){
+//            LM_INFO ("Here is lrkproxy_unforce\n");
+//    struct lrkproxy_hash_entry *entry = NULL;
+    str viabranch = STR_NULL;
+    str call_id;
+    int via_id;
+
+    if (get_callid(msg, &call_id) == -1) {
+                LM_ERR("can't get Call-Id field\n");
+        return -1;
+    }
+
+    /*We have to choice VIA id,
+     * for SIP_REQUEST we use VIA1 and for SIP_REPLY we use VIA2 */
+    via_id = more;
+
+    if (get_via_branch(msg, via_id, &viabranch) == -1) {
+                LM_ERR("can't get Call-Id field\n");
+        return -1;
+    }
+
+    if (op == OP_DELETE) {
+        /* Delete the key<->value from the hashtable */
+        if (!lrkproxy_hash_table_remove(call_id, viabranch, op)) {
+                    LM_ERR("lrkproxy hash table failed to remove entry for callen=%d callid=%.*s viabranch=%.*s\n",
+                           call_id.len, call_id.len, call_id.s,
+                           viabranch.len, viabranch.s);
+        } else {
+                    LM_DBG("lrkproxy hash table remove entry for callen=%d callid=%.*s viabranch=%.*s\n",
+                           call_id.len, call_id.len, call_id.s,
+                           viabranch.len, viabranch.s);
+        }
+    }
+    LM_INFO("lrkproxy hash table remove entry for callen=%d callid=%.*s viabranch=%.*s successfully\n",
+            call_id.len, call_id.len, call_id.s,
+            viabranch.len, viabranch.s);
+    return 1;
+}

+ 107 - 0
src/modules/lrkproxy/lrkproxy.h

@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2003-2008 Sippy Software, Inc., http://www.sippysoft.com
+ * Copyright (C) 2020 Mojtaba Esfandiari.S, Nasim-Telecom
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#ifndef _LRKPROXY_H
+#define _LRKPROXY_H
+
+#include <sys/uio.h>
+#include "../../core/str.h"
+
+/* Handy macros */
+#define STR2IOVEC(sx, ix)       do {(ix).iov_base = (sx).s; (ix).iov_len = (sx).len;} while(0)
+#define SZ2IOVEC(sx, ix)        do {(ix).iov_base = (sx); (ix).iov_len = strlen(sx);} while(0)
+
+#define CR '\r'
+#define LF '\n'
+#define EOB '\0'
+
+enum lrk_operation {
+    OP_OFFER = 1,
+    OP_ANSWER,
+    OP_DELETE,
+    OP_PING,
+    OP_GETINFO,
+    OP_SETCONNT,
+
+    OP_ANY,
+};
+
+
+enum lrk_alg{
+    LRK_LINER=0,
+    LRK_RR
+};
+
+struct lrkp_node_conf
+{
+    int start_port;
+    int end_port;
+    int current_port;
+    char internal_ip[20];
+    char external_ip[20];
+};
+
+struct lrkp_node {
+    unsigned int		idx;			/* overall index */
+    str					ln_url;			/* unparsed, deletable */
+    int					ln_umode;
+    char				*ln_address;	/* substring of rn_url */
+    int					ln_enable;	/* found unaccessible? */
+    unsigned			ln_weight;		/* for load balancing */
+//    unsigned int		ln_recheck_ticks;
+//    int                 ln_rep_supported;
+//    int                 ln_ptl_supported;
+    struct lrkp_node_conf     *lrkp_n_c;
+    struct lrkp_node	*ln_next;
+};
+
+
+
+struct lrkp_set{
+    unsigned int 		id_set;
+    unsigned			weight_sum;
+    unsigned int		lrkp_node_count;
+    int 				set_disabled;
+    unsigned int		set_recheck_ticks;
+    struct lrkp_node	*ln_first;
+    struct lrkp_node	*ln_last;
+    struct lrkp_set     *lset_next;
+};
+
+
+
+struct lrkp_set_head{
+    struct lrkp_set		*lset_first;
+    struct lrkp_set		*lset_last;
+};
+/* Functions from nathelper */
+//struct lrkp_node *lrkp_node(str, int);
+struct lrkp_node *select_lrkp_node(int);
+char *send_lrkp_command(struct lrkp_node *, struct iovec *, int, int);
+
+struct lrkp_set *get_lrkp_set(str *set_name);
+int insert_lrkp_node(struct lrkp_set *const lrkp_list, const str *const url,
+		const int weight, const int enable);
+
+
+#endif  //_LRKPROXY_H

+ 479 - 0
src/modules/lrkproxy/lrkproxy_funcs.c

@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2014-2015 Sipwise GmbH, http://www.sipwise.com
+ * Copyright (C) 2020 Mojtaba Esfandiari.S, Nasim-Telecom
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "lrkproxy_funcs.h"
+#include "../../core/dprint.h"
+#include "../../core/config.h"
+#include "../../core/ut.h"
+#include "../../core/forward.h"
+#include "../../core/resolve.h"
+#include "../../core/globals.h"
+#include "../../core/udp_server.h"
+#include "../../core/pt.h"
+#include "../../core/parser/msg_parser.h"
+#include "../../core/trim.h"
+#include "../../core/parser/parse_from.h"
+#include "../../core/parser/contact/parse_contact.h"
+#include "../../core/parser/parse_uri.h"
+#include "../../core/parser/parse_content.h"
+#include "../../core/parser/parser_f.h"
+#include "../../core/parser/sdp/sdp_helpr_funcs.h"
+
+#define READ(val) \
+	(*(val + 0) + (*(val + 1) << 8) + (*(val + 2) << 16) + (*(val + 3) << 24))
+#define advance(_ptr,_n,_str,_error) \
+	do{\
+		if ((_ptr)+(_n)>(_str).s+(_str).len)\
+			goto _error;\
+		(_ptr) = (_ptr) + (_n);\
+	}while(0);
+#define one_of_16( _x , _t ) \
+	(_x==_t[0]||_x==_t[15]||_x==_t[8]||_x==_t[2]||_x==_t[3]||_x==_t[4]\
+	||_x==_t[5]||_x==_t[6]||_x==_t[7]||_x==_t[1]||_x==_t[9]||_x==_t[10]\
+	||_x==_t[11]||_x==_t[12]||_x==_t[13]||_x==_t[14])
+#define one_of_8( _x , _t ) \
+	(_x==_t[0]||_x==_t[7]||_x==_t[1]||_x==_t[2]||_x==_t[3]||_x==_t[4]\
+	||_x==_t[5]||_x==_t[6])
+
+
+
+/**
+ * return:
+ * -1: error
+ *  1: text or sdp
+ *  2: multipart
+ *  3: trickle ice sdp fragment
+ */
+int check_content_type(struct sip_msg *msg)
+{
+    static unsigned int appl[16] = {
+            0x6c707061/*appl*/,0x6c707041/*Appl*/,0x6c705061/*aPpl*/,
+            0x6c705041/*APpl*/,0x6c507061/*apPl*/,0x6c507041/*ApPl*/,
+            0x6c505061/*aPPl*/,0x6c505041/*APPl*/,0x4c707061/*appL*/,
+            0x4c707041/*AppL*/,0x4c705061/*aPpL*/,0x4c705041/*APpL*/,
+            0x4c507061/*apPL*/,0x4c507041/*ApPL*/,0x4c505061/*aPPL*/,
+            0x4c505041/*APPL*/};
+    static unsigned int icat[16] = {
+            0x74616369/*icat*/,0x74616349/*Icat*/,0x74614369/*iCat*/,
+            0x74614349/*ICat*/,0x74416369/*icAt*/,0x74416349/*IcAt*/,
+            0x74414369/*iCAt*/,0x74414349/*ICAt*/,0x54616369/*icaT*/,
+            0x54616349/*IcaT*/,0x54614369/*iCaT*/,0x54614349/*ICaT*/,
+            0x54416369/*icAT*/,0x54416349/*IcAT*/,0x54414369/*iCAT*/,
+            0x54414349/*ICAT*/};
+    static unsigned int ion_[8] = {
+            0x006e6f69/*ion_*/,0x006e6f49/*Ion_*/,0x006e4f69/*iOn_*/,
+            0x006e4f49/*IOn_*/,0x004e6f69/*ioN_*/,0x004e6f49/*IoN_*/,
+            0x004e4f69/*iON_*/,0x004e4f49/*ION_*/};
+    static unsigned int sdp_[8] = {
+            0x00706473/*sdp_*/,0x00706453/*Sdp_*/,0x00704473/*sDp_*/,
+            0x00704453/*SDp_*/,0x00506473/*sdP_*/,0x00506453/*SdP_*/,
+            0x00504473/*sDP_*/,0x00504453/*SDP_*/};
+    str           str_type;
+    unsigned int  x;
+    char          *p;
+
+    if (!msg->content_type)
+    {
+                LM_WARN("the header Content-TYPE is absent!"
+                                "let's assume the content is text/plain ;-)\n");
+        return 1;
+    }
+
+    trim_len(str_type.len,str_type.s,msg->content_type->body);
+    if (str_type.len>=15 && (*str_type.s=='m' || *str_type.s=='M')
+        && strncasecmp(str_type.s, "multipart/mixed", 15) == 0) {
+        return 2;
+    }
+    p = str_type.s;
+    advance(p,4,str_type,error_1);
+    x = READ(p-4);
+    if (!one_of_16(x,appl))
+        goto other;
+    advance(p,4,str_type,error_1);
+    x = READ(p-4);
+    if (!one_of_16(x,icat))
+        goto other;
+    advance(p,3,str_type,error_1);
+    x = READ(p-3) & 0x00ffffff;
+    if (!one_of_8(x,ion_))
+        goto other;
+
+    /* skip spaces and tabs if any */
+    while (*p==' ' || *p=='\t')
+        advance(p,1,str_type,error_1);
+    if (*p!='/')
+    {
+                LM_ERR("no / found after primary type\n");
+        goto error;
+    }
+    advance(p,1,str_type,error_1);
+    while ((*p==' ' || *p=='\t') && p+1<str_type.s+str_type.len)
+        advance(p,1,str_type,error_1);
+
+    advance(p,3,str_type,error_1);
+    x = READ(p-3) & 0x00ffffff;
+    if (!one_of_8(x,sdp_)) {
+        if (strncasecmp(p-3, "trickle-ice-sdpfrag", 19) == 0)
+            return 3;
+        goto other;
+    }
+
+    if (*p==';'||*p==' '||*p=='\t'||*p=='\n'||*p=='\r'||*p==0) {
+                LM_DBG("type <%.*s> found valid\n", (int)(p-str_type.s), str_type.s);
+        return 1;
+    } else {
+                LM_ERR("bad end for type!\n");
+        return -1;
+    }
+
+    error_1:
+            LM_ERR("body ended :-(!\n");
+    error:
+    return -1;
+    other:
+            LM_ERR("invalid type for a message\n");
+    return -1;
+}
+
+
+/*
+ * Get message body and check Content-Type header field
+ */
+int extract_body(struct sip_msg *msg, str *body )
+{
+    char c;
+    int ret;
+    str mpdel;
+    char *rest, *p1, *p2;
+    struct hdr_field hf;
+    unsigned int mime;
+
+    body->s = get_body(msg);
+    if (body->s==0) {
+                LM_ERR("failed to get the message body\n");
+        goto error;
+    }
+
+    /*
+     * Better use the content-len value - no need of any explicit
+     * parcing as get_body() parsed all headers and Conten-Length
+     * body header is automaticaly parsed when found.
+     */
+    if (msg->content_length==0) {
+                LM_ERR("failed to get the content length in message\n");
+        goto error;
+    }
+
+    body->len = get_content_length(msg);
+    if (body->len==0) {
+                LM_ERR("message body has length zero\n");
+        goto error;
+    }
+
+    if (body->len + body->s > msg->buf + msg->len) {
+                LM_ERR("content-length exceeds packet-length by %d\n",
+                       (int)((body->len + body->s) - (msg->buf + msg->len)));
+        goto error;
+    }
+
+    /* no need for parse_headers(msg, EOH), get_body will
+     * parse everything */
+    /*is the content type correct?*/
+    if((ret = check_content_type(msg))==-1)
+    {
+                LM_ERR("content type mismatching\n");
+        goto error;
+    }
+
+    if(ret!=2)
+        goto done;
+
+    /* multipart body */
+    if(get_mixed_part_delimiter(&msg->content_type->body,&mpdel) < 0) {
+        goto error;
+    }
+    p1 = find_sdp_line_delimiter(body->s, body->s+body->len, mpdel);
+    if (p1 == NULL) {
+                LM_ERR("empty multipart content\n");
+        return -1;
+    }
+    p2=p1;
+    c = 0;
+    for(;;)
+    {
+        p1 = p2;
+        if (p1 == NULL || p1 >= body->s+body->len)
+            break; /* No parts left */
+        p2 = find_next_sdp_line_delimiter(p1, body->s+body->len,
+                                          mpdel, body->s+body->len);
+        /* p2 is text limit for application parsing */
+        rest = eat_line(p1 + mpdel.len + 2, p2 - p1 - mpdel.len - 2);
+        if ( rest > p2 ) {
+                    LM_ERR("Unparsable <%.*s>\n", (int)(p1-p1), p1);
+            return -1;
+        }
+        while( rest<p2 ) {
+            memset(&hf,0, sizeof(struct hdr_field));
+            rest = get_sdp_hdr_field(rest, p2, &hf);
+            if(hf.type==HDR_EOH_T)
+                break;
+            if(hf.type==HDR_ERROR_T)
+                return -1;
+            if(hf.type==HDR_CONTENTTYPE_T) {
+                if(decode_mime_type(hf.body.s, hf.body.s + hf.body.len,
+                                    &mime)==NULL)
+                    return -1;
+                if (((((unsigned int)mime)>>16) == TYPE_APPLICATION)
+                    && ((mime&0x00ff) == SUBTYPE_SDP)) {
+                    c = 1;
+                }
+            }
+        } /* end of while */
+        if(c==1)
+        {
+            if (rest < p2 && *rest == '\r') rest++;
+            if (rest < p2 && *rest == '\n') rest++;
+            if (rest < p2 && p2[-1] == '\n') p2--;
+            if (rest < p2 && p2[-1] == '\r') p2--;
+            body->s = rest;
+            body->len = p2-rest;
+            goto done;
+        }
+    }
+
+    error:
+    return -1;
+
+    done:
+    /*LM_DBG("DEBUG:extract_body:=|%.*s|\n",body->len,body->s);*/
+    return ret; /* mirrors return type of check_content_type */
+}
+
+/*
+ * Some helper functions taken verbatim from tm module.
+ */
+
+/*
+ * Extract Call-ID value
+ * assumes the callid header is already parsed
+ * (so make sure it is, before calling this function or
+ *  it might fail even if the message _has_ a callid)
+ */
+int
+get_callid(struct sip_msg* _m, str* _cid)
+{
+
+    if ((parse_headers(_m, HDR_CALLID_F, 0) == -1)) {
+                LM_ERR("failed to parse call-id header\n");
+        return -1;
+    }
+
+    if (_m->callid == NULL) {
+                LM_ERR("call-id not found\n");
+        return -1;
+    }
+
+    _cid->s = _m->callid->body.s;
+    _cid->len = _m->callid->body.len;
+    trim(_cid);
+    return 0;
+}
+
+/*
+ * Extract tag from To header field of a response
+ */
+int
+get_to_tag(struct sip_msg* _m, str* _tag)
+{
+
+    if (parse_to_header(_m) < 0) {
+                LM_ERR("To header field missing\n");
+        return -1;
+    }
+
+    if (get_to(_m)->tag_value.len) {
+        _tag->s = get_to(_m)->tag_value.s;
+        _tag->len = get_to(_m)->tag_value.len;
+    } else {
+        _tag->s = NULL; /* fixes gcc 4.0 warnings */
+        _tag->len = 0;
+    }
+
+    return 0;
+}
+
+/*
+ * Extract tag from From header field of a request
+ */
+int
+get_from_tag(struct sip_msg* _m, str* _tag)
+{
+
+    if (parse_from_header(_m)<0) {
+                LM_ERR("failed to parse From header\n");
+        return -1;
+    }
+
+    if (get_from(_m)->tag_value.len) {
+        _tag->s = get_from(_m)->tag_value.s;
+        _tag->len = get_from(_m)->tag_value.len;
+    } else {
+        _tag->s = NULL; /* fixes gcc 4.0 warnings */
+        _tag->len = 0;
+    }
+
+    return 0;
+}
+
+/*
+ * Extract URI from the Contact header field
+ */
+int
+get_contact_uri(struct sip_msg* _m, struct sip_uri *uri, contact_t** _c)
+{
+
+    if ((parse_headers(_m, HDR_CONTACT_F, 0) == -1) || !_m->contact)
+        return -1;
+    if (!_m->contact->parsed && parse_contact(_m->contact) < 0) {
+                LM_ERR("failed to parse Contact body\n");
+        return -1;
+    }
+    *_c = ((contact_body_t*)_m->contact->parsed)->contacts;
+    if (*_c == NULL)
+        /* no contacts found */
+        return -1;
+
+    if (parse_uri((*_c)->uri.s, (*_c)->uri.len, uri) < 0 || uri->host.len <= 0) {
+                LM_ERR("failed to parse Contact URI [%.*s]\n",
+                       (*_c)->uri.len, ((*_c)->uri.s)?(*_c)->uri.s:"");
+        return -1;
+    }
+    return 0;
+}
+
+/*
+ * Extract branch from Via header
+ */
+int
+get_via_branch(struct sip_msg* msg, int vianum, str* _branch)
+{
+    struct via_body *via;
+    struct via_param *p;
+
+    if (parse_via_header(msg, vianum, &via) < 0)
+        return -1;
+
+    for (p = via->param_lst; p; p = p->next)
+    {
+        if (p->name.len == strlen("branch")
+            && strncasecmp(p->name.s, "branch", strlen("branch")) == 0) {
+            _branch->s = p->value.s;
+            _branch->len = p->value.len;
+            return 0;
+        }
+    }
+    return -1;
+}
+
+int get_sdp_ipaddr_media(struct sip_msg *msg, str *ip_addr) {
+    sdp_session_cell_t *sdp_session;
+    sdp_stream_cell_t *sdp_stream;
+    sdp_info_t *sdp = (sdp_info_t *) msg->body;
+    if (!sdp) {
+                LM_INFO("sdp null\n");
+        return -1;
+    }
+
+
+    int sdp_session_num = 0;
+    sdp_session = get_sdp_session(msg, sdp_session_num);
+
+    if (!sdp_session) {
+                LM_INFO("can not get the sdp session\n");
+        return 0;
+    }
+
+    if (sdp_session->ip_addr.s && sdp_session->ip_addr.len > 0) {
+                LM_INFO("sdp_session->ip_addr:%.*s\n", sdp_session->ip_addr.len, sdp_session->ip_addr.s);
+        ip_addr->s = sdp_session->ip_addr.s;
+        ip_addr->len = sdp_session->ip_addr.len;
+        trim(ip_addr);
+    }
+    else {
+        int sdp_stream_num = 0;
+        sdp_stream = get_sdp_stream(msg, sdp_session_num, sdp_stream_num);
+        if (!sdp_stream) {
+                    LM_INFO("can not get the sdp stream\n");
+            return 0;
+        }
+        if (sdp_stream->ip_addr.s && sdp_stream->ip_addr.len > 0) {
+            LM_INFO("sdp_stream->ip_addr:%.*s\n", sdp_stream->ip_addr.len, sdp_stream->ip_addr.s);
+            ip_addr->s = sdp_stream->ip_addr.s;
+            ip_addr->len = sdp_stream->ip_addr.len;
+            trim(ip_addr);
+        }
+    }
+
+    
+    return 0;
+}
+
+
+int get_sdp_port_media(struct sip_msg *msg, str *port){
+//    sdp_session_cell_t *sdp_session;
+    sdp_stream_cell_t *sdp_stream;
+    int sdp_session_num = 0;
+
+    sdp_info_t *sdp = (sdp_info_t *)msg->body;
+    if(!sdp) {
+                LM_INFO("sdp null\n");
+        return -1;
+    }
+
+//    sdp_session = get_sdp_session(msg, sdp_session_num);
+//    if(!sdp_session) {
+//                LM_INFO("can not get the sdp session\n");
+//        return 0;
+//    } else {
+//                LM_INFO("NEW_IP_ADDRESS:>%.*s>\n", sdp_session->ip_addr.len, sdp_session->ip_addr.s);
+//        lrk_sdp_info->ip_addr.s = sdp_session->ip_addr;
+        int sdp_stream_num = 0;
+        sdp_stream = get_sdp_stream(msg, sdp_session_num, sdp_stream_num);
+        if (!sdp_stream) {
+                    LM_INFO("can not get the sdp stream\n");
+            return -1;
+        } else {
+//                    LM_INFO ("PORT:<%.*s>\n", sdp_stream->port.len, sdp_stream->port.s);
+//            str2int(&sdp_stream->port, lrk_sdp_info->port)
+            port->s = sdp_stream->port.s;
+            port->len = sdp_stream->port.len;
+            trim(port);
+        }
+//    }
+    return 0;
+
+}
+

+ 41 - 0
src/modules/lrkproxy/lrkproxy_funcs.h

@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2001-2003 FhG Fokus
+ * Copyright (C) 2014-2015 Sipwise GmbH, http://www.sipwise.com
+ * Copyright (C) 2020 Mojtaba Esfandiari.S, Nasim-Telecom
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+#ifndef _LRKPROXY_FUNCS_H
+#define _LRKPROXY_FUNCS_H
+
+
+#include "../../core/str.h"
+#include "../../core/parser/msg_parser.h"
+#include "../../core/parser/contact/contact.h"
+
+int extract_body(struct sip_msg * , str *);
+int check_content_type(struct sip_msg * );
+int get_callid(struct sip_msg *, str *);
+int get_to_tag(struct sip_msg *, str *);
+int get_from_tag(struct sip_msg *, str *);
+int get_contact_uri(struct sip_msg *, struct sip_uri *, contact_t **);
+int get_via_branch(struct sip_msg *, int, str *);
+int get_sdp_ipaddr_media(struct sip_msg *msg, str *ip_addr);
+int get_sdp_port_media(struct sip_msg *msg, str *port);
+
+
+#endif //_LRKPROXY_FUNCS_H

+ 522 - 0
src/modules/lrkproxy/lrkproxy_hash.c

@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2003-2008 Sippy Software, Inc., http://www.sippysoft.com
+ * Copyright (C) 2014-2015 Sipwise GmbH, http://www.sipwise.com
+ * Copyright (C) 2020 Mojtaba Esfandiari.S, Nasim-Telecom
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#include "lrkproxy.h"
+#include "lrkproxy_hash.h"
+
+#include "../../core/str.h"
+#include "../../core/dprint.h"
+#include "../../core/mem/shm_mem.h"
+#include "../../core/timer.h"
+
+static void lrkproxy_hash_table_free_row_lock(gen_lock_t *row_lock);
+
+static struct lrkproxy_hash_table *lrkproxy_hash_table;
+
+/* get from sipwise rtpengine */
+static int str_cmp_str(const str a, const str b) {
+    if (a.len < b.len)
+        return -1;
+    if (a.len > b.len)
+        return 1;
+    if (a.len == 0 && b.len == 0)
+        return 0;
+    return memcmp(a.s, b.s, a.len);
+}
+
+/* get from sipwise rtpengine */
+static int str_equal(str a, str b) {
+    return (str_cmp_str(a, b) == 0);
+}
+
+/* get from sipwise rtpengine */
+static unsigned int str_hash(str s) {
+    unsigned int ret = 5381;
+    str it = s;
+
+    while (it.len > 0) {
+        ret = (ret << 5) + ret + *it.s;
+        it.s++;
+        it.len--;
+    }
+
+    return ret % lrkproxy_hash_table->size;
+}
+
+/* lrkproxy hash API */
+int lrkproxy_hash_table_init(int size) {
+    int i;
+    int hash_table_size;
+
+
+    hash_table_size = size;
+
+
+//            LM_DBG(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>lrkproxy_hash_table size = %d\n", hash_table_size);
+            LM_INFO(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>lrkproxy_hash_table size = %d\n", hash_table_size);
+
+    // init hashtable
+    lrkproxy_hash_table = shm_malloc(sizeof(struct lrkproxy_hash_table));
+    if (!lrkproxy_hash_table) {
+                LM_ERR("no shm left to create lrkproxy_hash_table\n");
+        return 0;
+    }
+    memset(lrkproxy_hash_table, 0, sizeof(struct lrkproxy_hash_table));
+    lrkproxy_hash_table->size = hash_table_size;
+
+    // init hashtable row_locks
+    lrkproxy_hash_table->row_locks = shm_malloc(hash_table_size * sizeof(gen_lock_t*));
+    if (!lrkproxy_hash_table->row_locks) {
+                LM_ERR("no shm left to create lrkproxy_hash_table->row_locks\n");
+        lrkproxy_hash_table_destroy();
+        return 0;
+    }
+    memset(lrkproxy_hash_table->row_locks, 0, hash_table_size * sizeof(gen_lock_t*));
+
+    // init hashtable row_entry_list
+    lrkproxy_hash_table->row_entry_list = shm_malloc(lrkproxy_hash_table->size * sizeof(struct lrkproxy_hash_entry*));
+    if (!lrkproxy_hash_table->row_entry_list) {
+                LM_ERR("no shm left to create lrkproxy_hash_table->row_entry_list\n");
+        lrkproxy_hash_table_destroy();
+        return 0;
+    }
+    memset(lrkproxy_hash_table->row_entry_list, 0, lrkproxy_hash_table->size * sizeof(struct lrkproxy_hash_entry*));
+
+    // init hashtable row_totals
+    lrkproxy_hash_table->row_totals = shm_malloc(hash_table_size * sizeof(unsigned int));
+    if (!lrkproxy_hash_table->row_totals) {
+                LM_ERR("no shm left to create lrkproxy_hash_table->row_totals\n");
+        lrkproxy_hash_table_destroy();
+        return 0;
+    }
+    memset(lrkproxy_hash_table->row_totals, 0, hash_table_size * sizeof(unsigned int));
+
+    // init hashtable  row_locks[i], row_entry_list[i] and row_totals[i]
+    for (i = 0; i < hash_table_size; i++) {
+        // alloc hashtable row_locks[i]
+        lrkproxy_hash_table->row_locks[i] = lock_alloc();
+        if (!lrkproxy_hash_table->row_locks[i]) {
+                    LM_ERR("no shm left to create lrkproxy_hash_table->row_locks[%d]\n", i);
+            lrkproxy_hash_table_destroy();
+            return 0;
+        }
+
+        // init hashtable row_locks[i]
+        if (!lock_init(lrkproxy_hash_table->row_locks[i])) {
+                    LM_ERR("fail to init lrkproxy_hash_table->row_locks[%d]\n", i);
+            lrkproxy_hash_table_destroy();
+            return 0;
+        }
+
+        // init hashtable row_entry_list[i]
+        lrkproxy_hash_table->row_entry_list[i] = shm_malloc(sizeof(struct lrkproxy_hash_entry));
+        if (!lrkproxy_hash_table->row_entry_list[i]) {
+                    LM_ERR("no shm left to create lrkproxy_hash_table->row_entry_list[%d]\n", i);
+            lrkproxy_hash_table_destroy();
+            return 0;
+        }
+        memset(lrkproxy_hash_table->row_entry_list[i], 0, sizeof(struct lrkproxy_hash_entry));
+
+        lrkproxy_hash_table->row_entry_list[i]->tout = -1;
+        lrkproxy_hash_table->row_entry_list[i]->next = NULL;
+
+        // init hashtable row_totals[i]
+        lrkproxy_hash_table->row_totals[i] = 0;
+    }
+
+    return 1;
+}
+
+int lrkproxy_hash_table_destroy() {
+    int i;
+
+    // check lrkproxy hashtable
+    if (!lrkproxy_hash_table) {
+                LM_ERR("NULL lrkproxy_hash_table\n");
+        return 1;
+    }
+
+    // check lrkproxy hashtable->row_locks
+    if (!lrkproxy_hash_table->row_locks) {
+                LM_ERR("NULL lrkproxy_hash_table->row_locks\n");
+        shm_free(lrkproxy_hash_table);
+        lrkproxy_hash_table = NULL;
+        return 1;
+    }
+
+    // destroy hashtable content
+    for (i = 0; i < lrkproxy_hash_table->size; i++) {
+        // lock
+        if (!lrkproxy_hash_table->row_locks[i]) {
+                    LM_ERR("NULL lrkproxy_hash_table->row_locks[%d]\n", i);
+            continue;
+        } else {
+            lock_get(lrkproxy_hash_table->row_locks[i]);
+        }
+
+        // check lrkproxy hashtable->row_entry_list
+        if (!lrkproxy_hash_table->row_entry_list) {
+                    LM_ERR("NULL lrkproxy_hash_table->row_entry_list\n");
+        } else {
+            // destroy hashtable row_entry_list[i]
+            lrkproxy_hash_table_free_row_entry_list(lrkproxy_hash_table->row_entry_list[i]);
+            lrkproxy_hash_table->row_entry_list[i] = NULL;
+        }
+
+        // unlock
+        lock_release(lrkproxy_hash_table->row_locks[i]);
+
+        // destroy hashtable row_locks[i]
+        lrkproxy_hash_table_free_row_lock(lrkproxy_hash_table->row_locks[i]);
+        lrkproxy_hash_table->row_locks[i] = NULL;
+    }
+
+    // destroy hashtable row_entry_list
+    if (!lrkproxy_hash_table->row_entry_list) {
+                LM_ERR("NULL lrkproxy_hash_table->row_entry_list\n");
+    } else {
+        shm_free(lrkproxy_hash_table->row_entry_list);
+        lrkproxy_hash_table->row_entry_list = NULL;
+    }
+
+    // destroy hashtable row_totals
+    if (!lrkproxy_hash_table->row_totals) {
+                LM_ERR("NULL lrkproxy_hash_table->row_totals\n");
+    } else {
+        shm_free(lrkproxy_hash_table->row_totals);
+        lrkproxy_hash_table->row_totals = NULL;
+    }
+
+    // destroy hashtable row_locks
+    if (!lrkproxy_hash_table->row_locks) {
+        // should not be the case; just for code symmetry
+                LM_ERR("NULL lrkproxy_hash_table->row_locks\n");
+    } else {
+        shm_free(lrkproxy_hash_table->row_locks);
+        lrkproxy_hash_table->row_locks = NULL;
+    }
+
+    // destroy hashtable
+    if (!lrkproxy_hash_table) {
+        // should not be the case; just for code symmetry
+                LM_ERR("NULL lrkproxy_hash_table\n");
+    } else {
+        shm_free(lrkproxy_hash_table);
+        lrkproxy_hash_table = NULL;
+    }
+
+    return 1;
+}
+
+
+void lrkproxy_hash_table_free_entry(struct lrkproxy_hash_entry *entry) {
+    if (!entry) {
+                LM_ERR("try to free a NULL entry\n");
+        return ;
+    }
+
+    // free callid
+    if (entry->callid.s) {
+        shm_free(entry->callid.s);
+    }
+
+    // free viabranch
+    if (entry->viabranch.s) {
+        shm_free(entry->viabranch.s);
+    }
+
+    // free entry
+    shm_free(entry);
+
+    return ;
+}
+
+void lrkproxy_hash_table_free_row_entry_list(struct lrkproxy_hash_entry *row_entry_list) {
+    struct lrkproxy_hash_entry *entry, *last_entry;
+
+    if (!row_entry_list) {
+                LM_ERR("try to free a NULL row_entry_list\n");
+        return ;
+    }
+
+    entry = row_entry_list;
+    while (entry) {
+        last_entry = entry;
+        entry = entry->next;
+        lrkproxy_hash_table_free_entry(last_entry);
+        last_entry = NULL;
+    }
+
+    return ;
+}
+
+int lrkproxy_hash_table_insert(str callid, str viabranch, struct lrkproxy_hash_entry *value) {
+    struct lrkproxy_hash_entry *entry, *last_entry;
+    struct lrkproxy_hash_entry *new_entry = (struct lrkproxy_hash_entry *) value;
+    unsigned int hash_index;
+
+    // sanity checks
+    if (!lrkproxy_hash_table_sanity_checks()) {
+                LM_ERR("sanity checks failed\n");
+        return 0;
+    }
+
+    // get entry list
+    hash_index = str_hash(callid);
+    entry = lrkproxy_hash_table->row_entry_list[hash_index];
+    last_entry = entry;
+
+    // lock
+    if (lrkproxy_hash_table->row_locks[hash_index]) {
+        lock_get(lrkproxy_hash_table->row_locks[hash_index]);
+    } else {
+                LM_ERR("NULL lrkproxy_hash_table->row_locks[%d]\n", hash_index);
+        return 0;
+    }
+
+    while (entry) {
+        // if found, don't add new entry
+        if (str_equal(entry->callid, new_entry->callid) &&
+            str_equal(entry->viabranch, new_entry->viabranch)) {
+            // unlock
+            lock_release(lrkproxy_hash_table->row_locks[hash_index]);
+                    LM_NOTICE("callid=%.*s, viabranch=%.*s already in hashtable, ignore new value\n",
+                              entry->callid.len, entry->callid.s,
+                              entry->viabranch.len, entry->viabranch.s);
+            return 0;
+        }
+
+        // if expired entry discovered, delete it
+        if (entry->tout < get_ticks()) {
+            // set pointers; exclude entry
+            last_entry->next = entry->next;
+
+            // free current entry; entry points to unknown
+            lrkproxy_hash_table_free_entry(entry);
+
+            // set pointers
+            entry = last_entry;
+
+            // update total
+            lrkproxy_hash_table->row_totals[hash_index]--;
+        }
+
+        // next entry in the list
+        last_entry = entry;
+        entry = entry->next;
+    }
+
+    last_entry->next = new_entry;
+
+    // update total
+    lrkproxy_hash_table->row_totals[hash_index]++;
+
+    // unlock
+    lock_release(lrkproxy_hash_table->row_locks[hash_index]);
+
+    return 1;
+}
+
+int lrkproxy_hash_table_remove(str callid, str viabranch, enum lrk_operation op) {
+    struct lrkproxy_hash_entry *entry, *last_entry;
+    unsigned int hash_index;
+    int found = 0;
+
+    // sanity checks
+    if (!lrkproxy_hash_table_sanity_checks()) {
+                LM_ERR("sanity checks failed\n");
+        return 0;
+    }
+
+    // get first entry from entry list; jump over unused list head
+    hash_index = str_hash(callid);
+    entry = lrkproxy_hash_table->row_entry_list[hash_index];
+    last_entry = entry;
+
+    // lock
+    if (lrkproxy_hash_table->row_locks[hash_index]) {
+        lock_get(lrkproxy_hash_table->row_locks[hash_index]);
+    } else {
+                LM_ERR("NULL lrkproxy_hash_table->row_locks[%d]\n", hash_index);
+        return 0;
+    }
+
+    while (entry) {
+        // if callid found, delete entry
+        if ((str_equal(entry->callid, callid) && str_equal(entry->viabranch, viabranch)) ||
+            (str_equal(entry->callid, callid) && viabranch.len == 0 && op == OP_DELETE)) {
+            // set pointers; exclude entry
+            last_entry->next = entry->next;
+
+            // free current entry; entry points to unknown
+            lrkproxy_hash_table_free_entry(entry);
+
+            // set pointers
+            entry = last_entry;
+
+            // update total
+            lrkproxy_hash_table->row_totals[hash_index]--;
+
+            found = 1;
+
+            if (!(viabranch.len == 0 && op == OP_DELETE)) {
+                // unlock
+                lock_release(lrkproxy_hash_table->row_locks[hash_index]);
+                return found;
+            }
+
+            // try to also delete other viabranch entries for callid
+            last_entry = entry;
+            entry = entry->next;
+            continue;
+        }
+
+        // if expired entry discovered, delete it
+        if (entry->tout < get_ticks()) {
+            // set pointers; exclude entry
+            last_entry->next = entry->next;
+
+            // free current entry; entry points to unknown
+            lrkproxy_hash_table_free_entry(entry);
+
+            // set pointers
+            entry = last_entry;
+
+            // update total
+            lrkproxy_hash_table->row_totals[hash_index]--;
+        }
+
+        last_entry = entry;
+        entry = entry->next;
+    }
+
+    // unlock
+    lock_release(lrkproxy_hash_table->row_locks[hash_index]);
+
+    return found;
+}
+//struct lrkp_node *lrkproxy_hash_table_lookup(str callid, str viabranch, enum lrk_operation op) {
+//struct lrkproxy_hash_entry *lrkproxy_hash_table_lookup(str callid, str viabranch, enum lrk_operation op) {
+struct lrkproxy_hash_entry *lrkproxy_hash_table_lookup(str callid, str viabranch) {
+    struct lrkproxy_hash_entry *entry, *last_entry;
+    unsigned int hash_index;
+//    struct lrkp_node *node;
+
+    // sanity checks
+    if (!lrkproxy_hash_table_sanity_checks()) {
+                LM_ERR("sanity checks failed\n");
+        return 0;
+    }
+
+    // get first entry from entry list; jump over unused list head
+    hash_index = str_hash(callid);
+    entry = lrkproxy_hash_table->row_entry_list[hash_index];
+    last_entry = entry;
+
+    // lock
+    if (lrkproxy_hash_table->row_locks[hash_index]) {
+        lock_get(lrkproxy_hash_table->row_locks[hash_index]);
+    } else {
+                LM_ERR("NULL lrkproxy_hash_table->row_locks[%d]\n", hash_index);
+        return 0;
+    }
+
+    while (entry) {
+        // if callid found, return entry
+//        if ((str_equal(entry->callid, callid) && str_equal(entry->viabranch, viabranch)) ||
+//            (str_equal(entry->callid, callid) && viabranch.len == 0 && op == OP_DELETE)) {
+        if (str_equal(entry->callid, callid) && str_equal(entry->viabranch, viabranch)) {
+//            node = entry->node;
+            // unlock
+            lock_release(lrkproxy_hash_table->row_locks[hash_index]);
+            return entry;
+//            return node;
+        }
+
+        // if expired entry discovered, delete it
+        if (entry->tout < get_ticks()) {
+            // set pointers; exclude entry
+            last_entry->next = entry->next;
+
+            // free current entry; entry points to unknown
+            lrkproxy_hash_table_free_entry(entry);
+
+            // set pointers
+            entry = last_entry;
+
+            // update total
+            lrkproxy_hash_table->row_totals[hash_index]--;
+        }
+
+        last_entry = entry;
+        entry = entry->next;
+    }
+
+    // unlock
+    lock_release(lrkproxy_hash_table->row_locks[hash_index]);
+
+    return NULL;
+}
+
+
+static void lrkproxy_hash_table_free_row_lock(gen_lock_t *row_lock) {
+    if (!row_lock) {
+                LM_ERR("try to free a NULL lock\n");
+        return ;
+    }
+
+    lock_destroy(row_lock);
+    lock_dealloc(row_lock);
+
+    return ;
+}
+
+int lrkproxy_hash_table_sanity_checks() {
+    // check lrkproxy hashtable
+    if (!lrkproxy_hash_table) {
+                LM_ERR("NULL lrkproxy_hash_table\n");
+        return 0;
+    }
+
+    // check lrkproxy hashtable->row_locks
+    if (!lrkproxy_hash_table->row_locks) {
+                LM_ERR("NULL lrkproxy_hash_table->row_locks\n");
+        return 0;
+    }
+
+    // check lrkproxy hashtable->row_entry_list
+    if (!lrkproxy_hash_table->row_entry_list) {
+                LM_ERR("NULL lrkproxy_hash_table->row_entry_list\n");
+        return 0;
+    }
+
+    // check lrkproxy hashtable->row_totals
+    if (!lrkproxy_hash_table->row_totals) {
+                LM_ERR("NULL lrkproxy_hash_table->row_totals\n");
+        return 0;
+    }
+
+    return 1;
+}
+

+ 75 - 0
src/modules/lrkproxy/lrkproxy_hash.h

@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2003-2008 Sippy Software, Inc., http://www.sippysoft.com
+ * Copyright (C) 2014-2015 Sipwise GmbH, http://www.sipwise.com
+ * Copyright (C) 2020 Mojtaba Esfandiari.S, Nasim-Telecom
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * Kamailio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef LRKPROXY_HASH_H
+#define LRKPROXY_HASH_H
+
+#include "../../core/str.h"
+#include "../../core/locking.h"
+
+
+/* table entry */
+struct lrkproxy_hash_entry {
+    str src_ipv4;   //media ip address of initiator call in INVITE SIP message.
+    str dst_ipv4;   //media ip address of selected node in 200Ok SIP message.
+    str snat_ipv4;  //change media ip address to selected node.
+    str dnat_ipv4;  //change media ip address to orgin destination party.
+    str src_port;   //media port of initiator call in INVITE SIP message
+    str dst_port;   //media port of selected node in 200Ok SIP message.
+    str snat_port;  //change media port to selected node.
+    str dnat_port;  //change media port to orgin destination party.
+
+    str callid;				// call callid
+    str viabranch;				// call viabranch
+    struct lrkp_node *node;			// call selected node
+
+    unsigned int tout;			// call timeout
+    struct lrkproxy_hash_entry *next;	// call next
+};
+
+/* table */
+struct lrkproxy_hash_table {
+    struct lrkproxy_hash_entry **row_entry_list;	// vector of size pointers to entry
+    gen_lock_t **row_locks;				// vector of size pointers to locks
+    unsigned int *row_totals;			// vector of size numbers of entries in the hashtable rows
+    unsigned int size;				// hash table size
+};
+
+
+
+int lrkproxy_hash_table_init(int hsize);
+int lrkproxy_hash_table_destroy();
+int lrkproxy_hash_table_insert(str callid, str viabranch, struct lrkproxy_hash_entry *value);
+int lrkproxy_hash_table_remove(str callid, str viabranch, enum lrk_operation);
+struct lrkproxy_hash_entry *lrkproxy_hash_table_lookup(str callid, str viabranch);
+//struct lrkproxy_hash_entry *lrkproxy_hash_table_lookup(str callid, str viabranch, enum lrk_operation);
+//struct lrkp_node *lrkproxy_hash_table_lookup(str callid, str viabranch, enum lrk_operation);
+//void lrkproxy_hash_table_print();
+//unsigned int lrkproxy_hash_table_total();
+
+void lrkproxy_hash_table_free_entry(struct lrkproxy_hash_entry *entry);
+void lrkproxy_hash_table_free_row_entry_list(struct lrkproxy_hash_entry *row_entry_list);
+
+int lrkproxy_hash_table_sanity_checks();
+
+#endif //LRKPROXY_HASH_H

+ 12 - 10
src/modules/mangler/contact_ops.c

@@ -610,28 +610,28 @@ decode_uri (str* uri, char separator, str * result, str* dst_uri)
 		{
 			LOG(L_ERR,"ERROR: decode_uri: Unable to decode host address \n");
 			return -2;/* should I quit or ignore ? */
-		}			
+		}
 
 	if ((format.password.len > 0) && (format.username.len <= 0))
 		{
 			LOG(L_ERR,"ERROR: decode_uri: Password decoded but no username available\n");
 			return -3;
 		}
-		
+
 	/* a complete uri would be sip:username:password@ip:port;transport=protocol goes to
 	 * sip:enc_pref#username#password#ip#port#protocol@public_ip
 	 */
 	result->len = format.first + (uri->len - format.second);	/* not NULL terminated */
 	if (format.username.len > 0) result->len += format.username.len + 1;	//: or @
 	if (format.password.len > 0) result->len += format.password.len + 1;	//@
-		
+
 	/* if (format.ip.len > 0) */	     result->len += format.ip.len;
-		
+
 	if (format.port.len > 0)     result->len += 1 + format.port.len;	//:
 	if (format.protocol.len > 0) result->len += 1 + 10 + format.protocol.len;	//;transport=
-	
+
 	/* adding one comes from * */
-	result->s = pkg_malloc (result->len);
+	result->s = pkg_malloc (result->len + 1); /* NULL termination */
 	if (result->s == NULL)
 		{
 			LOG(L_ERR,"ERROR: decode_contact: Unable to allocate memory\n");
@@ -640,7 +640,7 @@ decode_uri (str* uri, char separator, str * result, str* dst_uri)
 	pos = result->s;
 	memcpy (pos, uri->s, format.first);	/* till sip: */
 	pos = pos + format.first;
-	
+
 	if (format.username.len > 0)
 	{
 		memcpy (pos, format.username.s, format.username.len);
@@ -662,7 +662,7 @@ decode_uri (str* uri, char separator, str * result, str* dst_uri)
 
 		memcpy (pos, format.ip.s, format.ip.len);
 		pos = pos + format.ip.len;
-	
+
 	if (format.port.len > 0)
 	{
 		memcpy (pos, ":", 1);
@@ -677,9 +677,11 @@ decode_uri (str* uri, char separator, str * result, str* dst_uri)
 		memcpy (pos, format.protocol.s, format.protocol.len);
 		pos = pos + format.protocol.len;
 	}
-	
+
 	memcpy (pos, uri->s + format.second, uri->len - format.second);	/* till end: */
-	
+
+	result->s[result->len] = '\0';
+
 	/* dst_uri */
 	if (dst_uri && format.rcv_ip.s){
 		dst_uri->len=4 /* sip: */ + format.rcv_ip.len;

+ 5 - 0
src/modules/ndb_redis/redis_client.c

@@ -1059,6 +1059,11 @@ int redisc_exec(str *srv, str *res, str *cmd, ...)
 			if(redisc_reconnect_server(rsrv)==0)
 			{
 				rpl->rplRedis = redisvCommand(rsrv->ctxRedis, cmd->s, ap4);
+				if(rpl->rplRedis == NULL)
+				{
+					redis_count_err_and_disable(rsrv);
+					goto error_exec;
+				}
 			} else {
 				LM_ERR("unable to reconnect to redis server: %.*s\n",
 						srv->len, srv->s);

+ 25 - 6
src/modules/pipelimit/README

@@ -46,7 +46,8 @@ Daniel-Constantin Mierla
         5. Functions
 
               5.1. pl_check(name [, algorithm, limit])
-              5.2. pl_drop([ [min ], max ])
+              5.2. pl_active(name)
+              5.3. pl_drop([ [min ], max ])
 
         6. RPC Commands
 
@@ -76,7 +77,8 @@ Daniel-Constantin Mierla
    1.14. Set clean_unused parameter
    1.15. Set clean_unused parameter at runtime
    1.16. pl_check usage
-   1.17. pl_drop usage
+   1.17. pl_active usage
+   1.18. pl_drop usage
 
 Chapter 1. Admin Guide
 
@@ -107,7 +109,8 @@ Chapter 1. Admin Guide
    5. Functions
 
         5.1. pl_check(name [, algorithm, limit])
-        5.2. pl_drop([ [min ], max ])
+        5.2. pl_active(name)
+        5.3. pl_drop([ [min ], max ])
 
    6. RPC Commands
 
@@ -379,7 +382,8 @@ kamcmd cfg.set_now_int pipelimit clean_unused 10
 5. Functions
 
    5.1. pl_check(name [, algorithm, limit])
-   5.2. pl_drop([ [min ], max ])
+   5.2. pl_active(name)
+   5.3. pl_drop([ [min ], max ])
 
 5.1.  pl_check(name [, algorithm, limit])
 
@@ -460,7 +464,22 @@ with unexpected retcode=$var(check_result)\n");
         }
 ...
 
-5.2.  pl_drop([ [min ], max ])
+5.2.  pl_active(name)
+
+   Check the pipe 'name' was already created. Return 1 (true) if the pipe
+   is found, -1 (false) if the pipe is not found.
+
+   This function can be used from ANY_ROUTE.
+
+   Example 1.17. pl_active usage
+...
+        if (!pl_active("one")) {
+                # pipe does not exist
+                exit;
+        }
+...
+
+5.3.  pl_drop([ [min ], max ])
 
    For the current request, a "503 - Server Unavailable" reply is sent
    back. The reply may or may not have a "Retry-After" header. If no
@@ -477,7 +496,7 @@ with unexpected retcode=$var(check_result)\n");
    This function can be used from
    REQUEST_ROUTE|BRANCH_ROUTE|FAILURE_ROUTE|ONSEND_ROUTE.
 
-   Example 1.17. pl_drop usage
+   Example 1.18. pl_drop usage
 ...
         if (!pl_check("one")) {
                 # send back a "503 - Server Unavailable"

+ 24 - 0
src/modules/pipelimit/doc/pipelimit_admin.xml

@@ -511,6 +511,30 @@ with unexpected retcode=$var(check_result)\n");
 </programlisting>
 		</example>
 	</section>
+	<section id="pipelimit.f.pl_active">
+		<title>
+		<function moreinfo="none">pl_active(name)</function>
+		</title>
+		<para>
+		Check the pipe 'name' was already created. Return 1 (true) if the pipe
+		is found, -1 (false) if the pipe is not found.
+		</para>
+		<para>
+		This function can be used from ANY_ROUTE.
+		</para>
+		<example>
+		<title><function>pl_active</function> usage</title>
+		<programlisting format="linespecific">
+...
+	if (!pl_active("one")) {
+		# pipe does not exist
+		exit;
+	}
+...
+</programlisting>
+		</example>
+	</section>
+
 	<section id="pipelimit.f.pl_drop">
 		<title>
 		<function moreinfo="none">pl_drop([ [min ], max ])</function>

+ 38 - 0
src/modules/pipelimit/pipelimit.c

@@ -122,6 +122,7 @@ static ticks_t pl_timer_handle(ticks_t, struct timer_ln*, void*);
 static void pl_timer_exec(unsigned int ticks, void *param);
 static int w_pl_check(struct sip_msg*, char *, char *);
 static int w_pl_check3(struct sip_msg*, char *, char *, char *);
+static int w_pl_active(sip_msg_t *, char *, char *);
 static int w_pl_drop_default(struct sip_msg*, char *, char *);
 static int w_pl_drop_forced(struct sip_msg*, char *, char *);
 static int w_pl_drop(struct sip_msg*, char *, char *);
@@ -133,6 +134,8 @@ static cmd_export_t cmds[]={
 		0,    ANY_ROUTE},
 	{"pl_check",      (cmd_function)w_pl_check3,       3, fixup_pl_check3,
 		0,    ANY_ROUTE},
+	{"pl_active",     (cmd_function)w_pl_active,       1, fixup_spve_null,
+		0,    ANY_ROUTE},
 	{"pl_drop",       (cmd_function)w_pl_drop_default, 0, 0,
 		0,    REQUEST_ROUTE|BRANCH_ROUTE|FAILURE_ROUTE|ONSEND_ROUTE},
 	{"pl_drop",       (cmd_function)w_pl_drop_forced,  1, fixup_uint_null,
@@ -729,6 +732,36 @@ static int fixup_pl_check3(void** param, int param_no)
 	return 0;
 }
 
+static int pl_active(sip_msg_t *msg, str *pipeid)
+{
+	pl_pipe_t *pipe = NULL;
+
+	pipe = pl_pipe_get(pipeid, 0);
+	if(pipe==NULL) {
+		LM_ERR("pipe does not exist [%.*s]\n", pipeid->len, pipeid->s);
+		return -1;
+	}
+
+	return 1;
+}
+
+/**
+ * checking if pipe is active
+ */
+static int w_pl_active(sip_msg_t* msg, char *p1, char *p2)
+{
+	str pipeid = {0, 0};
+
+	if(fixup_get_svalue(msg, (gparam_p)p1, &pipeid)!=0
+			|| pipeid.s == 0)
+	{
+		LM_ERR("invalid pipeid parameter");
+		return -1;
+	}
+
+	return pl_active(msg, &pipeid);
+}
+
 static void pl_timer_refresh(void)
 {
 	if(pl_load_fetch!=0) {
@@ -873,6 +906,11 @@ static sr_kemi_t sr_kemi_pipelimit_exports[] = {
 		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_INT,
 			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
 	},
+	{ str_init("pipelimit"), str_init("pl_active"),
+		SR_KEMIP_INT, pl_active,
+		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
+			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+	},
 	{ str_init("pipelimit"), str_init("pl_drop"),
 		SR_KEMIP_INT, ki_pl_drop,
 		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,

+ 2 - 2
src/modules/pipelimit/pl_ht.c

@@ -221,7 +221,7 @@ pl_pipe_t* pl_pipe_get(str *pipeid, int mode)
 	unsigned int cellid;
 	unsigned int idx;
 	pl_pipe_t *it;
-	
+
 	if(_pl_pipes_ht==NULL)
 		return NULL;
 
@@ -236,7 +236,7 @@ pl_pipe_t* pl_pipe_get(str *pipeid, int mode)
 	}
 	while(it!=NULL && it->cellid == cellid)
 	{
-		if(pipeid->len==it->name.len 
+		if(pipeid->len==it->name.len
 				&& strncmp(pipeid->s, it->name.s, pipeid->len)==0)
 		{
 			 if(mode==0) lock_release(&_pl_pipes_ht->slots[idx].lock);

+ 6 - 1
src/modules/presence/presence_dmq.c

@@ -102,11 +102,15 @@ static int pres_dmq_init_proc()
 		}
 	}
 
+	if(publ_cache_mode==PS_PCACHE_RECORD && pres_subs_dbmode==NO_DB) {
+		goto finish;
+	}
+
 	if(!pa_db) {
 		LM_DBG("Initializing presence DB connection for pid (%d)\n", my_pid());
 
 		if(pa_dbf.init == 0) {
-			LM_ERR("dmq_worker_init: database not bound\n");
+			LM_ERR("database not bound\n");
 			return -1;
 		}
 
@@ -123,6 +127,7 @@ static int pres_dmq_init_proc()
 		}
 	}
 
+finish:
 	*pres_dmq_proc_init = 1;
 
 	LM_DBG("process initialization complete\n");

+ 63 - 22
src/modules/pv/pv_branch.c

@@ -93,6 +93,12 @@ int pv_get_branchx_helper(sip_msg_t *msg, pv_param_t *param,
 			if(br->location_ua_len==0)
 				return pv_get_null(msg, param, res);
 			return pv_get_strlval(msg, param, res, br->location_ua, br->location_ua_len);
+		case 9: /* otcpid */
+			return pv_get_uintval(msg, param, res, br->otcpid);
+		case 10: /* instance */
+			if(br->instance_len==0)
+				return pv_get_null(msg, param, res);
+			return pv_get_strlval(msg, param, res, br->instance, br->instance_len);
 		default:
 			/* 0 - uri */
 			return pv_get_strlval(msg, param, res, br->uri, br->len);
@@ -283,6 +289,22 @@ int pv_set_branchx_helper(sip_msg_t *msg, pv_param_t *param,
 		case 8: /* location_ua */
 			/* do nothing - cannot set the location_ua */
 		break;
+		case 9: /* otcpid */
+			if(val==NULL || (val->flags&PV_VAL_NULL))
+			{
+				br->otcpid = 0;
+				break;
+			}
+			if(!(val->flags&PV_VAL_INT))
+			{
+				LM_ERR("int value required to set branch flags\n");
+				return -1;
+			}
+			br->otcpid = val->ri;
+		break;
+		case 10: /* instance */
+			/* do nothing - cannot set the instance */
+		break;
 		default:
 			/* 0 - uri */
 			if(val==NULL || (val->flags&PV_VAL_NULL))
@@ -335,42 +357,53 @@ int pv_parse_branchx_name(pv_spec_p sp, str *in)
 
 	switch(in->len)
 	{
-		case 3: 
-			if(strncmp(in->s, "uri", 3)==0)
-				sp->pvp.pvn.u.isname.name.n = 0;
+		case 1:
+			if(*in->s=='q' || *in->s=='Q')
+				sp->pvp.pvn.u.isname.name.n = 3;
 			else goto error;
 		break;
-		case 7: 
-			if(strncmp(in->s, "dst_uri", 7)==0)
-				sp->pvp.pvn.u.isname.name.n = 1;
+		case 3:
+			if(strncmp(in->s, "uri", 3)==0)
+				sp->pvp.pvn.u.isname.name.n = 0;
 			else goto error;
 		break;
-		case 4: 
+		case 4:
 			if(strncmp(in->s, "path", 4)==0)
 				sp->pvp.pvn.u.isname.name.n = 2;
 			else if (strncmp(in->s, "ruid", 4)==0)
 				sp->pvp.pvn.u.isname.name.n = 7;
 			else goto error;
 		break;
-		case 1: 
-			if(*in->s=='q' || *in->s=='Q')
-				sp->pvp.pvn.u.isname.name.n = 3;
+		case 5:
+			if(strncmp(in->s, "count", 5)==0)
+				sp->pvp.pvn.u.isname.name.n = 5;
+			else if(strncmp(in->s, "flags", 5)==0)
+				sp->pvp.pvn.u.isname.name.n = 6;
+			else goto error;
+		break;
+		case 6:
+			if(strncmp(in->s, "otcpid", 6)==0)
+				sp->pvp.pvn.u.isname.name.n = 9;
+			else goto error;
+		break;
+		case 7:
+			if(strncmp(in->s, "dst_uri", 7)==0)
+				sp->pvp.pvn.u.isname.name.n = 1;
+			else goto error;
+		break;
+		case 8:
+			if(strncmp(in->s, "instance", 8)==0)
+				sp->pvp.pvn.u.isname.name.n = 10;
 			else goto error;
 		break;
-		case 11: 
+		case 11:
 			if(strncmp(in->s, "send_socket", 11)==0)
 				sp->pvp.pvn.u.isname.name.n = 4;
 			else if(strncmp(in->s, "location_ua", 11)==0)
 				sp->pvp.pvn.u.isname.name.n = 8;
 			else goto error;
 		break;
-		case 5: 
-			if(strncmp(in->s, "count", 5)==0)
-				sp->pvp.pvn.u.isname.name.n = 5;
-			else if(strncmp(in->s, "flags", 5)==0)
-				sp->pvp.pvn.u.isname.name.n = 6;
-			else goto error;
-		break;
+
 		default:
 			goto error;
 	}
@@ -804,6 +837,7 @@ int sbranch_set_ruri(sip_msg_t *msg)
 		set_force_socket(msg, br->force_send_socket);
 
 	msg->reg_id = br->reg_id;
+	msg->otcpid = br->otcpid;
 	set_ruri_q(br->q);
 	old_bflags = 0;
 	getbflagsval(0, &old_bflags);
@@ -824,7 +858,9 @@ int sbranch_append(sip_msg_t *msg)
 	str path = {0};
 	str ruid = {0};
 	str location_ua = {0};
+	str instance = {0};
 	branch_t *br;
+	branch_t *newbr;
 
 	br = &_pv_sbranch;
 	if(br->len==0)
@@ -849,14 +885,19 @@ int sbranch_append(sip_msg_t *msg)
 		location_ua.s = br->location_ua;
 		location_ua.len = br->location_ua_len;
 	}
+	if(br->instance_len) {
+		instance.s = br->instance;
+		instance.len = br->instance_len;
+	}
 
-	if (append_branch(msg, &uri, &duri, &path, br->q, br->flags,
-					  br->force_send_socket, 0 /*instance*/, br->reg_id,
-					  &ruid, &location_ua)
-			    == -1) {
+	newbr = ksr_push_branch(msg, &uri, &duri, &path, br->q, br->flags,
+					  br->force_send_socket, &instance, br->reg_id,
+					  &ruid, &location_ua);
+	if(newbr==NULL) {
 		LM_ERR("failed to append static branch\n");
 		return -1;
 	}
+	newbr->otcpid = br->otcpid;
 	return 0;
 }
 

+ 3 - 2
src/modules/pv_headers/pvh_func.c

@@ -62,6 +62,7 @@ int pvh_collect_headers(struct sip_msg *msg)
 	char hvals[header_name_size][header_value_size];
 	int idx = 0, d_size = 0;
 	str val_part = STR_NULL;
+	char *marker = NULL;
 
 	if(pvh_hdrs_collected(msg)) {
 		LM_ERR("headers are already collected\n");
@@ -95,10 +96,10 @@ int pvh_collect_headers(struct sip_msg *msg)
 		val.len = hf->body.len;
 		val.s = hf->body.s;
 
-		if(strchr(val.s, ',') != NULL
+		if(( marker = pvh_detect_split_char(val.s)) != NULL
 				&& str_hash_case_get(&split_headers, name.s, name.len)) {
 
-			if(pvh_split_values(&val, hvals, &d_size, 1) < 0) {
+			if(pvh_split_values(&val, hvals, &d_size, 1, marker) < 0) {
 				LM_ERR("could not parse %.*s header comma separated "
 					   "value",
 						name.len, name.s);

+ 1 - 1
src/modules/pv_headers/pvh_hash.c

@@ -37,7 +37,7 @@ int pvh_str_hash_init(struct str_hash_table *ht, str *keys, char *desc)
 	int idx = 0, d_size = 0;
 	str val = STR_NULL;
 
-	if(pvh_split_values(keys, split, &d_size, 0) < 0) {
+	if(pvh_split_values(keys, split, &d_size, 0, NULL) < 0) {
 		LM_ERR("could not parse %s param\n", desc);
 		return -1;
 	}

+ 42 - 8
src/modules/pv_headers/pvh_str.c

@@ -103,10 +103,39 @@ int pvh_extract_display_uri(char *suri, str *display, str *duri)
 	return 1;
 }
 
-int pvh_split_values(
-		str *s, char d[][header_value_size], int *d_size, int keep_spaces)
+char *pvh_detect_split_char(char *val)
 {
-	char p;
+	char *quote_a = NULL, *quote_b = NULL;
+	char *split = NULL;
+
+	if(val == NULL)
+		return NULL;
+
+	split = strchr(val, ',');
+	if(split == NULL) {
+		LM_DBG("no split marker detected\n");
+		return NULL;
+	}
+
+	quote_a = strchr(val, '"');
+	if(quote_a == NULL || split < quote_a) {
+		LM_DBG("split marker detected[%ld], not between quotes\n", split - val);
+		return split;
+	}
+
+	quote_b = strchr(val + (split - quote_a + 1), '"');
+	if(quote_b == NULL) {
+		LM_DBG("split marker detected[%ld], quote occurrence unbalanced[%ld]\n",
+				split - val, quote_b - val);
+		return split;
+	}
+	return pvh_detect_split_char(val + (quote_b - val + 1));
+}
+
+int pvh_split_values(str *s, char d[][header_value_size], int *d_size,
+		int keep_spaces, char *marker)
+{
+	char *p = NULL;
 	int idx = 0, c_idx = 0;
 
 	*d_size = -1;
@@ -115,12 +144,17 @@ int pvh_split_values(
 		*d_size = 0;
 		return 1;
 	}
-
+	if(!marker)
+		marker = pvh_detect_split_char(s->s);
 	while(idx < s->len) {
-		strncpy(&p, s->s + idx++, 1);
-		if(keep_spaces == 0 && strncmp(&p, " ", 1) == 0)
+		p = s->s + idx++;
+		if(keep_spaces == 0 && strncmp(p, " ", 1) == 0)
 			continue;
-		if(strncmp(&p, ",", 1) == 0) {
+		if(p == marker) {
+			if(marker && idx < s->len) {
+				LM_DBG("search next split marker[%d]\n", idx);
+				marker = pvh_detect_split_char(p + 1);
+			}
 			if(c_idx == 0)
 				continue;
 			if(c_idx + 1 < header_value_size)
@@ -131,7 +165,7 @@ int pvh_split_values(
 		}
 		if(c_idx == 0)
 			(*d_size)++;
-		strncpy(&d[*d_size][c_idx++], &p, 1);
+		strncpy(&d[*d_size][c_idx++], p, 1);
 	}
 
 	if(c_idx > 0) {

+ 3 - 2
src/modules/pv_headers/pvh_str.h

@@ -34,7 +34,8 @@ int pvh_str_new(str *s, int size);
 int pvh_str_free(str *s);
 int pvh_str_copy(str *dst, str *src, unsigned int max_size);
 int pvh_extract_display_uri(char *suri, str *display, str *duri);
-int pvh_split_values(
-		str *s, char d[][header_value_size], int *d_size, int keep_spaces);
+char *pvh_detect_split_char(char *s);
+int pvh_split_values(str *s, char d[][header_value_size], int *d_size,
+		int keep_spaces, char *marker);
 
 #endif /* PV_STR_H */

+ 66 - 47
src/modules/registrar/README

@@ -85,15 +85,16 @@ Bogdan-Andre Iancu
 
               4.1. save(domain, [, flags [, uri]])
               4.2. lookup(domain [, uri])
-              4.3. lookup_branches(domain)
-              4.4. registered(domain [, uri [, match_option [,
+              4.3. lookup_to_dset(domain [, uri])
+              4.4. lookup_branches(domain)
+              4.5. registered(domain [, uri [, match_option [,
                       match_action]]])
 
-              4.5. add_sock_hdr(hdr_name)
-              4.6. unregister(domain, uri[, ruid])
-              4.7. reg_fetch_contacts(domain, uri, profile)
-              4.8. reg_free_contacts(profile)
-              4.9. reg_send_reply()
+              4.6. add_sock_hdr(hdr_name)
+              4.7. unregister(domain, uri[, ruid])
+              4.8. reg_fetch_contacts(domain, uri, profile)
+              4.9. reg_free_contacts(profile)
+              4.10. reg_send_reply()
 
         5. Event Routes
 
@@ -151,15 +152,16 @@ Bogdan-Andre Iancu
    1.34. Set use_expired_contacts parameter
    1.35. save usage
    1.36. lookup usage
-   1.37. lookup_branches usage
-   1.38. registered usage
-   1.39. add_sock_hdr usage
-   1.40. unregister usage
-   1.41. reg_fetch_contacts usage
-   1.42. reg_free_contacts usage
-   1.43. reg_send_reply usage
-   1.44. event_route[usrloc:contact-expired] usage
-   1.45. $ulc(name) usage
+   1.37. lookup_to_dset usage
+   1.38. lookup_branches usage
+   1.39. registered usage
+   1.40. add_sock_hdr usage
+   1.41. unregister usage
+   1.42. reg_fetch_contacts usage
+   1.43. reg_free_contacts usage
+   1.44. reg_send_reply usage
+   1.45. event_route[usrloc:contact-expired] usage
+   1.46. $ulc(name) usage
 
 Chapter 1. Admin Guide
 
@@ -217,13 +219,14 @@ Chapter 1. Admin Guide
 
         4.1. save(domain, [, flags [, uri]])
         4.2. lookup(domain [, uri])
-        4.3. lookup_branches(domain)
-        4.4. registered(domain [, uri [, match_option [, match_action]]])
-        4.5. add_sock_hdr(hdr_name)
-        4.6. unregister(domain, uri[, ruid])
-        4.7. reg_fetch_contacts(domain, uri, profile)
-        4.8. reg_free_contacts(profile)
-        4.9. reg_send_reply()
+        4.3. lookup_to_dset(domain [, uri])
+        4.4. lookup_branches(domain)
+        4.5. registered(domain [, uri [, match_option [, match_action]]])
+        4.6. add_sock_hdr(hdr_name)
+        4.7. unregister(domain, uri[, ruid])
+        4.8. reg_fetch_contacts(domain, uri, profile)
+        4.9. reg_free_contacts(profile)
+        4.10. reg_send_reply()
 
    5. Event Routes
 
@@ -968,13 +971,14 @@ kamcmd cfg.set_now_int registrar use_expired_contacts 0
 
    4.1. save(domain, [, flags [, uri]])
    4.2. lookup(domain [, uri])
-   4.3. lookup_branches(domain)
-   4.4. registered(domain [, uri [, match_option [, match_action]]])
-   4.5. add_sock_hdr(hdr_name)
-   4.6. unregister(domain, uri[, ruid])
-   4.7. reg_fetch_contacts(domain, uri, profile)
-   4.8. reg_free_contacts(profile)
-   4.9. reg_send_reply()
+   4.3. lookup_to_dset(domain [, uri])
+   4.4. lookup_branches(domain)
+   4.5. registered(domain [, uri [, match_option [, match_action]]])
+   4.6. add_sock_hdr(hdr_name)
+   4.7. unregister(domain, uri[, ruid])
+   4.8. reg_fetch_contacts(domain, uri, profile)
+   4.9. reg_free_contacts(profile)
+   4.10. reg_send_reply()
 
 4.1.  save(domain, [, flags [, uri]])
 
@@ -1055,7 +1059,8 @@ save("location", "0x00", "sip:[email protected]");
    Example 1.36. lookup usage
 ...
 lookup("location");
-switch ($retcode) {
+                        switch ($retcode) {:1
+
     case -1:
     case -3:
         sl_send_reply("404", "Not Found");
@@ -1066,7 +1071,21 @@ switch ($retcode) {
 };
 ...
 
-4.3.  lookup_branches(domain)
+4.3.  lookup_to_dset(domain [, uri])
+
+   Similar to lookup(...), but push the location contacts to destination
+   set, without changing the R-URI (first branch not changed, it creates
+   additional branches). For the meaning of the parameters and the return
+   codes, see the documentation for lookup(...) function.
+
+   This function can be used from REQUEST_ROUTE, FAILURE_ROUTE.
+
+   Example 1.37. lookup_to_dset usage
+...
+lookup_to_dset("location");
+...
+
+4.4.  lookup_branches(domain)
 
    The function performs lookup(domain) on r-uri and additional branches
    (only branches that have no other attributes set than uri).
@@ -1078,12 +1097,12 @@ switch ($retcode) {
 
    This function can be used from REQUEST_ROUTE, FAILURE_ROUTE.
 
-   Example 1.37. lookup_branches usage
+   Example 1.38. lookup_branches usage
 ...
 lookup_branches("location");
 ...
 
-4.4.  registered(domain [, uri [, match_option [, match_action]]])
+4.5.  registered(domain [, uri [, match_option [, match_action]]])
 
    The function returns true if the AOR in the URI is registered, false
    otherwise. The function does not modify the message being process, it
@@ -1110,7 +1129,7 @@ lookup_branches("location");
 
    This function can be used from ANY_ROUTE.
 
-   Example 1.38. registered usage
+   Example 1.39. registered usage
 ...
 if (registered("location")) {
         sl_send_reply("100", "Trying");
@@ -1124,7 +1143,7 @@ if (registered("location","$rz:$Au", 2)) {
 };
 ...
 
-4.5.  add_sock_hdr(hdr_name)
+4.6.  add_sock_hdr(hdr_name)
 
    Adds a new header to the current REGISTER request with “hdr_name” which
    contains the description of the received socket (proto:ip:port)
@@ -1137,12 +1156,12 @@ if (registered("location","$rz:$Au", 2)) {
 
    This function can be used from REQUEST_ROUTE.
 
-   Example 1.39. add_sock_hdr usage
+   Example 1.40. add_sock_hdr usage
 ...
 add_sock_hdr("Sock-Info");
 ...
 
-4.6.  unregister(domain, uri[, ruid])
+4.7.  unregister(domain, uri[, ruid])
 
    The function removes contacts associated with 'uri' from the location
    database. If 'ruid' is provided a specific contact is removed, if
@@ -1167,7 +1186,7 @@ add_sock_hdr("Sock-Info");
      * -2 - Error in unregistering user
      * -3 - Contacts for AOR not found
 
-   Example 1.40. unregister usage
+   Example 1.41. unregister usage
 ...
 unregister("location", "$ru");
 unregister("location", "sip:[email protected]");
@@ -1175,7 +1194,7 @@ unregister("location", "$ru", "$ulc(caller=>ruid)");
 unregister("location", "", "$ruid");
 ...
 
-4.7.  reg_fetch_contacts(domain, uri, profile)
+4.8.  reg_fetch_contacts(domain, uri, profile)
 
    The function fetches the contacts for 'uri' from table 'domain' to
    pseudo-variable $ulc(profile).
@@ -1191,13 +1210,13 @@ unregister("location", "", "$ruid");
 
    This function can be used from REQUEST_ROUTE, FAILURE_ROUTE.
 
-   Example 1.41. reg_fetch_contacts usage
+   Example 1.42. reg_fetch_contacts usage
 ...
 reg_fetch_contacts("location", "$ru", "callee");
 reg_fetch_contacts("location", "sip:[email protected]", "caller");
 ...
 
-4.8.  reg_free_contacts(profile)
+4.9.  reg_free_contacts(profile)
 
    The function frees the contacts from pseudo-variable $ulc(profile).
    Should be called to release the content of a profile. Anyhow, fetching
@@ -1210,12 +1229,12 @@ reg_fetch_contacts("location", "sip:[email protected]", "caller");
 
    This function can be used from REQUEST_ROUTE, FAILURE_ROUTE.
 
-   Example 1.42. reg_free_contacts usage
+   Example 1.43. reg_free_contacts usage
 ...
 reg_free_contacts("callee");
 ...
 
-4.9.  reg_send_reply()
+4.10.  reg_send_reply()
 
    The function sends the SIP reply that is normally sent by save(...),
    but that was skipped due to flag 0x2. It must be used after save(...,
@@ -1224,7 +1243,7 @@ reg_free_contacts("callee");
 
    This function can be used from REQUEST_ROUTE, FAILURE_ROUTE.
 
-   Example 1.43. reg_send_reply usage
+   Example 1.44. reg_send_reply usage
 ...
 save("location", "0x2");
 ...
@@ -1240,7 +1259,7 @@ reg_send_reply();
    Executed when a contact in location table has expired. The variable
    $ulc(exp=>...) is filled with the attributes of the expired contact.
 
-   Example 1.44. event_route[usrloc:contact-expired] usage
+   Example 1.45. event_route[usrloc:contact-expired] usage
 ...
 event_route[usrloc:contact-expired] {
     xlog("expired contact for $ulc(exp=>aor)\n");
@@ -1314,7 +1333,7 @@ event_route[usrloc:contact-expired] {
    The pseudo-variable accepts positive index value to access a specific
    contact record.
 
-   Example 1.45. $ulc(name) usage
+   Example 1.46. $ulc(name) usage
 ...
 if(reg_fetch_contacts("location", "$fu", "caller"))
 {

+ 25 - 1
src/modules/registrar/doc/registrar_admin.xml

@@ -1338,7 +1338,8 @@ save("location", "0x00", "sip:[email protected]");
 		<programlisting format="linespecific">
 ...
 lookup("location");
-switch ($retcode) {
+			switch ($retcode) {:1
+
     case -1:
     case -3:
         sl_send_reply("404", "Not Found");
@@ -1352,6 +1353,29 @@ switch ($retcode) {
 		</example>
 	</section>
 
+	<section id="registrar.f.lookup_to_dset">
+		<title>
+		<function moreinfo="none">lookup_to_dset(domain [, uri])</function>
+		</title>
+		<para>
+			Similar to lookup(...), but push the location contacts to destination
+			set, without changing the R-URI (first branch not changed, it creates
+			additional branches). For the meaning of the parameters and the return
+			codes, see the documentation for lookup(...) function.
+		</para>
+		<para>
+		This function can be used from REQUEST_ROUTE, FAILURE_ROUTE.
+		</para>
+		<example>
+		<title><function>lookup_to_dset</function> usage</title>
+		<programlisting format="linespecific">
+...
+lookup_to_dset("location");
+...
+</programlisting>
+		</example>
+	</section>
+
 	<section id="registrar.f.lookup_branches">
 		<title>
 		<function moreinfo="none">lookup_branches(domain)</function>

+ 0 - 1
src/modules/rtp_media_server/Makefile

@@ -9,5 +9,4 @@ BCUNITLIBS=-lbcunit
 MS2LIBS=-lmediastreamer
 
 LIBS=$(ORTPLIBS) $(BCUNITLIBS) $(MS2LIBS)
-DEFS+=-DKAMAILIO_MOD_INTERFACE
 include ../../Makefile.modules

+ 6 - 3
src/modules/siputils/contact_ops.c

@@ -165,12 +165,14 @@ int ki_decode_contact(sip_msg_t *msg)
 		return res;
 	} else {
 		/* we do not modify the original first line */
-		if((msg->new_uri.s == NULL) || (msg->new_uri.len == 0))
+		if((msg->new_uri.s == NULL) || (msg->new_uri.len == 0)) {
 			msg->new_uri = newUri;
-		else {
+		} else {
 			pkg_free(msg->new_uri.s);
 			msg->new_uri = newUri;
 		}
+		msg->parsed_uri_ok=0;
+		ruri_mark_new();
 	}
 	return 1;
 }
@@ -577,7 +579,7 @@ int decode_uri(str uri, char separator, str *result)
 			uri.len);
 
 	/* adding one comes from * */
-	result->s = pkg_malloc(result->len);
+	result->s = pkg_malloc(result->len + 1); /* NULL termination */
 	if(result->s == NULL) {
 		LM_ERR("unable to allocate pkg memory\n");
 		return -4;
@@ -626,6 +628,7 @@ int decode_uri(str uri, char separator, str *result)
 
 	memcpy(pos, uri.s + format.second, uri.len - format.second); /* till end: */
 
+	result->s[result->len] = '\0';
 	LM_DBG("New decoded uri [%.*s]\n", result->len, result->s);
 
 	return 0;

+ 25 - 1
src/modules/smsops/smsops_impl.c

@@ -173,6 +173,10 @@ void freeRP_DATA(sms_rp_data_t * rpdata) {
 #define BITMASK_HIGH_4BITS 0xF0
 #define BITMASK_LOW_4BITS 0x0F
 #define BITMASK_TP_UDHI 0x40
+#define BITMASK_TP_VPF 0x18
+#define BITMASK_TP_VPF_RELATIVE 0x10 
+#define BITMASK_TP_VPF_ENHANCED 0x08
+#define BITMASK_TP_VPF_ABSOLUTE 0x18
 
 // Encode SMS-Message by merging 7 bit ASCII characters into 8 bit octets.
 static int ascii_to_gsm(str sms, char * output_buffer, int buffer_size) {
@@ -525,7 +529,27 @@ int decode_3gpp_sms(struct sip_msg *msg) {
 				}
 				rp_data->pdu.pid = (unsigned char)body.s[p++];
 				rp_data->pdu.coding = (unsigned char)body.s[p++];
-				rp_data->pdu.validity = (unsigned char)body.s[p++];
+
+				// 3GPP TS 03.40 9.2.2.2 SMS SUBMIT type
+				// https://en.wikipedia.org/wiki/GSM_03.40
+				if(rp_data->pdu.msg_type == SUBMIT){
+					// 3GPP TS 03.40 9.2.3.3 TP Validity Period Format (TP VPF)
+					switch (rp_data->pdu.flags & BITMASK_TP_VPF){
+						case BITMASK_TP_VPF_RELATIVE:	// 3GPP TS 03.40 9.2.3.12.1 TP-VP (Relative format)
+							rp_data->pdu.validity = (unsigned char)body.s[p++];
+							break;
+						case BITMASK_TP_VPF_ENHANCED:	// 3GPP TS 03.40 9.2.3.12.2 TP-VP (Absolute format)
+							p += 7;
+							LM_WARN("3GPP TS 03.40 9.2.3.12.2 TP-VP (Absolute format) is not supported\n");
+							break;
+						case BITMASK_TP_VPF_ABSOLUTE:	// 3GPP TS 03.40 9.2.3.12.3 TP-VP (Enhanced format)
+							p += 7;
+							LM_WARN("3GPP TS 03.40 9.2.3.12.3 TP-VP (Enhanced format) is not supported\n");
+							break;
+						default:
+							break;
+					}
+				}
 
 				//TP-User-Data-Length and TP-User-Data
 				len = (unsigned char)body.s[p++];

+ 28 - 0
src/modules/stirshaken/Makefile

@@ -0,0 +1,28 @@
+#
+# stirshaken module makefile
+#
+# WARNING: do not run this directly, it should be run by the main Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=stirshaken.so
+
+ifeq ($(CROSS_COMPILE),)
+	BUILDER = $(shell which pkg-config)
+ifneq ($(BUILDER),)
+	PKGLIBSECSIPID = $(shell $(BUILDER) --exists stirshaken > /dev/null 2>&1 ; echo $$? )
+ifneq ($(PKGLIBSECSIPID),0)
+	BUILDER =
+endif
+endif
+endif
+
+ifeq ($(BUILDER),)
+	DEFS+= -I.
+	LIBS = -L. -lstirshaken
+else
+	DEFS+= $(shell pkg-config --cflags stirshaken)
+	LIBS = $(shell pkg-config --libs stirshaken)
+endif
+
+include ../../Makefile.modules

+ 489 - 0
src/modules/stirshaken/README

@@ -0,0 +1,489 @@
+Stirshaken Module
+
+Piotr Gregor
+
+   signalwire.com
+   <[email protected]>
+
+Edited by
+
+Piotr Gregor
+
+   <[email protected]>
+
+   Copyright © 2021 https://www.signalwire.com
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+        2. Dependencies
+
+              2.1. Kamailio Modules
+              2.2. External Libraries or Applications
+
+        3. Parameters
+
+              3.1. as_default_key (str)
+              3.2. vs_verify_x509_cert_path (int)
+              3.3. vs_ca_dir (str)
+              3.4. vs_crl_dir (str)
+              3.5. vs_identity_expire_s (int)
+              3.6. vs_connect_timeout_s (int)
+              3.7. vs_cache_certificates (int)
+              3.8. vs_cache_dir (str)
+              3.9. vs_cache_expire_s (int)
+
+        4. Functions
+
+              4.1. stirshaken_check_identity()
+              4.2. stirshaken_check_identity_with_key(keyPath)
+              4.3. stirshaken_check_identity_with_cert(certPath)
+              4.4. stirshaken_add_identity(x5u, attest, origtn_val,
+                      desttn_val, origid)
+
+              4.5. stirshaken_add_identity_with_key(x5u, attest,
+                      origtn_val, desttn_val, origid, keyPath)
+
+        5. Installation
+
+   List of Examples
+
+   1.1. Set as_default_key parameter
+   1.2. Set vs_verify_x509_cert_path parameter
+   1.3. Set vs_ca_dir parameter
+   1.4. Set vs_crl_dir parameter
+   1.5. Set vs_identity_expire_s parameter
+   1.6. Set vs_connect_timeout_s parameter
+   1.7. Set vs_cache_certificates parameter
+   1.8. Set vs_cache_dir parameter
+   1.9. Set vs_cache_expire_s parameter
+   1.10. stirshaken_check_identity usage
+   1.11. stirshaken_check_identity_with_key usage
+   1.12. stirshaken_check_identity_with_cert usage
+   1.13. stirshaken_add_identity with origid usage
+   1.14. stirshaken_add_identity with auto generated uuid as origid usage
+   1.15. stirshaken_add_identity_with_key usage
+   1.16. libstirshaken installation
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+   2. Dependencies
+
+        2.1. Kamailio Modules
+        2.2. External Libraries or Applications
+
+   3. Parameters
+
+        3.1. as_default_key (str)
+        3.2. vs_verify_x509_cert_path (int)
+        3.3. vs_ca_dir (str)
+        3.4. vs_crl_dir (str)
+        3.5. vs_identity_expire_s (int)
+        3.6. vs_connect_timeout_s (int)
+        3.7. vs_cache_certificates (int)
+        3.8. vs_cache_dir (str)
+        3.9. vs_cache_expire_s (int)
+
+   4. Functions
+
+        4.1. stirshaken_check_identity()
+        4.2. stirshaken_check_identity_with_key(keyPath)
+        4.3. stirshaken_check_identity_with_cert(certPath)
+        4.4. stirshaken_add_identity(x5u, attest, origtn_val, desttn_val,
+                origid)
+
+        4.5. stirshaken_add_identity_with_key(x5u, attest, origtn_val,
+                desttn_val, origid, keyPath)
+
+   5. Installation
+
+1. Overview
+
+   The module implements secure SIP identity specifications - STIR (Secure
+   Telephony Identity Revisited) and SHAKEN (Signature-based Handling of
+   Asserted information using toKENs) IETF extensions for SIP (RFC8224,
+   RFC8588).
+
+   stirshaken module exports the functions to check and to generate
+   PASSporT, wrapped into SIP Identity header. For call authentication two
+   functions are available: stirshaken_add_identity(...) and
+   stirshaken_add_identity_with_key(key). stirshaken_add_identity() uses
+   default key (through Authentication Service),
+   stirshaken_add_identity_with_key(..., key) uses specifoed key. For call
+   verification three methods are available: stirshaken_check_identity()
+   (through Verification Service), stirshaken_check_identity_with_key(key)
+   and stirshaken_check_identity_with_cert(cert).
+
+2. Dependencies
+
+   2.1. Kamailio Modules
+   2.2. External Libraries or Applications
+
+2.1. Kamailio Modules
+
+   The following modules must be loaded before this module:
+     * No dependencies on other Kamailio modules.
+
+2.2. External Libraries or Applications
+
+   The following libraries or applications must be installed before
+   running Kamailio with this module loaded:
+     * libstirshaken - https://github.com/signalwire/libstirshaken.
+
+3. Parameters
+
+   3.1. as_default_key (str)
+   3.2. vs_verify_x509_cert_path (int)
+   3.3. vs_ca_dir (str)
+   3.4. vs_crl_dir (str)
+   3.5. vs_identity_expire_s (int)
+   3.6. vs_connect_timeout_s (int)
+   3.7. vs_cache_certificates (int)
+   3.8. vs_cache_dir (str)
+   3.9. vs_cache_expire_s (int)
+
+3.1. as_default_key (str)
+
+   SSL private key to be used as default. Default key must be set if calls
+   to stirshaken_add_identity() are executed. When set, module starts
+   Authentication Service which makes each call to
+   stirshaken_add_identity() using this key. Default key doesn't need to
+   be set (Authentication Service doesn't need to be running) for the
+   stirshaken_add_identity_with_key(..., key) to be available. This param
+   has no meaning for calls to stirshaken_add_identity_with_key(..., key).
+
+   Default value is "" (not set).
+
+   Example 1.1. Set as_default_key parameter
+...
+modparam("stirshaken", "as_default_key", "/path/to/key")
+...
+
+3.2. vs_verify_x509_cert_path (int)
+
+   If set, then stirshaken_check_identity() will execute X509 certificate
+   path check on certificate referenced in PASSporT. This param has no
+   meaning for calls to stirshaken_check_identity_with_key(key) and
+   stirshaken_check_identity_with_cert(cert).
+
+   Default value is 0, (turned off).
+
+   Example 1.2. Set vs_verify_x509_cert_path parameter
+...
+modparam("stirshaken", "vs_verify_x509_cert_path", 1)
+...
+
+3.3. vs_ca_dir (str)
+
+   The path to folder containing CA root certificates with names hashed.
+   If set then must point to existing directory. This must be set when
+   enabled X509 certificate path check, otherwise no end entity
+   certificate will pass that check. This param has no meaning for calls
+   to stirshaken_check_identity_with_key(key) and
+   stirshaken_check_identity_with_cert(cert).
+
+   Default value is "" (not set).
+
+   Example 1.3. Set vs_ca_dir parameter
+...
+modparam("stirshaken", "vs_ca_dir", "/path/to/ca_dir")
+...
+
+3.4. vs_crl_dir (str)
+
+   The path to folder containing CRLs. If set, then must point to existing
+   directory. This is optional when X509 certificate path check is
+   enabled, only vs_ca_dir is mandatory. If X509 certificate path check is
+   enabled, and vs_crl_dir is set, then CRLs are loaded from this
+   directory, which renders revoked certificates invalid (not trusted).
+   This param has no meaning for calls to
+   stirshaken_check_identity_with_key(key) and
+   stirshaken_check_identity_with_cert(cert).
+
+   Default value is "" (not set).
+
+   Example 1.4. Set vs_crl_dir parameter
+...
+modparam("stirshaken", "vs_crl_dir", "/path/to/crl_dir")
+...
+
+3.5. vs_identity_expire_s (int)
+
+   This parameter defines a maximum time in seconds for which PASSporT is
+   considered valid.
+
+   Default value is 60 seconds.
+
+   Example 1.5. Set vs_identity_expire_s parameter
+...
+modparam("stirshaken", "vs_identity_expire_s", 20)
+...
+
+3.6. vs_connect_timeout_s (int)
+
+   During a call verification with stirshaken_check_identity() a blocking
+   HTTP(s) call is executed to download certificate referneced in PASSporT
+   (unless certificate caching is turned on and a valid cert is found in
+   cache). This parameter defines a maximum time in seconds for this
+   blocking HTTP(s) connection to be established. After this time had
+   passed and connection did not succeed (could not resolve host, address
+   unreachable or other network errors) a call to
+   stirshaken_check_identity() will return with error. This param has no
+   meaning for calls to stirshaken_check_identity_with_key(key) and
+   stirshaken_check_identity_with_cert(cert).
+
+   Default value is 5 seconds.
+
+   Example 1.6. Set vs_connect_timeout_s parameter
+...
+modparam("stirshaken", "vs_connect_timeout_s", 10)
+...
+
+3.7. vs_cache_certificates (int)
+
+   If set, then certificates caching is turned on. This means that
+   certificates downloaded during call verification with
+   stirshaken_check_identity() are cached inside vs_cache_dir, and will be
+   loaded from that cache as long as they are not there for more than
+   vs_cache_expire_s seconds (see vs_cache_expire_s). If
+   vs_cache_certificates is set then vs_cache_dir must be set too and
+   pointing to existing directory. This param has no meaning for calls to
+   stirshaken_check_identity_with_key(key) and
+   stirshaken_check_identity_with_cert(cert).
+
+   Default value is 0 (turned off).
+
+   Example 1.7. Set vs_cache_certificates parameter
+...
+modparam("stirshaken", "vs_cache_certificates", 1)
+...
+
+3.8. vs_cache_dir (str)
+
+   If vs_cache_certificates is set then vs_cache_dir must be set too and
+   pointing to existing directory. Cached certificates are saved in this
+   directory and loaded from there when needed during a call verification
+   executed with stirshaken_check_identity(), as long as they are not
+   there for more than vs_cache_expire_s seconds. This param has no
+   meaning for calls to stirshaken_check_identity_with_key(key) and
+   stirshaken_check_identity_with_cert(cert).
+
+   Default value is "" (not set).
+
+   Example 1.8. Set vs_cache_dir parameter
+...
+modparam("stirshaken", "vs_cache_dir", "/tmp/cert_cache")
+...
+
+3.9. vs_cache_expire_s (int)
+
+   If vs_cache_certificates is set then cached certificates are saved in
+   vs_cache_dir directory and loaded from there when needed during a call
+   verification executed with stirshaken_check_identity(), as long as they
+   are not there for more than vs_cache_expire_s seconds. If they are in
+   cache for more than vs_cache_expire_s seconds, then a blocking HTTP(s)
+   call is executed to download a new version of (expired) certificate. If
+   this is successful then old version is removed and new version is saved
+   in cache. This param has no meaning for calls to
+   stirshaken_check_identity_with_key(key) and
+   stirshaken_check_identity_with_cert(cert).
+
+   Default value is 120 seconds.
+
+   Example 1.9. Set vs_cache_expire_s parameter
+...
+modparam("stirshaken", "vs_cache_expire_s", 15)
+...
+
+4. Functions
+
+   4.1. stirshaken_check_identity()
+   4.2. stirshaken_check_identity_with_key(keyPath)
+   4.3. stirshaken_check_identity_with_cert(certPath)
+   4.4. stirshaken_add_identity(x5u, attest, origtn_val, desttn_val,
+          origid)
+
+   4.5. stirshaken_add_identity_with_key(x5u, attest, origtn_val,
+          desttn_val, origid, keyPath)
+
+4.1.  stirshaken_check_identity()
+
+   Check the validity of the Identity header by decoding PASSporT's
+   signature with a certificate referenced in it's x5u header and
+   (optionally) checking that certificate for being trusted by X509
+   certificate check with CA root certificates in vs_ca_dir (and
+   optionally CRLs in vs_crl_dir). PASSporT's iat grant is also checked
+   for being too fresh or expired against vs_identity_expire_s seconds.
+   This function executes a call to a callback which may supply
+   certificates from cache (see vs_cache_certificates param). If
+   certificate needs to be downloaded this call will block for a maximum
+   of vs_connect_timeout_s seconds (see vs_connect_timeout_s param);
+
+   This function takes no parameters (only SIP message is passed
+   implicitly).
+
+   This function can be used from ANY_ROUTE.
+
+   Example 1.10. stirshaken_check_identity usage
+...
+modparam("stirshaken", "vs_verify_x509_cert_path", 1)
+modparam("stirshaken", "vs_ca_dir", "/path/to/ca")
+modparam("stirshaken", "vs_cache_certificates", 1)
+modparam("stirshaken", "vs_cache_dir", "/tmp/cert_cache")
+modparam("stirshaken", "vs_cache_expire_s", 100)
+
+request_route {
+    ...
+        if(stirshaken_check_identity()) { // bad identity }
+    ...
+}
+...
+
+4.2.  stirshaken_check_identity_with_key(keyPath)
+
+   Check the validity of the Identity header by decoding PASSporT's
+   signature with a key read from the location provided. PASSporT's iat
+   grant is also checked for being too fresh or expired against
+   vs_identity_expire_s seconds. This method does not involve HTTP(s)
+   transcations. This method does not execute a call to a callback
+   (vs_cache_certificates param has no meaning for this method). WARNING:
+   This method only checks if SIP Identity Header was signed by a key
+   corresponding to specified public key. This method doesn't attempt to
+   obtain certificate referenced in PASSporT (but PASSporT should be
+   checked with key corresponding to that certificate). Therefore it is
+   possible that this check will be successful, while PASSporT is not
+   valid (could be signed with key that doesn't match certificate
+   referenced in x5u header). If you want a complete Shaken check or if
+   you are not sure what you're doing, then you should execute
+   w_stirshaken_check_identity() instead (and configure Verification
+   Service to perform X509 certificate path verification with
+   stirshaken_vs_verify_x509_cert_path param set to 1).
+
+   The parameters can contain pseudo-variables.
+
+   This function can be used from ANY_ROUTE.
+
+   Example 1.11. stirshaken_check_identity_with_key usage
+...
+request_route {
+        ...
+        if(stirshaken_check_identity_with_key("/path/to/key")) { // bad identity
+ }
+        ...
+}
+...
+
+4.3.  stirshaken_check_identity_with_cert(certPath)
+
+   Same as stirshaken_check_identity_with_key(keyPath) but the key is read
+   from the certificate which is read from the location provided.
+
+   The parameters can contain pseudo-variables.
+
+   This function can be used from ANY_ROUTE.
+
+   Example 1.12. stirshaken_check_identity_with_cert usage
+...
+request_route {
+        ...
+        if(stirshaken_check_identity_with_cert("/path/to/cert")) { // bad identi
+ty }
+        ...
+}
+...
+
+4.4.  stirshaken_add_identity(x5u, attest, origtn_val, desttn_val, origid)
+
+   Add SIP Identity Header to the call using default private key (see
+   as_default_key param). Authenticate call with STIR-Shaken. If origID is
+   empty, a UUID string is generated to fill the field. The origtn_val
+   represents the origination telephone number; desttn_val, represents the
+   destination telephone number; x5u is the HTTP(s) URL referencing to the
+   public key that should be used to verify the signature; attest
+   represents the attestation level (should be "A", "B" or "C").
+
+   The parameters can contain pseudo-variables. If origid is empty, an
+   unique identifier will be generated wih libuuid, e.g.
+   "3f31bd2b-9fc4-4084-b0b0-566506c46292".
+
+   This function can be used from ANY_ROUTE.
+
+   Example 1.13. stirshaken_add_identity with origid usage
+...
+request_route {
+        ...
+        stirshaken_add_identity("https://sp.com/sp.pem", "B", "+44100", "+44200"
+, "origid");
+        ...
+}
+...
+
+   Example 1.14. stirshaken_add_identity with auto generated uuid as
+   origid usage
+
+   If origid is empty, an unique identifier will be generated with
+   libuuid, e.g. "3f31bd2b-9fc4-4084-b0b0-566506c46292".
+...
+request_route {
+        ...
+        stirshaken_add_identity("https://sp.com/sp.pem", "B", "+44100", "+44200"
+, "");
+        ...
+}
+...
+
+4.5.  stirshaken_add_identity_with_key(x5u, attest, origtn_val, desttn_val,
+origid, keyPath)
+
+   Same as stirshaken_add_identity() but using the key read from the
+   location provided as a last parameter.
+
+   The parameters can contain pseudo-variables. If origid is empty, an
+   unique identifier will be generated with libuuid, e.g.
+   "3f31bd2b-9fc4-4084-b0b0-566506c46292".
+
+   This function can be used from ANY_ROUTE.
+
+   Example 1.15. stirshaken_add_identity_with_key usage
+...
+request_route {
+        ...
+        stirshaken_add_identity_with_key("https://sp.com/sp.pem", "B", "+44100",
+ "+44200", uuid, "/path/to/key");
+        ...
+}
+...
+
+5. Installation
+
+   The module depends on "libstirshaken", which is an open source C
+   library from SignalWire. It can be downloaded from
+   https://github.com/signalwire/libstirshaken. Until the libstirshaken is
+   packaged in OS distributions, libstirshaken must be compiled and
+   installed before the stirshaken module can be compiled.
+
+   Installling libstirshaken is easy:
+
+   Example 1.16. libstirshaken installation
+...
+                git clone [email protected]:signalwire/libstirshaken.git
+                cd libstirshaken
+                ./bootstrap.sh
+                ./configure
+                make
+                make check
+                sudo make install
+...
+
+   After libstirshaken had been installed, Kamailio's stirshaken module
+   can then be built with
+...
+                cd /path/to/kamailio/
+                make modules modules=src/modules/stirshaken/
+...

+ 4 - 0
src/modules/stirshaken/doc/Makefile

@@ -0,0 +1,4 @@
+docs = stirshaken.xml
+
+docbook_dir = ../../../../doc/docbook
+include $(docbook_dir)/Makefile.module

+ 42 - 0
src/modules/stirshaken/doc/stirshaken.xml

@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+    <bookinfo>
+	<title>Stirshaken Module</title>
+	<productname class="trade">&kamailioname;</productname>
+	<authorgroup>
+	    <author>
+		<firstname>Piotr</firstname>
+		<surname>Gregor</surname>
+		<affiliation><orgname>signalwire.com</orgname></affiliation>
+		<email>[email protected]</email>
+		<address>
+		<otheraddr>
+		<ulink url="https://www.signalwire.com">https://www.signalwire.com</ulink>
+		</otheraddr>
+		</address>
+	    </author>
+	    <editor>
+		<firstname>Piotr</firstname>
+		<surname>Gregor</surname>
+		<email>[email protected]</email>
+	    </editor>
+	</authorgroup>
+	<copyright>
+	    <year>2021</year>
+	    <holder>https://www.signalwire.com</holder>
+	</copyright>
+    </bookinfo>
+    <toc></toc>
+
+    <xi:include href="stirshaken_admin.xml"/>
+
+</book>

+ 471 - 0
src/modules/stirshaken/doc/stirshaken_admin.xml

@@ -0,0 +1,471 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module User's Guide -->
+
+<chapter>
+
+	<title>&adminguide;</title>
+
+	<section>
+	<title>Overview</title>
+	<para>
+		The module implements secure SIP identity specifications - STIR
+		(Secure Telephony Identity Revisited) and SHAKEN
+		(Signature-based Handling of Asserted information using toKENs)
+		IETF extensions for SIP (RFC8224, RFC8588).
+	</para>
+	<para>
+		stirshaken module exports the functions to check and to generate PASSporT, wrapped into SIP Identity header.
+		For call authentication two functions are available: stirshaken_add_identity(...) and stirshaken_add_identity_with_key(key).
+		stirshaken_add_identity() uses default key (through Authentication Service), stirshaken_add_identity_with_key(..., key) uses specifoed key.
+		For call verification three methods are available: stirshaken_check_identity() (through Verification Service),
+		stirshaken_check_identity_with_key(key) and stirshaken_check_identity_with_cert(cert).
+	</para>
+	</section>
+	<section>
+	<title>Dependencies</title>
+	<section>
+		<title>&kamailio; Modules</title>
+		<para>
+		The following modules must be loaded before this module:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>No dependencies on other &kamailio; modules</emphasis>.
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	<section>
+		<title>External Libraries or Applications</title>
+		<para>
+		The following libraries or applications must be installed before running
+		&kamailio; with this module loaded:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>libstirshaken</emphasis> - https://github.com/signalwire/libstirshaken.
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	</section>
+	<section>
+	<title>Parameters</title>
+	<section>
+		<title><varname>as_default_key</varname> (str)</title>
+		<para>
+		SSL private key to be used as default. Default key must be set if calls to stirshaken_add_identity() are executed.
+		When set, module starts Authentication Service which makes each call to stirshaken_add_identity() using this key.
+		Default key doesn't need to be set (Authentication Service doesn't need to be running) for the stirshaken_add_identity_with_key(..., key) to be available.
+		This param has no meaning for calls to stirshaken_add_identity_with_key(..., key).
+		</para>
+		<para>
+		<emphasis>
+			Default value is "" (not set).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>as_default_key</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("stirshaken", "as_default_key", "/path/to/key")
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>vs_verify_x509_cert_path</varname> (int)</title>
+		<para>
+		If set, then stirshaken_check_identity() will execute X509 certificate path check on certificate referenced in PASSporT.
+		This param has no meaning for calls to stirshaken_check_identity_with_key(key) and stirshaken_check_identity_with_cert(cert).
+		</para>
+		<para>
+		<emphasis>
+			Default value is 0, (turned off).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>vs_verify_x509_cert_path</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("stirshaken", "vs_verify_x509_cert_path", 1)
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>vs_ca_dir</varname> (str)</title>
+		<para>
+		The path to folder containing CA root certificates with names hashed. If set then must point to existing directory.
+		This must be set when enabled X509 certificate path check, otherwise no end entity certificate will pass that check. 
+		This param has no meaning for calls to stirshaken_check_identity_with_key(key) and stirshaken_check_identity_with_cert(cert).
+		</para>
+		<para>
+		<emphasis>
+			Default value is "" (not set).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>vs_ca_dir</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("stirshaken", "vs_ca_dir", "/path/to/ca_dir")
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>vs_crl_dir</varname> (str)</title>
+		<para>
+		The path to folder containing CRLs. If set, then must point to existing directory.
+		This is optional when X509 certificate path check is enabled, only vs_ca_dir is mandatory. 
+		If X509 certificate path check is enabled, and vs_crl_dir is set, then CRLs are loaded from this directory,
+		which renders revoked certificates invalid (not trusted).
+		This param has no meaning for calls to stirshaken_check_identity_with_key(key) and stirshaken_check_identity_with_cert(cert).
+		</para>
+		<para>
+		<emphasis>
+			Default value is "" (not set).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>vs_crl_dir</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("stirshaken", "vs_crl_dir", "/path/to/crl_dir")
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>vs_identity_expire_s</varname> (int)</title>
+		<para>
+		This parameter defines a maximum time in seconds for which PASSporT is considered valid.
+		</para>
+		<para>
+		<emphasis>
+			Default value is 60 seconds.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>vs_identity_expire_s</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("stirshaken", "vs_identity_expire_s", 20)
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>vs_connect_timeout_s </varname> (int)</title>
+		<para>
+		During a call verification with stirshaken_check_identity() a blocking HTTP(s) call is executed to download certificate
+		referneced in PASSporT (unless certificate caching is turned on and a valid cert is found in cache).
+		This parameter defines a maximum time in seconds for this blocking HTTP(s) connection to be established.
+		After this time had passed and connection did not succeed (could not resolve host, address unreachable or other network errors) 
+		a call to stirshaken_check_identity() will return with error. 
+		This param has no meaning for calls to stirshaken_check_identity_with_key(key) and stirshaken_check_identity_with_cert(cert).
+		</para>
+		<para>
+		<emphasis>
+			Default value is 5 seconds.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>vs_connect_timeout_s</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("stirshaken", "vs_connect_timeout_s", 10)
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>vs_cache_certificates</varname> (int)</title>
+		<para>
+		If set, then certificates caching is turned on. This means that certificates downloaded during call verification with stirshaken_check_identity()
+		are cached inside vs_cache_dir, and will be loaded from that cache as long as they are not there for more than vs_cache_expire_s seconds (see vs_cache_expire_s).
+		If vs_cache_certificates is set then vs_cache_dir must be set too and pointing to existing directory.
+		This param has no meaning for calls to stirshaken_check_identity_with_key(key) and stirshaken_check_identity_with_cert(cert).
+		</para>
+		<para>
+		<emphasis>
+			Default value is 0 (turned off).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>vs_cache_certificates</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("stirshaken", "vs_cache_certificates", 1)
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>vs_cache_dir</varname> (str)</title>
+		<para>
+		If vs_cache_certificates is set then vs_cache_dir must be set too and pointing to existing directory.
+		Cached certificates are saved in this directory and loaded from there when needed during a call verification executed with stirshaken_check_identity(),
+		as long as they are not there for more than vs_cache_expire_s seconds.
+		This param has no meaning for calls to stirshaken_check_identity_with_key(key) and stirshaken_check_identity_with_cert(cert).
+		</para>
+		<para>
+		<emphasis>
+			Default value is "" (not set).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>vs_cache_dir</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("stirshaken", "vs_cache_dir", "/tmp/cert_cache")
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>vs_cache_expire_s</varname> (int)</title>
+		<para>
+		If vs_cache_certificates is set then cached certificates are saved in vs_cache_dir directory and loaded from there
+		when needed during a call verification executed with stirshaken_check_identity(), as long as they are not there for more than vs_cache_expire_s seconds.
+		If they are in cache for more than vs_cache_expire_s seconds, then a blocking HTTP(s) call is executed to download a new version of (expired) certificate.
+		If this is successful then old version is removed and new version is saved in cache.
+		This param has no meaning for calls to stirshaken_check_identity_with_key(key) and stirshaken_check_identity_with_cert(cert).
+		</para>
+		<para>
+		<emphasis>
+			Default value is 120 seconds.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>vs_cache_expire_s</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("stirshaken", "vs_cache_expire_s", 15)
+...
+</programlisting>
+		</example>
+	</section>
+
+	</section>
+
+	<section>
+	<title>Functions</title>
+	<section id="stirshaken.f.stirshaken_check_identity">
+		<title>
+		<function moreinfo="none">stirshaken_check_identity()</function>
+		</title>
+		<para>
+			Check the validity of the Identity header by decoding PASSporT's signature with a certificate referenced in it's x5u header
+			and (optionally) checking that certificate for being trusted by X509 certificate check with CA root certificates in vs_ca_dir
+			(and optionally CRLs in vs_crl_dir). PASSporT's iat grant is also checked for being too fresh or expired against vs_identity_expire_s seconds.
+			This function executes a call to a callback which may supply certificates from cache (see vs_cache_certificates param).
+			If certificate needs to be downloaded this call will block for a maximum of vs_connect_timeout_s seconds (see vs_connect_timeout_s param);
+		</para>
+		<para>
+		This function takes no parameters (only SIP message is passed implicitly).
+		</para>
+		<para>
+		This function can be used from ANY_ROUTE.
+		</para>
+		<example>
+		<title><function>stirshaken_check_identity</function> usage</title>
+		<programlisting format="linespecific">
+...
+modparam("stirshaken", "vs_verify_x509_cert_path", 1)
+modparam("stirshaken", "vs_ca_dir", "/path/to/ca")
+modparam("stirshaken", "vs_cache_certificates", 1)
+modparam("stirshaken", "vs_cache_dir", "/tmp/cert_cache")
+modparam("stirshaken", "vs_cache_expire_s", 100)
+
+request_route {
+    ...
+	if(stirshaken_check_identity()) { // bad identity }
+    ...
+}
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="stirshaken.f.stirshaken_check_identity_with_key">
+		<title>
+		<function moreinfo="none">stirshaken_check_identity_with_key(keyPath)</function>
+		</title>
+		<para>
+			Check the validity of the Identity header by decoding PASSporT's signature with a key read from the location provided.
+			PASSporT's iat grant is also checked for being too fresh or expired against vs_identity_expire_s seconds.
+			This method does not involve HTTP(s) transcations.
+			This method does not execute a call to a callback (vs_cache_certificates param has no meaning for this method).
+			WARNING:
+			This method only checks if SIP Identity Header was signed by a key corresponding to specified public key.
+			This method doesn't attempt to obtain certificate referenced in PASSporT (but PASSporT should be checked with key corresponding to that certificate).
+			Therefore it is possible that this check will be successful, while PASSporT is not valid (could be signed with key that doesn't match certificate referenced in x5u header).
+			If you want a complete Shaken check or if you are not sure what you're doing, then you should execute w_stirshaken_check_identity() instead
+			(and configure Verification Service to perform X509 certificate path verification with stirshaken_vs_verify_x509_cert_path param set to 1).
+		</para>
+		<para>
+		The parameters can contain pseudo-variables.
+		</para>
+		<para>
+		This function can be used from ANY_ROUTE.
+		</para>
+		<example>
+		<title><function>stirshaken_check_identity_with_key</function> usage</title>
+		<programlisting format="linespecific">
+...
+request_route {
+ 	...
+	if(stirshaken_check_identity_with_key("/path/to/key")) { // bad identity }
+ 	...
+}
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="stirshaken.f.stirshaken_check_identity_with_cert">
+		<title>
+		<function moreinfo="none">stirshaken_check_identity_with_cert(certPath)</function>
+		</title>
+		<para>
+			Same as stirshaken_check_identity_with_key(keyPath) but the key is read from the certificate which is read from the location provided.
+		</para>
+		<para>
+		The parameters can contain pseudo-variables.
+		</para>
+		<para>
+		This function can be used from ANY_ROUTE.
+		</para>
+		<example>
+		<title><function>stirshaken_check_identity_with_cert</function> usage</title>
+		<programlisting format="linespecific">
+...
+request_route {
+ 	...
+	if(stirshaken_check_identity_with_cert("/path/to/cert")) { // bad identity }
+ 	...
+}
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="stirshaken.f.stirshaken_add_identity">
+		<title>
+		<function moreinfo="none">stirshaken_add_identity(x5u, attest, origtn_val, desttn_val, origid)</function>
+		</title>
+		<para>
+			Add SIP Identity Header to the call using default private key (see as_default_key param). Authenticate call with STIR-Shaken.
+			If origID is empty, a UUID string is generated to fill the field. The origtn_val represents the origination telephone number;
+			desttn_val, represents the destination telephone number; x5u is the HTTP(s) URL referencing to the public key that should be used
+			to verify the signature; attest represents the attestation level (should be "A", "B" or "C").
+		</para>
+		<para>
+		The parameters can contain pseudo-variables.
+		If origid is empty, an unique identifier will be generated wih libuuid, e.g. "3f31bd2b-9fc4-4084-b0b0-566506c46292".
+		</para>
+		<para>
+		This function can be used from ANY_ROUTE.
+		</para>
+		<example>
+		<title><function>stirshaken_add_identity</function> with origid usage</title>
+		<programlisting format="linespecific">
+...
+request_route {
+ 	...
+	stirshaken_add_identity("https://sp.com/sp.pem", "B", "+44100", "+44200", "origid");
+	...
+}
+...
+</programlisting>
+		</example>
+		<example>
+		<title><function>stirshaken_add_identity</function> with auto generated uuid as origid usage</title>
+		<para>
+		If origid is empty, an unique identifier will be generated with libuuid, e.g. "3f31bd2b-9fc4-4084-b0b0-566506c46292".
+		</para>
+		<programlisting format="linespecific">
+...
+request_route {
+ 	...
+	stirshaken_add_identity("https://sp.com/sp.pem", "B", "+44100", "+44200", "");
+	...
+}
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="stirshaken.f.stirshaken_add_identity_with_key">
+		<title>
+		<function moreinfo="none">stirshaken_add_identity_with_key(x5u, attest, origtn_val, desttn_val, origid, keyPath)</function>
+		</title>
+		<para>
+			Same as stirshaken_add_identity() but using the key read from the location provided as a last parameter.
+		</para>
+		<para>
+		The parameters can contain pseudo-variables.
+		If origid is empty, an unique identifier will be generated with libuuid, e.g. "3f31bd2b-9fc4-4084-b0b0-566506c46292".
+		</para>
+		<para>
+		This function can be used from ANY_ROUTE.
+		</para>
+		<example>
+		<title><function>stirshaken_add_identity_with_key</function> usage</title>
+		<programlisting format="linespecific">
+...
+request_route {
+ 	...
+	stirshaken_add_identity_with_key("https://sp.com/sp.pem", "B", "+44100", "+44200", uuid, "/path/to/key");
+ 	...
+}
+...
+</programlisting>
+		</example>
+	</section>
+	</section>
+	<section id="stirshaken.s.installation">
+	<title>Installation</title>
+	<para>
+		The module depends on "libstirshaken", which is an open source C library from SignalWire. It can be downloaded from https://github.com/signalwire/libstirshaken.
+		Until the libstirshaken is packaged in OS distributions, libstirshaken must be compiled and installed before the stirshaken module can be compiled.
+	</para>
+	<para>
+		Installling libstirshaken is easy:
+	</para>
+		<example>
+		<title>libstirshaken installation</title>
+		<programlisting format="linespecific">
+...
+		git clone [email protected]:signalwire/libstirshaken.git
+		cd libstirshaken
+		./bootstrap.sh
+		./configure
+		make
+		make check
+		sudo make install
+...
+</programlisting>
+	<title>Building Kamailio's stirshaken module</title>
+	<para>
+		After libstirshaken had been installed, Kamailio's stirshaken module can then be built with
+	</para>
+		<programlisting format="linespecific">
+...
+		cd /path/to/kamailio/
+		make modules modules=src/modules/stirshaken/
+...
+</programlisting>
+		</example>
+	</section>
+
+</chapter>
+

+ 981 - 0
src/modules/stirshaken/stirshaken_mod.c

@@ -0,0 +1,981 @@
+/**
+ * Copyright (C) 2021 kamailio.org
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <stir_shaken.h>
+
+#include "../../core/sr_module.h"
+#include "../../core/dprint.h"
+#include "../../core/mod_fix.h"
+#include "../../core/data_lump.h"
+#include "../../core/lvalue.h"
+#include "../../core/kemi.h"
+
+MODULE_VERSION
+
+// Authentication service
+static str stirshaken_as_default_key = str_init("");
+
+// Verification service
+static int stirshaken_vs_verify_x509_cert_path = 0;
+static str stirshaken_vs_ca_dir = str_init("");
+static str stirshaken_vs_crl_dir = str_init("");
+static int stirshaken_vs_identity_expire_s = 60;
+static int stirshaken_vs_connect_timeout_s = 5;
+
+static int stirshaken_vs_cache_certificates = 0;
+static size_t stirshaken_vs_cache_expire_s = 120;
+static str stirshaken_vs_cache_dir = str_init("");
+
+static int mod_init(void);
+static int child_init(int);
+static void mod_destroy(void);
+
+static int w_stirshaken_check_identity(sip_msg_t *msg, char *str1, char *str2);
+static int w_stirshaken_check_identity_with_cert(sip_msg_t *msg, char *cert_path, char *str2);
+static int w_stirshaken_check_identity_with_key(sip_msg_t *msg, char *pkey_path, char *str2);
+static int w_stirshaken_add_identity(sip_msg_t *msg, str *px5u, str *pattest, str *porigtn_val, str *pdesttn_val, str *porigid);
+static int w_stirshaken_add_identity_with_key(sip_msg_t *msg, str *px5u, str *pattest, str *porigtn_val, str *pdesttn_val, str *porigid, str *pkeypath);
+
+
+/* clang-format off */
+static cmd_export_t cmds[]={
+	{"stirshaken_check_identity", (cmd_function)w_stirshaken_check_identity, 0,
+		0, 0, ANY_ROUTE},
+	{"stirshaken_check_identity_with_cert", (cmd_function)w_stirshaken_check_identity_with_cert, 1,
+		fixup_spve_null, fixup_free_spve_null, ANY_ROUTE},
+	{"stirshaken_check_identity_with_key", (cmd_function)w_stirshaken_check_identity_with_key, 1,
+		fixup_spve_null, fixup_free_spve_null, ANY_ROUTE},
+	{"stirshaken_add_identity", (cmd_function)w_stirshaken_add_identity, 5,
+		fixup_spve_all, fixup_free_spve_all, ANY_ROUTE},
+	{"stirshaken_add_identity_with_key", (cmd_function)w_stirshaken_add_identity_with_key, 6,
+		fixup_spve_all, fixup_free_spve_all, ANY_ROUTE},
+	{0, 0, 0, 0, 0, 0}
+};
+
+static param_export_t params[] = {
+
+	// Authentication service
+	{"as_default_key",			PARAM_STR,   &stirshaken_as_default_key},
+
+	// Verification service
+	{"vs_verify_x509_cert_path",PARAM_INT,   &stirshaken_vs_verify_x509_cert_path},
+	{"vs_ca_dir",				PARAM_STR,   &stirshaken_vs_ca_dir},
+	{"vs_crl_dir",				PARAM_STR,   &stirshaken_vs_crl_dir},
+	{"vs_identity_expire_s",	PARAM_INT,   &stirshaken_vs_identity_expire_s},
+	{"vs_connect_timeout_s",	PARAM_INT,   &stirshaken_vs_connect_timeout_s},
+	{"vs_cache_certificates",	PARAM_INT,   &stirshaken_vs_cache_certificates},
+	{"vs_cache_expire_s",		PARAM_INT,   &stirshaken_vs_cache_expire_s},
+	{"vs_cache_dir",			PARAM_STR,   &stirshaken_vs_cache_dir},
+	{0, 0, 0}
+};
+
+struct module_exports exports = {
+	"stirshaken",
+	DEFAULT_DLFLAGS, /* dlopen flags */
+	cmds,
+	params,
+	0,              /* exported RPC methods */
+	0,              /* exported pseudo-variables */
+	0,              /* response function */
+	mod_init,       /* module initialization function */
+	child_init,     /* per child init function */
+	mod_destroy    	/* destroy function */
+};
+/* clang-format on */
+
+static void stirshaken_print_error_details(void *context)
+{
+	const char				*error_description = NULL;
+	stir_shaken_error_t		error_code = STIR_SHAKEN_ERROR_GENERAL;
+
+	if (stir_shaken_is_error_set(context)) {
+		error_description = stir_shaken_get_error(context, &error_code);
+		LM_DBG("failure details:\n");
+		LM_DBG("failure reason is: %s\n", error_description);
+		LM_DBG("failure error code is: %d\n", error_code);
+	}
+}
+
+static unsigned long hash_to_long(const char *str)
+{
+	unsigned long hash = 5381;
+	int c;
+
+	while ((c = *str++))
+		hash = ((hash << 5) + hash) + c;
+
+	return hash;
+}
+
+static void hash_to_string(const char *url, char *buf, int buf_len)
+{
+	unsigned long hash_long = hash_to_long(url);
+	snprintf(buf, buf_len, "%lu.pem", hash_long);
+}
+
+static int get_cert_name_hashed(const char *name, char *buf, int buf_len)
+{
+	char cert_hash[64] = { 0 };
+
+	hash_to_string(name, cert_hash, sizeof(cert_hash));
+
+	if (!stir_shaken_make_complete_path(buf, buf_len, stirshaken_vs_cache_dir.s, cert_hash, "/")) {
+		LM_ERR("Cannot create cert name hashed\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static stir_shaken_status_t shaken_callback(stir_shaken_callback_arg_t *arg)
+{
+	stir_shaken_context_t ss = { 0 };
+	stir_shaken_cert_t cache_copy = { 0 };
+	char cert_full_path[STIR_SHAKEN_BUFLEN] = { 0 };
+
+	switch (arg->action) {
+
+		case STIR_SHAKEN_CALLBACK_ACTION_CERT_FETCH_ENQUIRY:
+
+			// Default behaviour for certificate fetch enquiry is to request downloading, but in some cases it would be useful to avoid that and use pre-cached certificate.
+			// Here, we supply libstirshaken with certificate we cached earlier, avoiding HTTP(S) download.
+			// We must return STIR_SHAKEN_STATUS_HANDLED to signal this to the library, otherwise it would execute HTTP(S) download
+
+			if (!stirshaken_vs_cache_certificates) {
+				LM_DBG("Certificate caching is turned off - requesting certificate %s to be downloaded...\n", arg->cert.public_url);
+				return STIR_SHAKEN_STATUS_NOT_HANDLED;
+			}
+
+			if (-1 == get_cert_name_hashed(arg->cert.public_url, cert_full_path, STIR_SHAKEN_BUFLEN)) {
+				LM_ERR("Cannot get cert name hashed\n");
+				goto exit;
+			}
+
+			LM_DBG("Checking for certificate %s in cache (looking for name: %s)\n", arg->cert.public_url, cert_full_path);
+
+			if (STIR_SHAKEN_STATUS_OK == stir_shaken_file_exists(cert_full_path)) {
+
+				LM_DBG("Certificate %s found in cache\n", arg->cert.public_url);
+
+				if (stirshaken_vs_cache_expire_s) {
+
+					struct stat attr = { 0 };
+					time_t now_s = time(NULL), diff = 0;
+
+					LM_DBG("Checking cached certificate against expiration setting of %zus\n", stirshaken_vs_cache_expire_s);
+
+					if (-1 == stat(cert_full_path, &attr)) {
+						LM_ERR("Cannot get modification timestamp on certificate %s. Error code is %d (%s)\n", cert_full_path, errno, strerror(errno));
+						goto exit;
+					}
+
+					if (now_s < attr.st_mtime) {
+						LM_ERR("Modification timestamp on certificate %s is invalid\n", cert_full_path);
+						goto exit;
+					}
+
+					diff = now_s - attr.st_mtime;
+
+					LM_DBG("Checking cached certificate against expiration setting of %zus (now is: %zu, file modification timestamp is: %zu, difference is: %zu)\n",
+						stirshaken_vs_cache_expire_s, now_s, attr.st_mtime, diff);
+
+					if (diff > stirshaken_vs_cache_expire_s) {
+						LM_WARN("Cached certificate %s is behind expiration threshold (%zu > %zu). Need to download new certificate...\n", cert_full_path, diff, stirshaken_vs_cache_expire_s);
+						goto exit;
+					} else {
+						LM_WARN("Cached certificate %s is valid for next %zus\n", cert_full_path, stirshaken_vs_cache_expire_s - diff);
+					}
+				}
+
+				if (!(cache_copy.x = stir_shaken_load_x509_from_file(&ss, cert_full_path))) {
+					LM_ERR("Cannot load X509 from file %s\n", cert_full_path);
+					goto exit;
+				}
+
+				if (STIR_SHAKEN_STATUS_OK != stir_shaken_cert_copy(&ss, &arg->cert, &cache_copy)) {
+					LM_ERR("Cannot copy certificate %s\n", cert_full_path);
+					stir_shaken_cert_deinit(&cache_copy);
+					goto exit;
+				}
+
+				stir_shaken_cert_deinit(&cache_copy);
+
+				return STIR_SHAKEN_STATUS_HANDLED;
+			}
+
+		default:
+			LM_DBG("Certificate %s not found in cache\n", arg->cert.public_url);
+			return STIR_SHAKEN_STATUS_NOT_HANDLED;
+	}
+
+exit:
+
+	return STIR_SHAKEN_STATUS_NOT_HANDLED;
+}
+
+stir_shaken_as_t *as = NULL;
+stir_shaken_vs_t *vs = NULL;
+
+static int start_as(stir_shaken_context_t *ss)
+{
+	as = stir_shaken_as_create(ss);
+	if (!as) {
+		LM_ERR("Cannot create Authentication Service\n");
+		stirshaken_print_error_details(ss);
+		return -1;
+	}
+
+	if (stirshaken_as_default_key.len > 0) {
+
+		if (STIR_SHAKEN_STATUS_OK != stir_shaken_as_load_private_key(ss, as, stirshaken_as_default_key.s)) {
+			LM_ERR("Failed to load private key (%s). Please check @as_default_key param\n", stirshaken_as_default_key.s);
+			stirshaken_print_error_details(ss);
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
+static int start_vs(stir_shaken_context_t *ss)
+{
+	vs = stir_shaken_vs_create(ss);
+	if (!vs) {
+		LM_ERR("Cannot create Verification Service\n");
+		stirshaken_print_error_details(ss);
+		return -1;
+	}
+
+	// Handle settings
+
+	stir_shaken_vs_set_connect_timeout(ss, vs, stirshaken_vs_connect_timeout_s);
+	stir_shaken_vs_set_callback(ss, vs, shaken_callback);
+
+	if (stirshaken_vs_cache_certificates) {
+
+		if (!stirshaken_vs_cache_dir.len) {
+			LM_ERR("Certificate caching is turned on but cache dir is not set. Please set cache dir\n");
+			return -1;
+		}
+
+		if (STIR_SHAKEN_STATUS_OK != stir_shaken_dir_exists(stirshaken_vs_cache_dir.s)) {
+			LM_ERR("Certificate caching is turned on but cache dir %s does not exist. Please check cache dir name\n", stirshaken_vs_cache_dir.s);
+			return -1;
+		}
+	}
+
+	if (stirshaken_vs_verify_x509_cert_path) {
+
+		stir_shaken_vs_set_x509_cert_path_check(ss, vs, 1);
+
+		if (stirshaken_vs_ca_dir.len > 0) {
+			if (STIR_SHAKEN_STATUS_OK != stir_shaken_vs_load_ca_dir(ss, vs, stirshaken_vs_ca_dir.s)) {
+				LM_ERR("Failed to init X509 cert store with CA dir\n");
+				stirshaken_print_error_details(ss);
+				return -1;
+			}
+		} else {
+			LM_WARN("Cert path check is turned on, but CA dir is not set. No end entity certificate will ever pass this check. Did you forget to set CA dir?\n");
+		}
+
+		if (stirshaken_vs_crl_dir.len > 0) {
+			if (STIR_SHAKEN_STATUS_OK != stir_shaken_vs_load_crl_dir(ss, vs, stirshaken_vs_crl_dir.s)) {
+				LM_ERR("Failed to init X509 cert store with CRL dir\n");
+				stirshaken_print_error_details(ss);
+				return -1;
+			}
+		} else {
+			LM_WARN("Cert path check is turned on, but CRL dir is not set (it's not mandatory). Did you forget to set CRL dir?\n");
+		}
+	}
+
+	return 0;
+}
+
+static int mod_init(void)
+{
+	stir_shaken_context_t	ss = { 0 };
+
+	LM_INFO("Initialising STIR-Shaken\n");
+
+	if (STIR_SHAKEN_STATUS_OK != stir_shaken_init(&ss, STIR_SHAKEN_LOGLEVEL_NOTHING)) {
+		LM_ERR("Cannot init libstirshaken\n");
+		stirshaken_print_error_details(&ss);
+		return -1;
+	}
+
+	if (0 == start_as(&ss)) {
+		if (stirshaken_as_default_key.len > 0) {
+			LM_INFO("Authentication Service ready (with default key %s)\n", stirshaken_as_default_key.s);
+		} else {
+			LM_INFO("Authentication Service ready (without default key)\n");
+		}
+	} else {
+		LM_WARN("Cannot start Authentication Service (%s)\n", stirshaken_as_default_key.len > 0 ? "with default key" : "without default key");
+	}
+
+	if (stirshaken_as_default_key.len == 0) {
+		LM_WARN("Authentication Service using default key will not be available, because 'as_default_key' is not set (only authentication through w_stirshaken_add_identity_with_key is possible using a specific key)\n");
+	}
+
+	if (0 == start_vs(&ss)) {
+		LM_INFO("Verification Service ready (%s)\n", stirshaken_vs_verify_x509_cert_path ? "with X509 cert path check" : "without X509 cert path check");
+	} else {
+		LM_WARN("Cannot start Verification Service (%s)\n", stirshaken_vs_verify_x509_cert_path ? "with X509 cert path check" : "without X509 cert path check");
+		stir_shaken_vs_destroy(&vs);
+	}
+
+	if ((vs && !stirshaken_vs_verify_x509_cert_path) || !vs) {
+		LM_WARN("A complete Shaken check with X509 certificate path verification will not be available (stirshaken_check_identity). "
+				"Only checks of PASSporT decoding will be performed via stirshaken_check_identity_with_key() "
+				"and stirshaken_check_identity_with_cert(). If you want complete check with downloaded certificate and/or X509 cert path verification "
+				"then please set @vs_verify_x509_cert_path param to 1 and configure @vs_ca_dir (and optionally @vs_crl_dir)\n");
+	}
+
+	return 0;
+}
+
+/**
+ * @brief Initialize async module children
+ */
+static int child_init(int rank)
+{
+	LM_INFO("mod stirshaken child init\n");
+	return 0;
+}
+
+static void mod_destroy(void)
+{
+	LM_INFO("mod stirshaken destroy\n");
+	stir_shaken_as_destroy(&as);
+	stir_shaken_vs_destroy(&vs);
+	stir_shaken_deinit();
+	return;
+}
+
+static int stirshaken_handle_cache(stir_shaken_context_t *ss, stir_shaken_passport_t *passport, stir_shaken_cert_t *cert)
+{
+	if (!passport || !cert)
+		return -1;
+
+	LM_DBG("Handling certificate cache...\n");
+
+	if (!stirshaken_vs_cache_dir.len) {
+		LM_ERR("Cache dir not set\n");
+		return -1;
+	}
+
+	if (!ss->cert_fetched_from_cache) {
+
+		// save certificate to cache with url as a key 
+		char cert_full_path[STIR_SHAKEN_BUFLEN] = { 0 };
+		const char *x5u = stir_shaken_passport_get_header(ss, passport, "x5u");
+
+		if (stir_shaken_zstr(x5u)) {
+
+			// This should never happen as stir_shaken_sih_verify returns error in such case
+
+			LM_ERR("PASSporT has no x5u\n");
+			return -1;
+		}
+
+		if (-1 == get_cert_name_hashed(x5u, cert_full_path, STIR_SHAKEN_BUFLEN)) {
+			LM_ERR("Cannot get cert name hashed\n");
+			return -1;
+		}
+
+		LM_DBG("Checking for presence of expired version of freshly downloaded certificate %s in cache (looking for name: %s)\n", x5u, cert_full_path);
+
+		if (STIR_SHAKEN_STATUS_OK == stir_shaken_file_exists(cert_full_path)) {
+
+			LM_DBG("Expired version of certificate %s found in cache (with name: %s). Removing it...\n", x5u, cert_full_path);
+
+			if (STIR_SHAKEN_STATUS_OK != stir_shaken_file_remove(cert_full_path)) {
+				LM_ERR("Couldn't remove certificate %s from cache\n", cert_full_path);
+				return -1;
+			}
+		}
+
+		LM_DBG("Saving fresh certificate %s in cache (with name: %s)...\n", x5u, cert_full_path);
+
+		if (STIR_SHAKEN_STATUS_OK != stir_shaken_x509_to_disk(ss, cert->x, cert_full_path)) {
+			LM_ERR("Failed to write cert %s to disk (as: %s)", x5u, cert_full_path);
+		}
+
+	} else {
+		LM_DBG("Certificate was fetched from cache, so skipping saving it\n");
+	}
+
+	return 0;
+}
+
+#define STIRSHAKEN_HDR_IDENTITY "Identity"
+#define STIRSHAKEN_HDR_IDENTITY_LEN (sizeof(STIRSHAKEN_HDR_IDENTITY) - 1)
+
+static int ki_stirshaken_check_identity(sip_msg_t *msg)
+{
+	str ibody = STR_NULL;
+	hdr_field_t *hf = NULL;
+
+	stir_shaken_context_t ss = { 0 };
+	stir_shaken_passport_t *passport_out = NULL;
+	stir_shaken_cert_t *cert_out = NULL;
+
+	for (hf = msg->headers; hf; hf = hf->next) {
+		if (hf->name.len == STIRSHAKEN_HDR_IDENTITY_LEN
+				&& strncasecmp(hf->name.s, STIRSHAKEN_HDR_IDENTITY,
+					STIRSHAKEN_HDR_IDENTITY_LEN) == 0)
+			break;
+	}
+
+	if (hf == NULL) {
+		LM_DBG("no identity header\n");
+		goto fail;
+	}
+
+	ibody = hf->body;
+
+	if (STIR_SHAKEN_STATUS_OK != stir_shaken_vs_sih_verify(&ss, vs, ibody.s, &cert_out, &passport_out)) {
+		LM_ERR("SIP Identity Header did not pass verification\n");
+		stirshaken_print_error_details(&ss);
+		goto fail;
+	}
+
+	if (stirshaken_vs_cache_certificates) {
+		stirshaken_handle_cache(&ss, passport_out, cert_out);
+	}
+
+	// Check that PASSporT applies to the current moment in time
+	if (STIR_SHAKEN_STATUS_OK != stir_shaken_passport_validate_iat_against_freshness(&ss, passport_out, stirshaken_vs_identity_expire_s)) {
+
+		stir_shaken_error_t error_code = 0;
+		stir_shaken_get_error(&ss, &error_code);
+
+		if (error_code == STIR_SHAKEN_ERROR_PASSPORT_INVALID_IAT_VALUE_FUTURE) {
+			LM_ERR("PASSporT not valid yet\n");
+		} else if (error_code == STIR_SHAKEN_ERROR_PASSPORT_INVALID_IAT_VALUE_EXPIRED) {
+			LM_ERR("PASSporT expired\n");
+		} else if (error_code == STIR_SHAKEN_ERROR_PASSPORT_INVALID_IAT) {
+			LM_ERR("PASSporT is missing @iat grant\n");
+		}
+
+		LM_ERR("PASSporT doesn't apply to the current moment in time\n");
+		goto fail;
+	}
+
+	if (stirshaken_vs_verify_x509_cert_path) {
+
+		LM_DBG("Running X509 certificate path verification\n");
+
+		if (!vs) {
+			LM_ERR("Verification Service not started\n");
+			goto fail;
+		}
+
+		if (STIR_SHAKEN_STATUS_OK != stir_shaken_verify_cert_path(&ss, cert_out, vs->store)) {
+			LM_ERR("Cert did not pass X509 path validation\n");
+			stirshaken_print_error_details(&ss);
+			goto fail;
+		}
+	}
+
+	LM_DBG("identity check: ok (%s)\n", ss.x509_cert_path_checked ? "with X509 cert path check" : "without X509 cert path check");
+	stir_shaken_passport_destroy(&passport_out);
+	stir_shaken_cert_destroy(&cert_out);
+	return 1;
+
+fail:
+	stir_shaken_passport_destroy(&passport_out);
+	stir_shaken_cert_destroy(&cert_out);
+	LM_ERR("identity check: fail\n");
+	return -1;
+}
+
+/**
+ * Verify SIP Identity Header (involves call from libstirshaken to cache_callback,
+ * wich will supply requested certificate from cache [if configured to do so]
+ * or will let libstirshaken to perform HTTP(s) GET request to download certificate).
+ * Verify a call with STIR-Shaken.
+ *
+ * This method checks if SIP Identity Header covers semantically valid PASSporT.
+ * This method consults cache_callback in an attempt to obtain certificate that is referenced in PASSporT's x5u header.
+ * This method checks if PASSporT verifies successfully with a public key retrieved from obtained certificate.
+ * Optionally (if Verification Service is configured to do so with stirshaken_vs_verify_x509_cert_path param set to 1)
+ * this method checks if certificate is trusted, by execution of X509 certificate path check.
+ * 
+ * Optionally:
+ * 		- retrieve PASSporT from SIP Identity Header
+ * 		- retrieve certificate referenced in PASSporT's x5u header
+ * 		- cache certificate
+ *
+ * Kamailio config usage example:
+ *
+ *		stirshaken_check_identity();
+ */
+static int w_stirshaken_check_identity(sip_msg_t *msg, char *str1, char *str2)
+{
+	LM_INFO("identity check\n");
+
+	if (!vs) {
+		LM_ERR("Cannot perform identity check involving certificate downloading, caching, or X509 cert pach checking, "
+				"because Verification Service is not running. Please turn on and configure Verification Service, "
+				"otherwise only stirshaken_check_identity_with_cert() and stirshaken_check_identity_with_key() "
+				"methods will be available. Check log file for module's initialisation errors\n");
+		return -1;
+	}
+
+	return ki_stirshaken_check_identity(msg);
+}
+
+static int ki_stirshaken_check_identity_with_cert(sip_msg_t *msg, str *cert_path)
+{
+	str ibody = STR_NULL;
+	hdr_field_t *hf = NULL;
+
+	stir_shaken_context_t ss = { 0 };
+	stir_shaken_passport_t *passport_out = NULL;
+	stir_shaken_cert_t cert = { 0 };
+
+	for (hf = msg->headers; hf; hf = hf->next) {
+		if (hf->name.len == STIRSHAKEN_HDR_IDENTITY_LEN
+				&& strncasecmp(hf->name.s, STIRSHAKEN_HDR_IDENTITY,
+					STIRSHAKEN_HDR_IDENTITY_LEN) == 0)
+			break;
+	}
+
+	if (hf == NULL) {
+		LM_DBG("no identity header\n");
+		goto fail;
+	}
+
+	ibody = hf->body;
+
+	if (!(cert.x = stir_shaken_load_x509_from_file(&ss, cert_path->s))) {
+		LM_DBG("Cannot load X509 from file\n");
+		stirshaken_print_error_details(&ss);
+		goto fail;
+	}
+
+	if (STIR_SHAKEN_STATUS_OK != stir_shaken_sih_verify_with_cert(&ss, ibody.s, &cert, &passport_out)) {
+		LM_ERR("SIP Identity Header did not pass verification against certificate\n");
+		stirshaken_print_error_details(&ss);
+		goto fail;
+	}
+
+	// Check that PASSporT applies to the current moment in time
+	if (STIR_SHAKEN_STATUS_OK != stir_shaken_passport_validate_iat_against_freshness(&ss, passport_out, stirshaken_vs_identity_expire_s)) {
+
+		stir_shaken_error_t error_code = 0;
+		stir_shaken_get_error(&ss, &error_code);
+
+		if (error_code == STIR_SHAKEN_ERROR_PASSPORT_INVALID_IAT_VALUE_FUTURE) {
+			LM_ERR("PASSporT not valid yet\n");
+		} else if (error_code == STIR_SHAKEN_ERROR_PASSPORT_INVALID_IAT_VALUE_EXPIRED) {
+			LM_ERR("PASSporT expired\n");
+		} else if (error_code == STIR_SHAKEN_ERROR_PASSPORT_INVALID_IAT) {
+			LM_ERR("PASSporT is missing @iat grant\n");
+		}
+
+		LM_ERR("PASSporT doesn't apply to the current moment in time\n");
+		goto fail;
+	}
+
+	LM_DBG("identity check: ok (%s)\n", ss.x509_cert_path_checked ? "with X509 cert path check" : "without X509 cert path check");
+
+	stir_shaken_passport_destroy(&passport_out);
+	stir_shaken_cert_deinit(&cert);
+	return 1;
+
+fail:
+	stir_shaken_passport_destroy(&passport_out);
+	stir_shaken_cert_deinit(&cert);
+	LM_ERR("identity check: fail\n");
+	return -1;
+}
+
+/**
+ * Verify SIP Identity Header against specified certificate (does not involve HTTP(s) GET request).
+ * WARNING:
+ * This method only checks if SIP Identity Header was signed by a key from certificate given as argument.
+ * This method doesn't attempt to obtain certificate referenced in PASSporT (but PASSporT should be checked with key corresponding to that certificate).
+ * Therefore it is possible that this check will be successful, while PASSporT is not valid (could be signed with key that doesn't match certificate referenced in x5u header).
+ * If you want a complete Shaken check or if you are not sure what you're doing, then you should execute w_stirshaken_check_identity() instead
+ * (and configure Verification Service to perform X509 certificate path verification with stirshaken_vs_verify_x509_cert_path param set to 1).
+ *
+ * Kamailio config usage example:
+ *
+ *		stirshaken_check_identity_with_cert("/path/to/cert");
+ */
+static int w_stirshaken_check_identity_with_cert(sip_msg_t *msg, char *cert_path, char *str2)
+{
+	str keyval = STR_NULL;
+
+	LM_INFO("identity check using certificate\n");
+
+	if(fixup_get_svalue(msg, (gparam_t*)cert_path, &keyval)<0) {
+		LM_ERR("failed to get certificate path parameter\n");
+		return -1;
+	}
+
+	return ki_stirshaken_check_identity_with_cert(msg, &keyval);
+}
+
+static int ki_stirshaken_check_identity_with_key(sip_msg_t *msg, str *keypath)
+{
+	str ibody = STR_NULL;
+	hdr_field_t *hf = NULL;
+
+	stir_shaken_context_t ss = { 0 };
+	stir_shaken_passport_t *passport_out = NULL;
+	unsigned char key[STIR_SHAKEN_PUB_KEY_RAW_BUF_LEN] = { 0 };
+	uint32_t key_len = STIR_SHAKEN_PUB_KEY_RAW_BUF_LEN;
+
+	for (hf = msg->headers; hf; hf = hf->next) {
+		if (hf->name.len == STIRSHAKEN_HDR_IDENTITY_LEN
+				&& strncasecmp(hf->name.s, STIRSHAKEN_HDR_IDENTITY,
+					STIRSHAKEN_HDR_IDENTITY_LEN) == 0)
+			break;
+	}
+
+	if (hf == NULL) {
+		LM_DBG("no identity header\n");
+		goto fail;
+	}
+
+	ibody = hf->body;
+
+	if (keypath && keypath->s) {
+
+		if (STIR_SHAKEN_STATUS_OK != stir_shaken_load_key_raw(&ss, keypath->s, key, &key_len)) {
+			LM_ERR("Failed to load private key\n");
+			stirshaken_print_error_details(&ss);
+			goto fail;
+		}
+	}
+
+	if (STIR_SHAKEN_STATUS_OK != stir_shaken_sih_verify_with_key(&ss, ibody.s, key, key_len, &passport_out)) {
+		LM_ERR("SIP Identity Header did not pass verification against key\n");
+		stirshaken_print_error_details(&ss);
+		goto fail;
+	}
+
+	// We can do something with PASSporT here or just realease it
+	stir_shaken_passport_destroy(&passport_out);
+
+	LM_DBG("identity check: ok (%s)\n", ss.x509_cert_path_checked ? "with X509 cert path check" : "without X509 cert path check");
+	return 1;
+
+fail:
+	stir_shaken_passport_destroy(&passport_out);
+	LM_ERR("identity check: fail\n");
+	return -1;
+}
+
+/**
+ * Verify SIP Identity Header against specified public key (does not involve HTTP(s) GET request).
+ * WARNING:
+ * This method only checks if SIP Identity Header was signed by a key corresponding to specified public key.
+ * This method doesn't attempt to obtain certificate referenced in PASSporT (but PASSporT should be checked with key corresponding to that certificate).
+ * Therefore it is possible that this check will be successful, while PASSporT is not valid (could be signed with key that doesn't match certificate referenced in x5u header).
+ * If you want a complete Shaken check or if you are not sure what you're doing, then you should execute w_stirshaken_check_identity() instead
+ * (and configure Verification Service to perform X509 certificate path verification with stirshaken_vs_verify_x509_cert_path param set to 1).
+ *
+ * Kamailio config usage example:
+ *
+ *		stirshaken_check_identity_with_key("/path/to/key");
+ */
+static int w_stirshaken_check_identity_with_key(sip_msg_t *msg, char *pkey_path, char *str2)
+{
+	str keypath = STR_NULL;
+
+	LM_INFO("identity check using public key\n");
+
+	if(fixup_get_svalue(msg, (gparam_t*)pkey_path, &keypath)<0) {
+		LM_ERR("failed to get key path parameter\n");
+		return -1;
+	}
+
+	return ki_stirshaken_check_identity_with_key(msg, &keypath);
+}
+
+static int ki_stirshaken_add_identity_with_key(sip_msg_t *msg, str *x5u, str *attest,
+		str *origtn_val, str *desttn_val, str *origid, str *keypath)
+{
+	stir_shaken_context_t ss = { 0 };
+	char *sih = NULL;
+	stir_shaken_passport_t *passport = NULL;
+	str ibody = STR_NULL;
+	str hdr = STR_NULL;
+	sr_lump_t *anchor = NULL;
+	stir_shaken_passport_params_t params = {
+		.x5u = x5u ? x5u->s : NULL,
+		.attest = attest ? attest->s : NULL,
+		.desttn_key = "tn",
+		.desttn_val = desttn_val ? desttn_val->s : NULL,
+		.iat = time(NULL),
+		.origtn_key = "tn",
+		.origtn_val = origtn_val ? origtn_val->s : NULL,
+		.origid = origid ? origid->s : NULL
+	};
+	char uuid_str[37] = { 0 };
+
+	if (!params.origid || !strlen(params.origid)) {
+
+		uuid_t uuid;
+
+		uuid_generate(uuid);
+		uuid_unparse_lower(uuid, uuid_str);
+		params.origid = uuid_str;
+	}
+
+	if (keypath && keypath->s) {
+
+		unsigned char key[STIR_SHAKEN_PRIV_KEY_RAW_BUF_LEN];
+		uint32_t key_len = STIR_SHAKEN_PRIV_KEY_RAW_BUF_LEN;
+
+		if (STIR_SHAKEN_STATUS_OK != stir_shaken_load_key_raw(&ss, keypath->s, key, &key_len)) {
+			LM_ERR("Failed to load private key\n");
+			stirshaken_print_error_details(&ss);
+			goto error;
+		}
+
+		sih = stir_shaken_authenticate_to_sih_with_key(&ss, &params, &passport, key, key_len);
+		if (!sih) {
+			LM_ERR("Failed to create SIP Identity Header with key %s\n", keypath->s);
+			stirshaken_print_error_details(&ss);
+			goto error;
+		}
+
+	} else {
+
+		if (!as) {
+			LM_ERR("Authentication Service not started. Is default key set for Authentication Service? Please check @as_default_key param\n");
+			goto error;
+		}
+
+		sih = stir_shaken_as_authenticate_to_sih(&ss, as, &params, &passport);
+		if (!sih) {
+			LM_ERR("Failed to create SIP Identity Header with default key. Is a default key set for Authentication Service? Please check @as_default_key param\n");
+			stirshaken_print_error_details(&ss);
+			goto error;
+		}
+	}
+
+	// We can do something with PASSporT here or just realease it
+	stir_shaken_passport_destroy(&passport);
+
+	ibody.s = sih;
+	ibody.len = strlen(sih);
+
+	if (ibody.len <= 0) {
+		LM_DBG("Got SIP Identity Header with 0 length\n");
+		goto error;
+	}
+
+	LM_DBG("appending identity: %.*s\n", ibody.len, ibody.s);
+	if (parse_headers(msg, HDR_EOH_F, 0) == -1) {
+		LM_ERR("error while parsing message\n");
+		goto error;
+	}
+
+	hdr.len = STIRSHAKEN_HDR_IDENTITY_LEN + 1 + 1 + ibody.len + 2;
+	hdr.s = (char*)pkg_malloc(hdr.len + 1);
+	if (hdr.s == NULL) {
+		PKG_MEM_ERROR;
+		goto error;
+	}
+	memcpy(hdr.s, STIRSHAKEN_HDR_IDENTITY, STIRSHAKEN_HDR_IDENTITY_LEN);
+	*(hdr.s + STIRSHAKEN_HDR_IDENTITY_LEN) = ':';
+	*(hdr.s + STIRSHAKEN_HDR_IDENTITY_LEN + 1) = ' ';
+
+	memcpy(hdr.s + STIRSHAKEN_HDR_IDENTITY_LEN + 2, ibody.s, ibody.len);
+	*(hdr.s + STIRSHAKEN_HDR_IDENTITY_LEN + ibody.len + 2) = '\r';
+	*(hdr.s + STIRSHAKEN_HDR_IDENTITY_LEN + ibody.len + 3) = '\n';
+
+	/* anchor after last header */
+	anchor = anchor_lump(msg, msg->unparsed - msg->buf, 0, 0);
+	if((anchor == NULL)
+			|| (insert_new_lump_before(anchor, hdr.s, hdr.len, 0) == 0)) {
+		LM_ERR("cannot insert identity header\n");
+		pkg_free(hdr.s);
+		goto error;
+	}
+
+	if(sih) {
+		free(sih);
+	}
+	return 1;
+
+error:
+	stir_shaken_passport_destroy(&passport);
+	if(sih) {
+		free(sih);
+	}
+	return -1;
+}
+
+/**
+ * Add SIP Identity Header to the call using specific key. Authenticate call with STIR-Shaken.
+ *
+ * Kamailio config usage example:
+ *
+ * 		stirshaken_add_identity_with_key("https://sp.com/sp.pem", "B", "+44100", "+44200", "ref", "/path/to/key");
+ */
+static int w_stirshaken_add_identity_with_key(sip_msg_t *msg, str *px5u, str *pattest,
+		str *porigtn_val, str *pdesttn_val, str *porigid, str *pkeypath)
+{
+	str x5u = STR_NULL;
+	str attest = STR_NULL;
+	str origtn_val = STR_NULL;
+	str desttn_val = STR_NULL;
+	str origid = STR_NULL;
+	str keypath = STR_NULL;
+
+	if (fixup_get_svalue(msg, (gparam_t*)px5u, &x5u)<0) {
+		LM_ERR("failed to get x5u parameter\n");
+		return -1;
+	}
+
+	if (fixup_get_svalue(msg, (gparam_t*)pattest, &attest)<0) {
+		LM_ERR("failed to get attest parameter\n");
+		return -1;
+	}
+
+	if (fixup_get_svalue(msg, (gparam_t*)porigtn_val, &origtn_val)<0) {
+		LM_ERR("failed to get origtn_val parameter\n");
+		return -1;
+	}
+
+	if (fixup_get_svalue(msg, (gparam_t*)pdesttn_val, &desttn_val)<0) {
+		LM_ERR("failed to get desttn_val parameter\n");
+		return -1;
+	}
+
+	if (fixup_get_svalue(msg, (gparam_t*)porigid, &origid)<0) {
+		LM_ERR("failed to get origid parameter\n");
+		return -1;
+	}
+
+	if (fixup_get_svalue(msg, (gparam_t*)pkeypath, &keypath)<0) {
+		LM_ERR("failed to get keypath parameter\n");
+		return -1;
+	}
+
+	return ki_stirshaken_add_identity_with_key(msg, &x5u, &attest, &origtn_val, &desttn_val, &origid, &keypath);
+}
+
+static int ki_stirshaken_add_identity(sip_msg_t *msg, str *x5u, str *attest, str *origtn_val, str *desttn_val, str *origid)
+{
+	return ki_stirshaken_add_identity_with_key(msg, x5u, attest, origtn_val, desttn_val, origid, NULL);
+}
+
+/**
+ * Add SIP Identity Header to the call using default private key. Authenticate call with STIR-Shaken.
+ *
+ * Kamailio config usage example:
+ *
+ * 		stirshaken_add_identity("https://sp.com/sp.pem", "B", "+44100", "+44200", "ref");
+ */
+static int w_stirshaken_add_identity(sip_msg_t *msg, str *px5u, str *pattest, str *porigtn_val, str *pdesttn_val, str *porigid)
+{
+	str x5u = STR_NULL;
+	str attest = STR_NULL;
+	str origtn_val = STR_NULL;
+	str desttn_val = STR_NULL;
+	str origid = STR_NULL;
+
+	if (fixup_get_svalue(msg, (gparam_t*)px5u, &x5u)<0) {
+		LM_ERR("failed to get x5u parameter\n");
+		return -1;
+	}
+
+	if (fixup_get_svalue(msg, (gparam_t*)pattest, &attest)<0) {
+		LM_ERR("failed to get attest parameter\n");
+		return -1;
+	}
+
+	if (fixup_get_svalue(msg, (gparam_t*)porigtn_val, &origtn_val)<0) {
+		LM_ERR("failed to get origtn_val parameter\n");
+		return -1;
+	}
+
+	if (fixup_get_svalue(msg, (gparam_t*)pdesttn_val, &desttn_val)<0) {
+		LM_ERR("failed to get desttn_val parameter\n");
+		return -1;
+	}
+
+	if (fixup_get_svalue(msg, (gparam_t*)porigid, &origid)<0) {
+		LM_ERR("failed to get origid parameter\n");
+		return -1;
+	}
+
+	return ki_stirshaken_add_identity(msg, &x5u, &attest, &origtn_val, &desttn_val, &origid);
+}
+
+
+/**
+ *
+ */
+/* clang-format off */
+static sr_kemi_t sr_kemi_stirshaken_exports[] = {
+	{ str_init("stirshaken"), str_init("stirshaken_check_identity"),
+		SR_KEMIP_INT, ki_stirshaken_check_identity,
+		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
+			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+	},
+	{ str_init("stirshaken"), str_init("stirshaken_check_identity_with_cert"),
+		SR_KEMIP_INT, ki_stirshaken_check_identity_with_cert,
+		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
+			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+	},
+	{ str_init("stirshaken"), str_init("stirshaken_check_identity_with_key"),
+		SR_KEMIP_INT, ki_stirshaken_check_identity_with_key,
+		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
+			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+	},
+	{ str_init("stirshaken"), str_init("stirshaken_add_identity"),
+		SR_KEMIP_INT, ki_stirshaken_add_identity,
+		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
+			SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE }
+	},
+	{ str_init("stirshaken"), str_init("stirshaken_add_identity_with_key"),
+		SR_KEMIP_INT, ki_stirshaken_add_identity_with_key,
+		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
+			SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR }
+	},
+	{ {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
+};
+/* clang-format on */
+
+/**
+ *
+ */
+int mod_register(char *path, int *dlflags, void *p1, void *p2)
+{
+	sr_kemi_modules_add(sr_kemi_stirshaken_exports);
+	return 0;
+}

Plik diff jest za duży
+ 302 - 278
src/modules/textops/README


+ 40 - 0
src/modules/textops/doc/textops_admin.xml

@@ -90,6 +90,10 @@ From: medabeda
 			</para>
 		</listitem>
 		</itemizedlist>
+		<para>
+			Note: it performs Posix regex matching and the 're' parameter
+			is compiled with the flags REG_EXTENDED|REG_ICASE|REG_NEWLINE.
+		</para>
 		<para>
 		This function can be used from REQUEST_ROUTE, ONREPLY_ROUTE,
 		FAILURE_ROUTE, BRANCH_ROUTE.
@@ -128,6 +132,42 @@ if ( search("[Ss][Ii][Pp]") ) { /*....*/ };
 ...
 if ( search_body("[Ss][Ii][Pp]") ) { /*....*/ };
 ...
+</programlisting>
+		</example>
+	</section>
+
+	<section id="textops.f.search_str">
+		<title>
+		<function moreinfo="none">search_str(text. re)</function>
+		</title>
+		<para>
+		Searches for the re in the body of the message.
+		</para>
+		<para>Meaning of the parameters is as follows:</para>
+		<itemizedlist>
+		<listitem>
+			<para><emphasis>text</emphasis> - text to perform regex searching
+			over it.
+			</para>
+		</listitem>
+		<listitem>
+			<para><emphasis>re</emphasis> - regular expression to match over
+			the 'text' parameter.
+			</para>
+		</listitem>
+		</itemizedlist>
+		<para>
+			Both parameters can contain variables.
+		</para>
+		<para>
+		This function can be used from ANY_ROUTE.
+		</para>
+		<example>
+		<title><function>search_str</function> usage</title>
+		<programlisting format="linespecific">
+...
+if ( search_str("$ru", ";transport=tcp") ) { /*....*/ };
+...
 </programlisting>
 		</example>
 	</section>

+ 64 - 0
src/modules/textops/textops.c

@@ -148,6 +148,7 @@ static int is_present_hf_re_pv_f(sip_msg_t* msg, char* key, char* foo);
 static int is_audio_on_hold_f(struct sip_msg *msg, char *str1, char *str2 );
 static int regex_substring_f(struct sip_msg *msg,  char *input, char *regex,
 		char *matched_index, char *match_count, char *dst);
+static int w_search_str(sip_msg_t *msg, char *ptext, char *pre);
 static int fixup_substre(void**, int);
 static int hname_fixup(void** param, int param_no);
 static int free_hname_fixup(void** param, int param_no);
@@ -314,6 +315,9 @@ static cmd_export_t cmds[]={
 	{"cmp_istr",  (cmd_function)cmp_istr_f, 2,
 		fixup_spve_spve, 0,
 		ANY_ROUTE},
+	{"search_str",  (cmd_function)w_search_str, 2,
+		fixup_spve_spve, 0,
+		ANY_ROUTE},
 	{"starts_with",  (cmd_function)starts_with_f, 2,
 		fixup_spve_spve, 0,
 		ANY_ROUTE},
@@ -4593,6 +4597,61 @@ static int fixup_subst_hf(void** param, int param_no)
 	return 0;
 }
 
+/**
+ *
+ */
+static int ki_search_str(sip_msg_t *msg, str *stext, str *sre)
+{
+	int ret;
+	regex_t re;
+	regmatch_t pmatch;
+
+
+	if(sre==NULL || sre->len<=0) {
+		return 2;
+	}
+
+	if(stext==NULL || stext->len<=0) {
+		return -2;
+	}
+
+	memset(&re, 0, sizeof(regex_t));
+	if (regcomp(&re, sre->s, REG_EXTENDED|REG_ICASE|REG_NEWLINE)!=0) {
+		LM_ERR("failed to compile regex: %.*s\n", sre->len, sre->s);
+		return -2;
+	}
+
+	if (regexec(&re, stext->s, 1, &pmatch, 0)!=0) {
+		ret = -1;
+	} else {
+		ret = 1;
+	}
+
+	regfree(&re);
+
+	return ret;
+}
+
+/**
+ *
+ */
+static int w_search_str(sip_msg_t *msg, char *ptext, char *pre)
+{
+	str stext;
+	str sre;
+
+	if(fixup_get_svalue(msg, (gparam_t*)ptext, &stext)!=0) {
+		LM_ERR("cannot get first parameter\n");
+		return -2;
+	}
+	if(fixup_get_svalue(msg, (gparam_t*)pre, &sre)!=0) {
+		LM_ERR("cannot get second parameter\n");
+		return -2;
+	}
+
+	return ki_search_str(msg, &stext, &sre);
+}
+
 /**
  *
  */
@@ -4945,6 +5004,11 @@ static sr_kemi_t sr_kemi_textops_exports[] = {
 		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
 			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
 	},
+	{ str_init("textops"), str_init("search_str"),
+		SR_KEMIP_INT, ki_search_str,
+		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
+			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
+	},
 	{ str_init("textops"), str_init("starts_with"),
 		SR_KEMIP_INT, ki_starts_with,
 		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,

+ 3 - 1
src/modules/tm/t_cancel.c

@@ -527,7 +527,9 @@ unsigned int t_uac_cancel( str *headers, str *body,
 		LM_ERR("send failed\n");
 		goto error1;
 	}
-	start_retr(cancel);
+	if(start_retr(cancel)!=0) {
+		LM_CRIT("failed to start retransmission for cancel %p\n", cancel);
+	}
 	/* </start_sending> */
 
 	return ret;

+ 12 - 3
src/modules/topos/README

@@ -10,7 +10,13 @@ Daniel-Constantin Mierla
 
    <[email protected]>
 
+Frederic Gaisnon
+
+   <[email protected]>
+
    Copyright © 2016 FhG FOKUS
+
+   Copyright © 2021 MomentTech
      __________________________________________________________________
 
    Table of Contents
@@ -112,8 +118,8 @@ Chapter 1. Admin Guide
    It also works for SIP MESSAGE or other requests that do not create a
    dialog -- record_route() must be used for them as well, the headers are
    not going to be in the messages sent to the network, they are needed to
-   know local addresses used to communicate with each side. At this moment
-   it is not designed to work for presence (SUBSCRIBE-based) dialogs. The
+   know local addresses used to communicate with each side. This module is
+   designed to work for presence (SUBSCRIBE-based) dialogs too. The
    REGISTER and PUBLISH requests are skipped from processing by this
    module, expected to be terminated on a local SIP server.
 
@@ -225,7 +231,10 @@ modparam("topos", "branch_expire", 300)
    mind that the module does not update the dialog timestamp after the
    initial call setup on re-INVITEs or other in-dialog messages. So set a
    large enough value (according your longest call duration) to prevent
-   problems in re-writing messages.
+   problems in re-writing messages. This key is only relevant for INVITE
+   dialog. SUBSCRIBE dialog records lifetime are based on value set in
+   expires header. Moreover each re-SUBSCRIBEs update the dialog
+   timestamp.
 
    Default value is 10800 (3 hours).
 

+ 9 - 0
src/modules/topos/doc/topos.xml

@@ -23,11 +23,20 @@
 		<surname>Mierla</surname>
 		<email>[email protected]</email>
 	    </editor>
+            <editor>
+                <firstname>Frederic</firstname>
+                <surname>Gaisnon</surname>
+                <email>[email protected]</email>
+            </editor>
 	</authorgroup>
 	<copyright>
 	    <year>2016</year>
 	    <holder>&fhg;</holder>
 	</copyright>
+        <copyright>
+            <year>2021</year>
+            <holder>MomentTech</holder>
+        </copyright>
     </bookinfo>
     <toc></toc>
     

+ 5 - 2
src/modules/topos/doc/topos_admin.xml

@@ -30,8 +30,8 @@
 		a dialog -- record_route() must be used for them as well, the
 		headers are not going to be in the messages sent to the network, they
 		are needed to know local addresses used to communicate with each side.
-		At this moment it is not designed to work for presence (SUBSCRIBE-based)
-		dialogs. The REGISTER and PUBLISH requests are skipped from processing
+                This module is designed to work for presence (SUBSCRIBE-based) dialogs too.
+		The REGISTER and PUBLISH requests are skipped from processing
 		by this module, expected to be terminated on a local SIP server.
 	</para>
 	</section>
@@ -199,6 +199,9 @@ modparam("topos", "branch_expire", 300)
 			after the initial call setup on re-INVITEs or other in-dialog
 			messages. So set a large enough value (according your longest call
 			duration) to prevent problems in re-writing messages.
+			This key is only relevant for INVITE dialog. 
+                        SUBSCRIBE dialog records lifetime are based on value set in expires
+                        header. Moreover each re-SUBSCRIBEs update the dialog timestamp.
 		</para>
 		<para>
 		<emphasis>

+ 0 - 4
src/modules/topos/topos_mod.c

@@ -389,10 +389,6 @@ int tps_msg_received(sr_event_param_t *evp)
 		}
 	} else {
 		/* reply */
-		if(msg.first_line.u.reply.statuscode==100) {
-			/* nothing to do - it should be absorbed */
-			goto done;
-		}
 		tps_response_received(&msg);
 	}
 

+ 5 - 5
src/modules/topos/tps_msg.c

@@ -905,6 +905,11 @@ int tps_request_received(sip_msg_t *msg, int dialog)
 				goto error;
 			}
 		}
+		if((get_cseq(msg)->method_id)&(METHOD_SUBSCRIBE)) {
+			if(tps_storage_update_dialog(msg, &mtsd, &stsd, TPS_DBU_CONTACT|TPS_DBU_TIME)<0) {
+				goto error;
+			}
+		}
 	}
 	return 0;
 
@@ -926,11 +931,6 @@ int tps_response_received(sip_msg_t *msg)
 
 	LM_DBG("handling incoming response\n");
 
-	if(msg->first_line.u.reply.statuscode==100) {
-		/* nothing to do - it should be absorbed */
-		return 0;
-	}
-
 	memset(&mtsd, 0, sizeof(tps_data_t));
 	memset(&stsd, 0, sizeof(tps_data_t));
 	memset(&btsd, 0, sizeof(tps_data_t));

+ 33 - 8
src/modules/topos/tps_storage.c

@@ -41,6 +41,7 @@
 #include "../../core/parser/contact/parse_contact.h"
 #include "../../core/parser/parse_from.h"
 #include "../../core/parser/parse_to.h"
+#include "../../core/parser/parse_expires.h"
 
 #include "../../lib/srdb1/db.h"
 #include "../../core/utils/sruid.h"
@@ -336,8 +337,11 @@ int tps_storage_fill_contact(sip_msg_t *msg, tps_data_t *td, str *uuid, int dir,
 				td->cp += pv_val.rs.len;
 			}
 		}
-		*td->cp = '@';
-		td->cp++;
+
+		if (!((ctmode == 1) && (dir==TPS_DIR_DOWNSTREAM) && (curi.user.len <= 0))) {
+			*td->cp = '@';
+			td->cp++;
+		}
 
 		if (_tps_contact_host.len) { // using configured hostname in the contact header
 			memcpy(td->cp, _tps_contact_host.s, _tps_contact_host.len);
@@ -465,8 +469,8 @@ int tps_storage_link_msg(sip_msg_t *msg, tps_data_t *td, int dir)
 
 	/* extract the contact address */
 	if(parse_headers(msg, HDR_CONTACT_F, 0)<0 || msg->contact==NULL) {
-		if(td->s_method_id != METHOD_INVITE) {
-			/* no mandatory contact unless is INVITE - done */
+		if((td->s_method_id != METHOD_INVITE) && (td->s_method_id != METHOD_SUBSCRIBE)){
+			/* no mandatory contact unless is INVITE or SUBSCRIBE - done */
 			return 0;
 		}
 		if(msg->first_line.type==SIP_REPLY) {
@@ -504,6 +508,13 @@ int tps_storage_link_msg(sip_msg_t *msg, tps_data_t *td, int dir)
 		}
 	}
 
+	if  (td->s_method_id == METHOD_SUBSCRIBE) {
+		if(msg->expires && (msg->expires->body.len > 0) && (msg->expires->parsed || (parse_expires(msg->expires) >= 0))) {
+			td->expires = ((exp_body_t *)msg->expires->parsed)->val;
+		}
+	}
+
+
 	LM_DBG("downstream: %s - acontact: [%.*s] - bcontact: [%.*s]\n",
 			(dir==TPS_DIR_DOWNSTREAM)?"yes":"no",
 			td->a_contact.len, (td->a_contact.len>0)?td->a_contact.s:"",
@@ -1036,6 +1047,8 @@ int tps_db_load_branch(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd,
 	db_key_t db_cols[TPS_NR_KEYS];
 	db1_res_t* db_res = NULL;
 	str sinv = str_init("INVITE");
+	str ssub = str_init("SUBSCRIBE");
+	int bInviteDlg = 1;
 	int nr_keys;
 	int nr_cols;
 	int n;
@@ -1047,6 +1060,10 @@ int tps_db_load_branch(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd,
 	nr_keys = 0;
 	nr_cols = 0;
 
+	if((get_cseq(msg)->method_id == METHOD_SUBSCRIBE) || ((get_cseq(msg)->method_id == METHOD_NOTIFY) && (msg->event->len > 0))) {
+		bInviteDlg = 0;
+	}
+
 	if(mode==0) {
 		/* load same transaction using Via branch */
 		db_keys[nr_keys]=&tt_col_x_vbranch;
@@ -1075,7 +1092,7 @@ int tps_db_load_branch(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd,
 		db_ops[nr_keys]=OP_EQ;
 		db_vals[nr_keys].type = DB1_STR;
 		db_vals[nr_keys].nul = 0;
-		db_vals[nr_keys].val.str_val = sinv;
+		db_vals[nr_keys].val.str_val = bInviteDlg ? sinv : ssub;
 		nr_keys++;
 	}
 
@@ -1407,7 +1424,7 @@ int tps_storage_update_branch(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd,
 	if(msg==NULL || md==NULL || sd==NULL)
 		return -1;
 
-	if(md->s_method_id != METHOD_INVITE) {
+	if((md->s_method_id != METHOD_INVITE) && (md->s_method_id != METHOD_SUBSCRIBE)) {
 		return 0;
 	}
 
@@ -1514,6 +1531,14 @@ int tps_db_update_dialog(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd,
 			}
 		}
 	}
+	if ((mode & TPS_DBU_TIME) && ((sd->b_tag.len > 0)
+			&& ((md->direction == TPS_DIR_UPSTREAM) && (msg->first_line.type==SIP_REQUEST))
+			&& (msg->first_line.u.request.method_value == METHOD_SUBSCRIBE))) {
+		db_ucols[nr_ucols] = &td_col_rectime;
+		db_uvals[nr_ucols].type = DB1_DATETIME;
+		db_uvals[nr_ucols].val.time_val = time(NULL);
+		nr_ucols++;
+	}
 
 	if(nr_ucols==0) {
 		return 0;
@@ -1543,7 +1568,7 @@ int tps_storage_update_dialog(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd,
 	if(msg==NULL || md==NULL || sd==NULL)
 		return -1;
 
-	if(md->s_method_id != METHOD_INVITE) {
+	if((md->s_method_id != METHOD_INVITE) && (md->s_method_id != METHOD_SUBSCRIBE)) {
 		return 0;
 	}
 	if(msg->first_line.type==SIP_REPLY) {
@@ -1575,7 +1600,7 @@ int tps_db_end_dialog(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd)
 	if(msg==NULL || md==NULL || sd==NULL || _tps_db_handle==NULL)
 		return -1;
 
-	if(md->s_method_id != METHOD_BYE) {
+	if((md->s_method_id != METHOD_BYE) && !((md->s_method_id == METHOD_SUBSCRIBE) && (md->expires == 0))) {
 		return 0;
 	}
 

+ 2 - 0
src/modules/topos/tps_storage.h

@@ -41,6 +41,7 @@
 #define TPS_DBU_RPLATTRS	(1<<1)
 #define TPS_DBU_ARR		(1<<2)
 #define TPS_DBU_BRR		(1<<3)
+#define TPS_DBU_TIME		(1<<4)
 #define TPS_DBU_ALL		(0xffffffff)
 
 #define TPS_DATA_SIZE	8192
@@ -79,6 +80,7 @@ typedef struct tps_data {
 	int32_t iflags;
 	int32_t direction;
 	uint32_t s_method_id;
+	int32_t expires;
 } tps_data_t;
 
 int tps_storage_dialog_find(sip_msg_t *msg, tps_data_t *td);

+ 6 - 0
src/modules/topos_redis/README

@@ -10,9 +10,15 @@ Daniel-Constantin Mierla
 
    <[email protected]>
 
+Frederic Gaisnon
+
+   <[email protected]>
+
    Copyright © 2017 kamailio.org
 
    Copyright © 2017 flowroute.com
+
+   Copyright © 2021 MomentTech
      __________________________________________________________________
 
    Table of Contents

+ 9 - 0
src/modules/topos_redis/doc/topos_redis.xml

@@ -23,6 +23,11 @@
 		<surname>Mierla</surname>
 		<email>[email protected]</email>
 	    </editor>
+	    <editor>
+		<firstname>Frederic</firstname>
+		<surname>Gaisnon</surname>
+		<email>[email protected]</email>
+	    </editor>
 	</authorgroup>
 	<copyright>
 	    <year>2017</year>
@@ -32,6 +37,10 @@
 	    <year>2017</year>
 	    <holder>flowroute.com</holder>
 	</copyright>
+	<copyright>
+	    <year>2021</year>
+	    <holder>MomentTech</holder>
+	</copyright>
     </bookinfo>
     <toc></toc>
 

+ 63 - 12
src/modules/topos_redis/topos_redis_storage.c

@@ -265,7 +265,12 @@ int tps_redis_insert_dialog(tps_data_t *td)
 	argvlen[argc] = rkey.len;
 	argc++;
 
-	lval = (unsigned long)_tps_api.get_dialog_expire();
+	if(td->s_method.len==9 && strncmp(td->s_method.s, "SUBSCRIBE", 9)==0) {
+		lval = (unsigned long)td->expires;
+	} else {
+		lval = (unsigned long)_tps_api.get_dialog_expire();
+  }
+
 	if(lval==0) {
 		return 0;
 	}
@@ -297,7 +302,7 @@ int tps_redis_clean_dialogs(void)
 /**
  *
  */
-int tps_redis_insert_invite_branch(tps_data_t *td)
+int tps_redis_insert_initial_method_branch(tps_data_t *td)
 {
 	char* argv[TPS_REDIS_NR_KEYS];
 	size_t argvlen[TPS_REDIS_NR_KEYS];
@@ -328,8 +333,9 @@ int tps_redis_insert_invite_branch(tps_data_t *td)
 
 	rp = _tps_redis_cbuf;
 	rkey.len = snprintf(rp, TPS_REDIS_DATA_SIZE-128,
-					"%.*sINVITE:%.*s:%.*s",
+					"%.*s%.*s:%.*s:%.*s",
 					_tps_redis_bprefix.len, _tps_redis_bprefix.s,
+					td->s_method.len, td->s_method.s,
 					td->a_callid.len, td->a_callid.s,
 					td->b_tag.len, td->b_tag.s);
 	if(rkey.len<0 || rkey.len>=TPS_REDIS_DATA_SIZE-128) {
@@ -360,8 +366,8 @@ int tps_redis_insert_invite_branch(tps_data_t *td)
 		}
 		return -1;
 	}
-	LM_DBG("inserting invite branch record for [%.*s] with argc %d\n",
-			rkey.len, rkey.s, argc);
+	LM_DBG("inserting %.*s branch record for [%.*s] with argc %d\n",
+			td->s_method.len, td->s_method.s,rkey.len, rkey.s, argc);
 
 	freeReplyObject(rrpl);
 
@@ -552,7 +558,7 @@ int tps_redis_clean_branches(void)
 /**
  *
  */
-int tps_redis_load_invite_branch(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd)
+int tps_redis_load_initial_method_branch(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd)
 {
 	char* argv[TPS_REDIS_NR_KEYS];
 	size_t argvlen[TPS_REDIS_NR_KEYS];
@@ -588,8 +594,9 @@ int tps_redis_load_invite_branch(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd)
 	rp = _tps_redis_cbuf;
 
 	rkey.len = snprintf(rp, TPS_REDIS_DATA_SIZE,
-					"%.*sINVITE:%.*s:%.*s",
+					"%.*s%.*s:%.*s:%.*s",
 					_tps_redis_bprefix.len, _tps_redis_bprefix.s,
+					md->s_method.len, md->s_method.s,
 					md->a_callid.len, md->a_callid.s,
 					md->b_tag.len, md->b_tag.s);
 	if(rkey.len<0 || rkey.len>=TPS_REDIS_DATA_SIZE) {
@@ -733,9 +740,9 @@ int tps_redis_load_branch(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd,
 		/* load same transaction using Via branch */
 		xvbranch1 = &md->x_vbranch1;
 	} else {
-		/* load corresponding INVITE transaction using call-id + to-tag */
-		if(tps_redis_load_invite_branch(msg, md, &id)<0) {
-			LM_ERR("failed to load the INVITE branch value\n");
+		/* load corresponding INVITE or SUBSCRIBE transaction using call-id + to-tag */
+		if(tps_redis_load_initial_method_branch(msg, md, &id)<0) {
+			LM_ERR("failed to load the %.*s branch value\n", md->s_method.len, md->s_method.s);
 			return -1;
 		}
 		xvbranch1 = &id.x_vbranch1;
@@ -1122,11 +1129,18 @@ int tps_redis_update_branch(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd,
 	}
 
 	if(md->s_method.len==6 && strncmp(md->s_method.s, "INVITE", 6)==0) {
-		if(tps_redis_insert_invite_branch(md)<0) {
+		if(tps_redis_insert_initial_method_branch(md)<0) {
 			LM_ERR("failed to insert INVITE extra branch data\n");
 			return -1;
 		}
 	}
+	if(md->s_method.len==9 && strncmp(md->s_method.s, "SUBSCRIBE", 9)==0) {
+		if(tps_redis_insert_initial_method_branch(md)<0) {
+			LM_ERR("failed to insert SUBSCRIBE extra branch data\n");
+			return -1;
+		}
+	}
+
 	rsrv = _tps_redis_api.get_server(&_topos_redis_serverid);
 	if(rsrv==NULL) {
 		LM_ERR("cannot find redis server [%.*s]\n",
@@ -1209,6 +1223,7 @@ int tps_redis_update_dialog(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd,
 	redisc_server_t *rsrv = NULL;
 	redisReply *rrpl = NULL;
 	int32_t liflags;
+	unsigned long lval = 0;
 
 	if(sd->a_uuid.len<=0 && sd->b_uuid.len<=0) {
 		LM_INFO("no uuid for this message\n");
@@ -1297,6 +1312,11 @@ int tps_redis_update_dialog(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd,
 		}
 	}
 
+	if (mode & TPS_DBU_TIME) {
+		lval = (unsigned long)time(NULL);
+		TPS_REDIS_SET_ARGN(lval, rp, &rval, argc, &td_key_rectime, argv, argvlen);
+	}
+
 	if(argc<=2) {
 		return 0;
 	}
@@ -1313,6 +1333,37 @@ int tps_redis_update_dialog(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd,
 			rkey.len, rkey.s, argc);
 	freeReplyObject(rrpl);
 
+	if (mode & TPS_DBU_TIME) {
+		/* reset expire for the key */
+		argc = 0;
+
+		argv[argc]    = "EXPIRE";
+		argvlen[argc] = 6;
+		argc++;
+
+		argv[argc]    = rkey.s;
+		argvlen[argc] = rkey.len;
+		argc++;
+
+		lval = (unsigned long)md->expires;
+		if(lval==0) {
+			return 0;
+		}
+		TPS_REDIS_SET_ARGNV(lval, rp, &rval, argc, argv, argvlen);
+
+		rrpl = _tps_redis_api.exec_argv(rsrv, argc, (const char **)argv, argvlen);
+		if(rrpl==NULL) {
+			LM_ERR("failed to execute expire redis command\n");
+			if(rsrv->ctxRedis->err) {
+				LM_ERR("redis error: %s\n", rsrv->ctxRedis->errstr);
+			}
+			return -1;
+		}
+		LM_DBG("expire %lu set on dialog record for [%.*s] with argc %d\n", lval,
+			rkey.len, rkey.s, argc);
+		freeReplyObject(rrpl);
+	}
+
 	return 0;
 }
 
@@ -1333,7 +1384,7 @@ int tps_redis_end_dialog(sip_msg_t *msg, tps_data_t *md, tps_data_t *sd)
 	int32_t liflags;
 	unsigned long lval = 0;
 
-	if(md->s_method_id != METHOD_BYE) {
+	if((md->s_method_id != METHOD_BYE) && !((md->s_method_id == METHOD_SUBSCRIBE) && (md->expires == 0))) {
 		return 0;
 	}
 

+ 113 - 66
src/modules/uac/README

@@ -63,13 +63,14 @@ Ramona-Elena Modroiu
               4.5. uac_replace_to(uri)
               4.6. uac_restore_to()
               4.7. uac_auth([mode])
-              4.8. uac_req_send()
-              4.9. uac_reg_lookup(uuid, dst)
-              4.10. uac_reg_status(uuid)
-              4.11. uac_reg_request_to(user, mode)
-              4.12. uac_reg_enable(attr, val)
-              4.13. uac_reg_disable(attr, val)
-              4.14. uac_reg_refresh(luuid)
+              4.8. uac_auth_mode(vmode)
+              4.9. uac_req_send()
+              4.10. uac_reg_lookup(uuid, dst)
+              4.11. uac_reg_status(uuid)
+              4.12. uac_reg_request_to(user, mode)
+              4.13. uac_reg_enable(attr, val)
+              4.14. uac_reg_disable(attr, val)
+              4.15. uac_reg_refresh(luuid)
 
         5. Pseudo Variables
         6. Event Routes
@@ -123,25 +124,26 @@ Ramona-Elena Modroiu
    1.27. uac_replace_to usage
    1.28. uac_restore_to usage
    1.29. uac_auth usage
-   1.30. uac_req_send usage
-   1.31. uac_reg_lookup usage
-   1.32. uac_reg_status usage
-   1.33. uac_reg_request_to usage
-   1.34. uac_reg_enable usage
-   1.35. uac_reg_disable usage
-   1.36. uac_reg_refresh usage
-   1.37. event_route[uac:reply] usage
-   1.38. uac.reg_dump usage
-   1.39. uac.reg_info usage
-   1.40. uac.reg_enable usage
-   1.41. uac.reg_disable usage
-   1.42. uac.reg_unregister usage
-   1.43. uac.reg_reload usage
-   1.44. uac.reg_refresh usage
-   1.45. uac.reg_active usage
-   1.46. uac.reg_add usage
-   1.47. uac.reg_remove usage
-   1.48. lookup remote registrations usage
+   1.30. uac_auth_mode usage
+   1.31. uac_req_send usage
+   1.32. uac_reg_lookup usage
+   1.33. uac_reg_status usage
+   1.34. uac_reg_request_to usage
+   1.35. uac_reg_enable usage
+   1.36. uac_reg_disable usage
+   1.37. uac_reg_refresh usage
+   1.38. event_route[uac:reply] usage
+   1.39. uac.reg_dump usage
+   1.40. uac.reg_info usage
+   1.41. uac.reg_enable usage
+   1.42. uac.reg_disable usage
+   1.43. uac.reg_unregister usage
+   1.44. uac.reg_reload usage
+   1.45. uac.reg_refresh usage
+   1.46. uac.reg_active usage
+   1.47. uac.reg_add usage
+   1.48. uac.reg_remove usage
+   1.49. lookup remote registrations usage
 
 Chapter 1. Admin Guide
 
@@ -187,13 +189,14 @@ Chapter 1. Admin Guide
         4.5. uac_replace_to(uri)
         4.6. uac_restore_to()
         4.7. uac_auth([mode])
-        4.8. uac_req_send()
-        4.9. uac_reg_lookup(uuid, dst)
-        4.10. uac_reg_status(uuid)
-        4.11. uac_reg_request_to(user, mode)
-        4.12. uac_reg_enable(attr, val)
-        4.13. uac_reg_disable(attr, val)
-        4.14. uac_reg_refresh(luuid)
+        4.8. uac_auth_mode(vmode)
+        4.9. uac_req_send()
+        4.10. uac_reg_lookup(uuid, dst)
+        4.11. uac_reg_status(uuid)
+        4.12. uac_reg_request_to(user, mode)
+        4.13. uac_reg_enable(attr, val)
+        4.14. uac_reg_disable(attr, val)
+        4.15. uac_reg_refresh(luuid)
 
    5. Pseudo Variables
    6. Event Routes
@@ -631,13 +634,14 @@ end
    4.5. uac_replace_to(uri)
    4.6. uac_restore_to()
    4.7. uac_auth([mode])
-   4.8. uac_req_send()
-   4.9. uac_reg_lookup(uuid, dst)
-   4.10. uac_reg_status(uuid)
-   4.11. uac_reg_request_to(user, mode)
-   4.12. uac_reg_enable(attr, val)
-   4.13. uac_reg_disable(attr, val)
-   4.14. uac_reg_refresh(luuid)
+   4.8. uac_auth_mode(vmode)
+   4.9. uac_req_send()
+   4.10. uac_reg_lookup(uuid, dst)
+   4.11. uac_reg_status(uuid)
+   4.12. uac_reg_request_to(user, mode)
+   4.13. uac_reg_enable(attr, val)
+   4.14. uac_reg_disable(attr, val)
+   4.15. uac_reg_refresh(luuid)
 
 4.1.  uac_replace_from(display,uri)
 
@@ -821,7 +825,50 @@ failure_route[TRUNKAUTH] {
 }
 ...
 
-4.8.  uac_req_send()
+4.8.  uac_auth_mode(vmode)
+
+   This function can be called only from failure route and will build the
+   authentication response header and insert it into the request without
+   sending anything.
+
+   If mode is set to 1, then the password has to be provided in HA1
+   format. The parameter can be a static integer or a variable holding an
+   integer value.
+
+   This function can be used from FAILURE_ROUTE.
+
+   Example 1.30. uac_auth_mode usage
+...
+modparam("uac","auth_username_avp","$avp(auser)")
+modparam("uac","auth_password_avp","$avp(apass)")
+modparam("uac","auth_realm_avp","$avp(arealm)")
+
+request_route {
+   ...
+   if(is_method("INVITE")) {
+      t_on_failure("TRUNKAUTH");
+   }
+   ...
+}
+
+failure_route[TRUNKAUTH] {
+
+    if (t_is_canceled()) {
+        exit;
+    }
+    if(t_check_status("401|407")) {
+        $avp(auser) = "test";
+        $avp(apass) = "test";
+        # $avp(apass) = "36d0a02793542b4961e8348347236dbf";
+        if (uac_auth_mode("1")) {
+            t_relay();
+        }
+        exit;
+    }
+}
+...
+
+4.9.  uac_req_send()
 
    This function sends a SIP message from the configuration file. The
    message is built out of $uac_req(...) pseudo-variable.
@@ -829,7 +876,7 @@ failure_route[TRUNKAUTH] {
    This function can be used from REQUEST_ROUTE, FAILURE_ROUTE,
    BRANCH_ROUTE, ONREPLY_ROUTE, LOCAL_ROUTE.
 
-   Example 1.30. uac_req_send usage
+   Example 1.31. uac_req_send usage
 ...
 $uac_req(method)="OPTIONS";
 $uac_req(ruri)="sip:kamailio.org";
@@ -839,14 +886,14 @@ $uac_req(callid)=$(mb{s.md5});
 uac_req_send();
 ...
 
-4.9.  uac_reg_lookup(uuid, dst)
+4.10.  uac_reg_lookup(uuid, dst)
 
    This function sets the PV dst to SIP URI that correspond to uuid in uac
    registrations table. uuid and dst must be pseudo-variables.
 
    This function can be used from ANY_ROUTE.
 
-   Example 1.31. uac_reg_lookup usage
+   Example 1.32. uac_reg_lookup usage
 ...
 
 if(uac_reg_lookup("$rU", "$ru"))
@@ -855,7 +902,7 @@ if(uac_reg_lookup("$rU", "$ru"))
 }
 ...
 
-4.10.  uac_reg_status(uuid)
+4.11.  uac_reg_status(uuid)
 
    This function returns the current registration status for the uuid.
 
@@ -870,12 +917,12 @@ if(uac_reg_lookup("$rU", "$ru"))
 
    This function can be used from ANY_ROUTE.
 
-   Example 1.32. uac_reg_status usage
+   Example 1.33. uac_reg_status usage
 ...
 $var(status) = uac_reg_status("$rU");
 ...
 
-4.11.  uac_reg_request_to(user, mode)
+4.12.  uac_reg_request_to(user, mode)
 
    This function can be used to send an authenticated request to a remote
    user in the uac registrations table. It sets the request-uri, dst-uri
@@ -895,7 +942,7 @@ $var(status) = uac_reg_status("$rU");
    This function can be used from REQUEST_ROUTE, FAILURE_ROUTE,
    BRANCH_ROUTE.
 
-   Example 1.33. uac_reg_request_to usage
+   Example 1.34. uac_reg_request_to usage
 ...
 
 if(uac_reg_request_to("$fU", 0))
@@ -914,7 +961,7 @@ failure_route[REMOTE_AUTH] {
 }
 ...
 
-4.12.  uac_reg_enable(attr, val)
+4.13.  uac_reg_enable(attr, val)
 
    Enable a remote registration record based on a filter specified by
    attribute and value. The attribute can be: l_uuid, l_username,
@@ -923,12 +970,12 @@ failure_route[REMOTE_AUTH] {
 
    The SIP processing is done on the next timer routine.
 
-   Example 1.34. uac_reg_enable usage
+   Example 1.35. uac_reg_enable usage
 ...
    uac_reg_enable("l_uuid", "account123");
 ...
 
-4.13.  uac_reg_disable(attr, val)
+4.14.  uac_reg_disable(attr, val)
 
    Disable a remote registration record based on a filter specified by
    attribute and value. The attribute can be: l_uuid, l_username,
@@ -937,18 +984,18 @@ failure_route[REMOTE_AUTH] {
 
    The SIP processing is done on the next timer routine.
 
-   Example 1.35. uac_reg_disable usage
+   Example 1.36. uac_reg_disable usage
 ...
    uac_reg_disable("l_uuid", "account123");
 ...
 
-4.14.  uac_reg_refresh(luuid)
+4.15.  uac_reg_refresh(luuid)
 
    Refresh the uac remote registration record based on local uuid. If the
    record was already loaded, new values are taken from database,
    otherwise a new record is created.
 
-   Example 1.36. uac_reg_refresh usage
+   Example 1.37. uac_reg_refresh usage
 ...
    uac_reg_refresh("account123");
 ...
@@ -972,7 +1019,7 @@ failure_route[REMOTE_AUTH] {
    then the event_route is executed twice, first for 401/407 and second
    for final reply of the transaction.
 
-   Example 1.37. event_route[uac:reply] usage
+   Example 1.38. event_route[uac:reply] usage
 ...
 $uac_req(method)="OPTIONS";
 $uac_req(ruri)="sip:kamailio.org";
@@ -1012,7 +1059,7 @@ event_route[uac:reply] {
 
    Dump the content of remote registration table from memory.
 
-   Example 1.38. uac.reg_dump usage
+   Example 1.39. uac.reg_dump usage
 ...
    kamcmd uac.reg_dump
 ...
@@ -1033,7 +1080,7 @@ event_route[uac:reply] {
      * 16 (2^4) - registration initialized (after loading from database,
        the registration process was initialized)
 
-   Example 1.39. uac.reg_info usage
+   Example 1.40. uac.reg_info usage
 ...
    kamcmd uac.reg_info l_uuid account123
    kamcmd uac.reg_info l_uuid s:12345678
@@ -1047,7 +1094,7 @@ event_route[uac:reply] {
    matched against the value of the attribute in the remote registration
    record.
 
-   Example 1.40. uac.reg_enable usage
+   Example 1.41. uac.reg_enable usage
 ...
    kamcmd uac.reg_enable l_uuid account123
    kamcmd uac.reg_enable l_uuid s:12345678
@@ -1061,7 +1108,7 @@ event_route[uac:reply] {
    matched against the value of the attribute in the remote registration
    record.
 
-   Example 1.41. uac.reg_disable usage
+   Example 1.42. uac.reg_disable usage
 ...
    kamcmd uac.reg_disable l_uuid account123
    kamcmd uac.reg_disable l_uuid s:12345678
@@ -1075,7 +1122,7 @@ event_route[uac:reply] {
    should be matched against the value of the attribute in the remote
    registration record.
 
-   Example 1.42. uac.reg_unregister usage
+   Example 1.43. uac.reg_unregister usage
 ...
    kamcmd uac.reg_unregister l_uuid account123
    kamcmd uac.reg_unregister l_uuid s:12345678
@@ -1088,7 +1135,7 @@ event_route[uac:reply] {
    150 seconds between reloads -- see the reg_gc_interval parameter for
    more details.
 
-   Example 1.43. uac.reg_reload usage
+   Example 1.44. uac.reg_reload usage
 ...
    kamcmd uac.reg_reload
 ...
@@ -1099,7 +1146,7 @@ event_route[uac:reply] {
    the record exists in memory, it will be replaced with the new values
    loaded from database.
 
-   Example 1.44. uac.reg_refresh usage
+   Example 1.45. uac.reg_refresh usage
 ...
    kamcmd uac.reg_refresh account123
    kamcmd uac.reg_refresh s:12345678
@@ -1111,7 +1158,7 @@ event_route[uac:reply] {
    1 enables remote registrations for all records and 0 disables doing
    them.
 
-   Example 1.45. uac.reg_active usage
+   Example 1.46. uac.reg_active usage
 ...
    kamctl rpc uac.reg_active 0
    kamctl rpc uac.reg_active 1
@@ -1142,7 +1189,7 @@ event_route[uac:reply] {
    Use a dot (.) if no value should be set for auth_password, auth_ha1, or
    contact_addr.
 
-   Example 1.46. uac.reg_add usage
+   Example 1.47. uac.reg_add usage
 ...
    kamcmd uac.reg_add ...
 ...
@@ -1151,7 +1198,7 @@ event_route[uac:reply] {
 
    Remove a UAC remote registration record by l_uuid.
 
-   Example 1.47. uac.reg_remove usage
+   Example 1.48. uac.reg_remove usage
 ...
    kamcmd uac.reg_remove my_l_uuid
 ...
@@ -1207,7 +1254,7 @@ event_route[uac:reply] {
    if the call is coming from a remote SIP provider and can change the
    R-URI to local username@domain. Afterwards you can run location lookup.
 
-   Example 1.48. lookup remote registrations usage
+   Example 1.49. lookup remote registrations usage
 ...
     if(uac_reg_lookup("$rU", "$ru")) {
         xlog("request from a remote SIP provider [$ou => $ru]\n");

+ 51 - 0
src/modules/uac/doc/uac_admin.xml

@@ -884,6 +884,57 @@ failure_route[TRUNKAUTH] {
         exit;
     }
 }
+...
+				</programlisting>
+			</example>
+		</section>
+		<section id="uac.f.uac_auth_mode">
+			<title>
+				<function moreinfo="none">uac_auth_mode(vmode)</function>
+			</title>
+			<para>
+			This function can be called only from failure route and will
+			build the authentication response header and insert it into the
+			request without sending anything.
+			</para>
+			<para>
+			If mode is set to 1, then the password has to be provided in HA1 format.
+			The parameter can be a static integer or a variable holding an integer value.
+			</para>
+			<para>
+			This function can be used from FAILURE_ROUTE.
+			</para>
+			<example>
+				<title><function>uac_auth_mode</function> usage</title>
+				<programlisting format="linespecific">
+...
+modparam("uac","auth_username_avp","$avp(auser)")
+modparam("uac","auth_password_avp","$avp(apass)")
+modparam("uac","auth_realm_avp","$avp(arealm)")
+
+request_route {
+   ...
+   if(is_method("INVITE")) {
+      t_on_failure("TRUNKAUTH");
+   }
+   ...
+}
+
+failure_route[TRUNKAUTH] {
+
+    if (t_is_canceled()) {
+        exit;
+    }
+    if(t_check_status("401|407")) {
+        $avp(auser) = "test";
+        $avp(apass) = "test";
+        # $avp(apass) = "36d0a02793542b4961e8348347236dbf";
+        if (uac_auth_mode("1")) {
+            t_relay();
+        }
+        exit;
+    }
+}
 ...
 				</programlisting>
 			</example>

+ 2 - 0
src/modules/uac/uac.c

@@ -135,6 +135,8 @@ static cmd_export_t cmds[]={
 		REQUEST_ROUTE | BRANCH_ROUTE },
 	{"uac_restore_to",  (cmd_function)w_restore_to,  0, 0, 0, REQUEST_ROUTE },
 	{"uac_auth",	  (cmd_function)w_uac_auth,       0, 0, 0, FAILURE_ROUTE },
+	{"uac_auth",      (cmd_function)w_uac_auth_mode,  1,
+			fixup_igp_null, fixup_free_igp_null, FAILURE_ROUTE },
 	{"uac_auth_mode", (cmd_function)w_uac_auth_mode,  1,
 			fixup_igp_null, fixup_free_igp_null, FAILURE_ROUTE },
 	{"uac_req_send",  (cmd_function)w_uac_req_send,   0, 0, 0, ANY_ROUTE},

+ 27 - 9
src/modules/uac_redirect/README

@@ -31,6 +31,7 @@ Bogdan-Andrei Iancu
               4.5. acc_db_table (string)
               4.6. bflags (int)
               4.7. flags_hdr_mode (int)
+              4.8. q_value (int)
 
         5. Functions
 
@@ -50,11 +51,12 @@ Bogdan-Andrei Iancu
    1.5. Set acc_db_table parameter
    1.6. Set bflags module parameter
    1.7. Set flags_hdr_mode parameter
-   1.8. set_deny_filter usage
-   1.9. set_accept_filter usage
-   1.10. get_redirects usage
+   1.8. Set q_value parameter
+   1.9. set_deny_filter usage
+   1.10. set_accept_filter usage
    1.11. get_redirects usage
-   1.12. Redirection script example
+   1.12. get_redirects usage
+   1.13. Redirection script example
 
 Chapter 1. Admin Guide
 
@@ -76,6 +78,7 @@ Chapter 1. Admin Guide
         4.5. acc_db_table (string)
         4.6. bflags (int)
         4.7. flags_hdr_mode (int)
+        4.8. q_value (int)
 
    5. Functions
 
@@ -149,6 +152,7 @@ Chapter 1. Admin Guide
    4.5. acc_db_table (string)
    4.6. bflags (int)
    4.7. flags_hdr_mode (int)
+   4.8. q_value (int)
 
 4.1. default_filter (string)
 
@@ -273,6 +277,20 @@ branch_route[1] {
 modparam("uac_redirect","flags_hdr_mode",2)
 ...
 
+4.8. q_value (int)
+
+   Specifies the q-value to asign to contacts without one. Because
+   Kamailio doesn't support float parameter types, the value in the
+   parameter is divided by 1000 and stored as float. For example, if you
+   want q value to be 0.38, use value 380 here.
+
+   The default value is 10 (0.01).
+
+   Example 1.8. Set q_value parameter
+...
+modparam("uac_redirect","q_value",0)
+...
+
 5. Functions
 
    5.1. set_deny_filter(filter,flags)
@@ -295,7 +313,7 @@ modparam("uac_redirect","flags_hdr_mode",2)
 
    This function can be used from FAILURE_ROUTE.
 
-   Example 1.8. set_deny_filter usage
+   Example 1.9. set_deny_filter usage
 ...
 set_deny_filter(".*@domain2.net","reset_all");
 set_deny_filter(".*@domain1.net","");
@@ -316,7 +334,7 @@ set_deny_filter(".*@domain1.net","");
 
    This function can be used from FAILURE_ROUTE.
 
-   Example 1.9. set_accept_filter usage
+   Example 1.10. set_accept_filter usage
 ...
 set_accept_filter(".*@domain2.net","reset_added");
 set_accept_filter(".*@domain1.net","");
@@ -345,7 +363,7 @@ set_accept_filter(".*@domain1.net","");
 
    This function can be used from FAILURE_ROUTE.
 
-   Example 1.10. get_redirects usage
+   Example 1.11. get_redirects usage
 ...
 # max 2 contacts per branch, but no overall limit
 get_redirects("*:2");
@@ -369,14 +387,14 @@ get_redirects("*");
 
    This function can be used from FAILURE_ROUTE.
 
-   Example 1.11. get_redirects usage
+   Example 1.12. get_redirects usage
 ...
 get_redirects("4:1","Redirected");
 ...
 
 6. Script Example
 
-   Example 1.12. Redirection script example
+   Example 1.13. Redirection script example
 loadmodule "modules/sl/sl.so"
 loadmodule "modules/usrloc/usrloc.so"
 loadmodule "modules/registrar/registrar.so"

+ 22 - 0
src/modules/uac_redirect/doc/uac_redirect_admin.xml

@@ -337,6 +337,28 @@ branch_route[1] {
 				<programlisting format="linespecific">
 ...
 modparam("uac_redirect","flags_hdr_mode",2)
+...
+				</programlisting>
+			</example>
+		</section>
+		<section>
+			<title><varname>q_value</varname> (int)</title>
+			<para>
+			Specifies the q-value to asign to contacts without one. Because
+			Kamailio doesn't support float parameter types, the value in the
+			parameter is divided by 1000 and stored as float. For example, if
+			you want q value to be 0.38, use value 380 here.
+			</para>
+			<para>
+				<emphasis>
+					The default value is 10 (0.01).
+				</emphasis>
+			</para>
+			<example>
+				<title>Set <varname>q_value</varname> parameter</title>
+				<programlisting format="linespecific">
+...
+modparam("uac_redirect","q_value",0)
 ...
 				</programlisting>
 			</example>

+ 2 - 2
src/modules/uac_redirect/rd_funcs.c

@@ -33,9 +33,9 @@
 
 
 extern sruid_t _redirect_sruid;
+extern int _redirect_q_value;
 
 #define MAX_CONTACTS_PER_REPLY   16
-#define DEFAULT_Q_VALUE          10
 
 static int shmcontact2dset(struct sip_msg *req, struct sip_msg *shrpl,
 		long max, struct acc_param *reason, unsigned int bflags);
@@ -147,7 +147,7 @@ static int sort_contacts(hdr_field_t *chdr, contact_t **ct_array,
 			/* does the contact has a q val? */
 			q_para = ct_list->q;
 			if (q_para==0 || q_para->body.len==0) {
-				q = DEFAULT_Q_VALUE;
+				q = _redirect_q_value;
 			} else {
 				if (str2q( &q, q_para->body.s, q_para->body.len)!=0) {
 					LM_ERR("invalid q param\n");

+ 4 - 0
src/modules/uac_redirect/uac_redirect.c

@@ -52,6 +52,9 @@ int flags_hdr_mode = 0;
 
 #define ACCEPT_RULE_STR "accept"
 #define DENY_RULE_STR   "deny"
+#define DEFAULT_Q_VALUE 10
+
+int _redirect_q_value = DEFAULT_Q_VALUE;
 
 /* sruid to get internal uid */
 sruid_t _redirect_sruid;
@@ -88,6 +91,7 @@ static param_export_t params[] = {
 	{"acc_db_table",    PARAM_STRING,  &acc_db_table     },
 	{"bflags",    		INT_PARAM,  &bflags			  },
 	{"flags_hdr_mode",	INT_PARAM,  &flags_hdr_mode	  },
+	{"q_value",         INT_PARAM,  &_redirect_q_value   },
 	{0, 0, 0}
 };
 

+ 16 - 0
src/modules/usrloc/README

@@ -97,6 +97,7 @@ Carsten Bock
               3.55. ka_timeout (int)
               3.56. ka_loglevel (int)
               3.57. ka_logmsg (str)
+              3.58. load_rank (int)
 
         4. RPC Commands
 
@@ -201,6 +202,7 @@ Carsten Bock
    1.55. Set ka_timeout parameter
    1.56. ka_loglevel parameter usage
    1.57. ka_logmsg parameter usage
+   1.58. load_rank parameter usage
 
 Chapter 1. Admin Guide
 
@@ -274,6 +276,7 @@ Chapter 1. Admin Guide
         3.55. ka_timeout (int)
         3.56. ka_loglevel (int)
         3.57. ka_logmsg (str)
+        3.58. load_rank (int)
 
    4. RPC Commands
 
@@ -410,6 +413,7 @@ Chapter 1. Admin Guide
    3.55. ka_timeout (int)
    3.56. ka_loglevel (int)
    3.57. ka_logmsg (str)
+   3.58. load_rank (int)
 
 3.1. nat_bflag (int)
 
@@ -1215,6 +1219,18 @@ modparam("usrloc", "ka_loglevel", 1)
 modparam("usrloc", "ka_logmsg", " to-uri: [$tu] remote-addr: [$sas]")
 ...
 
+3.58. load_rank (int)
+
+   Allows to set the rank of the child SIP worker to load the location
+   records.
+
+   Default value is “1” (PROC_SIPINIT).
+
+   Example 1.58. load_rank parameter usage
+...
+modparam("usrloc", "load_rank", 1)
+...
+
 4. RPC Commands
 
    4.1. ul.dump

+ 19 - 0
src/modules/usrloc/doc/usrloc_admin.xml

@@ -1498,6 +1498,25 @@ modparam("usrloc", "ka_logmsg", " to-uri: [$tu] remote-addr: [$sas]")
 		</example>
 	</section>
 
+	<section id="usrloc.p.load_rank">
+		<title><varname>load_rank</varname> (int)</title>
+		<para>
+		Allows to set the rank of the child SIP worker to load the location
+		records.
+		</para>
+		<para>
+		Default value is <quote>1</quote> (PROC_SIPINIT).
+		</para>
+		<example>
+		<title><varname>load_rank</varname> parameter usage</title>
+		<programlisting format="linespecific">
+...
+modparam("usrloc", "load_rank", 1)
+...
+		</programlisting>
+		</example>
+	</section>
+
 	</section>
 
 	<section>

+ 4 - 4
src/modules/usrloc/ul_rpc.c

@@ -402,7 +402,7 @@ static void ul_rpc_lookup(rpc_t* rpc, void* ctx)
 	/* look for table */
 	dom = rpc_find_domain( &table );
 	if (dom == NULL) {
-		rpc->fault(ctx, 500, "Domain not found");
+		rpc->fault(ctx, 500, "Domain table not found");
 		return;
 	}
 
@@ -476,7 +476,7 @@ static void ul_rpc_rm_aor(rpc_t* rpc, void* ctx)
 	/* look for table */
 	dom = rpc_find_domain( &table );
 	if (dom == NULL) {
-		rpc->fault(ctx, 500, "Domain not found");
+		rpc->fault(ctx, 500, "Domain table not found");
 		return;
 	}
 
@@ -520,7 +520,7 @@ static void ul_rpc_rm_contact(rpc_t* rpc, void* ctx)
 	/* look for table */
 	dom = rpc_find_domain( &table );
 	if (dom == NULL) {
-		rpc->fault(ctx, 500, "Domain not found");
+		rpc->fault(ctx, 500, "Domain table not found");
 		return;
 	}
 
@@ -661,7 +661,7 @@ static void ul_rpc_add(rpc_t* rpc, void* ctx)
 	/* look for table */
 	dom = rpc_find_domain( &table );
 	if (dom == NULL) {
-		rpc->fault(ctx, 500, "Domain not found");
+		rpc->fault(ctx, 500, "Domain table not found");
 		return;
 	}
 

+ 3 - 1
src/modules/usrloc/usrloc_mod.c

@@ -116,6 +116,7 @@ int ul_db_raw_fetch_type = 0;
 int ul_rm_expired_delay = 0;
 int ul_version_table = 1;
 
+int ul_load_rank = PROC_SIPINIT;
 str ul_xavp_contact_name = {0};
 
 str ul_ka_from = str_init("sip:[email protected]");
@@ -261,6 +262,7 @@ static param_export_t params[] = {
 	{"ka_timeout",          PARAM_INT, &ul_keepalive_timeout},
 	{"ka_loglevel",         PARAM_INT, &ul_ka_loglevel},
 	{"ka_logmsg",           PARAM_STR, &ul_ka_logmsg},
+	{"load_rank",           PARAM_INT, &ul_load_rank},
 	{0, 0, 0}
 };
 
@@ -472,7 +474,7 @@ static int child_init(int _rank)
 		return -1;
 	}
 	/* _rank==PROC_SIPINIT is used even when fork is disabled */
-	if (_rank==PROC_SIPINIT && ul_db_mode!=DB_ONLY && ul_db_load) {
+	if (_rank==ul_load_rank && ul_db_mode!=DB_ONLY && ul_db_load) {
 		/* if cache is used, populate domains from DB */
 		for(ptr=_ksr_ul_root ; ptr ; ptr=ptr->next) {
 			if (preload_udomain(ul_dbh, ptr->d) < 0) {

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików