Skip to main content

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 crate::VirtioNetHdr;
10use futures::AsyncRead;
11use linux_net_bindings::gen_if;
12use linux_net_bindings::gen_if_tun;
13use linux_net_bindings::tun_get_iff;
14use linux_net_bindings::tun_get_vnet_hdr_sz;
15use linux_net_bindings::tun_set_iff;
16use linux_net_bindings::tun_set_offload;
17use linux_net_bindings::tun_set_vnet_hdr_sz;
18use pal_async::driver::Driver;
19use pal_async::pipe::PolledPipe;
20use std::ffi::CString;
21use std::fs::File;
22use std::io;
23use std::io::Write;
24use std::os::fd::OwnedFd;
25use std::os::raw::c_short;
26use std::os::unix::prelude::AsRawFd;
27use std::pin::Pin;
28use std::task::Context;
29use std::task::Poll;
30use thiserror::Error;
31
32#[derive(Error, Debug)]
33pub enum Error {
34    #[error("TAP interface name is too long: {0:#}")]
35    TapNameTooLong(usize),
36    #[error("failed to open /dev/net/tun")]
37    OpenTunFailed(#[source] io::Error),
38    #[error("TUNSETIFF ioctl failed")]
39    SetTapAttributes(#[source] io::Error),
40    #[error("TUNGETIFF ioctl failed")]
41    GetTapAttributes(#[source] io::Error),
42    #[error("TUNGETVNETHDRSZ ioctl failed")]
43    GetVnetHdrSize(#[source] io::Error),
44    #[error("TUNSETVNETHDRSZ ioctl failed")]
45    SetVnetHdrSize(#[source] io::Error),
46    #[error("TUNSETOFFLOAD ioctl failed")]
47    SetOffload(#[source] io::Error),
48    #[error("TAP name conversion to C string failed")]
49    TapNameConversion(#[source] std::ffi::NulError),
50    #[error("TAP interface does not have IFF_VNET_HDR set")]
51    NoVnetHdr,
52    #[error("TAP interface has unexpected vnet header size {actual}, expected {expected}")]
53    WrongVnetHdrSize { expected: usize, actual: usize },
54}
55
56/// Opens a TAP interface by name and returns the fd.
57///
58/// The fd is configured with `IFF_TAP | IFF_NO_PI | IFF_VNET_HDR`.
59pub fn open_tap(name: &str) -> Result<OwnedFd, Error> {
60    let tap_file = std::fs::OpenOptions::new()
61        .read(true)
62        .write(true)
63        .open("/dev/net/tun")
64        .map_err(Error::OpenTunFailed)?;
65
66    let mut ifreq: gen_if::ifreq = Default::default();
67
68    let tap_name_cstr = CString::new(name.as_bytes()).map_err(Error::TapNameConversion)?;
69    let tap_name_bytes = tap_name_cstr.into_bytes_with_nul();
70    let tap_name_length = tap_name_bytes.len();
71
72    // SAFETY: the ifr_ifrn union has a single member, and using
73    // ifr_ifrn is consistent with issuing the TUNSETIFF ioctl below.
74    let name_slice = unsafe { ifreq.ifr_ifrn.ifrn_name.as_mut() };
75
76    if name_slice.len() < tap_name_length {
77        return Err(Error::TapNameTooLong(tap_name_length));
78    }
79
80    for i in 0..tap_name_length {
81        name_slice[i] = tap_name_bytes[i] as libc::c_char;
82    }
83    ifreq.ifr_ifru.ifru_flags =
84        (gen_if_tun::IFF_TAP | gen_if_tun::IFF_NO_PI | gen_if_tun::IFF_VNET_HDR) as c_short;
85
86    // SAFETY: calling the ioctl according to implementation requirements.
87    unsafe {
88        tun_set_iff(tap_file.as_raw_fd(), &ifreq)
89            .map_err(|_e| Error::SetTapAttributes(io::Error::last_os_error()))?;
90    };
91
92    let fd = OwnedFd::from(tap_file);
93    Ok(fd)
94}
95
96/// Structure corresponding to a TAP interface.
97///
98/// Wraps a validated TAP fd with `IFF_VNET_HDR` and the correct vnet header
99/// size. Offloads are configured by
100/// [`TapEndpoint::new`](super::TapEndpoint::new) via [`Tap::set_offloads`].
101#[derive(Debug)]
102pub struct Tap {
103    tap: File,
104}
105
106impl Tap {
107    /// Wraps an already-open TAP fd and validates it.
108    ///
109    /// The fd must already have `TUNSETIFF` applied with `IFF_VNET_HDR`.
110    /// This function will:
111    /// - Query the fd with `TUNGETIFF` to verify `IFF_VNET_HDR` is set
112    /// - Set the vnet header size to the 12-byte v1 format
113    pub fn new(fd: OwnedFd) -> Result<Self, Error> {
114        let tap: File = fd.into();
115
116        // Verify IFF_VNET_HDR is set.
117        let mut ifreq: gen_if::ifreq = Default::default();
118        // SAFETY: calling the ioctl with a valid fd and zeroed ifreq.
119        unsafe {
120            tun_get_iff(tap.as_raw_fd(), &mut ifreq)
121                .map_err(|_e| Error::GetTapAttributes(io::Error::last_os_error()))?;
122        };
123        // SAFETY: the ifr_ifru union was populated by the TUNGETIFF ioctl,
124        // which writes the interface flags into ifru_flags.
125        if unsafe { ifreq.ifr_ifru.ifru_flags } as u32 & gen_if_tun::IFF_VNET_HDR == 0 {
126            return Err(Error::NoVnetHdr);
127        }
128
129        // Set the vnet header size to the 12-byte v1 format.
130        let expected_sz = size_of::<VirtioNetHdr>() as std::os::raw::c_int;
131        // SAFETY: calling the ioctl with a valid fd and correct argument type.
132        unsafe {
133            tun_set_vnet_hdr_sz(tap.as_raw_fd(), &expected_sz)
134                .map_err(|_e| Error::SetVnetHdrSize(io::Error::last_os_error()))?;
135        };
136
137        // Verify the header size was applied.
138        let mut actual_sz: std::os::raw::c_int = 0;
139        // SAFETY: calling the ioctl with a valid fd and correct argument type.
140        unsafe {
141            tun_get_vnet_hdr_sz(tap.as_raw_fd(), &mut actual_sz)
142                .map_err(|_e| Error::GetVnetHdrSize(io::Error::last_os_error()))?;
143        };
144        if actual_sz != expected_sz {
145            return Err(Error::WrongVnetHdrSize {
146                expected: expected_sz as usize,
147                actual: actual_sz as usize,
148            });
149        }
150
151        Ok(Self { tap })
152    }
153
154    /// Sets TX offload flags via `TUNSETOFFLOAD`.
155    ///
156    /// `flags` is a bitmask of `TUN_F_*` constants (e.g., `TUN_F_CSUM | TUN_F_TSO4`).
157    pub fn set_offloads(&self, flags: u32) -> Result<(), Error> {
158        // SAFETY: calling the ioctl with a valid fd and correct argument type.
159        unsafe {
160            tun_set_offload(self.tap.as_raw_fd(), flags as std::os::raw::c_int)
161                .map_err(|_e| Error::SetOffload(io::Error::last_os_error()))?;
162        };
163        Ok(())
164    }
165
166    pub fn polled(self, driver: &(impl Driver + ?Sized)) -> io::Result<PolledTap> {
167        Ok(PolledTap {
168            tap: PolledPipe::new(driver, self.tap)?,
169        })
170    }
171}
172
173/// A version of [`Tap`] that implements [`AsyncRead`].
174pub struct PolledTap {
175    tap: PolledPipe,
176}
177
178impl PolledTap {
179    pub fn into_inner(self) -> Tap {
180        Tap {
181            tap: self.tap.into_inner(),
182        }
183    }
184}
185
186impl AsyncRead for PolledTap {
187    fn poll_read(
188        mut self: Pin<&mut Self>,
189        cx: &mut Context<'_>,
190        buf: &mut [u8],
191    ) -> Poll<io::Result<usize>> {
192        Pin::new(&mut self.tap).poll_read(cx, buf)
193    }
194}
195
196impl Write for PolledTap {
197    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
198        // N.B. This will be a non-blocking write because `PolledPipe::new` puts
199        // the file into nonblocking mode.
200        self.tap.get().write(buf)
201    }
202
203    fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
204        self.tap.get().write_vectored(bufs)
205    }
206
207    fn flush(&mut self) -> io::Result<()> {
208        Ok(())
209    }
210}