mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
Import the std modules used across the crate instead of spelling the full paths at every use site. Signed-off-by: Henry Hrvoje Tonkovac <htonkovac@gmail.com> Assisted-by: Claude:Opus-4.8
891 lines
32 KiB
Rust
891 lines
32 KiB
Rust
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the THIRD-PARTY file.
|
|
|
|
use std::ffi::{CStr, CString};
|
|
use std::fs::File;
|
|
use std::io::{Error as IoError, Read, Result as IoResult, Write};
|
|
use std::net::{IpAddr, Ipv6Addr};
|
|
use std::os::raw::*;
|
|
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
|
|
use std::{mem, result, str};
|
|
|
|
use libc::{__c_anonymous_ifr_ifru, IFNAMSIZ, ifreq};
|
|
use thiserror::Error;
|
|
use vmm_sys_util::ioctl::{ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val};
|
|
|
|
use super::{
|
|
Error as NetUtilError, MacAddr, create_inet_socket, create_sockaddr, create_unix_socket,
|
|
vnet_hdr_len,
|
|
};
|
|
use crate::mac::MAC_ADDR_LEN;
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum Error {
|
|
#[error("Couldn't open /dev/net/tun")]
|
|
OpenTun(#[source] IoError),
|
|
#[error("Unable to configure tap interface")]
|
|
ConfigureTap(#[source] IoError),
|
|
#[error("Unable to retrieve features")]
|
|
GetFeatures(#[source] IoError),
|
|
#[error("Missing multiqueue support in the kernel")]
|
|
MultiQueueKernelSupport,
|
|
#[error("ioctl ({0}) failed: {1}")]
|
|
IoctlError(c_ulong, #[source] IoError),
|
|
#[error("Failed to create a socket")]
|
|
NetUtil(#[source] NetUtilError),
|
|
#[error("Interface name too long (max length is {max_len}): {0}", max_len = IFNAMSIZ - 1)]
|
|
IfnameTooLong(String),
|
|
#[error("Interface name contains interior NUL byte: {0:?}")]
|
|
IfnameContainsNUL(String),
|
|
#[error("Invalid interface name (does it exist?): {0}")]
|
|
InvalidIfname(String),
|
|
#[error("Error parsing MAC data")]
|
|
MacParsing(#[source] IoError),
|
|
#[error("Invalid netmask")]
|
|
InvalidNetmask,
|
|
}
|
|
|
|
pub type Result<T> = result::Result<T, Error>;
|
|
|
|
/// Handle for a network tap interface.
|
|
///
|
|
/// For now, this simply wraps the file descriptor for the tap device so methods
|
|
/// can run ioctls on the interface. The tap interface fd will be closed when
|
|
/// Tap goes out of scope, and the kernel will clean up the interface
|
|
/// automatically.
|
|
#[derive(Debug)]
|
|
pub struct Tap {
|
|
tap_file: File,
|
|
/// The name does not exceed [`IFNAMSIZ`] bytes including the NUL byte.
|
|
if_name: CString,
|
|
}
|
|
|
|
impl PartialEq for Tap {
|
|
fn eq(&self, other: &Tap) -> bool {
|
|
self.if_name == other.if_name
|
|
}
|
|
}
|
|
|
|
impl Clone for Tap {
|
|
fn clone(&self) -> Self {
|
|
Tap {
|
|
tap_file: self.tap_file.try_clone().unwrap(),
|
|
if_name: self.if_name.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn ipv6_mask_to_prefix(mask: Ipv6Addr) -> Result<u8> {
|
|
let mask = mask.segments();
|
|
let mut iter = mask.iter();
|
|
|
|
let mut prefix = 0;
|
|
for &segment in &mut iter {
|
|
if segment == 0xffff {
|
|
prefix += 16;
|
|
} else if segment == 0 {
|
|
break;
|
|
} else {
|
|
let prefix_bits = segment.leading_ones() as u8;
|
|
if segment << prefix_bits != 0 {
|
|
return Err(Error::InvalidNetmask);
|
|
}
|
|
|
|
prefix += prefix_bits;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check that remaining bits are all unset
|
|
for &segment in iter {
|
|
if segment != 0 {
|
|
return Err(Error::InvalidNetmask);
|
|
}
|
|
}
|
|
|
|
Ok(prefix)
|
|
}
|
|
|
|
impl Tap {
|
|
/// # Safety
|
|
/// The caller should ensure to pass a valid file descriptor and valid
|
|
/// arguments for the `ioctl()` syscall.
|
|
unsafe fn ioctl_with_mut_ref<F: AsRawFd, T>(fd: &F, req: c_ulong, arg: &mut T) -> Result<()> {
|
|
// SAFETY: file descriptor is valid and return value is checked
|
|
let ret = unsafe { ioctl_with_mut_ref(fd, req, arg) };
|
|
if ret < 0 {
|
|
return Err(Error::IoctlError(req, IoError::last_os_error()));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// # Safety
|
|
/// The caller should ensure to pass a valid file descriptor and valid
|
|
/// arguments for the `ioctl()` syscall.
|
|
unsafe fn ioctl_with_ref<F: AsRawFd, T>(fd: &F, req: c_ulong, arg: &T) -> Result<()> {
|
|
// SAFETY: file descriptor is valid and return value is checked
|
|
let ret = unsafe { ioctl_with_ref(fd, req, arg) };
|
|
if ret < 0 {
|
|
return Err(Error::IoctlError(req, IoError::last_os_error()));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// # Safety
|
|
/// The caller should ensure to pass a valid file descriptor and valid
|
|
/// arguments for the `ioctl()` syscall.
|
|
unsafe fn ioctl_with_val<F: AsRawFd>(fd: &F, req: c_ulong, arg: c_ulong) -> Result<()> {
|
|
// SAFETY: file descriptor is valid and return value is checked
|
|
let ret = unsafe { ioctl_with_val(fd, req, arg) };
|
|
if ret < 0 {
|
|
return Err(Error::IoctlError(req, IoError::last_os_error()));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn open_named(if_name: &str, num_queue_pairs: usize, flags: Option<i32>) -> Result<Tap> {
|
|
if if_name.len() > IFNAMSIZ - 1 {
|
|
return Err(Error::IfnameTooLong(if_name.to_string()));
|
|
}
|
|
|
|
let terminated_if_name =
|
|
CString::new(if_name).map_err(|_| Error::IfnameContainsNUL(if_name.to_string()))?;
|
|
|
|
// SAFETY: FFI call
|
|
let fd = unsafe {
|
|
// Open calls are safe because we give a constant null-terminated
|
|
// string and verify the result.
|
|
libc::open(
|
|
c"/dev/net/tun".as_ptr().cast(),
|
|
flags.unwrap_or(libc::O_RDWR | libc::O_NONBLOCK | libc::O_CLOEXEC),
|
|
)
|
|
};
|
|
if fd < 0 {
|
|
return Err(Error::OpenTun(IoError::last_os_error()));
|
|
}
|
|
|
|
// SAFETY: We just checked that the fd is valid.
|
|
let tuntap = unsafe { File::from_raw_fd(fd) };
|
|
|
|
// Let's validate some features before going any further.
|
|
// ioctl is safe since we call it with a valid tap fd and check the return
|
|
// value.
|
|
let mut features = 0;
|
|
// SAFETY: IOCTL with correct arguments
|
|
let ret =
|
|
unsafe { ioctl_with_mut_ref(&tuntap, libc::TUNGETFEATURES as c_ulong, &mut features) };
|
|
if ret < 0 {
|
|
return Err(Error::GetFeatures(IoError::last_os_error()));
|
|
}
|
|
|
|
// Check if the user parameters match the kernel support for MQ
|
|
if (features & libc::IFF_MULTI_QUEUE == 0) && num_queue_pairs > 1 {
|
|
return Err(Error::MultiQueueKernelSupport);
|
|
}
|
|
|
|
let mut ifru_flags = (libc::IFF_TAP | libc::IFF_NO_PI | libc::IFF_VNET_HDR) as c_short;
|
|
if num_queue_pairs > 1 {
|
|
ifru_flags |= libc::IFF_MULTI_QUEUE as c_short;
|
|
}
|
|
|
|
let mut ifreq = libc::ifreq {
|
|
ifr_name: [0; IFNAMSIZ],
|
|
ifr_ifru: __c_anonymous_ifr_ifru { ifru_flags },
|
|
};
|
|
|
|
// Convert and copy bytes to `ifr_name` buffer.
|
|
// `terminated_if_name` will fit into `ifr_name` since we enforce the length limit
|
|
// above.
|
|
ifreq
|
|
.ifr_name
|
|
.iter_mut()
|
|
.zip(terminated_if_name.as_bytes_with_nul())
|
|
.for_each(|(ifr_name_char, terminated_if_name_byte)| {
|
|
*ifr_name_char = *terminated_if_name_byte as c_char;
|
|
});
|
|
|
|
// SAFETY: ioctl is safe since we call it with a valid tap fd and check the return
|
|
// value.
|
|
let ret = unsafe { ioctl_with_mut_ref(&tuntap, libc::TUNSETIFF as c_ulong, &mut ifreq) };
|
|
if ret < 0 {
|
|
return Err(Error::ConfigureTap(IoError::last_os_error()));
|
|
}
|
|
|
|
// SAFETY: `ifreq.ifr_name` is set by the `ioctl_with_mut_ref` call and we checked the
|
|
// return code, so the name must be a valid `CStr`.
|
|
let if_name = unsafe { CStr::from_ptr(ifreq.ifr_name.as_ptr()) }.to_owned();
|
|
Ok(Tap {
|
|
tap_file: tuntap,
|
|
if_name,
|
|
})
|
|
}
|
|
|
|
/// Create a new tap interface.
|
|
pub fn new(num_queue_pairs: usize) -> Result<Tap> {
|
|
Self::open_named("vmtap%d", num_queue_pairs, None)
|
|
}
|
|
|
|
pub fn from_tap_fd(fd: RawFd, num_queue_pairs: usize) -> Result<Tap> {
|
|
// Ensure that the file is opened non-blocking, this is particularly
|
|
// needed when opened via the shell for macvtap.
|
|
// SAFETY: FFI call
|
|
let ret = unsafe {
|
|
let mut flags = libc::fcntl(fd, libc::F_GETFL);
|
|
flags |= libc::O_NONBLOCK;
|
|
libc::fcntl(fd, libc::F_SETFL, flags)
|
|
};
|
|
if ret < 0 {
|
|
return Err(Error::ConfigureTap(IoError::last_os_error()));
|
|
}
|
|
|
|
// SAFETY: fd is a tap fd
|
|
let tap_file = unsafe { File::from_raw_fd(fd) };
|
|
let mut ifreq: libc::ifreq = ifreq {
|
|
ifr_name: [0; IFNAMSIZ],
|
|
ifr_ifru: __c_anonymous_ifr_ifru { ifru_flags: 0 },
|
|
};
|
|
|
|
// Get current config including name
|
|
// SAFETY: IOCTL with correct arguments
|
|
unsafe { Self::ioctl_with_mut_ref(&tap_file, libc::TUNGETIFF as c_ulong, &mut ifreq)? };
|
|
|
|
let if_name = {
|
|
let ifr_ptr = ifreq.ifr_name.as_ptr();
|
|
// SAFETY: The `ifr_name` field of the union is a valid, nul-terminated C string since it
|
|
// was just set by the ioctl call, and we checked for errors.
|
|
// We immediately convert the `CStr` to the owned `CString, so the memory of the union field
|
|
// is not accessed or mutated during the lifetime of the `Cstr`.
|
|
unsafe { CStr::from_ptr(ifr_ptr).to_owned() }
|
|
};
|
|
|
|
// Try and update flags. Depending on how the tap was created (macvtap
|
|
// or via open_named()) this might return -EEXIST so we just ignore that.
|
|
// SAFETY: access union fields
|
|
unsafe {
|
|
ifreq.ifr_ifru.ifru_flags =
|
|
(libc::IFF_TAP | libc::IFF_NO_PI | libc::IFF_VNET_HDR) as c_short;
|
|
if num_queue_pairs > 1 {
|
|
ifreq.ifr_ifru.ifru_flags |= libc::IFF_MULTI_QUEUE as c_short;
|
|
}
|
|
}
|
|
// SAFETY: IOCTL with correct arguments
|
|
let ret = unsafe { ioctl_with_mut_ref(&tap_file, libc::TUNSETIFF as c_ulong, &mut ifreq) };
|
|
if ret < 0 && IoError::last_os_error().raw_os_error().unwrap() != libc::EEXIST {
|
|
return Err(Error::ConfigureTap(IoError::last_os_error()));
|
|
}
|
|
|
|
let tap = Tap { tap_file, if_name };
|
|
let vnet_hdr_size = vnet_hdr_len() as i32;
|
|
tap.set_vnet_hdr_size(vnet_hdr_size)?;
|
|
|
|
Ok(tap)
|
|
}
|
|
|
|
/// Set the host-side IP address for the tap interface.
|
|
pub fn set_ip_addr(&self, ip_addr: IpAddr, netmask: Option<IpAddr>) -> Result<()> {
|
|
let sock = create_inet_socket(ip_addr).map_err(Error::NetUtil)?;
|
|
|
|
let mut ifreq = self.get_ifreq();
|
|
|
|
match ip_addr {
|
|
IpAddr::V4(addr) => {
|
|
let addr = create_sockaddr(addr);
|
|
|
|
ifreq.ifr_ifru.ifru_addr = addr;
|
|
|
|
// SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return.
|
|
unsafe {
|
|
Self::ioctl_with_ref(&sock, libc::SIOCSIFADDR as c_ulong, &ifreq)?;
|
|
}
|
|
|
|
if let Some(IpAddr::V4(mask)) = netmask {
|
|
ifreq.ifr_ifru.ifru_netmask = create_sockaddr(mask);
|
|
|
|
// SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return.
|
|
unsafe {
|
|
Self::ioctl_with_ref(&sock, libc::SIOCSIFNETMASK as c_ulong, &ifreq)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
IpAddr::V6(addr) => {
|
|
let ifindex = {
|
|
// SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return.
|
|
unsafe {
|
|
Self::ioctl_with_ref(&sock, libc::SIOCGIFINDEX as c_ulong, &ifreq)?;
|
|
}
|
|
|
|
// SAFETY: ifru_ivalue contains the ifindex and is set by the previous ioctl
|
|
unsafe {
|
|
match ifreq.ifr_ifru.ifru_ifindex {
|
|
0 => {
|
|
let name = self.if_name.to_string_lossy().to_string();
|
|
return Err(Error::InvalidIfname(name));
|
|
}
|
|
i => i,
|
|
}
|
|
}
|
|
};
|
|
|
|
let prefixlen = match netmask {
|
|
Some(IpAddr::V6(netmask)) => ipv6_mask_to_prefix(netmask)?,
|
|
Some(IpAddr::V4(_)) => return Err(Error::InvalidNetmask),
|
|
None => 0,
|
|
};
|
|
|
|
let ifreq = libc::in6_ifreq {
|
|
// SAFETY: addr can be safely transmuted to in6_addr
|
|
ifr6_addr: unsafe { mem::transmute::<[u8; 16], libc::in6_addr>(addr.octets()) },
|
|
ifr6_prefixlen: prefixlen as u32,
|
|
ifr6_ifindex: ifindex,
|
|
};
|
|
|
|
// SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return.
|
|
unsafe { Self::ioctl_with_ref(&sock, libc::SIOCSIFADDR as c_ulong, &ifreq) }
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Set mac addr for tap interface.
|
|
pub fn set_mac_addr(&self, addr: MacAddr) -> Result<()> {
|
|
// Checking if the mac address already matches the desired one
|
|
// is useful to avoid making the "set ioctl" in the case where
|
|
// the VMM is running without the privilege to do that.
|
|
// In practice this comes from a reboot after the configuration
|
|
// has been update with the kernel generated address.
|
|
if self.get_mac_addr()? == addr {
|
|
return Ok(());
|
|
}
|
|
|
|
let sock = create_unix_socket().map_err(Error::NetUtil)?;
|
|
|
|
let mut ifreq = self.get_ifreq();
|
|
|
|
// SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return.
|
|
unsafe { Self::ioctl_with_ref(&sock, libc::SIOCGIFHWADDR as c_ulong, &ifreq)? };
|
|
|
|
// SAFETY: We only access one field of the ifru union
|
|
unsafe {
|
|
let ifru_hwaddr = &mut ifreq.ifr_ifru.ifru_hwaddr;
|
|
for (i, v) in addr.get_bytes().iter().enumerate() {
|
|
ifru_hwaddr.sa_data[i] = *v as c_char;
|
|
}
|
|
}
|
|
|
|
// SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return.
|
|
unsafe { Self::ioctl_with_ref(&sock, libc::SIOCSIFHWADDR as c_ulong, &ifreq) }
|
|
}
|
|
|
|
/// Get mac addr for tap interface.
|
|
pub fn get_mac_addr(&self) -> Result<MacAddr> {
|
|
let sock = create_unix_socket().map_err(Error::NetUtil)?;
|
|
|
|
let ifreq = self.get_ifreq();
|
|
|
|
// SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return.
|
|
unsafe { Self::ioctl_with_ref(&sock, libc::SIOCGIFHWADDR as c_ulong, &ifreq)? };
|
|
|
|
let addr = {
|
|
let bytes: Vec<u8> =
|
|
// SAFETY: The `ioctl_with_ref` ensures accessing `ifru_hwaddr` is valid.
|
|
unsafe { ifreq.ifr_ifru.ifru_hwaddr.sa_data[0..MAC_ADDR_LEN].iter() }
|
|
.map(|byte| {
|
|
// On some architectures, `c_char` is already a `u8`.
|
|
#[allow(clippy::unnecessary_cast)]
|
|
{
|
|
*byte as u8
|
|
}
|
|
})
|
|
.collect();
|
|
MacAddr::from_bytes(&bytes).map_err(Error::MacParsing)?
|
|
};
|
|
Ok(addr)
|
|
}
|
|
|
|
#[cfg(not(fuzzing))]
|
|
pub fn mtu(&self) -> Result<i32> {
|
|
let sock = create_unix_socket().map_err(Error::NetUtil)?;
|
|
|
|
let ifreq = self.get_ifreq();
|
|
|
|
// SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return.
|
|
unsafe { Self::ioctl_with_ref(&sock, libc::SIOCGIFMTU as c_ulong, &ifreq)? };
|
|
|
|
// SAFETY: access a union field
|
|
let mtu = unsafe { ifreq.ifr_ifru.ifru_mtu };
|
|
|
|
Ok(mtu)
|
|
}
|
|
|
|
#[cfg(fuzzing)]
|
|
pub fn mtu(&self) -> Result<i32> {
|
|
// Consistent with the `virtio_devices::net::MIN_MTU`
|
|
Ok(1280)
|
|
}
|
|
|
|
pub fn set_mtu(&self, mtu: i32) -> Result<()> {
|
|
let sock = create_unix_socket().map_err(Error::NetUtil)?;
|
|
|
|
let mut ifreq = self.get_ifreq();
|
|
ifreq.ifr_ifru.ifru_mtu = mtu;
|
|
|
|
// SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return.
|
|
unsafe { Self::ioctl_with_ref(&sock, libc::SIOCSIFMTU as c_ulong, &ifreq) }
|
|
}
|
|
|
|
/// Set the offload flags for the tap interface.
|
|
pub fn set_offload(&self, flags: c_uint) -> Result<()> {
|
|
// SAFETY: ioctl is safe. Called with a valid tap fd, and we check the return.
|
|
unsafe {
|
|
Self::ioctl_with_val(
|
|
&self.tap_file,
|
|
libc::TUNSETOFFLOAD as c_ulong,
|
|
flags as c_ulong,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Enable the tap interface.
|
|
pub fn enable(&self) -> Result<()> {
|
|
let sock = create_unix_socket().map_err(Error::NetUtil)?;
|
|
|
|
let mut ifreq = self.get_ifreq();
|
|
|
|
// SAFETY: IOCTL with correct arguments
|
|
unsafe { Self::ioctl_with_ref(&sock, libc::SIOCGIFFLAGS as c_ulong, &ifreq)? };
|
|
|
|
// If TAP device is already up don't try and enable it
|
|
// SAFETY: access a union field
|
|
let ifru_flags = unsafe { ifreq.ifr_ifru.ifru_flags };
|
|
if ifru_flags & libc::IFF_UP as i16 == libc::IFF_UP as i16 {
|
|
return Ok(());
|
|
}
|
|
|
|
ifreq.ifr_ifru.ifru_flags = libc::IFF_UP as i16;
|
|
|
|
// SAFETY: ioctl is safe. Called with a valid sock fd, and we check the return.
|
|
unsafe { Self::ioctl_with_ref(&sock, libc::SIOCSIFFLAGS as c_ulong, &ifreq) }
|
|
}
|
|
|
|
/// Set the size of the vnet hdr.
|
|
pub fn set_vnet_hdr_size(&self, size: c_int) -> Result<()> {
|
|
// SAFETY: ioctl is safe. Called with a valid tap fd, and we check the return.
|
|
unsafe { Self::ioctl_with_ref(&self.tap_file, libc::TUNSETVNETHDRSZ as c_ulong, &size) }
|
|
}
|
|
|
|
fn get_ifreq(&self) -> libc::ifreq {
|
|
let mut ifreq: libc::ifreq = libc::ifreq {
|
|
ifr_name: [0; IFNAMSIZ],
|
|
ifr_ifru: __c_anonymous_ifr_ifru { ifru_flags: 0 },
|
|
};
|
|
|
|
// Convert and copy bytes to `ifr_name` buffer.
|
|
// `self.if_name` will fit into `ifr_name` since we enforce the length when setting it.
|
|
ifreq
|
|
.ifr_name
|
|
.iter_mut()
|
|
.zip(self.if_name.as_bytes_with_nul())
|
|
.for_each(|(ifr_name_char, terminated_if_name_byte)| {
|
|
*ifr_name_char = *terminated_if_name_byte as c_char;
|
|
});
|
|
|
|
ifreq
|
|
}
|
|
|
|
/// Returns the interface name as a string, truncated at the first NUL byte
|
|
/// if present.
|
|
///
|
|
/// # Panic
|
|
/// Panics if the interface name is not encoded as valid UTF-8. This can
|
|
/// only be caused by unrecoverable internal errors as users and management
|
|
/// software are only allowed to specify interfaces names as Rust strings,
|
|
/// thus valid UTF-8. Also, self-generated interface names form CHV are
|
|
/// also always created from Rust strings, thus valid UTF-8.
|
|
pub fn if_name_as_str(&self) -> &str {
|
|
// Panicking here is fine, see function documentation.
|
|
str::from_utf8(self.if_name.as_bytes()).expect("Tap interface name should be valid UTF-8")
|
|
}
|
|
|
|
#[cfg(fuzzing)]
|
|
pub fn new_for_fuzzing(tap_file: File, if_name: &str) -> Self {
|
|
if if_name.len() > IFNAMSIZ - 1 {
|
|
panic!("provided name longer than `IFNAMSIZ` without NULL terminator")
|
|
}
|
|
|
|
let if_name = CString::new(if_name)
|
|
.map_err(|_| Error::IfnameContainsNUL(if_name.to_string()))
|
|
.unwrap();
|
|
Tap { tap_file, if_name }
|
|
}
|
|
}
|
|
|
|
impl Read for Tap {
|
|
fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
|
|
self.tap_file.read(buf)
|
|
}
|
|
}
|
|
|
|
impl Write for Tap {
|
|
fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
|
|
self.tap_file.write(buf)
|
|
}
|
|
|
|
fn flush(&mut self) -> IoResult<()> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl AsRawFd for Tap {
|
|
fn as_raw_fd(&self) -> RawFd {
|
|
self.tap_file.as_raw_fd()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[cfg(devcli_testenv)] // we need special permissions in the ENV to create Tap devices
|
|
mod unit_tests {
|
|
use std::net::Ipv4Addr;
|
|
use std::sync::{LazyLock, Mutex, mpsc};
|
|
use std::time::Duration;
|
|
use std::{str, thread};
|
|
|
|
use pnet::packet::ethernet::{EtherTypes, EthernetPacket, MutableEthernetPacket};
|
|
use pnet::packet::ip::IpNextHeaderProtocols;
|
|
use pnet::packet::ipv4::{Ipv4Packet, MutableIpv4Packet};
|
|
use pnet::packet::udp::{MutableUdpPacket, UdpPacket};
|
|
use pnet::packet::{MutablePacket, Packet};
|
|
use pnet::util::MacAddr;
|
|
use pnet_datalink::Channel::Ethernet;
|
|
use pnet_datalink::{DataLinkReceiver, DataLinkSender, NetworkInterface};
|
|
|
|
use super::*;
|
|
|
|
static DATA_STRING: &str = "test for tap";
|
|
static SUBNET_MASK: &str = "255.255.255.0";
|
|
|
|
// We needed to have a mutex as a global variable, so we use a once cell for testing. The main
|
|
// potential problem, caused by tests being run in parallel by cargo, is creating different
|
|
// TAPs and trying to associate the same address, so we hide the IP address &str behind this
|
|
// mutex, more as a convention to remember to lock it at the very beginning of each function
|
|
// susceptible to this issue. Another variant is to use a different IP address per function,
|
|
// but we must remember to pick an unique one each time.
|
|
static TAP_IP_LOCK: LazyLock<Mutex<&'static str>> =
|
|
LazyLock::new(|| Mutex::new("192.168.241.1"));
|
|
|
|
// Describes the outcomes we are currently interested in when parsing a packet (we use
|
|
// an UDP packet for testing).
|
|
struct ParsedPkt<'a> {
|
|
eth: EthernetPacket<'a>,
|
|
ipv4: Option<Ipv4Packet<'a>>,
|
|
udp: Option<UdpPacket<'a>>,
|
|
}
|
|
|
|
impl<'a> ParsedPkt<'a> {
|
|
fn new(buf: &'a [u8]) -> Self {
|
|
let eth = EthernetPacket::new(buf).unwrap();
|
|
let mut ipv4 = None;
|
|
let mut udp = None;
|
|
|
|
if eth.get_ethertype() == EtherTypes::Ipv4 {
|
|
let ipv4_start = 14;
|
|
ipv4 = Some(Ipv4Packet::new(&buf[ipv4_start..]).unwrap());
|
|
|
|
// Hiding the old ipv4 variable for the rest of this block.
|
|
let ipv4 = Ipv4Packet::new(eth.payload()).unwrap();
|
|
|
|
if ipv4.get_next_level_protocol() == IpNextHeaderProtocols::Udp {
|
|
// The value in header_length indicates the number of 32 bit words
|
|
// that make up the header, not the actual length in bytes.
|
|
let udp_start = ipv4_start + ipv4.get_header_length() as usize * 4;
|
|
udp = Some(UdpPacket::new(&buf[udp_start..]).unwrap());
|
|
}
|
|
}
|
|
|
|
ParsedPkt { eth, ipv4, udp }
|
|
}
|
|
|
|
fn print(&self) {
|
|
print!(
|
|
"{} {} {} ",
|
|
self.eth.get_source(),
|
|
self.eth.get_destination(),
|
|
self.eth.get_ethertype()
|
|
);
|
|
if let Some(ref ipv4) = self.ipv4 {
|
|
print!(
|
|
"{} {} {} ",
|
|
ipv4.get_source(),
|
|
ipv4.get_destination(),
|
|
ipv4.get_next_level_protocol()
|
|
);
|
|
}
|
|
if let Some(ref udp) = self.udp {
|
|
print!(
|
|
"{} {} {}",
|
|
udp.get_source(),
|
|
udp.get_destination(),
|
|
str::from_utf8(udp.payload()).unwrap()
|
|
);
|
|
}
|
|
println!();
|
|
}
|
|
}
|
|
|
|
// Given a buffer of appropriate size, this fills in the relevant fields based on the
|
|
// provided information. Payload refers to the UDP payload.
|
|
fn pnet_build_packet(buf: &mut [u8], dst_mac: MacAddr, payload: &[u8]) {
|
|
let mut eth = MutableEthernetPacket::new(buf).unwrap();
|
|
eth.set_source(MacAddr::new(0x06, 0, 0, 0, 0, 0));
|
|
eth.set_destination(dst_mac);
|
|
eth.set_ethertype(EtherTypes::Ipv4);
|
|
|
|
let mut ipv4 = MutableIpv4Packet::new(eth.payload_mut()).unwrap();
|
|
ipv4.set_version(4);
|
|
ipv4.set_header_length(5);
|
|
ipv4.set_total_length(20 + 8 + payload.len() as u16);
|
|
ipv4.set_ttl(200);
|
|
ipv4.set_next_level_protocol(IpNextHeaderProtocols::Udp);
|
|
ipv4.set_source(Ipv4Addr::new(192, 168, 241, 1));
|
|
ipv4.set_destination(Ipv4Addr::new(192, 168, 241, 2));
|
|
|
|
let mut udp = MutableUdpPacket::new(ipv4.payload_mut()).unwrap();
|
|
udp.set_source(1000);
|
|
udp.set_destination(1001);
|
|
udp.set_length(8 + payload.len() as u16);
|
|
udp.set_payload(payload);
|
|
}
|
|
|
|
// Sends a test packet on the interface named "ifname".
|
|
fn pnet_send_packet(ifname: &str) {
|
|
let payload = DATA_STRING.as_bytes();
|
|
|
|
// eth hdr + ip hdr + udp hdr + payload len
|
|
let buf_size = 14 + 20 + 8 + payload.len();
|
|
|
|
let (mac, mut tx, _) = pnet_get_mac_tx_rx(ifname);
|
|
|
|
let res = tx.build_and_send(1, buf_size, &mut |buf| {
|
|
pnet_build_packet(buf, mac, payload);
|
|
});
|
|
// Make sure build_and_send() -> Option<io::Result<()>> succeeds.
|
|
res.unwrap().unwrap();
|
|
}
|
|
|
|
// For a given interface name, this returns a tuple that contains the MAC address of the
|
|
// interface, an object that can be used to send Ethernet frames, and a receiver of
|
|
// Ethernet frames arriving at the specified interface.
|
|
fn pnet_get_mac_tx_rx(
|
|
ifname: &str,
|
|
) -> (MacAddr, Box<dyn DataLinkSender>, Box<dyn DataLinkReceiver>) {
|
|
let interface_name_matches = |iface: &NetworkInterface| iface.name == ifname;
|
|
|
|
// Find the network interface with the provided name.
|
|
let interfaces = pnet_datalink::interfaces();
|
|
let interface = interfaces.into_iter().find(interface_name_matches).unwrap();
|
|
|
|
if let Ok(Ethernet(tx, rx)) = pnet_datalink::channel(&interface, Default::default()) {
|
|
(interface.mac.unwrap(), tx, rx)
|
|
} else {
|
|
panic!("datalink channel error or unhandled channel type");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_tap_create() {
|
|
let _tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
|
|
|
let t = Tap::new(1).unwrap();
|
|
println!("created tap: {t:?}");
|
|
}
|
|
|
|
#[test]
|
|
fn test_tap_from_fd() {
|
|
let _tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
|
|
|
let orig_tap = Tap::new(1).unwrap();
|
|
let fd = orig_tap.as_raw_fd();
|
|
let _new_tap = Tap::from_tap_fd(fd, 1).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_tap_configure() {
|
|
// This should be the first thing to be called inside the function, so everything else
|
|
// is torn down by the time the mutex is automatically released. Also, we should
|
|
// explicitly bind the MutexGuard to a variable via let, the make sure it lives until
|
|
// the end of the function.
|
|
let tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
|
|
|
let tap = Tap::new(1).unwrap();
|
|
let ip_addr = IpAddr::V4((*tap_ip_guard).parse().unwrap());
|
|
let netmask = IpAddr::V4(SUBNET_MASK.parse().unwrap());
|
|
|
|
tap.set_ip_addr(ip_addr, Some(netmask)).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_tap_configure_ipv6() {
|
|
let tap_ip6_lock: Mutex<&'static str> = Mutex::new("2001:db8:85a3::8a2e:370:7334");
|
|
let tap_ip6_guard = tap_ip6_lock.lock().unwrap();
|
|
|
|
let tap = Tap::new(1).unwrap();
|
|
let ip_addr = IpAddr::V6((*tap_ip6_guard).parse().unwrap());
|
|
let netmask = IpAddr::V6("ffff:ffff::".parse().unwrap());
|
|
|
|
tap.set_ip_addr(ip_addr, Some(netmask)).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_set_options() {
|
|
let _tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
|
|
|
// This line will fail to provide an initialized FD if the test is not run as root.
|
|
let tap = Tap::new(1).unwrap();
|
|
tap.set_vnet_hdr_size(16).unwrap();
|
|
tap.set_offload(0).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_tap_enable() {
|
|
let _tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
|
|
|
let tap = Tap::new(1).unwrap();
|
|
tap.enable().unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_raw_fd() {
|
|
let _tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
|
|
|
let tap = Tap::new(1).unwrap();
|
|
assert_eq!(tap.as_raw_fd(), tap.tap_file.as_raw_fd());
|
|
}
|
|
|
|
#[test]
|
|
fn test_read() {
|
|
let tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
|
|
|
let mut tap = Tap::new(1).unwrap();
|
|
let ip_addr = IpAddr::V4((*tap_ip_guard).parse().unwrap());
|
|
let netmask = IpAddr::V4(SUBNET_MASK.parse().unwrap());
|
|
tap.set_ip_addr(ip_addr, Some(netmask)).unwrap();
|
|
tap.enable().unwrap();
|
|
|
|
// Send a packet to the interface. We expect to be able to receive it on the associated fd.
|
|
pnet_send_packet(tap.if_name_as_str());
|
|
|
|
let mut buf = [0u8; 4096];
|
|
|
|
let mut found_packet_sz = None;
|
|
|
|
// In theory, this could actually loop forever if something keeps sending data through the
|
|
// tap interface, but it's highly unlikely.
|
|
while found_packet_sz.is_none() {
|
|
let size = tap.read(&mut buf).unwrap();
|
|
|
|
// We skip the first 10 bytes because the IFF_VNET_HDR flag is set when the interface
|
|
// is created, and the legacy header is 10 bytes long without a certain flag which
|
|
// is not set in Tap::new().
|
|
let eth_bytes = &buf[10..size];
|
|
|
|
let packet = EthernetPacket::new(eth_bytes).unwrap();
|
|
if packet.get_ethertype() != EtherTypes::Ipv4 {
|
|
// not an IPv4 packet
|
|
continue;
|
|
}
|
|
|
|
let ipv4_bytes = ð_bytes[14..];
|
|
let packet = Ipv4Packet::new(ipv4_bytes).unwrap();
|
|
|
|
// Our packet should carry an UDP payload, and not contain IP options.
|
|
if packet.get_next_level_protocol() != IpNextHeaderProtocols::Udp
|
|
&& packet.get_header_length() != 5
|
|
{
|
|
continue;
|
|
}
|
|
|
|
let udp_bytes = &ipv4_bytes[20..];
|
|
|
|
let udp_len = UdpPacket::new(udp_bytes).unwrap().get_length() as usize;
|
|
|
|
// Skip the header bytes.
|
|
let inner_string = str::from_utf8(&udp_bytes[8..udp_len]).unwrap();
|
|
|
|
if inner_string.eq(DATA_STRING) {
|
|
found_packet_sz = Some(size);
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert!(found_packet_sz.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn test_write() {
|
|
let tap_ip_guard = TAP_IP_LOCK.lock().unwrap();
|
|
|
|
let mut tap = Tap::new(1).unwrap();
|
|
let ip_addr = IpAddr::V4((*tap_ip_guard).parse().unwrap());
|
|
let netmask = IpAddr::V4(SUBNET_MASK.parse().unwrap());
|
|
tap.set_ip_addr(ip_addr, Some(netmask)).unwrap();
|
|
tap.enable().unwrap();
|
|
|
|
let (mac, _, mut rx) = pnet_get_mac_tx_rx(tap.if_name_as_str());
|
|
|
|
let payload = DATA_STRING.as_bytes();
|
|
|
|
// vnet hdr + eth hdr + ip hdr + udp hdr + payload len
|
|
let buf_size = 10 + 14 + 20 + 8 + payload.len();
|
|
|
|
let mut buf = vec![0u8; buf_size];
|
|
// leave the vnet hdr as is
|
|
pnet_build_packet(&mut buf[10..], mac, payload);
|
|
|
|
tap.write_all(&buf).unwrap();
|
|
tap.flush().unwrap();
|
|
|
|
let (channel_tx, channel_rx) = mpsc::channel();
|
|
|
|
// We use a separate thread to wait for the test packet because the API exposed by pnet is
|
|
// blocking. This thread will be killed when the main thread exits.
|
|
let _handle = thread::spawn(move || {
|
|
loop {
|
|
let buf = rx.next().unwrap();
|
|
let p = ParsedPkt::new(buf);
|
|
p.print();
|
|
|
|
if let Some(ref udp) = p.udp
|
|
&& payload == udp.payload()
|
|
{
|
|
channel_tx.send(true).unwrap();
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
// We wait for at most SLEEP_MILLIS * SLEEP_ITERS milliseconds for the reception of the
|
|
// test packet to be detected.
|
|
static SLEEP_MILLIS: u64 = 500;
|
|
static SLEEP_ITERS: u32 = 6;
|
|
|
|
let mut found_test_packet = false;
|
|
|
|
for _ in 0..SLEEP_ITERS {
|
|
thread::sleep(Duration::from_millis(SLEEP_MILLIS));
|
|
if let Ok(true) = channel_rx.try_recv() {
|
|
found_test_packet = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert!(found_test_packet);
|
|
}
|
|
}
|