WebSocket support

From UnrealIRCd documentation wiki
Jump to navigation Jump to search

UnrealIRCd supports the WebSocket protocol (ws:// and wss://). This allows browsers to connect directly to IRC, without the need of intermediate gateways. This is more secure, stable and uses fewer resources. UnrealIRCd is compatible with KiwiIRC if you use websocket type 'text'.

A very crude websocket example can be seen on https://www.unrealircd.org/files/dev/ws/websocket_unrealircd.html which should work on all major browsers.

Getting started[edit]

1. Load the websocket module[edit]

Add this to your unrealircd.conf:

loadmodule "websocket";

And then rehash the server.

2. Enable websocket on the port[edit]

You need to update your Listen block by adding websocket { type text|binary; } under listen::options. You need to choose either text or binary for a type. Most web clients (such as KiwiIRC) expect type text.

For example, if before you had this:

listen {
    ip *;
    port 6667;
}

Then change it to:

listen {
    ip *;
    port 6667;
    websocket { type text; }
}

3. Connect to the server[edit]

That's all you need to do on the server in order to get ws:// to work! If you also want wss:// (SSL/TLS) then see Using SSL/TLS with websocket. Note that we recommend testing ws:// first, so connect a client.. see next.

Connect a client[edit]

You can now connect to your server from javascript using websockets. A very crude example can be found at https://www.unrealircd.org/files/dev/ws/websocket_unrealircd.html

IMPORTANT: If the website that hosts the javascript with websockets is located on https then you can only connect to wss:// servers (IRC Servers with websockets over SSL/TLS, explained in next section). You cannot connect to insecure ws:// such as port 6667.

So, if you want to test ws:// support on port 6667, then copy-paste the HTML code from the above URL and put it on a HTTP site or your local computer.

Do you know a good/better true websocket-to-IRC client? Let us know in the websocket forum thread or tell me at syzop@unrealircd.org

Using SSL/TLS with websocket[edit]

Websocket has two URI's, ws:// which is plaintext and wss:// which is secure. This is similar to http and https, the former is plaintext and the later is over SSL/TLS.

First of all, you need to have a valid official SSL certificate for your server. This is the hardest part. You cannot use self-signed certificates as all browsers will reject them. How to acquire such a certificate is beyond the scope of this documentation. You can use a "real" certificate authority like Comodo or use free Let's Encrypt.

Now, you need to configure a special port for websocket:

listen {
    ip *;
    port 6800;
    options {
        tls;
        websocket { type text; } // type must be either 'text' or 'binary'
    };
    tls-options {
        certificate "tls/server.cert.pem";
        key "tls/server.key.pem";
        options {
            no-client-certificate;
        };
    };
};

And then you can connect to this server from javascript using wss://ip:6800/

The key thing here is the no-client-certificate. This tells UnrealIRCd NOT to ask for a client certificate. If you don't have this option set then Chrome will FAIL to connect mysteriously. Firefox will work fine, though. It's clear that this is a Chrome bug (see https://bugs.chromium.org/p/chromium/issues/detail?id=329884).

As you can see, the certificate and key can be specified for this specific port. This may be useful when using Let's Encrypt.

Using port 443[edit]

You may want to have UnrealIRCd listen on port 443 (the standard HTTPS port) so your users can bypass firewall restrictions they may encounter. Now, do NOT let UnrealIRCd listen directly on port 443. Doing so would require root privileges and you must NOT run UnrealIRCd as root. You should use a firewall redirect instead. On Linux you can use iptables to redirect traffic to port 443 to another port (like 6800 in the example of above). This way UnrealIRCd can still run as a low privileged user (eg: ircd).

The command (run this as root) would be:

iptables -t nat -I PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 6800

The beauty of this is that Linux will preserve the IP address of your clients as well. Be sure to change port 6800 in the example of above if you use another port, eg 6697.

NOTE: No other application should be using port 443! (eg: apache or nginx)

NOTE 2: You will want to add this somewhere to a startup script or firewall configuration file, otherwise after a reboot the firewall rule will be lost.

NOTE 3: You may want to include a -d ipaddress here to restrict the rule to only apply to destination your IRC server IP address. This only matters if the machine is also used as a router though, not if you are just using a VM in the cloud for IRC.

TODO: Example for FreeBSD

Technical information for client developers[edit]

This is basically the IRC protocol over WebSocket. But there are two important points:

CR/LF[edit]

There is no CR or LF at the end of the line in websocket messages:

  • client->server: no \n required at the end of each line (but you may still send one). You may only issue 1 IRC command per websocket message.
  • server->client: no \r\n will be sent at the end of each line. UnrealIRCd will only send one IRC command per websocket message.

Type 'binary' vs 'text'[edit]

There are two websocket types: binary (WEBSOCKET_TYPE_BINARY) and text (WEBSOCKET_TYPE_TEXT). The difference between the two has consequences on the client (javascript) side.

Nowadays, most web devs want to use type text because it's easier for them. It does come with some caveats/problems with non-UTF8 characters, as explained later in this article.

Problems with websockets and non-UTF8[edit]

Summary (TL;DR): If you 1) do NOT set set::allowed-channelchars to 'any' and 2) ONLY use UTF8 nickchars in set::allowed-nickchars (or don't use allowed-nickchars at all) then you will be fine... mostly.

The problem[edit]

If you use the websocket type 'text', which is popular among websocket developers, then this means websocket users can only receive valid UTF8 text. The problem with this is that IRC traffic may also contain non-UTF8 text. If UnrealIRCd would send the non-UTF8 text to the websocket user then the websocket browser would disconnect the connection due to violating the websocket specification. So, UnrealIRCd replaces the non-UTF8 characters with a "replacement character" (shown as: �). So far so good.

The problem is that the original text is lost in the translation process and also that multiple texts containing a non-UTF8 sequence may resolve to the same "cleaned up text" with the "replacement character". For things like channel messages this is not a problem, but this also happens in other commands like JOIN and MODE.

It allows the following "attacks", such as:

  • If, in your set::allowed-nickchars, you allow a non-UTF8 nick character set, then you can have unkickable users and you can have ghost users (users that you don't see in the nick list but that are still in the channel).
  • If set::allowed-channelchars is set to 'any', this allows non-UTF8 characters in channel names. If a websocket user ends up in such a channel, eg due to invite or a redirect or ban forward, then the websocket user will be unable to part such a channel. And again, here too, there is the possibility of ghost users.

More attack details are available in this post: https://github.com/ircv3/ircv3-specifications/pull/342#issuecomment-491252301

The solution/workaround[edit]

  1. Do not use non-UTF8 character sets in set::allowed-nickchars
  2. Use the default setting set { allowed-channelchars utf8; } or even set { allowed-channelchars ascii; } if you are strict. Do NOT use 'any'.

This still leaves some other issues unresolved but the big issues like desync, ghosts and unkickable user issues should be resolved.

If you don't want any such issues then the websocket client should use type 'binary' instead of 'text'. Then the websocket client will receive the original text without any replacement characters and thus the issues related to that don't appear. That being said, using 'binary' is not popular among client coders.