Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@ dns = ["net", "smoltcp", "smoltcp/socket-dns"]
## Enables pretty-printing the network traffic to the serial device.
net-trace = ["smoltcp?/log", "smoltcp?/verbose"]

## Enables the generation of packet capture files.
##
## The guest path is specified via the `HERMIT_PCAP_PATH` *runtime* environment variable. If a
## complete path is not provided, values without a `/` are interpreted as file names and ones that
## end with a `/` as directories. The default directory is `/root/` and the default file name is the
## network device name. If a file already exists at the path, the file name is suffixed with
## successive numbers.
write-pcap-file = []

#! ### Network Drivers
#!
#! Currently, Hermit does not support multiple network drivers at the same time.
Expand Down
118 changes: 116 additions & 2 deletions src/executor/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use smoltcp::iface::{Config, Interface, SocketSet};
#[cfg(feature = "net-trace")]
use smoltcp::phy::Tracer;
use smoltcp::phy::{Device, Medium};
#[cfg(feature = "write-pcap-file")]
use smoltcp::phy::{PcapMode, PcapWriter};
#[cfg(feature = "dhcpv4")]
use smoltcp::socket::dhcpv4;
#[cfg(all(feature = "dns", not(feature = "dhcpv4")))]
Expand All @@ -16,6 +18,8 @@ use smoltcp::wire::{IpCidr, Ipv4Address, Ipv4Cidr};

use super::network::{NetworkInterface, NetworkState};
use crate::arch;
#[cfg(feature = "write-pcap-file")]
use crate::drivers::Driver;
use crate::drivers::net::NetworkDriver;

cfg_select! {
Expand All @@ -42,19 +46,30 @@ impl<'a> NetworkInterface<'a> {
feature = "rtl8139",
feature = "virtio-net",
) => {
#[cfg_attr(feature = "net-trace", expect(unused_mut))]
#[cfg_attr(any(feature = "net-trace", feature = "write-pcap-file"), expect(unused_mut))]
let Some(mut device) = NETWORK_DEVICE.lock().take() else {
return NetworkState::InitializationFailed;
};
}
_ => {
#[cfg_attr(feature = "net-trace", expect(unused_mut))]
#[cfg_attr(any(feature = "net-trace", feature = "write-pcap-file"), expect(unused_mut))]
let mut device = LoopbackDriver::new();
}
}

let mac = device.get_mac_address();

#[cfg_attr(feature = "net-trace", expect(unused_mut))]
#[cfg(feature = "write-pcap-file")]
let mut device = {
let default_name = device.get_name();
PcapWriter::new(
device,
pcap_writer::FileSink::new(default_name),
PcapMode::Both,
)
};

#[cfg(feature = "net-trace")]
let mut device = Tracer::new(device, |timestamp, printer| trace!("{timestamp} {printer}"));

Expand Down Expand Up @@ -132,3 +147,102 @@ impl<'a> NetworkInterface<'a> {
}))
}
}

#[cfg(feature = "write-pcap-file")]
pub(in crate::executor) mod pcap_writer {
use core::fmt::Write as _;

use embedded_io::Write as _;
use smoltcp::phy::PcapSink;

use crate::errno::Errno;
use crate::fs::File;

/// Sink for packet captures. If the file Option is None, the writes are ignored.
/// This is useful when we fail to create the sink file at runtime.
pub struct FileSink(Option<File>);
Comment thread
cagatay-y marked this conversation as resolved.

impl FileSink {
pub(super) fn new(device_name: &str) -> Self {
let (parent, file_prefix, extension) = parse_path(device_name);
let mut path = format!("{parent}/{file_prefix}");
let base_len = path.len();
for i in 1.. {
if let Some(extension) = extension {
path.push('.');
path.push_str(extension);
}
match File::create_new(path.as_str()) {
Ok(file) => {
info!("The packet capture will be written to '{path}'.");
return Self(Some(file));
}
Err(Errno::Exist) => {
path.truncate(base_len);
write!(&mut path, " ({i})").unwrap();
}
Err(e) => {
if e == Errno::Noent {
error!("'{parent}/' does not exist. Is it mounted?");
}
error!(
"Error {e:?} encountered while creating the pcap file. No pcap file will be written."
);
break;
}
}
}
Self(None)
}
}

fn parse_path(device_name: &str) -> (&str, &str, Option<&str>) {
let mut parent = "/root";
let mut file_prefix = device_name;
let mut extension = Some("pcap");

if let Some(path) = crate::env::var("HERMIT_PCAP_PATH").filter(|var| !var.is_empty()) {
let file_name = if let Some((l, r)) = path.rsplit_once('/') {
parent = l;
if r.is_empty() { None } else { Some(r) }
} else {
Some(path.as_str())
};

if let Some(file_name) = file_name {
(file_prefix, extension) = if let Some((l, r)) = file_name.rsplit_once('.')
&& !l.is_empty()
{
(l, Some(r))
} else {
(file_name, None)
}
};
}
(parent, file_prefix, extension)
}

impl PcapSink for FileSink {
fn write(&mut self, data: &[u8]) {
let Some(file) = self.0.as_mut() else {
trace!("No file to write packet capture.");
return;
};

if let Err(err) = file.write(data) {
error!("Error while writing to the pcap file: {err}");
}
}

fn flush(&mut self) {
let Some(file) = self.0.as_mut() else {
trace!("No file to write packet capture.");
return;
};

if let Err(err) = file.flush() {
error!("Error while flushing the pcap file: {err}");
}
}
}
}
54 changes: 33 additions & 21 deletions src/executor/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ pub(crate) enum NetworkState<'a> {
feature = "virtio-net",
))]
pub(crate) fn network_handler() {
NIC.lock().as_nic_mut().unwrap().handle_interrupt();
// It is possible for us to receive interrupts before we are done with initializing the network interface.
// This may for example be caused by an interrupt that was meant for the filesystem driver.
if let Ok(nic) = NIC.lock().as_nic_mut() {
nic.handle_interrupt();
}
Comment thread
cagatay-y marked this conversation as resolved.
}

impl<'a> NetworkState<'a> {
Expand All @@ -63,13 +67,24 @@ static LOCAL_ENDPOINT: AtomicU16 = AtomicU16::new(0);
pub(crate) static NIC: InterruptTicketMutex<NetworkState<'_>> =
InterruptTicketMutex::new(NetworkState::Missing);

type MaybePcapDevice = cfg_select! {
feature = "write-pcap-file" => {
smoltcp::phy::PcapWriter<NetworkDevice, crate::executor::device::pcap_writer::FileSink>
}
_ => NetworkDevice,
};

type MaybeTracerDevice = cfg_select! {
feature = "net-trace" => {
smoltcp::phy::Tracer<MaybePcapDevice>
}
_ => MaybePcapDevice,
};

pub(crate) struct NetworkInterface<'a> {
pub(super) iface: smoltcp::iface::Interface,
pub(super) sockets: SocketSet<'a>,
#[cfg(feature = "net-trace")]
pub(super) device: smoltcp::phy::Tracer<NetworkDevice>,
#[cfg(not(feature = "net-trace"))]
pub(super) device: NetworkDevice,
pub(super) device: MaybeTracerDevice,
#[cfg(feature = "dhcpv4")]
pub(super) dhcp_handle: SocketHandle,
#[cfg(feature = "dns")]
Expand Down Expand Up @@ -368,24 +383,21 @@ impl<'a> NetworkInterface<'a> {
feature = "virtio-net",
))]
fn handle_interrupt(&mut self) {
cfg_select! {
feature = "net-trace" => {
self.device.get_mut().handle_interrupt();
}
_ => {
self.device.handle_interrupt();
}
}
self.get_inner_device().handle_interrupt();
}

pub(crate) fn set_polling_mode(&mut self, value: bool) {
cfg_select! {
feature = "net-trace" => {
self.device.get_mut().set_polling_mode(value);
}
_ => {
self.device.set_polling_mode(value);
}
}
self.get_inner_device().set_polling_mode(value);
}

/// Gets the device inside the [smoltcp::phy::Tracer] and [smoltcp::phy::PcapWriter] layers.
fn get_inner_device(&mut self) -> &mut impl NetworkDriver {
let device = &mut self.device;
#[cfg(feature = "net-trace")]
let device = device.get_mut();
#[cfg(feature = "write-pcap-file")]
let device = device.get_mut();

device
}
}
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,11 @@ extern "C" fn initd(_arg: usize) {

// Initialize Drivers
drivers::init();
// The filesystem needs to be initialized before network to allow writing packet captures to a file.
fs::init();
crate::executor::init();

syscalls::init();
fs::init();
#[cfg(feature = "shell")]
shell::init();

Expand Down
Loading