Files
fips/src/bin/fipstop/client.rs
T
Johnathan Corgan 77ac8c822e Add fipstop TUI monitoring tool with smoothed metrics and quality indices
fipstop: ratatui-based TUI for real-time monitoring of a running FIPS daemon.

Tabs and navigation:
- 8 navigable tabs: Node, Peers, Transports, Sessions, Tree, Filters,
  Performance, Routing
- Tab/BackTab navigation with group separators in tab bar
- Table views with selectable rows, detail drill-down panels, and scrollbars

Node tab:
- Runtime info: pid, exe path, uptime, control socket path, TUN adapter name
- Identity: npub, node_addr, ipv6 address
- State summary with peer/session/link/transport/connection counts
- TUN IPv6 traffic and forwarded transit traffic counters

Peers tab:
- Table with Name, Address, Conn, Depth, SRTT, Loss, LQI, Pkts Tx/Rx
- Detail panel: identity, connection info, transport cross-reference,
  tree/bloom state, link stats, MMP metrics with LQI

Sessions tab:
- Table with Name, Remote Addr, State, Role, SRTT, Loss, SQI, Path MTU,
  Last Activity
- Detail panel: identity, session info, traffic stats, MMP metrics with SQI

Transports tab:
- Hierarchical tree view: expandable transport parents with nested links
  (▼/▶ indicators, ├─/└─ tree chars, Space/Arrow to expand/collapse)
- Transport detail: type-specific stats (UDP/TCP/Ethernet)
- Link detail: peer cross-reference with MMP metrics and LQI

Performance tab:
- Link-layer MMP: SRTT, loss, ETX, LQI, goodput per peer
- Session-layer MMP: SRTT, loss, ETX, SQI, path MTU per session
- Trend indicators (rising/falling/stable) with context-aware coloring

Routing tab:
- Routing state: cache sizes, pending lookups, recent requests
- Coordinate cache: entries, fill ratio, TTL, expiry, avg age
- Statistics: forwarding, discovery request/response, error signal counters

Tree tab:
- Spanning tree position with 16 announce stats (inbound/outbound/cumulative)

Filters tab:
- Bloom filter announce stats, per-peer fill ratio and estimated node count

MMP metrics enhancements:
- Add etx_trend DualEwma for smoothed ETX tracking
- Add smoothed_loss() and smoothed_etx() accessors (long-term EWMA)
- LQI (Link Quality Index) = smoothed_etx * (1 + srtt_ms / 100)
- SQI (Session Quality Index) = same formula for session layer
- All loss/ETX displays prefer smoothed values with raw fallback

Control socket:
- Add smoothed_loss, smoothed_etx, lqi/sqi to show_peers, show_sessions,
  and show_mmp JSON responses
- Rename fips_address to ipv6_addr in show_status and show_peers
- Add tun_name and control_socket to show_status
- FHS-compliant 3-tier default path: $XDG_RUNTIME_DIR, /run/fips, /tmp

Node extensions:
- Add started_at/uptime() to Node
- Add tun_name() accessor

Docker sidecar updates:
- TCP transport support via FIPS_PEER_TRANSPORT env var
- Build scripts include fipstop binary
2026-03-01 16:33:33 +00:00

66 lines
2.0 KiB
Rust

use serde_json::Value;
use std::path::{Path, PathBuf};
use std::time::Duration;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::UnixStream;
use tokio::time::timeout;
const IO_TIMEOUT: Duration = Duration::from_secs(5);
pub struct ControlClient {
socket_path: PathBuf,
}
impl ControlClient {
pub fn new(socket_path: &Path) -> Self {
Self {
socket_path: socket_path.to_path_buf(),
}
}
pub async fn query(&self, command: &str) -> Result<Value, String> {
let stream = timeout(IO_TIMEOUT, UnixStream::connect(&self.socket_path))
.await
.map_err(|_| "connection timed out".to_string())?
.map_err(|e| format!("connect: {e}"))?;
let (reader, mut writer) = stream.into_split();
let request = format!("{{\"command\":\"{command}\"}}\n");
timeout(IO_TIMEOUT, writer.write_all(request.as_bytes()))
.await
.map_err(|_| "write timed out".to_string())?
.map_err(|e| format!("write: {e}"))?;
writer
.shutdown()
.await
.map_err(|e| format!("shutdown: {e}"))?;
let mut buf_reader = BufReader::new(reader);
let mut line = String::new();
timeout(IO_TIMEOUT, buf_reader.read_line(&mut line))
.await
.map_err(|_| "read timed out".to_string())?
.map_err(|e| format!("read: {e}"))?;
let response: Value =
serde_json::from_str(line.trim()).map_err(|e| format!("parse: {e}"))?;
let status = response
.get("status")
.and_then(|v| v.as_str())
.unwrap_or("unknown");
if status == "error" {
let msg = response
.get("message")
.and_then(|v| v.as_str())
.unwrap_or("unknown error");
return Err(msg.to_string());
}
Ok(response.get("data").cloned().unwrap_or(Value::Null))
}
}