Browse code

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 authored on 29/04/2013 19:34:36
Showing 5 changed files
... ...
@@ -1,12 +1,11 @@
1
-
2 1
 WebSocket Module
3 2
 
4 3
 Peter Dunkley
5 4
 
6 5
    Crocodile RCS Ltd
7 6
 
8
-   Copyright � 2012-2013 Crocodile RCS Ltd
9
-     _________________________________________________________________
7
+   Copyright © 2012-2013 Crocodile RCS Ltd
8
+     __________________________________________________________________
10 9
 
11 10
    Table of Contents
12 11
 
... ...
@@ -32,10 +31,11 @@ Peter Dunkley
32 31
               4.4. keepalive_interval (integer)
33 32
               4.5. ping_application_data (string)
34 33
               4.6. sub_protocols (integer)
34
+              4.7. cors_mode (integer)
35 35
 
36 36
         5. Functions
37 37
 
38
-              5.1. ws_handle_handshake() 
38
+              5.1. ws_handle_handshake()
39 39
 
40 40
         6. MI Commands
41 41
 
... ...
@@ -48,7 +48,7 @@ Peter Dunkley
48 48
 
49 49
         7. Event routes
50 50
 
51
-              7.1. websocket:closed 
51
+              7.1. websocket:closed
52 52
 
53 53
    List of Examples
54 54
 
... ...
@@ -60,8 +60,9 @@ Peter Dunkley
60 60
    1.6. Set keepalive_interval parameter
61 61
    1.7. Set ping_application_data parameter
62 62
    1.8. Set sub_protocols parameter
63
-   1.9. ws_handle_handshake usage
64
-   1.10. event_route[websocket:closed] usage
63
+   1.9. Set cors_mode parameter
64
+   1.10. ws_handle_handshake usage
65
+   1.11. event_route[websocket:closed] usage
65 66
 
66 67
 Chapter 1. Admin Guide
67 68
 
... ...
@@ -87,10 +88,11 @@ Chapter 1. Admin Guide
87 88
         4.4. keepalive_interval (integer)
88 89
         4.5. ping_application_data (string)
89 90
         4.6. sub_protocols (integer)
91
+        4.7. cors_mode (integer)
90 92
 
91 93
    5. Functions
92 94
 
93
-        5.1. ws_handle_handshake() 
95
+        5.1. ws_handle_handshake()
94 96
 
95 97
    6. MI Commands
96 98
 
... ...
@@ -103,14 +105,14 @@ Chapter 1. Admin Guide
103 105
 
104 106
    7. Event routes
105 107
 
106
-        7.1. websocket:closed 
108
+        7.1. websocket:closed
107 109
 
108 110
 1. Overview
109 111
 
110
-   This  module  implements  a  WebSocket  (RFC 6455) server and provides
111
-   connection    establishment   (handshaking),   management   (including
112
-   connection  keep-alive),  and  framing  for the SIP and MSRP WebSocket
113
-   sub-protocols           (draft-ietf-sipcore-sip-websocket          and
112
+   This module implements a WebSocket (RFC 6455) server and provides
113
+   connection establishment (handshaking), management (including
114
+   connection keep-alive), and framing for the SIP and MSRP WebSocket
115
+   sub-protocols (draft-ietf-sipcore-sip-websocket and
114 116
    draft-pd-msrp-websocket).
115 117
 
116 118
    The module supports WebSockets (ws) and secure WebSockets (wss)
... ...
@@ -124,15 +126,15 @@ Chapter 1. Admin Guide
124 126
 2.1. Initiating a connection
125 127
 
126 128
    A WebSocket connection is initiated with an HTTP GET. The xhttp module
127
-   is   used   to   handle   this   GET   and  call  the  Section 5.1,  "
128
-   ws_handle_handshake() " exported function.
129
+   is used to handle this GET and call the Section 5.1, “
130
+   ws_handle_handshake() ” exported function.
129 131
 
130
-   event_route[xhttp:request]  should perform some validation of the HTTP
131
-   headers  before  calling  Section 5.1,  " ws_handle_handshake() ". The
132
+   event_route[xhttp:request] should perform some validation of the HTTP
133
+   headers before calling Section 5.1, “ ws_handle_handshake() ”. The
132 134
    event_route can also be used to make sure the HTTP GET has the correct
133
-   URI,  perform  HTTP  authentication  on  the WebSocket connection, and
134
-   check the Origin header (RFC 6454) to ensure a browser-based SIP UA or
135
-   MSRP client has been downloaded from the correct location.
135
+   URI, perform HTTP authentication on the WebSocket connection, and check
136
+   the Origin header (RFC 6454) to ensure a browser-based SIP UA or MSRP
137
+   client has been downloaded from the correct location.
136 138
 
137 139
    Example 1.1. event_route[xhttp:request]
138 140
 ...
... ...
@@ -198,22 +200,21 @@ event_route[xhttp:request] {
198 200
 
199 201
 2.2. SIP message routing
200 202
 
201
-   SIP  over  WebSockets  uses invalid URIs in routing headers (Contact:,
202
-   Record-Route:,  and  Via:)  because  a  JavaScript  stack running in a
203
-   browser  has  no  way  to  determine  the local address from which the
204
-   WebSocket  connection  is  made.  This  means that the routing headers
203
+   SIP over WebSockets uses invalid URIs in routing headers (Contact:,
204
+   Record-Route:, and Via:) because a JavaScript stack running in a
205
+   browser has no way to determine the local address from which the
206
+   WebSocket connection is made. This means that the routing headers
205 207
    cannot be used for request or response routing in the normal manner.
206 208
 
207 209
    draft-ietf-sipcore-sip-websocket states that SIP WebSocket Clients and
208
-   the  SIP  registrar should implement Outbound (RFC 5626) and Path (RFC
209
-   3327)  to  enable  requests  and  responses  to  be  correctly routed.
210
-   However,  Kamailio  does not currently support Outbound and it may not
211
-   be  possible  to  guarantee  all  SIP  WebSocket  clients will support
212
-   Outbound and Path.
213
-
214
-   The  nathelper module functions (nat_uac_test(), fix_nated_register(),
215
-   add_contact_alias(),  and  handle_ruri_alias())  and the Kamailio core
216
-   force_rport()  can  be used to ensure correct routing of SIP WebSocket
210
+   the SIP registrar should implement Outbound (RFC 5626) and Path (RFC
211
+   3327) to enable requests and responses to be correctly routed. However,
212
+   Kamailio does not currently support Outbound and it may not be possible
213
+   to guarantee all SIP WebSocket clients will support Outbound and Path.
214
+
215
+   The nathelper module functions (nat_uac_test(), fix_nated_register(),
216
+   add_contact_alias(), and handle_ruri_alias()) and the Kamailio core
217
+   force_rport() can be used to ensure correct routing of SIP WebSocket
217 218
    requests without using Outbound and Path.
218 219
 
219 220
    Example 1.2. WebSocket SIP Routing
... ...
@@ -279,11 +280,11 @@ onreply_route[WS_REPLY] {
279 280
 
280 281
 2.3. MSRP message routing
281 282
 
282
-   MSRP  over WebSocket clients create invalid local URIs for use in Path
283
-   headers  (From-Path:  and To-Path:) because a JavaScript stack running
284
-   in  a browser has no way to determine the local address from which the
285
-   WebSocket  connection  is made. This is OK because MSRP over WebSocket
286
-   clients   MUST   use  an  MSRP  relay  and  it  is  the  MSRP  relay's
283
+   MSRP over WebSocket clients create invalid local URIs for use in Path
284
+   headers (From-Path: and To-Path:) because a JavaScript stack running in
285
+   a browser has no way to determine the local address from which the
286
+   WebSocket connection is made. This is OK because MSRP over WebSocket
287
+   clients MUST use an MSRP relay and it is the MSRP relay's
287 288
    responsibility to select the correct connection to the client based on
288 289
    the MSRP URIs that it has created (and maintains a mapping for).
289 290
 
... ...
@@ -302,7 +303,7 @@ onreply_route[WS_REPLY] {
302 303
      * tm.
303 304
      * xhttp.
304 305
 
305
-   The  following  module  is  required to use the secure WebSocket (wss)
306
+   The following module is required to use the secure WebSocket (wss)
306 307
    scheme:
307 308
      * tls.
308 309
 
... ...
@@ -324,6 +325,7 @@ onreply_route[WS_REPLY] {
324 325
    4.4. keepalive_interval (integer)
325 326
    4.5. ping_application_data (string)
326 327
    4.6. sub_protocols (integer)
328
+   4.7. cors_mode (integer)
327 329
 
328 330
 4.1. keepalive_mechanism (integer)
329 331
 
... ...
@@ -331,10 +333,10 @@ onreply_route[WS_REPLY] {
331 333
 
332 334
 Note
333 335
 
334
-   If  nathelper  is  only  being  used  for  WebSocket  connections then
335
-   nathelper  NAT  pinging  is  not  required.  If  nathelper is used for
336
-   WebSocket   connections   and   TCP/TLS   aliasing/NAT-traversal  then
337
-   WebSocket keep-alives are not required.
336
+   If nathelper is only being used for WebSocket connections then
337
+   nathelper NAT pinging is not required. If nathelper is used for
338
+   WebSocket connections and TCP/TLS aliasing/NAT-traversal then WebSocket
339
+   keep-alives are not required.
338 340
 
339 341
      * 0 - no WebSocket keep-alives
340 342
      * 1 - Ping WebSocket keep-alives
... ...
@@ -349,7 +351,7 @@ modparam("websocket", "keepalive_mechanism", 0)
349 351
 
350 352
 4.2. keepalive_timeout (integer)
351 353
 
352
-   The  time  (in  seconds)  after  which  to  send  a keep-alive on idle
354
+   The time (in seconds) after which to send a keep-alive on idle
353 355
    WebSocket connections.
354 356
 
355 357
    Default value is 180.
... ...
@@ -361,7 +363,7 @@ modparam("websocket", "keepalive_timeout", 30)
361 363
 
362 364
 4.3. keepalive_processes (integer)
363 365
 
364
-   The  number  of  processes  to  start  to perform WebSocket connection
366
+   The number of processes to start to perform WebSocket connection
365 367
    keep-alives.
366 368
 
367 369
    Default value is 1.
... ...
@@ -401,33 +403,52 @@ modparam("websocket", "ping_application_data", "WebSockets rock")
401 403
      * 2 - msrp (draft-pd-msrp-websocket) - msrp.so must be loaded before
402 404
        websocket.so
403 405
 
404
-   Default  value  is  1  when  msrp.so  is  not loaded 3 when msrp.so is
405
-   loaded.
406
+   Default value is 1 when msrp.so is not loaded 3 when msrp.so is loaded.
406 407
 
407 408
    Example 1.8. Set sub_protocols parameter
408 409
 ...
409 410
 modparam("websocket", "sub_protocols", 2)
410 411
 ...
411 412
 
413
+4.7. cors_mode (integer)
414
+
415
+   This parameter lets you set the "Cross-origin resource sharing"
416
+   behaviour of the WebSocket server.
417
+     * 0 - Do not add an "Access-Control-Allow-Origin:" header to the
418
+       response accepting the WebSocket handshake.
419
+     * 1 - Add a "Access-Control-Allow-Origin: *" header to the response
420
+       accepting the WebSocket handshake.
421
+     * 2 - Add a "Access-Control-Allow-Origin:" header containing the same
422
+       body as the "Origin:" header from the request to the response
423
+       accepting the WebSocket handshake. If there is no "Origin:" header
424
+       in the request no header will be added to the response.
425
+
426
+   Default value is 0.
427
+
428
+   Example 1.9. Set cors_mode parameter
429
+...
430
+modparam("websocket", "cors_mode", 2)
431
+...
432
+
412 433
 5. Functions
413 434
 
414
-   5.1. ws_handle_handshake() 
435
+   5.1. ws_handle_handshake()
415 436
 
416 437
 5.1.  ws_handle_handshake()
417 438
 
418
-   This  function checks an HTTP GET request for the required headers and
419
-   values,  and  (if  successful)  upgrades  the  connection from HTTP to
439
+   This function checks an HTTP GET request for the required headers and
440
+   values, and (if successful) upgrades the connection from HTTP to
420 441
    WebSocket.
421 442
 
422
-   This  function  can  be  used  from  ANY_ROUTE  (but will only work in
443
+   This function can be used from ANY_ROUTE (but will only work in
423 444
    event_route[xhttp:request]).
424 445
 
425 446
 Note
426 447
 
427
-   This  function  returns  0,  stopping  all  further  processing of the
448
+   This function returns 0, stopping all further processing of the
428 449
    request, when there is a problem.
429 450
 
430
-   Example 1.9. ws_handle_handshake usage
451
+   Example 1.10. ws_handle_handshake usage
431 452
 ...
432 453
 ws_handle_handshake();
433 454
 ...
... ...
@@ -448,7 +469,7 @@ ws_handle_handshake();
448 469
    Name: ws.dump
449 470
 
450 471
    Parameters:
451
-     * order (optional) - "id_hash", "used_desc", or "used_asc".
472
+     * order (optional) - “id_hash”, “used_desc”, or “used_asc”.
452 473
 
453 474
 Note
454 475
 
... ...
@@ -531,15 +552,15 @@ Note
531 552
 
532 553
 7. Event routes
533 554
 
534
-   7.1. websocket:closed 
555
+   7.1. websocket:closed
535 556
 
536 557
 7.1.  websocket:closed
537 558
 
538
-   When  defined,  the  module calls event_route[websocket:closed] when a
539
-   connection  closes. The connection may be identified using the the $si
559
+   When defined, the module calls event_route[websocket:closed] when a
560
+   connection closes. The connection may be identified using the the $si
540 561
    and $sp pseudo-variables.
541 562
 
542
-   Example 1.10. event_route[websocket:closed] usage
563
+   Example 1.11. event_route[websocket:closed] usage
543 564
 ...
544 565
 event_route[websocket:closed] {
545 566
         xlog("L_INFO", "WebSocket connection from $si:$sp has closed\n");
... ...
@@ -397,6 +397,42 @@ modparam("websocket", "sub_protocols", 2)
397 397
 		</example>
398 398
 	</section>
399 399
 
400
+	<section id="websocket.p.cors_mode">
401
+		<title><varname>cors_mode</varname> (integer)</title>
402
+		<para>This parameter lets you set the &quot;Cross-origin
403
+		resource sharing&quot; behaviour of the WebSocket server.</para>
404
+		<itemizedlist>
405
+		<listitem><para>
406
+		<emphasis>0</emphasis> - Do not add an
407
+		&quot;Access-Control-Allow-Origin:&quot; header to the response
408
+		accepting the WebSocket handshake. 
409
+		</para></listitem>
410
+		<listitem><para>
411
+		<emphasis>1</emphasis> - Add a
412
+		&quot;Access-Control-Allow-Origin: *&quot; header to the
413
+		response accepting the WebSocket handshake.
414
+		</para></listitem>
415
+		<listitem><para>
416
+		<emphasis>2</emphasis> - Add a
417
+		&quot;Access-Control-Allow-Origin:&quot; header containing the
418
+		same body as the &quot;Origin:&quot; header from the request to
419
+		the response accepting the WebSocket handshake. If there is no
420
+		&quot;Origin:&quot; header in the request no header will be
421
+		added to the response.
422
+		</para></listitem>
423
+		</itemizedlist>
424
+		<para><emphasis>Default value is 0.</emphasis></para>
425
+		<example>
426
+		<title>Set <varname>cors_mode</varname>
427
+		parameter</title>
428
+		<programlisting format="linespecific">
429
+...
430
+modparam("websocket", "cors_mode", 2)
431
+...
432
+</programlisting>
433
+		</example>
434
+	</section>
435
+
400 436
 	</section>
401 437
 
402 438
 	<section>
... ...
@@ -44,6 +44,7 @@
44 44
 #define WS_VERSION		(13)
45 45
 
46 46
 int ws_sub_protocols = DEFAULT_SUB_PROTOCOLS;
47
+int ws_cors_mode = CORS_MODE_NONE;
47 48
 
48 49
 stat_var *ws_failed_handshakes;
49 50
 stat_var *ws_successful_handshakes;
... ...
@@ -63,12 +64,16 @@ static str str_hdr_sec_websocket_accept = str_init("Sec-WebSocket-Accept");
63 64
 static str str_hdr_sec_websocket_key = str_init("Sec-WebSocket-Key");
64 65
 static str str_hdr_sec_websocket_protocol = str_init("Sec-WebSocket-Protocol");
65 66
 static str str_hdr_sec_websocket_version = str_init("Sec-WebSocket-Version");
67
+static str str_hdr_origin = str_init("Origin");
68
+static str str_hdr_access_control_allow_origin
69
+				= str_init("Access-Control-Allow-Origin");
66 70
 #define CONNECTION		(1<<0)
67 71
 #define UPGRADE			(1<<1)
68 72
 #define SEC_WEBSOCKET_ACCEPT	(1<<2)
69 73
 #define SEC_WEBSOCKET_KEY	(1<<3)
70 74
 #define SEC_WEBSOCKET_PROTOCOL	(1<<4)
71 75
 #define SEC_WEBSOCKET_VERSION	(1<<5)
76
+#define ORIGIN			(1<<6)
72 77
 
73 78
 #define REQUIRED_HEADERS	(CONNECTION | UPGRADE | SEC_WEBSOCKET_KEY\
74 79
 					| SEC_WEBSOCKET_PROTOCOL\
... ...
@@ -114,7 +119,7 @@ static int ws_send_reply(sip_msg_t *msg, int code, str *reason, str *hdrs)
114 119
 
115 120
 int ws_handle_handshake(struct sip_msg *msg)
116 121
 {
117
-	str key = {0, 0}, headers = {0, 0}, reply_key = {0, 0};
122
+	str key = {0, 0}, headers = {0, 0}, reply_key = {0, 0}, origin = {0, 0};
118 123
 	unsigned char sha1[SHA_DIGEST_LENGTH];
119 124
 	unsigned int hdr_flags = 0, sub_protocol = 0;
120 125
 	int version;
... ...
@@ -274,6 +279,27 @@ int ws_handle_handshake(struct sip_msg *msg)
274 279
 				hdr->body.len, hdr->body.s);
275 280
 			hdr_flags |= SEC_WEBSOCKET_VERSION;
276 281
 		}
282
+		/* Decode Origin */
283
+		else if (cmp_hdrname_strzn(&hdr->name,
284
+				str_hdr_origin.s,
285
+				str_hdr_origin.len) == 0)
286
+		{
287
+			if (hdr_flags & ORIGIN)
288
+			{
289
+				LM_WARN("%.*s found multiple times\n",
290
+					hdr->name.len, hdr->name.s);
291
+				ws_send_reply(msg, 400,
292
+						&str_status_bad_request,
293
+						NULL);
294
+				return 0;
295
+			}
296
+
297
+			LM_DBG("found %.*s: %.*s\n",
298
+				hdr->name.len, hdr->name.s,
299
+				hdr->body.len, hdr->body.s);
300
+			origin = hdr->body;
301
+			hdr_flags |= ORIGIN;
302
+		}
277 303
 
278 304
 		hdr = hdr->next;
279 305
 	}
... ...
@@ -348,6 +374,21 @@ int ws_handle_handshake(struct sip_msg *msg)
348 374
 	headers.s = headers_buf;
349 375
 	headers.len = 0;
350 376
 
377
+	if (ws_cors_mode == CORS_MODE_ANY)
378
+		headers.len += snprintf(headers.s + headers.len,
379
+					HDR_BUF_LEN - headers.len,
380
+					"%.*s: *\r\n",
381
+					str_hdr_access_control_allow_origin.len,
382
+					str_hdr_access_control_allow_origin.s);
383
+	else if (ws_cors_mode == CORS_MODE_ORIGIN && origin.len > 0)
384
+		headers.len += snprintf(headers.s + headers.len,
385
+					HDR_BUF_LEN - headers.len,
386
+					"%.*s: %.*s\r\n",
387
+					str_hdr_access_control_allow_origin.len,
388
+					str_hdr_access_control_allow_origin.s,
389
+					origin.len,
390
+					origin.s);
391
+
351 392
 	if (sub_protocol & SUB_PROTOCOL_SIP)
352 393
 		headers.len += snprintf(headers.s + headers.len,
353 394
 					HDR_BUF_LEN - headers.len,
... ...
@@ -32,6 +32,11 @@
32 32
 #define SUB_PROTOCOL_ALL	(SUB_PROTOCOL_SIP | SUB_PROTOCOL_MSRP)
33 33
 extern int ws_sub_protocols;
34 34
 
35
+#define CORS_MODE_NONE		0
36
+#define CORS_MODE_ANY		1
37
+#define CORS_MODE_ORIGIN	2
38
+extern int ws_cors_mode;
39
+
35 40
 extern stat_var *ws_failed_handshakes;
36 41
 extern stat_var *ws_successful_handshakes;
37 42
 extern stat_var *ws_sip_successful_handshakes;
... ...
@@ -73,10 +73,11 @@ static param_export_t params[]=
73 73
 	/* ws_frame.c */
74 74
 	{ "keepalive_mechanism",	INT_PARAM, &ws_keepalive_mechanism },
75 75
 	{ "keepalive_timeout",		INT_PARAM, &ws_keepalive_timeout },
76
-	{ "ping_application_data",	STR_PARAM, &ws_ping_application_data.s},
76
+	{ "ping_application_data",	STR_PARAM, &ws_ping_application_data.s },
77 77
 
78 78
 	/* ws_handshake.c */
79
-	{ "sub_protocols",		INT_PARAM, &ws_sub_protocols},
79
+	{ "sub_protocols",		INT_PARAM, &ws_sub_protocols },
80
+	{ "cors_mode",			INT_PARAM, &ws_cors_mode },
80 81
 
81 82
 	/* ws_mod.c */
82 83
 	{ "keepalive_interval",		INT_PARAM, &ws_keepalive_interval },
... ...
@@ -244,6 +245,12 @@ static int mod_init(void)
244 245
 		goto error;
245 246
 	}
246 247
 
248
+	if (ws_cors_mode < 0 || ws_cors_mode > 2)
249
+	{
250
+		LM_ERR("bad value for cors_mode\n");
251
+		goto error;
252
+	}
253
+
247 254
 	if (cfg_declare("websocket", ws_cfg_def, &default_ws_cfg,
248 255
 			cfg_sizeof(websocket), &ws_cfg)) {
249 256
 		LM_ERR("declaring configuration\n");