vmm/devices/virtio/net/
tap.rs

1// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
5// Use of this source code is governed by a BSD-style license that can be
6// found in the THIRD-PARTY file.
7
8use 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
20// As defined in the Linux UAPI:
21// https://elixir.bootlin.com/linux/v4.17/source/include/uapi/linux/if.h#L33
22const IFACE_NAME_MAX_LEN: usize = 16;
23
24/// List of errors the tap implementation can throw.
25#[rustfmt::skip]
26#[derive(Debug, thiserror::Error, displaydoc::Display)]
27pub enum TapError {
28    /// Couldn't open /dev/net/tun: {0}
29    OpenTun(IoError),
30    /// Invalid interface name
31    InvalidIfname,
32    /// Error while creating ifreq structure: {0}. Invalid TUN/TAP Backend provided by {1}. Check our documentation on setting up the network devices.
33    IfreqExecuteError(IoError, String),
34    /// Error while setting the offload flags: {0}
35    SetOffloadFlags(IoError),
36    /// Error while setting size of the vnet header: {0}
37    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/// Handle for a network tap interface.
46///
47/// For now, this simply wraps the file descriptor for the tap device so methods
48/// can run ioctls on the interface. The tap interface fd will be closed when
49/// Tap goes out of scope, and the kernel will clean up the interface automatically.
50#[derive(Debug)]
51pub struct Tap {
52    tap_file: File,
53    pub(crate) if_name: [u8; IFACE_NAME_MAX_LEN],
54}
55
56// Returns a byte vector representing the contents of a null terminated C string which
57// contains if_name.
58fn build_terminated_if_name(if_name: &str) -> Result<[u8; IFACE_NAME_MAX_LEN], TapError> {
59    // Convert the string slice to bytes, and shadow the variable,
60    // since we no longer need the &str version.
61    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        // SAFETY: Since we don't call as_mut on the same union field more than once, this block is
89        // safe.
90        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        // SAFETY: ioctl is safe. Called with a valid socket fd, and we check the return.
107        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    /// Create a TUN/TAP device given the interface name.
117    /// # Arguments
118    ///
119    /// * `if_name` - the name of the interface.
120    pub fn open_named(if_name: &str) -> Result<Tap, TapError> {
121        // SAFETY: Open calls are safe because we give a constant null-terminated
122        // string and verify the result.
123        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        // SAFETY: We just checked that the fd is valid.
134        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            // SAFETY: Safe since only the name is accessed, and it's cloned out.
149            if_name: unsafe { ifreq.ifr_ifrn.ifrn_name },
150        })
151    }
152
153    /// Retrieve the interface's name as a str.
154    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    /// Set the offload flags for the tap interface.
164    pub fn set_offload(&self, flags: c_uint) -> Result<(), TapError> {
165        // SAFETY: ioctl is safe. Called with a valid tap fd, and we check the return.
166        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    /// Set the size of the vnet hdr.
174    pub fn set_vnet_hdr_size(&self, size: c_int) -> Result<(), TapError> {
175        // SAFETY: ioctl is safe. Called with a valid tap fd, and we check the return.
176        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    /// Write an `IoVecBuffer` to tap
184    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        // SAFETY: `writev` is safe. Called with a valid tap fd, the iovec pointer and length
189        // is provide by the `IoVecBuffer` implementation and we check the return value.
190        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    /// Read from tap to an `IoVecBufferMut`
198    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        // SAFETY: `readv` is safe. Called with a valid tap fd, the iovec pointer and length
203        // is provide by the `IoVecBufferMut` implementation and we check the return value.
204        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    // Redefine `IoVecBufferMut` with specific length. Otherwise
229    // Rust will not know what to do.
230    type IoVecBufferMut = crate::devices::virtio::iovec::IoVecBufferMut<256>;
231
232    // The size of the virtio net header
233    const VNET_HDR_SIZE: usize = 10;
234
235    const PAYLOAD_SIZE: usize = 512;
236
237    #[test]
238    fn test_tap_name() {
239        // Sanity check that the assumed max iface name length is correct.
240        assert_eq!(IFACE_NAME_MAX_LEN, unsafe {
241            generated::ifreq__bindgen_ty_1::default().ifrn_name.len()
242        });
243
244        // Empty name - The tap should be named "tap0" by default
245        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        // Test using '%d' to have the kernel assign an unused name,
250        // and that we correctly copy back that generated name
251        let tap = Tap::open_named("tap%d").unwrap();
252        // '%d' should be replaced with _some_ number, although we don't know what was the next
253        // available one. Just assert that '%d' definitely isn't there anymore.
254        assert_ne!(b"tap%d", &tap.if_name[..5]);
255
256        // 16 characters - too long.
257        let name = "a123456789abcdef";
258        match Tap::open_named(name) {
259            Err(TapError::InvalidIfname) => (),
260            _ => panic!("Expected Error::InvalidIfname"),
261        };
262
263        // 15 characters - OK.
264        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        // Opening same tap device a second time should not be permitted.
274        Tap::open_named("exclusivetap").unwrap_err();
275    }
276
277    #[test]
278    fn test_set_options() {
279        // This line will fail to provide an initialized FD if the test is not run as root.
280        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}