Commit e08de77a authored by Pekka Pessi's avatar Pekka Pessi
Browse files

nua session timer: refactored implementation, using timer values recommended by RFC 4028.

The problem and crash with session timers was reported by Fabio Margarido.

Now following RFC 4028 more closely. There are test cases for UAS and UAC
refreshing the session with INVITE and UPDATE, and a test case where UPDATE
is filtered away until non-refreshing party sends BYE.

The re-INVITE/UPDATE refresh is sent around middle of expiration time (e.g.,
if expiration time is 3600 seconds, it is sent 1795..1805 seconds after the
previous refresh).

The non-refreshing party now sends a BYE request before two thirds of
session expiration time has elapsed without session refresh request.

darcs-hash:20070411174513-65a35-229d09a2e5371b5a88c9430f80da637b4c2bb6be.gz
parent 4ffae050
......@@ -149,9 +149,10 @@ int nua_stack_set_defaults(nua_handle_t *nh,
NHP_SET(nhp, auto_ack, 1);
NHP_SET(nhp, invite_timeout, 120);
NHP_SET(nhp, session_timer, 1800);
nhp->nhp_session_timer = 1800;
nhp->nhp_refresher = nua_no_refresher;
NHP_SET(nhp, min_se, 120);
NHP_SET(nhp, refresher, nua_no_refresher);
NHP_SET(nhp, update_refresh, 0);
NHP_SET(nhp, message_enable, 1);
......@@ -1503,6 +1504,11 @@ int nua_stack_get_params(nua_t *nua, nua_handle_t *nh, nua_event_t e,
#define TIF(TAG, pref) \
TAG_IF(nhp->nhp_set.nhb_##pref, TAG(nhp->nhp_##pref))
/* Include tag in the list returned to user
* if it has been earlier set (by user) returning default parameters */
#define TIFD(TAG, pref) \
TAG_IF(nh == dnh || nhp->nhp_set.nhb_##pref, TAG(nhp->nhp_##pref))
/* Include string tag made out of SIP header
* if it has been earlier set (by user) */
#define TIF_STR(TAG, pref) \
......@@ -1540,9 +1546,9 @@ int nua_stack_get_params(nua_t *nua, nua_handle_t *nh, nua_event_t e,
TIF(NUTAG_AUTOACK, auto_ack),
TIF(NUTAG_INVITE_TIMER, invite_timeout),
TIF(NUTAG_SESSION_TIMER, session_timer),
TIFD(NUTAG_SESSION_TIMER, session_timer),
TIF(NUTAG_MIN_SE, min_se),
TIF(NUTAG_SESSION_REFRESHER, refresher),
TIFD(NUTAG_SESSION_REFRESHER, refresher),
TIF(NUTAG_UPDATE_REFRESH, update_refresh),
TIF(NUTAG_ENABLEMESSAGE, message_enable),
......
......@@ -226,4 +226,9 @@ typedef struct nua_handle_preferences
(NHP_ISSET((nh)->nh_prefs, pref) && \
(nh)->nh_nua->nua_dhandle->nh_prefs != (nh)->nh_prefs)
/* Check if preference has been set by applicationx */
#define NUA_PISSET(nua, nh, pref) \
(NHP_ISSET((nua)->nua_dhandle->nh_prefs, pref) || \
((nh) && NHP_ISSET((nh)->nh_prefs, pref)))
#endif /* NUA_PARAMS_H */
......@@ -149,14 +149,23 @@ typedef struct nua_session_usage
unsigned ss_precondition:1; /**< Precondition required */
unsigned ss_timer_set:1; /**< We have active session timer. */
unsigned ss_reporting:1; /**< True if reporting state */
unsigned : 0;
unsigned ss_session_timer; /**< Value of Session-Expires (delta) */
unsigned ss_min_se; /**< Minimum session expires */
enum nua_session_refresher ss_refresher; /**< none, local or remote */
struct session_timer {
unsigned interval; /**< Negotiated expiration time */
enum nua_session_refresher refresher; /**< Our Negotiated role */
struct {
unsigned expires, defaults; /**< Value of Session-Expires (delta) */
unsigned min_se; /**< Minimum session expires */
/** none, local or remote */
enum nua_session_refresher refresher;
unsigned supported:1, require:1, :0;
} local, remote;
unsigned timer_set:1; /**< We have active session timer. */
} ss_timer[1];
char const *ss_reason; /**< Reason for termination. */
......@@ -204,11 +213,16 @@ int nua_session_usage_add(nua_handle_t *nh,
nua_dialog_state_t *ds,
nua_dialog_usage_t *du)
{
nua_session_usage_t *ss = nua_dialog_usage_private(du);
if (ds->ds_has_session)
return -1;
ds->ds_has_session = 1;
ds->ds_got_session = 1;
ss->ss_timer->local.refresher = nua_any_refresher;
ss->ss_timer->remote.refresher = nua_any_refresher;
return 0;
}
......@@ -297,19 +311,29 @@ void nua_session_usage_destroy(nua_handle_t *nh,
int nua_stack_prack(nua_t *nua, nua_handle_t *nh, nua_event_t e,
tagi_t const *tags);
static void session_timer_preferences(nua_session_usage_t *ss,
unsigned expires,
unsigned min_se,
enum nua_session_refresher refresher);
static int session_timer_is_supported(struct session_timer const *t);
static void session_timer_preferences(struct session_timer *t,
sip_t const *sip,
sip_supported_t const *supported,
unsigned expires, int isset,
enum nua_session_refresher refresher,
unsigned min_se);
static int session_timer_is_supported(nua_handle_t const *nh);
static void session_timer_store(struct session_timer *t,
sip_t const *sip);
static int prefer_session_timer(nua_handle_t const *nh);
static int session_timer_check_min_se(msg_t *msg, sip_t *sip,
sip_t const *request,
unsigned long min_se);
static int use_session_timer(nua_session_usage_t *ss, int uas, int always,
msg_t *msg, sip_t *);
static int init_session_timer(nua_session_usage_t *ss, sip_t const *, int refresher);
static void set_session_timer(nua_session_usage_t *ss);
static int session_timer_add_headers(struct session_timer *t,
int initial,
msg_t *msg, sip_t *sip);
static void session_timer_negotiate(struct session_timer *t);
static void session_timer_set(nua_session_usage_t *ss);
static int session_timer_check_restart(nua_client_request_t *cr,
int status, char const *phrase,
......@@ -551,6 +575,7 @@ static int nua_invite_client_init(nua_client_request_t *cr,
{
nua_handle_t *nh = cr->cr_owner;
nua_dialog_usage_t *du;
nua_session_usage_t *ss;
cr->cr_usage = du = nua_dialog_usage_for_session(nh->nh_ds);
/* Errors returned by nua_invite_client_init()
......@@ -573,16 +598,22 @@ static int nua_invite_client_init(nua_client_request_t *cr,
}
else
du = nua_dialog_usage_add(nh, nh->nh_ds, nua_session_usage, NULL);
if (!du)
return -1;
if (nua_client_bind(cr, du) < 0)
return nua_client_return(cr, 900, "INVITE already in progress", msg);
session_timer_preferences(nua_dialog_usage_private(du),
ss = nua_dialog_usage_private(du);
session_timer_preferences(ss->ss_timer,
sip,
NH_PGET(nh, supported),
NH_PGET(nh, session_timer),
NH_PGET(nh, min_se),
NH_PGET(nh, refresher));
NUA_PISSET(nh->nh_nua, nh, session_timer),
NH_PGET(nh, refresher),
NH_PGET(nh, min_se));
cr->cr_neutral = 0;
......@@ -608,12 +639,13 @@ static int nua_invite_client_request(nua_client_request_t *cr,
if (invite_timeout == 0)
invite_timeout = UINT_MAX;
/* Send CANCEL if we don't get response within timeout*/
nua_dialog_usage_set_expires(du, invite_timeout);
/* nua_dialog_usage_set_expires(du, invite_timeout); Xyzzy */
nua_dialog_usage_set_refresh(du, 0);
/* Add session timer headers */
if (session_timer_is_supported(nh))
use_session_timer(ss, 0, prefer_session_timer(nh), msg, sip);
if (session_timer_is_supported(ss->ss_timer))
session_timer_add_headers(ss->ss_timer, ss->ss_state == nua_callstate_init,
msg, sip);
ss->ss_100rel = NH_PGET(nh, early_media);
ss->ss_precondition = sip_has_feature(sip->sip_require, "precondition");
......@@ -672,7 +704,6 @@ static int nua_invite_client_response(nua_client_request_t *cr,
int status, char const *phrase,
sip_t const *sip)
{
nua_handle_t *nh = cr->cr_owner;
nua_dialog_usage_t *du = cr->cr_usage;
nua_session_usage_t *ss = nua_dialog_usage_private(du);
......@@ -682,8 +713,10 @@ static int nua_invite_client_response(nua_client_request_t *cr,
else if (status < 300) {
du->du_ready = 1;
init_session_timer(ss, sip, NH_PGET(nh, refresher));
set_session_timer(ss);
if (session_timer_is_supported(ss->ss_timer))
session_timer_store(ss->ss_timer, sip);
session_timer_set(ss);
}
return nua_session_client_response(cr, status, phrase, sip);
......@@ -1263,42 +1296,37 @@ static void nua_session_usage_refresh(nua_handle_t *nh,
nua_client_request_t const *cr = du->du_cr;
nua_server_request_t const *sr;
assert(cr);
if (ss->ss_state >= nua_callstate_terminating ||
/* No INVITE template */
cr == NULL ||
/* INVITE is in progress or being authenticated */
cr->cr_orq || cr->cr_wait_for_cred)
(cr && (cr->cr_orq || cr->cr_wait_for_cred)))
return;
/* UPDATE in progress or being authenticated */
/* UPDATE has been queued */
for (cr = ds->ds_cr; cr; cr = cr->cr_next)
if (cr->cr_method == sip_method_update)
return;
/* INVITE or UPDATE in progress */
/* INVITE or UPDATE in progress on server side */
for (sr = ds->ds_sr; sr; sr = sr->sr_next)
if (sr->sr_usage == du &&
(sr->sr_method == sip_method_invite ||
sr->sr_method == sip_method_update))
return;
if (!ss->ss_refresher) {
if (du->du_expires == 0 || now < du->du_expires)
/* Refresh contact & route set using re-INVITE */
nua_client_resend_request(du->du_cr, 0);
else {
ss->ss_reason = "SIP;cause=408;text=\"Session timeout\"";
nua_stack_bye(nh->nh_nua, nh, nua_r_bye, NULL);
}
if (ss->ss_timer->refresher == nua_remote_refresher) {
ss->ss_reason = "SIP;cause=408;text=\"Session timeout\"";
nua_stack_bye(nh->nh_nua, nh, nua_r_bye, NULL);
return;
}
else if (NH_PGET(nh, update_refresh)) {
nua_stack_update(nh->nh_nua, nh, nua_r_update, NULL);
}
else {
else if (du->du_cr) {
nua_client_resend_request(du->du_cr, 0);
}
else {
nua_stack_invite(nh->nh_nua, nh, nua_r_invite, NULL);
}
}
/** @interal Shut down session usage.
......@@ -1787,8 +1815,6 @@ nua_session_server_init(nua_server_request_t *sr)
sip_t const *request = sr->sr_request.sip;
unsigned min = NH_PGET(nh, min_se);
if (!sr->sr_initial)
sr->sr_usage = nua_dialog_usage_get(nh->nh_ds, nua_session_usage, NULL);
......@@ -1817,21 +1843,15 @@ nua_session_server_init(nua_server_request_t *sr)
}
if (request->sip_session_expires &&
nta_check_session_expires(NULL, request, min, TAG_END())) {
sip_min_se_t *min_se, min_se0[1];
min_se = sip_min_se_init(min_se0);
min_se->min_delta = min;
if (request->sip_min_se && request->sip_min_se->min_delta > min)
min_se = request->sip_min_se;
sip_add_dup(msg, sip, (sip_header_t *)min_se);
return SR_STATUS1(sr, SIP_422_SESSION_TIMER_TOO_SMALL);
sip_has_feature(NH_PGET(nh, supported), "timer") &&
session_timer_check_min_se(msg, sip, request, NH_PGET(nh, min_se))) {
if (sip->sip_min_se)
return SR_STATUS1(sr, SIP_422_SESSION_TIMER_TOO_SMALL);
else
return SR_STATUS1(sr, SIP_500_INTERNAL_SERVER_ERROR);
}
session_get_description(sr->sr_request.sip, &sr->sr_sdp, &sr->sr_sdp_len);
session_get_description(request, &sr->sr_sdp, &sr->sr_sdp_len);
return 0;
}
......@@ -1885,14 +1905,7 @@ int nua_invite_server_preprocess(nua_server_request_t *sr)
if (ss->ss_precondition)
ss->ss_100rel = 1;
session_timer_preferences(ss,
NH_PGET(nh, session_timer),
NH_PGET(nh, min_se),
NH_PGET(nh, refresher));
/* Session Timer negotiation */
if (sip_has_supported(NH_PGET(nh, supported), "timer"))
init_session_timer(ss, request, ss->ss_refresher);
session_timer_store(ss->ss_timer, request);
assert(ss->ss_state >= nua_callstate_ready ||
ss->ss_state == nua_callstate_init);
......@@ -2043,9 +2056,18 @@ int nua_invite_server_respond(nua_server_request_t *sr, tagi_t const *tags)
return 0;
}
if (ss->ss_refresher && 200 <= sr->sr_status && sr->sr_status < 300)
if (session_timer_is_supported(nh))
use_session_timer(ss, 1, 1, msg, sip);
if (200 <= sr->sr_status && sr->sr_status < 300) {
session_timer_preferences(ss->ss_timer,
sip,
NH_PGET(nh, supported),
NH_PGET(nh, session_timer),
NUA_PISSET(nh->nh_nua, nh, session_timer),
NH_PGET(nh, refresher),
NH_PGET(nh, min_se));
if (session_timer_is_supported(ss->ss_timer))
session_timer_add_headers(ss->ss_timer, 0, msg, sip);
}
return nua_base_server_respond(sr, tags);
}
......@@ -2249,7 +2271,7 @@ int process_ack(nua_server_request_t *sr,
nua_stack_event(nh->nh_nua, nh, msg, nua_i_ack, SIP_200_OK, NULL);
signal_call_state_change(nh, ss, 200, "OK", nua_callstate_ready);
set_session_timer(ss);
session_timer_set(ss);
nua_server_request_destroy(sr);
......@@ -2559,171 +2581,6 @@ int nua_prack_server_report(nua_server_request_t *sr, tagi_t const *tags)
return retval;
}
/* ---------------------------------------------------------------------- */
/* Session timer - RFC 4028 */
static int session_timer_is_supported(nua_handle_t const *nh)
{
/* Is timer feature supported? */
return sip_has_supported(NH_PGET(nh, supported), "timer");
}
static int prefer_session_timer(nua_handle_t const *nh)
{
return
NH_PGET(nh, refresher) != nua_no_refresher ||
NH_PGET(nh, session_timer) != 0;
}
/* Initialize session timer */
static
void session_timer_preferences(nua_session_usage_t *ss,
unsigned expires,
unsigned min_se,
enum nua_session_refresher refresher)
{
if (expires < min_se)
expires = min_se;
if (refresher && expires == 0)
expires = 3600;
ss->ss_min_se = min_se;
ss->ss_session_timer = expires;
ss->ss_refresher = refresher;
}
/** Add timer featuretag and Session-Expires/Min-SE headers */
static int
use_session_timer(nua_session_usage_t *ss, int uas, int always,
msg_t *msg, sip_t *sip)
{
sip_min_se_t min_se[1];
sip_session_expires_t session_expires[1];
static sip_param_t const x_params_uac[] = {"refresher=uac", NULL};
static sip_param_t const x_params_uas[] = {"refresher=uas", NULL};
/* Session-Expires timer */
if (ss->ss_refresher == nua_no_refresher && !always)
return 0;
sip_min_se_init(min_se)->min_delta = ss->ss_min_se;
sip_session_expires_init(session_expires)->x_delta = ss->ss_session_timer;
if (ss->ss_refresher == nua_remote_refresher)
session_expires->x_params = uas ? x_params_uac : x_params_uas;
else if (ss->ss_refresher == nua_local_refresher)
session_expires->x_params = uas ? x_params_uas : x_params_uac;
sip_add_tl(msg, sip,
TAG_IF(ss->ss_session_timer,
SIPTAG_SESSION_EXPIRES(session_expires)),
TAG_IF(ss->ss_min_se != 0
/* Min-SE: 0 is optional with initial INVITE */
|| ss->ss_state != nua_callstate_init,
SIPTAG_MIN_SE(min_se)),
TAG_IF(ss->ss_refresher == nua_remote_refresher,
SIPTAG_REQUIRE_STR("timer")),
TAG_END());
return 1;
}
static int
init_session_timer(nua_session_usage_t *ss,
sip_t const *sip,
int refresher)
{
int server;
/* Session timer is not needed */
if (!sip->sip_session_expires) {
if (!sip_has_supported(sip->sip_supported, "timer"))
ss->ss_refresher = nua_local_refresher;
return 0;
}
ss->ss_refresher = nua_no_refresher;
ss->ss_session_timer = sip->sip_session_expires->x_delta;
if (sip->sip_min_se != NULL
&& sip->sip_min_se->min_delta > ss->ss_min_se)
ss->ss_min_se = sip->sip_min_se->min_delta;
server = sip->sip_request != NULL;
if (!sip_has_supported(sip->sip_supported, "timer"))
ss->ss_refresher = nua_local_refresher;
else if (!str0casecmp("uac", sip->sip_session_expires->x_refresher))
ss->ss_refresher = server ? nua_remote_refresher : nua_local_refresher;
else if (!str0casecmp("uas", sip->sip_session_expires->x_refresher))
ss->ss_refresher = server ? nua_local_refresher : nua_remote_refresher;
else if (!server)
return 0; /* XXX */
/* User preferences */
else if (refresher == nua_local_refresher)
ss->ss_refresher = nua_local_refresher;
else
ss->ss_refresher = nua_remote_refresher;
SU_DEBUG_7(("nua session: session expires in %u refreshed by %s (%s %s)\n",
ss->ss_session_timer,
ss->ss_refresher == nua_local_refresher ? "local" : "remote",
server ? sip->sip_request->rq_method_name : "response to",
server ? "request" : sip->sip_cseq->cs_method_name));
return 1;
}
static int session_timer_check_restart(nua_client_request_t *cr,
int status, char const *phrase,
sip_t const *sip)
{
if (cr->cr_usage && status == 422) {
nua_session_usage_t *ss = nua_dialog_usage_private(cr->cr_usage);
if (sip->sip_min_se && ss->ss_min_se < sip->sip_min_se->min_delta)
ss->ss_min_se = sip->sip_min_se->min_delta;
if (ss->ss_min_se > ss->ss_session_timer)
ss->ss_session_timer = ss->ss_min_se;
return nua_client_restart(cr, 100, "Re-Negotiating Session Timer");
}
return nua_base_client_check_restart(cr, status, phrase, sip);
}
static void
set_session_timer(nua_session_usage_t *ss)
{
nua_dialog_usage_t *du = nua_dialog_usage_public(ss);
if (ss == NULL)
return;
if (ss->ss_refresher == nua_local_refresher) {
ss->ss_timer_set = 1;
nua_dialog_usage_set_expires(du, ss->ss_session_timer);
}
else if (ss->ss_refresher == nua_remote_refresher) {
ss->ss_timer_set = 1;
nua_dialog_usage_set_expires(du, ss->ss_session_timer + 32);
nua_dialog_usage_reset_refresh(du);
}
else {
ss->ss_timer_set = 0;
nua_dialog_usage_set_expires(du, UINT_MAX);
nua_dialog_usage_reset_refresh(du);
}
}
static inline int
is_session_timer_set(nua_session_usage_t *ss)
{
return ss->ss_timer_set;
}
/* ---------------------------------------------------------------------- */
/* Automatic notifications from a referral */
......@@ -2787,7 +2644,6 @@ nh_referral_check(nua_handle_t *nh, tagi_t const *tags)
return 0;
}
static void
nh_referral_respond(nua_handle_t *nh, int status, char const *phrase)
{
......@@ -3099,8 +2955,17 @@ static int nua_update_client_request(nua_client_request_t *cr,
}
/* Add session timer headers */
if (session_timer_is_supported(nh))
use_session_timer(ss, 0, prefer_session_timer(nh), msg, sip);
session_timer_preferences(ss->ss_timer,
sip,
NH_PGET(nh, supported),
NH_PGET(nh, session_timer),
NUA_PISSET(nh->nh_nua, nh, session_timer),
NH_PGET(nh, refresher),
NH_PGET(nh, min_se));
if (session_timer_is_supported(ss->ss_timer))
session_timer_add_headers(ss->ss_timer, ss->ss_state < nua_callstate_ready,
msg, sip);
retval = nua_base_client_request(cr, msg, sip, NULL);
......@@ -3129,9 +2994,18 @@ static int nua_update_client_response(nua_client_request_t *cr,
assert(200 <= status);
if (ss && sip && status < 300) {
if (is_session_timer_set(ss)) {
init_session_timer(ss, sip, NH_PGET(nh, refresher));
set_session_timer(ss);
if (session_timer_is_supported(ss->ss_timer)) {
nua_server_request_t *sr;
for (sr = nh->nh_ds->ds_sr; sr; sr = sr->sr_next)
if (sr->sr_method == sip_method_invite ||
sr->sr_method == sip_method_update)
break;
if (!sr && (!du->du_cr || !du->du_cr->cr_orq)) {
session_timer_store(ss->ss_timer, sip);
session_timer_set(ss);
}
}
}
......@@ -3225,7 +3099,7 @@ int nua_update_server_init(nua_server_request_t *sr)
/* Do session timer negotiation */
if (request->sip_session_expires)
init_session_timer(ss, request, NH_PGET(nh, refresher));
session_timer_store(ss->ss_timer, request);
if (sr->sr_sdp) { /* Check for overlap */
nua_client_request_t *cr;
......@@ -3306,11 +3180,28 @@ int nua_update_server_respond(nua_server_request_t *sr, tagi_t const *tags)
}
}
if (ss->ss_refresher && 200 <= sr->sr_status && sr->sr_status < 300)
if (session_timer_is_supported(nh)) {
use_session_timer(ss, 1, 1, msg, sip);
set_session_timer(ss); /* XXX */
if (200 <= sr->sr_status && sr->sr_status < 300) {
session_timer_preferences(ss->ss_timer,
sip,
NH_PGET(nh, supported),
NH_PGET(nh, session_timer),
NUA_PISSET(nh->nh_nua, nh, session_timer),
NH_PGET(nh, refresher),
NH_PGET(nh, min_se));
if (ss && session_timer_is_supported(ss->ss_timer)) {
nua_server_request_t *sr0;
session_timer_add_headers(ss->ss_timer, 0, msg, sip);
for (sr0 = nh->nh_ds->ds_sr; sr0; sr0 = sr0->sr_next)
if (sr0->sr_method == sip_method_invite)
break;
if (!sr0 && (!sr->sr_usage->du_cr || !sr->sr_usage->du_cr->cr_orq))
session_timer_set(ss);
}
}
return nua_base_server_respond(sr, tags);
}
......@@ -3899,6 +3790,287 @@ int nua_server_retry_after(nua_server_request_t *sr,
return sr_status(sr, status, phrase);
}
/* ======================================================================== */
/* Session timer - RFC 4028 */