Tip of the day: Channel mode +f is a powerful anti-flood feature. It is also slightly complex. Enable it in your most important channels, and consider setting a default in set::modes-on-join.

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'. Another crude websocket example can be seen on https://www.unrealircd.org/files/dev/ws/websocket_unrealircd.html which should work on all major browsers.

Configuring the server

1. Load the websocket module

Add this to your unrealircd.conf, if you are using UnrealIRCd 6.0.5 or later:

loadmodule "websocket";
loadmodule "webserver";

Or, if you are using an older UnrealIRCd version, then use only:

loadmodule "websocket";

Then rehash the server.

2. Enable websocket on the port

Websocket has two URI's:

  • ws:// is plaintext, similar to insecure http://
  • wss:// is encrypted, similar to https://

Insecure

This uses the insecure plaintext protocol. It is not recommended but you could use it for testing.

Create a new Listen block and add 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:

listen {
    ip *;
    port 8000;
    options { 
       websocket { type text; }
    }
}

Secure

To use secure wss:// you need to have a valid official SSL certificate for your server. See Using Let's Encrypt with UnrealIRCd for the tutorial on how to do this. By default UnrealIRCd ships with a self-signed certificate, note that you CANNOT use a self-signed certificate, as all browsers will reject such a certificate. So: either get a certificate from Let's Encrypt or buy a certificate from (another) Certificate Authority.

Please first make sure you have SSL/TLS properly configured with a valid official certificate on your regular IRC TLS port 6697. Only after that continue with websockets below.

Configure a special port for websockets:

listen {
    ip *;
    port 8000;
    options {
        tls;
        websocket { type text; } // type must be either 'text' or 'binary'
    };
    tls-options {
        certificate "/etc/letsencrypt/live/irc.example.org/fullchain.pem";
        key "/etc/letsencrypt/live/irc.example.org/privkey.pem";
        options {
            no-client-certificate;
        };
    };
};

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

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.

Connect a client

You can now connect to your server from javascript using websockets.

Kiwiirc configuration

In the config.json file, change the startupOptions to match your wss settings:

"server": "ip.of.your.server",
"port": 8000,
"tls": true,
"direct": true

Note that direct must be setted to true

Other webclients

A very crude example can be found at https://www.unrealircd.org/files/dev/ws/websocket_unrealircd.html

Do you know other true websocket-to-IRC clients? Feel free to add them to this wiki or tell [email protected]

Fixing common problems

  • If the website that hosts the page with the webchat is located on https:// then you can ONLY connect to wss:// servers. You CANNOT connect from a https:// site to insecure ws://
  • Most browsers such as Firefox and Chrome will block common ports such as 6667 and 6697. You CANNOT use these ports for ws:// or wss://
  • Are you using a Reverse proxy in-between and is everyone showing up with the same IP, like 127.0.0.1? Then have a look at the Proxy block.

Optional: allow websockets on port 443

You may want to tweak your server to allow websocket connections on port 443 (the standard HTTPS port). This step is completely optional, but if you use port 443 then your users can bypass firewall restrictions they may encounter. This happens mostly with chatting from corporate environments.

You cannot let UnrealIRCd listen directly on port 443. Doing so would require root privileges and you must not and cannot run UnrealIRCd as root.

There are two ways to still have UnrealIRCd with websockets on port 443:

  • Use a firewall redirect
  • OR, put a reverse proxy in-between

Linux with firewall redirect

If you have no other application running on port 443, then this is the best option if you are on Linux. If you already have apache/nginx listening on port 443, then choose the other option, #Using a reverse proxy instead and NOT this one!

On Linux you can use iptables to redirect traffic to port 443 to another port (like 8000 in the example of above). This way UnrealIRCd can still run as a low privileged user (eg: ircd).

The beauty of all this is that Linux will preserve the IP address of your clients. Be sure to change port 8000 in the example below if you use another port for websockets with TLS.

ufw

If you are using ufw (such as on Ubuntu or Debian), then put this at the end of /etc/ufw/before.rules:

*nat
-A PREROUTING -p tcp --dport 443 -d internet.ip.address.of.your.irc.server -j REDIRECT --to-port 8000
COMMIT

Replace the internet.ip.address.of.your.irc.server with the actual IP address of your IRC server, e.g. 1.2.3.4.

Then, reload the firewall with ufw reload

iptables

If you don't have ufw then you may need to use iptables directly. Run this as root:

iptables -t nat -I PREROUTING -d internet.ip.address.of.your.irc.server -p tcp --dport 443 -j REDIRECT --to-port 8000

Replace the internet.ip.address.of.your.irc.server with the actual IP address of your IRC server, e.g. 1.2.3.4.

Important points:

  • You will want to add this somewhere to a startup script or firewall configuration file, eg /etc/rc.local. Otherwise after a reboot the firewall rule will be lost.

Using a reverse proxy

You could also set up NGINX or a similar service, and use it as a reverse proxy. This means NGINX will listen on port 443 and then create a connection to UnrealIRCd. So: client--reverseproxy--unrealircd. This adds another service in-between though, so if you are on Linux and not needing a webserver then just use #Linux with firewall redirect instead.

If you want to go this route, then see Proxy block with NGINX reverse proxy for websockets.

Technical information for client developers

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

CR/LF

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'

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

Below is a technical explanation. It is not required reading if you are just a server administrator deploying websockets!

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

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

  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.