Processing Call Redirection (3xx) Response in PJSIP
This article describes SIP redirection support in PJSIP, that is implemented by ticket #10 in release 1.0.1 (December 2008).
The redirection in this article is about processing SIP redirect (3xx) response in outgoing calls, and not about sending redirection/3xx response. Sending 3xx response in PJSUA-LIB is easy; just use pjsua_call_hangup() and give the new targets in Contact header in msg_data parameter.
Table of Contents
- Implementation Details and Usage Scenarios
- Changes to Application
- Open Issues
- Alternative Way to Handle Redirect Response
The redirected call handling is implemented by ticket #10. The objective statements of this work are:
- must be implemented in PJSIP level rather than PJSUA-LIB level, to allow the usage on non-PJSUA-LIB based applications.
- must allow application to accept or reject the redirection request on a per-target basis (by means of callback)
- must allow application to defer the decision to accept or reject the redirection request (for example, to ask for user confirmation to accept or reject the target)
- must be able to follow multiple targets in 3xx response (and targets that are returned in 3xx response of subsequent INVITE).
- must be able to remember which targets have been sent INVITE to and not retry the INVITE to these targets
- must be able to prioritize targets based on the q-value
- the subsequent INVITE requests sent after the 3xx response must have the same To, From, and Call-ID as used by the original INVITE. While RFC 3261 only put this at RECOMMENDED strength (see section 22.214.171.124), in practice there are many servers out there that relies on this property.
Implementation Details and Usage Scenarios
A new callback was added to the invite session callback (pjsip_inv_callback) in pjsip-ua library:
pjsip_redirect_op (*on_redirected)(pjsip_inv_session *inv, const pjsip_uri *target, const pjsip_event *e);
Application MUST implement this callback if it wants the invite session to manage redirection. If this callback is not implemented, the invite session will be disconnected upon receipt of 3xx response.
This callback is called when the invite session is being redirected to a new target. If there are multiple targets, then the callback will be called multiple times, once for each target. This callback will be called on these events:
- a 3xx response containing usable Contact header(s) is received
- a 4xx or 5xx response to subsequent INVITE request is received, and the invite session has at least one more valid target to try. This happens for example:
- when two Contact headers are received in the 3xx response for the original/first INVITE,
- A second INVITE is sent to the first alternate target, and 4xx/5xx response is received for this second INVITE.
- The callback then will be called before the invite session tries to send the third INVITE to the second alternate target.
The arguments for this callback are as follows:
- The invite session
- The current target URI to which the INVITE will be sent to.
- The event that triggers this callback to be called. This could be of type PJSIP_EVENT_TSX_STATE containing receipt of 3xx, 4xx, or 5xx response, or any other type depending on what is passed to pjsip_inv_process_redirect() below, or of type PJSIP_EVENT_USER if this callback is called by pjsip_inv_process_redirect() below and the event argument to that function is NULL, or NULL.
The return value specifies the action to be performed, and it must be set to one of the following:
- PJSIP_REDIRECT_ACCEPT: immediately accept the redirection (default value upon callback entry). The session will immediately resend INVITE request to the target upon return of this callback.
- PJSIP_REDIRECT_REJECT: immediately reject this target. The session will continue retrying with next target if present (and it will call this callback again for the next target), or immediately disconnect the call if there is no more target to try (notifying the on_state_changed() callback as usual).
- PJSIP_REDIRECT_STOP: stop the whole redirection process and immediately disconnect the call, regardless on whether there is other target to try or not (notifying the on_state_changed() callback as usual).
- PJSIP_REDIRECT_PENDING: set to this value if no decision can be made immediately (for example to request user approval). Application then MUST call pjsip_inv_process_redirect() (see below) to either accept or reject the redirection upon getting user decision.
A new function for the invite session (pjsip_inv_session) was also added:
PJ_DECL(pj_status_t) pjsip_inv_process_redirect(pjsip_inv_session *inv, pjsip_redirect_op cmd, pjsip_event *e);
This function allows the application to notify the invite session about the result of user confirmation.
The event (e) argument is the event that will be passed to on_redirected() callback when the user rejects the redirection and there is another target to try. When NULL is passed to this argument, then the invite session will create PJSIP_EVENT_USER event with NULL values to pass to the callback.
The redirection usage scenarios will be explained below.
Accept Redirection to This Target
Return PJSIP_REDIRECT_ACCEPT from the callback to accept the redirection to this target. And INVITE session will be sent immediately to the target.
Reject Redirection to This Target
Return PJSIP_REDIRECT_REJECT from the callback to reject the redirection to this target. If there is another target to try, then the callback will be called again with this next target, otherwise the invite session will be disconnected immediately.
Return PJSIP_REDIRECT_STOP from the callback to stop the redirection process and disconnect the call immediately, regardless of whether there are more targets to try.
Defer the Decision
Return PJSIP_REDIRECT_PENDING from the callback to tell the invite session that a decision cannot be made that this time (for example to ask for user approval), and the application will notify the invite session about the decision later.
Once the application gets the user approval (or disapproval), it MUST call pjsip_inv_process_redirect() function to notify the session about the decision. It may accept or reject the target, or stop the redirection altogether by setting the appropriate value to the cmd argument. It must not set PJSIP_REDIRECT_PENDING to this argument.
Failure to call pjsip_inv_process_redirect() will cause the invite session to be kept alive indefinitely until the library is shutdown.
When the pjsip_inv_process_redirect() function is called for the next target in the context of this function (that is when this function is called with reject command and next target is selected, hence the callback is called), the event (e) argument passed to this function will be passed down to the callback. And similarly when the disconnect callback is called. If NULL is given to the event argument of this function, this function will create a PJSIP_EVENT_USER event with NULL values, to be passed to the callbacks.
Because of this, application MUST be prepared to handle these type of events in both the on_redirected() and on_state_changed() callbacks. Traditionally only PJSIP_EVENT_TSX_STATE event is passed to on_state_changed() callback.
Similar callback and function were added to PJSUA-LIB:
pjsip_redirect_op (*on_call_redirected)(pjsua_call_id call_id, const pjsip_uri *target, const pjsip_event *e); PJ_DECL(pj_status_t) pjsua_call_process_redirect(pjsua_call_id call_id, pjsip_redirect_op cmd);
Please see the PJSIP section above on how to use this feature.
The following additions were made to pjsua:
- new command line argument "--accept-redirect=N", with valid values for N:
- 0: reject/stop,
- 1: follow automatically (default),
- 2: ask
- the default behavior is to follow redirection automatically
- when --accept-redirect is set to 2 (ask), user can enter 'Ra', 'Rr', or 'Rd' to accept, reject, or stop/disconnect the redirection.
Changes to Application
Type of Event in PJSIP's on_state_changed() and PJSUA-LIB's on_call_state() callbacks
It's good to remind here that while these callbacks traditionally only emits PJSIP_EVENT_TSX_STATE event, technically it can emit other types of events. Now especially with the pending redirection feature, a PJSIP_EVENT_USER event may be passed to these callbacks. Hence application should check for the event type before accessing the event structure.
3xx Response after 183
When 3xx response is received after 183 with SDP response is received, the invite session will treat the SDP answer in the subsequent INVITE as an offer rather than answer. This is because the SDP has been negotiated in the previous INVITE offer/answer.
While it is uncommon to send 183 before 3xx, this is a bug.
Library shutdown with a pending redirection causes assertion failure
Assertion will be triggered in the invite session when the library is shutdown while the invite session is still waiting for pjsip_inv_process_redirect function to be called.
While this is caused by application not following the requirement to call pjsip_inv_process_redirect(), nevertheless the library should be robust enough to handle this, so this will be fixed.
Alternative Way to Handle Redirect Response
As alternative to the mechanism described in this article, application can also handle redirection call with the following approach:
- extract the URI in the Contact header(s) in the SIP redirect/3xx response
- for each URI, create a new invite session/call and send INVITE to this target, trying the targets sequentially and sorting the preference based on the q-value of the Contact.
However with this approach, each call will have a different Call-ID, From, and To headers, hence the applicability of this approach probably is more limited (it depends on whether the server expects the client to maintain the Call-ID, From, and To headers).