//Italic Text//
====== AVPops Module Tutorial for Kamailio (OpenSER) ======
Ramona-Elena Modroiu
Bogdan-Andrei Iancu
Daniel-Constantin Mierla
Abstract
Tutorial for AVPOPS module of Kamailio (OpenSER) SIP Server.
===== Overview =====
AVPops (AVP-operations) module implements a set of script functions which allow access and manipulation of user AVPs (preferences). AVP means attribute-value-pair and it could be seen as a variable which can have integer or string ID and integer or string value. In Kamailio (OpenSER), an AVP is linked to SIP request or SIP transaction, if Kamailio (OpenSER) is in stateful mode. The AVP is automatically deallocated when the processing of the SIP request ends.
AVPs introduces in Kamailio (OpenSER) many new opportunities for implementing services and preferences per user or domain. With AVPops module they are usable directly from configuration script, allowing the administrators to customize the VoIP service in a more flexible way. Offering capabilities to load dynamically AVPs from databases, many situations that required to restart Kamailio (OpenSER) due to configuration changes can be avoided.
The AVPops module exports functions for interfacing DB resources (loading/storing/removing AVPs from database), functions for swapping information between AVPs and SIP messages, functions for testing/checking the value of an AVP.
===== Dependencies =====
==== OpenSER Modules ====
The following modules must be loaded before this module:
* Optionally a database module (mysql, dbtext, ...). This is required when you need operations with AVP database table, like avp_db_load(), avp_db_store(), avp_db_delete().
==== External Libraries or Applications ====
The following libraries or applications must be installed before running Kamailio (OpenSER) with this module loaded:
* None
===== AVP's names and aliases =====
The AVP core (provided by Kamailio core) defines two types of AVPs: with numerical ID and with string names. Each of them has its own advantage: the ID AVPs are faster to manipulate internally (no string operation involved); the name AVPs are easier to use (by nature, humans remember easier names than numbers). AVPOPS allows the usage of both types of AVPS, using the following grammar:
avp_name = string | ('I'|'i')':'ID | ('S'|'s')':'string
//Examples//:
* "email_address" - avp with string name "email_address"
* "S:fwd_uri" - avp with string name "fwd_uri"
* "i:456" - avp with numerical ID 456
To combine the benefits from both types of AVPS, AVPOPS module allows you to define aliases for AVP names. So you can use an ID avp - which is faster, but refer it by alias - as easy to remember string. The substitution of aliases with real AVP names is done only once, at configuration script compilation (at startup).
alias_definition = alias'='avp_name
alias = no_space_string
When used, the aliases are invoked with '$' sign in front (to avoid overlapping with predefined strings or AVP's name).
===== AVP database =====
AVPs can be associated to a user or a domain. So, when performing DB operations like load/store/delete, the AVPs can be identified as follows:
* **user's AVPs :**
* by username and domain (in multi-domain setups)
* by username (in single domain setups)
* by uuid (uuid based setups)
* ** domain's AVPs:**
* by domain name
The table format used by AVPOPS module in DB operations is:
* //unique user ID (uuid)// - string and optional - an unique identifier of the user who owns the AVP
* //username// - string and optional - username of the user who owns the AVP
* //domain// - string and optional - domain of the user who owns the AVP or domain whom the AVP belongs to
* //attribute name (AVP name)// - string and mandatory - name of AVP's attribute
* //attribute value (AVP value)// - string and mandatory - value of AVP's attribute
* //AVP type// - the AVP type consists in two binary flags (position 1 and 0): if the AVP name is ID and if the AVP value is integer:
* 0 - AVP with string name and string value
* 1 - AVP with string name and integer value
* 2 - AVP with integer name and string value
* 3 - AVP with integer name and integer value
Of a huge interest, is the posibility of accessing (loading) into AVPs more general data form all kind of sources (tables), sources which does not follow the standart AVPOPS database description. To do so, the AVPOPS module defines the DB schemes. Basically, a DB scheme describes how the AVPOPS should access and extract general data from a non-standart DB table - this description includes table name, column names, column to be used for extracting the value and pre-definition of value type.
In order to be used via DB schemes, the minimum requirement for a DB table is to contain at least an equivalent to UUID column or the equivalent to USERNAME and DOMAIN columns. This is required for making possible the AVP identification - by UUID or by USERNAME and DOMAIN.
A DB scheme contains:
* column name to be used as UUID
* column name to be used as DOMAIN
* column name to be used as USERNAME
* column name to be used as AVP VALUE
* expected TYPE of the AVP value
* table name to be used
===== Exported Parameters =====
==== avp_url (string) ====
For enabling DB operations, you must specify a DB URL pointing to the database containing the table(s) containing the AVPs. If required, a user name and password can be set for allowing the module to connect to the database server.
This parameter is optional, its default value being NULL.
Example 1. Set avp_url parameter
...
modparam("avpops","avp_url","mysql://user:passwd@host/database")
...
==== avp_table (string) ====
Indicates the name of the default table that store the AVPS. This table must be locate into the database specified by "avp_url" parameter.
This parameter is required only if avp_url was set. Note that the default value is NULL, so , if you want to use it, you must explicitly set it.
Example 2. Set avp_table parameter
...
modparam("avpops","avp_table","avptable")
...
==== avp_aliases (string) ====
Contains a multiple definition of aliases for AVP names.
Syntax:
* avp_aliases = alias_definition(';'alias_definition)*
This parameter is optional.
Example 3. Set avp_aliases parameter
...
modparam("avpops","avp_aliases","uuid=I:660;email=s:email_addr;fwd=i:753")
...
==== use_domain (integer) ====
If the domain part of the URI should be used for identifying an AVP in DB operations.
Default value is “0 (no)”.
Example 4. Set use_domain parameter
...
modparam("avpops","use_domain","1")
...
==== uuid_column (string) ====
Name of column containing the uuid (unique user id).
Default value is “uuid”.
Example 5. Set uuid_column parameter
...
modparam("avpops","uuid_column","uuid")
...
==== username_column (string) ====
Name of column containing the username.
Default value is “username”.
Example 6. Set username_column parameter
...
modparam("avpops","username_column","username")
...
==== domain_column (string) ====
Name of column containing the domain name.
Default value is “domain”.
Example 7. Set domain_column parameter
...
modparam("avpops","domain_column","domain")
...
==== attribute_column (string) ====
Name of column containing the attribute name (AVP name).
Default value is “attribute”.
Example 8. Set attribute_column parameter
...
modparam("avpops","attribute_column","attribute")
...
==== value_column (string) ====
Name of column containing the AVP value.
Default value is “value”.
Example 9. Set value_column parameter
...
modparam("avpops","value_column","value")
...
==== type_column (string) ====
Name of column containing the AVP type.
Default value is “type”.
Example 10. Set type_column parameter
...
modparam("avpops","type_column","type")
...
==== db_scheme (string) ====
Definition of a DB scheme. Scheme syntax is:
* db_scheme = name':'element[';'element]*
* element =
* 'uuid_col='string
* 'username_col='string
* 'domain_col='string
* 'value_col='string
* 'value_type='('integer'|'string')
* 'table='string
Default value is “NULL (none)”.
If some elements of a DB scheme are missing, the default values at module level will be used for columns and table names. For "value_type", default is “string”.
Example 11. Set db_scheme parameter
...
modparam("avpops","db_scheme",
"scheme1:uuid_col=uid;value_col=job;value_type=string;table=emp")
- from table "emp", using "uid" column as uuid, get as
string value the "job" column
modparam("avpops","db_scheme",
"scheme2:username_col=user;domain_col=domain;value_col=email;table=users")
- from table "users", using "user" column as username and
"domain" as domain column, get as string value the
"email" column
...
===== Exported Functions =====
==== avp_db_load(source,name) ====
Loads from DB into memory the AVPs corresponding to the given source (from user, to user, ruri user, uuid). Will be loaded all AVPs with name or, if empty, all AVPs. Normally, the function looks into the default table, but there is also the possibility to specify a different table to be used.
If a DB scheme is used, the data specified by the scheme will be loaded into AVP(s) name. In this case, full AVP name is required.
Function returns true only if there was at least one AVP loaded successfully.
**Meaning of the parameters is as follows:**
* **source** - what is used for identifying the AVPs. It can be used the information from a SIP URI or an UUID:
* $ruri - use information from RURI
* $from - use information from FROM header
* $to - use information from TO header
* $avp_alias - use the content of the AVP (defined by alias) as UUID
* str_value - use the string value directly as UUID
The following parameters are available:
* uri - use the value as an URI (extract the username and domain from it)
* uuid - use the value as uuid
* username - use only the username part of the URI
* domain - use only the domain part of the URI
* if no parameter is given, the value is considered to be 'UUID' if it is a constant string or an AVP and 'URI', otherwise.
Parameter syntax:
source = (sip_uri|avp_alias|str_value) ['/'('username'|'domain'|'uri'|'uuid')]
sip_uri = '$from' | '$to' | '$ruri'
* **name** - which AVPs will be loaded from DB into memory. This can be an avp_name or an avp_alias. It can be empty - which means all AVPs, or you may specify only the type - 'i:' means all AVPs with ID. It accepts as parameter the name of a DB table to be used.
Parameter syntax is:
name = (''|'s:'|'i:'|avp_name|avp_alias)['/'db_spec]
db_spec = table_name | '$'db_scheme_name
Example 12. avp_db_load usage
...
avp_db_load("$from","i:678");
- loads all AVPs with ID 678 for username@domain
from FROM header
avp_db_load("$ruri/domain","i:/domain_preferences");
- loads all AVPs with ID from 'domain_preferences'
table for domain from RURI
avp_db_load("$uuid","s:404fwd/fwd_table");
- loads from table 'fwd_table' all AVPs with NAME '404fwd'
and UUID given by the value of AVP defined by alias 'uuid'
avp_db_load("$to","$email");
- loads all AVPs defined by alias 'email' for
username@domain from TO header
avp_db_load("keys","s:my_keys");
- loads all AVPs with NAME 'my_keys' using
as UUID the string 'keys'
modparam("avpops","db_scheme",
"scheme1:uuid_col=uid;value_col=job;value_type=string;table=emp")
avp_db_load("$uuid","s:jobs/$scheme1");
- load from table 'emp', using 'uid' column as uuid and
'job' column as string value into AVP name 'jobs'
avp_db_load("sip:test@openser.org/uri","i:678");
- loads all AVPs with ID 678 for user 'test@openser.org'
...
==== avp_db_store(source,name) ====
Stores to DB the AVPs corresponding to the given source (from user, to user, ruri user, uuid). Will be stored all AVPs with name or, if empty, all AVPs. Normally, the function uses the default table, but there is also the possibility to specify a different table to be used.
The AVPs which were loaded from DB or which were already stored in a previous operation will not be stored into DB!!
Function returns true only if there was at least one AVP stored successfully.
The meaning and usage of the parameters are identical as for avp_db_load(source,name) function. Please refer to its description.
NOTE: function does not support DB schemes.
Example 13. avp_db_store usage
...
avp_db_store("$to","i:678");
- all AVPs with ID 678 will be stored with username@domain
from TO header
avp_db_store("$ruri/username","$email");
- all AVPs defined by alias 'email' will be stored with
username from RURI
avp_db_store("$uuid","s:/fwd_table");
- all AVPs with NAME will be stored into
table 'fwd_table' with the UUID given by the value
of AVP defined by alias 'uuid'
avp_db_store("keys","s:my_keys");
- stores all AVPs with NAME 'my_keys' using
as uuid the string 'keys'
...
==== avp_db_delete(source,name) ====
Deletes from DB the AVPs corresponding to the given source (from user, to user, ruri user, uuid). Will be deleted all AVPs with name or, if empty, all AVPs. Normally, the function uses the default table, but there is also the possibility to specify a different table to be used.
This function has no effect on the AVPs from memory!!
Function returns true if the delete operation was successfully.
The meaning and usage of the parameters are identical as for avp_db_load(source,name) function. Please refer to its description.
NOTE: function does not support DB schemes.
Example 14. avp_db_delete usage
...
avp_db_delete("$to","i:678");
- deletes all AVPs with ID 678 using username@domain
from TO header
avp_db_delete("$ruri/username","$email");
- deletes all AVPs defined by alias 'email' using
username from RURI
avp_db_delete("$uuid","s:404fwd/fwd_table");
- deletes from table 'fwd_table' all AVPs with
NAME '404fwd' using the UUID given by the value
of AVP defined by alias 'uuid'
avp_db_delete("keys","s:my_keys");
- deletes all AVPs with NAME 'my_keys' using
as UUID the string 'keys'
...
==== avp_write(value,name) ====
The function writes some value (given) or some information from the SIP message into a new AVP. The information is specified by source and the AVP name by name (it cannot be empty or generic).
Function returns true if the value was written into AVP successfully.
**
Meaning of the parameters is as follows:**
* **value** - the value to be written into the AVP. It can be a fixed value given in script or some value extracted from the SIP message:
* $ruri- write RURI
* $from - write FROM uri
* $ruri - write TO uri
* $duri - write destination URI (dst_uri)
* $src_ip - write source IP
* $hdr[hdr_name] - write the body of first header with name hdr_name
* value - write the value
If a SIP URI is used, other than '$duri', the following parameters are available:
* username - use only the username part of the URI
* domain - use only the domain part of the URI
* if no parameter is given, both the username and domain part of the URI will be used
Parameter syntax:
value = (variable) | (fix_value)
variable = '$src_ip' | '$duri' | '$hdr[hdr_name]' | (sip_uri)['/'('username'|'domain')]
sip_uri = '$from' | '$to' | '$ruri'
fix_value = 'i:'integer | 's:'string | string
* **name** - the name of the new written AVP. This can be an avp_name or an avp_alias. It cannot be empty. Parameter syntax is:
* name = avp_name | avp_alias
Example 15. avp_write usage
...
avp_write("$to","i:678");
- writes the whole TO uri as AVP ID 678
avp_write("$ruri/username","$email");
- writes the RURI username into AVP defined
by alias 'email'
avp_write("foobar","s:test");
- writes the 'foobar' string into AVP
NAME 'test'
avp_write("i:333","i:6");
- writes the integer 333 into AVP ID 6
avp_write("$src_ip","ip");
- writes the source IP into AVP name 'ip'
avp_write("$hdr[My-header]","i:11");
- writes the body of the first header
named 'My-header' into AVP id '11'
...
==== avp_delete(name) ====
Deletes from memory the AVP(s) with name or, if empty, all AVPs.
Function returns true if at least one AVP was deleted successfully.
**Meaning of the parameters is as follows:**
* **name** - which AVPs will be deleted from memory. This can be an avp_name or an avp_alias. It can be empty - which means all AVPs, or you may specify only the type - 'i:' means all AVPs with ID.
Parameter syntax is:
name = (''|'s:'|'i:'|avp_name|avp_alias)['/'flag]
flag = 'g' | 'G'
Without g global flag, if a full AVP name is given, only the first matching AVP will be deleted. The global flag can force the deletion of all AVP matching the given name.
Example 16. avp_delete usage
...
avp_delete("i:678");
- deletes the first AVPs with ID 678
avp_delete("$email/g");
- deletes all AVPs defined by alias 'email'
avp_delete("i:");
- deletes all AVPs with ID
avp_write("");
- deletes all AVPs
...
==== avp_pushto(destination, name) ====
Pushes the value of the (all) AVP(s) with name into the SIP message as RURI or header (destination).
Function returns true if at least one AVP was pushed successfully into the SIP message.
**Meaning of the parameters is as follows:**
* **destination** - as what will be the AVP value pushed into SIP message. It can be:
* $ruri - push the AVP value as RURI; it accepts as parameter what part of RURI to be written:
* username
* domain
By default the whole RURI will be replaced.
* $duri - push the AVP value as destination URI (dst_uri)
* $hdr_name - push the AVP value as header with name 'hdr_name'; it accepts as parameter the header belonging:
* request
* reply
By default the header is consider belonging to request.
Parameter syntax:
destination = ruri_dst | '$duri' | hdr_dst
ruri_dst = '$ruri'['/'('username'|'domain')]
hdr_dst = '$hdr_name'['/'('request'|'reply')]
* **name** - which AVP(s) should be pushed into the SIP message. This can be an avp_name or an avp_alias. It cannot be empty. It accepts the global flag (g)- if all the AVPs should be used or only the first one.
Parameter syntax is:
name = ( avp_name | avp_alias )['/'flags]
flags = 'g'
Example 17. avp_pushto usage
...
avp_pushto("$ruri","i:678");
- first AVP with ID 678 will be pushed to RURI
avp_pushto("$ruri/domain","s:backup_domains/g");
- all AVPs with NAME 'backup_domains' will be
pushed into domain RURI (request will fork)
avp_pushto("$Email/reply","s:email");
- first AVP with NAME 'email' will be pushed
as header into reply
avp_pushto("$Foo","$bar/g");
- all AVPs defined by alias 'bar' will be
pushed as header into request
avp_pushto("$duri","i:670");
- first AVP with ID 670 will be pushed to dst_uri
...
==== avp_check(name,op_value) ====
Checks the value of the (all) AVP(s) with name against the operator and value (op_value).
If the two involved values have different types (string/integer), the check will be considered failed and skipped.
Function returns true if at least one AVP satisfies successfully the check.
**Meaning of the parameters is as follows:**
* **name** - which AVP(s) should be checked. This can be an avp_name or an avp_alias. It cannot be empty. Parameter syntax is:
* name = ( avp_name | avp_alias )
* **op_value** - defines the check (operator) to be used, the value against which the check should be performed and some additional flags.
Check operators can be:
* eq - AVP value equal to value
* ne - AVP value not equal to value
* lt - AVP value less than value
* le - AVP value less than or equal to value
* gt - AVP value greater than value
* ge - AVP value greater than or equal to value
* re - AVP value match the regular expression given by the value.
* fm - AVP value match (using filename like expression) the regular expression given as value or indirect via an AVP value.
* and - bitwise AND test (only for AVPs with integer value)
* or - bitwise OR test (only for AVPs with integer value)
* xor - bitwise XOR test (only for AVPs with integer value)
The value can be:
* $ruri - check against RURI
* $from - check against FROM URI
* $to - check against TO URI
* $src_ip - check against source IP
* $avp_alias - check against the value of another AVP defined by its alias (the values of all these AVPs will be checked)
* value - check against a fixed, given value
Integer values can be given in hexadecimal using notation: 'i:0xhex_number' (e.g.,: 'i:0xabcd');
Possible flags are:
* g/G - use all values of the checked AVP (first parameter)
* i/I - if strings are checked, do it case insensitive
Parameter syntax is:
* op_value = operator '/' value ['/'flags]
* operator = 'eq' | 'ne' | 'lt' | 'le' | 'gt' | 'ge' | 're' | 'fm' | 'and' | 'or' | 'xor'
* value = variable | fix_value
* variable = '$src_ip'|'$from'|'$ruri'|'$from'|avp_alias
* fix_value = 'i:'integer | 's:'string | string
* flags = 'g' | 'G' | 'i' | 'I'
NOTE: for //re// operator, the value can be only a string fixed value which defines the regular expression.
NOTE: for //fm// operator, the value can be only a string fixed value which defines the regular expression or an AVP ALIAS which containes the regular expression.
Example 18. avp_check usage
...
avp_check("i:678", "lt/i:345/g");
- check values of all AVP with ID 678 if
less than integer 345.
avp_check("s:person","eq/$from/I");
- check case insensitive if AVP NAME 'person'
equals to FROM URI
avp_check("s:foo","gt/$bar/g");
- checks if one of the values of the AVP
NAME "foo" is greater than the one of values
of AVP defined by alias 'bar'
avp_check("s:friends","re/sip:.*@bar.com/g");
- checks if one of the values of the AVP
NAME "friends" matches the regular
expression "sip:.*@bar.com"
avp_write("*@siphub.net","$fm_avp");
avp_write("$from","$value");
avp_check("$value","fm/$fm_avp/g");
- checks if one of the values of the AVP
NAME "value" matches the regular
expression "*@siphub.net" (checks if
the FROM URI belongs to "siphub.net"
domain)
NOTE: the regular expresion can be
also loaded from DB
avp_check("i:678", "lt/i:345/g");
- check values of all AVP with ID 678 if
less than integer 345
avp_check("i:678", "and/i:0x02");
- check if the value of AVP i:678 has the second
bit set
...
==== avp_copy(old_name,new_name) ====
Copies / moves (all) AVP(s) with old_name under a different name (new_name), keeping the original value.
Both names can be AVP aliases and there is no restriction about their types (AVP ID or AVP NAME); any combination is allowed.
Function returns true if at least one AVP was successfully copied or moved.
**Meaning of the parameters is as follows:**
* **old_name** - which AVP(s) should be copied/moved. This can be an avp_name or an avp_alias. It cannot be empty. Parameter syntax is:
* name = ( avp_name | avp_alias )
* **new_name** - the new name of the copied/moved AVP(s). This can be an avp_name or an avp_alias. It cannot be empty. Parameter syntax is:
name = ( avp_name | avp_alias ) ['/'flags]
flags = 'g' | 'G' | 'd' | 'D' | 'n' | 'N' | 's' | 'S'
Meaning of the flags:
* 'g' | 'G' - copy/move all AVPs with 'old_name'
* 'd' | 'D' - delete old AVP after copy (results in move operation)
* 'n' | 'N' - convert the value to number (integer) (cast operation)
* 's' | 'S' - convert the value to string (cast operation)
Example 19. avp_copy usage
...
avp_copy("i:678", "s:test/g");
- copy all AVPs with ID 678 as
AVPs with NAME "test" (duplication).
avp_copy("$old", "$new/gd");
- copy all AVPs defined by alias
"old" as AVPs defined by alias "new"
and delete the original AVPs
(rename).
...
==== avp_op(name,op_value) ====
The function executes integer operations with AVPs.
**Meaning of the parameters is as follows:**
* **name** - 'source_avp/destination_avp' - which AVP(s) should be processed and where to store the result. If 'destination_avp' is missing, same name as 'source_avp' is used to store the result.
Parameter syntax is:
name = ( source_avp[/destination_avp] )
source_avp = ( avp_name | avp_alias )
destination_avp = ( avp_name | avp_alias )
* **op_value** - define the operation, the value and flags.
Parameter syntax is:
op_value = operator '/' value ['/'flags]
operator = 'add' | 'sub' | 'mul' | 'div' | 'mod' | 'and' | 'or' | 'xor' | 'not'
Meaning of the operators:
add - arithmetic addition (in C: '+')
sub - arithmetic substitution (in C: '-')
mul - arithmetic multiplication (in C: '*')
div - arithmetic division (in C: '/')
mod - arithmetic modulo (in C: '%')
and - bitwise AND (in C: '&')
or - bitwise OR (in C: '|')
xor - bitwise XOR (in C: '^')
not - bitwise negation (in C: '~')
value = variable | fix_value
variable = avp_alias
fix_value = 'i:'integer
flags = 'g' | 'G' | 'd' | 'D'
Meaning of the flags:
* 'g' | 'G' - apply the operations to all 'source_avp'
* 'd' | 'D' - delete the 'source_avp' after operation.
Integer values can be given in hexadecimal using notation 'i:0xhex_number' (e.g.,: 'i:0xabcd');
Example 20. avp_op usage
...
modparam("avpops", "avp_aliases", "number=i:30;number2=i:40")
...
avp_write("i:20","$number");
avp_write("i:5", "$number2");
avp_op("i:30", "add/i:5/g");
avp_op("$number","sub/$number2/d");
avp_op("$number2","or/i:0x02/d");
...
==== avp_printf(dest, format) ====
Prints the formatted string 'format' in the AVP 'dest'. The 'format' parameter can include any pseudo-variable defined in Kamailio (OpenSER). The list with all pseudo-variables in Kamailio (OpenSER) can be found at: http://kamailio.org/docs/pseudo-variables.html.
The function can be used for string concatenation between AVPs, pseudo-variables and constants. Functionalities like prefix or suffix are covered, as well.
**Meaning of the parameters is as follows:**
* **dest** - in which AVP should be stored the result. Parameter syntax is:
* name = ( avp_name | avp_alias )
* **format** - the formatted string to be printed in 'dest' AVP. It can include any pseudo-variable as specifier to be replaced.
Example 21. avp_printf usage
...
avp_printf("i:20", "This is a $rm request with the call-id $hdr(call-id)");
...
==== avp_subst(avps, subst) ====
Perl/sed-like substitutions applied to AVPs having string value.
**Meaning of the parameters is as follows:**
* **avps** - source AVP, destination AVP and flags. Parameter syntax is:
* avps = src_avp [ '/' dst_avp [ '/' flags ] ]
* src_avp = ( avp_name | avp_alias )
* dst_avp = ( avp_name | avp_alias ) - if dst_avp is missing then the value of src_avp will be replaced
* flags = ( d | D | g | G )
Meaning of the flags:
'g' | 'G' - apply the operations to all 'source_avp'
'd' | 'D' - delete the 'source_avp' after operation.
* **subst** - perl/sed-like reqular expression.
Parameter syntax is:
* subst = "/regexp/replacement/flags"
* regexp - regular expression
* replacement - replacement string, can include pseudo-variables and \1, ..., \9 for matching tokens,
\0 for whole matching text
* flags = 'g' | 'G' | 'i' | 'i'
Meaning of the flags:
* 'g' | 'G' - replace all matching tokens
* 'i' | 'I' - match ignore-case.
Example 22. avp_subst usage
...
# if avp i:678 has a string value in e-mail format, replace the
# domain part with the value of domain part from R-URI
avp_subst("i:678", "/(.*)@(.*)/\1@$rd/");
# if any avp i:678 has a string value in e-mail format, replace the
# domain part with the value of domain part from R-URI
# and place the result in avp i:679
avp_subst("i:678/i:679/g", "/(.*)@(.*)/\1@$rd/");
...
IMPORTANT NOTE: if the replacement string includes src_avp or dst_avp you will get something that you may not expect. In case you have many src_avp and you make the substitution to be applied to all of them, after the first src_avp is processed, it will be added in avp list and next processing will use it.
==== is_avp_set(name) ====
Check if any AVP identified by name is set. Flags can be used to test if the AVP has number or string value, as well.
**Meaning of the parameters is as follows:**
* **name** - name of AVP to look for. Parameter syntax is:
* name = ('s:'|'i:'|avp_name|avp_alias [ '/' flags ])
* flags = ('s'|'S'|'n'|'N') .
Meaning of the flags:
* 'n' | 'N' - return true only when the AVP 'name' exists and has number (integer) value.
* 's' | 'S' - return true only when the AVP 'name' exists and has string value.
If no flag is specified, the function returns true if any AVP with name 'name' exists.
Example 23. is_avp_set usage
...
if(is_avp_set("i:678/n"))
log("AVP with integer id 678 and having integer value exists\n");
if(is_avp_set("$email"))
log("AVP having the alias $email exists\n");
...
==== avp_print() ====
Prints the list with all the AVPs from memory. This is only a helper/debug function.
Example 24. avp_print usage
...
avp_print();
...
===== Installation =====
The AVPOPS module requires one more column in the usr_preferences table than how it is created now by ser_mysql script. The missing column is "type". Till the discrepancy is fixed, please add by hand the column "type" integer, not null, default 0.
For MySQL databases, if you want to create the table from scratch, you can use the next SQL script with a MySQL client.
Example 25. usr_preferences - table structure
...
DROP TABLE IF EXISTS usr_preferences;
CREATE TABLE usr_preferences (
uuid varchar(64) NOT NULL default '',
username varchar(100) NOT NULL default '',
domain varchar(128) NOT NULL default '',
attribute varchar(32) NOT NULL default '',
value varchar(128) NOT NULL default '',
type integer NOT NULL default '0',
modified timestamp(14) NOT NULL,
PRIMARY KEY (username, domain, attribute, value)
) TYPE=MyISAM;
...
To add the “type” column, you can use the next SQL script.
Example 26. usr_preferences - adding 'type' column
...
ALTER TABLE usr_preferences ADD COLUMN type integer NOT NULL default 0;
...
===== Usage Examples =====
A few examples of how to use the avpops module are listed below.
==== Trusting source IPs ====
Trusting different source IPs for each local domain (in multi or single domain scenarios).
In all Kamailio (OpenSER) based platforms, there are some external components which are considered trusted and no authentication is required for them - as Gateways, Media Servers, B2Bua, etc. In a multi-domain setup, for each domain will be a different set of components (IPs) which must be trusted.
Even in a single-domain configuration, hardwiring these IPs in config file will require restarting Kamailio (OpenSER) at each modification (adding/removing IPs). To implement this, you can define as AVPs belonging to a domain, all the IPs considered as trusted.
Let's consider AVP name 't_ips' of type 0 (string name and string value), stored in 'ips' table:
...
uuid username domain attribute value type
"" "" "domain1" "t_ips" "10.10.0.5" 0
"" "" "domain1" "t_ips" "10.10.0.7" 0
"" "" "domain2" "t_ips" "10.10.0.5" 0
...
So, domain 'domain1' will trust IPs 10.10.0.5 and 10.10.0.7, but domain 'domain2' will trust only 10.10.0.5.
The script will look like:
Example 27. Trusted IPs example
...
# if the request pretends to belong to a local domain
if (is_from_local())
{
# authenticate only INVITE and MESSAGES
if (method=="INVITE" || method=="MESSAGE")
{
# is it a trusted IP address? - first load the trusted IPs (avp
# NAME 't_ips" from DB table "ips") for the target domain (domain part
# of RURI); then check if at least one value of 't_ips' AVPs equals
# the source IP of the request
if (!(avp_db_load("$ruri/domain","s:t_ips/ips")
&& avp_check("s:t_ips", "eq/$src_ip/gi")))
{
#do proxy authentication
...........
}
}
}
...
==== Restricting access to user conference room ====
A user can own one or more conference rooms. He can allow access to each of these rooms only to some desired people (depending from room to room). For each conference room, there will be defined by AVPs the users who can access it.
The AVPs will belong to the conference room and the value will be the allowed users. Let's say, for '001' conference room, on 'domain1' domain, we define the AVPs ID 123, type 2 (ID name, string value - ID AVPs are faster):
...
uuid username domain attribute value type
"" "001" "domain1" "123" "user1@domain1" 2
"" "001" "domain1" "123" "user2@domain1" 2
"" "001" "domain1" "123" "userx@domain2" 2
...
This scenario works in single-domain, but also in multi-domain scenarios, by using domain along with username.
The script will look like:
Example 28. Restricting access example
...
#define 'conf_allowed' alias for AVP ID 123
modparam("avpops","avp_aliases","conf_alowed=i:123")
modparam("avpops","use_domain",1) # make sens for multi-domain setups
.....
.....
# is it for conference service? (prefix 444)
if (uri=~"sip:444.*@")
{
strip(3);
# we have in username only the conference room number - load all users
# that are allowed to access (if any) from "conf" db table and check
# if the caller (from user) is among these users
if ( avp_db_load("$ruri", "$conf_allowed/conf")
&& avp_check("$conf_allowed", "eq/$from/gi") )
{
# user allowed to access conf room
..........
break;
} else {
sl_send_reply("403", "Forbidden - no access to conf");
break;
}
}
...
==== Use of canonical RURI ====
The canonical format of a URI (username@domain) is obtained after all the possible transformations where applied to the received RURI: enum query, aliases, speed-dial, prefixes, etc. As all the checks and service access rely on this canonical URI, it's imperative to have it saved until the request processing is over - a request can be processed in multiple steps, if a forward on failure mechanism is used.
Obviously, any other useful information can be stored to be used later, in a next processing step. Also, the usage of canonical URI is very useful in accounting, since no other transformation is needed when accounting data are processed.
Let's store the canonical URI into memory as AVP ID 34.
Example 29. Canonical URI example
...
modparam("avpops","avp_aliases","can_uri=i:34")
.....
route
{
.....
#apply all transformations for RURIs targeting your domain
.....
#save the canonical URI
avp_write("$ruri","$can_uri");
....
t_on_failure("x");
....
}
failure_route[x]
{
# set back the canonical URI
avp_pushto("$ruri", "$can_uri");
# resume processing
.....
}
...
==== Serial forking ====
The greatest obstacle in implementing serial forking was storing the next addresses (as many as they are) for later usage and retrieving one by one in the next cycles (forward/failure cycles).
By storing all the next addresses as AVPs, it will be possible to use them in failure routes as next forward target. There are no limitations; the AVP list (with all addresses to be used in serial forking) can be defined before starting the forking process and also can be modified (by removing or adding new AVP) during the forking process.
Let's use the AVP ID 665 for keeping all addresses for serial forking. The script will look like:
Example 30. Serial forking example
...
modparam("avpops","avp_aliases","serial_fork=i:665")
.....
route
{
.....
# build the initial set of addresses for serial forking
# either loading them from DB (let's say table fork)
avp_db_load("$ruri", "$serial_fork/fork")
# either by writing them
avp_write("sip:addr1@domain1", "$serial_fork");
avp_write("sip:addr2@domain1", "$serial_fork");
# either by calling some hypothetical module function which generates the
# AVP list
set_fork_list();
......
# intercept failure replies
t_on_failure("x");
t_relay();
}
failure_route[x]
{
# use the first element of the list (if any) and delete it from list
if (avp_pushto("$ruri", "$serial_fork"))
{
append_branch();
avp_delete("$serial_fork");
t_on_failure("x");
t_relay();
}
}
...
Note: since internally the AVP list is kept backwards (a new AVP is inserted at the begin), the serial forking list should be built in opposite order.
==== Origin and destination checks ====
This is a trivial example that shows how the capabilities of AVPOPS module can be used for a wide range of checking using elements from AVPs and parts of SIP messages - from URI, To uri, Request URI, source IP, etc.
The example shows how to check if a request is to be sent to the same address as the one it was received from:
Example 31. Origin and destination checks example
...
# get to the final RURI
if (!lookup("location"))
{
sl_send_reply("404","Not found");
break;
}
# get the host part of the final uri (IP part) and store it in AVP ID 13
avp_write("$ruri/domain", "i:13");
if (avp_check("i:13","eq/$src_ip/i"))
{
# source IP is the same as destination IP
...........
}
avp_delete("i:13/g");
...
==== Keeping ACL per user as bitmap ====
This example shows how to use avpops module to store bitmap ACL (access control list) per user to control the calls to different destinations. The big advantage for this approach is the speed of processing (only one DB query and bitwise operations).
This approach is a replacement of 'group' usage which requires a DB query for each group membership checking.
Example 32. ACL per user as bitmap example
...
mpath="/usr/local/lib/kamailio/modules"
loadmodule "sl.so"
loadmodule "textops.so"
loadmodule "tm.so"
loadmodule "rr.so"
loadmodule "maxfwd.so"
loadmodule "db_mysql.so"
loadmodule "avpops.so"
# ----------------- setting module-specific parameters ---------------
--- avpops params ---
modparam("avpops", "avp_url","mysql://openser:openserrw@localhost/openser")
modparam("avpops","db_scheme",
"scheme0:username_col=username;domain_col=domain;value_col=acl;value_type=integer;table=subscriber")
modparam("avpops","avp_aliases","acl=i:800")
# ------------------------- request routing logic -------------------
# main routing logic
route{
# filter too old messages
if (!mf_process_maxfwd_header("10")) {
log("LOG: Too many hops\n");
sl_send_reply("483","Too Many Hops");
return;
};
if (len_gt( max_len )) {
sl_send_reply("513", "Wow -- Message too large");
return;
};
if (loose_route()) { t_relay(); return; };
if (method=="INVITE") {
record_route();
} else {
t_relay();
return;
};
if(!uri=~"sip:[0-9]+@") {
sl_send_reply("403", "Forbidden - Bad dialed number");
return;
};
if(!avp_db_load("$from","$acl/$scheme0")) {
sl_send_reply("403", "Forbidden - No ACL");
return;
};
# ACL bitmap
#
# 1 - free destination
# 2 - local call
# 3 - long distance call
# 4 - international call
#
if(uri=~"sip:0900[0-9]+") /* free destinations */
{
if (!avp_check("$acl", "and/i:0x01")
{
sl_send_reply("403","Forbidden - Free Destinations Not Allowed");
return;
};
} else if(uri=~"sip:0[1-9][0-9]+") {
if (!avp_check("$acl", "and/i:0x04")
{
sl_send_reply("403","Forbidden - Long Distance Calls Not Allowed");
return;
};
} else if(uri=~"sip:00[1-9][0-9]+") {
if (!avp_check("$acl", "and/i:0x08")
{
sl_send_reply("403","Forbidden - International Calls Not Allowed");
return;
};
} else {
if (!avp_check("$acl", "and/i:0x02")
{
sl_send_reply("403","Forbidden - Local Calls Not Allowed");
return;
};
};
# authorized
# rewritehostport("gateway_ip:gateway_port");
rewritehostport("10.10.10.10:5060");
if (!t_relay()) {
sl_reply_error();
return;
};
}
...