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