diff options
Diffstat (limited to 'res')
-rw-r--r-- | res/res_pjsip.c | 184 | ||||
-rw-r--r-- | res/res_pjsip/config_global.c | 21 | ||||
-rw-r--r-- | res/res_pjsip/include/res_pjsip_private.h | 6 | ||||
-rw-r--r-- | res/res_pjsip/location.c | 39 | ||||
-rw-r--r-- | res/res_pjsip/pjsip_configuration.c | 123 | ||||
-rw-r--r-- | res/res_pjsip/pjsip_options.c | 78 | ||||
-rw-r--r-- | res/res_pjsip/pjsip_resolver.c | 669 | ||||
-rw-r--r-- | res/res_pjsip_pubsub.c | 9 |
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); |