Skip to main content

vcl_protocol/
tun_device.rs

1//! # VCL TUN Interface
2//!
3//! Provides [`VCLTun`] — a TUN virtual network interface that captures
4//! IP packets from the OS network stack and injects them back after
5//! decryption/routing through a VCL tunnel.
6//!
7//! ## How it works
8//!
9//! ```text
10//! Application
11//!     ↓ (writes to TCP/UDP socket)
12//! OS Network Stack
13//!     ↓ (routes via routing table)
14//! TUN interface (vcl0)
15//!     ↓ (VCLTun::read_packet)
16//! VCL Protocol (encrypt + send)
17//!     ↓ (network)
18//! Remote VCL (recv + decrypt)
19//!     ↓ (VCLTun::write_packet)
20//! TUN interface (remote vcl0)
21//!     ↓ (inject into OS network stack)
22//! Remote application
23//! ```
24//!
25//! ## Requirements
26//!
27//! - Linux only (TUN/TAP is a Linux kernel feature)
28//! - Requires root or `CAP_NET_ADMIN` capability
29//! - Does NOT work in WSL2 without custom kernel
30//!
31//! ## Example
32//!
33//! ```no_run
34//! use vcl_protocol::tun_device::{VCLTun, TunConfig};
35//!
36//! #[tokio::main]
37//! async fn main() {
38//!     let config = TunConfig {
39//!         name: "vcl0".to_string(),
40//!         address: "10.0.0.1".parse().unwrap(),
41//!         destination: "10.0.0.2".parse().unwrap(),
42//!         netmask: "255.255.255.0".parse().unwrap(),
43//!         mtu: 1420,
44//!     };
45//!
46//!     let mut tun = VCLTun::create(config).unwrap();
47//!
48//!     loop {
49//!         let packet = tun.read_packet().await.unwrap();
50//!         println!("Captured {} bytes", packet.len());
51//!         // encrypt and send via VCL connection
52//!     }
53//! }
54//! ```
55
56use std::net::Ipv4Addr;
57use crate::error::VCLError;
58use etherparse::{Ipv4HeaderSlice, Ipv6HeaderSlice};
59use tracing::{debug, info, warn};
60
61/// Configuration for a TUN virtual network interface.
62#[derive(Debug, Clone)]
63pub struct TunConfig {
64    /// Interface name (e.g. "vcl0"). Max 15 chars on Linux.
65    pub name: String,
66    /// Local IP address assigned to the TUN interface.
67    pub address: Ipv4Addr,
68    /// Remote end of the point-to-point link.
69    pub destination: Ipv4Addr,
70    /// Netmask for the TUN interface.
71    pub netmask: Ipv4Addr,
72    /// MTU in bytes. Default 1420 leaves room for VCL headers over UDP.
73    pub mtu: u16,
74}
75
76impl Default for TunConfig {
77    fn default() -> Self {
78        TunConfig {
79            name: "vcl0".to_string(),
80            address: "10.0.0.1".parse().unwrap(),
81            destination: "10.0.0.2".parse().unwrap(),
82            netmask: "255.255.255.0".parse().unwrap(),
83            mtu: 1420,
84        }
85    }
86}
87
88/// IP protocol version detected in a packet.
89#[derive(Debug, Clone, PartialEq)]
90pub enum IpVersion {
91    V4,
92    V6,
93    Unknown(u8),
94}
95
96/// A parsed IP packet read from the TUN interface.
97#[derive(Debug, Clone)]
98pub struct IpPacket {
99    /// Raw packet bytes (including IP header).
100    pub raw: Vec<u8>,
101    /// IP version.
102    pub version: IpVersion,
103    /// Source IP (as string for both v4 and v6).
104    pub src: String,
105    /// Destination IP (as string for both v4 and v6).
106    pub dst: String,
107    /// IP protocol number (6=TCP, 17=UDP, 1=ICMP, etc).
108    pub protocol: u8,
109    /// Total packet length in bytes.
110    pub len: usize,
111}
112
113/// A TUN virtual network interface for capturing and injecting IP packets.
114///
115/// Requires root or `CAP_NET_ADMIN`. Linux only.
116pub struct VCLTun {
117    #[cfg(target_os = "linux")]
118    dev: tun::AsyncDevice,
119    config: TunConfig,
120}
121
122impl VCLTun {
123    /// Create and configure a new TUN interface.
124    ///
125    /// # Errors
126    /// Returns [`VCLError::IoError`] if:
127    /// - Not running as root / missing `CAP_NET_ADMIN`
128    /// - Interface name is invalid or already in use
129    /// - TUN driver not available
130    #[cfg(target_os = "linux")]
131    pub fn create(config: TunConfig) -> Result<Self, VCLError> {
132        let mut tun_config = tun::Configuration::default();
133        tun_config
134            .name(&config.name)
135            .address(config.address)
136            .destination(config.destination)
137            .netmask(config.netmask)
138            .mtu(config.mtu as i32)
139            .up();
140
141        let dev = tun::create_as_async(&tun_config)
142            .map_err(|e| VCLError::IoError(format!("Failed to create TUN device: {}", e)))?;
143
144        info!(
145            name = %config.name,
146            address = %config.address,
147            destination = %config.destination,
148            mtu = config.mtu,
149            "TUN interface created"
150        );
151
152        Ok(VCLTun { dev, config })
153    }
154
155    /// Stub for non-Linux platforms — TUN is not supported.
156    #[cfg(not(target_os = "linux"))]
157    pub fn create(_config: TunConfig) -> Result<Self, VCLError> {
158        Err(VCLError::IoError(
159            "TUN interface is only supported on Linux".to_string(),
160        ))
161    }
162
163    /// Read the next IP packet from the TUN interface.
164    ///
165    /// Blocks asynchronously until a packet is available.
166    /// Returns raw bytes including the IP header.
167    ///
168    /// # Errors
169    /// Returns [`VCLError::IoError`] on read failure.
170    #[cfg(target_os = "linux")]
171    pub async fn read_packet(&mut self) -> Result<Vec<u8>, VCLError> {
172        use tokio::io::AsyncReadExt;
173        let mut buf = vec![0u8; self.config.mtu as usize + 4];
174        let n = self.dev.read(&mut buf).await
175            .map_err(|e| VCLError::IoError(format!("TUN read failed: {}", e)))?;
176        buf.truncate(n);
177        debug!(size = n, "TUN packet read");
178        Ok(buf)
179    }
180
181    #[cfg(not(target_os = "linux"))]
182    pub async fn read_packet(&mut self) -> Result<Vec<u8>, VCLError> {
183        Err(VCLError::IoError("TUN not supported on this platform".to_string()))
184    }
185
186    /// Write (inject) a raw IP packet into the TUN interface.
187    ///
188    /// The packet will appear as if it came from the network
189    /// and will be delivered to the appropriate local socket.
190    ///
191    /// # Errors
192    /// Returns [`VCLError::IoError`] on write failure.
193    #[cfg(target_os = "linux")]
194    pub async fn write_packet(&mut self, packet: &[u8]) -> Result<(), VCLError> {
195        use tokio::io::AsyncWriteExt;
196        self.dev.write_all(packet).await
197            .map_err(|e| VCLError::IoError(format!("TUN write failed: {}", e)))?;
198        debug!(size = packet.len(), "TUN packet injected");
199        Ok(())
200    }
201
202    #[cfg(not(target_os = "linux"))]
203    pub async fn write_packet(&mut self, _packet: &[u8]) -> Result<(), VCLError> {
204        Err(VCLError::IoError("TUN not supported on this platform".to_string()))
205    }
206
207    /// Returns the name of the TUN interface (e.g. "vcl0").
208    pub fn name(&self) -> &str {
209        &self.config.name
210    }
211
212    /// Returns the MTU configured for this interface.
213    pub fn mtu(&self) -> u16 {
214        self.config.mtu
215    }
216
217    /// Returns the local IP address of the TUN interface.
218    pub fn address(&self) -> Ipv4Addr {
219        self.config.address
220    }
221
222    /// Returns the remote destination IP of the TUN interface.
223    pub fn destination(&self) -> Ipv4Addr {
224        self.config.destination
225    }
226
227    /// Returns a reference to the full [`TunConfig`].
228    pub fn config(&self) -> &TunConfig {
229        &self.config
230    }
231}
232
233// ─── IP Packet Parsing ────────────────────────────────────────────────────────
234
235/// Parse raw bytes from a TUN read into an [`IpPacket`].
236///
237/// Supports IPv4 and IPv6. Returns an error if the packet is too short
238/// or the IP version byte is missing.
239///
240/// # Errors
241/// Returns [`VCLError::InvalidPacket`] if the packet is malformed.
242pub fn parse_ip_packet(raw: Vec<u8>) -> Result<IpPacket, VCLError> {
243    if raw.is_empty() {
244        return Err(VCLError::InvalidPacket("Empty IP packet".to_string()));
245    }
246
247    let version_byte = raw[0] >> 4;
248    let len = raw.len();
249
250    match version_byte {
251        4 => parse_ipv4(raw, len),
252        6 => parse_ipv6(raw, len),
253        v => {
254            warn!(version = v, "Unknown IP version in TUN packet");
255            Ok(IpPacket {
256                raw,
257                version: IpVersion::Unknown(v),
258                src: String::new(),
259                dst: String::new(),
260                protocol: 0,
261                len,
262            })
263        }
264    }
265}
266
267fn parse_ipv4(raw: Vec<u8>, len: usize) -> Result<IpPacket, VCLError> {
268    let header = Ipv4HeaderSlice::from_slice(&raw)
269        .map_err(|e| VCLError::InvalidPacket(format!("IPv4 parse error: {}", e)))?;
270
271    let src = format!(
272        "{}.{}.{}.{}",
273        header.source()[0], header.source()[1],
274        header.source()[2], header.source()[3]
275    );
276    let dst = format!(
277        "{}.{}.{}.{}",
278        header.destination()[0], header.destination()[1],
279        header.destination()[2], header.destination()[3]
280    );
281    let protocol = header.protocol().0;
282
283    debug!(src = %src, dst = %dst, protocol, size = len, "IPv4 packet parsed");
284
285    Ok(IpPacket {
286        raw,
287        version: IpVersion::V4,
288        src,
289        dst,
290        protocol,
291        len,
292    })
293}
294
295fn parse_ipv6(raw: Vec<u8>, len: usize) -> Result<IpPacket, VCLError> {
296    let header = Ipv6HeaderSlice::from_slice(&raw)
297        .map_err(|e| VCLError::InvalidPacket(format!("IPv6 parse error: {}", e)))?;
298
299    let src = format!("{:?}", header.source_addr());
300    let dst = format!("{:?}", header.destination_addr());
301    let protocol = header.next_header().0;
302
303    debug!(src = %src, dst = %dst, protocol, size = len, "IPv6 packet parsed");
304
305    Ok(IpPacket {
306        raw,
307        version: IpVersion::V6,
308        src,
309        dst,
310        protocol,
311        len,
312    })
313}
314
315/// Check if a raw packet is IPv4.
316pub fn is_ipv4(raw: &[u8]) -> bool {
317    raw.first().map(|b| b >> 4 == 4).unwrap_or(false)
318}
319
320/// Check if a raw packet is IPv6.
321pub fn is_ipv6(raw: &[u8]) -> bool {
322    raw.first().map(|b| b >> 4 == 6).unwrap_or(false)
323}
324
325/// Returns the IP version of a raw packet, or `None` if empty.
326pub fn ip_version(raw: &[u8]) -> Option<IpVersion> {
327    raw.first().map(|b| match b >> 4 {
328        4 => IpVersion::V4,
329        6 => IpVersion::V6,
330        v => IpVersion::Unknown(v),
331    })
332}
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337
338    fn make_ipv4_packet() -> Vec<u8> {
339        // Minimal valid IPv4 header (20 bytes) + 4 bytes payload
340        vec![
341            0x45, // version=4, IHL=5
342            0x00, // DSCP/ECN
343            0x00, 0x18, // total length = 24
344            0x00, 0x01, // identification
345            0x00, 0x00, // flags + fragment offset
346            0x40, // TTL = 64
347            0x06, // protocol = TCP (6)
348            0x00, 0x00, // checksum (not validated here)
349            192, 168, 1, 1,   // src
350            10, 0, 0, 1,      // dst
351            0x00, 0x00, 0x00, 0x00, // payload
352        ]
353    }
354
355    fn make_ipv6_packet() -> Vec<u8> {
356        // Minimal IPv6 header (40 bytes)
357        let mut pkt = vec![
358            0x60, 0x00, 0x00, 0x00, // version=6, TC, flow label
359            0x00, 0x08,             // payload length = 8
360            0x11,                   // next header = UDP (17)
361            0x40,                   // hop limit = 64
362        ];
363        // src addr (16 bytes) = ::1
364        pkt.extend_from_slice(&[0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1]);
365        // dst addr (16 bytes) = ::2
366        pkt.extend_from_slice(&[0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,2]);
367        // payload (8 bytes)
368        pkt.extend_from_slice(&[0u8; 8]);
369        pkt
370    }
371
372    #[test]
373    fn test_tun_config_default() {
374        let c = TunConfig::default();
375        assert_eq!(c.name, "vcl0");
376        assert_eq!(c.mtu, 1420);
377        assert_eq!(c.address, "10.0.0.1".parse::<Ipv4Addr>().unwrap());
378    }
379
380    #[test]
381    fn test_parse_ipv4_packet() {
382        let raw = make_ipv4_packet();
383        let pkt = parse_ip_packet(raw).unwrap();
384        assert_eq!(pkt.version, IpVersion::V4);
385        assert_eq!(pkt.src, "192.168.1.1");
386        assert_eq!(pkt.dst, "10.0.0.1");
387        assert_eq!(pkt.protocol, 6); // TCP
388    }
389
390    #[test]
391    fn test_parse_ipv6_packet() {
392        let raw = make_ipv6_packet();
393        let pkt = parse_ip_packet(raw).unwrap();
394        assert_eq!(pkt.version, IpVersion::V6);
395        assert_eq!(pkt.protocol, 17); // UDP
396    }
397
398    #[test]
399    fn test_parse_empty_packet() {
400        let result = parse_ip_packet(vec![]);
401        assert!(result.is_err());
402    }
403
404    #[test]
405    fn test_parse_unknown_version() {
406        let raw = vec![0x30, 0x00, 0x00, 0x00]; // version = 3
407        let pkt = parse_ip_packet(raw).unwrap();
408        assert_eq!(pkt.version, IpVersion::Unknown(3));
409    }
410
411    #[test]
412    fn test_is_ipv4() {
413        assert!(is_ipv4(&make_ipv4_packet()));
414        assert!(!is_ipv4(&make_ipv6_packet()));
415        assert!(!is_ipv4(&[]));
416    }
417
418    #[test]
419    fn test_is_ipv6() {
420        assert!(is_ipv6(&make_ipv6_packet()));
421        assert!(!is_ipv6(&make_ipv4_packet()));
422        assert!(!is_ipv6(&[]));
423    }
424
425    #[test]
426    fn test_ip_version() {
427        assert_eq!(ip_version(&make_ipv4_packet()), Some(IpVersion::V4));
428        assert_eq!(ip_version(&make_ipv6_packet()), Some(IpVersion::V6));
429        assert_eq!(ip_version(&[0x30]), Some(IpVersion::Unknown(3)));
430        assert_eq!(ip_version(&[]), None);
431    }
432
433    #[test]
434    fn test_tun_create_non_linux() {
435        // На не-Linux платформах (включая WSL2) должна вернуть ошибку
436        #[cfg(not(target_os = "linux"))]
437        {
438            let result = VCLTun::create(TunConfig::default());
439            assert!(result.is_err());
440        }
441        // На Linux пропускаем — требует root
442        #[cfg(target_os = "linux")]
443        {
444            // Просто проверяем что конфиг создаётся
445            let c = TunConfig::default();
446            assert_eq!(c.mtu, 1420);
447        }
448    }
449
450    #[test]
451    fn test_ip_packet_len() {
452        let raw = make_ipv4_packet();
453        let expected_len = raw.len();
454        let pkt = parse_ip_packet(raw).unwrap();
455        assert_eq!(pkt.len, expected_len);
456    }
457}