There are three files that need to be configured for DUNDi:
dundi.conf
, extensions.conf
, and
sip.conf
.[167] The dundi.conf
file controls the
authentication of peers whom we allow to perform lookups through our
system. This file also manages the list of peers to whom we might submit
our own lookup requests. Since it is possible to run several different
networks on the same box, it is necessary to define a different section
for each peer, and then configure the networks in which those peers are
allowed to perform lookups. Additionally, we need to define which peers we
wish to use to perform lookups.
The [general]
section
of dundi.conf
contains parameters relating to the
overall operation of the DUNDi client and server:
; DUNDi configuration file for Toronto ; [general] ; department=IT organization=toronto.example.com locality=Toronto stateprov=ON country=CA email=support@toronto.example.com phone=+14165551212 ; ; Specify bind address and port number. Default is port 4520. ;bindaddr=0.0.0.0 port=4520 entityid=FF:FF:FF:FF:FF:FF ttl=32 autokill=yes ;secretpath=dundi
The entity identifier defined by entityid
should generally be the Media Access
Control (MAC) address of an interface in the machine. The entity ID
defaults to the first Ethernet address of the server, but you can
override this with entityid
, as long
as it is set to the MAC address of something you
own. The MAC address of the primary external interface is recommended.
This is the address that other peers will use to identify you.
The time-to-live (ttl
) field defines how many hops away the
peers we receive replies from can be and is used to break loops. Each
time a request is passed on down the line because the requested number
is not known, the value in the TTL field is decreased by one, much like
the TTL field of an ICMP packet. The TTL field also defines the maximum
number of seconds we are willing to wait for a reply.
When you request a number lookup, an initial
query (called a DPDISCOVER
) is sent
to your peers requesting that number. If you do not receive an
acknowledgment (ACK
) of your query
(DPDISCOVER
) within 2000 ms (enough
time for a single transmission only) and autokill
is set to yes
, Asterisk will send a CANCEL
to the peers. (Note that an
acknowledgment is not necessarily a reply to the query; it is just an
acknowledgment that the peer has received the request.) The purpose of
autokill
is to keep the lookup from
stalling due to hosts with high latency. In addition to the yes
and no
options, you may also specify the number of milliseconds to wait.
The pbx_dundi
module
creates a rotating key and stores it in the local Asterisk database
(AstDB). The key name secret
is
stored in the dundi
family. The value
of the key can be viewed with the database show
command at the Asterisk console. The database family can be overridden
with the secretpath
option.
We need another peer to interact with, so here’s the configuration for the other node:
; DUNDi configuration file for Vancouver ; [general] ; department=IT organization=vancouver.example.com locality=Vancouver stateprov=BC country=CA email=support@vancouver.example.com phone=+16135551212 ; ; Specify bind address and port number. Default port is 4520. ;bindaddr=0.0.0.0 port=4520 entityid=00:00:00:00:00:00 ttl=32 autokill=yes ;secretpath=dundi
In the next section we’ll create our initial DUNDi peers.
A DUNDi peer is identified by the unique layer-two MAC
address of an interface on the remote system. The
dundi.conf
file is where we define what context to
search for peers requesting a lookup and which peers we want to use when
doing a lookup for a particular network. The following configuration is
defined in the dundi.conf
file on our Toronto
system:
[00:00:00:00:00:00] ; Vancouver Remote Office model = symmetric host = vancouver.example.com inkey = vancouver outkey = toronto qualify = yes dynamic=yes
The remote peer’s identifier (MAC address) is
enclosed in square brackets ([]
). The
inkey
and outkey
are the public/private key pairs that
we use for authentication. Key pairs are generated with the astgenkey
script, located in the ~/src/asterisk-complete/asterisk/1.8/contrib/scripts/
source directory. We use the -n
flag so that we
don’t have to initialize passwords every time we start Asterisk:
$cd /var/lib/asterisk/keys
$sh ~/src/asterisk-complete/asterisk/1.8/contrib/scripts/astgenkey -n toronto
We’ll place the resulting keys, toronto.pub
and toronto.key
, in our /var/lib/asterisk/keys/
directory. The
toronto.pub
file is the public key,
which we’ll post to a web server so that it is easily accessible for
anyone with whom we wish to peer. When we peer, we can give our peers
the HTTP-accessible public key, which they can then place in their
/var/lib/asterisk/keys/
directories
(using something like wget).
On the Vancouver box, we’ll use the
following peer configuration in dundi.conf
:
[FF:FF:FF:FF:FF:FF] ; Toronto Remote Office model = symmetric host = toronto.example.com inkey = toronto outkey = vancouver qualify = yes dynamic=yes
Then we’ll execute the same
astgenkey
script on the Vancouver box to generate
the public and private vancouver keys. Finally,
we’ll place the toronto.pub
key on the Vancouver server in
/var/lib/asterisk/keys/
and place the
vancouver.pub
file on the Toronto server in the
same location.
After downloading the keys, we must reload the
res_crypto.so
and pbx_dundi.so
modules in Asterisk:
toronto*CLI> module reload res_crypto.so
-- Reloading module 'res_crypto.so' (Cryptographic Digital Signatures)
-- Loaded PUBLIC key 'vancouver'
-- Loaded PUBLIC key 'toronto'
-- Loaded PRIVATE key 'toronto'
vancouver*CLI> module reload res_crypto.so
-- Reloading module 'res_crypto.so' (Cryptographic Digital Signatures)
-- Loaded PUBLIC key 'toronto'
-- Loaded PUBLIC key 'vancouver'
-- Loaded PRIVATE key 'vancouver'
We can verify the keys so we know they’re ready to be loaded at any time with the keys show CLI command:
*CLI> keys show
Key Name Type Status Sum
------------------ -------- ---------------- --------------------------------
vancouver PRIVATE [Loaded] c02efb448c37f5386a546f03479f7d5e
vancouver PUBLIC [Loaded] 0a5e53420ede5c88de95e5d908274fb1
toronto PUBLIC [Loaded] 5f806860e0c8219f597f876caa6f2aff
3 known RSA keys.
With the keys loaded into memory, we can reload the
pbx_dundi.so
module on both systems in order to
peer them together:
*CLI> module reload pbx_dundi.so
-- Reloading module 'pbx_dundi.so' (Distributed Universal Number
Discovery (DUNDi))
== Parsing '/etc/asterisk/dundi.conf': Found
Finally, we can verify that the systems have peered successfully with dundi show peers:
toronto*CLI> dundi show peers
EID Host Port Model AvgTime Status
00:00:00:00:00:00 172.16.0.104 (S) 4520 Symmetric Unavail OK (3 ms)
1 dundi peers [1 online, 0 offline, 0 unmonitored]
Now, with our peers configured and reachable, we need to create the mapping contexts that will control what information will be returned in a lookup.
The dundi.conf
file defines DUNDi
contexts that are mapped to dialplan contexts in your
extensions.conf
file. DUNDi contexts are a way of
defining distinct and separate directory service groups. The contexts in
the [mapping]
section point to contexts in the
extensions.conf
file, which control the numbers
that you advertise.
When you create a peer, you need to define
which mapping contexts you will allow this peer to search. You do this
with the permit
statement (each peer
may contain multiple permit
statements). Mapping contexts are
related to dialplan contexts in the sense that they are a security
boundary for your peers. We’ll enable our mapping in the next
section.
All DUNDi mapping contexts take the form of:
dundi_context
=>local_context
,weight
,technology
,destination
[,options
]]
The following configuration creates a DUNDi
mapping context that we’ll use to advertise our local extension numbers
to the group. We’ll add this configuration to the
dundi.conf
file on the Toronto system under the
[mappings]
header. Note that this should all appear
on one line:
[mappings]
; All on a single line
;
extensions => RegisteredDevices,0,SIP,dundi:very_secret_secret
@toronto.example.com/
${NUMBER},nopartial
The configuration on the Vancouver system will look like this:
[mappings]
; All on a single line
;
extensions => RegisteredDevices,0,SIP,dundi:very_secret_secret
@vancouver.example.com/
${NUMBER},nopartial
In this example, the mapping context is
extensions
, which points to the
RegisteredDevices
context within
extensions.conf
(providing a listing of extension
numbers to reply with: our phone book). Numbers that resolve to the PBX
should be advertised with a weight
of zero
(directly connected). Numbers higher than zero indicate an increased
number of hops or paths to reach the final destination. This is useful
when multiple replies for the same lookup are received at the end that
initially requested the number; a path with a lower
weight
will be preferred. We’ll look at how
to control responses in the section called “Controlling Responses”.
If we can reply to a lookup, our response will contain the method by which the other end can connect to the system. This includes the technology to use (such as IAX2, SIP, H323, and so on), the username and password with which to authenticate, which host to send the authentication to, and finally the extension number.
Asterisk provides some shortcuts to allow us to create a “template” with which we can build our responses. The following channel variables can be used to construct the template:
It is generally safest to statically
configure the hostname, rather than make use of the ${IPADDR}
variable. The ${IPADDR}
variable will sometimes reply with
an address in the private IP space, which is unreachable from the
Internet.
With our mapping configured, let’s create a simple dialplan context against which we can perform lookups for testing. We’ll make this more dynamic in the section called “Controlling Responses”.
In extensions.conf
, we
can add the following on both systems:
[RegisteredDevices] exten => 1000,1,NoOp()
With our dialplan and mappings configured, we need to load them into memory from the CLI:
*CLI>dialplan reload
*CLI>module reload pbx_dundi.so
-- Reloading module 'pbx_dundi.so' (Distributed Universal Number Discovery (DUNDi)) == Parsing '/etc/asterisk/dundi.conf': == Found
We can verify the mapping was loaded into memory with the dundi show mappings command:
toronto*CLI> dundi show mappings
DUNDi Cntxt Weight Local Cntxt Options Tech Destination
extensions 0 RegisteredDe NONE SIP dundi:${SECRET}@172.16.0.
With our simple dialplan and mappings configured, we need to define the mappings each of our peers is allowed to use. We’ll do this in the next section.
With our mappings defined in the
dundi.conf
file, we need to give our peers
permission to use them. Control of the various mappings is done via the
permit
, deny
,
include
, and noinclude
options
within a peer definition. We use permit
and
deny
to control whether the remote peer is allowed to
search a particular mapping on our local system. We use
include
and noinclude
to control
which peers we will use to perform lookups with in a particular
mapping.
Since we only have a single mapping defined
(extensions
), we’re going to
permit
and include extensions
within our peer definitions on both the Toronto and Vancouver
systems.
On Toronto, we’ll permit Vancouver to search
the extensions
mapping, and use Vancouver whenever
we’re performing a lookup within the extensions
mapping:
[00:00:00:00:00:00] ; Vancouver Remote Office
model = symmetric
host = vancouver.example.com
inkey = vancouver
outkey = toronto
qualify = yes
dynamic=yes
permit=extensions
include=extensions
Similarly, we’ll
permit
and include
the
extensions
mapping for the Toronto office on the
Vancouver system:
[FF:FF:FF:FF:FF:FF] ; Toronto Remote Office
model = symmetric
host = toronto.example.com
inkey = toronto
outkey = vancouver
qualify = yes
dynamic=yes
permit=extensions
include=extensions
After modifying the peers, we reload the
pbx_dundi.so
module to have the changes take
effect:
*CLI> module reload pbx_dundi.so
The
include
and permit
configuration
can be verified via the dundi show peer command
on the Asterisk CLI:
*CLI>dundi show peer 00:00:00:00:00:00
Peer: 00:00:00:00:00:00 Model: Symmetric Host: 172.16.0.104 Port: 4520 Dynamic: no Reg: No In Key: vancouver Out Key: torontoInclude logic: -- include extensions Query logic: -- permit extensions
Now we can test our lookups. We can do this
easily from the Asterisk CLI using the dundi lookup
command. If we perform a lookup from the Vancouver system, we’ll
receive a response from the Toronto system with an address we can use to
place a call. We’ve added the keyword bypass
to the
end of the lookup in order to bypass the cache (in case we wish to
perform several tests):
vancouver*CLI>dundi lookup 1000@extensions bypass
1. 0 SIP/dundi:very_secret_secret
@172.16.0.161/1000 (EXISTS) from ff:ff:ff:ff:ff:ff, expires in 3600 s DUNDi lookup completed in 12 ms
The response of
SIP/dundi:
gives us an address that we can use to call extension
very_secret_secret
@172.16.0.161/10001000
. (Of course, we can’t use this address at the
moment because we haven’t configured any peers on the Toronto (or
Vancouver) system to actually receive the call, but at least we have the
DUNDi lookup portion working now!) In the next section we’ll explore how
to receive calls into our system after we’ve replied to a DUNDi
response.
Within our sip.conf
file, we need to
enable a peer that we can accept calls from and handle that peer’s calls
in the dialplan appropriately. The authentication is done using a
password as defined in the mapping within
dundi.conf
.
If you’re using
iax.conf
, you can use the ${SECRET}
variable
in the mapping in place of the password, which is
dynamically replaced with a rotated key and is refreshed every 3600
seconds (1 hour). The value of the secret key is stored in the
Asterisk database and is accessed using the
dbsecret
option within the peer definition of
iax.conf
.
Here is the user definition for the
dundi
user as defined in
sip.conf
:
[dundi]
type=user
secret=very_secret_secret
context=DUNDi_Incoming
disallow=all
allow=ulaw
allow=alaw
The context
entry, DUNDi_Incoming
, is where
authorized callers are sent in extensions.conf
. From there, we can control
the call just as we would in the dialplan of any other incoming
connection.
We could also use the
permit
and deny
options for the
peer in sip.conf
to control which IP addresses
we’ll accept calls from. Controlling the IP addresses will give us an
extra layer of security if we’re only expecting calls from known
endpoints, such as those within our organization.
Be sure to reload
chan_sip.so
to enable the newly created user in
sip.conf
:
toronto*CLI> sip reload
To
accept the incoming calls, define the
[DUNDi_Incoming]
context in
extensions.conf
and add the following to the
Toronto system’s dialplan.
[DUNDi_Incoming] exten => 1000,1,Verbose(2,Incoming call from the DUNDi peer) same => n,Answer() same => n,Playback(silence/1) same => n,Playback(tt-weasels) same => n,Hangup()
Reload the dialplan with dialplan
reload after saving your changes to
extensions.conf
.
For our first test, we’ll create an
extension in the LocalSets
context and try placing a
call to extension 1000
using the information provided
via DUNDi:
[LocalSets] exten => 1000,1,Verbose(2,Test extension to place call to remote server) same => n,Dial(SIP/dundi:very_secret_secret@172.16.0.161/1000,30) same => n,Hangup()
If we reload the dialplan and try testing
the extension by dialing 1000
, we should be connected
to the tt-weasels
prompt on the remote machine.
With our user configured correctly to accept incoming calls, let’s make
our dialplan and responses more dynamic with some additional
tools.
Responses are controlled with the dialplan. Whenever an
incoming request matches the dialplan configured for the mapping
(whether the request is for a specific extension or a pattern match), a
response will be sent. If the request does not match within the
dialplan, no response is sent. In the example we’ve been building, the
extension 1000
is the only extension that can be
matched and thus generate a response.
In the next few sections we’ll look at some of the methods we can use to control what requests are responded to.
The extensions.conf
file handles what numbers you advertise and what you do
with the calls that connect to them.
The simple method to control responses is to
simply add them manually to the [RegisteredDevices]
context. If we had several extensions at one of our locations, we
could add them all to that context:
[RegisteredDevices] exten => 1000,1,NoOp() exten => 1001,1,NoOp() exten => 1002,1,NoOp()
The NoOp()
dialplan application is used here because the matching and responding is done
only against the extension number, and no dialplan is executed. While
we could overload this context and cause it to also be the destination
for our calls, it’s not recommended. Other reasons for using the
NoOp()
application should become clear as we
progress.
Of course, adding everything we want to respond with manually would be silly, especially if we wanted to advertise a larger set of numbers, such as all numbers for an area code. As mentioned earlier, in our example we might wish to allow our Toronto and Vancouver offices to call out from one another when placing calls that are free or cheap to make from the other location.
We can respond with all of an area code using pattern matches, just as we do in other parts of the dialplan:
[RegisteredDevices] exten => _416NXXXXXX,1,NoOp() exten => _647NXXXXXX,1,NoOp() exten => _905NXXXXXX,1,NoOp()
We could also advertise a full or partial range of extensions using pattern matches:
[RegisteredDevices] exten => _1[1-3]XX,1,NoOp() ; extensions 1100->1399 exten => _1[7-9]XX,1,NoOp() ; extensions 1700->1999
Pattern matches are a good way of adding
ranges of numbers, but these are still static. In the next section
we’ll explore how we can add some fluidity to the Registered
Devices
context.
In some cases, you might want to only advertise extensions at your location that are currently registered to the system. Perhaps we have a salesperson who flies between the Toronto and Vancouver offices, and plugs her laptop into the network and registers at whichever location she is currently at. In that case, we would want to make sure that calls to that person are routed to the appropriate office in order to avoid sending calls across the country unnecessarily.
The regcontext
and regexten
options in iax.conf
and
sip.conf
are useful for this. When a peer
registers the value associated with regexten
for
that peer, an extension of that value will be created in the context
defined by regcontext
. So, for example, if we
define regcontext
in the
[general]
section of sip.conf
to contain RegisteredDevices
, and we define the
regexten
for each peer to contain the extension
number of that peer, when the peers register the
RegisteredDevices
context will be populated
automatically for us. We’ll modify our sip.conf
to look like this:
[general] regcontext=RegisteredDevices [0000FFFF0001](office-phone) regexten=1001
Now, we’ll register our device to the system
and look at the RegisteredDevices
context:
*CLI> dialplan show RegisteredDevices
[ Context 'RegisteredDevices' created by 'SIP' ]
'1001' => 1. Noop(0000FFFF0001) [SIP]
'1002' => 1. Noop(0000FFFF0002) [SIP]
With our devices registered and the context
used for determining when to respond populated, the only task left is
to include the LocalSets
context within the
DUNDi_
Incoming
context in order to permit
routing of calls to the endpoints.
Sometimes it’s useful to utilize a dialplan function
within the mappings to control what a peer responds with. Throughout
this book we’ve been touting the advantages of decoupling the user’s
extension number from the device in order to permit hot-desking.
Because the other end is just going to request an extension number and
won’t necessarily know the location of the device on our system, we
can use the DB()
and DB_EXISTS()
functions within the mapping to perform a lookup from our AstDB for
the device to call.[168]
Prior to Asterisk version 1.8.3, the
maximum length of the destination
field
(see the section called “Creating Mapping Contexts”) was 80 characters, which
made the use of nested dialplan functions nearly impossible. As of
Asterisk 1.8.3, the maximum length is 512 characters.
First we need to make sure our database is populated with the information we might respond with. While this would normally be done by the dialplan written for the hot-desking implementation, we’ll just add the content directly from the Asterisk console for demonstration purposes:
*CLI> database put phones 1001/device 0000FFFF0001
Updated database successfully
With our database populated, we need to
modify our mapping to utilize some dialplan functions that will take
the value requested, perform a lookup to our database for that value,
and return a value. If no value exists in the database, we’ll return
the value of None
.
Our existing mapping looks like this:
[mappings] ; The mapping exists on a single line extensions => RegisteredDevices,0,SIP, dundi:very_secret_secret@toronto.example.com/${NUMBER},nopartial
Our current example simply reflects back the
same extension number that was requested, along with some
authentication information. The number requested is the extension the
peer is looking for. However, because we’re using hot-desking, the
extension number may be located at various phone locations, so we may
want to return the device identifier directly.[169] We can do this by being clever with the use of dialplan
functions in our response. While we may not have the full power of the
dialplan (multiple lines, complex logic, etc.) at our disposal, we can
at least use some of the simpler dialplan functions, such as
DB()
, DB_EXISTS()
, and
IF()
.
We’re going to replace
${NUMBER}
with the following bit of dialplan
logic:
${IF($[${DB_EXISTS(phones/${NUMBER}/device)}]?${DB(phones/${NUMBER}/device)}:None)}
If we break this down, we end up with an
IF()
statement that will return either true or
false. If false, we return the value of None
. If
true, we return the value located in the database at
phones/${NUMBER}/device
(where
${NUMBER}
contains the value of
1001
for our example) using the
DB()
function. To determine which value the
IF()
function will return, we use the
DB_EXISTS()
function. This function checks whether
a value exists at phones/${NUMBER}/device
within
the AstDB, and returns either 1
or
0
(true or false).
The DB_EXISTS()
function not only returns 1
or
0
, but also sets the
${DB_RESULT}
channel variable that contains the
value inside the database if the return value is
1
. However, we can’t use that value because the
IF()
function is evaluated prior to the condition
field being evaluated, which means ${DB_RESULT}
will be blank. Thus, we need to use the DB()
function to look up the value prior to the condition field being
evaluated.
After reloading
pbx_dundi.so
from the console (module
reload pbx_dundi.so), we can perform a lookup from another
server and check out the result:
vancouver*CLI> dundi lookup 1001@extensions bypass
1. 0 SIP/dundi:very_awesome_password/0000FFFF0001 (EXISTS)
from ff:ff:ff:ff:ff:ff, expires in 3600 s
DUNDi lookup completed in 77 ms
With dialplan functions, you can make the
responses in your dialplans a lot more dynamic. In the next section
we’ll look at how you can perform these lookups from the dialplan
using the DUNDILOOKUP()
, DUNDIQUERY()
, and DUNDIRESULT()
functions.
When you perform lookups using the example
in this chapter, because all the peers in your network will return a
result (None
, or the value you want), you’ll need
to use the DUNDIQUERY()
and
DUNDIRESULT()
functions to parse through the list
of results returned. The alternative would be to try calling
SIP/dundi:very_long_pass@remote_server/None
, but
this wouldn’t be very effective. You might even want to handle the
extension None
elegantly, in case it gets
called.
Performing lookups from the dialplan is really the bread
and butter of all of this, because it allows more dynamic routing from
within the dialplan. With DUNDi, you can perform lookups and route calls
within your cluster using either the DUNDILOOKUP()
or
DUNDIQUERY()
and DUNDIRESULT()
functions.
The DUNDILOOKUP()
function
replaces the old DUNDiLookup()
dialplan
application, performing nearly the same functionality. With
DUNDILOOKUP()
, you perform your lookup like you would
at the Asterisk console, and the result can then be saved into a channel
variable, or used wherever you might use a dialplan function. Here is an
example:
[TestContext] exten => 1001,1,Verbose(2,Look up extension 1001) same => n,Set(DUNDi_Result=${DUNDILOOKUP(1001,extensions,b)}) same => n,Verbose(2,The result of the lookup was ${DUNDi_Result}) same => n,Hangup()
The arguments passed to
DUNDILOOKUP()
are:
extension
,context
,options
.
Only one option, b
, is available for the
DUNDILOOKUP()
function, and that is used to bypass
the local cache. The advantage to using the
DUNDILOOKUP()
function is that it is straightforward
and easy to use. The disadvantage is that it will only set the first
value returned; if multiple values are returned, they will be
discarded.
You won’t always want to use the bypass option when performing lookups, because the use of the cache is what will lower the number of requests over your network and limit the amount of resources required. We’re using it in our examples simply because it is useful for testing purposes, so that we know we’ve returned a result each time rather than just a cached value from the previous lookup.
To parse through multiple returned values, we
need to use the DUNDIQUERY()
and DUNDIRESULT()
functions. Each plays an important part in sifting through multiple
returned values from a lookup. The DUNDIQUERY()
function performs the initial lookup and saves the resulting hash into
memory. An ID value is then returned, which can be stored in a channel
variable. The ID value returned from the DUNDIQUERY()
function can then be passed to the DUNDIRESULT()
function to parse through the returned values from the query.
Lets take a look at some dialplan that uses these functions:
[TestContext] exten => _1XXX,1,Verbose(2,Looking up results for extension ${EXTEN}) ; Perform our lookup and save the resulting ID to DUNDI_ID same => n,Set(DUNDI_ID=${DUNDIQUERY(${EXTEN},extensions,b)}) same => n,Verbose(2,Showing all results returned from the DUNDi Query) ; The DUNDIRESULT() function can return the number of results using 'getnum' same => n,Set(NumberOfResults=${DUNDIRESULT(${DUNDI_ID},getnum)}) same => n,Set(ResultCounter=1) ; If there is less than 1 result, no results were returned same => n,GotoIf($[${NumberOfResults} < 1]?NoResults,1) ; The start of our loop showing the returned values same => n,While($[${ResultCounter} <= ${NumberOfResults}]) ; Save the returned result at position ${ResultCounter} to thisResult same => n,Set(thisResult=${DUNDIRESULT(${DUNDI_ID},${ResultCounter})}) ; Show the current result on the console same => n,Verbose(2,One of the results returned was: ${thisResult}) ; Increase the counter by one same => n,Set(ResultCounter=${INC(ResultCounter)}) ; End of our loop same => n,EndWhile() same => n,Playback(silence/1) same => n,Playback(vm-goodbye) same => n,Hangup() ; If no results were found, execute this dialplan exten => NoResults,1,Verbose(2,No results were found) same => n,Playback(silence/1) same => n,Playback(invalid) same => n,Hangup()
Our example dialplan performs a lookup using
the DUNDIQUERY()
function and stores the resulting ID
value in the DUNDI_ID
channel variable. Using the
DUNDIRESULT()
function and the
getnum
option, we store the total number of returned
results in the NumberOfResults
channel variable. We
then set the ResultCounter
channel variable to
1
as our starting position in the loop.
Using GotoIf()
, we check if
the ${NumberOfResults}
returned is less than one and,
if so, jump to the NoResults
extension, where we
Playback()
“Invalid extension”. If at least one
extension is found, we continue on in the dialplan.
Using the While()
application, we check if the ${ResultCounter}
is less
than or equal to the value of ${NumberOfResults}
. If
that is true, we continue on in the dialplan, and otherwise, we jump to
the EndWhile()
application.
For each iteration of our loop, the
DUNDIRESULT()
function is used to save the value at
position ${ResultCounter}
to the
thisResult
channel variable. After storing the value,
we output it to the Asterisk console using the
Verbose()
application. Following that, we increase
the value of ResultCounter
by one using the
INC()
function. Our loop test is then done again
within the While()
loop, and the loop will continue
while the value of ${ResultCounter}
is less than or
equal to the value of ${Number OfResults}
.
Using the same type of logic, we could check
for values other than None
and, if such a value is
found, ExitWhile()
and continue in the dialplan to
perform a call to the endpoint. The dialplan logic might look something
like this:
[subLookupExtension] exten => _1XXX,1,Verbose(2,Looking up results for extension ${EXTEN}) ; Perform our lookup and save the resulting ID to DUNDI_ID same => n,Set(DUNDI_ID=${DUNDIQUERY(${EXTEN},extensions,b)}) same => n,Set(NumberOfResults=${DUNDIRESULT(${DUNDI_ID},getnum)}) same => n,Set(ResultCounter=1) ; If no results are found, return 'None' same => n,GotoIf($[${NumberOfResults} < 1]?NoResults,1) ; Perform our loop same => n,While($[${ResultCounter} <= ${NumberOfResults}]) ; Get the current value same => n,Set(thisResult=${DUNDIRESULT(${DUNDI_ID},${ResultCounter})}) ; If the current value returned is not None, we have a resulting ; location to call and we can exit the loop same => n,ExecIf($["${thisResult}" != "None"]?ExitWhile()) ; If we made it this far, no value has been returned yet that we want to ; use, so increase the counter and try the next value. same => n,Set(ResultCounter=${INC(ResultCounter)}) ; End of our loop same => n,EndWhile() ; We've made it here because we made it to the end of the loop or we found ; a value we want to return. Check to see which it is. If we just ran out of ; values, return 'None'. ; same => n,GotoIf($["${thisResult}" = "None"]?NoResults,1) ; If we make it here, we have a value we want to return. same => n,Return(${thisResult}) ; If there were no acceptable results, return the value 'None' exten => NoResults,1,Verbose(2,No results were found) same => n,Return(None)
With the DUNDIQUERY()
and
DUNDIRESULT()
functions, you have a lot of power to
control how to handle the results returned and perform routing logic
with those values.
[167] The dundi.conf
and
extensions.conf
files must be configured. We have
chosen to configure sip.conf
for the purposes of
address advertisement on our network, but DUNDi is protocol-agnostic,
so iax.conf
, h323.conf
, or
mgcp.conf
could be used instead. DUNDi simply
performs the lookups; the standard methods of placing calls are still
required.
[168] …or func_odbc
, or
func_curl
, or res_ldap
(using the REALTIME_FIELD()
function).
[169] We’ve also looked at using the GROUP()
and GROUP_COUNT()
functions for looking up the
current channel usage on a remote system to determine which
location to route calls to (the one with the lowest channel usage)
as a simple load balancer.