1use std::{
2 fs::{File, OpenOptions},
3 mem::size_of,
4 os::fd::AsRawFd,
5};
6
7use ioctl_gen::{ioc, iow};
8use libc::{
9 __c_anonymous_ifr_ifru, ifreq, ioctl, IFF_MULTI_QUEUE, IFF_NO_PI, IFF_TUN, IF_NAMESIZE,
10};
11
12mod arch {
14 #[cfg(all(target_os = "linux", target_env = "gnu"))]
15 pub type IoctlRequestType = libc::c_ulong;
16 #[cfg(all(target_os = "linux", target_env = "musl"))]
17 pub type IoctlRequestType = libc::c_int;
18 #[cfg(not(any(target_env = "gnu", target_env = "musl")))]
19 compile_error!(
20 "Unsupported target environment. Only gnu and musl targets are currently supported."
21 );
22}
23
24pub struct Tun {
26 fds: Vec<File>,
28 name: String,
30}
31
32impl Tun {
33 #[allow(clippy::cast_possible_truncation)]
38 #[allow(clippy::cast_lossless)]
39 pub fn new(dev: &str, queues: usize) -> Result<Self, std::io::Error> {
40 log::debug!(
41 "Creating new TUN device with requested name: {} ({} queues)",
42 dev,
43 queues
44 );
45
46 log::trace!("Opening /dev/net/tun");
48 let mut fds = Vec::with_capacity(queues);
49 for _ in 0..queues {
50 let fd = OpenOptions::new()
51 .read(true)
52 .write(true)
53 .open("/dev/net/tun")?;
54 fds.push(fd);
55 }
56
57 let mut dev_cstr: [libc::c_char; IF_NAMESIZE] = [0; IF_NAMESIZE];
60 let dev_bytes: Vec<libc::c_char> = dev.chars().map(|c| c as libc::c_char).collect();
61 let dev_len = dev_bytes.len().min(IF_NAMESIZE);
62 log::trace!("Device name length after truncation: {}", dev_len);
63 dev_cstr[..dev_len].copy_from_slice(&dev_bytes[..dev_len]);
64
65 let mut ifr = ifreq {
67 ifr_name: dev_cstr,
68 ifr_ifru: __c_anonymous_ifr_ifru {
69 ifru_flags: (IFF_TUN | IFF_NO_PI | IFF_MULTI_QUEUE) as i16,
70 },
71 };
72
73 for fd in &mut fds {
75 log::trace!("Calling ioctl to create TUN device");
77 let err = unsafe {
78 ioctl(
79 fd.as_raw_fd(),
80 iow!('T', 202, size_of::<libc::c_int>()) as arch::IoctlRequestType,
81 &mut ifr,
82 )
83 };
84 log::trace!("ioctl returned: {}", err);
85
86 if err < 0 {
88 log::error!("ioctl failed: {}", err);
89 return Err(std::io::Error::last_os_error());
90 }
91 }
92
93 let name = unsafe { std::ffi::CStr::from_ptr(ifr.ifr_name.as_ptr()) }
95 .to_str()
96 .unwrap()
97 .to_string();
98
99 log::debug!("Created TUN device: {}", name);
101
102 Ok(Self { fds, name })
104 }
105
106 #[must_use]
108 pub fn name(&self) -> &str {
109 &self.name
110 }
111
112 #[must_use]
114 pub fn fd(&self, queue_id: usize) -> Option<&File> {
115 self.fds.get(queue_id)
116 }
117
118 #[must_use]
120 pub fn fd_mut(&mut self, queue_id: usize) -> Option<&mut File> {
121 self.fds.get_mut(queue_id).map(|fd| &mut *fd)
122 }
123}