Ticket #1044:
 - Added initial version of automatic re-registration after registration failure and automatic call disconnection after re-registration attempt fails.
 - Published auto re-registration setting to pjsua app.
 - Updated pjsip_regc_send() to retrieve the transport earlier (was only in tsx_callback()).
 - Fixed TCP and TLS transport to prevent transport deletion in transport disconnection callback.
 - Fixed wrong keep-alive settings used by TLS transport (was using TCP keep-alive settings).



git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@3128 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c
index b312b3a..6bb23d2 100644
--- a/pjsip-apps/src/pjsua/pjsua_app.c
+++ b/pjsip-apps/src/pjsua/pjsua_app.c
@@ -189,6 +189,7 @@
     puts  ("  --proxy=url         Optional URL of proxy server to visit");
     puts  ("                      May be specified multiple times");
     puts  ("  --reg-timeout=SEC   Optional registration interval (default 55)");
+    puts  ("  --rereg-delay=SEC   Optional auto retry registration interval (default 300)");
     puts  ("  --realm=string      Set realm");
     puts  ("  --username=string   Set authentication username");
     puts  ("  --password=string   Set authentication password");
@@ -484,6 +485,7 @@
 	   OPT_REGISTRAR, OPT_REG_TIMEOUT, OPT_PUBLISH, OPT_ID, OPT_CONTACT,
 	   OPT_BOUND_ADDR, OPT_CONTACT_PARAMS, OPT_CONTACT_URI_PARAMS,
 	   OPT_100REL, OPT_USE_IMS, OPT_REALM, OPT_USERNAME, OPT_PASSWORD,
+	   OPT_REG_RETRY_INTERVAL,
 	   OPT_MWI, OPT_NAMESERVER, OPT_STUN_SRV,
 	   OPT_ADD_BUDDY, OPT_OFFER_X_MS_MSG, OPT_NO_PRESENCE,
 	   OPT_AUTO_ANSWER, OPT_AUTO_PLAY, OPT_AUTO_PLAY_HANGUP, OPT_AUTO_LOOP,
@@ -550,6 +552,7 @@
 	{ "realm",	1, 0, OPT_REALM},
 	{ "username",	1, 0, OPT_USERNAME},
 	{ "password",	1, 0, OPT_PASSWORD},
+	{ "rereg-delay",1, 0, OPT_REG_RETRY_INTERVAL},
 	{ "nameserver", 1, 0, OPT_NAMESERVER},
 	{ "stun-srv",   1, 0, OPT_STUN_SRV},
 	{ "add-buddy",  1, 0, OPT_ADD_BUDDY},
@@ -959,6 +962,10 @@
 #endif
 	    break;
 
+	case OPT_REG_RETRY_INTERVAL:
+	    cur_acc->reg_retry_interval = pj_strtoul(pj_cstr(&tmp, pj_optarg));
+	    break;
+
 	case OPT_NEXT_CRED: /* next credential */
 	    cur_acc->cred_count++;
 	    break;
diff --git a/pjsip/include/pjsip-ua/sip_regc.h b/pjsip/include/pjsip-ua/sip_regc.h
index eb8b33c..a003641 100644
--- a/pjsip/include/pjsip-ua/sip_regc.h
+++ b/pjsip/include/pjsip-ua/sip_regc.h
@@ -94,6 +94,7 @@
     pj_bool_t	auto_reg;   /**< Will register automatically?		    */
     int		interval;   /**< Registration interval (seconds).	    */
     int		next_reg;   /**< Time until next registration (seconds).    */
+    pjsip_transport *transport; /**< Last transport used.		    */
 };
 
 /**
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index 451b8c9..5d22808 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -1865,6 +1865,18 @@
 
 
 /**
+ * Default auto retry re-registration interval, in seconds. Set to 0
+ * to disable this. Application can set the timer on per account basis 
+ * by setting the pjsua_acc_config.reg_retry_interval field instead.
+ *
+ * Default: 300 (5 minutes)
+ */
+#ifndef PJSUA_REG_RETRY_INTERVAL
+#   define PJSUA_REG_RETRY_INTERVAL	300
+#endif
+
+
+/**
  * This structure describes account configuration to be specified when
  * adding a new account with #pjsua_acc_add(). Application MUST initialize
  * this structure first by calling #pjsua_acc_config_default().
@@ -2116,6 +2128,24 @@
     int		     srtp_secure_signaling;
 #endif
 
+    /**
+     * Specify interval of auto registration retry upon registration failure
+     * (including caused by transport problem), in second. Set to 0 to
+     * disable auto re-registration.
+     *
+     * Default: #PJSUA_REG_RETRY_INTERVAL
+     */
+    unsigned	     reg_retry_interval;
+
+    /**
+     * Specify whether calls of the configured account should be dropped
+     * after registration failure and an attempt of re-registration has 
+     * also failed.
+     *
+     * Default: PJ_FALSE (disabled)
+     */
+    pj_bool_t	     drop_calls_on_reg_fail;
+
 } pjsua_acc_config;
 
 
diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h
index 527b53a..647adc0 100644
--- a/pjsip/include/pjsua-lib/pjsua_internal.h
+++ b/pjsip/include/pjsua-lib/pjsua_internal.h
@@ -128,6 +128,13 @@
     pj_status_t	     reg_last_err;  /**< Last registration error.	*/
     int		     reg_last_code; /**< Last status last register.	*/
 
+    struct {
+	pj_bool_t	 active;    /**< Flag of reregister status.	*/
+	pj_timer_entry   timer;	    /**< Timer for reregistration.	*/
+	void		*reg_tp;    /**< Transport for registration.	*/
+	unsigned	 attempt_cnt; /**< Attempt counter.		*/
+    } auto_rereg;		    /**< Reregister/reconnect data.	*/
+
     pj_timer_entry   ka_timer;	    /**< Keep-alive timer for UDP.	*/
     pjsip_transport *ka_transport;  /**< Transport for keep-alive.	*/
     pj_sockaddr	     ka_target;	    /**< Destination address for K-A	*/
diff --git a/pjsip/src/pjsip-ua/sip_reg.c b/pjsip/src/pjsip-ua/sip_reg.c
index 3975028..5190bb0 100644
--- a/pjsip/src/pjsip-ua/sip_reg.c
+++ b/pjsip/src/pjsip-ua/sip_reg.c
@@ -203,6 +203,7 @@
     info->is_busy = (pj_atomic_get(regc->busy_ctr) || regc->has_tsx);
     info->auto_reg = regc->auto_reg;
     info->interval = regc->expires;
+    info->transport = regc->last_transport;
     
     if (regc->has_tsx)
 	info->next_reg = 0;
@@ -1202,12 +1203,33 @@
     else
 	regc->current_op = REGC_REGISTERING;
 
+    /* Prevent deletion of tdata, e.g: when something wrong in sending,
+     * we need tdata to retrieve the transport.
+     */
+    pjsip_tx_data_add_ref(tdata);
+
     status = pjsip_endpt_send_request(regc->endpt, tdata, REGC_TSX_TIMEOUT,
 				      regc, &tsx_callback);
     if (status!=PJ_SUCCESS) {
 	PJ_LOG(4,(THIS_FILE, "Error sending request, status=%d", status));
     }
 
+    /* Get last transport used and add reference to it */
+    if (tdata->tp_info.transport != regc->last_transport) {
+	if (regc->last_transport) {
+	    pjsip_transport_dec_ref(regc->last_transport);
+	    regc->last_transport = NULL;
+	}
+
+	if (tdata->tp_info.transport) {
+	    regc->last_transport = tdata->tp_info.transport;
+	    pjsip_transport_add_ref(regc->last_transport);
+	}
+    }
+
+    /* Release tdata */
+    pjsip_tx_data_dec_ref(tdata);
+
     pj_lock_release(regc->lock);
 
     /* Delete the record if user destroy regc during the callback. */
diff --git a/pjsip/src/pjsip/sip_transport_tcp.c b/pjsip/src/pjsip/sip_transport_tcp.c
index d8f74a9..fa03f14 100644
--- a/pjsip/src/pjsip/sip_transport_tcp.c
+++ b/pjsip/src/pjsip/sip_transport_tcp.c
@@ -177,6 +177,12 @@
     if (tcp->base.is_shutdown)
 	return;
 
+    /* Prevent immediate transport destroy by application, as transport
+     * state notification callback may be stacked and transport instance
+     * must remain valid at any point in the callback.
+     */
+    pjsip_transport_add_ref(&tcp->base);
+
     /* Notify application of transport disconnected state */
     state_cb = pjsip_tpmgr_get_status_cb(tcp->base.tpmgr);
     if (state_cb) {
@@ -193,6 +199,9 @@
      * procedure for this transport.
      */
     pjsip_transport_shutdown(&tcp->base);
+
+    /* Now, it is ok to destroy the transport. */
+    pjsip_transport_dec_ref(&tcp->base);
 }
 
 
diff --git a/pjsip/src/pjsip/sip_transport_tls.c b/pjsip/src/pjsip/sip_transport_tls.c
index 1fd5c7b..fa5cd2b 100644
--- a/pjsip/src/pjsip/sip_transport_tls.c
+++ b/pjsip/src/pjsip/sip_transport_tls.c
@@ -184,6 +184,12 @@
     if (tls->base.is_shutdown)
 	return;
 
+    /* Prevent immediate transport destroy by application, as transport
+     * state notification callback may be stacked and transport instance
+     * must remain valid at any point in the callback.
+     */
+    pjsip_transport_add_ref(&tls->base);
+
     /* Notify application of transport disconnected state */
     state_cb = pjsip_tpmgr_get_status_cb(tls->base.tpmgr);
     if (state_cb) {
@@ -200,6 +206,9 @@
      * procedure for this transport.
      */
     pjsip_transport_shutdown(&tls->base);
+
+    /* Now, it is ok to destroy the transport. */
+    pjsip_transport_dec_ref(&tls->base);
 }
 
 
@@ -517,7 +526,7 @@
 			       struct tls_transport **p_tls)
 {
     struct tls_transport *tls;
-    const pj_str_t ka_pkt = PJSIP_TCP_KEEP_ALIVE_DATA;
+    const pj_str_t ka_pkt = PJSIP_TLS_KEEP_ALIVE_DATA;
     pj_status_t status;
     
 
@@ -578,7 +587,12 @@
     
     sockaddr_to_host_port(pool, &tls->base.local_name, 
 			  (pj_sockaddr_in*)&tls->base.local_addr);
-    sockaddr_to_host_port(pool, &tls->base.remote_name, remote);
+    if (tls->remote_name.slen) {
+	tls->base.remote_name.host = tls->remote_name;
+	tls->base.remote_name.port = pj_sockaddr_in_get_port(remote);
+    } else {
+	sockaddr_to_host_port(pool, &tls->base.remote_name, remote);
+    }
 
     tls->base.endpt = listener->endpt;
     tls->base.tpmgr = listener->tpmgr;
@@ -1075,8 +1089,8 @@
 	tls_destroy(&tls->base, status);
     } else {
 	/* Start keep-alive timer */
-	if (PJSIP_TCP_KEEP_ALIVE_INTERVAL) {
-	    pj_time_val delay = {PJSIP_TCP_KEEP_ALIVE_INTERVAL, 0};
+	if (PJSIP_TLS_KEEP_ALIVE_INTERVAL) {
+	    pj_time_val delay = {PJSIP_TLS_KEEP_ALIVE_INTERVAL, 0};
 	    pjsip_endpt_schedule_timer(listener->endpt, 
 				       &tls->ka_timer, 
 				       &delay);
@@ -1507,8 +1521,8 @@
     tls_flush_pending_tx(tls);
 
     /* Start keep-alive timer */
-    if (PJSIP_TCP_KEEP_ALIVE_INTERVAL) {
-	pj_time_val delay = { PJSIP_TCP_KEEP_ALIVE_INTERVAL, 0 };
+    if (PJSIP_TLS_KEEP_ALIVE_INTERVAL) {
+	pj_time_val delay = { PJSIP_TLS_KEEP_ALIVE_INTERVAL, 0 };
 	pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer, 
 				   &delay);
 	tls->ka_timer.id = PJ_TRUE;
@@ -1540,9 +1554,9 @@
     pj_gettimeofday(&now);
     PJ_TIME_VAL_SUB(now, tls->last_activity);
 
-    if (now.sec > 0 && now.sec < PJSIP_TCP_KEEP_ALIVE_INTERVAL) {
+    if (now.sec > 0 && now.sec < PJSIP_TLS_KEEP_ALIVE_INTERVAL) {
 	/* There has been activity, so don't send keep-alive */
-	delay.sec = PJSIP_TCP_KEEP_ALIVE_INTERVAL - now.sec;
+	delay.sec = PJSIP_TLS_KEEP_ALIVE_INTERVAL - now.sec;
 	delay.msec = 0;
 
 	pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer, 
@@ -1570,7 +1584,7 @@
     }
 
     /* Register next keep-alive */
-    delay.sec = PJSIP_TCP_KEEP_ALIVE_INTERVAL;
+    delay.sec = PJSIP_TLS_KEEP_ALIVE_INTERVAL;
     delay.msec = 0;
 
     pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer, 
diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c
index 1809662..8c2b942 100644
--- a/pjsip/src/pjsua-lib/pjsua_acc.c
+++ b/pjsip/src/pjsua-lib/pjsua_acc.c
@@ -24,6 +24,8 @@
 #define THIS_FILE		"pjsua_acc.c"
 
 
+static void schedule_reregistration(pjsua_acc *acc);
+
 /*
  * Get number of current accounts.
  */
@@ -442,6 +444,9 @@
 
     PJSUA_LOCK();
 
+    /* Cancel any re-registration timer */
+    pjsua_cancel_timer(&pjsua_var.acc[acc_id].auto_rereg.timer);
+
     /* Delete registration */
     if (pjsua_var.acc[acc_id].regc != NULL) {
 	pjsua_acc_set_registration(acc_id, PJ_FALSE);
@@ -1031,6 +1036,10 @@
 
     } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) {
 
+	/* Update auto registration flag */
+	acc->auto_rereg.active = PJ_FALSE;
+	acc->auto_rereg.attempt_cnt = 0;
+
 	if (param->expiration < 1) {
 	    pjsip_regc_destroy(acc->regc);
 	    acc->regc = NULL;
@@ -1082,6 +1091,21 @@
     if (pjsua_var.ua_cfg.cb.on_reg_state)
 	(*pjsua_var.ua_cfg.cb.on_reg_state)(acc->index);
 
+    /* Check if we need to auto retry registration. Basically, registration
+     * failure codes triggering auto-retry are those of temporal failures
+     * considered to be recoverable in relatively short term.
+     */
+    if (acc->cfg.reg_retry_interval && 
+	(param->code == PJSIP_SC_REQUEST_TIMEOUT ||
+	 param->code == PJSIP_SC_INTERNAL_SERVER_ERROR ||
+	 param->code == PJSIP_SC_BAD_GATEWAY ||
+	 param->code == PJSIP_SC_SERVICE_UNAVAILABLE ||
+	 param->code == PJSIP_SC_SERVER_TIMEOUT ||
+	 PJSIP_IS_STATUS_IN_CLASS(param->code, 600))) /* Global failure */
+    {
+	schedule_reregistration(acc);
+    }
+
     PJSUA_UNLOCK();
 }
 
@@ -1220,6 +1244,12 @@
 
     PJSUA_LOCK();
 
+    /* Cancel any re-registration timer */
+    pjsua_cancel_timer(&pjsua_var.acc[acc_id].auto_rereg.timer);
+
+    /* Reset pointer to registration transport */
+    pjsua_var.acc[acc_id].auto_rereg.reg_tp = NULL;
+
     if (renew) {
 	if (pjsua_var.acc[acc_id].regc == NULL) {
 	    status = pjsua_regc_init(acc_id);
@@ -1275,6 +1305,14 @@
 	status = pjsip_regc_send( pjsua_var.acc[acc_id].regc, tdata );
     }
 
+    /* Update pointer to registration transport */
+    if (status == PJ_SUCCESS) {
+	pjsip_regc_info reg_info;
+
+	pjsip_regc_get_info(pjsua_var.acc[acc_id].regc, &reg_info);
+	pjsua_var.acc[acc_id].auto_rereg.reg_tp = reg_info.transport;
+    }
+
     if (status != PJ_SUCCESS) {
 	pjsua_perror(THIS_FILE, "Unable to create/send REGISTER", 
 		     status);
@@ -1925,3 +1963,122 @@
     return PJ_SUCCESS;
 }
 
+
+/* Auto re-registration timeout callback */
+static void auto_rereg_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te)
+{
+    pjsua_acc *acc;
+    pj_status_t status;
+
+    PJ_UNUSED_ARG(th);
+    acc = (pjsua_acc*) te->user_data;
+    pj_assert(acc);
+
+    PJSUA_LOCK();
+
+    if (!acc->valid || !acc->auto_rereg.active)
+	goto on_return;
+
+    /* Start re-registration */
+    acc->auto_rereg.attempt_cnt++;
+    status = pjsua_acc_set_registration(acc->index, PJ_TRUE);
+    if (status != PJ_SUCCESS)
+	schedule_reregistration(acc);
+
+    /* If configured, disconnect calls of this account after the first
+     * reregistration attempt failed.
+     */
+    if (acc->cfg.drop_calls_on_reg_fail && acc->auto_rereg.attempt_cnt > 1)
+    {
+	unsigned i, cnt;
+
+	for (i = 0, cnt = 0; i < pjsua_var.ua_cfg.max_calls; ++i) {
+	    if (pjsua_var.calls[i].acc_id == acc->index) {
+		pjsua_call_hangup(i, 0, NULL, NULL);
+		++cnt;
+	    }
+	}
+
+	if (cnt) {
+	    PJ_LOG(3, (THIS_FILE, "Disconnecting %d call(s) of account #%d "
+				  "after reregistration attempt failed",
+				  cnt, acc->index));
+	}
+    }
+
+on_return:
+
+    PJSUA_UNLOCK();
+}
+
+
+/* Schedule reregistration for specified account. Note that the first 
+ * re-registration after a registration failure will be done immediately.
+ * Also note that this function should be called within PJSUA mutex.
+ */
+static void schedule_reregistration(pjsua_acc *acc)
+{
+    pj_time_val delay;
+
+    pj_assert(acc && acc->valid && acc->cfg.reg_retry_interval);
+
+    /* Cancel any re-registration timer */
+    pjsua_cancel_timer(&acc->auto_rereg.timer);
+
+    /* Update re-registration flag */
+    acc->auto_rereg.active = PJ_TRUE;
+
+    /* Set up timer for reregistration */
+    acc->auto_rereg.timer.cb = &auto_rereg_timer_cb;
+    acc->auto_rereg.timer.user_data = acc;
+
+    /* Reregistration attempt. The first attempt will be done immediately. */
+    delay.sec = acc->auto_rereg.attempt_cnt? acc->cfg.reg_retry_interval : 0;
+    delay.msec = 0;
+    pjsua_schedule_timer(&acc->auto_rereg.timer, &delay);
+}
+
+
+/* Internal function to perform auto-reregistration on transport 
+ * connection/disconnection events.
+ */
+void pjsua_acc_on_tp_state_changed(pjsip_transport *tp,
+				   pjsip_transport_state state,
+				   const pjsip_transport_state_info *info)
+{
+    unsigned i;
+
+    PJ_UNUSED_ARG(info);
+
+    /* Only care for transport disconnection events */
+    if (state != PJSIP_TP_STATE_DISCONNECTED)
+	return;
+
+    /* Shutdown this transport, to make sure that the transport manager 
+     * will create a new transport for reconnection.
+     */
+    pjsip_transport_shutdown(tp);
+
+    PJSUA_LOCK();
+
+    /* Enumerate accounts using this transport and perform actions
+     * based on the transport state.
+     */
+    for (i = 0; i < PJ_ARRAY_SIZE(pjsua_var.acc); ++i) {
+	pjsua_acc *acc = &pjsua_var.acc[i];
+
+	/* Skip if this account is not valid OR auto re-registration
+	 * feature is disabled OR this transport is not used by this account.
+	 */
+	if (!acc->valid || !acc->cfg.reg_retry_interval || 
+	    tp != acc->auto_rereg.reg_tp)
+	{
+	    continue;
+	}
+
+	/* Schedule reregistration for this account */
+	schedule_reregistration(acc);
+    }
+
+    PJSUA_UNLOCK();
+}
diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c
index 41eda87..f25d2e5 100644
--- a/pjsip/src/pjsua-lib/pjsua_core.c
+++ b/pjsip/src/pjsua-lib/pjsua_core.c
@@ -176,6 +176,7 @@
     cfg->use_srtp = pjsua_var.ua_cfg.use_srtp;
     cfg->srtp_secure_signaling = pjsua_var.ua_cfg.srtp_secure_signaling;
 #endif
+    cfg->reg_retry_interval = PJSUA_REG_RETRY_INTERVAL;
 }
 
 PJ_DEF(void) pjsua_buddy_config_default(pjsua_buddy_config *cfg)
@@ -1525,6 +1526,10 @@
     return str;
 }
 
+void pjsua_acc_on_tp_state_changed(pjsip_transport *tp,
+				   pjsip_transport_state state,
+				   const pjsip_transport_state_info *info);
+
 /* Callback to receive transport state notifications */
 static void on_tp_state_callback(pjsip_transport *tp,
 				 pjsip_transport_state state,
@@ -1536,6 +1541,7 @@
     if (pjsua_var.old_tp_cb) {
 	(*pjsua_var.old_tp_cb)(tp, state, info);
     }
+    pjsua_acc_on_tp_state_changed(tp, state, info);
 }
 
 /*