Ticket #407: keep-alive for UDP transports in PJSUA-LIB

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@1536 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/build.symbian/pjsua_libU.def b/build.symbian/pjsua_libU.def
index fd3a628..911efbc 100644
--- a/build.symbian/pjsua_libU.def
+++ b/build.symbian/pjsua_libU.def
@@ -2,120 +2,121 @@
 	pjsua_acc_add                            @ 1 NONAME
 	pjsua_acc_add_local                      @ 2 NONAME
 	pjsua_acc_config_default                 @ 3 NONAME
-	pjsua_acc_create_request                 @ 4 NONAME
-	pjsua_acc_create_uac_contact             @ 5 NONAME
-	pjsua_acc_create_uas_contact             @ 6 NONAME
-	pjsua_acc_del                            @ 7 NONAME
-	pjsua_acc_enum_info                      @ 8 NONAME
-	pjsua_acc_find_for_incoming              @ 9 NONAME
-	pjsua_acc_find_for_outgoing              @ 10 NONAME
-	pjsua_acc_get_count                      @ 11 NONAME
-	pjsua_acc_get_default                    @ 12 NONAME
-	pjsua_acc_get_info                       @ 13 NONAME
-	pjsua_acc_is_valid                       @ 14 NONAME
-	pjsua_acc_modify                         @ 15 NONAME
-	pjsua_acc_set_default                    @ 16 NONAME
-	pjsua_acc_set_online_status              @ 17 NONAME
-	pjsua_acc_set_online_status2             @ 18 NONAME
-	pjsua_acc_set_registration               @ 19 NONAME
-	pjsua_acc_set_transport                  @ 20 NONAME
-	pjsua_buddy_add                          @ 21 NONAME
-	pjsua_buddy_config_default               @ 22 NONAME
-	pjsua_buddy_del                          @ 23 NONAME
-	pjsua_buddy_get_info                     @ 24 NONAME
-	pjsua_buddy_is_valid                     @ 25 NONAME
-	pjsua_buddy_subscribe_pres               @ 26 NONAME
-	pjsua_buddy_update_pres                  @ 27 NONAME
-	pjsua_call_answer                        @ 28 NONAME
-	pjsua_call_dial_dtmf                     @ 29 NONAME
-	pjsua_call_dump                          @ 30 NONAME
-	pjsua_call_get_conf_port                 @ 31 NONAME
-	pjsua_call_get_count                     @ 32 NONAME
-	pjsua_call_get_info                      @ 33 NONAME
-	pjsua_call_get_max_count                 @ 34 NONAME
-	pjsua_call_get_rem_nat_type              @ 35 NONAME
-	pjsua_call_get_user_data                 @ 36 NONAME
-	pjsua_call_hangup                        @ 37 NONAME
-	pjsua_call_hangup_all                    @ 38 NONAME
-	pjsua_call_has_media                     @ 39 NONAME
-	pjsua_call_is_active                     @ 40 NONAME
-	pjsua_call_make_call                     @ 41 NONAME
-	pjsua_call_reinvite                      @ 42 NONAME
-	pjsua_call_send_im                       @ 43 NONAME
-	pjsua_call_send_request                  @ 44 NONAME
-	pjsua_call_send_typing_ind               @ 45 NONAME
-	pjsua_call_set_hold                      @ 46 NONAME
-	pjsua_call_set_user_data                 @ 47 NONAME
-	pjsua_call_update                        @ 48 NONAME
-	pjsua_call_xfer                          @ 49 NONAME
-	pjsua_call_xfer_replaces                 @ 50 NONAME
-	pjsua_codec_get_param                    @ 51 NONAME
-	pjsua_codec_set_param                    @ 52 NONAME
-	pjsua_codec_set_priority                 @ 53 NONAME
-	pjsua_conf_add_port                      @ 54 NONAME
-	pjsua_conf_adjust_rx_level               @ 55 NONAME
-	pjsua_conf_adjust_tx_level               @ 56 NONAME
-	pjsua_conf_connect                       @ 57 NONAME
-	pjsua_conf_disconnect                    @ 58 NONAME
-	pjsua_conf_get_active_ports              @ 59 NONAME
-	pjsua_conf_get_max_ports                 @ 60 NONAME
-	pjsua_conf_get_port_info                 @ 61 NONAME
-	pjsua_conf_get_signal_level              @ 62 NONAME
-	pjsua_conf_remove_port                   @ 63 NONAME
-	pjsua_config_default                     @ 64 NONAME
-	pjsua_config_dup                         @ 65 NONAME
-	pjsua_create                             @ 66 NONAME
-	pjsua_destroy                            @ 67 NONAME
-	pjsua_detect_nat_type                    @ 68 NONAME
-	pjsua_dump                               @ 69 NONAME
-	pjsua_enum_accs                          @ 70 NONAME
-	pjsua_enum_buddies                       @ 71 NONAME
-	pjsua_enum_calls                         @ 72 NONAME
-	pjsua_enum_codecs                        @ 73 NONAME
-	pjsua_enum_conf_ports                    @ 74 NONAME
-	pjsua_enum_snd_devs                      @ 75 NONAME
-	pjsua_enum_transports                    @ 76 NONAME
-	pjsua_get_buddy_count                    @ 77 NONAME
-	pjsua_get_ec_tail                        @ 78 NONAME
-	pjsua_get_nat_type                       @ 79 NONAME
-	pjsua_get_pjmedia_endpt                  @ 80 NONAME
-	pjsua_get_pjsip_endpt                    @ 81 NONAME
-	pjsua_get_pool_factory                   @ 82 NONAME
-	pjsua_get_snd_dev                        @ 83 NONAME
-	pjsua_get_var                            @ 84 NONAME
-	pjsua_handle_events                      @ 85 NONAME
-	pjsua_im_send                            @ 86 NONAME
-	pjsua_im_typing                          @ 87 NONAME
-	pjsua_init                               @ 88 NONAME
-	pjsua_logging_config_default             @ 89 NONAME
-	pjsua_logging_config_dup                 @ 90 NONAME
-	pjsua_media_config_default               @ 91 NONAME
-	pjsua_media_transports_create            @ 92 NONAME
-	pjsua_msg_data_init                      @ 93 NONAME
-	pjsua_perror                             @ 94 NONAME
-	pjsua_player_create                      @ 95 NONAME
-	pjsua_player_destroy                     @ 96 NONAME
-	pjsua_player_get_conf_port               @ 97 NONAME
-	pjsua_player_get_port                    @ 98 NONAME
-	pjsua_player_set_pos                     @ 99 NONAME
-	pjsua_playlist_create                    @ 100 NONAME
-	pjsua_pool_create                        @ 101 NONAME
-	pjsua_pres_dump                          @ 102 NONAME
-	pjsua_reconfigure_logging                @ 103 NONAME
-	pjsua_recorder_create                    @ 104 NONAME
-	pjsua_recorder_destroy                   @ 105 NONAME
-	pjsua_recorder_get_conf_port             @ 106 NONAME
-	pjsua_recorder_get_port                  @ 107 NONAME
-	pjsua_set_ec                             @ 108 NONAME
-	pjsua_set_no_snd_dev                     @ 109 NONAME
-	pjsua_set_null_snd_dev                   @ 110 NONAME
-	pjsua_set_snd_dev                        @ 111 NONAME
-	pjsua_start                              @ 112 NONAME
-	pjsua_transport_close                    @ 113 NONAME
-	pjsua_transport_config_default           @ 114 NONAME
-	pjsua_transport_config_dup               @ 115 NONAME
-	pjsua_transport_create                   @ 116 NONAME
-	pjsua_transport_get_info                 @ 117 NONAME
-	pjsua_transport_register                 @ 118 NONAME
-	pjsua_transport_set_enable               @ 119 NONAME
-	pjsua_verify_sip_url                     @ 120 NONAME
+	pjsua_acc_config_dup                     @ 4 NONAME
+	pjsua_acc_create_request                 @ 5 NONAME
+	pjsua_acc_create_uac_contact             @ 6 NONAME
+	pjsua_acc_create_uas_contact             @ 7 NONAME
+	pjsua_acc_del                            @ 8 NONAME
+	pjsua_acc_enum_info                      @ 9 NONAME
+	pjsua_acc_find_for_incoming              @ 10 NONAME
+	pjsua_acc_find_for_outgoing              @ 11 NONAME
+	pjsua_acc_get_count                      @ 12 NONAME
+	pjsua_acc_get_default                    @ 13 NONAME
+	pjsua_acc_get_info                       @ 14 NONAME
+	pjsua_acc_is_valid                       @ 15 NONAME
+	pjsua_acc_modify                         @ 16 NONAME
+	pjsua_acc_set_default                    @ 17 NONAME
+	pjsua_acc_set_online_status              @ 18 NONAME
+	pjsua_acc_set_online_status2             @ 19 NONAME
+	pjsua_acc_set_registration               @ 20 NONAME
+	pjsua_acc_set_transport                  @ 21 NONAME
+	pjsua_buddy_add                          @ 22 NONAME
+	pjsua_buddy_config_default               @ 23 NONAME
+	pjsua_buddy_del                          @ 24 NONAME
+	pjsua_buddy_get_info                     @ 25 NONAME
+	pjsua_buddy_is_valid                     @ 26 NONAME
+	pjsua_buddy_subscribe_pres               @ 27 NONAME
+	pjsua_buddy_update_pres                  @ 28 NONAME
+	pjsua_call_answer                        @ 29 NONAME
+	pjsua_call_dial_dtmf                     @ 30 NONAME
+	pjsua_call_dump                          @ 31 NONAME
+	pjsua_call_get_conf_port                 @ 32 NONAME
+	pjsua_call_get_count                     @ 33 NONAME
+	pjsua_call_get_info                      @ 34 NONAME
+	pjsua_call_get_max_count                 @ 35 NONAME
+	pjsua_call_get_rem_nat_type              @ 36 NONAME
+	pjsua_call_get_user_data                 @ 37 NONAME
+	pjsua_call_hangup                        @ 38 NONAME
+	pjsua_call_hangup_all                    @ 39 NONAME
+	pjsua_call_has_media                     @ 40 NONAME
+	pjsua_call_is_active                     @ 41 NONAME
+	pjsua_call_make_call                     @ 42 NONAME
+	pjsua_call_reinvite                      @ 43 NONAME
+	pjsua_call_send_im                       @ 44 NONAME
+	pjsua_call_send_request                  @ 45 NONAME
+	pjsua_call_send_typing_ind               @ 46 NONAME
+	pjsua_call_set_hold                      @ 47 NONAME
+	pjsua_call_set_user_data                 @ 48 NONAME
+	pjsua_call_update                        @ 49 NONAME
+	pjsua_call_xfer                          @ 50 NONAME
+	pjsua_call_xfer_replaces                 @ 51 NONAME
+	pjsua_codec_get_param                    @ 52 NONAME
+	pjsua_codec_set_param                    @ 53 NONAME
+	pjsua_codec_set_priority                 @ 54 NONAME
+	pjsua_conf_add_port                      @ 55 NONAME
+	pjsua_conf_adjust_rx_level               @ 56 NONAME
+	pjsua_conf_adjust_tx_level               @ 57 NONAME
+	pjsua_conf_connect                       @ 58 NONAME
+	pjsua_conf_disconnect                    @ 59 NONAME
+	pjsua_conf_get_active_ports              @ 60 NONAME
+	pjsua_conf_get_max_ports                 @ 61 NONAME
+	pjsua_conf_get_port_info                 @ 62 NONAME
+	pjsua_conf_get_signal_level              @ 63 NONAME
+	pjsua_conf_remove_port                   @ 64 NONAME
+	pjsua_config_default                     @ 65 NONAME
+	pjsua_config_dup                         @ 66 NONAME
+	pjsua_create                             @ 67 NONAME
+	pjsua_destroy                            @ 68 NONAME
+	pjsua_detect_nat_type                    @ 69 NONAME
+	pjsua_dump                               @ 70 NONAME
+	pjsua_enum_accs                          @ 71 NONAME
+	pjsua_enum_buddies                       @ 72 NONAME
+	pjsua_enum_calls                         @ 73 NONAME
+	pjsua_enum_codecs                        @ 74 NONAME
+	pjsua_enum_conf_ports                    @ 75 NONAME
+	pjsua_enum_snd_devs                      @ 76 NONAME
+	pjsua_enum_transports                    @ 77 NONAME
+	pjsua_get_buddy_count                    @ 78 NONAME
+	pjsua_get_ec_tail                        @ 79 NONAME
+	pjsua_get_nat_type                       @ 80 NONAME
+	pjsua_get_pjmedia_endpt                  @ 81 NONAME
+	pjsua_get_pjsip_endpt                    @ 82 NONAME
+	pjsua_get_pool_factory                   @ 83 NONAME
+	pjsua_get_snd_dev                        @ 84 NONAME
+	pjsua_get_var                            @ 85 NONAME
+	pjsua_handle_events                      @ 86 NONAME
+	pjsua_im_send                            @ 87 NONAME
+	pjsua_im_typing                          @ 88 NONAME
+	pjsua_init                               @ 89 NONAME
+	pjsua_logging_config_default             @ 90 NONAME
+	pjsua_logging_config_dup                 @ 91 NONAME
+	pjsua_media_config_default               @ 92 NONAME
+	pjsua_media_transports_create            @ 93 NONAME
+	pjsua_msg_data_init                      @ 94 NONAME
+	pjsua_perror                             @ 95 NONAME
+	pjsua_player_create                      @ 96 NONAME
+	pjsua_player_destroy                     @ 97 NONAME
+	pjsua_player_get_conf_port               @ 98 NONAME
+	pjsua_player_get_port                    @ 99 NONAME
+	pjsua_player_set_pos                     @ 100 NONAME
+	pjsua_playlist_create                    @ 101 NONAME
+	pjsua_pool_create                        @ 102 NONAME
+	pjsua_pres_dump                          @ 103 NONAME
+	pjsua_reconfigure_logging                @ 104 NONAME
+	pjsua_recorder_create                    @ 105 NONAME
+	pjsua_recorder_destroy                   @ 106 NONAME
+	pjsua_recorder_get_conf_port             @ 107 NONAME
+	pjsua_recorder_get_port                  @ 108 NONAME
+	pjsua_set_ec                             @ 109 NONAME
+	pjsua_set_no_snd_dev                     @ 110 NONAME
+	pjsua_set_null_snd_dev                   @ 111 NONAME
+	pjsua_set_snd_dev                        @ 112 NONAME
+	pjsua_start                              @ 113 NONAME
+	pjsua_transport_close                    @ 114 NONAME
+	pjsua_transport_config_default           @ 115 NONAME
+	pjsua_transport_config_dup               @ 116 NONAME
+	pjsua_transport_create                   @ 117 NONAME
+	pjsua_transport_get_info                 @ 118 NONAME
+	pjsua_transport_register                 @ 119 NONAME
+	pjsua_transport_set_enable               @ 120 NONAME
+	pjsua_verify_sip_url                     @ 121 NONAME
diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h
index 579d722..fb324f4 100644
--- a/pjsip/include/pjsua-lib/pjsua.h
+++ b/pjsip/include/pjsua-lib/pjsua.h
@@ -1768,7 +1768,7 @@
  * Default registration interval.
  */
 #ifndef PJSUA_REG_INTERVAL
-#   define PJSUA_REG_INTERVAL	    55
+#   define PJSUA_REG_INTERVAL	    300
 #endif
 
 
@@ -1897,7 +1897,7 @@
 
     /** 
      * Optional interval for registration, in seconds. If the value is zero, 
-     * default interval will be used (PJSUA_REG_INTERVAL, 55 seconds).
+     * default interval will be used (PJSUA_REG_INTERVAL, 300 seconds).
      */
     unsigned	    reg_timeout;
 
@@ -1947,6 +1947,28 @@
      */
     pj_bool_t auto_update_nat;
 
+    /**
+     * Set the interval for periodic keep-alive transmission for this account.
+     * If this value is zero, keep-alive will be disabled for this account.
+     * The keep-alive transmission will be sent to the registrar's address,
+     * after successful registration.
+     *
+     * Even if this setting is enabled, keep-alive transmission is only done
+     * when STUN is enabled in the global #pjsua_config, and the transport
+     * used for registration is UDP. For TCP and TLS transports, keep-alive
+     * is done by the transport themselves.
+     *
+     * Default: 15 (seconds)
+     */
+    unsigned	     ka_interval;
+
+    /**
+     * Specify the data to be transmitted as keep-alive packets.
+     *
+     * Default: CR-LF
+     */
+    pj_str_t	     ka_data;
+
 } pjsua_acc_config;
 
 
@@ -1966,6 +1988,18 @@
 
 
 /**
+ * Duplicate account config.
+ *
+ * @param pool	    Pool to be used for duplicating the config.
+ * @param dst	    Destination configuration.
+ * @param src	    Source configuration.
+ */
+PJ_DECL(void) pjsua_acc_config_dup(pj_pool_t *pool,
+				   pjsua_acc_config *dst,
+				   const pjsua_acc_config *src);
+
+
+/**
  * Account info. Application can query account info by calling 
  * #pjsua_acc_get_info().
  *
diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h
index 004c1ca..87d30b9 100644
--- a/pjsip/include/pjsua-lib/pjsua_internal.h
+++ b/pjsip/include/pjsua-lib/pjsua_internal.h
@@ -86,10 +86,14 @@
     int		     srv_port;	    /**< Port number of reg server.	*/
 
     pjsip_regc	    *regc;	    /**< Client registration session.   */
-    pj_timer_entry   reg_timer;	    /**< Registration timer.		*/
     pj_status_t	     reg_last_err;  /**< Last registration error.	*/
     int		     reg_last_code; /**< Last status last register.	*/
 
+    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	*/
+    unsigned	     ka_target_len; /**< Length of ka_target.		*/
+
     pjsip_route_hdr  route_set;	    /**< Complete route set inc. outbnd.*/
 
     unsigned	     cred_cnt;	    /**< Number of credentials.		*/
diff --git a/pjsip/src/pjsip/sip_transport.c b/pjsip/src/pjsip/sip_transport.c
index 1ac9f6f..11191c5 100644
--- a/pjsip/src/pjsip/sip_transport.c
+++ b/pjsip/src/pjsip/sip_transport.c
@@ -417,12 +417,12 @@
 PJ_DEF(char*) pjsip_tx_data_get_info( pjsip_tx_data *tdata )
 {
 
-    if (tdata==NULL || tdata->msg==NULL)
-	return "NULL";
-
     if (tdata->info)
 	return tdata->info;
 
+    if (tdata==NULL || tdata->msg==NULL)
+	return "NULL";
+
     pj_lock_acquire(tdata->lock);
     tdata->info = get_msg_info(tdata->pool, tdata->obj_name, tdata->msg);
     pj_lock_release(tdata->lock);
@@ -653,6 +653,8 @@
 	    return status;
 	}
 
+	tdata->info = "raw";
+
 	/* Add reference counter. */
 	pjsip_tx_data_add_ref(tdata);
     }
diff --git a/pjsip/src/pjsua-lib/pjsua_acc.c b/pjsip/src/pjsua-lib/pjsua_acc.c
index 012832c..a935300 100644
--- a/pjsip/src/pjsua-lib/pjsua_acc.c
+++ b/pjsip/src/pjsua-lib/pjsua_acc.c
@@ -64,9 +64,9 @@
 /*
  * Copy account configuration.
  */
-static void copy_acc_config(pj_pool_t *pool,
-			    pjsua_acc_config *dst,
-			    const pjsua_acc_config *src)
+PJ_DEF(void) pjsua_acc_config_dup( pj_pool_t *pool,
+				   pjsua_acc_config *dst,
+				   const pjsua_acc_config *src)
 {
     unsigned i;
 
@@ -87,6 +87,9 @@
     for (i=0; i<src->cred_count; ++i) {
 	pjsip_cred_dup(pool, &dst->cred_info[i], &src->cred_info[i]);
     }
+
+    dst->ka_interval = src->ka_interval;
+    pj_strdup(pool, &dst->ka_data, &src->ka_data);
 }
 
 
@@ -275,7 +278,7 @@
 			{PJSUA_UNLOCK(); return PJ_EBUG;});
 
     /* Copy config */
-    copy_acc_config(pjsua_var.pool, &pjsua_var.acc[id].cfg, cfg);
+    pjsua_acc_config_dup(pjsua_var.pool, &pjsua_var.acc[id].cfg, cfg);
     
     /* Normalize registration timeout */
     if (pjsua_var.acc[id].cfg.reg_uri.slen &&
@@ -631,6 +634,123 @@
 	      acc->index, uri_cnt));
 }
 
+
+/* Keep alive timer callback */
+static void keep_alive_timer_cb(pj_timer_heap_t *th, pj_timer_entry *te)
+{
+    pjsua_acc *acc;
+    pjsip_tpselector tp_sel;
+    pj_time_val delay;
+    pj_status_t status;
+
+    PJ_UNUSED_ARG(th);
+
+    PJSUA_LOCK();
+
+    te->id = PJ_FALSE;
+
+    acc = (pjsua_acc*) te->user_data;
+
+    /* Select the transport to send the packet */
+    pj_bzero(&tp_sel, sizeof(tp_sel));
+    tp_sel.type = PJSIP_TPSELECTOR_TRANSPORT;
+    tp_sel.u.transport = acc->ka_transport;
+
+    PJ_LOG(5,(THIS_FILE, 
+	      "Sending %d bytes keep-alive packet for acc %d to %s:%d",
+	      acc->cfg.ka_data.slen, acc->index,
+	      pj_inet_ntoa(acc->ka_target.ipv4.sin_addr),
+	      pj_ntohs(acc->ka_target.ipv4.sin_port)));
+
+    /* Send raw packet */
+    status = pjsip_tpmgr_send_raw(pjsip_endpt_get_tpmgr(pjsua_var.endpt),
+				  PJSIP_TRANSPORT_UDP, &tp_sel,
+				  NULL, acc->cfg.ka_data.ptr, 
+				  acc->cfg.ka_data.slen, 
+				  &acc->ka_target, acc->ka_target_len,
+				  NULL, NULL);
+
+    if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+	pjsua_perror(THIS_FILE, "Error sending keep-alive packet", status);
+    }
+
+    /* Reschedule next timer */
+    delay.sec = acc->cfg.ka_interval;
+    delay.msec = 0;
+    status = pjsip_endpt_schedule_timer(pjsua_var.endpt, te, &delay);
+    if (status == PJ_SUCCESS) {
+	te->id = PJ_TRUE;
+    } else {
+	pjsua_perror(THIS_FILE, "Error starting keep-alive timer", status);
+    }
+
+    PJSUA_UNLOCK();
+}
+
+
+/* Update keep-alive for the account */
+static void update_keep_alive(pjsua_acc *acc, pj_bool_t start,
+			      struct pjsip_regc_cbparam *param)
+{
+    /* In all cases, stop keep-alive timer if it's running. */
+    if (acc->ka_timer.id) {
+	pjsip_endpt_cancel_timer(pjsua_var.endpt, &acc->ka_timer);
+	acc->ka_timer.id = PJ_FALSE;
+
+	pjsip_transport_dec_ref(acc->ka_transport);
+	acc->ka_transport = NULL;
+    }
+
+    if (start) {
+	pj_time_val delay;
+	pj_status_t status;
+
+	/* Only do keep-alive if:
+	 *  - STUN is enabled in global config, and
+	 *  - ka_interval is not zero in the account, and
+	 *  - transport is UDP.
+	 */
+	if (pjsua_var.stun_srv.ipv4.sin_family == 0 ||
+	    acc->cfg.ka_interval == 0 ||
+	    param->rdata->tp_info.transport->key.type != PJSIP_TRANSPORT_UDP)
+	{
+	    /* Keep alive is not necessary */
+	    return;
+	}
+
+	/* Save transport and destination address. */
+	acc->ka_transport = param->rdata->tp_info.transport;
+	pjsip_transport_add_ref(acc->ka_transport);
+	pj_memcpy(&acc->ka_target, &param->rdata->pkt_info.src_addr,
+		  param->rdata->pkt_info.src_addr_len);
+	acc->ka_target_len = param->rdata->pkt_info.src_addr_len;
+
+	/* Setup and start the timer */
+	acc->ka_timer.cb = &keep_alive_timer_cb;
+	acc->ka_timer.user_data = (void*)acc;
+
+	delay.sec = acc->cfg.ka_interval;
+	delay.msec = 0;
+	status = pjsip_endpt_schedule_timer(pjsua_var.endpt, &acc->ka_timer, 
+					    &delay);
+	if (status == PJ_SUCCESS) {
+	    acc->ka_timer.id = PJ_TRUE;
+	    PJ_LOG(4,(THIS_FILE, "Keep-alive timer started for acc %d, "
+				 "destination:%s:%d, interval:%ds",
+				 acc->index,
+				 param->rdata->pkt_info.src_name,
+				 param->rdata->pkt_info.src_port,
+				 acc->cfg.ka_interval));
+	} else {
+	    acc->ka_timer.id = PJ_FALSE;
+	    pjsip_transport_dec_ref(acc->ka_transport);
+	    acc->ka_transport = NULL;
+	    pjsua_perror(THIS_FILE, "Error starting keep-alive timer", status);
+	}
+    }
+}
+
+
 /*
  * This callback is called by pjsip_regc when outgoing register
  * request has completed.
@@ -654,6 +774,9 @@
 	pjsip_regc_destroy(acc->regc);
 	acc->regc = NULL;
 	
+	/* Stop keep-alive timer if any. */
+	update_keep_alive(acc, PJ_FALSE, NULL);
+
     } else if (param->code < 0 || param->code >= 300) {
 	PJ_LOG(2, (THIS_FILE, "SIP registration failed, status=%d (%.*s)", 
 		   param->code, 
@@ -661,11 +784,18 @@
 	pjsip_regc_destroy(acc->regc);
 	acc->regc = NULL;
 
+	/* Stop keep-alive timer if any. */
+	update_keep_alive(acc, PJ_FALSE, NULL);
+
     } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) {
 
 	if (param->expiration < 1) {
 	    pjsip_regc_destroy(acc->regc);
 	    acc->regc = NULL;
+
+	    /* Stop keep-alive timer if any. */
+	    update_keep_alive(acc, PJ_FALSE, NULL);
+
 	    PJ_LOG(3,(THIS_FILE, "%s: unregistration success",
 		      pjsua_var.acc[acc->index].cfg.id.ptr));
 	} else {
@@ -687,6 +817,9 @@
 		       (int)param->reason.slen, param->reason.ptr,
 		       param->expiration));
 
+	    /* Start keep-alive timer if necessary. */
+	    update_keep_alive(acc, PJ_TRUE, param);
+
 	    /* Send initial PUBLISH if it is enabled */
 	    if (acc->cfg.publish_enabled && acc->publish_sess==NULL)
 		pjsua_pres_init_publish_acc(acc->index);
diff --git a/pjsip/src/pjsua-lib/pjsua_core.c b/pjsip/src/pjsua-lib/pjsua_core.c
index 2cfca8d..472df63 100644
--- a/pjsip/src/pjsua-lib/pjsua_core.c
+++ b/pjsip/src/pjsua-lib/pjsua_core.c
@@ -140,6 +140,8 @@
     cfg->transport_id = PJSUA_INVALID_ID;
     cfg->auto_update_nat = PJ_TRUE;
     cfg->require_100rel = pjsua_var.ua_cfg.require_100rel;
+    cfg->ka_interval = 15;
+    cfg->ka_data = pj_str("\r\n");
 }
 
 PJ_DEF(void) pjsua_buddy_config_default(pjsua_buddy_config *cfg)