1use crate::identity::encode_npub;
7use crate::node::Node;
8use crate::node::stats_history::Metric;
9use serde_json::{Value, json};
10
11mod stats;
12
13pub use stats::{
14 show_stats_all_history, show_stats_history, show_stats_history_all_peers, show_stats_list,
15 show_stats_peers,
16};
17
18fn now_ms() -> u64 {
20 std::time::SystemTime::now()
21 .duration_since(std::time::UNIX_EPOCH)
22 .map(|d| d.as_millis() as u64)
23 .unwrap_or(0)
24}
25
26fn trend_label(short: f64, long: f64) -> &'static str {
28 if !short.is_finite() || !long.is_finite() || long == 0.0 {
29 return "stable";
30 }
31 let ratio = short / long;
32 if ratio > 1.05 {
33 "rising"
34 } else if ratio < 0.95 {
35 "falling"
36 } else {
37 "stable"
38 }
39}
40
41pub fn show_status(node: &Node) -> Value {
43 let pid = std::process::id();
44 let exe_path = std::env::current_exe()
45 .map(|p| p.display().to_string())
46 .unwrap_or_else(|_| "-".into());
47 let uptime_secs = node.uptime().as_secs();
48 let fwd = node.stats().snapshot().forwarding;
49
50 const SPARK_N: usize = 30;
54 let hist = node.stats_history();
55 let sparklines = json!({
56 "mesh_size": hist.recent(Metric::MeshSize, SPARK_N),
57 "tree_depth": hist.recent(Metric::TreeDepth, SPARK_N),
58 "peer_count": hist.recent(Metric::PeerCount, SPARK_N),
59 "bytes_in": hist.recent(Metric::BytesIn, SPARK_N),
60 "bytes_out": hist.recent(Metric::BytesOut, SPARK_N),
61 "loss_rate": hist.recent(Metric::LossRate, SPARK_N),
62 });
63
64 json!({
65 "version": crate::version::short_version(),
66 "npub": node.npub(),
67 "node_addr": hex::encode(node.node_addr().as_bytes()),
68 "ipv6_addr": format!("{}", node.identity().address()),
69 "state": format!("{}", node.state()),
70 "is_leaf_only": node.is_leaf_only(),
71 "peer_count": node.peer_count(),
72 "session_count": node.session_count(),
73 "link_count": node.link_count(),
74 "transport_count": node.transport_count(),
75 "connection_count": node.connection_count(),
76 "tun_state": format!("{}", node.tun_state()),
77 "tun_name": node.tun_name().unwrap_or("-"),
78 "effective_ipv6_mtu": node.effective_ipv6_mtu(),
79 "control_socket": &node.config().node.control.socket_path,
80 "pid": pid,
81 "exe_path": exe_path,
82 "uptime_secs": uptime_secs,
83 "estimated_mesh_size": node.estimated_mesh_size(),
84 "forwarding": serde_json::to_value(&fwd).unwrap_or_default(),
85 "sparklines": sparklines,
86 })
87}
88
89pub fn show_acl(node: &Node) -> Value {
91 let status = node.peer_acl_status();
92
93 json!({
94 "allow_file": status.allow_file,
95 "deny_file": status.deny_file,
96 "enforcement_active": status.enforcement_active,
97 "effective_mode": status.effective_mode,
98 "default_decision": status.default_decision,
99 "allow_all": status.allow_all,
100 "deny_all": status.deny_all,
101 "allow_file_entries": status.allow_file_entries,
102 "deny_file_entries": status.deny_file_entries,
103 "allow_entries": status.allow_entries,
104 "deny_entries": status.deny_entries,
105 })
106}
107
108pub fn show_peers(node: &Node) -> Value {
110 let tree = node.tree_state();
111 let my_addr = *tree.my_node_addr();
112 let parent_id = *tree.my_declaration().parent_id();
113 let is_root = tree.is_root();
114
115 let nostr_state: std::collections::HashMap<String, _> = node
118 .nostr_discovery_handle()
119 .map(|d| {
120 d.failure_state_snapshot()
121 .into_iter()
122 .map(|view| (view.npub.clone(), view))
123 .collect()
124 })
125 .unwrap_or_default();
126 let retry_state: std::collections::HashMap<_, _> = node
127 .retry_state_iter()
128 .map(|(addr, state)| (*addr, state.retry_after_ms))
129 .collect();
130
131 let peers: Vec<Value> = node
132 .peers()
133 .map(|peer| {
134 let node_addr = *peer.node_addr();
135 let addr_hex = hex::encode(node_addr.as_bytes());
136
137 let is_parent = !is_root && node_addr == parent_id;
139 let is_child = tree
140 .peer_declaration(&node_addr)
141 .is_some_and(|decl| *decl.parent_id() == my_addr);
142
143 let mut peer_json = json!({
144 "node_addr": addr_hex,
145 "npub": peer.npub(),
146 "display_name": node.peer_display_name(&node_addr),
147 "ipv6_addr": format!("{}", peer.address()),
148 "connectivity": format!("{}", peer.connectivity()),
149 "link_id": peer.link_id().as_u64(),
150 "authenticated_at_ms": peer.authenticated_at(),
151 "last_seen_ms": peer.last_seen(),
152 "has_tree_position": peer.has_tree_position(),
153 "has_bloom_filter": peer.filter_sequence() > 0,
154 "filter_sequence": peer.filter_sequence(),
155 "is_parent": is_parent,
156 "is_child": is_child,
157 });
158
159 if let Some(addr) = peer.current_addr() {
161 peer_json["transport_addr"] = json!(format!("{}", addr));
162 }
163
164 let link_id = peer.link_id();
166 if let Some(link) = node.get_link(&link_id) {
167 peer_json["direction"] = json!(format!("{}", link.direction()));
168 let transport_id = link.transport_id();
169 if let Some(handle) = node.get_transport(&transport_id) {
170 peer_json["transport_type"] = json!(handle.transport_type().name);
171 }
172 }
173
174 if let Some(coords) = peer.coords() {
176 peer_json["tree_depth"] = json!(coords.depth());
177 }
178
179 let stats = peer.link_stats();
181 peer_json["stats"] = json!({
182 "packets_sent": stats.packets_sent,
183 "packets_recv": stats.packets_recv,
184 "bytes_sent": stats.bytes_sent,
185 "bytes_recv": stats.bytes_recv,
186 });
187
188 peer_json["replay_suppressed"] = json!(peer.replay_suppressed_count());
190 peer_json["consecutive_decrypt_failures"] = json!(peer.consecutive_decrypt_failures());
191
192 let npub = peer.npub();
197 let mut nostr_obj = json!({
198 "consecutive_failures": 0,
199 "in_cooldown": false,
200 "cooldown_until_ms": Value::Null,
201 "last_observed_skew_ms": Value::Null,
202 "direct_probe_pending": retry_state.contains_key(&node_addr),
203 "direct_probe_after_ms": retry_state
204 .get(&node_addr)
205 .map(|t| json!(t))
206 .unwrap_or(Value::Null),
207 });
208 if let Some(state) = nostr_state.get(&npub) {
209 nostr_obj["consecutive_failures"] = json!(state.consecutive_failures);
210 nostr_obj["in_cooldown"] = json!(state.cooldown_until_ms.is_some());
211 nostr_obj["cooldown_until_ms"] = state
212 .cooldown_until_ms
213 .map(|t| json!(t))
214 .unwrap_or(Value::Null);
215 nostr_obj["last_observed_skew_ms"] = state
216 .last_observed_skew_ms
217 .map(|s| json!(s))
218 .unwrap_or(Value::Null);
219 }
220 peer_json["nostr_traversal"] = nostr_obj;
221
222 if let Some(session) = peer.noise_session() {
224 peer_json["noise"] = json!({
225 "send_counter": session.current_send_counter(),
226 "highest_recv_counter": session.highest_received_counter(),
227 });
228 }
229
230 if let Some(idx) = peer.our_index() {
232 peer_json["our_session_index"] = json!(format!("{:08x}", idx.as_u32()));
233 }
234
235 if peer.rekey_in_progress() {
237 peer_json["rekey_in_progress"] = json!(true);
238 }
239 if peer.is_draining() {
240 peer_json["rekey_draining"] = json!(true);
241 }
242 peer_json["current_k_bit"] = json!(peer.current_k_bit());
243
244 if let Some(mmp) = peer.mmp() {
246 let mut mmp_json = json!({
247 "mode": format!("{}", mmp.mode()),
248 });
249 if let Some(srtt) = mmp.metrics.srtt_ms() {
250 mmp_json["srtt_ms"] = json!(srtt);
251 }
252 mmp_json["loss_rate"] = json!(mmp.metrics.loss_rate());
253 mmp_json["etx"] = json!(mmp.metrics.etx);
254 mmp_json["goodput_bps"] = json!(mmp.metrics.goodput_bps);
255 mmp_json["delivery_ratio_forward"] = json!(mmp.metrics.delivery_ratio_forward);
256 mmp_json["delivery_ratio_reverse"] = json!(mmp.metrics.delivery_ratio_reverse);
257 if let Some(smoothed_loss) = mmp.metrics.smoothed_loss() {
258 mmp_json["smoothed_loss"] = json!(smoothed_loss);
259 }
260 if let Some(smoothed_etx) = mmp.metrics.smoothed_etx() {
261 mmp_json["smoothed_etx"] = json!(smoothed_etx);
262 }
263 if let Some(srtt) = mmp.metrics.srtt_ms()
264 && let Some(setx) = mmp.metrics.smoothed_etx()
265 {
266 mmp_json["lqi"] = json!(setx * (1.0 + srtt / 100.0));
267 }
268 peer_json["mmp"] = mmp_json;
269 }
270
271 peer_json
272 })
273 .collect();
274
275 json!({ "peers": peers })
276}
277
278pub fn show_links(node: &Node) -> Value {
280 let links: Vec<Value> = node
281 .links()
282 .map(|link| {
283 let stats = link.stats();
284 json!({
285 "link_id": link.link_id().as_u64(),
286 "transport_id": link.transport_id().as_u32(),
287 "remote_addr": format!("{}", link.remote_addr()),
288 "direction": format!("{}", link.direction()),
289 "state": format!("{}", link.state()),
290 "created_at_ms": link.created_at(),
291 "stats": {
292 "packets_sent": stats.packets_sent,
293 "packets_recv": stats.packets_recv,
294 "bytes_sent": stats.bytes_sent,
295 "bytes_recv": stats.bytes_recv,
296 "last_recv_ms": stats.last_recv_ms,
297 },
298 })
299 })
300 .collect();
301
302 json!({ "links": links })
303}
304
305pub fn show_tree(node: &Node) -> Value {
307 let tree = node.tree_state();
308 let my_coords = tree.my_coords();
309 let decl = tree.my_declaration();
310
311 let coords: Vec<String> = my_coords
313 .entries()
314 .iter()
315 .map(|e| hex::encode(e.node_addr.as_bytes()))
316 .collect();
317
318 let peers: Vec<Value> = tree
320 .peer_ids()
321 .map(|peer_id| {
322 let mut peer_json = json!({
323 "node_addr": hex::encode(peer_id.as_bytes()),
324 "display_name": node.peer_display_name(peer_id),
325 });
326 if let Some(coords) = tree.peer_coords(peer_id) {
327 let coord_path: Vec<String> = coords
328 .entries()
329 .iter()
330 .map(|e| hex::encode(e.node_addr.as_bytes()))
331 .collect();
332 peer_json["depth"] = json!(coords.depth());
333 peer_json["root"] = json!(hex::encode(coords.root_id().as_bytes()));
334 peer_json["coords"] = json!(coord_path);
335 peer_json["distance_to_us"] = json!(my_coords.distance_to(coords));
336 }
337 peer_json
338 })
339 .collect();
340
341 let parent_addr = my_coords.parent_id();
343 let parent_hex = hex::encode(parent_addr.as_bytes());
344 let parent_display = node.peer_display_name(parent_addr);
345
346 let tree_stats = node.stats().snapshot().tree;
347
348 json!({
349 "my_node_addr": hex::encode(tree.my_node_addr().as_bytes()),
350 "root": hex::encode(tree.root().as_bytes()),
351 "is_root": tree.is_root(),
352 "depth": my_coords.depth(),
353 "my_coords": coords,
354 "parent": parent_hex,
355 "parent_display_name": parent_display,
356 "declaration_sequence": decl.sequence(),
357 "declaration_signed": decl.is_signed(),
358 "peer_tree_count": tree.peer_count(),
359 "peers": peers,
360 "stats": serde_json::to_value(&tree_stats).unwrap_or_default(),
361 })
362}
363
364pub fn show_sessions(node: &Node) -> Value {
366 let sessions: Vec<Value> = node
367 .session_entries()
368 .map(|(addr, entry)| {
369 let state_str = if entry.is_established() {
370 "established"
371 } else if entry.is_initiating() {
372 "initiating"
373 } else if entry.is_awaiting_msg3() {
374 "awaiting_msg3"
375 } else {
376 "unknown"
377 };
378
379 let mut session_json = json!({
380 "remote_addr": hex::encode(addr.as_bytes()),
381 "display_name": node.peer_display_name(addr),
382 "state": state_str,
383 "is_initiator": entry.is_initiator(),
384 "last_activity_ms": entry.last_activity(),
385 });
386
387 let (xonly, _parity) = entry.remote_pubkey().x_only_public_key();
389 session_json["npub"] = json!(encode_npub(&xonly));
390
391 let (pkts_tx, pkts_rx, bytes_tx, bytes_rx) = entry.traffic_counters();
393 session_json["stats"] = json!({
394 "packets_sent": pkts_tx,
395 "packets_recv": pkts_rx,
396 "bytes_sent": bytes_tx,
397 "bytes_recv": bytes_rx,
398 });
399
400 if !entry.is_established() {
402 session_json["resend_count"] = json!(entry.resend_count());
403 }
404
405 if entry.is_established() {
407 session_json["session_start_ms"] = json!(entry.session_start_ms());
408 session_json["current_k_bit"] = json!(entry.current_k_bit());
409 session_json["coords_warmup_remaining"] = json!(entry.coords_warmup_remaining());
410 session_json["is_draining"] = json!(entry.is_draining());
411 }
412
413 if let Some(mmp) = entry.mmp() {
415 let mut mmp_json = json!({
416 "mode": format!("{}", mmp.mode()),
417 "loss_rate": mmp.metrics.loss_rate(),
418 "etx": mmp.metrics.etx,
419 "goodput_bps": mmp.metrics.goodput_bps,
420 "delivery_ratio_forward": mmp.metrics.delivery_ratio_forward,
421 "delivery_ratio_reverse": mmp.metrics.delivery_ratio_reverse,
422 "path_mtu": mmp.path_mtu.current_mtu(),
423 });
424 if let Some(srtt) = mmp.metrics.srtt_ms() {
425 mmp_json["srtt_ms"] = json!(srtt);
426 }
427 if let Some(smoothed_loss) = mmp.metrics.smoothed_loss() {
428 mmp_json["smoothed_loss"] = json!(smoothed_loss);
429 }
430 if let Some(smoothed_etx) = mmp.metrics.smoothed_etx() {
431 mmp_json["smoothed_etx"] = json!(smoothed_etx);
432 }
433 if let Some(srtt) = mmp.metrics.srtt_ms()
434 && let Some(setx) = mmp.metrics.smoothed_etx()
435 {
436 mmp_json["sqi"] = json!(setx * (1.0 + srtt / 100.0));
437 }
438 session_json["mmp"] = mmp_json;
439 }
440
441 session_json
442 })
443 .collect();
444
445 json!({ "sessions": sessions })
446}
447
448pub fn show_bloom(node: &Node) -> Value {
450 let bloom = node.bloom_state();
451
452 let leaf_deps: Vec<String> = bloom
453 .leaf_dependents()
454 .iter()
455 .map(|addr| hex::encode(addr.as_bytes()))
456 .collect();
457
458 let peer_filters: Vec<Value> = node
460 .peers()
461 .map(|peer| {
462 let addr = *peer.node_addr();
463 let mut pf = json!({
464 "peer": hex::encode(addr.as_bytes()),
465 "display_name": node.peer_display_name(&addr),
466 "has_filter": peer.filter_sequence() > 0,
467 "filter_sequence": peer.filter_sequence(),
468 });
469 if let Some(filter) = peer.inbound_filter() {
470 let max_fpr = node.config().node.bloom.max_inbound_fpr;
471 pf["estimated_count"] = json!(filter.estimated_count(max_fpr));
472 pf["set_bits"] = json!(filter.count_ones());
473 pf["fill_ratio"] = json!(filter.fill_ratio());
474 }
475 pf
476 })
477 .collect();
478
479 let bloom_stats = node.stats().snapshot().bloom;
480
481 json!({
482 "own_node_addr": hex::encode(node.node_addr().as_bytes()),
483 "is_leaf_only": node.is_leaf_only(),
484 "sequence": bloom.sequence(),
485 "leaf_dependent_count": bloom.leaf_dependents().len(),
486 "leaf_dependents": leaf_deps,
487 "peer_filters": peer_filters,
488 "stats": serde_json::to_value(&bloom_stats).unwrap_or_default(),
489 })
490}
491
492pub fn show_mmp(node: &Node) -> Value {
494 let peers: Vec<Value> = node.peers().filter_map(|peer| {
496 let mmp = peer.mmp()?;
497 let addr = *peer.node_addr();
498 let metrics = &mmp.metrics;
499
500 let mut link_layer = json!({
501 "loss_rate": metrics.loss_rate(),
502 "etx": metrics.etx,
503 "goodput_bps": metrics.goodput_bps,
504 "spin_bit_role": if mmp.spin_bit.is_initiator() { "initiator" } else { "responder" },
505 });
506
507 if let Some(smoothed_loss) = metrics.smoothed_loss() {
508 link_layer["smoothed_loss"] = json!(smoothed_loss);
509 }
510 if let Some(smoothed_etx) = metrics.smoothed_etx() {
511 link_layer["smoothed_etx"] = json!(smoothed_etx);
512 }
513 if let Some(srtt) = metrics.srtt_ms() {
514 link_layer["srtt_ms"] = json!(srtt);
515 if let Some(setx) = metrics.smoothed_etx() {
516 link_layer["lqi"] = json!(setx * (1.0 + srtt / 100.0));
517 }
518 }
519
520 if metrics.rtt_trend.initialized() {
522 link_layer["rtt_trend"] = json!(trend_label(metrics.rtt_trend.short(), metrics.rtt_trend.long()));
523 }
524 if metrics.loss_trend.initialized() {
525 link_layer["loss_trend"] = json!(trend_label(metrics.loss_trend.short(), metrics.loss_trend.long()));
526 }
527 if metrics.goodput_trend.initialized() {
528 link_layer["goodput_trend"] = json!(trend_label(metrics.goodput_trend.short(), metrics.goodput_trend.long()));
529 }
530 if metrics.jitter_trend.initialized() {
531 link_layer["jitter_trend"] = json!(trend_label(metrics.jitter_trend.short(), metrics.jitter_trend.long()));
532 }
533
534 link_layer["delivery_ratio_forward"] = json!(metrics.delivery_ratio_forward);
535 link_layer["delivery_ratio_reverse"] = json!(metrics.delivery_ratio_reverse);
536 link_layer["ecn_ce_count"] = json!(metrics.last_ecn_ce_count());
537
538 Some(json!({
539 "peer": hex::encode(addr.as_bytes()),
540 "display_name": node.peer_display_name(&addr),
541 "mode": format!("{}", mmp.mode()),
542 "link_layer": link_layer,
543 }))
544 }).collect();
545
546 let sessions: Vec<Value> = node
548 .session_entries()
549 .filter_map(|(addr, entry)| {
550 let mmp = entry.mmp()?;
551 let metrics = &mmp.metrics;
552
553 let mut session_layer = json!({
554 "loss_rate": metrics.loss_rate(),
555 "etx": metrics.etx,
556 "path_mtu": mmp.path_mtu.current_mtu(),
557 });
558
559 if let Some(smoothed_loss) = metrics.smoothed_loss() {
560 session_layer["smoothed_loss"] = json!(smoothed_loss);
561 }
562 if let Some(smoothed_etx) = metrics.smoothed_etx() {
563 session_layer["smoothed_etx"] = json!(smoothed_etx);
564 }
565 if let Some(srtt) = metrics.srtt_ms() {
566 session_layer["srtt_ms"] = json!(srtt);
567 if let Some(setx) = metrics.smoothed_etx() {
568 session_layer["sqi"] = json!(setx * (1.0 + srtt / 100.0));
569 }
570 }
571
572 Some(json!({
573 "remote": hex::encode(addr.as_bytes()),
574 "display_name": node.peer_display_name(addr),
575 "mode": format!("{}", mmp.mode()),
576 "session_layer": session_layer,
577 }))
578 })
579 .collect();
580
581 json!({
582 "peers": peers,
583 "sessions": sessions,
584 })
585}
586
587pub fn show_cache(node: &Node) -> Value {
589 let cache = node.coord_cache();
590 let now = now_ms();
591 let stats = cache.stats(now);
592
593 let entries: Vec<Value> = cache
595 .iter(now)
596 .map(|(addr, entry)| {
597 let fips_addr = crate::identity::FipsAddress::from_node_addr(addr);
598 let coord_path: Vec<String> = entry
599 .coords()
600 .entries()
601 .iter()
602 .map(|e| hex::encode(e.node_addr.as_bytes()))
603 .collect();
604 let mut entry_json = json!({
605 "node_addr": hex::encode(addr.as_bytes()),
606 "display_name": node.peer_display_name(addr),
607 "ipv6_addr": format!("{}", fips_addr),
608 "depth": entry.coords().depth(),
609 "coords": coord_path,
610 "age_ms": now.saturating_sub(entry.created_at()),
611 "last_used_ms": entry.last_used(),
612 });
613 if let Some(mtu) = entry.path_mtu() {
614 entry_json["path_mtu"] = json!(mtu);
615 }
616 entry_json
617 })
618 .collect();
619
620 json!({
621 "count": stats.entries,
622 "max_entries": stats.max_entries,
623 "fill_ratio": stats.fill_ratio(),
624 "default_ttl_ms": cache.default_ttl_ms(),
625 "expired": stats.expired,
626 "avg_age_ms": stats.avg_age_ms,
627 "entries": entries,
628 })
629}
630
631pub fn show_connections(node: &Node) -> Value {
633 let now = now_ms();
634 let connections: Vec<Value> = node
635 .connections()
636 .map(|conn| {
637 let mut conn_json = json!({
638 "link_id": conn.link_id().as_u64(),
639 "direction": format!("{}", conn.direction()),
640 "handshake_state": format!("{}", conn.handshake_state()),
641 "started_at_ms": conn.started_at(),
642 "idle_ms": now.saturating_sub(conn.last_activity()),
643 "resend_count": conn.resend_count(),
644 });
645
646 if let Some(identity) = conn.expected_identity() {
647 conn_json["expected_peer"] = json!(identity.npub());
648 }
649
650 conn_json
651 })
652 .collect();
653
654 json!({ "connections": connections })
655}
656
657pub fn show_transports(node: &Node) -> Value {
659 let transports: Vec<Value> = node
660 .transport_ids()
661 .map(|id| {
662 let handle = node.get_transport(id).unwrap();
663 let mut t_json = json!({
664 "transport_id": id.as_u32(),
665 "type": handle.transport_type().name,
666 "state": format!("{}", handle.state()),
667 "mtu": handle.mtu(),
668 });
669
670 if let Some(name) = handle.name() {
671 t_json["name"] = json!(name);
672 }
673 if let Some(addr) = handle.local_addr() {
674 t_json["local_addr"] = json!(format!("{}", addr));
675 }
676
677 if let Some(mode) = handle.tor_mode() {
679 t_json["tor_mode"] = json!(mode);
680 }
681 if let Some(onion) = handle.onion_address() {
682 t_json["onion_address"] = json!(onion);
683 }
684 if let Some(monitoring) = handle.tor_monitoring() {
685 t_json["tor_monitoring"] = serde_json::to_value(&monitoring).unwrap_or_default();
686 }
687
688 t_json["stats"] = handle.transport_stats();
689
690 t_json
691 })
692 .collect();
693
694 json!({ "transports": transports })
695}
696
697pub fn show_routing(node: &Node) -> Value {
699 let cache = node.coord_cache();
700 let now = now_ms();
701 let cache_stats = cache.stats(now);
702 let node_stats = node.stats().snapshot();
703 let learned_routes = node.learned_route_table_snapshot(now);
704
705 let lookups: Vec<Value> = node
707 .pending_lookups_iter()
708 .map(|(addr, lookup)| {
709 json!({
710 "target": hex::encode(addr.as_bytes()),
711 "display_name": node.peer_display_name(addr),
712 "initiated_ms": lookup.initiated_ms,
713 "last_sent_ms": lookup.last_sent_ms,
714 "attempt": lookup.attempt,
715 "age_ms": now.saturating_sub(lookup.initiated_ms),
716 })
717 })
718 .collect();
719
720 let retries: Vec<Value> = node
722 .retry_state_iter()
723 .map(|(addr, state)| {
724 json!({
725 "node_addr": hex::encode(addr.as_bytes()),
726 "display_name": node.peer_display_name(addr),
727 "retry_count": state.retry_count,
728 "retry_after_ms": state.retry_after_ms,
729 "auto_reconnect": state.reconnect,
730 })
731 })
732 .collect();
733
734 json!({
735 "coord_cache_entries": cache_stats.entries,
736 "routing_mode": node.config().node.routing.mode.to_string(),
737 "learned_routes": serde_json::to_value(&learned_routes).unwrap_or_default(),
738 "identity_cache_entries": node.identity_cache_len(),
739 "pending_lookups": lookups,
740 "pending_tun_destinations": node.pending_tun_destinations(),
741 "pending_tun_packets": node.pending_tun_total_packets(),
742 "recent_requests": node.recent_request_count(),
743 "retries": retries,
744 "forwarding": serde_json::to_value(&node_stats.forwarding).unwrap_or_default(),
745 "discovery": serde_json::to_value(&node_stats.discovery).unwrap_or_default(),
746 "error_signals": serde_json::to_value(&node_stats.errors).unwrap_or_default(),
747 "congestion": serde_json::to_value(&node_stats.congestion).unwrap_or_default(),
748 })
749}
750
751pub fn show_identity_cache(node: &Node) -> Value {
758 let now = now_ms();
759 let entries: Vec<Value> = node
760 .identity_cache_iter()
761 .map(|(node_addr, pubkey, last_seen_ms)| {
762 let (xonly, _parity) = pubkey.x_only_public_key();
763 let fips_addr = crate::identity::FipsAddress::from_node_addr(node_addr);
764 json!({
765 "node_addr": hex::encode(node_addr.as_bytes()),
766 "npub": encode_npub(&xonly),
767 "display_name": node.peer_display_name(node_addr),
768 "ipv6_addr": format!("{}", fips_addr),
769 "last_seen_ms": last_seen_ms,
770 "age_ms": now.saturating_sub(last_seen_ms),
771 })
772 })
773 .collect();
774 let count = entries.len();
775
776 json!({
777 "entries": entries,
778 "count": count,
779 "max_entries": node.identity_cache_max(),
780 })
781}
782
783pub fn show_listening_sockets(node: &Node) -> Value {
786 let fips0 = crate::FipsAddress::from_node_addr(node.identity().node_addr()).to_ipv6();
787 #[cfg(test)]
788 let sockets: Vec<super::listening::ListeningSocket> = Vec::new();
789 #[cfg(not(test))]
790 let sockets = super::listening::enumerate(fips0);
791 #[cfg(test)]
792 let classifier = super::firewall_state::FilterClassifier::no_firewall();
793 #[cfg(not(test))]
794 let classifier = super::firewall_state::FilterClassifier::query();
795
796 let rows: Vec<Value> = sockets
797 .iter()
798 .map(|socket| {
799 let filter = classifier.classify(socket.proto, socket.port);
800 json!({
801 "proto": socket.proto.as_str(),
802 "local_addr": socket.local_addr.to_string(),
803 "port": socket.port,
804 "pid": socket.pid,
805 "process": socket.process,
806 "filter": filter.as_str(),
807 "wildcard_bind": socket.wildcard_bind,
808 })
809 })
810 .collect();
811
812 json!({
813 "fips0_addr": fips0.to_string(),
814 "firewall_active": classifier.is_active(),
815 "sockets": rows,
816 })
817}
818
819pub fn dispatch(node: &Node, command: &str, params: Option<&Value>) -> super::protocol::Response {
821 match command {
822 "show_acl" => super::protocol::Response::ok(show_acl(node)),
823 "show_status" => super::protocol::Response::ok(show_status(node)),
824 "show_peers" => super::protocol::Response::ok(show_peers(node)),
825 "show_links" => super::protocol::Response::ok(show_links(node)),
826 "show_tree" => super::protocol::Response::ok(show_tree(node)),
827 "show_sessions" => super::protocol::Response::ok(show_sessions(node)),
828 "show_bloom" => super::protocol::Response::ok(show_bloom(node)),
829 "show_mmp" => super::protocol::Response::ok(show_mmp(node)),
830 "show_cache" => super::protocol::Response::ok(show_cache(node)),
831 "show_connections" => super::protocol::Response::ok(show_connections(node)),
832 "show_transports" => super::protocol::Response::ok(show_transports(node)),
833 "show_routing" => super::protocol::Response::ok(show_routing(node)),
834 "show_identity_cache" => super::protocol::Response::ok(show_identity_cache(node)),
835 "show_listening_sockets" => super::protocol::Response::ok(show_listening_sockets(node)),
836 "show_stats_list" => super::protocol::Response::ok(show_stats_list()),
837 "show_stats_history" => show_stats_history(node, params),
838 "show_stats_all_history" => show_stats_all_history(node, params),
839 "show_stats_peers" => super::protocol::Response::ok(show_stats_peers(node)),
840 "show_stats_history_all_peers" => show_stats_history_all_peers(node, params),
841 _ => super::protocol::Response::error(format!("unknown command: {}", command)),
842 }
843}
844
845#[cfg(test)]
846mod tests;