Status#
Accepted
Status note (2026-04-18 backfill): Prior version of this page carried status "In Progress," which conflated the decision state with the implementation state. The decision was accepted at authorship; Phase 1 (Tokio native APIs) has since shipped via PRs #75, #80, and #81. Implementation status belongs in Git/CI, not the ADR.
Context#
DaemonEye historically implemented IPC communication between procmond and daemoneye-agent using custom hand-rolled code:
- Custom Unix socket server (~400 lines) with manual varint length framing, CRC32 validation, and connection management
- Custom Windows named pipe server with platform-specific error handling
- Complex protocol layer with custom message parsing and flow control
This imposed a real maintenance burden. Specific issues:
| Issue | Impact |
|---|---|
| High maintenance burden | Complex custom networking code duplicates standard library functionality |
| Platform complexity | Separate implementations for Unix and Windows with different error handling |
| Limited testing | Custom implementation lacks battle-testing compared to standard libraries |
| Development velocity | Time spent on IPC infrastructure instead of core security features |
| Requirements that any replacement must preserve: |
- Security — all current security features (socket permissions, connection limits, timeouts, max frame size).
- Protocol compatibility — existing protobuf message format and CRC32 validation.
- Performance — no regression in message throughput or latency.
- Rollback capability — ability to revert to the legacy implementation if issues arise.
Decision#
Migrate in two phases.
Phase 1 — Simplify with Tokio native APIs#
Replace custom socket implementations with standard Tokio APIs:
| Platform | API |
|---|---|
| Unix | tokio::net::UnixListener / tokio::net::UnixStream |
| Windows | tokio::net::windows::named_pipe |
Preserve the existing protobuf + CRC32 framing protocol. Feature flags: ipc-interprocess (default) vs ipc-legacy (rollback). |
Phase 2 — Interprocess crate migration (future)#
Evaluate and potentially migrate to interprocess::local_socket for true cross-platform unification once Phase 1 is validated. This is optional — Phase 1 already solves the maintenance and reliability issues; Phase 2 buys additional uniformity.
Implementation details#
Transport layer configuration:
pub struct IpcConfig {
pub transport: TransportType,
pub endpoint_path: String, // Unix socket path or Windows pipe name
pub max_frame_bytes: usize, // 1MB default
pub accept_timeout_ms: u64, // 5 seconds
pub read_timeout_ms: u64, // 30 seconds
pub write_timeout_ms: u64, // 10 seconds
pub max_connections: usize, // 16 default
pub crc32_variant: Crc32Variant, // IEEE default
}
Wire protocol (preserved from legacy):
Frame format (little-endian):
├── u32 length (N) of protobuf message bytes
├── u32 crc32 checksum over message bytes
└── N bytes protobuf message (prost-encoded)
Security preservation:
| Control | Implementation |
|---|---|
| Unix socket permissions | Directory 0700, socket 0600 (owner-only access) |
| Connection limits | Tokio semaphore-based connection limiting |
| Timeouts | Per-operation timeouts with graceful degradation |
| Network isolation | Local sockets/pipes only, verified by tests |
Rollback strategy: feature flag ipc-legacy maintains original implementation; config toggle ipc.transport = "legacy"; runtime telemetry identifies active transport; zero protocol changes enable hot rollback. |
Consequences#
Positive#
- Reduced complexity. ~600 lines of custom code replaced with ~200 lines using standard APIs.
- Better reliability. Tokio's battle-tested networking stack.
- Improved maintainability. Standard error handling and lifecycle management.
- Enhanced debugging. Standard tooling (strace, pipe inspection, socket utilities) works with Tokio sockets.
- Future extensibility. Phase 2 enables true cross-platform unification and additional transports (shared memory, enhanced named-pipe support).
Negative#
- Performance regression risk. Likelihood low — Tokio APIs are highly optimized. Mitigation: comprehensive benchmarks comparing old vs new; acceptance criterion
<5%latency regression. - Platform-specific issues. Likelihood medium — different behavior across platforms. Mitigation: extensive cross-platform testing (Linux, macOS, Windows); acceptance criterion all current tests pass on all platforms.
- Security boundary changes. Likelihood low — same permission model maintained. Mitigation: security-focused testing with permission validation; acceptance criterion identical access control and privilege separation.
- Interprocess crate limitations (Phase 2). Likelihood medium — newer crate with less real-world usage. Mitigation: Phase 1 with Tokio provides stable fallback; revert to Phase 1 if Phase 2 issues arise.
Neutral#
- Success metrics for Phase 1:
- Functional — all existing IPC tests pass; zero protocol changes required; feature-flag rollback works; cross-platform compatibility maintained.
- Performance —
<5%latency regression; equal or better throughput; memory within 10% of current. - Security — Unix socket permissions enforced (0700 dir, 0600 socket); connection limits respected under load; all timeouts trigger appropriately; no unintended network listeners.
- Operational — zero breaking changes for operators; configuration backward compatibility; logging and metrics parity.
- Implementation status at time of writing: Phase 1 shipped via PRs #75, #80, #81 (Tokio native APIs on Unix + Windows named pipes). Phase 2 evaluation has not started; no current plan to proceed unless Phase 1 reveals specific gaps.
Alternatives Considered#
Keep the custom implementation#
No migration risk, complete control over every byte of the protocol. Ongoing maintenance burden and limited feature expansion; platform-specific bugs that battle-tested crates have already fixed; time spent on IPC plumbing instead of security features. Rejected — the maintenance cost is the reason this ADR exists.
Direct migration to the interprocess crate#
Single migration effort, immediate cross-platform benefits via one API. Higher risk (bigger diff, less opportunity to identify regressions), less mature ecosystem at the time, harder rollback if the interprocess crate has a bug that surfaces only under DaemonEye's traffic pattern. Rejected — phased approach (Tokio-native first, interprocess second) reduces risk at the cost of two migrations.
gRPC or other RPC framework#
Industry standard, rich features, good tooling (code generation, streaming, deadline propagation). Protocol breaking changes (existing protobuf + CRC32 framing would be replaced), network-stack dependency even for local IPC, heavy overhead for a two-process local-only use case. Rejected — protocol compatibility is a hard requirement and local-only IPC doesn't justify the gRPC machinery.