⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/net/vpn/
openvpn.rs

1//! OpenVPN Protocol Implementation
2//!
3//! Implements the OpenVPN control and data channel protocols, including:
4//! - Packet opcodes and header parsing (P_CONTROL, P_DATA, P_ACK)
5//! - TLS-Auth pre-shared HMAC authentication
6//! - Client state machine (Initial -> TLS -> Auth -> Connected)
7//! - Packet ID anti-replay protection (sliding window)
8//! - Configuration file parsing (key=value format)
9
10#![allow(dead_code)]
11
12use alloc::vec::Vec;
13
14use super::tunnel::TunnelType;
15use crate::net::Ipv4Address;
16
17// ── Constants ────────────────────────────────────────────────────────────────
18
19/// Default OpenVPN UDP port
20pub const DEFAULT_PORT: u16 = 1194;
21
22/// HMAC-SHA1 key size (bytes)
23const HMAC_KEY_SIZE: usize = 20;
24
25/// TLS-Auth pre-shared key size (bytes)
26const TLS_AUTH_KEY_SIZE: usize = 64;
27
28/// Packet ID anti-replay sliding window size (entries)
29const REPLAY_WINDOW_SIZE: usize = 64;
30
31/// Session ID size in bytes
32const SESSION_ID_SIZE: usize = 8;
33
34/// Maximum control channel message size
35const MAX_CONTROL_SIZE: usize = 1500;
36
37/// Maximum data channel payload
38const MAX_DATA_SIZE: usize = 65536;
39
40/// Maximum number of pending ACKs
41const MAX_PENDING_ACKS: usize = 64;
42
43/// Renegotiation interval in seconds
44const RENEG_SECONDS: u64 = 3600;
45
46/// Maximum config line length
47const MAX_CONFIG_LINE: usize = 256;
48
49// ── OpenVPN Opcodes ──────────────────────────────────────────────────────────
50
51/// OpenVPN packet opcodes (5 bits, high nibble of first byte)
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53#[repr(u8)]
54pub enum OpenvpnOpcode {
55    /// Control channel: hard reset from client (v2)
56    ControlHardResetClientV2 = 7,
57    /// Control channel: hard reset from server (v2)
58    ControlHardResetServerV2 = 8,
59    /// Control channel: reliable data
60    ControlV1 = 4,
61    /// Control channel: acknowledgement
62    AckV1 = 5,
63    /// Data channel: v1 (no peer-id)
64    DataV1 = 6,
65    /// Data channel: v2 (with peer-id)
66    DataV2 = 9,
67}
68
69impl OpenvpnOpcode {
70    /// Parse an opcode from the high 5 bits of a byte
71    pub fn from_byte(b: u8) -> Option<Self> {
72        match b >> 3 {
73            7 => Some(Self::ControlHardResetClientV2),
74            8 => Some(Self::ControlHardResetServerV2),
75            4 => Some(Self::ControlV1),
76            5 => Some(Self::AckV1),
77            6 => Some(Self::DataV1),
78            9 => Some(Self::DataV2),
79            _ => None,
80        }
81    }
82
83    /// Encode opcode into the high 5 bits with key_id in low 3 bits
84    pub fn encode(&self, key_id: u8) -> u8 {
85        ((*self as u8) << 3) | (key_id & 0x07)
86    }
87
88    /// Whether this opcode belongs to the control channel
89    pub fn is_control(&self) -> bool {
90        matches!(
91            self,
92            Self::ControlHardResetClientV2
93                | Self::ControlHardResetServerV2
94                | Self::ControlV1
95                | Self::AckV1
96        )
97    }
98}
99
100// ── OpenVPN Header ───────────────────────────────────────────────────────────
101
102/// Parsed OpenVPN packet header
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct OpenvpnHeader {
105    /// Opcode (5 bits)
106    pub opcode: OpenvpnOpcode,
107    /// Key ID (3 bits) -- identifies the TLS session / key
108    pub key_id: u8,
109    /// Session ID (8 bytes)
110    pub session_id: u64,
111    /// HMAC hash for tls-auth (20 bytes, SHA-1)
112    pub hmac_hash: [u8; HMAC_KEY_SIZE],
113    /// Packet ID for replay protection
114    pub packet_id: u32,
115    /// Timestamp (seconds since epoch)
116    pub timestamp: u32,
117}
118
119impl OpenvpnHeader {
120    /// Header size in bytes: 1 (opcode+key_id) + 8 (session) + 20 (hmac) + 4
121    /// (pid) + 4 (ts)
122    pub const SIZE: usize = 1 + SESSION_ID_SIZE + HMAC_KEY_SIZE + 4 + 4;
123
124    /// Parse a header from raw bytes
125    pub fn parse(data: &[u8]) -> Option<Self> {
126        if data.len() < Self::SIZE {
127            return None;
128        }
129
130        let opcode = OpenvpnOpcode::from_byte(data[0])?;
131        let key_id = data[0] & 0x07;
132        let session_id = u64::from_be_bytes([
133            data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8],
134        ]);
135
136        let mut hmac_hash = [0u8; HMAC_KEY_SIZE];
137        hmac_hash.copy_from_slice(&data[9..29]);
138
139        let packet_id = u32::from_be_bytes([data[29], data[30], data[31], data[32]]);
140        let timestamp = u32::from_be_bytes([data[33], data[34], data[35], data[36]]);
141
142        Some(Self {
143            opcode,
144            key_id,
145            session_id,
146            hmac_hash,
147            packet_id,
148            timestamp,
149        })
150    }
151
152    /// Serialise the header to bytes
153    pub fn to_bytes(&self) -> Vec<u8> {
154        let mut buf = Vec::with_capacity(Self::SIZE);
155        buf.push(self.opcode.encode(self.key_id));
156        buf.extend_from_slice(&self.session_id.to_be_bytes());
157        buf.extend_from_slice(&self.hmac_hash);
158        buf.extend_from_slice(&self.packet_id.to_be_bytes());
159        buf.extend_from_slice(&self.timestamp.to_be_bytes());
160        buf
161    }
162}
163
164// ── OpenVPN State Machine ────────────────────────────────────────────────────
165
166/// Client connection state
167#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
168pub enum OpenvpnState {
169    /// Not connected, no session
170    #[default]
171    Initial,
172    /// TLS handshake in progress
173    TlsHandshake,
174    /// Authenticating (username/password or certificate)
175    Authentication,
176    /// Fully connected and passing data
177    Connected,
178    /// Reconnecting after a disconnect
179    Reconnecting,
180    /// Cleanly disconnected
181    Disconnected,
182}
183
184// ── TLS-Auth ─────────────────────────────────────────────────────────────────
185
186/// TLS-Auth pre-shared HMAC key for control channel authentication
187#[derive(Clone)]
188pub struct TlsAuth {
189    /// Pre-shared key (64 bytes: 32 encrypt + 32 HMAC, or split by direction)
190    key: [u8; TLS_AUTH_KEY_SIZE],
191    /// Direction: 0 = client->server keys first, 1 = server->client keys first
192    direction: u8,
193}
194
195impl TlsAuth {
196    /// Create a new TLS-Auth context from key material and direction
197    pub fn new(key: [u8; TLS_AUTH_KEY_SIZE], direction: u8) -> Self {
198        Self {
199            key,
200            direction: direction & 1,
201        }
202    }
203
204    /// Get the HMAC key portion based on direction
205    fn hmac_key(&self) -> &[u8] {
206        if self.direction == 0 {
207            &self.key[0..32]
208        } else {
209            &self.key[32..64]
210        }
211    }
212
213    /// Compute HMAC-SHA1 over data using the directional key
214    ///
215    /// Uses a simplified HMAC construction: H(key XOR opad || H(key XOR ipad ||
216    /// message)) where H is a simple hash (not cryptographically strong --
217    /// placeholder for real SHA-1).
218    pub fn compute_hmac(&self, data: &[u8]) -> [u8; HMAC_KEY_SIZE] {
219        let key = self.hmac_key();
220        let mut result = [0u8; HMAC_KEY_SIZE];
221
222        // Inner hash: H(key XOR ipad || message)
223        let mut inner = [0u8; HMAC_KEY_SIZE];
224        for (i, byte) in inner.iter_mut().enumerate() {
225            let k = if i < key.len() { key[i] } else { 0 };
226            *byte = k ^ 0x36;
227        }
228        // Mix in data bytes
229        for (i, &b) in data.iter().enumerate() {
230            inner[i % HMAC_KEY_SIZE] ^= b;
231        }
232
233        // Outer hash: H(key XOR opad || inner)
234        for (i, byte) in result.iter_mut().enumerate() {
235            let k = if i < key.len() { key[i] } else { 0 };
236            *byte = (k ^ 0x5C) ^ inner[i];
237        }
238
239        result
240    }
241
242    /// Verify an HMAC tag against data
243    pub fn verify_hmac(&self, data: &[u8], expected: &[u8; HMAC_KEY_SIZE]) -> bool {
244        let computed = self.compute_hmac(data);
245        // Constant-time comparison
246        let mut diff = 0u8;
247        for (a, b) in computed.iter().zip(expected.iter()) {
248            diff |= a ^ b;
249        }
250        diff == 0
251    }
252}
253
254// ── Cipher / Auth Enums ──────────────────────────────────────────────────────
255
256/// Data channel cipher algorithm
257#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
258pub enum CipherAlgorithm {
259    /// AES-256-GCM (AEAD, recommended)
260    #[default]
261    Aes256Gcm,
262    /// AES-128-CBC (legacy)
263    Aes128Cbc,
264    /// ChaCha20-Poly1305 (AEAD, modern alternative)
265    ChaCha20Poly1305,
266}
267
268/// HMAC authentication algorithm (for non-AEAD ciphers)
269#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
270pub enum AuthAlgorithm {
271    /// SHA-256
272    #[default]
273    Sha256,
274    /// SHA-512
275    Sha512,
276}
277
278/// Compression algorithm
279#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
280pub enum Compression {
281    /// No compression
282    #[default]
283    None,
284    /// LZO compression (legacy)
285    Lzo,
286    /// LZ4 compression
287    Lz4,
288}
289
290/// Transport protocol for the tunnel
291#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
292pub enum TransportProto {
293    /// UDP (default, preferred)
294    #[default]
295    Udp,
296    /// TCP (fallback for restrictive networks)
297    Tcp,
298}
299
300// ── OpenVPN Configuration ────────────────────────────────────────────────────
301
302/// OpenVPN client/server configuration
303#[derive(Debug, Clone)]
304pub struct OpenvpnConfig {
305    /// Remote server address
306    pub remote_host: Ipv4Address,
307    /// Remote server port
308    pub remote_port: u16,
309    /// Transport protocol (UDP or TCP)
310    pub protocol: TransportProto,
311    /// Data channel cipher
312    pub cipher: CipherAlgorithm,
313    /// Authentication algorithm (for non-AEAD ciphers)
314    pub auth: AuthAlgorithm,
315    /// Compression method
316    pub compress: Compression,
317    /// TLS-Auth pre-shared key (optional)
318    pub tls_auth_key: Option<[u8; TLS_AUTH_KEY_SIZE]>,
319    /// Device type (TUN or TAP)
320    pub dev_type: TunnelType,
321}
322
323impl Default for OpenvpnConfig {
324    fn default() -> Self {
325        Self {
326            remote_host: Ipv4Address::new(0, 0, 0, 0),
327            remote_port: DEFAULT_PORT,
328            protocol: TransportProto::Udp,
329            cipher: CipherAlgorithm::Aes256Gcm,
330            auth: AuthAlgorithm::Sha256,
331            compress: Compression::None,
332            tls_auth_key: None,
333            dev_type: TunnelType::Tun,
334        }
335    }
336}
337
338// ── Anti-Replay ──────────────────────────────────────────────────────────────
339
340/// Sliding-window anti-replay protection for packet IDs
341pub struct PacketIdWindow {
342    /// Bitmap of seen packet IDs relative to `base`
343    window: [u8; REPLAY_WINDOW_SIZE / 8],
344    /// Base packet ID (lowest ID in the window)
345    base: u32,
346    /// Highest packet ID seen so far
347    highest: u32,
348}
349
350impl Default for PacketIdWindow {
351    fn default() -> Self {
352        Self::new()
353    }
354}
355
356impl PacketIdWindow {
357    /// Create a new anti-replay window
358    pub fn new() -> Self {
359        Self {
360            window: [0u8; REPLAY_WINDOW_SIZE / 8],
361            base: 0,
362            highest: 0,
363        }
364    }
365
366    /// Check whether a packet ID is acceptable (not replayed)
367    pub fn check_replay(&self, packet_id: u32) -> bool {
368        if packet_id == 0 {
369            return false; // Packet ID 0 is never valid
370        }
371
372        // If packet_id is ahead of our window, it's always acceptable
373        if packet_id > self.highest {
374            return true;
375        }
376
377        // If packet_id is too old (below the window base), reject
378        if packet_id < self.base {
379            return false;
380        }
381
382        // Check if already seen within the window
383        let offset = (packet_id - self.base) as usize;
384        if offset >= REPLAY_WINDOW_SIZE {
385            return false;
386        }
387
388        let byte_idx = offset / 8;
389        let bit_idx = offset % 8;
390        (self.window[byte_idx] & (1 << bit_idx)) == 0
391    }
392
393    /// Record a packet ID as seen and advance the window if needed
394    pub fn update(&mut self, packet_id: u32) {
395        if packet_id == 0 {
396            return;
397        }
398
399        if packet_id > self.highest {
400            // Advance the window
401            let shift = (packet_id - self.highest) as usize;
402            if shift >= REPLAY_WINDOW_SIZE {
403                // Complete window reset
404                self.window = [0u8; REPLAY_WINDOW_SIZE / 8];
405            } else {
406                // Shift window bits forward
407                self.shift_window(shift);
408            }
409            self.highest = packet_id;
410            self.base = packet_id.saturating_sub(REPLAY_WINDOW_SIZE as u32 - 1);
411        }
412
413        // Mark as seen
414        if packet_id >= self.base {
415            let offset = (packet_id - self.base) as usize;
416            if offset < REPLAY_WINDOW_SIZE {
417                let byte_idx = offset / 8;
418                let bit_idx = offset % 8;
419                self.window[byte_idx] |= 1 << bit_idx;
420            }
421        }
422    }
423
424    /// Shift the window bitmap forward by `count` bits
425    fn shift_window(&mut self, count: usize) {
426        if count >= REPLAY_WINDOW_SIZE {
427            self.window = [0u8; REPLAY_WINDOW_SIZE / 8];
428            return;
429        }
430
431        let byte_shift = count / 8;
432        let bit_shift = count % 8;
433
434        if byte_shift > 0 {
435            for i in (byte_shift..self.window.len()).rev() {
436                self.window[i] = self.window[i - byte_shift];
437            }
438            for byte in self.window.iter_mut().take(byte_shift) {
439                *byte = 0;
440            }
441        }
442
443        if bit_shift > 0 {
444            for i in (1..self.window.len()).rev() {
445                self.window[i] =
446                    (self.window[i] << bit_shift) | (self.window[i - 1] >> (8 - bit_shift));
447            }
448            self.window[0] <<= bit_shift;
449        }
450    }
451}
452
453// ── OpenVPN Client ───────────────────────────────────────────────────────────
454
455/// OpenVPN protocol error
456#[derive(Debug, Clone, Copy, PartialEq, Eq)]
457pub enum OpenvpnError {
458    /// Not connected
459    NotConnected,
460    /// Already connected
461    AlreadyConnected,
462    /// Invalid packet format
463    InvalidPacket,
464    /// HMAC verification failed
465    HmacFailed,
466    /// Replay attack detected
467    ReplayDetected,
468    /// Packet too large
469    PacketTooLarge,
470    /// Session not established
471    NoSession,
472    /// Invalid opcode
473    InvalidOpcode,
474    /// Invalid state for the requested operation
475    InvalidState,
476    /// Renegotiation required
477    RenegotiationRequired,
478}
479
480/// OpenVPN client connection
481pub struct OpenvpnClient {
482    /// Configuration
483    config: OpenvpnConfig,
484    /// Current state
485    state: OpenvpnState,
486    /// Local session ID
487    session_id: u64,
488    /// Remote session ID (received from server)
489    remote_session_id: u64,
490    /// Monotonic packet ID counter for outgoing packets
491    packet_id_counter: u32,
492    /// Anti-replay window for incoming packets
493    replay_window: PacketIdWindow,
494    /// Current key_id (rotates on renegotiation)
495    key_id: u8,
496    /// TLS-Auth context (if configured)
497    tls_auth: Option<TlsAuth>,
498    /// Pending ACK packet IDs to send
499    pending_acks: Vec<u32>,
500    /// Seconds since session start (for renegotiation timer)
501    session_start: u64,
502    /// Total bytes sent on this session
503    bytes_sent: u64,
504    /// Total bytes received on this session
505    bytes_received: u64,
506}
507
508impl OpenvpnClient {
509    /// Create a new OpenVPN client with the given configuration
510    pub fn new(config: OpenvpnConfig, session_id: u64) -> Self {
511        let tls_auth = config.tls_auth_key.map(|key| TlsAuth::new(key, 0));
512
513        Self {
514            config,
515            state: OpenvpnState::Initial,
516            session_id,
517            remote_session_id: 0,
518            packet_id_counter: 1, // Packet ID 0 is invalid
519            replay_window: PacketIdWindow::new(),
520            key_id: 0,
521            tls_auth,
522            pending_acks: Vec::new(),
523            session_start: 0,
524            bytes_sent: 0,
525            bytes_received: 0,
526        }
527    }
528
529    /// Get current connection state
530    pub fn state(&self) -> OpenvpnState {
531        self.state
532    }
533
534    /// Get the session ID
535    pub fn session_id(&self) -> u64 {
536        self.session_id
537    }
538
539    /// Initiate a connection (send hard reset)
540    pub fn connect(&mut self, now: u64) -> Result<Vec<u8>, OpenvpnError> {
541        if self.state != OpenvpnState::Initial && self.state != OpenvpnState::Disconnected {
542            return Err(OpenvpnError::InvalidState);
543        }
544
545        self.state = OpenvpnState::TlsHandshake;
546        self.session_start = now;
547
548        // Build P_CONTROL_HARD_RESET_CLIENT_V2 packet
549        let packet =
550            self.build_control_packet(OpenvpnOpcode::ControlHardResetClientV2, &[], now as u32);
551        Ok(packet)
552    }
553
554    /// Clean disconnect
555    pub fn disconnect(&mut self) {
556        self.state = OpenvpnState::Disconnected;
557        self.pending_acks.clear();
558    }
559
560    /// Send a control channel message (reliable, with pending ACK)
561    pub fn send_control(&mut self, payload: &[u8], now: u64) -> Result<Vec<u8>, OpenvpnError> {
562        if self.state == OpenvpnState::Initial || self.state == OpenvpnState::Disconnected {
563            return Err(OpenvpnError::NotConnected);
564        }
565        if payload.len() > MAX_CONTROL_SIZE {
566            return Err(OpenvpnError::PacketTooLarge);
567        }
568
569        let packet = self.build_control_packet(OpenvpnOpcode::ControlV1, payload, now as u32);
570        Ok(packet)
571    }
572
573    /// Encrypt and send a data channel packet
574    pub fn send_data(&mut self, payload: &[u8]) -> Result<Vec<u8>, OpenvpnError> {
575        if self.state != OpenvpnState::Connected {
576            return Err(OpenvpnError::NotConnected);
577        }
578        if payload.len() > MAX_DATA_SIZE {
579            return Err(OpenvpnError::PacketTooLarge);
580        }
581
582        let pid = self.next_packet_id();
583        let mut packet = Vec::with_capacity(1 + 4 + payload.len());
584
585        // Opcode + key_id byte
586        packet.push(OpenvpnOpcode::DataV2.encode(self.key_id));
587        // Packet ID (for replay protection on data channel too)
588        packet.extend_from_slice(&pid.to_be_bytes());
589        // Payload (in a real implementation, this would be encrypted)
590        packet.extend_from_slice(payload);
591
592        self.bytes_sent += payload.len() as u64;
593        Ok(packet)
594    }
595
596    /// Process an incoming packet
597    pub fn receive_packet(&mut self, data: &[u8]) -> Result<Vec<u8>, OpenvpnError> {
598        if data.is_empty() {
599            return Err(OpenvpnError::InvalidPacket);
600        }
601
602        let opcode = OpenvpnOpcode::from_byte(data[0]).ok_or(OpenvpnError::InvalidOpcode)?;
603
604        if opcode.is_control() {
605            self.process_control(data)
606        } else {
607            self.process_data(data)
608        }
609    }
610
611    /// Handle control channel messages
612    pub fn process_control(&mut self, data: &[u8]) -> Result<Vec<u8>, OpenvpnError> {
613        let header = OpenvpnHeader::parse(data).ok_or(OpenvpnError::InvalidPacket)?;
614
615        // Verify HMAC if tls-auth is configured
616        if let Some(ref tls_auth) = self.tls_auth {
617            // HMAC covers everything after the HMAC field
618            let hmac_data = if data.len() > 29 { &data[29..] } else { &[] };
619            if !tls_auth.verify_hmac(hmac_data, &header.hmac_hash) {
620                return Err(OpenvpnError::HmacFailed);
621            }
622        }
623
624        // Anti-replay check on control channel
625        if !self.replay_window.check_replay(header.packet_id) {
626            return Err(OpenvpnError::ReplayDetected);
627        }
628        self.replay_window.update(header.packet_id);
629
630        // State machine transitions
631        match header.opcode {
632            OpenvpnOpcode::ControlHardResetServerV2 => {
633                if self.state == OpenvpnState::TlsHandshake {
634                    self.remote_session_id = header.session_id;
635                    // Queue ACK
636                    self.queue_ack(header.packet_id);
637                    self.state = OpenvpnState::Authentication;
638                }
639            }
640            OpenvpnOpcode::ControlV1 => {
641                self.queue_ack(header.packet_id);
642                // In Authentication state, a ControlV1 from the server signals
643                // successful auth and transition to Connected
644                if self.state == OpenvpnState::Authentication {
645                    self.state = OpenvpnState::Connected;
646                }
647            }
648            OpenvpnOpcode::AckV1 => {
649                // Remove acknowledged packet IDs from pending list
650                self.pending_acks.retain(|&pid| pid != header.packet_id);
651            }
652            _ => {}
653        }
654
655        // Return any payload beyond the header
656        if data.len() > OpenvpnHeader::SIZE {
657            self.bytes_received += (data.len() - OpenvpnHeader::SIZE) as u64;
658            Ok(data[OpenvpnHeader::SIZE..].to_vec())
659        } else {
660            Ok(Vec::new())
661        }
662    }
663
664    /// Handle data channel packets
665    fn process_data(&mut self, data: &[u8]) -> Result<Vec<u8>, OpenvpnError> {
666        if self.state != OpenvpnState::Connected {
667            return Err(OpenvpnError::NotConnected);
668        }
669
670        // Data packet: opcode(1) + packet_id(4) + payload
671        if data.len() < 5 {
672            return Err(OpenvpnError::InvalidPacket);
673        }
674
675        let packet_id = u32::from_be_bytes([data[1], data[2], data[3], data[4]]);
676
677        // Anti-replay
678        if !self.replay_window.check_replay(packet_id) {
679            return Err(OpenvpnError::ReplayDetected);
680        }
681        self.replay_window.update(packet_id);
682
683        let payload = data[5..].to_vec();
684        self.bytes_received += payload.len() as u64;
685        Ok(payload)
686    }
687
688    /// Initiate TLS key renegotiation
689    pub fn renegotiate(&mut self, now: u64) -> Result<Vec<u8>, OpenvpnError> {
690        if self.state != OpenvpnState::Connected {
691            return Err(OpenvpnError::InvalidState);
692        }
693
694        self.key_id = (self.key_id + 1) & 0x07;
695        self.state = OpenvpnState::TlsHandshake;
696        self.replay_window = PacketIdWindow::new();
697
698        let packet =
699            self.build_control_packet(OpenvpnOpcode::ControlHardResetClientV2, &[], now as u32);
700        Ok(packet)
701    }
702
703    /// Check if renegotiation is needed based on elapsed time
704    pub fn needs_renegotiation(&self, now: u64) -> bool {
705        if self.state != OpenvpnState::Connected {
706            return false;
707        }
708        now.saturating_sub(self.session_start) >= RENEG_SECONDS
709    }
710
711    /// Get the next packet ID and increment the counter
712    fn next_packet_id(&mut self) -> u32 {
713        let id = self.packet_id_counter;
714        self.packet_id_counter = self.packet_id_counter.wrapping_add(1);
715        if self.packet_id_counter == 0 {
716            self.packet_id_counter = 1; // Skip 0
717        }
718        id
719    }
720
721    /// Queue an ACK for a received packet ID
722    fn queue_ack(&mut self, packet_id: u32) {
723        if self.pending_acks.len() < MAX_PENDING_ACKS {
724            self.pending_acks.push(packet_id);
725        }
726    }
727
728    /// Build a control channel packet with header
729    fn build_control_packet(
730        &mut self,
731        opcode: OpenvpnOpcode,
732        payload: &[u8],
733        timestamp: u32,
734    ) -> Vec<u8> {
735        let pid = self.next_packet_id();
736
737        let hmac_hash = if let Some(ref tls_auth) = self.tls_auth {
738            // Compute HMAC over packet_id + timestamp + payload
739            let mut hmac_data = Vec::new();
740            hmac_data.extend_from_slice(&pid.to_be_bytes());
741            hmac_data.extend_from_slice(&timestamp.to_be_bytes());
742            hmac_data.extend_from_slice(payload);
743            tls_auth.compute_hmac(&hmac_data)
744        } else {
745            [0u8; HMAC_KEY_SIZE]
746        };
747
748        let header = OpenvpnHeader {
749            opcode,
750            key_id: self.key_id,
751            session_id: self.session_id,
752            hmac_hash,
753            packet_id: pid,
754            timestamp,
755        };
756
757        let mut packet = header.to_bytes();
758        packet.extend_from_slice(payload);
759        packet
760    }
761
762    /// Get bytes sent/received counters
763    pub fn traffic_stats(&self) -> (u64, u64) {
764        (self.bytes_sent, self.bytes_received)
765    }
766}
767
768// ── Config File Parser ───────────────────────────────────────────────────────
769
770/// Parse an OpenVPN-style configuration from key=value lines.
771///
772/// Supported directives: proto, remote, port, dev, cipher, auth, compress,
773/// tls-auth (key bytes are not parsed here -- set separately).
774pub fn parse_config(input: &str) -> OpenvpnConfig {
775    let mut config = OpenvpnConfig::default();
776
777    for line in input.lines() {
778        let line = line.trim();
779        if line.is_empty() || line.starts_with('#') || line.starts_with(';') {
780            continue;
781        }
782
783        // Split at first whitespace
784        let (key, value) = match line.find(|c: char| c.is_ascii_whitespace()) {
785            Some(pos) => (line[..pos].trim(), line[pos + 1..].trim()),
786            None => (line, ""),
787        };
788
789        match key {
790            "proto" => {
791                config.protocol = match value {
792                    "tcp" | "tcp-client" => TransportProto::Tcp,
793                    _ => TransportProto::Udp,
794                };
795            }
796            "remote" => {
797                // "remote <host> [port]"
798                let parts: Vec<&str> = value.split_whitespace().collect();
799                if let Some(host) = parts.first() {
800                    config.remote_host = parse_ipv4(host);
801                }
802                if let Some(port_str) = parts.get(1) {
803                    if let Some(port) = parse_u16(port_str) {
804                        config.remote_port = port;
805                    }
806                }
807            }
808            "port" => {
809                if let Some(port) = parse_u16(value) {
810                    config.remote_port = port;
811                }
812            }
813            "dev" => {
814                config.dev_type = if value.starts_with("tap") {
815                    TunnelType::Tap
816                } else {
817                    TunnelType::Tun
818                };
819            }
820            "cipher" => {
821                config.cipher = match value {
822                    "AES-128-CBC" => CipherAlgorithm::Aes128Cbc,
823                    "CHACHA20-POLY1305" => CipherAlgorithm::ChaCha20Poly1305,
824                    _ => CipherAlgorithm::Aes256Gcm,
825                };
826            }
827            "auth" => {
828                config.auth = match value {
829                    "SHA512" => AuthAlgorithm::Sha512,
830                    _ => AuthAlgorithm::Sha256,
831                };
832            }
833            "compress" => {
834                config.compress = match value {
835                    "lzo" => Compression::Lzo,
836                    "lz4" | "lz4-v2" => Compression::Lz4,
837                    _ => Compression::None,
838                };
839            }
840            _ => {} // Ignore unknown directives
841        }
842    }
843
844    config
845}
846
847/// Parse a dotted-quad IPv4 address string (simple, no error handling)
848fn parse_ipv4(s: &str) -> Ipv4Address {
849    let mut octets = [0u8; 4];
850    for (i, part) in s.split('.').take(4).enumerate() {
851        if let Some(val) = parse_u8(part) {
852            octets[i] = val;
853        }
854    }
855    Ipv4Address(octets)
856}
857
858/// Parse a u16 from a decimal string
859fn parse_u16(s: &str) -> Option<u16> {
860    let mut result: u16 = 0;
861    for &b in s.as_bytes() {
862        if !b.is_ascii_digit() {
863            return None;
864        }
865        result = result.checked_mul(10)?.checked_add((b - b'0') as u16)?;
866    }
867    Some(result)
868}
869
870/// Parse a u8 from a decimal string
871fn parse_u8(s: &str) -> Option<u8> {
872    let mut result: u16 = 0;
873    for &b in s.as_bytes() {
874        if !b.is_ascii_digit() {
875            return None;
876        }
877        result = result.checked_mul(10)?.checked_add((b - b'0') as u16)?;
878    }
879    if result > 255 {
880        None
881    } else {
882        Some(result as u8)
883    }
884}
885
886// ── Tests ────────────────────────────────────────────────────────────────────
887
888#[cfg(test)]
889mod tests {
890    use super::*;
891
892    #[test]
893    fn test_opcode_encode_decode() {
894        for opcode in [
895            OpenvpnOpcode::ControlHardResetClientV2,
896            OpenvpnOpcode::ControlHardResetServerV2,
897            OpenvpnOpcode::ControlV1,
898            OpenvpnOpcode::AckV1,
899            OpenvpnOpcode::DataV1,
900            OpenvpnOpcode::DataV2,
901        ] {
902            let key_id = 3u8;
903            let byte = opcode.encode(key_id);
904            let decoded = OpenvpnOpcode::from_byte(byte).unwrap();
905            assert_eq!(decoded, opcode);
906            assert_eq!(byte & 0x07, key_id);
907        }
908    }
909
910    #[test]
911    fn test_opcode_is_control() {
912        assert!(OpenvpnOpcode::ControlV1.is_control());
913        assert!(OpenvpnOpcode::AckV1.is_control());
914        assert!(OpenvpnOpcode::ControlHardResetClientV2.is_control());
915        assert!(!OpenvpnOpcode::DataV1.is_control());
916        assert!(!OpenvpnOpcode::DataV2.is_control());
917    }
918
919    #[test]
920    fn test_header_serialize_parse() {
921        let header = OpenvpnHeader {
922            opcode: OpenvpnOpcode::ControlV1,
923            key_id: 2,
924            session_id: 0xDEADBEEF_CAFEBABE,
925            hmac_hash: [0xAA; HMAC_KEY_SIZE],
926            packet_id: 42,
927            timestamp: 1000,
928        };
929
930        let bytes = header.to_bytes();
931        assert_eq!(bytes.len(), OpenvpnHeader::SIZE);
932
933        let parsed = OpenvpnHeader::parse(&bytes).unwrap();
934        assert_eq!(parsed.opcode, OpenvpnOpcode::ControlV1);
935        assert_eq!(parsed.key_id, 2);
936        assert_eq!(parsed.session_id, 0xDEADBEEF_CAFEBABE);
937        assert_eq!(parsed.hmac_hash, [0xAA; HMAC_KEY_SIZE]);
938        assert_eq!(parsed.packet_id, 42);
939        assert_eq!(parsed.timestamp, 1000);
940    }
941
942    #[test]
943    fn test_header_parse_too_short() {
944        assert!(OpenvpnHeader::parse(&[0u8; 10]).is_none());
945    }
946
947    #[test]
948    fn test_tls_auth_hmac() {
949        let key = [0x42u8; TLS_AUTH_KEY_SIZE];
950        let tls_auth = TlsAuth::new(key, 0);
951
952        let mac1 = tls_auth.compute_hmac(b"hello");
953        let mac2 = tls_auth.compute_hmac(b"hello");
954        assert_eq!(mac1, mac2);
955
956        let mac3 = tls_auth.compute_hmac(b"world");
957        assert_ne!(mac1, mac3);
958    }
959
960    #[test]
961    fn test_tls_auth_verify() {
962        let key = [0x42u8; TLS_AUTH_KEY_SIZE];
963        let tls_auth = TlsAuth::new(key, 0);
964
965        let mac = tls_auth.compute_hmac(b"test data");
966        assert!(tls_auth.verify_hmac(b"test data", &mac));
967        assert!(!tls_auth.verify_hmac(b"wrong data", &mac));
968    }
969
970    #[test]
971    fn test_tls_auth_direction() {
972        // Verify that different directions select different key halves
973        let mut key = [0u8; TLS_AUTH_KEY_SIZE];
974        for (i, b) in key.iter_mut().enumerate() {
975            *b = if i < 32 { 0x42 } else { 0xAB };
976        }
977        let auth0 = TlsAuth::new(key, 0);
978        let auth1 = TlsAuth::new(key, 1);
979
980        // Direction 0 uses first half, direction 1 uses second half
981        assert_eq!(auth0.hmac_key()[0], 0x42);
982        assert_eq!(auth1.hmac_key()[0], 0xAB);
983    }
984
985    #[test]
986    fn test_replay_window_basic() {
987        let mut window = PacketIdWindow::new();
988
989        // Packet ID 0 is always invalid
990        assert!(!window.check_replay(0));
991
992        // New IDs are accepted
993        assert!(window.check_replay(1));
994        window.update(1);
995
996        // Duplicate is rejected
997        assert!(!window.check_replay(1));
998
999        // Forward ID is accepted
1000        assert!(window.check_replay(2));
1001        window.update(2);
1002        assert!(window.check_replay(100));
1003    }
1004
1005    #[test]
1006    fn test_replay_window_large_jump() {
1007        let mut window = PacketIdWindow::new();
1008
1009        window.update(1);
1010        window.update(1000);
1011
1012        // Old IDs are outside the window
1013        assert!(!window.check_replay(1));
1014
1015        // IDs within the window are still available
1016        assert!(window.check_replay(999));
1017    }
1018
1019    #[test]
1020    fn test_client_connect_disconnect() {
1021        let config = OpenvpnConfig::default();
1022        let mut client = OpenvpnClient::new(config, 0x1234);
1023
1024        assert_eq!(client.state(), OpenvpnState::Initial);
1025
1026        let pkt = client.connect(100).unwrap();
1027        assert!(!pkt.is_empty());
1028        assert_eq!(client.state(), OpenvpnState::TlsHandshake);
1029
1030        // Cannot connect twice
1031        assert_eq!(client.connect(101), Err(OpenvpnError::InvalidState));
1032
1033        client.disconnect();
1034        assert_eq!(client.state(), OpenvpnState::Disconnected);
1035    }
1036
1037    #[test]
1038    fn test_client_send_data_requires_connected() {
1039        let config = OpenvpnConfig::default();
1040        let mut client = OpenvpnClient::new(config, 0x1234);
1041
1042        assert_eq!(client.send_data(b"hello"), Err(OpenvpnError::NotConnected));
1043    }
1044
1045    #[test]
1046    fn test_config_parse_basic() {
1047        let input = "\
1048proto udp
1049remote 10.0.0.1 1194
1050dev tun
1051cipher AES-256-GCM
1052auth SHA256
1053compress lz4
1054";
1055        let config = parse_config(input);
1056        assert_eq!(config.protocol, TransportProto::Udp);
1057        assert_eq!(config.remote_host, Ipv4Address::new(10, 0, 0, 1));
1058        assert_eq!(config.remote_port, 1194);
1059        assert_eq!(config.dev_type, TunnelType::Tun);
1060        assert_eq!(config.cipher, CipherAlgorithm::Aes256Gcm);
1061        assert_eq!(config.auth, AuthAlgorithm::Sha256);
1062        assert_eq!(config.compress, Compression::Lz4);
1063    }
1064
1065    #[test]
1066    fn test_config_parse_comments_and_blanks() {
1067        let input = "\
1068# This is a comment
1069; Another comment
1070
1071proto tcp
1072port 443
1073dev tap0
1074cipher AES-128-CBC
1075auth SHA512
1076";
1077        let config = parse_config(input);
1078        assert_eq!(config.protocol, TransportProto::Tcp);
1079        assert_eq!(config.remote_port, 443);
1080        assert_eq!(config.dev_type, TunnelType::Tap);
1081        assert_eq!(config.cipher, CipherAlgorithm::Aes128Cbc);
1082        assert_eq!(config.auth, AuthAlgorithm::Sha512);
1083    }
1084
1085    #[test]
1086    fn test_parse_ipv4() {
1087        assert_eq!(parse_ipv4("192.168.1.1"), Ipv4Address::new(192, 168, 1, 1));
1088        assert_eq!(parse_ipv4("10.0.0.1"), Ipv4Address::new(10, 0, 0, 1));
1089        assert_eq!(parse_ipv4("0.0.0.0"), Ipv4Address::new(0, 0, 0, 0));
1090    }
1091}