How croc's TCP Relay Manages Rooms and Connections
croc's TCP relay pairs two clients through cryptographically authenticated "rooms" using a synchronized map structure, forwarding encrypted data while automatically expiring stale sessions via background cleanup goroutines.
The open-source file transfer tool croc eliminates NAT traversal headaches by routing connections through a lightweight TCP relay. Understanding how croc's TCP relay manages rooms and connections reveals the concurrent architecture that securely bridges senders and receivers without direct peer-to-peer visibility.
Handshake and PAKE Authentication
When a client connects to the relay, the server initiates a Password-Authenticated Key Exchange (PAKE) to establish a shared secret. According to src/tcp/tcp.go, the relay calls pake.InitCurve to negotiate encryption parameters before the client transmits the relay password.
If validation succeeds, the relay responds with an encrypted "ok" banner (lines 37-45), confirming the connection is authenticated and ready for room assignment. This step ensures that only clients possessing the correct code phrase can proceed to room allocation.
Room Allocation and Data Structures
The relay maintains a thread-safe roomMap (defined in src/tcp/tcp.go) that stores metadata for every active transfer session. Each entry maps a room identifier to a roomInfo struct containing:
firstandsecondpointers to*comm.Commobjects representing the two connected clients- A
fullboolean flag indicating whether both peers have joined - An
openedtimestamp tracking room creation time
Access to this map is guarded by a sync.Mutex embedded in the roomMap structure, ensuring safe concurrent modifications as multiple goroutines handle incoming connections.
First and Second Client Logic
The relay distinguishes between the sender (first client) and receiver (second client) through deterministic room assignment.
First Client – Room Creation
If the room identifier derived from the code phrase does not exist in roomMap, the relay creates a new roomInfo entry with the connecting client stored as first (lines 66-71). It marks the creation time and replies with an encrypted "ok" to signal that the client now owns the room (lines 74-80).
Second Client – Join Existing Room
When a subsequent connection arrives with a matching room name, the relay retrieves the existing entry and checks the full flag. If the room is not full, it stores the new client as second, sets full to true, and transmits an encrypted "ok" confirmation (lines 87-100). Once both sides are present, the room transitions to data-forwarding mode.
Bidirectional Data Forwarding
After pairing, the relay acts as a transparent pipe between the two comm.Comm connections. The server spawns dedicated goroutines for each client that:
- Periodically transmit keep-alive bytes (
[]byte{1}) to the first client to prevent timeouts - Block on read operations, immediately forwarding any received data to the opposite peer through the
comm.Commabstraction
This design effectively bridges the two TCP streams without decrypting the payload, maintaining end-to-end encryption while managing the underlying transport.
Health Checks and Room Lifecycle
The relay implements automatic resource reclamation through TTL-based expiration. Configuration constants defined in src/tcp/defaults.go specify DEFAULT_ROOM_TTL and DEFAULT_ROOM_CLEANUP_INTERVAL, determining how long rooms persist and how frequently the sweep runs.
Background Cleanup
A goroutine running deleteOldRooms (lines 30-38 in src/tcp/tcp.go) executes every roomCleanupInterval, iterating through roomMap to remove entries where opened exceeds roomTTL. During server shutdown, a stop context triggers deleteRoom to forcibly close all active sockets and purge the map.
Health-Check Endpoint
The reserve room name pinglkasjdlfjsaldjf enables external monitoring. Clients sending "ping" to this room receive an immediate "pong" response before the connection closes (lines 88-94), allowing load balancers to verify relay liveness without creating persistent sessions.
Concurrency Safety
To handle high connection volumes, the relay spawns a new goroutine for every incoming TCP connection. Each goroutine acquires the roomMap mutex only during read or write operations on the shared state, minimizing lock contention. This pattern ensures that room lookups and modifications remain atomic while maximizing throughput for simultaneous transfers.
Practical Usage Example
The following commands demonstrate the room lifecycle in a local environment:
# Start a local relay on 0.0.0.0:9009
croc relay
# Sender creates a room (derived from "secretcode")
croc send --code "secretcode" myfile.txt
# Receiver joins the same room to establish the pipe
croc recv --code "secretcode"
When the receiver executes the final command, the relay matches both connections in the same room entry and begins forwarding encrypted data between the peers.
Summary
- croc's TCP relay authenticates clients using PAKE (
pake.InitCurve) before admitting them to rooms. - Rooms are tracked in a mutex-protected
roomMapstoringroomInfostructs withfirst,second, andfullfields. - The first client creates the room; the second client joins and seals it, triggering bidirectional forwarding.
- Background goroutines enforce TTL-based expiration via
deleteOldRoomsand support health checks through the reservedpinglkasjdlfjsaldjfroom name. - All map accesses are synchronized with
sync.Mutexto ensure safe concurrent operations across per-connection goroutines.
Frequently Asked Questions
How does croc's TCP relay authenticate connecting clients?
The relay performs a PAKE-based handshake using pake.InitCurve as implemented in src/tcp/tcp.go. After establishing a shared secret, the client must send the correct relay password; the server validates this and replies with an encrypted "ok" banner before allowing room operations.
What data structure does the relay use to track active rooms?
The relay maintains a thread-safe roomMap that maps room identifiers to roomInfo structs. Each struct contains two *comm.Comm pointers (first and second), a full boolean, and an opened timestamp, all protected by an embedded sync.Mutex.
How does the relay clean up inactive rooms?
A background goroutine runs deleteOldRooms every roomCleanupInterval (configured in src/tcp/defaults.go), removing entries where the opened timestamp exceeds roomTTL. During shutdown, the server triggers deleteRoom to close all sockets and clear the map immediately.
What is the purpose of the pinglkasjdlfjsaldjf room name?
This reserved room name provides a health-check endpoint for monitoring tools. When a client sends "ping" to this specific room, the relay responds with "pong" and closes the connection, enabling external verification that the relay is accepting TCP connections and processing logic without creating persistent room states.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →