IRC Client Design Thoughts IRC, and the design of the surrounding software, is somewhat of an idée fixe[1] for me. I've been thinking about, sketching, noodling, and hacking on existing IRC-related software for nearly 15 years, including a stint as the maintainer of the Ithildin IRCd[2] and some work on the Atheme services package. The protocol has an appealing simplicity which makes it very easy to get a basic client working, especially in a language with a powerful socket library, and in fact it can be spoken with netcat or telnet if you so desire. However, building a *good* client is often very challenging, despite the protocol being simple. Why? One major problem is that the protocol itself requires the client to stay connected or lose state/history/etc, which means that clients are essentially required to be long-running, persistent programs. This is totally incompatible with mobile or laptop use, and so the de facto way to run an IRC client is to run it on a server somewhere which has stable power and network connectivity and to then remotely connect to the client. That usually looks like this: phone, laptop, etc server +------------------+ +---------------+ +------+ | ephemeral client | ---> | stable client | ---> | ircd | +------------------+ +---------------+ +------+ There are three basic schools of thought about how to do this, which we can group by how the ephemeral client connects to the stable client: Type 1 are "bouncers", like psybnc or znc; these speak the IRC protocol on both the client and server sides, and so they are essentially caching or keep-alive proxies for IRC itself. One connects to the bouncer's server side with any other client and the bouncer does some trickery to convince the client to have the correct state as though it had been connected all along, including some backlogged messages. There are recent protocol extensions to make this all work smoothly but even without them it is fairly simple and easy to do. The largest single downside of this approach is that the IRC protocol is inherently one-connection-per-socket, meaning that if I want my bouncer to maintain connections to 10 networks, the client must itself open 10 connections to the bouncer so that each connection can have its own socket. It is possible to hack around this, in a gruesome way, by prefixing channel and user names with some unique thing per network and having the bouncer pretend that all the downstream networks are actually a single one, but this breaks tab completion and generally requires a lot of cooperation from the connected client anyway[3]. It is also often difficult to set up secure remote access to these clients from mobile devices; usually one has to set up an SSH tunnel from the mobile device to the server hosting the bouncer and then connect over the tunnel, which may require tunnelling many ports because of the limitation mentioned above. Type 2 are actually ordinary IRC clients running in screen/tmux/dtach. In this case, the ephemeral client is usually an ssh client, not an IRC client, and the protocol spoken between the ephemeral and stable clients is usually the VT100 terminal protocol or something like it. These clients are a pain in the butt to write, often using ncurses or similar, but this approach also basically works. The main downside of the type 2 approach is that mobile clients generally still suck. Few terminal apps behave well on mobile terminals, and there's no way for the client to adapt to being displayed on different form factors - there is simply no way for the terminal to express to the IRC client that it is on a mobile device, even if the client was aware of it. The same problem exists for desktop clients but to a lesser degree. For example, a desktop client might wish to deliver notifications or play noises in response to mentions on IRC, but if the desktop "client" is actually just an xterm and the remote IRC client is a curses app that has no idea the desktop environment exists, that's pretty challenging to do. There are complex workarounds for this[4] but in general it is an ugly problem to deal with. Type 3 are the most interesting to me. These designs are characterized by a protocol between the ephemeral and stable clients that is something other than IRC and VT100-over-SSH. The most popular example that I know of here is IRCCloud, which is functionally an IRC <-> HTTP bridge with web and mobile frontends, but there are others (like Quassel). When designing such a protocol, there are a few properties that seem desirable: 1. It should not require a separate tunneling layer for use over the Internet or from a mobile device, although it should be comfortable running over such a layer if one is necessary. This would dramatically simplify client setup for users. 2. It should expose the entire IRC data model to the client ideally in its "raw" form, meaning no renaming of users/channels, no hiding of state bits, etc. This allows for clients with potentially rich display behavior. 3. It should support some notion of replaying messages and state changes since a given ephemeral client last connected, so that ephemeral clients can display backlog and give the user context. 4. It should support multiple ephemeral clients connecting concurrently - that is, the user should not need to remember to disconnect their laptop client to use their phone client. 5. It should tolerate clients connecting very sporadically over long stretches of time, and not assume that clients can store much (or any) local state. With those properties in mind a rough design sketch starts to emerge: the ephemeral -> stable protocol could look like: * TLS-over-TCP (or some other protocol, like spiped[5] or noise[6], over TCP) so that the protocol can run directly over the Internet. TLS is widely deployed and likely to "work everywhere", but setting it up on the server side is not easy compared to other approaches, and we can likely rely on a preshared secret key, so spiped's protocol may work well. * A data model somewhat like the stock IRC model, but extended with backlog (perhaps in the IRCv3 style) and with support for "you sent this, but on another client" type messages, and tagged with an individual network so that multiple IRC connections can be run over one socket * A client-stored notion of "the last message I saw" so that the stable client can replay as needed In a subsequent post, I may turn these thoughts into a more concrete protocol. Stay tuned! [1]: A preoccupation that resists attempts to remove it. [2]: A pretty nifty piece of software but excessively modular and clever in design. It never really took off and it had some considerable deficiencies, as old IRC servers usually do. [3]: Specifically, because IRC's line length limit is 512 bytes. A message that is near that max length, if rewritten to contain a longer channel or nickname than it originally did, may end up truncated. Avoiding that requires the client and bouncer to negotiate a larger max line length, which IRC theoretically allows for but cannot be relied on in general, or that the client reconstructs fragmented messages, which is very hard to do properly in the presence of network latency. [4]: Specifically, one can use the stable client's scripting functionality to emit an event on the host system, then have a separate ephemeral client program which connects to the same host system to listen for that event. That's exactly how, for example, irssi-notify works. [5]: spiped is https://github.com/Tarsnap/spiped/tree/master/spipe [6]: noise is https://noiseprotocol.org/