1. uid_avp_db Module

Jiri Kuthan

   FhG FOKUS
   <jiri@iptel.org>

   Copyright © 2004, 2005 FhG FOKUS
     __________________________________________________________________

   1.1. Overview
   1.2. Dependencies
   1.3. Parameters

        1.3.1. db_url (string)
        1.3.2. user_attrs_table (string)
        1.3.3. uri_attrs_table (string)
        1.3.4. uid_column (string)
        1.3.5. username_column (string)
        1.3.6. did_column (string)
        1.3.7. name (string)
        1.3.8. value_column (string)
        1.3.9. type_column (string)
        1.3.10. flags_column (string)
        1.3.11. scheme_column (string)
        1.3.12. attr_group (string)
        1.3.13. auto_unlock_extra_attrs (string)

   1.4. Functions

        1.4.1. load_attrs (track, id)
        1.4.2. load_extra_attrs (group_id, id)
        1.4.3. save_extra_attrs (group_id, id)
        1.4.4. remove_extra_attrs (group_id, id)
        1.4.5. lock_extra_attrs (group_id, id)
        1.4.6. unlock_extra_attrs (group_id, id)

   1.5. Example extra attributes usage

1.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.

1.2. Dependencies

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

1.3. Parameters

1.3.1. db_url (string)

   The URL of the database to be used.

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

1.3.2. user_attrs_table (string)

   Name of the table with user attributes.

   Default value is "user_attrs".

1.3.3. uri_attrs_table (string)

   Name of the table with uri attributes.

   Default value is "uri_attrs".

1.3.4. uid_column (string)

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

   Default value is "uid".

1.3.5. username_column (string)

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

   Default value is "username".

1.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".

1.3.7. name (string)

   The name of the column containing attribute names.

   Default value is "name".

1.3.8. value_column (string)

   The name of the column containing attribute values.

   Default value is "value".

1.3.9. type_column (string)

   The name of the column containing attribute value type.

   Default value is "type".

1.3.10. flags_column (string)

   The name of the column containing attribute flags.

   Default value is "flags".

1.3.11. scheme_column (string)

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

   Default value is "scheme".

1.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. attribute group definition
modparam("avp_db", "attr_group", "id=dlg,flag=dialog_flag,table=dlg_attrs,key_co
lumn=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");
        ...
}

1.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.

1.4. Functions

1.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.

1.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 1.3.12, "attr_group
          (string)".

   id
          Identifies attributes which should be loaded.

1.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 1.3.12, "attr_group
          (string)".

   id
          Identifier stored with flagged attributes.

1.4.4. remove_extra_attrs (group_id, id)

   Removes all extra attributes with given id.

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

   id
          Identifies attributes which should be removed.

1.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 1.3.12, "attr_group
          (string)".

   id
          Identifies attributes which should be locked.

1.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 1.3.12, "attr_group
          (string)".

   id
          Identifies attributes which should be unlocked.

1.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_co
lumn=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 e
xists
        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 terminatio
n)
                $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 the
m 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 e
xists
        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 e
xists
        xlset_attr("$id", "call-id:%@call_id caller_tag:%@to.tag callee_tag:%@fr
om.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 (respo
nse!)
                }
                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 (reque
st!)
                }
                else {
                        $dlg_callee = @contact;
                }
        }

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

        if (method=="BYE") {
                $dlg_status = "pre-destroyed"; # to see that BYE already went th
rough
        }
        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 set
                        # 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, t
o_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();
        };
}
