mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-11-24 12:29:04 +08:00
new lan discovery https://github.com/rustdesk/rustdesk/issues/261
This commit is contained in:
parent
4071f803f7
commit
8ea5d80f01
@ -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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -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<FramedSocket> {
|
||||
assert!(multi_addr.ip().is_multicast(), "Must be multcast address");
|
||||
pub fn bind_multicast(maddr: Option<SocketAddrV4>) -> ResultType<FramedSocket> {
|
||||
// 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(),
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
12
src/ui.rs
12
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ class SessionList: Reactor.Component {
|
||||
function render() {
|
||||
var sessions = this.getSessions();
|
||||
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;
|
||||
sessions = sessions.map(function(x) { return me.getSession(x); });
|
||||
@ -108,7 +108,7 @@ class SessionList: Reactor.Component {
|
||||
<li #rdp>RDP<EditRdpPort /></li>
|
||||
<div .separator />
|
||||
<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>}
|
||||
<li #forget-password>{translate('Unremember Password')}</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>
|
||||
<span class={!type ? 'active' : 'inactive'}>{translate('Recent Sessions')}</span>
|
||||
<span #fav class={type == "fav" ? 'active' : 'inactive'}>{translate('Favorites')}</span>
|
||||
<span #lan class={type == "lan" ? 'active' : 'inactive'}>{translate('Discovered')}</span>
|
||||
</div>
|
||||
{!this.hidden && <SearchBar type={type} />}
|
||||
{!this.hidden && <SessionStyle type={type} />}
|
||||
</div>
|
||||
{!this.hidden &&
|
||||
((type == "fav" && <Favorites />) ||
|
||||
(type == "lan" && <LanPeers />) ||
|
||||
<SessionList sessions={handler.get_recent_sessions()} />)}
|
||||
</div>;
|
||||
}
|
||||
@ -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 <SessionList sessions={sessions} type="lan" />;
|
||||
}
|
||||
}
|
||||
|
||||
view.on("size", function() { if (app && app.multipleSessions) app.multipleSessions.onSize(); });
|
||||
|
Loading…
Reference in New Issue
Block a user