- Rust 97.2%
- Shell 1.9%
- Just 0.7%
- Dockerfile 0.2%
| .cargo | ||
| .config | ||
| .github/workflows | ||
| docs | ||
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| deny.toml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| justfile | ||
| LICENSE-MIT | ||
| README.md | ||
Zenoh TCP Bridge
A bidirectional bridge that connects TCP services to the Zenoh distributed data bus. Export TCP backends as Zenoh services or import Zenoh services as TCP listeners.
Overview
This bridge enables:
- Export Mode: Expose TCP backend services (e.g., databases, APIs) over Zenoh
- Import Mode: Make Zenoh services accessible via TCP listeners
- Multiple Services: Run multiple exports and imports simultaneously in a single bridge instance
- Automatic Connection Management: Lazy connections with liveliness detection
Features
Core Functionality
- Lazy Connections: Backend connections are created only when clients connect (export mode)
- Per-Client Isolation: Each client gets dedicated Zenoh pub/sub channels and backend connection
- Concurrent Services: Handle multiple services in one bridge process
- Flexible Configuration: Command-line arguments or Zenoh config files
- WebSocket Support: Bridge WebSocket backends alongside TCP services
- Graceful Shutdown: Clean shutdown via CancellationToken with per-task tracking and connection draining
- Backend Reconnection: Exponential backoff retry when backends become unavailable
- Input Validation: Early CLI validation (buffer size, timeouts, spec formats, log options) with clear error messages
- Strict TLS Parsing: RFC 6066/1035-compliant SNI validation with size bounds checking
HTTP/HTTPS Routing
- DNS-Based Routing: Route HTTP requests by Host header to different backends
- SNI-Based HTTPS: Route HTTPS traffic by SNI without terminating TLS
- Automatic Protocol Detection: Detects HTTP vs HTTPS automatically
- DNS Normalization: Case-insensitive, port-aware routing
- Multiple Backends: One listener can route to N different HTTP/HTTPS servers
- End-to-End TLS: HTTPS traffic is never decrypted by the bridge
- Per-Request Multiroute: HTTP/1.1 keep-alive connections can route to different backends per request
- Optional TLS Termination: Decrypt HTTPS at the bridge with
--https-terminate(requirestls-terminationfeature)
Protocol Auto-Detection
- Auto-Import Mode: Single listener detects TLS/HTTPS, HTTP, WebSocket, or raw TCP from the first bytes
- Zero Configuration: No need to pre-declare protocol per listener
Liveliness Detection
- Automatic client presence tracking using Zenoh liveliness tokens
- Clean disconnection handling and resource cleanup
- Backend connection lifecycle tied to client presence
Multiple Zenoh Modes
- Peer Mode (default): Direct peer-to-peer communication
- Client Mode: Connect to existing Zenoh routers
- Router Mode: Act as a Zenoh router
Architecture
Export Mode
TCP Backend (e.g., HTTP server on :8003)
↕
Zenoh Bridge (Export)
↕
Zenoh Network
↕
Zenoh Bridge (Import)
↕
TCP Client connects to :8002
The exporter:
- Monitors for client liveliness tokens on
{service}/clients/{client_id} - Creates backend connection when client appears
- Subscribes to
{service}/tx/{client_id}(data from client) - Publishes to
{service}/rx/{client_id}(data to client) - Cleans up when client disconnects
Import Mode
The importer:
- Listens for TCP connections on specified address
- For each connection, creates unique client ID
- Declares liveliness token at
{service}/clients/{client_id} - Publishes to
{service}/tx/{client_id}(data from TCP client) - Subscribes to
{service}/rx/{client_id}(data to TCP client) - Undeclares liveliness on disconnection
Quick Start
Export a TCP Service
Make a local HTTP server accessible over Zenoh:
# Terminal 1: Start your HTTP server
python3 -m http.server 8003
# Terminal 2: Export it as "webserver" service
zenoh-bridge-tcp --export 'webserver/127.0.0.1:8003'
Import a Zenoh Service
Make the Zenoh service accessible via TCP:
# Terminal 3: Import "webserver" and listen on :8002
zenoh-bridge-tcp --import 'webserver/127.0.0.1:8002'
# Terminal 4: Test with curl
curl http://127.0.0.1:8002
Multiple Services
Run multiple exports/imports in one bridge:
zenoh-bridge-tcp \
--export 'api/127.0.0.1:3000' \
--export 'db/127.0.0.1:5432' \
--import 'frontend/0.0.0.0:8080' \
--mode peer
Building
Prerequisites
- Rust 1.85 or later (edition 2024)
Build
cargo build --release
The binary will be at target/release/zenoh-bridge-tcp.
Build with TLS Termination
cargo build --release --features tls-termination
Run Tests
# Run all tests (recommended: use nextest for better test isolation)
cargo nextest run
# Run unit tests only
cargo test --lib
# Run a specific integration test suite
cargo nextest run --test http_routing_integration
Usage
Command Line Options
zenoh-bridge-tcp [OPTIONS]
Options:
-c, --config <FILE> Path to Zenoh configuration file (JSON5)
--export <SPEC> Export TCP backend as Zenoh service
Format: 'service_name/backend_addr'
Example: 'myapi/127.0.0.1:3000'
--import <SPEC> Import Zenoh service as TCP listener
Format: 'service_name/listen_addr'
Example: 'myapi/0.0.0.0:8080'
--http-export <SPEC> Export HTTP backend with DNS-based routing
Format: 'service_name/dns/backend_addr'
Example: 'http-service/api.example.com/127.0.0.1:8000'
--http-import <SPEC> Import HTTP service with DNS-based routing
Format: 'service_name/listen_addr'
Example: 'http-service/0.0.0.0:8080'
--http-multiroute-import <SPEC> Import HTTP with per-request Host routing
Format: 'service_name/listen_addr'
Example: 'http-service/0.0.0.0:8080'
--auto-import <SPEC> Auto-detect protocol and route accordingly
Format: 'service_name/listen_addr'
Example: 'myservice/0.0.0.0:8080'
--https-terminate <SPEC> Import HTTPS with TLS termination (feature: tls-termination)
Format: 'service_name/listen_addr'
Example: 'https-service/0.0.0.0:8443'
--tls-cert <PATH> TLS certificate for --https-terminate
--tls-key <PATH> TLS private key for --https-terminate
--ws-export <SPEC> Export WebSocket backend as Zenoh service
Format: 'service_name/ws_url'
Example: 'myws/ws://127.0.0.1:9000'
--ws-import <SPEC> Import Zenoh service as WebSocket listener
Format: 'service_name/listen_addr'
Example: 'myws/0.0.0.0:8080'
-m, --mode <MODE> Zenoh mode: peer, client, or router [default: peer]
-e, --connect <ENDPOINT> Zenoh connect endpoint (e.g., tcp/localhost:7447)
-l, --listen <ENDPOINT> Zenoh listen endpoint (e.g., tcp/0.0.0.0:7447)
--buffer-size <BYTES> Buffer size for read operations [default: 65536]
--read-timeout <SECS> Timeout for reading headers [default: 10]
--drain-timeout <SECS> Connection drain timeout [default: 5]
--log-level <LEVEL> Log level: trace, debug, info, warn, error [default: info]
--log-format <FORMAT> Log format: pretty, compact, json [default: pretty]
-h, --help Print help
-V, --version Print version
Export Specification Format
service_name/backend_address:port
Examples:
webserver/127.0.0.1:8080- Export local web serverapi/backend-host:3000- Export remote APIpostgres/localhost:5432- Export PostgreSQL database
Import Specification Format
service_name/listen_address:port
Examples:
webserver/0.0.0.0:8080- Listen on all interfacesapi/127.0.0.1:3000- Listen only on localhostservice/[::]:8080- Listen on IPv6
Configuration File
Use a Zenoh configuration file for advanced settings:
zenoh-bridge-tcp \
--config zenoh-config.json5 \
--export 'myservice/127.0.0.1:8003' \
--import 'myservice/0.0.0.0:8002'
Examples
Example 1: Traditional TCP Bridge for HTTP
# Terminal 1: Start HTTP server
python3 -m http.server 8003
# Terminal 2: Export side
zenoh-bridge-tcp --export 'http/127.0.0.1:8003'
# Terminal 3: Import side (can be on different machine)
zenoh-bridge-tcp --import 'http/127.0.0.1:8002' --connect tcp/exporter-host:7447
# Terminal 4: Test
curl http://127.0.0.1:8002
Example 2: HTTP Routing with Multiple Backends
# Terminal 1: Start multiple HTTP servers
python3 -m http.server 8001 # API backend
python3 -m http.server 8002 # Web backend
# Terminal 2: Export API backend for api.example.com
zenoh-bridge-tcp --http-export 'http-service/api.example.com/127.0.0.1:8001'
# Terminal 3: Export Web backend for web.example.com
zenoh-bridge-tcp --http-export 'http-service/web.example.com/127.0.0.1:8002'
# Terminal 4: Import (single listener routes to both backends)
zenoh-bridge-tcp --http-import 'http-service/0.0.0.0:8080'
# Terminal 5: Test routing by Host header
curl -H "Host: api.example.com" http://127.0.0.1:8080/ # -> API backend
curl -H "Host: web.example.com" http://127.0.0.1:8080/ # -> Web backend
Example 3: HTTPS Routing with SNI
# Start HTTPS backends with certificates
# (Configure your HTTPS servers for api.example.com and web.example.com)
# Export HTTPS backends
zenoh-bridge-tcp --http-export 'https-service/api.example.com/127.0.0.1:8443'
zenoh-bridge-tcp --http-export 'https-service/web.example.com/127.0.0.1:8444'
# Import (automatically detects HTTPS via SNI)
zenoh-bridge-tcp --http-import 'https-service/0.0.0.0:8443'
# Test - SNI determines routing (no TLS termination!)
curl https://api.example.com:8443/ --resolve api.example.com:8443:127.0.0.1
curl https://web.example.com:8443/ --resolve web.example.com:8443:127.0.0.1
Example 4: WebSocket Bridge
# Terminal 1: Start WebSocket echo server (using websocat or similar)
websocat -s 127.0.0.1:9000
# Terminal 2: Export WebSocket backend
zenoh-bridge-tcp --ws-export 'wsecho/ws://127.0.0.1:9000'
# Terminal 3: Import as WebSocket listener
zenoh-bridge-tcp --ws-import 'wsecho/0.0.0.0:8080'
# Terminal 4: Connect WebSocket client
websocat ws://127.0.0.1:8080
# Type messages - they go through Zenoh to the WebSocket backend
Example 5: Netcat Echo Test
# Terminal 1: Start netcat server
nc -l 8003
# Terminal 2: Export
zenoh-bridge-tcp --export 'echo/127.0.0.1:8003'
# Terminal 3: Import
zenoh-bridge-tcp --import 'echo/127.0.0.1:8002'
# Terminal 4: Client
nc 127.0.0.1 8002
# Type messages - they go through Zenoh to the server
Example 6: Multiple Services
zenoh-bridge-tcp \
--export 'web/127.0.0.1:8080' \
--export 'api/127.0.0.1:3000' \
--import 'frontend/0.0.0.0:9001' \
--import 'admin/0.0.0.0:9002' \
--mode peer
Example 7: With Zenoh Router
# Terminal 1: Start Zenoh router
zenohd
# Terminal 2: Export side (client mode)
zenoh-bridge-tcp \
--export 'service/127.0.0.1:8003' \
--mode client \
--connect tcp/localhost:7447
# Terminal 3: Import side (client mode)
zenoh-bridge-tcp \
--import 'service/0.0.0.0:8002' \
--mode client \
--connect tcp/localhost:7447
Docker Deployment
Build and run with Docker:
# Build image
docker build -t zenoh-bridge-tcp .
# Run with docker-compose
docker-compose up -d
# Test multi-bridge setup
docker-compose --profile multi-bridge up -d
Testing
The project includes comprehensive integration tests. Use cargo nextest run for best results (parallel execution with process isolation).
Test Suites
tests/export_import_integration.rs- Core export/import functionalitytests/tcp_sanity_tests.rs- Basic TCP sanity checkstests/http_integration.rs- HTTP/HTTPS service bridgingtests/liveliness_integration.rs- Liveliness detectiontests/multi_service_integration.rs- Multiple concurrent servicestests/http_routing_integration.rs- HTTP routing with multiple backendstests/https_routing_integration.rs- HTTPS routing with SNItests/http_edge_cases.rs- Edge cases and error handlingtests/http_multiroute_integration.rs- Per-request HTTP multiroutetests/ws_integration.rs- WebSocket bridgingtests/drain_integration.rs- Connection drain on shutdowntests/auto_import_integration.rs- Protocol auto-detectiontests/https_termination_integration.rs- TLS terminationtests/stress_test.rs- Load and stress testingtests/bug_demonstrations.rs- Verification tests for 16 audit bug fixestests/coverage_integration.rs- Large messages, partial transfers, concurrent clients, rapid connect/disconnect
Run tests:
# All tests (recommended)
cargo nextest run
# Unit tests only
cargo test --lib
# Specific test suite
cargo nextest run --test http_routing_integration
Network Topology Tests (nlink-lab)
End-to-end tests using nlink-lab to create isolated network namespaces with realistic WAN conditions. These require Linux with network namespace support and nlink-lab installed on the host.
# Raw TCP multi-hop: client -> import bridge -> zenoh -> export bridge -> backend
./tests/nlink/run-multi-hop-test.sh
# HTTP host-header routing multi-hop: multiple backends, single import listener
./tests/nlink/run-multi-hop-http-test.sh
# With WAN simulation
./tests/nlink/run-multi-hop-test.sh --wan-delay 100ms --wan-loss 1%
# Skip cargo build if already built
./tests/nlink/run-multi-hop-test.sh --skip-build
See tests/nlink/README.md for debugging tips and topology details.
See tests/README.md for detailed testing documentation and docs/HTTP_ROUTING_GUIDE.md for the HTTP/HTTPS routing guide.
Zenoh Key Expression Design
The bridge uses a structured key expression pattern:
Traditional TCP Mode
{service_name}/tx/{client_id} # Client -> Backend data
{service_name}/rx/{client_id} # Backend -> Client data
{service_name}/clients/{client_id} # Liveliness token
HTTP/HTTPS Mode (DNS-based routing)
{service_name}/{dns}/tx/{client_id} # Client -> Backend data (for specific DNS)
{service_name}/{dns}/rx/{client_id} # Backend -> Client data (for specific DNS)
{service_name}/{dns}/clients/{client_id} # Liveliness token (per DNS)
{service_name}/{dns}/available # Backend availability signal
Each TCP connection gets a unique client_id, ensuring isolation between clients. In HTTP/HTTPS mode, the {dns} component (extracted from Host header or SNI) enables routing to multiple backends through a single listener.
Logging
Control log verbosity with CLI flags or RUST_LOG environment variable:
# Using CLI flags (recommended)
zenoh-bridge-tcp --log-level debug --export 'service/127.0.0.1:8003'
# JSON format for production/log aggregation
zenoh-bridge-tcp --log-level info --log-format json --export 'service/127.0.0.1:8003'
# Compact format (less verbose)
zenoh-bridge-tcp --log-format compact --export 'service/127.0.0.1:8003'
# Using RUST_LOG (takes precedence over --log-level)
RUST_LOG=debug zenoh-bridge-tcp --export 'service/127.0.0.1:8003'
# Module-specific with RUST_LOG
RUST_LOG=zenoh_bridge_tcp=debug,zenoh=warn zenoh-bridge-tcp --export 'service/127.0.0.1:8003'
Log Formats
- pretty (default): Human-readable with colors
- compact: Single-line format, less verbose
- json: Structured JSON, ideal for log aggregation (ELK, Loki, etc.)
Performance Considerations
- Buffer Size: 64KB (65,536 bytes) default per connection
- Concurrent Connections: Limited by system resources (file descriptors, memory)
- Latency: Adds ~1-2ms overhead vs direct TCP (depends on Zenoh setup)
- Throughput: Tested with HTTP, HTTPS, and raw TCP; handles typical workloads well
Use Cases
Traditional TCP Bridging
- Service Discovery: Expose services without static IP addresses
- Network Abstraction: Abstract away network topology
- Cloud-Edge Bridging: Connect edge devices to cloud services via Zenoh
- Legacy Integration: Make TCP services Zenoh-native
- Multi-Region Deployment: Leverage Zenoh's peer-to-peer or routed mesh
- Protocol Bridging: Connect TCP clients to Zenoh-based backends
HTTP/HTTPS Routing
- Virtual Host Routing: Route HTTP traffic by hostname to different backends
- Multi-Tenant SaaS: Single listener routes customers to their dedicated backends
- API Gateway: Route API requests by domain to microservices
- SNI-Based Load Distribution: Distribute HTTPS traffic without TLS termination
- Development/Staging Environments: Route by hostname to different environments
- Hybrid Cloud: Route traffic to backends across different networks via Zenoh
Dependencies
Core dependencies:
zenoh1.8.0 - Zenoh distributed data buszenoh-ext- Extended pub/sub with reliability featurestokio- Async runtimetokio-util- CancellationToken for graceful shutdownclap- Command-line parsinganyhow/thiserror- Error handlingtracing/tracing-subscriber- Structured logging (pretty, compact, json)httparse- HTTP/1.x parser (for HTTP routing)tls-parser- TLS/SNI parser (for HTTPS routing)tokio-tungstenite- WebSocket supportfutures-util- Async stream utilitiesbackon- Retry with exponential backoffuuid- Unique client ID generation
Development/test dependencies include: axum, hyper, rustls, reqwest, futures for protocol testing.
Version Information
- Current Version: 0.5.0
- Zenoh Version: 1.8.0
- Rust Edition: 2024
- MSRV: 1.85 (required by edition 2024)
Quality Tools
The project uses standard Rust quality tools:
# Format code
cargo fmt
# Lint
cargo clippy
# Check dependencies
cargo deny check
Configuration files:
deny.toml- Dependency auditing
Feature Highlights
HTTP/HTTPS Routing Architecture
The HTTP/HTTPS routing feature enables DNS-based service discovery and routing:
How it works:
- Import side listens for HTTP/HTTPS connections
- Protocol detection: Automatically detects HTTP (text) vs HTTPS (TLS)
- DNS extraction:
- HTTP: Parses
Hostheader - HTTPS: Extracts SNI from TLS ClientHello (before encryption)
- HTTP: Parses
- DNS normalization: Converts to lowercase, strips default ports (80/443)
- Backend discovery: Queries Zenoh for backends registered with that DNS
- Routing: Forwards to correct backend via DNS-specific Zenoh keys
- Pass-through: For HTTPS, TLS handshake and data pass through unchanged
Benefits:
- One listener -> N backends (multi-tenant)
- No configuration changes needed for new backends
- HTTPS works without TLS termination (end-to-end encryption)
- Automatic DNS normalization (case-insensitive, port-aware)
- Backend availability detection (HTTP 502 when unavailable)
Documentation:
- HTTP/HTTPS Routing Guide - Complete guide with examples
Contributing
Contributions welcome! Please:
- Run
cargo fmtbefore committing - Ensure
cargo clippypasses - Add tests for new features
- Update documentation as needed
License
Licensed under the MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT).