<?xml version="1.0" encoding='ISO-8859-1'?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [

<!-- Include general documentation entities -->
<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
%docentities;

]>
<!-- Module User's Guide -->

<chapter>

	<title>&adminguide;</title>

	<section>
	<title>Overview</title>
	<para>This module provides C-API functions to enable &kamailio; to be
	used as an outbound Edge Proxy (see &rfc5626; section 5).</para>
	<para>The <emphasis>path</emphasis> and <emphasis>rr</emphasis> modules will
	bind to this module if it is loaded before they are.</para>
	<section>
		<title>Edge Proxy Keep-Alives (STUN)</title>
		<para>Outbound Edge Proxies MUST support STUN NAT keep-alives
		on their SIP UDP ports. &kamailio; supports this though the
		<quote>stun</quote> module.</para>
	</section>
	<section>
		<title>Flow Timer</title>
		<para>The maximum interval at which a User Agent must send a
		keep-alive may be specified by the Registrar using the
		Flow-Timer: header in 2xx responses to REGISTERs.</para>
		<para>When using TCP or TLS as the SIP transport care should
		be taken to set the <quote>tcp_connection_lifetime</quote>
		on the Edge Proxy to a value slightly larger than the interval
		the Registrar is using for flow timer. Setting
		<quote>tcp_connection_lifetime</quote> to less than the
		interval could cause connections to be lost, and setting it
		to a value much larger than the interval will keep connections
		open far longer than is required (which is wasteful).</para>
		<para>Application-layer keep-alives are optional when the
		underlying transport already has a keep-alive mechanism. The
		WebSocket transport has a transport-layer keep-alive. When
		using the WebSocket transport the
		<quote>keepalive_timeout</quote> should be set to a value
		a little greater than the Registrar flow timer interval and a
		little less than the <quote>tcp_connection_lifetime</quote>.
		</para>
	</section>
	<example>
	<title>Edge Proxy Configuration</title>
	<programlisting><![CDATA[
#!KAMAILIO
#
# Edge proxy configuration
#

#!subst "/REGISTRAR_IP/192.168.122.3/"
#!subst "/REGISTRAR_PORT/5060/"
#!substdef "/FLOW_TIMER/20/"

####### Global Parameters #########

debug=2
log_stderror=no
log_facility=LOG_LOCAL0
fork=yes
children=4
alias="example.com"
mpath="/usr/lib64/kamailio/modules"
tcp_connection_lifetime=30 # FLOW_TIMER + 10
force_rport=yes


####### Modules Section ########

loadmodule "tm.so"
loadmodule "sl.so"
loadmodule "outbound.so"
loadmodule "rr.so"
loadmodule "path.so"
loadmodule "pv.so"
loadmodule "maxfwd.so"
loadmodule "xlog.so"
loadmodule "sanity.so"
loadmodule "ctl.so"
loadmodule "textops.so"
loadmodule "siputils.so"
loadmodule "stun.so"

# ----------------- setting module-specific parameters ---------------

# ----- tm params -----
modparam("tm", "failure_reply_mode", 3)

# ----- rr params -----
modparam("rr", "append_fromtag", 0)


####### Routing Logic ########

request_route {
	route(REQINIT);

	if (is_method("CANCEL")) {
		if (t_check_trans()) {
			route(RELAY);
		}
		exit;
	}

	route(WITHINDLG);

	t_check_trans();

	if (is_method("REGISTER")) {
		remove_hf("Route");
		add_path();
		$du = "sip:REGISTRAR_IP:REGISTRAR_PORT";
	} else {
		if (is_method("INVITE|SUBSCRIBE"))
			record_route();

		if (@via[2] == "") {
			# From client so route to registrar...

			if ($rU == $null) {
				sl_send_reply("484", "Address Incomplete");
				exit;
			}
			remove_hf("Route");
			$du = "sip:REGISTRAR_IP:REGISTRAR_PORT";
		} else {
			# From registrar so route using "Route:" headers...

			if (!loose_route()) {
				switch($rc) {
				case -2:
					sl_send_reply("403", "Forbidden");
					exit;
				default:
					xlog("L_ERR", "in request_route\n");
					sl_reply_error();
					exit;
				}
			}

			t_on_failure("FAIL_OUTBOUND");
		}
	}

	route(RELAY);
}

route[RELAY] {
	if (!t_relay()) {
		sl_reply_error();
	}
	exit;
}

route[REQINIT] {
	if (!mf_process_maxfwd_header("10")) {
		sl_send_reply("483","Too Many Hops");
		exit;
	}

	if(!sanity_check("1511", "7"))
	{
		xlog("Malformed SIP message from $si:$sp\n");
		exit;
	}
}

route[WITHINDLG] {
	if (has_totag()) {
		if (!loose_route()) {
			switch($rc) {
			case -2:
				sl_send_reply("403", "Forbidden");
				exit;
			default:
				if (is_method("ACK")) {
					if ( t_check_trans() ) {
						route(RELAY);
						exit;
					} else {
						exit;
					}
				}
				sl_send_reply("404","Not Found");
			}
		} else {
			if (is_method("NOTIFY")) {
				record_route();
			}
			route(RELAY);
		}
		exit;
	}
}

onreply_route {
	if (!t_check_trans()) {
		drop;
	}

	if ($rm == "REGISTER" && $rs >= 200 && $rs <= 299) {
		remove_hf("Flow-Timer");
		if ($(hdr(Require)[*])=~"outbound")
			insert_hf("Flow-Timer: FLOW_TIMER\r\n", "Call-ID");
	}
}

failure_route[FAIL_OUTBOUND] {
	if (t_branch_timeout() && !t_branch_replied()) {
		send_reply("430", "Flow Failed");
	}
}
]]></programlisting>
	</example>
	<example>
	<title>Registrar Configuration</title>
	<programlisting><![CDATA[
MAILIO
#
# Registrar configuration
#


####### Global Parameters #########

debug=2
log_stderror=no
log_facility=LOG_LOCAL0
fork=yes
children=4
alias="example.com"
mpath="/usr/lib64/kamailio/modules"


####### Modules Section ########

loadmodule "tm.so"
loadmodule "tmx.so"
loadmodule "sl.so"
loadmodule "rr.so"
loadmodule "pv.so"
loadmodule "maxfwd.so"
loadmodule "xlog.so"
loadmodule "sanity.so"
loadmodule "ctl.so"
loadmodule "textops.so"
loadmodule "siputils.so"
loadmodule "usrloc.so"
loadmodule "registrar.so"

# ----------------- setting module-specific parameters ---------------


# ----- tm params -----
modparam("tm", "failure_reply_mode", 3)
modparam("tm", "restart_fr_on_each_reply", 0)
modparam("tm", "contact_flows_avp", "tm_contact_flows")
modparam("tm", "contacts_avp", "tm_contacts")

# ----- rr params -----
modparam("rr", "append_fromtag", 0)

# ----- registrar params -----
modparam("registrar", "use_path", 1)
modparam("registrar", "gruu_enabled", 1)
modparam("registrar", "outbound_mode", 1)


####### Routing Logic ########

request_route {
	route(REQINIT);

	if (is_method("CANCEL")) {
		if (t_check_trans()) {
			route(RELAY);
		}
		exit;
	}

	route(WITHINDLG);

	t_check_trans();

	remove_hf("Route");
	if (is_method("INVITE|SUBSCRIBE"))
		record_route();

	route(REGISTRAR);

	if ($rU==$null) {
		xlog("L_INFO", "Address Incomplete\n");
		send_reply("484","Address Incomplete");
		exit;
	}

	route(LOCATION);
}


route[RELAY] {
	if (!t_relay()) {
		xlog("L_ERR", "t_relay() failed\n");
		sl_reply_error();
	}
	exit;
}

route[REQINIT] {
	if (!mf_process_maxfwd_header("10")) {
		xlog("L_INFO", "Too Many Hops\n");
		send_reply("483","Too Many Hops");
		exit;
	}

	if(!sanity_check("1511", "7"))
	{
		xlog("Malformed SIP message from $si:$sp\n");
		exit;
	}
}

route[WITHINDLG] {
	if (has_totag()) {
		if (loose_route()) {
			if (is_method("NOTIFY")) {
				record_route();
			}
			route(RELAY);
		} else {
			if (is_method("ACK")) {
				if (t_check_trans()) {
					route(RELAY);
					exit;
				} else {
					exit;
				}
			}
			xlog("L_INFO", "Not Found");
			send_reply("404","Not Found");
		}
		exit;
	}
}

route[REGISTRAR] {
	if (is_method("REGISTER"))
	{
		if (!save("location")) {
			xlog("L_ERR", "Unable to save location\n");
			sl_reply_error();
		}
		exit;
	}
}

route[LOCATION] {
	if (!lookup("location")) {
		$var(rc) = $rc;
		t_newtran();
		switch ($var(rc)) {
			case -1:
			case -3:
				send_reply("404", "Not Found");
				exit;
			case -2:
				send_reply("405", "Method Not Allowed");
				exit;
		}
	}

	if (!t_load_contacts() || !t_next_contacts()) {
		xlog("L_ERR", "t_(load|next)_contacts() failed\n");
		sl_reply_error();
		exit;
	}

	t_on_failure("FAIL_TRANSACTION");
	t_on_branch_failure("FAIL-BRANCH");
	route(RELAY);
	exit;
}

onreply_route {
	if (!t_check_trans()) {
		drop;
	}
}

failure_route[FAIL_TRANSACTION] {
	if (!t_check_status("6[0-9][0-9]")) {
		if (t_next_contacts()) {
			t_relay();
			exit;
		}
	}

	if (t_check_status("430")) {
		t_reply("480", "Temporarily Unavailable");
		exit;
	}
}

event_route[tm:branch-failure:FAIL-BRANCH] {
	if (t_check_status("403|430")
			|| (t_branch_timeout() && !t_branch_replied())) {
		unregister("location", "$tu", "$T_reply_ruid");

		if (t_next_contact_flow()) {
			t_on_branch_failure("FAIL-BRANCH");
			t_relay();
		}
	}
}
]]></programlisting>
	</example>
	</section>

	<section>
	<title>Dependencies</title>
	<section>
		<title>&kamailio; Modules</title>
		<para>
		The following modules must be loaded before this module:
		<itemizedlist>
		<listitem>
		<para><emphasis>None</emphasis></para>
		</listitem>
		</itemizedlist>
		</para>
		<para>
		The following modules are required to make proper use of this
		module:
		<itemizedlist>
		<listitem>
		<para><emphasis>stun</emphasis>.</para>
		</listitem>
		</itemizedlist>
		</para>
	</section>

	<section>
		<title>External Libraries or Applications</title>
		<para>
		The following libraries must be installed before running
		&kamailio; with this module loaded:
		<itemizedlist>
		<listitem>
		<para><emphasis>&openssl;</emphasis>.</para>
		</listitem>
		</itemizedlist>
		</para>
	</section>
	</section>


	<section>
	<title>Parameters</title>
	<section>
		<title><varname>force_outbound_flag</varname> (integer)</title>
		<para>A flag which, if set for a request, will force
		<emphasis>path</emphasis> and <emphasis>rr</emphasis> to add
		flow tokens to Path: and Record-Route: headers regardless of
		the request contents.</para>
		<para><emphasis>Default value is -1.</emphasis></para>
		<example>
		<title>Set <varname>force_outbound_flag</varname> parameter
		</title>
		<programlisting format="linespecific">
...
modparam("outbound", "force_outbound_flag", 1)
...
</programlisting>
		</example>
	</section>

	<section>
		<title><varname>force_no_outbound_flag</varname> (integer)</title>
		<para>A flag which, if set for a request, will force
		<emphasis>path</emphasis> and <emphasis>rr</emphasis> not
		to add flow tokens to Path: and Record-Route: headers
		regardless of the request contents.</para>
		<para><emphasis>Default value is -1.</emphasis></para>
		<example>
		<title>Set <varname>force_no_outbound_flag</varname> parameter
		</title>
		<programlisting format="linespecific">
...
modparam("outbound", "force_no_outbound_flag", 2)
...
</programlisting>
		</example>
	</section>

	<section>
		<title><varname>flow_token_secret</varname> (string)</title>
		<para>
			Secret phrase used to calculate the outbound key value
			used for flow tokens validation.
			Allows to set persistent outbound key.
		</para>
		<para>
			If not specified, <emphasis>outbound</emphasis> will use randomly generated outbound key
		</para>
		<example>
			<title>
				Set <varname>flow_token_secret</varname> parameter
			</title>
			<programlisting format="linespecific">
...
modparam("outbound", "flow_token_secret", "johndoessecretphrase")
...
			</programlisting>
		</example>
	</section>

	</section>

</chapter>
