Skip to content

Commit 1ed889d

Browse files
committed
feat(server): IPv6 dual-stack, SO_REUSEADDR, and ConnectionHandler for run()
Replace TcpListener::bind() with TcpSocket for control over socket options before binding: - SO_REUSEADDR: prevents EADDRINUSE on server restart (TIME_WAIT) - IPv6 dual-stack: when bound to [::], accepts both IPv4 and IPv6 on a single socket. Address family is detected from SocketAddr. - Backlog increased from default to 1024 Add ConnectionHandler trait for connection lifecycle hooks: - on_accept(peer) -> bool: pre-handshake hook (rate limiting, logging) - on_disconnected(peer, duration, error): post-connection cleanup The handler is optional (set via set_connection_handler) and the default implementation is a no-op, so this is fully backward compatible. Callers using run_connection() directly are unaffected.
1 parent a6b4109 commit 1ed889d

1 file changed

Lines changed: 93 additions & 4 deletions

File tree

crates/ironrdp-server/src/server.rs

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

57
use anyhow::{Context as _, Result, bail};
68
use ironrdp_acceptor::{Acceptor, AcceptorResult, BeginResult, DesktopSize};
@@ -22,13 +24,47 @@ use ironrdp_svc::{ChannelFlags, StaticChannelId, StaticChannelSet, SvcProcessor,
2224
use ironrdp_tokio::{FramedRead, FramedWrite, TokioFramed, split_tokio_framed, unsplit_tokio_framed};
2325
use rdpsnd::server::{RdpsndServer, RdpsndServerMessage};
2426
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt as _};
25-
use tokio::net::TcpListener;
27+
use tokio::net::TcpSocket;
2628
use tokio::sync::{Mutex, mpsc, oneshot};
2729
use tokio::task;
2830
use tokio_rustls::TlsAcceptor;
2931
use tracing::{debug, error, trace, warn};
3032
use {ironrdp_dvc as dvc, ironrdp_rdpsnd as rdpsnd};
3133

34+
/// Connection lifecycle handler for `RdpServer::run()`.
35+
///
36+
/// Implement this trait to hook into the TCP accept loop. The default
37+
/// implementation is a no-op, so existing callers are unaffected.
38+
///
39+
/// # Example
40+
///
41+
/// ```rust,ignore
42+
/// use ironrdp_server::ConnectionHandler;
43+
/// use std::net::SocketAddr;
44+
/// use std::time::Duration;
45+
///
46+
/// struct MyHandler;
47+
///
48+
/// impl ConnectionHandler for MyHandler {
49+
/// fn on_accept(&self, peer: SocketAddr) -> bool {
50+
/// println!("Connection from {peer}");
51+
/// true // accept all connections
52+
/// }
53+
/// }
54+
/// ```
55+
pub trait ConnectionHandler: Send + Sync {
56+
/// Called after TCP accept, before the RDP handshake begins.
57+
///
58+
/// Return `true` to proceed with the connection, or `false` to
59+
/// drop it immediately (e.g. for rate limiting).
60+
fn on_accept(&self, _peer: SocketAddr) -> bool {
61+
true
62+
}
63+
64+
/// Called after a connection ends, whether successfully or with an error.
65+
fn on_disconnected(&self, _peer: SocketAddr, _duration: Duration, _error: Option<&anyhow::Error>) {}
66+
}
67+
3268
use crate::clipboard::CliprdrServerFactory;
3369
use crate::display::{DisplayUpdate, RdpServerDisplay};
3470
use crate::echo::{EchoDvcBridge, EchoServerHandle, EchoServerMessage, build_echo_request};
@@ -240,6 +276,7 @@ pub struct RdpServer {
240276
ev_receiver: Arc<Mutex<mpsc::UnboundedReceiver<ServerEvent>>>,
241277
creds: Option<Credentials>,
242278
local_addr: Option<SocketAddr>,
279+
connection_handler: Option<Box<dyn ConnectionHandler>>,
243280
}
244281

245282
#[derive(Debug)]
@@ -307,9 +344,18 @@ impl RdpServer {
307344
ev_receiver: Arc::new(Mutex::new(ev_receiver)),
308345
creds: None,
309346
local_addr: None,
347+
connection_handler: None,
310348
}
311349
}
312350

351+
/// Set an optional connection lifecycle handler.
352+
///
353+
/// The handler is called on TCP accept and disconnect events during
354+
/// `run()`. Has no effect when using `run_connection()` directly.
355+
pub fn set_connection_handler(&mut self, handler: Box<dyn ConnectionHandler>) {
356+
self.connection_handler = Some(handler);
357+
}
358+
313359
pub fn builder() -> builder::RdpServerBuilder<builder::WantsAddr> {
314360
builder::RdpServerBuilder::new()
315361
}
@@ -448,7 +494,25 @@ impl RdpServer {
448494
}
449495

450496
pub async fn run(&mut self) -> Result<()> {
451-
let listener = TcpListener::bind(self.opts.addr).await?;
497+
// Create socket with SO_REUSEADDR and IPv6 dual-stack support.
498+
// Using TcpSocket instead of TcpListener::bind() gives control
499+
// over socket options before binding.
500+
let socket = match self.opts.addr {
501+
SocketAddr::V4(_) => TcpSocket::new_v4().context("Failed to create IPv4 socket")?,
502+
SocketAddr::V6(_) => {
503+
// IPv6 socket with dual-stack: accepts both IPv4 and IPv6
504+
// on a single socket. IPv4 clients appear as IPv4-mapped
505+
// addresses (::ffff:x.x.x.x). Dual-stack is the Linux
506+
// default (net.ipv6.bindv6only=0).
507+
TcpSocket::new_v6().context("Failed to create IPv6 socket")?
508+
}
509+
};
510+
511+
socket.set_reuseaddr(true).context("Failed to set SO_REUSEADDR")?;
512+
513+
socket.bind(self.opts.addr).context("Failed to bind listen address")?;
514+
515+
let listener = socket.listen(1024).context("Failed to start listener")?;
452516
let local_addr = listener.local_addr()?;
453517

454518
debug!("Listening for connections on {local_addr}");
@@ -476,11 +540,36 @@ impl RdpServer {
476540
}
477541
},
478542
Ok((stream, peer)) = listener.accept() => {
543+
// Connection lifecycle: on_accept hook
544+
if let Some(ref handler) = self.connection_handler {
545+
if !handler.on_accept(peer) {
546+
debug!(?peer, "Connection rejected by handler");
547+
continue;
548+
}
549+
}
550+
479551
debug!(?peer, "Received connection");
480552
drop(ev_receiver);
481-
if let Err(error) = self.run_connection(stream).await {
482-
error!(?error, "Connection error");
553+
554+
let start = Instant::now();
555+
let result = self.run_connection(stream).await;
556+
let duration = start.elapsed();
557+
558+
// Connection lifecycle: on_disconnected hook
559+
match &result {
560+
Ok(()) => {
561+
if let Some(ref handler) = self.connection_handler {
562+
handler.on_disconnected(peer, duration, None);
563+
}
564+
}
565+
Err(error) => {
566+
if let Some(ref handler) = self.connection_handler {
567+
handler.on_disconnected(peer, duration, Some(error));
568+
}
569+
error!(?error, "Connection error");
570+
}
483571
}
572+
484573
self.static_channels = StaticChannelSet::new();
485574
}
486575
else => break,

0 commit comments

Comments
 (0)