fix(ironrdp-async)!: use static dispatch for NetworkClient trait (#1043)

- Rename `AsyncNetworkClient` to `NetworkClient`
- Replace dynamic dispatch (`Option<&mut dyn ...>`) with static dispatch
using generics (`&mut N where N: NetworkClient`)
- Reorder `connect_finalize` parameters for consistency across crates
This commit is contained in:
Benoît Cortier 2025-12-03 02:31:12 -05:00 committed by GitHub
parent cca323adab
commit bca6d190a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 123 additions and 146 deletions

View file

@ -1,4 +1,4 @@
use ironrdp_async::AsyncNetworkClient;
use ironrdp_async::NetworkClient;
use ironrdp_connector::sspi::credssp::{
CredSspServer, CredentialsProxy, ServerError, ServerMode, ServerState, TsRequest,
};
@ -71,7 +71,7 @@ impl CredentialsProxy for CredentialsProxyImpl<'_> {
pub(crate) async fn resolve_generator(
generator: &mut CredsspProcessGenerator<'_>,
network_client: &mut dyn AsyncNetworkClient,
network_client: &mut impl NetworkClient,
) -> Result<ServerState, ServerError> {
let mut state = generator.start();

View file

@ -1,7 +1,7 @@
#![cfg_attr(doc, doc = include_str!("../README.md"))]
#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
use ironrdp_async::{single_sequence_step, AsyncNetworkClient, Framed, FramedRead, FramedWrite, StreamWrapper};
use ironrdp_async::{single_sequence_step, Framed, FramedRead, FramedWrite, NetworkClient, StreamWrapper};
use ironrdp_connector::sspi::credssp::EarlyUserAuthResult;
use ironrdp_connector::sspi::{AuthIdentity, KerberosServerConfig, Username};
use ironrdp_connector::{custom_err, general_err, ConnectorResult, ServerName};
@ -51,16 +51,17 @@ where
}
}
pub async fn accept_credssp<S>(
pub async fn accept_credssp<S, N>(
framed: &mut Framed<S>,
acceptor: &mut Acceptor,
network_client: &mut N,
client_computer_name: ServerName,
public_key: Vec<u8>,
kerberos_config: Option<KerberosServerConfig>,
network_client: Option<&mut dyn AsyncNetworkClient>,
) -> ConnectorResult<()>
where
S: FramedRead + FramedWrite,
N: NetworkClient,
{
let mut buf = WriteBuf::new();
@ -68,11 +69,11 @@ where
perform_credssp_step(
framed,
acceptor,
network_client,
&mut buf,
client_computer_name,
public_key,
kerberos_config,
network_client,
)
.await
} else {
@ -98,34 +99,73 @@ where
}
#[instrument(level = "trace", skip_all, ret)]
async fn perform_credssp_step<S>(
async fn perform_credssp_step<S, N>(
framed: &mut Framed<S>,
acceptor: &mut Acceptor,
network_client: &mut N,
buf: &mut WriteBuf,
client_computer_name: ServerName,
public_key: Vec<u8>,
kerberos_config: Option<KerberosServerConfig>,
network_client: Option<&mut dyn AsyncNetworkClient>,
) -> ConnectorResult<()>
where
S: FramedRead + FramedWrite,
N: NetworkClient,
{
assert!(acceptor.should_perform_credssp());
let AcceptorState::Credssp { protocol, .. } = acceptor.state else {
unreachable!()
};
async fn credssp_loop<S>(
let result = credssp_loop(
framed,
acceptor,
network_client,
buf,
client_computer_name,
public_key,
kerberos_config,
)
.await;
if protocol.intersects(nego::SecurityProtocol::HYBRID_EX) {
trace!(?result, "HYBRID_EX");
let result = if result.is_ok() {
EarlyUserAuthResult::Success
} else {
EarlyUserAuthResult::AccessDenied
};
buf.clear();
result
.to_buffer(&mut *buf)
.map_err(|e| ironrdp_connector::custom_err!("to_buffer", e))?;
let response = &buf[..result.buffer_len()];
framed
.write_all(response)
.await
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
result?;
acceptor.mark_credssp_as_done();
return Ok(());
async fn credssp_loop<S, N>(
framed: &mut Framed<S>,
acceptor: &mut Acceptor,
network_client: &mut N,
buf: &mut WriteBuf,
client_computer_name: ServerName,
public_key: Vec<u8>,
kerberos_config: Option<KerberosServerConfig>,
mut network_client: Option<&mut dyn AsyncNetworkClient>,
) -> ConnectorResult<()>
where
S: FramedRead + FramedWrite,
N: NetworkClient,
{
let creds = acceptor
.creds
@ -164,12 +204,7 @@ where
let result = {
let mut generator = sequence.process_ts_request(ts_request);
if let Some(network_client_ref) = network_client.as_deref_mut() {
resolve_generator(&mut generator, network_client_ref).await
} else {
generator.resolve_to_result()
}
resolve_generator(&mut generator, network_client).await
}; // drop generator
buf.clear();
@ -184,43 +219,7 @@ where
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
}
Ok(())
}
let result = credssp_loop(
framed,
acceptor,
buf,
client_computer_name,
public_key,
kerberos_config,
network_client,
)
.await;
if protocol.intersects(nego::SecurityProtocol::HYBRID_EX) {
trace!(?result, "HYBRID_EX");
let result = if result.is_ok() {
EarlyUserAuthResult::Success
} else {
EarlyUserAuthResult::AccessDenied
};
buf.clear();
result
.to_buffer(&mut *buf)
.map_err(|e| ironrdp_connector::custom_err!("to_buffer", e))?;
let response = &buf[..result.buffer_len()];
framed
.write_all(response)
.await
.map_err(|e| ironrdp_connector::custom_err!("write all", e))?;
}
result?;
acceptor.mark_credssp_as_done();
Ok(())
}

View file

@ -2,14 +2,14 @@ use ironrdp_connector::credssp::{CredsspProcessGenerator, CredsspSequence, Kerbe
use ironrdp_connector::sspi::credssp::ClientState;
use ironrdp_connector::sspi::generator::GeneratorState;
use ironrdp_connector::{
custom_err, general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult,
ServerName, State as _,
general_err, ClientConnector, ClientConnectorState, ConnectionResult, ConnectorError, ConnectorResult, ServerName,
State as _,
};
use ironrdp_core::WriteBuf;
use tracing::{debug, info, instrument, trace};
use crate::framed::{Framed, FramedRead, FramedWrite};
use crate::{single_sequence_step, AsyncNetworkClient};
use crate::{single_sequence_step, NetworkClient};
#[non_exhaustive]
pub struct ShouldUpgrade;
@ -49,28 +49,29 @@ pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector) -> Up
}
#[instrument(skip_all)]
pub async fn connect_finalize<S>(
pub async fn connect_finalize<S, N>(
_: Upgraded,
framed: &mut Framed<S>,
mut connector: ClientConnector,
framed: &mut Framed<S>,
network_client: &mut N,
server_name: ServerName,
server_public_key: Vec<u8>,
network_client: Option<&mut dyn AsyncNetworkClient>,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<ConnectionResult>
where
S: FramedRead + FramedWrite,
N: NetworkClient,
{
let mut buf = WriteBuf::new();
if connector.should_perform_credssp() {
perform_credssp_step(
framed,
&mut connector,
framed,
network_client,
&mut buf,
server_name,
server_public_key,
network_client,
kerberos_config,
)
.await?;
@ -91,7 +92,7 @@ where
async fn resolve_generator(
generator: &mut CredsspProcessGenerator<'_>,
network_client: &mut dyn AsyncNetworkClient,
network_client: &mut impl NetworkClient,
) -> ConnectorResult<ClientState> {
let mut state = generator.start();
@ -110,17 +111,18 @@ async fn resolve_generator(
}
#[instrument(level = "trace", skip_all)]
async fn perform_credssp_step<S>(
framed: &mut Framed<S>,
async fn perform_credssp_step<S, N>(
connector: &mut ClientConnector,
framed: &mut Framed<S>,
network_client: &mut N,
buf: &mut WriteBuf,
server_name: ServerName,
server_public_key: Vec<u8>,
mut network_client: Option<&mut dyn AsyncNetworkClient>,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<()>
where
S: FramedRead + FramedWrite,
N: NetworkClient,
{
assert!(connector.should_perform_credssp());
@ -141,15 +143,8 @@ where
loop {
let client_state = {
let mut generator = sequence.process_ts_request(ts_request);
if let Some(network_client_ref) = network_client.as_deref_mut() {
trace!("resolving network");
resolve_generator(&mut generator, network_client_ref).await?
} else {
generator
.resolve_to_result()
.map_err(|e| custom_err!("resolve without network client", e))?
}
trace!("resolving network");
resolve_generator(&mut generator, network_client).await?
}; // drop generator
buf.clear();

View file

@ -1,15 +1,14 @@
#![cfg_attr(doc, doc = include_str!("../README.md"))]
#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
use core::future::Future;
pub use bytes;
mod connector;
mod framed;
mod session;
use core::future::Future;
use core::pin::Pin;
use ironrdp_connector::sspi::generator::NetworkRequest;
use ironrdp_connector::ConnectorResult;
@ -17,9 +16,6 @@ pub use self::connector::*;
pub use self::framed::*;
// pub use self::session::*;
pub trait AsyncNetworkClient {
fn send<'a>(
&'a mut self,
network_request: &'a NetworkRequest,
) -> Pin<Box<dyn Future<Output = ConnectorResult<Vec<u8>>> + 'a>>;
pub trait NetworkClient {
fn send(&mut self, network_request: &NetworkRequest) -> impl Future<Output = ConnectorResult<Vec<u8>>>;
}

View file

@ -53,11 +53,11 @@ pub fn mark_as_upgraded(_: ShouldUpgrade, connector: &mut ClientConnector) -> Up
#[instrument(skip_all)]
pub fn connect_finalize<S>(
_: Upgraded,
framed: &mut Framed<S>,
mut connector: ClientConnector,
framed: &mut Framed<S>,
network_client: &mut impl NetworkClient,
server_name: ServerName,
server_public_key: Vec<u8>,
network_client: &mut impl NetworkClient,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<ConnectionResult>
where
@ -69,12 +69,12 @@ where
if connector.should_perform_credssp() {
perform_credssp_step(
framed,
&mut connector,
framed,
network_client,
&mut buf,
server_name,
server_public_key,
network_client,
kerberos_config,
)?;
}
@ -118,12 +118,12 @@ fn resolve_generator(
#[instrument(level = "trace", skip_all)]
fn perform_credssp_step<S>(
framed: &mut Framed<S>,
connector: &mut ClientConnector,
framed: &mut Framed<S>,
network_client: &mut impl NetworkClient,
buf: &mut WriteBuf,
server_name: ServerName,
server_public_key: Vec<u8>,
network_client: &mut impl NetworkClient,
kerberos_config: Option<KerberosConfig>,
) -> ConnectorResult<()>
where

View file

@ -240,11 +240,11 @@ async fn connect(
let connection_result = ironrdp_tokio::connect_finalize(
upgraded,
&mut upgraded_framed,
connector,
&mut upgraded_framed,
&mut ReqwestNetworkClient::new(),
(&config.destination).into(),
server_public_key,
Some(&mut ReqwestNetworkClient::new()),
None,
)
.await?;
@ -326,11 +326,11 @@ async fn connect_ws(
let connection_result = ironrdp_tokio::connect_finalize(
upgraded,
&mut framed,
connector,
&mut framed,
&mut ReqwestNetworkClient::new(),
(&config.destination).into(),
server_public_key,
Some(&mut ReqwestNetworkClient::new()),
None,
)
.await?;

View file

@ -349,10 +349,10 @@ impl RdpServer {
ironrdp_acceptor::accept_credssp(
&mut framed,
&mut acceptor,
&mut ironrdp_tokio::reqwest::ReqwestNetworkClient::new(),
client_name.into(),
pub_key.clone(),
None,
None,
)
.await?;
}

View file

@ -212,12 +212,12 @@ where
let mut upgraded_framed = ironrdp_tokio::TokioFramed::new(upgraded_stream);
let connection_result = ironrdp_async::connect_finalize(
upgraded,
&mut upgraded_framed,
connector,
&mut upgraded_framed,
&mut ironrdp_tokio::reqwest::ReqwestNetworkClient::new(),
"localhost".into(),
server_public_key,
None,
None,
)
.await
.expect("finalize connection");

View file

@ -1,6 +1,4 @@
use core::future::Future;
use core::net::{IpAddr, Ipv4Addr};
use core::pin::Pin;
use ironrdp_connector::{custom_err, general_err, ConnectorResult};
use reqwest::Client;
@ -9,18 +7,15 @@ use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
use tokio::net::{TcpStream, UdpSocket};
use url::Url;
use crate::AsyncNetworkClient;
use crate::NetworkClient;
pub struct ReqwestNetworkClient {
client: Option<Client>,
}
impl AsyncNetworkClient for ReqwestNetworkClient {
fn send<'a>(
&'a mut self,
network_request: &'a sspi::generator::NetworkRequest,
) -> Pin<Box<dyn Future<Output = ConnectorResult<Vec<u8>>> + 'a>> {
Box::pin(ReqwestNetworkClient::send_request(self, network_request))
impl NetworkClient for ReqwestNetworkClient {
async fn send(&mut self, network_request: &sspi::generator::NetworkRequest) -> ConnectorResult<Vec<u8>> {
ReqwestNetworkClient::send_request(self, network_request).await
}
}

View file

@ -1,53 +1,45 @@
use core::pin::Pin;
use futures_util::Future;
use ironrdp::connector::sspi::generator::NetworkRequest;
use ironrdp::connector::sspi::network_client::NetworkProtocol;
use ironrdp::connector::{custom_err, reason_err, ConnectorResult};
use ironrdp_futures::AsyncNetworkClient;
use ironrdp_futures::NetworkClient;
use tracing::debug;
#[derive(Debug)]
pub(crate) struct WasmNetworkClient;
impl AsyncNetworkClient for WasmNetworkClient {
fn send<'a>(
&'a mut self,
network_request: &'a NetworkRequest,
) -> Pin<Box<dyn Future<Output = ConnectorResult<Vec<u8>>> + 'a>> {
Box::pin(async move {
debug!(?network_request.protocol, ?network_request.url);
impl NetworkClient for WasmNetworkClient {
async fn send(&mut self, network_request: &NetworkRequest) -> ConnectorResult<Vec<u8>> {
debug!(?network_request.protocol, ?network_request.url);
match &network_request.protocol {
NetworkProtocol::Http | NetworkProtocol::Https => {
let body = js_sys::Uint8Array::from(network_request.data.as_slice());
match &network_request.protocol {
NetworkProtocol::Http | NetworkProtocol::Https => {
let body = js_sys::Uint8Array::from(network_request.data.as_slice());
let response = gloo_net::http::Request::post(network_request.url.as_str())
.header("keep-alive", "true")
.body(body)
.map_err(|e| custom_err!("failed to send KDC request", e))?
.send()
.await
.map_err(|e| custom_err!("failed to send KDC request", e))?;
let response = gloo_net::http::Request::post(network_request.url.as_str())
.header("keep-alive", "true")
.body(body)
.map_err(|e| custom_err!("failed to send KDC request", e))?
.send()
.await
.map_err(|e| custom_err!("failed to send KDC request", e))?;
if !response.ok() {
return Err(reason_err!(
"KdcProxy",
"HTTP status error ({} {})",
response.status(),
response.status_text(),
));
}
let body = response
.binary()
.await
.map_err(|e| custom_err!("failed to retrieve HTTP response", e))?;
Ok(body)
if !response.ok() {
return Err(reason_err!(
"KdcProxy",
"HTTP status error ({} {})",
response.status(),
response.status_text(),
));
}
unsupported => Err(reason_err!("CredSSP", "unsupported protocol: {unsupported:?}")),
let body = response
.binary()
.await
.map_err(|e| custom_err!("failed to retrieve HTTP response", e))?;
Ok(body)
}
})
unsupported => Err(reason_err!("CredSSP", "unsupported protocol: {unsupported:?}")),
}
}
}

View file

@ -979,11 +979,11 @@ async fn connect(
let connection_result = ironrdp_futures::connect_finalize(
upgraded,
&mut framed,
connector,
&mut framed,
&mut WasmNetworkClient,
(&destination).into(),
server_public_key,
Some(&mut WasmNetworkClient),
url::Url::parse(kdc_proxy_url.unwrap_or_default().as_str()) // if kdc_proxy_url does not exit, give url parser a empty string, it will fail anyway and map to a None
.ok()
.map(|url| KerberosConfig {

View file

@ -258,11 +258,11 @@ fn connect(
let mut network_client = ReqwestNetworkClient;
let connection_result = ironrdp_blocking::connect_finalize(
upgraded,
&mut upgraded_framed,
connector,
&mut upgraded_framed,
&mut network_client,
server_name.into(),
server_public_key,
&mut network_client,
None,
)
.context("finalize connection")?;