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

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 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 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):

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:

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:

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 – Contains setupLocalRelay, broadcastOnLocalNetwork, and discoverReceivePeers, implementing the core discovery logic and client coordination.
  • 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 – 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →