
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:
- DNS is fundamentally a request-response protocol, not just a name resolution service
- TXT records can carry arbitrary text data, making them suitable for application-level communication
- The stateless nature of DNS queries forces careful consideration of session management
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:
- Handle multiple zones if needed (though this implementation handles one zone)
- Properly respond to NS (name server) queries for the zone
- Return appropriate error codes for queries outside the zone
- Maintain clear boundaries for what the server is authoritative for
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:
new.tictactoe.phakorn.com- Creates a new game session and returns a session IDlist.tictactoe.phakorn.com- Lists all active game sessionshelp.tictactoe.phakorn.com- Returns usage instructions
When the server receives a query like new.tictactoe.phakorn.com, it:
- Verifies the query belongs to the
tictactoe.phakorn.comzone - Extracts the subdomain (
new) - Recognizes it as a session management command
- Creates a new game session with a unique 8-character session ID
- Returns the session ID in the TXT record response
Game Commands
These commands require a session ID and operate on a specific game:
{session-id}.join.tictactoe.phakorn.com- Join a game session and receive a player token (X or O){session-id}.board.tictactoe.phakorn.com- View the current game board{session-id}.reset.tictactoe.phakorn.com- Reset the game to initial state{session-id}.json.tictactoe.phakorn.com- Get game state as JSON
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:
- Session ID:
abc123 - Command:
join
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:
{session-id}-{token}-move-ROW-COL.tictactoe.phakorn.com
For example, abc123-xyz78901-move-1-1.tictactoe.phakorn.com means:
- Session ID:
abc123 - Player Token:
xyz78901 - Command: Move to row 1, column 1
The server parses this by:
- Splitting the subdomain by hyphens
- Finding the
movekeyword (which must be at index 2) - Extracting the session ID (index 0), token (index 1), row (index 3), and column (index 4)
- Validating the token matches a player in the session
- 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:
- The 3x3 game board state
- Current player’s turn (X or O)
- Game status (pending, playing, X wins, O wins, or draw)
- Player tokens and their assigned symbols (X or O)
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:
- Session creation: Returns the new session ID and example commands
- Join command: Returns the player token and assigned symbol (X or O)
- Board view: Returns the current board state with turn and status information
- Move command: Returns the updated board state and indicates whether the move was accepted
- Error responses: Include descriptive error messages and helpful context
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:
- A unique session ID (8 characters by default)
- Game state (board, turn, status)
- Player tokens and their assigned symbols
- Creation timestamp for cleanup purposes
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.