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.
kamailio/src/modules/dialog/dlg_req_within.c

556 lines
13 KiB

/*
* Copyright (C) 2007 Voice System SRL
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
/*!
* \file
* \brief Requests
* \ingroup dialog
* Module: \ref dialog
*/
#include <stdlib.h>
#include <string.h>
#include "../../core/dprint.h"
#include "../../core/ut.h"
#include "../../lib/srdb1/db.h"
#include "../../core/dprint.h"
#include "../../core/config.h"
#include "../../core/socket_info.h"
#include "../../core/dset.h"
#include "../../modules/tm/dlg.h"
#include "../../modules/tm/tm_load.h"
#include "../../core/counters.h"
#include "dlg_timer.h"
#include "dlg_hash.h"
#include "dlg_handlers.h"
#include "dlg_req_within.h"
#include "dlg_db_handler.h"
#define MAX_FWD_HDR "Max-Forwards: " MAX_FWD CRLF
#define MAX_FWD_HDR_LEN (sizeof(MAX_FWD_HDR) - 1)
extern str dlg_extra_hdrs;
extern str dlg_lreq_callee_headers;
extern int dlg_ka_failed_limit;
/**
*
*/
int free_tm_dlg(dlg_t *td)
{
if(td)
{
if(td->route_set)
free_rr(&td->route_set);
pkg_free(td);
}
return 0;
}
dlg_t * build_dlg_t(struct dlg_cell * cell, int dir){
dlg_t* td = NULL;
str cseq;
unsigned int loc_seq;
char nbuf[MAX_URI_SIZE];
char dbuf[80];
str nuri = STR_NULL;
str duri = STR_NULL;
size_t sz;
char *p;
/*remote target--- Request URI*/
if(cell->contact[dir].s==0 || cell->contact[dir].len==0){
LM_ERR("no contact available\n");
goto error;
}
if(cell->route_set[dir].s==NULL || cell->route_set[dir].len<=0){
/*try to restore alias parameter if no route set */
nuri.s = nbuf;
nuri.len = MAX_URI_SIZE;
duri.s = dbuf;
duri.len = 80;
if(uri_restore_rcv_alias(&cell->contact[dir], &nuri, &duri)<0) {
nuri.len = 0;
duri.len = 0;
}
}
if(nuri.len>0 && duri.len>0) {
sz = sizeof(dlg_t) + (nuri.len+duri.len+2)*sizeof(char);
} else {
sz = sizeof(dlg_t);
}
td = (dlg_t*)pkg_malloc(sz);
if(!td){
LM_ERR("out of pkg memory\n");
return NULL;
}
memset(td, 0, sz);
/*local sequence number*/
cseq = (dir == DLG_CALLER_LEG) ? cell->cseq[DLG_CALLEE_LEG]:
cell->cseq[DLG_CALLER_LEG];
if(str2int(&cseq, &loc_seq) != 0){
LM_ERR("invalid cseq\n");
goto error;
}
/*we don not increase here the cseq as this will be done by TM*/
td->loc_seq.value = loc_seq;
td->loc_seq.is_set = 1;
/*route set*/
if( cell->route_set[dir].s && cell->route_set[dir].len){
if( parse_rr_body(cell->route_set[dir].s, cell->route_set[dir].len,
&td->route_set) !=0){
LM_ERR("failed to parse route set\n");
goto error;
}
}
if(nuri.len>0 && duri.len>0) {
/* req uri */
p = (char*)td + sizeof(dlg_t);
strncpy(p, nuri.s, nuri.len);
p[nuri.len] = '\0';
td->rem_target.s = p;
td->rem_target.len = nuri.len;
/* dst uri */
p += nuri.len + 1;
strncpy(p, duri.s, duri.len);
p[duri.len] = '\0';
td->dst_uri.s = p;
td->dst_uri.len = duri.len;
} else {
td->rem_target = cell->contact[dir];
}
td->rem_uri = (dir == DLG_CALLER_LEG)? cell->from_uri: cell->to_uri;
td->loc_uri = (dir == DLG_CALLER_LEG)? cell->to_uri: cell->from_uri;
td->id.call_id = cell->callid;
td->id.rem_tag = cell->tag[dir];
td->id.loc_tag = (dir == DLG_CALLER_LEG) ? cell->tag[DLG_CALLEE_LEG]:
cell->tag[DLG_CALLER_LEG];
td->state= DLG_CONFIRMED;
td->send_sock = cell->bind_addr[dir];
return td;
error:
free_tm_dlg(td);
return NULL;
}
/* callback function to handle responses to the BYE request */
void bye_reply_cb(struct cell* t, int type, struct tmcb_params* ps){
struct dlg_cell* dlg;
int event, old_state, new_state, unref, ret;
dlg_iuid_t *iuid = NULL;
if(ps->param == NULL || *ps->param == NULL){
LM_ERR("invalid parameter\n");
return;
}
if(ps->code < 200){
LM_DBG("receiving a provisional reply\n");
return;
}
LM_DBG("receiving a final reply %d\n",ps->code);
iuid = (dlg_iuid_t*)(*ps->param);
dlg = dlg_get_by_iuid(iuid);
if(dlg==0)
return;
event = DLG_EVENT_REQBYE;
next_state_dlg(dlg, event, &old_state, &new_state, &unref);
if(new_state == DLG_STATE_DELETED && old_state != DLG_STATE_DELETED){
LM_DBG("removing dialog with h_entry %u and h_id %u\n",
dlg->h_entry, dlg->h_id);
/* remove from timer */
ret = remove_dialog_timer(&dlg->tl);
if (ret < 0) {
LM_CRIT("unable to unlink the timer on dlg %p [%u:%u] "
"with clid '%.*s' and tags '%.*s' '%.*s'\n",
dlg, dlg->h_entry, dlg->h_id,
dlg->callid.len, dlg->callid.s,
dlg->tag[DLG_CALLER_LEG].len, dlg->tag[DLG_CALLER_LEG].s,
dlg->tag[DLG_CALLEE_LEG].len, dlg->tag[DLG_CALLEE_LEG].s);
} else if (ret > 0) {
LM_WARN("inconsistent dlg timer data on dlg %p [%u:%u] "
"with clid '%.*s' and tags '%.*s' '%.*s'\n",
dlg, dlg->h_entry, dlg->h_id,
dlg->callid.len, dlg->callid.s,
dlg->tag[DLG_CALLER_LEG].len, dlg->tag[DLG_CALLER_LEG].s,
dlg->tag[DLG_CALLEE_LEG].len, dlg->tag[DLG_CALLEE_LEG].s);
} else {
unref++;
}
/* dialog terminated (BYE) */
run_dlg_callbacks( DLGCB_TERMINATED_CONFIRMED, dlg, ps->req, ps->rpl, DLG_DIR_NONE, 0);
LM_DBG("first final reply\n");
/* derefering the dialog */
dlg_unref(dlg, unref+1);
if_update_stat( dlg_enable_stats, active_dlgs, -1);
}
if(new_state == DLG_STATE_DELETED && old_state == DLG_STATE_DELETED ) {
/* trash the dialog from DB and memory */
LM_DBG("second final reply\n");
/* delete the dialog from DB */
if (dlg_db_mode)
remove_dialog_from_db(dlg);
/* force delete from mem */
dlg_unref(dlg, 1);
}
dlg_iuid_sfree(iuid);
}
/* callback function to handle responses to the keep-alive request */
void dlg_ka_cb_all(struct cell* t, int type, struct tmcb_params* ps, int dir)
{
int tend;
dlg_cell_t* dlg;
dlg_iuid_t *iuid = NULL;
if(ps->param == NULL || *ps->param == NULL) {
LM_ERR("invalid parameter\n");
return;
}
if(ps->code < 200) {
LM_DBG("receiving a provisional reply\n");
return;
}
LM_DBG("receiving a final reply %d\n",ps->code);
iuid = (dlg_iuid_t*)(*ps->param);
dlg = dlg_get_by_iuid(iuid);
if(dlg==0) {
dlg_iuid_sfree(iuid);
return;
}
if(ps->code==408 || ps->code==481) {
if (dlg->state != DLG_STATE_CONFIRMED) {
LM_DBG("skip updating non-confirmed dialogs\n");
goto done;
}
tend = 0;
if(dir==DLG_CALLER_LEG) {
dlg->ka_src_counter++;
if(dlg->ka_src_counter>=dlg_ka_failed_limit) {
tend = 1;
}
} else {
dlg->ka_dst_counter++;
if(dlg->ka_dst_counter>=dlg_ka_failed_limit) {
tend = 1;
}
}
if(tend) {
if(update_dlg_timer(&dlg->tl, 10)<0) {
LM_ERR("failed to update dialog lifetime\n");
goto done;
}
dlg->lifetime = 10;
dlg->dflags |= DLG_FLAG_CHANGED;
}
} else {
if (dlg->state == DLG_STATE_CONFIRMED) {
if(dir==DLG_CALLER_LEG) {
dlg->ka_src_counter = 0;
} else {
dlg->ka_dst_counter = 0;
}
}
}
done:
dlg_unref(dlg, 1);
dlg_iuid_sfree(iuid);
}
/* callback function to handle responses to the keep-alive request to src */
void dlg_ka_cb_src(struct cell* t, int type, struct tmcb_params* ps)
{
dlg_ka_cb_all(t, type, ps, DLG_CALLER_LEG);
}
/* callback function to handle responses to the keep-alive request to dst */
void dlg_ka_cb_dst(struct cell* t, int type, struct tmcb_params* ps)
{
dlg_ka_cb_all(t, type, ps, DLG_CALLEE_LEG);
}
static inline int build_extra_hdr(struct dlg_cell * cell, str *extra_hdrs,
str *str_hdr)
{
char *p;
int blen;
str_hdr->len = MAX_FWD_HDR_LEN + dlg_extra_hdrs.len;
if(extra_hdrs && extra_hdrs->len>0)
str_hdr->len += extra_hdrs->len;
blen = str_hdr->len + 3 /* '\r\n\0' */;
/* reserve space for callee headers in local requests */
if(dlg_lreq_callee_headers.len>0)
blen += dlg_lreq_callee_headers.len + 2 /* '\r\n' */;
str_hdr->s = (char*)pkg_malloc( blen * sizeof(char) );
if(!str_hdr->s){
LM_ERR("out of pkg memory\n");
goto error;
}
memcpy(str_hdr->s , MAX_FWD_HDR, MAX_FWD_HDR_LEN );
p = str_hdr->s + MAX_FWD_HDR_LEN;
if (dlg_extra_hdrs.len) {
memcpy( p, dlg_extra_hdrs.s, dlg_extra_hdrs.len);
p += dlg_extra_hdrs.len;
}
if (extra_hdrs && extra_hdrs->len>0)
memcpy( p, extra_hdrs->s, extra_hdrs->len);
return 0;
error:
return -1;
}
/* cell- pointer to a struct dlg_cell
* dir- direction: the request will be sent to:
* DLG_CALLER_LEG (0): caller
* DLG_CALLEE_LEG (1): callee
*/
static inline int send_bye(struct dlg_cell * cell, int dir, str *hdrs)
{
uac_req_t uac_r;
dlg_t* dialog_info;
str met = {"BYE", 3};
int result;
dlg_iuid_t *iuid = NULL;
str lhdrs;
/* do not send BYE request for non-confirmed dialogs (not supported) */
if (cell->state != DLG_STATE_CONFIRMED_NA && cell->state != DLG_STATE_CONFIRMED) {
LM_ERR("terminating non-confirmed dialogs not supported\n");
return -1;
}
/*verify direction*/
if ((dialog_info = build_dlg_t(cell, dir)) == 0){
LM_ERR("failed to create dlg_t\n");
goto err;
}
/* safety bump of cseq if prack was involved in call setup */
if(cell->iflags & DLG_IFLAG_PRACK) {
dialog_info->loc_seq.value += 80;
}
LM_DBG("sending BYE to %s\n", (dir==DLG_CALLER_LEG)?"caller":"callee");
iuid = dlg_get_iuid_shm_clone(cell);
if(iuid==NULL)
{
LM_ERR("failed to create dialog unique id clone\n");
goto err;
}
lhdrs = *hdrs;
if(dir==DLG_CALLEE_LEG && dlg_lreq_callee_headers.len>0) {
/* space allocated in hdrs->s by build_extra_hdrs() */
memcpy(lhdrs.s+lhdrs.len, dlg_lreq_callee_headers.s,
dlg_lreq_callee_headers.len);
lhdrs.len += dlg_lreq_callee_headers.len;
if(dlg_lreq_callee_headers.s[dlg_lreq_callee_headers.len-1]!='\n') {
memcpy(lhdrs.s+lhdrs.len, CRLF, CRLF_LEN);
lhdrs.len += CRLF_LEN;
}
}
set_uac_req(&uac_r, &met, &lhdrs, NULL, dialog_info, TMCB_LOCAL_COMPLETED,
bye_reply_cb, (void*)iuid);
result = d_tmb.t_request_within(&uac_r);
if(result < 0){
LM_ERR("failed to send the BYE request\n");
goto err;
}
free_tm_dlg(dialog_info);
LM_DBG("BYE sent to %s\n", (dir==0)?"caller":"callee");
return 0;
err:
if(dialog_info)
free_tm_dlg(dialog_info);
return -1;
}
/* send keep-alive
* dlg - pointer to a struct dlg_cell
* dir - direction: the request will be sent to:
* DLG_CALLER_LEG (0): caller
* DLG_CALLEE_LEG (1): callee
*/
int dlg_send_ka(dlg_cell_t *dlg, int dir)
{
uac_req_t uac_r;
dlg_t* di;
str met = {"OPTIONS", 7};
int result;
dlg_iuid_t *iuid = NULL;
/* do not send KA request for non-confirmed dialogs (not supported) */
if (dlg->state != DLG_STATE_CONFIRMED) {
LM_DBG("skipping non-confirmed dialogs\n");
return 0;
}
/* build tm dlg by direction */
if ((di = build_dlg_t(dlg, dir)) == 0){
LM_ERR("failed to create dlg_t\n");
goto err;
}
/* tm increases cseq value, decrease it no to make it invalid
* - dialog is ended on timeout (408) or C/L does not exist (481) */
if(di->loc_seq.value>1)
di->loc_seq.value -= 2;
else
di->loc_seq.value -= 1;
LM_DBG("sending OPTIONS to %s\n", (dir==DLG_CALLER_LEG)?"caller":"callee");
iuid = dlg_get_iuid_shm_clone(dlg);
if(iuid==NULL)
{
LM_ERR("failed to create dialog unique id clone\n");
goto err;
}
if(dir==DLG_CALLEE_LEG && dlg_lreq_callee_headers.len>0) {
set_uac_req(&uac_r, &met, &dlg_lreq_callee_headers, NULL, di,
TMCB_LOCAL_COMPLETED, dlg_ka_cb_dst, (void*)iuid);
} else {
set_uac_req(&uac_r, &met, NULL, NULL, di, TMCB_LOCAL_COMPLETED,
(dir==DLG_CALLEE_LEG)?dlg_ka_cb_dst:dlg_ka_cb_src, (void*)iuid);
}
result = d_tmb.t_request_within(&uac_r);
if(result < 0){
LM_ERR("failed to send the OPTIONS request\n");
goto err;
}
free_tm_dlg(di);
LM_DBG("keep-alive sent to %s\n", (dir==0)?"caller":"callee");
return 0;
err:
if(di)
free_tm_dlg(di);
return -1;
}
int dlg_bye(struct dlg_cell *dlg, str *hdrs, int side)
{
str all_hdrs = { 0, 0 };
int ret;
if(side==DLG_CALLER_LEG)
{
if(dlg->dflags&DLG_FLAG_CALLERBYE)
return -1;
dlg->dflags |= DLG_FLAG_CALLERBYE;
} else {
if(dlg->dflags&DLG_FLAG_CALLEEBYE)
return -1;
dlg->dflags |= DLG_FLAG_CALLEEBYE;
}
if ((build_extra_hdr(dlg, hdrs, &all_hdrs)) != 0)
{
LM_ERR("failed to build dlg headers\n");
return -1;
}
ret = send_bye(dlg, side, &all_hdrs);
pkg_free(all_hdrs.s);
dlg_run_event_route(dlg, NULL, dlg->state, DLG_STATE_DELETED);
return ret;
}
int dlg_bye_all(struct dlg_cell *dlg, str *hdrs)
{
str all_hdrs = { 0, 0 };
int ret;
/* run dialog terminated callbacks */
run_dlg_callbacks( DLGCB_TERMINATED, dlg, NULL, NULL, DLG_DIR_NONE, 0);
if ((build_extra_hdr(dlg, hdrs, &all_hdrs)) != 0)
{
LM_ERR("failed to build dlg headers\n");
return -1;
}
ret = send_bye(dlg, DLG_CALLER_LEG, &all_hdrs);
ret |= send_bye(dlg, DLG_CALLEE_LEG, &all_hdrs);
pkg_free(all_hdrs.s);
dlg_run_event_route(dlg, NULL, dlg->state, DLG_STATE_DELETED);
return ret;
}