The Domain Name System (DNS) is designed to make it easier for humans to locate resources on the Internet. While ultimately all connections between endpoints are handled through numerical IP addresses, it can be very helpful to associate a name (such as www.google.com) with what may in fact be multiple IP addresses.
In the case of VoIP, the use of a
domain name can take something like 100@192.168.1.1
(
)
and make it available as extension
@server
leif@shifteight.org
(which looks so much sexier
on a business card).
A SIP URI generally looks like sip:
.
Depending on your SIP client, you may be able to dial a SIP URI as
endpoint
@domain.tld
,
or even just as endpoint
@domain.tld
(if you
have a proxy server and the endpoint you are calling is part of your
domain).endpoint
For a SIP telephone, which often
only has a numerical dialpad, it can be problematic to dial a SIP URI by
name,[107] so it has become common to use numerical dialing to reach
external resources. We are also used to making “phone calls” using
“phone numbers.” The SIP protocol itself, however, only understands
,
so whatever you dial must ultimately be converted to this format before
SIP can do anything with it. Usually the only reason you can dial
something by “phone number” from your SIP phone is because you are
registered to a resource that understands how to convert the numerical
strings you dial into SIP URIs.resource
@address
In Asterisk, the
part of
the URI (the part before the resource
@
) must
match an extension in the dialplan.[108] The
portion
will be the address (or hostname) of the Asterisk server itself. So, a
URI of address
sip:100@shifteight.org
will
end up at an extension called 100
,
somewhere in the dialplan of the server that provides SIP service for
shifteight.org
.
What is dialed (100
) may not in any way relate to the actual
identifier of the endpoint being connected to. For example, we might
have a user named Leif whose phone may be a device that registers itself
by its MAC address, and therefore could be something like 0000FFFF0001@192.168.1.99
.[109] Much of the purpose of the Asterisk dialplan is to
simplify addressing for users and to handle the complexities of the
various protocols that Asterisk supports.
A Service Record (SRV) is a somewhat new type of DNS record that provides information about available services. Defined in RFC 2782, it is often used by newer protocols (SIP being one of them). If you want to support SIP lookups on your domain, you will require a relevant SRV record in order to properly respond.
When a SIP connection does a
lookup on leif@shifteight.org
, for the purposes of
SIP, the SRV record can respond that the requested service (SIP) is
actually found on the server pbx.shifteight.org
(or possibly even on a
completely different domain, such as pbx.tothemoon.net
).
Internet hosting providers typically offer a web-based interface for setting up DNS records, but many of them do not provide a good interface for SRV records (assuming they offer anything at all). You can generally set up A records and MX records easily enough, but SRV records can be trickier. If your host does not support SRV records, you will need to move your DNS hosting to another provider if you want to be able to support SIP SRV lookups for your domain.
The majority of DNS servers run BIND (Berkeley Internet Name Daemon). The BIND record for an SRV entry for SIP will look something like this:
_sip._udp.shifteight.org. 86400 IN SRV 0 0 5060 pbx.shifteight.org.
The form of the record is detailed in Table 12.1, “Components of a SIP SRV record”.
Table 12.1. Components of a SIP SRV record
Name | Description | Example |
---|---|---|
Service | Symbolic name of service | _sip. |
Proto | Transport protocol | _udp. |
Name | Domain name for this record[a] | shifteight.org. |
TTL | Time to live (in seconds) | 86400 |
Class | DNS class field (always IN ) | IN |
Priority | Target host priority | 0 |
Weight | Relative weight of this record | 0 |
Port | TCP/UDP port number | 5060 |
Target | Hostname of machine providing this service | pbx.shifteight.org. |
[a] Note the trailing dot. |
When you configure an SRV record, you can test it with the following Linux command:
# dig SRV _sip._udp.shifteight.org
The result will contain several components, but the section you are interested in is:
;; ANSWER SECTION:
_sip._udp.shifteight.org. 14210 IN SRV 0 0 5060 pbx.shifteight.org.
This means that your DNS server
is responding correctly to an SRV lookup for SIP to your domain by responding with the
hostname of your PBX (in this case, pbx.shifteight.org
).
Any SIP requests to your domain will be referred to your Asterisk server, which will be responsible for handling incoming SIP connections.[110]
If your dialplan does not understand the name/resource/endpoint portion of the SIP URI, calls will fail. This means that if you want to be able to offer resources in your Asterisk system by name, you will need relevant dialplan entries.
When a SIP URI comes into your Asterisk system, the
resource portion of the URI will arrive in the dialplan as an ${EXTEN}
. So, for example, leif@shifteight.org
would arrive in the
dialplan as leif
within the ${EXTEN}
channel variable in whatever context
you use to handle unauthenticated SIP calls (if you are building your
dialplan using the examples in this book, that will be the unauthenticated
dialplan context).
Once you are familiar with the security implications of
allowing unauthenticated SIP connections, you will need to ensure that
your sip.conf
file allows for
them. While Asterisk allows them by default, in earlier chapters of
this book we have instructed you to disable unauthenticated SIP calls.
The logic for this is simple: if you don’t need it, don’t enable
it.
Since we are now interested in
allowing calls from the Internet, we will need to allow
unauthenticated SIP calls. We do that by setting a general variable in
the /etc/asterisk/sip.conf
file,
as follows:
[general]
context=unauthenticated ; default context for incoming calls
allowguest=yes ; enable unauthenticated calls
After making this change, don’t forget to reload SIP, using this command from the command line:
$
sudo asterisk -rx "sip reload"
or this one from the Asterisk CLI:
*CLI>
sip reload
You can verify that the
changes have succeeded using the Asterisk CLI command sip show settings. What you want to see is Allow
unknown access: Yes
under the Global Settings
section, and Context: unauthenticated
under the Default Settings
header.
In order to handle an incoming name, your dialplan needs to contain an extension that matches that name.
A dialplan entry on the
pbx.shifteight.org
system might
look like this:
[unauthenticated] exten => leif,1,Goto(PublicExtensions,100,1) exten => jim,1,Goto(PublicExtensions,101,1) exten => tilghman,1,Goto(PublicExtensions,102,1) exten => russell,1,Goto(PublicExtensions,103,1)
This is by far the simplest way to implement name dialing, but it is also complex to maintain, especially in systems with hundreds of users.
In order to implement name
handling in a more powerful way, you could add something like the
following to your extensions.conf
file. Note that some lines have been wrapped in this
example due to space restrictions. These lines must appear on a single
line in the dialplan. All lines should start with exten =>
, same
=>
, or a comment indicator (;
).
[unauthenticated] exten => _[A-Za-z0-9].,1,Verbose(2,UNAUTHENTICATED REQUEST TO ${EXTEN} FROM ${CALLERID(all)}) same => n,Set(FilteredExtension=${FILTER(A-Za-z0-9,${EXTEN})}) same => n,Set(CheckPublicExtensionResult=${DIALPLAN_EXISTS(PublicExtensions, ${FilteredExtension},1)}) same => n,GotoIf($["${CheckPublicExtensionResult}" = "0"]?CheckEmailLookup) same => n,Goto(PublicExtensions,${FilteredExtension},1) ; This is our handler for when someone dials a SIP URI with a name same => n(CheckEmailLookup),GoSub(subEmailToExtensionLookup,start,1 (${TOLOWER(${FilteredExtension})})) same => n,GotoIf($["${GOSUB_RETVAL}" = "NoResult"]?i,1:PublicExtensions, ${GOSUB_RETVAL},1) same => n,Goto(i,1) ; This handles invalid numbers/names exten => i,1,Verbose(2,Incoming call from ${CALLERID(all)} to context ${CONTEXT} found no result) same => n,Playback(silence/1&invalid) same => n,Hangup() ; These are explicit extension matches (useful on small systems) exten => leif,1,Goto(PublicExtensions,100,1) exten => jim,1,Goto(PublicExtensions,101,1) exten => tilghman,1,Goto(PublicExtensions,102,1) exten => russell,1,Goto(PublicExtensions,103,1)
When a call enters the
dialplan, it can match in one of two places: it can match our pattern
match at the top, or it can match the explicit named extensions closer
to the bottom of our example (i.e., leif
, jim
, tilghman
, or russell
).
If the call does not
explicitly match our named extensions, the pattern match will be
utilized. Our pattern match of _[A-Za-z0-9].
matches any string starting
with an alphanumeric character followed by one or more other
characters.
The incoming string needs to
be made safe, so we utilize the FILTER()
function to remove nonalphanumeric characters, and assign the
result to the FilteredExtension
channel variable.
The DIALPLAN_EXISTS()
function will be used to see if the request matches anything in
the PublicExtensions
context. This
function will return either a 0
(if
no match is found) or a 1
(when a match is found) and assign the
result to the Check
Public
Extension
Result
channel variable.
The next line is a GotoIf()
that checks the status of the
CheckPublicExtensionResult
variable. If the result returned was 0
, the dialplan will continue at the
CheckEmailLookup
priority label. If
the result was anything other than 0
(in this case, the other result could have
been a 1
), the next line of the
dialplan will be executed. This line will perform a Goto()
and continue execution in the
PublicExtensions
context
(presumably to dial our destination endpoint).
Assuming our CheckPublicExtensionResult
variable was a
0
, our dialplan will continue at
the CheckEmailLookup
priority
label, where we use the subroutine sub
Email
To
ExtensionLookup
via a GoSub()
.[111] We pass the value contained within the Filtered
Extension
channel
variable to the subroutine, but you’ll notice that we’ve wrapped
it in the TOLOWER()
dialplan function (which
expects your email addresses to be stored in lowercase as opposed to
mixed case).
Upon return from the subEmailToExtensionLookup
subroutine, we
check the GOSUB_RETVAL
channel
variable (which was automatically set when the subroutine
returned). The result will be one of two things: the extension number
that matches the name that was passed to the subroutine, or the string
NoResult
. Our dialplan checks
${GOSUB_RETVAL}
, and if it contains
NoResult
, the caller is passed to
the i
(invalid) extension, where we
inform the caller that the extension dialed is invalid. If all is
well, the call will continue execution in the PublicExtensions
context.
This little trick will allow you to use the voicemail.conf
file to look up valid
usernames against their email address. This could end up being kludgy,
and it requires that the email field in voicemail.conf
is filled out and contains a
username (before the @
symbol) that
you will support in your dialplan, but it’s simple to code in the
dialplan, and if nothing else it will give you some ideas of how you
might handle providing a more automated way of linking names to
extension numbers for the purpose of SIP URI dialing. Note that this
method will not allow you to exclude some people from name dialing.
It’s all or nothing.
We’ve written this as a subroutine, which is invoked something like this:
; where 'name' is the username as found in the email address GoSub(subEmailToExtensionLookup,start,1(name))
The subroutine looks like this:
[subEmailToExtensionLookup] exten => start,1,Verbose(2,Checking for user in voicemail.conf) same => n,Set(LOCAL(FilteredExtension)=${FILTER(a-z0-9,${ARG1})}) same => n,Set(LOCAL(Result)=${SHELL(grep "${LOCAL(FilteredExtension)}@" /etc/asterisk/voicemail.conf)}) same => n,GotoIf($[${ISNULL(${LOCAL(Result)})}]?no_Result,1) same => n,Set(LOCAL(ExtensionToDial)=${CUT(${LOCAL(Result)},=,1)}) same => n,Set(LOCAL(ExtensionToDial)=${FILTER(0-9,${LOCAL(ExtensionToDial)})}) same => n,Return(${LOCAL(ExtensionToDial)}) exten => no_Result,1,Verbose(2,No user ${ARG1} found in voicemail.conf) same => n,Return(NoResult)
Let’s go over this code, because there are some useful actions being performed that you may be able to apply for other purposes as well.
First, a channel variable
named FilteredExtension
is created.
This variable is local to the subroutine:
Set(LOCAL(FilteredExtension)=${FILTER(a-z0-9,${ARG1})})
The FILTER()
function looks at the entire
${ARG1}
and removes any
nonalphanumeric characters. This is primarily for security reasons. We
are passing this string out to the shell, so it’s critical to ensure
it will only contain characters that we expect.
The next step is where the coolness happens:
Set(LOCAL(Result)=${SHELL(grep "${LOCAL(FilteredExtension)}@" /etc/asterisk/voicemail.conf)})
The shell is invoked in order
to run the grep shell application,
which will search through the voicemail.conf
file, return any lines that
contain name@
, and assign the
result to the variable ${Result}
:
GotoIf($[${ISNULL(${LOCAL(Result)})}]?no_result,1)
If no lines contain the string
we’re looking for, we’ll return from the subroutine the value NoResult
(which will be found in the
${GOSUB_RETVAL}
channel variable).
The dialplan section that called the subroutine will need to handle
this condition.
We’ve created an extension
named no_result
for this
purpose:
exten => no_result,1,Verbose(2,No user ${ARG1} found in voicemail.conf) same => n,Return(NoResult)
If
${Result}
is not null, the next
steps will clean up ${Result}
in
order to extract the extension number[112] of the user with the name passed in ${ARG1}
:
Set(LOCAL(ExtensionToDial)=${CUT(${LOCAL(Result)},=,1)})
The CUT()
function will use the =
symbol as the field delimiter and will
assign the value from the first field found in ${Result}
to the new variable ExtensionToDial
. From there, we simply need
to trim any trailing spaces by filtering all nonnumeric
characters:
Set(LOCAL(ExtensionToDial)=${FILTER(0-9,${LOCAL(ExtensionToDial)})})
We can now return the extension number of the name we received:
Return(${LOCAL(ExtensionToDial)})
This example was something we whipped up for the purposes of illustrating some methods you can employ in order to easily match names to extension numbers for the purposes of SIP URI dialing. This is by no means the best way of doing this, but it is fairly simple to implement, and in many cases may be all that you need.
Using a database is by far the best way to handle user information on larger, more complex systems. We will discuss integrating Asterisk with databases in more detail in Chapter 16, Relational Database Integration, but it is useful to introduce the concept here.
A database is ideal for handling name lookup, as it makes maintenance of user data (and integration with external systems such as web interfaces) far simpler. However, it does require a bit more effort to design and implement.
The example we will use in this chapter will work, but for a production environment it is probably too simplistic. Our goal here is simply to give you enough information to understand the concept; a tighter integration is part of what is covered in Chapter 16, Relational Database Integration.
First, we’ll need a table to
handle our name-to-extension mapping. This could be a separate table from the main user
table, or it could be handled in the main user table, provided that
that table contains a field that will contain the exact strings that
users will publish as their SIP URIs (as an example, some companies
have rules regarding how email addresses look, so Leif might have a
URI such as lmadsen@shifteight.org
,
or leif.madsen@shifteight.org
).
If you are serious about implementing this example in a production system, make sure you are familiar with the material in Chapter 16, Relational Database Integration, as some key concepts are covered there that we omit here.
Our sample NameMapping
table
looks like Table 12.2, “NameMapping table”.
We believe that having a separate table that only handles name-to-extension/context mapping is the most useful solution, since this table can be used to handle more than just users with telephone sets. You are encouraged to come up with other ways to handle this that may be more suitable to your environment.
In the dialplan, we would
refer to this table using Asterisk’s func_odbc
function:
[subLookupNameInNameMappingTable] exten => start,1,Verbose(2,Looking up ${ARG1}) ; where 'name' is the username as found in the email address same => n,Set(ARRAY(CalleeExtension,CalleeContext)=${GET_NAME_LOOKUP(${ARG1})}) same => n,GotoIf($[${ISNULL(${CalleeExtension})}]?no_result,1) same => n,GotoIf($[${ISNULL(${CalleeContext})}]?no_result,1) same => n,Return() ; You'll need to handle the new CalleeExtension and ; CalleeContext variables in the code that called this ; subroutine exten => no_result,1,Verbose(2,Name was not found in the database.) same => n,Return(NoResult)
The /etc/asterisk/func_odbc.conf
file will
require the following entry:
[NAME_LOOKUP](DB) prefix=GET SELECT Extension,Context FROM NameMapping WHERE Name='${ARG1}'
Keep in mind that there’s nothing to say you can’t reference more than one datastore to look up names. For example, you might have a table such as the one we’ve described here, but also have a secondary lookup that goes to, say, an LDAP database to try to resolve names there as well. This can get complicated to configure and maintain, but if designed right it can also mean that your Asterisk system can be tightly integrated with other systems in your enterprise.
Details on how to handle all of this in your dialplan are beyond the scope of this book. Suffice it to say that in your dialplan you will still need to handle the values that your subroutine creates or assigns.
Asterisk can dial a SIP URI as easily as any other sort of destination, but it is the endpoint (namely, your telephone) that is ultimately going to shoulder the burden of composing the address, and there lies the difficulty.
Most SIP telephones will allow
you to compose a SIP URI using the dialpad. This sounds like a great
idea at first, but since there are no typewriter keys on a phone set, in
order to dial something like jim.vanmeggelen@shifteight.org
what you would
need to actually input into the phone would be something along the lines
of:
5-444-6-*-888-2-66(pause)-6-33-4(pause)-4-33-555-33-66-#-7777-44(pause)-444-333-8-33- 444-(pause)-4(pause)-44-8-*-666-777-4
To support this in your dialplan, you would need something similar to this[113]:
exten => _[0-9a-zA-Z].,1,Verbose() same => n,Set(FilteredExtension=${FILTER(0-9a-zA-Z@-_.,${EXTEN})}) same => n,Dial(SIP/${FilteredExtension})
It’s simple, it’s fun, and it works! … ?
The reality is that until all phones support complex and flexible address books, as well as a QWERTY-style keyboard (perhaps via touchscreen), SIP URI dialing is not going to take off.
If you have a SIP URI that you want to dial on a regular basis (for example, during the writing of this book there were many calls made between Jim and Leif), you could add something like this to your dialplan:
exten => 5343,1,Dial(SIP/leif.madsen@shifteight.org)
With this in your dialplan, you
could dial 5343
(LEIF)
on your phone and the Asterisk dialplan
would translate it into the appropriate SIP URI. It’s not practical for
a large number of URIs, but for a few here and there it can be a helpful
shortcut.
Nevertheless, keep reading, because there are some very useful components of DNS that simplify the process of dialing directly between systems without the use of the PSTN.
[107] Do you know where the @
symbol is on your dialpad?
[108] Bear in mind that an extension in Asterisk can be any
alphanumeric string, such as leif
or 100
.
[109] You could actually dial this URI directly from your phone and
bypass the Asterisk server, but you can see how dialing 100
is going to be a lot more popular than
trying to figure out how to type 0004f2a1b2c3@192.168.1.99
into your phone
using just the numeric dialpad (it can be done, by the way).
[110] This could just as easily be a proxy server, or any other server capable of handling incoming SIP connections.
[111] We explain the use of subEmailToExtensionLookup
in the
following section.
[112] In actual fact, what we are extracting is the voicemail box number; however, this number is generally going to be the same as the user’s dialable internal extension number. If it is not the same, this particular technique will not accomplish name-to-extension lookups, and another way will have to be found.
[113] Technically, the characters ! # $ %
& ' * + / = ? ^ ` { | } ~
are also valid as part of
the local-part
of an email
address; however, they are uncommon, and we have elected not to
allow them in our dialplan examples.