Browse Source

Added support to limit number of calls per customer/profile

- added function cnxcc_set_max_channels() per customer/profile
- added function cnxcc_get_channel_count() per customer/profile
- added function cnxcc_terminate_all() to terminate calls per customer/profile
- added select @cnxcc.channels["customer/profile"].count
- added modified version of kamailio-cnxcc.cfg to reflect changes and examples
Carlos Ruiz Diaz 12 năm trước cách đây
mục cha
commit
fc83d3b1f1

+ 20 - 3
modules/cnxcc/cnxcc_check.c

@@ -1,8 +1,25 @@
 /*
 /*
- * cnxcc_check.c
+ * $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
  *
  *
- *  Created on: Dec 10, 2012
- *      Author: carlos
  */
  */
 
 
 #include <stdio.h>
 #include <stdio.h>

+ 20 - 3
modules/cnxcc/cnxcc_check.h

@@ -1,8 +1,25 @@
 /*
 /*
- * cnxcc_check.h
+ * $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
  *
  *
- *  Created on: Dec 10, 2012
- *      Author: carlos
  */
  */
 
 
 #ifndef CNXCC_CHECK_H_
 #ifndef CNXCC_CHECK_H_

+ 355 - 14
modules/cnxcc/cnxcc_mod.c

@@ -42,7 +42,6 @@
 #include "../../locking.h"
 #include "../../locking.h"
 #include "../../lock_ops.h"
 #include "../../lock_ops.h"
 #include "../../str_hash.h"
 #include "../../str_hash.h"
-//#include "../../timer.h"
 #include "../../timer_proc.h"
 #include "../../timer_proc.h"
 #include "../../modules/tm/tm_load.h"
 #include "../../modules/tm/tm_load.h"
 #include "../../parser/parse_from.h"
 #include "../../parser/parse_from.h"
@@ -52,7 +51,6 @@
 #include "../../parser/contact/parse_contact.h"
 #include "../../parser/contact/parse_contact.h"
 #include "../../parser/contact/contact.h"
 #include "../../parser/contact/contact.h"
 #include "../../parser/parse_rr.h"
 #include "../../parser/parse_rr.h"
-//#include "../../lib/kcore/parser_helpers.h"
 #include "../../mod_fix.h"
 #include "../../mod_fix.h"
 #include "../dialog/dlg_load.h"
 #include "../dialog/dlg_load.h"
 #include "../dialog/dlg_hash.h"
 #include "../dialog/dlg_hash.h"
@@ -66,11 +64,12 @@
 #include "cnxcc_sip_msg_faker.h"
 #include "cnxcc_sip_msg_faker.h"
 #include "cnxcc_check.h"
 #include "cnxcc_check.h"
 #include "cnxcc_rpc.h"
 #include "cnxcc_rpc.h"
+#include "cnxcc_select.h"
 
 
 MODULE_VERSION
 MODULE_VERSION
 
 
 #define HT_SIZE						229
 #define HT_SIZE						229
-#define MODULE_NAME					"CNXCC"
+#define MODULE_NAME					"cnxcc"
 #define NUMBER_OF_TIMERS			2
 #define NUMBER_OF_TIMERS			2
 
 
 #define TRUE						1
 #define TRUE						1
@@ -106,6 +105,10 @@ static int pv_get_calls(struct sip_msg *msg, pv_param_t *param, pv_value_t *res)
  */
  */
 static int set_max_time(struct sip_msg* msg, char* number, char* str2);
 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 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 int set_max_channels(struct sip_msg* msg, char* str_pv_client, char* str_pv_max_chan);
+static int get_channel_count(struct sip_msg* msg, char* str_pv_client, char* str_pv_max_chan);
+static int terminate_all(struct sip_msg* msg, char* str_pv_client);
+
 static void start_billing(str *callid, str tags[2]);
 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 setup_billing(str *callid, unsigned int h_entry, unsigned int h_id);
 static void stop_billing(str *callid);
 static void stop_billing(str *callid);
@@ -140,6 +143,10 @@ 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_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},
 	{"cnxcc_set_max_credit",   (cmd_function) set_max_credit, 5, fixup_par, NULL, ANY_ROUTE},
+	{"cnxcc_set_max_channels",   (cmd_function) set_max_channels, 2, fixup_pvar_pvar, NULL, ANY_ROUTE},
+	{"cnxcc_get_channel_count",   (cmd_function) get_channel_count, 2, fixup_pvar_pvar, NULL, ANY_ROUTE},
+	{"cnxcc_terminate_all",   (cmd_function) terminate_all, 1, fixup_pvar_null, NULL, ANY_ROUTE},
+
 	{0,0,0,0,0,0}
 	{0,0,0,0,0,0}
 };
 };
 
 
@@ -176,10 +183,19 @@ rpc_export_t ul_rpc[] =
     {0, 0, 0, 0}
     {0, 0, 0, 0}
 };
 };
 
 
+/* selects declaration */
+select_row_t sel_declaration[] = {
+        { NULL, SEL_PARAM_STR, STR_STATIC_INIT("cnxcc"), sel_root, SEL_PARAM_EXPECTED},
+        { sel_root, SEL_PARAM_STR, STR_STATIC_INIT("channels"), sel_channels, SEL_PARAM_EXPECTED|CONSUME_NEXT_STR|FIXUP_CALL},
+        { sel_channels, SEL_PARAM_STR, STR_STATIC_INIT("count"), sel_channels_count, 0},
+
+        { NULL, SEL_PARAM_STR, STR_NULL, NULL, 0}
+};
+
 /** module exports */
 /** module exports */
 struct module_exports exports =
 struct module_exports exports =
 {
 {
-	"cnxcc",
+	MODULE_NAME,
 	DEFAULT_DLFLAGS, 	/* dlopen flags */
 	DEFAULT_DLFLAGS, 	/* dlopen flags */
 	cmds,
 	cmds,
 	params,
 	params,
@@ -217,7 +233,7 @@ static int fixup_par(void** param, int param_no)
 
 
 static int mod_init(void)
 static int mod_init(void)
 {
 {
-	LM_ALERT("Loading " MODULE_NAME " module\n");
+	LM_INFO("Loading " MODULE_NAME " module\n");
 
 
 	_data.cs_route_number = route_get(&event_rt, "cnxcc:call-shutdown");
 	_data.cs_route_number = route_get(&event_rt, "cnxcc:call-shutdown");
 
 
@@ -240,6 +256,8 @@ static int mod_init(void)
 	_data.time.call_data_by_cid 		= 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.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.money.call_data_by_cid 		= shm_malloc(sizeof(struct str_hash_table));
+	_data.channel.credit_data_by_client	= shm_malloc(sizeof(struct str_hash_table));
+	_data.channel.call_data_by_cid 		= shm_malloc(sizeof(struct str_hash_table));
 
 
 	_data.stats							= (stats_t *) shm_malloc(sizeof(stats_t));
 	_data.stats							= (stats_t *) shm_malloc(sizeof(stats_t));
 
 
@@ -265,9 +283,16 @@ static int mod_init(void)
 	if (init_hashtable(_data.money.call_data_by_cid) != 0)
 	if (init_hashtable(_data.money.call_data_by_cid) != 0)
 		return -1;
 		return -1;
 
 
+	if (init_hashtable(_data.channel.credit_data_by_client) != 0)
+		return -1;
+
+	if (init_hashtable(_data.channel.call_data_by_cid) != 0)
+		return -1;
+
 	lock_init(&_data.lock);
 	lock_init(&_data.lock);
 	lock_init(&_data.time.lock);
 	lock_init(&_data.time.lock);
 	lock_init(&_data.money.lock);
 	lock_init(&_data.money.lock);
+	lock_init(&_data.channel.lock);
 
 
 	register_mi_cmd(mi_credit_control_stats, "cnxcc_stats", NULL, NULL, 0);
 	register_mi_cmd(mi_credit_control_stats, "cnxcc_stats", NULL, NULL, 0);
 
 
@@ -291,6 +316,8 @@ static int mod_init(void)
 
 
 	_dlgbinds.register_dlgcb(NULL, DLGCB_CREATED, dialog_created_callback, NULL, NULL);
 	_dlgbinds.register_dlgcb(NULL, DLGCB_CREATED, dialog_created_callback, NULL, NULL);
 
 
+	 register_select_table(sel_declaration);
+
 	return 0;
 	return 0;
 }
 }
 
 
@@ -397,6 +424,7 @@ int try_get_credit_data_entry(str *client_id, credit_data_t **credit_data)
 	hash_tables_t *hts				= NULL;
 	hash_tables_t *hts				= NULL;
 	*credit_data					= NULL;
 	*credit_data					= NULL;
 
 
+	/* by money */
 	hts					= &_data.money;
 	hts					= &_data.money;
 	lock_get(&hts->lock);
 	lock_get(&hts->lock);
 
 
@@ -411,10 +439,26 @@ int try_get_credit_data_entry(str *client_id, credit_data_t **credit_data)
 
 
 	lock_release(&hts->lock);
 	lock_release(&hts->lock);
 
 
+	/* by time */
 	hts					= &_data.time;
 	hts					= &_data.time;
 	lock_get(&hts->lock);
 	lock_get(&hts->lock);
 
 
-	cd_entry			= str_hash_get(hts->call_data_by_cid, client_id->s, client_id->len);
+	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);
+
+	/* by channel */
+	hts					= &_data.channel;
+	lock_get(&hts->lock);
+
+	cd_entry			= str_hash_get(hts->credit_data_by_client, client_id->s, client_id->len);
 
 
 	if (cd_entry != NULL)
 	if (cd_entry != NULL)
 	{
 	{
@@ -434,6 +478,7 @@ int try_get_call_entry(str *callid, call_t **call, hash_tables_t **hts)
 
 
 	*call					= NULL;
 	*call					= NULL;
 
 
+	/* by money */
 	*hts					= &_data.money;
 	*hts					= &_data.money;
 	lock_get(&(*hts)->lock);
 	lock_get(&(*hts)->lock);
 
 
@@ -448,6 +493,7 @@ int try_get_call_entry(str *callid, call_t **call, hash_tables_t **hts)
 
 
 	lock_release(&(*hts)->lock);
 	lock_release(&(*hts)->lock);
 
 
+	/* by time */
 	*hts				= &_data.time;
 	*hts				= &_data.time;
 	lock_get(&(*hts)->lock);
 	lock_get(&(*hts)->lock);
 
 
@@ -462,6 +508,21 @@ int try_get_call_entry(str *callid, call_t **call, hash_tables_t **hts)
 
 
 	lock_release(&(*hts)->lock);
 	lock_release(&(*hts)->lock);
 
 
+	/* by channel */
+	*hts				= &_data.channel;
+	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;
 	return -1;
 }
 }
 
 
@@ -800,8 +861,13 @@ static void free_call(call_t *call)
 
 
 		if (e == NULL)
 		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;
+			e			= str_hash_get(_data.channel.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;
+			}
 		}
 		}
 	}
 	}
 
 
@@ -841,10 +907,30 @@ static int shm_str_hash_alloc(struct str_hash_table *ht, int size)
 
 
 static credit_data_t *get_or_create_credit_data_entry(str *client_id, credit_type_t type)
 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_table *ht	= NULL;
+	gen_lock_t *lock			= NULL;
 	struct str_hash_entry *e	= NULL;
 	struct str_hash_entry *e	= NULL;
 
 
+	switch(type)
+	{
+	case CREDIT_MONEY:
+		ht		= _data.money.credit_data_by_client;
+		lock	=  &_data.money.lock;
+		break;
+	case CREDIT_TIME:
+		ht		= _data.time.credit_data_by_client;
+		lock	=  &_data.time.lock;
+		break;
+	case CREDIT_CHANNEL:
+		ht		= _data.channel.credit_data_by_client;
+		lock	=  &_data.channel.lock;
+		break;
+	default:
+		LM_ERR("Something went terribly wrong");
+		return NULL;
+	}
+
+
 	lock_get(lock);
 	lock_get(lock);
 	e							= str_hash_get(ht, client_id->s, client_id->len);
 	e							= str_hash_get(ht, client_id->s, client_id->len);
 	lock_release(lock);
 	lock_release(lock);
@@ -1121,11 +1207,95 @@ error:
 	return NULL;
 	return NULL;
 }
 }
 
 
+static call_t *alloc_new_call_by_channel(credit_data_t *credit_data, struct sip_msg *msg, int max_chan)
+{
+	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_chan;
+
+	/*
+	 * 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)
 static int add_call_by_cid(str *cid, call_t *call, credit_type_t type)
 {
 {
+	struct str_hash_table *ht	= NULL;
+	gen_lock_t *lock			= NULL;
 	struct str_hash_entry *e	= NULL;
 	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;
+
+	switch(type)
+	{
+	case CREDIT_MONEY:
+		ht		= _data.money.call_data_by_cid;
+		lock	=  &_data.money.lock;
+		break;
+	case CREDIT_TIME:
+		ht		= _data.time.call_data_by_cid;
+		lock	=  &_data.time.lock;
+		break;
+	case CREDIT_CHANNEL:
+		ht		= _data.channel.call_data_by_cid;
+		lock	=  &_data.channel.lock;
+		break;
+	default:
+		LM_ERR("Something went terribly wrong");
+		return -1;
+	}
 
 
 	e	= str_hash_get(ht, cid->s, cid->len);
 	e	= str_hash_get(ht, cid->s, cid->len);
 
 
@@ -1326,13 +1496,184 @@ static int set_max_credit(struct sip_msg* msg,
 	}
 	}
 	else
 	else
 	{
 	{
-		LM_ALERT("MSG was not a request\n");
+		LM_ALERT("MSG was not an INVITE\n");
 		return -1;
 		return -1;
 	}
 	}
 
 
 	return 1;
 	return 1;
 }
 }
 
 
+static int terminate_all(struct sip_msg* msg, char* str_pv_client)
+{
+	credit_data_t *credit_data 	= NULL;
+	pv_spec_t *client_id_spec	= (pv_spec_t *) str_pv_client;
+
+	pv_value_t client_id_val;
+
+	if (pv_get_spec_value(msg, client_id_spec, &client_id_val) != 0)
+	{
+		LM_ERR("[%.*s]: can't get client_id pvar 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;
+	}
+
+	if (try_get_credit_data_entry(&client_id_val.rs, &credit_data) != 0)
+	{
+		LM_DBG("[%.*s] not found\n", msg->callid->body.len, msg->callid->body.s);
+		return -1;
+	}
+
+	terminate_all_calls(credit_data);
+
+	return 1;
+}
+
+static int get_channel_count(struct sip_msg* msg, char* str_pv_client, char* str_pv_chan_count)
+{
+	credit_data_t *credit_data 	= NULL;
+	pv_spec_t *chan_count_spec	= (pv_spec_t *) str_pv_chan_count,
+			  *client_id_spec	= (pv_spec_t *) str_pv_client;
+
+	pv_value_t chan_count_val, client_id_val;
+	int value					= -1;
+
+	if (pv_get_spec_value(msg, client_id_spec, &client_id_val) != 0)
+	{
+		LM_ERR("[%.*s]: can't get client_id pvar 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;
+	}
+
+	if (try_get_credit_data_entry(&client_id_val.rs, &credit_data) == 0)
+		value	= credit_data->number_of_calls;
+	else
+		LM_ALERT("[%.*s] not found\n", msg->callid->body.len, msg->callid->body.s);
+
+	if (!pv_is_w(chan_count_spec))
+	{
+		LM_ERR("pvar is not writable");
+		return -1;
+	}
+
+	memset(&chan_count_val, 0, sizeof(chan_count_val));
+
+	chan_count_val.flags 	= PV_VAL_STR;
+
+	if (value > 0)
+		chan_count_val.rs.s 	= int2str(value, &chan_count_val.rs.len);
+	else
+	{
+		char buff[2]			= { '-', '1' };
+		chan_count_val.rs.s 	= buff;
+		chan_count_val.rs.len	= 2;
+	}
+
+	if (pv_set_spec_value(msg, chan_count_spec, 0, &chan_count_val) != 0)
+	{
+		LM_ERR("Error writing value to pvar");
+		return -1;
+	}
+
+	return 1;
+}
+
+static int set_max_channels(struct sip_msg* msg, char* str_pv_client, char* str_pv_max_chan)
+{
+	credit_data_t *credit_data 	= NULL;
+	call_t *call				= NULL;
+	pv_spec_t *max_chan_spec	= (pv_spec_t *) str_pv_max_chan,
+			  *client_id_spec	= (pv_spec_t *) str_pv_client;
+	pv_value_t max_chan_val, client_id_val;
+	int max_chan				= 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_chan_spec, &max_chan_val) != 0)
+		{
+			LM_ERR("Can't get max_chan pvar value\n");
+			return -1;
+		}
+		max_chan	= max_chan_val.ri;
+
+		if (max_chan <= 0)
+		{
+			LM_ERR("[%.*s] MAX_CHAN cannot be less than or equal to zero: %d\n", msg->callid->body.len, msg->callid->body.s, max_chan);
+			return -1;
+		}
+
+		if (pv_get_spec_value(msg, client_id_spec, &client_id_val) != 0)
+		{
+			LM_ERR("[%.*s]: can't get client_id pvar 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-chan[%d], call-id[%.*s]\n", client_id_val.rs.len, client_id_val.rs.s,
+																		max_chan,
+																		msg->callid->body.len, msg->callid->body.s);
+
+		if ((credit_data = get_or_create_credit_data_entry(&client_id_val.rs, CREDIT_CHANNEL)) == 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 (credit_data->number_of_calls + 1 > max_chan)
+			return -2; // you have, between calls being setup plus those established, more than you maximum quota
+
+		if (credit_data->concurrent_calls + 1 > max_chan)
+			return -3; // you have the max amount of established calls already
+
+		if ((call = alloc_new_call_by_channel(credit_data, msg, max_chan)) == 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_CHANNEL) != 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;
+		}
+
+		return 1;
+	}
+	else
+	{
+		LM_ALERT("MSG was not an INVITE\n");
+		return -1;
+	}
+}
+
 static int set_max_time(struct sip_msg* msg, char* str_pv_client, char* str_pv_maxsecs)
 static int set_max_time(struct sip_msg* msg, char* str_pv_client, char* str_pv_maxsecs)
 {
 {
 	credit_data_t *credit_data 	= NULL;
 	credit_data_t *credit_data 	= NULL;
@@ -1407,7 +1748,7 @@ static int set_max_time(struct sip_msg* msg, char* str_pv_client, char* str_pv_m
 	}
 	}
 	else
 	else
 	{
 	{
-		LM_ALERT("MSG was not a request\n");
+		LM_ALERT("MSG was not an INVITE\n");
 		return -1;
 		return -1;
 	}
 	}
 
 

+ 3 - 3
modules/cnxcc/cnxcc_mod.h

@@ -24,7 +24,6 @@
 #ifndef _CNXCC_MOD_H
 #ifndef _CNXCC_MOD_H
 #define _CNXCC_MOD_H
 #define _CNXCC_MOD_H
 
 
-// 3,778
 #include "../../locking.h"
 #include "../../locking.h"
 #include "../../str_hash.h"
 #include "../../str_hash.h"
 #include "../../parser/parse_rr.h"
 #include "../../parser/parse_rr.h"
@@ -48,7 +47,8 @@ typedef enum cnxpvtypes
 typedef enum credit_type
 typedef enum credit_type
 {
 {
 	CREDIT_TIME,
 	CREDIT_TIME,
-	CREDIT_MONEY
+	CREDIT_MONEY,
+	CREDIT_CHANNEL
 } credit_type_t;
 } credit_type_t;
 
 
 typedef struct hash_tables
 typedef struct hash_tables
@@ -65,6 +65,7 @@ typedef struct data
 
 
 	hash_tables_t time;
 	hash_tables_t time;
 	hash_tables_t money;
 	hash_tables_t money;
+	hash_tables_t channel;
 
 
 	/*struct str_hash_table *credit_data_by_client;
 	/*struct str_hash_table *credit_data_by_client;
 	struct str_hash_table *call_data_by_cid;*/
 	struct str_hash_table *call_data_by_cid;*/
@@ -82,7 +83,6 @@ typedef struct data
 	flag_t ctrl_flag;
 	flag_t ctrl_flag;
 
 
 	int check_period;
 	int check_period;
-	int number_of_timers;
 
 
 } data_t;
 } data_t;
 
 

+ 20 - 3
modules/cnxcc/cnxcc_rpc.c

@@ -1,8 +1,25 @@
 /*
 /*
- * cnxcc_rpc.c
+ * $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
  *
  *
- *  Created on: Dec 6, 2012
- *      Author: carlos
  */
  */
 
 
 #include <stdio.h>
 #include <stdio.h>

+ 20 - 3
modules/cnxcc/cnxcc_rpc.h

@@ -1,8 +1,25 @@
 /*
 /*
- * cnxcc_rpc.h
+ * $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
  *
  *
- *  Created on: Dec 6, 2012
- *      Author: carlos
  */
  */
 
 
 #ifndef CNXCC_RPC_H_
 #ifndef CNXCC_RPC_H_

+ 67 - 0
modules/cnxcc/cnxcc_select.c

@@ -0,0 +1,67 @@
+/*
+ * $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 "../../select.h"
+#include "../../select_buf.h"
+
+#include "cnxcc_mod.h"
+
+extern data_t _data;
+
+int sel_root(str* res, select_t* s, struct sip_msg* msg)  /* dummy */
+{
+	return 0;
+}
+
+int sel_channels(str* res, select_t* s, struct sip_msg* msg)
+{
+	LM_DBG("sel_channels");
+
+	return 0;
+}
+
+int sel_channels_count(str* res, select_t* s, struct sip_msg* msg)
+{
+	LM_DBG("sel_channels_count for [%.*s]",  s->params[2].v.s.len, s->params[2].v.s.s);
+
+	credit_data_t *credit_data	= NULL;
+	int value					= 0;
+
+	if (s->params[2].v.s.len <= 0)
+	{
+		LM_ERR("Client must be specified");
+		return -1;
+	}
+
+	if (try_get_credit_data_entry(&s->params[2].v.s, &credit_data) >= 0)
+		value = credit_data->number_of_calls;
+	else
+		LM_DBG("Client [%.*s] not found", s->params[2].v.s.len, s->params[2].v.s.s);
+
+	res->s 	= int2str(value, &res->len);
+
+	return 0;
+}
+
+

+ 32 - 0
modules/cnxcc/cnxcc_select.h

@@ -0,0 +1,32 @@
+/*
+ * $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_SELECT_H_
+#define CNXCC_SELECT_H_
+
+int sel_root(str* res, select_t* s, struct sip_msg* msg);
+int sel_channels(str* res, select_t* s, struct sip_msg* msg);
+int sel_channels_count(str* res, select_t* s, struct sip_msg* msg);
+
+#endif /* CNXCC_SELECT_H_ */

+ 92 - 13
modules/cnxcc/example/kamailio-cnxcc.cfg

@@ -1,4 +1,9 @@
 #!KAMAILIO
 #!KAMAILIO
+
+##!define CNXCC_TIME
+##!define CNXCC_MONEY
+#!define CNXCC_CHANNEL
+
 #
 #
 # Kamailio (OpenSER) SIP Server v3.2 - default configuration script
 # Kamailio (OpenSER) SIP Server v3.2 - default configuration script
 #     - web: http://www.kamailio.org
 #     - web: http://www.kamailio.org
@@ -129,9 +134,10 @@
 
 
 ####### Global Parameters #########
 ####### Global Parameters #########
 
 
+
 #!ifdef WITH_DEBUG
 #!ifdef WITH_DEBUG
 debug=4
 debug=4
-log_stderror=yes
+log_stderror=no
 #!else
 #!else
 debug=2
 debug=2
 log_stderror=no
 log_stderror=no
@@ -204,7 +210,7 @@ voicemail.srv_port = "5060" desc "VoiceMail Port"
 #!ifdef WITH_SRCPATH
 #!ifdef WITH_SRCPATH
 mpath="modules_k:modules"
 mpath="modules_k:modules"
 #!else
 #!else
-mpath="/usr/local/lib/kamailio/modules_k/:/usr/local/lib/kamailio/modules/"
+mpath="/usr/local/lib64/kamailio/modules_k/:/usr/local/lib64/kamailio/modules/"
 #!endif
 #!endif
 
 
 #!ifdef WITH_MYSQL
 #!ifdef WITH_MYSQL
@@ -274,7 +280,7 @@ loadmodule "xmlrpc.so"
 #!endif
 #!endif
 
 
 #!ifdef WITH_DEBUG
 #!ifdef WITH_DEBUG
-loadmodule "debugger.so"
+#loadmodule "debugger.so"
 #!endif
 #!endif
 
 
 # ----------------- setting module-specific parameters ---------------
 # ----------------- setting module-specific parameters ---------------
@@ -437,7 +443,7 @@ modparam("xmlrpc", "route", "XMLRPC");
 
 
 #!ifdef WITH_DEBUG
 #!ifdef WITH_DEBUG
 # ----- debugger params -----
 # ----- debugger params -----
-modparam("debugger", "cfgtrace", 1)
+#modparam("debugger", "cfgtrace", 1)
 #!endif
 #!endif
 
 
 #!define DLG_FLAG 28
 #!define DLG_FLAG 28
@@ -449,9 +455,15 @@ modparam("dialog", "default_timeout", 3600)
 modparam("dialog", "db_mode", 0)
 modparam("dialog", "db_mode", 0)
 modparam("dialog", "dlg_flag", DLG_FLAG)
 modparam("dialog", "dlg_flag", DLG_FLAG)
 
 
+#!ifdef CNXCC_CHANNEL
+loadmodule "rtimer.so";
+modparam("rtimer", "timer", "name=ta;interval=1;mode=1;")
+modparam("rtimer", "exec", "timer=ta;route=SHOW_CHANNEL_COUNT")
+#!endif
+
 loadmodule "cnxcc.so"
 loadmodule "cnxcc.so"
 modparam("cnxcc", "dlg_flag", CC_FLAG)
 modparam("cnxcc", "dlg_flag", CC_FLAG)
-modparam("cnxcc", "credit_check_period", 1)
+modparam("cnxcc", "credit_check_period", 1) #check every 1 second
 
 
 ####### Routing Logic ########
 ####### Routing Logic ########
 
 
@@ -502,6 +514,7 @@ request_route {
 	# dispatch requests to foreign domains
 	# dispatch requests to foreign domains
 	route(SIPOUT);
 	route(SIPOUT);
 
 
+	
 	### requests for my local domains
 	### requests for my local domains
 
 
 	# handle presence related requests
 	# handle presence related requests
@@ -522,9 +535,12 @@ request_route {
 
 
 	# user location service
 	# user location service
 	route(LOCATION);
 	route(LOCATION);
-
-	route(CNXCC);
 	
 	
+	if (is_method("INVITE")) {
+		route(CNXCC);
+	}
+
+        	
 	route(RELAY);
 	route(RELAY);
 }
 }
 
 
@@ -536,14 +552,18 @@ route[CNXCC]
 	# 
 	# 
 	# This hardcoded values are just for illustrative purposes
 	# This hardcoded values are just for illustrative purposes
 	#
 	#
+	
+	$var(client)		= "customer1";
+
+#!ifdef CNXCC_MONEY
+	xlog("L_INFO", "Setting up money based credit control");
 
 
-	$var(client)		= "test-client-0-123-01";
-	$var(credit) 		= "50";
-	$var(cost_per_sec) 	= "0.5";
-	$var(i_pulse)		= "30";
-	$var(f_pulse)		= "6";
+	$var(credit) 		= "10";	# 10$ of credit
+	$var(cost_per_sec) 	= "1";  # 1$ per sec
+	$var(i_pulse)		= "1";  # 1$ to establish the call
+	$var(f_pulse)		= "1";  # 1$ per second
 
 
-	# if only one call is established, that call should last 1m, 36s
+	# if only one call is established, that call should last 9 seconds.
 
 
 	if (!cnxcc_set_max_credit("$var(client)",
 	if (!cnxcc_set_max_credit("$var(client)",
 				  "$var(credit)", 
 				  "$var(credit)", 
@@ -551,7 +571,53 @@ route[CNXCC]
 				  "$var(i_pulse)", 
 				  "$var(i_pulse)", 
 				  "$var(f_pulse)")) {
 				  "$var(f_pulse)")) {
 		xlog("Error setting up credit control");
 		xlog("Error setting up credit control");
+		return;
 	}
 	}
+#!endif
+
+#!ifdef CNXCC_CHANNEL	
+	xlog("L_INFO", "Setting up channel based credit control");
+
+	$var(max_chan)	= 2;
+	$var(retcode)	= cnxcc_set_max_channels("$var(client)", "$var(max_chan)");
+
+	if ($var(retcode) == -1) {
+		xlog("Error setting up credit control");
+		return;
+	}
+
+        $var(count)     = -1;
+
+        if (!cnxcc_get_channel_count("$var(client)", "$var(count)")) {
+                xlog("Error getting customer's channel count");
+        }
+
+        xlog("L_INFO", "CNXCC ROUTE: $var(client) has $var(count) call(s)");
+
+	if ($var(retcode) < -1) {
+		xlog("Too many channels for customer");
+		sl_send_reply(403, "Forbidden");
+
+		if (!cnxcc_terminate_all("$var(client)")) {
+			xlog("Error terminating customer's calls");
+		}
+
+		exit;
+	}
+#!endif
+
+#!ifdef CNXCC_TIME
+	xlog("L_INFO", "Setting up time based credit control");
+	
+	$var(max_time)	= 10;
+	
+	if (!cnxcc_set_max_time("$var(client)",
+                                  "$var(max_time)")) {
+                xlog("Error setting up credit control");
+                return;
+        }
+#!endif
+
 }
 }
 
 
 event_route[cnxcc:call-shutdown]
 event_route[cnxcc:call-shutdown]
@@ -561,6 +627,14 @@ event_route[cnxcc:call-shutdown]
 	# perform some kind of notification, database update, email sending, etc
 	# perform some kind of notification, database update, email sending, etc
 }
 }
 
 
+#!ifdef CNXCC_CHANNEL
+route[SHOW_CHANNEL_COUNT]
+{
+	$var(count) = @cnxcc.channels["customer1"].count;
+	xlog("L_INFO", "customer1 has $var(count) call(s)");
+}
+#!endif
+
 route[RELAY] {
 route[RELAY] {
 
 
 	# enable additional event routes for forwarded requests
 	# enable additional event routes for forwarded requests
@@ -981,3 +1055,8 @@ failure_route[MANAGE_FAILURE] {
 	}
 	}
 #!endif
 #!endif
 }
 }
+
+event_route[dialog:failed]
+{
+	xlog("dialog failed");
+}