Skip to content

Flow Direct Connection

Tobias Vorwachs edited this page Oct 26, 2026 · 1 revision

Part of the Plugin data protocol. Packet: Packet-mumcord-p2p.

Flow: direct connection

Two Mumcord clients open a direct, encrypted QUIC connection for large data. Plugin data carries only the introduction; the data itself never touches the Mumble server. Users: Flow-Full-Image and Flow-Screen-Share.

sequenceDiagram
    participant A as Client A
    participant S as Mumble server
    participant B as Client B
    A->>S: plugin data mumcord.p2p offer (s, candidates c)
    S-->>B: relayed
    Note over B: User must allow direct connections, otherwise decline
    B->>S: plugin data mumcord.p2p answer (s, candidates c)
    S-->>A: relayed
    A-->>B: QUIC connect (direct), mutual TLS
    Note over A,B: Each side accepts the other only if the certificate hash equals UserState.hash from the server
    alt direct path works
        A-->>B: feature streams (direct, server not involved)
    else decline, no answer within 15 s or no path
        Note over A,B: Feature falls back to plugin data, or is not available
    end
Loading

Steps

  1. A feature (an image download or a screen share) wants a direct connection to a person who announced p2p.
  2. If the user has not decided yet, the client asks for consent and waits up to 60 seconds (CONSENT_WAIT). Without an answer the feature falls back to plugin data and the question stays open.
  3. With consent, A gathers address candidates: IPv6 and LAN addresses, and a router port mapping (PCP, NAT-PMP or UPnP, within about 3 seconds). It sends an offer with a fresh 16-hex-digit attempt id s and up to 8 candidates.
  4. B, if it allows direct connections, gathers its own candidates and sends an answer with the same s. If not, it sends decline with r = declined; if it allowed but could not set up, unavailable. An offer that arrives before B's user decided is declined.
  5. A waits up to 15 seconds (ANSWER_WAIT) for answer or decline, then both sides try the candidates over QUIC (ALPN mumcord-p2p/1, connect timeout 10 seconds in the library).
  6. Mutual TLS: each side presents its Mumble client certificate and accepts the peer only if the certificate hash equals the hash the Mumble server reported for that user (UserState.hash). A mismatch ends the connection.
  7. One connection per pair of users on a server is shared by all features. Each feature opens its own stream; the first byte is the stream kind.

The connection has a 30 second idle timeout with keep-alive pings every 10 seconds.

Streams

Kind Feature Format
1 Full image Request: the image's SHA-256 (32 bytes). Reply: 0 and the size (u32, big-endian) followed by the image, or 1 (unavailable). Same checks as Packet-mumcord-image. A read may stall at most 15 seconds.
2 Screen share The sharer writes frames back to back. The viewer's direction carries single-byte requests; 1 asks for a keyframe, unknown bytes are ignored.

Screen share frames

Header of 20 bytes, big-endian, followed by the payload:

Bytes Field
1 Version, 1. Another version ends the stream.
1 Kind: 1 video (VP9), 2 audio (Opus)
1 Flags: bit 0 keyframe, all other bits zero
1 Temporal layer, at most 3
8 Capture timestamp in microseconds
2 Width in pixels (0 for audio)
2 Height in pixels (0 for audio)
4 Payload size in bytes (not 0; video at most 4 MiB, audio at most 4 KiB)

Video is at most 7680 x 4320. See crates/engine/src/screen_share/wire.rs.

Limits and errors

Situation Result
User declines or has not allowed direct connections decline; feature falls back or is unavailable.
No answer within 15 s The attempt ends; fallback.
No candidate reachable Fallback; unavailable where the other side reports it.
Certificate hash differs from UserState.hash Connection refused.
More than 8 candidates, unusable addresses (port 0, unspecified, multicast, broadcast, IPv6 link-local, IPv4-mapped IPv6) Rejected.
Unknown stream kind Stream closed.

Packets used

Packet-mumcord-p2p

Clone this wiki locally