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
This commit is contained in:
marian 2012-09-18 08:40:29 +00:00
parent c12e643719
commit de2b64b26b
2 changed files with 322 additions and 55 deletions

View File

@ -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

View File

@ -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<String*>(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<const SDPMedia*>(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<const SDPMedia*>(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<String*>(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)