Forum Discussion

bigben932_22424's avatar
bigben932_22424
Icon for Altostratus rankAltostratus
Apr 24, 2017

HTML5 WebSocket rewriting

I am having an issue with rewriting my WebSocket connection. Let me explain my scenario, and then I will explain the issue I am experiencing.

 

On my Internal network (192.168.252.x) I have a HTML5 gateway device(252.100), that is used to establish an HTML5 RDP session. This component is setup correctly, from the internal network I am able to log into the web based interface and successfully establish an HTML5 based RDP connection from my HTML5 gateway device (252.100) to my target machine 252.101.

 

What I am trying to accomplish is to do this HTML5 RDP connection going through the F5. From the webtop, my user will click on a portal access link. This portal access link takes the user to the web based front-end of my HTML5 gateway device via and https webpage. My user is able to successfully go through the webtop, and log into my web front end.

 

The issue occurs when starting the HTML5 RDP session from the webtop. My HTML5 proxy machine is throwing an error saying Websocket closed. Tracing the network traffic between all components, there is no traffic flow from the HTML gateway and the Target machine, and no traffic flows into the HTML gateway. This is due to the fact that the Websocket is not being rewritten by the F5.

 

I will try to attach an image with the Chrome dev console that shows this websocket address:

 

In short: when a new websocket is created I get the following:

 

WebSocket connection to 'wss://192.168.252.100/myservice?mypage.hsl_mode=DIRECT&servicename_name=encodedlinkname failed: Error in connection establishment net::ERR_CONNECTION_TIMED_OUT.

 

What I would expect to see is not the direct Internal IP address of the HTML5 gateway but some External IP from the F5 since the websocket should be rewritten by F5.

 

My External net address is 192.168.210.x. I am testing from 192.168.210.100 and my F5 External self IP is 192.168.210.22. So I would expect to see that the websocket address would the External SelfIP.

 

I have attempted playing around with the HTTP profile, Redirect Rewrite settings (None, All, Matching, and Nodes). But this didn't seem to help. I have also tried creating a WebSocket profile and tested all the Masking settings, (Preserve, Unmask, Selective, and Remmask) Also no dice. I have tested quite a few settings on the other profiles as well without any luck.

 

Any suggestions would be helpful.

 

As a side note: I was able to get this successfully running on Big-ip 11.4. With the same configuration, I am not able to get this working on our newer 12.1 implementation.

 

I am also currently unable to observe the 11.4 websocket creation behavior as this VE install has eaten itself while trying to do a version update.. But that is another story.

 

 

  • I am able to connect to the HTML5 Gateway using a VS configured with a Pool containing the HTML5 Gateway. This however is just a work around, as I am still not rewriting the websocket connection, so this workaround is not actually a real solution.

     

    I foresee this possible workaround working by using my VS with the pooled address of the HTML5 gateway added. I found some sort of bug, or unexpected behavior when i did the following.

     

    Connected to my HTML5 Gatway using the VS with the Pool: https://192.168.220.204:9001. This connects me directly to the Gateway. I authenticate, and then start my HTML5 session, the session successfully works. I then disconnect the session.

     

    Only after doing this. I then log into my VS (https://192.168.220.204:443) that has my webtop and portal access link to the Internal HTML5 Gateway server. I login, start the HTML5 session and I am able to successfully connect to my target using my HTML5 rdp link on the HTML5 gateway. At this point though, the websocket is technically not rewritten, but it somehow used the websocket connection used by the previous connection to the VS Pool address.

     

    Image below:

     

    This image shows the working connection, but the portal access link was not configured in a way to use this 192.168.210.204:9001 VS Pool address. But perhaps as a workaround it might be possible to build a portal access link in a way where it makes first a TCP connection to the gateway and then add a wss resource??? I don't know if this is at all possible, and none of the configurations I have tried worked, but it might be an idea to try. Just to clarify, this is not actually a rewritten websocket, it is just a bug.

     

     

  • Doing a websocket test on websocket.org via a portal access link, I noticed that rewriting in fact is not possible with my setup and is not a problem of my HTML5 gateway.

     

     

  • PSilva's avatar
    PSilva
    Ret. Employee

    Hi Bigben932~ I don't have an answer for your issue but did find some materials that could help. Apologies if you've already dug into these but thought I'd pass on.

     

    K14754: BIG-IP support for the WebSocket protocol: https://support.f5.com/csp/article/K14754

     

    K14814: The BIG-IP system may drop WebSocket traffic: https://support.f5.com/csp/article/K14814

     

    K15357: WebSocket connections to virtual servers may fail when using OneConnect: https://support.f5.com/csp/article/K15357

     

    and one pertaining to v12:

     

    K04597703: Overview of the Local Traffic Policies feature (12.1.0 and later): https://support.f5.com/csp/article/K04597703

     

    hope those get you in the right direction.

     

    ps

     

  • Thank you Peter Silva.

     

    With 11.4 I am able to get this connection to work just fine. But as I stated, I cannot trace the websocket creation at this time because that instance is destroyed and waiting on a new license to retest. Once this is rebuilt I can add more information to this post.

     

    Regarding K04597703, I will look through this to see if this could help, thank you.

     

  • Hey Bigben,

     

    Did you ever figure this out as I am having the same issue in 12.1.2. I would like to be able to rewrite the websocket when using portal access on a webtop.

     

    Thanks

     

  • Hi Nolan,

     

    So I did resolve this issue. I was in direct contact with F5 and they gave me an iRule to try, which I will post below. Since this was a few months back, all I remember was that I was still having some issues with this iRule and I ended up just using Version 13. Version 13 handles this natively and is much nicer to develop with.

     

    Here is the iRule I received from F5:

     

    when HTTP_RESPONSE_RELEASE {
    
     ID560601: [Portal Access] Support for HTML5 video loaded with Media Source Extensions
    
     Allow access to blob: scheme URLs
    
        if { [HTTP::header exists Content-Security-Policy] } {
            HTTP::header replace Content-Security-Policy \
                [string map {"data:" "data: blob:"} [HTTP::header Content-Security-Policy]]
        }
        `
    
        }
        when ACCESS_ACL_ALLOWED {
    
         ID470231: [RFE][Portal Access] WebSocket support
    
         Experimental code for wrapping WebSocket constructor and disabling plugins for WS response
    
         Tested on: IE11, Chrome, Firefox, Safari 9.1
    
        `if { [HTTP::path] ends_with "/AccessNow/ericom.min.js" } {
        `
    
         log local0. "Injecting WebSocket helper into [HTTP::host][HTTP::uri]"
    
        
    `    set inject_ws_helper 1
            HTTP::header remove Accept-Encoding
            if { [HTTP::version] eq "1.1" } {
                if { [HTTP::header is_keepalive] } {
                    HTTP::header replace "Connection" "Keep-Alive"
                }
                HTTP::version "1.0"
            }
        } elseif { [info exists inject_ws_helper] } {
            unset inject_ws_helper
        }
    
    }
    when REWRITE_REQUEST_DONE {
        if { [HTTP::header exists Upgrade] && \
                [string tolower [HTTP::header Upgrade]] contains "websocket" } {
            REWRITE::disable
        }
    }
    when HTTP_RESPONSE {
        if { [info exists inject_ws_helper] } {
            HTTP::collect 32
        }
    }
    when HTTP_RESPONSE_DATA {
        if { [info exists inject_ws_helper] } {
            unset inject_ws_helper
            HTTP::payload replace 0 0 {
    (function() {
    function F5_Wrap_WebSocket(F5_orig) {
        var origin = 'F5_origin=' +
                (location.protocol + '//' + location.host)
                    .replace(/./g,
                        function(c){return c.charCodeAt(0).toString(16)}
                    ) +
                '&F5CH=I';
        var wrapper = function WebSocket(url) {
            var orig_url = url;
            if (arguments.length > 0) {
                url = F5_WrapURL((url+'').replace(/^ws/i,'http'), 'I')
                    .replace(/^http/i,'ws')
                    .replace('F5CH=I', origin);
            }
            var socket = (arguments.length > 1) ?
                new F5_orig(url, arguments[1]):
                new F5_orig(url);
            Object.defineProperty(socket, 'url', {
                get: function(){return orig_url;}
            });
            return socket;
        };
        if (Object.setPrototypeOf) {
            Object.setPrototypeOf(wrapper, F5_orig);
        } else { // IE10
            var keys = Object.keys(F5_orig);
            for (var i = 0; i < keys.length; ++i) {
                wrapper[keys[i]] = F5_orig[keys[i]];
            }
        }
        wrapper.F5_original = F5_orig;
        wrapper.prototype = F5_orig.prototype;
        Object.defineProperty(wrapper.prototype, 'constructor', {
            value: wrapper, writable: false, enumerable: false
        });
        wrapper.toString = function toString() { return F5_orig.toString();};
        return wrapper;
    }
    if ((typeof(self.MozWebSocket) !== 'undefined') &&
            !self.MozWebSocket.F5_original) {
        self.MozWebSocket = F5_Wrap_WebSocket(self.MozWebSocket)
    }
    if ((typeof(self.WebSocket) !== 'undefined') &&
            !self.WebSocket.F5_original) {
        self.WebSocket = F5_Wrap_WebSocket(self.WebSocket)
    }
    })();
            }
        }
     }