diff --git a/uefi-raw/src/protocol/network/mod.rs b/uefi-raw/src/protocol/network/mod.rs index f2a016cfc..84bc11091 100644 --- a/uefi-raw/src/protocol/network/mod.rs +++ b/uefi-raw/src/protocol/network/mod.rs @@ -6,4 +6,5 @@ pub mod ip4; pub mod ip4_config2; pub mod pxe; pub mod snp; +pub mod tcpv4; pub mod tls; diff --git a/uefi-raw/src/protocol/network/tcpv4.rs b/uefi-raw/src/protocol/network/tcpv4.rs new file mode 100644 index 000000000..3e3a07c5f --- /dev/null +++ b/uefi-raw/src/protocol/network/tcpv4.rs @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{Event, Guid, Ipv4Address, Status, guid}; +use core::{ + ffi::c_void, + fmt::{Debug, Formatter}, + marker::PhantomData, + ptr::NonNull, +}; + +pub type UnmodelledPtr = NonNull; + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4AccessPoint { + pub use_default_address: bool, + pub station_address: Ipv4Address, + pub subnet_mask: Ipv4Address, + pub station_port: u16, + pub remote_address: Ipv4Address, + pub remote_port: u16, + pub active_flag: bool, +} + +impl Tcpv4AccessPoint { + pub fn new(connection_mode: Tcpv4ConnectionMode) -> Tcpv4AccessPoint { + let (remote_ip, remote_port, is_client) = match connection_mode { + Tcpv4ConnectionMode::Client(params) => (params.remote_ip, params.remote_port, true), + Tcpv4ConnectionMode::Server => (Ipv4Address([0, 0, 0, 0]), 0, false), + }; + Self { + use_default_address: true, + // These two fields are meaningless because we set use_default_address above + station_address: Ipv4Address([0, 0, 0, 0]), + subnet_mask: Ipv4Address([0, 0, 0, 0]), + // Chosen on-demand + station_port: 0, + remote_address: remote_ip, + remote_port, + active_flag: is_client, + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4Option { + pub receive_buffer_size: u32, + pub send_buffer_size: u32, + pub max_syn_back_log: u32, + pub connection_timeout: u32, + pub data_retries: u32, + pub fin_timeout: u32, + pub time_wait_timeout: u32, + pub keep_alive_probes: u32, + pub keep_alive_time: u32, + pub keep_alive_interval: u32, + pub enable_nagle: bool, + pub enable_time_stamp: bool, + pub enable_window_scaling: bool, + pub enable_selective_ack: bool, + pub enable_path_mtu_discovery: bool, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4ConfigData<'a> { + pub type_of_service: u8, + pub time_to_live: u8, + pub access_point: Tcpv4AccessPoint, + pub option: Option<&'a Tcpv4Option>, +} + +#[derive(Debug)] +pub struct Tcpv4ClientConnectionModeParams { + pub remote_ip: Ipv4Address, + pub remote_port: u16, +} + +impl Tcpv4ClientConnectionModeParams { + pub fn new(remote_ip: Ipv4Address, remote_port: u16) -> Tcpv4ClientConnectionModeParams { + Self { + remote_ip, + remote_port, + } + } +} + +#[derive(Debug)] +pub enum Tcpv4ConnectionMode { + Client(Tcpv4ClientConnectionModeParams), + // TODO(PT): There may be parameters we need to model when operating as a server + Server, +} + +impl<'a> Tcpv4ConfigData<'a> { + pub fn new( + connection_mode: Tcpv4ConnectionMode, + options: Option<&'a Tcpv4Option>, + ) -> Tcpv4ConfigData<'a> { + Tcpv4ConfigData { + type_of_service: 0, + time_to_live: 255, + access_point: Tcpv4AccessPoint::new(connection_mode), + option: options, + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4IoToken<'a> { + pub completion_token: Tcpv4CompletionToken, + pub packet: Tcpv4Packet<'a>, +} + +#[repr(C)] +pub union Tcpv4Packet<'a> { + pub rx_data: Option<&'a Tcpv4ReceiveData<'a>>, + pub tx_data: Option<&'a Tcpv4TransmitData<'a>>, +} + +impl Debug for Tcpv4Packet<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Tcpv4Packet").finish() + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4CompletionToken { + pub event: Event, + pub status: Status, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4FragmentData<'a> { + pub fragment_length: u32, + pub fragment_buf: *const c_void, + pub _pd: PhantomData<&'a mut [u8]>, +} + +impl<'a> Tcpv4FragmentData<'a> { + pub fn with_buf(buf: &'a [u8]) -> Tcpv4FragmentData<'a> { + Self { + fragment_length: buf.len() as u32, + fragment_buf: buf.as_ptr() as *const c_void, + _pd: PhantomData, + } + } + + pub fn with_mut_buf(buf: &'a mut [u8]) -> Tcpv4FragmentData<'a> { + Self { + fragment_length: buf.len() as u32, + fragment_buf: buf.as_ptr() as *const c_void, + _pd: PhantomData, + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub enum Tcpv4ConnectionState { + Closed = 0, + Listen = 1, + SynSent = 2, + SynReceived = 3, + Established = 4, + FinWait1 = 5, + FinWait2 = 6, + Closing = 7, + TimeWait = 8, + CloseWait = 9, + LastAck = 10, +} + +/// Current IPv4 configuration data used by the TCPv4 instance. +#[derive(Debug)] +#[repr(C)] +pub struct Ipv4ModeData<'a> { + pub is_started: bool, + pub max_packet_size: u32, + pub config_data: Ipv4ConfigData, + pub is_configured: bool, + pub group_count: bool, + pub group_table: &'a [Ipv4Address; 0], + pub route_count: u32, + pub ip4_route_table: &'a [Ipv4RouteTable; 0], + pub icmp_type_count: u32, + pub icmp_type_list: &'a [Ipv4IcmpType; 0], +} + +#[derive(Debug)] +#[repr(C)] +pub struct Ipv4ConfigData { + pub default_protocol: u8, + pub accept_any_protocol: bool, + pub accept_icmp_errors: bool, + pub accept_broadcast: bool, + pub accept_promiscuous: bool, + pub use_default_address: bool, + pub station_address: Ipv4Address, + pub subnet_mask: Ipv4Address, + pub type_of_service: u8, + pub time_to_live: u8, + pub do_not_fragment: bool, + pub raw_data: bool, + pub receive_timeout: u32, + pub transmit_timeout: u32, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Ipv4RouteTable { + pub subnet_address: Ipv4Address, + pub subnet_mask: Ipv4Address, + pub gateway_address: Ipv4Address, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Ipv4IcmpType { + pub type_: u8, + pub code: u8, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4Protocol { + // TODO: should this &mut? It's plain *This in the spec but + // HttpProtocol uses `*const Self` for `get_mode_data`. + #[allow(clippy::type_complexity)] + pub get_mode_data: extern "efiapi" fn( + this: &Self, + out_connection_state: Option<&mut Tcpv4ConnectionState>, + out_config_data: Option, + out_ip4_mode_data: Option<&mut Ipv4ModeData>, + out_managed_network_config_data: Option, + out_simple_network_mode: Option, + ) -> Status, + pub configure: extern "efiapi" fn(&mut Self, config_data: Option<&Tcpv4ConfigData>) -> Status, + pub routes: extern "efiapi" fn( + &mut Self, + delete_route: bool, + subnet_address: &Ipv4Address, + subnet_mask: &Ipv4Address, + gateway_address: &Ipv4Address, + ) -> Status, + pub connect: extern "efiapi" fn(&mut Self, connection_token: &Tcpv4CompletionToken) -> Status, + pub accept: extern "efiapi" fn(&mut Self, listen_token: UnmodelledPtr) -> Status, + pub transmit: extern "efiapi" fn(&mut Self, token: &Tcpv4IoToken) -> Status, + pub receive: extern "efiapi" fn(&mut Self, token: &Tcpv4IoToken) -> Status, + pub close: extern "efiapi" fn(&mut Self, close_token: UnmodelledPtr) -> Status, + pub cancel: extern "efiapi" fn(&mut Self, completion_token: UnmodelledPtr) -> Status, + pub poll: extern "efiapi" fn(&mut Self) -> Status, +} + +impl Tcpv4Protocol { + pub const GUID: Guid = guid!("65530BC7-A359-410F-B010-5AADC7EC2B62"); + pub const SERVICE_BINDING_GUID: Guid = guid!("00720665-67EB-4a99-BAF7-D3C33A1C7CC9"); +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4ReceiveData<'a> { + pub urgent: bool, + pub data_length: u32, + pub fragment_count: u32, + pub fragment_table: [Tcpv4FragmentData<'a>; 1], +} + +#[derive(Debug)] +#[repr(C)] +pub struct Tcpv4TransmitData<'a> { + pub push: bool, + pub urgent: bool, + pub data_length: u32, + pub fragment_count: u32, + pub fragment_table: [Tcpv4FragmentData<'a>; 1], +} diff --git a/uefi/Cargo.toml b/uefi/Cargo.toml index 1048c1ab4..9e7f4387e 100644 --- a/uefi/Cargo.toml +++ b/uefi/Cargo.toml @@ -43,7 +43,7 @@ uguid.workspace = true cfg-if = "1.0.0" ucs2 = "0.3.3" uefi-macros = "0.18.1" -uefi-raw = "0.11.0" +uefi-raw.path = "../uefi-raw" qemu-exit = { version = "3.0.2", optional = true } [package.metadata.docs.rs] diff --git a/uefi/src/proto/network/mod.rs b/uefi/src/proto/network/mod.rs index 925287f2c..b0d1d11b2 100644 --- a/uefi/src/proto/network/mod.rs +++ b/uefi/src/proto/network/mod.rs @@ -8,6 +8,7 @@ pub mod http; pub mod ip4config2; pub mod pxe; pub mod snp; +pub mod tcpv4; pub use uefi_raw::MacAddress; diff --git a/uefi/src/proto/network/tcpv4.rs b/uefi/src/proto/network/tcpv4.rs new file mode 100755 index 000000000..8439e48c0 --- /dev/null +++ b/uefi/src/proto/network/tcpv4.rs @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +#![cfg(feature = "alloc")] + +//! TCPv4 protocol. +//! +//! See [Tcpv4]. + +use crate::{ + Error, Event, Handle, Result, ResultExt, Status, StatusExt, + boot::{self, EventType, Tpl}, + proto::unsafe_protocol, +}; +use core::{fmt::Debug, ptr, time::Duration}; +use uefi_raw::protocol::{ + driver::ServiceBindingProtocol, + network::tcpv4::{ + Ipv4ModeData, Tcpv4CompletionToken, Tcpv4ConfigData, Tcpv4ConnectionMode, + Tcpv4ConnectionState, Tcpv4FragmentData, Tcpv4IoToken, Tcpv4Packet, Tcpv4Protocol, + Tcpv4ReceiveData, Tcpv4TransmitData, + }, +}; + +fn make_completion_token(event: &Event) -> Tcpv4CompletionToken { + // Safety: The lifetime of this token is bound by the lifetime + // of the ManagedEvent. + let event_clone = unsafe { event.unsafe_clone() }; + Tcpv4CompletionToken { + event: event_clone.as_ptr(), + status: Status::SUCCESS, + } +} + +fn make_io_token<'a>( + event: &Event, + tx: Option<&'a Tcpv4TransmitData>, + rx: Option<&'a Tcpv4ReceiveData>, +) -> Tcpv4IoToken<'a> { + let packet = { + if tx.is_some() { + Tcpv4Packet { tx_data: tx } + } else { + let rx_ref = rx.as_ref(); + rx_ref.expect("Either RX or TX data handles must be provided"); + Tcpv4Packet { rx_data: rx } + } + }; + Tcpv4IoToken { + completion_token: make_completion_token(event), + packet, + } +} + +/// A TCPv4 connection. +/// +/// # Examples +/// +/// ```no_run +/// # fn hello_world() -> uefi::Result { +/// extern crate alloc; +/// +/// use alloc::string::String; +/// use uefi::{ +/// boot, print, println, +/// proto::network::tcpv4::{Tcpv4, Tcpv4ServiceBinding}, +/// }; +/// use uefi_raw::{ +/// Ipv4Address, +/// protocol::network::tcpv4::{Tcpv4ClientConnectionModeParams, Tcpv4ConnectionMode}, +/// }; +/// +/// let addr = Ipv4Address([192, 0, 2, 2]); +/// let port = 5050; +/// +/// println!("Connecting to {addr:?}:{port}..."); +/// let mut tcp = { +/// let tcp_svc_handle = boot::get_handle_for_protocol::()?; +/// let mut tcp_svc_proto = +/// boot::open_protocol_exclusive::(tcp_svc_handle)?; +/// let tcp_proto_handle = tcp_svc_proto.create_child()?; +/// let mut tcp_proto = boot::open_protocol_exclusive::(tcp_proto_handle)?; +/// tcp_proto +/// .configure(Tcpv4ConnectionMode::Client( +/// Tcpv4ClientConnectionModeParams::new(addr, port), +/// )) +/// .expect("configure failed"); +/// tcp_proto.connect()?; +/// tcp_proto +/// }; +/// +/// let tx_msg = "Hello"; +/// println!("Sending {tx_msg:?} over TCP..."); +/// tcp.transmit(tx_msg.as_bytes())?; +/// +/// print!("Received "); +/// let mut buf = [0_u8; 64]; +/// let n = tcp.receive(&mut buf)?; +/// let rx_string = String::from_utf8_lossy(&buf[..n]); +/// println!("{rx_string:?}"); +/// # Ok(()) +/// # } +/// ``` +#[derive(Debug)] +#[repr(transparent)] +#[unsafe_protocol(Tcpv4Protocol::GUID)] +pub struct Tcpv4(pub Tcpv4Protocol); + +impl Tcpv4 { + /// See + pub fn configure( + &mut self, + connection_mode: Tcpv4ConnectionMode, + ) -> uefi::Result<(), &'static str> { + let configuration = Tcpv4ConfigData::new(connection_mode, None); + // Maximum timeout of 10 seconds + for _ in 0..10 { + let result = (self.0.configure)(&mut self.0, Some(&configuration)); + if result == Status::SUCCESS { + log::debug!("Configured connection! {result:?}"); + return Ok(()); + } else if result == Status::NO_MAPPING { + log::debug!("DHCP still running, waiting..."); + boot::stall(Duration::from_secs(1)); + } else { + log::warn!("Error {result:?}, will spin and try again"); + boot::stall(Duration::from_secs(1)); + } + } + Err(Error::new( + Status::PROTOCOL_ERROR, + "timeout before configuring the connection succeeded", + )) + } + + /// See + pub fn get_tcp_connection_state(&self) -> Result { + let mut connection_state = core::mem::MaybeUninit::::uninit(); + let connection_state_ptr = connection_state.as_mut_ptr(); + unsafe { + (self.0.get_mode_data)( + &self.0, + Some(&mut *connection_state_ptr), + None, + None, + None, + None, + ) + .to_result()?; + Ok(connection_state.assume_init()) + } + } + + /// See + pub fn get_ipv4_mode_data(&self) -> Result> { + let mut mode_data = core::mem::MaybeUninit::::uninit(); + let mode_data_ptr = mode_data.as_mut_ptr(); + unsafe { + (self.0.get_mode_data)(&self.0, None, None, Some(&mut *mode_data_ptr), None, None) + .to_result()?; + Ok(mode_data.assume_init()) + } + } + + /// See + pub fn connect(&mut self) -> Result { + // SAFETY: safe because there is no callback nor callback-data. + let event = + unsafe { boot::create_event(EventType::NOTIFY_WAIT, Tpl::CALLBACK, Some(noop), None) }?; + let completion_token = make_completion_token(&event); + (self.0.connect)(&mut self.0, &completion_token).to_result()?; + unsafe { + boot::wait_for_event(&mut [event.unsafe_clone()]).expect("can't fail waiting for event") + }; + Ok(()) + } + + /// See + pub fn transmit(&mut self, data: &[u8]) -> Result { + // SAFETY: safe because there is no callback nor callback-data. + let event = + unsafe { boot::create_event(EventType::NOTIFY_WAIT, Tpl::CALLBACK, Some(noop), None) }?; + let tx_data = Tcpv4TransmitData { + push: false, + urgent: false, + data_length: data.len() as u32, + fragment_count: 1, + fragment_table: [Tcpv4FragmentData::with_buf(data)], + }; + let io_token = make_io_token(&event, Some(&tx_data), None); + (self.0.transmit)(&mut self.0, &io_token).to_result()?; + // See docs on `poll` for why this is crucial for performance. + self.poll()?; + unsafe { boot::wait_for_event(&mut [event.unsafe_clone()]).discard_errdata()? }; + Ok(()) + } + + /// Receives data from the remote connection. On success, returns + /// the number of bytes read. + /// + /// See + pub fn receive(&mut self, buf: &mut [u8]) -> Result { + let rx_data_len = { + // SAFETY: safe because there is no callback nor callback-data. + let event = unsafe { + boot::create_event(EventType::NOTIFY_WAIT, Tpl::CALLBACK, Some(noop), None) + }?; + let rx_data = Tcpv4ReceiveData { + urgent: false, + data_length: buf.len() as u32, + fragment_count: 1, + fragment_table: [Tcpv4FragmentData::with_mut_buf(buf); 1], + }; + let io_token = make_io_token(&event, None, Some(&rx_data)); + (self.0.receive)(&mut self.0, &io_token).to_result()?; + // See docs on `poll` for why this is crucial for performance. + self.poll()?; + unsafe { boot::wait_for_event(&mut [event.unsafe_clone()]).discard_errdata()? }; + // SAFETY: calling `len` after a callback doesn't reflect + // the value updated by uefi. + unsafe { core::ptr::read_volatile(&rx_data.data_length as *const u32) as usize } + }; + Ok(rx_data_len) + } + + /// Receives the exact number of bytes required to fill buf. + /// + /// This function receives as many bytes as necessary to + /// completely fill the specified buffer buf. + pub fn receive_exact(&mut self, mut buf: &mut [u8]) -> Result { + while !buf.is_empty() { + let n = self.receive(buf)?; + buf = &mut buf[n..]; + } + Ok(()) + } + + /// **28.1.13. EFI_TCP4_PROTOCOL.Poll()**: + /// + /// > The Poll() function polls for incoming data packets and + /// > processes outgoing data packets. Network drivers and + /// > applications can call the EFI_IP4_PROTOCOL .Poll() + /// > function to increase the rate that data packets are + /// > moved between the communications device and the transmit + /// > and receive queues. + /// > + /// > In some systems the periodic timer event may not poll the + /// > underlying communications device fast enough to transmit + /// > and/or receive all data packets without missing incoming + /// > packets or dropping outgoing packets. Drivers and + /// > applications that are experiencing packet loss should + /// > try calling the EFI_IP4_PROTOCOL .Poll() function more + /// > often. + fn poll(&mut self) -> Result { + (self.0.poll)(&mut self.0).to_result()?; + Ok(()) + } +} + +/// TCPv4 Service Binding Protocol. +#[derive(Debug)] +#[repr(transparent)] +#[unsafe_protocol(Tcpv4Protocol::SERVICE_BINDING_GUID)] +pub struct Tcpv4ServiceBinding(ServiceBindingProtocol); + +impl Tcpv4ServiceBinding { + /// Create TCPv4 Protocol Handle. + pub fn create_child(&mut self) -> uefi::Result { + let mut c_handle = ptr::null_mut(); + let status; + let handle; + unsafe { + status = (self.0.create_child)(&mut self.0, &mut c_handle); + handle = Handle::from_ptr(c_handle); + }; + match status { + Status::SUCCESS => Ok(handle.unwrap()), + _ => Err(status.into()), + } + } + + /// Destroy TCPv4 Protocol Handle. + pub fn destroy_child(&mut self, handle: Handle) -> uefi::Result<()> { + let status = unsafe { (self.0.destroy_child)(&mut self.0, handle.as_ptr()) }; + match status { + Status::SUCCESS => Ok(()), + _ => Err(status.into()), + } + } +} + +extern "efiapi" fn noop(_event: Event, _context: Option>) {} + +mod example { + #[allow(dead_code)] + fn hello_world() -> uefi::Result { + use alloc::string::String; + use uefi::{ + boot, print, println, + proto::network::tcpv4::{Tcpv4, Tcpv4ServiceBinding}, + }; + use uefi_raw::{ + Ipv4Address, + protocol::network::tcpv4::{Tcpv4ClientConnectionModeParams, Tcpv4ConnectionMode}, + }; + + let addr = Ipv4Address([192, 0, 2, 2]); + let port = 5050; + + println!("Connecting to {addr:?}:{port}..."); + let mut tcp = { + let tcp_svc_handle = boot::get_handle_for_protocol::()?; + let mut tcp_svc_proto = + boot::open_protocol_exclusive::(tcp_svc_handle)?; + let tcp_proto_handle = tcp_svc_proto.create_child()?; + let mut tcp_proto = boot::open_protocol_exclusive::(tcp_proto_handle)?; + tcp_proto + .configure(Tcpv4ConnectionMode::Client( + Tcpv4ClientConnectionModeParams::new(addr, port), + )) + .expect("configure failed"); + tcp_proto.connect()?; + tcp_proto + }; + + let tx_msg = "Hello"; + println!("Sending {tx_msg:?} over TCP..."); + tcp.transmit(tx_msg.as_bytes())?; + + print!("Received "); + let mut buf = [0_u8; 64]; + let n = tcp.receive(&mut buf)?; + let rx_string = String::from_utf8_lossy(&buf[..n]); + println!("{rx_string:?}"); + + Ok(()) + } +}