From 135890ac3cca662d419cb7bbb2f839a63abdda2f Mon Sep 17 00:00:00 2001 From: Stefan Sayer Date: Fri, 16 Aug 2013 16:32:30 +0200 Subject: [PATCH] sbc: fix call transfers with Replaces through SBC Replaces header is fixed in either Replaces of INVITE or of REFER messages. New sbc profile options: fix_replaces_inv=[yes|no] fix_replaces_ref=[yes|no] --- apps/sbc/ReplacesMapper.cpp | 171 +++++++++++++++++++++++ apps/sbc/ReplacesMapper.h | 34 +++++ apps/sbc/SBC.cpp | 70 ++++++++-- apps/sbc/SBCCallProfile.cpp | 6 + apps/sbc/SBCCallProfile.h | 3 + apps/sbc/SBCCallRegistry.cpp | 79 +++++++++++ apps/sbc/SBCCallRegistry.h | 61 ++++++++ apps/sbc/etc/transparent.sbcprofile.conf | 4 + core/AmSipHeaders.h | 4 + core/tests/Makefile | 29 +++- core/tests/sems_tests.cpp | 1 + core/tests/test_replaces.cpp | 103 ++++++++++++++ core/tests/test_replaces.h | 1 + core/tests/test_uriparser.cpp | 8 ++ doc/Readme.sbc.txt | 15 ++ 15 files changed, 569 insertions(+), 20 deletions(-) create mode 100644 apps/sbc/ReplacesMapper.cpp create mode 100644 apps/sbc/ReplacesMapper.h create mode 100644 apps/sbc/SBCCallRegistry.cpp create mode 100644 apps/sbc/SBCCallRegistry.h create mode 100644 core/tests/test_replaces.cpp create mode 100644 core/tests/test_replaces.h diff --git a/apps/sbc/ReplacesMapper.cpp b/apps/sbc/ReplacesMapper.cpp new file mode 100644 index 00000000..d00e265b --- /dev/null +++ b/apps/sbc/ReplacesMapper.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2013 Stefan Sayer + * + * This file is part of SEMS, a free SIP media server. + * + * SEMS 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 SEMS 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 + * + * SEMS 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 + */ + +#include "ReplacesMapper.h" +#include "AmUtils.h" +#include "AmUriParser.h" +#include "AmSipHeaders.h" + +bool findTag(const string replaces, const string& tag, size_t& p1, size_t& len); + +void fixReplaces(AmSipRequest& req, bool is_invite) { + + string replaces; + string refer_to; + AmUriParser refer_target; + vector hdrs; // headers from Refer-To URI + vector::iterator replaces_hdr_it; // Replaces header from Refer-To URI + + DBG("Replaces handler: fixing %s request\n", is_invite?"INVITE":"REFER"); + + if (is_invite) { + replaces = getHeader(req.hdrs, SIP_HDR_REPLACES, true); + if (replaces.empty()) { + DBG("Replaces handler: no Replaces in INVITE, ignoring\n"); + return; + } + } else { + refer_to = getHeader(req.hdrs, SIP_HDR_REFER_TO, SIP_HDR_REFER_TO_COMPACT, true); + if (refer_to.empty()) { + DBG("Replaces handler: empty Refer-To header, ignoring\n"); + return; + } + + size_t pos=0; size_t end=0; + if (!refer_target.parse_contact(refer_to, pos, end)) { + DBG("Replaces handler: unable to parse Refer-To name-addr, ignoring\n"); + return; + } + + if (refer_target.uri_headers.empty()) { + DBG("Replaces handler: no headers in Refer-To target, ignoring\n"); + return; + } + + hdrs = explode(refer_target.uri_headers, ";"); + for (replaces_hdr_it=hdrs.begin(); replaces_hdr_it != hdrs.end(); replaces_hdr_it++) { + + string s = URL_decode(*replaces_hdr_it); + const char* Replaces_str = "Replaces"; + if ((s.length() >= 8) && + !strncmp(Replaces_str, s.c_str(), 8)) { + size_t pos = 8; + while (s.length()>pos && (s[pos] == ' ' || s[pos] == '\t')) pos++; + if (s[pos] != '=') + continue; + pos++; + while (s.length()>pos && (s[pos] == ' ' || s[pos] == '\t')) pos++; + replaces = s.substr(pos); + break; + } + } + + if (replaces_hdr_it == hdrs.end()) { + DBG("Replaces handler: no Replaces headers in Refer-To target, ignoring\n"); + return; + } + } + + DBG("Replaces found: '%s'\n", replaces.c_str()); + size_t ftag_begin; size_t ftag_len; + size_t ttag_begin; size_t ttag_len; + size_t cid_len=0; + + // todo: parse full replaces header and reconstruct including unknown params + if (!findTag(replaces, "from-tag=", ftag_begin, ftag_len)) { + WARN("Replaces missing 'from-tag', ignoring\n"); + return; + } + + if (!findTag(replaces, "to-tag=", ttag_begin, ttag_len)) { + WARN("Replaces missing 'to-tag', ignoring\n"); + return; + } + while (cid_len < replaces.size() && replaces[cid_len] != ';') + cid_len++; + + string ftag = replaces.substr(ftag_begin, ftag_len); + string ttag = replaces.substr(ttag_begin, ttag_len); + string callid = replaces.substr(0, cid_len); + bool early_only = replaces.find("early-only") != string::npos; + + DBG("Replaces handler: found callid='%s', ftag='%s', ttag='%s'\n", + callid.c_str(), ftag.c_str(), ttag.c_str()); + + SBCCallRegistryEntry other_dlg; + if (SBCCallRegistry::lookupCall(ttag, other_dlg)) { + replaces = other_dlg.callid+ + ";from-tag="+other_dlg.ltag+";to-tag="+other_dlg.rtag; + if (early_only) + replaces += ";early_only"; + DBG("Replaces handler: mapped Replaces to: '%s'\n", replaces.c_str()); + + if (is_invite) { + removeHeader(req.hdrs, SIP_HDR_REPLACES); + req.hdrs+=SIP_HDR_COLSP(SIP_HDR_REPLACES)+replaces+CRLF; + } else { + string replaces_enc = SIP_HDR_REPLACES "="+URL_encode(replaces); + string new_hdrs; + for (vector::iterator it = hdrs.begin(); it != hdrs.end(); it++) { + if (it != hdrs.begin()) + new_hdrs+=";"; + + if (it != replaces_hdr_it) { + // different hdr, just add it + new_hdrs+=*it; + } else { + //reconstructed replaces hdr + new_hdrs+=replaces_enc; + } + } + refer_target.uri_headers=new_hdrs; + removeHeader(req.hdrs, SIP_HDR_REFER_TO); + removeHeader(req.hdrs, SIP_HDR_REFER_TO_COMPACT); + req.hdrs+=SIP_HDR_COLSP(SIP_HDR_REFER_TO)+refer_target.nameaddr_str()+CRLF; + } + + } else { + DBG("Replaces handler: call with tag '%s' not found\n", ttag.c_str()); + } + + +} + +bool findTag(const string replaces, const string& tag, size_t& p1, size_t& len) +{ + size_t i = replaces.find(tag); + if (i == string::npos) return false; + + p1 = i+tag.length(); + size_t j = replaces.find(';', p1); + + if (j != string::npos) { + len = j - p1; + } else { + len = replaces.size() - i; + } + return true; +} + diff --git a/apps/sbc/ReplacesMapper.h b/apps/sbc/ReplacesMapper.h new file mode 100644 index 00000000..7b1c46fe --- /dev/null +++ b/apps/sbc/ReplacesMapper.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2013 Stefan Sayer + * + * This file is part of SEMS, a free SIP media server. + * + * SEMS 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 SEMS 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 + * + * SEMS 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 + */ + +#ifndef _ReplacesMapper_H +#define _ReplacesMapper_H + +#include "SBCCallRegistry.h" +#include "AmSipMsg.h" + +void fixReplaces(AmSipRequest& req, bool is_invite); + +#endif diff --git a/apps/sbc/SBC.cpp b/apps/sbc/SBC.cpp index d6245a61..2675156d 100644 --- a/apps/sbc/SBC.cpp +++ b/apps/sbc/SBC.cpp @@ -47,6 +47,8 @@ SBC - feature-wishlist #include "HeaderFilter.h" #include "ParamReplacer.h" #include "SDPFilter.h" +#include "SBCCallRegistry.h" +#include "ReplacesMapper.h" using std::map; @@ -513,6 +515,7 @@ SBCDialog::SBCDialog(const SBCCallProfile& call_profile) SBCDialog::~SBCDialog() { + SBCCallRegistry::removeCall(dlg.local_tag); } void SBCDialog::onInvite(const AmSipRequest& req) @@ -718,6 +721,15 @@ void SBCDialog::onInvite(const AmSipRequest& req) } } + call_profile.fix_replaces_inv = + replaceParameters(call_profile.fix_replaces_inv, "fix_replaces_inv", REPLACE_VALS); + call_profile.fix_replaces_ref = + replaceParameters(call_profile.fix_replaces_ref, "fix_replaces_ref", REPLACE_VALS); + + if (call_profile.fix_replaces_inv == "yes") { + fixReplaces(invite_req, true); + } + #undef REPLACE_VALS DBG("SBC: connecting to '%s'\n",ruri.c_str()); @@ -786,13 +798,21 @@ void SBCDialog::onControlCmd(string& cmd, AmArg& params) { } int SBCDialog::relayEvent(AmEvent* ev) { - if ((call_profile.headerfilter != Transparent) && - (ev->event_id == B2BSipRequest)) { - // header filter + if (ev->event_id == B2BSipRequest) { + B2BSipRequestEvent* req_ev = dynamic_cast(ev); - assert(req_ev); - inplaceHeaderFilter(req_ev->req.hdrs, - call_profile.headerfilter_list, call_profile.headerfilter); + assert(req_ev); + + if (call_profile.headerfilter != Transparent) { + inplaceHeaderFilter(req_ev->req.hdrs, + call_profile.headerfilter_list, call_profile.headerfilter); + } + + if (req_ev->req.method == SIP_METH_REFER && + call_profile.fix_replaces_ref == "yes") { + fixReplaces(req_ev->req, false); + } + } else { if (ev->event_id == B2BSipReply) { if ((call_profile.headerfilter != Transparent) || @@ -1120,6 +1140,11 @@ void SBCDialog::createCalleeSession() throw; } + // A->B + SBCCallRegistry::addCall(dlg.local_tag, SBCCallRegistryEntry(callee_dlg.callid, callee_dlg.local_tag, "")); + // B->A + SBCCallRegistry::addCall(callee_dlg.local_tag, SBCCallRegistryEntry(dlg.callid, dlg.local_tag, dlg.remote_tag)); + callee_session->start(); AmSessionContainer* sess_cont = AmSessionContainer::instance(); @@ -1143,6 +1168,8 @@ SBCCalleeSession::SBCCalleeSession(const AmB2BCallerSession* caller, SBCCalleeSession::~SBCCalleeSession() { if (auth) delete auth; + + SBCCallRegistry::removeCall(dlg.local_tag); } inline UACAuthCred* SBCCalleeSession::getCredentials() { @@ -1150,13 +1177,21 @@ inline UACAuthCred* SBCCalleeSession::getCredentials() { } int SBCCalleeSession::relayEvent(AmEvent* ev) { - if ((call_profile.headerfilter != Transparent) && - (ev->event_id == B2BSipRequest)) { - // header filter + if (ev->event_id == B2BSipRequest) { + B2BSipRequestEvent* req_ev = dynamic_cast(ev); - assert(req_ev); - inplaceHeaderFilter(req_ev->req.hdrs, - call_profile.headerfilter_list, call_profile.headerfilter); + assert(req_ev); + + if (call_profile.headerfilter != Transparent) { + inplaceHeaderFilter(req_ev->req.hdrs, + call_profile.headerfilter_list, call_profile.headerfilter); + } + + if (req_ev->req.method == SIP_METH_REFER && + call_profile.fix_replaces_ref == "yes") { + fixReplaces(req_ev->req, false); + } + } else { if (ev->event_id == B2BSipReply) { if ((call_profile.headerfilter != Transparent) || @@ -1232,18 +1267,25 @@ void SBCCalleeSession::onSipReply(const AmSipReply& reply, int old_dlg_status, bool fwd = t != relayed_req.end(); DBG("onSipReply: %i %s (fwd=%i)\n",reply.code,reply.reason.c_str(),fwd); DBG("onSipReply: content-type = %s\n",reply.content_type.c_str()); + if(fwd) { CALL_EVENT_H(onSipReply,reply, old_dlg_status, trans_method); } - if (NULL == auth) { + // update call registry (unfortunately has to be done always - + // not possible to determine if learned in this reply) + if (!dlg.remote_tag.empty()) { + SBCCallRegistry::updateCall(other_id, dlg.remote_tag); + } + + if (NULL == auth) { AmB2BCalleeSession::onSipReply(reply,old_dlg_status, trans_method); return; } unsigned int cseq_before = dlg.cseq; if (!auth->onSipReply(reply, old_dlg_status, trans_method)) { - AmB2BCalleeSession::onSipReply(reply, old_dlg_status, trans_method); + AmB2BCalleeSession::onSipReply(reply, old_dlg_status, trans_method); } else { if (cseq_before != dlg.cseq) { DBG("uac_auth consumed reply with cseq %d and resent with cseq %d; " diff --git a/apps/sbc/SBCCallProfile.cpp b/apps/sbc/SBCCallProfile.cpp index b727e820..37b5c183 100644 --- a/apps/sbc/SBCCallProfile.cpp +++ b/apps/sbc/SBCCallProfile.cpp @@ -108,6 +108,9 @@ bool SBCCallProfile::readFromConfiguration(const string& name, } } + fix_replaces_inv = cfg.getParameter("fix_replaces_inv"); + fix_replaces_ref = cfg.getParameter("fix_replaces_ref");; + sst_enabled = cfg.getParameter("enable_session_timer", "no") == "yes"; use_global_sst_config = !cfg.hasParameter("session_expires"); @@ -215,6 +218,9 @@ bool SBCCallProfile::readFromConfiguration(const string& name, sdpfilter_enabled?"en":"dis", FilterType2String(sdpfilter), sdpfilter_list.size()); + INFO("SBC: fixing Replaces in INVITE: '%s'\n", fix_replaces_inv.c_str()); + INFO("SBC: fixing Replaces in REFER: '%s'\n", fix_replaces_ref.c_str()); + INFO("SBC: RTP relay %sabled\n", rtprelay_enabled?"en":"dis"); if (rtprelay_enabled) { if (!force_symmetric_rtp.empty()) { diff --git a/apps/sbc/SBCCallProfile.h b/apps/sbc/SBCCallProfile.h index bec361c6..23c18825 100644 --- a/apps/sbc/SBCCallProfile.h +++ b/apps/sbc/SBCCallProfile.h @@ -68,6 +68,9 @@ struct SBCCallProfile { FilterType sdpfilter; set sdpfilter_list; + string fix_replaces_inv; + string fix_replaces_ref; + bool sst_enabled; bool use_global_sst_config; diff --git a/apps/sbc/SBCCallRegistry.cpp b/apps/sbc/SBCCallRegistry.cpp new file mode 100644 index 00000000..c0677c43 --- /dev/null +++ b/apps/sbc/SBCCallRegistry.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2013 Stefan Sayer + * + * This file is part of SEMS, a free SIP media server. + * + * SEMS 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 SEMS 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 + * + * SEMS 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 + */ + +#include "SBCCallRegistry.h" +#include "log.h" + +AmMutex SBCCallRegistry::registry_mutex; +std::map SBCCallRegistry::registry; + +void SBCCallRegistry::addCall(const string& ltag, const SBCCallRegistryEntry& other_dlg) { + registry_mutex.lock(); + registry[ltag] = other_dlg; + registry_mutex.unlock(); + + DBG("SBCCallRegistry: Added call '%s' - mapped to: '%s'\n", ltag.c_str(), other_dlg.ltag.c_str()); +} + +void SBCCallRegistry::updateCall(const string& ltag, const string& other_rtag) { + registry_mutex.lock(); + + std::map::iterator it = registry.find(ltag); + if (it != registry.end()) { + it->second.rtag = other_rtag; + } + + registry_mutex.unlock(); + + DBG("SBCCallRegistry: Updated call '%s' - rtag to: '%s'\n", ltag.c_str(), other_rtag.c_str()); +} + +bool SBCCallRegistry::lookupCall(const string& ltag, SBCCallRegistryEntry& other_dlg) { + bool res = false; + + registry_mutex.lock(); + std::map::iterator it = registry.find(ltag); + if (it != registry.end()) { + res = true; + other_dlg = it->second; + } + registry_mutex.unlock(); + + if (res) { + DBG("SBCCallRegistry: found call mapping '%s' -> '%s'/'%s'/'%s'\n", + ltag.c_str(), other_dlg.ltag.c_str(), other_dlg.rtag.c_str(), other_dlg.callid.c_str()); + } else { + DBG("SBCCallRegistry: no call mapping found for '%s'\n", ltag.c_str()); + } + return res; +} + +void SBCCallRegistry::removeCall(const string& ltag) { + registry_mutex.lock(); + registry.erase(ltag); + registry_mutex.unlock(); + + DBG("SBCCallRegistry: removed entry for call '%s'\n", ltag.c_str()); +} diff --git a/apps/sbc/SBCCallRegistry.h b/apps/sbc/SBCCallRegistry.h new file mode 100644 index 00000000..be8f1139 --- /dev/null +++ b/apps/sbc/SBCCallRegistry.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2013 Stefan Sayer + * + * This file is part of SEMS, a free SIP media server. + * + * SEMS 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 SEMS 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 + * + * SEMS 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 + */ + +#ifndef _SBCCallRegistry_H +#define _SBCCallRegistry_H + +#include "AmThread.h" + +#include +using std::string; +#include + +struct SBCCallRegistryEntry +{ + string ltag; + string rtag; + string callid; + + SBCCallRegistryEntry() { } +SBCCallRegistryEntry(const string& callid, const string& ltag, const string& rtag) + : ltag(ltag), rtag(rtag), callid(callid) { } +}; + +class SBCCallRegistry +{ + static AmMutex registry_mutex; + static std::map registry; + + public: + SBCCallRegistry() { } + ~SBCCallRegistry() { } + + static void addCall(const string& ltag, const SBCCallRegistryEntry& other_dlg); + static void updateCall(const string& ltag, const string& other_rtag); + static bool lookupCall(const string& ltag, SBCCallRegistryEntry& other_dlg); + static void removeCall(const string& ltag); +}; + +#endif diff --git a/apps/sbc/etc/transparent.sbcprofile.conf b/apps/sbc/etc/transparent.sbcprofile.conf index 3356b07f..6c092e17 100644 --- a/apps/sbc/etc/transparent.sbcprofile.conf +++ b/apps/sbc/etc/transparent.sbcprofile.conf @@ -46,6 +46,10 @@ # translate some 6xx class replies to 4xx class: #reply_translations="603=>488 Not acceptable here|600=>406 Not Acceptable" +## fix replaces for call transfers +# fix_replaces_inv=yes +# fix_replaces_ref=yes + ## authentication: #enable_auth=yes #auth_user=$P(u) diff --git a/core/AmSipHeaders.h b/core/AmSipHeaders.h index 911a3a44..13e09fe2 100644 --- a/core/AmSipHeaders.h +++ b/core/AmSipHeaders.h @@ -11,6 +11,8 @@ #define SIP_METH_ACK "ACK" #define SIP_METH_SUBSCRIBE "SUBSCRIBE" #define SIP_METH_NOTIFY "NOTIFY" +#define SIP_METH_CANCEL "CANCEL" +#define SIP_METH_REFER "REFER" #define SIP_HDR_FROM "From" #define SIP_HDR_TO "To" @@ -26,6 +28,7 @@ #define SIP_HDR_P_ASSERTED_IDENTITY "P-Asserted-Identity" #define SIP_HDR_P_PREFERRED_IDENTITY "P-Preferred-Identity" #define SIP_HDR_REFER_TO "Refer-To" +#define SIP_HDR_REFER_TO_COMPACT "r" #define SIP_HDR_EXPIRES "Expires" #define SIP_HDR_SESSION_EXPIRES "Session-Expires" #define SIP_HDR_MIN_SE "Min-SE" @@ -40,6 +43,7 @@ #define SIP_HDR_ACCEPT "Accept" #define SIP_HDR_EVENT "Event" #define SIP_HDR_SUBSCRIPTION_STATE "Subscription-State" +#define SIP_HDR_REPLACES "Replaces" #define SIP_HDR_COL(_hdr) _hdr ":" #define SIP_HDR_COLSP(_hdr) SIP_HDR_COL(_hdr) " " diff --git a/core/tests/Makefile b/core/tests/Makefile index 5b79a9d7..d89844fe 100644 --- a/core/tests/Makefile +++ b/core/tests/Makefile @@ -2,11 +2,20 @@ NAME=sems_tests SIP_STACK_DIR=../sip SIP_STACK=$(SIP_STACK_DIR)/sip_stack.a -CORE_SRCS=$(filter-out ../sems.cpp , $(wildcard ../*.cpp)) +CORE_SRCS=$(filter-out ../sems.cpp , $(wildcard ../*.cpp)) CORE_HDRS=$(CORE_SRCS:.cpp=.h) CORE_OBJS=$(CORE_SRCS:.cpp=.o) CORE_DEPS=$(subst ../,,$(CORE_SRCS:.cpp=.d)) +SBC_DIR=../../apps/sbc/ +SBC_SRCS=$(wildcard $(SBC_DIR)*.cpp) +SBC_HDRS=$(SBC_SRCS:.cpp=.h) +SBC_OBJS=$(SBC_SRCS:.cpp=.o) +SBC_DEPS=$(subst $(SBC_DIR),,$(SBC_SRCS:.cpp=.d)) + +AUTH_DIR=../plug-in/uac_auth +AUTH_OBJS=$(AUTH_DIR)/UACAuth.o + SRCS=$(wildcard *.cpp) HDRS=$(SRCS:.cpp=.h) OBJS=$(SRCS:.cpp=.o) @@ -18,7 +27,7 @@ EXTRA_LDFLAGS += -lresolv .PHONY: all all: ../../Makefile.defs - -@$(MAKE) core_deps && $(MAKE) deps && \ + -@$(MAKE) core_deps && $(MAKE) sbc_deps && $(MAKE) deps && \ $(MAKE) $(NAME) && \ ./$(NAME) @@ -40,6 +49,12 @@ deps: $(DEPS) .PHONY: core_deps core_deps: $(CORE_DEPS) +.PHONY: sbc_deps +sbc_deps: $(SBC_DEPS) + +AUTH_OBJS: $(AUTH_DIR)/UACAuth.cpp $(AUTH_DIR)/UACAuth.h + cd $(AUTH_DIR) ; $(MAKE) AUTH_OBJS + COREPATH=.. include ../../Makefile.defs @@ -50,17 +65,19 @@ include ../../Makefile.defs %.d : %.cpp %.h ../../Makefile.defs $(CXX) -MM $< $(CPPFLAGS) $(CXXFLAGS) > $@ +%.d : $(SBC_DIR)%.cpp $(SBC_DIR)%.h ../../Makefile.defs + $(CXX) -MM $< $(CPPFLAGS) $(CXXFLAGS) > $@ + %.d : ../%.cpp ../%.h ../../Makefile.defs $(CXX) -MM $< $(CPPFLAGS) $(CXXFLAGS) > $@ -$(NAME): $(OBJS) $(CORE_OBJS) $(SIP_STACK) ../../Makefile.defs +$(NAME): $(OBJS) $(CORE_OBJS) $(SBC_OBJS) $(SIP_STACK) ../../Makefile.defs -@echo "" -@echo "making $(NAME)" - $(LD) -o $(NAME) $(OBJS) $(CORE_OBJS) $(SIP_STACK) $(LDFLAGS) $(EXTRA_LDFLAGS) - + $(LD) -o $(NAME) $(OBJS) $(CORE_OBJS) $(SBC_OBJS) $(SIP_STACK) $(LDFLAGS) $(EXTRA_LDFLAGS) $(AUTH_OBJS) ifeq '$(NAME)' '$(MAKECMDGOALS)' -include $(DEPS) $(CORE_DEPS) +include $(DEPS) $(CORE_DEPS) $(SBC_DEPS) endif diff --git a/core/tests/sems_tests.cpp b/core/tests/sems_tests.cpp index 274f300a..77f83982 100644 --- a/core/tests/sems_tests.cpp +++ b/core/tests/sems_tests.cpp @@ -24,6 +24,7 @@ FCT_BGN() { FCTMF_SUITE_CALL(test_headers); FCTMF_SUITE_CALL(test_jsonarg); FCTMF_SUITE_CALL(test_uriparser); + FCTMF_SUITE_CALL(test_replaces); } FCT_END(); diff --git a/core/tests/test_replaces.cpp b/core/tests/test_replaces.cpp new file mode 100644 index 00000000..cbe7ad25 --- /dev/null +++ b/core/tests/test_replaces.cpp @@ -0,0 +1,103 @@ +#include "fct.h" + +#include "log.h" + +#include "AmSipHeaders.h" +#include "AmSipMsg.h" +#include "AmUtils.h" +#include "AmUriParser.h" + +#include "../../apps/sbc/ReplacesMapper.h" +#include "../../apps/sbc/SBCCallRegistry.h" + + +FCTMF_SUITE_BGN(test_replaces) { + + FCT_TEST_BGN(registry_simple) { + SBCCallRegistryEntry e = SBCCallRegistryEntry("callid2", "ltag2", "rtag2"); + SBCCallRegistry::addCall("ltag", e); + fct_chk(SBCCallRegistry::lookupCall("ltag",e)); + fct_chk(!SBCCallRegistry::lookupCall("ltag3",e)); + SBCCallRegistry::removeCall("ltag"); + } FCT_TEST_END(); + + FCT_TEST_BGN(replaces_fixup_invite) { + SBCCallRegistryEntry e = SBCCallRegistryEntry("C2", "C2f", "C2t"); + SBCCallRegistry::addCall("Ct", e); + SBCCallRegistryEntry e2 = SBCCallRegistryEntry("C", "Ct", "Cf"); + SBCCallRegistry::addCall("C2f", e2); + + AmSipRequest r; + r.hdrs="Replaces: C;from-tag=Cf;to-tag=Ct\r\n"; + fixReplaces(r, true); + DBG("r.hdrs='%s'\n", r.hdrs.c_str()); + fct_chk(r.hdrs=="Replaces: C2;from-tag=C2f;to-tag=C2t\r\n"); + + SBCCallRegistry::removeCall("Ct"); + SBCCallRegistry::removeCall("C2f"); + } FCT_TEST_END(); + + FCT_TEST_BGN(replaces_fixup_refer) { + SBCCallRegistryEntry e = SBCCallRegistryEntry("C2", "C2f", "C2t"); + SBCCallRegistry::addCall("Ct", e); + SBCCallRegistryEntry e2 = SBCCallRegistryEntry("C", "Ct", "Cf"); + SBCCallRegistry::addCall("C2f", e2); + + AmSipRequest r; + string orig_str = "Refer-To: \"Mr. Watson\" ;q=0.1"; + string new_str = "Refer-To: \"Mr. Watson\" ;q=0.1\r\n"; + + r.hdrs=orig_str+"\r\n"; + fixReplaces(r, false); + DBG("r.hdrs='%s'\n", r.hdrs.c_str()); + DBG("new s='%s'\n", new_str.c_str()); + + fct_chk(r.hdrs==new_str); + + SBCCallRegistry::removeCall("Ct"); + SBCCallRegistry::removeCall("C2f"); + } FCT_TEST_END(); + + FCT_TEST_BGN(replaces_fixup_refer2) { + SBCCallRegistryEntry e = SBCCallRegistryEntry("C2", "C2f", "C2t"); + SBCCallRegistry::addCall("Ct", e); + SBCCallRegistryEntry e2 = SBCCallRegistryEntry("C", "Ct", "Cf"); + SBCCallRegistry::addCall("C2f", e2); + + AmSipRequest r; + string orig_str = "Refer-To: \"Mr. Watson\" ;q=0.1\r\n"; + string new_str = "Refer-To: \"Mr. Watson\" ;q=0.1\r\n"; + + r.hdrs=orig_str; + fixReplaces(r, false); + DBG("r.hdrs='%s'\n", r.hdrs.c_str()); + DBG("new s='%s'\n", new_str.c_str()); + + fct_chk(r.hdrs==new_str); + + SBCCallRegistry::removeCall("Ct"); + SBCCallRegistry::removeCall("C2f"); + } FCT_TEST_END(); + + FCT_TEST_BGN(replaces_fixup_refer3) { + SBCCallRegistryEntry e = SBCCallRegistryEntry("C2", "C2f", "C2t"); + SBCCallRegistry::addCall("Ct", e); + SBCCallRegistryEntry e2 = SBCCallRegistryEntry("C", "Ct", "Cf"); + SBCCallRegistry::addCall("C2f", e2); + + AmSipRequest r; + string orig_str = "Refer-To: \"Mr. Watson\" ;q=0.1\r\n"; + string new_str = "Refer-To: \"Mr. Watson\" ;q=0.1\r\n"; + + r.hdrs=orig_str; + fixReplaces(r, false); + DBG("r.hdrs='%s'\n", r.hdrs.c_str()); + DBG("new s='%s'\n", new_str.c_str()); + + fct_chk(r.hdrs==new_str); + + SBCCallRegistry::removeCall("Ct"); + SBCCallRegistry::removeCall("C2f"); + } FCT_TEST_END(); + +} FCTMF_SUITE_END(); diff --git a/core/tests/test_replaces.h b/core/tests/test_replaces.h new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/core/tests/test_replaces.h @@ -0,0 +1 @@ + diff --git a/core/tests/test_uriparser.cpp b/core/tests/test_uriparser.cpp index de6902f7..3bdc7275 100644 --- a/core/tests/test_uriparser.cpp +++ b/core/tests/test_uriparser.cpp @@ -123,5 +123,13 @@ FCTMF_SUITE_BGN(test_uriparser) { fct_chk(orig_str == a_str); } FCT_TEST_END(); + FCT_TEST_BGN(uriparser_url_escape) { + string src = "Replaces: CSADFSD;from-tag=31241231abc;to-tag=235123"; + string dst = "Replaces%3A%20CSADFSD%3Bfrom-tag%3D31241231abc%3Bto-tag%3D235123"; + fct_chk ( URL_decode(dst)==src ); + fct_chk ( URL_encode(src)==dst ); + fct_chk ( URL_decode(URL_encode(src))==src ); + + } FCT_TEST_END(); } FCTMF_SUITE_END(); diff --git a/doc/Readme.sbc.txt b/doc/Readme.sbc.txt index 49017291..994d6ae9 100644 --- a/doc/Readme.sbc.txt +++ b/doc/Readme.sbc.txt @@ -22,6 +22,7 @@ Features o reply code translation o SIP authentication o SIP Session Timers + o Fixing Call Transfers (Replaces in REFER target and INVITE) o call timer o prepaid accounting o CDR generation @@ -343,6 +344,20 @@ Warning: Changing response codes, especially between different response code classes, can seriously mess up everything. Use with caution and only if you know what you are doing! +Fixing Call Transfers (Replaces in REFER target and INVITE) +----------------------------------------------------------- +Using the profile options fix_replaces_inv and fix_replaces_ref Replaces +can be fixed for call transfers going through the SBC. + + fix_replaces_inv=yes + fix_replaces_ref=yes + +For situations where the call transfer is handled by the UAs (phone handles +the REFER), the Replaces should be fixed in the INVITE message (fix_replaces_inv=yes). + +For situations where a PBX handles the call transfer (handles the REFER), +the Replaces should be fixed in the REFER message (fix_replaces_ref=yes). + Reliable 1xx (PRACK) --------------------