diff --git a/uefi-test-runner/src/proto/network/mod.rs b/uefi-test-runner/src/proto/network/mod.rs new file mode 100644 index 000000000..436d78635 --- /dev/null +++ b/uefi-test-runner/src/proto/network/mod.rs @@ -0,0 +1,11 @@ +use uefi::prelude::*; + +pub fn test(bt: &BootServices) { + info!("Testing Network protocols"); + + pxe::test(bt); + snp::test(bt); +} + +mod pxe; +mod snp; diff --git a/uefi-test-runner/src/proto/network.rs b/uefi-test-runner/src/proto/network/pxe.rs similarity index 98% rename from uefi-test-runner/src/proto/network.rs rename to uefi-test-runner/src/proto/network/pxe.rs index f19ba9026..17b00416e 100644 --- a/uefi-test-runner/src/proto/network.rs +++ b/uefi-test-runner/src/proto/network/pxe.rs @@ -8,7 +8,7 @@ use uefi::{ }; pub fn test(bt: &BootServices) { - info!("Testing Network protocols"); + info!("Testing The PXE base code protocol"); if let Ok(handles) = bt.find_handles::() { for handle in handles { diff --git a/uefi-test-runner/src/proto/network/snp.rs b/uefi-test-runner/src/proto/network/snp.rs new file mode 100644 index 000000000..196fd5cc5 --- /dev/null +++ b/uefi-test-runner/src/proto/network/snp.rs @@ -0,0 +1,124 @@ +use uefi::prelude::BootServices; +use uefi::proto::network::snp::{InterruptStatus, ReceiveFlags, SimpleNetwork}; +use uefi::proto::network::MacAddress; +use uefi::Status; + +pub fn test(bt: &BootServices) { + info!("Testing the simple network protocol"); + + let handles = bt.find_handles::().unwrap_or_default(); + + for handle in handles { + let simple_network = bt.open_protocol_exclusive::(handle); + if simple_network.is_err() { + continue; + } + let simple_network = simple_network.unwrap(); + + // Check shutdown + simple_network + .shutdown() + .expect("Failed to shutdown Simple Network"); + + // Check stop + simple_network + .stop() + .expect("Failed to stop Simple Network"); + + // Check start + simple_network + .start() + .expect("Failed to start Simple Network"); + + // Check initialize + simple_network + .initialize(0, 0) + .expect("Failed to initialize Simple Network"); + + simple_network.reset_statistics().unwrap(); + + // Reading the interrupt status clears it + simple_network.get_interrupt_status().unwrap(); + + // Set receive filters + simple_network + .receive_filters( + ReceiveFlags::UNICAST | ReceiveFlags::MULTICAST | ReceiveFlags::BROADCAST, + ReceiveFlags::empty(), + false, + None, + ) + .expect("Failed to set receive filters"); + + // Check media + if !simple_network.mode().media_present_supported || !simple_network.mode().media_present { + continue; + } + + let payload = b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\ + \x45\x00\ + \x00\x21\ + \x00\x01\ + \x00\x00\ + \x10\ + \x11\ + \x07\x6a\ + \xc0\xa8\x11\x0f\ + \xc0\xa8\x11\x02\ + \x54\x45\ + \x54\x44\ + \x00\x0d\ + \xa9\xe4\ + \x04\x01\x02\x03\x04"; + + let dest_addr = MacAddress([0xffu8; 32]); + assert!(!simple_network + .get_interrupt_status() + .unwrap() + .contains(InterruptStatus::TRANSMIT)); + + // Send the frame + simple_network + .transmit( + simple_network.mode().media_header_size as usize, + payload, + None, + Some(dest_addr), + Some(0x0800), + ) + .expect("Failed to transmit frame"); + + info!("Waiting for the transmit"); + while !simple_network + .get_interrupt_status() + .unwrap() + .contains(InterruptStatus::TRANSMIT) + {} + + // Attempt to receive a frame + let mut buffer = [0u8; 1500]; + + info!("Waiting for the reception"); + if simple_network.receive(&mut buffer, None, None, None, None) + == Err(Status::NOT_READY.into()) + { + bt.stall(1_000_000); + + simple_network + .receive(&mut buffer, None, None, None, None) + .unwrap(); + } + + assert_eq!(buffer[42..47], [4, 4, 3, 2, 1]); + + // Get stats + let stats = simple_network + .collect_statistics() + .expect("Failed to collect statistics"); + info!("Stats: {:?}", stats); + + // One frame should have been transmitted and one received + assert_eq!(stats.tx_total_frames().unwrap(), 1); + assert_eq!(stats.rx_total_frames().unwrap(), 1); + } +} diff --git a/uefi/src/proto/network/mod.rs b/uefi/src/proto/network/mod.rs index debfde916..69316b1c9 100644 --- a/uefi/src/proto/network/mod.rs +++ b/uefi/src/proto/network/mod.rs @@ -3,6 +3,7 @@ //! These protocols can be used to interact with network resources. pub mod pxe; +pub mod snp; /// Represents an IPv4/v6 address. /// diff --git a/uefi/src/proto/network/snp.rs b/uefi/src/proto/network/snp.rs new file mode 100644 index 000000000..6554455c3 --- /dev/null +++ b/uefi/src/proto/network/snp.rs @@ -0,0 +1,589 @@ +//! Simple Network Protocol +//! +//! Provides a packet level interface to a network adapter. +//! Once the adapter is initialized, the protocol provides services that allows +//! packets to be transmitted and received. +//! +//! No interface function must be called until `SimpleNetwork.start` is successfully +//! called first. + +use super::{IpAddress, MacAddress}; +use crate::data_types::Event; +use crate::{Result, Status}; +use bitflags::bitflags; +use core::ffi::c_void; +use core::ptr; +use core::ptr::NonNull; +use uefi_macros::{unsafe_guid, Protocol}; + +/// The Simple Network Protocol +#[repr(C)] +#[unsafe_guid("a19832b9-ac25-11d3-9a2d-0090273fc14d")] +#[derive(Protocol)] +pub struct SimpleNetwork { + revision: u64, + start: extern "efiapi" fn(this: &Self) -> Status, + stop: extern "efiapi" fn(this: &Self) -> Status, + initialize: extern "efiapi" fn( + this: &Self, + extra_recv_buffer_size: usize, + extra_transmit_buffer_size: usize, + ) -> Status, + reset: extern "efiapi" fn(this: &Self, extended_verification: bool) -> Status, + shutdown: extern "efiapi" fn(this: &Self) -> Status, + receive_filters: extern "efiapi" fn( + this: &Self, + enable: u32, + disable: u32, + reset_mcast_filter: bool, + mcast_filter_count: usize, + mcast_filter: Option>, + ) -> Status, + station_address: + extern "efiapi" fn(this: &Self, reset: bool, new: Option<&MacAddress>) -> Status, + statistics: extern "efiapi" fn( + this: &Self, + reset: bool, + stats_size: Option<&mut usize>, + stats_table: Option<&mut NetworkStats>, + ) -> Status, + mcast_ip_to_mac: + extern "efiapi" fn(this: &Self, ipv6: bool, ip: &IpAddress, mac: &mut MacAddress) -> Status, + nv_data: extern "efiapi" fn( + this: &Self, + read_write: bool, + offset: usize, + buffer_size: usize, + buffer: *mut c_void, + ) -> Status, + get_status: extern "efiapi" fn( + this: &Self, + interrupt_status: Option<&mut InterruptStatus>, + tx_buf: Option<&mut *mut c_void>, + ) -> Status, + transmit: extern "efiapi" fn( + this: &Self, + header_size: usize, + buffer_size: usize, + buffer: *const c_void, + src_addr: Option<&MacAddress>, + dest_addr: Option<&MacAddress>, + protocol: Option<&u16>, + ) -> Status, + receive: extern "efiapi" fn( + this: &Self, + header_size: Option<&mut usize>, + buffer_size: &mut usize, + buffer: *mut c_void, + src_addr: Option<&mut MacAddress>, + dest_addr: Option<&mut MacAddress>, + protocol: Option<&mut u16>, + ) -> Status, + // On QEMU, this event seems to never fire. + wait_for_packet: Event, + mode: *const NetworkMode, +} + +impl SimpleNetwork { + /// Change the state of a network from "Stopped" to "Started". + pub fn start(&self) -> Result { + (self.start)(self).into() + } + + /// Change the state of a network interface from "Started" to "Stopped". + pub fn stop(&self) -> Result { + (self.stop)(self).into() + } + + /// Reset a network adapter and allocate the transmit and receive buffers + /// required by the network interface; optionally, also request allocation of + /// additional transmit and receive buffers. + pub fn initialize(&self, extra_rx_buffer_size: usize, extra_tx_buffer_size: usize) -> Result { + (self.initialize)(self, extra_rx_buffer_size, extra_tx_buffer_size).into() + } + + /// Reset a network adapter and reinitialize it with the parameters that were + /// provided in the previous call to `initialize`. + pub fn reset(&self, extended_verification: bool) -> Result { + (self.reset)(self, extended_verification).into() + } + + /// Reset a network adapter, leaving it in a state that is safe + /// for another driver to initialize + pub fn shutdown(&self) -> Result { + (self.shutdown)(self).into() + } + + /// Manage the multicast receive filters of a network. + pub fn receive_filters( + &self, + enable: ReceiveFlags, + disable: ReceiveFlags, + reset_mcast_filter: bool, + mcast_filter: Option<&[MacAddress]>, + ) -> Result { + if let Some(mcast_filter) = mcast_filter { + (self.receive_filters)( + self, + enable.bits, + disable.bits, + reset_mcast_filter, + mcast_filter.len(), + NonNull::new(mcast_filter.as_ptr() as *mut _), + ) + .into() + } else { + (self.receive_filters)(self, enable.bits, disable.bits, reset_mcast_filter, 0, None) + .into() + } + } + + /// Modify or reset the current station address, if supported. + pub fn station_address(&self, reset: bool, new: Option<&MacAddress>) -> Result { + (self.station_address)(self, reset, new).into() + } + + /// Reset statistics on a network interface. + pub fn reset_statistics(&self) -> Result { + (self.statistics)(self, true, None, None).into() + } + + /// Collect statistics on a network interface. + pub fn collect_statistics(&self) -> Result { + let mut stats_table: NetworkStats = Default::default(); + let mut stats_size = core::mem::size_of::(); + let status = (self.statistics)(self, false, Some(&mut stats_size), Some(&mut stats_table)); + Result::from(status)?; + Ok(stats_table) + } + + /// Convert a multicast IP address to a multicast HW MAC Address. + pub fn mcast_ip_to_mac(&self, ipv6: bool, ip: IpAddress) -> Result { + let mut mac_address = MacAddress([0; 32]); + let status = (self.mcast_ip_to_mac)(self, ipv6, &ip, &mut mac_address); + Result::from(status)?; + Ok(mac_address) + } + + /// Perform read operations on the NVRAM device attached to + /// a network interface. + pub fn read_nv_data(&self, offset: usize, buffer: &[u8]) -> Result { + (self.nv_data)( + self, + true, + offset, + buffer.len(), + buffer.as_ptr() as *mut c_void, + ) + .into() + } + + /// Perform write operations on the NVRAM device attached to a network interface. + pub fn write_nv_data(&self, offset: usize, buffer: &mut [u8]) -> Result { + (self.nv_data)( + self, + false, + offset, + buffer.len(), + buffer.as_mut_ptr().cast(), + ) + .into() + } + + /// Read the current interrupt status and recycled transmit buffer + /// status from a network interface. + pub fn get_interrupt_status(&self) -> Result { + let mut interrupt_status = InterruptStatus::empty(); + let status = (self.get_status)(self, Some(&mut interrupt_status), None); + Result::from(status)?; + Ok(interrupt_status) + } + + /// Read the current recycled transmit buffer status from a + /// network interface. + pub fn get_recycled_transmit_buffer_status(&self) -> Result>> { + let mut tx_buf: *mut c_void = ptr::null_mut(); + let status = (self.get_status)(self, None, Some(&mut tx_buf)); + Result::from(status)?; + Ok(NonNull::new(tx_buf.cast())) + } + + /// Place a packet in the transmit queue of a network interface. + pub fn transmit( + &self, + header_size: usize, + buffer: &[u8], + src_addr: Option, + dest_addr: Option, + protocol: Option, + ) -> Result { + (self.transmit)( + self, + header_size, + buffer.len() + header_size, + buffer.as_ptr().cast(), + src_addr.as_ref(), + dest_addr.as_ref(), + protocol.as_ref(), + ) + .into() + } + + /// Receive a packet from a network interface. + /// + /// On success, returns the size of bytes of the received packet. + pub fn receive( + &self, + buffer: &mut [u8], + header_size: Option<&mut usize>, + src_addr: Option<&mut MacAddress>, + dest_addr: Option<&mut MacAddress>, + protocol: Option<&mut u16>, + ) -> Result { + let mut buffer_size = buffer.len(); + let status = (self.receive)( + self, + header_size, + &mut buffer_size, + buffer.as_mut_ptr().cast(), + src_addr, + dest_addr, + protocol, + ); + Result::from(status)?; + Ok(buffer_size) + } + + /// Event that fires once a packet is available to be received. + /// + /// On QEMU, this event seems to never fire; it is suggested to verify that your implementation + /// of UEFI properly implements this event before using it. + #[must_use] + pub fn wait_for_packet(&self) -> &Event { + &self.wait_for_packet + } + + /// Returns a reference to the Simple Network mode. + #[must_use] + pub fn mode(&self) -> &NetworkMode { + unsafe { &*self.mode } + } +} + +bitflags! { + /// Flags to pass to receive_filters to enable/disable reception of some kinds of packets. + pub struct ReceiveFlags : u32 { + /// Receive unicast packets. + const UNICAST = 0x01; + /// Receive multicast packets. + const MULTICAST = 0x02; + /// Receive broadcast packets. + const BROADCAST = 0x04; + /// Receive packets in promiscuous mode. + const PROMISCUOUS = 0x08; + /// Receive packets in promiscuous multicast mode. + const PROMISCUOUS_MULTICAST = 0x10; + } +} + +bitflags! { + /// Flags returned by get_interrupt_status to indicate which interrupts have fired on the + /// interace since the last call. + pub struct InterruptStatus : u32 { + /// Packet received. + const RECEIVE = 0x01; + /// Packet transmitted. + const TRANSMIT = 0x02; + /// Command interrupt fired. + const COMMAND = 0x04; + /// Software interrupt fired. + const SOFTWARE = 0x08; + } +} + +/// Network Statistics +/// +/// The description of statistics on the network with the SNP's `statistics` function +/// is returned in this structure +/// +/// Any of these statistics may or may not be available on the device. So, all the +/// retriever functions of the statistics return `None` when a statistic is not supported +#[repr(C)] +#[derive(Default, Debug)] +pub struct NetworkStats { + rx_total_frames: u64, + rx_good_frames: u64, + rx_undersize_frames: u64, + rx_oversize_frames: u64, + rx_dropped_frames: u64, + rx_unicast_frames: u64, + rx_broadcast_frames: u64, + rx_multicast_frames: u64, + rx_crc_error_frames: u64, + rx_total_bytes: u64, + tx_total_frames: u64, + tx_good_frames: u64, + tx_undersize_frames: u64, + tx_oversize_frames: u64, + tx_dropped_frames: u64, + tx_unicast_frames: u64, + tx_broadcast_frames: u64, + tx_multicast_frames: u64, + tx_crc_error_frames: u64, + tx_total_bytes: u64, + collisions: u64, + unsupported_protocol: u64, + rx_duplicated_frames: u64, + rx_decrypt_error_frames: u64, + tx_error_frames: u64, + tx_retry_frames: u64, +} + +impl NetworkStats { + /// Any statistic value of -1 is not available + fn available(&self, stat: u64) -> bool { + stat as i64 != -1 + } + + /// Takes a statistic and converts it to an option + /// + /// When the statistic is not available, `None` is returned + fn to_option(&self, stat: u64) -> Option { + match self.available(stat) { + true => Some(stat), + false => None, + } + } + + /// The total number of frames received, including error frames + /// and dropped frames + #[must_use] + pub fn rx_total_frames(&self) -> Option { + self.to_option(self.rx_total_frames) + } + + /// The total number of good frames received and copied + /// into receive buffers + #[must_use] + pub fn rx_good_frames(&self) -> Option { + self.to_option(self.rx_good_frames) + } + + /// The number of frames below the minimum length for the + /// communications device + #[must_use] + pub fn rx_undersize_frames(&self) -> Option { + self.to_option(self.rx_undersize_frames) + } + + /// The number of frames longer than the maximum length for + /// the communications length device + #[must_use] + pub fn rx_oversize_frames(&self) -> Option { + self.to_option(self.rx_oversize_frames) + } + + /// The number of valid frames that were dropped because + /// the receive buffers were full + #[must_use] + pub fn rx_dropped_frames(&self) -> Option { + self.to_option(self.rx_dropped_frames) + } + + /// The number of valid unicast frames received and not dropped + #[must_use] + pub fn rx_unicast_frames(&self) -> Option { + self.to_option(self.rx_unicast_frames) + } + + /// The number of valid broadcast frames received and not dropped + #[must_use] + pub fn rx_broadcast_frames(&self) -> Option { + self.to_option(self.rx_broadcast_frames) + } + + /// The number of valid multicast frames received and not dropped + #[must_use] + pub fn rx_multicast_frames(&self) -> Option { + self.to_option(self.rx_multicast_frames) + } + + /// Number of frames with CRC or alignment errors + #[must_use] + pub fn rx_crc_error_frames(&self) -> Option { + self.to_option(self.rx_crc_error_frames) + } + + /// The total number of bytes received including frames with errors + /// and dropped frames + #[must_use] + pub fn rx_total_bytes(&self) -> Option { + self.to_option(self.rx_total_bytes) + } + + /// The total number of frames transmitted including frames + /// with errors and dropped frames + #[must_use] + pub fn tx_total_frames(&self) -> Option { + self.to_option(self.tx_total_frames) + } + + /// The total number of valid frames transmitted and copied + /// into receive buffers + #[must_use] + pub fn tx_good_frames(&self) -> Option { + self.to_option(self.tx_good_frames) + } + + /// The number of frames below the minimum length for + /// the media. This would be less than 64 for Ethernet + #[must_use] + pub fn tx_undersize_frames(&self) -> Option { + self.to_option(self.tx_undersize_frames) + } + + /// The number of frames longer than the maximum length for + /// the media. This would be 1500 for Ethernet + #[must_use] + pub fn tx_oversize_frames(&self) -> Option { + self.to_option(self.tx_oversize_frames) + } + + /// The number of valid frames that were dropped because + /// received buffers were full + #[must_use] + pub fn tx_dropped_frames(&self) -> Option { + self.to_option(self.tx_dropped_frames) + } + + /// The number of valid unicast frames transmitted and not + /// dropped + #[must_use] + pub fn tx_unicast_frames(&self) -> Option { + self.to_option(self.tx_unicast_frames) + } + + /// The number of valid broadcast frames transmitted and + /// not dropped + #[must_use] + pub fn tx_broadcast_frames(&self) -> Option { + self.to_option(self.tx_broadcast_frames) + } + + /// The number of valid multicast frames transmitted + /// and not dropped + #[must_use] + pub fn tx_multicast_frames(&self) -> Option { + self.to_option(self.tx_multicast_frames) + } + + /// The number of transmitted frames with CRC or + /// alignment errors + #[must_use] + pub fn tx_crc_error_frames(&self) -> Option { + self.to_option(self.tx_crc_error_frames) + } + + /// The total number of bytes transmitted including + /// error frames and dropped frames + #[must_use] + pub fn tx_total_bytes(&self) -> Option { + self.to_option(self.tx_total_bytes) + } + + /// The number of collisions detected on this subnet + #[must_use] + pub fn collisions(&self) -> Option { + self.to_option(self.collisions) + } + + /// The number of frames destined for unsupported protocol + #[must_use] + pub fn unsupported_protocol(&self) -> Option { + self.to_option(self.unsupported_protocol) + } + + /// The number of valid frames received that were duplicated + #[must_use] + pub fn rx_duplicated_frames(&self) -> Option { + self.to_option(self.rx_duplicated_frames) + } + + /// The number of encrypted frames received that failed + /// to decrypt + #[must_use] + pub fn rx_decrypt_error_frames(&self) -> Option { + self.to_option(self.rx_decrypt_error_frames) + } + + /// The number of frames that failed to transmit after + /// exceeding the retry limit + #[must_use] + pub fn tx_error_frames(&self) -> Option { + self.to_option(self.tx_error_frames) + } + + /// The number of frames that transmitted successfully + /// after more than one attempt + #[must_use] + pub fn tx_retry_frames(&self) -> Option { + self.to_option(self.tx_retry_frames) + } +} + +/// The Simple Network Mode +#[repr(C)] +pub struct NetworkMode { + /// Reports the current state of the network interface + pub state: NetworkState, + /// The size of the network interface's hardware address in bytes + pub hw_address_size: u32, + /// The size of the network interface's media header in bytes + pub media_header_size: u32, + /// The maximum size of the packets supported by the network interface in bytes + pub max_packet_size: u32, + /// The size of the NVRAM device attached to the network interface in bytes + pub nv_ram_size: u32, + /// The size that must be used for all NVRAM reads and writes + pub nv_ram_access_size: u32, + /// The multicast receive filter settings supported by the network interface + pub receive_filter_mask: u32, + /// The current multicast receive filter settings + pub receive_filter_setting: u32, + /// The maximum number of multicast address receive filters supported by the driver + pub max_mcast_filter_count: u32, + /// The current number of multicast address receive filters + pub mcast_filter_count: u32, + /// The array containing the addresses of the current multicast address receive filters + pub mcast_filter: [MacAddress; 16], + /// The current hardware MAC address for the network interface + pub current_address: MacAddress, + /// The current hardware MAC address for broadcast packets + pub broadcast_address: MacAddress, + /// The permanent hardware MAC address for the network interface + pub permanent_address: MacAddress, + /// The interface type of the network interface + pub if_type: u8, + /// Tells if the MAC address can be changed + pub mac_address_changeable: bool, + /// Tells if the network interface can transmit more than one packet at a time + pub multiple_tx_supported: bool, + /// Tells if the presence of the media can be determined + pub media_present_supported: bool, + /// Tells if media are connected to the network interface + pub media_present: bool, +} + +newtype_enum! { + /// The state of a network interface + pub enum NetworkState: u32 => { + /// The interface has been stopped + STOPPED = 0, + /// The interface has been started + STARTED = 1, + /// The interface has been initialized + INITIALIZED = 2, + /// No state can have a number higher than this + MAX_STATE = 4, + } +} diff --git a/xtask/src/util.rs b/xtask/src/util.rs index 747eee28d..33b8f3c08 100644 --- a/xtask/src/util.rs +++ b/xtask/src/util.rs @@ -51,7 +51,7 @@ mod tests { #[test] fn test_command_to_string() { let mut cmd = Command::new("MyCommand"); - cmd.args(&["abc", "123"]).envs([ + cmd.args(["abc", "123"]).envs([ ("VAR1", "val1"), ("VAR2", "val2"), ("PATH", "pathval"),