From e010736858bb04766b5871fc6ede0b1279d84132 Mon Sep 17 00:00:00 2001 From: jaybeepee Date: Wed, 20 Jan 2016 10:36:53 +0200 Subject: [PATCH] modules/ims_charging: added ability to send vendor-specific charge information - this allows for call dispositions to be stored and reported on in OCS - by default this id disabled (modparam - vendor_specific_chargeinfo=0) --- modules/ims_charging/dialog.c | 45 +++++++++--- modules/ims_charging/dialog.h | 2 +- modules/ims_charging/ims_ro.c | 38 +++++++++- modules/ims_charging/ims_ro.h | 7 +- modules/ims_charging/mod.c | 99 +++++++++++++++++++++++++- modules/ims_charging/ro_session_hash.c | 2 +- modules/ims_charging/ro_session_hash.h | 1 + 7 files changed, 178 insertions(+), 16 deletions(-) diff --git a/modules/ims_charging/dialog.c b/modules/ims_charging/dialog.c index 9e463cd46c8..e14793849fc 100644 --- a/modules/ims_charging/dialog.c +++ b/modules/ims_charging/dialog.c @@ -16,14 +16,19 @@ extern struct ims_charging_counters_h ims_charging_cnts_h; void dlg_callback_received(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params) { LM_DBG("Received dialog callback event [%d]\n", type); + unsigned int termcode = 0; switch (type) { case DLGCB_CONFIRMED: dlg_answered(dlg, type, _params); break; case DLGCB_TERMINATED: + dlg_terminated(dlg, type, termcode, "normal call clearing", _params); + break; case DLGCB_FAILED: + dlg_terminated(dlg, type, termcode, "call failed", _params); + break; case DLGCB_EXPIRED: - dlg_terminated(dlg, type, _params); + dlg_terminated(dlg, type, termcode, "dialog timeout", _params); break; default: LM_WARN("Received unknown dialog callback [%d]\n", type); @@ -31,7 +36,6 @@ void dlg_callback_received(struct dlg_cell *dlg, int type, struct dlg_cb_params } void dlg_answered(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params) { - struct sip_msg *reply; struct ro_session* session = 0; struct ro_session_entry* ro_session_entry; time_t now = get_current_time_micro(); @@ -116,19 +120,38 @@ void dlg_answered(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params) } -void dlg_terminated(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params) { +void dlg_terminated(struct dlg_cell *dlg, int type, unsigned int termcode, char* reason, struct dlg_cb_params *_params) { //int i; int unref = 0; struct ro_session *ro_session = 0; struct ro_session_entry *ro_session_entry; struct sip_msg *request; + str s_reason; + + s_reason.s = reason; + s_reason.len = strlen(reason); - LM_DBG("dialog [%p] terminated, lets send stop record\n", dlg); + LM_DBG("dialog [%p] terminated on type [%d], lets send stop record\n", dlg, type); if (!_params) { return; } + LM_DBG("Direction is %d\n", _params->direction); + if (_params->req) { + if (_params->req->first_line.u.request.method_value == METHOD_BYE) { + if (_params->direction == DLG_DIR_DOWNSTREAM) { + LM_DBG("Dialog ended by Caller\n"); + } else { + LM_DBG("Dialog ended by Callee\n"); + } + } else { + LM_DBG("Request is %.*s\n", _params->req->first_line.u.request.method.len, _params->req->first_line.u.request.method.s); + } + } else if (_params->rpl) { + LM_DBG("Reply is [%d - %.*s]", _params->rpl->first_line.u.reply.statuscode, _params->rpl->first_line.u.reply.reason.len, _params->rpl->first_line.u.reply.reason.s); + } + ro_session = (struct ro_session*)*_params->param; if (!ro_session) { LM_ERR("Ro Session object is NULL...... aborting\n"); @@ -155,14 +178,14 @@ void dlg_terminated(struct dlg_cell *dlg, int type, struct dlg_cb_params *_param //double processing for various dialog_terminated callback events. //If however, the call was never answered, then we can continue as normal ro_session_lock(ro_session_table, ro_session_entry); - if (!ro_session->active && (ro_session->start_time != 0)) { - unref_ro_session(ro_session,1); - LM_ERR("Ro Session is not active, but may have been answered [%d]\n", (int)ro_session->start_time); + + LM_DBG("processing dlg_terminated in Ro and session [%.*s] has active = %d", ro_session->ro_session_id.len, ro_session->ro_session_id.s, ro_session->active); + if ((!ro_session->active && (ro_session->start_time != 0)) || (ro_session->ccr_sent == 1)) { + unref_ro_session_unsafe(ro_session,1,ro_session_entry); + LM_ERR("CCR already sent or Ro Session is not active, but may have been answered [%d]\n", (int)ro_session->start_time); ro_session_unlock(ro_session_table, ro_session_entry); return; } - - if (ro_session->active) { // if the call was never activated, there's no timer to remove int ret = remove_ro_timer(&ro_session->ro_tl); @@ -178,8 +201,10 @@ void dlg_terminated(struct dlg_cell *dlg, int type, struct dlg_cb_params *_param } LM_DBG("Sending CCR STOP on Ro_Session [%p]\n", ro_session); - send_ccr_stop(ro_session); + send_ccr_stop_with_param(ro_session, termcode, &s_reason); ro_session->active = -1; //deleted.... terminated .... + ro_session->ccr_sent = 1; +// counter_add(ims_charging_cnts_h.active_ro_sessions, -1); if (ro_db_mode == DB_MODE_REALTIME) { ro_session->flags |= RO_SESSION_FLAG_DELETED; diff --git a/modules/ims_charging/dialog.h b/modules/ims_charging/dialog.h index e64b4d99e37..a295ad31469 100644 --- a/modules/ims_charging/dialog.h +++ b/modules/ims_charging/dialog.h @@ -9,7 +9,7 @@ extern int ro_timer_buffer; void dlg_callback_received(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params); -void dlg_terminated(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params); +void dlg_terminated(struct dlg_cell *dlg, int type, unsigned int termcode, char* reason, struct dlg_cb_params *_params); void dlg_answered(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params); void add_dlg_data_to_contact(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params); void remove_dlg_data_from_contact(struct dlg_cell *dlg, int type, struct dlg_cb_params *_params); diff --git a/modules/ims_charging/ims_ro.c b/modules/ims_charging/ims_ro.c index 783a929c456..73be3609ef3 100644 --- a/modules/ims_charging/ims_ro.c +++ b/modules/ims_charging/ims_ro.c @@ -42,6 +42,8 @@ extern cdp_avp_bind_t *cdp_avp; extern str ro_forced_peer; extern int ro_db_mode; extern struct ims_charging_counters_h ims_charging_cnts_h; +extern int vendor_specific_id; +extern int vendor_specific_chargeinfo; struct session_setup_data { struct ro_session *ro_session; @@ -180,6 +182,21 @@ inline int Ro_add_termination_cause(AAAMessage *msg, unsigned int term_code) { return Ro_add_avp(msg, s.s, s.len, AVP_Termination_Cause, AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__); } +inline int Ro_add_vendor_specific_termination_cause(AAAMessage *msg, unsigned int term_code) { + char x[4]; + str s = {x, 4}; + uint32_t code = htonl(term_code); + memcpy(x, &code, sizeof (uint32_t)); + + return Ro_add_avp(msg, s.s, s.len, VS_TERMCODE, AAA_AVP_FLAG_VENDOR_SPECIFIC, 10, AVP_DUPLICATE_DATA, __FUNCTION__); +} + +inline int Ro_add_vendor_specific_termination_reason(AAAMessage *msg, str* reason) { + return Ro_add_avp(msg, reason->s, reason->len, VS_TERMREASON, AAA_AVP_FLAG_VENDOR_SPECIFIC, 10, AVP_DUPLICATE_DATA, __FUNCTION__); +} + + + /* called only when building stop record AVPS */ inline int Ro_add_multiple_service_credit_Control_stop(AAAMessage *msg, int used_unit, int active_rating_group, int active_service_identifier) { char x[4]; @@ -745,7 +762,7 @@ long get_current_time_micro() { return tv.tv_sec*1000000 + tv.tv_usec; } -void send_ccr_stop(struct ro_session *ro_session) { +void send_ccr_stop_with_param(struct ro_session *ro_session, unsigned int code, str* reason) { AAASession * auth = 0; Ro_CCR_t * ro_ccr_data = 0; AAAMessage * ccr = 0; @@ -890,6 +907,16 @@ void send_ccr_stop(struct ro_session *ro_session) { LM_ERR("problem add Termination cause AVP to STOP record.\n"); } + if (vendor_specific_chargeinfo) { + if (!Ro_add_vendor_specific_termination_cause(ccr, code)) { + LM_ERR("problem add Termination cause AVP to STOP record.\n"); + } + + if (!Ro_add_vendor_specific_termination_reason(ccr, reason)) { + LM_ERR("problem add Termination cause AVP to STOP record.\n"); + } + } + cdpb.AAASessionsUnlock(auth->hash); if (ro_forced_peer.len > 0) { @@ -956,6 +983,11 @@ static void resume_on_termination_ccr(int is_timeout, void *param, AAAMessage *c } counter_inc(ims_charging_cnts_h.successful_final_ccrs); + Ro_free_CCA(ro_cca_data); + if (!is_timeout && cca) { + cdpb.AAAFreeMessage(&cca); + } + return; error: counter_inc(ims_charging_cnts_h.failed_final_ccrs); @@ -1162,6 +1194,7 @@ int Ro_Send_CCR(struct sip_msg *msg, struct dlg_cell *dlg, int dir, int reservat LM_DBG("new CC Ro Session ID: [%.*s] stored in shared memory address [%p]\n", cc_acc_session->id.len, cc_acc_session->id.s, new_session); LM_DBG("Sending CCR Diameter message.\n"); +// new_session->ccr_sent = 1; //assume we will send successfully cdpb.AAASessionsUnlock(cc_acc_session->hash); if (ro_forced_peer.len > 0) { @@ -1174,6 +1207,7 @@ int Ro_Send_CCR(struct sip_msg *msg, struct dlg_cell *dlg, int dir, int reservat if (ret != 1) { LM_ERR("Failed to send Diameter CCR\n"); +// new_session->ccr_sent = 0; goto error; } @@ -1290,7 +1324,7 @@ static void resume_on_initial_ccr(int is_timeout, void *param, AAAMessage *cca, LM_DBG("Freeing CCA message\n"); cdpb.AAAFreeMessage(&cca); - link_ro_session(ssd->ro_session, 1); /* create extra ref for the fact that dialog has a handle in the callbacks */ + link_ro_session(ssd->ro_session, 0); if (ro_db_mode == DB_MODE_REALTIME) { ssd->ro_session->flags |= RO_SESSION_FLAG_NEW; diff --git a/modules/ims_charging/ims_ro.h b/modules/ims_charging/ims_ro.h index 361bd8f57ae..7a1fc3a7bb7 100644 --- a/modules/ims_charging/ims_ro.h +++ b/modules/ims_charging/ims_ro.h @@ -6,6 +6,11 @@ #include "../dialog_ng/dlg_hash.h" #include "ro_session_hash.h" +typedef enum { + VS_TERMCODE = 3, + VS_TERMREASON = 2 +} vs_term_avp; + struct interim_ccr { struct ro_session* ro_session; int new_credit; @@ -19,7 +24,7 @@ int Ro_Send_CCR(struct sip_msg *msg, struct dlg_cell *dlg, int dir, int reservat str *incoming_trunk_id, str *outgoing_trunk_id, str *enb_cell_id, cfg_action_t* action, unsigned int tindex, unsigned int tlabel); long get_current_time_micro(); void send_ccr_interim(struct ro_session* ro_session, unsigned int used, unsigned int reserve); -void send_ccr_stop(struct ro_session *ro_session); +void send_ccr_stop_with_param(struct ro_session *ro_session, unsigned int code, str* reason); int get_direction_as_int(str* direction); #endif /* CLIENT_RF_IMS_RO_H */ diff --git a/modules/ims_charging/mod.c b/modules/ims_charging/mod.c index 9f7774e5fc2..c01cb8df536 100644 --- a/modules/ims_charging/mod.c +++ b/modules/ims_charging/mod.c @@ -33,6 +33,9 @@ char* ro_service_context_id_ext_s = "ext"; char* ro_service_context_id_mnc_s = "01"; char* ro_service_context_id_mcc_s = "001"; char* ro_service_context_id_release_s = "8"; +int termination_code = 0; +int vendor_specific_id = 10; +int vendor_specific_chargeinfo = 0; static int ro_session_hash_size = 4096; int ro_timer_buffer = 5; int interim_request_credits = 30; @@ -84,6 +87,7 @@ static int mod_child_init(int); static void mod_destroy(void); 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); +static int w_ro_ccr_stop(struct sip_msg *msg, char* direction, char* _code, char* _reason); //void ro_session_ontimeout(struct ro_tl *tl); @@ -91,10 +95,12 @@ int create_response_avp_string(char* name, str* val); static int w_ro_set_session_id_avp(struct sip_msg *msg, char *str1, char *str2); static int ro_fixup(void **param, int param_no); +static int ro_fixup_stop(void **param, int param_no); static cmd_export_t cmds[] = { { "Ro_CCR", (cmd_function) w_ro_ccr, 5, ro_fixup, 0, REQUEST_ROUTE }, - { "Ro_set_session_id_avp", (cmd_function) w_ro_set_session_id_avp, 0, 0, 0, REQUEST_ROUTE | ONREPLY_ROUTE }, + { "Ro_CCR_Stop",(cmd_function) w_ro_ccr_stop, 3, ro_fixup_stop, 0, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE}, + { "Ro_set_session_id_avp", (cmd_function) w_ro_set_session_id_avp, 0, 0, 0, REQUEST_ROUTE | ONREPLY_ROUTE }, { 0, 0, 0, 0, 0, 0 } }; @@ -127,6 +133,8 @@ static param_export_t params[] = { { "db_mode", INT_PARAM, &ro_db_mode_param }, { "db_url", PARAM_STRING, &db_url }, { "db_update_period", INT_PARAM, &db_update_period }, + { "vendor_specific_chargeinfo", INT_PARAM, &vendor_specific_chargeinfo }, /* VSI for extra charing info in Ro */ + { "vendor_specific_id", INT_PARAM, &vendor_specific_id }, /* VSI for extra charing info in Ro */ { 0, 0, 0 } }; @@ -355,11 +363,93 @@ static int w_ro_set_session_id_avp(struct sip_msg *msg, char *str1, char *str2) //set avp response with session id res = create_response_avp_string("ro_session_id", &ro_session->ro_session_id); dlgb.release_dlg(dlg); + unref_ro_session(ro_session, 1); return res; } +static int w_ro_ccr_stop(struct sip_msg *msg, char* c_direction, char* _code, char* _reason) { + struct ro_session* ro_session; + struct ro_session_entry *ro_session_entry; + unsigned int h_entry; + str s_code, s_reason; + unsigned int code; + int dir = 0; /*any side*/ + + LM_DBG("Inside Ro_CCR_Stop with direction [%s]\n", c_direction); + if (strlen(c_direction) == 4) { + if (c_direction[0] == 'O' || c_direction[0] == 'o') { + dir = RO_ORIG_DIRECTION; + } else { + dir = RO_TERM_DIRECTION; + } + } else { + LM_ERR("Unknown direction [%s] to terminate\n", c_direction); + return RO_RETURN_FALSE; + } + struct dlg_cell* dlg = dlgb.get_dlg(msg); + if (!dlg) { + LM_ERR("Unable to find dialog to send CCR STOP record\n"); + return RO_RETURN_ERROR; + } + if (get_str_fparam(&s_code, msg, (fparam_t*) _code) < 0) { + LM_ERR("failed to get code\n"); + return RO_RETURN_ERROR; + } + LM_DBG("Code is [%.*s]\n", s_code.len, s_code.s); + if (get_str_fparam(&s_reason, msg, (fparam_t*) _reason) < 0) { + LM_ERR("failed to get reason\n"); + return RO_RETURN_ERROR; + } + if (str2int(&s_code, &code) != 0) { + LM_ERR("Bad response code: [%.*s]\n", s_code.len, s_code.s); + return RO_RETURN_FALSE; + } + +// switch (code) { +// case 486: +// termcode = VS_TERMCODE_BUSYHERE; +// break; +// case 487: +// termcode = VS_TERMCODE_CANCELLED; +// break; +// case 480: +// case 408: +// /* subscriber not available */ +// termcode = VS_TERMCODE_NOTFOUND; +// break; +// } + + LM_DBG("Sending Stop record with code [%d] and reason [%.*s]\n", code, s_reason.len, s_reason.s); + + LM_DBG("Found DLG [%d : %d]\n", dlg->h_id, dlg->h_entry); + + ro_session = lookup_ro_session(dlg->h_entry, &dlg->callid, dir, 0); + if (ro_session == NULL) { + LM_DBG("no ro_session - ignoring\n"); + return RO_RETURN_TRUE; + } + h_entry = ro_session->h_entry; + ro_session_entry = &(ro_session_table->entries[h_entry]); + + ro_session_lock(ro_session_table, ro_session_entry); + + if (ro_session->ccr_sent == 1) { + LM_DBG("Ro CCR already sent for session [%.*s]\n", ro_session->ro_session_id.len, ro_session->ro_session_id.s); + goto done; + } + send_ccr_stop_with_param(ro_session, code, &s_reason); + //TODO = check the CCR was sent successfully. + LM_DBG("Setting Ro session [%.*s] ccr_sent to 1\n", ro_session->ro_session_id.len, ro_session->ro_session_id.s); + ro_session->ccr_sent = 1; + ro_session->active = -1; +// counter_add(ims_charging_cnts_h.active_ro_sessions, -1); +done: + unref_ro_session_unsafe(ro_session, 1, ro_session_entry); + ro_session_unlock(ro_session_table, ro_session_entry); + return RO_RETURN_TRUE; +} static int w_ro_ccr(struct sip_msg *msg, char* c_route_name, char* c_direction, int reservation_units, char* c_incoming_trunk_id, char* c_outgoing_trunk_id) { /* PSEUDOCODE/NOTES @@ -544,3 +634,10 @@ static int ro_fixup(void **param, int param_no) { return 0; } + +static int ro_fixup_stop(void **param, int param_no) { + if (param_no == 2 || param_no == 3) { + return fixup_var_pve_12(param, param_no); + } + return 0; +} diff --git a/modules/ims_charging/ro_session_hash.c b/modules/ims_charging/ro_session_hash.c index 9c2866cf8ef..850d99f5e27 100644 --- a/modules/ims_charging/ro_session_hash.c +++ b/modules/ims_charging/ro_session_hash.c @@ -227,7 +227,7 @@ struct ro_session* build_new_ro_session(int direction, int auth_appid, int auth_ new_ro_session->h_entry = dlg_h_entry; /* we will use the same entry ID as the dlg - saves us using our own hash function */ new_ro_session->h_id = 0; - new_ro_session->ref = 0; + new_ro_session->ref = 1; new_ro_session->rating_group = active_rating_group; new_ro_session->service_identifier = active_service_identifier; diff --git a/modules/ims_charging/ro_session_hash.h b/modules/ims_charging/ro_session_hash.h index 07d373e39d1..422e502e23b 100644 --- a/modules/ims_charging/ro_session_hash.h +++ b/modules/ims_charging/ro_session_hash.h @@ -76,6 +76,7 @@ struct ro_session { int service_identifier; unsigned int is_final_allocation; long billed; + unsigned int ccr_sent; }; /*! entries in the main ro_session table */