Skip to content

Improve Tunnel Performance by Separating Control Plane and Data Plane #9

@dpkrn

Description

@dpkrn

🚀 Improve Tunnel Performance by Separating Control Plane and Data Plane

📌 Problem

The current tunnel implementation processes requests and responses using a JSON-based message approach, where the entire HTTP request/response body is read into memory before being forwarded.

Example:

body, err := io.ReadAll(resp.Body)

This leads to:

  • ❌ High memory usage for large responses
  • ❌ No streaming support (responses are buffered fully before sending)
  • ❌ Increased latency
  • ❌ Inefficient handling of large or continuous data (e.g., file downloads, SSE, etc.)

🧠 Root Cause

The current design mixes:

  • Control Plane (metadata) → JSON (method, headers, path)
  • Data Plane (actual payload) → also sent via JSON

This prevents leveraging Go's streaming capabilities.


💡 Proposed Solution

Separate responsibilities into:

1. Control Plane (JSON)

Used for:

  • Handshake (clientHello)
  • Request metadata (method, path, headers)
  • Logging / inspector events

2. Data Plane (Streaming via io.Copy)

Used for:

  • Request body
  • Response body

Example:

go io.Copy(localConn, stream) // request body
go io.Copy(stream, localConn) // response body

🎯 Benefits

  • ✅ True streaming (no buffering entire payloads)
  • ✅ Lower memory footprint
  • ✅ Automatic backpressure handling (TCP-level)
  • ✅ Better performance for large/continuous responses
  • ✅ Aligns with real-world tunnel implementations (e.g., ngrok-like systems)

🔧 Suggested Changes

  • Avoid using io.ReadAll for HTTP bodies
  • Introduce streaming between yamux stream and local connection
  • Keep JSON strictly for control messages only
  • Optionally introduce a simple protocol to signal start/end of streams

⚠️ Considerations

  • Need a way to distinguish control messages vs raw stream data
  • Ensure proper connection lifecycle handling (close both sides on error)
  • Maintain compatibility with existing inspector/logging system

📎 References

Current implementation: handleStream uses full buffering via io.ReadAll
Suggested approach: use io.Copy for streaming data paths


🧩 Summary

Move from a buffered JSON-based tunnel → to a streaming tunnel architecture by separating control and data planes.

This will significantly improve performance, scalability, and reliability.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions