void resume_ro_session_ontimeout(struct interim_ccr *i_req, int timeout_or_error) { time_t now = get_current_time_micro(); long used_secs; struct ro_session_entry *ro_session_entry = NULL; int call_terminated = 0; if (!i_req) { LM_ERR("This is so wrong: i_req is NULL\n"); return; } ro_session_entry = &(ro_session_table->entries[i_req->ro_session->h_entry]); ro_session_lock(ro_session_table, ro_session_entry); LM_DBG("credit=%d credit_valid_for=%d", i_req->new_credit, i_req->credit_valid_for); used_secs = rint((now - ((timeout_or_error==1 && i_req->ro_session->last_event_timestamp_backup>0)?i_req->ro_session->last_event_timestamp_backup : i_req->ro_session->last_event_timestamp)) / (float) 1000000); /* check to make sure diameter server is giving us sane values */ if (i_req->credit_valid_for !=0 && i_req->new_credit > i_req->credit_valid_for) { LM_WARN("That's weird, Diameter server gave us credit with a lower validity period :D. Setting reserved time to validity period instead \n"); i_req->new_credit = i_req->credit_valid_for; } if (i_req->new_credit > 0) { //now insert the new timer i_req->ro_session->last_event_timestamp = get_current_time_micro(); i_req->ro_session->event_type = answered; i_req->ro_session->valid_for = i_req->credit_valid_for; int ret = 0; if (i_req->is_final_allocation) { LM_DBG("This is a final allocation and call will end in %i seconds\n", i_req->new_credit); i_req->ro_session->event_type = no_more_credit; ret = insert_ro_timer(&i_req->ro_session->ro_tl, i_req->new_credit); } else { int timer_timeout = i_req->new_credit; if (i_req->new_credit > 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 // 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; } ret = insert_ro_timer(&i_req->ro_session->ro_tl, timer_timeout); } // update to the new block of units we got i_req->ro_session->reserved_secs = i_req->new_credit; if (ret != 0) { LM_CRIT("unable to insert timer for Ro Session [%.*s]\n", i_req->ro_session->ro_session_id.len, i_req->ro_session->ro_session_id.s); } else { ref_ro_session(i_req->ro_session, 1, 0); } if (ro_db_mode == DB_MODE_REALTIME) { i_req->ro_session->flags |= RO_SESSION_FLAG_CHANGED; if (update_ro_dbinfo_unsafe(i_req->ro_session) != 0) { LM_ERR("Failed to update Ro session in DB... continuing\n"); } } } else { /* just put the timer back in with however many seconds are left (if any!!! in which case we need to kill */ /* also update the event type to no_more_credit to save on processing the next time we get here */ i_req->ro_session->event_type = no_more_credit; if (!timeout_or_error) i_req->ro_session->last_event_timestamp = get_current_time_micro(); int whatsleft = i_req->ro_session->reserved_secs - used_secs; if (whatsleft <= 0) { // TODO we need to handle this situation more precisely. // in case CCR times out, we get a call shutdown but the error message assumes it was due to a lack of credit. // LM_WARN("Immediately killing call due to no more credit *OR* no CCA received (timeout) after reservation request\n"); // // we need to unlock the session or else we might get a deadlock on dlg_terminated() dialog callback. // Do not unref the session because it will be made inside the dlg_terminated() function. // //unref_ro_session_unsafe(i_req->ro_session, 1, ro_session_entry); ro_session_unlock(ro_session_table, ro_session_entry); dlgb.lookup_terminate_dlg(i_req->ro_session->dlg_h_entry, i_req->ro_session->dlg_h_id, NULL); call_terminated = 1; } else { LM_DBG("No more credit for user - letting call run out of money in [%i] seconds", whatsleft); int ret = insert_ro_timer(&i_req->ro_session->ro_tl, whatsleft); if (ret != 0) { LM_CRIT("unable to insert timer for Ro Session [%.*s]\n", i_req->ro_session->ro_session_id.len, i_req->ro_session->ro_session_id.s); } else { ref_ro_session(i_req->ro_session, 1, 0); } } } // // if call was forcefully terminated, the lock was released before dlgb.lookup_terminate_dlg() function call. // if (!call_terminated) { unref_ro_session(i_req->ro_session, 1, 0); //unref from the initial timer that fired this event. ro_session_unlock(ro_session_table, ro_session_entry); } shm_free(i_req); LM_DBG("Exiting async ccr interim nicely"); }
static void resume_on_initial_ccr(int is_timeout, void *param, AAAMessage *cca, long elapsed_msecs) { Ro_CCA_t *ro_cca_data = NULL; struct cell *t = NULL; struct session_setup_data *ssd = (struct session_setup_data *) param; int error_code = RO_RETURN_ERROR; if (is_timeout) { counter_inc(ims_charging_cnts_h.ccr_timeouts); LM_ERR("Transaction timeout - did not get CCA\n"); error_code = RO_RETURN_ERROR; goto error0; } counter_inc(ims_charging_cnts_h.ccr_replies_received); counter_add(ims_charging_cnts_h.ccr_response_time, elapsed_msecs); if (!cca) { LM_ERR("Error reserving credit for CCA.\n"); error_code = RO_RETURN_ERROR; goto error0; } if (!ssd) { LM_ERR("Session lookup data is NULL.\n"); error_code = RO_RETURN_ERROR; goto error0; } // we make sure the transaction exists if (tmb.t_lookup_ident(&t, ssd->tindex, ssd->tlabel) < 0) { LM_ERR("t_continue: transaction not found\n"); error_code = RO_RETURN_ERROR; goto error0; } // we bring the list of AVPs of the transaction to the current context set_avp_list(AVP_TRACK_FROM | AVP_CLASS_URI, &t->uri_avps_from); set_avp_list(AVP_TRACK_TO | AVP_CLASS_URI, &t->uri_avps_to); set_avp_list(AVP_TRACK_FROM | AVP_CLASS_USER, &t->user_avps_from); set_avp_list(AVP_TRACK_TO | AVP_CLASS_USER, &t->user_avps_to); set_avp_list(AVP_TRACK_FROM | AVP_CLASS_DOMAIN, &t->domain_avps_from); set_avp_list(AVP_TRACK_TO | AVP_CLASS_DOMAIN, &t->domain_avps_to); ro_cca_data = Ro_parse_CCA_avps(cca); if (!ro_cca_data) { LM_ERR("Could not parse CCA message response.\n"); error_code = RO_RETURN_ERROR; create_cca_result_code(0); goto error0; } create_cca_result_code((int)ro_cca_data->resultcode); if (ro_cca_data->resultcode != 2001) { LM_ERR("Got bad CCA result code - reservation failed"); error_code = RO_RETURN_FALSE; goto error1; } LM_DBG("Valid CCA response with time chunk of [%i] and validity [%i]\n", ro_cca_data->mscc->granted_service_unit->cc_time, ro_cca_data->mscc->validity_time); if (ro_cca_data->mscc->granted_service_unit->cc_time <= 0) { LM_DBG("got zero GSU.... reservation failed"); error_code = RO_RETURN_FALSE; goto error1; } ssd->ro_session->last_event_timestamp = get_current_time_micro(); ssd->ro_session->event_type = pending; ssd->ro_session->reserved_secs = 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->final_unit_action && (ro_cca_data->mscc->final_unit_action->action == 0)) ssd->ro_session->is_final_allocation = 1; Ro_free_CCA(ro_cca_data); LM_DBG("Freeing CCA message\n"); cdpb.AAAFreeMessage(&cca); link_ro_session(ssd->ro_session, 0); if (ro_db_mode == DB_MODE_REALTIME) { ssd->ro_session->flags |= RO_SESSION_FLAG_NEW; if (update_ro_dbinfo(ssd->ro_session) != 0) { LM_ERR("Failed to update ro_session in database... continuing\n"); }; } unref_ro_session(ssd->ro_session, 1); /* release our reference */ create_cca_return_code(RO_RETURN_TRUE); if (t) tmb.unref_cell(t); tmb.t_continue(ssd->tindex, ssd->tlabel, ssd->action); shm_free(ssd); counter_inc(ims_charging_cnts_h.successful_initial_ccrs); return; error1: Ro_free_CCA(ro_cca_data); error0: LM_DBG("Trying to reserve credit on initial INVITE failed on cdp callback\n"); // counter_add(ims_charging_cnts_h.active_ro_sessions, -1); /*we bumped active on the original initial ccr sent */ counter_inc(ims_charging_cnts_h.failed_initial_ccrs); /* drop by one as theoretically this is failed initial ccr */ create_cca_return_code(error_code); if (!is_timeout && cca) { cdpb.AAAFreeMessage(&cca); } if (t) tmb.unref_cell(t); tmb.t_continue(ssd->tindex, ssd->tlabel, ssd->action); shm_free(ssd); }
void dlg_terminated(struct dlg_cell *dlg, int type, 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; LM_DBG("dialog [%p] terminated, lets send stop record\n", dlg); if (!_params) { return; } ro_session = (struct ro_session*)*_params->param; if (!ro_session) { LM_ERR("Ro Session object is NULL...... aborting\n"); return; } request = _params->req; if (!request) { LM_WARN("dlg_terminated has no SIP request associated.\n"); } if (dlg && (dlg->callid.s && dlg->callid.len > 0)) { /* find the session for this call, possibly both for orig and term*/ //for (i=0; i<2; i++) { //TODO: try and get the Ro session specifically for terminating dialog or originating one //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 the Ro session is not active we don't need to do anything. This prevents //double processing for various dialog_terminated callback events. //If however, the call was never answered, then we can continue as normal 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); return; } ro_session_lock(ro_session_table, ro_session_entry); 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); if (ret < 0) { LM_CRIT("unable to unlink the timer on ro_session %p [%.*s]\n", ro_session, ro_session->cdp_session_id.len, ro_session->cdp_session_id.s); } else if (ret > 0) { LM_WARN("inconsistent ro timer data on ro_session %p [%.*s]\n", ro_session, ro_session->cdp_session_id.len, ro_session->cdp_session_id.s); } else { unref++; } } LM_DBG("Sending CCR STOP on Ro_Session [%p]\n", ro_session); send_ccr_stop(ro_session); ro_session->active = 0; //ro_session->start_time; unref_ro_session_unsafe(ro_session, 1+unref, ro_session_entry); //lock already acquired //unref_ro_session_unsafe(ro_session, 2+unref, ro_session_entry); //lock already acquired ro_session_unlock(ro_session_table, ro_session_entry); //} //} } }
static int w_ro_ccr(struct sip_msg *msg, char* c_route_name, char* c_direction, char* c_charge_type, char* c_unit_type, int reservation_units, char* c_trunk_id) { /* PSEUDOCODE/NOTES * 1. What mode are we in - terminating or originating * 2. check request type - IEC - Immediate Event Charging * ECUR - Event Charging with Unit Reservation * SCUR - Session Charging with Unit Reservation * 3. probably only do SCUR in this module for now - can see event based charging in another component instead (AS for SMS for example, etc) * 4. Check a dialog exists for call, if not we fail * 5. make sure we dont already have an Ro Session for this dialog * 6. create new Ro Session * 7. register for DLG callback passing new Ro session as parameter - (if dlg torn down we know which Ro session it is associated with) * * */ int ret = RO_RETURN_TRUE; int dir = 0; str identity = {0, 0}, contact = {0, 0}; struct hdr_field *h=0; cfg_action_t* cfg_action; tm_cell_t *t; unsigned int tindex = 0, tlabel = 0; struct impu_data *impu_data; char *p; struct dlg_cell* dlg; unsigned int len; struct ro_session *ro_session = 0; int free_contact = 0; str s_route_name, s_direction, s_charge_type, s_unit_type, s_trunk_id; if (get_str_fparam(&s_route_name, msg, (fparam_t*) c_route_name) < 0) { LM_ERR("failed to get s_route_name\n"); return RO_RETURN_ERROR; } if (get_str_fparam(&s_direction, msg, (fparam_t*) c_direction) < 0) { LM_ERR("failed to get s_direction\n"); return RO_RETURN_ERROR; } if (get_str_fparam(&s_charge_type, msg, (fparam_t*) c_charge_type) < 0) { LM_ERR("failed to get s_charge_type\n"); return RO_RETURN_ERROR; } if (get_str_fparam(&s_unit_type, msg, (fparam_t*) c_unit_type) < 0) { LM_ERR("failed to get s_unit_type\n"); return RO_RETURN_ERROR; } if (get_str_fparam(&s_trunk_id, msg, (fparam_t*) c_trunk_id) < 0) { LM_ERR("failed to get s_trunk_id\n"); return RO_RETURN_ERROR; } LM_DBG("Ro CCR initiated: direction:%.*s, charge_type:%.*s, unit_type:%.*s, reservation_units:%i, route_name:%.*s, trunk_id:%.*s\n", s_direction.len, s_direction.s, s_charge_type.len, s_charge_type.s, s_unit_type.len, s_unit_type.s, reservation_units, s_route_name.len, s_route_name.s, s_trunk_id.len, s_trunk_id.s); if (msg->first_line.type != SIP_REQUEST) { LM_ERR("Ro_CCR() called from SIP reply."); return RO_RETURN_ERROR;; } //make sure we can get the dialog! if not, we can't continue dlg = dlgb.get_dlg(msg); if (!dlg) { LM_ERR("Unable to find dialog and cannot do Ro charging without it\n"); return RO_RETURN_ERROR; } dir = get_direction_as_int(&s_direction); if (dir == RO_ORIG_DIRECTION) { //get caller IMPU from asserted identity if ((identity = cscf_get_asserted_identity(msg, 0)).len == 0) { LM_DBG("No P-Asserted-Identity hdr found. Using From hdr for asserted_identity"); identity = dlg->from_uri; } //get caller contact from contact header - if not present then skip this if ((contact = cscf_get_contact(msg)).len == 0) { LM_WARN("Can not get contact from message - will not get callbacks if this IMPU is removed to terminate call"); goto send_ccr; } } else if (dir == RO_TERM_DIRECTION){ //get callee IMPU from called part id - if not present then skip this if ((identity = cscf_get_public_identity_from_called_party_id(msg, &h)).len == 0) { LM_WARN("No P-Called-Identity hdr found - will not get callbacks if this IMPU is removed to terminate call"); goto send_ccr; } //get callee contact from request URI contact = cscf_get_contact_from_requri(msg); free_contact = 1; } else { LM_CRIT("don't know what to do in unknown mode - should we even get here\n"); ret = RO_RETURN_ERROR; goto done; } LM_DBG("IMPU data to pass to usrloc: contact <%.*s> identity <%.*s>\n", contact.len, contact.s, identity.len, identity.s); //create impu_data_parcel len = identity.len + contact.len + sizeof (struct impu_data); impu_data = (struct impu_data*) shm_malloc(len); if (!impu_data) { LM_ERR("Unable to allocate memory for impu_data, trying to send CCR\n"); ret = RO_RETURN_ERROR; goto done; } memset(impu_data, 0, len); p = (char*) (impu_data + 1); impu_data->identity.s = p; impu_data->identity.len = identity.len; memcpy(p, identity.s, identity.len); p += identity.len; impu_data->contact.s = p; impu_data->contact.len = contact.len; memcpy(p, contact.s, contact.len); p += contact.len; if (p != (((char*) impu_data) + len)) { LM_ERR("buffer overflow creating impu data, trying to send CCR\n"); shm_free(impu_data); ret = RO_RETURN_ERROR; goto done; } //reg for callbacks on confirmed and terminated if (dlgb.register_dlgcb(dlg, /* DLGCB_RESPONSE_FWDED */ DLGCB_CONFIRMED, add_dlg_data_to_contact, (void*)impu_data ,NULL ) != 0) { LM_CRIT("cannot register callback for dialog confirmation\n"); ret = RO_RETURN_ERROR; goto done; } if (dlgb.register_dlgcb(dlg, DLGCB_TERMINATED | DLGCB_FAILED | DLGCB_EXPIRED /*| DLGCB_DESTROY */, remove_dlg_data_from_contact, (void*)impu_data, NULL ) != 0) { LM_CRIT("cannot register callback for dialog termination\n"); ret = RO_RETURN_ERROR; goto done; } send_ccr: //check if we need to send_ccr - //we get the ro_session based on dlg->h_id and dlg->h_entry and direction 0 (so get any ro_session) //if it already exists then we go to done if (single_ro_session_per_dialog && (ro_session = lookup_ro_session(dlg->h_entry, &dlg->callid, 0, 0))) { LM_DBG("single_ro_session_per_dialog = 1 and ro_session already exists for this dialog -so we don't need to send another one\n"); unref_ro_session(ro_session,1);//for the lookup ro session ref goto done; } LM_DBG("Looking for route block [%.*s]\n", s_route_name.len, s_route_name.s); int ri = route_get(&main_rt, s_route_name.s); if (ri < 0) { LM_ERR("unable to find route block [%.*s]\n", s_route_name.len, s_route_name.s); ret = RO_RETURN_ERROR; goto done; } cfg_action = main_rt.rlist[ri]; if (!cfg_action) { LM_ERR("empty action lists in route block [%.*s]\n", s_route_name.len, s_route_name.s); ret = RO_RETURN_ERROR; goto done; } //before we send lets suspend the transaction t = tmb.t_gett(); if (t == NULL || t == T_UNDEFINED) { if (tmb.t_newtran(msg) < 0) { LM_ERR("cannot create the transaction for CCR async\n"); ret = RO_RETURN_ERROR; goto done; } t = tmb.t_gett(); if (t == NULL || t == T_UNDEFINED) { LM_ERR("cannot lookup the transaction\n"); ret = RO_RETURN_ERROR; goto done; } } LM_DBG("Suspending SIP TM transaction\n"); if (tmb.t_suspend(msg, &tindex, &tlabel) < 0) { LM_ERR("failed to suspend the TM processing\n"); ret = RO_RETURN_ERROR; goto done; } ret = Ro_Send_CCR(msg, dlg, dir, &s_charge_type, &s_unit_type, reservation_units, &s_trunk_id, cfg_action, tindex, tlabel); if(ret < 0){ LM_ERR("Failed to send CCR\n"); tmb.t_cancel_suspend(tindex, tlabel); } done: if(free_contact) shm_free(contact.s);// shm_malloc in cscf_get_public_identity_from_requri return ret; }