mirror of https://github.com/sipwise/kamailio.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
492 lines
17 KiB
492 lines
17 KiB
/*
|
|
* File: ro_timer.c
|
|
* Author: Jason Penton
|
|
*
|
|
* Created on 06 April 2011, 1:37 PM
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include <math.h>
|
|
|
|
#include "../../mem/shm_mem.h"
|
|
#include "../ims_dialog/dlg_load.h"
|
|
#include "ro_timer.h"
|
|
#include "ro_session_hash.h"
|
|
#include "ims_ro.h"
|
|
#include "ro_db_handler.h"
|
|
#include "mod.h"
|
|
#include "ims_charging_stats.h"
|
|
#include "../../counters.h"
|
|
|
|
extern int interim_request_credits;
|
|
extern int ro_timer_buffer;
|
|
extern int ro_db_mode;
|
|
extern struct dlg_binds dlgb;
|
|
extern struct ims_charging_counters_h ims_charging_cnts_h;
|
|
|
|
/*! global dialog timer */
|
|
struct ro_timer *roi_timer = 0;
|
|
/*! global dialog timer handler */
|
|
ro_timer_handler timer_hdl = 0;
|
|
|
|
/*!
|
|
* \brief Initialize the ro_session timer handler
|
|
* Initialize the ro_session timer handler, allocate the lock and a global
|
|
* timer in shared memory. The global timer handler will be set on success.
|
|
* \param hdl ro_session timer handler
|
|
* \return 0 on success, -1 on failure
|
|
*/
|
|
int init_ro_timer(ro_timer_handler hdl) {
|
|
roi_timer = (struct ro_timer*) shm_malloc(sizeof (struct ro_timer));
|
|
if (roi_timer == 0) {
|
|
LM_ERR("no more shm mem\n");
|
|
return -1;
|
|
}
|
|
memset(roi_timer, 0, sizeof (struct ro_timer));
|
|
|
|
roi_timer->first.next = roi_timer->first.prev = &(roi_timer->first);
|
|
|
|
roi_timer->lock = lock_alloc();
|
|
if (roi_timer->lock == 0) {
|
|
LM_ERR("failed to alloc lock\n");
|
|
goto error0;
|
|
}
|
|
|
|
if (lock_init(roi_timer->lock) == 0) {
|
|
LM_ERR("failed to init lock\n");
|
|
goto error1;
|
|
}
|
|
|
|
timer_hdl = hdl;
|
|
return 0;
|
|
error1:
|
|
lock_dealloc(roi_timer->lock);
|
|
error0:
|
|
shm_free(roi_timer);
|
|
roi_timer = 0;
|
|
return -1;
|
|
}
|
|
|
|
/*!
|
|
* \brief Destroy global ro_session timer
|
|
*/
|
|
void destroy_ro_timer(void) {
|
|
if (roi_timer == 0)
|
|
return;
|
|
|
|
lock_destroy(roi_timer->lock);
|
|
lock_dealloc(roi_timer->lock);
|
|
|
|
shm_free(roi_timer);
|
|
roi_timer = 0;
|
|
}
|
|
|
|
/*!
|
|
* \brief Helper function for insert_ro_session_timer
|
|
* \see insert_ro_session_timer
|
|
* \param tl ro_session timer list
|
|
*/
|
|
static inline void insert_ro_timer_unsafe(struct ro_tl *tl) {
|
|
struct ro_tl* ptr;
|
|
|
|
/* insert in sorted order */
|
|
for (ptr = roi_timer->first.prev; ptr != &roi_timer->first; ptr = ptr->prev) {
|
|
if (ptr->timeout <= tl->timeout)
|
|
break;
|
|
}
|
|
|
|
LM_DBG("inserting %p for %d\n", tl, tl->timeout);
|
|
LM_DBG("BEFORE ptr [%p], ptr->next [%p], ptr->next->prev [%p]", ptr, ptr->next, ptr->next->prev);
|
|
tl->prev = ptr;
|
|
tl->next = ptr->next;
|
|
tl->prev->next = tl;
|
|
tl->next->prev = tl;
|
|
LM_DBG("AFTER tl->prev [%p], tl->next [%p]", tl->prev, tl->next);
|
|
}
|
|
|
|
/*!
|
|
* \brief Insert a ro_session timer to the list
|
|
* \param tl ro_session timer list
|
|
* \param interval timeout value in seconds
|
|
* \return 0 on success, -1 when the input timer list is invalid
|
|
*/
|
|
int insert_ro_timer(struct ro_tl *tl, int interval) {
|
|
lock_get(roi_timer->lock);
|
|
|
|
LM_DBG("inserting timer for interval [%i]\n", interval);
|
|
if (tl->next != 0 || tl->prev != 0) {
|
|
lock_release(roi_timer->lock);
|
|
LM_CRIT("Trying to insert a bogus ro tl=%p tl->next=%p tl->prev=%p\n",
|
|
tl, tl->next, tl->prev);
|
|
return -1;
|
|
}
|
|
tl->timeout = get_ticks() + interval;
|
|
insert_ro_timer_unsafe(tl);
|
|
|
|
|
|
LM_DBG("TIMER inserted");
|
|
lock_release(roi_timer->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*!
|
|
* \brief Helper function for remove_ro_session_timer
|
|
* \param tl ro_session timer list
|
|
* \see remove_ro_session_timer
|
|
*/
|
|
static inline void remove_ro_timer_unsafe(struct ro_tl *tl) {
|
|
tl->prev->next = tl->next;
|
|
tl->next->prev = tl->prev;
|
|
}
|
|
|
|
/*!
|
|
* \brief Remove a ro_session timer from the list
|
|
* \param tl ro_session timer that should be removed
|
|
* \return 1 when the input timer is empty, 0 when the timer was removed,
|
|
* -1 when the input timer list is invalid
|
|
*/
|
|
int remove_ro_timer(struct ro_tl *tl) {
|
|
lock_get(roi_timer->lock);
|
|
|
|
if (tl->prev == NULL && tl->timeout == 0) {
|
|
lock_release(roi_timer->lock);
|
|
return 1;
|
|
}
|
|
|
|
if (tl->prev == NULL || tl->next == NULL) {
|
|
LM_CRIT("bogus tl=%p tl->prev=%p tl->next=%p\n",
|
|
tl, tl->prev, tl->next);
|
|
lock_release(roi_timer->lock);
|
|
return -1;
|
|
}
|
|
LM_DBG("TIMER [%p] REMOVED\n", tl);
|
|
remove_ro_timer_unsafe(tl);
|
|
tl->next = NULL;
|
|
tl->prev = NULL;
|
|
tl->timeout = 0;
|
|
|
|
lock_release(roi_timer->lock);
|
|
return 0;
|
|
}
|
|
|
|
/*!
|
|
* \brief Update a ro_session timer on the list
|
|
* \param tl dialog timer
|
|
* \param timeout new timeout value in seconds
|
|
* \return 0 on success, -1 when the input list is invalid
|
|
* \note the update is implemented as a remove, insert
|
|
*/
|
|
int update_ro_timer(struct ro_tl *tl, int timeout) {
|
|
lock_get(roi_timer->lock);
|
|
|
|
LM_DBG("Updating ro timer [%p] with timeout [%d]\n", tl, timeout);
|
|
if (tl->next) {
|
|
if (tl->prev == 0) {
|
|
lock_release(roi_timer->lock);
|
|
return -1;
|
|
}
|
|
remove_ro_timer_unsafe(tl);
|
|
}
|
|
|
|
tl->timeout = get_ticks() + timeout;
|
|
insert_ro_timer_unsafe(tl);
|
|
|
|
lock_release(roi_timer->lock);
|
|
return 0;
|
|
}
|
|
|
|
/*!
|
|
* \brief Helper function for ro_timer_routine
|
|
* \param time time for expiration check
|
|
* \return list of expired credit reservations on sessions on success, 0 on failure
|
|
*/
|
|
static inline struct ro_tl* get_expired_ro_sessions(unsigned int time) {
|
|
struct ro_tl *tl, *end, *ret;
|
|
|
|
lock_get(roi_timer->lock);
|
|
|
|
LM_DBG("my ticks are [%d]\n", time);
|
|
|
|
if (roi_timer->first.next == &(roi_timer->first) || roi_timer->first.next->timeout > time) {
|
|
lock_release(roi_timer->lock);
|
|
return 0;
|
|
}
|
|
|
|
end = &roi_timer->first;
|
|
tl = roi_timer->first.next;
|
|
LM_DBG("start with tl=%p tl->prev=%p tl->next=%p (%d) at %d and end with end=%p end->prev=%p end->next=%p\n", tl, tl->prev, tl->next, tl->timeout, time, end, end->prev, end->next);
|
|
while (tl != end && tl->timeout <= time) {
|
|
LM_DBG("getting tl=%p tl->prev=%p tl->next=%p with %d\n", tl, tl->prev, tl->next, tl->timeout);
|
|
tl->prev = 0;
|
|
tl->timeout = 0;
|
|
tl = tl->next;
|
|
}
|
|
LM_DBG("end with tl=%p tl->prev=%p tl->next=%p and d_timer->first.next->prev=%p\n", tl, tl->prev, tl->next, roi_timer->first.next->prev);
|
|
|
|
if (tl == end && roi_timer->first.next->prev) {
|
|
ret = 0;
|
|
} else {
|
|
ret = roi_timer->first.next;
|
|
tl->prev->next = 0;
|
|
roi_timer->first.next = tl;
|
|
tl->prev = &roi_timer->first;
|
|
}
|
|
|
|
lock_release(roi_timer->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*!
|
|
* \brief Timer routine for expiration of credit reservations
|
|
* Timer handler for expiration of credit reservations on a session, runs the global timer handler on them.
|
|
* \param time for expiration checks
|
|
* \param attr unused
|
|
*/
|
|
void ro_timer_routine(unsigned int ticks, void * attr) {
|
|
|
|
struct ro_tl *tl, *ctl;
|
|
LM_DBG("getting expired ro-sessions");
|
|
|
|
tl = get_expired_ro_sessions(ticks);
|
|
|
|
while (tl) {
|
|
ctl = tl;
|
|
tl = tl->next;
|
|
ctl->next = NULL;
|
|
LM_DBG("Ro Session Timer firing: tl=%p next=%p\n", ctl, tl);
|
|
timer_hdl(ctl);
|
|
}
|
|
}
|
|
|
|
void resume_ro_session_ontimeout(struct interim_ccr *i_req) {
|
|
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 - i_req->ro_session->last_event_timestamp) / (float) 1000000);
|
|
|
|
/* check to make sure diameter server is giving us sane values */
|
|
if (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_unsafe(i_req->ro_session, 1);
|
|
}
|
|
|
|
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;
|
|
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_unsafe(i_req->ro_session, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// if call was forcefully terminated, the lock was released before dlgb.lookup_terminate_dlg() function call.
|
|
//
|
|
if (!call_terminated) {
|
|
unref_ro_session_unsafe(i_req->ro_session, 1, ro_session_entry); //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");
|
|
}
|
|
|
|
/* this is the function called when a we need to request more funds/credit. We need to try and reserve more credit.
|
|
* If we cant we need to put a new timer to kill the call at the appropriate time
|
|
*/
|
|
void ro_session_ontimeout(struct ro_tl *tl) {
|
|
time_t now, call_time;
|
|
long used_secs;
|
|
int adjustment;
|
|
str default_out_of_credit_hdrs = {"Reason: outofcredit\r\n", 21};
|
|
|
|
LM_DBG("We have a fired timer [p=%p] and tl=[%i].\n", tl, tl->timeout);
|
|
|
|
/* find the session id for this timer*/
|
|
struct ro_session* ro_session = ((struct ro_session*) ((char *) (tl) - (unsigned long) (&((struct ro_session*) 0)->ro_tl)));
|
|
LM_DBG("offset for ro_tl is [%lu] and ro_session id is [%.*s]\n", (unsigned long) (&((struct ro_session*) 0)->ro_tl), ro_session->ro_session_id.len, ro_session->ro_session_id.s);
|
|
|
|
if (!ro_session) {
|
|
LM_ERR("Can't find a session. This is bad");
|
|
return;
|
|
}
|
|
|
|
LM_DBG("event-type=%d", ro_session->event_type);
|
|
|
|
// if (!ro_session->active) {
|
|
// LM_ALERT("Looks like this session was terminated while requesting more units");
|
|
// goto exit;
|
|
// return;
|
|
// }
|
|
|
|
|
|
if(ro_session->is_final_allocation) {
|
|
now = get_current_time_micro();
|
|
used_secs = now - ro_session->last_event_timestamp;
|
|
if((ro_session->reserved_secs - used_secs) > 0) {
|
|
update_ro_timer(&ro_session->ro_tl, (ro_session->reserved_secs - used_secs));
|
|
return;
|
|
}
|
|
else {
|
|
ro_session->event_type = no_more_credit;
|
|
}
|
|
}
|
|
|
|
switch (ro_session->event_type) {
|
|
case answered:
|
|
now = get_current_time_micro();
|
|
used_secs = rint((now - ro_session->last_event_timestamp) / (float) 1000000);
|
|
call_time = rint((now - ro_session->start_time) / (float) 1000000);
|
|
|
|
if ((used_secs + ro_session->billed) < (call_time)) {
|
|
adjustment = call_time - (used_secs + ro_session->billed);
|
|
LM_DBG("Making adjustment for Ro interim timer by adding %d seconds\n", adjustment);
|
|
used_secs += adjustment;
|
|
}
|
|
|
|
counter_add(ims_charging_cnts_h.billed_secs, used_secs);
|
|
|
|
if (ro_session->callid.s != NULL
|
|
&& ro_session->dlg_h_entry > 0
|
|
&& ro_session->dlg_h_id > 0
|
|
&& ro_session->ro_session_id.s != NULL) {
|
|
LM_DBG("Found a session to re-apply for timing [%.*s] and user is [%.*s]\n",
|
|
ro_session->ro_session_id.len,
|
|
ro_session->ro_session_id.s,
|
|
ro_session->asserted_identity.len,
|
|
ro_session->asserted_identity.s);
|
|
|
|
LM_DBG("Call session has been active for %i seconds. The last reserved secs was [%i] and the last event was [%i seconds] ago",
|
|
(unsigned int) call_time,
|
|
(unsigned int) ro_session->reserved_secs,
|
|
(unsigned int) used_secs);
|
|
|
|
LM_DBG("Call session [p=%p]: we will now make a request for another [%i] of credit with a usage of [%i] seconds from the last bundle.\n",
|
|
ro_session,
|
|
interim_request_credits/* new reservation request amount */,
|
|
(unsigned int) used_secs/* charged seconds from previous reservation */);
|
|
|
|
// Apply for more credit.
|
|
//
|
|
// The function call will return immediately and we will receive the reply asynchronously via a callback
|
|
ro_session->billed += used_secs;
|
|
send_ccr_interim(ro_session, (unsigned int) used_secs, interim_request_credits);
|
|
return;
|
|
} else {
|
|
LM_ERR("Hmmm, the session we have either doesn't have all the data or something else has gone wrong.\n");
|
|
/* put the timer back so the call will be killed according to previous timeout. */
|
|
ro_session->event_type = unknown_error;
|
|
int ret = insert_ro_timer(&ro_session->ro_tl,
|
|
(ro_session->reserved_secs - used_secs) / 1000000);
|
|
if (ret != 0) {
|
|
LM_CRIT("unable to insert timer for Ro Session [%.*s]\n",
|
|
ro_session->ro_session_id.len, ro_session->ro_session_id.s);
|
|
} else {
|
|
ref_ro_session_unsafe(ro_session, 1);
|
|
return;
|
|
}
|
|
LM_ERR("Immediately killing call due to unknown error\n");
|
|
}
|
|
break;
|
|
case delayed_delete:
|
|
destroy_ro_session(ro_session);
|
|
return;
|
|
default:
|
|
LM_ERR("Diameter call session - event [%d]\n", ro_session->event_type);
|
|
|
|
if (ro_session->event_type == no_more_credit)
|
|
LM_INFO("Call/session must be ended - no more funds.\n");
|
|
else if (ro_session->event_type == unknown_error)
|
|
LM_ERR("last event caused an error. We will now tear down this session.\n");
|
|
}
|
|
|
|
counter_inc(ims_charging_cnts_h.killed_calls);
|
|
|
|
dlgb.lookup_terminate_dlg(ro_session->dlg_h_entry, ro_session->dlg_h_id, &default_out_of_credit_hdrs);
|
|
return;
|
|
}
|
|
|