summaryrefslogtreecommitdiff
path: root/res/res_pjsip/pjsip_distributor.c
diff options
context:
space:
mode:
Diffstat (limited to 'res/res_pjsip/pjsip_distributor.c')
-rw-r--r--res/res_pjsip/pjsip_distributor.c358
1 files changed, 352 insertions, 6 deletions
diff --git a/res/res_pjsip/pjsip_distributor.c b/res/res_pjsip/pjsip_distributor.c
index 834ca10d3..cbe955728 100644
--- a/res/res_pjsip/pjsip_distributor.c
+++ b/res/res_pjsip/pjsip_distributor.c
@@ -24,6 +24,7 @@
#include "include/res_pjsip_private.h"
#include "asterisk/taskprocessor.h"
#include "asterisk/threadpool.h"
+#include "asterisk/res_pjsip_cli.h"
static int distribute(void *data);
static pj_bool_t distributor(pjsip_rx_data *rdata);
@@ -37,6 +38,26 @@ static pjsip_module distributor_mod = {
.on_rx_response = distributor,
};
+struct ast_sched_context *prune_context;
+
+/* From the auth/realm realtime column size */
+#define MAX_REALM_LENGTH 40
+static char default_realm[MAX_REALM_LENGTH + 1];
+
+#define DEFAULT_SUSPECTS_BUCKETS 53
+
+static struct ao2_container *unidentified_requests;
+static unsigned int unidentified_count;
+static unsigned int unidentified_period;
+static unsigned int unidentified_prune_interval;
+static int using_auth_username;
+
+struct unidentified_request{
+ struct timeval first_seen;
+ int count;
+ char src_name[];
+};
+
/*!
* \internal
* \brief Record the task's serializer name on the tdata structure.
@@ -322,7 +343,7 @@ static int create_artificial_auth(void)
return -1;
}
- ast_string_field_set(artificial_auth, realm, "asterisk");
+ ast_string_field_set(artificial_auth, realm, default_realm);
ast_string_field_set(artificial_auth, auth_user, "");
ast_string_field_set(artificial_auth, auth_pass, "");
artificial_auth->type = AST_SIP_AUTH_TYPE_ARTIFICIAL;
@@ -359,27 +380,65 @@ struct ast_sip_endpoint *ast_sip_get_artificial_endpoint(void)
return artificial_endpoint;
}
-static void log_unidentified_request(pjsip_rx_data *rdata)
+static void log_unidentified_request(pjsip_rx_data *rdata, unsigned int count, unsigned int period)
{
char from_buf[PJSIP_MAX_URL_SIZE];
char callid_buf[PJSIP_MAX_URL_SIZE];
pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, rdata->msg_info.from->uri, from_buf, PJSIP_MAX_URL_SIZE);
ast_copy_pj_str(callid_buf, &rdata->msg_info.cid->id, PJSIP_MAX_URL_SIZE);
- ast_log(LOG_NOTICE, "Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found\n",
- from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf);
+ if (count) {
+ ast_log(LOG_NOTICE, "Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found"
+ " after %u tries in %.3f ms\n",
+ from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf, count, period / 1000.0);
+ } else {
+ ast_log(LOG_NOTICE, "Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found",
+ from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf);
+ }
+}
+
+static void check_endpoint(pjsip_rx_data *rdata, struct unidentified_request *unid,
+ const char *name)
+{
+ int64_t ms = ast_tvdiff_ms(ast_tvnow(), unid->first_seen);
+
+ ao2_wrlock(unid);
+ unid->count++;
+
+ if (ms < (unidentified_period * 1000) && unid->count >= unidentified_count) {
+ log_unidentified_request(rdata, unid->count, ms);
+ ast_sip_report_invalid_endpoint(name, rdata);
+ }
+ ao2_unlock(unid);
}
static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata)
{
struct ast_sip_endpoint *endpoint;
+ struct unidentified_request *unid;
int is_ack = rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD;
endpoint = rdata->endpt_info.mod_data[endpoint_mod.id];
if (endpoint) {
+ /*
+ * ao2_find with OBJ_UNLINK always write locks the container before even searching
+ * for the object. Since the majority case is that the object won't be found, do
+ * the find without OBJ_UNLINK to prevent the unnecessary write lock, then unlink
+ * if needed.
+ */
+ if ((unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY))) {
+ ao2_unlink(unidentified_requests, unid);
+ ao2_ref(unid, -1);
+ }
return PJ_FALSE;
}
endpoint = ast_sip_identify_endpoint(rdata);
+ if (endpoint) {
+ if ((unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY))) {
+ ao2_unlink(unidentified_requests, unid);
+ ao2_ref(unid, -1);
+ }
+ }
if (!endpoint && !is_ack) {
char name[AST_UUID_STR_LEN] = "";
@@ -397,8 +456,32 @@ static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata)
ast_copy_pj_str(name, &sip_from->user, sizeof(name));
}
- log_unidentified_request(rdata);
- ast_sip_report_invalid_endpoint(name, rdata);
+ if ((unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY))) {
+ check_endpoint(rdata, unid, name);
+ ao2_ref(unid, -1);
+ } else if (using_auth_username) {
+ ao2_wrlock(unidentified_requests);
+ /* The check again with the write lock held allows us to eliminate the DUPS_REPLACE and sort_fn */
+ if ((unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY | OBJ_NOLOCK))) {
+ check_endpoint(rdata, unid, name);
+ } else {
+ unid = ao2_alloc_options(sizeof(*unid) + strlen(rdata->pkt_info.src_name) + 1, NULL,
+ AO2_ALLOC_OPT_LOCK_RWLOCK);
+ if (!unid) {
+ ao2_unlock(unidentified_requests);
+ return PJ_TRUE;
+ }
+ strcpy(unid->src_name, rdata->pkt_info.src_name); /* Safe */
+ unid->first_seen = ast_tvnow();
+ unid->count = 1;
+ ao2_link_flags(unidentified_requests, unid, OBJ_NOLOCK);
+ }
+ ao2_ref(unid, -1);
+ ao2_unlock(unidentified_requests);
+ } else {
+ log_unidentified_request(rdata, 0, 0);
+ ast_sip_report_invalid_endpoint(name, rdata);
+ }
}
rdata->endpt_info.mod_data[endpoint_mod.id] = endpoint;
return PJ_FALSE;
@@ -413,6 +496,8 @@ static pj_bool_t authenticate(pjsip_rx_data *rdata)
if (!is_ack && ast_sip_requires_authentication(endpoint, rdata)) {
pjsip_tx_data *tdata;
+ struct unidentified_request *unid;
+
pjsip_endpt_create_response(ast_sip_get_pjsip_endpoint(), rdata, 401, NULL, &tdata);
switch (ast_sip_check_authentication(endpoint, rdata, tdata)) {
case AST_SIP_AUTHENTICATION_CHALLENGE:
@@ -421,6 +506,11 @@ static pj_bool_t authenticate(pjsip_rx_data *rdata)
pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL);
return PJ_TRUE;
case AST_SIP_AUTHENTICATION_SUCCESS:
+ /* See note in endpoint_lookup about not holding an unnecessary write lock */
+ if ((unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY))) {
+ ao2_unlink(unidentified_requests, unid);
+ ao2_ref(unid, -1);
+ }
ast_sip_report_auth_success(endpoint, rdata);
pjsip_tx_data_dec_ref(tdata);
return PJ_FALSE;
@@ -480,31 +570,287 @@ struct ast_sip_endpoint *ast_pjsip_rdata_get_endpoint(pjsip_rx_data *rdata)
return endpoint;
}
+static int suspects_sort(const void *obj, const void *arg, int flags)
+{
+ const struct unidentified_request *object_left = obj;
+ const struct unidentified_request *object_right = arg;
+ const char *right_key = arg;
+ int cmp;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ right_key = object_right->src_name;
+ /* Fall through */
+ case OBJ_SEARCH_KEY:
+ cmp = strcmp(object_left->src_name, right_key);
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ cmp = strncmp(object_left->src_name, right_key, strlen(right_key));
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+ return cmp;
+}
+
+static int suspects_compare(void *obj, void *arg, int flags)
+{
+ const struct unidentified_request *object_left = obj;
+ const struct unidentified_request *object_right = arg;
+ const char *right_key = arg;
+ int cmp = 0;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ right_key = object_right->src_name;
+ /* Fall through */
+ case OBJ_SEARCH_KEY:
+ if (strcmp(object_left->src_name, right_key) == 0) {
+ cmp = CMP_MATCH | CMP_STOP;
+ }
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ if (strncmp(object_left->src_name, right_key, strlen(right_key)) == 0) {
+ cmp = CMP_MATCH;
+ }
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+ return cmp;
+}
+
+static int suspects_hash(const void *obj, int flags) {
+ const struct unidentified_request *object_left = obj;
+
+ if (flags & OBJ_SEARCH_OBJECT) {
+ return ast_str_hash(object_left->src_name);
+ } else if (flags & OBJ_SEARCH_KEY) {
+ return ast_str_hash(obj);
+ }
+ return -1;
+}
+
+static struct ao2_container *cli_unid_get_container(const char *regex)
+{
+ struct ao2_container *s_container;
+
+ s_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
+ suspects_sort, suspects_compare);
+ if (!s_container) {
+ return NULL;
+ }
+
+ if (ao2_container_dup(s_container, unidentified_requests, 0)) {
+ ao2_ref(s_container, -1);
+ return NULL;
+ }
+
+ return s_container;
+}
+
+static int cli_unid_iterate(void *container, ao2_callback_fn callback, void *args)
+{
+ ao2_callback(container, 0, callback, args);
+
+ return 0;
+}
+
+static void *cli_unid_retrieve_by_id(const char *id)
+{
+ return ao2_find(unidentified_requests, id, OBJ_SEARCH_KEY);
+}
+
+static const char *cli_unid_get_id(const void *obj)
+{
+ const struct unidentified_request *unid = obj;
+
+ return unid->src_name;
+}
+
+static int cli_unid_print_header(void *obj, void *arg, int flags)
+{
+ struct ast_sip_cli_context *context = arg;
+ RAII_VAR(struct ast_sip_cli_formatter_entry *, formatter_entry, NULL, ao2_cleanup);
+
+ int indent = CLI_INDENT_TO_SPACES(context->indent_level);
+ int filler = CLI_LAST_TABSTOP - indent - 7;
+
+ ast_assert(context->output_buffer != NULL);
+
+ ast_str_append(&context->output_buffer, 0,
+ "%*s: <IP Address%*.*s> <Count> <Age(sec)>\n",
+ indent, "Request", filler, filler, CLI_HEADER_FILLER);
+
+ return 0;
+}
+static int cli_unid_print_body(void *obj, void *arg, int flags)
+{
+ struct unidentified_request *unid = obj;
+ struct ast_sip_cli_context *context = arg;
+ int indent;
+ int flexwidth;
+ int64_t ms = ast_tvdiff_ms(ast_tvnow(), unid->first_seen);
+
+ ast_assert(context->output_buffer != NULL);
+
+ indent = CLI_INDENT_TO_SPACES(context->indent_level);
+ flexwidth = CLI_LAST_TABSTOP - 4;
+
+ ast_str_append(&context->output_buffer, 0, "%*s: %-*.*s %7d %10.3f\n",
+ indent,
+ "Request",
+ flexwidth, flexwidth,
+ unid->src_name, unid->count, ms / 1000.0);
+
+ return 0;
+}
+
+static struct ast_cli_entry cli_commands[] = {
+ AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Unidentified Requests",
+ .command = "pjsip show unidentified_requests",
+ .usage = "Usage: pjsip show unidentified_requests\n"
+ " Show the PJSIP Unidentified Requests\n"),
+};
+
+struct ast_sip_cli_formatter_entry *unid_formatter;
+
+static int expire_requests(void *object, void *arg, int flags)
+{
+ struct unidentified_request *unid = object;
+ int *maxage = arg;
+ int64_t ms = ast_tvdiff_ms(ast_tvnow(), unid->first_seen);
+
+ if (ms > (*maxage) * 2 * 1000) {
+ return CMP_MATCH;
+ }
+
+ return 0;
+}
+
+static int prune_task(const void *data)
+{
+ unsigned int maxage;
+
+ ast_sip_get_unidentified_request_thresholds(&unidentified_count, &unidentified_period, &unidentified_prune_interval);
+ maxage = unidentified_period * 2;
+ ao2_callback(unidentified_requests, OBJ_MULTIPLE | OBJ_NODATA | OBJ_UNLINK, expire_requests, &maxage);
+
+ return unidentified_prune_interval * 1000;
+}
+
+static int clean_task(const void *data)
+{
+ return 0;
+}
+
+static void global_loaded(const char *object_type)
+{
+ char *identifier_order = ast_sip_get_endpoint_identifier_order();
+ char *io_copy = ast_strdupa(identifier_order);
+ char *identify_method;
+
+ ast_free(identifier_order);
+ using_auth_username = 0;
+ while ((identify_method = ast_strip(strsep(&io_copy, ",")))) {
+ if (!strcmp(identify_method, "auth_username")) {
+ using_auth_username = 1;
+ break;
+ }
+ }
+
+ ast_sip_get_default_realm(default_realm, sizeof(default_realm));
+ ast_sip_get_unidentified_request_thresholds(&unidentified_count, &unidentified_period, &unidentified_prune_interval);
+
+ /* Clean out the old task, if any */
+ ast_sched_clean_by_callback(prune_context, prune_task, clean_task);
+ if (ast_sched_add_variable(prune_context, unidentified_prune_interval * 1000, prune_task, NULL, 1) < 0) {
+ return;
+ }
+}
+
+/*! \brief Observer which is used to update our interval and default_realm when the global setting changes */
+static struct ast_sorcery_observer global_observer = {
+ .loaded = global_loaded,
+};
+
+
int ast_sip_initialize_distributor(void)
{
+ unidentified_requests = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, 0,
+ DEFAULT_SUSPECTS_BUCKETS, suspects_hash, NULL, suspects_compare);
+ if (!unidentified_requests) {
+ return -1;
+ }
+
+ prune_context = ast_sched_context_create();
+ if (!prune_context) {
+ ast_sip_destroy_distributor();
+ return -1;
+ }
+
+ if (ast_sched_start_thread(prune_context)) {
+ ast_sip_destroy_distributor();
+ return -1;
+ }
+
+ ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &global_observer);
+ ast_sorcery_reload_object(ast_sip_get_sorcery(), "global");
+
if (create_artificial_endpoint() || create_artificial_auth()) {
+ ast_sip_destroy_distributor();
return -1;
}
if (internal_sip_register_service(&distributor_mod)) {
+ ast_sip_destroy_distributor();
return -1;
}
if (internal_sip_register_service(&endpoint_mod)) {
+ ast_sip_destroy_distributor();
return -1;
}
if (internal_sip_register_service(&auth_mod)) {
+ ast_sip_destroy_distributor();
return -1;
}
+ unid_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL);
+ if (!unid_formatter) {
+ ast_log(LOG_ERROR, "Unable to allocate memory for unid_formatter\n");
+ return -1;
+ }
+ unid_formatter->name = "unidentified_request";
+ unid_formatter->print_header = cli_unid_print_header;
+ unid_formatter->print_body = cli_unid_print_body;
+ unid_formatter->get_container = cli_unid_get_container;
+ unid_formatter->iterate = cli_unid_iterate;
+ unid_formatter->get_id = cli_unid_get_id;
+ unid_formatter->retrieve_by_id = cli_unid_retrieve_by_id;
+ ast_sip_register_cli_formatter(unid_formatter);
+ ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands));
+
return 0;
}
void ast_sip_destroy_distributor(void)
{
+ ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
+ ast_sip_unregister_cli_formatter(unid_formatter);
+
internal_sip_unregister_service(&distributor_mod);
internal_sip_unregister_service(&endpoint_mod);
internal_sip_unregister_service(&auth_mod);
ao2_cleanup(artificial_auth);
ao2_cleanup(artificial_endpoint);
+ ao2_cleanup(unidentified_requests);
+
+ ast_sorcery_observer_remove(ast_sip_get_sorcery(), "global", &global_observer);
+
+ if (prune_context) {
+ ast_sched_context_destroy(prune_context);
+ }
}