[svn-commits] kharwell: branch group/pimp_my_sip r391888 - in /team/group/pimp_my_sip: incl...

SVN commits to the Digium repositories svn-commits at lists.digium.com
Fri Jun 14 15:39:24 CDT 2013


Author: kharwell
Date: Fri Jun 14 15:39:17 2013
New Revision: 391888

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=391888
Log:
SIP Qualify Support

Completed the work that had been started for SIP qualify support in the new
channel driver.  Qualify requests are now sent for every contact associated
with an endpoint.  When a contact is registered a check is done to make sure
it is available and the round trip time is stored for reference.  The contact
continues to be checked at a specified interval (qualify_frequency - now set
on the AOR) until it is removed.

A CLI command ("sip qualify <endpoint>") is also available that when issued
will send qualifies for all contacts on the given endpoint.  Also added CLI
command "sip show endpoint <endpoint>" that will show all the contacts and
their status for the given endpoint.

(closes issue ASTERISK-21499)
Reported by: Matt Jordan
Review: https://reviewboard.asterisk.org/r/2584/

Modified:
    team/group/pimp_my_sip/include/asterisk/res_sip.h
    team/group/pimp_my_sip/res/res_sip.c
    team/group/pimp_my_sip/res/res_sip/location.c
    team/group/pimp_my_sip/res/res_sip/sip_configuration.c
    team/group/pimp_my_sip/res/res_sip/sip_options.c
    team/group/pimp_my_sip/res/res_sip_registrar.c

Modified: team/group/pimp_my_sip/include/asterisk/res_sip.h
URL: http://svnview.digium.com/svn/asterisk/team/group/pimp_my_sip/include/asterisk/res_sip.h?view=diff&rev=391888&r1=391887&r2=391888
==============================================================================
--- team/group/pimp_my_sip/include/asterisk/res_sip.h (original)
+++ team/group/pimp_my_sip/include/asterisk/res_sip.h Fri Jun 14 15:39:17 2013
@@ -137,6 +137,36 @@
 	);
 	/*! Absolute time that this contact is no longer valid after */
 	struct timeval expiration_time;
+	/*! Frequency to send OPTIONS requests to contact. 0 is disabled. */
+	unsigned int qualify_frequency;
+	/*! If true authenticate the qualify if needed */
+	int authenticate_qualify;
+};
+
+#define CONTACT_STATUS "contact_status"
+
+/*!
+ * \brief Status type for a contact.
+ */
+enum ast_sip_contact_status_type {
+	UNAVAILABLE,
+	AVAILABLE
+};
+
+/*!
+ * \brief A contact's status.
+ *
+ * \detail Maintains a contact's current status and round trip time
+ *         if available.
+ */
+struct ast_sip_contact_status {
+	SORCERY_OBJECT(details);
+	/*! Current status for a contact (default - unavailable) */
+	enum ast_sip_contact_status_type status;
+	/*! The round trip start time set before sending a qualify request */
+	struct timeval rtt_start;
+	/*! The round trip time in microseconds */
+	int64_t rtt;
 };
 
 /*!
@@ -166,6 +196,10 @@
 	unsigned int maximum_expiration;
 	/*! Default contact expiration if one is not provided in the contact */
 	unsigned int default_expiration;
+	/*! Frequency to send OPTIONS requests to AOR contacts. 0 is disabled. */
+	unsigned int qualify_frequency;
+	/*! If true authenticate the qualify if needed */
+	int authenticate_qualify;
 	/*! Maximum number of external contacts, 0 to disable */
 	unsigned int max_contacts;
 	/*! Whether to remove any existing contacts not related to an incoming REGISTER when it comes in */
@@ -326,8 +360,6 @@
 	unsigned int sess_expires;
 	/*! List of outbound registrations */
 	AST_LIST_HEAD_NOLOCK(, ast_sip_registration) registrations;
-	/*! Frequency to send OPTIONS requests to endpoint. 0 is disabled. */
-	unsigned int qualify_frequency;
 	/*! Method(s) by which the endpoint should be identified. */
 	enum ast_sip_endpoint_identifier_type ident_method;
 	/*! Boolean indicating if direct_media is permissible */
@@ -589,6 +621,16 @@
 int ast_sip_initialize_sorcery_transport(struct ast_sorcery *sorcery);
 
 /*!
+ * \brief Initialize qualify support on a sorcery instance
+ *
+ * \param sorcery The sorcery instance
+ *
+ * \retval -1 failure
+ * \retval 0 success
+ */
+int ast_sip_initialize_sorcery_qualify(struct ast_sorcery *sorcery);
+
+/*!
  * \brief Initialize location support on a sorcery instance
  *
  * \param sorcery The sorcery instance

Modified: team/group/pimp_my_sip/res/res_sip.c
URL: http://svnview.digium.com/svn/asterisk/team/group/pimp_my_sip/res/res_sip.c?view=diff&rev=391888&r1=391887&r2=391888
==============================================================================
--- team/group/pimp_my_sip/res/res_sip.c (original)
+++ team/group/pimp_my_sip/res/res_sip.c Fri Jun 14 15:39:17 2013
@@ -53,7 +53,7 @@
 					It contains the core SIP related options only, endpoints are <emphasis>NOT</emphasis>
 					dialable entries of their own. Communication with another SIP device is
 					accomplished via Addresses of Record (AoRs) which have one or more
-					contacts assicated with them. Endpoints <emphasis>NOT</emphasis> configured to 
+					contacts assicated with them. Endpoints <emphasis>NOT</emphasis> configured to
 					use a <literal>transport</literal> will default to first transport found
 					in <filename>res_sip.conf</filename> that matches its type.
 					</para>
@@ -236,13 +236,6 @@
 				<configOption name="outbound_proxy">
 					<synopsis>Proxy through which to send requests</synopsis>
 				</configOption>
-				<configOption name="qualify_frequency" default="0">
-					<synopsis>Interval at which to qualify an endpoint</synopsis>
-					<description><para>
-						Interval between attempts to qualify the endpoint for reachability.
-						If <literal>0</literal> never qualify. Time in seconds.
-					</para></description>
-				</configOption>
 				<configOption name="rewrite_contact">
 					<synopsis>Allow Contact header to be rewritten with the source IP address-port</synopsis>
 				</configOption>
@@ -552,6 +545,35 @@
 					<synopsis>Time to keep alive a contact</synopsis>
 					<description><para>
 						Time to keep alive a contact. String style specification.
+					</para></description>
+				</configOption>
+				<configOption name="qualify_frequency" default="0">
+					<synopsis>Interval at which to qualify a contact</synopsis>
+					<description><para>
+						Interval between attempts to qualify the contact for reachability.
+						If <literal>0</literal> never qualify. Time in seconds.
+					</para></description>
+				</configOption>
+			</configObject>
+			<configObject name="contact_status">
+				<synopsis>Status for a contact</synopsis>
+				<description><para>
+					The contact status keeps track of whether or not a contact is reachable
+					and how long it took to qualify the contact (round trip time).
+				</para></description>
+				<configOption name="status">
+					<synopsis>A contact's status</synopsis>
+					<description>
+						<enumlist>
+							<enum name="AVAILABLE" />
+							<enum name="UNAVAILABLE" />
+						</enumlist>
+					</description>
+				</configOption>
+				<configOption name="rtt">
+					<synopsis>Round trip time</synopsis>
+					<description><para>
+						The time, in microseconds, it took to qualify the contact.
 					</para></description>
 				</configOption>
 			</configObject>
@@ -624,6 +646,20 @@
 				<configOption name="type">
 					<synopsis>Must be of type 'aor'.</synopsis>
 				</configOption>
+				<configOption name="qualify_frequency" default="0">
+					<synopsis>Interval at which to qualify an AoR</synopsis>
+					<description><para>
+						Interval between attempts to qualify the AoR for reachability.
+						If <literal>0</literal> never qualify. Time in seconds.
+					</para></description>
+				</configOption>
+				<configOption name="authenticate_qualify" default="no">
+					<synopsis>Authenticates a qualify request if needed</synopsis>
+					<description><para>
+						If true and a qualify request receives a challenge or authenticate response
+						authentication is attempted before declaring the contact available.
+					</para></description>
+				</configOption>
 			</configObject>
 		</configFile>
 	</configInfo>
@@ -1046,6 +1082,11 @@
 	pjsip_tpselector selector = { .type = PJSIP_TPSELECTOR_NONE, };
 
 	if (ast_strlen_zero(uri)) {
+		if (!endpoint) {
+			ast_log(LOG_ERROR, "An endpoint and/or uri must be specified\n");
+			return -1;
+		}
+
 		contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
 		if (!contact || ast_strlen_zero(contact->uri)) {
 			ast_log(LOG_ERROR, "Unable to retrieve contact for endpoint %s\n",
@@ -1058,10 +1099,12 @@
 		pj_cstr(&remote_uri, uri);
 	}
 
-	if (sip_get_tpselector_from_endpoint(endpoint, &selector)) {
-		ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport selector for endpoint %s\n",
+	if (endpoint) {
+		if (sip_get_tpselector_from_endpoint(endpoint, &selector)) {
+			ast_log(LOG_ERROR, "Unable to retrieve PJSIP transport selector for endpoint %s\n",
 				ast_sorcery_object_get_id(endpoint));
-		return -1;
+			return -1;
+		}
 	}
 
 	pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Outbound request", 256, 256);
@@ -1166,7 +1209,7 @@
 	pj_str_t hdr_name;
 	pj_str_t hdr_value;
 	pjsip_generic_string_hdr *hdr;
-	
+
 	pj_cstr(&hdr_name, name);
 	pj_cstr(&hdr_value, value);
 
@@ -1185,7 +1228,7 @@
 	pj_cstr(&type, body->type);
 	pj_cstr(&subtype, body->subtype);
 	pj_cstr(&body_text, body->body_text);
-	
+
 	return pjsip_msg_body_create(pool, &type, &subtype, &body_text);
 }
 

Modified: team/group/pimp_my_sip/res/res_sip/location.c
URL: http://svnview.digium.com/svn/asterisk/team/group/pimp_my_sip/res/res_sip/location.c?view=diff&rev=391888&r1=391887&r2=391888
==============================================================================
--- team/group/pimp_my_sip/res/res_sip/location.c (original)
+++ team/group/pimp_my_sip/res/res_sip/location.c Fri Jun 14 15:39:17 2013
@@ -235,6 +235,8 @@
 
 	ast_string_field_set(contact, uri, uri);
 	contact->expiration_time = expiration_time;
+	contact->qualify_frequency = aor->qualify_frequency;
+	contact->authenticate_qualify = aor->authenticate_qualify;
 
 	return ast_sorcery_create(ast_sip_get_sorcery(), contact);
 }
@@ -294,11 +296,15 @@
 	ast_sorcery_object_field_register(sorcery, "contact", "type", "", OPT_NOOP_T, 0, 0);
 	ast_sorcery_object_field_register(sorcery, "contact", "uri", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, uri));
 	ast_sorcery_object_field_register_custom(sorcery, "contact", "expiration_time", "", expiration_str2struct, expiration_struct2str, 0, 0);
+	ast_sorcery_object_field_register(sorcery, "contact", "qualify_frequency", 0, OPT_UINT_T,
+					  PARSE_IN_RANGE, FLDSET(struct ast_sip_contact, qualify_frequency), 0, 86400);
 
 	ast_sorcery_object_field_register(sorcery, "aor", "type", "", OPT_NOOP_T, 0, 0);
 	ast_sorcery_object_field_register(sorcery, "aor", "minimum_expiration", "60", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, minimum_expiration));
 	ast_sorcery_object_field_register(sorcery, "aor", "maximum_expiration", "7200", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, maximum_expiration));
 	ast_sorcery_object_field_register(sorcery, "aor", "default_expiration", "3600", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, default_expiration));
+	ast_sorcery_object_field_register(sorcery, "aor", "qualify_frequency", 0, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_aor, qualify_frequency), 0, 86400);
+	ast_sorcery_object_field_register(sorcery, "aor", "authenticate_qualify", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, authenticate_qualify));
 	ast_sorcery_object_field_register(sorcery, "aor", "max_contacts", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, max_contacts));
 	ast_sorcery_object_field_register(sorcery, "aor", "remove_existing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_aor, remove_existing));
 	ast_sorcery_object_field_register_custom(sorcery, "aor", "contact", "", permanent_uri_handler, NULL, 0, 0);

Modified: team/group/pimp_my_sip/res/res_sip/sip_configuration.c
URL: http://svnview.digium.com/svn/asterisk/team/group/pimp_my_sip/res/res_sip/sip_configuration.c?view=diff&rev=391888&r1=391887&r2=391888
==============================================================================
--- team/group/pimp_my_sip/res/res_sip/sip_configuration.c (original)
+++ team/group/pimp_my_sip/res/res_sip/sip_configuration.c Fri Jun 14 15:39:17 2013
@@ -57,8 +57,93 @@
 	return CLI_SUCCESS;
 }
 
+static int show_contact(void *obj, void *arg, int flags)
+{
+	struct ast_sip_contact *contact = obj;
+	struct ast_cli_args *a = arg;
+	RAII_VAR(struct ast_sip_contact_status *, status, ast_sorcery_retrieve_by_id(
+			 ast_sip_get_sorcery(), CONTACT_STATUS,
+			 ast_sorcery_object_get_id(contact)), ao2_cleanup);
+
+	ast_cli(a->fd, "\tContact %s:\n", contact->uri);
+
+	if (!status) {
+		ast_cli(a->fd, "\tStatus not found!\n");
+		return 0;
+	}
+
+	ast_cli(a->fd, "\t\tavailable = %s\n", status->status ? "yes" : "no");
+
+	if (status->status) {
+		ast_cli(a->fd, "\t\tRTT = %lld microseconds\n", (long long)status->rtt);
+	}
+
+	return 0;
+}
+
+static void show_endpoint(struct ast_sip_endpoint *endpoint, struct ast_cli_args *a)
+{
+	char *aor_name, *aors;
+
+	if (ast_strlen_zero(endpoint->aors)) {
+		return;
+	}
+
+	aors = ast_strdupa(endpoint->aors);
+
+	while ((aor_name = strsep(&aors, ","))) {
+		RAII_VAR(struct ast_sip_aor *, aor,
+			 ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
+		RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
+
+		if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) {
+			continue;
+		}
+
+		ast_cli(a->fd, "AOR %s:\n", ast_sorcery_object_get_id(aor));
+		ao2_callback(contacts, OBJ_NODATA, show_contact, a);
+	}
+
+	return;
+}
+
+static char *cli_show_endpoint(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
+	const char *endpoint_name;
+
+	switch (cmd) {
+	case CLI_INIT:
+		e->command = "sip show endpoint";
+		e->usage =
+			"Usage: sip show endpoint <endpoint>\n"
+			"       Show the given SIP endpoint.\n";
+		return NULL;
+	case CLI_GENERATE:
+		return NULL;
+	}
+
+	if (a->argc != 4) {
+		return CLI_SHOWUSAGE;
+	}
+
+	endpoint_name = a->argv[3];
+
+	if (!(endpoint = ast_sorcery_retrieve_by_id(
+		      ast_sip_get_sorcery(), "endpoint", endpoint_name))) {
+		ast_cli(a->fd, "Unable to retrieve endpoint %s\n", endpoint_name);
+		return CLI_FAILURE;
+	}
+
+	ast_cli(a->fd, "Endpoint %s:\n", endpoint_name);
+	show_endpoint(endpoint, a);
+
+	return CLI_SUCCESS;
+}
+
 static struct ast_cli_entry cli_commands[] = {
 	AST_CLI_DEFINE(handle_cli_show_endpoints, "Show SIP Endpoints"),
+	AST_CLI_DEFINE(cli_show_endpoint, "Show SIP Endpoint")
 };
 
 static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
@@ -393,7 +478,6 @@
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "context", "default", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, context));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "disallow", "", OPT_CODEC_T, 0, FLDSET(struct ast_sip_endpoint, prefs, codecs));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow", "", OPT_CODEC_T, 1, FLDSET(struct ast_sip_endpoint, prefs, codecs));
-	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "qualify_frequency", 0, OPT_UINT_T, PARSE_IN_RANGE, FLDSET(struct ast_sip_endpoint, qualify_frequency), 0, 86400);
 	ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "dtmfmode", "rfc4733", dtmf_handler, NULL, 0, 0);
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_ipv6", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, rtp_ipv6));
 	ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_symmetric", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, rtp_symmetric));
@@ -451,6 +535,13 @@
 		return -1;
 	}
 
+	if (ast_sip_initialize_sorcery_qualify(sip_sorcery)) {
+		ast_log(LOG_ERROR, "Failed to register SIP qualify support with sorcery\n");
+		ast_sorcery_unref(sip_sorcery);
+		sip_sorcery = NULL;
+		return -1;
+	}
+
 	if (ast_sip_initialize_sorcery_domain_alias(sip_sorcery)) {
 		ast_log(LOG_ERROR, "Failed to register SIP domain aliases support with sorcery\n");
 		ast_sorcery_unref(sip_sorcery);
@@ -548,4 +639,3 @@
 {
 	return sip_sorcery;
 }
-

Modified: team/group/pimp_my_sip/res/res_sip/sip_options.c
URL: http://svnview.digium.com/svn/asterisk/team/group/pimp_my_sip/res/res_sip/sip_options.c?view=diff&rev=391888&r1=391887&r2=391888
==============================================================================
--- team/group/pimp_my_sip/res/res_sip/sip_options.c (original)
+++ team/group/pimp_my_sip/res/res_sip/sip_options.c Fri Jun 14 15:39:17 2013
@@ -1,8 +1,19 @@
 /*
- * sip_options.c
+ * Asterisk -- An open source telephony toolkit.
  *
- *  Created on: Jan 25, 2013
- *      Author: mjordan
+ * Copyright (C) 2013, Digium, Inc.
+ *
+ * Matt Jordan <mjordan at digium.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
  */
 
 #include "asterisk.h"
@@ -16,41 +27,429 @@
 #include "asterisk/pbx.h"
 #include "asterisk/astobj2.h"
 #include "asterisk/cli.h"
+#include "asterisk/time.h"
 #include "include/res_sip_private.h"
 
 #define DEFAULT_LANGUAGE "en"
 #define DEFAULT_ENCODING "text/plain"
 #define QUALIFIED_BUCKETS 211
 
-/*! \brief Scheduling context for qualifies */
-static struct ast_sched_context *sched; /* XXX move this to registrar */
-
-struct ao2_container *scheduled_qualifies;
-
-struct qualify_info {
-	AST_DECLARE_STRING_FIELDS(
-		AST_STRING_FIELD(endpoint_id);
-	);
-	char *scheduler_data;
-	int scheduler_id;
+static int qualify_contact(struct ast_sip_contact *contact);
+
+/*!
+ * \internal
+ * \brief Create a ast_sip_contact_status object.
+ */
+static void *contact_status_alloc(const char *name)
+{
+	struct ast_sip_contact_status *status = ao2_alloc_options(
+		sizeof(*status), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
+
+	if (!status) {
+		ast_log(LOG_ERROR, "Unable to allocate ast_sip_contact_status\n");
+		return NULL;
+	}
+
+	status->status = UNAVAILABLE;
+
+	return status;
+}
+
+/*!
+ * \internal
+ * \brief Retrieve a ast_sip_contact_status object from sorcery creating
+ *        one if not found.
+ */
+static struct ast_sip_contact_status *find_or_create_contact_status(const struct ast_sip_contact *contact)
+{
+	struct ast_sip_contact_status *status = ast_sorcery_retrieve_by_id(
+		ast_sip_get_sorcery(), CONTACT_STATUS,
+		ast_sorcery_object_get_id(contact));
+
+	if (status) {
+		return status;
+	}
+
+	if (!(status = ast_sorcery_alloc(
+		      ast_sip_get_sorcery(), CONTACT_STATUS,
+		      ast_sorcery_object_get_id(contact)))) {
+
+		ast_log(LOG_ERROR, "Unable to create ast_sip_contact_status for contact %s\n",
+			contact->uri);
+		return NULL;
+	}
+
+	if (ast_sorcery_create(ast_sip_get_sorcery(), status)) {
+		ast_log(LOG_ERROR, "Unable to persist ast_sip_contact_status for contact %s\n",
+			contact->uri);
+		return NULL;
+	}
+
+	return status;
+}
+
+/*!
+ * \internal
+ * \brief Update an ast_sip_contact_status's elements.
+ */
+static void update_contact_status(const struct ast_sip_contact *contact,
+				  enum ast_sip_contact_status_type value)
+{
+	RAII_VAR(struct ast_sip_contact_status *, status,
+		 find_or_create_contact_status(contact), ao2_cleanup);
+
+	RAII_VAR(struct ast_sip_contact_status *, update, ast_sorcery_alloc(
+		      ast_sip_get_sorcery(), CONTACT_STATUS,
+		      ast_sorcery_object_get_id(status)), ao2_cleanup);
+
+	if (!update) {
+		ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n",
+			contact->uri);
+		return;
+	}
+
+	update->status = value;
+
+	/* if the contact is available calculate the rtt as
+	   the diff between the last start time and "now" */
+	update->rtt = update->status ?
+		ast_tvdiff_us(ast_tvnow(), status->rtt_start) : 0;
+
+	update->rtt_start = ast_tv(0, 0);
+
+	if (ast_sorcery_update(ast_sip_get_sorcery(), update)) {
+		ast_log(LOG_ERROR, "Unable to update ast_sip_contact_status for contact %s\n",
+			contact->uri);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Initialize the start time on a contact status so the round
+ *        trip time can be calculated upon a valid response.
+ */
+static void init_start_time(const struct ast_sip_contact *contact)
+{
+	RAII_VAR(struct ast_sip_contact_status *, status,
+		 find_or_create_contact_status(contact), ao2_cleanup);
+
+	RAII_VAR(struct ast_sip_contact_status *, update, ast_sorcery_alloc(
+		      ast_sip_get_sorcery(), CONTACT_STATUS,
+		      ast_sorcery_object_get_id(status)), ao2_cleanup);
+
+	if (!update) {
+		ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n",
+			contact->uri);
+		return;
+	}
+
+	update->rtt_start = ast_tvnow();
+
+	if (ast_sorcery_update(ast_sip_get_sorcery(), update)) {
+		ast_log(LOG_ERROR, "Unable to update ast_sip_contact_status for contact %s\n",
+			contact->uri);
+	}
+}
+
+/*!
+ * \internal
+ * \brief For an endpoint try to match on a given contact.
+ */
+static int on_endpoint(void *obj, void *arg, int flags)
+{
+	struct ast_sip_endpoint *endpoint = obj;
+	char *aor_name, *aors;
+
+	if (!arg || ast_strlen_zero(endpoint->aors)) {
+		return 0;
+	}
+
+	aors = ast_strdupa(endpoint->aors);
+
+	while ((aor_name = strsep(&aors, ","))) {
+		RAII_VAR(struct ast_sip_aor *, aor,
+			 ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
+		RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
+
+		if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) {
+			continue;
+		}
+
+		if (ao2_find(contacts, arg, OBJ_NODATA | OBJ_POINTER)) {
+			return CMP_MATCH;
+		}
+	}
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Find endpoints associated with the given contact.
+ */
+static struct ao2_container *find_endpoints(struct ast_sip_contact *contact)
+{
+	RAII_VAR(struct ao2_container *, endpoints,
+		 ast_res_sip_get_endpoints(), ao2_cleanup);
+
+	return ao2_callback(endpoints, OBJ_MULTIPLE, on_endpoint, contact);
+}
+
+/*!
+ * \internal
+ * \brief Receive an response to the qualify contact request.
+ */
+static void qualify_contact_cb(void *token, pjsip_event *e)
+{
+	RAII_VAR(struct ast_sip_contact *, contact, token, ao2_cleanup);
+	RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup);
+	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
+
+	pjsip_transaction *tsx = e->body.tsx_state.tsx;
+	pjsip_rx_data *challenge = e->body.tsx_state.src.rdata;
+	pjsip_tx_data *tdata;
+
+	switch(e->body.tsx_state.type) {
+	case PJSIP_EVENT_TRANSPORT_ERROR:
+	case PJSIP_EVENT_TIMER:
+		update_contact_status(contact, UNAVAILABLE);
+		return;
+	default:
+		break;
+	}
+
+	if (!contact->authenticate_qualify || (tsx->status_code != 401 &&
+					       tsx->status_code != 407)) {
+		update_contact_status(contact, AVAILABLE);
+		return;
+	}
+
+	/* try to find endpoints that are associated with the contact */
+	if (!(endpoints = find_endpoints(contact))) {
+		ast_log(LOG_ERROR, "No endpoints found for contact %s, cannot authenticate",
+			contact->uri);
+		return;
+	}
+
+	/* find "first" endpoint in order to authenticate - actually any
+	   endpoint should do that matched on the contact */
+	endpoint = ao2_callback(endpoints, 0, NULL, NULL);
+
+	if (!ast_sip_create_request_with_auth(endpoint->sip_outbound_auths,
+					      endpoint->num_outbound_auths,
+					      challenge, tsx, &tdata)) {
+		pjsip_endpt_send_request(ast_sip_get_pjsip_endpoint(), tdata,
+					 -1, NULL, NULL);
+	}
+}
+
+/*!
+ * \internal
+ * \brief Attempt to qualify the contact
+ *
+ * \detail Sends a SIP OPTIONS request to the given contact in order to make
+ *         sure that contact is available.
+ */
+static int qualify_contact(struct ast_sip_contact *contact)
+{
+	pjsip_tx_data *tdata;
+
+	if (ast_sip_create_request("OPTIONS", NULL, NULL, contact->uri, &tdata)) {
+		ast_log(LOG_ERROR, "Unable to create request to qualify contact %s\n",
+			contact->uri);
+		return -1;
+	}
+
+	init_start_time(contact);
+
+	ao2_ref(contact, +1);
+	if (pjsip_endpt_send_request(ast_sip_get_pjsip_endpoint(),
+				     tdata, -1, contact, qualify_contact_cb) != PJ_SUCCESS) {
+		pjsip_tx_data_dec_ref(tdata);
+		ast_log(LOG_ERROR, "Unable to send request to qualify contact %s\n",
+			contact->uri);
+		ao2_ref(contact, -1);
+		return -1;
+	}
+
+	return 0;
+}
+
+/*!
+ * \internal
+ * \brief Scheduling context for sending QUALIFY request at specified intervals.
+ */
+static struct ast_sched_context *sched;
+
+/*!
+ * \internal
+ * \brief Container to hold all actively scheduled qualifies.
+ */
+static struct ao2_container *sched_qualifies;
+
+/*!
+ * \internal
+ * \brief Structure to hold qualify contact scheduling information.
+ */
+struct sched_data {
+	/*! The scheduling id */
+	int id;
+	/*! The the contact being checked */
+	struct ast_sip_contact *contact;
 };
 
-static pj_bool_t options_module_start(void);
-static pj_bool_t options_module_stop(void);
-static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata);
-static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata);
-
-static pjsip_module options_module = {
-	.name = {"Options Module", 14},
-	.id = -1,
-	.priority = PJSIP_MOD_PRIORITY_APPLICATION,
-	.start = options_module_start,
-	.stop = options_module_stop,
-	.on_rx_request = options_module_on_rx_request,
-	.on_rx_response = options_module_on_rx_response,
+/*!
+ * \internal
+ * \brief Destroy the scheduled data and remove from scheduler.
+ */
+static void sched_data_destructor(void *obj)
+{
+	struct sched_data *data = obj;
+	ao2_cleanup(data->contact);
+}
+/*!
+ * \internal
+ * \brief Create the scheduling data object.
+ */
+static struct sched_data *sched_data_create(struct ast_sip_contact *contact)
+{
+	struct sched_data *data = ao2_alloc(sizeof(*data), sched_data_destructor);
+
+	if (!data) {
+		ast_log(LOG_ERROR, "Unable to create schedule qualify data\n");
+		return NULL;
+	}
+
+	data->contact = contact;
+	ao2_ref(data->contact, +1);
+
+	return data;
+}
+
+/*!
+ * \internal
+ * \brief Send a qualify contact request within a threaded task.
+ */
+static int qualify_contact_task(void *obj)
+{
+	RAII_VAR(struct ast_sip_contact *, contact, obj, ao2_cleanup);
+	return qualify_contact(contact);
+}
+
+/*!
+ * \internal
+ * \brief Send a scheduled qualify contact request.
+ */
+static int qualify_contact_sched(const void *obj)
+{
+	struct sched_data *data = (struct sched_data *)obj;
+
+	ao2_ref(data->contact, +1);
+	if (ast_sip_push_task(NULL, qualify_contact_task, data->contact)) {
+		ao2_ref(data->contact, -1);
+		ao2_cleanup(data);
+		return 0;
+	}
+
+	return data->contact->qualify_frequency * 1000;
+}
+
+/*!
+ * \internal
+ * \brief Set up a scheduled qualify contact check.
+ */
+static void schedule_qualify(struct ast_sip_contact *contact)
+{
+	RAII_VAR(struct sched_data *, data, sched_data_create(contact), ao2_cleanup);
+
+	if (!data) {
+		return;
+	}
+
+	ao2_ref(data, +1);
+	if ((data->id = ast_sched_add_variable(
+		    sched, contact->qualify_frequency * 1000,
+		    qualify_contact_sched, data, 1)) < 0) {
+
+		ao2_ref(data, -1);
+		ast_log(LOG_ERROR, "Unable to schedule qualify for contact %s\n",
+			contact->uri);
+		return;
+	}
+
+	ao2_link(sched_qualifies, data);
+}
+
+/*!
+ * \internal
+ * \brief Remove the contact from the scheduler.
+ */
+static void unschedule_qualify(struct ast_sip_contact *contact)
+{
+	RAII_VAR(struct sched_data *, data, ao2_find(
+			 sched_qualifies, contact, OBJ_UNLINK), ao2_cleanup);
+
+	if (!data) {
+		return;
+	}
+
+	AST_SCHED_DEL_UNREF(sched, data->id, ao2_cleanup(data));
+}
+
+/*!
+ * \internal
+ * \brief Qualify the given contact and set up scheduling if configured.
+ */
+static void qualify_and_schedule(struct ast_sip_contact *contact)
+{
+	unschedule_qualify(contact);
+
+	if (contact->qualify_frequency) {
+		ao2_ref(contact, +1);
+		ast_sip_push_task(NULL, qualify_contact_task, contact);
+
+		schedule_qualify(contact);
+	}
+}
+
+/*!
+ * \internal
+ * \brief A new contact has been created make sure it is available.
+ */
+static void contact_created(const void *obj)
+{
+	qualify_and_schedule((struct ast_sip_contact *)obj);
+}
+
+/*!
+ * \internal
+ * \brief A contact has been deleted remove status tracking.
+ */
+static void contact_deleted(const void *obj)
+{
+	struct ast_sip_contact *contact = (struct ast_sip_contact *)obj;
+	RAII_VAR(struct ast_sip_contact_status *, status, NULL, ao2_cleanup);
+
+	unschedule_qualify(contact);
+
+	if (!(status = ast_sorcery_retrieve_by_id(
+		      ast_sip_get_sorcery(), CONTACT_STATUS,
+		      ast_sorcery_object_get_id(contact)))) {
+		return;
+	}
+
+	if (ast_sorcery_delete(ast_sip_get_sorcery(), status)) {
+		ast_log(LOG_ERROR, "Unable to delete ast_sip_contact_status for contact %s\n",
+			contact->uri);
+	}
+}
+
+struct ast_sorcery_observer contact_observer = {
+	.created = contact_created,
+	.deleted = contact_deleted
 };
 
-static pj_bool_t options_module_start(void)
+static pj_bool_t options_start(void)
 {
 	if (!(sched = ast_sched_context_create()) ||
 	    ast_sched_start_thread(sched)) {
@@ -60,9 +459,11 @@
 	return PJ_SUCCESS;
 }
 
-static pj_bool_t options_module_stop(void)
-{
-	ao2_t_ref(scheduled_qualifies, -1, "Remove scheduled qualifies on module stop");
+static pj_bool_t options_stop(void)
+{
+	ast_sorcery_observer_remove(ast_sip_get_sorcery(), "contact", &contact_observer);
+
+	ao2_t_ref(sched_qualifies, -1, "Remove scheduled qualifies on module stop");
 
 	if (sched) {
 		ast_sched_context_destroy(sched);
@@ -71,18 +472,20 @@
 	return PJ_SUCCESS;
 }
 
-static pj_status_t send_options_response(pjsip_rx_data *rdata, pjsip_dialog *pj_dlg, int code)
+static pj_status_t send_options_response(pjsip_rx_data *rdata, int code)
 {
 	pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint();
-	pjsip_transaction *pj_trans = pjsip_rdata_get_tsx(rdata);
+	pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
+	pjsip_transaction *trans = pjsip_rdata_get_tsx(rdata);
 	pjsip_tx_data *tdata;
 	const pjsip_hdr *hdr;
 	pjsip_response_addr res_addr;
 	pj_status_t status;
 
 	/* Make the response object */
-	status = pjsip_endpt_create_response(endpt, rdata, code, NULL, &tdata);
-	if (status != PJ_SUCCESS) {
+	if ((status = pjsip_endpt_create_response(
+		     endpt, rdata, code, NULL, &tdata) != PJ_SUCCESS)) {
+		ast_log(LOG_ERROR, "Unable to create response (%d)\n", status);
 		return status;
 	}
 
@@ -106,262 +509,267 @@
 	ast_sip_add_header(tdata, "Accept-Encoding", DEFAULT_ENCODING);
 	ast_sip_add_header(tdata, "Accept-Language", DEFAULT_LANGUAGE);
 
-	if (pj_dlg && pj_trans) {
-		status = pjsip_dlg_send_response(pj_dlg, pj_trans, tdata);
+	if (dlg && trans) {
+		status = pjsip_dlg_send_response(dlg, trans, tdata);
 	} else {
 		/* Get where to send request. */
-		status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
-		if (status != PJ_SUCCESS) {
+		if ((status = pjsip_get_response_addr(
+			     tdata->pool, rdata, &res_addr)) != PJ_SUCCESS) {
+			ast_log(LOG_ERROR, "Unable to get response address (%d)\n",
+				status);
+
 			pjsip_tx_data_dec_ref(tdata);
 			return status;
 		}
-		status = pjsip_endpt_send_response(endpt, &res_addr, tdata, NULL, NULL);
+		status = pjsip_endpt_send_response(endpt, &res_addr, tdata,
+						   NULL, NULL);
+	}
+
+	if (status != PJ_SUCCESS) {
+		ast_log(LOG_ERROR, "Unable to send response (%d)\n", status);
 	}
 
 	return status;
 }
 
-static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata)
+static pj_bool_t options_on_rx_request(pjsip_rx_data *rdata)
 {
 	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
-	pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
 	pjsip_uri *ruri;
 	pjsip_sip_uri *sip_ruri;
 	char exten[AST_MAX_EXTENSION];
 
-	if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_options_method)) {
+	if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method,
+			     &pjsip_options_method)) {
 		return PJ_FALSE;
 	}
-	endpoint = ast_pjsip_rdata_get_endpoint(rdata);
-	ast_assert(endpoint != NULL);
+
+	if (!(endpoint = ast_pjsip_rdata_get_endpoint(rdata))) {
+		return PJ_FALSE;
+	}
 
 	ruri = rdata->msg_info.msg->line.req.uri;
 	if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) {
-		send_options_response(rdata, dlg, 416);
-		return -1;
-	}
-	
+		send_options_response(rdata, 416);
+		return -1;
+	}
+
 	sip_ruri = pjsip_uri_get_uri(ruri);
 	ast_copy_pj_str(exten, &sip_ruri->user, sizeof(exten));
 
 	if (ast_shutting_down()) {
-		send_options_response(rdata, dlg, 503);
+		send_options_response(rdata, 503);
 	} else if (!ast_exists_extension(NULL, endpoint->context, exten, 1, NULL)) {
-		send_options_response(rdata, dlg, 404);
+		send_options_response(rdata, 404);
 	} else {
-		send_options_response(rdata, dlg, 200);
+		send_options_response(rdata, 200);
 	}
 	return PJ_TRUE;
 }
 
-static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata)
-{
-
-	return PJ_SUCCESS;
-}
-
-static int qualify_info_hash_fn(const void *obj, int flags)
-{
-	const struct qualify_info *info = obj;
-	const char *endpoint_id = flags & OBJ_KEY ? obj : info->endpoint_id;
-
-	return ast_str_hash(endpoint_id);
-}
-
-static int qualify_info_cmp_fn(void *obj, void *arg, int flags)
-{
-	struct qualify_info *left = obj;
-	struct qualify_info *right = arg;
-	const char *right_endpoint_id = flags & OBJ_KEY ? arg : right->endpoint_id;
-
-	return strcmp(left->endpoint_id, right_endpoint_id) ? 0 : CMP_MATCH | CMP_STOP;
-}
-
-
-static void qualify_info_destructor(void *obj)
-{
-	struct qualify_info *info = obj;
-	if (!info) {
-		return;
-	}
-	ast_string_field_free_memory(info);
-	/* Cancel the qualify */
-	if (!AST_SCHED_DEL(sched, info->scheduler_id)) {
-		/* If we successfully deleted the qualify, we got it before it
-		 * fired. We can safely delete the data that was passed to it.
-		 * Otherwise, we're getting deleted while this is firing - don't
-		 * touch that memory!
-		 */
-		ast_free(info->scheduler_data);
-	}
-}
-
-static struct qualify_info *create_qualify_info(struct ast_sip_endpoint *endpoint)
-{
-	struct qualify_info *info;
-
-	info = ao2_alloc(sizeof(*info), qualify_info_destructor);
-	if (!info) {
-		return NULL;
-	}
-
-	if (ast_string_field_init(info, 64)) {
-		ao2_ref(info, -1);
-		return NULL;
-	}
-	ast_string_field_set(info, endpoint_id, ast_sorcery_object_get_id(endpoint));
-
-	return info;
-}
-
-static int send_qualify_request(void *data)
-{
-	struct ast_sip_endpoint *endpoint = data;
-	pjsip_tx_data *tdata;
-	/* YAY! Send an OPTIONS request. */
-
-	ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata);
-	ast_sip_send_request(tdata, NULL, endpoint);
-
-	ao2_cleanup(endpoint);
+static pjsip_module options_module = {
+	.name = {"Options Module", 14},
+	.id = -1,
+	.priority = PJSIP_MOD_PRIORITY_APPLICATION,
+	.start = options_start,
+	.stop = options_stop,
+	.on_rx_request = options_on_rx_request,
+};
+
+/*!
+ * \internal
+ * \brief Send qualify request to the given contact.
+ */
+static int cli_on_contact(void *obj, void *arg, int flags)
+{
+	struct ast_sip_contact *contact = obj;
+	struct ast_cli_args *a = arg;
+	ast_cli(a->fd, " contact %s\n", contact->uri);
+	qualify_contact(contact);
 	return 0;
 }
 
-static int qualify_endpoint_scheduler_cb(const void *data)
-{
-	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
-	struct ast_sorcery *sorcery;
-	char *endpoint_id = (char *)data;
-
-	sorcery = ast_sip_get_sorcery();
-	if (!sorcery) {
-		ast_free(endpoint_id);
-		return 0;
-	}
-
-	endpoint = ast_sorcery_retrieve_by_id(sorcery, "endpoint", endpoint_id);
-	if (!endpoint) {
-		/* Whoops, endpoint went away */
-		ast_free(endpoint_id);
-		return 0;
-	}
-
-	ast_sip_push_task(NULL, send_qualify_request, endpoint);
-
-	return 1;
-}
-
-static void schedule_qualifies(void)
-{
-	RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup);
-	struct ao2_iterator it_endpoints;
-	struct ast_sip_endpoint *endpoint;
-	struct qualify_info *info;
-	char *endpoint_id;
-
-	endpoints = ast_res_sip_get_endpoints();
-	if (!endpoints) {
-		return;
-	}
-
-	it_endpoints = ao2_iterator_init(endpoints, 0);
-	while ((endpoint = ao2_iterator_next(&it_endpoints))) {
-		if (endpoint->qualify_frequency) {
-			/* XXX TODO: This really should only qualify registered peers,
-			 * which means we need a registrar. We should check the
-			 * registrar to see if this endpoint has registered and, if
-			 * not, pass on it.
-			 *
-			 * Actually, all of this should just get moved into the registrar.
-			 * Otherwise, the registar will have to kick this off when a
-			 * new endpoint registers, so it just makes sense to have it
-			 * all live there.
-			 */
-			info = create_qualify_info(endpoint);
-			if (!info) {
-				ao2_ref(endpoint, -1);
-				break;
-			}
-			endpoint_id = ast_strdup(info->endpoint_id);
-			if (!endpoint_id) {
-				ao2_t_ref(info, -1, "Dispose of info on off nominal");
-				ao2_ref(endpoint, -1);
-				break;
-			}
-			info->scheduler_data = endpoint_id;
-			info->scheduler_id = ast_sched_add_variable(sched, endpoint->qualify_frequency * 1000, qualify_endpoint_scheduler_cb, endpoint_id, 1);
-			ao2_t_link(scheduled_qualifies, info, "Link scheduled qualify information into container");
-			ao2_t_ref(info, -1, "Dispose of creation ref");
+/*!
+ * \internal
+ * \brief For an endpoint iterate over and qualify all aors/contacts
+ */
+static void cli_qualify_contacts(struct ast_cli_args *a, const char *endpoint_name,
+				 struct ast_sip_endpoint *endpoint)
+{
+	char *aor_name, *aors;
+
+	if (ast_strlen_zero(endpoint->aors)) {
+		ast_cli(a->fd, "Endpoint %s has no AoR's configured\n",
+			endpoint_name);
+		return;
+	}
+
+	aors = ast_strdupa(endpoint->aors);
+
+	while ((aor_name = strsep(&aors, ","))) {
+		RAII_VAR(struct ast_sip_aor *, aor,
+			 ast_sip_location_retrieve_aor(aor_name), ao2_cleanup);
+		RAII_VAR(struct ao2_container *, contacts, NULL, ao2_cleanup);
+
+		if (!aor || !(contacts = ast_sip_location_retrieve_aor_contacts(aor))) {
+			continue;
 		}
-		ao2_t_ref(endpoint, -1, "Dispose of iterator ref");
-	}
-	ao2_iterator_destroy(&it_endpoints);
-}
-
-static char *send_options(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+
+		ast_cli(a->fd, "Sending qualify to endpoint %s", endpoint_name);
+		ao2_callback(contacts, OBJ_NODATA, cli_on_contact, a);
+	}
+}
+
+static char *cli_qualify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
 	RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
 	const char *endpoint_name;
-	pjsip_tx_data *tdata;
 
 	switch (cmd) {
 	case CLI_INIT:
-		e->command = "sip send options";
+		e->command = "sip qualify";
 		e->usage =
-			"Usage: sip send options <endpoint>\n"
-			"       Send a SIP OPTIONS request to the specified endpoint.\n";
+			"Usage: sip qualify <endpoint>\n"
+			"       Send a SIP OPTIONS request to all contacts on the endpoint.\n";
 		return NULL;
 	case CLI_GENERATE:
 		return NULL;
 	}
 
-	if (a->argc != 4) {
+	if (a->argc != 3) {
 		return CLI_SHOWUSAGE;
 	}
 
-	endpoint_name = a->argv[3];
-
-	endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
-	if (!endpoint) {
-		ast_log(LOG_ERROR, "Unable to retrieve endpoint %s\n", endpoint_name);
+	endpoint_name = a->argv[2];
+
+	if (!(endpoint = ast_sorcery_retrieve_by_id(
+		      ast_sip_get_sorcery(), "endpoint", endpoint_name))) {
+		ast_cli(a->fd, "Unable to retrieve endpoint %s\n", endpoint_name);
 		return CLI_FAILURE;
 	}
 
-	if (ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata)) {
-		ast_log(LOG_ERROR, "Unable to create OPTIONS request to endpoint %s\n", endpoint_name);
-		return CLI_FAILURE;
-	}
-
-	if (ast_sip_send_request(tdata, NULL, endpoint)) {
-		ast_log(LOG_ERROR, "Unable to send OPTIONS request to endpoint %s\n", endpoint_name);

[... 163 lines stripped ...]



More information about the svn-commits mailing list