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