[Asterisk-code-review] res_pjsip_registrar: Remove unavailable contacts if exceeds max_contacts (asterisk[18])

Joe asteriskteam at digium.com
Thu Aug 5 08:39:49 CDT 2021


Joe has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/16264 )


Change subject: res_pjsip_registrar: Remove unavailable contacts if exceeds max_contacts
......................................................................

res_pjsip_registrar: Remove unavailable contacts if exceeds max_contacts

The behavior of max_contacts and remove_existing are connected.  If
remove_existing is enabled, the soonest expiring contacts are removed.
This may occur when there is an unavailable contact.  Similarly,
when remove_existing is not enabled, registrations from good
endpoints are rejected in favor of retaining unavailable contacts.

This commit adds a new AOR option remove_unavailable, and the effect
of this setting will depend on remove_existing.  If remove_existing
is set to no, we will still remove unavailable contacts when they
exceed max_contacts, if there are any. If remove_exising is set to
yes, we will prioritize the removal of unavailable contacts before
those that are expiring soonest.

ASTERISK-29525

Change-Id: Ia2711b08f2b4d1177411b1be23e970d7fdff5784
---
M configs/samples/pjsip.conf.sample
A contrib/ast-db-manage/config/versions/f56d79a9f337_pjsip_create_remove_unavailable.py
A doc/CHANGES-staging/res_pjsip_registrar.txt
M include/asterisk/res_pjsip.h
M res/res_pjsip.c
M res/res_pjsip/location.c
M res/res_pjsip_registrar.c
7 files changed, 180 insertions(+), 18 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/64/16264/1

diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index 874fcc3..a70d580 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -1109,6 +1109,16 @@
                         ; contact created by rewrite_contact that the device is
                         ; refreshing.
                         ; (default: "no")
+;remove_unavailable=no  ; If remove_existing is disabled, will allow a registration
+			; to succeed by removing only unavailable contacts when
+			; max_contacts is exceeded. This will reject a registration
+			; that exceeds max_contacts if no unavailable contacts are
+			; present to remove. If remove_existing is enabled, will
+			; prioritize removal of unavailable contacts before removing
+			; expiring soonest.  This tames the behavior of remove_existing
+			; to only remove an available contact if an unavailable one is
+			; not present.
+			; (default: "no")
 ;type=  ; Must be of type aor (default: "")
 ;qualify_frequency=0    ; Interval at which to qualify an AoR via OPTIONS requests.
                         ; (default: "0")
diff --git a/contrib/ast-db-manage/config/versions/f56d79a9f337_pjsip_create_remove_unavailable.py b/contrib/ast-db-manage/config/versions/f56d79a9f337_pjsip_create_remove_unavailable.py
new file mode 100644
index 0000000..fc82e7a
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/f56d79a9f337_pjsip_create_remove_unavailable.py
@@ -0,0 +1,30 @@
+"""pjsip create remove_unavailable
+
+Revision ID: f56d79a9f337
+Revises: c20d6e3992f4
+Create Date: 2021-07-28 02:09:11.082061
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'f56d79a9f337'
+down_revision = 'c20d6e3992f4'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+AST_BOOL_NAME = 'ast_bool_values'
+AST_BOOL_VALUES = [ '0', '1',
+                    'off', 'on',
+                    'false', 'true',
+                    'no', 'yes' ]
+
+def upgrade():
+    ast_bool_values = ENUM(*AST_BOOL_VALUES, name=AST_BOOL_NAME, create_type=False)
+    op.add_column('ps_aors', sa.Column('remove_unavailable', ast_bool_values))
+
+def downgrade():
+    op.drop_column('ps_aors', 'remove_unavailable')
+    pass
+
diff --git a/doc/CHANGES-staging/res_pjsip_registrar.txt b/doc/CHANGES-staging/res_pjsip_registrar.txt
new file mode 100644
index 0000000..a80f69f
--- /dev/null
+++ b/doc/CHANGES-staging/res_pjsip_registrar.txt
@@ -0,0 +1,7 @@
+Subject: res_pjsip_registrar
+
+Adds new PJSIP AOR option remove_unavailable to either
+remove unavailable contacts when a REGISTER exceeds
+max_contacts when remove_existing is disabled, or
+prioritize unavailable contacts over other existing
+contacts when remove_existing is enabled.
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 351ce09..e1d6b98 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -389,6 +389,8 @@
 	double qualify_timeout;
 	/*! Voicemail extension to set in Message-Account */
 	char *voicemail_extension;
+	/*! Whether to remove unavailable contacts over max_contacts at all or first if remove_existing is enabled */
+	unsigned int remove_unavailable;
 };
 
 /*!
@@ -1021,6 +1023,9 @@
 
 	/*! \brief Return only reachable or unknown contacts */
 	AST_SIP_CONTACT_FILTER_REACHABLE = (1 << 0),
+
+	/*! \brief Return only unreachable contacts */
+	AST_SIP_CONTACT_FILTER_UNREACHABLE = (1 << 4),
 };
 
 /*!
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 84c2559..a375fe0 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -1997,8 +1997,9 @@
 						TLS.  Unfortunately, refreshing a registration may register a
 						different 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>remove_existing</replaceable> and
+						<replaceable>remove_unavailable</replaceable> options can help by
+						removing either the soonest to expire or unavailable contact(s) over
 						<replaceable>max_contacts</replaceable> which is likely the
 						old <replaceable>rewrite_contact</replaceable> contact source
 						address being refreshed.
@@ -2041,6 +2042,26 @@
 						</para></note>
 					</description>
 				</configOption>
+				<configOption name="remove_unavailable" default="no">
+					<synopsis>Determines whether new contacts should replace unavailable ones.</synopsis>
+					<description><para>
+						The effect of this setting depends on the setting of
+						<replaceable>remove_existing</replaceable>.</para>
+						<para>If <replaceable>remove_existing</replaceable> is set to
+						<literal>no</literal> (default), setting remove_unavailable to
+						<literal>yes</literal> will remove only unavailable contacts that exceed
+						<replaceable>max_contacts</replaceable>	to allow an incoming
+						REGISTER to complete sucessfully.</para>
+						<para>If <replaceable>remove_existing</replaceable> is set to
+						<literal>yes</literal>, setting remove_unavailable to
+						<literal>yes</literal> will prioritize unavailable contacts for removal
+						instead of just removing the contact that expires the soonest.</para>
+						<note><para>See <replaceable>remove_existing</replaceable> and
+						<replaceable>max_contacts</replaceable> for further information about how
+						these 3 settings interact.
+						</para></note>
+					</description>
+				</configOption>
 				<configOption name="type">
 					<synopsis>Must be of type 'aor'.</synopsis>
 				</configOption>
@@ -2444,6 +2465,9 @@
 				<parameter name="RemoveExisting">
 					<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='aor']/configOption[@name='remove_existing']/synopsis/node())"/></para>
 				</parameter>
+				<parameter name="RemoveUnavailable">
+					<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='aor']/configOption[@name='remove_unavailable']/synopsis/node())"/></para>
+				</parameter>
 				<parameter name="Mailboxes">
 					<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='aor']/configOption[@name='mailboxes']/synopsis/node())"/></para>
 				</parameter>
@@ -2904,6 +2928,9 @@
 				<parameter name="RemoveExisting">
 					<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='aor']/configOption[@name='remove_existing']/synopsis/node())"/></para>
 				</parameter>
+				<parameter name="RemoveUnavailable">
+					<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='aor']/configOption[@name='remove_unavailable']/synopsis/node())"/></para>
+				</parameter>
 				<parameter name="Mailboxes">
 					<para><xi:include xpointer="xpointer(/docs/configInfo[@name='res_pjsip']/configFile[@name='pjsip.conf']/configObject[@name='aor']/configOption[@name='mailboxes']/synopsis/node())"/></para>
 				</parameter>
diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c
index cef5ad7..1d26b94 100644
--- a/res/res_pjsip/location.c
+++ b/res/res_pjsip/location.c
@@ -191,6 +191,24 @@
 	return unreachable ? CMP_MATCH : 0;
 }
 
+/*! \brief Internal callback function which contains only contact which is unreachable */
+static int contact_only_unreachable(void *obj, void *arg, int flags)
+{
+	struct ast_sip_contact *contact = obj;
+	struct ast_sip_contact_status *status;
+	int unreachable;
+
+	status = ast_sip_get_contact_status(contact);
+	if (!status) {
+		return 0;
+	}
+
+	unreachable = (status->status == UNAVAILABLE);
+	ao2_ref(status, -1);
+
+	return unreachable ? 0 : CMP_MATCH;
+}
+
 struct ast_sip_contact *ast_sip_location_retrieve_first_aor_contact(const struct ast_sip_aor *aor)
 {
 	return ast_sip_location_retrieve_first_aor_contact_filtered(aor, AST_SIP_CONTACT_FILTER_DEFAULT);
@@ -237,6 +255,10 @@
 		ao2_callback(aor->permanent_contacts, OBJ_NODATA, contact_link_static, contacts);
 	}
 
+	if (flags & AST_SIP_CONTACT_FILTER_UNREACHABLE) {
+		ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, contact_only_unreachable, NULL);
+	}
+
 	if (flags & AST_SIP_CONTACT_FILTER_REACHABLE) {
 		ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK, contact_remove_unreachable, NULL);
 	}
@@ -1410,6 +1432,7 @@
 	ast_sorcery_object_field_register(sorcery, "aor", "authenticate_qualify", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, authenticate_qualify));
 	ast_sorcery_object_field_register(sorcery, "aor", "max_contacts", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, max_contacts));
 	ast_sorcery_object_field_register(sorcery, "aor", "remove_existing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, remove_existing));
+	ast_sorcery_object_field_register(sorcery, "aor", "remove_unavailable", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, remove_unavailable));
 	ast_sorcery_object_field_register_custom(sorcery, "aor", "contact", "", permanent_uri_handler, contacts_to_str, contacts_to_var_list, 0, 0);
 	ast_sorcery_object_field_register(sorcery, "aor", "mailboxes", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_aor, mailboxes));
 	ast_sorcery_object_field_register_custom(sorcery, "aor", "voicemail_extension", "", voicemail_extension_handler, voicemail_extension_to_str, NULL, 0, 0);
diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c
index 6fe4058..f894a1f 100644
--- a/res/res_pjsip_registrar.c
+++ b/res/res_pjsip_registrar.c
@@ -204,6 +204,7 @@
 enum contact_delete_type {
 	CONTACT_DELETE_ERROR,
 	CONTACT_DELETE_EXISTING,
+	CONTACT_DELETE_UNAVAILABLE,
 	CONTACT_DELETE_EXPIRE,
 	CONTACT_DELETE_REQUEST,
 	CONTACT_DELETE_SHUTDOWN,
@@ -448,6 +449,9 @@
 			case CONTACT_DELETE_EXISTING:
 				reason = "remove existing";
 				break;
+			case CONTACT_DELETE_UNAVAILABLE:
+				reason = "remove unavailable";
+				break;
 			case CONTACT_DELETE_EXPIRE:
 				reason = "expiration";
 				break;
@@ -481,8 +485,43 @@
 {
 	struct ast_sip_contact *left_contact = left;
 	struct ast_sip_contact *right_contact = right;
+	struct ast_sip_aor *aor = ast_sip_location_retrieve_aor(left_contact->aor);
+	struct ast_sip_contact_status *left_status;
+	struct ast_sip_contact_status *right_status;
+	int remove_unavailable = NULL;
+	int left_unreachable;
+	int right_unreachable;
 
-	/* Sort from soonest to expire to last to expire */
+	if (aor) {
+		remove_unavailable = aor->remove_unavailable;
+		ao2_ref(aor, -1);
+	}
+
+	if (!remove_unavailable) {
+		/* Sort from soonest to expire to last to expire */
+		return ast_tvcmp(left_contact->expiration_time, right_contact->expiration_time);
+	}
+
+	/* If contact is unreachable move to top of list */
+	left_status = ast_sip_get_contact_status(left_contact);
+	right_status = ast_sip_get_contact_status(right_contact);
+	if (left_status && right_status) {
+		left_unreachable = (left_status->status == UNAVAILABLE);
+		right_unreachable = (right_status->status == UNAVAILABLE);
+		ao2_cleanup(left_status);
+		ao2_cleanup(right_status);
+		if (left_unreachable == right_unreachable) {
+			/* Either both available or both unavailable */
+			/* Sort from soonest to expire to last to expire */
+			return ast_tvcmp(left_contact->expiration_time, right_contact->expiration_time);
+		}
+		if (left_unreachable) return -1;
+		if (right_unreachable) return 1;
+	}
+
+	/* No status from one or both contacts, return by expiration */
+	ao2_cleanup(left_status);
+	ao2_cleanup(right_status);
 	return ast_tvcmp(left_contact->expiration_time, right_contact->expiration_time);
 }
 
@@ -512,7 +551,7 @@
 
 /*!
  * \internal
- * \brief Remove excess existing contacts that expire the soonest.
+ * \brief Remove excess existing contacts that are unavailable or expire soonest.
  * \since 13.18.0
  *
  * \param contacts Container of unmodified contacts that could remove.
@@ -521,7 +560,7 @@
  * \return Nothing
  */
 static void remove_excess_contacts(struct ao2_container *contacts, struct ao2_container *response_contacts,
-	unsigned int to_remove)
+	unsigned int to_remove, unsigned int remove_existing)
 {
 	struct excess_contact_vector contact_vec;
 
@@ -545,13 +584,17 @@
 	ast_assert(AST_VECTOR_SIZE(&contact_vec) == to_remove);
 	to_remove = AST_VECTOR_SIZE(&contact_vec);
 
-	/* Remove the excess contacts that expire the soonest */
+	/* Remove the excess contacts that are unavailable or expire the soonest */
 	while (to_remove--) {
 		struct ast_sip_contact *contact;
 
 		contact = AST_VECTOR_GET(&contact_vec, to_remove);
 
-		registrar_contact_delete(CONTACT_DELETE_EXISTING, NULL, contact, contact->aor);
+		if (!remove_existing) {
+			registrar_contact_delete(CONTACT_DELETE_UNAVAILABLE, NULL, contact, contact->aor);
+		} else {
+			registrar_contact_delete(CONTACT_DELETE_EXISTING, NULL, contact, contact->aor);
+		}
 
 		ao2_unlink(response_contacts, contact);
 	}
@@ -596,6 +639,7 @@
 	int permanent = 0;
 	int contact_count;
 	struct ao2_container *existing_contacts = NULL;
+	struct ao2_container *unavail_contacts = NULL;
 	pjsip_contact_hdr *contact_hdr = (pjsip_contact_hdr *)&rdata->msg_info.msg->hdr;
 	struct registrar_contact_details details = { 0, };
 	pjsip_tx_data *tdata;
@@ -666,16 +710,31 @@
 		/* Total contacts after this registration */
 		contact_count = ao2_container_count(contacts) - permanent + added - deleted;
 	}
+
 	if (contact_count > aor->max_contacts) {
-		/* Enforce the maximum number of contacts */
-		ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_exceeds_maximum_configured_contacts");
-		ast_log(LOG_WARNING, "Registration attempt from endpoint '%s' (%s:%d) to AOR '%s' will exceed max contacts of %u\n",
-				ast_sorcery_object_get_id(endpoint), rdata->pkt_info.src_name, rdata->pkt_info.src_port,
-				aor_name, aor->max_contacts);
-		response->code = 403;
-		pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
-		ao2_cleanup(existing_contacts);
-		return;
+		/* Get unreachable contact total */
+		int unavail_count = 0;
+
+		unavail_contacts = ast_sip_location_retrieve_aor_contacts_filtered(aor, AST_SIP_CONTACT_FILTER_UNREACHABLE);
+		unavail_count = ao2_container_count(unavail_contacts);
+
+		/* Check to see if removing those will not help */
+		if (contact_count - unavail_count > aor->max_contacts || !aor->remove_unavailable) {
+			/* Enforce the maximum number of contacts */
+			ast_sip_report_failed_acl(endpoint, rdata, "registrar_attempt_exceeds_maximum_configured_contacts");
+			ast_log(LOG_WARNING, "Registration attempt from endpoint '%s' (%s:%d) to AOR '%s' will exceed max contacts of %u\n",
+					ast_sorcery_object_get_id(endpoint), rdata->pkt_info.src_name, rdata->pkt_info.src_port,
+					aor_name, aor->max_contacts);
+			response->code = 403;
+			pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
+			ao2_cleanup(unavail_contacts);
+			ao2_cleanup(existing_contacts);
+			return;
+		}
+
+		/* Remove any unreachable contacts */
+		remove_excess_contacts(unavail_contacts, contacts, contact_count - aor->max_contacts, aor->remove_existing);
+		ao2_cleanup(unavail_contacts);
 	}
 
 	user_agent_hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &USER_AGENT, NULL);
@@ -867,8 +926,9 @@
 		/* Total contacts after this registration */
 		contact_count = ao2_container_count(existing_contacts) + updated + added;
 		if (contact_count > aor->max_contacts) {
-			/* Remove excess existing contacts that expire the soonest */
-			remove_excess_contacts(existing_contacts, contacts, contact_count - aor->max_contacts);
+			/* Remove excess existing contacts that are unavailable or expire soonest */
+			remove_excess_contacts(existing_contacts, contacts, contact_count - aor->max_contacts,
+				aor->remove_existing);
 		}
 		ao2_ref(existing_contacts, -1);
 	}

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/16264
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: 18
Gerrit-Change-Id: Ia2711b08f2b4d1177411b1be23e970d7fdff5784
Gerrit-Change-Number: 16264
Gerrit-PatchSet: 1
Gerrit-Owner: Joe <ynadiv at corpit.xyz>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20210805/7ddb3045/attachment-0001.html>


More information about the asterisk-code-review mailing list