ipv6 support for direct connection, todo: UI input check, relay port

change based on ipv6
This commit is contained in:
rustdesk 2023-01-04 18:35:31 +08:00
parent fec4e3a049
commit 55962f2fc9
5 changed files with 99 additions and 11 deletions

View File

@ -285,7 +285,7 @@ mod tests {
let addr = "[2001:db8::1]:8080".parse::<SocketAddr>().unwrap();
assert_eq!(addr, AddrMangle::decode(&AddrMangle::encode(addr)));
let addr = "[2001:db8:ff::1111]:80".parse::<SocketAddr>().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)
}
}
#[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);
}
}

View File

@ -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<T: ToSocketAddrs>(addr: T, reuse: bool) -> ResultType<
}
}
pub async fn listen_any(port: u16) -> ResultType<TcpListener> {
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 {

View File

@ -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<RwLock<LoginConfigHandler>>;
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

View File

@ -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<T: std::string::ToString>(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<FileEntry>) -> 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");
}
}

View File

@ -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 {