wiki:IPAddressChange

IP Address change and Access Point Reconnection Issues

Table of Contents

  1. Problem description
  2. Issues and solution related to IP address change
    1. Approach 1: Restart everything
    2. Approach 2: Selective update
      1. Account Contact URI update
      2. Media addresses update
      3. Call in progress issues
  3. Handling IP Change on iPhone
  4. Disconnection detection

This article describes some issues and their corresponding solutions related to access point disconnection, reconnection, IP address change, and how to handle these events in your PJSIP applications. The general issues related to the discussion will be explained, along with some specific issues to Symbian applications.


Problem description

IP address change and/or access point disconnection and reconnection are scenarios that need to be handled in mobile applications. Few issues or scenarios related to this for example are:

  • user moves outside the range of a Wi-Fi access point (AP) and lost the connection
  • user moves outside the range of one AP and reconnect to another
  • the handset may get new IP address if user reconnects to different AP

Each of the scenarios above may need different handling in the application.


Issues and solution related to IP address change

When the connection is reconnected, the handset may get different IP address than what it previously got. There are few ramifications of this, for example if PJSUA-LIB is used:

  • the SIP registration needs to be updated with a new Contact URI
  • the account URI also needs to be updated
  • media addresses need to be updated
  • if there is ongoing dialog, the remote party needs to be informed with new Contact URI as well as new media (RTP/RTCP) addresses.

Note that the monitoring of connection/interface status is outside the scope of PJSIP, so the application must implement this itself (for example using connection progress monitor in Symbian). Having said that, PJSIP does have some capability to detect some IP address change scenarios, for example by monitoring the IP address in SIP REGISTER response or in STUN Binding response when ICE transport is used and STUN is enabled.

Once the application has detected that the IP interface address has changed, there are two solutions to inform PJSIP about this.


Approach 1: Restart everything

The most straightforward solution is of course to restart everything, which means in pjsua terms to call pjsua_destroy() and followed by pjsua_create(), pjsua_init(), and so on. While this solution may sound crude, it is the easiest to do and as will be explained later it is not considerably worse then the more refined alternative.


Approach 2: Selective update

Alternatively there may be a way to allow the stack to continue to run, updating the address information when necessary. This approach will require some specific features to be used, as well as some actions by the application when it detects that the IP address has changed.

The specific configuration and tasks will be explained below. Note we assume that the SIP and media sockets are bound to INADDR_ANY (0.0.0.0) and not to a specific interface IP address (this is the default behavior).

Update for 2.7 or above: application can apply this selective update approach with much simpler way, please check ticket #2041.

Account Contact URI update

Task:
The account URI needs to be updated with the new address, and re-registration is necessary to inform the registrar about the new URI.
Description:
PJSUA-LIB has the capability to detect the (SIP) IP address change based on the response of REGISTER request and automatically update the registration with the correct IP if it detects that the IP/port seen by the server is different than the address specified in the Contact URI. This feature is enabled by default, via the pjsua_acc_config.allow_contact_rewrite setting.

So the solution is simply to trigger the re-registration by calling pjsua_acc_set_registration() function (after the new connection is up of course). The PJSUA-LIB will send re-REGISTER request, check the IP address/port in the response, and re-REGISTER again and update the account URI as necessary.

Media addresses update

Task:
The media (RTP/RTCP) addresses in PJSUA-LIB are normally determined during PJSUA-LIB startup, hence they need to be updated with the new address.
Description:
If ICE media transport is used, and STUN is enabled on the media transport, then the media transport will automatically update its publicly mapped IP address from the STUN Binding response. The transport should send STUN Binding request periodically (approximately every 15 seconds) as NAT keep-alive mechanism, so the address change will be detected by the transport automatically during this operation.

Note that at present there is no API to explicitly request the ICE media transport to initiate STUN Binding request immediately.

If ICE is not used, then at present there is no mechanism to update the IP address of media transport, nor the media transport will update its address even when STUN is used. ~The only solution would be to recreate the media transports and supply them to PJSUA-LIB with pjsua_media_transports_attach().~ You need to call pjsua_media_transports_create() to recreate the media transports.

Update: since 2.0, media transports are created on demand, so recreating media transports is no longer necessary, see On Demand Media Transport.

Call in progress issues

Task 1:
Dialog's Contact URI needs to be updated.
Description:
To update the dialog's Contact URI, application can use the flag PJSUA_CALL_UPDATE_CONTACT when calling the API pjsua_call_reinvite().
Task 2:
Changing of RTP/RTCP media addresses of ongoing call
Description:
If ICE is used, then new STUN srflx address will be signaled in updated SDP offer, as long as:
  • ICE media transport has detected that the IP address has changed (via the keep-alive above), and
  • the media was previously inactive, since if media has been active (hence ICE session is active), the SDP will contain only the used candidates and not all the list of candidates.

Alternatively, we may not need to inform the new RTP/RTCP address at all. If the remote media endpoint has the capability to switch its RTP/RTCP transmission to the source address of the RTP/RTCP packets (note: PJMEDIA has this capability), then it should automatically switch its destination address to our new address, provided that the source address of our RTP/RTCP packets (as viewed by the remote peer) have indeed changed.

  • Note:
    • by default we bind transports to INADDRANY/0.0.0.0, so when sending outgoing (UDP) packets, we rely on the OS to select the correct interface for us, based on what interfaces are currently online and the OS's internal routing table. In other words, we just call sendto() and let the OS "do the right thing". In case of IP address change, we are also relying on the OS to switch the interface from one interface to the new one for our UDP transmissions.

Starting from release 2.6, in ticket #1982, we add a feature which will allow a call to reinitialize its media. To do this, you can specify the call flag PJSUA_CALL_REINIT_MEDIA when calling the API pjsua_call_reinvite() (if you want to update the contact as well (see Task 1 above), use both flags PJSUA_CALL_REINIT_MEDIA and PJSUA_CALL_UPDATE_CONTACT).


Handling IP Change on iPhone

TCP is preferred on iPhone because of the background feature, but it has been reported that simply re-registering after an IP address change is detected may not work, presumably because the TCP socket itself is already in bad state and is unable to communicate anymore. The following steps can be used to perform re-registration with a new TCP transport. For a demonstration, please apply attachment:iphone_ip_change_pjsip_1_12.patch at the bottom of this page. The patch is tested on version pjsip-1.12.

  1. You need to implement reachability API (sample is provided in the patch). With this API we can monitor the access point connection status and perform re-registration when necessary.
  2. We need to keep track of which transport is being used by the registration, by implementing the on_reg_state2() and on_transport_state() callbacks. Add reference counter to it to prevent other from deleting the transport while we're referencing it (it shouldn't happen while the registration is active, but just in case). Sample code:
    static pjsua_acc_id the_acc_id;
    static pjsip_transport *the_transport;
    
    static void on_reg_state2(pjsua_acc_id acc_id, pjsua_reg_info *info)
    {
       struct pjsip_regc_cbparam *rp = info->cbparam;
    
     
        ...
        if (acc_id != the_acc_id)
            return;
    
        if (rp->code/100 == 2 && rp->expiration > 0 && rp->contact_cnt > 0) {
    	/* Registration success */
    	if (the_transport) {
    	    PJ_LOG(3,(THIS_FILE, "xxx: Releasing transport.."));
    	    pjsip_transport_dec_ref(the_transport);
    	    the_transport = NULL;
    	}
    	/* Save transport instance so that we can close it later when
    	 * new IP address is detected.
    	 */
    	PJ_LOG(3,(THIS_FILE, "xxx: Saving transport.."));
    	the_transport = rp->rdata->tp_info.transport;
    	pjsip_transport_add_ref(the_transport);
        } else {
    	if (the_transport) {
    	    PJ_LOG(3,(THIS_FILE, "xxx: Releasing transport.."));
    	    pjsip_transport_dec_ref(the_transport);
    	    the_transport = NULL;
    	}
        }
        ...
    }
    
    /* Also release the transport when it is disconnected (see ticket #1482) */
    static void on_transport_state(pjsip_transport *tp, 
    			       pjsip_transport_state state,
    			       const pjsip_transport_state_info *info)
    {
        ...
        if (state == PJSIP_TP_STATE_DISCONNECTED && the_transport == tp) {
            PJ_LOG(3,(THIS_FILE, "xxx: Releasing transport.."));
    	pjsip_transport_dec_ref(the_transport);
    	the_transport = NULL;
        }
        ...
    }
    
    New: Starting from release 2.4, we have a new callback on_reg_started2() (for more details, please refer to ticket #1825), which allows application to get the transport much earlier, i.e. when the registration process begins. This allows application to close the transport before connecting state. So, you can choose to split the implementation in on_reg_state2() above into two callbacks on_reg_started2() and on_reg_state2():
    static void on_reg_started2(pjsua_acc_id acc_id, pjsua_reg_info *info)
    {
        pjsip_regc_info regc_info;
    
        pjsip_regc_get_info(info->regc, &regc_info);
     
        ...
        if (acc_id != the_acc_id)
            return;
    
        if (the_transport != regc_info.transport) {
    	if (the_transport) {
    	    PJ_LOG(3,(THIS_FILE, "xxx: Releasing transport.."));
    	    pjsip_transport_dec_ref(the_transport);
    	    the_transport = NULL;
    	}
    	/* Save transport instance so that we can close it later when
    	 * new IP address is detected.
    	 */
    	PJ_LOG(3,(THIS_FILE, "xxx: Saving transport.."));
    	the_transport = regc_info.transport;
    	pjsip_transport_add_ref(the_transport);
         }
    }
    
    static void on_reg_state2(pjsua_acc_id acc_id, pjsua_reg_info *info)
    {
       struct pjsip_regc_cbparam *rp = info->cbparam;
    
     
        ...
        if (acc_id != the_acc_id)
            return;
    
        if (rp->code/100 == 2 && rp->expiration > 0 && rp->contact_cnt > 0) {
    	/* We already saved the transport instance */
        } else {
    	if (the_transport) {
    	    PJ_LOG(3,(THIS_FILE, "xxx: Releasing transport.."));
    	    pjsip_transport_dec_ref(the_transport);
    	    the_transport = NULL;
    	}
        }
        ...
    }
    
  1. When IP address change is detected: a) close the TCP transport that we saved in step 2) above, and b) send unregistration. Sample code:
    static void ip_change()
    {
        pj_status_t status;
    
        PJ_LOG(3,(THIS_FILE, "xxx: IP change.."));
    
        if (the_transport) {
            status = pjsip_transport_shutdown(the_transport);
            if (status != PJ_SUCCESS)
        	    PJ_PERROR(1,(THIS_FILE, status, "xxx: pjsip_transport_shutdown() error"));
            pjsip_transport_dec_ref(the_transport);
            the_transport = NULL;
        }
    
        status = pjsua_acc_set_registration(the_acc_id, PJ_FALSE);
        if (status != PJ_SUCCESS)
            PJ_PERROR(1,(THIS_FILE, status, "xxx: pjsua_acc_set_registration(0) error"));
    }
    
  1. And finally, once unregistration in 2b) above is complete, re-register (with TCP).

Disconnection detection

For TCP/TLS, you can enable socket keep-alive parameters via pjsua_transport_config.sockopt_params to detect if there is a disconnection. The below code has been reported to work on iOS (30s timeout).

static int time=30, probe=5, interval=1, enable=1;
cfg.sockopt_params.cnt = 4;
cfg.sockopt_params.options[0].level     = pj_SOL_TCP();
cfg.sockopt_params.options[0].optname   = TCP_KEEPIDLE;
cfg.sockopt_params.options[0].optval    = &time;
cfg.sockopt_params.options[0].optlen    = sizeof(time);

cfg.sockopt_params.options[1].level     = pj_SOL_TCP();
cfg.sockopt_params.options[1].optname   = TCP_KEEPINTVL;
cfg.sockopt_params.options[1].optval    = &interval;
cfg.sockopt_params.options[1].optlen    = sizeof(interval);

cfg.sockopt_params.options[2].level     = pj_SOL_TCP();
cfg.sockopt_params.options[2].optname   = TCP_KEEPCNT;
cfg.sockopt_params.options[2].optval    = &probe;
cfg.sockopt_params.options[2].optlen    = sizeof(probe);

cfg.sockopt_params.options[3].level     = pj_SOL_SOCKET();
cfg.sockopt_params.options[3].optname   = SO_KEEPALIVE;
cfg.sockopt_params.options[3].optval    = &enable;
cfg.sockopt_params.options[3].optlen    = sizeof(enable);
Last modified 4 years ago Last modified on Oct 11, 2019 3:37:36 AM

Attachments (1)

Download all attachments as: .zip