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