| /* $Id$ */ |
| /* |
| * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #include <pjsip/sip_transport_tls.h> |
| #include <pjsip/sip_endpoint.h> |
| #include <pjsip/sip_errno.h> |
| #include <pj/compat/socket.h> |
| #include <pj/addr_resolv.h> |
| #include <pj/ssl_sock.h> |
| #include <pj/assert.h> |
| #include <pj/hash.h> |
| #include <pj/lock.h> |
| #include <pj/log.h> |
| #include <pj/os.h> |
| #include <pj/pool.h> |
| #include <pj/string.h> |
| |
| #if defined(PJSIP_HAS_TLS_TRANSPORT) && PJSIP_HAS_TLS_TRANSPORT!=0 |
| |
| #define THIS_FILE "sip_transport_tls.c" |
| |
| #define MAX_ASYNC_CNT 16 |
| #define POOL_LIS_INIT 512 |
| #define POOL_LIS_INC 512 |
| #define POOL_TP_INIT 512 |
| #define POOL_TP_INC 512 |
| |
| struct tls_listener; |
| struct tls_transport; |
| |
| /* |
| * Definition of TLS/SSL transport listener, and it's descendant of |
| * pjsip_tpfactory. |
| */ |
| struct tls_listener |
| { |
| pjsip_tpfactory factory; |
| pj_bool_t is_registered; |
| pjsip_endpoint *endpt; |
| pjsip_tpmgr *tpmgr; |
| pj_ssl_sock_t *ssock; |
| pj_sockaddr bound_addr; |
| pj_ssl_cert_t *cert; |
| pjsip_tls_setting tls_setting; |
| }; |
| |
| |
| /* |
| * This structure is used to keep delayed transmit operation in a list. |
| * A delayed transmission occurs when application sends tx_data when |
| * the TLS connect/establishment is still in progress. These delayed |
| * transmission will be "flushed" once the socket is connected (either |
| * successfully or with errors). |
| */ |
| struct delayed_tdata |
| { |
| PJ_DECL_LIST_MEMBER(struct delayed_tdata); |
| pjsip_tx_data_op_key *tdata_op_key; |
| pj_time_val timeout; |
| }; |
| |
| |
| /* |
| * TLS/SSL transport, and it's descendant of pjsip_transport. |
| */ |
| struct tls_transport |
| { |
| pjsip_transport base; |
| pj_bool_t is_server; |
| pj_str_t remote_name; |
| |
| pj_bool_t is_registered; |
| pj_bool_t is_closing; |
| pj_status_t close_reason; |
| pj_ssl_sock_t *ssock; |
| pj_bool_t has_pending_connect; |
| pj_bool_t verify_server; |
| |
| /* 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; |
| |
| /* TLS transport can only have one rdata! |
| * Otherwise chunks of incoming PDU may be received on different |
| * buffer. |
| */ |
| pjsip_rx_data rdata; |
| |
| /* Pending transmission list. */ |
| struct delayed_tdata delayed_list; |
| }; |
| |
| |
| /**************************************************************************** |
| * PROTOTYPES |
| */ |
| |
| /* This callback is called when pending accept() operation completes. */ |
| static pj_bool_t on_accept_complete(pj_ssl_sock_t *ssock, |
| pj_ssl_sock_t *new_ssock, |
| const pj_sockaddr_t *src_addr, |
| int src_addr_len); |
| |
| /* Callback on incoming data */ |
| static pj_bool_t on_data_read(pj_ssl_sock_t *ssock, |
| void *data, |
| pj_size_t size, |
| pj_status_t status, |
| pj_size_t *remainder); |
| |
| /* Callback when packet is sent */ |
| static pj_bool_t on_data_sent(pj_ssl_sock_t *ssock, |
| pj_ioqueue_op_key_t *send_key, |
| pj_ssize_t sent); |
| |
| /* This callback is called by transport manager to destroy listener */ |
| static pj_status_t lis_destroy(pjsip_tpfactory *factory); |
| |
| /* This callback is called by transport manager to create transport */ |
| static pj_status_t lis_create_transport(pjsip_tpfactory *factory, |
| pjsip_tpmgr *mgr, |
| pjsip_endpoint *endpt, |
| const pj_sockaddr *rem_addr, |
| int addr_len, |
| pjsip_tx_data *tdata, |
| pjsip_transport **transport); |
| |
| /* Common function to create and initialize transport */ |
| static pj_status_t tls_create(struct tls_listener *listener, |
| pj_pool_t *pool, |
| pj_ssl_sock_t *ssock, |
| pj_bool_t is_server, |
| const pj_sockaddr *local, |
| const pj_sockaddr *remote, |
| const pj_str_t *remote_name, |
| struct tls_transport **p_tls); |
| |
| |
| static void tls_perror(const char *sender, const char *title, |
| pj_status_t status) |
| { |
| char errmsg[PJ_ERR_MSG_SIZE]; |
| |
| pj_strerror(status, errmsg, sizeof(errmsg)); |
| |
| PJ_LOG(1,(sender, "%s: %s [code=%d]", title, errmsg, status)); |
| } |
| |
| |
| static void sockaddr_to_host_port( pj_pool_t *pool, |
| pjsip_host_port *host_port, |
| const pj_sockaddr *addr ) |
| { |
| host_port->host.ptr = (char*) pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+4); |
| pj_sockaddr_print(addr, host_port->host.ptr, PJ_INET6_ADDRSTRLEN+4, 0); |
| host_port->host.slen = pj_ansi_strlen(host_port->host.ptr); |
| host_port->port = pj_sockaddr_get_port(addr); |
| } |
| |
| |
| static void tls_init_shutdown(struct tls_transport *tls, pj_status_t status) |
| { |
| pjsip_tp_state_callback state_cb; |
| |
| if (tls->close_reason == PJ_SUCCESS) |
| tls->close_reason = status; |
| |
| if (tls->base.is_shutdown || tls->base.is_destroying) |
| 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_state_cb(tls->base.tpmgr); |
| if (state_cb) { |
| pjsip_transport_state_info state_info; |
| pjsip_tls_state_info tls_info; |
| pj_ssl_sock_info ssl_info; |
| |
| /* Init transport state info */ |
| pj_bzero(&state_info, sizeof(state_info)); |
| state_info.status = tls->close_reason; |
| |
| if (tls->ssock && |
| pj_ssl_sock_get_info(tls->ssock, &ssl_info) == PJ_SUCCESS) |
| { |
| pj_bzero(&tls_info, sizeof(tls_info)); |
| tls_info.ssl_sock_info = &ssl_info; |
| state_info.ext_info = &tls_info; |
| } |
| |
| (*state_cb)(&tls->base, PJSIP_TP_STATE_DISCONNECTED, &state_info); |
| } |
| |
| /* check again */ |
| if (tls->base.is_shutdown || tls->base.is_destroying) |
| return; |
| |
| /* We can not destroy the transport since high level objects may |
| * still keep reference to this transport. So we can only |
| * instruct transport manager to gracefully start the shutdown |
| * procedure for this transport. |
| */ |
| pjsip_transport_shutdown(&tls->base); |
| |
| /* Now, it is ok to destroy the transport. */ |
| pjsip_transport_dec_ref(&tls->base); |
| } |
| |
| |
| /**************************************************************************** |
| * The TLS listener/transport factory. |
| */ |
| |
| /* |
| * This is the public API to create, initialize, register, and start the |
| * TLS listener. |
| */ |
| PJ_DEF(pj_status_t) pjsip_tls_transport_start (pjsip_endpoint *endpt, |
| const pjsip_tls_setting *opt, |
| const pj_sockaddr_in *local_in, |
| const pjsip_host_port *a_name, |
| unsigned async_cnt, |
| pjsip_tpfactory **p_factory) |
| { |
| pj_sockaddr local; |
| |
| if (local_in) |
| pj_sockaddr_cp(&local, local_in); |
| |
| return pjsip_tls_transport_start2(endpt, opt, (local_in? &local : NULL), |
| a_name, async_cnt, p_factory); |
| } |
| |
| PJ_DEF(pj_status_t) pjsip_tls_transport_start2( pjsip_endpoint *endpt, |
| const pjsip_tls_setting *opt, |
| const pj_sockaddr *local, |
| const pjsip_host_port *a_name, |
| unsigned async_cnt, |
| pjsip_tpfactory **p_factory) |
| { |
| pj_pool_t *pool; |
| pj_bool_t is_ipv6; |
| int af; |
| struct tls_listener *listener; |
| pj_ssl_sock_param ssock_param; |
| pj_sockaddr *listener_addr; |
| pj_bool_t has_listener; |
| pj_status_t status; |
| |
| /* Sanity check */ |
| PJ_ASSERT_RETURN(endpt && async_cnt, PJ_EINVAL); |
| |
| is_ipv6 = (local && local->addr.sa_family == pj_AF_INET6()); |
| af = is_ipv6 ? pj_AF_INET6() : pj_AF_INET(); |
| |
| /* Verify that address given in a_name (if any) is valid */ |
| if (a_name && a_name->host.slen) { |
| pj_sockaddr tmp; |
| |
| status = pj_sockaddr_init(af, &tmp, &a_name->host, |
| (pj_uint16_t)a_name->port); |
| if (status != PJ_SUCCESS || !pj_sockaddr_has_addr(&tmp) || |
| (!is_ipv6 && tmp.ipv4.sin_addr.s_addr == PJ_INADDR_NONE)) |
| { |
| /* Invalid address */ |
| return PJ_EINVAL; |
| } |
| } |
| |
| pool = pjsip_endpt_create_pool(endpt, "tlslis", POOL_LIS_INIT, |
| POOL_LIS_INC); |
| PJ_ASSERT_RETURN(pool, PJ_ENOMEM); |
| |
| listener = PJ_POOL_ZALLOC_T(pool, struct tls_listener); |
| listener->factory.pool = pool; |
| if (is_ipv6) |
| listener->factory.type = PJSIP_TRANSPORT_TLS6; |
| else |
| listener->factory.type = PJSIP_TRANSPORT_TLS; |
| listener->factory.type_name = (char*) |
| pjsip_transport_get_type_name(listener->factory.type); |
| listener->factory.flag = |
| pjsip_transport_get_flag_from_type(listener->factory.type); |
| |
| pj_ansi_strcpy(listener->factory.obj_name, "tlslis"); |
| if (is_ipv6) |
| pj_ansi_strcat(listener->factory.obj_name, "6"); |
| |
| if (opt) |
| pjsip_tls_setting_copy(pool, &listener->tls_setting, opt); |
| else |
| pjsip_tls_setting_default(&listener->tls_setting); |
| |
| status = pj_lock_create_recursive_mutex(pool, listener->factory.obj_name, |
| &listener->factory.lock); |
| if (status != PJ_SUCCESS) |
| goto on_error; |
| |
| if (async_cnt > MAX_ASYNC_CNT) |
| async_cnt = MAX_ASYNC_CNT; |
| |
| /* Build SSL socket param */ |
| pj_ssl_sock_param_default(&ssock_param); |
| ssock_param.sock_af = af; |
| ssock_param.cb.on_accept_complete = &on_accept_complete; |
| ssock_param.cb.on_data_read = &on_data_read; |
| ssock_param.cb.on_data_sent = &on_data_sent; |
| ssock_param.async_cnt = async_cnt; |
| ssock_param.ioqueue = pjsip_endpt_get_ioqueue(endpt); |
| ssock_param.require_client_cert = listener->tls_setting.require_client_cert; |
| ssock_param.timeout = listener->tls_setting.timeout; |
| ssock_param.user_data = listener; |
| ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket |
| * due to verification error */ |
| if (ssock_param.send_buffer_size < PJSIP_MAX_PKT_LEN) |
| ssock_param.send_buffer_size = PJSIP_MAX_PKT_LEN; |
| if (ssock_param.read_buffer_size < PJSIP_MAX_PKT_LEN) |
| ssock_param.read_buffer_size = PJSIP_MAX_PKT_LEN; |
| ssock_param.ciphers_num = listener->tls_setting.ciphers_num; |
| ssock_param.ciphers = listener->tls_setting.ciphers; |
| ssock_param.reuse_addr = listener->tls_setting.reuse_addr; |
| ssock_param.qos_type = listener->tls_setting.qos_type; |
| ssock_param.qos_ignore_error = listener->tls_setting.qos_ignore_error; |
| pj_memcpy(&ssock_param.qos_params, &listener->tls_setting.qos_params, |
| sizeof(ssock_param.qos_params)); |
| |
| has_listener = PJ_FALSE; |
| |
| switch(listener->tls_setting.method) { |
| case PJSIP_TLSV1_METHOD: |
| ssock_param.proto = PJ_SSL_SOCK_PROTO_TLS1; |
| break; |
| case PJSIP_SSLV2_METHOD: |
| ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL2; |
| break; |
| case PJSIP_SSLV3_METHOD: |
| ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL3; |
| break; |
| case PJSIP_SSLV23_METHOD: |
| ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL23; |
| break; |
| default: |
| ssock_param.proto = PJ_SSL_SOCK_PROTO_DEFAULT; |
| break; |
| } |
| |
| /* Create SSL socket */ |
| status = pj_ssl_sock_create(pool, &ssock_param, &listener->ssock); |
| if (status != PJ_SUCCESS) |
| goto on_error; |
| |
| /* Bind address may be different than factory.local_addr because |
| * factory.local_addr will be resolved below. |
| */ |
| listener_addr = &listener->factory.local_addr; |
| if (local) { |
| pj_sockaddr_cp((pj_sockaddr_t*)listener_addr, |
| (const pj_sockaddr_t*)local); |
| pj_sockaddr_cp(&listener->bound_addr, local); |
| } else { |
| pj_sockaddr_init(af, listener_addr, NULL, 0); |
| pj_sockaddr_init(af, &listener->bound_addr, NULL, 0); |
| } |
| |
| /* Check if certificate/CA list for SSL socket is set */ |
| if (listener->tls_setting.cert_file.slen || |
| listener->tls_setting.ca_list_file.slen) |
| { |
| status = pj_ssl_cert_load_from_files(pool, |
| &listener->tls_setting.ca_list_file, |
| &listener->tls_setting.cert_file, |
| &listener->tls_setting.privkey_file, |
| &listener->tls_setting.password, |
| &listener->cert); |
| if (status != PJ_SUCCESS) |
| goto on_error; |
| |
| status = pj_ssl_sock_set_certificate(listener->ssock, pool, |
| listener->cert); |
| if (status != PJ_SUCCESS) |
| goto on_error; |
| } |
| |
| /* Start accepting incoming connections. Note that some TLS/SSL backends |
| * may not support for SSL socket server. |
| */ |
| has_listener = PJ_FALSE; |
| |
| status = pj_ssl_sock_start_accept(listener->ssock, pool, |
| (pj_sockaddr_t*)listener_addr, |
| pj_sockaddr_get_len((pj_sockaddr_t*)listener_addr)); |
| if (status == PJ_SUCCESS || status == PJ_EPENDING) { |
| pj_ssl_sock_info info; |
| has_listener = PJ_TRUE; |
| |
| /* Retrieve the bound address */ |
| status = pj_ssl_sock_get_info(listener->ssock, &info); |
| if (status == PJ_SUCCESS) |
| pj_sockaddr_cp(listener_addr, (pj_sockaddr_t*)&info.local_addr); |
| } else if (status != PJ_ENOTSUP) { |
| goto on_error; |
| } |
| |
| /* If published host/IP is specified, then use that address as the |
| * listener advertised address. |
| */ |
| if (a_name && a_name->host.slen) { |
| /* Copy the address */ |
| listener->factory.addr_name = *a_name; |
| pj_strdup(listener->factory.pool, &listener->factory.addr_name.host, |
| &a_name->host); |
| listener->factory.addr_name.port = a_name->port; |
| |
| } else { |
| /* No published address is given, use the bound address */ |
| |
| /* If the address returns 0.0.0.0, use the default |
| * interface address as the transport's address. |
| */ |
| if (!pj_sockaddr_has_addr(listener_addr)) { |
| pj_sockaddr hostip; |
| |
| status = pj_gethostip(af, &hostip); |
| if (status != PJ_SUCCESS) |
| goto on_error; |
| |
| pj_sockaddr_copy_addr(listener_addr, &hostip); |
| } |
| |
| /* Save the address name */ |
| sockaddr_to_host_port(listener->factory.pool, |
| &listener->factory.addr_name, listener_addr); |
| } |
| |
| /* If port is zero, get the bound port */ |
| if (listener->factory.addr_name.port == 0) { |
| listener->factory.addr_name.port = pj_sockaddr_get_port(listener_addr); |
| } |
| |
| pj_ansi_snprintf(listener->factory.obj_name, |
| sizeof(listener->factory.obj_name), |
| "tlslis:%d", listener->factory.addr_name.port); |
| |
| /* Register to transport manager */ |
| listener->endpt = endpt; |
| listener->tpmgr = pjsip_endpt_get_tpmgr(endpt); |
| listener->factory.create_transport2 = lis_create_transport; |
| listener->factory.destroy = lis_destroy; |
| listener->is_registered = PJ_TRUE; |
| status = pjsip_tpmgr_register_tpfactory(listener->tpmgr, |
| &listener->factory); |
| if (status != PJ_SUCCESS) { |
| listener->is_registered = PJ_FALSE; |
| goto on_error; |
| } |
| |
| if (has_listener) { |
| PJ_LOG(4,(listener->factory.obj_name, |
| "SIP TLS listener is ready for incoming connections " |
| "at %.*s:%d", |
| (int)listener->factory.addr_name.host.slen, |
| listener->factory.addr_name.host.ptr, |
| listener->factory.addr_name.port)); |
| } else { |
| PJ_LOG(4,(listener->factory.obj_name, "SIP TLS is ready " |
| "(client only)")); |
| } |
| |
| /* Return the pointer to user */ |
| if (p_factory) *p_factory = &listener->factory; |
| |
| return PJ_SUCCESS; |
| |
| on_error: |
| lis_destroy(&listener->factory); |
| return status; |
| } |
| |
| |
| /* This callback is called by transport manager to destroy listener */ |
| static pj_status_t lis_destroy(pjsip_tpfactory *factory) |
| { |
| struct tls_listener *listener = (struct tls_listener *)factory; |
| |
| if (listener->is_registered) { |
| pjsip_tpmgr_unregister_tpfactory(listener->tpmgr, &listener->factory); |
| listener->is_registered = PJ_FALSE; |
| } |
| |
| if (listener->ssock) { |
| pj_ssl_sock_close(listener->ssock); |
| listener->ssock = NULL; |
| } |
| |
| if (listener->factory.lock) { |
| pj_lock_destroy(listener->factory.lock); |
| listener->factory.lock = NULL; |
| } |
| |
| if (listener->factory.pool) { |
| pj_pool_t *pool = listener->factory.pool; |
| |
| PJ_LOG(4,(listener->factory.obj_name, "SIP TLS listener destroyed")); |
| |
| listener->factory.pool = NULL; |
| pj_pool_release(pool); |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /***************************************************************************/ |
| /* |
| * TLS Transport |
| */ |
| |
| /* |
| * Prototypes. |
| */ |
| /* Called by transport manager to send message */ |
| static pj_status_t tls_send_msg(pjsip_transport *transport, |
| pjsip_tx_data *tdata, |
| const pj_sockaddr_t *rem_addr, |
| int addr_len, |
| void *token, |
| pjsip_transport_callback callback); |
| |
| /* Called by transport manager to shutdown */ |
| static pj_status_t tls_shutdown(pjsip_transport *transport); |
| |
| /* Called by transport manager to destroy transport */ |
| static pj_status_t tls_destroy_transport(pjsip_transport *transport); |
| |
| /* Utility to destroy transport */ |
| static pj_status_t tls_destroy(pjsip_transport *transport, |
| pj_status_t reason); |
| |
| /* Callback when connect completes */ |
| static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock, |
| 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 |
| * pending connect() complete. |
| */ |
| static pj_status_t tls_create( struct tls_listener *listener, |
| pj_pool_t *pool, |
| pj_ssl_sock_t *ssock, |
| pj_bool_t is_server, |
| const pj_sockaddr *local, |
| const pj_sockaddr *remote, |
| const pj_str_t *remote_name, |
| struct tls_transport **p_tls) |
| { |
| struct tls_transport *tls; |
| const pj_str_t ka_pkt = PJSIP_TLS_KEEP_ALIVE_DATA; |
| char print_addr[PJ_INET6_ADDRSTRLEN+10]; |
| pj_status_t status; |
| |
| |
| PJ_ASSERT_RETURN(listener && ssock && local && remote && p_tls, PJ_EINVAL); |
| |
| |
| if (pool == NULL) { |
| pool = pjsip_endpt_create_pool(listener->endpt, "tls", |
| POOL_TP_INIT, POOL_TP_INC); |
| PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); |
| } |
| |
| /* |
| * Create and initialize basic transport structure. |
| */ |
| tls = PJ_POOL_ZALLOC_T(pool, struct tls_transport); |
| tls->is_server = is_server; |
| tls->verify_server = listener->tls_setting.verify_server; |
| pj_list_init(&tls->delayed_list); |
| tls->base.pool = pool; |
| |
| pj_ansi_snprintf(tls->base.obj_name, PJ_MAX_OBJ_NAME, |
| (is_server ? "tlss%p" :"tlsc%p"), tls); |
| |
| status = pj_atomic_create(pool, 0, &tls->base.ref_cnt); |
| if (status != PJ_SUCCESS) { |
| goto on_error; |
| } |
| |
| status = pj_lock_create_recursive_mutex(pool, "tls", &tls->base.lock); |
| if (status != PJ_SUCCESS) { |
| goto on_error; |
| } |
| |
| if (remote_name) |
| pj_strdup(pool, &tls->remote_name, remote_name); |
| |
| tls->base.key.type = listener->factory.type; |
| pj_sockaddr_cp(&tls->base.key.rem_addr, remote); |
| tls->base.type_name = (char*)pjsip_transport_get_type_name( |
| (pjsip_transport_type_e)tls->base.key.type); |
| tls->base.flag = pjsip_transport_get_flag_from_type( |
| (pjsip_transport_type_e)tls->base.key.type); |
| |
| tls->base.info = (char*) pj_pool_alloc(pool, 64); |
| pj_ansi_snprintf(tls->base.info, 64, "%s to %s", |
| tls->base.type_name, |
| pj_sockaddr_print(remote, print_addr, |
| sizeof(print_addr), 3)); |
| |
| |
| tls->base.addr_len = pj_sockaddr_get_len(remote); |
| tls->base.dir = is_server? PJSIP_TP_DIR_INCOMING : PJSIP_TP_DIR_OUTGOING; |
| |
| /* Set initial local address */ |
| if (!pj_sockaddr_has_addr(local)) { |
| pj_sockaddr_cp(&tls->base.local_addr, |
| &listener->factory.local_addr); |
| } else { |
| pj_sockaddr_cp(&tls->base.local_addr, local); |
| } |
| |
| sockaddr_to_host_port(pool, &tls->base.local_name, &tls->base.local_addr); |
| if (tls->remote_name.slen) { |
| tls->base.remote_name.host = tls->remote_name; |
| tls->base.remote_name.port = pj_sockaddr_get_port(remote); |
| } else { |
| sockaddr_to_host_port(pool, &tls->base.remote_name, remote); |
| } |
| |
| tls->base.endpt = listener->endpt; |
| tls->base.tpmgr = listener->tpmgr; |
| tls->base.send_msg = &tls_send_msg; |
| tls->base.do_shutdown = &tls_shutdown; |
| tls->base.destroy = &tls_destroy_transport; |
| |
| tls->ssock = ssock; |
| |
| /* Register transport to transport manager */ |
| status = pjsip_transport_register(listener->tpmgr, &tls->base); |
| if (status != PJ_SUCCESS) { |
| goto on_error; |
| } |
| |
| tls->is_registered = PJ_TRUE; |
| |
| /* Initialize keep-alive timer */ |
| tls->ka_timer.user_data = (void*)tls; |
| tls->ka_timer.cb = &tls_keep_alive_timer; |
| pj_ioqueue_op_key_init(&tls->ka_op_key.key, sizeof(pj_ioqueue_op_key_t)); |
| pj_strdup(tls->base.pool, &tls->ka_pkt, &ka_pkt); |
| |
| /* Done setting up basic transport. */ |
| *p_tls = tls; |
| |
| PJ_LOG(4,(tls->base.obj_name, "TLS %s transport created", |
| (tls->is_server ? "server" : "client"))); |
| |
| return PJ_SUCCESS; |
| |
| on_error: |
| tls_destroy(&tls->base, status); |
| return status; |
| } |
| |
| |
| /* Flush all delayed transmision once the socket is connected. */ |
| static void tls_flush_pending_tx(struct tls_transport *tls) |
| { |
| pj_time_val now; |
| |
| pj_gettickcount(&now); |
| pj_lock_acquire(tls->base.lock); |
| while (!pj_list_empty(&tls->delayed_list)) { |
| struct delayed_tdata *pending_tx; |
| pjsip_tx_data *tdata; |
| pj_ioqueue_op_key_t *op_key; |
| pj_ssize_t size; |
| pj_status_t status; |
| |
| pending_tx = tls->delayed_list.next; |
| pj_list_erase(pending_tx); |
| |
| tdata = pending_tx->tdata_op_key->tdata; |
| op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; |
| |
| if (pending_tx->timeout.sec > 0 && |
| PJ_TIME_VAL_GT(now, pending_tx->timeout)) |
| { |
| continue; |
| } |
| |
| /* send! */ |
| size = tdata->buf.cur - tdata->buf.start; |
| status = pj_ssl_sock_send(tls->ssock, op_key, tdata->buf.start, |
| &size, 0); |
| |
| if (status != PJ_EPENDING) { |
| pj_lock_release(tls->base.lock); |
| on_data_sent(tls->ssock, op_key, size); |
| pj_lock_acquire(tls->base.lock); |
| } |
| } |
| pj_lock_release(tls->base.lock); |
| } |
| |
| |
| /* Called by transport manager to destroy transport */ |
| static pj_status_t tls_destroy_transport(pjsip_transport *transport) |
| { |
| struct tls_transport *tls = (struct tls_transport*)transport; |
| |
| /* Transport would have been unregistered by now since this callback |
| * is called by transport manager. |
| */ |
| tls->is_registered = PJ_FALSE; |
| |
| return tls_destroy(transport, tls->close_reason); |
| } |
| |
| |
| /* Destroy TLS transport */ |
| static pj_status_t tls_destroy(pjsip_transport *transport, |
| pj_status_t reason) |
| { |
| struct tls_transport *tls = (struct tls_transport*)transport; |
| |
| if (tls->close_reason == 0) |
| tls->close_reason = reason; |
| |
| if (tls->is_registered) { |
| tls->is_registered = PJ_FALSE; |
| pjsip_transport_destroy(transport); |
| |
| /* pjsip_transport_destroy will recursively call this function |
| * again. |
| */ |
| return PJ_SUCCESS; |
| } |
| |
| /* Mark transport as closing */ |
| tls->is_closing = PJ_TRUE; |
| |
| /* Stop keep-alive timer. */ |
| if (tls->ka_timer.id) { |
| pjsip_endpt_cancel_timer(tls->base.endpt, &tls->ka_timer); |
| tls->ka_timer.id = PJ_FALSE; |
| } |
| |
| /* Cancel all delayed transmits */ |
| while (!pj_list_empty(&tls->delayed_list)) { |
| struct delayed_tdata *pending_tx; |
| pj_ioqueue_op_key_t *op_key; |
| |
| pending_tx = tls->delayed_list.next; |
| pj_list_erase(pending_tx); |
| |
| op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; |
| |
| on_data_sent(tls->ssock, op_key, -reason); |
| } |
| |
| if (tls->rdata.tp_info.pool) { |
| pj_pool_release(tls->rdata.tp_info.pool); |
| tls->rdata.tp_info.pool = NULL; |
| } |
| |
| if (tls->ssock) { |
| pj_ssl_sock_close(tls->ssock); |
| tls->ssock = NULL; |
| } |
| if (tls->base.lock) { |
| pj_lock_destroy(tls->base.lock); |
| tls->base.lock = NULL; |
| } |
| |
| if (tls->base.ref_cnt) { |
| pj_atomic_destroy(tls->base.ref_cnt); |
| tls->base.ref_cnt = NULL; |
| } |
| |
| if (tls->base.pool) { |
| pj_pool_t *pool; |
| |
| if (reason != PJ_SUCCESS) { |
| char errmsg[PJ_ERR_MSG_SIZE]; |
| |
| pj_strerror(reason, errmsg, sizeof(errmsg)); |
| PJ_LOG(4,(tls->base.obj_name, |
| "TLS transport destroyed with reason %d: %s", |
| reason, errmsg)); |
| |
| } else { |
| |
| PJ_LOG(4,(tls->base.obj_name, |
| "TLS transport destroyed normally")); |
| |
| } |
| |
| pool = tls->base.pool; |
| tls->base.pool = NULL; |
| pj_pool_release(pool); |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * This utility function creates receive data buffers and start |
| * asynchronous recv() operations from the socket. It is called after |
| * accept() or connect() operation complete. |
| */ |
| static pj_status_t tls_start_read(struct tls_transport *tls) |
| { |
| pj_pool_t *pool; |
| pj_uint32_t size; |
| pj_sockaddr *rem_addr; |
| void *readbuf[1]; |
| pj_status_t status; |
| |
| /* Init rdata */ |
| pool = pjsip_endpt_create_pool(tls->base.endpt, |
| "rtd%p", |
| PJSIP_POOL_RDATA_LEN, |
| PJSIP_POOL_RDATA_INC); |
| if (!pool) { |
| tls_perror(tls->base.obj_name, "Unable to create pool", PJ_ENOMEM); |
| return PJ_ENOMEM; |
| } |
| |
| tls->rdata.tp_info.pool = pool; |
| |
| tls->rdata.tp_info.transport = &tls->base; |
| tls->rdata.tp_info.tp_data = tls; |
| tls->rdata.tp_info.op_key.rdata = &tls->rdata; |
| pj_ioqueue_op_key_init(&tls->rdata.tp_info.op_key.op_key, |
| sizeof(pj_ioqueue_op_key_t)); |
| |
| tls->rdata.pkt_info.src_addr = tls->base.key.rem_addr; |
| tls->rdata.pkt_info.src_addr_len = sizeof(tls->rdata.pkt_info.src_addr); |
| rem_addr = &tls->base.key.rem_addr; |
| pj_sockaddr_print(rem_addr, tls->rdata.pkt_info.src_name, |
| sizeof(tls->rdata.pkt_info.src_name), 0); |
| tls->rdata.pkt_info.src_port = pj_sockaddr_get_port(rem_addr); |
| |
| size = sizeof(tls->rdata.pkt_info.packet); |
| readbuf[0] = tls->rdata.pkt_info.packet; |
| status = pj_ssl_sock_start_read2(tls->ssock, tls->base.pool, size, |
| readbuf, 0); |
| if (status != PJ_SUCCESS && status != PJ_EPENDING) { |
| PJ_LOG(4, (tls->base.obj_name, |
| "pj_ssl_sock_start_read() error, status=%d", |
| status)); |
| return status; |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* This callback is called by transport manager for the TLS factory |
| * to create outgoing transport to the specified destination. |
| */ |
| static pj_status_t lis_create_transport(pjsip_tpfactory *factory, |
| pjsip_tpmgr *mgr, |
| pjsip_endpoint *endpt, |
| const pj_sockaddr *rem_addr, |
| int addr_len, |
| pjsip_tx_data *tdata, |
| pjsip_transport **p_transport) |
| { |
| struct tls_listener *listener; |
| struct tls_transport *tls; |
| pj_pool_t *pool; |
| pj_ssl_sock_t *ssock; |
| pj_ssl_sock_param ssock_param; |
| pj_sockaddr local_addr; |
| pj_str_t remote_name; |
| pj_status_t status; |
| |
| /* Sanity checks */ |
| PJ_ASSERT_RETURN(factory && mgr && endpt && rem_addr && |
| addr_len && p_transport, PJ_EINVAL); |
| |
| /* Check that address is a sockaddr_in or sockaddr_in6*/ |
| PJ_ASSERT_RETURN((rem_addr->addr.sa_family == pj_AF_INET() && |
| addr_len == sizeof(pj_sockaddr_in)) || |
| (rem_addr->addr.sa_family == pj_AF_INET6() && |
| addr_len == sizeof(pj_sockaddr_in6)), PJ_EINVAL); |
| |
| |
| listener = (struct tls_listener*)factory; |
| |
| pool = pjsip_endpt_create_pool(listener->endpt, "tls", |
| POOL_TP_INIT, POOL_TP_INC); |
| PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); |
| |
| /* Get remote host name from tdata */ |
| if (tdata) |
| remote_name = tdata->dest_info.name; |
| else |
| pj_bzero(&remote_name, sizeof(remote_name)); |
| |
| /* Build SSL socket param */ |
| pj_ssl_sock_param_default(&ssock_param); |
| ssock_param.sock_af = (factory->type & PJSIP_TRANSPORT_IPV6) ? |
| pj_AF_INET6() : pj_AF_INET(); |
| ssock_param.cb.on_connect_complete = &on_connect_complete; |
| ssock_param.cb.on_data_read = &on_data_read; |
| ssock_param.cb.on_data_sent = &on_data_sent; |
| ssock_param.async_cnt = 1; |
| ssock_param.ioqueue = pjsip_endpt_get_ioqueue(listener->endpt); |
| ssock_param.server_name = remote_name; |
| ssock_param.timeout = listener->tls_setting.timeout; |
| ssock_param.user_data = NULL; /* pending, must be set later */ |
| ssock_param.verify_peer = PJ_FALSE; /* avoid SSL socket closing the socket |
| * due to verification error */ |
| if (ssock_param.send_buffer_size < PJSIP_MAX_PKT_LEN) |
| ssock_param.send_buffer_size = PJSIP_MAX_PKT_LEN; |
| if (ssock_param.read_buffer_size < PJSIP_MAX_PKT_LEN) |
| ssock_param.read_buffer_size = PJSIP_MAX_PKT_LEN; |
| ssock_param.ciphers_num = listener->tls_setting.ciphers_num; |
| ssock_param.ciphers = listener->tls_setting.ciphers; |
| ssock_param.qos_type = listener->tls_setting.qos_type; |
| ssock_param.qos_ignore_error = listener->tls_setting.qos_ignore_error; |
| pj_memcpy(&ssock_param.qos_params, &listener->tls_setting.qos_params, |
| sizeof(ssock_param.qos_params)); |
| |
| switch(listener->tls_setting.method) { |
| case PJSIP_TLSV1_METHOD: |
| ssock_param.proto = PJ_SSL_SOCK_PROTO_TLS1; |
| break; |
| case PJSIP_SSLV2_METHOD: |
| ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL2; |
| break; |
| case PJSIP_SSLV3_METHOD: |
| ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL3; |
| break; |
| case PJSIP_SSLV23_METHOD: |
| ssock_param.proto = PJ_SSL_SOCK_PROTO_SSL23; |
| break; |
| default: |
| ssock_param.proto = PJ_SSL_SOCK_PROTO_DEFAULT; |
| break; |
| } |
| |
| status = pj_ssl_sock_create(pool, &ssock_param, &ssock); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| /* Apply SSL certificate */ |
| if (listener->cert) { |
| status = pj_ssl_sock_set_certificate(ssock, pool, listener->cert); |
| if (status != PJ_SUCCESS) |
| return status; |
| } |
| |
| /* Initially set bind address to listener's bind address */ |
| pj_sockaddr_init(listener->bound_addr.addr.sa_family, |
| &local_addr, NULL, 0); |
| pj_sockaddr_copy_addr(&local_addr, &listener->bound_addr); |
| |
| /* Create the transport descriptor */ |
| status = tls_create(listener, pool, ssock, PJ_FALSE, &local_addr, |
| rem_addr, &remote_name, &tls); |
| if (status != PJ_SUCCESS) |
| return status; |
| |
| /* Set the "pending" SSL socket user data */ |
| pj_ssl_sock_set_user_data(tls->ssock, tls); |
| |
| /* Start asynchronous connect() operation */ |
| tls->has_pending_connect = PJ_TRUE; |
| status = pj_ssl_sock_start_connect(tls->ssock, tls->base.pool, |
| (pj_sockaddr_t*)&local_addr, |
| (pj_sockaddr_t*)rem_addr, |
| addr_len); |
| if (status == PJ_SUCCESS) { |
| on_connect_complete(tls->ssock, PJ_SUCCESS); |
| } else if (status != PJ_EPENDING) { |
| tls_destroy(&tls->base, status); |
| return status; |
| } |
| |
| if (tls->has_pending_connect) { |
| pj_ssl_sock_info info; |
| |
| /* Update local address, just in case local address currently set is |
| * different now that asynchronous connect() is started. |
| */ |
| |
| /* Retrieve the bound address */ |
| status = pj_ssl_sock_get_info(tls->ssock, &info); |
| if (status == PJ_SUCCESS) { |
| pj_uint16_t new_port; |
| |
| new_port = pj_sockaddr_get_port((pj_sockaddr_t*)&info.local_addr); |
| |
| if (pj_sockaddr_has_addr((pj_sockaddr_t*)&info.local_addr)) { |
| /* Update sockaddr */ |
| pj_sockaddr_cp((pj_sockaddr_t*)&tls->base.local_addr, |
| (pj_sockaddr_t*)&info.local_addr); |
| } else if (new_port && new_port != pj_sockaddr_get_port( |
| (pj_sockaddr_t*)&tls->base.local_addr)) |
| { |
| /* Update port only */ |
| pj_sockaddr_set_port(&tls->base.local_addr, |
| new_port); |
| } |
| |
| sockaddr_to_host_port(tls->base.pool, &tls->base.local_name, |
| &tls->base.local_addr); |
| } |
| |
| PJ_LOG(4,(tls->base.obj_name, |
| "TLS transport %.*s:%d is connecting to %.*s:%d...", |
| (int)tls->base.local_name.host.slen, |
| tls->base.local_name.host.ptr, |
| tls->base.local_name.port, |
| (int)tls->base.remote_name.host.slen, |
| tls->base.remote_name.host.ptr, |
| tls->base.remote_name.port)); |
| } |
| |
| /* Done */ |
| *p_transport = &tls->base; |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * This callback is called by SSL socket when pending accept() operation |
| * has completed. |
| */ |
| static pj_bool_t on_accept_complete(pj_ssl_sock_t *ssock, |
| pj_ssl_sock_t *new_ssock, |
| const pj_sockaddr_t *src_addr, |
| int src_addr_len) |
| { |
| struct tls_listener *listener; |
| struct tls_transport *tls; |
| pj_ssl_sock_info ssl_info; |
| char addr[PJ_INET6_ADDRSTRLEN+10]; |
| pjsip_tp_state_callback state_cb; |
| pj_sockaddr tmp_src_addr; |
| pj_bool_t is_shutdown; |
| pj_status_t status; |
| |
| PJ_UNUSED_ARG(src_addr_len); |
| |
| listener = (struct tls_listener*) pj_ssl_sock_get_user_data(ssock); |
| |
| PJ_ASSERT_RETURN(new_ssock, PJ_TRUE); |
| |
| PJ_LOG(4,(listener->factory.obj_name, |
| "TLS listener %.*s:%d: got incoming TLS connection " |
| "from %s, sock=%d", |
| (int)listener->factory.addr_name.host.slen, |
| listener->factory.addr_name.host.ptr, |
| listener->factory.addr_name.port, |
| pj_sockaddr_print(src_addr, addr, sizeof(addr), 3), |
| new_ssock)); |
| |
| /* Retrieve SSL socket info, close the socket if this is failed |
| * as the SSL socket info availability is rather critical here. |
| */ |
| status = pj_ssl_sock_get_info(new_ssock, &ssl_info); |
| if (status != PJ_SUCCESS) { |
| pj_ssl_sock_close(new_ssock); |
| return PJ_TRUE; |
| } |
| |
| /* Copy to larger buffer, just in case */ |
| pj_bzero(&tmp_src_addr, sizeof(tmp_src_addr)); |
| pj_sockaddr_cp(&tmp_src_addr, src_addr); |
| |
| /* |
| * Incoming connection! |
| * Create TLS transport for the new socket. |
| */ |
| status = tls_create( listener, NULL, new_ssock, PJ_TRUE, |
| &listener->factory.local_addr, |
| &tmp_src_addr, NULL, &tls); |
| |
| if (status != PJ_SUCCESS) |
| return PJ_TRUE; |
| |
| /* Set the "pending" SSL socket user data */ |
| pj_ssl_sock_set_user_data(new_ssock, tls); |
| |
| /* Prevent immediate transport destroy as application may access it |
| * (getting info, etc) in transport state notification callback. |
| */ |
| pjsip_transport_add_ref(&tls->base); |
| |
| /* If there is verification error and verification is mandatory, shutdown |
| * and destroy the transport. |
| */ |
| if (ssl_info.verify_status && listener->tls_setting.verify_client) { |
| if (tls->close_reason == PJ_SUCCESS) |
| tls->close_reason = PJSIP_TLS_ECERTVERIF; |
| pjsip_transport_shutdown(&tls->base); |
| } |
| |
| /* Notify transport state to application */ |
| state_cb = pjsip_tpmgr_get_state_cb(tls->base.tpmgr); |
| if (state_cb) { |
| pjsip_transport_state_info state_info; |
| pjsip_tls_state_info tls_info; |
| pjsip_transport_state tp_state; |
| |
| /* Init transport state info */ |
| pj_bzero(&tls_info, sizeof(tls_info)); |
| pj_bzero(&state_info, sizeof(state_info)); |
| tls_info.ssl_sock_info = &ssl_info; |
| state_info.ext_info = &tls_info; |
| |
| /* Set transport state based on verification status */ |
| if (ssl_info.verify_status && listener->tls_setting.verify_client) |
| { |
| tp_state = PJSIP_TP_STATE_DISCONNECTED; |
| state_info.status = PJSIP_TLS_ECERTVERIF; |
| } else { |
| tp_state = PJSIP_TP_STATE_CONNECTED; |
| state_info.status = PJ_SUCCESS; |
| } |
| |
| (*state_cb)(&tls->base, tp_state, &state_info); |
| } |
| |
| /* Release transport reference. If transport is shutting down, it may |
| * get destroyed here. |
| */ |
| is_shutdown = tls->base.is_shutdown; |
| pjsip_transport_dec_ref(&tls->base); |
| if (is_shutdown) |
| return PJ_TRUE; |
| |
| |
| status = tls_start_read(tls); |
| if (status != PJ_SUCCESS) { |
| PJ_LOG(3,(tls->base.obj_name, "New transport cancelled")); |
| tls_init_shutdown(tls, status); |
| tls_destroy(&tls->base, status); |
| } 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); |
| } |
| } |
| |
| return PJ_TRUE; |
| } |
| |
| |
| /* |
| * Callback from ioqueue when packet is sent. |
| */ |
| static pj_bool_t on_data_sent(pj_ssl_sock_t *ssock, |
| pj_ioqueue_op_key_t *op_key, |
| pj_ssize_t bytes_sent) |
| { |
| struct tls_transport *tls = (struct tls_transport*) |
| pj_ssl_sock_get_user_data(ssock); |
| 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; |
| |
| if (tdata_op_key->callback) { |
| /* |
| * Notify sip_transport.c that packet has been sent. |
| */ |
| if (bytes_sent == 0) |
| bytes_sent = -PJ_RETURN_OS_ERROR(OSERR_ENOTCONN); |
| |
| tdata_op_key->callback(&tls->base, tdata_op_key->token, bytes_sent); |
| |
| /* Mark last activity time */ |
| pj_gettimeofday(&tls->last_activity); |
| |
| } |
| |
| /* Check for error/closure */ |
| if (bytes_sent <= 0) { |
| pj_status_t status; |
| |
| PJ_LOG(5,(tls->base.obj_name, "TLS send() error, sent=%d", |
| bytes_sent)); |
| |
| status = (bytes_sent == 0) ? PJ_RETURN_OS_ERROR(OSERR_ENOTCONN) : |
| (pj_status_t)-bytes_sent; |
| |
| tls_init_shutdown(tls, status); |
| |
| return PJ_FALSE; |
| } |
| |
| return PJ_TRUE; |
| } |
| |
| |
| /* |
| * This callback is called by transport manager to send SIP message |
| */ |
| static pj_status_t tls_send_msg(pjsip_transport *transport, |
| pjsip_tx_data *tdata, |
| const pj_sockaddr_t *rem_addr, |
| int addr_len, |
| void *token, |
| pjsip_transport_callback callback) |
| { |
| struct tls_transport *tls = (struct tls_transport*)transport; |
| pj_ssize_t size; |
| pj_bool_t delayed = PJ_FALSE; |
| pj_status_t status = PJ_SUCCESS; |
| |
| /* Sanity check */ |
| PJ_ASSERT_RETURN(transport && tdata, PJ_EINVAL); |
| |
| /* Check that there's no pending operation associated with the tdata */ |
| PJ_ASSERT_RETURN(tdata->op_key.tdata == NULL, PJSIP_EPENDINGTX); |
| |
| /* Check the address is supported */ |
| PJ_ASSERT_RETURN(rem_addr && (addr_len==sizeof(pj_sockaddr_in) || |
| addr_len==sizeof(pj_sockaddr_in6)), |
| PJ_EINVAL); |
| |
| /* Init op key. */ |
| tdata->op_key.tdata = tdata; |
| tdata->op_key.token = token; |
| tdata->op_key.callback = callback; |
| |
| /* If asynchronous connect() has not completed yet, just put the |
| * transmit data in the pending transmission list since we can not |
| * use the socket yet. |
| */ |
| if (tls->has_pending_connect) { |
| |
| /* |
| * Looks like connect() is still in progress. Check again (this time |
| * with holding the lock) to be sure. |
| */ |
| pj_lock_acquire(tls->base.lock); |
| |
| if (tls->has_pending_connect) { |
| struct delayed_tdata *delayed_tdata; |
| |
| /* |
| * connect() is still in progress. Put the transmit data to |
| * the delayed list. |
| * Starting from #1583 (https://trac.pjsip.org/repos/ticket/1583), |
| * we also add timeout value for the transmit data. When the |
| * connect() is completed, the timeout value will be checked to |
| * determine whether the transmit data needs to be sent. |
| */ |
| delayed_tdata = PJ_POOL_ZALLOC_T(tdata->pool, |
| struct delayed_tdata); |
| delayed_tdata->tdata_op_key = &tdata->op_key; |
| if (tdata->msg && tdata->msg->type == PJSIP_REQUEST_MSG) { |
| pj_gettickcount(&delayed_tdata->timeout); |
| delayed_tdata->timeout.msec += pjsip_cfg()->tsx.td; |
| pj_time_val_normalize(&delayed_tdata->timeout); |
| } |
| |
| pj_list_push_back(&tls->delayed_list, delayed_tdata); |
| status = PJ_EPENDING; |
| |
| /* Prevent pj_ioqueue_send() to be called below */ |
| delayed = PJ_TRUE; |
| } |
| |
| pj_lock_release(tls->base.lock); |
| } |
| |
| if (!delayed) { |
| /* |
| * Transport is ready to go. Send the packet to ioqueue to be |
| * sent asynchronously. |
| */ |
| size = tdata->buf.cur - tdata->buf.start; |
| status = pj_ssl_sock_send(tls->ssock, |
| (pj_ioqueue_op_key_t*)&tdata->op_key, |
| tdata->buf.start, &size, 0); |
| |
| if (status != PJ_EPENDING) { |
| /* Not pending (could be immediate success or error) */ |
| tdata->op_key.tdata = NULL; |
| |
| /* Shutdown transport on closure/errors */ |
| if (size <= 0) { |
| |
| PJ_LOG(5,(tls->base.obj_name, "TLS send() error, sent=%d", |
| size)); |
| |
| if (status == PJ_SUCCESS) |
| status = PJ_RETURN_OS_ERROR(OSERR_ENOTCONN); |
| |
| tls_init_shutdown(tls, status); |
| } |
| } |
| } |
| |
| return status; |
| } |
| |
| |
| /* |
| * This callback is called by transport manager to shutdown transport. |
| */ |
| static pj_status_t tls_shutdown(pjsip_transport *transport) |
| { |
| struct tls_transport *tls = (struct tls_transport*)transport; |
| |
| /* Stop keep-alive timer. */ |
| if (tls->ka_timer.id) { |
| pjsip_endpt_cancel_timer(tls->base.endpt, &tls->ka_timer); |
| tls->ka_timer.id = PJ_FALSE; |
| } |
| |
| return PJ_SUCCESS; |
| } |
| |
| |
| /* |
| * Callback from ioqueue that an incoming data is received from the socket. |
| */ |
| static pj_bool_t on_data_read(pj_ssl_sock_t *ssock, |
| void *data, |
| pj_size_t size, |
| pj_status_t status, |
| pj_size_t *remainder) |
| { |
| enum { MAX_IMMEDIATE_PACKET = 10 }; |
| struct tls_transport *tls; |
| pjsip_rx_data *rdata; |
| |
| PJ_UNUSED_ARG(data); |
| |
| tls = (struct tls_transport*) pj_ssl_sock_get_user_data(ssock); |
| rdata = &tls->rdata; |
| |
| /* Don't do anything if transport is closing. */ |
| if (tls->is_closing) { |
| tls->is_closing++; |
| return PJ_FALSE; |
| } |
| |
| /* Houston, we have packet! Report the packet to transport manager |
| * to be parsed. |
| */ |
| if (status == PJ_SUCCESS) { |
| pj_size_t size_eaten; |
| |
| /* Mark this as an activity */ |
| pj_gettimeofday(&tls->last_activity); |
| |
| pj_assert((void*)rdata->pkt_info.packet == data); |
| |
| /* Init pkt_info part. */ |
| rdata->pkt_info.len = size; |
| rdata->pkt_info.zero = 0; |
| pj_gettimeofday(&rdata->pkt_info.timestamp); |
| |
| /* Report to transport manager. |
| * The transport manager will tell us how many bytes of the packet |
| * have been processed (as valid SIP message). |
| */ |
| size_eaten = |
| pjsip_tpmgr_receive_packet(rdata->tp_info.transport->tpmgr, |
| rdata); |
| |
| pj_assert(size_eaten <= (pj_size_t)rdata->pkt_info.len); |
| |
| /* Move unprocessed data to the front of the buffer */ |
| *remainder = size - size_eaten; |
| if (*remainder > 0 && *remainder != size) { |
| pj_memmove(rdata->pkt_info.packet, |
| rdata->pkt_info.packet + size_eaten, |
| *remainder); |
| } |
| |
| } else { |
| |
| /* Transport is closed */ |
| PJ_LOG(4,(tls->base.obj_name, "TLS connection closed")); |
| |
| tls_init_shutdown(tls, status); |
| |
| return PJ_FALSE; |
| |
| } |
| |
| /* Reset pool. */ |
| pj_pool_reset(rdata->tp_info.pool); |
| |
| return PJ_TRUE; |
| } |
| |
| |
| /* |
| * Callback from ioqueue when asynchronous connect() operation completes. |
| */ |
| static pj_bool_t on_connect_complete(pj_ssl_sock_t *ssock, |
| pj_status_t status) |
| { |
| struct tls_transport *tls; |
| pj_ssl_sock_info ssl_info; |
| pj_sockaddr addr, *tp_addr; |
| pjsip_tp_state_callback state_cb; |
| pj_bool_t is_shutdown; |
| |
| tls = (struct tls_transport*) pj_ssl_sock_get_user_data(ssock); |
| |
| /* Check connect() status */ |
| if (status != PJ_SUCCESS) { |
| |
| tls_perror(tls->base.obj_name, "TLS connect() error", status); |
| |
| /* Cancel all delayed transmits */ |
| while (!pj_list_empty(&tls->delayed_list)) { |
| struct delayed_tdata *pending_tx; |
| pj_ioqueue_op_key_t *op_key; |
| |
| pending_tx = tls->delayed_list.next; |
| pj_list_erase(pending_tx); |
| |
| op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; |
| |
| on_data_sent(tls->ssock, op_key, -status); |
| } |
| |
| goto on_error; |
| } |
| |
| /* Retrieve SSL socket info, shutdown the transport if this is failed |
| * as the SSL socket info availability is rather critical here. |
| */ |
| status = pj_ssl_sock_get_info(tls->ssock, &ssl_info); |
| if (status != PJ_SUCCESS) |
| goto on_error; |
| |
| /* Update (again) local address, just in case local address currently |
| * set is different now that the socket is connected (could happen |
| * on some systems, like old Win32 probably?). |
| */ |
| tp_addr = &tls->base.local_addr; |
| pj_sockaddr_cp((pj_sockaddr_t*)&addr, |
| (pj_sockaddr_t*)&ssl_info.local_addr); |
| if (pj_sockaddr_cmp(tp_addr, &addr) != 0) { |
| pj_sockaddr_cp(tp_addr, &addr); |
| sockaddr_to_host_port(tls->base.pool, &tls->base.local_name, |
| tp_addr); |
| } |
| |
| /* Server identity verification based on server certificate. */ |
| if (ssl_info.remote_cert_info->version) { |
| pj_str_t *remote_name; |
| pj_ssl_cert_info *serv_cert = ssl_info.remote_cert_info; |
| pj_bool_t matched = PJ_FALSE; |
| unsigned i; |
| |
| /* Remote name may be hostname or IP address */ |
| if (tls->remote_name.slen) |
| remote_name = &tls->remote_name; |
| else |
| remote_name = &tls->base.remote_name.host; |
| |
| /* Start matching remote name with SubjectAltName fields of |
| * server certificate. |
| */ |
| for (i = 0; i < serv_cert->subj_alt_name.cnt && !matched; ++i) { |
| pj_str_t *cert_name = &serv_cert->subj_alt_name.entry[i].name; |
| |
| switch (serv_cert->subj_alt_name.entry[i].type) { |
| case PJ_SSL_CERT_NAME_DNS: |
| case PJ_SSL_CERT_NAME_IP: |
| matched = !pj_stricmp(remote_name, cert_name); |
| break; |
| case PJ_SSL_CERT_NAME_URI: |
| if (pj_strnicmp2(cert_name, "sip:", 4) == 0 || |
| pj_strnicmp2(cert_name, "sips:", 5) == 0) |
| { |
| pj_str_t host_part; |
| char *p; |
| |
| p = pj_strchr(cert_name, ':') + 1; |
| pj_strset(&host_part, p, cert_name->slen - |
| (p - cert_name->ptr)); |
| matched = !pj_stricmp(remote_name, &host_part); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* When still not matched or no SubjectAltName fields in server |
| * certificate, try with Common Name of Subject field. |
| */ |
| if (!matched) { |
| matched = !pj_stricmp(remote_name, &serv_cert->subject.cn); |
| } |
| |
| if (!matched) |
| ssl_info.verify_status |= PJ_SSL_CERT_EIDENTITY_NOT_MATCH; |
| } |
| |
| /* Prevent immediate transport destroy as application may access it |
| * (getting info, etc) in transport state notification callback. |
| */ |
| pjsip_transport_add_ref(&tls->base); |
| |
| /* If there is verification error and verification is mandatory, shutdown |
| * and destroy the transport. |
| */ |
| if (ssl_info.verify_status && tls->verify_server) { |
| if (tls->close_reason == PJ_SUCCESS) |
| tls->close_reason = PJSIP_TLS_ECERTVERIF; |
| pjsip_transport_shutdown(&tls->base); |
| } |
| |
| /* Notify transport state to application */ |
| state_cb = pjsip_tpmgr_get_state_cb(tls->base.tpmgr); |
| if (state_cb) { |
| pjsip_transport_state_info state_info; |
| pjsip_tls_state_info tls_info; |
| pjsip_transport_state tp_state; |
| |
| /* Init transport state info */ |
| pj_bzero(&state_info, sizeof(state_info)); |
| pj_bzero(&tls_info, sizeof(tls_info)); |
| state_info.ext_info = &tls_info; |
| tls_info.ssl_sock_info = &ssl_info; |
| |
| /* Set transport state based on verification status */ |
| if (ssl_info.verify_status && tls->verify_server) |
| { |
| tp_state = PJSIP_TP_STATE_DISCONNECTED; |
| state_info.status = PJSIP_TLS_ECERTVERIF; |
| } else { |
| tp_state = PJSIP_TP_STATE_CONNECTED; |
| state_info.status = PJ_SUCCESS; |
| } |
| |
| (*state_cb)(&tls->base, tp_state, &state_info); |
| } |
| |
| /* Release transport reference. If transport is shutting down, it may |
| * get destroyed here. |
| */ |
| is_shutdown = tls->base.is_shutdown; |
| pjsip_transport_dec_ref(&tls->base); |
| if (is_shutdown) { |
| status = tls->close_reason; |
| tls_perror(tls->base.obj_name, "TLS connect() error", status); |
| |
| /* Cancel all delayed transmits */ |
| while (!pj_list_empty(&tls->delayed_list)) { |
| struct delayed_tdata *pending_tx; |
| pj_ioqueue_op_key_t *op_key; |
| |
| pending_tx = tls->delayed_list.next; |
| pj_list_erase(pending_tx); |
| |
| op_key = (pj_ioqueue_op_key_t*)pending_tx->tdata_op_key; |
| |
| on_data_sent(tls->ssock, op_key, -status); |
| } |
| |
| return PJ_FALSE; |
| } |
| |
| |
| /* Mark that pending connect() operation has completed. */ |
| tls->has_pending_connect = PJ_FALSE; |
| |
| PJ_LOG(4,(tls->base.obj_name, |
| "TLS transport %.*s:%d is connected to %.*s:%d", |
| (int)tls->base.local_name.host.slen, |
| tls->base.local_name.host.ptr, |
| tls->base.local_name.port, |
| (int)tls->base.remote_name.host.slen, |
| tls->base.remote_name.host.ptr, |
| tls->base.remote_name.port)); |
| |
| /* Start pending read */ |
| status = tls_start_read(tls); |
| if (status != PJ_SUCCESS) |
| goto on_error; |
| |
| /* 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->base.endpt, &tls->ka_timer, |
| &delay); |
| tls->ka_timer.id = PJ_TRUE; |
| pj_gettimeofday(&tls->last_activity); |
| } |
| |
| return PJ_TRUE; |
| |
| on_error: |
| tls_init_shutdown(tls, status); |
| |
| return PJ_FALSE; |
| } |
| |
| |
| /* 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; |
| pj_time_val delay; |
| pj_time_val now; |
| pj_ssize_t size; |
| 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->base.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)tls->ka_pkt.slen, (int)tls->base.remote_name.host.slen, |
| tls->base.remote_name.host.ptr, |
| tls->base.remote_name.port)); |
| |
| /* Send the data */ |
| size = tls->ka_pkt.slen; |
| status = pj_ssl_sock_send(tls->ssock, &tls->ka_op_key.key, |
| tls->ka_pkt.ptr, &size, 0); |
| |
| if (status != PJ_SUCCESS && status != PJ_EPENDING) { |
| tls_perror(tls->base.obj_name, |
| "Error sending keep-alive packet", status); |
| |
| tls_init_shutdown(tls, status); |
| return; |
| } |
| |
| /* Register next keep-alive */ |
| delay.sec = PJSIP_TLS_KEEP_ALIVE_INTERVAL; |
| delay.msec = 0; |
| |
| pjsip_endpt_schedule_timer(tls->base.endpt, &tls->ka_timer, |
| &delay); |
| tls->ka_timer.id = PJ_TRUE; |
| } |
| |
| #endif /* PJSIP_HAS_TLS_TRANSPORT */ |