summaryrefslogtreecommitdiff
path: root/res
diff options
context:
space:
mode:
Diffstat (limited to 'res')
-rw-r--r--res/res_pjsip.c184
-rw-r--r--res/res_pjsip/config_global.c21
-rw-r--r--res/res_pjsip/include/res_pjsip_private.h6
-rw-r--r--res/res_pjsip/location.c39
-rw-r--r--res/res_pjsip/pjsip_configuration.c123
-rw-r--r--res/res_pjsip/pjsip_options.c78
-rw-r--r--res/res_pjsip/pjsip_resolver.c669
-rw-r--r--res/res_pjsip_pubsub.c9
8 files changed, 1092 insertions, 37 deletions
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index fcd8516b6..2bc5abdd7 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -21,6 +21,8 @@
#include <pjsip.h>
/* Needed for SUBSCRIBE, NOTIFY, and PUBLISH method definitions */
#include <pjsip_simple.h>
+#include <pjsip/sip_transaction.h>
+#include <pj/timer.h>
#include <pjlib.h>
#include "asterisk/res_pjsip.h"
@@ -1009,6 +1011,14 @@
If <literal>0</literal> never qualify. Time in seconds.
</para></description>
</configOption>
+ <configOption name="qualify_timeout" default="3.0">
+ <synopsis>Timeout for qualify</synopsis>
+ <description><para>
+ If the contact doesn't repond to the OPTIONS request before the timeout,
+ the contact is marked unavailable.
+ If <literal>0</literal> no timeout. Time in fractional seconds.
+ </para></description>
+ </configOption>
<configOption name="outbound_proxy">
<synopsis>Outbound proxy used when sending OPTIONS request</synopsis>
<description><para>
@@ -1123,6 +1133,14 @@
If <literal>0</literal> never qualify. Time in seconds.
</para></description>
</configOption>
+ <configOption name="qualify_timeout" default="3.0">
+ <synopsis>Timeout for qualify</synopsis>
+ <description><para>
+ If the contact doesn't repond to the OPTIONS request before the timeout,
+ the contact is marked unavailable.
+ If <literal>0</literal> no timeout. Time in fractional seconds.
+ </para></description>
+ </configOption>
<configOption name="authenticate_qualify" default="no">
<synopsis>Authenticates a qualify request if needed</synopsis>
<description><para>
@@ -1211,6 +1229,10 @@
<configOption name="keep_alive_interval" default="0">
<synopsis>The interval (in seconds) to send keepalives to active connection-oriented transports.</synopsis>
</configOption>
+ <configOption name="max_initial_qualify_time" default="0">
+ <synopsis>The maximum amount of time from startup that qualifies should be attempted on all contacts.
+ If greater than the qualify_frequency for an aor, qualify_frequency will be used instead.</synopsis>
+ </configOption>
<configOption name="type">
<synopsis>Must be of type 'global'.</synopsis>
</configOption>
@@ -2815,6 +2837,128 @@ static pj_bool_t does_method_match(const pj_str_t *message_method, const char *s
/*! Maximum number of challenges before assuming that we are in a loop */
#define MAX_RX_CHALLENGES 10
+#define TIMER_INACTIVE 0
+#define TIMEOUT_TIMER2 5
+
+struct tsx_data {
+ void *token;
+ void (*cb)(void*, pjsip_event*);
+ pjsip_transaction *tsx;
+ pj_timer_entry *timeout_timer;
+};
+
+static void send_tsx_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event);
+
+pjsip_module send_tsx_module = {
+ .name = { "send_tsx_module", 23 },
+ .id = -1,
+ .priority = PJSIP_MOD_PRIORITY_APPLICATION,
+ .on_tsx_state = &send_tsx_on_tsx_state,
+};
+
+/*! \brief This is the pjsip_tsx_send_msg callback */
+static void send_tsx_on_tsx_state(pjsip_transaction *tsx, pjsip_event *event)
+{
+ struct tsx_data *tsx_data;
+
+ if (event->type != PJSIP_EVENT_TSX_STATE) {
+ return;
+ }
+
+ tsx_data = (struct tsx_data*) tsx->mod_data[send_tsx_module.id];
+ if (tsx_data == NULL) {
+ return;
+ }
+
+ if (tsx->status_code < 200) {
+ return;
+ }
+
+ if (event->body.tsx_state.type == PJSIP_EVENT_TIMER) {
+ ast_debug(1, "PJSIP tsx timer expired\n");
+ }
+
+ if (tsx_data->timeout_timer && tsx_data->timeout_timer->id != TIMER_INACTIVE) {
+ pj_mutex_lock(tsx->mutex_b);
+ pj_timer_heap_cancel_if_active(pjsip_endpt_get_timer_heap(tsx->endpt),
+ tsx_data->timeout_timer, TIMER_INACTIVE);
+ pj_mutex_unlock(tsx->mutex_b);
+ }
+
+ /* Call the callback, if any, and prevent the callback from being called again
+ * by clearing the transaction's module_data.
+ */
+ tsx->mod_data[send_tsx_module.id] = NULL;
+
+ if (tsx_data->cb) {
+ (*tsx_data->cb)(tsx_data->token, event);
+ }
+}
+
+static void tsx_timer_callback(pj_timer_heap_t *theap, pj_timer_entry *entry)
+{
+ struct tsx_data *tsx_data = entry->user_data;
+
+ entry->id = TIMER_INACTIVE;
+ ast_debug(1, "Internal tsx timer expired\n");
+ pjsip_tsx_terminate(tsx_data->tsx, PJSIP_SC_TSX_TIMEOUT);
+}
+
+static pj_status_t endpt_send_transaction(pjsip_endpoint *endpt,
+ pjsip_tx_data *tdata, int timeout, void *token,
+ pjsip_endpt_send_callback cb)
+{
+ pjsip_transaction *tsx;
+ struct tsx_data *tsx_data;
+ pj_status_t status;
+ pjsip_event event;
+
+ ast_assert(endpt && tdata);
+
+ status = pjsip_tsx_create_uac(&send_tsx_module, tdata, &tsx);
+ if (status != PJ_SUCCESS) {
+ pjsip_tx_data_dec_ref(tdata);
+ ast_log(LOG_ERROR, "Unable to create pjsip uac\n");
+ return status;
+ }
+
+ tsx_data = PJ_POOL_ALLOC_T(tsx->pool, struct tsx_data);
+ tsx_data->token = token;
+ tsx_data->cb = cb;
+ tsx_data->tsx = tsx;
+ if (timeout > 0) {
+ tsx_data->timeout_timer = PJ_POOL_ALLOC_T(tsx->pool, pj_timer_entry);
+ } else {
+ tsx_data->timeout_timer = NULL;
+ }
+ tsx->mod_data[send_tsx_module.id] = tsx_data;
+
+ PJSIP_EVENT_INIT_TX_MSG(event, tdata);
+ pjsip_tx_data_set_transport(tdata, &tsx->tp_sel);
+
+ if (timeout > 0) {
+ pj_time_val timeout_timer_val = { timeout / 1000, timeout % 1000 };
+
+ pj_timer_entry_init(tsx_data->timeout_timer, TIMEOUT_TIMER2,
+ tsx_data, &tsx_timer_callback);
+ pj_mutex_lock(tsx->mutex_b);
+ pj_timer_heap_cancel_if_active(pjsip_endpt_get_timer_heap(tsx->endpt),
+ tsx_data->timeout_timer, TIMER_INACTIVE);
+ pj_timer_heap_schedule(pjsip_endpt_get_timer_heap(tsx->endpt),
+ tsx_data->timeout_timer, &timeout_timer_val);
+ tsx_data->timeout_timer->id = TIMEOUT_TIMER2;
+ pj_mutex_unlock(tsx->mutex_b);
+ }
+
+ status = (*tsx->state_handler)(tsx, &event);
+ pjsip_tx_data_dec_ref(tdata);
+ if (status != PJ_SUCCESS) {
+ ast_log(LOG_ERROR, "Unable to send message\n");
+ return status;
+ }
+
+ return status;
+}
/*! \brief Structure to hold information about an outbound request */
struct send_request_data {
@@ -2874,7 +3018,7 @@ static void endpt_send_request_wrapper(void *token, pjsip_event *e)
}
static pj_status_t endpt_send_request(struct ast_sip_endpoint *endpoint,
- pjsip_tx_data *tdata, pj_int32_t timeout, void *token, pjsip_endpt_send_callback cb)
+ pjsip_tx_data *tdata, int timeout, void *token, pjsip_endpt_send_callback cb)
{
struct send_request_wrapper *req_wrapper;
pj_status_t ret_val;
@@ -2890,7 +3034,7 @@ static pj_status_t endpt_send_request(struct ast_sip_endpoint *endpoint,
req_wrapper->callback = cb;
ao2_ref(req_wrapper, +1);
- ret_val = pjsip_endpt_send_request(ast_sip_get_pjsip_endpoint(), tdata, timeout,
+ ret_val = endpt_send_transaction(ast_sip_get_pjsip_endpoint(), tdata, timeout,
req_wrapper, endpt_send_request_wrapper);
if (ret_val != PJ_SUCCESS) {
char errmsg[PJ_ERR_MSG_SIZE];
@@ -2930,6 +3074,10 @@ static void send_request_cb(void *token, pjsip_event *e)
int res;
switch(e->body.tsx_state.type) {
+ case PJSIP_EVENT_USER:
+ /* Map USER (transaction cancelled by timeout) to TIMER */
+ e->body.tsx_state.type = PJSIP_EVENT_TIMER;
+ break;
case PJSIP_EVENT_TRANSPORT_ERROR:
case PJSIP_EVENT_TIMER:
break;
@@ -2980,8 +3128,9 @@ static void send_request_cb(void *token, pjsip_event *e)
ao2_ref(req_data, -1);
}
-static int send_out_of_dialog_request(pjsip_tx_data *tdata, struct ast_sip_endpoint *endpoint,
- void *token, void (*callback)(void *token, pjsip_event *e))
+int ast_sip_send_out_of_dialog_request(pjsip_tx_data *tdata,
+ struct ast_sip_endpoint *endpoint, int timeout, void *token,
+ void (*callback)(void *token, pjsip_event *e))
{
struct ast_sip_supplement *supplement;
struct send_request_data *req_data;
@@ -3007,7 +3156,7 @@ static int send_out_of_dialog_request(pjsip_tx_data *tdata, struct ast_sip_endpo
ast_sip_mod_data_set(tdata->pool, tdata->mod_data, supplement_module.id, MOD_DATA_CONTACT, NULL);
ao2_cleanup(contact);
- if (endpt_send_request(endpoint, tdata, -1, req_data, send_request_cb)
+ if (endpt_send_request(endpoint, tdata, timeout, req_data, send_request_cb)
!= PJ_SUCCESS) {
ao2_cleanup(req_data);
return -1;
@@ -3025,7 +3174,7 @@ int ast_sip_send_request(pjsip_tx_data *tdata, struct pjsip_dialog *dlg,
if (dlg) {
return send_in_dialog_request(tdata, dlg);
} else {
- return send_out_of_dialog_request(tdata, endpoint, token, callback);
+ return ast_sip_send_out_of_dialog_request(tdata, endpoint, -1, token, callback);
}
}
@@ -3480,8 +3629,6 @@ static int load_module(void)
return AST_MODULE_LOAD_DECLINE;
}
- ast_sip_initialize_dns();
-
pjsip_tsx_layer_init_module(ast_pjsip_endpoint);
pjsip_ua_init_module(ast_pjsip_endpoint, NULL);
@@ -3514,6 +3661,9 @@ static int load_module(void)
return AST_MODULE_LOAD_DECLINE;
}
+ ast_sip_initialize_resolver();
+ ast_sip_initialize_dns();
+
if (ast_sip_initialize_distributor()) {
ast_log(LOG_ERROR, "Failed to register distributor module. Aborting load\n");
ast_res_pjsip_destroy_configuration();
@@ -3543,8 +3693,25 @@ static int load_module(void)
return AST_MODULE_LOAD_DECLINE;
}
+ if (internal_sip_register_service(&send_tsx_module)) {
+ ast_log(LOG_ERROR, "Failed to initialize send request module. Aborting load\n");
+ internal_sip_unregister_service(&supplement_module);
+ ast_sip_destroy_distributor();
+ ast_res_pjsip_destroy_configuration();
+ ast_sip_destroy_global_headers();
+ stop_monitor_thread();
+ ast_sip_destroy_system();
+ pj_pool_release(memory_pool);
+ memory_pool = NULL;
+ pjsip_endpt_destroy(ast_pjsip_endpoint);
+ ast_pjsip_endpoint = NULL;
+ pj_caching_pool_destroy(&caching_pool);
+ return AST_MODULE_LOAD_DECLINE;
+ }
+
if (internal_sip_initialize_outbound_authentication()) {
ast_log(LOG_ERROR, "Failed to initialize outbound authentication. Aborting load\n");
+ internal_sip_unregister_service(&send_tsx_module);
internal_sip_unregister_service(&supplement_module);
ast_sip_destroy_distributor();
ast_res_pjsip_destroy_configuration();
@@ -3588,6 +3755,7 @@ static int unload_pjsip(void *data)
ast_res_pjsip_destroy_configuration();
ast_sip_destroy_system();
ast_sip_destroy_global_headers();
+ internal_sip_unregister_service(&send_tsx_module);
internal_sip_unregister_service(&supplement_module);
if (monitor_thread) {
stop_monitor_thread();
diff --git a/res/res_pjsip/config_global.c b/res/res_pjsip/config_global.c
index 2aa15838f..42ba23487 100644
--- a/res/res_pjsip/config_global.c
+++ b/res/res_pjsip/config_global.c
@@ -33,6 +33,7 @@
#define DEFAULT_OUTBOUND_ENDPOINT "default_outbound_endpoint"
#define DEFAULT_DEBUG "no"
#define DEFAULT_ENDPOINT_IDENTIFIER_ORDER "ip,username,anonymous"
+#define DEFAULT_MAX_INITIAL_QUALIFY_TIME 0
static char default_useragent[256];
@@ -50,6 +51,8 @@ struct global_config {
unsigned int max_forwards;
/* The interval at which to send keep alive messages to active connection-oriented transports */
unsigned int keep_alive_interval;
+ /* The maximum time for all contacts to be qualified at startup */
+ unsigned int max_initial_qualify_time;
};
static void global_destructor(void *obj)
@@ -161,6 +164,21 @@ unsigned int ast_sip_get_keep_alive_interval(void)
return interval;
}
+unsigned int ast_sip_get_max_initial_qualify_time(void)
+{
+ unsigned int time;
+ struct global_config *cfg;
+
+ cfg = get_global_cfg();
+ if (!cfg) {
+ return DEFAULT_MAX_INITIAL_QUALIFY_TIME;
+ }
+
+ time = cfg->max_initial_qualify_time;
+ ao2_ref(cfg, -1);
+ return time;
+}
+
/*!
* \internal
* \brief Observer to set default global object if none exist.
@@ -271,6 +289,9 @@ int ast_sip_initialize_sorcery_global(void)
ast_sorcery_object_field_register(sorcery, "global", "keep_alive_interval",
__stringify(DEFAULT_KEEPALIVE_INTERVAL),
OPT_UINT_T, 0, FLDSET(struct global_config, keep_alive_interval));
+ ast_sorcery_object_field_register(sorcery, "global", "max_initial_qualify_time",
+ __stringify(DEFAULT_MAX_INITIAL_QUALIFY_TIME),
+ OPT_UINT_T, 0, FLDSET(struct global_config, max_initial_qualify_time));
if (ast_sorcery_instance_observer_add(sorcery, &observer_callbacks_global)) {
return -1;
diff --git a/res/res_pjsip/include/res_pjsip_private.h b/res/res_pjsip/include/res_pjsip_private.h
index bf428d5c5..a8b94112b 100644
--- a/res/res_pjsip/include/res_pjsip_private.h
+++ b/res/res_pjsip/include/res_pjsip_private.h
@@ -233,6 +233,12 @@ void ast_sip_initialize_dns(void);
/*!
* \internal
+ * \brief Initialize our own resolver support
+ */
+void ast_sip_initialize_resolver(void);
+
+/*!
+ * \internal
* \brief Initialize global configuration
*
* \retval 0 Success
diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c
index 73ffdca0e..f784cb40f 100644
--- a/res/res_pjsip/location.c
+++ b/res/res_pjsip/location.c
@@ -188,6 +188,40 @@ struct ast_sip_contact *ast_sip_location_retrieve_contact_from_aor_list(const ch
return contact;
}
+static int permanent_uri_sort_fn(const void *obj_left, const void *obj_right, int flags);
+static int cli_contact_populate_container(void *obj, void *arg, int flags);
+
+static int gather_contacts_for_aor(void *obj, void *arg, int flags)
+{
+ struct ao2_container *aor_contacts;
+ struct ast_sip_aor *aor = obj;
+ struct ao2_container *container = arg;
+
+ aor_contacts = ast_sip_location_retrieve_aor_contacts(aor);
+ if (!aor_contacts) {
+ return 0;
+ }
+ ao2_callback(aor_contacts, OBJ_MULTIPLE | OBJ_NODATA, cli_contact_populate_container,
+ container);
+ ao2_ref(aor_contacts, -1);
+ return CMP_MATCH;
+}
+
+struct ao2_container *ast_sip_location_retrieve_contacts_from_aor_list(const char *aor_list)
+{
+ struct ao2_container *contacts;
+
+ contacts = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK,
+ AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, permanent_uri_sort_fn, NULL);
+ if (!contacts) {
+ return NULL;
+ }
+
+ ast_sip_for_each_aor(aor_list, gather_contacts_for_aor, contacts);
+
+ return contacts;
+}
+
struct ast_sip_contact *ast_sip_location_retrieve_contact(const char *contact_name)
{
return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "contact", contact_name);
@@ -208,6 +242,7 @@ int ast_sip_location_add_contact(struct ast_sip_aor *aor, const char *uri,
ast_string_field_set(contact, uri, uri);
contact->expiration_time = expiration_time;
contact->qualify_frequency = aor->qualify_frequency;
+ contact->qualify_timeout = aor->qualify_timeout;
contact->authenticate_qualify = aor->authenticate_qualify;
if (path_info && aor->support_path) {
ast_string_field_set(contact, path, path_info);
@@ -853,7 +888,8 @@ int ast_sip_initialize_sorcery_location(void)
ast_sorcery_object_field_register(sorcery, "contact", "path", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, path));
ast_sorcery_object_field_register_custom(sorcery, "contact", "expiration_time", "", expiration_str2struct, expiration_struct2str, NULL, 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);
+ PARSE_IN_RANGE, FLDSET(struct ast_sip_contact, qualify_frequency), 0, 86400);
+ ast_sorcery_object_field_register(sorcery, "contact", "qualify_timeout", "3.0", OPT_DOUBLE_T, 0, FLDSET(struct ast_sip_contact, qualify_timeout));
ast_sorcery_object_field_register(sorcery, "contact", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, outbound_proxy));
ast_sorcery_object_field_register(sorcery, "contact", "user_agent", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, user_agent));
@@ -862,6 +898,7 @@ int ast_sip_initialize_sorcery_location(void)
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", "qualify_timeout", "3.0", OPT_DOUBLE_T, 0, FLDSET(struct ast_sip_aor, qualify_timeout));
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));
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index 0eecb5e0a..ab0d08449 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -19,6 +19,7 @@
#include "asterisk/utils.h"
#include "asterisk/sorcery.h"
#include "asterisk/callerid.h"
+#include "asterisk/test.h"
/*! \brief Number of buckets for persistent endpoint information */
#define PERSISTENT_BUCKETS 53
@@ -59,31 +60,66 @@ static int persistent_endpoint_cmp(void *obj, void *arg, int flags)
static int persistent_endpoint_update_state(void *obj, void *arg, int flags)
{
struct sip_persistent_endpoint *persistent = obj;
+ struct ast_endpoint *endpoint = persistent->endpoint;
char *aor = arg;
- RAII_VAR(struct ast_sip_contact *, contact, NULL, ao2_cleanup);
- RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref);
+ struct ao2_container *contacts;
+ struct ast_json *blob;
+ struct ao2_iterator i;
+ struct ast_sip_contact *contact;
+ enum ast_endpoint_state state = AST_ENDPOINT_OFFLINE;
if (!ast_strlen_zero(aor) && !strstr(persistent->aors, aor)) {
return 0;
}
- if ((contact = ast_sip_location_retrieve_contact_from_aor_list(persistent->aors))) {
- ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_ONLINE);
+ /* Find all the contacts for this endpoint. If ANY are available,
+ * mark the endpoint as ONLINE.
+ */
+ contacts = ast_sip_location_retrieve_contacts_from_aor_list(persistent->aors);
+ if (contacts) {
+ i = ao2_iterator_init(contacts, 0);
+ while ((contact = ao2_iterator_next(&i))
+ && state == AST_ENDPOINT_OFFLINE) {
+ struct ast_sip_contact_status *contact_status;
+ const char *contact_id = ast_sorcery_object_get_id(contact);
+
+ contact_status = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(),
+ CONTACT_STATUS, contact_id);
+
+ if (contact_status && contact_status->status == AVAILABLE) {
+ state = AST_ENDPOINT_ONLINE;
+ }
+ ao2_cleanup(contact_status);
+ ao2_ref(contact, -1);
+ }
+ ao2_iterator_destroy(&i);
+ ao2_ref(contacts, -1);
+ }
+
+ /* If there was no state change, don't publish anything. */
+ if (ast_endpoint_get_state(endpoint) == state) {
+ return 0;
+ }
+
+ if (state == AST_ENDPOINT_ONLINE) {
+ ast_endpoint_set_state(endpoint, AST_ENDPOINT_ONLINE);
blob = ast_json_pack("{s: s}", "peer_status", "Reachable");
+ ast_verb(1, "Endpoint %s is now Reachable\n", ast_endpoint_get_resource(endpoint));
} else {
- ast_endpoint_set_state(persistent->endpoint, AST_ENDPOINT_OFFLINE);
+ ast_endpoint_set_state(endpoint, AST_ENDPOINT_OFFLINE);
blob = ast_json_pack("{s: s}", "peer_status", "Unreachable");
+ ast_verb(1, "Endpoint %s is now Unreachable\n", ast_endpoint_get_resource(endpoint));
}
- ast_endpoint_blob_publish(persistent->endpoint, ast_endpoint_state_type(), blob);
-
- ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_endpoint_get_resource(persistent->endpoint));
+ ast_endpoint_blob_publish(endpoint, ast_endpoint_state_type(), blob);
+ ast_json_unref(blob);
+ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_endpoint_get_resource(endpoint));
return 0;
}
/*! \brief Function called when stuff relating to a contact happens (created/deleted) */
-static void persistent_endpoint_contact_observer(const void *object)
+static void persistent_endpoint_contact_created_observer(const void *object)
{
char *id = ast_strdupa(ast_sorcery_object_get_id(object)), *aor = NULL;
@@ -92,12 +128,74 @@ static void persistent_endpoint_contact_observer(const void *object)
ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, aor);
}
+/*! \brief Function called when stuff relating to a contact happens (created/deleted) */
+static void persistent_endpoint_contact_deleted_observer(const void *object)
+{
+ char *id = ast_strdupa(ast_sorcery_object_get_id(object));
+ char *aor = NULL;
+ char *contact = NULL;
+
+ aor = id;
+ /* Dynamic contacts are delimited with ";@" and static ones with "@@" */
+ if ((contact = strstr(id, ";@")) || (contact = strstr(id, "@@"))) {
+ *contact = '\0';
+ contact += 2;
+ } else {
+ contact = id;
+ }
+
+ ast_verb(1, "Contact %s/%s is now Unavailable\n", aor, contact);
+
+ ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, aor);
+}
+
/*! \brief Observer for contacts so state can be updated on respective endpoints */
static const struct ast_sorcery_observer state_contact_observer = {
- .created = persistent_endpoint_contact_observer,
- .deleted = persistent_endpoint_contact_observer,
+ .created = persistent_endpoint_contact_created_observer,
+ .deleted = persistent_endpoint_contact_deleted_observer,
};
+/*! \brief Function called when stuff relating to a contact status happens (updated) */
+static void persistent_endpoint_contact_status_observer(const void *object)
+{
+ const struct ast_sip_contact_status *contact_status = object;
+ char *id = ast_strdupa(ast_sorcery_object_get_id(object));
+ char *aor = NULL;
+ char *contact = NULL;
+
+ /* If rtt_start is set (this is the outgoing OPTIONS) or
+ * there's no status change, ignore.
+ */
+ if (contact_status->rtt_start.tv_sec > 0
+ || contact_status->status == contact_status->last_status) {
+ return;
+ }
+
+ aor = id;
+ /* Dynamic contacts are delimited with ";@" and static ones with "@@" */
+ if ((contact = strstr(id, ";@")) || (contact = strstr(id, "@@"))) {
+ *contact = '\0';
+ contact += 2;
+ } else {
+ contact = id;
+ }
+
+ ast_test_suite_event_notify("AOR_CONTACT_UPDATE",
+ "Contact: %s\r\n"
+ "Status: %s",
+ ast_sorcery_object_get_id(contact_status),
+ (contact_status->status == AVAILABLE ? "Available" : "Unavailable"));
+
+ ast_verb(1, "Contact %s/%s is now %s\n", aor, contact,
+ contact_status->status == AVAILABLE ? "Available" : "Unavailable");
+
+ ao2_callback(persistent_endpoints, OBJ_NODATA, persistent_endpoint_update_state, aor);
+}
+
+/*! \brief Observer for contacts so state can be updated on respective endpoints */
+static const struct ast_sorcery_observer state_contact_status_observer = {
+ .updated = persistent_endpoint_contact_status_observer,
+};
static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
{
@@ -1796,6 +1894,7 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
}
ast_sorcery_observer_add(sip_sorcery, "contact", &state_contact_observer);
+ ast_sorcery_observer_add(sip_sorcery, CONTACT_STATUS, &state_contact_status_observer);
if (ast_sip_initialize_sorcery_domain_alias()) {
ast_log(LOG_ERROR, "Failed to register SIP domain aliases support with sorcery\n");
@@ -1852,6 +1951,8 @@ int ast_res_pjsip_initialize_configuration(const struct ast_module_info *ast_mod
void ast_res_pjsip_destroy_configuration(void)
{
+ ast_sorcery_observer_remove(sip_sorcery, CONTACT_STATUS, &state_contact_status_observer);
+ ast_sorcery_observer_remove(sip_sorcery, "contact", &state_contact_observer);
ast_sip_destroy_sorcery_global();
ast_sip_destroy_sorcery_location();
ast_sip_destroy_sorcery_auth();
diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c
index 9794827b5..9c0a1379d 100644
--- a/res/res_pjsip/pjsip_options.c
+++ b/res/res_pjsip/pjsip_options.c
@@ -28,6 +28,7 @@
#include "asterisk/astobj2.h"
#include "asterisk/cli.h"
#include "asterisk/time.h"
+#include "asterisk/test.h"
#include "include/res_pjsip_private.h"
#define DEFAULT_LANGUAGE "en"
@@ -110,18 +111,20 @@ static void update_contact_status(const struct ast_sip_contact *contact,
status = find_or_create_contact_status(contact);
if (!status) {
+ ast_log(LOG_ERROR, "Unable to find ast_sip_contact_status for contact %s\n",
+ contact->uri);
return;
}
update = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,
ast_sorcery_object_get_id(status));
if (!update) {
- ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n",
+ ast_log(LOG_ERROR, "Unable to allocate ast_sip_contact_status for contact %s\n",
contact->uri);
- ao2_ref(status, -1);
return;
}
+ update->last_status = status->status;
update->status = value;
/* if the contact is available calculate the rtt as
@@ -131,13 +134,21 @@ static void update_contact_status(const struct ast_sip_contact *contact,
update->rtt_start = ast_tv(0, 0);
+ ast_test_suite_event_notify("AOR_CONTACT_QUALIFY_RESULT",
+ "Contact: %s\r\n"
+ "Status: %s\r\n"
+ "RTT: %ld",
+ ast_sorcery_object_get_id(update),
+ (update->status == AVAILABLE ? "Available" : "Unavailable"),
+ update->rtt);
+
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);
}
- ao2_ref(update, -1);
ao2_ref(status, -1);
+ ao2_ref(update, -1);
}
/*!
@@ -152,18 +163,22 @@ static void init_start_time(const struct ast_sip_contact *contact)
status = find_or_create_contact_status(contact);
if (!status) {
+ ast_log(LOG_ERROR, "Unable to find ast_sip_contact_status for contact %s\n",
+ contact->uri);
return;
}
update = ast_sorcery_alloc(ast_sip_get_sorcery(), CONTACT_STATUS,
ast_sorcery_object_get_id(status));
if (!update) {
- ast_log(LOG_ERROR, "Unable to create update ast_sip_contact_status for contact %s\n",
+ ast_log(LOG_ERROR, "Unable to copy ast_sip_contact_status for contact %s\n",
contact->uri);
- ao2_ref(status, -1);
return;
}
+ update->status = status->status;
+ update->last_status = status->last_status;
+ update->rtt = status->rtt;
update->rtt_start = ast_tvnow();
if (ast_sorcery_update(ast_sip_get_sorcery(), update)) {
@@ -171,8 +186,8 @@ static void init_start_time(const struct ast_sip_contact *contact)
contact->uri);
}
- ao2_ref(update, -1);
ao2_ref(status, -1);
+ ao2_ref(update, -1);
}
/*!
@@ -320,7 +335,7 @@ static int qualify_contact(struct ast_sip_endpoint *endpoint, struct ast_sip_con
init_start_time(contact);
ao2_ref(contact, +1);
- if (ast_sip_send_request(tdata, NULL, endpoint_local, contact, qualify_contact_cb)
+ if (ast_sip_send_out_of_dialog_request(tdata, endpoint_local, (int)(contact->qualify_timeout * 1000), contact, qualify_contact_cb)
!= PJ_SUCCESS) {
ast_log(LOG_ERROR, "Unable to send request to qualify contact %s\n",
contact->uri);
@@ -923,6 +938,32 @@ static int sched_qualifies_cmp_fn(void *obj, void *arg, int flags)
return CMP_MATCH;
}
+static int rtt_start_handler(const struct aco_option *opt,
+ struct ast_variable *var, void *obj)
+{
+ struct ast_sip_contact_status *status = obj;
+ long int sec, usec;
+
+ if (sscanf(var->value, "%ld.%06ld", &sec, &usec) != 2) {
+ return -1;
+ }
+
+ status->rtt_start = ast_tv(sec, usec);
+
+ return 0;
+}
+
+static int rtt_start_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct ast_sip_contact_status *status = obj;
+
+ if (ast_asprintf(buf, "%ld.%06ld", status->rtt_start.tv_sec, status->rtt_start.tv_usec) == -1) {
+ return -1;
+ }
+
+ return 0;
+}
+
int ast_sip_initialize_sorcery_qualify(void)
{
struct ast_sorcery *sorcery = ast_sip_get_sorcery();
@@ -936,10 +977,14 @@ int ast_sip_initialize_sorcery_qualify(void)
return -1;
}
- ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "rtt", "0", OPT_UINT_T,
- 1, FLDSET(struct ast_sip_contact_status, status));
- ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "rtt", "0", OPT_UINT_T,
- 1, FLDSET(struct ast_sip_contact_status, rtt));
+ ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "last_status",
+ "0", OPT_UINT_T, 1, FLDSET(struct ast_sip_contact_status, last_status));
+ ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "status",
+ "0", OPT_UINT_T, 1, FLDSET(struct ast_sip_contact_status, status));
+ ast_sorcery_object_field_register_custom_nodoc(sorcery, CONTACT_STATUS, "rtt_start",
+ "0.0", rtt_start_handler, rtt_start_to_str, NULL, 0, 0);
+ ast_sorcery_object_field_register_nodoc(sorcery, CONTACT_STATUS, "rtt",
+ "0", OPT_UINT_T, 1, FLDSET(struct ast_sip_contact_status, rtt));
return 0;
}
@@ -949,13 +994,20 @@ static int qualify_and_schedule_cb(void *obj, void *arg, int flags)
struct ast_sip_contact *contact = obj;
struct ast_sip_aor *aor = arg;
int initial_interval;
+ int max_time = ast_sip_get_max_initial_qualify_time();
contact->qualify_frequency = aor->qualify_frequency;
+ contact->qualify_timeout = aor->qualify_timeout;
contact->authenticate_qualify = aor->authenticate_qualify;
/* Delay initial qualification by a random fraction of the specified interval */
- initial_interval = contact->qualify_frequency * 1000;
- initial_interval = (int)(initial_interval * ast_random_double());
+ if (max_time && max_time < contact->qualify_frequency) {
+ initial_interval = max_time;
+ } else {
+ initial_interval = contact->qualify_frequency;
+ }
+
+ initial_interval = (int)((initial_interval * 1000) * ast_random_double());
if (contact->qualify_frequency) {
schedule_qualify(contact, initial_interval);
diff --git a/res/res_pjsip/pjsip_resolver.c b/res/res_pjsip/pjsip_resolver.c
new file mode 100644
index 000000000..e4cc51af1
--- /dev/null
+++ b/res/res_pjsip/pjsip_resolver.c
@@ -0,0 +1,669 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2015, Digium, Inc.
+ *
+ * Joshua Colp <jcolp@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"
+
+#include <pjsip.h>
+#include <pjlib-util/errno.h>
+
+#include <arpa/nameser.h>
+
+#include "asterisk/astobj2.h"
+#include "asterisk/dns_core.h"
+#include "asterisk/dns_query_set.h"
+#include "asterisk/dns_srv.h"
+#include "asterisk/dns_naptr.h"
+#include "asterisk/res_pjsip.h"
+#include "include/res_pjsip_private.h"
+
+#ifdef HAVE_PJSIP_EXTERNAL_RESOLVER
+
+/*! \brief Structure which contains transport+port information for an active query */
+struct sip_target {
+ /*! \brief The transport to be used */
+ pjsip_transport_type_e transport;
+ /*! \brief The port */
+ int port;
+};
+
+/*! \brief The vector used for current targets */
+AST_VECTOR(targets, struct sip_target);
+
+/*! \brief Structure which keeps track of resolution */
+struct sip_resolve {
+ /*! \brief Addresses currently being resolved, indexed based on index of queries in query set */
+ struct targets resolving;
+ /*! \brief Active queries */
+ struct ast_dns_query_set *queries;
+ /*! \brief Current viable server addresses */
+ pjsip_server_addresses addresses;
+ /*! \brief Callback to invoke upon completion */
+ pjsip_resolver_callback *callback;
+ /*! \brief User provided data */
+ void *token;
+};
+
+/*! \brief Our own defined transports, reduces the size of sip_available_transports */
+enum sip_resolver_transport {
+ SIP_RESOLVER_TRANSPORT_UDP,
+ SIP_RESOLVER_TRANSPORT_TCP,
+ SIP_RESOLVER_TRANSPORT_TLS,
+ SIP_RESOLVER_TRANSPORT_UDP6,
+ SIP_RESOLVER_TRANSPORT_TCP6,
+ SIP_RESOLVER_TRANSPORT_TLS6,
+};
+
+/*! \brief Available transports on the system */
+static int sip_available_transports[] = {
+ /* This is a list of transports with whether they are available as a valid transport
+ * stored. We use our own identifier as to reduce the size of sip_available_transports.
+ * As this array is only manipulated at startup it does not require a lock to protect
+ * it.
+ */
+ [SIP_RESOLVER_TRANSPORT_UDP] = 0,
+ [SIP_RESOLVER_TRANSPORT_TCP] = 0,
+ [SIP_RESOLVER_TRANSPORT_TLS] = 0,
+ [SIP_RESOLVER_TRANSPORT_UDP6] = 0,
+ [SIP_RESOLVER_TRANSPORT_TCP6] = 0,
+ [SIP_RESOLVER_TRANSPORT_TLS6] = 0,
+};
+
+/*!
+ * \internal
+ * \brief Destroy resolution data
+ *
+ * \param data The resolution data to destroy
+ *
+ * \return Nothing
+ */
+static void sip_resolve_destroy(void *data)
+{
+ struct sip_resolve *resolve = data;
+
+ AST_VECTOR_FREE(&resolve->resolving);
+ ao2_cleanup(resolve->queries);
+}
+
+/*!
+ * \internal
+ * \brief Check whether a transport is available or not
+ *
+ * \param transport The PJSIP transport type
+ *
+ * \return 1 success (transport is available)
+ * \return 0 failure (transport is not available)
+ */
+static int sip_transport_is_available(enum pjsip_transport_type_e transport)
+{
+ enum sip_resolver_transport resolver_transport;
+
+ if (transport == PJSIP_TRANSPORT_UDP) {
+ resolver_transport = SIP_RESOLVER_TRANSPORT_UDP;
+ } else if (transport == PJSIP_TRANSPORT_TCP) {
+ resolver_transport = SIP_RESOLVER_TRANSPORT_TCP;
+ } else if (transport == PJSIP_TRANSPORT_TLS) {
+ resolver_transport = SIP_RESOLVER_TRANSPORT_TLS;
+ } else if (transport == PJSIP_TRANSPORT_UDP6) {
+ resolver_transport = SIP_RESOLVER_TRANSPORT_UDP6;
+ } else if (transport == PJSIP_TRANSPORT_TCP6) {
+ resolver_transport = SIP_RESOLVER_TRANSPORT_TCP6;
+ } else if (transport == PJSIP_TRANSPORT_TLS6) {
+ resolver_transport = SIP_RESOLVER_TRANSPORT_TLS6;
+ } else {
+ return 0;
+ }
+
+ return sip_available_transports[resolver_transport];
+}
+
+/*!
+ * \internal
+ * \brief Add a query to be resolved
+ *
+ * \param resolve The ongoing resolution
+ * \param name What to resolve
+ * \param rr_type The type of record to look up
+ * \param rr_class The type of class to look up
+ * \param transport The transport to use for any resulting records
+ * \param port The port to use for any resulting records - if not specified the
+ * default for the transport is used
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+static int sip_resolve_add(struct sip_resolve *resolve, const char *name, int rr_type, int rr_class, pjsip_transport_type_e transport, int port)
+{
+ struct sip_target target = {
+ .transport = transport,
+ .port = port,
+ };
+
+ if (!resolve->queries) {
+ resolve->queries = ast_dns_query_set_create();
+ }
+
+ if (!resolve->queries) {
+ return -1;
+ }
+
+ if (!port) {
+ target.port = pjsip_transport_get_default_port_for_type(transport);
+ }
+
+ if (AST_VECTOR_APPEND(&resolve->resolving, target)) {
+ return -1;
+ }
+
+ ast_debug(2, "[%p] Added target '%s' with record type '%d', transport '%s', and port '%d'\n",
+ resolve, name, rr_type, pjsip_transport_get_type_name(transport), target.port);
+
+ return ast_dns_query_set_add(resolve->queries, name, rr_type, rr_class);
+}
+
+/*!
+ * \internal
+ * \brief Task used to invoke the user specific callback
+ *
+ * \param data The complete resolution
+ *
+ * \return Nothing
+ */
+static int sip_resolve_invoke_user_callback(void *data)
+{
+ struct sip_resolve *resolve = data;
+ int idx;
+
+ for (idx = 0; idx < resolve->addresses.count; ++idx) {
+ /* This includes space for the IP address, [, ], :, and the port */
+ char addr[PJ_INET6_ADDRSTRLEN + 10];
+
+ ast_debug(2, "[%p] Address '%d' is %s with transport '%s'\n",
+ resolve, idx, pj_sockaddr_print(&resolve->addresses.entry[idx].addr, addr, sizeof(addr), 3),
+ pjsip_transport_get_type_name(resolve->addresses.entry[idx].type));
+ }
+
+ ast_debug(2, "[%p] Invoking user callback with '%d' addresses\n", resolve, resolve->addresses.count);
+ resolve->callback(resolve->addresses.count ? PJ_SUCCESS : PJLIB_UTIL_EDNSNOANSWERREC, resolve->token, &resolve->addresses);
+
+ ao2_ref(resolve, -1);
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Handle a NAPTR record according to RFC3263
+ *
+ * \param resolve The ongoing resolution
+ * \param record The NAPTR record itself
+ * \param service The service to look for
+ * \param transport The transport to use for resulting queries
+ *
+ * \retval 0 success
+ * \retval -1 failure (record not handled / supported)
+ */
+static int sip_resolve_handle_naptr(struct sip_resolve *resolve, const struct ast_dns_record *record,
+ const char *service, pjsip_transport_type_e transport)
+{
+ if (strcasecmp(ast_dns_naptr_get_service(record), service)) {
+ return -1;
+ }
+
+ if (!sip_transport_is_available(transport) &&
+ !sip_transport_is_available(transport + PJSIP_TRANSPORT_IPV6)) {
+ ast_debug(2, "[%p] NAPTR service %s skipped as transport is unavailable\n",
+ resolve, service);
+ return -1;
+ }
+
+ if (strcasecmp(ast_dns_naptr_get_flags(record), "s")) {
+ ast_debug(2, "[%p] NAPTR service %s received with unsupported flags '%s'\n",
+ resolve, service, ast_dns_naptr_get_flags(record));
+ return -1;
+ }
+
+ if (ast_strlen_zero(ast_dns_naptr_get_replacement(record))) {
+ return -1;
+ }
+
+ return sip_resolve_add(resolve, ast_dns_naptr_get_replacement(record), ns_t_srv, ns_c_in,
+ transport, 0);
+}
+
+/*!
+ * \internal
+ * \brief Query set callback function, invoked when all queries have completed
+ *
+ * \param query_set The completed query set
+ *
+ * \return Nothing
+ */
+static void sip_resolve_callback(const struct ast_dns_query_set *query_set)
+{
+ struct sip_resolve *resolve = ast_dns_query_set_get_data(query_set);
+ struct ast_dns_query_set *queries = resolve->queries;
+ struct targets resolving;
+ int idx, address_count = 0, have_naptr = 0, have_srv = 0;
+ unsigned short order = 0;
+ int strict_order = 0;
+
+ ast_debug(2, "[%p] All parallel queries completed\n", resolve);
+
+ resolve->queries = NULL;
+
+ /* This purposely steals the resolving list so we can add entries to the new one in
+ * the same loop and also have access to the old.
+ */
+ resolving = resolve->resolving;
+ AST_VECTOR_INIT(&resolve->resolving, 0);
+
+ /* The order of queries is what defines the preference order for the records within
+ * this specific query set. The preference order overall is defined as a result of
+ * drilling down from other records. Each completed query set replaces the results
+ * of the last.
+ */
+ for (idx = 0; idx < ast_dns_query_set_num_queries(queries); ++idx) {
+ struct ast_dns_query *query = ast_dns_query_set_get(queries, idx);
+ struct ast_dns_result *result = ast_dns_query_get_result(query);
+ struct sip_target *target;
+ const struct ast_dns_record *record;
+
+ if (!result) {
+ ast_debug(2, "[%p] No result information for target '%s' of type '%d'\n", resolve,
+ ast_dns_query_get_name(query), ast_dns_query_get_rr_type(query));
+ continue;
+ }
+
+ target = AST_VECTOR_GET_ADDR(&resolving, idx);
+ for (record = ast_dns_result_get_records(result); record; record = ast_dns_record_get_next(record)) {
+
+ if (ast_dns_record_get_rr_type(record) == ns_t_a ||
+ ast_dns_record_get_rr_type(record) == ns_t_aaaa) {
+ /* If NAPTR or SRV records exist the subsequent results from them take preference */
+ if (have_naptr || have_srv) {
+ ast_debug(2, "[%p] %s record being skipped on target '%s' because NAPTR or SRV record exists\n",
+ resolve, ast_dns_record_get_rr_type(record) == ns_t_a ? "A" : "AAAA",
+ ast_dns_query_get_name(query));
+ continue;
+ }
+
+ /* PJSIP has a fixed maximum number of addresses that can exist, so limit ourselves to that */
+ if (address_count == PJSIP_MAX_RESOLVED_ADDRESSES) {
+ break;
+ }
+
+ resolve->addresses.entry[address_count].type = target->transport;
+
+ /* Populate address information for the new address entry */
+ if (ast_dns_record_get_rr_type(record) == ns_t_a) {
+ ast_debug(2, "[%p] A record received on target '%s'\n", resolve, ast_dns_query_get_name(query));
+ resolve->addresses.entry[address_count].addr_len = sizeof(pj_sockaddr_in);
+ pj_sockaddr_init(pj_AF_INET(), &resolve->addresses.entry[address_count].addr, NULL,
+ target->port);
+ resolve->addresses.entry[address_count].addr.ipv4.sin_addr = *(struct pj_in_addr*)ast_dns_record_get_data(record);
+ } else {
+ ast_debug(2, "[%p] AAAA record received on target '%s'\n", resolve, ast_dns_query_get_name(query));
+ resolve->addresses.entry[address_count].addr_len = sizeof(pj_sockaddr_in6);
+ pj_sockaddr_init(pj_AF_INET6(), &resolve->addresses.entry[address_count].addr, NULL,
+ target->port);
+ pj_memcpy(&resolve->addresses.entry[address_count].addr.ipv6.sin6_addr, ast_dns_record_get_data(record),
+ ast_dns_record_get_data_size(record));
+ }
+
+ address_count++;
+ } else if (ast_dns_record_get_rr_type(record) == ns_t_srv) {
+ if (have_naptr) {
+ ast_debug(2, "[%p] SRV record being skipped on target '%s' because NAPTR record exists\n",
+ resolve, ast_dns_query_get_name(query));
+ continue;
+ }
+
+ /* SRV records just create new queries for AAAA+A, nothing fancy */
+ ast_debug(2, "[%p] SRV record received on target '%s'\n", resolve, ast_dns_query_get_name(query));
+
+ if (sip_transport_is_available(target->transport + PJSIP_TRANSPORT_IPV6)) {
+ sip_resolve_add(resolve, ast_dns_srv_get_host(record), ns_t_aaaa, ns_c_in, target->transport + PJSIP_TRANSPORT_IPV6,
+ ast_dns_srv_get_port(record));
+ have_srv = 1;
+ }
+
+ if (sip_transport_is_available(target->transport)) {
+ sip_resolve_add(resolve, ast_dns_srv_get_host(record), ns_t_a, ns_c_in, target->transport,
+ ast_dns_srv_get_port(record));
+ have_srv = 1;
+ }
+ } else if (ast_dns_record_get_rr_type(record) == ns_t_naptr) {
+ int added = -1;
+
+ ast_debug(2, "[%p] NAPTR record received on target '%s'\n", resolve, ast_dns_query_get_name(query));
+
+ if (strict_order && (ast_dns_naptr_get_order(record) != order)) {
+ ast_debug(2, "[%p] NAPTR record skipped because order '%hu' does not match strict order '%hu'\n",
+ resolve, ast_dns_naptr_get_order(record), order);
+ continue;
+ }
+
+ if (target->transport == PJSIP_TRANSPORT_UNSPECIFIED || target->transport == PJSIP_TRANSPORT_UDP) {
+ added = sip_resolve_handle_naptr(resolve, record, "sip+d2u", PJSIP_TRANSPORT_UDP);
+ }
+ if (target->transport == PJSIP_TRANSPORT_UNSPECIFIED || target->transport == PJSIP_TRANSPORT_TCP) {
+ added = sip_resolve_handle_naptr(resolve, record, "sip+d2t", PJSIP_TRANSPORT_TCP);
+ }
+ if (target->transport == PJSIP_TRANSPORT_UNSPECIFIED || target->transport == PJSIP_TRANSPORT_TLS) {
+ added = sip_resolve_handle_naptr(resolve, record, "sips+d2t", PJSIP_TRANSPORT_TLS);
+ }
+
+ /* If this record was successfully handled then we need to limit ourselves to this order */
+ if (!added) {
+ have_naptr = 1;
+ strict_order = 1;
+ order = ast_dns_naptr_get_order(record);
+ }
+ }
+ }
+ }
+
+ /* Update the server addresses count, this is not limited as it can never exceed the max allowed */
+ resolve->addresses.count = address_count;
+
+ /* Free the vector we stole as we are responsible for it */
+ AST_VECTOR_FREE(&resolving);
+
+ /* If additional queries were added start the resolution process again */
+ if (resolve->queries) {
+ ast_debug(2, "[%p] New queries added, performing parallel resolution again\n", resolve);
+ ast_dns_query_set_resolve_async(resolve->queries, sip_resolve_callback, resolve);
+ ao2_ref(queries, -1);
+ return;
+ }
+
+ ast_debug(2, "[%p] Resolution completed - %d viable targets\n", resolve, resolve->addresses.count);
+
+ /* Push a task to invoke the callback, we do this so it is guaranteed to run in a PJSIP thread */
+ ao2_ref(resolve, +1);
+ if (ast_sip_push_task(NULL, sip_resolve_invoke_user_callback, resolve)) {
+ ao2_ref(resolve, -1);
+ }
+
+ ao2_ref(queries, -1);
+}
+
+/*!
+ * \internal
+ * \brief Determine what address family a host may be if it is already an IP address
+ *
+ * \param host The host (which may be an IP address)
+ *
+ * \retval 6 The host is an IPv6 address
+ * \retval 4 The host is an IPv4 address
+ * \retval 0 The host is not an IP address
+ */
+static int sip_resolve_get_ip_addr_ver(const pj_str_t *host)
+{
+ pj_in_addr dummy;
+ pj_in6_addr dummy6;
+
+ if (pj_inet_aton(host, &dummy) > 0) {
+ return 4;
+ }
+
+ if (pj_inet_pton(pj_AF_INET6(), host, &dummy6) == PJ_SUCCESS) {
+ return 6;
+ }
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Perform SIP resolution of a host
+ *
+ * \param resolver Configured resolver instance
+ * \param pool Memory pool to allocate things from
+ * \param target The target we are resolving
+ * \param token User data to pass to the resolver callback
+ * \param cb User resolver callback to invoke upon resolution completion
+ */
+static void sip_resolve(pjsip_resolver_t *resolver, pj_pool_t *pool, const pjsip_host_info *target,
+ void *token, pjsip_resolver_callback *cb)
+{
+ int ip_addr_ver;
+ pjsip_transport_type_e type = target->type;
+ struct sip_resolve *resolve;
+ char host[NI_MAXHOST];
+ int res = 0;
+
+ ast_copy_pj_str(host, &target->addr.host, sizeof(host));
+
+ ast_debug(2, "Performing SIP DNS resolution of target '%s'\n", host);
+
+ /* If the provided target is already an address don't bother resolving */
+ ip_addr_ver = sip_resolve_get_ip_addr_ver(&target->addr.host);
+
+ /* Determine the transport to use if none has been explicitly specified */
+ if (type == PJSIP_TRANSPORT_UNSPECIFIED) {
+ /* If we've been told to use a secure or reliable transport restrict ourselves to that */
+#if PJ_HAS_TCP
+ if (target->flag & PJSIP_TRANSPORT_SECURE) {
+ type = PJSIP_TRANSPORT_TLS;
+ } else if (target->flag & PJSIP_TRANSPORT_RELIABLE) {
+ type = PJSIP_TRANSPORT_TCP;
+ } else
+#endif
+ /* According to the RFC otherwise if an explicit IP address OR an explicit port is specified
+ * we use UDP
+ */
+ if (ip_addr_ver || target->addr.port) {
+ type = PJSIP_TRANSPORT_UDP;
+ }
+
+ if (ip_addr_ver == 6) {
+ type = (pjsip_transport_type_e)((int) type + PJSIP_TRANSPORT_IPV6);
+ }
+ }
+
+ ast_debug(2, "Transport type for target '%s' is '%s'\n", host, pjsip_transport_get_type_name(type));
+
+ /* If it's already an address call the callback immediately */
+ if (ip_addr_ver) {
+ pjsip_server_addresses addresses = {
+ .entry[0].type = type,
+ .count = 1,
+ };
+
+ if (ip_addr_ver == 4) {
+ addresses.entry[0].addr_len = sizeof(pj_sockaddr_in);
+ pj_sockaddr_init(pj_AF_INET(), &addresses.entry[0].addr, NULL, 0);
+ pj_inet_aton(&target->addr.host, &addresses.entry[0].addr.ipv4.sin_addr);
+ } else {
+ addresses.entry[0].addr_len = sizeof(pj_sockaddr_in6);
+ pj_sockaddr_init(pj_AF_INET6(), &addresses.entry[0].addr, NULL, 0);
+ pj_inet_pton(pj_AF_INET6(), &target->addr.host, &addresses.entry[0].addr.ipv6.sin6_addr);
+ }
+
+ pj_sockaddr_set_port(&addresses.entry[0].addr, !target->addr.port ? pjsip_transport_get_default_port_for_type(type) : target->addr.port);
+
+ ast_debug(2, "Target '%s' is an IP address, skipping resolution\n", host);
+
+ cb(PJ_SUCCESS, token, &addresses);
+
+ return;
+ }
+
+ resolve = ao2_alloc_options(sizeof(*resolve), sip_resolve_destroy, AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!resolve) {
+ cb(PJ_ENOMEM, token, NULL);
+ return;
+ }
+
+ resolve->callback = cb;
+ resolve->token = token;
+
+ if (AST_VECTOR_INIT(&resolve->resolving, 2)) {
+ ao2_ref(resolve, -1);
+ cb(PJ_ENOMEM, token, NULL);
+ return;
+ }
+
+ ast_debug(2, "[%p] Created resolution tracking for target '%s'\n", resolve, host);
+
+ /* If no port has been specified we can do NAPTR + SRV */
+ if (!target->addr.port) {
+ char srv[NI_MAXHOST];
+
+ res |= sip_resolve_add(resolve, host, ns_t_naptr, ns_c_in, type, 0);
+
+ if ((type == PJSIP_TRANSPORT_TLS || type == PJSIP_TRANSPORT_UNSPECIFIED) &&
+ (sip_transport_is_available(PJSIP_TRANSPORT_TLS) ||
+ sip_transport_is_available(PJSIP_TRANSPORT_TLS6))) {
+ snprintf(srv, sizeof(srv), "_sips._tcp.%s", host);
+ res |= sip_resolve_add(resolve, srv, ns_t_srv, ns_c_in, PJSIP_TRANSPORT_TLS, 0);
+ }
+ if ((type == PJSIP_TRANSPORT_TCP || type == PJSIP_TRANSPORT_UNSPECIFIED) &&
+ (sip_transport_is_available(PJSIP_TRANSPORT_TCP) ||
+ sip_transport_is_available(PJSIP_TRANSPORT_TCP6))) {
+ snprintf(srv, sizeof(srv), "_sip._tcp.%s", host);
+ res |= sip_resolve_add(resolve, srv, ns_t_srv, ns_c_in, PJSIP_TRANSPORT_TCP, 0);
+ }
+ if ((type == PJSIP_TRANSPORT_UDP || type == PJSIP_TRANSPORT_UNSPECIFIED) &&
+ (sip_transport_is_available(PJSIP_TRANSPORT_UDP) ||
+ sip_transport_is_available(PJSIP_TRANSPORT_UDP6))) {
+ snprintf(srv, sizeof(srv), "_sip._udp.%s", host);
+ res |= sip_resolve_add(resolve, srv, ns_t_srv, ns_c_in, PJSIP_TRANSPORT_UDP, 0);
+ }
+ }
+
+ if ((type == PJSIP_TRANSPORT_UNSPECIFIED && sip_transport_is_available(PJSIP_TRANSPORT_UDP6)) ||
+ sip_transport_is_available(type + PJSIP_TRANSPORT_IPV6)) {
+ res |= sip_resolve_add(resolve, host, ns_t_aaaa, ns_c_in, (type == PJSIP_TRANSPORT_UNSPECIFIED ? PJSIP_TRANSPORT_UDP6 : type + PJSIP_TRANSPORT_IPV6), target->addr.port);
+ }
+
+ if ((type == PJSIP_TRANSPORT_UNSPECIFIED && sip_transport_is_available(PJSIP_TRANSPORT_UDP)) ||
+ sip_transport_is_available(type)) {
+ res |= sip_resolve_add(resolve, host, ns_t_a, ns_c_in, (type == PJSIP_TRANSPORT_UNSPECIFIED ? PJSIP_TRANSPORT_UDP : type), target->addr.port);
+ }
+
+ if (res) {
+ ao2_ref(resolve, -1);
+ cb(PJ_ENOMEM, token, NULL);
+ return;
+ }
+
+ ast_debug(2, "[%p] Starting initial resolution using parallel queries for target '%s'\n", resolve, host);
+ ast_dns_query_set_resolve_async(resolve->queries, sip_resolve_callback, resolve);
+
+ ao2_ref(resolve, -1);
+}
+
+/*!
+ * \internal
+ * \brief Determine if a specific transport is configured on the system
+ *
+ * \param pool A memory pool to allocate things from
+ * \param transport The type of transport to check
+ * \param name A friendly name to print in the verbose message
+ *
+ * \return Nothing
+ */
+static void sip_check_transport(pj_pool_t *pool, pjsip_transport_type_e transport, const char *name)
+{
+ pjsip_tpmgr_fla2_param prm;
+ enum sip_resolver_transport resolver_transport;
+
+ pjsip_tpmgr_fla2_param_default(&prm);
+ prm.tp_type = transport;
+
+ if (transport == PJSIP_TRANSPORT_UDP) {
+ resolver_transport = SIP_RESOLVER_TRANSPORT_UDP;
+ } else if (transport == PJSIP_TRANSPORT_TCP) {
+ resolver_transport = SIP_RESOLVER_TRANSPORT_TCP;
+ } else if (transport == PJSIP_TRANSPORT_TLS) {
+ resolver_transport = SIP_RESOLVER_TRANSPORT_TLS;
+ } else if (transport == PJSIP_TRANSPORT_UDP6) {
+ resolver_transport = SIP_RESOLVER_TRANSPORT_UDP6;
+ } else if (transport == PJSIP_TRANSPORT_TCP6) {
+ resolver_transport = SIP_RESOLVER_TRANSPORT_TCP6;
+ } else if (transport == PJSIP_TRANSPORT_TLS6) {
+ resolver_transport = SIP_RESOLVER_TRANSPORT_TLS6;
+ } else {
+ ast_verb(2, "'%s' is an unsupported SIP transport\n", name);
+ return;
+ }
+
+ if (pjsip_tpmgr_find_local_addr2(pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()),
+ pool, &prm) == PJ_SUCCESS) {
+ ast_verb(2, "'%s' is an available SIP transport\n", name);
+ sip_available_transports[resolver_transport] = 1;
+ } else {
+ ast_verb(2, "'%s' is not an available SIP transport, disabling resolver support for it\n",
+ name);
+ }
+}
+
+/*! \brief External resolver implementation for PJSIP */
+static pjsip_ext_resolver resolver = {
+ .resolve = sip_resolve,
+};
+
+/*!
+ * \internal
+ * \brief Task to determine available transports and set ourselves an external resolver
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+static int sip_replace_resolver(void *data)
+{
+ pj_pool_t *pool;
+
+
+ pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "Transport Availability", 256, 256);
+ if (!pool) {
+ return -1;
+ }
+
+ /* Determine what transports are available on the system */
+ sip_check_transport(pool, PJSIP_TRANSPORT_UDP, "UDP+IPv4");
+ sip_check_transport(pool, PJSIP_TRANSPORT_TCP, "TCP+IPv4");
+ sip_check_transport(pool, PJSIP_TRANSPORT_TLS, "TLS+IPv4");
+ sip_check_transport(pool, PJSIP_TRANSPORT_UDP6, "UDP+IPv6");
+ sip_check_transport(pool, PJSIP_TRANSPORT_TCP6, "TCP+IPv6");
+ sip_check_transport(pool, PJSIP_TRANSPORT_TLS6, "TLS+IPv6");
+
+ pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool);
+
+ /* Replace the PJSIP resolver with our own implementation */
+ pjsip_endpt_set_ext_resolver(ast_sip_get_pjsip_endpoint(), &resolver);
+ return 0;
+}
+
+void ast_sip_initialize_resolver(void)
+{
+ /* Replace the existing PJSIP resolver with our own implementation */
+ ast_sip_push_task_synchronous(NULL, sip_replace_resolver, NULL);
+}
+
+#else
+
+void ast_sip_initialize_resolver(void)
+{
+ /* External resolver support does not exist in the version of PJSIP in use */
+ ast_log(LOG_NOTICE, "The version of PJSIP in use does not support external resolvers, using PJSIP provided resolver\n");
+}
+
+#endif
diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c
index e7b4b02ec..cf649b453 100644
--- a/res/res_pjsip_pubsub.c
+++ b/res/res_pjsip_pubsub.c
@@ -2601,11 +2601,12 @@ static pj_bool_t pubsub_on_rx_subscribe_request(pjsip_rx_data *rdata)
sip_subscription_accept(sub_tree, rdata, resp);
if (generate_initial_notify(sub_tree->root)) {
pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE);
+ } else {
+ send_notify(sub_tree, 1);
+ ast_test_suite_event_notify("SUBSCRIPTION_ESTABLISHED",
+ "Resource: %s",
+ sub_tree->root->resource);
}
- send_notify(sub_tree, 1);
- ast_test_suite_event_notify("SUBSCRIPTION_ESTABLISHED",
- "Resource: %s",
- sub_tree->root->resource);
}
resource_tree_destroy(&tree);