Merge remote-tracking branch 'upstream/master' into forapi
# Conflicts: # .github/workflows/build.yaml # .github/workflows/ghcr.yml # README-DE.md # README-NL.md # README-TW.md # README.md # libs/hbb_common # src/main.rs # src/rendezvous_server.rs
This commit is contained in:
commit
916999abd3
24 files changed
+3331
-455
No files matched your search
+190
-6
@@ -7,10 +7,68 @@ use sodiumoxide::crypto::sign;
|
||||
use std::{
|
||||
io::prelude::*,
|
||||
io::Read,
|
||||
net::SocketAddr,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
time::{Instant, SystemTime},
|
||||
};
|
||||
|
||||
pub fn parse_bind_address(value: &str) -> Result<Option<IpAddr>> {
|
||||
let value = value.trim();
|
||||
if value.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
value
|
||||
.parse()
|
||||
.with_context(|| format!("Invalid bind address: {value}"))
|
||||
.map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn listen_tcp(
|
||||
bind_addr: Option<IpAddr>,
|
||||
port: u16,
|
||||
) -> ResultType<hbb_common::tokio::net::TcpListener> {
|
||||
if let Some(bind_addr) = bind_addr {
|
||||
hbb_common::tcp::new_listener(SocketAddr::new(bind_addr, port), true).await
|
||||
} else {
|
||||
hbb_common::tcp::listen_any(port).await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn console_addr(bind_addr: Option<IpAddr>, port: u16) -> Option<SocketAddr> {
|
||||
let bind_addr = bind_addr?;
|
||||
if bind_addr.is_unspecified() || bind_addr == IpAddr::V4(Ipv4Addr::LOCALHOST) {
|
||||
return None;
|
||||
}
|
||||
Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port))
|
||||
}
|
||||
|
||||
// The runtime console (check_cmd) is reached via 127.0.0.1, so when the bind
|
||||
// address does not already accept connections to 127.0.0.1 (it is neither the
|
||||
// any-address nor 127.0.0.1 itself), the console gets a dedicated listener
|
||||
// there; it is never bound to the external bind address.
|
||||
pub async fn listen_console(
|
||||
bind_addr: Option<IpAddr>,
|
||||
port: u16,
|
||||
) -> ResultType<Option<hbb_common::tokio::net::TcpListener>> {
|
||||
match console_addr(bind_addr, port) {
|
||||
Some(addr) => {
|
||||
let listener = hbb_common::tcp::new_listener(addr, true).await?;
|
||||
log::info!("Listening on tcp {} for the console", addr);
|
||||
Ok(Some(listener))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn accept_or_pending(
|
||||
listener: Option<&hbb_common::tokio::net::TcpListener>,
|
||||
) -> std::io::Result<(hbb_common::tokio::net::TcpStream, SocketAddr)> {
|
||||
match listener {
|
||||
Some(listener) => listener.accept().await,
|
||||
None => std::future::pending().await,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn get_expired_time() -> Instant {
|
||||
let now = Instant::now();
|
||||
@@ -52,6 +110,12 @@ fn arg_name(name: &str) -> String {
|
||||
name.to_uppercase().replace('_', "-")
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub fn set_arg(name: &str, value: &str) {
|
||||
std::env::set_var(arg_name(name), value);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn init_args(args: &str, name: &str, about: &str) {
|
||||
let matches = App::new(name)
|
||||
@@ -64,7 +128,7 @@ pub fn init_args(args: &str, name: &str, about: &str) {
|
||||
if let Some(section) = v.section(None::<String>) {
|
||||
section
|
||||
.iter()
|
||||
.for_each(|(k, v)| std::env::set_var(arg_name(k), v));
|
||||
.for_each(|(k, v)| set_arg(k, v));
|
||||
}
|
||||
}
|
||||
if let Some(config) = matches.value_of("config") {
|
||||
@@ -72,17 +136,42 @@ pub fn init_args(args: &str, name: &str, about: &str) {
|
||||
if let Some(section) = v.section(None::<String>) {
|
||||
section
|
||||
.iter()
|
||||
.for_each(|(k, v)| std::env::set_var(arg_name(k), v));
|
||||
.for_each(|(k, v)| set_arg(k, v));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (k, v) in matches.args {
|
||||
if let Some(v) = v.vals.first() {
|
||||
std::env::set_var(arg_name(k), v.to_string_lossy().to_string());
|
||||
set_arg(k, &v.to_string_lossy());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_arg_opt(name: &str) -> Option<String> {
|
||||
let dashed = arg_name(name);
|
||||
let underscored = dashed.replace('-', "_");
|
||||
let lower_dashed = dashed.to_lowercase();
|
||||
let lower_underscored = underscored.to_lowercase();
|
||||
for alias in [&dashed, &underscored, &lower_dashed, &lower_underscored] {
|
||||
if let Ok(value) = std::env::var(alias) {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
let mut aliases = std::env::vars_os()
|
||||
.filter_map(|(key, value)| {
|
||||
let key = key.into_string().ok()?;
|
||||
if arg_name(&key) == dashed {
|
||||
Some((key, value.into_string().ok()?))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
aliases.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
aliases.into_iter().next().map(|(_, value)| value)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub fn get_arg(name: &str) -> String {
|
||||
@@ -92,7 +181,7 @@ pub fn get_arg(name: &str) -> String {
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub fn get_arg_or(name: &str, default: String) -> String {
|
||||
std::env::var(arg_name(name)).unwrap_or(default)
|
||||
get_arg_opt(name).unwrap_or(default)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -215,4 +304,99 @@ async fn check_software_update_() -> hbb_common::ResultType<()> {
|
||||
log::info!("new version is available: {}", latest_release_version);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
#[test]
|
||||
fn argument_names_ignore_case_and_separator() {
|
||||
let aliases = [
|
||||
"RUSTDESK-CONFIG-ALIAS-TEST",
|
||||
"RUSTDESK_CONFIG_ALIAS_TEST",
|
||||
"rustdesk-config-alias-test",
|
||||
"rustdesk_config_alias_test",
|
||||
"RustDesk_Config-Alias_Test",
|
||||
];
|
||||
for alias in aliases {
|
||||
std::env::remove_var(alias);
|
||||
}
|
||||
for alias in aliases {
|
||||
std::env::set_var(alias, alias);
|
||||
assert_eq!(get_arg("RUSTDESK_CONFIG_ALIAS_TEST"), alias);
|
||||
std::env::remove_var(alias);
|
||||
}
|
||||
set_arg("rustdesk_config_alias_test", "normalized");
|
||||
assert_eq!(
|
||||
std::env::var("RUSTDESK-CONFIG-ALIAS-TEST").unwrap(),
|
||||
"normalized"
|
||||
);
|
||||
std::env::set_var("RUSTDESK_CONFIG_ALIAS_TEST", "inherited");
|
||||
set_arg("rustdesk-config-alias-test", "higher-priority");
|
||||
assert_eq!(get_arg("rustdesk_config_alias_test"), "higher-priority");
|
||||
std::env::remove_var("RUSTDESK-CONFIG-ALIAS-TEST");
|
||||
std::env::remove_var("RUSTDESK_CONFIG_ALIAS_TEST");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_bind_address() {
|
||||
assert_eq!(parse_bind_address("").unwrap(), None);
|
||||
assert_eq!(
|
||||
parse_bind_address("127.0.0.1").unwrap(),
|
||||
Some(IpAddr::V4(Ipv4Addr::LOCALHOST))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_bind_address("::1").unwrap(),
|
||||
Some(IpAddr::V6(Ipv6Addr::LOCALHOST))
|
||||
);
|
||||
assert!(parse_bind_address("not-an-ip").is_err());
|
||||
}
|
||||
|
||||
#[hbb_common::tokio::test]
|
||||
async fn tcp_listener_uses_bind_address() {
|
||||
let bind_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
|
||||
let listener = listen_tcp(Some(bind_addr), 0).await.unwrap();
|
||||
assert_eq!(listener.local_addr().unwrap().ip(), bind_addr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn console_addr_only_when_bind_does_not_cover_ipv4_localhost() {
|
||||
for bind_addr in [
|
||||
None,
|
||||
Some(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
|
||||
Some(IpAddr::V6(Ipv6Addr::UNSPECIFIED)),
|
||||
Some(IpAddr::V4(Ipv4Addr::LOCALHOST)),
|
||||
] {
|
||||
assert_eq!(console_addr(bind_addr, 21117), None);
|
||||
}
|
||||
for bind_addr in [
|
||||
Some(IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1))),
|
||||
Some("2001:db8::1".parse().unwrap()),
|
||||
Some(IpAddr::V6(Ipv6Addr::LOCALHOST)),
|
||||
] {
|
||||
assert_eq!(
|
||||
console_addr(bind_addr, 21117),
|
||||
Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 21117))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[hbb_common::tokio::test]
|
||||
async fn console_listener_binds_ipv4_localhost() {
|
||||
let listener = listen_console(Some(IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1))), 0)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
listener.local_addr().unwrap().ip(),
|
||||
IpAddr::V4(Ipv4Addr::LOCALHOST)
|
||||
);
|
||||
assert!(listen_console(None, 0).await.unwrap().is_none());
|
||||
assert!(listen_console(Some(IpAddr::V4(Ipv4Addr::LOCALHOST)), 0)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none());
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -51,8 +51,7 @@ impl Database {
|
||||
if !std::path::Path::new(url).exists() {
|
||||
std::fs::File::create(url).ok();
|
||||
}
|
||||
let n: usize = std::env::var("MAX_DATABASE_CONNECTIONS")
|
||||
.unwrap_or_else(|_| "1".to_owned())
|
||||
let n: usize = crate::common::get_arg_or("MAX_DATABASE_CONNECTIONS", "1".to_owned())
|
||||
.parse()
|
||||
.unwrap_or(1);
|
||||
log::debug!("MAX_DATABASE_CONNECTIONS={}", n);
|
||||
|
||||
+16
-7
@@ -13,7 +13,8 @@ fn main() -> ResultType<()> {
|
||||
.write_mode(WriteMode::Async)
|
||||
.start()?;
|
||||
let args = format!(
|
||||
"-p, --port=[NUMBER(default={RELAY_PORT})] 'Sets the listening port'
|
||||
"-b, --bind=[IP] 'Sets the IP address to bind to (default: all interfaces)'
|
||||
-p, --port=[NUMBER(default={RELAY_PORT})] 'Sets the listening port'
|
||||
-k, --key=[KEY] 'Only allow the client with the same key'
|
||||
",
|
||||
);
|
||||
@@ -25,21 +26,29 @@ fn main() -> ResultType<()> {
|
||||
.get_matches();
|
||||
if let Ok(v) = ini::Ini::load_from_file(".env") {
|
||||
if let Some(section) = v.section(None::<String>) {
|
||||
section.iter().for_each(|(k, v)| std::env::set_var(k, v));
|
||||
section.iter().for_each(|(k, v)| common::set_arg(k, v));
|
||||
}
|
||||
}
|
||||
let mut port = RELAY_PORT;
|
||||
if let Ok(v) = std::env::var("PORT") {
|
||||
if let Some(v) = common::get_arg_opt("PORT") {
|
||||
let v: i32 = v.parse().unwrap_or_default();
|
||||
if v > 0 {
|
||||
port = v + 1;
|
||||
}
|
||||
}
|
||||
start(
|
||||
let bind = matches
|
||||
.value_of("bind")
|
||||
.map(str::to_owned)
|
||||
.unwrap_or_else(|| common::get_arg("BIND"));
|
||||
let bind_addr = common::parse_bind_address(&bind)?;
|
||||
let key = matches
|
||||
.value_of("key")
|
||||
.map(str::to_owned)
|
||||
.unwrap_or_else(|| common::get_arg("KEY"));
|
||||
start_with_bind(
|
||||
bind_addr,
|
||||
matches.value_of("port").unwrap_or(&port.to_string()),
|
||||
matches
|
||||
.value_of("key")
|
||||
.unwrap_or(&std::env::var("KEY").unwrap_or_default()),
|
||||
&key,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
+13
-5
@@ -15,13 +15,14 @@ fn main() -> ResultType<()> {
|
||||
.start()?;
|
||||
let args = format!(
|
||||
"-c --config=[FILE] +takes_value 'Sets a custom config file'
|
||||
-b, --bind=[IP] 'Sets the IP address to bind to (default: all interfaces)'
|
||||
-p, --port=[NUMBER(default={RENDEZVOUS_PORT})] 'Sets the listening port'
|
||||
-s, --serial=[NUMBER(default=0)] 'Sets configure update serial number'
|
||||
-R, --rendezvous-servers=[HOSTS] 'Sets rendezvous servers, separated by comma'
|
||||
-u, --software-url=[URL] 'Sets download url of RustDesk software of newest version'
|
||||
-s, --serial=[NUMBER(default=0)] '[DEPRECATED] Sets configure update serial number'
|
||||
-R, --rendezvous-servers=[HOSTS] '[DEPRECATED] Sets rendezvous servers, separated by comma'
|
||||
-u, --software-url=[URL] '[DEPRECATED] Sets download url of RustDesk software of newest version'
|
||||
-r, --relay-servers=[HOST] 'Sets the default relay servers, separated by comma'
|
||||
-M, --rmem=[NUMBER(default={RMEM})] 'Sets UDP recv buffer size, set system rmem_max first, e.g., sudo sysctl -w net.core.rmem_max=52428800. vi /etc/sysctl.conf, net.core.rmem_max=52428800, sudo sysctl –p'
|
||||
, --mask=[MASK] 'Determine if the connection comes from LAN, e.g. 192.168.0.0/16'
|
||||
, --mask=[MASK] '[DEPRECATED] Determine if the connection comes from LAN, e.g. 192.168.0.0/16'
|
||||
-k, --key=[KEY] 'Only allow the client with the same key'
|
||||
, --must-login=[Y|N] 'Only allow the client with login'",
|
||||
);
|
||||
@@ -30,9 +31,16 @@ fn main() -> ResultType<()> {
|
||||
if port < 3 {
|
||||
bail!("Invalid port");
|
||||
}
|
||||
let bind_addr = parse_bind_address(&get_arg("bind"))?;
|
||||
let rmem = get_arg("rmem").parse::<usize>().unwrap_or(RMEM);
|
||||
let serial: i32 = get_arg("serial").parse().unwrap_or(0);
|
||||
crate::common::check_software_update();
|
||||
RendezvousServer::start(port, serial, &get_arg_or("key", "-".to_owned()), rmem)?;
|
||||
RendezvousServer::start_with_bind(
|
||||
bind_addr,
|
||||
port,
|
||||
serial,
|
||||
&get_arg_or("key", "-".to_owned()),
|
||||
rmem,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
+1
-1
@@ -67,7 +67,7 @@ pub(crate) struct PeerMap {
|
||||
|
||||
impl PeerMap {
|
||||
pub(crate) async fn new() -> ResultType<Self> {
|
||||
let db = std::env::var("DB_URL").unwrap_or({
|
||||
let db = get_arg_opt("DB_URL").unwrap_or_else(|| {
|
||||
let mut db = "db_v2.sqlite3".to_owned();
|
||||
#[cfg(all(windows, not(debug_assertions)))]
|
||||
{
|
||||
|
||||
+54
-17
@@ -8,7 +8,7 @@ use hbb_common::{
|
||||
protobuf::Message as _,
|
||||
rendezvous_proto::*,
|
||||
sleep,
|
||||
tcp::{listen_any, FramedStream},
|
||||
tcp::FramedStream,
|
||||
timeout,
|
||||
tokio::{
|
||||
self,
|
||||
@@ -24,7 +24,7 @@ use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
io::prelude::*,
|
||||
io::Error,
|
||||
net::SocketAddr,
|
||||
net::{IpAddr, SocketAddr},
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
@@ -39,14 +39,18 @@ lazy_static::lazy_static! {
|
||||
|
||||
static DOWNGRADE_THRESHOLD_100: AtomicUsize = AtomicUsize::new(66); // 0.66
|
||||
static DOWNGRADE_START_CHECK: AtomicUsize = AtomicUsize::new(1_800_000); // in ms
|
||||
static LIMIT_SPEED: AtomicUsize = AtomicUsize::new(4 * 1024 * 1024); // in bit/s
|
||||
static LIMIT_SPEED: AtomicUsize = AtomicUsize::new(32 * 1024 * 1024); // in bit/s
|
||||
static TOTAL_BANDWIDTH: AtomicUsize = AtomicUsize::new(1024 * 1024 * 1024); // in bit/s
|
||||
static SINGLE_BANDWIDTH: AtomicUsize = AtomicUsize::new(16 * 1024 * 1024); // in bit/s
|
||||
static SINGLE_BANDWIDTH: AtomicUsize = AtomicUsize::new(128 * 1024 * 1024); // in bit/s
|
||||
const BLACKLIST_FILE: &str = "blacklist.txt";
|
||||
const BLOCKLIST_FILE: &str = "blocklist.txt";
|
||||
|
||||
#[tokio::main(flavor = "multi_thread")]
|
||||
pub async fn start(port: &str, key: &str) -> ResultType<()> {
|
||||
pub async fn start_with_bind(
|
||||
bind_addr: Option<IpAddr>,
|
||||
port: &str,
|
||||
key: &str,
|
||||
) -> ResultType<()> {
|
||||
let key = get_server_sk(key);
|
||||
if let Ok(mut file) = std::fs::File::open(BLACKLIST_FILE) {
|
||||
let mut contents = String::new();
|
||||
@@ -85,7 +89,13 @@ pub async fn start(port: &str, key: &str) -> ResultType<()> {
|
||||
let main_task = async move {
|
||||
loop {
|
||||
log::info!("Start");
|
||||
io_loop(listen_any(port).await?, listen_any(port2).await?, &key).await;
|
||||
io_loop(
|
||||
crate::common::listen_tcp(bind_addr, port).await?,
|
||||
crate::common::listen_tcp(bind_addr, port2).await?,
|
||||
crate::common::listen_console(bind_addr, port).await?,
|
||||
&key,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
};
|
||||
let listen_signal = crate::common::listen_signal();
|
||||
@@ -96,8 +106,8 @@ pub async fn start(port: &str, key: &str) -> ResultType<()> {
|
||||
}
|
||||
|
||||
fn check_params() {
|
||||
let tmp = std::env::var("DOWNGRADE_THRESHOLD")
|
||||
.map(|x| x.parse::<f64>().unwrap_or(0.))
|
||||
let tmp = crate::common::get_arg("DOWNGRADE_THRESHOLD")
|
||||
.parse::<f64>()
|
||||
.unwrap_or(0.);
|
||||
if tmp > 0. {
|
||||
DOWNGRADE_THRESHOLD_100.store((tmp * 100.) as _, Ordering::SeqCst);
|
||||
@@ -106,8 +116,8 @@ fn check_params() {
|
||||
"DOWNGRADE_THRESHOLD: {}",
|
||||
DOWNGRADE_THRESHOLD_100.load(Ordering::SeqCst) as f64 / 100.
|
||||
);
|
||||
let tmp = std::env::var("DOWNGRADE_START_CHECK")
|
||||
.map(|x| x.parse::<usize>().unwrap_or(0))
|
||||
let tmp = crate::common::get_arg("DOWNGRADE_START_CHECK")
|
||||
.parse::<usize>()
|
||||
.unwrap_or(0);
|
||||
if tmp > 0 {
|
||||
DOWNGRADE_START_CHECK.store(tmp * 1000, Ordering::SeqCst);
|
||||
@@ -116,8 +126,8 @@ fn check_params() {
|
||||
"DOWNGRADE_START_CHECK: {}s",
|
||||
DOWNGRADE_START_CHECK.load(Ordering::SeqCst) / 1000
|
||||
);
|
||||
let tmp = std::env::var("LIMIT_SPEED")
|
||||
.map(|x| x.parse::<f64>().unwrap_or(0.))
|
||||
let tmp = crate::common::get_arg("LIMIT_SPEED")
|
||||
.parse::<f64>()
|
||||
.unwrap_or(0.);
|
||||
if tmp > 0. {
|
||||
LIMIT_SPEED.store((tmp * 1024. * 1024.) as usize, Ordering::SeqCst);
|
||||
@@ -126,8 +136,8 @@ fn check_params() {
|
||||
"LIMIT_SPEED: {}Mb/s",
|
||||
LIMIT_SPEED.load(Ordering::SeqCst) as f64 / 1024. / 1024.
|
||||
);
|
||||
let tmp = std::env::var("TOTAL_BANDWIDTH")
|
||||
.map(|x| x.parse::<f64>().unwrap_or(0.))
|
||||
let tmp = crate::common::get_arg("TOTAL_BANDWIDTH")
|
||||
.parse::<f64>()
|
||||
.unwrap_or(0.);
|
||||
if tmp > 0. {
|
||||
TOTAL_BANDWIDTH.store((tmp * 1024. * 1024.) as usize, Ordering::SeqCst);
|
||||
@@ -137,8 +147,8 @@ fn check_params() {
|
||||
"TOTAL_BANDWIDTH: {}Mb/s",
|
||||
TOTAL_BANDWIDTH.load(Ordering::SeqCst) as f64 / 1024. / 1024.
|
||||
);
|
||||
let tmp = std::env::var("SINGLE_BANDWIDTH")
|
||||
.map(|x| x.parse::<f64>().unwrap_or(0.))
|
||||
let tmp = crate::common::get_arg("SINGLE_BANDWIDTH")
|
||||
.parse::<f64>()
|
||||
.unwrap_or(0.);
|
||||
if tmp > 0. {
|
||||
SINGLE_BANDWIDTH.store((tmp * 1024. * 1024.) as usize, Ordering::SeqCst);
|
||||
@@ -323,7 +333,12 @@ async fn check_cmd(cmd: &str, limiter: Limiter) -> String {
|
||||
res
|
||||
}
|
||||
|
||||
async fn io_loop(listener: TcpListener, listener2: TcpListener, key: &str) {
|
||||
async fn io_loop(
|
||||
listener: TcpListener,
|
||||
listener2: TcpListener,
|
||||
listener_console: Option<TcpListener>,
|
||||
key: &str,
|
||||
) {
|
||||
check_params();
|
||||
let limiter = <Limiter>::new(TOTAL_BANDWIDTH.load(Ordering::SeqCst) as _);
|
||||
loop {
|
||||
@@ -352,6 +367,18 @@ async fn io_loop(listener: TcpListener, listener2: TcpListener, key: &str) {
|
||||
}
|
||||
}
|
||||
}
|
||||
res = crate::common::accept_or_pending(listener_console.as_ref()) => {
|
||||
match res {
|
||||
Ok((stream, addr)) => {
|
||||
stream.set_nodelay(true).ok();
|
||||
handle_connection(stream, addr, &limiter, key, false).await;
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("console listener.accept failed: {}", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -401,6 +428,15 @@ async fn make_pair(
|
||||
use tokio_tungstenite::tungstenite::handshake::server::{Request, Response};
|
||||
let callback = |req: &Request, response: Response| {
|
||||
let headers = req.headers();
|
||||
// X-Real-IP / X-Forwarded-For are trusted as-is so that the real
|
||||
// client IP is preserved when the WebSocket port runs behind a
|
||||
// reverse proxy (WSS). They are NOT validated: anyone who can reach
|
||||
// this port directly can spoof an arbitrary IP, bypassing IP-based
|
||||
// rate limiting / blocking and corrupting logged IPs. Do not expose
|
||||
// the WebSocket port directly to untrusted networks; only the
|
||||
// reverse proxy, which overwrites these headers, should be able to
|
||||
// connect to it.
|
||||
// https://github.com/rustdesk/rustdesk-server/issues/634
|
||||
let real_ip = headers
|
||||
.get("X-Real-IP")
|
||||
.or_else(|| headers.get("X-Forwarded-For"))
|
||||
@@ -428,6 +464,7 @@ async fn make_pair_(stream: impl StreamTrait, addr: SocketAddr, key: &str, limit
|
||||
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
|
||||
if let Some(rendezvous_message::Union::RequestRelay(rf)) = msg_in.union {
|
||||
if !key.is_empty() && rf.licence_key != key {
|
||||
log::warn!("Relay authentication failed from {} - invalid key", addr);
|
||||
return;
|
||||
}
|
||||
if !rf.uuid.is_empty() {
|
||||
|
||||
+133
-38
@@ -23,7 +23,7 @@ use hbb_common::{
|
||||
sodiumoxide::hex,
|
||||
tcp,
|
||||
tcp::Encrypt,
|
||||
tcp::{listen_any, FramedStream},
|
||||
tcp::FramedStream,
|
||||
timeout,
|
||||
tokio::{
|
||||
self,
|
||||
@@ -56,7 +56,7 @@ enum Data {
|
||||
RelayServers(RelayServers),
|
||||
}
|
||||
|
||||
const REG_TIMEOUT: i32 = 30_000;
|
||||
const REG_TIMEOUT: i64 = 30_000;
|
||||
type TcpStreamSink = SplitSink<Framed<TcpStream, BytesCodec>, Bytes>;
|
||||
type WsSink = SplitSink<tokio_tungstenite::WebSocketStream<TcpStream>, tungstenite::Message>;
|
||||
struct SafeWsSink {
|
||||
@@ -105,6 +105,14 @@ const CHECK_RELAY_TIMEOUT: u64 = 3_000;
|
||||
static ALWAYS_USE_RELAY: AtomicBool = AtomicBool::new(false);
|
||||
static MUST_LOGIN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
// Store punch hole requests
|
||||
use once_cell::sync::Lazy;
|
||||
use tokio::sync::Mutex as TokioMutex; // differentiate if needed
|
||||
#[derive(Clone)]
|
||||
struct PunchReqEntry { tm: Instant, from_ip: String, to_ip: String, to_id: String }
|
||||
static PUNCH_REQS: Lazy<TokioMutex<Vec<PunchReqEntry>>> = Lazy::new(|| TokioMutex::new(Vec::new()));
|
||||
const PUNCH_REQ_DEDUPE_SEC: u64 = 60;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Inner {
|
||||
serial: i32,
|
||||
@@ -134,21 +142,29 @@ enum LoopFailure {
|
||||
Listener3,
|
||||
Listener2,
|
||||
Listener,
|
||||
ConsoleListener,
|
||||
}
|
||||
|
||||
impl RendezvousServer {
|
||||
pub fn start(port: i32, serial: i32, key: &str, rmem: usize) -> ResultType<()> {
|
||||
Self::start_with_bind(None, port, serial, key, rmem)
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "multi_thread")]
|
||||
pub async fn start(port: i32, serial: i32, key: &str, rmem: usize) -> ResultType<()> {
|
||||
pub async fn start_with_bind(
|
||||
bind_addr: Option<IpAddr>,
|
||||
port: i32,
|
||||
serial: i32,
|
||||
key: &str,
|
||||
rmem: usize,
|
||||
) -> ResultType<()> {
|
||||
let (key, sk) = Self::get_server_sk(key);
|
||||
let nat_port = port - 1;
|
||||
let ws_port = port + 2;
|
||||
let pm = PeerMap::new().await?;
|
||||
log::info!("serial={}", serial);
|
||||
let rendezvous_servers = get_servers(&get_arg("rendezvous-servers"), "rendezvous-servers");
|
||||
log::info!("Listening on tcp/udp :{}", port);
|
||||
log::info!("Listening on tcp :{}, extra port for NAT test", nat_port);
|
||||
log::info!("Listening on websocket :{}", ws_port);
|
||||
let mut socket = create_udp_listener(port, rmem).await?;
|
||||
let mut socket = create_udp_listener(bind_addr, port, rmem).await?;
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<Data>();
|
||||
let software_url = get_arg("software-url");
|
||||
let version = hbb_common::get_version_from_url(&software_url);
|
||||
@@ -191,15 +207,18 @@ impl RendezvousServer {
|
||||
log::info!("local-ip: {:?}", rs.inner.local_ip);
|
||||
std::env::set_var("PORT_FOR_API", port.to_string());
|
||||
rs.parse_relay_servers(&get_arg("relay-servers"));
|
||||
let mut listener = create_tcp_listener(port).await?;
|
||||
let mut listener2 = create_tcp_listener(nat_port).await?;
|
||||
let mut listener3 = create_tcp_listener(ws_port).await?;
|
||||
let test_addr = std::env::var("TEST_HBBS").unwrap_or_default();
|
||||
if std::env::var("ALWAYS_USE_RELAY")
|
||||
.unwrap_or_default()
|
||||
.to_uppercase()
|
||||
== "Y"
|
||||
{
|
||||
let mut listener = create_tcp_listener(bind_addr, port).await?;
|
||||
let mut listener2 = create_tcp_listener(bind_addr, nat_port).await?;
|
||||
let mut listener3 = create_tcp_listener(bind_addr, ws_port).await?;
|
||||
let mut listener_console = listen_console(bind_addr, nat_port as _).await?;
|
||||
log::info!("Listening on tcp/udp {}", listener.local_addr()?);
|
||||
log::info!(
|
||||
"Listening on tcp {}, extra port for NAT test",
|
||||
listener2.local_addr()?
|
||||
);
|
||||
log::info!("Listening on websocket {}", listener3.local_addr()?);
|
||||
let test_addr = get_arg("TEST_HBBS");
|
||||
if get_arg("ALWAYS_USE_RELAY").to_uppercase() == "Y" {
|
||||
ALWAYS_USE_RELAY.store(true, Ordering::SeqCst);
|
||||
}
|
||||
log::info!(
|
||||
@@ -262,6 +281,7 @@ impl RendezvousServer {
|
||||
&mut listener,
|
||||
&mut listener2,
|
||||
&mut listener3,
|
||||
&mut listener_console,
|
||||
&mut socket,
|
||||
&key,
|
||||
)
|
||||
@@ -269,19 +289,23 @@ impl RendezvousServer {
|
||||
{
|
||||
LoopFailure::UdpSocket => {
|
||||
drop(socket);
|
||||
socket = create_udp_listener(port, rmem).await?;
|
||||
socket = create_udp_listener(bind_addr, port, rmem).await?;
|
||||
}
|
||||
LoopFailure::Listener => {
|
||||
drop(listener);
|
||||
listener = create_tcp_listener(port).await?;
|
||||
listener = create_tcp_listener(bind_addr, port).await?;
|
||||
}
|
||||
LoopFailure::Listener2 => {
|
||||
drop(listener2);
|
||||
listener2 = create_tcp_listener(nat_port).await?;
|
||||
listener2 = create_tcp_listener(bind_addr, nat_port).await?;
|
||||
}
|
||||
LoopFailure::ConsoleListener => {
|
||||
drop(listener_console.take());
|
||||
listener_console = listen_console(bind_addr, nat_port as _).await?;
|
||||
}
|
||||
LoopFailure::Listener3 => {
|
||||
drop(listener3);
|
||||
listener3 = create_tcp_listener(ws_port).await?;
|
||||
listener3 = create_tcp_listener(bind_addr, ws_port).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,6 +323,7 @@ impl RendezvousServer {
|
||||
listener: &mut TcpListener,
|
||||
listener2: &mut TcpListener,
|
||||
listener3: &mut TcpListener,
|
||||
listener_console: &mut Option<TcpListener>,
|
||||
socket: &mut FramedSocket,
|
||||
key: &str,
|
||||
) -> LoopFailure {
|
||||
@@ -350,6 +375,18 @@ impl RendezvousServer {
|
||||
}
|
||||
}
|
||||
}
|
||||
res = accept_or_pending(listener_console.as_ref()) => {
|
||||
match res {
|
||||
Ok((stream, addr)) => {
|
||||
stream.set_nodelay(true).ok();
|
||||
self.handle_listener2(stream, addr).await;
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("console listener.accept failed: {}", err);
|
||||
return LoopFailure::ConsoleListener;
|
||||
}
|
||||
}
|
||||
}
|
||||
res = listener3.accept() => {
|
||||
match res {
|
||||
Ok((stream, addr)) => {
|
||||
@@ -432,22 +469,14 @@ impl RendezvousServer {
|
||||
}
|
||||
}
|
||||
Some(rendezvous_message::Union::PunchHoleRequest(ph)) => {
|
||||
if self.pm.is_in_memory(&ph.id).await {
|
||||
self.handle_udp_punch_hole_request(addr, ph, key).await?;
|
||||
} else {
|
||||
// not in memory, fetch from db with spawn in case blocking me
|
||||
let mut me = self.clone();
|
||||
let key = key.to_owned();
|
||||
tokio::spawn(async move {
|
||||
allow_err!(me.handle_udp_punch_hole_request(addr, ph, &key).await);
|
||||
});
|
||||
}
|
||||
// UDP PunchHoleRequest is intentionally unsupported.
|
||||
// The supported client path sends PunchHoleRequest over TCP/WS.
|
||||
}
|
||||
Some(rendezvous_message::Union::PunchHoleSent(phs)) => {
|
||||
self.handle_hole_sent(phs, addr, Some(socket)).await?;
|
||||
// UDP PunchHoleSent is intentionally unsupported to avoid UDP reflection/amplification
|
||||
}
|
||||
Some(rendezvous_message::Union::LocalAddr(la)) => {
|
||||
self.handle_local_addr(la, addr, Some(socket)).await?;
|
||||
// UDP LocalAddr is intentionally unsupported to avoid UDP reflection/amplification
|
||||
}
|
||||
Some(rendezvous_message::Union::ConfigureUpdate(mut cu)) => {
|
||||
if try_into_v4(addr).ip().is_loopback() && cu.serial > self.inner.serial {
|
||||
@@ -665,7 +694,7 @@ impl RendezvousServer {
|
||||
let mut states = BytesMut::zeroed((peers.len() + 7) / 8);
|
||||
for (i, peer_id) in peers.iter().enumerate() {
|
||||
if let Some(peer) = self.pm.get_in_memory(peer_id).await {
|
||||
let elapsed = peer.read().await.last_reg_time.elapsed().as_millis() as i32;
|
||||
let elapsed = peer.read().await.last_reg_time.elapsed().as_millis() as i64;
|
||||
// bytes index from left to right
|
||||
let states_idx = i / 8;
|
||||
let bit_idx = 7 - i % 8;
|
||||
@@ -891,6 +920,7 @@ impl RendezvousServer {
|
||||
) -> ResultType<(RendezvousMessage, Option<SocketAddr>)> {
|
||||
let mut ph = ph;
|
||||
if !key.is_empty() && ph.licence_key != key {
|
||||
log::warn!("Authentication failed from {} for peer {} - invalid key", addr, ph.id);
|
||||
let mut msg_out = RendezvousMessage::new();
|
||||
msg_out.set_punch_hole_response(PunchHoleResponse {
|
||||
failure: punch_hole_response::Failure::LICENSE_MISMATCH.into(),
|
||||
@@ -930,7 +960,7 @@ impl RendezvousServer {
|
||||
if let Some(peer) = self.pm.get(&id).await {
|
||||
let (elapsed, peer_addr) = {
|
||||
let r = peer.read().await;
|
||||
(r.last_reg_time.elapsed().as_millis() as i32, r.socket_addr)
|
||||
(r.last_reg_time.elapsed().as_millis() as i64, r.socket_addr)
|
||||
};
|
||||
if elapsed >= REG_TIMEOUT {
|
||||
let mut msg_out = RendezvousMessage::new();
|
||||
@@ -940,6 +970,23 @@ impl RendezvousServer {
|
||||
});
|
||||
return Ok((msg_out, None));
|
||||
}
|
||||
|
||||
// record punch hole request (from addr -> peer id/peer_addr)
|
||||
{
|
||||
let from_ip = try_into_v4(addr).ip().to_string();
|
||||
let to_ip = try_into_v4(peer_addr).ip().to_string();
|
||||
let to_id_clone = id.clone();
|
||||
let mut lock = PUNCH_REQS.lock().await;
|
||||
let mut dup = false;
|
||||
for e in lock.iter().rev().take(30) { // only check recent tail subset for speed
|
||||
if e.from_ip == from_ip && e.to_id == to_id_clone {
|
||||
if e.tm.elapsed().as_secs() < PUNCH_REQ_DEDUPE_SEC { dup = true; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !dup { lock.push(PunchReqEntry { tm: Instant::now(), from_ip, to_ip, to_id: to_id_clone }); }
|
||||
}
|
||||
|
||||
let mut msg_out = RendezvousMessage::new();
|
||||
let peer_is_lan = self.is_lan(peer_addr);
|
||||
let is_lan = self.is_lan(addr);
|
||||
@@ -1136,11 +1183,12 @@ impl RendezvousServer {
|
||||
match fds.next() {
|
||||
Some("h") => {
|
||||
res = format!(
|
||||
"{}\n{}\n{}\n{}\n{}\n{}\n{}\n",
|
||||
"{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n",
|
||||
"relay-servers(rs) <separated by ,>",
|
||||
"reload-geo(rg)",
|
||||
"ip-blocker(ib) [<ip>|<number>] [-]",
|
||||
"ip-changes(ic) [<id>|<number>] [-]",
|
||||
"punch-requests(pr) [<number>] [-]",
|
||||
"always-use-relay(aur) [Y|N]",
|
||||
"test-geo(tg) <ip1> <ip2>",
|
||||
"must-login(ml) [Y|N]",
|
||||
@@ -1241,6 +1289,24 @@ impl RendezvousServer {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some("punch-requests" | "pr") => {
|
||||
use std::fmt::Write as _;
|
||||
let mut lock = PUNCH_REQS.lock().await;
|
||||
let arg = fds.next();
|
||||
if let Some("-") = arg { lock.clear(); }
|
||||
else {
|
||||
let mut start = arg.and_then(|x| x.parse::<usize>().ok()).unwrap_or(0);
|
||||
let mut page_size = fds.next().and_then(|x| x.parse::<usize>().ok()).unwrap_or(10);
|
||||
if page_size == 0 { page_size = 10; }
|
||||
for (_, e) in lock.iter().enumerate().skip(start).take(page_size) {
|
||||
let age = e.tm.elapsed();
|
||||
let event_system = std::time::SystemTime::now() - age;
|
||||
let event_iso = chrono::DateTime::<chrono::Utc>::from(event_system)
|
||||
.to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
|
||||
let _ = writeln!(res, "{} {} -> {}@{}", event_iso, e.from_ip, e.to_id, e.to_ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some("always-use-relay" | "aur") => {
|
||||
if let Some(rs) = fds.next() {
|
||||
if rs.to_uppercase() == "Y" {
|
||||
@@ -1348,6 +1414,15 @@ impl RendezvousServer {
|
||||
use tokio_tungstenite::tungstenite::handshake::server::{Request, Response};
|
||||
let callback = |req: &Request, response: Response| {
|
||||
let headers = req.headers();
|
||||
// X-Real-IP / X-Forwarded-For are trusted as-is so that the real
|
||||
// client IP is preserved when the WebSocket port runs behind a
|
||||
// reverse proxy (WSS). They are NOT validated: anyone who can reach
|
||||
// this port directly can spoof an arbitrary IP, bypassing IP-based
|
||||
// rate limiting / blocking and corrupting logged IPs. Do not expose
|
||||
// the WebSocket port directly to untrusted networks; only the
|
||||
// reverse proxy, which overwrites these headers, should be able to
|
||||
// connect to it.
|
||||
// https://github.com/rustdesk/rustdesk-server/issues/634
|
||||
let real_ip = headers
|
||||
.get("X-Real-IP")
|
||||
.or_else(|| headers.get("X-Forwarded-For"))
|
||||
@@ -1582,7 +1657,15 @@ async fn send_rk_res(
|
||||
socket.send(&msg_out, addr).await
|
||||
}
|
||||
|
||||
async fn create_udp_listener(port: i32, rmem: usize) -> ResultType<FramedSocket> {
|
||||
async fn create_udp_listener(
|
||||
bind_addr: Option<IpAddr>,
|
||||
port: i32,
|
||||
rmem: usize,
|
||||
) -> ResultType<FramedSocket> {
|
||||
if let Some(bind_addr) = bind_addr {
|
||||
let addr = SocketAddr::new(bind_addr, port as _);
|
||||
return FramedSocket::new_reuse(&addr, true, rmem).await;
|
||||
}
|
||||
let addr = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port as _);
|
||||
if let Ok(s) = FramedSocket::new_reuse(&addr, true, rmem).await {
|
||||
log::debug!("listen on udp {:?}", s.local_addr());
|
||||
@@ -1595,8 +1678,8 @@ async fn create_udp_listener(port: i32, rmem: usize) -> ResultType<FramedSocket>
|
||||
}
|
||||
|
||||
#[inline]
|
||||
async fn create_tcp_listener(port: i32) -> ResultType<TcpListener> {
|
||||
let s = listen_any(port as _).await?;
|
||||
async fn create_tcp_listener(bind_addr: Option<IpAddr>, port: i32) -> ResultType<TcpListener> {
|
||||
let s = listen_tcp(bind_addr, port as _).await?;
|
||||
log::debug!("listen on tcp {:?}", s.local_addr());
|
||||
Ok(s)
|
||||
}
|
||||
@@ -1619,3 +1702,15 @@ fn get_symetric_key_from_msg(
|
||||
Err(e) => panic!("Error while opening the seal key{:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[hbb_common::tokio::test]
|
||||
async fn udp_listener_uses_bind_address() {
|
||||
let bind_addr = IpAddr::V4(Ipv4Addr::LOCALHOST);
|
||||
let socket = create_udp_listener(Some(bind_addr), 0, 0).await.unwrap();
|
||||
assert_eq!(socket.local_addr().unwrap().ip(), bind_addr);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -10,7 +10,7 @@ use std::{
|
||||
fn print_help() {
|
||||
println!(
|
||||
"Usage:
|
||||
rustdesk-util [command]\n
|
||||
rustdesk-utils [command]\n
|
||||
Available Commands:
|
||||
genkeypair Generate a new keypair
|
||||
validatekeypair [public key] [secret key] Validate an existing keypair
|
||||
|
||||
Reference in new issue
Block a user