Forráskód Böngészése

ims_charging: Various changes to make module compatible to other charging servers

- Use origin-host of CCR response as destination-host of subsequent CCR requests,
  to make sure those requests are routed to the same charging server (when using relay).
- use str_dup and str_free from ims_getters instead of locally defined.
- Honour AVP_Time_Quota_Threshold in CCR response, as an alternative to static configured
  modparam "timer_buffer".
- Add new modparam "strip_plus_from_e164" to enable sending subscription id with correct
  format according to specified type in the AVP.
- Moved subscription id formatting into a common function.
- Handle CCR response without AVP_Validity_Time set correctly. Was interpreted as
  immediate expire.
- Handle IMS_RAR (Re-auth) request from charging server. Triggers an immediate interim
  update if the session is alive.
- Handle IMS_ASR (Abort) request from charging server. Triggers an immediate termination
  of the session/call.
- Fixed issue with signed/unsigned int related to check for Expires header.
  cscf_get_expires_hdr() returns signed (-1 for not found), while that value was added
  to the outgoing diameter request unsigned. That again resulted in an Expires AVP with
  a really large value, and was rejected by the charging server.
Morten Tryfoss 1 éve
szülő
commit
e17b554ba9

+ 7 - 33
src/modules/ims_charging/Ro_data.c

@@ -1,36 +1,6 @@
 #include "Ro_data.h"
 #include "config.h"
-
-
-#define str_dup(dst, src, mem)                                                 \
-	do {                                                                       \
-		if((src).len) {                                                        \
-			(dst).s = mem##_malloc((src).len);                                 \
-			if(!(dst).s) {                                                     \
-				LM_ERR("Error allocating %d bytes in %s!\n", (src).len, #mem); \
-				(dst).len = 0;                                                 \
-				goto out_of_memory;                                            \
-			}                                                                  \
-			memcpy((dst).s, (src).s, (src).len);                               \
-			(dst).len = (src).len;                                             \
-		} else {                                                               \
-			(dst).s = 0;                                                       \
-			(dst).len = 0;                                                     \
-		}                                                                      \
-	} while(0)
-
-/**
- * Frees a str content.
- * @param x - the str to free
- * @param mem - type of memory that the content is using (shm/pkg)
- */
-#define str_free(x, mem)       \
-	do {                       \
-		if((x).s)              \
-			mem##_free((x).s); \
-		(x).s = 0;             \
-		(x).len = 0;           \
-	} while(0)
+#include "../../lib/ims/ims_getters.h"
 
 extern client_ro_cfg cfg;
 
@@ -198,7 +168,8 @@ out_of_memory:
 }
 
 Ro_CCR_t *new_Ro_CCR(int32_t acc_record_type, str *user_name,
-		ims_information_t *ims_info, subscription_id_t *subscription)
+		ims_information_t *ims_info, subscription_id_t *subscription,
+		str *destination_host)
 {
 
 
@@ -214,7 +185,9 @@ Ro_CCR_t *new_Ro_CCR(int32_t acc_record_type, str *user_name,
 	if(cfg.origin_realm.s && cfg.origin_realm.len > 0)
 		str_dup(x->origin_realm, cfg.origin_realm, pkg);
 
-	if(cfg.destination_host.s && cfg.destination_host.len > 0)
+	if(destination_host && destination_host->len > 0)
+		str_dup(x->destination_host, *destination_host, pkg);
+	else if(cfg.destination_host.s && cfg.destination_host.len > 0)
 		str_dup(x->destination_host, cfg.destination_host, pkg);
 
 	if(cfg.destination_realm.s && cfg.destination_realm.len > 0)
@@ -360,5 +333,6 @@ void Ro_free_CCA(Ro_CCA_t *x)
 	}
 	mem_free(x->mscc->granted_service_unit, pkg);
 	mem_free(x->mscc, pkg);
+	str_free(x->origin_host, pkg);
 	mem_free(x, pkg);
 }

+ 4 - 1
src/modules/ims_charging/Ro_data.h

@@ -416,6 +416,7 @@ typedef struct
 	uint32_t validity_time;
 	final_unit_indication_t *final_unit_action;
 	uint32_t resultcode;
+	uint32_t time_quota_threshold;
 } multiple_services_credit_control_t;
 
 typedef struct
@@ -424,6 +425,7 @@ typedef struct
 	uint32_t cc_request_type;
 	uint32_t cc_request_number;
 	multiple_services_credit_control_t *mscc;
+	str origin_host;
 } Ro_CCA_t;
 
 event_type_t *new_event_type(str *sip_method, str *event, uint32_t *expires);
@@ -446,7 +448,8 @@ void ims_information_free(ims_information_t *x);
 void service_information_free(service_information_t *x);
 
 Ro_CCR_t *new_Ro_CCR(int32_t acc_record_type, str *user_name,
-		ims_information_t *ims_info, subscription_id_t *subscription);
+		ims_information_t *ims_info, subscription_id_t *subscription,
+		str *destination_host);
 void Ro_free_CCR(Ro_CCR_t *x);
 void Ro_free_CCA(Ro_CCA_t *x);
 

+ 8 - 0
src/modules/ims_charging/ccr.c

@@ -1,4 +1,5 @@
 #include "../cdp_avp/cdp_avp_mod.h"
+#include "../../lib/ims/ims_getters.h"
 
 #include "ccr.h"
 #include "Ro_data.h"
@@ -402,6 +403,10 @@ Ro_CCA_t *Ro_parse_CCA_avps(AAAMessage *cca)
 						case AVP_Validity_Time:
 							mscc->validity_time = get_4bytes(mscc_avp->data.s);
 							break;
+						case AVP_Time_Quota_Threshold:
+							mscc->time_quota_threshold =
+									get_4bytes(mscc_avp->data.s);
+							break;
 						case AVP_Result_Code:
 							mscc->resultcode = get_4bytes(mscc_avp->data.s);
 							break;
@@ -478,6 +483,9 @@ Ro_CCA_t *Ro_parse_CCA_avps(AAAMessage *cca)
 				x = get_4bytes(avp->data.s);
 				ro_cca_data->resultcode = x;
 				break;
+			case AVP_Origin_Host:
+				str_dup(ro_cca_data->origin_host, avp->data, pkg);
+				break;
 		}
 		avp = avp->next;
 	}

+ 1 - 0
src/modules/ims_charging/config.h

@@ -8,6 +8,7 @@ typedef struct
 	str destination_host;
 	str destination_realm;
 	str *service_context_id;
+	int strip_plus_from_e164;
 } client_ro_cfg;
 
 #endif

+ 13 - 5
src/modules/ims_charging/dialog.c

@@ -86,23 +86,27 @@ void dlg_answered(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params)
 		   "seconds since we requested\n",
 			(int)session->reserved_secs, (int)session->valid_for,
 			(int)time_since_last_event);
-	if(session->reserved_secs < (session->valid_for - time_since_last_event)) {
-		if(session->reserved_secs > ro_timer_buffer /*TIMEOUTBUFFER*/) {
+	// valid_for might not be included in the answer from the charging server
+	if(session->valid_for == 0
+			|| session->reserved_secs
+					   < (session->valid_for - time_since_last_event)) {
+		if(session->reserved_secs
+				> session->ro_timer_buffer /*TIMEOUTBUFFER*/) {
 			ret = insert_ro_timer(&session->ro_tl,
 					session->reserved_secs
 							- (session->is_final_allocation
 											? 0
-											: ro_timer_buffer)); //subtract 5 seconds so as to get more credit before we run out
+											: session->ro_timer_buffer)); //subtract ro_timer_buffer to get more credit before we run out
 		} else {
 			ret = insert_ro_timer(&session->ro_tl, session->reserved_secs);
 		}
 	} else {
-		if(session->valid_for > ro_timer_buffer) {
+		if(session->valid_for > session->ro_timer_buffer) {
 			ret = insert_ro_timer(&session->ro_tl,
 					session->valid_for
 							- (session->is_final_allocation
 											? 0
-											: ro_timer_buffer)); //subtract 5 seconds so as to get more credit before we run out
+											: session->ro_timer_buffer)); //subtract ro_timer_buffer to get more credit before we run out
 		} else {
 			ret = insert_ro_timer(&session->ro_tl, session->valid_for);
 		}
@@ -202,6 +206,10 @@ void dlg_terminated(struct dlg_cell *dlg, int type, unsigned int termcode,
 		//currently the way we are doing is a hack.....
 		//if ((ro_session = lookup_ro_session(dlg->h_entry, &dlg->callid, 0, 0))) {
 		ro_session_entry = &(ro_session_table->entries[ro_session->h_entry]);
+		if(!ro_session_entry) {
+			LM_ERR("ro session entry is NULL - aborting\n");
+			return;
+		}
 
 		//if the Ro session is not active we don't need to do anything. This prevents
 		//double processing for various dialog_terminated callback events.

+ 20 - 0
src/modules/ims_charging/doc/ims_charging_admin.xml

@@ -834,6 +834,26 @@ modparam("ims_charging", "vendor_specific_chargeinfo", 1)
         <programlisting format="linespecific">
 ...
 modparam("ims_charging", "vendor_specific_id", 10)
+...
+        </programlisting>
+      </example>
+    </section>
+
+    <section>
+      <title><varname>strip_plus_from_e164</varname> (int)</title>
+
+      <para>Strip + from subscription id when E.164 format is used. This is according
+        to spec, but this parameter is to keep existing behavior as the default.</para>
+
+      <para><emphasis>Default value is 0.</emphasis></para>
+
+      <example>
+        <title><varname>strip_plus_from_e164</varname>parameter
+        usage</title>
+
+        <programlisting format="linespecific">
+...
+modparam("ims_charging", "strip_plus_from_e164", 1)
 ...
         </programlisting>
       </example>

+ 58 - 1
src/modules/ims_charging/ims_charging_mod.c

@@ -24,6 +24,8 @@
 
 MODULE_VERSION
 
+extern gen_lock_t *process_lock; /* lock on the process table */
+
 struct dlg_binds *dlgb_p;
 
 /* parameters */
@@ -57,7 +59,8 @@ int ro_db_mode = DB_MODE_NONE;
 
 char *domain = "location";
 
-client_ro_cfg cfg = {str_init(""), str_init(""), str_init(""), str_init(""), 0};
+client_ro_cfg cfg = {
+		str_init(""), str_init(""), str_init(""), str_init(""), 0, 0};
 
 static str custom_user_spec = {NULL, 0};
 static str app_provided_party_spec = {NULL, 0};
@@ -88,6 +91,9 @@ static int mod_init(void);
 static int mod_child_init(int);
 static void mod_destroy(void);
 
+AAAMessage *callback_cdp_request(AAAMessage *request, void *param);
+int *callback_singleton; /*< Callback singleton */
+
 static int w_ro_ccr(struct sip_msg *msg, char *route_name, char *direction,
 		int reservation_units, char *incoming_trunk_id,
 		char *outgoing_trunk_id);
@@ -163,6 +169,8 @@ static param_export_t params[] = {
 				&vendor_specific_id}, /* VSI for extra charing info in Ro */
 		{"custom_user_avp", PARAM_STR, &custom_user_spec},
 		{"app_provided_party_avp", PARAM_STR, &app_provided_party_spec},
+		{"strip_plus_from_e164", INT_PARAM,
+				&cfg.strip_plus_from_e164}, /*wheter to strip or keep + sign from E164 numbers (tel: uris), according to diameter spec*/
 		{0, 0, 0}};
 
 /** module exports */
@@ -236,6 +244,9 @@ static int mod_init(void)
 	int n;
 	load_tm_f load_tm;
 
+	callback_singleton = shm_malloc(sizeof(int));
+	*callback_singleton = 0;
+
 	if(!fix_parameters()) {
 		LM_ERR("unable to set Ro configuration parameters correctly\n");
 		goto error;
@@ -349,6 +360,13 @@ static int mod_child_init(int rank)
 	if(ro_db_mode == DB_MODE_REALTIME && rank == PROC_MAIN)
 		ro_db_mode = DB_MODE_NONE;
 
+	lock_get(process_lock);
+	if((*callback_singleton) == 0) {
+		*callback_singleton = 1;
+		cdpb.AAAAddRequestHandler(callback_cdp_request, NULL);
+	}
+	lock_release(process_lock);
+
 	return 0;
 }
 
@@ -361,6 +379,45 @@ static void mod_destroy(void)
 	}
 }
 
+/**
+ * Handler for incoming Diameter requests.
+ * @param request - the received request
+ * @param param - generic pointer
+ * @returns the answer to this request
+ */
+AAAMessage *callback_cdp_request(AAAMessage *request, void *param)
+{
+	if(is_req(request)) {
+		switch(request->applicationId) {
+			case IMS_Ro:
+				switch(request->commandCode) {
+					case IMS_RAR:
+						return ro_process_rar(request);
+						break;
+					case IMS_ASR:
+						return ro_process_asr(request);
+						break;
+					default:
+						LM_ERR("Ro request handler(): - Received unknown "
+							   "request for Ro command %d, flags %#1x "
+							   "endtoend %u hopbyhop %u\n",
+								request->commandCode, request->flags,
+								request->endtoendId, request->hopbyhopId);
+						return 0;
+						break;
+				}
+				break;
+			default:
+				LM_ERR("Ro request handler(): - Received unknown request "
+					   "for app %d command %d\n",
+						request->applicationId, request->commandCode);
+				return 0;
+				break;
+		}
+	}
+	return 0;
+}
+
 int create_response_avp_string(char *name, str *val)
 {
 	int rc;

+ 199 - 33
src/modules/ims_charging/ims_ro.c

@@ -138,6 +138,32 @@ static int get_app_provided_party(struct sip_msg *req, str *address)
 	return -1;
 }
 
+/*!
+ * \brief Format subscription id according to settings
+ * \param subscription_id subscription id
+ * \param subscription_id_type format according to subscription id value
+ */
+static void format_subscription_id(
+		str *subscription_id, int *subscription_id_type)
+{
+	if(strncasecmp(subscription_id->s, "tel:", 4) == 0) {
+		*subscription_id_type = Subscription_Type_MSISDN;
+		subscription_id->s += 4;
+		subscription_id->len -= 4;
+		/*if stripping is not done the format should actually be Subscription_Type_IMPU/END_USER_SIP_URI,
+		but that could be a breaking change */
+		if(cfg.strip_plus_from_e164
+				&& strncasecmp(subscription_id->s, "+", 1) == 0) {
+			//subscription should be purely digits when using MSISDN/E164 format
+			subscription_id->s += 1;
+			subscription_id->len -= 1;
+		}
+	} else {
+		*subscription_id_type =
+				Subscription_Type_IMPU; //default is END_USER_SIP_URI
+	}
+}
+
 
 void credit_control_session_callback(int event, void *session)
 {
@@ -474,7 +500,7 @@ int get_sip_header_info(struct sip_msg *req, struct sip_msg *reply,
 		int32_t *acc_record_type, str *sip_method, str *event,
 		uint32_t *expires, str *callid, str *asserted_id_uri, str *to_uri)
 {
-
+	int expires_header;
 	sip_method->s = req->first_line.u.request.method.s;
 	sip_method->len = req->first_line.u.request.method.len;
 
@@ -486,7 +512,12 @@ int get_sip_header_info(struct sip_msg *req, struct sip_msg *reply,
 		*acc_record_type = AAA_ACCT_EVENT;
 
 	*event = cscf_get_event(req);
-	*expires = cscf_get_expires_hdr(req, 0);
+	/* return value is signed int, while *expires is uint32_t.
+	when -1 is returned (header not found) and assigned directly to *expires,
+	this is actually added as an AVP in the outgoing diameter request */
+	expires_header = cscf_get_expires_hdr(req, 0);
+	if(expires_header > 0)
+		*expires = expires_header;
 	*callid = cscf_get_call_id(req, NULL);
 
 	if(get_custom_user(req, asserted_id_uri) == -1) {
@@ -602,7 +633,7 @@ Ro_CCR_t *dlg_create_ro_session(struct sip_msg *req, struct sip_msg *reply,
 	subscr.id.len = subscription_id.len;
 	subscr.type = subscription_id_type;
 
-	ro_ccr_data = new_Ro_CCR(acc_record_type, &user_name, ims_info, &subscr);
+	ro_ccr_data = new_Ro_CCR(acc_record_type, &user_name, ims_info, &subscr, 0);
 	if(!ro_ccr_data) {
 		LM_ERR("dlg_create_ro_session: no memory left for generic\n");
 		goto out_of_memory;
@@ -734,22 +765,15 @@ void send_ccr_interim(
 		goto error;
 	}
 
-	//getting subscription id type
-	if(strncasecmp(subscr.id.s, "tel:", 4) == 0) {
-		subscr.type = Subscription_Type_MSISDN;
-		// Strip "tel:":
-		subscr.id.s += 4;
-		subscr.id.len -= 4;
-	} else {
-		subscr.type = Subscription_Type_IMPU; //default is END_USER_SIP_URI
-	}
+	format_subscription_id(&subscr.id, &subscr.type);
 
 	user_name.s = subscr.id.s;
 	user_name.len = subscr.id.len;
 
 	acc_record_type = AAA_ACCT_INTERIM;
 
-	ro_ccr_data = new_Ro_CCR(acc_record_type, &user_name, ims_info, &subscr);
+	ro_ccr_data = new_Ro_CCR(acc_record_type, &user_name, ims_info, &subscr,
+			&ro_session->origin_host);
 	if(!ro_ccr_data) {
 		LM_ERR("no memory left for generic\n");
 		goto error;
@@ -844,7 +868,8 @@ error:
 
 	if(auth) {
 		cdpb.AAASessionsUnlock(auth->hash);
-		cdpb.AAADropCCAccSession(auth);
+		/*we're no longer using cdpb.AAADropCCAccSession() here, since this
+		session might be restarted by a re-auth request from the server or by call end*/
 	}
 
 	shm_free(i_req);
@@ -909,11 +934,41 @@ static void resume_on_interim_ccr(
 	i_req->new_credit = ro_cca_data->mscc->granted_service_unit->cc_time;
 	i_req->credit_valid_for = ro_cca_data->mscc->validity_time;
 	i_req->is_final_allocation = 0;
+	if(ro_cca_data->mscc->time_quota_threshold > 0) {
+		LM_DBG("updating session ro_timer_buffer to %i from "
+			   "time_quota_threshold in server response\n",
+				ro_cca_data->mscc->time_quota_threshold);
+		i_req->ro_session->ro_timer_buffer =
+				ro_cca_data->mscc->time_quota_threshold;
+	}
 
 	if(ro_cca_data->mscc->final_unit_action
 			&& (ro_cca_data->mscc->final_unit_action->action == 0))
 		i_req->is_final_allocation = 1;
 
+	if(ro_cca_data->origin_host.s && ro_cca_data->origin_host.len > 0) {
+		if(i_req->ro_session->origin_host.s
+				&& i_req->ro_session->origin_host.len > 0
+				&& (!strncasecmp(i_req->ro_session->origin_host.s,
+							ro_cca_data->origin_host.s,
+							ro_cca_data->origin_host.len)
+						|| i_req->ro_session->origin_host.len
+								   != ro_cca_data->origin_host.len)) {
+			LM_DBG("origin host for session has changed\n");
+			i_req->ro_session->origin_host.s =
+					(char *)shm_realloc(i_req->ro_session->origin_host.s,
+							ro_cca_data->origin_host.len);
+			if(!i_req->ro_session->origin_host.s) {
+				LM_ERR("no more shm mem\n");
+				goto error;
+			}
+
+			i_req->ro_session->origin_host.len = ro_cca_data->origin_host.len;
+			memcpy(i_req->ro_session->origin_host.s, ro_cca_data->origin_host.s,
+					ro_cca_data->origin_host.len);
+		}
+	}
+
 	Ro_free_CCA(ro_cca_data);
 	cdpb.AAAFreeMessage(&cca);
 
@@ -1039,14 +1094,7 @@ void send_ccr_stop_with_param(
 		goto error0;
 	}
 
-	//getting subscription id type
-	if(strncasecmp(subscr.id.s, "tel:", 4) == 0) {
-		subscr.type = Subscription_Type_MSISDN;
-		subscr.id.s += 4;
-		subscr.id.len -= 4;
-	} else {
-		subscr.type = Subscription_Type_IMPU; //default is END_USER_SIP_URI
-	}
+	format_subscription_id(&subscr.id, &subscr.type);
 
 	user_name.s = subscr.id.s;
 	user_name.len = subscr.id.len;
@@ -1054,7 +1102,8 @@ void send_ccr_stop_with_param(
 
 	acc_record_type = AAA_ACCT_STOP;
 
-	ro_ccr_data = new_Ro_CCR(acc_record_type, &user_name, ims_info, &subscr);
+	ro_ccr_data = new_Ro_CCR(acc_record_type, &user_name, ims_info, &subscr,
+			&ro_session->origin_host);
 	if(!ro_ccr_data) {
 		LM_ERR("send_ccr_stop: no memory left for generic\n");
 		goto error0;
@@ -1298,15 +1347,7 @@ int Ro_Send_CCR(struct sip_msg *msg, struct dlg_cell *dlg, int dir,
 		goto error;
 	}
 
-	//getting subscription id type
-	if(strncasecmp(subscription_id.s, "tel:", 4) == 0) {
-		subscription_id_type = Subscription_Type_MSISDN;
-		subscription_id.s += 4;
-		subscription_id.len -= 4;
-	} else {
-		subscription_id_type =
-				Subscription_Type_IMPU; //default is END_USER_SIP_URI
-	}
+	format_subscription_id(&subscription_id, &subscription_id_type);
 
 	str mac = {0, 0};
 	if(get_mac_avp_value(msg, &mac) != 0)
@@ -1364,7 +1405,7 @@ int Ro_Send_CCR(struct sip_msg *msg, struct dlg_cell *dlg, int dir,
 			&asserted_identity, &called_asserted_identity, &mac, dlg->h_entry,
 			dlg->h_id, reservation_units, 0, active_rating_group,
 			active_service_identifier, incoming_trunk_id, outgoing_trunk_id,
-			pani, &app_provided_party);
+			pani, &app_provided_party, ro_timer_buffer);
 
 	if(!new_session) {
 		LM_ERR("Couldn't create new Ro Session - this is BAD!\n");
@@ -1654,11 +1695,31 @@ static void resume_on_initial_ccr(
 			ro_cca_data->mscc->granted_service_unit->cc_time;
 	ssd->ro_session->valid_for = ro_cca_data->mscc->validity_time;
 	ssd->ro_session->is_final_allocation = 0;
+	if(ro_cca_data->mscc->time_quota_threshold > 0) {
+		LM_DBG("setting session ro_timer_buffer to %i from "
+			   "time_quota_threshold in server response\n",
+				ro_cca_data->mscc->time_quota_threshold);
+		ssd->ro_session->ro_timer_buffer =
+				ro_cca_data->mscc->time_quota_threshold;
+	}
 
 	if(ro_cca_data->mscc->final_unit_action
 			&& (ro_cca_data->mscc->final_unit_action->action == 0))
 		ssd->ro_session->is_final_allocation = 1;
 
+	if(ro_cca_data->origin_host.s && ro_cca_data->origin_host.len > 0) {
+		ssd->ro_session->origin_host.s =
+				(char *)shm_malloc(ro_cca_data->origin_host.len);
+		if(!ssd->ro_session->origin_host.s) {
+			LM_ERR("no more shm mem\n");
+			goto error0;
+		}
+
+		ssd->ro_session->origin_host.len = ro_cca_data->origin_host.len;
+		memcpy(ssd->ro_session->origin_host.s, ro_cca_data->origin_host.s,
+				ro_cca_data->origin_host.len);
+	}
+
 	Ro_free_CCA(ro_cca_data);
 
 	LM_DBG("Freeing CCA message\n");
@@ -1866,3 +1927,108 @@ static int get_mac_avp_value(struct sip_msg *msg, str *value)
 	*value = val.rs;
 	return 0;
 }
+
+/*!
+ * \brief Process re-auth request
+ * \param request Diameter request
+ * \return diameter response
+ */
+AAAMessage *ro_process_rar(AAAMessage *request)
+{
+	AAAMessage *response;
+	struct ro_session *ro_session = NULL;
+	struct ro_session_entry *ro_session_entry;
+	unsigned int h_entry;
+	int unref = 0;
+	int result_code = DIAMETER_LIMITED_SUCCESS;
+	char x[4];
+
+	if(request->sessionId && request->sessionId->data.s) {
+		LM_INFO("Received an IMS_RAR for session id %.*s\n",
+				request->sessionId->data.len, request->sessionId->data.s);
+
+		ro_session = lookup_ro_session_by_session_id(&request->sessionId->data);
+		if(ro_session == NULL) {
+			LM_WARN("no active ro_session with id %.*s - ignoring\n",
+					request->sessionId->data.len, request->sessionId->data.s);
+			result_code = DIAMETER_UNKNOWN_SESSION_ID;
+			goto end;
+		}
+
+		h_entry = ro_session->h_entry;
+		ro_session_entry = &(ro_session_table->entries[h_entry]);
+
+		ro_session_lock(ro_session_table, ro_session_entry);
+
+		/*we might receive a re-auth request with either timer active or inactive.
+		first remove any existing. if success, decrement ref count.*/
+		if(remove_ro_timer(&ro_session->ro_tl) == 0) {
+			unref++;
+		}
+		if(insert_ro_timer(&ro_session->ro_tl, 1) == 0) {
+			/*increment ref count on timer create success */
+			ref_ro_session(ro_session, 1, 0);
+		}
+
+		/*finally, unref session returned by lookup */
+		unref_ro_session(ro_session, unref + 1, 0);
+		ro_session_unlock(ro_session_table, ro_session_entry);
+	} else {
+		LM_WARN("Received an IMS_RAR without session id\n");
+		result_code = DIAMETER_UNABLE_TO_COMPLY;
+	}
+end:
+	response = cdpb.AAACreateResponse(request);
+	if(!response)
+		return 0;
+	set_4bytes(x, result_code);
+	Ro_add_avp(response, x, 4, AVP_Result_Code, AAA_AVP_FLAG_MANDATORY, 0,
+			AVP_DUPLICATE_DATA, __FUNCTION__);
+	return response;
+}
+
+/*!
+ * \brief Process session-abort request
+ * \param request Diameter request
+ * \return diameter response
+ */
+AAAMessage *ro_process_asr(AAAMessage *request)
+{
+	AAAMessage *response;
+	struct ro_session *ro_session = NULL;
+	int result_code = DIAMETER_LIMITED_SUCCESS;
+	char x[4];
+
+	if(request->sessionId && request->sessionId->data.s) {
+		LM_INFO("Received an IMS_ASR for session id %.*s\n",
+				request->sessionId->data.len, request->sessionId->data.s);
+
+		ro_session = lookup_ro_session_by_session_id(&request->sessionId->data);
+		if(ro_session == NULL) {
+			LM_WARN("no active ro_session with id %.*s - ignoring\n",
+					request->sessionId->data.len, request->sessionId->data.s);
+			result_code = DIAMETER_UNKNOWN_SESSION_ID;
+			goto end;
+		}
+
+		if(dlgb.lookup_terminate_dlg(
+				   ro_session->dlg_h_entry, ro_session->dlg_h_id, NULL)
+				< 0) {
+			result_code = DIAMETER_UNABLE_TO_COMPLY;
+		}
+
+		unref_ro_session(ro_session, 1, 0);
+	} else {
+		LM_WARN("Received an IMS_ASR without session id\n");
+		result_code = DIAMETER_UNABLE_TO_COMPLY;
+	}
+end:
+	response = cdpb.AAACreateResponse(request);
+	if(!response)
+		return 0;
+	set_4bytes(x, result_code);
+	Ro_add_avp(response, x, 4, AVP_Result_Code, AAA_AVP_FLAG_MANDATORY, 0,
+			AVP_DUPLICATE_DATA, __FUNCTION__);
+
+	return response;
+}

+ 3 - 0
src/modules/ims_charging/ims_ro.h

@@ -36,4 +36,7 @@ int get_direction_as_int(str *direction);
 void init_custom_user(pv_spec_t *custom_user_avp);
 void init_app_provided_party(pv_spec_t *app_provided_party_avp_p);
 
+AAAMessage *ro_process_rar(AAAMessage *rtr);
+AAAMessage *ro_process_asr(AAAMessage *rtr);
+
 #endif /* CLIENT_RF_IMS_RO_H */

+ 32 - 11
src/modules/ims_charging/ro_db_handler.c

@@ -35,6 +35,7 @@ str pani_column = str_init(PANI_COL);
 str mac_column = str_init(MAC_COL);
 str app_provided_party_column = str_init(APP_PROVIDED_PARTY_COL);
 str is_final_allocation_column = str_init(IS_FINAL_ALLOCATION_COL);
+str origin_host_column = str_init(ORIGIN_HOST_COL);
 
 typedef enum ro_session_field_idx
 {
@@ -61,7 +62,8 @@ typedef enum ro_session_field_idx
 	PANI_COL_IDX,
 	MAC_COL_IDX,
 	APP_PROVIDED_PARTY_COL_IDX,
-	IS_FINAL_ALLOCATION_COL_IDX
+	IS_FINAL_ALLOCATION_COL_IDX,
+	ORIGIN_HOST_COL_IDX
 
 } ro_session_field_idx_t;
 
@@ -167,7 +169,7 @@ static int select_entire_ro_session_table(db1_res_t **res, int fetch_num_rows)
 			&rating_group_column, &service_identifier_column,
 			&auth_app_id_column, &auth_session_type_column, &pani_column,
 			&mac_column, &app_provided_party_column,
-			&is_final_allocation_column};
+			&is_final_allocation_column, &origin_host_column};
 
 	if(use_ro_table() != 0) {
 		return -1;
@@ -202,18 +204,20 @@ static int get_timer_value(
 {
 	int timer_value;
 	if(session->reserved_secs < (session->valid_for - time_since_last_event)) {
-		if(session->reserved_secs > ro_timer_buffer) {
+		if(session->reserved_secs > session->ro_timer_buffer) {
 			timer_value =
 					session->reserved_secs - time_since_last_event
-					- (session->is_final_allocation ? 0 : ro_timer_buffer);
+					- (session->is_final_allocation ? 0
+													: session->ro_timer_buffer);
 		} else {
 			timer_value = session->reserved_secs - time_since_last_event;
 		}
 	} else {
-		if(session->valid_for > ro_timer_buffer) {
+		if(session->valid_for > session->ro_timer_buffer) {
 			timer_value =
 					session->valid_for - time_since_last_event
-					- (session->is_final_allocation ? 0 : ro_timer_buffer);
+					- (session->is_final_allocation ? 0
+													: session->ro_timer_buffer);
 		} else {
 			timer_value = session->valid_for - time_since_last_event;
 		}
@@ -237,7 +241,8 @@ int load_ro_info_from_db(int hash_size, int fetch_num_rows)
 	int i, nr_rows, dir, active_rating_group, active_service_identifier,
 			reservation_units, dlg_h_entry, dlg_h_id;
 	str session_id, asserted_identity, called_asserted_identity,
-			incoming_trunk_id, outgoing_trunk_id, pani, app_provided_party, mac;
+			incoming_trunk_id, outgoing_trunk_id, pani, app_provided_party, mac,
+			origin_host;
 	time_t now = get_current_time_micro();
 	time_t time_since_last_event;
 	AAASession *auth = 0;
@@ -331,6 +336,7 @@ int load_ro_info_from_db(int hash_size, int fetch_num_rows)
 			GET_STR_VALUE(mac, values, MAC_COL_IDX, 0, 0);
 			GET_STR_VALUE(app_provided_party, values,
 					APP_PROVIDED_PARTY_COL_IDX, 0, 0);
+			GET_STR_VALUE(origin_host, values, ORIGIN_HOST_COL_IDX, 0, 0);
 			active_rating_group =
 					VAL_INT(GET_FIELD_IDX(values, RATING_GROUP_COL_IDX));
 			active_service_identifier =
@@ -343,7 +349,7 @@ int load_ro_info_from_db(int hash_size, int fetch_num_rows)
 					dlg_h_entry, dlg_h_id, reservation_units, 0,
 					active_rating_group, active_service_identifier,
 					&incoming_trunk_id, &outgoing_trunk_id, &pani,
-					&app_provided_party);
+					&app_provided_party, ro_timer_buffer);
 
 			if(!session) {
 				LM_ERR("Couldn't restore Ro Session - this is BAD!\n");
@@ -371,6 +377,17 @@ int load_ro_info_from_db(int hash_size, int fetch_num_rows)
 			session->ro_session_id.len = session_id.len;
 			memcpy(session->ro_session_id.s, session_id.s, session_id.len);
 
+			if(origin_host.s && origin_host.len > 0) {
+				session->origin_host.s = (char *)shm_malloc(origin_host.len);
+				if(!session->origin_host.s) {
+					LM_ERR("no more shm mem\n");
+					goto error;
+				}
+
+				session->origin_host.len = origin_host.len;
+				memcpy(session->origin_host.s, origin_host.s, origin_host.len);
+			}
+
 			session->active = VAL_INT(GET_FIELD_IDX(values, STATE_COL_IDX));
 			session->last_event_timestamp =
 					VAL_TIME(GET_FIELD_IDX(values, LAST_EVENT_TS_COL_IDX))
@@ -543,7 +560,8 @@ int update_ro_dbinfo_unsafe(struct ro_session *ro_session)
 				&outgoing_trunk_id_column, &rating_group_column,
 				&service_identifier_column, &auth_app_id_column,
 				&auth_session_type_column, &pani_column, &mac_column,
-				&app_provided_party_column, &is_final_allocation_column};
+				&app_provided_party_column, &is_final_allocation_column,
+				&origin_host_column};
 
 		VAL_TYPE(GET_FIELD_IDX(values, ID_COL_IDX)) = DB1_INT;
 		VAL_NULL(GET_FIELD_IDX(values, ID_COL_IDX)) = 1;
@@ -582,6 +600,7 @@ int update_ro_dbinfo_unsafe(struct ro_session *ro_session)
 				&ro_session->app_provided_party);
 		db_set_int_val(values, IS_FINAL_ALLOCATION_COL_IDX,
 				ro_session->is_final_allocation);
+		db_set_str_val(values, ORIGIN_HOST_COL_IDX, &ro_session->origin_host);
 
 
 		LM_DBG("Inserting ro_session into database\n");
@@ -607,7 +626,7 @@ int update_ro_dbinfo_unsafe(struct ro_session *ro_session)
 				&rating_group_column, &service_identifier_column,
 				&auth_app_id_column, &auth_session_type_column, &pani_column,
 				&mac_column, &app_provided_party_column,
-				&is_final_allocation_column};
+				&is_final_allocation_column, &origin_host_column};
 
 		db_set_int_val(values, HASH_ENTRY_COL_IDX - 1, ro_session->h_entry);
 		db_set_int_val(values, HASH_ID_COL_IDX - 1, ro_session->h_id);
@@ -646,11 +665,13 @@ int update_ro_dbinfo_unsafe(struct ro_session *ro_session)
 				&ro_session->app_provided_party);
 		db_set_int_val(values, IS_FINAL_ALLOCATION_COL_IDX - 1,
 				ro_session->is_final_allocation);
+		db_set_str_val(
+				values, ORIGIN_HOST_COL_IDX - 1, &ro_session->origin_host);
 
 		LM_DBG("Updating ro_session in database\n");
 		if((ro_dbf.update(ro_db_handle, update_keys /*match*/, 0 /*match*/,
 				   values /*match*/, update_keys /*update*/, values /*update*/,
-				   3 /*match*/, 23 /*update*/))
+				   3 /*match*/, 24 /*update*/))
 				!= 0) {
 			LM_ERR("could not update Ro session information in DB... "
 				   "continuing\n");

+ 3 - 2
src/modules/ims_charging/ro_db_handler.h

@@ -12,9 +12,9 @@
 #include "../../lib/srdb1/db.h"
 #include "ro_session_hash.h"
 
-#define RO_TABLE_VERSION 2
+#define RO_TABLE_VERSION 3
 #define RO_SESSION_TABLE_NAME "ro_session"
-#define RO_SESSION_TABLE_COL_NUM 24
+#define RO_SESSION_TABLE_COL_NUM 25
 
 #define ID_COL "id"
 #define HASH_ENTRY_COL "hash_entry"
@@ -40,6 +40,7 @@
 #define MAC_COL "mac"
 #define APP_PROVIDED_PARTY_COL "app_provided_party"
 #define IS_FINAL_ALLOCATION_COL "is_final_allocation"
+#define ORIGIN_HOST_COL "origin_host"
 
 int init_ro_db(const str *db_url, int dlg_hash_size, int db_update_period,
 		int fetch_num_rows);

+ 45 - 1
src/modules/ims_charging/ro_session_hash.c

@@ -175,6 +175,9 @@ inline void destroy_ro_session(struct ro_session *ro_session)
 	if(ro_session->ro_session_id.s && (ro_session->ro_session_id.len > 0)) {
 		shm_free(ro_session->ro_session_id.s);
 	}
+	if(ro_session->origin_host.s && (ro_session->origin_host.len > 0)) {
+		shm_free(ro_session->origin_host.s);
+	}
 
 	shm_free(ro_session);
 }
@@ -217,7 +220,7 @@ struct ro_session *build_new_ro_session(int direction, int auth_appid,
 		unsigned int requested_secs, unsigned int validity_timeout,
 		int active_rating_group, int active_service_identifier,
 		str *incoming_trunk_id, str *outgoing_trunk_id, str *pani,
-		str *app_provided_party)
+		str *app_provided_party, int ro_timer_buffer)
 {
 	LM_DBG("Building Ro Session **********\n");
 	char *p;
@@ -263,6 +266,8 @@ struct ro_session *build_new_ro_session(int direction, int auth_appid,
 	new_ro_session->h_id = 0;
 	new_ro_session->ref = 1;
 
+	new_ro_session->ro_timer_buffer = ro_timer_buffer;
+
 	new_ro_session->rating_group = active_rating_group;
 	new_ro_session->service_identifier = active_service_identifier;
 
@@ -370,3 +375,42 @@ void free_impu_data(struct impu_data *impu_data)
 		impu_data = 0;
 	}
 }
+
+/*!
+ * \brief Lookup an Ro session in the global list by session id
+ * \param session_id diameter session id
+ * \return ro_session on success, NULL on failure
+ */
+struct ro_session *lookup_ro_session_by_session_id(str *session_id)
+{
+	struct ro_session *ro_session;
+	struct ro_session_entry *ro_session_entry;
+	int h_entry;
+
+	for(h_entry = 0; h_entry < ro_session_table->size; h_entry++) {
+		ro_session_entry = &(ro_session_table->entries[h_entry]);
+
+		ro_session_lock(ro_session_table, ro_session_entry);
+
+		for(ro_session = ro_session_entry->first; ro_session;
+				ro_session = ro_session->next) {
+			if(strncmp(ro_session->ro_session_id.s, session_id->s,
+					   session_id->len)
+							== 0
+					&& ro_session->ro_session_id.len == session_id->len) {
+				ref_ro_session(ro_session, 1, 0);
+				LM_DBG("ref ro_session %p with 1 -> %d\n", ro_session,
+						ro_session->ref);
+				ro_session_unlock(ro_session_table, ro_session_entry);
+				LM_DBG("ro_session id=%u found on entry %u\n", ro_session->h_id,
+						h_entry);
+				return ro_session;
+			}
+		}
+		ro_session_unlock(ro_session_table, ro_session_entry);
+	}
+
+	LM_DBG("no ro_session for callid=%.*s found on entry %u\n", session_id->len,
+			session_id->s, h_entry);
+	return 0;
+}

+ 5 - 1
src/modules/ims_charging/ro_session_hash.h

@@ -85,6 +85,8 @@ struct ro_session
 	unsigned int is_final_allocation;
 	long billed;
 	unsigned int ccr_sent;
+	str origin_host;
+	int ro_timer_buffer;
 };
 
 /*! entries in the main ro_session table */
@@ -229,7 +231,7 @@ struct ro_session *build_new_ro_session(int direction, int auth_appid,
 		unsigned int requested_secs, unsigned int validity_timeout,
 		int active_rating_group, int active_service_identifier,
 		str *incoming_trunk_id, str *outgoing_trunk_id, str *pani,
-		str *app_provided_party);
+		str *app_provided_party, int ro_timer_buffer);
 
 /*!
  * \brief Refefence a ro_session with locking
@@ -256,6 +258,8 @@ void unref_ro_session_helper(struct ro_session *ro_session, unsigned int cnt,
 struct ro_session *lookup_ro_session(
 		unsigned int h_entry, str *callid, int direction, unsigned int *del);
 
+struct ro_session *lookup_ro_session_by_session_id(str *session_id);
+
 void free_impu_data(struct impu_data *impu_data);
 
 int put_ro_session_on_wait(struct ro_session *session);

+ 5 - 3
src/modules/ims_charging/ro_timer.c

@@ -333,13 +333,15 @@ void resume_ro_session_ontimeout(
 		} else {
 			int timer_timeout = i_req->new_credit;
 
-			if(i_req->new_credit > ro_timer_buffer /*TIMEOUTBUFFER*/) {
+			if(i_req->new_credit
+					> i_req->ro_session->ro_timer_buffer /*TIMEOUTBUFFER*/) {
 
 				// We haven't finished using our 1st block of units, and we need to set the timer to
-				// (new_credit - ro_timer_buffer[5 secs]) to ensure we get new credit before our previous
+				// (new_credit - i_req->ro_session->ro_timer_buffer[5 secs]) to ensure we get new credit before our previous
 				// reservation is exhausted. This will only be done the first time, because the timer
 				// will always be fired 5 seconds before we run out of time thanks to this operation
-				timer_timeout = i_req->new_credit - ro_timer_buffer;
+				timer_timeout =
+						i_req->new_credit - i_req->ro_session->ro_timer_buffer;
 			}
 
 			ret = insert_ro_timer(&i_req->ro_session->ro_tl, timer_timeout);

+ 2 - 1
utils/kamctl/mysql/ims_charging-create.sql

@@ -1,4 +1,4 @@
-INSERT INTO version (table_name, table_version) values ('ro_session','2');
+INSERT INTO version (table_name, table_version) values ('ro_session','3');
 CREATE TABLE `ro_session` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `hash_entry` int(10) unsigned NOT NULL,
@@ -24,6 +24,7 @@ CREATE TABLE `ro_session` (
   `mac` varchar(17) DEFAULT NULL,
   `app_provided_party` varchar(100) DEFAULT NULL,
   `is_final_allocation` int(1) unsigned NOT NULL,
+  `origin_host` varchar(150) DEFAULT NULL,
   PRIMARY KEY (`id`),
   KEY `hash_idx` (`hash_entry`,`hash_id`)
 );