]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
res_pjsip_registrar: Remove unavailable contacts if exceeds max_contacts
authorJoseph Nadiv <ynadiv@corpit.xyz>
Wed, 21 Jul 2021 21:36:03 +0000 (17:36 -0400)
committerFriendly Automation <jenkins2@gerrit.asterisk.org>
Fri, 24 Sep 2021 14:48:47 +0000 (09:48 -0500)
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_existing is set to
yes, we will prioritize the removal of unavailable contacts before
those that are expiring soonest.

ASTERISK-29525

Change-Id: Ia2711b08f2b4d1177411b1be23e970d7fdff5784

configs/samples/pjsip.conf.sample
contrib/ast-db-manage/config/versions/f56d79a9f337_pjsip_create_remove_unavailable.py [new file with mode: 0644]
doc/CHANGES-staging/res_pjsip_registrar.txt [new file with mode: 0644]
include/asterisk/res_pjsip.h
res/res_pjsip.c
res/res_pjsip/location.c
res/res_pjsip_registrar.c

index 874fcc390cdb7237851405bb6403deb0aa0fa95f..80888c32ab8251f6c01275021018e881ac4431ad 100644 (file)
                         ; 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 (file)
index 0000000..fc82e7a
--- /dev/null
@@ -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 (file)
index 0000000..a80f69f
--- /dev/null
@@ -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.
index 351ce09edaadbab5c13eaed7f527ad071e4cf0c6..8c5b775bf5db7f2f15523bad6c14095b15951b9e 100644 (file)
@@ -389,6 +389,8 @@ struct ast_sip_aor {
        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;
 };
 
 /*!
index 84c25594b1b78c33f5ac331386ed9e0720a4bf89..a375fe083b34112707a5e883f6545c444d9223af 100644 (file)
                                                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.
                                                </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>
                                <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>
                                <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>
index cef5ad78ff56607c1190aabc0498f3c1a8e40272..bae8a2d7255c8362b064415678e2d43df1fea92e 100644 (file)
@@ -1410,6 +1410,7 @@ int ast_sip_initialize_sorcery_location(void)
        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);
index 6fe40588fdc322d583b1eaacd38a21e530c5d3e3..605c0fa2f520e5e82c7dbf7451d6a45e4e106534 100644 (file)
@@ -204,6 +204,7 @@ static int registrar_validate_contacts(const pjsip_rx_data *rdata, pj_pool_t *po
 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 @@ static int registrar_contact_delete(enum contact_delete_type type, pjsip_transpo
                        case CONTACT_DELETE_EXISTING:
                                reason = "remove existing";
                                break;
+                       case CONTACT_DELETE_UNAVAILABLE:
+                               reason = "remove unavailable";
+                               break;
                        case CONTACT_DELETE_EXPIRE:
                                reason = "expiration";
                                break;
@@ -483,7 +487,48 @@ static int vec_contact_cmp(struct ast_sip_contact *left, struct ast_sip_contact
        struct ast_sip_contact *right_contact = right;
 
        /* Sort from soonest to expire to last to expire */
-       return ast_tvcmp(left_contact->expiration_time, right_contact->expiration_time);
+       int time_sorted = ast_tvcmp(left_contact->expiration_time, right_contact->expiration_time);
+
+       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 = 0;
+       int left_unreachable;
+       int right_unreachable;
+
+       if (aor) {
+               remove_unavailable = aor->remove_unavailable;
+               ao2_ref(aor, -1);
+       }
+
+       if (!remove_unavailable) {
+               return time_sorted;
+       }
+
+       /* Get contact status if available */
+       left_status = ast_sip_get_contact_status(left_contact);
+       if (!left_status) {
+               return time_sorted;
+       }
+
+       right_status = ast_sip_get_contact_status(right_contact);
+       if (!right_status) {
+               ao2_ref(left_status, -1);
+               return time_sorted;
+       }
+
+       left_unreachable = (left_status->status == UNAVAILABLE);
+       right_unreachable = (right_status->status == UNAVAILABLE);
+       ao2_ref(left_status, -1);
+       ao2_ref(right_status, -1);
+       if (left_unreachable != right_unreachable) {
+               /* Set unavailable contact to top of vector */
+               if (left_unreachable) return -1;
+               if (right_unreachable) return 1;
+       }
+
+       /* Either both available or both unavailable */
+       return time_sorted;
 }
 
 static int vec_contact_add(void *obj, void *arg, int flags)
@@ -512,7 +557,7 @@ static int vec_contact_add(void *obj, void *arg, int flags)
 
 /*!
  * \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 +566,7 @@ static int vec_contact_add(void *obj, void *arg, int flags)
  * \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 +590,17 @@ static void remove_excess_contacts(struct ao2_container *contacts, struct ao2_co
        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);
        }
@@ -574,6 +623,29 @@ static int registrar_add_non_permanent(void *obj, void *arg, int flags)
        return 0;
 }
 
+/*! \brief Internal callback function which adds any contact which is unreachable */
+static int registrar_add_unreachable(void *obj, void *arg, int flags)
+{
+       struct ast_sip_contact *contact = obj;
+       struct ao2_container *container = arg;
+       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);
+
+       if (unreachable) {
+               ao2_link(container, contact);
+       }
+
+       return 0;
+}
+
 struct aor_core_response {
        /*! Tx data to use for statefull response.  NULL for stateless response. */
        pjsip_tx_data *tdata;
@@ -596,6 +668,7 @@ static void register_aor_core(pjsip_rx_data *rdata,
        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,6 +739,33 @@ static void register_aor_core(pjsip_rx_data *rdata,
                /* Total contacts after this registration */
                contact_count = ao2_container_count(contacts) - permanent + added - deleted;
        }
+
+       if (contact_count > aor->max_contacts && aor->remove_unavailable) {
+               /* Get unavailable contact total */
+               int unavail_count = 0;
+
+               unavail_contacts = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0,
+                       NULL, ast_sorcery_object_id_compare);
+               if (!unavail_contacts) {
+                       response->code = 500;
+                       pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), details.pool);
+                       return;
+               }
+               ao2_callback(contacts, OBJ_NODATA, registrar_add_unreachable, unavail_contacts);
+               if (unavail_contacts) {
+                       unavail_count = ao2_container_count(unavail_contacts);
+               }
+
+               /* Check to see if removing unavailable contacts will help */
+               if (contact_count - unavail_count <= aor->max_contacts) {
+                       /* Remove any unavailable contacts */
+                       remove_excess_contacts(unavail_contacts, contacts, contact_count - aor->max_contacts, aor->remove_existing);
+                       ao2_cleanup(unavail_contacts);
+                       /* We're only here if !aor->remove_existing so this count is correct */
+                       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");
@@ -867,8 +967,9 @@ static void register_aor_core(pjsip_rx_data *rdata,
                /* 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);
        }