Skip to main content

apfsds_protocol/
frame.rs

1//! ProxyFrame - The core data transmission unit
2
3
4use rkyv::{Archive, Deserialize, Serialize};
5
6/// Proxy frame - the fundamental unit of all data transmission
7///
8/// Each frame represents either:
9/// - A data packet (payload contains actual traffic)
10/// - A control message (DoH query, keepalive, etc.)
11#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq)]
12#[rkyv(compare(PartialEq), derive(Debug))]
13pub struct ProxyFrame {
14    /// Connection ID - unique per logical connection
15    pub conn_id: u64,
16
17    /// Remote IP address (16 bytes for IPv6, IPv4 mapped to ::ffff:x.x.x.x)
18    pub rip: [u8; 16],
19
20    /// Remote port
21    pub rport: u16,
22
23    /// Payload data
24    pub payload: Vec<u8>,
25
26    /// Frame UUID - unique per frame (replay protection)
27    pub uuid: [u8; 16],
28
29    /// Timestamp in milliseconds since Unix epoch
30    pub timestamp: u64,
31
32    /// CRC32 checksum of payload
33    pub checksum: u32,
34
35    /// Frame flags
36    pub flags: FrameFlags,
37}
38
39/// Frame flags for control flow
40#[derive(Archive, Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq)]
41#[rkyv(compare(PartialEq), derive(Debug))]
42pub struct FrameFlags {
43    /// This is a control frame (DoH, keepalive, etc.)
44    pub is_control: bool,
45
46    /// Payload is zstd compressed
47    pub is_compressed: bool,
48
49    /// This is the final frame for this connection
50    pub is_final: bool,
51
52    /// Request acknowledgment
53    pub needs_ack: bool,
54
55    /// This frame is an acknowledgment
56    pub is_ack: bool,
57}
58
59impl ProxyFrame {
60    /// Create a new data frame
61    pub fn new_data(conn_id: u64, rip: [u8; 16], rport: u16, payload: Vec<u8>) -> Self {
62        let checksum = crc32fast::hash(&payload);
63        let uuid = uuid::Uuid::new_v4().into_bytes();
64        let timestamp = std::time::SystemTime::now()
65            .duration_since(std::time::UNIX_EPOCH)
66            .unwrap()
67            .as_millis() as u64;
68
69        Self {
70            conn_id,
71            rip,
72            rport,
73            payload,
74            uuid,
75            timestamp,
76            checksum,
77            flags: FrameFlags::default(),
78        }
79    }
80
81    /// Create a control frame (e.g., DoH query)
82    pub fn new_control(payload: Vec<u8>) -> Self {
83        let mut frame = Self::new_data(0, [0; 16], 0, payload);
84        frame.flags.is_control = true;
85        frame
86    }
87
88    /// Create a connection close frame
89    pub fn new_close(conn_id: u64) -> Self {
90        let mut frame = Self::new_data(conn_id, [0; 16], 0, vec![]);
91        frame.flags.is_final = true;
92        frame
93    }
94
95    /// Verify the checksum
96    pub fn verify_checksum(&self) -> bool {
97        crc32fast::hash(&self.payload) == self.checksum
98    }
99
100    /// Convert IPv4 address to mapped IPv6 format
101    pub fn ipv4_to_mapped(ipv4: [u8; 4]) -> [u8; 16] {
102        let mut mapped = [0u8; 16];
103        mapped[10] = 0xff;
104        mapped[11] = 0xff;
105        mapped[12..16].copy_from_slice(&ipv4);
106        mapped
107    }
108
109    /// Extract IPv4 from mapped IPv6 format (if applicable)
110    pub fn mapped_to_ipv4(mapped: &[u8; 16]) -> Option<[u8; 4]> {
111        if mapped[..10] == [0; 10] && mapped[10] == 0xff && mapped[11] == 0xff {
112            let mut ipv4 = [0u8; 4];
113            ipv4.copy_from_slice(&mapped[12..16]);
114            Some(ipv4)
115        } else {
116            None
117        }
118    }
119}
120
121/// Proxy group information
122#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq)]
123#[rkyv(compare(PartialEq), derive(Debug))]
124pub struct GroupInfo {
125    /// Group ID
126    pub group_id: i32,
127    /// Group name/description
128    pub name: String,
129    /// Number of active nodes in this group
130    pub node_count: u32,
131    /// Average load (0-100)
132    pub load: u8,
133}
134
135/// Control frame types
136#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq)]
137#[rkyv(compare(PartialEq), derive(Debug))]
138pub enum ControlMessage {
139    /// DNS over HTTPS query
140    DohQuery { query: Vec<u8> },
141
142    /// DNS over HTTPS response
143    DohResponse { response: Vec<u8> },
144
145    /// Keepalive ping
146    Ping { nonce: u64 },
147
148    /// Keepalive pong
149    Pong { nonce: u64 },
150
151    /// Key rotation notification
152    KeyRotation {
153        new_pk: [u8; 32],
154        valid_from: u64,
155        valid_until: u64,
156    },
157
158    /// Emergency mode warning
159    Emergency {
160        level: EmergencyLevel,
161        trigger_after: u64,
162    },
163
164    /// Available proxy groups (handler -> exit-node)
165    GroupList { groups: Vec<GroupInfo> },
166
167    /// Group selection (exit-node -> handler)
168    GroupSelect { group_id: i32 },
169}
170
171/// Emergency level
172#[derive(Archive, Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
173#[rkyv(compare(PartialEq), derive(Debug))]
174pub enum EmergencyLevel {
175    /// Warning only - client should prepare
176    Warning,
177    /// Stop all new connections
178    Stop,
179    /// Immediate shutdown
180    Shutdown,
181}
182
183/// Plain packet for exit node communication (no encryption needed - internal network)
184///
185/// This is used between Handler and Exit nodes over Cloudflare Tunnel or internal network.
186#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq)]
187#[rkyv(compare(PartialEq), derive(Debug))]
188pub struct PlainPacket {
189    /// Magic number for validation (0xDEADBEEF)
190    pub magic: u32,
191
192    /// Connection ID
193    pub conn_id: u64,
194
195    /// Handler node ID (for response routing)
196    pub handler_id: u64,
197
198    /// Remote IP address (16 bytes)
199    pub rip: [u8; 16],
200
201    /// Remote port
202    pub rport: u16,
203
204    /// Payload data
205    pub payload: Vec<u8>,
206
207    /// CRC32 checksum
208    pub checksum: u32,
209
210    /// Is this a response (from exit to handler)?
211    pub is_response: bool,
212}
213
214impl PlainPacket {
215    /// Magic number constant
216    pub const MAGIC: u32 = 0xDEADBEEF;
217
218    /// Create a new plain packet from a ProxyFrame
219    pub fn from_frame(frame: &ProxyFrame, handler_id: u64) -> Self {
220        Self {
221            magic: Self::MAGIC,
222            conn_id: frame.conn_id,
223            handler_id,
224            rip: frame.rip,
225            rport: frame.rport,
226            payload: frame.payload.clone(),
227            checksum: frame.checksum,
228            is_response: false,
229        }
230    }
231
232    /// Create a response packet
233    pub fn response(conn_id: u64, handler_id: u64, payload: Vec<u8>) -> Self {
234        let checksum = crc32fast::hash(&payload);
235        Self {
236            magic: Self::MAGIC,
237            conn_id,
238            handler_id,
239            rip: [0; 16],
240            rport: 0,
241            payload,
242            checksum,
243            is_response: true,
244        }
245    }
246
247    /// Verify magic number
248    pub fn is_valid(&self) -> bool {
249        self.magic == Self::MAGIC && crc32fast::hash(&self.payload) == self.checksum
250    }
251}
252
253/// Raft commands for distributed state machine
254#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq)]
255#[rkyv(compare(PartialEq), derive(Debug))]
256pub enum RaftCommand {
257    /// Insert or update a connection record
258    Upsert {
259        conn_id: u64,
260        txid: u64,
261        client_addr: [u8; 16],
262        nat_entry: (u16, u16),
263        assigned_pod: u32,
264    },
265
266    /// Delete a connection record
267    Delete { conn_id: u64 },
268
269    /// Cleanup expired connections (TTL based)
270    Cleanup { before_timestamp: u64 },
271
272    /// No-op for leader election
273    Noop,
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279
280    #[test]
281    fn test_frame_creation() {
282        let payload = vec![1, 2, 3, 4, 5];
283        let frame = ProxyFrame::new_data(
284            42,
285            ProxyFrame::ipv4_to_mapped([192, 168, 1, 1]),
286            8080,
287            payload.clone(),
288        );
289
290        assert_eq!(frame.conn_id, 42);
291        assert_eq!(frame.rport, 8080);
292        assert_eq!(frame.payload, payload);
293        assert!(frame.verify_checksum());
294    }
295
296    #[test]
297    fn test_ipv4_mapping() {
298        let ipv4 = [192, 168, 1, 1];
299        let mapped = ProxyFrame::ipv4_to_mapped(ipv4);
300        let extracted = ProxyFrame::mapped_to_ipv4(&mapped);
301
302        assert_eq!(extracted, Some(ipv4));
303    }
304
305    #[test]
306    fn test_serialization() {
307        let frame = ProxyFrame::new_data(1, [0; 16], 443, vec![0xDE, 0xAD, 0xBE, 0xEF]);
308
309        let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&frame).unwrap();
310        let archived = rkyv::access::<ArchivedProxyFrame, rkyv::rancor::Error>(&bytes).unwrap();
311
312        assert_eq!(archived.conn_id, 1);
313        assert_eq!(archived.rport, 443);
314    }
315}