Coffee Space


Listen:

Telnet Chat

Preview Image

TL;DR: A very basic telnet chat server with a simple (and hopefully robust) design.

Background

For a while now, the IRC server has been down on this server - something that has been bugging me immensely. The reason was simply that the server didn’t have any remaining resources left for running the thing. This server has 128MB of RAM, zero swap, and is absolutely pegged running nginx, pandoc/ffmpeg page rebuilds and dead social.

But to be honest, IRC is a very complex beast anyway - and will now only get even more complex as IRCv3 is rolled out, which is not backwards compatible. Essentially what they are edging towards is an implementation of XMPP, which is fair enough if you want that sort of thing.

What I want is something that goes back to basics - just people talking to each other. None of this authentication stuff, none of these complex protocols - just text. Sure, people could pretend to be each other, but this was also part of the enjoyment of the old internet - it was the Wild West! Absolutely worst case, I will just take the chat server down.

Design

As you may have guessed, I chose to use telnet as the client software. It’s a simple piece of software that every internet connected computer can run. In fact, it’s so simple that even netcat (nc) can communicate with the server…

Using telnet:

0001 telnet coffeespace.org.uk 6666

Or nc (netcat):

0002 nc coffeespace.org.uk 6666

And then - just start typing to talk. It’s that simple.

Implementation

There is an implementation now live on this server and the code is available on GitHub. At the time of writing, the main server code (server.c) is just 322 lines - much of this is comments. Come and have a chat!

Now for some of the juicy implementation details…

Main Loop

This happens outside of the server code like so:

0003   /* Run main server loop */
0004   while(1){
0005     server_service();
0006   }

The reason for this is that it allows a person to regularly perform server related tasks, whilst still restricting the entire application to one core.

Yes, you heard that correctly. We handle multiple clients in a single RAM-limited loop (not including the kernel space). This is achieved with the following:

0007   /* Block until input arrives on one or more active sockets */
0008   read_fd_set = active_fd_set;
0009   if(select(FD_SETSIZE, &read_fd_set, NULL, NULL, NULL) < 0){

We block until there is some activity, in which case the program comes alive and the main loop begins to spin again. For the most part, this program is actually quite efficient and hardly does anything at all.

Write To Client

Writing to client is done in two modes, TCP_DELAY (send as you like) and TCP_CORK (wait until I tell you send bulk buffered traffic). The implementations are overlapped, such that there is good code-reuse.

For example, a call to just server_writeToClient() will simply use TCP_DELAY (up to 40ms delay).

On the other hand, if you call server_writeToClient_start() beforehand and then server_writeToClient_end() after, you can enable and then disable TCP_CORK. This allows you to buffer messages in the kernel space and then send them off in one go.

Write To All

Another nice functionality is the ability to write to all active client sockets, minus one. The idea here is that we shouldn’t be echo’ing what the user just sent. This has a similar functionality with server_writeToAll(), as well as the TCP_CORK enable/disable functions on each socket (server_writeToAll_start() and server_writeToAll_end()).

There is even the ability to write a notice to all active client sockets using the server_writeNoticeAll() function. This allows us to give global messages such as connect/disconnect messages.

Commands

Finally we have some basic commands (output from the terminal):

0010 | Commands are as follows:
0011 | /b       - Display the banner
0012 | /h or /? - Display this help
0013 | /l       - List connected IPs
0014 | /q       - Quit the connection

We simply have a way to reprint the banner /b (maybe there is some MOTD the user misses), display help with /h (or /? for Windows users), /l to list currently connected IP addresses and finally /q to quit (amusingly telnet eats CTRL-C requests so this is required).

Finally

This only took a few hours to code up a basic version and I think it is more than suitable to go straight to live running. Let’s see what happens as a result of it running in the wild!

As I mentioned on the repository, there are a few features that could be nice to add in the future:

  • Cloaking - Add at least some privacy.
  • Moderation - The ability to kick out annoying people.
  • Auto-moderation - Stop people from spamming/abusing the service.
  • Limits - Put limits on connection time, bandwidth, number of connections, etc.

But these are things that can be worked on iteratively. There is no rush to work on these immediately. Let’s not start putting out fires where there are none!