Dean

How To Build An ENUM Server

Written by dean on Aug 28, 2008 - 05:32 PM

With ENUM ever growing in use across multiple networks and having just gone through the process of rebuilding our own ENUM server I thought I would document the overall process.

This procedure is based on a base Debian install, but should be appropriate for any *nix based server with package installs altered as appropriate.

All text within "code" tags (so green text) is either something you type on the command line, a script or a MySQL command line query, preceded root> , <?php or MySQL> accordingly. If you're not root, precede any root> command line operations with "sudo" and type in your root password when requested.

We use PowerDNS but, again, pretty much any DNS server will work fine for ENUM. I chose PowerDNS because it neatly operates in both recursive and authority modes and can use MySQL, which we use on VoIP User as our core database, as the backend. Part of the install documentation relating specifically for PowerDNS is taken from their own installation guide.


Setup Parent Domain Nameservers

I recommend buying a dedicated domain for your ENUM server as we'll be messing with nameserver and DNS entries in a way which I would not recommend for any domain running a website or mailserver.

Assuming you bought the domain "mydomain.net" for your ENUM server, you'll need this DNS server setup as the authority for it so that ENUM queries head your way over the internet. Updating the nameservers takes time to populate across the internet - assume 24-72 hours for this to take effect. Don't forget to change all references to mydomain.net in the following to your own domain and TLD.

Set parent domain nameservers to:-

ns.mydomain.net
ns2.mydomain.net

... and point those to the IP address of the server that you're building this on. NOTE : this is invalid according to section 2.2 of the DNS RFC (http://www.faqs.org/rfcs/rfc1035.html) which states that you must have at least 2 nameservers on different IP addresses per domain. I've never seen DNS fail for that reason but be aware that having 2 nameservers on one IP address is technically a breach of the standard.

Package Installation

Start of with a basic Debian install.

First off, update the packaging system.

Code:
root> apt-get update


Install Apache, MySQL and PHP5. Apache and PHP5 are not critical, but these are the basic steps to create a LAMP server and having both available enables you do create a webservices API for inserting, updating and deleting ENUM records if you need to (more on this below).

Code:
root> apt-get install mysql-server mysql-client php5-mysql

root> apt-get install apache2 php5 libapache2-mod-php5


Ensure NTP is installed and configured correctly. This is not critical, but recommended.

Code:
root> apt-get install ntp


The PHP Command Line Interpreter is a useful tool for testing, and recommend if you want to build a webservice API, but is not required for core ENUM operation.

Code:
root> apt-get install php5-cli


Install pDNS

Code:
root> apt-get install pdns-server


We use the MySQL backend which also needs to be installed.

Code:
root> apt-get install pdns-backend-mysql


Edit the config file in your favourite editor (I'm using NANO here):-

Code:
root> nano /etc/powerdns/pdns.conf


Add at the end we need to add our MySQL connection information (we'll be setting the database up shortly):-

launch=gmysql
gmysql-host=127.0.0.1
gmysql-user=pdns
gmysql-dbname=pdns
gmysql-password=[CHOOSE A PASSWORD]

Set up MySQL

Connect to MySQL as Root:-

Code:
root> mysql -u root


Create a user for pdns (change the password below to the one you chose):-

Code:
MySQL> GRANT ALL PRIVILEGES ON *.* TO 'pdns'@'localhost' IDENTIFIED BY '[PASSWORD YOU CHOSE]' WITH GRANT OPTION;


Create a new database for PDNS:-

Code:
MySQL> create database pdns;


Build the Database Tables

Connect to MySQL as a user with sufficient privileges and issue the following commands to build the PDNS tables:

Code:
MySQL> create table domains (
 id       INT auto_increment,
 name       VARCHAR(255) NOT NULL,
 master       VARCHAR(128) DEFAULT NULL,
 last_check    INT DEFAULT NULL,
 type       VARCHAR(6) NOT NULL,
 notified_serial INT DEFAULT NULL,
 account         VARCHAR(40) DEFAULT NULL,
 primary key (id)
)type=InnoDB;

CREATE UNIQUE INDEX name_index ON domains(name);

CREATE TABLE records (
  id              INT auto_increment,
  domain_id       INT DEFAULT NULL,
  name            VARCHAR(255) DEFAULT NULL,
  type            VARCHAR(6) DEFAULT NULL,
  content         VARCHAR(255) DEFAULT NULL,
  ttl             INT DEFAULT NULL,
  prio            INT DEFAULT NULL,
  change_date     INT DEFAULT NULL,
  primary key(id)
)type=InnoDB;

CREATE INDEX rec_name_index ON records(name);
CREATE INDEX nametype_index ON records(name,type);
CREATE INDEX domain_id ON records(domain_id);

create table supermasters (
  ip VARCHAR(25) NOT NULL,
  nameserver VARCHAR(255) NOT NULL,
  account VARCHAR(40) DEFAULT NULL
);

GRANT SELECT ON supermasters TO pdns;
GRANT ALL ON domains TO pdns;
GRANT ALL ON records TO pdns;


Now we need to delegate our DNS sub-zone in the main zone properly by adding a delegation record:-

Code:
MySQL> INSERT INTO records (domain_id, name, content, type,ttl,prio)
VALUES (1,'e164.mydomain.net','ns.mydomain.net','NS',86400,NULL);

INSERT INTO records (domain_id, name, content, type,ttl,prio)
VALUES (1,'e164.mydomain.net','ns2.mydomain.net','NS',86400,NULL);


And a glue record:-

Code:
MySQL> INSERT INTO records (domain_id, name, content, type,ttl,prio)
VALUES (1,'ns.mydomain.net','[IP Address of the ENUM server you're building]','A',120,NULL);


Code:
MySQL> INSERT INTO records (domain_id, name, content, type,ttl,prio)
VALUES (1,'ns2.mydomain.net','[IP Address of the ENUM server you're building]','A',120,NULL);


Tell the server that we're authoritative for the e164.mydomain.net ENUM tree with an SOA record:-

The recommended format (per RFC1912 2.2) for SOA Serial numbers is YYYYMMDDnn, where 'nn' is the revision. For example, if you are making the 3rd change on 27 August 2008, you would use 2008082803. This number should be incremented every time you make a DNS change. In the below example, you should change "2008082803" to the appropriate value in accordance with this recommendation. Don't forget to change the hostmaster email address to one of your own.

Code:
MySQL> INSERT INTO records (domain_id, name, content, type,ttl,prio) VALUES (1,'mydomain.net','localhost hostmaster<at>mydomain.net 2008082803','SOA',86400,NULL);


Testing

OK, we're done. We should now be able to insert some test records into the database and start querying.

Insert a test record:-

Code:
MySQL> INSERT INTO `records` VALUES (16, 1, '0.0.1.e164.mydomain.net', 'NAPTR', '100 10 "u" "E2U+sip" "!^.*$!sip:hello@mydomain.net!" .', 600, NULL, NULL);


Now we can test the database for the return of this ENUM record:-

Code:
root> dig 0.0.1.e164.mydomain.net ANY

; <<>> DiG 9.4.2-P1 <<>> 0.0.1.e164.mydomain.net ANY
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9906
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;0.0.1.e164.mydomain.net   IN   ANY

;; ANSWER SECTION:
0.0.1.e164.mydomain.net. 591   IN   NAPTR   100 10 "u" "E2U+sip" "!^.*$!sip:hello@mydomain.net!" .

;; Query time: 107 msec
;; SERVER: 192.168.1.254#53(192.168.1.254)
;; WHEN: Sat Aug 16 15:36:01 2008
;; MSG SIZE  rcvd: 100


Webservices API

You need a mechanism to get records in, updated, and out of your ENUM server. At VoIP User we use a simple PHP webservices API which uses a set of functions defined in an application like that shown below. You can include this PHP file in any other PHP application that you want to give the ability to add delete and update records. Keep this file outside of public HTTP access.

Code:

<?php

///////////////////////////////////////////////////////////////////////////////////////////////
//
// ENUM webservices API functions
//
// Version 0.1 by Dean Elwood 1st August 2008
//
// A set of functions to add, delete and update records in a central ENUM database.
//
// Released under GNU Public licence http://www.gnu.org/licenses/gpl.txt
//
///////////////////////////////////////////////////////////////////////////////////////////////

// EDIT THIS
$database_password = "[SET THIS TO YOUR DB PASSWORD]";
$logfile_path = "[SET THIS TO YOUR LOGFILE PATH]";

// AND DON'T FORGET TO CHANGE ALL REFERENCES TO mydomain.net TO YOUR OWN DOMAIN

function getEnumDatabaseConnection () {
        $connection = mysql_connect("localhost", "pdns", $database_password) or die ("Cannot make the connection");
        $db = mysql_selectdb("pdns", $connection) or die ("Cannot connect to the database");
        return $db;
}


function logThis($text) {
   if (!$fp = fopen($logfile_path, 'a')) {
      $result = "0";
   } else {
      flock ($fp, LOCK_SH);
      $result = fwrite($fp, date("y-m-d G.i:s", time())." ".$text."\n");
      flock($fp, LOCK_UN);
      fclose($fp);
   }
   
   return $result;
}

function cleanUpNumber($number) {

   // Strip leading chars
   $number = trim($number);
   
   // Strip non-numerics
   $number = preg_replace ("/[^0-9]/", "", $number);
   
   // Strip leading zeros
   while ( $number[0] == "0" ) {
      $number = ltrim($number, "0");
   }
   
   return $number;
}


function enumFormat($number) {
// Takes an e164 formatted number and returns ENUM formatted

   $number = cleanUp($number);

   $e164 = ereg_replace("[^0-9]", "", $number);

   // Reverse the order of our e164 number and put dots in between
   $rev_e164 = "";

   for ($i = strlen($e164); $i >= 0; $i-- ) {
      $rev_e164 .= $e164[$i];
      if ( $i != strlen($e164) ) { $rev_e164 .= "."; }
   }

   // Shove our private tree tail on the end
   $rev_e164 .= "e164.mydomain.net";
   
   return $rev_e164;

}

function getEnumRecord($number) {
    $enum = enumFormat($number);
    $db   = getEnumDatabaseConnection();
    $sql  = "SELECT * FROM `records` WHERE `name` = '$enum' LIMIT 1";
    $result = mysql_query($sql);

    if ($result) {
        $row = mysql_fetch_row($result);
        $response = $row['0'];
    } else {
        $response = False;
    }
       
    return $response;
}

function addEnumRecord($sipUri, $number) {
   // Check it doesn't already exist...
   logThis("Trying to add $sipUri as destination for $number");

   if ( getEnumRecord($number) ) {
      $response = False;
   } else {
   
       $enum = enumFormat($number);
       $content = "100 10 \"u\" \"E2U+sip\" \"!^.*$!sip:".$sipUri."!\" .";

       // Shove it in the DB as a new NAPTR record
       $db = getEnumDatabaseConnection();
       $sql = "INSERT INTO `records` values('', '10', '$enum', 'NAPTR', '$content', '600', NULL, UNIX_TIMESTAMP());";
       $response = mysql_query($sql);

      if (!$response) {
          logThis("Database insert failed trying to add enum record.");
      } else {
         logThis("Added $number to ENUM routing to $sipUri");
      }    

   }
    return $response;
}

function updateEnumRecord($number, $sipUri) {
   $enum = enumFormat($number);

   $content = "100 10 \"u\" \"E2U+sip\" \"!^.*$!sip:".$sipUri."!\"";

   $db = getEnumDatabaseConnection();
      $sql = "UPDATE `records` SET `content` = '$content' WHERE `name` ='$enum' LIMIT 1";   
      $response = mysql_query($sql);

      if (!$response) {
          logThis("Database insert failed trying to update enum record.");
      }     
   
   return $response;
}


function deleteEnumRecord($recordId) {
   $db = getEnumDatabaseConnection();
    $sql = "DELETE FROM `records` WHERE `id` = '$recordId'";
    $response = mysql_query($sql);

    if (!$response) {
       logThis("Database insert failed trying to delete enum record.");
    }
   
    return $response;
}

?>




If you need to call these functions from another server over HTTP, you can use a webservice front-end like the script below. This is not finished, but a basic framework ready for final touches (and debugging) as required. Should be enough to get you started.

Code:

<?php

///////////////////////////////////////////////////////////////////////////////////////////////
//
// ENUM Webservice API
//
// Version 0.1 by Dean Elwood 1st August 2008
//
// Released under GNU Public licence http://www.gnu.org/licenses/gpl.txt
//
//
///////////////////////////////////////////////////////////////////////////////////////////////

// EDIT THIS LINE
include_once('[/path/to/the/functions/file/above]');

$todo     = $_GET['todo'];
$number   = $_GET['number'];
$sipuri   = $_GET['sipuri'];

$success  = false;

switch ($todo) {
   
   case "add" :
      $success = addEnumRecord($sipuri, $number);
      break;
      
   case "delete" :
      $recordId = getEnumRecord($number);

      if ( $recordId > 0 ) {
         $success = deleteEnumRecord($recordId);
      } else {
         $success = False;
      }

      break;

   case "update" :
      $success = updateEnumRecord($sipuri, $number);
      break;

   default :
      echo "Nothing to do?\n";
      break;
   
}

if ( $success ) {
   echo $success;
} else {
      print_r($_GET);
}

?>



Using the webservice API is a matter of calling it with the vars required as GET commands in the URL:-

Code:
http://mydomain.net/webservice.php?todo=add&number=200&sipuri=hello2@mydomain.net


Bear in mind that the above makes no attempt at any security measures. You might want to consider using HTTPS (as we do) or at least firewalling access to the ENUM box such that only trusted IP addresses can access this (as we also do).

OpenSER configuration

To get an OpenSER box to use the enum service, you need to make sure the enum module is loaded in your modparam statements in the global section of your openser.cfg. Then you need to place a snippet of config similar to the one below in your config section that deals with outbound calls from local, authenticated users.

Code:

# enum section
# - This snippet should be placed in your local number routing block
# - ENUM_REGEX_VAL should be replaced with a valid regex to suit your environment
# - e164.voipuser.org should be replaced with your enum server

if ( (method=="INVITE" || method=="CANCEL") && uri=~"ENUM_REGEX_VAL") {
    xlog("SCRIPT: trying enum lookup for [$ru]\n");
    if (enum_query("e164.mydomain.net")) {
        xlog("SCRIPT: enum lookup [ => $ru]\n");
        if (!(uri==myself )) {
            append_hf("P-hint: ENUM-OUTBOUND\r\n");
            return(-1);
        }
    }
}


Asterisk Configuration

For ENUM lookups from asterisk the following code will perform the lookup as well as route the call, if no result is found it will route over a defined other route.

Change all references of mydoman.net to your own domain and TLD.

Code:
[enum]
; enum lookup.
;The folllowing may need regionalising if your international numbers dont start 00 when dialing.
exten=> _740XXX.,1,Set(enum_m=${EXTEN:2:2})
exten=> _740XXX.,n,GotoIf($["${enum_m}" = "00"]?3:120)
exten=> _740XXX.,n,Set(enum_d=${EXTEN:4})
exten=> _740XXX.,n,Set(enum_n=${ENUMLOOKUP(+${enum_d},,,e164.mydomain.net)})
exten=> _740XXX.,n,Set(enum_c=${ENUMLOOKUP(+${enum_d},,c,e164.mydomain.net)})

exten=> _740XXX.,n,NoOp(${enum_c})
exten=> _740XXX.,n,GotoIf($["${enum_c}" = "0"]?102)
exten=> _740XXX.,n,GotoIf($["${enum_c}" = ""]?102)
exten=> _740XXX.,n,NoOp(SIP/${enum_n})
exten=> _740XXX.,n,Dial(SIP/${enum_n})
exten=> _740XXX.,n,Hangup()
exten=> _740XXX.,102,Dial,${TRUNKout}/${EXTEN:2}
exten=> _740XXX.,103,Hangup()
exten=> _740XXX.,111,Hangup()
exten=> _740XXX.,120,Set(enum_d=44${EXTEN:3}) ; You need to make sure your national code is here


What can I do with this?

If you run a network, like we do, you can use ENUM to re-write calls to a PSTN number to a SIP URI and route them on net without hitting the PSTN. You can also use it to restrict calls to certain numbers by populating those into your ENUM registry and creating a NAPTR record for them pointing to sip:dev@null.com or an IVR or recorded message.

Asterisk can use ENUM in the same way allowing callers to use PSTN numbers to make on-net SIP calls. For example, if you have a satellite office, with an Asterisk server at each and a SIP trunk between the two, you can rewrite calls from one office to a DDI number at the satellite office so that callers don't have to use a secondary number and the call does not hit the PSTN.

Of course ENUM is pretty useless without a populated ENUM registry. If you don't have a network and there's no point in you running your own, you can always point your openSER or asterisk at one of the public registrars instead.

 


Further resources:-

http://bind8nt.meiway.com/publicDNS.cfm
http://www.ludd.luth.se/~kavli/BIND-FAQ.html
http://www.faqs.org/rfcs/rfc1035.html
http://www.isc.org/index.pl?/sw/bind/
http://www.powerdns.com
Add To Delicious Print this Thread Grab our feed
Reply from nmthaker on May 16, 2009 - 10:17 AM
Hi,

Your information is very nice and helpful to me as well i done that in my LAB, I want to setup complate ENUM Netowrking in india, can you guide me how can i make things ready ?

Nishit
Reply from dean on May 16, 2009 - 07:55 PM
Welcome to VoIP User Nishit.

Where did you get stuck with the above?
Reply from nmthaker on May 19, 2009 - 09:48 AM
Hi,

I setup above things now i want to know how can i register my DNS , where to get this and once i done this how can i connect teleco. I want to be a next xconnect , can you tell me how can it be possible....

Nishit
Reply from dean on May 19, 2009 - 08:18 PM
Quote:
I setup above things now i want to know how can i register my DNS , where to get this and once i done this how can i connect teleco.


From a technology perspective it's as simple as asking all telco's to route via you (or a lookup through your ENUM tree).

Quote:
I want to be a next xconnect , can you tell me how can it be possible....


Sure - trust. You need to convince all the Tier 1 telcos that they should route their traffic via you. That takes a large amount of business development effort and a lot of money and infrastructure to prove that they can trust you.

It's not as simple as dropping them an email I'm afraid. You need to leverage your connections in the industry. If you have CTO level contacts at major telco's you might be in with a shot, but even then it's a long one.

Exam question:-

What value are you adding for them?
Reply from georgebratoveanu on Jun 04, 2009 - 11:21 AM
Regarded Sir,

I 'm a newbie in this matter. My knowledge is only to make an asterisk or a pbx to a machine in VoIP environment. We are in Jordan and we try to push the National regulatory to be a member in ENUM. (to request delegation from ITU)
But in the mean time I should make an ENUM server an to check if it is working.
I follow your material but unfortunately when I make the request for the number with dig the answer contain everything except the answer. I put debian 5.2 to play.
I try to play with slackware 12.2 but I play around the tail.
Sad
And I do not find the way to go out from this mess.
Can you please give me a hint how I suppose to check the mistake.

Best regards,
George.
Reply from dean on Jun 11, 2009 - 11:07 AM
Quote:
I follow your material but unfortunately when I make the request for the number with dig the answer contain everything except the answer.


What answer are you getting ?
Reply from georgebratoveanu on Jun 11, 2009 - 01:44 PM
Regarded Sir,

I solve the problem with the answer from the ENUM server, but I think I have a problem in the TrixBox servers that should connect together thru the ENUM server.
This is the answer,

[trixbox1.localdomain ~]# dig 0.0.0.0.0.8.7.8.2.6.9.e164.jcsfiberlink.net ANY

; <<>> DiG 9.3.4-P1 <<>> 0.0.0.0.0.8.7.8.2.6.9.e164.jcsfiberlink.net ANY
;; global options: printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39476
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 1, ADDITIONAL: 1

;; QUESTION SECTION:
;0.0.0.0.0.8.7.8.2.6.9.e164.jcsfiberlink.net. IN ANY

;; ANSWER SECTION:
0.0.0.0.0.8.7.8.2.6.9.e164.jcsfiberlink.net. 86400 IN NAPTR 100 10 "u" "E2U+sip"
"!^.*$!sip:87800000@79.134.128.14!" .
0.0.0.0.0.8.7.8.2.6.9.e164.jcsfiberlink.net. 86400 IN NAPTR 102 10 "u" "E2U+emai
l:mailto" "!^.*$!mailto:office@jcs.jo!i" .

;; AUTHORITY SECTION:
jcsfiberlink.net. 86400 IN NS ns.jcsfiberlink.net.

;; ADDITIONAL SECTION:
ns.jcsfiberlink.net. 158452 IN A 79.134.128.15

;; Query time: 6 msec
;; SERVER: 79.134.128.3#53(79.134.128.3)
;; WHEN: Thu Jun 11 15:57:12 2009
;; MSG SIZE rcvd: 220

May be the sentence for the number is not correct. 8780000 [!at] 79.134.128.14 (replace the [!at] with a @) should be something else than user@pbx(which connect it)?

Any help will be very appreciated.
George
Reply from georgebratoveanu on Jun 13, 2009 - 07:46 AM
Regarded sir,

I find where is the problem. Even if you configure correct the ENUM trunk in TrixBox and modify the order of preference in /etc/asterisk/enum.conf you do not have any success with it.
The only solution is to write enumlookup.agi from /var/lib/asterisk/agi-bin/enumlookup.agi with the right sequence of searching for the enum server. Unfortunately if you make it with nano or vi the file will rewrite himself at another update in TrixBox.
Anybody know what modify is needed to not happened this everytime?

Best regards,
George
Voip User Forum Index » The World of VoIP » VoIP General
Reply to topic
Forum Rules and Guidelines | About VoIP User | Privacy Policy


All logos and trademarks in this site are property of their respective owner.
Comments and posts are property of the poster, all the rest (c) 2003-2008 VoIP User Limited.

VoIP User Limited is incorporated in England and Wales under Company Number 6694577.

No part of this site may be reproduced without our prior consent.