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/// Control frame types
122#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq)]
123#[rkyv(compare(PartialEq), derive(Debug))]
124pub enum ControlMessage {
125    /// DNS over HTTPS query
126    DohQuery { query: Vec<u8> },
127
128    /// DNS over HTTPS response
129    DohResponse { response: Vec<u8> },
130
131    /// Keepalive ping
132    Ping { nonce: u64 },
133
134    /// Keepalive pong
135    Pong { nonce: u64 },
136
137    /// Key rotation notification
138    KeyRotation {
139        new_pk: [u8; 32],
140        valid_from: u64,
141        valid_until: u64,
142    },
143
144    /// Emergency mode warning
145    Emergency {
146        level: EmergencyLevel,
147        trigger_after: u64,
148    },
149}
150
151/// Emergency level
152#[derive(Archive, Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
153#[rkyv(compare(PartialEq), derive(Debug))]
154pub enum EmergencyLevel {
155    /// Warning only - client should prepare
156    Warning,
157    /// Stop all new connections
158    Stop,
159    /// Immediate shutdown
160    Shutdown,
161}
162
163/// Plain packet for exit node communication (no encryption needed - internal network)
164///
165/// This is used between Handler and Exit nodes over Cloudflare Tunnel or internal network.
166#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq)]
167#[rkyv(compare(PartialEq), derive(Debug))]
168pub struct PlainPacket {
169    /// Magic number for validation (0xDEADBEEF)
170    pub magic: u32,
171
172    /// Connection ID
173    pub conn_id: u64,
174
175    /// Handler node ID (for response routing)
176    pub handler_id: u64,
177
178    /// Remote IP address (16 bytes)
179    pub rip: [u8; 16],
180
181    /// Remote port
182    pub rport: u16,
183
184    /// Payload data
185    pub payload: Vec<u8>,
186
187    /// CRC32 checksum
188    pub checksum: u32,
189
190    /// Is this a response (from exit to handler)?
191    pub is_response: bool,
192}
193
194impl PlainPacket {
195    /// Magic number constant
196    pub const MAGIC: u32 = 0xDEADBEEF;
197
198    /// Create a new plain packet from a ProxyFrame
199    pub fn from_frame(frame: &ProxyFrame, handler_id: u64) -> Self {
200        Self {
201            magic: Self::MAGIC,
202            conn_id: frame.conn_id,
203            handler_id,
204            rip: frame.rip,
205            rport: frame.rport,
206            payload: frame.payload.clone(),
207            checksum: frame.checksum,
208            is_response: false,
209        }
210    }
211
212    /// Create a response packet
213    pub fn response(conn_id: u64, handler_id: u64, payload: Vec<u8>) -> Self {
214        let checksum = crc32fast::hash(&payload);
215        Self {
216            magic: Self::MAGIC,
217            conn_id,
218            handler_id,
219            rip: [0; 16],
220            rport: 0,
221            payload,
222            checksum,
223            is_response: true,
224        }
225    }
226
227    /// Verify magic number
228    pub fn is_valid(&self) -> bool {
229        self.magic == Self::MAGIC && crc32fast::hash(&self.payload) == self.checksum
230    }
231}
232
233/// Raft commands for distributed state machine
234#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq)]
235#[rkyv(compare(PartialEq), derive(Debug))]
236pub enum RaftCommand {
237    /// Insert or update a connection record
238    Upsert {
239        conn_id: u64,
240        txid: u64,
241        client_addr: [u8; 16],
242        nat_entry: (u16, u16),
243        assigned_pod: u32,
244    },
245
246    /// Delete a connection record
247    Delete { conn_id: u64 },
248
249    /// Cleanup expired connections (TTL based)
250    Cleanup { before_timestamp: u64 },
251
252    /// No-op for leader election
253    Noop,
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259
260    #[test]
261    fn test_frame_creation() {
262        let payload = vec![1, 2, 3, 4, 5];
263        let frame = ProxyFrame::new_data(
264            42,
265            ProxyFrame::ipv4_to_mapped([192, 168, 1, 1]),
266            8080,
267            payload.clone(),
268        );
269
270        assert_eq!(frame.conn_id, 42);
271        assert_eq!(frame.rport, 8080);
272        assert_eq!(frame.payload, payload);
273        assert!(frame.verify_checksum());
274    }
275
276    #[test]
277    fn test_ipv4_mapping() {
278        let ipv4 = [192, 168, 1, 1];
279        let mapped = ProxyFrame::ipv4_to_mapped(ipv4);
280        let extracted = ProxyFrame::mapped_to_ipv4(&mapped);
281
282        assert_eq!(extracted, Some(ipv4));
283    }
284
285    #[test]
286    fn test_serialization() {
287        let frame = ProxyFrame::new_data(1, [0; 16], 443, vec![0xDE, 0xAD, 0xBE, 0xEF]);
288
289        let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&frame).unwrap();
290        let archived = rkyv::access::<ArchivedProxyFrame, rkyv::rancor::Error>(&bytes).unwrap();
291
292        assert_eq!(archived.conn_id, 1);
293        assert_eq!(archived.rport, 443);
294    }
295}