mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 16:24:45 +00:00
Implement hop-by-hop ECN congestion signaling through the FMP layer, transport-level congestion detection via kernel drop counters, and chaos harness integration for end-to-end validation. FMP/session ECN plumbing: - Thread ce_flag parsed at link layer through dispatch_link_message, handle_session_datagram, handle_session_payload, and handle_encrypted_session_msg to session delivery - Replace hardcoded false in session-layer record_recv() with actual ce_flag, activating ecn_ce_count tracking in session MMP ECN congestion detection and CE relay: - Add EcnConfig (node.ecn.*) with configurable loss_threshold (5%) and etx_threshold (3.0) for transit congestion detection - Add send_encrypted_link_message_with_ce() that ORs FLAG_CE into FMP header flags; original method delegates with ce_flag=false - Compute outgoing_ce = incoming_ce || local congestion on next-hop link, enabling hop-by-hop CE relay through transit nodes IPv6 ECN-CE marking: - Mark ECN-CE (0b11) in IPv6 Traffic Class on received DataPackets before TUN delivery when FMP CE flag is set - Only marks ECN-capable packets (ECT(0)/ECT(1)); Not-ECT packets unchanged per RFC 3168 Transport congestion abstraction and UDP kernel drop detection: - Add TransportCongestion struct to transport layer for transport- agnostic local congestion indicators - Replace tokio::UdpSocket with AsyncFd<socket2::Socket> using libc::recvmsg() with ancillary data parsing - Enable SO_RXQ_OVFL for kernel receive buffer drop counter on every packet, wiring up previously-stubbed UdpStats.kernel_drops - Add TransportDropState for per-transport delta tracking with 1s tick sampling via sample_transport_congestion() - Extend detect_congestion() with transport kernel drop check alongside MMP loss metrics Congestion monitoring and control: - Add CongestionStats (ce_forwarded, ce_received, congestion_detected, kernel_drop_events) to NodeStats with snapshot serialization - Wire counters into forwarding path, session handler, and transport drop sampling with rate-limited warn logging (5s interval) - Expose congestion data in show_routing control query and ecn_ce_count in show_mmp peer entries - Add congestion counters to fipstop routing tab in two-column layout Chaos harness integration: - Add query_routing(), query_transports(), snapshot_all_congestion() to chaos control module - Add congestion/kernel-drop log analysis in logs module - Add congestion-stress scenario: 10-node tree, 1 Mbps bandwidth, 5-10% netem loss, heavy iperf3 traffic - Add IngressConfig for tc ingress policing with per-peer policer filters simulating upstream bandwidth bottlenecks - Add iperf3 JSON result capture to traffic manager for throughput measurement across scenarios - Add ECN A/B test scenarios (ecn-ab-on/off.yaml) with ingress policing and comparison script - Enable TCP ECN negotiation (tcp_ecn=1 sysctl) in container entrypoint for end-to-end CE propagation Tests: - 10 ECN unit/integration tests: mark_ipv6_ecn_ce variants, CE relay chain (3-node propagation), EcnConfig serde roundtrip - 3 transport drop congestion detection unit tests Documentation: - Update fips-mesh-layer.md: replace outdated CE Echo stub with full ECN Congestion Signaling section covering detection logic, CE relay, IPv6 marking, session tracking, and monitoring counters - Update fips-configuration.md: add node.ecn.* parameter table and ecn block in complete reference YAML - Update fips-transport-layer.md: add Congestion Reporting section with TransportCongestion struct, congestion() trait method, and per-transport status; document AsyncFd/recvmsg/SO_RXQ_OVFL in UDP - Update chaos README: add congestion/ECN scenario docs, ingress traffic control, and iperf3 JSON capture sections - Update README.md: add ECN to features list and "What works today"; update transport and tooling entries
512 lines
19 KiB
Rust
512 lines
19 KiB
Rust
//! Control query implementations.
|
|
//!
|
|
//! Each function takes `&Node` and returns a `serde_json::Value`.
|
|
//! Query logic is kept separate from socket handling.
|
|
|
|
use crate::identity::encode_npub;
|
|
use crate::node::Node;
|
|
use serde_json::{json, Value};
|
|
|
|
/// Helper: get current Unix time in milliseconds.
|
|
fn now_ms() -> u64 {
|
|
std::time::SystemTime::now()
|
|
.duration_since(std::time::UNIX_EPOCH)
|
|
.map(|d| d.as_millis() as u64)
|
|
.unwrap_or(0)
|
|
}
|
|
|
|
/// Classify a DualEwma trend as "rising", "falling", or "stable".
|
|
fn trend_label(short: f64, long: f64) -> &'static str {
|
|
if !short.is_finite() || !long.is_finite() || long == 0.0 {
|
|
return "stable";
|
|
}
|
|
let ratio = short / long;
|
|
if ratio > 1.05 {
|
|
"rising"
|
|
} else if ratio < 0.95 {
|
|
"falling"
|
|
} else {
|
|
"stable"
|
|
}
|
|
}
|
|
|
|
/// `show_status` — Node overview.
|
|
pub fn show_status(node: &Node) -> Value {
|
|
let pid = std::process::id();
|
|
let exe_path = std::env::current_exe()
|
|
.map(|p| p.display().to_string())
|
|
.unwrap_or_else(|_| "-".into());
|
|
let uptime_secs = node.uptime().as_secs();
|
|
let fwd = node.stats().snapshot().forwarding;
|
|
|
|
json!({
|
|
"npub": node.npub(),
|
|
"node_addr": hex::encode(node.node_addr().as_bytes()),
|
|
"ipv6_addr": format!("{}", node.identity().address()),
|
|
"state": format!("{}", node.state()),
|
|
"is_leaf_only": node.is_leaf_only(),
|
|
"peer_count": node.peer_count(),
|
|
"session_count": node.session_count(),
|
|
"link_count": node.link_count(),
|
|
"transport_count": node.transport_count(),
|
|
"connection_count": node.connection_count(),
|
|
"tun_state": format!("{}", node.tun_state()),
|
|
"tun_name": node.tun_name().unwrap_or("-"),
|
|
"effective_ipv6_mtu": node.effective_ipv6_mtu(),
|
|
"control_socket": &node.config().node.control.socket_path,
|
|
"pid": pid,
|
|
"exe_path": exe_path,
|
|
"uptime_secs": uptime_secs,
|
|
"forwarding": serde_json::to_value(&fwd).unwrap_or_default(),
|
|
})
|
|
}
|
|
|
|
/// `show_peers` — Authenticated peers.
|
|
pub fn show_peers(node: &Node) -> Value {
|
|
let peers: Vec<Value> = node.peers().map(|peer| {
|
|
let node_addr = *peer.node_addr();
|
|
let addr_hex = hex::encode(node_addr.as_bytes());
|
|
|
|
let mut peer_json = json!({
|
|
"node_addr": addr_hex,
|
|
"npub": peer.npub(),
|
|
"display_name": node.peer_display_name(&node_addr),
|
|
"ipv6_addr": format!("{}", peer.address()),
|
|
"connectivity": format!("{}", peer.connectivity()),
|
|
"link_id": peer.link_id().as_u64(),
|
|
"authenticated_at_ms": peer.authenticated_at(),
|
|
"last_seen_ms": peer.last_seen(),
|
|
"has_tree_position": peer.has_tree_position(),
|
|
"has_bloom_filter": peer.filter_sequence() > 0,
|
|
"filter_sequence": peer.filter_sequence(),
|
|
});
|
|
|
|
// Add transport address if available
|
|
if let Some(addr) = peer.current_addr() {
|
|
peer_json["transport_addr"] = json!(format!("{}", addr));
|
|
}
|
|
|
|
// Add tree depth if available
|
|
if let Some(coords) = peer.coords() {
|
|
peer_json["tree_depth"] = json!(coords.depth());
|
|
}
|
|
|
|
// Add link stats
|
|
let stats = peer.link_stats();
|
|
peer_json["stats"] = json!({
|
|
"packets_sent": stats.packets_sent,
|
|
"packets_recv": stats.packets_recv,
|
|
"bytes_sent": stats.bytes_sent,
|
|
"bytes_recv": stats.bytes_recv,
|
|
});
|
|
|
|
// Add MMP metrics if available
|
|
if let Some(mmp) = peer.mmp() {
|
|
let mut mmp_json = json!({
|
|
"mode": format!("{}", mmp.mode()),
|
|
});
|
|
if let Some(srtt) = mmp.metrics.srtt_ms() {
|
|
mmp_json["srtt_ms"] = json!(srtt);
|
|
}
|
|
mmp_json["loss_rate"] = json!(mmp.metrics.loss_rate());
|
|
mmp_json["etx"] = json!(mmp.metrics.etx);
|
|
mmp_json["goodput_bps"] = json!(mmp.metrics.goodput_bps);
|
|
mmp_json["delivery_ratio_forward"] = json!(mmp.metrics.delivery_ratio_forward);
|
|
mmp_json["delivery_ratio_reverse"] = json!(mmp.metrics.delivery_ratio_reverse);
|
|
if let Some(smoothed_loss) = mmp.metrics.smoothed_loss() {
|
|
mmp_json["smoothed_loss"] = json!(smoothed_loss);
|
|
}
|
|
if let Some(smoothed_etx) = mmp.metrics.smoothed_etx() {
|
|
mmp_json["smoothed_etx"] = json!(smoothed_etx);
|
|
}
|
|
if let Some(srtt) = mmp.metrics.srtt_ms()
|
|
&& let Some(setx) = mmp.metrics.smoothed_etx()
|
|
{
|
|
mmp_json["lqi"] = json!(setx * (1.0 + srtt / 100.0));
|
|
}
|
|
peer_json["mmp"] = mmp_json;
|
|
}
|
|
|
|
peer_json
|
|
}).collect();
|
|
|
|
json!({ "peers": peers })
|
|
}
|
|
|
|
/// `show_links` — Active links.
|
|
pub fn show_links(node: &Node) -> Value {
|
|
let links: Vec<Value> = node.links().map(|link| {
|
|
let stats = link.stats();
|
|
json!({
|
|
"link_id": link.link_id().as_u64(),
|
|
"transport_id": link.transport_id().as_u32(),
|
|
"remote_addr": format!("{}", link.remote_addr()),
|
|
"direction": format!("{}", link.direction()),
|
|
"state": format!("{}", link.state()),
|
|
"created_at_ms": link.created_at(),
|
|
"stats": {
|
|
"packets_sent": stats.packets_sent,
|
|
"packets_recv": stats.packets_recv,
|
|
"bytes_sent": stats.bytes_sent,
|
|
"bytes_recv": stats.bytes_recv,
|
|
"last_recv_ms": stats.last_recv_ms,
|
|
},
|
|
})
|
|
}).collect();
|
|
|
|
json!({ "links": links })
|
|
}
|
|
|
|
/// `show_tree` — Spanning tree state.
|
|
pub fn show_tree(node: &Node) -> Value {
|
|
let tree = node.tree_state();
|
|
let my_coords = tree.my_coords();
|
|
let decl = tree.my_declaration();
|
|
|
|
// Build coords array as hex strings
|
|
let coords: Vec<String> = my_coords.entries()
|
|
.iter()
|
|
.map(|e| hex::encode(e.node_addr.as_bytes()))
|
|
.collect();
|
|
|
|
// Build peer tree data
|
|
let peers: Vec<Value> = tree.peer_ids().map(|peer_id| {
|
|
let mut peer_json = json!({
|
|
"node_addr": hex::encode(peer_id.as_bytes()),
|
|
"display_name": node.peer_display_name(peer_id),
|
|
});
|
|
if let Some(coords) = tree.peer_coords(peer_id) {
|
|
let coord_path: Vec<String> = coords.entries()
|
|
.iter()
|
|
.map(|e| hex::encode(e.node_addr.as_bytes()))
|
|
.collect();
|
|
peer_json["depth"] = json!(coords.depth());
|
|
peer_json["root"] = json!(hex::encode(coords.root_id().as_bytes()));
|
|
peer_json["coords"] = json!(coord_path);
|
|
peer_json["distance_to_us"] = json!(my_coords.distance_to(coords));
|
|
}
|
|
peer_json
|
|
}).collect();
|
|
|
|
// Determine parent display name
|
|
let parent_addr = my_coords.parent_id();
|
|
let parent_hex = hex::encode(parent_addr.as_bytes());
|
|
let parent_display = node.peer_display_name(parent_addr);
|
|
|
|
let tree_stats = node.stats().snapshot().tree;
|
|
|
|
json!({
|
|
"my_node_addr": hex::encode(tree.my_node_addr().as_bytes()),
|
|
"root": hex::encode(tree.root().as_bytes()),
|
|
"is_root": tree.is_root(),
|
|
"depth": my_coords.depth(),
|
|
"my_coords": coords,
|
|
"parent": parent_hex,
|
|
"parent_display_name": parent_display,
|
|
"declaration_sequence": decl.sequence(),
|
|
"declaration_signed": decl.is_signed(),
|
|
"peer_tree_count": tree.peer_count(),
|
|
"peers": peers,
|
|
"stats": serde_json::to_value(&tree_stats).unwrap_or_default(),
|
|
})
|
|
}
|
|
|
|
/// `show_sessions` — End-to-end sessions.
|
|
pub fn show_sessions(node: &Node) -> Value {
|
|
let sessions: Vec<Value> = node.session_entries().map(|(addr, entry)| {
|
|
let state_str = if entry.is_established() {
|
|
"established"
|
|
} else if entry.is_initiating() {
|
|
"initiating"
|
|
} else if entry.is_awaiting_msg3() {
|
|
"awaiting_msg3"
|
|
} else {
|
|
"unknown"
|
|
};
|
|
|
|
let mut session_json = json!({
|
|
"remote_addr": hex::encode(addr.as_bytes()),
|
|
"display_name": node.peer_display_name(addr),
|
|
"state": state_str,
|
|
"is_initiator": entry.is_initiator(),
|
|
"last_activity_ms": entry.last_activity(),
|
|
});
|
|
|
|
// Derive npub from session's remote public key
|
|
let (xonly, _parity) = entry.remote_pubkey().x_only_public_key();
|
|
session_json["npub"] = json!(encode_npub(&xonly));
|
|
|
|
// Traffic counters
|
|
let (pkts_tx, pkts_rx, bytes_tx, bytes_rx) = entry.traffic_counters();
|
|
session_json["stats"] = json!({
|
|
"packets_sent": pkts_tx,
|
|
"packets_recv": pkts_rx,
|
|
"bytes_sent": bytes_tx,
|
|
"bytes_recv": bytes_rx,
|
|
});
|
|
|
|
// Add session MMP if available
|
|
if let Some(mmp) = entry.mmp() {
|
|
let mut mmp_json = json!({
|
|
"mode": format!("{}", mmp.mode()),
|
|
"loss_rate": mmp.metrics.loss_rate(),
|
|
"etx": mmp.metrics.etx,
|
|
"goodput_bps": mmp.metrics.goodput_bps,
|
|
"delivery_ratio_forward": mmp.metrics.delivery_ratio_forward,
|
|
"delivery_ratio_reverse": mmp.metrics.delivery_ratio_reverse,
|
|
"path_mtu": mmp.path_mtu.current_mtu(),
|
|
});
|
|
if let Some(srtt) = mmp.metrics.srtt_ms() {
|
|
mmp_json["srtt_ms"] = json!(srtt);
|
|
}
|
|
if let Some(smoothed_loss) = mmp.metrics.smoothed_loss() {
|
|
mmp_json["smoothed_loss"] = json!(smoothed_loss);
|
|
}
|
|
if let Some(smoothed_etx) = mmp.metrics.smoothed_etx() {
|
|
mmp_json["smoothed_etx"] = json!(smoothed_etx);
|
|
}
|
|
if let Some(srtt) = mmp.metrics.srtt_ms()
|
|
&& let Some(setx) = mmp.metrics.smoothed_etx()
|
|
{
|
|
mmp_json["sqi"] = json!(setx * (1.0 + srtt / 100.0));
|
|
}
|
|
session_json["mmp"] = mmp_json;
|
|
}
|
|
|
|
session_json
|
|
}).collect();
|
|
|
|
json!({ "sessions": sessions })
|
|
}
|
|
|
|
/// `show_bloom` — Bloom filter state.
|
|
pub fn show_bloom(node: &Node) -> Value {
|
|
let bloom = node.bloom_state();
|
|
|
|
let leaf_deps: Vec<String> = bloom.leaf_dependents()
|
|
.iter()
|
|
.map(|addr| hex::encode(addr.as_bytes()))
|
|
.collect();
|
|
|
|
// Build per-peer filter info
|
|
let peer_filters: Vec<Value> = node.peers().map(|peer| {
|
|
let addr = *peer.node_addr();
|
|
let mut pf = json!({
|
|
"peer": hex::encode(addr.as_bytes()),
|
|
"display_name": node.peer_display_name(&addr),
|
|
"has_filter": peer.filter_sequence() > 0,
|
|
"filter_sequence": peer.filter_sequence(),
|
|
});
|
|
if let Some(filter) = peer.inbound_filter() {
|
|
pf["estimated_count"] = json!(filter.estimated_count());
|
|
pf["set_bits"] = json!(filter.count_ones());
|
|
pf["fill_ratio"] = json!(filter.fill_ratio());
|
|
}
|
|
pf
|
|
}).collect();
|
|
|
|
let bloom_stats = node.stats().snapshot().bloom;
|
|
|
|
json!({
|
|
"own_node_addr": hex::encode(node.node_addr().as_bytes()),
|
|
"is_leaf_only": node.is_leaf_only(),
|
|
"sequence": bloom.sequence(),
|
|
"leaf_dependent_count": bloom.leaf_dependents().len(),
|
|
"leaf_dependents": leaf_deps,
|
|
"peer_filters": peer_filters,
|
|
"stats": serde_json::to_value(&bloom_stats).unwrap_or_default(),
|
|
})
|
|
}
|
|
|
|
/// `show_mmp` — MMP metrics summary.
|
|
pub fn show_mmp(node: &Node) -> Value {
|
|
// Link-layer MMP per peer
|
|
let peers: Vec<Value> = node.peers().filter_map(|peer| {
|
|
let mmp = peer.mmp()?;
|
|
let addr = *peer.node_addr();
|
|
let metrics = &mmp.metrics;
|
|
|
|
let mut link_layer = json!({
|
|
"loss_rate": metrics.loss_rate(),
|
|
"etx": metrics.etx,
|
|
"goodput_bps": metrics.goodput_bps,
|
|
"spin_bit_role": if mmp.spin_bit.is_initiator() { "initiator" } else { "responder" },
|
|
});
|
|
|
|
if let Some(smoothed_loss) = metrics.smoothed_loss() {
|
|
link_layer["smoothed_loss"] = json!(smoothed_loss);
|
|
}
|
|
if let Some(smoothed_etx) = metrics.smoothed_etx() {
|
|
link_layer["smoothed_etx"] = json!(smoothed_etx);
|
|
}
|
|
if let Some(srtt) = metrics.srtt_ms() {
|
|
link_layer["srtt_ms"] = json!(srtt);
|
|
if let Some(setx) = metrics.smoothed_etx() {
|
|
link_layer["lqi"] = json!(setx * (1.0 + srtt / 100.0));
|
|
}
|
|
}
|
|
|
|
// Trend indicators
|
|
if metrics.rtt_trend.initialized() {
|
|
link_layer["rtt_trend"] = json!(trend_label(metrics.rtt_trend.short(), metrics.rtt_trend.long()));
|
|
}
|
|
if metrics.loss_trend.initialized() {
|
|
link_layer["loss_trend"] = json!(trend_label(metrics.loss_trend.short(), metrics.loss_trend.long()));
|
|
}
|
|
if metrics.goodput_trend.initialized() {
|
|
link_layer["goodput_trend"] = json!(trend_label(metrics.goodput_trend.short(), metrics.goodput_trend.long()));
|
|
}
|
|
if metrics.jitter_trend.initialized() {
|
|
link_layer["jitter_trend"] = json!(trend_label(metrics.jitter_trend.short(), metrics.jitter_trend.long()));
|
|
}
|
|
|
|
link_layer["delivery_ratio_forward"] = json!(metrics.delivery_ratio_forward);
|
|
link_layer["delivery_ratio_reverse"] = json!(metrics.delivery_ratio_reverse);
|
|
link_layer["ecn_ce_count"] = json!(metrics.last_ecn_ce_count());
|
|
|
|
Some(json!({
|
|
"peer": hex::encode(addr.as_bytes()),
|
|
"display_name": node.peer_display_name(&addr),
|
|
"mode": format!("{}", mmp.mode()),
|
|
"link_layer": link_layer,
|
|
}))
|
|
}).collect();
|
|
|
|
// Session-layer MMP
|
|
let sessions: Vec<Value> = node.session_entries().filter_map(|(addr, entry)| {
|
|
let mmp = entry.mmp()?;
|
|
let metrics = &mmp.metrics;
|
|
|
|
let mut session_layer = json!({
|
|
"loss_rate": metrics.loss_rate(),
|
|
"etx": metrics.etx,
|
|
"path_mtu": mmp.path_mtu.current_mtu(),
|
|
});
|
|
|
|
if let Some(smoothed_loss) = metrics.smoothed_loss() {
|
|
session_layer["smoothed_loss"] = json!(smoothed_loss);
|
|
}
|
|
if let Some(smoothed_etx) = metrics.smoothed_etx() {
|
|
session_layer["smoothed_etx"] = json!(smoothed_etx);
|
|
}
|
|
if let Some(srtt) = metrics.srtt_ms() {
|
|
session_layer["srtt_ms"] = json!(srtt);
|
|
if let Some(setx) = metrics.smoothed_etx() {
|
|
session_layer["sqi"] = json!(setx * (1.0 + srtt / 100.0));
|
|
}
|
|
}
|
|
|
|
Some(json!({
|
|
"remote": hex::encode(addr.as_bytes()),
|
|
"display_name": node.peer_display_name(addr),
|
|
"mode": format!("{}", mmp.mode()),
|
|
"session_layer": session_layer,
|
|
}))
|
|
}).collect();
|
|
|
|
json!({
|
|
"peers": peers,
|
|
"sessions": sessions,
|
|
})
|
|
}
|
|
|
|
/// `show_cache` — Coordinate cache stats.
|
|
pub fn show_cache(node: &Node) -> Value {
|
|
let cache = node.coord_cache();
|
|
let stats = cache.stats(now_ms());
|
|
|
|
json!({
|
|
"entries": stats.entries,
|
|
"max_entries": stats.max_entries,
|
|
"fill_ratio": stats.fill_ratio(),
|
|
"default_ttl_ms": cache.default_ttl_ms(),
|
|
"expired": stats.expired,
|
|
"avg_age_ms": stats.avg_age_ms,
|
|
})
|
|
}
|
|
|
|
/// `show_connections` — Pending handshakes.
|
|
pub fn show_connections(node: &Node) -> Value {
|
|
let now = now_ms();
|
|
let connections: Vec<Value> = node.connections().map(|conn| {
|
|
let mut conn_json = json!({
|
|
"link_id": conn.link_id().as_u64(),
|
|
"direction": format!("{}", conn.direction()),
|
|
"handshake_state": format!("{}", conn.handshake_state()),
|
|
"started_at_ms": conn.started_at(),
|
|
"idle_ms": now.saturating_sub(conn.last_activity()),
|
|
"resend_count": conn.resend_count(),
|
|
});
|
|
|
|
if let Some(identity) = conn.expected_identity() {
|
|
conn_json["expected_peer"] = json!(identity.npub());
|
|
}
|
|
|
|
conn_json
|
|
}).collect();
|
|
|
|
json!({ "connections": connections })
|
|
}
|
|
|
|
/// `show_transports` — Transport instances.
|
|
pub fn show_transports(node: &Node) -> Value {
|
|
let transports: Vec<Value> = node.transport_ids().map(|id| {
|
|
let handle = node.get_transport(id).unwrap();
|
|
let mut t_json = json!({
|
|
"transport_id": id.as_u32(),
|
|
"type": handle.transport_type().name,
|
|
"state": format!("{}", handle.state()),
|
|
"mtu": handle.mtu(),
|
|
});
|
|
|
|
if let Some(name) = handle.name() {
|
|
t_json["name"] = json!(name);
|
|
}
|
|
if let Some(addr) = handle.local_addr() {
|
|
t_json["local_addr"] = json!(format!("{}", addr));
|
|
}
|
|
|
|
t_json["stats"] = handle.transport_stats();
|
|
|
|
t_json
|
|
}).collect();
|
|
|
|
json!({ "transports": transports })
|
|
}
|
|
|
|
/// `show_routing` — Routing table summary and node statistics.
|
|
pub fn show_routing(node: &Node) -> Value {
|
|
let cache = node.coord_cache();
|
|
let cache_stats = cache.stats(now_ms());
|
|
let node_stats = node.stats().snapshot();
|
|
|
|
json!({
|
|
"coord_cache_entries": cache_stats.entries,
|
|
"identity_cache_entries": node.identity_cache_len(),
|
|
"pending_lookups": node.pending_lookup_count(),
|
|
"recent_requests": node.recent_request_count(),
|
|
"forwarding": serde_json::to_value(&node_stats.forwarding).unwrap_or_default(),
|
|
"discovery": serde_json::to_value(&node_stats.discovery).unwrap_or_default(),
|
|
"error_signals": serde_json::to_value(&node_stats.errors).unwrap_or_default(),
|
|
"congestion": serde_json::to_value(&node_stats.congestion).unwrap_or_default(),
|
|
})
|
|
}
|
|
|
|
/// Dispatch a command string to the appropriate query function.
|
|
pub fn dispatch(node: &Node, command: &str) -> super::protocol::Response {
|
|
match command {
|
|
"show_status" => super::protocol::Response::ok(show_status(node)),
|
|
"show_peers" => super::protocol::Response::ok(show_peers(node)),
|
|
"show_links" => super::protocol::Response::ok(show_links(node)),
|
|
"show_tree" => super::protocol::Response::ok(show_tree(node)),
|
|
"show_sessions" => super::protocol::Response::ok(show_sessions(node)),
|
|
"show_bloom" => super::protocol::Response::ok(show_bloom(node)),
|
|
"show_mmp" => super::protocol::Response::ok(show_mmp(node)),
|
|
"show_cache" => super::protocol::Response::ok(show_cache(node)),
|
|
"show_connections" => super::protocol::Response::ok(show_connections(node)),
|
|
"show_transports" => super::protocol::Response::ok(show_transports(node)),
|
|
"show_routing" => super::protocol::Response::ok(show_routing(node)),
|
|
_ => super::protocol::Response::error(format!("unknown command: {}", command)),
|
|
}
|
|
}
|