]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
res/ari: Register Stasis application on WebSocket attempt 20/520/1
authorMatt Jordan <mjordan@digium.com>
Mon, 18 May 2015 01:36:41 +0000 (20:36 -0500)
committerMatt Jordan <mjordan@digium.com>
Fri, 22 May 2015 16:13:34 +0000 (11:13 -0500)
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

res/ari/resource_events.c
res/ari/resource_events.h
res/res_ari_events.c
res/stasis/app.c
rest-api-templates/ari_resource.h.mustache
rest-api-templates/res_ari_resource.c.mustache

index eddcb662c94694c047c73b938bc402380274846e..e666f2e05866b889b6d648f52940bbad64fe06db 100644 (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)
 {
index 646cf9bfceb763fa27cc7f817bcd169b0871f12f..2b631819b266ac93fa9cb23564ed58d778f03db3 100644 (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 */
index 66b01a743d998fa77dbce78fcc6fb3dd09de7d81..454233945e7f234e208667b0e83df69868bef5d9 100644 (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;
index 5a1c5f8e24b121fe46d76e2a221ba87f381a2e10..b99e23205e560fef439f0854bdb1a5048b245329 100644 (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);
index 3a20776a77a11b2c1dade8dd18dfe5dc98b8753f..d3f40b6bd65e2e21b9a0edaa7092940777242726 100644 (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}}
index 7d138b7af25b2e48f71e065db26cd77fd4d8897f..7fe360e896c75983299e8d4bc2714fa3165948ea 100644 (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}}