use std::net::Ipv4Addr;
use crate::error::VCLError;
use etherparse::{Ipv4HeaderSlice, Ipv6HeaderSlice};
use tracing::{debug, info, warn};
#[derive(Debug, Clone)]
pub struct TunConfig {
pub name: String,
pub address: Ipv4Addr,
pub destination: Ipv4Addr,
pub netmask: Ipv4Addr,
pub mtu: u16,
}
impl Default for TunConfig {
fn default() -> Self {
TunConfig {
name: "vcl0".to_string(),
address: "10.0.0.1".parse().unwrap(),
destination: "10.0.0.2".parse().unwrap(),
netmask: "255.255.255.0".parse().unwrap(),
mtu: 1420,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum IpVersion {
V4,
V6,
Unknown(u8),
}
#[derive(Debug, Clone)]
pub struct IpPacket {
pub raw: Vec<u8>,
pub version: IpVersion,
pub src: String,
pub dst: String,
pub protocol: u8,
pub len: usize,
}
pub struct VCLTun {
#[cfg(target_os = "linux")]
dev: tun::AsyncDevice,
config: TunConfig,
}
impl VCLTun {
#[cfg(target_os = "linux")]
pub fn create(config: TunConfig) -> Result<Self, VCLError> {
let mut tun_config = tun::Configuration::default();
tun_config
.name(&config.name)
.address(config.address)
.destination(config.destination)
.netmask(config.netmask)
.mtu(config.mtu as i32)
.up();
let dev = tun::create_as_async(&tun_config)
.map_err(|e| VCLError::IoError(format!("Failed to create TUN device: {}", e)))?;
info!(
name = %config.name,
address = %config.address,
destination = %config.destination,
mtu = config.mtu,
"TUN interface created"
);
Ok(VCLTun { dev, config })
}
#[cfg(not(target_os = "linux"))]
pub fn create(_config: TunConfig) -> Result<Self, VCLError> {
Err(VCLError::IoError(
"TUN interface is only supported on Linux".to_string(),
))
}
#[cfg(target_os = "linux")]
pub async fn read_packet(&mut self) -> Result<Vec<u8>, VCLError> {
use tokio::io::AsyncReadExt;
let mut buf = vec![0u8; self.config.mtu as usize + 4];
let n = self.dev.read(&mut buf).await
.map_err(|e| VCLError::IoError(format!("TUN read failed: {}", e)))?;
buf.truncate(n);
debug!(size = n, "TUN packet read");
Ok(buf)
}
#[cfg(not(target_os = "linux"))]
pub async fn read_packet(&mut self) -> Result<Vec<u8>, VCLError> {
Err(VCLError::IoError("TUN not supported on this platform".to_string()))
}
#[cfg(target_os = "linux")]
pub async fn write_packet(&mut self, packet: &[u8]) -> Result<(), VCLError> {
use tokio::io::AsyncWriteExt;
self.dev.write_all(packet).await
.map_err(|e| VCLError::IoError(format!("TUN write failed: {}", e)))?;
debug!(size = packet.len(), "TUN packet injected");
Ok(())
}
#[cfg(not(target_os = "linux"))]
pub async fn write_packet(&mut self, _packet: &[u8]) -> Result<(), VCLError> {
Err(VCLError::IoError("TUN not supported on this platform".to_string()))
}
pub fn name(&self) -> &str {
&self.config.name
}
pub fn mtu(&self) -> u16 {
self.config.mtu
}
pub fn address(&self) -> Ipv4Addr {
self.config.address
}
pub fn destination(&self) -> Ipv4Addr {
self.config.destination
}
pub fn config(&self) -> &TunConfig {
&self.config
}
}
pub fn parse_ip_packet(raw: Vec<u8>) -> Result<IpPacket, VCLError> {
if raw.is_empty() {
return Err(VCLError::InvalidPacket("Empty IP packet".to_string()));
}
let version_byte = raw[0] >> 4;
let len = raw.len();
match version_byte {
4 => parse_ipv4(raw, len),
6 => parse_ipv6(raw, len),
v => {
warn!(version = v, "Unknown IP version in TUN packet");
Ok(IpPacket {
raw,
version: IpVersion::Unknown(v),
src: String::new(),
dst: String::new(),
protocol: 0,
len,
})
}
}
}
fn parse_ipv4(raw: Vec<u8>, len: usize) -> Result<IpPacket, VCLError> {
let header = Ipv4HeaderSlice::from_slice(&raw)
.map_err(|e| VCLError::InvalidPacket(format!("IPv4 parse error: {}", e)))?;
let src = format!(
"{}.{}.{}.{}",
header.source()[0], header.source()[1],
header.source()[2], header.source()[3]
);
let dst = format!(
"{}.{}.{}.{}",
header.destination()[0], header.destination()[1],
header.destination()[2], header.destination()[3]
);
let protocol = header.protocol().0;
debug!(src = %src, dst = %dst, protocol, size = len, "IPv4 packet parsed");
Ok(IpPacket {
raw,
version: IpVersion::V4,
src,
dst,
protocol,
len,
})
}
fn parse_ipv6(raw: Vec<u8>, len: usize) -> Result<IpPacket, VCLError> {
let header = Ipv6HeaderSlice::from_slice(&raw)
.map_err(|e| VCLError::InvalidPacket(format!("IPv6 parse error: {}", e)))?;
let src = format!("{:?}", header.source_addr());
let dst = format!("{:?}", header.destination_addr());
let protocol = header.next_header().0;
debug!(src = %src, dst = %dst, protocol, size = len, "IPv6 packet parsed");
Ok(IpPacket {
raw,
version: IpVersion::V6,
src,
dst,
protocol,
len,
})
}
pub fn is_ipv4(raw: &[u8]) -> bool {
raw.first().map(|b| b >> 4 == 4).unwrap_or(false)
}
pub fn is_ipv6(raw: &[u8]) -> bool {
raw.first().map(|b| b >> 4 == 6).unwrap_or(false)
}
pub fn ip_version(raw: &[u8]) -> Option<IpVersion> {
raw.first().map(|b| match b >> 4 {
4 => IpVersion::V4,
6 => IpVersion::V6,
v => IpVersion::Unknown(v),
})
}
#[cfg(test)]
mod tests {
use super::*;
fn make_ipv4_packet() -> Vec<u8> {
vec![
0x45, 0x00, 0x00, 0x18, 0x00, 0x01, 0x00, 0x00, 0x40, 0x06, 0x00, 0x00, 192, 168, 1, 1, 10, 0, 0, 1, 0x00, 0x00, 0x00, 0x00, ]
}
fn make_ipv6_packet() -> Vec<u8> {
let mut pkt = vec![
0x60, 0x00, 0x00, 0x00, 0x00, 0x08, 0x11, 0x40, ];
pkt.extend_from_slice(&[0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1]);
pkt.extend_from_slice(&[0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,2]);
pkt.extend_from_slice(&[0u8; 8]);
pkt
}
#[test]
fn test_tun_config_default() {
let c = TunConfig::default();
assert_eq!(c.name, "vcl0");
assert_eq!(c.mtu, 1420);
assert_eq!(c.address, "10.0.0.1".parse::<Ipv4Addr>().unwrap());
}
#[test]
fn test_parse_ipv4_packet() {
let raw = make_ipv4_packet();
let pkt = parse_ip_packet(raw).unwrap();
assert_eq!(pkt.version, IpVersion::V4);
assert_eq!(pkt.src, "192.168.1.1");
assert_eq!(pkt.dst, "10.0.0.1");
assert_eq!(pkt.protocol, 6); }
#[test]
fn test_parse_ipv6_packet() {
let raw = make_ipv6_packet();
let pkt = parse_ip_packet(raw).unwrap();
assert_eq!(pkt.version, IpVersion::V6);
assert_eq!(pkt.protocol, 17); }
#[test]
fn test_parse_empty_packet() {
let result = parse_ip_packet(vec![]);
assert!(result.is_err());
}
#[test]
fn test_parse_unknown_version() {
let raw = vec![0x30, 0x00, 0x00, 0x00]; let pkt = parse_ip_packet(raw).unwrap();
assert_eq!(pkt.version, IpVersion::Unknown(3));
}
#[test]
fn test_is_ipv4() {
assert!(is_ipv4(&make_ipv4_packet()));
assert!(!is_ipv4(&make_ipv6_packet()));
assert!(!is_ipv4(&[]));
}
#[test]
fn test_is_ipv6() {
assert!(is_ipv6(&make_ipv6_packet()));
assert!(!is_ipv6(&make_ipv4_packet()));
assert!(!is_ipv6(&[]));
}
#[test]
fn test_ip_version() {
assert_eq!(ip_version(&make_ipv4_packet()), Some(IpVersion::V4));
assert_eq!(ip_version(&make_ipv6_packet()), Some(IpVersion::V6));
assert_eq!(ip_version(&[0x30]), Some(IpVersion::Unknown(3)));
assert_eq!(ip_version(&[]), None);
}
#[test]
fn test_tun_create_non_linux() {
#[cfg(not(target_os = "linux"))]
{
let result = VCLTun::create(TunConfig::default());
assert!(result.is_err());
}
#[cfg(target_os = "linux")]
{
let c = TunConfig::default();
assert_eq!(c.mtu, 1420);
}
}
#[test]
fn test_ip_packet_len() {
let raw = make_ipv4_packet();
let expected_len = raw.len();
let pkt = parse_ip_packet(raw).unwrap();
assert_eq!(pkt.len, expected_len);
}
}