gate4agent_node_core/
wg_status.rs1use std::sync::atomic::{AtomicI32, AtomicU64, AtomicU8, Ordering};
4
5const ST_OFF: u8 = 0;
7const ST_DOWN: u8 = 1;
8const ST_WAIT: u8 = 2;
9const ST_UP: u8 = 3;
10const ST_REBIND: u8 = 4;
11
12static STATE: AtomicU8 = AtomicU8::new(ST_OFF);
13static HANDSHAKE_AGE_SECS: AtomicU64 = AtomicU64::new(u64::MAX);
14static UDP_OUT: AtomicU64 = AtomicU64::new(0);
15static UDP_IN: AtomicU64 = AtomicU64::new(0);
16static SEND_FAIL: AtomicU64 = AtomicU64::new(0);
17static LAST_ERR: AtomicI32 = AtomicI32::new(0);
18static EXIT_IF: AtomicU64 = AtomicU64::new(0);
19static REBIND_COUNT: AtomicU64 = AtomicU64::new(0);
20
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct WgStatus {
23 pub state: String,
24 pub exit_if_index: Option<u32>,
25 pub handshake_age_secs: Option<u64>,
26 pub udp_out: u64,
27 pub udp_in: u64,
28 pub send_fail: u64,
29 pub last_err: i32,
30 pub rebind_count: u64,
31}
32
33impl WgStatus {
34 pub fn snapshot() -> Self {
35 let state = match STATE.load(Ordering::Relaxed) {
36 ST_DOWN => "down",
37 ST_WAIT => "waiting_exit_tun",
38 ST_UP => "up",
39 ST_REBIND => "rebinding",
40 _ => "off",
41 };
42 let age = HANDSHAKE_AGE_SECS.load(Ordering::Relaxed);
43 let ifx = EXIT_IF.load(Ordering::Relaxed);
44 Self {
45 state: state.to_owned(),
46 exit_if_index: if ifx == 0 {
47 None
48 } else {
49 Some(ifx as u32)
50 },
51 handshake_age_secs: if age == u64::MAX { None } else { Some(age) },
52 udp_out: UDP_OUT.load(Ordering::Relaxed),
53 udp_in: UDP_IN.load(Ordering::Relaxed),
54 send_fail: SEND_FAIL.load(Ordering::Relaxed),
55 last_err: LAST_ERR.load(Ordering::Relaxed),
56 rebind_count: REBIND_COUNT.load(Ordering::Relaxed),
57 }
58 }
59}
60
61pub(crate) fn set_off() {
62 STATE.store(ST_OFF, Ordering::Relaxed);
63}
64
65pub(crate) fn set_down() {
66 STATE.store(ST_DOWN, Ordering::Relaxed);
67 HANDSHAKE_AGE_SECS.store(u64::MAX, Ordering::Relaxed);
68}
69
70pub(crate) fn set_waiting() {
71 STATE.store(ST_WAIT, Ordering::Relaxed);
72}
73
74pub(crate) fn set_up() {
75 STATE.store(ST_UP, Ordering::Relaxed);
76}
77
78pub(crate) fn set_rebinding() {
79 STATE.store(ST_REBIND, Ordering::Relaxed);
80}
81
82pub(crate) fn set_exit_if(if_index: u32) {
83 EXIT_IF.store(u64::from(if_index), Ordering::Relaxed);
84}
85
86pub(crate) fn bump_rebind() {
87 REBIND_COUNT.fetch_add(1, Ordering::Relaxed);
88}
89
90pub(crate) fn publish_stats(
91 udp_out: u64,
92 udp_in: u64,
93 send_fail: u64,
94 last_err: i32,
95 handshake_age_secs: Option<u64>,
96) {
97 UDP_OUT.store(udp_out, Ordering::Relaxed);
98 UDP_IN.store(udp_in, Ordering::Relaxed);
99 SEND_FAIL.store(send_fail, Ordering::Relaxed);
100 LAST_ERR.store(last_err, Ordering::Relaxed);
101 HANDSHAKE_AGE_SECS.store(handshake_age_secs.unwrap_or(u64::MAX), Ordering::Relaxed);
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn snapshot_roundtrip_states() {
110 set_down();
111 assert_eq!(WgStatus::snapshot().state, "down");
112 set_waiting();
113 assert_eq!(WgStatus::snapshot().state, "waiting_exit_tun");
114 set_up();
115 publish_stats(3, 2, 1, -5, Some(12));
116 set_exit_if(40);
117 bump_rebind();
118 let s = WgStatus::snapshot();
119 assert_eq!(s.state, "up");
120 assert_eq!(s.udp_out, 3);
121 assert_eq!(s.udp_in, 2);
122 assert_eq!(s.send_fail, 1);
123 assert_eq!(s.last_err, -5);
124 assert_eq!(s.handshake_age_secs, Some(12));
125 assert_eq!(s.exit_if_index, Some(40));
126 assert!(s.rebind_count >= 1);
127 set_off();
128 }
129}