@serve.zone/remoteingress 4.12.1 → 4.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/remoteingress',
6
- version: '4.12.1',
7
- 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.'
6
+ version: '4.13.0',
7
+ 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.'
8
8
  };
9
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwyQkFBMkI7SUFDakMsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLHlLQUF5SztDQUN2TCxDQUFBIn0=
9
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwyQkFBMkI7SUFDakMsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLCtKQUErSjtDQUM3SyxDQUFBIn0=
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@serve.zone/remoteingress",
3
- "version": "4.12.1",
3
+ "version": "4.13.0",
4
4
  "private": false,
5
- "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.",
5
+ "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.",
6
6
  "main": "dist_ts/index.js",
7
7
  "typings": "dist_ts/index.d.ts",
8
8
  "type": "module",
package/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @serve.zone/remoteingress
2
2
 
3
- 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.
3
+ 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.
4
4
 
5
5
  ## Issue Reporting and Security
6
6
 
@@ -17,43 +17,46 @@ pnpm install @serve.zone/remoteingress
17
17
  `@serve.zone/remoteingress` uses a **Hub/Edge** topology with a high-performance Rust core and a TypeScript API surface:
18
18
 
19
19
  ```
20
- ┌─────────────────────┐ TLS Tunnel ┌─────────────────────┐
20
+ ┌─────────────────────┐ TLS or QUIC Tunnel ┌─────────────────────┐
21
21
  │ Network Edge │ ◄══════════════════════════► │ Private Cluster │
22
- │ │ (multiplexed frames + │ │
23
- │ RemoteIngressEdge │ shared-secret auth) │ RemoteIngressHub │
24
- Accepts client TCP Forwards to │
25
- connections on │ │ SmartProxy on
26
- │ hub-assigned ports │ │ local ports
22
+ │ │ TCP+TLS: frame mux │ │
23
+ │ RemoteIngressEdge │ QUIC: native streams │ RemoteIngressHub │
24
+ │ UDP: QUIC datagrams
25
+ Accepts TCP & UDP │ │ Forwards to
26
+ on hub-assigned │ │ SmartProxy on
27
+ │ ports │ │ local ports │
27
28
  └─────────────────────┘ └─────────────────────┘
28
29
  ▲ │
29
- │ TCP from end users
30
+ │ TCP + UDP from end users
30
31
  Internet DcRouter / SmartProxy
31
32
  ```
32
33
 
33
34
  | Component | Role |
34
35
  |-----------|------|
35
- | **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. |
36
- | **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. |
36
+ | **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. |
37
+ | **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. |
37
38
  | **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`. |
38
39
 
39
40
  ### ✨ Key Features
40
41
 
41
- - 🔒 **TLS-encrypted tunnel** between edge and hub (auto-generated self-signed cert or bring your own)
42
- - 🔀 **Multiplexed streams** — thousands of client connections flow over a single tunnel
43
- - 🌐 **PROXY protocol v1** — SmartProxy sees the real client IP, not the tunnel IP
42
+ - 🔒 **Dual transport** choose between TCP+TLS (frame-multiplexed) or QUIC (native stream multiplexing, zero head-of-line blocking)
43
+ - 🌐 **TCP + UDP tunneling** — tunnel any TCP connection or UDP datagram through the same edge/hub pair
44
+ - 📋 **PROXY protocol v1 & v2** — SmartProxy sees the real client IP for both TCP (v1 text) and UDP (v2 binary)
45
+ - 🔀 **Multiplexed streams** — thousands of concurrent TCP connections over a single tunnel
46
+ - ⚡ **QUIC datagrams** — UDP traffic forwarded via QUIC unreliable datagrams for lowest possible latency
44
47
  - 🔑 **Shared-secret authentication** — edges must present valid credentials to connect
45
- - 🎫 **Connection tokens** — encode all connection details into a single opaque string
46
- - 📡 **STUN-based public IP discovery** — the edge automatically discovers its public IP via Cloudflare STUN
48
+ - 🎫 **Connection tokens** — encode all connection details into a single opaque base64url string
49
+ - 📡 **STUN-based public IP discovery** — edges automatically discover their public IP via Cloudflare STUN
47
50
  - 🔄 **Auto-reconnect** with exponential backoff if the tunnel drops
48
- - 🎛️ **Dynamic port configuration** — the hub assigns listen ports per edge and can hot-reload them at runtime via `FRAME_CONFIG` frames
51
+ - 🎛️ **Dynamic port configuration** — the hub assigns TCP and UDP listen ports per edge, hot-reloadable at runtime
49
52
  - 📣 **Event-driven** — both Hub and Edge extend `EventEmitter` for real-time monitoring
50
- - ⚡ **Rust core** — all frame encoding, TLS, and TCP proxying happen in native code for maximum throughput
51
53
  - 🎚️ **3-tier QoS** — control frames, normal data, and sustained (elephant flow) traffic each get their own priority queue
52
54
  - 📊 **Adaptive flow control** — per-stream windows scale with active stream count to prevent memory overuse
55
+ - 🕒 **UDP session management** — automatic session tracking with 60s idle timeout and cleanup
53
56
 
54
57
  ## 🚀 Usage
55
58
 
56
- 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.
59
+ Both classes are imported from the package and communicate with the Rust binary under the hood.
57
60
 
58
61
  ### Setting Up the Hub (Private Cluster Side)
59
62
 
@@ -63,32 +66,25 @@ import { RemoteIngressHub } from '@serve.zone/remoteingress';
63
66
  const hub = new RemoteIngressHub();
64
67
 
65
68
  // Listen for events
66
- hub.on('edgeConnected', ({ edgeId }) => {
67
- console.log(`Edge ${edgeId} connected`);
68
- });
69
- hub.on('edgeDisconnected', ({ edgeId }) => {
70
- console.log(`Edge ${edgeId} disconnected`);
71
- });
72
- hub.on('streamOpened', ({ edgeId, streamId }) => {
73
- console.log(`Stream ${streamId} opened from edge ${edgeId}`);
74
- });
75
- hub.on('streamClosed', ({ edgeId, streamId }) => {
76
- console.log(`Stream ${streamId} closed from edge ${edgeId}`);
77
- });
69
+ hub.on('edgeConnected', ({ edgeId }) => console.log(`Edge ${edgeId} connected`));
70
+ hub.on('edgeDisconnected', ({ edgeId }) => console.log(`Edge ${edgeId} disconnected`));
71
+ hub.on('streamOpened', ({ edgeId, streamId }) => console.log(`Stream ${streamId} from ${edgeId}`));
72
+ hub.on('streamClosed', ({ edgeId, streamId }) => console.log(`Stream ${streamId} closed`));
78
73
 
79
- // Start the hub — it will listen for incoming edge TLS connections
74
+ // Start the hub — listens for edge connections on both TCP and QUIC (same port)
80
75
  await hub.start({
81
76
  tunnelPort: 8443, // port edges connect to (default: 8443)
82
- targetHost: '127.0.0.1', // SmartProxy host to forward streams to (default: 127.0.0.1)
77
+ targetHost: '127.0.0.1', // SmartProxy host to forward traffic to
83
78
  });
84
79
 
85
- // Register which edges are allowed to connect, including their listen ports
80
+ // Register allowed edges with TCP and UDP listen ports
86
81
  await hub.updateAllowedEdges([
87
82
  {
88
83
  id: 'edge-nyc-01',
89
84
  secret: 'supersecrettoken1',
90
- listenPorts: [80, 443], // ports the edge should listen on
91
- stunIntervalSecs: 300, // STUN discovery interval (default: 300)
85
+ listenPorts: [80, 443], // TCP ports the edge should listen on
86
+ listenPortsUdp: [53, 51820], // UDP ports (e.g., DNS, WireGuard)
87
+ stunIntervalSecs: 300,
92
88
  },
93
89
  {
94
90
  id: 'edge-fra-02',
@@ -97,38 +93,29 @@ await hub.updateAllowedEdges([
97
93
  },
98
94
  ]);
99
95
 
100
- // Dynamically update ports for a connected edge — changes are pushed instantly
96
+ // Dynamically update ports — changes are pushed instantly to connected edges
101
97
  await hub.updateAllowedEdges([
102
98
  {
103
99
  id: 'edge-nyc-01',
104
100
  secret: 'supersecrettoken1',
105
- listenPorts: [80, 443, 8443], // added port 8443 — edge picks it up in real time
101
+ listenPorts: [80, 443, 8443], // added TCP port 8443
102
+ listenPortsUdp: [53], // removed WireGuard UDP port
106
103
  },
107
104
  ]);
108
105
 
109
- // Check status at any time
106
+ // Check status
110
107
  const status = await hub.getStatus();
111
- console.log(status);
112
- // {
113
- // running: true,
114
- // tunnelPort: 8443,
115
- // connectedEdges: [
116
- // { edgeId: 'edge-nyc-01', connectedAt: 1700000000, activeStreams: 12 }
117
- // ]
118
- // }
119
-
120
- // Graceful shutdown
108
+ // { running: true, tunnelPort: 8443, connectedEdges: [...] }
109
+
121
110
  await hub.stop();
122
111
  ```
123
112
 
124
113
  ### Setting Up the Edge (Network Edge Side)
125
114
 
126
- The edge can be configured in two ways: with an **opaque connection token** (recommended) or with explicit config fields.
115
+ The edge can connect via **TCP+TLS** (default) or **QUIC** transport.
127
116
 
128
117
  #### Option A: Connection Token (Recommended)
129
118
 
130
- A single token encodes all connection details — ideal for provisioning edges at scale:
131
-
132
119
  ```typescript
133
120
  import { RemoteIngressEdge } from '@serve.zone/remoteingress';
134
121
 
@@ -137,79 +124,64 @@ const edge = new RemoteIngressEdge();
137
124
  edge.on('tunnelConnected', () => console.log('Tunnel established'));
138
125
  edge.on('tunnelDisconnected', () => console.log('Tunnel lost — will auto-reconnect'));
139
126
  edge.on('publicIpDiscovered', ({ ip }) => console.log(`Public IP: ${ip}`));
140
- edge.on('portsAssigned', ({ listenPorts }) => console.log(`Listening on ports: ${listenPorts}`));
141
- edge.on('portsUpdated', ({ listenPorts }) => console.log(`Ports updated: ${listenPorts}`));
127
+ edge.on('portsAssigned', ({ listenPorts }) => console.log(`TCP ports: ${listenPorts}`));
142
128
 
143
- // Single token contains hubHost, hubPort, edgeId, and secret
144
129
  await edge.start({
145
- token: 'eyJoIjoiaHViLmV4YW1wbGUuY29tIiwicCI6ODQ0MywiZSI6ImVkZ2UtbnljLTAxIiwicyI6InN1cGVyc2VjcmV0dG9rZW4xIn0',
130
+ token: 'eyJoIjoiaHViLmV4YW1wbGUuY29tIiwi...',
146
131
  });
147
132
  ```
148
133
 
149
- #### Option B: Explicit Config
134
+ #### Option B: Explicit Config with QUIC Transport
150
135
 
151
136
  ```typescript
152
137
  import { RemoteIngressEdge } from '@serve.zone/remoteingress';
153
138
 
154
139
  const edge = new RemoteIngressEdge();
155
140
 
156
- edge.on('tunnelConnected', () => console.log('Tunnel established'));
157
- edge.on('tunnelDisconnected', () => console.log('Tunnel lost — will auto-reconnect'));
158
- edge.on('publicIpDiscovered', ({ ip }) => console.log(`Public IP: ${ip}`));
159
- edge.on('portsAssigned', ({ listenPorts }) => console.log(`Listening on ports: ${listenPorts}`));
160
- edge.on('portsUpdated', ({ listenPorts }) => console.log(`Ports updated: ${listenPorts}`));
161
-
162
141
  await edge.start({
163
- hubHost: 'hub.example.com', // hostname or IP of the hub
164
- hubPort: 8443, // must match hub's tunnelPort (default: 8443)
165
- edgeId: 'edge-nyc-01', // unique edge identifier
166
- secret: 'supersecrettoken1', // must match the hub's allowed edge secret
142
+ hubHost: 'hub.example.com',
143
+ hubPort: 8443,
144
+ edgeId: 'edge-nyc-01',
145
+ secret: 'supersecrettoken1',
146
+ transportMode: 'quic', // 'tcpTls' (default) | 'quic' | 'quicWithFallback'
167
147
  });
168
148
 
169
- // Check status at any time
170
149
  const edgeStatus = await edge.getStatus();
171
- console.log(edgeStatus);
172
- // {
173
- // running: true,
174
- // connected: true,
175
- // publicIp: '203.0.113.42',
176
- // activeStreams: 5,
177
- // listenPorts: [80, 443]
178
- // }
179
-
180
- // Graceful shutdown
150
+ // { running: true, connected: true, publicIp: '203.0.113.42', activeStreams: 5, listenPorts: [80, 443] }
151
+
181
152
  await edge.stop();
182
153
  ```
183
154
 
155
+ #### Transport Modes
156
+
157
+ | Mode | Description |
158
+ |------|-------------|
159
+ | `'tcpTls'` | **Default.** Single TLS connection with frame-based multiplexing. Universal compatibility. |
160
+ | `'quic'` | QUIC with native stream multiplexing. Eliminates head-of-line blocking. Uses QUIC datagrams for UDP traffic. |
161
+ | `'quicWithFallback'` | Tries QUIC first (5s timeout), falls back to TCP+TLS if UDP is blocked by the network. |
162
+
184
163
  ### 🎫 Connection Tokens
185
164
 
186
- 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.
165
+ Encode all connection details into a single opaque string for easy distribution:
187
166
 
188
167
  ```typescript
189
168
  import { encodeConnectionToken, decodeConnectionToken } from '@serve.zone/remoteingress';
190
169
 
191
- // Hub side: generate a token for a new edge
170
+ // Hub operator generates a token
192
171
  const token = encodeConnectionToken({
193
172
  hubHost: 'hub.example.com',
194
173
  hubPort: 8443,
195
174
  edgeId: 'edge-nyc-01',
196
175
  secret: 'supersecrettoken1',
197
176
  });
198
- console.log(token);
199
177
  // => 'eyJoIjoiaHViLmV4YW1wbGUuY29tIiwi...'
200
178
 
201
- // Edge side: inspect a token (optional — start() does this automatically)
179
+ // Edge operator decodes (optional — start() does this automatically)
202
180
  const data = decodeConnectionToken(token);
203
- console.log(data);
204
- // {
205
- // hubHost: 'hub.example.com',
206
- // hubPort: 8443,
207
- // edgeId: 'edge-nyc-01',
208
- // secret: 'supersecrettoken1'
209
- // }
181
+ // { hubHost: 'hub.example.com', hubPort: 8443, edgeId: 'edge-nyc-01', secret: '...' }
210
182
  ```
211
183
 
212
- Tokens are base64url-encoded (URL-safe, no padding) — safe to pass as environment variables, CLI arguments, or store in config files.
184
+ Tokens are base64url-encoded — safe for environment variables, CLI arguments, and config files.
213
185
 
214
186
  ## 📖 API Reference
215
187
 
@@ -217,10 +189,10 @@ Tokens are base64url-encoded (URL-safe, no padding) — safe to pass as environm
217
189
 
218
190
  | Method / Property | Description |
219
191
  |-------------------|-------------|
220
- | `start(config?)` | Spawns the Rust binary and starts the tunnel listener. Config: `{ tunnelPort?: number, targetHost?: string }` |
221
- | `stop()` | Gracefully shuts down the hub and kills the Rust process. |
222
- | `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. |
223
- | `getStatus()` | Returns current hub status including connected edges and active stream counts. |
192
+ | `start(config?)` | Start the hub. Config: `{ tunnelPort?: number, targetHost?: string }`. Listens on both TCP and UDP (QUIC) on the tunnel port. |
193
+ | `stop()` | Graceful shutdown. |
194
+ | `updateAllowedEdges(edges)` | Set authorized edges. Each: `{ id, secret, listenPorts?, listenPortsUdp?, stunIntervalSecs? }`. Port changes are pushed to connected edges in real time. |
195
+ | `getStatus()` | Returns `{ running, tunnelPort, connectedEdges: [...] }`. |
224
196
  | `running` | `boolean` — whether the Rust binary is alive. |
225
197
 
226
198
  **Events:** `edgeConnected`, `edgeDisconnected`, `streamOpened`, `streamClosed`
@@ -229,9 +201,9 @@ Tokens are base64url-encoded (URL-safe, no padding) — safe to pass as environm
229
201
 
230
202
  | Method / Property | Description |
231
203
  |-------------------|-------------|
232
- | `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. |
233
- | `stop()` | Gracefully shuts down the edge and kills the Rust process. |
234
- | `getStatus()` | Returns current edge status including connection state, public IP, listen ports, and active streams. |
204
+ | `start(config)` | Connect to hub. Accepts `{ token }` or `{ hubHost, hubPort, edgeId, secret, transportMode? }`. |
205
+ | `stop()` | Graceful shutdown. |
206
+ | `getStatus()` | Returns `{ running, connected, publicIp, activeStreams, listenPorts }`. |
235
207
  | `running` | `boolean` — whether the Rust binary is alive. |
236
208
 
237
209
  **Events:** `tunnelConnected`, `tunnelDisconnected`, `publicIpDiscovered`, `portsAssigned`, `portsUpdated`
@@ -240,8 +212,8 @@ Tokens are base64url-encoded (URL-safe, no padding) — safe to pass as environm
240
212
 
241
213
  | Function | Description |
242
214
  |----------|-------------|
243
- | `encodeConnectionToken(data)` | Encodes `IConnectionTokenData` into a base64url token string. |
244
- | `decodeConnectionToken(token)` | Decodes a token back into `IConnectionTokenData`. Throws on malformed or incomplete tokens. |
215
+ | `encodeConnectionToken(data)` | Encodes connection info into a base64url token. |
216
+ | `decodeConnectionToken(token)` | Decodes a token. Throws on malformed input. |
245
217
 
246
218
  ### Interfaces
247
219
 
@@ -256,6 +228,8 @@ interface IEdgeConfig {
256
228
  hubPort?: number; // default: 8443
257
229
  edgeId: string;
258
230
  secret: string;
231
+ bindAddress?: string;
232
+ transportMode?: 'tcpTls' | 'quic' | 'quicWithFallback';
259
233
  }
260
234
 
261
235
  interface IConnectionTokenData {
@@ -268,7 +242,9 @@ interface IConnectionTokenData {
268
242
 
269
243
  ## 🔌 Wire Protocol
270
244
 
271
- The tunnel uses a custom binary frame protocol over TLS:
245
+ ### TCP+TLS Transport (Frame Protocol)
246
+
247
+ The tunnel uses a custom binary frame protocol over a single TLS connection:
272
248
 
273
249
  ```
274
250
  [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:
276
252
 
277
253
  | Frame Type | Value | Direction | Purpose |
278
254
  |------------|-------|-----------|---------|
279
- | `OPEN` | `0x01` | Edge → Hub | Open a new stream; payload is PROXY v1 header |
280
- | `DATA` | `0x02` | Edge → Hub | Client data flowing upstream |
281
- | `CLOSE` | `0x03` | Edge → Hub | Client closed the connection |
282
- | `DATA_BACK` | `0x04` | Hub → Edge | Response data flowing downstream |
283
- | `CLOSE_BACK` | `0x05` | Hub → Edge | Upstream (SmartProxy) closed the connection |
284
- | `CONFIG` | `0x06` | Hub → Edge | Runtime configuration update (e.g. port changes); payload is JSON |
285
- | `PING` | `0x07` | Hub → Edge | Heartbeat probe (sent every 15s) |
255
+ | `OPEN` | `0x01` | Edge → Hub | Open TCP stream; payload is PROXY v1 header |
256
+ | `DATA` | `0x02` | Edge → Hub | Client data (upload) |
257
+ | `CLOSE` | `0x03` | Edge → Hub | Client closed connection |
258
+ | `DATA_BACK` | `0x04` | Hub → Edge | Response data (download) |
259
+ | `CLOSE_BACK` | `0x05` | Hub → Edge | Upstream closed connection |
260
+ | `CONFIG` | `0x06` | Hub → Edge | Runtime config update (JSON payload) |
261
+ | `PING` | `0x07` | Hub → Edge | Heartbeat probe (every 15s) |
286
262
  | `PONG` | `0x08` | Edge → Hub | Heartbeat response |
287
- | `WINDOW_UPDATE` | `0x09` | Edge → Hub | Per-stream flow control: edge consumed N bytes, hub can send more |
288
- | `WINDOW_UPDATE_BACK` | `0x0A` | Hub → Edge | Per-stream flow control: hub consumed N bytes, edge can send more |
263
+ | `WINDOW_UPDATE` | `0x09` | Edge → Hub | Flow control: edge consumed N bytes |
264
+ | `WINDOW_UPDATE_BACK` | `0x0A` | Hub → Edge | Flow control: hub consumed N bytes |
265
+ | `UDP_OPEN` | `0x0B` | Edge → Hub | Open UDP session; payload is PROXY v2 header |
266
+ | `UDP_DATA` | `0x0C` | Edge → Hub | UDP datagram (upload) |
267
+ | `UDP_DATA_BACK` | `0x0D` | Hub → Edge | UDP datagram (download) |
268
+ | `UDP_CLOSE` | `0x0E` | Either | Close UDP session |
269
+
270
+ ### QUIC Transport
289
271
 
290
- Max payload size per frame: **16 MB**. Stream IDs are 32-bit unsigned integers.
272
+ When using QUIC, the frame protocol is replaced by native QUIC primitives:
273
+
274
+ - **TCP connections:** Each tunneled TCP connection gets its own QUIC bidirectional stream. No framing overhead.
275
+ - **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]`.
276
+ - **Control channel:** First QUIC bidirectional stream carries auth handshake + config updates using `[type: 1][length: 4][payload]` format.
291
277
 
292
278
  ### Handshake Sequence
293
279
 
294
- 1. Edge opens a TLS connection to the hub
280
+ 1. Edge opens a TLS or QUIC connection to the hub
295
281
  2. Edge sends: `EDGE <edgeId> <secret>\n`
296
- 3. Hub verifies credentials (constant-time comparison) and responds with JSON: `{"listenPorts":[...],"stunIntervalSecs":300}\n`
297
- 4. Edge starts TCP listeners on the assigned ports
298
- 5. Frame protocol begins `OPEN`/`DATA`/`CLOSE` frames flow in both directions
299
- 6. Hub can push `CONFIG` frames at any time to update the edge's listen ports
282
+ 3. Hub verifies credentials (constant-time comparison) and responds with JSON:
283
+ `{"listenPorts":[...],"listenPortsUdp":[...],"stunIntervalSecs":300}\n`
284
+ 4. Edge starts TCP and UDP listeners on the assigned ports
285
+ 5. Data flows TCP frames/QUIC streams for TCP traffic, UDP frames/QUIC datagrams for UDP traffic
300
286
 
301
287
  ## 🎚️ QoS & Flow Control
302
288
 
303
- The tunnel multiplexer uses a **3-tier priority system** and **per-stream flow control** to ensure fair bandwidth sharing across thousands of concurrent streams.
304
-
305
- ### Priority Tiers
306
-
307
- All outbound frames are queued into one of three priority levels:
289
+ ### Priority Tiers (TCP+TLS Transport)
308
290
 
309
- | Tier | Queue | Frames | Behavior |
310
- |------|-------|--------|----------|
311
- | 🔴 **Control** (highest) | `ctrl_queue` | PING, PONG, WINDOW_UPDATE, OPEN, CLOSE, CONFIG | Always drained first. Never delayed. |
312
- | 🟡 **Data** (normal) | `data_queue` | DATA, DATA_BACK from normal streams | Drained when ctrl is empty. Gated at 64 buffered items for backpressure. |
313
- | 🟢 **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. |
314
-
315
- 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.
291
+ | Tier | Frames | Behavior |
292
+ |------|--------|----------|
293
+ | 🔴 **Control** | PING, PONG, WINDOW_UPDATE, OPEN, CLOSE, CONFIG | Always drained first. Never delayed. |
294
+ | 🟡 **Data** | DATA/DATA_BACK from normal streams, UDP frames | Drained when control queue is empty. |
295
+ | 🟢 **Sustained** | DATA/DATA_BACK from elephant flows | Lowest priority with guaranteed **1 MB/s** drain rate. |
316
296
 
317
297
  ### Sustained Stream Classification
318
298
 
319
- A stream is automatically classified as **sustained** (elephant flow) when:
320
- - It has been active for **>10 seconds**, AND
321
- - Its average throughput exceeds **20 Mbit/s** (2.5 MB/s)
299
+ A TCP stream is classified as **sustained** (elephant flow) when:
300
+ - Active for **>10 seconds**, AND
301
+ - Average throughput exceeds **20 Mbit/s** (2.5 MB/s)
322
302
 
323
- 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.
303
+ Once classified, its flow control window locks to 1 MB and data frames move to the lowest-priority queue.
324
304
 
325
305
  ### Adaptive Per-Stream Windows
326
306
 
327
- 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**:
307
+ Each TCP stream has a send window from a shared **200 MB budget**:
328
308
 
329
309
  | Active Streams | Window per Stream |
330
310
  |---|---|
331
311
  | 1–50 | 4 MB (maximum) |
332
- | 51–100 | Scales down (4 MB → 2 MB) |
312
+ | 51–200 | Scales down (4 MB → 1 MB) |
333
313
  | 200+ | 1 MB (floor) |
334
314
 
335
- 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.
315
+ UDP traffic uses no flow control datagrams are fire-and-forget, matching UDP semantics.
336
316
 
337
317
  ## 💡 Example Scenarios
338
318
 
339
- ### 1. Expose a Private Kubernetes Cluster to the Internet
319
+ ### 1. Expose a Private Cluster to the Internet
340
320
 
341
- 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.
321
+ 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.
342
322
 
343
323
  ### 2. Multi-Region Edge Ingress
344
324
 
345
- 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.
325
+ 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.
326
+
327
+ ### 3. UDP Forwarding (DNS, Gaming, VoIP)
328
+
329
+ 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.
346
330
 
347
- ### 3. Secure API Exposure
331
+ ```typescript
332
+ await hub.updateAllowedEdges([
333
+ {
334
+ id: 'edge-nyc-01',
335
+ secret: 'secret',
336
+ listenPorts: [80, 443], // TCP
337
+ listenPortsUdp: [53, 27015], // DNS + game server
338
+ },
339
+ ]);
340
+ ```
348
341
 
349
- 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.
342
+ ### 4. QUIC Transport for Low-Latency
350
343
 
351
- ### 4. Token-Based Edge Provisioning
344
+ 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.
352
345
 
353
- 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.
346
+ ```typescript
347
+ await edge.start({
348
+ hubHost: 'hub.example.com',
349
+ hubPort: 8443,
350
+ edgeId: 'edge-01',
351
+ secret: 'secret',
352
+ transportMode: 'quicWithFallback', // try QUIC, fall back to TLS if UDP blocked
353
+ });
354
+ ```
355
+
356
+ ### 5. Token-Based Edge Provisioning
357
+
358
+ Generate connection tokens on the hub side and distribute them to edge operators:
354
359
 
355
360
  ```typescript
356
- // Hub operator generates token
357
361
  const token = encodeConnectionToken({
358
362
  hubHost: 'hub.prod.example.com',
359
363
  hubPort: 8443,
360
364
  edgeId: 'edge-tokyo-01',
361
365
  secret: 'generated-secret-abc123',
362
366
  });
363
- // Send `token` to the edge operator via secure channel
367
+ // Send `token` to the edge operator a single string is all they need
364
368
 
365
- // Edge operator starts with just the token
366
369
  const edge = new RemoteIngressEdge();
367
370
  await edge.start({ token });
368
371
  ```
369
372
 
370
- ### 5. Dynamic Port Management
371
-
372
- 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.
373
-
374
- ```typescript
375
- // Initially assign ports 80 and 443
376
- await hub.updateAllowedEdges([
377
- { id: 'edge-nyc-01', secret: 'secret', listenPorts: [80, 443] },
378
- ]);
379
-
380
- // Later, add port 8080 — the connected edge picks it up instantly
381
- await hub.updateAllowedEdges([
382
- { id: 'edge-nyc-01', secret: 'secret', listenPorts: [80, 443, 8080] },
383
- ]);
384
- ```
385
-
386
373
  ## License and Legal Information
387
374
 
388
375
  This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/remoteingress',
6
- version: '4.12.1',
7
- 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.'
6
+ version: '4.13.0',
7
+ 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.'
8
8
  }