From a2cdadc5e30502a8ba17bccd911c33f2a4c595b9 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Thu, 19 Mar 2026 14:43:42 +0000 Subject: [PATCH] feat(docs): document TCP and UDP tunneling over TLS and QUIC --- changelog.md | 6 + package.json | 2 +- readme.md | 305 +++++++++++++++++++-------------------- ts/00_commitinfo_data.ts | 4 +- 4 files changed, 155 insertions(+), 162 deletions(-) diff --git a/changelog.md b/changelog.md index cdb3f4f..ea84be8 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## 2026-03-19 - 4.13.0 - feat(docs) +document TCP and UDP tunneling over TLS and QUIC + +- update package description to reflect TCP and UDP support and TLS or QUIC transports +- refresh README architecture, features, and usage examples for UDP forwarding, QUIC transport, and PROXY protocol v1/v2 support + ## 2026-03-19 - 4.12.1 - fix(remoteingress-core) send PROXY v2 headers for UDP upstream sessions and expire idle UDP sessions diff --git a/package.json b/package.json index cc0febc..e465419 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@serve.zone/remoteingress", "version": "4.12.1", "private": false, - "description": "Edge ingress tunnel for DcRouter - accepts incoming TCP connections at network edge and tunnels them to DcRouter SmartProxy preserving client IP via PROXY protocol v1.", + "description": "Edge ingress tunnel for DcRouter - tunnels TCP and UDP traffic from the network edge to SmartProxy over TLS or QUIC, preserving client IP via PROXY protocol.", "main": "dist_ts/index.js", "typings": "dist_ts/index.d.ts", "type": "module", diff --git a/readme.md b/readme.md index 706a5ae..fa97659 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@ # @serve.zone/remoteingress -Edge ingress tunnel for DcRouter — accepts incoming TCP connections at the network edge and tunnels them over a single encrypted TLS connection to a DcRouter SmartProxy instance, preserving the original client IP via PROXY protocol v1. +Edge ingress tunnel for DcRouter — tunnels **TCP and UDP** traffic from the network edge to a private DcRouter/SmartProxy cluster over encrypted TLS or QUIC connections, preserving the original client IP via PROXY protocol. ## Issue Reporting and Security @@ -17,43 +17,46 @@ pnpm install @serve.zone/remoteingress `@serve.zone/remoteingress` uses a **Hub/Edge** topology with a high-performance Rust core and a TypeScript API surface: ``` -┌─────────────────────┐ TLS Tunnel ┌─────────────────────┐ +┌─────────────────────┐ TLS or QUIC Tunnel ┌─────────────────────┐ │ Network Edge │ ◄══════════════════════════► │ Private Cluster │ -│ │ (multiplexed frames + │ │ -│ RemoteIngressEdge │ shared-secret auth) │ RemoteIngressHub │ -│ Accepts client TCP │ │ Forwards to │ -│ connections on │ │ SmartProxy on │ -│ hub-assigned ports │ │ local ports │ +│ │ TCP+TLS: frame mux │ │ +│ RemoteIngressEdge │ QUIC: native streams │ RemoteIngressHub │ +│ │ UDP: QUIC datagrams │ │ +│ Accepts TCP & UDP │ │ Forwards to │ +│ on hub-assigned │ │ SmartProxy on │ +│ ports │ │ local ports │ └─────────────────────┘ └─────────────────────┘ ▲ │ - │ TCP from end users ▼ + │ TCP + UDP from end users ▼ Internet DcRouter / SmartProxy ``` | Component | Role | |-----------|------| -| **RemoteIngressEdge** | Deployed at the network edge (e.g. a VPS or cloud instance). Listens on ports assigned by the hub, accepts raw TCP connections, and multiplexes them over a single TLS tunnel to the hub. Ports are hot-reloadable — the hub can change them at runtime. | -| **RemoteIngressHub** | Deployed alongside DcRouter/SmartProxy in a private cluster. Accepts edge connections, demuxes streams, and forwards each to SmartProxy with a PROXY protocol v1 header so the real client IP is preserved. Controls which ports each edge listens on. | +| **RemoteIngressEdge** | Deployed at the network edge (VPS, cloud instance). Listens on TCP and UDP ports assigned by the hub, accepts connections/datagrams, and tunnels them to the hub. Ports are hot-reloadable at runtime. | +| **RemoteIngressHub** | Deployed alongside DcRouter/SmartProxy in a private cluster. Accepts edge connections, demuxes streams/datagrams, and forwards each to SmartProxy with PROXY protocol headers so the real client IP is preserved. | | **Rust Binary** (`remoteingress-bin`) | The performance-critical networking core. Managed via `@push.rocks/smartrust` RustBridge IPC — you never interact with it directly. Cross-compiled for `linux/amd64` and `linux/arm64`. | ### ✨ Key Features -- 🔒 **TLS-encrypted tunnel** between edge and hub (auto-generated self-signed cert or bring your own) -- 🔀 **Multiplexed streams** — thousands of client connections flow over a single tunnel -- 🌐 **PROXY protocol v1** — SmartProxy sees the real client IP, not the tunnel IP +- 🔒 **Dual transport** — choose between TCP+TLS (frame-multiplexed) or QUIC (native stream multiplexing, zero head-of-line blocking) +- 🌐 **TCP + UDP tunneling** — tunnel any TCP connection or UDP datagram through the same edge/hub pair +- 📋 **PROXY protocol v1 & v2** — SmartProxy sees the real client IP for both TCP (v1 text) and UDP (v2 binary) +- 🔀 **Multiplexed streams** — thousands of concurrent TCP connections over a single tunnel +- ⚡ **QUIC datagrams** — UDP traffic forwarded via QUIC unreliable datagrams for lowest possible latency - 🔑 **Shared-secret authentication** — edges must present valid credentials to connect -- 🎫 **Connection tokens** — encode all connection details into a single opaque string -- 📡 **STUN-based public IP discovery** — the edge automatically discovers its public IP via Cloudflare STUN +- 🎫 **Connection tokens** — encode all connection details into a single opaque base64url string +- 📡 **STUN-based public IP discovery** — edges automatically discover their public IP via Cloudflare STUN - 🔄 **Auto-reconnect** with exponential backoff if the tunnel drops -- 🎛️ **Dynamic port configuration** — the hub assigns listen ports per edge and can hot-reload them at runtime via `FRAME_CONFIG` frames +- 🎛️ **Dynamic port configuration** — the hub assigns TCP and UDP listen ports per edge, hot-reloadable at runtime - 📣 **Event-driven** — both Hub and Edge extend `EventEmitter` for real-time monitoring -- ⚡ **Rust core** — all frame encoding, TLS, and TCP proxying happen in native code for maximum throughput - 🎚️ **3-tier QoS** — control frames, normal data, and sustained (elephant flow) traffic each get their own priority queue - 📊 **Adaptive flow control** — per-stream windows scale with active stream count to prevent memory overuse +- 🕒 **UDP session management** — automatic session tracking with 60s idle timeout and cleanup ## 🚀 Usage -Both classes are imported from the package and communicate with the Rust binary under the hood. All you need to do is configure and start them. +Both classes are imported from the package and communicate with the Rust binary under the hood. ### Setting Up the Hub (Private Cluster Side) @@ -63,32 +66,25 @@ import { RemoteIngressHub } from '@serve.zone/remoteingress'; const hub = new RemoteIngressHub(); // Listen for events -hub.on('edgeConnected', ({ edgeId }) => { - console.log(`Edge ${edgeId} connected`); -}); -hub.on('edgeDisconnected', ({ edgeId }) => { - console.log(`Edge ${edgeId} disconnected`); -}); -hub.on('streamOpened', ({ edgeId, streamId }) => { - console.log(`Stream ${streamId} opened from edge ${edgeId}`); -}); -hub.on('streamClosed', ({ edgeId, streamId }) => { - console.log(`Stream ${streamId} closed from edge ${edgeId}`); -}); +hub.on('edgeConnected', ({ edgeId }) => console.log(`Edge ${edgeId} connected`)); +hub.on('edgeDisconnected', ({ edgeId }) => console.log(`Edge ${edgeId} disconnected`)); +hub.on('streamOpened', ({ edgeId, streamId }) => console.log(`Stream ${streamId} from ${edgeId}`)); +hub.on('streamClosed', ({ edgeId, streamId }) => console.log(`Stream ${streamId} closed`)); -// Start the hub — it will listen for incoming edge TLS connections +// Start the hub — listens for edge connections on both TCP and QUIC (same port) await hub.start({ tunnelPort: 8443, // port edges connect to (default: 8443) - targetHost: '127.0.0.1', // SmartProxy host to forward streams to (default: 127.0.0.1) + targetHost: '127.0.0.1', // SmartProxy host to forward traffic to }); -// Register which edges are allowed to connect, including their listen ports +// Register allowed edges with TCP and UDP listen ports await hub.updateAllowedEdges([ { id: 'edge-nyc-01', secret: 'supersecrettoken1', - listenPorts: [80, 443], // ports the edge should listen on - stunIntervalSecs: 300, // STUN discovery interval (default: 300) + listenPorts: [80, 443], // TCP ports the edge should listen on + listenPortsUdp: [53, 51820], // UDP ports (e.g., DNS, WireGuard) + stunIntervalSecs: 300, }, { id: 'edge-fra-02', @@ -97,38 +93,29 @@ await hub.updateAllowedEdges([ }, ]); -// Dynamically update ports for a connected edge — changes are pushed instantly +// Dynamically update ports — changes are pushed instantly to connected edges await hub.updateAllowedEdges([ { id: 'edge-nyc-01', secret: 'supersecrettoken1', - listenPorts: [80, 443, 8443], // added port 8443 — edge picks it up in real time + listenPorts: [80, 443, 8443], // added TCP port 8443 + listenPortsUdp: [53], // removed WireGuard UDP port }, ]); -// Check status at any time +// Check status const status = await hub.getStatus(); -console.log(status); -// { -// running: true, -// tunnelPort: 8443, -// connectedEdges: [ -// { edgeId: 'edge-nyc-01', connectedAt: 1700000000, activeStreams: 12 } -// ] -// } +// { running: true, tunnelPort: 8443, connectedEdges: [...] } -// Graceful shutdown await hub.stop(); ``` ### Setting Up the Edge (Network Edge Side) -The edge can be configured in two ways: with an **opaque connection token** (recommended) or with explicit config fields. +The edge can connect via **TCP+TLS** (default) or **QUIC** transport. #### Option A: Connection Token (Recommended) -A single token encodes all connection details — ideal for provisioning edges at scale: - ```typescript import { RemoteIngressEdge } from '@serve.zone/remoteingress'; @@ -137,79 +124,64 @@ const edge = new RemoteIngressEdge(); edge.on('tunnelConnected', () => console.log('Tunnel established')); edge.on('tunnelDisconnected', () => console.log('Tunnel lost — will auto-reconnect')); edge.on('publicIpDiscovered', ({ ip }) => console.log(`Public IP: ${ip}`)); -edge.on('portsAssigned', ({ listenPorts }) => console.log(`Listening on ports: ${listenPorts}`)); -edge.on('portsUpdated', ({ listenPorts }) => console.log(`Ports updated: ${listenPorts}`)); +edge.on('portsAssigned', ({ listenPorts }) => console.log(`TCP ports: ${listenPorts}`)); -// Single token contains hubHost, hubPort, edgeId, and secret await edge.start({ - token: 'eyJoIjoiaHViLmV4YW1wbGUuY29tIiwicCI6ODQ0MywiZSI6ImVkZ2UtbnljLTAxIiwicyI6InN1cGVyc2VjcmV0dG9rZW4xIn0', + token: 'eyJoIjoiaHViLmV4YW1wbGUuY29tIiwi...', }); ``` -#### Option B: Explicit Config +#### Option B: Explicit Config with QUIC Transport ```typescript import { RemoteIngressEdge } from '@serve.zone/remoteingress'; const edge = new RemoteIngressEdge(); -edge.on('tunnelConnected', () => console.log('Tunnel established')); -edge.on('tunnelDisconnected', () => console.log('Tunnel lost — will auto-reconnect')); -edge.on('publicIpDiscovered', ({ ip }) => console.log(`Public IP: ${ip}`)); -edge.on('portsAssigned', ({ listenPorts }) => console.log(`Listening on ports: ${listenPorts}`)); -edge.on('portsUpdated', ({ listenPorts }) => console.log(`Ports updated: ${listenPorts}`)); - await edge.start({ - hubHost: 'hub.example.com', // hostname or IP of the hub - hubPort: 8443, // must match hub's tunnelPort (default: 8443) - edgeId: 'edge-nyc-01', // unique edge identifier - secret: 'supersecrettoken1', // must match the hub's allowed edge secret + hubHost: 'hub.example.com', + hubPort: 8443, + edgeId: 'edge-nyc-01', + secret: 'supersecrettoken1', + transportMode: 'quic', // 'tcpTls' (default) | 'quic' | 'quicWithFallback' }); -// Check status at any time const edgeStatus = await edge.getStatus(); -console.log(edgeStatus); -// { -// running: true, -// connected: true, -// publicIp: '203.0.113.42', -// activeStreams: 5, -// listenPorts: [80, 443] -// } +// { running: true, connected: true, publicIp: '203.0.113.42', activeStreams: 5, listenPorts: [80, 443] } -// Graceful shutdown await edge.stop(); ``` +#### Transport Modes + +| Mode | Description | +|------|-------------| +| `'tcpTls'` | **Default.** Single TLS connection with frame-based multiplexing. Universal compatibility. | +| `'quic'` | QUIC with native stream multiplexing. Eliminates head-of-line blocking. Uses QUIC datagrams for UDP traffic. | +| `'quicWithFallback'` | Tries QUIC first (5s timeout), falls back to TCP+TLS if UDP is blocked by the network. | + ### 🎫 Connection Tokens -Connection tokens let you distribute a single opaque string instead of four separate config values. The hub operator generates the token; the edge operator just pastes it in. +Encode all connection details into a single opaque string for easy distribution: ```typescript import { encodeConnectionToken, decodeConnectionToken } from '@serve.zone/remoteingress'; -// Hub side: generate a token for a new edge +// Hub operator generates a token const token = encodeConnectionToken({ hubHost: 'hub.example.com', hubPort: 8443, edgeId: 'edge-nyc-01', secret: 'supersecrettoken1', }); -console.log(token); // => 'eyJoIjoiaHViLmV4YW1wbGUuY29tIiwi...' -// Edge side: inspect a token (optional — start() does this automatically) +// Edge operator decodes (optional — start() does this automatically) const data = decodeConnectionToken(token); -console.log(data); -// { -// hubHost: 'hub.example.com', -// hubPort: 8443, -// edgeId: 'edge-nyc-01', -// secret: 'supersecrettoken1' -// } +// { hubHost: 'hub.example.com', hubPort: 8443, edgeId: 'edge-nyc-01', secret: '...' } ``` -Tokens are base64url-encoded (URL-safe, no padding) — safe to pass as environment variables, CLI arguments, or store in config files. +Tokens are base64url-encoded — safe for environment variables, CLI arguments, and config files. ## 📖 API Reference @@ -217,10 +189,10 @@ Tokens are base64url-encoded (URL-safe, no padding) — safe to pass as environm | Method / Property | Description | |-------------------|-------------| -| `start(config?)` | Spawns the Rust binary and starts the tunnel listener. Config: `{ tunnelPort?: number, targetHost?: string }` | -| `stop()` | Gracefully shuts down the hub and kills the Rust process. | -| `updateAllowedEdges(edges)` | Dynamically update which edges are authorized and what ports they listen on. Each edge: `{ id: string, secret: string, listenPorts?: number[], stunIntervalSecs?: number }`. If ports change for a connected edge, the update is pushed immediately via a `FRAME_CONFIG` frame. | -| `getStatus()` | Returns current hub status including connected edges and active stream counts. | +| `start(config?)` | Start the hub. Config: `{ tunnelPort?: number, targetHost?: string }`. Listens on both TCP and UDP (QUIC) on the tunnel port. | +| `stop()` | Graceful shutdown. | +| `updateAllowedEdges(edges)` | Set authorized edges. Each: `{ id, secret, listenPorts?, listenPortsUdp?, stunIntervalSecs? }`. Port changes are pushed to connected edges in real time. | +| `getStatus()` | Returns `{ running, tunnelPort, connectedEdges: [...] }`. | | `running` | `boolean` — whether the Rust binary is alive. | **Events:** `edgeConnected`, `edgeDisconnected`, `streamOpened`, `streamClosed` @@ -229,9 +201,9 @@ Tokens are base64url-encoded (URL-safe, no padding) — safe to pass as environm | Method / Property | Description | |-------------------|-------------| -| `start(config)` | Spawns the Rust binary and connects to the hub. Accepts `{ token: string }` or `IEdgeConfig`. Listen ports are received from the hub during handshake. | -| `stop()` | Gracefully shuts down the edge and kills the Rust process. | -| `getStatus()` | Returns current edge status including connection state, public IP, listen ports, and active streams. | +| `start(config)` | Connect to hub. Accepts `{ token }` or `{ hubHost, hubPort, edgeId, secret, transportMode? }`. | +| `stop()` | Graceful shutdown. | +| `getStatus()` | Returns `{ running, connected, publicIp, activeStreams, listenPorts }`. | | `running` | `boolean` — whether the Rust binary is alive. | **Events:** `tunnelConnected`, `tunnelDisconnected`, `publicIpDiscovered`, `portsAssigned`, `portsUpdated` @@ -240,8 +212,8 @@ Tokens are base64url-encoded (URL-safe, no padding) — safe to pass as environm | Function | Description | |----------|-------------| -| `encodeConnectionToken(data)` | Encodes `IConnectionTokenData` into a base64url token string. | -| `decodeConnectionToken(token)` | Decodes a token back into `IConnectionTokenData`. Throws on malformed or incomplete tokens. | +| `encodeConnectionToken(data)` | Encodes connection info into a base64url token. | +| `decodeConnectionToken(token)` | Decodes a token. Throws on malformed input. | ### Interfaces @@ -256,6 +228,8 @@ interface IEdgeConfig { hubPort?: number; // default: 8443 edgeId: string; secret: string; + bindAddress?: string; + transportMode?: 'tcpTls' | 'quic' | 'quicWithFallback'; } interface IConnectionTokenData { @@ -268,7 +242,9 @@ interface IConnectionTokenData { ## 🔌 Wire Protocol -The tunnel uses a custom binary frame protocol over TLS: +### TCP+TLS Transport (Frame Protocol) + +The tunnel uses a custom binary frame protocol over a single TLS connection: ``` [stream_id: 4 bytes BE][type: 1 byte][length: 4 bytes BE][payload: N bytes] @@ -276,113 +252,124 @@ The tunnel uses a custom binary frame protocol over TLS: | Frame Type | Value | Direction | Purpose | |------------|-------|-----------|---------| -| `OPEN` | `0x01` | Edge → Hub | Open a new stream; payload is PROXY v1 header | -| `DATA` | `0x02` | Edge → Hub | Client data flowing upstream | -| `CLOSE` | `0x03` | Edge → Hub | Client closed the connection | -| `DATA_BACK` | `0x04` | Hub → Edge | Response data flowing downstream | -| `CLOSE_BACK` | `0x05` | Hub → Edge | Upstream (SmartProxy) closed the connection | -| `CONFIG` | `0x06` | Hub → Edge | Runtime configuration update (e.g. port changes); payload is JSON | -| `PING` | `0x07` | Hub → Edge | Heartbeat probe (sent every 15s) | +| `OPEN` | `0x01` | Edge → Hub | Open TCP stream; payload is PROXY v1 header | +| `DATA` | `0x02` | Edge → Hub | Client data (upload) | +| `CLOSE` | `0x03` | Edge → Hub | Client closed connection | +| `DATA_BACK` | `0x04` | Hub → Edge | Response data (download) | +| `CLOSE_BACK` | `0x05` | Hub → Edge | Upstream closed connection | +| `CONFIG` | `0x06` | Hub → Edge | Runtime config update (JSON payload) | +| `PING` | `0x07` | Hub → Edge | Heartbeat probe (every 15s) | | `PONG` | `0x08` | Edge → Hub | Heartbeat response | -| `WINDOW_UPDATE` | `0x09` | Edge → Hub | Per-stream flow control: edge consumed N bytes, hub can send more | -| `WINDOW_UPDATE_BACK` | `0x0A` | Hub → Edge | Per-stream flow control: hub consumed N bytes, edge can send more | +| `WINDOW_UPDATE` | `0x09` | Edge → Hub | Flow control: edge consumed N bytes | +| `WINDOW_UPDATE_BACK` | `0x0A` | Hub → Edge | Flow control: hub consumed N bytes | +| `UDP_OPEN` | `0x0B` | Edge → Hub | Open UDP session; payload is PROXY v2 header | +| `UDP_DATA` | `0x0C` | Edge → Hub | UDP datagram (upload) | +| `UDP_DATA_BACK` | `0x0D` | Hub → Edge | UDP datagram (download) | +| `UDP_CLOSE` | `0x0E` | Either | Close UDP session | -Max payload size per frame: **16 MB**. Stream IDs are 32-bit unsigned integers. +### QUIC Transport + +When using QUIC, the frame protocol is replaced by native QUIC primitives: + +- **TCP connections:** Each tunneled TCP connection gets its own QUIC bidirectional stream. No framing overhead. +- **UDP datagrams:** Forwarded via QUIC unreliable datagrams (RFC 9221). Format: `[session_id: 4 bytes][payload]`. Session open uses magic byte `0xFF`: `[session_id: 4][0xFF][PROXY v2 header]`. +- **Control channel:** First QUIC bidirectional stream carries auth handshake + config updates using `[type: 1][length: 4][payload]` format. ### Handshake Sequence -1. Edge opens a TLS connection to the hub +1. Edge opens a TLS or QUIC connection to the hub 2. Edge sends: `EDGE \n` -3. Hub verifies credentials (constant-time comparison) and responds with JSON: `{"listenPorts":[...],"stunIntervalSecs":300}\n` -4. Edge starts TCP listeners on the assigned ports -5. Frame protocol begins — `OPEN`/`DATA`/`CLOSE` frames flow in both directions -6. Hub can push `CONFIG` frames at any time to update the edge's listen ports +3. Hub verifies credentials (constant-time comparison) and responds with JSON: + `{"listenPorts":[...],"listenPortsUdp":[...],"stunIntervalSecs":300}\n` +4. Edge starts TCP and UDP listeners on the assigned ports +5. Data flows — TCP frames/QUIC streams for TCP traffic, UDP frames/QUIC datagrams for UDP traffic ## 🎚️ QoS & Flow Control -The tunnel multiplexer uses a **3-tier priority system** and **per-stream flow control** to ensure fair bandwidth sharing across thousands of concurrent streams. +### Priority Tiers (TCP+TLS Transport) -### Priority Tiers - -All outbound frames are queued into one of three priority levels: - -| Tier | Queue | Frames | Behavior | -|------|-------|--------|----------| -| 🔴 **Control** (highest) | `ctrl_queue` | PING, PONG, WINDOW_UPDATE, OPEN, CLOSE, CONFIG | Always drained first. Never delayed. | -| 🟡 **Data** (normal) | `data_queue` | DATA, DATA_BACK from normal streams | Drained when ctrl is empty. Gated at 64 buffered items for backpressure. | -| 🟢 **Sustained** (lowest) | `sustained_queue` | DATA, DATA_BACK from elephant flows | Drained freely when ctrl+data are empty. Otherwise guaranteed **1 MB/s** via forced drain every second. | - -This prevents large bulk transfers (e.g. git clones, file downloads) from starving interactive traffic and ensures `WINDOW_UPDATE` frames are never delayed — which would cause flow control deadlocks. +| Tier | Frames | Behavior | +|------|--------|----------| +| 🔴 **Control** | PING, PONG, WINDOW_UPDATE, OPEN, CLOSE, CONFIG | Always drained first. Never delayed. | +| 🟡 **Data** | DATA/DATA_BACK from normal streams, UDP frames | Drained when control queue is empty. | +| 🟢 **Sustained** | DATA/DATA_BACK from elephant flows | Lowest priority with guaranteed **1 MB/s** drain rate. | ### Sustained Stream Classification -A stream is automatically classified as **sustained** (elephant flow) when: -- It has been active for **>10 seconds**, AND -- Its average throughput exceeds **20 Mbit/s** (2.5 MB/s) +A TCP stream is classified as **sustained** (elephant flow) when: +- Active for **>10 seconds**, AND +- Average throughput exceeds **20 Mbit/s** (2.5 MB/s) -Once classified, the stream's flow control window is locked to the **1 MB floor** and its data frames move to the lowest-priority queue. Classification is one-way — a stream never gets promoted back to normal. +Once classified, its flow control window locks to 1 MB and data frames move to the lowest-priority queue. ### Adaptive Per-Stream Windows -Each stream has a send window that limits bytes-in-flight. The window size adapts to the number of active streams using a shared **200 MB memory budget**: +Each TCP stream has a send window from a shared **200 MB budget**: | Active Streams | Window per Stream | |---|---| | 1–50 | 4 MB (maximum) | -| 51–100 | Scales down (4 MB → 2 MB) | +| 51–200 | Scales down (4 MB → 1 MB) | | 200+ | 1 MB (floor) | -The consumer sends `WINDOW_UPDATE` frames after processing data, allowing the producer to send more. This prevents any single stream from consuming unbounded memory and provides natural backpressure. +UDP traffic uses no flow control — datagrams are fire-and-forget, matching UDP semantics. ## 💡 Example Scenarios -### 1. Expose a Private Kubernetes Cluster to the Internet +### 1. Expose a Private Cluster to the Internet -Deploy an Edge on a public VPS, point your DNS to the VPS IP. The Edge tunnels all traffic to the Hub running inside the cluster, which hands it off to SmartProxy/DcRouter. Your cluster stays fully private — no public-facing ports needed. +Deploy an Edge on a public VPS, point DNS to its IP. The Edge tunnels all TCP and UDP traffic to the Hub running inside your private cluster. No public ports needed on the cluster. ### 2. Multi-Region Edge Ingress -Run multiple Edges in different geographic regions (NYC, Frankfurt, Tokyo) all connecting to a single Hub. Use GeoDNS to route users to their nearest Edge. The Hub sees the real client IPs via PROXY protocol regardless of which edge they connected through. +Run Edges in NYC, Frankfurt, and Tokyo — all connecting to a single Hub. Use GeoDNS to route users to their nearest Edge. PROXY protocol ensures the Hub sees real client IPs regardless of which Edge they entered through. -### 3. Secure API Exposure +### 3. UDP Forwarding (DNS, Gaming, VoIP) -Your backend runs on a private network with no direct internet access. An Edge on a minimal cloud instance acts as the only public entry point. TLS tunnel + shared-secret auth ensure only your authorized Edge can forward traffic. - -### 4. Token-Based Edge Provisioning - -Generate connection tokens on the hub side and distribute them to edge operators. Each edge only needs a single token string to connect — no manual configuration of host, port, ID, and secret. +Configure UDP listen ports alongside TCP ports. DNS queries, game server traffic, or VoIP packets are tunneled through the same edge/hub connection and forwarded to SmartProxy with a PROXY v2 binary header preserving the client's real IP. + +```typescript +await hub.updateAllowedEdges([ + { + id: 'edge-nyc-01', + secret: 'secret', + listenPorts: [80, 443], // TCP + listenPortsUdp: [53, 27015], // DNS + game server + }, +]); +``` + +### 4. QUIC Transport for Low-Latency + +Use QUIC transport to eliminate head-of-line blocking — a lost packet on one stream doesn't stall others. QUIC also enables 0-RTT reconnection and connection migration. + +```typescript +await edge.start({ + hubHost: 'hub.example.com', + hubPort: 8443, + edgeId: 'edge-01', + secret: 'secret', + transportMode: 'quicWithFallback', // try QUIC, fall back to TLS if UDP blocked +}); +``` + +### 5. Token-Based Edge Provisioning + +Generate connection tokens on the hub side and distribute them to edge operators: ```typescript -// Hub operator generates token const token = encodeConnectionToken({ hubHost: 'hub.prod.example.com', hubPort: 8443, edgeId: 'edge-tokyo-01', secret: 'generated-secret-abc123', }); -// Send `token` to the edge operator via secure channel +// Send `token` to the edge operator — a single string is all they need -// Edge operator starts with just the token const edge = new RemoteIngressEdge(); await edge.start({ token }); ``` -### 5. Dynamic Port Management - -The hub controls which ports each edge listens on. Ports can be changed at runtime without restarting the edge — the hub pushes a `CONFIG` frame and the edge hot-reloads its TCP listeners. - -```typescript -// Initially assign ports 80 and 443 -await hub.updateAllowedEdges([ - { id: 'edge-nyc-01', secret: 'secret', listenPorts: [80, 443] }, -]); - -// Later, add port 8080 — the connected edge picks it up instantly -await hub.updateAllowedEdges([ - { id: 'edge-nyc-01', secret: 'secret', listenPorts: [80, 443, 8080] }, -]); -``` - ## License and Legal Information This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file. diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 20c7be5..e7b5e1e 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/remoteingress', - version: '4.12.1', - description: 'Edge ingress tunnel for DcRouter - accepts incoming TCP connections at network edge and tunnels them to DcRouter SmartProxy preserving client IP via PROXY protocol v1.' + version: '4.13.0', + description: 'Edge ingress tunnel for DcRouter - tunnels TCP and UDP traffic from the network edge to SmartProxy over TLS or QUIC, preserving client IP via PROXY protocol.' }