# How croc's TCP Relay Manages Rooms and Connections

> Discover how croc's TCP relay manages rooms and connections. It uses synchronized maps and background goroutines for secure, authenticated data transfer and automatic session expiry.

- Repository: [Zack/croc](https://github.com/schollz/croc)
- Tags: internals
- Published: 2026-07-23

---

**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](https://github.com/schollz/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`](https://github.com/schollz/croc/blob/main/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`](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go)) that stores metadata for every active transfer session. Each entry maps a room identifier to a `roomInfo` struct containing:

- **`first`** and **`second`** pointers to `*comm.Comm` objects representing the two connected clients
- A **`full`** boolean flag indicating whether both peers have joined
- An **`opened`** timestamp 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:

1. Periodically transmit keep-alive bytes (`[]byte{1}`) to the first client to prevent timeouts
2. Block on read operations, immediately forwarding any received data to the opposite peer through the `comm.Comm` abstraction

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`](https://github.com/schollz/croc/blob/main/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`](https://github.com/schollz/croc/blob/main/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:

```bash

# 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 `roomMap` storing `roomInfo` structs with `first`, `second`, and `full` fields.
- The first client creates the room; the second client joins and seals it, triggering bidirectional forwarding.
- Background goroutines enforce TTL-based expiration via `deleteOldRooms` and support health checks through the reserved `pinglkasjdlfjsaldjf` room name.
- All map accesses are synchronized with `sync.Mutex` to 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`](https://github.com/schollz/croc/blob/main/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`](https://github.com/schollz/croc/blob/main/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.