port forward: a legacy window stays legacy until it is reopened

Review: the merged `Claimed | Legacy` arm gave a legacy window a hot
transition to a tunnel — every accept re-negotiated, and a peer upgraded
while the window stayed open was promoted underneath live connections.
The product does not need a mode switch inside a window's lifetime, and
the transition was extra state-machine surface for nothing: reopening
the window picks up an upgraded peer.

The two arms are separate again. `Claimed` negotiates once and the
peer's answer fixes the window's mode. `Legacy` logs in for every accept
as before, asks for no tunnel — `LoginConfigHandler::port_forward_mux`
carries the request per login, so the raw pipe never has to talk to a
peer that thinks it agreed to multiplex — and ignores what the peer
reports. Both arms keep skipping a local socket that hung up during
login.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
This commit is contained in:
rustdesk
2026-09-04 11:14:17 +08:00
parent f69e706cfa
commit 8b7f3b8b75
2 changed files with 35 additions and 10 deletions

View File

@@ -1753,10 +1753,9 @@ pub struct LoginConfigHandler {
pub remember: bool,
config: PeerConfig,
pub port_forward: (String, i32),
/// Held by a port-forward mapping from filling `port_forward` and `hash`
/// until its login is built from them; a window's mappings log in
/// concurrently.
pub(crate) port_forward_login_turn: Arc<hbb_common::tokio::sync::Mutex<()>>,
/// Whether the next port-forward login asks for the multiplexed tunnel.
/// `port_forward::listen` sets it per accept.
pub port_forward_mux: bool,
pub version: i64,
features: Option<Features>,
pub session_id: u64, // used for local <-> server communication
@@ -2769,7 +2768,7 @@ impl LoginConfigHandler {
ConnType::PORT_FORWARD | ConnType::RDP => lr.set_port_forward(PortForward {
host: self.port_forward.0.clone(),
port: self.port_forward.1,
multiplex: crate::common::get_port_forward_mux_enabled(),
multiplex: self.port_forward_mux,
..Default::default()
}),
ConnType::TERMINAL => {

View File

@@ -125,11 +125,14 @@ pub async fn listen(
log::debug!("cannot open channel for {:?}: {}", peer_addr, e);
}
}
// 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);
// The claiming accept negotiates: it asks for the tunnel, and
// the peer's answer fixes the window's mode until it closes.
Claim::Claimed => {
{
let mut lc = lc.write().unwrap();
lc.port_forward = (remote_host.clone(), remote_port);
lc.port_forward_mux = crate::common::get_port_forward_mux_enabled();
}
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 {
@@ -161,6 +164,29 @@ pub async fn listen(
_ => tunnel.set_failed(),
}
}
// A `Legacy` window stays legacy until it closes: every accept
// logs in on its own, asks for no tunnel, and takes the raw pipe
// whatever the peer reports. Reopening the window is how a user
// picks up an upgraded peer; nothing switches modes underneath
// live connections.
Claim::Legacy => {
{
let mut lc = lc.write().unwrap();
lc.port_forward = (remote_host.clone(), remote_port);
lc.port_forward_mux = false;
}
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)) if outcome.local_eof => {
log::debug!("legacy peer and local {:?} already gone", peer_addr);
}
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()),
_ => {}
}
}
// Resolved above; a stray `Wait` just drops this accept.
Claim::Wait => continue,
}