vmm/devices/virtio/net/
tap.rs1use std::fmt::{self, Debug};
9use std::fs::File;
10use std::io::Error as IoError;
11use std::os::raw::*;
12use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
13
14use vmm_sys_util::ioctl::{ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val};
15use vmm_sys_util::ioctl_iow_nr;
16
17use crate::devices::virtio::iovec::IoVecBuffer;
18use crate::devices::virtio::net::generated;
19
20const IFACE_NAME_MAX_LEN: usize = 16;
23
24#[rustfmt::skip]
26#[derive(Debug, thiserror::Error, displaydoc::Display)]
27pub enum TapError {
28 OpenTun(IoError),
30 InvalidIfname,
32 IfreqExecuteError(IoError, String),
34 SetOffloadFlags(IoError),
36 SetSizeOfVnetHdr(IoError),
38}
39
40const TUNTAP: ::std::os::raw::c_uint = 84;
41ioctl_iow_nr!(TUNSETIFF, TUNTAP, 202, ::std::os::raw::c_int);
42ioctl_iow_nr!(TUNSETOFFLOAD, TUNTAP, 208, ::std::os::raw::c_uint);
43ioctl_iow_nr!(TUNSETVNETHDRSZ, TUNTAP, 216, ::std::os::raw::c_int);
44
45#[derive(Debug)]
51pub struct Tap {
52 tap_file: File,
53 pub(crate) if_name: [u8; IFACE_NAME_MAX_LEN],
54}
55
56fn build_terminated_if_name(if_name: &str) -> Result<[u8; IFACE_NAME_MAX_LEN], TapError> {
59 let if_name = if_name.as_bytes();
62
63 if if_name.len() >= IFACE_NAME_MAX_LEN {
64 return Err(TapError::InvalidIfname);
65 }
66
67 let mut terminated_if_name = [b'\0'; IFACE_NAME_MAX_LEN];
68 terminated_if_name[..if_name.len()].copy_from_slice(if_name);
69
70 Ok(terminated_if_name)
71}
72
73#[derive(Copy, Clone)]
74pub struct IfReqBuilder(generated::ifreq);
75
76impl fmt::Debug for IfReqBuilder {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 write!(f, "IfReqBuilder {{ .. }}")
79 }
80}
81
82impl IfReqBuilder {
83 pub fn new() -> Self {
84 Self(Default::default())
85 }
86
87 pub fn if_name(mut self, if_name: &[u8; IFACE_NAME_MAX_LEN]) -> Self {
88 let ifrn_name = unsafe { self.0.ifr_ifrn.ifrn_name.as_mut() };
91 ifrn_name.copy_from_slice(if_name.as_ref());
92
93 self
94 }
95
96 pub(crate) fn flags(mut self, flags: i16) -> Self {
97 self.0.ifr_ifru.ifru_flags = flags;
98 self
99 }
100
101 pub(crate) fn execute<F: AsRawFd + Debug>(
102 mut self,
103 socket: &F,
104 ioctl: u64,
105 ) -> std::io::Result<generated::ifreq> {
106 if unsafe { ioctl_with_mut_ref(socket, ioctl, &mut self.0) } < 0 {
108 return Err(IoError::last_os_error());
109 }
110
111 Ok(self.0)
112 }
113}
114
115impl Tap {
116 pub fn open_named(if_name: &str) -> Result<Tap, TapError> {
121 let fd = unsafe {
124 libc::open(
125 c"/dev/net/tun".as_ptr(),
126 libc::O_RDWR | libc::O_NONBLOCK | libc::O_CLOEXEC,
127 )
128 };
129 if fd < 0 {
130 return Err(TapError::OpenTun(IoError::last_os_error()));
131 }
132
133 let tuntap = unsafe { File::from_raw_fd(fd) };
135
136 let terminated_if_name = build_terminated_if_name(if_name)?;
137 let ifreq = IfReqBuilder::new()
138 .if_name(&terminated_if_name)
139 .flags(
140 i16::try_from(generated::IFF_TAP | generated::IFF_NO_PI | generated::IFF_VNET_HDR)
141 .unwrap(),
142 )
143 .execute(&tuntap, TUNSETIFF())
144 .map_err(|io_error| TapError::IfreqExecuteError(io_error, if_name.to_owned()))?;
145
146 Ok(Tap {
147 tap_file: tuntap,
148 if_name: unsafe { ifreq.ifr_ifrn.ifrn_name },
150 })
151 }
152
153 pub fn if_name_as_str(&self) -> &str {
155 let len = self
156 .if_name
157 .iter()
158 .position(|x| *x == 0)
159 .unwrap_or(IFACE_NAME_MAX_LEN);
160 std::str::from_utf8(&self.if_name[..len]).unwrap_or("")
161 }
162
163 pub fn set_offload(&self, flags: c_uint) -> Result<(), TapError> {
165 if unsafe { ioctl_with_val(&self.tap_file, TUNSETOFFLOAD(), c_ulong::from(flags)) } < 0 {
167 return Err(TapError::SetOffloadFlags(IoError::last_os_error()));
168 }
169
170 Ok(())
171 }
172
173 pub fn set_vnet_hdr_size(&self, size: c_int) -> Result<(), TapError> {
175 if unsafe { ioctl_with_ref(&self.tap_file, TUNSETVNETHDRSZ(), &size) } < 0 {
177 return Err(TapError::SetSizeOfVnetHdr(IoError::last_os_error()));
178 }
179
180 Ok(())
181 }
182
183 pub(crate) fn write_iovec(&mut self, buffer: &IoVecBuffer) -> Result<usize, IoError> {
185 let iovcnt = i32::try_from(buffer.iovec_count()).unwrap();
186 let iov = buffer.as_iovec_ptr();
187
188 let ret = unsafe { libc::writev(self.tap_file.as_raw_fd(), iov, iovcnt) };
191 if ret == -1 {
192 return Err(IoError::last_os_error());
193 }
194 Ok(usize::try_from(ret).unwrap())
195 }
196
197 pub(crate) fn read_iovec(&mut self, buffer: &mut [libc::iovec]) -> Result<usize, IoError> {
199 let iov = buffer.as_mut_ptr();
200 let iovcnt = buffer.len().try_into().unwrap();
201
202 let ret = unsafe { libc::readv(self.tap_file.as_raw_fd(), iov, iovcnt) };
205 if ret == -1 {
206 return Err(IoError::last_os_error());
207 }
208 Ok(usize::try_from(ret).unwrap())
209 }
210}
211
212impl AsRawFd for Tap {
213 fn as_raw_fd(&self) -> RawFd {
214 self.tap_file.as_raw_fd()
215 }
216}
217
218#[cfg(test)]
219pub mod tests {
220 #![allow(clippy::undocumented_unsafe_blocks)]
221
222 use std::os::unix::ffi::OsStrExt;
223
224 use super::*;
225 use crate::devices::virtio::net::generated;
226 use crate::devices::virtio::net::test_utils::{TapTrafficSimulator, enable, if_index};
227
228 type IoVecBufferMut = crate::devices::virtio::iovec::IoVecBufferMut<256>;
231
232 const VNET_HDR_SIZE: usize = 10;
234
235 const PAYLOAD_SIZE: usize = 512;
236
237 #[test]
238 fn test_tap_name() {
239 assert_eq!(IFACE_NAME_MAX_LEN, unsafe {
241 generated::ifreq__bindgen_ty_1::default().ifrn_name.len()
242 });
243
244 let tap = Tap::open_named("").unwrap();
246 assert_eq!(b"tap0\0\0\0\0\0\0\0\0\0\0\0\0", &tap.if_name);
247 assert_eq!("tap0", tap.if_name_as_str());
248
249 let tap = Tap::open_named("tap%d").unwrap();
252 assert_ne!(b"tap%d", &tap.if_name[..5]);
255
256 let name = "a123456789abcdef";
258 match Tap::open_named(name) {
259 Err(TapError::InvalidIfname) => (),
260 _ => panic!("Expected Error::InvalidIfname"),
261 };
262
263 let name = "a123456789abcde";
265 let tap = Tap::open_named(name).unwrap();
266 assert_eq!(&format!("{}\0", name).as_bytes(), &tap.if_name);
267 assert_eq!(name, tap.if_name_as_str());
268 }
269
270 #[test]
271 fn test_tap_exclusive_open() {
272 let _tap1 = Tap::open_named("exclusivetap").unwrap();
273 Tap::open_named("exclusivetap").unwrap_err();
275 }
276
277 #[test]
278 fn test_set_options() {
279 let tap = Tap::open_named("").unwrap();
281 tap.set_vnet_hdr_size(16).unwrap();
282 tap.set_offload(0).unwrap();
283 }
284
285 #[test]
286 fn test_raw_fd() {
287 let tap = Tap::open_named("").unwrap();
288 assert_eq!(tap.as_raw_fd(), tap.tap_file.as_raw_fd());
289 }
290
291 #[test]
292 fn test_write_iovec() {
293 let mut tap = Tap::open_named("").unwrap();
294 enable(&tap);
295 let tap_traffic_simulator = TapTrafficSimulator::new(if_index(&tap));
296
297 let mut fragment1 = vmm_sys_util::rand::rand_bytes(PAYLOAD_SIZE);
298 fragment1.as_mut_slice()[..generated::ETH_HLEN as usize]
299 .copy_from_slice(&[0; generated::ETH_HLEN as usize]);
300 let fragment2 = vmm_sys_util::rand::rand_bytes(PAYLOAD_SIZE);
301 let fragment3 = vmm_sys_util::rand::rand_bytes(PAYLOAD_SIZE);
302
303 let scattered = IoVecBuffer::from(vec![
304 fragment1.as_slice(),
305 fragment2.as_slice(),
306 fragment3.as_slice(),
307 ]);
308
309 let num_bytes = tap.write_iovec(&scattered).unwrap();
310 assert_eq!(num_bytes, scattered.len() as usize);
311
312 let mut read_buf = vec![0u8; scattered.len() as usize];
313 assert!(tap_traffic_simulator.pop_rx_packet(&mut read_buf));
314 assert_eq!(
315 &read_buf[..PAYLOAD_SIZE - VNET_HDR_SIZE],
316 &fragment1[VNET_HDR_SIZE..]
317 );
318 assert_eq!(
319 &read_buf[PAYLOAD_SIZE - VNET_HDR_SIZE..2 * PAYLOAD_SIZE - VNET_HDR_SIZE],
320 fragment2
321 );
322 assert_eq!(
323 &read_buf[2 * PAYLOAD_SIZE - VNET_HDR_SIZE..3 * PAYLOAD_SIZE - VNET_HDR_SIZE],
324 fragment3
325 );
326 }
327
328 #[test]
329 fn test_read_iovec() {
330 let mut tap = Tap::open_named("").unwrap();
331 enable(&tap);
332 let tap_traffic_simulator = TapTrafficSimulator::new(if_index(&tap));
333
334 let mut buff1 = vec![0; PAYLOAD_SIZE + VNET_HDR_SIZE];
335 let mut buff2 = vec![0; 2 * PAYLOAD_SIZE];
336
337 let mut rx_buffers = IoVecBufferMut::from(vec![buff1.as_mut_slice(), buff2.as_mut_slice()]);
338
339 let packet = vmm_sys_util::rand::rand_alphanumerics(2 * PAYLOAD_SIZE);
340 tap_traffic_simulator.push_tx_packet(packet.as_bytes());
341 assert_eq!(
342 tap.read_iovec(rx_buffers.as_iovec_mut_slice()).unwrap(),
343 2 * PAYLOAD_SIZE + VNET_HDR_SIZE
344 );
345 assert_eq!(&buff1[VNET_HDR_SIZE..], &packet.as_bytes()[..PAYLOAD_SIZE]);
346 assert_eq!(&buff2[..PAYLOAD_SIZE], &packet.as_bytes()[PAYLOAD_SIZE..]);
347 assert_eq!(&buff2[PAYLOAD_SIZE..], &vec![0; PAYLOAD_SIZE])
348 }
349}