feat(readme): document dynamic port assignment and runtime port updates; clarify TLS multiplexing, frame format, and handshake sequence
This commit is contained in:
@@ -1,5 +1,14 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-02-18 - 3.3.0 - feat(readme)
|
||||
document dynamic port assignment and runtime port updates; clarify TLS multiplexing, frame format, and handshake sequence
|
||||
|
||||
- Adds documentation for dynamic port configuration: hub-assigned listen ports, hot-reloadable via FRAME_CONFIG frames
|
||||
- Introduces new FRAME type CONFIG (0x06) and describes payload as JSON; notes immediate push of port changes to connected edges
|
||||
- Clarifies that the tunnel is a single encrypted TLS multiplexed connection to the hub (preserves PROXY v1 behavior)
|
||||
- Specifies frame integer fields are big-endian and that stream IDs are 32-bit unsigned integers
|
||||
- Adds new events: portsAssigned and portsUpdated, and updates examples showing updateAllowedEdges usage and live port changes
|
||||
|
||||
## 2026-02-18 - 3.2.1 - fix(tests)
|
||||
add comprehensive unit and async tests across Rust crates and TypeScript runtime
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"author": "Task Venture Capital GmbH",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "(tstest test/ --web)",
|
||||
"test": "(tstest test/ --verbose --logfile --timeout 60)",
|
||||
"build": "(tsbuild tsfolders --allowimplicitany && tsrust)",
|
||||
"buildDocs": "(tsdoc)"
|
||||
},
|
||||
|
||||
77
readme.md
77
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 to a DcRouter SmartProxy instance, preserving the original client IP via PROXY protocol v1.
|
||||
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.
|
||||
|
||||
## Issue Reporting and Security
|
||||
|
||||
@@ -22,8 +22,8 @@ pnpm install @serve.zone/remoteingress
|
||||
│ │ (multiplexed frames + │ │
|
||||
│ RemoteIngressEdge │ shared-secret auth) │ RemoteIngressHub │
|
||||
│ Accepts client TCP │ │ Forwards to │
|
||||
│ connections │ │ SmartProxy on │
|
||||
│ │ │ local ports │
|
||||
│ connections on │ │ SmartProxy on │
|
||||
│ hub-assigned ports │ │ local ports │
|
||||
└─────────────────────┘ └─────────────────────┘
|
||||
▲ │
|
||||
│ TCP from end users ▼
|
||||
@@ -32,8 +32,8 @@ pnpm install @serve.zone/remoteingress
|
||||
|
||||
| Component | Role |
|
||||
|-----------|------|
|
||||
| **RemoteIngressEdge** | Deployed at the network edge (e.g. a VPS or cloud instance). Accepts raw TCP connections and multiplexes them over a single TLS tunnel to the hub. |
|
||||
| **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. |
|
||||
| **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. |
|
||||
| **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
|
||||
@@ -45,6 +45,7 @@ pnpm install @serve.zone/remoteingress
|
||||
- 🎫 **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
|
||||
- 🔄 **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
|
||||
- 📣 **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
|
||||
|
||||
@@ -79,10 +80,28 @@ await hub.start({
|
||||
targetHost: '127.0.0.1', // SmartProxy host to forward streams to (default: 127.0.0.1)
|
||||
});
|
||||
|
||||
// Register which edges are allowed to connect
|
||||
// Register which edges are allowed to connect, including their listen ports
|
||||
await hub.updateAllowedEdges([
|
||||
{ id: 'edge-nyc-01', secret: 'supersecrettoken1' },
|
||||
{ id: 'edge-fra-02', secret: 'supersecrettoken2' },
|
||||
{
|
||||
id: 'edge-nyc-01',
|
||||
secret: 'supersecrettoken1',
|
||||
listenPorts: [80, 443], // ports the edge should listen on
|
||||
stunIntervalSecs: 300, // STUN discovery interval (default: 300)
|
||||
},
|
||||
{
|
||||
id: 'edge-fra-02',
|
||||
secret: 'supersecrettoken2',
|
||||
listenPorts: [443, 8080],
|
||||
},
|
||||
]);
|
||||
|
||||
// Dynamically update ports for a connected edge — changes are pushed instantly
|
||||
await hub.updateAllowedEdges([
|
||||
{
|
||||
id: 'edge-nyc-01',
|
||||
secret: 'supersecrettoken1',
|
||||
listenPorts: [80, 443, 8443], // added port 8443 — edge picks it up in real time
|
||||
},
|
||||
]);
|
||||
|
||||
// Check status at any time
|
||||
@@ -116,6 +135,8 @@ 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}`));
|
||||
|
||||
// Single token contains hubHost, hubPort, edgeId, and secret
|
||||
await edge.start({
|
||||
@@ -133,6 +154,8 @@ 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
|
||||
@@ -194,7 +217,7 @@ Tokens are base64url-encoded (URL-safe, no padding) — safe to pass as environm
|
||||
|-------------------|-------------|
|
||||
| `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. Each edge: `{ id: string, secret: string }` |
|
||||
| `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. |
|
||||
| `running` | `boolean` — whether the Rust binary is alive. |
|
||||
|
||||
@@ -204,12 +227,12 @@ 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`. |
|
||||
| `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, and active streams. |
|
||||
| `getStatus()` | Returns current edge status including connection state, public IP, listen ports, and active streams. |
|
||||
| `running` | `boolean` — whether the Rust binary is alive. |
|
||||
|
||||
**Events:** `tunnelConnected`, `tunnelDisconnected`, `publicIpDiscovered`
|
||||
**Events:** `tunnelConnected`, `tunnelDisconnected`, `publicIpDiscovered`, `portsAssigned`, `portsUpdated`
|
||||
|
||||
### Token Utilities
|
||||
|
||||
@@ -246,7 +269,7 @@ interface IConnectionTokenData {
|
||||
The tunnel uses a custom binary frame protocol over TLS:
|
||||
|
||||
```
|
||||
[stream_id: 4 bytes][type: 1 byte][length: 4 bytes][payload: N bytes]
|
||||
[stream_id: 4 bytes BE][type: 1 byte][length: 4 bytes BE][payload: N bytes]
|
||||
```
|
||||
|
||||
| Frame Type | Value | Direction | Purpose |
|
||||
@@ -256,8 +279,18 @@ The tunnel uses a custom binary frame protocol over TLS:
|
||||
| `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 |
|
||||
|
||||
Max payload size per frame: **16 MB**.
|
||||
Max payload size per frame: **16 MB**. Stream IDs are 32-bit unsigned integers.
|
||||
|
||||
### Handshake Sequence
|
||||
|
||||
1. Edge opens a TLS connection to the hub
|
||||
2. Edge sends: `EDGE <edgeId> <secret>\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
|
||||
|
||||
## 💡 Example Scenarios
|
||||
|
||||
@@ -292,6 +325,22 @@ 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.
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/remoteingress',
|
||||
version: '3.2.1',
|
||||
version: '3.3.0',
|
||||
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.'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user