Skip to content

Bidirectional Proxy Architecture

Implementation Status: ✅ IMPLEMENTED

The bidirectional proxy architecture has been fully implemented and is working in production.

Architecture Overview

Client ←→ WebSocket ←→ Server ←→ Target (TCP)
         ↖_________________________↗
              Bidirectional flow

Data Flow

  1. Client sends ProxyConnect → Server connects to target → Server sends ProxyResponse back
  2. Client sends ProxyData → Server writes to target
  3. Server reads from target → Server encrypts ProxyData → Server sends to client
  4. Concurrent reads from both WebSocket and target TCP stream are handled properly

Technical Implementation

1. Concurrent Access to Ratchet

The DoubleRatchet is wrapped in Arc<RwLock<DoubleRatchet>> for thread-safe access: - WebSocket reader task: Decrypts client messages - Target reader task: Encrypts target responses

Both operations can now proceed concurrently using the SplitRatchet implementation.

2. Target Reader Task

For each proxy connection, a dedicated task reads from the target:

async fn target_reader(
    connection_id: u64,
    mut target_stream: TcpStream,
    ratchet: Arc<RwLock<DoubleRatchet>>,
    mut ws_write: impl SinkExt<Message>,
) {
    let mut buf = [0u8; 8192];
    loop {
        match target_stream.read(&mut buf).await {
            Ok(0) => break, // EOF
            Ok(n) => {
                let data = &buf[..n];
                let proxy_data = ProxyData {
                    connection_id,
                    data: data.to_vec(),
                    close: false,
                };
                // Encrypt and send via WebSocket
                let encrypted = {
                    let mut ratchet_guard = ratchet.write().await;
                    ratchet_guard.encrypt(&msg_bytes, PayloadType::ProxyData as u8)
                };
                ws_write.send(Message::Binary(encrypted)).await.ok();
            }
            Err(_) => break,
        }
    }
}

3. Connection Lifecycle Management

Each proxy connection maintains: - Connection ID: Unique identifier for multiplexing - Target TCP stream: Connection to the destination server - Target reader task: Async task reading target responses - Cleanup on disconnect: Proper resource cleanup in both directions

4. Connection Pooling

The server implements connection pooling for efficient resource reuse: - Reuses established connections when possible - Properly closes idle connections - Handles concurrent connections efficiently

Files Modified

  1. rvpn-server/src/handler.rs
  2. handle_vpn_traffic with Arc<RwLock<DoubleRatchet>>
  3. handle_proxy_connect spawns target reader task
  4. target_reader function for reading target responses
  5. Connection cleanup logic

  6. rvpn-core/src/crypto/ratchet.rs

  7. DoubleRatchet is Send for async usage
  8. SplitRatchet for concurrent encrypt/decrypt

Testing Results

  • ✅ Basic HTTP request/response
  • ✅ HTTPS request/response
  • ✅ Large file download
  • ✅ Concurrent multiple connections
  • ✅ Connection timeout handling
  • ✅ Graceful shutdown
  • ✅ Error propagation to client