qemu-rdp: initial rdp server

This commit is contained in:
mihneabuz 2023-05-30 19:12:44 +03:00 committed by Marc-André Lureau
parent a4b0394f27
commit 8c3b5e7cba
7 changed files with 382 additions and 0 deletions

View File

@ -5,6 +5,7 @@ members = [
"qemu-rdw",
"qemu-vnc",
"qemu-vte",
"qemu-rdp",
"xtask",
]
default-members = ["qemu-rdw"]

22
qemu-rdp/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "qemu-rdp"
version = "0.1.0"
authors = ["Mihnea Buzatu <mihneabuzatu88@gmail.com>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
qemu-display = { path = "../qemu-display" }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
keycodemap = { path = "../keycodemap" }
bytes = "1.4"
rustls = { version = "0.21" }
rustls-pemfile = "1.0"
tokio = { version = "1.28", features = ["full"] }
tokio-rustls = "0.24"
anyhow = "1.0"
clap = { version = "4.2", features = ["derive", "cargo"] }
async-trait = "0.1"
ironrdp = { git = "https://github.com/Devolutions/IronRDP", features = ["server"] }

59
qemu-rdp/src/args.rs Normal file
View File

@ -0,0 +1,59 @@
use clap::clap_derive::ValueEnum;
use clap::{crate_name, Parser};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
pub enum SecurityProtocol {
Ssl,
Hybrid,
HybridEx,
}
impl From<SecurityProtocol> for ironrdp::pdu::nego::SecurityProtocol {
fn from(value: SecurityProtocol) -> Self {
match value {
SecurityProtocol::Ssl => ironrdp::pdu::nego::SecurityProtocol::SSL,
SecurityProtocol::Hybrid => ironrdp::pdu::nego::SecurityProtocol::HYBRID,
SecurityProtocol::HybridEx => ironrdp::pdu::nego::SecurityProtocol::HYBRID_EX,
}
}
}
#[derive(Parser, Debug)]
pub struct ServerArgs {
/// IP address
#[clap(short, long, default_value = "127.0.0.1")]
pub address: std::net::IpAddr,
/// IP port
#[clap(short, long, default_value = "3389")]
pub port: u16,
/// Specify the security protocols to use
#[clap(long, value_enum, value_parser, default_value_t = SecurityProtocol::Ssl)]
pub security_protocol: SecurityProtocol,
/// Path to tls certificate
#[clap(short, long, value_parser)]
pub cert: Option<String>,
/// Path to private key
#[clap(short, long, value_parser)]
pub key: Option<String>,
}
#[derive(Parser, Debug)]
pub struct Args {
#[clap(short, long, value_parser, default_value_t = format!("{}.log", crate_name!()))]
pub log_file: String,
#[clap(flatten)]
pub server: ServerArgs,
/// DBUS address
#[clap(short, long)]
pub dbus_address: Option<String>,
}
pub fn parse() -> Args {
Args::parse()
}

58
qemu-rdp/src/main.rs Normal file
View File

@ -0,0 +1,58 @@
use anyhow::Context;
use qemu_display::zbus;
mod args;
mod server;
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let mut args = args::parse();
setup_logging(args.log_file.as_str()).context("unable to initialize logging")?;
let dbus = match args.dbus_address.take() {
None => zbus::Connection::session().await,
Some(addr) => {
zbus::ConnectionBuilder::address(addr.as_str())?
.build()
.await
}
}
.expect("Failed to connect to DBus");
server::Server::new(dbus, args.server).run().await?;
Ok(())
}
fn setup_logging(log_file: &str) -> anyhow::Result<()> {
use std::fs::OpenOptions;
use tracing::metadata::LevelFilter;
use tracing_subscriber::prelude::*;
use tracing_subscriber::EnvFilter;
let file = OpenOptions::new()
.create(true)
.append(true)
.open(log_file)
.with_context(|| format!("couldnt open {log_file}"))?;
let fmt_layer = tracing_subscriber::fmt::layer()
.compact()
.with_ansi(false)
.with_writer(file);
let env_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::WARN.into())
.with_env_var("QEMURDP_LOG_LEVEL")
.from_env_lossy();
tracing_subscriber::registry()
.with(fmt_layer)
.with(env_filter)
.try_init()
.context("failed to set tracing global subscriber")?;
Ok(())
}

View File

@ -0,0 +1,107 @@
use anyhow::Result;
use ironrdp::connector::DesktopSize;
use qemu_display::{zbus, Console, ConsoleListenerHandler, Cursor, MouseSet, Scanout, Update};
use ironrdp::server::{BitmapUpdate, DisplayUpdate, PixelFormat, PixelOrder, RdpServerDisplay};
pub struct DisplayHandler {
console: Console,
receiver: tokio::sync::mpsc::Receiver<DisplayUpdate>,
}
impl DisplayHandler {
pub async fn connect(dbus: zbus::Connection) -> Result<Self> {
let (sender, receiver) = tokio::sync::mpsc::channel::<DisplayUpdate>(32);
let listener = Listener::new(sender);
let console = Console::new(&dbus, 0).await?;
console.register_listener(listener).await?;
Ok(Self { console, receiver })
}
}
#[async_trait::async_trait]
impl RdpServerDisplay for DisplayHandler {
async fn size(&mut self) -> DesktopSize {
let width = self.console.proxy.width().await.unwrap() as u16;
let height = self.console.proxy.height().await.unwrap() as u16;
DesktopSize { height, width }
}
async fn get_update(&mut self) -> Option<DisplayUpdate> {
self.receiver.recv().await
}
}
struct Listener {
sender: tokio::sync::mpsc::Sender<DisplayUpdate>,
}
impl Listener {
fn new(sender: tokio::sync::mpsc::Sender<DisplayUpdate>) -> Self {
Self { sender }
}
async fn send(&mut self, update: DisplayUpdate) {
if let Err(e) = self.sender.send(update).await {
println!("{:?}", e);
};
}
}
#[async_trait::async_trait]
impl ConsoleListenerHandler for Listener {
async fn scanout(&mut self, scanout: Scanout) {
let format = match scanout.format {
537_004_168 => PixelFormat::BgrA32,
_ => PixelFormat::RgbA32,
};
let bitmap = DisplayUpdate::Bitmap(BitmapUpdate {
top: 0,
left: 0,
width: scanout.width,
height: scanout.height,
format,
order: PixelOrder::TopToBottom,
data: scanout.data,
});
self.send(bitmap).await;
}
async fn update(&mut self, update: Update) {
let format = match update.format {
537_004_168 => PixelFormat::BgrA32,
_ => PixelFormat::RgbA32,
};
let bitmap = DisplayUpdate::Bitmap(BitmapUpdate {
// TODO: fix scary conversion
top: update.y as u32,
left: update.x as u32,
width: update.w as u32,
height: update.h as u32,
format,
order: PixelOrder::TopToBottom,
data: update.data,
});
self.send(bitmap).await;
}
#[cfg(unix)]
async fn scanout_dmabuf(&mut self, _scanout: qemu_display::ScanoutDMABUF) {}
#[cfg(unix)]
async fn update_dmabuf(&mut self, _update: qemu_display::UpdateDMABUF) {}
async fn disable(&mut self) {}
async fn mouse_set(&mut self, _set: MouseSet) {}
async fn cursor_define(&mut self, _cursor: Cursor) {}
fn disconnected(&mut self) {}
fn interfaces(&self) -> Vec<String> {
vec![]
}
}

View File

@ -0,0 +1,74 @@
use qemu_display::{zbus, Console, KeyboardProxy, MouseButton, MouseProxy};
use ironrdp::server::{KeyboardEvent, MouseEvent, RdpServerInputHandler};
pub struct InputHandler<'a> {
pos: (u16, u16),
mouse: MouseProxy<'a>,
keyboard: KeyboardProxy<'a>,
}
#[async_trait::async_trait]
impl<'a> RdpServerInputHandler for InputHandler<'a> {
async fn keyboard(&mut self, event: KeyboardEvent) {
let result = match event {
KeyboardEvent::Pressed { code, .. } => self.keyboard.press(code as u32).await,
KeyboardEvent::Released { code, .. } => self.keyboard.release(code as u32).await,
other => {
eprintln!("unhandled keyboard event: {:?}", other);
Ok(())
}
};
if let Err(e) = result {
eprintln!("keyboard error: {:?}", e);
}
}
async fn mouse(&mut self, event: MouseEvent) {
let result = match event {
MouseEvent::Move { x, y } => self.mouse_move(x, y).await,
MouseEvent::RightPressed => self.mouse.press(MouseButton::Right).await,
MouseEvent::RightReleased => self.mouse.release(MouseButton::Right).await,
MouseEvent::LeftPressed => self.mouse.press(MouseButton::Left).await,
MouseEvent::LeftReleased => self.mouse.release(MouseButton::Left).await,
MouseEvent::VerticalScroll { value } => {
let motion = if value > 0 {
MouseButton::WheelUp
} else {
MouseButton::WheelDown
};
self.mouse.press(motion).await
}
};
if let Err(e) = result {
eprintln!("keyboard error: {:?}", e);
}
}
}
impl<'a> InputHandler<'a> {
pub async fn connect(dbus: zbus::Connection) -> anyhow::Result<InputHandler<'a>> {
let console = Console::new(&dbus, 0).await?;
Ok(Self {
pos: (0, 0),
mouse: console.mouse,
keyboard: console.keyboard,
})
}
pub async fn mouse_move(&mut self, x: u16, y: u16) -> Result<(), zbus::Error> {
if self.mouse.is_absolute().await.unwrap_or(true) {
self.mouse.set_abs_position(x.into(), y.into()).await
} else {
let (dx, dy) = (x as i32 - self.pos.0 as i32, y as i32 - self.pos.1 as i32);
let res = self.mouse.rel_motion(dx, dy).await;
self.pos = (x, y);
res
}
}
}

View File

@ -0,0 +1,61 @@
mod display;
mod input;
use anyhow::Error;
use qemu_display::zbus;
use rustls::ServerConfig;
use rustls_pemfile::{certs, pkcs8_private_keys};
use std::{fs::File, io::BufReader, sync::Arc};
use tokio_rustls::TlsAcceptor;
use ironrdp::server::RdpServer;
use crate::args::ServerArgs;
use display::DisplayHandler;
use input::InputHandler;
pub struct Server {
dbus: zbus::Connection,
args: ServerArgs,
}
impl Server {
pub fn new(dbus: zbus::Connection, args: ServerArgs) -> Self {
Self { dbus, args }
}
pub async fn run(&mut self) -> Result<(), Error> {
let tls = self
.args
.cert
.as_ref()
.zip(self.args.key.as_ref())
.map(|(cert, key)| acceptor(cert, key).unwrap());
let handler = InputHandler::connect(self.dbus.clone()).await?;
let display = DisplayHandler::connect(self.dbus.clone()).await?;
let mut server = RdpServer::builder()
.with_addr((self.args.address, self.args.port))
.with_tls(tls.unwrap())
.with_input_handler(handler)
.with_display_handler(display)
.build();
server.run().await
}
}
fn acceptor(cert_path: &str, key_path: &str) -> Result<TlsAcceptor, Error> {
let cert = certs(&mut BufReader::new(File::open(cert_path)?))?[0].clone();
let key = pkcs8_private_keys(&mut BufReader::new(File::open(key_path)?))?[0].clone();
let server_config = ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(vec![rustls::Certificate(cert)], rustls::PrivateKey(key))
.expect("bad certificate/key");
Ok(TlsAcceptor::from(Arc::new(server_config)))
}