From de2b64b26b04c12bc7f3d6c92187fad05b2cf947 Mon Sep 17 00:00:00 2001 From: marian Date: Tue, 18 Sep 2012 08:40:29 +0000 Subject: [PATCH] Allow send dtmf method(s) to be configurable. Detect remote party INFO support from 'Allow' header. git-svn-id: http://voip.null.ro/svn/yate@5264 acf43c95-373e-0410-b603-e72c3f656dc1 --- conf.d/ysipchan.conf.sample | 35 +++- modules/ysipchan.cpp | 342 ++++++++++++++++++++++++++++++------ 2 files changed, 322 insertions(+), 55 deletions(-) diff --git a/conf.d/ysipchan.conf.sample b/conf.d/ysipchan.conf.sample index f16d66b6..0e2248a3 100644 --- a/conf.d/ysipchan.conf.sample +++ b/conf.d/ysipchan.conf.sample @@ -94,11 +94,38 @@ ; transactions unless a retransmission arrives before having a final answer ;lazy100=no -; dtmfinband: bool: Generate DTMF inband by default -;dtmfinband=no +; check_allow_info: bool: Check 'Allow' header in INVITE and OK for INFO support +; If enabled and INFO is not supported the 'info' dtmf method will be disabled +; This parameter can be overridden from routing by 'ocheck_allow_info' for outgoing call leg +; and 'icheck_allow_info' for incoming call leg +; This parameter is ignored if info method is not enabled +; This parameter is applied on reload for new calls only +;check_allow_info=yes -; dtmfinfo: bool: Generate INFO messages to send keypad tones -;dtmfinfo=no +; missing_allow_info: bool: The default value for dtmf info support if +; 'check_allow_info' is enabled and the 'Allow' header is missing +; This parameter can be overridden from routing by 'omissing_allow_info' for outgoing call leg +; and 'imissing_allow_info' for incoming call leg +; This parameter is applied on reload for new calls only +;missing_allow_info=enable + +; dtmfmethods: string: Comma separated list of methods used to send DTMFs +; Allowed values in list: +; info: Use SIP INFO if initial transaction finished +; rfc2833: Use RFC 2833 signals if remote party advertised support +; inband: Send tones in audio stream +; The methods will be used in the listed order +; Defaults to 'rfc2833,info,inband' if missing or empty +; Invalid values are ignored +; E.g. +; 'info,foo' leads to 'info' +; 'foo,foo1' leads to 'rfc2833,info,inband' +; This parameter can be overridden from routing by 'odtmfmethods' for outgoing call leg +; and 'idtmfmethods' for incoming call leg +; Also, this parameter can be overridden in chan.dtmf messages by a 'methods' parameter +; NOTE: When overridden from chan.dtmf an empty or invalid 'methods' parameter will be ignored +; This parameter is applied on reload for new calls only +;dtmfmethods=rfc2833,info,inband ; rfc2833: bool: Offer RFC2833 telephone-event by default ; A numeric payload >= 96 can be provided diff --git a/modules/ysipchan.cpp b/modules/ysipchan.cpp index 6a58007b..98ca561a 100644 --- a/modules/ysipchan.cpp +++ b/modules/ysipchan.cpp @@ -144,6 +144,58 @@ private: ProtocolHolder() {} // No default }; +class DtmfMethods +{ +public: + enum Method { + Info, + Rfc2833, + Inband, + MethodCount + }; + inline DtmfMethods() + { setDefault(); } + inline void set(int _0 = MethodCount, int _1 = MethodCount, int _2 = MethodCount) { + m_methods[0] = _0; + m_methods[1] = _1; + m_methods[2] = _2; + } + inline void setDefault() + { set(Rfc2833,Info,Inband); } + // Replace all methods from comma separated list + // If no method is set use other or setDefEmpty (reset to default) + // Return false if methods contain unknown methods + bool set(const String& methods, const DtmfMethods* other, bool setDefEmpty = true); + // Retrieve a method from deperecated parameters + // Reset the method if the parameter is false + // Display a message anyway if warn is not false + // Return true if the parameter was found + bool getDeprecatedDtmfMethod(const NamedList& list, const char* param, int method, bool* warn); + // Reset a method + void reset(int method); + // Build a string list from methods + void buildMethods(String& buf, const char* sep = ","); + bool hasMethod(int method); + inline void printMethods(DebugEnabler* enabler, int level, const String& str) { + String tmp; + buildMethods(tmp); + Debug(enabler,level,"Built DTMF methods '%s' from '%s'",tmp.safe(),str.safe()); + } + inline int operator[](unsigned int index) { + if (index < MethodCount) + return m_methods[index]; + return MethodCount; + } + inline DtmfMethods& operator=(const DtmfMethods& other) { + for (int i = 0; i < MethodCount; i++) + m_methods[i] = other.m_methods[i]; + return *this; + } + static const TokenDict s_methodName[]; +protected: + int m_methods[MethodCount]; +}; + // A SIP party holder class YateSIPPartyHolder : public ProtocolHolder { @@ -883,6 +935,8 @@ private: MimeBody* buildSIPBody(); // Update NAT address from params or transport void updateRtpNatAddress(NamedList* params = 0); + // Process allow list. Get INFO support + bool infoAllowed(const SIPMessage* msg); SIPTransaction* m_tr; SIPTransaction* m_tr2; @@ -907,8 +961,9 @@ private: Message* m_route; ObjList* m_routes; bool m_authBye; - bool m_inband; - bool m_info; + bool m_checkAllowInfo; // Check Allow in INVITE and OK for INFO support + bool m_missingAllowInfoDefVal; // Default INFO support if Allow header is missing + DtmfMethods m_dtmfMethods; // REFER already running bool m_referring; // reINVITE requested or in progress @@ -1037,8 +1092,6 @@ static int s_nat_refresh = 25; static bool s_privacy = false; static bool s_auto_nat = true; static bool s_progress = false; -static bool s_inband = false; -static bool s_info = false; static bool s_start_rtp = false; static bool s_ack_required = true; static bool s_1xx_formats = true; @@ -1072,6 +1125,24 @@ static u_int64_t s_printFloodTime = 0; int YateSIPEndPoint::s_evCount = 0; +// DTMF methods +static bool s_checkAllowInfo = true; // Check Allow in INVITE and OK for INFO support +static bool s_missingAllowInfoDefVal = true; // Default INFO support if Allow header is missing +static DtmfMethods s_dtmfMethods; +// Deprecated dtmf params warn +static bool s_warnDtmfInfoCfg = true; +static bool s_warnDtmfInbandCfg = true; +static bool s_warnDtmfInfoCallExecute = true; +static bool s_warnDtmfInbandCallExecute = true; +static bool s_warnDtmfMethodChanDtmf = true; + +const TokenDict DtmfMethods::s_methodName[] = { + { "info", Info}, + { "rfc2833", Rfc2833}, + { "inband", Inband}, + { 0, 0 }, +}; + // Lower case proto name const TokenDict ProtocolHolder::s_protoLC[] = { { "udp", Udp}, @@ -1777,6 +1848,79 @@ static void setAuthError(SIPTransaction* trans, const NamedList& params, } +// Replace all methods from comma separated list +// If no method is set use other or setDefEmpty (reset to default) +bool DtmfMethods::set(const String& methods, const DtmfMethods* other, bool setDefEmpty) +{ + set(); + bool found = false; + bool ok = true; + ObjList* m = methods.split(','); + int i = 0; + for (ObjList* o = m->skipNull(); o && i < MethodCount; o = o->skipNext()) { + String* s = static_cast(o->get()); + int meth = lookup(s->trimBlanks(),s_methodName,MethodCount); + if (meth != MethodCount) { + m_methods[i++] = meth; + found = true; + } + else if (*s) + ok = false; + } + TelEngine::destruct(m); + if (!found) { + if (other) + *this = *other; + else if (setDefEmpty) + setDefault(); + } + return ok; +} + +// Retrieve a method from deperecated parameters +// Reset the method if the parameter is false +// Display a message anyway if warn is not false +bool DtmfMethods::getDeprecatedDtmfMethod(const NamedList& list, const char* param, + int method, bool* warn) +{ + String* p = list.getParam(param); + if (!p) + return false; + if (!p->toBoolean()) + reset(method); + if (warn && *warn) { + *warn = false; + Debug(&plugin,DebugConf,"Deprecated '%s' in '%s'. Use 'dtmfmethods' instead!",param,list.c_str()); + } + return true; +} + +// Reset a method +void DtmfMethods::reset(int method) +{ + for (int i = 0; i < MethodCount; i++) + if (m_methods[i] == method) { + m_methods[i] = MethodCount; + break; + } +} + +// Build a string list from methods +void DtmfMethods::buildMethods(String& buf, const char* sep) +{ + for (int i = 0; i < MethodCount; i++) + buf.append(lookup(m_methods[i],s_methodName),sep); +} + +bool DtmfMethods::hasMethod(int method) +{ + for (int i = 0; i < MethodCount; i++) + if (m_methods[i] == method) + return true; + return false; +} + + bool YateSIPPartyHolder::change(String& dest, const String& src) { if (dest == src) @@ -4913,7 +5057,8 @@ YateSIPConnection::YateSIPConnection(SIPEvent* ev, SIPTransaction* tr) YateSIPPartyHolder(driver()), m_tr(tr), m_tr2(0), m_hungup(false), m_byebye(true), m_cancel(false), m_state(Incoming), m_port(0), m_route(0), m_routes(0), - m_authBye(true), m_inband(s_inband), m_info(s_info), + m_authBye(true), + m_checkAllowInfo(s_checkAllowInfo), m_missingAllowInfoDefVal(s_missingAllowInfoDefVal), m_referring(false), m_reInviting(ReinviteNone), m_lastRseq(0) { Debug(this,DebugAll,"YateSIPConnection::YateSIPConnection(%p,%p) [%p]",ev,tr,this); @@ -4931,6 +5076,10 @@ YateSIPConnection::YateSIPConnection(SIPEvent* ev, SIPTransaction* tr) // Set channel SIP party setParty(m_tr->initialMessage()->getParty()); updateRtpNatAddress(); + // Get dtmf methods + s_globalMutex.lock(); + m_dtmfMethods = s_dtmfMethods; + s_globalMutex.unlock(); URI uri(m_tr->getURI()); YateSIPLine* line = plugin.findLine(m_host,m_port,uri.getUser()); @@ -5080,15 +5229,26 @@ YateSIPConnection::YateSIPConnection(Message& msg, const String& uri, const char YateSIPPartyHolder(driver()), m_tr(0), m_tr2(0), m_hungup(false), m_byebye(true), m_cancel(true), m_state(Outgoing), m_port(0), m_route(0), m_routes(0), - m_authBye(false), m_inband(s_inband), m_info(s_info), + m_authBye(false), + m_checkAllowInfo(s_checkAllowInfo), m_missingAllowInfoDefVal(s_missingAllowInfoDefVal), m_referring(false), m_reInviting(ReinviteNone), m_lastRseq(0) { Debug(this,DebugAll,"YateSIPConnection::YateSIPConnection(%p,'%s') [%p]", &msg,uri.c_str(),this); m_targetid = target; setReason(); - m_inband = msg.getBoolValue(YSTRING("dtmfinband"),s_inband); - m_info = msg.getBoolValue(YSTRING("dtmfinfo"),s_info); + m_checkAllowInfo = msg.getBoolValue(YSTRING("ocheck_allow_info"),m_checkAllowInfo); + m_missingAllowInfoDefVal = msg.getBoolValue(YSTRING("omissing_allow_info"),m_missingAllowInfoDefVal); + String* meths = msg.getParam("odtmfmethods"); + s_globalMutex.lock(); + if (meths) + m_dtmfMethods.set(*meths,&s_dtmfMethods); + else { + m_dtmfMethods = s_dtmfMethods; + m_dtmfMethods.getDeprecatedDtmfMethod(msg,"dtmfinfo",DtmfMethods::Info,&s_warnDtmfInfoCallExecute); + m_dtmfMethods.getDeprecatedDtmfMethod(msg,"dtmfinband",DtmfMethods::Inband,&s_warnDtmfInbandCallExecute); + } + s_globalMutex.unlock(); m_secure = msg.getBoolValue(YSTRING("secure"),plugin.parser().secure()); setRfc2833(msg.getParam(YSTRING("rfc2833"))); m_rtpForward = msg.getBoolValue(YSTRING("rtp_forward")); @@ -5733,6 +5893,10 @@ bool YateSIPConnection::process(SIPEvent* ev) } if (msg->isAnswer() && ((msg->code / 100) == 2)) { + if (m_checkAllowInfo) { + if (m_dtmfMethods.hasMethod(DtmfMethods::Info) && !infoAllowed(msg)) + m_dtmfMethods.reset(DtmfMethods::Info); + } updateTags = false; m_cancel = false; Lock lock(driver()); @@ -6386,58 +6550,80 @@ bool YateSIPConnection::msgTone(Message& msg, const char* tone) { if (m_hungup) return false; - bool info = m_info; - bool inband = m_inband; - const String* method = msg.getParam(YSTRING("method")); - if (method) { - if ((*method == YSTRING("info")) || (*method == YSTRING("sip-info"))) { - info = true; - inband = false; - } - else if (*method == YSTRING("rfc2833")) { - info = false; - inband = false; - } - else if (*method == YSTRING("inband")) { - info = false; - inband = true; - } - } - // RFC 2833 and inband require that we have an active local RTP stream - if (m_rtpMedia && (m_mediaStatus == MediaStarted) && !info) { - ObjList* l = m_rtpMedia->find("audio"); - const SDPMedia* m = static_cast(l ? l->get() : 0); - if (m) { - if (!(inband || m->rfc2833().toBoolean(true))) { - Debug(this,DebugNote,"Forcing DTMF '%s' inband, format '%s' [%p]", - tone,m->format().c_str(),this); - inband = true; + if (TelEngine::null(tone)) + return true; + DtmfMethods methods = m_dtmfMethods; + const String* param = msg.getParam(YSTRING("methods")); + if (param) + methods.set(*param,&m_dtmfMethods); + else { + const String* method = msg.getParam(YSTRING("method")); + if (method) { + if (s_warnDtmfMethodChanDtmf) { + s_warnDtmfMethodChanDtmf = false; + Debug(this,DebugConf,"Deprecated 'method' parameter in '%s'. Use 'methods' instead!",msg.c_str()); } - if (inband && dtmfInband(tone)) - return true; - msg.setParam("targetid",m->id()); - return false; + if ((*method == YSTRING("info")) || (*method == YSTRING("sip-info"))) + methods.set(DtmfMethods::Info); + else if (*method == YSTRING("rfc2833")) + methods.set(DtmfMethods::Rfc2833); + else if (*method == YSTRING("inband")) + methods.set(DtmfMethods::Inband); } } - // either INFO was requested or we have no other choice - for (; tone && *tone; tone++) { - char c = *tone; - for (int i = 0; i <= 16; i++) { - if (s_dtmfs[i] == c) { - SIPMessage* m = createDlgMsg("INFO"); - if (m) { + bool retVal = false; + bool ok = false; + for (int i = 0; !ok && i < DtmfMethods::MethodCount; i++) { + int meth = methods[i]; + if (meth == DtmfMethods::Info) { + // Send INFO only if initial transaction finished + if (m_tr) + continue; + for (; tone && *tone; tone++) { + char c = *tone; + for (int j = 0; j <= 16; j++) { + if (s_dtmfs[j] != c) + continue; + SIPMessage* m = createDlgMsg("INFO"); + if (!m) + break; copySipHeaders(*m,msg); String tmp; - tmp << "Signal=" << i << "\r\n"; + tmp << "Signal=" << j << "\r\n"; m->setBody(new MimeStringBody("application/dtmf-relay",tmp)); plugin.ep()->engine()->addMessage(m); m->deref(); + break; } - break; + } + ok = true; + retVal = true; + } + else if (meth == DtmfMethods::Rfc2833 || meth == DtmfMethods::Inband) { + // RFC2833 and inband require media to be started + if (!(m_rtpMedia && (m_mediaStatus == MediaStarted))) + continue; + ObjList* l = m_rtpMedia->find("audio"); + const SDPMedia* m = static_cast(l ? l->get() : 0); + if (!m) + continue; + if (meth == DtmfMethods::Rfc2833) { + ok = m->rfc2833().toBoolean(true); + if (ok) + msg.setParam("targetid",m->id()); + } + else { + ok = dtmfInband(tone); + retVal = ok; } } } - return true; + if (!ok && debugAt(DebugNote)) { + String tmp; + methods.buildMethods(tmp); + Debug(this,DebugNote,"Failed to send tones '%s' methods=%s [%p]",tone,tmp.c_str(),this); + } + return retVal; } bool YateSIPConnection::msgText(Message& msg, const char* text) @@ -6670,6 +6856,20 @@ void YateSIPConnection::callAccept(Message& msg) m_rtpForward = false; } m_secure = m_secure && msg.getBoolValue(YSTRING("secure"),true); + // Update dtmf methods from message + m_checkAllowInfo = msg.getBoolValue(YSTRING("icheck_allow_info"),m_checkAllowInfo); + m_missingAllowInfoDefVal = msg.getBoolValue(YSTRING("imissing_allow_info"),m_missingAllowInfoDefVal); + String* meths = msg.getParam(YSTRING("idtmfmethods")); + if (meths) { + DtmfMethods old = m_dtmfMethods; + m_dtmfMethods.set(*meths,&old); + } + // Reset INFO? + if (m_checkAllowInfo && m_tr && m_dtmfMethods.hasMethod(DtmfMethods::Info)) { + Lock lock(driver()); + if (m_tr && !infoAllowed(m_tr->initialMessage())) + m_dtmfMethods.reset(DtmfMethods::Info); + } Channel::callAccept(msg); if ((m_reInviting == ReinviteNone) && !m_rtpForward && !isAnswered() && @@ -6900,6 +7100,32 @@ void YateSIPConnection::updateRtpNatAddress(NamedList* params) Debug(this,DebugAll,"NAT address is '%s' [%p]",m_rtpNatAddr.c_str(),this); } +// Process allow list. Get INFO support +bool YateSIPConnection::infoAllowed(const SIPMessage* msg) +{ + static const String s_infoName = "INFO"; + if (!msg) + return m_missingAllowInfoDefVal; + bool ok = false; + const MimeHeaderLine* hdr = msg->getHeader("Allow"); + if (hdr) { + ObjList* allows = hdr->split(','); + for (ObjList* o = allows->skipNull(); o; o = o->skipNext()) { + String* s = static_cast(o->get()); + s->trimBlanks().toUpper(); + if (*s == s_infoName) { + ok = true; + break; + } + } + TelEngine::destruct(allows); + } + else + ok = m_missingAllowInfoDefVal; + Debug(this,DebugNote,"infoAllowed: info=%u [%p]",ok,this); + return ok; +} + YateSIPLine::YateSIPLine(const String& name) : String(name), Mutex(true,"YateSIPLine"), @@ -7721,15 +7947,30 @@ void SIPDriver::initialize() s_cfg = Engine::configFile("ysipchan"); s_globalMutex.lock(); s_cfg.load(); + NamedList* general = s_cfg.getSection("general"); + if (general) { + String* dtmfMethods = general->getParam("dtmfmethods"); + if (dtmfMethods) { + if (!s_dtmfMethods.set(*dtmfMethods,0)) + s_dtmfMethods.printMethods(this,DebugConf,*dtmfMethods); + } + else { + s_dtmfMethods.setDefault(); + s_dtmfMethods.getDeprecatedDtmfMethod(*general,"dtmfinfo",DtmfMethods::Info,&s_warnDtmfInfoCfg); + s_dtmfMethods.getDeprecatedDtmfMethod(*general,"dtmfinband",DtmfMethods::Inband,&s_warnDtmfInbandCfg); + } + } + else + s_dtmfMethods.setDefault(); s_globalMutex.unlock(); + s_checkAllowInfo = s_cfg.getBoolValue("general","check_allow_info",true); + s_missingAllowInfoDefVal = s_cfg.getBoolValue("general","missing_allow_info",true); s_maxForwards = s_cfg.getIntValue("general","maxforwards",20); s_floodEvents = s_cfg.getIntValue("general","floodevents",100); s_floodProtection = s_cfg.getBoolValue("general","floodprotection",true); s_privacy = s_cfg.getBoolValue("general","privacy"); s_auto_nat = s_cfg.getBoolValue("general","nat",true); s_progress = s_cfg.getBoolValue("general","progress",false); - s_inband = s_cfg.getBoolValue("general","dtmfinband",false); - s_info = s_cfg.getBoolValue("general","dtmfinfo",false); s_start_rtp = s_cfg.getBoolValue("general","rtp_start",false); s_multi_ringing = s_cfg.getBoolValue("general","multi_ringing",false); s_refresh_nosdp = s_cfg.getBoolValue("general","refresh_nosdp",true); @@ -7788,7 +8029,6 @@ void SIPDriver::initialize() // Mark listeners m_endpoint->initializing(true); // Setup general listener - NamedList* general = s_cfg.getSection("general"); NamedList dummy("general"); NamedList* def = general; if (!def)