]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
res/ari: Register Stasis application on WebSocket attempt 86/486/4
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:12:03 +0000 (11:12 -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 d159741c268b4896a92a3cd6c68dd35883feb0af..09bcafc2d1e82ca1ea72e28e3531cc69acedbea2 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 40a9dfe8ca007426b923d0d167d3e4f190bd4b92..4265385116a9925b40f1077de717829857bce6f4 100644 (file)
@@ -53,7 +53,92 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #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 330e711e68fe7d11d3a4bc3f1704aef20acf3957..caa27abfc2ce7316c98116bd8a75324de35dab91 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 4cc9e0db04a770cd3c79a005f829f001e89dee4d..08f6204be2af65925608b04eb25842eaeb5010b1 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}}