mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-04-02 07:51:28 +03:00
udp punch and ipv6 punch
This commit is contained in:
360
src/common.rs
360
src/common.rs
@@ -1,10 +1,12 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
future::Future,
|
||||
net::{SocketAddr, ToSocketAddrs},
|
||||
sync::{Arc, Mutex, RwLock},
|
||||
task::Poll,
|
||||
};
|
||||
|
||||
use default_net::ip;
|
||||
use serde_json::{json, Map, Value};
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@@ -23,10 +25,10 @@ use hbb_common::{
|
||||
rendezvous_proto::*,
|
||||
socket_client,
|
||||
sodiumoxide::crypto::{box_, secretbox, sign},
|
||||
tcp::FramedStream,
|
||||
timeout,
|
||||
tokio::{
|
||||
self,
|
||||
net::UdpSocket,
|
||||
time::{Duration, Instant, Interval},
|
||||
},
|
||||
ResultType, Stream,
|
||||
@@ -78,6 +80,7 @@ lazy_static::lazy_static! {
|
||||
pub static ref SOFTWARE_UPDATE_URL: Arc<Mutex<String>> = Default::default();
|
||||
pub static ref DEVICE_ID: Arc<Mutex<String>> = Default::default();
|
||||
pub static ref DEVICE_NAME: Arc<Mutex<String>> = Default::default();
|
||||
static ref PUBLIC_IPV6_ADDR: Arc<Mutex<(Option<SocketAddr>, Option<Instant>)>> = Default::default();
|
||||
}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@@ -526,6 +529,7 @@ impl Drop for CheckTestNatType {
|
||||
}
|
||||
|
||||
pub fn test_nat_type() {
|
||||
test_ipv6_sync();
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
std::thread::spawn(move || {
|
||||
static IS_RUNNING: AtomicBool = AtomicBool::new(false);
|
||||
@@ -1674,6 +1678,318 @@ pub fn get_hwid() -> Bytes {
|
||||
Bytes::from(hasher.finalize().to_vec())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_builtin_option(key: &str) -> String {
|
||||
config::BUILTIN_SETTINGS
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(key)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_custom_client() -> bool {
|
||||
get_app_name() != "RustDesk"
|
||||
}
|
||||
|
||||
pub fn verify_login(raw: &str, id: &str) -> bool {
|
||||
true
|
||||
/*
|
||||
if is_custom_client() {
|
||||
return true;
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
return true;
|
||||
let Ok(pk) = crate::decode64("IycjQd4TmWvjjLnYd796Rd+XkK+KG+7GU1Ia7u4+vSw=") else {
|
||||
return false;
|
||||
};
|
||||
let Some(key) = get_pk(&pk).map(|x| sign::PublicKey(x)) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(v) = crate::decode64(raw) else {
|
||||
return false;
|
||||
};
|
||||
let raw = sign::verify(&v, &key).unwrap_or_default();
|
||||
let v_str = std::str::from_utf8(&raw)
|
||||
.unwrap_or_default()
|
||||
.split(":")
|
||||
.next()
|
||||
.unwrap_or_default();
|
||||
v_str == id
|
||||
*/
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_udp_disabled() -> bool {
|
||||
get_builtin_option(config::keys::OPTION_DISABLE_UDP) == "Y"
|
||||
}
|
||||
|
||||
// this crate https://github.com/yoshd/stun-client supports nat type
|
||||
async fn stun_ipv6_test(stun_server: &str) -> ResultType<(SocketAddr, String)> {
|
||||
use std::net::ToSocketAddrs;
|
||||
use stunclient::StunClient;
|
||||
let local_addr = SocketAddr::from(([0u16; 8], 0)); // [::]:0
|
||||
let socket = UdpSocket::bind(&local_addr).await?;
|
||||
let Some(stun_addr) = stun_server
|
||||
.to_socket_addrs()?
|
||||
.filter(|x| x.is_ipv6())
|
||||
.next()
|
||||
else {
|
||||
bail!(
|
||||
"Failed to resolve STUN ipv6 server address: {}",
|
||||
stun_server
|
||||
);
|
||||
};
|
||||
let client = StunClient::new(stun_addr);
|
||||
let addr = client.query_external_address_async(&socket).await?;
|
||||
Ok(if addr.ip().is_ipv6() {
|
||||
(addr, stun_server.to_owned())
|
||||
} else {
|
||||
bail!("STUN server returned non-IPv6 address: {}", addr)
|
||||
})
|
||||
}
|
||||
|
||||
async fn stun_ipv4_test(stun_server: &str) -> ResultType<(SocketAddr, String)> {
|
||||
use std::net::ToSocketAddrs;
|
||||
use stunclient::StunClient;
|
||||
let local_addr = SocketAddr::from(([0u8; 4], 0));
|
||||
let socket = UdpSocket::bind(&local_addr).await?;
|
||||
let Some(stun_addr) = stun_server
|
||||
.to_socket_addrs()?
|
||||
.filter(|x| x.is_ipv4())
|
||||
.next()
|
||||
else {
|
||||
bail!(
|
||||
"Failed to resolve STUN ipv4 server address: {}",
|
||||
stun_server
|
||||
);
|
||||
};
|
||||
let client = StunClient::new(stun_addr);
|
||||
let addr = client.query_external_address_async(&socket).await?;
|
||||
Ok(if addr.ip().is_ipv4() {
|
||||
(addr, stun_server.to_owned())
|
||||
} else {
|
||||
bail!("STUN server returned non-IPv6 address: {}", addr)
|
||||
})
|
||||
}
|
||||
|
||||
static STUNS_V4: [&str; 3] = [
|
||||
"stun.l.google.com:19302",
|
||||
"stun.cloudflare.com:3478",
|
||||
"stun.chat.bilibili.com:3478",
|
||||
];
|
||||
|
||||
static STUNS_V6: [&str; 3] = [
|
||||
"stun.l.google.com:19302",
|
||||
"stun.cloudflare.com:3478",
|
||||
"stun.nextcloud.com:3478",
|
||||
];
|
||||
|
||||
pub async fn test_nat_ipv4() -> ResultType<(SocketAddr, String)> {
|
||||
use hbb_common::futures::future::{select_ok, FutureExt};
|
||||
let tests = STUNS_V4
|
||||
.iter()
|
||||
.map(|&stun| stun_ipv4_test(stun).boxed())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
match select_ok(tests).await {
|
||||
Ok(res) => {
|
||||
return Ok(res.0);
|
||||
}
|
||||
Err(e) => {
|
||||
bail!(
|
||||
"Failed to get public IPv4 address via public STUN servers: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async fn test_bind_ipv6() -> ResultType<SocketAddr> {
|
||||
let local_addr = SocketAddr::from(([0u16; 8], 0)); // [::]:0
|
||||
let socket = UdpSocket::bind(local_addr).await?;
|
||||
let addr = STUNS_V6[0]
|
||||
.to_socket_addrs()?
|
||||
.filter(|x| x.is_ipv6())
|
||||
.next()
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"Failed to resolve STUN ipv6 server address: {}",
|
||||
STUNS_V6[0]
|
||||
)
|
||||
})?;
|
||||
socket.connect(addr).await?;
|
||||
Ok(socket.local_addr()?)
|
||||
}
|
||||
|
||||
pub async fn test_ipv6() -> Option<tokio::task::JoinHandle<()>> {
|
||||
if PUBLIC_IPV6_ADDR
|
||||
.lock()
|
||||
.unwrap()
|
||||
.1
|
||||
.map(|x| x.elapsed().as_secs() < 60)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
PUBLIC_IPV6_ADDR.lock().unwrap().1 = Some(Instant::now());
|
||||
|
||||
match test_bind_ipv6().await {
|
||||
Ok(mut addr) => {
|
||||
if let std::net::IpAddr::V6(ip) = addr.ip() {
|
||||
if !ip.is_loopback()
|
||||
&& !ip.is_unspecified()
|
||||
&& !ip.is_multicast()
|
||||
&& (ip.segments()[0] & 0xe000) == 0x2000
|
||||
{
|
||||
addr.set_port(0);
|
||||
PUBLIC_IPV6_ADDR.lock().unwrap().0 = Some(addr);
|
||||
log::debug!("Found public IPv6 address locally: {}", addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to bind IPv6 socket: {}", e);
|
||||
}
|
||||
}
|
||||
// Interestingly, on my macOS, sometimes my ipv6 works, sometimes not (test with ping6 or https://test-ipv6.com/).
|
||||
// I checked ifconfig, could not see any difference. Both secure ipv6 and temporary ipv6 are there.
|
||||
// So we can not rely on the local ipv6 address queries with if_addrs.
|
||||
// above test_bind_ipv6 is safer, because it can fail in this case.
|
||||
/*
|
||||
std::thread::spawn(|| {
|
||||
if let Ok(ifaces) = if_addrs::get_if_addrs() {
|
||||
for iface in ifaces {
|
||||
if let if_addrs::IfAddr::V6(v6) = iface.addr {
|
||||
let ip = v6.ip;
|
||||
if !ip.is_loopback()
|
||||
&& !ip.is_unspecified()
|
||||
&& !ip.is_multicast()
|
||||
&& !ip.is_unique_local()
|
||||
&& !ip.is_unicast_link_local()
|
||||
&& (ip.segments()[0] & 0xe000) == 0x2000
|
||||
{
|
||||
// only use the first one, on mac, the first one is the stable
|
||||
// one, the last one is the temporary one. The middle ones are deperecated.
|
||||
*PUBLIC_IPV6_ADDR.lock().unwrap() =
|
||||
Some((SocketAddr::from((ip, 0)), Instant::now()));
|
||||
log::debug!("Found public IPv6 address locally: {}", ip);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
Some(tokio::spawn(async {
|
||||
use hbb_common::futures::future::{select_ok, FutureExt};
|
||||
let tests = STUNS_V6
|
||||
.iter()
|
||||
.map(|&stun| stun_ipv6_test(stun).boxed())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
match select_ok(tests).await {
|
||||
Ok(res) => {
|
||||
let mut addr = res.0 .0;
|
||||
addr.set_port(0); // Set port to 0 to avoid conflicts
|
||||
PUBLIC_IPV6_ADDR.lock().unwrap().0 = Some(addr);
|
||||
log::debug!(
|
||||
"Found public IPv6 address via STUN server {}: {}",
|
||||
res.0 .1,
|
||||
addr
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to get public IPv6 address: {}", e);
|
||||
}
|
||||
};
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn punch_udp(
|
||||
socket: Arc<UdpSocket>,
|
||||
listen: bool,
|
||||
) -> ResultType<Option<bytes::BytesMut>> {
|
||||
let mut retry_interval = Duration::from_millis(20);
|
||||
const MAX_INTERVAL: Duration = Duration::from_millis(200);
|
||||
const MAX_TIME: Duration = Duration::from_secs(20);
|
||||
let mut packets_sent = 0;
|
||||
socket.send(&[]).await.ok();
|
||||
packets_sent += 1;
|
||||
let mut last_send_time = Instant::now();
|
||||
let tm = Instant::now();
|
||||
let mut data = [0u8; 1024];
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = hbb_common::sleep(retry_interval.as_secs_f32()) => {
|
||||
if tm.elapsed() > MAX_TIME {
|
||||
bail!("UDP punch is timed out, stop sending packets after {:?} packets", packets_sent);
|
||||
}
|
||||
let elapsed = last_send_time.elapsed();
|
||||
|
||||
if elapsed >= retry_interval {
|
||||
socket.send(&[]).await.ok();
|
||||
packets_sent += 1;
|
||||
|
||||
// Exponentially increase interval to reduce network pressure
|
||||
retry_interval = std::cmp::min(
|
||||
Duration::from_millis((retry_interval.as_millis() as f64 * 1.5) as u64),
|
||||
MAX_INTERVAL
|
||||
);
|
||||
last_send_time = Instant::now();
|
||||
}
|
||||
}
|
||||
res = socket.recv(&mut data) => match res {
|
||||
Err(e) => bail!("UDP punch failed, {packets_sent} packets sent: {e}"),
|
||||
Ok(n) => {
|
||||
// log::debug!("UDP punch succeeded after sending {} packets after {:?}", packets_sent, tm.elapsed());
|
||||
if listen {
|
||||
if n == 0 {
|
||||
continue;
|
||||
}
|
||||
return Ok(Some(bytes::BytesMut::from(&data[..n])));
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_ipv6_sync() {
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn func() {
|
||||
if let Some(job) = test_ipv6().await {
|
||||
job.await.ok();
|
||||
}
|
||||
}
|
||||
std::thread::spawn(func);
|
||||
}
|
||||
|
||||
pub async fn get_ipv6_socket() -> Option<(Arc<UdpSocket>, bytes::Bytes)> {
|
||||
let Some(addr) = PUBLIC_IPV6_ADDR.lock().unwrap().0 else {
|
||||
return None;
|
||||
};
|
||||
|
||||
match UdpSocket::bind(addr).await {
|
||||
Err(err) => {
|
||||
log::warn!("Failed to create UDP socket for IPv6: {err}");
|
||||
}
|
||||
Ok(socket) => {
|
||||
if let Ok(local_addr_v6) = socket.local_addr() {
|
||||
return Some((
|
||||
Arc::new(socket),
|
||||
hbb_common::AddrMangle::encode(local_addr_v6).into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1815,45 +2131,3 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_builtin_option(key: &str) -> String {
|
||||
config::BUILTIN_SETTINGS
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(key)
|
||||
.cloned()
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_custom_client() -> bool {
|
||||
get_app_name() != "RustDesk"
|
||||
}
|
||||
|
||||
pub fn verify_login(raw: &str, id: &str) -> bool {
|
||||
true
|
||||
/*
|
||||
if is_custom_client() {
|
||||
return true;
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
return true;
|
||||
let Ok(pk) = crate::decode64("IycjQd4TmWvjjLnYd796Rd+XkK+KG+7GU1Ia7u4+vSw=") else {
|
||||
return false;
|
||||
};
|
||||
let Some(key) = get_pk(&pk).map(|x| sign::PublicKey(x)) else {
|
||||
return false;
|
||||
};
|
||||
let Ok(v) = crate::decode64(raw) else {
|
||||
return false;
|
||||
};
|
||||
let raw = sign::verify(&v, &key).unwrap_or_default();
|
||||
let v_str = std::str::from_utf8(&raw)
|
||||
.unwrap_or_default()
|
||||
.split(":")
|
||||
.next()
|
||||
.unwrap_or_default();
|
||||
v_str == id
|
||||
*/
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user