sca Module

Andrew Mortensen

   University of Pennsylvania

   Copyright © 2012 Andrew Mortensen, admorten@isc.upenn.edu
     __________________________________________________________________

   Table of Contents

   1. Admin Guide

        1. Overview
        2. Dependencies

              2.1. Modules

        3. Parameters

              3.1. hash_table_size (integer)
              3.2. call_info_max_expires (integer)
              3.3. line_seize_max_expires (integer)
              3.4. purge_expired_interval (integer)
              3.5. db_url (str)
              3.6. subs_table (str)
              3.7. db_update_interval (integer)

        4. Functions

              4.1. sca_handle_subscribe()
              4.2. sca_call_info_update()

        5. Exported RPC Commands

              5.1. sca.all_subscriptions
              5.2. sca.all_appearances
              5.3. sca.seize_appearance
              5.4. sca.update_appearance
              5.5. sca.release_appearance

        6. Sample kamailio.cfg with SCA

   List of Examples

   1.1. Set hash_table_size:
   1.2. Set call_info_max_expires:
   1.3. Set line_seize_max_expires:
   1.4. Set purge_expired_interval:
   1.5. Set db_url parameter:
   1.6. Set subs_table parameter:
   1.7. Set db_update_interval:
   1.8. sca_handle_subscribe usage:
   1.9. sca_call_info_update usage:
   1.10. kamailio.cfg

Chapter 1. Admin Guide

   Table of Contents

   1. Overview
   2. Dependencies

        2.1. Modules

   3. Parameters

        3.1. hash_table_size (integer)
        3.2. call_info_max_expires (integer)
        3.3. line_seize_max_expires (integer)
        3.4. purge_expired_interval (integer)
        3.5. db_url (str)
        3.6. subs_table (str)
        3.7. db_update_interval (integer)

   4. Functions

        4.1. sca_handle_subscribe()
        4.2. sca_call_info_update()

   5. Exported RPC Commands

        5.1. sca.all_subscriptions
        5.2. sca.all_appearances
        5.3. sca.seize_appearance
        5.4. sca.update_appearance
        5.5. sca.release_appearance

   6. Sample kamailio.cfg with SCA

1. Overview

   The sca module implements Shared Call Appearances. It handles SUBSCRIBE
   messages for call-info and line-seize events, and sends call-info
   NOTIFYs to line subscribers to implement line bridging. The module
   implements SCA as defined in Broadworks SIP Access Side Extensions
   Interface Specifications, Release 13.0, version 1, sections 2, 3 and 4.

   SCA group members receive call state notifications when other group
   members participate in calls. An SCA caller can place a call on hold,
   and the call may be retrieved from hold by another member of the group.

   Subscribers to SCA call-info events SUBSCRIBE to their
   address-of-record (AoR), asking the application server to send
   call-info NOTIFYs with line state information as lines in the
   subscriber group are used.

   For example, when an SCA subscriber takes the phone off hook, it sends
   a line-seize SUBSCRIBE to the application server. The application
   server acknowledges the request, and sends to the subscriber a
   line-seize NOTIFY with the appearance index of the line claimed for the
   subscriber. The application also sends call-info NOTIFYs to the other
   SCA subscribers to the AoR, letting them know that an appearance within
   the group has gone off hook. Subscribers update their display
   appropriately.

   Subscribers to an SCA address-of-record will receive call-info NOTIFYs
   when a member of the group seizes a line (seized); receives a 180
   ringing response from the remote party (ringing); receives a 183
   progressing response from the remote party (progressing); when the
   remote party answers the call (active); when either party in the call
   places the call on hold (held); and when an SCA line goes back on hook
   (idle).

   The call-info subscriber information is stored in memory and is
   periodically written to the database. Call state information is stored
   in memory. A future release may periodically write call state to the
   database, as well. The database is purely for restoring subscriptions
   after a restart of the application server. Subscriber information is
   also flushed to the database when the service is stopped.

   At the time of this writing, Polycom and Cisco handsets are known to
   implement the call-info and line-seize event packages defined in the
   document, which may be found freely on the web.

   To date, this module has only been tested with Polycom Soundpoint 550s
   and 650s running Polycom SIP 3.3.4.

2. Dependencies

   2.1. Modules

2.1. Modules

   The following modules must be loaded before this module:
     * a database module
     * sl
     * tm

3. Parameters

   3.1. hash_table_size (integer)
   3.2. call_info_max_expires (integer)
   3.3. line_seize_max_expires (integer)
   3.4. purge_expired_interval (integer)
   3.5. db_url (str)
   3.6. subs_table (str)
   3.7. db_update_interval (integer)

3.1. hash_table_size (integer)

   Size, as a power of two, of the shared memory hash table containing the
   call-info subscriptions and the appearance state. A larger power of two
   means better performance (fewer collisions, making for fewer subscriber
   URI comparisons) at the expense of increased shared memory use.

   Default value is 9 (2 ^ 9 == 512).

   Example 1.1. Set hash_table_size:
...
# create shared memory hash table with 2^8 (256) slots
modparam( "sca", "hash_table_size", 8 )
...

3.2. call_info_max_expires (integer)

   The maximum allowed call-info subscription time in seconds.

   Default value is 3600 (1 hour).

   Example 1.2. Set call_info_max_expires:
...
modparam( "sca", "call_info_max_expires", 1800 )
...

3.3. line_seize_max_expires (integer)

   The maximum allowed line-seize subscription time in seconds.

   Default value is 15 (15 seconds).

   A maximum line-seize subscription time of 15 seconds is recommended in
   the SIP Access Side Extensions document. This interval is purposely
   short to prevent a client from seizing an appearance without making a
   call for extended periods of time.

   Example 1.3. Set line_seize_max_expires:
...
modparam( "sca", "line_seize_max_expires", 30 )
...

3.4. purge_expired_interval (integer)

   The period of time in seconds between purges of expired call-info and
   line-seize subscriptions.

   Default value is 120 (2 minutes).

   On finding an expired subscription, the module removes the subscription
   from the shared memory hash table, and sends a NOTIFY with
   Subscription-State "terminated;expired" header value to the subscriber.
   It also NOTIFYs other members of the group, in the event that the
   expired subscription was a line-seize.

   Example 1.4. Set purge_expired_interval:
...
modparam( "sca", "purge_expired_interval", 60 )
...

3.5. db_url (str)

   URL of database to which subscribers will be written.

   Default value is mysql://kamailio:kamailiorw@localhost/kamailio

   Example 1.5. Set db_url parameter:
...
modparam( "sca", "db_url", "mysql://kamailio:kamailiorw@localhost/kamailio" )
...

3.6. subs_table (str)

   Name of the database table where call-info subscriptions are written.

   Default value is "sca_subscriptions".

   Example 1.6. Set subs_table parameter:
...
modparam( "sca", "subs_table", "call_info_subscriptions" )
...

3.7. db_update_interval (integer)

   Period in seconds between writes of call-info subscriber information to
   the database.

   Default value is 300 (5 minutes).

   Example 1.7. Set db_update_interval:
...
modparam( "sca", "db_update_interval", 120 )
...

4. Functions

   4.1. sca_handle_subscribe()
   4.2. sca_call_info_update()

4.1.  sca_handle_subscribe()

   The function handling call-info and line-seize SUBSCRIBE requests. It
   stores or updates the subscriptions in shared memory, and sends NOTIFYs
   to the subscriber and other members of the group as needed.

   For example, a line-seize SUBSCRIBE will cause the module to reserve an
   appearance index for the subscriber; send a line-seize NOTIFY to the
   subscriber indicating which appearance index it must use; and send
   call-info NOTIFYs to other subscribers to the address-of-record letting
   them know the appearance is off hook.

   This function can be used from the REQUEST_ROUTE.

   Return code:
     * 1 - successful
     * -1 - failed, error logged

   Example 1.8. sca_handle_subscribe usage:
...
if ( is_method( "SUBSCRIBE" )) {
    if ( $hdr(Event) == "call-info" || $hdr(Event) == "line-seize" ) {
        sca_handle_subscribe();
        exit;
    }
}
...

4.2.  sca_call_info_update()

   The sca_call_info_update function updates call state for SCA
   appearances. If a request or response packet contains a Call-Info
   header, the function extracts call state from the header and sends
   NOTIFYs to subscribers if needed. If no Call-Info header is included in
   the packet, the module looks up the To and From URIs to see if either
   are SCA addresses-of-record. If either the To or From URI are SCA AoRs,
   the function looks up the appearance by dialog and updates call state
   as needed, sending NOTIFYs to members of the group if the call state
   has changed.

   The sca_call_info_update function updates call state for INVITE,
   CANCEL, BYE, PRACK and REFER requests and responses.

   This function can be used from the REQUEST_ROUTE, REPLY_ROUTE, and
   FAILURE_ROUTE.

   Return code:
     * 1 - successful
     * -1 - failed, error logged

   Example 1.9. sca_call_info_update usage:
...
route
{
...
    sca_call_info_update();
...
}

onreply_route[REPLY_ROUTE]
{
...
    if ( status =~ "[456][0-9][0-9]" ) {
        # don't update SCA state here, since there may be
        # failure route processing (e.g., call forwarding).
        # update state in failure route instead.
        break;
    }

    sca_call_info_update();
...
}

failure_route[FAILURE_ROUTE]
{
...
    sca_call_info_update();
...
}
...

5. Exported RPC Commands

   5.1. sca.all_subscriptions
   5.2. sca.all_appearances
   5.3. sca.seize_appearance
   5.4. sca.update_appearance
   5.5. sca.release_appearance

5.1. sca.all_subscriptions

   List all current call-info and line-seize subscriptions.

   Name: sca.all_subscriptions

   Parameters: none

   Example:
            kamcmd sca.all_subscriptions

5.2. sca.all_appearances

   List all SCA appearances with non-idle state.

   Name: sca.all_appearances

   Parameters: none

   Example:
            kamcmd sca.all_appearances

5.3. sca.seize_appearance

   Seize an appearance index for a specific contact within an SCA group,
   and notify other members of the group that the appearance is off hook.
   Useful for testing SCA signaling.

   Name: sca.seize_appearance

   Parameters: 2
     * SCA Address-of-Record
     * SCA Contact URI

   Example:
            # seize next available appearance of sip:215@voice.example.com
            # for contact sip:215@10.0.1.2
            kamcmd sca.seize_appearance sip:215@voice.example.com sip:215@10.0.1
.2

5.4. sca.update_appearance

   Update the state of an in-use appearance index, and notify other
   members of the group. Useful for testing SCA signaling.

   Name: sca.update_appearance

   Parameters: 3 or 4
     * SCA Address-of-Record
     * Index of In-Use Appearance
     * Appearance State (seized, ringing, progressing, active, held,
       held-private)
     * Appearance Display Info (Optional)

   Example:
            # update in-use appearance index 3 of sip:215@voice.example.com
            # state held.
            kamcmd sca.update_appearance sip:215@voice.example.com 3 held

5.5. sca.release_appearance

   Set a non-idle appearance index to idle and NOTIFY members of the
   group.

   Name: sca.release_appearance

   Parameters: 2
     * SCA Address-of-Record
     * Appearance Index

   Example:
            # release appearance of sip:215@voice.example.com with
            # appearance index 3
            kamcmd sca.release_appearance sip:215@voice.example.com 3

6. Sample kamailio.cfg with SCA

   The following is a basic kamailio.cfg providing Shared Call Appearances
   to local subscribers. It has been tested with Polycom handsets.

   Example 1.10. kamailio.cfg
##
#!KAMAILIO
#
# example kamailio.cfg with Shared Call Appearances (SCA)

#!define WITH_AUTH
#!define WITH_MYSQL
#!define WITH_SCA

####### Defined Values #########

#!ifdef WITH_MYSQL
# - database URL - used to connect to database server by modules such
#       as: auth_db, acc, usrloc, a.s.o.
#!ifndef DBURL
#!define DBURL "mysql://kamailio:kamailiorw@localhost/kamailio"
#!endif
#!endif

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

#!ifdef WITH_DEBUG
debug=4
log_stderror=yes
#!else
debug=2
log_stderror=no
#!endif

memdbg=5
memlog=5

log_facility=LOG_LOCAL0

fork=yes
children=4

listen=udp:10.0.0.10:5060
port=5060

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

# set paths to location of modules (to sources or installation folders)
#!ifdef WITH_SRCPATH
mpath="modules_k:modules"
#!else
mpath="/usr/local/kamailio/lib64/kamailio/modules_k/:/usr/local/kamailio/lib64/k
amailio/modules/"
#!endif

#!ifdef WITH_MYSQL
loadmodule "db_mysql.so"
#!endif

loadmodule "tm.so"
loadmodule "sl.so"
loadmodule "rr.so"
loadmodule "pv.so"
loadmodule "maxfwd.so"
loadmodule "usrloc.so"
loadmodule "registrar.so"
loadmodule "textops.so"
loadmodule "siputils.so"
loadmodule "xlog.so"
loadmodule "sanity.so"
loadmodule "ctl.so"
loadmodule "cfg_rpc.so"

#!ifdef WITH_AUTH
loadmodule "auth.so"
loadmodule "auth_db.so"
#!ifdef WITH_IPAUTH
loadmodule "permissions.so"
#!endif
#!endif

#!ifdef WITH_SCA
loadmodule "sca.so"
#!endif


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


# ----- tm params -----
# auto-discard branches from previous serial forking leg
modparam("tm", "failure_reply_mode", 3)
# default retransmission timeout: 30sec
modparam("tm", "fr_timer", 30000)
# default invite retransmission timeout after 1xx: 120sec
modparam("tm", "fr_inv_timer", 120000)


# ----- rr params -----
# add value to ;lr param to cope with most of the UAs
modparam("rr", "enable_full_lr", 1)
# do not append from tag to the RR (no need for this script)
modparam("rr", "append_fromtag", 0)


# ----- registrar params -----
modparam("registrar", "method_filtering", 1)
/* uncomment the next line to disable parallel forking via location */
# modparam("registrar", "append_branches", 0)
/* uncomment the next line not to allow more than 10 contacts per AOR */
#modparam("registrar", "max_contacts", 10)
# max value for expires of registrations
modparam("registrar", "max_expires", 3600)
# set it to 1 to enable GRUU
modparam("registrar", "gruu_enabled", 0)


# ----- usrloc params -----
/* enable DB persistency for location entries */
#!ifdef WITH_USRLOCDB
modparam("usrloc", "db_url", DBURL)
modparam("usrloc", "db_mode", 2)
modparam("usrloc", "use_domain", 0)
#!endif


# ----- auth_db params -----
#!ifdef WITH_AUTH
modparam("auth_db", "db_url", DBURL)
modparam("auth_db", "calculate_ha1", yes)
modparam("auth_db", "password_column", "password")
modparam("auth_db", "load_credentials", "")

# ----- permissions params -----
#!ifdef WITH_IPAUTH
modparam("permissions", "db_url", DBURL)
modparam("permissions", "db_mode", 1)
#!endif

#!endif

# ----- sca params -----
#!ifdef WITH_SCA
modparam("sca", "call_info_max_expires", 300)
modparam("sca", "db_url", DBURL)
#!endif


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

# Main SIP request routing logic
# - processing of any incoming SIP request starts with this route
# - note: this is the same as route { ... }
request_route {

        # per request initial checks
        route(REQINIT);

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

        # handle requests within SIP dialogs
        route(WITHINDLG);

        ### only initial requests (no To tag)

        t_check_trans();

        # authentication
        route(AUTH);

        # record routing for dialog forming requests (in case they are routed)
        # - remove preloaded route headers
        remove_hf("Route");
        if (is_method("INVITE|SUBSCRIBE"))
                record_route();

        # dispatch requests to foreign domains
        route(SIPOUT);

        # handle registrations
        route(REGISTRAR);

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

        # user location service
        route(LOCATION);

        route(RELAY);
}


route[RELAY] {

        # enable additional event routes for forwarded requests
        if (is_method("INVITE|BYE|SUBSCRIBE|PRACK|REFER|UPDATE")) {
                if(!t_is_set("onreply_route")) t_on_reply("MANAGE_REPLY");
        }
        if (is_method("INVITE")) {
                if(!t_is_set("failure_route")) t_on_failure("MANAGE_FAILURE");
        }

        route(SCA);

        if (!t_relay()) {
                sl_reply_error();
        }
        exit;
}

# Per SIP request initial checks
route[REQINIT] {
        if (!mf_process_maxfwd_header("10")) {
                sl_send_reply("483","Too Many Hops");
                exit;
        }

        if(!sanity_check("1511", "7"))
        {
                xlog("Malformed SIP message from $si:$sp\n");
                exit;
        }
}

# Handle requests within SIP dialogs
route[WITHINDLG] {
        if (has_totag()) {
                # sequential request withing a dialog should
                # take the path determined by record-routing
                if (loose_route()) {
                        if ( is_method("NOTIFY") ) {
                                # Add Record-Route for in-dialog NOTIFY as per R
FC 6665.
                                record_route();
                        }
                        route(RELAY);
                } else {
                        if (is_method("SUBSCRIBE") && uri == myself) {
                                # in-dialog subscribe requests
                                route(SCA);
                                exit;
                        }
                        if ( is_method("ACK") ) {
                                if ( t_check_trans() ) {
                                        # no 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 ... i
gnore and discard
                                        exit;
                                }
                        }
                        sl_send_reply("404","Not here");
                }
                exit;
        }
}

# Handle SIP registrations
route[REGISTRAR] {
        if (is_method("REGISTER"))
        {
                if (!save("location"))
                        sl_reply_error();

                exit;
        }
}

# USER location service
route[LOCATION] {
        $avp(oexten) = $rU;
        if (!lookup("location")) {
                $var(rc) = $rc;
                t_newtran();
                switch ($var(rc)) {
                        case -1:
                        case -3:
                                send_reply("404", "Not Found");
                                exit;
                        case -2:
                                send_reply("405", "Method Not Allowed");
                                exit;
                }
        }
}

# Authentication route
route[AUTH] {
#!ifdef WITH_AUTH

#!ifdef WITH_IPAUTH
        if((!is_method("REGISTER")) && allow_source_address())
        {
                # source IP allowed
                return;
        }
#!endif

        if (is_method("REGISTER") || from_uri==myself)
        {
                # authenticate requests
                if (!auth_check("$fd", "subscriber", "1")) {
                        auth_challenge("$fd", "0");
                        exit;
                }
                # user authenticated - remove auth header
                if(!is_method("REGISTER|PUBLISH"))
                        consume_credentials();
        }
        # if caller is not local subscriber, then check if it calls
        # a local destination, otherwise deny, not an open relay here
        if (from_uri!=myself && uri!=myself)
        {
                sl_send_reply("403","Not relaying");
                exit;
        }

#!endif
        return;
}

# Shared Call Appearances handling
route[SCA] {
#!ifdef WITH_SCA
        if(is_method("SUBSCRIBE")) {
                if ($hdr(Event) == "call-info" || $hdr(Event) == "line-seize") {
                        xdbg("SCA: $hdr(Event) SUBSCRIBE $ru from $si:$sp");
                        sca_handle_subscribe();
                        exit;
                }

                return;
        }

        if (!is_method("BYE|CANCEL|INVITE|PRACK|REFER")) {
                return;
        }

        # this updates appearance state and NOTIFYs SCA subscribers as
        # necessary. it also removes the Call-Info header, if found.
        sca_call_info_update();
#!endif

        return;
}

# Routing to foreign domains
route[SIPOUT] {
        if (!uri==myself)
        {
                append_hf("P-hint: outbound\r\n");
                route(RELAY);
        }
}

# XMLRPC routing
#!ifdef WITH_XMLRPC
route[XMLRPC] {
        # allow XMLRPC from localhost
        if ((method=="POST" || method=="GET")
                        && (src_ip==127.0.0.1)) {
                # close connection only for xmlrpclib user agents (there is a bu
g in
                # xmlrpclib: it waits for EOF before interpreting the response).
                if ($hdr(User-Agent) =~ "xmlrpclib")
                        set_reply_close();
                set_reply_no_connect();
                dispatch_rpc();
                exit;
        }
        send_reply("403", "Forbidden");
        exit;
}
#!endif

# manage incoming replies
onreply_route[MANAGE_REPLY] {
        xdbg("incoming reply\n");

        if (status =~ "[456][0-9][0-9]") {
                # don't update SCA state here, since there may be
                # failure route processing (e.g., call forwarding).
                # update state in failure route instead.
            return;
        }

        route(SCA);
}

# manage failure routing cases
failure_route[MANAGE_FAILURE] {
        if (t_is_canceled()) {
                exit;
        }

        route(SCA);
}
