The pimp_my_sip branch is being merged at this point because it offers basic functionality, and from an API standpoint, things are complete. SIP work is *not* feature-complete; however, with the completion of the SUBSCRIBE/NOTIFY API, all APIs (except a PUBLISH API) have been created, and thus it is possible for developers to attempt to create new SIP work. API documentation can be found in the doxygen in the code, but usability documentation is still lacking. git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@386540 65c4cc65-6c06-0410-ace0-fbb531ad65f3
379 lines
10 KiB
C
379 lines
10 KiB
C
/*
|
|
* sip_options.c
|
|
*
|
|
* Created on: Jan 25, 2013
|
|
* Author: mjordan
|
|
*/
|
|
|
|
#include "asterisk.h"
|
|
|
|
#include <pjsip.h>
|
|
#include <pjsip_ua.h>
|
|
#include <pjlib.h>
|
|
|
|
#include "asterisk/res_sip.h"
|
|
#include "asterisk/channel.h"
|
|
#include "asterisk/pbx.h"
|
|
#include "asterisk/astobj2.h"
|
|
#include "asterisk/cli.h"
|
|
#include "include/res_sip_private.h"
|
|
|
|
#define DEFAULT_LANGUAGE "en"
|
|
#define DEFAULT_ENCODING "text/plain"
|
|
#define QUALIFIED_BUCKETS 211
|
|
|
|
/*! \brief Scheduling context for qualifies */
|
|
static struct ast_sched_context *sched; /* XXX move this to registrar */
|
|
|
|
struct ao2_container *scheduled_qualifies;
|
|
|
|
struct qualify_info {
|
|
AST_DECLARE_STRING_FIELDS(
|
|
AST_STRING_FIELD(endpoint_id);
|
|
);
|
|
char *scheduler_data;
|
|
int scheduler_id;
|
|
};
|
|
|
|
static pj_bool_t options_module_start(void);
|
|
static pj_bool_t options_module_stop(void);
|
|
static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata);
|
|
static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata);
|
|
|
|
static pjsip_module options_module = {
|
|
.name = {"Options Module", 14},
|
|
.id = -1,
|
|
.priority = PJSIP_MOD_PRIORITY_APPLICATION,
|
|
.start = options_module_start,
|
|
.stop = options_module_stop,
|
|
.on_rx_request = options_module_on_rx_request,
|
|
.on_rx_response = options_module_on_rx_response,
|
|
};
|
|
|
|
static pj_bool_t options_module_start(void)
|
|
{
|
|
if (!(sched = ast_sched_context_create()) ||
|
|
ast_sched_start_thread(sched)) {
|
|
return -1;
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
static pj_bool_t options_module_stop(void)
|
|
{
|
|
ao2_t_ref(scheduled_qualifies, -1, "Remove scheduled qualifies on module stop");
|
|
|
|
if (sched) {
|
|
ast_sched_context_destroy(sched);
|
|
}
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
static pj_status_t send_options_response(pjsip_rx_data *rdata, pjsip_dialog *pj_dlg, int code)
|
|
{
|
|
pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint();
|
|
pjsip_transaction *pj_trans = pjsip_rdata_get_tsx(rdata);
|
|
pjsip_tx_data *tdata;
|
|
const pjsip_hdr *hdr;
|
|
pjsip_response_addr res_addr;
|
|
pj_status_t status;
|
|
|
|
/* Make the response object */
|
|
status = pjsip_endpt_create_response(endpt, rdata, code, NULL, &tdata);
|
|
if (status != PJ_SUCCESS) {
|
|
return status;
|
|
}
|
|
|
|
/* Add appropriate headers */
|
|
if ((hdr = pjsip_endpt_get_capability(endpt, PJSIP_H_ACCEPT, NULL))) {
|
|
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr));
|
|
}
|
|
if ((hdr = pjsip_endpt_get_capability(endpt, PJSIP_H_ALLOW, NULL))) {
|
|
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr));
|
|
}
|
|
if ((hdr = pjsip_endpt_get_capability(endpt, PJSIP_H_SUPPORTED, NULL))) {
|
|
pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)pjsip_hdr_clone(tdata->pool, hdr));
|
|
}
|
|
|
|
/*
|
|
* XXX TODO: pjsip doesn't care a lot about either of these headers -
|
|
* while it provides specific methods to create them, they are defined
|
|
* to be the standard string header creation. We never did add them
|
|
* in chan_sip, although RFC 3261 says they SHOULD. Hard coded here.
|
|
*/
|
|
ast_sip_add_header(tdata, "Accept-Encoding", DEFAULT_ENCODING);
|
|
ast_sip_add_header(tdata, "Accept-Language", DEFAULT_LANGUAGE);
|
|
|
|
if (pj_dlg && pj_trans) {
|
|
status = pjsip_dlg_send_response(pj_dlg, pj_trans, tdata);
|
|
} else {
|
|
/* Get where to send request. */
|
|
status = pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
|
|
if (status != PJ_SUCCESS) {
|
|
pjsip_tx_data_dec_ref(tdata);
|
|
return status;
|
|
}
|
|
status = pjsip_endpt_send_response(endpt, &res_addr, tdata, NULL, NULL);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static pj_bool_t options_module_on_rx_request(pjsip_rx_data *rdata)
|
|
{
|
|
RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
|
|
pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata);
|
|
pjsip_uri *ruri;
|
|
pjsip_sip_uri *sip_ruri;
|
|
char exten[AST_MAX_EXTENSION];
|
|
|
|
if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_options_method)) {
|
|
return PJ_FALSE;
|
|
}
|
|
endpoint = ast_pjsip_rdata_get_endpoint(rdata);
|
|
ast_assert(endpoint != NULL);
|
|
|
|
ruri = rdata->msg_info.msg->line.req.uri;
|
|
if (!PJSIP_URI_SCHEME_IS_SIP(ruri) && !PJSIP_URI_SCHEME_IS_SIPS(ruri)) {
|
|
send_options_response(rdata, dlg, 416);
|
|
return -1;
|
|
}
|
|
|
|
sip_ruri = pjsip_uri_get_uri(ruri);
|
|
ast_copy_pj_str(exten, &sip_ruri->user, sizeof(exten));
|
|
|
|
if (ast_shutting_down()) {
|
|
send_options_response(rdata, dlg, 503);
|
|
} else if (!ast_exists_extension(NULL, endpoint->context, exten, 1, NULL)) {
|
|
send_options_response(rdata, dlg, 404);
|
|
} else {
|
|
send_options_response(rdata, dlg, 200);
|
|
}
|
|
return PJ_TRUE;
|
|
}
|
|
|
|
static pj_bool_t options_module_on_rx_response(pjsip_rx_data *rdata)
|
|
{
|
|
|
|
return PJ_SUCCESS;
|
|
}
|
|
|
|
static int qualify_info_hash_fn(const void *obj, int flags)
|
|
{
|
|
const struct qualify_info *info = obj;
|
|
const char *endpoint_id = flags & OBJ_KEY ? obj : info->endpoint_id;
|
|
|
|
return ast_str_hash(endpoint_id);
|
|
}
|
|
|
|
static int qualify_info_cmp_fn(void *obj, void *arg, int flags)
|
|
{
|
|
struct qualify_info *left = obj;
|
|
struct qualify_info *right = arg;
|
|
const char *right_endpoint_id = flags & OBJ_KEY ? arg : right->endpoint_id;
|
|
|
|
return strcmp(left->endpoint_id, right_endpoint_id) ? 0 : CMP_MATCH | CMP_STOP;
|
|
}
|
|
|
|
|
|
static void qualify_info_destructor(void *obj)
|
|
{
|
|
struct qualify_info *info = obj;
|
|
if (!info) {
|
|
return;
|
|
}
|
|
ast_string_field_free_memory(info);
|
|
/* Cancel the qualify */
|
|
if (!AST_SCHED_DEL(sched, info->scheduler_id)) {
|
|
/* If we successfully deleted the qualify, we got it before it
|
|
* fired. We can safely delete the data that was passed to it.
|
|
* Otherwise, we're getting deleted while this is firing - don't
|
|
* touch that memory!
|
|
*/
|
|
ast_free(info->scheduler_data);
|
|
}
|
|
}
|
|
|
|
static struct qualify_info *create_qualify_info(struct ast_sip_endpoint *endpoint)
|
|
{
|
|
struct qualify_info *info;
|
|
|
|
info = ao2_alloc(sizeof(*info), qualify_info_destructor);
|
|
if (!info) {
|
|
return NULL;
|
|
}
|
|
|
|
if (ast_string_field_init(info, 64)) {
|
|
ao2_ref(info, -1);
|
|
return NULL;
|
|
}
|
|
ast_string_field_set(info, endpoint_id, ast_sorcery_object_get_id(endpoint));
|
|
|
|
return info;
|
|
}
|
|
|
|
static int send_qualify_request(void *data)
|
|
{
|
|
struct ast_sip_endpoint *endpoint = data;
|
|
pjsip_tx_data *tdata;
|
|
/* YAY! Send an OPTIONS request. */
|
|
|
|
ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata);
|
|
ast_sip_send_request(tdata, NULL, endpoint);
|
|
|
|
ao2_cleanup(endpoint);
|
|
return 0;
|
|
}
|
|
|
|
static int qualify_endpoint_scheduler_cb(const void *data)
|
|
{
|
|
RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
|
|
struct ast_sorcery *sorcery;
|
|
char *endpoint_id = (char *)data;
|
|
|
|
sorcery = ast_sip_get_sorcery();
|
|
if (!sorcery) {
|
|
ast_free(endpoint_id);
|
|
return 0;
|
|
}
|
|
|
|
endpoint = ast_sorcery_retrieve_by_id(sorcery, "endpoint", endpoint_id);
|
|
if (!endpoint) {
|
|
/* Whoops, endpoint went away */
|
|
ast_free(endpoint_id);
|
|
return 0;
|
|
}
|
|
|
|
ast_sip_push_task(NULL, send_qualify_request, endpoint);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void schedule_qualifies(void)
|
|
{
|
|
RAII_VAR(struct ao2_container *, endpoints, NULL, ao2_cleanup);
|
|
struct ao2_iterator it_endpoints;
|
|
struct ast_sip_endpoint *endpoint;
|
|
struct qualify_info *info;
|
|
char *endpoint_id;
|
|
|
|
endpoints = ast_res_sip_get_endpoints();
|
|
if (!endpoints) {
|
|
return;
|
|
}
|
|
|
|
it_endpoints = ao2_iterator_init(endpoints, 0);
|
|
while ((endpoint = ao2_iterator_next(&it_endpoints))) {
|
|
if (endpoint->qualify_frequency) {
|
|
/* XXX TODO: This really should only qualify registered peers,
|
|
* which means we need a registrar. We should check the
|
|
* registrar to see if this endpoint has registered and, if
|
|
* not, pass on it.
|
|
*
|
|
* Actually, all of this should just get moved into the registrar.
|
|
* Otherwise, the registar will have to kick this off when a
|
|
* new endpoint registers, so it just makes sense to have it
|
|
* all live there.
|
|
*/
|
|
info = create_qualify_info(endpoint);
|
|
if (!info) {
|
|
ao2_ref(endpoint, -1);
|
|
break;
|
|
}
|
|
endpoint_id = ast_strdup(info->endpoint_id);
|
|
if (!endpoint_id) {
|
|
ao2_t_ref(info, -1, "Dispose of info on off nominal");
|
|
ao2_ref(endpoint, -1);
|
|
break;
|
|
}
|
|
info->scheduler_data = endpoint_id;
|
|
info->scheduler_id = ast_sched_add_variable(sched, endpoint->qualify_frequency * 1000, qualify_endpoint_scheduler_cb, endpoint_id, 1);
|
|
ao2_t_link(scheduled_qualifies, info, "Link scheduled qualify information into container");
|
|
ao2_t_ref(info, -1, "Dispose of creation ref");
|
|
}
|
|
ao2_t_ref(endpoint, -1, "Dispose of iterator ref");
|
|
}
|
|
ao2_iterator_destroy(&it_endpoints);
|
|
}
|
|
|
|
static char *send_options(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
|
|
{
|
|
RAII_VAR(struct ast_sip_endpoint *, endpoint, NULL, ao2_cleanup);
|
|
const char *endpoint_name;
|
|
pjsip_tx_data *tdata;
|
|
|
|
switch (cmd) {
|
|
case CLI_INIT:
|
|
e->command = "sip send options";
|
|
e->usage =
|
|
"Usage: sip send options <endpoint>\n"
|
|
" Send a SIP OPTIONS request to the specified endpoint.\n";
|
|
return NULL;
|
|
case CLI_GENERATE:
|
|
return NULL;
|
|
}
|
|
|
|
if (a->argc != 4) {
|
|
return CLI_SHOWUSAGE;
|
|
}
|
|
|
|
endpoint_name = a->argv[3];
|
|
|
|
endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name);
|
|
if (!endpoint) {
|
|
ast_log(LOG_ERROR, "Unable to retrieve endpoint %s\n", endpoint_name);
|
|
return CLI_FAILURE;
|
|
}
|
|
|
|
if (ast_sip_create_request("OPTIONS", NULL, endpoint, NULL, &tdata)) {
|
|
ast_log(LOG_ERROR, "Unable to create OPTIONS request to endpoint %s\n", endpoint_name);
|
|
return CLI_FAILURE;
|
|
}
|
|
|
|
if (ast_sip_send_request(tdata, NULL, endpoint)) {
|
|
ast_log(LOG_ERROR, "Unable to send OPTIONS request to endpoint %s\n", endpoint_name);
|
|
return CLI_FAILURE;
|
|
}
|
|
|
|
return CLI_SUCCESS;
|
|
}
|
|
|
|
static struct ast_cli_entry cli_options[] = {
|
|
AST_CLI_DEFINE(send_options, "Send an OPTIONS requst to an arbitrary SIP URI"),
|
|
};
|
|
|
|
int ast_res_sip_init_options_handling(int reload)
|
|
{
|
|
const pj_str_t STR_OPTIONS = { "OPTIONS", 7 };
|
|
|
|
if (scheduled_qualifies) {
|
|
ao2_t_ref(scheduled_qualifies, -1, "Remove old scheduled qualifies");
|
|
}
|
|
scheduled_qualifies = ao2_t_container_alloc(QUALIFIED_BUCKETS, qualify_info_hash_fn, qualify_info_cmp_fn, "Create container for scheduled qualifies");
|
|
if (!scheduled_qualifies) {
|
|
return -1;
|
|
}
|
|
|
|
if (reload) {
|
|
return 0;
|
|
}
|
|
|
|
if (pjsip_endpt_register_module(ast_sip_get_pjsip_endpoint(), &options_module) != PJ_SUCCESS) {
|
|
options_module_stop();
|
|
return -1;
|
|
}
|
|
|
|
if (pjsip_endpt_add_capability(ast_sip_get_pjsip_endpoint(), NULL, PJSIP_H_ALLOW, NULL, 1, &STR_OPTIONS) != PJ_SUCCESS) {
|
|
pjsip_endpt_unregister_module(ast_sip_get_pjsip_endpoint(), &options_module);
|
|
return -1;
|
|
}
|
|
|
|
ast_cli_register_multiple(cli_options, ARRAY_LEN(cli_options));
|
|
|
|
schedule_qualifies();
|
|
|
|
return 0;
|
|
}
|