Kyle Givler

projects/happy-protocols

Happy Protocols

A collection of Gopher, Gemini, Finger, Echo, Discard, Daytime, Quote of the Day, and Time servers that started with one Gopher hole and somehow ended with me running real old Internet services on their real assigned ports like a maniac.

.NET 10 · TCP / UDP · TLS · Gopher · Gemini · classic RFC protocols · Docker

It started with Gopher

I do not remember exactly how I wandered into Gopher, but Gopher was the thing that started all of this. I built HappyGopher, put an actual Gopher site on the Internet, and discovered that implementing old protocols is much more fun when the result is a real service instead of a localhost demo you throw away afterward.

Gemini came up in the same small-web spaces, so naturally I had to try that too. After that the question gradually stopped being "why would I implement another obsolete or tiny protocol?" and became "well, why wouldn't I?"

Somewhere along the way the Happy prefix stuck, and separate experiments turned into a loose family of protocol servers.

Real protocols on real ports

Part of the appeal is that these are not just protocol-shaped unit tests. Several of them are publicly reachable services, and the implementations use the ports the protocols were actually assigned.

Echo · port 7

TCP and UDP. Send bytes, get exactly those bytes back. There is a real Echo server on the real Echo port because apparently that is the sort of infrastructure I operate now.

Discard · port 9

TCP and optional UDP. Send bytes and receive the satisfaction of knowing that they were professionally thrown away.

Daytime · port 13

RFC 867 over TCP and UDP. Ask for the time and get one readable UTC timestamp back.

QOTD · port 17

Quote of the Day over TCP and UDP, backed by the same daily quote data used by its small HTTP API.

Time · port 37

RFC 868 over TCP and UDP. Four big-endian bytes containing the number of seconds since January 1, 1900. Yes, it wraps in 2036.

Finger · port 79

Plain-text public records, a traditional .plan, and some deliberately non-traditional integrations.

Gopher · port 70

Static content, menus, dynamic pages, search/input selectors, plugins, and the Gopher hole that started the whole mess.

Gemini · port 1965

TLS, virtual hosts, static Gemtext and files, dynamic pages, SNI, and external page plugins.

Gopher became more than a toy server

HappyGopher can serve plain files, generated directory menus, custom gophermap files, images and binary downloads, but the part that made it interesting was adding compiled dynamic pages.

Dynamic selectors can implement things such as server time, Quote of the Day, random quotes, search-style input, and a guestbook while still speaking ordinary Gopher to the client. External page plugins can add more selectors without being compiled into the host application.

The public Gopher hole at gopher.kgivler.com is the practical test bed. It is intentionally phone-friendly, mostly plain text, and weird enough to justify keeping a Gopher client installed.

Gemini was the obvious next rabbit hole

Gemini showed up in the same small-web conversations as Gopher, so I checked it out and ended up writing HappyGemini.

The protocol is still small, but TLS makes the server architecture meaningfully different. HappyGemini handles TLS 1.2/1.3, SNI, certificate selection, virtual hosts, strict request parsing, static content, dynamic pages, and per-plugin assembly loading.

That makes Gopher and Gemini useful bookends for the collection: Gopher is wonderfully bare TCP and tab-separated menus; Gemini stays intentionally small while still bringing certificates, host routing, status codes, and TLS into the picture.

Eventually I stopped rewriting the same TCP server

The first few services each had slightly different versions of the same infrastructure: bind a listener, limit concurrent connections, handle cancellation, time out bad clients, shut down cleanly, wire in dependency injection, and try not to invent a new bug in every repo.

That drift was getting silly, so the common server behavior moved into JoyfulReaperLib.TcpServer. The shared host provides bounded, dependency-injected TCP server infrastructure while each Happy service keeps the protocol-specific part small.

It simplified the individual projects and, more importantly, made fixes to the boring networking machinery apply consistently instead of requiring the same repair in a pile of nearly-identical listeners.

Finger got weird in a different direction

HappyFinger is still recognizable as a Finger server: send a small query and receive a plain-text record. It serves a public directory, profile/project/service records, and a traditional .plan file for the now record.

It also has the most ridiculous integration in the family. Query it with a public 17-digit Steam ID and HappyFinger calls Random Steam Game and returns a randomly selected game from that person's public library.

That feature has almost nothing to do with the historical purpose of Finger, which is precisely why I like it.

finger [email protected]

Finger could still use more love, but it is already a nice example of taking an old protocol seriously enough to implement it correctly and not seriously enough to leave it alone.

Old protocols, modern operational habits

I do not treat "the RFC is from the 1980s" as an excuse to make the server sloppy. The implementations use explicit byte/request limits, connection timeouts, bounded concurrency, graceful shutdown, IPv4/IPv6 support where appropriate, and tests around the wire behavior.

Several of the services also publish best-effort events into Mission Control, but protocol payloads stay out of telemetry when they do not belong there. HappyEcho, for example, returns bytes unchanged without decoding, storing, logging, or publishing the echoed content. Discard accepts bytes and throws them away just as aggressively.

That mix is part of the fun: the protocol might be tiny enough to fit on a page, while the implementation still gets the same operational care I would give a modern network service.

A few of them are actually out there

Some of the family can be reached directly on their classic ports. Depending on your client tools, examples include:

curl.exe gopher://gopher.kgivler.com/
finger [email protected]
nc qotd.kgivler.com 17
nc echo.kgivler.com 7
nc discard.kgivler.com 9

Running a real Echo server on TCP port 7 is objectively unnecessary. This has not stopped me.

The family

HappyGopher

Gopher server, Gopher hole, dynamic selectors, plugins, and guestbook.

Source ↗

HappyGemini

TLS Gemini server with virtual hosts, static content, pages, and plugins.

Source ↗

HappyFinger

Finger records, .plan, and Random Steam Game integration.

Source ↗

HappyEcho

RFC-style TCP/UDP byte echoing on the classic Echo service.

Source ↗

HappyDiscard

A carefully engineered service whose main feature is deleting your bytes.

Source ↗

HappyDaytime

RFC 867 readable UTC time over TCP and UDP.

Source ↗

HappyQOTD

Quote of the Day over TCP/UDP plus a small shared HTTP API.

Source ↗

HappyTime

RFC 868's delightfully inconvenient four-byte time format.

Source ↗

Technology

.NET 10 C# TCP UDP TLS JoyfulReaperLib.TcpServer Native AOT Docker Mission Control

Current status

This is not one product with a roadmap. It is a collection that grows when I run into another protocol that looks fun enough to implement. Gopher and Gemini are the richer servers; the smaller RFC services are useful little laboratories for network behavior and shared server code.

I may add more eventually. The selection criterion is not complicated: if an old or unusual protocol makes me think "I should run one of those," that is usually enough.