DNS and SIP URIs

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 (extension@server) and make it available as leif@shifteight.org (which looks so much sexier on a business card).

The SIP URI

A SIP URI generally looks like sip:endpoint@domain.tld. Depending on your SIP client, you may be able to dial a SIP URI as endpoint@domain.tld, or even just as endpoint (if you have a proxy server and the endpoint you are calling is part of your domain).

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 resource@address, 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.

In Asterisk, the resource part of the URI (the part before the @) must match an extension in the dialplan.[108] The address portion will be the address (or hostname) of the Asterisk server itself. So, a URI of 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.

SRV Records

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

NameDescriptionExample
ServiceSymbolic name of service_sip.
ProtoTransport protocol_udp.
NameDomain name for this record[a]shifteight.org.
TTLTime to live (in seconds)86400
ClassDNS class field (always IN)IN
PriorityTarget host priority0
WeightRelative weight of this record0
PortTCP/UDP port number5060
TargetHostname of machine providing this servicepbx.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.

Accepting Calls to Your System

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).

Modifying sip.conf

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.

Standard dialplan

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 CheckPublicExtensionResult 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 subEmailToExtensionLookup via a GoSub().[111] We pass the value contained within the FilteredExtension 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.

File parsing

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.

Database lookup

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).

Warning

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”.

Table 12.2. NameMapping table

NameExtensionContext
leif100publicExtensions
leif.madsen100publicExtensions
lmadsen100publicExtensions
jim101publicExtensions
reception0Services [a]
voicemail*98Services

[a] Make sure this context exists on your system.


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}'

Note

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.

Dialing SIP URIs from Asterisk

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.