8. Usage Examples

A few examples of how to use the avpops module are listed below.

8.1. Trusting source IPs

Trusting different source IPs for each local domain (in multi or single domain scenarios).

In all 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 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
			...........
		}
	}
}
...
				

8.2. 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;
	}
}
...
				

8.3. 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
	.....
}

...
				

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

8.5. 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");
...
				

8.6. 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/openser/modules"
loadmodule "sl.so"
loadmodule "textops.so"
loadmodule "tm.so"
loadmodule "rr.so"
loadmodule "maxfwd.so"
loadmodule "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; 
	};

}
...