/* * 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 using namespace std; #define TRACE DBG // helper functions static const SdpPayload *findPayload(const std::vector& payloads, const SdpPayload &payload, int transport) { string pname = payload.encoding_name; transform(pname.begin(), pname.end(), pname.begin(), ::tolower); for (vector::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& 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::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::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(ev); assert(req_ev); if (call_profile.headerfilter.size()) { //B2BSipRequestEvent* req_ev = dynamic_cast(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(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 >::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::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::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::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::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::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::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(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(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(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::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::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::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::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::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;isetStatus(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 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::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::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::iterator i = cc_ext.begin(); i != cc_ext.end(); ++i) { (*i)->onStateChange(this, cause); } } void SBCCallLeg::onBLegRefused(const AmSipReply& reply) { for (vector::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::iterator it = rtp_pegs.begin(); it != rtp_pegs.end(); ++it) { (*it)->inc(p->getBufferSize()); } } void SBCCallLeg::logCallStart(const AmSipReply& reply) { std::map::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::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 &transcoder_codecs = call_profile.transcoder.audio_codecs; unsigned stream_idx = 0; vector::const_iterator p; for (vector::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 &transcoder_codecs = call_profile.transcoder.audio_codecs; for (vector::iterator m = sdp.media.begin(); m != sdp.media.end(); ++m) { if (m->type != MT_AUDIO) continue; unsigned idx = 0; for (vector::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& cc_module_di) { // init extended call control modules vector::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(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 _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& 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::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::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 &norelay_payloads = a_leg ? transcoder_settings.audio_codecs_norelay_aleg : transcoder_settings.audio_codecs_norelay; vector::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); } }