How Croc's Multicast Local Discovery Works
Croc uses UDP multicast on a well-known group address (default 239.255.255.250) to broadcast discovery packets containing the sender's public key, TCP port, and transfer token, allowing peers on the same LAN to connect directly without requiring a public relay server.
The schollz/croc repository implements a zero-configuration multicast local discovery mechanism that enables file transfers between machines on the same subnet without manual IP address entry. This system allows croc instances to discover each other automatically by joining a shared multicast group and exchanging small UDP packets before establishing direct TCP connections.
The Discovery Flow
Croc's multicast implementation follows a six-step process that bridges the gap between application startup and direct peer-to-peer communication.
Enabling Multicast via CLI
The discovery process begins with the --multicast command-line flag defined in src/cli/cli.go. When a user specifies this flag, croc stores the provided address (defaulting to 239.255.255.250) in the Settings.MulticastAddress field.
# Sender broadcasts discovery on the LAN
croc send --multicast 239.255.255.250 large-video.mkv
# Receiver listens on the same multicast group
croc receive --multicast 239.255.255.250
Joining the Multicast Group
When a croc instance initializes, it creates a UDP socket and joins the multicast group specified in Settings.MulticastAddress. This implementation resides in src/croc/croc.go, where the networking layer calls net.ListenMulticastUDP (or equivalent group join operations via src/comm/comm.go) to become a member of the multicast group. All instances with multicast enabled listen on the same address, creating a shared communication channel.
Broadcasting Discovery Packets
The sender constructs a small discovery packet containing three critical pieces of information: its public key (or hash), its TCP listening port, and the transfer token. This packet is sent via conn.WriteTo to the multicast address on port 5000. Because UDP is connectionless, this single transmission reaches every croc instance listening on the LAN segment simultaneously.
Receiving and Validating Packets
Listeners in src/croc/croc.go receive the multicast packet through their joined socket interfaces. The code decodes the payload, verifies that the transfer token matches the expected format, and extracts the sender's advertised TCP address. This validation step ensures that only intended recipients—those knowing the transfer token—proceed to connection establishment.
Establishing Direct TCP Connections
Upon successful validation, each listener initiates a direct TCP connection back to the sender using net.DialTCP with the address extracted from the discovery packet. This bypasses the need for NAT traversal or external relay servers, as both endpoints communicate directly on the local subnet.
Handling Link-Local Addresses
A critical implementation detail appears in src/utils/utils.go (lines 456-462), where link-local addresses are explicitly excluded from the standard LocalIP list. As noted in the source comments, these addresses are "discovered through multicast instead because dialing them also requires [zone identifiers]." This ensures that IPv6 link-local addresses and similar interface-scoped IPs are handled correctly through the multicast mechanism rather than standard discovery methods.
Why Multicast for Local Discovery?
Croc employs multicast UDP rather than other discovery methods for three specific technical advantages:
- Zero-configuration networking – Peers locate each other without pre-shared IP addresses or DNS configuration.
- Subnet efficiency – A single UDP packet reaches all listening instances, minimizing network overhead compared to scanning or broadcasting.
- NAT traversal avoidance – For machines on the same subnet, direct TCP connections work without relay servers, STUN, or port forwarding.
Security Considerations
The multicast discovery packet intentionally contains minimal information: only the public key (or its hash) and the temporary transfer token. No file metadata or content traverses the multicast channel. The TCP connection is established only after cryptographic verification of the token, preventing unauthorized devices from connecting to the sender even if they receive the multicast packet.
Implementation Examples
CLI Configuration
Enable multicast discovery using the default address:
croc send --multicast 239.255.255.250 document.pdf
Both sender and receiver must specify the same multicast address to join the group.
Programmatic Configuration in Go
Configure multicast programmatically using the croc library:
import (
"github.com/schollz/croc/v10/src/croc"
)
func main() {
settings := croc.Settings{
MulticastAddress: "239.255.255.250",
// Additional fields: Mode, Relay, etc.
}
c, err := croc.New(settings)
if err != nil {
log.Fatal(err)
}
// Automatically broadcasts discovery packets
err = c.Send("path/to/file.txt")
}
The croc.New constructor handles UDP socket creation, multicast group joining, and packet transmission internally.
Manual Packet Construction
For advanced networking scenarios, you can manually send discovery packets:
addr, _ := net.ResolveUDPAddr("udp", "239.255.255.250:5000")
conn, _ := net.DialUDP("udp", nil, addr)
// Payload contains serialized token and TCP port
payload := []byte{ /* serialized discovery data */ }
conn.Write(payload)
Key Source Files
Understanding croc's multicast implementation requires familiarity with these specific files:
| File | Role in Multicast Discovery |
|---|---|
src/cli/cli.go |
Defines the --multicast flag and populates Settings.MulticastAddress (lines 147-154). |
src/croc/croc.go |
Contains the Settings struct, discovery packet construction, and TCP connection logic (net.DialTCP). |
src/comm/comm.go |
Implements low-level UDP socket operations, including ListenMulticastUDP and packet read/write routines. |
src/utils/utils.go |
Handles local IP enumeration and excludes link-local addresses from standard discovery (lines 456-462). |
Summary
- Multicast address – Croc uses
239.255.255.250by default, configurable via the--multicastflag. - Discovery packet – Contains public key, TCP port, and transfer token; sent via UDP to port
5000. - Direct connection – Peers establish TCP connections immediately after multicast discovery, bypassing external relays.
- Link-local handling – Interface-scoped addresses are discovered via multicast rather than standard IP enumeration.
- Security – Discovery packets expose only cryptographic identifiers; actual file transfers occur over encrypted TCP connections.
Frequently Asked Questions
What is the default multicast address used by croc?
Croc uses 239.255.255.250 as the default multicast group address, which is a reserved administratively scoped IPv4 multicast address. This can be overridden using the --multicast flag in the CLI or by setting the MulticastAddress field in the Go API.
How does croc handle IPv6 link-local addresses during discovery?
According to the source code in src/utils/utils.go (lines 456-462), link-local addresses are intentionally excluded from the standard local IP list. Instead, these addresses are discovered through the multicast mechanism, as dialing them requires zone identifiers that the multicast discovery process handles automatically.
Is multicast discovery secure for local networks?
Yes. The multicast packet contains only the sender's public key hash and the transfer token—no file data or metadata. The actual TCP connection requires token verification, ensuring that only peers with the correct transfer token can establish a connection, even if they receive the multicast packet.
Why does croc use multicast instead of IP broadcasting?
Multicast is more efficient than broadcasting because it targets only hosts that have explicitly joined the multicast group, reducing unnecessary network traffic. Additionally, multicast works across different network segments where broadcasting might be blocked by routers, and it provides a cleaner mechanism for handling link-local and IPv6 addresses.
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 →