Project

General

Profile

[Solved] Possible problem with mod_wstunnel going from V1.4.72 and earlier to V1.4.73 and later

Added by amcbride1 about 1 month ago

I have a simple websocket setup in the lighttpd.conf file:

$HTTP["url"] =~ "^/websocket" {
 wstunnel.server = ( "" => ( ( "host" => "127.0.0.1", "port" => "5039" ) ) )
 wstunnel.frame-type = "binary" 
 wstunnel.ping-interval = 10
 server.stream-request-body  = 2
 server.stream-response-body = 2
}

When making a connection you get:
GET /websocket HTTP/1.1
Host: 10.1.3.193
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0
Upgrade: websocket
Origin: http://10.1.3.193
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8,es;q=0.7,pt;q=0.6
Cookie: Phone=0
Sec-WebSocket-Key: 040ULsL6Scq0VprjPMTw7A==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Protocol: binary

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Sec-WebSocket-Accept: FecC6uqHnpBiJtDqKIO9PDhEcpk=
Sec-WebSocket-Protocol: binary
Connection: upgrade
Date: Thu, 03 Oct 2024 11:36:10 GMT
Server: lighttpd

When updating to anything after V1.4.73, it does not work. Although it requests binary there is no protocol header in the response which seems to stop things working.

GET /websocket HTTP/1.1
Host: 10.1.1.220
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0
Upgrade: websocket
Origin: http://10.1.1.220
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8,es;q=0.7,pt;q=0.6
Cookie: Phone=0
Sec-WebSocket-Key: DIQ8iSGO+Dr3J+2zCOMJ3g==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Protocol: binary

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Sec-WebSocket-Accept: M4Zn1RiZ11aYCxnnLGO9SsM3KFM=
Connection: upgrade
Date: Thu, 03 Oct 2024 11:32:05 GMT
Server: lighttpd

Looking at the code I think it is sort of related to the change in V1.4.73 "[mod_wstunnel] Sec-WebSocket-Protocol only if req hdr".

The problem is if the frame-type in the config is set to "binary" then in the "wstunnel_handler_setup" function it doesn't go into the code that sets hctx->subproto variable, so left alone which picks up 0.

This means no header gets generated for the response. Previously it just used the hctx->frame.type variable.

The workaround is to either set the frame-type to "text" as below or remove the setting of the frame-type.

$HTTP["url"] =~ "^/websocket" {
 wstunnel.server = ( "" => ( ( "host" => "127.0.0.1", "port" => "5039" ) ) )
 wstunnel.frame-type = "text" 
 wstunnel.ping-interval = 10
 server.stream-request-body  = 2
 server.stream-response-body = 2
}

I wonder if the "if (!binary)" part in the "wstunnel_handler_setup" function needs changed ?


Replies (6)

RE: Possible problem with mod_wstunnel going from V1.4.72 and earlier to V1.4.73 and later - Added by gstrauss about 1 month ago

Thanks for digging into this.

Please see discussion in https://redmine.lighttpd.net/boards/2/topics/11202 for why this was changed.
Unconditionally sending Sec-WebSocket-Protocol: binary with lighttpd config wstunnel.frame-type = "binary" caused issues with some clients.

Please see https://redmine.lighttpd.net/boards/2/topics/11202?r=11213#message-11213 or the commit message in commit 83eadca2 for another workaround.

Is there a specific reason you must lock the configuration to wstunnel.frame-type = "binary", or can you leave the config as wstunnel.frame-type = "text", or omit wstunnel.frame-type from the config?

BTW, streaming is automatic with mod_wstunnel, so this should not be necessary to specify for the websocket config:

 server.stream-request-body  = 2
 server.stream-response-body = 2

I'll consider your suggestion: If the client does send explicit Sec-WebSocket-Protocol: binary, then lighttpd should respond with Sec-WebSocket-Protocol: binary if lighttpd is configured for wstunnel.frame-type = "binary", though maybe only for HTTP/1.1.

Another alternative to implement that test in your lighttpd config is

server.modules += ("mod_setenv")
$HTTP["url"] =~ "^/websocket" {
  wstunnel.server = ( "" => ( ( "host" => "127.0.0.1", "port" => "5039" ) ) )
  wstunnel.ping-interval = 10
  wstunnel.frame-type = "binary" 
  $REQUEST_HEADER["Sec-WebSocket-Protocol"] == "binary" {
    setenv.set-response-header = ("Sec-WebSocket-Protocol" => "binary")
  }
}

RE: Possible problem with mod_wstunnel going from V1.4.72 and earlier to V1.4.73 and later - Added by amcbride1 about 1 month ago

Thanks for the reply, I did have a look at that.

I think the reason I had put in the 'binary' originally was just that we were using binary frames in the web socket.

It was more the fact of having that in the config and then upgrading it then broke.

So taking it out or changing it for me was fine.

I may well add the set response just to be sure.

More just a question / note in case anyone else had that set and upgraded.

RE: Possible problem with mod_wstunnel going from V1.4.72 and earlier to V1.4.73 and later - Added by gstrauss about 1 month ago

Untested patch [Edited]:

--- a/src/mod_wstunnel.c
+++ b/src/mod_wstunnel.c
@@ -523,7 +523,7 @@ static handler_t wstunnel_handler_setup (request_st * const r, plugin_data * con
     hctx->frame.payload       = chunk_buffer_acquire();

     unsigned int binary = hctx->conf.frame_type; /*(0 = "text"; 1 = "binary")*/
-    if (!binary) {
+    {
         const buffer *vb =
           http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Protocol"));
         if (NULL != vb) {
@@ -538,6 +538,9 @@ static handler_t wstunnel_handler_setup (request_st * const r, plugin_data * con
                         break;
                     }
                 }
+                else if (binary) {
+                    /* ignore other subprotos if already configured "binary" */
+                }
                 else if (buffer_eq_icase_ssn(s, CONST_STR_LEN("base64"))) {
                     s += sizeof("base64")-1;
                     while (*s==' '||*s=='\t'||*s=='\r'||*s=='\n') ++s;

RE: Possible problem with mod_wstunnel going from V1.4.72 and earlier to V1.4.73 and later - Added by amcbride1 about 1 month ago

That worked for me. It works if I specify 'binary', 'text' or leave it out.

I did have a look at the websocket procotol a bit more and I was wondering if the processing of that header would actually be better done outside of the plugin.

I had not noticed that the specification says that is more of an application defined thing so can be something like:

Sec-WebSocket-Protocol: soap, chat

For example, so you are saying in the request what you want in order of preference and then the backend would say what it supports.

I don't know if something like would work:

server.modules += ("mod_setenv")
$HTTP["url"] =~ "^/websocket" {
  $REQUEST_HEADER["Sec-WebSocket-Protocol"] =~ "/soap/" {
    wstunnel.server = ( "" => ( ( "host" => "127.0.0.1", "port" => "5039" ) ) )
    wstunnel.ping-interval = 10
    wstunnel.frame-type = "binary" 
    setenv.set-response-header = ("Sec-WebSocket-Protocol" => "soap")
  }
  else $REQUEST_HEADER["Sec-WebSocket-Protocol"] ~= "/chat/" {
    wstunnel.server = ( "" => ( ( "host" => "127.0.0.1", "port" => "5040" ) ) )
    wstunnel.ping-interval = 10
    wstunnel.frame-type = "binary" 
    setenv.set-response-header = ("Sec-WebSocket-Protocol" => "chat")
  }
}

In this case you would be using the header to choose the backend, not sure about the regex but maybe something like that would be better ?

RE: Possible problem with mod_wstunnel going from V1.4.72 and earlier to V1.4.73 and later - Added by gstrauss about 1 month ago

These are not mutually exclusive solutions.

Your last post would cause mod_setenv to overwrite the Sec-WebSocket-Protocol response header with your desired value before the response was sent to the client. The idea is fine.
(...although the syntax for the regex would need to be corrected, e.g. $REQUEST_HEADER["Sec-WebSocket-Protocol"] =~ "soap")

A more pedantic, but probably unnecessary regex: $REQUEST_HEADER["Sec-WebSocket-Protocol"] =~ "(?:^| |,)soap(?:,| |$)"

Again, I do not understand why specifying wstunnel.frame-type = "binary" in lighttpd.conf is necessary and would recommend leaving it out unless you have a reason to explicitly specify it.

RE: [Solved] Possible problem with mod_wstunnel going from V1.4.72 and earlier to V1.4.73 and later - Added by amcbride1 about 1 month ago

Thanks again for looking, as you say I don't think I should be setting it.

I think the fact I was came from the example on the wiki so was a misunderstanding on my part.

Doing the setenv in the config seems the better way of handing it given other people may be using different sub-protocols and that would mean it would be set to one of the values in the request if it was there.

It probably needs another else to handle the case when no Sec-WebSocket-Protocol was send but that does depend on the thing that is connecting.

    (1-6/6)