From e27b8a021b6768b20189ba7c9e3d8ba303172010 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Thu, 4 Oct 2007 09:50:36 +0000 Subject: Ticket #95: Keep-alive mechanism for TCP and TLS transports git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1473 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip/build/pjsip_core.dsp | 2 + pjsip/include/pjsip/sip_config.h | 50 ++++++++++++- pjsip/src/pjsip/sip_transport_tcp.c | 109 ++++++++++++++++++++++++++-- pjsip/src/pjsip/sip_transport_tls_ossl.c | 117 ++++++++++++++++++++++++++++--- 4 files changed, 263 insertions(+), 15 deletions(-) diff --git a/pjsip/build/pjsip_core.dsp b/pjsip/build/pjsip_core.dsp index 38f1ab03..2cd4fc06 100644 --- a/pjsip/build/pjsip_core.dsp +++ b/pjsip/build/pjsip_core.dsp @@ -40,6 +40,7 @@ RSC=rc.exe # PROP Output_Dir ".\output\pjsip-core-i386-win32-vc6-release" # PROP Intermediate_Dir ".\output\pjsip-core-i386-win32-vc6-release" # PROP Target_Dir "" +F90=df.exe # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c # ADD CPP /nologo /MD /W4 /Zi /O2 /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /D "NDEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /c # SUBTRACT CPP /YX @@ -64,6 +65,7 @@ LIB32=link.exe -lib # PROP Output_Dir ".\output\pjsip-core-i386-win32-vc6-debug" # PROP Intermediate_Dir ".\output\pjsip-core-i386-win32-vc6-debug" # PROP Target_Dir "" +F90=df.exe # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c # ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /I "../../pjlib-util/include" /D "_DEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /FD /GZ /c # SUBTRACT CPP /YX diff --git a/pjsip/include/pjsip/sip_config.h b/pjsip/include/pjsip/sip_config.h index 846c2a57..3d386097 100644 --- a/pjsip/include/pjsip/sip_config.h +++ b/pjsip/include/pjsip/sip_config.h @@ -265,10 +265,10 @@ * Idle timeout interval to be applied to transports with no usage * before the transport is destroyed. Value is in seconds. * - * Default: 60 + * Default: 600 */ #ifndef PJSIP_TRANSPORT_IDLE_TIME -# define PJSIP_TRANSPORT_IDLE_TIME 60 +# define PJSIP_TRANSPORT_IDLE_TIME 600 #endif @@ -297,6 +297,52 @@ #endif +/** + * Set the interval to send keep-alive packet for TCP transports. + * If the value is zero, keep-alive will be disabled for TCP. + * + * Default: 90 (seconds) + * + * @see PJSIP_TCP_KEEP_ALIVE_DATA + */ +#ifndef PJSIP_TCP_KEEP_ALIVE_INTERVAL +# define PJSIP_TCP_KEEP_ALIVE_INTERVAL 90 +#endif + + +/** + * Set the payload of the TCP keep-alive packet. + * + * Default: CRLF + */ +#ifndef PJSIP_TCP_KEEP_ALIVE_DATA +# define PJSIP_TCP_KEEP_ALIVE_DATA { "\r\n", 2 } +#endif + + +/** + * Set the interval to send keep-alive packet for TLS transports. + * If the value is zero, keep-alive will be disabled for TLS. + * + * Default: 90 (seconds) + * + * @see PJSIP_TLS_KEEP_ALIVE_DATA + */ +#ifndef PJSIP_TLS_KEEP_ALIVE_INTERVAL +# define PJSIP_TLS_KEEP_ALIVE_INTERVAL 90 +#endif + + +/** + * Set the payload of the TLS keep-alive packet. + * + * Default: CRLF + */ +#ifndef PJSIP_TLS_KEEP_ALIVE_DATA +# define PJSIP_TLS_KEEP_ALIVE_DATA { "\r\n", 2 } +#endif + + /** * This macro specifies whether full DNS resolution should be used. * When enabled, #pjsip_resolve() will perform asynchronous DNS SRV and diff --git a/pjsip/src/pjsip/sip_transport_tcp.c b/pjsip/src/pjsip/sip_transport_tcp.c index 585dad1f..1bf0a030 100644 --- a/pjsip/src/pjsip/sip_transport_tcp.c +++ b/pjsip/src/pjsip/sip_transport_tcp.c @@ -41,7 +41,6 @@ #define POOL_TP_INIT 4000 #define POOL_TP_INC 4002 - struct tcp_listener; struct tcp_transport; @@ -112,6 +111,11 @@ struct tcp_transport pj_ioqueue_key_t *key; pj_bool_t has_pending_connect; + /* Keep-alive timer. */ + pj_timer_entry ka_timer; + pj_time_val last_activity; + pjsip_tx_data_op_key ka_op_key; + pj_str_t ka_pkt; /* TCP transport can only have one rdata! * Otherwise chunks of incoming PDU may be received on different @@ -476,6 +480,8 @@ static void on_write_complete(pj_ioqueue_key_t *key, static void on_connect_complete(pj_ioqueue_key_t *key, pj_status_t status); +/* TCP keep-alive timer callback */ +static void tcp_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e); /* * Common function to create TCP transport, called when pending accept() and @@ -491,6 +497,7 @@ static pj_status_t tcp_create( struct tcp_listener *listener, struct tcp_transport *tcp; pj_ioqueue_t *ioqueue; pj_ioqueue_callback tcp_callback; + const pj_str_t ka_pkt = PJSIP_TCP_KEEP_ALIVE_DATA; pj_status_t status; @@ -569,6 +576,12 @@ static pj_status_t tcp_create( struct tcp_listener *listener, tcp->is_registered = PJ_TRUE; + /* Initialize keep-alive timer */ + tcp->ka_timer.user_data = (void*)tcp; + tcp->ka_timer.cb = &tcp_keep_alive_timer; + pj_ioqueue_op_key_init(&tcp->ka_op_key.key, sizeof(pj_ioqueue_op_key_t)); + pj_strdup(tcp->base.pool, &tcp->ka_pkt, &ka_pkt); + /* Done setting up basic transport. */ *p_tcp = tcp; @@ -966,6 +979,16 @@ static void on_accept_complete( pj_ioqueue_key_t *key, if (status != PJ_SUCCESS) { PJ_LOG(3,(tcp->base.obj_name, "New transport cancelled")); tcp_destroy(&tcp->base, status); + } else { + /* Start keep-alive timer */ + if (PJSIP_TCP_KEEP_ALIVE_INTERVAL) { + pj_time_val delay = {PJSIP_TCP_KEEP_ALIVE_INTERVAL, 0}; + pjsip_endpt_schedule_timer(listener->endpt, + &tcp->ka_timer, + &delay); + tcp->ka_timer.id = PJ_TRUE; + pj_gettimeofday(&tcp->last_activity); + } } } @@ -1005,6 +1028,10 @@ static void on_write_complete(pj_ioqueue_key_t *key, pj_ioqueue_get_user_data(key); pjsip_tx_data_op_key *tdata_op_key = (pjsip_tx_data_op_key*)op_key; + /* Note that op_key may be the op_key from keep-alive, thus + * it will not have tdata etc. + */ + tdata_op_key->tdata = NULL; /* Check for error/closure */ @@ -1025,6 +1052,9 @@ static void on_write_complete(pj_ioqueue_key_t *key, * Notify sip_transport.c that packet has been sent. */ tdata_op_key->callback(&tcp->base, tdata_op_key->token, bytes_sent); + + /* Mark last activity time */ + pj_gettimeofday(&tcp->last_activity); } } @@ -1127,14 +1157,17 @@ static pj_status_t tcp_send_msg(pjsip_transport *transport, /* * This callback is called by transport manager to shutdown transport. - * This normally is only used by UDP transport. */ static pj_status_t tcp_shutdown(pjsip_transport *transport) { + struct tcp_transport *tcp = (struct tcp_transport*)transport; + + /* Stop keep-alive timer. */ + if (tcp->ka_timer.id) { + pjsip_endpt_cancel_timer(tcp->listener->endpt, &tcp->ka_timer); + tcp->ka_timer.id = PJ_FALSE; + } - PJ_UNUSED_ARG(transport); - - /* Nothing to do for TCP */ return PJ_SUCCESS; } @@ -1175,6 +1208,9 @@ static void on_read_complete(pj_ioqueue_key_t *key, if (bytes_read > 0) { pj_size_t size_eaten; + /* Mark this as an activity */ + pj_gettimeofday(&tcp->last_activity); + /* Init pkt_info part. */ rdata->pkt_info.len += bytes_read; rdata->pkt_info.zero = 0; @@ -1364,7 +1400,70 @@ static void on_connect_complete(pj_ioqueue_key_t *key, /* Flush all pending send operations */ tcp_flush_pending_tx(tcp); + + /* Start keep-alive timer */ + if (PJSIP_TCP_KEEP_ALIVE_INTERVAL) { + pj_time_val delay = { PJSIP_TCP_KEEP_ALIVE_INTERVAL, 0 }; + pjsip_endpt_schedule_timer(tcp->listener->endpt, &tcp->ka_timer, + &delay); + tcp->ka_timer.id = PJ_TRUE; + pj_gettimeofday(&tcp->last_activity); + } +} + +/* Transport keep-alive timer callback */ +static void tcp_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e) +{ + struct tcp_transport *tcp = (struct tcp_transport*) e->user_data; + pj_time_val delay; + pj_time_val now; + pj_ssize_t size; + pj_status_t status; + + PJ_UNUSED_ARG(th); + + tcp->ka_timer.id = PJ_TRUE; + + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, tcp->last_activity); + + if (now.sec > 0 && now.sec < PJSIP_TCP_KEEP_ALIVE_INTERVAL) { + /* There has been activity, so don't send keep-alive */ + delay.sec = PJSIP_TCP_KEEP_ALIVE_INTERVAL - now.sec; + delay.msec = 0; + + pjsip_endpt_schedule_timer(tcp->listener->endpt, &tcp->ka_timer, + &delay); + tcp->ka_timer.id = PJ_TRUE; + return; + } + + PJ_LOG(5,(tcp->base.obj_name, "Sending %d byte(s) keep-alive to %.*s:%d", + (int)tcp->ka_pkt.slen, (int)tcp->base.remote_name.host.slen, + tcp->base.remote_name.host.ptr, + tcp->base.remote_name.port)); + + /* Send the data */ + size = tcp->ka_pkt.slen; + status = pj_ioqueue_send(tcp->key, &tcp->ka_op_key.key, + tcp->ka_pkt.ptr, &size, 0); + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + tcp_perror(tcp->base.obj_name, + "Error sending keep-alive packet", status); + pjsip_transport_shutdown(&tcp->base); + return; + } + + /* Register next keep-alive */ + delay.sec = PJSIP_TCP_KEEP_ALIVE_INTERVAL; + delay.msec = 0; + + pjsip_endpt_schedule_timer(tcp->listener->endpt, &tcp->ka_timer, + &delay); + tcp->ka_timer.id = PJ_TRUE; } + #endif /* PJ_HAS_TCP */ diff --git a/pjsip/src/pjsip/sip_transport_tls_ossl.c b/pjsip/src/pjsip/sip_transport_tls_ossl.c index 235a32e1..3998b6d8 100644 --- a/pjsip/src/pjsip/sip_transport_tls_ossl.c +++ b/pjsip/src/pjsip/sip_transport_tls_ossl.c @@ -167,6 +167,10 @@ struct tls_transport SSL *ssl; pj_bool_t ssl_shutdown_called; + /* Keep alive */ + pj_timer_entry ka_timer; + pj_time_val last_activity; + /* TLS transport can only have one rdata! * Otherwise chunks of incoming PDU may be received on different * buffer. @@ -741,15 +745,16 @@ static pj_status_t ssl_accept(struct tls_transport *tls) /* Send outgoing data with SSL connection */ -static pj_status_t ssl_write(struct tls_transport *tls, - pjsip_tx_data *tdata) +static pj_status_t ssl_write_bytes(struct tls_transport *tls, + const void *data, + int size, + const char *data_name) { - int size = tdata->buf.cur - tdata->buf.start; int sent = 0; do { const int fragment_sent = SSL_write(tls->ssl, - tdata->buf.start + sent, + ((pj_uint8_t*)data) + sent, size - sent); switch( SSL_get_error(tls->ssl, fragment_sent)) { @@ -798,7 +803,7 @@ static pj_status_t ssl_write(struct tls_transport *tls, default: ssl_report_error(tls->base.obj_name, 4, PJ_SUCCESS, "Error sending %s with SSL_write()", - pjsip_tx_data_get_info(tdata)); + data_name); return pj_get_netos_error() ? pj_get_netos_error() : PJSIP_TLS_ESEND; } @@ -806,6 +811,16 @@ static pj_status_t ssl_write(struct tls_transport *tls, } while (sent < size); return PJ_SUCCESS; + +} + +/* Send outgoing tdata with SSL connection */ +static pj_status_t ssl_write(struct tls_transport *tls, + pjsip_tx_data *tdata) +{ + return ssl_write_bytes(tls, tdata->buf.start, + tdata->buf.cur - tdata->buf.start, + pjsip_tx_data_get_info(tdata)); } @@ -1161,6 +1176,8 @@ static void on_write_complete(pj_ioqueue_key_t *key, static void on_connect_complete(pj_ioqueue_key_t *key, pj_status_t status); +/* TLS keep-alive timer callback */ +static void tls_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e); /* * Common function to create TLS transport, called when pending accept() and @@ -1203,7 +1220,7 @@ static pj_status_t tls_create( struct tls_listener *listener, (is_server ? "tlss%p" :"tlsc%p"), tls); /* Initialize transport reference counter to 1 */ - status = pj_atomic_create(pool, 1, &tls->base.ref_cnt); + status = pj_atomic_create(pool, 0, &tls->base.ref_cnt); if (status != PJ_SUCCESS) { goto on_error; } @@ -1273,6 +1290,11 @@ static pj_status_t tls_create( struct tls_listener *listener, tls->is_registered = PJ_TRUE; + /* Initialize keep-alive timer */ + tls->ka_timer.user_data = (void*) tls; + tls->ka_timer.cb = &tls_keep_alive_timer; + + /* Done setting up basic transport. */ *p_tls = tls; @@ -1723,6 +1745,17 @@ static void on_accept_complete( pj_ioqueue_key_t *key, ssl_report_error(tls->base.obj_name, 4, status, "Error creating incoming TLS transport"); pjsip_transport_shutdown(&tls->base); + + } else { + /* Start keep-alive timer */ + 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); + tls->ka_timer.id = PJ_TRUE; + pj_gettimeofday(&tls->last_activity); + } } accept_op = new_op; @@ -1774,6 +1807,9 @@ static void on_write_complete(pj_ioqueue_key_t *key, -bytes_sent; if (tls->close_reason==PJ_SUCCESS) tls->close_reason = status; pjsip_transport_shutdown(&tls->base); + } else { + /* Mark last activity */ + pj_gettimeofday(&tls->last_activity); } if (tdata_op_key->callback) { @@ -1918,8 +1954,7 @@ static pj_status_t tls_shutdown(pjsip_transport *transport) /* Shutdown SSL */ if (!tls->ssl_shutdown_called) { - /* Release our reference counter and shutdown SSL */ - pjsip_transport_dec_ref(transport); + /* shutdown SSL */ SSL_shutdown(tls->ssl); tls->ssl_shutdown_called = PJ_TRUE; @@ -1969,6 +2004,9 @@ static void on_read_complete(pj_ioqueue_key_t *key, */ pj_size_t size_eaten; + /* Mark last activity */ + pj_gettimeofday(&tls->last_activity); + /* Init pkt_info part. */ rdata->pkt_info.zero = 0; pj_gettimeofday(&rdata->pkt_info.timestamp); @@ -2216,6 +2254,69 @@ static void on_connect_complete(pj_ioqueue_key_t *key, /* Flush all pending send operations */ tls_flush_pending_tx(tls); + + /* Start keep-alive timer */ + if (PJSIP_TLS_KEEP_ALIVE_INTERVAL) { + pj_time_val delay = { PJSIP_TLS_KEEP_ALIVE_INTERVAL, 0 }; + pjsip_endpt_schedule_timer(tls->listener->endpt, &tls->ka_timer, + &delay); + tls->ka_timer.id = PJ_TRUE; + pj_gettimeofday(&tls->last_activity); + } + +} + + +/* Transport keep-alive timer callback */ +static void tls_keep_alive_timer(pj_timer_heap_t *th, pj_timer_entry *e) +{ + struct tls_transport *tls = (struct tls_transport*) e->user_data; + const pj_str_t ka_data = PJSIP_TLS_KEEP_ALIVE_DATA; + pj_time_val delay; + pj_time_val now; + pj_status_t status; + + PJ_UNUSED_ARG(th); + + tls->ka_timer.id = PJ_TRUE; + + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, tls->last_activity); + + if (now.sec > 0 && now.sec < PJSIP_TLS_KEEP_ALIVE_INTERVAL) { + /* There has been activity, so don't send keep-alive */ + delay.sec = PJSIP_TLS_KEEP_ALIVE_INTERVAL - now.sec; + delay.msec = 0; + + pjsip_endpt_schedule_timer(tls->listener->endpt, &tls->ka_timer, + &delay); + tls->ka_timer.id = PJ_TRUE; + return; + } + + PJ_LOG(5,(tls->base.obj_name, "Sending %d byte(s) keep-alive to %.*s:%d", + (int)ka_data.slen, (int)tls->base.remote_name.host.slen, + tls->base.remote_name.host.ptr, + tls->base.remote_name.port)); + + /* Send the data */ + status = ssl_write_bytes(tls, ka_data.ptr, (int)ka_data.slen, + "keep-alive"); + if (status != PJ_SUCCESS && + status != PJ_STATUS_FROM_OS(OSERR_EWOULDBLOCK)) + { + ssl_report_error(tls->base.obj_name, 1, status, + "Error sending keep-alive packet"); + return; + } + + /* Register next keep-alive */ + delay.sec = PJSIP_TLS_KEEP_ALIVE_INTERVAL; + delay.msec = 0; + + pjsip_endpt_schedule_timer(tls->listener->endpt, &tls->ka_timer, + &delay); + tls->ka_timer.id = PJ_TRUE; } #endif /* PJSIP_HAS_TLS_TRANSPORT */ -- cgit v1.2.3