2
0
Эх сурвалжийг харах

websocket: added "cors_mode" parameter to enable "Cross-origin resource sharing" on WebSocket handshakes
- I don't know of any WebSocket clients that require this (yet). But having it
in there won't break anything.

Peter Dunkley 12 жил өмнө
parent
commit
3d5c66997e

+ 79 - 58
modules/websocket/README

@@ -1,12 +1,11 @@
-
 WebSocket Module
 
 Peter Dunkley
 
    Crocodile RCS Ltd
 
-   Copyright © 2012-2013 Crocodile RCS Ltd
-     _________________________________________________________________
+   Copyright © 2012-2013 Crocodile RCS Ltd
+     __________________________________________________________________
 
    Table of Contents
 
@@ -32,10 +31,11 @@ Peter Dunkley
               4.4. keepalive_interval (integer)
               4.5. ping_application_data (string)
               4.6. sub_protocols (integer)
+              4.7. cors_mode (integer)
 
         5. Functions
 
-              5.1. ws_handle_handshake() 
+              5.1. ws_handle_handshake()
 
         6. MI Commands
 
@@ -48,7 +48,7 @@ Peter Dunkley
 
         7. Event routes
 
-              7.1. websocket:closed 
+              7.1. websocket:closed
 
    List of Examples
 
@@ -60,8 +60,9 @@ Peter Dunkley
    1.6. Set keepalive_interval parameter
    1.7. Set ping_application_data parameter
    1.8. Set sub_protocols parameter
-   1.9. ws_handle_handshake usage
-   1.10. event_route[websocket:closed] usage
+   1.9. Set cors_mode parameter
+   1.10. ws_handle_handshake usage
+   1.11. event_route[websocket:closed] usage
 
 Chapter 1. Admin Guide
 
@@ -87,10 +88,11 @@ Chapter 1. Admin Guide
         4.4. keepalive_interval (integer)
         4.5. ping_application_data (string)
         4.6. sub_protocols (integer)
+        4.7. cors_mode (integer)
 
    5. Functions
 
-        5.1. ws_handle_handshake() 
+        5.1. ws_handle_handshake()
 
    6. MI Commands
 
@@ -103,14 +105,14 @@ Chapter 1. Admin Guide
 
    7. Event routes
 
-        7.1. websocket:closed 
+        7.1. websocket:closed
 
 1. Overview
 
-   This  module  implements  a  WebSocket  (RFC 6455) server and provides
-   connection    establishment   (handshaking),   management   (including
-   connection  keep-alive),  and  framing  for the SIP and MSRP WebSocket
-   sub-protocols           (draft-ietf-sipcore-sip-websocket          and
+   This module implements a WebSocket (RFC 6455) server and provides
+   connection establishment (handshaking), management (including
+   connection keep-alive), and framing for the SIP and MSRP WebSocket
+   sub-protocols (draft-ietf-sipcore-sip-websocket and
    draft-pd-msrp-websocket).
 
    The module supports WebSockets (ws) and secure WebSockets (wss)
@@ -124,15 +126,15 @@ Chapter 1. Admin Guide
 2.1. Initiating a connection
 
    A WebSocket connection is initiated with an HTTP GET. The xhttp module
-   is   used   to   handle   this   GET   and  call  the  Section 5.1,  "
-   ws_handle_handshake() " exported function.
+   is used to handle this GET and call the Section 5.1, “
+   ws_handle_handshake() � exported function.
 
-   event_route[xhttp:request]  should perform some validation of the HTTP
-   headers  before  calling  Section 5.1,  " ws_handle_handshake() ". The
+   event_route[xhttp:request] should perform some validation of the HTTP
+   headers before calling Section 5.1, “ ws_handle_handshake() �. The
    event_route can also be used to make sure the HTTP GET has the correct
-   URI,  perform  HTTP  authentication  on  the WebSocket connection, and
-   check the Origin header (RFC 6454) to ensure a browser-based SIP UA or
-   MSRP client has been downloaded from the correct location.
+   URI, perform HTTP authentication on the WebSocket connection, and check
+   the Origin header (RFC 6454) to ensure a browser-based SIP UA or MSRP
+   client has been downloaded from the correct location.
 
    Example 1.1. event_route[xhttp:request]
 ...
@@ -198,22 +200,21 @@ event_route[xhttp:request] {
 
 2.2. SIP message routing
 
-   SIP  over  WebSockets  uses invalid URIs in routing headers (Contact:,
-   Record-Route:,  and  Via:)  because  a  JavaScript  stack running in a
-   browser  has  no  way  to  determine  the local address from which the
-   WebSocket  connection  is  made.  This  means that the routing headers
+   SIP over WebSockets uses invalid URIs in routing headers (Contact:,
+   Record-Route:, and Via:) because a JavaScript stack running in a
+   browser has no way to determine the local address from which the
+   WebSocket connection is made. This means that the routing headers
    cannot be used for request or response routing in the normal manner.
 
    draft-ietf-sipcore-sip-websocket states that SIP WebSocket Clients and
-   the  SIP  registrar should implement Outbound (RFC 5626) and Path (RFC
-   3327)  to  enable  requests  and  responses  to  be  correctly routed.
-   However,  Kamailio  does not currently support Outbound and it may not
-   be  possible  to  guarantee  all  SIP  WebSocket  clients will support
-   Outbound and Path.
-
-   The  nathelper module functions (nat_uac_test(), fix_nated_register(),
-   add_contact_alias(),  and  handle_ruri_alias())  and the Kamailio core
-   force_rport()  can  be used to ensure correct routing of SIP WebSocket
+   the SIP registrar should implement Outbound (RFC 5626) and Path (RFC
+   3327) to enable requests and responses to be correctly routed. However,
+   Kamailio does not currently support Outbound and it may not be possible
+   to guarantee all SIP WebSocket clients will support Outbound and Path.
+
+   The nathelper module functions (nat_uac_test(), fix_nated_register(),
+   add_contact_alias(), and handle_ruri_alias()) and the Kamailio core
+   force_rport() can be used to ensure correct routing of SIP WebSocket
    requests without using Outbound and Path.
 
    Example 1.2. WebSocket SIP Routing
@@ -279,11 +280,11 @@ onreply_route[WS_REPLY] {
 
 2.3. MSRP message routing
 
-   MSRP  over WebSocket clients create invalid local URIs for use in Path
-   headers  (From-Path:  and To-Path:) because a JavaScript stack running
-   in  a browser has no way to determine the local address from which the
-   WebSocket  connection  is made. This is OK because MSRP over WebSocket
-   clients   MUST   use  an  MSRP  relay  and  it  is  the  MSRP  relay's
+   MSRP over WebSocket clients create invalid local URIs for use in Path
+   headers (From-Path: and To-Path:) because a JavaScript stack running in
+   a browser has no way to determine the local address from which the
+   WebSocket connection is made. This is OK because MSRP over WebSocket
+   clients MUST use an MSRP relay and it is the MSRP relay's
    responsibility to select the correct connection to the client based on
    the MSRP URIs that it has created (and maintains a mapping for).
 
@@ -302,7 +303,7 @@ onreply_route[WS_REPLY] {
      * tm.
      * xhttp.
 
-   The  following  module  is  required to use the secure WebSocket (wss)
+   The following module is required to use the secure WebSocket (wss)
    scheme:
      * tls.
 
@@ -324,6 +325,7 @@ onreply_route[WS_REPLY] {
    4.4. keepalive_interval (integer)
    4.5. ping_application_data (string)
    4.6. sub_protocols (integer)
+   4.7. cors_mode (integer)
 
 4.1. keepalive_mechanism (integer)
 
@@ -331,10 +333,10 @@ onreply_route[WS_REPLY] {
 
 Note
 
-   If  nathelper  is  only  being  used  for  WebSocket  connections then
-   nathelper  NAT  pinging  is  not  required.  If  nathelper is used for
-   WebSocket   connections   and   TCP/TLS   aliasing/NAT-traversal  then
-   WebSocket keep-alives are not required.
+   If nathelper is only being used for WebSocket connections then
+   nathelper NAT pinging is not required. If nathelper is used for
+   WebSocket connections and TCP/TLS aliasing/NAT-traversal then WebSocket
+   keep-alives are not required.
 
      * 0 - no WebSocket keep-alives
      * 1 - Ping WebSocket keep-alives
@@ -349,7 +351,7 @@ modparam("websocket", "keepalive_mechanism", 0)
 
 4.2. keepalive_timeout (integer)
 
-   The  time  (in  seconds)  after  which  to  send  a keep-alive on idle
+   The time (in seconds) after which to send a keep-alive on idle
    WebSocket connections.
 
    Default value is 180.
@@ -361,7 +363,7 @@ modparam("websocket", "keepalive_timeout", 30)
 
 4.3. keepalive_processes (integer)
 
-   The  number  of  processes  to  start  to perform WebSocket connection
+   The number of processes to start to perform WebSocket connection
    keep-alives.
 
    Default value is 1.
@@ -401,33 +403,52 @@ modparam("websocket", "ping_application_data", "WebSockets rock")
      * 2 - msrp (draft-pd-msrp-websocket) - msrp.so must be loaded before
        websocket.so
 
-   Default  value  is  1  when  msrp.so  is  not loaded 3 when msrp.so is
-   loaded.
+   Default value is 1 when msrp.so is not loaded 3 when msrp.so is loaded.
 
    Example 1.8. Set sub_protocols parameter
 ...
 modparam("websocket", "sub_protocols", 2)
 ...
 
+4.7. cors_mode (integer)
+
+   This parameter lets you set the "Cross-origin resource sharing"
+   behaviour of the WebSocket server.
+     * 0 - Do not add an "Access-Control-Allow-Origin:" header to the
+       response accepting the WebSocket handshake.
+     * 1 - Add a "Access-Control-Allow-Origin: *" header to the response
+       accepting the WebSocket handshake.
+     * 2 - Add a "Access-Control-Allow-Origin:" header containing the same
+       body as the "Origin:" header from the request to the response
+       accepting the WebSocket handshake. If there is no "Origin:" header
+       in the request no header will be added to the response.
+
+   Default value is 0.
+
+   Example 1.9. Set cors_mode parameter
+...
+modparam("websocket", "cors_mode", 2)
+...
+
 5. Functions
 
-   5.1. ws_handle_handshake() 
+   5.1. ws_handle_handshake()
 
 5.1.  ws_handle_handshake()
 
-   This  function checks an HTTP GET request for the required headers and
-   values,  and  (if  successful)  upgrades  the  connection from HTTP to
+   This function checks an HTTP GET request for the required headers and
+   values, and (if successful) upgrades the connection from HTTP to
    WebSocket.
 
-   This  function  can  be  used  from  ANY_ROUTE  (but will only work in
+   This function can be used from ANY_ROUTE (but will only work in
    event_route[xhttp:request]).
 
 Note
 
-   This  function  returns  0,  stopping  all  further  processing of the
+   This function returns 0, stopping all further processing of the
    request, when there is a problem.
 
-   Example 1.9. ws_handle_handshake usage
+   Example 1.10. ws_handle_handshake usage
 ...
 ws_handle_handshake();
 ...
@@ -448,7 +469,7 @@ ws_handle_handshake();
    Name: ws.dump
 
    Parameters:
-     * order (optional) - "id_hash", "used_desc", or "used_asc".
+     * order (optional) - “id_hash�, “used_desc�, or “used_asc�.
 
 Note
 
@@ -531,15 +552,15 @@ Note
 
 7. Event routes
 
-   7.1. websocket:closed 
+   7.1. websocket:closed
 
 7.1.  websocket:closed
 
-   When  defined,  the  module calls event_route[websocket:closed] when a
-   connection  closes. The connection may be identified using the the $si
+   When defined, the module calls event_route[websocket:closed] when a
+   connection closes. The connection may be identified using the the $si
    and $sp pseudo-variables.
 
-   Example 1.10. event_route[websocket:closed] usage
+   Example 1.11. event_route[websocket:closed] usage
 ...
 event_route[websocket:closed] {
         xlog("L_INFO", "WebSocket connection from $si:$sp has closed\n");

+ 36 - 0
modules/websocket/doc/websocket_admin.xml

@@ -397,6 +397,42 @@ modparam("websocket", "sub_protocols", 2)
 		</example>
 	</section>
 
+	<section id="websocket.p.cors_mode">
+		<title><varname>cors_mode</varname> (integer)</title>
+		<para>This parameter lets you set the &quot;Cross-origin
+		resource sharing&quot; behaviour of the WebSocket server.</para>
+		<itemizedlist>
+		<listitem><para>
+		<emphasis>0</emphasis> - Do not add an
+		&quot;Access-Control-Allow-Origin:&quot; header to the response
+		accepting the WebSocket handshake. 
+		</para></listitem>
+		<listitem><para>
+		<emphasis>1</emphasis> - Add a
+		&quot;Access-Control-Allow-Origin: *&quot; header to the
+		response accepting the WebSocket handshake.
+		</para></listitem>
+		<listitem><para>
+		<emphasis>2</emphasis> - Add a
+		&quot;Access-Control-Allow-Origin:&quot; header containing the
+		same body as the &quot;Origin:&quot; header from the request to
+		the response accepting the WebSocket handshake. If there is no
+		&quot;Origin:&quot; header in the request no header will be
+		added to the response.
+		</para></listitem>
+		</itemizedlist>
+		<para><emphasis>Default value is 0.</emphasis></para>
+		<example>
+		<title>Set <varname>cors_mode</varname>
+		parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("websocket", "cors_mode", 2)
+...
+</programlisting>
+		</example>
+	</section>
+
 	</section>
 
 	<section>

+ 42 - 1
modules/websocket/ws_handshake.c

@@ -44,6 +44,7 @@
 #define WS_VERSION		(13)
 
 int ws_sub_protocols = DEFAULT_SUB_PROTOCOLS;
+int ws_cors_mode = CORS_MODE_NONE;
 
 stat_var *ws_failed_handshakes;
 stat_var *ws_successful_handshakes;
@@ -63,12 +64,16 @@ static str str_hdr_sec_websocket_accept = str_init("Sec-WebSocket-Accept");
 static str str_hdr_sec_websocket_key = str_init("Sec-WebSocket-Key");
 static str str_hdr_sec_websocket_protocol = str_init("Sec-WebSocket-Protocol");
 static str str_hdr_sec_websocket_version = str_init("Sec-WebSocket-Version");
+static str str_hdr_origin = str_init("Origin");
+static str str_hdr_access_control_allow_origin
+				= str_init("Access-Control-Allow-Origin");
 #define CONNECTION		(1<<0)
 #define UPGRADE			(1<<1)
 #define SEC_WEBSOCKET_ACCEPT	(1<<2)
 #define SEC_WEBSOCKET_KEY	(1<<3)
 #define SEC_WEBSOCKET_PROTOCOL	(1<<4)
 #define SEC_WEBSOCKET_VERSION	(1<<5)
+#define ORIGIN			(1<<6)
 
 #define REQUIRED_HEADERS	(CONNECTION | UPGRADE | SEC_WEBSOCKET_KEY\
 					| SEC_WEBSOCKET_PROTOCOL\
@@ -114,7 +119,7 @@ static int ws_send_reply(sip_msg_t *msg, int code, str *reason, str *hdrs)
 
 int ws_handle_handshake(struct sip_msg *msg)
 {
-	str key = {0, 0}, headers = {0, 0}, reply_key = {0, 0};
+	str key = {0, 0}, headers = {0, 0}, reply_key = {0, 0}, origin = {0, 0};
 	unsigned char sha1[SHA_DIGEST_LENGTH];
 	unsigned int hdr_flags = 0, sub_protocol = 0;
 	int version;
@@ -274,6 +279,27 @@ int ws_handle_handshake(struct sip_msg *msg)
 				hdr->body.len, hdr->body.s);
 			hdr_flags |= SEC_WEBSOCKET_VERSION;
 		}
+		/* Decode Origin */
+		else if (cmp_hdrname_strzn(&hdr->name,
+				str_hdr_origin.s,
+				str_hdr_origin.len) == 0)
+		{
+			if (hdr_flags & ORIGIN)
+			{
+				LM_WARN("%.*s found multiple times\n",
+					hdr->name.len, hdr->name.s);
+				ws_send_reply(msg, 400,
+						&str_status_bad_request,
+						NULL);
+				return 0;
+			}
+
+			LM_DBG("found %.*s: %.*s\n",
+				hdr->name.len, hdr->name.s,
+				hdr->body.len, hdr->body.s);
+			origin = hdr->body;
+			hdr_flags |= ORIGIN;
+		}
 
 		hdr = hdr->next;
 	}
@@ -348,6 +374,21 @@ int ws_handle_handshake(struct sip_msg *msg)
 	headers.s = headers_buf;
 	headers.len = 0;
 
+	if (ws_cors_mode == CORS_MODE_ANY)
+		headers.len += snprintf(headers.s + headers.len,
+					HDR_BUF_LEN - headers.len,
+					"%.*s: *\r\n",
+					str_hdr_access_control_allow_origin.len,
+					str_hdr_access_control_allow_origin.s);
+	else if (ws_cors_mode == CORS_MODE_ORIGIN && origin.len > 0)
+		headers.len += snprintf(headers.s + headers.len,
+					HDR_BUF_LEN - headers.len,
+					"%.*s: %.*s\r\n",
+					str_hdr_access_control_allow_origin.len,
+					str_hdr_access_control_allow_origin.s,
+					origin.len,
+					origin.s);
+
 	if (sub_protocol & SUB_PROTOCOL_SIP)
 		headers.len += snprintf(headers.s + headers.len,
 					HDR_BUF_LEN - headers.len,

+ 5 - 0
modules/websocket/ws_handshake.h

@@ -32,6 +32,11 @@
 #define SUB_PROTOCOL_ALL	(SUB_PROTOCOL_SIP | SUB_PROTOCOL_MSRP)
 extern int ws_sub_protocols;
 
+#define CORS_MODE_NONE		0
+#define CORS_MODE_ANY		1
+#define CORS_MODE_ORIGIN	2
+extern int ws_cors_mode;
+
 extern stat_var *ws_failed_handshakes;
 extern stat_var *ws_successful_handshakes;
 extern stat_var *ws_sip_successful_handshakes;

+ 9 - 2
modules/websocket/ws_mod.c

@@ -73,10 +73,11 @@ static param_export_t params[]=
 	/* ws_frame.c */
 	{ "keepalive_mechanism",	INT_PARAM, &ws_keepalive_mechanism },
 	{ "keepalive_timeout",		INT_PARAM, &ws_keepalive_timeout },
-	{ "ping_application_data",	STR_PARAM, &ws_ping_application_data.s},
+	{ "ping_application_data",	STR_PARAM, &ws_ping_application_data.s },
 
 	/* ws_handshake.c */
-	{ "sub_protocols",		INT_PARAM, &ws_sub_protocols},
+	{ "sub_protocols",		INT_PARAM, &ws_sub_protocols },
+	{ "cors_mode",			INT_PARAM, &ws_cors_mode },
 
 	/* ws_mod.c */
 	{ "keepalive_interval",		INT_PARAM, &ws_keepalive_interval },
@@ -244,6 +245,12 @@ static int mod_init(void)
 		goto error;
 	}
 
+	if (ws_cors_mode < 0 || ws_cors_mode > 2)
+	{
+		LM_ERR("bad value for cors_mode\n");
+		goto error;
+	}
+
 	if (cfg_declare("websocket", ws_cfg_def, &default_ws_cfg,
 			cfg_sizeof(websocket), &ws_cfg)) {
 		LM_ERR("declaring configuration\n");