Skip to content

initiate_session: add retry with backoff and progress feedback #12

@rafabd1

Description

@rafabd1

Description

initiate_session (the command called when a user pastes an ech0:// link) makes a single connection attempt to the peer. If the I2P tunnels are degraded — which is common, especially in the first few minutes after bootstrap — the attempt fails and the user gets a generic error with no option to retry automatically.

I2P connections are inherently unreliable on the first attempt. Tunnels may be in the process of being rebuilt, the peer's lease set might not have propagated yet, or intermediate hops may reject the tunnel build (reason=30 "bandwidth" as seen in logs). A single-shot approach is not appropriate for this network.

Current behavior

In session.rs, initiate_session (line ~326) does:

// 1. Single connection attempt — no retry
let tunnel = session.connect_to_peer(&peer.dest).await?;

// 2. Single handshake attempt — no retry
write_framed(&mut writer, &init_msg).await?;
let ack_frame = read_framed(&mut reader).await?;

If any step fails, the error propagates to the frontend and the user must manually try again.

What needs to change

1. Add retry loop with exponential backoff around the full dial+handshake

Wrap the entire sequence (SAM STREAM CONNECT + write_framed(INIT) + read_framed(ACK)) in a retry loop:

  • Max attempts: 3–4 (I2P latency means more than that is wasteful)
  • Backoff: start at ~5s, increase to ~10s, then ~15s — giving tunnels time to rebuild between attempts
  • Per-attempt timeout: 45–60s (see issue Peer connection hangs indefinitely: missing timeouts on handshake I/O over degraded I2P tunnels #11) covering the whole handshake within a single attempt
  • Each retry should open a fresh SAM STREAM CONNECT since the previous tunnel may be dead
  • The ephemeral key (ek_a) must be regenerated on each attempt — reusing it after a failed attempt where the peer may have partially processed the handshake would be a security issue

2. Emit progress events to the frontend

The user should know what's happening during retries. Add a new event:

app.emit("connection_progress", serde_json::json!({
    "attempt": attempt,
    "max_attempts": MAX_ATTEMPTS,
    "status": "retrying"  // or "connecting", "failed"
}))?;

The frontend should listen for connection_progress and show a status like:

"Connecting to peer... (attempt 2/4)"

3. Frontend: show progress in SessionSetup

SessionSetup currently calls invoke("initiate_session") and only shows a spinner or error. It should:

  • Listen for connection_progress events while connecting
  • Show which attempt is in progress
  • Show a meaningful error on final failure (e.g., "Could not reach peer after 4 attempts — their node may be offline or still bootstrapping")

4. Distinguish error types for the user

Not all failures are the same. Map errors to user-friendly messages:

  • STREAM CONNECT failed: RESULT=CANT_REACH_PEER → "Peer is unreachable — they may be offline"
  • STREAM CONNECT failed: RESULT=TIMEOUT → "Connection timed out — I2P tunnels may be building"
  • Handshake timeout → "Connected but handshake failed — retrying"

Notes

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions