diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index 874fcc390cdb7237851405bb6403deb0aa0fa95f..80888c32ab8251f6c01275021018e881ac4431ad 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 0000000000000000000000000000000000000000..fc82e7a954afa5a8d0976c836acd576f01495147 --- /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 0000000000000000000000000000000000000000..a80f69ff082335e51e38ef5490a7aba486297106 --- /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 351ce09edaadbab5c13eaed7f527ad071e4cf0c6..8c5b775bf5db7f2f15523bad6c14095b15951b9e 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -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; }; /*! diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 84c25594b1b78c33f5ac331386ed9e0720a4bf89..a375fe083b34112707a5e883f6545c444d9223af 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 cef5ad78ff56607c1190aabc0498f3c1a8e40272..bae8a2d7255c8362b064415678e2d43df1fea92e 100644 --- a/res/res_pjsip/location.c +++ b/res/res_pjsip/location.c @@ -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); diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c index 6fe40588fdc322d583b1eaacd38a21e530c5d3e3..605c0fa2f520e5e82c7dbf7451d6a45e4e106534 100644 --- a/res/res_pjsip_registrar.c +++ b/res/res_pjsip_registrar.c @@ -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); }