A lightweight, RFC-first service mesh proxy written in Rust. Zero-config mTLS, automatic protocol detection, policy-based authorization, and ALPN-negotiated multiplexed tunnels that amortize the TLS handshake across all connections to a peer (−63 % proxy CPU under connection churn). Designed for 1M devices — from cloud servers to smartphones.
graph LR
subgraph Cloud
A[Service A<br/>Go/Node/Rust] --- P1[interlink proxy]
end
subgraph Edge
B[Service B<br/>Python/Java] --- P2[interlink proxy]
end
P1 -- mTLS encrypted --- P2
style P1 fill:#4a9eff,stroke:#2a6fcc
style P2 fill:#4a9eff,stroke:#2a6fcc
# Generate certificates
cargo run --example ca_bootstrap
# Terminal 1: start backend
cargo run --example backend -- /tmp/interlink-demo 8443
# Terminal 2: connect frontend
cargo run --example frontend -- /tmp/interlink-demo localhost:8443Expected output:
mTLS connected! Backend identity = spiffe://example.local/ns/default/sa/backend
Sent: Hello from frontend!
Received: backend-echo[frontend]: Hello from frontend!
# Run with defaults (reads env vars and an optional config file)
cargo run --bin interlinkd
# Override specific settings
INTERLINK_INBOUND_PORT=4143 \
INTERLINK_OUTBOUND_PORT=4140 \
INTERLINK_ADMIN_PORT=4192 \
INTERLINK_CA_CERT=/etc/interlink/ca.der \
INTERLINK_CA_KEY=/etc/interlink/ca.key \
cargo run --bin interlinkdSee Configuration and the full Quickstart Guide for the full set of options.
For a step-by-step walkthrough, see the Quickstart Guide.
interlinkd loads configuration from two sources, in order of increasing precedence:
- A JSON config file (default:
/etc/interlink/config.json) - Environment variables prefixed with
INTERLINK_
Example config file:
{
"inbound_port": 4143,
"outbound_port": 4140,
"admin_port": 4192,
"ca_cert_path": "/etc/interlink/ca.der",
"ca_key_path": "/etc/interlink/ca.key",
"node_name": "worker-01",
"cluster_domain": "cluster.local",
"metrics_enabled": true
}Environment variable equivalents:
| Variable | Description | Default |
|---|---|---|
INTERLINK_PROXY_INBOUND_PORT |
Inbound transparent proxy port | 4143 |
INTERLINK_PROXY_OUTBOUND_PORT |
Outbound transparent proxy port (0 disables) | 4140 |
INTERLINK_METRICS_PORT |
Prometheus metrics port | 4190 |
INTERLINK_TRUST_DOMAIN |
SPIFFE trust domain | cluster.local |
INTERLINK_IDENTITY |
This proxy's SPIFFE ID | derived |
INTERLINK_CA_BUNDLE_PATH |
CA bundle (DER) for peer validation | — |
INTERLINK_CERT_PATH / INTERLINK_KEY_PATH |
Leaf cert (DER) / key (PKCS#8 DER) | — |
INTERLINK_DEFAULT_UPSTREAM |
Fallback upstream when SO_ORIGINAL_DST is unavailable |
— |
INTERLINK_MUX |
Offer multiplexed tunnels (ALPN il/mux/1) to peers |
true |
INTERLINK_ACCEPTORS |
SO_REUSEPORT acceptor tasks per listener (1–16) | min(cores,4) |
INTERLINK_COPY_BUF_SIZE |
Relay copy buffer size in bytes (4 KiB–1 MiB) | 65536 |
INTERLINK_MAX_CONNECTIONS |
Per-proxy connection limit | 1024 |
INTERLINK_CONFIG_FILE |
Path to JSON config file | /etc/interlink/config.json |
Runtime reload of policy and certificate settings is available via POST /reload on the admin port.
For deployment topologies and detailed connection flows, see the Architecture Overview and Key Management docs.
flowchart TB
subgraph Proxy["interlink Proxy (per-pod sidecar)"]
direction TB
subgraph Inbound["Inbound (port 4143)"]
L[TCP Listener] --> H[TLS 1.3 Handshake<br/>RFC 8446]
H --> M{ALPN<br/>il/mux/1?}
M -- yes --> Y[Serve yamux streams<br/>one tunnel, many conns]
M -- no --> I
Y --> I[Identity Extraction<br/>RFC 5280 SAN]
I --> P[Policy Engine<br/>default-deny]
P --> D[Protocol Detection<br/>HTTP/1.1 / HTTP/2 / TCP]
D --> F[Forward to Upstream]
end
subgraph Outbound["Outbound (port 4140)"]
O[TCP Listener] --> OM[Orig. Dst Lookup]
OM --> OT{Mux tunnel<br/>to peer?}
OT -- yes --> OS[Open stream<br/>no handshake]
OT -- no --> OH[TLS 1.3 Handshake<br/>ALPN il/mux/1 + client cert]
OH --> OF[Tunnel or 1:1 relay]
OS --> OF
end
subgraph Admin["Admin (port 4192)"]
AD[HTTP Server] --> AH["/healthz /readyz /reload"]
end
end
Peer[Peer interlink<br/>proxy] -- mTLS --> L
Client[Application<br/>Container] -- plaintext --> O
F --> App[Local App<br/>Container]
OF -- mTLS --> RemotePeer[Peer interlink<br/>proxy]
style Proxy fill:#1a1a2e,stroke:#4a9eff
sequenceDiagram
participant C as Client App
participant P as interlink Proxy
participant U as Upstream Service
Note over C,P: TCP connection (app → sidecar port)
C->>+P: TCP SYN
P-->>-C: SYN-ACK
Note over C,P: mTLS Handshake (RFC 8446 §2)
C->>P: ClientHello + key_share
P->>C: ServerHello + key_share + CertificateRequest
P->>C: Certificate (spiffe://trust/ns/default/sa/proxy)
P->>C: CertificateVerify + Finished
C->>P: Certificate (spiffe://trust/ns/default/sa/client)
C->>P: CertificateVerify + Finished
Note over C,P: Encrypted tunnel established
C->>P: GET /api HTTP/1.1 (encrypted)
Note over P,U: Forward to upstream
P->>U: GET /api HTTP/1.1
U->>P: HTTP 200 OK (response)
P->>C: HTTP 200 OK (encrypted)
Note over C,P: Policy: allow
Detailed RFC compliance notes live in docs/rfcs/.
| RFC | Title | Usage |
|---|---|---|
| RFC 8446 | TLS 1.3 | mTLS handshake, cipher suites, key exchange |
| RFC 5280 | X.509 PKI | Certificate profiles, SAN encoding, path validation |
| RFC 9110 | HTTP Semantics | Method tokens, status codes, header handling |
| RFC 9112 | HTTP/1.1 | Request-line format, persistent connections |
| RFC 9113 | HTTP/2 | Frame format, multiplexing, connection preface |
| RFC 1034 | DNS Concepts | Hierarchical namespace, resolution algorithm |
| RFC 1035 | DNS Implementation | Message format, resource records |
| RFC 9293 | TCP | Connection management, congestion control |
interlink/
├── Cargo.toml # Dependencies (tokio, rustls, ring, rcgen, hickory-resolver, metrics, serde_json)
├── README.md # This file
├── lore/ # Architecture decisions & RFC plans
│ ├── ideation.md # Component breakdown, resource budget
│ ├── architecture-decisions.md # ADR-0001 through ADR-0008
│ ├── rfc-8446-tls1.3.md # TLS 1.3 implementation plan
│ ├── rfc-5280-x509.md # X.509 PKI implementation plan
│ ├── rfc-911x-http.md # HTTP/1.1 + HTTP/2 plan
│ └── rfc-103x-dns.md # DNS implementation plan
├── docs/ # Documentation
│ ├── architecture/ # Deployment topologies, network flows
│ ├── rfcs/ # RFC summaries and compliance
│ ├── examples/ # Example walkthroughs
│ └── benchmarks/ # Performance data
├── src/
│ ├── lib.rs # Module root
│ ├── common/ # Identity types, errors, constants
│ │ ├── identity.rs # SpiffeId, TrustDomain, IdentityProvider
│ │ ├── error.rs # InterlinkError enum
│ │ ├── constants.rs # Ports, timeouts, buffer sizes
│ │ └── config.rs # Runtime configuration
│ ├── identity/
│ │ ├── ca/mod.rs # CertificateAuthority (Ed25519, rcgen)
│ │ └── provider/ # K8s/environment identity providers
│ ├── proxy/
│ │ ├── handshake.rs # TlsHandshake trait, TlsClient, TlsServer
│ │ ├── tcp.rs # Inbound TcpProxy (accept, mTLS, forward, copy)
│ │ ├── outbound.rs # Outbound proxy: mux tunnel pool + 1:1 fallback
│ │ ├── mux.rs # Multiplexed mTLS tunnels (yamux over ALPN il/mux/1), per-peer tunnel pool
│ │ ├── verify.rs # SpiffeServerVerifier: RFC 5280 chain + SPIFFE identity (no RFC 6125 name match)
│ │ ├── original_dst.rs # SO_ORIGINAL_DST helper (optional transparent mode, self-connect guarded)
│ │ └── config.rs # ProxyConfig
│ ├── admin/mod.rs # Admin HTTP server (/healthz, /readyz, /reload)
│ ├── protocol/mod.rs # ProtocolDetector (H1, H2, TCP)
│ ├── discovery/dns.rs # ServiceDiscovery (hickory-resolver)
│ ├── policy/mod.rs # PolicyEngine (default-deny, wildcards)
│ └── metrics/mod.rs # Prometheus exporter, atomic counters
├── examples/
│ ├── ca_bootstrap.rs # Generate CA + certs
│ ├── backend.rs # mTLS echo server
│ └── frontend.rs # mTLS client
├── scripts/
│ └── demo.sh # End-to-end demo runner
├── tests/
│ ├── e2e_proxy_test.rs # Full mTLS handshake + echo through proxy
│ ├── mtls_handshake.rs # SPIFFE, policy, TLS-resumption tests
│ ├── mux_tunnel.rs # Mux tunnels: 1 handshake for N conns, pool past 512 streams, fallback
│ ├── spiffe_verify.rs # SPIFFE server verification: identity accept, untrusted-CA/wrong-domain reject
│ ├── halfclose_propagation.rs # FIN/close_notify propagation through the relay
│ └── proxy_shutdown.rs # Accept-loop shutdown + bind-failure handling
└── benches/
├── proxy.rs # Protocol detection, SPIFFE parse benchmarks
└── crypto.rs # Ed25519 sign/verify, X25519 keygen
./scripts/preflight.sh # clippy -D warnings + full test suite (pre-commit gate)
cargo test # 85 tests across unit + integration suites
cargo bench # Micro-benchmarks
bash scripts/demo.sh # End-to-end mTLS demo| Suite | What it covers |
|---|---|
| Unit (54) | SpiffeId, compiled policy patterns, CA, DNS single-flight (gated-fake concurrency), metrics, config |
| E2E (31) | mTLS handshake + echo through the proxy, TLS 1.3 resumption (Full→Resumed), mux tunnels (1 handshake for N connections, 700 concurrent streams across the tunnel pool, legacy-peer fallback), SPIFFE server verification (accepts peer dialed by non-SAN address; rejects untrusted CA and wrong trust domain), half-close propagation, shutdown + bind-failure handling |
See docs/benchmarks/results.md for methodology.
policy_engine/evaluate time: [40.6 ns] (compiled patterns; was 8.2 µs string-matched)
pattern_match/compiled time: [25.4 ns]
protocol_detection/http1.1 time: [0.84 ns]
spiffe_id/parse time: [174 ns] (validated try_new — the only constructor)
Hot-path numbers that matter more than microbenches: TLS 1.3 session resumption is
verified end-to-end (Full → Resumed), and multiplexed tunnels carry ~19,000
connections over a single TLS handshake (measured: −63 % combined proxy CPU and −46 %
p50 latency at 500 conns/s churn versus per-connection handshakes — see
docs/performance-plan.md).
A reproducible benchmark harness lives in bench/. All three meshes are
deployed the same way — as per-pod sidecars (interlink and Linkerd sidecars; Istio
ambient's per-node ztunnel) — on one 3-node kind cluster
(bench/manifests/kind-3node.yaml) with the load generator and echo server pinned to
separate worker nodes so mesh traffic always crosses the node boundary. Identical
workload (Go echo server, 200 ms fixed delay, 1 KB payload, Fortio), identical load, and
CPU/memory sampled per proxy container via kubectl top. Every profile below passed the
validity gate (achieved RPS within 3 % of target, zero errors, host CPU < 85 %).
Measured 2026-07-08 (60 s/profile, 3-node kind on a 16-core host):
| Mesh | 320 rps | 800 rps | 1600 rps |
|---|---|---|---|
| p99 latency overhead (above the 200 ms base) | |||
| interlink | +4.0 ms | +7.3 ms | +9.9 ms |
| Linkerd | +8.6 ms | +18.1 ms | +25.8 ms |
| Istio ambient | +14.0 ms | +9.1 ms | +13.9 ms |
| proxy CPU (avg millicores) | |||
| interlink | 15.4 | 31.8 | 47.7 |
| Linkerd | 25.3 | 67.7 | 121.8 |
| Istio ambient | 28.1 | 56.4 | 92.4 |
| proxy memory (avg MB) | |||
| interlink | 21.3 | 43.7 | 94.1 |
| Linkerd | 13.9 | 26.3 | 42.0 |
| Istio ambient | 8.3 | 11.8 | 18.7 |
Takeaways. interlink adds the least latency overhead and uses the least CPU
(~2.5× less than Linkerd, ~2× less than Istio ambient at 1600 rps) — the multiplexed
mTLS tunnels amortize the handshake. The trade-off is memory (94 MB vs Istio's 19 MB at
1600 rps): a verified allocator high-water effect of the 64 KiB relay buffers under
connection churn, not live state — root cause, reproduction, and options in
lore/benchmark-status.md. Full table and methodology:
bench/results/comparison.md.
Reproduce:
cd bench && ./setup.sh # pinned kind/kubectl/linkerd/istioctl
BENCH_PROFILES="320:80,800:200,1600:400" ./scripts/run-interlink.sh
BENCH_PROFILES="320:80,800:200,1600:400" ./scripts/run-linkerd.sh
BENCH_PROFILES="320:80,800:200,1600:400" ./scripts/run-istio.sh
python3 scripts/aggregate.py # -> bench/results/comparison.md (gated)Also measured on the single-proxy microbenchmark harness: compiled policy evaluation at 40.5 ns, verified TLS 1.3 resumption, and multiplexed tunnels carrying ~19,000 connections over a single handshake (−63 % proxy CPU) under connection churn.
Committed to the public domain. See UNLICENSE or LICENSE file.
- SPIFFE — Secure Production Identity Framework for Everyone
- IETF RFC Editor — All referenced RFCs
- Linkerd — Inspiration for the sidecar proxy pattern
- rustls — TLS library in Rust
- rcgen — X.509 certificate generation