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.
1966 lines
61 KiB
1966 lines
61 KiB
/*
|
|
* $Id$
|
|
*
|
|
* This C-file takes care of matching requests and replies with
|
|
* existing transactions. Note that we do not do SIP-compliant
|
|
* request matching as asked by SIP spec. We do bitwise matching of
|
|
* all header fields in requests which form a transaction key.
|
|
* It is much faster and it works pretty well -- we haven't
|
|
* had any interop issue neither in lab nor in bake-offs. The reason
|
|
* is that retransmissions do look same as original requests
|
|
* (it would be really silly if they would be mangled). The only
|
|
* exception is we parse To as To in ACK is compared to To in
|
|
* reply and both of them are constructed by different software.
|
|
*
|
|
* As for reply matching, we match based on branch value -- that is
|
|
* faster too. There are two versions .. with SYNONYMs #define
|
|
* enabled, the branch includes ordinal number of a transaction
|
|
* in a synonym list in hash table and is somewhat faster but
|
|
* not reboot-resilient. SYNONYMs turned off are little slower
|
|
* but work across reboots as well.
|
|
*
|
|
* The branch parameter is formed as follows:
|
|
* SYNONYMS on: hash.synonym.branch
|
|
* SYNONYMS off: hash.md5.branch
|
|
*
|
|
* -jiri
|
|
*
|
|
*
|
|
* Copyright (C) 2001-2003 FhG Fokus
|
|
*
|
|
* This file is part of ser, a free SIP server.
|
|
*
|
|
* ser 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
|
|
*
|
|
* For a license to use the ser software under conditions
|
|
* other than those described here, or to purchase support for this
|
|
* software, please contact iptel.org by e-mail at the following addresses:
|
|
* info@iptel.org
|
|
*
|
|
* ser 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* History:
|
|
* ----------
|
|
* 2003-01-23 options for disabling r-uri matching introduced (jiri)
|
|
* nameser_compat.h (andrei)
|
|
* 2003-01-27 next baby-step to removing ZT - PRESERVE_ZT (jiri)
|
|
* 2003-01-28 scratchpad removed (jiri)
|
|
* 2003-02-13 init_rb() is proto indep. & it uses struct dest_info (andrei)
|
|
* 2003-02-24 s/T_NULL/T_NULL_CELL/ to avoid redefinition conflict w/
|
|
* 2003-02-27 3261 ACK/200 consumption bug removed (jiri)
|
|
* 2003-02-28 scratchpad compatibility abandoned (jiri)
|
|
* 2003-03-01 kr set through a function now (jiri)
|
|
* 2003-03-06 dialog matching introduced for ACKs -- that's important for
|
|
* INVITE UAS (like INVITE) and 200/ACK proxy matching (jiri)
|
|
* 2003-03-29 optimization: e2e ACK matching only if callback installed
|
|
* (jiri)
|
|
* 2003-03-30 set_kr for requests only (jiri)
|
|
* 2003-04-04 bug_fix: RESPONSE_IN callback not called for local
|
|
* UAC transactions (jiri)
|
|
* 2003-04-07 new transactions inherit on_negative and on_relpy from script
|
|
* variables on instantiation (jiri)
|
|
* 2003-04-30 t_newtran clean up (jiri)
|
|
* 2003-08-21 request lookups fixed to skip UAC transactions,
|
|
* thanks Ed (jiri)
|
|
* 2003-12-04 global TM callbacks switched to per transaction callbacks
|
|
* (bogdan)
|
|
* 2004-02-11 FIFO/CANCEL + alignments (hash=f(callid,cseq)) (uli+jiri)
|
|
* 2004-02-13: t->is_invite and t->local replaced with flags (bogdan)
|
|
* 2004-10-10: use of mhomed disabled for replies (jiri)
|
|
* 2005-02-01: use the incoming request interface for sending the replies
|
|
* - changes in init_rb() (bogdan)
|
|
* 2005-12-09 added t_set_fr() (andrei)
|
|
* 2006-01-27 transaction lookup function will set up a cancel flag
|
|
* if the searched transaction was pre-canceled (andrei)
|
|
* 2006-10-16 401 & 407 replies are completely parsed if tm_aggregate_auth is
|
|
* set (andrei)
|
|
* 2006-11-10 a valid msg->hash_index is now marked by FL_HASH_INDEX in
|
|
* msg_flags
|
|
* t_lookupOriginalT computes the hash_index by itself if
|
|
* needed (andrei)
|
|
* 2007-03-17 added callbacks for retransmitted request, ack to negative
|
|
* replies and replies to local transactions (andrei)
|
|
* 2007-06-01 support for different retransmissions intervals per transaction;
|
|
* added maximum inv. and non-inv. transaction life time (andrei)
|
|
* 2007-06-05 added delayed error reply support in t_unref;
|
|
* added support for turning off 100 repl. sending on inv. (andrei)
|
|
* 2007-06-01 support for different retransmissions intervals per transaction;
|
|
* added maximum inv. and non-inv. transaction life time (andrei)
|
|
* 2007-06-06 switched tm bucket list to a simpler and faster clist;
|
|
* 2008-02-28 try matching e2e acks in t_lookup() only for transactions
|
|
* which have E2EACK callbacks registered (andrei)
|
|
* 2008-03-31 message flags are updated in shared memory even if they are set
|
|
* after t_newtran() (Miklos)
|
|
* 2009-06-24 added T_branch and changed set_t() to take also a branch
|
|
* parameter (andrei)
|
|
*/
|
|
|
|
#include "defs.h"
|
|
|
|
|
|
#include "../../comp_defs.h"
|
|
#include "../../compiler_opt.h"
|
|
#include "../../dprint.h"
|
|
#include "../../config.h"
|
|
#include "../../parser/parser_f.h"
|
|
#include "../../parser/parse_from.h"
|
|
#include "../../ut.h"
|
|
#include "../../timer.h"
|
|
#include "../../timer_ticks.h"
|
|
#include "../../hash_func.h"
|
|
#include "../../globals.h"
|
|
#include "../../forward.h"
|
|
#include "t_funcs.h"
|
|
#include "config.h"
|
|
#include "sip_msg.h"
|
|
#include "t_hooks.h"
|
|
#include "t_fwd.h"
|
|
#include "t_lookup.h"
|
|
#include "dlg.h" /* for t_lookup_callid */
|
|
#include "t_msgbuilder.h" /* for t_lookup_callid */
|
|
|
|
#define EQ_VIA_LEN(_via)\
|
|
( (p_msg->via1->bsize-(p_msg->_via->name.s-(p_msg->_via->hdr.s+p_msg->_via->hdr.len)))==\
|
|
(t_msg->via1->bsize-(t_msg->_via->name.s-(t_msg->_via->hdr.s+t_msg->_via->hdr.len))) )
|
|
|
|
|
|
|
|
#define EQ_LEN(_hf) (t_msg->_hf->body.len==p_msg->_hf->body.len)
|
|
#define EQ_REQ_URI_LEN\
|
|
(p_msg->first_line.u.request.uri.len==t_msg->first_line.u.request.uri.len)
|
|
|
|
#define EQ_STR(_hf) (memcmp(t_msg->_hf->body.s,\
|
|
p_msg->_hf->body.s, \
|
|
p_msg->_hf->body.len)==0)
|
|
#define EQ_REQ_URI_STR\
|
|
( memcmp( t_msg->first_line.u.request.uri.s,\
|
|
p_msg->first_line.u.request.uri.s,\
|
|
p_msg->first_line.u.request.uri.len)==0)
|
|
#define EQ_VIA_STR(_via)\
|
|
( memcmp( t_msg->_via->name.s,\
|
|
p_msg->_via->name.s,\
|
|
(t_msg->via1->bsize-(t_msg->_via->name.s-(t_msg->_via->hdr.s+t_msg->_via->hdr.len)))\
|
|
)==0 )
|
|
|
|
|
|
|
|
#define HF_LEN(_hf) ((_hf)->len)
|
|
|
|
/* presumably matching transaction for an e2e ACK */
|
|
static struct cell *t_ack;
|
|
|
|
/* this is a global variable which keeps pointer to
|
|
transaction currently processed by a process; it it
|
|
set by t_lookup_request or t_reply_matching; don't
|
|
dare to change it anywhere else as it would
|
|
break ref_counting.
|
|
It has a valid value only if:
|
|
- it's checked inside a failure or tm on_reply route (not core
|
|
on_reply[0]!)
|
|
- global_msg_id == msg->id in all the other kinds of routes
|
|
Note that this is the safest check and is valid also for
|
|
failure routes (because fake_env() sets global_msg_id) or tm
|
|
tm onreply routes (the internal reply_received() t_check() will set
|
|
T and global_msg_id).
|
|
*/
|
|
static struct cell *T;
|
|
|
|
/* this is a global variable which keeps the current branch
|
|
for the transaction currently processed.
|
|
It has a valid value only if T is valid (global_msg_id==msg->id -- see
|
|
above, and T!=0 and T!=T_UNDEFINED).
|
|
For a request it's value is T_BR_UNDEFINED (it can have valid values only
|
|
for replies).
|
|
*/
|
|
static int T_branch;
|
|
|
|
/* number of currently processed message; good to know
|
|
to be able to doublecheck whether we are still working
|
|
on a current transaction or a new message arrived;
|
|
don't even think of changing it.
|
|
*/
|
|
unsigned int global_msg_id;
|
|
|
|
|
|
|
|
struct cell *get_t() { return T; }
|
|
void set_t(struct cell *t, int branch) { T=t; T_branch=branch; }
|
|
void init_t() {global_msg_id=0; set_t(T_UNDEFINED, T_BR_UNDEFINED);}
|
|
int get_t_branch() { return T_branch; }
|
|
|
|
static inline int parse_dlg( struct sip_msg *msg )
|
|
{
|
|
if (parse_headers(msg, HDR_FROM_F | HDR_CSEQ_F | HDR_TO_F, 0)==-1) {
|
|
LOG(L_ERR, "ERROR: parse_dlg: From or Cseq or To invalid\n");
|
|
return 0;
|
|
}
|
|
if ((msg->from==0)||(msg->cseq==0)||(msg->to==0)) {
|
|
LOG(L_ERR, "ERROR: parse_dlg: missing From or Cseq or To\n");
|
|
return 0;
|
|
}
|
|
|
|
if (parse_from_header(msg)==-1) {
|
|
LOG(L_ERR, "ERROR: parse_dlg: From broken\n");
|
|
return 0;
|
|
}
|
|
/* To is automatically parsed through HDR_TO in parse bitmap,
|
|
* we don't need to worry about it now
|
|
if (parse_to_header(msg)==-1) {
|
|
LOG(L_ERR, "ERROR: tid_matching: To broken\n");
|
|
return 0;
|
|
}
|
|
*/
|
|
return 1;
|
|
}
|
|
|
|
/* is the ACK (p_msg) in p_msg dialog-wise equal to the INVITE (t_msg)
|
|
* except to-tags? */
|
|
static inline int partial_dlg_matching(struct sip_msg *t_msg, struct sip_msg *p_msg)
|
|
{
|
|
struct to_body *inv_from;
|
|
|
|
if (!EQ_LEN(callid)) return 0;
|
|
if (get_cseq(t_msg)->number.len!=get_cseq(p_msg)->number.len)
|
|
return 0;
|
|
inv_from=get_from(t_msg);
|
|
if (!inv_from) {
|
|
LOG(L_ERR, "ERROR: partial_dlg_matching: INV/From not parsed\n");
|
|
return 0;
|
|
}
|
|
if (inv_from->tag_value.len!=get_from(p_msg)->tag_value.len)
|
|
return 0;
|
|
if (!EQ_STR(callid))
|
|
return 0;
|
|
if (memcmp(get_cseq(t_msg)->number.s, get_cseq(p_msg)->number.s,
|
|
get_cseq(p_msg)->number.len)!=0)
|
|
return 0;
|
|
if (memcmp(inv_from->tag_value.s, get_from(p_msg)->tag_value.s,
|
|
get_from(p_msg)->tag_value.len)!=0)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/* are to-tags in ACK/200 same as those we sent out? */
|
|
static inline int dlg_matching(struct cell *p_cell, struct sip_msg *ack )
|
|
{
|
|
if (get_to(ack)->tag_value.len!=p_cell->uas.local_totag.len)
|
|
return 0;
|
|
if (memcmp(get_to(ack)->tag_value.s,p_cell->uas.local_totag.s,
|
|
p_cell->uas.local_totag.len)!=0)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
/* returns 2 if one of the save totags matches the totag in the current
|
|
* message (which should be an ACK) and 0 if not */
|
|
static inline int totag_e2e_ack_matching(struct cell* p_cell,
|
|
struct sip_msg *ack)
|
|
{
|
|
struct totag_elem *i;
|
|
str *tag;
|
|
|
|
tag=&get_to(ack)->tag_value;
|
|
/* no locking needed for reading/searching, see update_totag_set() */
|
|
for (i=p_cell->fwded_totags; i; i=i->next){
|
|
membar_depends(); /* make sure we don't see some old i content
|
|
(needed on CPUs like Alpha) */
|
|
if (i->tag.len==tag->len && memcmp(i->tag.s, tag->s, tag->len)==0) {
|
|
return 2;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/* returns: 0 - no match
|
|
* 1 - full match to a local transaction
|
|
* 2 - full match to a proxied transaction
|
|
* 3 - partial match to a proxied transaction (totag not checked) =>
|
|
* care must be taken not to falsely match an ACK for a negative
|
|
* reply to a "fork" of the transaction
|
|
*/
|
|
static inline int ack_matching(struct cell *p_cell, struct sip_msg *p_msg)
|
|
{
|
|
/* partial dialog matching -- no to-tag, only from-tag,
|
|
* callid, cseq number ; */
|
|
if (!partial_dlg_matching(p_cell->uas.request, p_msg))
|
|
return 0;
|
|
|
|
/* if this transaction is proxied (as opposed to UAS) we're
|
|
* done now -- we ignore to-tags; the ACK simply belongs to
|
|
* this UAS part of dialog, whatever to-tag it gained
|
|
*/
|
|
if (likely(p_cell->relayed_reply_branch!=-2)) {
|
|
if (likely(has_tran_tmcbs(p_cell,
|
|
TMCB_E2EACK_IN|TMCB_E2EACK_RETR_IN)))
|
|
return totag_e2e_ack_matching(p_cell, p_msg); /* 2 or 0 */
|
|
else
|
|
LOG(L_WARN, "WARNING: ack_matching() attempted on"
|
|
" a transaction with no E2EACK callbacks => the results"
|
|
" are not completely reliable when forking is involved\n");
|
|
return 3; /* e2e proxied ACK partial match */
|
|
}
|
|
/* it's a local dialog -- we wish to verify to-tags too */
|
|
if (dlg_matching(p_cell, p_msg)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* branch-based transaction matching */
|
|
static inline int via_matching( struct via_body *inv_via,
|
|
struct via_body *ack_via )
|
|
{
|
|
if (inv_via->tid.len!=ack_via->tid.len)
|
|
return 0;
|
|
if (memcmp(inv_via->tid.s, ack_via->tid.s,
|
|
ack_via->tid.len)!=0)
|
|
return 0;
|
|
/* ok, tid matches -- now make sure that the
|
|
* originator matches too to avoid confusion with
|
|
* different senders generating the same tid
|
|
*/
|
|
if (inv_via->host.len!=ack_via->host.len)
|
|
return 0;;
|
|
if (memcmp(inv_via->host.s, ack_via->host.s,
|
|
ack_via->host.len)!=0)
|
|
return 0;
|
|
if (inv_via->port!=ack_via->port)
|
|
return 0;
|
|
if (inv_via->transport.len!=ack_via->transport.len)
|
|
return 0;
|
|
if (memcmp(inv_via->transport.s, ack_via->transport.s,
|
|
ack_via->transport.len)!=0)
|
|
return 0;
|
|
/* everything matched -- we found it */
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* transaction matching a-la RFC-3261 using transaction ID in branch
|
|
(the function assumes there is magic cookie in branch)
|
|
It returns:
|
|
2 if e2e ACK for a proxied transaction found
|
|
1 if found (covers ACK for local UAS)
|
|
0 if not found (trans undefined)
|
|
It also sets *cancel if a cancel was found for the searched transaction
|
|
*/
|
|
|
|
static int matching_3261( struct sip_msg *p_msg, struct cell **trans,
|
|
enum request_method skip_method, int* cancel)
|
|
{
|
|
struct cell *p_cell;
|
|
struct cell *e2e_ack_trans;
|
|
struct sip_msg *t_msg;
|
|
struct via_body *via1;
|
|
int is_ack;
|
|
int dlg_parsed;
|
|
int ret = 0;
|
|
struct entry* hash_bucket;
|
|
|
|
*cancel=0;
|
|
e2e_ack_trans=0;
|
|
via1=p_msg->via1;
|
|
is_ack=p_msg->REQ_METHOD==METHOD_ACK;
|
|
dlg_parsed=0;
|
|
/* update parsed tid */
|
|
via1->tid.s=via1->branch->value.s+MCOOKIE_LEN;
|
|
via1->tid.len=via1->branch->value.len-MCOOKIE_LEN;
|
|
|
|
hash_bucket=&(get_tm_table()->entries[p_msg->hash_index]);
|
|
clist_foreach(hash_bucket, p_cell, next_c){
|
|
prefetch_loc_r(p_cell->next_c, 1);
|
|
t_msg=p_cell->uas.request;
|
|
if (unlikely(!t_msg)) continue;/*don't try matching UAC transactions */
|
|
/* we want to set *cancel for transaction for which there is
|
|
* already a canceled transaction (e.g. re-ordered INV-CANCEL, or
|
|
* INV blocked in dns lookup); we don't care about ACKs */
|
|
if ((is_ack || (t_msg->REQ_METHOD!=METHOD_CANCEL)) &&
|
|
(skip_method & t_msg->REQ_METHOD))
|
|
continue;
|
|
|
|
/* here we do an exercise which will be removed from future code
|
|
versions: we try to match end-2-end ACKs if they appear at our
|
|
server. This allows some applications bound to TM via callbacks
|
|
to correlate the e2e ACKs with transaction context, e.g., for
|
|
purpose of accounting. We think it is a bad place here, among
|
|
other things because it is not reliable. If a transaction loops
|
|
via SER the ACK can't be matched to proper INVITE transaction
|
|
(it is a separate transactino with its own branch ID) and it
|
|
matches all transaction instances in the loop dialog-wise.
|
|
Eventually, regardless to which transaction in the loop the
|
|
ACK belongs, only the first one will match.
|
|
*/
|
|
|
|
/* dialog matching needs to be applied for ACK/200s but only if
|
|
* this is a local transaction or its a proxied transaction interested
|
|
* in e2e ACKs (has E2EACK* callbacks installed) */
|
|
if (unlikely(is_ack && p_cell->uas.status<300)) {
|
|
if (unlikely(has_tran_tmcbs(p_cell,
|
|
TMCB_E2EACK_IN|TMCB_E2EACK_RETR_IN) ||
|
|
(p_cell->relayed_reply_branch==-2) )) {
|
|
/* make sure we have parsed all things we need for dialog
|
|
* matching */
|
|
if (!dlg_parsed) {
|
|
dlg_parsed=1;
|
|
if (unlikely(!parse_dlg(p_msg))) {
|
|
LOG(L_INFO, "ERROR: matching_3261: dlg parsing "
|
|
"failed\n");
|
|
return 0;
|
|
}
|
|
}
|
|
ret=ack_matching(p_cell /* t w/invite */, p_msg /* ack */);
|
|
if (unlikely(ret>0)) {
|
|
/* if ret==1 => fully matching e2e ack for local trans
|
|
* if ret==2 => matching e2e ack for proxied transaction.
|
|
* which is interested in it (E2EACK* callbacks)
|
|
* if ret==3 => partial match => we should at least
|
|
* make sure the ACK is not for a negative reply
|
|
* (FIXME: ret==3 should never happen, it's a bug catch
|
|
* case)*/
|
|
if (unlikely(ret==1)) goto found;
|
|
if (unlikely(ret==3)){
|
|
if (e2e_ack_trans==0)
|
|
e2e_ack_trans=p_cell;
|
|
continue; /* maybe we get a better
|
|
match for a neg.
|
|
replied trans. */
|
|
}
|
|
e2e_ack_trans=p_cell;
|
|
goto e2eack_found;
|
|
}
|
|
/* this ACK is neither local "negative" one, nor a proxied
|
|
* end-2-end one, nor an end-2-end one for a UAS transaction
|
|
* -- we failed to match */
|
|
continue;
|
|
}
|
|
/* not interested, it's an ack and a 2xx replied
|
|
transaction but the transaction is not local and
|
|
the transaction is not interested in e2eacks (no e2e callbacks)*/
|
|
continue;
|
|
}
|
|
/* now real tid matching occurs for negative ACKs and any
|
|
* other requests */
|
|
if (!via_matching(t_msg->via1 /* inv via */, via1 /* ack */ ))
|
|
continue;
|
|
if (t_msg->REQ_METHOD==METHOD_CANCEL){
|
|
if ((p_msg->REQ_METHOD!=METHOD_CANCEL) && !is_ack){
|
|
/* found an existing cancel for the searched transaction */
|
|
*cancel=1;
|
|
}
|
|
if (skip_method & t_msg->REQ_METHOD) continue;
|
|
}
|
|
found:
|
|
prefetch_w(p_cell); /* great chance of modifiying it */
|
|
/* all matched -- we found the transaction ! */
|
|
DBG("DEBUG: RFC3261 transaction matched, tid=%.*s\n",
|
|
via1->tid.len, via1->tid.s);
|
|
*trans=p_cell;
|
|
return 1;
|
|
}
|
|
/* :-( ... we didn't find any */
|
|
|
|
/* just check if it we found an e2e ACK previously
|
|
* (Note: this is not very reliable, since we match e2e proxy ACKs
|
|
* w/o totag => for a pre-forked invite it might match the wrong
|
|
* transaction) */
|
|
if (e2e_ack_trans) {
|
|
e2eack_found:
|
|
*trans=e2e_ack_trans;
|
|
return 2;
|
|
}
|
|
DBG("DEBUG: RFC3261 transaction matching failed\n");
|
|
return 0;
|
|
}
|
|
|
|
|
|
/** find the transaction corresponding to a request.
|
|
* @return - negative - transaction wasn't found (-1) or
|
|
* possible e2eACK match (-2).
|
|
* 1 - transaction found
|
|
* 0 - parse error
|
|
* It also sets *cancel if there is already a cancel transaction.
|
|
* Side-effects: sets T and T_branch
|
|
* (T_branch is always set to T_BR_UNDEFINED).
|
|
*/
|
|
|
|
int t_lookup_request( struct sip_msg* p_msg , int leave_new_locked,
|
|
int* cancel)
|
|
{
|
|
struct cell *p_cell;
|
|
unsigned int isACK;
|
|
struct sip_msg *t_msg;
|
|
struct via_param *branch;
|
|
int match_status;
|
|
struct cell *e2e_ack_trans;
|
|
struct entry* hash_bucket;
|
|
|
|
/* parse all*/
|
|
if (unlikely(check_transaction_quadruple(p_msg)==0))
|
|
{
|
|
LOG(L_ERR, "ERROR: TM module: t_lookup_request: too few headers\n");
|
|
set_t(0, T_BR_UNDEFINED);
|
|
/* stop processing */
|
|
return 0;
|
|
}
|
|
|
|
/* start searching into the table */
|
|
if (!(p_msg->msg_flags & FL_HASH_INDEX)){
|
|
p_msg->hash_index=hash( p_msg->callid->body , get_cseq(p_msg)->number);
|
|
p_msg->msg_flags|=FL_HASH_INDEX;
|
|
}
|
|
isACK = p_msg->REQ_METHOD==METHOD_ACK;
|
|
DBG("t_lookup_request: start searching: hash=%d, isACK=%d\n",
|
|
p_msg->hash_index,isACK);
|
|
|
|
|
|
/* assume not found */
|
|
e2e_ack_trans = 0;
|
|
|
|
/* first of all, look if there is RFC3261 magic cookie in branch; if
|
|
* so, we can do very quick matching and skip the old-RFC bizzar
|
|
* comparison of many header fields
|
|
*/
|
|
if (!p_msg->via1) {
|
|
LOG(L_ERR, "ERROR: t_lookup_request: no via\n");
|
|
set_t(0, T_BR_UNDEFINED);
|
|
return 0;
|
|
}
|
|
branch=p_msg->via1->branch;
|
|
if (branch && branch->value.s && branch->value.len>MCOOKIE_LEN
|
|
&& memcmp(branch->value.s,MCOOKIE,MCOOKIE_LEN)==0) {
|
|
/* huhuhu! the cookie is there -- let's proceed fast */
|
|
LOCK_HASH(p_msg->hash_index);
|
|
match_status=matching_3261(p_msg,&p_cell,
|
|
/* skip transactions with different method; otherwise CANCEL
|
|
* would match the previous INVITE trans. */
|
|
isACK ? ~METHOD_INVITE: ~p_msg->REQ_METHOD,
|
|
cancel);
|
|
switch(match_status) {
|
|
case 0: goto notfound; /* no match */
|
|
case 1: goto found; /* match */
|
|
case 2: goto e2e_ack; /* e2e proxy ACK */
|
|
}
|
|
}
|
|
|
|
/* ok -- it's ugly old-fashioned transaction matching -- it is
|
|
* a bit simplified to be fast -- we don't do all the comparisons
|
|
* of parsed uri, which was simply too bloated */
|
|
DBG("DEBUG: proceeding to pre-RFC3261 transaction matching\n");
|
|
*cancel=0;
|
|
/* lock the whole entry*/
|
|
LOCK_HASH(p_msg->hash_index);
|
|
|
|
hash_bucket=&(get_tm_table()->entries[p_msg->hash_index]);
|
|
|
|
if (likely(!isACK)) {
|
|
/* all the transactions from the entry are compared */
|
|
clist_foreach(hash_bucket, p_cell, next_c){
|
|
prefetch_loc_r(p_cell->next_c, 1);
|
|
t_msg = p_cell->uas.request;
|
|
if (!t_msg) continue; /* skip UAC transactions */
|
|
/* for non-ACKs we want same method matching, we
|
|
* make an exception for pre-exisiting CANCELs because we
|
|
* want to set *cancel */
|
|
if ((t_msg->REQ_METHOD!=p_msg->REQ_METHOD) &&
|
|
(t_msg->REQ_METHOD!=METHOD_CANCEL))
|
|
continue;
|
|
/* compare lengths first */
|
|
if (!EQ_LEN(callid)) continue;
|
|
/* CSeq only the number without method ! */
|
|
if (get_cseq(t_msg)->number.len!=get_cseq(p_msg)->number.len)
|
|
continue;
|
|
if (!EQ_LEN(from)) continue;
|
|
if (!EQ_LEN(to)) continue;
|
|
if (cfg_get(tm, tm_cfg, ruri_matching) && !EQ_REQ_URI_LEN)
|
|
continue;
|
|
if (cfg_get(tm, tm_cfg, via1_matching) && !EQ_VIA_LEN(via1))
|
|
continue;
|
|
/* length ok -- move on */
|
|
if (!EQ_STR(callid)) continue;
|
|
if (memcmp(get_cseq(t_msg)->number.s, get_cseq(p_msg)->number.s,
|
|
get_cseq(p_msg)->number.len)!=0) continue;
|
|
if (!EQ_STR(from)) continue;
|
|
if (!EQ_STR(to)) continue;
|
|
if (cfg_get(tm, tm_cfg, ruri_matching) && !EQ_REQ_URI_STR)
|
|
continue;
|
|
if (cfg_get(tm, tm_cfg, via1_matching) && !EQ_VIA_STR(via1))
|
|
continue;
|
|
|
|
if ((t_msg->REQ_METHOD==METHOD_CANCEL) &&
|
|
(p_msg->REQ_METHOD!=METHOD_CANCEL)){
|
|
/* we've matched an existing CANCEL */
|
|
*cancel=1;
|
|
continue;
|
|
}
|
|
|
|
/* request matched ! */
|
|
DBG("DEBUG: non-ACK matched\n");
|
|
goto found;
|
|
} /* synonym loop */
|
|
} else { /* it's an ACK request*/
|
|
/* all the transactions from the entry are compared */
|
|
clist_foreach(hash_bucket, p_cell, next_c){
|
|
prefetch_loc_r(p_cell->next_c, 1);
|
|
t_msg = p_cell->uas.request;
|
|
if (!t_msg) continue; /* skip UAC transactions */
|
|
/* ACK's relate only to INVITEs */
|
|
if (t_msg->REQ_METHOD!=METHOD_INVITE) continue;
|
|
/* From|To URI , CallID, CSeq # must be always there */
|
|
/* compare lengths now */
|
|
if (!EQ_LEN(callid)) continue;
|
|
/* CSeq only the number without method ! */
|
|
if (get_cseq(t_msg)->number.len!=get_cseq(p_msg)->number.len)
|
|
continue;
|
|
/* To only the uri -- to many UACs screw up tags */
|
|
if (get_to(t_msg)->uri.len!=get_to(p_msg)->uri.len)
|
|
continue;
|
|
if (!EQ_STR(callid)) continue;
|
|
if (memcmp(get_cseq(t_msg)->number.s, get_cseq(p_msg)->number.s,
|
|
get_cseq(p_msg)->number.len)!=0) continue;
|
|
if (memcmp(get_to(t_msg)->uri.s, get_to(p_msg)->uri.s,
|
|
get_to(t_msg)->uri.len)!=0) continue;
|
|
|
|
/* it is e2e ACK/200 */
|
|
if (p_cell->uas.status<300) {
|
|
/* For e2e ACKs, From's tag 'MUST' equal INVITE's, while use
|
|
* of the URI in this case is to be deprecated (Sec. 12.2.1.1).
|
|
* Comparing entire From body is dangerous, since some UAs
|
|
* screw the display name up. */
|
|
if (parse_from_header(p_msg) < 0) {
|
|
ERR("failed to parse From HF; ACK might not match.\n");
|
|
continue;
|
|
}
|
|
if (! STR_EQ(get_from(t_msg)->tag_value,
|
|
get_from(p_msg)->tag_value))
|
|
continue;
|
|
#ifdef TM_E2E_ACK_CHECK_FROM_URI
|
|
if (! STR_EQ(get_from(t_msg)->uri,
|
|
get_from(p_msg)->uri))
|
|
continue;
|
|
#endif
|
|
|
|
/* all criteria for proxied ACK are ok */
|
|
if (likely(p_cell->relayed_reply_branch!=-2)) {
|
|
if (unlikely(has_tran_tmcbs(p_cell,
|
|
TMCB_E2EACK_IN|TMCB_E2EACK_RETR_IN))){
|
|
if (likely(totag_e2e_ack_matching(p_cell, p_msg)==2))
|
|
goto e2e_ack;
|
|
else if (e2e_ack_trans==0)
|
|
e2e_ack_trans=p_cell;
|
|
}
|
|
continue;
|
|
}
|
|
/* it's a local UAS transaction */
|
|
if (dlg_matching(p_cell, p_msg))
|
|
goto found;
|
|
continue;
|
|
} else {
|
|
/* for hbh ACKs, From HF 'MUST' equal INVITE's one */
|
|
if (! EQ_LEN(from)) continue;
|
|
if (! EQ_STR(from)) continue;
|
|
}
|
|
|
|
/* it is not an e2e ACK/200 -- perhaps it is
|
|
* local negative case; in which case we will want
|
|
* more elements to match: r-uri and via; allow
|
|
* mismatching r-uri as an config option for broken
|
|
* UACs */
|
|
if (cfg_get(tm, tm_cfg, ruri_matching) && !EQ_REQ_URI_LEN )
|
|
continue;
|
|
if (cfg_get(tm, tm_cfg, via1_matching) && !EQ_VIA_LEN(via1))
|
|
continue;
|
|
if (cfg_get(tm, tm_cfg, ruri_matching) && !EQ_REQ_URI_STR)
|
|
continue;
|
|
if (cfg_get(tm, tm_cfg, via1_matching) && !EQ_VIA_STR(via1))
|
|
continue;
|
|
|
|
/* wow -- we survived all the check! we matched! */
|
|
DBG("DEBUG: non-2xx ACK matched\n");
|
|
goto found;
|
|
} /* synonym loop */
|
|
} /* ACK */
|
|
|
|
notfound:
|
|
|
|
if (e2e_ack_trans) {
|
|
p_cell=e2e_ack_trans;
|
|
goto e2e_ack;
|
|
}
|
|
|
|
/* no transaction found */
|
|
set_t(0, T_BR_UNDEFINED);
|
|
if (!leave_new_locked) {
|
|
UNLOCK_HASH(p_msg->hash_index);
|
|
}
|
|
DBG("DEBUG: t_lookup_request: no transaction found\n");
|
|
return -1;
|
|
|
|
e2e_ack:
|
|
t_ack=p_cell; /* e2e proxied ACK */
|
|
set_t(0, T_BR_UNDEFINED);
|
|
if (!leave_new_locked) {
|
|
UNLOCK_HASH(p_msg->hash_index);
|
|
}
|
|
DBG("DEBUG: t_lookup_request: e2e proxy ACK found\n");
|
|
return -2;
|
|
|
|
found:
|
|
set_t(p_cell, T_BR_UNDEFINED);
|
|
REF_UNSAFE( T );
|
|
set_kr(REQ_EXIST);
|
|
UNLOCK_HASH( p_msg->hash_index );
|
|
DBG("DEBUG: t_lookup_request: transaction found (T=%p)\n",T);
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
/* function lookups transaction being canceled by CANCEL in p_msg;
|
|
* it returns:
|
|
* 0 - transaction wasn't found
|
|
* T - transaction found
|
|
*/
|
|
struct cell* t_lookupOriginalT( struct sip_msg* p_msg )
|
|
{
|
|
struct cell *p_cell;
|
|
unsigned int hash_index;
|
|
struct sip_msg *t_msg;
|
|
struct via_param *branch;
|
|
struct entry* hash_bucket;
|
|
int foo;
|
|
int ret;
|
|
|
|
|
|
/* start searching in the table */
|
|
if (!(p_msg->msg_flags & FL_HASH_INDEX)){
|
|
/* parse all*/
|
|
if (check_transaction_quadruple(p_msg)==0)
|
|
{
|
|
LOG(L_ERR, "ERROR: TM module: t_lookupOriginalT:"
|
|
" too few headers\n");
|
|
/* stop processing */
|
|
return 0;
|
|
}
|
|
p_msg->hash_index=hash( p_msg->callid->body , get_cseq(p_msg)->number);
|
|
p_msg->msg_flags|=FL_HASH_INDEX;
|
|
}
|
|
hash_index = p_msg->hash_index;
|
|
DBG("DEBUG: t_lookupOriginalT: searching on hash entry %d\n",hash_index );
|
|
|
|
|
|
/* first of all, look if there is RFC3261 magic cookie in branch; if
|
|
* so, we can do very quick matching and skip the old-RFC bizzar
|
|
* comparison of many header fields
|
|
*/
|
|
if (!p_msg->via1) {
|
|
LOG(L_ERR, "ERROR: t_lookupOriginalT: no via\n");
|
|
return 0;
|
|
}
|
|
branch=p_msg->via1->branch;
|
|
if (branch && branch->value.s && branch->value.len>MCOOKIE_LEN
|
|
&& memcmp(branch->value.s,MCOOKIE,MCOOKIE_LEN)==0) {
|
|
/* huhuhu! the cookie is there -- let's proceed fast */
|
|
LOCK_HASH(hash_index);
|
|
ret=matching_3261(p_msg, &p_cell,
|
|
/* we are seeking the original transaction --
|
|
* skip CANCEL transactions during search
|
|
*/
|
|
METHOD_CANCEL, &foo);
|
|
if (ret==1) goto found; else goto notfound;
|
|
}
|
|
|
|
/* no cookies --proceed to old-fashioned pre-3261 t-matching */
|
|
|
|
LOCK_HASH(hash_index);
|
|
|
|
hash_bucket=&(get_tm_table()->entries[hash_index]);
|
|
/* all the transactions from the entry are compared */
|
|
clist_foreach(hash_bucket, p_cell, next_c){
|
|
prefetch_loc_r(p_cell->next_c, 1);
|
|
t_msg = p_cell->uas.request;
|
|
|
|
if (!t_msg) continue; /* skip UAC transactions */
|
|
|
|
/* we don't cancel CANCELs ;-) */
|
|
if (unlikely(t_msg->REQ_METHOD==METHOD_CANCEL))
|
|
continue;
|
|
|
|
/* check lengths now */
|
|
if (!EQ_LEN(callid))
|
|
continue;
|
|
if (get_cseq(t_msg)->number.len!=get_cseq(p_msg)->number.len)
|
|
continue;
|
|
if (!EQ_LEN(from))
|
|
continue;
|
|
#ifdef CANCEL_TAG
|
|
if (!EQ_LEN(to))
|
|
continue;
|
|
#else
|
|
/* relaxed matching -- we don't care about to-tags anymore,
|
|
* many broken UACs screw them up and ignoring them does not
|
|
* actually hurt
|
|
*/
|
|
if (get_to(t_msg)->uri.len!=get_to(p_msg)->uri.len)
|
|
continue;
|
|
#endif
|
|
if (cfg_get(tm, tm_cfg, ruri_matching) && !EQ_REQ_URI_LEN)
|
|
continue;
|
|
if (cfg_get(tm, tm_cfg, via1_matching) && !EQ_VIA_LEN(via1))
|
|
continue;
|
|
|
|
/* check the content now */
|
|
if (!EQ_STR(callid))
|
|
continue;
|
|
if (memcmp(get_cseq(t_msg)->number.s,
|
|
get_cseq(p_msg)->number.s,get_cseq(p_msg)->number.len)!=0)
|
|
continue;
|
|
if (!EQ_STR(from))
|
|
continue;
|
|
#ifdef CANCEL_TAG
|
|
if (!EQ_STR(to))
|
|
continue;
|
|
#else
|
|
if (memcmp(get_to(t_msg)->uri.s, get_to(p_msg)->uri.s,
|
|
get_to(t_msg)->uri.len)!=0)
|
|
continue;
|
|
#endif
|
|
if (cfg_get(tm, tm_cfg, ruri_matching) && !EQ_REQ_URI_STR)
|
|
continue;
|
|
if (cfg_get(tm, tm_cfg, via1_matching) && !EQ_VIA_STR(via1))
|
|
continue;
|
|
|
|
/* found */
|
|
goto found;
|
|
}
|
|
|
|
notfound:
|
|
/* no transaction found */
|
|
DBG("DEBUG: t_lookupOriginalT: no CANCEL matching found! \n" );
|
|
UNLOCK_HASH(hash_index);
|
|
DBG("DEBUG: t_lookupOriginalT completed\n");
|
|
return 0;
|
|
|
|
found:
|
|
DBG("DEBUG: t_lookupOriginalT: canceled transaction"
|
|
" found (%p)! \n",p_cell );
|
|
REF_UNSAFE( p_cell );
|
|
UNLOCK_HASH(hash_index);
|
|
DBG("DEBUG: t_lookupOriginalT completed\n");
|
|
return p_cell;
|
|
}
|
|
|
|
|
|
|
|
|
|
/** get the transaction corresponding to a reply.
|
|
* @return -1 - nothing found, 1 - T found
|
|
* Side-effects: sets T and T_branch on success.
|
|
*/
|
|
int t_reply_matching( struct sip_msg *p_msg , int *p_branch )
|
|
{
|
|
struct cell* p_cell;
|
|
unsigned int hash_index = 0;
|
|
unsigned int entry_label = 0;
|
|
unsigned int branch_id = 0;
|
|
char *hashi, *branchi, *p, *n;
|
|
struct entry* hash_bucket;
|
|
int hashl, branchl;
|
|
int scan_space;
|
|
str cseq_method;
|
|
str req_method;
|
|
|
|
char *loopi;
|
|
int loopl;
|
|
char *syni;
|
|
int synl;
|
|
|
|
short is_cancel;
|
|
|
|
/* make compiler warnings happy */
|
|
loopi=0;
|
|
loopl=0;
|
|
syni=0;
|
|
synl=0;
|
|
|
|
/* split the branch into pieces: loop_detection_check(ignored),
|
|
hash_table_id, synonym_id, branch_id */
|
|
|
|
if (!(p_msg->via1 && p_msg->via1->branch && p_msg->via1->branch->value.s))
|
|
goto nomatch2;
|
|
|
|
/* we do RFC 3261 tid matching and want to see first if there is
|
|
* magic cookie in branch */
|
|
if (p_msg->via1->branch->value.len<=MCOOKIE_LEN)
|
|
goto nomatch2;
|
|
if (memcmp(p_msg->via1->branch->value.s, MCOOKIE, MCOOKIE_LEN)!=0)
|
|
goto nomatch2;
|
|
|
|
p=p_msg->via1->branch->value.s+MCOOKIE_LEN;
|
|
scan_space=p_msg->via1->branch->value.len-MCOOKIE_LEN;
|
|
|
|
|
|
/* hash_id */
|
|
n=eat_token2_end( p, p+scan_space, BRANCH_SEPARATOR);
|
|
hashl=n-p;
|
|
scan_space-=hashl;
|
|
if (!hashl || scan_space<2 || *n!=BRANCH_SEPARATOR) goto nomatch2;
|
|
hashi=p;
|
|
p=n+1;scan_space--;
|
|
|
|
if (!syn_branch) {
|
|
/* md5 value */
|
|
n=eat_token2_end( p, p+scan_space, BRANCH_SEPARATOR );
|
|
loopl = n-p;
|
|
scan_space-= loopl;
|
|
if (n==p || scan_space<2 || *n!=BRANCH_SEPARATOR)
|
|
goto nomatch2;
|
|
loopi=p;
|
|
p=n+1; scan_space--;
|
|
} else {
|
|
/* synonym id */
|
|
n=eat_token2_end( p, p+scan_space, BRANCH_SEPARATOR);
|
|
synl=n-p;
|
|
scan_space-=synl;
|
|
if (!synl || scan_space<2 || *n!=BRANCH_SEPARATOR)
|
|
goto nomatch2;
|
|
syni=p;
|
|
p=n+1;scan_space--;
|
|
}
|
|
|
|
/* branch id - should exceed the scan_space */
|
|
n=eat_token_end( p, p+scan_space );
|
|
branchl=n-p;
|
|
if (!branchl ) goto nomatch2;
|
|
branchi=p;
|
|
|
|
/* sanity check */
|
|
if (unlikely(reverse_hex2int(hashi, hashl, &hash_index)<0
|
|
||hash_index>=TABLE_ENTRIES
|
|
|| reverse_hex2int(branchi, branchl, &branch_id)<0
|
|
||branch_id>=MAX_BRANCHES
|
|
|| (syn_branch ? (reverse_hex2int(syni, synl, &entry_label))<0
|
|
: loopl!=MD5_LEN ))
|
|
) {
|
|
DBG("DEBUG: t_reply_matching: poor reply labels %d label %d "
|
|
"branch %d\n", hash_index, entry_label, branch_id );
|
|
goto nomatch2;
|
|
}
|
|
|
|
|
|
DBG("DEBUG: t_reply_matching: hash %d label %d branch %d\n",
|
|
hash_index, entry_label, branch_id );
|
|
|
|
|
|
/* search the hash table list at entry 'hash_index'; lock the
|
|
entry first
|
|
*/
|
|
cseq_method=get_cseq(p_msg)->method;
|
|
is_cancel=cseq_method.len==CANCEL_LEN
|
|
&& memcmp(cseq_method.s, CANCEL, CANCEL_LEN)==0;
|
|
LOCK_HASH(hash_index);
|
|
hash_bucket=&(get_tm_table()->entries[hash_index]);
|
|
/* all the transactions from the entry are compared */
|
|
clist_foreach(hash_bucket, p_cell, next_c){
|
|
prefetch_loc_r(p_cell->next_c, 1);
|
|
/* first look if branch matches */
|
|
if (likely(syn_branch)) {
|
|
if (p_cell->label != entry_label)
|
|
continue;
|
|
} else {
|
|
if ( memcmp(p_cell->md5, loopi,MD5_LEN)!=0)
|
|
continue;
|
|
}
|
|
|
|
/* sanity check ... too high branch ? */
|
|
if (unlikely(branch_id>=p_cell->nr_of_outgoings))
|
|
continue;
|
|
|
|
/* does method match ? (remember -- CANCELs have the same branch
|
|
as canceled transactions) */
|
|
req_method=p_cell->method;
|
|
if ( /* method match */
|
|
! ((cseq_method.len==req_method.len
|
|
&& memcmp( cseq_method.s, req_method.s, cseq_method.len )==0)
|
|
/* or it is a local cancel */
|
|
|| (is_cancel && is_invite(p_cell)
|
|
/* commented out -- should_cancel_branch set it to
|
|
BUSY_BUFFER to avoid collisions with replies;
|
|
thus, we test here by buffer size
|
|
*/
|
|
/* && p_cell->uac[branch_id].local_cancel.buffer ))) */
|
|
&& p_cell->uac[branch_id].local_cancel.buffer_len )))
|
|
continue;
|
|
|
|
|
|
/* we passed all disqualifying factors .... the transaction has been
|
|
matched !
|
|
*/
|
|
set_t(p_cell, (int)branch_id);
|
|
*p_branch =(int) branch_id;
|
|
REF_UNSAFE( T );
|
|
UNLOCK_HASH(hash_index);
|
|
DBG("DEBUG: t_reply_matching: reply matched (T=%p)!\n",T);
|
|
/* if this is a 200 for INVITE, we will wish to store to-tags to be
|
|
* able to distinguish retransmissions later and not to call
|
|
* TMCB_RESPONSE_OUT uselessly; we do it only if callbacks are
|
|
* enabled -- except callback customers, nobody cares about
|
|
* retransmissions of multiple 200/INV or ACK/200s
|
|
*/
|
|
if (unlikely( is_invite(p_cell) && p_msg->REPLY_STATUS>=200
|
|
&& p_msg->REPLY_STATUS<300
|
|
&& ((!is_local(p_cell) &&
|
|
has_tran_tmcbs(p_cell,
|
|
TMCB_RESPONSE_OUT|TMCB_RESPONSE_READY
|
|
|TMCB_E2EACK_IN|TMCB_E2EACK_RETR_IN) )
|
|
|| (is_local(p_cell)&&has_tran_tmcbs(p_cell, TMCB_LOCAL_COMPLETED))
|
|
)) ) {
|
|
if (parse_headers(p_msg, HDR_TO_F, 0)==-1) {
|
|
LOG(L_ERR, "ERROR: t_reply_matching: to parsing failed\n");
|
|
}
|
|
}
|
|
if (unlikely(has_tran_tmcbs(T, TMCB_RESPONSE_IN |
|
|
TMCB_LOCAL_RESPONSE_IN))){
|
|
if (!is_local(p_cell)) {
|
|
run_trans_callbacks( TMCB_RESPONSE_IN, T, T->uas.request,
|
|
p_msg, p_msg->REPLY_STATUS);
|
|
}else{
|
|
run_trans_callbacks( TMCB_LOCAL_RESPONSE_IN, T, T->uas.request,
|
|
p_msg, p_msg->REPLY_STATUS);
|
|
}
|
|
}
|
|
return 1;
|
|
} /* for cycle */
|
|
|
|
/* nothing found */
|
|
UNLOCK_HASH(hash_index);
|
|
DBG("DEBUG: t_reply_matching: no matching transaction exists\n");
|
|
|
|
nomatch2:
|
|
DBG("DEBUG: t_reply_matching: failure to match a transaction\n");
|
|
*p_branch = -1;
|
|
set_t(0, T_BR_UNDEFINED);
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Determine current transaction (w/ e2eack support).
|
|
*
|
|
* script/t_lookup_request return convention:
|
|
* Found Not Found Error (e.g. parsing) E2E ACK
|
|
* @return 1 -1 0 -2
|
|
* T ptr 0 T_UNDEFINED| 0 0
|
|
* Side-effects: sets T and T_branch.
|
|
*/
|
|
int t_check_msg( struct sip_msg* p_msg , int *param_branch )
|
|
{
|
|
int local_branch;
|
|
int canceled;
|
|
int ret;
|
|
|
|
ret=0;
|
|
/* is T still up-to-date ? */
|
|
DBG("DEBUG: t_check_msg: msg id=%d global id=%d T start=%p\n",
|
|
p_msg->id,global_msg_id,T);
|
|
if ( p_msg->id != global_msg_id || T==T_UNDEFINED )
|
|
{
|
|
global_msg_id = p_msg->id;
|
|
set_t(T_UNDEFINED, T_BR_UNDEFINED);
|
|
/* transaction lookup */
|
|
if ( p_msg->first_line.type==SIP_REQUEST ) {
|
|
/* force parsing all the needed headers*/
|
|
prefetch_loc_r(p_msg->unparsed+64, 1);
|
|
if (parse_headers(p_msg, HDR_EOH_F, 0 )==-1) {
|
|
LOG(L_ERR, "ERROR: t_check_msg: parsing error\n");
|
|
goto error;
|
|
}
|
|
/* in case, we act as UAS for INVITE and reply with 200,
|
|
* we will need to run dialog-matching for subsequent
|
|
* ACK, for which we need From-tag; We also need from-tag
|
|
* in case people want to have proxied e2e ACKs accounted
|
|
*/
|
|
if (p_msg->REQ_METHOD==METHOD_INVITE
|
|
&& parse_from_header(p_msg)==-1) {
|
|
LOG(L_ERR, "ERROR: t_check_msg: from parsing failed\n");
|
|
goto error;
|
|
}
|
|
ret=t_lookup_request( p_msg , 0 /* unlock before returning */,
|
|
&canceled);
|
|
} else {
|
|
/* we need Via for branch and Cseq method to distinguish
|
|
replies with the same branch/cseqNr (CANCEL)
|
|
and we need all the WWW/Proxy Authenticate headers for
|
|
401 & 407 replies
|
|
*/
|
|
if (cfg_get(tm, tm_cfg, tm_aggregate_auth) &&
|
|
(p_msg->REPLY_STATUS==401 || p_msg->REPLY_STATUS==407)){
|
|
if (parse_headers(p_msg, HDR_EOH_F,0)==-1){
|
|
LOG(L_WARN, "WARNING: the reply cannot be "
|
|
"completely parsed\n");
|
|
/* try to continue, via1 & cseq are checked below */
|
|
}
|
|
}else if ( parse_headers(p_msg, HDR_VIA1_F|HDR_CSEQ_F, 0 )==-1) {
|
|
LOG(L_ERR, "ERROR: reply cannot be parsed\n");
|
|
goto error;
|
|
}
|
|
if ((p_msg->via1==0) || (p_msg->cseq==0)){
|
|
LOG(L_ERR, "ERROR: reply doesn't have a via or cseq"
|
|
" header\n");
|
|
goto error;
|
|
}
|
|
/* if that is an INVITE, we will also need to-tag
|
|
for later ACK matching
|
|
*/
|
|
if ( get_cseq(p_msg)->method.len==INVITE_LEN
|
|
&& memcmp( get_cseq(p_msg)->method.s, INVITE, INVITE_LEN )==0)
|
|
{
|
|
if (parse_headers(p_msg, HDR_TO_F, 0)==-1 || !p_msg->to) {
|
|
LOG(L_ERR, "ERROR: INVITE reply cannot be parsed\n");
|
|
goto error;
|
|
}
|
|
}
|
|
ret=t_reply_matching( p_msg ,
|
|
param_branch!=0?param_branch:&local_branch );
|
|
}
|
|
#ifdef EXTRA_DEBUG
|
|
if ( T && T!=T_UNDEFINED && T->flags & (T_IN_AGONY)) {
|
|
LOG( L_WARN, "WARNING: transaction %p scheduled for deletion "
|
|
"and called from t_check_msg (flags=%x) (but it might be ok)"
|
|
"\n", T, T->flags);
|
|
}
|
|
#endif
|
|
DBG("DEBUG: t_check_msg: msg id=%d global id=%d T end=%p\n",
|
|
p_msg->id,global_msg_id,T);
|
|
} else { /* ( p_msg->id == global_msg_id && T!=T_UNDEFINED ) */
|
|
if (T){
|
|
DBG("DEBUG: t_check_msg: T already found!\n");
|
|
ret=1;
|
|
}else{
|
|
DBG("DEBUG: t_check_msg: T previously sought and not found\n");
|
|
ret=-1;
|
|
}
|
|
if (likely(param_branch))
|
|
*param_branch=T_branch;
|
|
}
|
|
return ret;
|
|
error:
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/** Determine current transaction (old version).
|
|
*
|
|
* Found Not Found Error (e.g. parsing)
|
|
* @return 1 0 -1
|
|
* T ptr 0 T_UNDEFINED | 0
|
|
*
|
|
* Side-effects: sets T and T_branch.
|
|
*/
|
|
int t_check( struct sip_msg* p_msg , int *param_branch )
|
|
{
|
|
int ret;
|
|
|
|
ret=t_check_msg(p_msg, param_branch);
|
|
/* fix t_check_msg return */
|
|
switch(ret){
|
|
case -2: /* e2e ack */ return 0; /* => not found */
|
|
case -1: /* not found */ return 0; /* => not found */
|
|
case 0: /* parse error */ return -1; /* => error */
|
|
case 1: /* found */ return ret; /* => found */
|
|
};
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
int init_rb( struct retr_buf *rb, struct sip_msg *msg)
|
|
{
|
|
/*struct socket_info* send_sock;*/
|
|
struct via_body* via;
|
|
int proto;
|
|
|
|
/* rb. timers are init. init_t()/new_cell() */
|
|
via=msg->via1;
|
|
/* rb->dst is already init (0) by new_t()/build_cell() */
|
|
if (!reply_to_via) {
|
|
update_sock_struct_from_ip( &rb->dst.to, msg );
|
|
proto=msg->rcv.proto;
|
|
} else {
|
|
/*init retrans buffer*/
|
|
if (update_sock_struct_from_via( &(rb->dst.to), msg, via )==-1) {
|
|
LOG(L_ERR, "ERROR: init_rb: cannot lookup reply dst: %.*s\n",
|
|
via->host.len, via->host.s );
|
|
ser_error=E_BAD_VIA;
|
|
return 0;
|
|
}
|
|
proto=via->proto;
|
|
}
|
|
rb->dst.proto=proto;
|
|
rb->dst.id=msg->rcv.proto_reserved1;
|
|
#ifdef USE_COMP
|
|
rb->dst.comp=via->comp_no;
|
|
#endif
|
|
rb->dst.send_flags=msg->rpl_send_flags;
|
|
|
|
membar_write();
|
|
rb->dst.send_sock=msg->rcv.bind_address;
|
|
return 1;
|
|
}
|
|
|
|
|
|
static inline void init_new_t(struct cell *new_cell, struct sip_msg *p_msg)
|
|
{
|
|
struct sip_msg *shm_msg;
|
|
unsigned int timeout; /* avp timeout gets stored here (in s) */
|
|
ticks_t lifetime;
|
|
|
|
shm_msg=new_cell->uas.request;
|
|
new_cell->from.s=shm_msg->from->name.s;
|
|
new_cell->from.len=HF_LEN(shm_msg->from);
|
|
new_cell->to.s=shm_msg->to->name.s;
|
|
new_cell->to.len=HF_LEN(shm_msg->to);
|
|
new_cell->callid.s=shm_msg->callid->name.s;
|
|
new_cell->callid.len=HF_LEN(shm_msg->callid);
|
|
new_cell->cseq_n.s=shm_msg->cseq->name.s;
|
|
new_cell->cseq_n.len=get_cseq(shm_msg)->number.s
|
|
+get_cseq(shm_msg)->number.len
|
|
-shm_msg->cseq->name.s;
|
|
|
|
new_cell->method=new_cell->uas.request->first_line.u.request.method;
|
|
if (p_msg->REQ_METHOD==METHOD_INVITE){
|
|
/* set flags */
|
|
new_cell->flags |= T_IS_INVITE_FLAG |
|
|
get_msgid_val(user_cell_set_flags, p_msg->id, int);
|
|
new_cell->flags|=T_AUTO_INV_100 &
|
|
(!cfg_get(tm, tm_cfg, tm_auto_inv_100) -1);
|
|
new_cell->flags|=T_DISABLE_6xx &
|
|
(!cfg_get(tm, tm_cfg, disable_6xx) -1);
|
|
#ifdef CANCEL_REASON_SUPPORT
|
|
new_cell->flags|=T_NO_E2E_CANCEL_REASON &
|
|
(!!cfg_get(tm, tm_cfg, e2e_cancel_reason) -1);
|
|
#endif /* CANCEL_REASON_SUPPORT */
|
|
/* reset flags */
|
|
new_cell->flags &=
|
|
(~ get_msgid_val(user_cell_reset_flags, p_msg->id, int));
|
|
|
|
lifetime=(ticks_t)get_msgid_val(user_inv_max_lifetime,
|
|
p_msg->id, int);
|
|
if (likely(lifetime==0))
|
|
lifetime=cfg_get(tm, tm_cfg, tm_max_inv_lifetime);
|
|
}else{
|
|
lifetime=(ticks_t)get_msgid_val(user_noninv_max_lifetime,
|
|
p_msg->id, int);
|
|
if (likely(lifetime==0))
|
|
lifetime=cfg_get(tm, tm_cfg, tm_max_noninv_lifetime);
|
|
}
|
|
new_cell->on_negative=get_on_negative();
|
|
new_cell->on_reply=get_on_reply();
|
|
new_cell->end_of_life=get_ticks_raw()+lifetime;;
|
|
new_cell->fr_timeout=(ticks_t)get_msgid_val(user_fr_timeout,
|
|
p_msg->id, int);
|
|
new_cell->fr_inv_timeout=(ticks_t)get_msgid_val(user_fr_inv_timeout,
|
|
p_msg->id, int);
|
|
if (likely(new_cell->fr_timeout==0)){
|
|
if (unlikely(!fr_avp2timer(&timeout))) {
|
|
DBG("init_new_t: FR__TIMER = %d s\n", timeout);
|
|
new_cell->fr_timeout=S_TO_TICKS((ticks_t)timeout);
|
|
}else{
|
|
new_cell->fr_timeout=cfg_get(tm, tm_cfg, fr_timeout);
|
|
}
|
|
}
|
|
if (likely(new_cell->fr_inv_timeout==0)){
|
|
if (unlikely(!fr_inv_avp2timer(&timeout))) {
|
|
DBG("init_new_t: FR_INV_TIMER = %d s\n", timeout);
|
|
new_cell->fr_inv_timeout=S_TO_TICKS((ticks_t)timeout);
|
|
new_cell->flags |= T_NOISY_CTIMER_FLAG;
|
|
}else{
|
|
new_cell->fr_inv_timeout=cfg_get(tm, tm_cfg, fr_inv_timeout);
|
|
}
|
|
}
|
|
#ifdef TM_DIFF_RT_TIMEOUT
|
|
new_cell->rt_t1_timeout_ms = (retr_timeout_t) get_msgid_val(
|
|
user_rt_t1_timeout_ms,
|
|
p_msg->id, int);
|
|
if (likely(new_cell->rt_t1_timeout_ms == 0))
|
|
new_cell->rt_t1_timeout_ms = cfg_get(tm, tm_cfg, rt_t1_timeout_ms);
|
|
new_cell->rt_t2_timeout_ms = (retr_timeout_t) get_msgid_val(
|
|
user_rt_t2_timeout_ms,
|
|
p_msg->id, int);
|
|
if (likely(new_cell->rt_t2_timeout_ms == 0))
|
|
new_cell->rt_t2_timeout_ms = cfg_get(tm, tm_cfg, rt_t2_timeout_ms);
|
|
#endif
|
|
new_cell->on_branch=get_on_branch();
|
|
}
|
|
|
|
|
|
|
|
/** creates a new transaction from a message.
|
|
* No checks are made if the transaction exists. It is created and
|
|
* added to the tm hashes. T is set to the new transaction.
|
|
* @param p_msg - pointer to sip message
|
|
* @return >0 on success, <0 on error (an E_* error code, see error.h)
|
|
* Side-effects: sets T and T_branch (T_branch always to T_BR_UNDEFINED).
|
|
*/
|
|
static inline int new_t(struct sip_msg *p_msg)
|
|
{
|
|
struct cell *new_cell;
|
|
|
|
/* for ACK-dlw-wise matching, we want From-tags */
|
|
if (p_msg->REQ_METHOD==METHOD_INVITE && parse_from_header(p_msg)<0) {
|
|
LOG(L_ERR, "ERROR: new_t: no valid From in INVITE\n");
|
|
return E_BAD_REQ;
|
|
}
|
|
/* make sure uri will be parsed before cloning */
|
|
if (parse_sip_msg_uri(p_msg)<0) {
|
|
LOG(L_ERR, "ERROR: new_t: uri invalid\n");
|
|
return E_BAD_REQ;
|
|
}
|
|
|
|
/* add new transaction */
|
|
new_cell = build_cell( p_msg ) ;
|
|
if ( !new_cell ){
|
|
LOG(L_ERR, "ERROR: new_t: out of mem:\n");
|
|
return E_OUT_OF_MEM;
|
|
}
|
|
|
|
#ifdef TM_DEL_UNREF
|
|
INIT_REF(new_cell, 2); /* 1 because it will be ref'ed from the
|
|
hash and +1 because we set T to it */
|
|
#endif
|
|
insert_into_hash_table_unsafe( new_cell, p_msg->hash_index );
|
|
set_t(new_cell, T_BR_UNDEFINED);
|
|
#ifndef TM_DEL_UNREF
|
|
INIT_REF_UNSAFE(T);
|
|
#endif
|
|
/* init pointers to headers needed to construct local
|
|
requests such as CANCEL/ACK
|
|
*/
|
|
init_new_t(new_cell, p_msg);
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
/** if no transaction already exists for the message, create a new one.
|
|
* atomic "new_tran" construct; it returns:
|
|
*
|
|
* @return <0 on error
|
|
* +1 if a request did not match a transaction
|
|
* 0 on retransmission
|
|
* On success, if the request was an ack, the calling function shall
|
|
* forward statelessly. Otherwise it means, a new transaction was
|
|
* introduced and the calling function shall reply/relay/whatever_appropriate.
|
|
* Side-effects: sets T and T_branch (T_branch always to T_BR_UNDEFINED).
|
|
*/
|
|
int t_newtran( struct sip_msg* p_msg )
|
|
{
|
|
int lret, my_err;
|
|
int canceled;
|
|
|
|
|
|
/* is T still up-to-date ? */
|
|
DBG("DEBUG: t_newtran: msg id=%d , global msg id=%d ,"
|
|
" T on entrance=%p\n",p_msg->id,global_msg_id,T);
|
|
|
|
if ( T && T!=T_UNDEFINED ) {
|
|
/* ERROR message moved to w_t_newtran */
|
|
DBG("DEBUG: t_newtran: "
|
|
"transaction already in process %p\n", T );
|
|
|
|
/* t_newtran() has been already called, and the script
|
|
might changed the flags after it, so we must update the flags
|
|
in shm memory -- Miklos */
|
|
if (T->uas.request)
|
|
T->uas.request->flags = p_msg->flags;
|
|
|
|
return E_SCRIPT;
|
|
}
|
|
|
|
global_msg_id = p_msg->id;
|
|
set_t(T_UNDEFINED, T_BR_UNDEFINED);
|
|
/* first of all, parse everything -- we will store in shared memory
|
|
and need to have all headers ready for generating potential replies
|
|
later; parsing later on demand is not an option since the request
|
|
will be in shmem and applying parse_headers to it would intermix
|
|
shmem with pkg_mem
|
|
*/
|
|
|
|
if (parse_headers(p_msg, HDR_EOH_F, 0 )) {
|
|
LOG(L_ERR, "ERROR: t_newtran: parse_headers failed\n");
|
|
return E_BAD_REQ;
|
|
}
|
|
if ((p_msg->parsed_flag & HDR_EOH_F)!=HDR_EOH_F) {
|
|
LOG(L_ERR, "ERROR: t_newtran: EoH not parsed\n");
|
|
return E_OUT_OF_MEM;
|
|
}
|
|
/* t_lookup_requests attempts to find the transaction;
|
|
it also calls check_transaction_quadruple -> it is
|
|
safe to assume we have from/callid/cseq/to
|
|
*/
|
|
lret = t_lookup_request( p_msg, 1 /* leave locked if not found */,
|
|
&canceled );
|
|
|
|
/* on error, pass the error in the stack ... nothing is locked yet
|
|
if 0 is returned */
|
|
if (lret==0) return E_BAD_TUPEL;
|
|
|
|
/* transaction found, it's a retransmission */
|
|
if (lret>0) {
|
|
if (p_msg->REQ_METHOD==METHOD_ACK) {
|
|
if (unlikely(has_tran_tmcbs(T, TMCB_ACK_NEG_IN)))
|
|
run_trans_callbacks(TMCB_ACK_NEG_IN, T, p_msg, 0,
|
|
p_msg->REQ_METHOD);
|
|
t_release_transaction(T);
|
|
} else {
|
|
if (unlikely(has_tran_tmcbs(T, TMCB_REQ_RETR_IN)))
|
|
run_trans_callbacks(TMCB_REQ_RETR_IN, T, p_msg, 0,
|
|
p_msg->REQ_METHOD);
|
|
t_retransmit_reply(T);
|
|
}
|
|
/* things are done -- return from script */
|
|
return 0;
|
|
}
|
|
|
|
/* from now on, be careful -- hash table is locked */
|
|
|
|
if (lret==-2) { /* was it an e2e ACK ? if so, trigger a callback */
|
|
/* no callbacks? complete quickly */
|
|
if (likely( !has_tran_tmcbs(t_ack,
|
|
TMCB_E2EACK_IN|TMCB_E2EACK_RETR_IN) )) {
|
|
UNLOCK_HASH(p_msg->hash_index);
|
|
return 1;
|
|
}
|
|
REF_UNSAFE(t_ack);
|
|
UNLOCK_HASH(p_msg->hash_index);
|
|
/* we don't call from within REPLY_LOCK -- that introduces
|
|
* a race condition; however, it is so unlikely and the
|
|
* impact is so small (callback called multiple times of
|
|
* multiple ACK/200s received in parallel), that we do not
|
|
* better waste time in locks */
|
|
if (unmatched_totag(t_ack, p_msg)) {
|
|
if (likely (has_tran_tmcbs(t_ack, TMCB_E2EACK_IN)))
|
|
run_trans_callbacks( TMCB_E2EACK_IN , t_ack, p_msg, 0,
|
|
-p_msg->REQ_METHOD );
|
|
}else if (unlikely(has_tran_tmcbs(t_ack, TMCB_E2EACK_RETR_IN))){
|
|
run_trans_callbacks( TMCB_E2EACK_RETR_IN , t_ack, p_msg, 0,
|
|
-p_msg->REQ_METHOD );
|
|
}
|
|
UNREF(t_ack);
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* transaction not found, it's a new request (lret<0, lret!=-2);
|
|
establish a new transaction ... */
|
|
if (p_msg->REQ_METHOD==METHOD_ACK) { /* ... unless it is in ACK */
|
|
my_err=1;
|
|
goto new_err;
|
|
}
|
|
|
|
my_err=new_t(p_msg);
|
|
if (my_err<0) {
|
|
LOG(L_ERR, "ERROR: t_newtran: new_t failed\n");
|
|
goto new_err;
|
|
}
|
|
if (canceled) T->flags|=T_CANCELED; /* mark it for future ref. */
|
|
|
|
|
|
UNLOCK_HASH(p_msg->hash_index);
|
|
/* now, when the transaction state exists, check if
|
|
there is a meaningful Via and calculate it; better
|
|
do it now than later: state is established so that
|
|
subsequent retransmissions will be absorbed and will
|
|
not possibly block during Via DNS resolution; doing
|
|
it later would only burn more CPU as if there is an
|
|
error, we cannot relay later whatever comes out of the
|
|
the transaction
|
|
*/
|
|
if (!init_rb( &T->uas.response, p_msg)) {
|
|
LOG(L_ERR, "ERROR: t_newtran: unresolvable via1\n");
|
|
put_on_wait( T );
|
|
t_unref(p_msg);
|
|
return E_BAD_VIA;
|
|
}
|
|
|
|
return 1;
|
|
|
|
|
|
new_err:
|
|
UNLOCK_HASH(p_msg->hash_index);
|
|
return my_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** releases the current transaction (corresp. to p_msg).
|
|
* The current transaction (T) corresponding to the sip message being
|
|
* processed is released. Delayed replies are sent (if no other reply
|
|
* was sent in the script). Extra checks are made to see if the transaction
|
|
* was forwarded, explicitly replied or explicitly released. If not the
|
|
* transaction * is force-killed and a warning is logged (script error).
|
|
* @param p_msg - sip message being processed
|
|
* @return -1 on error, 1 on success.
|
|
* Side-effects: resets T and T_branch to T_UNDEFINED, T_BR_UNDEFINED,
|
|
* resets tm_error.
|
|
*/
|
|
int t_unref( struct sip_msg* p_msg )
|
|
{
|
|
enum kill_reason kr;
|
|
|
|
if (T==T_UNDEFINED || T==T_NULL_CELL)
|
|
return -1;
|
|
if (p_msg->first_line.type==SIP_REQUEST){
|
|
kr=get_kr();
|
|
if (unlikely(kr == REQ_ERR_DELAYED)){
|
|
DBG("t_unref: delayed error reply generation(%d)\n", tm_error);
|
|
if (unlikely(is_route_type(FAILURE_ROUTE))){
|
|
BUG("tm: t_unref: called w/ kr=REQ_ERR_DELAYED in failure"
|
|
" route for %p\n", T);
|
|
}else if (unlikely( kill_transaction(T, tm_error)<=0 )){
|
|
ERR("ERROR: t_unref: generation of a delayed stateful reply"
|
|
" failed\n");
|
|
t_release_transaction(T);
|
|
}
|
|
}else if ( unlikely (kr==0 ||(p_msg->REQ_METHOD==METHOD_ACK &&
|
|
!(kr & REQ_RLSD)))) {
|
|
LOG(L_WARN, "WARNING: script writer didn't release transaction\n");
|
|
t_release_transaction(T);
|
|
}else if (unlikely((kr & REQ_ERR_DELAYED) &&
|
|
(kr & ~(REQ_RLSD|REQ_RPLD|REQ_ERR_DELAYED|REQ_FWDED)))){
|
|
BUG("tm: t_unref: REQ_ERR DELAYED should have been caught much"
|
|
" earlier for %p: %d (hex %x)\n",T, kr, kr);
|
|
t_release_transaction(T);
|
|
}
|
|
}
|
|
tm_error=0; /* clear it */
|
|
UNREF( T );
|
|
set_t(T_UNDEFINED, T_BR_UNDEFINED);
|
|
return 1;
|
|
}
|
|
|
|
|
|
|
|
int t_get_trans_ident(struct sip_msg* p_msg, unsigned int* hash_index, unsigned int* label)
|
|
{
|
|
struct cell* t;
|
|
if(t_check(p_msg,0) != 1){
|
|
LOG(L_ERR,"ERROR: t_get_trans_ident: no transaction found\n");
|
|
return -1;
|
|
}
|
|
t = get_t();
|
|
if(!t){
|
|
LOG(L_ERR,"ERROR: t_get_trans_ident: transaction found is NULL\n");
|
|
return -1;
|
|
}
|
|
|
|
*hash_index = t->hash_index;
|
|
*label = t->label;
|
|
|
|
return 1;
|
|
}
|
|
|
|
#ifdef WITH_AS_SUPPORT
|
|
/**
|
|
* Returns the hash coordinates of the transaction current CANCEL is targeting.
|
|
*/
|
|
int t_get_canceled_ident(struct sip_msg* msg, unsigned int* hash_index,
|
|
unsigned int* label)
|
|
{
|
|
struct cell *orig;
|
|
if (msg->REQ_METHOD != METHOD_CANCEL) {
|
|
WARN("looking up original transaction for non-CANCEL method (%d).\n",
|
|
msg->REQ_METHOD);
|
|
return -1;
|
|
}
|
|
orig = t_lookupOriginalT(msg);
|
|
if ((orig == T_NULL_CELL) || (orig == T_UNDEFINED))
|
|
return -1;
|
|
*hash_index = orig->hash_index;
|
|
*label = orig->label;
|
|
DEBUG("original T found @%p, %d:%d.\n", orig, *hash_index, *label);
|
|
/* TODO: why's w_t_lookup_cancel setting T to 'undefined'?? */
|
|
UNREF(orig);
|
|
return 1;
|
|
}
|
|
#endif /* WITH_AS_SUPPORT */
|
|
|
|
|
|
|
|
/** lookup a transaction based on its identifier (hash_index:label).
|
|
* @param trans - double pointer to cell structure, that will be filled
|
|
* with the result (a pointer to an existing transaction or
|
|
* 0).
|
|
* @param hash_index - searched transaction hash_index (part of the ident).
|
|
* @param label - searched transaction label (part of the ident).
|
|
* @return -1 on error/not found, 1 on success (found)
|
|
* Side-effects: sets T and T_branch (T_branch always to T_BR_UNDEFINED).
|
|
*/
|
|
int t_lookup_ident(struct cell ** trans, unsigned int hash_index,
|
|
unsigned int label)
|
|
{
|
|
struct cell* p_cell;
|
|
struct entry* hash_bucket;
|
|
|
|
if(unlikely(hash_index >= TABLE_ENTRIES)){
|
|
LOG(L_ERR,"ERROR: t_lookup_ident: invalid hash_index=%u\n",hash_index);
|
|
return -1;
|
|
}
|
|
|
|
LOCK_HASH(hash_index);
|
|
|
|
#ifndef E2E_CANCEL_HOP_BY_HOP
|
|
#warning "t_lookup_ident() can only reliably match INVITE transactions in " \
|
|
"E2E_CANCEL_HOP_BY_HOP mode"
|
|
#endif
|
|
hash_bucket=&(get_tm_table()->entries[hash_index]);
|
|
/* all the transactions from the entry are compared */
|
|
clist_foreach(hash_bucket, p_cell, next_c){
|
|
prefetch_loc_r(p_cell->next_c, 1);
|
|
if(p_cell->label == label){
|
|
REF_UNSAFE(p_cell);
|
|
UNLOCK_HASH(hash_index);
|
|
set_t(p_cell, T_BR_UNDEFINED);
|
|
*trans=p_cell;
|
|
DBG("DEBUG: t_lookup_ident: transaction found\n");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
UNLOCK_HASH(hash_index);
|
|
set_t(0, T_BR_UNDEFINED);
|
|
*trans=p_cell;
|
|
|
|
DBG("DEBUG: t_lookup_ident: transaction not found\n");
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
/** check if a transaction is local or not.
|
|
* Check if the transaction corresponding to the current message
|
|
* is local or not.
|
|
* @param p_msg - pointer to sip_msg
|
|
* @return -1 on error, 0 if the transaction is not local, 1 if it is local.
|
|
* Side-effects: sets T and T_branch.
|
|
*/
|
|
int t_is_local(struct sip_msg* p_msg)
|
|
{
|
|
struct cell* t;
|
|
if(t_check(p_msg,0) != 1){
|
|
LOG(L_ERR,"ERROR: t_is_local: no transaction found\n");
|
|
return -1;
|
|
}
|
|
t = get_t();
|
|
if(!t){
|
|
LOG(L_ERR,"ERROR: t_is_local: transaction found is NULL\n");
|
|
return -1;
|
|
}
|
|
|
|
return is_local(t);
|
|
}
|
|
|
|
/** lookup a transaction based on callid and cseq.
|
|
* The parameters are pure header field content only,
|
|
* e.g. "123@10.0.0.1" and "11"
|
|
* @param trans - double pointer to the transaction, filled with a pointer
|
|
* to the found transaction on success and with 0 if the
|
|
* transaction was not found.
|
|
* @param callid - callid for the searched transaction.
|
|
* @param cseq - cseq for the searched transaction.
|
|
* @return -1 on error/not found, 1 if found.
|
|
* Side-effects: sets T and T_branch (T_branch always to T_BR_UNDEFINED).
|
|
*/
|
|
int t_lookup_callid(struct cell ** trans, str callid, str cseq) {
|
|
struct cell* p_cell;
|
|
unsigned hash_index;
|
|
struct entry* hash_bucket;
|
|
|
|
/* I use MAX_HEADER, not sure if this is a good choice... */
|
|
char callid_header[MAX_HEADER];
|
|
char cseq_header[MAX_HEADER];
|
|
/* save return value of print_* functions here */
|
|
char* endpos;
|
|
|
|
/* need method, which is always INVITE in our case */
|
|
/* CANCEL is only useful after INVITE */
|
|
str invite_method;
|
|
char* invite_string = INVITE;
|
|
|
|
invite_method.s = invite_string;
|
|
invite_method.len = INVITE_LEN;
|
|
|
|
/* lookup the hash index where the transaction is stored */
|
|
hash_index=hash(callid, cseq);
|
|
|
|
if(unlikely(hash_index >= TABLE_ENTRIES)){
|
|
LOG(L_ERR,"ERROR: t_lookup_callid: invalid hash_index=%u\n",hash_index);
|
|
return -1;
|
|
}
|
|
|
|
/* create header fields the same way tm does itself, then compare headers */
|
|
endpos = print_callid_mini(callid_header, callid);
|
|
DBG("created comparable call_id header field: >%.*s<\n",
|
|
(int)(endpos - callid_header), callid_header);
|
|
|
|
endpos = print_cseq_mini(cseq_header, &cseq, &invite_method);
|
|
DBG("created comparable cseq header field: >%.*s<\n",
|
|
(int)(endpos - cseq_header), cseq_header);
|
|
|
|
LOCK_HASH(hash_index);
|
|
DBG("just locked hash index %u, looking for transactions there:\n", hash_index);
|
|
|
|
hash_bucket=&(get_tm_table()->entries[hash_index]);
|
|
/* all the transactions from the entry are compared */
|
|
clist_foreach(hash_bucket, p_cell, next_c){
|
|
|
|
prefetch_loc_r(p_cell->next_c, 1);
|
|
/* compare complete header fields, casecmp to make sure invite=INVITE*/
|
|
if ((strncmp(callid_header, p_cell->callid.s, p_cell->callid.len) == 0)
|
|
&& (strncasecmp(cseq_header, p_cell->cseq_n.s, p_cell->cseq_n.len)
|
|
== 0)) {
|
|
DBG("we have a match: callid=>>%.*s<< cseq=>>%.*s<<\n",
|
|
p_cell->callid.len, p_cell->callid.s, p_cell->cseq_n.len,
|
|
p_cell->cseq_n.s);
|
|
REF_UNSAFE(p_cell);
|
|
UNLOCK_HASH(hash_index);
|
|
set_t(p_cell, T_BR_UNDEFINED);
|
|
*trans=p_cell;
|
|
DBG("DEBUG: t_lookup_callid: transaction found.\n");
|
|
return 1;
|
|
}
|
|
DBG("NO match: callid=%.*s cseq=%.*s\n", p_cell->callid.len,
|
|
p_cell->callid.s, p_cell->cseq_n.len, p_cell->cseq_n.s);
|
|
|
|
}
|
|
|
|
UNLOCK_HASH(hash_index);
|
|
DBG("DEBUG: t_lookup_callid: transaction not found.\n");
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
/* params: fr_inv & fr value in ms, 0 means "do not touch"
|
|
* ret: 1 on success, -1 on error (script safe)*/
|
|
int t_set_fr(struct sip_msg* msg, unsigned int fr_inv_to, unsigned int fr_to)
|
|
{
|
|
struct cell *t;
|
|
ticks_t fr_inv, fr;
|
|
|
|
|
|
fr_inv=MS_TO_TICKS((ticks_t)fr_inv_to);
|
|
if ((fr_inv==0) && (fr_inv_to!=0)){
|
|
ERR("t_set_fr_inv: fr_inv_timeout too small (%d)\n", fr_inv_to);
|
|
return -1;
|
|
}
|
|
fr=MS_TO_TICKS((ticks_t)fr_to);
|
|
if ((fr==0) && (fr_to!=0)){
|
|
ERR("t_set_fr_inv: fr_timeout too small (%d)\n", fr_to);
|
|
return -1;
|
|
}
|
|
|
|
t=get_t();
|
|
/* in REPLY_ROUTE and FAILURE_ROUTE T will be set to current transaction;
|
|
* in REQUEST_ROUTE T will be set only if the transaction was already
|
|
* created; if not -> use the static variables */
|
|
if (!t || t==T_UNDEFINED ){
|
|
set_msgid_val(user_fr_inv_timeout, msg->id, int, (int)fr_inv);
|
|
set_msgid_val(user_fr_timeout, msg->id, int, (int)fr);
|
|
}else{
|
|
change_fr(t, fr_inv, fr); /* change running uac timers */
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* reset fr_timer and fr_inv_timer to the default values */
|
|
int t_reset_fr()
|
|
{
|
|
struct cell *t;
|
|
|
|
t=get_t();
|
|
/* in REPLY_ROUTE and FAILURE_ROUTE T will be set to current transaction;
|
|
* in REQUEST_ROUTE T will be set only if the transaction was already
|
|
* created; if not -> use the static variables */
|
|
if (!t || t==T_UNDEFINED ){
|
|
memset(&user_fr_inv_timeout, 0, sizeof(user_fr_inv_timeout));
|
|
memset(&user_fr_timeout, 0, sizeof(user_fr_timeout));
|
|
}else{
|
|
change_fr(t,
|
|
cfg_get(tm, tm_cfg, fr_inv_timeout),
|
|
cfg_get(tm, tm_cfg, fr_timeout)); /* change running uac timers */
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#ifdef TM_DIFF_RT_TIMEOUT
|
|
|
|
/* params: retr. t1 & retr. t2 value in ms, 0 means "do not touch"
|
|
* ret: 1 on success, -1 on error (script safe)*/
|
|
int t_set_retr(struct sip_msg* msg, unsigned int t1_ms, unsigned int t2_ms)
|
|
{
|
|
struct cell *t;
|
|
ticks_t retr_t1, retr_t2;
|
|
|
|
|
|
retr_t1=MS_TO_TICKS((ticks_t)t1_ms);
|
|
if (unlikely((retr_t1==0) && (t1_ms!=0))){
|
|
ERR("t_set_retr: retr. t1 interval too small (%u)\n", t1_ms);
|
|
return -1;
|
|
}
|
|
if (unlikely(MAX_UVAR_VALUE(t->rt_t1_timeout_ms) < t1_ms)){
|
|
ERR("t_set_retr: retr. t1 interval too big: %d (max %lu)\n",
|
|
t1_ms, MAX_UVAR_VALUE(t->rt_t1_timeout_ms));
|
|
return -1;
|
|
}
|
|
retr_t2=MS_TO_TICKS((ticks_t)t2_ms);
|
|
if (unlikely((retr_t2==0) && (t2_ms!=0))){
|
|
ERR("t_set_retr: retr. t2 interval too small (%d)\n", t2_ms);
|
|
return -1;
|
|
}
|
|
if (unlikely(MAX_UVAR_VALUE(t->rt_t2_timeout_ms) < t2_ms)){
|
|
ERR("t_set_retr: retr. t2 interval too big: %u (max %lu)\n",
|
|
t2_ms, MAX_UVAR_VALUE(t->rt_t2_timeout_ms));
|
|
return -1;
|
|
}
|
|
|
|
t=get_t();
|
|
/* in REPLY_ROUTE and FAILURE_ROUTE T will be set to current transaction;
|
|
* in REQUEST_ROUTE T will be set only if the transaction was already
|
|
* created; if not -> use the static variables */
|
|
if (!t || t==T_UNDEFINED ){
|
|
set_msgid_val(user_rt_t1_timeout_ms, msg->id, int, (int)t1_ms);
|
|
set_msgid_val(user_rt_t2_timeout_ms, msg->id, int, (int)t2_ms);
|
|
}else{
|
|
change_retr(t, 1, t1_ms, t2_ms); /* change running uac timers */
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* reset retr. t1 and t2 to the default values */
|
|
int t_reset_retr()
|
|
{
|
|
struct cell *t;
|
|
|
|
t=get_t();
|
|
/* in REPLY_ROUTE and FAILURE_ROUTE T will be set to current transaction;
|
|
* in REQUEST_ROUTE T will be set only if the transaction was already
|
|
* created; if not -> use the static variables */
|
|
if (!t || t==T_UNDEFINED ){
|
|
memset(&user_rt_t1_timeout_ms, 0, sizeof(user_rt_t1_timeout_ms));
|
|
memset(&user_rt_t2_timeout_ms, 0, sizeof(user_rt_t2_timeout_ms));
|
|
}else{
|
|
/* change running uac timers */
|
|
change_retr(t,
|
|
1,
|
|
cfg_get(tm, tm_cfg, rt_t1_timeout_ms),
|
|
cfg_get(tm, tm_cfg, rt_t2_timeout_ms));
|
|
}
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
|
|
/* params: maximum transaction lifetime for inv and non-inv
|
|
* 0 means do not touch"
|
|
* ret: 1 on success, -1 on error (script safe)*/
|
|
int t_set_max_lifetime(struct sip_msg* msg,
|
|
unsigned int lifetime_inv_to,
|
|
unsigned int lifetime_noninv_to)
|
|
{
|
|
struct cell *t;
|
|
ticks_t max_inv_lifetime, max_noninv_lifetime;
|
|
|
|
|
|
max_noninv_lifetime=MS_TO_TICKS((ticks_t)lifetime_noninv_to);
|
|
max_inv_lifetime=MS_TO_TICKS((ticks_t)lifetime_inv_to);
|
|
if (unlikely((max_noninv_lifetime==0) && (lifetime_noninv_to!=0))){
|
|
ERR("t_set_max_lifetime: non-inv. interval too small (%d)\n",
|
|
lifetime_noninv_to);
|
|
return -1;
|
|
}
|
|
if (unlikely((max_inv_lifetime==0) && (lifetime_inv_to!=0))){
|
|
ERR("t_set_max_lifetime: inv. interval too small (%d)\n",
|
|
lifetime_inv_to);
|
|
return -1;
|
|
}
|
|
|
|
t=get_t();
|
|
/* in REPLY_ROUTE and FAILURE_ROUTE T will be set to current transaction;
|
|
* in REQUEST_ROUTE T will be set only if the transaction was already
|
|
* created; if not -> use the static variables */
|
|
if (!t || t==T_UNDEFINED ){
|
|
set_msgid_val(user_noninv_max_lifetime, msg->id, int,
|
|
(int)max_noninv_lifetime);
|
|
set_msgid_val(user_inv_max_lifetime, msg->id, int,
|
|
(int)max_inv_lifetime);
|
|
}else{
|
|
change_end_of_life(t, 1, is_invite(t)?max_inv_lifetime:
|
|
max_noninv_lifetime);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* reset maximum invite/non-invite lifetime to the default value */
|
|
int t_reset_max_lifetime()
|
|
{
|
|
struct cell *t;
|
|
|
|
t=get_t();
|
|
/* in REPLY_ROUTE and FAILURE_ROUTE T will be set to current transaction;
|
|
* in REQUEST_ROUTE T will be set only if the transaction was already
|
|
* created; if not -> use the static variables */
|
|
if (!t || t==T_UNDEFINED ){
|
|
memset(&user_inv_max_lifetime, 0, sizeof(user_inv_max_lifetime));
|
|
memset(&user_noninv_max_lifetime, 0, sizeof(user_noninv_max_lifetime));
|
|
}else{
|
|
change_end_of_life(t,
|
|
1,
|
|
is_invite(t)?
|
|
cfg_get(tm, tm_cfg, tm_max_inv_lifetime):
|
|
cfg_get(tm, tm_cfg, tm_max_noninv_lifetime)
|
|
);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#ifdef WITH_TM_CTX
|
|
|
|
tm_ctx_t _tm_ctx;
|
|
|
|
tm_ctx_t* tm_ctx_get(void)
|
|
{
|
|
return &_tm_ctx;
|
|
}
|
|
|
|
void tm_ctx_init(void)
|
|
{
|
|
memset(&_tm_ctx, 0, sizeof(tm_ctx_t));
|
|
}
|
|
|
|
void tm_ctx_set_branch_index(int v)
|
|
{
|
|
_tm_ctx.branch_index = v;
|
|
}
|
|
|
|
#endif
|
|
|