net_tap/
tap.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! A structure corresponding to a TAP interface.
5
6// UNSAFETY: Interacting with a union in bindgen-generated code and calling an ioctl.
7#![expect(unsafe_code)]
8
9use futures::AsyncRead;
10use linux_net_bindings::gen_if;
11use linux_net_bindings::gen_if_tun;
12use linux_net_bindings::tun_set_iff;
13use pal_async::driver::Driver;
14use pal_async::pipe::PolledPipe;
15use std::ffi::CString;
16use std::fs::File;
17use std::io;
18use std::io::Write;
19use std::os::raw::c_short;
20use std::os::unix::prelude::AsRawFd;
21use std::pin::Pin;
22use std::task::Context;
23use std::task::Poll;
24use thiserror::Error;
25
26#[derive(Error, Debug)]
27pub enum Error {
28    #[error("TAP interface name is too long: {0:#}")]
29    TapNameTooLong(usize),
30    #[error("failed to open /dev/net/tun")]
31    OpenTunFailed(#[source] io::Error),
32    #[error("TUNSETIFF ioctl failed")]
33    SetTapAttributes(#[source] io::Error),
34    #[error("TAP name conversion to C string failed")]
35    TapNameConversion(#[source] std::ffi::NulError),
36}
37
38/// Structure corresponding to a TAP interface.
39#[derive(Debug)]
40pub struct Tap {
41    tap: File,
42}
43
44impl Tap {
45    pub fn new(name: &str) -> Result<Self, Error> {
46        let tap = Self::open_tap_interface(name)?;
47        Ok(Self { tap })
48    }
49
50    fn open_tap_interface(tap_name: &str) -> Result<File, Error> {
51        // Open the TUN/TAP interface.
52        //
53        // - Packets received from this TAP interface (i.e., fom host's network)
54        //  will be drained using a nonblocking read() loop, then will be sent to
55        //  the guest.
56        //
57        // - A best effort will be made to send to host's network the packets
58        //  received from the guest, by using a nonblocking write() loop on this
59        //  TAP interface.
60        //
61        // RX and/or TX packets might get lost when queues are full, as they get
62        // lost sometimes on a physical NIC.
63        let tap_file = std::fs::OpenOptions::new()
64            .read(true)
65            .write(true)
66            .open("/dev/net/tun")
67            .map_err(Error::OpenTunFailed)?;
68
69        // Set TAP interface attributes.
70        let mut ifreq: gen_if::ifreq = Default::default();
71
72        let tap_name_cstr = CString::new(tap_name.as_bytes()).map_err(Error::TapNameConversion)?;
73        let tap_name_bytes = tap_name_cstr.into_bytes_with_nul();
74        let tap_name_length = tap_name_bytes.len();
75
76        // SAFETY: the ifr_ifrn union has a single member, and using
77        // ifr_ifrn is consistent with issuing the TUNSETIFF ioctl below.
78        let name_slice = unsafe { ifreq.ifr_ifrn.ifrn_name.as_mut() };
79
80        if name_slice.len() < tap_name_length {
81            Err(Error::TapNameTooLong(tap_name_length))
82        } else {
83            for i in 0..tap_name_length {
84                name_slice[i] = tap_name_bytes[i] as libc::c_char;
85            }
86            ifreq.ifr_ifru.ifru_flags = (gen_if_tun::IFF_TAP | gen_if_tun::IFF_NO_PI) as c_short;
87
88            // SAFETY: calling the ioctl according to implementation requirements.
89            unsafe {
90                tun_set_iff(tap_file.as_raw_fd(), &ifreq)
91                    .map_err(|_e| Error::SetTapAttributes(io::Error::last_os_error()))?;
92            };
93            Ok(tap_file)
94        }
95    }
96
97    pub fn polled(self, driver: &(impl Driver + ?Sized)) -> io::Result<PolledTap> {
98        Ok(PolledTap {
99            tap: PolledPipe::new(driver, self.tap)?,
100        })
101    }
102}
103
104/// A version of [`Tap`] that implements [`AsyncRead`].
105pub struct PolledTap {
106    tap: PolledPipe,
107}
108
109impl PolledTap {
110    pub fn into_inner(self) -> Tap {
111        Tap {
112            tap: self.tap.into_inner(),
113        }
114    }
115}
116
117impl AsyncRead for PolledTap {
118    fn poll_read(
119        mut self: Pin<&mut Self>,
120        cx: &mut Context<'_>,
121        buf: &mut [u8],
122    ) -> Poll<io::Result<usize>> {
123        Pin::new(&mut self.tap).poll_read(cx, buf)
124    }
125}
126
127impl Write for PolledTap {
128    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
129        // N.B. This will be a non-blocking write because `PolledPipe::new` puts
130        // the file into nonblocking mode.
131        self.tap.get().write(buf)
132    }
133
134    fn flush(&mut self) -> io::Result<()> {
135        Ok(())
136    }
137}