2 * mod_wstunnel originally based off https://github.com/nori0428/mod_websocket
3 * Portions of this module Copyright(c) 2017, Glenn Strauss, All rights reserved
4 * Portions of this module Copyright(c) 2010, Norio Kobota, All rights reserved.
8 * Copyright(c) 2010, Norio Kobota, All rights reserved.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions are met:
13 * - Redistributions of source code must retain the above copyright notice,
14 * this list of conditions and the following disclaimer.
15 * - Redistributions in binary form must reproduce the above copyright notice,
16 * this list of conditions and the following disclaimer in the documentation
17 * and/or other materials provided with the distribution.
18 * - Neither the name of the 'incremental' nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
32 * THE POSSIBILITY OF SUCH DAMAGE.
37 * mod_wstunnel has been largely rewritten from Norio Kobota mod_websocket.
39 * highlighted differences from Norio Kobota mod_websocket
40 * - re-coded to use lighttpd 1.4.46 buffer, chunkqueue, and gw_backend APIs
41 * - websocket.server "ext" value is no longer regex;
42 * operates similar to mod_proxy for either path prefix or extension match
43 * - validation of "origins" value is no longer regex; operates as suffix match
44 * (admin could use lighttpd.conf regex on "Origin" or "Sec-WebSocket-Origin"
45 * and reject non-matches with mod_access if such regex validation required)
46 * - websocket transparent proxy mode removed; functionality is now in mod_proxy
47 * Backend server which responds to Connection: upgrade and Upgrade: websocket
48 * should check "Origin" and/or "Sec-WebSocket-Origin". lighttpd.conf could
49 * additionally be configured to check
50 * $REQUEST_HEADER["Sec-WebSocket-Origin"] !~ "..."
51 * with regex, and mod_access used to reject non-matches, if desired.
52 * - connections to backend no longer block, but only first address returned
53 * by getaddrinfo() is used; lighttpd does not cycle through all addresses
54 * returned by DNS resolution. Note: DNS resolution occurs once at startup.
55 * - directives renamed from websocket.* to wstunnel.*
56 * - directive websocket.ping_interval replaced with wstunnel.ping-interval
57 * (note the '_' changed to '-')
58 * - directive websocket.timeout should be replaced with server.max-read-idle
59 * - attribute "type" is an independent directive wstunnel.frame-type
60 * (default is "text" unless "binary" is specified)
61 * - attribute "origins" is an independent directive wstunnel.origins
62 * - attribute "proto" removed; mod_proxy can proxy to backend websocket server
63 * - attribute "subproto" should be replaced with mod_setenv directive
64 * setenv.set-response-header = ( "Sec-WebSocket-Protocol" => "..." )
65 * if header is required
68 * - websocket protocol compliance has not been reviewed
69 * e.g. when to send 1000 Normal Closure and when to send 1001 Going Away
70 * - websocket protocol sanity checking has not been reviewed
73 * https://en.wikipedia.org/wiki/WebSocket
74 * https://tools.ietf.org/html/rfc6455
75 * https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
79 #include <sys/types.h>
84 #include "gw_backend.h"
91 #include "http_header.h"
95 #define MOD_WEBSOCKET_LOG_NONE 0
96 #define MOD_WEBSOCKET_LOG_ERR 1
97 #define MOD_WEBSOCKET_LOG_WARN 2
98 #define MOD_WEBSOCKET_LOG_INFO 3
99 #define MOD_WEBSOCKET_LOG_DEBUG 4
101 #define DEBUG_LOG(level, format, ...) \
102 if (hctx->gw.conf.debug >= (level)) { \
103 log_error_write(hctx->srv, __FILE__, __LINE__, (format), __VA_ARGS__); \
110 unsigned short int ping_interval
;
113 typedef struct plugin_data
{
115 plugin_config
**config_storage
;
120 MOD_WEBSOCKET_FRAME_STATE_INIT
,
122 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
123 MOD_WEBSOCKET_FRAME_STATE_READ_LENGTH
,
124 MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH
,
125 MOD_WEBSOCKET_FRAME_STATE_READ_MASK
,
126 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
128 MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD
129 } mod_wstunnel_frame_state_t
;
132 MOD_WEBSOCKET_FRAME_TYPE_TEXT
,
133 MOD_WEBSOCKET_FRAME_TYPE_BIN
,
134 MOD_WEBSOCKET_FRAME_TYPE_CLOSE
,
136 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
137 MOD_WEBSOCKET_FRAME_TYPE_PING
,
138 MOD_WEBSOCKET_FRAME_TYPE_PONG
139 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
141 } mod_wstunnel_frame_type_t
;
146 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
149 #define MOD_WEBSOCKET_MASK_CNT 4
150 unsigned char mask
[MOD_WEBSOCKET_MASK_CNT
];
151 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
153 } mod_wstunnel_frame_control_t
;
156 mod_wstunnel_frame_state_t state
;
157 mod_wstunnel_frame_control_t ctl
;
158 mod_wstunnel_frame_type_t type
, type_before
, type_backend
;
160 } mod_wstunnel_frame_t
;
164 mod_wstunnel_frame_t frame
;
170 server
*srv
; /*(for mod_wstunnel module-specific DEBUG_LOG() macro)*/
175 static handler_t
mod_wstunnel_handshake_create_response(handler_ctx
*);
176 static int mod_wstunnel_frame_send(handler_ctx
*, mod_wstunnel_frame_type_t
, const char *, size_t);
177 static int mod_wstunnel_frame_recv(handler_ctx
*);
178 #define _MOD_WEBSOCKET_SPEC_IETF_00_
179 #define _MOD_WEBSOCKET_SPEC_RFC_6455_
181 INIT_FUNC(mod_wstunnel_init
) {
182 return calloc(1, sizeof(plugin_data
));
185 FREE_FUNC(mod_wstunnel_free
) {
186 plugin_data
*p
= p_d
;
187 if (p
->config_storage
) {
188 for (size_t i
= 0; i
< srv
->config_context
->used
; ++i
) {
189 plugin_config
*s
= p
->config_storage
[i
];
190 if (NULL
== s
) continue;
191 buffer_free(s
->frame_type
);
192 array_free(s
->origins
);
193 /*assert(0 == offsetof(s->gw));*/
194 gw_plugin_config_free(&s
->gw
);
195 /*free(s);*//*free'd by gw_plugin_config_free()*/
197 free(p
->config_storage
);
200 return HANDLER_GO_ON
;
203 SETDEFAULTS_FUNC(mod_wstunnel_set_defaults
) {
204 plugin_data
*p
= p_d
;
206 config_values_t cv
[] = {
207 { "wstunnel.server", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
},
208 { "wstunnel.debug", NULL
, T_CONFIG_SHORT
, T_CONFIG_SCOPE_CONNECTION
},
209 { "wstunnel.balance", NULL
, T_CONFIG_LOCAL
, T_CONFIG_SCOPE_CONNECTION
},
210 { "wstunnel.map-extensions",NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
},
211 { "wstunnel.frame-type", NULL
, T_CONFIG_STRING
,T_CONFIG_SCOPE_CONNECTION
},
212 { "wstunnel.origins", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
},
213 { "wstunnel.ping-interval", NULL
, T_CONFIG_SHORT
, T_CONFIG_SCOPE_CONNECTION
},
214 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
217 p
->config_storage
= calloc(srv
->config_context
->used
, sizeof(plugin_config
*));
218 force_assert(p
->config_storage
);
219 for (size_t i
= 0; i
< srv
->config_context
->used
; ++i
) {
220 array
*ca
= ((data_config
*)(srv
->config_context
->data
[i
]))->value
;
221 plugin_config
*s
= calloc(1, sizeof(plugin_config
));
224 s
->gw
.debug
= 0; /* MOD_WEBSOCKET_LOG_NONE */
225 s
->gw
.ext_mapping
= array_init();
226 s
->frame_type
= buffer_init();
227 s
->origins
= array_init();
228 s
->ping_interval
= 0; /* do not send ping */
230 cv
[0].destination
= NULL
; /* T_CONFIG_LOCAL */
231 cv
[1].destination
= &(s
->gw
.debug
);
232 cv
[2].destination
= NULL
; /* T_CONFIG_LOCAL */
233 cv
[3].destination
= s
->gw
.ext_mapping
;
234 cv
[4].destination
= s
->frame_type
;
235 cv
[5].destination
= s
->origins
;
236 cv
[6].destination
= &(s
->ping_interval
);
238 p
->config_storage
[i
] = s
;
240 if (0 != config_insert_values_global(srv
, ca
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
241 return HANDLER_ERROR
;
244 du
= array_get_element(ca
, "wstunnel.server");
245 if (!gw_set_defaults_backend(srv
, (gw_plugin_data
*)p
, du
, i
, 0)) {
246 return HANDLER_ERROR
;
249 du
= array_get_element(ca
, "wstunnel.balance");
250 if (!gw_set_defaults_balance(srv
, &s
->gw
, du
)) {
251 return HANDLER_ERROR
;
254 /* disable check-local for all exts (default enabled) */
255 if (s
->gw
.exts
) { /*(check after gw_set_defaults_backend())*/
256 for (size_t j
= 0; j
< s
->gw
.exts
->used
; ++j
) {
257 gw_extension
*ex
= s
->gw
.exts
->exts
[j
];
258 for (size_t n
= 0; n
< ex
->used
; ++n
) {
259 ex
->hosts
[n
]->check_local
= 0;
264 /* error if "mode" = "authorizer"; wstunnel can not act as authorizer */
265 /*(check after gw_set_defaults_backend())*/
266 if (s
->gw
.exts_auth
&& s
->gw
.exts_auth
->used
) {
267 log_error_write(srv
, __FILE__
, __LINE__
, "s",
268 "wstunnel.server must not define any hosts "
269 "with attribute \"mode\" = \"authorizer\"");
270 return HANDLER_ERROR
;
273 /*(default frame-type to "text" unless "binary" is specified)*/
274 if (!buffer_is_empty(s
->frame_type
)
275 && !buffer_is_equal_caseless_string(s
->frame_type
,
276 CONST_STR_LEN("binary"))) {
277 buffer_clear(s
->frame_type
);
280 if (!array_is_vlist(s
->origins
)) {
281 log_error_write(srv
, __FILE__
, __LINE__
, "s",
282 "unexpected value for wstunnel.origins; expected wstunnel.origins = ( \"...\", \"...\" )");
283 return HANDLER_ERROR
;
285 for (size_t j
= 0; j
< s
->origins
->used
; ++j
) {
286 if (buffer_string_is_empty(((data_string
*)s
->origins
->data
[j
])->value
)) {
287 log_error_write(srv
, __FILE__
, __LINE__
, "s",
288 "unexpected empty string in wstunnel.origins");
289 return HANDLER_ERROR
;
294 /*assert(0 == offsetof(s->gw));*/
295 return HANDLER_GO_ON
;
298 static handler_t
wstunnel_create_env(server
*srv
, gw_handler_ctx
*gwhctx
) {
299 handler_ctx
*hctx
= (handler_ctx
*)gwhctx
;
300 connection
*con
= hctx
->gw
.remote_conn
;
302 if (0 == con
->request
.content_length
) {
303 http_response_upgrade_read_body_unknown(srv
, con
);
304 chunkqueue_append_chunkqueue(con
->request_content_queue
,
307 rc
= mod_wstunnel_handshake_create_response(hctx
);
308 if (rc
!= HANDLER_GO_ON
) return rc
;
310 con
->http_status
= 101; /* Switching Protocols */
311 con
->file_started
= 1;
313 hctx
->ping_ts
= srv
->cur_ts
;
314 gw_set_transparent(srv
, &hctx
->gw
);
316 return HANDLER_GO_ON
;
319 static handler_t
wstunnel_stdin_append(server
*srv
, gw_handler_ctx
*gwhctx
) {
320 /* prepare websocket frames to backend */
321 /* (caller should verify con->request_content_queue) */
322 /*assert(!chunkqueue_is_empty(con->request_content_queue));*/
323 handler_ctx
*hctx
= (handler_ctx
*)gwhctx
;
324 if (0 == mod_wstunnel_frame_recv(hctx
))
325 return HANDLER_GO_ON
;
328 /* future: might differentiate client close request from client error,
329 * and then send 1000 or 1001 */
330 connection
*con
= hctx
->gw
.remote_conn
;
331 DEBUG_LOG(MOD_WEBSOCKET_LOG_INFO
, "sds",
332 "disconnected from client ( fd =", con
->fd
, ")");
333 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sds",
334 "send close response to client ( fd =", con
->fd
, ")");
335 mod_wstunnel_frame_send(hctx
, MOD_WEBSOCKET_FRAME_TYPE_CLOSE
, CONST_STR_LEN("1000")); /* 1000 Normal Closure */
336 gw_connection_reset(srv
, con
, hctx
->gw
.plugin_data
);
337 return HANDLER_FINISHED
;
341 static handler_t
wstunnel_recv_parse(server
*srv
, connection
*con
, http_response_opts
*opts
, buffer
*b
, size_t n
) {
342 handler_ctx
*hctx
= (handler_ctx
*)opts
->pdata
;
343 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sdsx",
344 "recv data from backend ( fd =", hctx
->gw
.fd
, "), size =", n
);
345 if (0 == n
) return HANDLER_FINISHED
;
346 if (mod_wstunnel_frame_send(hctx
,hctx
->frame
.type_backend
,b
->ptr
,n
) < 0) {
347 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
, "s", "fail to send data to client");
348 return HANDLER_ERROR
;
353 return HANDLER_GO_ON
;
356 #define PATCH(x) p->conf.x = s->x
357 #define PATCH_GW(x) p->conf.gw.x = s->gw.x
358 static void mod_wstunnel_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
360 plugin_config
*s
= p
->config_storage
[0];
367 PATCH_GW(ext_mapping
);
370 PATCH(ping_interval
);
372 /* skip the first, the global context */
373 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
374 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
375 s
= p
->config_storage
[i
];
377 /* condition didn't match */
378 if (!config_check_cond(srv
, con
, dc
)) {
382 for (j
= 0; j
< dc
->value
->used
; j
++) {
383 data_unset
*du
= dc
->value
->data
[j
];
385 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("wstunnel.server"))) {
387 /*(wstunnel can not act as authorizer,
388 * but p->conf.exts_auth must not be NULL)*/
391 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("wstunnel.debug"))) {
393 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("wstunnel.balance"))) {
395 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("wstunnel.map-extensions"))) {
396 PATCH_GW(ext_mapping
);
397 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("wstunnel.frame-type"))) {
399 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("wstunnel.origins"))) {
401 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("wstunnel.ping-interval"))) {
402 PATCH(ping_interval
);
410 static int header_contains_token (buffer
*b
, const char *m
, size_t mlen
)
412 for (char *s
= b
->ptr
; s
; s
= strchr(s
, ',')) {
413 while (*s
== ' ' || *s
== '\t' || *s
== ',') ++s
;
414 if (0 == strncasecmp(s
, m
, mlen
)) {
416 if (*s
== '\0' || *s
== ' ' || *s
== '\t' || *s
== ',' || *s
== ';')
423 static int wstunnel_is_allowed_origin(connection
*con
, handler_ctx
*hctx
) {
424 /* If allowed origins is set (and not empty list), fail closed if no match.
425 * Note that origin provided in request header has not been normalized, so
426 * change in case or other non-normal forms might not match allowed list */
427 const array
* const allowed_origins
= hctx
->conf
.origins
;
428 buffer
*origin
= NULL
;
431 if (0 == allowed_origins
->used
) {
432 DEBUG_LOG(MOD_WEBSOCKET_LOG_INFO
, "s", "allowed origins not specified");
436 /* "Origin" header is preferred
437 * ("Sec-WebSocket-Origin" is from older drafts of websocket spec) */
438 origin
= http_header_request_get(con
, HTTP_HEADER_OTHER
, CONST_STR_LEN("Origin"));
439 if (NULL
== origin
) {
441 http_header_request_get(con
, HTTP_HEADER_OTHER
, CONST_STR_LEN("Sec-WebSocket-Origin"));
443 olen
= buffer_string_length(origin
);
445 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
, "s", "Origin header is invalid");
446 con
->http_status
= 400; /* Bad Request */
450 for (size_t i
= 0; i
< allowed_origins
->used
; ++i
) {
451 buffer
*b
= ((data_string
*)allowed_origins
->data
[i
])->value
;
452 size_t blen
= buffer_string_length(b
);
453 if ((olen
> blen
? origin
->ptr
[olen
-blen
-1] == '.' : olen
== blen
)
454 && buffer_is_equal_right_len(origin
, b
, blen
)) {
455 DEBUG_LOG(MOD_WEBSOCKET_LOG_INFO
, "bsb",
456 origin
, "matches allowed origin:", b
);
460 DEBUG_LOG(MOD_WEBSOCKET_LOG_INFO
, "bs",
461 origin
, "does not match any allowed origins");
462 con
->http_status
= 403; /* Forbidden */
466 static int wstunnel_check_request(connection
*con
, handler_ctx
*hctx
) {
467 const buffer
* const vers
=
468 http_header_request_get(con
, HTTP_HEADER_OTHER
, CONST_STR_LEN("Sec-WebSocket-Version"));
469 const long hybivers
= (NULL
!= vers
) ? strtol(vers
->ptr
, NULL
, 10) : 0;
470 if (hybivers
< 0 || hybivers
> INT_MAX
) {
471 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
, "s", "invalid Sec-WebSocket-Version");
472 con
->http_status
= 400; /* Bad Request */
476 /*(redundant since HTTP/1.1 required in mod_wstunnel_check_extension())*/
477 if (buffer_is_empty(con
->request
.http_host
)) {
478 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
, "s", "Host header does not exist");
479 con
->http_status
= 400; /* Bad Request */
483 if (!wstunnel_is_allowed_origin(con
, hctx
)) {
487 return (int)hybivers
;
490 static void wstunnel_backend_error(gw_handler_ctx
*gwhctx
) {
491 handler_ctx
*hctx
= (handler_ctx
*)gwhctx
;
492 if (hctx
->gw
.state
== GW_STATE_WRITE
|| hctx
->gw
.state
== GW_STATE_READ
) {
493 mod_wstunnel_frame_send(hctx
, MOD_WEBSOCKET_FRAME_TYPE_CLOSE
, CONST_STR_LEN("1001")); /* 1001 Going Away */
497 static void wstunnel_handler_ctx_free(void *gwhctx
) {
498 handler_ctx
*hctx
= (handler_ctx
*)gwhctx
;
499 chunk_buffer_release(hctx
->frame
.payload
);
502 static handler_t
wstunnel_handler_setup (server
*srv
, connection
*con
, plugin_data
*p
) {
503 handler_ctx
*hctx
= con
->plugin_ctx
[p
->id
];
506 hctx
->srv
= srv
; /*(for mod_wstunnel module-specific DEBUG_LOG() macro)*/
507 hctx
->conf
= p
->conf
; /*(copies struct)*/
508 hybivers
= wstunnel_check_request(con
, hctx
);
509 if (hybivers
< 0) return HANDLER_FINISHED
;
510 hctx
->hybivers
= hybivers
;
512 DEBUG_LOG(MOD_WEBSOCKET_LOG_INFO
,"s","WebSocket Version = hybi-00");
515 DEBUG_LOG(MOD_WEBSOCKET_LOG_INFO
,"sd","WebSocket Version =",hybivers
);
518 hctx
->gw
.opts
.backend
= BACKEND_PROXY
; /*(act proxy-like; not used)*/
519 hctx
->gw
.opts
.pdata
= hctx
;
520 hctx
->gw
.opts
.parse
= wstunnel_recv_parse
;
521 hctx
->gw
.stdin_append
= wstunnel_stdin_append
;
522 hctx
->gw
.create_env
= wstunnel_create_env
;
523 hctx
->gw
.handler_ctx_free
= wstunnel_handler_ctx_free
;
524 hctx
->gw
.backend_error
= wstunnel_backend_error
;
525 hctx
->gw
.response
= chunk_buffer_acquire();
527 hctx
->frame
.state
= MOD_WEBSOCKET_FRAME_STATE_INIT
;
528 hctx
->frame
.ctl
.siz
= 0;
529 hctx
->frame
.payload
= chunk_buffer_acquire();
531 binary
= !buffer_is_empty(hctx
->conf
.frame_type
); /*("binary")*/
534 http_header_request_get(con
, HTTP_HEADER_OTHER
, CONST_STR_LEN("Sec-WebSocket-Protocol"));
536 for (const char *s
= vb
->ptr
; *s
; ++s
) {
537 while (*s
==' '||*s
=='\t'||*s
=='\r'||*s
=='\n') ++s
;
538 if (0 == strncasecmp(s
, "binary", sizeof("binary")-1)) {
539 s
+= sizeof("binary")-1;
540 while (*s
==' '||*s
=='\t'||*s
=='\r'||*s
=='\n') ++s
;
541 if (*s
==','||*s
=='\0') {
547 else if (0 == strncasecmp(s
, "base64", sizeof("base64")-1)) {
548 s
+= sizeof("base64")-1;
549 while (*s
==' '||*s
=='\t'||*s
=='\r'||*s
=='\n') ++s
;
550 if (*s
==','||*s
=='\0') {
556 if (NULL
== s
) break;
562 DEBUG_LOG(MOD_WEBSOCKET_LOG_INFO
, "s",
563 "will recv binary data from backend");
564 hctx
->frame
.type
= MOD_WEBSOCKET_FRAME_TYPE_BIN
;
565 hctx
->frame
.type_before
= MOD_WEBSOCKET_FRAME_TYPE_BIN
;
566 hctx
->frame
.type_backend
= MOD_WEBSOCKET_FRAME_TYPE_BIN
;
569 DEBUG_LOG(MOD_WEBSOCKET_LOG_INFO
, "s",
570 "will recv text data from backend");
571 hctx
->frame
.type
= MOD_WEBSOCKET_FRAME_TYPE_TEXT
;
572 hctx
->frame
.type_before
= MOD_WEBSOCKET_FRAME_TYPE_TEXT
;
573 hctx
->frame
.type_backend
= MOD_WEBSOCKET_FRAME_TYPE_TEXT
;
576 return HANDLER_GO_ON
;
579 static handler_t
mod_wstunnel_check_extension(server
*srv
, connection
*con
, void *p_d
) {
580 plugin_data
*p
= p_d
;
584 if (con
->mode
!= DIRECT
)
585 return HANDLER_GO_ON
;
586 if (con
->request
.http_method
!= HTTP_METHOD_GET
)
587 return HANDLER_GO_ON
;
588 if (con
->request
.http_version
!= HTTP_VERSION_1_1
)
589 return HANDLER_GO_ON
;
592 * Connection: upgrade, keep-alive, ...
593 * Upgrade: WebSocket, ...
595 vb
= http_header_request_get(con
, HTTP_HEADER_UPGRADE
, CONST_STR_LEN("Upgrade"));
597 || !header_contains_token(vb
, CONST_STR_LEN("websocket")))
598 return HANDLER_GO_ON
;
599 vb
= http_header_request_get(con
, HTTP_HEADER_CONNECTION
, CONST_STR_LEN("Connection"));
601 || !header_contains_token(vb
, CONST_STR_LEN("upgrade")))
602 return HANDLER_GO_ON
;
604 mod_wstunnel_patch_connection(srv
, con
, p
);
605 if (NULL
== p
->conf
.gw
.exts
) return HANDLER_GO_ON
;
607 rc
= gw_check_extension(srv
,con
,(gw_plugin_data
*)p
,1,sizeof(handler_ctx
));
608 return (HANDLER_GO_ON
== rc
&& con
->mode
== p
->id
)
609 ? wstunnel_handler_setup(srv
, con
, p
)
613 TRIGGER_FUNC(mod_wstunnel_handle_trigger
) {
614 const plugin_data
* const p
= p_d
;
615 const time_t cur_ts
= srv
->cur_ts
+ 1;
617 gw_handle_trigger(srv
, p_d
);
619 for (size_t i
= 0; i
< srv
->conns
->used
; ++i
) {
620 connection
*con
= srv
->conns
->ptr
[i
];
621 handler_ctx
*hctx
= con
->plugin_ctx
[p
->id
];
622 if (NULL
== hctx
|| con
->mode
!= p
->id
)
625 if (hctx
->gw
.state
!= GW_STATE_WRITE
&& hctx
->gw
.state
!= GW_STATE_READ
)
628 if (cur_ts
- con
->read_idle_ts
> con
->conf
.max_read_idle
) {
629 DEBUG_LOG(MOD_WEBSOCKET_LOG_INFO
, "sds",
630 "timeout client ( fd =", con
->fd
, ")");
631 mod_wstunnel_frame_send(hctx
, MOD_WEBSOCKET_FRAME_TYPE_CLOSE
, NULL
, 0);
632 gw_connection_reset(srv
, con
, p_d
);
633 joblist_append(srv
, con
);
634 /* avoid server.c closing connection with error due to max_read_idle
635 * (might instead run joblist after plugins_call_handle_trigger())*/
636 con
->read_idle_ts
= cur_ts
;
640 if (0 != hctx
->hybivers
641 && hctx
->conf
.ping_interval
> 0
642 && (time_t)hctx
->conf
.ping_interval
+ hctx
->ping_ts
< cur_ts
) {
643 hctx
->ping_ts
= cur_ts
;
644 mod_wstunnel_frame_send(hctx
, MOD_WEBSOCKET_FRAME_TYPE_PING
, CONST_STR_LEN("ping"));
645 joblist_append(srv
, con
);
650 return HANDLER_GO_ON
;
653 int mod_wstunnel_plugin_init(plugin
*p
);
654 int mod_wstunnel_plugin_init(plugin
*p
) {
655 p
->version
= LIGHTTPD_VERSION_ID
;
656 p
->name
= buffer_init_string("wstunnel");
657 p
->init
= mod_wstunnel_init
;
658 p
->cleanup
= mod_wstunnel_free
;
659 p
->set_defaults
= mod_wstunnel_set_defaults
;
660 p
->connection_reset
= gw_connection_reset
;
661 p
->handle_uri_clean
= mod_wstunnel_check_extension
;
662 p
->handle_subrequest
= gw_handle_subrequest
;
663 p
->handle_trigger
= mod_wstunnel_handle_trigger
;
664 p
->handle_waitpid
= gw_handle_waitpid_cb
;
673 * modified from Norio Kobota mod_websocket_handshake.c
676 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_
678 #include "sys-endian.h" /* lighttpd */
679 #include "md5.h" /* lighttpd */
681 static int get_key3(connection
*con
, char *buf
) {
682 /* 8 bytes should have been sent with request
683 * for draft-ietf-hybi-thewebsocketprotocol-00 */
684 chunkqueue
*cq
= con
->request_content_queue
;
686 /*(caller should ensure bytes available prior to calling this routine)*/
687 /*assert(chunkqueue_length(cq) >= 8);*/
688 for (chunk
*c
= cq
->first
; NULL
!= c
; c
= c
->next
) {
689 /*(chunk_remaining_length() on MEM_CHUNK)*/
690 size_t n
= (size_t)(buffer_string_length(c
->mem
) - c
->offset
);
691 /*(expecting 8 bytes to be in memory directly after headers)*/
692 if (c
->type
!= MEM_CHUNK
) break; /* FILE_CHUNK not handled here */
693 if (n
> bytes
) n
= bytes
;
694 memcpy(buf
, c
->mem
->ptr
+c
->offset
, n
);
696 if (0 == (bytes
-= n
)) break;
698 if (0 != bytes
) return -1;
699 chunkqueue_mark_written(cq
, 8);
703 static int get_key_number(uint32_t *ret
, const buffer
*b
) {
704 const char * const s
= b
->ptr
;
708 char tmp
[10 + 1]; /* #define UINT32_MAX_STRLEN 10 */
710 for (size_t i
= 0, used
= buffer_string_length(b
); i
< used
; ++i
) {
711 if (light_isdigit(s
[i
])) {
713 if (++j
>= sizeof(tmp
)) return -1;
715 else if (s
[i
] == ' ') ++sp
; /* count num spaces */
718 n
= strtoul(tmp
, NULL
, 10);
719 if (n
> UINT32_MAX
|| 0 == sp
) return -1;
720 *ret
= (uint32_t)n
/ sp
;
724 static int create_MD5_sum(connection
*con
) {
725 uint32_t buf
[4]; /* MD5 binary hash len */
729 http_header_request_get(con
, HTTP_HEADER_OTHER
, CONST_STR_LEN("Sec-WebSocket-Key1"));
731 http_header_request_get(con
, HTTP_HEADER_OTHER
, CONST_STR_LEN("Sec-WebSocket-Key2"));
733 if (NULL
== key1
|| get_key_number(buf
+0, key1
) < 0 ||
734 NULL
== key2
|| get_key_number(buf
+1, key2
) < 0 ||
735 get_key3(con
, (char *)(buf
+2)) < 0) {
738 #ifdef __BIG_ENDIAN__
739 #define ws_htole32(s,u)\
744 ws_htole32((unsigned char *)(buf
+0), buf
[0]);
745 ws_htole32((unsigned char *)(buf
+1), buf
[1]);
748 li_MD5_Update(&ctx
, buf
, sizeof(buf
));
749 li_MD5_Final((unsigned char *)buf
, &ctx
); /*(overwrite buf[] with result)*/
750 chunkqueue_append_mem(con
->write_queue
, (char *)buf
, sizeof(buf
));
754 static int create_response_ietf_00(handler_ctx
*hctx
) {
755 connection
*con
= hctx
->gw
.remote_conn
;
756 buffer
*value
= hctx
->srv
->tmp_buf
;
758 /* "Origin" header is preferred
759 * ("Sec-WebSocket-Origin" is from older drafts of websocket spec) */
760 buffer
*origin
= http_header_request_get(con
, HTTP_HEADER_OTHER
, CONST_STR_LEN("Origin"));
761 if (NULL
== origin
) {
763 http_header_request_get(con
, HTTP_HEADER_OTHER
, CONST_STR_LEN("Sec-WebSocket-Origin"));
765 if (NULL
== origin
) {
766 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
, "s", "Origin header is invalid");
769 if (buffer_is_empty(con
->request
.http_host
)) {
770 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
, "s", "Host header does not exist");
774 /* calc MD5 sum from keys */
775 if (create_MD5_sum(con
) < 0) {
776 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
, "s", "Sec-WebSocket-Key is invalid");
780 http_header_response_set(con
, HTTP_HEADER_UPGRADE
,
781 CONST_STR_LEN("Upgrade"),
782 CONST_STR_LEN("websocket"));
783 #if 0 /*(added later in http_response_write_header())*/
784 http_header_response_append(con
, HTTP_HEADER_CONNECTION
,
785 CONST_STR_LEN("Connection"),
786 CONST_STR_LEN("upgrade"));
788 #if 0 /*(Sec-WebSocket-Origin header is not required for hybi-00)*/
789 /* Note: it is insecure to simply reflect back origin provided by client
790 * (if admin did not configure restricted list of valid origins)
791 * (see wstunnel_check_request()) */
792 http_header_response_set(con
, HTTP_HEADER_OTHER
,
793 CONST_STR_LEN("Sec-WebSocket-Origin"),
794 CONST_BUF_LEN(origin
));
797 if (buffer_is_equal_string(con
->uri
.scheme
, CONST_STR_LEN("https")))
798 buffer_copy_string_len(value
, CONST_STR_LEN("wss://"));
800 buffer_copy_string_len(value
, CONST_STR_LEN("ws://"));
801 buffer_append_string_buffer(value
, con
->request
.http_host
);
802 buffer_append_string_buffer(value
, con
->uri
.path
);
803 http_header_response_set(con
, HTTP_HEADER_OTHER
,
804 CONST_STR_LEN("Sec-WebSocket-Location"),
805 CONST_BUF_LEN(value
));
810 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */
813 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_
815 #include "algo_sha1.h" /* lighttpd */
816 #include "base64.h" /* lighttpd */
818 static int create_response_rfc_6455(handler_ctx
*hctx
) {
819 connection
*con
= hctx
->gw
.remote_conn
;
821 unsigned char sha_digest
[SHA_DIGEST_LENGTH
];
824 http_header_request_get(con
, HTTP_HEADER_OTHER
, CONST_STR_LEN("Sec-WebSocket-Key"));
826 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
, "s", "Sec-WebSocket-Key is invalid");
830 /* get SHA1 hash of key */
831 /* refer: RFC-6455 Sec.1.3 Opening Handshake */
833 SHA1_Update(&sha
, (const unsigned char *)CONST_BUF_LEN(value
));
834 SHA1_Update(&sha
, (const unsigned char *)CONST_STR_LEN("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
835 SHA1_Final(sha_digest
, &sha
);
837 http_header_response_set(con
, HTTP_HEADER_UPGRADE
,
838 CONST_STR_LEN("Upgrade"),
839 CONST_STR_LEN("websocket"));
840 #if 0 /*(added later in http_response_write_header())*/
841 http_header_response_append(con
, HTTP_HEADER_CONNECTION
,
842 CONST_STR_LEN("Connection"),
843 CONST_STR_LEN("upgrade"));
846 value
= hctx
->srv
->tmp_buf
;
848 buffer_append_base64_encode(value
, sha_digest
, SHA_DIGEST_LENGTH
, BASE64_STANDARD
);
849 http_header_response_set(con
, HTTP_HEADER_OTHER
,
850 CONST_STR_LEN("Sec-WebSocket-Accept"),
851 CONST_BUF_LEN(value
));
853 if (hctx
->frame
.type
== MOD_WEBSOCKET_FRAME_TYPE_BIN
)
854 http_header_response_set(con
, HTTP_HEADER_OTHER
,
855 CONST_STR_LEN("Sec-WebSocket-Protocol"),
856 CONST_STR_LEN("binary"));
857 else if (-1 == hctx
->subproto
)
858 http_header_response_set(con
, HTTP_HEADER_OTHER
,
859 CONST_STR_LEN("Sec-WebSocket-Protocol"),
860 CONST_STR_LEN("base64"));
865 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
868 handler_t
mod_wstunnel_handshake_create_response(handler_ctx
*hctx
) {
869 connection
*con
= hctx
->gw
.remote_conn
;
870 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_
871 if (hctx
->hybivers
>= 8) {
872 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "s", "send handshake response");
873 if (0 != create_response_rfc_6455(hctx
)) {
874 con
->http_status
= 400; /* Bad Request */
875 return HANDLER_ERROR
;
877 return HANDLER_GO_ON
;
879 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
881 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_
882 if (hctx
->hybivers
== 0) {
883 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_
884 /* 8 bytes should have been sent with request
885 * for draft-ietf-hybi-thewebsocketprotocol-00 */
886 chunkqueue
*cq
= con
->request_content_queue
;
887 if (chunkqueue_length(cq
) < 8)
888 return HANDLER_WAIT_FOR_EVENT
;
889 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */
891 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "s", "send handshake response");
892 if (0 != create_response_ietf_00(hctx
)) {
893 con
->http_status
= 400; /* Bad Request */
894 return HANDLER_ERROR
;
896 return HANDLER_GO_ON
;
898 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */
900 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
, "s", "not supported WebSocket Version");
901 con
->http_status
= 503; /* Service Unavailable */
902 return HANDLER_ERROR
;
909 * modified from Norio Kobota mod_websocket_frame.c
912 #include "base64.h" /* lighttpd */
913 #include "http_chunk.h" /* lighttpd */
915 #define MOD_WEBSOCKET_BUFMAX (0x0fffff)
917 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_
920 static int send_ietf_00(handler_ctx
*hctx
, mod_wstunnel_frame_type_t type
, const char *payload
, size_t siz
) {
921 static const char head
= 0; /* 0x00 */
922 static const char tail
= ~0; /* 0xff */
923 server
*srv
= hctx
->srv
;
924 connection
*con
= hctx
->gw
.remote_conn
;
929 case MOD_WEBSOCKET_FRAME_TYPE_TEXT
:
930 if (0 == siz
) return 0;
931 http_chunk_append_mem(srv
, con
, &head
, 1);
932 http_chunk_append_mem(srv
, con
, payload
, siz
);
933 http_chunk_append_mem(srv
, con
, &tail
, 1);
936 case MOD_WEBSOCKET_FRAME_TYPE_BIN
:
937 if (0 == siz
) return 0;
938 http_chunk_append_mem(srv
, con
, &head
, 1);
940 /* avoid accumulating too much data in memory; send to tmpfile */
943 len
=li_to_base64(mem
,len
,(unsigned char *)payload
,siz
,BASE64_STANDARD
);
944 http_chunk_append_mem(srv
, con
, mem
, len
);
946 http_chunk_append_mem(srv
, con
, &tail
, 1);
949 case MOD_WEBSOCKET_FRAME_TYPE_CLOSE
:
950 http_chunk_append_mem(srv
, con
, &tail
, 1);
951 http_chunk_append_mem(srv
, con
, &head
, 1);
955 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
, "s", "invalid frame type");
958 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sdsx",
959 "send data to client ( fd =", con
->fd
, "), frame size =", len
);
963 static int recv_ietf_00(handler_ctx
*hctx
) {
964 connection
*con
= hctx
->gw
.remote_conn
;
965 chunkqueue
*cq
= con
->request_content_queue
;
966 buffer
*payload
= hctx
->frame
.payload
;
968 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sdsx",
969 "recv data from client ( fd =", con
->fd
,
970 "), size =", chunkqueue_length(cq
));
971 for (chunk
*c
= cq
->first
; c
; c
= c
->next
) {
972 char *frame
= c
->mem
->ptr
+c
->offset
;
973 /*(chunk_remaining_length() on MEM_CHUNK)*/
974 size_t flen
= (size_t)(buffer_string_length(c
->mem
) - c
->offset
);
975 /*(FILE_CHUNK not handled, but might need to add support)*/
976 force_assert(c
->type
== MEM_CHUNK
);
977 for (size_t i
= 0; i
< flen
; ) {
978 switch (hctx
->frame
.state
) {
979 case MOD_WEBSOCKET_FRAME_STATE_INIT
:
980 hctx
->frame
.ctl
.siz
= 0;
981 if (frame
[i
] == 0x00) {
982 hctx
->frame
.state
= MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD
;
985 else if (((unsigned char *)frame
)[i
] == 0xff) {
986 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
,"s","recv close frame");
990 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
,"s","recv invalid frame");
994 case MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD
:
995 mem
= (char *)memchr(frame
+i
, 0xff, flen
- i
);
997 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sx",
998 "got continuous payload, size =", flen
- i
);
999 hctx
->frame
.ctl
.siz
+= flen
- i
;
1000 if (hctx
->frame
.ctl
.siz
> MOD_WEBSOCKET_BUFMAX
) {
1001 DEBUG_LOG(MOD_WEBSOCKET_LOG_WARN
, "sx",
1002 "frame size has been exceeded:",
1003 MOD_WEBSOCKET_BUFMAX
);
1006 buffer_append_string_len(payload
, frame
+i
, flen
- i
);
1010 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sx",
1011 "got final payload, size =", (mem
- frame
+i
));
1012 hctx
->frame
.ctl
.siz
+= (mem
- frame
+i
);
1013 if (hctx
->frame
.ctl
.siz
> MOD_WEBSOCKET_BUFMAX
) {
1014 DEBUG_LOG(MOD_WEBSOCKET_LOG_WARN
, "sx",
1015 "frame size has been exceeded:",
1016 MOD_WEBSOCKET_BUFMAX
);
1019 buffer_append_string_len(payload
, frame
+i
, mem
- frame
+i
);
1020 i
+= (mem
- frame
+i
);
1021 hctx
->frame
.state
= MOD_WEBSOCKET_FRAME_STATE_INIT
;
1024 if (hctx
->frame
.type
== MOD_WEBSOCKET_FRAME_TYPE_TEXT
1025 && !buffer_is_empty(payload
)) {
1026 hctx
->frame
.ctl
.siz
= 0;
1027 chunkqueue_append_buffer(hctx
->gw
.wb
, payload
);
1028 buffer_clear(payload
);
1031 if (hctx
->frame
.state
== MOD_WEBSOCKET_FRAME_STATE_INIT
1032 && !buffer_is_empty(payload
)) {
1034 size_t len
= buffer_string_length(payload
);
1035 len
= (len
+3)/4*3+1;
1036 chunkqueue_get_memory(hctx
->gw
.wb
, &len
);
1037 b
= hctx
->gw
.wb
->last
->mem
;
1038 len
= buffer_string_length(b
);
1039 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "ss",
1040 "try to base64 decode:", payload
->ptr
);
1041 if (NULL
== buffer_append_base64_decode(b
, CONST_BUF_LEN(payload
), BASE64_STANDARD
)) {
1042 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
, "s",
1043 "fail to base64-decode");
1046 buffer_clear(payload
);
1047 /*chunkqueue_use_memory()*/
1048 hctx
->gw
.wb
->bytes_in
+= buffer_string_length(b
)-len
;
1052 default: /* never reach */
1053 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
,"s", "BUG: unknown state");
1058 /* XXX: should add ability to handle and preserve partial frames above */
1059 /*(not chunkqueue_reset(); do not reset cq->bytes_in, cq->bytes_out)*/
1060 chunkqueue_mark_written(cq
, chunkqueue_length(cq
));
1064 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */
1067 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_
1069 #define MOD_WEBSOCKET_OPCODE_CONT 0x00
1070 #define MOD_WEBSOCKET_OPCODE_TEXT 0x01
1071 #define MOD_WEBSOCKET_OPCODE_BIN 0x02
1072 #define MOD_WEBSOCKET_OPCODE_CLOSE 0x08
1073 #define MOD_WEBSOCKET_OPCODE_PING 0x09
1074 #define MOD_WEBSOCKET_OPCODE_PONG 0x0A
1076 #define MOD_WEBSOCKET_FRAME_LEN16 0x7E
1077 #define MOD_WEBSOCKET_FRAME_LEN63 0x7F
1078 #define MOD_WEBSOCKET_FRAME_LEN16_CNT 2
1079 #define MOD_WEBSOCKET_FRAME_LEN63_CNT 8
1081 static int send_rfc_6455(handler_ctx
*hctx
, mod_wstunnel_frame_type_t type
, const char *payload
, size_t siz
) {
1082 server
*srv
= hctx
->srv
;
1083 connection
*con
= hctx
->gw
.remote_conn
;
1087 /* allowed null payload for ping, pong, close frame */
1088 if (payload
== NULL
&& ( type
== MOD_WEBSOCKET_FRAME_TYPE_TEXT
1089 || type
== MOD_WEBSOCKET_FRAME_TYPE_BIN
)) {
1094 case MOD_WEBSOCKET_FRAME_TYPE_TEXT
:
1095 mem
[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_TEXT
);
1096 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "s", "type = text");
1098 case MOD_WEBSOCKET_FRAME_TYPE_BIN
:
1099 mem
[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_BIN
);
1100 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "s", "type = binary");
1102 case MOD_WEBSOCKET_FRAME_TYPE_PING
:
1103 mem
[0] = (char) (0x80 | MOD_WEBSOCKET_OPCODE_PING
);
1104 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "s", "type = ping");
1106 case MOD_WEBSOCKET_FRAME_TYPE_PONG
:
1107 mem
[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_PONG
);
1108 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "s", "type = pong");
1110 case MOD_WEBSOCKET_FRAME_TYPE_CLOSE
:
1112 mem
[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_CLOSE
);
1113 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "s", "type = close");
1117 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sx", "payload size =", siz
);
1118 if (siz
< MOD_WEBSOCKET_FRAME_LEN16
) {
1122 else if (siz
<= UINT16_MAX
) {
1123 mem
[1] = MOD_WEBSOCKET_FRAME_LEN16
;
1124 mem
[2] = (siz
>> 8) & 0xff;
1125 mem
[3] = siz
& 0xff;
1126 len
= 1+MOD_WEBSOCKET_FRAME_LEN16_CNT
+1;
1129 mem
[1] = MOD_WEBSOCKET_FRAME_LEN63
;
1134 mem
[6] = (siz
>> 24) & 0xff;
1135 mem
[7] = (siz
>> 16) & 0xff;
1136 mem
[8] = (siz
>> 8) & 0xff;
1137 mem
[9] = siz
& 0xff;
1138 len
= 1+MOD_WEBSOCKET_FRAME_LEN63_CNT
+1;
1140 http_chunk_append_mem(srv
, con
, mem
, len
);
1141 if (siz
) http_chunk_append_mem(srv
, con
, payload
, siz
);
1142 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sdsx",
1143 "send data to client ( fd =",con
->fd
,"), frame size =",len
+siz
);
1147 static void unmask_payload(handler_ctx
*hctx
) {
1148 buffer
* const b
= hctx
->frame
.payload
;
1149 for (size_t i
= 0, used
= buffer_string_length(b
); i
< used
; ++i
) {
1150 b
->ptr
[i
] ^= hctx
->frame
.ctl
.mask
[hctx
->frame
.ctl
.mask_cnt
];
1151 hctx
->frame
.ctl
.mask_cnt
= (hctx
->frame
.ctl
.mask_cnt
+ 1) % 4;
1155 static int recv_rfc_6455(handler_ctx
*hctx
) {
1156 connection
*con
= hctx
->gw
.remote_conn
;
1157 chunkqueue
*cq
= con
->request_content_queue
;
1158 buffer
*payload
= hctx
->frame
.payload
;
1159 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sdsx",
1160 "recv data from client ( fd =", con
->fd
,
1161 "), size =", chunkqueue_length(cq
));
1162 for (chunk
*c
= cq
->first
; c
; c
= c
->next
) {
1163 char *frame
= c
->mem
->ptr
+c
->offset
;
1164 /*(chunk_remaining_length() on MEM_CHUNK)*/
1165 size_t flen
= (size_t)(buffer_string_length(c
->mem
) - c
->offset
);
1166 /*(FILE_CHUNK not handled, but might need to add support)*/
1167 force_assert(c
->type
== MEM_CHUNK
);
1168 for (size_t i
= 0; i
< flen
; ) {
1169 switch (hctx
->frame
.state
) {
1170 case MOD_WEBSOCKET_FRAME_STATE_INIT
:
1171 switch (frame
[i
] & 0x0f) {
1172 case MOD_WEBSOCKET_OPCODE_CONT
:
1173 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "s", "type = continue");
1174 hctx
->frame
.type
= hctx
->frame
.type_before
;
1176 case MOD_WEBSOCKET_OPCODE_TEXT
:
1177 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "s", "type = text");
1178 hctx
->frame
.type
= MOD_WEBSOCKET_FRAME_TYPE_TEXT
;
1179 hctx
->frame
.type_before
= hctx
->frame
.type
;
1181 case MOD_WEBSOCKET_OPCODE_BIN
:
1182 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "s", "type = binary");
1183 hctx
->frame
.type
= MOD_WEBSOCKET_FRAME_TYPE_BIN
;
1184 hctx
->frame
.type_before
= hctx
->frame
.type
;
1186 case MOD_WEBSOCKET_OPCODE_PING
:
1187 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "s", "type = ping");
1188 hctx
->frame
.type
= MOD_WEBSOCKET_FRAME_TYPE_PING
;
1190 case MOD_WEBSOCKET_OPCODE_PONG
:
1191 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "s", "type = pong");
1192 hctx
->frame
.type
= MOD_WEBSOCKET_FRAME_TYPE_PONG
;
1194 case MOD_WEBSOCKET_OPCODE_CLOSE
:
1195 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "s", "type = close");
1196 hctx
->frame
.type
= MOD_WEBSOCKET_FRAME_TYPE_CLOSE
;
1200 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
, "s", "type is invalid");
1205 hctx
->frame
.state
= MOD_WEBSOCKET_FRAME_STATE_READ_LENGTH
;
1207 case MOD_WEBSOCKET_FRAME_STATE_READ_LENGTH
:
1208 if ((frame
[i
] & 0x80) != 0x80) {
1209 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
, "s",
1210 "payload was not masked");
1213 hctx
->frame
.ctl
.mask_cnt
= 0;
1214 hctx
->frame
.ctl
.siz
= (uint64_t)(frame
[i
] & 0x7f);
1215 if (hctx
->frame
.ctl
.siz
== 0) {
1216 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sx",
1217 "specified payload size =", hctx
->frame
.ctl
.siz
);
1218 hctx
->frame
.state
= MOD_WEBSOCKET_FRAME_STATE_READ_MASK
;
1220 else if (hctx
->frame
.ctl
.siz
== MOD_WEBSOCKET_FRAME_LEN16
) {
1221 hctx
->frame
.ctl
.siz
= 0;
1222 hctx
->frame
.ctl
.siz_cnt
= MOD_WEBSOCKET_FRAME_LEN16_CNT
;
1224 MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH
;
1226 else if (hctx
->frame
.ctl
.siz
== MOD_WEBSOCKET_FRAME_LEN63
) {
1227 hctx
->frame
.ctl
.siz
= 0;
1228 hctx
->frame
.ctl
.siz_cnt
= MOD_WEBSOCKET_FRAME_LEN63_CNT
;
1230 MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH
;
1233 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sx",
1234 "specified payload size =", hctx
->frame
.ctl
.siz
);
1235 hctx
->frame
.state
= MOD_WEBSOCKET_FRAME_STATE_READ_MASK
;
1239 case MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH
:
1240 hctx
->frame
.ctl
.siz
=
1241 (hctx
->frame
.ctl
.siz
<< 8) + (frame
[i
] & 0xff);
1242 hctx
->frame
.ctl
.siz_cnt
--;
1243 if (hctx
->frame
.ctl
.siz_cnt
<= 0) {
1244 if (hctx
->frame
.type
== MOD_WEBSOCKET_FRAME_TYPE_PING
&&
1245 hctx
->frame
.ctl
.siz
> MOD_WEBSOCKET_BUFMAX
) {
1246 DEBUG_LOG(MOD_WEBSOCKET_LOG_WARN
, "sx",
1247 "frame size has been exceeded:",
1248 MOD_WEBSOCKET_BUFMAX
);
1251 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sx",
1252 "specified payload size =", hctx
->frame
.ctl
.siz
);
1253 hctx
->frame
.state
= MOD_WEBSOCKET_FRAME_STATE_READ_MASK
;
1257 case MOD_WEBSOCKET_FRAME_STATE_READ_MASK
:
1258 hctx
->frame
.ctl
.mask
[hctx
->frame
.ctl
.mask_cnt
] = frame
[i
];
1259 hctx
->frame
.ctl
.mask_cnt
++;
1260 if (hctx
->frame
.ctl
.mask_cnt
>= MOD_WEBSOCKET_MASK_CNT
) {
1261 hctx
->frame
.ctl
.mask_cnt
= 0;
1262 if (hctx
->frame
.type
== MOD_WEBSOCKET_FRAME_TYPE_PING
&&
1263 hctx
->frame
.ctl
.siz
== 0) {
1264 mod_wstunnel_frame_send(hctx
,
1265 MOD_WEBSOCKET_FRAME_TYPE_PONG
,
1268 if (hctx
->frame
.ctl
.siz
== 0) {
1269 hctx
->frame
.state
= MOD_WEBSOCKET_FRAME_STATE_INIT
;
1273 MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD
;
1278 case MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD
:
1279 /* hctx->frame.ctl.siz <= SIZE_MAX */
1280 if (hctx
->frame
.ctl
.siz
<= flen
- i
) {
1281 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sx",
1282 "read payload, size =", hctx
->frame
.ctl
.siz
);
1283 buffer_append_string_len(payload
, frame
+i
, (size_t)
1284 (hctx
->frame
.ctl
.siz
& SIZE_MAX
));
1285 i
+= (size_t)(hctx
->frame
.ctl
.siz
& SIZE_MAX
);
1286 hctx
->frame
.ctl
.siz
= 0;
1287 hctx
->frame
.state
= MOD_WEBSOCKET_FRAME_STATE_INIT
;
1288 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sx",
1289 "rest of frame size =", flen
- i
);
1290 /* SIZE_MAX < hctx->frame.ctl.siz */
1293 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sx",
1294 "read payload, size =", flen
- i
);
1295 buffer_append_string_len(payload
, frame
+i
, flen
- i
);
1296 hctx
->frame
.ctl
.siz
-= flen
- i
;
1298 DEBUG_LOG(MOD_WEBSOCKET_LOG_DEBUG
, "sx",
1299 "rest of payload size =", hctx
->frame
.ctl
.siz
);
1301 switch (hctx
->frame
.type
) {
1302 case MOD_WEBSOCKET_FRAME_TYPE_TEXT
:
1303 case MOD_WEBSOCKET_FRAME_TYPE_BIN
:
1305 unmask_payload(hctx
);
1306 chunkqueue_append_buffer(hctx
->gw
.wb
, payload
);
1307 buffer_clear(payload
);
1310 case MOD_WEBSOCKET_FRAME_TYPE_PING
:
1311 if (hctx
->frame
.ctl
.siz
== 0) {
1312 unmask_payload(hctx
);
1313 mod_wstunnel_frame_send(hctx
,
1314 MOD_WEBSOCKET_FRAME_TYPE_PONG
,
1315 payload
->ptr
, buffer_string_length(payload
));
1316 buffer_clear(payload
);
1319 case MOD_WEBSOCKET_FRAME_TYPE_PONG
:
1320 buffer_clear(payload
);
1322 case MOD_WEBSOCKET_FRAME_TYPE_CLOSE
:
1324 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
, "s",
1325 "BUG: invalid frame type");
1330 DEBUG_LOG(MOD_WEBSOCKET_LOG_ERR
, "s", "BUG: invalid state");
1335 /* XXX: should add ability to handle and preserve partial frames above */
1336 /*(not chunkqueue_reset(); do not reset cq->bytes_in, cq->bytes_out)*/
1337 chunkqueue_mark_written(cq
, chunkqueue_length(cq
));
1341 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
1344 int mod_wstunnel_frame_send(handler_ctx
*hctx
, mod_wstunnel_frame_type_t type
,
1345 const char *payload
, size_t siz
) {
1346 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_
1347 if (hctx
->hybivers
>= 8) return send_rfc_6455(hctx
, type
, payload
, siz
);
1348 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
1349 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_
1350 if (0 == hctx
->hybivers
) return send_ietf_00(hctx
, type
, payload
, siz
);
1351 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */
1355 int mod_wstunnel_frame_recv(handler_ctx
*hctx
) {
1356 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_
1357 if (hctx
->hybivers
>= 8) return recv_rfc_6455(hctx
);
1358 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
1359 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_
1360 if (0 == hctx
->hybivers
) return recv_ietf_00(hctx
);
1361 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */