Changes between Version 20 and Version 21 of Symbian_AP_Reconnection


Ignore:
Timestamp:
Feb 25, 2011 12:15:09 AM (13 years ago)
Author:
bennylp
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Symbian_AP_Reconnection

    v20 v21  
    1 {{{ 
    2 #!html 
    3 <!-- MAIN TABLE START --> 
    4 <table border=0 width="90%" align="center"><tr><td> 
    5 }}} 
    61 
    7 = IP Address change and Access Point Reconnection Issues = 
    8  
    9 '''Table of Contents''' 
    10 [[PageOutline(2-3,,inline)]] 
    11  
    12 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. 
    13  
    14 [[BR]] 
    15  
    16 == Problem description == 
    17  
    18 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: 
    19  - user moves outside the range of a Wi-Fi access point (AP) and lost the connection 
    20  - user moves outside the range of one AP and reconnect to another 
    21  - the handset may get new IP address if user reconnects to different AP 
    22  
    23 Each of the scenarios above may need different handling in the application. 
    24  
    25 [[BR]] 
    26  
    27 == Issues and solution related to IP address change == 
    28  
    29 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: 
    30  - the SIP registration needs to be updated with a new Contact URI 
    31  - the account URI also needs to be updated 
    32  - media addresses need to be updated 
    33  - if there is ongoing dialog, the remote party needs to be informed with new Contact URI as well as new media (RTP/RTCP) addresses. 
    34  
    35 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. 
    36  
    37 Once the application has detected that the IP interface address has changed, there are two solutions to inform PJSIP about this. 
    38  
    39 [[BR]] 
    40  
    41 === Approach 1: Restart everything === 
    42  
    43 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. 
    44  
    45 [[BR]] 
    46  
    47 === Approach 2: Selective update === 
    48  
    49 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.  
    50  
    51 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). 
    52  
    53  
    54 ==== Account Contact URI update ==== 
    55  
    56  '''Task:''' :: 
    57  The account URI needs to be updated with the new address, and re-registration is necessary to inform the registrar about the new URI. 
    58  
    59  '''Description:''' :: 
    60  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. 
    61  
    62  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. 
    63  
    64 ==== Media addresses update ==== 
    65  
    66  '''Task:''' :: 
    67  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. 
    68  
    69  '''Description:''' :: 
    70  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. 
    71  
    72  Note that at present there is no API to explicitly request the ICE media transport to initiate STUN Binding request immediately. 
    73  
    74  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()}}}. 
    75  
    76  
    77 ==== Call in progress issues ==== 
    78  
    79  '''Task:''' :: 
    80  Dialog's Contact URI needs to be updated. 
    81  
    82  '''Description:''' :: 
    83  The dialog's Contact URI is set initially when the dialog is created, from the account's Contact URI. While at the PJSIP level the {{{pjsip_inv_reinvite()}}} allows changing of Contact URI via the {{{new_contact}}} argument, currently this feature is not used by PJSUA-LIB, i.e. the {{{pjsua_call_reinvite()}}} does not allow the application to change the Contact URI. 
    84  
    85  So this is an open issue. 
    86  
    87  '''Task:''' :: 
    88  Changing of RTP/RTCP media addresses of ongoing call 
    89  
    90  '''Description:''' :: 
    91  If ICE is used, then new STUN srflx address will be signaled in updated SDP offer, as long as: 
    92    - ICE media transport has detected that the IP address has changed (via the keep-alive above), and 
    93    - 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. 
    94  
    95  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. 
    96  
    97  
    98  
    99 [[BR]] 
    100  
    101 == Issues with iPhone/TCP == #iphone 
    102  
    103 [Update 2011/01/26] 
    104  
    105 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: 
    106  
    107  1. We need to keep track of which transport is being used by the registration, by implementing the {{{on_reg_state2()}}} callback. 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: 
    108  {{{ 
    109 static pjsua_acc_id the_acc_id; 
    110 static pjsip_transport *the_transport; 
    111  
    112 static void on_reg_state2(pjsua_acc_id acc_id, pjsua_reg_info *info) 
    113 { 
    114    struct pjsip_regc_cbparam *rp = info->cbparam; 
    115  
    116   
    117     ... 
    118     if (acc_id != the_acc_id) 
    119         return; 
    120  
    121     if (rp->code/100 == 2 && rp->expiration > 0 && rp->contact_cnt > 0) { 
    122         /* Registration success */ 
    123         if (the_transport) { 
    124             PJ_LOG(3,(THIS_FILE, "xxx: Releasing transport..")); 
    125             pjsip_transport_dec_ref(the_transport); 
    126             the_transport = NULL; 
    127         } 
    128         /* Save transport instance so that we can close it later when 
    129          * new IP address is detected. 
    130          */ 
    131         PJ_LOG(3,(THIS_FILE, "xxx: Saving transport..")); 
    132         the_transport = rp->rdata->tp_info.transport; 
    133         pjsip_transport_add_ref(the_transport); 
    134     } else { 
    135         if (the_transport) { 
    136             PJ_LOG(3,(THIS_FILE, "xxx: Releasing transport..")); 
    137             pjsip_transport_dec_ref(the_transport); 
    138             the_transport = NULL; 
    139         } 
    140     } 
    141     ... 
    142 } 
    143  }}} 
    144  
    145  2. When IP address change is detected: a) send unregistration, and b) close the TCP transport that we saved in step 1) above. Sample code: 
    146  {{{ 
    147 pj_status_t status; 
    148  
    149 PJ_LOG(3,(THIS_FILE, "xxx: IP change..")); 
    150  
    151 status = pjsua_acc_set_registration(the_acc_id, PJ_FALSE); 
    152 if (status != PJ_SUCCESS) 
    153     PJ_PERROR(1,(THIS_FILE, status, "xxx: pjsua_acc_set_registration(0) error")); 
    154  
    155 if (the_transport) { 
    156     status = pjsip_transport_shutdown(the_transport); 
    157     if (status != PJ_SUCCESS) 
    158         PJ_PERROR(1,(THIS_FILE, status, "xxx: pjsip_transport_shutdown() error")); 
    159     pjsip_transport_dec_ref(the_transport); 
    160     the_transport = NULL; 
    161 } 
    162  
    163 }}} 
    164  
    165  3. And finally, once unregistration in 2a) above is complete, re-register (with TCP).  
    166  
    167 Note that ideally the closing the TCP transport is done in step 3 and not in step 2b. The drawback with doing it in 2b is, when the unregistration request is challenged (i.e. step 2a resulted in 401 response being received), a new TCP transport will be created, and the request retry will be sent with this new TCP transport. Your server may not like this, since it will see the unregistration request is coming from different TCP connection than the original request.  But having said that, the existing TCP transport may not be in usable state anyway, so I suppose this is not a worse situation than that. But in general, you may need to tweak the timing of this closing the transport part (you may even want to put it before 2a). 
    168  
    169 == Symbian specific issues and solution == #sym 
    170  
    171 Being a mobile operating system, Symbian has good supports in managing access point connection. In Symbian, the {{{RConnection}}} object is used to manage the connection, and each socket ''handle'' ({{{RSocket}}}) is created in a context of an {{{RConnection}}}. The {{{RConnection.ProgressNotification()}}} method can be used to register an Active Object to be run when the connection status has changed, so the application has good control over the connection. 
    172  
    173 However there are still couple of unsolved issues remaining (probably due to lack of knowledge in our part): 
    174  - when the connection in {{{RConnection}}} is down, it seems that the sockets created with that RConnection will detach themselves from the connection, so even though the RConnection is reconnected, this will not automatically make the sockets recover to a "good" state. If the application tries to make use of the socket, for example, to call {{{!SendTo()}}}, it will cause the socket to pop up the access point selection dialog again 
    175  - more over, if user selects different access point in the dialog, this will put the sockets in somewhat worse state. If the application tries to make use of the socket, for example, to call {{{!SendTo()}}}, it will cause the TRequestStatus associated with the operation to block for a long time (about one and half minute). And even worse, a second call to {{{!SendTo()}}} will cause the TRequestStatus to block indefinitely! 
    176  
    177 At the moment we are not aware of any solutions for the above issues. Lacking this, we created a workaround in PJLIB to prevent it from accessing any Symbian socket API's when the connection has been down and reconnected. The API is '''{{{pj_symbianos_set_connection_status()}}}''' and it was added in PJSIP version 1.0.2/1.1 (ticket #733 and #732 respectively).  
    178  
    179 Below is a sample code, implemented in {{{symbian_ua\ua.cpp}}} sample application, to restart PJSIP and use the {{{pj_symbianos_set_connection_status()}}} function. 
    180  
    181  {{{ 
    182 class CConnMon : public CActive { 
    183 public: 
    184     static CConnMon* NewL(RConnection &conn, RSocketServ &sserver) { 
    185         CConnMon *self = new (ELeave) CConnMon(conn, sserver); 
    186         CleanupStack::PushL(self); 
    187         self->ConstructL(); 
    188         CleanupStack::Pop(self); 
    189         return self; 
    190     } 
    191      
    192     void Start() { 
    193         conn_.ProgressNotification(nif_progress_, iStatus); 
    194         SetActive(); 
    195     } 
    196      
    197     void Stop() { 
    198         Cancel(); 
    199     } 
    200      
    201     ~CConnMon() { Stop(); } 
    202      
    203 private: 
    204     CConnMon(RConnection &conn, RSocketServ &sserver) :  
    205         CActive(EPriorityHigh),  
    206         conn_(conn),  
    207         sserver_(sserver) 
    208     { 
    209         CActiveScheduler::Add(this); 
    210     } 
    211      
    212     void ConstructL() {} 
    213  
    214     void DoCancel() { 
    215         conn_.CancelProgressNotification(); 
    216     } 
    217  
    218     void RunL() { 
    219         if (nif_progress_().iStage == KLinkLayerClosed) { 
    220             pj_status_t status; 
    221             TInt err; 
    222  
    223             // Tell pjlib the connection has been down. 
    224             pj_symbianos_set_connection_status(PJ_FALSE); 
    225              
    226             PJ_LOG(3, (THIS_FILE, "RConnection closed, restarting PJSUA..")); 
    227              
    228             // Destroy pjsua 
    229             pjsua_destroy(); 
    230             PJ_LOG(3, (THIS_FILE, "PJSUA destroyed.")); 
    231  
    232             // Reopen the connection 
    233             err = conn_.Open(sserver_); 
    234             if (err == KErrNone) 
    235                 err = conn_.Start(); 
    236             if (err != KErrNone) { 
    237                 CActiveScheduler::Stop(); 
    238                 return; 
    239             } 
    240  
    241             // Reinit Symbian OS param before pj_init() 
    242             pj_symbianos_params sym_params; 
    243             pj_bzero(&sym_params, sizeof(sym_params)); 
    244             sym_params.rsocketserv = &sserver_; 
    245             sym_params.rconnection = &conn_; 
    246             pj_symbianos_set_params(&sym_params); 
    247  
    248             // Reinit pjsua 
    249             status = app_startup(); 
    250             if (status != PJ_SUCCESS) { 
    251                 pjsua_perror(THIS_FILE, "app_startup() error", status); 
    252                 CActiveScheduler::Stop(); 
    253                 return; 
    254             } 
    255  
    256              
    257             PJ_LOG(3, (THIS_FILE, "PJSUA restarted.")); 
    258             PrintMenu(); 
    259         } 
    260          
    261         Start(); 
    262     } 
    263      
    264 private: 
    265     RConnection& conn_; 
    266     RSocketServ& sserver_; 
    267     TNifProgressBuf nif_progress_; 
    268 }; 
    269  
    270  }}} 
    271  
    272 What the code in {{{RunL()}}} above does is it shuts down PJSIP when the connection is down, ask user to reconnect by showing up the access point dialog, and (re)start the application. 
    273  
    274 Note that the drawback with this approach is that it does not clean up the registration and calls properly, that is no SIP unregistration will be sent and if the application is in the middle of a call while the connection is down then no BYE will be sent either. Currently we can't suggest any other solution, as we can't get rid of the socket gets stuck problem. 
    275  
    276 '''More about the socket stuck problem''' 
    277  
    278 This is a problem with the socket in general. Below are steps to reproduce with a plain UDP socket:  
    279  1. Create RConnection, call Start() to connect to AP  
    280  2. Create UDP socket, call !SendTo() to send a packet  
    281  3. Disconnect the AP (Menu -> Connectivity -> Conn. mgr. -> Active data connections -> (highlight the AP) -> Options (menu) -> Disconnect).  
    282  4. Call udp.!SendTo() again  
    283  5. AP selection dialog appears, select different AP  
    284  6. !WaitForRequest() to the udp.!SendTo() operation now '''will get stuck for 1-2 minutes''' before ''tcpip6_error_NoRoute'' error (-5105) is returned.  
    285  7. Now if you call udp.!SendTo() again, now !WaitForRequest() will get stuck indefinitely  
    286  
    287 The problem above does not occur if:  
    288  - the user selects the same AP. In this case the udp.!SendTo() should complete successfully  
    289  - the user cancels the AP selection dialog. In this case the udp.!SendTo() will fail immediately with KErrCancel without blocking the application.  
    290  - the socket is closed and re-opened. In this case the udp.!SendTo() should complete successfully  
    291  
    292 As additional info:  
    293  - the problem still persists even if the RConnection is restarted (RConnection.Start() is called to select new AP) in between step 3 and 4 above.  
    294  
    295  
    296  
    297  
    298 {{{ 
    299 #!html 
    300 <!-- MAIN TABLE END --> 
    301 </td></tr></table> 
    302 }}} 
    303  
     2This document has been moved to [wiki:IPAddressChange]