Commit 3e3b13dd authored by Pekka Pessi's avatar Pekka Pessi
Browse files

nua: try to cope if a re-INVITE nor its ACK contain SDP

Some SIP user-agents use INVITE without SDP offer to refresh session.
By default, NUA sends an offer in 200 OK to such an INVITE and expects
an answer back in ACK. Now nua tries to recover from such a Offer/Answer
protocol error.

Also, if NUTAG_REFRESH_WITHOUT_SDP(1) tag is used, and if the re-INVITE was
received without SDP, no SDP offer is sent in 200 OK.

Thanks for Anthony Minessale for reporting the problem.

darcs-hash:20081121105255-db55f-ec539b1dd5e1f8e7e09fbcbd4ec2694e70a0d8da.gz
parent 0a15213b
......@@ -1077,6 +1077,71 @@ START_TEST(call_2_3_2)
}
END_TEST
START_TEST(call_2_3_3)
{
nua_handle_t *nh;
struct message *response;
s2_case("2.3.3", "Handling re-INVITE without SDP gracefully",
"NUA receives INVITE, "
"re-INVITE without SDP (w/o NUTAG_REFRESH_WITHOUT_SDP(), "
"re-INVITE without SDP (using NUTAG_REFRESH_WITHOUT_SDP(), "
"sends BYE.");
nh = invite_to_nua(
TAG_END());
s2_request_to(dialog, SIP_METHOD_INVITE, NULL,
SIPTAG_USER_AGENT_STR("evil (evil) evil"),
TAG_END());
nua_respond(nh, SIP_200_OK, TAG_END());
fail_unless(s2_check_callstate(nua_callstate_completed));
response = s2_wait_for_response(200, SIP_METHOD_INVITE);
fail_if(!response);
s2_update_dialog(dialog, response);
fail_if(!response->sip->sip_content_type);
s2_free_message(response);
fail_if(s2_request_to(dialog, SIP_METHOD_ACK, NULL, TAG_END()));
fail_unless(s2_check_event(nua_i_ack, 200));
fail_unless(s2_check_callstate(nua_callstate_ready));
s2_fast_forward(10);
nua_set_hparams(nh, NUTAG_REFRESH_WITHOUT_SDP(1), TAG_END());
fail_unless(s2_check_event(nua_r_set_params, 200));
s2_request_to(dialog, SIP_METHOD_INVITE, NULL,
SIPTAG_USER_AGENT_STR("evil (evil) evil"),
TAG_END());
nua_respond(nh, SIP_200_OK, TAG_END());
fail_unless(s2_check_callstate(nua_callstate_completed));
response = s2_wait_for_response(200, SIP_METHOD_INVITE);
fail_if(!response);
s2_update_dialog(dialog, response);
fail_if(response->sip->sip_content_type);
s2_free_message(response);
fail_if(s2_request_to(dialog, SIP_METHOD_ACK, NULL, TAG_END()));
fail_unless(s2_check_event(nua_i_ack, 200));
fail_unless(s2_check_callstate(nua_callstate_ready));
bye_by_nua(nh, TAG_END());
nua_handle_destroy(nh);
}
END_TEST
TCase *session_timer_tcase(void)
{
......@@ -1085,6 +1150,7 @@ TCase *session_timer_tcase(void)
{
tcase_add_test(tc, call_2_3_1);
tcase_add_test(tc, call_2_3_2);
tcase_add_test(tc, call_2_3_3);
}
return tc;
}
......
......@@ -116,6 +116,7 @@ struct nua_server_request {
unsigned sr_offer_sent:2; /**< We have offered SDP (reliably, if >1) */
unsigned sr_answer_recv:1; /**< We have received SDP answer */
unsigned :0;
char const *sr_sdp; /**< SDP received from client */
......
......@@ -301,6 +301,7 @@ int nua_stack_init_instance(nua_handle_t *nh, tagi_t const *tags)
* NUTAG_PROXY() (aka NTATAG_DEFAULT_PROXY()) \n
* NUTAG_REFER_EXPIRES() \n
* NUTAG_REFER_WITH_ID() \n
* NUTAG_REFRESH_WITHOUT_SDP() \n
* NUTAG_REGISTRAR() \n
* NUTAG_RETRY_COUNT() \n
* NUTAG_SERVICE_ROUTE_ENABLE() \n
......@@ -422,6 +423,7 @@ int nua_stack_init_instance(nua_handle_t *nh, tagi_t const *tags)
* NUTAG_PROXY() (aka NTATAG_DEFAULT_PROXY()) \n
* NUTAG_REFER_EXPIRES() \n
* NUTAG_REFER_WITH_ID() \n
* NUTAG_REFRESH_WITHOUT_SDP() \n
* NUTAG_REGISTRAR() \n
* NUTAG_RETRY_COUNT() \n
* NUTAG_SERVICE_ROUTE_ENABLE() \n
......@@ -770,6 +772,10 @@ static int nhp_set_tags(su_home_t *home,
else if (tag == nutag_update_refresh) {
NHP_SET(nhp, update_refresh, value != 0);
}
/* NUTAG_REFRESH_WITHOUT_SDP(refresh_without_sdp) */
else if (tag == nutag_refresh_without_sdp) {
NHP_SET(nhp, refresh_without_sdp, value != 0);
}
/* NUTAG_ENABLEMESSAGE(message_enable) */
else if (tag == nutag_enablemessage) {
NHP_SET(nhp, message_enable, value != 0);
......@@ -1478,6 +1484,7 @@ int nua_stack_set_smime_params(nua_t *nua, tagi_t const *tags)
* NUTAG_PATH_ENABLE() \n
* NUTAG_REFER_EXPIRES() \n
* NUTAG_REFER_WITH_ID() \n
* NUTAG_REFRESH_WITHOUT_SDP() \n
* NUTAG_REGISTRAR() \n
* NUTAG_RETRY_COUNT() \n
* NUTAG_SERVICE_ROUTE_ENABLE() \n
......@@ -1639,6 +1646,7 @@ int nua_stack_get_params(nua_t *nua, nua_handle_t *nh, nua_event_t e,
TIF(NUTAG_MIN_SE, min_se),
TIFD(NUTAG_SESSION_REFRESHER, refresher),
TIF(NUTAG_UPDATE_REFRESH, update_refresh),
TIF(NUTAG_REFRESH_WITHOUT_SDP, refresh_without_sdp),
TIF(NUTAG_ENABLEMESSAGE, message_enable),
TIF(NUTAG_ENABLEMESSENGER, win_messenger_enable),
......
......@@ -82,6 +82,9 @@ struct nua_handle_preferences
/** no (preference), local or remote */
enum nua_session_refresher nhp_refresher;
unsigned nhp_update_refresh:1; /**< Use UPDATE to refresh */
/**< Accept refreshes without SDP */
unsigned nhp_refresh_without_sdp:1;
/* Messaging preferences */
unsigned nhp_message_enable : 1;
......@@ -164,6 +167,7 @@ struct nua_handle_preferences
unsigned nhb_min_se:1;
unsigned nhb_refresher:1;
unsigned nhb_update_refresh:1;
unsigned nhb_refresh_without_sdp:1;
unsigned nhb_message_enable:1;
unsigned nhb_win_messenger_enable:1;
unsigned nhb_message_auto_respond:1;
......@@ -179,9 +183,9 @@ struct nua_handle_preferences
unsigned nhb_keepalive:1;
unsigned nhb_keepalive_stream:1;
unsigned nhb_registrar:1;
unsigned :0; /* at most 32 bits before this point */
unsigned nhb_allow:1;
unsigned :0; /* at most 32 bits before this point */
unsigned nhb_supported:1;
unsigned nhb_allow_events:1;
......
......@@ -171,6 +171,9 @@ typedef struct nua_session_usage
/* Offer-Answer status */
char const *ss_oa_recv, *ss_oa_sent;
/**< Version of user SDP from latest successful O/A */
unsigned ss_sdp_version;
} nua_session_usage_t;
static char const Offer[] = "offer", Answer[] = "answer";
......@@ -964,11 +967,14 @@ static int nua_session_client_response(nua_client_request_t *cr,
/* XXX */
sdp = NULL;
}
else if (soa_activate(nh->nh_soa, NULL) < 0)
else if (soa_activate(nh->nh_soa, NULL) < 0) {
/* XXX - what about errors? */
LOG3("error activating media after");
else
}
else {
ss->ss_sdp_version = soa_get_user_version(nh->nh_soa);
LOG5("processed SDP");
}
}
else if (cr->cr_method != sip_method_invite) {
/* If non-invite request did not have offer, ignore SDP in response */
......@@ -1309,7 +1315,8 @@ int nua_invite_client_ack(nua_client_request_t *cr, tagi_t const *tags)
;
else if (nh->nh_soa && soa_is_complete(nh->nh_soa)) {
/* signal SOA that O/A round(s) is (are) complete */
soa_activate(nh->nh_soa, NULL);
if (soa_activate(nh->nh_soa, NULL) >= 0)
ss->ss_sdp_version = soa_get_user_version(nh->nh_soa);
}
else if (nh->nh_soa == NULL
/* NUA does not necessarily know dirty details */
......@@ -1741,7 +1748,8 @@ static int nua_prack_client_request(nua_client_request_t *cr,
}
else {
answer_sent = 1;
soa_activate(nh->nh_soa, NULL);
if (soa_activate(nh->nh_soa, NULL) >= 0)
ss->ss_sdp_version = soa_get_user_version(nh->nh_soa);
}
}
else if (nh->nh_soa == NULL) {
......@@ -1967,6 +1975,8 @@ static int
process_ack_or_cancel(nua_server_request_t *, nta_incoming_t *,
sip_t const *),
process_ack(nua_server_request_t *, nta_incoming_t *, sip_t const *),
process_ack_error(nua_server_request_t *sr, msg_t *ackmsg,
int status, char const *phrase, char const *reason),
process_cancel(nua_server_request_t *, nta_incoming_t *, sip_t const *),
process_timeout(nua_server_request_t *, nta_incoming_t *),
process_prack(nua_server_request_t *,
......@@ -2207,7 +2217,7 @@ int nua_invite_server_respond(nua_server_request_t *sr, tagi_t const *tags)
msg_t *msg = sr->sr_response.msg;
sip_t *sip = sr->sr_response.sip;
int reliable = 0, offer = 0, answer = 0, early_answer = 0, extra = 0;
int reliable = 0, maybe_answer = 0, offer = 0, answer = 0, extra = 0;
enter;
......@@ -2217,8 +2227,11 @@ int nua_invite_server_respond(nua_server_request_t *sr, tagi_t const *tags)
return nua_base_server_respond(sr, tags);
}
if (nua_invite_server_is_100rel(sr, tags)) {
reliable = 1, early_answer = 1;
if (200 <= sr->sr_status && sr->sr_status < 300) {
reliable = 1, maybe_answer = 1;
}
else if (nua_invite_server_is_100rel(sr, tags)) {
reliable = 1, maybe_answer = 1;
}
else if (!nh->nh_soa || sr->sr_status >= 300) {
if (sr->sr_neutral)
......@@ -2234,10 +2247,10 @@ int nua_invite_server_respond(nua_server_request_t *sr, tagi_t const *tags)
SOATAG_USER_SDP_STR_REF(user_sdp_str),
TAG_END());
early_answer = user_sdp || user_sdp_str;
maybe_answer = user_sdp || user_sdp_str;
}
else {
early_answer = NH_PGET(nh, early_answer);
maybe_answer = NH_PGET(nh, early_answer);
}
if (!nh->nh_soa) {
......@@ -2259,11 +2272,12 @@ int nua_invite_server_respond(nua_server_request_t *sr, tagi_t const *tags)
tagi_t const *t = tl_find_last(tags, nutag_include_extra_sdp);
extra = t && t->t_value;
}
else if (sr->sr_offer_recv && !sr->sr_answer_sent && early_answer) {
else if (sr->sr_offer_recv && !sr->sr_answer_sent && maybe_answer) {
/* Generate answer */
if (soa_generate_answer(nh->nh_soa, NULL) >= 0 &&
soa_activate(nh->nh_soa, NULL) >= 0) {
answer = 1; /* signal that O/A answer sent (answer to invite) */
/* ss_sdp_version is updated only after answer is sent reliably */
}
/* We have an error! */
else if (sr->sr_status >= 200) {
......@@ -2288,12 +2302,16 @@ int nua_invite_server_respond(nua_server_request_t *sr, tagi_t const *tags)
/* 1xx - we don't have to send answer */
}
}
else if (sr->sr_offer_recv && sr->sr_answer_sent == 1 && early_answer) {
else if (sr->sr_offer_recv && sr->sr_answer_sent == 1 && maybe_answer) {
/* The answer was sent unreliably, keep sending it */
answer = 1;
}
else if (!sr->sr_offer_recv && !sr->sr_offer_sent && reliable) {
/* Generate offer */
if (200 <= sr->sr_status && nua_callstate_ready <= ss->ss_state &&
NH_PGET(nh, refresh_without_sdp))
/* This is a re-INVITE without SDP - do not try to send offer in 200 */;
else
/* Generate offer */
if (soa_generate_offer(nh->nh_soa, 0, NULL) < 0)
sr->sr_status = soa_error_as_sip_response(nh->nh_soa, &sr->sr_phrase);
else
......@@ -2307,6 +2325,9 @@ int nua_invite_server_respond(nua_server_request_t *sr, tagi_t const *tags)
sr->sr_offer_sent = 1 + reliable, ss->ss_oa_sent = Offer;
else if (answer)
sr->sr_answer_sent = 1 + reliable, ss->ss_oa_sent = Answer;
if (answer && reliable)
ss->ss_sdp_version = soa_get_user_version(nh->nh_soa);
}
if (reliable && sr->sr_status < 200) {
......@@ -2340,16 +2361,15 @@ static
int nua_invite_server_is_100rel(nua_server_request_t *sr, tagi_t const *tags)
{
nua_handle_t *nh = sr->sr_owner;
sip_t const *sip = sr->sr_response.sip;
sip_require_t *require = sr->sr_request.sip->sip_require;
sip_supported_t *supported = sr->sr_request.sip->sip_supported;
if (sr->sr_status >= 200)
return 1;
return 0;
else if (sr->sr_status == 100)
return 0;
if (sip_has_feature(sip->sip_require, "100rel"))
if (sip_has_feature(sr->sr_response.sip->sip_require, "100rel"))
return 1;
if (require == NULL && supported == NULL)
......@@ -2473,7 +2493,7 @@ int process_ack_or_cancel(nua_server_request_t *sr,
*
* @END_NUA_EVENT
*/
static
int process_ack(nua_server_request_t *sr,
nta_incoming_t *irq,
sip_t const *sip)
......@@ -2489,7 +2509,6 @@ int process_ack(nua_server_request_t *sr,
if (sr->sr_offer_sent && !sr->sr_answer_recv) {
char const *sdp;
size_t len;
int error;
if (session_get_description(sip, &sdp, &len))
recv = Answer;
......@@ -2499,32 +2518,45 @@ int process_ack(nua_server_request_t *sr,
ss->ss_oa_recv = recv;
}
if (nh->nh_soa == NULL)
if (nh->nh_soa == NULL)
;
else if (recv == NULL ||
soa_set_remote_sdp(nh->nh_soa, NULL, sdp, len) < 0 ||
soa_process_answer(nh->nh_soa, NULL) < 0 ||
soa_activate(nh->nh_soa, NULL) < 0) {
else if (recv == NULL ) {
if (ss->ss_state >= nua_callstate_ready &&
soa_get_user_version(nh->nh_soa) == ss->ss_sdp_version &&
soa_process_reject(nh->nh_soa, NULL) >= 0) {
url_t const *m;
/* The re-INVITE was a refresh and re-INVITEr ignored our offer */
ss->ss_oa_sent = NULL;
if (sr->sr_request.sip->sip_contact)
m = sr->sr_request.sip->sip_contact->m_url;
else
m = sr->sr_request.sip->sip_from->a_url;
SU_DEBUG_3(("nua(%p): re-INVITEr ignored offer in our %u response "
"(Contact: <" URL_PRINT_FORMAT ">)\n",
(void *)nh, sr->sr_status, URL_PRINT_ARGS(m)));
if (sr->sr_request.sip->sip_user_agent)
SU_DEBUG_3(("nua(%p): re-INVITE: \"User-Agent: %s\"\n", (void *)nh,
sr->sr_request.sip->sip_user_agent->g_string));
}
else
return process_ack_error(sr, msg, 488, "Offer-Answer error",
"SIP;cause=488;text=\"No answer to offer\"");
}
else if (soa_set_remote_sdp(nh->nh_soa, NULL, sdp, len) >= 0 &&
soa_process_answer(nh->nh_soa, NULL) >= 0 &&
soa_activate(nh->nh_soa, NULL) >= 0) {
ss->ss_sdp_version = soa_get_user_version(nh->nh_soa);
}
else {
int status; char const *phrase, *reason;
status = soa_error_as_sip_response(nh->nh_soa, &phrase);
reason = soa_error_as_sip_reason(nh->nh_soa);
nua_stack_event(nh->nh_nua, nh, msg,
nua_i_ack, status, phrase, NULL);
nua_stack_event(nh->nh_nua, nh, NULL,
nua_i_media_error, status, phrase, NULL);
ss->ss_reporting = 1; /* We report terminated state here if BYE fails */
error = nua_client_create(nh, nua_r_bye, &nua_bye_client_methods, NULL);
ss->ss_reporting = 0;
signal_call_state_change(nh, ss, 488, "Offer-Answer Error",
error
? nua_callstate_terminated
: nua_callstate_terminating);
return 0;
return process_ack_error(sr, msg, status, phrase, reason);
}
}
......@@ -2540,6 +2572,38 @@ int process_ack(nua_server_request_t *sr,
return 0;
}
static int
process_ack_error(nua_server_request_t *sr,
msg_t *ackmsg,
int status,
char const *phrase,
char const *reason)
{
nua_handle_t *nh = sr->sr_owner;
nua_session_usage_t *ss = nua_dialog_usage_private(sr->sr_usage);
int error;
nua_stack_event(nh->nh_nua, nh, ackmsg,
nua_i_ack, status, phrase, NULL);
nua_stack_event(nh->nh_nua, nh, NULL,
nua_i_media_error, status, phrase, NULL);
if (reason) ss->ss_reason = reason;
ss->ss_reporting = 1;
error = nua_client_create(nh, nua_r_bye, &nua_bye_client_methods, NULL);
ss->ss_reporting = 0;
signal_call_state_change(nh, ss,
488, "Offer-Answer Error",
/* We report terminated state if BYE failed */
error
? nua_callstate_terminated
: nua_callstate_terminating);
return 0;
}
/** @NUA_EVENT nua_i_cancel
*
* Incoming INVITE has been cancelled by the client.
......@@ -2809,8 +2873,10 @@ int nua_prack_server_report(nua_server_request_t *sr, tagi_t const *tags)
signal_call_state_change(nh, ss,
status, phrase,
ss->ss_state);
if (nh->nh_soa)
if (nh->nh_soa) {
soa_activate(nh->nh_soa, NULL);
ss->ss_sdp_version = soa_get_user_version(nh->nh_soa);
}
}
if (status < 200 || 300 <= status)
......@@ -3469,6 +3535,7 @@ int nua_update_server_respond(nua_server_request_t *sr, tagi_t const *tags)
}
else {
sr->sr_answer_sent = 1, ss->ss_oa_sent = Answer;
ss->ss_sdp_version = soa_get_user_version(nh->nh_soa);
}
}
......
......@@ -182,7 +182,8 @@
* UPDATE requests.
* Settings:
* - NUTAG_MIN_SE(), NUTAG_SESSION_REFRESHER(),
* NUTAG_SESSION_TIMER(), NUTAG_UPDATE_REFRESH()
* NUTAG_SESSION_TIMER(), NUTAG_UPDATE_REFRESH(),
* NUTAG_REFRESH_WITHOUT_SDP(),
* - "timer" in NUTAG_SUPPORTED()/SIPTAG_SUPPORTED()
* Specifications:
* - @RFC4028
......@@ -954,6 +955,11 @@ tag_typedef_t nutag_invite_timer = UINTTAG_TYPEDEF(invite_timer);
* @SessionExpires header sends a re-INVITE requests (or an UPDATE
* request if NUTAG_UPDATE_REFRESH(1) parameter tag has been set).
*
* Some SIP user-agents use INVITE without SDP offer to refresh session.
* By default, NUA sends an offer in 200 OK to such an INVITE and expects
* an answer back in ACK. If NUTAG_REFRESH_WITHOUT_SDP(1) tag is used,
* no SDP offer is sent in 200 OK if re-INVITE was received without SDP.
*
* @par When to Use NUTAG_SESSION_TIMER()?
*
* The session time extension is enabled ("timer" feature tag is included in
......@@ -1106,7 +1112,7 @@ tag_typedef_t nutag_session_refresher = INTTAG_TYPEDEF(session_refresher);
* Corresponding tag taking reference parameter is NUTAG_UPDATE_REFRESH_REF().
*
* @sa #nua_r_update, NUTAG_SESSION_TIMER(), NUTAG_MIN_SE_REF(),
* NUTAG_UPDATE_REFRESH(), @RFC4028, @SessionExpires, @MinSE
* NUTAG_SESSION_REFRESHER(), @RFC4028, @SessionExpires, @MinSE
*/
tag_typedef_t nutag_update_refresh = BOOLTAG_TYPEDEF(update_refresh);
......@@ -1115,6 +1121,45 @@ tag_typedef_t nutag_update_refresh = BOOLTAG_TYPEDEF(update_refresh);
*/
/**@def NUTAG_REFRESH_WITHOUT_SDP(x)
*
* Do not send offer in response if re-INVITE was received without SDP.
*
* Some SIP user-agents use INVITE without SDP offer to refresh session.
* By default, NUA sends an offer in 200 OK to such an INVITE and expects
* an answer back in ACK.
*
* If NUTAG_REFRESH_WITHOUT_SDP(1) tag is used, no SDP offer is sent in 200
* OK if re-INVITE was received without SDP.
*
* @par Used with
* nua_handle(), nua_invite(), nua_update(), nua_respond() \n
* nua_set_params() or nua_set_hparams() \n
* nua_get_params() or nua_get_hparams()
*
* See nua_set_hparams() for a complete list of all the nua operations that
* accept this tag.
*
* @par Parameter type
* int (boolean: nonzero is true, zero is false)
*
* @par Values
* - 1 (true, do not try to send offer in response to re-INVITE)
* - 0 (false, always use SDP offer-answer in re-INVITEs)
*
* Corresponding tag taking reference parameter is NUTAG_REFRESH_WITHOUT_SDP_REF().
*
* @sa #nua_r_update, NUTAG_SESSION_TIMER(), NUTAG_MIN_SE_REF(),
* NUTAG_SESSION_REFRESHER(), NUTAG_UPDATE_REFRESH(), @RFC4028,
* @SessionExpires, @MinSE
*/
tag_typedef_t nutag_refresh_without_sdp = BOOLTAG_TYPEDEF(refresh_without_sdp);
/**@def NUTAG_REFRESH_WITHOUT_SDP_REF(x)
* Reference tag for NUTAG_REFRESH_WITHOUT_SDP_REF().
*/
/**@def NUTAG_REFER_EXPIRES()
*
* Default lifetime for implicit subscriptions created by REFER.
......
......@@ -183,6 +183,12 @@ SOFIAPUBVAR tag_typedef_t nutag_update_refresh;
#define NUTAG_UPDATE_REFRESH_REF(x) nutag_update_refresh_ref, tag_bool_vr((&(x)))
SOFIAPUBVAR tag_typedef_t nutag_update_refresh_ref;
#define NUTAG_REFRESH_WITHOUT_SDP(x) nutag_refresh_without_sdp, tag_bool_v((x))
SOFIAPUBVAR tag_typedef_t nutag_refresh_without_sdp;
#define NUTAG_REFRESH_WITHOUT_SDP_REF(x) \
nutag_refresh_without_sdp_ref, tag_bool_vr((&(x)))
SOFIAPUBVAR tag_typedef_t nutag_refresh_without_sdp_ref;
#define NUTAG_AUTOALERT(x) nutag_autoalert, tag_bool_v(x)
SOFIAPUBVAR tag_typedef_t nutag_autoalert;
#define NUTAG_AUTOALERT_REF(x) nutag_autoalert_ref, tag_bool_vr(&(x))
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment