<p>Joshua Colp <strong>merged</strong> this change.</p><p><a href="https://gerrit.asterisk.org/8758">View Change</a></p><div style="white-space:pre-wrap">Approvals:
Corey Farrell: Looks good to me, but someone else must approve
Joshua Colp: Looks good to me, but someone else must approve; Approved for Submit
Kevin Harwell: Looks good to me, approved
</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">pjsip: Rewrite OPTIONS support with new eyes.<br><br>The OPTIONS support in PJSIP has organically grown, like many things in<br>Asterisk. It has been tweaked, changed, and adapted based on situations<br>run into. Unfortunately this has taken its toll. Configuration file<br>based objects have poor performance and even dynamic ones aren't that<br>great.<br><br>This change scraps the existing code and starts fresh with new eyes. It<br>leverages all of the APIs made available such as sorcery observers and<br>serializers to provide a better implementation.<br><br>1. The state of contacts, AORs, and endpoints relevant to the qualify<br>process is maintained. This state can be updated by external forces (such<br>as a device registering/unregistering) and also the reload process. This<br>state also includes the association between endpoints and AORs.<br><br>2. AORs are scheduled and not contacts. This reduces the amount of work<br>spent juggling scheduled items.<br><br>3. Manipulation of which AORs are being qualified and the endpoint states<br>all occur within a serializer to reduce the conflict that can occur with<br>multiple threads attempting to modify things.<br><br>4. Operations regarding an AOR use a serializer specific to that AOR.<br><br>5. AORs and endpoint state act as state compositors. They take input<br>from lower level objects (contacts feed AORs, AORs feed endpoint state)<br>and determine if a sufficient enough change has occurred to be fed further<br>up the chain.<br><br>6. Realtime is supported by using observers to know when a contact has<br>been registered. If state does not exist for the associated AOR then it<br>is retrieved and becomes active as appropriate.<br><br>The end result of all of this is best shown with a configuration file of<br>3000 endpoints each with an AOR that has a static contact. In the old<br>code it would take over a minute to load and use all 8 of my cores. This<br>new code takes 2-3 seconds and barely touches the CPU even while dealing<br>with all of the OPTIONS requests.<br><br>ASTERISK-26806<br><br>Change-Id: I6a5ebbfca9001dfe933eaeac4d3babd8d2e6f082<br>---<br>M funcs/func_pjsip_contact.c<br>M include/asterisk/res_pjsip.h<br>M res/res_pjsip.c<br>M res/res_pjsip/include/res_pjsip_private.h<br>M res/res_pjsip/location.c<br>M res/res_pjsip/pjsip_configuration.c<br>M res/res_pjsip/pjsip_options.c<br>7 files changed, 2,442 insertions(+), 1,431 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/funcs/func_pjsip_contact.c b/funcs/func_pjsip_contact.c<br>index fac4cb6..91f2f77 100644<br>--- a/funcs/func_pjsip_contact.c<br>+++ b/funcs/func_pjsip_contact.c<br>@@ -142,12 +142,12 @@<br> return -1;<br> }<br> <br>- contact_status = ast_sorcery_retrieve_by_id(pjsip_sorcery, CONTACT_STATUS, ast_sorcery_object_get_id(contact_obj));<br>+ contact_status = ast_sip_get_contact_status(contact_obj);<br> <br> if (!strcmp(args.field_name, "status")) {<br>- ast_str_set(buf, len, "%s", ast_sip_get_contact_status_label(contact_status->status));<br>+ ast_str_set(buf, len, "%s", ast_sip_get_contact_status_label(contact_status ? contact_status->status : UNKNOWN));<br> } else if (!strcmp(args.field_name, "rtt")) {<br>- if (contact_status->status == UNKNOWN) {<br>+ if (!contact_status || contact_status->status != AVAILABLE) {<br> ast_str_set(buf, len, "%s", "N/A");<br> } else {<br> ast_str_set(buf, len, "%" PRId64, contact_status->rtt);<br>diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h<br>index d2ae39b..028051a 100644<br>--- a/include/asterisk/res_pjsip.h<br>+++ b/include/asterisk/res_pjsip.h<br>@@ -283,8 +283,6 @@<br> int prune_on_boot;<br> };<br> <br>-#define CONTACT_STATUS "contact_status"<br>-<br> /*!<br> * \brief Status type for a contact.<br> */<br>@@ -307,23 +305,20 @@<br> * if available.<br> */<br> struct ast_sip_contact_status {<br>- SORCERY_OBJECT(details);<br> AST_DECLARE_STRING_FIELDS(<br> /*! The original contact's URI */<br> AST_STRING_FIELD(uri);<br> /*! The name of the aor this contact_status belongs to */<br> AST_STRING_FIELD(aor);<br> );<br>- /*! Current status for a contact (default - unavailable) */<br>- enum ast_sip_contact_status_type status;<br>- /*! The round trip start time set before sending a qualify request */<br>- struct timeval rtt_start;<br> /*! The round trip time in microseconds */<br> int64_t rtt;<br>+ /*! Current status for a contact (default - unavailable) */<br>+ enum ast_sip_contact_status_type status;<br> /*! Last status for a contact (default - unavailable) */<br> enum ast_sip_contact_status_type last_status;<br>- /*! TRUE if the contact was refreshed. e.g., re-registered */<br>- unsigned int refresh:1;<br>+ /*! Name of the contact */<br>+ char name[0];<br> };<br> <br> /*!<br>@@ -1061,7 +1056,7 @@<br> /*!<br> * \brief Change state of a persistent endpoint.<br> *<br>- * \param endpoint The SIP endpoint name to change state.<br>+ * \param endpoint_name The SIP endpoint name to change state.<br> * \param state The new state<br> * \retval 0 Success<br> * \retval -1 Endpoint not found<br>@@ -1069,6 +1064,26 @@<br> int ast_sip_persistent_endpoint_update_state(const char *endpoint_name, enum ast_endpoint_state state);<br> <br> /*!<br>+ * \brief Publish the change of state for a contact.<br>+ *<br>+ * \param endpoint_name The SIP endpoint name.<br>+ * \param contact_status The contact status.<br>+ */<br>+void ast_sip_persistent_endpoint_publish_contact_state(const char *endpoint_name, const struct ast_sip_contact_status *contact_status);<br>+<br>+/*!<br>+ * \brief Retrieve the current status for a contact.<br>+ *<br>+ * \param contact The contact.<br>+ *<br>+ * \retval non-NULL Success<br>+ * \retval NULL Status information not found<br>+ *<br>+ * \note The returned contact status object is immutable.<br>+ */<br>+struct ast_sip_contact_status *ast_sip_get_contact_status(const struct ast_sip_contact *contact);<br>+<br>+/*!<br> * \brief Get a pointer to the PJSIP endpoint.<br> *<br> * This is useful when modules have specific information they need<br>diff --git a/res/res_pjsip.c b/res/res_pjsip.c<br>index 19e6e1d..b6a736b 100644<br>--- a/res/res_pjsip.c<br>+++ b/res/res_pjsip.c<br>@@ -5174,8 +5174,12 @@<br> }<br> <br> ast_sip_initialize_dns();<br>-<br> ast_sip_initialize_global_headers();<br>+<br>+ if (ast_res_pjsip_preinit_options_handling()) {<br>+ ast_log(LOG_ERROR, "Failed to pre-initialize OPTIONS handling. Aborting load\n");<br>+ goto error;<br>+ }<br> <br> if (ast_res_pjsip_initialize_configuration()) {<br> ast_log(LOG_ERROR, "Failed to initialize SIP configuration. Aborting load\n");<br>@@ -5200,7 +5204,10 @@<br> goto error;<br> }<br> <br>- ast_res_pjsip_init_options_handling(0);<br>+ if (ast_res_pjsip_init_options_handling(0)) {<br>+ ast_log(LOG_ERROR, "Failed to initialize OPTIONS handling. Aborting load\n");<br>+ goto error;<br>+ }<br> <br> if (ast_res_pjsip_init_message_filter()) {<br> ast_log(LOG_ERROR, "Failed to initialize message IP updating. Aborting load\n");<br>diff --git a/res/res_pjsip/include/res_pjsip_private.h b/res/res_pjsip/include/res_pjsip_private.h<br>index 71947f8..2fd6d31 100644<br>--- a/res/res_pjsip/include/res_pjsip_private.h<br>+++ b/res/res_pjsip/include/res_pjsip_private.h<br>@@ -193,6 +193,15 @@<br> <br> /*!<br> * \internal<br>+ * \brief Pre-initialize OPTIONS request handling.<br>+ *<br>+ * \retval 0 on success<br>+ * \retval other on failure<br>+ */<br>+int ast_res_pjsip_preinit_options_handling(void);<br>+<br>+/*!<br>+ * \internal<br> * \brief Initialize OPTIONS request handling.<br> *<br> * XXX This currently includes qualifying peers. It shouldn't.<br>diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c<br>index 6e79dc4..114a66b 100644<br>--- a/res/res_pjsip/location.c<br>+++ b/res/res_pjsip/location.c<br>@@ -180,7 +180,7 @@<br> struct ast_sip_contact_status *status;<br> int unreachable;<br> <br>- status = ast_res_pjsip_find_or_create_contact_status(contact);<br>+ status = ast_sip_get_contact_status(contact);<br> if (!status) {<br> return 0;<br> }<br>@@ -1065,7 +1065,7 @@<br> ast_assert(contact->uri != NULL);<br> ast_assert(context->output_buffer != NULL);<br> <br>- status = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), CONTACT_STATUS, contact_id);<br>+ status = ast_sip_get_contact_status(contact);<br> <br> indent = CLI_INDENT_TO_SPACES(context->indent_level);<br> flexwidth = CLI_LAST_TABSTOP - indent - 9 - strlen(contact->aor) + 1;<br>@@ -1078,7 +1078,7 @@<br> contact->uri,<br> hash_start,<br> ast_sip_get_contact_short_status_label(status ? status->status : UNKNOWN),<br>- (status && (status->status != UNKNOWN) ? ((long long) status->rtt) / 1000.0 : NAN));<br>+ (status && (status->status == AVAILABLE)) ? ((long long) status->rtt) / 1000.0 : NAN);<br> <br> ao2_cleanup(status);<br> return 0;<br>diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c<br>index fb84a1f..80983af 100644<br>--- a/res/res_pjsip/pjsip_configuration.c<br>+++ b/res/res_pjsip/pjsip_configuration.c<br>@@ -42,8 +42,6 @@<br> struct sip_persistent_endpoint {<br> /*! \brief Asterisk endpoint itself */<br> struct ast_endpoint *endpoint;<br>- /*! \brief AORs that we should react to */<br>- char *aors;<br> };<br> <br> /*! \brief Container for persistent endpoint information */<br>@@ -69,239 +67,6 @@<br> <br> return !strcmp(ast_endpoint_get_resource(persistent1->endpoint), id) ? CMP_MATCH | CMP_STOP : 0;<br> }<br>-<br>-/*! \brief Internal function for changing the state of an endpoint */<br>-static void endpoint_update_state(struct ast_endpoint *endpoint, enum ast_endpoint_state state)<br>-{<br>- struct ast_json *blob;<br>- char *regcontext;<br>-<br>- /* If there was no state change, don't publish anything. */<br>- if (ast_endpoint_get_state(endpoint) == state) {<br>- return;<br>- }<br>-<br>- regcontext = ast_sip_get_regcontext();<br>-<br>- if (state == AST_ENDPOINT_ONLINE) {<br>- ast_endpoint_set_state(endpoint, AST_ENDPOINT_ONLINE);<br>- blob = ast_json_pack("{s: s}", "peer_status", "Reachable");<br>-<br>- if (!ast_strlen_zero(regcontext)) {<br>- if (!ast_exists_extension(NULL, regcontext, ast_endpoint_get_resource(endpoint), 1, NULL)) {<br>- ast_add_extension(regcontext, 1, ast_endpoint_get_resource(endpoint), 1, NULL, NULL,<br>- "Noop", ast_strdup(ast_endpoint_get_resource(endpoint)), ast_free_ptr, "SIP");<br>- }<br>- }<br>-<br>- ast_verb(2, "Endpoint %s is now Reachable\n", ast_endpoint_get_resource(endpoint));<br>- } else {<br>- ast_endpoint_set_state(endpoint, AST_ENDPOINT_OFFLINE);<br>- blob = ast_json_pack("{s: s}", "peer_status", "Unreachable");<br>-<br>- if (!ast_strlen_zero(regcontext)) {<br>- struct pbx_find_info q = { .stacklen = 0 };<br>-<br>- if (pbx_find_extension(NULL, NULL, &q, regcontext, ast_endpoint_get_resource(endpoint), 1, NULL, "", E_MATCH)) {<br>- ast_context_remove_extension(regcontext, ast_endpoint_get_resource(endpoint), 1, NULL);<br>- }<br>- }<br>-<br>- ast_verb(2, "Endpoint %s is now Unreachable\n", ast_endpoint_get_resource(endpoint));<br>- }<br>-<br>- ast_free(regcontext);<br>-<br>- ast_endpoint_blob_publish(endpoint, ast_endpoint_state_type(), blob);<br>- ast_json_unref(blob);<br>- ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_endpoint_get_resource(endpoint));<br>-}<br>-<br>-static void endpoint_publish_contact_status(struct ast_endpoint *endpoint, struct ast_sip_contact_status *contact)<br>-{<br>- struct ast_json *blob;<br>- char rtt[32];<br>-<br>- snprintf(rtt, sizeof(rtt), "%" PRId64, contact->rtt);<br>- blob = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",<br>- "contact_status", ast_sip_get_contact_status_label(contact->status),<br>- "aor", contact->aor,<br>- "uri", contact->uri,<br>- "roundtrip_usec", rtt,<br>- "endpoint_name", ast_endpoint_get_resource(endpoint));<br>- if (blob) {<br>- ast_endpoint_blob_publish(endpoint, ast_endpoint_contact_state_type(), blob);<br>- ast_json_unref(blob);<br>- }<br>-}<br>-<br>-/*! \brief Callback function for publishing the status of an endpoint */<br>-static int persistent_endpoint_publish_status(void *obj, void *arg, int flags)<br>-{<br>- struct sip_persistent_endpoint *persistent = obj;<br>- struct ast_endpoint *endpoint = persistent->endpoint;<br>- struct ast_sip_contact_status *status = arg;<br>-<br>- /* If the status' aor isn't one of the endpoint's, we skip */<br>- if (!strstr(persistent->aors, status->aor)) {<br>- return 0;<br>- }<br>-<br>- endpoint_publish_contact_status(endpoint, status);<br>- return 0;<br>-}<br>-<br>-/*! \brief Callback function for changing the state of an endpoint */<br>-static int persistent_endpoint_update_state(void *obj, void *arg, int flags)<br>-{<br>- struct sip_persistent_endpoint *persistent = obj;<br>- struct ast_endpoint *endpoint = persistent->endpoint;<br>- struct ast_sip_contact_status *status = arg;<br>- struct ao2_container *contacts;<br>- struct ao2_iterator iter;<br>- struct ast_sip_contact *contact;<br>- enum ast_endpoint_state state = AST_ENDPOINT_OFFLINE;<br>-<br>- /* If the status' aor isn't one of the endpoint's, we skip */<br>- if (!strstr(persistent->aors, status->aor)) {<br>- return 0;<br>- }<br>-<br>- endpoint_publish_contact_status(endpoint, status);<br>-<br>- /* Find all the contacts for this endpoint. If ANY are available,<br>- * mark the endpoint as ONLINE.<br>- */<br>- contacts = ast_sip_location_retrieve_contacts_from_aor_list(persistent->aors);<br>- if (contacts) {<br>- iter = ao2_iterator_init(contacts, 0);<br>- while (state == AST_ENDPOINT_OFFLINE && (contact = ao2_iterator_next(&iter))) {<br>- struct ast_sip_contact_status *contact_status;<br>- const char *contact_id = ast_sorcery_object_get_id(contact);<br>-<br>- contact_status = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(),<br>- CONTACT_STATUS, contact_id);<br>- if (contact_status && contact_status->status != UNAVAILABLE) {<br>- state = AST_ENDPOINT_ONLINE;<br>- }<br>- ao2_cleanup(contact_status);<br>- ao2_ref(contact, -1);<br>- }<br>- ao2_iterator_destroy(&iter);<br>- ao2_ref(contacts, -1);<br>- }<br>-<br>- endpoint_update_state(endpoint, state);<br>-<br>- return 0;<br>-}<br>-<br>-/*! \brief Function called when a contact is created */<br>-static void persistent_endpoint_contact_created_observer(const void *object)<br>-{<br>- const struct ast_sip_contact *contact = object;<br>- struct ast_sip_contact_status *contact_status;<br>-<br>- contact_status = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,<br>- ast_sorcery_object_get_id(contact));<br>- if (!contact_status) {<br>- ast_log(LOG_ERROR, "Unable to create ast_sip_contact_status for contact %s/%s\n",<br>- contact->aor, contact->uri);<br>- return;<br>- }<br>- ast_string_field_set(contact_status, uri, contact->uri);<br>-<br>- contact_status->status = CREATED;<br>-<br>- ast_verb(2, "Contact %s/%s has been created\n", contact->aor, contact->uri);<br>-<br>- ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, contact_status);<br>- ao2_cleanup(contact_status);<br>-}<br>-<br>-/*! \brief Function called when a contact is deleted */<br>-static void persistent_endpoint_contact_deleted_observer(const void *object)<br>-{<br>- const struct ast_sip_contact *contact = object;<br>- struct ast_sip_contact_status *contact_status;<br>-<br>- contact_status = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), CONTACT_STATUS, ast_sorcery_object_get_id(contact));<br>- if (!contact_status) {<br>- ast_log(LOG_ERROR, "Unable to find ast_sip_contact_status for contact %s/%s\n",<br>- contact->aor, contact->uri);<br>- return;<br>- }<br>-<br>- ast_verb(2, "Contact %s/%s has been deleted\n", contact->aor, contact->uri);<br>- ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>- "-1", 1.0, ast_sip_get_contact_status_label(contact_status->status));<br>- ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>- "+1", 1.0, ast_sip_get_contact_status_label(REMOVED));<br>-<br>- ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, contact_status);<br>- ast_sorcery_delete(ast_sip_get_sorcery(), contact_status);<br>- ao2_cleanup(contact_status);<br>-}<br>-<br>-/*! \brief Observer for contacts so state can be updated on respective endpoints */<br>-static const struct ast_sorcery_observer state_contact_observer = {<br>- .created = persistent_endpoint_contact_created_observer,<br>- .deleted = persistent_endpoint_contact_deleted_observer,<br>-};<br>-<br>-/*! \brief Function called when a contact_status is updated */<br>-static void persistent_endpoint_contact_status_observer(const void *object)<br>-{<br>- struct ast_sip_contact_status *contact_status = (struct ast_sip_contact_status *)object;<br>-<br>- if (contact_status->refresh) {<br>- /* We are only re-publishing the contact status. */<br>- ao2_callback(persistent_endpoints, OBJ_NODATA,<br>- persistent_endpoint_publish_status, contact_status);<br>- return;<br>- }<br>-<br>- /* If rtt_start is set (this is the outgoing OPTIONS), ignore. */<br>- if (contact_status->rtt_start.tv_sec > 0) {<br>- return;<br>- }<br>-<br>- if (contact_status->status != contact_status->last_status) {<br>- ast_verb(3, "Contact %s/%s is now %s. RTT: %.3f msec\n",<br>- contact_status->aor, contact_status->uri,<br>- ast_sip_get_contact_status_label(contact_status->status),<br>- contact_status->rtt / 1000.0);<br>-<br>- ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>- "-1", 1.0, ast_sip_get_contact_status_label(contact_status->last_status));<br>- ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>- "+1", 1.0, ast_sip_get_contact_status_label(contact_status->status));<br>-<br>- ast_test_suite_event_notify("AOR_CONTACT_UPDATE",<br>- "Contact: %s\r\n"<br>- "Status: %s",<br>- ast_sorcery_object_get_id(contact_status),<br>- ast_sip_get_contact_status_label(contact_status->status));<br>-<br>- ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state,<br>- contact_status);<br>- } else {<br>- ast_debug(3, "Contact %s/%s status didn't change: %s, RTT: %.3f msec\n",<br>- contact_status->aor, contact_status->uri,<br>- ast_sip_get_contact_status_label(contact_status->status),<br>- contact_status->rtt / 1000.0);<br>- }<br>-<br>- ast_statsd_log_full_va("PJSIP.contacts.%s.rtt", AST_STATSD_TIMER,<br>- contact_status->status != AVAILABLE ? -1 : contact_status->rtt / 1000,<br>- 1.0,<br>- ast_sorcery_object_get_id(contact_status));<br>-}<br>-<br>-/*! \brief Observer for contacts so state can be updated on respective endpoints */<br>-static const struct ast_sorcery_observer state_contact_status_observer = {<br>- .updated = persistent_endpoint_contact_status_observer,<br>-};<br> <br> static void endpoint_deleted_observer(const void *object)<br> {<br>@@ -1352,21 +1117,89 @@<br> struct sip_persistent_endpoint *persistent = obj;<br> <br> ast_endpoint_shutdown(persistent->endpoint);<br>- ast_free(persistent->aors);<br> }<br> <br> int ast_sip_persistent_endpoint_update_state(const char *endpoint_name, enum ast_endpoint_state state)<br> {<br> struct sip_persistent_endpoint *persistent;<br>+ struct ast_json *blob;<br>+ char *regcontext;<br> <br>- ao2_lock(persistent_endpoints);<br>- persistent = ao2_find(persistent_endpoints, endpoint_name, OBJ_SEARCH_KEY | OBJ_NOLOCK);<br>- if (persistent) {<br>- endpoint_update_state(persistent->endpoint, state);<br>- ao2_ref(persistent, -1);<br>+ persistent = ao2_find(persistent_endpoints, endpoint_name, OBJ_SEARCH_KEY);<br>+ if (!persistent) {<br>+ return -1;<br> }<br>- ao2_unlock(persistent_endpoints);<br>- return persistent ? 0 : -1;<br>+<br>+ /* If there was no state change, don't publish anything. */<br>+ if (ast_endpoint_get_state(persistent->endpoint) == state) {<br>+ ao2_ref(persistent, -1);<br>+ return 0;<br>+ }<br>+<br>+ regcontext = ast_sip_get_regcontext();<br>+<br>+ if (state == AST_ENDPOINT_ONLINE) {<br>+ ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_ONLINE);<br>+ blob = ast_json_pack("{s: s}", "peer_status", "Reachable");<br>+<br>+ if (!ast_strlen_zero(regcontext)) {<br>+ if (!ast_exists_extension(NULL, regcontext, ast_endpoint_get_resource(persistent->endpoint), 1, NULL)) {<br>+ ast_add_extension(regcontext, 1, ast_endpoint_get_resource(persistent->endpoint), 1, NULL, NULL,<br>+ "Noop", ast_strdup(ast_endpoint_get_resource(persistent->endpoint)), ast_free_ptr, "SIP");<br>+ }<br>+ }<br>+<br>+ ast_verb(2, "Endpoint %s is now Reachable\n", ast_endpoint_get_resource(persistent->endpoint));<br>+ } else {<br>+ ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_OFFLINE);<br>+ blob = ast_json_pack("{s: s}", "peer_status", "Unreachable");<br>+<br>+ if (!ast_strlen_zero(regcontext)) {<br>+ struct pbx_find_info q = { .stacklen = 0 };<br>+<br>+ if (pbx_find_extension(NULL, NULL, &q, regcontext, ast_endpoint_get_resource(persistent->endpoint), 1, NULL, "", E_MATCH)) {<br>+ ast_context_remove_extension(regcontext, ast_endpoint_get_resource(persistent->endpoint), 1, NULL);<br>+ }<br>+ }<br>+<br>+ ast_verb(2, "Endpoint %s is now Unreachable\n", ast_endpoint_get_resource(persistent->endpoint));<br>+ }<br>+<br>+ ast_free(regcontext);<br>+<br>+ ast_endpoint_blob_publish(persistent->endpoint, ast_endpoint_state_type(), blob);<br>+ ast_json_unref(blob);<br>+ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_endpoint_get_resource(persistent->endpoint));<br>+<br>+ ao2_ref(persistent, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+void ast_sip_persistent_endpoint_publish_contact_state(const char *endpoint_name, const struct ast_sip_contact_status *contact_status)<br>+{<br>+ struct sip_persistent_endpoint *persistent;<br>+ struct ast_json *blob;<br>+ char rtt[32];<br>+<br>+ persistent = ao2_find(persistent_endpoints, endpoint_name, OBJ_SEARCH_KEY);<br>+ if (!persistent) {<br>+ return;<br>+ }<br>+<br>+ snprintf(rtt, sizeof(rtt), "%" PRId64, contact_status->rtt);<br>+ blob = ast_json_pack("{s: s, s: s, s: s, s: s, s: s}",<br>+ "contact_status", ast_sip_get_contact_status_label(contact_status->status),<br>+ "aor", contact_status->aor,<br>+ "uri", contact_status->uri,<br>+ "roundtrip_usec", rtt,<br>+ "endpoint_name", ast_endpoint_get_resource(persistent->endpoint));<br>+ if (blob) {<br>+ ast_endpoint_blob_publish(persistent->endpoint, ast_endpoint_contact_state_type(), blob);<br>+ ast_json_unref(blob);<br>+ }<br>+<br>+ ao2_ref(persistent, -1);<br> }<br> <br> /*! \brief Internal function which finds (or creates) persistent endpoint information */<br>@@ -1390,22 +1223,9 @@<br> return NULL;<br> }<br> <br>- persistent->aors = ast_strdup(endpoint->aors);<br>- if (!persistent->aors) {<br>- return NULL;<br>- }<br>-<br> ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_OFFLINE);<br> <br> ao2_link_flags(persistent_endpoints, persistent, OBJ_NOLOCK);<br>- } else if (strcmp(persistent->aors, endpoint->aors)) {<br>- char *new_aors = ast_strdup(endpoint->aors);<br>-<br>- /* make sure we don't NULL persistent->aors if allocation fails. */<br>- if (new_aors) {<br>- ast_free(persistent->aors);<br>- persistent->aors = new_aors;<br>- }<br> }<br> <br> ao2_ref(persistent->endpoint, +1);<br>@@ -2097,16 +1917,7 @@<br> return -1;<br> }<br> <br>- if (ast_sip_initialize_sorcery_qualify()) {<br>- ast_log(LOG_ERROR, "Failed to register SIP qualify support with sorcery\n");<br>- ast_sorcery_unref(sip_sorcery);<br>- sip_sorcery = NULL;<br>- return -1;<br>- }<br>-<br> ast_sorcery_observer_add(sip_sorcery, "endpoint", &endpoint_observers);<br>- ast_sorcery_observer_add(sip_sorcery, "contact", &state_contact_observer);<br>- ast_sorcery_observer_add(sip_sorcery, CONTACT_STATUS, &state_contact_status_observer);<br> <br> if (ast_sip_initialize_sorcery_domain_alias()) {<br> ast_log(LOG_ERROR, "Failed to register SIP domain aliases support with sorcery\n");<br>@@ -2155,8 +1966,6 @@<br> return;<br> }<br> <br>- ast_sorcery_observer_remove(sip_sorcery, CONTACT_STATUS, &state_contact_status_observer);<br>- ast_sorcery_observer_remove(sip_sorcery, "contact", &state_contact_observer);<br> ast_sip_destroy_sorcery_global();<br> ast_sip_destroy_sorcery_location();<br> ast_sip_destroy_sorcery_auth();<br>diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c<br>index 4ae1f31..904e70c 100644<br>--- a/res/res_pjsip/pjsip_options.c<br>+++ b/res/res_pjsip/pjsip_options.c<br>@@ -1,9 +1,10 @@<br> /*<br> * Asterisk -- An open source telephony toolkit.<br> *<br>- * Copyright (C) 2013, Digium, Inc.<br>+ * Copyright (C) 2018, Digium, Inc.<br> *<br>- * Matt Jordan <mjordan@digium.com><br>+ * Joshua Colp <jcolp@digium.com><br>+ * Richard Mudgett <rmudgett@digium.com><br> *<br> * See http://www.asterisk.org for more information about<br> * the Asterisk project. Please do not directly contact<br>@@ -32,652 +33,184 @@<br> #include "asterisk/statsd.h"<br> #include "include/res_pjsip_private.h"<br> #include "asterisk/taskprocessor.h"<br>+#include "asterisk/threadpool.h"<br>+<br>+/*<br>+ * This implementation for OPTIONS support is based around the idea<br>+ * that realistically an AOR generally has very few contacts and is<br>+ * referenced by only a few endpoints. While it is perfectly fine for<br>+ * use in opposite scenarios it works best in the above case. It is<br>+ * also not shy to keeping state but it is reactive to outside changes<br>+ * so it can be updated.<br>+ *<br>+ * The lowest level object in here is a contact and its associated<br>+ * contact status. The result of an OPTIONS request to a contact is<br>+ * reflected in the contact status. The scheduling of these OPTIONS<br>+ * request is driven by the AOR. The AOR periodicially (according to<br>+ * configuration) sends OPTIONS requests out to any contacts<br>+ * associated with it. Contacts themselves are not individually<br>+ * scheduled. Contacts can be added or deleted as appropriate with no<br>+ * requirement to reschedule.<br>+ *<br>+ * The next level object up is the AOR itself. The result of a contact<br>+ * status change is fed into it and the result composited with all<br>+ * other contacts. This may result in the AOR itself changing state<br>+ * (it can be either AVAILABLE or UNAVAILABLE).<br>+ *<br>+ * The highest level object up is the endpoint state compositor (ESC).<br>+ * The result of AOR state changes is fed into it and the result<br>+ * composited with all other referenced AORs. This may result in the<br>+ * endpoint itself changing state (it can be either ONLINE or<br>+ * OFFLINE). If this occurs the permanent endpoint is updated to<br>+ * reflect it.<br>+ *<br>+ * The threading model errs on the side of a world where things are<br>+ * not constantly changing. That is: A world where AORs and endpoints<br>+ * are not being constantly added/removed. This more closely mirrors<br>+ * the usage of the vast majority of people. This scenario can still<br>+ * be done but it may not be applied immediately.<br>+ *<br>+ * Manipulation of which AORs, endpoint state compositors, and<br>+ * contacts exist is done within a single serializer. This ensures<br>+ * that no matter the source threads order is preserved and you won't<br>+ * get into a weird situation where things are referencing other<br>+ * things that should have already been destroyed.<br>+ *<br>+ * Operations which impact the state of an AOR are done within a<br>+ * serializer that is specific to the AOR. This includes the result of<br>+ * a contact status change. This change is queued and executed on the<br>+ * AOR serializer afterwards.<br>+ *<br>+ * Operations which impact an endpoint state compositor are protected<br>+ * by a lock. This is done as the endpoint state compositor usage is<br>+ * minimal and the overhead of using a serializer and queueing things<br>+ * is not warranted.<br>+ *<br>+ * AORs which do not have a qualify frequency are also kept in here<br>+ * but do not require the same criteria as qualified AORs to be<br>+ * considered available. In their case as long as at least 1 contact<br>+ * is configured on the AOR (or added to it by registration) it is<br>+ * considered available.<br>+ */<br> <br> #define DEFAULT_LANGUAGE "en"<br> #define DEFAULT_ENCODING "text/plain"<br>-#define QUALIFIED_BUCKETS 211<br> <br>-static const char *status_map [] = {<br>- [UNAVAILABLE] = "Unreachable",<br>- [AVAILABLE] = "Reachable",<br>- [UNKNOWN] = "Unknown",<br>- [CREATED] = "NonQualified",<br>- [REMOVED] = "Removed",<br>+/*! \brief These are the number of buckets to store AORs in */<br>+#ifdef LOW_MEMORY<br>+#define AOR_BUCKETS 61<br>+#else<br>+#define AOR_BUCKETS 1567<br>+#endif<br>+<br>+/*! \brief These are the number of contact status buckets */<br>+#ifdef LOW_MEMORY<br>+#define CONTACT_STATUS_BUCKETS 61<br>+#else<br>+#define CONTACT_STATUS_BUCKETS 1567<br>+#endif<br>+<br>+/*! \brief These are the number of buckets (per AOR) to use to store contacts */<br>+#define CONTACT_BUCKETS 13<br>+<br>+/*! \brief These are the number of buckets to store endpoint state compositors */<br>+#define ENDPOINT_STATE_COMPOSITOR_BUCKETS 13<br>+<br>+/*! \brief The initial vector size for the endpoint state compositors on an AOR */<br>+#define ENDPOINT_STATE_COMPOSITOR_INITIAL_SIZE 1<br>+<br>+/*! \brief These are the number of buckets (per endpoint state compositor) to use to store AOR statuses */<br>+#define AOR_STATUS_BUCKETS 3<br>+<br>+/*! \brief Maximum wait time to join the below shutdown group */<br>+#define MAX_UNLOAD_TIMEOUT_TIME 10 /* Seconds */<br>+<br>+/*! \brief Shutdown group for options serializers */<br>+static struct ast_serializer_shutdown_group *shutdown_group;<br>+<br>+/*!<br>+ * \brief Structure which contains status information for an AOR feeding an endpoint state compositor<br>+ */<br>+struct sip_options_endpoint_aor_status {<br>+ /*! \brief The last contributed available status of the named AOR (1 if available, 0 if not available) */<br>+ char available;<br>+ /*! \brief The name of the AOR */<br>+ char name[0];<br> };<br> <br>-static const char *short_status_map [] = {<br>- [UNAVAILABLE] = "Unavail",<br>- [AVAILABLE] = "Avail",<br>- [UNKNOWN] = "Unknown",<br>- [CREATED] = "NonQual",<br>- [REMOVED] = "Removed",<br>-};<br>-<br>-static void contact_deleted(const void *obj);<br>-static void qualify_and_schedule(struct ast_sip_contact *contact);<br>-<br>-const char *ast_sip_get_contact_status_label(const enum ast_sip_contact_status_type status)<br>-{<br>- return status_map[status];<br>-}<br>-<br>-const char *ast_sip_get_contact_short_status_label(const enum ast_sip_contact_status_type status)<br>-{<br>- return short_status_map[status];<br>-}<br>-<br> /*!<br>- * \internal<br>- * \brief Destroy a ast_sip_contact_status object.<br>+ * \brief Structure which contains composites information for endpoint state<br> */<br>-static void contact_status_destroy(void * obj)<br>-{<br>- struct ast_sip_contact_status *status = obj;<br>-<br>- ast_string_field_free_memory(status);<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Create a ast_sip_contact_status object.<br>- */<br>-static void *contact_status_alloc(const char *name)<br>-{<br>- struct ast_sip_contact_status *status = ast_sorcery_generic_alloc(sizeof(*status), contact_status_destroy);<br>- char *id = ast_strdupa(name);<br>- char *aor = id;<br>- char *aor_separator = NULL;<br>-<br>- if (!status) {<br>- ast_log(LOG_ERROR, "Unable to allocate ast_sip_contact_status\n");<br>- return NULL;<br>- }<br>-<br>- if (ast_string_field_init(status, 256)) {<br>- ast_log(LOG_ERROR, "Unable to allocate ast_sip_contact_status stringfields\n");<br>- ao2_cleanup(status);<br>- return NULL;<br>- }<br>-<br>- /* Dynamic contacts are delimited with ";@" and static ones with "@@" */<br>- if ((aor_separator = strstr(id, ";@")) || (aor_separator = strstr(id, "@@"))) {<br>- *aor_separator = '\0';<br>- }<br>- ast_assert(aor_separator != NULL);<br>-<br>- ast_string_field_set(status, aor, aor);<br>- status->status = CREATED;<br>-<br>- return status;<br>-}<br>-<br>-static int qualify_and_schedule_aor_contact(void *obj)<br>-{<br>- struct ast_sip_contact *contact = obj;<br>- struct ast_sip_aor *aor;<br>-<br>- if (!contact || ast_strlen_zero(contact->aor) ||<br>- !(aor = ast_sip_location_retrieve_aor(contact->aor))) {<br>- ao2_ref(contact, -1);<br>- return -1;<br>- }<br>-<br>- contact->qualify_frequency = aor->qualify_frequency;<br>- contact->qualify_timeout = aor->qualify_timeout;<br>- contact->authenticate_qualify = aor->authenticate_qualify;<br>-<br>- ao2_ref(aor, -1);<br>-<br>- qualify_and_schedule(contact);<br>- ao2_ref(contact, -1);<br>-<br>- return 0;<br>-}<br>-<br>-AST_MUTEX_DEFINE_STATIC(creation_lock);<br>-<br>-/*!<br>- * \brief Retrieve a ast_sip_contact_status object from sorcery creating<br>- * one if not found.<br>- */<br>-struct ast_sip_contact_status *ast_res_pjsip_find_or_create_contact_status(const struct ast_sip_contact *contact)<br>-{<br>- struct ast_sip_contact_status *status;<br>- SCOPED_MUTEX(lock, &creation_lock);<br>-<br>- status = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), CONTACT_STATUS,<br>- ast_sorcery_object_get_id(contact));<br>- if (status) {<br>- return status;<br>- }<br>-<br>- status = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,<br>- ast_sorcery_object_get_id(contact));<br>- if (!status) {<br>- ast_log(LOG_ERROR, "Unable to create ast_sip_contact_status for contact %s/%s\n",<br>- contact->aor, contact->uri);<br>- return NULL;<br>- }<br>-<br>- ast_string_field_set(status, uri, contact->uri);<br>- status->rtt_start = ast_tv(0, 0);<br>- status->rtt = 0;<br>-<br>- if (ast_sorcery_create(ast_sip_get_sorcery(), status)) {<br>- ast_log(LOG_ERROR, "Unable to persist ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- ao2_ref(status, -1);<br>- return NULL;<br>- }<br>-<br>- /* The permanent contact added after asterisk start should be qualified. */<br>- if (ast_test_flag(&ast_options, AST_OPT_FLAG_FULLY_BOOTED) && ast_tvzero(contact->expiration_time)) {<br>- /*<br>- * The FULLY_BOOTED to filter out contacts that already existed when asterisk started.<br>- * The zero expiration_time to select only permanent contacts.<br>- */<br>- ao2_ref((struct ast_sip_contact *) contact, +1);<br>- if (ast_sip_push_task(NULL, qualify_and_schedule_aor_contact, (struct ast_sip_contact *) contact)) {<br>- ao2_ref((struct ast_sip_contact *) contact, -1);<br>- }<br>- }<br>-<br>- ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>- "+1", 1.0, ast_sip_get_contact_status_label(status->status));<br>-<br>- return status;<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Update an ast_sip_contact_status's elements.<br>- */<br>-static void update_contact_status(const struct ast_sip_contact *contact,<br>- enum ast_sip_contact_status_type value, int is_contact_refresh)<br>-{<br>- RAII_VAR(struct ast_sip_contact_status *, status, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_sip_contact_status *, update, NULL, ao2_cleanup);<br>-<br>- status = ast_res_pjsip_find_or_create_contact_status(contact);<br>- if (!status) {<br>- ast_log(LOG_ERROR, "Unable to find ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- return;<br>- }<br>-<br>- /*<br>- * If the current status is CREATED, and it's a refresh or the given value is<br>- * also CREATED then there is nothing to update as nothing needs to change.<br>+struct sip_options_endpoint_state_compositor {<br>+ /*! \brief The last contributed available status of the AORs feeding this compositor */<br>+ struct ao2_container *aor_statuses;<br>+ /*!<br>+ * \brief Non-zero if the compositor is in normal operation. i.e. Not being setup/reconfigured.<br>+ *<br>+ * \details<br>+ * The aor layer can only update its aor_statuses record when not active.<br>+ * When active the aor layer can update its aor_statuses record, calculate the new<br>+ * number of available aors, determine if the endpoint compositor changed state,<br>+ * and report it.<br> */<br>- if (status->status == CREATED && (is_contact_refresh || status->status == value)) {<br>- return;<br>- }<br>-<br>- update = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,<br>- ast_sorcery_object_get_id(status));<br>- if (!update) {<br>- ast_log(LOG_ERROR, "Unable to allocate ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- return;<br>- }<br>-<br>- ast_string_field_set(update, uri, contact->uri);<br>-<br>- if (is_contact_refresh) {<br>- /* Copy everything just to set the refresh flag. */<br>- update->status = status->status;<br>- update->last_status = status->last_status;<br>- update->rtt = status->rtt;<br>- update->rtt_start = status->rtt_start;<br>- update->refresh = 1;<br>- } else {<br>- update->last_status = status->status;<br>- update->status = value;<br>-<br>- /*<br>- * if the contact is available calculate the rtt as<br>- * the diff between the last start time and "now"<br>- */<br>- update->rtt = update->status == AVAILABLE && status->rtt_start.tv_sec > 0<br>- ? ast_tvdiff_us(ast_tvnow(), status->rtt_start)<br>- : 0;<br>- update->rtt_start = ast_tv(0, 0);<br>-<br>- ast_test_suite_event_notify("AOR_CONTACT_QUALIFY_RESULT",<br>- "Contact: %s\r\n"<br>- "Status: %s\r\n"<br>- "RTT: %" PRId64,<br>- ast_sorcery_object_get_id(update),<br>- ast_sip_get_contact_status_label(update->status),<br>- update->rtt);<br>- }<br>-<br>- if (ast_sorcery_update(ast_sip_get_sorcery(), update)) {<br>- ast_log(LOG_ERROR, "Unable to update ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- }<br>-}<br>+ char active;<br>+ /*! \brief The name of the endpoint */<br>+ char name[0];<br>+};<br> <br> /*!<br>- * \internal<br>- * \brief Initialize the start time on a contact status so the round<br>- * trip time can be calculated upon a valid response.<br>+ * \brief Structure which contains an AOR and contacts for qualifying purposes<br> */<br>-static void init_start_time(const struct ast_sip_contact *contact)<br>-{<br>- RAII_VAR(struct ast_sip_contact_status *, status, NULL, ao2_cleanup);<br>- RAII_VAR(struct ast_sip_contact_status *, update, NULL, ao2_cleanup);<br>-<br>- status = ast_res_pjsip_find_or_create_contact_status(contact);<br>- if (!status) {<br>- ast_log(LOG_ERROR, "Unable to find ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- return;<br>- }<br>-<br>- update = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,<br>- ast_sorcery_object_get_id(status));<br>- if (!update) {<br>- ast_log(LOG_ERROR, "Unable to copy ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- return;<br>- }<br>-<br>- ast_string_field_set(status, uri, contact->uri);<br>- update->status = status->status;<br>- update->last_status = status->last_status;<br>- update->rtt = status->rtt;<br>- update->rtt_start = ast_tvnow();<br>-<br>- if (ast_sorcery_update(ast_sip_get_sorcery(), update)) {<br>- ast_log(LOG_ERROR, "Unable to update ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- }<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief For an endpoint try to match the given contact->aor.<br>- */<br>-static int on_endpoint(void *obj, void *arg, int flags)<br>-{<br>- struct ast_sip_endpoint *endpoint = obj;<br>- char *contact_aor = arg;<br>- char *aor_name;<br>- char *aors;<br>-<br>- if (!arg || ast_strlen_zero(endpoint->aors)) {<br>- return 0;<br>- }<br>-<br>- aors = ast_strdupa(endpoint->aors);<br>- while ((aor_name = ast_strip(strsep(&aors, ",")))) {<br>- if (!strcmp(contact_aor, aor_name)) {<br>- return CMP_MATCH;<br>- }<br>- }<br>-<br>- return 0;<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Find an endpoint associated with the given contact.<br>- */<br>-static struct ast_sip_endpoint *find_an_endpoint(struct ast_sip_contact *contact)<br>-{<br>- struct ao2_container *endpoints;<br>- struct ast_sip_endpoint *endpoint;<br>- struct ast_variable *var;<br>- char *aor = ast_alloca(strlen(contact->aor) + 3);<br>-<br>- sprintf(aor, "%%%s%%", contact->aor);<br>- var = ast_variable_new("aors LIKE", aor, "");<br>- endpoints = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),<br>- "endpoint", AST_RETRIEVE_FLAG_MULTIPLE, var);<br>-<br>- ast_variables_destroy(var);<br>-<br>- /*<br>- * Because aors are a string list, we have to use a pattern match but since a simple<br>- * pattern match could return an endpoint that has an aor of "aaabccc" when searching<br>- * for "abc", we still have to iterate over them to find an exact aor match.<br>+struct sip_options_aor {<br>+ /*! \brief The scheduler task for this AOR */<br>+ struct ast_sip_sched_task *sched_task;<br>+ /*! \brief The serializer for this AOR */<br>+ struct ast_taskprocessor *serializer;<br>+ /*! \brief All contacts associated with this AOR */<br>+ struct ao2_container *contacts;<br>+ /*!<br>+ * \brief Only dynamic contacts associated with this AOR<br>+ * \note Used to speed up applying AOR configuration by<br>+ * minimizing wild card sorcery access.<br> */<br>- endpoint = ao2_callback(endpoints, 0, on_endpoint, (char *)contact->aor);<br>- ao2_ref(endpoints, -1);<br>-<br>- return endpoint;<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Receive a response to the qualify contact request.<br>- */<br>-static void qualify_contact_cb(void *token, pjsip_event *e)<br>-{<br>- struct ast_sip_contact *contact = token;<br>-<br>- switch(e->body.tsx_state.type) {<br>- default:<br>- ast_log(LOG_ERROR, "Unexpected PJSIP event %u\n", e->body.tsx_state.type);<br>- /* Fall through */<br>- case PJSIP_EVENT_TRANSPORT_ERROR:<br>- case PJSIP_EVENT_TIMER:<br>- update_contact_status(contact, UNAVAILABLE, 0);<br>- break;<br>- case PJSIP_EVENT_RX_MSG:<br>- update_contact_status(contact, AVAILABLE, 0);<br>- break;<br>- }<br>- ao2_cleanup(contact);<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Attempt to qualify the contact<br>- *<br>- * \details Sends a SIP OPTIONS request to the given contact in order to make<br>- * sure that contact is available.<br>- */<br>-static int qualify_contact(struct ast_sip_endpoint *endpoint, struct ast_sip_contact *contact)<br>-{<br>- pjsip_tx_data *tdata;<br>- RAII_VAR(struct ast_sip_endpoint *, endpoint_local, NULL, ao2_cleanup);<br>-<br>- if (endpoint) {<br>- endpoint_local = ao2_bump(endpoint);<br>- } else {<br>- if (!ast_strlen_zero(contact->endpoint_name)) {<br>- endpoint_local = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", contact->endpoint_name);<br>- }<br>- if (!endpoint_local) {<br>- endpoint_local = find_an_endpoint(contact);<br>- }<br>- if (!endpoint_local) {<br>- ast_log(LOG_WARNING, "Unable to find an endpoint to qualify contact %s. Deleting this contact\n",<br>- contact->uri);<br>- contact_deleted(contact);<br>- return -1;<br>- }<br>- }<br>-<br>- if (ast_sip_create_request("OPTIONS", NULL, endpoint_local, NULL, contact, &tdata)) {<br>- ast_log(LOG_ERROR, "Unable to create request to qualify contact %s\n",<br>- contact->uri);<br>- return -1;<br>- }<br>-<br>- /* If an outbound proxy is specified set it on this request */<br>- if (!ast_strlen_zero(contact->outbound_proxy) &&<br>- ast_sip_set_outbound_proxy(tdata, contact->outbound_proxy)) {<br>- pjsip_tx_data_dec_ref(tdata);<br>- ast_log(LOG_ERROR, "Unable to apply outbound proxy on request to qualify contact %s\n",<br>- contact->uri);<br>- return -1;<br>- }<br>-<br>- init_start_time(contact);<br>-<br>- ao2_ref(contact, +1);<br>- if (ast_sip_send_out_of_dialog_request(tdata, endpoint_local, (int)(contact->qualify_timeout * 1000), contact, qualify_contact_cb)<br>- != PJ_SUCCESS) {<br>- ast_log(LOG_ERROR, "Unable to send request to qualify contact %s\n",<br>- contact->uri);<br>- update_contact_status(contact, UNAVAILABLE, 0);<br>- ao2_ref(contact, -1);<br>- return -1;<br>- }<br>-<br>- return 0;<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Scheduling context for sending QUALIFY request at specified intervals.<br>- */<br>-static struct ast_sched_context *sched;<br>-<br>-/*!<br>- * \internal<br>- * \brief Container to hold all actively scheduled qualifies.<br>- */<br>-static struct ao2_container *sched_qualifies;<br>-<br>-/*!<br>- * \internal<br>- * \brief Structure to hold qualify contact scheduling information.<br>- */<br>-struct sched_data {<br>- /*! The scheduling id */<br>- int id;<br>- /*! The the contact being checked */<br>- struct ast_sip_contact *contact;<br>+ struct ao2_container *dynamic_contacts;<br>+ /*! \brief The endpoint state compositors we are feeding, a reference is held to each */<br>+ AST_VECTOR(, struct sip_options_endpoint_state_compositor *) compositors;<br>+ /*! \brief The number of available contacts on this AOR */<br>+ unsigned int available;<br>+ /*! \brief Frequency to send OPTIONS requests to AOR contacts. 0 is disabled. */<br>+ unsigned int qualify_frequency;<br>+ /*! If true authenticate the qualify challenge response if needed */<br>+ int authenticate_qualify;<br>+ /*! \brief Qualify timeout. 0 is diabled. */<br>+ double qualify_timeout;<br>+ /*! \brief The name of the AOR */<br>+ char name[0];<br> };<br> <br> /*!<br> * \internal<br>- * \brief Destroy the scheduled data and remove from scheduler.<br>+ * \brief Container of active SIP AORs for qualifying<br> */<br>-static void sched_data_destructor(void *obj)<br>-{<br>- struct sched_data *data = obj;<br>-<br>- ao2_cleanup(data->contact);<br>-}<br>-/*!<br>- * \internal<br>- * \brief Create the scheduling data object.<br>- */<br>-static struct sched_data *sched_data_create(struct ast_sip_contact *contact)<br>-{<br>- struct sched_data *data;<br>-<br>- data = ao2_t_alloc(sizeof(*data), sched_data_destructor, contact->uri);<br>- if (!data) {<br>- ast_log(LOG_ERROR, "Unable to create schedule qualify data for contact %s\n",<br>- contact->uri);<br>- return NULL;<br>- }<br>-<br>- data->contact = contact;<br>- ao2_ref(data->contact, +1);<br>-<br>- return data;<br>-}<br>+static struct ao2_container *sip_options_aors;<br> <br> /*!<br> * \internal<br>- * \brief Send a qualify contact request within a threaded task.<br>+ * \brief Container of contact statuses<br> */<br>-static int qualify_contact_task(void *obj)<br>-{<br>- struct ast_sip_contact *contact = obj;<br>- int res;<br>-<br>- res = qualify_contact(NULL, contact);<br>- ao2_ref(contact, -1);<br>- return res;<br>-}<br>+static struct ao2_container *sip_options_contact_statuses;<br> <br> /*!<br> * \internal<br>- * \brief Send a scheduled qualify contact request.<br>+ * \brief Container of endpoint state compositors<br> */<br>-static int qualify_contact_sched(const void *obj)<br>-{<br>- struct sched_data *data = (struct sched_data *) obj;<br>-<br>- ao2_ref(data->contact, +1);<br>- if (ast_sip_push_task(NULL, qualify_contact_task, data->contact)) {<br>- ao2_ref(data->contact, -1);<br>- }<br>-<br>- /*<br>- * Always reschedule rather than have a potential race cleaning<br>- * up the data object ref between self deletion and an external<br>- * deletion.<br>- */<br>- return data->contact->qualify_frequency * 1000;<br>-}<br>+static struct ao2_container *sip_options_endpoint_state_compositors;<br> <br> /*!<br> * \internal<br>- * \brief Set up a scheduled qualify contact check.<br>+ * \brief Serializer for AOR, endpoint state compositor, and contact existence management<br> */<br>-static void schedule_qualify(struct ast_sip_contact *contact, int initial_interval)<br>-{<br>- struct sched_data *data;<br>-<br>- data = sched_data_create(contact);<br>- if (!data) {<br>- return;<br>- }<br>-<br>- ast_assert(contact->qualify_frequency != 0);<br>-<br>- ao2_t_ref(data, +1, "Ref for qualify_contact_sched() scheduler entry");<br>- data->id = ast_sched_add_variable(sched, initial_interval,<br>- qualify_contact_sched, data, 1);<br>- if (data->id < 0) {<br>- ao2_t_ref(data, -1, "Cleanup failed scheduler add");<br>- ast_log(LOG_ERROR, "Unable to schedule qualify for contact %s\n",<br>- contact->uri);<br>- } else if (!ao2_link(sched_qualifies, data)) {<br>- AST_SCHED_DEL_UNREF(sched, data->id,<br>- ao2_t_ref(data, -1, "Cleanup scheduler for failed ao2_link"));<br>- }<br>- ao2_t_ref(data, -1, "Done setting up scheduler entry");<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Remove the contact from the scheduler.<br>- */<br>-static void unschedule_qualify(struct ast_sip_contact *contact)<br>-{<br>- struct sched_data *data;<br>-<br>- data = ao2_find(sched_qualifies, contact, OBJ_UNLINK | OBJ_SEARCH_KEY);<br>- if (!data) {<br>- return;<br>- }<br>-<br>- AST_SCHED_DEL_UNREF(sched, data->id,<br>- ao2_t_ref(data, -1, "Delete scheduler entry ref"));<br>- ao2_t_ref(data, -1, "Done with ao2_find ref");<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Qualify the given contact and set up scheduling if configured.<br>- */<br>-static void qualify_and_schedule(struct ast_sip_contact *contact)<br>-{<br>- unschedule_qualify(contact);<br>-<br>- if (contact->qualify_frequency) {<br>- ao2_ref(contact, +1);<br>- if (ast_sip_push_task(NULL, qualify_contact_task, contact)) {<br>- ao2_ref(contact, -1);<br>- }<br>-<br>- schedule_qualify(contact, contact->qualify_frequency * 1000);<br>- } else {<br>- update_contact_status(contact, CREATED, 0);<br>- }<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief A new contact has been created make sure it is available.<br>- */<br>-static void contact_created(const void *obj)<br>-{<br>- qualify_and_schedule((struct ast_sip_contact *) obj);<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief A contact has been updated.<br>- */<br>-static void contact_updated(const void *obj)<br>-{<br>- update_contact_status(obj, AVAILABLE, 1);<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief A contact has been deleted remove status tracking.<br>- */<br>-static void contact_deleted(const void *obj)<br>-{<br>- struct ast_sip_contact *contact = (struct ast_sip_contact *) obj;<br>- struct ast_sip_contact_status *status;<br>-<br>- unschedule_qualify(contact);<br>-<br>- status = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), CONTACT_STATUS,<br>- ast_sorcery_object_get_id(contact));<br>- if (!status) {<br>- return;<br>- }<br>-<br>- if (ast_sorcery_delete(ast_sip_get_sorcery(), status)) {<br>- ast_log(LOG_ERROR, "Unable to delete ast_sip_contact_status for contact %s\n",<br>- contact->uri);<br>- }<br>- ao2_ref(status, -1);<br>-}<br>-<br>-static const struct ast_sorcery_observer contact_observer = {<br>- .created = contact_created,<br>- .updated = contact_updated,<br>- .deleted = contact_deleted,<br>-};<br>-<br>-static pj_bool_t options_start(void)<br>-{<br>- sched = ast_sched_context_create();<br>- if (!sched) {<br>- return -1;<br>- }<br>- if (ast_sched_start_thread(sched)) {<br>- ast_sched_context_destroy(sched);<br>- sched = NULL;<br>- return -1;<br>- }<br>-<br>- if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "contact", &contact_observer)) {<br>- ast_log(LOG_WARNING, "Unable to add contact observer\n");<br>- ast_sched_context_destroy(sched);<br>- sched = NULL;<br>- return -1;<br>- }<br>-<br>- return PJ_SUCCESS;<br>-}<br>-<br>-static int sched_qualifies_empty(void *obj, void *arg, int flags)<br>-{<br>- ao2_t_ref(obj, -1, "Release ref held by destroyed scheduler context.");<br>- return CMP_MATCH;<br>-}<br>-<br>-static pj_bool_t options_stop(void)<br>-{<br>- ast_sorcery_observer_remove(ast_sip_get_sorcery(), "contact", &contact_observer);<br>-<br>- if (sched) {<br>- ast_sched_context_destroy(sched);<br>- sched = NULL;<br>- }<br>-<br>- /* Empty the container of scheduling data refs. */<br>- ao2_callback(sched_qualifies, OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE,<br>- sched_qualifies_empty, NULL);<br>-<br>- return PJ_SUCCESS;<br>-}<br>+static struct ast_taskprocessor *management_serializer;<br> <br> static pj_status_t send_options_response(pjsip_rx_data *rdata, int code)<br> {<br>@@ -781,102 +314,1996 @@<br> .name = {"Options Module", 14},<br> .id = -1,<br> .priority = PJSIP_MOD_PRIORITY_APPLICATION,<br>- .start = options_start,<br>- .stop = options_stop,<br> .on_rx_request = options_on_rx_request,<br> };<br> <br>-/*!<br>- * \internal<br>- * \brief Send qualify request to the given contact.<br>- */<br>-static int cli_on_contact(void *obj, void *arg, void *data, int flags)<br>-{<br>- struct ast_sip_contact *contact = obj;<br>- struct ast_sip_endpoint *endpoint = data;<br>- int *cli_fd = arg;<br>-<br>- ast_cli(*cli_fd, " contact %s\n", contact->uri);<br>- qualify_contact(endpoint, contact);<br>- return 0;<br>-}<br>-<br>-/*!<br>- * \brief Data pushed to threadpool to qualify endpoints from the CLI<br>- */<br>-struct qualify_data {<br>- /*! Endpoint that is being qualified */<br>- struct ast_sip_endpoint *endpoint;<br>- /*! CLI File descriptor for printing messages */<br>- int cli_fd;<br>+static const char *status_map[] = {<br>+ [UNAVAILABLE] = "Unreachable",<br>+ [AVAILABLE] = "Reachable",<br>+ [UNKNOWN] = "Unknown",<br>+ [CREATED] = "NonQualified",<br>+ [REMOVED] = "Removed",<br> };<br> <br>-static struct qualify_data *qualify_data_alloc(struct ast_sip_endpoint *endpoint, int cli_fd)<br>-{<br>- struct qualify_data *qual_data;<br>+static const char *short_status_map[] = {<br>+ [UNAVAILABLE] = "Unavail",<br>+ [AVAILABLE] = "Avail",<br>+ [UNKNOWN] = "Unknown",<br>+ [CREATED] = "NonQual",<br>+ [REMOVED] = "Removed",<br>+};<br> <br>- qual_data = ast_malloc(sizeof(*qual_data));<br>- if (!qual_data) {<br>+const char *ast_sip_get_contact_status_label(const enum ast_sip_contact_status_type status)<br>+{<br>+ ast_assert(0 <= status && status < ARRAY_LEN(status_map));<br>+ return status_map[status];<br>+}<br>+<br>+const char *ast_sip_get_contact_short_status_label(const enum ast_sip_contact_status_type status)<br>+{<br>+ ast_assert(0 <= status && status < ARRAY_LEN(short_status_map));<br>+ return short_status_map[status];<br>+}<br>+<br>+/*! \brief Destructor for contact statuses */<br>+static void sip_contact_status_dtor(void *obj)<br>+{<br>+ struct ast_sip_contact_status *contact_status = obj;<br>+<br>+ ast_string_field_free_memory(contact_status);<br>+}<br>+<br>+static struct ast_sip_contact_status *sip_contact_status_alloc(const char *name)<br>+{<br>+ struct ast_sip_contact_status *contact_status;<br>+ size_t size = sizeof(*contact_status) + strlen(name) + 1;<br>+<br>+ contact_status = ao2_alloc_options(size, sip_contact_status_dtor,<br>+ AO2_ALLOC_OPT_LOCK_NOLOCK);<br>+ if (!contact_status) {<br>+ return NULL;<br>+ }<br>+ if (ast_string_field_init(contact_status, 256)) {<br>+ ao2_ref(contact_status, -1);<br>+ return NULL;<br>+ }<br>+ strcpy(contact_status->name, name); /* SAFE */<br>+ return contact_status;<br>+}<br>+<br>+static struct ast_sip_contact_status *sip_contact_status_copy(const struct ast_sip_contact_status *src)<br>+{<br>+ struct ast_sip_contact_status *dst;<br>+<br>+ dst = sip_contact_status_alloc(src->name);<br>+ if (!dst) {<br> return NULL;<br> }<br> <br>- qual_data->endpoint = ao2_bump(endpoint);<br>- qual_data->cli_fd = cli_fd;<br>- return qual_data;<br>+ if (ast_string_fields_copy(dst, src)) {<br>+ ao2_ref(dst, -1);<br>+ return NULL;<br>+ }<br>+ dst->rtt = src->rtt;<br>+ dst->status = src->status;<br>+ dst->last_status = src->last_status;<br>+ return dst;<br> }<br> <br>-static void qualify_data_destroy(struct qualify_data *qual_data)<br>+/*! \brief Hashing function for contact statuses */<br>+AO2_STRING_FIELD_HASH_FN(ast_sip_contact_status, name);<br>+<br>+/*! \brief Sort function for contact statuses */<br>+AO2_STRING_FIELD_SORT_FN(ast_sip_contact_status, name);<br>+<br>+/*! \brief Comparator function for contact statuses */<br>+AO2_STRING_FIELD_CMP_FN(ast_sip_contact_status, name);<br>+<br>+/*! \brief Helper function to allocate a contact statuses container */<br>+static struct ao2_container *sip_options_contact_statuses_alloc(void)<br> {<br>- ao2_cleanup(qual_data->endpoint);<br>- ast_free(qual_data);<br>+ /*<br>+ * Replace duplicate objects so we can update the immutable<br>+ * contact status objects by simply linking in a new object.<br>+ */<br>+ return ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_MUTEX,<br>+ AO2_CONTAINER_ALLOC_OPT_DUPS_REPLACE, CONTACT_STATUS_BUCKETS,<br>+ ast_sip_contact_status_hash_fn, ast_sip_contact_status_sort_fn,<br>+ ast_sip_contact_status_cmp_fn);<br>+}<br>+<br>+/*! \brief Function which publishes a contact status update to all interested endpoints */<br>+static void sip_options_publish_contact_state(const struct sip_options_aor *aor_options,<br>+ const struct ast_sip_contact_status *contact_status)<br>+{<br>+ int i;<br>+<br>+ for (i = 0; i < AST_VECTOR_SIZE(&aor_options->compositors); ++i) {<br>+ const struct sip_options_endpoint_state_compositor *endpoint_state_compositor;<br>+<br>+ endpoint_state_compositor = AST_VECTOR_GET(&aor_options->compositors, i);<br>+ ast_sip_persistent_endpoint_publish_contact_state(endpoint_state_compositor->name,<br>+ contact_status);<br>+ }<br> }<br> <br> /*!<br>- * \internal<br>- * \brief For an endpoint iterate over and qualify all aors/contacts<br>+ * \brief Task to notify endpoints of a contact status change<br>+ * \note Run by management_serializer<br> */<br>-static int cli_qualify_contacts(void *data)<br>+static int contact_status_publish_update_task(void *obj)<br> {<br>- char *aors;<br>- char *aor_name;<br>- RAII_VAR(struct qualify_data *, qual_data, data, qualify_data_destroy);<br>- struct ast_sip_endpoint *endpoint = qual_data->endpoint;<br>- int cli_fd = qual_data->cli_fd;<br>- const char *endpoint_name = ast_sorcery_object_get_id(endpoint);<br>+ struct ast_sip_contact_status *contact_status = obj;<br>+ struct sip_options_aor *aor_options;<br> <br>- if (ast_strlen_zero(endpoint->aors)) {<br>- ast_cli(cli_fd, "Endpoint %s has no AoR's configured\n",<br>- endpoint_name);<br>+ aor_options = ao2_find(sip_options_aors, contact_status->aor, OBJ_SEARCH_KEY);<br>+ if (aor_options) {<br>+ sip_options_publish_contact_state(aor_options, contact_status);<br>+ ao2_ref(aor_options, -1);<br>+ }<br>+ ao2_ref(contact_status, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+static void sip_options_contact_status_update(struct ast_sip_contact_status *contact_status)<br>+{<br>+ struct ast_taskprocessor *mgmt_serializer = management_serializer;<br>+<br>+ if (mgmt_serializer) {<br>+ ao2_ref(contact_status, +1);<br>+ if (ast_sip_push_task(mgmt_serializer, contact_status_publish_update_task,<br>+ contact_status)) {<br>+ ao2_ref(contact_status, -1);<br>+ }<br>+ }<br>+}<br>+<br>+struct ast_sip_contact_status *ast_res_pjsip_find_or_create_contact_status(const struct ast_sip_contact *contact)<br>+{<br>+ struct ast_sip_contact_status *contact_status;<br>+ int res;<br>+<br>+ /*<br>+ * At startup a contact status can be retrieved when static contacts<br>+ * are themselves being setup. This happens before we are fully setup.<br>+ * Since we don't actually trigger qualify or anything as a result it<br>+ * is safe to do so. They'll just get back a contact status that will<br>+ * be updated later. At this time they only care that the contact<br>+ * status gets created for the static contact anyway.<br>+ */<br>+ if (!sip_options_contact_statuses) {<br>+ /*<br>+ * We haven't been pre-initialized or we are shutting down.<br>+ * Neither situation should happen.<br>+ */<br>+ ast_assert(0);<br>+ return NULL;<br>+ }<br>+<br>+ ao2_lock(sip_options_contact_statuses);<br>+<br>+ /* If contact status for this contact already exists just return it */<br>+ contact_status = ao2_find(sip_options_contact_statuses,<br>+ ast_sorcery_object_get_id(contact), OBJ_SEARCH_KEY | OBJ_NOLOCK);<br>+ if (contact_status) {<br>+ ao2_unlock(sip_options_contact_statuses);<br>+ return contact_status;<br>+ }<br>+<br>+ /* Otherwise we have to create and store a new contact status */<br>+ contact_status = sip_contact_status_alloc(ast_sorcery_object_get_id(contact));<br>+ if (!contact_status) {<br>+ ao2_unlock(sip_options_contact_statuses);<br>+ return NULL;<br>+ }<br>+<br>+ contact_status->rtt = 0;<br>+ contact_status->status = CREATED;<br>+ contact_status->last_status = CREATED;<br>+ res = ast_string_field_set(contact_status, uri, contact->uri);<br>+ res |= ast_string_field_set(contact_status, aor, contact->aor);<br>+ if (res) {<br>+ ao2_unlock(sip_options_contact_statuses);<br>+ ao2_ref(contact_status, -1);<br>+ return NULL;<br>+ }<br>+<br>+ ao2_link_flags(sip_options_contact_statuses, contact_status, OBJ_NOLOCK);<br>+ ao2_unlock(sip_options_contact_statuses);<br>+<br>+ ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>+ "+1", 1.0, ast_sip_get_contact_status_label(contact_status->status));<br>+<br>+ sip_options_contact_status_update(contact_status);<br>+<br>+ return contact_status;<br>+}<br>+<br>+struct ast_sip_contact_status *ast_sip_get_contact_status(const struct ast_sip_contact *contact)<br>+{<br>+ return ao2_find(sip_options_contact_statuses, ast_sorcery_object_get_id(contact),<br>+ OBJ_SEARCH_KEY);<br>+}<br>+<br>+/*! \brief Hashing function for OPTIONS AORs */<br>+AO2_STRING_FIELD_HASH_FN(sip_options_aor, name);<br>+<br>+/*! \brief Comparator function for SIP OPTIONS AORs */<br>+AO2_STRING_FIELD_CMP_FN(sip_options_aor, name);<br>+<br>+/*! \brief Hashing function for endpoint state compositors */<br>+AO2_STRING_FIELD_HASH_FN(sip_options_endpoint_state_compositor, name);<br>+<br>+/*! \brief Comparator function for endpoint state compositors */<br>+AO2_STRING_FIELD_CMP_FN(sip_options_endpoint_state_compositor, name);<br>+<br>+/*! \brief Structure used to contain information for an OPTIONS callback */<br>+struct sip_options_contact_callback_data {<br>+ /*! \brief The contact we qualified */<br>+ struct ast_sip_contact *contact;<br>+ /*! \brief The AOR options */<br>+ struct sip_options_aor *aor_options;<br>+ /*! \brief The time at which this OPTIONS attempt was started */<br>+ struct timeval rtt_start;<br>+ /*! \brief The new status of the contact */<br>+ enum ast_sip_contact_status_type status;<br>+};<br>+<br>+/*!<br>+ * \brief Return the current state of an endpoint state compositor<br>+ * \pre The endpoint_state_compositor lock must be held.<br>+ */<br>+static enum ast_endpoint_state sip_options_get_endpoint_state_compositor_state(<br>+ const struct sip_options_endpoint_state_compositor *endpoint_state_compositor)<br>+{<br>+ struct ao2_iterator it_aor_statuses;<br>+ struct sip_options_endpoint_aor_status *aor_status;<br>+ enum ast_endpoint_state state = AST_ENDPOINT_OFFLINE;<br>+<br>+ it_aor_statuses = ao2_iterator_init(endpoint_state_compositor->aor_statuses, 0);<br>+ for (; (aor_status = ao2_iterator_next(&it_aor_statuses)); ao2_ref(aor_status, -1)) {<br>+ if (aor_status->available) {<br>+ state = AST_ENDPOINT_ONLINE;<br>+ break;<br>+ }<br>+ }<br>+ ao2_iterator_destroy(&it_aor_statuses);<br>+<br>+ return state;<br>+}<br>+<br>+/*!<br>+ * \brief Update the AOR status on an endpoint state compositor<br>+ * \pre The endpoint_state_compositor lock must be held.<br>+ */<br>+static void sip_options_update_endpoint_state_compositor_aor(struct sip_options_endpoint_state_compositor *endpoint_state_compositor,<br>+ const char *name, enum ast_sip_contact_status_type status)<br>+{<br>+ struct sip_options_endpoint_aor_status *aor_status;<br>+ enum ast_endpoint_state endpoint_state;<br>+<br>+ aor_status = ao2_find(endpoint_state_compositor->aor_statuses, name,<br>+ OBJ_SEARCH_KEY | OBJ_NOLOCK);<br>+ if (!aor_status) {<br>+ /* The AOR status doesn't exist already so we don't need to go any further */<br>+ if (status == REMOVED) {<br>+ return;<br>+ }<br>+<br>+ aor_status = ao2_alloc_options(sizeof(*aor_status) + strlen(name) + 1, NULL,<br>+ AO2_ALLOC_OPT_LOCK_NOLOCK);<br>+ if (!aor_status) {<br>+ return;<br>+ }<br>+<br>+ strcpy(aor_status->name, name); /* SAFE */<br>+ ao2_link(endpoint_state_compositor->aor_statuses, aor_status);<br>+ }<br>+<br>+ if (status == REMOVED) {<br>+ /*<br>+ * If the AOR is being removed then remove its AOR status<br>+ * from the endpoint compositor.<br>+ */<br>+ ao2_unlink(endpoint_state_compositor->aor_statuses, aor_status);<br>+ } else {<br>+ aor_status->available = (status == AVAILABLE ? 1 : 0);<br>+ }<br>+ ao2_ref(aor_status, -1);<br>+<br>+ if (!endpoint_state_compositor->active) {<br>+ return;<br>+ }<br>+<br>+ /* If this AOR is available then the endpoint itself has to be online */<br>+ if (status == AVAILABLE) {<br>+ ast_debug(3, "Endpoint state compositor '%s' is online as AOR '%s' is available\n",<br>+ endpoint_state_compositor->name, name);<br>+ endpoint_state = AST_ENDPOINT_ONLINE;<br>+ } else {<br>+ endpoint_state =<br>+ sip_options_get_endpoint_state_compositor_state(endpoint_state_compositor);<br>+ }<br>+<br>+ ast_sip_persistent_endpoint_update_state(endpoint_state_compositor->name,<br>+ endpoint_state);<br>+}<br>+<br>+/*! \brief Function which notifies endpoint state compositors of a state change of an AOR */<br>+static void sip_options_notify_endpoint_state_compositors(struct sip_options_aor *aor_options,<br>+ enum ast_sip_contact_status_type status)<br>+{<br>+ int i;<br>+<br>+ /* Iterate through the associated endpoint state compositors updating them */<br>+ for (i = 0; i < AST_VECTOR_SIZE(&aor_options->compositors); ++i) {<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor;<br>+<br>+ endpoint_state_compositor = AST_VECTOR_GET(&aor_options->compositors, i);<br>+<br>+ ao2_lock(endpoint_state_compositor);<br>+ sip_options_update_endpoint_state_compositor_aor(endpoint_state_compositor,<br>+ aor_options->name, status);<br>+ ao2_unlock(endpoint_state_compositor);<br>+ }<br>+<br>+ if (status == REMOVED) {<br>+ AST_VECTOR_RESET(&aor_options->compositors, ao2_cleanup);<br>+ }<br>+}<br>+<br>+/*!<br>+ * \brief Task to notify an AOR of a contact status change<br>+ * \note Run by aor_options->serializer<br>+ */<br>+static int sip_options_contact_status_notify_task(void *obj)<br>+{<br>+ struct sip_options_contact_callback_data *contact_callback_data = obj;<br>+ struct ast_sip_contact *contact;<br>+ struct ast_sip_contact_status *cs_old;<br>+ struct ast_sip_contact_status *cs_new;<br>+<br>+ /*<br>+ * Determine if this is a late arriving notification, as it is<br>+ * possible that we get a callback from PJSIP giving us contact<br>+ * status but in the mean time said contact has been removed<br>+ * from the controlling AOR.<br>+ */<br>+<br>+ if (!contact_callback_data->aor_options->qualify_frequency) {<br>+ /* Contact qualify response is late */<br>+ ao2_ref(contact_callback_data, -1);<br> return 0;<br> }<br> <br>- aors = ast_strdupa(endpoint->aors);<br>- while ((aor_name = ast_strip(strsep(&aors, ",")))) {<br>- struct ast_sip_aor *aor;<br>+ contact = ao2_find(contact_callback_data->aor_options->contacts,<br>+ contact_callback_data->contact, OBJ_SEARCH_OBJECT);<br>+ if (!contact) {<br>+ /* Contact qualify response is late */<br>+ ao2_ref(contact_callback_data, -1);<br>+ return 0;<br>+ }<br>+ ao2_ref(contact, -1);<br>+<br>+ cs_old = ao2_find(sip_options_contact_statuses,<br>+ ast_sorcery_object_get_id(contact_callback_data->contact), OBJ_SEARCH_KEY);<br>+ if (!cs_old) {<br>+ /* Contact qualify response is late */<br>+ ao2_ref(contact_callback_data, -1);<br>+ return 0;<br>+ }<br>+<br>+ /* Update the contact specific status information */<br>+ cs_new = sip_contact_status_copy(cs_old);<br>+ ao2_ref(cs_old, -1);<br>+ if (!cs_new) {<br>+ ao2_ref(contact_callback_data, -1);<br>+ return 0;<br>+ }<br>+ cs_new->last_status = cs_new->status;<br>+ cs_new->status = contact_callback_data->status;<br>+ cs_new->rtt =<br>+ cs_new->status == AVAILABLE<br>+ ? ast_tvdiff_us(ast_tvnow(), contact_callback_data->rtt_start)<br>+ : 0;<br>+ ao2_link(sip_options_contact_statuses, cs_new);<br>+<br>+ /*<br>+ * If the status has changed then notify the endpoint state compositors<br>+ * and publish our events.<br>+ */<br>+ if (cs_new->last_status != cs_new->status) {<br>+ if (cs_new->status == AVAILABLE) {<br>+ /* If this is the first available contact then the AOR has become available */<br>+ ++contact_callback_data->aor_options->available;<br>+ if (contact_callback_data->aor_options->available == 1) {<br>+ sip_options_notify_endpoint_state_compositors(<br>+ contact_callback_data->aor_options, AVAILABLE);<br>+ }<br>+ } else if (cs_new->last_status == AVAILABLE) {<br>+ ast_assert(cs_new->status == UNAVAILABLE);<br>+<br>+ /* If there are no more available contacts then this AOR is unavailable */<br>+ --contact_callback_data->aor_options->available;<br>+ if (!contact_callback_data->aor_options->available) {<br>+ sip_options_notify_endpoint_state_compositors(<br>+ contact_callback_data->aor_options, UNAVAILABLE);<br>+ }<br>+ }<br>+<br>+ ast_verb(3, "Contact %s/%s is now %s. RTT: %.3f msec\n",<br>+ cs_new->aor,<br>+ cs_new->uri,<br>+ ast_sip_get_contact_status_label(cs_new->status),<br>+ cs_new->rtt / 1000.0);<br>+<br>+ ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>+ "-1", 1.0, ast_sip_get_contact_status_label(cs_new->last_status));<br>+ ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>+ "+1", 1.0, ast_sip_get_contact_status_label(cs_new->status));<br>+<br>+ sip_options_contact_status_update(cs_new);<br>+<br>+ ast_test_suite_event_notify("AOR_CONTACT_UPDATE",<br>+ "Contact: %s\r\n"<br>+ "Status: %s",<br>+ cs_new->name,<br>+ ast_sip_get_contact_status_label(cs_new->status));<br>+ } else {<br>+ ast_debug(3, "Contact %s/%s status didn't change: %s, RTT: %.3f msec\n",<br>+ cs_new->aor,<br>+ cs_new->uri,<br>+ ast_sip_get_contact_status_label(cs_new->status),<br>+ cs_new->rtt / 1000.0);<br>+ }<br>+<br>+ ast_statsd_log_full_va("PJSIP.contacts.%s.rtt", AST_STATSD_TIMER,<br>+ cs_new->status != AVAILABLE ? -1 : cs_new->rtt / 1000,<br>+ 1.0,<br>+ cs_new->name);<br>+<br>+ ast_test_suite_event_notify("AOR_CONTACT_QUALIFY_RESULT",<br>+ "Contact: %s\r\n"<br>+ "Status: %s\r\n"<br>+ "RTT: %" PRId64,<br>+ cs_new->name,<br>+ ast_sip_get_contact_status_label(cs_new->status),<br>+ cs_new->rtt);<br>+<br>+ ast_debug(3, "AOR '%s' now has %d available contacts\n",<br>+ contact_callback_data->aor_options->name,<br>+ contact_callback_data->aor_options->available);<br>+<br>+ ao2_ref(cs_new, -1);<br>+ ao2_ref(contact_callback_data, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Callback for when we get a result from a SIP OPTIONS request (a response or a timeout) */<br>+static void qualify_contact_cb(void *token, pjsip_event *e)<br>+{<br>+ struct sip_options_contact_callback_data *contact_callback_data = token;<br>+ enum ast_sip_contact_status_type status;<br>+<br>+ switch(e->body.tsx_state.type) {<br>+ default:<br>+ ast_log(LOG_ERROR, "Unexpected PJSIP event %u\n", e->body.tsx_state.type);<br>+ /* Fall through */<br>+ case PJSIP_EVENT_TRANSPORT_ERROR:<br>+ case PJSIP_EVENT_TIMER:<br>+ status = UNAVAILABLE;<br>+ break;<br>+ case PJSIP_EVENT_RX_MSG:<br>+ status = AVAILABLE;<br>+ break;<br>+ }<br>+<br>+ /* Update the callback data with the new status, this will get handled in the AOR serializer */<br>+ contact_callback_data->status = status;<br>+<br>+ if (ast_sip_push_task(contact_callback_data->aor_options->serializer,<br>+ sip_options_contact_status_notify_task, contact_callback_data)) {<br>+ ast_log(LOG_NOTICE, "Unable to queue contact status update for '%s' on AOR '%s', state will be incorrect\n",<br>+ ast_sorcery_object_get_id(contact_callback_data->contact),<br>+ contact_callback_data->aor_options->name);<br>+ ao2_ref(contact_callback_data, -1);<br>+ }<br>+<br>+ /* The task inherited our reference so we don't unreference here */<br>+}<br>+<br>+/*! \brief Destructor for contact callback data */<br>+static void sip_options_contact_callback_data_dtor(void *obj)<br>+{<br>+ struct sip_options_contact_callback_data *contact_callback_data = obj;<br>+<br>+ ao2_cleanup(contact_callback_data->contact);<br>+ ao2_cleanup(contact_callback_data->aor_options);<br>+}<br>+<br>+/*! \brief Contact callback data allocator */<br>+static struct sip_options_contact_callback_data *sip_options_contact_callback_data_alloc(<br>+ struct ast_sip_contact *contact, struct sip_options_aor *aor_options)<br>+{<br>+ struct sip_options_contact_callback_data *contact_callback_data;<br>+<br>+ contact_callback_data = ao2_alloc_options(sizeof(*contact_callback_data),<br>+ sip_options_contact_callback_data_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK);<br>+ if (!contact_callback_data) {<br>+ return NULL;<br>+ }<br>+<br>+ contact_callback_data->contact = ao2_bump(contact);<br>+ contact_callback_data->aor_options = ao2_bump(aor_options);<br>+ contact_callback_data->rtt_start = ast_tvnow();<br>+<br>+ return contact_callback_data;<br>+}<br>+<br>+/*! \brief Send a SIP OPTIONS request for a contact */<br>+static int sip_options_qualify_contact(void *obj, void *arg, int flags)<br>+{<br>+ struct ast_sip_contact *contact = obj;<br>+ struct sip_options_aor *aor_options = arg;<br>+ RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);<br>+ pjsip_tx_data *tdata;<br>+ struct ast_sip_contact_status *contact_status;<br>+ struct sip_options_contact_callback_data *contact_callback_data;<br>+<br>+ ast_debug(3, "Qualifying contact '%s' on AOR '%s'\n",<br>+ ast_sorcery_object_get_id(contact), aor_options->name);<br>+<br>+ if (!ast_strlen_zero(contact->endpoint_name)) {<br>+ endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint",<br>+ contact->endpoint_name);<br>+ }<br>+ if (!endpoint && AST_VECTOR_SIZE(&aor_options->compositors)) {<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor;<br>+<br>+ endpoint_state_compositor = AST_VECTOR_GET(&aor_options->compositors, 0);<br>+ endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint",<br>+ endpoint_state_compositor->name);<br>+ }<br>+ if (!endpoint) {<br>+ ast_debug(3, "Could not find an endpoint to qualify contact '%s' on AOR '%s'\n",<br>+ ast_sorcery_object_get_id(contact), aor_options->name);<br>+ return 0;<br>+ }<br>+<br>+ if (ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, contact, &tdata)) {<br>+ ast_log(LOG_ERROR, "Unable to create request to qualify contact %s on AOR %s\n",<br>+ contact->uri, aor_options->name);<br>+ return 0;<br>+ }<br>+<br>+ /* If an outbound proxy is specified set it on this request */<br>+ if (!ast_strlen_zero(contact->outbound_proxy) &&<br>+ ast_sip_set_outbound_proxy(tdata, contact->outbound_proxy)) {<br>+ ast_log(LOG_ERROR, "Unable to apply outbound proxy on request to qualify contact %s\n",<br>+ contact->uri);<br>+ pjsip_tx_data_dec_ref(tdata);<br>+ return 0;<br>+ }<br>+<br>+ contact_status = ast_res_pjsip_find_or_create_contact_status(contact);<br>+ if (!contact_status) {<br>+ ast_log(LOG_ERROR, "Unable to retrieve contact status information for contact %s on AOR %s\n",<br>+ contact->uri, aor_options->name);<br>+ pjsip_tx_data_dec_ref(tdata);<br>+ return 0;<br>+ }<br>+ ao2_ref(contact_status, -1);<br>+<br>+ contact_callback_data = sip_options_contact_callback_data_alloc(contact, aor_options);<br>+ if (!contact_callback_data) {<br>+ ast_log(LOG_ERROR, "Unable to create object to contain callback data for contact %s on AOR %s\n",<br>+ contact->uri, aor_options->name);<br>+ pjsip_tx_data_dec_ref(tdata);<br>+ return 0;<br>+ }<br>+<br>+ if (ast_sip_send_out_of_dialog_request(tdata, endpoint,<br>+ (int)(aor_options->qualify_timeout * 1000), contact_callback_data,<br>+ qualify_contact_cb)) {<br>+ ast_log(LOG_ERROR, "Unable to send request to qualify contact %s on AOR %s\n",<br>+ contact->uri, aor_options->name);<br>+ ao2_ref(contact_callback_data, -1);<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+/*!<br>+ * \brief Task to qualify contacts of an AOR<br>+ * \note Run by aor_options->serializer<br>+ */<br>+static int sip_options_qualify_aor(void *obj)<br>+{<br>+ struct sip_options_aor *aor_options = obj;<br>+<br>+ ast_debug(3, "Qualifying all contacts on AOR '%s'\n", aor_options->name);<br>+<br>+ /* Attempt to send an OPTIONS request to every contact on this AOR */<br>+ ao2_callback(aor_options->contacts, OBJ_NODATA, sip_options_qualify_contact,<br>+ (struct sip_options_aor *) aor_options);<br>+<br>+ /* Always reschedule to the frequency we should go */<br>+ return aor_options->qualify_frequency * 1000;<br>+}<br>+<br>+/*! \brief Forward declaration of this helpful function */<br>+static int sip_options_remove_contact(void *obj, void *arg, int flags);<br>+<br>+/*! \brief Destructor function for SIP OPTIONS AORs */<br>+static void sip_options_aor_dtor(void *obj)<br>+{<br>+ struct sip_options_aor *aor_options = obj;<br>+<br>+ /*<br>+ * Any contacts are unreachable since the AOR is being destroyed<br>+ * so remove their contact status<br>+ */<br>+ if (aor_options->contacts) {<br>+ ao2_callback(aor_options->contacts, OBJ_NODATA | OBJ_UNLINK,<br>+ sip_options_remove_contact, aor_options);<br>+ ao2_ref(aor_options->contacts, -1);<br>+ }<br>+ ao2_cleanup(aor_options->dynamic_contacts);<br>+<br>+ ast_taskprocessor_unreference(aor_options->serializer);<br>+<br>+ ast_assert(AST_VECTOR_SIZE(&aor_options->compositors) == 0);<br>+ AST_VECTOR_FREE(&aor_options->compositors);<br>+}<br>+<br>+/*! \brief Allocator for AOR OPTIONS */<br>+static struct sip_options_aor *sip_options_aor_alloc(struct ast_sip_aor *aor)<br>+{<br>+ struct sip_options_aor *aor_options;<br>+ char tps_name[AST_TASKPROCESSOR_MAX_NAME + 1];<br>+<br>+ aor_options = ao2_alloc_options(sizeof(*aor_options) + strlen(ast_sorcery_object_get_id(aor)) + 1,<br>+ sip_options_aor_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK);<br>+ if (!aor_options) {<br>+ return NULL;<br>+ }<br>+<br>+ strcpy(aor_options->name, ast_sorcery_object_get_id(aor)); /* SAFE */<br>+<br>+ ast_taskprocessor_build_name(tps_name, sizeof(tps_name), "pjsip/options/%s",<br>+ ast_sorcery_object_get_id(aor));<br>+ aor_options->serializer = ast_sip_create_serializer_group(tps_name,<br>+ shutdown_group);<br>+ if (!aor_options->serializer) {<br>+ ao2_ref(aor_options, -1);<br>+ return NULL;<br>+ }<br>+<br>+ if (AST_VECTOR_INIT(&aor_options->compositors, ENDPOINT_STATE_COMPOSITOR_INITIAL_SIZE)) {<br>+ ao2_ref(aor_options, -1);<br>+ return NULL;<br>+ }<br>+<br>+ aor_options->contacts = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_NOLOCK,<br>+ AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, CONTACT_BUCKETS, ast_sorcery_object_id_hash,<br>+ ast_sorcery_object_id_sort, ast_sorcery_object_id_compare);<br>+ if (!aor_options->contacts) {<br>+ ao2_ref(aor_options, -1);<br>+ return NULL;<br>+ }<br>+<br>+ aor_options->dynamic_contacts = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_NOLOCK,<br>+ AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, CONTACT_BUCKETS, ast_sorcery_object_id_hash,<br>+ ast_sorcery_object_id_sort, ast_sorcery_object_id_compare);<br>+ if (!aor_options->dynamic_contacts) {<br>+ ao2_ref(aor_options, -1);<br>+ return NULL;<br>+ }<br>+<br>+ return aor_options;<br>+}<br>+<br>+/*! \brief Remove contact status for a hint */<br>+static void sip_options_remove_contact_status(struct sip_options_aor *aor_options,<br>+ struct ast_sip_contact *contact)<br>+{<br>+ struct ast_sip_contact_status *cs_new;<br>+ struct ast_sip_contact_status *cs_old;<br>+<br>+ cs_old = ao2_find(sip_options_contact_statuses, ast_sorcery_object_get_id(contact),<br>+ OBJ_SEARCH_KEY | OBJ_UNLINK);<br>+ if (!cs_old) {<br>+ ast_debug(3, "Attempted to remove contact status for '%s' but it does not exist\n",<br>+ ast_sorcery_object_get_id(contact));<br>+ return;<br>+ }<br>+<br>+ ast_verb(2, "Contact %s/%s has been deleted\n", contact->aor, contact->uri);<br>+<br>+ /* Update the contact status to reflect its new state */<br>+ cs_new = sip_contact_status_copy(cs_old);<br>+ if (!cs_new) {<br>+ /*<br>+ * We'll have to violate the immutable property because we<br>+ * couldn't create a new one to modify and we are deleting<br>+ * the contact status anyway.<br>+ */<br>+ cs_new = cs_old;<br>+ } else {<br>+ ao2_ref(cs_old, -1);<br>+ }<br>+ cs_new->last_status = cs_new->status;<br>+ cs_new->status = REMOVED;<br>+ cs_new->rtt = 0;<br>+<br>+ ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>+ "-1", 1.0, ast_sip_get_contact_status_label(cs_new->last_status));<br>+ ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>+ "+1", 1.0, ast_sip_get_contact_status_label(cs_new->status));<br>+<br>+ sip_options_contact_status_update(cs_new);<br>+<br>+ /*<br>+ * The only time we need to update the AOR is if this contact was<br>+ * available and qualify is in use, otherwise we can just stop<br>+ * early.<br>+ */<br>+ if (!aor_options->qualify_frequency || cs_new->last_status != AVAILABLE) {<br>+ ao2_ref(cs_new, -1);<br>+ return;<br>+ }<br>+<br>+ --aor_options->available;<br>+ if (!aor_options->available) {<br>+ sip_options_notify_endpoint_state_compositors(aor_options, UNAVAILABLE);<br>+ }<br>+<br>+ ast_debug(3, "AOR '%s' now has %d available contacts\n", aor_options->name,<br>+ aor_options->available);<br>+<br>+ ao2_ref(cs_new, -1);<br>+}<br>+<br>+/*! \brief Task data for AOR creation or updating */<br>+struct sip_options_synchronize_aor_task_data {<br>+ /*! \brief The AOR options for this AOR */<br>+ struct sip_options_aor *aor_options;<br>+ /*! \brief The AOR which contains the new configuraton */<br>+ struct ast_sip_aor *aor;<br>+ /*! \brief Optional container of existing AOR s*/<br>+ struct ao2_container *existing;<br>+ /*! \brief Whether this AOR is being added */<br>+ int added;<br>+};<br>+<br>+/*! \brief Callback function to remove a contact and its contact status from an AOR */<br>+static int sip_options_remove_contact(void *obj, void *arg, int flags)<br>+{<br>+ struct ast_sip_contact *contact = obj;<br>+ struct sip_options_aor *aor_options = arg;<br>+<br>+ sip_options_remove_contact_status(aor_options, contact);<br>+<br>+ return CMP_MATCH;<br>+}<br>+<br>+/*! \brief Determine an initial time for scheduling AOR qualifying */<br>+static int sip_options_determine_initial_qualify_time(int qualify_frequency)<br>+{<br>+ int initial_interval;<br>+ int max_time = ast_sip_get_max_initial_qualify_time();<br>+<br>+ if (max_time && max_time < qualify_frequency) {<br>+ initial_interval = max_time;<br>+ } else {<br>+ initial_interval = qualify_frequency;<br>+ }<br>+<br>+ initial_interval = (int)((initial_interval * 1000) * ast_random_double());<br>+ return 0 < initial_interval ? initial_interval : 1;<br>+}<br>+<br>+/*! \brief Set the contact status for a contact */<br>+static void sip_options_set_contact_status(struct ast_sip_contact_status *contact_status,<br>+ enum ast_sip_contact_status_type status)<br>+{<br>+ struct ast_sip_contact_status *cs_new;<br>+<br>+ /* Update the contact specific status information */<br>+ cs_new = sip_contact_status_copy(contact_status);<br>+ if (!cs_new) {<br>+ return;<br>+ }<br>+ cs_new->last_status = cs_new->status;<br>+ cs_new->status = status;<br>+<br>+ /*<br>+ * We need to always set the RTT to zero because we haven't completed<br>+ * an OPTIONS ping so RTT is unknown. If the OPTIONS ping were still<br>+ * running it will be refreshed on the next go round anyway.<br>+ */<br>+ cs_new->rtt = 0;<br>+<br>+ ao2_link(sip_options_contact_statuses, cs_new);<br>+<br>+ if (cs_new->status != cs_new->last_status) {<br>+ ast_verb(3, "Contact %s/%s is now %s.\n",<br>+ cs_new->aor, cs_new->uri,<br>+ ast_sip_get_contact_status_label(cs_new->status));<br>+<br>+ ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>+ "-1", 1.0, ast_sip_get_contact_status_label(cs_new->last_status));<br>+ ast_statsd_log_string_va("PJSIP.contacts.states.%s", AST_STATSD_GAUGE,<br>+ "+1", 1.0, ast_sip_get_contact_status_label(cs_new->status));<br>+<br>+ sip_options_contact_status_update(cs_new);<br>+<br>+ ast_test_suite_event_notify("AOR_CONTACT_UPDATE",<br>+ "Contact: %s\r\n"<br>+ "Status: %s",<br>+ cs_new->name,<br>+ ast_sip_get_contact_status_label(cs_new->status));<br>+ }<br>+ ao2_ref(cs_new, -1);<br>+}<br>+<br>+/*! \brief Transition the contact status to unqualified mode */<br>+static int sip_options_set_contact_status_unqualified(void *obj, void *arg, int flags)<br>+{<br>+ struct ast_sip_contact *contact = obj;<br>+ struct ast_sip_contact_status *contact_status;<br>+<br>+ contact_status = ast_res_pjsip_find_or_create_contact_status(contact);<br>+ if (!contact_status) {<br>+ return 0;<br>+ }<br>+<br>+ switch (contact_status->status) {<br>+ case AVAILABLE:<br>+ case UNAVAILABLE:<br>+ case UNKNOWN:<br>+ sip_options_set_contact_status(contact_status, CREATED);<br>+ break;<br>+ case CREATED:<br>+ case REMOVED:<br>+ break;<br>+ }<br>+<br>+ ao2_ref(contact_status, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Transition the contact status to qualified mode */<br>+static int sip_options_set_contact_status_qualified(void *obj, void *arg, int flags)<br>+{<br>+ struct ast_sip_contact *contact = obj;<br>+ struct ast_sip_contact_status *contact_status;<br>+<br>+ contact_status = ast_res_pjsip_find_or_create_contact_status(contact);<br>+ if (!contact_status) {<br>+ return 0;<br>+ }<br>+<br>+ switch (contact_status->status) {<br>+ case AVAILABLE:<br>+ sip_options_set_contact_status(contact_status, UNAVAILABLE);<br>+ break;<br>+ case UNAVAILABLE:<br>+ case UNKNOWN:<br>+ case CREATED:<br>+ case REMOVED:<br>+ break;<br>+ }<br>+<br>+ ao2_ref(contact_status, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Count AVAILABLE qualified contacts. */<br>+static int sip_options_contact_status_available_count(void *obj, void *arg, int flags)<br>+{<br>+ struct ast_sip_contact *contact = obj;<br>+ unsigned int *available = arg;<br>+ struct ast_sip_contact_status *contact_status;<br>+<br>+ contact_status = ast_res_pjsip_find_or_create_contact_status(contact);<br>+ if (!contact_status) {<br>+ return 0;<br>+ }<br>+<br>+ /* Count qualified available contacts. */<br>+ switch (contact_status->status) {<br>+ case AVAILABLE:<br>+ ++*available;<br>+ break;<br>+ case UNAVAILABLE:<br>+ case UNKNOWN:<br>+ case CREATED:<br>+ case REMOVED:<br>+ break;<br>+ }<br>+<br>+ ao2_ref(contact_status, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+/*!<br>+ * \brief Function which applies configuration to an AOR options structure<br>+ * \note Run by aor_options->serializer (or management_serializer on aor_options creation)<br>+ */<br>+static void sip_options_apply_aor_configuration(struct sip_options_aor *aor_options,<br>+ struct ast_sip_aor *aor, int is_new)<br>+{<br>+ struct ao2_container *existing_contacts;<br>+ struct ast_sip_contact *contact;<br>+ struct ao2_iterator iter;<br>+<br>+ ast_debug(3, "Configuring AOR '%s' with current state of configuration and world\n",<br>+ aor_options->name);<br>+<br>+ /*<br>+ * Permanent contacts, since we receive no notification that they<br>+ * are gone, follow the same approach as AORs. We create a copy<br>+ * of the existing container and any reused contacts are removed<br>+ * from it. Any contacts remaining in the container after<br>+ * processing no longer exist so we need to remove their state.<br>+ */<br>+ existing_contacts = ao2_container_clone(aor_options->contacts, 0);<br>+ if (!existing_contacts) {<br>+ ast_log(LOG_WARNING, "Synchronization of AOR '%s' failed for qualify, retaining existing state\n",<br>+ aor_options->name);<br>+ return;<br>+ }<br>+<br>+ ao2_callback(aor_options->contacts, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE,<br>+ NULL, NULL);<br>+<br>+ /* Process permanent contacts */<br>+ if (aor->permanent_contacts) {<br>+ iter = ao2_iterator_init(aor->permanent_contacts, 0);<br>+ for (; (contact = ao2_iterator_next(&iter)); ao2_ref(contact, -1)) {<br>+ ao2_find(existing_contacts, ast_sorcery_object_get_id(contact),<br>+ OBJ_NODATA | OBJ_UNLINK | OBJ_SEARCH_KEY);<br>+ ao2_link(aor_options->contacts, contact);<br>+ }<br>+ ao2_iterator_destroy(&iter);<br>+ }<br>+<br>+ /*<br>+ * If this is newly added we need to see if there are any<br>+ * existing dynamic contacts to add. Ones that are added<br>+ * after creation will occur as a result of the contact<br>+ * observer creation callback.<br>+ */<br>+ if (is_new) {<br>+ size_t prefix_len = strlen(ast_sorcery_object_get_id(aor)) + sizeof(";@") - 1;<br>+ char prefix[prefix_len + 1];<br> struct ao2_container *contacts;<br> <br>- aor = ast_sip_location_retrieve_aor(aor_name);<br>- if (!aor) {<br>+ sprintf(prefix, "%s;@", ast_sorcery_object_get_id(aor)); /* Safe */<br>+ contacts = ast_sorcery_retrieve_by_prefix(ast_sip_get_sorcery(), "contact",<br>+ prefix, prefix_len);<br>+ if (contacts) {<br>+ ao2_container_dup(aor_options->dynamic_contacts, contacts, 0);<br>+ ao2_ref(contacts, -1);<br>+ }<br>+ }<br>+<br>+ /* Process dynamic contacts */<br>+ iter = ao2_iterator_init(aor_options->dynamic_contacts, 0);<br>+ for (; (contact = ao2_iterator_next(&iter)); ao2_ref(contact, -1)) {<br>+ ao2_find(existing_contacts, ast_sorcery_object_get_id(contact),<br>+ OBJ_NODATA | OBJ_UNLINK | OBJ_SEARCH_KEY);<br>+ ao2_link(aor_options->contacts, contact);<br>+ }<br>+ ao2_iterator_destroy(&iter);<br>+<br>+ /* Any contacts left no longer exist, so raise events and make them disappear */<br>+ ao2_callback(existing_contacts, OBJ_NODATA | OBJ_UNLINK,<br>+ sip_options_remove_contact, aor_options);<br>+ ao2_ref(existing_contacts, -1);<br>+<br>+ /*<br>+ * Update the available count if we transition between qualified<br>+ * and unqualified. In the qualified case we need to start with<br>+ * 0 available as the qualify process will take care of it. In<br>+ * the unqualified case it is based on the number of contacts<br>+ * present.<br>+ */<br>+ if (!aor->qualify_frequency) {<br>+ ao2_callback(aor_options->contacts, OBJ_NODATA,<br>+ sip_options_set_contact_status_unqualified, NULL);<br>+ aor_options->available = ao2_container_count(aor_options->contacts);<br>+ ast_debug(3, "AOR '%s' is unqualified, number of available contacts is therefore '%d'\n",<br>+ aor_options->name, aor_options->available);<br>+ } else if (!aor_options->qualify_frequency) {<br>+ ao2_callback(aor_options->contacts, OBJ_NODATA,<br>+ sip_options_set_contact_status_qualified, NULL);<br>+ aor_options->available = 0;<br>+ ast_debug(3, "AOR '%s' has transitioned from unqualified to qualified, reset available contacts to 0\n",<br>+ aor_options->name);<br>+ } else {<br>+ /*<br>+ * Count the number of AVAILABLE qualified contacts to ensure<br>+ * the count is in sync with reality.<br>+ */<br>+ aor_options->available = 0;<br>+ ao2_callback(aor_options->contacts, OBJ_NODATA,<br>+ sip_options_contact_status_available_count, &aor_options->available);<br>+ }<br>+<br>+ aor_options->authenticate_qualify = aor->authenticate_qualify;<br>+ aor_options->qualify_timeout = aor->qualify_timeout;<br>+<br>+ /*<br>+ * If we need to stop or start the scheduled callback then do so.<br>+ * This occurs due to the following:<br>+ * 1. The qualify frequency has changed<br>+ * 2. Contacts were added when previously there were none<br>+ * 3. There are no contacts but previously there were some<br>+ */<br>+ if (aor_options->qualify_frequency != aor->qualify_frequency<br>+ || (!aor_options->sched_task && ao2_container_count(aor_options->contacts))<br>+ || (aor_options->sched_task && !ao2_container_count(aor_options->contacts))) {<br>+ if (aor_options->sched_task) {<br>+ ast_sip_sched_task_cancel(aor_options->sched_task);<br>+ ao2_ref(aor_options->sched_task, -1);<br>+ aor_options->sched_task = NULL;<br>+ }<br>+<br>+ /* If there is still a qualify frequency then schedule this */<br>+ aor_options->qualify_frequency = aor->qualify_frequency;<br>+ if (aor_options->qualify_frequency<br>+ && ao2_container_count(aor_options->contacts)) {<br>+ aor_options->sched_task = ast_sip_schedule_task(aor_options->serializer,<br>+ sip_options_determine_initial_qualify_time(aor_options->qualify_frequency),<br>+ sip_options_qualify_aor, ast_taskprocessor_name(aor_options->serializer),<br>+ aor_options, AST_SIP_SCHED_TASK_VARIABLE | AST_SIP_SCHED_TASK_DATA_AO2);<br>+ if (!aor_options->sched_task) {<br>+ ast_log(LOG_ERROR, "Unable to schedule qualify for contacts of AOR '%s'\n",<br>+ aor_options->name);<br>+ }<br>+ }<br>+ }<br>+<br>+ ast_debug(3, "AOR '%s' now has %d available contacts\n", aor_options->name,<br>+ aor_options->available);<br>+}<br>+<br>+/*!<br>+ * \brief Task to synchronize an AOR with our local state<br>+ * \note Run by aor_options->serializer (or management_serializer on aor_options creation)<br>+ */<br>+static int sip_options_synchronize_aor_task(void *obj)<br>+{<br>+ struct sip_options_synchronize_aor_task_data *task_data = obj;<br>+ int i;<br>+<br>+ ast_debug(3, "Synchronizing AOR '%s' with current state of configuration and world\n",<br>+ task_data->aor_options->name);<br>+<br>+ sip_options_apply_aor_configuration(task_data->aor_options, task_data->aor,<br>+ task_data->added);<br>+<br>+ /*<br>+ * Endpoint state compositors are removed in this operation but not<br>+ * added. To reduce the amount of work done they are done later. In<br>+ * the mean time things can still qualify and once an endpoint state<br>+ * compositor is added to the AOR it will be updated with the current<br>+ * state.<br>+ */<br>+ for (i = 0; i < AST_VECTOR_SIZE(&task_data->aor_options->compositors); ++i) {<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor;<br>+<br>+ endpoint_state_compositor = AST_VECTOR_GET(&task_data->aor_options->compositors, i);<br>+<br>+ ao2_lock(endpoint_state_compositor);<br>+ endpoint_state_compositor->active = 0;<br>+ sip_options_update_endpoint_state_compositor_aor(endpoint_state_compositor,<br>+ task_data->aor_options->name, REMOVED);<br>+ ao2_unlock(endpoint_state_compositor);<br>+ }<br>+ AST_VECTOR_RESET(&task_data->aor_options->compositors, ao2_cleanup);<br>+<br>+ return 0;<br>+}<br>+<br>+/*!<br>+ * \brief Synchronize an AOR with our local state<br>+ * \note Run by management_serializer<br>+ */<br>+static int sip_options_synchronize_aor(void *obj, void *arg, int flags)<br>+{<br>+ struct sip_options_synchronize_aor_task_data task_data = {<br>+ .aor = obj,<br>+ .existing = arg,<br>+ };<br>+<br>+ task_data.aor_options = ao2_find(sip_options_aors,<br>+ ast_sorcery_object_get_id(task_data.aor), OBJ_SEARCH_KEY);<br>+ if (!task_data.aor_options) {<br>+ task_data.aor_options = sip_options_aor_alloc(task_data.aor);<br>+ if (!task_data.aor_options) {<br>+ return 0;<br>+ }<br>+<br>+ task_data.added = 1;<br>+<br>+ /* Nothing is aware of this AOR yet so we can just update it in this thread */<br>+ sip_options_synchronize_aor_task(&task_data);<br>+ ao2_link(sip_options_aors, task_data.aor_options);<br>+ } else {<br>+ /* This AOR already exists so we have to do manipulation in its serializer */<br>+ ast_sip_push_task_wait_serializer(task_data.aor_options->serializer,<br>+ sip_options_synchronize_aor_task, &task_data);<br>+ }<br>+<br>+ ao2_ref(task_data.aor_options, -1);<br>+<br>+ if (task_data.existing) {<br>+ ao2_find(task_data.existing, ast_sorcery_object_get_id(task_data.aor),<br>+ OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NODATA);<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Destructor for endpoint state compositors */<br>+static void sip_options_endpoint_state_compositor_dtor(void *obj)<br>+{<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor = obj;<br>+<br>+ ao2_cleanup(endpoint_state_compositor->aor_statuses);<br>+}<br>+<br>+/*! \brief Hashing function for endpoint AOR status */<br>+AO2_STRING_FIELD_HASH_FN(sip_options_endpoint_aor_status, name);<br>+<br>+/*! \brief Comparator function for endpoint AOR status */<br>+AO2_STRING_FIELD_CMP_FN(sip_options_endpoint_aor_status, name);<br>+<br>+/*! \brief Find (or create) an endpoint state compositor */<br>+static struct sip_options_endpoint_state_compositor *sip_options_endpoint_state_compositor_find_or_alloc(const struct ast_sip_endpoint *endpoint)<br>+{<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor;<br>+<br>+ ao2_lock(sip_options_endpoint_state_compositors);<br>+ endpoint_state_compositor = ao2_find(sip_options_endpoint_state_compositors,<br>+ ast_sorcery_object_get_id(endpoint), OBJ_SEARCH_KEY | OBJ_NOLOCK);<br>+ if (endpoint_state_compositor) {<br>+ ao2_unlock(sip_options_endpoint_state_compositors);<br>+ return endpoint_state_compositor;<br>+ }<br>+<br>+ endpoint_state_compositor = ao2_alloc(sizeof(*endpoint_state_compositor)<br>+ + strlen(ast_sorcery_object_get_id(endpoint)) + 1,<br>+ sip_options_endpoint_state_compositor_dtor);<br>+ if (!endpoint_state_compositor) {<br>+ ao2_unlock(sip_options_endpoint_state_compositors);<br>+ return NULL;<br>+ }<br>+<br>+ /*<br>+ * NOTE: The endpoint_state_compositor->aor_statuses container is<br>+ * externally protected by the endpoint_state_compositor lock.<br>+ */<br>+ endpoint_state_compositor->aor_statuses = ao2_container_alloc_hash(<br>+ AO2_ALLOC_OPT_LOCK_NOLOCK, 0, AOR_STATUS_BUCKETS,<br>+ sip_options_endpoint_aor_status_hash_fn, NULL,<br>+ sip_options_endpoint_aor_status_cmp_fn);<br>+ if (!endpoint_state_compositor->aor_statuses) {<br>+ ao2_unlock(sip_options_endpoint_state_compositors);<br>+ ao2_ref(endpoint_state_compositor, -1);<br>+ return NULL;<br>+ }<br>+<br>+ strcpy(endpoint_state_compositor->name, ast_sorcery_object_get_id(endpoint)); /* SAFE */<br>+<br>+ ao2_link_flags(sip_options_endpoint_state_compositors, endpoint_state_compositor,<br>+ OBJ_NOLOCK);<br>+ ao2_unlock(sip_options_endpoint_state_compositors);<br>+<br>+ return endpoint_state_compositor;<br>+}<br>+<br>+/*! \brief Task details for adding an AOR to an endpoint state compositor */<br>+struct sip_options_endpoint_compositor_task_data {<br>+ /*! \brief The AOR options that the endpoint state compositor should be added to */<br>+ struct sip_options_aor *aor_options;<br>+ /*! \brief The endpoint state compositor */<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor;<br>+};<br>+<br>+/*!<br>+ * \brief Task which adds an AOR to an endpoint state compositor<br>+ * \note Run by aor_options->serializer<br>+ */<br>+static int sip_options_endpoint_compositor_add_task(void *obj)<br>+{<br>+ struct sip_options_endpoint_compositor_task_data *task_data = obj;<br>+<br>+ ast_debug(3, "Adding endpoint compositor '%s' to AOR '%s'\n",<br>+ task_data->endpoint_state_compositor->name, task_data->aor_options->name);<br>+<br>+ if (AST_VECTOR_APPEND(&task_data->aor_options->compositors,<br>+ ao2_bump(task_data->endpoint_state_compositor))) {<br>+ /* Failed to add so no need to update the endpoint status. Nothing changed. */<br>+ ao2_cleanup(task_data->endpoint_state_compositor);<br>+ return 0;<br>+ }<br>+<br>+ ao2_lock(task_data->endpoint_state_compositor);<br>+ sip_options_update_endpoint_state_compositor_aor(task_data->endpoint_state_compositor,<br>+ task_data->aor_options->name,<br>+ task_data->aor_options->available ? AVAILABLE : UNAVAILABLE);<br>+ ao2_unlock(task_data->endpoint_state_compositor);<br>+<br>+ return 0;<br>+}<br>+<br>+/*!<br>+ * \brief Task which adds removes an AOR from an endpoint state compositor<br>+ * \note Run by aor_options->serializer<br>+ */<br>+static int sip_options_endpoint_compositor_remove_task(void *obj)<br>+{<br>+ struct sip_options_endpoint_compositor_task_data *task_data = obj;<br>+ int i;<br>+<br>+ ast_debug(3, "Removing endpoint compositor '%s' from AOR '%s'\n",<br>+ task_data->endpoint_state_compositor->name,<br>+ task_data->aor_options->name);<br>+<br>+ for (i = 0; i < AST_VECTOR_SIZE(&task_data->aor_options->compositors); ++i) {<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor;<br>+<br>+ endpoint_state_compositor = AST_VECTOR_GET(&task_data->aor_options->compositors, i);<br>+ if (endpoint_state_compositor != task_data->endpoint_state_compositor) {<br> continue;<br> }<br> <br>- contacts = ast_sip_location_retrieve_aor_contacts(aor);<br>- if (contacts) {<br>- ast_cli(cli_fd, "Sending qualify to endpoint %s\n", endpoint_name);<br>- ao2_callback_data(contacts, OBJ_NODATA, cli_on_contact, &cli_fd, endpoint);<br>- ao2_ref(contacts, -1);<br>- }<br>-<br>- ao2_ref(aor, -1);<br>+ AST_VECTOR_REMOVE(&task_data->aor_options->compositors, i, 0);<br>+ ao2_ref(endpoint_state_compositor, -1);<br>+ break;<br> }<br>+<br> return 0;<br> }<br>+<br>+/*!<br>+ * \brief Synchronize an endpoint with our local state<br>+ * \note Run by management_serializer<br>+ */<br>+static int sip_options_synchronize_endpoint(void *obj, void *arg, int flags)<br>+{<br>+ struct ast_sip_endpoint *endpoint = obj;<br>+ struct ast_sip_aor *aor = arg;<br>+ char *aors;<br>+ char *aor_name;<br>+ struct sip_options_endpoint_compositor_task_data task_data = { NULL, };<br>+<br>+ if (ast_strlen_zero(endpoint->aors)) {<br>+ /* There are no AORs, so really... who the heck knows */<br>+ ast_debug(3, "Endpoint '%s' is not interested in any AORs so not creating endpoint state compositor\n",<br>+ ast_sorcery_object_get_id(endpoint));<br>+ return 0;<br>+ }<br>+<br>+ ast_debug(3, "Synchronizing endpoint '%s' with AORs '%s'\n",<br>+ ast_sorcery_object_get_id(endpoint), endpoint->aors);<br>+<br>+ aors = ast_strdupa(endpoint->aors);<br>+ while ((aor_name = ast_strip(strsep(&aors, ",")))) {<br>+ if (ast_strlen_zero(aor_name)) {<br>+ continue;<br>+ }<br>+ if (aor && strcasecmp(ast_sorcery_object_get_id(aor), aor_name)) {<br>+ ast_debug(3, "Filtered AOR '%s' on endpoint '%s' as we are looking for '%s'\n",<br>+ aor_name, ast_sorcery_object_get_id(endpoint),<br>+ ast_sorcery_object_get_id(aor));<br>+ continue;<br>+ }<br>+<br>+ task_data.aor_options = ao2_find(sip_options_aors, aor_name, OBJ_SEARCH_KEY);<br>+ if (!task_data.aor_options) {<br>+ /*<br>+ * They have referenced an invalid AOR. If that's all they've<br>+ * done we will set them to offline at the end.<br>+ */<br>+ ast_debug(3, "Endpoint '%s' referenced invalid AOR '%s'\n",<br>+ ast_sorcery_object_get_id(endpoint), aor_name);<br>+ continue;<br>+ }<br>+<br>+ if (!task_data.endpoint_state_compositor) {<br>+ /*<br>+ * We create an endpoint state compositor only after we know<br>+ * for sure we need it.<br>+ */<br>+ task_data.endpoint_state_compositor =<br>+ sip_options_endpoint_state_compositor_find_or_alloc(endpoint);<br>+ if (!task_data.endpoint_state_compositor) {<br>+ ast_log(LOG_WARNING,<br>+ "Could not create endpoint state compositor for '%s', endpoint state will be incorrect\n",<br>+ ast_sorcery_object_get_id(endpoint));<br>+ ao2_ref(task_data.aor_options, -1);<br>+ ast_sip_persistent_endpoint_update_state(ast_sorcery_object_get_id(endpoint),<br>+ AST_ENDPOINT_OFFLINE);<br>+ return 0;<br>+ }<br>+ }<br>+<br>+ /* We use a synchronous task so that we don't flood the system */<br>+ ast_sip_push_task_wait_serializer(task_data.aor_options->serializer,<br>+ sip_options_endpoint_compositor_add_task, &task_data);<br>+<br>+ ao2_ref(task_data.aor_options, -1);<br>+<br>+ /*<br>+ * If we filtered on a specific AOR name then the endpoint can<br>+ * only reference it once so break early.<br>+ */<br>+ if (aor) {<br>+ break;<br>+ }<br>+ }<br>+<br>+ if (task_data.endpoint_state_compositor) {<br>+ /*<br>+ * If an endpoint state compositor is present determine the current state<br>+ * of the endpoint and update it.<br>+ */<br>+ ao2_lock(task_data.endpoint_state_compositor);<br>+ task_data.endpoint_state_compositor->active = 1;<br>+ ast_sip_persistent_endpoint_update_state(ast_sorcery_object_get_id(endpoint),<br>+ sip_options_get_endpoint_state_compositor_state(task_data.endpoint_state_compositor));<br>+ ao2_unlock(task_data.endpoint_state_compositor);<br>+<br>+ ao2_ref(task_data.endpoint_state_compositor, -1);<br>+ } else {<br>+ /* If there is none then they may have referenced an invalid AOR or none at all */<br>+ ast_debug(3, "Endpoint '%s' has no AORs feeding it, setting it to offline state as default\n",<br>+ ast_sorcery_object_get_id(endpoint));<br>+ ast_sip_persistent_endpoint_update_state(ast_sorcery_object_get_id(endpoint),<br>+ AST_ENDPOINT_OFFLINE);<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+/*!<br>+ * \brief Task which removes an AOR from all of the ESCs it is reporting to<br>+ * \note Run by aor_options->serializer<br>+ */<br>+static int sip_options_aor_remove_task(void *obj)<br>+{<br>+ struct sip_options_aor *aor_options = obj;<br>+<br>+ sip_options_notify_endpoint_state_compositors(aor_options, REMOVED);<br>+<br>+ if (aor_options->sched_task) {<br>+ ast_sip_sched_task_cancel(aor_options->sched_task);<br>+ ao2_ref(aor_options->sched_task, -1);<br>+ aor_options->sched_task = NULL;<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+/*!<br>+ * \brief Callback which removes any unused AORs that remained after reloading<br>+ * \note Run by management_serializer<br>+ */<br>+static int sip_options_unused_aor(void *obj, void *arg, int flags)<br>+{<br>+ struct sip_options_aor *aor_options = obj;<br>+<br>+ ast_debug(3, "AOR '%s' is no longer configured, removing it\n", aor_options->name);<br>+<br>+ ast_sip_push_task_wait_serializer(aor_options->serializer, sip_options_aor_remove_task,<br>+ aor_options);<br>+ ao2_unlink(sip_options_aors, aor_options);<br>+<br>+ return CMP_MATCH;<br>+}<br>+<br>+/*!<br>+ * \brief Callback function used to unlink and remove event state compositors that have no AORs feeding them<br>+ * \note Run by management_serializer<br>+ */<br>+static int sip_options_unused_endpoint_state_compositor(void *obj, void *arg, int flags)<br>+{<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor = obj;<br>+<br>+ if (ao2_container_count(endpoint_state_compositor->aor_statuses)) {<br>+ return 0;<br>+ }<br>+<br>+ /* No AORs are feeding this endpoint state compositor */<br>+ ast_sip_persistent_endpoint_update_state(endpoint_state_compositor->name,<br>+ AST_ENDPOINT_OFFLINE);<br>+<br>+ return CMP_MATCH;<br>+}<br>+<br>+/*! \brief Structure which contains information required to synchronize */<br>+struct sip_options_synchronize_task_data {<br>+ /*! \brief Whether this is a reload or not */<br>+ int reload;<br>+};<br>+<br>+/*!<br>+ * \brief Task to synchronize our local container of AORs and endpoint state compositors with the current configuration<br>+ * \note Run by management_serializer<br>+ */<br>+static int sip_options_synchronize_task(void *obj)<br>+{<br>+ struct sip_options_synchronize_task_data *task_data = obj;<br>+ struct ao2_container *existing = NULL;<br>+ struct ao2_container *objects;<br>+<br>+ /*<br>+ * When reloading we keep track of the existing AORs so we can<br>+ * terminate old ones that are no longer referenced or used.<br>+ */<br>+ if (task_data->reload) {<br>+ existing = ao2_container_clone(sip_options_aors, 0);<br>+ if (!existing) {<br>+ return 0;<br>+ }<br>+ }<br>+<br>+ objects = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "aor",<br>+ AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);<br>+ if (objects) {<br>+ /* Go through the returned AORs and synchronize with our local state */<br>+ ao2_callback(objects, OBJ_NODATA, sip_options_synchronize_aor, existing);<br>+ ao2_ref(objects, -1);<br>+ }<br>+<br>+ /*<br>+ * Any AORs remaining in existing are no longer referenced by<br>+ * the current container of AORs we retrieved, so remove them.<br>+ */<br>+ if (existing) {<br>+ ao2_callback(existing, OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK,<br>+ sip_options_unused_aor, NULL);<br>+ ao2_ref(existing, -1);<br>+ }<br>+<br>+ objects = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "endpoint",<br>+ AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);<br>+ if (objects) {<br>+ /* Go through the provided endpoints and update AORs */<br>+ ao2_callback(objects, OBJ_NODATA, sip_options_synchronize_endpoint, NULL);<br>+ ao2_ref(objects, -1);<br>+ }<br>+<br>+ /*<br>+ * All endpoint state compositors that don't have any AORs<br>+ * feeding them information can be removed. If they end<br>+ * up getting needed later they'll just be recreated.<br>+ */<br>+ ao2_callback(sip_options_endpoint_state_compositors,<br>+ OBJ_NODATA | OBJ_MULTIPLE | OBJ_UNLINK,<br>+ sip_options_unused_endpoint_state_compositor, NULL);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Synchronize our local container of AORs and endpoint state compositors with the current configuration */<br>+static void sip_options_synchronize(int reload)<br>+{<br>+ struct sip_options_synchronize_task_data task_data = {<br>+ .reload = reload,<br>+ };<br>+<br>+ ast_sip_push_task_wait_serializer(management_serializer, sip_options_synchronize_task,<br>+ &task_data);<br>+}<br>+<br>+/*!<br>+ * \brief Unlink AORs feeding the endpoint status compositor<br>+ * \note Run by management_serializer<br>+ */<br>+static void sip_options_endpoint_unlink_aor_feeders(struct ast_sip_endpoint *endpoint,<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor)<br>+{<br>+ struct ao2_iterator it_aor_statuses;<br>+ struct sip_options_endpoint_aor_status *aor_status;<br>+ struct sip_options_endpoint_compositor_task_data task_data = {<br>+ .endpoint_state_compositor = endpoint_state_compositor,<br>+ };<br>+<br>+ ao2_lock(endpoint_state_compositor);<br>+ endpoint_state_compositor->active = 0;<br>+<br>+ /* Unlink AOR feeders pointing to endpoint */<br>+ it_aor_statuses = ao2_iterator_init(endpoint_state_compositor->aor_statuses, 0);<br>+ for (; (aor_status = ao2_iterator_next(&it_aor_statuses)); ao2_ref(aor_status, -1)) {<br>+ task_data.aor_options = ao2_find(sip_options_aors, aor_status->name,<br>+ OBJ_SEARCH_KEY);<br>+ if (!task_data.aor_options) {<br>+ continue;<br>+ }<br>+<br>+ ast_debug(3, "Removing endpoint state compositor '%s' from AOR '%s'\n",<br>+ ast_sorcery_object_get_id(endpoint), aor_status->name);<br>+ ao2_unlock(endpoint_state_compositor);<br>+ ast_sip_push_task_wait_serializer(task_data.aor_options->serializer,<br>+ sip_options_endpoint_compositor_remove_task, &task_data);<br>+ ao2_lock(endpoint_state_compositor);<br>+ ao2_ref(task_data.aor_options, -1);<br>+ }<br>+ ao2_iterator_destroy(&it_aor_statuses);<br>+<br>+ /*<br>+ * We do not need to remove the AOR feeder status memory from the<br>+ * aor_statuses container. The endpoint_state_compositor is about<br>+ * to die and do it for us.<br>+ */<br>+<br>+ ao2_unlock(endpoint_state_compositor);<br>+}<br>+<br>+/*!<br>+ * \brief Task to delete an endpoint from the known universe<br>+ * \note Run by management_serializer<br>+ */<br>+static int sip_options_endpoint_observer_deleted_task(void *obj)<br>+{<br>+ struct ast_sip_endpoint *endpoint = obj;<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor;<br>+<br>+ endpoint_state_compositor = ao2_find(sip_options_endpoint_state_compositors,<br>+ ast_sorcery_object_get_id(endpoint), OBJ_SEARCH_KEY | OBJ_UNLINK);<br>+ if (!endpoint_state_compositor) {<br>+ return 0;<br>+ }<br>+<br>+ ast_debug(3, "Endpoint '%s' has been deleted, removing endpoint state compositor from AORs\n",<br>+ ast_sorcery_object_get_id(endpoint));<br>+ sip_options_endpoint_unlink_aor_feeders(endpoint, endpoint_state_compositor);<br>+ ao2_ref(endpoint_state_compositor, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Observer callback invoked on endpoint deletion */<br>+static void endpoint_observer_deleted(const void *obj)<br>+{<br>+ ast_sip_push_task_wait_serializer(management_serializer,<br>+ sip_options_endpoint_observer_deleted_task, (void *) obj);<br>+}<br>+<br>+/*!<br>+ * \brief Task to synchronize the endpoint<br>+ * \note Run by management_serializer<br>+ */<br>+static int sip_options_endpoint_observer_modified_task(void *obj)<br>+{<br>+ struct ast_sip_endpoint *endpoint = obj;<br>+ struct sip_options_endpoint_state_compositor *endpoint_state_compositor;<br>+<br>+ ast_debug(3, "Endpoint '%s' has been created or modified, updating state\n",<br>+ ast_sorcery_object_get_id(endpoint));<br>+<br>+ endpoint_state_compositor = ao2_find(sip_options_endpoint_state_compositors,<br>+ ast_sorcery_object_get_id(endpoint), OBJ_SEARCH_KEY | OBJ_UNLINK);<br>+ if (endpoint_state_compositor) {<br>+ /* Unlink the AORs currently feeding the endpoint. */<br>+ sip_options_endpoint_unlink_aor_feeders(endpoint, endpoint_state_compositor);<br>+ ao2_ref(endpoint_state_compositor, -1);<br>+ }<br>+<br>+ /* Connect the AORs that now feed the endpoint. */<br>+ sip_options_synchronize_endpoint(endpoint, NULL, 0);<br>+ return 0;<br>+}<br>+<br>+/*! \brief Observer callback invoked on endpoint creation or modification */<br>+static void endpoint_observer_modified(const void *obj)<br>+{<br>+ ast_sip_push_task_wait_serializer(management_serializer,<br>+ sip_options_endpoint_observer_modified_task, (void *)obj);<br>+}<br>+<br>+/*! \brief Observer callbacks for endpoints */<br>+static const struct ast_sorcery_observer endpoint_observer_callbacks = {<br>+ .created = endpoint_observer_modified,<br>+ .updated = endpoint_observer_modified,<br>+ .deleted = endpoint_observer_deleted,<br>+};<br>+<br>+/*!<br>+ * \brief Task to synchronize an AOR with our local state<br>+ * \note Run by aor_options->serializer<br>+ */<br>+static int sip_options_update_aor_task(void *obj)<br>+{<br>+ struct sip_options_synchronize_aor_task_data *task_data = obj;<br>+ int available = task_data->aor_options->available;<br>+<br>+ ast_debug(3, "Individually updating AOR '%s' with current state of configuration and world\n",<br>+ task_data->aor_options->name);<br>+<br>+ sip_options_apply_aor_configuration(task_data->aor_options, task_data->aor,<br>+ task_data->added);<br>+<br>+ if (!available && task_data->aor_options->available) {<br>+ ast_debug(3, "After modifying AOR '%s' it has now become available\n",<br>+ task_data->aor_options->name);<br>+ sip_options_notify_endpoint_state_compositors(task_data->aor_options, AVAILABLE);<br>+ } else if (available && !task_data->aor_options->available) {<br>+ ast_debug(3, "After modifying AOR '%s' it has become unavailable\n",<br>+ task_data->aor_options->name);<br>+ sip_options_notify_endpoint_state_compositors(task_data->aor_options, UNAVAILABLE);<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+/*!<br>+ * \brief Task to synchronize the AOR<br>+ * \note Run by management_serializer<br>+ */<br>+static int sip_options_aor_observer_modified_task(void *obj)<br>+{<br>+ struct ast_sip_aor *aor = obj;<br>+ struct sip_options_aor *aor_options;<br>+<br>+ aor_options = ao2_find(sip_options_aors, ast_sorcery_object_get_id(aor),<br>+ OBJ_SEARCH_KEY);<br>+ if (!aor_options) {<br>+ struct ao2_container *endpoints;<br>+<br>+ aor_options = sip_options_aor_alloc(aor);<br>+ if (!aor_options) {<br>+ return 0;<br>+ }<br>+<br>+ /*<br>+ * This is a newly added AOR and we need to establish any<br>+ * endpoint state compositors that may reference only the<br>+ * AOR. If these need to be updated later then they'll<br>+ * be done by modifying the endpoint or issuing a reload.<br>+ */<br>+ sip_options_apply_aor_configuration(aor_options, aor, 1);<br>+ ao2_link(sip_options_aors, aor_options);<br>+<br>+ /*<br>+ * Using LIKE doesn't seem to work very well with non-realtime so we<br>+ * fetch everything right now and do a filter on our side.<br>+ */<br>+ endpoints = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),<br>+ "endpoint", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);<br>+ if (endpoints) {<br>+ ao2_callback(endpoints, OBJ_NODATA, sip_options_synchronize_endpoint, aor);<br>+ ao2_ref(endpoints, -1);<br>+ }<br>+ } else {<br>+ struct sip_options_synchronize_aor_task_data task_data = {<br>+ .aor_options = aor_options,<br>+ .aor = aor,<br>+ };<br>+<br>+ /*<br>+ * If this AOR was modified we have to do our work in its serializer<br>+ * instead of this thread to ensure that things aren't modified by<br>+ * multiple threads.<br>+ */<br>+ ast_sip_push_task_wait_serializer(aor_options->serializer,<br>+ sip_options_update_aor_task, &task_data);<br>+ }<br>+<br>+ ao2_ref(aor_options, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Observer callback invoked on AOR creation or modification */<br>+static void aor_observer_modified(const void *obj)<br>+{<br>+ ast_sip_push_task_wait_serializer(management_serializer,<br>+ sip_options_aor_observer_modified_task, (void *) obj);<br>+}<br>+<br>+/*!<br>+ * \brief Task to delete an AOR from the known universe<br>+ * \note Run by management_serializer<br>+ */<br>+static int sip_options_aor_observer_deleted_task(void *obj)<br>+{<br>+ struct ast_sip_aor *aor = obj;<br>+ struct sip_options_aor *aor_options;<br>+<br>+ aor_options = ao2_find(sip_options_aors, ast_sorcery_object_get_id(aor),<br>+ OBJ_SEARCH_KEY | OBJ_UNLINK);<br>+ if (!aor_options) {<br>+ return 0;<br>+ }<br>+<br>+ ast_debug(3, "AOR '%s' has been deleted, removing it\n", aor_options->name);<br>+<br>+ ast_sip_push_task_wait_serializer(aor_options->serializer, sip_options_aor_remove_task,<br>+ aor_options);<br>+ ao2_ref(aor_options, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Observer callback invoked on AOR deletion */<br>+static void aor_observer_deleted(const void *obj)<br>+{<br>+ ast_sip_push_task_wait_serializer(management_serializer,<br>+ sip_options_aor_observer_deleted_task, (void *) obj);<br>+}<br>+<br>+/*! \brief Observer callbacks for AORs */<br>+static const struct ast_sorcery_observer aor_observer_callbacks = {<br>+ .created = aor_observer_modified,<br>+ .updated = aor_observer_modified,<br>+ .deleted = aor_observer_deleted,<br>+};<br>+<br>+/*! \brief Task details for adding an AOR to an endpoint state compositor */<br>+struct sip_options_contact_observer_task_data {<br>+ /*! \brief The AOR options that the contact is referring to */<br>+ struct sip_options_aor *aor_options;<br>+ /*! \brief The contact itself */<br>+ struct ast_sip_contact *contact;<br>+};<br>+<br>+/*!<br>+ * \brief Task which adds a dynamic contact to an AOR<br>+ * \note Run by aor_options->serializer<br>+ */<br>+static int sip_options_contact_add_task(void *obj)<br>+{<br>+ struct sip_options_contact_observer_task_data *task_data = obj;<br>+ struct ast_sip_contact_status *contact_status;<br>+<br>+ ao2_link(task_data->aor_options->dynamic_contacts, task_data->contact);<br>+ ao2_link(task_data->aor_options->contacts, task_data->contact);<br>+<br>+ contact_status = ast_res_pjsip_find_or_create_contact_status(task_data->contact);<br>+ ao2_cleanup(contact_status);<br>+<br>+ if (task_data->aor_options->qualify_frequency) {<br>+ /* If this is the first contact we need to schedule up qualification */<br>+ if (ao2_container_count(task_data->aor_options->contacts) == 1) {<br>+ ast_debug(3, "Starting scheduled callback on AOR '%s' for qualifying as there is now a contact on it\n",<br>+ task_data->aor_options->name);<br>+ /*<br>+ * We immediately schedule the initial qualify so that we get<br>+ * reachable/unreachable as soon as possible. Realistically<br>+ * since they pretty much just registered they should be<br>+ * reachable.<br>+ */<br>+ if (task_data->aor_options->sched_task) {<br>+ ast_sip_sched_task_cancel(task_data->aor_options->sched_task);<br>+ ao2_ref(task_data->aor_options->sched_task, -1);<br>+ task_data->aor_options->sched_task = NULL;<br>+ }<br>+ task_data->aor_options->sched_task = ast_sip_schedule_task(<br>+ task_data->aor_options->serializer, 1, sip_options_qualify_aor,<br>+ ast_taskprocessor_name(task_data->aor_options->serializer),<br>+ task_data->aor_options,<br>+ AST_SIP_SCHED_TASK_VARIABLE | AST_SIP_SCHED_TASK_DATA_AO2);<br>+ if (!task_data->aor_options->sched_task) {<br>+ ast_log(LOG_ERROR, "Unable to schedule qualify for contacts of AOR '%s'\n",<br>+ task_data->aor_options->name);<br>+ }<br>+ }<br>+ } else {<br>+ /*<br>+ * If this was the first contact added to a non-qualified AOR then<br>+ * it should become available.<br>+ */<br>+ task_data->aor_options->available =<br>+ ao2_container_count(task_data->aor_options->contacts);<br>+ if (task_data->aor_options->available == 1) {<br>+ ast_debug(3, "An unqualified contact has been added to AOR '%s' so it is now available\n",<br>+ task_data->aor_options->name);<br>+ sip_options_notify_endpoint_state_compositors(task_data->aor_options,<br>+ AVAILABLE);<br>+ }<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+/*!<br>+ * \brief Task to add a dynamic contact to an AOR in its serializer<br>+ * \note Run by management_serializer<br>+ */<br>+static int sip_options_contact_add_management_task(void *obj)<br>+{<br>+ struct sip_options_contact_observer_task_data task_data;<br>+<br>+ task_data.contact = obj;<br>+ task_data.aor_options = ao2_find(sip_options_aors, task_data.contact->aor,<br>+ OBJ_SEARCH_KEY);<br>+ if (!task_data.aor_options) {<br>+ struct ast_sip_aor *aor;<br>+<br>+ /*<br>+ * The only reason this would occur is if the AOR was sourced<br>+ * after the last reload happened. To handle this we fetch the<br>+ * AOR and treat it as if we received notification that it had<br>+ * been created. This will create the needed AOR feeder<br>+ * compositor and will cause any associated contact statuses and<br>+ * endpoint state compositors to also get created if needed.<br>+ */<br>+ aor = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "aor",<br>+ task_data.contact->aor);<br>+ if (aor) {<br>+ sip_options_aor_observer_modified_task(aor);<br>+ ao2_ref(aor, -1);<br>+ }<br>+ return 0;<br>+ }<br>+<br>+ ast_sip_push_task_wait_serializer(task_data.aor_options->serializer,<br>+ sip_options_contact_add_task, &task_data);<br>+ ao2_ref(task_data.aor_options, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Observer callback invoked on contact creation */<br>+static void contact_observer_created(const void *obj)<br>+{<br>+ ast_sip_push_task_wait_serializer(management_serializer,<br>+ sip_options_contact_add_management_task, (void *) obj);<br>+}<br>+<br>+/*!<br>+ * \brief Task which updates a dynamic contact to an AOR<br>+ * \note Run by aor_options->serializer<br>+ */<br>+static int sip_options_contact_update_task(void *obj)<br>+{<br>+ struct sip_options_contact_observer_task_data *task_data = obj;<br>+ struct ast_sip_contact_status *contact_status;<br>+<br>+ contact_status = ast_sip_get_contact_status(task_data->contact);<br>+ if (contact_status) {<br>+ switch (contact_status->status) {<br>+ case CREATED:<br>+ case UNAVAILABLE:<br>+ case AVAILABLE:<br>+ case UNKNOWN:<br>+ /* Refresh the ContactStatus AMI events. */<br>+ sip_options_contact_status_update(contact_status);<br>+ break;<br>+ case REMOVED:<br>+ break;<br>+ }<br>+ ao2_ref(contact_status, -1);<br>+ }<br>+<br>+ ao2_ref(task_data->contact, -1);<br>+ ao2_ref(task_data->aor_options, -1);<br>+ ast_free(task_data);<br>+ return 0;<br>+}<br>+<br>+/*! \brief Observer callback invoked on contact update */<br>+static void contact_observer_updated(const void *obj)<br>+{<br>+ struct sip_options_contact_observer_task_data *task_data;<br>+<br>+ task_data = ast_malloc(sizeof(*task_data));<br>+ if (!task_data) {<br>+ return;<br>+ }<br>+<br>+ task_data->contact = (struct ast_sip_contact *) obj;<br>+ task_data->aor_options = ao2_find(sip_options_aors, task_data->contact->aor,<br>+ OBJ_SEARCH_KEY);<br>+ if (!task_data->aor_options) {<br>+ ast_free(task_data);<br>+ return;<br>+ }<br>+<br>+ ao2_ref(task_data->contact, +1);<br>+ if (ast_sip_push_task(task_data->aor_options->serializer,<br>+ sip_options_contact_update_task, task_data)) {<br>+ ao2_ref(task_data->contact, -1);<br>+ ao2_ref(task_data->aor_options, -1);<br>+ ast_free(task_data);<br>+ }<br>+}<br>+<br>+/*!<br>+ * \brief Task which deletes a dynamic contact from an AOR<br>+ * \note Run by aor_options->serializer<br>+ */<br>+static int sip_options_contact_delete_task(void *obj)<br>+{<br>+ struct sip_options_contact_observer_task_data *task_data = obj;<br>+<br>+ ao2_find(task_data->aor_options->dynamic_contacts, task_data->contact,<br>+ OBJ_NODATA | OBJ_UNLINK | OBJ_SEARCH_OBJECT);<br>+ ao2_find(task_data->aor_options->contacts, task_data->contact,<br>+ OBJ_NODATA | OBJ_UNLINK | OBJ_SEARCH_OBJECT);<br>+<br>+ sip_options_remove_contact_status(task_data->aor_options, task_data->contact);<br>+<br>+ if (task_data->aor_options->qualify_frequency) {<br>+ /* If this is the last contact then we need to stop the scheduled callback */<br>+ if (!ao2_container_count(task_data->aor_options->contacts)) {<br>+ ast_debug(3, "Terminating scheduled callback on AOR '%s' as there are no contacts to qualify\n",<br>+ task_data->aor_options->name);<br>+ if (task_data->aor_options->sched_task) {<br>+ ast_sip_sched_task_cancel(task_data->aor_options->sched_task);<br>+ ao2_ref(task_data->aor_options->sched_task, -1);<br>+ task_data->aor_options->sched_task = NULL;<br>+ }<br>+ }<br>+ } else {<br>+ task_data->aor_options->available =<br>+ ao2_container_count(task_data->aor_options->contacts);<br>+ if (!task_data->aor_options->available) {<br>+ ast_debug(3, "An unqualified contact has been removed from AOR '%s' leaving no remaining contacts\n",<br>+ task_data->aor_options->name);<br>+ sip_options_notify_endpoint_state_compositors(task_data->aor_options,<br>+ UNAVAILABLE);<br>+ }<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+/*!<br>+ * \brief Task to delete a contact from an AOR in its serializer<br>+ * \note Run by management_serializer<br>+ */<br>+static int sip_options_contact_delete_management_task(void *obj)<br>+{<br>+ struct sip_options_contact_observer_task_data task_data;<br>+<br>+ task_data.contact = obj;<br>+ task_data.aor_options = ao2_find(sip_options_aors, task_data.contact->aor,<br>+ OBJ_SEARCH_KEY);<br>+ if (!task_data.aor_options) {<br>+ /* For contacts that are deleted we don't really care if there is no AOR locally */<br>+ return 0;<br>+ }<br>+<br>+ ast_sip_push_task_wait_serializer(task_data.aor_options->serializer,<br>+ sip_options_contact_delete_task, &task_data);<br>+ ao2_ref(task_data.aor_options, -1);<br>+<br>+ return 0;<br>+}<br>+<br>+/*! \brief Observer callback invoked on contact deletion */<br>+static void contact_observer_deleted(const void *obj)<br>+{<br>+ ast_sip_push_task_wait_serializer(management_serializer,<br>+ sip_options_contact_delete_management_task, (void *) obj);<br>+}<br>+<br>+/*! \brief Observer callbacks for contacts */<br>+static const struct ast_sorcery_observer contact_observer_callbacks = {<br>+ .created = contact_observer_created,<br>+ .updated = contact_observer_updated,<br>+ .deleted = contact_observer_deleted,<br>+};<br> <br> static char *cli_qualify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)<br> {<br> RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);<br> const char *endpoint_name;<br>- struct qualify_data *qual_data;<br>+ char *aors;<br>+ char *aor_name;<br> <br> switch (cmd) {<br> case CLI_INIT:<br>@@ -895,38 +2322,34 @@<br> <br> endpoint_name = a->argv[2];<br> <br>- if (!(endpoint = ast_sorcery_retrieve_by_id(<br>- ast_sip_get_sorcery(), "endpoint", endpoint_name))) {<br>+ endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint",<br>+ endpoint_name);<br>+ if (!endpoint) {<br> ast_cli(a->fd, "Unable to retrieve endpoint %s\n", endpoint_name);<br> return CLI_FAILURE;<br> }<br> <br>- qual_data = qualify_data_alloc(endpoint, a->fd);<br>- if (!qual_data) {<br>+ if (ast_strlen_zero(endpoint->aors)) {<br>+ ast_cli(a->fd, "No AORs configured for endpoint '%s'\n", endpoint_name);<br> return CLI_FAILURE;<br> }<br> <br>- if (ast_sip_push_task(NULL, cli_qualify_contacts, qual_data)) {<br>- qualify_data_destroy(qual_data);<br>- return CLI_FAILURE;<br>+ aors = ast_strdupa(endpoint->aors);<br>+ while ((aor_name = ast_strip(strsep(&aors, ",")))) {<br>+ struct sip_options_aor *aor_options;<br>+<br>+ aor_options = ao2_find(sip_options_aors, aor_name, OBJ_SEARCH_KEY);<br>+ if (!aor_options) {<br>+ continue;<br>+ }<br>+<br>+ ast_cli(a->fd, "Qualifying AOR '%s' on endpoint '%s'\n", aor_name, endpoint_name);<br>+ ast_sip_push_task_wait_serializer(aor_options->serializer, sip_options_qualify_aor,<br>+ aor_options);<br>+ ao2_ref(aor_options, -1);<br> }<br> <br> return CLI_SUCCESS;<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Send qualify request to the given contact.<br>- */<br>-static int ami_contact_cb(void *obj, void *arg, int flags)<br>-{<br>- struct ast_sip_contact *contact = obj;<br>-<br>- ao2_ref(contact, +1);<br>- if (ast_sip_push_task(NULL, qualify_contact_task, contact)) {<br>- ao2_ref(contact, -1);<br>- }<br>- return 0;<br> }<br> <br> static struct ao2_container *get_all_contacts(void)<br>@@ -953,7 +2376,6 @@<br> struct ast_sip_contact_status *status;<br> <br> buf = ast_sip_create_ami_event("ContactList", ami);<br>-<br> if (!buf) {<br> return CMP_STOP;<br> }<br>@@ -964,16 +2386,15 @@<br> }<br> <br> /* Add extra info */<br>- status = ast_sorcery_retrieve_by_id(<br>- ast_sip_get_sorcery(), CONTACT_STATUS,<br>- ast_sorcery_object_get_id(contact));<br>+ status = ast_sip_get_contact_status(contact);<br> ast_str_append(&buf, 0, "Status: %s\r\n",<br>- ast_sip_get_contact_status_label(status ? status->status : UNKNOWN));<br>- if (!status || status->status == UNKNOWN) {<br>+ ast_sip_get_contact_status_label(status ? status->status : UNKNOWN));<br>+ if (!status || status->status != AVAILABLE) {<br> ast_str_append(&buf, 0, "RoundtripUsec: N/A\r\n");<br> } else {<br> ast_str_append(&buf, 0, "RoundtripUsec: %" PRId64 "\r\n", status->rtt);<br> }<br>+ ao2_cleanup(status);<br> <br> astman_append(ami->s, "%s\r\n", ast_str_buffer(buf));<br> <br>@@ -1041,21 +2462,16 @@<br> <br> aors = ast_strdupa(endpoint->aors);<br> while ((aor_name = ast_strip(strsep(&aors, ",")))) {<br>- struct ast_sip_aor *aor;<br>- struct ao2_container *contacts;<br>+ struct sip_options_aor *aor_options;<br> <br>- aor = ast_sip_location_retrieve_aor(aor_name);<br>- if (!aor) {<br>+ aor_options = ao2_find(sip_options_aors, aor_name, OBJ_SEARCH_KEY);<br>+ if (!aor_options) {<br> continue;<br> }<br> <br>- contacts = ast_sip_location_retrieve_aor_contacts(aor);<br>- if (contacts) {<br>- ao2_callback(contacts, OBJ_NODATA, ami_contact_cb, NULL);<br>- ao2_ref(contacts, -1);<br>- }<br>-<br>- ao2_ref(aor, -1);<br>+ ast_sip_push_task_wait_serializer(aor_options->serializer, sip_options_qualify_aor,<br>+ aor_options);<br>+ ao2_ref(aor_options, -1);<br> }<br> <br> astman_send_ack(s, m, "Endpoint found, will qualify");<br>@@ -1065,234 +2481,6 @@<br> static struct ast_cli_entry cli_options[] = {<br> AST_CLI_DEFINE(cli_qualify, "Send an OPTIONS request to a PJSIP endpoint")<br> };<br>-<br>-static int sched_qualifies_hash_fn(const void *obj, int flags)<br>-{<br>- const struct sched_data *object;<br>- const struct ast_sip_contact *key;<br>-<br>- switch (flags & OBJ_SEARCH_MASK) {<br>- case OBJ_SEARCH_KEY:<br>- key = obj;<br>- break;<br>- case OBJ_SEARCH_OBJECT:<br>- object = obj;<br>- key = object->contact;<br>- break;<br>- default:<br>- /* Hash can only work on something with a full key. */<br>- ast_assert(0);<br>- return 0;<br>- }<br>- return ast_str_hash(ast_sorcery_object_get_id(key));<br>-}<br>-<br>-static int sched_qualifies_cmp_fn(void *obj, void *arg, int flags)<br>-{<br>- const struct sched_data *object_left = obj;<br>- const struct sched_data *object_right = arg;<br>- struct ast_sip_contact *right_key = arg;<br>- int cmp;<br>-<br>- switch (flags & OBJ_SEARCH_MASK) {<br>- case OBJ_SEARCH_OBJECT:<br>- right_key = object_right->contact;<br>- /* Fall through */<br>- case OBJ_SEARCH_KEY:<br>- cmp = strcmp(ast_sorcery_object_get_id(object_left->contact),<br>- ast_sorcery_object_get_id(right_key));<br>- break;<br>- case OBJ_SEARCH_PARTIAL_KEY:<br>- /* Not supported by container. */<br>- ast_assert(0);<br>- return 0;<br>- default:<br>- /*<br>- * What arg points to is specific to this traversal callback<br>- * and has no special meaning to astobj2.<br>- */<br>- cmp = 0;<br>- break;<br>- }<br>- if (cmp) {<br>- return 0;<br>- }<br>- /*<br>- * At this point the traversal callback is identical to a sorted<br>- * container.<br>- */<br>- return CMP_MATCH;<br>-}<br>-<br>-static int rtt_start_handler(const struct aco_option *opt,<br>- struct ast_variable *var, void *obj)<br>-{<br>- struct ast_sip_contact_status *status = obj;<br>- long int sec, usec;<br>-<br>- if (sscanf(var->value, "%ld.%06ld", &sec, &usec) != 2) {<br>- return -1;<br>- }<br>-<br>- status->rtt_start = ast_tv(sec, usec);<br>-<br>- return 0;<br>-}<br>-<br>-static int rtt_start_to_str(const void *obj, const intptr_t *args, char **buf)<br>-{<br>- const struct ast_sip_contact_status *status = obj;<br>-<br>- if (ast_asprintf(buf, "%ld.%06ld", (long)status->rtt_start.tv_sec, (long)status->rtt_start.tv_usec) == -1) {<br>- return -1;<br>- }<br>-<br>- return 0;<br>-}<br>-<br>-static char status_value_unknown[2];<br>-static char status_value_created[2];<br>-<br>-int ast_sip_initialize_sorcery_qualify(void)<br>-{<br>- struct ast_sorcery *sorcery = ast_sip_get_sorcery();<br>-<br>- /* initialize sorcery ast_sip_contact_status resource */<br>- ast_sorcery_apply_default(sorcery, CONTACT_STATUS, "memory", NULL);<br>- ast_sorcery_object_set_congestion_levels(sorcery, CONTACT_STATUS, -1,<br>- 3 * AST_TASKPROCESSOR_HIGH_WATER_LEVEL);<br>-<br>- if (ast_sorcery_internal_object_register(sorcery, CONTACT_STATUS,<br>- contact_status_alloc, NULL, NULL)) {<br>- ast_log(LOG_ERROR, "Unable to register ast_sip_contact_status in sorcery\n");<br>- return -1;<br>- }<br>-<br>- snprintf(status_value_unknown, sizeof(status_value_unknown), "%u", UNKNOWN);<br>- ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "last_status",<br>- status_value_unknown, OPT_UINT_T, 0, FLDSET(struct ast_sip_contact_status, last_status));<br>- snprintf(status_value_created, sizeof(status_value_created), "%u", CREATED);<br>- ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "status",<br>- status_value_created, OPT_UINT_T, 0, FLDSET(struct ast_sip_contact_status, status));<br>- ast_sorcery_object_field_register_custom_nodoc(sorcery, CONTACT_STATUS, "rtt_start",<br>- "0.0", rtt_start_handler, rtt_start_to_str, NULL, 0, 0);<br>- ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "rtt",<br>- "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_contact_status, rtt));<br>-<br>- return 0;<br>-}<br>-<br>-static void qualify_and_schedule_contact(struct ast_sip_contact *contact)<br>-{<br>- int initial_interval;<br>- int max_time = ast_sip_get_max_initial_qualify_time();<br>-<br>- /* Delay initial qualification by a random fraction of the specified interval */<br>- if (max_time && max_time < contact->qualify_frequency) {<br>- initial_interval = max_time;<br>- } else {<br>- initial_interval = contact->qualify_frequency;<br>- }<br>-<br>- initial_interval = (int)((initial_interval * 1000) * ast_random_double());<br>-<br>- unschedule_qualify(contact);<br>- if (contact->qualify_frequency) {<br>- schedule_qualify(contact, initial_interval);<br>- } else {<br>- update_contact_status(contact, CREATED, 0);<br>- }<br>-}<br>-<br>-static int qualify_and_schedule_cb_with_aor(void *obj, void *arg, int flags)<br>-{<br>- struct ast_sip_contact *contact = obj;<br>- struct ast_sip_aor *aor = arg;<br>-<br>- contact->qualify_frequency = aor->qualify_frequency;<br>- contact->qualify_timeout = aor->qualify_timeout;<br>- contact->authenticate_qualify = aor->authenticate_qualify;<br>-<br>- qualify_and_schedule_contact(contact);<br>-<br>- return 0;<br>-}<br>-<br>-static int qualify_and_schedule_cb_without_aor(void *obj, void *arg, int flags)<br>-{<br>- /*<br>- * These are really dynamic contacts. We need to retrieve the aor associated<br>- * with the contact since it's possible some of the aor's fields were updated<br>- * since last load.<br>- */<br>- struct ast_sip_contact *contact = obj;<br>- struct ast_sip_aor *aor = ast_sip_location_retrieve_aor(contact->aor);<br>-<br>- if (aor) {<br>- qualify_and_schedule_cb_with_aor(obj, aor, flags);<br>- ao2_ref(aor, -1);<br>- } else {<br>- ast_log(LOG_WARNING, "Unable to locate AOR for contact '%s'. Keeping old "<br>- "associated settings: frequency=%d, timeout=%f, authenticate=%s\n",<br>- contact->uri, contact->qualify_frequency, contact->qualify_timeout,<br>- contact->authenticate_qualify ? "yes" : "no");<br>- qualify_and_schedule_contact(contact);<br>- }<br>-<br>- return 0;<br>-}<br>-<br>-/*!<br>- * \internal<br>- * \brief Qualify and schedule an aor's contacts<br>- *<br>- * \details For the given aor check if it has permanent contacts,<br>- * qualify all contacts and schedule for checks if configured.<br>- */<br>-static int qualify_and_schedule_all_cb(void *obj, void *arg, int flags)<br>-{<br>- struct ast_sip_aor *aor = obj;<br>- struct ao2_container *contacts;<br>-<br>- if (aor->permanent_contacts) {<br>- contacts = ast_sip_location_retrieve_aor_contacts(aor);<br>- if (contacts) {<br>- ao2_callback(contacts, OBJ_NODATA, qualify_and_schedule_cb_with_aor, aor);<br>- ao2_ref(contacts, -1);<br>- }<br>- }<br>-<br>- return 0;<br>-}<br>-<br>-static void qualify_and_schedule_all(void)<br>-{<br>- struct ao2_container *aors;<br>- struct ao2_container *contacts;<br>-<br>- /*<br>- * It's possible that the AOR had some of it's fields updated prior to a<br>- * reload. For instance qualifying could have been turned on or off by<br>- * setting the qualify_frequency. Due to this we have to iterate through<br>- * all contacts (static and dynamic), and not just ones where the frequency<br>- * is greater than zero, updating any contact fields with the AOR's values.<br>- */<br>-<br>- aors = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),<br>- "aor", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);<br>-<br>- if (aors) {<br>- ao2_callback(aors, OBJ_NODATA, qualify_and_schedule_all_cb, NULL);<br>- ao2_ref(aors, -1);<br>- }<br>-<br>- contacts = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),<br>- "contact", AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);<br>- if (contacts) {<br>- ao2_callback(contacts, OBJ_NODATA, qualify_and_schedule_cb_without_aor, NULL);<br>- ao2_ref(contacts, -1);<br>- }<br>-}<br> <br> int ast_sip_format_contact_ami(void *obj, void *arg, int flags)<br> {<br>@@ -1308,9 +2496,7 @@<br> return -1;<br> }<br> <br>- status = ast_sorcery_retrieve_by_id(<br>- ast_sip_get_sorcery(), CONTACT_STATUS,<br>- ast_sorcery_object_get_id(contact));<br>+ status = ast_sip_get_contact_status(contact);<br> <br> ast_str_append(&buf, 0, "AOR: %s\r\n", wrapper->aor_id);<br> ast_str_append(&buf, 0, "URI: %s\r\n", contact->uri);<br>@@ -1326,14 +2512,15 @@<br> if (!ast_strlen_zero(contact->call_id)) {<br> ast_str_append(&buf, 0, "CallID: %s\r\n", contact->call_id);<br> }<br>- ast_str_append(&buf, 0, "Status: %s\r\n", ast_sip_get_contact_status_label(status ? status->status : UNKNOWN));<br>- if (!status || status->status == UNKNOWN) {<br>+ ast_str_append(&buf, 0, "Status: %s\r\n",<br>+ ast_sip_get_contact_status_label(status ? status->status : UNKNOWN));<br>+ if (!status || status->status != AVAILABLE) {<br> ast_str_append(&buf, 0, "RoundtripUsec: N/A\r\n");<br> } else {<br> ast_str_append(&buf, 0, "RoundtripUsec: %" PRId64 "\r\n", status->rtt);<br> }<br> ast_str_append(&buf, 0, "EndpointName: %s\r\n",<br>- endpoint ? ast_sorcery_object_get_id(endpoint) : S_OR(contact->endpoint_name, ""));<br>+ endpoint ? ast_sorcery_object_get_id(endpoint) : S_OR(contact->endpoint_name, ""));<br> <br> ast_str_append(&buf, 0, "ID: %s\r\n", ast_sorcery_object_get_id(contact));<br> ast_str_append(&buf, 0, "AuthenticateQualify: %d\r\n", contact->authenticate_qualify);<br>@@ -1368,220 +2555,204 @@<br> .format_ami = format_ami_contact_status<br> };<br> <br>-static void aor_observer_modified(const void *obj)<br>+/*!<br>+ * \brief Management task to clean up an AOR<br>+ * \note Run by aor_options->serializer<br>+ */<br>+static int sip_options_cleanup_aor_task(void *obj)<br> {<br>- struct ast_sip_aor *aor = (void *)obj;<br>- struct ao2_container *contacts;<br>+ struct sip_options_aor *aor_options = obj;<br> <br>- contacts = ast_sip_location_retrieve_aor_contacts(aor);<br>- if (contacts) {<br>- ao2_callback(contacts, OBJ_NODATA, qualify_and_schedule_cb_with_aor, aor);<br>- ao2_ref(contacts, -1);<br>+ ast_debug(2, "Cleaning up AOR '%s' for shutdown\n", aor_options->name);<br>+<br>+ aor_options->qualify_frequency = 0;<br>+ if (aor_options->sched_task) {<br>+ ast_sip_sched_task_cancel(aor_options->sched_task);<br>+ ao2_ref(aor_options->sched_task, -1);<br>+ aor_options->sched_task = NULL;<br> }<br>+ AST_VECTOR_RESET(&aor_options->compositors, ao2_cleanup);<br>+<br>+ return 0;<br> }<br> <br>-static int unschedule_contact_cb(void *obj, void *arg, int flags)<br>+/*!<br>+ * \brief Management task to clean up the environment<br>+ * \note Run by management_serializer<br>+ */<br>+static int sip_options_cleanup_task(void *obj)<br> {<br>- unschedule_qualify(obj);<br>+ struct ao2_iterator it_aor;<br>+ struct sip_options_aor *aor_options;<br> <br>- return CMP_MATCH;<br>-}<br>-<br>-static void aor_observer_deleted(const void *obj)<br>-{<br>- const struct ast_sip_aor *aor = obj;<br>- struct ao2_container *contacts;<br>-<br>- contacts = ast_sip_location_retrieve_aor_contacts(aor);<br>- if (contacts) {<br>- ao2_callback(contacts, OBJ_NODATA | OBJ_MULTIPLE, unschedule_contact_cb, NULL);<br>- ao2_ref(contacts, -1);<br>- }<br>-}<br>-<br>-static const struct ast_sorcery_observer observer_callbacks_options = {<br>- .created = aor_observer_modified,<br>- .updated = aor_observer_modified,<br>- .deleted = aor_observer_deleted<br>-};<br>-<br>-static int aor_update_endpoint_state(void *obj, void *arg, int flags)<br>-{<br>- struct ast_sip_endpoint *endpoint = obj;<br>- const char *endpoint_name = ast_sorcery_object_get_id(endpoint);<br>- char *aor = arg;<br>- char *endpoint_aor;<br>- char *endpoint_aors;<br>-<br>- if (ast_strlen_zero(aor) || ast_strlen_zero(endpoint->aors)) {<br>+ if (!sip_options_aors) {<br>+ /* Nothing to do */<br> return 0;<br> }<br> <br>- endpoint_aors = ast_strdupa(endpoint->aors);<br>- while ((endpoint_aor = ast_strip(strsep(&endpoint_aors, ",")))) {<br>- if (!strcmp(aor, endpoint_aor)) {<br>- if (ast_sip_persistent_endpoint_update_state(endpoint_name, AST_ENDPOINT_ONLINE) == -1) {<br>- ast_log(LOG_WARNING, "Unable to find persistent endpoint '%s' for aor '%s'\n",<br>- endpoint_name, aor);<br>- }<br>- }<br>+ it_aor = ao2_iterator_init(sip_options_aors, AO2_ITERATOR_UNLINK);<br>+ for (; (aor_options = ao2_iterator_next(&it_aor)); ao2_ref(aor_options, -1)) {<br>+ ast_sip_push_task_wait_serializer(aor_options->serializer,<br>+ sip_options_cleanup_aor_task, aor_options);<br> }<br>-<br>- return 0;<br>-}<br>-<br>-static int on_aor_update_endpoint_state(void *obj, void *arg, int flags)<br>-{<br>- struct ast_sip_aor *aor = obj;<br>- struct ao2_container *endpoints;<br>- RAII_VAR(struct ast_variable *, var, NULL, ast_variables_destroy);<br>- const char *aor_name = ast_sorcery_object_get_id(aor);<br>- char *aor_like;<br>-<br>- if (ast_strlen_zero(aor_name)) {<br>- return -1;<br>- }<br>-<br>- if (aor->permanent_contacts && ((int)(aor->qualify_frequency * 1000)) <= 0) {<br>- aor_like = ast_alloca(strlen(aor_name) + 3);<br>- sprintf(aor_like, "%%%s%%", aor_name);<br>- var = ast_variable_new("aors LIKE", aor_like, "");<br>- if (!var) {<br>- return -1;<br>- }<br>- endpoints = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),<br>- "endpoint", AST_RETRIEVE_FLAG_MULTIPLE, var);<br>-<br>- if (endpoints) {<br>- /*<br>- * Because aors are a string list, we have to use a pattern match but since a simple<br>- * pattern match could return an endpoint that has an aor of "aaabccc" when searching<br>- * for "abc", we still have to iterate over them to find an exact aor match.<br>- */<br>- ao2_callback(endpoints, 0, aor_update_endpoint_state, (char *)aor_name);<br>- ao2_ref(endpoints, -1);<br>- }<br>- }<br>-<br>- return 0;<br>-}<br>-<br>-static int contact_update_endpoint_state(void *obj, void *arg, int flags)<br>-{<br>- const struct ast_sip_contact *contact = obj;<br>- struct timeval tv = ast_tvnow();<br>-<br>- if (!ast_strlen_zero(contact->endpoint_name) && ((int)(contact->qualify_frequency * 1000)) <= 0 &&<br>- contact->expiration_time.tv_sec > tv.tv_sec) {<br>-<br>- if (ast_sip_persistent_endpoint_update_state(contact->endpoint_name, AST_ENDPOINT_ONLINE) == -1) {<br>- ast_log(LOG_WARNING, "Unable to find persistent endpoint '%s' for contact '%s/%s'\n",<br>- contact->endpoint_name, contact->aor, contact->uri);<br>- return -1;<br>- }<br>- }<br>-<br>- return 0;<br>-}<br>-<br>-static void update_all_unqualified_endpoints(void)<br>-{<br>- struct ao2_container *aors;<br>- struct ao2_container *contacts;<br>- RAII_VAR(struct ast_variable *, var_aor, NULL, ast_variables_destroy);<br>- RAII_VAR(struct ast_variable *, var_contact, NULL, ast_variables_destroy);<br>- RAII_VAR(char *, time_now, NULL, ast_free);<br>- struct timeval tv = ast_tvnow();<br>-<br>- if (!(var_aor = ast_variable_new("contact !=", "", ""))) {<br>- return;<br>- }<br>- if (!(var_aor->next = ast_variable_new("qualify_frequency <=", "0", ""))) {<br>- return;<br>- }<br>-<br>- if (ast_asprintf(&time_now, "%ld", tv.tv_sec) == -1) {<br>- return;<br>- }<br>- if (!(var_contact = ast_variable_new("expiration_time >", time_now, ""))) {<br>- return;<br>- }<br>- if (!(var_contact->next = ast_variable_new("qualify_frequency <=", "0", ""))) {<br>- return;<br>- }<br>-<br>- aors = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),<br>- "aor", AST_RETRIEVE_FLAG_MULTIPLE, var_aor);<br>- if (aors) {<br>- ao2_callback(aors, OBJ_NODATA, on_aor_update_endpoint_state, NULL);<br>- ao2_ref(aors, -1);<br>- }<br>-<br>- contacts = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(),<br>- "contact", AST_RETRIEVE_FLAG_MULTIPLE, var_contact);<br>- if (contacts) {<br>- ao2_callback(contacts, OBJ_NODATA, contact_update_endpoint_state, NULL);<br>- ao2_ref(contacts, -1);<br>- }<br>-}<br>-<br>-int ast_res_pjsip_init_options_handling(int reload)<br>-{<br>- static const pj_str_t STR_OPTIONS = { "OPTIONS", 7 };<br>-<br>- if (reload) {<br>- qualify_and_schedule_all();<br>- return 0;<br>- }<br>-<br>- sched_qualifies = ao2_t_container_alloc(QUALIFIED_BUCKETS,<br>- sched_qualifies_hash_fn, sched_qualifies_cmp_fn,<br>- "Create container for scheduled qualifies");<br>- if (!sched_qualifies) {<br>- return -1;<br>- }<br>-<br>- if (pjsip_endpt_register_module(ast_sip_get_pjsip_endpoint(), &options_module) != PJ_SUCCESS) {<br>- ao2_cleanup(sched_qualifies);<br>- sched_qualifies = NULL;<br>- return -1;<br>- }<br>-<br>- if (pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_ALLOW,<br>- NULL, 1, &STR_OPTIONS) != PJ_SUCCESS) {<br>- pjsip_endpt_unregister_module(ast_sip_get_pjsip_endpoint(), &options_module);<br>- ao2_cleanup(sched_qualifies);<br>- sched_qualifies = NULL;<br>- return -1;<br>- }<br>-<br>- if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "aor", &observer_callbacks_options)) {<br>- pjsip_endpt_unregister_module(ast_sip_get_pjsip_endpoint(), &options_module);<br>- ao2_cleanup(sched_qualifies);<br>- sched_qualifies = NULL;<br>- return -1;<br>- }<br>-<br>- ast_sip_register_endpoint_formatter(&contact_status_formatter);<br>- ast_manager_register_xml("PJSIPQualify", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING, ami_sip_qualify);<br>- ast_manager_register_xml("PJSIPShowContacts", EVENT_FLAG_SYSTEM, ami_show_contacts);<br>- ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));<br>-<br>- update_all_unqualified_endpoints();<br>- qualify_and_schedule_all();<br>+ ao2_iterator_destroy(&it_aor);<br> <br> return 0;<br> }<br> <br> void ast_res_pjsip_cleanup_options_handling(void)<br> {<br>+ int remaining;<br>+ struct ast_taskprocessor *mgmt_serializer;<br>+<br> ast_cli_unregister_multiple(cli_options, ARRAY_LEN(cli_options));<br> ast_manager_unregister("PJSIPQualify");<br> ast_manager_unregister("PJSIPShowContacts");<br> ast_sip_unregister_endpoint_formatter(&contact_status_formatter);<br> <br>- ast_sorcery_observer_remove(ast_sip_get_sorcery(), "aor", &observer_callbacks_options);<br>+ ast_sorcery_observer_remove(ast_sip_get_sorcery(), "contact",<br>+ &contact_observer_callbacks);<br>+ ast_sorcery_observer_remove(ast_sip_get_sorcery(), "aor",<br>+ &aor_observer_callbacks);<br>+ ast_sorcery_observer_remove(ast_sip_get_sorcery(), "endpoint",<br>+ &endpoint_observer_callbacks);<br>+<br>+ mgmt_serializer = management_serializer;<br>+ management_serializer = NULL;<br>+ if (mgmt_serializer) {<br>+ ast_sip_push_task_wait_serializer(mgmt_serializer, sip_options_cleanup_task, NULL);<br>+ }<br>+<br>+ remaining = ast_serializer_shutdown_group_join(shutdown_group,<br>+ MAX_UNLOAD_TIMEOUT_TIME);<br>+ if (remaining) {<br>+ ast_log(LOG_WARNING, "Cleanup incomplete. Could not stop %d AORs.\n",<br>+ remaining);<br>+ }<br>+ ao2_cleanup(shutdown_group);<br>+ shutdown_group = NULL;<br>+<br>+ if (mgmt_serializer) {<br>+ ast_taskprocessor_unreference(mgmt_serializer);<br>+ }<br>+<br>+ ao2_cleanup(sip_options_aors);<br>+ sip_options_aors = NULL;<br>+ ao2_cleanup(sip_options_contact_statuses);<br>+ sip_options_contact_statuses = NULL;<br>+ ao2_cleanup(sip_options_endpoint_state_compositors);<br>+ sip_options_endpoint_state_compositors = NULL;<br>+<br> pjsip_endpt_unregister_module(ast_sip_get_pjsip_endpoint(), &options_module);<br>- ao2_cleanup(sched_qualifies);<br>- sched_qualifies = NULL;<br>+}<br>+<br>+/*!<br>+ * \brief Management task to finish setting up the environment.<br>+ * \note Run by management_serializer<br>+ */<br>+static int sip_options_init_task(void *mgmt_serializer)<br>+{<br>+ management_serializer = mgmt_serializer;<br>+<br>+ shutdown_group = ast_serializer_shutdown_group_alloc();<br>+ if (!shutdown_group) {<br>+ return -1;<br>+ }<br>+<br>+ if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "endpoint",<br>+ &endpoint_observer_callbacks)) {<br>+ return -1;<br>+ }<br>+ if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "aor",<br>+ &aor_observer_callbacks)) {<br>+ return -1;<br>+ }<br>+ if (ast_sorcery_observer_add(ast_sip_get_sorcery(), "contact",<br>+ &contact_observer_callbacks)) {<br>+ return -1;<br>+ }<br>+<br>+ sip_options_synchronize(0);<br>+<br>+ return 0;<br>+}<br>+<br>+int ast_res_pjsip_preinit_options_handling(void)<br>+{<br>+ sip_options_contact_statuses = sip_options_contact_statuses_alloc();<br>+ return sip_options_contact_statuses ? 0 : -1;<br>+}<br>+<br>+int ast_res_pjsip_init_options_handling(int reload)<br>+{<br>+ struct ast_taskprocessor *mgmt_serializer;<br>+<br>+ static const pj_str_t STR_OPTIONS = { "OPTIONS", 7 };<br>+<br>+ if (reload) {<br>+ sip_options_synchronize(1);<br>+ return 0;<br>+ }<br>+<br>+ if (pjsip_endpt_register_module(ast_sip_get_pjsip_endpoint(), &options_module)<br>+ != PJ_SUCCESS) {<br>+ return -1;<br>+ }<br>+<br>+ if (pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_ALLOW,<br>+ NULL, 1, &STR_OPTIONS) != PJ_SUCCESS) {<br>+ ast_res_pjsip_cleanup_options_handling();<br>+ return -1;<br>+ }<br>+<br>+ sip_options_aors = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, 0, AOR_BUCKETS,<br>+ sip_options_aor_hash_fn, NULL, sip_options_aor_cmp_fn);<br>+ if (!sip_options_aors) {<br>+ ast_res_pjsip_cleanup_options_handling();<br>+ return -1;<br>+ }<br>+ sip_options_endpoint_state_compositors =<br>+ ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, 0,<br>+ ENDPOINT_STATE_COMPOSITOR_BUCKETS,<br>+ sip_options_endpoint_state_compositor_hash_fn, NULL,<br>+ sip_options_endpoint_state_compositor_cmp_fn);<br>+ if (!sip_options_endpoint_state_compositors) {<br>+ ast_res_pjsip_cleanup_options_handling();<br>+ return -1;<br>+ }<br>+<br>+ mgmt_serializer = ast_sip_create_serializer("pjsip/options/manage");<br>+ if (!mgmt_serializer) {<br>+ ast_res_pjsip_cleanup_options_handling();<br>+ return -1;<br>+ }<br>+<br>+ /*<br>+ * Set the water mark levels high because we can get a flood of<br>+ * contact status updates from sip_options_synchronize() that<br>+ * quickly clears on initial load or reload.<br>+ */<br>+ ast_taskprocessor_alert_set_levels(mgmt_serializer, -1,<br>+ 10 * AST_TASKPROCESSOR_HIGH_WATER_LEVEL);<br>+<br>+ /*<br>+ * We make sure that the environment is completely setup before we allow<br>+ * any other threads to post contact_status updates to the<br>+ * management_serializer.<br>+ */<br>+ if (ast_sip_push_task_wait_serializer(mgmt_serializer, sip_options_init_task,<br>+ mgmt_serializer)) {<br>+ /* Set management_serializer in case pushing the task actually failed. */<br>+ management_serializer = mgmt_serializer;<br>+ ast_res_pjsip_cleanup_options_handling();<br>+ return -1;<br>+ }<br>+<br>+ ast_sip_register_endpoint_formatter(&contact_status_formatter);<br>+ ast_manager_register_xml("PJSIPQualify", EVENT_FLAG_SYSTEM | EVENT_FLAG_REPORTING,<br>+ ami_sip_qualify);<br>+ ast_manager_register_xml("PJSIPShowContacts", EVENT_FLAG_SYSTEM, ami_show_contacts);<br>+ ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));<br>+<br>+ return 0;<br> }<br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/8758">change 8758</a>. To unsubscribe, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/8758"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: merged </div>
<div style="display:none"> Gerrit-Change-Id: I6a5ebbfca9001dfe933eaeac4d3babd8d2e6f082 </div>
<div style="display:none"> Gerrit-Change-Number: 8758 </div>
<div style="display:none"> Gerrit-PatchSet: 4 </div>
<div style="display:none"> Gerrit-Owner: Richard Mudgett <rmudgett@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Corey Farrell <git@cfware.com> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins2 </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Kevin Harwell <kharwell@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Richard Mudgett <rmudgett@digium.com> </div>