# How Croc's Multicast Local Discovery Works

> Discover how Croc's multicast local discovery enables direct peer-to-peer connections on your LAN. Learn about broadcast packets, public keys, and token transfers without relays.

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

---

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

```bash

# 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`](https://github.com/schollz/croc/blob/main/src/croc/croc.go), where the networking layer calls `net.ListenMulticastUDP` (or equivalent group join operations via [`src/comm/comm.go`](https://github.com/schollz/croc/blob/main/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`](https://github.com/schollz/croc/blob/main/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`](https://github.com/schollz/croc/blob/main/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:

```bash
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:

```go
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:

```go
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`](https://github.com/schollz/croc/blob/main/src/cli/cli.go) | Defines the `--multicast` flag and populates `Settings.MulticastAddress` (lines 147-154). |
| [`src/croc/croc.go`](https://github.com/schollz/croc/blob/main/src/croc/croc.go) | Contains the `Settings` struct, discovery packet construction, and TCP connection logic (`net.DialTCP`). |
| [`src/comm/comm.go`](https://github.com/schollz/croc/blob/main/src/comm/comm.go) | Implements low-level UDP socket operations, including `ListenMulticastUDP` and packet read/write routines. |
| [`src/utils/utils.go`](https://github.com/schollz/croc/blob/main/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.250` by default, configurable via the `--multicast` flag.
- **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`](https://github.com/schollz/croc/blob/main/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.