mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-12 07:21:01 +03:00
port forward: a mapping latched to the raw pipe logs in without asking for the tunnel
The login copied the window's `port_forward_mux` into `multiplex`, so a mapping that had latched to the raw pipe on an old peer kept asking for the tunnel. Once that peer was upgraded it answered with a tunnel while the controller switched to raw framing, and every later connection on the mapping was dead until it was re-added. The login now carries its own `port_forward_multiplex`, filled with the target under the turn lock: the probe asks, the raw pipe does not. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
This commit is contained in:
@@ -1753,8 +1753,13 @@ pub struct LoginConfigHandler {
|
|||||||
pub remember: bool,
|
pub remember: bool,
|
||||||
config: PeerConfig,
|
config: PeerConfig,
|
||||||
pub port_forward: (String, i32),
|
pub port_forward: (String, i32),
|
||||||
/// Set once per window, before its mappings start: the claim and the
|
/// This login's `multiplex`, filled with `port_forward` under the turn
|
||||||
/// login of every accept must agree on it.
|
/// lock. `port_forward_mux` says whether a mapping probes for the tunnel;
|
||||||
|
/// one the probe latched to the raw pipe logs in without asking, so an
|
||||||
|
/// upgraded peer keeps giving it the raw pipe.
|
||||||
|
pub(crate) port_forward_multiplex: bool,
|
||||||
|
/// Set once per window, before its mappings start: every accept's claim
|
||||||
|
/// reads it.
|
||||||
pub(crate) port_forward_mux: bool,
|
pub(crate) port_forward_mux: bool,
|
||||||
/// Held by a port-forward mapping from filling `port_forward` and `hash`
|
/// 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
|
/// until its login is built from them; a window's mappings log in
|
||||||
@@ -2772,7 +2777,7 @@ impl LoginConfigHandler {
|
|||||||
ConnType::PORT_FORWARD | ConnType::RDP => lr.set_port_forward(PortForward {
|
ConnType::PORT_FORWARD | ConnType::RDP => lr.set_port_forward(PortForward {
|
||||||
host: self.port_forward.0.clone(),
|
host: self.port_forward.0.clone(),
|
||||||
port: self.port_forward.1,
|
port: self.port_forward.1,
|
||||||
multiplex: self.port_forward_mux,
|
multiplex: self.port_forward_multiplex,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
ConnType::TERMINAL => {
|
ConnType::TERMINAL => {
|
||||||
@@ -4075,7 +4080,7 @@ mod port_forward_mux_tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_multiplexed_window_asks_for_the_tunnel() {
|
fn a_login_asks_for_the_tunnel_when_its_mapping_probes() {
|
||||||
let mut lc = LoginConfigHandler::default();
|
let mut lc = LoginConfigHandler::default();
|
||||||
lc.conn_type = ConnType::PORT_FORWARD;
|
lc.conn_type = ConnType::PORT_FORWARD;
|
||||||
let asks = |lc: &LoginConfigHandler| {
|
let asks = |lc: &LoginConfigHandler| {
|
||||||
@@ -4085,7 +4090,7 @@ mod port_forward_mux_tests {
|
|||||||
.multiplex
|
.multiplex
|
||||||
};
|
};
|
||||||
assert!(!asks(&lc));
|
assert!(!asks(&lc));
|
||||||
lc.port_forward_mux = true;
|
lc.port_forward_multiplex = true;
|
||||||
assert!(asks(&lc));
|
assert!(asks(&lc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ async fn connect_and_login(
|
|||||||
match msg_in.union {
|
match msg_in.union {
|
||||||
Some(message::Union::Hash(hash)) => {
|
Some(message::Union::Hash(hash)) => {
|
||||||
challenge = Some(hash.clone());
|
challenge = Some(hash.clone());
|
||||||
if !hash_arrived(&interface, password, hash, pending_login.take(), remote_host, remote_port, &mut stream).await {
|
if !hash_arrived(&interface, password, hash, pending_login.take(), remote_host, remote_port, false, &mut stream).await {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,7 +237,7 @@ async fn connect_and_login(
|
|||||||
d = ui_receiver.recv() => {
|
d = ui_receiver.recv() => {
|
||||||
match d {
|
match d {
|
||||||
Some(Data::Login(login)) => match &challenge {
|
Some(Data::Login(login)) => match &challenge {
|
||||||
Some(hash) => login_from_ui(&interface, hash, login, remote_host, remote_port, &mut stream).await,
|
Some(hash) => login_from_ui(&interface, hash, login, remote_host, remote_port, false, &mut stream).await,
|
||||||
None => pending_login = Some(login),
|
None => pending_login = Some(login),
|
||||||
},
|
},
|
||||||
Some(Data::Message(msg)) => {
|
Some(Data::Message(msg)) => {
|
||||||
@@ -264,22 +264,24 @@ async fn connect_and_login(
|
|||||||
|
|
||||||
|
|
||||||
/// A mapping's login is built from the window's shared handler:
|
/// A mapping's login is built from the window's shared handler:
|
||||||
/// `create_login_msg` reads `port_forward` and `handle_login_from_ui` reads
|
/// `create_login_msg` reads `port_forward` and `port_forward_multiplex`,
|
||||||
/// `hash`. Mappings log in concurrently, so each fills them and sends under
|
/// `handle_login_from_ui` reads `hash`. Mappings log in concurrently, so each
|
||||||
/// the window's turn lock, or one login carried another mapping's target or
|
/// fills them and sends under the window's turn lock, or one login carried
|
||||||
/// answered another's challenge.
|
/// another mapping's target or answered another's challenge.
|
||||||
async fn login_with_hash(
|
async fn login_with_hash(
|
||||||
interface: &impl Interface,
|
interface: &impl Interface,
|
||||||
password: &str,
|
password: &str,
|
||||||
hash: Hash,
|
hash: Hash,
|
||||||
remote_host: &str,
|
remote_host: &str,
|
||||||
remote_port: i32,
|
remote_port: i32,
|
||||||
|
mux: bool,
|
||||||
stream: &mut Stream,
|
stream: &mut Stream,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let lc = interface.get_lch();
|
let lc = interface.get_lch();
|
||||||
let turn = lc.read().unwrap().port_forward_login_turn.clone();
|
let turn = lc.read().unwrap().port_forward_login_turn.clone();
|
||||||
let _turn = turn.lock().await;
|
let _turn = turn.lock().await;
|
||||||
lc.write().unwrap().port_forward = (remote_host.to_owned(), remote_port);
|
lc.write().unwrap().port_forward = (remote_host.to_owned(), remote_port);
|
||||||
|
lc.write().unwrap().port_forward_multiplex = mux;
|
||||||
interface.handle_hash(password, hash, stream).await
|
interface.handle_hash(password, hash, stream).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,14 +299,15 @@ async fn hash_arrived(
|
|||||||
pending_login: Option<UiLogin>,
|
pending_login: Option<UiLogin>,
|
||||||
remote_host: &str,
|
remote_host: &str,
|
||||||
remote_port: i32,
|
remote_port: i32,
|
||||||
|
mux: bool,
|
||||||
stream: &mut Stream,
|
stream: &mut Stream,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match pending_login {
|
match pending_login {
|
||||||
Some(login) => {
|
Some(login) => {
|
||||||
login_from_ui(interface, &hash, login, remote_host, remote_port, stream).await;
|
login_from_ui(interface, &hash, login, remote_host, remote_port, mux, stream).await;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
None => login_with_hash(interface, password, hash, remote_host, remote_port, stream).await,
|
None => login_with_hash(interface, password, hash, remote_host, remote_port, mux, stream).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,6 +319,7 @@ async fn login_from_ui(
|
|||||||
login: UiLogin,
|
login: UiLogin,
|
||||||
remote_host: &str,
|
remote_host: &str,
|
||||||
remote_port: i32,
|
remote_port: i32,
|
||||||
|
mux: bool,
|
||||||
stream: &mut Stream,
|
stream: &mut Stream,
|
||||||
) {
|
) {
|
||||||
let lc = interface.get_lch();
|
let lc = interface.get_lch();
|
||||||
@@ -324,6 +328,7 @@ async fn login_from_ui(
|
|||||||
{
|
{
|
||||||
let mut lc = lc.write().unwrap();
|
let mut lc = lc.write().unwrap();
|
||||||
lc.port_forward = (remote_host.to_owned(), remote_port);
|
lc.port_forward = (remote_host.to_owned(), remote_port);
|
||||||
|
lc.port_forward_multiplex = mux;
|
||||||
lc.set_hash(hash.clone());
|
lc.set_hash(hash.clone());
|
||||||
}
|
}
|
||||||
let (os_username, os_password, password, remember) = login;
|
let (os_username, os_password, password, remember) = login;
|
||||||
@@ -390,8 +395,8 @@ async fn establish_tunnel(
|
|||||||
/// stops at one window rather than growing without bound, and a local EOF
|
/// stops at one window rather than growing without bound, and a local EOF
|
||||||
/// no longer ends the login, since the tunnel may still be wanted. It
|
/// no longer ends the login, since the tunnel may still be wanted. It
|
||||||
/// reports what the peer answered rather than a raw stream, because the
|
/// reports what the peer answered rather than a raw stream, because the
|
||||||
/// caller's next step depends on it. The login itself is the raw pipe's:
|
/// caller's next step depends on it. The login itself is the raw pipe's,
|
||||||
/// the window's `port_forward_mux` is what makes it ask for the tunnel.
|
/// told to ask for the tunnel.
|
||||||
async fn connect_and_login_mux(
|
async fn connect_and_login_mux(
|
||||||
id: &str,
|
id: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
@@ -443,7 +448,7 @@ async fn connect_and_login_mux(
|
|||||||
match msg_in.union {
|
match msg_in.union {
|
||||||
Some(message::Union::Hash(hash)) => {
|
Some(message::Union::Hash(hash)) => {
|
||||||
challenge = Some(hash.clone());
|
challenge = Some(hash.clone());
|
||||||
if !hash_arrived(&interface, password, hash, pending_login.take(), remote_host, remote_port, &mut stream).await {
|
if !hash_arrived(&interface, password, hash, pending_login.take(), remote_host, remote_port, true, &mut stream).await {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,7 +481,7 @@ async fn connect_and_login_mux(
|
|||||||
d = ui_receiver.recv() => {
|
d = ui_receiver.recv() => {
|
||||||
match d {
|
match d {
|
||||||
Some(Data::Login(login)) => match &challenge {
|
Some(Data::Login(login)) => match &challenge {
|
||||||
Some(hash) => login_from_ui(&interface, hash, login, remote_host, remote_port, &mut stream).await,
|
Some(hash) => login_from_ui(&interface, hash, login, remote_host, remote_port, true, &mut stream).await,
|
||||||
None => pending_login = Some(login),
|
None => pending_login = Some(login),
|
||||||
},
|
},
|
||||||
Some(Data::Message(msg)) => {
|
Some(Data::Message(msg)) => {
|
||||||
@@ -698,8 +703,8 @@ mod login_tests {
|
|||||||
let (mut a, mut a_peer) = loopback().await;
|
let (mut a, mut a_peer) = loopback().await;
|
||||||
let (mut b, mut b_peer) = loopback().await;
|
let (mut b, mut b_peer) = loopback().await;
|
||||||
tokio::join!(
|
tokio::join!(
|
||||||
login_with_hash(&ui, "pw", hash("a"), "a", 1, &mut a),
|
login_with_hash(&ui, "pw", hash("a"), "a", 1, false, &mut a),
|
||||||
login_with_hash(&ui, "pw", hash("b"), "b", 2, &mut b),
|
login_with_hash(&ui, "pw", hash("b"), "b", 2, false, &mut b),
|
||||||
);
|
);
|
||||||
assert_eq!(target(&login_at(&mut a_peer).await), ("a".to_owned(), 1));
|
assert_eq!(target(&login_at(&mut a_peer).await), ("a".to_owned(), 1));
|
||||||
assert_eq!(target(&login_at(&mut b_peer).await), ("b".to_owned(), 2));
|
assert_eq!(target(&login_at(&mut b_peer).await), ("b".to_owned(), 2));
|
||||||
@@ -717,10 +722,10 @@ mod login_tests {
|
|||||||
let (mut a, mut a_peer) = loopback().await;
|
let (mut a, mut a_peer) = loopback().await;
|
||||||
let (mut b, mut b_peer) = loopback().await;
|
let (mut b, mut b_peer) = loopback().await;
|
||||||
// A's hash arrived last, so it is the one the handler holds.
|
// A's hash arrived last, so it is the one the handler holds.
|
||||||
assert!(login_with_hash(&ui, "pw", hash("a"), "a", 1, &mut a).await);
|
assert!(login_with_hash(&ui, "pw", hash("a"), "a", 1, false, &mut a).await);
|
||||||
login_at(&mut a_peer).await;
|
login_at(&mut a_peer).await;
|
||||||
let typed = (String::new(), String::new(), "pw".to_owned(), false);
|
let typed = (String::new(), String::new(), "pw".to_owned(), false);
|
||||||
login_from_ui(&ui, &hash("b"), typed, "b", 2, &mut b).await;
|
login_from_ui(&ui, &hash("b"), typed, "b", 2, false, &mut b).await;
|
||||||
let lr = login_at(&mut b_peer).await;
|
let lr = login_at(&mut b_peer).await;
|
||||||
assert_eq!(lr.password, digest("b"));
|
assert_eq!(lr.password, digest("b"));
|
||||||
assert_eq!(target(&lr), ("b".to_owned(), 2));
|
assert_eq!(target(&lr), ("b".to_owned(), 2));
|
||||||
@@ -739,12 +744,60 @@ mod login_tests {
|
|||||||
// The prompt's password reached B before its hash, and no other
|
// The prompt's password reached B before its hash, and no other
|
||||||
// mapping has stored it in the handler yet.
|
// mapping has stored it in the handler yet.
|
||||||
let typed = (String::new(), String::new(), "pw".to_owned(), false);
|
let typed = (String::new(), String::new(), "pw".to_owned(), false);
|
||||||
assert!(hash_arrived(&ui, "", hash("b"), Some(typed), "b", 2, &mut b).await);
|
assert!(hash_arrived(&ui, "", hash("b"), Some(typed), "b", 2, false, &mut b).await);
|
||||||
let lr = login_at(&mut b_peer).await;
|
let lr = login_at(&mut b_peer).await;
|
||||||
assert_eq!(lr.password, digest("b"));
|
assert_eq!(lr.password, digest("b"));
|
||||||
assert_eq!(target(&lr), ("b".to_owned(), 2));
|
assert_eq!(target(&lr), ("b".to_owned(), 2));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_raw_pipe_login_on_a_multiplexed_window_does_not_ask_for_the_tunnel() {
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rt.block_on(async {
|
||||||
|
let ui = window();
|
||||||
|
// The window probes for the tunnel, but this mapping latched to
|
||||||
|
// the raw pipe: its login must read as the raw pipe's, or an
|
||||||
|
// upgraded peer answers with a tunnel it then never gets.
|
||||||
|
ui.lc.write().unwrap().port_forward_mux = true;
|
||||||
|
let (mut a, mut a_peer) = loopback().await;
|
||||||
|
assert!(login_with_hash(&ui, "pw", hash("a"), "a", 1, false, &mut a).await);
|
||||||
|
assert!(!login_at(&mut a_peer).await.port_forward().multiplex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_password_typed_at_the_prompt_keeps_a_raw_pipe_login_raw() {
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rt.block_on(async {
|
||||||
|
let ui = window();
|
||||||
|
ui.lc.write().unwrap().port_forward_mux = true;
|
||||||
|
let (mut b, mut b_peer) = loopback().await;
|
||||||
|
let typed = (String::new(), String::new(), "pw".to_owned(), false);
|
||||||
|
login_from_ui(&ui, &hash("b"), typed, "b", 2, false, &mut b).await;
|
||||||
|
assert!(!login_at(&mut b_peer).await.port_forward().multiplex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_probing_login_asks_for_the_tunnel() {
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
rt.block_on(async {
|
||||||
|
let ui = window();
|
||||||
|
let (mut a, mut a_peer) = loopback().await;
|
||||||
|
assert!(login_with_hash(&ui, "pw", hash("a"), "a", 1, true, &mut a).await);
|
||||||
|
assert!(login_at(&mut a_peer).await.port_forward().multiplex);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in New Issue
Block a user