mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-05 23:51:04 +03:00
port forward: apply the whole-branch review
Correctness:
- listen(): the Legacy arm is merged with the Claimed arm. On its own it
ignored outcome.local_eof, so a client that hung up during login still
got a target connect, an audit record and a CM row on the controlled
side, and ignored outcome.mux, so a peer upgraded while a legacy window
stayed open answered as a tunnel while the controller went raw.
- Refusal dialogs are deduplicated per quiet spell (10 s) rather than per
tunnel lifetime; the lifetime set went silent for the rest of a
long-lived window after the first burst.
- Android's CM listener handles UpdatePortForward; it fell into `_ => {}`.
- relay_socket_to_tunnel reads into one scratch buffer per channel and
sends an exact-size copy. A frame owning its 64 KiB read allocation
pinned it until sent, once per byte on interactive traffic.
Consistency and cleanups:
- The controlled side's refusal text is the raw pipe's wording, RDP
substitution included.
- connection.rs: the PortForwardChannel arm is a one-line hook, the CM
label is pushed from the 1 s tick alone, and the unreachable inner.tx
fall-through is gone.
- The Ready enum is removed; wait_ready() returns Option<Claim>.
- SendCredit::add wakes with notify_one alone.
- on_ui_command() replaces the two ui_receiver handlers in listen().
- TunnelHandle is no longer re-exported (unused-import warning).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
This commit is contained in:
@@ -97,24 +97,15 @@ pub async fn listen(
|
||||
// never shadow it.
|
||||
Ok((forward, peer_addr)) = listener.accept() => {
|
||||
log::debug!("new connection from {:?}", peer_addr);
|
||||
let claim = tunnel.try_claim();
|
||||
let claim = match claim {
|
||||
let claim = match tunnel.try_claim() {
|
||||
Claim::Wait => {
|
||||
// Keep servicing the UI while the establishing accept holds the
|
||||
// prompt. `None` means establishment failed: this accept is dropped.
|
||||
let resolved: Option<Claim> = loop {
|
||||
let resolved = loop {
|
||||
tokio::select! {
|
||||
// `tokio::select!` brings its own `Poll::{Ready, Pending}` into
|
||||
// scope, so `Ready` must be qualified here or it resolves to that.
|
||||
r = tunnel.wait_ready() => break match r {
|
||||
crate::port_forward_mux::Ready::Muxed(h) => Some(Claim::Muxed(h)),
|
||||
crate::port_forward_mux::Ready::Legacy => Some(Claim::Legacy),
|
||||
crate::port_forward_mux::Ready::Failed => None,
|
||||
},
|
||||
d = ui_receiver.recv() => match d {
|
||||
Some(Data::Close) => return Ok(()),
|
||||
Some(Data::NewRDP) => run_rdp(addr.port(), &rdp_display_name(&lc, &id)),
|
||||
_ => {}
|
||||
r = tunnel.wait_ready() => break r,
|
||||
d = ui_receiver.recv() => if on_ui_command(d, addr.port(), &lc, &id) {
|
||||
return Ok(());
|
||||
},
|
||||
}
|
||||
};
|
||||
@@ -134,18 +125,10 @@ pub async fn listen(
|
||||
log::debug!("cannot open channel for {:?}: {}", peer_addr, e);
|
||||
}
|
||||
}
|
||||
Claim::Legacy => {
|
||||
lc.write().unwrap().port_forward = (remote_host.clone(), remote_port);
|
||||
let mut forward = Framed::new(forward, BytesCodec::new());
|
||||
let mut close_port_forward = false;
|
||||
match connect_and_login(&id, &password, &mut ui_receiver, interface.clone(), &mut forward, key, token, is_rdp, &mut close_port_forward).await {
|
||||
Ok(Some(outcome)) => run_legacy(outcome, forward, peer_addr, interface.clone()),
|
||||
_ if close_port_forward => break,
|
||||
Err(err) => interface.on_establish_connection_error(err.to_string()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Claim::Claimed => {
|
||||
// A `Legacy` window logs in for every accept, as it always did,
|
||||
// and publishes the outcome the same way the claiming accept
|
||||
// does: a peer upgraded since the window opened becomes a tunnel.
|
||||
Claim::Claimed | Claim::Legacy => {
|
||||
lc.write().unwrap().port_forward = (remote_host.clone(), remote_port);
|
||||
let mut forward = Framed::new(forward, BytesCodec::new());
|
||||
let mut close_port_forward = false;
|
||||
@@ -182,23 +165,27 @@ pub async fn listen(
|
||||
Claim::Wait => continue,
|
||||
}
|
||||
}
|
||||
d = ui_receiver.recv() => {
|
||||
match d {
|
||||
Some(Data::Close) => {
|
||||
break;
|
||||
}
|
||||
Some(Data::NewRDP) => {
|
||||
println!("receive run_rdp from ui_receiver");
|
||||
run_rdp(addr.port(), &rdp_display_name(&lc, &id));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
d = ui_receiver.recv() => if on_ui_command(d, addr.port(), &lc, &id) {
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Commands the window sends its listener. `true` means stop listening: the
|
||||
/// window is closing, or its sender is gone.
|
||||
fn on_ui_command(d: Option<Data>, port: u16, lc: &Arc<RwLock<LoginConfigHandler>>, id: &str) -> bool {
|
||||
match d {
|
||||
Some(Data::Close) | None => true,
|
||||
Some(Data::NewRDP) => {
|
||||
run_rdp(port, &rdp_display_name(lc, id));
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Today's raw pipe, for peers without multiplexing.
|
||||
fn run_legacy(
|
||||
outcome: LoginOutcome,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use hbb_common::{
|
||||
bytes::{BufMut, Bytes, BytesMut},
|
||||
bytes::Bytes,
|
||||
log,
|
||||
message_proto::*,
|
||||
tokio::{
|
||||
@@ -118,7 +118,6 @@ impl SendCredit {
|
||||
let mut credit = self.credit.lock().unwrap();
|
||||
*credit = credit.saturating_add(n).min(MAX_SEND_CREDIT);
|
||||
}
|
||||
self.notify.notify_waiters();
|
||||
self.notify.notify_one();
|
||||
}
|
||||
|
||||
@@ -260,18 +259,17 @@ async fn relay_socket_to_tunnel<R: AsyncRead + Unpin>(
|
||||
mut cancel: watch::Receiver<bool>,
|
||||
) -> RelayEnd {
|
||||
let mut reader = std::io::Cursor::new(prebuf).chain(reader);
|
||||
// One scratch buffer per channel and an exact-size copy per frame: a frame
|
||||
// that owned its read allocation would pin up to MAX_FRAME until sent,
|
||||
// whatever its length, and interactive traffic is mostly tiny frames.
|
||||
let mut scratch = vec![0u8; MAX_FRAME];
|
||||
loop {
|
||||
let allow = tokio::select! {
|
||||
n = credit.take(MAX_FRAME) => n,
|
||||
_ = cancel.changed() => return RelayEnd::Cancelled,
|
||||
};
|
||||
let mut buf = BytesMut::with_capacity(allow);
|
||||
let mut limited = (&mut buf).limit(allow);
|
||||
let got = tokio::select! {
|
||||
r = reader.read_buf(&mut limited) => match r {
|
||||
Ok(n) => n,
|
||||
Err(_) => 0,
|
||||
},
|
||||
r = reader.read(&mut scratch[..allow]) => r.unwrap_or(0),
|
||||
_ = cancel.changed() => {
|
||||
credit.add(allow as u32);
|
||||
return RelayEnd::Cancelled;
|
||||
@@ -284,7 +282,8 @@ async fn relay_socket_to_tunnel<R: AsyncRead + Unpin>(
|
||||
if got == 0 {
|
||||
return RelayEnd::LocalEof;
|
||||
}
|
||||
if sink.send_ordered(data_msg(id, buf.freeze())).await.is_err() {
|
||||
let frame = data_msg(id, Bytes::copy_from_slice(&scratch[..got]));
|
||||
if sink.send_ordered(frame).await.is_err() {
|
||||
return RelayEnd::TunnelGone;
|
||||
}
|
||||
}
|
||||
@@ -381,7 +380,7 @@ pub async fn run_channel<R, W>(
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
pub use tunnel::{Claim, Ready, Tunnel, TunnelHandle};
|
||||
pub use tunnel::{Claim, Tunnel};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
mod tunnel {
|
||||
@@ -394,15 +393,20 @@ mod tunnel {
|
||||
Stream,
|
||||
};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
collections::HashMap,
|
||||
sync::atomic::{AtomicI32, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
/// One dialog per distinct reason, and never an unbounded number: the
|
||||
/// message text comes from the peer.
|
||||
const MAX_REPORTED_OPEN_ERRORS: usize = 8;
|
||||
/// A refused reason is shown once, then not again until it has been quiet
|
||||
/// for this long: a page load's dozen refusals make one dialog, and a
|
||||
/// target that breaks again hours later is reported again.
|
||||
const REPORT_AGAIN_AFTER: Duration = Duration::from_secs(10);
|
||||
/// Distinct reasons remembered at once, so the dialog count stays bounded
|
||||
/// however the peer varies the text it sends.
|
||||
pub(super) const MAX_REPORTED_OPEN_ERRORS: usize = 8;
|
||||
|
||||
// Internal state only; `Claim` and `Ready` are the API listeners see.
|
||||
// Internal state only; `Claim` is the API listeners see.
|
||||
enum TunnelState {
|
||||
Unset,
|
||||
Establishing,
|
||||
@@ -418,12 +422,6 @@ mod tunnel {
|
||||
Legacy,
|
||||
}
|
||||
|
||||
pub enum Ready {
|
||||
Muxed(Arc<TunnelHandle>),
|
||||
Legacy,
|
||||
Failed,
|
||||
}
|
||||
|
||||
/// One per port-forward window. `watch::Sender::send_if_modified` is the
|
||||
/// atomic claim; nothing is ever awaited while it runs.
|
||||
pub struct Tunnel {
|
||||
@@ -457,22 +455,20 @@ mod tunnel {
|
||||
outcome
|
||||
}
|
||||
|
||||
pub async fn wait_ready(&self) -> Ready {
|
||||
/// What the establishing accept ended up with; `None` when it failed.
|
||||
pub async fn wait_ready(&self) -> Option<Claim> {
|
||||
let mut rx = self.state.subscribe();
|
||||
loop {
|
||||
let ready = match &*rx.borrow_and_update() {
|
||||
TunnelState::Muxed(h) => Some(Ready::Muxed(h.clone())),
|
||||
TunnelState::Legacy => Some(Ready::Legacy),
|
||||
match &*rx.borrow_and_update() {
|
||||
TunnelState::Muxed(h) => return Some(Claim::Muxed(h.clone())),
|
||||
TunnelState::Legacy => return Some(Claim::Legacy),
|
||||
// `Unset` here means the tunnel died between the claim
|
||||
// and this wait; the waiter treats it as a failure.
|
||||
TunnelState::Failed | TunnelState::Unset => Some(Ready::Failed),
|
||||
TunnelState::Establishing => None,
|
||||
};
|
||||
if let Some(r) = ready {
|
||||
return r;
|
||||
TunnelState::Failed | TunnelState::Unset => return None,
|
||||
TunnelState::Establishing => {}
|
||||
}
|
||||
if rx.changed().await.is_err() {
|
||||
return Ready::Failed;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,7 +511,7 @@ mod tunnel {
|
||||
sink: FrameSink,
|
||||
channels: Mutex<HashMap<i32, ChannelEntry>>,
|
||||
next_id: AtomicI32,
|
||||
reported: Mutex<HashSet<String>>,
|
||||
reported: Mutex<HashMap<String, Instant>>,
|
||||
}
|
||||
|
||||
impl TunnelHandle {
|
||||
@@ -626,17 +622,28 @@ mod tunnel {
|
||||
}
|
||||
}
|
||||
|
||||
/// The peer's reason for refusing a channel, the first time we see it.
|
||||
/// One page load can have a dozen connections refused for the same
|
||||
/// reason, and the user needs one dialog, not a dozen.
|
||||
/// The peer's reason for refusing a channel, unless it was reported
|
||||
/// within `REPORT_AGAIN_AFTER`. One page load can have a dozen
|
||||
/// connections refused for the same reason, and the user needs one
|
||||
/// dialog, not a dozen; a burst that keeps going keeps it quiet.
|
||||
fn first_report(&self, message: String) -> Option<String> {
|
||||
self.first_report_at(message, Instant::now())
|
||||
}
|
||||
|
||||
pub(super) fn first_report_at(&self, message: String, now: Instant) -> Option<String> {
|
||||
if message.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let mut reported = self.reported.lock().unwrap();
|
||||
if reported.len() >= MAX_REPORTED_OPEN_ERRORS || !reported.insert(message.clone()) {
|
||||
reported.retain(|_, last| now.duration_since(*last) < REPORT_AGAIN_AFTER);
|
||||
if let Some(last) = reported.get_mut(&message) {
|
||||
*last = now;
|
||||
return None;
|
||||
}
|
||||
if reported.len() >= MAX_REPORTED_OPEN_ERRORS {
|
||||
return None;
|
||||
}
|
||||
reported.insert(message.clone(), now);
|
||||
Some(message)
|
||||
}
|
||||
|
||||
@@ -1039,7 +1046,7 @@ mod tests {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
mod tunnel {
|
||||
use super::*;
|
||||
use crate::port_forward_mux::{Claim, Ready, Tunnel, TunnelHandle};
|
||||
use crate::port_forward_mux::{tunnel::{TunnelHandle, MAX_REPORTED_OPEN_ERRORS}, Claim, Tunnel};
|
||||
use hbb_common::{
|
||||
protobuf::Message as _,
|
||||
tcp::FramedStream,
|
||||
@@ -1085,11 +1092,11 @@ mod tests {
|
||||
assert!(matches!(t.try_claim(), Claim::Wait));
|
||||
let w = { let t = t.clone(); tokio::spawn(async move { t.wait_ready().await }) };
|
||||
t.set_failed();
|
||||
assert!(matches!(w.await.unwrap(), Ready::Failed));
|
||||
assert!(w.await.unwrap().is_none());
|
||||
assert!(matches!(t.try_claim(), Claim::Claimed));
|
||||
t.set_legacy();
|
||||
assert!(matches!(t.try_claim(), Claim::Legacy));
|
||||
assert!(matches!(t.wait_ready().await, Ready::Legacy));
|
||||
assert!(matches!(t.wait_ready().await, Some(Claim::Legacy)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1212,6 +1219,35 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_refused_reason_is_reported_again_after_a_quiet_spell() {
|
||||
rt().block_on(async {
|
||||
let (ours, _peer) = stream_pair().await;
|
||||
let t = Tunnel::new();
|
||||
t.try_claim();
|
||||
let h = t.set_muxed(ours, NoUi::default());
|
||||
let t0 = Instant::now();
|
||||
let at = |secs: u64| t0 + std::time::Duration::from_secs(secs);
|
||||
let down = || "down".to_owned();
|
||||
assert_eq!(h.first_report_at(down(), at(0)), Some(down()));
|
||||
// A burst that keeps going keeps the dialog quiet ...
|
||||
assert_eq!(h.first_report_at(down(), at(8)), None);
|
||||
assert_eq!(h.first_report_at(down(), at(16)), None);
|
||||
// ... and one that stopped is reported afresh.
|
||||
assert_eq!(h.first_report_at(down(), at(27)), Some(down()));
|
||||
// The cap counts reasons still live, so it cannot silence the
|
||||
// window for good.
|
||||
for i in 0..MAX_REPORTED_OPEN_ERRORS {
|
||||
h.first_report_at(format!("reason {}", i), at(27));
|
||||
}
|
||||
assert_eq!(h.first_report_at("one more".to_owned(), at(27)), None);
|
||||
assert_eq!(
|
||||
h.first_report_at("one more".to_owned(), at(40)),
|
||||
Some("one more".to_owned())
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tunnel_death_closes_channels_and_resets_state() {
|
||||
rt().block_on(async {
|
||||
@@ -1293,7 +1329,7 @@ mod tests {
|
||||
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.handle(ch, || true);
|
||||
mux.sweep();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1669,16 +1669,12 @@ impl Connection {
|
||||
return true;
|
||||
};
|
||||
if pf.multiplex {
|
||||
if let Some(tx) = self.inner.tx.clone() {
|
||||
self.port_forward_mux = Some(super::port_forward_mux::PortForwardMux::new(
|
||||
tx,
|
||||
self.port_forward_address.clone(),
|
||||
));
|
||||
return true;
|
||||
}
|
||||
// Without a sender there is no way to answer channel frames: fall
|
||||
// through to the raw pipe below rather than return with neither
|
||||
// flag set, which would make the connection look like remote desktop.
|
||||
// `inner.tx` is set for the connection's whole life; `None` here is
|
||||
// unreachable, and refusing the login is the only honest answer.
|
||||
self.port_forward_mux = self.inner.tx.clone().map(|tx| {
|
||||
super::port_forward_mux::PortForwardMux::new(tx, self.port_forward_address.clone())
|
||||
});
|
||||
return self.port_forward_mux.is_some();
|
||||
}
|
||||
let mut pf = pf.clone();
|
||||
let (mut addr, is_rdp) = Self::normalize_port_forward_target(&mut pf);
|
||||
@@ -2245,6 +2241,16 @@ impl Connection {
|
||||
self.send_to_cm(ipc::Data::UpdatePortForward(label));
|
||||
}
|
||||
|
||||
fn handle_port_forward_channel(&mut self, ch: PortForwardChannel) {
|
||||
let Some(mux) = self.port_forward_mux.as_mut() else {
|
||||
log::debug!("port forward channel frame on a non-multiplexed connection");
|
||||
return;
|
||||
};
|
||||
mux.handle(ch, || {
|
||||
Self::permission(keys::OPTION_ENABLE_TUNNEL, &self.control_permissions)
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn send_fs(&mut self, data: ipc::FS) {
|
||||
self.send_to_cm(ipc::Data::FS(data));
|
||||
@@ -3901,27 +3907,7 @@ impl Connection {
|
||||
self.refresh_video_display(Some(request.display as usize));
|
||||
}
|
||||
}
|
||||
Some(message::Union::PortForwardChannel(ch)) => {
|
||||
// Only open/close can change the target set; sweeping after every
|
||||
// data frame would walk the whole table per 64 KiB.
|
||||
let may_change_targets = matches!(
|
||||
ch.union,
|
||||
Some(port_forward_channel::Union::Open(_))
|
||||
| Some(port_forward_channel::Union::Close(_))
|
||||
);
|
||||
// Only `open` consults permissions; short-circuit so the lookup
|
||||
// isn't made per 64 KiB of data.
|
||||
let permitted = matches!(ch.union, Some(port_forward_channel::Union::Open(_)))
|
||||
&& Self::permission(keys::OPTION_ENABLE_TUNNEL, &self.control_permissions);
|
||||
if let Some(mux) = self.port_forward_mux.as_mut() {
|
||||
mux.handle(ch, permitted);
|
||||
if may_change_targets {
|
||||
self.push_port_forward_label();
|
||||
}
|
||||
} else {
|
||||
log::debug!("port forward channel frame on a non-multiplexed connection");
|
||||
}
|
||||
}
|
||||
Some(message::Union::PortForwardChannel(ch)) => self.handle_port_forward_channel(ch),
|
||||
Some(message::Union::TerminalAction(action)) => {
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
allow_err!(self.handle_terminal_action(action).await);
|
||||
|
||||
@@ -50,9 +50,14 @@ impl PortForwardMux {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle(&mut self, frame: PortForwardChannel, permitted: bool) {
|
||||
/// `tunnel_permitted` is consulted for `open` alone, so the lookup is not
|
||||
/// made per 64 KiB of data.
|
||||
pub fn handle(&mut self, frame: PortForwardChannel, tunnel_permitted: impl FnOnce() -> bool) {
|
||||
match frame.union {
|
||||
Some(port_forward_channel::Union::Open(open)) => self.on_open(open, permitted),
|
||||
Some(port_forward_channel::Union::Open(open)) => {
|
||||
let permitted = tunnel_permitted();
|
||||
self.on_open(open, permitted)
|
||||
}
|
||||
Some(port_forward_channel::Union::Data(d)) => {
|
||||
let len = d.data.len();
|
||||
let Some(entry) = self.channels.get(&d.channel_id) else {
|
||||
@@ -113,7 +118,7 @@ impl PortForwardMux {
|
||||
port: open.port,
|
||||
..Default::default()
|
||||
};
|
||||
let (addr, _is_rdp) = Connection::normalize_port_forward_target(&mut pf);
|
||||
let (addr, is_rdp) = Connection::normalize_port_forward_target(&mut pf);
|
||||
let (inbound_tx, inbound_rx) = mpsc::unbounded_channel();
|
||||
let credit = Arc::new(SendCredit::new(effective_window(open.window)));
|
||||
let window = Arc::new(Mutex::new(RecvWindow::new(CHANNEL_WINDOW)));
|
||||
@@ -129,6 +134,7 @@ impl PortForwardMux {
|
||||
tokio::spawn(run_controlled_channel(
|
||||
id,
|
||||
addr,
|
||||
is_rdp,
|
||||
credit,
|
||||
window,
|
||||
inbound_rx,
|
||||
@@ -184,6 +190,7 @@ impl PortForwardMux {
|
||||
async fn run_controlled_channel(
|
||||
id: i32,
|
||||
addr: String,
|
||||
is_rdp: bool,
|
||||
credit: Arc<SendCredit>,
|
||||
window: Arc<Mutex<RecvWindow>>,
|
||||
mut inbound: mpsc::UnboundedReceiver<Inbound>,
|
||||
@@ -215,19 +222,16 @@ async fn run_controlled_channel(
|
||||
return;
|
||||
}
|
||||
},
|
||||
res = &mut connect => match res {
|
||||
Ok(Ok(s)) => break s,
|
||||
Ok(Err(e)) => {
|
||||
log::debug!("port forward channel {} connect {} failed: {}", id, addr, e);
|
||||
sink.send_ordered(opened_msg(id, false, &format!("Failed to access remote {}", addr), 0)).await.ok();
|
||||
return;
|
||||
}
|
||||
Err(_) => {
|
||||
log::debug!("port forward channel {} connect {} timed out", id, addr);
|
||||
sink.send_ordered(opened_msg(id, false, &format!("Failed to access remote {}", addr), 0)).await.ok();
|
||||
return;
|
||||
}
|
||||
},
|
||||
res = &mut connect => {
|
||||
let err = match res {
|
||||
Ok(Ok(s)) => break s,
|
||||
Ok(Err(e)) => e.to_string(),
|
||||
Err(e) => e.to_string(),
|
||||
};
|
||||
log::debug!("port forward channel {} connect {} failed: {}", id, addr, err);
|
||||
sink.send_ordered(opened_msg(id, false, &unreachable_message(&addr, is_rdp), 0)).await.ok();
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
if sink
|
||||
@@ -241,6 +245,15 @@ async fn run_controlled_channel(
|
||||
run_channel(id, reader, writer, Vec::new(), pending, credit, window, inbound, sink).await;
|
||||
}
|
||||
|
||||
/// The same words the raw pipe puts in its login error, so one problem reads
|
||||
/// the same whichever path the peer takes.
|
||||
fn unreachable_message(addr: &str, is_rdp: bool) -> String {
|
||||
format!(
|
||||
"Failed to access remote {}. Please make sure it is reachable/open.",
|
||||
if is_rdp { "RDP" } else { addr }
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -340,11 +353,11 @@ mod tests {
|
||||
let port = echo_target().await;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, "localhost:1".to_owned());
|
||||
mux.handle(open(1, port), true);
|
||||
mux.handle(data(1, b"ping"), true);
|
||||
mux.handle(open(1, port), || true);
|
||||
mux.handle(data(1, b"ping"), || true);
|
||||
assert_eq!(opened(&next_frame(&mut rx).await), (1, true));
|
||||
assert_eq!(data_of(&next_frame(&mut rx).await), (1, b"ping".to_vec()));
|
||||
mux.handle(close(1), true);
|
||||
mux.handle(close(1), || true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -356,8 +369,8 @@ mod tests {
|
||||
drop(l);
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, "localhost:1".to_owned());
|
||||
mux.handle(open(1, port), true);
|
||||
mux.handle(data(1, b"lost"), true);
|
||||
mux.handle(open(1, port), || true);
|
||||
mux.handle(data(1, b"lost"), || true);
|
||||
assert_eq!(opened(&next_frame(&mut rx).await), (1, false));
|
||||
assert!(tokio::time::timeout(std::time::Duration::from_millis(50), rx.recv()).await.is_err());
|
||||
});
|
||||
@@ -369,7 +382,7 @@ mod tests {
|
||||
let port = echo_target().await;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, "localhost:1".to_owned());
|
||||
mux.handle(open(1, port), false);
|
||||
mux.handle(open(1, port), || false);
|
||||
assert_eq!(opened(&next_frame(&mut rx).await), (1, false));
|
||||
assert_eq!(mux.live_channels(), 0);
|
||||
});
|
||||
@@ -384,8 +397,8 @@ mod tests {
|
||||
let port = echo_target().await;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, "localhost:1".to_owned());
|
||||
mux.handle(open(1, port), true);
|
||||
mux.handle(close(1), true);
|
||||
mux.handle(open(1, port), || true);
|
||||
mux.handle(close(1), || true);
|
||||
assert!(tokio::time::timeout(std::time::Duration::from_millis(200), rx.recv()).await.is_err());
|
||||
assert_eq!(mux.live_channels(), 0);
|
||||
});
|
||||
@@ -397,21 +410,21 @@ mod tests {
|
||||
let port = echo_target().await;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, "localhost:1".to_owned());
|
||||
mux.handle(open(1, port), true);
|
||||
mux.handle(open(2, port), true);
|
||||
mux.handle(open(1, port), || true);
|
||||
mux.handle(open(2, port), || true);
|
||||
let mut seen = 0;
|
||||
while seen < 2 {
|
||||
opened(&next_frame(&mut rx).await);
|
||||
seen += 1;
|
||||
}
|
||||
let too_much = vec![0u8; CHANNEL_WINDOW as usize + 1];
|
||||
mux.handle(data(1, &too_much), true);
|
||||
mux.handle(data(1, &too_much), || true);
|
||||
let ch = next_frame(&mut rx).await;
|
||||
match &ch.union {
|
||||
Some(port_forward_channel::Union::Close(c)) => assert_eq!(c.channel_id, 1),
|
||||
other => panic!("expected close, got {:?}", other),
|
||||
}
|
||||
mux.handle(data(2, b"still fine"), true);
|
||||
mux.handle(data(2, b"still fine"), || true);
|
||||
assert_eq!(data_of(&next_frame(&mut rx).await), (2, b"still fine".to_vec()));
|
||||
});
|
||||
}
|
||||
@@ -454,7 +467,7 @@ mod tests {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, "localhost:1".to_owned());
|
||||
for id in 1..=(MAX_CHANNELS as i32 * 2) {
|
||||
mux.handle(open(id, port), true);
|
||||
mux.handle(open(id, port), || true);
|
||||
assert_eq!(opened(&next_frame(&mut rx).await), (id, true));
|
||||
// The task sends `close` on the target's EOF and exits; the
|
||||
// entry is dead until the next `open` sweeps it.
|
||||
@@ -480,15 +493,15 @@ mod tests {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let mut mux = PortForwardMux::new(tx, format!("127.0.0.1:{}", a));
|
||||
assert_eq!(mux.sweep(), None);
|
||||
mux.handle(open(1, a), true);
|
||||
mux.handle(open(2, a), true);
|
||||
mux.handle(open(1, a), || true);
|
||||
mux.handle(open(2, a), || true);
|
||||
opened(&next_frame(&mut rx).await);
|
||||
opened(&next_frame(&mut rx).await);
|
||||
assert_eq!(mux.sweep(), None);
|
||||
mux.handle(open(3, b), true);
|
||||
mux.handle(open(3, b), || true);
|
||||
opened(&next_frame(&mut rx).await);
|
||||
assert_eq!(mux.sweep(), Some(format!("127.0.0.1:{} +1", a)));
|
||||
mux.handle(close(3), true);
|
||||
mux.handle(close(3), || true);
|
||||
tokio::task::yield_now().await;
|
||||
assert_eq!(mux.sweep(), Some(format!("127.0.0.1:{}", a)));
|
||||
});
|
||||
|
||||
@@ -963,6 +963,17 @@ pub async fn start_listen<T: InvokeUiCM>(
|
||||
Some(Data::Close) => {
|
||||
break;
|
||||
}
|
||||
Some(Data::UpdatePortForward(port_forward)) => {
|
||||
let updated = {
|
||||
let mut clients = CLIENTS.write().unwrap();
|
||||
clients.get_mut(¤t_id).map(|c| {
|
||||
c.port_forward = port_forward.clone();
|
||||
})
|
||||
};
|
||||
if updated.is_some() {
|
||||
cm.ui_handler.update_port_forward(current_id, port_forward);
|
||||
}
|
||||
}
|
||||
Some(Data::StartVoiceCall) => {
|
||||
cm.voice_call_started(current_id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user