mirror of
https://github.com/Devolutions/IronRDP.git
synced 2025-07-07 17:45:01 +00:00
fix: inject socket local address for the client addr (#759)
We used to inject the resolved target server address, but that is not what is expected. Server typically ignores this field so this was not a problem up until now.
This commit is contained in:
parent
ec1832bba0
commit
712da42ded
14 changed files with 125 additions and 112 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2455,6 +2455,7 @@ dependencies = [
|
|||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"transport",
|
||||
"url",
|
||||
"uuid",
|
||||
"whoami",
|
||||
"windows 0.61.1",
|
||||
|
|
|
@ -77,6 +77,7 @@ semver = "1"
|
|||
raw-window-handle = "0.6"
|
||||
uuid = { version = "1.16" }
|
||||
x509-cert = { version = "0.2", default-features = false, features = ["std"] }
|
||||
url = "2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = { version = "0.61", features = ["Win32_Foundation"] }
|
||||
|
|
|
@ -8,6 +8,7 @@ use ironrdp::connector::{self, Credentials};
|
|||
use ironrdp::pdu::rdp::capability_sets::MajorPlatformType;
|
||||
use ironrdp::pdu::rdp::client_info::PerformanceFlags;
|
||||
use tap::prelude::*;
|
||||
use url::Url;
|
||||
|
||||
const DEFAULT_WIDTH: u16 = 1920;
|
||||
const DEFAULT_HEIGHT: u16 = 1080;
|
||||
|
@ -131,7 +132,7 @@ impl From<&Destination> for connector::ServerName {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RDCleanPathConfig {
|
||||
pub url: String,
|
||||
pub url: Url,
|
||||
pub auth_token: String,
|
||||
}
|
||||
|
||||
|
@ -161,7 +162,7 @@ struct Args {
|
|||
|
||||
/// Proxy URL to connect to for the RDCleanPath
|
||||
#[clap(long, requires("rdcleanpath_token"))]
|
||||
rdcleanpath_url: Option<String>,
|
||||
rdcleanpath_url: Option<Url>,
|
||||
|
||||
/// Authentication token to insert in the RDCleanPath packet
|
||||
#[clap(long, requires("rdcleanpath_url"))]
|
||||
|
|
|
@ -121,18 +121,17 @@ async fn connect(
|
|||
) -> ConnectorResult<(ConnectionResult, UpgradedFramed)> {
|
||||
let dest = format!("{}:{}", config.destination.name(), config.destination.port());
|
||||
|
||||
let stream = TcpStream::connect(dest)
|
||||
let socket = TcpStream::connect(dest)
|
||||
.await
|
||||
.map_err(|e| connector::custom_err!("TCP connect", e))?;
|
||||
|
||||
let server_addr = stream
|
||||
.peer_addr()
|
||||
.map_err(|e| connector::custom_err!("Peer address", e))?;
|
||||
let client_addr = socket
|
||||
.local_addr()
|
||||
.map_err(|e| connector::custom_err!("get socket local address", e))?;
|
||||
|
||||
let mut framed = ironrdp_tokio::TokioFramed::new(stream);
|
||||
let mut framed = ironrdp_tokio::TokioFramed::new(socket);
|
||||
|
||||
let mut connector = connector::ClientConnector::new(config.connector.clone())
|
||||
.with_client_addr(server_addr)
|
||||
let mut connector = connector::ClientConnector::new(config.connector.clone(), client_addr)
|
||||
.with_static_channel(
|
||||
ironrdp::dvc::DrdynvcClient::new().with_dynamic_channel(DisplayControlClient::new(|_| Ok(Vec::new()))),
|
||||
)
|
||||
|
@ -184,7 +183,26 @@ async fn connect_ws(
|
|||
rdcleanpath: &RDCleanPathConfig,
|
||||
cliprdr_factory: Option<&(dyn CliprdrBackendFactory + Send)>,
|
||||
) -> ConnectorResult<(ConnectionResult, UpgradedFramed)> {
|
||||
let (ws, _) = tokio_tungstenite::connect_async(&rdcleanpath.url)
|
||||
let hostname = rdcleanpath
|
||||
.url
|
||||
.host_str()
|
||||
.ok_or_else(|| connector::general_err!("host missing from the URL"))?;
|
||||
|
||||
let port = rdcleanpath.url.port_or_known_default().unwrap_or(443);
|
||||
|
||||
let socket = TcpStream::connect((hostname, port))
|
||||
.await
|
||||
.map_err(|e| connector::custom_err!("TCP connect", e))?;
|
||||
|
||||
socket
|
||||
.set_nodelay(true)
|
||||
.map_err(|e| connector::custom_err!("set TCP_NODELAY", e))?;
|
||||
|
||||
let client_addr = socket
|
||||
.local_addr()
|
||||
.map_err(|e| connector::custom_err!("get socket local address", e))?;
|
||||
|
||||
let (ws, _) = tokio_tungstenite::client_async_tls(rdcleanpath.url.as_str(), socket)
|
||||
.await
|
||||
.map_err(|e| connector::custom_err!("WS connect", e))?;
|
||||
|
||||
|
@ -192,7 +210,7 @@ async fn connect_ws(
|
|||
|
||||
let mut framed = ironrdp_tokio::TokioFramed::new(ws);
|
||||
|
||||
let mut connector = connector::ClientConnector::new(config.connector.clone())
|
||||
let mut connector = connector::ClientConnector::new(config.connector.clone(), client_addr)
|
||||
.with_static_channel(
|
||||
ironrdp::dvc::DrdynvcClient::new().with_dynamic_channel(DisplayControlClient::new(|_| Ok(Vec::new()))),
|
||||
)
|
||||
|
@ -312,7 +330,7 @@ where
|
|||
|
||||
debug!(message = ?rdcleanpath_res, "Received RDCleanPath PDU");
|
||||
|
||||
let (x224_connection_response, server_cert_chain, server_addr) = match rdcleanpath_res
|
||||
let (x224_connection_response, server_cert_chain) = match rdcleanpath_res
|
||||
.into_enum()
|
||||
.map_err(|e| connector::custom_err!("invalid RDCleanPath PDU", e))?
|
||||
{
|
||||
|
@ -324,19 +342,13 @@ where
|
|||
ironrdp_rdcleanpath::RDCleanPath::Response {
|
||||
x224_connection_response,
|
||||
server_cert_chain,
|
||||
server_addr,
|
||||
} => (x224_connection_response, server_cert_chain, server_addr),
|
||||
server_addr: _,
|
||||
} => (x224_connection_response, server_cert_chain),
|
||||
ironrdp_rdcleanpath::RDCleanPath::Err(error) => {
|
||||
return Err(connector::custom_err!("received an RDCleanPath error", error));
|
||||
}
|
||||
};
|
||||
|
||||
let server_addr = server_addr
|
||||
.parse()
|
||||
.map_err(|e| connector::custom_err!("failed to parse server address sent by proxy", e))?;
|
||||
|
||||
connector.attach_client_addr(server_addr);
|
||||
|
||||
let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state else {
|
||||
return Err(connector::general_err!("invalid connector state (wait confirm)"));
|
||||
};
|
||||
|
|
|
@ -123,32 +123,21 @@ impl State for ClientConnectorState {
|
|||
pub struct ClientConnector {
|
||||
pub config: Config,
|
||||
pub state: ClientConnectorState,
|
||||
pub client_addr: Option<SocketAddr>,
|
||||
/// The client address to be used in the Client Info PDU.
|
||||
pub client_addr: SocketAddr,
|
||||
pub static_channels: StaticChannelSet,
|
||||
}
|
||||
|
||||
impl ClientConnector {
|
||||
pub fn new(config: Config) -> Self {
|
||||
pub fn new(config: Config, client_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
config,
|
||||
state: ClientConnectorState::ConnectionInitiationSendRequest,
|
||||
client_addr: None,
|
||||
client_addr,
|
||||
static_channels: StaticChannelSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the client address to be used in the Client Info PDU.
|
||||
#[must_use]
|
||||
pub fn with_client_addr(mut self, addr: SocketAddr) -> Self {
|
||||
self.client_addr = Some(addr);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the client address to be used in the Client Info PDU.
|
||||
pub fn attach_client_addr(&mut self, addr: SocketAddr) {
|
||||
self.client_addr = Some(addr);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_static_channel<T>(mut self, channel: T) -> Self
|
||||
where
|
||||
|
@ -448,12 +437,7 @@ impl Sequence for ClientConnector {
|
|||
} => {
|
||||
debug!("Secure Settings Exchange");
|
||||
|
||||
let client_addr = self
|
||||
.client_addr
|
||||
.as_ref()
|
||||
.ok_or_else(|| general_err!("client address is missing"))?;
|
||||
|
||||
let client_info = create_client_info_pdu(&self.config, client_addr);
|
||||
let client_info = create_client_info_pdu(&self.config, &self.client_addr);
|
||||
|
||||
debug!(message = ?client_info, "Send");
|
||||
|
||||
|
|
|
@ -195,10 +195,11 @@ where
|
|||
let client = tokio::task::spawn_local(async move {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
ev.send(ServerEvent::GetLocalAddr(tx)).unwrap();
|
||||
let addr = rx.await.unwrap().unwrap();
|
||||
let tcp_stream = TcpStream::connect(addr).await.expect("TCP connect");
|
||||
let server_addr = rx.await.unwrap().unwrap();
|
||||
let tcp_stream = TcpStream::connect(server_addr).await.expect("TCP connect");
|
||||
let client_addr = tcp_stream.local_addr().expect("local_addr");
|
||||
let mut framed = ironrdp_tokio::TokioFramed::new(tcp_stream);
|
||||
let mut connector = connector::ClientConnector::new(client_config).with_client_addr(addr);
|
||||
let mut connector = connector::ClientConnector::new(client_config, client_addr);
|
||||
let should_upgrade = ironrdp_async::connect_begin(&mut framed, &mut connector)
|
||||
.await
|
||||
.expect("begin connection");
|
||||
|
|
|
@ -5,6 +5,7 @@ use core::cell::RefCell;
|
|||
use core::num::NonZeroU32;
|
||||
use core::time::Duration;
|
||||
use std::borrow::Cow;
|
||||
use std::net::{Ipv4Addr, SocketAddrV4};
|
||||
use std::rc::Rc;
|
||||
|
||||
use anyhow::Context as _;
|
||||
|
@ -919,7 +920,10 @@ async fn connect(
|
|||
) -> Result<(connector::ConnectionResult, WebSocket), IronError> {
|
||||
let mut framed = ironrdp_futures::LocalFuturesFramed::new(ws);
|
||||
|
||||
let mut connector = ClientConnector::new(config);
|
||||
// In web browser environments, we do not have an easy access to the local address of the socket.
|
||||
let dummy_client_addr = std::net::SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 33899));
|
||||
|
||||
let mut connector = ClientConnector::new(config, dummy_client_addr);
|
||||
|
||||
if let Some(clipboard_backend) = clipboard_backend {
|
||||
connector.attach_static_channel(CliprdrClient::new(Box::new(clipboard_backend)));
|
||||
|
@ -1031,7 +1035,7 @@ where
|
|||
|
||||
debug!(message = ?rdcleanpath_res, "Received RDCleanPath PDU");
|
||||
|
||||
let (x224_connection_response, server_cert_chain, server_addr) =
|
||||
let (x224_connection_response, server_cert_chain) =
|
||||
match rdcleanpath_res.into_enum().context("invalid RDCleanPath PDU")? {
|
||||
ironrdp_rdcleanpath::RDCleanPath::Request { .. } => {
|
||||
return Err(anyhow::Error::msg("received an unexpected RDCleanPath type (request)").into());
|
||||
|
@ -1039,8 +1043,8 @@ where
|
|||
ironrdp_rdcleanpath::RDCleanPath::Response {
|
||||
x224_connection_response,
|
||||
server_cert_chain,
|
||||
server_addr,
|
||||
} => (x224_connection_response, server_cert_chain, server_addr),
|
||||
server_addr: _,
|
||||
} => (x224_connection_response, server_cert_chain),
|
||||
ironrdp_rdcleanpath::RDCleanPath::Err(error) => {
|
||||
return Err(
|
||||
IronError::from(anyhow::Error::new(error).context("received an RDCleanPath error"))
|
||||
|
@ -1049,12 +1053,6 @@ where
|
|||
}
|
||||
};
|
||||
|
||||
let server_addr = server_addr
|
||||
.parse()
|
||||
.context("failed to parse server address sent by proxy")?;
|
||||
|
||||
connector.attach_client_addr(server_addr);
|
||||
|
||||
let connector::ClientConnectorState::ConnectionInitiationWaitConfirm { .. } = connector.state else {
|
||||
return Err(anyhow::Error::msg("invalid connector state (wait confirm)").into());
|
||||
};
|
||||
|
|
|
@ -237,9 +237,11 @@ fn connect(
|
|||
.set_read_timeout(Some(Duration::from_secs(3)))
|
||||
.expect("set_read_timeout call failed");
|
||||
|
||||
let client_addr = tcp_stream.local_addr().context("get socket local address")?;
|
||||
|
||||
let mut framed = ironrdp_blocking::Framed::new(tcp_stream);
|
||||
|
||||
let mut connector = connector::ClientConnector::new(config).with_client_addr(server_addr);
|
||||
let mut connector = connector::ClientConnector::new(config, client_addr);
|
||||
|
||||
let should_upgrade = ironrdp_blocking::connect_begin(&mut framed, &mut connector).context("begin connection")?;
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace Devolutions.IronRdp.ConnectExample
|
|||
var readPduTask = framed.ReadPdu();
|
||||
Action? action = null;
|
||||
byte[]? payload = null;
|
||||
if (readPduTask == await Task.WhenAny(readPduTask, Task.Delay(1000)))
|
||||
if (readPduTask == await Task.WhenAny(readPduTask, Task.Delay(2000)))
|
||||
{
|
||||
var pduReadTask = await readPduTask;
|
||||
action = pduReadTask.Item1;
|
||||
|
|
|
@ -37,45 +37,31 @@ public partial class ClientConnector: IDisposable
|
|||
_inner = handle;
|
||||
}
|
||||
|
||||
/// <exception cref="IronRdpException"></exception>
|
||||
/// <returns>
|
||||
/// A <c>ClientConnector</c> allocated on Rust side.
|
||||
/// </returns>
|
||||
public static ClientConnector New(Config config)
|
||||
public static ClientConnector New(Config config, string clientAddr)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
byte[] clientAddrBuf = DiplomatUtils.StringToUtf8(clientAddr);
|
||||
nuint clientAddrBufLength = (nuint)clientAddrBuf.Length;
|
||||
Raw.Config* configRaw;
|
||||
configRaw = config.AsFFI();
|
||||
if (configRaw == null)
|
||||
{
|
||||
throw new ObjectDisposedException("Config");
|
||||
}
|
||||
Raw.ClientConnector* retVal = Raw.ClientConnector.New(configRaw);
|
||||
return new ClientConnector(retVal);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Must use
|
||||
/// </summary>
|
||||
/// <exception cref="IronRdpException"></exception>
|
||||
public void WithClientAddr(string clientAddr)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (_inner == null)
|
||||
{
|
||||
throw new ObjectDisposedException("ClientConnector");
|
||||
}
|
||||
byte[] clientAddrBuf = DiplomatUtils.StringToUtf8(clientAddr);
|
||||
nuint clientAddrBufLength = (nuint)clientAddrBuf.Length;
|
||||
fixed (byte* clientAddrBufPtr = clientAddrBuf)
|
||||
{
|
||||
Raw.ConnectorFfiResultVoidBoxIronRdpError result = Raw.ClientConnector.WithClientAddr(_inner, clientAddrBufPtr, clientAddrBufLength);
|
||||
Raw.ConnectorFfiResultBoxClientConnectorBoxIronRdpError result = Raw.ClientConnector.New(configRaw, clientAddrBufPtr, clientAddrBufLength);
|
||||
if (!result.isOk)
|
||||
{
|
||||
throw new IronRdpException(new IronRdpError(result.Err));
|
||||
}
|
||||
Raw.ClientConnector* retVal = result.Ok;
|
||||
return new ClientConnector(retVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,13 +17,7 @@ public partial struct ClientConnector
|
|||
private const string NativeLib = "DevolutionsIronRdp";
|
||||
|
||||
[DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_new", ExactSpelling = true)]
|
||||
public static unsafe extern ClientConnector* New(Config* config);
|
||||
|
||||
/// <summary>
|
||||
/// Must use
|
||||
/// </summary>
|
||||
[DllImport(NativeLib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "ClientConnector_with_client_addr", ExactSpelling = true)]
|
||||
public static unsafe extern ConnectorFfiResultVoidBoxIronRdpError WithClientAddr(ClientConnector* self, byte* clientAddr, nuint clientAddrSz);
|
||||
public static unsafe extern ConnectorFfiResultBoxClientConnectorBoxIronRdpError New(Config* config, byte* clientAddr, nuint clientAddrSz);
|
||||
|
||||
/// <summary>
|
||||
/// Must use
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
// <auto-generated/> by Diplomat
|
||||
|
||||
#pragma warning disable 0105
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Devolutions.IronRdp.Diplomat;
|
||||
#pragma warning restore 0105
|
||||
|
||||
namespace Devolutions.IronRdp.Raw;
|
||||
|
||||
#nullable enable
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public partial struct ConnectorFfiResultBoxClientConnectorBoxIronRdpError
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
private unsafe struct InnerUnion
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
internal ClientConnector* ok;
|
||||
[FieldOffset(0)]
|
||||
internal IronRdpError* err;
|
||||
}
|
||||
|
||||
private InnerUnion _inner;
|
||||
|
||||
[MarshalAs(UnmanagedType.U1)]
|
||||
public bool isOk;
|
||||
|
||||
public unsafe ClientConnector* Ok
|
||||
{
|
||||
get
|
||||
{
|
||||
return _inner.ok;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe IronRdpError* Err
|
||||
{
|
||||
get
|
||||
{
|
||||
return _inner.err;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,21 +9,16 @@ public static class Connection
|
|||
public static async Task<(ConnectionResult, Framed<SslStream>)> Connect(Config config, string serverName,
|
||||
CliprdrBackendFactory? factory, int port = 3389)
|
||||
{
|
||||
var stream = await CreateTcpConnection(serverName, port);
|
||||
var framed = new Framed<NetworkStream>(stream);
|
||||
var client = await CreateTcpConnection(serverName, port);
|
||||
string clientAddr = client.Client.LocalEndPoint.ToString();
|
||||
Console.WriteLine(clientAddr);
|
||||
|
||||
var connector = ClientConnector.New(config);
|
||||
var framed = new Framed<NetworkStream>(client.GetStream());
|
||||
|
||||
var ip = await Dns.GetHostAddressesAsync(serverName);
|
||||
if (ip.Length == 0)
|
||||
{
|
||||
throw new IronRdpLibException(IronRdpLibExceptionType.CannotResolveDns,
|
||||
"Cannot resolve DNS to " + serverName);
|
||||
}
|
||||
var connector = ClientConnector.New(config, clientAddr);
|
||||
|
||||
var clientAddr = ip[0] + ":" + port;
|
||||
connector.WithClientAddr(clientAddr);
|
||||
connector.WithDynamicChannelDisplayControl();
|
||||
|
||||
if (factory != null)
|
||||
{
|
||||
var cliprdr = factory.BuildCliprdr();
|
||||
|
@ -33,6 +28,7 @@ public static class Connection
|
|||
await ConnectBegin(framed, connector);
|
||||
var (serverPublicKey, framedSsl) = await SecurityUpgrade(framed, connector);
|
||||
var result = await ConnectFinalize(serverName, connector, serverPublicKey, framedSsl);
|
||||
|
||||
return (result, framedSsl);
|
||||
}
|
||||
|
||||
|
@ -209,7 +205,7 @@ public static class Connection
|
|||
await framed.Write(response);
|
||||
}
|
||||
|
||||
static async Task<NetworkStream> CreateTcpConnection(String servername, int port)
|
||||
static async Task<TcpClient> CreateTcpConnection(String servername, int port)
|
||||
{
|
||||
IPAddress ipAddress;
|
||||
|
||||
|
@ -228,9 +224,8 @@ public static class Connection
|
|||
TcpClient client = new TcpClient();
|
||||
|
||||
await client.ConnectAsync(ipEndPoint);
|
||||
NetworkStream stream = client.GetStream();
|
||||
|
||||
return stream;
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,23 +25,15 @@ pub mod ffi {
|
|||
|
||||
// Basic Impl for ClientConnector
|
||||
impl ClientConnector {
|
||||
pub fn new(config: &Config) -> Box<ClientConnector> {
|
||||
Box::new(ClientConnector(Some(ironrdp::connector::ClientConnector::new(
|
||||
config.0.clone(),
|
||||
pub fn new(config: &Config, client_addr: &str) -> Result<Box<ClientConnector>, Box<IronRdpError>> {
|
||||
let client_addr = client_addr.parse().map_err(|_| IronRdpErrorKind::Generic)?;
|
||||
|
||||
Ok(Box::new(ClientConnector(Some(
|
||||
ironrdp::connector::ClientConnector::new(config.0.clone(), client_addr),
|
||||
))))
|
||||
}
|
||||
|
||||
/// Must use
|
||||
pub fn with_client_addr(&mut self, client_addr: &str) -> Result<(), Box<IronRdpError>> {
|
||||
let Some(connector) = self.0.take() else {
|
||||
return Err(IronRdpErrorKind::Consumed.into());
|
||||
};
|
||||
let client_addr = client_addr.parse().map_err(|_| IronRdpErrorKind::Generic)?;
|
||||
self.0 = Some(connector.with_client_addr(client_addr));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// FIXME: Naming: since this is not a builder pattern, use "attach"?
|
||||
// FIXME: We need to create opaque for ironrdp::svc::StaticChannelSet
|
||||
/// Must use
|
||||
pub fn with_static_channel_rdp_snd(&mut self) -> Result<(), Box<IronRdpError>> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue