Skip to content

Commit

Permalink
Made Network interface generic with trait and added implementation fo…
Browse files Browse the repository at this point in the history
…r existing TAP devices
  • Loading branch information
jounathaen committed Jun 12, 2024
1 parent bb6fc6d commit f4b2793
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 113 deletions.
3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ instrument = ["rftrace", "rftrace-frontend"]
[dependencies]
byte-unit = { version = "5", features = ["byte"] }
clap = { version = "4.5", features = ["derive", "env"] }
nix = { version = "0.28", features = ["mman", "pthread", "signal", "net"] }
nix = { version = "0.28", features = ["mman", "pthread", "signal", "net", "ioctl"] }
core_affinity = "0.8"
either = "1.12"
env_logger = "0.11"
Expand All @@ -59,7 +59,6 @@ tun-tap = { version = "0.1.3", default-features = false }
uhyve-interface = { version = "0.1.1", path = "uhyve-interface", features = ["std"] }
virtio-bindings = { version = "0.2", features = ["virtio-v4_14_0"] }
bitflags = "2.4"

rftrace = { version = "0.1", optional = true }
rftrace-frontend = { version = "0.1", optional = true }
vm-memory = { version = "0.14.1", features = ["backend-mmap"] }
Expand Down
111 changes: 111 additions & 0 deletions src/net/macvtap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//! Tap device wrapper for uhyve.

use std::{
io::{self, Read, Write},
os::fd::AsRawFd,
str::FromStr,
sync::Mutex,
};

use macvtap::{Iface, Mode};
use nix::{ifaddrs::InterfaceAddress, net::if_::InterfaceFlags, sys::socket::LinkAddr};

use crate::net::{NetworkInterface, UHYVE_NET_MTU};

/// Wrapper for a tap device, containing the descriptor and mac address.
pub struct MacVTap {
tap: Iface,
interface_address: InterfaceAddress,
}

impl MacVTap {
/// Create a Layer 2 Linux/*BSD tap device, named "tap[0-9]+".
pub fn new() -> io::Result<Self> {
let iface_name = std::env::var("TAP").unwrap_or("tap10".to_owned());

Self::from_str(&iface_name)
}

/// Return the tap device name
pub fn name(&self) -> &str {
&self.interface_address.interface_name
}

fn mac_addr(&self) -> LinkAddr {
*self
.interface_address
.address
.unwrap()
.as_link_addr()
.unwrap()
}
}

impl NetworkInterface for Mutex<MacVTap> {
fn mac_address_as_bytes(&self) -> [u8; 6] {
self.lock().unwrap().mac_addr().addr().unwrap()
}

fn send(&self, buf: &[u8]) -> io::Result<usize> {
let mut guard = self.lock().unwrap();
trace!("sending {} bytes on MacVTap {}", buf.len(), guard.name());
guard.tap.write(buf)
}

fn recv(&self, buf: &mut [u8]) -> io::Result<usize> {
let mut guard = self.lock().unwrap();
let res = guard.tap.read(buf);
trace!("receiving {res:?} bytes on MacVTap {}", guard.name());
res
}
}

impl Drop for MacVTap {
fn drop(&mut self) {
self.tap.close();
}
}

impl AsRawFd for MacVTap {
fn as_raw_fd(&self) -> i32 {
self.tap.as_raw_fd()
}
}

impl Default for MacVTap {
fn default() -> Self {
Self::new().unwrap()
}
}

impl FromStr for MacVTap {
type Err = std::io::Error;

fn from_str(name: &str) -> std::result::Result<Self, Self::Err> {
// TODO: MacVTap mode
let tap = Iface::new(name, Mode::Tap, UHYVE_NET_MTU.try_into().unwrap())
.expect("Failed to create tap device (Device busy, or you may need to enable CAP_NET_ADMIN capability).");

let interface_address = nix::ifaddrs::getifaddrs()
.unwrap()
.find(|dev| dev.interface_name == name)
.expect("Could not find TAP interface.");

// TODO: ensure the tap device is header-less

assert!(
interface_address.flags.contains(InterfaceFlags::IFF_TAP),
"Interface is not a valid TAP device."
);

assert!(
interface_address.flags.contains(InterfaceFlags::IFF_UP),
"Interface is not up and running."
);

Ok(MacVTap {
tap,
interface_address,
})
}
}
25 changes: 24 additions & 1 deletion src/net/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::io;

pub use crate::consts::{UHYVE_NET_MTU, UHYVE_QUEUE_SIZE};

pub const BROADCAST_MAC_ADDR: [u8; 6] = [0xff; 6];
Expand All @@ -12,4 +14,25 @@ pub const UHYVE_PCI_CLASS_INFO: [u8; 3] = [
PCI_ETHERNET_SUBCLASS,
];

pub mod tap;
pub(crate) mod macvtap;
pub(crate) mod tap;

// TODO: Remove Sync and split in two
pub(crate) trait NetworkInterface: Sync + Send {
/// Return the MAC address as a byte array
fn mac_address_as_bytes(&self) -> [u8; 6];

/// Sends a packet to the interface.
///
/// **NOTE**: ensure the packet has the appropriate format and header.
/// Incorrect packets will be dropped without warning.
fn send(&self, buf: &[u8]) -> io::Result<usize>;

/// Receives a packet from the interface.
///
/// Blocks until a packet is sent into the virtual interface. At that point, the content of the
/// packet is copied into the provided buffer.
///
/// Returns the size of the received packet
fn recv(&self, buf: &mut [u8]) -> io::Result<usize>;
}
175 changes: 75 additions & 100 deletions src/net/tap.rs
Original file line number Diff line number Diff line change
@@ -1,119 +1,94 @@
//! Tap device wrapper for uhyve.

use std::{
io::{self, Read, Write},
os::fd::AsRawFd,
str::FromStr,
fs::{File, OpenOptions},
io::{self, Error, Read, Write},
os::unix::io::AsRawFd,
sync::Mutex,
};

use macvtap::{Iface, Mode};
use nix::{ifaddrs::InterfaceAddress, net::if_::InterfaceFlags, sys::socket::LinkAddr};
use libc::{ifreq, IFF_NO_PI, IFF_TAP};
use nix::{ifaddrs::getifaddrs, ioctl_write_int};
use vm_memory::address;

use crate::net::UHYVE_NET_MTU;
use crate::net::NetworkInterface;

/// Wrapper for a tap device, containing the descriptor and mac address.
/// An existing (externally created) TAP device
pub struct Tap {
tap: Iface,
interface_address: InterfaceAddress,
fd: File,
mac: [u8; 6],
name: String,
}

impl Tap {
/// Create a Layer 2 Linux/*BSD tap device, named "tap[0-9]+".
pub fn new() -> io::Result<Self> {
let iface_name = std::env::var("TAP").unwrap_or("tap10".to_owned());

Self::from_str(&iface_name)
}

/// Return the tap device name
pub fn name(&self) -> &str {
&self.interface_address.interface_name
}

fn mac_addr(&self) -> LinkAddr {
*self
.interface_address
.address
.unwrap()
.as_link_addr()
.unwrap()
}

/// Return the MAC address as a byte array
pub fn mac_address_as_bytes(&self) -> [u8; 6] {
self.mac_addr().addr().unwrap()
}

/// Get the tap interface struct
pub fn get_iface(&self) -> &Iface {
&self.tap
}

/// Sends a packet to the interface.
///
/// **NOTE**: ensure the packet has the appropriate format and header.
/// Incorrect packets will be dropped without warning.
pub fn send(&mut self, buf: &[u8]) -> io::Result<usize> {
self.tap.write(buf)
}

/// Receives a packet from the interface.
///
/// Blocks until a packet is sent into the virtual interface. At that point, the content of the
/// packet is copied into the provided buffer.
///
/// Returns the size of the received packet
pub fn recv(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.tap.read(buf)
if iface_name.as_bytes().len() > 16 {
return Err(Error::other("Interface name must not exceed 16 bytes"));
}
let mut ifr_name: [i8; 16] = [0; 16];
iface_name
.as_bytes()
.iter()
.take(15)
.map(|b| *b as i8)
.enumerate()
.for_each(|(i, b)| ifr_name[i] = b);

let config_str = ifreq {
ifr_name,
ifr_ifru: libc::__c_anonymous_ifr_ifru {
ifru_flags: IFF_TAP as i16 | IFF_NO_PI as i16, // TODO: Investigate if IFF_NO_PI is necessary as well
},
};

let fd = OpenOptions::new()
.read(true)
.write(true)
.open("/dev/net/tun")?;

ioctl_write_int!(tun_set_iff, b'T', 202);

let res =
unsafe { tun_set_iff(fd.as_raw_fd(), &config_str as *const ifreq as u64).unwrap() };

if res == -1 {
error!("Can't open TAP device {iface_name}");
return Err(Error::other("Can't open TAP device"));
}

// Find MAC address of the TAP device
let mut mac_addr = None;
for ifaddr in getifaddrs().unwrap() {
if let Some(address) = ifaddr.address {
if ifaddr.interface_name == iface_name {
if let Some(link_addr) = address.as_link_addr() {
mac_addr = Some(link_addr.addr().unwrap());
}
}
}
}

Ok(Self {
fd,
name: iface_name,
mac: mac_addr.expect("TAP device without MAC address?"),
})
}
}

impl Drop for Tap {
fn drop(&mut self) {
self.tap.close();
impl NetworkInterface for Mutex<Tap> {
fn mac_address_as_bytes(&self) -> [u8; 6] {
self.lock().unwrap().mac
}
}

impl AsRawFd for Tap {
fn as_raw_fd(&self) -> i32 {
self.tap.as_raw_fd()
fn send(&self, buf: &[u8]) -> io::Result<usize> {
let mut guard = self.lock().unwrap();
trace!("sending {} bytes on {}", buf.len(), guard.name);
guard.fd.write(buf)
}
}

impl Default for Tap {
fn default() -> Self {
Self::new().unwrap()
}
}

impl FromStr for Tap {
type Err = std::io::Error;

fn from_str(name: &str) -> std::result::Result<Self, Self::Err> {
// TODO: MacVTap mode
let tap = Iface::new(name, Mode::Tap, UHYVE_NET_MTU.try_into().unwrap())
.expect("Failed to create tap device (Device busy, or you may need to enable CAP_NET_ADMIN capability).");

let interface_address = nix::ifaddrs::getifaddrs()
.unwrap()
.find(|dev| dev.interface_name == name)
.expect("Could not find TAP interface.");

// TODO: ensure the tap device is header-less

assert!(
interface_address.flags.contains(InterfaceFlags::IFF_TAP),
"Interface is not a valid TAP device."
);

assert!(
interface_address.flags.contains(InterfaceFlags::IFF_UP),
"Interface is not up and running."
);

Ok(Tap {
tap,
interface_address,
})
fn recv(&self, buf: &mut [u8]) -> io::Result<usize> {
let mut guard = self.lock().unwrap();
let res = guard.fd.read(buf);
trace!("receiving {res:?} bytes on {}", guard.name);
res
}
}
Loading

0 comments on commit f4b2793

Please sign in to comment.