mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-12 23:41:02 +03:00
server: multiplexed port-forward connections stay in the protobuf loop
Wire PortForwardMux into Connection: take the multiplexed path at login when the controller sets PortForward.multiplex, route PortForwardChannel frames to it from on_message, sweep the channel table's targets after open/close, and clean it up on connection close. Introduce is_port_forward() (socket-based or multiplexed) and use it at the four sites that classify the connection, so a multiplexed connection stays in the message loop, gets TestDelay keepalives, and reports features.port_forward_mux in PeerInfo. The three sites that break into the raw pipe loop or gate the keepalive still check port_forward_socket specifically, since a multiplexed connection must not take that path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZ49AbZJYfm8NTp5yDPMab
This commit is contained in:
@@ -257,6 +257,7 @@ pub struct Connection {
|
|||||||
view_camera: bool,
|
view_camera: bool,
|
||||||
terminal: bool,
|
terminal: bool,
|
||||||
port_forward_socket: Option<Framed<TcpStream, BytesCodec>>,
|
port_forward_socket: Option<Framed<TcpStream, BytesCodec>>,
|
||||||
|
port_forward_mux: Option<super::port_forward_mux::PortForwardMux>,
|
||||||
port_forward_address: String,
|
port_forward_address: String,
|
||||||
tx_to_cm: mpsc::UnboundedSender<ipc::Data>,
|
tx_to_cm: mpsc::UnboundedSender<ipc::Data>,
|
||||||
authorized: bool,
|
authorized: bool,
|
||||||
@@ -469,6 +470,7 @@ impl Connection {
|
|||||||
view_camera: false,
|
view_camera: false,
|
||||||
terminal: false,
|
terminal: false,
|
||||||
port_forward_socket: None,
|
port_forward_socket: None,
|
||||||
|
port_forward_mux: None,
|
||||||
port_forward_address: "".to_owned(),
|
port_forward_address: "".to_owned(),
|
||||||
tx_to_cm,
|
tx_to_cm,
|
||||||
authorized: false,
|
authorized: false,
|
||||||
@@ -1044,6 +1046,7 @@ impl Connection {
|
|||||||
conn.on_close("Timeout", true).await;
|
conn.on_close("Timeout", true).await;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
conn.push_port_forward_label();
|
||||||
// The control end will jump out of the loop after receiving LoginResponse and will not reply to the TestDelay
|
// The control end will jump out of the loop after receiving LoginResponse and will not reply to the TestDelay
|
||||||
if conn.last_test_delay.is_none() && !(conn.port_forward_socket.is_some() && conn.authorized) {
|
if conn.last_test_delay.is_none() && !(conn.port_forward_socket.is_some() && conn.authorized) {
|
||||||
conn.last_test_delay = Some(Instant::now());
|
conn.last_test_delay = Some(Instant::now());
|
||||||
@@ -1665,6 +1668,18 @@ impl Connection {
|
|||||||
let Some(login_request::Union::PortForward(pf)) = self.lr.union.as_ref() else {
|
let Some(login_request::Union::PortForward(pf)) = self.lr.union.as_ref() else {
|
||||||
return true;
|
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.
|
||||||
|
}
|
||||||
let mut pf = pf.clone();
|
let mut pf = pf.clone();
|
||||||
let (mut addr, is_rdp) = Self::normalize_port_forward_target(&mut pf);
|
let (mut addr, is_rdp) = Self::normalize_port_forward_target(&mut pf);
|
||||||
self.port_forward_address = addr.clone();
|
self.port_forward_address = addr.clone();
|
||||||
@@ -1752,7 +1767,7 @@ impl Connection {
|
|||||||
self.clear_id_whitelist_failures();
|
self.clear_id_whitelist_failures();
|
||||||
let (conn_type, auth_conn_type) = if self.file_transfer.is_some() {
|
let (conn_type, auth_conn_type) = if self.file_transfer.is_some() {
|
||||||
(1, AuthConnType::FileTransfer)
|
(1, AuthConnType::FileTransfer)
|
||||||
} else if self.port_forward_socket.is_some() {
|
} else if self.is_port_forward() {
|
||||||
(2, AuthConnType::PortForward)
|
(2, AuthConnType::PortForward)
|
||||||
} else if self.view_camera {
|
} else if self.view_camera {
|
||||||
(3, AuthConnType::ViewCamera)
|
(3, AuthConnType::ViewCamera)
|
||||||
@@ -1865,7 +1880,12 @@ impl Connection {
|
|||||||
pi.platform_additions = serde_json::to_string(&platform_additions).unwrap_or("".into());
|
pi.platform_additions = serde_json::to_string(&platform_additions).unwrap_or("".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.port_forward_socket.is_some() {
|
if self.is_port_forward() {
|
||||||
|
pi.features = Some(Features {
|
||||||
|
port_forward_mux: self.port_forward_mux.is_some(),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.into();
|
||||||
let mut msg_out = Message::new();
|
let mut msg_out = Message::new();
|
||||||
res.set_peer_info(pi);
|
res.set_peer_info(pi);
|
||||||
msg_out.set_login_response(res);
|
msg_out.set_login_response(res);
|
||||||
@@ -2063,11 +2083,16 @@ impl Connection {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn is_remote(&self) -> bool {
|
fn is_remote(&self) -> bool {
|
||||||
self.file_transfer.is_none()
|
self.file_transfer.is_none()
|
||||||
&& self.port_forward_socket.is_none()
|
&& !self.is_port_forward()
|
||||||
&& !self.view_camera
|
&& !self.view_camera
|
||||||
&& !self.terminal
|
&& !self.terminal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_port_forward(&self) -> bool {
|
||||||
|
self.port_forward_socket.is_some() || self.port_forward_mux.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
fn try_sub_monitor_services(&mut self) {
|
fn try_sub_monitor_services(&mut self) {
|
||||||
let is_remote = self.is_remote();
|
let is_remote = self.is_remote();
|
||||||
if is_remote && !self.services_subed {
|
if is_remote && !self.services_subed {
|
||||||
@@ -2211,6 +2236,14 @@ impl Connection {
|
|||||||
self.tx_to_cm.send(data).ok();
|
self.tx_to_cm.send(data).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_port_forward_label(&mut self) {
|
||||||
|
let Some(label) = self.port_forward_mux.as_mut().and_then(|m| m.sweep()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.port_forward_address = label.clone();
|
||||||
|
log::debug!("port forward targets now {}", label);
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn send_fs(&mut self, data: ipc::FS) {
|
fn send_fs(&mut self, data: ipc::FS) {
|
||||||
self.send_to_cm(ipc::Data::FS(data));
|
self.send_to_cm(ipc::Data::FS(data));
|
||||||
@@ -3867,6 +3900,25 @@ impl Connection {
|
|||||||
self.refresh_video_display(Some(request.display as usize));
|
self.refresh_video_display(Some(request.display as usize));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(message::Union::PortForwardChannel(ch)) => {
|
||||||
|
let permitted =
|
||||||
|
Self::permission(keys::OPTION_ENABLE_TUNNEL, &self.control_permissions);
|
||||||
|
// 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(_))
|
||||||
|
);
|
||||||
|
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::TerminalAction(action)) => {
|
Some(message::Union::TerminalAction(action)) => {
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
allow_err!(self.handle_terminal_action(action).await);
|
allow_err!(self.handle_terminal_action(action).await);
|
||||||
@@ -5076,6 +5128,9 @@ impl Connection {
|
|||||||
let data = ipc::Data::Close;
|
let data = ipc::Data::Close;
|
||||||
self.tx_to_cm.send(data).ok();
|
self.tx_to_cm.send(data).ok();
|
||||||
self.port_forward_socket.take();
|
self.port_forward_socket.take();
|
||||||
|
if let Some(mut mux) = self.port_forward_mux.take() {
|
||||||
|
mux.close_all();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The `reason` should be consistent with `check_if_retry` if not empty
|
// The `reason` should be consistent with `check_if_retry` if not empty
|
||||||
@@ -5677,7 +5732,7 @@ impl Connection {
|
|||||||
let allowed = match conn_type {
|
let allowed = match conn_type {
|
||||||
AuthConnType::Remote => true,
|
AuthConnType::Remote => true,
|
||||||
AuthConnType::FileTransfer => Self::is_file_transfer_scoped_message(msg),
|
AuthConnType::FileTransfer => Self::is_file_transfer_scoped_message(msg),
|
||||||
AuthConnType::PortForward => false,
|
AuthConnType::PortForward => Self::is_port_forward_scoped_message(msg),
|
||||||
AuthConnType::ViewCamera => Self::is_view_camera_scoped_message(msg),
|
AuthConnType::ViewCamera => Self::is_view_camera_scoped_message(msg),
|
||||||
AuthConnType::Terminal => Self::is_terminal_scoped_message(msg),
|
AuthConnType::Terminal => Self::is_terminal_scoped_message(msg),
|
||||||
};
|
};
|
||||||
@@ -5751,6 +5806,10 @@ impl Connection {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_port_forward_scoped_message(msg: &Message) -> bool {
|
||||||
|
matches!(msg.union.as_ref(), Some(message::Union::PortForwardChannel(_)))
|
||||||
|
}
|
||||||
|
|
||||||
fn is_terminal_scoped_message(msg: &Message) -> bool {
|
fn is_terminal_scoped_message(msg: &Message) -> bool {
|
||||||
match msg.union.as_ref() {
|
match msg.union.as_ref() {
|
||||||
Some(message::Union::TerminalAction(_)) => true,
|
Some(message::Union::TerminalAction(_)) => true,
|
||||||
@@ -5901,6 +5960,7 @@ impl Connection {
|
|||||||
Some(message::Union::ScreenshotResponse(_)) => "screenshot_response",
|
Some(message::Union::ScreenshotResponse(_)) => "screenshot_response",
|
||||||
Some(message::Union::TerminalAction(_)) => "terminal_action",
|
Some(message::Union::TerminalAction(_)) => "terminal_action",
|
||||||
Some(message::Union::TerminalResponse(_)) => "terminal_response",
|
Some(message::Union::TerminalResponse(_)) => "terminal_response",
|
||||||
|
Some(message::Union::PortForwardChannel(_)) => "port_forward_channel",
|
||||||
Some(message::Union::Misc(misc)) => Self::misc_message_family(misc),
|
Some(message::Union::Misc(misc)) => Self::misc_message_family(misc),
|
||||||
Some(_) => "message.other",
|
Some(_) => "message.other",
|
||||||
None => "empty",
|
None => "empty",
|
||||||
@@ -7230,6 +7290,10 @@ mod test {
|
|||||||
}),
|
}),
|
||||||
Some("misc.option"),
|
Some("misc.option"),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
msg(|m| m.set_port_forward_channel(PortForwardChannel::new())),
|
||||||
|
Some("port_forward_channel"),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -7291,6 +7355,10 @@ mod test {
|
|||||||
}),
|
}),
|
||||||
Some("misc.option"),
|
Some("misc.option"),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
msg(|m| m.set_port_forward_channel(PortForwardChannel::new())),
|
||||||
|
Some("port_forward_channel"),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -7391,6 +7459,7 @@ mod test {
|
|||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
|
(msg(|m| m.set_port_forward_channel(PortForwardChannel::new())), None),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user