Quellcode durchsuchen

Merge branch 'master' into hpw/branch_failure_route

* master: (33 commits)
  core: set TOS for IPv6 UDP sockets
  rtpproxy: add missing wrapper function for unforce_rtp_proxy
  modules/outbound: further improvement to the use_outbound() check
  rtpproxy: support pvars in function parameters
  modules/rr: copy the flow-token for "incoming" messages when using outbound
  modules/outbound: improved check for outbound
  modules/rr: only use flow-token for routing if it doesn't point to the source of the request
  pkg/kamailio/(centos|fedora): Updated .spec after addition of cnxcc module
  Makefile.groups: cnxcc module added to extra list
  core: auto-define cfg directive MOD_modname for each loaded module
  snmpstats Add tcpasync and tcpmaxconns
  snmpstats Add IDs to sections in documentation
  cnxcc: added new module for credit control
  snmpstats activate new parts of the KAMAILIO-MIB
  snmpstats Add information about version and tcp connections
  usrloc: new option for db_mode - DB_READONLY (4)
  enum: define the max size for numbers
  db_mysql: new module parameter - insert_delayed
  Makefile: exclude debian sym link when generating tarball
  msrp: fix compile warning of argument type in dbg message
  ...
Hugh Waite vor 12 Jahren
Ursprung
Commit
464ce5d71d
66 geänderte Dateien mit 5972 neuen und 227 gelöschten Zeilen
  1. 1 0
      Makefile
  2. 3 3
      Makefile.groups
  3. 32 10
      examples/scscf/kamailio.cfg
  4. 0 7
      modules/auth_diameter/auth_diameter.c
  5. 1 3
      modules/auth_diameter/authorize.c
  6. 6 1
      modules/carrierroute/README
  7. 43 1
      modules/carrierroute/carrierroute.c
  8. 3 0
      modules/carrierroute/carrierroute.h
  9. 118 3
      modules/carrierroute/cr_func.c
  10. 7 1
      modules/carrierroute/doc/carrierroute_admin.xml
  11. 17 0
      modules/cnxcc/Makefile
  12. 270 0
      modules/cnxcc/README
  13. 63 0
      modules/cnxcc/cnxcc.c
  14. 39 0
      modules/cnxcc/cnxcc.h
  15. 176 0
      modules/cnxcc/cnxcc_check.c
  16. 16 0
      modules/cnxcc/cnxcc_check.h
  17. 1526 0
      modules/cnxcc/cnxcc_mod.c
  18. 156 0
      modules/cnxcc/cnxcc_mod.h
  19. 269 0
      modules/cnxcc/cnxcc_rpc.c
  20. 16 0
      modules/cnxcc/cnxcc_rpc.h
  21. 71 0
      modules/cnxcc/cnxcc_sip_msg_faker.c
  22. 29 0
      modules/cnxcc/cnxcc_sip_msg_faker.h
  23. 4 0
      modules/cnxcc/doc/Makefile
  24. 36 0
      modules/cnxcc/doc/cnxcc.xml
  25. 299 0
      modules/cnxcc/doc/cnxcc_admin.xml
  26. 983 0
      modules/cnxcc/example/kamailio-cnxcc.cfg
  27. 22 6
      modules/db_mysql/README
  28. 20 0
      modules/db_mysql/doc/db_mysql_admin.xml
  29. 1 0
      modules/db_mysql/km_db_mysql.c
  30. 1 0
      modules/db_mysql/km_db_mysql.h
  31. 8 3
      modules/db_mysql/km_dbase.c
  32. 1 0
      modules/db_mysql/mysql_mod.c
  33. 22 15
      modules/drouting/README
  34. 15 0
      modules/drouting/doc/drouting_admin.xml
  35. 38 0
      modules/drouting/drouting.c
  36. 4 4
      modules/enum/enum.c
  37. 3 2
      modules/enum/enum.h
  38. 1 1
      modules/msrp/msrp_parser.c
  39. 109 38
      modules/outbound/ob_mod.c
  40. 2 0
      modules/p_usrloc/udomain.c
  41. 2 0
      modules/p_usrloc/udomain.h
  42. 7 4
      modules/rr/loose.c
  43. 136 25
      modules/rr/record.c
  44. 4 4
      modules/rtpproxy/README
  45. 1 1
      modules/rtpproxy/doc/rtpproxy_admin.xml
  46. 55 19
      modules/rtpproxy/rtpproxy.c
  47. 8 8
      modules/snmpstats/README
  48. 10 10
      modules/snmpstats/doc/snmpstats_admin.xml
  49. 402 0
      modules/snmpstats/kamailioNet.c
  50. 21 0
      modules/snmpstats/kamailioNet.h
  51. 440 0
      modules/snmpstats/kamailioServer.c
  52. 52 0
      modules/snmpstats/kamailioServer.h
  53. 302 2
      modules/snmpstats/mibs/KAMAILIO-MIB
  54. 0 1
      modules/snmpstats/mibs/KAMAILIO-SIP-COMMON-MIB
  55. 5 15
      modules/snmpstats/snmpSIPMethodSupportedTable.c
  56. 1 1
      modules/snmpstats/snmpSIPServerObjects.c
  57. 10 0
      modules/snmpstats/sub_agent.c
  58. 28 23
      modules/usrloc/README
  59. 9 0
      modules/usrloc/doc/usrloc_admin.xml
  60. 9 5
      modules/usrloc/ul_mod.c
  61. 1 0
      modules/usrloc/urecord.c
  62. 1 0
      modules/usrloc/usrloc.h
  63. 4 0
      pkg/kamailio/fedora/17/kamailio.spec
  64. 16 0
      sr_module.c
  65. 17 4
      udp_server.c
  66. 0 7
      utils/sercmd/sercmd.c

+ 1 - 0
Makefile

@@ -622,6 +622,7 @@ tar: makefile_vars $(auto_gen_keep)
 	$(TAR) -C .. \
 		--exclude=$(notdir $(CURDIR))/test* \
 		--exclude=$(notdir $(CURDIR))/tmp* \
+		--exclude=$(notdir $(CURDIR))/debian \
 		--exclude=$(notdir $(CURDIR))/debian/$(MAIN_NAME) \
 		--exclude=$(notdir $(CURDIR))/debian/$(MAIN_NAME)-* \
 		--exclude=$(notdir $(CURDIR))/$(MAIN_NAME)_tls* \

+ 3 - 3
Makefile.groups

@@ -16,9 +16,9 @@ mod_list_basic=async auth benchmark blst cfg_rpc cfgutils corex counters \
 				   textopsx tm tmx topoh xlog
 
 # - extra used modules, with no extra dependency
-mod_list_extra=avp auth_diameter call_control dmq domainpolicy msrp pdb qos \
-				 sca seas sms sst timer tmrec uac_redirect xhttp xhttp_rpc \
-				 xprint
+mod_list_extra=avp auth_diameter call_control cnxcc dmq domainpolicy msrp pdb \
+			     qos sca seas sms sst timer tmrec uac_redirect xhttp \
+				 xhttp_rpc xprint
 
 # - common modules depending on database
 mod_list_db=acc alias_db auth_db avpops cfg_db db_text db_flatstore \

+ 32 - 10
examples/scscf/kamailio.cfg

@@ -430,16 +430,38 @@ route[XMLRPC] {
 # Route for handling Registrations:
 ######################################################################
 route[REGISTER] {
-	xlog("L_ERR", "Enter register block");
-	t_newtran();
-	
-	ims_www_authenticate(NETWORKNAME);
-
-	#check to see if user is authenticated - ie sip header has auth information - (already challenged)
-	if ($avp(maa_return_code) == 1) {
-		# user has not been authenticated. Lets send a challenge via 401 Unauthorized
-		ims_www_challenge("$td"); 
-		exit;
+        if (!ims_www_authenticate(NETWORKNAME)) {
+                if ($? == -2) {
+                        t_reply("403", "Authentication Failed");
+                        exit;
+                } else if ($? == -3) {
+                        t_reply("400", "Bad Request");
+                        exit;
+                } else {
+                        #user has not been authenticated. Lets send a challenge via 401 Unauthorized
+                        xlog("L_DBG","About to challenge! auth_ims\n");
+                        ims_www_challenge("$td");
+                        #this is async so to know status we have to check the reply avp
+                        xlog("L_DBG","maa_return code is $avp(s:maa_return_code)\n");
+
+                        switch ($avp(s:maa_return_code)){
+                                case 1: #success
+                                        xlog("L_DBG", "MAR success - 401/407 response sent from module");
+                                        break;
+                                case -1: #failure
+                                        xlog("L_ERR", "MAR failure - error response sent from module");
+                                        break;
+                                case -2: #error
+                                        xlog("L_ERR", "MAR error - sending error response now");
+                                        t_reply("500", "MAR failed");
+                                        break;
+                                default:
+                                        xlog("L_ERR", "Unknown return code from MAR, value is [$avp(s:uaa_return_code)]");
+                                        t_reply("500", "Unknown response code from MAR");
+                                        break;
+                        }
+                        exit;
+                }
 	} else {
 		# We need to check if this user is registered or not
 		if (!impu_registered("location")) {

+ 0 - 7
modules/auth_diameter/auth_diameter.c

@@ -212,13 +212,10 @@ int diameter_www_authorize(struct sip_msg* _msg, char* _realm, char* _s2)
 
 static int group_fixup(void** param, int param_no)
 {
-	void* ptr;
 	str* s;
 
 	if (param_no == 1) 
 	{
-		ptr = *param;
-		
 		if (!strcasecmp((char*)*param, "Request-URI")) 
 		{
 			*param = (void*)1;
@@ -245,8 +242,6 @@ static int group_fixup(void** param, int param_no)
 				
 		LM_ERR("unsupported Header Field identifier\n");
 		return E_UNSPEC;
-		
-		//pkg_free(ptr);
 	} 
 	
 	if (param_no == 2) 
@@ -257,14 +252,12 @@ static int group_fixup(void** param, int param_no)
 			LM_ERR("no pkg memory left\n");
 			return E_UNSPEC;
 		}
-		ptr = *param;
 		s->s = (char*)*param;
 		s->len = strlen(s->s);
 		*param = (void*)s;
 	}
 
 end:
-//	pkg_free(ptr);
 	return 0;
 }
 

+ 1 - 3
modules/auth_diameter/authorize.c

@@ -295,7 +295,7 @@ int authorize(struct sip_msg* msg, pv_elem_t* realm, int hftype)
 int diameter_authorize(struct hdr_field* hdr, str* p_method, struct sip_uri uri,
 						struct sip_uri ruri, unsigned int m_id, rd_buf_t* rb)
 {
-	str method, user_name;
+	str user_name;
 	AAAMessage *req;
 	AAA_AVP *avp, *position; 
 	int name_flag, port_flag;
@@ -316,8 +316,6 @@ int diameter_authorize(struct hdr_field* hdr, str* p_method, struct sip_uri uri,
 	else
 		cred = NULL;
 			
-	method = *p_method;
-
 	if(!cred)
 	{
 		/* Username AVP */

+ 6 - 1
modules/carrierroute/README

@@ -511,6 +511,11 @@ descavp)
    This is useful if you need some additional informations that belongs to
    each gw, like the destination or the number of channels.
 
+   The function pays special attention to the failurerouting cases, so
+   that any destination that has failed to provide a successful response
+   will not be reused in a subsequent call of cr_route. This situation can
+   appear when different route domains contain a set of common gateways.
+
    This function is only usable with rewrite_user and prefix_matching
    containing a valid string. This string needs to be numerical if the
    match_mode parameter is set to 10. It uses the standard CRC32 algorithm
@@ -1063,7 +1068,7 @@ Chapter 2. Module parameter for database access.
 
    URL to the database containing the data.
 
-   Default value is “mysql://openserro:openserro@localhost/openser”.
+   Default value is “mysql://kamailioro:kamailioro@localhost/kamailio”.
 
    Example 2.1. Set db_url parameter
 ...

+ 43 - 1
modules/carrierroute/carrierroute.c

@@ -41,6 +41,7 @@
 #include "../../str.h"
 #include "../../mem/mem.h"
 #include "../../ut.h" /* for user2uid() */
+#include "../../rpc_lookup.h" /* for sercmd */
 #include "carrierroute.h"
 #include "cr_fixup.h"
 #include "cr_map.h"
@@ -51,6 +52,9 @@
 #include "config.h"
 #include <sys/stat.h>
 
+#define AVP_CR_URIS "_cr_uris"
+int_str cr_uris_avp; // contains all PSTN destinations
+
 MODULE_VERSION
 
 str carrierroute_db_url = str_init(DEFAULT_RODB_URL);
@@ -129,6 +133,8 @@ static mi_export_t mi_cmds[] = {
 	{ 0, 0, 0, 0, 0}
 };
 
+static rpc_export_t rpc_methods[];
+
 struct module_exports exports = {
 	"carrierroute",
 	DEFAULT_DLFLAGS, /* dlopen flags */
@@ -164,6 +170,11 @@ static int mod_init(void) {
 		return -1;
 	}
 
+	if(rpc_register_array(rpc_methods)!=0) {
+		LM_ERR("failed to register RPC commands\n");
+		return -1;
+	}
+
 	subscriber_table.len = strlen(subscriber_table.s);
 	subscriber_username_col.len = strlen(subscriber_username_col.s);
 	subscriber_domain_col.len = strlen(subscriber_domain_col.s);
@@ -241,6 +252,10 @@ static int mod_init(void) {
 	if(mode == CARRIERROUTE_MODE_DB){
 		carrierroute_db_close();
 	}
+
+	cr_uris_avp.s.s = AVP_CR_URIS;
+	cr_uris_avp.s.len = sizeof(AVP_CR_URIS) -1;
+
 	return 0;
 }
 
@@ -263,10 +278,37 @@ static int mi_child_init(void) {
 	return 0;
 }
 
-
 static void mod_destroy(void) {
 	if(mode == CARRIERROUTE_MODE_DB){
 		carrierroute_db_close();
 	}
 	destroy_route_data();
 }
+
+static const char *rpc_cr_reload_routes_doc[2] = {
+	"Reload routes", 0
+};
+
+static void rpc_cr_reload_routes(rpc_t *rpc, void *c) {
+
+	if(mode == CARRIERROUTE_MODE_DB){
+		if (carrierroute_dbh==NULL) {
+			carrierroute_dbh = carrierroute_dbf.init(&carrierroute_db_url);
+			if(carrierroute_dbh==0 ) {
+				LM_ERR("cannot initialize database connection\n");
+				return;
+			}
+		}
+	}
+
+	if ( (reload_route_data())!=0 ) {
+		LM_ERR("failed to load routing data\n");
+		return;
+	}
+}
+
+static rpc_export_t rpc_methods[] = {
+	{ "cr.reload_routes",  rpc_cr_reload_routes, rpc_cr_reload_routes_doc, 0},
+	{0, 0, 0, 0}
+};
+

+ 3 - 0
modules/carrierroute/carrierroute.h

@@ -31,6 +31,7 @@
 #define CARRIERROUTE_H
 
 #include "../../str.h"
+#include "../../usr_avp.h"
 
 #define DICE_MAX 1000
 
@@ -53,4 +54,6 @@ extern const str CR_EMPTY_PREFIX;
 extern int mode;
 extern int cr_match_mode;
 
+extern int_str cr_uris_avp;
+
 #endif

+ 118 - 3
modules/carrierroute/cr_func.c

@@ -49,6 +49,8 @@
 #include "carrierroute.h"
 #include "config.h"
 
+#define MAX_DESTINATIONS 64
+
 enum hash_algorithm {
 	alg_crc32 = 1, /*!< hashing algorithm is CRC32 */
 	alg_crc32_nofallback, /*!< same algorithm as alg_crc32, with only a backup rule, but no fallback tree is chosen
@@ -269,6 +271,65 @@ static struct route_rule * get_rule_by_hash(const struct route_flags * rf,
 	return act_hash;
 }
 
+// debug functions for cr_uri_avp
+/*
+static void print_cr_uri_avp(){
+	struct search_state st;
+	int_str val;
+	int elem = 0;
+
+	if (!search_first_avp( AVP_VAL_STR | AVP_NAME_STR, cr_uris_avp, &val, &st)) {
+		LM_DBG("no AVPs - we are done!\n");
+		return;
+	}
+
+	LM_DBG("	cr_uri_avp[%d]=%.*s\n", elem++, val.s.len, val.s.s);
+
+	while (  search_next_avp(&st, &val) ) {
+		LM_DBG("	cr_uri_avp[%d]=%.*s\n", elem++, val.s.len, val.s.s);
+	}
+}
+*/
+
+static void build_used_uris_list(avp_value_t* used_dests, int* no_dests){
+	struct search_state st;
+	int_str val;
+	*no_dests = 0;
+
+	if (!search_first_avp( AVP_VAL_STR | AVP_NAME_STR, cr_uris_avp, &val, &st)) {
+		//LM_DBG("no AVPs - we are done!\n");
+		return;
+	}
+
+	used_dests[(*no_dests)++] = val;
+	//LM_DBG("	used_dests[%d]=%.*s \n", (*no_dests)-1, used_dests[(*no_dests)-1].s.len, used_dests[(*no_dests)-1].s.s);
+
+	while ( search_next_avp(&st, &val) ) {
+		if ( MAX_DESTINATIONS == *no_dests ) {
+			LM_ERR("Too many  AVPs - we are done!\n");
+			return;
+		}
+		used_dests[(*no_dests)++] = val;
+		//LM_DBG("	used_dests[%d]=%.*s \n", (*no_dests)-1, used_dests[(*no_dests)-1].s.len, used_dests[(*no_dests)-1].s.s);
+	}
+
+	//LM_DBG("sucessfully built used_uris list!\n");
+}
+
+int cr_uri_already_used(str dest , avp_value_t* used_dests, int no_dests){
+	int i;
+	for (i=0; i<no_dests; i++){
+		if ( (dest.len == used_dests[i].s.len) &&
+				(memcmp(dest.s, used_dests[i].s.s, dest.len)==0)){
+			LM_NOTICE("Candidate destination <%.*s> was previously used.\n", dest.len, dest.s);
+			return 1;
+		}
+
+	}
+	//LM_DBG("cr_uri_already_used: Candidate destination <%.*s> was NEVER USED.\n", dest.len, dest.s);
+	return 0;
+}
+
 
 /**
  * does the work for rewrite_on_rule, writes the new URI into dest
@@ -288,7 +349,6 @@ static int actually_rewrite(const struct route_rule *rs, str *dest,
 	char *p;
 	int_str avp_val;
 	int strip = 0;
-
 	str l_user;
 	
 	if( !rs || !dest || !msg || !user) {
@@ -406,6 +466,11 @@ static int rewrite_on_rule(struct route_flags *rf_head, flag_t flags, str * dest
 
 	switch (alg) {
 		case alg_crc32:
+		{
+			static avp_value_t used_dests[MAX_DESTINATIONS];
+			static int no_dests = 0;
+			avp_value_t cr_new_uri;
+
 			if(rf->dice_max == 0) {
 				LM_ERR("invalid dice_max value\n");
 				return -1;
@@ -414,15 +479,48 @@ static int rewrite_on_rule(struct route_flags *rf_head, flag_t flags, str * dest
 				LM_ERR("could not hash message with CRC32");
 				return -1;
 			}
+
 			/* This auto-magically takes the last rule if anything is broken.
 			 * Sometimes the hash result is zero. If the first rule is off
 			 * (has a probablility of zero) then it has also a dice_to of
 			 * zero and the message could not be routed at all if we use
 			 * '<' here. Thus the '<=' is necessary.
+			 *
+			 * cr_uri_already_used is a function that checks that the selected
+			 * rule has not been previously used as a failed destinatin
 			 */
+
 			for (rr = rf->rule_list;
-			        rr->next != NULL && rr->dice_to <= prob;
-		        rr = rr->next) {}
+				rr->next!= NULL && rr->dice_to <= prob ; rr = rr->next) {}
+
+			//LM_DBG("CR: candidate hashed destination is: <%.*s>\n", rr->host.len, rr->host.s);
+
+			if (is_route_type(FAILURE_ROUTE) && (mode == CARRIERROUTE_MODE_DB) ){
+				build_used_uris_list(used_dests, &no_dests);
+
+				if (cr_uri_already_used(rr->host, used_dests, no_dests) ) {
+					//LM_DBG("CR: selecting new destination !!! \n");
+					for (rr = rf->rule_list;
+								rr!= NULL && cr_uri_already_used(rr->host, used_dests, no_dests); rr = rr->next) {}
+					/* are there any destinations that were not already used? */
+					if (rr == NULL) {
+						LM_NOTICE("All gateways from this group were already used\n");
+						return -1;
+					}
+
+					/* this is a hack: we do not take probabilities into consideration if first destination
+					 * was previously tried */
+
+					do {
+						int rule_no = rand() % rf->rule_num;
+						//LM_DBG("CR: trying rule_no=%d \n", rule_no);
+						for (rr = rf->rule_list; (rule_no > 0) && (rr->next!=NULL) ; rule_no-- , rr = rr->next) {}
+					} while (cr_uri_already_used(rr->host, used_dests, no_dests));
+					LM_DBG("CR: candidate selected destination is: <%.*s>\n", rr->host.len, rr->host.s);
+				}
+			}
+			/*This should be regarded as an ELSE branch for the if above
+			 * ( status exists for mode == CARRIERROUTE_MODE_FILE */
 			if (!rr->status) {
 				if (!rr->backup) {
 					LM_ERR("all routes are off\n");
@@ -435,7 +533,24 @@ static int rewrite_on_rule(struct route_flags *rf_head, flag_t flags, str * dest
 					rr = rr->backup->rr;
 				}
 			}
+
+			//LM_DBG("CR: destination is: <%.*s>\n", rr->host.len, rr->host.s);
+			cr_new_uri.s = rr->host;
+			/* insert used destination into avp, in case corresponding request fails and
+			 * another destination has to be used; this new destination must not be one
+			 * that failed before
+			 */
+
+			if (mode == CARRIERROUTE_MODE_DB){
+				if ( add_avp( AVP_VAL_STR | AVP_NAME_STR, cr_uris_avp, cr_new_uri) < 0){
+					LM_ERR("set AVP failed\n");
+					return -1;
+				}
+				//print_cr_uri_avp();
+			}
+
 			break;
+		}
 		case alg_crc32_nofallback:
 			if ((prob = (hash_func(msg, hash_source, rf->max_targets))) < 0) {
 				LM_ERR("could not hash message with CRC32");

+ 7 - 1
modules/carrierroute/doc/carrierroute_admin.xml

@@ -427,7 +427,13 @@ cr_tree_rewrite_uri(tree, domain)
         is stored in an AVP). This is useful if you need some additional
         informations that belongs to each gw, like the destination or the
         number of channels.
-        </para>
+		</para>
+		<para>
+		The function pays special attention to the failurerouting cases, so that
+		any destination that has failed to provide a successful response will not
+		be reused in a subsequent call of cr_route. This situation can appear when
+		different route domains contain a set of common gateways.
+		</para>
         <para>
         This function is only usable with rewrite_user and prefix_matching
         containing a valid string. This string needs to be numerical if the match_mode

+ 17 - 0
modules/cnxcc/Makefile

@@ -0,0 +1,17 @@
+# $Id$
+#
+# example module makefile
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=cnxcc.so
+
+DEFS+=-DOPENSER_MOD_INTERFACE
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/kmi/kmi
+SER_LIBS+=$(SERLIBPATH)/srdb1/srdb1
+SER_LIBS+=$(SERLIBPATH)/kcore/kcore
+include ../../Makefile.modules

+ 270 - 0
modules/cnxcc/README

@@ -0,0 +1,270 @@
+
+cnxcc Module
+
+Carlos Ruiz Diaz
+
+   ConexionGroup SA
+
+   Copyright © 2012 Carlos Ruiz Diaz, [email protected]
+     __________________________________________________________________
+
+   Table of Contents
+
+   1. Admin Guide
+
+        1. Overview
+        2. Dependencies
+
+              2.1. Modules
+
+        3. Parameters
+
+              3.1. dlg_flag (integer)
+              3.2. credit_check_period (integer)
+
+        4. Functions
+
+              4.1. cnxcc_set_max_credit()
+              4.2. cnxcc_set_max_time()
+
+        5. Exported RPC Commands
+
+              5.1. cnxcc.active_clients
+              5.2. cnxcc.check_client
+              5.3. cnxcc.kill_call
+
+        6. Events
+        7. Web Interface
+        8. Sample
+
+   List of Examples
+
+   1.1. dlg_flag
+   1.2. credit_check_period
+   1.3. cnxcc_set_max_credit()
+   1.4. cnxcc_set_max_time()
+   1.5. kamailio-cnxcc.cfg
+
+Chapter 1. Admin Guide
+
+   Table of Contents
+
+   1. Overview
+   2. Dependencies
+
+        2.1. Modules
+
+   3. Parameters
+
+        3.1. dlg_flag (integer)
+        3.2. credit_check_period (integer)
+
+   4. Functions
+
+        4.1. cnxcc_set_max_credit()
+        4.2. cnxcc_set_max_time()
+
+   5. Exported RPC Commands
+
+        5.1. cnxcc.active_clients
+        5.2. cnxcc.check_client
+        5.3. cnxcc.kill_call
+
+   6. Events
+   7. Web Interface
+   8. Sample
+
+1. Overview
+
+   This module was designed to act as a mechanism to limit call duration
+   based on credit information parameters. After getting the credit
+   information of the call being set up, you can instruct the module to
+   start monitoring the consumed credit to shutdown a single call or a
+   group of calls in case of credit exhaustion.
+
+   Every call is associated to an unique client/customer identifier. If a
+   credit event occurs, all calls hooked to this identifier are
+   automatically shutdown.
+
+   Cnxcc is dialog-aware so there's no need to explicitly
+   allocate/deallocate the monitoring. Only a single function call inside
+   the script is needed upon reception of the INVITE.
+
+   The credit discount rate is proportional to the number of calls grouped
+   inside an identifier. Once the setup of the first call is done, the
+   information remains while the call is active. If the customer starts a
+   new call with the same routing criteria, it will land in the same
+   monitoring bag and it will consume the same pool of credit in rates
+   that are equal to the cost per second of both calls.
+
+   If your accounting program does not maintain the state of the call in
+   real time, this module can provide you that ability.
+
+2. Dependencies
+
+   2.1. Modules
+
+2.1. Modules
+
+   The following module must be loaded before this module:
+     * dialog
+
+3. Parameters
+
+   3.1. dlg_flag (integer)
+   3.2. credit_check_period (integer)
+
+3.1.  dlg_flag (integer)
+
+   Flag to indicate if the dialog must be monitored or not. Messages are
+   flagged with this value if we call one of the monitoring functions.
+
+   Example 1.1. dlg_flag
+...
+modparam("cnxcc", "dlg_flag", 29)
+...
+
+3.2. credit_check_period (integer)
+
+   Indicates how often the credit checking function should be called. It
+   is directly related to the precison of the module. The maximum
+   precision is 1, which means that every call is checked every one
+   second.
+
+   Values greater than 1 leads to precision lost but less CPU consumption.
+
+   Example 1.2. credit_check_period
+...
+modparam("cnxcc", "credit_check_period", 1)
+...
+
+4. Functions
+
+   4.1. cnxcc_set_max_credit()
+   4.2. cnxcc_set_max_time()
+
+4.1.  cnxcc_set_max_credit()
+
+   Specifies the initial pulse, final pulse, max credit and cost per
+   second of a call. The discount is calculated in pulses (30/6, 1/1, etc)
+   and sustracted from the pool of credit.
+
+   Return code:
+     * 1 - successful
+     * -1 - failed, error logged
+
+   Example 1.3. cnxcc_set_max_credit()
+...
+$var(customer) = "john-doe-123-premium";
+$var(credit) = "100";
+$var(cps)   = "2.00";         # cost per second
+$var(initial_p)   = "030";    # intial pulse
+$var(final_p)   = "006";      # final pulse
+
+cnxcc_set_max_credit("$var(customer)", "$var(credit)", "$var(cps)", "$var(initia
+l_p)", "$var(final_p)");
+...
+
+4.2.  cnxcc_set_max_time()
+
+   Specifies the amount of time the call should last at most.
+
+   Return code:
+     * 1 - successful
+     * -1 - failed, error logged
+
+   Example 1.4. cnxcc_set_max_time()
+...
+$var(customer) = "john-doe-123-basic";
+$var(max_time) = 120;
+
+cnxcc_set_max_tim ("$var(customer)", "$var(max_time)");
+...
+
+5. Exported RPC Commands
+
+   5.1. cnxcc.active_clients
+   5.2. cnxcc.check_client
+   5.3. cnxcc.kill_call
+
+5.1. cnxcc.active_clients
+
+   Retrieves all calls grouped by their identifiers.
+
+   Parameters: none
+
+   Example:
+            sercmd cnxcc.active_clients
+
+5.2. cnxcc.check_client
+
+   Retrives all calls from a particular identifier.
+
+   Parameters: client/customer identifier
+
+   Example:
+            sercmd cnxcc.check_client john-doe-123-premium
+
+5.3. cnxcc.kill_call
+
+   Kills an active call using its call ID.
+
+   Parameters: Call-ID
+
+   Example:
+            sercmd cnxcc.kill_call [email protected]
+
+6. Events
+
+   When a call is forced to end an event route is automatically invoked.
+   This route is suited with a fake OPTIONS message containing the call
+   ID, ftag and ttag of the original call so it can be located somehow in
+   the accounting database.
+
+   Example:
+...
+event_route[cnxcc:call-shutdown]
+{
+        xlog("L_INFO", "[$ci]: call killed");
+
+        # perform some kind of notification, database update, email sending, etc
+.
+}
+...
+
+7. Web Interface
+
+   The module contains a web management interface completely optional.
+   With it, you can review your calls in real time and hang them up if
+   necessary.
+
+   Link: https://github.com/caruizdiaz/cnxcc-web
+
+8. Sample
+
+   Example 1.5. kamailio-cnxcc.cfg
+...
+route[CNXCC]
+{
+        $var(client)              = "test-client-0-123-01";
+        $var(credit)              = "50";
+        $var(cost_per_sec)        = "0.5";
+        $var(i_pulse)             = "30";
+        $var(f_pulse)             = "6";
+
+        if (!cnxcc_set_max_credit("$var(client)",
+                          "$var(credit)",
+                          "$var(cost_per_sec)",
+                          "$var(i_pulse)",
+                          "$var(f_pulse)")) {
+                 xlog("Error setting up credit control");
+        }
+}
+
+event_route[cnxcc:call-shutdown]
+{
+        xlog("L_INFO", "[$ci]: call killed");
+
+
+}
+...

+ 63 - 0
modules/cnxcc/cnxcc.c

@@ -0,0 +1,63 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2012 Carlos Ruiz Díaz (caruizdiaz.com),
+ *                    ConexionGroup (www.conexiongroup.com)
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#include <time.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/time.h>
+
+#include "cnxcc.h"
+
+inline void get_datetime(str *dest)
+{
+	timestamp2isodt(dest, get_current_timestamp());
+}
+
+inline unsigned int get_current_timestamp()
+{
+	return time(NULL);
+}
+
+inline int timestamp2isodt(str *dest, unsigned int timestamp)
+{
+	time_t  		tim;
+	struct tm 		*tmPtr;
+
+	tim 		= timestamp;
+	tmPtr 		= localtime(&tim);
+
+	strftime( dest->s, DATETIME_SIZE, "%Y-%m-%d %H:%M:%S", tmPtr);
+	dest->len	= DATETIME_LENGTH;
+
+	return 0;
+}
+
+double str2double(str *string)
+{
+	char buffer[string->len + 1];
+
+	buffer[string->len]	= '\0';
+	memcpy(buffer, string->s, string->len);
+
+	return atof(buffer);
+}

+ 39 - 0
modules/cnxcc/cnxcc.h

@@ -0,0 +1,39 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2012 Carlos Ruiz Díaz (caruizdiaz.com),
+ *                    ConexionGroup (www.conexiongroup.com)
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef _CNXCC_H
+#define _CNXCC_H
+
+#include "../../str.h"
+
+#define DATETIME_SIZE		sizeof("0001-01-01 00:00:00")
+#define DATETIME_LENGTH		DATETIME_SIZE - 1
+
+
+inline void get_datetime(str *dest);
+inline unsigned int get_current_timestamp();
+inline int timestamp2isodt(str *dest, unsigned int timestamp);
+double str2double(str *string);
+
+#endif /* _CNXCC_H */

+ 176 - 0
modules/cnxcc/cnxcc_check.c

@@ -0,0 +1,176 @@
+/*
+ * cnxcc_check.c
+ *
+ *  Created on: Dec 10, 2012
+ *      Author: carlos
+ */
+
+#include <stdio.h>
+
+#include "../../locking.h"
+#include "../../lock_ops.h"
+
+#include "cnxcc_mod.h"
+#include "cnxcc.h"
+#include "cnxcc_check.h"
+
+extern data_t _data;
+
+void check_calls_by_money(unsigned int ticks, void *param)
+{
+	struct str_hash_entry *h_entry 	= NULL,
+						  *tmp		= NULL;
+	call_t *tmp_call				= NULL;
+	int i;
+
+	lock_get(&_data.money.lock);
+
+	if (_data.money.credit_data_by_client->table)
+		for(i = 0; i < _data.money.credit_data_by_client->size; i++)
+			clist_foreach_safe(&_data.money.credit_data_by_client->table[i], h_entry, tmp, next)
+			{
+				credit_data_t *credit_data	= (credit_data_t *) h_entry->u.p;
+				call_t *call				= NULL;
+				double total_consumed_money	= 0;
+
+				if (i > SAFE_ITERATION_THRESHOLD)
+				{
+					LM_ERR("Too many iterations for this loop: %d", i);
+					break;
+				}
+
+				lock_get(&credit_data->lock);
+
+				clist_foreach_safe(credit_data->call_list, call, tmp_call, next)
+				{
+					int consumed_time = 0;
+
+					if (!call->confirmed)
+						continue;
+
+					consumed_time 				= get_current_timestamp() - call->start_timestamp;
+
+					if (consumed_time > call->money_based.initial_pulse)
+					{
+						call->consumed_amount = (call->money_based.cost_per_second * call->money_based.initial_pulse)
+												+
+												call->money_based.cost_per_second *
+												( (consumed_time - call->money_based.initial_pulse) / call->money_based.final_pulse + 1 ) *
+												call->money_based.final_pulse;
+					}
+
+					total_consumed_money	+= call->consumed_amount;
+
+					if (call->consumed_amount > call->max_amount)
+					{
+						LM_ALERT("[%.*s] call has exhausted its credit. Breaking the loop\n", call->sip_data.callid.len, call->sip_data.callid.s);
+						break;
+					}
+
+					LM_DBG("CID [%.*s], start_timestamp [%d], seconds alive [%d], consumed credit [%f]\n",
+																			call->sip_data.callid.len, call->sip_data.callid.s,
+																			call->start_timestamp,
+																			consumed_time,
+																			call->consumed_amount
+																			);
+				}
+
+				if (credit_data->concurrent_calls == 0)
+				{
+					lock_release(&credit_data->lock);
+					continue;
+				}
+
+				credit_data->consumed_amount	= credit_data->ended_calls_consumed_amount + total_consumed_money;
+
+				LM_DBG("Client [%.*s] | Ended-Calls-Credit-Spent: %f  TotalCredit/MaxCredit: %f/%f\n", credit_data->call_list->client_id.len, credit_data->call_list->client_id.s,
+																									credit_data->ended_calls_consumed_amount,
+																									credit_data->consumed_amount,
+																									credit_data->max_amount);
+
+				if (credit_data->consumed_amount >= credit_data->max_amount)
+				{
+					terminate_all_calls(credit_data);
+					lock_release(&credit_data->lock);
+					break;
+				}
+
+				lock_release(&credit_data->lock);
+			}
+
+	lock_release(&_data.money.lock);
+}
+
+void check_calls_by_time(unsigned int ticks, void *param)
+{
+	struct str_hash_entry *h_entry 	= NULL,
+						  *tmp		= NULL;
+	call_t *tmp_call				= NULL;
+	int i;
+
+	lock_get(&_data.time.lock);
+
+	if (_data.time.credit_data_by_client->table)
+		for(i = 0; i < _data.time.credit_data_by_client->size; i++)
+			clist_foreach_safe(&_data.time.credit_data_by_client->table[i], h_entry, tmp, next)
+			{
+				credit_data_t *credit_data	= (credit_data_t *) h_entry->u.p;
+				call_t *call				= NULL;
+				int total_consumed_secs		= 0;
+
+				lock_get(&credit_data->lock);
+
+				if (i > SAFE_ITERATION_THRESHOLD)
+				{
+					LM_ERR("Too many iterations for this loop: %d", i);
+					break;
+				}
+
+				LM_DBG("Iterating through calls of client [%.*s]\n", credit_data->call_list->client_id.len, credit_data->call_list->client_id.s);
+
+				clist_foreach_safe(credit_data->call_list, call, tmp_call, next)
+				{
+					if (!call->confirmed)
+						continue;
+
+					call->consumed_amount		= get_current_timestamp() - call->start_timestamp;
+					total_consumed_secs			+= call->consumed_amount;
+
+					if (call->consumed_amount > call->max_amount)
+					{
+						LM_ALERT("[%.*s] call has exhausted its time. Breaking the loop\n", call->sip_data.callid.len, call->sip_data.callid.s);
+						break;
+					}
+
+					LM_DBG("CID [%.*s], start_timestamp [%d], seconds alive [%d]\n",
+																			call->sip_data.callid.len, call->sip_data.callid.s,
+																			call->start_timestamp,
+																			(int) call->consumed_amount
+																			);
+				}
+
+				if (credit_data->concurrent_calls == 0)
+				{
+					lock_release(&credit_data->lock);
+					continue;
+				}
+
+				credit_data->consumed_amount	= credit_data->ended_calls_consumed_amount + total_consumed_secs;
+
+				LM_DBG("Client [%.*s] | Ended-Calls-Time: %d  TotalTime/MaxTime: %d/%d\n", credit_data->call_list->client_id.len, credit_data->call_list->client_id.s,
+																									(int) credit_data->ended_calls_consumed_amount,
+																									(int) credit_data->consumed_amount,
+																									(int) credit_data->max_amount);
+
+				if (credit_data->consumed_amount >= credit_data->max_amount)
+				{
+					terminate_all_calls(credit_data);
+					lock_release(&credit_data->lock);
+					break;
+				}
+
+				lock_release(&credit_data->lock);
+			}
+
+	lock_release(&_data.time.lock);
+}

+ 16 - 0
modules/cnxcc/cnxcc_check.h

@@ -0,0 +1,16 @@
+/*
+ * cnxcc_check.h
+ *
+ *  Created on: Dec 10, 2012
+ *      Author: carlos
+ */
+
+#ifndef CNXCC_CHECK_H_
+#define CNXCC_CHECK_H_
+
+#define SAFE_ITERATION_THRESHOLD 5000
+
+void check_calls_by_time(unsigned int ticks, void *param);
+void check_calls_by_money(unsigned int ticks, void *param);
+
+#endif /* CNXCC_CHECK_H_ */

+ 1526 - 0
modules/cnxcc/cnxcc_mod.c

@@ -0,0 +1,1526 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2012 Carlos Ruiz Díaz (caruizdiaz.com),
+ *                    ConexionGroup (www.conexiongroup.com)
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <ctype.h>
+
+#include "../../sr_module.h"
+#include "../../dprint.h"
+#include "../../error.h"
+#include "../../mem/mem.h"
+#include "../../shm_init.h"
+#include "../../mem/shm_mem.h"
+#include "../../pvar.h"
+#include "../../locking.h"
+#include "../../lock_ops.h"
+#include "../../str_hash.h"
+//#include "../../timer.h"
+#include "../../timer_proc.h"
+#include "../../modules/tm/tm_load.h"
+#include "../../parser/parse_from.h"
+#include "../../parser/parse_to.h"
+#include "../../parser/parse_uri.h"
+#include "../../parser/parse_cseq.h"
+#include "../../parser/contact/parse_contact.h"
+#include "../../parser/contact/contact.h"
+#include "../../parser/parse_rr.h"
+//#include "../../lib/kcore/parser_helpers.h"
+#include "../../mod_fix.h"
+#include "../dialog/dlg_load.h"
+#include "../dialog/dlg_hash.h"
+#include "../../mi/mi_types.h"
+#include "../../lib/kcore/faked_msg.h"
+#include "../../rpc.h"
+#include "../../rpc_lookup.h"
+
+#include "cnxcc_mod.h"
+#include "cnxcc.h"
+#include "cnxcc_sip_msg_faker.h"
+#include "cnxcc_check.h"
+#include "cnxcc_rpc.h"
+
+MODULE_VERSION
+
+#define HT_SIZE						229
+#define MODULE_NAME					"CNXCC"
+#define NUMBER_OF_TIMERS			2
+
+#define TRUE						1
+#define FALSE						0
+
+data_t _data;
+struct dlg_binds _dlgbinds;
+
+static int fixup_par(void** param, int param_no);
+
+/*
+ *  module core functions
+ */
+static int mod_init(void);
+static int child_init(int);
+static int init_hashtable(struct str_hash_table *ht);
+
+/*
+ * Memory management functions
+ */
+static int shm_str_hash_alloc(struct str_hash_table *ht, int size);
+static void free_credit_data_hash_entry(struct str_hash_entry *e);
+
+/*
+ * PV management functions
+ */
+static int pv_parse_calls_param(pv_spec_p sp, str *in);
+static int pv_get_calls(struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
+//static int get_str_pv(struct sip_msg* msg, str *pv_name, str *pvvalue);
+
+/*
+ * Billing management functions
+ */
+static int set_max_time(struct sip_msg* msg, char* number, char* str2);
+static int set_max_credit(struct sip_msg* msg, char *str_pv_client, char *str_pv_credit, char *str_pv_cps, char *str_pv_inip, char *str_pv_finp);
+static void start_billing(str *callid, str tags[2]);
+static void setup_billing(str *callid, unsigned int h_entry, unsigned int h_id);
+static void stop_billing(str *callid);
+static int add_call_by_cid(str *cid, call_t *call, credit_type_t type);
+static credit_data_t *get_or_create_credit_data_entry(str *client_id, credit_type_t type);
+static call_t *alloc_new_call_by_time(credit_data_t *credit_data, struct sip_msg *msg, int max_secs);
+static call_t *alloc_new_call_by_money(credit_data_t *credit_data, struct sip_msg *msg, double credit, double cost_per_second, int initial_pulse, int final_pulse);
+static void notify_call_termination(str *callid, str *from_tag, str *to_tag);
+static void free_call(call_t *call);
+static int has_to_tag(struct sip_msg *msg);
+
+/*
+ * MI interface
+ */
+static struct mi_root *mi_credit_control_stats(struct mi_root *tree, void *param);
+
+/*
+ * Dialog management callback functions
+ */
+static void dialog_terminated_callback(struct dlg_cell *cell, int type, struct dlg_cb_params *params);
+static void dialog_confirmed_callback(struct dlg_cell *cell, int type, struct dlg_cb_params *params);
+static void dialog_created_callback(struct dlg_cell *cell, int type, struct dlg_cb_params *params);
+
+static pv_export_t mod_pvs[] =
+{
+	{ {"cnxcc", sizeof("cnxcc")-1 }, PVT_OTHER, pv_get_calls, 0,
+		                pv_parse_calls_param, 0, 0, 0 },
+	{ {0, 0}, 0, 0, 0, 0, 0, 0, 0 }
+};
+
+static cmd_export_t cmds[] =
+{
+	{"cnxcc_set_max_time",   (cmd_function) set_max_time, 2, fixup_pvar_pvar, fixup_free_pvar_pvar, ANY_ROUTE},
+	{"cnxcc_set_max_credit",   (cmd_function) set_max_credit, 5, fixup_par, NULL, ANY_ROUTE},
+	{0,0,0,0,0,0}
+};
+
+static param_export_t params[] =
+{
+	{"dlg_flag",  				INT_PARAM,			&_data.ctrl_flag	},
+	{"credit_check_period",  	INT_PARAM,			&_data.check_period	},
+	{ 0, 0, 0 }
+};
+
+static const char* rpc_active_clients_doc[2] =
+{
+	"List of clients with active calls",
+	0
+};
+
+static const char* rpc_check_client_stats_doc[2] =
+{
+	"Check specific client calls",
+	0
+};
+
+static const char* rpc_kill_call_doc[2] =
+{
+	"Kill call using its call ID",
+	0
+};
+
+rpc_export_t ul_rpc[] =
+{
+    {"cnxcc.active_clients",	rpc_active_clients,	rpc_active_clients_doc,	0},
+    {"cnxcc.check_client",		rpc_check_client_stats,	rpc_check_client_stats_doc,	0},
+    {"cnxcc.kill_call",			rpc_kill_call,	rpc_kill_call_doc,	0},
+    {0, 0, 0, 0}
+};
+
+/** module exports */
+struct module_exports exports =
+{
+	"cnxcc",
+	DEFAULT_DLFLAGS, 	/* dlopen flags */
+	cmds,
+	params,
+	0,          		/* exported statistics */
+	0, 		    		/* exported MI functions */
+	mod_pvs,  			/* exported pseudo-variables */
+	0,          		/* extra processes */
+	mod_init,   		/* module initialization function */
+	0,
+	0,
+	child_init          /* per-child init function */
+};
+
+static int fixup_par(void** param, int param_no)
+{
+	str var;
+
+	var.s	= (char *) *param;
+	var.len = strlen(var.s);
+
+	if (fixup_pvar_null(param, 1))
+	{
+		LM_ERR("Invalid PV [%.*s] as parameter\n", var.len, var.s);
+		return E_CFG;
+	}
+/*
+	if (((pv_spec_t*)(*param))->setf == NULL)
+	{
+		LM_ERR("[%.*s] has to be writable\n", var.len, var.s);
+		return E_CFG;
+	} */
+
+	return 0;
+}
+
+static int mod_init(void)
+{
+	LM_ALERT("Loading " MODULE_NAME " module\n");
+
+	_data.cs_route_number = route_get(&event_rt, "cnxcc:call-shutdown");
+
+	if (_data.cs_route_number < 0)
+		LM_INFO("No cnxcc:call-shutdown event route found");
+
+	if (_data.cs_route_number > 0 && event_rt.rlist[_data.cs_route_number] == NULL)
+	{
+		LM_INFO("cnxcc:call-shutdown route is empty");
+		_data.cs_route_number	= -1;
+	}
+
+	if (_data.check_period <= 0)
+	{
+		LM_INFO("credit_check_period cannot be less than 1 second");
+		return -1;
+	}
+
+	_data.time.credit_data_by_client	= shm_malloc(sizeof(struct str_hash_table));
+	_data.time.call_data_by_cid 		= shm_malloc(sizeof(struct str_hash_table));
+	_data.money.credit_data_by_client	= shm_malloc(sizeof(struct str_hash_table));
+	_data.money.call_data_by_cid 		= shm_malloc(sizeof(struct str_hash_table));
+
+	_data.stats							= (stats_t *) shm_malloc(sizeof(stats_t));
+
+	if (!_data.stats)
+	{
+		LM_ERR("Error allocating shared memory stats\n");
+		return -1;
+	}
+
+	_data.stats->active		= 0;
+	_data.stats->dropped	= 0;
+	_data.stats->total		= 0;
+
+	if (init_hashtable(_data.time.credit_data_by_client) != 0)
+		return -1;
+
+	if (init_hashtable(_data.time.call_data_by_cid) != 0)
+		return -1;
+
+	if (init_hashtable(_data.money.credit_data_by_client) != 0)
+		return -1;
+
+	if (init_hashtable(_data.money.call_data_by_cid) != 0)
+		return -1;
+
+	lock_init(&_data.lock);
+	lock_init(&_data.time.lock);
+	lock_init(&_data.money.lock);
+
+	register_mi_cmd(mi_credit_control_stats, "cnxcc_stats", NULL, NULL, 0);
+
+	/*
+	 * One for time based monitoring
+	 * One for money based monitoring
+	 */
+	register_dummy_timers(NUMBER_OF_TIMERS);
+
+	if (rpc_register_array(ul_rpc) != 0)
+	{
+		LM_ERR("Failed registering RPC commands\n");
+		return -1;
+	}
+
+	if (load_dlg_api(&_dlgbinds) != 0)
+	{
+		LM_ERR("Error loading dialog API\n");
+	    return -1;
+	}
+
+	_dlgbinds.register_dlgcb(NULL, DLGCB_CREATED, dialog_created_callback, NULL, NULL);
+
+	return 0;
+}
+
+static int child_init(int rank)
+{
+	if (rank != PROC_MAIN)
+		return 0;
+
+
+	if(fork_dummy_timer(PROC_TIMER, "CNXCC TB TIMER", 1,
+			check_calls_by_money, NULL, _data.check_period) < 0)
+	{
+		LM_ERR("failed to register TB TIMER routine as process\n");
+		return -1;
+	}
+
+	if(fork_dummy_timer(PROC_TIMER, "CNXCC MB TIMER", 1,
+								check_calls_by_time, NULL, _data.check_period) < 0)
+	{
+		LM_ERR("failed to register MB TIMER routine as process\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int init_hashtable(struct str_hash_table *ht)
+{
+	if (shm_str_hash_alloc(ht, HT_SIZE) != 0)
+	{
+		LM_ERR("Error allocating shared memory hashtable\n");
+		return -1;
+	}
+
+	str_hash_init(ht);
+
+	return 0;
+}
+
+static void dialog_created_callback(struct dlg_cell *cell, int type, struct dlg_cb_params *params)
+{
+	struct sip_msg *msg	= NULL;
+
+	msg	= params->direction == SIP_REPLY ? params->rpl : params->req;
+
+	if (msg == NULL)
+	{
+		LM_ERR("Error getting direction of SIP msg\n");
+		return;
+	}
+
+	if (isflagset(msg, _data.ctrl_flag) == -1)
+	{
+		LM_DBG("Flag is not set for this message. Ignoring\n");
+		return;
+	}
+
+	LM_DBG("Dialog created for CID [%.*s]", cell->callid.len, cell->callid.s);
+
+	_dlgbinds.register_dlgcb(cell, DLGCB_CONFIRMED, dialog_confirmed_callback, NULL, NULL);
+	_dlgbinds.register_dlgcb(cell, DLGCB_TERMINATED|DLGCB_FAILED|DLGCB_EXPIRED, dialog_terminated_callback, NULL, NULL);
+
+	setup_billing(&cell->callid, cell->h_entry, cell->h_id);
+}
+
+static void dialog_confirmed_callback(struct dlg_cell *cell, int type, struct dlg_cb_params *params)
+{
+	LM_DBG("Dialog confirmed for CID [%.*s]", cell->callid.len, cell->callid.s);
+
+	start_billing(&cell->callid, cell->tag);
+}
+
+static void dialog_terminated_callback(struct dlg_cell *cell, int type, struct dlg_cb_params *params)
+{
+	LM_DBG("Dialog terminated for CID [%.*s]", cell->callid.len, cell->callid.s);
+
+	stop_billing(&cell->callid);
+}
+
+static void notify_call_termination(str *callid, str *from_tag, str *to_tag)
+{
+	struct run_act_ctx ra_ctx;
+	struct sip_msg *msg;
+
+	if (_data.cs_route_number < 0)
+		return;
+
+	if (faked_msg_init_with_dlg_info(callid, from_tag, to_tag,  &msg) != 0)
+	{
+		LM_ERR("[%.*s]: error generating faked sip message\n", callid->len, callid->s);
+		return;
+	}
+
+	init_run_actions_ctx(&ra_ctx);
+	//run_top_route(event_rt.rlist[_data.cs_route_number], msg, &ra_ctx);
+
+	if (run_actions(&ra_ctx, event_rt.rlist[_data.cs_route_number], msg) < 0)
+		LM_ERR("Error executing cnxcc:call-shutdown route");
+}
+
+int try_get_credit_data_entry(str *client_id, credit_data_t **credit_data)
+{
+	struct str_hash_entry *cd_entry	= NULL;
+	hash_tables_t *hts				= NULL;
+	*credit_data					= NULL;
+
+	hts					= &_data.money;
+	lock_get(&hts->lock);
+
+	cd_entry			= str_hash_get(hts->credit_data_by_client, client_id->s, client_id->len);
+
+	if (cd_entry != NULL)
+	{
+		*credit_data	= cd_entry->u.p;
+		lock_release(&hts->lock);
+		return 0;
+	}
+
+	lock_release(&hts->lock);
+
+	hts					= &_data.time;
+	lock_get(&hts->lock);
+
+	cd_entry			= str_hash_get(hts->call_data_by_cid, client_id->s, client_id->len);
+
+	if (cd_entry != NULL)
+	{
+		*credit_data	= cd_entry->u.p;
+		lock_release(&hts->lock);
+		return 0;
+	}
+
+	lock_release(&hts->lock);
+
+	return -1;
+}
+
+int try_get_call_entry(str *callid, call_t **call, hash_tables_t **hts)
+{
+	struct str_hash_entry *call_entry	= NULL;
+
+	*call					= NULL;
+
+	*hts					= &_data.money;
+	lock_get(&(*hts)->lock);
+
+	call_entry			= str_hash_get((*hts)->call_data_by_cid, callid->s, callid->len);
+
+	if (call_entry != NULL)
+	{
+		*call	= call_entry->u.p;
+		lock_release(&(*hts)->lock);
+		return 0;
+	}
+
+	lock_release(&(*hts)->lock);
+
+	*hts				= &_data.time;
+	lock_get(&(*hts)->lock);
+
+	call_entry			= str_hash_get((*hts)->call_data_by_cid, callid->s, callid->len);
+
+	if (call_entry != NULL)
+	{
+		*call	= call_entry->u.p;
+		lock_release(&(*hts)->lock);
+		return 0;
+	}
+
+	lock_release(&(*hts)->lock);
+
+	return -1;
+}
+
+static void stop_billing(str *callid)
+{
+	struct str_hash_entry *cd_entry		= NULL;
+	call_t *call						= NULL;
+	hash_tables_t *hts					= NULL;
+	credit_data_t *credit_data			= NULL;
+
+	/*
+	 * Search call data by call-id
+	 */
+	if (try_get_call_entry(callid, &call, &hts) != 0)
+	{
+		LM_ERR("Call [%.*s] not found", callid->len, callid->s);
+		return;
+	}
+
+	if (call == NULL)
+	{
+		LM_ERR("[%.*s] call pointer is null", callid->len, callid->s);
+		return;
+	}
+
+	if (hts == NULL)
+	{
+		LM_ERR("[%.*s] result hashtable pointer is null", callid->len, callid->s);
+		return;
+	}
+
+	lock_get(&hts->lock);
+
+	/*
+	 * Search credit_data by client_id
+	 */
+	cd_entry			= str_hash_get(hts->credit_data_by_client, call->client_id.s, call->client_id.len);
+
+	if (cd_entry == NULL)
+	{
+		LM_ERR("Credit data not found for CID [%.*s], client-ID [%.*s]\n", callid->len, callid->s, call->client_id.len, call->client_id.s);
+		lock_release(&hts->lock);
+		return;
+	}
+
+	credit_data	= (credit_data_t *) cd_entry->u.p;
+
+	if (credit_data == NULL)
+	{
+		LM_ERR("[%.*s]: credit_data pointer is null", callid->len, callid->s);
+		lock_release(&hts->lock);
+		return;
+	}
+
+	lock_release(&hts->lock);
+
+	/*
+	 * Update calls statistics
+	 */
+	lock_get(&_data.lock);
+
+	_data.stats->active--;
+	_data.stats->total--;
+
+	lock_release(&_data.lock);
+
+	lock(&credit_data->lock);
+
+	LM_DBG("Call [%.*s] of client-ID [%.*s], ended\n", callid->len, callid->s, call->client_id.len, call->client_id.s);
+
+	/*
+	 * This call just ended and we need to remove it from the summ.
+	 */
+	if (call->confirmed)
+	{
+		credit_data->concurrent_calls--;
+		credit_data->ended_calls_consumed_amount += call->consumed_amount;
+	}
+
+	credit_data->number_of_calls--;
+
+	if (credit_data->concurrent_calls < 0)
+	{
+		LM_ERR("[BUG]: number of concurrent calls dropped to negative value: %d", credit_data->concurrent_calls);
+	}
+
+	if (credit_data->number_of_calls < 0)
+	{
+		LM_ERR("[BUG]: number of calls dropped to negative value: %d", credit_data->number_of_calls);
+	}
+
+	/*
+	 * Remove (and free) the call from the list of calls of the current credit_data
+	 */
+	clist_rm(call, next, prev);
+	free_call(call);
+
+	/*
+	 * In case there are no active calls for a certain client, we remove the client-id from the hash table.
+	 * This way, we can save memory for useful clients.
+	 */
+	if (credit_data->number_of_calls == 0)
+	{
+		LM_DBG("Removing client [%.*s] and its calls from the list\n", credit_data->call_list->client_id.len, credit_data->call_list->client_id.s);
+
+		lock(&hts->lock);
+		/*
+		 * Remove the credit_data_t from the hash table
+		 */
+		str_hash_del(cd_entry);
+
+		lock_release(&hts->lock);
+
+		/*
+		 * Free client_id in list's root
+		 */
+		shm_free(credit_data->call_list->client_id.s);
+		shm_free(credit_data->call_list);
+
+		/*
+		 * Release the lock since we are going to free the entry down below
+		 */
+		lock_release(&credit_data->lock);
+
+		/*
+		 * Free the whole entry
+		 */
+		free_credit_data_hash_entry(cd_entry);
+
+		/*
+		 * return without releasing the acquired lock over credit_data. Why? Because we just freed it.
+		 */
+		return;
+	}
+
+	lock_release(&credit_data->lock);
+}
+
+static void setup_billing(str *callid, unsigned int h_entry, unsigned int h_id)
+{
+	call_t *call						= NULL;
+	hash_tables_t *hts					= NULL;
+
+	LM_DBG("Creating dialog for [%.*s], h_id [%u], h_entry [%u]", callid->len, callid->s, h_id, h_entry);
+
+//	lock_get(&_data.lock);
+
+	/*
+	 * Search call data by call-id
+	 */
+	if (try_get_call_entry(callid, &call, &hts) != 0)
+	{
+		LM_ERR("Call [%.*s] not found", callid->len, callid->s);
+		return;
+	}
+
+	if (call == NULL)
+	{
+		LM_ERR("[%.*s] call pointer is null", callid->len, callid->s);
+		return;
+	}
+
+	if (hts == NULL)
+	{
+		LM_ERR("[%.*s] result hashtable pointer is null", callid->len, callid->s);
+		return;
+	}
+
+	/*
+	 * Update calls statistics
+	 */
+	lock_get(&_data.lock);
+
+	_data.stats->active++;
+	_data.stats->total++;
+
+	lock_release(&_data.lock);
+
+	lock_get(&call->lock);
+
+	call->dlg_h_entry		= h_entry;
+	call->dlg_h_id			= h_id;
+
+	LM_DBG("Call [%.*s] from client [%.*s], created\n", callid->len, callid->s, call->client_id.len, call->client_id.s);
+
+	lock_release(&call->lock);
+}
+
+static void start_billing(str *callid, str tags[2])
+{
+	struct str_hash_entry *cd_entry		= NULL;
+	call_t *call						= NULL;
+	hash_tables_t *hts					= NULL;
+	credit_data_t *credit_data			= NULL;
+
+	LM_DBG("Billing started for call [%.*s]", callid->len, callid->s);
+
+//	lock_get(&_data.lock);
+
+	/*
+	 * Search call data by call-id
+	 */
+	if (try_get_call_entry(callid, &call, &hts) != 0)
+	{
+		LM_ERR("Call [%.*s] not found", callid->len, callid->s);
+		return;
+	}
+
+	if (call == NULL)
+	{
+		LM_ERR("[%.*s] call pointer is null", callid->len, callid->s);
+		return;
+	}
+
+	if (hts == NULL)
+	{
+		LM_ERR("[%.*s] result hashtable pointer is null", callid->len, callid->s);
+		return;
+	}
+
+	lock_get(&hts->lock);
+
+	/*
+	 * Search credit_data by client_id
+	 */
+	cd_entry			= str_hash_get(hts->credit_data_by_client, call->client_id.s, call->client_id.len);
+
+	if (cd_entry == NULL)
+	{
+		LM_ERR("Credit data not found for CID [%.*s], client-ID [%.*s]\n", callid->len, callid->s, call->client_id.len, call->client_id.s);
+		lock_release(&hts->lock);
+		return;
+	}
+
+	credit_data	= (credit_data_t *) cd_entry->u.p;
+
+	if (credit_data == NULL)
+	{
+		LM_ERR("[%.*s]: credit_data pointer is null", callid->len, callid->s);
+		lock_release(&hts->lock);
+		return;
+	}
+
+	lock_release(&hts->lock);
+
+	lock(&credit_data->lock);
+
+	/*
+	 * Now that the call is confirmed, we can increase the count of "concurrent_calls".
+	 * This will impact in the discount rate performed by the check_calls() function.
+	 *
+	 */
+	credit_data->concurrent_calls++;
+
+	if (credit_data->max_amount == 0)
+		credit_data->max_amount	= call->max_amount; // first time setup
+
+	if (call->max_amount > credit_data->max_amount)
+	{
+		LM_ALERT("Maximum-speak-time/credit changed, maybe a credit reload? %f > %f. Client [%.*s]", call->max_amount, credit_data->max_amount,
+																							call->client_id.len, call->client_id.s);
+
+		credit_data->max_amount += call->max_amount - credit_data->max_amount;
+	}
+
+	/*
+	 * Update max_amount, discounting what was already consumed by other calls of the same client
+	 */
+
+	call->max_amount = credit_data->max_amount - credit_data->consumed_amount;
+
+	lock_release(&credit_data->lock);
+
+	lock_get(&call->lock);
+
+	/*
+	 * Store from-tag value
+	 */
+	if (shm_str_dup(&call->sip_data.from_tag, &tags[0]) != 0)
+	{
+		LM_ERR("No more pkg memory\n");
+		goto exit;
+	}
+
+	/*
+	 * Store to-tag value
+	 */
+	if (shm_str_dup(&call->sip_data.to_tag, &tags[1]) != 0)
+	{
+		LM_ERR("No more pkg memory\n");
+		goto exit;
+	}
+
+	call->start_timestamp	= get_current_timestamp();
+	call->confirmed			= TRUE;
+
+	LM_DBG("Call [%.*s] from client [%.*s], confirmed\n", callid->len, callid->s, call->client_id.len, call->client_id.s);
+
+exit:
+	lock_release(&call->lock);
+}
+
+
+void terminate_all_calls(credit_data_t *credit_data)
+{
+	call_t 	*call 	= NULL,
+			*tmp 	= NULL;
+
+	clist_foreach_safe(credit_data->call_list, call, tmp, next)
+	{
+		LM_DBG("Killing call with CID [%.*s]\n", call->sip_data.callid.len, call->sip_data.callid.s);
+
+		/*
+		 * Update number of calls forced to end
+		 */
+		_data.stats->dropped++;
+
+		terminate_call(call);
+	}
+}
+
+/*
+ * WARNING: When calling this function, the proper lock should have been acquired
+ */
+static void free_call(call_t *call)
+{
+	struct str_hash_entry *e	= NULL;
+
+	LM_DBG("Freeing call [%.*s]\n", call->sip_data.callid.len, call->sip_data.callid.s);
+
+	e			= str_hash_get(_data.money.call_data_by_cid, call->sip_data.callid.s, call->sip_data.callid.len);
+
+	if (e == NULL)
+	{
+		e			= str_hash_get(_data.time.call_data_by_cid, call->sip_data.callid.s, call->sip_data.callid.len);
+
+		if (e == NULL)
+		{
+			LM_ERR("Call [%.*s] not found. Couldn't be able to free it from hashtable", call->sip_data.callid.len, call->sip_data.callid.s);
+			return;
+		}
+	}
+
+	str_hash_del(e);
+
+	shm_free(e->key.s);
+	shm_free(e);
+
+	str_shm_free_if_not_null(call->sip_data.callid);
+	str_shm_free_if_not_null(call->sip_data.to_tag);
+	str_shm_free_if_not_null(call->sip_data.from_tag);
+
+	shm_free(call);
+}
+
+/*
+ * WARNING: When calling this function, the proper lock should have been acquired
+ */
+static void free_credit_data_hash_entry(struct str_hash_entry *e)
+{
+	shm_free(e->key.s);
+//	shm_free(((credit_data_t *) e->u.p)->call);
+	shm_free(e->u.p);
+	shm_free(e);
+}
+
+static int shm_str_hash_alloc(struct str_hash_table *ht, int size)
+{
+	ht->table	= shm_malloc(sizeof(struct str_hash_head) * size);
+
+	if (!ht->table)
+		return -1;
+
+	ht->size	= size;
+	return 0;
+}
+
+static credit_data_t *get_or_create_credit_data_entry(str *client_id, credit_type_t type)
+{
+	struct str_hash_table *ht	= type == CREDIT_MONEY ? _data.money.credit_data_by_client : _data.time.credit_data_by_client;
+	gen_lock_t *lock			= type == CREDIT_MONEY ? &_data.money.lock : &_data.time.lock;
+	struct str_hash_entry *e	= NULL;
+
+	lock_get(lock);
+	e							= str_hash_get(ht, client_id->s, client_id->len);
+	lock_release(lock);
+
+	/*
+	 * Alloc new call_array_t if it doesn't exist
+	 */
+	if (e != NULL)
+	{
+		LM_DBG("Found key %.*s in hash table\n", e->key.len, e->key.s);
+	}
+	else
+	{
+		credit_data_t *credit_data	= NULL;
+		e							= shm_malloc(sizeof(struct str_hash_entry));
+
+		if (e == NULL)
+		{
+			LM_ERR("No shared memory left\n");
+			return NULL;
+		}
+
+		if (shm_str_dup(&e->key, client_id) != 0)
+		{
+			LM_ERR("No shared memory left\n");
+			return NULL;
+		}
+
+		e->flags					= 0;
+		e->u.p						= (void *) shm_malloc(sizeof(credit_data_t));
+		credit_data					= (credit_data_t *) e->u.p;
+
+		lock_init(&credit_data->lock);
+
+		credit_data->call_list 		= shm_malloc(sizeof(call_t));
+
+		if (credit_data->call_list == NULL)
+		{
+			LM_ERR("No shared memory left\n");
+			return NULL;
+		}
+
+		credit_data->max_amount					= 0;
+		credit_data->concurrent_calls			= 0;
+		credit_data->consumed_amount			= 0;
+		credit_data->ended_calls_consumed_amount= 0;
+		credit_data->number_of_calls			= 0;
+
+		credit_data->type						= type;
+
+		/*
+		 * Copy the client_id value to the root of the calls list.
+		 * This will be used later to get the credit_data_t of the
+		 * call when it is being searched by call ID.
+		 */
+		if (shm_str_dup(&credit_data->call_list->client_id, client_id) != 0)
+		{
+			LM_ERR("No shared memory left\n");
+			return NULL;
+		}
+
+		clist_init(credit_data->call_list, next, prev);
+
+		lock_get(lock);
+		str_hash_add(ht, e);
+		lock_release(lock);
+
+		LM_DBG("Call didn't exist. Allocated new entry\n");
+	}
+
+	return (credit_data_t *) e->u.p;
+}
+
+int terminate_call(call_t *call)
+{
+	LM_DBG("Got kill signal for call [%.*s] client [%.*s] h_id [%u] h_entry [%u]. Dropping it now\n",
+						call->sip_data.callid.len,
+						call->sip_data.callid.s,
+						call->client_id.len,
+						call->client_id.s,
+						call->dlg_h_id,
+						call->dlg_h_entry);
+
+	struct mi_root *root, *result	= NULL;
+	struct mi_node *node, *node1	= NULL;
+	struct mi_cmd *end_dlg_cmd		= NULL;
+
+	root	= init_mi_tree(0, 0, 0);
+	if (root == NULL)
+	{
+		LM_ERR("Error initializing tree to terminate call\n");
+		goto error;
+	}
+
+	node	= &root->node;
+
+	node1	= addf_mi_node_child(node, MI_DUP_VALUE, MI_SSTR("h_entry"), "%u", call->dlg_h_entry);
+	if (node1 == NULL)
+	{
+		LM_ERR("Error initializing h_entry node to terminate call\n");
+		goto error;
+	}
+
+	node1	= addf_mi_node_child(node, MI_DUP_VALUE, MI_SSTR("h_id"), "%u", call->dlg_h_id);
+	if (node1 == NULL)
+	{
+		LM_ERR("Error initializing dlg_h_id node to terminate call\n");
+		goto error;
+	}
+
+	end_dlg_cmd = lookup_mi_cmd(MI_SSTR("dlg_end_dlg"));
+	if (node == NULL)
+	{
+		LM_ERR("Error initializing dlg_end_dlg command\n");
+		goto error;
+	}
+
+	result		= run_mi_cmd(end_dlg_cmd, root);
+	if (result == NULL)
+	{
+		LM_ERR("Error executing dlg_end_dlg command\n");
+		goto error;
+	}
+
+	if (result->code == 200)
+	{
+		LM_DBG("dlg_end_dlg sent to call [%.*s]\n", call->sip_data.callid.len, call->sip_data.callid.s);
+		free_mi_tree(root);
+		free_mi_tree(result);
+
+		notify_call_termination(&call->sip_data.callid, &call->sip_data.from_tag, &call->sip_data.to_tag);
+
+		return 0;
+	}
+
+	LM_ERR("Error executing dlg_end_dlg command. Return code was [%d]\n", result->code);
+error:
+	if (root)
+		free_mi_tree(root);
+
+	return -1;
+}
+
+static call_t *alloc_new_call_by_money(credit_data_t *credit_data,
+										struct sip_msg *msg, double credit, double cost_per_second, int initial_pulse, int final_pulse)
+{
+	call_t *call		= NULL;
+
+	lock_get(&credit_data->lock);
+
+	if (credit_data->call_list == NULL)
+	{
+		LM_ERR("Credit data call list is NULL\n");
+		goto error;
+	}
+
+	call 				= shm_malloc(sizeof(call_t));
+	if (call == NULL)
+	{
+		LM_ERR("No shared memory left\n");
+		goto error;
+	}
+
+	if ( (!msg->callid && parse_headers(msg, HDR_CALLID_F, 0) != 0) ||
+		   shm_str_dup(&call->sip_data.callid, &msg->callid->body) != 0 )
+	{
+		LM_ERR("Error processing CALLID hdr\n");
+		goto error;
+	}
+
+	call->sip_data.to_tag.s		= NULL;
+	call->sip_data.to_tag.len 	= 0;
+	call->sip_data.from_tag.s	= NULL;
+	call->sip_data.from_tag.len = 0;
+
+	call->consumed_amount		= initial_pulse * cost_per_second;
+	call->confirmed				= FALSE;
+	call->max_amount			= credit;
+
+	call->money_based.cost_per_second	= cost_per_second;
+	call->money_based.initial_pulse		= initial_pulse;
+	call->money_based.final_pulse		= final_pulse;
+
+	/*
+	 * Reference the client_id from the root of the list
+	 */
+	call->client_id.s			= credit_data->call_list->client_id.s;
+	call->client_id.len			= credit_data->call_list->client_id.len;
+
+	/*
+	 * Insert the newly created call to the list of calls
+	 */
+	clist_insert(credit_data->call_list, call, next, prev);
+
+	lock_init(&call->lock);
+
+	/*
+	 * Increase the number of calls for this client. This call is not yet confirmed.
+	 */
+	credit_data->number_of_calls++;
+
+	lock_release(&credit_data->lock);
+
+	LM_DBG("New call allocated for client [%.*s]\n", call->client_id.len, call->client_id.s);
+
+	return call;
+
+error:
+	lock_release(&credit_data->lock);
+	return NULL;
+}
+
+static call_t *alloc_new_call_by_time(credit_data_t *credit_data, struct sip_msg *msg, int max_secs)
+{
+	call_t *call		= NULL;
+
+	lock_get(&credit_data->lock);
+
+	if (credit_data->call_list == NULL)
+	{
+		LM_ERR("Credit data call list is NULL\n");
+		goto error;
+	}
+
+	call 				= shm_malloc(sizeof(call_t));
+	if (call == NULL)
+	{
+		LM_ERR("No shared memory left\n");
+		goto error;
+	}
+
+	if ( (!msg->callid && parse_headers(msg, HDR_CALLID_F, 0) != 0) ||
+		   shm_str_dup(&call->sip_data.callid, &msg->callid->body) != 0 )
+	{
+		LM_ERR("Error processing CALLID hdr\n");
+		goto error;
+	}
+
+	call->sip_data.to_tag.s		= NULL;
+	call->sip_data.to_tag.len 	= 0;
+	call->sip_data.from_tag.s	= NULL;
+	call->sip_data.from_tag.len = 0;
+
+	call->consumed_amount		= 0;
+	call->confirmed				= FALSE;
+	call->max_amount			= max_secs;
+
+	/*
+	 * Reference the client_id from the root of the list
+	 */
+	call->client_id.s			= credit_data->call_list->client_id.s;
+	call->client_id.len			= credit_data->call_list->client_id.len;
+
+	/*
+	 * Insert the newly created call to the list of calls
+	 */
+	clist_insert(credit_data->call_list, call, next, prev);
+
+	lock_init(&call->lock);
+
+	/*
+	 * Increase the number of calls for this client. This call is not yet confirmed.
+	 */
+	credit_data->number_of_calls++;
+
+	lock_release(&credit_data->lock);
+
+	LM_DBG("New call allocated for client [%.*s]\n", call->client_id.len, call->client_id.s);
+
+	return call;
+
+error:
+	lock_release(&credit_data->lock);
+	return NULL;
+}
+
+static int add_call_by_cid(str *cid, call_t *call, credit_type_t type)
+{
+	struct str_hash_entry *e	= NULL;
+	struct str_hash_table *ht	= type == CREDIT_MONEY ? _data.money.call_data_by_cid : _data.time.call_data_by_cid;
+	gen_lock_t *lock			= type == CREDIT_MONEY ? &_data.money.lock : &_data.time.lock;
+
+	e	= str_hash_get(ht, cid->s, cid->len);
+
+	if (e != NULL)
+	{
+		LM_DBG("e != NULL\n");
+
+		call_t *value	= (call_t *) e->u.p;
+
+		if (value == NULL)
+		{
+			LM_ERR("Value of CID [%.*s] is NULL\n", cid->len, cid->s);
+			return -1;
+		}
+
+		LM_WARN("value cid: len=%d | value [%.*s]", value->sip_data.callid.len, value->sip_data.callid.len, value->sip_data.callid.s);
+		LM_WARN("added cid: len=%d | value [%.*s]", cid->len, cid->len, cid->s);
+
+		if (value->sip_data.callid.len != cid->len ||
+			strncasecmp(value->sip_data.callid.s, cid->s, cid->len) != 0)
+		{
+			LM_ERR("Value of CID is [%.*s] and differs from value being added [%.*s]\n", cid->len, cid->s,
+																			value->sip_data.callid.len, value->sip_data.callid.s);
+			return -1;
+		}
+
+		LM_DBG("CID already present\n");
+
+		return 0;
+	}
+
+	e	= shm_malloc(sizeof(struct str_hash_entry));
+
+	if (e == NULL)
+	{
+		LM_ERR("No shared memory left\n");
+		return -1;
+	}
+
+	if (shm_str_dup(&e->key, cid) != 0)
+	{
+		LM_ERR("No shared memory left\n");
+		return -1;
+	}
+
+	e->u.p		= call;
+
+	lock_get(lock);
+	str_hash_add(ht, e);
+	lock_release(lock);
+
+	return 0;
+}
+
+static inline void set_ctrl_flag(struct sip_msg* msg)
+{
+	if (_data.ctrl_flag != -1)
+	{
+		LM_DBG("Flag set!\n");
+		setflag(msg, _data.ctrl_flag);
+	}
+}
+
+static inline int get_pv_value(struct sip_msg* msg, pv_spec_t* spec, pv_value_t* value)
+{
+	if (pv_get_spec_value(msg, spec, value) != 0)
+	{
+		LM_ERR("Can't get PV's value\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int set_max_credit(struct sip_msg* msg,
+							char *str_pv_client,
+							char *str_pv_credit, char *str_pv_cps,
+							char *str_pv_inip, char *str_pv_finp)
+{
+	credit_data_t *credit_data 	= NULL;
+	call_t *call				= NULL;
+
+	pv_spec_t *client_id_spec		= (pv_spec_t *) str_pv_client,
+			  *credit_spec			= (pv_spec_t *) str_pv_credit,
+			  *cps_spec				= (pv_spec_t *) str_pv_cps,
+			  *initial_pulse_spec	= (pv_spec_t *) str_pv_inip,
+			  *final_pulse_spec		= (pv_spec_t *) str_pv_finp;
+
+	pv_value_t client_id_val,
+				credit_val,
+				cps_val,
+				initial_pulse_val,
+				final_pulse_val;
+
+	double credit					= 0,
+		   cost_per_second			= 0;
+
+	unsigned int initial_pulse		= 0,
+			final_pulse				= 0;
+
+	if (msg->first_line.type == SIP_REQUEST && msg->first_line.u.request.method_value == METHOD_INVITE)
+	{
+		if (has_to_tag(msg))
+		{
+			LM_ERR("INVITE is a reINVITE\n");
+			return -1;
+		}
+
+		if (pv_get_spec_value(msg, client_id_spec, &client_id_val) != 0)
+		{
+			LM_ERR("Can't get client_id's value\n");
+			return -1;
+		}
+
+		if (pv_get_spec_value(msg, credit_spec, &credit_val) != 0)
+		{
+			LM_ERR("Can't get credit's value\n");
+			return -1;
+		}
+
+		credit	= str2double(&credit_val.rs);
+
+		if (credit <= 0)
+		{
+			LM_ERR("credit value must be > 0: %f", credit);
+			return -1;
+		}
+
+		if (pv_get_spec_value(msg, cps_spec, &cps_val) != 0)
+		{
+			LM_ERR("Can't get cost_per_sec's value\n");
+			return -1;
+		}
+
+		cost_per_second	= str2double(&cps_val.rs);
+
+		if (cost_per_second <= 0)
+		{
+			LM_ERR("cost_per_second value must be > 0: %f", cost_per_second);
+			return -1;
+		}
+
+		if (pv_get_spec_value(msg, initial_pulse_spec, &initial_pulse_val) != 0)
+		{
+			LM_ERR("Can't get initial_pulse's value\n");
+			return -1;
+		}
+
+		if (str2int(&initial_pulse_val.rs, &initial_pulse) != 0)
+		{
+			LM_ERR("initial_pulse value is invalid: %.*s", initial_pulse_val.rs.len, initial_pulse_val.rs.s);
+			return -1;
+		}
+
+		if (pv_get_spec_value(msg, final_pulse_spec, &final_pulse_val) != 0)
+		{
+			LM_ERR("Can't get final_pulse's value\n");
+			return -1;
+		}
+
+		if (str2int(&final_pulse_val.rs, &final_pulse) != 0)
+		{
+			LM_ERR("final_pulse value is invalid: %.*s", final_pulse_val.rs.len, final_pulse_val.rs.s);
+			return -1;
+		}
+
+		if (client_id_val.rs.len == 0 || client_id_val.rs.s == NULL)
+		{
+			LM_ERR("[%.*s]: client ID cannot be null\n", msg->callid->body.len, msg->callid->body.s);
+			return -1;
+		}
+
+		LM_DBG("Setting up new call for client [%.*s], max-credit[%f], "
+				"cost-per-sec[%f], initial-pulse [%d], "
+				"final-pulse [%d], call-id[%.*s]\n", client_id_val.rs.len, client_id_val.rs.s,
+													 credit,
+													 cost_per_second, initial_pulse,
+													 final_pulse, msg->callid->body.len, msg->callid->body.s);
+		set_ctrl_flag(msg);
+
+		if ((credit_data = get_or_create_credit_data_entry(&client_id_val.rs, CREDIT_MONEY)) == NULL)
+		{
+			LM_ERR("Error retrieving credit data from shared memory for client [%.*s]\n", client_id_val.rs.len, client_id_val.rs.s);
+			return -1;
+		}
+
+		if ((call = alloc_new_call_by_money(credit_data, msg, credit, cost_per_second, initial_pulse, final_pulse)) == NULL)
+		{
+			LM_ERR("Unable to allocate new call for client [%.*s]\n", client_id_val.rs.len, client_id_val.rs.s);
+			return -1;
+		}
+
+		if (add_call_by_cid(&call->sip_data.callid, call, CREDIT_MONEY) != 0)
+		{
+			LM_ERR("Unable to allocate new cid_by_client for client [%.*s]\n", client_id_val.rs.len, client_id_val.rs.s);
+			return -1;
+		}
+	}
+	else
+	{
+		LM_ALERT("MSG was not a request\n");
+		return -1;
+	}
+
+	return 1;
+}
+
+static int set_max_time(struct sip_msg* msg, char* str_pv_client, char* str_pv_maxsecs)
+{
+	credit_data_t *credit_data 	= NULL;
+	call_t *call				= NULL;
+	pv_spec_t *max_secs_spec	= (pv_spec_t *) str_pv_maxsecs,
+			  *client_id_spec	= (pv_spec_t *) str_pv_client;
+	pv_value_t max_secs_val, client_id_val;
+	int max_secs				= 0;
+
+	set_ctrl_flag(msg);
+
+	if (parse_headers(msg, HDR_CALLID_F, 0) != 0)
+	{
+		LM_ERR("Error parsing Call-ID");
+		return -1;
+	}
+
+	if (msg->first_line.type == SIP_REQUEST && msg->first_line.u.request.method_value == METHOD_INVITE)
+	{
+		if (has_to_tag(msg))
+		{
+			LM_ERR("INVITE is a reINVITE\n");
+			return -1;
+		}
+
+		if (pv_get_spec_value(msg, max_secs_spec, &max_secs_val) != 0)
+		{
+			LM_ERR("Can't get max_secs PV value\n");
+			return -1;
+		}
+		max_secs	= max_secs_val.ri;
+
+		if (max_secs <= 0)
+		{
+			LM_ERR("[%.*s] MAXSECS cannot be less than or equal to zero: %d\n", msg->callid->body.len, msg->callid->body.s, max_secs);
+			return -1;
+		}
+
+		if (pv_get_spec_value(msg, client_id_spec, &client_id_val) != 0)
+		{
+			LM_ERR("[%.*s]: can't get client_id PV value\n", msg->callid->body.len, msg->callid->body.s);
+			return -1;
+		}
+
+		if (client_id_val.rs.len == 0 || client_id_val.rs.s == NULL)
+		{
+			LM_ERR("[%.*s]: client ID cannot be null\n", msg->callid->body.len, msg->callid->body.s);
+			return -1;
+		}
+
+		LM_DBG("Setting up new call for client [%.*s], max-secs[%d], call-id[%.*s]\n", client_id_val.rs.len, client_id_val.rs.s,
+																		max_secs,
+																		msg->callid->body.len, msg->callid->body.s);
+
+		if ((credit_data = get_or_create_credit_data_entry(&client_id_val.rs, CREDIT_TIME)) == NULL)
+		{
+			LM_ERR("Error retrieving credit data from shared memory for client [%.*s]\n", client_id_val.rs.len, client_id_val.rs.s);
+			return -1;
+		}
+
+		if ((call = alloc_new_call_by_time(credit_data, msg, max_secs)) == NULL)
+		{
+			LM_ERR("Unable to allocate new call for client [%.*s]\n", client_id_val.rs.len, client_id_val.rs.s);
+			return -1;
+		}
+
+		if (add_call_by_cid(&call->sip_data.callid, call, CREDIT_TIME) != 0)
+		{
+			LM_ERR("Unable to allocate new cid_by_client for client [%.*s]\n", client_id_val.rs.len, client_id_val.rs.s);
+			return -1;
+		}
+	}
+	else
+	{
+		LM_ALERT("MSG was not a request\n");
+		return -1;
+	}
+
+	return 1;
+}
+
+static int has_to_tag(struct sip_msg *msg)
+{
+	if (msg->to == NULL && parse_headers(msg, HDR_TO_F, 0) != 0)
+	{
+		LM_ERR("Cannot parse to-tag\n");
+		return 0;
+	}
+
+	return !(get_to(msg)->tag_value.s == NULL || get_to(msg)->tag_value.len == 0);
+}
+
+static int pv_parse_calls_param(pv_spec_p sp, str *in)
+{
+	if (sp == NULL || in == NULL || in->len == 0)
+		return -1;
+
+	switch(in->len)
+	{
+	case 5:
+		if (strncmp("total", in->s, in->len) == 0)
+			sp->pvp.pvn.u.isname.name.n	= CNX_PV_TOTAL;
+		else
+			return -1;
+		break;
+	case 6:
+		if (strncmp("active", in->s, in->len) == 0)
+			sp->pvp.pvn.u.isname.name.n	= CNX_PV_ACTIVE;
+		else
+			return -1;
+		break;
+	case 7:
+		if (strncmp("dropped", in->s, in->len) == 0)
+			sp->pvp.pvn.u.isname.name.n	= CNX_PV_DROPPED;
+		else
+			return -1;
+		break;
+
+	}
+
+	sp->pvp.pvn.type = PV_NAME_INTSTR;
+	sp->pvp.pvn.u.isname.type = 0;
+
+	return 0;
+}
+
+static int pv_get_calls(struct sip_msg *msg, pv_param_t *param, pv_value_t *res)
+{
+	switch(param->pvn.u.isname.name.n)
+	{
+	case CNX_PV_ACTIVE:
+		return pv_get_uintval(msg, param, res, _data.stats->active);
+	case CNX_PV_TOTAL:
+		return pv_get_uintval(msg, param, res, _data.stats->total);
+	case CNX_PV_DROPPED:
+		return pv_get_uintval(msg, param, res, _data.stats->dropped);
+	default:
+		LM_ERR("Unknown PV type %d\n", param->pvn.u.isname.name.n);
+		break;
+	}
+
+	return -1;
+}
+
+static struct mi_root *mi_credit_control_stats(struct mi_root *tree, void *param)
+{
+	char *p;
+	int len;
+	struct mi_root *rpl_tree;
+	struct mi_node *node, *node1;
+
+	rpl_tree	= init_mi_tree(200, "OK", 2);
+	node		= &rpl_tree->node;
+
+	node1 = add_mi_node_child(node, 0, MI_SSTR("CNX Credit Control"), 0, 0);
+	if (node1 == NULL)
+	{
+		LM_ERR("Error creating child node\n");
+		goto error;
+	}
+
+	p	= int2str((unsigned long) _data.stats->active, &len);
+	if (p == NULL)
+	{
+		LM_ERR("Error converting INT to STR\n");
+		goto error;
+	}
+	add_mi_node_child(node1, MI_DUP_VALUE, MI_SSTR("active"), p, len);
+
+	p	= int2str((unsigned long) _data.stats->dropped, &len);
+	if (p == NULL)
+	{
+		LM_ERR("Error converting INT to STR\n");
+		goto error;
+	}
+	add_mi_node_child(node1, MI_DUP_VALUE, MI_SSTR("dropped"), p, len);
+
+	p	= int2str((unsigned long) _data.stats->total, &len);
+	if (p == NULL)
+	{
+		LM_ERR("Error converting INT to STR\n");
+		goto error;
+	}
+
+	add_mi_node_child(node1, MI_DUP_VALUE, MI_SSTR("total"), p, len);
+
+	return rpl_tree;
+
+error:
+	return init_mi_tree(500, MI_INTERNAL_ERR, MI_INTERNAL_ERR_LEN);
+}

+ 156 - 0
modules/cnxcc/cnxcc_mod.h

@@ -0,0 +1,156 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2012 Carlos Ruiz Díaz (caruizdiaz.com),
+ *                    ConexionGroup (www.conexiongroup.com)
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#ifndef _CNXCC_MOD_H
+#define _CNXCC_MOD_H
+
+// 3,778
+#include "../../locking.h"
+#include "../../str_hash.h"
+#include "../../parser/parse_rr.h"
+
+#define str_shm_free_if_not_null(_var_) if (_var_.s != NULL)  { shm_free(_var_.s); _var_.s = NULL; _var_.len = 0; }
+
+typedef struct stats
+{
+	unsigned int total;
+	unsigned int active;
+	unsigned int dropped;
+} stats_t;
+
+typedef enum cnxpvtypes
+{
+	CNX_PV_ACTIVE = 1,
+	CNX_PV_TOTAL,
+	CNX_PV_DROPPED
+} cnxpvtypes_t;
+
+typedef enum credit_type
+{
+	CREDIT_TIME,
+	CREDIT_MONEY
+} credit_type_t;
+
+typedef struct hash_tables
+{
+	struct str_hash_table *credit_data_by_client;
+	struct str_hash_table *call_data_by_cid;
+
+	gen_lock_t lock;
+} hash_tables_t;
+
+typedef struct data
+{
+	gen_lock_t lock;
+
+	hash_tables_t time;
+	hash_tables_t money;
+
+	/*struct str_hash_table *credit_data_by_client;
+	struct str_hash_table *call_data_by_cid;*/
+
+	stats_t *stats;
+
+	/*
+	 * Call Shutdown Route Number
+	 */
+	int cs_route_number;
+
+	/*
+	 * Dialog flag used to track the call
+	 */
+	flag_t ctrl_flag;
+
+	int check_period;
+	int number_of_timers;
+
+} data_t;
+
+typedef struct sip_data
+{
+	str callid;
+	str to_tag;
+	str from_tag;
+} sip_data_t;
+
+typedef struct money_spec_data
+{
+	double cost_per_second;
+	int initial_pulse;
+	int final_pulse;
+
+} money_spec_data_t;
+
+struct call;
+typedef struct call
+{
+	struct call *prev;
+	struct call *next;
+
+	gen_lock_t lock;
+
+	char confirmed;
+	double max_amount;
+	money_spec_data_t money_based;
+
+	unsigned int start_timestamp;
+	double consumed_amount;
+
+	unsigned int dlg_h_entry;
+	unsigned int dlg_h_id;
+
+	str client_id;
+
+	sip_data_t sip_data;
+} call_t;
+
+typedef struct call_array
+{
+	call_t *array;
+	int length;
+
+} call_array_t;
+
+typedef struct credit_data
+{
+	gen_lock_t lock;
+
+	double max_amount;
+	double consumed_amount;
+	double ended_calls_consumed_amount;
+	int number_of_calls;
+	int concurrent_calls;
+
+	credit_type_t type;
+
+	call_t *call_list;
+
+} credit_data_t;
+
+
+int try_get_call_entry(str *callid, call_t **call, hash_tables_t **hts);
+int try_get_credit_data_entry(str *client_id, credit_data_t **credit_data);
+int terminate_call(call_t *call);
+void terminate_all_calls(credit_data_t *credit_data);
+
+#endif /* _CNXCC_MOD_H */

+ 269 - 0
modules/cnxcc/cnxcc_rpc.c

@@ -0,0 +1,269 @@
+/*
+ * cnxcc_rpc.c
+ *
+ *  Created on: Dec 6, 2012
+ *      Author: carlos
+ */
+
+#include <stdio.h>
+
+#include "../../locking.h"
+#include "../../lock_ops.h"
+#include "../../rpc.h"
+#include "../../rpc_lookup.h"
+
+#include "cnxcc_mod.h"
+
+extern data_t _data;
+
+void rpc_kill_call(rpc_t* rpc, void* ctx)
+{
+	call_t *call;
+	hash_tables_t *hts;
+	str callid;
+
+	if (!rpc->scan(ctx, "S", &callid))
+	{
+		LM_ERR("%s: error reading RPC param\n", __FUNCTION__);
+		return;
+	}
+
+	if (try_get_call_entry(&callid, &call, &hts) != 0)
+	{
+		LM_ERR("%s: call [%.*s] not found\n", __FUNCTION__, callid.len, callid.s);
+		rpc->fault(ctx, 404, "CallID Not Found");
+		return;
+	}
+
+	if (call == NULL)
+	{
+		LM_ERR("%s: call [%.*s] is in null state\n", __FUNCTION__, callid.len, callid.s);
+		rpc->fault(ctx, 500, "Call is NULL");
+		return;
+	}
+
+	LM_ALERT("Killing call [%.*s] via XMLRPC request\n", callid.len, callid.s);
+
+	lock_get(&call->lock);
+
+	terminate_call(call);
+
+	lock_release(&call->lock);
+}
+
+void rpc_check_client_stats(rpc_t* rpc, void* ctx)
+{
+	call_t *call, *tmp;
+	int index	= 0;
+	str client_id, rows;
+	char row_buffer[512];
+	credit_data_t *credit_data;
+
+	if (!rpc->scan(ctx, "S", &client_id))
+	{
+		LM_ERR("%s: error reading RPC param\n", __FUNCTION__);
+		return;
+	}
+
+	if (try_get_credit_data_entry(&client_id, &credit_data) != 0)
+	{
+		LM_ERR("%s: client [%.*s] not found\n", __FUNCTION__, client_id.len, client_id.s);
+		rpc->fault(ctx, 404, "Not Found");
+		return;
+	}
+
+	if (credit_data == NULL)
+	{
+		LM_ERR("%s: credit data for client [%.*s] is NULL\n", __FUNCTION__, client_id.len, client_id.s);
+		rpc->fault(ctx, 500, "Internal Server Error");
+		return;
+	}
+
+	lock_get(&credit_data->lock);
+
+	if (credit_data->number_of_calls <= 0)
+	{
+		lock_release(&credit_data->lock);
+		LM_INFO("No calls for current client\n");
+		return;
+	}
+
+	rows.len = 0;
+	rows.s	 = pkg_malloc(10);
+
+	if (rows.s == NULL)
+		goto nomem;
+
+	clist_foreach_safe(credit_data->call_list, call, tmp, next)
+	{
+		int row_len = 0;
+
+		memset(row_buffer, 0, sizeof(row_buffer));
+
+		if (credit_data->type == CREDIT_MONEY)
+			snprintf(row_buffer, sizeof(row_buffer), "id:%d,confirmed:%s,local_consumed_amount:%f,global_consumed_amount:%f,local_max_amount:%f,global_max_amount:%f,call_id:%.*s,start_timestamp:%d"
+																",inip:%d,finp:%d,cps:%f;",
+																 index,
+																 call->confirmed ? "yes" : "no",
+																 call->consumed_amount,
+																 credit_data->consumed_amount,
+																 call->max_amount,
+																 credit_data->max_amount,
+																 call->sip_data.callid.len, call->sip_data.callid.s,
+																 call->start_timestamp,
+																 call->money_based.initial_pulse,
+																 call->money_based.final_pulse,
+																 call->money_based.cost_per_second
+																 );
+		else
+			snprintf(row_buffer, sizeof(row_buffer), "id:%d,confirmed:%s,local_consumed_amount:%d,global_consumed_amount:%d,local_max_amount:%d,global_max_amount:%d,call_id:%.*s,start_timestamp:%d;",
+					 	 	 	 	 	 	 	 	 	 	 	 index,
+																 call->confirmed ? "yes" : "no",
+																 (int) call->consumed_amount,
+																 (int) credit_data->consumed_amount,
+																 (int) call->max_amount,
+																 (int) credit_data->max_amount,
+																 call->sip_data.callid.len, call->sip_data.callid.s,
+																 call->start_timestamp);
+
+		row_len 	= strlen(row_buffer);
+		rows.s		= pkg_realloc(rows.s, rows.len + row_len);
+
+		if (rows.s == NULL)
+		{
+			lock_release(&credit_data->lock);
+			goto nomem;
+		}
+
+		memcpy(rows.s + rows.len, row_buffer, row_len);
+		rows.len += row_len;
+
+		index++;
+	}
+
+	lock_release(&credit_data->lock);
+
+	if (rpc->add(ctx, "S", &rows) < 0)
+	{
+		LM_ERR("%s: error creating RPC struct\n", __FUNCTION__);
+	}
+
+	if (rows.s != NULL)
+		pkg_free(rows.s);
+
+	return;
+
+nomem:
+	LM_ERR("No more pkg memory");
+	rpc->fault(ctx, 500, "No more memory\n");
+}
+
+static int iterate_over_table(hash_tables_t *hts, str *result, credit_type_t type)
+{
+	struct str_hash_entry *h_entry, *tmp;
+	char row_buffer[512];
+	int index = 0;
+
+	lock_get(&hts->lock);
+
+	if (hts->credit_data_by_client->table)
+		for(index = 0; index < hts->credit_data_by_client->size; index++)
+			clist_foreach_safe(&hts->credit_data_by_client->table[index], h_entry, tmp, next)
+			{
+				credit_data_t *credit_data	= (credit_data_t *) h_entry->u.p;
+				lock_get(&credit_data->lock);
+
+				int row_len = 0;
+
+				memset(row_buffer, 0, sizeof(row_buffer));
+
+				if (type == CREDIT_TIME)
+				{
+					snprintf(row_buffer, sizeof(row_buffer), "client_id:%.*s,"
+															 "number_of_calls:%d,"
+															 "concurrent_calls:%d,"
+															 "type:%d,"
+															 "max_amount:%d,"
+															 "consumed_amount:%d;",
+															 credit_data->call_list->client_id.len, credit_data->call_list->client_id.s,
+															 credit_data->number_of_calls,
+															 credit_data->concurrent_calls,
+															 type,
+															 (int) credit_data->max_amount,
+															 (int) credit_data->consumed_amount);
+				}
+				else if (type == CREDIT_MONEY)
+				{
+					snprintf(row_buffer, sizeof(row_buffer), "client_id:%.*s,"
+															 "number_of_calls:%d,"
+															 "concurrent_calls:%d,"
+															 "type:%d,"
+															 "max_amount:%f,"
+															 "consumed_amount:%f;",
+															 credit_data->call_list->client_id.len, credit_data->call_list->client_id.s,
+															 credit_data->number_of_calls,
+															 credit_data->concurrent_calls,
+															 type,
+															 credit_data->max_amount,
+															 credit_data->consumed_amount);
+				}
+				else
+				{
+					LM_ERR("Unknown credit type: %d", type);
+					return -1;
+				}
+
+				lock_release(&credit_data->lock);
+
+				row_len 	= strlen(row_buffer);
+				result->s	= pkg_realloc(result->s, result->len + row_len);
+
+				if (result->s == NULL)
+				{
+					lock_release(&hts->lock);
+					goto nomem;
+				}
+
+				memcpy(result->s + result->len, row_buffer, row_len);
+				result->len += row_len;
+
+			}
+
+	lock_release(&hts->lock);
+
+	return 0;
+
+nomem:
+	LM_ERR("No more pkg memory");
+	return -1;
+}
+
+void rpc_active_clients(rpc_t* rpc, void* ctx)
+{
+	str rows;
+
+	rows.s	 = pkg_malloc(10);
+
+	if (rows.s == NULL)
+		goto nomem;
+
+	rows.len = 0;
+
+	iterate_over_table(&_data.time, &rows, CREDIT_TIME);
+	iterate_over_table(&_data.money, &rows, CREDIT_MONEY);
+
+	if (!rpc->add(ctx, "S", &rows) < 0)
+	{
+		LM_ERR("%s: error creating RPC struct\n", __FUNCTION__);
+	}
+
+	if (rows.s != NULL)
+		pkg_free(rows.s);
+
+	return;
+
+nomem:
+	LM_ERR("No more pkg memory");
+	rpc->fault(ctx, 500, "No more memory\n");
+}
+

+ 16 - 0
modules/cnxcc/cnxcc_rpc.h

@@ -0,0 +1,16 @@
+/*
+ * cnxcc_rpc.h
+ *
+ *  Created on: Dec 6, 2012
+ *      Author: carlos
+ */
+
+#ifndef CNXCC_RPC_H_
+#define CNXCC_RPC_H_
+
+void rpc_active_clients(rpc_t* rpc, void* ctx);
+void rpc_kill_call(rpc_t* rpc, void* ctx);
+void rpc_active_clients(rpc_t* rpc, void* ctx);
+void rpc_check_client_stats(rpc_t* rpc, void* ctx);
+
+#endif /* CNXCC_RPC_H_ */

+ 71 - 0
modules/cnxcc/cnxcc_sip_msg_faker.c

@@ -0,0 +1,71 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2012 Carlos Ruiz Díaz (caruizdiaz.com),
+ *                    ConexionGroup (www.conexiongroup.com)
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include "../../parser/msg_parser.h"
+#include "../../globals.h"
+
+#include <sys/socket.h>
+
+#define FAKED_SIP_MSG_FORMAT "OPTIONS sip:[email protected] SIP/2.0\r\nVia: SIP/2.0/UDP 127.0.0.1\r\nFrom: <[email protected]>;tag=%.*s\r\nTo: <[email protected]>;tag=%.*s\r\nCall-ID: %.*s\r\nCSeq: 1 OPTIONS\r\nContent-Length: 0\r\n\r\n"
+
+#define FAKED_SIP_MSG_BUF_LEN	1024
+char _faked_sip_msg_buf[FAKED_SIP_MSG_BUF_LEN];
+
+static struct sip_msg _faked_msg;
+
+int faked_msg_init_with_dlg_info(str *callid, str *from_tag, str *to_tag,  struct sip_msg **msg)
+{
+	memset(_faked_sip_msg_buf, 0, FAKED_SIP_MSG_BUF_LEN);
+
+	sprintf(_faked_sip_msg_buf, FAKED_SIP_MSG_FORMAT, from_tag->len, from_tag->s,
+													  to_tag->len, to_tag->s,
+													  callid->len, callid->s);
+
+	memset(&_faked_msg, 0, sizeof(struct sip_msg));
+
+	_faked_msg.buf = _faked_sip_msg_buf;
+	_faked_msg.len = strlen(_faked_sip_msg_buf);
+
+	_faked_msg.set_global_address	= default_global_address;
+	_faked_msg.set_global_port		= default_global_port;
+
+	if (parse_msg(_faked_msg.buf, _faked_msg.len, &_faked_msg) != 0)
+	{
+			LM_ERR("parse_msg failed\n");
+			return -1;
+	}
+
+	_faked_msg.rcv.proto = PROTO_UDP;
+	_faked_msg.rcv.src_port = 5060;
+	_faked_msg.rcv.src_ip.u.addr32[0] = 0x7f000001;
+	_faked_msg.rcv.src_ip.af = AF_INET;
+	_faked_msg.rcv.src_ip.len = 4;
+	_faked_msg.rcv.dst_port = 5060;
+	_faked_msg.rcv.dst_ip.u.addr32[0] = 0x7f000001;
+	_faked_msg.rcv.dst_ip.af = AF_INET;
+	_faked_msg.rcv.dst_ip.len = 4;
+
+	*msg	= &_faked_msg;
+	return 0;
+}

+ 29 - 0
modules/cnxcc/cnxcc_sip_msg_faker.h

@@ -0,0 +1,29 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2012 Carlos Ruiz Díaz (caruizdiaz.com),
+ *                    ConexionGroup (www.conexiongroup.com)
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#ifndef CNXCC_SIP_MSG_FAKER_H_
+#define CNXCC_SIP_MSG_FAKER_H_
+
+int faked_msg_init_with_dlg_info(str *callid, str *from_tag, str *to_tag,  struct sip_msg **msg);
+
+#endif /* CNXCC_SIP_MSG_FAKER_H_ */

+ 4 - 0
modules/cnxcc/doc/Makefile

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

+ 36 - 0
modules/cnxcc/doc/cnxcc.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+    <bookinfo>
+    <title>cnxcc Module</title>
+    <productname class="trade">&kamailioname;</productname>
+    <authorgroup>
+	<author>
+	<firstname>Carlos</firstname>
+	<surname>Ruiz Diaz</surname>
+	<affiliation>
+	    <orgname>ConexionGroup SA</orgname>
+	</affiliation>
+	<address>
+	    <email>[email protected]</email>
+	</address>
+	</author>
+    </authorgroup>
+    <copyright>
+	<year>2012</year>
+	<holder>Carlos Ruiz Diaz, [email protected]</holder>
+    </copyright>
+    </bookinfo>
+
+    <toc></toc>
+
+    <xi:include href="cnxcc_admin.xml" />
+</book>

+ 299 - 0
modules/cnxcc/doc/cnxcc_admin.xml

@@ -0,0 +1,299 @@
+<?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 "../../../docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module Admin Guide -->
+
+<chapter xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>&adminguide;</title>
+
+    <section>
+	<title>Overview</title>
+	<para>
+		This module was designed to act as a mechanism to limit call duration based on credit information parameters.
+		After getting the credit information of the call being set up, you can instruct the module to start monitoring
+		the consumed credit to shutdown a single call or a group of calls in case of credit exhaustion.	
+	</para>
+	<para>
+		Every call is associated to an unique client/customer identifier. If a credit event occurs, all calls hooked
+	   	to this identifier are automatically shutdown.
+	</para>
+	<para>
+		Cnxcc is dialog-aware so there's no need to explicitly allocate/deallocate the monitoring. Only a single function
+		call inside the script is needed upon reception of the INVITE.
+	</para>
+	<para>
+		The credit discount rate is proportional to the number of calls grouped inside an identifier. Once the setup
+		of the first call is done, the information remains while the call is active. If the customer starts a new call with
+		the same routing criteria, it will land in the same monitoring bag and it will consume the same pool of credit in
+		rates that are equal to the cost per second of both calls.
+	</para>
+	<para>
+		If your accounting program does not maintain the state of the call in real time, this module can provide you
+		that ability.
+	</para>
+    </section>
+
+    <section>
+	<title>Dependencies</title>
+	<section>
+	    <title>Modules</title>
+	    <para>
+		The following module must be loaded before this module: 
+		<itemizedlist>
+		    <listitem>
+		    <para>
+			<emphasis>dialog</emphasis>
+		    </para>
+		    </listitem>
+		</itemizedlist>
+	    </para>
+	</section>
+    </section>
+
+    <section>
+	<title>Parameters</title>
+	<section>
+		<title><varname> dlg_flag </varname> (integer)</title>
+	    <para>
+		Flag to indicate if the dialog must be monitored or not. Messages are flagged with this value if we call one of
+		the monitoring functions.
+	    </para>
+	    <example>
+
+		<title>dlg_flag</title>
+		<programlisting format="linespecific">
+...
+modparam("cnxcc", "dlg_flag", 29)
+...		
+		</programlisting>
+	    </example>
+	</section>
+
+	<section>
+	    <title><varname>credit_check_period</varname> (integer)</title>
+	    <para>
+		Indicates how often the credit checking function should be called. It is directly related to the precison of the
+		module. The maximum precision is 1, which means that every call is checked every one second.
+
+	    </para>
+	    <para>
+		Values greater than 1 leads to precision lost but less CPU consumption.
+	    </para>
+	    <example>
+		<title>credit_check_period</title>
+		<programlisting format="linespecific">
+...
+modparam("cnxcc", "credit_check_period", 1)
+...		
+		</programlisting>
+	    </example>
+	</section> 
+   </section>
+
+    <section>
+	<title>Functions</title>
+	<section>
+	    <title>
+		<function moreinfo="none">cnxcc_set_max_credit()</function>
+	    </title>
+	    <para>
+		Specifies the initial pulse, final pulse, max credit and cost per second of a call. The discount
+		is calculated in pulses (30/6, 1/1, etc) and sustracted from the pool of credit.
+	    </para>
+	    <para>
+		<emphasis>Return code:</emphasis>
+		<itemizedlist>
+		    <listitem>
+		    <para>
+			<emphasis>1 - successful</emphasis>
+		    </para>
+		    </listitem>
+
+		    <listitem>
+		    <para>
+			<emphasis>-1 - failed, error logged</emphasis>
+		    </para>
+		    </listitem>
+		</itemizedlist>
+	    </para>
+	    <example>
+		<title>cnxcc_set_max_credit()</title>
+		<programlisting format="linespecific">
+...
+$var(customer) = "john-doe-123-premium";
+$var(credit) = "100";
+$var(cps)   = "2.00";         # cost per second
+$var(initial_p)   = "030";    # intial pulse
+$var(final_p)   = "006";      # final pulse
+
+cnxcc_set_max_credit("$var(customer)", "$var(credit)", "$var(cps)", "$var(initial_p)", "$var(final_p)");
+...		
+		</programlisting>
+	    </example>
+	</section>
+
+        <section>
+            <title>
+                <function moreinfo="none">cnxcc_set_max_time()</function>
+            </title>
+            <para>
+		Specifies the amount of time the call should last at most.
+            </para>
+            <para>
+                <emphasis>Return code:</emphasis>
+                <itemizedlist>
+                    <listitem>
+                    <para>
+                        <emphasis>1 - successful</emphasis>
+                    </para>
+                    </listitem>
+                     
+                    <listitem>
+                    <para>
+                        <emphasis>-1 - failed, error logged</emphasis>
+                    </para>
+                    </listitem>
+                </itemizedlist>
+            </para>
+            <example>
+		<title>cnxcc_set_max_time()</title>
+                <programlisting format="linespecific">
+...
+$var(customer) = "john-doe-123-basic";
+$var(max_time) = 120;
+
+cnxcc_set_max_tim ("$var(customer)", "$var(max_time)");
+...
+		</programlisting>
+            </example>
+        </section>
+    </section>
+    
+    <section>
+	<title>Exported RPC Commands</title>
+
+	<section>
+	    <title><varname>cnxcc.active_clients</varname></title>
+	    <para>
+		Retrieves all calls grouped by their identifiers.
+	    </para>
+	    <para>
+		Parameters: <emphasis>none</emphasis>
+	    </para>
+	    <para>
+		Example:
+	    </para>
+	    <programlisting format="linespecific">
+	    &sercmd; cnxcc.active_clients
+	    </programlisting>
+	</section>
+
+	<section>
+            <title><varname>cnxcc.check_client</varname></title>
+            <para>
+		Retrives all calls from a particular identifier.
+            </para>
+            <para>
+		    Parameters: <emphasis>client/customer identifier</emphasis>
+            </para>
+            <para>
+                Example:
+            </para>
+            <programlisting format="linespecific">
+	    &sercmd; cnxcc.check_client john-doe-123-premium
+            </programlisting>
+        </section>
+
+        <section>
+            <title><varname>cnxcc.kill_call</varname></title>
+            <para>
+		    Kills an active call using its call ID.
+	    </para>
+            <para>
+                Parameters: <emphasis>Call-ID</emphasis>
+            </para>
+            <para>
+                Example:
+            </para>
+            <programlisting format="linespecific">
+            &sercmd; cnxcc.kill_call [email protected]
+            </programlisting>
+        </section>
+    </section>
+
+    <section>
+	    <title>Events</title>
+	    
+	    <para>
+		When a call is forced to end an event route is automatically invoked. This route is suited with a fake OPTIONS
+		message containing the call ID, ftag and ttag of the original call so it can be located somehow in the accounting
+		database.
+	    </para>
+	    <para>
+                Example:
+            </para>
+            	<programlisting format="linespecific">
+...
+event_route[cnxcc:call-shutdown]
+{
+	xlog("L_INFO", "[$ci]: call killed");
+
+        # perform some kind of notification, database update, email sending, etc.
+}
+...
+		</programlisting>
+	    
+    </section>
+    <section>
+            <title>Web Interface</title>
+
+            <para>
+		The module contains a web management interface completely optional. With it, you can review your calls in real time
+		and hang them up if necessary.    
+	    </para>
+            <para>
+                Link: https://github.com/caruizdiaz/cnxcc-web
+            </para>
+    </section>
+
+    <section>
+	<title>Sample</title>
+	<example>
+	    <title>kamailio-cnxcc.cfg</title>
+	    <programlisting format="linespecific">
+...
+route[CNXCC]
+{
+	$var(client)              = "test-client-0-123-01";
+      	$var(credit)              = "50";
+      	$var(cost_per_sec)        = "0.5";
+      	$var(i_pulse)             = "30";
+      	$var(f_pulse)             = "6";
+
+      	if (!cnxcc_set_max_credit("$var(client)",
+                          "$var(credit)",
+                          "$var(cost_per_sec)",
+                          "$var(i_pulse)",
+                          "$var(f_pulse)")) {
+		 xlog("Error setting up credit control");
+      	}
+}
+
+event_route[cnxcc:call-shutdown]
+{
+	xlog("L_INFO", "[$ci]: call killed");
+
+
+}	    
+...
+	    </programlisting>
+	</example>
+    </section>
+
+</chapter>

+ 983 - 0
modules/cnxcc/example/kamailio-cnxcc.cfg

@@ -0,0 +1,983 @@
+#!KAMAILIO
+#
+# Kamailio (OpenSER) SIP Server v3.2 - default configuration script
+#     - web: http://www.kamailio.org
+#     - git: http://sip-router.org
+#
+# Direct your questions about this file to: <[email protected]>
+#
+# Refer to the Core CookBook at http://www.kamailio.org/dokuwiki/doku.php
+# for an explanation of possible statements, functions and parameters.
+#
+# Several features can be enabled using '#!define WITH_FEATURE' directives:
+#
+# *** To run in debug mode: 
+#     - define WITH_DEBUG
+#
+# *** To enable mysql: 
+#     - define WITH_MYSQL
+#
+# *** To enable authentication execute:
+#     - enable mysql
+#     - define WITH_AUTH
+#     - add users using 'kamctl'
+#
+# *** To enable IP authentication execute:
+#     - enable mysql
+#     - enable authentication
+#     - define WITH_IPAUTH
+#     - add IP addresses with group id '1' to 'address' table
+#
+# *** To enable persistent user location execute:
+#     - enable mysql
+#     - define WITH_USRLOCDB
+#
+# *** To enable presence server execute:
+#     - enable mysql
+#     - define WITH_PRESENCE
+#
+# *** To enable nat traversal execute:
+#     - define WITH_NAT
+#     - install RTPProxy: http://www.rtpproxy.org
+#     - start RTPProxy:
+#        rtpproxy -l _your_public_ip_ -s udp:localhost:7722
+#
+# *** To enable PSTN gateway routing execute:
+#     - define WITH_PSTN
+#     - set the value of pstn.gw_ip
+#     - check route[PSTN] for regexp routing condition
+#
+# *** To enable database aliases lookup execute:
+#     - enable mysql
+#     - define WITH_ALIASDB
+#
+# *** To enable speed dial lookup execute:
+#     - enable mysql
+#     - define WITH_SPEEDDIAL
+#
+# *** To enable multi-domain support execute:
+#     - enable mysql
+#     - define WITH_MULTIDOMAIN
+#
+# *** To enable TLS support execute:
+#     - adjust CFGDIR/tls.cfg as needed
+#     - define WITH_TLS
+#
+# *** To enable XMLRPC support execute:
+#     - define WITH_XMLRPC
+#     - adjust route[XMLRPC] for access policy
+#
+# *** To enable anti-flood detection execute:
+#     - adjust pike and htable=>ipban settings as needed (default is
+#       block if more than 16 requests in 2 seconds and ban for 300 seconds)
+#     - define WITH_ANTIFLOOD
+#
+# *** To block 3XX redirect replies execute:
+#     - define WITH_BLOCK3XX
+#
+# *** To enable VoiceMail routing execute:
+#     - define WITH_VOICEMAIL
+#     - set the value of voicemail.srv_ip
+#     - adjust the value of voicemail.srv_port
+#
+# *** To enhance accounting execute:
+#     - enable mysql
+#     - define WITH_ACCDB
+#     - add following columns to database
+#!ifdef ACCDB_COMMENT
+  ALTER TABLE acc ADD COLUMN src_user VARCHAR(64) NOT NULL DEFAULT '';
+  ALTER TABLE acc ADD COLUMN src_domain VARCHAR(128) NOT NULL DEFAULT '';
+  ALTER TABLE acc ADD COLUMN src_ip varchar(64) NOT NULL default '';
+  ALTER TABLE acc ADD COLUMN dst_ouser VARCHAR(64) NOT NULL DEFAULT '';
+  ALTER TABLE acc ADD COLUMN dst_user VARCHAR(64) NOT NULL DEFAULT '';
+  ALTER TABLE acc ADD COLUMN dst_domain VARCHAR(128) NOT NULL DEFAULT '';
+  ALTER TABLE missed_calls ADD COLUMN src_user VARCHAR(64) NOT NULL DEFAULT '';
+  ALTER TABLE missed_calls ADD COLUMN src_domain VARCHAR(128) NOT NULL DEFAULT '';
+  ALTER TABLE missed_calls ADD COLUMN src_ip varchar(64) NOT NULL default '';
+  ALTER TABLE missed_calls ADD COLUMN dst_ouser VARCHAR(64) NOT NULL DEFAULT '';
+  ALTER TABLE missed_calls ADD COLUMN dst_user VARCHAR(64) NOT NULL DEFAULT '';
+  ALTER TABLE missed_calls ADD COLUMN dst_domain VARCHAR(128) NOT NULL DEFAULT '';
+#!endif
+
+####### Defined Values #########
+
+# *** Value defines - IDs used later in config
+#!ifdef WITH_MYSQL
+# - database URL - used to connect to database server by modules such
+#       as: auth_db, acc, usrloc, a.s.o.
+#!define DBURL "mysql://openser:openserrw@localhost/openser"
+#!endif
+#!ifdef WITH_MULTIDOMAIN
+# - the value for 'use_domain' parameters
+#!define MULTIDOMAIN 1
+#!else
+#!define MULTIDOMAIN 0
+#!endif
+
+# - flags
+#   FLT_ - per transaction (message) flags
+#	FLB_ - per branch flags
+#!define FLT_ACC 1
+#!define FLT_ACCMISSED 2
+#!define FLT_ACCFAILED 3
+#!define FLT_NATS 5
+
+#!define FLB_NATB 6
+#!define FLB_NATSIPPING 7
+
+#!define WITH_XMLRPC
+
+####### Global Parameters #########
+
+#!ifdef WITH_DEBUG
+debug=4
+log_stderror=yes
+#!else
+debug=2
+log_stderror=no
+#!endif
+
+memdbg=5
+memlog=5
+
+log_facility=LOG_LOCAL0
+
+fork=yes
+children=4
+
+/* uncomment the next line to disable TCP (default on) */
+#disable_tcp=yes
+
+/* uncomment the next line to disable the auto discovery of local aliases
+   based on reverse DNS on IPs (default on) */
+#auto_aliases=no
+
+/* add local domain aliases */
+#alias="sip.mydomain.com"
+
+/* uncomment and configure the following line if you want Kamailio to 
+   bind on a specific interface/port/proto (default bind on all available) */
+#listen=udp:10.0.0.10:5060
+
+/* port to listen to
+ * - can be specified more than once if needed to listen on many ports */
+port=5060
+
+#!ifdef WITH_TLS
+enable_tls=yes
+#!endif
+
+# life time of TCP connection when there is no traffic
+# - a bit higher than registration expires to cope with UA behind NAT
+tcp_connection_lifetime=3605
+
+####### Custom Parameters #########
+
+# These parameters can be modified runtime via RPC interface
+# - see the documentation of 'cfg_rpc' module.
+#
+# Format: group.id = value 'desc' description
+# Access: $sel(cfg_get.group.id) or @cfg_get.group.id
+#
+
+#!ifdef WITH_PSTN
+# PSTN GW Routing
+#
+# - pstn.gw_ip: valid IP or hostname as string value, example:
+# pstn.gw_ip = "10.0.0.101" desc "My PSTN GW Address"
+#
+# - by default is empty to avoid misrouting
+pstn.gw_ip = "" desc "PSTN GW Address"
+#!endif
+
+#!ifdef WITH_VOICEMAIL
+# VoiceMail Routing on offline, busy or no answer
+#
+# - by default Voicemail server IP is empty to avoid misrouting
+voicemail.srv_ip = "" desc "VoiceMail IP Address"
+voicemail.srv_port = "5060" desc "VoiceMail Port"
+#!endif
+
+####### Modules Section ########
+
+# set paths to location of modules (to sources or installation folders)
+#!ifdef WITH_SRCPATH
+mpath="modules_k:modules"
+#!else
+mpath="/usr/local/lib/kamailio/modules_k/:/usr/local/lib/kamailio/modules/"
+#!endif
+
+#!ifdef WITH_MYSQL
+loadmodule "db_mysql.so"
+#!endif
+
+loadmodule "mi_fifo.so"
+loadmodule "kex.so"
+loadmodule "tm.so"
+loadmodule "tmx.so"
+loadmodule "sl.so"
+loadmodule "rr.so"
+loadmodule "pv.so"
+loadmodule "maxfwd.so"
+loadmodule "usrloc.so"
+loadmodule "registrar.so"
+loadmodule "textops.so"
+loadmodule "siputils.so"
+loadmodule "xlog.so"
+loadmodule "sanity.so"
+loadmodule "ctl.so"
+loadmodule "cfg_rpc.so"
+loadmodule "mi_rpc.so"
+loadmodule "acc.so"
+
+#!ifdef WITH_AUTH
+loadmodule "auth.so"
+loadmodule "auth_db.so"
+#!ifdef WITH_IPAUTH
+loadmodule "permissions.so"
+#!endif
+#!endif
+
+#!ifdef WITH_ALIASDB
+loadmodule "alias_db.so"
+#!endif
+
+#!ifdef WITH_SPEEDDIAL
+loadmodule "speeddial.so"
+#!endif
+
+#!ifdef WITH_MULTIDOMAIN
+loadmodule "domain.so"
+#!endif
+
+#!ifdef WITH_PRESENCE
+loadmodule "presence.so"
+loadmodule "presence_xml.so"
+#!endif
+
+#!ifdef WITH_NAT
+loadmodule "nathelper.so"
+loadmodule "rtpproxy.so"
+#!endif
+
+#!ifdef WITH_TLS
+loadmodule "tls.so"
+#!endif
+
+#!ifdef WITH_ANTIFLOOD
+loadmodule "htable.so"
+loadmodule "pike.so"
+#!endif
+
+#!ifdef WITH_XMLRPC
+loadmodule "xmlrpc.so"
+#!endif
+
+#!ifdef WITH_DEBUG
+loadmodule "debugger.so"
+#!endif
+
+# ----------------- setting module-specific parameters ---------------
+
+
+# ----- mi_fifo params -----
+modparam("mi_fifo", "fifo_name", "/tmp/kamailio_fifo")
+
+
+# ----- tm params -----
+# auto-discard branches from previous serial forking leg
+modparam("tm", "failure_reply_mode", 3)
+# default retransmission timeout: 30sec
+modparam("tm", "fr_timer", 30000)
+# default invite retransmission timeout after 1xx: 120sec
+modparam("tm", "fr_inv_timer", 120000)
+
+
+# ----- rr params -----
+# add value to ;lr param to cope with most of the UAs
+modparam("rr", "enable_full_lr", 1)
+# do not append from tag to the RR (no need for this script)
+modparam("rr", "append_fromtag", 0)
+
+
+# ----- registrar params -----
+modparam("registrar", "method_filtering", 1)
+/* uncomment the next line to disable parallel forking via location */
+# modparam("registrar", "append_branches", 0)
+/* uncomment the next line not to allow more than 10 contacts per AOR */
+#modparam("registrar", "max_contacts", 10)
+# max value for expires of registrations
+modparam("registrar", "max_expires", 3600)
+
+
+# ----- acc params -----
+/* what special events should be accounted ? */
+modparam("acc", "early_media", 0)
+modparam("acc", "report_ack", 0)
+modparam("acc", "report_cancels", 0)
+/* by default ww do not adjust the direct of the sequential requests.
+   if you enable this parameter, be sure the enable "append_fromtag"
+   in "rr" module */
+modparam("acc", "detect_direction", 0)
+/* account triggers (flags) */
+modparam("acc", "log_flag", FLT_ACC)
+modparam("acc", "log_missed_flag", FLT_ACCMISSED)
+modparam("acc", "log_extra", 
+	"src_user=$fU;src_domain=$fd;src_ip=$si;"
+	"dst_ouser=$tU;dst_user=$rU;dst_domain=$rd")
+modparam("acc", "failed_transaction_flag", FLT_ACCFAILED)
+/* enhanced DB accounting */
+#!ifdef WITH_ACCDB
+modparam("acc", "db_flag", FLT_ACC)
+modparam("acc", "db_missed_flag", FLT_ACCMISSED)
+modparam("acc", "db_url", DBURL)
+modparam("acc", "db_extra",
+	"src_user=$fU;src_domain=$fd;src_ip=$si;"
+	"dst_ouser=$tU;dst_user=$rU;dst_domain=$rd")
+#!endif
+
+
+# ----- usrloc params -----
+/* enable DB persistency for location entries */
+#!ifdef WITH_USRLOCDB
+modparam("usrloc", "db_url", DBURL)
+modparam("usrloc", "db_mode", 2)
+modparam("usrloc", "use_domain", MULTIDOMAIN)
+#!endif
+
+
+# ----- auth_db params -----
+#!ifdef WITH_AUTH
+modparam("auth_db", "db_url", DBURL)
+modparam("auth_db", "calculate_ha1", yes)
+modparam("auth_db", "password_column", "password")
+modparam("auth_db", "load_credentials", "")
+modparam("auth_db", "use_domain", MULTIDOMAIN)
+
+# ----- permissions params -----
+#!ifdef WITH_IPAUTH
+modparam("permissions", "db_url", DBURL)
+modparam("permissions", "db_mode", 1)
+#!endif
+
+#!endif
+
+
+# ----- alias_db params -----
+#!ifdef WITH_ALIASDB
+modparam("alias_db", "db_url", DBURL)
+modparam("alias_db", "use_domain", MULTIDOMAIN)
+#!endif
+
+
+# ----- speedial params -----
+#!ifdef WITH_SPEEDDIAL
+modparam("speeddial", "db_url", DBURL)
+modparam("speeddial", "use_domain", MULTIDOMAIN)
+#!endif
+
+
+# ----- domain params -----
+#!ifdef WITH_MULTIDOMAIN
+modparam("domain", "db_url", DBURL)
+# use caching
+modparam("domain", "db_mode", 1)
+# register callback to match myself condition with domains list
+modparam("domain", "register_myself", 1)
+#!endif
+
+
+#!ifdef WITH_PRESENCE
+# ----- presence params -----
+modparam("presence", "db_url", DBURL)
+
+# ----- presence_xml params -----
+modparam("presence_xml", "db_url", DBURL)
+modparam("presence_xml", "force_active", 1)
+#!endif
+
+
+#!ifdef WITH_NAT
+# ----- rtpproxy params -----
+modparam("rtpproxy", "rtpproxy_sock", "udp:127.0.0.1:7722")
+
+# ----- nathelper params -----
+modparam("nathelper", "natping_interval", 30)
+modparam("nathelper", "ping_nated_only", 1)
+modparam("nathelper", "sipping_bflag", FLB_NATSIPPING)
+modparam("nathelper", "sipping_from", "sip:[email protected]")
+
+# params needed for NAT traversal in other modules
+modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)")
+modparam("usrloc", "nat_bflag", FLB_NATB)
+#!endif
+
+
+#!ifdef WITH_TLS
+# ----- tls params -----
+modparam("tls", "config", "/usr/local/etc/kamailio/tls.cfg")
+#!endif
+
+#!ifdef WITH_ANTIFLOOD
+# ----- pike params -----
+modparam("pike", "sampling_time_unit", 2)
+modparam("pike", "reqs_density_per_unit", 16)
+modparam("pike", "remove_latency", 4)
+
+# ----- htable params -----
+# ip ban htable with autoexpire after 5 minutes
+modparam("htable", "htable", "ipban=>size=8;autoexpire=300;")
+#!endif
+
+#!ifdef WITH_XMLRPC
+# ----- xmlrpc params -----
+modparam("xmlrpc", "route", "XMLRPC");
+#modparam("xmlrpc", "url_match", "^/RPC")
+#!endif
+
+#!ifdef WITH_DEBUG
+# ----- debugger params -----
+modparam("debugger", "cfgtrace", 1)
+#!endif
+
+#!define DLG_FLAG 28
+#!define CC_FLAG 29
+
+loadmodule "dialog.so"
+modparam("dialog", "hash_size", 2048)
+modparam("dialog", "default_timeout", 3600)
+modparam("dialog", "db_mode", 0)
+modparam("dialog", "dlg_flag", DLG_FLAG)
+
+loadmodule "cnxcc.so"
+modparam("cnxcc", "dlg_flag", CC_FLAG)
+modparam("cnxcc", "credit_check_period", 1)
+
+####### Routing Logic ########
+
+
+# Main SIP request routing logic
+# - processing of any incoming SIP request starts with this route
+# - note: this is the same as route { ... }
+request_route {
+
+	setflag(DLG_FLAG);
+
+	# per request initial checks
+	route(REQINIT);
+
+	# NAT detection
+	route(NATDETECT);
+
+	# handle requests within SIP dialogs
+	route(WITHINDLG);
+
+	### only initial requests (no To tag)
+
+	# CANCEL processing
+	if (is_method("CANCEL"))
+	{
+		if (t_check_trans())
+			t_relay();
+		exit;
+	}
+
+	t_check_trans();
+
+	# authentication
+	route(AUTH);
+
+	# record routing for dialog forming requests (in case they are routed)
+	# - remove preloaded route headers
+	remove_hf("Route");
+	if (is_method("INVITE|SUBSCRIBE"))
+		record_route();
+
+	# account only INVITEs
+	if (is_method("INVITE"))
+	{
+		setflag(FLT_ACC); # do accounting
+	}
+
+	# dispatch requests to foreign domains
+	route(SIPOUT);
+
+	### requests for my local domains
+
+	# handle presence related requests
+	route(PRESENCE);
+
+	# handle registrations
+	route(REGISTRAR);
+
+	if ($rU==$null)
+	{
+		# request with no Username in RURI
+		sl_send_reply("484","Address Incomplete");
+		exit;
+	}
+
+	# dispatch destinations to PSTN
+	route(PSTN);
+
+	# user location service
+	route(LOCATION);
+
+	route(CNXCC);
+	
+	route(RELAY);
+}
+
+route[CNXCC]
+{
+	#
+	# In real life scenarios, all the authorization values 
+	# are retrieved from a database and calculed on-the-fly.
+	# 
+	# This hardcoded values are just for illustrative purposes
+	#
+
+	$var(client)		= "test-client-0-123-01";
+	$var(credit) 		= "50";
+	$var(cost_per_sec) 	= "0.5";
+	$var(i_pulse)		= "30";
+	$var(f_pulse)		= "6";
+
+	# if only one call is established, that call should last 1m, 36s
+
+	if (!cnxcc_set_max_credit("$var(client)",
+				  "$var(credit)", 
+				  "$var(cost_per_sec)", 
+				  "$var(i_pulse)", 
+				  "$var(f_pulse)")) {
+		xlog("Error setting up credit control");
+	}
+}
+
+event_route[cnxcc:call-shutdown]
+{
+        xlog("L_INFO", "[$ci]: call killed");
+	
+	# perform some kind of notification, database update, email sending, etc
+}
+
+route[RELAY] {
+
+	# enable additional event routes for forwarded requests
+	# - serial forking, RTP relaying handling, a.s.o.
+	if (is_method("INVITE|SUBSCRIBE")) {
+		t_on_branch("MANAGE_BRANCH");
+		t_on_reply("MANAGE_REPLY");
+	}
+	if (is_method("INVITE")) {
+		t_on_failure("MANAGE_FAILURE");
+	}
+
+	if (!t_relay()) {
+		sl_reply_error();
+	}
+	exit;
+}
+
+# Per SIP request initial checks
+route[REQINIT] {
+#!ifdef WITH_ANTIFLOOD
+	# flood dection from same IP and traffic ban for a while
+	# be sure you exclude checking trusted peers, such as pstn gateways
+	# - local host excluded (e.g., loop to self)
+	if(src_ip!=myself)
+	{
+		if($sht(ipban=>$si)!=$null)
+		{
+			# ip is already blocked
+			xdbg("request from blocked IP - $rm from $fu (IP:$si:$sp)\n");
+			exit;
+		}
+		if (!pike_check_req())
+		{
+			xlog("L_ALERT","ALERT: pike blocking $rm from $fu (IP:$si:$sp)\n");
+			$sht(ipban=>$si) = 1;
+			exit;
+		}
+	}
+#!endif
+
+	if (!mf_process_maxfwd_header("10")) {
+		sl_send_reply("483","Too Many Hops");
+		exit;
+	}
+
+	if(!sanity_check("1511", "7"))
+	{
+		xlog("Malformed SIP message from $si:$sp\n");
+		exit;
+	}
+}
+
+# Handle requests within SIP dialogs
+route[WITHINDLG] {
+	if (has_totag()) {
+		# sequential request withing a dialog should
+		# take the path determined by record-routing
+		if (loose_route()) {
+			if (is_method("BYE")) {
+				setflag(FLT_ACC); # do accounting ...
+				setflag(FLT_ACCFAILED); # ... even if the transaction fails
+			}
+			if ( is_method("ACK") ) {
+				# ACK is forwarded statelessy
+				route(NATMANAGE);
+			}
+			route(RELAY);
+		} else {
+			if (is_method("SUBSCRIBE") && uri == myself) {
+				# in-dialog subscribe requests
+				route(PRESENCE);
+				exit;
+			}
+			if ( is_method("ACK") ) {
+				if ( t_check_trans() ) {
+					# no loose-route, but stateful ACK;
+					# must be an ACK after a 487
+					# or e.g. 404 from upstream server
+					t_relay();
+					exit;
+				} else {
+					# ACK without matching transaction ... ignore and discard
+					exit;
+				}
+			}
+			sl_send_reply("404","Not here");
+		}
+		exit;
+	}
+}
+
+# Handle SIP registrations
+route[REGISTRAR] {
+	if (is_method("REGISTER"))
+	{
+		if(isflagset(FLT_NATS))
+		{
+			setbflag(FLB_NATB);
+			# uncomment next line to do SIP NAT pinging 
+			## setbflag(FLB_NATSIPPING);
+		}
+		if (!save("location"))
+			sl_reply_error();
+
+		exit;
+	}
+}
+
+# USER location service
+route[LOCATION] {
+
+#!ifdef WITH_SPEEDIAL
+	# search for short dialing - 2-digit extension
+	if($rU=~"^[0-9][0-9]$")
+		if(sd_lookup("speed_dial"))
+			route(SIPOUT);
+#!endif
+
+#!ifdef WITH_ALIASDB
+	# search in DB-based aliases
+	if(alias_db_lookup("dbaliases"))
+		route(SIPOUT);
+#!endif
+
+	$avp(oexten) = $rU;
+	if (!lookup("location")) {
+		$var(rc) = $rc;
+		route(TOVOICEMAIL);
+		t_newtran();
+		switch ($var(rc)) {
+			case -1:
+			case -3:
+				send_reply("404", "Not Found");
+				exit;
+			case -2:
+				send_reply("405", "Method Not Allowed");
+				exit;
+		}
+	}
+
+	# when routing via usrloc, log the missed calls also
+	if (is_method("INVITE"))
+	{
+		setflag(FLT_ACCMISSED);
+	}
+}
+
+# Presence server route
+route[PRESENCE] {
+	if(!is_method("PUBLISH|SUBSCRIBE"))
+		return;
+
+#!ifdef WITH_PRESENCE
+	if (!t_newtran())
+	{
+		sl_reply_error();
+		exit;
+	};
+
+	if(is_method("PUBLISH"))
+	{
+		handle_publish();
+		t_release();
+	}
+	else
+	if( is_method("SUBSCRIBE"))
+	{
+		handle_subscribe();
+		t_release();
+	}
+	exit;
+#!endif
+	
+	# if presence enabled, this part will not be executed
+	if (is_method("PUBLISH") || $rU==$null)
+	{
+		sl_send_reply("404", "Not here");
+		exit;
+	}
+	return;
+}
+
+# Authentication route
+route[AUTH] {
+#!ifdef WITH_AUTH
+	if (is_method("REGISTER"))
+	{
+		# authenticate the REGISTER requests (uncomment to enable auth)
+		if (!www_authorize("$td", "subscriber"))
+		{
+			www_challenge("$td", "0");
+			exit;
+		}
+
+		if ($au!=$tU)
+		{
+			sl_send_reply("403","Forbidden auth ID");
+			exit;
+		}
+	} else {
+
+#!ifdef WITH_IPAUTH
+		if(allow_source_address())
+		{
+			# source IP allowed
+			return;
+		}
+#!endif
+
+		# authenticate if from local subscriber
+		if (from_uri==myself)
+		{
+			if (!proxy_authorize("$fd", "subscriber")) {
+				proxy_challenge("$fd", "0");
+				exit;
+			}
+			if (is_method("PUBLISH"))
+			{
+				if ($au!=$fU || $au!=$tU) {
+					sl_send_reply("403","Forbidden auth ID");
+					exit;
+				}
+				if ($au!=$rU) {
+					sl_send_reply("403","Forbidden R-URI");
+					exit;
+				}
+#!ifdef WITH_MULTIDOMAIN
+				if ($fd!=$rd) {
+					sl_send_reply("403","Forbidden R-URI domain");
+					exit;
+				}
+#!endif
+			} else {
+				if ($au!=$fU) {
+					sl_send_reply("403","Forbidden auth ID");
+					exit;
+				}
+			}
+
+			consume_credentials();
+			# caller authenticated
+		} else {
+			# caller is not local subscriber, then check if it calls
+			# a local destination, otherwise deny, not an open relay here
+			if (!uri==myself)
+			{
+				sl_send_reply("403","Not relaying");
+				exit;
+			}
+		}
+	}
+#!endif
+	return;
+}
+
+# Caller NAT detection route
+route[NATDETECT] {
+#!ifdef WITH_NAT
+	force_rport();
+	if (nat_uac_test("19")) {
+		if (is_method("REGISTER")) {
+			fix_nated_register();
+		} else {
+			fix_nated_contact();
+		}
+		setflag(FLT_NATS);
+	}
+#!endif
+	return;
+}
+
+# RTPProxy control
+route[NATMANAGE] {
+#!ifdef WITH_NAT
+	if (is_request()) {
+		if(has_totag()) {
+			if(check_route_param("nat=yes")) {
+				setbflag(FLB_NATB);
+			}
+		}
+	}
+	if (!(isflagset(FLT_NATS) || isbflagset(FLB_NATB)))
+		return;
+
+	rtpproxy_manage();
+
+	if (is_request()) {
+		if (!has_totag()) {
+			add_rr_param(";nat=yes");
+		}
+	}
+	if (is_reply()) {
+		if(isbflagset(FLB_NATB)) {
+			fix_nated_contact();
+		}
+	}
+#!endif
+	return;
+}
+
+# Routing to foreign domains
+route[SIPOUT] {
+	if (!uri==myself)
+	{
+		append_hf("P-hint: outbound\r\n");
+		route(RELAY);
+	}
+}
+
+# PSTN GW routing
+route[PSTN] {
+#!ifdef WITH_PSTN
+	# check if PSTN GW IP is defined
+	if (strempty($sel(cfg_get.pstn.gw_ip))) {
+		xlog("SCRIPT: PSTN rotuing enabled but pstn.gw_ip not defined\n");
+		return;
+	}
+
+	# route to PSTN dialed numbers starting with '+' or '00'
+	#     (international format)
+	# - update the condition to match your dialing rules for PSTN routing
+	if(!($rU=~"^(\+|00)[1-9][0-9]{3,20}$"))
+		return;
+
+	# only local users allowed to call
+	if(from_uri!=myself) {
+		sl_send_reply("403", "Not Allowed");
+		exit;
+	}
+
+	$ru = "sip:" + $rU + "@" + $sel(cfg_get.pstn.gw_ip);
+
+	route(RELAY);
+	exit;
+#!endif
+
+	return;
+}
+
+# XMLRPC routing
+#!ifdef WITH_XMLRPC
+route[XMLRPC] {
+	# allow XMLRPC from localhost
+	if ((method=="POST" || method=="GET")
+			&& (src_ip==127.0.0.1)) {
+		# close connection only for xmlrpclib user agents (there is a bug in
+		# xmlrpclib: it waits for EOF before interpreting the response).
+		if ($hdr(User-Agent) =~ "xmlrpclib")
+			set_reply_close();
+		set_reply_no_connect();
+		dispatch_rpc();
+		exit;
+	}
+	send_reply("403", "Forbidden");
+	exit;
+}
+#!endif
+
+# route to voicemail server
+route[TOVOICEMAIL] {
+#!ifdef WITH_VOICEMAIL
+	if(!is_method("INVITE"))
+		return;
+
+	# check if VoiceMail server IP is defined
+	if (strempty($sel(cfg_get.voicemail.srv_ip))) {
+		xlog("SCRIPT: VoiceMail rotuing enabled but IP not defined\n");
+		return;
+	}
+	if($avp(oexten)==$null)
+		return;
+
+	$ru = "sip:" + $avp(oexten) + "@" + $sel(cfg_get.voicemail.srv_ip)
+				+ ":" + $sel(cfg_get.voicemail.srv_port);
+	route(RELAY);
+	exit;
+#!endif
+
+	return;
+}
+
+# manage outgoing branches
+branch_route[MANAGE_BRANCH] {
+	xdbg("new branch [$T_branch_idx] to $ru\n");
+	route(NATMANAGE);
+}
+
+# manage incoming replies
+onreply_route[MANAGE_REPLY] {
+	xdbg("incoming reply\n");
+	if(status=~"[12][0-9][0-9]")
+		route(NATMANAGE);
+}
+
+# manage failure routing cases
+failure_route[MANAGE_FAILURE] {
+	route(NATMANAGE);
+
+	if (t_is_canceled()) {
+		exit;
+	}
+
+#!ifdef WITH_BLOCK3XX
+	# block call redirect based on 3xx replies.
+	if (t_check_status("3[0-9][0-9]")) {
+		t_reply("404","Not found");
+		exit;
+	}
+#!endif
+
+#!ifdef WITH_VOICEMAIL
+	# serial forking
+	# - route to voicemail on busy or no answer (timeout)
+	if (t_check_status("486|408")) {
+		route(TOVOICEMAIL);
+		exit;
+	}
+#!endif
+}

+ 22 - 6
modules/db_mysql/README

@@ -28,6 +28,7 @@ Daniel-Constantin Mierla
               3.1. ping_interval (integer)
               3.2. timeout_interval (integer)
               3.3. auto_reconnect (integer)
+              3.4. insert_delayed (integer)
 
         4. Functions
         5. Installation
@@ -38,9 +39,10 @@ Daniel-Constantin Mierla
    1.1. Set ping_interval parameter
    1.2. Set timeout_interval parameter
    1.3. Set auto_reconnect parameter
-   1.4. Set a my.cnf group in db_url parameter
-   1.5. Adding a kamailio group to my.cnf
-   1.6. Using [client] and specific group
+   1.4. Set insert_delayed parameter
+   1.5. Set a my.cnf group in db_url parameter
+   1.6. Adding a kamailio group to my.cnf
+   1.7. Using [client] and specific group
 
 Chapter 1. Admin Guide
 
@@ -57,6 +59,7 @@ Chapter 1. Admin Guide
         3.1. ping_interval (integer)
         3.2. timeout_interval (integer)
         3.3. auto_reconnect (integer)
+        3.4. insert_delayed (integer)
 
    4. Functions
    5. Installation
@@ -89,6 +92,7 @@ Chapter 1. Admin Guide
    3.1. ping_interval (integer)
    3.2. timeout_interval (integer)
    3.3. auto_reconnect (integer)
+   3.4. insert_delayed (integer)
 
 3.1. ping_interval (integer)
 
@@ -132,6 +136,18 @@ modparam("db_mysql", "timeout_interval", 2)
 modparam("db_mysql", "auto_reconnect", 0)
 ...
 
+3.4. insert_delayed (integer)
+
+   If set to 1, all INSERT SQL queries will be sent to MySQL server as
+   INSERT DELAYED.
+
+   Default value is 0 (1 - on / 0 - off).
+
+   Example 1.4. Set insert_delayed parameter
+...
+modparam("db_mysql", "insert_delayed", 1)
+...
+
 4. Functions
 
    No function exported to be used from configuration file.
@@ -158,12 +174,12 @@ modparam("db_mysql", "auto_reconnect", 0)
      * mysql://user:pass@[group]/db
      * mysql://[group]/db
 
-   Example 1.4. Set a my.cnf group in db_url parameter
+   Example 1.5. Set a my.cnf group in db_url parameter
 ...
 modparam("usrloc", "db_url", "mysql://[kamailio]/kamailio)
 ...
 
-   Example 1.5. Adding a kamailio group to my.cnf
+   Example 1.6. Adding a kamailio group to my.cnf
 ...
 [kamailio]
 socket = /path/to/mysql.sock
@@ -177,7 +193,7 @@ default-character-set = utf8
    both your specific group and the client group, then the value is taken
    from the last one.
 
-   Example 1.6. Using [client] and specific group
+   Example 1.7. Using [client] and specific group
 ...
 [client]
 socket = /var/run/mysql/mysqld.sock

+ 20 - 0
modules/db_mysql/doc/db_mysql_admin.xml

@@ -118,6 +118,26 @@ modparam("db_mysql", "timeout_interval", 2)
 ...
 modparam("db_mysql", "auto_reconnect", 0)
 ...
+</programlisting>
+		</example>
+	</section>
+	<section id="db_mysql.p.insert_delayed">
+		<title><varname>insert_delayed</varname> (integer)</title>
+		<para>
+		If set to 1, all INSERT SQL queries will be sent to MySQL server as
+		INSERT DELAYED.
+		</para>
+		<para>
+		<emphasis>
+			Default value is 0 (1 - on / 0 - off).
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>insert_delayed</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("db_mysql", "insert_delayed", 1)
+...
 </programlisting>
 		</example>
 	</section>

+ 1 - 0
modules/db_mysql/km_db_mysql.c

@@ -50,6 +50,7 @@
 
 unsigned int db_mysql_timeout_interval = 2;   /* Default is 6 seconds */
 unsigned int db_mysql_auto_reconnect = 1;     /* Default is enabled   */
+unsigned int db_mysql_insert_all_delayed = 0; /* Default is off */
 
 /* MODULE_VERSION */
 

+ 1 - 0
modules/db_mysql/km_db_mysql.h

@@ -42,6 +42,7 @@
 
 extern unsigned int db_mysql_timeout_interval;
 extern unsigned int db_mysql_auto_reconnect;
+extern unsigned int db_mysql_insert_all_delayed;
 
 int db_mysql_bind_api(db_func_t *dbb);
 

+ 8 - 3
modules/db_mysql/km_dbase.c

@@ -404,10 +404,15 @@ int db_mysql_raw_query(const db1_con_t* _h, const str* _s, db1_res_t** _r)
  * \param _n number of key=value pairs
  * \return zero on success, negative value on failure
  */
-int db_mysql_insert(const db1_con_t* _h, const db_key_t* _k, const db_val_t* _v, const int _n)
+int db_mysql_insert(const db1_con_t* _h, const db_key_t* _k, const db_val_t* _v,
+		const int _n)
 {
-	return db_do_insert(_h, _k, _v, _n, db_mysql_val2str,
-	db_mysql_submit_query);
+	if(unlikely(db_mysql_insert_all_delayed==1))
+		return db_do_insert_delayed(_h, _k, _v, _n, db_mysql_val2str,
+				db_mysql_submit_query);
+	else
+		return db_do_insert(_h, _k, _v, _n, db_mysql_val2str,
+				db_mysql_submit_query);
 }
 
 

+ 1 - 0
modules/db_mysql/mysql_mod.c

@@ -107,6 +107,7 @@ static param_export_t params[] = {
 
 	{"timeout_interval", INT_PARAM, &db_mysql_timeout_interval},
 	{"auto_reconnect",   INT_PARAM, &db_mysql_auto_reconnect},
+	{"insert_delayed",   INT_PARAM, &db_mysql_insert_all_delayed},
 	{0, 0, 0}
 };
 

+ 22 - 15
modules/drouting/README

@@ -304,18 +304,18 @@ Chapter 1. Admin Guide
        draft 09):
        Table 1.4. Time recurrence attributes
 
-   Attribute Description
-   dastard Start of interval (RFC 2445 DATE-TIME)
-   duration Length of interval (RFC 2445 DURATION)
-   freq Frequency of recurrence (secondly,minutely,hourly, daily,weekly,
-   monthly, or yearly).
-   until bound of recurrence (RFC 2445 DATE-TIME)
-   interval How often the recurrence repeats
-   byday List of days of the week
-   bymonthday List of days of the month
-   byyearday List of days of the year
-   byweekno List of weeks of the year
-   bymonth List of months of the year
+     Attribute                            Description
+     dastard    Start of interval (RFC 2445 DATE-TIME)
+     duration   Length of interval (RFC 2445 DURATION)
+     freq       Frequency of recurrence (secondly,minutely,hourly, daily,weekly,
+                monthly, or yearly).
+     until      bound of recurrence (RFC 2445 DATE-TIME)
+     interval   How often the recurrence repeats
+     byday      List of days of the week
+     bymonthday List of days of the month
+     byyearday  List of days of the year
+     byweekno   List of weeks of the year
+     bymonth    List of months of the year
        The value stored in database has the format of:
        <dtstart>|<duration>|<freq>|<until>|<interval>|<byday>|<bymonthday>
        |<byyearday>|<byweekno>|<bymonth>
@@ -570,9 +570,16 @@ modparam("drouting", "drl_table", "my_gw_lists")
        destinations). Ex: 1,2;3,4,5;6 -> randomizer -> (A) 2,1;4,3,5;6 ->
        usage 2,1,4,3,5,6 (B) 1,2;3,5,4;6 -> usage 1,2,3,5,4,6
      * 2 - from each destination group, only a single destination is
-       randomly selected; groups do maintain their order (as given); Ex:
-       1,2;3,4,5;6 -> randomizer -> (A) 2;4;6 -> usage 2,4,6 (B) 1;5;6 ->
-       usage 1,5,6
+       randomly selected; groups do maintain their order (as given);
+       Ex: 1,2;3,4,5;6 -> randomizer ->
+       (A) 2;4;6 -> usage 2,4,6
+       (B) 1;5;6 -> usage 1,5,6
+       It is ok to have repeating gateways in different groups. The module
+       will take care internally in case of failure not to choose a
+       gateway that was tried already.
+       Ex: 1,2,3; 1,2,3; 1,2,3 -> no gateway will be choosen twice. So in
+       case there are 2 failures, all the three gateways (1,2,3) will be
+       tried in a random order.
 
    Default value is “0”.
 

+ 15 - 0
modules/drouting/doc/drouting_admin.xml

@@ -942,9 +942,24 @@ modparam("drouting", "drl_table", "my_gw_lists")
 			<emphasis>2</emphasis> - from each destination group, only a 
 			single destination is randomly selected; groups do maintain their
 			order (as given);
+			<para>
 			Ex: 1,2;3,4,5;6 -> randomizer ->
+			</para>
+			<para>
 			(A) 2;4;6  -> usage 2,4,6
+			</para>
+			<para>
 			(B) 1;5;6  -> usage 1,5,6
+			</para>
+			<para>
+			It is ok to have repeating gateways in different groups. The module will
+			take care internally in case of failure not to choose a gateway that
+			was tried already.
+			</para>
+			<para>
+			Ex: 1,2,3; 1,2,3; 1,2,3 -> no gateway will be choosen twice. So in case there
+			are 2 failures, all the three gateways (1,2,3) will be tried in a random order.
+			</para>
 		</listitem>
 		</itemizedlist>
 		</para>

+ 38 - 0
modules/drouting/drouting.c

@@ -646,6 +646,20 @@ static int use_next_gw(struct sip_msg* msg)
 	return 1;
 }
 
+int dr_already_choosen(rt_info_t* rt_info, int* local_gwlist, int lgw_size, int check)
+{
+	int l;
+
+	for ( l = 0; l<lgw_size; l++ ) {
+		if ( rt_info->pgwl[local_gwlist[l]].pgw == rt_info->pgwl[check].pgw ) {
+			LM_INFO("Gateway already choosen %.*s, local_gwlist[%d]=%d, %d\n",
+					rt_info->pgwl[check].pgw->ip.len, rt_info->pgwl[check].pgw->ip.s, l, local_gwlist[l], check);
+			return 1;
+		}
+	}
+
+	return 0;
+}
 
 static int do_routing(struct sip_msg* msg, dr_group_t *drg)
 {
@@ -806,6 +820,30 @@ again:
 					}
 				}
 			}
+
+			if ( sort_order == 2 ) {
+				/* check not to use the same gateway as before */
+				if ( t>1 ) {
+					/* check if all in the current set were already chosen */
+					if (i-j <= t-1) {
+						for( l = j; l< i; l++) {
+							if ( ! dr_already_choosen(rt_info, local_gwlist, t-1, l) )
+								break;
+						}
+						if ( l == i ) {
+							LM_INFO("All gateways in group from %d - %d were already used\n", j, i);
+							t--; /* jump over this group, nothing to choose here */
+							j=i; continue;
+						}
+					}
+					while ( dr_already_choosen(rt_info, local_gwlist, t-1, local_gwlist[t-1]) ) {
+						local_gwlist[t-1]   = j + rand()%(i-j);
+					}
+				}
+				LM_DBG("The %d gateway is %.*s [%d]\n", t, rt_info->pgwl[local_gwlist[t-1]].pgw->ip.len,
+						rt_info->pgwl[local_gwlist[t-1]].pgw->ip.s, local_gwlist[t-1]);
+			}
+
 			/* next group starts from i */
 			j=i;
 		}

+ 4 - 4
modules/enum/enum.c

@@ -256,7 +256,7 @@ static inline int is_e164(str* _user)
 	int i;
 	char c;
 	
-	if ((_user->len > 2) && (_user->len < 17) && ((_user->s)[0] == '+')) {
+	if ((_user->len > 2) && (_user->len < MAX_NUM_LEN) && ((_user->s)[0] == '+')) {
 		for (i = 1; i < _user->len; i++) {
 			c = (_user->s)[i];
 			if ((c < '0') || (c > '9')) return -1;
@@ -309,7 +309,7 @@ int is_from_user_enum_2(struct sip_msg* _msg, char* _suffix, char* _service)
 	struct naptr_rdata* naptr;
 
 	str pattern, replacement, result;
-	char string[17];
+	char string[MAX_NUM_LEN];
 
 	if (parse_from_header(_msg) < 0) {
 	    LM_ERR("Failed to parse From header\n");
@@ -754,7 +754,7 @@ int enum_query(struct sip_msg* _msg, str* suffix, str* service)
 	char *user_s;
 	int user_len, i, j;
 	char name[MAX_DOMAIN_SIZE];
-	char string[17];
+	char string[MAX_NUM_LEN];
 
 	LM_DBG("enum_query on suffix <%.*s> service <%.*s>\n",
 	       suffix->len, suffix->s, service->len, service->s);
@@ -818,7 +818,7 @@ int i_enum_query_2(struct sip_msg* _msg, char* _suffix, char* _service)
 	int cc_len;
 	struct rdata* head;
 
-	char string[17];
+	char string[MAX_NUM_LEN];
 
 	str *suffix, *service;
 

+ 3 - 2
modules/enum/enum.h

@@ -37,8 +37,9 @@
 
 
 #define MAX_DOMAIN_SIZE 256
-#define MAX_COMPONENT_SIZE 32  /* separator, apex, ... This simplifies checks */
-		
+#define MAX_NUM_LEN 22
+#define MAX_COMPONENT_SIZE (MAX_NUM_LEN * 2)  /* separator, apex, ... This simplifies checks */
+
 
 /*
  * Check if from user is an e164 number and has a naptr record

+ 1 - 1
modules/msrp/msrp_parser.c

@@ -538,7 +538,7 @@ int msrp_parse_uri(char *start, int len, msrp_uri_t *uri)
 
 error:
 	LM_ERR("parsing error in [%.*s] at [%ld]\n",
-			len, start, s - start);
+			len, start, (long int)(s - start));
 	memset(uri, 0, sizeof(msrp_uri_t));
 	return -1;
 }

+ 109 - 38
modules/outbound/ob_mod.c

@@ -206,7 +206,7 @@ int decode_flow_token(struct receive_info *rcv, str flow_token)
 
 	if (flow_token.s == NULL)
 	{
-		LM_INFO("no flow-token provided\n");
+		LM_DBG("no flow-token provided\n");
 		return -2;
 	}
 
@@ -275,29 +275,10 @@ int decode_flow_token(struct receive_info *rcv, str flow_token)
 	return 0;
 }
 
-int use_outbound(struct sip_msg *msg)
+static int use_outbound_register(struct sip_msg *msg)
 {
 	contact_t *contact;
-	rr_t *rt;
-	struct sip_uri puri;
-	param_hooks_t hooks;
-	param_t *params;
-	int ret;
-
-	/* If Outbound is forced return success without any further checks */
-	if (ob_force_flag != -1 && isflagset(msg, ob_force_flag) > 0)
-	{
-		LM_INFO("outbound forced\n");
-		return 1;
-	}
-
-	/* Use Outbound when there is a single Via: header and:
-	    # It's a REGISTER request with a Contact-URI containing a ;reg-id
-	      parameter, or
-	    # The Contact-URI has an ;ob parameter, or
-	    # The top Route-URI points to use and has an ;ob parameter
-	*/
-
+	
 	/* Check there is a single Via: */
 	if (!(parse_headers(msg, HDR_VIA2_F, 0) == -1 || msg->via2 == 0
 		|| msg->via2->error != PARSE_OK))
@@ -306,8 +287,7 @@ int use_outbound(struct sip_msg *msg)
 		return 0;
 	}
 
-	/* Look for ;reg-id in REGISTER Contact-URIs and ;ob in any
-	   Contact-URIs */
+	/* Look for ;reg-id in Contact-URIs */
 	if (msg->contact
 		|| (parse_headers(msg, HDR_CONTACT_F, 0) != -1 && msg->contact))
 	{
@@ -323,21 +303,29 @@ int use_outbound(struct sip_msg *msg)
 			return 0;
 		}
 		
-		if (msg->REQ_METHOD == METHOD_REGISTER && contact->reg_id)
+		if (contact->reg_id)
 		{
 			LM_INFO("found REGISTER with ;reg-id paramter on"
 				" Contact-URI - outbound used\n");
 			return 1;
 		}
 
-		if (hooks.contact.ob)
-		{
-			LM_INFO("found ;ob parameter on Contact-URI - outbound"
-				" used\n");
-			return 1;
-		}
 	}
 
+	LM_INFO("outbound not used\n");
+	return 0;
+}
+
+static int use_outbound_non_reg(struct sip_msg *msg)
+{
+	contact_t *contact;
+	rr_t *rt;
+	struct sip_uri puri;
+	param_hooks_t hooks;
+	param_t *params;
+	int ret;
+	struct receive_info rcv;
+
 	/* Check to see if the top Route-URI is me and has a ;ob parameter */
 	if (msg->route
 		|| (parse_headers(msg, HDR_ROUTE_F, 0) != -1 && msg->route))
@@ -359,13 +347,6 @@ int use_outbound(struct sip_msg *msg)
 			LM_ERR("parsing Route-URI\n");
 			return 0;
 		}
-		if (parse_params(&puri.params, CLASS_URI, &hooks,
-			&params) != 0)
-		{
-			LM_ERR("parsing Route-URI parameters\n");
-			return 0;
-		}
-
 		ret = check_self(&puri.host,
 				puri.port_no ? puri.port_no : SIP_PORT, 0);
 		if (ret < 1 || (ret == 1 && puri.gr.s != NULL))
@@ -377,10 +358,73 @@ int use_outbound(struct sip_msg *msg)
 			return 0;
 		}
 
+		if (parse_params(&puri.params, CLASS_URI, &hooks,
+			&params) != 0)
+		{
+			LM_ERR("parsing Route-URI parameters\n");
+			return 0;
+		}
+
 		if (hooks.uri.ob)
 		{
 			LM_INFO("found ;ob parameter on Route-URI - outbound"
 				" used\n");
+
+			if (decode_flow_token(&rcv, puri.user) == 0)
+			{
+				if (!ip_addr_cmp(&rcv.src_ip, &msg->rcv.src_ip)
+					|| rcv.src_port != msg->rcv.src_port)
+				{
+					LM_INFO("\"incoming\" request found\n");
+					return 2;
+				}
+			}
+
+			LM_INFO("\"outgoing\" request found\n");
+			return 1;
+		}
+	}
+
+	/* Check there is a single Via: */
+	if (!(parse_headers(msg, HDR_VIA2_F, 0) == -1 || msg->via2 == 0
+		|| msg->via2->error != PARSE_OK))
+	{
+		LM_INFO("second Via: found - outbound not used\n");
+		return 0;
+	}
+
+	/* Look for ;ob in Contact-URIs */
+	if (msg->contact
+		|| (parse_headers(msg, HDR_CONTACT_F, 0) != -1 && msg->contact))
+	{
+		if (parse_contact(msg->contact) < 0)
+		{
+			LM_ERR("parsing Contact: header body\n");
+			return 0;
+		}
+		contact = ((contact_body_t *) msg->contact->parsed)->contacts;
+		if (!contact)
+		{
+			LM_ERR("empty Contact:\n");
+			return 0;
+		}
+	
+		if (parse_uri(contact->uri.s, contact->uri.len, &puri)
+			< 0)
+		{
+			LM_ERR("parsing Contact-URI\n");
+			return 0;
+		}
+		if (parse_params(&puri.params, CLASS_CONTACT, &hooks, &params)
+			!= 0)
+		{
+			LM_ERR("parsing Contact-URI parameters\n");
+			return 0;
+		}
+		if (hooks.contact.ob)
+		{
+			LM_INFO("found ;ob parameter on Contact-URI - outbound"
+				" used\n");
 			return 1;
 		}
 	}
@@ -389,6 +433,33 @@ int use_outbound(struct sip_msg *msg)
 	return 0;
 }
 
+int use_outbound(struct sip_msg *msg)
+{
+	if (msg->first_line.type != SIP_REQUEST)
+	{
+		LM_ERR("use_outbound called for something that isn't a SIP"
+			" request\n");
+		return 0;
+	}
+
+	/* If Outbound is forced return success without any further checks */
+	if (ob_force_flag != -1 && isflagset(msg, ob_force_flag) > 0)
+	{
+		LM_DBG("outbound forced\n");
+		return 1;
+	}
+
+	LM_INFO("Analysing %.*s for outbound markers\n",
+		msg->first_line.u.request.method.len,
+		msg->first_line.u.request.method.s);
+
+	if (msg->REQ_METHOD == METHOD_REGISTER)
+		return use_outbound_register(msg);
+	else
+		return use_outbound_non_reg(msg);
+
+}
+
 int bind_ob(struct ob_binds *pxb)
 {
 	if (pxb == NULL)

+ 2 - 0
modules/p_usrloc/udomain.c

@@ -589,6 +589,7 @@ done:
  * \param _d cleaned domain
  * \return 0 on success, -1 on failure
  */
+/*
 int db_timer_udomain(udomain_t* _d)
 {
 	db_key_t keys[2];
@@ -611,6 +612,7 @@ int db_timer_udomain(udomain_t* _d)
 	//if (ul_db_layer_delete(_d, NULL, NULL, keys, ops, vals, 2) < 0) { //FIXME
 	return 0;
 }
+*/
 
 
 

+ 2 - 0
modules/p_usrloc/udomain.h

@@ -95,7 +95,9 @@ void print_udomain(FILE* _f, udomain_t* _d);
  * \param _d cleaned domain
  * \return 0 on success, -1 on failure
  */
+/*
 int db_timer_udomain(udomain_t* _d);
+*/
 
 
 /*!

+ 7 - 4
modules/rr/loose.c

@@ -513,12 +513,15 @@ static inline int process_outbound(struct sip_msg *_m, str flow_token,
 	ret = rr_obb.decode_flow_token(&rcv, flow_token);
 
 	if (ret == -2) {
-		LM_INFO("no flow token found - outbound not in use\n");
+		LM_DBG("no flow token found - outbound not in use\n");
 		return 0;
 	} else if (ret == -1) {
 		LM_ERR("failed to decode flow token\n");
 		return -1;
-	} else {
+	} else if (!ip_addr_cmp(&rcv.src_ip, &_m->rcv.src_ip)
+			|| rcv.src_port != _m->rcv.src_port) {
+		LM_DBG("\"incoming\" request found. Using flow-token for"
+			"routing\n");
 
 		/* First, force the local socket */
 		si = find_si(&rcv.dst_ip, rcv.dst_port, rcv.proto);
@@ -546,9 +549,9 @@ static inline int process_outbound(struct sip_msg *_m, str flow_token,
 					rcv.src_ip.af == AF_INET6 ? "]" : "",
 					rcv.src_port,
 					get_proto_name(rcv.proto));
-
-		return 1;
 	}
+
+	return 1;
 }
 
 /*!

+ 136 - 25
modules/rr/record.c

@@ -37,14 +37,18 @@
 #include "../../dprint.h"
 #include "../../parser/parse_uri.h"
 #include "../../parser/parse_from.h"
+#include "../../parser/parse_rr.h"
 #include "../../str.h"
 #include "../../data_lump.h"
 #include "record.h"
 #include "rr_mod.h"
 
 
-#define RR_PREFIX "Record-Route: <sip:"
-#define RR_PREFIX_LEN (sizeof(RR_PREFIX)-1)
+#define RR_PREFIX_SIP "Record-Route: <sip:"
+#define RR_PREFIX_SIP_LEN (sizeof(RR_PREFIX_SIP)-1)
+
+#define RR_PREFIX_SIPS "Record-Route: <sip:"
+#define RR_PREFIX_SIPS_LEN (sizeof(RR_PREFIX_SIPS)-1)
 
 #define RR_LR ";lr"
 #define RR_LR_LEN (sizeof(RR_LR)-1)
@@ -78,6 +82,15 @@ static unsigned int rr_param_msg;
 static pv_spec_t *custom_user_avp;		/*!< AVP for custom_user setting */
 
 
+inline static int rr_is_sips(sip_msg_t *_m)
+{
+	if(parse_sip_msg_uri(_m)<0)
+		return 0;
+	if(_m->parsed_uri.type==SIPS_URI_T)
+		return 1;
+	return 0;
+}
+
 void init_custom_user(pv_spec_t *custom_user_avp_p)
 {
     custom_user_avp = custom_user_avp_p;
@@ -187,13 +200,23 @@ static inline struct lump *insert_rr_param_lump(struct lump *before,
  * \return 0 on success, negative on failure
  */
 static inline int build_rr(struct lump* _l, struct lump* _l2, str* user,
-				str *tag, str *params, int _inbound, int _use_ob)
+				str *tag, str *params, int _inbound, int _use_ob, int _sips)
 {
 	char* prefix, *suffix, *term, *r2;
 	int suffix_len, prefix_len;
 	char *p;
+	char *rr_prefix;
+	int rr_prefix_len;
 
-	prefix_len = RR_PREFIX_LEN + (user->len ? (user->len + 1) : 0);
+	if(_sips==0) {
+		rr_prefix = RR_PREFIX_SIP;
+		rr_prefix_len = RR_PREFIX_SIP_LEN;
+	} else {
+		rr_prefix = RR_PREFIX_SIPS;
+		rr_prefix_len = RR_PREFIX_SIPS_LEN;
+	}
+
+	prefix_len = rr_prefix_len + (user->len ? (user->len + 1) : 0);
 	if (enable_full_lr) {
 		suffix_len = RR_LR_FULL_LEN + (params?params->len:0) +
 				((tag && tag->len) ? (RR_FROMTAG_LEN + tag->len) : 0);
@@ -216,21 +239,21 @@ static inline int build_rr(struct lump* _l, struct lump* _l2, str* user,
 		return -3;
 	}
 	
-	memcpy(prefix, RR_PREFIX, RR_PREFIX_LEN);
+	memcpy(prefix, rr_prefix, rr_prefix_len);
 	if (user->len) {
-		memcpy(prefix + RR_PREFIX_LEN, user->s, user->len);
+		memcpy(prefix + rr_prefix_len, user->s, user->len);
 #ifdef ENABLE_USER_CHECK
 		/* don't add the ignored user into a RR */
 		if(i_user.len && i_user.len == user->len && 
 				!strncmp(i_user.s, user->s, i_user.len))
 		{
-			if(prefix[RR_PREFIX_LEN]=='x')
-				prefix[RR_PREFIX_LEN]='y';
+			if(prefix[rr_prefix_len]=='x')
+				prefix[rr_prefix_len]='y';
 			else
-				prefix[RR_PREFIX_LEN]='x';
+				prefix[rr_prefix_len]='x';
 		}
 #endif
-		prefix[RR_PREFIX_LEN + user->len] = '@';
+		prefix[rr_prefix_len + user->len] = '@';
 	}
 
 	p = suffix;
@@ -294,6 +317,51 @@ lump_err:
 	return -4;
 }
 
+/*!
+ * \brief Copy flow-token from top-Route: to a string
+ *
+ * Copy the user part of the top-Route: to a string (allocating private memory
+ * for this).
+ * \param token where the user-part of the top-Route: will be copied to
+ * \param _m the SIP message to extract the top-Route: from
+ * \return 0 on success, negative on failure
+ */
+static int copy_flow_token(str *token, struct sip_msg *_m)
+{
+	rr_t *rt;
+	struct sip_uri puri;
+
+	if (_m->route
+	    || (parse_headers(_m, HDR_ROUTE_F, 0) != -1 && _m->route)) {
+		if (parse_rr(_m->route) < 0) {
+			LM_ERR("parsing Route: header body\n");
+			return -1;
+		}
+		rt = (rr_t *) _m->route->parsed;
+		if (!rt) {
+			LM_ERR("empty Route:\n");
+			return -1;
+		}
+		if (parse_uri(rt->nameaddr.uri.s, rt->nameaddr.uri.len,
+				&puri) < 0) {
+			LM_ERR("parsing Route-URI\n");
+			return -1;
+		}
+
+		token->s = pkg_malloc(puri.user.len * sizeof(char));
+		if (token->s == NULL) {
+			LM_ERR("allocating memory\n");
+			return -1;
+		}
+		memcpy(token->s, puri.user.s, puri.user.len);
+		token->len = puri.user.len;
+		return 0;
+	}
+
+	LM_ERR("no Route: headers found\n");
+	return -1;
+}
+
 
 /*!
  * \brief Insert a new Record-Route header field with lr parameter
@@ -311,6 +379,7 @@ int record_route(struct sip_msg* _m, str *params)
 	struct to_body* from = NULL;
 	str* tag;
 	int use_ob = rr_obb.use_outbound ? rr_obb.use_outbound(_m) : 0;
+	int sips;
 	
 	user.len = 0;
 	
@@ -322,11 +391,16 @@ int record_route(struct sip_msg* _m, str *params)
 				return -1;
 			}
 		}
-	} else if (use_ob) {
+	} else if (use_ob == 1) {
 		if (rr_obb.encode_flow_token(&user, _m->rcv) != 0) {
 			LM_ERR("encoding outbound flow-token\n");
 			return -1;
 		}
+	} else if (use_ob == 2) {
+		if (copy_flow_token(&user, _m) != 0) {
+			LM_ERR("copying outbound flow-token\n");
+			return -1;
+		}
 	}
 
 	if (append_fromtag) {
@@ -345,6 +419,8 @@ int record_route(struct sip_msg* _m, str *params)
 		rr_param_buf.len = 0;
 	}
 
+	sips = rr_is_sips(_m);
+
 	if (enable_double_rr && !use_ob) {
 		l = anchor_lump(_m, _m->headers->name.s - _m->buf,0,HDR_RECORDROUTE_T);
 		l2 = anchor_lump(_m, _m->headers->name.s - _m->buf, 0, 0);
@@ -358,7 +434,7 @@ int record_route(struct sip_msg* _m, str *params)
 			LM_ERR("failed to insert conditional lump\n");
 			return -6;
 		}
-		if (build_rr(l, l2, &user, tag, params, OUTBOUND, 0) < 0) {
+		if (build_rr(l, l2, &user, tag, params, OUTBOUND, 0, sips) < 0) {
 			LM_ERR("failed to insert outbound Record-Route\n");
 			return -7;
 		}
@@ -372,7 +448,7 @@ int record_route(struct sip_msg* _m, str *params)
 	}
 	
 	if (build_rr(l, l2, &user, tag, params, use_ob ? OUTBOUND : INBOUND,
-			use_ob) < 0) {
+			use_ob, sips) < 0) {
 		LM_ERR("failed to insert inbound Record-Route\n");
 		return -4;
 	}
@@ -401,7 +477,19 @@ int record_route_preset(struct sip_msg* _m, str* _data)
 	char* hdr, *p;
 	int hdr_len;
 	int use_ob = rr_obb.use_outbound ? rr_obb.use_outbound(_m) : 0;
-	
+	char *rr_prefix;
+	int rr_prefix_len;
+	int sips;
+
+	sips = rr_is_sips(_m);
+	if(sips==0) {
+		rr_prefix = RR_PREFIX_SIP;
+		rr_prefix_len = RR_PREFIX_SIP_LEN;
+	} else {
+		rr_prefix = RR_PREFIX_SIPS;
+		rr_prefix_len = RR_PREFIX_SIPS_LEN;
+	}
+
 	from = 0;
 	user.len = 0;
 	user.s = 0;
@@ -411,11 +499,16 @@ int record_route_preset(struct sip_msg* _m, str* _data)
 			LM_ERR("failed to extract username\n");
 			return -1;
 		}
-	} else if (use_ob) {
+	} else if (use_ob == 1) {
 		if (rr_obb.encode_flow_token(&user, _m->rcv) != 0) {
 			LM_ERR("encoding outbound flow-token\n");
 			return -1;
 		}
+	} else if (use_ob == 2) {
+		if (copy_flow_token(&user, _m) != 0) {
+			LM_ERR("copying outbound flow-token\n");
+			return -1;
+		}
 	}
 
 	if (append_fromtag) {
@@ -432,7 +525,7 @@ int record_route_preset(struct sip_msg* _m, str* _data)
 		return -3;
 	}
 
-	hdr_len = RR_PREFIX_LEN;
+	hdr_len = rr_prefix_len;
 	if (user.len)
 		hdr_len += user.len + 1; /* @ */
 	hdr_len += _data->len;
@@ -456,8 +549,8 @@ int record_route_preset(struct sip_msg* _m, str* _data)
 	}
 
 	p = hdr;
-	memcpy(p, RR_PREFIX, RR_PREFIX_LEN);
-	p += RR_PREFIX_LEN;
+	memcpy(p, rr_prefix, rr_prefix_len);
+	p += rr_prefix_len;
 
 	if (user.len) {
 		memcpy(p, user.s, user.len);
@@ -508,13 +601,23 @@ int record_route_preset(struct sip_msg* _m, str* _data)
 #define RR_TRANS_LEN 11
 #define RR_TRANS ";transport="
 static inline int build_advertised_rr(struct lump* _l, struct lump* _l2, str *_data,
-				str* user, str *tag, int _inbound, int _use_ob)
+				str* user, str *tag, int _inbound, int _use_ob, int _sips)
 {
 	char *p;
 	char *hdr, *trans, *r2, *suffix, *term;
 	int hdr_len, suffix_len;
+	char *rr_prefix;
+	int rr_prefix_len;
 
-	hdr_len = RR_PREFIX_LEN;
+	if(_sips==0) {
+		rr_prefix = RR_PREFIX_SIP;
+		rr_prefix_len = RR_PREFIX_SIP_LEN;
+	} else {
+		rr_prefix = RR_PREFIX_SIPS;
+		rr_prefix_len = RR_PREFIX_SIPS_LEN;
+	}
+
+	hdr_len = rr_prefix_len;
 	if (user && user->len)
 		hdr_len += user->len + 1; /* @ */
 	hdr_len += _data->len;
@@ -546,8 +649,8 @@ static inline int build_advertised_rr(struct lump* _l, struct lump* _l2, str *_d
 	}
 
 	p = hdr;
-	memcpy(p, RR_PREFIX, RR_PREFIX_LEN);
-	p += RR_PREFIX_LEN;
+	memcpy(p, rr_prefix, rr_prefix_len);
+	p += rr_prefix_len;
 
 	if (user->len) {
 		memcpy(p, user->s, user->len);
@@ -625,6 +728,7 @@ int record_route_advertised_address(struct sip_msg* _m, str* _data)
 	struct lump* l;
 	struct lump* l2;
 	int use_ob = rr_obb.use_outbound ? rr_obb.use_outbound(_m) : 0;
+	int sips;
 	
 	user.len = 0;
 	user.s = 0;
@@ -634,11 +738,16 @@ int record_route_advertised_address(struct sip_msg* _m, str* _data)
 			LM_ERR("failed to extract username\n");
 			return -1;
 		}
-	} else if (use_ob) {
+	} else if (use_ob == 1) {
 		if (rr_obb.encode_flow_token(&user, _m->rcv) != 0) {
 			LM_ERR("encoding outbound flow-token\n");
 			return -1;
 		}
+	} else if (use_ob == 2) {
+		if (copy_flow_token(&user, _m) != 0) {
+			LM_ERR("copying outbound flow-token\n");
+			return -1;
+		}
 	}
 
 	if (append_fromtag) {
@@ -649,6 +758,8 @@ int record_route_advertised_address(struct sip_msg* _m, str* _data)
 		tag = &((struct to_body*)_m->from->parsed)->tag_value;
 	}
 
+	sips = rr_is_sips(_m);
+
 	if (enable_double_rr && !use_ob) {
 		l = anchor_lump(_m, _m->headers->name.s - _m->buf,0,HDR_RECORDROUTE_T);
 		l2 = anchor_lump(_m, _m->headers->name.s - _m->buf, 0, 0);
@@ -663,7 +774,7 @@ int record_route_advertised_address(struct sip_msg* _m, str* _data)
 			return -4;
 		}
 		if (build_advertised_rr(l, l2, _data, &user, tag, OUTBOUND,
-					0) < 0) {
+					0, sips) < 0) {
 			LM_ERR("failed to insert outbound Record-Route\n");
 			return -5;
 		}
@@ -678,7 +789,7 @@ int record_route_advertised_address(struct sip_msg* _m, str* _data)
 	
 	if (build_advertised_rr(l, l2, _data, &user, tag,
 				use_ob ? OUTBOUND: INBOUND,
-				use_ob) < 0) {
+				use_ob, sips) < 0) {
 		LM_ERR("failed to insert outbound Record-Route\n");
 		return -7;
 	}

+ 4 - 4
modules/rtpproxy/README

@@ -77,7 +77,7 @@ Carsten Bock
 
         6. Exported Pseudo Variables
 
-              6.1. $rtpstart
+              6.1. $rtpstat
 
         7. MI Commands
 
@@ -145,7 +145,7 @@ Chapter 1. Admin Guide
 
    6. Exported Pseudo Variables
 
-        6.1. $rtpstart
+        6.1. $rtpstat
 
    7. MI Commands
 
@@ -659,9 +659,9 @@ start_recording();
 
 6. Exported Pseudo Variables
 
-   6.1. $rtpstart
+   6.1. $rtpstat
 
-6.1. $rtpstart
+6.1. $rtpstat
 
    Returns the RTP-Statistics from the RTP-Proxy. The RTP-Statistics from
    the RTP-Proxy are provided as a string and it does contain several

+ 1 - 1
modules/rtpproxy/doc/rtpproxy_admin.xml

@@ -761,7 +761,7 @@ start_recording();
 	<section>
 		<title>Exported Pseudo Variables</title>
 		<section>
-			<title><function moreinfo="none">$rtpstart</function></title>
+			<title><function moreinfo="none">$rtpstat</function></title>
 			<para>
 			Returns the RTP-Statistics from the RTP-Proxy. The RTP-Statistics from the RTP-Proxy
 			are provided as a string and it does contain several packet-counters. The statistics

+ 55 - 19
modules/rtpproxy/rtpproxy.c

@@ -283,7 +283,8 @@ static int alter_mediaport(struct sip_msg *, str *, str *, str *, int);
 static int alter_rtcp(struct sip_msg *msg, str *body, str *oldport, str *newport);
 static char *gencookie();
 static int rtpp_test(struct rtpp_node*, int, int);
-static int unforce_rtp_proxy_f(struct sip_msg *, char *, char *);
+static int unforce_rtp_proxy1_f(struct sip_msg *, char *, char *);
+static int unforce_rtp_proxy(struct sip_msg *, char *);
 static int force_rtp_proxy(struct sip_msg *, char *, char *, int, int);
 static int start_recording_f(struct sip_msg *, char *, char *);
 static int rtpproxy_answer1_f(struct sip_msg *, char *, char *);
@@ -359,17 +360,17 @@ static cmd_export_t cmds[] = {
 	{"set_rtp_proxy_set",  (cmd_function)set_rtp_proxy_set_f,    1,
 		fixup_set_id, 0,
 		ANY_ROUTE},
-	{"unforce_rtp_proxy",  (cmd_function)unforce_rtp_proxy_f,    0,
+	{"unforce_rtp_proxy",  (cmd_function)unforce_rtp_proxy1_f,   0,
 		0, 0,
 		ANY_ROUTE},
-	{"rtpproxy_destroy",   (cmd_function)unforce_rtp_proxy_f,    0,
+	{"rtpproxy_destroy",   (cmd_function)unforce_rtp_proxy1_f,   0,
 		0, 0,
 		ANY_ROUTE},
-	{"unforce_rtp_proxy",  (cmd_function)unforce_rtp_proxy_f,    1,
-		0, 0,
+	{"unforce_rtp_proxy",  (cmd_function)unforce_rtp_proxy1_f,   1,
+		fixup_spve_null, 0,
 		ANY_ROUTE},
-	{"rtpproxy_destroy",   (cmd_function)unforce_rtp_proxy_f,    1,
-		0, 0,
+	{"rtpproxy_destroy",   (cmd_function)unforce_rtp_proxy1_f,   1,
+		fixup_spve_null, 0,
 		ANY_ROUTE},
 	{"start_recording",    (cmd_function)start_recording_f,      0,
 		0, 0,
@@ -378,19 +379,19 @@ static cmd_export_t cmds[] = {
 		0, 0,
 		ANY_ROUTE},
 	{"rtpproxy_offer",	(cmd_function)rtpproxy_offer1_f,     1,
-		0, 0,
+		fixup_spve_null, 0,
 		ANY_ROUTE},
 	{"rtpproxy_offer",	(cmd_function)rtpproxy_offer2_f,     2,
-		0, 0,
+		fixup_spve_spve, 0,
 		ANY_ROUTE},
 	{"rtpproxy_answer",	(cmd_function)rtpproxy_answer1_f,    0,
 		0, 0,
 		ANY_ROUTE},
 	{"rtpproxy_answer",	(cmd_function)rtpproxy_answer1_f,    1,
-		0, 0,
+		fixup_spve_null, 0,
 		ANY_ROUTE},
 	{"rtpproxy_answer",	(cmd_function)rtpproxy_answer2_f,    2,
-		0, 0,
+		fixup_spve_spve, 0,
 		ANY_ROUTE},
 	{"rtpproxy_stream2uac",(cmd_function)rtpproxy_stream2uac2_f, 2,
 		fixup_var_str_int, 0,
@@ -1800,7 +1801,21 @@ get_extra_id(struct sip_msg* msg, str *id_str) {
 
 
 static int
-unforce_rtp_proxy_f(struct sip_msg* msg, char* flags, char* str2)
+unforce_rtp_proxy1_f(struct sip_msg* msg, char* str1, char* str2)
+{
+	str flags;
+
+	if (str1)
+		get_str_fparam(&flags, msg, (fparam_t *) str1);
+	else
+		flags.s = NULL;
+
+	return unforce_rtp_proxy(msg, flags.s);
+}
+
+
+static int
+unforce_rtp_proxy(struct sip_msg* msg, char* flags)
 {
 	str callid, from_tag, to_tag, viabranch;
 	char *cp;
@@ -1813,6 +1828,7 @@ unforce_rtp_proxy_f(struct sip_msg* msg, char* flags, char* str2)
 	struct iovec v[1 + 4 + 3 + 2] = {{NULL, 0}, {"D", 1}, {" ", 1}, {NULL, 0}, {NULL, 0}, {NULL, 0}, {" ", 1}, {NULL, 0}, {" ", 1}, {NULL, 0}};
 	                                            /* 1 */   /* 2 */   /* 3 */    /* 4 */    /* 5 */    /* 6 */   /* 7 */    /* 8 */   /* 9 */
 
+
 	for (cp = flags; cp && *cp; cp++) {
 		switch (*cp) {
 			case '1':
@@ -1976,7 +1992,7 @@ rtpproxy_manage(struct sip_msg *msg, char *flags, char *ip)
 		return -1;
 
 	if(method==METHOD_CANCEL || method==METHOD_BYE)
-		return unforce_rtp_proxy_f(msg, flags, 0);
+		return unforce_rtp_proxy(msg, flags);
 
 	if(ip==NULL)
 	{
@@ -2002,13 +2018,13 @@ rtpproxy_manage(struct sip_msg *msg, char *flags, char *ip)
 					&& tmb.t_gett()!=T_UNDEFINED)
 				tmb.t_gett()->uas.request->msg_flags |= FL_SDP_BODY;
 			if(route_type==FAILURE_ROUTE)
-				return unforce_rtp_proxy_f(msg, flags, 0);
+				return unforce_rtp_proxy(msg, flags);
 			return force_rtp_proxy(msg, flags, (cp!=NULL)?newip:ip, 1,
 					(ip!=NULL)?1:0);
 		}
 	} else if(msg->first_line.type == SIP_REPLY) {
 		if(msg->first_line.u.reply.statuscode>=300)
-			return unforce_rtp_proxy_f(msg, flags, 0);
+			return unforce_rtp_proxy(msg, flags);
 		if(nosdp==0) {
 			if(method==METHOD_UPDATE)
 				return force_rtp_proxy(msg, flags, (cp!=NULL)?newip:ip, 0,
@@ -2068,16 +2084,26 @@ rtpproxy_offer1_f(struct sip_msg *msg, char *str1, char *str2)
 {
         char *cp;
         char newip[IP_ADDR_MAX_STR_SIZE];
+	str flags;
 
         cp = ip_addr2a(&msg->rcv.dst_ip);
         strcpy(newip, cp);
-	return force_rtp_proxy(msg, str1, newip, 1, 0);
+
+	if (str1)
+		get_str_fparam(&flags, msg, (fparam_t *) str1);
+	else
+		flags.s = NULL;
+	return force_rtp_proxy(msg, flags.s, newip, 1, 0);
 }
 
 static int
 rtpproxy_offer2_f(struct sip_msg *msg, char *param1, char *param2)
 {
-	return force_rtp_proxy(msg, param1, param2, 1, 1);
+	str flags, new_ip;
+
+	get_str_fparam(&flags, msg, (fparam_t *) param1);
+	get_str_fparam(&new_ip, msg, (fparam_t *) param2);
+	return force_rtp_proxy(msg, flags.s, new_ip.s, 1, 1);
 }
 
 static int
@@ -2085,6 +2111,7 @@ rtpproxy_answer1_f(struct sip_msg *msg, char *str1, char *str2)
 {
         char *cp;
         char newip[IP_ADDR_MAX_STR_SIZE];
+	str flags;
 
 	if (msg->first_line.type == SIP_REQUEST)
 		if (msg->first_line.u.request.method_value != METHOD_ACK)
@@ -2092,18 +2119,27 @@ rtpproxy_answer1_f(struct sip_msg *msg, char *str1, char *str2)
 
         cp = ip_addr2a(&msg->rcv.dst_ip);
         strcpy(newip, cp);
-	return force_rtp_proxy(msg, str1, newip, 0, 0);
+
+	if (str1)
+		get_str_fparam(&flags, msg, (fparam_t *) str1);
+	else
+		flags.s = NULL;
+	return force_rtp_proxy(msg, flags.s, newip, 0, 0);
 }
 
 static int
 rtpproxy_answer2_f(struct sip_msg *msg, char *param1, char *param2)
 {
 
+	str flags, new_ip;
+
 	if (msg->first_line.type == SIP_REQUEST)
 		if (msg->first_line.u.request.method_value != METHOD_ACK)
 			return -1;
 
-	return force_rtp_proxy(msg, param1, param2, 0, 1);
+	get_str_fparam(&flags, msg, (fparam_t *) param1);
+	get_str_fparam(&new_ip, msg, (fparam_t *) param2);
+	return force_rtp_proxy(msg, flags.s, new_ip.s, 0, 1);
 }
 
 

+ 8 - 8
modules/snmpstats/README

@@ -254,10 +254,10 @@ Chapter 1. Admin Guide
      * usrloc - all scalars and tables relating to users and contacts are
        dependent on the usrloc module. If the module is not loaded, the
        respective tables will be empty.
-     * dialog - all scalars relating to the number of dialogs are
-       dependent on the presence of the dialog module. Furthermore, if the
-       module is not loaded, then the kamailioDialogLimitMinorEvent, and
-       kamailioDialogLimitMajorEvent alarm will be disabled.
+     * dialog or dialog-ng - all scalars relating to the number of dialogs
+       are dependent on the presence of a dialog module. Furthermore, if
+       the module is not loaded, then the kamailioDialogLimitMinorEvent,
+       and kamailioDialogLimitMajorEvent alarm will be disabled.
 
    The contents of the kamailioSIPMethodSupportedTable change depending on
    which modules are loaded.
@@ -429,7 +429,7 @@ modparam("snmpstats", "export_registrar", 1)
    There are several things that need to be done to get the SNMPStats
    module compiled and up and running.
 
-6.1.  Compiling the SNMPStats Module
+6.1. Compiling the SNMPStats Module
 
    In order for the SNMPStats module to compile, you will need at least
    version 5.3 of the NetSNMP source code. The source can be found at:
@@ -451,7 +451,7 @@ modparam("snmpstats", "export_registrar", 1)
    recommended you install NetSNMP from source to avoid bringing in
    excessive dependencies to the SNMPStats module.
 
-6.2.  Configuring NetSNMP to allow connections from the SNMPStats module.
+6.2. Configuring NetSNMP to allow connections from the SNMPStats module.
 
    The SNMPStats module will communicate with the NetSNMP Master Agent.
    This communication happens over a protocol known as AgentX. This means
@@ -483,7 +483,7 @@ modparam("snmpstats", "export_registrar", 1)
    This tells NetSNMP to act as a master agent, listening on the localhost
    UDP interface at port 705.
 
-6.3.  Configuring the SNMPStats module for communication with a Master Agent
+6.3. Configuring the SNMPStats module for communication with a Master Agent
 
    The previous section explained how to set up a NetSNMP master agent to
    accept AgentX connections. We now need to tell the SNMPStats module how
@@ -503,7 +503,7 @@ modparam("snmpstats", "export_registrar", 1)
    be present on the same machine as Kamailio. localhost could be replaced
    with any other machine.
 
-6.4.  Testing for a proper Configuration
+6.4. Testing for a proper Configuration
 
    As a quick test to make sure that the SNMPStats module sub-agent can
    succesfully connect to the NetSNMP Master agent, start snmpd with the

+ 10 - 10
modules/snmpstats/doc/snmpstats_admin.xml

@@ -203,8 +203,8 @@
 		
 		<listitem>
 		<para>
-		<emphasis>dialog</emphasis> - all scalars relating to the number of dialogs are 
-		dependent on the presence of the dialog module.  Furthermore, if the module is 
+		<emphasis>dialog or dialog-ng</emphasis> - all scalars relating to the number of dialogs are 
+		dependent on the presence of a dialog module.  Furthermore, if the module is 
 		not loaded, then the kamailioDialogLimitMinorEvent, and kamailioDialogLimitMajorEvent
 		alarm will be disabled. 
 		</para>
@@ -243,7 +243,7 @@
 	</section>
 	<section>
 	<title>Parameters</title>
-	<section>
+	<section id ="snmpstats.p.sipentitytape">
 		<title><varname>sipEntityType</varname> (String) </title>
 
 		<para>
@@ -275,7 +275,7 @@ modparam("snmpstats", "sipEntityType", "proxyServer")
 		</para>
 	</section>
 
-	<section>
+	<section id ="snmpstats.p.MsqQueueMinorTreshold">
 		<title><varname>MsgQueueMinorThreshold</varname> (Integer)</title>
 
 		<para>
@@ -300,7 +300,7 @@ modparam("snmpstats", "MsgQueueMinorThreshold", 2000)
 		</para>
 	</section>
 	
-	<section>
+	<section id ="snmpstats.p.MsqQueueMajorTreshold">
 		<title><varname>MsgQueueMajorThreshold</varname> (Integer)</title>
 
 		<para>
@@ -325,7 +325,7 @@ modparam("snmpstats", "MsgQueueMajorThreshold", 5000)
 		</para>
 	</section>
 
-	<section>
+	<section id ="snmpstats.p.dlg_minor_treshold">
 		<title><varname>dlg_minor_threshold</varname> (Integer)</title>
 
 		<para>
@@ -349,7 +349,7 @@ modparam("snmpstats", "MsgQueueMajorThreshold", 5000)
 		</para>
 	</section>
 
-	<section>
+	<section id ="snmpstats.p.dlg_major_treshold">
 		<title><varname>dlg_major_threshold</varname> (Integer)</title>
 
 		<para>
@@ -373,7 +373,7 @@ modparam("snmpstats", "MsgQueueMajorThreshold", 5000)
 		</para>
 	</section>
 
-	<section>
+	<section id ="snmpstats.p.snmpgetPath">
 		<title><varname>snmpgetPath</varname> (String)</title>
 
 		<para>
@@ -399,7 +399,7 @@ modparam("snmpstats", "snmpgetPath",     "/my/custom/path/")
 		</example>
 	</section>
 	
-	<section>
+	<section id ="snmpstats.p.snmpCommunity">
 		<title><varname>snmpCommunity</varname> (String)</title>
 
 		<para>
@@ -425,7 +425,7 @@ modparam("snmpstats", "snmpCommunity", "customCommunityString")
 		</example>
 	</section>
 
-	<section>
+	<section id ="snmpstats.p.export_registrar">
 		<title><varname>export_registrar</varname> (int)</title>
 
 		<para>

+ 402 - 0
modules/snmpstats/kamailioNet.c

@@ -0,0 +1,402 @@
+/*
+ * SNMPStats Module  - Network Statistics
+ *
+ * Copyright (C) 2006 SOMA Networks, INC.
+ * Written by: Jeffrey Magder ([email protected])
+ *
+ * Kamailio Server Net objects addition
+ * Copyright (C) 2013 Edvina AB, Sollentuna, Sweden
+ * Written by Olle E. Johansson
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * History:
+ * --------
+ * 2013-03-24 initial version (oej)
+ * 
+ * Note: this file originally auto-generated by mib2c 
+ *
+ */
+
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+#include <net-snmp/agent/net-snmp-agent-includes.h>
+#include "kamailioNet.h"
+
+#include "snmpstats_globals.h"
+#include "utilities.h"
+#include "../../lib/kcore/statistics.h"
+#include "../../globals.h"
+#include "../../tcp_options.h"
+
+/*
+ * Note: this file originally auto-generated by mib2c using
+ *        $
+ */
+
+
+/** Initializes the kamailioNet module */
+void
+init_kamailioNet(void)
+{
+    const oid kamailioNetTcpConnEstablished_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,4,1,2,1 };
+    const oid kamailioNetTcpConnFailed_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,4,1,2,2 };
+    const oid kamailioNetTcpConnReset_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,4,1,2,3 };
+    const oid kamailioNetTcpConnSuccess_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,4,1,2,4 };
+    const oid kamailioNetTcpConnOpened_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,4,1,2,5 };
+    const oid kamailioNetTcpConnPassiveOpen_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,4,1,2,6 };
+    const oid kamailioNetTcpConnReject_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,4,1,2,8 };
+    const oid kamailioNetTcpEnabled_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,4,1,3,1 };
+    const oid kamailioNetTcpMaxConns_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,4,1,3,2 };
+    const oid kamailioNetTcpAsync_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,4,1,3,3 };
+
+  DEBUGMSGTL(("kamailioNet", "Initializing\n"));
+
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioNetTcpConnEstablished", handle_kamailioNetTcpConnEstablished,
+                               kamailioNetTcpConnEstablished_oid, OID_LENGTH(kamailioNetTcpConnEstablished_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioNetTcpConnFailed", handle_kamailioNetTcpConnFailed,
+                               kamailioNetTcpConnFailed_oid, OID_LENGTH(kamailioNetTcpConnFailed_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioNetTcpConnReset", handle_kamailioNetTcpConnReset,
+                               kamailioNetTcpConnReset_oid, OID_LENGTH(kamailioNetTcpConnReset_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioNetTcpConnSuccess", handle_kamailioNetTcpConnSuccess,
+                               kamailioNetTcpConnSuccess_oid, OID_LENGTH(kamailioNetTcpConnSuccess_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioNetTcpConnOpened", handle_kamailioNetTcpConnOpened,
+                               kamailioNetTcpConnOpened_oid, OID_LENGTH(kamailioNetTcpConnOpened_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioNetTcpConnPassiveOpen", handle_kamailioNetTcpConnPassiveOpen,
+                               kamailioNetTcpConnPassiveOpen_oid, OID_LENGTH(kamailioNetTcpConnPassiveOpen_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioNetTcpConnReject", handle_kamailioNetTcpConnReject,
+                               kamailioNetTcpConnReject_oid, OID_LENGTH(kamailioNetTcpConnReject_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioNetTcpEnabled", handle_kamailioNetTcpEnabled,
+                               kamailioNetTcpEnabled_oid, OID_LENGTH(kamailioNetTcpEnabled_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioNetTcpMaxConns", handle_kamailioNetTcpMaxConns,
+                               kamailioNetTcpMaxConns_oid, OID_LENGTH(kamailioNetTcpMaxConns_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioNetTcpAsync", handle_kamailioNetTcpAsync,
+                               kamailioNetTcpAsync_oid, OID_LENGTH(kamailioNetTcpAsync_oid),
+                               HANDLER_CAN_RONLY
+        ));
+}
+
+#ifdef SKREP
+	con_timeout: 0
+	connect_success: 0
+	current_write_queue_size: 0
+	local_reject: 0
+	passive_open: 0
+	send_timeout: 0
+	sendq_full: 0
+#endif
+
+int
+handle_kamailioNetTcpConnEstablished(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    /* We are never called for a GETNEXT if it's registered as a
+       "instance", as it's "magically" handled for us.  */
+
+    /* a instance handler also only hands us one request at a time, so
+       we don't need to loop over a list of requests; we'll only get one. */
+
+	int datafield = get_statistic("established");
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_COUNTER,
+			(u_char *) &datafield, sizeof(int));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioNetTcpConnEstablished\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+int
+handle_kamailioNetTcpConnFailed(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+
+	int datafield = get_statistic("connect_failed");
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_COUNTER,
+			(u_char *) &datafield, sizeof(int));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioNetTcpConnFailed\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+int
+handle_kamailioNetTcpConnReset(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+	int datafield = get_statistic("con_reset");
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_COUNTER,
+			(u_char *) &datafield, sizeof(int));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioNetTcpConnReset\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+int
+handle_kamailioNetTcpConnSuccess(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+
+	int datafield = get_statistic("connect_success");
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_COUNTER,
+			(u_char *) &datafield, sizeof(int));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioNetTcpConnSuccess\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+int
+handle_kamailioNetTcpConnOpened(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+	int datafield = get_statistic("current_opened_connections");
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_GAUGE,
+			(u_char *) &datafield, sizeof(int));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioNetTcpConnOpened\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+int
+handle_kamailioNetTcpConnPassiveOpen(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+	int datafield = get_statistic("passive_open");
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_COUNTER,
+			(u_char *) &datafield, sizeof(int));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioNetTcpConnPassiveOpen\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+int
+handle_kamailioNetTcpConnReject(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    /* We are never called for a GETNEXT if it's registered as a
+       "instance", as it's "magically" handled for us.  */
+
+    /* a instance handler also only hands us one request at a time, so
+       we don't need to loop over a list of requests; we'll only get one. */
+	int datafield = get_statistic("local_reject");
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_COUNTER,
+			(u_char *) &datafield, sizeof(int));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioNetTcpConnReject\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+int
+handle_kamailioNetTcpEnabled(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    /* We are never called for a GETNEXT if it's registered as a
+       "instance", as it's "magically" handled for us.  */
+
+    /* a instance handler also only hands us one request at a time, so
+       we don't need to loop over a list of requests; we'll only get one. */
+	int enabled = (tcp_disable == 0);
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER,
+			(u_char *) &enabled, sizeof(int));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioNetTcpEnabled\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+
+int
+handle_kamailioNetTcpMaxConns(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    struct cfg_group_tcp t;
+    unsigned int maxconn;
+
+    tcp_options_get(&t);
+    maxconn = t.max_connections;
+
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER,
+			 (u_char *) &maxconn, sizeof(int));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioNetTcpMaxConns\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+int
+handle_kamailioNetTcpAsync(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    struct cfg_group_tcp t;
+    unsigned int value;
+
+    tcp_options_get(&t);
+    value = t.async;
+    /* We are never called for a GETNEXT if it's registered as a
+       "instance", as it's "magically" handled for us.  */
+
+    /* a instance handler also only hands us one request at a time, so
+       we don't need to loop over a list of requests; we'll only get one. */
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_INTEGER,
+			 (u_char *) &value, sizeof(int));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioNetTcpAsync\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}

+ 21 - 0
modules/snmpstats/kamailioNet.h

@@ -0,0 +1,21 @@
+/*
+ * Note: this file originally auto-generated by mib2c using
+ *        $
+ */
+#ifndef KAMAILIONET_H
+#define KAMAILIONET_H
+
+/* function declarations */
+void init_kamailioNet(void);
+Netsnmp_Node_Handler handle_kamailioNetTcpConnEstablished;
+Netsnmp_Node_Handler handle_kamailioNetTcpConnFailed;
+Netsnmp_Node_Handler handle_kamailioNetTcpConnReset;
+Netsnmp_Node_Handler handle_kamailioNetTcpConnSuccess;
+Netsnmp_Node_Handler handle_kamailioNetTcpConnOpened;
+Netsnmp_Node_Handler handle_kamailioNetTcpConnPassiveOpen;
+Netsnmp_Node_Handler handle_kamailioNetTcpConnReject;
+Netsnmp_Node_Handler handle_kamailioNetTcpEnabled;
+Netsnmp_Node_Handler handle_kamailioNetTcpMaxConns;
+Netsnmp_Node_Handler handle_kamailioNetTcpAsync;
+
+#endif /* KAMAILIONET_H */

+ 440 - 0
modules/snmpstats/kamailioServer.c

@@ -0,0 +1,440 @@
+/*
+ *
+ * SNMPStats Module 
+ * Copyright (C) 2006 SOMA Networks, INC.
+ * Written by: Jeffrey Magder ([email protected])
+ *
+ * Kamailio Server core objects addition
+ * Copyright (C) 2013 Edvina AB, Sollentuna, Sweden
+ * Written by Olle E. Johansson
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * History:
+ * --------
+ * 2013-03-24 initial version (oej)
+ * 
+ * Note: this file originally auto-generated by mib2c 
+ *
+ */
+
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+#include <net-snmp/agent/net-snmp-agent-includes.h>
+#include "kamailioServer.h"
+
+#include "snmpstats_globals.h"
+#include "utilities.h"
+#include "../../lib/kcore/statistics.h"
+#include "../../ver.h"
+
+/** Initializes the kamailioServer module */
+void
+init_kamailioServer(void)
+{
+    const oid kamailioSrvMaxMemory_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,1,1,1 };
+    const oid kamailioSrvFreeMemory_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,1,1,2 };
+
+    const oid kamailioSrvCnfFullVersion_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,1,2,1 };
+    const oid kamailioSrvCnfVerName_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,1,2,2 };
+    const oid kamailioSrvCnfVerVersion_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,1,2,3 };
+    const oid kamailioSrvCnfVerArch_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,1,2,4 };
+    const oid kamailioSrvCnfVerOs_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,1,2,5 };
+    const oid kamailioSrvCnfVerId_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,1,2,6 };
+    const oid kamailioSrvCnfVerCompTime_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,1,2,7 };
+    const oid kamailioSrvCnfVerCompiler_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,1,2,8 };
+    const oid kamailioSrvCnfVerFlags_oid[] = { 1,3,6,1,4,1,34352,3,1,3,1,1,2,9 };
+
+     DEBUGMSGTL(("kamailioServer", "Initializing\n"));
+     LM_DBG("initializing Kamailio Server OID's X\n");
+
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioSrvMaxMemory", handle_kamailioSrvMaxMemory,
+                               kamailioSrvMaxMemory_oid, OID_LENGTH(kamailioSrvMaxMemory_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioSrvFreeMemory", handle_kamailioSrvFreeMemory,
+                               kamailioSrvFreeMemory_oid, OID_LENGTH(kamailioSrvFreeMemory_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioSrvCnfFullVersion", handle_kamailioSrvCnfFullVersion,
+                               kamailioSrvCnfFullVersion_oid, OID_LENGTH(kamailioSrvCnfFullVersion_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioSrvCnfVerName", handle_kamailioSrvCnfVerName,
+                               kamailioSrvCnfVerName_oid, OID_LENGTH(kamailioSrvCnfVerName_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioSrvCnfVerVersion", handle_kamailioSrvCnfVerVersion,
+                               kamailioSrvCnfVerVersion_oid, OID_LENGTH(kamailioSrvCnfVerVersion_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioSrvCnfVerArch", handle_kamailioSrvCnfVerArch,
+                               kamailioSrvCnfVerArch_oid, OID_LENGTH(kamailioSrvCnfVerArch_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioSrvCnfVerOs", handle_kamailioSrvCnfVerOs,
+                               kamailioSrvCnfVerOs_oid, OID_LENGTH(kamailioSrvCnfVerOs_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioSrvCnfVerId", handle_kamailioSrvCnfVerId,
+                               kamailioSrvCnfVerId_oid, OID_LENGTH(kamailioSrvCnfVerId_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioSrvCnfVerCompTime", handle_kamailioSrvCnfVerCompTime,
+                               kamailioSrvCnfVerCompTime_oid, OID_LENGTH(kamailioSrvCnfVerCompTime_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioSrvCnfVerCompiler", handle_kamailioSrvCnfVerCompiler,
+                               kamailioSrvCnfVerCompiler_oid, OID_LENGTH(kamailioSrvCnfVerCompiler_oid),
+                               HANDLER_CAN_RONLY
+        ));
+    netsnmp_register_scalar(
+        netsnmp_create_handler_registration("kamailioSrvCnfVerFlags", handle_kamailioSrvCnfVerFlags,
+                               kamailioSrvCnfVerFlags_oid, OID_LENGTH(kamailioSrvCnfVerFlags_oid),
+                               HANDLER_CAN_RONLY
+        ));
+}
+
+int
+handle_kamailioSrvMaxMemory(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    /* We are never called for a GETNEXT if it's registered as a
+       "instance", as it's "magically" handled for us.  */
+
+    /* a instance handler also only hands us one request at a time, so
+       we don't need to loop over a list of requests; we'll only get one. */
+
+    int maxmemory = get_statistic("total_size");
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_GAUGE,
+                        	(u_char *) &maxmemory, sizeof(int));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioSrvMaxMemory\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+
+
+int
+handle_kamailioSrvFreeMemory(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    int freememory = get_statistic("free_size");
+
+    /* We are never called for a GETNEXT if it's registered as a
+       "instance", as it's "magically" handled for us.  */
+
+    /* a instance handler also only hands us one request at a time, so
+       we don't need to loop over a list of requests; we'll only get one. */
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_GAUGE,
+                        	(u_char *) &freememory, sizeof(int));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioSrvFreeMemory\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+
+
+int
+handle_kamailioSrvCnfFullVersion(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    /* We are never called for a GETNEXT if it's registered as a
+       "instance", as it's "magically" handled for us.  */
+
+    /* a instance handler also only hands us one request at a time, so
+       we don't need to loop over a list of requests; we'll only get one. */
+
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
+					full_version, strlen(full_version));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioSrvCnfFullVersion\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+
+int
+handle_kamailioSrvCnfVerName(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    /* We are never called for a GETNEXT if it's registered as a
+       "instance", as it's "magically" handled for us.  */
+
+    /* a instance handler also only hands us one request at a time, so
+       we don't need to loop over a list of requests; we'll only get one. */
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
+					ver_name, strlen(ver_name));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioSrvCnfVerName\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+int
+handle_kamailioSrvCnfVerVersion(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    /* We are never called for a GETNEXT if it's registered as a
+       "instance", as it's "magically" handled for us.  */
+
+    /* a instance handler also only hands us one request at a time, so
+       we don't need to loop over a list of requests; we'll only get one. */
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
+					ver_version, strlen(ver_version));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioSrvCnfVerVersion\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+int
+handle_kamailioSrvCnfVerArch(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    /* We are never called for a GETNEXT if it's registered as a
+       "instance", as it's "magically" handled for us.  */
+
+    /* a instance handler also only hands us one request at a time, so
+       we don't need to loop over a list of requests; we'll only get one. */
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
+					ver_arch, strlen(ver_arch));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioSrvCnfVerArch\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+int
+handle_kamailioSrvCnfVerOs(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    /* We are never called for a GETNEXT if it's registered as a
+       "instance", as it's "magically" handled for us.  */
+
+    /* a instance handler also only hands us one request at a time, so
+       we don't need to loop over a list of requests; we'll only get one. */
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
+					ver_os, strlen(ver_os));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioSrvCnfVerOs\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+int
+handle_kamailioSrvCnfVerId(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    /* We are never called for a GETNEXT if it's registered as a
+       "instance", as it's "magically" handled for us.  */
+
+    /* a instance handler also only hands us one request at a time, so
+       we don't need to loop over a list of requests; we'll only get one. */
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
+					ver_id, strlen(ver_id));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioSrvCnfVerId\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+int
+handle_kamailioSrvCnfVerCompTime(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    /* We are never called for a GETNEXT if it's registered as a
+       "instance", as it's "magically" handled for us.  */
+
+    /* a instance handler also only hands us one request at a time, so
+       we don't need to loop over a list of requests; we'll only get one. */
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
+					ver_compiled_time, strlen(ver_compiled_time));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioSrvCnfVerCompTime\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+int
+handle_kamailioSrvCnfVerCompiler(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    /* We are never called for a GETNEXT if it's registered as a
+       "instance", as it's "magically" handled for us.  */
+
+    /* a instance handler also only hands us one request at a time, so
+       we don't need to loop over a list of requests; we'll only get one. */
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
+					ver_compiler, strlen(ver_compiler));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioSrvCnfVerCompiler\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}
+int
+handle_kamailioSrvCnfVerFlags(netsnmp_mib_handler *handler,
+                          netsnmp_handler_registration *reginfo,
+                          netsnmp_agent_request_info   *reqinfo,
+                          netsnmp_request_info         *requests)
+{
+    /* We are never called for a GETNEXT if it's registered as a
+       "instance", as it's "magically" handled for us.  */
+
+    /* a instance handler also only hands us one request at a time, so
+       we don't need to loop over a list of requests; we'll only get one. */
+    
+    switch(reqinfo->mode) {
+
+        case MODE_GET:
+            snmp_set_var_typed_value(requests->requestvb, ASN_OCTET_STR,
+					ver_flags, strlen(ver_flags));
+            break;
+
+
+        default:
+            /* we should never get here, so this is a really bad error */
+            snmp_log(LOG_ERR, "unknown mode (%d) in handle_kamailioSrvCnfVerFlags\n", reqinfo->mode );
+            return SNMP_ERR_GENERR;
+    }
+
+    return SNMP_ERR_NOERROR;
+}

+ 52 - 0
modules/snmpstats/kamailioServer.h

@@ -0,0 +1,52 @@
+/*
+ * SNMPStats Module 
+ * Copyright (C) 2006 SOMA Networks, INC.
+ * Written by: Jeffrey Magder ([email protected])
+ *
+ * Kamailio Server core objects addition
+ * Copyright (C) 2013 Edvina AB, Sollentuna, Sweden
+ * Written by Olle E. Johansson
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * History:
+ * --------
+ * 2013-03-24 initial version (oej)
+ * 
+ * Note: this file originally auto-generated by mib2c 
+ *
+ */
+#ifndef KAMAILIOSERVER_H
+#define KAMAILIOSERVER_H
+
+/* function declarations */
+void init_kamailioServer(void);
+Netsnmp_Node_Handler handle_kamailioSrvMaxMemory;
+Netsnmp_Node_Handler handle_kamailioSrvFreeMemory;
+
+Netsnmp_Node_Handler handle_kamailioSrvCnfFullVersion;
+Netsnmp_Node_Handler handle_kamailioSrvCnfVerName;
+Netsnmp_Node_Handler handle_kamailioSrvCnfVerVersion;
+Netsnmp_Node_Handler handle_kamailioSrvCnfVerArch;
+Netsnmp_Node_Handler handle_kamailioSrvCnfVerOs;
+Netsnmp_Node_Handler handle_kamailioSrvCnfVerId;
+Netsnmp_Node_Handler handle_kamailioSrvCnfVerCompTime;
+Netsnmp_Node_Handler handle_kamailioSrvCnfVerCompiler;
+Netsnmp_Node_Handler handle_kamailioSrvCnfVerFlags;
+
+#endif /* KAMAILIOSERVER_H */

+ 302 - 2
modules/snmpstats/mibs/KAMAILIO-MIB

@@ -7,6 +7,7 @@
 -- 
 -- Copyright (c) The Internet Society (2006)
 -- Ammendments (c) Soma Networks, Inc. (2006)
+-- Kamailio extras (c) Edvina AB, 2013
 --
 -- All rights reserved.
 -- *****************************************************************
@@ -25,7 +26,11 @@ KAMAILIO-MIB DEFINITIONS ::= BEGIN
         Gauge32
             FROM SNMPv2-SMI
             
-        DateAndTime
+        DateAndTime,
+	TEXTUAL-CONVENTION,
+	DisplayString,
+	RowStatus,
+	TruthValue
             FROM SNMPv2-TC
 
         SnmpAdminString
@@ -72,6 +77,9 @@ KAMAILIO-MIB DEFINITIONS ::= BEGIN
         REVISION   "201301081200Z"
         DESCRIPTION
            "Renamed MIB and mib entries to new product name, Kamailio."
+        REVISION   "201303231200Z"
+        DESCRIPTION
+           "Added Kamailio core memory allocation data."
         ::= { kamailioModules 5 }
 
 --
@@ -94,7 +102,6 @@ KAMAILIO-MIB DEFINITIONS ::= BEGIN
         DESCRIPTION 
         "Sub-tree for Conformance specifications."
         ::= { kamailioMIB 3 }
-                                
 
 --
 -- kamailioObjects sub-components
@@ -117,9 +124,302 @@ KAMAILIO-MIB DEFINITIONS ::= BEGIN
         "Sub-tree for tracking of SIP Dialogs being processed by Kamailio."
     ::= { kamailioObjects 3 }
 
+   kamailioNet OBJECT-IDENTITY
+        STATUS current
+        DESCRIPTION 
+        "Sub-tree for tracking of Network Transports being enabled in Kamailio."
+    ::= { kamailioObjects 4 }
+
 -- 
 -- Kamailio Server Objects
 --
+    kamailioSrvMemory OBJECT-IDENTITY
+        STATUS current
+        DESCRIPTION 
+        "Sub-tree for Core Memory Statistics." 
+        ::= { kamailioServer 1 }
+
+    kamailioSrvConfig OBJECT-IDENTITY
+        STATUS current
+        DESCRIPTION 
+        "Sub-tree for Core configuration - compile flags etc." 
+        ::= { kamailioServer 2 }
+
+    kamailioSrvCounters OBJECT-IDENTITY
+        STATUS current
+        DESCRIPTION 
+        "Sub-tree for Core Counters/Statistic variables." 
+        ::= { kamailioServer 3 }
+
+    kamailioSrvProcess OBJECT-IDENTITY
+        STATUS current
+        DESCRIPTION 
+        "Sub-tree for Core Processes." 
+        ::= { kamailioServer 4 }
+
+-- 
+-- Kamailio Server Configuration
+--
+
+    kamailioSrvCnfFullVersion OBJECT-TYPE
+        SYNTAX DisplayString
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Full version of Kamailio"
+        ::= { kamailioSrvConfig 1 }
+
+    kamailioSrvCnfVerName OBJECT-TYPE
+        SYNTAX DisplayString
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Version of Kamailio"
+        ::= { kamailioSrvConfig 2 }
+
+    kamailioSrvCnfVerVersion OBJECT-TYPE
+        SYNTAX DisplayString
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Version of Kamailio"
+        ::= { kamailioSrvConfig 3 }
+
+    kamailioSrvCnfVerArch OBJECT-TYPE
+        SYNTAX      DisplayString
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Architecture Kamailio is compiled for"
+        ::= { kamailioSrvConfig 4 }
+
+    kamailioSrvCnfVerOs	OBJECT-TYPE
+        SYNTAX DisplayString
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Operating System Kamailio is compiled for"
+        ::= { kamailioSrvConfig 5 }
+
+    kamailioSrvCnfVerId	OBJECT-TYPE
+        SYNTAX DisplayString
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Kamailio Version ID"
+        ::= { kamailioSrvConfig 6 }
+
+    kamailioSrvCnfVerCompTime OBJECT-TYPE
+        SYNTAX DisplayString
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Kamailio Build time"
+        ::= { kamailioSrvConfig 7 }
+
+    kamailioSrvCnfVerCompiler OBJECT-TYPE
+        SYNTAX DisplayString
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Kamailio Build compiler used"
+        ::= { kamailioSrvConfig 8 }
+
+    kamailioSrvCnfVerFlags OBJECT-TYPE
+        SYNTAX DisplayString
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Kamailio Build compile time flags"
+        ::= { kamailioSrvConfig 9 }
+
+
+-- 
+-- Kamailio Server Memory data
+--
+    kamailioSrvMaxMemory OBJECT-TYPE
+        SYNTAX      Gauge32
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Maximum available Shared Memory"
+        ::= { kamailioSrvMemory 1 }
+
+    kamailioSrvFreeMemory OBJECT-TYPE
+        SYNTAX      Gauge32
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Available free memory in the Shared Memory pool"
+        ::= { kamailioSrvMemory 2 }
+
+-- 
+-- Kamailio Server Core counters
+--
+
+-- 
+-- Kamailio Network Objects
+--
+    kamailioNetTcp OBJECT-IDENTITY
+        STATUS current
+        DESCRIPTION 
+        "Sub-tree for TCP Network Transport objects (incl TLS conncetions)." 
+        ::= { kamailioNet 1 }
+
+
+    kamailioNetWebsocket OBJECT-IDENTITY
+        STATUS current
+        DESCRIPTION 
+        "Sub-tree for Websocket Network Transport objects ." 
+        ::= { kamailioNet 2 }
+
+    kamailioNetHttp OBJECT-IDENTITY
+        STATUS current
+        DESCRIPTION 
+        "Sub-tree for HTTP Server Transport objects ." 
+        ::= { kamailioNet 3 }
+
+
+--
+-- TCP connection objects
+--
+    kamailioNetTcpTls OBJECT-IDENTITY
+        STATUS current
+        DESCRIPTION 
+        "Sub-tree for TCP/TLS objects." 
+        ::= { kamailioNetTcp 1 }
+
+    kamailioNetTcpStat OBJECT-IDENTITY
+        STATUS current
+        DESCRIPTION 
+        "Sub-tree for TCP/TLS statistics." 
+        ::= { kamailioNetTcp 2 }
+
+    kamailioNetTcpConfig OBJECT-IDENTITY
+        STATUS current
+        DESCRIPTION 
+        "Sub-tree for TCP/TLS configuration data." 
+        ::= { kamailioNetTcp 3 }
+
+--
+-- TCP connection config objects
+--
+    kamailioNetTcpEnabled OBJECT-TYPE
+        SYNTAX  TruthValue
+        MAX-ACCESS  read-only
+        STATUS current
+	DEFVAL { false }
+        DESCRIPTION 
+        "True if TCP is enabled in this server."
+        ::= { kamailioNetTcpConfig 1 }
+
+    kamailioNetTcpMaxConns OBJECT-TYPE
+        SYNTAX  Integer32
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Maximum number of TCP connections (configurable)"
+        ::= { kamailioNetTcpConfig 2 }
+
+    kamailioNetTcpAsync OBJECT-TYPE
+        SYNTAX  TruthValue
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "True if TCP Async is enabled in this server."
+        ::= { kamailioNetTcpConfig 3 }
+
+	---	connect_timeout: 10
+	---	send_timeout: 10
+	---	connection_lifetime: 120
+	---	max_connections(soft): 2048
+	---	max_tls_connections(soft): 2048
+	---	no_connect: 0
+	---	fd_cache: 1
+	---	async: 1
+	---	connect_wait: 1
+	---	conn_wq_max: 32768
+	---	wq_max: 10485760
+	---	defer_accept: 0
+	---	delayed_ack: 0
+	---	syncnt: 0
+	---	linger2: 0
+	---	keepalive: 1
+	---	keepidle: 0
+	---	keepintvl: 0
+	---	keepcnt: 0
+	---	crlf_ping: 1
+	---	accept_aliases: 0
+	---	alias_flags: 1
+	---	new_conn_alias_flags: 2
+
+--
+-- TCP connection statistic objects
+--
+    kamailioNetTcpConnEstablished OBJECT-TYPE
+        SYNTAX      Counter32
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Incremented each time a TCP connection is established."
+        ::= { kamailioNetTcpStat 1 }
+
+    kamailioNetTcpConnFailed OBJECT-TYPE
+        SYNTAX      Counter32
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Number of failed active TCP connection attempts."
+        ::= { kamailioNetTcpStat 2 }
+
+    kamailioNetTcpConnReset OBJECT-TYPE
+        SYNTAX      Counter32
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Number of reset TCP connections"
+        ::= { kamailioNetTcpStat 3 }
+
+    kamailioNetTcpConnSuccess OBJECT-TYPE
+        SYNTAX      Counter32
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Number of successful TCP connections"
+        ::= { kamailioNetTcpStat 4 }
+
+    kamailioNetTcpConnOpened OBJECT-TYPE
+        SYNTAX      Gauge32
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Number of current opened TCP connections"
+        ::= { kamailioNetTcpStat 5 }
+
+    kamailioNetTcpConnPassiveOpen OBJECT-TYPE
+        SYNTAX      Counter32
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Total number of accepted TCP connections (so far)."
+        ::= { kamailioNetTcpStat 6 }
+
+    kamailioNetTcpConnSuccess OBJECT-TYPE
+        SYNTAX      Counter32
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Total number of successfully active opened TCP connections"
+        ::= { kamailioNetTcpStat 7 }
+
+    kamailioNetTcpConnReject OBJECT-TYPE
+        SYNTAX      Counter32
+        MAX-ACCESS  read-only
+        STATUS current
+        DESCRIPTION 
+        "Number of rejected incoming TCP connections."
+        ::= { kamailioNetTcpStat 8 }
+
 
 
 --

+ 0 - 1
modules/snmpstats/mibs/KAMAILIO-SIP-COMMON-MIB

@@ -113,7 +113,6 @@ KAMAILIO-SIP-COMMON-MIB DEFINITIONS ::= BEGIN
               Registrar: A registrar is a server that accepts
               REGISTER requests and places the information it
               receives in those requests into the location service
-
               for the domain it handles.
 
               Copyright (C) The Internet Society (2005). This version

+ 5 - 15
modules/snmpstats/snmpSIPMethodSupportedTable.c

@@ -140,23 +140,16 @@ void init_kamailioSIPMethodSupportedTable(void)
 	 * NOTE: My way of checking what METHODS we support is probably wrong.
 	 * Please feel free to correct it! */
 	
-	if (module_loaded("sl")) {
-		createRow(1, "METHOD_INVITE");
-		createRow(2, "METHOD_CANCEL");
-		createRow(3, "METHOD_ACK");
-	}
-
-	if (module_loaded("tm")) {
-		createRow(4, "METHOD_BYE");
-	}
+	createRow(1, "METHOD_INVITE");
+	createRow(2, "METHOD_CANCEL");
+	createRow(3, "METHOD_ACK");
+	createRow(4, "METHOD_BYE");
 
 	if (module_loaded("options") || module_loaded("siputils")) {
 		createRow(6, "METHOD_OPTIONS");
 	}
 
-	if (module_loaded("dialog")) {
-		createRow(7, "METHOD_UPDATE");
-	}
+	createRow(7, "METHOD_UPDATE");
 
 	if (module_loaded("registrar")) {
 		createRow(8, "METHOD_REGISTER");
@@ -166,9 +159,6 @@ void init_kamailioSIPMethodSupportedTable(void)
 
 	createRow(5,  "METHOD_INFO");
 	createRow(9,  "METHOD_MESSAGE");
-
-	/* I'm not sure what these guys are, so saying we support them by
-	 * default.  */
 	createRow(12, "METHOD_PRACK");
 	createRow(13, "METHOD_REFER");
 	createRow(14, "METHOD_PUBLISH");

+ 1 - 1
modules/snmpstats/snmpSIPServerObjects.c

@@ -196,7 +196,7 @@ int handle_kamailioSIPProxyStatefulness(netsnmp_mib_handler *handler,
 {
 	int statefullness;
 
-	if (module_loaded("dialog")) 
+	if (module_loaded("dialog") || module_loaded("dialog_ng")) 
 	{
 		statefullness = PROXY_STATEFULNESS_CALL_STATEFUL;
 	}

+ 10 - 0
modules/snmpstats/sub_agent.c

@@ -63,6 +63,8 @@
 #include "snmpSIPContactTable.h"
 #include "snmpSIPRegUserLookupTable.h"
 #include "snmpMIBNotifications.h"
+#include "kamailioServer.h"
+#include "kamailioNet.h"
 
 #include "../../dprint.h"
 #include "../../cfg/cfg_struct.h"
@@ -91,6 +93,8 @@ static int initialize_agentx(void)
 {
 	/* We register with a master agent */
 	register_with_master_agent(AGENT_PROCESS_NAME);
+
+	LM_DBG("Initializing Kamailio OID's for SNMPD MasterX\n");
 	
 	/* Initialize all scalars, and let the master agent know we want to
 	 * handle all OID's pertaining to these scalars. */
@@ -106,6 +110,9 @@ static int initialize_agentx(void)
 	init_kamailioSIPRegUserTable();
 	init_kamailioSIPContactTable();
 	init_kamailioSIPRegUserLookupTable();
+	init_kamailioServer();
+	init_kamailioNet();
+	LM_DBG("Done initializing Kamailio OID's for SNMPD MasterX\n");
 
 	/* In case we recevie a request to stop (kill -TERM or kill -INT) */
 	keep_running = 1;
@@ -117,6 +124,7 @@ static int initialize_agentx(void)
 		agent_check_and_process(1); /* 0 == don't block */
 	}
 
+	LM_DBG("Shutting down Kamailio SNMPD MasterX sub agent.\n");
 	snmp_shutdown(AGENT_PROCESS_NAME);
 	SOCK_CLEANUP;
 	exit (0);
@@ -181,10 +189,12 @@ void register_with_master_agent(char *name_to_register_under)
 	/* Initialize TCP if necessary.  (Its here for WIN32) compatibility. */
 	SOCK_STARTUP;
 
+	LM_DBG("Connecting to SNMPD MasterX\n");
 	/* Read in our configuration file to determine master agent ping times
 	 * what port communication is to take place over, etc. */
 	init_agent("snmpstats");
 
 	/* Use a name we can register our agent under. */
 	init_snmp(name_to_register_under);
+	LM_DBG("** Connected to SNMPD MasterX\n");
 }

+ 28 - 23
modules/usrloc/README

@@ -580,6 +580,11 @@ modparam("usrloc", "db_url", "dbdriver://username:password@dbhost/dbname")
        For example NAT pinging is a killer since during each ping cycle
        all nated contact are loaded from the DB; The lack of memory
        caching also disable the statistics exports.
+     * 4 - This uses database to load records at startup but uses only
+       memory during the runtime. Records are not written back at all, not
+       even at shutdown. Useful for scenarios when registrations are
+       replicated to a node that does the storage in database during
+       runtime.
 
 Warning
 
@@ -740,7 +745,7 @@ modparam("usrloc", "xavp_contact", "ulattrs")
    5.5. ul_add
    5.6. ul_show_contact
 
-5.1.  ul_rm
+5.1. ul_rm
 
    Deletes an entire AOR record (including its contacts).
 
@@ -749,7 +754,7 @@ modparam("usrloc", "xavp_contact", "ulattrs")
      * AOR - user AOR in username[@domain] format (domain must be supplied
        only if use_domain option is on).
 
-5.2.  ul_rm_contact
+5.2. ul_rm_contact
 
    Deletes a contact from an AOR record.
 
@@ -759,7 +764,7 @@ modparam("usrloc", "xavp_contact", "ulattrs")
        only if use_domain option is on).
      * contact - exact contact to be removed
 
-5.3.  ul_dump
+5.3. ul_dump
 
    Dumps the entire content of the USRLOC in memory cache
 
@@ -768,11 +773,11 @@ modparam("usrloc", "xavp_contact", "ulattrs")
        "brief", a brief dump will be done (only AOR and contacts, with no
        other details)
 
-5.4.  ul_flush
+5.4. ul_flush
 
    Triggers the flush of USRLOC memory cache into DB.
 
-5.5.  ul_add
+5.5. ul_add
 
    Adds a new contact for an user AOR.
 
@@ -788,7 +793,7 @@ modparam("usrloc", "xavp_contact", "ulattrs")
      * cflags - per branch flags of the contact
      * methods - mask with supported requests of the contact
 
-5.6.  ul_show_contact
+5.6. ul_show_contact
 
    Dumps the contacts of an user AOR.
 
@@ -802,14 +807,14 @@ modparam("usrloc", "xavp_contact", "ulattrs")
    6.1. ul.dump
    6.2. ul.lookup table AOR
 
-6.1.  ul.dump
+6.1. ul.dump
 
    Dumps the content of the location table
 
    Parameters:
      * None.
 
-6.2.  ul.lookup table AOR
+6.2. ul.lookup table AOR
 
    Looks up the contents of an AOR entry in the location table
 
@@ -897,7 +902,7 @@ Chapter 2. Developer Guide
    1.14. ul_register_ulcb(type ,callback, param)
    1.15. ul_get_num_users()
 
-1.1.  ul_register_domain(name)
+1.1. ul_register_domain(name)
 
    The function registers a new domain. Domain is just another name for
    table used in registrar. The function is called from fixups in
@@ -912,7 +917,7 @@ Chapter 2. Developer Guide
      * const char* name - Name of the domain (also called table) to be
        registered.
 
-1.2.  ul_insert_urecord(domain, aor, rec)
+1.2. ul_insert_urecord(domain, aor, rec)
 
    The function creates a new record structure and inserts it in the
    specified domain. The record is structure that contains all the
@@ -927,7 +932,7 @@ Chapter 2. Developer Guide
 
      * urecord_t** rec - The newly created record structure.
 
-1.3.  ul_delete_urecord(domain, aor)
+1.3. ul_delete_urecord(domain, aor)
 
    The function deletes all the contacts bound with the given Address Of
    Record.
@@ -939,7 +944,7 @@ Chapter 2. Developer Guide
      * str* aor - Address of record (aka username) of the record, that
        should be deleted.
 
-1.4.  ul_get_urecord(domain, aor)
+1.4. ul_get_urecord(domain, aor)
 
    The function returns pointer to record with given Address of Record.
 
@@ -949,7 +954,7 @@ Chapter 2. Developer Guide
 
      * str* aor - Address of Record of request record.
 
-1.5.  ul_lock_udomain(domain)
+1.5. ul_lock_udomain(domain)
 
    The function lock the specified domain, it means, that no other
    processes will be able to access during the time. This prevents race
@@ -960,14 +965,14 @@ Chapter 2. Developer Guide
    Meaning of the parameters is as follows:
      * udomain_t* domain - Domain to be locked.
 
-1.6.  ul_unlock_udomain(domain)
+1.6. ul_unlock_udomain(domain)
 
    Unlock the specified domain previously locked by ul_lock_udomain.
 
    Meaning of the parameters is as follows:
      * udomain_t* domain - Domain to be unlocked.
 
-1.7.  ul_release_urecord(record)
+1.7. ul_release_urecord(record)
 
    Do some sanity checks - if all contacts have been removed, delete the
    entire record structure.
@@ -975,7 +980,7 @@ Chapter 2. Developer Guide
    Meaning of the parameters is as follows:
      * urecord_t* record - Record to be released.
 
-1.8.  ul_insert_ucontact(record, contact, expires, q, callid, cseq, flags,
+1.8. ul_insert_ucontact(record, contact, expires, q, callid, cseq, flags,
 cont, ua, sock)
 
    The function inserts a new contact in the given record with specified
@@ -996,7 +1001,7 @@ cont, ua, sock)
      * struct socket_info *sock - socket on which the REGISTER message was
        received on.
 
-1.9.  ul_delete_ucontact (record, contact)
+1.9. ul_delete_ucontact (record, contact)
 
    The function deletes given contact from record.
 
@@ -1006,7 +1011,7 @@ cont, ua, sock)
 
      * ucontact_t* contact - Contact to be deleted.
 
-1.10.  ul_get_ucontact(record, contact)
+1.10. ul_get_ucontact(record, contact)
 
    The function tries to find contact with given Contact URI and returns
    pointer to structure representing the contact.
@@ -1016,7 +1021,7 @@ cont, ua, sock)
 
      * str_t* contact - URI of the request contact.
 
-1.11.  ul_get_all_ucontacts (buf, len, flags)
+1.11. ul_get_all_ucontacts (buf, len, flags)
 
    The function retrieves all contacts of all registered users and returns
    them in the caller-supplied buffer. If the buffer is too small, the
@@ -1037,7 +1042,7 @@ cont, ua, sock)
 
      * unsigned int flags - Flags that must be set.
 
-1.12.  ul_update_ucontact(contact, expires, q, callid, cseq, set, res, ua,
+1.12. ul_update_ucontact(contact, expires, q, callid, cseq, set, res, ua,
 sock)
 
    The function updates contact with new values.
@@ -1056,7 +1061,7 @@ sock)
      * struct socket_info *sock - socket on which the REGISTER message was
        received on.
 
-1.13.  ul_bind_ursloc( api )
+1.13. ul_bind_ursloc( api )
 
    The function imports all functions that are exported by the USRLOC
    module. Overs for other modules which want to user the internal USRLOC
@@ -1065,7 +1070,7 @@ sock)
    Meaning of the parameters is as follows:
      * usrloc_api_t* api - USRLOC API
 
-1.14.  ul_register_ulcb(type ,callback, param)
+1.14. ul_register_ulcb(type ,callback, param)
 
    The function register with USRLOC a callback function to be called when
    some event occures inside USRLOC.
@@ -1078,6 +1083,6 @@ sock)
      * void *param - some parameter to be passed to the callback each time
        when it is called.
 
-1.15.  ul_get_num_users()
+1.15. ul_get_num_users()
 
    The function loops through all domains summing up the number of users.

+ 9 - 0
modules/usrloc/doc/usrloc_admin.xml

@@ -588,6 +588,15 @@ modparam("usrloc", "db_url", "&exampledb;")
 			caching also disable the statistics exports.
 			</para>
 		</listitem>
+		<listitem>
+			<para>
+			4 - This uses database to load records at startup but uses only
+			memory during the runtime. Records are not written back at all,
+			not even at shutdown. Useful for scenarios when registrations are
+			replicated to a node that does the storage in database during
+			runtime.
+			</para>
+		</listitem>
 		</itemizedlist>
 		<warning>
 		<para>

+ 9 - 5
modules/usrloc/ul_mod.c

@@ -413,15 +413,19 @@ static int child_init(int _rank)
 			return 0;
 		case DB_ONLY:
 		case WRITE_THROUGH:
-			/* we need connection from working SIP and TIMER and MAIN
-			 * processes only */
+			/* connect to db only from SIP workers, TIMER and MAIN processes */
 			if (_rank<=0 && _rank!=PROC_TIMER && _rank!=PROC_MAIN)
 				return 0;
 			break;
 		case WRITE_BACK:
-			/* connect only from TIMER (for flush), from MAIN (for
+			/* connect to db only from TIMER (for flush), from MAIN (for
 			 * final flush() and from child 1 for preload */
-			if (_rank!=PROC_TIMER && _rank!=PROC_MAIN && _rank!=1)
+			if (_rank!=PROC_TIMER && _rank!=PROC_MAIN && _rank!=PROC_SIPINIT)
+				return 0;
+			break;
+		case DB_READONLY:
+			/* connect to db only from child 1 for preload */
+			if(_rank!=PROC_SIPINIT)
 				return 0;
 			break;
 	}
@@ -432,7 +436,7 @@ static int child_init(int _rank)
 		return -1;
 	}
 	/* _rank==PROC_SIPINIT is used even when fork is disabled */
-	if (_rank==PROC_SIPINIT && db_mode!= DB_ONLY) {
+	if (_rank==PROC_SIPINIT && db_mode!=DB_ONLY) {
 		/* if cache is used, populate domains from DB */
 		for( ptr=root ; ptr ; ptr=ptr->next) {
 			if (preload_udomain(ul_dbh, ptr->d) < 0) {

+ 1 - 0
modules/usrloc/urecord.c

@@ -382,6 +382,7 @@ static inline void wb_timer(urecord_t* _r)
 void timer_urecord(urecord_t* _r)
 {
 	switch(db_mode) {
+	case DB_READONLY:
 	case NO_DB:         nodb_timer(_r);
 						break;
 	/* use also the write_back timer routine to handle the failed

+ 1 - 0
modules/usrloc/usrloc.h

@@ -40,6 +40,7 @@
 #define WRITE_THROUGH 1
 #define WRITE_BACK    2
 #define DB_ONLY       3
+#define DB_READONLY   4
 
 /*forward declaration necessary for udomain*/
 

+ 4 - 0
pkg/kamailio/fedora/17/kamailio.spec

@@ -518,6 +518,7 @@ fi
 %doc %{_docdir}/kamailio/modules/README.cfg_db
 %doc %{_docdir}/kamailio/modules/README.cfg_rpc
 %doc %{_docdir}/kamailio/modules/README.cfgutils
+%doc %{_docdir}/kamailio/modules/README.cnxcc
 %doc %{_docdir}/kamailio/modules/README.corex
 %doc %{_docdir}/kamailio/modules/README.counters
 %doc %{_docdir}/kamailio/modules/README.ctl
@@ -657,6 +658,7 @@ fi
 %{_libdir}/kamailio/modules/cfg_db.so
 %{_libdir}/kamailio/modules/cfg_rpc.so
 %{_libdir}/kamailio/modules/cfgutils.so
+%{_libdir}/kamailio/modules/cnxcc.so
 %{_libdir}/kamailio/modules/corex.so
 %{_libdir}/kamailio/modules/counters.so
 %{_libdir}/kamailio/modules/ctl.so
@@ -1088,6 +1090,8 @@ fi
 
 
 %changelog
+* Wed Mar 27 2013 Peter Dunkley <[email protected]>
+  - Added cnxcc module to .spec
 * Thu Mar 7 2013 Peter Dunkley <[email protected]>
   - Added build requirement for docbook2X for Fedora builds
 * Wed Mar 6 2013 Peter Dunkley <[email protected]>

+ 16 - 0
sr_module.c

@@ -64,6 +64,7 @@
 #include "globals.h"
 #include "rpc_lookup.h"
 #include "sr_compat.h"
+#include "ppcfg.h"
 
 #include <sys/stat.h>
 #include <regex.h>
@@ -271,6 +272,7 @@ static int register_module(unsigned ver, union module_exports_u* e,
 {
 	int ret, i;
 	struct sr_module* mod;
+	char defmod[64];
 
 	ret=-1;
 
@@ -366,6 +368,20 @@ static int register_module(unsigned ver, union module_exports_u* e,
 		/* i==0 => success */
 	}
 
+	/* add cfg define for each module: MOD_modulename */
+	if(strlen(mod->exports.name)>=60) {
+		LM_ERR("too long module name: %s\n", mod->exports.name);
+		goto error;
+	}
+	strcpy(defmod, "MOD_");
+	strcat(defmod, mod->exports.name);
+	pp_define_set_type(0);
+	if(pp_define(strlen(defmod), defmod)<0) {
+		LM_ERR("unable to set cfg define for module: %s\n",
+				mod->exports.name);
+		goto error;
+	}
+
 	/* link module in the list */
 	mod->next=modules;
 	modules=mod;

+ 17 - 4
udp_server.c

@@ -321,11 +321,24 @@ int udp_init(struct socket_info* sock_info)
 	}
 	/* tos */
 	optval = tos;
-	if (setsockopt(sock_info->socket, IPPROTO_IP, IP_TOS, (void*)&optval, 
-			sizeof(optval)) ==-1){
-		LOG(L_WARN, "WARNING: udp_init: setsockopt tos: %s\n", strerror(errno));
-		/* continue since this is not critical */
+	if (addr->s.sa_family==AF_INET){
+		if (setsockopt(sock_info->socket, IPPROTO_IP, IP_TOS, (void*)&optval, 
+				sizeof(optval)) ==-1){
+			LOG(L_WARN, "WARNING: udp_init: setsockopt tos: %s\n",
+					strerror(errno));
+			/* continue since this is not critical */
+		}
+#ifdef USE_IPV6
+	} else if (addr->s.sa_family==AF_INET6){
+		if (setsockopt(sock_info->socket, IPPROTO_IPV6, IPV6_TCLASS,
+					(void*)&optval, sizeof(optval)) ==-1) {
+			LOG(L_WARN, "WARNING: udp_init: setsockopt v6 tos: %s\n",
+					strerror(errno));
+			/* continue since this is not critical */
+		}
+#endif
 	}
+
 #if defined (__OS_linux) && defined(UDP_ERRORS)
 	optval=1;
 	/* enable error receiving on unconnected sockets */

+ 0 - 7
utils/sercmd/sercmd.c

@@ -2154,25 +2154,19 @@ int main(int argc, char** argv)
 {
 	int c;
 	char* sock_name;
-	int port_no;
 	int sock_type;
 	int s;
 	struct binrpc_cmd cmd;
-	int rec;
 	struct id_list* sock_id;
 	char* format;
-	int interactive;
 	char* line;
 	char* l;
 
 	quit=0;
 	format=0;
 	line=0;
-	interactive=0;
-	rec=1;
 	s=-1;
 	sock_name=0;
-	port_no=0;
 	sock_type=UNIXS_SOCK;
 	opterr=0;
 	while((c=getopt(argc, argv, "UVhs:D:R:vf:"))!=-1){
@@ -2277,7 +2271,6 @@ int main(int argc, char** argv)
 	sock_id=0;
 	
 	if (optind>=argc){
-			interactive=1;
 			/*fprintf(stderr, "ERROR: no command specified\n");
 			goto error; */
 	}else{