You are on page 1of 13

howto: isdn phone <> sip routing (sipgate / freenet

iphone)
(software: asterisk, mISDN and festival)
Steffen Buehl < asterisk@sbuehl.com >

v0.82 08.09.2004

the offical version of this document can be obtained from <http://www.sbuehl.com/projects/asterisk/>


- pdf version is available here <http://www.sbuehl.com/projects/asterisk/asterisk-howto.pdf>

introduction

during a trip to lloret de mar (in april 2004) i read some stuff about pbx4linux in the german computer
magazine ct. they explained how to connect a standard isdn phone to a linux box (acting as pbx) running
pbx4linux. unfortunately pbx4linux supports only h.323 connections at this time so i decided to use asterisk
to connect my isdn phone to a sip (german provider sipgate) network.
asterisk is a linux pbx software with a nice plugin interface that allow to use a wide spectrum of
endpoints. e.g. you can route between h.323, sipgate, capi (isdn), mISDN (new isdn interface for kernel 2.6)
etc.. there are also some application plugins for asterisk: festival - a linux text2speech engine - makes your
pbx speaking english mp3player - playback of mp3s stored on your harddisk a cgi-like extensions - called
agi (asterisk gateway interface) - allows you to control asterisk with your favourite scripting language (perl,
php, python, ...). i wrote a small phonebook script to lookup the callerid in a mysql table and display a
custom name on my isdn phone.
my configuration routes between mISDN (HFC-S card with attached isdn phone) and sipgate (multiple accounts). by using different prefixes on the phone i can choose which sipgate account to use, e.g. prefix
1 for hamburg (040), prefix 2 for berlin etc.
to plug a standard isdn phone in a ISDN card you need a 100ohm resistor, a power source and a
crossed isdn cable (standard crossover ethernet doesnt work). (see <http://isdn.jolly.de> ). the easiest
solution for this is to use an NT1 (also called NTBA). it contains the needed resistors and supplies power
to your attached isdn phones, additionally you can wire your cable directly to internal pins - so its kinda
perfect for this job.
you can configure multiple MSNs on your isdn phone for each sip account (i used the sip-id as MSN) and
therefore a ringtone for each id.
Okay - i got a really cheap fritz!pci isdn card (5 eur) to continue this project... The installation was
pretty easy - just plugged that card into my box and connected it to my isdn s0 bus. I configured asterisk
to dial-out with prefix 0, see example config for more details.
festival isnt used so far - but its easy to extend your configuration with the following command:
<http://www.voip-info.org/wiki-Asterisk+cmd+Festival>

2. installation

okay, enough theory, lets get started...

2
2.1
2.1.1
-

installation
requirements
hardware

NT1 (NTBA)
HFC-S card (see <http://isdn.jolly.de/cards.html>
ISDN phone
ISDN card (capi/mISDN)

2.1.2

other requirements

- linux (2.6 works, dont know about 2.4)

2.2

fetching software

asterisk: <http://www.asterisk.org/>
festival: <http://www.cstr.ed.ac.uk/projects/festival>
chan misdn: <http://www.beronet.com/?PageID=3018>
chan capi: <http://ns1.jnetdns.de/jn/relaunch/asterisk/page1.html>

2.3

compiling

should be easy - just read the docs - maybe i write some documentation for this laster...
2.3.1

asterisk

2.3.2

mISDN

2.3.3

chan misdn

2.3.4

chan capi

2.3.5

festival (text2speech)

<http://www.voip-info.org/wiki-Asterisk+Festival+installation>
with gcc 3.2.2 you have to patch festival and speech tools: <files/festival-gcc3.3.diff>
<files/speech-tools-gcc3.3.diff>

3. configuration

configuration

3.1

mISDN

3.2

asterisk

3.2.1

capi.conf
[general]
nationalprefix=
internationalprefix=0
rxgain=0.8
txgain=0.8
[interfaces]
msn=5
incomingmsn=5
controller=1
softdtmf=0
context=in
devices=2

3.2.2

misdn.conf
[general]
context=misdn
; 0 - No Debug
; 1 - mISDN Messages and * - Messages, and * - State changes
; 2 - Messages + Message specific Informations (e.g. bearer capability)
; 3 - very Verbose, the above + lots of Driver specific infos
; 4 - even more Verbose than 3
debug=1
[intern]
ports=2
context=out

3.2.3

extensions.conf
[general]
static=yes
writeprotect=no
[globals]
PHONE=mISDN/2
ISDNOUT=CAPI/5:b
NULL=
#include "macro_dial-result.inc"
[macro-process-call]

3. configuration

; ${ARG1} Device
; ${ARG1} MSN
exten => s,1,Ringing
exten => s,2,Wait,2
exten => s,3,agi,directory.agi
exten => s,4,Dial(${ARG1}/${ARG2})
exten => s,5,Hangup
[macro-dialout]
; ${ARG1} CIDNAME
; ${ARG2} Device
; ${ARG3} Num
; ${ARG4} SIP EXT
exten => s,1,SetCIDName(${ARG1})
exten => s,2,Dial(${ARG2}${ARG3}${ARG4},,t)
exten => s,3,Playback(invalid)
exten => s,4,Hangup
[sipgate-in]
exten => <sip-id-1>,1,Macro(process-call,${PHONE},<sip-id-1>)
exten => <sip-id-2>,1,Macro(process-call,${PHONE},<sip-id-2>)
exten => <freenet-id>,1,Macro(process-call,${PHONE},1)
[isdn-in]
exten => 5,1,Macro(process-call,${PHONE},5)
[sipgate-out]
exten => _1.,1,Macro(dialout,<Name ID 1>,SIP/,${EXTEN:1},@<sip-extension-1>)
exten => _2.,1,Macro(dialout,<Name ID 2>,SIP/,${EXTEN:1},@<sip-extension-2>)
exten => _3.,1,Macro(dialout,<Name ID 3>,SIP/,${EXTEN:1},@<sip-freenet-extension>)

[isdn-out]
;ISDN OUT
exten => _0.,1,Macro(dialout,<Name>,${ISDNOUT},${EXTEN},${NULL})
;ISDN-INTERN
exten => _8.,1,Macro(dialout,<Name>,${ISDNOUT},${EXTEN:1},${NULL})
[ip-out]
;IP DIAL
exten =>
exten =>
exten =>
exten =>
exten =>

_9.,1,SetCIDName(<Name>)
_9.,2,AGI(parseExtension.agi)
_9.,3,Dial(SIP/${CNUM}@${CIP},30,tr)
_9.,4,Playback(invalid)
_9.,5,Hangup

[in]
include => sipgate-in
include => isdn-in
exten => t,1,Macro(dial-result)

3. configuration

[out]
include => sipgate-out
include => isdn-out
include => ip-out
exten => s,1,Answer
exten => s,2,DigitTimeout,5
exten => s,3,ResponseTimeout,30
exten => i,1,Macro(dial-result,5)
exten => t,1,Macro(dial-result)
exten => T,2,PlayTones(congestion)
exten => T,3,Wait(5)
exten => T,4,Hangup

3.2.4

modules.conf
[modules]
autoload=yes
load => res_musiconhold.so
load => chan_misdn.so
load => chan_capi.so
noload => pbx_gtkconsole.so
noload => pbx_kdeconsole.so
noload => app_intercom.so
noload => chan_modem_i4l.so
noload => chan_modem_bestdata.so
noload => chan_modem_aopen.so
noload => chan_modem.so;
noload => chan_zap.so
noload => chan_alsa.so
noload => chan_oss.so
noload => app_groupcount.so
noload => chan_agent.so
noload => chan_iax.so
noload => app_txtcidname.so
noload => app_controlplayback.so
[global]
chan_capi.so=yes

3.2.5

sip.conf
[general]
port = 5060
bindaddr = 0.0.0.0
localnet = 192.168.0.0
localmask = 255.255.255.0
maxexpirey=3600
defaultexpirey=300
nat=no
disallow=all
allow=alaw

3. configuration

allow=ulaw
allow=g726
allow=gsm
allow=ilbc
musicclass=random
srvlookup=yes
tos=0x18
register => <sipgate-id-1>:<sipgate-password-1>@sipgate.de/<sipgate-id-1>
register => <sipgate-id-2>:<sipgate-password-2>@sipgate.de/<sipgate-id-2>
register => <freenet-id>:<freenet-password>@freenet.de/<freenet-id>
[<sip-extension-1>]
username=<sipgate-id-1>
fromuser=<sipgate-id-1>
fromdomain=sipgate.net
host=sipgate.de
secret=<sipgate-password-1>
type=friend
context=in
disallow=all
allow=gsm
allow=ilbc
allow=alaw
allow=ulaw
insecure=very
[<sip-extension-2>]
username=<sipgate-id-2>
fromuser=<sipgate-id-2>
fromdomain=sipgate.net
host=sipgate.de
secret=<sipgate-password-2>
type=friend
context=in
disallow=all
allow=gsm
allow=ilbc
allow=alaw
allow=ulaw
insecure=very
;ATTENTION!!! You have to add "194.97.54.97 freenet.de" to your /etc/hosts file
;194.97.54.97 is IP of "iphone.freenet.de" but the server accepts only "freenet.de" host...
[<sip-freenet-extension>]
username=<freenet-id>
fromuser=<freenet-id>
fromdomain=freenet.de
host=freenet.de
secret=<freenet-password>
type=friend
context=in

3. configuration

disallow=all
allow=gsm
allow=ilbc
allow=alaw
allow=ulaw
insecure=very

3.2.6

festival.conf
[general]
host=localhost
port=1314
festivalcommand=(tts_textasterisk "%s" file)(quit)\n
usecache=yes
cachedir=/var/lib/asterisk/festivalcache/

3.2.7

macro dial-result.inc

got this part from another page (works at least fine for me)
[macro-dial-result]
exten => s,1,AbsoluteTimeout(35)
exten => s,2,GotoIf($[foo${ARG1} != foo]?cause_${ARG1},1:cause_${HANGUPCAUSE},1)
; undefined error (mostly when an existing extension is currently unavailable)
exten => _cause_0,1,Answer
exten => _cause_0,2,Wait(1)
exten => _cause_0,3,Playback(the-number-u-dialed,skip)
exten => _cause_0,4,Playback(is-curntly-unavail,skip)
exten => _cause_0,5,Playback(pls-try-call-later,skip)
exten => _cause_0,6,Wait(3)
exten => _cause_0,7,Goto(3)
; normal call clearing
exten => _cause_1,1,Hangup
; extension currently busy
exten => _cause_2,1,Answer
exten => _cause_2,2,Wait(1)
exten => _cause_2,3,Playback(the-number-u-dialed,skip)
exten => _cause_2,4,Playback(is-curntly-busy,skip,skip)
exten => _cause_2,5,Playback(pls-try-call-later,skip)
exten => _cause_2,6,Wait(3)
exten => _cause_2,7,Goto(3)
; something failed
exten => _cause_3,1,Answer
exten => _cause_3,2,PlayTones(info)
exten => _cause_3,3,Wait(2)
exten => _cause_3,4,Playback(an-error-has-occured,skip)

3. configuration

exten => _cause_3,5,Playback(pls-try-call-later,skip)


exten => _cause_3,6,Wait(2)
exten => _cause_3,7,Goto(2)
; congestion
exten => _cause_4,1,Congestion
; unassigned number
exten => _cause_5,1,Answer
exten => _cause_5,2,PlayTones(info)
exten => _cause_5,3,Wait(2)
exten => _cause_5,4,Playback(ss-noservice,skip)
exten => _cause_5,5,Wait(2)
exten => _cause_5,6,Goto(2)
; unallowed number
exten => _cause_99,1,Answer
exten => _cause_99,2,PlayTones(info)
exten => _cause_99,3,Wait(2)
exten => _cause_99,4,Playback(discon-or-out-of-service,skip)
exten => _cause_99,5,Wait(2)
exten => _cause_99,6,Goto(2)
; unauthorized extension
exten => _cause_100,1,Answer
exten => _cause_100,2,PlayTones(info)
exten => _cause_100,3,Wait(2)
exten => _cause_100,4,Playback(im-sorry,skip)
exten => _cause_100,5,Playback(your,skip)
exten => _cause_100,6,Playback(extension,skip)
exten => _cause_100,7,Playback(not-yet-assigned,skip)
exten => _cause_100,8,Playback(please-contact-tech-supt,skip)
exten => _cause_100,9,Wait(2)
exten => _cause_100,10,Goto(2)
; all
exten
exten
exten
exten
exten
exten
exten
exten
exten
exten
exten
exten
exten
exten
exten

other errors
=> _cause_X.,1,Answer
=> _cause_X.,2,PlayTones(info)
=> _cause_X.,3,Wait(2)
=> _cause_X.,4,Playback(an-error-has-occured,skip)
=> _cause_X.,5,Playback(error-number,skip)
=> _cause_X.,6,GotoIf($[foo${ARG1} != foo]?7:11)
=> _cause_X.,7,SayNumber(${ARG1})
=> _cause_X.,8,Wait(1)
=> _cause_X.,9,Playback(please-contact-tech-supt,skip)
=> _cause_X.,10,Goto(14)
=> _cause_X.,11,SayNumber(${HANGUPCAUSE})
=> _cause_X.,12,Wait(1)
=> _cause_X.,13,Playback(please-contact-tech-supt,skip)
=> _cause_X.,14,Wait(2)
=> _cause_X.,15,Goto(2)

3. configuration

exten => T,1,PlayTones(congestion)


exten => T,2,Wait(5)
exten => T,3,Hangup

3.3
3.3.1

asterisk-agi
IP Dialing (resolve 1234*111222333444 to 1234@111.222.333.444)

this script is used for ip dialing (prefix 9 here)


just dial the number, followed by a *, followed by the destination ip with leading zeros

parseExtension.agi
#!/usr/local/bin/php -q
<?php
include("phpagi.php"); //see http://bcwireless.net/~matthewa/phpagi/
$agi=new AGI();

function stripZero($tmp) {
return preg_replace("/^[0]*/","",$tmp);
}
//source
$CallerID = $agi->request["agi_callerid"];
//destination
$Extension = $agi->request["agi_extension"];
preg_match("/^[0-9]([0-9]*)\*([0-9]*)$/U",$Extension,$tmp);
$num = $tmp[1];
//IP
$p1 = stripZero(substr($tmp[2],0,3));
$p2 = stripZero(substr($tmp[2],3,3));
$p3 = stripZero(substr($tmp[2],6,3));
$p4 = stripZero(substr($tmp[2],9,3));
$IP = "$p1.$p2.$p3.$p4";
$agi->agi_exec("SET VARIABLE CNUM $num");
$agi->agi_exec("SET VARIABLE CIP $IP");
exit;

3.3.2

small phonebook application

searches callerid in a mysql database and sets CIDName. if CID + Name is too long for your phone (eg.
20 char display) it will adapt the length (only firstname, only callerid, ...)
directory.agi is called from 3.2.3 (extension.conf) (incoming calls)

3. configuration

10

table structure
create table directory (
id int(14) auto_increment primary key,
firstname varchar(200),
lastname varchar(200),
number varchar(50),
display varchar(200)
);

directory.agi
#!/usr/local/bin/php -q
<?php
include("phpagi.php"); //see http://bcwireless.net/~matthewa/phpagi/
$agi=new AGI();
$dbconn = mysql_connect("localhost","root","");
mysql_select_db("asterisk");
$inDB = false;
function lookup($number) {
$query = "SELECT firstname, lastname, display from directory where number=$number";
$tmp = mysql_query($query);
if(mysql_num_rows($tmp) > 0) {
list($firstname, $lastname, $display) = mysql_fetch_array($tmp);
return $display ? $display : $firstname." ".$lastname;
}
else {
return "";
}
}
function parseCallerID($ID) {
if(preg_match("/\"(.*)\"[ ]*\<(.*)\>/",$ID,$tmp)) {
return array($tmp[1],$tmp[2]);
}
if(preg_match("/^[0-9]+$/",$ID,$tmp)) {
return array("",$ID);
}
}
function doCrop($Name, $ID) {
global $inDB;
//ID = 0
if(strlen("\"$Name\" <0>") <= 20 && $inDB) {
return array($Name, 0);
}
//only first name
$tmp = split(" ",$Name);
if(strlen("\"$tmp[0]\" <$ID>") <= 20) {
return array($tmp[0],$ID);

3. configuration

}
//first name + ID = 0
if(strlen("\"$tmp[0]\" <0>") <= 20 && $inDB) {
return array($tmp[0],0);
}
//ID only
return array("", $ID);
}
function getExtensionPrefix($Extension) {
switch ($Extension) {
case "4319000": //sipgate-hh
return 1;
break;
case "8707000": //sipgate-b
return 2;
break;
case "1": //freenet-muc
return 3;
break;
case "5": //isdn
return 0;
break;
default:
return -1;
break;
}
}
$CallerID = $agi->request["agi_callerid"];
//$Extension = $agi->request["agi_extension"];
$Extension = $agi->get_var("MACRO_EXTEN");
//$agi->con_print_r($agi->request["agi_callerid"]);
list($Name,$ID) = parseCallerID($agi->request["agi_callerid"]);
if(!$Name && $ID == 0) { //isdn call - no caller id
$agi->agi_exec("EXEC SetCallerID ".getExtensionPrefix($Extension));
$agi->agi_exec("EXEC SetCIDName \"No CallerID\"");
exit;
}
elseif($Extension == 5) {
if(strlen($ID) == 1) {
$ID = "8".$ID;
}
else {
$ID = getExtensionPrefix($Extension).$ID;
}
$Name = "";

11

4. appendix a: contact

}
elseif($Name == "anonymous") {
$agi->agi_exec("EXEC SetCallerID ".getExtensionPrefix($Extension));
$agi->agi_exec("EXEC SetCIDName \"No CallerID\"");
exit;
}
elseif(!preg_match("/^[0-9]*$/", $ID)) { //non-numeric callerid - freenet?
$ID = 0;
}
elseif(($Name == $ID) || !$Name) { //format "number" <number> - so try to lookup...
$Name = lookup($ID);
$ID = getExtensionPrefix($Extension).$ID;
$inDB = true;
}
else {
$ID = getExtensionPrefix($Extension).$ID;
}
//phone limited to 20 chars - try to crop
$comp = "\"$Name\" <$ID>";
if(strlen($comp) > 20) {
list($Name,$ID) = doCrop($Name, $ID);
}
$agi->agi_exec("EXEC SetCallerID $ID");
if($Name) {
$agi->agi_exec("EXEC SetCIDName \"$Name\"");
}
exit;

appendix a: contact

you can contact me via

email: asterisk@sbuehl.com )
IRC: server freechat-network.de, nick Shadow
IRC: ircnet, nick sbuehl

appendix b: changes

0.6
begin changelog
0.7

12

5. appendix b: changes

fixed freenet# parsing in directory.agi


added freenet-iphone support
0.8
extension.conf cleanup
added direct isdn dialout
0.81
fixed (incomplete) agi listings
0.82
fixed sip.conf comments

13

You might also like