# How croc Handles Local Relay and Peer Discovery: A Deep Dive into the Source Code

> Explore croc source code to understand its local relay and peer discovery process. Learn how it establishes direct LAN connections and falls back to public relays.

- Repository: [Zack/croc](https://github.com/schollz/croc)
- Tags: deep-dive
- Published: 2026-07-26

---

**When transferring files, croc first attempts to establish a direct LAN connection by spawning an ephemeral local relay, broadcasting its presence via multicast UDP, and allowing receivers to discover and ping the sender before falling back to public relays.**

Understanding the **local relay and peer discovery in croc** is essential for users who need fast, offline-capable file transfers on local networks. According to the `schollz/croc` source code, the implementation prioritizes direct LAN connections through a sophisticated three-phase discovery protocol that automatically degrades to public relays when local paths fail.

## Setting Up the Ephemeral Local Relay

When a sender initiates a transfer without the `--disable-local` flag, the `Client.setupLocalRelay` function in [`src/croc/croc.go`](https://github.com/schollz/croc/blob/main/src/croc/croc.go) orchestrates the local infrastructure. This process allocates a set of free TCP ports bound to `127.0.0.1`, designates the first available port as the control port (`localRelayPort`), and launches relay instances for each port in separate goroutines.

The control port is immediately saved to `c.localRelayPort` before the `RelayPorts` slice can be modified by concurrent operations. This ephemeral relay acts as a bridge, listening for incoming connections while the sender prepares to announce its presence on the network.

## Broadcasting Presence on the Local Network

With the local relay active, the sender enters a discovery broadcast loop via `Client.broadcastOnLocalNetwork`. This function constructs multicast packets containing the payload `"croc"+c.localRelayPort` and transmits them repeatedly across the local network.

By default, croc uses the IPv4 multicast address **239.255.255.250**, though this can be overridden with the `--multicast` flag (e.g., `224.0.0.1` for constrained networks) or forced to IPv6 when `broadcastOnLocalNetwork(true)` is invoked. The discovery mechanism leverages the `github.com/schollz/peerdiscovery` library, configured with a `peerdiscovery.Settings` struct that defines a **30-second `TimeLimit`** for standard operations or runs indefinitely when `OnlyLocal` mode is enabled.

## Receiver Peer Discovery and Connection

When the receiver starts via `Client.Receive`, it immediately executes `discoverReceivePeers` to scan for local senders. This function launches two parallel discovery attempts—one for IPv4 and one for IPv6—each transmitting a minimal `"ok"` payload with a strict **0.5-second timeout**.

If any discovery response begins with the `"croc"` prefix, the receiver extracts the embedded port number, combines it with the peer's IP address, and validates reachability through `tcp.PingServer`. Upon successful verification, the receiver updates its `RelayAddress` to the discovered local endpoint and sets `usingLocal = true`, ensuring the entire transfer proceeds through the LAN without touching the public internet.

## Fallback Mechanism and Transfer Completion

If the receiver exhausts its discovery attempts without finding a reachable local peer—either due to network segmentation, multicast filtering, or ping failures—it automatically proceeds to connect to the configured public relay (`c.Options.RelayAddress`). Meanwhile, the sender continues listening on its local relay ports; if a receiver connects locally, the WebSocket-to-TCP bridge in [`src/webrelay/webrelay.go`](https://github.com/schollz/croc/blob/main/src/webrelay/webrelay.go) forwards the raw byte stream directly to the relay host, completing the transfer entirely within the local network.

## Practical Usage Examples

**Enable local discovery when sending (default behavior):**

```bash
croc send --relay "" --disable-local=false bigfile.zip

```

This command spawns a local relay, broadcasts the discovery packet, and waits for a receiver to find it on the LAN.

**Force local-only mode on the receiver:**

```bash
croc receive --only-local

```

The receiver will exclusively use peer discovery. If no local peer is found within the timeout period, the command aborts with an error rather than falling back to public relays.

**Use a custom multicast address:**

```bash
croc send --multicast 224.0.0.1 huge.iso
croc receive --multicast 224.0.0.1

```

Both parties must specify identical multicast addresses for the discovery packets to reach their destination.

## Key Source Files

- **[`src/croc/croc.go`](https://github.com/schollz/croc/blob/main/src/croc/croc.go)** – Contains `setupLocalRelay`, `broadcastOnLocalNetwork`, and `discoverReceivePeers`, implementing the core discovery logic and client coordination.
- **[`src/webrelay/webrelay.go`](https://github.com/schollz/croc/blob/main/src/webrelay/webrelay.go)** – Houses the embedded web server and WebSocket-to-TCP bridge that enables browser-based and local relay connections.
- **[`src/utils/utils.go`](https://github.com/schollz/croc/blob/main/src/utils/utils.go)** – Provides networking utilities including port availability checks and ping functions used during peer validation.
- **`go.mod`** – Declares the `github.com/schollz/peerdiscovery` dependency that powers the multicast discovery mechanism.

## Summary

- **Ephemeral relay creation**: The sender invokes `setupLocalRelay` to bind free TCP ports and launch local relay instances before any network announcement occurs.
- **Multicast announcement**: `broadcastOnLocalNetwork` continuously transmits `"croc"+port` payloads to the LAN multicast address (default `239.255.255.250`) for 30 seconds or indefinitely in local-only mode.
- **Active discovery**: Receivers run `discoverReceivePeers` with parallel IPv4/IPv6 scans, validating candidates via `tcp.PingServer` before committing to a local connection.
- **Automatic fallback**: If local discovery fails, the system transparently routes traffic through the public relay defined in `c.Options.RelayAddress`.
- **Zero-configuration networking**: The entire process requires no manual IP entry, leveraging multicast UDP and ephemeral port allocation to establish direct LAN connections.

## Frequently Asked Questions

### How does croc discover peers on the same network without knowing their IP addresses?

Croc utilizes multicast UDP packets sent to the address `239.255.255.250` (or a user-specified alternative). The sender's `broadcastOnLocalNetwork` function transmits payloads containing the relay port, while the receiver's `discoverReceivePeers` listens for these packets across IPv4 and IPv6 simultaneously, extracting the port and sender IP from any valid response beginning with `"croc"`.

### What happens if multicast traffic is blocked on my network?

If multicast packets cannot traverse the network—common in corporate environments with strict firewall rules—the receiver's 0.5-second discovery timeout will expire without finding a local peer. In this scenario, croc automatically falls back to connecting through the public relay specified by `c.Options.RelayAddress`, ensuring the transfer can still proceed via the internet.

### Can I force croc to only use local transfers and fail if no peer is found?

Yes, by passing the `--only-local` flag when receiving. This sets `OnlyLocal` to true in the discovery settings, causing `broadcastOnLocalNetwork` to run with an unlimited time limit and preventing any fallback to public relays. If no local peer responds, the application terminates with an error rather than attempting external connections.

### Why does the sender use `127.0.0.1` for the local relay instead of the machine's LAN IP?

The local relay binds to `127.0.0.1` for security and simplicity, creating a listening socket that accepts connections forwarded from the broader network interface. The actual LAN IP is communicated through the multicast discovery payload (`"croc"+port`), allowing receivers to calculate the correct address while the relay itself remains isolated from direct external binding, reducing attack surface.