mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-07 21:11:05 +03:00
port_forward_mux: end-to-end tests over a loopback tunnel
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
This commit is contained in:
@@ -1186,5 +1186,170 @@ mod tests {
|
||||
Arc::new(std::sync::RwLock::new(Default::default()))
|
||||
}
|
||||
}
|
||||
|
||||
use crate::server::port_forward_mux::PortForwardMux;
|
||||
use hbb_common::tokio::time::Instant;
|
||||
|
||||
/// Stands in for `Connection`: one task owning the stream, draining
|
||||
/// `inner.tx` into it and dispatching inbound frames to the mux.
|
||||
fn fake_controlled(mut stream: Stream, login_target: String) {
|
||||
tokio::spawn(async move {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<(Instant, Arc<Message>)>();
|
||||
let mut mux = PortForwardMux::new(tx, login_target);
|
||||
let mut tick = tokio::time::interval(std::time::Duration::from_millis(100));
|
||||
loop {
|
||||
tokio::select! {
|
||||
Some((_, m)) = rx.recv() => {
|
||||
if stream.send(&*m).await.is_err() { return; }
|
||||
}
|
||||
res = stream.next() => match res {
|
||||
Some(Ok(bytes)) => {
|
||||
let Ok(m) = Message::parse_from_bytes(&bytes) else { continue };
|
||||
if let Some(message::Union::PortForwardChannel(ch)) = m.union {
|
||||
mux.handle(ch, true);
|
||||
mux.sweep();
|
||||
}
|
||||
}
|
||||
_ => return,
|
||||
},
|
||||
_ = tick.tick() => { mux.sweep(); }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn echo_target() -> u16 {
|
||||
let l = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = l.local_addr().unwrap().port();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let (mut s, _) = l.accept().await.unwrap();
|
||||
tokio::spawn(async move {
|
||||
let mut buf = vec![0u8; 64 * 1024];
|
||||
loop {
|
||||
let n = s.read(&mut buf).await.unwrap_or(0);
|
||||
if n == 0 || s.write_all(&buf[..n]).await.is_err() { return; }
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
port
|
||||
}
|
||||
|
||||
async fn muxed_tunnel() -> (Arc<TunnelHandle>, u16) {
|
||||
let (ours, theirs) = stream_pair().await;
|
||||
let port = echo_target().await;
|
||||
fake_controlled(theirs, format!("127.0.0.1:{}", port));
|
||||
let t = Tunnel::new();
|
||||
t.try_claim();
|
||||
(t.set_muxed(ours, NoUi), port)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_channels_echo_concurrently_and_a_bulk_one_does_not_starve_them() {
|
||||
rt().block_on(async {
|
||||
let (h, port) = muxed_tunnel().await;
|
||||
let mut apps = Vec::new();
|
||||
for i in 0..20u8 {
|
||||
let (app, sock) = local_pair().await;
|
||||
h.open("127.0.0.1", port as i32, sock, vec![i]).unwrap();
|
||||
apps.push(app);
|
||||
}
|
||||
// Channel 0 streams 4 MiB; the others each expect their one byte back promptly.
|
||||
// Read and write the bulk socket from separate tasks: the echo can only
|
||||
// drain if this side keeps reading while it writes.
|
||||
let bulk = vec![0xAB; 4 << 20];
|
||||
let (mut bulk_rd, mut bulk_wr) = apps.remove(0).into_split();
|
||||
let bulk_reader = {
|
||||
let bulk = bulk.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut back = vec![0u8; bulk.len() + 1];
|
||||
bulk_rd.read_exact(&mut back).await.unwrap();
|
||||
assert_eq!(back[0], 0);
|
||||
assert_eq!(&back[1..], &bulk[..]);
|
||||
})
|
||||
};
|
||||
let bulk_writer = {
|
||||
let bulk = bulk.clone();
|
||||
tokio::spawn(async move { bulk_wr.write_all(&bulk).await.unwrap() })
|
||||
};
|
||||
for (i, app) in apps.iter_mut().enumerate() {
|
||||
let mut b = [0u8; 1];
|
||||
tokio::time::timeout(std::time::Duration::from_secs(2), app.read_exact(&mut b))
|
||||
.await
|
||||
.expect("small channel starved")
|
||||
.unwrap();
|
||||
assert_eq!(b[0], (i + 1) as u8);
|
||||
}
|
||||
bulk_writer.await.unwrap();
|
||||
bulk_reader.await.unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tail_before_close_is_delivered_in_both_directions() {
|
||||
rt().block_on(async {
|
||||
let (h, port) = muxed_tunnel().await;
|
||||
let (app, sock) = local_pair().await;
|
||||
h.open("127.0.0.1", port as i32, sock, vec![]).unwrap();
|
||||
let payload = vec![7u8; 300 * 1024];
|
||||
let (mut rd, mut wr) = app.into_split();
|
||||
let reader = {
|
||||
let payload = payload.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut back = vec![0u8; payload.len()];
|
||||
rd.read_exact(&mut back).await.unwrap();
|
||||
assert_eq!(back, payload);
|
||||
rd
|
||||
})
|
||||
};
|
||||
wr.write_all(&payload).await.unwrap();
|
||||
// The full echo proves every byte reached the target ahead of anything
|
||||
// else; only then close, and the peer's `close` must follow cleanly.
|
||||
let mut rd = reader.await.unwrap();
|
||||
drop(wr);
|
||||
let mut one = [0u8; 1];
|
||||
assert_eq!(rd.read(&mut one).await.unwrap(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_byte_frames_never_trip_the_window() {
|
||||
rt().block_on(async {
|
||||
let (h, port) = muxed_tunnel().await;
|
||||
let (mut app, sock) = local_pair().await;
|
||||
h.open("127.0.0.1", port as i32, sock, vec![]).unwrap();
|
||||
for i in 0..5000u32 {
|
||||
app.write_all(&[(i % 251) as u8]).await.unwrap();
|
||||
app.flush().await.unwrap();
|
||||
}
|
||||
let mut back = vec![0u8; 5000];
|
||||
app.read_exact(&mut back).await.unwrap();
|
||||
for (i, b) in back.iter().enumerate() {
|
||||
assert_eq!(*b, (i as u32 % 251) as u8);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sequential_connections_far_beyond_max_channels_all_succeed() {
|
||||
rt().block_on(async {
|
||||
let (h, port) = muxed_tunnel().await;
|
||||
for i in 0..(MAX_CHANNELS * 3) {
|
||||
let (mut app, sock) = local_pair().await;
|
||||
h.open("127.0.0.1", port as i32, sock, vec![i as u8]).unwrap();
|
||||
let mut b = [0u8; 1];
|
||||
app.read_exact(&mut b).await.unwrap();
|
||||
assert_eq!(b[0], i as u8);
|
||||
drop(app);
|
||||
// The controller's entry goes when its coordinator task exits,
|
||||
// which takes a cancel and a join; wait for it rather than
|
||||
// trusting a single yield, or the cap trips around round 256.
|
||||
while h.live_channels() != 0 {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user