/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2017, Digium Inc. * * Richard Mudgett * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! * \file * \brief Manages the global transport event notification callbacks. * * \author Richard Mudgett * See Also: * * \arg \ref AstCREDITS */ #include "asterisk.h" #include "asterisk/res_pjsip.h" #include "asterisk/res_pjsip_cli.h" #include "include/res_pjsip_private.h" #include "asterisk/linkedlists.h" #include "asterisk/vector.h" /* ------------------------------------------------------------------- */ /*! \brief Number of buckets for monitored active transports */ #define ACTIVE_TRANSPORTS_BUCKETS 127 /*! Who to notify when transport shuts down. */ struct transport_monitor_notifier { /*! Who to call when transport shuts down. */ ast_transport_monitor_shutdown_cb cb; /*! ao2 data object to pass to callback. */ void *data; }; /*! \brief Structure for transport to be monitored */ struct transport_monitor { /*! \brief Key : */ char key[IP6ADDR_COLON_PORT_BUFLEN]; /*! \brief The underlying PJSIP transport */ pjsip_transport *transport; /*! For debugging purposes, we save the obj_name * in case the transport goes away. */ char *transport_obj_name; /*! Who is interested in when this transport shuts down. */ AST_VECTOR(, struct transport_monitor_notifier) monitors; }; /*! \brief Global container of active reliable transports */ static AO2_GLOBAL_OBJ_STATIC(active_transports); /*! \brief Existing transport events callback that we need to invoke */ static pjsip_tp_state_callback tpmgr_state_callback; /*! List of registered transport state callbacks. */ static AST_RWLIST_HEAD(, ast_sip_tpmgr_state_callback) transport_state_list; /*! \brief Hashing function for struct transport_monitor */ AO2_STRING_FIELD_HASH_FN(transport_monitor, key); /*! \brief Comparison function for struct transport_monitor */ AO2_STRING_FIELD_CMP_FN(transport_monitor, key); /*! \brief Sort function for struct transport_monitor */ AO2_STRING_FIELD_SORT_FN(transport_monitor, key); static const char *transport_state2str(pjsip_transport_state state) { const char *name; switch (state) { case PJSIP_TP_STATE_CONNECTED: name = "CONNECTED"; break; case PJSIP_TP_STATE_DISCONNECTED: name = "DISCONNECTED"; break; case PJSIP_TP_STATE_SHUTDOWN: name = "SHUTDOWN"; break; case PJSIP_TP_STATE_DESTROY: name = "DESTROY"; break; default: /* * We have to have a default case because the enum is * defined by a third-party library. */ ast_assert(0); name = ""; break; } return name; } static void transport_monitor_dtor(void *vdoomed) { struct transport_monitor *monitored = vdoomed; int idx; for (idx = AST_VECTOR_SIZE(&monitored->monitors); idx--;) { struct transport_monitor_notifier *notifier; notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx); ao2_cleanup(notifier->data); } AST_VECTOR_FREE(&monitored->monitors); ast_debug(3, "Transport %s(%s,%s) RefCnt: %ld : state:MONITOR_DESTROYED\n", monitored->key, monitored->transport->obj_name, monitored->transport->type_name,pj_atomic_get(monitored->transport->ref_cnt)); ast_free(monitored->transport_obj_name); pjsip_transport_dec_ref(monitored->transport); } /*! * \internal * \brief Do registered callbacks for the transport. * \since 13.21.0 * * \param transports Active transports container * \param transport Which transport to do callbacks for. */ static void transport_state_do_reg_callbacks(struct ao2_container *transports, pjsip_transport *transport) { struct transport_monitor *monitored; char key[IP6ADDR_COLON_PORT_BUFLEN]; AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR(transport, key); monitored = ao2_find(transports, key, OBJ_SEARCH_KEY | OBJ_UNLINK); if (monitored) { int idx; for (idx = AST_VECTOR_SIZE(&monitored->monitors); idx--;) { struct transport_monitor_notifier *notifier; notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx); ast_debug(3, "Transport %s(%s,%s) RefCnt: %ld : running callback %p(%p)\n", monitored->key, monitored->transport->obj_name, monitored->transport->type_name, pj_atomic_get(monitored->transport->ref_cnt), notifier->cb, notifier->data); notifier->cb(notifier->data); } ao2_ref(monitored, -1); } } static void verify_log_result(int log_level, const pjsip_transport *transport, pj_uint32_t verify_status) { const char *status[32]; unsigned int count; unsigned int i; count = ARRAY_LEN(status); if (pj_ssl_cert_get_verify_status_strings(verify_status, status, &count) != PJ_SUCCESS) { ast_log(LOG_ERROR, "Error retrieving certificate verification result(s)\n"); return; } for (i = 0; i < count; ++i) { ast_log(log_level, _A_, "Transport '%s' to remote '%.*s' - %s\n", transport->factory->info, (int)pj_strlen(&transport->remote_name.host), pj_strbuf(&transport->remote_name.host), status[i]); } } static int verify_cert_name(const pj_str_t *local, const pj_str_t *remote) { const char *p; pj_ssize_t size; ast_debug(3, "Verify certificate name: local = %.*s, remote = %.*s\n", (unsigned int)pj_strlen(local), pj_strbuf(local), (unsigned int)pj_strlen(remote), pj_strbuf(remote)); if (!pj_stricmp(remote, local)) { return 1; } if (pj_strnicmp2(remote, "*.", 2)) { return 0; } p = pj_strchr(local, '.'); if (!p) { return 0; } size = pj_strbuf(local) + pj_strlen(local) - ++p; return size == pj_strlen(remote) - 2 ? !pj_memcmp(pj_strbuf(remote) + 2, p, size) : 0; } static int verify_cert_names(const pj_str_t *host, const pj_ssl_cert_info *remote) { unsigned int i; for (i = 0; i < remote->subj_alt_name.cnt; ++i) { /* * DNS is the only type we're matching wildcards against, * so only recheck those. */ if (remote->subj_alt_name.entry[i].type == PJ_SSL_CERT_NAME_DNS && verify_cert_name(host, &remote->subj_alt_name.entry[i].name)) { return 1; } } return verify_cert_name(host, &remote->subject.cn); } static int transport_tls_verify(const pjsip_transport *transport, const pjsip_tls_state_info *state_info) { pj_uint32_t verify_status; const struct ast_sip_transport_state *state; if (transport->dir == PJSIP_TP_DIR_INCOMING) { return 1; } /* transport_id should always be in factory info (see config_transport) */ ast_assert(!ast_strlen_zero(transport->factory->info)); state = ast_sip_get_transport_state(transport->factory->info); if (!state) { /* * There should always be an associated state, but if for some * reason there is not then fail verification */ ast_log(LOG_ERROR, "Transport state not found for '%s'\n", transport->factory->info); return 0; } verify_status = state_info->ssl_sock_info->verify_status; /* * By this point pjsip has already completed its verification process. If * there was a name matching error it could be because they disallow wildcards. * If this transport has been configured to allow wildcards then we'll need * to re-check the name(s) for such. */ if (state->allow_wildcard_certs && (verify_status & PJ_SSL_CERT_EIDENTITY_NOT_MATCH)) { if (verify_cert_names(&transport->remote_name.host, state_info->ssl_sock_info->remote_cert_info)) { /* A name matched a wildcard, so clear the error */ verify_status &= ~PJ_SSL_CERT_EIDENTITY_NOT_MATCH; } } if (state->verify_server && verify_status != PJ_SSL_CERT_ESUCCESS) { verify_log_result(__LOG_ERROR, transport, verify_status); return 0; } verify_log_result(__LOG_NOTICE, transport, verify_status); return 1; } /*! \brief Callback invoked when transport state changes occur */ static void transport_state_callback(pjsip_transport *transport, pjsip_transport_state state, const pjsip_transport_state_info *info) { struct ao2_container *transports; /* We only care about monitoring reliable transports */ if (PJSIP_TRANSPORT_IS_RELIABLE(transport) && (transports = ao2_global_obj_ref(active_transports))) { struct transport_monitor *monitored; ast_debug(3, "Transport " PJSTR_PRINTF_SPEC ":%d(%s,%s): RefCnt: %ld state:%s\n", PJSTR_PRINTF_VAR(transport->remote_name.host), transport->remote_name.port, transport->obj_name, transport->type_name, pj_atomic_get(transport->ref_cnt), transport_state2str(state)); switch (state) { case PJSIP_TP_STATE_CONNECTED: if (PJSIP_TRANSPORT_IS_SECURE(transport) && !transport_tls_verify(transport, info->ext_info)) { pjsip_transport_shutdown(transport); return; } monitored = ao2_alloc_options(sizeof(*monitored), transport_monitor_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK); if (!monitored) { break; } monitored->transport = transport; AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR(transport, monitored->key); monitored->transport_obj_name = ast_strdup(transport->obj_name); if (AST_VECTOR_INIT(&monitored->monitors, 5)) { ao2_ref(monitored, -1); break; } pjsip_transport_add_ref(monitored->transport); ast_debug(3, "Transport %s(%s,%s): RefCnt: %ld state:MONITOR_CREATED\n", monitored->key, monitored->transport_obj_name, monitored->transport->type_name, pj_atomic_get(monitored->transport->ref_cnt)); ao2_link(transports, monitored); ao2_ref(monitored, -1); break; case PJSIP_TP_STATE_DISCONNECTED: if (!transport->is_shutdown) { pjsip_transport_shutdown(transport); } transport_state_do_reg_callbacks(transports, transport); break; case PJSIP_TP_STATE_SHUTDOWN: /* * Set shutdown flag early so we can force a new transport to be * created if a monitor callback needs to reestablish a link. * PJPROJECT sets the flag after this routine returns even though * it has already called the transport's shutdown routine. */ transport->is_shutdown = PJ_TRUE; transport_state_do_reg_callbacks(transports, transport); break; case PJSIP_TP_STATE_DESTROY: transport_state_do_reg_callbacks(transports, transport); break; default: /* * We have to have a default case because the enum is * defined by a third-party library. */ ast_assert(0); break; } ao2_ref(transports, -1); } /* Loop over other transport state callbacks registered with us. */ if (!AST_LIST_EMPTY(&transport_state_list)) { struct ast_sip_tpmgr_state_callback *tpmgr_notifier; AST_RWLIST_RDLOCK(&transport_state_list); AST_LIST_TRAVERSE(&transport_state_list, tpmgr_notifier, node) { tpmgr_notifier->cb(transport, state, info); } AST_RWLIST_UNLOCK(&transport_state_list); } /* Forward to the old state callback if present */ if (tpmgr_state_callback) { tpmgr_state_callback(transport, state, info); } } struct callback_data { ast_transport_monitor_shutdown_cb cb; void *data; ast_transport_monitor_data_matcher matches; }; static int transport_monitor_unregister_cb(void *obj, void *arg, int flags) { struct transport_monitor *monitored = obj; struct callback_data *cb_data = arg; int idx; for (idx = AST_VECTOR_SIZE(&monitored->monitors); idx--;) { struct transport_monitor_notifier *notifier; notifier = AST_VECTOR_GET_ADDR(&monitored->monitors, idx); if (notifier->cb == cb_data->cb && (!cb_data->data || cb_data->matches(cb_data->data, notifier->data))) { ao2_cleanup(notifier->data); AST_VECTOR_REMOVE_UNORDERED(&monitored->monitors, idx); ast_debug(3, "Transport %s(%s,%s) RefCnt: %ld : Unregistered monitor %p(%p)\n", monitored->key, monitored->transport_obj_name, monitored->transport->type_name, pj_atomic_get(monitored->transport->ref_cnt), notifier->cb, notifier->data); } } return 0; } static int ptr_matcher(void *a, void *b) { return a == b; } void ast_sip_transport_monitor_unregister_all(ast_transport_monitor_shutdown_cb cb, void *data, ast_transport_monitor_data_matcher matches) { struct ao2_container *transports; struct callback_data cb_data = { .cb = cb, .data = data, .matches = matches ?: ptr_matcher, }; ast_assert(cb != NULL); transports = ao2_global_obj_ref(active_transports); if (!transports) { return; } ao2_callback(transports, OBJ_MULTIPLE | OBJ_NODATA, transport_monitor_unregister_cb, &cb_data); ao2_ref(transports, -1); } void ast_sip_transport_monitor_unregister(pjsip_transport *transport, ast_transport_monitor_shutdown_cb cb, void *data, ast_transport_monitor_data_matcher matches) { char key[IP6ADDR_COLON_PORT_BUFLEN]; AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR(transport, key); ast_sip_transport_monitor_unregister_key(key, cb, data, matches); } void ast_sip_transport_monitor_unregister_key(const char *transport_key, ast_transport_monitor_shutdown_cb cb, void *data, ast_transport_monitor_data_matcher matches) { struct ao2_container *transports; struct transport_monitor *monitored; ast_assert(transport_key != NULL && cb != NULL); transports = ao2_global_obj_ref(active_transports); if (!transports) { return; } ao2_lock(transports); monitored = ao2_find(transports, transport_key, OBJ_SEARCH_KEY | OBJ_NOLOCK); if (monitored) { struct callback_data cb_data = { .cb = cb, .data = data, .matches = matches ?: ptr_matcher, }; transport_monitor_unregister_cb(monitored, &cb_data, 0); ao2_ref(monitored, -1); } ao2_unlock(transports); ao2_ref(transports, -1); } enum ast_transport_monitor_reg ast_sip_transport_monitor_register(pjsip_transport *transport, ast_transport_monitor_shutdown_cb cb, void *ao2_data) { char key[IP6ADDR_COLON_PORT_BUFLEN]; AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR(transport, key); return ast_sip_transport_monitor_register_replace_key(key, cb, ao2_data, NULL); } enum ast_transport_monitor_reg ast_sip_transport_monitor_register_key(const char *transport_key, ast_transport_monitor_shutdown_cb cb, void *ao2_data) { return ast_sip_transport_monitor_register_replace_key(transport_key, cb, ao2_data, NULL); } enum ast_transport_monitor_reg ast_sip_transport_monitor_register_replace(pjsip_transport *transport, ast_transport_monitor_shutdown_cb cb, void *ao2_data, ast_transport_monitor_data_matcher matches) { char key[IP6ADDR_COLON_PORT_BUFLEN]; AST_SIP_MAKE_REMOTE_IPADDR_PORT_STR(transport, key); return ast_sip_transport_monitor_register_replace_key(key, cb, ao2_data, NULL); } enum ast_transport_monitor_reg ast_sip_transport_monitor_register_replace_key(const char *transport_key, ast_transport_monitor_shutdown_cb cb, void *ao2_data, ast_transport_monitor_data_matcher matches) { struct ao2_container *transports; struct transport_monitor *monitored; enum ast_transport_monitor_reg res = AST_TRANSPORT_MONITOR_REG_NOT_FOUND; ast_assert(transport_key != NULL && cb != NULL); transports = ao2_global_obj_ref(active_transports); if (!transports) { return res; } ao2_lock(transports); monitored = ao2_find(transports, transport_key, OBJ_SEARCH_KEY | OBJ_NOLOCK); if (monitored) { struct transport_monitor_notifier new_monitor; struct callback_data cb_data = { .cb = cb, .data = ao2_data, .matches = matches ?: ptr_matcher, }; transport_monitor_unregister_cb(monitored, &cb_data, 0); /* Add new monitor to vector */ new_monitor.cb = cb; new_monitor.data = ao2_bump(ao2_data); if (AST_VECTOR_APPEND(&monitored->monitors, new_monitor)) { ao2_cleanup(ao2_data); res = AST_TRANSPORT_MONITOR_REG_FAILED; ast_debug(3, "Transport %s(%s) RefCnt: %ld : Monitor registration failed %p(%p)\n", monitored->key, monitored->transport_obj_name, pj_atomic_get(monitored->transport->ref_cnt), cb, ao2_data); } else { res = AST_TRANSPORT_MONITOR_REG_SUCCESS; ast_debug(3, "Transport %s(%s,%s) RefCnt: %ld : Registered monitor %p(%p)\n", monitored->key, monitored->transport_obj_name, monitored->transport->type_name, pj_atomic_get(monitored->transport->ref_cnt), cb, ao2_data); } ao2_ref(monitored, -1); } ao2_unlock(transports); ao2_ref(transports, -1); return res; } void ast_sip_transport_state_unregister(struct ast_sip_tpmgr_state_callback *element) { AST_RWLIST_WRLOCK(&transport_state_list); AST_LIST_REMOVE(&transport_state_list, element, node); AST_RWLIST_UNLOCK(&transport_state_list); } void ast_sip_transport_state_register(struct ast_sip_tpmgr_state_callback *element) { struct ast_sip_tpmgr_state_callback *tpmgr_notifier; AST_RWLIST_WRLOCK(&transport_state_list); AST_LIST_TRAVERSE(&transport_state_list, tpmgr_notifier, node) { if (element == tpmgr_notifier) { /* Already registered. */ AST_RWLIST_UNLOCK(&transport_state_list); return; } } AST_LIST_INSERT_HEAD(&transport_state_list, element, node); AST_RWLIST_UNLOCK(&transport_state_list); } static char *cli_show_monitors(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { char *cli_rc = CLI_FAILURE; int rc = 0; int using_regex = 0; regex_t regex = { 0, }; int container_count; struct ao2_iterator iter; struct ao2_container *sorted_monitors = NULL; struct ao2_container *transports; struct transport_monitor *monitored; switch (cmd) { case CLI_INIT: e->command = "pjsip show transport-monitors"; e->usage = "Usage: pjsip show transport-monitors [ like ]\n" " Show pjsip transport monitors\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3 && a->argc != 5) { return CLI_SHOWUSAGE; } if (a->argc == 5) { int regrc; if (strcasecmp(a->argv[3], "like")) { return CLI_SHOWUSAGE; } regrc = regcomp(®ex, a->argv[4], REG_EXTENDED | REG_ICASE | REG_NOSUB); if (regrc) { char err[256]; regerror(regrc, ®ex, err, 256); ast_cli(a->fd, "PJSIP Transport Monitor: Error: %s\n", err); return CLI_FAILURE; } using_regex = 1; } /* Get a sorted snapshot of the scheduled tasks */ sorted_monitors = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, transport_monitor_sort_fn, NULL); if (!sorted_monitors) { ast_cli(a->fd, "PJSIP Transport Monitor: Unable to allocate temporary container\n"); goto error; } transports = ao2_global_obj_ref(active_transports); if (!transports) { ast_cli(a->fd, "PJSIP Transport Monitor: Unable to get transports\n"); goto error; } ao2_lock(transports); rc = ao2_container_dup(sorted_monitors, transports, 0); ao2_unlock(transports); ao2_ref(transports, -1); if (rc != 0) { ast_cli(a->fd, "PJSIP Transport Monitors: Unable to sort temporary container\n"); goto error; } container_count = ao2_container_count(sorted_monitors); ast_cli(a->fd, "PJSIP Transport Monitors:\n\n"); ast_cli(a->fd, " \n"); iter = ao2_iterator_init(sorted_monitors, AO2_ITERATOR_UNLINK); for (; (monitored = ao2_iterator_next(&iter)); ao2_ref(monitored, -1)) { char *state; if (using_regex && regexec(®ex, monitored->key, 0, NULL, 0) == REG_NOMATCH) { continue; } if (monitored->transport->is_destroying) { state = "DESTROYING"; } else if (monitored->transport->is_shutdown) { state = "SHUTDOWN"; } else { state = "ACTIVE"; } ast_cli(a->fd, " %-46.46s %-10s %-9s %6ld %8" PRIu64 " %s\n", monitored->key, state, monitored->transport->dir == PJSIP_TP_DIR_OUTGOING ? "Outgoing" : "Incoming", pj_atomic_get(monitored->transport->ref_cnt), AST_VECTOR_SIZE(&monitored->monitors), monitored->transport->obj_name); } ao2_iterator_destroy(&iter); ast_cli(a->fd, "\nTotal Transport Monitors: %d\n\n", container_count); cli_rc = CLI_SUCCESS; error: if (using_regex) { regfree(®ex); } ao2_cleanup(sorted_monitors); return cli_rc; } static struct ast_cli_entry cli_commands[] = { AST_CLI_DEFINE(cli_show_monitors, "Show pjsip transport monitors"), }; void ast_sip_destroy_transport_events(void) { pjsip_tpmgr *tpmgr; ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands)); tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()); if (tpmgr) { pjsip_tpmgr_set_state_cb(tpmgr, tpmgr_state_callback); } ao2_global_obj_release(active_transports); } int ast_sip_initialize_transport_events(void) { pjsip_tpmgr *tpmgr; struct ao2_container *transports; tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()); if (!tpmgr) { return -1; } transports = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, ACTIVE_TRANSPORTS_BUCKETS, transport_monitor_hash_fn, transport_monitor_sort_fn, transport_monitor_cmp_fn); if (!transports) { return -1; } ao2_global_obj_replace_unref(active_transports, transports); ao2_ref(transports, -1); tpmgr_state_callback = pjsip_tpmgr_get_state_cb(tpmgr); pjsip_tpmgr_set_state_cb(tpmgr, &transport_state_callback); ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands)); return 0; }