#multiplexing #networking #reverse-tunnel

quic-reverse

Reverse-initiated, multiplexed services over QUIC

3 releases

new 0.1.2 Dec 25, 2025
0.1.1 Dec 25, 2025
0.1.0 Dec 25, 2025

#514 in Network programming

Apache-2.0

275KB
4.5K SLoC

quic-reverse

Crates.io Documentation CI License MSRV Downloads

A Rust library for reverse-initiated, multiplexed streams over QUIC.

quic-reverse helps with the "reverse connection" problem: allowing services behind NAT or firewalls to accept incoming connections without exposing a public port. The library handles connection negotiation, stream lifecycle management, and multiplexing while staying out of authentication, certificate management, and application-level protocols.

What can you build with quic-reverse?

quic-reverse is useful whenever a service behind NAT or a firewall needs to accept incoming connections but isn't explicitly exposed to do so. Common use cases include:

  • Remote access tools - SSH, RDP, or VNC tunneling through restrictive networks
  • IoT device management - Push commands to devices that can only make outbound connections
  • Development tunnels - Expose localhost services to the internet (similar to ngrok)
  • Edge-to-cloud connectivity - Let edge nodes receive work from a central orchestrator
  • Multiplexed service proxies - Route multiple logical services over a single QUIC connection

What quic-reverse handles

  • Connection negotiation and feature discovery
  • Stream lifecycle (open requests, responses, graceful close)
  • Logical stream multiplexing with service-based routing
  • Keep-alive and timeout management

What you provide

  • The QUIC connection (quic-reverse abstracts over Quinn; you control TLS and certificates)
  • Service handlers for incoming stream requests
  • Application-level framing for your data streams

Quick Start

# Run the echo server example
cargo run --example echo-server -p quic-reverse

# In another terminal, run the echo client
cargo run --example echo-client -p quic-reverse -- echo/uppercase "Hello World"
# Output: HELLO WORLD

See Examples for more details and crates/quic-reverse/examples/ for full source code.

Installation

Add quic-reverse to your Cargo.toml:

[dependencies]
quic-reverse = "0.1"

The library uses Quinn as the default QUIC implementation. If you need a different transport, disable default features:

[dependencies]
quic-reverse = { version = "0.1", default-features = false }

Usage

Server (Responder)

The server accepts incoming QUIC connections and handles stream open requests from clients.

use quic_reverse::{Config, ControlEvent, Role, Session};
use quic_reverse_transport::QuinnConnection;

// Wrap your Quinn connection
let quinn_conn = QuinnConnection::new(connection);
let config = Config::new().with_agent("my-server/1.0");

// Create and start the session
let session = Session::new(quinn_conn, Role::Server, config);
let mut handle = session.start().await?;

// Process control messages
loop {
    match handle.process_message().await? {
        Some(ControlEvent::OpenRequest { request_id, service, .. }) => {
            // Accept or reject based on service name
            if service.as_str() == "ssh" {
                handle.accept_open(request_id, stream_id).await?;
                // Open a data stream back to the client
                let (send, recv) = connection.open_bi().await?;
                // Handle the stream...
            } else {
                handle.reject_open(request_id, RejectCode::UnsupportedService, None).await?;
            }
        }
        Some(ControlEvent::CloseReceived { .. }) => break,
        None => break,
        _ => {}
    }
}

Client (Requester)

The client initiates connections and requests streams to specific services.

use quic_reverse::{Config, Metadata, Role, Session};
use quic_reverse_transport::QuinnConnection;

let quinn_conn = QuinnConnection::new(connection);
let config = Config::new().with_agent("my-client/1.0");

let session = Session::new(quinn_conn, Role::Client, config);
let mut handle = session.start().await?;

// Request a stream to a service
let (mut send, mut recv) = handle.open("ssh", Metadata::Empty).await?;

// Use the stream for bidirectional communication
send.write_all(b"hello").await?;

Architecture

quic-reverse is organized into three crates:

Crate Purpose
quic-reverse Public API: Session, Config, Error types
quic-reverse-control Protocol messages, framing, serialization
quic-reverse-transport Transport traits, Quinn adapter, mock transport

The protocol uses a dedicated control stream for signaling, separate from data streams:

Client                              Server
  │                                   │
  │──── Hello ─────────────────────►  │
  │◄─── Hello ─────────────────────── │
  │──── HelloAck ──────────────────►  │
  │◄─── HelloAck ──────────────────── │
  │                                   │
  │          [Session Ready]          │
  │                                   │
  │──── OpenRequest ───────────────►  │
  │◄─── OpenResponse (accept) ─────── │
  │◄════ Data Stream ════════════════►│
  │                                   │

See ARCHITECTURE.md for design rationale and PROTOCOL.md for wire format details.

Configuration

Sessions are configured using Config:

use quic_reverse::Config;
use std::time::Duration;

let config = Config::new()
    .with_agent("my-app/1.0")
    .with_open_timeout(Duration::from_secs(30))
    .with_ping_timeout(Duration::from_secs(10))
    .with_max_inflight_opens(16)
    .with_max_concurrent_streams(256);

Timeouts

Setting Default Description
open_timeout 30s How long to wait for an open request response
stream_bind_timeout 10s How long to wait for the data stream after acceptance
negotiation_timeout 30s How long to wait for the handshake to complete
ping_timeout 10s How long to wait for a pong response

Examples

The repository includes working examples demonstrating the library:

# Basic example using mock transport
cargo run --example basic -p quic-reverse

# Multi-service echo server (uses real QUIC)
cargo run --example echo-server -p quic-reverse

# Echo client
cargo run --example echo-client -p quic-reverse echo/uppercase "Hello World"

The echo server demonstrates multiplexing with three services:

  • echo/plain - Returns input unchanged
  • echo/uppercase - Returns input in uppercase
  • echo/reverse - Returns input reversed

Error Handling

The library provides structured error types for different failure modes:

use quic_reverse::{Error, TimeoutKind};

match handle.open("service", Metadata::Empty).await {
    Ok((send, recv)) => { /* use streams */ }
    Err(Error::Timeout(TimeoutKind::OpenRequest)) => { /* request timed out */ }
    Err(Error::StreamRejected { code, reason }) => { /* server rejected */ }
    Err(Error::SessionClosed) => { /* session ended */ }
    Err(e) => { /* other error */ }
}

Observability

quic-reverse uses the tracing crate for structured logging. Enable tracing in your application:

tracing_subscriber::fmt()
    .with_max_level(tracing::Level::DEBUG)
    .init();

Log levels follow this convention:

  • trace - Low-level I/O operations
  • debug - Protocol operations and state changes
  • info - Session lifecycle events
  • warn/error - Problems requiring attention

Documentation

License

Licensed under Apache-2.0.

Dependencies

~9–24MB
~232K SLoC