Skip to content

Commit 5c08c7f

Browse files
authored
feat(server): add ConnectionHandler trait for connection lifecycle hooks (#1194)
1 parent e60500d commit 5c08c7f

2 files changed

Lines changed: 90 additions & 4 deletions

File tree

crates/ironrdp-server/src/builder.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use super::display::{DesktopSize, RdpServerDisplay};
99
#[cfg(feature = "egfx")]
1010
use super::gfx::GfxServerFactory;
1111
use super::handler::{KeyboardEvent, MouseEvent, RdpServerInputHandler};
12-
use super::server::{RdpServer, RdpServerOptions, RdpServerSecurity};
12+
use super::server::{ConnectionHandler, RdpServer, RdpServerOptions, RdpServerSecurity};
1313
use crate::{DisplayUpdate, RdpServerDisplayUpdates, SoundServerFactory};
1414

1515
pub struct WantsAddr {}
@@ -34,6 +34,7 @@ pub struct BuilderDone {
3434
display: Box<dyn RdpServerDisplay>,
3535
cliprdr_factory: Option<Box<dyn CliprdrServerFactory>>,
3636
sound_factory: Option<Box<dyn SoundServerFactory>>,
37+
connection_handler: Option<Box<dyn ConnectionHandler>>,
3738
#[cfg(feature = "egfx")]
3839
gfx_factory: Option<Box<dyn GfxServerFactory>>,
3940
}
@@ -128,6 +129,7 @@ impl RdpServerBuilder<WantsDisplay> {
128129
display: Box::new(display),
129130
sound_factory: None,
130131
cliprdr_factory: None,
132+
connection_handler: None,
131133
codecs: server_codecs_capabilities(&[]).expect("can't panic for &[]"),
132134
max_request_size: RdpServerOptions::DEFAULT_MAX_REQUEST_SIZE,
133135
#[cfg(feature = "egfx")]
@@ -145,6 +147,7 @@ impl RdpServerBuilder<WantsDisplay> {
145147
display: Box::new(NoopDisplay),
146148
sound_factory: None,
147149
cliprdr_factory: None,
150+
connection_handler: None,
148151
codecs: server_codecs_capabilities(&[]).expect("can't panic for &[]"),
149152
max_request_size: RdpServerOptions::DEFAULT_MAX_REQUEST_SIZE,
150153
#[cfg(feature = "egfx")]
@@ -188,6 +191,13 @@ impl RdpServerBuilder<BuilderDone> {
188191
self
189192
}
190193

194+
/// Set a handler for connection lifecycle events (accept filtering,
195+
/// post-disconnect cleanup).
196+
pub fn with_connection_handler(mut self, handler: Option<Box<dyn ConnectionHandler>>) -> Self {
197+
self.state.connection_handler = handler;
198+
self
199+
}
200+
191201
pub fn build(self) -> RdpServer {
192202
RdpServer::new(
193203
RdpServerOptions {
@@ -200,6 +210,7 @@ impl RdpServerBuilder<BuilderDone> {
200210
self.state.display,
201211
self.state.sound_factory,
202212
self.state.cliprdr_factory,
213+
self.state.connection_handler,
203214
#[cfg(feature = "egfx")]
204215
self.state.gfx_factory,
205216
)

crates/ironrdp-server/src/server.rs

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use core::net::SocketAddr;
2+
use core::time::Duration;
23
use std::rc::Rc;
34
use std::sync::Arc;
45

@@ -42,6 +43,50 @@ use crate::{SoundServerFactory, builder, capabilities};
4243
/// TCP listen backlog size for the RDP server socket.
4344
const LISTENER_BACKLOG: u32 = 1024;
4445

46+
/// Action to take after a client disconnects.
47+
///
48+
/// Returned by [`ConnectionHandler::on_disconnected`] to control whether
49+
/// the server continues accepting new connections or shuts down.
50+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51+
pub enum PostConnectionAction {
52+
/// Continue accepting new connections.
53+
Continue,
54+
/// Stop the accept loop and return from [`RdpServer::run`].
55+
Stop,
56+
}
57+
58+
/// Hooks for connection lifecycle events in [`RdpServer::run`].
59+
///
60+
/// Implement this trait to add pre-accept filtering (rate limiting,
61+
/// IP allowlists) and post-disconnect logic (cleanup, session validity
62+
/// checks, metrics).
63+
///
64+
/// All methods have default implementations that accept all connections
65+
/// and continue unconditionally.
66+
pub trait ConnectionHandler: Send {
67+
/// Called after `accept()` returns but before `run_connection()`.
68+
///
69+
/// Return `false` to reject the connection (the TCP stream is dropped).
70+
fn on_accept(&mut self, peer: SocketAddr) -> bool {
71+
let _ = peer;
72+
true
73+
}
74+
75+
/// Called after `run_connection()` completes (successfully or with error).
76+
///
77+
/// `duration` is the wall-clock time the connection was active.
78+
/// `error` is `Some` if the connection ended with an error.
79+
fn on_disconnected(
80+
&mut self,
81+
peer: SocketAddr,
82+
duration: Duration,
83+
error: Option<&anyhow::Error>,
84+
) -> PostConnectionAction {
85+
let _ = (peer, duration, error);
86+
PostConnectionAction::Continue
87+
}
88+
}
89+
4590
#[derive(Clone)]
4691
pub struct RdpServerOptions {
4792
pub addr: SocketAddr,
@@ -245,6 +290,7 @@ pub struct RdpServer {
245290
creds: Option<Credentials>,
246291
local_addr: Option<SocketAddr>,
247292
autodetect: Option<AutoDetectManager>,
293+
connection_handler: Option<Box<dyn ConnectionHandler>>,
248294
}
249295

250296
#[derive(Debug)]
@@ -285,6 +331,7 @@ impl RdpServer {
285331
display: Box<dyn RdpServerDisplay>,
286332
mut sound_factory: Option<Box<dyn SoundServerFactory>>,
287333
mut cliprdr_factory: Option<Box<dyn CliprdrServerFactory>>,
334+
connection_handler: Option<Box<dyn ConnectionHandler>>,
288335
#[cfg(feature = "egfx")] mut gfx_factory: Option<Box<dyn GfxServerFactory>>,
289336
) -> Self {
290337
let (ev_sender, ev_receiver) = ServerEvent::create_channel();
@@ -315,6 +362,7 @@ impl RdpServer {
315362
creds: None,
316363
local_addr: None,
317364
autodetect: None,
365+
connection_handler,
318366
}
319367
}
320368

@@ -531,10 +579,37 @@ impl RdpServer {
531579
Ok((stream, peer)) = listener.accept() => {
532580
debug!(?peer, "Received connection");
533581
drop(ev_receiver);
534-
if let Err(error) = self.run_connection(stream).await {
535-
error!(?error, "Connection error");
582+
583+
let accepted = self.connection_handler
584+
.as_mut()
585+
.is_none_or(|h| h.on_accept(peer));
586+
587+
if !accepted {
588+
debug!(?peer, "Connection rejected by handler");
589+
drop(stream);
590+
} else {
591+
let started = tokio::time::Instant::now();
592+
let result = self.run_connection(stream).await;
593+
let duration = started.elapsed();
594+
595+
if let Err(ref error) = result {
596+
error!(?error, "Connection error");
597+
}
598+
599+
self.static_channels = StaticChannelSet::new();
600+
601+
if let Some(ref mut handler) = self.connection_handler {
602+
let action = handler.on_disconnected(
603+
peer,
604+
duration,
605+
result.as_ref().err(),
606+
);
607+
if action == PostConnectionAction::Stop {
608+
debug!(?peer, "Handler requested stop after disconnect");
609+
break;
610+
}
611+
}
536612
}
537-
self.static_channels = StaticChannelSet::new();
538613
}
539614
else => break,
540615
}

0 commit comments

Comments
 (0)