[Asterisk-code-review] res pjsip registrar.c: Update remove existing AOR contact ha... (asterisk[13])
Richard Mudgett
asteriskteam at digium.com
Thu Sep 28 16:27:03 CDT 2017
Richard Mudgett has uploaded this change for review. ( https://gerrit.asterisk.org/6630
Change subject: res_pjsip_registrar.c: Update remove_existing AOR contact handling.
......................................................................
res_pjsip_registrar.c: Update remove_existing AOR contact handling.
When "rewrite_contact" is enabled, the "max_contacts" count can block
re-registrations because the source port from the endpoint can be random.
When the re-registration is blocked, the endpoint may give up
re-registering and require manual intervention.
* The "remove_existing" option now removes the soonest to expire contacts a
registration does not affect that are over the "max_contacts" count. The
behaviour change is beneficial when "rewrite_contact" is enabled and
"max_contacts" is greater than one. The removed contact is likely the old
contact created by "rewrite_contact" that the device is refreshing.
ASTERISK-27192
Change-Id: I64c107a10b70db1697d17136051ae6bf22b5314b
---
M CHANGES
M configs/samples/pjsip.conf.sample
M res/res_pjsip.c
M res/res_pjsip_registrar.c
4 files changed, 164 insertions(+), 20 deletions(-)
git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/30/6630/1
diff --git a/CHANGES b/CHANGES
index 9b6c2b1..c890057 100644
--- a/CHANGES
+++ b/CHANGES
@@ -34,6 +34,12 @@
unsolicited MWI NOTIFY requests and make them available to other modules via
the stasis message bus.
+ * The "remove_existing" option now removes the soonest to expire contacts a
+ registration does not affect that are over the "max_contacts" count. The
+ behaviour change is beneficial when "rewrite_contact" is enabled and
+ "max_contacts" is greater than one. The removed contact is likely the
+ old contact created by "rewrite_contact" that the device is refreshing.
+
res_musiconhold
------------------
* By default, when res_musiconhold reloads or unloads, it sends a HUP signal
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index c92c98c..a40f22d 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -894,7 +894,8 @@
;max_contacts=0 ; Maximum number of contacts that can bind to an AoR (default:
; "0")
;minimum_expiration=60 ; Minimum keep alive time for an AoR (default: "60")
-;remove_existing=no ; Determines whether new contacts replace existing ones
+;remove_existing=no ; Determines whether new contacts remove the number of
+ ; existing ones over max_contacts which expire the soonest.
; (default: "no")
;type= ; Must be of type aor (default: "")
;qualify_frequency=0 ; Interval at which to qualify an AoR (default: "0")
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 3c8f730..088c873 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -1408,6 +1408,17 @@
It only limits contacts added through external interaction, such as
registration.
</para>
+ <note><para>The <replaceable>rewrite_contact</replaceable> option
+ registers random contact addresses to help with NAT and reusing
+ connection oriented transports such as TCP and TLS. Unfortunately,
+ refreshing a registration may create a different random contact
+ address and exceed <replaceable>max_contacts</replaceable>. The
+ <replaceable>remove_existing</replaceable> option can help by
+ removing the soonest to expire contact(s) over
+ <replaceable>max_contacts</replaceable> which is likely the
+ old random <replaceable>rewrite_contact</replaceable> contact
+ being refreshed.
+ </para></note>
<note><para>This should be set to <literal>1</literal> and
<replaceable>remove_existing</replaceable> set to <literal>yes</literal> if you
wish to stick with the older <literal>chan_sip</literal> behaviour.
@@ -1417,15 +1428,28 @@
<configOption name="minimum_expiration" default="60">
<synopsis>Minimum keep alive time for an AoR</synopsis>
<description><para>
- Minimum time to keep a peer with an explict expiration. Time in seconds.
+ Minimum time to keep a peer with an explicit expiration. Time in seconds.
</para></description>
</configOption>
<configOption name="remove_existing" default="no">
<synopsis>Determines whether new contacts replace existing ones.</synopsis>
<description><para>
- On receiving a new registration to the AoR should it remove
- the existing contact that was registered against it?
+ On receiving a new registration to the AoR should it remove enough
+ existing contacts not added or updated by the registration to
+ satisfy <replaceable>max_contacts</replaceable>? Any removed
+ contacts will expire the soonest.
</para>
+ <note><para>The <replaceable>rewrite_contact</replaceable> option
+ registers random contact addresses to help with NAT and reusing
+ connection oriented transports such as TCP and TLS. Unfortunately,
+ refreshing a registration may create a different random contact
+ address and exceed <replaceable>max_contacts</replaceable>. The
+ <replaceable>remove_existing</replaceable> option can help by
+ removing the soonest to expire contact(s) over
+ <replaceable>max_contacts</replaceable> which is likely the
+ old random <replaceable>rewrite_contact</replaceable> contact
+ being refreshed.
+ </para></note>
<note><para>This should be set to <literal>yes</literal> and
<replaceable>max_contacts</replaceable> set to <literal>1</literal> if you
wish to stick with the older <literal>chan_sip</literal> behaviour.
diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c
index 48b4835..92292d5 100644
--- a/res/res_pjsip_registrar.c
+++ b/res/res_pjsip_registrar.c
@@ -32,6 +32,7 @@
#include "asterisk/module.h"
#include "asterisk/paths.h"
#include "asterisk/test.h"
+#include "asterisk/heap.h"
#include "asterisk/taskprocessor.h"
#include "asterisk/manager.h"
#include "asterisk/named_locks.h"
@@ -129,7 +130,8 @@
/*! \brief Internal function which validates provided Contact headers to confirm that they are acceptable, and returns number of contacts */
static int registrar_validate_contacts(const pjsip_rx_data *rdata, struct ao2_container *contacts, struct ast_sip_aor *aor, int *added, int *updated, int *deleted)
{
- pjsip_contact_hdr *previous = NULL, *contact = (pjsip_contact_hdr *)&rdata->msg_info.msg->hdr;
+ pjsip_contact_hdr *previous = NULL;
+ pjsip_contact_hdr *contact = (pjsip_contact_hdr *)&rdata->msg_info.msg->hdr;
struct registrar_contact_details details = {
.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Contact Comparison", 256, 256),
};
@@ -140,15 +142,18 @@
while ((contact = (pjsip_contact_hdr *) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, contact->next))) {
int expiration = registrar_get_expiration(aor, contact, rdata);
- RAII_VAR(struct ast_sip_contact *, existing, NULL, ao2_cleanup);
+ struct ast_sip_contact *existing;
char contact_uri[pjsip_max_url_size];
if (contact->star) {
/* The expiration MUST be 0 when a '*' contact is used and there must be no other contact */
- if ((expiration != 0) || previous) {
+ if (expiration != 0 || previous) {
pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
return -1;
}
+ /* Count all contacts to delete */
+ *deleted = ao2_container_count(contacts);
+ previous = contact;
continue;
} else if (previous && previous->star) {
/* If there is a previous contact and it is a '*' this is a deal breaker */
@@ -177,14 +182,16 @@
}
/* Determine if this is an add, update, or delete for policy enforcement purposes */
- if (!(existing = ao2_callback(contacts, 0, registrar_find_contact, &details))) {
+ existing = ao2_callback(contacts, 0, registrar_find_contact, &details);
+ ao2_cleanup(existing);
+ if (!existing) {
if (expiration) {
- (*added)++;
+ ++*added;
}
} else if (expiration) {
- (*updated)++;
+ ++*updated;
} else {
- (*deleted)++;
+ ++*deleted;
}
}
@@ -219,7 +226,7 @@
contact->user_agent);
}
- return 0;
+ return CMP_MATCH;
}
/*! \brief Internal function which adds a contact to a response */
@@ -351,6 +358,84 @@
ast_named_lock_put(lock);
}
+static unsigned int heap_calc_height(unsigned int num_elements)
+{
+ unsigned int height = 0;
+
+ while (num_elements) {
+ num_elements >>= 1;
+ ++height;
+ }
+ return height;
+}
+
+static int heap_min_contact_expire_cmp(void *left, void *right)
+{
+ struct ast_sip_contact *left_contact = left;
+ struct ast_sip_contact *right_contact = right;
+
+ /* Swap left and right to get minimum expiration time remaining heap. */
+ return ast_tvcmp(right_contact->expiration_time, left_contact->expiration_time);
+}
+
+static int heap_push_contact(void *obj, void *arg, int flags)
+{
+ struct ast_sip_contact *contact = obj;
+ struct ast_heap *contact_heap = arg;
+
+ ast_heap_push(contact_heap, contact);
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Remove excess existing contacts that expire the soonest.
+ * \since 13.18.0
+ *
+ * \param contacts Container of unmodified contacts that could remove.
+ * \param to_remove Maximum number of contacts to remove.
+ *
+ * \return Nothing
+ */
+static void remove_excess_contacts(struct ao2_container *contacts, unsigned int to_remove)
+{
+ struct ast_heap *contact_heap;
+
+ ast_assert(0 < to_remove);
+
+ /* Create a minimum contact expiration time remaining heap */
+ contact_heap = ast_heap_create(heap_calc_height(ao2_container_count(contacts)),
+ heap_min_contact_expire_cmp, -1);
+ if (!contact_heap) {
+ return;
+ }
+ ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, heap_push_contact, contact_heap);
+
+ /* Remove the excess contacts that expire the soonest */
+ while (to_remove--) {
+ struct ast_sip_contact *contact;
+
+ contact = ast_heap_pop(contact_heap);
+ if (!contact) {
+ break;
+ }
+
+ ast_sip_location_delete_contact(contact);
+ ast_verb(3, "Removed contact '%s' from AOR '%s' due to remove_existing\n",
+ contact->uri, contact->aor);
+ ast_test_suite_event_notify("AOR_CONTACT_REMOVED",
+ "Contact: %s\r\n"
+ "AOR: %s\r\n"
+ "UserAgent: %s",
+ contact->uri,
+ contact->aor,
+ contact->user_agent);
+ }
+
+ ast_heap_destroy(contact_heap);
+}
+
static int register_aor_core(pjsip_rx_data *rdata,
struct ast_sip_endpoint *endpoint,
struct ast_sip_aor *aor,
@@ -359,7 +444,10 @@
{
static const pj_str_t USER_AGENT = { "User-Agent", 10 };
- int added = 0, updated = 0, deleted = 0;
+ int added = 0;
+ int updated = 0;
+ int deleted = 0;
+ int contact_count;
pjsip_contact_hdr *contact_hdr = NULL;
struct registrar_contact_details details = { 0, };
pjsip_tx_data *tdata;
@@ -396,7 +484,14 @@
return PJ_TRUE;
}
- if ((MAX(added - deleted, 0) + (!aor->remove_existing ? ao2_container_count(contacts) : 0)) > aor->max_contacts) {
+ if (aor->remove_existing) {
+ /* Cumulative number of contacts affected by this registration */
+ contact_count = MAX(updated + added - deleted, 0);
+ } else {
+ /* Total contacts after this registration */
+ contact_count = ao2_container_count(contacts) + added - deleted;
+ }
+ if (contact_count > aor->max_contacts) {
/* Enforce the maximum number of contacts */
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 403, NULL, NULL, NULL);
ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_exceeds_maximum_configured_contacts");
@@ -405,7 +500,9 @@
return PJ_TRUE;
}
- if (!(details.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Contact Comparison", 256, 256))) {
+ details.pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(),
+ "Contact Comparison", 256, 256);
+ if (!details.pool) {
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
return PJ_TRUE;
}
@@ -446,7 +543,8 @@
if (contact_hdr->star) {
/* A star means to unregister everything, so do so for the possible contacts */
- ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, registrar_delete_contact, (void *)aor_name);
+ ao2_callback(contacts, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE,
+ registrar_delete_contact, (void *)aor_name);
break;
}
@@ -459,7 +557,8 @@
details.uri = pjsip_uri_get_uri(contact_hdr->uri);
pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, details.uri, contact_uri, sizeof(contact_uri));
- if (!(contact = ao2_callback(contacts, OBJ_UNLINK, registrar_find_contact, &details))) {
+ contact = ao2_callback(contacts, OBJ_UNLINK, registrar_find_contact, &details);
+ if (!contact) {
int prune_on_boot = 0;
pj_str_t host_name;
@@ -600,15 +699,29 @@
pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
- /* If the AOR is configured to remove any existing contacts that have not been updated/added as a result of this REGISTER
- * do so
+ /*
+ * If the AOR is configured to remove any contacts over max_contacts
+ * that have not been updated/added/deleted as a result of this
+ * REGISTER do so.
+ *
+ * The contacts container currently holds the existing contacts that
+ * were not affected by this REGISTER.
*/
if (aor->remove_existing) {
- ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, registrar_delete_contact, NULL);
+ /* Total contacts after this registration */
+ contact_count = ao2_container_count(contacts) + updated + added;
+ if (contact_count > aor->max_contacts) {
+ /* Remove excess existing contacts that expire the soonest */
+ remove_excess_contacts(contacts, contact_count - aor->max_contacts);
+ }
}
/* Re-retrieve contacts. Caller will clean up the original container. */
contacts = ast_sip_location_retrieve_aor_contacts_nolock(aor);
+ if (!contacts) {
+ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 500, NULL, NULL, NULL);
+ return PJ_TRUE;
+ }
response_contact = ao2_callback(contacts, 0, NULL, NULL);
/* Send a response containing all of the contacts (including static) that are present on this AOR */
--
To view, visit https://gerrit.asterisk.org/6630
To unsubscribe, visit https://gerrit.asterisk.org/settings
Gerrit-Project: asterisk
Gerrit-Branch: 13
Gerrit-MessageType: newchange
Gerrit-Change-Id: I64c107a10b70db1697d17136051ae6bf22b5314b
Gerrit-Change-Number: 6630
Gerrit-PatchSet: 1
Gerrit-Owner: Richard Mudgett <rmudgett at digium.com>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20170928/f672ae85/attachment-0001.html>
More information about the asterisk-code-review
mailing list