Skip to main content

apfsds_protocol/
frame.rs

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