---
title: Rust troubleshooting
description: Diagnose Rust connection, stream, discovery, and event problems by symptom.
---

Start from the observable symptom. Building an endpoint fails with a `BindError` (or an `EndpointOptionsError` from a listen setter) naming the part that failed; methods on a running endpoint return `minip2p::Error`. Problems found while driving arrive as events.

## Common problems

| Symptom | Likely cause | What to do |
| --- | --- | --- |
| `listen_on` or `bind` fails with an `EndpointOptionsError` (`BindError::Options`) | A listen address is not listenable (`/p2p`, `/p2p-circuit`, a DNS host, or a transport whose Cargo feature is off), or the options conflict (no listen address, two QUIC addresses for one IP family) | The variant names the address and what was expected; fix the options. |
| `bind` fails with `BindError::Quic` or `BindError::Listen` | The platform refused a socket | Match the error's `IoErrorKind`: `AddrInUse` means another process holds the port, `PermissionDenied` a privileged port or sandbox. |
| `PeerAddr` fails to parse | The address lacks a terminal `/p2p/<peer-id>` | Copy the complete peer address. |
| `connect` returns `ConnectTargetError` | The address list was empty, or its addresses name different peers | Pass one peer's complete addresses. |
| `ConnectSettled` fails with `NoUsableRoute` | No candidate had a usable shape or transport, and no relay applied | Use `/udp/<port>/quic-v1` or `/tcp/<port>` and listen on that transport, or configure a relay. |
| A remote peer cannot connect to a printed address | The address contains `0.0.0.0` or `::` | Advertise a real interface, public, or relay address. |
| A connection exists but an application protocol cannot open | Protocol unregistered, or remote rejects negotiation | Confirm matching protocol IDs and keep both peers driving. `PeerReady` is optional; it only enables early `RemoteDoesNotSupport`. |
| `ProtocolNotRegistered` | The local endpoint never registered the protocol | Add `.protocol("/app/name/1.0.0")` or call `add_protocol`. |
| `RemoteDoesNotSupport` | The peer does not advertise the protocol version | Register the same version on both peers. |
| An optional capability reports "not enabled" | The Cargo feature exists, but the builder did not activate it | Add the builder method listed in the feature matrix. |
| AutoNAT reports reachability but `connect` cannot relay | Only an AutoNAT server was configured | Add a Circuit Relay v2 server with `.relay(...)`. |
| Hole punching fails | UDP paths or observed addresses are unusable | Keep the relay path, inspect `HolePunchFailed`, and keep both peers driving. |
| Pubsub receives no local copy | Self-delivery is disabled | Apply the local action directly and use pubsub for remote delivery. |
| Discovery rejects a beacon | Discovery beacons must be signed | Fix the publishing peer. `allow_unsigned` does not apply to discovery. |
| mDNS finds no peers | mDNS is inactive, multicast is blocked, or peers do not share a link | Activate mDNS on both peers and test on a multicast-capable network. Watch for `EndpointEvent::MdnsFailed`. |

## Event responses

| Event | Typical response |
| --- | --- |
| `EndpointEvent::PeerReady` | Record advertised protocols, or open streams if you waited for Identify first. |
| `EndpointEvent::StreamReady` | Attach the stream to its protocol handler. |
| `EndpointEvent::StreamData` | Feed the bytes into the application's decoder. |
| `EndpointEvent::StreamRemoteWriteClosed` | Finish decoding and optionally close the local write side. |
| `EndpointEvent::StreamWriteStopped` | Stop writing: the peer refused further bytes (QUIC STOP_SENDING). Keep reading until `StreamClosed`. |
| `EndpointEvent::Error` | Record the structured context and decide whether to retry. |
| `EndpointEvent::MdnsFailed` | Log the `MdnsError` (operation, `IoErrorKind`, interface). mDNS keeps retrying and emits `MdnsRecovered` once it works again. |
| `RelayServerEvent::AddrsRejected` | Fix the listener or confirmed address it names. The previously accepted addresses stay in effect until `AddrsRestored`. |
| `NatEvent::PathEstablished` | Begin traffic with the ordinary stream methods. |
| `NatEvent::PathUpgraded` | Update path state. Existing peer operations keep working. |
| `EndpointEvent::ConnectSettled` with `ConnectOutcome::Failed` | Inspect the failure, correct the candidates or infrastructure, and retry. |
| `GossipsubEvent::OutboundFailure` | Retry only if the application requires it. |
| `DiscoveryEvent::DialFailed` | Keep discovery running. Backoff controls later attempts. |

## Debug in order

1. Confirm that both endpoints keep calling `wait` or `poll`. Each call drives only its own endpoint, so blocking on one endpoint can delay others sharing the same thread.
2. Log the complete peer address and local peer ID.
3. Distinguish an authenticated connection from `PeerReady` (Identify).
4. Confirm both the Cargo feature and its builder method.
5. Match the capability's `EndpointEvent` variant (`Nat`, `Gossipsub`, `Discovery`, `RelayServer`, `MdnsFailed`) from `wait` or `poll`, and check durable state with the snapshot getters (`path`, `reachability`, `active_reservation`, `known_peers`).

For the complete capability map, see the [feature matrix](/docs/reference/feature-matrix).
