
UID AVP DB Module

Jiri Kuthan

   FhG FOKUS
   <jiri@iptel.org>

   Copyright © 2004, 2005 FhG FOKUS
     _________________________________________________________________

   Table of Contents

   1. Admin Guide

        1. Overview
        2. Dependencies
        3. Parameters

              3.1. db_url (string)
              3.2. user_attrs_table (string)
              3.3. uri_attrs_table (string)
              3.4. uid_column (string)
              3.5. username_column (string)
              3.6. did_column (string)
              3.7. name (string)
              3.8. value_column (string)
              3.9. type_column (string)
              3.10. flags_column (string)
              3.11. scheme_column (string)
              3.12. attr_group (string)
              3.13. auto_unlock_extra_attrs (string)

        4. Functions

              4.1. load_attrs (track, id)
              4.2. load_extra_attrs (group_id, id)
              4.3. save_extra_attrs (group_id, id)
              4.4. remove_extra_attrs (group_id, id)
              4.5. lock_extra_attrs (group_id, id)
              4.6. unlock_extra_attrs (group_id, id)

        5. Example extra attributes usage

   List of Examples

   1.1. attribute group definition

Chapter 1. Admin Guide

   Table of Contents

   1. Overview
   2. Dependencies
   3. Parameters

        3.1. db_url (string)
        3.2. user_attrs_table (string)
        3.3. uri_attrs_table (string)
        3.4. uid_column (string)
        3.5. username_column (string)
        3.6. did_column (string)
        3.7. name (string)
        3.8. value_column (string)
        3.9. type_column (string)
        3.10. flags_column (string)
        3.11. scheme_column (string)
        3.12. attr_group (string)
        3.13. auto_unlock_extra_attrs (string)

   4. Functions

        4.1. load_attrs (track, id)
        4.2. load_extra_attrs (group_id, id)
        4.3. save_extra_attrs (group_id, id)
        4.4. remove_extra_attrs (group_id, id)
        4.5. lock_extra_attrs (group_id, id)
        4.6. unlock_extra_attrs (group_id, id)

   5. Example extra attributes usage

1. Overview

   This  module contains several functions that can be used to manipulate
   the  contents  of AVPs (Attribute-Value pairs). The AVPs are variables
   attached  to  the  SIP  message being processed. Each variable has its
   name and value. AVPs can be used to store arbitrary data or as a means
   of inter-module comminication.

   You  may  also  want to check the avpops module which is more flexible
   and  contains  more  functions.  In future SER releases the avp module
   will be probably deprecated in favor of avpops module.

   Domain  module  operates  in  caching  mode.  Domain  module reads the
   default  values  of  AVPs into cache memory when the module is loaded.
   After  that  default  values  is  re-read  only  when  module is given
   avp_list_reload  fifo  command.  Any  changes in usr_preferences_types
   table  must  thus  be  followed by avp_list_reload command in order to
   reflect them in module behavior.

2. Dependencies

   A database module, such as mysql, postgres, or dbtext.

3. Parameters

   3.1. db_url (string)
   3.2. user_attrs_table (string)
   3.3. uri_attrs_table (string)
   3.4. uid_column (string)
   3.5. username_column (string)
   3.6. did_column (string)
   3.7. name (string)
   3.8. value_column (string)
   3.9. type_column (string)
   3.10. flags_column (string)
   3.11. scheme_column (string)
   3.12. attr_group (string)
   3.13. auto_unlock_extra_attrs (string)

3.1. db_url (string)

   The URL of the database to be used.

   Default value is "mysql://ser:heslo@localhost/ser".

3.2. user_attrs_table (string)

   Name of the table with user attributes.

   Default value is "user_attrs".

3.3. uri_attrs_table (string)

   Name of the table with uri attributes.

   Default value is "uri_attrs".

3.4. uid_column (string)

   Name of the column that stores UID in the user attributes table.

   Default value is "uid".

3.5. username_column (string)

   Name  of  the  column containing the username of the subscriber in uri
   attributes table.

   Default value is "username".

3.6. did_column (string)

   Name of the column in uri attributes table containing the ID of domain
   that the subscriber belongs to.

   Default value is "did".

3.7. name (string)

   The name of the column containing attribute names.

   Default value is "name".

3.8. value_column (string)

   The name of the column containing attribute values.

   Default value is "value".

3.9. type_column (string)

   The name of the column containing attribute value type.

   Default value is "type".

3.10. flags_column (string)

   The name of the column containing attribute flags.

   Default value is "flags".

3.11. scheme_column (string)

   The   name  of  the  column  containing  subscriber's  scheme  in  uri
   attributes.

   Default value is "scheme".

3.12. attr_group (string)

   'Extra  attribute' group definition. It can be repeated to define more
   attribute groups.

   The  group  definition  contains  one  or more assignments in the form
   key=value. Possible keys are:

   id
          Attribute group identifier. Must be set.

   table
          Table  name  used  for  storing  attributes from this attribute
          group. Must be set.

   flag
          Attribute flag name used to mark attributes in this group. Must
          be set.

   key_column
          Column name holding key. Default value is "id".

   name_column
          Column  name  used for storing attribute name. Default value is
          "name".

   value_column
          Column  name used for storing attribute value. Default value is
          "value".

   type_column
          Column  name  used for storing attribute type. Default value is
          "type".

   flags_column
          Column  name used for storing attribute flags. Default value is
          "flags".

   None defined by default.

   Example 1.1. attribute group definition
modparam("avp_db", "attr_group", "id=dlg,flag=dialog_flag,table=dlg_attrs,key_c
olumn=dlg_id");

   Table used for these attributes:
mysql> describe dlg_attrs;
+--------+------------------+------+-----+---------+-------+
| Field  | Type             | Null | Key | Default | Extra |
+--------+------------------+------+-----+---------+-------+
| dlg_id | varchar(256)     | NO   | MUL |         |       |
| name   | varchar(32)      | NO   |     |         |       |
| value  | varchar(255)     | YES  |     | NULL    |       |
| type   | int(11)          | NO   |     | 0       |       |
| flags  | int(10) unsigned | NO   |     | 0       |       |
+--------+------------------+------+-----+---------+-------+
5 rows in set (0.00 sec)

   Setting flags from code (all attrs beginning with "dlg_"):
avpflags dialog_flag;
...
route {
        ...
        setavpflag("$f./^dlg_/", "dialog_flag");
        ...
}

3.13. auto_unlock_extra_attrs (string)

   Determines  the  action  when  any  of  the 'extra attributes' lock is
   detected when routing script execution was finished. When the value of
   this  parameter is zero (default) BUG level message is logged, but the
   lock  is  kept, so another process trying to obtain the lock might get
   stuck. If the value is nonzero, DEBUG level message is sent to the log
   and all the locks are released.

   Default value is 0.

4. Functions

   4.1. load_attrs (track, id)
   4.2. load_extra_attrs (group_id, id)
   4.3. save_extra_attrs (group_id, id)
   4.4. remove_extra_attrs (group_id, id)
   4.5. lock_extra_attrs (group_id, id)
   4.6. unlock_extra_attrs (group_id, id)

4.1. load_attrs (track, id)

   Loads attributes from the database.

   track

        $fu
                Load  user  attributes  into from track. In this case the
                second parameter is UID used to search attributes.

        $tu
                Load  user  attributes  into  to  track. In this case the
                second parameter is UID used to search attributes.

        $fr
                Load  uri  attributes  into  from track. In this case the
                second parameter is URI used to search attributes.

        $tr
                Load  uri  attributes  into  to  track.  In this case the
                second parameter is URI used to search attributes.

   id
          Identifier  used  for  searching attributes. When searching for
          user  attributes it is UID, when searchnig uri attributes it is
          URI.

4.2. load_extra_attrs (group_id, id)

   Loads 'extra attributes' stored by previous call to save_extra_attrs.

   group_id
          Identifies   attribute  group,  see  Section 3.12,  "attr_group
          (string)".

   id
          Identifies attributes which should be loaded.

4.3. save_extra_attrs (group_id, id)

   Saves 'extra attributes' flagged by group flag under given id.

   group_id
          Identifies   attribute  group,  see  Section 3.12,  "attr_group
          (string)".

   id
          Identifier stored with flagged attributes.

4.4. remove_extra_attrs (group_id, id)

   Removes all extra attributes with given id.

   group_id
          Identifies   attribute  group,  see  Section 3.12,  "attr_group
          (string)".

   id
          Identifies attributes which should be removed.

4.5. lock_extra_attrs (group_id, id)

   Locks  extra  attributes.  Currently  locks whole attribute group (not
   only id).

   group_id
          Identifies   attribute  group,  see  Section 3.12,  "attr_group
          (string)".

   id
          Identifies attributes which should be locked.

4.6. unlock_extra_attrs (group_id, id)

   Unlocks extra attributes. Currently unlocks whole attribute group (not
   only id).

   group_id
          Identifies   attribute  group,  see  Section 3.12,  "attr_group
          (string)".

   id
          Identifies attributes which should be unlocked.

5. Example extra attributes usage

debug=3
memdbg=5
server_signature=0
sip_warning=0
check_via=yes;
dns=no;
rev_dns=no;

children=4;
tcp_children=4;
tcp_max_connections=2048;
port=5060

loadmodule "/home/kubartv/SER/lib/ser/modules/sl.so";
#loadmodule "/home/kubartv/SER/lib/ser/modules/maxfwd.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/tm.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/rr.so";
#loadmodule "/home/kubartv/SER/lib/ser/modules/xmlrpc.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/mysql.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/domain.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/uri_db.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/avp.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/avp_db.so";

loadmodule "/home/kubartv/SER/lib/ser/modules/usrloc.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/registrar.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/xprint.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/eval.so";

loadmodule "/home/kubartv/SER/lib/ser/modules/gflags.so"

#modparam("maxfwd", "max_limit", 70);

modparam("usrloc", "db_mode", 1);
modparam("usrloc|avp_db", "db_url", "mysql://ser:heslo@127.0.0.1/ser")

modparam("avp_db", "attr_group", "id=dlg,flag=dialog_flag,table=dlg_attrs,key_c
olumn=dlg_id");

modparam("gflags", "load_global_attrs", 1);

avpflags dialog_flag;

route["create_dialog"] {
        # sets attributes needed by dialog (stored when processing reply)
        $dlg_caller = @contact;
        $dlg_caller_cseq = @cseq.num;
        $dlg_status = "new";
        $dlg_init_method = @cseq.method;
        $dir = "caller2callee";

        t_on_reply("dialog_creation_reply");
        return 1;
}

onreply_route["dialog_creation_reply"] {
        xplog("L_ERR", "dialog creation reply (%rs, %@cseq.method) [%@from.tag,
 %@to.tag]\n");

        $res = @msg.response.code;
        xplog("L_ERR", " ... response: %$res\n");
        if ((!@to.tag) || (@to.tag=="")) {
                # don't create dialog from response without to tag
                break;
        }
        if ($res < 101) {
                xplog("L_ERR", " ... I won't create dialog from 100 response.\n
");
                break;
        }

        del_attr("$id"); # xlset_attr works strange when the attribute already
exists
        xlset_attr("$id", "call-id:%@call_id caller_tag:%@from.tag callee_tag:%
@to.tag");
        if ($res > 299) {
                xplog("L_ERR", " ... dialog terminated\n");
                remove_extra_attrs("dlg", "$id");
                break;
        }

        xplog("L_ERR", " ... creating dialog '%$id'\n");
        # generate dialog id

        lock_extra_attrs("dlg", "$id");

        # TODO: try to load the dialog (early dialog may exist)?

        # update dialog data

        if (($dlg_status == "new") && ($res < 200)) {
                # early response may arrive after final one
                $dlg_callee = @contact;
                $dlg_status = "early";
                xplog("L_ERR", " ... creating early dialog\n");
        }
        if ($res >= 200) {
                $dlg_callee = @contact;
                $dlg_status = "confirmed";
                $dlg_confirmed_at = @sys.now.local;
                xplog("L_ERR", " ... confirming dialog\n");
        }

        route("save_dialog");
}

route["save_dialog"] {
        if ($dlg_status == "destroyed") {
                xplog("L_ERR", " ... destroying dialog %$id\n");

                # use this if you want to delete destroyed dialogs:
                # remove_extra_attrs("dlg", "$id");

                # else if you want leave them in DB (with the time of terminati
on)
                $dlg_destroyed_at = @sys.now.local;

                # set flag for attributes with name beggining with dlg_
                setavpflag("$f./^dlg_/", "dialog_flag");
                save_extra_attrs("dlg", "$id");
        }
        else {
                # set flag for attributes with name beggining with dlg_
                setavpflag("$f./^dlg_/", "dialog_flag");

                save_extra_attrs("dlg", "$id");
        }
        unlock_extra_attrs("dlg", "$id");
}

route["load_dialog_data"] {
        lock_extra_attrs("dlg", "$id");

        del_attr("$dlg_init_method"); # used as flag of succesful read of data

        # delete all used dlg attrs (because load_extra_attrs doesn't delete th
em itself before adding)
        del_attr("$dlg_init_method");
        del_attr("$dlg_caller");
        del_attr("$dlg_callee");
        del_attr("$dlg_caller_cseq");
        del_attr("$dlg_callee_cseq");
        del_attr("$dlg_status");

        load_extra_attrs("dlg", "$id");
        if (!$dlg_init_method) {
                # dialog was not loaded
                unlock_extra_attrs("dlg", "$id");
                return -1;
        }
        return 1;
}

route["load_dialog"] {
        # tries to load dialog according tags and callid

        # try to load dialog
        del_attr("$id"); # xlset_attr works strange when the attribute already
exists
        xlset_attr("$id", "call-id:%@call_id caller_tag:%@from.tag callee_tag:%
@to.tag");
        if (route("load_dialog_data")) {
                $dir = "caller2callee";
                return 1;
        }

        # try to load dialog in other direction
        del_attr("$id"); # xlset_attr works strange when the attribute already
exists
        xlset_attr("$id", "call-id:%@call_id caller_tag:%@to.tag callee_tag:%@f
rom.tag");
        if (route("load_dialog_data")) {
                $dir = "callee2caller";
                return 1;
        }

        $id = "error";

        return -1;
}

route["update_dialog_reply"] {
        if ((@cseq.method == "INVITE") || (@cseq.method == "UPDATE")) {
                # target refresh for INVITE dialogs

                if ($dir == "caller2calle") { # if request from caller
                        $dlg_callee = @contact; # update callee's contact (resp
onse!)
                }
                else {
                        $dlg_caller = @contact;
                }
        }
        if (@cseq.method=="BYE") {
                $dlg_status = "destroyed"; # will be removed in save_dialog
        }
}

route["update_dialog"] {
        if ((@cseq.method == "INVITE") || (@cseq.method == "UPDATE")) {
                # target refresh for INVITE dialogs

                if ($dir == "caller2calle") { # if request from caller
                        $dlg_caller = @contact; # update caller's contact (requ
est!)
                }
                else {
                        $dlg_callee = @contact;
                }
        }

        if ($dir == "caller2callee") { # if request from caller
                # TODO: verify CSeq before modifying and return 500 if lower th
an last one
                $dlg_caller_cseq = @cseq.num;
        }
        else {
                # TODO: verify CSeq before modifying and return 500 if lower th
an last one
                $dlg_callee_cseq = @cseq.num;
        }

        if (method=="BYE") {
                $dlg_status = "pre-destroyed"; # to see that BYE already went t
hrough
        }
        return 1;
}

route["trace_dialog"] {
        xplog("L_ERR", " ... dialog '%$id'\n");
        xplog("L_ERR", "       -> initial method: %$dlg_init_method\n");
        xplog("L_ERR", "       -> request dir: %$dir\n");
        xplog("L_ERR", "       -> caller: %$dlg_caller\n");
        xplog("L_ERR", "       -> caller's CSeq: %$dlg_caller_cseq\n");
        xplog("L_ERR", "       -> callee: %$dlg_callee\n");
        xplog("L_ERR", "       -> callee's CSeq: %$dlg_callee_cseq\n");
        xplog("L_ERR", "       -> status: %$dlg_status\n");
        return 1;
}

onreply_route["dialog_reply"] {
        if ($id) {
                xplog("L_ERR", "In-dialog reply (%rs, %@cseq.method) [%@to.tag,
 %@from.tag]\n");
                if (!route("load_dialog")) {
                        xplog("L_ERR", "Can't load dialog data\n");
                }
                else {
                        route("update_dialog_reply");
                        route("trace_dialog");
                        route("save_dialog");
                }
        }
}

route {
        if (method=="SUBSCRIBE") {
                # here we support only INVITE/BYE dialogs
                sl_reply("400", "Unsupported");
                break;
        }

        if (!lookup_domain("$td", "@to.uri.host")) {
                sl_send_reply("404", "Domain Not Found");
                break;
        }

        if (!lookup_user("To")) {
                sl_send_reply("404", "Unknown user");
                break;
        }

        if (method=="REGISTER") {
                save("location");
                break;
        };

        xplog("L_ERR", "----> processing request %@cseq.method\n");

        if (!lookup_domain("$fd", "@from.uri.host")) {
                sl_send_reply("404", "From domain Not Found");
                break;
        }

        if (!lookup_user("From")) {
                sl_send_reply("404", "Unknown user in From");
                break;
        }

        if (method != "REGISTER") record_route();

        # dialog needs transactions
        if (!t_newtran()) {
                sl_send_reply("500", "Can not start transaction");
                drop;
        }

        # dialog creation/loading
        if (!@to.tag || (@to.tag == "")) {
                # initial request or non-dialog message
                if (method=="INVITE") {
                        route("create_dialog");
                        # we don't save the dialog here because AVPs will be se
t
                        # when reply comes and the dialog will be stored then
                }
                else {
                        xplog("L_ERR", "Non-dialog message: %@cseq\n");
                }
        }
        else {
                # message within dialog
                if (route("load_dialog")) {
                        route("update_dialog");
                        route("trace_dialog");
                        route("save_dialog");
                        t_on_reply("dialog_reply");
                }
                else {
                        xplog("L_ERR", "Message within unknown dialog: %@cseq,
to_tag=%@to.tag from_tag=%@from.tag\n");
                }
        }

        if (loose_route()) {
                route(1);
                break;
        }

        if (!lookup("location")) {
                t_reply("404", "Not Found");
                break;
        };
        route(1);
}

route[1]
{
        xplog("L_ERR", "<---- request %@cseq.method processed\n");
        # send it out now; use stateful forwarding as it works reliably
        # even for UDP2TCP
        if (!t_relay()) {
                sl_reply_error();
        };
}
