mirror of https://github.com/sipwise/sems.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.
1940 lines
61 KiB
1940 lines
61 KiB
/*
|
|
* Copyright (C) 2010-2013 Stefan Sayer
|
|
* Copyright (C) 2012-2013 FRAFOS GmbH
|
|
*
|
|
* 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 "SBCCallLeg.h"
|
|
|
|
#include "SBCCallControlAPI.h"
|
|
|
|
#include "log.h"
|
|
#include "AmUtils.h"
|
|
#include "AmAudio.h"
|
|
#include "AmPlugIn.h"
|
|
#include "AmMediaProcessor.h"
|
|
#include "AmConfigReader.h"
|
|
#include "AmSessionContainer.h"
|
|
#include "AmSipHeaders.h"
|
|
#include "SBCSimpleRelay.h"
|
|
#include "RegisterDialog.h"
|
|
#include "SubscriptionDialog.h"
|
|
|
|
#include "sip/pcap_logger.h"
|
|
#include "sip/sip_parser.h"
|
|
#include "sip/sip_trans.h"
|
|
|
|
#include "HeaderFilter.h"
|
|
#include "ReplacesMapper.h"
|
|
#include "ParamReplacer.h"
|
|
#include "SDPFilter.h"
|
|
#include "SBCEventLog.h"
|
|
#include "SBC.h"
|
|
|
|
#include <algorithm>
|
|
|
|
using namespace std;
|
|
|
|
#define TRACE DBG
|
|
|
|
// helper functions
|
|
|
|
static const SdpPayload *findPayload(const std::vector<SdpPayload>& payloads, const SdpPayload &payload, int transport)
|
|
{
|
|
string pname = payload.encoding_name;
|
|
transform(pname.begin(), pname.end(), pname.begin(), ::tolower);
|
|
|
|
for (vector<SdpPayload>::const_iterator p = payloads.begin(); p != payloads.end(); ++p) {
|
|
// fix for clients using non-standard names for static payload type (SPA504g: G729a)
|
|
if (transport == TP_RTPAVP && payload.payload_type < 20) {
|
|
if (payload.payload_type != p->payload_type) continue;
|
|
}
|
|
else {
|
|
string s = p->encoding_name;
|
|
transform(s.begin(), s.end(), s.begin(), ::tolower);
|
|
if (s != pname) continue;
|
|
}
|
|
|
|
if (p->clock_rate != payload.clock_rate) continue;
|
|
if ((p->encoding_param >= 0) && (payload.encoding_param >= 0) &&
|
|
(p->encoding_param != payload.encoding_param)) continue;
|
|
return &(*p);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool containsPayload(const std::vector<SdpPayload>& payloads, const SdpPayload &payload, int transport)
|
|
{
|
|
return findPayload(payloads, payload, transport) != NULL;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// map stream index and transcoder payload index (two dimensions) into one under
|
|
// presumption that there will be less than 128 payloads for transcoding
|
|
// (might be handy to remember mapping only for dynamic ones (96-127)
|
|
#define MAP_INDEXES(stream_idx, payload_idx) ((stream_idx) * 128 + payload_idx)
|
|
|
|
void PayloadIdMapping::map(int stream_index, int payload_index, int payload_id)
|
|
{
|
|
mapping[MAP_INDEXES(stream_index, payload_index)] = payload_id;
|
|
}
|
|
|
|
int PayloadIdMapping::get(int stream_index, int payload_index)
|
|
{
|
|
std::map<int, int>::iterator i = mapping.find(MAP_INDEXES(stream_index, payload_index));
|
|
if (i != mapping.end()) return i->second;
|
|
else return -1;
|
|
}
|
|
|
|
void PayloadIdMapping::reset()
|
|
{
|
|
mapping.clear();
|
|
}
|
|
|
|
#undef MAP_INDEXES
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
// A leg constructor (from SBCDialog)
|
|
SBCCallLeg::SBCCallLeg(const SBCCallProfile& call_profile, AmSipDialog* p_dlg,
|
|
AmSipSubscription* p_subs)
|
|
: CallLeg(p_dlg,p_subs),
|
|
m_state(BB_Init),
|
|
auth(NULL), auth_di(NULL),
|
|
call_profile(call_profile),
|
|
cc_timer_id(SBC_TIMER_ID_CALL_TIMERS_START),
|
|
ext_cc_timer_id(SBC_TIMER_ID_CALL_TIMERS_END + 1),
|
|
cc_started(false),
|
|
logger(NULL)
|
|
{
|
|
#ifdef WITH_ZRTP
|
|
enable_zrtp = false;
|
|
#endif
|
|
set_sip_relay_only(false);
|
|
dlg->setRel100State(Am100rel::REL100_IGNORED);
|
|
|
|
memset(&call_start_ts, 0, sizeof(struct timeval));
|
|
memset(&call_connect_ts, 0, sizeof(struct timeval));
|
|
memset(&call_end_ts, 0, sizeof(struct timeval));
|
|
|
|
if(call_profile.rtprelay_bw_limit_rate > 0 &&
|
|
call_profile.rtprelay_bw_limit_peak > 0) {
|
|
|
|
RateLimit* limit = new RateLimit(call_profile.rtprelay_bw_limit_rate,
|
|
call_profile.rtprelay_bw_limit_peak,
|
|
1000);
|
|
rtp_relay_rate_limit.reset(limit);
|
|
}
|
|
}
|
|
|
|
// B leg constructor (from SBCCalleeSession)
|
|
SBCCallLeg::SBCCallLeg(SBCCallLeg* caller, AmSipDialog* p_dlg,
|
|
AmSipSubscription* p_subs)
|
|
: auth(NULL), auth_di(NULL),
|
|
call_profile(caller->getCallProfile()),
|
|
CallLeg(caller,p_dlg,p_subs),
|
|
ext_cc_timer_id(SBC_TIMER_ID_CALL_TIMERS_END + 1),
|
|
cc_started(false),
|
|
logger(NULL)
|
|
{
|
|
#ifdef WITH_ZRTP
|
|
enable_zrtp = false;
|
|
#endif
|
|
|
|
// FIXME: do we want to inherit cc_vars from caller?
|
|
// Can be pretty dangerous when caller stored pointer to object - we should
|
|
// not probably operate on it! But on other hand it could be handy for
|
|
// something, so just take care when using stored objects...
|
|
// call_profile.cc_vars.clear();
|
|
|
|
dlg->setRel100State(Am100rel::REL100_IGNORED);
|
|
|
|
// we need to apply it here instead of in applyBProfile because we have caller
|
|
// here (FIXME: do it on better place and better way than accessing internals
|
|
// of caller->dlg directly)
|
|
if (call_profile.transparent_dlg_id && caller) {
|
|
dlg->setCallid(caller->dlg->getCallid());
|
|
dlg->setExtLocalTag(caller->dlg->getRemoteTag());
|
|
dlg->cseq = caller->dlg->r_cseq;
|
|
}
|
|
|
|
// copy RTP rate limit from caller leg
|
|
if(caller->rtp_relay_rate_limit.get()) {
|
|
rtp_relay_rate_limit.reset(new RateLimit(*caller->rtp_relay_rate_limit.get()));
|
|
}
|
|
|
|
// CC interfaces and variables should be already "evaluated" by A leg, we just
|
|
// need to load the DI interfaces for us (later they will be initialized with
|
|
// original INVITE so it must be done in A leg's thread!)
|
|
if (!getCCInterfaces()) {
|
|
throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR);
|
|
}
|
|
|
|
if (!initCCExtModules(call_profile.cc_interfaces, cc_modules)) {
|
|
ERROR("initializing extended call control modules\n");
|
|
throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR);
|
|
}
|
|
|
|
setLogger(caller->getLogger());
|
|
|
|
subs->allowUnsolicitedNotify(call_profile.allow_subless_notify);
|
|
}
|
|
|
|
SBCCallLeg::SBCCallLeg(AmSipDialog* p_dlg, AmSipSubscription* p_subs)
|
|
: CallLeg(p_dlg,p_subs),
|
|
m_state(BB_Init),
|
|
auth(NULL), auth_di(NULL),
|
|
cc_timer_id(SBC_TIMER_ID_CALL_TIMERS_START),
|
|
cc_started(false),
|
|
logger(NULL)
|
|
{
|
|
#ifdef WITH_ZRTP
|
|
enable_zrtp = false;
|
|
#endif
|
|
|
|
memset(&call_start_ts, 0, sizeof(struct timeval));
|
|
memset(&call_connect_ts, 0, sizeof(struct timeval));
|
|
memset(&call_end_ts, 0, sizeof(struct timeval));
|
|
}
|
|
|
|
void SBCCallLeg::onStart()
|
|
{
|
|
// this should be the first thing called in session's thread
|
|
CallLeg::onStart();
|
|
if (!a_leg) applyBProfile(); // A leg needs to evaluate profile first
|
|
else if (!getOtherId().empty()) {
|
|
// A leg but we already have a peer, what means that this call leg was
|
|
// created as an A leg for already existing B leg (for example call
|
|
// transfer)
|
|
// we need to apply a profile, we use B profile and understand it as an
|
|
// "outbound" profile though we are in A leg
|
|
applyBProfile();
|
|
}
|
|
}
|
|
|
|
void SBCCallLeg::applyAProfile()
|
|
{
|
|
// apply A leg configuration (but most of the configuration is applied in
|
|
// SBCFactory::onInvite)
|
|
|
|
if (call_profile.rtprelay_enabled_value || call_profile.transcoder.isActive()) {
|
|
DBG("Enabling RTP relay mode for SBC call\n");
|
|
|
|
setRtpRelayForceSymmetricRtp(call_profile.aleg_force_symmetric_rtp_value);
|
|
DBG("%s\n",getRtpRelayForceSymmetricRtp() ?
|
|
"forcing symmetric RTP (passive mode)":
|
|
"disabled symmetric RTP (normal mode)");
|
|
|
|
if (call_profile.aleg_rtprelay_interface_value >= 0) {
|
|
setRtpInterface(call_profile.aleg_rtprelay_interface_value);
|
|
DBG("using RTP interface %i for A leg\n", rtp_interface);
|
|
}
|
|
|
|
setRtpRelayTransparentSeqno(call_profile.rtprelay_transparent_seqno);
|
|
setRtpRelayTransparentSSRC(call_profile.rtprelay_transparent_ssrc);
|
|
setEnableDtmfRtpFiltering(call_profile.rtprelay_dtmf_filtering);
|
|
setEnableDtmfRtpDetection(call_profile.rtprelay_dtmf_detection);
|
|
|
|
if(call_profile.transcoder.isActive()) {
|
|
setRtpRelayMode(RTP_Transcoding);
|
|
switch(call_profile.transcoder.dtmf_mode) {
|
|
case SBCCallProfile::TranscoderSettings::DTMFAlways:
|
|
enable_dtmf_transcoding = true; break;
|
|
case SBCCallProfile::TranscoderSettings::DTMFNever:
|
|
enable_dtmf_transcoding = false; break;
|
|
case SBCCallProfile::TranscoderSettings::DTMFLowFiCodecs:
|
|
enable_dtmf_transcoding = false;
|
|
lowfi_payloads = call_profile.transcoder.lowfi_codecs;
|
|
break;
|
|
};
|
|
}
|
|
else {
|
|
setRtpRelayMode(RTP_Relay);
|
|
}
|
|
|
|
// copy stats counters
|
|
rtp_pegs = call_profile.aleg_rtp_counters;
|
|
}
|
|
|
|
if(!call_profile.dlg_contact_params.empty())
|
|
dlg->setContactParams(call_profile.dlg_contact_params);
|
|
}
|
|
|
|
int SBCCallLeg::applySSTCfg(AmConfigReader& sst_cfg,
|
|
const AmSipRequest* p_req)
|
|
{
|
|
DBG("Enabling SIP Session Timers\n");
|
|
if (NULL == SBCFactory::instance()->session_timer_fact) {
|
|
ERROR("session_timer module not loaded - "
|
|
"unable to create call with SST\n");
|
|
return -1;
|
|
}
|
|
|
|
if (p_req && !SBCFactory::instance()->session_timer_fact->
|
|
onInvite(*p_req, sst_cfg)) {
|
|
return -1;
|
|
}
|
|
|
|
AmSessionEventHandler* h = SBCFactory::instance()->session_timer_fact->
|
|
getHandler(this);
|
|
|
|
if (!h) {
|
|
ERROR("could not get a session timer event handler\n");
|
|
return -1;
|
|
}
|
|
|
|
if (h->configure(sst_cfg)) {
|
|
ERROR("Could not configure the session timer: "
|
|
"disabling session timers.\n");
|
|
delete h;
|
|
}
|
|
else {
|
|
addHandler(h);
|
|
// hack: repeat calling the handler again to start timers because
|
|
// it was called before SST was applied
|
|
if(p_req) h->onSipRequest(*p_req);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void SBCCallLeg::applyBProfile()
|
|
{
|
|
// TODO: fix this!!! (see d85ed5c7e6b8d4c24e7e5b61c732c2e1ddd31784)
|
|
// if (!call_profile.contact.empty()) {
|
|
// dlg->contact_uri = SIP_HDR_COLSP(SIP_HDR_CONTACT) + call_profile.contact + CRLF;
|
|
// }
|
|
|
|
if (call_profile.auth_enabled) {
|
|
// adding auth handler
|
|
AmSessionEventHandlerFactory* uac_auth_f =
|
|
AmPlugIn::instance()->getFactory4Seh("uac_auth");
|
|
if (NULL == uac_auth_f) {
|
|
INFO("uac_auth module not loaded. uac auth NOT enabled.\n");
|
|
} else {
|
|
AmSessionEventHandler* h = uac_auth_f->getHandler(this);
|
|
|
|
// we cannot use the generic AmSessionEventHandler hooks,
|
|
// because the hooks don't work in AmB2BSession
|
|
setAuthHandler(h);
|
|
DBG("uac auth enabled for callee session.\n");
|
|
}
|
|
}
|
|
|
|
if (call_profile.uas_auth_bleg_enabled) {
|
|
AmDynInvokeFactory* fact = AmPlugIn::instance()->getFactory4Di("uac_auth");
|
|
if (NULL != fact) {
|
|
AmDynInvoke* di_inst = fact->getInstance();
|
|
if(NULL != di_inst) {
|
|
setAuthDI(di_inst);
|
|
}
|
|
} else {
|
|
ERROR("B-leg UAS auth enabled (uas_auth_bleg_enabled), but uac_auth module not loaded!\n");
|
|
}
|
|
}
|
|
|
|
if (call_profile.sst_enabled_value) {
|
|
if(applySSTCfg(call_profile.sst_b_cfg,NULL) < 0) {
|
|
throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR);
|
|
}
|
|
}
|
|
|
|
if (!call_profile.outbound_proxy.empty()) {
|
|
dlg->outbound_proxy = call_profile.outbound_proxy;
|
|
dlg->force_outbound_proxy = call_profile.force_outbound_proxy;
|
|
}
|
|
|
|
if (!call_profile.next_hop.empty()) {
|
|
DBG("set next hop to '%s' (1st_req=%s,fixed=%s)\n",
|
|
call_profile.next_hop.c_str(), call_profile.next_hop_1st_req?"true":"false",
|
|
call_profile.next_hop_fixed?"true":"false");
|
|
dlg->setNextHop(call_profile.next_hop);
|
|
dlg->setNextHop1stReq(call_profile.next_hop_1st_req);
|
|
dlg->setNextHopFixed(call_profile.next_hop_fixed);
|
|
}
|
|
|
|
DBG("patch_ruri_next_hop = %i",call_profile.patch_ruri_next_hop);
|
|
dlg->setPatchRURINextHop(call_profile.patch_ruri_next_hop);
|
|
|
|
// was read from caller but reading directly from profile now
|
|
if (call_profile.outbound_interface_value >= 0)
|
|
dlg->setOutboundInterface(call_profile.outbound_interface_value);
|
|
|
|
// was read from caller but reading directly from profile now
|
|
if (call_profile.rtprelay_enabled_value || call_profile.transcoder.isActive()) {
|
|
|
|
if (call_profile.rtprelay_interface_value >= 0)
|
|
setRtpInterface(call_profile.rtprelay_interface_value);
|
|
|
|
setRtpRelayForceSymmetricRtp(call_profile.force_symmetric_rtp_value);
|
|
DBG("%s\n",getRtpRelayForceSymmetricRtp() ?
|
|
"forcing symmetric RTP (passive mode)":
|
|
"disabled symmetric RTP (normal mode)");
|
|
|
|
setRtpRelayTransparentSeqno(call_profile.rtprelay_transparent_seqno);
|
|
setRtpRelayTransparentSSRC(call_profile.rtprelay_transparent_ssrc);
|
|
setEnableDtmfRtpFiltering(call_profile.rtprelay_dtmf_filtering);
|
|
setEnableDtmfRtpDetection(call_profile.rtprelay_dtmf_detection);
|
|
|
|
// copy stats counters
|
|
rtp_pegs = call_profile.bleg_rtp_counters;
|
|
}
|
|
|
|
// was read from caller but reading directly from profile now
|
|
if (!call_profile.callid.empty())
|
|
dlg->setCallid(call_profile.callid);
|
|
|
|
if(!call_profile.bleg_dlg_contact_params.empty())
|
|
dlg->setContactParams(call_profile.bleg_dlg_contact_params);
|
|
}
|
|
|
|
int SBCCallLeg::relayEvent(AmEvent* ev)
|
|
{
|
|
for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
|
|
int res = (*i)->relayEvent(this, ev);
|
|
if (res > 0) return 0;
|
|
if (res < 0) return res;
|
|
}
|
|
|
|
switch (ev->event_id) {
|
|
case B2BSipRequest:
|
|
{
|
|
B2BSipRequestEvent* req_ev = dynamic_cast<B2BSipRequestEvent*>(ev);
|
|
assert(req_ev);
|
|
|
|
if (call_profile.headerfilter.size()) {
|
|
//B2BSipRequestEvent* req_ev = dynamic_cast<B2BSipRequestEvent*>(ev);
|
|
// header filter
|
|
inplaceHeaderFilter(req_ev->req.hdrs, call_profile.headerfilter);
|
|
}
|
|
|
|
if (req_ev->req.method == SIP_METH_REFER &&
|
|
call_profile.fix_replaces_ref == "yes") {
|
|
fixReplaces(req_ev->req.hdrs, false);
|
|
}
|
|
|
|
DBG("filtering body for request '%s' (c/t '%s')\n",
|
|
req_ev->req.method.c_str(), req_ev->req.body.getCTStr().c_str());
|
|
// todo: handle filtering errors
|
|
|
|
int res = filterSdp(req_ev->req.body, req_ev->req.method);
|
|
if (res < 0) {
|
|
delete ev; // failed relayEvent should destroy the event
|
|
return res;
|
|
}
|
|
|
|
if((a_leg && call_profile.keep_vias)
|
|
|| (!a_leg && call_profile.bleg_keep_vias)) {
|
|
req_ev->req.hdrs = req_ev->req.vias + req_ev->req.hdrs;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case B2BSipReply:
|
|
{
|
|
B2BSipReplyEvent* reply_ev = dynamic_cast<B2BSipReplyEvent*>(ev);
|
|
assert(reply_ev);
|
|
|
|
if(call_profile.transparent_dlg_id &&
|
|
(reply_ev->reply.from_tag == dlg->getExtLocalTag()))
|
|
reply_ev->reply.from_tag = dlg->getLocalTag();
|
|
|
|
if (call_profile.headerfilter.size() ||
|
|
call_profile.reply_translations.size()) {
|
|
// header filter
|
|
if (call_profile.headerfilter.size()) {
|
|
inplaceHeaderFilter(reply_ev->reply.hdrs, call_profile.headerfilter);
|
|
}
|
|
|
|
// reply translations
|
|
map<unsigned int, pair<unsigned int, string> >::iterator it =
|
|
call_profile.reply_translations.find(reply_ev->reply.code);
|
|
|
|
if (it != call_profile.reply_translations.end()) {
|
|
DBG("translating reply %u %s => %u %s\n",
|
|
reply_ev->reply.code, reply_ev->reply.reason.c_str(),
|
|
it->second.first, it->second.second.c_str());
|
|
reply_ev->reply.code = it->second.first;
|
|
reply_ev->reply.reason = it->second.second;
|
|
}
|
|
}
|
|
|
|
DBG("filtering body for reply '%s' (c/t '%s')\n",
|
|
reply_ev->trans_method.c_str(), reply_ev->reply.body.getCTStr().c_str());
|
|
|
|
filterSdp(reply_ev->reply.body, reply_ev->reply.cseq_method);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return CallLeg::relayEvent(ev);
|
|
}
|
|
|
|
SBCCallLeg::~SBCCallLeg()
|
|
{
|
|
if (auth)
|
|
delete auth;
|
|
if (logger) dec_ref(logger);
|
|
}
|
|
|
|
void SBCCallLeg::onBeforeDestroy()
|
|
{
|
|
for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
|
|
(*i)->onDestroyLeg(this);
|
|
}
|
|
}
|
|
|
|
UACAuthCred* SBCCallLeg::getCredentials() {
|
|
if (a_leg) return &call_profile.auth_aleg_credentials;
|
|
else return &call_profile.auth_credentials;
|
|
}
|
|
|
|
void SBCCallLeg::onSipRequest(const AmSipRequest& req) {
|
|
// AmB2BSession does not call AmSession::onSipRequest for
|
|
// forwarded requests - so lets call event handlers here
|
|
// todo: this is a hack, replace this by calling proper session
|
|
// event handler in AmB2BSession
|
|
bool fwd = sip_relay_only && (req.method != SIP_METH_CANCEL);
|
|
if (fwd) {
|
|
CALL_EVENT_H(onSipRequest,req);
|
|
}
|
|
|
|
if (fwd && call_profile.messagefilter.size()) {
|
|
for (vector<FilterEntry>::iterator it=
|
|
call_profile.messagefilter.begin();
|
|
it != call_profile.messagefilter.end(); it++) {
|
|
|
|
if (isActiveFilter(it->filter_type)) {
|
|
string method = req.method;
|
|
std::transform(method.begin(), method.end(), method.begin(), ::tolower);
|
|
|
|
bool is_filtered = (it->filter_type == Whitelist) ^
|
|
(it->filter_list.find(method) != it->filter_list.end());
|
|
if (is_filtered) {
|
|
DBG("replying 405 to filtered message '%s'\n", req.method.c_str());
|
|
dlg->reply(req, 405, "Method Not Allowed", NULL, "", SIP_FLAGS_VERBATIM);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
|
|
if ((*i)->onInDialogRequest(this, req) == StopProcessing) return;
|
|
}
|
|
|
|
if (call_profile.uas_auth_bleg_enabled && NULL != auth_di) {
|
|
AmArg di_args, di_ret;
|
|
try {
|
|
DBG("Auth: checking authentication\n");
|
|
di_args.push((AmObject*)&req);
|
|
di_args.push(call_profile.uas_auth_bleg_credentials.realm);
|
|
di_args.push(call_profile.uas_auth_bleg_credentials.user);
|
|
di_args.push(call_profile.uas_auth_bleg_credentials.pwd);
|
|
auth_di->invoke("checkAuth", di_args, di_ret);
|
|
|
|
if (di_ret.size() >= 3) {
|
|
if (di_ret[0].asInt() != 200) {
|
|
DBG("Auth: replying %u %s - hdrs: '%s'\n",
|
|
di_ret[0].asInt(), di_ret[1].asCStr(), di_ret[2].asCStr());
|
|
dlg->reply(req, di_ret[0].asInt(), di_ret[1].asCStr(), NULL, di_ret[2].asCStr());
|
|
return;
|
|
} else {
|
|
DBG("Successfully authenticated request.\n");
|
|
}
|
|
} else {
|
|
ERROR("internal: no proper result from checkAuth: '%s'\n", AmArg::print(di_ret).c_str());
|
|
}
|
|
|
|
} catch (const AmDynInvoke::NotImplemented& ni) {
|
|
ERROR("not implemented DI function 'checkAuth'\n");
|
|
dlg->reply(req, 500, SIP_REPLY_SERVER_INTERNAL_ERROR, NULL, "", SIP_FLAGS_VERBATIM);
|
|
return;
|
|
} catch (const AmArg::OutOfBoundsException& oob) {
|
|
ERROR("out of bounds in DI call 'checkAuth'\n");
|
|
dlg->reply(req, 500, SIP_REPLY_SERVER_INTERNAL_ERROR, NULL, "", SIP_FLAGS_VERBATIM);
|
|
return;
|
|
} catch (const AmArg::TypeMismatchException& oob) {
|
|
ERROR("type mismatch in DI call checkAuth\n");
|
|
dlg->reply(req, 500, SIP_REPLY_SERVER_INTERNAL_ERROR, NULL, "", SIP_FLAGS_VERBATIM);
|
|
return;
|
|
} catch (...) {
|
|
ERROR("unexpected Exception in DI call checkAuth\n");
|
|
dlg->reply(req, 500, SIP_REPLY_SERVER_INTERNAL_ERROR, NULL, "", SIP_FLAGS_VERBATIM);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (fwd && req.method == SIP_METH_INVITE) {
|
|
DBG("replying 100 Trying to INVITE to be fwd'ed\n");
|
|
dlg->reply(req, 100, SIP_REPLY_TRYING);
|
|
}
|
|
|
|
CallLeg::onSipRequest(req);
|
|
}
|
|
|
|
void SBCCallLeg::setOtherId(const AmSipReply& reply)
|
|
{
|
|
DBG("setting other_id to '%s'",reply.from_tag.c_str());
|
|
setOtherId(reply.from_tag);
|
|
if(call_profile.transparent_dlg_id && !reply.to_tag.empty()) {
|
|
dlg->setExtLocalTag(reply.to_tag);
|
|
}
|
|
}
|
|
|
|
void SBCCallLeg::onInitialReply(B2BSipReplyEvent *e)
|
|
{
|
|
if (call_profile.transparent_dlg_id && !e->reply.to_tag.empty()
|
|
&& dlg->getStatus() != AmBasicSipDialog::Connected) {
|
|
dlg->setExtLocalTag(e->reply.to_tag);
|
|
}
|
|
CallLeg::onInitialReply(e);
|
|
}
|
|
|
|
void SBCCallLeg::onSipReply(const AmSipRequest& req, const AmSipReply& reply,
|
|
AmBasicSipDialog::Status old_dlg_status)
|
|
{
|
|
TransMap::iterator t = relayed_req.find(reply.cseq);
|
|
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.body.getCTStr().c_str());
|
|
if (fwd) {
|
|
CALL_EVENT_H(onSipReply, req, reply, old_dlg_status);
|
|
}
|
|
|
|
if (NULL != auth) {
|
|
// only for SIP authenticated
|
|
unsigned int cseq_before = dlg->cseq;
|
|
if (auth->onSipReply(req, reply, old_dlg_status)) {
|
|
if (cseq_before != dlg->cseq) {
|
|
DBG("uac_auth consumed reply with cseq %d and resent with cseq %d; "
|
|
"updating relayed_req map\n", reply.cseq, cseq_before);
|
|
updateUACTransCSeq(reply.cseq, cseq_before);
|
|
|
|
// don't relay to other leg, process in AmSession
|
|
AmSession::onSipReply(req, reply, old_dlg_status);
|
|
// skip presenting reply to ext_cc modules, too
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
|
|
if ((*i)->onInDialogReply(this, reply) == StopProcessing) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
CallLeg::onSipReply(req, reply, old_dlg_status);
|
|
}
|
|
|
|
void SBCCallLeg::onSendRequest(AmSipRequest& req, int &flags) {
|
|
|
|
if(a_leg) {
|
|
if (!call_profile.aleg_append_headers_req.empty()) {
|
|
DBG("appending '%s' to outbound request (A leg)\n",
|
|
call_profile.aleg_append_headers_req.c_str());
|
|
req.hdrs+=call_profile.aleg_append_headers_req;
|
|
}
|
|
}
|
|
else {
|
|
if (!call_profile.append_headers_req.empty()) {
|
|
DBG("appending '%s' to outbound request (B leg)\n",
|
|
call_profile.append_headers_req.c_str());
|
|
req.hdrs+=call_profile.append_headers_req;
|
|
}
|
|
}
|
|
|
|
if (NULL != auth) {
|
|
DBG("auth->onSendRequest cseq = %d\n", req.cseq);
|
|
auth->onSendRequest(req, flags);
|
|
}
|
|
|
|
CallLeg::onSendRequest(req, flags);
|
|
}
|
|
|
|
void SBCCallLeg::onRemoteDisappeared(const AmSipReply& reply)
|
|
{
|
|
CallLeg::onRemoteDisappeared(reply);
|
|
if(a_leg)
|
|
SBCEventLog::instance()->logCallEnd(dlg,"reply",&call_connect_ts);
|
|
}
|
|
|
|
void SBCCallLeg::onBye(const AmSipRequest& req)
|
|
{
|
|
CallLeg::onBye(req);
|
|
if(a_leg)
|
|
SBCEventLog::instance()->logCallEnd(req,getLocalTag(),"bye",&call_connect_ts);
|
|
}
|
|
|
|
void SBCCallLeg::onOtherBye(const AmSipRequest& req)
|
|
{
|
|
CallLeg::onOtherBye(req);
|
|
if(a_leg)
|
|
SBCEventLog::instance()->logCallEnd(req,getLocalTag(),"bye",&call_connect_ts);
|
|
}
|
|
|
|
void SBCCallLeg::onDtmf(int event, int duration)
|
|
{
|
|
DBG("received DTMF on %c-leg (%i;%i)\n", a_leg ? 'A': 'B', event, duration);
|
|
|
|
for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
|
|
if ((*i)->onDtmf(this, event, duration) == StopProcessing)
|
|
return;
|
|
}
|
|
|
|
AmB2BMedia *ms = getMediaSession();
|
|
if(ms) {
|
|
DBG("sending DTMF (%i;%i)\n", event, duration);
|
|
ms->sendDtmf(!a_leg,event,duration);
|
|
}
|
|
}
|
|
|
|
void SBCCallLeg::updateLocalSdp(AmSdp &sdp)
|
|
{
|
|
// anonymize SDP if configured to do so (we need to have our local media IP,
|
|
// not the media IP of our peer leg there)
|
|
if (call_profile.anonymize_sdp) normalizeSDP(sdp, call_profile.anonymize_sdp, advertisedIP());
|
|
|
|
// remember transcodable payload IDs
|
|
if (call_profile.transcoder.isActive()) savePayloadIDs(sdp);
|
|
CallLeg::updateLocalSdp(sdp);
|
|
}
|
|
|
|
void SBCCallLeg::onControlCmd(string& cmd, AmArg& params) {
|
|
if (cmd == "teardown") {
|
|
if (a_leg) {
|
|
// was for caller:
|
|
DBG("teardown requested from control cmd\n");
|
|
stopCall("ctrl-cmd");
|
|
SBCEventLog::instance()->logCallEnd(dlg,"ctrl-cmd",&call_connect_ts);
|
|
// FIXME: don't we want to relay the controll event as well?
|
|
}
|
|
else {
|
|
// was for callee:
|
|
DBG("relaying teardown control cmd to A leg\n");
|
|
relayEvent(new SBCControlEvent(cmd, params));
|
|
// FIXME: don't we want to stopCall as well?
|
|
}
|
|
return;
|
|
}
|
|
DBG("ignoring unknown control cmd : '%s'\n", cmd.c_str());
|
|
}
|
|
|
|
|
|
void SBCCallLeg::process(AmEvent* ev) {
|
|
for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
|
|
if ((*i)->onEvent(this, ev) == StopProcessing) return;
|
|
}
|
|
|
|
if (a_leg) {
|
|
// was for caller (SBCDialog):
|
|
AmPluginEvent* plugin_event = dynamic_cast<AmPluginEvent*>(ev);
|
|
if(plugin_event && plugin_event->name == "timer_timeout") {
|
|
int timer_id = plugin_event->data.get(0).asInt();
|
|
if (timer_id >= SBC_TIMER_ID_CALL_TIMERS_START &&
|
|
timer_id <= SBC_TIMER_ID_CALL_TIMERS_END) {
|
|
DBG("timer %d timeout, stopping call\n", timer_id);
|
|
stopCall("timer");
|
|
SBCEventLog::instance()->logCallEnd(dlg,"timeout",&call_connect_ts);
|
|
ev->processed = true;
|
|
}
|
|
}
|
|
|
|
SBCCallTimerEvent* ct_event;
|
|
if (ev->event_id == SBCCallTimerEvent_ID &&
|
|
(ct_event = dynamic_cast<SBCCallTimerEvent*>(ev)) != NULL) {
|
|
switch (m_state) {
|
|
case BB_Connected:
|
|
switch (ct_event->timer_action) {
|
|
case SBCCallTimerEvent::Remove:
|
|
DBG("removing timer %d on call timer request\n", ct_event->timer_id);
|
|
removeTimer(ct_event->timer_id); return;
|
|
case SBCCallTimerEvent::Set:
|
|
DBG("setting timer %d to %f on call timer request\n",
|
|
ct_event->timer_id, ct_event->timeout);
|
|
setTimer(ct_event->timer_id, ct_event->timeout); return;
|
|
case SBCCallTimerEvent::Reset:
|
|
DBG("resetting timer %d to %f on call timer request\n",
|
|
ct_event->timer_id, ct_event->timeout);
|
|
removeTimer(ct_event->timer_id);
|
|
setTimer(ct_event->timer_id, ct_event->timeout);
|
|
return;
|
|
default: ERROR("unknown timer_action in sbc call timer event\n"); return;
|
|
}
|
|
|
|
case BB_Init:
|
|
case BB_Dialing:
|
|
|
|
switch (ct_event->timer_action) {
|
|
case SBCCallTimerEvent::Remove:
|
|
clearCallTimer(ct_event->timer_id);
|
|
return;
|
|
|
|
case SBCCallTimerEvent::Set:
|
|
case SBCCallTimerEvent::Reset:
|
|
saveCallTimer(ct_event->timer_id, ct_event->timeout);
|
|
return;
|
|
|
|
default: ERROR("unknown timer_action in sbc call timer event\n"); return;
|
|
}
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
|
|
SBCControlEvent* ctl_event;
|
|
if (ev->event_id == SBCControlEvent_ID &&
|
|
(ctl_event = dynamic_cast<SBCControlEvent*>(ev)) != NULL) {
|
|
onControlCmd(ctl_event->cmd, ctl_event->params);
|
|
return;
|
|
}
|
|
|
|
CallLeg::process(ev);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// was for caller only (SBCDialog)
|
|
// FIXME: move the stuff related to CC interface outside of this class?
|
|
|
|
|
|
#define REPLACE_VALS req, app_param, ruri_parser, from_parser, to_parser
|
|
|
|
void SBCCallLeg::onInvite(const AmSipRequest& req)
|
|
{
|
|
DBG("processing initial INVITE %s\n", req.r_uri.c_str());
|
|
|
|
ParamReplacerCtx ctx(&call_profile);
|
|
ctx.app_param = getHeader(req.hdrs, PARAM_HDR, true);
|
|
|
|
// process call control
|
|
if (call_profile.cc_interfaces.size()) {
|
|
gettimeofday(&call_start_ts, NULL);
|
|
|
|
call_profile.eval_cc_list(ctx,req);
|
|
|
|
// fix up module names
|
|
for (CCInterfaceListIteratorT cc_it=call_profile.cc_interfaces.begin();
|
|
cc_it != call_profile.cc_interfaces.end(); cc_it++) {
|
|
|
|
cc_it->cc_module =
|
|
ctx.replaceParameters(cc_it->cc_module, "cc_module", req);
|
|
}
|
|
|
|
if (!getCCInterfaces()) {
|
|
throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR);
|
|
}
|
|
|
|
// fix up variables
|
|
call_profile.replace_cc_values(ctx,req,NULL);
|
|
|
|
if (!CCStart(req)) {
|
|
setStopped();
|
|
oodHandlingTerminated(req, cc_modules, call_profile);
|
|
return;
|
|
}
|
|
}
|
|
|
|
call_profile.sst_aleg_enabled =
|
|
ctx.replaceParameters(call_profile.sst_aleg_enabled,
|
|
"enable_aleg_session_timer", req);
|
|
|
|
call_profile.sst_enabled = ctx.replaceParameters(call_profile.sst_enabled,
|
|
"enable_session_timer", req);
|
|
|
|
if ((call_profile.sst_aleg_enabled == "yes") &&
|
|
(call_profile.sst_enabled == "yes")) {
|
|
|
|
call_profile.eval_sst_config(ctx,req,call_profile.sst_a_cfg);
|
|
if(applySSTCfg(call_profile.sst_a_cfg,&req) < 0) {
|
|
throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR);
|
|
}
|
|
}
|
|
|
|
if(dlg->reply(req, 100, "Connecting") != 0) {
|
|
throw AmSession::Exception(500,"Failed to reply 100");
|
|
}
|
|
|
|
if (!call_profile.evaluate(ctx, req)) {
|
|
ERROR("call profile evaluation failed\n");
|
|
throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR);
|
|
}
|
|
|
|
if (!initCCExtModules(call_profile.cc_interfaces, cc_modules)) {
|
|
ERROR("initializing extended call control modules\n");
|
|
throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR);
|
|
}
|
|
|
|
string ruri, to, from;
|
|
AmSipRequest uac_req(req);
|
|
AmUriParser uac_ruri;
|
|
uac_ruri.uri = uac_req.r_uri;
|
|
if(!uac_ruri.parse_uri()) {
|
|
DBG("Error parsing R-URI '%s'\n",uac_ruri.uri.c_str());
|
|
throw AmSession::Exception(400,"Failed to parse R-URI");
|
|
}
|
|
|
|
if(call_profile.contact_hiding) {
|
|
if(RegisterDialog::decodeUsername(req.user,uac_ruri)) {
|
|
uac_req.r_uri = uac_ruri.uri_str();
|
|
}
|
|
}
|
|
else if(call_profile.reg_caching) {
|
|
// REG-Cache lookup
|
|
uac_req.r_uri = call_profile.retarget(req.user);
|
|
}
|
|
|
|
ruri = call_profile.ruri.empty() ? uac_req.r_uri : call_profile.ruri;
|
|
if(!call_profile.ruri_host.empty()){
|
|
ctx.ruri_parser.uri = ruri;
|
|
if(!ctx.ruri_parser.parse_uri()) {
|
|
WARN("Error parsing R-URI '%s'\n", ruri.c_str());
|
|
}
|
|
else {
|
|
ctx.ruri_parser.uri_port.clear();
|
|
ctx.ruri_parser.uri_host = call_profile.ruri_host;
|
|
ruri = ctx.ruri_parser.uri_str();
|
|
}
|
|
}
|
|
from = call_profile.from.empty() ? req.from : call_profile.from;
|
|
to = call_profile.to.empty() ? req.to : call_profile.to;
|
|
|
|
applyAProfile();
|
|
call_profile.apply_a_routing(ctx,req,*dlg);
|
|
|
|
m_state = BB_Dialing;
|
|
|
|
// prepare request to relay to the B leg(s)
|
|
|
|
AmSipRequest invite_req(req);
|
|
est_invite_cseq = req.cseq;
|
|
|
|
removeHeader(invite_req.hdrs,PARAM_HDR);
|
|
removeHeader(invite_req.hdrs,"P-App-Name");
|
|
|
|
if (call_profile.sst_enabled_value) {
|
|
removeHeader(invite_req.hdrs,SIP_HDR_SESSION_EXPIRES);
|
|
removeHeader(invite_req.hdrs,SIP_HDR_MIN_SE);
|
|
}
|
|
|
|
inplaceHeaderFilter(invite_req.hdrs, call_profile.headerfilter);
|
|
|
|
if (call_profile.fix_replaces_inv == "yes") {
|
|
fixReplaces(invite_req.hdrs, true);
|
|
}
|
|
|
|
if (call_profile.append_headers.size() > 2) {
|
|
string append_headers = call_profile.append_headers;
|
|
assertEndCRLF(append_headers);
|
|
invite_req.hdrs+=append_headers;
|
|
}
|
|
|
|
int res = filterSdp(invite_req.body, invite_req.method);
|
|
if (res < 0) {
|
|
// FIXME: quick hack, throw the exception from the filtering function for
|
|
// requests
|
|
throw AmSession::Exception(488, SIP_REPLY_NOT_ACCEPTABLE_HERE);
|
|
}
|
|
|
|
#undef REPLACE_VALS
|
|
|
|
DBG("SBC: connecting to '%s'\n",ruri.c_str());
|
|
DBG(" From: '%s'\n",from.c_str());
|
|
DBG(" To: '%s'\n",to.c_str());
|
|
|
|
// we evaluated the settings, now we can initialize internals (like RTP relay)
|
|
// we have to use original request (not the altered one) because for example
|
|
// codecs filtered out might be used in direction to caller
|
|
CallLeg::onInvite(req);
|
|
|
|
if(a_leg && call_profile.keep_vias)
|
|
invite_req.hdrs = invite_req.vias + invite_req.hdrs;
|
|
|
|
subs->allowUnsolicitedNotify(call_profile.allow_subless_notify);
|
|
|
|
// call extend call controls
|
|
InitialInviteHandlerParams params(to, ruri, from, &req, &invite_req);
|
|
for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
|
|
(*i)->onInitialInvite(this, params);
|
|
// initialize possibily added modules
|
|
initPendingCCExtModules();
|
|
}
|
|
|
|
if (getCallStatus() == Disconnected) {
|
|
// no CC module connected a callee yet
|
|
connectCallee(to, ruri, from, req, invite_req); // connect to the B leg(s) using modified request
|
|
}
|
|
}
|
|
|
|
void SBCCallLeg::connectCallee(const string& remote_party,
|
|
const string& remote_uri,
|
|
const string &from,
|
|
const AmSipRequest &original_invite,
|
|
const AmSipRequest &invite)
|
|
{
|
|
// FIXME: no fork for now
|
|
|
|
SBCCallLeg* callee_session = SBCFactory::instance()->
|
|
getCallLegCreator()->create(this);
|
|
|
|
callee_session->setLocalParty(from, from);
|
|
callee_session->setRemoteParty(remote_party, remote_uri);
|
|
|
|
DBG("Created B2BUA callee leg, From: %s\n", from.c_str());
|
|
|
|
// FIXME: inconsistent with other filtering stuff - here goes the INVITE
|
|
// already filtered so need not to be catched (can not) in relayEvent because
|
|
// it is sent other way
|
|
addCallee(callee_session, invite);
|
|
|
|
// we could start in SIP relay mode from the beginning if only one B leg, but
|
|
// serial fork might mess it
|
|
// set_sip_relay_only(true);
|
|
}
|
|
|
|
bool SBCCallLeg::getCCInterfaces() {
|
|
return ::getCCInterfaces(call_profile.cc_interfaces, cc_modules);
|
|
}
|
|
|
|
void SBCCallLeg::onCallConnected(const AmSipReply& reply) {
|
|
if (a_leg) { // FIXME: really?
|
|
m_state = BB_Connected;
|
|
|
|
if (!startCallTimers())
|
|
return;
|
|
|
|
if (call_profile.cc_interfaces.size()) {
|
|
gettimeofday(&call_connect_ts, NULL);
|
|
}
|
|
|
|
logCallStart(reply);
|
|
CCConnect(reply);
|
|
}
|
|
}
|
|
|
|
void SBCCallLeg::onStop() {
|
|
if (call_profile.cc_interfaces.size()) {
|
|
gettimeofday(&call_end_ts, NULL);
|
|
}
|
|
|
|
if (a_leg && m_state == BB_Connected) { // m_state might be valid for A leg only
|
|
stopCallTimers();
|
|
}
|
|
|
|
m_state = BB_Teardown;
|
|
|
|
// call only if really started (on CCStart failure CCEnd will be called
|
|
// explicitly)
|
|
// Note that role may change, so testing for a_leg need not to be correct.
|
|
if (cc_started) CCEnd();
|
|
}
|
|
|
|
void SBCCallLeg::saveCallTimer(int timer, double timeout) {
|
|
call_timers[timer] = timeout;
|
|
}
|
|
|
|
void SBCCallLeg::clearCallTimer(int timer) {
|
|
call_timers.erase(timer);
|
|
}
|
|
|
|
void SBCCallLeg::clearCallTimers() {
|
|
call_timers.clear();
|
|
}
|
|
|
|
/** @return whether successful */
|
|
bool SBCCallLeg::startCallTimers() {
|
|
for (map<int, double>::iterator it=
|
|
call_timers.begin(); it != call_timers.end(); it++) {
|
|
DBG("SBC: starting call timer %i of %f seconds\n", it->first, it->second);
|
|
setTimer(it->first, it->second);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void SBCCallLeg::stopCallTimers() {
|
|
for (map<int, double>::iterator it=
|
|
call_timers.begin(); it != call_timers.end(); it++) {
|
|
DBG("SBC: removing call timer %i\n", it->first);
|
|
removeTimer(it->first);
|
|
}
|
|
}
|
|
|
|
bool SBCCallLeg::CCStart(const AmSipRequest& req) {
|
|
if (!a_leg) return true; // preserve original behavior of the CC interface
|
|
|
|
vector<AmDynInvoke*>::iterator cc_mod=cc_modules.begin();
|
|
|
|
for (CCInterfaceListIteratorT cc_it=call_profile.cc_interfaces.begin();
|
|
cc_it != call_profile.cc_interfaces.end(); cc_it++) {
|
|
CCInterface& cc_if = *cc_it;
|
|
|
|
AmArg di_args,ret;
|
|
di_args.push(cc_if.cc_name);
|
|
di_args.push(getLocalTag());
|
|
di_args.push((AmObject*)&call_profile);
|
|
di_args.push((AmObject*)&req); // INVITE request
|
|
di_args.push(AmArg());
|
|
di_args.back().push((int)call_start_ts.tv_sec);
|
|
di_args.back().push((int)call_start_ts.tv_usec);
|
|
for (int i=0;i<4;i++)
|
|
di_args.back().push((int)0);
|
|
|
|
di_args.push(AmArg());
|
|
AmArg& vals = di_args.back();
|
|
vals.assertStruct();
|
|
for (map<string, string>::iterator it = cc_if.cc_values.begin();
|
|
it != cc_if.cc_values.end(); it++) {
|
|
vals[it->first] = it->second;
|
|
}
|
|
|
|
di_args.push(cc_timer_id); // current timer ID
|
|
|
|
bool exception_occured = false;
|
|
try {
|
|
(*cc_mod)->invoke("start", di_args, ret);
|
|
} catch (const AmArg::OutOfBoundsException& e) {
|
|
ERROR("OutOfBoundsException executing call control interface start "
|
|
"module '%s' named '%s', parameters '%s'\n",
|
|
cc_if.cc_module.c_str(), cc_if.cc_name.c_str(),
|
|
AmArg::print(di_args).c_str());
|
|
exception_occured = true;
|
|
} catch (const AmArg::TypeMismatchException& e) {
|
|
ERROR("TypeMismatchException executing call control interface start "
|
|
"module '%s' named '%s', parameters '%s'\n",
|
|
cc_if.cc_module.c_str(), cc_if.cc_name.c_str(),
|
|
AmArg::print(di_args).c_str());
|
|
exception_occured = true;
|
|
}
|
|
|
|
if(exception_occured) {
|
|
SBCEventLog::instance()->
|
|
logCallStart(req, getLocalTag(), dlg->getRemoteUA(), "",
|
|
500, SIP_REPLY_SERVER_INTERNAL_ERROR);
|
|
AmBasicSipDialog::reply_error(req, 500, SIP_REPLY_SERVER_INTERNAL_ERROR);
|
|
|
|
// call 'end' of call control modules up to here
|
|
call_end_ts.tv_sec = call_start_ts.tv_sec;
|
|
call_end_ts.tv_usec = call_start_ts.tv_usec;
|
|
CCEnd(cc_it);
|
|
|
|
return false;
|
|
}
|
|
|
|
if (!logger) {
|
|
// open the logger if not already opened
|
|
msg_logger *l = call_profile.get_logger(req);
|
|
if (l) setLogger(l);
|
|
}
|
|
|
|
// evaluate ret
|
|
if (isArgArray(ret)) {
|
|
for (size_t i=0;i<ret.size();i++) {
|
|
if (!isArgArray(ret[i]) || !ret[i].size())
|
|
continue;
|
|
if (!isArgInt(ret[i][SBC_CC_ACTION])) {
|
|
ERROR("in call control module '%s' - action type not int\n",
|
|
cc_if.cc_name.c_str());
|
|
continue;
|
|
}
|
|
switch (ret[i][SBC_CC_ACTION].asInt()) {
|
|
case SBC_CC_DROP_ACTION: {
|
|
DBG("dropping call on call control action DROP from '%s'\n",
|
|
cc_if.cc_name.c_str());
|
|
dlg->setStatus(AmSipDialog::Disconnected);
|
|
|
|
// call 'end' of call control modules up to here
|
|
call_end_ts.tv_sec = call_start_ts.tv_sec;
|
|
call_end_ts.tv_usec = call_start_ts.tv_usec;
|
|
CCEnd(cc_it);
|
|
|
|
return false;
|
|
}
|
|
|
|
case SBC_CC_REFUSE_ACTION: {
|
|
if (ret[i].size() < 3 ||
|
|
!isArgInt(ret[i][SBC_CC_REFUSE_CODE]) ||
|
|
!isArgCStr(ret[i][SBC_CC_REFUSE_REASON])) {
|
|
ERROR("in call control module '%s' - REFUSE action parameters missing/wrong: '%s'\n",
|
|
cc_if.cc_name.c_str(), AmArg::print(ret[i]).c_str());
|
|
continue;
|
|
}
|
|
string headers;
|
|
if (ret[i].size() > SBC_CC_REFUSE_HEADERS) {
|
|
for (size_t h=0;h<ret[i][SBC_CC_REFUSE_HEADERS].size();h++)
|
|
headers += string(ret[i][SBC_CC_REFUSE_HEADERS][h].asCStr()) + CRLF;
|
|
}
|
|
|
|
DBG("replying with %d %s on call control action REFUSE from '%s' headers='%s'\n",
|
|
ret[i][SBC_CC_REFUSE_CODE].asInt(), ret[i][SBC_CC_REFUSE_REASON].asCStr(),
|
|
cc_if.cc_name.c_str(), headers.c_str());
|
|
|
|
SBCEventLog::instance()->
|
|
logCallStart(req, getLocalTag(), dlg->getRemoteUA(), "",
|
|
ret[i][SBC_CC_REFUSE_CODE].asInt(),
|
|
ret[i][SBC_CC_REFUSE_REASON].asCStr());
|
|
|
|
dlg->reply(req,
|
|
ret[i][SBC_CC_REFUSE_CODE].asInt(),
|
|
ret[i][SBC_CC_REFUSE_REASON].asCStr(),
|
|
NULL, headers);
|
|
|
|
// call 'end' of call control modules up to here
|
|
call_end_ts.tv_sec = call_start_ts.tv_sec;
|
|
call_end_ts.tv_usec = call_start_ts.tv_usec;
|
|
CCEnd(cc_it);
|
|
return false;
|
|
}
|
|
|
|
case SBC_CC_SET_CALL_TIMER_ACTION: {
|
|
if (cc_timer_id > SBC_TIMER_ID_CALL_TIMERS_END) {
|
|
ERROR("too many call timers - ignoring timer\n");
|
|
continue;
|
|
}
|
|
|
|
if (ret[i].size() < 2 ||
|
|
(!(isArgInt(ret[i][SBC_CC_TIMER_TIMEOUT]) ||
|
|
isArgDouble(ret[i][SBC_CC_TIMER_TIMEOUT])))) {
|
|
ERROR("in call control module '%s' - SET_CALL_TIMER action parameters missing: '%s'\n",
|
|
cc_if.cc_name.c_str(), AmArg::print(ret[i]).c_str());
|
|
continue;
|
|
}
|
|
|
|
double timeout;
|
|
if (isArgInt(ret[i][SBC_CC_TIMER_TIMEOUT]))
|
|
timeout = ret[i][SBC_CC_TIMER_TIMEOUT].asInt();
|
|
else
|
|
timeout = ret[i][SBC_CC_TIMER_TIMEOUT].asDouble();
|
|
|
|
DBG("saving call timer %i: timeout %f\n", cc_timer_id, timeout);
|
|
saveCallTimer(cc_timer_id, timeout);
|
|
cc_timer_id++;
|
|
} break;
|
|
default: {
|
|
ERROR("unknown call control action: '%s'\n", AmArg::print(ret[i]).c_str());
|
|
continue;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
cc_mod++;
|
|
}
|
|
cc_started = true;
|
|
return true;
|
|
}
|
|
|
|
void SBCCallLeg::CCConnect(const AmSipReply& reply) {
|
|
if (!cc_started) return; // preserve original behavior of the CC interface
|
|
|
|
vector<AmDynInvoke*>::iterator cc_mod=cc_modules.begin();
|
|
|
|
for (CCInterfaceListIteratorT cc_it=call_profile.cc_interfaces.begin();
|
|
cc_it != call_profile.cc_interfaces.end(); cc_it++) {
|
|
CCInterface& cc_if = *cc_it;
|
|
|
|
AmArg di_args,ret;
|
|
di_args.push(cc_if.cc_name); // cc name
|
|
di_args.push(getLocalTag()); // call ltag
|
|
di_args.push((AmObject*)&call_profile); // call profile
|
|
di_args.push((AmObject*)NULL); // there is no sip msg
|
|
di_args.push(AmArg()); // timestamps
|
|
di_args.back().push((int)call_start_ts.tv_sec);
|
|
di_args.back().push((int)call_start_ts.tv_usec);
|
|
di_args.back().push((int)call_connect_ts.tv_sec);
|
|
di_args.back().push((int)call_connect_ts.tv_usec);
|
|
for (int i=0;i<2;i++)
|
|
di_args.back().push((int)0);
|
|
di_args.push(getOtherId()); // other leg ltag
|
|
|
|
|
|
try {
|
|
(*cc_mod)->invoke("connect", di_args, ret);
|
|
} catch (const AmArg::OutOfBoundsException& e) {
|
|
ERROR("OutOfBoundsException executing call control interface connect "
|
|
"module '%s' named '%s', parameters '%s'\n",
|
|
cc_if.cc_module.c_str(), cc_if.cc_name.c_str(),
|
|
AmArg::print(di_args).c_str());
|
|
stopCall(StatusChangeCause::InternalError);
|
|
return;
|
|
} catch (const AmArg::TypeMismatchException& e) {
|
|
ERROR("TypeMismatchException executing call control interface connect "
|
|
"module '%s' named '%s', parameters '%s'\n",
|
|
cc_if.cc_module.c_str(), cc_if.cc_name.c_str(),
|
|
AmArg::print(di_args).c_str());
|
|
stopCall(StatusChangeCause::InternalError);
|
|
return;
|
|
}
|
|
|
|
cc_mod++;
|
|
}
|
|
}
|
|
|
|
void SBCCallLeg::CCEnd() {
|
|
CCEnd(call_profile.cc_interfaces.end());
|
|
}
|
|
|
|
void SBCCallLeg::CCEnd(const CCInterfaceListIteratorT& end_interface) {
|
|
vector<AmDynInvoke*>::iterator cc_mod=cc_modules.begin();
|
|
|
|
for (CCInterfaceListIteratorT cc_it=call_profile.cc_interfaces.begin();
|
|
cc_it != end_interface; cc_it++) {
|
|
CCInterface& cc_if = *cc_it;
|
|
|
|
AmArg di_args,ret;
|
|
di_args.push(cc_if.cc_name);
|
|
di_args.push(getLocalTag()); // call ltag
|
|
di_args.push((AmObject*)&call_profile);
|
|
di_args.push((AmObject*)NULL); // there is no sip msg
|
|
di_args.push(AmArg()); // timestamps
|
|
di_args.back().push((int)call_start_ts.tv_sec);
|
|
di_args.back().push((int)call_start_ts.tv_usec);
|
|
di_args.back().push((int)call_connect_ts.tv_sec);
|
|
di_args.back().push((int)call_connect_ts.tv_usec);
|
|
di_args.back().push((int)call_end_ts.tv_sec);
|
|
di_args.back().push((int)call_end_ts.tv_usec);
|
|
|
|
try {
|
|
(*cc_mod)->invoke("end", di_args, ret);
|
|
} catch (const AmArg::OutOfBoundsException& e) {
|
|
ERROR("OutOfBoundsException executing call control interface end "
|
|
"module '%s' named '%s', parameters '%s'\n",
|
|
cc_if.cc_module.c_str(), cc_if.cc_name.c_str(),
|
|
AmArg::print(di_args).c_str());
|
|
} catch (const AmArg::TypeMismatchException& e) {
|
|
ERROR("TypeMismatchException executing call control interface end "
|
|
"module '%s' named '%s', parameters '%s'\n",
|
|
cc_if.cc_module.c_str(), cc_if.cc_name.c_str(),
|
|
AmArg::print(di_args).c_str());
|
|
}
|
|
|
|
cc_mod++;
|
|
}
|
|
}
|
|
|
|
void SBCCallLeg::onCallStatusChange(const StatusChangeCause &cause)
|
|
{
|
|
for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
|
|
(*i)->onStateChange(this, cause);
|
|
}
|
|
}
|
|
|
|
void SBCCallLeg::onBLegRefused(const AmSipReply& reply)
|
|
{
|
|
for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) {
|
|
if ((*i)->onBLegRefused(this, reply) == StopProcessing) return;
|
|
}
|
|
}
|
|
|
|
void SBCCallLeg::onCallFailed(CallFailureReason reason, const AmSipReply *reply)
|
|
{
|
|
switch (reason) {
|
|
case CallRefused:
|
|
if (reply) logCallStart(*reply);
|
|
break;
|
|
|
|
case CallCanceled:
|
|
logCanceledCall();
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool SBCCallLeg::onBeforeRTPRelay(AmRtpPacket* p, sockaddr_storage* remote_addr)
|
|
{
|
|
if(rtp_relay_rate_limit.get() &&
|
|
rtp_relay_rate_limit->limit(p->getBufferSize()))
|
|
return false; // drop
|
|
|
|
return true; // relay
|
|
}
|
|
|
|
void SBCCallLeg::onAfterRTPRelay(AmRtpPacket* p, sockaddr_storage* remote_addr)
|
|
{
|
|
for(list<atomic_int*>::iterator it = rtp_pegs.begin();
|
|
it != rtp_pegs.end(); ++it) {
|
|
(*it)->inc(p->getBufferSize());
|
|
}
|
|
}
|
|
|
|
void SBCCallLeg::logCallStart(const AmSipReply& reply)
|
|
{
|
|
std::map<int,AmSipRequest>::iterator t_req = recvd_req.find(reply.cseq);
|
|
if (t_req != recvd_req.end()) {
|
|
string b_leg_ua = getHeader(reply.hdrs,"Server");
|
|
SBCEventLog::instance()->logCallStart(t_req->second,getLocalTag(),
|
|
dlg->getRemoteUA(),b_leg_ua,
|
|
(int)reply.code,reply.reason);
|
|
}
|
|
else {
|
|
DBG("could not log call-start/call-attempt (ci='%s';lt='%s')",
|
|
getCallID().c_str(),getLocalTag().c_str());
|
|
}
|
|
}
|
|
|
|
void SBCCallLeg::logCanceledCall()
|
|
{
|
|
std::map<int,AmSipRequest>::iterator t_req = recvd_req.find(est_invite_cseq);
|
|
if (t_req != recvd_req.end()) {
|
|
SBCEventLog::instance()->logCallStart(t_req->second,getLocalTag(),
|
|
"","",
|
|
0,"canceled");
|
|
}
|
|
else {
|
|
ERROR("could not log call-attempt (canceled, ci='%s';lt='%s')",
|
|
getCallID().c_str(),getLocalTag().c_str());
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// body filtering
|
|
|
|
int SBCCallLeg::filterSdp(AmMimeBody &body, const string &method)
|
|
{
|
|
DBG("filtering body\n");
|
|
|
|
AmMimeBody* sdp_body = body.hasContentType(SIP_APPLICATION_SDP);
|
|
if (!sdp_body) return 0;
|
|
|
|
// filter body for given methods only
|
|
if (!(method == SIP_METH_INVITE ||
|
|
method == SIP_METH_UPDATE ||
|
|
method == SIP_METH_PRACK ||
|
|
method == SIP_METH_ACK)) return 0;
|
|
|
|
AmSdp sdp;
|
|
int res = sdp.parse((const char *)sdp_body->getPayload());
|
|
if (0 != res) {
|
|
DBG("SDP parsing failed during body filtering!\n");
|
|
return res;
|
|
}
|
|
|
|
bool changed = false;
|
|
bool prefer_existing_codecs = call_profile.codec_prefs.preferExistingCodecs(a_leg);
|
|
|
|
bool needs_normalization =
|
|
call_profile.codec_prefs.shouldOrderPayloads(a_leg) ||
|
|
call_profile.transcoder.isActive() ||
|
|
!call_profile.sdpfilter.empty();
|
|
|
|
if (needs_normalization) {
|
|
normalizeSDP(sdp, false, ""); // anonymization is done in the other leg to use correct IP address
|
|
changed = true;
|
|
}
|
|
|
|
if (!call_profile.mediafilter.empty()) {
|
|
res = filterMedia(sdp, call_profile.mediafilter);
|
|
if (res < 0) {
|
|
// result may be ignored, we need to set the SDP
|
|
string n_body;
|
|
sdp.print(n_body);
|
|
sdp_body->setPayload((const unsigned char*)n_body.c_str(), n_body.length());
|
|
return res;
|
|
}
|
|
changed = true;
|
|
}
|
|
|
|
if (prefer_existing_codecs) {
|
|
// We have to order payloads before adding transcoder codecs to leave
|
|
// transcoding as the last chance (existing codecs are preferred thus
|
|
// relaying will be used if possible).
|
|
if (call_profile.codec_prefs.shouldOrderPayloads(a_leg)) {
|
|
call_profile.codec_prefs.orderSDP(sdp, a_leg);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
// Add transcoder codecs before filtering because otherwise SDP filter could
|
|
// inactivate some media lines which shouldn't be inactivated.
|
|
|
|
if (call_profile.transcoder.isActive()) {
|
|
appendTranscoderCodecs(sdp);
|
|
changed = true;
|
|
}
|
|
|
|
if (!prefer_existing_codecs) {
|
|
// existing codecs are not preferred - reorder AFTER adding transcoder
|
|
// codecs so it might happen that transcoding will be forced though relaying
|
|
// would be possible
|
|
if (call_profile.codec_prefs.shouldOrderPayloads(a_leg)) {
|
|
call_profile.codec_prefs.orderSDP(sdp, a_leg);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
// It doesn't make sense to filter out codecs allowed for transcoding and thus
|
|
// if the filter filters them out it can be considered as configuration
|
|
// problem, right?
|
|
// => So we wouldn't try to avoid filtering out transcoder codecs what would
|
|
// just complicate things.
|
|
|
|
if (call_profile.sdpfilter.size()) {
|
|
res = filterSDP(sdp, call_profile.sdpfilter);
|
|
changed = true;
|
|
}
|
|
if (call_profile.sdpalinesfilter.size()) {
|
|
// filter SDP "a=lines"
|
|
filterSDPalines(sdp, call_profile.sdpalinesfilter);
|
|
changed = true;
|
|
}
|
|
|
|
if (changed) {
|
|
string n_body;
|
|
sdp.print(n_body);
|
|
sdp_body->setPayload((const unsigned char*)n_body.c_str(), n_body.length());
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void SBCCallLeg::appendTranscoderCodecs(AmSdp &sdp)
|
|
{
|
|
// append codecs for transcoding, remember the added ones to be able to filter
|
|
// them out from relayed reply!
|
|
|
|
// important: normalized SDP should get here
|
|
|
|
TRACE("going to append transcoder codecs into SDP\n");
|
|
const std::vector<SdpPayload> &transcoder_codecs = call_profile.transcoder.audio_codecs;
|
|
|
|
unsigned stream_idx = 0;
|
|
vector<SdpPayload>::const_iterator p;
|
|
for (vector<SdpMedia>::iterator m = sdp.media.begin(); m != sdp.media.end(); ++m) {
|
|
|
|
// handle audio transcoder codecs
|
|
if (m->type == MT_AUDIO) {
|
|
// transcoder codecs can be added only if there are common payloads with
|
|
// the remote (only those allowed for transcoder)
|
|
// if there are no such common payloads adding transcoder codecs can't help
|
|
// because we won't be able to transcode later on!
|
|
// (we have to check for each media stream independently)
|
|
|
|
// find first unused dynamic payload number & detect transcodable codecs
|
|
// in original SDP
|
|
int id = 96;
|
|
bool transcodable = false;
|
|
PayloadMask used_payloads;
|
|
for (p = m->payloads.begin(); p != m->payloads.end(); ++p) {
|
|
if (p->payload_type >= id) id = p->payload_type + 1;
|
|
if (containsPayload(transcoder_codecs, *p, m->transport)) transcodable = true;
|
|
used_payloads.set(p->payload_type);
|
|
}
|
|
|
|
if (transcodable) {
|
|
// there are some transcodable codecs present in the SDP, we can safely
|
|
// add the other transcoder codecs to the SDP
|
|
unsigned idx = 0;
|
|
for (p = transcoder_codecs.begin(); p != transcoder_codecs.end(); ++p, ++idx) {
|
|
// add all payloads which are not already there
|
|
if (!containsPayload(m->payloads, *p, m->transport)) {
|
|
m->payloads.push_back(*p);
|
|
int &pid = m->payloads.back().payload_type;
|
|
if (pid < 0) {
|
|
// try to use remembered ID
|
|
pid = transcoder_payload_mapping.get(stream_idx, idx);
|
|
}
|
|
|
|
if ((pid < 0) || used_payloads.get(pid)) {
|
|
// payload ID is not set or is already used in current SDP, we
|
|
// need to assign a new one
|
|
pid = id++;
|
|
}
|
|
}
|
|
}
|
|
if (id > 128) ERROR("assigned too high payload type number (%d), see RFC 3551\n", id);
|
|
}
|
|
else {
|
|
// no compatible codecs found
|
|
TRACE("can not transcode stream %d - no compatible codecs with transcoder_codecs found\n", stream_idx + 1);
|
|
}
|
|
|
|
stream_idx++; // count chosen media type only
|
|
}
|
|
}
|
|
|
|
// remembered payload IDs should be used just once, in SDP answer
|
|
// unfortunatelly the SDP answer might be present in 1xx and in 2xx as well so
|
|
// we can't clear it here
|
|
// on other hand it might be useful to use the same payload ID if offer/answer
|
|
// is repeated in the other direction next time
|
|
}
|
|
|
|
void SBCCallLeg::savePayloadIDs(AmSdp &sdp)
|
|
{
|
|
unsigned stream_idx = 0;
|
|
std::vector<SdpPayload> &transcoder_codecs = call_profile.transcoder.audio_codecs;
|
|
for (vector<SdpMedia>::iterator m = sdp.media.begin(); m != sdp.media.end(); ++m) {
|
|
if (m->type != MT_AUDIO) continue;
|
|
|
|
unsigned idx = 0;
|
|
for (vector<SdpPayload>::iterator p = transcoder_codecs.begin();
|
|
p != transcoder_codecs.end(); ++p, ++idx)
|
|
{
|
|
if (p->payload_type < 0) {
|
|
const SdpPayload *pp = findPayload(m->payloads, *p, m->transport);
|
|
if (pp && (pp->payload_type >= 0))
|
|
transcoder_payload_mapping.map(stream_idx, idx, pp->payload_type);
|
|
}
|
|
}
|
|
|
|
stream_idx++; // count chosen media type only
|
|
}
|
|
}
|
|
|
|
bool SBCCallLeg::reinvite(const AmSdp &sdp, unsigned &request_cseq)
|
|
{
|
|
request_cseq = 0;
|
|
|
|
AmMimeBody body;
|
|
AmMimeBody *sdp_body = body.addPart(SIP_APPLICATION_SDP);
|
|
if (!sdp_body) return false;
|
|
|
|
string body_str;
|
|
sdp.print(body_str);
|
|
sdp_body->parse(SIP_APPLICATION_SDP, (const unsigned char*)body_str.c_str(), body_str.length());
|
|
|
|
if (dlg->reinvite("", &body, SIP_FLAGS_VERBATIM) != 0) return false;
|
|
request_cseq = dlg->cseq - 1;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* initialize modules from @arg cc_module_list with DI interface instances from @arg cc_module_di
|
|
* add sucessfull ext-API call control instances to cc_ext
|
|
* @return true on success (all modules properly initialized)
|
|
*/
|
|
bool SBCCallLeg::initCCExtModules(const CCInterfaceListT& cc_module_list, const vector<AmDynInvoke*>& cc_module_di)
|
|
{
|
|
// init extended call control modules
|
|
vector<AmDynInvoke*>::const_iterator cc_mod = cc_module_di.begin();
|
|
for (CCInterfaceListConstIteratorT cc_it = cc_module_list.begin(); cc_it != cc_module_list.end(); cc_it++)
|
|
{
|
|
const CCInterface& cc_if = *cc_it;
|
|
const string& cc_module = cc_it->cc_module;
|
|
|
|
// get extended CC interface
|
|
try {
|
|
AmArg args, ret;
|
|
(*cc_mod)->invoke("getExtendedInterfaceHandler", args, ret);
|
|
ExtendedCCInterface *iface = dynamic_cast<ExtendedCCInterface*>(ret[0].asObject());
|
|
if (iface) {
|
|
DBG("extended CC interface offered by cc_module '%s'\n", cc_module.c_str());
|
|
// module initialization
|
|
if (!iface->init(this, cc_if.cc_values)) {
|
|
ERROR("initializing extended call control interface '%s'\n", cc_module.c_str());
|
|
return false;
|
|
}
|
|
|
|
cc_ext.push_back(iface);
|
|
}
|
|
else WARN("BUG: returned invalid extended CC interface by cc_module '%s'\n", cc_module.c_str());
|
|
}
|
|
catch (const string& s) {
|
|
DBG("initialization error '%s' or extended CC interface "
|
|
"not supported by cc_module '%s'\n", s.c_str(), cc_module.c_str());
|
|
}
|
|
catch (...) {
|
|
DBG("initialization error or extended CC interface not "
|
|
"supported by cc_module '%s'\n", cc_module.c_str());
|
|
}
|
|
|
|
++cc_mod;
|
|
}
|
|
|
|
if (!initPendingCCExtModules()) {
|
|
return false;
|
|
}
|
|
|
|
return true; // success
|
|
}
|
|
|
|
/** init pending modules until queue is empty */
|
|
bool SBCCallLeg::initPendingCCExtModules() {
|
|
while (cc_module_queue.size()) {
|
|
// local copy
|
|
CCInterfaceListT _cc_mod_queue = cc_module_queue;
|
|
cc_module_queue.clear();
|
|
vector<AmDynInvoke*> _cc_mod_ifs;
|
|
|
|
// get corresponding DI interfaces
|
|
if (!::getCCInterfaces(_cc_mod_queue, _cc_mod_ifs))
|
|
return false;
|
|
|
|
// add to call leg
|
|
if (!initCCExtModules(_cc_mod_queue, _cc_mod_ifs)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void SBCCallLeg::addPendingCCExtModule(const string& cc_name, const string& cc_module, const map<string, string>& cc_values) {
|
|
cc_module_queue.push_back(CCInterface(cc_name));
|
|
cc_module_queue.back().cc_module = cc_module;
|
|
cc_module_queue.back().cc_values = cc_values;
|
|
DBG("added module '%s' from module '%s' to pending CC Ext modules\n",
|
|
cc_name.c_str(), cc_module.c_str());
|
|
}
|
|
|
|
#define CALL_EXT_CC_MODULES(method) \
|
|
do { \
|
|
for (vector<ExtendedCCInterface*>::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) { \
|
|
(*i)->method(this); \
|
|
} \
|
|
} while (0)
|
|
|
|
void SBCCallLeg::holdRequested()
|
|
{
|
|
TRACE("%s: hold requested\n", getLocalTag().c_str());
|
|
CALL_EXT_CC_MODULES(holdRequested);
|
|
CallLeg::holdRequested();
|
|
}
|
|
|
|
void SBCCallLeg::holdAccepted()
|
|
{
|
|
TRACE("%s: hold accepted\n", getLocalTag().c_str());
|
|
CALL_EXT_CC_MODULES(holdAccepted);
|
|
CallLeg::holdAccepted();
|
|
}
|
|
|
|
void SBCCallLeg::holdRejected()
|
|
{
|
|
TRACE("%s: hold rejected\n", getLocalTag().c_str());
|
|
CALL_EXT_CC_MODULES(holdRejected);
|
|
CallLeg::holdRejected();
|
|
}
|
|
|
|
void SBCCallLeg::resumeRequested()
|
|
{
|
|
TRACE("%s: resume requested\n", getLocalTag().c_str());
|
|
CALL_EXT_CC_MODULES(resumeRequested);
|
|
CallLeg::resumeRequested();
|
|
}
|
|
|
|
void SBCCallLeg::resumeAccepted()
|
|
{
|
|
TRACE("%s: resume accepted\n", getLocalTag().c_str());
|
|
CALL_EXT_CC_MODULES(resumeAccepted);
|
|
CallLeg::resumeAccepted();
|
|
}
|
|
|
|
void SBCCallLeg::resumeRejected()
|
|
{
|
|
TRACE("%s: resume rejected\n", getLocalTag().c_str());
|
|
CALL_EXT_CC_MODULES(resumeRejected);
|
|
CallLeg::resumeRejected();
|
|
}
|
|
|
|
static void replace_address(SdpConnection &c, const string &ip)
|
|
{
|
|
if (!c.address.empty()) {
|
|
if (c.addrType == AT_V4) {
|
|
c.address = ip;
|
|
return;
|
|
}
|
|
// TODO: IPv6?
|
|
DBG("unsupported address type for replacing IP");
|
|
}
|
|
}
|
|
|
|
static void alterHoldRequest(AmSdp &sdp, SBCCallProfile::HoldSettings::Activity a, const string &ip)
|
|
{
|
|
if (!ip.empty()) replace_address(sdp.conn, ip);
|
|
for (vector<SdpMedia>::iterator m = sdp.media.begin(); m != sdp.media.end(); ++m) {
|
|
if (!ip.empty()) replace_address(m->conn, ip);
|
|
m->recv = (a == SBCCallProfile::HoldSettings::sendrecv || a == SBCCallProfile::HoldSettings::recvonly);
|
|
m->send = (a == SBCCallProfile::HoldSettings::sendrecv || a == SBCCallProfile::HoldSettings::sendonly);
|
|
}
|
|
}
|
|
|
|
void SBCCallLeg::alterHoldRequestImpl(AmSdp &sdp)
|
|
{
|
|
if (call_profile.hold_settings.mark_zero_connection(a_leg)) {
|
|
static const string zero("0.0.0.0");
|
|
::alterHoldRequest(sdp, call_profile.hold_settings.activity(a_leg), zero);
|
|
}
|
|
else {
|
|
if (getRtpRelayMode() == RTP_Direct) {
|
|
// we can not put our IP there if not relaying, using empty not to
|
|
// overwrite existing addresses
|
|
static const string empty;
|
|
::alterHoldRequest(sdp, call_profile.hold_settings.activity(a_leg), empty);
|
|
}
|
|
else {
|
|
// use public IP to be put into connection addresses (overwrite 0.0.0.0
|
|
// there)
|
|
::alterHoldRequest(sdp, call_profile.hold_settings.activity(a_leg), advertisedIP());
|
|
}
|
|
}
|
|
}
|
|
|
|
void SBCCallLeg::alterHoldRequest(AmSdp &sdp)
|
|
{
|
|
TRACE("altering B2B hold request(%s, %s, %s)\n",
|
|
call_profile.hold_settings.alter_b2b(a_leg) ? "alter B2B" : "do not alter B2B",
|
|
call_profile.hold_settings.mark_zero_connection(a_leg) ? "0.0.0.0" : "own IP",
|
|
call_profile.hold_settings.activity_str(a_leg).c_str()
|
|
);
|
|
|
|
if (!call_profile.hold_settings.alter_b2b(a_leg)) return;
|
|
|
|
alterHoldRequestImpl(sdp);
|
|
}
|
|
|
|
void SBCCallLeg::createHoldRequest(AmSdp &sdp)
|
|
{
|
|
// hack: we need to have other side SDP (if the stream is hold already
|
|
// it should be marked as inactive)
|
|
// FIXME: fix SDP versioning! (remember generated versions and increase the
|
|
// version number in every SDP passing through?)
|
|
|
|
AmMimeBody *s = established_body.hasContentType(SIP_APPLICATION_SDP);
|
|
if (s) sdp.parse((const char*)s->getPayload());
|
|
if (sdp.media.empty()) {
|
|
// established SDP is not valid! generate complete fake
|
|
sdp.version = 0;
|
|
sdp.origin.user = "sems";
|
|
sdp.sessionName = "sems";
|
|
sdp.conn.network = NT_IN;
|
|
sdp.conn.addrType = AT_V4;
|
|
sdp.conn.address = "0.0.0.0";
|
|
|
|
sdp.media.push_back(SdpMedia());
|
|
SdpMedia &m = sdp.media.back();
|
|
m.type = MT_AUDIO;
|
|
m.transport = TP_RTPAVP;
|
|
m.send = false;
|
|
m.recv = false;
|
|
m.payloads.push_back(SdpPayload(0));
|
|
}
|
|
|
|
AmB2BMedia *ms = getMediaSession();
|
|
if (ms) ms->replaceOffer(sdp, a_leg);
|
|
|
|
alterHoldRequestImpl(sdp);
|
|
}
|
|
|
|
void SBCCallLeg::setMediaSession(AmB2BMedia *new_session)
|
|
{
|
|
if (new_session) {
|
|
if (call_profile.log_rtp) new_session->setRtpLogger(logger);
|
|
else new_session->setRtpLogger(NULL);
|
|
}
|
|
CallLeg::setMediaSession(new_session);
|
|
}
|
|
|
|
bool SBCCallLeg::openLogger(const std::string &path)
|
|
{
|
|
file_msg_logger *log = new pcap_logger();
|
|
|
|
if(log->open(path.c_str()) != 0) {
|
|
// open error
|
|
delete log;
|
|
return false;
|
|
}
|
|
|
|
// opened successfully
|
|
setLogger(log);
|
|
return true;
|
|
}
|
|
|
|
void SBCCallLeg::setLogger(msg_logger *_logger)
|
|
{
|
|
if (logger) dec_ref(logger); // release the old one
|
|
|
|
logger = _logger;
|
|
if (logger) inc_ref(logger);
|
|
if (call_profile.log_sip) dlg->setMsgLogger(logger);
|
|
else dlg->setMsgLogger(NULL);
|
|
|
|
AmB2BMedia *m = getMediaSession();
|
|
if (m) {
|
|
if (call_profile.log_rtp) m->setRtpLogger(logger);
|
|
else m->setRtpLogger(NULL);
|
|
}
|
|
}
|
|
|
|
void SBCCallLeg::computeRelayMask(const SdpMedia &m, bool &enable, PayloadMask &mask)
|
|
{
|
|
if (call_profile.transcoder.isActive()) {
|
|
TRACE("entering transcoder's computeRelayMask(%s)\n", a_leg ? "A leg" : "B leg");
|
|
|
|
SBCCallProfile::TranscoderSettings &transcoder_settings = call_profile.transcoder;
|
|
PayloadMask m1, m2;
|
|
bool use_m1 = false;
|
|
|
|
/* if "m" contains only "norelay" codecs, relay is enabled for them (main idea
|
|
* of these codecs is to limit network bandwidth and it makes not much sense
|
|
* to transcode between codecs 'which are better to avoid', right?)
|
|
*
|
|
* if "m" contains other codecs, relay is enabled as well
|
|
*
|
|
* => if m contains at least some codecs, relay is enabled */
|
|
enable = !m.payloads.empty();
|
|
|
|
vector<SdpPayload> &norelay_payloads =
|
|
a_leg ? transcoder_settings.audio_codecs_norelay_aleg : transcoder_settings.audio_codecs_norelay;
|
|
|
|
vector<SdpPayload>::const_iterator p;
|
|
for (p = m.payloads.begin(); p != m.payloads.end(); ++p) {
|
|
|
|
// do not mark telephone-event payload for relay (and do not use it for
|
|
// transcoding as well)
|
|
if(strcasecmp("telephone-event",p->encoding_name.c_str()) == 0) continue;
|
|
|
|
// mark every codec for relay in m2
|
|
TRACE("m2: marking payload %d for relay\n", p->payload_type);
|
|
m2.set(p->payload_type);
|
|
|
|
if (!containsPayload(norelay_payloads, *p, m.transport)) {
|
|
// this payload can be relayed
|
|
|
|
TRACE("m1: marking payload %d for relay\n", p->payload_type);
|
|
m1.set(p->payload_type);
|
|
|
|
if (!use_m1 && containsPayload(transcoder_settings.audio_codecs, *p, m.transport)) {
|
|
// the remote SDP contains transcodable codec which can be relayed (i.e.
|
|
// the one with higher "priority" so we want to disable relaying of the
|
|
// payloads which should not be ralyed if possible)
|
|
use_m1 = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
TRACE("using %s\n", use_m1 ? "m1" : "m2");
|
|
if (use_m1) mask = m1;
|
|
else mask = m2;
|
|
}
|
|
else {
|
|
// for non-transcoding modes use default
|
|
CallLeg::computeRelayMask(m, enable, mask);
|
|
}
|
|
}
|