You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kamailio/modules/nat_traversal/doc/nat_traversal_admin.xml

891 lines
41 KiB

<?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 "../../../docbook/entities.xml">
%docentities;
]>
<!-- Module's Admin Guide -->
<chapter>
<title>&adminguide;</title>
<section>
<title>Overview</title>
<para>
The nat_traversal module provides support for handling far-end NAT
traversal for SIP signaling. The module includes functionality to
detect user agents behind NAT, to modify SIP headers to allow user
agents to work transparently behind NAT and to send keepalive messages
to user agents behind NAT in order to preserve their visibility in the
network. The module can handle user agents behind multiple cascaded
NAT boxes as easily as user agents behind a single level of NAT.
</para>
<para>
The module is designed to work in complex environments where multiple
SIP proxies may be involved in handling registration and routing and
where the incoming and outgoing paths may not necessarily be the same,
or where the routing path may even change between consecutive dialogs.
The nat_traversal functionality is built primarily for IPv4 NAT handling
and hasn't been adapted to support IPv6 session keepalives.
</para>
</section>
<section>
<title>Keepalive functionality</title>
<section>
<title>Overview</title>
<para>
The nat_traversal module implements a very sophisticated keepalive
mechanism, that is able to handle the most complex environments and
use cases, including distributed environments with multiple proxies.
Unlike existing keepalive solutions that only send keepalive messages
to user agents that have registered (during their registration), the
nat_traversal module can keepalive an user agent based on multiple
conditions, making it not only more flexible and more efficient, but
also able to work in environments and with use cases where a simple
keepalive implementation based on keeping alive registrations alone
cannot work.
</para>
<para>
The keepalive mechanism works by sending a SIP request to a user agent
behind NAT to make that user agent send back a reply. The purpose is
to have packets sent from inside the NAT to the proxy often enough to
prevent the NAT box from timing out the connection. Many NAT boxes do
not consider packets that travel from the outside to the inside of the
NAT to reset the connection expiration timer, thus to keepalive a user
agent we need to trigger an answer from it.
</para>
</section>
<section>
<title>Background</title>
<para>
One of the major limitations of an implementation that only sends
keepalive messages to registered user agents, is that it creates an
artificial association between the concept of network visibility with
the concept of user registration. The registration process only creates
network visibility for incoming INVITE requests, in other words for
incoming calls. However, there are other cases where a user agent needs
to preserve its network visibility when behind NAT, that have nothing
to do with receiving incoming calls. One of them is the ability of the
user agent to keep receiving NOTIFY requests for a presence subscription
it has made. Another situation is where the user agent should be able
to receive all messages within a dialog it has initiated, even if it is
not registered. In the first case, a presence agent is required to
register to be able to receive notifications for its subscriptions and
it has to keep the registration active the whole time. In the second
case a user agent that wants to make an outgoing call has to register
and keep the registration active during the call, otherwise it may
not be able to receive future in-dialog messages, including the BYE
that closes the dialog.
</para>
<para>
Not only we have this forced association shown above, that requires a
user agent to register to be able to do anything, but a simple keepalive
implementation based on sending keepalive messages only to registered
user agents, will also fail to work in common cases, exactly because of
this artificial association. For example lets assume that we have an
user agent that is registered. If during an outgoing call initiated by
this user agent, the agent stops registering, then it will not be able
to receive further in-dialog messages after the NAT binding expires.
The same is true for a presence agent, receiving notifications for its
subscriptions.
</para>
<para>
In environments with multiple proxies handling the same domains, the
problem gets even more acute. In this case the incoming and outgoing
paths for a call may be completely different: the user agent may
register using one proxy as an entry point to the network, but may
make an outgoing call using a different proxy as the network entry
point. Even more a registration may use a different proxy as the entry
point to the network with each renewal of the registration, making it
volatile and unreliable for anything else except incoming calls.
A keepalive implementation that only sends keepalive messages to
registered user agents will not be able to guarantee the delivery of
in-dialog messages for outgoing calls even if it requires the user
agent to register before making a call. In this case, even if we
assume that the user agent would pick the same proxy for an outgoing
call as the one it has used for the last registration, at the next
registration it may pick another one (as returned by DNS), and will
dissociate the incoming and outgoing paths rendering the outgoing
path unusable (assuming the outgoing call takes longer than the
registration period).
</para>
<para>
All this leads to the conclusion that a keepalive implementation based
solely on sending keepalive messages to registered user agents can only
work in single proxy environments and then only work reliably if it
requires the user agent to register before doing anything else, even
though some actions would not require a user agent to register.
</para>
</section>
<section>
<title>Implementation</title>
<para>
To avoid the above mentioned issues, this implementation introduces
the concept of network visibility for a given condition. This way we
can keepalive a user agent for multiple independent conditions, thus
avoiding all the problems presented above.
</para>
<para>
The conditions for which the module will send keepalive messages are:
<itemizedlist>
<listitem>
<para>
<emphasis>Registration</emphasis> - for user agents that have
registered to preserve their visibility for incoming calls. This
is the result of triggering keepalive for a REGISTER request.
</para>
</listitem>
<listitem>
<para>
<emphasis>Subscription</emphasis> - for presence agents that
have subscribed to some events to preserve their visibility for
receiving back notifications. This is the result of triggering
keepalive for a SUBSCRIBE request.
</para>
</listitem>
<listitem>
<para>
<emphasis>Dialogs</emphasis> - for user agents that have
initiated an outgoing call to preserve their visibility for
receiving further in-dialog messages. This is the result of
triggering keepalive for an outgoing INVITE request.
</para>
</listitem>
</itemizedlist>
</para>
<para>
A user agent's NAT entry point may be kept alive for one or multiple
of the conditions listed above. Even when a NAT endpoint is kept alive
for more than one condition, only one keepalive message is sent to
that NAT endpoint. The presence of multiple conditions for a NAT
endpoint, only guarantees that the network visibility for a user agent
based on a certain condition will be available while that condition is
true, independently of the other conditions. When all the conditions
to keepalive a NAT endpoint will disappear, that endpoint will be
removed from the list with the NAT endpoints that need to be kept
alive.
</para>
<para>
The user interface for the keepalive functionality is very simple. It
consists of a single function called nat_keepalive() that needs to be
called only once for the requests that trigger the need for network
visibility. These requests are: REGISTER, SUBSCRIBE and outgoing
INVITEs. After such a request arrives it makes the user agent visible
for the purpose of receiving back other messages. Thus, after a
REGISTER the user agent may receive back incoming calls, after a
SUBSCRIBE it may receive back notifications and after an outgoing
INVITE it may receive back further in-dialog messages including the
BYE that ends the dialog. The nat_keepalive() function needs to be
called on the proxy that directly receives the request from the user
agent, if it determines that the user agent making the request is
behind NAT. The function needs to be called before the request gets
either a stateless reply or it is relayed with t_relay(). Calling the
nat_keepalive() function has no effect if the request gets no stateless
reply or it is not relayed.
</para>
<para>
For environments with multiple proxies, where the proxy that acts as
an entry point to the network for a given request is not the one that
actually handles the request, then the nat_keepalive() function needs
to be called on the proxy that is the entry point and after that the
request must be sent to the proxy that actually handles the request
using t_relay(). This is needed because the keepalive functionality
detects from the stateless replies or the TM relayed replies if the
NAT endpoint needs to be kept alive for the condition triggered by
the request for which the nat_keepalive() function was called.
For example assume a network where a proxy P1 receives a REGISTER
from an user agent behind NAT. P1 will determine that the user agent
is behind NAT so it needs keepalive functionality, but another proxy
called P2 is actually handling the subscriber registrations. In this
case P1 has to call nat_keepalive() even though it doesn't yet know
the answer P2 will give to the REGISTER request (which may even be a
negative reply) or if P2 will restrict the proposed expiration time
in any way. Thus P1 calls nat_keepalive() after which it calls
t_relay(). When the reply from P2 arrives, a callback is triggered
which will determine if the request did get a positive reply, and if
so it will extract the registration expiration time and enable the
keepalive functionality for that endpoint for the registration
condition for the time given by the registration expiration.
For single proxy environments, or if P1 is the same as P2, then
t_relay() is not called, instead save_location() is called if the
registration is accepted. Then the same process described above
happens only this time triggered by a stateless reply callback.
In both cases, calling nat_keepalive() when the REGISTER is received
has no other effect that to trigger some callbacks that will determine
from the reply if the caller endpoint should be kept alive or not.
</para>
<para>
Below is described how nat_keepalive() should be called and what it
does for each of the requests that need keepalive functionality (the
function should only be called if it is determined that the user agent
that generated the request is behind NAT):
<itemizedlist>
<listitem>
<para>
<emphasis>REGISTER</emphasis> - called before save_location() or
t_relay() (depending on whether the proxy that received the
REGISTER is also handling registration for that subscriber or
not). It will determine from either the stateless reply
generated by save_location() or the TM relayed reply if the
registration was successful and what is its expiration time. If
the registration was successful it will mark the given NAT
endpoint for keepalive for the registration condition using the
detected expiration time. If the REGISTER request is discarded
after nat_keepalive() was called or if it intercepts a negative
reply it will have no effect and the registration condition will
not be activated for that endpoint.
</para>
</listitem>
<listitem>
<para>
<emphasis>SUBSCRIBE</emphasis> - called before handle_subscribe()
or t_relay() (depending on whether the proxy that received the
SUBSCRIBE is also handling subscriptions for that subscriber or
not). It will determine from either the stateless reply
generated by handle_subscribe() or the TM relayed reply if the
subscription was successful and what is its expiration time. If
the subscription was successful it will mark the given NAT
endpoint for keepalive for the subscription condition using the
detected expiration time. If the SUBSCRIBE request is discarded
after nat_keepalive() was called or if it intercepts a negative
reply it will have no effect and the subscription condition will
not be activated for that endpoint. It should be called for
every SUBSCRIBE received, not only the ones that start a
subscription (do not have a to tag), because it needs to update
(extend) the expiration time for the subscription.
</para>
</listitem>
<listitem>
<para>
<emphasis>INVITE</emphasis> - called before t_relay() for the
first INVITE in a dialog. It will automatically trigger dialog
tracing for that dialog and will use the dialog callbacks to
detect changes in the dialog state. It will add a keepalive
entry with the dialog condition for the caller NAT endpoint as
soon as the dialog is created (this happens when t_relay() is
called). It will then keep that condition for the given endpoint
until the dialog is destroyed (either terminated, failed or
expired). If the INVITE request cannot be relayed after
nat_keepalive() was called it will have no effect and the
dialog condition will not be activated for that endpoint.
</para>
<para>
In addition an INVITE that starts a dialog will automatically
trigger keepalive functionality for the destination endpoints
if they are behind NAT. This is done by detecting if any of the
destination endpoints already has a keepalive entry for the
register condition. If so, a dialog condition will be added to
that entry thus preserving that endpoint visibility even if the
registration expires during the dialog or is moved to another
proxy. During the call setup stage, multiple entries for the
callee may be added with the dialog condition if parallel
forking is used, however only the destination endpoints behind
NAT will have the extra dialog condition set. Later when the
dialog is confirmed, only the endpoint that answered the call
will keep the dialog condition activated (if present), while all
the endpoints from the unanswered branches will have it removed.
This is done automatically without any need to call any function.
</para>
</listitem>
</itemizedlist>
</para>
<para>
Considering the elements presented in this section, we can say that
the nat_traversal module provides a flexible and efficient keepalive
functionality that is very easy to use. Because only the border
proxies send keepalive messages, the network traffic is minimized.
For the same reason, message processing in the proxies is also
minimized, as border proxies generate keepalive messages themselves
and send them statelessly, instead of having to relay messages
generated by the registrars. Network traffic is also minimized by only
sending a single keepalive message for an endpoint no matter for how
many reasons the endpoint is kept alive. Keepalive messages are also
distributed over the keepalive interval to avoid overloading the
proxy by generating too many messages at a time. The nat_traversal
module keeps its internal state about endpoints that need keepalive,
state that is build while messages are processed by the proxy and
thus it doesn't need to transfer any information from the usrloc
module, which should also improve its efficiency.
</para>
</section>
</section>
<section>
<title>Dependencies</title>
<section>
<title>&kamailio; Modules</title>
<para>
The following modules must be loaded before this module:
<itemizedlist>
<listitem>
<para>
<emphasis>sl</emphasis> module - if keepalive is enabled.
</para>
</listitem>
<listitem>
<para>
<emphasis>tm</emphasis> module - if keepalive is enabled.
</para>
</listitem>
<listitem>
<para>
<emphasis>dialog</emphasis> module - if keepalive is enabled
and keeping alive INVITE dialogs is needed.
</para>
</listitem>
</itemizedlist>
</para>
</section>
<section>
<title>External Libraries or Applications</title>
<para>
The following libraries or applications must be installed before
running &kamailio; with this module loaded:
<itemizedlist>
<listitem><para>
<emphasis>None</emphasis>.
</para></listitem>
</itemizedlist>
</para>
</section>
</section>
<section>
<title>Exported parameters</title>
<section>
<title><varname>keepalive_interval</varname> (integer)</title>
<para>
The time interval (in seconds) required to send a keepalive message to
all the endpoints that need being kept alive. During this interval,
each endpoint will receive exactly one keepalive message. A negative
value or zero will disable the keepalive functionality.
</para>
<para>
<emphasis>
Default value is <quote>60</quote>.
</emphasis>
</para>
<example>
<title>Setting the <varname>keepalive_interval</varname> parameter</title>
<programlisting format="linespecific">
...
modparam("nat_traversal", "keepalive_interval", 90)
...
</programlisting>
</example>
</section>
<section>
<title><varname>keepalive_method</varname> (string)</title>
<para>
What SIP method to use to send keepalive messages. Typical methods
used for this purpose are NOTIFY and OPTIONS. NOTIFY generates smaller
replies from user agents, but they are almost entirely negative replies.
Apparently almost none of the user agents understand that the purpose
of the NOTIFY with a <quote>keep-alive</quote> event is to keep NAT
open, even though many user agents send such NOTIFY requests themselves.
However this does not affect the result at all, since the purpose is
to trigger a response from the user agent behind NAT, positive or
negative replies having little relevance as they are discarded anyway.
The OPTIONS method on the other hand has a much higher rate of positive
replies, but at the same time those positive replies are much bigger,
mostly because the OPTIONS method is used to inform about the user
agent capabilities and thus it includes a lot of extra headers to
indicate those capabilities. Many user agents also include a SDP body
with a bogus media session, probably to indicate media capabilities.
All of this makes that positive replies to OPTIONS requests are 2 to
3 times bigger than negative replies or replies to NOTIFY requests.
For this reason the default value for the used method is NOTIFY.
</para>
<para>
<emphasis>
Default value is <quote>NOTIFY</quote>.
</emphasis>
</para>
<example>
<title>Setting the <varname>keepalive_method</varname> parameter</title>
<programlisting format="linespecific">
...
modparam("nat_traversal", "keepalive_method", "OPTIONS")
...
</programlisting>
</example>
</section>
<section>
<title><varname>keepalive_from</varname> (string)</title>
<para>
Indicates what SIP URI to use in the From header of the keepalive
requests. If not specified it will use sip:keepalive@proxy_ip, where
proxy_ip is the IP address of the outgoing interface used to send the
keepalive message, which is the same interface on which the request
that triggered keepalive functionality arrived.
</para>
<para>
<emphasis>
Default value is <quote>sip:keepalive@proxy_ip</quote> with proxy_ip
being the actual IP of the outgoing interface.
</emphasis>
</para>
<example>
<title>Setting the <varname>keepalive_from</varname> parameter</title>
<programlisting format="linespecific">
...
modparam("nat_traversal", "keepalive_from", "sip:keepalive@my-domain.com")
...
</programlisting>
</example>
</section>
<section>
<title><varname>keepalive_extra_headers</varname> (string)</title>
<para>
Specifies extra headers that should be added to the keepalive messages
that are sent by the proxy. The header specification must also include
the CRLF (\r\n) line separator. Multiple headers can be specified by
concatenating them and each of them must include the \r\n separator.
</para>
<para>
<emphasis>
Default value is undefined (send no extra headers).
</emphasis>
</para>
<example>
<title>Setting the <varname>keepalive_extra_headers</varname> parameter</title>
<programlisting format="linespecific">
...
modparam("nat_traversal", "keepalive_extra_headers", "User-Agent: &kamailio;\r\nX-MyHeader: some_value\r\n")
...
</programlisting>
</example>
</section>
<section>
<title><varname>keepalive_state_file</varname> (string)</title>
<para>
Specifies a filename where information about the NAT endpoints and the
conditions for which they are being kept alive is saved when &kamailio;
exits. The information in this file is then used when &kamailio; starts
to restore its internal state and continue to send keepalive messages
to the NAT endpoints that have not expired in the meantime. This is
useful when restarting &kamailio; to avoid losing keepalive state
information about the NAT endpoints. The internal keepalive state is
guaranteed to be saved in this file on exit, even when &kamailio;
crashes.
</para>
<para>
The value of this parameter can be either a relative path, in which
case it will store it in the &kamailio; working directory, or an
absolute path.
</para>
<para>
<emphasis>
Default value is undefined <quote>keepalive_state</quote>.
</emphasis>
</para>
<example>
<title>Setting the <varname>keepalive_state_file</varname> parameter</title>
<programlisting format="linespecific">
...
modparam("nat_traversal", "keepalive_state_file", "/var/run/kamailio/keepalive_state")
...
</programlisting>
</example>
</section>
</section>
<section>
<title>Exported functions</title>
<section>
<title>
<function moreinfo="none">client_nat_test(type)</function>
</title>
<para>
Check if the client is behind NAT. What tests are performed is
specified by the type parameter which is an integer given by the sum
of the numbers corresponsing to the tests that one wishes to perform.
The numbers corresponding to individual tests are shown below:
</para>
<para>
<itemizedlist>
<listitem><para>
1 - tests if client has a private IP address (as defined by RFC1918)
or one from shared address space (RFC6598) in the Contact field
of the SIP message.
</para></listitem>
<listitem><para>
2 - tests if client has contacted &kamailio; from an address that
is different from the one in the Via field. Both the IP and
port are compared by this test.
</para></listitem>
<listitem><para>
4 - tests if client has a private IP address (as defined by RFC1918)
or one from shared address space (RFC6598) in the top Via field
of the SIP message.
</para></listitem>
</itemizedlist>
</para>
<para>
For example calling client_nat_test("3") will perform test 1 and
test 2 and return true if at least one succeeds, otherwise false.
</para>
<para>
This function can be used from REQUEST_ROUTE, ONREPLY_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE.
</para>
<example>
<title>Using the <function>client_nat_test</function> function</title>
<programlisting format="linespecific">
...
if (client_nat_test("3")) {
.....
}
...
</programlisting>
</example>
</section>
<section>
<title>
<function moreinfo="none">fix_contact()</function>
</title>
<para>
Will replace the IP and port in the Contact header with the
IP and port the SIP message was received from. Usually called
after a succesful call to client_nat_test(type)
</para>
<para>
This function can be used from REQUEST_ROUTE, ONREPLY_ROUTE, BRANCH_ROUTE.
</para>
<example>
<title>Using the <function>fix_contact</function> function</title>
<programlisting format="linespecific">
...
if (client_nat_test("3")) {
fix_contact();
}
...
</programlisting>
</example>
</section>
<section>
<title>
<function moreinfo="none">nat_keepalive()</function>
</title>
<para>
Trigger keepalive functionality for the source address of the request.
When called it only sets some internal flags, which will trigger
later the addition of the endpoint to the keepalive list if a
positive reply is generated/received (for REGISTER and SUBSCRIBE)
or when the dialog is started/replied (for INVITEs).
For this reason, it can be called early or late in the script. The
only condition is to call it before replying to the request or before
sending it to another proxy. If the request needs to be sent to
another proxy, t_relay() must be used to be able to intercept replies
via TM or dialog callbacks. If stateless forwarding is used, the
keepalive functionality will not work. Also for outgoing INVITEs,
record_route() should also be used to make sure the proxy that keeps
the caller endpoint alive stays in the path. For multi-proxy setups,
this function should always be called on the border proxies (the ones
that received the request directly from the user agent). For more
details about this function, see the <emphasis>Implementation</emphasis>
subsection from the <emphasis>Keepalive functionality</emphasis> section.
</para>
<para>
This function can be used from REQUEST_ROUTE.
</para>
<example>
<title>Using the <function>nat_keepalive</function> function</title>
<programlisting format="linespecific">
...
if ((method=="REGISTER" || method=="SUBSCRIBE" ||
(method=="INVITE" &amp;&amp; !has_totag())) &amp;&amp; client_nat_test("3"))
{
nat_keepalive();
}
...
</programlisting>
</example>
</section>
</section>
<section>
<title>Statistics</title>
<section>
<title><varname>keepalive_endpoints</varname></title>
<para>
Indicates the total number of NAT endpoints that are being kept alive.
</para>
</section>
<section>
<title><varname>registered_endpoints</varname></title>
<para>
Indicates how many of the NAT endpoints are kept alive for registrations.
</para>
</section>
<section>
<title><varname>subscribed_endpoints</varname></title>
<para>
Indicates how many of the NAT endpoints are kept alive for subscriptions.
</para>
</section>
<section>
<title><varname>dialog_endpoints</varname></title>
<para>
Indicates how many of the NAT endpoints are kept alive for taking part
in an INVITE dialog.
</para>
</section>
</section>
<section>
<title>Exported pseudo-variables</title>
<section>
<title><varname>$keepalive.socket(nat_endpoint)</varname></title>
<para>
Returns the local socket used to send messages to the given NAT
endpoint URI. The socket has the form proto:ip:port. The NAT endpoint
URI is in the form: sip:ip:port[;transport=xxx] with transport missing
if UDP. If the requested NAT endpoint URI is present in the internal
keepalive table for any condition, it will return its associated local
socket, else it will return null. The nat_endpoint can be a string or
another pseudo-variable.
</para>
<para>
This can be useful to restore the sending socket when relaying messages
to a given user agent in multi-proxy environments. Consider an example
where 2 proxies are involved, P1 and P2. A user agent registers by
sending a REGISTER request to P1. P1 will call nat_keepalive() but
because it determines that P2 should actually handle the user
registration will forward the request to P2. Now assume P2 receives an
incoming INVITE for this user. It will determine that the registration
came through P1 and will forward the request to P1. P2 should also
include the NAT endpoint URI where this request is to be relayed.
This information should have been provided by P1 when it relayed the
REGISTER request to P2. The means to do this is out of the scope of
this example, but one can either use the path extension or custom
headers to do this. When P1 receives the INVITE it will use the NAT
endpoint URI it has received along with the request to determine the
socket to send out the request, which should be the same as the one
where the registration request was originally received. In the example
below lets assume that P2 provided the original NAT endpoint address
in a custom header called X-NAT-URI and that it also provides a custom
header called X-Scope to indicate that the message is sent to P1 for
being relayed back to the user agent by P1 which has the NAT open
with it.
</para>
<example>
<title>Using <varname>$keepalive.socket</varname> in multi-proxy environments</title>
<programlisting format="linespecific">
...
# This code runs on P1 which has received an INVITE from P2 to forward
# it to the user agent behind NAT (because P1 has the NAT open with it).
if (method=="INVITE" &amp;&amp; $hdr(X-Scope)=="nat-relay") {
$du = $hdr(X-NAT-URI);
$fs = $keepalive.socket($du);
t_relay();
exit;
}
...
</programlisting>
</example>
</section>
<section>
<title><varname>$source_uri</varname></title>
<para>
Returns the URI specification from where a request was received in the
form sip:ip:port[;transport=xxx] with transport missing if UDP.
</para>
<para>
This pseudo-variable can be used to set the received AVP for the
registrar module to indicate that a user agent is behind NAT. This is
meant as a more flexible replacement for the fix_nated_register()
function, because it allows one to modify the source uri by appending
some extra parameters before saving it to the received AVP.
</para>
<para>
Another use for this pseudo-variable is in multi-proxy environments to
indicate the NAT endpoint URI to the next proxy (if needed). Consider
the previous example with two proxies P1 and P2. P1 receives the
REGISTER request from a user agent and forwards it to P2 which does
the actual registration. P1 needs to indicate the NAT endpoint URI to
P2, so that P2 can include it later for incoming INVITE requests to
this user agent.
</para>
<example>
<title>Using <varname>$source_uri</varname> to set the received AVP on registrars</title>
<programlisting format="linespecific">
...
modparam("registrar", "received_avp", "$avp(s:received_uri)")
modparam("registrar", "tcp_persistent_flag", 10)
...
# This code runs on the registrar, assuming it has received the
# REGISTER request directly from the user agent.
if (method=="REGISTER") {
if (client_nat_test("3")) {
if (proto==UDP) {
nat_keepalive();
} else {
# Keep TCP/TLS connections open until the registration
# expires, by setting the tcp_persistent_flag
setflag(10);
}
force_rport();
$avp(s:received_uri) = $source_uri;
# or we could add some extra parameters to it if needed
# $avp(s:received_uri) = $source_uri + ";relayed=false"
}
if (!www_authorize("", "subscriber")) {
www_challenge("", "0");
return;
} else if (!check_to()) {
sl_send_reply("403", "Username!=To not allowed ($au!=$tU)");
return;
}
if (!save("location")) {
sl_reply_error();
}
exit;
}
...
</programlisting>
</example>
<example>
<title>Using <varname>$source_uri</varname> in multi-proxy environments</title>
<programlisting format="linespecific">
...
# This code runs on P1 which received the REGISTER request and has to
# forward it to the registrar P2.
if (method=="REGISTER") {
if (client_nat_test("3")) {
force_rport();
nat_keepalive();
append_hf("X-NAT-URI: $source_uri\r\n");
}
$du = "sip:P2_ip:P2_port";
t_relay();
exit;
}
...
</programlisting>
</example>
</section>
</section>
<section>
<title>Keepalive use cases</title>
<section>
<title>Single proxy environments</title>
<para>
In this case the usage is straight forward. The nat_keepalive() function
needs to be called before save_location() for REGISTER requests, before
handle_subscribe() for SUBSCRIBE requests and before t_relay() for the
first INVITE of a dialog.
</para>
</section>
<section>
<title>Registration in multi-proxy environments</title>
<para>
If the proxy receiving the REGISTER request is the same as the proxy
handling it, then the case is reduced to the single proxy case. For
this example, lets assume they are different. We have a user agent UA1
for which the registration is handled by the proxy P1. However UA1
sends the REGISTER to P0 which in turn forwards it to P1 like this:
UA1 --> P0 --> P1. In this case P0 calls nat_keepalive(), adds the NAT
endpoint URI to the request (for example using a custom header) and
forwards the request to P1. P1 will save the user in the user location
together with the NAT endpoint URI.
</para>
<para>
When an incoming INVITE request arrives on P1 for UA1, P1, will lookup
the location and determine that it has to relay it to P0 because P0
has the NAT open with UA1. P1 will include the original NAT endpoint
URI in the request and an indication that the only role P0 has in this
transaction is to relay it to UA1. P0 will receive this request and
determine that is has to act as a relay for it. It will extract the
NAT endpoint URI, then based on it the corresponding local socket
using $keepalive.socket(endpoint_uri). It will then set both $du and
$fs to the values it has found, call record_route() to stay in the
path and call t_relay() to send it to UA1.
</para>
<para>
Handling other type of requests (like for example SUBSCRIBE or
MESSAGE) that arrive on P1 for UA1 is done the same way as with the
first INVITE, on both P1 and P0.
</para>
</section>
<section>
<title>Subscription in multi-proxy environments</title>
<para>
If the proxy receiving the SUBSCRIBE request is the same as the proxy
handling it, then the case is reduced to the single proxy case. For
this example, lets assume they are different. We have a user agent UA1
for which subscriptions are handled by the proxy P1. However UA1
sends the SUBSCRIBE to P0 which in turn forwards it to P1 like this:
UA1 --> P0 --> P1. In this case P0 calls nat_keepalive(), then calls
record_route() to stay in the path and forwards the request to P1
using t_relay(). Further SUBSCRIBE and NOTIFY requests will follow
the record route and use P0 as a NAT entry point to have access to UA1.
Further in-dialog SUBSCRIBE requests should also call record_route().
</para>
</section>
<section>
<title>Outgoing INVITEs in multi-proxy environments</title>
<para>
If the proxy receiving the INVITE request is the same as the proxy
handling it, then the case is reduced to the single proxy case. For
this example, lets assume they are different. We have a user agent UA1
which is handled by the proxy P1 and UA2 which is handled by P2. UA2
has registered with P2 going through P3, while UA1 calls UA2 by sending
the first INVITE to P0. The call flow for the first INVITE looks like
this: UA1 --> P0 --> P1 --> P2 --> P3 --> UA2.
In this case P0 calls nat_keepalive(), then calls record_route() to
stay in the path and forwards the request to P1. P1 authenticates UA1
then forwards the request to P2, which is the home proxy for UA2. P1
doesn't have to use record_route to stay in the path, but it can do
that if needed for other purposes. P2 will lookup UA2 and find out
that it is reachable through P3. It will take the original NAT
endpoint URI that is has saved in the user location when UA2 has
registered and include it in the message along with an indication that
P3 only has to relay the message to UA2. If P2 does accounting or
starts a media relay, it should also call record_route() to stay in
the path. Then it forwards the request to P3 using t_relay(). P3 will
detect that it only has to relay the request to UA2 because it has the
NAT open with it. It will extract the NAT endpoint URI from the message
and the local sending socket using $keepalive.socket(endpoint_uri) and
will set both $du and $fs. After that it will call record_route() to
stay in the path, and forward the request to UA2 using t_relay().
Further in-dialog requests will follow the recorded route and use
P0 and P3 as access points to UA1 respectively UA2. All the proxies
that have used record_route() during the first INVITE should also
call record_route() during further in-dialog requests to keep staying
in the path.
</para>
</section>
</section>
</chapter>