OpenSIPS จะจัดการกับ SIP protocol เท่านั้น ไม่สามารถจัดการ media ต่างๆ ได้เลย จำเป็นต้องใช้ Asterisk หรือ FreeSwitch หรือ YATE มาจัดการ media เช่น เสียงตอบรับ ระบบ voicemail ในบทความตอนนี้ เราจะติดตั้ง FreeSwitch บน server เดียวกันกับ OpenSIPS และ คอนฟิก FreeSwitch ให้ทำหน้าที่เป็นระบบ voicemail

 

1. ติดตั้ง FreeSwitch บน Debian 9.x

 

 

(OpenSIPS IP = 192.168.10.222:5060, FreeSwitch IP = 192.168.10.222:5070 - internal sip profile,

FreeSwitch IP = 192.168.10.223:5080 - external sip profile บน server ตัวเดียวกัน)

 

2. คอนฟิก FreeSWITCH ให้ใช้ users ร่วมกันกับ OpenSIPS

 

2.1 #cd /etc/freeswitch/sip_profils

     #rm -rf *

     #vi external/opensips.xml

==========

<include>

  <gateway name="opensips">

    <param name="username" value=""/>

    <param name="password" value=""/>

    <param name="from-domain" value=""/>

    <param name="from-user" value=""/>

    <param name="sip-port" value="5060"/>

    <param name="proxy" value="192.168.10.222"/>

    <param name="extension" value=""/>

    <param name="register" value="false"/>

    <param name="ping" value="25"/>

    <param name="context" value="public"/>

  </gateway>

</include>

==========

 #vi external.xml

==========

<profile name="external">

  <aliases></aliases>

  <gateways>

  <X-PRE-PROCESS cmd="include" data="external/*.xml"/>

  </gateways>

  <domains></domains>

  <settings>

    <param name="sip-ip" value="192.168.10.223"/>

    <param name="rtp-ip" value="192.168.10.223"/>

    <param name="ext-sip-ip" value="192.168.10.223"/>

    <param name="ext-rtp-ip" value="192.168.10.223"/>

    <param name="sip-port" value="5080"/>

    <param name="context" value="public"/>

  </settings>

</profile>

 ==========

#vi internal.xml

 

==========

<profile name="internal">

<aliases></aliases>

<gateways>

</gateways>

<domains>

<domain name="all" alias="true" parse="false"/>

</domains>

<settings>

<param name="sip-ip" value="192.168.10.222"/>

<param name="rtp-ip" value="192.168.10.222"/>

<param name="sip-port" value="5070"/>

<param name="context" value="default"/>

<param name="auth-calls" value="true"/>

</settings>

</profile>

==========

 

2.2 #cd /etc/freeswitch/directory/default

     #rm -rf

     ลบ users ใน FreeSWITCH ทั้งหมด

 

2.3 #vi /etc/freeswitch/autoload_configs/modules.conf.xml

     แล้วเปิดใช้งาน 

    <load module = "mod_xml_curl"/>

 

2.4 edit ไฟล์ /etc/freeswitch/autoload_configs/xml_curl.conf.xml ดังนี้

==========

<configuration name="xml_curl.conf" description="cURL XML Gateway">

  <bindings>

  <binding name="directory">

             <param name="method" value="GET"/>

     <param name="gateway-url" value="http://localhost/xml_curl/get_directory.php" bindings="directory"/>                 

          </binding>

  </bindings>

</configuration>

==========

 

2.5 คอนฟิก web server เพื่อให้ FreeSWITCH ใช้โมดูล xml_curl มาดึงคอนฟิก users ของ OpenSIPS จาก mariadb ผ่าน web server ทำให้ FreeSwitch และ OpenSIPS ใช้ users ร่วมกันได้

#mkdir /var/www/html/xml_curl

edit ไฟล์ /var/www/html/xml_curl/get_directory.xml  ดังนี้

==========

<?php

 

$con=mysqli_connect("localhost","opensips","opensipsrw","opensips");

 

if (mysqli_connect_errno()) {

  echo "Failed to connect to MySQL: " . mysqli_connect_error();

  exit();

}

 

$sql = "SELECT username as number, domain, password, name, myvar1 FROM subscriber ORDER BY username";

$users = mysqli_query($con,$sql);

 

?>

 

<document type="freeswitch/xml">

    <section name="directory">

        <domain name="192.168.10.222">

            <params>

                <param name="dial-string" value="{presence_id=${dialed_user}@${dialed_domain}}${sofia_contact(${dialed_user}@${dialed_domain})}"/>

            </params>

            <groups>

                <group name="default">

                    <users>

                        <?php

                            foreach($users as $user) {

                        ?>

                        <user id="<?php echo $user['number']; ?>">

                            <params>

                                <param name="password" value="<?php echo $user['password']; ?>"/>

                                <param name="vm-password" value="<?php echo $user['myvar1']; ?>"/>

                            </params>

                            <variables>

                                <variable name="accountcode" value="<?php echo $user['myvar1']; ?>"/>

                                <variable name="user_context" value="default"/>

                                <variable name="effective_caller_id_name" value="<?php echo $user['name']; ?>"/>

                                <variable name="effective_caller_id_number" value="<?php echo $user['number']; ?>"/>

                                <variable name="outbound_caller_id_name" value="<?php echo $user['name']; ?>"/>

                                <variable name="outbound_caller_id_number" value="<?php echo $user['number']; ?>"/>

                                <variable name="mySuperVariable" value="<?php echo $user['myvar1']; ?>" />

                            </variables>

                        </user>

                        <?php } ?>

                    </users>

                </group>

            </groups>

        </domain>

    </section>

</document>

 

<?php

// Free result set

mysqli_free_result($users);

mysqli_close($con);

?>

==========

 

3. คอนฟิก opensips ดังนี้

คอนฟิก users

 

คอนฟิก domain

 

คอนฟิก groups ใน Dynamic routing

 

คอนฟิก gateways ใน Dynamic routing

 

คอนฟิก rules ใน Dynamic routing

 

ปรับคอนฟิกไฟล์ /ect/opensips/opensips.cfg ดังนี้

==========

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

log_level=3

log_stderror=no

log_facility=LOG_LOCAL0

children=4

/* uncomment the following lines to enable debugging */

#debug_mode=yes

/* uncomment the next line to enable the auto temporary blacklisting of 

   not available destinations (default disabled) */

#disable_dns_blacklist=no

/* uncomment the next line to enable IPv6 lookup after IPv4 dns 

   lookup failures (default disabled) */

#dns_try_ipv6=yes

/* comment the next line to enable the auto discovery of local aliases

   based on reverse DNS on IPs */

auto_aliases=no

 

listen=udp:192.168.10.222:5060

 

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

 

#set module path

mpath="/lib64/opensips/modules/"

 

#### SIGNALING module

loadmodule "signaling.so"

 

#### StateLess module

loadmodule "sl.so"

 

#### Transaction Module

loadmodule "tm.so"

modparam("tm", "fr_timeout", 5)

modparam("tm", "fr_inv_timeout", 30)

modparam("tm", "restart_fr_on_each_reply", 0)

modparam("tm", "onreply_avp_mode", 1)

 

#### Record Route Module

loadmodule "rr.so"

/* do not append from tag to the RR (no need for this script) */

modparam("rr", "append_fromtag", 0)

 

#### MAX ForWarD module

loadmodule "maxfwd.so"

 

#### SIP MSG OPerationS module

loadmodule "sipmsgops.so"

 

#### FIFO Management Interface

loadmodule "mi_fifo.so"

modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")

modparam("mi_fifo", "fifo_mode", 0666)

 

#### URI module

loadmodule "uri.so"

modparam("uri", "use_uri_table", 0)

 

#### MYSQL module

loadmodule "db_mysql.so"

 

#### HTTPD module

loadmodule "httpd.so"

modparam("httpd", "port", 8888)

 

#### USeR LOCation module

loadmodule "usrloc.so"

modparam("usrloc", "nat_bflag", "NAT")

modparam("usrloc", "db_mode",   2)

modparam("usrloc", "db_url",

"mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME

 

#### REGISTRAR module

loadmodule "registrar.so"

modparam("registrar", "tcp_persistent_flag", "TCP_PERSISTENT")

modparam("registrar", "received_avp", "$avp(received_nh)")/* uncomment the next line not to allow more than 10 contacts per AOR */

#modparam("registrar", "max_contacts", 10)

 

#### ACCounting module

loadmodule "acc.so"

/* what special events should be accounted ? */

modparam("acc", "early_media", 0)

modparam("acc", "report_cancels", 0)

/* by default we do not adjust the direct of the sequential requests.

   if you enable this parameter, be sure the enable "append_fromtag"

   in "rr" module */

modparam("acc", "detect_direction", 0)

modparam("acc", "db_url",

"mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME

 

#### AUTHentication modules

loadmodule "auth.so"

loadmodule "auth_db.so"

modparam("auth_db", "calculate_ha1", yes)

modparam("auth_db", "password_column", "password")

modparam("auth_db|uri", "db_url",

"mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME

modparam("auth_db", "load_credentials", "")

 

#### ALIAS module

loadmodule "alias_db.so"

modparam("alias_db", "db_url",

"mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME

 

#### DOMAIN module

loadmodule "domain.so"

modparam("domain", "db_url",

"mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME

modparam("domain", "db_mode", 1)   # Use caching

modparam("auth_db|usrloc|uri", "use_domain", 1)

 

#### PRESENCE modules

loadmodule "xcap.so"

loadmodule "presence.so"

loadmodule "presence_xml.so"

modparam("xcap|presence", "db_url",

"mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME

modparam("presence_xml", "force_active", 1)

modparam("presence", "fallback2db", 0)

 

#### DIALOG module

loadmodule "dialog.so"

modparam("dialog", "dlg_match_mode", 1)

modparam("dialog", "default_timeout", 21600)  # 6 hours timeout

modparam("dialog", "db_mode", 2)

modparam("dialog", "db_url",

"mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME

 

####  NAT modules

loadmodule "nathelper.so"

modparam("nathelper", "natping_interval", 10)

modparam("nathelper", "ping_nated_only", 1)

modparam("nathelper", "sipping_bflag", "SIP_PING_FLAG")

modparam("nathelper", "sipping_from", "sip:pinger@127.0.0.1") # CUSTOMIZE ME

modparam("nathelper", "received_avp", "$avp(received_nh)")

 

loadmodule "rtpproxy.so"

modparam("rtpproxy", "rtpproxy_sock", "udp:localhost:12221") # CUSTOMIZE ME

 

####  DIALPLAN module

loadmodule "dialplan.so"

modparam("dialplan", "db_url",

"mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME

 

####  DYNAMMIC ROUTING module

loadmodule "drouting.so"

modparam("drouting", "db_url",

"mysql://opensips:opensipsrw@localhost/opensips") # CUSTOMIZE ME

 

#### MI_JSON module

loadmodule "mi_json.so"

modparam("mi_json", "mi_json_root", "json")

 

loadmodule "permissions.so"

modparam("permissions", "db_url", "mysql://opensips:opensipsrw@localhost/opensips")

 

loadmodule "group.so"

modparam("group", "db_url", "mysql://opensips:opensipsrw@localhost/opensips")

 

loadmodule "proto_udp.so"

 

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

 

# main request routing logic

 

route {

 

# initial NAT handling; detect if the request comes from behind a NAT

# and apply contact fixing

force_rport();

if (nat_uac_test("23")) {

if (is_method("REGISTER")) {

fix_nated_register();

setbflag(NAT);

} else {

fix_nated_contact();

setflag(NAT);

}

}

 

if (!mf_process_maxfwd_header("10")) {

send_reply("483","Too Many Hops");

exit;

}

 

if (has_totag()) {

 

# handle hop-by-hop ACK (no routing required)

if ( is_method("ACK") && t_check_trans() ) {

t_relay();

exit;

}

 

# sequential request within a dialog should

# take the path determined by record-routing

if ( !loose_route() ) {

if (is_method("SUBSCRIBE") && is_myself("$rd")) {

# in-dialog subscribe requests

route(handle_presence);

exit;

}

# we do record-routing for all our traffic, so we should not

# receive any sequential requests without Route hdr.

send_reply("404","Not here");

exit;

}

 

# validate the sequential request against dialog

if ( $DLG_status!=NULL && !validate_dialog() ) {

xlog("In-Dialog $rm from $si (callid=$ci) is not valid according to dialog\n");

## exit;

}

 

if (is_method("BYE")) {

# do accounting even if the transaction fails

do_accounting("db","failed");

 

}

 

 

if (check_route_param("nat=yes")) 

setflag(NAT);

# route it out to whatever destination was set by loose_route()

# in $du (destination URI).

route(relay);

exit;

}

 

# CANCEL processing

if (is_method("CANCEL")) {

if (t_check_trans())

t_relay();

exit;

}

 

# absorb retransmissions, but do not create transaction

t_check_trans();

 

if ( !(is_method("REGISTER")  || is_from_gw() ) ) {

 

if (is_from_local()) {

# authenticate if from local subscriber

# authenticate all initial non-REGISTER request that pretend to be

# generated by local subscriber (domain from FROM URI is local)

if (!proxy_authorize("", "subscriber")) {

proxy_challenge("", "0");

exit;

}

if (!db_check_from()) {

send_reply("403","Forbidden auth ID");

exit;

}

 

consume_credentials();

# caller authenticated

 

} else {

# if caller is not local, then called number must be local

 

if (!is_uri_host_local()) {

send_reply("403","Relay Forbidden");

exit;

}

}

 

}

 

# 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")) {

 

# create dialog with timeout

if ( !create_dialog("B") ) {

send_reply("500","Internal Server Error");

exit;

}

 

do_accounting("db");

 

}

 

 

if (!is_uri_host_local()) {

append_hf("P-hint: outbound\r\n"); 

 

route(relay);

}

 

# requests for my domain

 

if( is_method("PUBLISH|SUBSCRIBE"))

route(handle_presence);

 

if (is_method("REGISTER")) {

# authenticate the REGISTER requests

if (!www_authorize("", "subscriber")) {

www_challenge("", "0");

exit;

}

 

if (!db_check_to()) {

send_reply("403","Forbidden auth ID");

exit;

}if (isflagset(NAT)) {

setbflag(SIP_PING_FLAG);

}

# store the registration and generate a SIP reply

if (!save("location"))

xlog("failed to register AoR $tu\n");

 

exit;

}

 

if ($rU==NULL) {

# request with no Username in RURI

send_reply("484","Address Incomplete");

exit;

}

 

# apply DB based aliases

alias_db_lookup("dbaliases");

 

# apply transformations from dialplan table

dp_translate("0","$rU/$rU");

 

if ($rU=~"^[05][1-9][0-9]+$") {

 

if (!do_routing("0")) {

send_reply("500","No PSTN Route found");

exit;

}

 

route(relay);

exit;

}

 

 

# do lookup with method filtering

if (!lookup("location","m")) {

if (!db_does_uri_exist()) {

send_reply("420","Bad Extension");

exit;

}

 

# redirect to a different VM system

$du = "sip:192.168.10.223:5080"; # CUSTOMIZE ME

route(relay);

 

 

if (isbflagset(NAT)) setflag(NAT);

 

# when routing via usrloc, log the missed calls also

do_accounting("db","missed");

 

route(relay);

}

 

route[relay] {

# for INVITEs enable some additional helper routes

if (is_method("INVITE")) {

 

if (isflagset(NAT)) {

rtpproxy_offer("ro");

}

 

t_on_branch("per_branch_ops");

t_on_reply("handle_nat");

t_on_failure("missed_call");

}

 

if (isflagset(NAT)) {

add_rr_param(";nat=yes");

}

 

if (!t_relay()) {

send_reply("500","Internal Error");

}

exit;

}

 

# Presence route

route[handle_presence]

{

if (!t_newtran()) {

sl_reply_error();

exit;

}

 

if(is_method("PUBLISH")) {

handle_publish();

} else

if( is_method("SUBSCRIBE")) {

handle_subscribe();

}

 

exit;

}

 

branch_route[per_branch_ops] {

xlog("new branch at $ru\n");

}

 

onreply_route[handle_nat] {

if (nat_uac_test("1"))

fix_nated_contact();

if ( isflagset(NAT) )

rtpproxy_answer("ro");

xlog("incoming reply\n");

}

 

failure_route[missed_call] {

if (t_was_cancelled()) {

exit;

}

 

# uncomment the following lines if you want to block client 

# redirect based on 3xx replies.

##if (t_check_status("3[0-9][0-9]")) {

##t_reply("404","Not found");

##exit;

##}

 

# redirect the failed to a different VM system

if (t_check_status("408|408|486|487")) {

$du = "sip:192.168.10.223:5080"; # CUSTOMIZE ME

# do not set the missed call flag again

t_relay();

exit();

}

}

 

local_route {

if (is_method("BYE") && $DLG_dir=="UPSTREAM") {

acc_db_request("200 Dialog Timeout", "acc");

}

}

==========

 

สุดท้าย แก้ไขฐานข้อมูล opensips table subcriber โดยเพิ่ม columm name และ myvar1 ดังรูป

 

 

4. ทดสอบ

คอนฟิก ip phone register เข้ากับ extension 1000, 1001, 1002 opensips server ip = 192.168.10.222

ลองโทรไปเบอร์ที่ไม่ได้ register ip phone (1003, 1004) หรือเบอร์ที่ไม่ว่าง หรือไม่รับสาย  

opensips ก็จะส่งต่อไประบบ voicemail ที่ FreeSwitch สามาถฝากข้อความได้

 

การฟังข้อความ

จาก ip phone กด  54000 opensips จะส่ง call ไปที่ระบบ voicemail บน FreeSWITCH

กดเบอร์ที่ต้องการรับฟัง voicemail (1000 - 1004) ตามด้วย # และ password (1234) ตามด้วย #

 

To Do

- ระบบ voicemail ยัง notify ไปที่ ip phone ไม่ได้ว่ามีข้อความฝากไว้