Changes between Initial Version and Version 1 of Using_Standalone_ICE

Mar 16, 2009 2:35:33 PM (13 years ago)



  • Using_Standalone_ICE

    v1 v1  
     3<!-- MAIN TABLE START --> 
     4<table border=0 width="90%" align="center"><tr><td> 
     7= Using Standalone PJNATH's ICE in (non-SIP) Applications = 
     9'''Table of Contents''' 
     12This article describes how to use the ICE stream transport of [ PJNATH] in a standalone, probably non-SIP/SDP applications. 
     14While reading this article, it's recommended to also open the [ ICE stream transport reference] page for more detailed info for the API. This article will provide links to the API in that page for further reading about the detail specification of the API. 
     18== Introduction == #intro 
     20The [ PJNATH] (PJSIP NAT Traversal Helper) library contains various objects to assist application with NAT traversal, using standard based protocols such as STUN, TURN, and ICE. The "ultimate" object in the library is the [ ICE stream transport] (will be called ''ice_strans'' for short in this article), where it wraps the STUN, TURN, and ICE functionality in one object and provides applications with API to send and receive data, as well as to perform ICE session management. 
     22From the library design point of view, all features in PJNATH are implemented in two layers, the transport independent/session layer, and the transport layer. The session layer contains only the logic to manage the corresponding session (for example, STUN, TURN, and ICE session). The transport layer wraps together the session object with socket(s) to make them ready to use transport objects. 
     24This article assumes that the application wants to use ICE transport, and not the ICE session (layer). 
     27== Terms == 
     29The following are terms used throughout this article: 
     31 '''ice_strans''' :: 
     32 The ICE stream transport as explained above. 
     34 '''ICE session''' :: 
     35 One multimedia session (e.g. one '''call''' session) between two ICE endpoint, within the ''ice_strans''. 
     37 '''ICE endpoint''' :: 
     38 The application which implements ICE. It is synonymous for ICE agent in the RFC. 
     42== Using standalone ICE in the application == #using 
     44=== Design === 
     46To use ICE, the application would need to replace it's send/receive socket(s) with [ ICE stream transport] object (will be called ''ice_strans'' for short). Once the ICE session in ''ice_strans'' is up and running, application uses ''pj_ice_strans_sendto()'' to send data, and registers ''on_rx_data()'' in ''pj_ice_strans_cb'' structure to receive incoming data. 
     48For SIP/SDP usage, one ''ice_strans'' is good for one media stream, and one media stream may contain more than one media transports, or called component in ICE terms (e.g. one RTP and one RTCP). Each component typically will be provided with more than one candidates, e.g. local candidates, STUN candidate, and TURN candidate. It will be then ICE's job to work out which of the candidate pair to be used for the session. 
     50For non-SIP usage, it will be application's design decision whether to create one ''ice_strans'' with multiple components, or multiple ''ice_strans'' with one component each. Using former would definitely be simpler since we only need to work with one session, but the later would have the advantage of faster negotiation (by tens to hundreds of msecs) since the two ''ice_strans'' objects can then do the negotiation in parallel. 
     54=== Preparations === 
     56Before using PJNATH's ICE, several steps need to be done. 
     58The PJNATH library depends on the following libraries, hence they need to be built (or ported if necessary) and added to the application's linking specifications: 
     59 * [ PJLIB] (for memory access, timer, network I/O, as well as the basic data structure/framework) 
     60 * [ PJLIB-UTIL] (mainly for the encryption algorithms needed by STUN) 
     62Several PJLIB objects need to be prepared by applications: 
     63 * at least one [ memory pool factory] instance is required for all PJLIB's based application. The memory pool factory is used to manage memory allocations by the libraries. 
     64 * at least one [ timer heap] instance for managing the timers 
     65 * at least one [ ioqueue] instance for managing network I/O events. 
     67One object of each typically is enough, although application may create more to fine tune the performance (by limiting the number of objects that each manages) or for other reasons. 
     69Once these objects are created, there need to be something that polls the timer heap and the ioqueue (except on Symbian where polling is not used). Typically application would create at least one thread to do this polling. 
     71These are pretty ''basic'' tasks that are required for all PJLIB network based applications, so please see the samples for some code snippets (e.g. [ turn-client sample]. 
     74=== Basic lifecycle === 
     76The following are the basic life cycle of ''ice_strans'': 
     77 * create ''ice_strans'' 
     78 * start one or more ICE session(s): 
     79     - create ICE session 
     80     - exchange ICE info with remote (username/password, candidate list). 
     81     - start ICE negotiation 
     82     - exchange data 
     83     - destroy ICE session 
     84     - repeat above to start new ICE session 
     85 * destroy ''ice_strans'' 
     87More will be explained below. 
     91=== Creating the ICE stream transport === 
     93To create the ''ice_strans'': 
     94 * initialize the [ pj_ice_strans_cfg]. Among other things, this structure contains settings required to enable and use STUN and TURN, as well as instances of the memory pool factory, timer heap and ioqueue (mentioned earlier) in the ''stun_cfg'' field. 
     95 * call [ pj_ice_strans_create()] 
     96 * wait for the ''on_ice_complete()'' callback of the [ pj_ice_strans_cb] to be called with ''op'' argument of '''PJ_ICE_STRANS_OP_INIT''', to indicate the status of the candidate gathering process (e.g. the result of STUN binding request and TURN allocation operations). The status of this candidate gathering process will be indicated in the ''status'' argument of the callback, with PJ_SUCCESS indicates succesful operation. 
     99Once ''ice_strans'' is created, it can be used to create ICE sessions. One ICE session represents one multimedia session between endpoints (i.e. one call session). After one session completes, the same ''ice_strans'' can be used to facilitate further sessions. Only one session may be active in one ''ice_strans'' at the same time. 
     103=== Working with session === 
     105The steps to use the session are typically as follows. 
     107==== Session creation ==== #sess_create 
     109Create the session by calling [ pj_ice_strans_init_ice()], specifying the initial role of the (ICE) endpoint and optionally, the local username and password. 
     111 '''Note:''' :: 
     112 The role affects ICE's negotiation behavior, especially to determine which endpoint is the ''controlling'' side. While ICE provides ''role conflict'' resolution in its negotiation process, it's always recommended to supply this with correct initial value to avoid unnecessary round-trips for the ''role conflict'' resolution. 
     115==== Exchanging ICE information with remote endpoint ==== #sess_oa 
     117Before ICE negotiation can start, each ICE endpoint would need to know the ICE information of the other endpoint. On SIP/SDP usage, this will happen when the application exchanges SDP's between each other. For non-SIP usage, this will be up to exchange this information (as well as how to encode it). 
     119The following information needs to be sent to remote ICE endpoint: 
     120 * the local ICE session's username and password (the so called ''ufrag''/user fragment and password). 
     121 * the candidate list for each and all ICE components.  The [ pj_ice_strans_enum_cands()] function is used to list the candidates of the specified ICE component. For each candidate, the following information needs to be exchanged: 
     122    - component ID 
     123    - candidate type (i.e. host, srflx, or relay) 
     124    - foundation ID 
     125    - priority 
     126    - transport type (only UDP is supported for now) 
     127    - transport address (address family, IP address, and port) 
     128    - optional related address (e.g. for srflx/STUN candidate, the related address is the local address where STUN request is sent from). This would only be used for troubleshooting purposes and is not required by ''ice_strans''. 
     129 * optionally the default candidate address for each ICE component. If remote doesn't support ICE, it can send data to this address. Application may also use this address to exchange data while ICE negotiation is in progress. The default candidate should be chosen from the candidate that is most likely to succeed, e.g. TURN, STUN, or one of the local candidate, in this order. Application may use [ pj_ice_strans_get_def_cand()] function to get the default candidate from the ''ice_strans''. 
     131How to encode/decode as well as to exchange the above information in non-SIP usage is up to the application/usage scenario. In PJSIP sample usage where ICE is integrated with media transport, the task to encode/decode the above information is done by the PJMEDIA's ICE transport (pjmedia/transport_ice.[hc]), and the information will be exchanged in SDP offer/answer. Below is a sample SDP generated by PJSIP which contains ICE information, with the relevant ICE attributes in '''bold''': 
     133 v=0[[BR]] 
     134 o=- 3423381096 3423381096 IN IP4 81.178.x.y[[BR]] 
     135 s=pjmedia[[BR]] 
     136 c=IN IP4 '''81.178.x.y'''[[BR]] 
     137 t=0 0[[BR]] 
     138 a=X-nat:5 [[BR]] 
     139 m=audio '''4808''' RTP/AVP 103 102 104 117 3 0 8 9 101[[BR]] 
     140 '''a=rtcp:4809 IN IP4 81.178.x.y'''[[BR]] 
     141 a=rtpmap:103 speex/16000[[BR]] 
     142 a=rtpmap:102 speex/8000[[BR]] 
     143 a=rtpmap:104 speex/32000[[BR]] 
     144 a=rtpmap:117 iLBC/8000[[BR]] 
     145 a=fmtp:117 mode=30[[BR]] 
     146 a=sendrecv[[BR]] 
     147 a=rtpmap:101 telephone-event/8000[[BR]] 
     148 a=fmtp:101 0-15[[BR]] 
     149 '''a=ice-ufrag:2b2c6196''' [[BR]] 
     150 '''a=ice-pwd:06ea0fa8''' [[BR]] 
     151 '''a=candidate:Sc0a8000e 1 UDP 1694498815 81.178.x.y 4808 typ srflx raddr rport 4808'''[[BR]] 
     152 '''a=candidate:Hc0a8000e 1 UDP 2130705151 4808 typ host'''[[BR]] 
     153 '''a=candidate:Sc0a8000e 2 UDP 1694498814 81.178.x.y 4809 typ srflx raddr rport 4809'''[[BR]] 
     154 '''a=candidate:Hc0a8000e 2 UDP 2130705150 4809 typ host'''[[BR]] 
     157(Note: the c= and a=rtcp lines contain the default ICE candidate address for the RTP and RTCP components respectively. Public IP addresses have also been scrambled a bit in the SDP above to protect the innocence). 
     159The ''ice_strans'' would also need to '''receive''' the above information before it can start ICE negotiation. 
     162==== Starting ICE negotiation ==== #sess_start 
     164Once ICE endpoints have sent/received ICE information to/from remote, they can start ICE negotiation by calling [ pj_ice_strans_start_ice()]. This function would need the above ICE information as its arguments. Each endpoint will need to call this in order for the negotiation to succeed. 
     166ICE negotiation then will start. 
     169 '''Note:''' :: 
     170 * The timing when each endpoint starts ''pj_ice_strans_start_ice()'' doesn't have to be absolutely simultaneously, though the more synchronized the better of course to speed up negotiation, and there is also limit of approximately 7-8 seconds before ICE negotiation will complete with timeout status. 
     172==== Getting ICE negotiation result ==== #sess_negotiated 
     174Application will be notified about the result in the (again) ''on_ice_complete()'' callback of the [ pj_ice_strans_cb], although this time with ''op'' argument of PJ_ICE_STRANS_OP_NEGOTIATION. The status of the operation will be indicated in the ''status'' argument of the callback, with PJ_SUCCESS indicates succesful negotiation. 
     176 '''Notes:''' :: 
     177 * It is possible that the number of components between the two ICE endpoints are different, e.g. we support RTCP but remote doesn't. The ''pj_ice_strans_get_running_comp_cnt()'' function can be used (after ICE negotiation completes) to find out how many components have been negotiated by ICE. Application can always deduce this information by comparing its local candidate list against remote's of course. 
     178 * See also the remarks about negotiation time in the global [#notes Notes] section at the end of this article. 
     180==== Sending and Receiving Data ==== #sess_data 
     182Use [ pj_ice_strans_sendto()] to send data to remote ICE endpoint. Incoming data will be reported in ''on_rx_data()'' callback of the [ pj_ice_strans_cb] structure. 
     185==== Finishing with the session ==== #sess_finish 
     187Once the session is done (e.g. call has ended), call [ pj_ice_strans_stop_ice()] to clean up local resources allocated for the session. 
     189Application may reuse this same ''ice_strans'' instance to start another session by repeating the steps from [#sess_create Session creation] above. 
     193=== Destroying ICE stream transport === 
     195Use [ pj_ice_strans_destroy()] to destroy the ICE stream transport itself. This will initiate TURN deallocation procedure (if TURN in used), and ultimately will close down sockets as well as all resources allocated by this ''ice_strans'' instance. 
     197Note that ''ice_strans'' destruction will not complete immediately if TURN is used (since it needs to wait for deallocation procedure), hence it is important that polling to the timer heap and ioqueue continues to be done. Application will not be notified when ''ice_strans'' destruction completes, it just needs to assume that the ''ice_strans'' object is no longer usable as soon as ''pj_ice_strans_destroy()'' is called. 
     201=== Notes === #notes 
     203Note that the information below applies to current PJSIP release (version 1.1 as of 2009/03/16). They may change (and definitely will be improved if we can) in subsequent releases. 
     205==== Keep-alive ==== 
     207Once the ''ice_strans'' is created, the STUN and TURN keep-alive will be done automatically and internally. The default STUN keep-alive period is 15 seconds (PJ_STUN_KEEP_ALIVE_SEC), and TURN is also 15 seconds (PJ_TURN_KEEP_ALIVE_SEC). 
     209==== IP address change ==== 
     211Changes in STUN mapped address is handled automatically by ''ice_strans'' via the STUN keep-alive exchanges, although currently there is no callback to notify application about this event. Call to [ pj_ice_strans_enum_cands()] will get the updated address. 
     213Changes in local interface's IP address are not detected. 
     215If IP address change is of application's concern, currently we can only recommend the application to implement this detection, and destroy/recreate the ''ice_strans'' once it detects the IP address change. 
     217==== Negotiation time ==== 
     219ICE negotiation may take tens to hundreds of milliseconds to complete. The time it takes to complete negotiation depends on the number of candidates across all components in one single ''ice_strans'' and the round-trip between the two ICE endpoints. In our brief and non-scientific test, it took about 100-150 msec to complete, in scenario where two (SIP) endpoints were behind ADSL connection (both are in UK), with two components and 2-4 candidates per component. 
     221But please also note that '''it may take seconds''' for ICE to report negotiation failure. ICE will wait until all STUN retransmissions have timed-out, and with the default setting, it will take 7-8 seconds before it will report ICE negotiation failure. 
     227<!-- MAIN TABLE END -->