[Asterisk-code-review] res/res http websocket: Add a pre-session established callback (asterisk[master])

Matt Jordan asteriskteam at digium.com
Thu May 21 07:21:04 CDT 2015


Matt Jordan has submitted this change and it was merged.

Change subject: res/res_http_websocket: Add a pre-session established callback
......................................................................


res/res_http_websocket: Add a pre-session established callback

This patch updates http_websocket and its corresponding implementation
with a pre-session established callback. This callback allows for
WebSocket server consumers to be notified when a WebSocket connection is
attempted, but before we accept it. Consumers can choose to reject the
connection, if their application specific logic allows for it.

As a result, this patch pulls out the previously private
websocket_protocol struct and makes it public, as
ast_websocket_protocol. In order to preserve backwards compatibility
with existing modules, the existing APIs were left as-is, and new APIs
were added for the creation of the ast_websocket_protocol as well as for
adding a sub-protocol to a WebSocket server.

In particular, the following new API calls were added:
* ast_websocket_add_protocol2 - add a protocol to the core WebSocket
  server
* ast_websocket_server_add_protocol2 - add a protocol to a specific
  WebSocket server
* ast_websocket_sub_protocol_alloc - allocate a sub-protocol object.
  Consumers can populate this with whatever callbacks they wish to
  support, then add it to the core server or a specified server.

ASTERISK-24988
Reported by: Joshua Colp

Change-Id: Ibe0bbb30c17eec6b578071bdbd197c911b620ab2
---
M include/asterisk/http_websocket.h
M res/res_http_websocket.c
2 files changed, 172 insertions(+), 33 deletions(-)

Approvals:
  Matt Jordan: Looks good to me, approved; Verified
  Joshua Colp: Looks good to me, but someone else must approve



diff --git a/include/asterisk/http_websocket.h b/include/asterisk/http_websocket.h
index 3e07e60..5adc089 100644
--- a/include/asterisk/http_websocket.h
+++ b/include/asterisk/http_websocket.h
@@ -68,6 +68,24 @@
 struct ast_websocket;
 
 /*!
+ * \brief Callback from the HTTP request attempting to establish a websocket connection
+ *
+ * This callback occurs when an HTTP request is made to establish a websocket connection.
+ * Implementers of \ref ast_websocket_protocol can use this to deny a request, or to
+ * set up application specific data before invocation of \ref ast_websocket_callback.
+ *
+ * \param ser The TCP/TLS session
+ * \param parameters Parameters extracted from the request URI
+ * \param headers Headers included in the request
+ *
+ * \retval 0 The session should be accepted
+ * \retval -1 The session should be rejected. Note that the caller must send an error
+ * response using \ref ast_http_error.
+ * \since 13.5.0
+ */
+typedef int (*ast_websocket_pre_callback)(struct ast_tcptls_session_instance *ser, struct ast_variable *parameters, struct ast_variable *headers);
+
+/*!
  * \brief Callback for when a new connection for a sub-protocol is established
  *
  * \param session A WebSocket session structure
@@ -79,6 +97,32 @@
  *
  */
 typedef void (*ast_websocket_callback)(struct ast_websocket *session, struct ast_variable *parameters, struct ast_variable *headers);
+
+/*!
+ * \brief A websocket protocol implementation
+ *
+ * Users of the Websocket API can register themselves as a websocket
+ * protocol. See \ref ast_websocket_add_protocol2 and \ref ast_websocket_server_add_protocol2.
+ * Simpler implementations may use only \ref ast_websocket_add_protocol and
+ * \ref ast_websocket_server_add_protocol.
+ *
+ * \since 13.5.0
+ */
+struct ast_websocket_protocol {
+	/*! \brief Name of the protocol */
+	char *name;
+/*!
+ * \brief Protocol version. This prevents dynamically loadable modules from registering
+ * if this struct is changed.
+ */
+#define AST_WEBSOCKET_PROTOCOL_VERSION 1
+	/*! \brief Protocol version. Should be set to /ref AST_WEBSOCKET_PROTOCOL_VERSION */
+	unsigned int version;
+	/*! \brief Callback called when a new session is attempted. Optional. */
+	ast_websocket_pre_callback session_attempted;
+	/* \brief Callback called when a new session is established. Mandatory. */
+	ast_websocket_callback session_established;
+};
 
 /*!
  * \brief Creates a \ref websocket_server
@@ -98,6 +142,15 @@
 AST_OPTIONAL_API(int, ast_websocket_uri_cb, (struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers), { return -1; });
 
 /*!
+ * \brief Allocate a websocket sub-protocol instance
+ *
+ * \retval An instance of \ref ast_websocket_protocol on success
+ * \retval NULL on error
+ * \since 13.5.0
+ */
+AST_OPTIONAL_API(struct ast_websocket_protocol *, ast_websocket_sub_protocol_alloc, (const char *name), {return NULL;});
+
+/*!
  * \brief Add a sub-protocol handler to the default /ws server
  *
  * \param name Name of the sub-protocol to register
@@ -109,10 +162,25 @@
 AST_OPTIONAL_API(int, ast_websocket_add_protocol, (const char *name, ast_websocket_callback callback), {return -1;});
 
 /*!
+ * \brief Add a sub-protocol handler to the default /ws server
+ *
+ * \param protocol The sub-protocol to register. Note that this must
+ * be allocated using /ref ast_websocket_sub_protocol_alloc.
+ *
+ * \note This method is reference stealing. It will steal the reference to \ref protocol
+ * on success.
+ *
+ * \retval 0 success
+ * \retval -1 if sub-protocol handler could not be registered
+ * \since 13.5.0
+ */
+AST_OPTIONAL_API(int, ast_websocket_add_protocol2, (struct ast_websocket_protocol *protocol), {return -1;});
+
+/*!
  * \brief Remove a sub-protocol handler from the default /ws server.
  *
  * \param name Name of the sub-protocol to unregister
- * \param callback Callback that was previously registered with the sub-protocol
+ * \param callback Session Established callback that was previously registered with the sub-protocol
  *
  * \retval 0 success
  * \retval -1 if sub-protocol was not found or if callback did not match
@@ -132,6 +200,22 @@
 AST_OPTIONAL_API(int, ast_websocket_server_add_protocol, (struct ast_websocket_server *server, const char *name, ast_websocket_callback callback), {return -1;});
 
 /*!
+ * \brief Add a sub-protocol handler to the given server.
+ *
+ * \param server The server to add the sub-protocol to.
+ * \param protocol The sub-protocol to register. Note that this must
+ * be allocated using /ref ast_websocket_sub_protocol_alloc.
+ *
+ * \note This method is reference stealing. It will steal the reference to \ref protocol
+ * on success.
+ *
+ * \retval 0 success
+ * \retval -1 if sub-protocol handler could not be registered
+ * \since 13.5.0
+ */
+AST_OPTIONAL_API(int, ast_websocket_server_add_protocol2, (struct ast_websocket_server *server, struct ast_websocket_protocol *protocol), {return -1;});
+
+/*!
  * \brief Remove a sub-protocol handler from the given server.
  *
  * \param name Name of the sub-protocol to unregister
diff --git a/res/res_http_websocket.c b/res/res_http_websocket.c
index 046c76e..40aedff 100644
--- a/res/res_http_websocket.c
+++ b/res/res_http_websocket.c
@@ -88,16 +88,10 @@
 	struct websocket_client *client;  /*!< Client object when connected as a client websocket */
 };
 
-/*! \brief Structure definition for protocols */
-struct websocket_protocol {
-	char *name;                      /*!< Name of the protocol */
-	ast_websocket_callback callback; /*!< Callback called when a new session is established */
-};
-
 /*! \brief Hashing function for protocols */
 static int protocol_hash_fn(const void *obj, const int flags)
 {
-	const struct websocket_protocol *protocol = obj;
+	const struct ast_websocket_protocol *protocol = obj;
 	const char *name = obj;
 
 	return ast_str_case_hash(flags & OBJ_KEY ? name : protocol->name);
@@ -106,7 +100,7 @@
 /*! \brief Comparison function for protocols */
 static int protocol_cmp_fn(void *obj, void *arg, int flags)
 {
-	const struct websocket_protocol *protocol1 = obj, *protocol2 = arg;
+	const struct ast_websocket_protocol *protocol1 = obj, *protocol2 = arg;
 	const char *protocol = arg;
 
 	return !strcasecmp(protocol1->name, flags & OBJ_KEY ? protocol : protocol2->name) ? CMP_MATCH | CMP_STOP : 0;
@@ -115,7 +109,7 @@
 /*! \brief Destructor function for protocols */
 static void protocol_destroy_fn(void *obj)
 {
-	struct websocket_protocol *protocol = obj;
+	struct ast_websocket_protocol *protocol = obj;
 	ast_free(protocol->name);
 }
 
@@ -182,54 +176,91 @@
 	ast_free(session->payload);
 }
 
+struct ast_websocket_protocol *AST_OPTIONAL_API_NAME(ast_websocket_sub_protocol_alloc)(const char *name)
+{
+	struct ast_websocket_protocol *protocol;
+
+	protocol = ao2_alloc(sizeof(*protocol), protocol_destroy_fn);
+	if (!protocol) {
+		return NULL;
+	}
+
+	protocol->name = ast_strdup(name);
+	if (!protocol->name) {
+		ao2_ref(protocol, -1);
+		return NULL;
+	}
+	protocol->version = AST_WEBSOCKET_PROTOCOL_VERSION;
+
+	return protocol;
+}
+
 int AST_OPTIONAL_API_NAME(ast_websocket_server_add_protocol)(struct ast_websocket_server *server, const char *name, ast_websocket_callback callback)
 {
-	struct websocket_protocol *protocol;
+	struct ast_websocket_protocol *protocol;
 
 	if (!server->protocols) {
+		return -1;
+	}
+
+	protocol = ast_websocket_sub_protocol_alloc(name);
+	if (!protocol) {
+		ao2_unlock(server->protocols);
+		return -1;
+	}
+	protocol->session_established = callback;
+
+	if (ast_websocket_server_add_protocol2(server, protocol)) {
+		ao2_ref(protocol, -1);
+		return -1;
+	}
+
+	return 0;
+}
+
+int AST_OPTIONAL_API_NAME(ast_websocket_server_add_protocol2)(struct ast_websocket_server *server, struct ast_websocket_protocol *protocol)
+{
+	struct ast_websocket_protocol *existing;
+
+	if (!server->protocols) {
+		return -1;
+	}
+
+	if (protocol->version != AST_WEBSOCKET_PROTOCOL_VERSION) {
+		ast_log(LOG_WARNING, "WebSocket could not register sub-protocol '%s': "
+			"expected version '%u', got version '%u'\n",
+			protocol->name, AST_WEBSOCKET_PROTOCOL_VERSION, protocol->version);
 		return -1;
 	}
 
 	ao2_lock(server->protocols);
 
 	/* Ensure a second protocol handler is not registered for the same protocol */
-	if ((protocol = ao2_find(server->protocols, name, OBJ_KEY | OBJ_NOLOCK))) {
-		ao2_ref(protocol, -1);
+	existing = ao2_find(server->protocols, protocol->name, OBJ_KEY | OBJ_NOLOCK);
+	if (existing) {
+		ao2_ref(existing, -1);
 		ao2_unlock(server->protocols);
 		return -1;
 	}
-
-	if (!(protocol = ao2_alloc(sizeof(*protocol), protocol_destroy_fn))) {
-		ao2_unlock(server->protocols);
-		return -1;
-	}
-
-	if (!(protocol->name = ast_strdup(name))) {
-		ao2_ref(protocol, -1);
-		ao2_unlock(server->protocols);
-		return -1;
-	}
-
-	protocol->callback = callback;
 
 	ao2_link_flags(server->protocols, protocol, OBJ_NOLOCK);
 	ao2_unlock(server->protocols);
-	ao2_ref(protocol, -1);
 
-	ast_verb(2, "WebSocket registered sub-protocol '%s'\n", name);
+	ast_verb(2, "WebSocket registered sub-protocol '%s'\n", protocol->name);
+	ao2_ref(protocol, -1);
 
 	return 0;
 }
 
 int AST_OPTIONAL_API_NAME(ast_websocket_server_remove_protocol)(struct ast_websocket_server *server, const char *name, ast_websocket_callback callback)
 {
-	struct websocket_protocol *protocol;
+	struct ast_websocket_protocol *protocol;
 
 	if (!(protocol = ao2_find(server->protocols, name, OBJ_KEY))) {
 		return -1;
 	}
 
-	if (protocol->callback != callback) {
+	if (protocol->session_established != callback) {
 		ao2_ref(protocol, -1);
 		return -1;
 	}
@@ -600,7 +631,7 @@
 /*!
  * \brief If the server has exactly one configured protocol, return it.
  */
-static struct websocket_protocol *one_protocol(
+static struct ast_websocket_protocol *one_protocol(
 	struct ast_websocket_server *server)
 {
 	SCOPED_AO2LOCK(lock, server->protocols);
@@ -643,7 +674,7 @@
 	struct ast_variable *v;
 	char *upgrade = NULL, *key = NULL, *key1 = NULL, *key2 = NULL, *protos = NULL, *requested_protocols = NULL, *protocol = NULL;
 	int version = 0, flags = 1;
-	struct websocket_protocol *protocol_handler = NULL;
+	struct ast_websocket_protocol *protocol_handler = NULL;
 	struct ast_websocket *session;
 	struct ast_websocket_server *server;
 
@@ -742,6 +773,14 @@
 		}
 		session->timeout =  AST_DEFAULT_WEBSOCKET_WRITE_TIMEOUT;
 
+		if (protocol_handler->session_attempted
+		    && protocol_handler->session_attempted(ser, get_vars, headers)) {
+			ast_debug(3, "WebSocket connection from '%s' rejected by protocol handler '%s'\n",
+				ast_sockaddr_stringify(&ser->remote_address), protocol_handler->name);
+			ao2_ref(protocol_handler, -1);
+			return 0;
+		}
+
 		fprintf(ser->f, "HTTP/1.1 101 Switching Protocols\r\n"
 			"Upgrade: %s\r\n"
 			"Connection: Upgrade\r\n"
@@ -797,7 +836,7 @@
 
 	/* Give up ownership of the socket and pass it to the protocol handler */
 	ast_tcptls_stream_set_exclusive_input(ser->stream_cookie, 0);
-	protocol_handler->callback(session, get_vars, headers);
+	protocol_handler->session_established(session, get_vars, headers);
 	ao2_ref(protocol_handler, -1);
 
 	/*
@@ -881,6 +920,22 @@
 	return res;
 }
 
+int AST_OPTIONAL_API_NAME(ast_websocket_add_protocol2)(struct ast_websocket_protocol *protocol)
+{
+	struct ast_websocket_server *ws_server = websocketuri.data;
+
+	if (!ws_server) {
+		return -1;
+	}
+
+	if (ast_websocket_server_add_protocol2(ws_server, protocol)) {
+		return -1;
+	}
+
+	ast_module_ref(ast_module_info->self);
+	return 0;
+}
+
 static int websocket_remove_protocol_internal(const char *name, ast_websocket_callback callback)
 {
 	struct ast_websocket_server *ws_server = websocketuri.data;

-- 
To view, visit https://gerrit.asterisk.org/496
To unsubscribe, visit https://gerrit.asterisk.org/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Ibe0bbb30c17eec6b578071bdbd197c911b620ab2
Gerrit-PatchSet: 1
Gerrit-Project: asterisk
Gerrit-Branch: master
Gerrit-Owner: Matt Jordan <mjordan at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at digium.com>
Gerrit-Reviewer: Matt Jordan <mjordan at digium.com>



More information about the asterisk-code-review mailing list