This commit is contained in:
rustdesk 2022-01-12 03:10:15 +08:00
parent 4071f803f7
commit 8ea5d80f01
8 changed files with 177 additions and 25 deletions

View File

@ -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<u64> {
let p = Config::file_("_lan_peers");
Ok(fs::metadata(p)?
.modified()?
.duration_since(SystemTime::UNIX_EPOCH)?
.as_millis() as _)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -4,7 +4,7 @@ use bytes::{Bytes, BytesMut};
use futures::{SinkExt, StreamExt}; use futures::{SinkExt, StreamExt};
use protobuf::Message; use protobuf::Message;
use socket2::{Domain, Protocol, Socket, Type}; use socket2::{Domain, Protocol, Socket, Type};
use std::net::{SocketAddr, SocketAddrV4}; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
use tokio::net::{ToSocketAddrs, UdpSocket}; use tokio::net::{ToSocketAddrs, UdpSocket};
use tokio_socks::{udp::Socks5UdpFramed, IntoTargetAddr, TargetAddr, ToProxyAddrs}; use tokio_socks::{udp::Socks5UdpFramed, IntoTargetAddr, TargetAddr, ToProxyAddrs};
use tokio_util::{codec::BytesCodec, udp::UdpFramed}; use tokio_util::{codec::BytesCodec, udp::UdpFramed};
@ -145,13 +145,26 @@ impl FramedSocket {
// const DEFAULT_MULTICAST: &str = "239.255.42.98"; // const DEFAULT_MULTICAST: &str = "239.255.42.98";
pub fn bind_multicast(addr: &SocketAddrV4, multi_addr: &SocketAddrV4) -> ResultType<FramedSocket> { pub fn bind_multicast(maddr: Option<SocketAddrV4>) -> ResultType<FramedSocket> {
assert!(multi_addr.ip().is_multicast(), "Must be multcast address"); // 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()))?; let socket = Socket::new(Domain::ipv4(), Type::dgram(), Some(Protocol::udp()))?;
socket.set_reuse_address(true)?; socket.set_reuse_address(true)?;
socket.bind(&socket2::SockAddr::from(*addr))?; // somehow without this, timer.tick() under tokio::select! does not work
socket.set_multicast_loop_v4(true)?; socket.set_read_timeout(Some(std::time::Duration::from_millis(100)))?;
socket.join_multicast_v4(multi_addr.ip(), addr.ip())?; 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( Ok(FramedSocket::Direct(UdpFramed::new(
UdpSocket::from_std(socket.into_udp_socket())?, UdpSocket::from_std(socket.into_udp_socket())?,
BytesCodec::new(), BytesCodec::new(),

View File

@ -194,5 +194,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Invalid folder name", "无效文件夹名称"), ("Invalid folder name", "无效文件夹名称"),
("Socks5 Proxy", "Socks5 代理"), ("Socks5 Proxy", "Socks5 代理"),
("Hostname", "主机名"), ("Hostname", "主机名"),
("Discovered", "已发现"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -188,5 +188,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Edit Tag", "Modifier la balise"), ("Edit Tag", "Modifier la balise"),
("Invalid folder name", "Nom de dossier invalide"), ("Invalid folder name", "Nom de dossier invalide"),
("Hostname", "nom d'hôte"), ("Hostname", "nom d'hôte"),
("Discovered", "Découvert"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -188,6 +188,7 @@ lazy_static::lazy_static! {
("Edit Tag", "Modifica tag"), ("Edit Tag", "Modifica tag"),
("Invalid folder name", "Nome della cartella non valido"), ("Invalid folder name", "Nome della cartella non valido"),
("Hostname", "Nome host"), ("Hostname", "Nome host"),
("Discovered", "Scoperto"),
].iter().cloned().collect(); ].iter().cloned().collect();
} }

View File

@ -2,7 +2,7 @@ use crate::server::{check_zombie, new as new_server, ServerPtr};
use hbb_common::{ use hbb_common::{
allow_err, allow_err,
anyhow::bail, anyhow::bail,
config::{Config, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT}, config::{self, Config, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
futures::future::join_all, futures::future::join_all,
log, log,
protobuf::Message as _, protobuf::Message as _,
@ -21,7 +21,7 @@ use std::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
Arc, Mutex, Arc, Mutex,
}, },
time::SystemTime, time::{Instant, SystemTime},
}; };
use uuid::Uuid; 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 port = (RENDEZVOUS_PORT + 3) as u16;
let maddr = SocketAddrV4::new([239, 255, 42, 98].into(), port); SocketAddrV4::new([239, 255, 42, 98].into(), port)
Ok(( }
udp::bind_multicast(&SocketAddrV4::new([0, 0, 0, 0].into(), port), &maddr)?,
SocketAddr::V4(maddr), 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<()> { 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 { loop {
select! { select! {
Some(Ok((bytes, _))) = socket.next() => { Some(Ok((bytes, addr))) = socket.next() => {
if let Ok(msg_in) = Message::parse_from_bytes(&bytes) { if let Ok(msg_in) = Message::parse_from_bytes(&bytes) {
match msg_in.union { match msg_in.union {
Some(rendezvous_message::Union::peer_discovery(p)) => { Some(rendezvous_message::Union::peer_discovery(p)) => {
if p.cmd == "ping" { if p.cmd == "ping" {
let mut msg_out = Message::new(); 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 { let peer = PeerDiscovery {
cmd: "pong".to_owned(), cmd: "pong".to_owned(),
mac, mac: get_mac(),
id: Config::get_id(), id: Config::get_id(),
hostname: whoami::hostname(), hostname: whoami::hostname(),
username: crate::platform::get_active_username(), username: crate::platform::get_active_username(),
@ -539,7 +539,7 @@ async fn lan_discovery() -> ResultType<()> {
..Default::default() ..Default::default()
}; };
msg_out.set_peer_discovery(peer); 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(())
}

View File

@ -609,6 +609,16 @@ impl UI {
crate::platform::windows::create_shortcut(&_id).ok(); 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) { fn open_url(&self, url: String) {
#[cfg(windows)] #[cfg(windows)]
let p = "explorer"; let p = "explorer";
@ -683,6 +693,8 @@ impl sciter::EventHandler for UI {
fn get_software_ext(); fn get_software_ext();
fn open_url(String); fn open_url(String);
fn create_shortcut(String); fn create_shortcut(String);
fn discover();
fn get_lan_peers();
} }
} }

View File

@ -95,7 +95,7 @@ class SessionList: Reactor.Component {
function render() { function render() {
var sessions = this.getSessions(); var sessions = this.getSessions();
if (sessions.length == 0) { if (sessions.length == 0) {
return <div style="margin: *; font-size: 1.6em;">{translate("Empty")}</div>; return <div style="margin: *; font-size: 1.6em; text-align: center;">{translate("Empty")}</div>;
} }
var me = this; var me = this;
sessions = sessions.map(function(x) { return me.getSession(x); }); sessions = sessions.map(function(x) { return me.getSession(x); });
@ -108,7 +108,7 @@ class SessionList: Reactor.Component {
<li #rdp>RDP<EditRdpPort /></li> <li #rdp>RDP<EditRdpPort /></li>
<div .separator /> <div .separator />
<li #rename>{translate('Rename')}</li> <li #rename>{translate('Rename')}</li>
{this.type != "fav" && <li #remove>{translate('Remove')}</li>} {this.type != "fav" && this.type != "lan" && <li #remove>{translate('Remove')}</li>}
{is_win && <li #shortcut>{translate('Create Desktop Shortcut')}</li>} {is_win && <li #shortcut>{translate('Create Desktop Shortcut')}</li>}
<li #forget-password>{translate('Unremember Password')}</li> <li #forget-password>{translate('Unremember Password')}</li>
{(!this.type || this.type == "fav") && <li #add-fav>{translate('Add to Favorites')}</li>} {(!this.type || this.type == "fav") && <li #add-fav>{translate('Add to Favorites')}</li>}
@ -253,12 +253,14 @@ class MultipleSessions: Reactor.Component {
<div style="width:*" .sessions-tab #sessions-type> <div style="width:*" .sessions-tab #sessions-type>
<span class={!type ? 'active' : 'inactive'}>{translate('Recent Sessions')}</span> <span class={!type ? 'active' : 'inactive'}>{translate('Recent Sessions')}</span>
<span #fav class={type == "fav" ? 'active' : 'inactive'}>{translate('Favorites')}</span> <span #fav class={type == "fav" ? 'active' : 'inactive'}>{translate('Favorites')}</span>
<span #lan class={type == "lan" ? 'active' : 'inactive'}>{translate('Discovered')}</span>
</div> </div>
{!this.hidden && <SearchBar type={type} />} {!this.hidden && <SearchBar type={type} />}
{!this.hidden && <SessionStyle type={type} />} {!this.hidden && <SessionStyle type={type} />}
</div> </div>
{!this.hidden && {!this.hidden &&
((type == "fav" && <Favorites />) || ((type == "fav" && <Favorites />) ||
(type == "lan" && <LanPeers />) ||
<SessionList sessions={handler.get_recent_sessions()} />)} <SessionList sessions={handler.get_recent_sessions()} />)}
</div>; </div>;
} }
@ -275,6 +277,9 @@ class MultipleSessions: Reactor.Component {
} }
event click $(div#sessions-type span.inactive) (_, el) { event click $(div#sessions-type span.inactive) (_, el) {
if (el.id == "lan") {
discover();
}
handler.set_option('show-sessions-type', el.id || ""); handler.set_option('show-sessions-type', el.id || "");
this.stupidUpdate(); 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 <SessionList sessions={sessions} type="lan" />;
}
}
view.on("size", function() { if (app && app.multipleSessions) app.multipleSessions.onSize(); }); view.on("size", function() { if (app && app.multipleSessions) app.multipleSessions.onSize(); });