Opensips Asterisk integration

A growing system…..currently at about 300 call center staff, running about 2500 calls per hour. Gearing up to double that. You are unsure about that a single server will support that growth. You trying to come up with ways to scale the system and still maintain a simplistic design. So you’d like to bounce some ideas around.
This is why you reading this post I guess :) So let’s see how an Opensips Asterisk integration is done, because that will solve the scalability issues for you.

A typical use case is Opensips as a SIP proxy to scale Asterisk, by handling the user authentication and registration, letting a farm of Asterisk servers to deal with call handling (e.g., IVR, transconding, billing, etc).

This howto assumes that you already have Opensips and Asterisk installed from source, both services are up and running before you begin. Used versions are the latest stable releases from the both projects at the time of writing, so it presents the concept and implementation of a realtime integration of OpenSIPS SIP server and Asterisk media server.

OpenSIPS is used a SIP server – users are registering with it, it routes calls, etc – while the purpose of Asterisk is to provide a full set of media services – like voicemail, conference, announcements, etc.

It is a realtime integration because both OpenSIPS and Asterisk are provisioned in the same same time when comes to user accounts – when creating a new OpenSIPS users, automatically Asterisk will learn about it an provide and configure all necessary media services for it.

Both OpenSIPS and Asterisk will be provisioned (for user accounts) via a shared mysql database.

With such architecture, several other benefits can be achieved quickly:

  • increase of security – Opensips handling SIP signaling only, can absorb flooding attacks easily, protecting the Asterisk boxes
  • transport layer gatewaying – Opensips has mature implementations for UDP, TCP, TLS and SCTP, so you can use it in front of Asterisk to translate between these protocols
  • load balancing – you can use several instances of Asterisk, Opensips can do load balancing among them
  • high availability – Opensips can be configured to re-route the call if selected Asterisk box does not react in a given period of time, e.g., if one Asterisk is not responsive in 2 sec, sent the call to another Asterisk server.

As you can see, a lot of thing can be done with Opensips, if you need help with it, we can help you out, even for free! (depending on the complexity of the tasks ahead of us)

The following services will be offered by this integrated configuration:

  • voicemail – users will get access to their mailbox; authentication will be done by OpenSIPS; while Asterisk will only provide voicemail IVR (with no access PIN);
  • conference‘ – opensips will detect and forward calls related to conference service (based on prefixes) to Asterisk, which will provide access (pin based) to the conference rooms;
  • announcements – OpenSIPS will identify the cases and types of announcements that needs to be played and will simply redirect to Asterisk.

 

Only voicemail and conference services do require DB support. While the conference DB table will be exclusively be used by Asterisk (OpenSIPS does not require any information form there), the voicemail service do require a tight sharing of DB information about users between OpenSIPS and Asterisk.

Considering OpenSIPS as the core SIP element of the platform, the core DB will be also belonging to OpenSIPS – we will use the OpenSIPS DB to drive both OpenSIPS and Asterisk. The tables required by Asterisk (for voicemail service) will be mapped over the OpenSIPS tables.

This approach assumes two steps:

  1. creating the OpenSIP tables and extend them to contain also the fields (info) required by Asterisk.
  2. creating the mysql views over the OpenSIPS tables, in order to simulate the Asterisk tables

So you have installed Opensips and Asterisk, let’s see some explnation regarding new columns in subscriber table:

Once the default tables are created, we need to alter the subscriber table in order to add some additional fields required by the Asterisk DB view. These changes are:

  • add vm_password to be used as voicemail pin
  • add first_name and last_name as subscriber name
  • add email_address as subscriber’s email address
  • add datetime_created as timestamp of the subscriber creation.

Login to the opensips database  and run:

  > alter table subscriber add column `vmail_password` varchar(8) NOT NULL default '1234';
  > alter table subscriber add column `first_name` varchar(25) NOT NULL default '';
  > alter table subscriber add column `last_name` varchar(45) NOT NULL default '';
  > alter table subscriber add column `email_address` varchar(50) NOT NULL default '';
  > alter table subscriber add column `datetime_created` datetime NOT NULL default '0000-00-00 00:00:00';
Creating the Asterisk tables

Create a separate database to be used by Asterisk – login as root into mysql server and run:

  > create database asterisk;
  > GRANT ALL PRIVILEGES ON asterisk.* TO 'asterisk' IDENTIFIED  BY 'asterisk_pwd';

Create the Asterisk tables which are exclusively used only by Asterisk (as mysql tables):

# create table for the meetme service

CREATE TABLE `meetme` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `confno` varchar(80) NOT NULL DEFAULT '0',
  `username` varchar(64) NOT NULL DEFAULT '',
  `domain` varchar(128) NOT NULL DEFAULT '',
  `pin` varchar(20) DEFAULT NULL,
  `adminpin` varchar(20) DEFAULT NULL,
  `members` int(11) DEFAULT NULL,
  PRIMARY KEY (`confno`),
  UNIQUE KEY `id` (`id`)
);

# create table to store the voicemail massages
CREATE TABLE `voicemessages` (
  `id` int(11) NOT NULL auto_increment,
  `msgnum` int(11) NOT NULL default '0',
  `dir` varchar(80) default '',
  `context` varchar(80) default '',
  `macrocontext` varchar(80) default '',
  `callerid` varchar(40) default '',
  `origtime` varchar(40) default '',
  `duration` varchar(20) default '',
  `mailboxuser` varchar(80) default '',
  `mailboxcontext` varchar(80) default '',
  `recording` longblob,
  PRIMARY KEY  (`id`),
  KEY `dir` (`dir`)
);

Create the Asterisk tables (as mysql views) that import the information from OpenSIPS tables:

# create the asterisk users tables as a view over the OpenSIPS subscriber table
CREATE VIEW `sipusers` AS select
  `opensips`.`subscriber`.`username` AS `name`
  ,_latin1'friend' AS `type`,
  NULL AS `secret`,
  `opensips`.`subscriber`.`domain` AS `host`,
  concat(`opensips`.`subscriber`.`rpid`,_latin1' ',_latin1'<',`opensips`.`subscriber`.`username`,_latin1'>') AS `callerid`,
  _latin1'default' AS `context`,
  `opensips`.`subscriber`.`username` AS `mailbox`,
  _latin1'yes' AS `nat`,
  _latin1'no' AS `qualify`,
  `opensips`.`subscriber`.`username` AS `fromuser`,
  NULL AS `authuser`,
  `opensips`.`subscriber`.`domain` AS `fromdomain`,
  NULL AS `insecure`,
  _latin1'no' AS `canreinvite`,
  NULL AS `disallow`,
  NULL AS `allow`,
  NULL AS `restrictcid`,
  `opensips`.`subscriber`.`domain` AS `defaultip`,
  `opensips`.`subscriber`.`domain` AS `ipaddr`,
  _latin1'5060' AS `port`,
  NULL AS `regseconds`,
  `opensips`.`subscriber`.`username` AS `defaultuser`,
  NULL AS `fullcontact`,
  `opensips`.`subscriber`.`domain` AS `regserver`,
  NULL AS `useragent`,
  0 AS `lastms`
from `opensips`.`subscriber`;

# create the asterisk voceimail users table as a view over the OpenSIPS subscriber table
CREATE VIEW `vmusers` AS select
  concat(`opensips`.`subscriber`.`username`,`opensips`.`subscriber`.`domain`) AS `uniqueid`,
  `opensips`.`subscriber`.`username` AS `customer_id`,
  _latin1'default' AS `context`,
  `opensips`.`subscriber`.`username` AS `mailbox`,
  `opensips`.`subscriber`.`vmail_password` AS `password`,
  _latin1'joe' AS `fullname`,
  `opensips`.`subscriber`.`email_address` AS `email`,
  NULL AS `pager`,
  `opensips`.`subscriber`.`datetime_created` AS `stamp`
from `opensips`.`subscriber`;

#create the asterisk voicemail aliases table as a view over the OpenSIPS dbaliases table
CREATE VIEW `asterisk`.`vmaliases` AS select
  `opensips`.`dbaliases`.`alias_username` AS `alias`,
  _latin1'default' AS `context`,
  `opensips`.`dbaliases`.`username` AS `mailbox`
from `opensips`.`dbaliases`;

Configure the access for Asterisk to the created database (via unixodbc):

  • in etc/odbcinst.ini
[MySQL]
Description     = MySQL driver
Driver      = /usr/lib/odbc/libmyodbc.so
Setup       = /usr/lib/odbc/libodbcmyS.so
CPTimeout       =
CPReuse     =
UsageCount      = 1
  • in /etc/odbc.ini
[MySQL-asterisk]
Description = MySQL Asterisk database
Trace       = Off
TraceFile   = stderr
Driver      = MySQL
SERVER      = localhost
USER        = asterisk
PASSWORD    = asterisk_pwd
PORT        = 3306
DATABASE    = asterisk
  • in /etc/asterisk/res_odbc.conf
[asterisk]
enabled => yes
dsn => MySQL-asterisk
username => asterisk
password => asterisk_pwd
pre-connect => yes
  • in /etc/asterisk/extconfig.conf
sipusers => odbc,asterisk,sipusers
sippeers => odbc,asterisk,sipusers
voicemail => odbc,asterisk,vmusers
meetme => odbc,asterisk,meetme

The following extensions are set for Asterisk:

  1. VMR_ prefix indicates that the caller (identify by SIP FROM header) leaves a voicemail message to the user indicated by the RURI (after the prefix).
  2. VM_pickup RURI indicates that the caller (identify by SIP FROM header) accesses his own voicemailbox IVR (to listen his messages); the access is done directly, without any PIN required from Asterisk
  3. AN_notavailable plays the “Not Available” audio message
  4. AN_time plays the current time message (for the given timezone)
  5. AN_date plays the current time message (for the given timezone)
  6. AN_echo accesses the echo audio service
  7. CR_ prefix indicates access to a conference room; the number of the room is part of the RURI, after the prefix (like CR_3322); Asterisk will perform the checks for conference room existence and for the access PIN code.

Set in /etc/asterisk/extensions.conf‘ :

[general]
static=yes
writeprotect=no

[default]

; Voicemail 
exten => _VMR_.,n,Ringing
exten => _VMR_.,n,Wait(1)
exten => _VMR_.,n,Answer
exten => _VMR_.,n,Wait(1)
exten => _VMR_.,n,Voicemail(${EXTEN:4}|u)
exten => _VMR_.,n,Hangup

; Allow users to call their Voicemail directly
exten => VM_pickup,n,Ringing
exten => VM_pickup,n,wait(1)
exten => VM_pickup,n,VoicemailMain(${CALLERIDNUM}|s)
exten => VM_pickup,n,Hangup

; announcement: not available
exten => AN_notavailable,1,Ringing
exten => AN_notavailable,2,Playback(notavailable)
exten => AN_notavailable,3,Hangup

; announcement: time
exten => AN_time,1,Ringing
exten => AN_time,2,Wait(1)
exten => AN_time,3,SayUnixTime(,Europe/Bucharest,HMp)
exten => AN_time,4,Hangup

; announcement:date
exten => AN_date,1,Ringing
exten => AN_date,2,SayUnixTime(,Europe/Bucharest,ABdY)
exten => AN_date,3,Hangup

; announcement: echo
exten => AN_echo,1,Ringing
exten => AN_echo,2,Answer
exten => AN_echo,3,Echo

; Conference service
exten => _CR_.,1,Ringing
exten => _CR_.,n,Wait(1)
exten => _CR_.,n,MeetMe(${EXTEN:3}|Mi)

In this example we take the OpenSIPS default config file that provides user registration and authentication against the DB and extends the script for adding the following media oriented services:

  1. voicemail
    1. leaving a message – if the called user is not registered on OpenSIPS, the call will be forwarded by OpenSIPS (by adding VMR_ prefix) to Asterisk
    2. listening messages -if a subscriber calls to the *1111 number, OpenSIPS will rewrite it to VM_pickup and send it to Asterisk
  2. announcements
    1. service number *2111 for listening the current time message – OpenSIPS will rewrite it to AN_time and send it to Asterisk
    2. service number *2112 for listening the current date message – OpenSIPS will rewrite it to AN_date and send it to Asterisk
    3. service number *2113 for accessing the echo service – OpenSIPS will rewrite it to AN_echo and send it to Asterisk
  3. conference
    1. service number *3XXX for dialling into the conference room XXX – OpenSIPS will rewrite it to CR_XXX and send it to Asterisk

In /etc/opensips/opensips.cfg place (see the ASTERISK_HOOKS markers to find the script parts relevant to Asterisk integration):

####### Global Parameters #########

debug=3
log_stderror=no
log_facility=LOG_LOCAL0

fork=yes
children=4

/* uncomment the following lines to enable debugging */
#debug=6
#fork=no
#log_stderror=yes

port=5060

/* uncomment and configure the following line if you want opensips to 
   bind on a specific interface/port/proto (default bind on all available) */
#listen=udp:192.168.1.2:5060

####### Modules Section ########

#set module path
mpath="/lib/opensips/modules/"

loadmodule "db_mysql.so"
loadmodule "signaling.so"
loadmodule "sl.so"
loadmodule "tm.so"
loadmodule "rr.so"
loadmodule "maxfwd.so"
loadmodule "usrloc.so"
loadmodule "registrar.so"
loadmodule "textops.so"
loadmodule "mi_fifo.so"
loadmodule "uri_db.so"
loadmodule "uri.so"
loadmodule "xlog.so"
loadmodule "acc.so"
loadmodule "auth.so"
loadmodule "auth_db.so"
loadmodule "domain.so"

# ----------------- setting module-specific parameters ---------------

# ----- mi_fifo params -----
modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")

# ----- rr params -----
# do not append from tag to the RR (no need for this script)
modparam("rr", "append_fromtag", 0)

# ----- usrloc params -----
modparam("usrloc", "db_mode",   2)
modparam("usrloc", "db_url",
	"mysql://opensips:opensipsrw@localhost/opensips")

# ----- uri_db params -----
modparam("uri_db", "use_uri_table", 0)
modparam("uri_db", "db_url", "")

# ----- acc params -----
/* what sepcial events should be accounted ? */
modparam("acc", "early_media", 1)
modparam("acc", "report_cancels", 1)
/* account triggers (flags) */
modparam("acc", "failed_transaction_flag", 3)
modparam("acc", "log_flag", 1)
modparam("acc", "log_missed_flag", 2)
/* uncomment the following lines to enable DB accounting also */
modparam("acc", "db_flag", 1)
modparam("acc", "db_missed_flag", 2)

# ----- auth_db params -----
modparam("auth_db", "calculate_ha1", yes)
modparam("auth_db", "password_column", "password")
modparam("auth_db", "db_url",
	"mysql://opensips:opensipsrw@localhost/opensips")
modparam("auth_db", "load_credentials", "")

# ----- domain params -----
modparam("domain", "db_url",
	"mysql://opensips:opensipsrw@localhost/opensips")
modparam("domain", "db_mode", 1)   # Use caching

# ----- multi-module params -----
/* uncomment the following line if you want to enable multi-domain support
   in the modules (dafault off) */
modparam("alias_db|auth_db|usrloc|uri_db", "use_domain", 1)

####### Routing Logic ########

# main request routing logic

route{

	if (!mf_process_maxfwd_header("10")) {
		send_reply("483","Too Many Hops");
		exit;
	}

	if (has_totag()) {
		# sequential request withing a dialog should
		# take the path determined by record-routing
		if (loose_route()) {
			if (is_method("BYE")) {
				setflag(1); # do accounting ...
				setflag(3); # ... even if the transaction fails
			} else if (is_method("INVITE")) {
				# even if in most of the cases is useless, do RR for
				# re-INVITEs alos, as some buggy clients do change route set
				# during the dialog.
				record_route();
			}
			# route it out to whatever destination was set by loose_route()
			# in $du (destination URI).
			route(1);
		} else {
			if ( is_method("ACK") ) {
				if ( t_check_trans() ) {
					# non loose-route, but stateful ACK; must be an ACK after 
					# a 487 or e.g. 404 from upstream server
					t_relay();
					exit;
				} else {
					# ACK without matching transaction ->
					# ignore and discard
					exit;
				}
			}
			send_reply("404","Not here");
		}
		exit;
	}

	#initial requests

	# CANCEL processing
	if (is_method("CANCEL")) {
		if (t_check_trans())
			t_relay();
		exit;
	}

	t_check_trans();

	# authenticate if from local subscriber
	if (!(method=="REGISTER") && is_from_local()) {
		if (!proxy_authorize("", "subscriber")) {
			proxy_challenge("", "0");
			exit;
		}
		if (!check_from()) {
			send_reply("403","Forbidden auth ID");
			exit;
		}

		consume_credentials();
		# caller authenticated
	}

	# preloaded route checking
	if (loose_route()) {
		xlog("L_ERR",
		"Attempt to route with preloaded Route's [$fu/$tu/$ru/$ci]");
		if (!is_method("ACK"))
			send_reply("403","Preload Route denied");
		exit;
	}

	# record routing
	if (!is_method("REGISTER|MESSAGE"))
		record_route();

	# account only INVITEs
	if (is_method("INVITE")) {
		setflag(1); # do accounting
	}

	# if not a targetting a local SIP domain, just send it out
	# based on DNS (calls to foreign SIP domains)
	if (!is_uri_host_local()) {
		append_hf("P-hint: outbound\r\n"); 
		route(1);
	}

	# requests for my domain

	if (is_method("REGISTER")) {
		# authenticate the REGISTER requests
		if (!www_authorize("", "subscriber")) {
			www_challenge("", "0");
			exit;
		}
		if (!check_to()) {
			send_reply("403","Forbidden auth ID");
			exit;
		}

		if (!save("location"))
			sl_reply_error();

		exit;
	}

	if ($rU==NULL) {
		# request with no Username in RURI
		send_reply("484","Address Incomplete");
		exit;
	}

	# ASTERISK HOOK - BEGIN
	# media service number? (digits starting with *)
	if ($rU=~"^\*[1-9]+") {
		# we do provide access to media services only to our
		# subscribers, who were previously authenticated 
		if (!is_from_local()) {
			send_reply("403","Forbidden access to media service");
			exit;
		}
		#identify the services and translate to Asterisk extensions
		if ($rU=="*1111") {
			# access to own voicemail IVR
			$ru = "sip:VM_pickup@ASTERISK_IP:ASTERISK_PORT";
		} else
		if ($rU=="*2111") {
			# access to the "say time" announcement 
			$ru = "sip:AN_time@ASTERISK_IP:ASTERISK_PORT";
		} else
		if ($rU=="*2112") {
			# access to the "say date" announcement 
			$ru = "sip:AN_date@ASTERISK_IP:ASTERISK_PORT";
		} else
		if ($rU=="*2113") {
			# access to the "echo" service
			$ru = "sip:AN_echo@ASTERISK_IP:ASTERISK_PORT";
		} else
		if ($rU=~"\*3[0-9]{3}") {
			# access to the conference service 
			# remove the "*3" prefix and place the "CR_" prefix
			strip(2);
			prefix("CR_");
			rewritehostport("ASTERISK_IP:ASTERISK_PORT");
		} else {
			# unknown service
			$ru = "sip:AN_notavailable@ASTERISK_IP:ASTERISK_PORT";
		}
		# after setting the proper RURI (to point to corresponding ASTERISK extension),
		# simply forward the call
		t_relay();
		exit;
	}
	# ASTERISK HOOK - END

	# do lookup
	if (!lookup("location")) {
		# ASTERISK HOOK - BEGIN
		# callee is not registered, so different to Voicemail
		# First add the VM recording prefix to the RURI
		prefix("VMR_");
		# forward to the call to Asterisk (replace below with real IP and port)
 		rewritehostport("ASTERISK_IP:ASTERISK_PORT");
		route(1);
		# ASTERISK HOOK - END
		exit;
	}

	# when routing via usrloc, log the missed calls also
	setflag(2);

	# arm a failure route in order to catch failed calls
	# targeting local subscribers; if we fail to deliver
	# the call to the user, we send the call to voicemail
	t_on_failure("1");

	route(1);
}

route[1] {
	if (!t_relay()) {
		sl_reply_error();
	};
	exit;
}

failure_route[1] {
	if (t_was_cancelled()) {
		exit;
	}

	# if the failure code is "408 - timeout" or "486 - busy",
	# forward the calls to voicemail recording
	if (t_check_status("486|408")) {
		# ASTERISK HOOK - BEGIN
		# First revert the RURI to get the original user in RURI
		# Then add the VM recording prefix to the RURI
		revert_uri();
		prefix("VMR_");
		# forward to the call to Asterisk (replace below with real IP and port)
 		rewritehostport("ASTERISK_IP:ASTERISK_PORT");
		t_relay();
		# ASTERISK HOOK - END
		exit;
	}
}

 

Add a SIP domain in the OpenSIPS server (note that the sip domain most point -via DNS- to your opensips server; also for a SIP domain, you can use the IP address of the SIP server too):

$ mysql -h'localhost' -u'opensips' -p'opensipsrw' opensips
 > insert into domain (domain) values ("test.com");

$ opensipsctl fifo domain_reload

Create some subscribers with your OpenSIPS platform (alice@test.com with SIP password ’1234′):

$ opensipsctl add alice@test.com 1234
$ opensipsctl add bob@test.com 4321

If you want to change the voicemail pin (for subscriber alice@test.com):

$ mysql -h'localhost' -u'opensips' -p'opensipsrw' opensips
 > update subscriber set vm_password="6745" where username="alice" and domain="test.com";
Test voicemail

Register your alice subscriber (ex: using twinkle soft client) to your server. Keep bob unregistered.

From alice dial bob address – you should get the prompt for leaving a voicemail messages/recording.

Register bob also and dial *1111 – you should get the voicemail IVR and listen the recording left by alice.

Test announcements

Form a registered subscriber, simply dial the service numbers as configured in OpenSIPS (see the beginning of the previous chapter).

Test conference

Add a conference room to your system:

$ mysql -h'localhost' -u'asterisk' -p'asterisk_pwd' asterisk
 > insert into meetme (confno, pin, adminpin) values ("761","1122","4322");

From a registered SIP user, dial *3761 (to dial in conf room 761) and at IVR prompt type 1122 access pin.

At this point, we are done! Your suggestions are always welcome! We want to say thank you for your continued liking and sharing. If you haven’t liked this post yet, you may do that by hitting the buttons just below. If you are a blogger and you appreciate the hard work, whether or not you copied this post, we implore you to kindly link back this post here, you may do it anyhow you can. We consider that a great contribution.

 

 

Click here to submit your review.




Submit your review
* Required Field


Calliotel
13 Kadar street BudapestBP1132 HU 
 • +36307134111

The following two tabs change content below.

CrunchBase Information Laszlo Bekesi

Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

CommentLuv badge