An Adventure in IRC-Land

Hi everyone. I’m Kegan, one of the core developers at This is the first in a series on the IRC bridge. The aim of this series is to try to give a behind the scenes look at how the IRC bridge works, what kinds of problems we encountered, and how we plan to scale in the future. This post looks at how the IRC bridge actually works.

Firstly, what is “bridging”? The simple answer is that it is a program which maps between different messaging protocols so that users on different protocols can communicate with each other. Some protocols may have features which are not supported in the other (typing notifications in Matrix, DCC – direct file transfers – in IRC). This means that bridging will always be “inferior” to just using the respective protocol. That being said, where there is common ground a bridge can work well; all messaging protocols support sending and receiving text messages for example. As we’ll see however, the devil is in the detail…

A lot of existing IRC bridges for different protocols share one thing in common: they use a single global bot to bridge traffic. This bot listens to all messages from IRC, and sends them to the other network. The bot also listens for messages from users on the other network, and sends messages on their behalf to IRC. This is a lot easier than having to maintain dedicated TCP connections for each user. However, it isn’t a great experience for IRC users as they:

  • Don’t know who is reading messages on a channel as there is just 1 bot in the membership list.
  • Cannot PM users on the other network.
  • Cannot kick/ban users on the other network without affecting everyone else.
  • Cannot bing/mention users on the other network easily (tab completion).

We made the decision very early on that we would keep dedicated TCP connections for each Matrix user. This means every Matrix user has their own tiny IRC client. This has its own problems:

  • It involves multiple connections to the IRCd so you need special permission to set up an i:line.
  • You need to be able to support identification of individual users (via ident or unique IPv6 addresses).
  • With all these connections to the same IRC channels, you need to have some way to identify which incoming messages have already been handled and which have not.

Mapping Rooms

So now that we have a way to send and receive messages, how do we map the rooms/channels between protocols? This isn’t as easy as you may think. We can have a single static one-to-one mapping:

  • All messages to #channel go to !
  • All messages from ! go to #channel.
  • All PMs between and Bob go to ! and the respective PM on IRC.

In order to make PMs secure, we need to limit who can access the room. This is done by making the Matrix PM room “invite-only”. This can cause problems though if the Matrix user ever leaves that room: they won’t be able to ever re-join! The IRC bridges get around this by allowing Matrix users to replace their dedicated PM room with a new room, and by checking to make sure that the Matrix user is inside the room before sending messages.

Then you have problem of “ownership” of rooms. Who should be able to kick users in a bridged room? There are two main scenarios to consider:

  • The IRC channel has existed for a while and there are existing IRC channel operators.
  • The IRC channel does not exist, but there are existing Matrix moderators.

In the first case, we want to defer ownership to the channel operators. This is what happens by default for all bridged IRC channels on The Matrix users have no power in the room, and are at the mercy of the IRC channel operators. The channel operators are represented by virtual Matrix users in the room. However, they do not have any power level: they are at the same level as real Matrix users. Why? The bridge does this because, unlike IRC, it’s not possible in Matrix to bring a user to the same level as yourself (e.g +o), and then downgrade them back to a regular user (e.g. -o). Instead, the bridge bot itself acts as a custodian for the room, and performs privileged IRC operations (topic changing, kickbans, etc) on the IRC channel operator’s behalf.

In the second case, we want to defer ownership to the Matrix moderators. This is what happens when you “provision a room” in Matrix. The bridge will PM a currently online channel operator and ask for their permission to bridge to Matrix. If they accept, the bridge is made and the power levels in the pre-existing Matrix room are left untouched, giving moderators in Matrix control over the room. However, this power doesn’t extend completely to IRC. If a Matrix moderator grants moderator powers to another Matrix user, this will not be mapped to IRC. Why? It’s not possible for the bridge to give chanops to any random user on any random IRC channel, so it cannot always honour the request. This relies on the humans on either side of the bridge to communicate and map power accordingly. This is done on purpose as there is no 100% perfect mapping between IRC powers and Matrix powers: it’s always going to need to compromise which only a human can make.

Finally, there is the problem of one-to-many mappings. It is possible to have two Matrix rooms bridged to the same IRC channel. The problem occurs when a Matrix user in one room speaks. The bridge can easily map that to IRC, but unless it also maps it back to Matrix, the message will never make it to the 2nd Matrix room. The bridge cannot control/puppet the Matrix user who spoke, so instead it creates a virtual Matrix user to represent that real Matrix user and then sends the message into the 2nd Matrix room. Needless to say, this can be quite confusing and we strongly discourage one-to-many mappings for this reason.

Mapping Messages

Mapping Matrix messages to IRC is rather easy for the most part. Messages are passed from the Homeserver to the bridge via the AS API, and the bridge sends a textual representation of the message to IRC using the IRC connection for that Matrix user. The exact form of the text for images, videos and long text can be quite subjective, and there is inevitably some data loss along the way. For example, you can send big text headings, tables and lists in Matrix, but there is no equivalent on IRC. Thankfully, most Matrix users are sending the corresponding markdown and so the formatting can be reasonably preserved by just sending the plaintext (markdown) body.

Mapping IRC messages to Matrix is more difficult: not because it’s hard to represent the message in Matrix, but because of the architecture of the bridge. The bridge maintains separate connections for each Matrix user. This means the bridge might have, for example, 5 users (and hence connections) on the same channel. When an IRC user sends a message, the bridge gets 5 copies of the message. How does the bridge know:

  • If the message has already been sent?
  • If the message is an intentional duplicate?

The IRC protocol does not have message IDs, so the bridge cannot de-duplicate messages as they arrive. Instead, it “nominates” a single user’s connection to be responsible for delivering messages from that channel. This introduces another problem though. Long-lived TCP connections are fickle things, and can fail without any kind of visible warning until you try to send bytes down it. If a user’s connection drops, another user needs to take over responsibility for delivering messages. This is what the “IRC Event Broker” class does. It allows users to “steal” messages if the bridge has any indication that the connection in charge has dropped. This technique has worked well for us, and gives us the ability to have more robust connections to the channel than with one TCP connection alone.

Admin Rooms

Admin rooms are private Matrix rooms between a real Matrix user and the bridge bot. It allows the Matrix user to control their connection to IRC. It allows:

  • The IRC nick to be changed.
  • The ability to issue /whois commands.
  • The ability to bypass the bridge and send raw IRC commands directly down the TCP connection (e.g. MODE commands).
  • The ability to save a NickServ password for use when the bridge reconnects you.
  • The ability to disconnect from the network entirely.

To perform these actions, Matrix users send a text message which starts with a command name, e.g !whois $ARG. Like all commands, you expect to get a reply once you’ve issued it. However, IRC makes this extremely difficult to do. There is no request/response pair like there is with HTTP requests. Instead, the IRC server may:

  • Ignore the request entirely.
  • Send an error you’re aware of (in the RFC/most servers)
  • Send some information which can be assumed to indicate success.
  • Send an error you’re unaware of.
  • Send some information which sometimes indicates success.

This makes it very difficult to know if a request succeeded or failed, and I’ll go into more detail in the next post which focuses on problems we’ve encountered when developing the IRC bridge. This room is also used to inform the Matrix user about general information about their IRC connection, such as when their connection has been lost, or if there are any errors (e.g. “requires chanops to do this action”). The bridge makes no effort to parse these errors, because it doesn’t always know what caused the error to happen.


Developing a comprehensive IRC bridge is a very difficult task. This post has outlined a few of the ways in which we’ve designed our bridge, and some of the general problems in this field. The bridge is constantly improving as we discover new edge cases with the plethora of IRCd implementations out there. The next post will look at some of these edge cases and look back at some previous outages and examine why they occurred.

New bridged IRC network: GIMPNet

Hey everyone! As of last week, we are now bridging (GIMPNet) for all your GTK+/GNOME needs! It’s running a bleeding-edge version of the IRC bridge which supports basic chanops syncing from IRC to Matrix. This means that if an IRC user gives chanops to a Matrix connection, the bridge will give that Matrix user moderator privileges in the room, allowing them to set the room topic/avatar/alias/etc! We hope this will make customising Matrix-bridged rooms a lot easier.

For a more complete list of current and future bridged IRC networks, see the official wishlist.

Matrix-IRC Bridge reaches v0.4.0

A new version of the IRC bridge has been released onto NPM and the bridges!

The IRC bridge has undergone quite a number of modifications since its original inception over a year ago. Version 0.4 introduces a number of additional features and improvements, which can be found in the changelog. These include automatically linkifying large blocks of text and mirroring kicks/bans to and from Matrix.

With a plethora of protocol gotchas and non-standard features on well-known IRC networks, IRC is a challenging protocol to work with. It’s inevitable that some corner cases are not handled well by the bridge. Over time, the bridge has been hardened by edge cases which we have encountered and patched. These releases signify the continual improvement in the robustness of the bridge, which we aim to continue with into the foreseeable future.

Performance wise, our busiest bridge which we host is the bridge to Freenode. We now have over 1300 active connections to it and have a steady rate of about 240 messages per minute going through to Matrix. We expect to see this number increase significantly over the next few months. Let’s see what the next year will bring!

Micropub support as an Application Service!

I was at IndieWebCamp Edinburgh last week and during the hack day I created a Matrix Application Service (AS) which could act as an IndieWeb Micropub client. Any Matrix message sent to the AS (@micropub:domain) would be converted to a request to a Micropub endpoint.

This required the AS to support IndieAuth – which it does by sending !indieauth http://yourdomain.dom to @micropub:domain which then returns an OAuth2 URL to login via. Currently, the AS just supports a 1:1 mapping from m.text to h:entry but in the future, it can be expanded to include categories and potentially the reverse mapping (where Micropub clients can act as Matrix users!).

Overall, it was a great weekend and I look forward to adding more support for IndieWeb protocols in the future.

The All New Matrix-IRC Application Service

This post has now been edited into a guide – you can find the source in github, and the formatted guide on the website!


Like a lot of open source projects, we use IRC a lot. Naturally, we also use Matrix to communicate with each other. For some time now we’ve had an IRC bot sitting on specific channels to “bridge” together IRC and Matrix. This bot simply sent IRC messages when it received Matrix messages and vice versa. As we started to rely on it more and more though, we realised that there were things that were impossible for simple client-side bots to do by themselves. This spurred the development of Application Services which I introduced in my previous post. In this blog post, I want to outline some of the features and techniques of the IRC application service which we’ve been working on over the past few weeks.


  • Specific channel-to-matrix room bridging : This is what the original IRC bot did. You can specify specific channels and specific room IDs, and messages will be bridged.
  • Dynamic channel-to-matrix room bridging : This allows Matrix users to join any channel on an IRC network, rather than being forced to use one of the specific channels configured.
  • Two-way PM support : IRC users can PM the virtual “M-” users and private Matrix rooms will be created. Likewise, Matrix users can invite the virtual “@irc_Nick:domain” user IDs to a room and a PM to the IRC nick will be made.
  • IRC nick changing support : Matrix users are no longer forced to use “M-” nicks and can change them by sending “!nick” messages directly to the bridge.
  • Ident support : This allows usernames to be authenticated for virtual IRC clients, which means IRC bans can be targeted at the Matrix user rather than the entire application service.

The use of the Application Services API means:

  • The bot can reserve user IDs. This prevents humans from registering for @irc_… user IDs which would then clash with the operation of the bot.
  • The bot can reserve room aliases. This prevents humans from register for #irc_… aliases which would then clash with the operation of the bot.
  • The bot can trivially manage hundreds of users. Events are pushed to the application service directly. If you tried to do this as a client-side bot, you would need one event stream connection per virtual user.
  • The bot can lazily create rooms on demand. This means Matrix users can join non-existent room aliases and have the application service quickly track an IRC channel and create a room with that alias, allowing the join request to succeed.

Implementation details:

  • Written in Node.js, designed to be run using forever.
  • Built on the generic matrix-appservice-node framework.
  • Supports sending metrics in statsd format.
  • Uses matrix-appservice-node to provide a standardised interface when writing application services, rather than an explicit web framework (though under the hood matrix-appservice-node is using Express).

At present, the IRC application service is in beta, and is being run on #matrix and #matrix-dev. If you want to give it a go, check it out on Github – it is not currently released on npm. N.B. it requires features from the develop branch of synapse; either run your own synapse off the develop branch or wait a few days for us to release Synapse 0.9.0.

Needless to say, we look forward to this being the first of many full network<->network bridges into Matrix – come chat on if you’d like to write or run your own! Next up is Lync and XMPP…