mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-18 10:21:03 +03:00
- prefer-P2P racing (race_transports_prefer_webrtc) across punch and RelayResponse; ICE bridge with 400ms candidate resend - controlled-side answerer and ICE routing; sign local DTLS fingerprint into SignedId, controller verifies the binding fail-closed - fix pc leaks: close_webrtc() on insecure-decline paths (io_loop, port_forward); compute direct before disarming the offerer guard - point hbb_common to the WebRTC data-plane commit 9f5a296 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
275 lines
9.7 KiB
Rust
275 lines
9.7 KiB
Rust
use std::sync::{Arc, RwLock};
|
|
|
|
use crate::client::*;
|
|
use hbb_common::{
|
|
allow_err, bail,
|
|
config::READ_TIMEOUT,
|
|
futures::{SinkExt, StreamExt},
|
|
log,
|
|
message_proto::*,
|
|
protobuf::Message as _,
|
|
rendezvous_proto::ConnType,
|
|
tcp, timeout,
|
|
tokio::{self, net::TcpStream, sync::mpsc},
|
|
tokio_util::codec::{BytesCodec, Framed},
|
|
ResultType, Stream,
|
|
};
|
|
|
|
fn run_rdp(port: u16, name: &str) {
|
|
std::process::Command::new("cmdkey")
|
|
.arg("/delete:localhost")
|
|
.output()
|
|
.ok();
|
|
let username = std::env::var("rdp_username").unwrap_or_default();
|
|
let password = std::env::var("rdp_password").unwrap_or_default();
|
|
if !username.is_empty() || !password.is_empty() {
|
|
let mut args = vec!["/generic:localhost".to_owned()];
|
|
if !username.is_empty() {
|
|
args.push(format!("/user:{}", username));
|
|
}
|
|
if !password.is_empty() {
|
|
args.push(format!("/pass:{}", password));
|
|
}
|
|
std::process::Command::new("cmdkey")
|
|
.args(&args)
|
|
.output()
|
|
.ok();
|
|
}
|
|
// Keep using /v instead of a generated .rdp file: mstsc then preserves the
|
|
// user's Default.rdp settings and avoids unsigned-file warnings or policies.
|
|
match std::process::Command::new("mstsc")
|
|
.arg(format!("/v:localhost:{}", port))
|
|
.spawn()
|
|
{
|
|
Ok(child) => {
|
|
#[cfg(windows)]
|
|
crate::platform::set_rdp_window_title(child, name.to_owned());
|
|
#[cfg(not(windows))]
|
|
let _ = (child, name);
|
|
}
|
|
Err(err) => log::warn!("Failed to launch mstsc: {}", err),
|
|
}
|
|
}
|
|
|
|
// Show the peer identity with its hostname, using the ID when no alias exists.
|
|
fn rdp_display_name(lc: &Arc<RwLock<LoginConfigHandler>>, id: &str) -> String {
|
|
let lc = lc.read().unwrap();
|
|
let alias = lc
|
|
.options
|
|
.get("alias")
|
|
.map(|s| s.trim())
|
|
.unwrap_or_default();
|
|
let hostname = lc.info.hostname.trim();
|
|
let identity = if !alias.is_empty() { alias } else { id };
|
|
if hostname.is_empty() || hostname == identity {
|
|
identity.to_owned()
|
|
} else {
|
|
format!("{} ({})", identity, hostname)
|
|
}
|
|
}
|
|
|
|
pub async fn listen(
|
|
id: String,
|
|
password: String,
|
|
port: i32,
|
|
interface: impl Interface,
|
|
ui_receiver: mpsc::UnboundedReceiver<Data>,
|
|
key: &str,
|
|
token: &str,
|
|
lc: Arc<RwLock<LoginConfigHandler>>,
|
|
remote_host: String,
|
|
remote_port: i32,
|
|
) -> ResultType<()> {
|
|
let listener = tcp::new_listener(format!("127.0.0.1:{}", port), true).await?;
|
|
let addr = listener.local_addr()?;
|
|
log::info!("listening on port {:?}", addr);
|
|
let is_rdp = port == 0;
|
|
if is_rdp {
|
|
run_rdp(addr.port(), &rdp_display_name(&lc, &id));
|
|
}
|
|
let mut ui_receiver = ui_receiver;
|
|
loop {
|
|
tokio::select! {
|
|
Ok((forward, addr)) = listener.accept() => {
|
|
log::info!("new connection from {:?}", addr);
|
|
lc.write().unwrap().port_forward = (remote_host.clone(), remote_port);
|
|
let id = id.clone();
|
|
let password = password.clone();
|
|
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(stream)) => {
|
|
let interface = interface.clone();
|
|
tokio::spawn(async move {
|
|
if let Err(err) = run_forward(forward, stream).await {
|
|
interface.msgbox("error", "Error", &err.to_string(), "");
|
|
}
|
|
log::info!("connection from {:?} closed", addr);
|
|
});
|
|
}
|
|
_ if close_port_forward => {
|
|
break;
|
|
}
|
|
Err(err) => {
|
|
interface.on_establish_connection_error(err.to_string());
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
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));
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn connect_and_login(
|
|
id: &str,
|
|
password: &str,
|
|
ui_receiver: &mut mpsc::UnboundedReceiver<Data>,
|
|
interface: impl Interface,
|
|
forward: &mut Framed<TcpStream, BytesCodec>,
|
|
key: &str,
|
|
token: &str,
|
|
is_rdp: bool,
|
|
close_port_forward: &mut bool,
|
|
) -> ResultType<Option<Stream>> {
|
|
let conn_type = if is_rdp {
|
|
ConnType::RDP
|
|
} else {
|
|
ConnType::PORT_FORWARD
|
|
};
|
|
let ((mut stream, direct, _pk, _kcp, _stream_type), (feedback, rendezvous_server)) =
|
|
Client::start(id, key, token, conn_type, interface.clone()).await?;
|
|
interface.update_direct(Some(direct));
|
|
if !stream.is_secured() && !crate::common::is_direct_ip_access(id) {
|
|
if !confirm_insecure_connection(&interface, ui_receiver).await {
|
|
*close_port_forward = true;
|
|
// Close the WebRTC pc on this decline path too (no-op for TCP/WS), matching every
|
|
// other exit in this function; a bare drop leaks it in the global session cache.
|
|
stream.close_webrtc().await;
|
|
return Ok(None);
|
|
}
|
|
}
|
|
let mut buffer = Vec::new();
|
|
let mut received = false;
|
|
|
|
let _keep_it = hc_connection(feedback, rendezvous_server, token).await;
|
|
|
|
loop {
|
|
tokio::select! {
|
|
res = timeout(READ_TIMEOUT, stream.next()) => match res {
|
|
Err(_) => {
|
|
stream.close_webrtc().await;
|
|
bail!("Timeout");
|
|
}
|
|
Ok(Some(Ok(bytes))) => {
|
|
if !received {
|
|
received = true;
|
|
interface.update_received(true);
|
|
}
|
|
let msg_in = match Message::parse_from_bytes(&bytes) {
|
|
Ok(msg) => msg,
|
|
Err(err) => {
|
|
stream.close_webrtc().await;
|
|
return Err(err.into());
|
|
}
|
|
};
|
|
match msg_in.union {
|
|
Some(message::Union::Hash(hash)) => {
|
|
if !interface.handle_hash(password, hash, &mut stream).await {
|
|
return Ok(None);
|
|
}
|
|
}
|
|
Some(message::Union::LoginResponse(lr)) => match lr.union {
|
|
Some(login_response::Union::Error(err)) => {
|
|
if !interface.handle_login_error(&err) {
|
|
stream.close_webrtc().await;
|
|
return Ok(None);
|
|
}
|
|
}
|
|
Some(login_response::Union::PeerInfo(pi)) => {
|
|
interface.handle_peer_info(pi);
|
|
break;
|
|
}
|
|
_ => {}
|
|
}
|
|
Some(message::Union::TestDelay(t)) => {
|
|
interface.handle_test_delay(t, &mut stream).await;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
Ok(Some(Err(err))) => {
|
|
stream.close_webrtc().await;
|
|
bail!("Connection closed: {}", err);
|
|
}
|
|
_ => {
|
|
stream.close_webrtc().await;
|
|
bail!("Reset by the peer");
|
|
}
|
|
},
|
|
d = ui_receiver.recv() => {
|
|
match d {
|
|
Some(Data::Login((os_username, os_password, password, remember))) => {
|
|
interface.handle_login_from_ui(os_username, os_password, password, remember, &mut stream).await;
|
|
}
|
|
Some(Data::Message(msg)) => {
|
|
allow_err!(stream.send(&msg).await);
|
|
}
|
|
_ => {}
|
|
}
|
|
},
|
|
res = forward.next() => {
|
|
if let Some(Ok(bytes)) = res {
|
|
buffer.extend(bytes);
|
|
} else {
|
|
stream.close_webrtc().await;
|
|
return Ok(None);
|
|
}
|
|
},
|
|
}
|
|
}
|
|
stream.set_raw();
|
|
if !buffer.is_empty() {
|
|
allow_err!(stream.send_bytes(buffer.into()).await);
|
|
}
|
|
Ok(Some(stream))
|
|
}
|
|
|
|
async fn run_forward(forward: Framed<TcpStream, BytesCodec>, stream: Stream) -> ResultType<()> {
|
|
log::info!("new port forwarding connection started");
|
|
let mut forward = forward;
|
|
let mut stream = stream;
|
|
loop {
|
|
tokio::select! {
|
|
res = forward.next() => {
|
|
if let Some(Ok(bytes)) = res {
|
|
allow_err!(stream.send_bytes(bytes.into()).await);
|
|
} else {
|
|
break;
|
|
}
|
|
},
|
|
res = stream.next() => {
|
|
if let Some(Ok(bytes)) = res {
|
|
allow_err!(forward.send(bytes).await);
|
|
} else {
|
|
break;
|
|
}
|
|
},
|
|
}
|
|
}
|
|
stream.close_webrtc().await;
|
|
Ok(())
|
|
}
|