summaryrefslogtreecommitdiff
path: root/res/res_pjsip
diff options
context:
space:
mode:
authorGeorge Joseph <george.joseph@fairview5.com>2016-03-07 17:34:31 -0700
committerGeorge Joseph <gjoseph@digium.com>2016-04-27 16:33:51 -0500
commit4ebf9a938daed520ba627dd0f17f4f3c65245ab2 (patch)
tree31abd9fa94ed622b1d5c2713df81ff1fe9c4c65f /res/res_pjsip
parentf3248017633bc8d556bfbb3e81ce56ca67f2d822 (diff)
res_pjsip: Add ability to identify by Authorization username
A feature of chan_sip that service providers relied upon was the ability to identify by the Authorization username. This is most often used when customers have a PBX that needs to register rather than identify by IP address. From my own experiance, this is pretty common with small businesses who otherwise don't need a static IP. In this scenario, a register from the customer's PBX may succeed because From will usually contain the PBXs account id but an INVITE will contain the caller id. With nothing recognizable in From, the service provider's Asterisk can never match to an endpoint and the INVITE just stays unauthorized. The fixes: A new value "auth_username" has been added to endpoint/identify_by that will use the username and digest fields in the Authorization header instead of username and domain in the the From header to match an endpoint, or the To header to match an aor. This code as added to res_pjsip_endpoint_identifier_user rather than creating a new module. Although identify_by was always a comma-separated list, there was only 1 choice so order wasn't preserved. So to keep the order, a vector was added to the end of ast_sip_endpoint. This is only used by res_pjsip_registrar to find the aor. The res_pjsip_endpoint_identifier_* modules are called in globals/endpoint_identifier_order. Along the way, the logic in res_pjsip_registrar was corrected to match most-specific to least-specific as res_pjsip_endpoint_identifier_user does. The order is: username@domain username@domain_alias username Auth by username does present 1 problem however, the first INVITE won't have an Authorization header so the distributor, not finding a match on anything, sends a securty_alert. It still sends a 401 with a challenge so the next INVITE will have the Authorization header and presumably succeed. As a result though, that first security alert is actually a false alarm. To address this, a new feature has been added to pjsip_distributor that keeps track of unidentified requests and only sends the security alert if a configurable number of unidentified requests come from the same IP in a configurable amout of time. Those configuration options have been added to the global config object. This feature is only used when auth_username is enabled. Finally, default_realm was added to the globals object to replace the hard coded "asterisk" used when an endpoint is not yet identified. The testsuite tests all pass but new tests are forthcoming for this new feature. ASTERISK-25835 #close Reported-by: Ross Beer Change-Id: I30ba62d208e6f63439600916fcd1c08a365ed69d
Diffstat (limited to 'res/res_pjsip')
-rw-r--r--res/res_pjsip/config_global.c57
-rw-r--r--res/res_pjsip/pjsip_configuration.c56
-rw-r--r--res/res_pjsip/pjsip_distributor.c358
3 files changed, 459 insertions, 12 deletions
diff --git a/res/res_pjsip/config_global.c b/res/res_pjsip/config_global.c
index ccfec5435..6bb688804 100644
--- a/res/res_pjsip/config_global.c
+++ b/res/res_pjsip/config_global.c
@@ -35,10 +35,14 @@
#define DEFAULT_ENDPOINT_IDENTIFIER_ORDER "ip,username,anonymous"
#define DEFAULT_MAX_INITIAL_QUALIFY_TIME 0
#define DEFAULT_FROM_USER "asterisk"
+#define DEFAULT_REALM "asterisk"
#define DEFAULT_REGCONTEXT ""
#define DEFAULT_CONTACT_EXPIRATION_CHECK_INTERVAL 30
#define DEFAULT_DISABLE_MULTI_DOMAIN 0
#define DEFAULT_VOICEMAIL_EXTENSION ""
+#define DEFAULT_UNIDENTIFIED_REQUEST_COUNT 5
+#define DEFAULT_UNIDENTIFIED_REQUEST_PERIOD 5
+#define DEFAULT_UNIDENTIFIED_REQUEST_PRUNE_INTERVAL 30
static char default_useragent[256];
@@ -56,6 +60,8 @@ struct global_config {
AST_STRING_FIELD(default_from_user);
/*! Default voicemail extension */
AST_STRING_FIELD(default_voicemail_extension);
+ /*! Realm to use in challenges before an endpoint is identified */
+ AST_STRING_FIELD(default_realm);
);
/* Value to put in Max-Forwards header */
unsigned int max_forwards;
@@ -67,6 +73,12 @@ struct global_config {
unsigned int contact_expiration_check_interval;
/*! Nonzero to disable multi domain support */
unsigned int disable_multi_domain;
+ /* The maximum number of unidentified requests per source IP address before a security event is logged */
+ unsigned int unidentified_request_count;
+ /* The period during which unidentified requests are accumulated */
+ unsigned int unidentified_request_period;
+ /* Interval at which expired unidentifed requests will be pruned */
+ unsigned int unidentified_request_prune_interval;
};
static void global_destructor(void *obj)
@@ -255,6 +267,40 @@ unsigned int ast_sip_get_max_initial_qualify_time(void)
return time;
}
+void ast_sip_get_unidentified_request_thresholds(unsigned int *count, unsigned int *period,
+ unsigned int *prune_interval)
+{
+ struct global_config *cfg;
+
+ cfg = get_global_cfg();
+ if (!cfg) {
+ *count = DEFAULT_UNIDENTIFIED_REQUEST_COUNT;
+ *period = DEFAULT_UNIDENTIFIED_REQUEST_PERIOD;
+ *prune_interval = DEFAULT_UNIDENTIFIED_REQUEST_PRUNE_INTERVAL;
+ return;
+ }
+
+ *count = cfg->unidentified_request_count;
+ *period = cfg->unidentified_request_period;
+ *prune_interval = cfg->unidentified_request_prune_interval;
+
+ ao2_ref(cfg, -1);
+ return;
+}
+
+void ast_sip_get_default_realm(char *realm, size_t size)
+{
+ struct global_config *cfg;
+
+ cfg = get_global_cfg();
+ if (!cfg) {
+ ast_copy_string(realm, DEFAULT_REALM, size);
+ } else {
+ ast_copy_string(realm, cfg->default_realm, size);
+ ao2_ref(cfg, -1);
+ }
+}
+
void ast_sip_get_default_from_user(char *from_user, size_t size)
{
struct global_config *cfg;
@@ -393,6 +439,17 @@ int ast_sip_initialize_sorcery_global(void)
OPT_UINT_T, 0, FLDSET(struct global_config, contact_expiration_check_interval));
ast_sorcery_object_field_register(sorcery, "global", "disable_multi_domain", "no",
OPT_BOOL_T, 1, FLDSET(struct global_config, disable_multi_domain));
+ ast_sorcery_object_field_register(sorcery, "global", "unidentified_request_count",
+ __stringify(DEFAULT_UNIDENTIFIED_REQUEST_COUNT),
+ OPT_UINT_T, 0, FLDSET(struct global_config, unidentified_request_count));
+ ast_sorcery_object_field_register(sorcery, "global", "unidentified_request_period",
+ __stringify(DEFAULT_UNIDENTIFIED_REQUEST_PERIOD),
+ OPT_UINT_T, 0, FLDSET(struct global_config, unidentified_request_period));
+ ast_sorcery_object_field_register(sorcery, "global", "unidentified_request_prune_interval",
+ __stringify(DEFAULT_UNIDENTIFIED_REQUEST_PRUNE_INTERVAL),
+ OPT_UINT_T, 0, FLDSET(struct global_config, unidentified_request_prune_interval));
+ ast_sorcery_object_field_register(sorcery, "global", "default_realm", DEFAULT_REALM,
+ OPT_STRINGFIELD_T, 0, STRFLDSET(struct global_config, default_realm));
if (ast_sorcery_instance_observer_add(sorcery, &observer_callbacks_global)) {
return -1;
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index baa5063e4..c370ab75f 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -476,6 +476,16 @@ static int ident_handler(const struct aco_option *opt, struct ast_variable *var,
struct ast_sip_endpoint *endpoint = obj;
char *idents = ast_strdupa(var->value);
char *val;
+ enum ast_sip_endpoint_identifier_type method;
+
+ /*
+ * If there's already something in the vector when we get here,
+ * it's the default value so we need to clean it out.
+ */
+ if (AST_VECTOR_SIZE(&endpoint->ident_method_order)) {
+ AST_VECTOR_RESET(&endpoint->ident_method_order, AST_VECTOR_ELEM_CLEANUP_NOOP);
+ endpoint->ident_method = 0;
+ }
while ((val = ast_strip(strsep(&idents, ",")))) {
if (ast_strlen_zero(val)) {
@@ -483,27 +493,55 @@ static int ident_handler(const struct aco_option *opt, struct ast_variable *var,
}
if (!strcasecmp(val, "username")) {
- endpoint->ident_method |= AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME;
+ method = AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME;
+ } else if (!strcasecmp(val, "auth_username")) {
+ method = AST_SIP_ENDPOINT_IDENTIFY_BY_AUTH_USERNAME;
} else {
ast_log(LOG_ERROR, "Unrecognized identification method %s specified for endpoint %s\n",
val, ast_sorcery_object_get_id(endpoint));
+ AST_VECTOR_RESET(&endpoint->ident_method_order, AST_VECTOR_ELEM_CLEANUP_NOOP);
+ endpoint->ident_method = 0;
return -1;
}
+
+ endpoint->ident_method |= method;
+ AST_VECTOR_APPEND(&endpoint->ident_method_order, method);
}
+
return 0;
}
static int ident_to_str(const void *obj, const intptr_t *args, char **buf)
{
const struct ast_sip_endpoint *endpoint = obj;
- switch (endpoint->ident_method) {
- case AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME :
- *buf = "username"; break;
- default:
+ int methods;
+ char *method;
+ int i;
+ int j = 0;
+
+ methods = AST_VECTOR_SIZE(&endpoint->ident_method_order);
+ if (!methods) {
return 0;
}
- *buf = ast_strdup(*buf);
+ if (!(*buf = ast_calloc(MAX_OBJECT_FIELD, sizeof(char)))) {
+ return -1;
+ }
+
+ for (i = 0; i < methods; i++) {
+ switch (AST_VECTOR_GET(&endpoint->ident_method_order, i)) {
+ case AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME :
+ method = "username";
+ break;
+ case AST_SIP_ENDPOINT_IDENTIFY_BY_AUTH_USERNAME :
+ method = "auth_username";
+ break;
+ default:
+ continue;
+ }
+ j = sprintf(*buf + j, "%s%s", method, i < methods - 1 ? "," : "");
+ }
+
return 0;
}
@@ -1849,6 +1887,7 @@ static void endpoint_destructor(void* obj)
endpoint->pickup.named_pickupgroups = ast_unref_namedgroups(endpoint->pickup.named_pickupgroups);
ao2_cleanup(endpoint->persistent);
ast_variables_destroy(endpoint->channel_vars);
+ AST_VECTOR_FREE(&endpoint->ident_method_order);
}
static int init_subscription_configuration(struct ast_sip_endpoint_subscription_configuration *subscription)
@@ -1893,6 +1932,11 @@ void *ast_sip_endpoint_alloc(const char *name)
return NULL;
}
ast_party_id_init(&endpoint->id.self);
+
+ if (AST_VECTOR_INIT(&endpoint->ident_method_order, 1)) {
+ return NULL;
+ }
+
return endpoint;
}
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);
+ }
}