block/buzz · error

mesh endpoint bind on {} failed: {e}

Error message

mesh endpoint bind on {} failed: {e}

What it means

boot_mesh (mesh_boot.rs) runs only when BUZZ_MESH is on/true/1 (strict opt-in, config.rs:605). It binds an iroh QUIC endpoint — MeshEndpoint::bind (endpoint.rs:19) builds Endpoint with the given bind_addr and RelayMode::Disabled, and both builder and bind errors collapse into MeshError::Transport(String) — on BUZZ_MESH_BIND_ADDR, default 0.0.0.0:3478 (UDP). This error wraps that failure with the address. Failure is fatal by design: an operator who asked for the mesh gets it or gets told why not.

Source

Thrown at crates/buzz-relay/src/mesh_boot.rs:426

/// sets `BUZZ_MESH=on` wants the mesh or wants to know why not; silently
/// booting meshless would be the same class of bug as silently dropping to a
/// default tenant.
pub async fn boot_mesh(
    config: &Config,
    redis_pool: deadpool_redis::Pool,
    db: buzz_db::Db,
    relay_keypair: &nostr::Keys,
    shutting_down: Arc<AtomicBool>,
) -> anyhow::Result<Option<MeshHandle>> {
    if !config.mesh.enabled {
        tracing::info!("mesh disabled (BUZZ_MESH is not 'on') — single-instance behavior");
        return Ok(None);
    }

    let endpoint = MeshEndpoint::bind(config.mesh.bind_addr)
        .await
        .map_err(|e| {
            anyhow::anyhow!(
                "mesh endpoint bind on {} failed: {e}",
                config.mesh.bind_addr
            )
        })?;
    let runtime_id = endpoint.runtime_id();
    let addrs = advertise_addrs(&endpoint);
    tracing::info!(
        runtime_id = %runtime_id,
        bind_addr = %config.mesh.bind_addr,
        advertise_addrs = ?addrs,
        "mesh endpoint bound"
    );

    let mut local_record = GossipRecord::new(runtime_id, addrs.clone(), PROTO_VERSION);
    local_record.capabilities = capabilities();
    // Anchor ready-record acceptance to this deployment's relay identity: all
    // pods share the relay signing key, so a seed attested by any other key is
    // foreign and rejected (Wren's review — possession is not authorization).

View on GitHub (pinned to f956e6fe06)

Solutions

  1. Find who holds the UDP port: ss -lunp 'sport = :3478' — stop that process or move the mesh elsewhere.
  2. Set BUZZ_MESH_BIND_ADDR to a free UDP port and open it in firewalls/security groups so peers can dial it.
  3. If the mesh was not intended, unset BUZZ_MESH (anything other than on/true/1 keeps exact single-instance behavior).
  4. In containers, ensure UDP is allowed by securityContext/NetworkPolicy and the bind IP exists on an interface.

Example fix

# before: BUZZ_MESH=on with the default bind — coturn already owns UDP 3478
#   Error: mesh endpoint bind on 0.0.0.0:3478 failed: ...

# after
BUZZ_MESH=on
BUZZ_MESH_BIND_ADDR=0.0.0.0:3479
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: confirm the mesh UDP port is free before booting with BUZZ_MESH=on.
fn udp_port_available(addr: std::net::SocketAddr) -> bool {
    std::net::UdpSocket::bind(addr).is_ok()
}

if let Ok(mesh) = std::env::var("BUZZ_MESH") {
    if mesh.eq_ignore_ascii_case("on") || mesh == "true" || mesh == "1" {
        let bind: std::net::SocketAddr = std::env::var("BUZZ_MESH_BIND_ADDR")
            .unwrap_or_else(|_| "0.0.0.0:3478".into())
            .parse()
            .expect("BUZZ_MESH_BIND_ADDR must parse");
        assert!(udp_port_available(bind), "mesh UDP {bind} busy — often coturn/STUN on 3478");
    }
}

Type guard

fn is_mesh_bind_error(e: &anyhow::Error) -> bool {
    e.to_string().starts_with("mesh endpoint bind")
}

Try / catch

let endpoint = match MeshEndpoint::bind(config.mesh.bind_addr).await {
    Ok(ep) => ep,
    Err(e) => {
        tracing::error!(addr = %config.mesh.bind_addr, %e,
            "mesh bind failed — check `ss -lunp 'sport = :3478'` for a STUN/TURN holder");
        return Err(anyhow!("mesh endpoint bind on {} failed: {e}", config.mesh.bind_addr));
    }
};

Prevention

When it happens

Trigger: BUZZ_MESH=on while UDP 3478 is already taken — 3478 is the standard STUN port, so coturn/turnserver or another relay replica commonly owns it; container seccomp/firewall denying UDP socket creation; BUZZ_MESH_BIND_ADDR naming an IP that does not exist on the host.

Common situations: Co-locating the relay with a TURN server (both default to 3478); k8s hostNetwork pods; multiple mesh-enabled replicas on one node without distinct BUZZ_MESH_BIND_ADDR; restrictive container securityContext blocking UDP.

Related errors


AI-assisted analysis of block/buzz@f956e6fe06 (2026-08-16). Data as JSON: /api/errors/bc2bee53f111cbf9. Report an issue: GitHub.