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**: TUN/TAP kernel feature, requires root or `CAP_NET_ADMIN`
28//! - **Windows**: Wintun driver, requires administrator privileges
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, arbitrary on Windows.
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` on Linux, administrator privileges on Windows.
116pub struct VCLTun {
117    #[cfg(target_os = "linux")]
118    dev: tun::AsyncDevice,
119    #[cfg(target_os = "windows")]
120    dev: wintun::Adapter,
121    #[cfg(not(any(target_os = "linux", target_os = "windows")))]
122    _marker: std::marker::PhantomData<()>,
123    config: TunConfig,
124}
125
126impl VCLTun {
127    /// Create and configure a new TUN interface.
128    ///
129    /// # Errors
130    /// Returns [`VCLError::IoError`] if:
131    /// - Not running as root / missing `CAP_NET_ADMIN` (Linux)
132    /// - Not running as administrator (Windows)
133    /// - Interface name is invalid or already in use
134    /// - TUN/Wintun driver not available
135    #[cfg(target_os = "linux")]
136    pub fn create(config: TunConfig) -> Result<Self, VCLError> {
137        let mut tun_config = tun::Configuration::default();
138        tun_config.tun_name(&config.name);
139        tun_config
140            .address(config.address)
141            .destination(config.destination)
142            .netmask(config.netmask)
143            .mtu(config.mtu)
144            .up();
145
146        let dev = tun::create_as_async(&tun_config)
147            .map_err(|e| VCLError::IoError(format!("Failed to create TUN device: {}", e)))?;
148
149        info!(
150            name = %config.name,
151            address = %config.address,
152            destination = %config.destination,
153            mtu = config.mtu,
154            platform = "linux",
155            "TUN interface created"
156        );
157
158        Ok(VCLTun { dev, config })
159    }
160
161    /// Create and configure a new TUN interface on Windows using Wintun.
162    #[cfg(target_os = "windows")]
163    pub fn create(config: TunConfig) -> Result<Self, VCLError> {
164        let adapter = wintun::Adapter::open(&config.name)
165            .or_else(|_| wintun::Adapter::create(&config.name, "VCL", None))
166            .map_err(|e| VCLError::IoError(format!("Failed to create Wintun adapter: {}", e)))?;
167
168        adapter
169            .set_address(config.address)
170            .map_err(|e| VCLError::IoError(format!("Failed to set adapter address: {}", e)))?;
171        adapter
172            .set_gateway(config.destination)
173            .map_err(|e| VCLError::IoError(format!("Failed to set adapter gateway: {}", e)))?;
174        adapter
175            .set_netmask(config.netmask)
176            .map_err(|e| VCLError::IoError(format!("Failed to set adapter netmask: {}", e)))?;
177
178        info!(
179            name = %config.name,
180            address = %config.address,
181            destination = %config.destination,
182            mtu = config.mtu,
183            platform = "windows",
184            "Wintun interface created"
185        );
186
187        Ok(VCLTun { dev: adapter, config })
188    }
189
190    /// Stub for unsupported platforms.
191    #[cfg(not(any(target_os = "linux", target_os = "windows")))]
192    pub fn create(_config: TunConfig) -> Result<Self, VCLError> {
193        Err(VCLError::IoError(
194            "TUN interface is only supported on Linux and Windows".to_string(),
195        ))
196    }
197
198    /// Read the next IP packet from the TUN interface.
199    #[cfg(target_os = "linux")]
200    pub async fn read_packet(&mut self) -> Result<Vec<u8>, VCLError> {
201        use tokio::io::AsyncReadExt;
202        let mut buf = vec![0u8; self.config.mtu as usize + 4];
203        let n = self.dev.read(&mut buf).await
204            .map_err(|e| VCLError::IoError(format!("TUN read failed: {}", e)))?;
205        buf.truncate(n);
206        debug!(size = n, platform = "linux", "TUN packet read");
207        Ok(buf)
208    }
209
210    #[cfg(target_os = "windows")]
211    pub async fn read_packet(&mut self) -> Result<Vec<u8>, VCLError> {
212        let adapter = self.dev.clone();
213        let mtu = self.config.mtu as usize;
214        tokio::task::spawn_blocking(move || {
215            let mut buf = vec![0u8; mtu + 4];
216            let n = adapter.receive_packet(&mut buf)
217                .map_err(|e| VCLError::IoError(format!("Wintun read failed: {}", e)))?;
218            buf.truncate(n);
219            Ok(buf)
220        })
221        .await
222        .map_err(|e| VCLError::IoError(format!("Wintun read task failed: {}", e)))?
223    }
224
225    #[cfg(not(any(target_os = "linux", target_os = "windows")))]
226    pub async fn read_packet(&mut self) -> Result<Vec<u8>, VCLError> {
227        Err(VCLError::IoError("TUN not supported on this platform".to_string()))
228    }
229
230    /// Write (inject) a raw IP packet into the TUN interface.
231    #[cfg(target_os = "linux")]
232    pub async fn write_packet(&mut self, packet: &[u8]) -> Result<(), VCLError> {
233        use tokio::io::AsyncWriteExt;
234        self.dev.write_all(packet).await
235            .map_err(|e| VCLError::IoError(format!("TUN write failed: {}", e)))?;
236        debug!(size = packet.len(), platform = "linux", "TUN packet injected");
237        Ok(())
238    }
239
240    #[cfg(target_os = "windows")]
241    pub async fn write_packet(&mut self, packet: &[u8]) -> Result<(), VCLError> {
242        let adapter = self.dev.clone();
243        let packet = packet.to_vec();
244        tokio::task::spawn_blocking(move || {
245            adapter.send_packet(&packet)
246                .map_err(|e| VCLError::IoError(format!("Wintun write failed: {}", e)))?;
247            Ok(())
248        })
249        .await
250        .map_err(|e| VCLError::IoError(format!("Wintun write task failed: {}", e)))?
251    }
252
253    #[cfg(not(any(target_os = "linux", target_os = "windows")))]
254    pub async fn write_packet(&mut self, _packet: &[u8]) -> Result<(), VCLError> {
255        Err(VCLError::IoError("TUN not supported on this platform".to_string()))
256    }
257
258    /// Returns the name of the TUN interface.
259    pub fn name(&self) -> &str {
260        &self.config.name
261    }
262
263    /// Returns the MTU configured for this interface.
264    pub fn mtu(&self) -> u16 {
265        self.config.mtu
266    }
267
268    /// Returns the local IP address of the TUN interface.
269    pub fn address(&self) -> Ipv4Addr {
270        self.config.address
271    }
272
273    /// Returns the remote destination IP of the TUN interface.
274    pub fn destination(&self) -> Ipv4Addr {
275        self.config.destination
276    }
277
278    /// Returns a reference to the full [`TunConfig`].
279    pub fn config(&self) -> &TunConfig {
280        &self.config
281    }
282}
283
284// ─── IP Packet Parsing ────────────────────────────────────────────────────────
285
286/// Parse raw bytes from a TUN read into an [`IpPacket`].
287///
288/// Supports IPv4 and IPv6. Returns an error if the packet is too short
289/// or the IP version byte is missing.
290///
291/// # Errors
292/// Returns [`VCLError::InvalidPacket`] if the packet is malformed.
293pub fn parse_ip_packet(raw: Vec<u8>) -> Result<IpPacket, VCLError> {
294    if raw.is_empty() {
295        return Err(VCLError::InvalidPacket("Empty IP packet".to_string()));
296    }
297
298    let version_byte = raw[0] >> 4;
299    let len = raw.len();
300
301    match version_byte {
302        4 => parse_ipv4(raw, len),
303        6 => parse_ipv6(raw, len),
304        v => {
305            warn!(version = v, "Unknown IP version in TUN packet");
306            Ok(IpPacket {
307                raw,
308                version: IpVersion::Unknown(v),
309                src: String::new(),
310                dst: String::new(),
311                protocol: 0,
312                len,
313            })
314        }
315    }
316}
317
318fn parse_ipv4(raw: Vec<u8>, len: usize) -> Result<IpPacket, VCLError> {
319    let header = Ipv4HeaderSlice::from_slice(&raw)
320        .map_err(|e| VCLError::InvalidPacket(format!("IPv4 parse error: {}", e)))?;
321
322    let src = format!(
323        "{}.{}.{}.{}",
324        header.source()[0], header.source()[1],
325        header.source()[2], header.source()[3]
326    );
327    let dst = format!(
328        "{}.{}.{}.{}",
329        header.destination()[0], header.destination()[1],
330        header.destination()[2], header.destination()[3]
331    );
332    let protocol = header.protocol().0;
333
334    debug!(src = %src, dst = %dst, protocol, size = len, "IPv4 packet parsed");
335
336    Ok(IpPacket {
337        raw,
338        version: IpVersion::V4,
339        src,
340        dst,
341        protocol,
342        len,
343    })
344}
345
346fn parse_ipv6(raw: Vec<u8>, len: usize) -> Result<IpPacket, VCLError> {
347    let header = Ipv6HeaderSlice::from_slice(&raw)
348        .map_err(|e| VCLError::InvalidPacket(format!("IPv6 parse error: {}", e)))?;
349
350    let src = format!("{:?}", header.source_addr());
351    let dst = format!("{:?}", header.destination_addr());
352    let protocol = header.next_header().0;
353
354    debug!(src = %src, dst = %dst, protocol, size = len, "IPv6 packet parsed");
355
356    Ok(IpPacket {
357        raw,
358        version: IpVersion::V6,
359        src,
360        dst,
361        protocol,
362        len,
363    })
364}
365
366/// Check if a raw packet is IPv4.
367pub fn is_ipv4(raw: &[u8]) -> bool {
368    raw.first().map(|b| b >> 4 == 4).unwrap_or(false)
369}
370
371/// Check if a raw packet is IPv6.
372pub fn is_ipv6(raw: &[u8]) -> bool {
373    raw.first().map(|b| b >> 4 == 6).unwrap_or(false)
374}
375
376/// Returns the IP version of a raw packet, or `None` if empty.
377pub fn ip_version(raw: &[u8]) -> Option<IpVersion> {
378    raw.first().map(|b| match b >> 4 {
379        4 => IpVersion::V4,
380        6 => IpVersion::V6,
381        v => IpVersion::Unknown(v),
382    })
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388
389    fn make_ipv4_packet() -> Vec<u8> {
390        // Minimal valid IPv4 header (20 bytes) + 4 bytes payload
391        vec![
392            0x45, // version=4, IHL=5
393            0x00, // DSCP/ECN
394            0x00, 0x18, // total length = 24
395            0x00, 0x01, // identification
396            0x00, 0x00, // flags + fragment offset
397            0x40, // TTL = 64
398            0x06, // protocol = TCP (6)
399            0x00, 0x00, // checksum (not validated here)
400            192, 168, 1, 1,   // src
401            10, 0, 0, 1,      // dst
402            0x00, 0x00, 0x00, 0x00, // payload
403        ]
404    }
405
406    fn make_ipv6_packet() -> Vec<u8> {
407        // Minimal IPv6 header (40 bytes)
408        let mut pkt = vec![
409            0x60, 0x00, 0x00, 0x00, // version=6, TC, flow label
410            0x00, 0x08,             // payload length = 8
411            0x11,                   // next header = UDP (17)
412            0x40,                   // hop limit = 64
413        ];
414        // src addr (16 bytes) = ::1
415        pkt.extend_from_slice(&[0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1]);
416        // dst addr (16 bytes) = ::2
417        pkt.extend_from_slice(&[0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,2]);
418        // payload (8 bytes)
419        pkt.extend_from_slice(&[0u8; 8]);
420        pkt
421    }
422
423    #[test]
424    fn test_tun_config_default() {
425        let c = TunConfig::default();
426        assert_eq!(c.name, "vcl0");
427        assert_eq!(c.mtu, 1420);
428        assert_eq!(c.address, "10.0.0.1".parse::<Ipv4Addr>().unwrap());
429    }
430
431    #[test]
432    fn test_parse_ipv4_packet() {
433        let raw = make_ipv4_packet();
434        let pkt = parse_ip_packet(raw).unwrap();
435        assert_eq!(pkt.version, IpVersion::V4);
436        assert_eq!(pkt.src, "192.168.1.1");
437        assert_eq!(pkt.dst, "10.0.0.1");
438        assert_eq!(pkt.protocol, 6); // TCP
439    }
440
441    #[test]
442    fn test_parse_ipv6_packet() {
443        let raw = make_ipv6_packet();
444        let pkt = parse_ip_packet(raw).unwrap();
445        assert_eq!(pkt.version, IpVersion::V6);
446        assert_eq!(pkt.protocol, 17); // UDP
447    }
448
449    #[test]
450    fn test_parse_empty_packet() {
451        let result = parse_ip_packet(vec![]);
452        assert!(result.is_err());
453    }
454
455    #[test]
456    fn test_parse_unknown_version() {
457        let raw = vec![0x30, 0x00, 0x00, 0x00]; // version = 3
458        let pkt = parse_ip_packet(raw).unwrap();
459        assert_eq!(pkt.version, IpVersion::Unknown(3));
460    }
461
462    #[test]
463    fn test_is_ipv4() {
464        assert!(is_ipv4(&make_ipv4_packet()));
465        assert!(!is_ipv4(&make_ipv6_packet()));
466        assert!(!is_ipv4(&[]));
467    }
468
469    #[test]
470    fn test_is_ipv6() {
471        assert!(is_ipv6(&make_ipv6_packet()));
472        assert!(!is_ipv6(&make_ipv4_packet()));
473        assert!(!is_ipv6(&[]));
474    }
475
476    #[test]
477    fn test_ip_version() {
478        assert_eq!(ip_version(&make_ipv4_packet()), Some(IpVersion::V4));
479        assert_eq!(ip_version(&make_ipv6_packet()), Some(IpVersion::V6));
480        assert_eq!(ip_version(&[0x30]), Some(IpVersion::Unknown(3)));
481        assert_eq!(ip_version(&[]), None);
482    }
483
484    #[test]
485    fn test_tun_create_non_linux() {
486        #[cfg(not(target_os = "linux"))]
487        {
488            let result = VCLTun::create(TunConfig::default());
489            assert!(result.is_err());
490        }
491        #[cfg(target_os = "linux")]
492        {
493            let c = TunConfig::default();
494            assert_eq!(c.mtu, 1420);
495        }
496    }
497
498    #[test]
499    fn test_ip_packet_len() {
500        let raw = make_ipv4_packet();
501        let expected_len = raw.len();
502        let pkt = parse_ip_packet(raw).unwrap();
503        assert_eq!(pkt.len, expected_len);
504    }
505}