Skip to content

std: sys: net: uefi: Implement TCP4 connect #139254

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 31 additions & 28 deletions library/std/src/sys/net/connection/uefi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,117 +4,120 @@ use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr};
use crate::sys::unsupported;
use crate::time::Duration;

pub struct TcpStream(!);
mod tcp;
pub(crate) mod tcp4;

pub struct TcpStream(#[expect(dead_code)] tcp::Tcp);

impl TcpStream {
pub fn connect(_: io::Result<&SocketAddr>) -> io::Result<TcpStream> {
unsupported()
pub fn connect(addr: io::Result<&SocketAddr>) -> io::Result<TcpStream> {
tcp::Tcp::connect(addr?).map(Self)
}

pub fn connect_timeout(_: &SocketAddr, _: Duration) -> io::Result<TcpStream> {
unsupported()
}

pub fn set_read_timeout(&self, _: Option<Duration>) -> io::Result<()> {
self.0
unsupported()
}

pub fn set_write_timeout(&self, _: Option<Duration>) -> io::Result<()> {
self.0
unsupported()
}

pub fn read_timeout(&self) -> io::Result<Option<Duration>> {
self.0
unsupported()
}

pub fn write_timeout(&self) -> io::Result<Option<Duration>> {
self.0
unsupported()
}

pub fn peek(&self, _: &mut [u8]) -> io::Result<usize> {
self.0
unsupported()
}

pub fn read(&self, _: &mut [u8]) -> io::Result<usize> {
self.0
unsupported()
}

pub fn read_buf(&self, _buf: BorrowedCursor<'_>) -> io::Result<()> {
self.0
unsupported()
}

pub fn read_vectored(&self, _: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
self.0
unsupported()
}

pub fn is_read_vectored(&self) -> bool {
self.0
false
}

pub fn write(&self, _: &[u8]) -> io::Result<usize> {
self.0
unsupported()
}

pub fn write_vectored(&self, _: &[IoSlice<'_>]) -> io::Result<usize> {
self.0
unsupported()
}

pub fn is_write_vectored(&self) -> bool {
self.0
false
}

pub fn peer_addr(&self) -> io::Result<SocketAddr> {
self.0
unsupported()
}

pub fn socket_addr(&self) -> io::Result<SocketAddr> {
self.0
unsupported()
}

pub fn shutdown(&self, _: Shutdown) -> io::Result<()> {
self.0
unsupported()
}

pub fn duplicate(&self) -> io::Result<TcpStream> {
self.0
unsupported()
}

pub fn set_linger(&self, _: Option<Duration>) -> io::Result<()> {
self.0
unsupported()
}

pub fn linger(&self) -> io::Result<Option<Duration>> {
self.0
unsupported()
}

pub fn set_nodelay(&self, _: bool) -> io::Result<()> {
self.0
unsupported()
}

pub fn nodelay(&self) -> io::Result<bool> {
self.0
unsupported()
}

pub fn set_ttl(&self, _: u32) -> io::Result<()> {
self.0
unsupported()
}

pub fn ttl(&self) -> io::Result<u32> {
self.0
unsupported()
}

pub fn take_error(&self) -> io::Result<Option<io::Error>> {
self.0
unsupported()
}

pub fn set_nonblocking(&self, _: bool) -> io::Result<()> {
self.0
unsupported()
}
}

impl fmt::Debug for TcpStream {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0
todo!()
}
}

Expand Down
21 changes: 21 additions & 0 deletions library/std/src/sys/net/connection/uefi/tcp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use super::tcp4;
use crate::io;
use crate::net::SocketAddr;

pub(crate) enum Tcp {
V4(#[expect(dead_code)] tcp4::Tcp4),
}

impl Tcp {
pub(crate) fn connect(addr: &SocketAddr) -> io::Result<Self> {
match addr {
SocketAddr::V4(x) => {
let temp = tcp4::Tcp4::new()?;
temp.configure(true, Some(x), None)?;
temp.connect()?;
Ok(Tcp::V4(temp))
}
SocketAddr::V6(_) => todo!(),
}
}
}
124 changes: 124 additions & 0 deletions library/std/src/sys/net/connection/uefi/tcp4.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use r_efi::efi::{self, Status};
use r_efi::protocols::tcp4;

use crate::io;
use crate::net::SocketAddrV4;
use crate::ptr::NonNull;
use crate::sync::atomic::{AtomicBool, Ordering};
use crate::sys::pal::helpers;

const TYPE_OF_SERVICE: u8 = 8;
const TIME_TO_LIVE: u8 = 255;

pub(crate) struct Tcp4 {
protocol: NonNull<tcp4::Protocol>,
flag: AtomicBool,
#[expect(dead_code)]
service_binding: helpers::ServiceProtocol,
}

const DEFAULT_ADDR: efi::Ipv4Address = efi::Ipv4Address { addr: [0u8; 4] };

impl Tcp4 {
pub(crate) fn new() -> io::Result<Self> {
let service_binding = helpers::ServiceProtocol::open(tcp4::SERVICE_BINDING_PROTOCOL_GUID)?;
let protocol = helpers::open_protocol(service_binding.child_handle(), tcp4::PROTOCOL_GUID)?;

Ok(Self { service_binding, protocol, flag: AtomicBool::new(false) })
}

pub(crate) fn configure(
&self,
active: bool,
remote_address: Option<&SocketAddrV4>,
station_address: Option<&SocketAddrV4>,
) -> io::Result<()> {
let protocol = self.protocol.as_ptr();

let (remote_address, remote_port) = if let Some(x) = remote_address {
(helpers::ipv4_to_r_efi(*x.ip()), x.port())
} else {
(DEFAULT_ADDR, 0)
};

// FIXME: Remove when passive connections with proper subnet handling are added
assert!(station_address.is_none());
let use_default_address = efi::Boolean::TRUE;
let (station_address, station_port) = (DEFAULT_ADDR, 0);
let subnet_mask = helpers::ipv4_to_r_efi(crate::net::Ipv4Addr::new(0, 0, 0, 0));

let mut config_data = tcp4::ConfigData {
type_of_service: TYPE_OF_SERVICE,
time_to_live: TIME_TO_LIVE,
access_point: tcp4::AccessPoint {
use_default_address,
remote_address,
remote_port,
active_flag: active.into(),
station_address,
station_port,
subnet_mask,
},
control_option: crate::ptr::null_mut(),
};

let r = unsafe { ((*protocol).configure)(protocol, &mut config_data) };
if r.is_error() { Err(crate::io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) }
}

pub(crate) fn connect(&self) -> io::Result<()> {
let evt = unsafe { self.create_evt() }?;
let completion_token =
tcp4::CompletionToken { event: evt.as_ptr(), status: Status::SUCCESS };

let protocol = self.protocol.as_ptr();
let mut conn_token = tcp4::ConnectionToken { completion_token };

let r = unsafe { ((*protocol).connect)(protocol, &mut conn_token) };
if r.is_error() {
return Err(io::Error::from_raw_os_error(r.as_usize()));
}

self.wait_for_flag().unwrap();

if completion_token.status.is_error() {
Err(io::Error::from_raw_os_error(completion_token.status.as_usize()))
} else {
Ok(())
}
}

unsafe fn create_evt(&self) -> io::Result<helpers::OwnedEvent> {
self.flag.store(false, Ordering::Relaxed);
helpers::OwnedEvent::new(
efi::EVT_NOTIFY_SIGNAL,
efi::TPL_CALLBACK,
Some(toggle_atomic_flag),
Some(unsafe { NonNull::new_unchecked(self.flag.as_ptr().cast()) }),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes wait_for_flag unsound – there is no guarantee that the structure will not be moved in the time between the calls, which would invalidate this pointer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I use Condvar then? Or is there a way to have Atomic on Heap or something?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd remove the flag, make the notification function a no-op and use CheckEvent on the event.

Copy link
Contributor Author

@Ayush1325 Ayush1325 Apr 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The event is of type Signal. The CheckEvent doc states the following

If Event is of type EVT_NOTIFY_SIGNAL, then EFI_INVALID_PARAMETER is returned.

The reason the event is of type Signal is because the tcp4 protocol connect doc states the following

The Event to signal after request is finished and Status field is updated by the EFI TCPv4 Protocol driver. The
type of Event must be EVT_NOTIFY_SIGNAL, and its Task Priority Level (TPL) must be lower than or equal to TPL_CALLBACK.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I missed that. Then the easiest way is probably just to ensure that the flag outlives the event. Why not just inline wait_for_flag and make flag a local variable? Alternatively, make create_evt unsafe.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I missed that. Then the easiest way is probably just to ensure that the flag outlives the event. Why not just inline wait_for_flag and make flag a local variable? Alternatively, make create_evt unsafe.

I would prefer not to inline wait_for_flag since that will be repeated in almost all other implementations soon enough (read, write, close, etc). I have made create_evt unsafe. Is that ok?

)
}

fn wait_for_flag(&self) -> io::Result<()> {
while !self.flag.load(Ordering::Relaxed) {
self.poll()?;
}

Ok(())
}

fn poll(&self) -> io::Result<()> {
let protocol = self.protocol.as_ptr();
let r = unsafe { ((*protocol).poll)(protocol) };

if r.is_error() {
return Err(io::Error::from_raw_os_error(r.as_usize()));
} else {
Ok(())
}
}
}

extern "efiapi" fn toggle_atomic_flag(_: r_efi::efi::Event, ctx: *mut crate::ffi::c_void) {
let flag = unsafe { AtomicBool::from_ptr(ctx.cast()) };
flag.store(true, Ordering::Relaxed);
}
10 changes: 8 additions & 2 deletions library/std/src/sys/pal/uefi/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,6 @@ pub(crate) struct ServiceProtocol {
}

impl ServiceProtocol {
#[expect(dead_code)]
pub(crate) fn open(service_guid: r_efi::efi::Guid) -> io::Result<Self> {
let handles = locate_handles(service_guid)?;

Expand All @@ -670,7 +669,6 @@ impl ServiceProtocol {
Err(io::const_error!(io::ErrorKind::NotFound, "no service binding protocol found"))
}

#[expect(dead_code)]
pub(crate) fn child_handle(&self) -> NonNull<crate::ffi::c_void> {
self.child_handle
}
Expand Down Expand Up @@ -732,6 +730,10 @@ impl OwnedEvent {
}
}

pub(crate) fn as_ptr(&self) -> efi::Event {
self.0.as_ptr()
}

pub(crate) fn into_raw(self) -> *mut crate::ffi::c_void {
let r = self.0.as_ptr();
crate::mem::forget(self);
Expand All @@ -755,3 +757,7 @@ impl Drop for OwnedEvent {
}
}
}

pub(crate) const fn ipv4_to_r_efi(addr: crate::net::Ipv4Addr) -> efi::Ipv4Address {
efi::Ipv4Address { addr: addr.octets() }
}
Loading