---
title: Discover peers
description: Activate signed presence beacons or local-link mDNS with a bounded peer book and automatic dialing.
---

Choose the source that matches where peers meet:

- Use mDNS for peers on the same local network.
- Use signed discovery when peers already share a pubsub mesh.
- Enable both when the application needs both sources.

Both sources feed one bounded peer book and one automatic-dial policy.

## Enable signed discovery

```bash
cargo add minip2p-rs --features discovery
```

Signed discovery publishes application-scoped presence beacons over pubsub:

```rust
let mut node = minip2p::Endpoint::builder()
    .discovery()
    .listen_default()?
    .bind()?;

node.listen_all()?;
```

`.discovery_config(...)` selects a custom topic, publication cadence, and announcement limit. Tune the shared address book and dial policy with `.peer_discovery_config(...)`.

Discovery can dial direct addresses announced by other peers without a relay. A configured relay is still required for relay circuits, reservations, and relay-assisted hole punching:

```rust
let node = minip2p::Endpoint::builder()
    .relay(relay)
    .discovery()
    .listen_default()?
    .bind()?;
```

## Enable local mDNS

```bash
cargo add minip2p-rs --features mdns
```

Then enable it on the endpoint:

```rust
let node = minip2p::Endpoint::builder()
    .mdns()
    .listen_default()?
    .bind()?;
```

mDNS needs no pubsub or relay. Its observations are unauthenticated until the transport verifies the remote peer, and its automatic dials remain direct-only. `KnownPeer::beacon_addrs` and `KnownPeer::mdns_addrs` show where each address came from.

An invalid configuration passed to `mdns_config` returns `MdnsConfigError` immediately. If mDNS sockets cannot be opened during `bind`, it returns `BindError::MdnsSocket`. Once running, an mDNS socket failure is `EndpointEvent::MdnsFailed { error }`, not a `wait` error: mDNS keeps retrying, the rest of the endpoint is unaffected, and `EndpointEvent::MdnsRecovered` follows once a retry works. Call `shutdown()` to send goodbyes and stop mDNS; it returns the first goodbye that could not be sent.

Enable both methods on the same builder when the application needs LAN and pubsub-based discovery.

## How peers appear

Each endpoint periodically publishes a signed beacon containing its current dialable address snapshot. Discovery:

1. verifies the beacon identity and signature;
2. normalizes and bounds the advertised addresses;
3. updates the TTL-based known-peer book;
4. starts or cancels NAT-aware dials according to discovery policy.

Address-less beacons can refresh peer presence without starting a dial. Wildcard listen addresses are filtered because another peer cannot dial them.

## Observe the address book

```rust
for peer in node.known_peers() {
    println!(
        "peer={} connected={} addresses={:?}",
        peer.peer,
        peer.connected,
        peer.addrs,
    );
}

loop {
    match node.wait(std::time::Duration::from_millis(250))? {
        minip2p::EndpointWaitOutcome::Event(minip2p::EndpointEvent::Discovery(event)) => {
            println!("{event:?}");
        }
        minip2p::EndpointWaitOutcome::Event(other) => println!("{other:?}"), // dispatch as usual
        _ => {} // deadline or interruption
    }
}
```

`EndpointEvent::Discovery(DiscoveryEvent)` reports peers being discovered, updated, or expired, plus automatic dial failures and rejected beacons. `known_peers` is a State snapshot and may be ahead of these events.

## Customize policy

```rust
use minip2p::{BeaconConfig, Endpoint, PeerDiscoveryConfig};

let beacon = BeaconConfig {
    topic: "my-app/discovery/v1".into(),
    ..BeaconConfig::default()
};
let policy = PeerDiscoveryConfig {
    max_known_peers: 256,
    ..PeerDiscoveryConfig::default()
};

let node = Endpoint::builder()
    .discovery_config(beacon)?
    .peer_discovery_config(policy)?
    .listen_default()?
    .bind()?;
```

`BeaconConfig` controls the pubsub topic, cadence, and local announcement cap. `PeerDiscoveryConfig` is shared by signed discovery and mDNS; it controls peer and address bounds, source TTLs, automatic dialing, tie breaking, retry backoff, and mDNS dial-rate limits. Invalid bounds are rejected before a socket is allocated.

> **Warning**
>
> Discovery beacons are always required to be signed. Unsigned discovery beacons are rejected even when application pubsub was configured with `allow_unsigned: true`.

> **Note**
>
> minip2p owns the discovery topic. Manually subscribing is redundant, and its pubsub messages are consumed before reaching application pubsub events. Calling `unsubscribe` for that topic returns `GossipsubError::DiscoveryTopicReserved`.

For a complete mesh demonstration, use the existing [`minip2p-chat` example](https://github.com/deepso7/minip2p/tree/main/examples/chat). It combines discovery, gossipsub, relay circuits, and direct upgrades.
