From 6d5e1e0c552367dbca6d0220d1076422378df114 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Wed, 27 Apr 2022 19:47:16 +0200 Subject: [PATCH 01/14] add PXE Base Code protocol --- src/proto/mod.rs | 1 + src/proto/network/mod.rs | 32 + src/proto/network/pxe.rs | 1273 ++++++++++++++++++++++++ uefi-test-runner/src/proto/mod.rs | 2 + uefi-test-runner/src/proto/network.rs | 116 +++ uefi-test-runner/tftp/example-file.txt | 1 + xtask/src/main.rs | 1 + xtask/src/net.rs | 30 + xtask/src/qemu.rs | 8 + 9 files changed, 1464 insertions(+) create mode 100644 src/proto/network/mod.rs create mode 100644 src/proto/network/pxe.rs create mode 100644 uefi-test-runner/src/proto/network.rs create mode 100644 uefi-test-runner/tftp/example-file.txt create mode 100644 xtask/src/net.rs diff --git a/src/proto/mod.rs b/src/proto/mod.rs index a87b7bf8e..5eb43f118 100644 --- a/src/proto/mod.rs +++ b/src/proto/mod.rs @@ -69,6 +69,7 @@ pub mod debug; pub mod device_path; pub mod loaded_image; pub mod media; +pub mod network; pub mod pi; pub mod rng; pub mod shim; diff --git a/src/proto/network/mod.rs b/src/proto/network/mod.rs new file mode 100644 index 000000000..ca637c139 --- /dev/null +++ b/src/proto/network/mod.rs @@ -0,0 +1,32 @@ +//! Network access protocols. +//! +//! These protocols can be used to interact with network resources. + +pub mod pxe; + +/// EFI_IP_ADDRESS +#[derive(Clone, Copy)] +#[repr(C, align(4))] +pub struct IpAddress(pub [u8; 16]); + +impl IpAddress { + /// Construct a new IPv4 address. + pub const fn new_v4(ip_addr: [u8; 4]) -> Self { + let mut buffer = [0; 16]; + buffer[0] = ip_addr[0]; + buffer[1] = ip_addr[1]; + buffer[2] = ip_addr[2]; + buffer[3] = ip_addr[3]; + Self(buffer) + } + + /// Construct a new IPv6 address. + pub const fn new_v6(ip_addr: [u8; 16]) -> Self { + Self(ip_addr) + } +} + +/// EFI_MAC_ADDRESS +#[derive(Clone, Copy)] +#[repr(C)] +pub struct MacAddr(pub [u8; 32]); diff --git a/src/proto/network/pxe.rs b/src/proto/network/pxe.rs new file mode 100644 index 000000000..84e212999 --- /dev/null +++ b/src/proto/network/pxe.rs @@ -0,0 +1,1273 @@ +//! PXE Base Code protocol. + +use core::{ffi::c_void, iter::from_fn, ptr::NonNull}; + +use bitflags::bitflags; +use uefi_macros::{unsafe_guid, Protocol}; + +use crate::{CStr8, Char8, Result, Status}; + +use super::{IpAddress, MacAddr}; + +/// PXE Base Code protocol +#[repr(C)] +#[unsafe_guid("03c4e603-ac28-11d3-9a2d-0090273fc14d")] +#[derive(Protocol)] +#[allow(clippy::type_complexity)] +pub struct BaseCode { + revision: u64, + start: extern "efiapi" fn(this: &Self, use_ipv6: bool) -> Status, + stop: extern "efiapi" fn(this: &Self) -> Status, + dhcp: extern "efiapi" fn(this: &Self, sort_offers: bool) -> Status, + discover: extern "efiapi" fn( + this: &Self, + ty: BootstrapType, + layer: &mut u16, + use_bis: bool, + info: Option<*const DiscoverInfo<[Server; 0]>>, + ) -> Status, + mtftp: unsafe extern "efiapi" fn( + this: &Self, + operation: TftpOpcode, + buffer: Option>, + overwrite: bool, + buffer_size: &mut u64, + block_size: Option<&usize>, + server_ip: &IpAddress, + filename: Option>, + info: Option<&MtftpInfo>, + dont_use_buffer: bool, + ) -> Status, + udp_write: unsafe extern "efiapi" fn( + this: &Self, + op_flags: UdpOpFlags, + dest_ip: &IpAddress, + dest_port: &u16, + gateway_ip: Option<&IpAddress>, + src_ip: Option<&IpAddress>, + src_port: Option<&mut u16>, + header_size: Option<&usize>, + header_ptr: Option>, + buffer_size: &usize, + buffer_ptr: NonNull, + ) -> Status, + udp_read: unsafe extern "efiapi" fn( + this: &Self, + op_flags: UdpOpFlags, + dest_ip: Option<&mut IpAddress>, + dest_port: Option<&mut u16>, + src_ip: Option<&mut IpAddress>, + src_port: Option<&mut u16>, + header_size: Option<&usize>, + header_ptr: Option>, + buffer_size: &mut usize, + buffer_ptr: NonNull, + ) -> Status, + set_ip_filter: extern "efiapi" fn(this: &Self, new_filter: &IpFilter) -> Status, + arp: extern "efiapi" fn( + this: &Self, + ip_addr: &IpAddress, + mac_addr: Option<&mut MacAddr>, + ) -> Status, + set_parameters: extern "efiapi" fn( + this: &Self, + new_auto_arp: Option<&bool>, + new_send_guid: Option<&bool>, + new_ttl: Option<&u8>, + new_tos: Option<&u8>, + new_make_callback: Option<&bool>, + ) -> Status, + set_station_ip: extern "efiapi" fn( + this: &Self, + new_station_ip: Option<&IpAddress>, + new_subnet_mask: Option<&IpAddress>, + ) -> Status, + set_packets: extern "efiapi" fn( + this: &Self, + new_dhcp_discover_valid: Option<&bool>, + new_dhcp_ack_received: Option<&bool>, + new_proxy_offer_received: Option<&bool>, + new_pxe_discover_valid: Option<&bool>, + new_pxe_reply_received: Option<&bool>, + new_pxe_bis_reply_received: Option<&bool>, + new_dhcp_discover: Option<&Packet>, + new_dhcp_ack: Option<&Packet>, + new_proxy_offer: Option<&Packet>, + new_pxe_discover: Option<&Packet>, + new_pxe_reply: Option<&Packet>, + new_pxe_bis_reply: Option<&Packet>, + ) -> Status, + mode: NonNull, +} + +impl BaseCode { + /// Enables the use of the PXE Base Code Protocol functions. + pub fn start(&mut self, use_ipv6: bool) -> Result { + (self.start)(self, use_ipv6).into() + } + + /// Disables the use of the PXE Base Code Protocol functions. + pub fn stop(&mut self) -> Result { + (self.stop)(self).into() + } + + /// Attempts to complete a DHCPv4 D.O.R.A. (discover / offer / request / + /// acknowledge) or DHCPv6 S.A.R.R (solicit / advertise / request / reply) sequence. + pub fn dhcp(&mut self, sort_offers: bool) -> Result { + (self.dhcp)(self, sort_offers).into() + } + + /// Attempts to complete the PXE Boot Server and/or boot image discovery + /// sequence. + pub fn discover( + &mut self, + ty: BootstrapType, + layer: &mut u16, + use_bis: bool, + info: Option<&DiscoverInfo<[Server]>>, + ) -> Result { + (self.discover)( + self, + ty, + layer, + use_bis, + info.map(|info| info as *const DiscoverInfo<[Server]> as *const _), + ) + .into() + } + + /// Returns the size of a file located on a TFTP server. + pub fn tftp_get_file_size(&mut self, server_ip: &IpAddress, filename: &CStr8) -> Result { + let mut buffer_size = 0; + let filename = NonNull::from(&filename.to_bytes_with_nul()[0]).cast(); + + let status = unsafe { + (self.mtftp)( + self, + TftpOpcode::TftpGetFileSize, + None, + false, + &mut buffer_size, + None, + server_ip, + Some(filename), + None, + false, + ) + }; + Result::from(status)?; + + Ok(buffer_size) + } + + /// Reads a file located on a TFTP server. + pub fn tftp_read_file( + &mut self, + server_ip: &IpAddress, + filename: &CStr8, + buffer: Option<&mut [u8]>, + ) -> Result { + let filename = NonNull::from(&filename.to_bytes_with_nul()[0]).cast(); + + let (buffer_ptr, mut buffer_size, dont_use_buffer) = if let Some(buffer) = buffer { + let buffer_ptr = NonNull::from(&mut buffer[0]).cast::(); + let buffer_size = u64::try_from(buffer.len()).unwrap(); + (Some(buffer_ptr), buffer_size, false) + } else { + (None, 0, true) + }; + + let status = unsafe { + (self.mtftp)( + self, + TftpOpcode::TftpReadFile, + buffer_ptr, + false, + &mut buffer_size, + None, + server_ip, + Some(filename), + None, + dont_use_buffer, + ) + }; + Result::from(status)?; + + Ok(buffer_size) + } + + /// Writes to a file located on a TFTP server. + pub fn tftp_write_file( + &mut self, + server_ip: &IpAddress, + filename: &CStr8, + overwrite: bool, + buffer: &[u8], + ) -> Result { + let filename = NonNull::from(&filename.to_bytes_with_nul()[0]).cast(); + + let buffer_ptr = NonNull::from(&buffer[0]).cast(); + let mut buffer_size = u64::try_from(buffer.len()).expect("buffer length should fit in u64"); + + unsafe { + (self.mtftp)( + self, + TftpOpcode::TftpWriteFile, + Some(buffer_ptr), + overwrite, + &mut buffer_size, + None, + server_ip, + Some(filename), + None, + false, + ) + } + .into() + } + + /// Reads a directory listing of a directory on a TFTP server. + pub fn tftp_read_dir<'a>( + &self, + server_ip: &IpAddress, + directory_name: &CStr8, + buffer: &'a mut [u8], + ) -> Result> + 'a> { + let filename = NonNull::from(&directory_name.to_bytes_with_nul()[0]).cast(); + + let buffer_ptr = NonNull::from(&buffer[0]).cast(); + let mut buffer_size = u64::try_from(buffer.len()).expect("buffer length should fit in u64"); + + let status = unsafe { + (self.mtftp)( + self, + TftpOpcode::TftpReadDirectory, + Some(buffer_ptr), + false, + &mut buffer_size, + None, + server_ip, + Some(filename), + None, + false, + ) + }; + Result::from(status)?; + + let buffer_size = usize::try_from(buffer_size).expect("buffer length should fit in usize"); + let buffer = &buffer[..buffer_size]; + + let mut iterator = buffer.split_inclusive(|b| *b == 0); + + Ok(from_fn(move || { + let filename = iterator + .next() + .expect("the final entry should have an empty file name"); + if filename == [0] { + // This is the final entry. + return None; + } + let filename = CStr8::from_bytes_with_nul(filename).unwrap(); + + let information_string = iterator + .next() + .expect("each file should have an information string"); + let (_null_terminator, information_string) = information_string.split_last().unwrap(); + let information_string = core::str::from_utf8(information_string) + .expect("the information string should be valid utf-8"); + + let (size, rest) = information_string + .split_once(' ') + .expect("the information string should be valid"); + let (year, rest) = rest + .split_once('-') + .expect("the information string should be valid"); + let (month, rest) = rest + .split_once('-') + .expect("the information string should be valid"); + let (day, rest) = rest + .split_once(' ') + .expect("the information string should be valid"); + let (hour, rest) = rest + .split_once(':') + .expect("the information string should be valid"); + let (minute, second) = rest + .split_once(':') + .expect("the information string should be valid"); + + let size = size.parse().expect("size should be a number"); + let year = year.parse().expect("year should be a number"); + let month = month.parse().expect("month should be a number"); + let day = day.parse().expect("day should be a number"); + let hour = hour.parse().expect("hour should be a number"); + let minute = minute.parse().expect("minute should be a number"); + let second = second.parse().expect("second should be a number"); + + Some(TftpFileInfo { + filename, + size, + year, + month, + day, + hour, + minute, + second, + }) + }) + .fuse()) + } + + /// Returns the size of a file located on a MTFTP server. + pub fn mtftp_get_file_size( + &mut self, + server_ip: &IpAddress, + filename: &CStr8, + info: &MtftpInfo, + ) -> Result { + let mut buffer_size = 0; + let filename = NonNull::from(&filename.to_bytes_with_nul()[0]).cast(); + + let status = unsafe { + (self.mtftp)( + self, + TftpOpcode::MtftpGetFileSize, + None, + false, + &mut buffer_size, + None, + server_ip, + Some(filename), + Some(info), + false, + ) + }; + Result::from(status)?; + + Ok(buffer_size) + } + + /// Reads a file located on a MTFTP server. + pub fn mtftp_read_file( + &mut self, + server_ip: &IpAddress, + filename: &CStr8, + buffer: Option<&mut [u8]>, + info: &MtftpInfo, + ) -> Result { + let filename = NonNull::from(&filename.to_bytes_with_nul()[0]).cast(); + + let (buffer_ptr, mut buffer_size, dont_use_buffer) = if let Some(buffer) = buffer { + let buffer_ptr = NonNull::from(&mut buffer[0]).cast::(); + let buffer_size = u64::try_from(buffer.len()).unwrap(); + (Some(buffer_ptr), buffer_size, false) + } else { + (None, 0, true) + }; + + let status = unsafe { + (self.mtftp)( + self, + TftpOpcode::MtftpReadFile, + buffer_ptr, + false, + &mut buffer_size, + None, + server_ip, + Some(filename), + Some(info), + dont_use_buffer, + ) + }; + Result::from(status)?; + + Ok(buffer_size) + } + + /// Reads a directory listing of a directory on a MTFTP server. + pub fn mtftp_read_dir<'a>( + &self, + server_ip: &IpAddress, + buffer: &'a mut [u8], + info: &MtftpInfo, + ) -> Result> + 'a> { + let buffer_ptr = NonNull::from(&buffer[0]).cast(); + let mut buffer_size = u64::try_from(buffer.len()).expect("buffer length should fit in u64"); + + let status = unsafe { + (self.mtftp)( + self, + TftpOpcode::MtftpReadDirectory, + Some(buffer_ptr), + false, + &mut buffer_size, + None, + server_ip, + None, + Some(info), + false, + ) + }; + Result::from(status)?; + + let buffer_size = usize::try_from(buffer_size).expect("buffer length should fit in usize"); + let buffer = &buffer[..buffer_size]; + + let mut iterator = buffer.split_inclusive(|b| *b == 0); + + Ok(from_fn(move || { + let filename = iterator + .next() + .expect("the final entry should have an empty file name"); + if filename == [0] { + // This is the final entry. + return None; + } + let filename = CStr8::from_bytes_with_nul(filename).unwrap(); + + let multicast_ip = iterator + .next() + .expect("each file should have a multicast ip address"); + let (_null_terminator, multicast_ip) = multicast_ip.split_last().unwrap(); + let multicast_ip = core::str::from_utf8(multicast_ip) + .expect("the multicast ip address should be valid utf-8"); + let mut octets = multicast_ip.split('.'); + let mut buffer = [0; 4]; + for b in buffer.iter_mut() { + let octet = octets + .next() + .expect("the information string should be valid"); + let octet = octet + .parse() + .expect("each octet in the ip address should be a number"); + *b = octet; + } + let ip_address = IpAddress::new_v4(buffer); + + let information_string = iterator + .next() + .expect("each file should have an information string"); + let (_null_terminator, information_string) = information_string.split_last().unwrap(); + let information_string = core::str::from_utf8(information_string) + .expect("the information string should be valid utf-8"); + + let (size, rest) = information_string + .split_once(' ') + .expect("the information string should be valid"); + let (year, rest) = rest + .split_once('-') + .expect("the information string should be valid"); + let (month, rest) = rest + .split_once('-') + .expect("the information string should be valid"); + let (day, rest) = rest + .split_once(' ') + .expect("the information string should be valid"); + let (hour, rest) = rest + .split_once(':') + .expect("the information string should be valid"); + let (minute, second) = rest + .split_once(':') + .expect("the information string should be valid"); + + let size = size.parse().expect("size should be a number"); + let year = year.parse().expect("year should be a number"); + let month = month.parse().expect("month should be a number"); + let day = day.parse().expect("day should be a number"); + let hour = hour.parse().expect("hour should be a number"); + let minute = minute.parse().expect("minute should be a number"); + let second = second.parse().expect("second should be a number"); + + Some(MtftpFileInfo { + filename, + ip_address, + size, + year, + month, + day, + hour, + minute, + second, + }) + }) + .fuse()) + } + + /// Writes a UDP packet to the network interface. + #[allow(clippy::too_many_arguments)] + pub fn udp_write( + &mut self, + op_flags: UdpOpFlags, + dest_ip: &IpAddress, + dest_port: u16, + gateway_ip: Option<&IpAddress>, + src_ip: Option<&IpAddress>, + src_port: Option<&mut u16>, + header: Option<&[u8]>, + buffer: &[u8], + ) -> Result { + let header_size_tmp; + let (header_size, header_ptr) = if let Some(header) = header { + header_size_tmp = header.len(); + ( + Some(&header_size_tmp), + Some(NonNull::from(&header[0]).cast()), + ) + } else { + (None, None) + }; + + unsafe { + (self.udp_write)( + self, + op_flags, + dest_ip, + &dest_port, + gateway_ip, + src_ip, + src_port, + header_size, + header_ptr, + &buffer.len(), + NonNull::from(&buffer[0]).cast(), + ) + } + .into() + } + + /// Reads a UDP packet from the network interface. + #[allow(clippy::too_many_arguments)] + pub fn udp_read( + &mut self, + op_flags: UdpOpFlags, + dest_ip: Option<&mut IpAddress>, + dest_port: Option<&mut u16>, + src_ip: Option<&mut IpAddress>, + src_port: Option<&mut u16>, + header: Option<&mut [u8]>, + buffer: &mut [u8], + ) -> Result { + let header_size_tmp; + let (header_size, header_ptr) = if let Some(header) = header { + header_size_tmp = header.len(); + ( + Some(&header_size_tmp), + Some(NonNull::from(&mut header[0]).cast()), + ) + } else { + (None, None) + }; + + let mut buffer_size = buffer.len(); + + let status = unsafe { + (self.udp_read)( + self, + op_flags, + dest_ip, + dest_port, + src_ip, + src_port, + header_size, + header_ptr, + &mut buffer_size, + NonNull::from(&buffer[0]).cast(), + ) + }; + Result::from(status)?; + + Ok(buffer_size) + } + + /// Updates the IP receive filters of a network device and enables software + /// filtering. + pub fn set_ip_filter(&mut self, new_filter: &IpFilter) -> Result { + (self.set_ip_filter)(self, new_filter).into() + } + + /// Uses the ARP protocol to resolve a MAC address. + pub fn arp(&mut self, ip_addr: &IpAddress, mac_addr: Option<&mut MacAddr>) -> Result { + (self.arp)(self, ip_addr, mac_addr).into() + } + + /// Updates the parameters that affect the operation of the PXE Base Code + /// Protocol. + pub fn set_parameters( + &mut self, + new_auto_arp: Option, + new_send_guid: Option, + new_ttl: Option, + new_tos: Option, + new_make_callback: Option, + ) -> Result { + (self.set_parameters)( + self, + new_auto_arp.as_ref(), + new_send_guid.as_ref(), + new_ttl.as_ref(), + new_tos.as_ref(), + new_make_callback.as_ref(), + ) + .into() + } + + /// Updates the station IP address and/or subnet mask values of a network + /// device. + pub fn set_station_ip( + &mut self, + new_station_ip: Option<&IpAddress>, + new_subnet_mask: Option<&IpAddress>, + ) -> Result { + (self.set_station_ip)(self, new_station_ip, new_subnet_mask).into() + } + + /// Updates the contents of the cached DHCP and Discover packets. + #[allow(clippy::too_many_arguments)] + pub fn set_packets( + &mut self, + new_dhcp_discover_valid: Option, + new_dhcp_ack_received: Option, + new_proxy_offer_received: Option, + new_pxe_discover_valid: Option, + new_pxe_reply_received: Option, + new_pxe_bis_reply_received: Option, + new_dhcp_discover: Option<&Packet>, + new_dhcp_ack: Option<&Packet>, + new_proxy_offer: Option<&Packet>, + new_pxe_discover: Option<&Packet>, + new_pxe_reply: Option<&Packet>, + new_pxe_bis_reply: Option<&Packet>, + ) -> Result { + (self.set_packets)( + self, + new_dhcp_discover_valid.as_ref(), + new_dhcp_ack_received.as_ref(), + new_proxy_offer_received.as_ref(), + new_pxe_discover_valid.as_ref(), + new_pxe_reply_received.as_ref(), + new_pxe_bis_reply_received.as_ref(), + new_dhcp_discover, + new_dhcp_ack, + new_proxy_offer, + new_pxe_discover, + new_pxe_reply, + new_pxe_bis_reply, + ) + .into() + } + + /// Returns a reference to the `Mode` struct. + pub fn mode(&self) -> &Mode { + unsafe { self.mode.as_ref() } + } +} + +// EFI_PXE_BASE_CODE_BOOT_* +#[repr(u16)] +#[allow(missing_docs)] +pub enum BootstrapType { + Bootstrap = 0, + MsWinntRis = 1, + IntelLcm = 2, + DosUndi = 3, + NecEsmpro = 4, + IbmWsoD = 5, + IbmLccm = 6, + CaUnicenterTng = 7, + HpOpenview = 8, + Altiris9 = 9, + Altiris10 = 10, + Altiris11 = 11, + // NOT_USED_12 = 12, + RedhatInstall = 13, + RedhatBoot = 14, + Rembo = 15, + Beoboot = 16, + // + // Values 17 through 32767 are reserved. + // Values 32768 through 65279 are for vendor use. + // Values 65280 through 65534 are reserved. + // + PxeTest = 65535, +} + +/// This struct contains optional parameters for [`BaseCode::discover`]. +// EFI_PXE_BASE_CODE_DISCOVER_INFO +#[repr(C)] +pub struct DiscoverInfo { + use_m_cast: bool, + use_b_cast: bool, + use_u_cast: bool, + must_use_list: bool, + server_m_cast_ip: IpAddress, + ip_cnt: u16, + srv_list: T, +} + +impl DiscoverInfo<[Server; N]> { + /// Create a `DiscoverInfo`. + pub const fn new( + use_m_cast: bool, + use_b_cast: bool, + use_u_cast: bool, + must_use_list: bool, + server_m_cast_ip: IpAddress, + srv_list: [Server; N], + ) -> Self { + assert!(N <= u16::MAX as usize, "too many servers"); + let ip_cnt = N as u16; + Self { + use_m_cast, + use_b_cast, + use_u_cast, + must_use_list, + server_m_cast_ip, + ip_cnt, + srv_list, + } + } +} + +impl DiscoverInfo { + /// Returns whether discovery should use multicast. + pub fn use_m_cast(&self) -> bool { + self.use_m_cast + } + + /// Returns whether discovery should use broadcast. + pub fn use_b_cast(&self) -> bool { + self.use_b_cast + } + + /// Returns whether discovery should use unicast. + pub fn use_u_cast(&self) -> bool { + self.use_u_cast + } + + /// Returns whether discovery should only accept boot servers in the server + /// list (boot server verification). + pub fn must_use_list(&self) -> bool { + self.must_use_list + } + + /// Returns the address used in multicast discovery. + pub fn server_m_cast_ip(&self) -> &IpAddress { + &self.server_m_cast_ip + } + + /// Returns the amount of Boot Server. + pub fn ip_cnt(&self) -> u16 { + self.ip_cnt + } + + /// Returns the Boot Server list used for unicast discovery or boot server + /// verification. + pub fn srv_list(&self) -> &T { + &self.srv_list + } +} + +/// An entry in the Boot Server list +// EFI_PXE_BASE_CODE_SRVLIST +#[repr(C)] +pub struct Server { + /// The type of Boot Server reply + pub ty: u16, + accept_any_response: bool, + _reserved: u8, + /// The ip address of the server + ip_addr: IpAddress, +} + +impl Server { + /// Construct a `Server` for a Boot Server reply type. If `ip_addr` is not + /// `None` only Boot Server replies with matching the ip address will be + /// accepted. + pub fn new(ty: u16, ip_addr: Option) -> Self { + Self { + ty, + accept_any_response: ip_addr.is_none(), + _reserved: 0, + ip_addr: ip_addr.unwrap_or(IpAddress([0; 16])), + } + } + + /// Returns a `None` if the any response should be accepted or the ip + /// address of a Boot Server whose responses should be accepted. + pub fn ip_addr(&self) -> Option<&IpAddress> { + if self.accept_any_response { + None + } else { + Some(&self.ip_addr) + } + } +} + +// EFI_PXE_BASE_CODE_TFTP_OPCODE +#[repr(C)] +enum TftpOpcode { + TftpGetFileSize = 1, + TftpReadFile, + TftpWriteFile, + TftpReadDirectory, + MtftpGetFileSize, + MtftpReadFile, + MtftpReadDirectory, +} + +/// MTFTP connection parameters +// EFI_PXE_BASE_CODE_MTFTP_INFO +#[derive(Clone, Copy)] +#[repr(C)] +pub struct MtftpInfo { + /// File multicast IP address. This is the IP address to which the server + /// will send the requested file. + pub m_cast_ip: IpAddress, + /// Client multicast listening port. This is the UDP port to which the + /// server will send the requested file. + pub c_port: u16, + /// Server multicast listening port. This is the UDP port on which the + /// server listens for multicast open requests and data acks. + pub s_port: u16, + /// The number of seconds a client should listen for an active multicast + /// session before requesting a new multicast session. + pub listen_timeout: u16, + /// The number of seconds a client should wait for a packet from the server + /// before retransmitting the previous open request or data ack packet. + pub transmit_timeout: u16, +} + +// No corresponding type in the UEFI spec, it just uses UINT16. +bitflags! { + /// Flags for UDP read and write operations. + #[repr(transparent)] + pub struct UdpOpFlags: u16 { + /// Receive a packet sent from any IP address in UDP read operations. + const ANY_SRC_IP = 0x0001; + /// Receive a packet sent from any UDP port in UDP read operations. If + /// the source port is no specified in UDP write operations, the + /// source port will be automatically selected. + const ANY_SRC_PORT = 0x0002; + /// Receive a packet sent to any IP address in UDP read operations. + const ANY_DEST_IP = 0x0004; + /// Receive a packet sent to any UDP port in UDP read operations. + const ANY_DEST_PORT = 0x0008; + /// The software filter is used in UDP read operations. + const USE_FILTER = 0x0010; + /// If required, a UDP write operation may be broken up across multiple packets. + const MAY_FRAGMENT = 0x0020; + } +} + +/// IP receive filter settings +// EFI_PXE_BASE_CODE_IP_FILTER +#[repr(C)] +pub struct IpFilter { + /// A set of filters. + pub filters: IpFilters, + ip_cnt: u8, + _reserved: u16, + ip_list: [IpAddress; 8], +} + +impl IpFilter { + /// Construct a new `IpFilter`. + /// + /// # Panics + /// + /// Panics if `ip_list` contains more than 8 entries. + pub fn new(filters: IpFilters, ip_list: &[IpAddress]) -> Self { + assert!(ip_list.len() <= 8); + + let ip_cnt = ip_list.len() as u8; + let mut buffer = [IpAddress([0; 16]); 8]; + buffer[..ip_list.len()].copy_from_slice(ip_list); + + Self { + filters, + ip_cnt, + _reserved: 0, + ip_list: buffer, + } + } + + /// A list of ip addresses other than the Station Ip that should be + /// enabled. Maybe be multicast or unicast. + pub fn ip_list(&self) -> &[IpAddress] { + &self.ip_list[..usize::from(self.ip_cnt)] + } +} + +bitflags! { + /// IP receive filters. + #[repr(transparent)] + pub struct IpFilters: u8 { + /// Enable the Station ip address. + const STATION_IP = 0x01; + /// Enable IPv4 broadcast addresses. + const BROADCAST = 0x02; + /// Enable all addresses. + const PROMISCUOUS = 0x04; + /// Enable all multicast addresses. + const PROMISCUOUS_MULTICAST = 0x08; + } +} + +/// A network packet. +// EFI_PXE_BASE_CODE_PACKET +#[repr(C)] +pub union Packet { + raw: [u8; 1472], + dhcpv4: DhcpV4Packet, + dhcpv6: DhcpV6Packet, +} + +impl AsRef<[u8; 1472]> for Packet { + fn as_ref(&self) -> &[u8; 1472] { + unsafe { &self.raw } + } +} + +impl AsRef for Packet { + fn as_ref(&self) -> &DhcpV4Packet { + unsafe { &self.dhcpv4 } + } +} + +impl AsRef for Packet { + fn as_ref(&self) -> &DhcpV6Packet { + unsafe { &self.dhcpv6 } + } +} + +/// A Dhcpv4 Packet. +// EFI_PXE_BASE_CODE_DHCPV4_PACKET +#[repr(C)] +#[derive(Clone, Copy)] +pub struct DhcpV4Packet { + /// Packet op code / message type. + pub bootp_opcode: u8, + /// Hardware address type. + pub bootp_hw_type: u8, + /// Hardware address length. + pub bootp_hw_addr_len: u8, + /// Client sets to zero, optionally used by gateways in cross-gateway booting. + pub bootp_gate_hops: u8, + bootp_ident: u32, + bootp_seconds: u16, + bootp_flags: u16, + /// Client IP address, filled in by client in bootrequest if known. + pub bootp_ci_addr: [u8; 4], + /// 'your' (client) IP address; filled by server if client doesn't know its own address (`bootp_ci_addr` was 0). + pub bootp_yi_addr: [u8; 4], + /// Server IP address, returned in bootreply by server. + pub bootp_si_addr: [u8; 4], + /// Gateway IP address, used in optional cross-gateway booting. + pub bootp_gi_addr: [u8; 4], + /// Client hardware address, filled in by client. + pub bootp_hw_addr: [u8; 16], + /// Optional server host name, null terminated string. + pub bootp_srv_name: [u8; 64], + /// Boot file name, null terminated string, 'generic' name or null in + /// bootrequest, fully qualified directory-path name in bootreply. + pub bootp_boot_file: [u8; 128], + dhcp_magik: u32, + /// Optional vendor-specific area, e.g. could be hardware type/serial on request, or 'capability' / remote file system handle on reply. This info may be set aside for use by a third phase bootstrap or kernel. + pub dhcp_options: [u8; 56], +} + +impl DhcpV4Packet { + /// Transaction ID, a random number, used to match this boot request with the responses it generates. + pub fn bootp_ident(&self) -> u32 { + u32::from_be(self.bootp_ident) + } + + /// Filled in by client, seconds elapsed since client started trying to boot. + pub fn bootp_seconds(&self) -> u16 { + u16::from_be(self.bootp_seconds) + } + + /// The flags. + pub fn bootp_flags(&self) -> DhcpV4Flags { + DhcpV4Flags::from_bits_truncate(u16::from_be(self.bootp_flags)) + } + + /// A magic cookie, should be `0x63825363`. + pub fn dhcp_magik(&self) -> u32 { + u32::from_be(self.dhcp_magik) + } +} + +bitflags! { + /// Represents the 'flags' field for a [`DhcpV4Packet`]. + pub struct DhcpV4Flags: u16 { + /// Should be set when the client cannot receive unicast IP datagrams + /// until its protocol software has been configured with an IP address. + const BROADCAST = 1; + } +} + +/// A Dhcpv6 Packet. +// EFI_PXE_BASE_CODE_DHCPV6_PACKET +#[repr(C)] +#[derive(Clone, Copy)] +pub struct DhcpV6Packet { + /// The message type. + pub message_type: u8, + transaction_id: [u8; 3], + /// A byte array containing dhcp options. + pub dhcp_options: [u8; 1024], +} + +impl DhcpV6Packet { + /// The transaction id. + pub fn transaction_id(&self) -> u32 { + u32::from(self.transaction_id[0]) << 16 + | u32::from(self.transaction_id[1]) << 8 + | u32::from(self.transaction_id[2]) + } +} + +/// The data values in this structure are read-only and are updated by the +/// [`BaseCode`]. +// EFI_PXE_BASE_CODE_MODE +#[repr(C)] +pub struct Mode { + /// `true` if this device has been started by calling [`BaseCode::start`]. + /// This field is set to `true` by [`BaseCode::start`] and to `false` by + /// the [`BaseCode::stop`] function. + pub started: bool, + /// `true` if the UNDI protocol supports IPv6 + pub ipv6_available: bool, + /// `true` if this PXE Base Code Protocol implementation supports IPv6. + pub ipv6_supported: bool, + /// `true` if this device is currently using IPv6. This field is set by + /// [`BaseCode::start`]. + pub using_ipv6: bool, + /// `true` if this PXE Base Code implementation supports Boot Integrity + /// Services (BIS). This field is set by [`BaseCode::start`]. + pub bis_supported: bool, + /// `true` if this device and the platform support Boot Integrity Services + /// (BIS). This field is set by [`BaseCode::start`]. + pub bis_detected: bool, + /// `true` for automatic ARP packet generation, `false` otherwise. This + /// field is initialized to `true` by [`BaseCode::start`] and can be + /// modified with [`BaseCode::set_parameters`]. + pub auto_arp: bool, + /// This field is used to change the Client Hardware Address (chaddr) field + /// in the DHCP and Discovery packets. Set to `true` to send the SystemGuid + /// (if one is available). Set to `false` to send the client NIC MAC + /// address. This field is initialized to `false` by [`BaseCode::start`] + /// and can be modified with [`BaseCode::set_parameters`]. + pub send_guid: bool, + /// This field is initialized to `false` by [`BaseCode::start`] and set to + /// `true` when [`BaseCode::dhcp`] completes successfully. When `true`, + /// [`Self::dhcp_discover`] is valid. This field can also be changed by + /// [`BaseCode::set_packets`]. + pub dhcp_discover_valid: bool, + /// This field is initialized to `false` by [`BaseCode::start`] and set to + /// `true` when [`BaseCode::dhcp`] completes successfully. When `true`, + /// [`Self::dhcp_ack`] is valid. This field can also be changed by + /// [`BaseCode::set_packets`]. + pub dhcp_ack_received: bool, + /// This field is initialized to `false` by [`BaseCode::start`] and set to + /// `true` when [`BaseCode::dhcp`] completes successfully and a proxy DHCP + /// offer packet was received. When `true`, [`Self::proxy_offer`] is valid. + /// This field can also be changed by [`BaseCode::set_packets`]. + pub proxy_offer_received: bool, + /// When `true`, [`Self::pxe_discover`] is valid. This field is set to + /// `false` by [`BaseCode::start`] and [`BaseCode::dhcp`], and can be set + /// to `true` or `false` by [`BaseCode::discover`] and + /// [`BaseCode::set_packets`]. + pub pxe_discover_valid: bool, + /// When `true`, [`Self::pxe_reply`] is valid. This field is set to `false` + /// by [`BaseCode::start`] and [`BaseCode::dhcp`], and can be set to `true` + /// or `false` by [`BaseCode::discover`] and [`BaseCode::set_packets`]. + pub pxe_reply_received: bool, + /// When `true`, [`Self::pxe_bis_reply`] is valid. This field is set to + /// `false` by [`BaseCode::start`] and [`BaseCode::dhcp`], and can be set + /// to `true` or `false` by the [`BaseCode::discover`] and + /// [`BaseCode::set_packets`]. + pub pxe_bis_reply_received: bool, + /// Indicates whether [`Self::icmp_error`] has been updated. This field is + /// reset to `false` by [`BaseCode::start`], [`BaseCode::dhcp`], + /// [`BaseCode::discover`],[`BaseCode::udp_read`], [`BaseCode::udp_write`], + /// [`BaseCode::arp`] and any of the TFTP/MTFTP operations. If an ICMP + /// error is received, this field will be set to `true` after + /// [`Self::icmp_error`] is updated. + pub icmp_error_received: bool, + /// Indicates whether [`Self::tftp_error`] has been updated. This field is + /// reset to `false` by [`BaseCode::start`] and any of the TFTP/MTFTP + /// operations. If a TFTP error is received, this field will be set to + /// `true` after [`Self::tftp_error`] is updated. + pub tftp_error_received: bool, + /// When `false`, callbacks will not be made. When `true`, make callbacks + /// to the PXE Base Code Callback Protocol. This field is reset to `false` + /// by [`BaseCode::start`] if the PXE Base Code Callback Protocol is not + /// available. It is reset to `true` by [`BaseCode::start`] if the PXE Base + /// Code Callback Protocol is available. + pub make_callbacks: bool, + /// The "time to live" field of the IP header. This field is initialized to + /// `16` by [`BaseCode::start`] and can be modified by + /// [`BaseCode::set_parameters`]. + pub ttl: u8, + /// The type of service field of the IP header. This field is initialized + /// to `0` by [`BaseCode::start`], and can be modified with + /// [`BaseCode::set_parameters`]. + pub tos: u8, + /// The device’s current IP address. This field is initialized to a zero + /// address by Start(). This field is set when [`BaseCode::dhcp`] completes + /// successfully. This field can also be set by + /// [`BaseCode::set_station_ip`]. This field must be set to a valid IP + /// address by either [`BaseCode::dhcp`] or [`BaseCode::set_station_ip`] + /// before [`BaseCode::discover`], [`BaseCode::udp_read`], + /// [`BaseCode::udp_write`], [`BaseCode::arp`] and any of the TFTP/MTFTP + /// operations are called. + pub station_ip: IpAddress, + /// The device's current subnet mask. This field is initialized to a zero + /// address by [`BaseCode::start`]. This field is set when + /// [`BaseCode::dhcp`] completes successfully. This field can also be set + /// by [`BaseCode::set_station_ip`]. This field must be set to a valid + /// subnet mask by either [`BaseCode::dhcp`] or + /// [`BaseCode::set_station_ip`] before [`BaseCode::discover`], + /// [`BaseCode::udp_read`], [`BaseCode::udp_write`], + /// [`BaseCode::arp`] or any of the TFTP/MTFTP operations are called. + pub subnet_mask: IpAddress, + /// Cached DHCP Discover packet. This field is zero-filled by the + /// [`BaseCode::start`] function, and is set when [`BaseCode::dhcp`] + /// completes successfully. The contents of this field can replaced by + /// [`BaseCode::set_packets`]. + pub dhcp_discover: Packet, + /// Cached DHCP Ack packet. This field is zero-filled by + /// [`BaseCode::start`], and is set when [`BaseCode::dhcp`] completes + /// successfully. The contents of this field can be replaced by + /// [`BaseCode::set_packets`]. + pub dhcp_ack: Packet, + /// Cached Proxy Offer packet. This field is zero-filled by + /// [`BaseCode::start`], and is set when [`BaseCode::dhcp`] completes + /// successfully. The contents of this field can be replaced by + /// [`BaseCode::set_packets`]. + pub proxy_offer: Packet, + /// Cached PXE Discover packet. This field is zero-filled by + /// [`BaseCode::start`], and is set when [`BaseCode::discover`] completes + /// successfully. The contents of this field can be replaced by + /// [`BaseCode::set_packets`]. + pub pxe_discover: Packet, + /// Cached PXE Reply packet. This field is zero-filled by + /// [`BaseCode::start`], and is set when [`BaseCode::discover`] completes + /// successfully. The contents of this field can be replaced by the + /// [`BaseCode::set_packets`] function. + pub pxe_reply: Packet, + /// Cached PXE BIS Reply packet. This field is zero-filled by + /// [`BaseCode::start`], and is set when [`BaseCode::discover`] completes + /// successfully. This field can be replaced by [`BaseCode::set_packets`]. + pub pxe_bis_reply: Packet, + /// The current IP receive filter settings. The receive filter is disabled + /// and the number of IP receive filters is set to zero by + /// [`BaseCode::start`], and is set by [`BaseCode::set_ip_filter`]. + pub ip_filter: IpFilter, + /// The number of valid entries in the ARP cache. This field is reset to + /// zero by [`BaseCode::start`]. + pub arp_cache_entries: u32, + /// Array of cached ARP entries. + pub arp_cache: [ArpEntry; 8], + /// The number of valid entries in the current route table. This field is + /// reset to zero by [`BaseCode::start`]. + pub route_table_entries: u32, + /// Array of route table entries. + pub route_table: [RouteEntry; 8], + /// ICMP error packet. This field is updated when an ICMP error is received + /// and is undefined until the first ICMP error is received. This field is + /// zero-filled by [`BaseCode::start`]. + pub icmp_error: IcmpError, + /// TFTP error packet. This field is updated when a TFTP error is received + /// and is undefined until the first TFTP error is received. This field is + /// zero-filled by the [`BaseCode::start`] function. + pub tftp_error: TftpError, +} + +/// An entry for the ARP cache found in [`Mode::arp_cache`] +// EFI_PXE_BASE_CODE_ARP_ENTRY +#[repr(C)] +pub struct ArpEntry { + /// The ip address. + pub ip_addr: IpAddress, + /// The mac address of the device that is addressed by [`Self::ip_addr`]. + pub mac_addr: MacAddr, +} + +/// An entry for the route table found in [`Mode::route_table`] +// EFI_PXE_BASE_CODE_ROUTE_ENTRY +#[repr(C)] +#[allow(missing_docs)] +pub struct RouteEntry { + pub ip_addr: IpAddress, + pub subnet_mask: IpAddress, + pub gw_addr: IpAddress, +} + +// EFI_PXE_BASE_CODE_ICMP_ERROR +#[repr(C)] +#[allow(missing_docs)] +pub struct IcmpError { + pub ty: u8, + pub code: u8, + pub checksum: u16, + pub u: IcmpErrorUnion, + pub data: [u8; 494], +} + +// This type represents the anonymous union inside +// EFI_PXE_BASE_CODE_ICMP_ERROR. +#[repr(C)] +#[allow(missing_docs)] +pub union IcmpErrorUnion { + pub reserved: u32, + pub mtu: u32, + pub pointer: u32, + pub echo: IcmpErrorEcho, +} + +// This type represents on of the fields in the anonymous union inside +// EFI_PXE_BASE_CODE_ICMP_ERROR. +#[repr(C)] +#[derive(Clone, Copy)] +#[allow(missing_docs)] +pub struct IcmpErrorEcho { + pub identifier: u16, + pub sequence: u16, +} + +// EFI_PXE_BASE_CODE_TFTP_ERROR +#[repr(C)] +#[allow(missing_docs)] +pub struct TftpError { + pub error_code: u8, + pub error_string: [u8; 127], +} + +/// Returned by [`BaseCode::tftp_read_dir`]. +#[allow(missing_docs)] +pub struct TftpFileInfo<'a> { + pub filename: &'a CStr8, + pub size: u64, + pub year: u16, + pub month: u8, + pub day: u8, + pub hour: u8, + pub minute: u8, + pub second: f32, +} + +/// Returned by [`BaseCode::mtftp_read_dir`]. +#[allow(missing_docs)] +pub struct MtftpFileInfo<'a> { + pub filename: &'a CStr8, + pub ip_address: IpAddress, + pub size: u64, + pub year: u16, + pub month: u8, + pub day: u8, + pub hour: u8, + pub minute: u8, + pub second: f32, +} diff --git a/uefi-test-runner/src/proto/mod.rs b/uefi-test-runner/src/proto/mod.rs index b9cd21b3f..53b127cf4 100644 --- a/uefi-test-runner/src/proto/mod.rs +++ b/uefi-test-runner/src/proto/mod.rs @@ -16,6 +16,7 @@ pub fn test(image: Handle, st: &mut SystemTable) { device_path::test(image, bt); loaded_image::test(image, bt); media::test(image, bt); + network::test(image, bt); pi::test(bt); rng::test(image, bt); @@ -60,6 +61,7 @@ mod debug; mod device_path; mod loaded_image; mod media; +mod network; mod pi; mod rng; #[cfg(any( diff --git a/uefi-test-runner/src/proto/network.rs b/uefi-test-runner/src/proto/network.rs new file mode 100644 index 000000000..6c4cf9cf7 --- /dev/null +++ b/uefi-test-runner/src/proto/network.rs @@ -0,0 +1,116 @@ +use uefi::{ + prelude::BootServices, + proto::network::{ + pxe::{BaseCode, DhcpV4Packet, IpFilter, IpFilters, UdpOpFlags}, + IpAddress, + }, + table::boot::{OpenProtocolAttributes, OpenProtocolParams}, + CStr8, Handle, +}; + +pub fn test(image: Handle, bt: &BootServices) { + info!("Testing Network protocols"); + + let handles = bt + .find_handles::() + .expect("Failed to get handles for `BaseCode` protocol"); + + for handle in handles { + let base_code = bt + .open_protocol::( + OpenProtocolParams { + handle, + agent: image, + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) + .unwrap(); + + let base_code = unsafe { &mut *base_code.interface.get() }; + + info!("Starting PXE Base Code"); + base_code + .start(false) + .expect("failed to start PXE Base Code"); + base_code + .dhcp(false) + .expect("failed to complete a dhcpv4 handshake"); + + assert!(base_code.mode().dhcp_ack_received); + let dhcp_ack: &DhcpV4Packet = base_code.mode().dhcp_ack.as_ref(); + let server_ip = dhcp_ack.bootp_si_addr; + let server_ip = IpAddress::new_v4(server_ip); + + const EXAMPLE_FILE_NAME: &[u8] = b"example-file.txt\0"; + const EXAMPLE_FILE_CONTENT: &[u8] = b"Hello world!"; + let example_file_name = CStr8::from_bytes_with_nul(EXAMPLE_FILE_NAME).unwrap(); + + info!("Getting remote file size"); + let file_size = base_code + .tftp_get_file_size(&server_ip, example_file_name) + .expect("failed to query file size"); + assert_eq!(file_size, EXAMPLE_FILE_CONTENT.len() as u64); + + info!("Reading remote file"); + let mut buffer = [0; 512]; + let len = base_code + .tftp_read_file(&server_ip, example_file_name, Some(&mut buffer)) + .expect("failed to read file"); + let len = usize::try_from(len).unwrap(); + assert_eq!(EXAMPLE_FILE_CONTENT, &buffer[..len]); + + base_code + .set_ip_filter(&IpFilter::new(IpFilters::STATION_IP, &[])) + .expect("failed to set ip filter"); + + const EXAMPLE_SERVICE_PORT: u16 = 21572; + + info!("Writing UDP packet to example service"); + + let payload = [1, 2, 3, 4]; + let header = [payload.len() as u8]; + let mut write_src_port = 0; + base_code + .udp_write( + UdpOpFlags::ANY_SRC_PORT, + &server_ip, + EXAMPLE_SERVICE_PORT, + None, + None, + Some(&mut write_src_port), + Some(&header), + &payload, + ) + .expect("failed to write UDP packet"); + + info!("Reading UDP packet from example service"); + + let mut src_ip = server_ip; + let mut src_port = EXAMPLE_SERVICE_PORT; + let mut dest_ip = base_code.mode().station_ip; + let mut dest_port = write_src_port; + let mut header = [0; 1]; + let mut received = [0; 4]; + base_code + .udp_read( + UdpOpFlags::USE_FILTER, + Some(&mut dest_ip), + Some(&mut dest_port), + Some(&mut src_ip), + Some(&mut src_port), + Some(&mut header), + &mut received, + ) + .unwrap(); + + // Check the header. + assert_eq!(header[0] as usize, payload.len()); + // Check that we receive the reversed payload. + received.reverse(); + assert_eq!(payload, received); + + info!("Stopping PXE Base Code"); + base_code.stop().expect("failed to stop PXE Base Code"); + } +} diff --git a/uefi-test-runner/tftp/example-file.txt b/uefi-test-runner/tftp/example-file.txt new file mode 100644 index 000000000..6769dd60b --- /dev/null +++ b/uefi-test-runner/tftp/example-file.txt @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 1e0adc7e8..b7970a8f7 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,6 +1,7 @@ mod arch; mod cargo; mod disk; +mod net; mod opt; mod qemu; mod util; diff --git a/xtask/src/net.rs b/xtask/src/net.rs new file mode 100644 index 000000000..a3514d7ef --- /dev/null +++ b/xtask/src/net.rs @@ -0,0 +1,30 @@ +use std::net::UdpSocket; + +/// Start a thread that listens on UDP port 21572 and simulates a simple echo +/// service that reverses the incoming messages. +pub fn start_reverse_echo_service() { + std::thread::spawn(reverse_echo_service); +} + +fn reverse_echo_service() { + let socket = UdpSocket::bind(("127.0.0.1", 21572)).expect("failed to bind to UDP socket"); + + let mut buffer = [0; 257]; + loop { + // Receive a packet. + let (len, addr) = socket + .recv_from(&mut buffer) + .expect("failed to receive packet"); + let buffer = &mut buffer[..len]; + + // Extract header information. + let (payload_len, payload) = buffer.split_first_mut().unwrap(); + assert_eq!(usize::from(*payload_len), payload.len()); + + // Simulate processing the data: Reverse the payload. + payload.reverse(); + + // Send a reply. + socket.send_to(buffer, addr).expect("failed to send packet"); + } +} diff --git a/xtask/src/qemu.rs b/xtask/src/qemu.rs index abd18e382..473790c28 100644 --- a/xtask/src/qemu.rs +++ b/xtask/src/qemu.rs @@ -1,5 +1,6 @@ use crate::arch::UefiArch; use crate::disk::{check_mbr_test_disk, create_mbr_test_disk}; +use crate::net; use crate::opt::QemuOpt; use crate::util::command_to_string; use anyhow::{bail, Context, Result}; @@ -368,6 +369,13 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> { // Map the QEMU monitor to a pair of named pipes cmd.args(&["-qmp", &qemu_monitor_pipe.qemu_arg]); + // Attach network device with DHCP configured for PXE + cmd.args(&[ + "-nic", + "user,model=e1000,net=192.168.17.0/24,tftp=uefi-test-runner/tftp/,bootfile=fake-boot-file", + ]); + net::start_reverse_echo_service(); + println!("{}", command_to_string(&cmd)); cmd.stdin(Stdio::piped()); From 837491209593c632148983d85385134f41268a24 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Wed, 27 Apr 2022 20:00:38 +0200 Subject: [PATCH 02/14] fix warnings --- src/proto/network/pxe.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proto/network/pxe.rs b/src/proto/network/pxe.rs index 84e212999..595cddba2 100644 --- a/src/proto/network/pxe.rs +++ b/src/proto/network/pxe.rs @@ -131,7 +131,7 @@ impl BaseCode { ty, layer, use_bis, - info.map(|info| info as *const DiscoverInfo<[Server]> as *const _), + info.map(|info| (info as *const DiscoverInfo<[Server]>).cast()), ) .into() } From 28d1ecb501bab8b6e6fe04be28763a9fbc69804f Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Wed, 27 Apr 2022 20:06:53 +0200 Subject: [PATCH 03/14] don't test PXE Base Code protocol on unsupported platforms --- uefi-test-runner/src/proto/network.rs | 202 +++++++++++++------------- 1 file changed, 101 insertions(+), 101 deletions(-) diff --git a/uefi-test-runner/src/proto/network.rs b/uefi-test-runner/src/proto/network.rs index 6c4cf9cf7..929b2dbf1 100644 --- a/uefi-test-runner/src/proto/network.rs +++ b/uefi-test-runner/src/proto/network.rs @@ -11,106 +11,106 @@ use uefi::{ pub fn test(image: Handle, bt: &BootServices) { info!("Testing Network protocols"); - let handles = bt - .find_handles::() - .expect("Failed to get handles for `BaseCode` protocol"); - - for handle in handles { - let base_code = bt - .open_protocol::( - OpenProtocolParams { - handle, - agent: image, - controller: None, - }, - OpenProtocolAttributes::Exclusive, - ) - .unwrap(); - - let base_code = unsafe { &mut *base_code.interface.get() }; - - info!("Starting PXE Base Code"); - base_code - .start(false) - .expect("failed to start PXE Base Code"); - base_code - .dhcp(false) - .expect("failed to complete a dhcpv4 handshake"); - - assert!(base_code.mode().dhcp_ack_received); - let dhcp_ack: &DhcpV4Packet = base_code.mode().dhcp_ack.as_ref(); - let server_ip = dhcp_ack.bootp_si_addr; - let server_ip = IpAddress::new_v4(server_ip); - - const EXAMPLE_FILE_NAME: &[u8] = b"example-file.txt\0"; - const EXAMPLE_FILE_CONTENT: &[u8] = b"Hello world!"; - let example_file_name = CStr8::from_bytes_with_nul(EXAMPLE_FILE_NAME).unwrap(); - - info!("Getting remote file size"); - let file_size = base_code - .tftp_get_file_size(&server_ip, example_file_name) - .expect("failed to query file size"); - assert_eq!(file_size, EXAMPLE_FILE_CONTENT.len() as u64); - - info!("Reading remote file"); - let mut buffer = [0; 512]; - let len = base_code - .tftp_read_file(&server_ip, example_file_name, Some(&mut buffer)) - .expect("failed to read file"); - let len = usize::try_from(len).unwrap(); - assert_eq!(EXAMPLE_FILE_CONTENT, &buffer[..len]); - - base_code - .set_ip_filter(&IpFilter::new(IpFilters::STATION_IP, &[])) - .expect("failed to set ip filter"); - - const EXAMPLE_SERVICE_PORT: u16 = 21572; - - info!("Writing UDP packet to example service"); - - let payload = [1, 2, 3, 4]; - let header = [payload.len() as u8]; - let mut write_src_port = 0; - base_code - .udp_write( - UdpOpFlags::ANY_SRC_PORT, - &server_ip, - EXAMPLE_SERVICE_PORT, - None, - None, - Some(&mut write_src_port), - Some(&header), - &payload, - ) - .expect("failed to write UDP packet"); - - info!("Reading UDP packet from example service"); - - let mut src_ip = server_ip; - let mut src_port = EXAMPLE_SERVICE_PORT; - let mut dest_ip = base_code.mode().station_ip; - let mut dest_port = write_src_port; - let mut header = [0; 1]; - let mut received = [0; 4]; - base_code - .udp_read( - UdpOpFlags::USE_FILTER, - Some(&mut dest_ip), - Some(&mut dest_port), - Some(&mut src_ip), - Some(&mut src_port), - Some(&mut header), - &mut received, - ) - .unwrap(); - - // Check the header. - assert_eq!(header[0] as usize, payload.len()); - // Check that we receive the reversed payload. - received.reverse(); - assert_eq!(payload, received); - - info!("Stopping PXE Base Code"); - base_code.stop().expect("failed to stop PXE Base Code"); + if let Ok(handles) = bt.find_handles::() { + for handle in handles { + let base_code = bt + .open_protocol::( + OpenProtocolParams { + handle, + agent: image, + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) + .unwrap(); + + let base_code = unsafe { &mut *base_code.interface.get() }; + + info!("Starting PXE Base Code"); + base_code + .start(false) + .expect("failed to start PXE Base Code"); + base_code + .dhcp(false) + .expect("failed to complete a dhcpv4 handshake"); + + assert!(base_code.mode().dhcp_ack_received); + let dhcp_ack: &DhcpV4Packet = base_code.mode().dhcp_ack.as_ref(); + let server_ip = dhcp_ack.bootp_si_addr; + let server_ip = IpAddress::new_v4(server_ip); + + const EXAMPLE_FILE_NAME: &[u8] = b"example-file.txt\0"; + const EXAMPLE_FILE_CONTENT: &[u8] = b"Hello world!"; + let example_file_name = CStr8::from_bytes_with_nul(EXAMPLE_FILE_NAME).unwrap(); + + info!("Getting remote file size"); + let file_size = base_code + .tftp_get_file_size(&server_ip, example_file_name) + .expect("failed to query file size"); + assert_eq!(file_size, EXAMPLE_FILE_CONTENT.len() as u64); + + info!("Reading remote file"); + let mut buffer = [0; 512]; + let len = base_code + .tftp_read_file(&server_ip, example_file_name, Some(&mut buffer)) + .expect("failed to read file"); + let len = usize::try_from(len).unwrap(); + assert_eq!(EXAMPLE_FILE_CONTENT, &buffer[..len]); + + base_code + .set_ip_filter(&IpFilter::new(IpFilters::STATION_IP, &[])) + .expect("failed to set ip filter"); + + const EXAMPLE_SERVICE_PORT: u16 = 21572; + + info!("Writing UDP packet to example service"); + + let payload = [1, 2, 3, 4]; + let header = [payload.len() as u8]; + let mut write_src_port = 0; + base_code + .udp_write( + UdpOpFlags::ANY_SRC_PORT, + &server_ip, + EXAMPLE_SERVICE_PORT, + None, + None, + Some(&mut write_src_port), + Some(&header), + &payload, + ) + .expect("failed to write UDP packet"); + + info!("Reading UDP packet from example service"); + + let mut src_ip = server_ip; + let mut src_port = EXAMPLE_SERVICE_PORT; + let mut dest_ip = base_code.mode().station_ip; + let mut dest_port = write_src_port; + let mut header = [0; 1]; + let mut received = [0; 4]; + base_code + .udp_read( + UdpOpFlags::USE_FILTER, + Some(&mut dest_ip), + Some(&mut dest_port), + Some(&mut src_ip), + Some(&mut src_port), + Some(&mut header), + &mut received, + ) + .unwrap(); + + // Check the header. + assert_eq!(header[0] as usize, payload.len()); + // Check that we receive the reversed payload. + received.reverse(); + assert_eq!(payload, received); + + info!("Stopping PXE Base Code"); + base_code.stop().expect("failed to stop PXE Base Code"); + } + } else { + warn!("PXE Base Code protocol is not supported"); } } From 80a4c61083d7b536ae574b428dc6931a383c269c Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Fri, 29 Apr 2022 21:00:01 +0200 Subject: [PATCH 04/14] add accociated constant for dhcp magic cookie --- src/proto/network/pxe.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/proto/network/pxe.rs b/src/proto/network/pxe.rs index 595cddba2..60b4f25f8 100644 --- a/src/proto/network/pxe.rs +++ b/src/proto/network/pxe.rs @@ -976,6 +976,9 @@ pub struct DhcpV4Packet { } impl DhcpV4Packet { + /// The expected value for [`Self::dhcp_magik`]. + pub const DHCP_MAGIK: u32 = 0x63825363; + /// Transaction ID, a random number, used to match this boot request with the responses it generates. pub fn bootp_ident(&self) -> u32 { u32::from_be(self.bootp_ident) @@ -991,7 +994,7 @@ impl DhcpV4Packet { DhcpV4Flags::from_bits_truncate(u16::from_be(self.bootp_flags)) } - /// A magic cookie, should be `0x63825363`. + /// A magic cookie, should be [`Self::DHCP_MAGIK`]. pub fn dhcp_magik(&self) -> u32 { u32::from_be(self.dhcp_magik) } From 76221012ad8804c31d11c96e92243cbe7f678f2e Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Fri, 29 Apr 2022 21:04:18 +0200 Subject: [PATCH 05/14] rename `MacAddr` to `MacAddress` --- src/proto/network/mod.rs | 2 +- src/proto/network/pxe.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/proto/network/mod.rs b/src/proto/network/mod.rs index ca637c139..27496e7bc 100644 --- a/src/proto/network/mod.rs +++ b/src/proto/network/mod.rs @@ -29,4 +29,4 @@ impl IpAddress { /// EFI_MAC_ADDRESS #[derive(Clone, Copy)] #[repr(C)] -pub struct MacAddr(pub [u8; 32]); +pub struct MacAddress(pub [u8; 32]); diff --git a/src/proto/network/pxe.rs b/src/proto/network/pxe.rs index 60b4f25f8..2fb75beae 100644 --- a/src/proto/network/pxe.rs +++ b/src/proto/network/pxe.rs @@ -7,7 +7,7 @@ use uefi_macros::{unsafe_guid, Protocol}; use crate::{CStr8, Char8, Result, Status}; -use super::{IpAddress, MacAddr}; +use super::{IpAddress, MacAddress}; /// PXE Base Code protocol #[repr(C)] @@ -67,7 +67,7 @@ pub struct BaseCode { arp: extern "efiapi" fn( this: &Self, ip_addr: &IpAddress, - mac_addr: Option<&mut MacAddr>, + mac_addr: Option<&mut MacAddress>, ) -> Status, set_parameters: extern "efiapi" fn( this: &Self, @@ -585,7 +585,7 @@ impl BaseCode { } /// Uses the ARP protocol to resolve a MAC address. - pub fn arp(&mut self, ip_addr: &IpAddress, mac_addr: Option<&mut MacAddr>) -> Result { + pub fn arp(&mut self, ip_addr: &IpAddress, mac_addr: Option<&mut MacAddress>) -> Result { (self.arp)(self, ip_addr, mac_addr).into() } @@ -1195,7 +1195,7 @@ pub struct ArpEntry { /// The ip address. pub ip_addr: IpAddress, /// The mac address of the device that is addressed by [`Self::ip_addr`]. - pub mac_addr: MacAddr, + pub mac_addr: MacAddress, } /// An entry for the route table found in [`Mode::route_table`] From 6ec7b0e911a3f3c74f62424d3290a25a2da3a91b Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Fri, 29 Apr 2022 21:04:46 +0200 Subject: [PATCH 06/14] improve doc comment for `MacAddress` --- src/proto/network/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/proto/network/mod.rs b/src/proto/network/mod.rs index 27496e7bc..648357101 100644 --- a/src/proto/network/mod.rs +++ b/src/proto/network/mod.rs @@ -26,7 +26,8 @@ impl IpAddress { } } -/// EFI_MAC_ADDRESS +/// Represents a MAC (media access control) address. Corresponds to the +/// `EFI_MAC_ADDRESS` type in the C API. #[derive(Clone, Copy)] #[repr(C)] pub struct MacAddress(pub [u8; 32]); From 350dddd06858b3f83d5f404f04cf1361e921bddf Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Fri, 29 Apr 2022 21:05:00 +0200 Subject: [PATCH 07/14] improve doc comment for `IpAddress` --- src/proto/network/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/proto/network/mod.rs b/src/proto/network/mod.rs index 648357101..b849f10f1 100644 --- a/src/proto/network/mod.rs +++ b/src/proto/network/mod.rs @@ -4,7 +4,8 @@ pub mod pxe; -/// EFI_IP_ADDRESS +/// Represents an IPv4/v6 address. Corresponds to `EFI_IP_ADDRESS` type in the +/// C API. #[derive(Clone, Copy)] #[repr(C, align(4))] pub struct IpAddress(pub [u8; 16]); From 9c674a872d2232bf10307d1d8f260568567d7248 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Sun, 1 May 2022 10:17:35 +0200 Subject: [PATCH 08/14] return `ReadDirParseError` for malformed responses --- src/proto/network/pxe.rs | 160 ++++++++++++++++----------------------- 1 file changed, 67 insertions(+), 93 deletions(-) diff --git a/src/proto/network/pxe.rs b/src/proto/network/pxe.rs index 2fb75beae..621d05a7d 100644 --- a/src/proto/network/pxe.rs +++ b/src/proto/network/pxe.rs @@ -232,7 +232,8 @@ impl BaseCode { server_ip: &IpAddress, directory_name: &CStr8, buffer: &'a mut [u8], - ) -> Result> + 'a> { + ) -> Result, ReadDirParseError>> + 'a> + { let filename = NonNull::from(&directory_name.to_bytes_with_nul()[0]).cast(); let buffer_ptr = NonNull::from(&buffer[0]).cast(); @@ -258,52 +259,37 @@ impl BaseCode { let buffer = &buffer[..buffer_size]; let mut iterator = buffer.split_inclusive(|b| *b == 0); - - Ok(from_fn(move || { - let filename = iterator - .next() - .expect("the final entry should have an empty file name"); + let mut parse_next = move || { + let filename = iterator.next().ok_or(ReadDirParseError)?; if filename == [0] { // This is the final entry. - return None; + return Ok(None); } let filename = CStr8::from_bytes_with_nul(filename).unwrap(); - let information_string = iterator - .next() - .expect("each file should have an information string"); + let information_string = iterator.next().ok_or(ReadDirParseError)?; let (_null_terminator, information_string) = information_string.split_last().unwrap(); - let information_string = core::str::from_utf8(information_string) - .expect("the information string should be valid utf-8"); + let information_string = + core::str::from_utf8(information_string).map_err(|_| ReadDirParseError)?; let (size, rest) = information_string .split_once(' ') - .expect("the information string should be valid"); - let (year, rest) = rest - .split_once('-') - .expect("the information string should be valid"); - let (month, rest) = rest - .split_once('-') - .expect("the information string should be valid"); - let (day, rest) = rest - .split_once(' ') - .expect("the information string should be valid"); - let (hour, rest) = rest - .split_once(':') - .expect("the information string should be valid"); - let (minute, second) = rest - .split_once(':') - .expect("the information string should be valid"); - - let size = size.parse().expect("size should be a number"); - let year = year.parse().expect("year should be a number"); - let month = month.parse().expect("month should be a number"); - let day = day.parse().expect("day should be a number"); - let hour = hour.parse().expect("hour should be a number"); - let minute = minute.parse().expect("minute should be a number"); - let second = second.parse().expect("second should be a number"); - - Some(TftpFileInfo { + .ok_or(ReadDirParseError)?; + let (year, rest) = rest.split_once('-').ok_or(ReadDirParseError)?; + let (month, rest) = rest.split_once('-').ok_or(ReadDirParseError)?; + let (day, rest) = rest.split_once(' ').ok_or(ReadDirParseError)?; + let (hour, rest) = rest.split_once(':').ok_or(ReadDirParseError)?; + let (minute, second) = rest.split_once(':').ok_or(ReadDirParseError)?; + + let size = size.parse().map_err(|_| ReadDirParseError)?; + let year = year.parse().map_err(|_| ReadDirParseError)?; + let month = month.parse().map_err(|_| ReadDirParseError)?; + let day = day.parse().map_err(|_| ReadDirParseError)?; + let hour = hour.parse().map_err(|_| ReadDirParseError)?; + let minute = minute.parse().map_err(|_| ReadDirParseError)?; + let second = second.parse().map_err(|_| ReadDirParseError)?; + + Ok(Some(TftpFileInfo { filename, size, year, @@ -312,9 +298,9 @@ impl BaseCode { hour, minute, second, - }) - }) - .fuse()) + })) + }; + Ok(from_fn(move || parse_next().transpose()).fuse()) } /// Returns the size of a file located on a MTFTP server. @@ -389,7 +375,8 @@ impl BaseCode { server_ip: &IpAddress, buffer: &'a mut [u8], info: &MtftpInfo, - ) -> Result> + 'a> { + ) -> Result, ReadDirParseError>> + 'a> + { let buffer_ptr = NonNull::from(&buffer[0]).cast(); let mut buffer_size = u64::try_from(buffer.len()).expect("buffer length should fit in u64"); @@ -413,71 +400,53 @@ impl BaseCode { let buffer = &buffer[..buffer_size]; let mut iterator = buffer.split_inclusive(|b| *b == 0); - - Ok(from_fn(move || { - let filename = iterator - .next() - .expect("the final entry should have an empty file name"); + let mut parse_next = move || { + let filename = iterator.next().ok_or(ReadDirParseError)?; if filename == [0] { // This is the final entry. - return None; + return Ok(None); } let filename = CStr8::from_bytes_with_nul(filename).unwrap(); - let multicast_ip = iterator - .next() - .expect("each file should have a multicast ip address"); + let multicast_ip = iterator.next().ok_or(ReadDirParseError)?; let (_null_terminator, multicast_ip) = multicast_ip.split_last().unwrap(); - let multicast_ip = core::str::from_utf8(multicast_ip) - .expect("the multicast ip address should be valid utf-8"); + let multicast_ip = core::str::from_utf8(multicast_ip).map_err(|_| ReadDirParseError)?; let mut octets = multicast_ip.split('.'); let mut buffer = [0; 4]; for b in buffer.iter_mut() { - let octet = octets - .next() - .expect("the information string should be valid"); - let octet = octet - .parse() - .expect("each octet in the ip address should be a number"); + let octet = octets.next().ok_or(ReadDirParseError)?; + let octet = octet.parse().map_err(|_| ReadDirParseError)?; *b = octet; } + if octets.next().is_some() { + // The ip should have exact 4 octets, not more. + return Err(ReadDirParseError); + } let ip_address = IpAddress::new_v4(buffer); - let information_string = iterator - .next() - .expect("each file should have an information string"); + let information_string = iterator.next().ok_or(ReadDirParseError)?; let (_null_terminator, information_string) = information_string.split_last().unwrap(); - let information_string = core::str::from_utf8(information_string) - .expect("the information string should be valid utf-8"); + let information_string = + core::str::from_utf8(information_string).map_err(|_| ReadDirParseError)?; let (size, rest) = information_string .split_once(' ') - .expect("the information string should be valid"); - let (year, rest) = rest - .split_once('-') - .expect("the information string should be valid"); - let (month, rest) = rest - .split_once('-') - .expect("the information string should be valid"); - let (day, rest) = rest - .split_once(' ') - .expect("the information string should be valid"); - let (hour, rest) = rest - .split_once(':') - .expect("the information string should be valid"); - let (minute, second) = rest - .split_once(':') - .expect("the information string should be valid"); - - let size = size.parse().expect("size should be a number"); - let year = year.parse().expect("year should be a number"); - let month = month.parse().expect("month should be a number"); - let day = day.parse().expect("day should be a number"); - let hour = hour.parse().expect("hour should be a number"); - let minute = minute.parse().expect("minute should be a number"); - let second = second.parse().expect("second should be a number"); - - Some(MtftpFileInfo { + .ok_or(ReadDirParseError)?; + let (year, rest) = rest.split_once('-').ok_or(ReadDirParseError)?; + let (month, rest) = rest.split_once('-').ok_or(ReadDirParseError)?; + let (day, rest) = rest.split_once(' ').ok_or(ReadDirParseError)?; + let (hour, rest) = rest.split_once(':').ok_or(ReadDirParseError)?; + let (minute, second) = rest.split_once(':').ok_or(ReadDirParseError)?; + + let size = size.parse().map_err(|_| ReadDirParseError)?; + let year = year.parse().map_err(|_| ReadDirParseError)?; + let month = month.parse().map_err(|_| ReadDirParseError)?; + let day = day.parse().map_err(|_| ReadDirParseError)?; + let hour = hour.parse().map_err(|_| ReadDirParseError)?; + let minute = minute.parse().map_err(|_| ReadDirParseError)?; + let second = second.parse().map_err(|_| ReadDirParseError)?; + + Ok(Some(MtftpFileInfo { filename, ip_address, size, @@ -487,9 +456,9 @@ impl BaseCode { hour, minute, second, - }) - }) - .fuse()) + })) + }; + Ok(from_fn(move || parse_next().transpose()).fuse()) } /// Writes a UDP packet to the network interface. @@ -1274,3 +1243,8 @@ pub struct MtftpFileInfo<'a> { pub minute: u8, pub second: f32, } + +/// Returned if a server sends a malformed response in +/// [`BaseCode::tftp_read_dir`] or [`BaseCode::mtftp_read_dir`]. +#[derive(Clone, Copy, Debug)] +pub struct ReadDirParseError; From 842eb8b26122a161b4baeb43e4d71b681b8606a5 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Sun, 1 May 2022 10:27:46 +0200 Subject: [PATCH 09/14] use raw pointers instead of `Option>` --- src/proto/network/pxe.rs | 78 +++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 46 deletions(-) diff --git a/src/proto/network/pxe.rs b/src/proto/network/pxe.rs index 621d05a7d..ed8684c72 100644 --- a/src/proto/network/pxe.rs +++ b/src/proto/network/pxe.rs @@ -1,6 +1,10 @@ //! PXE Base Code protocol. -use core::{ffi::c_void, iter::from_fn, ptr::NonNull}; +use core::{ + ffi::c_void, + iter::from_fn, + ptr::{null, null_mut, NonNull}, +}; use bitflags::bitflags; use uefi_macros::{unsafe_guid, Protocol}; @@ -29,12 +33,12 @@ pub struct BaseCode { mtftp: unsafe extern "efiapi" fn( this: &Self, operation: TftpOpcode, - buffer: Option>, + buffer: *mut c_void, overwrite: bool, buffer_size: &mut u64, block_size: Option<&usize>, server_ip: &IpAddress, - filename: Option>, + filename: *const Char8, info: Option<&MtftpInfo>, dont_use_buffer: bool, ) -> Status, @@ -47,7 +51,7 @@ pub struct BaseCode { src_ip: Option<&IpAddress>, src_port: Option<&mut u16>, header_size: Option<&usize>, - header_ptr: Option>, + header_ptr: *const c_void, buffer_size: &usize, buffer_ptr: NonNull, ) -> Status, @@ -59,7 +63,7 @@ pub struct BaseCode { src_ip: Option<&mut IpAddress>, src_port: Option<&mut u16>, header_size: Option<&usize>, - header_ptr: Option>, + header_ptr: *mut c_void, buffer_size: &mut usize, buffer_ptr: NonNull, ) -> Status, @@ -139,18 +143,17 @@ impl BaseCode { /// Returns the size of a file located on a TFTP server. pub fn tftp_get_file_size(&mut self, server_ip: &IpAddress, filename: &CStr8) -> Result { let mut buffer_size = 0; - let filename = NonNull::from(&filename.to_bytes_with_nul()[0]).cast(); let status = unsafe { (self.mtftp)( self, TftpOpcode::TftpGetFileSize, - None, + null_mut(), false, &mut buffer_size, None, server_ip, - Some(filename), + filename.as_ptr(), None, false, ) @@ -167,14 +170,11 @@ impl BaseCode { filename: &CStr8, buffer: Option<&mut [u8]>, ) -> Result { - let filename = NonNull::from(&filename.to_bytes_with_nul()[0]).cast(); - let (buffer_ptr, mut buffer_size, dont_use_buffer) = if let Some(buffer) = buffer { - let buffer_ptr = NonNull::from(&mut buffer[0]).cast::(); let buffer_size = u64::try_from(buffer.len()).unwrap(); - (Some(buffer_ptr), buffer_size, false) + ((&mut buffer[0] as *mut u8).cast(), buffer_size, false) } else { - (None, 0, true) + (null_mut(), 0, true) }; let status = unsafe { @@ -186,7 +186,7 @@ impl BaseCode { &mut buffer_size, None, server_ip, - Some(filename), + filename.as_ptr(), None, dont_use_buffer, ) @@ -204,21 +204,19 @@ impl BaseCode { overwrite: bool, buffer: &[u8], ) -> Result { - let filename = NonNull::from(&filename.to_bytes_with_nul()[0]).cast(); - - let buffer_ptr = NonNull::from(&buffer[0]).cast(); + let buffer_ptr = (&buffer[0] as *const u8 as *mut u8).cast(); let mut buffer_size = u64::try_from(buffer.len()).expect("buffer length should fit in u64"); unsafe { (self.mtftp)( self, TftpOpcode::TftpWriteFile, - Some(buffer_ptr), + buffer_ptr, overwrite, &mut buffer_size, None, server_ip, - Some(filename), + filename.as_ptr(), None, false, ) @@ -234,21 +232,19 @@ impl BaseCode { buffer: &'a mut [u8], ) -> Result, ReadDirParseError>> + 'a> { - let filename = NonNull::from(&directory_name.to_bytes_with_nul()[0]).cast(); - - let buffer_ptr = NonNull::from(&buffer[0]).cast(); + let buffer_ptr = (&buffer[0] as *const u8 as *mut u8).cast(); let mut buffer_size = u64::try_from(buffer.len()).expect("buffer length should fit in u64"); let status = unsafe { (self.mtftp)( self, TftpOpcode::TftpReadDirectory, - Some(buffer_ptr), + buffer_ptr, false, &mut buffer_size, None, server_ip, - Some(filename), + directory_name.as_ptr(), None, false, ) @@ -311,18 +307,17 @@ impl BaseCode { info: &MtftpInfo, ) -> Result { let mut buffer_size = 0; - let filename = NonNull::from(&filename.to_bytes_with_nul()[0]).cast(); let status = unsafe { (self.mtftp)( self, TftpOpcode::MtftpGetFileSize, - None, + null_mut(), false, &mut buffer_size, None, server_ip, - Some(filename), + filename.as_ptr(), Some(info), false, ) @@ -340,14 +335,11 @@ impl BaseCode { buffer: Option<&mut [u8]>, info: &MtftpInfo, ) -> Result { - let filename = NonNull::from(&filename.to_bytes_with_nul()[0]).cast(); - let (buffer_ptr, mut buffer_size, dont_use_buffer) = if let Some(buffer) = buffer { - let buffer_ptr = NonNull::from(&mut buffer[0]).cast::(); let buffer_size = u64::try_from(buffer.len()).unwrap(); - (Some(buffer_ptr), buffer_size, false) + ((&mut buffer[0] as *mut u8).cast(), buffer_size, false) } else { - (None, 0, true) + (null_mut(), 0, true) }; let status = unsafe { @@ -359,7 +351,7 @@ impl BaseCode { &mut buffer_size, None, server_ip, - Some(filename), + filename.as_ptr(), Some(info), dont_use_buffer, ) @@ -377,19 +369,19 @@ impl BaseCode { info: &MtftpInfo, ) -> Result, ReadDirParseError>> + 'a> { - let buffer_ptr = NonNull::from(&buffer[0]).cast(); + let buffer_ptr = (&buffer[0] as *const u8 as *mut u8).cast(); let mut buffer_size = u64::try_from(buffer.len()).expect("buffer length should fit in u64"); let status = unsafe { (self.mtftp)( self, TftpOpcode::MtftpReadDirectory, - Some(buffer_ptr), + buffer_ptr, false, &mut buffer_size, None, server_ip, - None, + null_mut(), Some(info), false, ) @@ -477,12 +469,9 @@ impl BaseCode { let header_size_tmp; let (header_size, header_ptr) = if let Some(header) = header { header_size_tmp = header.len(); - ( - Some(&header_size_tmp), - Some(NonNull::from(&header[0]).cast()), - ) + (Some(&header_size_tmp), (&header[0] as *const u8).cast()) } else { - (None, None) + (None, null()) }; unsafe { @@ -518,12 +507,9 @@ impl BaseCode { let header_size_tmp; let (header_size, header_ptr) = if let Some(header) = header { header_size_tmp = header.len(); - ( - Some(&header_size_tmp), - Some(NonNull::from(&mut header[0]).cast()), - ) + (Some(&header_size_tmp), (&mut header[0] as *mut u8).cast()) } else { - (None, None) + (None, null_mut()) }; let mut buffer_size = buffer.len(); From b2b816e83e0cbf5a83b172252374d09db4965c86 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Sun, 1 May 2022 17:29:05 +0200 Subject: [PATCH 10/14] use raw pointers instead of `NonNull` --- src/proto/network/pxe.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/proto/network/pxe.rs b/src/proto/network/pxe.rs index ed8684c72..2c23a2097 100644 --- a/src/proto/network/pxe.rs +++ b/src/proto/network/pxe.rs @@ -3,7 +3,7 @@ use core::{ ffi::c_void, iter::from_fn, - ptr::{null, null_mut, NonNull}, + ptr::{null, null_mut}, }; use bitflags::bitflags; @@ -53,7 +53,7 @@ pub struct BaseCode { header_size: Option<&usize>, header_ptr: *const c_void, buffer_size: &usize, - buffer_ptr: NonNull, + buffer_ptr: *const c_void, ) -> Status, udp_read: unsafe extern "efiapi" fn( this: &Self, @@ -65,7 +65,7 @@ pub struct BaseCode { header_size: Option<&usize>, header_ptr: *mut c_void, buffer_size: &mut usize, - buffer_ptr: NonNull, + buffer_ptr: *mut c_void, ) -> Status, set_ip_filter: extern "efiapi" fn(this: &Self, new_filter: &IpFilter) -> Status, arp: extern "efiapi" fn( @@ -101,7 +101,7 @@ pub struct BaseCode { new_pxe_reply: Option<&Packet>, new_pxe_bis_reply: Option<&Packet>, ) -> Status, - mode: NonNull, + mode: *const Mode, } impl BaseCode { @@ -486,7 +486,7 @@ impl BaseCode { header_size, header_ptr, &buffer.len(), - NonNull::from(&buffer[0]).cast(), + (&buffer[0] as *const u8).cast(), ) } .into() @@ -525,7 +525,7 @@ impl BaseCode { header_size, header_ptr, &mut buffer_size, - NonNull::from(&buffer[0]).cast(), + (&mut buffer[0] as *mut u8).cast(), ) }; Result::from(status)?; @@ -612,7 +612,7 @@ impl BaseCode { /// Returns a reference to the `Mode` struct. pub fn mode(&self) -> &Mode { - unsafe { self.mode.as_ref() } + unsafe { &*self.mode } } } From 0efe446280a32bcced322c2aa3518969d6e2c86a Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Thu, 5 May 2022 10:46:52 +0200 Subject: [PATCH 11/14] add derives --- src/proto/network/mod.rs | 4 ++-- src/proto/network/pxe.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/proto/network/mod.rs b/src/proto/network/mod.rs index b849f10f1..5c30e29ce 100644 --- a/src/proto/network/mod.rs +++ b/src/proto/network/mod.rs @@ -6,7 +6,7 @@ pub mod pxe; /// Represents an IPv4/v6 address. Corresponds to `EFI_IP_ADDRESS` type in the /// C API. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[repr(C, align(4))] pub struct IpAddress(pub [u8; 16]); @@ -29,6 +29,6 @@ impl IpAddress { /// Represents a MAC (media access control) address. Corresponds to the /// `EFI_MAC_ADDRESS` type in the C API. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[repr(C)] pub struct MacAddress(pub [u8; 32]); diff --git a/src/proto/network/pxe.rs b/src/proto/network/pxe.rs index 2c23a2097..1f52f4a24 100644 --- a/src/proto/network/pxe.rs +++ b/src/proto/network/pxe.rs @@ -617,6 +617,7 @@ impl BaseCode { } // EFI_PXE_BASE_CODE_BOOT_* +#[derive(Clone, Copy, PartialEq, Eq, Hash)] #[repr(u16)] #[allow(missing_docs)] pub enum BootstrapType { From 892d3dccf536470752e3cf6b08615ac0b838b774 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Thu, 5 May 2022 10:47:30 +0200 Subject: [PATCH 12/14] capitalize "IP" --- src/proto/network/pxe.rs | 14 +++++++------- uefi-test-runner/src/proto/network.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/proto/network/pxe.rs b/src/proto/network/pxe.rs index 1f52f4a24..bc8ec8d87 100644 --- a/src/proto/network/pxe.rs +++ b/src/proto/network/pxe.rs @@ -411,7 +411,7 @@ impl BaseCode { *b = octet; } if octets.next().is_some() { - // The ip should have exact 4 octets, not more. + // The IP should have exact 4 octets, not more. return Err(ReadDirParseError); } let ip_address = IpAddress::new_v4(buffer); @@ -730,13 +730,13 @@ pub struct Server { pub ty: u16, accept_any_response: bool, _reserved: u8, - /// The ip address of the server + /// The IP address of the server ip_addr: IpAddress, } impl Server { /// Construct a `Server` for a Boot Server reply type. If `ip_addr` is not - /// `None` only Boot Server replies with matching the ip address will be + /// `None` only Boot Server replies with matching the IP address will be /// accepted. pub fn new(ty: u16, ip_addr: Option) -> Self { Self { @@ -747,7 +747,7 @@ impl Server { } } - /// Returns a `None` if the any response should be accepted or the ip + /// Returns a `None` if the any response should be accepted or the IP /// address of a Boot Server whose responses should be accepted. pub fn ip_addr(&self) -> Option<&IpAddress> { if self.accept_any_response { @@ -846,7 +846,7 @@ impl IpFilter { } } - /// A list of ip addresses other than the Station Ip that should be + /// A list of IP addresses other than the Station Ip that should be /// enabled. Maybe be multicast or unicast. pub fn ip_list(&self) -> &[IpAddress] { &self.ip_list[..usize::from(self.ip_cnt)] @@ -857,7 +857,7 @@ bitflags! { /// IP receive filters. #[repr(transparent)] pub struct IpFilters: u8 { - /// Enable the Station ip address. + /// Enable the Station IP address. const STATION_IP = 0x01; /// Enable IPv4 broadcast addresses. const BROADCAST = 0x02; @@ -1148,7 +1148,7 @@ pub struct Mode { // EFI_PXE_BASE_CODE_ARP_ENTRY #[repr(C)] pub struct ArpEntry { - /// The ip address. + /// The IP address. pub ip_addr: IpAddress, /// The mac address of the device that is addressed by [`Self::ip_addr`]. pub mac_addr: MacAddress, diff --git a/uefi-test-runner/src/proto/network.rs b/uefi-test-runner/src/proto/network.rs index 929b2dbf1..e0002382f 100644 --- a/uefi-test-runner/src/proto/network.rs +++ b/uefi-test-runner/src/proto/network.rs @@ -59,7 +59,7 @@ pub fn test(image: Handle, bt: &BootServices) { base_code .set_ip_filter(&IpFilter::new(IpFilters::STATION_IP, &[])) - .expect("failed to set ip filter"); + .expect("failed to set IP filter"); const EXAMPLE_SERVICE_PORT: u16 = 21572; From 64ff2f5d51f78fff665854c7013efdcca81fad4a Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Thu, 5 May 2022 10:47:39 +0200 Subject: [PATCH 13/14] add missing doc comment --- src/proto/network/pxe.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/proto/network/pxe.rs b/src/proto/network/pxe.rs index bc8ec8d87..e26676e09 100644 --- a/src/proto/network/pxe.rs +++ b/src/proto/network/pxe.rs @@ -616,6 +616,7 @@ impl BaseCode { } } +/// A type of bootstrap to perform in [`BaseCode::discover`]. // EFI_PXE_BASE_CODE_BOOT_* #[derive(Clone, Copy, PartialEq, Eq, Hash)] #[repr(u16)] From b32b364c1a0fbffc5c657eadb4645a9242fe6cba Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Sat, 7 May 2022 10:50:34 +0200 Subject: [PATCH 14/14] publically document the corresponding types --- src/proto/network/mod.rs | 10 ++++---- src/proto/network/pxe.rs | 51 ++++++++++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/proto/network/mod.rs b/src/proto/network/mod.rs index 5c30e29ce..66c3a4220 100644 --- a/src/proto/network/mod.rs +++ b/src/proto/network/mod.rs @@ -4,8 +4,9 @@ pub mod pxe; -/// Represents an IPv4/v6 address. Corresponds to `EFI_IP_ADDRESS` type in the -/// C API. +/// Represents an IPv4/v6 address. +/// +/// Corresponds to the `EFI_IP_ADDRESS` type in the C API. #[derive(Clone, Copy, PartialEq, Eq, Hash)] #[repr(C, align(4))] pub struct IpAddress(pub [u8; 16]); @@ -27,8 +28,9 @@ impl IpAddress { } } -/// Represents a MAC (media access control) address. Corresponds to the -/// `EFI_MAC_ADDRESS` type in the C API. +/// Represents a MAC (media access control) address. +/// +/// Corresponds to the `EFI_MAC_ADDRESS` type in the C API. #[derive(Clone, Copy, PartialEq, Eq, Hash)] #[repr(C)] pub struct MacAddress(pub [u8; 32]); diff --git a/src/proto/network/pxe.rs b/src/proto/network/pxe.rs index e26676e09..c8b850d3a 100644 --- a/src/proto/network/pxe.rs +++ b/src/proto/network/pxe.rs @@ -617,7 +617,8 @@ impl BaseCode { } /// A type of bootstrap to perform in [`BaseCode::discover`]. -// EFI_PXE_BASE_CODE_BOOT_* +/// +/// Corresponds to the `EFI_PXE_BASE_CODE_BOOT_` constants in the C API. #[derive(Clone, Copy, PartialEq, Eq, Hash)] #[repr(u16)] #[allow(missing_docs)] @@ -648,7 +649,8 @@ pub enum BootstrapType { } /// This struct contains optional parameters for [`BaseCode::discover`]. -// EFI_PXE_BASE_CODE_DISCOVER_INFO +/// +/// Corresponds to the `EFI_PXE_BASE_CODE_DISCOVER_INFO` type in the C API. #[repr(C)] pub struct DiscoverInfo { use_m_cast: bool, @@ -724,7 +726,8 @@ impl DiscoverInfo { } /// An entry in the Boot Server list -// EFI_PXE_BASE_CODE_SRVLIST +/// +/// Corresponds to the `EFI_PXE_BASE_CODE_SRVLIST` type in the C API. #[repr(C)] pub struct Server { /// The type of Boot Server reply @@ -759,7 +762,7 @@ impl Server { } } -// EFI_PXE_BASE_CODE_TFTP_OPCODE +/// Corresponds to the `EFI_PXE_BASE_CODE_TFTP_OPCODE` type in the C API. #[repr(C)] enum TftpOpcode { TftpGetFileSize = 1, @@ -772,7 +775,8 @@ enum TftpOpcode { } /// MTFTP connection parameters -// EFI_PXE_BASE_CODE_MTFTP_INFO +/// +/// Corresponds to the `EFI_PXE_BASE_CODE_MTFTP_INFO` type in the C API. #[derive(Clone, Copy)] #[repr(C)] pub struct MtftpInfo { @@ -816,7 +820,8 @@ bitflags! { } /// IP receive filter settings -// EFI_PXE_BASE_CODE_IP_FILTER +/// +/// Corresponds to the `EFI_PXE_BASE_CODE_IP_FILTER` type in the C API. #[repr(C)] pub struct IpFilter { /// A set of filters. @@ -870,7 +875,8 @@ bitflags! { } /// A network packet. -// EFI_PXE_BASE_CODE_PACKET +/// +/// Corresponds to the `EFI_PXE_BASE_CODE_PACKET` type in the C API. #[repr(C)] pub union Packet { raw: [u8; 1472], @@ -897,7 +903,8 @@ impl AsRef for Packet { } /// A Dhcpv4 Packet. -// EFI_PXE_BASE_CODE_DHCPV4_PACKET +/// +/// Corresponds to the `EFI_PXE_BASE_CODE_DHCPV4_PACKET` type in the C API. #[repr(C)] #[derive(Clone, Copy)] pub struct DhcpV4Packet { @@ -967,7 +974,8 @@ bitflags! { } /// A Dhcpv6 Packet. -// EFI_PXE_BASE_CODE_DHCPV6_PACKET +/// +/// Corresponds to the `EFI_PXE_BASE_CODE_DHCPV6_PACKET` type in the C API. #[repr(C)] #[derive(Clone, Copy)] pub struct DhcpV6Packet { @@ -989,7 +997,8 @@ impl DhcpV6Packet { /// The data values in this structure are read-only and are updated by the /// [`BaseCode`]. -// EFI_PXE_BASE_CODE_MODE +/// +/// Corresponds to the `EFI_PXE_BASE_CODE_MODE` type in the C API. #[repr(C)] pub struct Mode { /// `true` if this device has been started by calling [`BaseCode::start`]. @@ -1146,7 +1155,8 @@ pub struct Mode { } /// An entry for the ARP cache found in [`Mode::arp_cache`] -// EFI_PXE_BASE_CODE_ARP_ENTRY +/// +/// Corresponds to the `EFI_PXE_BASE_CODE_ARP_ENTRY` type in the C API. #[repr(C)] pub struct ArpEntry { /// The IP address. @@ -1156,7 +1166,8 @@ pub struct ArpEntry { } /// An entry for the route table found in [`Mode::route_table`] -// EFI_PXE_BASE_CODE_ROUTE_ENTRY +/// +/// Corresponds to the `EFI_PXE_BASE_CODE_ROUTE_ENTRY` type in the C API. #[repr(C)] #[allow(missing_docs)] pub struct RouteEntry { @@ -1165,7 +1176,9 @@ pub struct RouteEntry { pub gw_addr: IpAddress, } -// EFI_PXE_BASE_CODE_ICMP_ERROR +/// An ICMP error packet. +/// +/// Corresponds to the `EFI_PXE_BASE_CODE_ICMP_ERROR` type in the C API. #[repr(C)] #[allow(missing_docs)] pub struct IcmpError { @@ -1176,8 +1189,8 @@ pub struct IcmpError { pub data: [u8; 494], } -// This type represents the anonymous union inside -// EFI_PXE_BASE_CODE_ICMP_ERROR. +/// Corresponds to the anonymous union inside +/// `EFI_PXE_BASE_CODE_ICMP_ERROR` in the C API. #[repr(C)] #[allow(missing_docs)] pub union IcmpErrorUnion { @@ -1187,8 +1200,8 @@ pub union IcmpErrorUnion { pub echo: IcmpErrorEcho, } -// This type represents on of the fields in the anonymous union inside -// EFI_PXE_BASE_CODE_ICMP_ERROR. +/// Corresponds to the `Echo` field in the anonymous union inside +/// `EFI_PXE_BASE_CODE_ICMP_ERROR` in the C API. #[repr(C)] #[derive(Clone, Copy)] #[allow(missing_docs)] @@ -1197,7 +1210,9 @@ pub struct IcmpErrorEcho { pub sequence: u16, } -// EFI_PXE_BASE_CODE_TFTP_ERROR +/// A TFTP error packet. +/// +/// Corresponds to the `EFI_PXE_BASE_CODE_TFTP_ERROR` type in the C API. #[repr(C)] #[allow(missing_docs)] pub struct TftpError {