Skip to content
Go back

Tic-tac-toe Over DNS

🎮 Play the game live →

While reading up on how DNS works, I started to wonder what fun things I could do with it. That’s when the idea hit me, what if I made a tic-tac-toe game that runs entirely over DNS queries? It sounded a bit silly, but experimenting with it turned out to be a great way to learn more about how DNS really works.

The Concept

The idea is straightforward, use DNS TXT queries as both the request mechanism and response channel for game commands. Instead of traditional HTTP endpoints, players interact with the game by querying DNS records like abc123.board.tictactoe.phakorn.com or abc123-xyz78901-move-1-1.tictactoe.phakorn.com. The DNS server parses these queries, executes game logic, and returns the game state encoded in TXT record responses.

This approach highlights several interesting aspects of DNS:

Architecture Overview

The system consists of two main components:

Backend: A Go-based DNS server built on the github.com/miekg/dns library that handles all game logic and state management. The server maintains game sessions in memory and processes commands embedded in DNS query subdomains.

Frontend: A Next.js web application that provides a user-friendly interface, translating user interactions into DNS queries via a Node.js DNS client library.

The backend is where the interesting protocol work happens, so let’s dive into how the command system works.

Understanding DNS Zones

Before diving into commands, it’s important to understand DNS zones. A DNS zone is a portion of the DNS namespace that’s managed by a specific authoritative name server. In this project, the server is configured to handle queries for a specific zone (e.g., tictactoe.phakorn.com or game.local).

When a DNS query arrives, the server first checks if the query belongs to its configured zone. If the query name ends with the zone name (like abc123.board.tictactoe.phakorn.com ending with tictactoe.phakorn.com), the server processes it. If not, it returns an NXDOMAIN error, indicating the name doesn’t exist in this zone.

This zone-based routing allows the server to:

How Commands Work

The game uses DNS query names to encode commands. The server parses the subdomain portion (everything before the zone name) to extract the command, session ID, and any parameters. There are three main command formats:

Session Management Commands

These commands don’t require a session ID and operate at the zone level:

When the server receives a query like new.tictactoe.phakorn.com, it:

  1. Verifies the query belongs to the tictactoe.phakorn.com zone
  2. Extracts the subdomain (new)
  3. Recognizes it as a session management command
  4. Creates a new game session with a unique 8-character session ID
  5. Returns the session ID in the TXT record response

Game Commands

These commands require a session ID and operate on a specific game:

The parsing logic splits the subdomain by dots. The first part is the session ID, and the remaining parts form the command. For example, abc123.join.tictactoe.phakorn.com becomes:

The server validates the session ID format (must be 4-36 characters) and looks up the session in memory. If the session exists, it executes the command and returns the result.

Move Commands

Move commands have a special format that includes the player token for authentication:

For example, abc123-xyz78901-move-1-1.tictactoe.phakorn.com means:

The server parses this by:

  1. Splitting the subdomain by hyphens
  2. Finding the move keyword (which must be at index 2)
  3. Extracting the session ID (index 0), token (index 1), row (index 3), and column (index 4)
  4. Validating the token matches a player in the session
  5. Executing the move if it’s valid (checking turn order, position validity, etc.)

The move command format uses hyphens instead of dots to clearly separate the token from the move parameters, making parsing unambiguous.

Game State Management

Since DNS is stateless, the server maintain all game state in memory. Each game session stores:

Encoding Responses in TXT Records

DNS TXT records have limitations: they’re designed for short text strings, and the total response size is constrained by UDP packet limits (typically 512 bytes for standard DNS, though EDNS0 can extend this). The server encodes all responses as human-readable text in TXT records.

For board visualization, the server formats the game state as ASCII art:

_ _ _
_ X _
O _ _
Turn: O | Status: playing

Responses include contextual information based on the command:

The server sets TTL (Time To Live) to 0 by default to prevent DNS resolvers from caching responses, which is crucial for a real-time game where state changes frequently. However, some system DNS resolvers may enforce minimum TTL values regardless of what the server returns.

Session Management and Cleanup

Since DNS is stateless, the server maintains all game sessions in memory. Each session tracks:

The server runs a background goroutine that periodically cleans up stale sessions. By default, sessions older than 120 seconds without activity are removed. This prevents memory leaks in long-running servers while allowing reasonable time for players to complete games.

The cleanup interval is configurable, allowing operators to balance between memory usage and session availability based on expected game duration and server resources.

Conclusion

This project demonstrates that even “simple” protocols like DNS have rich implementation details worth exploring.

The complete implementation is available on GitHub, and you can try the live version at dns-tic-tac-toe.phakorn.com. While this approach isn’t practical for production applications, it serves as an excellent educational exercise.

Sometimes the best way to understand a protocol is to build something unconventional with it. This project reminded me that engineering is as much about curiosity and experimentation as it is about following established patterns.


Share this post on:

Next Post
Less Is More in the Age of AI