From 55962f2fc98a59c11a70ffcc6d1eb6710af9ba94 Mon Sep 17 00:00:00 2001 From: rustdesk Date: Wed, 4 Jan 2023 18:35:31 +0800 Subject: [PATCH] ipv6 support for direct connection, todo: UI input check, relay port change based on ipv6 --- libs/hbb_common/src/lib.rs | 37 +++++++++++++++++++++++++++++++++++-- libs/hbb_common/src/tcp.rs | 34 +++++++++++++++++++++++++++++++++- src/client.rs | 10 +++++++--- src/common.rs | 22 +++++++++++++++++++++- src/rendezvous_mediator.rs | 7 +++---- 5 files changed, 99 insertions(+), 11 deletions(-) diff --git a/libs/hbb_common/src/lib.rs b/libs/hbb_common/src/lib.rs index d1893564c..85e0100d9 100644 --- a/libs/hbb_common/src/lib.rs +++ b/libs/hbb_common/src/lib.rs @@ -285,7 +285,7 @@ mod tests { let addr = "[2001:db8::1]:8080".parse::().unwrap(); assert_eq!(addr, AddrMangle::decode(&AddrMangle::encode(addr))); - + let addr = "[2001:db8:ff::1111]:80".parse::().unwrap(); assert_eq!(addr, AddrMangle::decode(&AddrMangle::encode(addr))); } @@ -306,4 +306,37 @@ pub fn is_ipv4_str(id: &str) -> bool { regex::Regex::new(r"^\d+\.\d+\.\d+\.\d+(:\d+)?$") .unwrap() .is_match(id) -} \ No newline at end of file +} + +#[inline] +pub fn is_ipv6_str(id: &str) -> bool { + regex::Regex::new(r"^((([a-fA-F0-9]{1,4}:{1,2})+[a-fA-F0-9]{1,4})|(\[([a-fA-F0-9]{1,4}:{1,2})+[a-fA-F0-9]{1,4}\]:\d+))$") + .unwrap() + .is_match(id) +} + +#[inline] +pub fn is_ip_str(id: &str) -> bool { + is_ipv4_str(id) || is_ipv6_str(id) +} + +#[cfg(test)] +mod test_lib { + use super::*; + + #[test] + fn test_ipv6() { + assert_eq!(is_ipv6_str("1:2:3"), true); + assert_eq!(is_ipv6_str("[ab:2:3]:12"), true); + assert_eq!(is_ipv6_str("[ABEF:2a:3]:12"), true); + assert_eq!(is_ipv6_str("[ABEG:2a:3]:12"), false); + assert_eq!(is_ipv6_str("1[ab:2:3]:12"), false); + assert_eq!(is_ipv6_str("1.1.1.1"), false); + assert_eq!(is_ip_str("1.1.1.1"), true); + assert_eq!(is_ipv6_str("1:2:"), false); + assert_eq!(is_ipv6_str("1:2::0"), true); + assert_eq!(is_ipv6_str("[1:2::0]:1"), true); + assert_eq!(is_ipv6_str("[1:2::0]:"), false); + assert_eq!(is_ipv6_str("1:2::0]:1"), false); + } +} diff --git a/libs/hbb_common/src/tcp.rs b/libs/hbb_common/src/tcp.rs index f46d836da..a1322fc15 100644 --- a/libs/hbb_common/src/tcp.rs +++ b/libs/hbb_common/src/tcp.rs @@ -5,7 +5,7 @@ use protobuf::Message; use sodiumoxide::crypto::secretbox::{self, Key, Nonce}; use std::{ io::{self, Error, ErrorKind}, - net::SocketAddr, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, ops::{Deref, DerefMut}, pin::Pin, task::{Context, Poll}, @@ -258,6 +258,38 @@ pub async fn new_listener(addr: T, reuse: bool) -> ResultType< } } +pub async fn listen_any(port: u16) -> ResultType { + if let Ok(mut socket) = TcpSocket::new_v6() { + #[cfg(unix)] + { + use std::os::unix::io::{FromRawFd, IntoRawFd}; + let raw_fd = socket.into_raw_fd(); + let sock2 = unsafe { socket2::Socket::from_raw_fd(raw_fd) }; + sock2.set_only_v6(false).ok(); + socket = unsafe { TcpSocket::from_raw_fd(sock2.into_raw_fd()) }; + } + #[cfg(windows)] + { + use std::os::windows::prelude::{FromRawSocket, IntoRawSocket}; + let raw_socket = socket.into_raw_socket(); + let sock2 = unsafe { socket2::Socket::from_raw_socket(raw_socket) }; + sock2.set_only_v6(false).ok(); + socket = unsafe { TcpSocket::from_raw_socket(sock2.into_raw_socket()) }; + } + if socket + .bind(SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), port)) + .is_ok() + { + if let Ok(l) = socket.listen(DEFAULT_BACKLOG) { + return Ok(l); + } + } + } + let s = TcpSocket::new_v4()?; + s.bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port))?; + Ok(s.listen(DEFAULT_BACKLOG)?) +} + impl Unpin for DynTcpStream {} impl AsyncRead for DynTcpStream { diff --git a/src/client.rs b/src/client.rs index fe9d9dac0..635c8b661 100644 --- a/src/client.rs +++ b/src/client.rs @@ -167,7 +167,7 @@ impl Client { interface: impl Interface, ) -> ResultType<(Stream, bool)> { // to-do: remember the port for each peer, so that we can retry easier - if hbb_common::is_ipv4_str(peer) { + if hbb_common::is_ip_str(peer) { return Ok(( socket_client::connect_tcp( crate::check_port(peer, RELAY_PORT + 1), @@ -376,7 +376,8 @@ impl Client { log::info!("peer address: {}, timeout: {}", peer, connect_timeout); let start = std::time::Instant::now(); // NOTICE: Socks5 is be used event in intranet. Which may be not a good way. - let mut conn = socket_client::connect_tcp_local(peer, Some(local_addr), connect_timeout).await; + let mut conn = + socket_client::connect_tcp_local(peer, Some(local_addr), connect_timeout).await; let mut direct = !conn.is_err(); if interface.is_force_relay() || conn.is_err() { if !relay_server.is_empty() { @@ -1847,7 +1848,10 @@ pub trait Interface: Send + Clone + 'static + Sized { fn get_login_config_handler(&self) -> Arc>; fn set_force_relay(&self, direct: bool, received: bool) { - self.get_login_config_handler().write().unwrap().set_force_relay(direct, received); + self.get_login_config_handler() + .write() + .unwrap() + .set_force_relay(direct, received); } fn is_force_relay(&self) -> bool { self.get_login_config_handler().read().unwrap().force_relay diff --git a/src/common.rs b/src/common.rs index c28bbc3fc..07da3ea17 100644 --- a/src/common.rs +++ b/src/common.rs @@ -21,7 +21,7 @@ use hbb_common::{ anyhow::bail, compress::compress as compress_func, config::{self, Config, COMPRESS_LEVEL, RENDEZVOUS_TIMEOUT}, - get_version_number, log, + get_version_number, is_ipv6_str, log, message_proto::*, protobuf::Enum, protobuf::Message as _, @@ -477,6 +477,12 @@ pub fn username() -> String { #[inline] pub fn check_port(host: T, port: i32) -> String { let host = host.to_string(); + if is_ipv6_str(&host) { + if host.contains("[") { + return host; + } + return format!("[{}]:{}", host, port); + } if !host.contains(":") { return format!("{}:{}", host, port); } @@ -706,3 +712,17 @@ pub fn make_fd_to_json(id: i32, path: String, entries: &Vec) -> Strin fd_json.insert("entries".into(), json!(entries_out)); serde_json::to_string(&fd_json).unwrap_or("".into()) } + +#[cfg(test)] +mod test_common { + use super::*; + + #[test] + fn test_check_port() { + assert_eq!(check_port("[1:2]:12", 32), "[1:2]:12"); + assert_eq!(check_port("1:2", 32), "[1:2]:32"); + assert_eq!(check_port("1.1.1.1", 32), "1.1.1.1:32"); + assert_eq!(check_port("1.1.1.1:32", 32), "1.1.1.1:32"); + assert_eq!(check_port("test.com:32", 0), "test.com:32"); + } +} diff --git a/src/rendezvous_mediator.rs b/src/rendezvous_mediator.rs index ec70bdf84..2dccb3f1a 100644 --- a/src/rendezvous_mediator.rs +++ b/src/rendezvous_mediator.rs @@ -506,8 +506,7 @@ async fn direct_server(server: ServerPtr) { let disabled = Config::get_option("direct-server").is_empty(); if !disabled && listener.is_none() { port = get_direct_port(); - let addr = format!("0.0.0.0:{}", port); - match hbb_common::tcp::new_listener(&addr, false).await { + match hbb_common::tcp::listen_any(port as _).await { Ok(l) => { listener = Some(l); log::info!( @@ -518,8 +517,8 @@ async fn direct_server(server: ServerPtr) { Err(err) => { // to-do: pass to ui log::error!( - "Failed to start direct server on : {}, error: {}", - addr, + "Failed to start direct server on port: {}, error: {}", + port, err ); loop {