feat: self-connection blocking (A connects to A) server-side solution

This commit is contained in:
admin committed 2026-09-16 13:33:22 +08:00
1 parent 29a953bd92
commit 18a966c28e
5 files changed
+165 -7

No files matched your search

+61 -4
View File
@@ -40,6 +40,7 @@ use hbb_common::{
use ipnetwork::Ipv4Network;
use crate::jwt;
use crate::token_device;
use std::io::Error;
use std::{
collections::HashMap,
@@ -232,13 +233,16 @@ impl RendezvousServer {
let must_login = get_arg("must-login");
log::debug!("must_login={}", must_login);
if must_login.to_uppercase() == "Y"
// 默认开启:仅当显式配置为 N(--must-login=N 或环境变量 MUST_LOGIN=N)时关闭
if must_login.to_uppercase() == "N"
|| (must_login == ""
&& std::env::var("MUST_LOGIN")
.unwrap_or_default()
.to_uppercase()
== "Y")
== "N")
{
MUST_LOGIN.store(false, Ordering::SeqCst);
} else {
MUST_LOGIN.store(true, Ordering::SeqCst);
}
@@ -250,6 +254,11 @@ impl RendezvousServer {
"N"
}
);
if MUST_LOGIN.load(Ordering::SeqCst) && jwt::SECRET.is_empty() {
log::warn!("RUSTDESK_API_JWT_KEY is not set: client token verification and self-connection blocking are DISABLED (fail-open). Set RUSTDESK_API_JWT_KEY to enable them.");
} else if !jwt::SECRET.is_empty() {
log::info!("Self-connection blocking: enabled (requires client login)");
}
if test_addr.to_lowercase() != "no" {
let test_addr = if test_addr.is_empty() {
listener.local_addr()?
@@ -562,6 +571,31 @@ impl RendezvousServer {
if let Some(sink) = sink.take() {
self.tcp_punch.lock().await.insert(try_into_v4(addr), sink);
}
// 补上 MUST_LOGIN 强制校验(打洞处已有,中继处此前缺失)
if MUST_LOGIN.load(Ordering::SeqCst)
&& (rf.token.is_empty()
|| (!jwt::SECRET.is_empty()
&& jwt::verify_token(&rf.token).is_err()))
{
log::warn!("RequestRelay rejected from {}: token missing or invalid", addr);
return true;
}
// 自连检测(fail-open:无 token/查询失败时跳过)
if !rf.token.is_empty()
&& !jwt::SECRET.is_empty()
&& jwt::verify_token(&rf.token).is_ok()
{
if let Some(device_id) = token_device::get_device_id(&rf.token).await {
if device_id == rf.id {
log::warn!(
"Blocked self-connection relay attempt from {} (device id {})",
addr,
rf.id
);
return true;
}
}
}
if let Some(peer) = self.pm.get_in_memory(&rf.id).await {
let mut msg_out = RendezvousMessage::new();
rf.socket_addr = AddrMangle::encode(addr).into();
@@ -938,8 +972,7 @@ impl RendezvousServer {
});
return Ok((msg_out, None));
} else if !jwt::SECRET.is_empty() {
let token = ph.token;
let token = jwt::verify_token(token.as_str());
let token = jwt::verify_token(&ph.token);
if token.is_err() {
let mut msg_out = RendezvousMessage::new();
msg_out.set_punch_hole_response(PunchHoleResponse {
@@ -951,6 +984,30 @@ impl RendezvousServer {
}
}
}
// 自连检测:token 有效且查得到登录设备ID时,拦截"远程连接自己"。
// 无 token(MUST_LOGIN=N 下未登录)或查询失败时跳过(fail-open,保持现状行为)。
if !ph.token.is_empty() && !jwt::SECRET.is_empty() {
let token_ok = MUST_LOGIN.load(Ordering::SeqCst) || jwt::verify_token(&ph.token).is_ok();
if token_ok {
if let Some(device_id) = token_device::get_device_id(&ph.token).await {
if device_id == ph.id {
log::warn!(
"Blocked self-connection attempt from {} (device id {})",
addr,
ph.id
);
let mut msg_out = RendezvousMessage::new();
msg_out.set_punch_hole_response(PunchHoleResponse {
other_failure: String::from(
"不能远程自己:发起方与目标是同一台设备 / You are connecting to yourself",
),
..Default::default()
});
return Ok((msg_out, None));
}
}
}
}
let id = ph.id;
// punch hole request from A, relay to B,
// check if in same intranet first,