res_pjsip_session: segfault on already disconnected session
authorAlexei Gradinari <alex2grad@gmail.com>
Thu, 18 Aug 2016 19:45:59 +0000 (15:45 -0400)
committerAlexei Gradinari <alex2grad@gmail.com>
Tue, 6 Sep 2016 13:58:42 +0000 (08:58 -0500)
On heavy loaded system the TCP/TLS incoming calls could be
disconnected by pjproject while these calls are being
processed by asterisk which could use the session's memory pools.
If the session in the disconnected state then the session memory
pools were already freed, so we get segfault.

This patch adds a lifetime control on an INVITE session to pjproject.
The lifetime of the session is manipulated by calling
pjsip_inv_add_ref/pjsip_inv_dec_ref.
This patch uses these functions to inform pjproject that the
session is in use.

This patch adds check if the session state is not disconnected
and also checks if the memory pool is not NULL.

This patch also places tasks 'session_end' and 'session_end_completion'
into session's serializer to avoid race condition.

ASTERISK-26291 #close

Change-Id: I4d28b1fb3b91f0492a911d110049d670fdc3c8d7

configure
configure.ac
include/asterisk/autoconfig.h.in
res/res_pjsip_session.c
third-party/pjproject/configure.m4
third-party/pjproject/patches/0002-r5435-add-pjsip_inv_session-ref_cnt.patch [new file with mode: 0644]

index 3e5140d..886ef6f 100755 (executable)
--- a/configure
+++ b/configure
@@ -935,6 +935,10 @@ PBX_POPT
 POPT_DIR
 POPT_INCLUDE
 POPT_LIB
+PBX_PJSIP_INV_SESSION_REF
+PJSIP_INV_SESSION_REF_DIR
+PJSIP_INV_SESSION_REF_INCLUDE
+PJSIP_INV_SESSION_REF_LIB
 PBX_PJSIP_EVSUB_GRP_LOCK
 PJSIP_EVSUB_GRP_LOCK_DIR
 PJSIP_EVSUB_GRP_LOCK_INCLUDE
@@ -11001,6 +11005,18 @@ PBX_PJSIP_EVSUB_GRP_LOCK=0
 
 
 
+PJSIP_INV_SESSION_REF_DESCRIP="PJSIP INVITE Session Reference Count support"
+PJSIP_INV_SESSION_REF_OPTION=pjsip
+PJSIP_INV_SESSION_REF_DIR=${PJPROJECT_DIR}
+
+PBX_PJSIP_INV_SESSION_REF=0
+
+
+
+
+
+
+
 
     POPT_DESCRIP="popt"
     POPT_OPTION="popt"
@@ -25269,6 +25285,9 @@ $as_echo "#define HAVE_PJSIP_TLS_TRANSPORT_PROTO 1" >>confdefs.h
 $as_echo "#define HAVE_PJSIP_EVSUB_GRP_LOCK 1" >>confdefs.h
 
 
+$as_echo "#define HAVE_PJSIP_INV_SESSION_REF 1" >>confdefs.h
+
+
    else
 
    if test "x${PBX_PJPROJECT}" != "x1" -a "${USE_PJPROJECT}" != "no"; then
@@ -26089,6 +26108,110 @@ _ACEOF
 fi
 
 
+
+if test "x${PBX_PJSIP_INV_SESSION_REF}" != "x1" -a "${USE_PJSIP_INV_SESSION_REF}" != "no"; then
+   pbxlibdir=""
+   # if --with-PJSIP_INV_SESSION_REF=DIR has been specified, use it.
+   if test "x${PJSIP_INV_SESSION_REF_DIR}" != "x"; then
+      if test -d ${PJSIP_INV_SESSION_REF_DIR}/lib; then
+         pbxlibdir="-L${PJSIP_INV_SESSION_REF_DIR}/lib"
+      else
+         pbxlibdir="-L${PJSIP_INV_SESSION_REF_DIR}"
+      fi
+   fi
+   pbxfuncname="pjsip_inv_add_ref"
+   if test "x${pbxfuncname}" = "x" ; then   # empty lib, assume only headers
+      AST_PJSIP_INV_SESSION_REF_FOUND=yes
+   else
+      ast_ext_lib_check_save_CFLAGS="${CFLAGS}"
+      CFLAGS="${CFLAGS} $PJPROJECT_CFLAGS"
+      as_ac_Lib=`$as_echo "ac_cv_lib_pjsip_${pbxfuncname}" | $as_tr_sh`
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ${pbxfuncname} in -lpjsip" >&5
+$as_echo_n "checking for ${pbxfuncname} in -lpjsip... " >&6; }
+if eval \${$as_ac_Lib+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lpjsip ${pbxlibdir} $PJPROJECT_LIB $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char ${pbxfuncname} ();
+int
+main ()
+{
+return ${pbxfuncname} ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  eval "$as_ac_Lib=yes"
+else
+  eval "$as_ac_Lib=no"
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+eval ac_res=\$$as_ac_Lib
+              { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then :
+  AST_PJSIP_INV_SESSION_REF_FOUND=yes
+else
+  AST_PJSIP_INV_SESSION_REF_FOUND=no
+fi
+
+      CFLAGS="${ast_ext_lib_check_save_CFLAGS}"
+   fi
+
+   # now check for the header.
+   if test "${AST_PJSIP_INV_SESSION_REF_FOUND}" = "yes"; then
+      PJSIP_INV_SESSION_REF_LIB="${pbxlibdir} -lpjsip $PJPROJECT_LIB"
+      # if --with-PJSIP_INV_SESSION_REF=DIR has been specified, use it.
+      if test "x${PJSIP_INV_SESSION_REF_DIR}" != "x"; then
+         PJSIP_INV_SESSION_REF_INCLUDE="-I${PJSIP_INV_SESSION_REF_DIR}/include"
+      fi
+      PJSIP_INV_SESSION_REF_INCLUDE="${PJSIP_INV_SESSION_REF_INCLUDE} $PJPROJECT_CFLAGS"
+      if test "xpjsip.h" = "x" ; then  # no header, assume found
+         PJSIP_INV_SESSION_REF_HEADER_FOUND="1"
+      else                             # check for the header
+         ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}"
+         CPPFLAGS="${CPPFLAGS} ${PJSIP_INV_SESSION_REF_INCLUDE}"
+         ac_fn_c_check_header_mongrel "$LINENO" "pjsip.h" "ac_cv_header_pjsip_h" "$ac_includes_default"
+if test "x$ac_cv_header_pjsip_h" = xyes; then :
+  PJSIP_INV_SESSION_REF_HEADER_FOUND=1
+else
+  PJSIP_INV_SESSION_REF_HEADER_FOUND=0
+fi
+
+
+         CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}"
+      fi
+      if test "x${PJSIP_INV_SESSION_REF_HEADER_FOUND}" = "x0" ; then
+         PJSIP_INV_SESSION_REF_LIB=""
+         PJSIP_INV_SESSION_REF_INCLUDE=""
+      else
+         if test "x${pbxfuncname}" = "x" ; then                # only checking headers -> no library
+            PJSIP_INV_SESSION_REF_LIB=""
+         fi
+         PBX_PJSIP_INV_SESSION_REF=1
+         cat >>confdefs.h <<_ACEOF
+#define HAVE_PJSIP_INV_SESSION_REF 1
+_ACEOF
+
+      fi
+   fi
+fi
+
+
    fi
 fi
 
index 950cfc5..7fd702f 100644 (file)
@@ -487,6 +487,7 @@ AST_EXT_LIB_SETUP_OPTIONAL([PJ_SSL_CERT_LOAD_FROM_FILES2], [pj_ssl_cert_load_fro
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_EXTERNAL_RESOLVER], [PJSIP External Resolver Support], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_TLS_TRANSPORT_PROTO], [PJSIP TLS Transport proto field support], [PJPROJECT], [pjsip])
 AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_EVSUB_GRP_LOCK], [PJSIP EVSUB Group Lock support], [PJPROJECT], [pjsip])
+AST_EXT_LIB_SETUP_OPTIONAL([PJSIP_INV_SESSION_REF], [PJSIP INVITE Session Reference Count support], [PJPROJECT], [pjsip])
 
 AST_EXT_LIB_SETUP([POPT], [popt], [popt])
 AST_EXT_LIB_SETUP([PORTAUDIO], [PortAudio], [portaudio])
@@ -2210,6 +2211,7 @@ if test "$USE_PJPROJECT" != "no" ; then
       CPPFLAGS="${saved_cppflags}"
 
       AST_EXT_LIB_CHECK([PJSIP_EVSUB_GRP_LOCK], [pjsip], [pjsip_evsub_add_ref], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
+      AST_EXT_LIB_CHECK([PJSIP_INV_SESSION_REF], [pjsip], [pjsip_inv_add_ref], [pjsip.h], [$PJPROJECT_LIB], [$PJPROJECT_CFLAGS])
    fi
 fi
 
index f9a6b46..5e7ea7e 100644 (file)
 /* Define if your system has pjsip_get_dest_info declared. */
 #undef HAVE_PJSIP_GET_DEST_INFO
 
+/* Define if your system has PJSIP_INV_SESSION_REF */
+#undef HAVE_PJSIP_INV_SESSION_REF
+
 /* Define if your system has the PJSIP_REPLACE_MEDIA_STREAM headers. */
 #undef HAVE_PJSIP_REPLACE_MEDIA_STREAM
 
index 315393f..f54ee94 100644 (file)
@@ -213,6 +213,11 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd
        int i;
        int handled = 0;
 
+       if (session->inv_session && session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
+               ast_log(LOG_ERROR, "Failed to handle incoming SDP. Session has been already disconnected\n");
+               return -1;
+       }
+
        for (i = 0; i < sdp->media_count; ++i) {
                /* See if there are registered handlers for this media stream type */
                char media[20];
@@ -2087,6 +2092,16 @@ static int new_invite(void *data)
         * so that we will be notified so we can destroy the session properly
         */
 
+       if (invite->session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
+               ast_log(LOG_ERROR, "Session already DISCONNECTED [reason=%d (%s)]\n",
+                       invite->session->inv_session->cause,
+                       pjsip_get_status_text(invite->session->inv_session->cause)->ptr);
+#ifdef HAVE_PJSIP_INV_SESSION_REF
+               pjsip_inv_dec_ref(invite->session->inv_session);
+#endif
+               return -1;
+       }
+
        switch (get_destination(invite->session, invite->rdata)) {
        case SIP_GET_DEST_EXTEN_FOUND:
                /* Things worked. Keep going */
@@ -2097,7 +2112,7 @@ static int new_invite(void *data)
                } else  {
                        pjsip_inv_terminate(invite->session->inv_session, 416, PJ_TRUE);
                }
-               return 0;
+               goto end;
        case SIP_GET_DEST_EXTEN_NOT_FOUND:
        case SIP_GET_DEST_EXTEN_PARTIAL:
        default:
@@ -2110,7 +2125,7 @@ static int new_invite(void *data)
                } else  {
                        pjsip_inv_terminate(invite->session->inv_session, 404, PJ_TRUE);
                }
-               return 0;
+               goto end;
        };
 
        if ((sdp_info = pjsip_rdata_get_sdp_info(invite->rdata)) && (sdp_info->sdp_err == PJ_SUCCESS) && sdp_info->sdp) {
@@ -2120,7 +2135,7 @@ static int new_invite(void *data)
                        } else  {
                                pjsip_inv_terminate(invite->session->inv_session, 488, PJ_TRUE);
                        }
-                       return 0;
+                       goto end;
                }
                /* We are creating a local SDP which is an answer to their offer */
                local = create_local_sdp(invite->session->inv_session, invite->session, sdp_info->sdp);
@@ -2136,7 +2151,7 @@ static int new_invite(void *data)
                } else  {
                        pjsip_inv_terminate(invite->session->inv_session, 500, PJ_TRUE);
                }
-               return 0;
+               goto end;
        } else {
                pjsip_inv_set_local_sdp(invite->session->inv_session, local);
                pjmedia_sdp_neg_set_prefer_remote_codec_order(invite->session->inv_session->neg, PJ_FALSE);
@@ -2153,12 +2168,16 @@ static int new_invite(void *data)
        /* At this point, we've verified what we can, so let's go ahead and send a 100 Trying out */
        if (pjsip_inv_initial_answer(invite->session->inv_session, invite->rdata, 100, NULL, NULL, &tdata) != PJ_SUCCESS) {
                pjsip_inv_terminate(invite->session->inv_session, 500, PJ_TRUE);
-               return 0;
+               goto end;
        }
        ast_sip_session_send_response(invite->session, tdata);
 
        handle_incoming_request(invite->session, invite->rdata);
 
+end:
+#ifdef HAVE_PJSIP_INV_SESSION_REF
+       pjsip_inv_dec_ref(invite->session->inv_session);
+#endif
        return 0;
 }
 
@@ -2179,6 +2198,20 @@ static void handle_new_invite_request(pjsip_rx_data *rdata)
                return;
        }
 
+#ifdef HAVE_PJSIP_INV_SESSION_REF
+       if (pjsip_inv_add_ref(inv_session) != PJ_SUCCESS) {
+               ast_log(LOG_ERROR, "Can't increase the session reference counter\n");
+               if (inv_session->state != PJSIP_INV_STATE_DISCONNECTED) {
+                       if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) {
+                               pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
+                       } else {
+                               internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
+                       }
+               }
+               return;
+       }
+#endif
+
        session = ast_sip_session_alloc(endpoint, NULL, inv_session, rdata);
        if (!session) {
                if (pjsip_inv_initial_answer(inv_session, rdata, 500, NULL, NULL, &tdata) == PJ_SUCCESS) {
@@ -2186,6 +2219,9 @@ static void handle_new_invite_request(pjsip_rx_data *rdata)
                } else {
                        internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
                }
+#ifdef HAVE_PJSIP_INV_SESSION_REF
+               pjsip_inv_dec_ref(inv_session);
+#endif
                return;
        }
 
@@ -2196,6 +2232,9 @@ static void handle_new_invite_request(pjsip_rx_data *rdata)
                } else {
                        internal_pjsip_inv_send_msg(inv_session, endpoint->transport, tdata);
                }
+#ifdef HAVE_PJSIP_INV_SESSION_REF
+               pjsip_inv_dec_ref(inv_session);
+#endif
                ao2_cleanup(invite);
        }
        ao2_ref(session, -1);
@@ -2467,8 +2506,9 @@ static void handle_outgoing(struct ast_sip_session *session, pjsip_tx_data *tdat
        }
 }
 
-static void session_end(struct ast_sip_session *session)
+static int session_end(void *vsession)
 {
+       struct ast_sip_session *session = vsession;
        struct ast_sip_session_supplement *iter;
 
        /* Stop the scheduled termination */
@@ -2480,6 +2520,7 @@ static void session_end(struct ast_sip_session *session)
                        iter->session_end(session);
                }
        }
+       return 0;
 }
 
 /*!
@@ -2617,7 +2658,10 @@ static void session_inv_on_state_changed(pjsip_inv_session *inv, pjsip_event *e)
        }
 
        if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
-               session_end(session);
+               if (ast_sip_push_task(session->serializer, session_end, session)) {
+                       /* Do it anyway even though this is not the right thread. */
+                       session_end(session);
+               }
        }
 }
 
@@ -2784,7 +2828,11 @@ static void session_inv_on_tsx_state_changed(pjsip_inv_session *inv, pjsip_trans
                         * Pass the session ref held by session->inv_session to
                         * session_end_completion().
                         */
-                       session_end_completion(session);
+                       if (session
+                               && ast_sip_push_task(session->serializer, session_end_completion, session)) {
+                               /* Do it anyway even though this is not the right thread. */
+                               session_end_completion(session);
+                       }
                        return;
                }
                break;
@@ -2909,7 +2957,12 @@ static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, stru
        static const pj_str_t STR_IP6 = { "IP6", 3 };
        pjmedia_sdp_session *local;
 
-       if (!(local = PJ_POOL_ZALLOC_T(inv->pool_prov, pjmedia_sdp_session))) {
+       if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
+               ast_log(LOG_ERROR, "Failed to create session SDP. Session has been already disconnected\n");
+               return NULL;
+       }
+
+       if (!inv->pool_prov || !(local = PJ_POOL_ZALLOC_T(inv->pool_prov, pjmedia_sdp_session))) {
                return NULL;
        }
 
index 67ac04d..7a079f6 100644 (file)
@@ -45,4 +45,5 @@ AC_DEFUN([PJPROJECT_CONFIGURE],
        PJPROJECT_SYMBOL_CHECK([PJSIP_EXTERNAL_RESOLVER], [pjsip_endpt_set_ext_resolver], [pjsip.h])
        AC_DEFINE([HAVE_PJSIP_TLS_TRANSPORT_PROTO], 1, [Define if your system has PJSIP_TLS_TRANSPORT_PROTO])
        AC_DEFINE([HAVE_PJSIP_EVSUB_GRP_LOCK], 1, [Define if your system has PJSIP_EVSUB_GRP_LOCK])
+       AC_DEFINE([HAVE_PJSIP_INV_SESSION_REF], 1, [Define if your system has PJSIP_INV_SESSION_REF])
 ])
diff --git a/third-party/pjproject/patches/0002-r5435-add-pjsip_inv_session-ref_cnt.patch b/third-party/pjproject/patches/0002-r5435-add-pjsip_inv_session-ref_cnt.patch
new file mode 100644 (file)
index 0000000..12ae6a0
--- /dev/null
@@ -0,0 +1,212 @@
+When a transport error occured on an INVITE session
+the stack calls on_tsx_state_changed with new state
+PJSIP_INV_STATE_DISCONNECTED and immediately destroys
+the INVITE session.
+At the same time this INVITE session could being processed
+on another thread. This thread could use the session's
+memory pools which were already freed, so we get segfault.
+
+This patch adds a reference counter and new functions:
+pjsip_inv_add_ref and pjsip_inv_dec_ref.
+The INVITE session is destroyed only when the reference
+counter has reached zero.
+
+To avoid race condition an application should call
+pjsip_inv_add_ref/pjsip_inv_dec_ref.
+
+Index: pjsip/include/pjsip-ua/sip_inv.h
+===================================================================
+--- a/pjsip/include/pjsip-ua/sip_inv.h (revision 5434)
++++ b/pjsip/include/pjsip-ua/sip_inv.h (revision 5435)
+@@ -383,6 +383,11 @@
+  * Other applications that want to use these pools must understand
+  * that the flip-flop pool's lifetimes are synchronized to the
+  * SDP offer-answer negotiation.
++ *
++ * The lifetime of this session is controlled by the reference counter in this
++ * structure, which is manipulated by calling #pjsip_inv_add_ref and
++ * #pjsip_inv_dec_ref. When the reference counter has reached zero, then
++ * this session will be destroyed.
+  */
+ struct pjsip_inv_session
+ {
+@@ -412,6 +417,7 @@
+     struct pjsip_timer        *timer;                     /**< Session Timers.    */
+     pj_bool_t          following_fork;            /**< Internal, following
+                                                        forked media?      */
++    pj_atomic_t               *ref_cnt;                   /**< Reference counter. */
+ };
+@@ -631,6 +637,30 @@
+ /**
++ * Add reference counter to the INVITE session. The reference counter controls
++ * the life time of the session, ie. when the counter reaches zero, then it 
++ * will be destroyed.
++ *
++ * @param inv       The INVITE session.
++ * @return          PJ_SUCCESS if the INVITE session reference counter
++ *                  was increased.
++ */
++PJ_DECL(pj_status_t) pjsip_inv_add_ref( pjsip_inv_session *inv );
++
++/**
++ * Decrement reference counter of the INVITE session.
++ * When the session is no longer used, it will be destroyed and
++ * caller is informed with PJ_EGONE return status.
++ *
++ * @param inv       The INVITE session.
++ * @return          PJ_SUCCESS if the INVITE session reference counter
++ *                  was decreased. A status PJ_EGONE will be returned to 
++ *                  inform that session is destroyed.
++ */
++PJ_DECL(pj_status_t) pjsip_inv_dec_ref( pjsip_inv_session *inv );
++
++
++/**
+  * Forcefully terminate and destroy INVITE session, regardless of
+  * the state of the session. Note that this function should only be used
+  * when there is failure in the INVITE session creation. After the
+Index: pjsip/src/pjsip-ua/sip_inv.c
+===================================================================
+--- a/pjsip/src/pjsip-ua/sip_inv.c     (revision 5434)
++++ b/pjsip/src/pjsip-ua/sip_inv.c     (revision 5435)
+@@ -195,6 +195,65 @@
+ }
+ /*
++ * Add reference to INVITE session.
++ */
++PJ_DEF(pj_status_t) pjsip_inv_add_ref( pjsip_inv_session *inv )
++{
++    PJ_ASSERT_RETURN(inv && inv->ref_cnt, PJ_EINVAL);
++
++    pj_atomic_inc(inv->ref_cnt);
++
++    return PJ_SUCCESS;
++}
++
++static void inv_session_destroy(pjsip_inv_session *inv)
++{
++    if (inv->last_ack) {
++      pjsip_tx_data_dec_ref(inv->last_ack);
++      inv->last_ack = NULL;
++    }
++    if (inv->invite_req) {
++      pjsip_tx_data_dec_ref(inv->invite_req);
++      inv->invite_req = NULL;
++    }
++    if (inv->pending_bye) {
++      pjsip_tx_data_dec_ref(inv->pending_bye);
++      inv->pending_bye = NULL;
++    }
++    pjsip_100rel_end_session(inv);
++    pjsip_timer_end_session(inv);
++    pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod);
++
++    /* Release the flip-flop pools */
++    pj_pool_release(inv->pool_prov);
++    inv->pool_prov = NULL;
++    pj_pool_release(inv->pool_active);
++    inv->pool_active = NULL;
++
++    pj_atomic_destroy(inv->ref_cnt);
++    inv->ref_cnt = NULL;
++}
++
++/*
++ * Decrease INVITE session reference, destroy it when the reference count
++ * reaches zero.
++ */
++PJ_DEF(pj_status_t) pjsip_inv_dec_ref( pjsip_inv_session *inv )
++{
++    pj_atomic_value_t ref_cnt;
++
++    PJ_ASSERT_RETURN(inv && inv->ref_cnt, PJ_EINVAL);
++
++    ref_cnt = pj_atomic_dec_and_get(inv->ref_cnt);
++    pj_assert( ref_cnt >= 0);
++    if (ref_cnt == 0) {
++        inv_session_destroy(inv);
++        return PJ_EGONE;
++    } 
++    return PJ_SUCCESS;    
++}
++
++/*
+  * Set session state.
+  */
+ static void inv_set_state(pjsip_inv_session *inv, pjsip_inv_state state,
+@@ -261,27 +320,7 @@
+     if (inv->state == PJSIP_INV_STATE_DISCONNECTED &&
+       prev_state != PJSIP_INV_STATE_DISCONNECTED) 
+     {
+-      if (inv->last_ack) {
+-          pjsip_tx_data_dec_ref(inv->last_ack);
+-          inv->last_ack = NULL;
+-      }
+-      if (inv->invite_req) {
+-          pjsip_tx_data_dec_ref(inv->invite_req);
+-          inv->invite_req = NULL;
+-      }
+-      if (inv->pending_bye) {
+-          pjsip_tx_data_dec_ref(inv->pending_bye);
+-          inv->pending_bye = NULL;
+-      }
+-      pjsip_100rel_end_session(inv);
+-      pjsip_timer_end_session(inv);
+-      pjsip_dlg_dec_session(inv->dlg, &mod_inv.mod);
+-
+-      /* Release the flip-flop pools */
+-      pj_pool_release(inv->pool_prov);
+-      inv->pool_prov = NULL;
+-      pj_pool_release(inv->pool_active);
+-      inv->pool_active = NULL;
++      pjsip_inv_dec_ref(inv);
+     }
+ }
+@@ -838,6 +877,12 @@
+     inv = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_inv_session);
+     pj_assert(inv != NULL);
++    status = pj_atomic_create(dlg->pool, 0, &inv->ref_cnt);
++    if (status != PJ_SUCCESS) {
++      pjsip_dlg_dec_lock(dlg);
++      return status;
++    }
++
+     inv->pool = dlg->pool;
+     inv->role = PJSIP_ROLE_UAC;
+     inv->state = PJSIP_INV_STATE_NULL;
+@@ -881,6 +926,7 @@
+     pjsip_100rel_attach(inv);
+     /* Done */
++    pjsip_inv_add_ref(inv);
+     *p_inv = inv;
+     pjsip_dlg_dec_lock(dlg);
+@@ -1471,6 +1517,12 @@
+     inv = PJ_POOL_ZALLOC_T(dlg->pool, pjsip_inv_session);
+     pj_assert(inv != NULL);
++    status = pj_atomic_create(dlg->pool, 0, &inv->ref_cnt);
++    if (status != PJ_SUCCESS) {
++      pjsip_dlg_dec_lock(dlg);
++      return status;
++    }
++
+     inv->pool = dlg->pool;
+     inv->role = PJSIP_ROLE_UAS;
+     inv->state = PJSIP_INV_STATE_NULL;
+@@ -1540,6 +1592,7 @@
+     }
+     /* Done */
++    pjsip_inv_add_ref(inv);
+     pjsip_dlg_dec_lock(dlg);
+     *p_inv = inv;