1. avp_db Module

Jiri Kuthan

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_column=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/xlog.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_column=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"] {
	xlog("L_ERR", "dialog creation reply (%rs, %@cseq.method) [%@from.tag, %@to.tag]\n");

	$res = @msg.response.code;
	xlog("L_ERR", " ... response: %$res\n");
	if ((!@to.tag) || (@to.tag=="")) {
		# don't create dialog from response without to tag
		break;
	}
	if ($res < 101) {
		xlog("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) {
		xlog("L_ERR", " ... dialog terminated\n");
		remove_extra_attrs("dlg", "$id");
		break;
	}

	xlog("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";
		xlog("L_ERR", " ... creating early dialog\n");
	}
	if ($res >= 200) {
		$dlg_callee = @contact;
		$dlg_status = "confirmed";
		$dlg_confirmed_at = @sys.now.local;
		xlog("L_ERR", " ... confirming dialog\n");
	}

	route("save_dialog");
}

route["save_dialog"] {
	if ($dlg_status == "destroyed") {
		xlog("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 termination)
		$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 them 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:%@from.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 (response!)
		}
		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 (request!)
		}
		else {
			$dlg_callee = @contact;
		}
	}

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

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

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

onreply_route["dialog_reply"] {
	if ($id) {
		xlog("L_ERR", "In-dialog reply (%rs, %@cseq.method) [%@to.tag, %@from.tag]\n");
		if (!route("load_dialog")) {
			xlog("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;
	};
		
	xlog("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 {
			xlog("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 {	
			xlog("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] 
{
	xlog("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();
	};
}