Each SNMP GET/WALK now runs in a forked child process. If libnetsnmp
triggers a SIGSEGV or other fatal signal, only the child dies - the
parent agent logs the crash and continues operating for all other devices.
Key changes:
- C helper: snmp_get_isolated() and snmp_walk_isolated() using
fork+pipe pattern with 60s alarm watchdog and mutex-serialized forks
- Rust: IsolationMode enum (Fork/Direct) controlled by
TOWEROPS_SNMP_ISOLATION env var, defaults to Fork
- New CrashRecovered error variant with signal info and logging
- Device poller logs crash recovery events at error level
- Startup logs active isolation mode
Set TOWEROPS_SNMP_ISOLATION=direct to disable isolation for debugging.
The builder (rust:1.93-alpine) uses Alpine 3.23 but the runtime used
Alpine 3.19. The net-snmp-dev headers from 3.23 don't match
net-snmp-libs from 3.19, causing a segfault on the first SNMP call.
Also adds a SIGSEGV/SIGBUS/SIGABRT signal handler that prints a
diagnostic message instead of silently exiting with code 139.
Tests against unreachable hosts (192.0.2.1) were waiting 10s * 3 attempts
per request. With cfg(test) overrides (1s timeout, 0 retries), the full
test suite runs in ~3s instead of ~90s.
Both ring and aws-lc-rs were enabled transitively (webpki-roots enables
rustls default features), causing rustls to panic at runtime because it
couldn't auto-detect which crypto provider to use.
- Add rustls as direct dep with ring only, disable default features
- Switch russh from aws-lc-rs (default) to ring crypto backend
- Call ring::default_provider().install_default() at startup
- Add macOS build fix: detect Homebrew OpenSSL path for netsnmp's libcrypto
Switched from surge-ping Rust library to system ping command (iputils)
to avoid requiring CAP_NET_RAW capability for ICMP ping.
Changes:
- Use tokio::process::Command to execute /bin/ping or /bin/ping6
- Parse response time from ping output (time=X.XX ms)
- Removed surge-ping and rand dependencies from Cargo.toml
- Added tokio "process" feature
Benefits:
- No special capabilities required (ping binary has setuid root)
- Works in restricted container environments
- Already available in Docker image (iputils package)
- More portable across different container runtimes
The system ping command is already installed in the Docker image
and has setuid root permissions, allowing it to create raw sockets
without requiring the container to have CAP_NET_RAW.
Handle server-initiated restart (exit for Docker restart) and
self-update (download binary, verify SHA256, replace via exec)
commands received over the WebSocket channel. Report architecture
in heartbeat so the server can select the correct binary.
- Add restart and update handlers in websocket_client.rs
- Add self_update() with download, checksum verification, and exec
- Add arch field to AgentHeartbeat protobuf
- Add reqwest and sha2 dependencies
- chown binary in Dockerfile for non-root self-update
- Extract standalone binaries from Docker images in CI
- Create GitHub Releases with binary assets on version tags
Drop ratatui, crossterm, hostname dependencies and the tui feature flag.
Delete src/tui/ directory, remove event bus plumbing from websocket client,
and simplify connect/init_logger to single implementations.
-1,979 lines of conditional compilation removed.
- Map "Failed to parse OID" C library errors to InvalidOid variant
- Walk to unreachable host may return empty results instead of error
on some Linux configurations
Implements health monitoring in the agent using ICMP ping, allowing
devices assigned to local (non-cloud) agents to be monitored.
Changes:
- Add surge-ping library for async ICMP ping operations
- Create ping.rs module with ping_device() function (5s timeout)
- Rename JobType::MONITOR to JobType::PING in protobuf
- Add monitoring_check channels for result communication
- Implement execute_ping_job() to handle PING jobs
- Update TUI to display ping result events
The agent now receives PING jobs from Phoenix, executes ICMP pings,
and sends MonitoringCheck results back for storage in the database.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Implement get_local_ip() using UDP socket trick to resolve local
interface address without sending traffic, so heartbeat reports
actual agent IP instead of "unknown"
- Remove dead poller/scheduler/executor and api_client modules that
referenced non-existent modules and were never compiled
Discovery with SNMPv3 was completing in 52 seconds, dangerously close
to the 60s timeout. Increasing to 120s provides comfortable headroom
for complete MikroTik OID tree traversal with encryption overhead.
SNMPv3 has significant encryption and authentication overhead compared to
v2c. MikroTik enterprise tree walks (1.3.6.1.4.1.14988) can take 16+
seconds with SNMPv3, causing timeouts with the previous 30s limit.
Increased timeout from 30s to 60s in both client.rs and device_poller.rs
to accommodate SNMPv3's additional latency.
- Add trap.rs with BER/ASN.1 parser for SNMP trap PDUs
- Support both SNMPv1 and SNMPv2c trap formats
- Listen on configurable UDP port (default 162)
- Log received traps at INFO level
- Add LOG_LEVEL env var for log verbosity control
- Add TRAP_PORT env var and CLI flag
- Update docker-compose with trap port mapping