res/ari: Register Stasis application on WebSocket attempt

Prior to this patch, when a WebSocket connection is made, ARI would not
be informed of the connection until after the WebSocket layer had
accepted the connection. This created a brief race condition where the
ARI client would be notified that it was connected, a channel would be
sent into the Stasis dialplan application, but ARI would not yet have
registered the Stasis application presented in the HTTP request that
established the WebSocket.

This patch resolves this issue by doing the following:
 * When a WebSocket attempt is made, a callback is made into the ARI
   application layer, which verifies and registers the apps presented in
   the HTTP request. Because we do not yet have a WebSocket, we cannot
   have an event session for the corresponding applications. Some
   defensive checks were thus added to make the application objects
   tolerant to a NULL event session.
 * When a WebSocket connection is made, the registered application is
   updated with the newly created event session that wraps the WebSocket
   connection.

ASTERISK-24988 #close
Reported by: Joshua Colp

Change-Id: Ia5dc60dc2b6bee76cd5aff0f69dd53b36e83f636
This commit is contained in:
Matt Jordan 2015-05-17 20:36:41 -05:00
parent d7086a27b4
commit 9cffcca5f9
6 changed files with 231 additions and 25 deletions

View File

@ -119,6 +119,10 @@ static void app_handler(void *data, const char *app_name,
const char *msg_application = S_OR(
ast_json_string_get(ast_json_object_get(message, "application")),
"");
if (!session) {
return;
}
/* Determine if we've been replaced */
if (strcmp(msg_type, "ApplicationReplaced") == 0 &&
@ -168,7 +172,40 @@ static int session_register_app(struct event_session *session,
return 0;
}
void ast_ari_websocket_events_event_websocket(struct ast_ari_websocket_session *ws_session,
int ast_ari_websocket_events_event_websocket_attempted(struct ast_tcptls_session_instance *ser,
struct ast_variable *headers,
struct ast_ari_events_event_websocket_args *args)
{
int res = 0;
size_t i, j;
ast_debug(3, "/events WebSocket attempted\n");
if (args->app_count == 0) {
ast_http_error(ser, 400, "Bad Request", "Missing param 'app'");
return -1;
}
for (i = 0; i < args->app_count; ++i) {
if (ast_strlen_zero(args->app[i])) {
res = -1;
break;
}
res |= stasis_app_register(args->app[i], app_handler, NULL);
}
if (res) {
for (j = 0; j < i; ++j) {
stasis_app_unregister(args->app[j]);
}
ast_http_error(ser, 400, "Bad Request", "Invalid application provided in param 'app'.");
}
return res;
}
void ast_ari_websocket_events_event_websocket_established(struct ast_ari_websocket_session *ws_session,
struct ast_variable *headers,
struct ast_ari_events_event_websocket_args *args)
{

View File

@ -48,6 +48,19 @@ struct ast_ari_events_event_websocket_args {
/*! Parsing context for app. */
char *app_parse;
};
/*!
* \brief WebSocket connection for events.
*
* \param ser HTTP TCP/TLS Server Session
* \param headers HTTP headers
* \param args Swagger parameters
*
* \retval 0 success
* \retval non-zero error
*/
int ast_ari_websocket_events_event_websocket_attempted(struct ast_tcptls_session_instance *ser, struct ast_variable *headers, struct ast_ari_events_event_websocket_args *args);
/*!
* \brief WebSocket connection for events.
*
@ -55,7 +68,7 @@ struct ast_ari_events_event_websocket_args {
* \param headers HTTP headers.
* \param args Swagger parameters.
*/
void ast_ari_websocket_events_event_websocket(struct ast_ari_websocket_session *session, struct ast_variable *headers, struct ast_ari_events_event_websocket_args *args);
void ast_ari_websocket_events_event_websocket_established(struct ast_ari_websocket_session *session, struct ast_variable *headers, struct ast_ari_events_event_websocket_args *args);
/*! Argument struct for ast_ari_events_user_event() */
struct ast_ari_events_user_event_args {
/*! Event name */

View File

@ -53,7 +53,92 @@ ASTERISK_REGISTER_FILE()
#define MAX_VALS 128
static void ast_ari_events_event_websocket_ws_cb(struct ast_websocket *ws_session,
static int ast_ari_events_event_websocket_ws_attempted_cb(struct ast_tcptls_session_instance *ser, struct ast_variable *get_params, struct ast_variable *headers)
{
struct ast_ari_events_event_websocket_args args = {};
int res = 0;
RAII_VAR(struct ast_ari_response *, response, NULL, ast_free);
struct ast_variable *i;
response = ast_calloc(1, sizeof(*response));
if (!response) {
ast_log(LOG_ERROR, "Failed to create response.\n");
goto fin;
}
for (i = get_params; i; i = i->next) {
if (strcmp(i->name, "app") == 0) {
/* Parse comma separated list */
char *vals[MAX_VALS];
size_t j;
args.app_parse = ast_strdup(i->value);
if (!args.app_parse) {
ast_ari_response_alloc_failed(response);
goto fin;
}
if (strlen(args.app_parse) == 0) {
/* ast_app_separate_args can't handle "" */
args.app_count = 1;
vals[0] = args.app_parse;
} else {
args.app_count = ast_app_separate_args(
args.app_parse, ',', vals,
ARRAY_LEN(vals));
}
if (args.app_count == 0) {
ast_ari_response_alloc_failed(response);
goto fin;
}
if (args.app_count >= MAX_VALS) {
ast_ari_response_error(response, 400,
"Bad Request",
"Too many values for app");
goto fin;
}
args.app = ast_malloc(sizeof(*args.app) * args.app_count);
if (!args.app) {
ast_ari_response_alloc_failed(response);
goto fin;
}
for (j = 0; j < args.app_count; ++j) {
args.app[j] = (vals[j]);
}
} else
{}
}
res = ast_ari_websocket_events_event_websocket_attempted(ser, headers, &args);
fin: __attribute__((unused))
if (!response) {
ast_http_error(ser, 500, "Server Error", "Memory allocation error");
res = -1;
} else if (response->response_code != 0) {
/* Param parsing failure */
RAII_VAR(char *, msg, NULL, ast_json_free);
if (response->message) {
msg = ast_json_dump_string(response->message);
} else {
ast_log(LOG_ERROR, "Missing response message\n");
}
if (msg) {
ast_http_error(ser, response->response_code, response->response_text, msg);
}
res = -1;
}
ast_free(args.app_parse);
ast_free(args.app);
return res;
}
static void ast_ari_events_event_websocket_ws_established_cb(struct ast_websocket *ws_session,
struct ast_variable *get_params, struct ast_variable *headers)
{
struct ast_ari_events_event_websocket_args args = {};
@ -126,16 +211,11 @@ static void ast_ari_events_event_websocket_ws_cb(struct ast_websocket *ws_sessio
{}
}
ast_ari_websocket_events_event_websocket(session, headers, &args);
ast_ari_websocket_events_event_websocket_established(session, headers, &args);
fin: __attribute__((unused))
if (response && response->response_code != 0) {
/* Param parsing failure */
/* TODO - ideally, this would return the error code to the
* HTTP client; but we've already done the WebSocket
* negotiation. Param parsing should happen earlier, but we
* need a way to pass it through the WebSocket code to the
* callback */
RAII_VAR(char *, msg, NULL, ast_json_free);
if (response->message) {
msg = ast_json_dump_string(response->message);
@ -351,12 +431,22 @@ static struct stasis_rest_handlers events = {
static int load_module(void)
{
int res = 0;
struct ast_websocket_protocol *protocol;
events.ws_server = ast_websocket_server_create();
if (!events.ws_server) {
return AST_MODULE_LOAD_FAILURE;
}
res |= ast_websocket_server_add_protocol(events.ws_server,
"ari", ast_ari_events_event_websocket_ws_cb);
protocol = ast_websocket_sub_protocol_alloc("ari");
if (!protocol) {
ao2_ref(events.ws_server, -1);
events.ws_server = NULL;
return AST_MODULE_LOAD_FAILURE;
}
protocol->session_attempted = ast_ari_events_event_websocket_ws_attempted_cb;
protocol->session_established = ast_ari_events_event_websocket_ws_established_cb;
res |= ast_websocket_server_add_protocol2(events.ws_server, protocol);
stasis_app_ref();
res |= ast_ari_add_handler(&events);
return res;

View File

@ -871,8 +871,7 @@ struct stasis_app *app_create(const char *name, stasis_app_cb handler, void *dat
strncpy(app->name, name, size - sizeof(*app));
app->handler = handler;
ao2_ref(data, +1);
app->data = data;
app->data = ao2_bump(data);
ao2_ref(app, +1);
return app;
@ -950,7 +949,7 @@ void app_update(struct stasis_app *app, stasis_app_cb handler, void *data)
{
SCOPED_AO2LOCK(lock, app);
if (app->handler) {
if (app->handler && app->data) {
RAII_VAR(struct ast_json *, msg, NULL, ast_json_unref);
ast_verb(1, "Replacing Stasis app '%s'\n", app->name);

View File

@ -89,6 +89,23 @@ int ast_ari_{{c_name}}_{{c_nickname}}_parse_body(
void ast_ari_{{c_name}}_{{c_nickname}}(struct ast_variable *headers, struct ast_ari_{{c_name}}_{{c_nickname}}_args *args, struct ast_ari_response *response);
{{/is_req}}
{{#is_websocket}}
/*!
* \brief {{summary}}
{{#notes}}
*
* {{{notes}}}
{{/notes}}
*
* \param ser HTTP TCP/TLS Server Session
* \param headers HTTP headers
* \param args Swagger parameters
*
* \retval 0 success
* \retval non-zero error
*/
int ast_ari_websocket_{{c_name}}_{{c_nickname}}_attempted(struct ast_tcptls_session_instance *ser, struct ast_variable *headers, struct ast_ari_{{c_name}}_{{c_nickname}}_args *args);
/*!
* \brief {{summary}}
{{#notes}}
@ -100,7 +117,7 @@ void ast_ari_{{c_name}}_{{c_nickname}}(struct ast_variable *headers, struct ast_
* \param headers HTTP headers.
* \param args Swagger parameters.
*/
void ast_ari_websocket_{{c_name}}_{{c_nickname}}(struct ast_ari_websocket_session *session, struct ast_variable *headers, struct ast_ari_{{c_name}}_{{c_nickname}}_args *args);
void ast_ari_websocket_{{c_name}}_{{c_nickname}}_established(struct ast_ari_websocket_session *session, struct ast_variable *headers, struct ast_ari_{{c_name}}_{{c_nickname}}_args *args);
{{/is_websocket}}
{{/operations}}
{{/apis}}

View File

@ -137,7 +137,52 @@ fin: __attribute__((unused))
}
{{/is_req}}
{{#is_websocket}}
static void ast_ari_{{c_name}}_{{c_nickname}}_ws_cb(struct ast_websocket *ws_session,
static int ast_ari_{{c_name}}_{{c_nickname}}_ws_attempted_cb(struct ast_tcptls_session_instance *ser, struct ast_variable *get_params, struct ast_variable *headers)
{
struct ast_ari_{{c_name}}_{{c_nickname}}_args args = {};
{{#has_parameters}}
int res = 0;
RAII_VAR(struct ast_ari_response *, response, NULL, ast_free);
struct ast_variable *i;
{{/has_parameters}}
{{#has_parameters}}
response = ast_calloc(1, sizeof(*response));
if (!response) {
ast_log(LOG_ERROR, "Failed to create response.\n");
goto fin;
}
{{/has_parameters}}
{{> param_parsing}}
res = ast_ari_websocket_{{c_name}}_{{c_nickname}}_attempted(ser, headers, &args);
fin: __attribute__((unused))
if (!response) {
ast_http_error(ser, 500, "Server Error", "Memory allocation error");
res = -1;
} else if (response->response_code != 0) {
/* Param parsing failure */
RAII_VAR(char *, msg, NULL, ast_json_free);
if (response->message) {
msg = ast_json_dump_string(response->message);
} else {
ast_log(LOG_ERROR, "Missing response message\n");
}
if (msg) {
ast_http_error(ser, response->response_code, response->response_text, msg);
}
res = -1;
}
{{> param_cleanup}}
{{#has_parameters}}
return res;
{{/has_parameters}}
}
static void ast_ari_{{c_name}}_{{c_nickname}}_ws_established_cb(struct ast_websocket *ws_session,
struct ast_variable *get_params, struct ast_variable *headers)
{
struct ast_ari_{{c_name}}_{{c_nickname}}_args args = {};
@ -175,16 +220,11 @@ static void ast_ari_{{c_name}}_{{c_nickname}}_ws_cb(struct ast_websocket *ws_ses
{{> param_parsing}}
ast_ari_websocket_{{c_name}}_{{c_nickname}}(session, headers, &args);
ast_ari_websocket_{{c_name}}_{{c_nickname}}_established(session, headers, &args);
fin: __attribute__((unused))
if (response && response->response_code != 0) {
/* Param parsing failure */
/* TODO - ideally, this would return the error code to the
* HTTP client; but we've already done the WebSocket
* negotiation. Param parsing should happen earlier, but we
* need a way to pass it through the WebSocket code to the
* callback */
RAII_VAR(char *, msg, NULL, ast_json_free);
if (response->message) {
msg = ast_json_dump_string(response->message);
@ -211,16 +251,26 @@ static int load_module(void)
{
int res = 0;
{{#apis}}
{{#operations}}
{{#has_websocket}}
struct ast_websocket_protocol *protocol;
{{full_name}}.ws_server = ast_websocket_server_create();
if (!{{full_name}}.ws_server) {
return AST_MODULE_LOAD_FAILURE;
}
protocol = ast_websocket_sub_protocol_alloc("{{websocket_protocol}}");
if (!protocol) {
ao2_ref({{full_name}}.ws_server, -1);
{{full_name}}.ws_server = NULL;
return AST_MODULE_LOAD_FAILURE;
}
protocol->session_attempted = ast_ari_{{c_name}}_{{c_nickname}}_ws_attempted_cb;
protocol->session_established = ast_ari_{{c_name}}_{{c_nickname}}_ws_established_cb;
{{/has_websocket}}
{{#operations}}
{{#is_websocket}}
res |= ast_websocket_server_add_protocol({{full_name}}.ws_server,
"{{websocket_protocol}}", ast_ari_{{c_name}}_{{c_nickname}}_ws_cb);
res |= ast_websocket_server_add_protocol2({{full_name}}.ws_server, protocol);
{{/is_websocket}}
{{/operations}}
{{/apis}}