527 lines
14 KiB
C++
527 lines
14 KiB
C++
/**
|
|
* enumroute.cpp
|
|
* This file is part of the YATE Project http://YATE.null.ro
|
|
*
|
|
* ENUM routing module
|
|
*
|
|
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
|
* Copyright (C) 2004-2006 Null Team
|
|
*
|
|
* This program 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.
|
|
*
|
|
* This program 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <yatephone.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <arpa/nameser.h>
|
|
#include <resolv.h>
|
|
|
|
|
|
using namespace TelEngine;
|
|
namespace { // anonymous
|
|
|
|
#define ENUM_DEF_TIMEOUT 3
|
|
#define ENUM_DEF_RETRIES 2
|
|
#define ENUM_DEF_MINLEN 8
|
|
#define ENUM_DEF_MAXCALL 30000
|
|
|
|
class NAPTR : public GenObject
|
|
{
|
|
public:
|
|
NAPTR(int ord, int pref, const char* flags, const char* serv, const char* regexp, const char* replace);
|
|
bool replace(String& str);
|
|
inline int order() const
|
|
{ return m_order; }
|
|
inline int pref() const
|
|
{ return m_pref; }
|
|
inline const String& flags() const
|
|
{ return m_flags; }
|
|
inline const String& serv() const
|
|
{ return m_service; }
|
|
|
|
private:
|
|
int m_order;
|
|
int m_pref;
|
|
String m_flags;
|
|
String m_service;
|
|
Regexp m_regmatch;
|
|
String m_template;
|
|
String m_replace;
|
|
};
|
|
|
|
class EnumHandler : public MessageHandler
|
|
{
|
|
public:
|
|
inline EnumHandler(unsigned int prio = 90)
|
|
: MessageHandler("call.route",prio)
|
|
{ }
|
|
virtual bool received(Message& msg);
|
|
private:
|
|
static bool resolve(Message& msg,bool canRedirect);
|
|
static void addRoute(String& dest,const String& src);
|
|
};
|
|
|
|
class EnumModule : public Module
|
|
{
|
|
public:
|
|
inline EnumModule()
|
|
: Module("enumroute","route"), m_init(false)
|
|
{ }
|
|
virtual void initialize();
|
|
virtual void statusParams(String& str);
|
|
void genUpdate(Message& msg);
|
|
private:
|
|
bool m_init;
|
|
};
|
|
|
|
// weird but NS_MAXSTRING and dn_string() are NOT part of the resolver API...
|
|
#ifndef NS_MAXSTRING
|
|
#define NS_MAXSTRING 255
|
|
#endif
|
|
|
|
// copy one string (not domain) from response
|
|
static int dn_string(const unsigned char* end, const unsigned char* src, char *dest, int maxlen)
|
|
{
|
|
int n = src[0];
|
|
maxlen--;
|
|
if (maxlen > n)
|
|
maxlen = n;
|
|
if (dest && (maxlen > 0)) {
|
|
while ((maxlen-- > 0) && (src < end))
|
|
*dest++ = *++src;
|
|
*dest = 0;
|
|
}
|
|
return n+1;
|
|
}
|
|
|
|
|
|
static String s_prefix;
|
|
static String s_forkStop;
|
|
static String s_domains;
|
|
static unsigned int s_minlen;
|
|
static int s_timeout;
|
|
static int s_retries;
|
|
static int s_maxcall;
|
|
|
|
static bool s_redirect;
|
|
static bool s_autoFork;
|
|
static bool s_sipUsed;
|
|
static bool s_iaxUsed;
|
|
static bool s_h323Used;
|
|
static bool s_xmppUsed;
|
|
static bool s_telUsed;
|
|
static bool s_voiceUsed;
|
|
static bool s_pstnUsed;
|
|
static bool s_voidUsed;
|
|
|
|
static Mutex s_mutex(false,"EnumRoute");
|
|
static int s_queries = 0;
|
|
static int s_routed = 0;
|
|
static int s_reroute = 0;
|
|
|
|
static EnumModule emodule;
|
|
|
|
// Initializes the resolver library in the current thread
|
|
static bool resolvInit()
|
|
{
|
|
if ((_res.options & RES_INIT) == 0) {
|
|
// need to initialize in this thread
|
|
if (res_init())
|
|
return false;
|
|
}
|
|
// always set the timeout variables
|
|
_res.retrans = s_timeout;
|
|
_res.retry = s_retries;
|
|
return true;
|
|
}
|
|
|
|
// Perform DNS query, return list of only NAPTR records
|
|
static ObjList* naptrQuery(const char* dname)
|
|
{
|
|
unsigned char buf[2048];
|
|
int r,q,a;
|
|
unsigned char *p, *e;
|
|
DDebug(&emodule,DebugInfo,"Querying %s",dname);
|
|
r = res_query(dname,ns_c_in,ns_t_naptr,
|
|
buf,sizeof(buf));
|
|
XDebug(&emodule,DebugAll,"res_query %d",r);
|
|
if ((r < 0) || (r > (int)sizeof(buf)))
|
|
return 0;
|
|
p = buf+NS_QFIXEDSZ;
|
|
NS_GET16(q,p);
|
|
NS_GET16(a,p);
|
|
XDebug(&emodule,DebugAll,"questions: %d, answers: %d",q,a);
|
|
p = buf + NS_HFIXEDSZ;
|
|
e = buf + r;
|
|
for (; q > 0; q--) {
|
|
int n = dn_skipname(p,e);
|
|
if (n < 0)
|
|
return 0;
|
|
p += (n + NS_QFIXEDSZ);
|
|
}
|
|
XDebug(&emodule,DebugAll,"skipped questions");
|
|
ObjList* lst = 0;
|
|
for (; a > 0; a--) {
|
|
int ty,cl,sz;
|
|
long int tt;
|
|
char name[NS_MAXLABEL+1];
|
|
unsigned char* l;
|
|
int n = dn_expand(buf,e,p,name,sizeof(name));
|
|
if ((n <= 0) || (n > NS_MAXLABEL))
|
|
return lst;
|
|
buf[n] = 0;
|
|
p += n;
|
|
NS_GET16(ty,p);
|
|
NS_GET16(cl,p);
|
|
NS_GET32(tt,p);
|
|
NS_GET16(sz,p);
|
|
XDebug(&emodule,DebugAll,"found '%s' type %d size %d",name,ty,sz);
|
|
l = p;
|
|
p += sz;
|
|
if (ty == ns_t_naptr) {
|
|
int ord,pr;
|
|
char fla[NS_MAXSTRING+1];
|
|
char ser[NS_MAXSTRING+1];
|
|
char reg[NS_MAXSTRING+1];
|
|
char rep[NS_MAXLABEL+1];
|
|
NS_GET16(ord,l);
|
|
NS_GET16(pr,l);
|
|
n = dn_string(e,l,fla,sizeof(fla));
|
|
l += n;
|
|
n = dn_string(e,l,ser,sizeof(ser));
|
|
l += n;
|
|
n = dn_string(e,l,reg,sizeof(reg));
|
|
l += n;
|
|
n = dn_expand(buf,e,l,rep,sizeof(rep));
|
|
l += n;
|
|
DDebug(&emodule,DebugAll,"order=%d pref=%d flags='%s' serv='%s' regexp='%s' replace='%s'",
|
|
ord,pr,fla,ser,reg,rep);
|
|
if (!lst)
|
|
lst = new ObjList;
|
|
NAPTR* ptr;
|
|
ObjList* cur = lst;
|
|
// cycle existing records, insert at the right place
|
|
for (; cur; cur = cur->next()) {
|
|
ptr = static_cast<NAPTR*>(cur->get());
|
|
if (!ptr)
|
|
continue;
|
|
if (ptr->order() > ord)
|
|
break;
|
|
if (ptr->order() < ord)
|
|
continue;
|
|
// sort first by order and then by preference
|
|
if (ptr->pref() > pr)
|
|
break;
|
|
}
|
|
ptr = new NAPTR(ord,pr,fla,ser,reg,rep);
|
|
if (cur)
|
|
cur->insert(ptr);
|
|
else
|
|
lst->append(ptr);
|
|
}
|
|
}
|
|
return lst;
|
|
}
|
|
|
|
|
|
NAPTR::NAPTR(int ord, int pref, const char* flags, const char* serv, const char* regexp, const char* replace)
|
|
: m_order(ord), m_pref(pref), m_flags(flags), m_service(serv), m_replace(replace)
|
|
{
|
|
// use case-sensitive extended regular expressions
|
|
m_regmatch.setFlags(true,false);
|
|
if (!null(regexp)) {
|
|
// look for <sep>regexp<sep>template<sep>
|
|
char sep[2] = { regexp[0], 0 };
|
|
String tmp(regexp+1);
|
|
if (tmp.endsWith(sep)) {
|
|
int pos = tmp.find(sep);
|
|
if (pos > 0) {
|
|
m_regmatch = tmp.substr(0,pos);
|
|
m_template = tmp.substr(pos+1,tmp.length()-pos-2);
|
|
XDebug(&emodule,DebugAll,"NAPTR match '%s' template '%s'",m_regmatch.c_str(),m_template.c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Perform the Regexp replacement, return true if succeeded
|
|
bool NAPTR::replace(String& str)
|
|
{
|
|
if (m_regmatch && str.matches(m_regmatch)) {
|
|
str = str.replaceMatches(m_template);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// Routing message handler, performs checks and calls resolve method
|
|
bool EnumHandler::received(Message& msg)
|
|
{
|
|
if (s_domains.null() || !msg.getBoolValue("enumroute",true))
|
|
return false;
|
|
// perform per-thread initialization of resolver and timeout settings
|
|
if (!resolvInit())
|
|
return false;
|
|
return resolve(msg,s_telUsed);
|
|
}
|
|
|
|
// Resolver function, may call itself recursively at most once
|
|
bool EnumHandler::resolve(Message& msg,bool canRedirect)
|
|
{
|
|
// give preference to full (e164) called number if exists
|
|
String called(msg.getValue("calledfull"));
|
|
if (called.null())
|
|
called = msg.getValue("called");
|
|
if (called.null())
|
|
return false;
|
|
// check if the called starts with international prefix, remove it
|
|
if (!(called.startSkip("+",false) ||
|
|
(s_prefix && called.startSkip(s_prefix,false))))
|
|
return false;
|
|
if (called.length() < s_minlen)
|
|
return false;
|
|
s_mutex.lock();
|
|
ObjList* domains = s_domains.split(',',false);
|
|
s_mutex.unlock();
|
|
if (!domains)
|
|
return false;
|
|
bool rval = false;
|
|
// put the standard international prefix in front
|
|
called = "+" + called;
|
|
String tmp;
|
|
for (int i = called.length()-1; i > 0; i--)
|
|
tmp << called.at(i) << ".";
|
|
u_int64_t dt = Time::now();
|
|
ObjList* res = 0;
|
|
for (ObjList* l = domains; l; l = l->next()) {
|
|
const String* s = static_cast<const String*>(l->get());
|
|
if (!s || s->null())
|
|
continue;
|
|
res = naptrQuery(tmp + *s);
|
|
if (res)
|
|
break;
|
|
}
|
|
dt = Time::now() - dt;
|
|
Debug(&emodule,DebugInfo,"Returned %d NAPTR records in %u.%06u s",
|
|
res ? res->count() : 0,
|
|
(unsigned int)(dt / 1000000),
|
|
(unsigned int)(dt % 1000000));
|
|
domains->destruct();
|
|
bool reroute = false;
|
|
bool unassigned = false;
|
|
if (res) {
|
|
msg.retValue().clear();
|
|
bool autoFork = msg.getBoolValue("autofork",s_autoFork);
|
|
ObjList* cur = res;
|
|
for (; cur; cur = cur->next()) {
|
|
NAPTR* ptr = static_cast<NAPTR*>(cur->get());
|
|
if (!ptr)
|
|
continue;
|
|
DDebug(&emodule,DebugAll,"order=%d pref=%d '%s'",
|
|
ptr->order(),ptr->pref(),ptr->serv().c_str());
|
|
String serv = ptr->serv();
|
|
serv.toUpper();
|
|
String callto = called;
|
|
if (s_sipUsed && (serv == "E2U+SIP") && ptr->replace(callto)) {
|
|
addRoute(msg.retValue(),"sip/" + callto);
|
|
rval = true;
|
|
if (autoFork)
|
|
continue;
|
|
break;
|
|
}
|
|
if (s_iaxUsed && (serv == "E2U+IAX2") && ptr->replace(callto)) {
|
|
addRoute(msg.retValue(),"iax/" + callto);
|
|
rval = true;
|
|
if (autoFork)
|
|
continue;
|
|
break;
|
|
}
|
|
if (s_h323Used && (serv == "E2U+H323") && ptr->replace(callto)) {
|
|
addRoute(msg.retValue(),"h323/" + callto);
|
|
rval = true;
|
|
if (autoFork)
|
|
continue;
|
|
break;
|
|
}
|
|
if (s_xmppUsed && (serv == "E2U+XMPP") && ptr->replace(callto)) {
|
|
addRoute(msg.retValue(),"jingle/" + callto);
|
|
rval = true;
|
|
if (autoFork)
|
|
continue;
|
|
break;
|
|
}
|
|
if (s_pstnUsed && serv.startsWith("E2U+PSTN") && ptr->replace(callto)) {
|
|
addRoute(msg.retValue(),"pstn/" + callto);
|
|
rval = true;
|
|
if (autoFork)
|
|
continue;
|
|
break;
|
|
}
|
|
if (s_voiceUsed && serv.startsWith("E2U+VOICE") && ptr->replace(callto)) {
|
|
addRoute(msg.retValue(),"voice/" + callto);
|
|
rval = true;
|
|
if (autoFork)
|
|
continue;
|
|
break;
|
|
}
|
|
if (canRedirect && (serv == "E2U+TEL") && ptr->replace(callto)) {
|
|
if (callto.startSkip("tel:",false) ||
|
|
callto.startSkip("TEL:",false) ||
|
|
callto.startSkip("e164:",false) ||
|
|
callto.startSkip("E164:",false))
|
|
{
|
|
reroute = true;
|
|
rval = false;
|
|
msg.setParam("called",callto);
|
|
msg.clearParam("calledfull");
|
|
if (msg.retValue()) {
|
|
Debug(&emodule,DebugMild,"Redirect drops collected route: %s",
|
|
msg.retValue().c_str());
|
|
msg.retValue().clear();
|
|
}
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
if (s_voidUsed && serv.startsWith("E2U+VOID") && ptr->replace(callto)) {
|
|
// remember it's unassigned but still continue scanning
|
|
unassigned = true;
|
|
}
|
|
}
|
|
res->destruct();
|
|
}
|
|
s_mutex.lock();
|
|
if (rval) {
|
|
if (msg.retValue().startsWith("fork",true)) {
|
|
msg.setParam("maxcall",String(s_maxcall));
|
|
msg.setParam("fork.stop",s_forkStop);
|
|
}
|
|
else if (s_redirect)
|
|
msg.setParam("redirect",String::boolText(true));
|
|
}
|
|
s_queries++;
|
|
if (rval)
|
|
s_routed++;
|
|
if (reroute)
|
|
s_reroute++;
|
|
emodule.changed();
|
|
s_mutex.unlock();
|
|
if (reroute)
|
|
return resolve(msg,false);
|
|
if (unassigned && !rval) {
|
|
rval = true;
|
|
msg.retValue() = "-";
|
|
msg.setParam("error","unallocated");
|
|
}
|
|
return rval;
|
|
}
|
|
|
|
// Add one route to the result, take care of forking
|
|
void EnumHandler::addRoute(String& dest,const String& src)
|
|
{
|
|
if (dest.null())
|
|
dest = src;
|
|
else {
|
|
if (!dest.startsWith("fork",true))
|
|
dest = "fork " + dest;
|
|
dest << " | " << src;
|
|
}
|
|
}
|
|
|
|
|
|
void EnumModule::statusParams(String& str)
|
|
{
|
|
str.append("queries=",",") << s_queries << ",routed=" << s_routed << ",rerouted=" << s_reroute;
|
|
}
|
|
|
|
void EnumModule::genUpdate(Message& msg)
|
|
{
|
|
msg.setParam("queries",String(s_queries));
|
|
msg.setParam("routed",String(s_routed));
|
|
msg.setParam("rerouted",String(s_reroute));
|
|
}
|
|
|
|
void EnumModule::initialize()
|
|
{
|
|
Module::initialize();
|
|
Configuration cfg(Engine::configFile("enumroute"));
|
|
int prio = cfg.getIntValue("general","priority",0);
|
|
if ((prio <= 0) && !m_init)
|
|
return;
|
|
Output("Initializing ENUM routing");
|
|
s_mutex.lock();
|
|
// in most of the world this default international prefix should work
|
|
s_prefix = cfg.getValue("general","prefix","00");
|
|
s_domains = cfg.getValue("general","domains");
|
|
if (s_domains.null()) {
|
|
// old style, just for compatibility
|
|
s_domains = cfg.getValue("general","domain","e164.arpa");
|
|
s_domains.append(cfg.getValue("general","backup","e164.org"),",");
|
|
}
|
|
s_forkStop = cfg.getValue("general","forkstop","busy");
|
|
s_mutex.unlock();
|
|
DDebug(&emodule,DebugInfo,"Domain list: %s",s_domains.c_str());
|
|
s_minlen = cfg.getIntValue("general","minlen",ENUM_DEF_MINLEN);
|
|
int tmp = cfg.getIntValue("general","timeout",ENUM_DEF_TIMEOUT);
|
|
// limit between 1 and 10 seconds
|
|
if (tmp < 1)
|
|
tmp = 1;
|
|
if (tmp > 10)
|
|
tmp = 10;
|
|
s_timeout = tmp;
|
|
tmp = cfg.getIntValue("general","retries",ENUM_DEF_RETRIES);
|
|
// limit between 1 and 5 retries
|
|
if (tmp < 1)
|
|
tmp = 1;
|
|
if (tmp > 5)
|
|
tmp = 5;
|
|
s_retries = tmp;
|
|
// overall a resolve attempt will take at most 50s per domain
|
|
|
|
tmp = cfg.getIntValue("general","maxcall",ENUM_DEF_MAXCALL);
|
|
// limit between 2 and 120 seconds
|
|
if (tmp < 2000)
|
|
tmp = 2000;
|
|
if (tmp > 120000)
|
|
tmp = 120000;
|
|
s_maxcall = tmp;
|
|
|
|
s_redirect = cfg.getBoolValue("general","redirect");
|
|
s_autoFork = cfg.getBoolValue("general","autofork");
|
|
s_sipUsed = cfg.getBoolValue("protocols","sip",true);
|
|
s_iaxUsed = cfg.getBoolValue("protocols","iax",true);
|
|
s_h323Used = cfg.getBoolValue("protocols","h323",true);
|
|
s_xmppUsed = cfg.getBoolValue("protocols","jingle",true);
|
|
s_voidUsed = cfg.getBoolValue("protocols","void",true);
|
|
// by default don't support the number rerouting
|
|
s_telUsed = cfg.getBoolValue("protocols","tel",false);
|
|
// also don't enable gateways by default as more setup is needed
|
|
s_pstnUsed = cfg.getBoolValue("protocols","pstn",false);
|
|
s_voiceUsed= cfg.getBoolValue("protocols","voice",false);
|
|
if (m_init || (prio <= 0))
|
|
return;
|
|
m_init = true;
|
|
int res = res_init();
|
|
if (res)
|
|
Debug(&emodule,DebugGoOn,"res_init returned error %d",res);
|
|
else
|
|
Engine::install(new EnumHandler(cfg.getIntValue("general","priority",prio)));
|
|
}
|
|
|
|
}; // anonymous namespace
|
|
|
|
/* vi: set ts=8 sw=4 sts=4 noet: */
|