From 8ea5d80f01bf191d215f141fbfa4f67eee5d1dee Mon Sep 17 00:00:00 2001 From: rustdesk Date: Wed, 12 Jan 2022 03:10:15 +0800 Subject: [PATCH] new lan discovery https://github.com/rustdesk/rustdesk/issues/261 --- libs/hbb_common/src/config.rs | 34 ++++++++++++++ libs/hbb_common/src/udp.rs | 25 +++++++--- src/lang/cn.rs | 1 + src/lang/fr.rs | 1 + src/lang/it.rs | 1 + src/rendezvous_mediator.rs | 88 ++++++++++++++++++++++++++++------- src/ui.rs | 12 +++++ src/ui/ab.tis | 40 +++++++++++++++- 8 files changed, 177 insertions(+), 25 deletions(-) diff --git a/libs/hbb_common/src/config.rs b/libs/hbb_common/src/config.rs index d248f0c28..21ca74a06 100644 --- a/libs/hbb_common/src/config.rs +++ b/libs/hbb_common/src/config.rs @@ -754,6 +754,40 @@ impl Fav { } } +#[derive(Debug, Default, Serialize, Deserialize, Clone)] +pub struct LanPeers { + #[serde(default)] + pub peers: String, +} + +impl LanPeers { + pub fn load() -> LanPeers { + let _ = CONFIG.read().unwrap(); // for lock + match confy::load_path(&Config::file_("_lan_peers")) { + Ok(peers) => peers, + Err(err) => { + log::error!("Failed to load lan peers: {}", err); + Default::default() + } + } + } + + pub fn store(peers: String) { + let f = LanPeers { peers }; + if let Err(err) = confy::store_path(Config::file_("_lan_peers"), f) { + log::error!("Failed to store lan peers: {}", err); + } + } + + pub fn modify_time() -> crate::ResultType { + let p = Config::file_("_lan_peers"); + Ok(fs::metadata(p)? + .modified()? + .duration_since(SystemTime::UNIX_EPOCH)? + .as_millis() as _) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/libs/hbb_common/src/udp.rs b/libs/hbb_common/src/udp.rs index 876180475..f0c731b15 100644 --- a/libs/hbb_common/src/udp.rs +++ b/libs/hbb_common/src/udp.rs @@ -4,7 +4,7 @@ use bytes::{Bytes, BytesMut}; use futures::{SinkExt, StreamExt}; use protobuf::Message; use socket2::{Domain, Protocol, Socket, Type}; -use std::net::{SocketAddr, SocketAddrV4}; +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use tokio::net::{ToSocketAddrs, UdpSocket}; use tokio_socks::{udp::Socks5UdpFramed, IntoTargetAddr, TargetAddr, ToProxyAddrs}; use tokio_util::{codec::BytesCodec, udp::UdpFramed}; @@ -145,13 +145,26 @@ impl FramedSocket { // const DEFAULT_MULTICAST: &str = "239.255.42.98"; -pub fn bind_multicast(addr: &SocketAddrV4, multi_addr: &SocketAddrV4) -> ResultType { - assert!(multi_addr.ip().is_multicast(), "Must be multcast address"); +pub fn bind_multicast(maddr: Option) -> ResultType { + // todo: https://github.com/bltavares/multicast-socket + // 0.0.0.0 bind to default interface, if there are two interfaces, there will be problem. let socket = Socket::new(Domain::ipv4(), Type::dgram(), Some(Protocol::udp()))?; socket.set_reuse_address(true)?; - socket.bind(&socket2::SockAddr::from(*addr))?; - socket.set_multicast_loop_v4(true)?; - socket.join_multicast_v4(multi_addr.ip(), addr.ip())?; + // somehow without this, timer.tick() under tokio::select! does not work + socket.set_read_timeout(Some(std::time::Duration::from_millis(100)))?; + if let Some(maddr) = maddr { + assert!(maddr.ip().is_multicast(), "Must be multcast address"); + let addr = SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0).into(), maddr.port()); + socket.join_multicast_v4(maddr.ip(), addr.ip())?; + socket.set_multicast_loop_v4(true)?; + socket.bind(&socket2::SockAddr::from(addr))?; + } else { + socket.set_multicast_if_v4(&Ipv4Addr::new(0, 0, 0, 0))?; + socket.bind(&socket2::SockAddr::from(SocketAddr::new( + Ipv4Addr::new(0, 0, 0, 0).into(), + 0, + )))?; + } Ok(FramedSocket::Direct(UdpFramed::new( UdpSocket::from_std(socket.into_udp_socket())?, BytesCodec::new(), diff --git a/src/lang/cn.rs b/src/lang/cn.rs index 60fb0d22f..23dd4348c 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -194,5 +194,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Invalid folder name", "无效文件夹名称"), ("Socks5 Proxy", "Socks5 代理"), ("Hostname", "主机名"), + ("Discovered", "已发现"), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index f114ab252..480d85469 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -188,5 +188,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Edit Tag", "Modifier la balise"), ("Invalid folder name", "Nom de dossier invalide"), ("Hostname", "nom d'hôte"), + ("Discovered", "Découvert"), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index cc8a617a5..13b972f80 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -188,6 +188,7 @@ lazy_static::lazy_static! { ("Edit Tag", "Modifica tag"), ("Invalid folder name", "Nome della cartella non valido"), ("Hostname", "Nome host"), + ("Discovered", "Scoperto"), ].iter().cloned().collect(); } \ No newline at end of file diff --git a/src/rendezvous_mediator.rs b/src/rendezvous_mediator.rs index 64638c15a..c12270e81 100644 --- a/src/rendezvous_mediator.rs +++ b/src/rendezvous_mediator.rs @@ -2,7 +2,7 @@ use crate::server::{check_zombie, new as new_server, ServerPtr}; use hbb_common::{ allow_err, anyhow::bail, - config::{Config, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT}, + config::{self, Config, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT}, futures::future::join_all, log, protobuf::Message as _, @@ -21,7 +21,7 @@ use std::{ atomic::{AtomicBool, Ordering}, Arc, Mutex, }, - time::SystemTime, + time::{Instant, SystemTime}, }; use uuid::Uuid; @@ -505,33 +505,33 @@ async fn direct_server(server: ServerPtr) -> ResultType<()> { } } -pub fn create_multicast_socket() -> ResultType<(FramedSocket, SocketAddr)> { +pub fn get_multicast_addr() -> SocketAddrV4 { let port = (RENDEZVOUS_PORT + 3) as u16; - let maddr = SocketAddrV4::new([239, 255, 42, 98].into(), port); - Ok(( - udp::bind_multicast(&SocketAddrV4::new([0, 0, 0, 0].into(), port), &maddr)?, - SocketAddr::V4(maddr), - )) + SocketAddrV4::new([239, 255, 42, 98].into(), port) +} + +pub fn get_mac() -> String { + if let Ok(Some(mac)) = mac_address::get_mac_address() { + mac.to_string() + } else { + "".to_owned() + } } async fn lan_discovery() -> ResultType<()> { - let (mut socket, maddr) = create_multicast_socket()?; + let mut socket = udp::bind_multicast(Some(get_multicast_addr()))?; + log::info!("lan discovery listener started"); loop { select! { - Some(Ok((bytes, _))) = socket.next() => { + Some(Ok((bytes, addr))) = socket.next() => { if let Ok(msg_in) = Message::parse_from_bytes(&bytes) { match msg_in.union { Some(rendezvous_message::Union::peer_discovery(p)) => { if p.cmd == "ping" { let mut msg_out = Message::new(); - let mac = if let Ok(Some(mac)) = mac_address::get_mac_address() { - mac.to_string() - } else { - "".to_owned() - }; let peer = PeerDiscovery { cmd: "pong".to_owned(), - mac, + mac: get_mac(), id: Config::get_id(), hostname: whoami::hostname(), username: crate::platform::get_active_username(), @@ -539,7 +539,7 @@ async fn lan_discovery() -> ResultType<()> { ..Default::default() }; msg_out.set_peer_discovery(peer); - socket.send(&msg_out, maddr).await?; + socket.send(&msg_out, addr).await?; } } _ => {} @@ -549,3 +549,57 @@ async fn lan_discovery() -> ResultType<()> { } } } + +#[tokio::main(flavor = "current_thread")] +pub async fn discover() -> ResultType<()> { + let mut socket = udp::bind_multicast(None)?; + let mut msg_out = Message::new(); + let peer = PeerDiscovery { + cmd: "ping".to_owned(), + ..Default::default() + }; + msg_out.set_peer_discovery(peer); + let maddr = SocketAddr::V4(get_multicast_addr()); + socket.send(&msg_out, maddr).await?; + log::debug!("discover ping sent"); + const TIMER_OUT: Duration = Duration::from_millis(100); + let mut timer = interval(TIMER_OUT); + let mut last_recv_time = Instant::now(); + let mut last_write_time = Instant::now(); + let mut last_write_n = 0; + // to-do: load saved peers, and update incrementally (then we can see offline) + let mut peers = Vec::new(); + let mac = get_mac(); + loop { + select! { + Some(Ok((bytes, _))) = socket.next() => { + if let Ok(msg_in) = Message::parse_from_bytes(&bytes) { + match msg_in.union { + Some(rendezvous_message::Union::peer_discovery(p)) => { + last_recv_time = Instant::now(); + if p.cmd == "pong" { + if p.mac != mac { + peers.push((p.id, p.username, p.hostname, p.platform)); + } + } + } + _ => {} + } + } + }, + _ = timer.tick() => { + if last_write_time.elapsed().as_millis() > 300 && last_write_n != peers.len() { + config::LanPeers::store(serde_json::to_string(&peers)?); + last_write_time = Instant::now(); + last_write_n = peers.len(); + } + if last_recv_time.elapsed().as_millis() > 3_000 { + break; + } + } + } + } + log::debug!("discover ping done"); + config::LanPeers::store(serde_json::to_string(&peers)?); + Ok(()) +} diff --git a/src/ui.rs b/src/ui.rs index dcf97b298..7255c47b7 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -609,6 +609,16 @@ impl UI { crate::platform::windows::create_shortcut(&_id).ok(); } + fn discover(&self) { + std::thread::spawn(move || { + allow_err!(crate::rendezvous_mediator::discover()); + }); + } + + fn get_lan_peers(&self) -> String { + config::LanPeers::load().peers + } + fn open_url(&self, url: String) { #[cfg(windows)] let p = "explorer"; @@ -683,6 +693,8 @@ impl sciter::EventHandler for UI { fn get_software_ext(); fn open_url(String); fn create_shortcut(String); + fn discover(); + fn get_lan_peers(); } } diff --git a/src/ui/ab.tis b/src/ui/ab.tis index 84cfcef4f..a740b6cbe 100644 --- a/src/ui/ab.tis +++ b/src/ui/ab.tis @@ -95,7 +95,7 @@ class SessionList: Reactor.Component { function render() { var sessions = this.getSessions(); if (sessions.length == 0) { - return
{translate("Empty")}
; + return
{translate("Empty")}
; } var me = this; sessions = sessions.map(function(x) { return me.getSession(x); }); @@ -108,7 +108,7 @@ class SessionList: Reactor.Component {
  • RDP
  • {translate('Rename')}
  • - {this.type != "fav" &&
  • {translate('Remove')}
  • } + {this.type != "fav" && this.type != "lan" &&
  • {translate('Remove')}
  • } {is_win &&
  • {translate('Create Desktop Shortcut')}
  • }
  • {translate('Unremember Password')}
  • {(!this.type || this.type == "fav") &&
  • {translate('Add to Favorites')}
  • } @@ -253,12 +253,14 @@ class MultipleSessions: Reactor.Component {
    {translate('Recent Sessions')} {translate('Favorites')} + {translate('Discovered')}
    {!this.hidden && } {!this.hidden && }
    {!this.hidden && ((type == "fav" && ) || + (type == "lan" && ) || )} ; } @@ -275,6 +277,9 @@ class MultipleSessions: Reactor.Component { } event click $(div#sessions-type span.inactive) (_, el) { + if (el.id == "lan") { + discover(); + } handler.set_option('show-sessions-type', el.id || ""); this.stupidUpdate(); } @@ -287,4 +292,35 @@ class MultipleSessions: Reactor.Component { } } +function discover() { + handler.discover(); + var tries = 15; + function update() { + self.timer(300ms, function() { + tries -= 1; + if (tries == 0) return; + update(); + var p = (app || {}).multipleSessions; + if (p) { + p.update(); + } + }); + } + update(); +} + +if (getSessionsType() == "lan") { + discover(); +} + +class LanPeers: Reactor.Component { + function render() { + var sessions = []; + try { + sessions = JSON.parse(handler.get_lan_peers()); + } catch (_) {} + return ; + } +} + view.on("size", function() { if (app && app.multipleSessions) app.multipleSessions.onSize(); });