Skip to main content

arcbox_virtio_net/
tap.rs

1//! TAP-device backend (Linux only).
2
3#![cfg(target_os = "linux")]
4
5use std::os::unix::io::RawFd;
6
7use crate::backend::{NetBackend, NetOffloadFlags};
8use crate::header::NetPacket;
9
10// Linux kernel `TUNSETOFFLOAD` / `TUNSETVNETHDRSZ` ioctl numbers and the
11// `TUN_F_*` flag bits. See `<linux/if_tun.h>` — we encode them inline rather
12// than pulling in another crate because only the TAP backend needs them.
13const TUNSETOFFLOAD: libc::c_ulong = 0x400454d0;
14const TUNSETVNETHDRSZ: libc::c_ulong = 0x400454d8;
15
16const TUN_F_CSUM: u32 = 0x01;
17const TUN_F_TSO4: u32 = 0x02;
18const TUN_F_TSO6: u32 = 0x04;
19const TUN_F_TSO_ECN: u32 = 0x08;
20const TUN_F_UFO: u32 = 0x10;
21
22/// TAP network backend for Linux.
23pub struct TapBackend {
24    /// TAP file descriptor.
25    fd: RawFd,
26    /// TAP device name.
27    name: String,
28    /// Non-blocking mode.
29    nonblocking: bool,
30}
31
32impl TapBackend {
33    /// Creates a new TAP device.
34    ///
35    /// # Errors
36    ///
37    /// Returns an error if TAP device creation fails.
38    pub fn new(name: Option<&str>) -> std::io::Result<Self> {
39        // SAFETY: open() with a static null-terminated path string. The
40        // returned fd (if non-negative) is owned by `Self` and closed in `Drop`.
41        let fd: RawFd = unsafe {
42            libc::open(
43                b"/dev/net/tun\0".as_ptr() as *const libc::c_char,
44                libc::O_RDWR | libc::O_CLOEXEC,
45            )
46        };
47
48        if fd < 0 {
49            return Err(std::io::Error::last_os_error());
50        }
51
52        #[repr(C)]
53        struct Ifreq {
54            ifr_name: [libc::c_char; libc::IFNAMSIZ],
55            ifr_flags: libc::c_short,
56            _padding: [u8; 22], // Padding to match ifreq size
57        }
58
59        let mut ifr = Ifreq {
60            ifr_name: [0; libc::IFNAMSIZ],
61            ifr_flags: (libc::IFF_TAP | libc::IFF_NO_PI) as libc::c_short,
62            _padding: [0; 22],
63        };
64
65        if let Some(dev_name) = name {
66            let name_bytes = dev_name.as_bytes();
67            let len = name_bytes.len().min(libc::IFNAMSIZ - 1);
68            for (i, &b) in name_bytes[..len].iter().enumerate() {
69                ifr.ifr_name[i] = b as libc::c_char;
70            }
71        }
72
73        // Create TAP device
74        const TUNSETIFF: libc::c_ulong = 0x400454ca;
75        // SAFETY: ioctl reads `&ifr` for the duration of the call; on failure
76        // we close the fd we just opened.
77        let ret = unsafe { libc::ioctl(fd, TUNSETIFF, &ifr) };
78        if ret < 0 {
79            unsafe { libc::close(fd) };
80            return Err(std::io::Error::last_os_error());
81        }
82
83        let name = {
84            let len = ifr
85                .ifr_name
86                .iter()
87                .position(|&c| c == 0)
88                .unwrap_or(libc::IFNAMSIZ);
89            let bytes: Vec<u8> = ifr.ifr_name[..len].iter().map(|&c| c as u8).collect();
90            String::from_utf8_lossy(&bytes).into_owned()
91        };
92
93        tracing::info!("Created TAP device: {}", name);
94
95        Ok(Self {
96            fd,
97            name,
98            nonblocking: false,
99        })
100    }
101
102    /// Sets non-blocking mode.
103    pub fn set_nonblocking(&mut self, nonblocking: bool) -> std::io::Result<()> {
104        // SAFETY: F_GETFL/F_SETFL on a fd we exclusively own.
105        let flags = unsafe { libc::fcntl(self.fd, libc::F_GETFL) };
106        if flags < 0 {
107            return Err(std::io::Error::last_os_error());
108        }
109
110        let new_flags = if nonblocking {
111            flags | libc::O_NONBLOCK
112        } else {
113            flags & !libc::O_NONBLOCK
114        };
115
116        // SAFETY: see above.
117        let ret = unsafe { libc::fcntl(self.fd, libc::F_SETFL, new_flags) };
118        if ret < 0 {
119            return Err(std::io::Error::last_os_error());
120        }
121
122        self.nonblocking = nonblocking;
123        Ok(())
124    }
125
126    /// Returns the TAP device name.
127    #[must_use]
128    pub fn name(&self) -> &str {
129        &self.name
130    }
131
132    /// Brings the interface up.
133    pub fn bring_up(&self) -> std::io::Result<()> {
134        use std::process::Command;
135
136        let status = Command::new("ip")
137            .args(["link", "set", &self.name, "up"])
138            .status()?;
139
140        if status.success() {
141            Ok(())
142        } else {
143            Err(std::io::Error::new(
144                std::io::ErrorKind::Other,
145                "Failed to bring interface up",
146            ))
147        }
148    }
149
150    /// Sets the IP address.
151    pub fn set_ip(&self, ip: &str, prefix_len: u8) -> std::io::Result<()> {
152        use std::process::Command;
153
154        let addr = format!("{}/{}", ip, prefix_len);
155        let status = Command::new("ip")
156            .args(["addr", "add", &addr, "dev", &self.name])
157            .status()?;
158
159        if status.success() {
160            Ok(())
161        } else {
162            Err(std::io::Error::new(
163                std::io::ErrorKind::Other,
164                "Failed to set IP address",
165            ))
166        }
167    }
168}
169
170impl Drop for TapBackend {
171    fn drop(&mut self) {
172        if self.fd >= 0 {
173            // SAFETY: closing an fd we exclusively own.
174            unsafe { libc::close(self.fd) };
175        }
176    }
177}
178
179impl NetBackend for TapBackend {
180    fn send(&mut self, packet: &NetPacket) -> std::io::Result<usize> {
181        // SAFETY: write reads packet.data.len() bytes from a borrowed slice.
182        let ret = unsafe {
183            libc::write(
184                self.fd,
185                packet.data.as_ptr() as *const libc::c_void,
186                packet.data.len(),
187            )
188        };
189
190        if ret < 0 {
191            Err(std::io::Error::last_os_error())
192        } else {
193            Ok(ret as usize)
194        }
195    }
196
197    fn recv(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
198        // SAFETY: read writes at most buf.len() bytes into a borrowed mut slice.
199        let ret = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
200
201        if ret < 0 {
202            let err = std::io::Error::last_os_error();
203            if err.kind() == std::io::ErrorKind::WouldBlock {
204                Ok(0)
205            } else {
206                Err(err)
207            }
208        } else {
209            Ok(ret as usize)
210        }
211    }
212
213    fn has_data(&self) -> bool {
214        let mut pollfd = libc::pollfd {
215            fd: self.fd,
216            events: libc::POLLIN,
217            revents: 0,
218        };
219
220        // SAFETY: poll borrows our pollfd for the duration of the call (timeout 0).
221        let ret = unsafe { libc::poll(&mut pollfd, 1, 0) };
222        ret > 0 && (pollfd.revents & libc::POLLIN) != 0
223    }
224
225    fn configure_offload(&mut self, flags: NetOffloadFlags) -> std::io::Result<()> {
226        let mut tun_flags: u32 = 0;
227        if flags.csum {
228            tun_flags |= TUN_F_CSUM;
229        }
230        if flags.tso4 {
231            tun_flags |= TUN_F_TSO4;
232        }
233        if flags.tso6 {
234            tun_flags |= TUN_F_TSO6;
235        }
236        if flags.tso_ecn {
237            tun_flags |= TUN_F_TSO_ECN;
238        }
239        if flags.ufo {
240            tun_flags |= TUN_F_UFO;
241        }
242
243        // SAFETY: TUNSETOFFLOAD reads `tun_flags` as an unsigned int. The fd
244        // is exclusively owned by `self`.
245        let ret = unsafe { libc::ioctl(self.fd, TUNSETOFFLOAD, tun_flags) };
246        if ret < 0 {
247            return Err(std::io::Error::last_os_error());
248        }
249        tracing::debug!(
250            "TAP {}: configured offload flags=0x{:x} (csum={} tso4={} tso6={} tso_ecn={} ufo={})",
251            self.name,
252            tun_flags,
253            flags.csum,
254            flags.tso4,
255            flags.tso6,
256            flags.tso_ecn,
257            flags.ufo,
258        );
259        Ok(())
260    }
261
262    fn set_vnet_hdr_sz(&mut self, size: u32) -> std::io::Result<()> {
263        let sz: libc::c_int = size as libc::c_int;
264        // SAFETY: TUNSETVNETHDRSZ reads `sz` as an int. The fd is exclusively owned.
265        let ret = unsafe { libc::ioctl(self.fd, TUNSETVNETHDRSZ, &sz) };
266        if ret < 0 {
267            return Err(std::io::Error::last_os_error());
268        }
269        tracing::debug!("TAP {}: set vnet_hdr_sz = {}", self.name, size);
270        Ok(())
271    }
272}