Skip to content
Snippets Groups Projects
Commit ac8db871 authored by Friendly Automation's avatar Friendly Automation Committed by Gerrit Code Review
Browse files

Merge "res_pjsip_mwi: add better handling of solicited vs unsolicited subscriptions"

parents 9d3d00ce 172e183b
No related branches found
No related tags found
No related merge requests found
......@@ -42,6 +42,7 @@
struct mwi_subscription;
static struct ao2_container *unsolicited_mwi;
static struct ao2_container *solicited_mwi;
static char *default_voicemail_extension;
......@@ -119,6 +120,8 @@ struct mwi_subscription {
char *aors;
/*! Is the MWI solicited (i.e. Initiated with an external SUBSCRIBE) ? */
unsigned int is_solicited;
/*! True if this subscription is to be terminated */
unsigned int terminate;
/*! Identifier for the subscription.
* The identifier is the same as the corresponding endpoint's stasis ID.
* Used as a hash key
......@@ -665,7 +668,7 @@ static void send_mwi_notify(struct mwi_subscription *sub)
ao2_cleanup(aor);
ao2_cleanup(endpoint);
ast_sip_subscription_notify(sub->sip_sub, &data, 0);
ast_sip_subscription_notify(sub->sip_sub, &data, sub->terminate);
return;
}
......@@ -676,18 +679,22 @@ static void send_mwi_notify(struct mwi_subscription *sub)
static int unsubscribe_stasis(void *obj, void *arg, int flags)
{
struct mwi_stasis_subscription *mwi_stasis = obj;
if (mwi_stasis->mwi_subscriber) {
ast_debug(3, "Removing stasis subscription to mailbox %s\n", mwi_stasis->mailbox);
mwi_stasis->mwi_subscriber = ast_mwi_unsubscribe_and_join(mwi_stasis->mwi_subscriber);
}
return CMP_MATCH;
}
static int create_unsolicited_mwi_subscriptions(struct ast_sip_endpoint *endpoint,
int recreate, int send_now);
static void mwi_subscription_shutdown(struct ast_sip_subscription *sub)
{
struct mwi_subscription *mwi_sub;
struct ast_datastore *mwi_datastore;
struct ast_sip_endpoint *endpoint = NULL;
mwi_datastore = ast_sip_subscription_get_datastore(sub, MWI_DATASTORE);
if (!mwi_datastore) {
......@@ -695,10 +702,25 @@ static void mwi_subscription_shutdown(struct ast_sip_subscription *sub)
}
mwi_sub = mwi_datastore->data;
ao2_callback(mwi_sub->stasis_subs, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, unsubscribe_stasis, NULL);
ast_sip_subscription_remove_datastore(sub, MWI_DATASTORE);
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", mwi_sub->id);
ao2_ref(mwi_datastore, -1);
ao2_unlink(solicited_mwi, mwi_sub);
/*
* When a solicited subscription is removed it's possible an unsolicited one
* needs to be [re-]created. Attempt to establish unsolicited MWI.
*/
if (unsolicited_mwi && endpoint) {
ao2_lock(unsolicited_mwi);
create_unsolicited_mwi_subscriptions(endpoint, 1, 1);
ao2_unlock(unsolicited_mwi);
}
ao2_cleanup(endpoint);
}
static void mwi_ds_destroy(void *data)
......@@ -734,43 +756,165 @@ static int add_mwi_datastore(struct mwi_subscription *sub)
}
/*!
* \brief Determines if an endpoint is receiving unsolicited MWI for a particular mailbox.
* \internal
* \brief Determine if an MWI subscription already exists for the given endpoint/mailbox
*
* Search the given container, and attempt to find out if the given endpoint has a
* current subscription within. If so pass back the associated mwi_subscription and
* mwi_stasis_subscription objects.
*
* \note If a subscription is located then the caller is responsible for removing the
* references to the passed back mwi_subscription and mwi_stasis_subscription objects.
*
* \note Must be called with the given container already locked.
*
* \param endpoint The endpoint to check
* \param mailbox The candidate mailbox
* \retval 0 The endpoint does not receive unsolicited MWI for this mailbox
* \retval 1 The endpoint receives unsolicited MWI for this mailbox
* \param container The ao2_container to search
* \param endpoint The endpoint to find
* \param mailbox The mailbox potentially subscribed
* \param mwi_sub [out] May contain the located mwi_subscription
* \param mwi_stasis [out] May contain the located mwi_stasis_subscription
*
* \retval 1 if a subscription was located, 0 otherwise
*/
static int endpoint_receives_unsolicited_mwi_for_mailbox(struct ast_sip_endpoint *endpoint,
const char *mailbox)
static int has_mwi_subscription(struct ao2_container *container,
struct ast_sip_endpoint *endpoint, const char *mailbox,
struct mwi_subscription **mwi_sub, struct mwi_stasis_subscription **mwi_stasis)
{
struct ao2_iterator *mwi_subs;
struct mwi_subscription *mwi_sub;
const char *endpoint_id = ast_sorcery_object_get_id(endpoint);
int ret = 0;
mwi_subs = ao2_find(unsolicited_mwi, endpoint_id, OBJ_SEARCH_KEY | OBJ_MULTIPLE);
*mwi_sub = NULL;
*mwi_stasis = NULL;
mwi_subs = ao2_find(container, ast_sorcery_object_get_id(endpoint),
OBJ_SEARCH_KEY | OBJ_MULTIPLE | OBJ_NOLOCK);
if (!mwi_subs) {
return 0;
}
for (; (mwi_sub = ao2_iterator_next(mwi_subs)) && !ret; ao2_cleanup(mwi_sub)) {
struct mwi_stasis_subscription *mwi_stasis;
mwi_stasis = ao2_find(mwi_sub->stasis_subs, mailbox, OBJ_SEARCH_KEY);
if (mwi_stasis) {
if (endpoint->subscription.mwi.subscribe_replaces_unsolicited) {
unsubscribe_stasis(mwi_stasis, NULL, 0);
ao2_unlink(mwi_sub->stasis_subs, mwi_stasis);
} else {
ret = 1;
}
ao2_cleanup(mwi_stasis);
while ((*mwi_sub = ao2_iterator_next(mwi_subs))) {
*mwi_stasis = ao2_find((*mwi_sub)->stasis_subs, mailbox, OBJ_SEARCH_KEY);
if (*mwi_stasis) {
/* If found then caller is responsible for unrefs of passed back objects */
break;
}
ao2_ref(*mwi_sub, -1);
}
ao2_iterator_destroy(mwi_subs);
return ret;
return *mwi_stasis ? 1 : 0;
}
/*!
* \internal
* \brief Allow and/or replace the unsolicited subscription
*
* Checks to see if solicited subscription is allowed. If allowed, and an
* unsolicited one exists then prepare for replacement by removing the
* current unsolicited subscription.
*
* \param endpoint The endpoint
* \param mailbox The mailbox
*
* \retval 1 if a solicited subscription is allowed for the endpoint/mailbox
* 0 otherwise
*/
static int allow_and_or_replace_unsolicited(struct ast_sip_endpoint *endpoint, const char *mailbox)
{
struct mwi_subscription *mwi_sub;
struct mwi_stasis_subscription *mwi_stasis;
if (!has_mwi_subscription(unsolicited_mwi, endpoint, mailbox, &mwi_sub, &mwi_stasis)) {
/* If no unsolicited subscription then allow the solicited one */
return 1;
}
if (!endpoint->subscription.mwi.subscribe_replaces_unsolicited) {
/* Has unsolicited subscription and can't replace, so disallow */
ao2_ref(mwi_stasis, -1);
ao2_ref(mwi_sub, -1);
return 0;
}
/*
* The unsolicited subscription exists, and it is allowed to be replaced.
* So, first remove the unsolicited stasis subscription, and if aggregation
* is not enabled then also remove the mwi_subscription object as well.
*/
ast_debug(1, "Unsolicited subscription being replaced by solicited for "
"endpoint '%s' mailbox '%s'\n", ast_sorcery_object_get_id(endpoint), mailbox);
unsubscribe_stasis(mwi_stasis, NULL, 0);
ao2_unlink(mwi_sub->stasis_subs, mwi_stasis);
if (!endpoint->subscription.mwi.aggregate) {
ao2_unlink(unsolicited_mwi, mwi_sub);
}
ao2_ref(mwi_stasis, -1);
ao2_ref(mwi_sub, -1);
/* This solicited subscription is replacing an unsolicited one, so allow */
return 1;
}
static int send_notify(void *obj, void *arg, int flags);
/*!
* \internal
* \brief Determine if an unsolicited MWI subscription is allowed
*
* \param endpoint The endpoint
* \param mailbox The mailbox
*
* \retval 1 if an unsolicited subscription is allowed for the endpoint/mailbox
* 0 otherwise
*/
static int is_unsolicited_allowed(struct ast_sip_endpoint *endpoint, const char *mailbox)
{
struct mwi_subscription *mwi_sub;
struct mwi_stasis_subscription *mwi_stasis;
if (ast_strlen_zero(mailbox)) {
return 0;
}
/*
* First check if an unsolicited subscription exists. If it does then we don't
* want to add another one.
*/
if (has_mwi_subscription(unsolicited_mwi, endpoint, mailbox, &mwi_sub, &mwi_stasis)) {
ao2_ref(mwi_stasis, -1);
ao2_ref(mwi_sub, -1);
return 0;
}
/*
* If there is no unsolicited subscription, next check to see if a solicited
* subscription exists for the endpoint/mailbox. If not, then allow.
*/
if (!has_mwi_subscription(solicited_mwi, endpoint, mailbox, &mwi_sub, &mwi_stasis)) {
return 1;
}
/*
* If however, a solicited subscription does exist then we'll need to see if that
* subscription is allowed to replace the unsolicited one. If is allowed to replace
* then disallow the unsolicited one.
*/
if (endpoint->subscription.mwi.subscribe_replaces_unsolicited) {
ao2_ref(mwi_stasis, -1);
ao2_ref(mwi_sub, -1);
return 0;
}
/* Otherwise, shutdown the solicited subscription and allow the unsolicited */
mwi_sub->terminate = 1;
send_notify(mwi_sub, NULL, 0);
ao2_ref(mwi_stasis, -1);
ao2_ref(mwi_sub, -1);
return 1;
}
/*!
......@@ -796,19 +940,23 @@ static int mwi_validate_for_aor(void *obj, void *arg, int flags)
return 0;
}
/* A reload could be taking place so lock while checking if allowed */
ao2_lock(unsolicited_mwi);
mailboxes = ast_strdupa(aor->mailboxes);
while ((mailbox = ast_strip(strsep(&mailboxes, ",")))) {
if (ast_strlen_zero(mailbox)) {
continue;
}
if (endpoint_receives_unsolicited_mwi_for_mailbox(endpoint, mailbox)) {
if (!allow_and_or_replace_unsolicited(endpoint, mailbox)) {
ast_debug(1, "Endpoint '%s' already configured for unsolicited MWI for mailbox '%s'. "
"Denying MWI subscription to %s\n", ast_sorcery_object_get_id(endpoint), mailbox,
ast_sorcery_object_get_id(aor));
ao2_unlock(unsolicited_mwi);
return -1;
}
}
ao2_unlock(unsolicited_mwi);
return 0;
}
......@@ -954,6 +1102,7 @@ static int mwi_subscription_established(struct ast_sip_subscription *sip_sub)
ast_sip_subscription_remove_datastore(sip_sub, MWI_DATASTORE);
}
ao2_link(solicited_mwi, sub);
ao2_cleanup(sub);
ao2_cleanup(endpoint);
return 0;
......@@ -1090,12 +1239,25 @@ static void mwi_stasis_cb(void *userdata, struct stasis_subscription *sub,
}
}
/*! \note Called with the unsolicited_mwi container lock held. */
static int create_mwi_subscriptions_for_endpoint(void *obj, void *arg, int flags)
/*!
* \internal
* \brief Create unsolicited MWI subscriptions for an endpoint
*
* \note Call with the unsolicited_mwi container lock held.
*
* \param endpoint An endpoint object
* \param recreate Whether or not unsolicited subscriptions are potentially being recreated
* \param send_now Whether or not to send a notify once the subscription is created
*
* \retval 0
*/
static int create_unsolicited_mwi_subscriptions(struct ast_sip_endpoint *endpoint,
int recreate, int send_now)
{
RAII_VAR(struct mwi_subscription *, aggregate_sub, NULL, ao2_cleanup);
struct ast_sip_endpoint *endpoint = obj;
char *mailboxes, *mailbox;
char *mailboxes;
char *mailbox;
int sub_added = 0;
if (ast_strlen_zero(endpoint->subscription.mwi.mailboxes)) {
return 0;
......@@ -1104,45 +1266,83 @@ static int create_mwi_subscriptions_for_endpoint(void *obj, void *arg, int flags
if (endpoint->subscription.mwi.aggregate) {
const char *endpoint_id = ast_sorcery_object_get_id(endpoint);
/* Check if subscription exists */
/* Check if aggregate subscription exists */
aggregate_sub = ao2_find(unsolicited_mwi, endpoint_id, OBJ_SEARCH_KEY | OBJ_NOLOCK);
if (aggregate_sub) {
/*
* If enabled there should only ever exist a single aggregate subscription object
* for an endpoint. So if it exists just return unless subscriptions are potentially
* being added back in. If that's the case then continue.
*/
if (aggregate_sub && !recreate) {
return 0;
}
aggregate_sub = mwi_subscription_alloc(endpoint, 0, NULL);
if (!aggregate_sub) {
return 0;
aggregate_sub = mwi_subscription_alloc(endpoint, 0, NULL);
if (!aggregate_sub) {
return 0; /* No MWI aggregation for you */
}
}
}
/* Lock solicited so we don't potentially add to both containers */
ao2_lock(solicited_mwi);
mailboxes = ast_strdupa(endpoint->subscription.mwi.mailboxes);
while ((mailbox = ast_strip(strsep(&mailboxes, ",")))) {
struct mwi_subscription *sub;
struct mwi_stasis_subscription *mwi_stasis_sub;
/* check if subscription exists */
if (ast_strlen_zero(mailbox) ||
(!aggregate_sub && endpoint_receives_unsolicited_mwi_for_mailbox(endpoint, mailbox))) {
if (!is_unsolicited_allowed(endpoint, mailbox)) {
continue;
}
sub = aggregate_sub ?: mwi_subscription_alloc(endpoint, 0, NULL);
if (!sub) {
continue;
}
mwi_stasis_sub = mwi_stasis_subscription_alloc(mailbox, sub);
if (mwi_stasis_sub) {
ao2_link(sub->stasis_subs, mwi_stasis_sub);
ao2_ref(mwi_stasis_sub, -1);
}
if (!aggregate_sub && sub) {
if (!aggregate_sub) {
ao2_link_flags(unsolicited_mwi, sub, OBJ_NOLOCK);
if (send_now) {
send_notify(sub, NULL, 0);
}
ao2_ref(sub, -1);
}
if (aggregate_sub && !sub_added) {
/* If aggregation track if at least one subscription has been added */
sub_added = 1;
}
}
if (aggregate_sub) {
ao2_link_flags(unsolicited_mwi, aggregate_sub, OBJ_NOLOCK);
if (ao2_container_count(aggregate_sub->stasis_subs)) {
ao2_link_flags(unsolicited_mwi, aggregate_sub, OBJ_NOLOCK);
if (send_now && sub_added) {
send_notify(aggregate_sub, NULL, 0);
}
} else {
/* No stasis subscriptions then no MWI data to aggregate */
ao2_ref(aggregate_sub, -1);
}
}
ao2_unlock(solicited_mwi);
return 0;
}
static int create_mwi_subscriptions_for_endpoint(void *obj, void *arg, int flags)
{
return create_unsolicited_mwi_subscriptions(obj, 0, 0);
}
static int unsubscribe(void *obj, void *arg, int flags)
{
struct mwi_subscription *mwi_sub = obj;
......@@ -1347,11 +1547,20 @@ static int load_module(void)
ast_log(AST_LOG_WARNING, "Failed to create MWI serializer pool. The default SIP pool will be used for MWI\n");
}
solicited_mwi = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, MWI_BUCKETS,
mwi_sub_hash, NULL, mwi_sub_cmp);
if (!solicited_mwi) {
mwi_serializer_pool_shutdown();
ast_sip_unregister_subscription_handler(&mwi_handler);
return AST_MODULE_LOAD_DECLINE;
}
unsolicited_mwi = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX, 0, MWI_BUCKETS,
mwi_sub_hash, NULL, mwi_sub_cmp);
if (!unsolicited_mwi) {
mwi_serializer_pool_shutdown();
ast_sip_unregister_subscription_handler(&mwi_handler);
ao2_ref(solicited_mwi, -1);
return AST_MODULE_LOAD_DECLINE;
}
......@@ -1384,6 +1593,8 @@ static int unload_module(void)
ao2_ref(unsolicited_mwi, -1);
unsolicited_mwi = NULL;
ao2_cleanup(solicited_mwi);
mwi_serializer_pool_shutdown();
ast_sip_unregister_subscription_handler(&mwi_handler);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment