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/modules/ims_dialog/dlg_req_within.c

550 lines
16 KiB

/*
* $Id$
*
* 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
*
* History:
* --------
* 2007-07-10 initial version (ancuta)
* 2008-04-04 added direction reporting in dlg callbacks (bogdan)
* 2011-10 added support for early dialog termination (jason)
*/
#include <stdlib.h>
#include <string.h>
#include "../../dprint.h"
#include "../../ut.h"
#include "../../lib/srdb1/db.h"
#include "../../dprint.h"
#include "../../config.h"
#include "../../socket_info.h"
#include "../../modules/tm/dlg.h"
#include "../../modules/tm/tm_load.h"
#include "../../lib/kmi/tree.h"
#include "../../lib/kcore/kstats_wrapper.h"
#include "../../locking.h"
#include "dlg_timer.h"
#include "dlg_hash.h"
#include "dlg_req_within.h"
#include "dlg_db_handler.h"
#include "dlg_ng_stats.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 int dlg_db_mode;
extern struct dialog_ng_counters_h dialog_ng_cnts_h;
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;
str route_set;
str contact;
struct dlg_cell_out *dlg_out = 0;
struct dlg_entry_out* dlg_entry_out = 0;
/* if trying to send by to callee we need to get the corresponding dlg_out cell */
lock_get(cell->dlg_out_entries_lock);
dlg_entry_out = &cell->dlg_entry_out;
dlg_out = dlg_entry_out->first;
//must be concurrent call - lets choose - TODO - ie. check if there is more
if (!dlg_out) {
LM_ERR("Trying to send BYE for dialog with no callee leg\n");
lock_release(cell->dlg_out_entries_lock);
return NULL;
}
td = (dlg_t*) pkg_malloc(sizeof (dlg_t));
if (!td) {
LM_ERR("out of pkg memory\n");
lock_release(cell->dlg_out_entries_lock);
return NULL;
}
memset(td, 0, sizeof (dlg_t));
if (dir == DLG_CALLER_LEG) {
cseq = cell->first_req_cseq;
route_set = cell->caller_route_set;
contact = cell->caller_contact;
td->rem_uri = cell->from_uri;
td->loc_uri = dlg_out->to_uri;
td->id.rem_tag = cell->from_tag;
td->id.loc_tag = dlg_out->to_tag;
td->send_sock = cell->caller_bind_addr;
} else {
cseq = dlg_out->callee_cseq;
route_set = dlg_out->callee_route_set;
contact = dlg_out->callee_contact;
td->rem_uri = dlg_out->to_uri;
td->loc_uri = cell->from_uri;
td->id.rem_tag = dlg_out->to_tag;
td->id.loc_tag = cell->from_tag;
td->send_sock = dlg_out->callee_bind_addr;
}
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 (route_set.s && route_set.len) {
if (parse_rr_body(route_set.s, route_set.len, &td->route_set) != 0) {
LM_ERR("failed to parse route set\n");
goto error;
}
}
if (contact.s == 0 || contact.len == 0) {
LM_ERR("no contact available\n");
goto error;
}
td->id.call_id = cell->callid;
td->rem_target = contact;
td->state = DLG_CONFIRMED;
lock_release(cell->dlg_out_entries_lock);
return td;
error:
lock_release(cell->dlg_out_entries_lock);
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;
struct dlg_cell_out *dlg_out = 0;
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);
dlg = (struct dlg_cell *) (*(ps->param));
event = DLG_EVENT_REQBYE;
//get the corresponding dlg out structure for this REQ
struct dlg_entry_out *dlg_entry_out = &dlg->dlg_entry_out;
lock_get(dlg->dlg_out_entries_lock);
dlg_out = dlg_entry_out->first; //TODO check for concurrent call
if (!dlg_out)
return;
next_state_dlg(dlg, event, &old_state, &new_state, &unref, &dlg_out->to_tag);
lock_release(dlg->dlg_out_entries_lock);
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'\n",
dlg, dlg->h_entry, dlg->h_id,
dlg->callid.len, dlg->callid.s);
} else if (ret > 0) {
LM_WARN("inconsitent dlg timer data on dlg %p [%u:%u] "
"with clid '%.*s'\n",
dlg, dlg->h_entry, dlg->h_id,
dlg->callid.len, dlg->callid.s);
} else {
unref++;
}
counter_add(dialog_ng_cnts_h.active, -1);
/* dialog terminated (BYE) */
run_dlg_callbacks(DLGCB_TERMINATED, dlg, ps->req, ps->rpl, DLG_DIR_NONE, 0);
/* derefering the dialog */
unref_dlg(dlg, unref); /*removed the extra +1 on the unref that is usually added in the unref of next_state_dlg */
}
if (new_state == DLG_STATE_DELETED && old_state == DLG_STATE_DELETED) {
/* trash the dialog from DB and memory */
if (dlg_db_mode)
remove_dialog_in_from_db(dlg);
/* force delete from mem */
unref_dlg(dlg, 1);
}
}
static inline int build_extra_hdr(struct dlg_cell * cell, str *extra_hdrs,
str *str_hdr) {
char *p;
str_hdr->len = MAX_FWD_HDR_LEN + dlg_extra_hdrs.len;
if (extra_hdrs && extra_hdrs->len > 0)
str_hdr->len += extra_hdrs->len;
str_hdr->s = (char*) pkg_malloc(str_hdr->len * 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;
/* do not send BYE request for non-confirmed dialogs (not supported) */
if (cell->state != DLG_STATE_CONFIRMED) {
LM_ERR("terminating only 1 side of non-confirmed dialogs not supported by this function\n");
return -1;
}
/*verify direction*/
if ((dialog_info = build_dlg_t(cell, dir)) == 0) {
LM_ERR("failed to create dlg_t\n");
goto err;
}
LM_DBG("sending BYE to %s\n", (dir == DLG_CALLER_LEG) ? "caller" : "callee");
ref_dlg(cell, 1);
memset(&uac_r, '\0', sizeof (uac_req_t));
set_uac_req(&uac_r, &met, hdrs, NULL, dialog_info, TMCB_LOCAL_COMPLETED,
bye_reply_cb, (void*) cell);
result = d_tmb.t_request_within(&uac_r);
if (result < 0) {
LM_ERR("failed to send the BYE request\n");
goto err1;
}
free_tm_dlg(dialog_info);
LM_DBG("BYE sent to %s\n", (dir == 0) ? "caller" : "callee");
return 0;
err1:
unref_dlg(cell, 1);
err:
if (dialog_info)
free_tm_dlg(dialog_info);
return -1;
}
/*static void early_transaction_destroyed(struct cell* t, int type, struct tmcb_params *param) {
struct dlg_cell *dlg = (struct dlg_cell *) (*param->param);
if (!dlg)
return;
LM_DBG("Early transaction destroyed\n");
}*/
/* side =
* 0: caller
* 1: callee
* 2: all
*/
int dlg_terminate(struct dlg_cell *dlg, struct sip_msg *msg, str *reason, int side, str *extra_hdrs) {
struct cell* t;
str default_reason = {"call failed", 11};
int cfg_cmd = 0;
str default_extra_headers = {0,0};
if (!dlg) {
LM_ERR("calling end_dialog with NULL pointer dlg\n");
return -1;
}
if (!extra_hdrs)
extra_hdrs = &default_extra_headers;
if (msg) {
//assume called from cfg command -> dlg_terminate, as opposed to internal API or mi interface
cfg_cmd = 1;
}
if (!reason || reason->len <= 0 || !reason->s) {
reason = &default_reason;
}
if (dlg->state != DLG_STATE_CONFIRMED) {
if (side != 2) {
LM_ERR("can't terminate only 1 side of an early dialog\n");
return -1;
}
if (dlg->transaction) {
LM_DBG("terminating early dialog with %d outbound forks on transaction %p\n",
dlg->transaction->nr_of_outgoings, dlg->transaction);
t = dlg->transaction;
if (t && t!=(void*) -1 && t->uas.request) {
if (t->method.len!=6 || t->method.s[0]!='I' || t->method.s[1]!='N' || t->method.s[2]!='V')
{
//well this is the transaction of a subsequent request within the dialog
//and the dialog is not confirmed yet, so its a PRACK or an UPDATE
//could also be an options, but the important thing is how am i going to get
//the transaction of the invite, that is the one i have to cancel
LM_WARN("this is not my transaction so where am i?\n");
return 1; //TODO - need to check why we got in here once before? this crashed on t_reply as t seemed invalid
}
//TODO: here we are assuming none of the CALLEE's have sent a 200, in
//which case we would have to send an ACK, BYE
//so right now - we are sending 488 to caller and CANCEL's to all CALLEEs
LM_DBG("tearing down dialog in EARLY state - no clients responded > 199\n");
if (cfg_cmd) {
d_tmb.t_reply(msg,488,reason->s);
d_tmb.t_release(msg);
} else {
d_tmb.t_reply(t->uas.request,488,reason->s);
d_tmb.t_release(t->uas.request);
}
}
} else {
LM_WARN("can't terminate early dialog without a transaction\n");
return -1;
}
} else {
LM_DBG("terminating confirmed dialog\n");
if (side == DLG_CALLER_LEG /* 0 */ || side == DLG_CALLEE_LEG /* 1 */) {
if (dlg_bye(dlg, (extra_hdrs->len > 0) ? extra_hdrs : NULL, side) < 0)
return -1;
} else {
if (dlg_bye_all(dlg, (extra_hdrs->len > 0) ? extra_hdrs : NULL) < 0)
return -1;
}
}
return 1;
}
/*parameters from MI: callid, from tag, to tag*/
/* TODO: add reason parameter to mi interface */
struct mi_root * mi_terminate_dlg(struct mi_root *cmd_tree, void *param) {
struct mi_node* node;
struct dlg_cell * dlg = NULL;
str mi_extra_hdrs = {"Reason: mi_terminated\r\n", 23};
int status, msg_len;
char *msg;
str callid = {NULL, 0};
str ftag = {NULL, 0};
str ttag = {NULL, 0};
str reason = {"mi_terminated", 13};
if (d_table == NULL)
goto end;
node = cmd_tree->node.kids;
if (node == NULL || node->next == NULL || node->next->next == NULL)
return init_mi_tree(400, MI_MISSING_PARM_S, MI_MISSING_PARM_LEN);
if (!node->value.s || !node->value.len) {
goto error;
} else {
callid = node->value;
}
node = node->next;
if (!node->value.s || !node->value.len) {
goto error;
} else {
ftag = node->value;
}
node = node->next;
if (!node->value.s || !node->value.len) {
goto error;
} else {
ttag = node->value;
}
if (node->next) {
node = node->next;
if (node->value.len && node->value.s)
mi_extra_hdrs = node->value;
}
unsigned int dir = DLG_DIR_NONE;
LM_DBG("Looking for callid [%.*s]\n", callid.len, callid.s);
dlg = get_dlg(&callid, &ftag, &ttag, &dir); //increments ref count!
if (dlg) {
LM_DBG("Found dialog to terminate and it is in state [%i] [%.*s]\n", dlg->state, mi_extra_hdrs.len, mi_extra_hdrs.s);
if (dlg_terminate(dlg, 0, &reason/*reson*/, /* all sides of a dialog*/ 2, &mi_extra_hdrs) < 0) {
status = 500;
msg = MI_DLG_OPERATION_ERR;
msg_len = MI_DLG_OPERATION_ERR_LEN;
} else {
status = 200;
msg = MI_OK_S;
msg_len = MI_OK_LEN;
}
unref_dlg(dlg, 1);
return init_mi_tree(status, msg, msg_len);
}
end:
return init_mi_tree(404, MI_DIALOG_NOT_FOUND, MI_DIALOG_NOT_FOUND_LEN);
error:
return init_mi_tree(400, MI_BAD_PARM_S, MI_BAD_PARM_LEN);
}
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);
return ret;
}
/* Wrapper for terminating dialog from API - from other modules */
int w_api_terminate_dlg(str *callid, str *ftag, str *ttag, str *hdrs, str* reason) {
struct dlg_cell *dlg;
unsigned int dir = DLG_DIR_NONE;
dlg = get_dlg(callid, ftag, ttag, &dir); //increments ref count!
if (!dlg) {
LM_ERR("Asked to tear down non existent dialog\n");
return -1;
}
unref_dlg(dlg, 1);
return dlg_terminate(dlg, NULL, reason, 2, hdrs);
}
int dlg_bye_all(struct dlg_cell *dlg, str *hdrs) {
str all_hdrs = {0, 0};
int ret;
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);
return ret;
}
/* Wrapper for terminating dialog from API - from other modules */
int w_api_lookup_terminate_dlg(unsigned int h_entry, unsigned int h_id, str *hdrs) {
struct dlg_cell *dlg;
dlg = lookup_dlg(h_entry, h_id); //increments ref count!
if (!dlg) {
LM_ERR("Asked to tear down non existent dialog\n");
return -1;
}
unref_dlg(dlg, 1);
return dlg_terminate(dlg, NULL, NULL/*reason*/, 2, hdrs);
}