mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-06-14 03:32:00 +08:00
refactor: 删除部分多余的代码和注释
This commit is contained in:
@@ -1,21 +1,11 @@
|
||||
//! RustDesk BytesCodec - Variable-length framing for TCP messages
|
||||
//!
|
||||
//! RustDesk uses a custom variable-length encoding for message framing:
|
||||
//! - Length <= 0x3F (63): 1-byte header, format `(len << 2)`
|
||||
//! - Length <= 0x3FFF (16383): 2-byte LE header, format `(len << 2) | 0x1`
|
||||
//! - Length <= 0x3FFFFF (4194303): 3-byte LE header, format `(len << 2) | 0x2`
|
||||
//! - Length <= 0x3FFFFFFF (1073741823): 4-byte LE header, format `(len << 2) | 0x3`
|
||||
//!
|
||||
//! The low 2 bits of the first byte indicate the header length (+1).
|
||||
//! Variable-length TCP framing (RustDesk wire format).
|
||||
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use std::io;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
|
||||
/// Maximum packet length (1GB)
|
||||
const MAX_PACKET_LENGTH: usize = 0x3FFFFFFF;
|
||||
|
||||
/// Encode a message with RustDesk's variable-length framing
|
||||
pub fn encode_frame(data: &[u8]) -> io::Result<Vec<u8>> {
|
||||
let len = data.len();
|
||||
let mut buf = Vec::with_capacity(len + 4);
|
||||
@@ -44,8 +34,6 @@ pub fn encode_frame(data: &[u8]) -> io::Result<Vec<u8>> {
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Decode the header to get message length
|
||||
/// Returns (header_length, message_length)
|
||||
fn decode_header(first_byte: u8, header_bytes: &[u8]) -> (usize, usize) {
|
||||
let head_len = ((first_byte & 0x3) + 1) as usize;
|
||||
|
||||
@@ -64,21 +52,17 @@ fn decode_header(first_byte: u8, header_bytes: &[u8]) -> (usize, usize) {
|
||||
(head_len, msg_len)
|
||||
}
|
||||
|
||||
/// Read a single framed message from an async reader
|
||||
pub async fn read_frame<R: AsyncRead + Unpin>(reader: &mut R) -> io::Result<BytesMut> {
|
||||
// Read first byte to determine header length
|
||||
let mut first_byte = [0u8; 1];
|
||||
reader.read_exact(&mut first_byte).await?;
|
||||
|
||||
let head_len = ((first_byte[0] & 0x3) + 1) as usize;
|
||||
|
||||
// Read remaining header bytes if needed
|
||||
let mut header_rest = [0u8; 3];
|
||||
if head_len > 1 {
|
||||
reader.read_exact(&mut header_rest[..head_len - 1]).await?;
|
||||
}
|
||||
|
||||
// Calculate message length
|
||||
let (_, msg_len) = decode_header(first_byte[0], &header_rest);
|
||||
|
||||
if msg_len > MAX_PACKET_LENGTH {
|
||||
@@ -88,7 +72,6 @@ pub async fn read_frame<R: AsyncRead + Unpin>(reader: &mut R) -> io::Result<Byte
|
||||
));
|
||||
}
|
||||
|
||||
// Read message body
|
||||
let mut buf = BytesMut::with_capacity(msg_len);
|
||||
buf.resize(msg_len, 0);
|
||||
reader.read_exact(&mut buf).await?;
|
||||
@@ -96,7 +79,6 @@ pub async fn read_frame<R: AsyncRead + Unpin>(reader: &mut R) -> io::Result<Byte
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Write a framed message to an async writer
|
||||
pub async fn write_frame<W: AsyncWrite + Unpin>(writer: &mut W, data: &[u8]) -> io::Result<()> {
|
||||
let frame = encode_frame(data)?;
|
||||
writer.write_all(&frame).await?;
|
||||
@@ -104,10 +86,6 @@ pub async fn write_frame<W: AsyncWrite + Unpin>(writer: &mut W, data: &[u8]) ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write a framed message using a reusable buffer (reduces allocations)
|
||||
///
|
||||
/// This version reuses the provided BytesMut buffer to avoid allocation on each call.
|
||||
/// The buffer is cleared before use and will grow as needed.
|
||||
pub async fn write_frame_buffered<W: AsyncWrite + Unpin>(
|
||||
writer: &mut W,
|
||||
data: &[u8],
|
||||
@@ -120,11 +98,9 @@ pub async fn write_frame_buffered<W: AsyncWrite + Unpin>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Encode a message with RustDesk's variable-length framing into an existing buffer
|
||||
pub fn encode_frame_into(data: &[u8], buf: &mut BytesMut) -> io::Result<()> {
|
||||
let len = data.len();
|
||||
|
||||
// Reserve space for header (max 4 bytes) + data
|
||||
buf.reserve(4 + len);
|
||||
|
||||
if len <= 0x3F {
|
||||
@@ -149,7 +125,7 @@ pub fn encode_frame_into(data: &[u8], buf: &mut BytesMut) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// BytesCodec for stateful decoding (compatible with tokio-util codec)
|
||||
/// Stateful decoder for `Framed`.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BytesCodec {
|
||||
state: DecodeState,
|
||||
@@ -180,7 +156,6 @@ impl BytesCodec {
|
||||
self.max_packet_length = n;
|
||||
}
|
||||
|
||||
/// Decode from a BytesMut buffer (for use with Framed)
|
||||
pub fn decode(&mut self, src: &mut BytesMut) -> io::Result<Option<BytesMut>> {
|
||||
let n = match self.state {
|
||||
DecodeState::Head => match self.decode_head(src)? {
|
||||
@@ -242,7 +217,6 @@ impl BytesCodec {
|
||||
Ok(Some(src.split_to(n)))
|
||||
}
|
||||
|
||||
/// Encode a message into a BytesMut buffer
|
||||
pub fn encode(&mut self, data: Bytes, buf: &mut BytesMut) -> io::Result<()> {
|
||||
let len = data.len();
|
||||
|
||||
@@ -276,7 +250,7 @@ mod tests {
|
||||
fn test_encode_decode_small() {
|
||||
let data = vec![1u8; 63];
|
||||
let encoded = encode_frame(&data).unwrap();
|
||||
assert_eq!(encoded.len(), 63 + 1); // 1 byte header
|
||||
assert_eq!(encoded.len(), 63 + 1);
|
||||
|
||||
let mut codec = BytesCodec::new();
|
||||
let mut buf = BytesMut::from(&encoded[..]);
|
||||
@@ -288,7 +262,7 @@ mod tests {
|
||||
fn test_encode_decode_medium() {
|
||||
let data = vec![2u8; 1000];
|
||||
let encoded = encode_frame(&data).unwrap();
|
||||
assert_eq!(encoded.len(), 1000 + 2); // 2 byte header
|
||||
assert_eq!(encoded.len(), 1000 + 2);
|
||||
|
||||
let mut codec = BytesCodec::new();
|
||||
let mut buf = BytesMut::from(&encoded[..]);
|
||||
@@ -300,7 +274,7 @@ mod tests {
|
||||
fn test_encode_decode_large() {
|
||||
let data = vec![3u8; 100000];
|
||||
let encoded = encode_frame(&data).unwrap();
|
||||
assert_eq!(encoded.len(), 100000 + 3); // 3 byte header
|
||||
assert_eq!(encoded.len(), 100000 + 3);
|
||||
|
||||
let mut codec = BytesCodec::new();
|
||||
let mut buf = BytesMut::from(&encoded[..]);
|
||||
|
||||
@@ -1,57 +1,26 @@
|
||||
//! RustDesk Configuration
|
||||
//!
|
||||
//! Configuration types for the RustDesk protocol integration.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
|
||||
/// RustDesk configuration
|
||||
#[typeshare]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct RustDeskConfig {
|
||||
/// Enable RustDesk protocol
|
||||
pub enabled: bool,
|
||||
|
||||
/// Rendezvous server address (hbbs), e.g., "rs.example.com" or "192.168.1.100:21116"
|
||||
/// Required for RustDesk to function
|
||||
pub rendezvous_server: String,
|
||||
|
||||
/// Relay server address (hbbr), if different from rendezvous server
|
||||
/// Usually the same host as rendezvous server but different port (21117)
|
||||
pub relay_server: Option<String>,
|
||||
|
||||
/// Relay server authentication key (licence_key)
|
||||
/// Required if the relay server is configured with -k option
|
||||
#[typeshare(skip)]
|
||||
pub relay_key: Option<String>,
|
||||
|
||||
/// Device ID (9-digit number), auto-generated if empty
|
||||
pub device_id: String,
|
||||
|
||||
/// Device password for client authentication
|
||||
#[typeshare(skip)]
|
||||
pub device_password: String,
|
||||
|
||||
/// Public key for encryption (Curve25519, base64 encoded), auto-generated
|
||||
#[typeshare(skip)]
|
||||
pub public_key: Option<String>,
|
||||
|
||||
/// Private key for encryption (Curve25519, base64 encoded), auto-generated
|
||||
#[typeshare(skip)]
|
||||
pub private_key: Option<String>,
|
||||
|
||||
/// Signing public key (Ed25519, base64 encoded), auto-generated
|
||||
/// Used for SignedId verification by clients
|
||||
#[typeshare(skip)]
|
||||
pub signing_public_key: Option<String>,
|
||||
|
||||
/// Signing private key (Ed25519, base64 encoded), auto-generated
|
||||
/// Used for signing SignedId messages
|
||||
#[typeshare(skip)]
|
||||
pub signing_private_key: Option<String>,
|
||||
|
||||
/// UUID for rendezvous server registration (persisted to avoid UUID_MISMATCH)
|
||||
#[typeshare(skip)]
|
||||
pub uuid: Option<String>,
|
||||
}
|
||||
@@ -75,8 +44,6 @@ impl Default for RustDeskConfig {
|
||||
}
|
||||
|
||||
impl RustDeskConfig {
|
||||
/// Check if the configuration is valid for starting the service
|
||||
/// Returns true if enabled and has a valid server
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.enabled
|
||||
&& !self.rendezvous_server.is_empty()
|
||||
@@ -84,44 +51,35 @@ impl RustDeskConfig {
|
||||
&& !self.device_password.is_empty()
|
||||
}
|
||||
|
||||
/// Get the rendezvous server (user-configured)
|
||||
pub fn effective_rendezvous_server(&self) -> &str {
|
||||
&self.rendezvous_server
|
||||
}
|
||||
|
||||
/// Generate a new random device ID
|
||||
pub fn generate_device_id() -> String {
|
||||
generate_device_id()
|
||||
}
|
||||
|
||||
/// Generate a new random password
|
||||
pub fn generate_password() -> String {
|
||||
generate_random_password()
|
||||
}
|
||||
|
||||
/// Get or generate the UUID for rendezvous registration
|
||||
/// Returns (uuid_bytes, is_new) where is_new indicates if a new UUID was generated
|
||||
pub fn ensure_uuid(&mut self) -> ([u8; 16], bool) {
|
||||
if let Some(ref uuid_str) = self.uuid {
|
||||
// Try to parse existing UUID
|
||||
if let Ok(uuid) = uuid::Uuid::parse_str(uuid_str) {
|
||||
return (*uuid.as_bytes(), false);
|
||||
}
|
||||
}
|
||||
// Generate new UUID
|
||||
let new_uuid = uuid::Uuid::new_v4();
|
||||
self.uuid = Some(new_uuid.to_string());
|
||||
(*new_uuid.as_bytes(), true)
|
||||
}
|
||||
|
||||
/// Get the UUID bytes (returns None if not set)
|
||||
pub fn get_uuid_bytes(&self) -> Option<[u8; 16]> {
|
||||
self.uuid
|
||||
.as_ref()
|
||||
.and_then(|s| uuid::Uuid::parse_str(s).ok().map(|u| *u.as_bytes()))
|
||||
}
|
||||
|
||||
/// Get the rendezvous server address with default port
|
||||
pub fn rendezvous_addr(&self) -> String {
|
||||
let server = &self.rendezvous_server;
|
||||
if server.is_empty() {
|
||||
@@ -133,7 +91,6 @@ impl RustDeskConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the relay server address with default port
|
||||
pub fn relay_addr(&self) -> Option<String> {
|
||||
self.relay_server
|
||||
.as_ref()
|
||||
@@ -145,7 +102,6 @@ impl RustDeskConfig {
|
||||
}
|
||||
})
|
||||
.or_else(|| {
|
||||
// Default: same host as rendezvous server
|
||||
let server = &self.rendezvous_server;
|
||||
if !server.is_empty() {
|
||||
let host = server.split(':').next().unwrap_or("");
|
||||
@@ -161,7 +117,6 @@ impl RustDeskConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a random 9-digit device ID
|
||||
pub fn generate_device_id() -> String {
|
||||
use rand::Rng;
|
||||
let mut rng = rand::rng();
|
||||
@@ -169,7 +124,6 @@ pub fn generate_device_id() -> String {
|
||||
id.to_string()
|
||||
}
|
||||
|
||||
/// Generate a random 8-character password
|
||||
pub fn generate_random_password() -> String {
|
||||
use rand::Rng;
|
||||
const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
@@ -212,7 +166,6 @@ mod tests {
|
||||
config.rendezvous_server = "example.com:21116".to_string();
|
||||
assert_eq!(config.rendezvous_addr(), "example.com:21116");
|
||||
|
||||
// Empty server returns empty string
|
||||
config.rendezvous_server = String::new();
|
||||
assert_eq!(config.rendezvous_addr(), "");
|
||||
}
|
||||
@@ -224,17 +177,14 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Rendezvous server configured, relay defaults to same host
|
||||
assert_eq!(config.relay_addr(), Some("example.com:21117".to_string()));
|
||||
|
||||
// Explicit relay server
|
||||
config.relay_server = Some("relay.example.com".to_string());
|
||||
assert_eq!(
|
||||
config.relay_addr(),
|
||||
Some("relay.example.com:21117".to_string())
|
||||
);
|
||||
|
||||
// No rendezvous server, relay is None
|
||||
config.rendezvous_server = String::new();
|
||||
config.relay_server = None;
|
||||
assert_eq!(config.relay_addr(), None);
|
||||
@@ -247,10 +197,8 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// When user sets a server, use it
|
||||
assert_eq!(config.effective_rendezvous_server(), "custom.example.com");
|
||||
|
||||
// When empty, returns empty
|
||||
config.rendezvous_server = String::new();
|
||||
assert_eq!(config.effective_rendezvous_server(), "");
|
||||
}
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
//! RustDesk Connection Handler
|
||||
//!
|
||||
//! This module handles incoming connections from RustDesk clients.
|
||||
//! It manages the connection lifecycle including:
|
||||
//! - Connection establishment (P2P or via relay)
|
||||
//! - Encrypted handshake
|
||||
//! - Authentication
|
||||
//! - Message routing (video, audio, input)
|
||||
//! - Video frame streaming (shared with WebRTC)
|
||||
//! Incoming RustDesk TCP sessions (handshake, AV, input).
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
@@ -23,6 +15,7 @@ use tracing::{debug, error, info, warn};
|
||||
|
||||
use crate::audio::AudioController;
|
||||
use crate::hid::{CanonicalKey, HidController, KeyEventType, KeyboardEvent, KeyboardModifiers};
|
||||
use crate::utils::hostname_from_etc;
|
||||
use crate::video::codec_constraints::{
|
||||
encoder_codec_to_id, encoder_codec_to_video_codec, video_codec_to_encoder_codec,
|
||||
};
|
||||
@@ -94,13 +87,6 @@ impl InputThrottler {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get system hostname
|
||||
fn get_hostname() -> String {
|
||||
std::fs::read_to_string("/etc/hostname")
|
||||
.map(|s| s.trim().to_string())
|
||||
.unwrap_or_else(|_| "One-KVM".to_string())
|
||||
}
|
||||
|
||||
/// Connection state
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ConnectionState {
|
||||
@@ -1165,7 +1151,7 @@ impl Connection {
|
||||
|
||||
let mut peer_info = PeerInfo::new();
|
||||
peer_info.username = "one-kvm".to_string();
|
||||
peer_info.hostname = get_hostname();
|
||||
peer_info.hostname = hostname_from_etc();
|
||||
peer_info.platform = RUSTDESK_COMPAT_PLATFORM.to_string();
|
||||
peer_info.displays.push(display_info);
|
||||
peer_info.current_display = 0;
|
||||
@@ -1786,7 +1772,7 @@ async fn run_audio_streaming(
|
||||
}
|
||||
|
||||
// Subscribe to the audio Opus stream
|
||||
let mut opus_rx = match audio_controller.subscribe_opus_async().await {
|
||||
let mut opus_rx = match audio_controller.subscribe_opus().await {
|
||||
Some(rx) => rx,
|
||||
None => {
|
||||
// Audio not available, wait and retry
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
//! RustDesk Cryptography
|
||||
//!
|
||||
//! This module implements the NaCl-based cryptography used by RustDesk:
|
||||
//! - Curve25519 for key exchange
|
||||
//! - XSalsa20-Poly1305 for authenticated encryption
|
||||
//! - Ed25519 for signatures
|
||||
//! - Ed25519 to Curve25519 key conversion for unified keypair usage
|
||||
//! NaCl crypto (RustDesk-compatible).
|
||||
|
||||
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
|
||||
use sodiumoxide::crypto::box_::{self, Nonce, PublicKey, SecretKey};
|
||||
@@ -12,7 +6,6 @@ use sodiumoxide::crypto::secretbox;
|
||||
use sodiumoxide::crypto::sign::{self, ed25519};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Cryptography errors
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CryptoError {
|
||||
#[error("Failed to initialize sodiumoxide")]
|
||||
@@ -31,13 +24,10 @@ pub enum CryptoError {
|
||||
KeyConversionFailed,
|
||||
}
|
||||
|
||||
/// Initialize the cryptography library
|
||||
/// Must be called before using any crypto functions
|
||||
pub fn init() -> Result<(), CryptoError> {
|
||||
sodiumoxide::init().map_err(|_| CryptoError::InitError)
|
||||
}
|
||||
|
||||
/// A keypair for asymmetric encryption
|
||||
#[derive(Clone)]
|
||||
pub struct KeyPair {
|
||||
pub public_key: PublicKey,
|
||||
@@ -45,7 +35,6 @@ pub struct KeyPair {
|
||||
}
|
||||
|
||||
impl KeyPair {
|
||||
/// Generate a new random keypair
|
||||
pub fn generate() -> Self {
|
||||
let (public_key, secret_key) = box_::gen_keypair();
|
||||
Self {
|
||||
@@ -54,7 +43,6 @@ impl KeyPair {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create from existing keys
|
||||
pub fn from_keys(public_key: &[u8], secret_key: &[u8]) -> Result<Self, CryptoError> {
|
||||
let pk = PublicKey::from_slice(public_key).ok_or(CryptoError::InvalidKeyLength)?;
|
||||
let sk = SecretKey::from_slice(secret_key).ok_or(CryptoError::InvalidKeyLength)?;
|
||||
@@ -64,27 +52,22 @@ impl KeyPair {
|
||||
})
|
||||
}
|
||||
|
||||
/// Get public key as bytes
|
||||
pub fn public_key_bytes(&self) -> &[u8] {
|
||||
self.public_key.as_ref()
|
||||
}
|
||||
|
||||
/// Get secret key as bytes
|
||||
pub fn secret_key_bytes(&self) -> &[u8] {
|
||||
self.secret_key.as_ref()
|
||||
}
|
||||
|
||||
/// Encode public key as base64
|
||||
pub fn public_key_base64(&self) -> String {
|
||||
BASE64.encode(self.public_key_bytes())
|
||||
}
|
||||
|
||||
/// Encode secret key as base64
|
||||
pub fn secret_key_base64(&self) -> String {
|
||||
BASE64.encode(self.secret_key_bytes())
|
||||
}
|
||||
|
||||
/// Create from base64-encoded keys
|
||||
pub fn from_base64(public_key: &str, secret_key: &str) -> Result<Self, CryptoError> {
|
||||
let pk_bytes = BASE64
|
||||
.decode(public_key)
|
||||
@@ -96,15 +79,10 @@ impl KeyPair {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a random nonce for box encryption
|
||||
pub fn generate_nonce() -> Nonce {
|
||||
box_::gen_nonce()
|
||||
}
|
||||
|
||||
/// Encrypt data using public-key cryptography (NaCl box)
|
||||
///
|
||||
/// Uses the sender's secret key and receiver's public key for encryption.
|
||||
/// Returns (nonce, ciphertext).
|
||||
pub fn encrypt_box(
|
||||
data: &[u8],
|
||||
their_public_key: &PublicKey,
|
||||
@@ -115,7 +93,6 @@ pub fn encrypt_box(
|
||||
(nonce, ciphertext)
|
||||
}
|
||||
|
||||
/// Decrypt data using public-key cryptography (NaCl box)
|
||||
pub fn decrypt_box(
|
||||
ciphertext: &[u8],
|
||||
nonce: &Nonce,
|
||||
@@ -126,14 +103,12 @@ pub fn decrypt_box(
|
||||
.map_err(|_| CryptoError::DecryptionFailed)
|
||||
}
|
||||
|
||||
/// Encrypt data with a precomputed shared key
|
||||
pub fn encrypt_with_key(data: &[u8], key: &secretbox::Key) -> (secretbox::Nonce, Vec<u8>) {
|
||||
let nonce = secretbox::gen_nonce();
|
||||
let ciphertext = secretbox::seal(data, &nonce, key);
|
||||
(nonce, ciphertext)
|
||||
}
|
||||
|
||||
/// Decrypt data with a precomputed shared key
|
||||
pub fn decrypt_with_key(
|
||||
ciphertext: &[u8],
|
||||
nonce: &secretbox::Nonce,
|
||||
@@ -142,8 +117,6 @@ pub fn decrypt_with_key(
|
||||
secretbox::open(ciphertext, nonce, key).map_err(|_| CryptoError::DecryptionFailed)
|
||||
}
|
||||
|
||||
/// Compute a shared symmetric key from public/private keypair
|
||||
/// This is the precomputed key for the NaCl box
|
||||
pub fn precompute_key(
|
||||
their_public_key: &PublicKey,
|
||||
our_secret_key: &SecretKey,
|
||||
@@ -151,23 +124,18 @@ pub fn precompute_key(
|
||||
box_::precompute(their_public_key, our_secret_key)
|
||||
}
|
||||
|
||||
/// Create a symmetric key from raw bytes
|
||||
pub fn symmetric_key_from_slice(key: &[u8]) -> Result<secretbox::Key, CryptoError> {
|
||||
secretbox::Key::from_slice(key).ok_or(CryptoError::InvalidKeyLength)
|
||||
}
|
||||
|
||||
/// Parse a nonce from bytes
|
||||
pub fn nonce_from_slice(bytes: &[u8]) -> Result<Nonce, CryptoError> {
|
||||
Nonce::from_slice(bytes).ok_or(CryptoError::InvalidNonce)
|
||||
}
|
||||
|
||||
/// Parse a public key from bytes
|
||||
pub fn public_key_from_slice(bytes: &[u8]) -> Result<PublicKey, CryptoError> {
|
||||
PublicKey::from_slice(bytes).ok_or(CryptoError::InvalidKeyLength)
|
||||
}
|
||||
|
||||
/// Hash a password for storage/comparison
|
||||
/// RustDesk uses simple SHA256 for password hashing
|
||||
pub fn hash_password(password: &str, salt: &str) -> Vec<u8> {
|
||||
use sha2::{Digest, Sha256};
|
||||
let mut hasher = Sha256::new();
|
||||
@@ -176,35 +144,24 @@ pub fn hash_password(password: &str, salt: &str) -> Vec<u8> {
|
||||
hasher.finalize().to_vec()
|
||||
}
|
||||
|
||||
/// RustDesk double hash for password verification
|
||||
/// Client calculates: SHA256(SHA256(password + salt) + challenge)
|
||||
/// This matches what the client sends in LoginRequest
|
||||
pub fn hash_password_double(password: &str, salt: &str, challenge: &str) -> Vec<u8> {
|
||||
use sha2::{Digest, Sha256};
|
||||
// First hash: SHA256(password + salt)
|
||||
let mut hasher1 = Sha256::new();
|
||||
hasher1.update(password.as_bytes());
|
||||
hasher1.update(salt.as_bytes());
|
||||
let first_hash = hasher1.finalize();
|
||||
|
||||
// Second hash: SHA256(first_hash + challenge)
|
||||
let mut hasher2 = Sha256::new();
|
||||
hasher2.update(first_hash);
|
||||
hasher2.update(challenge.as_bytes());
|
||||
hasher2.finalize().to_vec()
|
||||
}
|
||||
|
||||
/// Verify a password hash
|
||||
pub fn verify_password(password: &str, salt: &str, expected_hash: &[u8]) -> bool {
|
||||
let computed = hash_password(password, salt);
|
||||
// Constant-time comparison would be better, but for our use case this is acceptable
|
||||
computed == expected_hash
|
||||
}
|
||||
|
||||
/// Decrypt symmetric key using Curve25519 secret key directly
|
||||
///
|
||||
/// This is used when we have a fresh Curve25519 keypair for the connection
|
||||
/// (as per RustDesk protocol - each connection generates a new keypair)
|
||||
pub fn decrypt_symmetric_key(
|
||||
their_temp_public_key: &[u8],
|
||||
sealed_symmetric_key: &[u8],
|
||||
@@ -217,7 +174,6 @@ pub fn decrypt_symmetric_key(
|
||||
let their_pk =
|
||||
PublicKey::from_slice(their_temp_public_key).ok_or(CryptoError::InvalidKeyLength)?;
|
||||
|
||||
// Use zero nonce as per RustDesk protocol
|
||||
let nonce = box_::Nonce([0u8; box_::NONCEBYTES]);
|
||||
|
||||
let key_bytes = box_::open(sealed_symmetric_key, &nonce, &their_pk, our_secret_key)
|
||||
@@ -226,11 +182,7 @@ pub fn decrypt_symmetric_key(
|
||||
secretbox::Key::from_slice(&key_bytes).ok_or(CryptoError::InvalidKeyLength)
|
||||
}
|
||||
|
||||
/// Encrypt a message using the negotiated symmetric key
|
||||
///
|
||||
/// RustDesk uses a specific nonce format for session encryption
|
||||
pub fn encrypt_message(data: &[u8], key: &secretbox::Key, nonce_counter: u64) -> Vec<u8> {
|
||||
// Create nonce from counter (little-endian, padded to 24 bytes)
|
||||
let mut nonce_bytes = [0u8; secretbox::NONCEBYTES];
|
||||
nonce_bytes[..8].copy_from_slice(&nonce_counter.to_le_bytes());
|
||||
let nonce = secretbox::Nonce(nonce_bytes);
|
||||
@@ -238,13 +190,11 @@ pub fn encrypt_message(data: &[u8], key: &secretbox::Key, nonce_counter: u64) ->
|
||||
secretbox::seal(data, &nonce, key)
|
||||
}
|
||||
|
||||
/// Decrypt a message using the negotiated symmetric key
|
||||
pub fn decrypt_message(
|
||||
ciphertext: &[u8],
|
||||
key: &secretbox::Key,
|
||||
nonce_counter: u64,
|
||||
) -> Result<Vec<u8>, CryptoError> {
|
||||
// Create nonce from counter (little-endian, padded to 24 bytes)
|
||||
let mut nonce_bytes = [0u8; secretbox::NONCEBYTES];
|
||||
nonce_bytes[..8].copy_from_slice(&nonce_counter.to_le_bytes());
|
||||
let nonce = secretbox::Nonce(nonce_bytes);
|
||||
@@ -252,7 +202,6 @@ pub fn decrypt_message(
|
||||
secretbox::open(ciphertext, &nonce, key).map_err(|_| CryptoError::DecryptionFailed)
|
||||
}
|
||||
|
||||
/// Ed25519 signing keypair for RustDesk SignedId messages
|
||||
#[derive(Clone)]
|
||||
pub struct SigningKeyPair {
|
||||
pub public_key: sign::PublicKey,
|
||||
@@ -260,7 +209,6 @@ pub struct SigningKeyPair {
|
||||
}
|
||||
|
||||
impl SigningKeyPair {
|
||||
/// Generate a new random signing keypair
|
||||
pub fn generate() -> Self {
|
||||
let (public_key, secret_key) = sign::gen_keypair();
|
||||
Self {
|
||||
@@ -269,7 +217,6 @@ impl SigningKeyPair {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create from existing keys
|
||||
pub fn from_keys(public_key: &[u8], secret_key: &[u8]) -> Result<Self, CryptoError> {
|
||||
let pk = sign::PublicKey::from_slice(public_key).ok_or(CryptoError::InvalidKeyLength)?;
|
||||
let sk = sign::SecretKey::from_slice(secret_key).ok_or(CryptoError::InvalidKeyLength)?;
|
||||
@@ -279,27 +226,22 @@ impl SigningKeyPair {
|
||||
})
|
||||
}
|
||||
|
||||
/// Get public key as bytes
|
||||
pub fn public_key_bytes(&self) -> &[u8] {
|
||||
self.public_key.as_ref()
|
||||
}
|
||||
|
||||
/// Get secret key as bytes
|
||||
pub fn secret_key_bytes(&self) -> &[u8] {
|
||||
self.secret_key.as_ref()
|
||||
}
|
||||
|
||||
/// Encode public key as base64
|
||||
pub fn public_key_base64(&self) -> String {
|
||||
BASE64.encode(self.public_key_bytes())
|
||||
}
|
||||
|
||||
/// Encode secret key as base64
|
||||
pub fn secret_key_base64(&self) -> String {
|
||||
BASE64.encode(self.secret_key_bytes())
|
||||
}
|
||||
|
||||
/// Create from base64-encoded keys
|
||||
pub fn from_base64(public_key: &str, secret_key: &str) -> Result<Self, CryptoError> {
|
||||
let pk_bytes = BASE64
|
||||
.decode(public_key)
|
||||
@@ -310,42 +252,27 @@ impl SigningKeyPair {
|
||||
Self::from_keys(&pk_bytes, &sk_bytes)
|
||||
}
|
||||
|
||||
/// Sign a message
|
||||
/// Returns the signature prepended to the message (as per RustDesk protocol)
|
||||
pub fn sign(&self, message: &[u8]) -> Vec<u8> {
|
||||
sign::sign(message, &self.secret_key)
|
||||
}
|
||||
|
||||
/// Sign a message and return just the signature (64 bytes)
|
||||
pub fn sign_detached(&self, message: &[u8]) -> [u8; 64] {
|
||||
let sig = sign::sign_detached(message, &self.secret_key);
|
||||
// Use as_ref() to access the signature bytes since the inner field is private
|
||||
let sig_bytes: &[u8] = sig.as_ref();
|
||||
let mut result = [0u8; 64];
|
||||
result.copy_from_slice(sig_bytes);
|
||||
result
|
||||
}
|
||||
|
||||
/// Convert Ed25519 public key to Curve25519 public key for encryption
|
||||
///
|
||||
/// This allows using the same keypair for both signing and encryption,
|
||||
/// which is required by RustDesk's protocol where clients encrypt the
|
||||
/// symmetric key using the public key from IdPk.
|
||||
pub fn to_curve25519_pk(&self) -> Result<PublicKey, CryptoError> {
|
||||
ed25519::to_curve25519_pk(&self.public_key).map_err(|_| CryptoError::KeyConversionFailed)
|
||||
}
|
||||
|
||||
/// Convert Ed25519 secret key to Curve25519 secret key for decryption
|
||||
///
|
||||
/// This allows decrypting messages that were encrypted using the
|
||||
/// converted public key.
|
||||
pub fn to_curve25519_sk(&self) -> Result<SecretKey, CryptoError> {
|
||||
ed25519::to_curve25519_sk(&self.secret_key).map_err(|_| CryptoError::KeyConversionFailed)
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify a signed message
|
||||
/// Returns the original message if signature is valid
|
||||
pub fn verify_signed(
|
||||
signed_message: &[u8],
|
||||
public_key: &sign::PublicKey,
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
//! RustDesk Frame Adapters
|
||||
//!
|
||||
//! Converts One-KVM video/audio frames to RustDesk protocol format.
|
||||
//! Optimized for zero-copy where possible and buffer reuse.
|
||||
|
||||
use bytes::Bytes;
|
||||
use protobuf::Message as ProtobufMessage;
|
||||
|
||||
@@ -11,7 +6,6 @@ use super::protocol::hbb::message::{
|
||||
CursorData, CursorPosition, EncodedVideoFrame, EncodedVideoFrames, Message, Misc, VideoFrame,
|
||||
};
|
||||
|
||||
/// Video codec type for RustDesk
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum VideoCodec {
|
||||
H264,
|
||||
@@ -22,7 +16,6 @@ pub enum VideoCodec {
|
||||
}
|
||||
|
||||
impl VideoCodec {
|
||||
/// Get the codec ID for the RustDesk protocol
|
||||
pub fn to_codec_id(self) -> i32 {
|
||||
match self {
|
||||
VideoCodec::H264 => 0,
|
||||
@@ -34,21 +27,15 @@ impl VideoCodec {
|
||||
}
|
||||
}
|
||||
|
||||
/// Video frame adapter for converting to RustDesk format
|
||||
pub struct VideoFrameAdapter {
|
||||
/// Current codec
|
||||
codec: VideoCodec,
|
||||
/// Frame sequence number
|
||||
seq: u32,
|
||||
/// Timestamp offset
|
||||
timestamp_base: u64,
|
||||
/// Cached H264 SPS/PPS (Annex B NAL without start code)
|
||||
h264_sps: Option<Bytes>,
|
||||
h264_pps: Option<Bytes>,
|
||||
}
|
||||
|
||||
impl VideoFrameAdapter {
|
||||
/// Create a new video frame adapter
|
||||
pub fn new(codec: VideoCodec) -> Self {
|
||||
Self {
|
||||
codec,
|
||||
@@ -59,14 +46,10 @@ impl VideoFrameAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set codec type
|
||||
pub fn set_codec(&mut self, codec: VideoCodec) {
|
||||
self.codec = codec;
|
||||
}
|
||||
|
||||
/// Convert encoded video data to RustDesk Message (zero-copy version)
|
||||
///
|
||||
/// This version takes Bytes directly to avoid copying the frame data.
|
||||
pub fn encode_frame_from_bytes(
|
||||
&mut self,
|
||||
data: Bytes,
|
||||
@@ -74,7 +57,6 @@ impl VideoFrameAdapter {
|
||||
timestamp_ms: u64,
|
||||
) -> Message {
|
||||
let data = self.prepare_h264_frame(data, is_keyframe);
|
||||
// Calculate relative timestamp
|
||||
if self.seq == 0 {
|
||||
self.timestamp_base = timestamp_ms;
|
||||
}
|
||||
@@ -87,11 +69,9 @@ impl VideoFrameAdapter {
|
||||
|
||||
self.seq = self.seq.wrapping_add(1);
|
||||
|
||||
// Wrap in EncodedVideoFrames container
|
||||
let mut frames = EncodedVideoFrames::new();
|
||||
frames.frames.push(frame);
|
||||
|
||||
// Create the appropriate VideoFrame variant based on codec
|
||||
let mut video_frame = VideoFrame::new();
|
||||
match self.codec {
|
||||
VideoCodec::H264 => video_frame.union = Some(vf_union::Union::H264s(frames)),
|
||||
@@ -111,7 +91,6 @@ impl VideoFrameAdapter {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Parse SPS/PPS from Annex B data (without start codes)
|
||||
let (sps, pps) = crate::webrtc::rtp::extract_sps_pps(&data);
|
||||
let mut has_sps = false;
|
||||
let mut has_pps = false;
|
||||
@@ -125,7 +104,6 @@ impl VideoFrameAdapter {
|
||||
has_pps = true;
|
||||
}
|
||||
|
||||
// Inject cached SPS/PPS before IDR when missing
|
||||
if is_keyframe && (!has_sps || !has_pps) {
|
||||
if let (Some(sps), Some(pps)) = (self.h264_sps.as_ref(), self.h264_pps.as_ref()) {
|
||||
let mut out = Vec::with_capacity(8 + sps.len() + pps.len() + data.len());
|
||||
@@ -141,14 +119,10 @@ impl VideoFrameAdapter {
|
||||
data
|
||||
}
|
||||
|
||||
/// Convert encoded video data to RustDesk Message
|
||||
pub fn encode_frame(&mut self, data: &[u8], is_keyframe: bool, timestamp_ms: u64) -> Message {
|
||||
self.encode_frame_from_bytes(Bytes::copy_from_slice(data), is_keyframe, timestamp_ms)
|
||||
}
|
||||
|
||||
/// Encode frame to bytes for sending (zero-copy version)
|
||||
///
|
||||
/// Takes Bytes directly to avoid copying the frame data.
|
||||
pub fn encode_frame_bytes_zero_copy(
|
||||
&mut self,
|
||||
data: Bytes,
|
||||
@@ -159,7 +133,6 @@ impl VideoFrameAdapter {
|
||||
Bytes::from(msg.write_to_bytes().unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Encode frame to bytes for sending
|
||||
pub fn encode_frame_bytes(
|
||||
&mut self,
|
||||
data: &[u8],
|
||||
@@ -169,24 +142,18 @@ impl VideoFrameAdapter {
|
||||
self.encode_frame_bytes_zero_copy(Bytes::copy_from_slice(data), is_keyframe, timestamp_ms)
|
||||
}
|
||||
|
||||
/// Get current sequence number
|
||||
pub fn seq(&self) -> u32 {
|
||||
self.seq
|
||||
}
|
||||
}
|
||||
|
||||
/// Audio frame adapter for converting to RustDesk format
|
||||
pub struct AudioFrameAdapter {
|
||||
/// Sample rate
|
||||
sample_rate: u32,
|
||||
/// Channels
|
||||
channels: u8,
|
||||
/// Format sent flag
|
||||
format_sent: bool,
|
||||
}
|
||||
|
||||
impl AudioFrameAdapter {
|
||||
/// Create a new audio frame adapter
|
||||
pub fn new(sample_rate: u32, channels: u8) -> Self {
|
||||
Self {
|
||||
sample_rate,
|
||||
@@ -195,7 +162,6 @@ impl AudioFrameAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create audio format message (should be sent once before audio frames)
|
||||
pub fn create_format_message(&mut self) -> Message {
|
||||
self.format_sent = true;
|
||||
|
||||
@@ -211,12 +177,10 @@ impl AudioFrameAdapter {
|
||||
msg
|
||||
}
|
||||
|
||||
/// Check if format message has been sent
|
||||
pub fn format_sent(&self) -> bool {
|
||||
self.format_sent
|
||||
}
|
||||
|
||||
/// Convert Opus audio data to RustDesk Message
|
||||
pub fn encode_opus_frame(&self, data: &[u8]) -> Message {
|
||||
let mut frame = AudioFrame::new();
|
||||
frame.data = Bytes::copy_from_slice(data);
|
||||
@@ -226,23 +190,19 @@ impl AudioFrameAdapter {
|
||||
msg
|
||||
}
|
||||
|
||||
/// Encode Opus frame to bytes for sending
|
||||
pub fn encode_opus_bytes(&self, data: &[u8]) -> Bytes {
|
||||
let msg = self.encode_opus_frame(data);
|
||||
Bytes::from(msg.write_to_bytes().unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Reset state (call when restarting audio stream)
|
||||
pub fn reset(&mut self) {
|
||||
self.format_sent = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Cursor data adapter
|
||||
pub struct CursorAdapter;
|
||||
|
||||
impl CursorAdapter {
|
||||
/// Create cursor data message
|
||||
pub fn encode_cursor(
|
||||
id: u64,
|
||||
hotx: i32,
|
||||
@@ -264,7 +224,6 @@ impl CursorAdapter {
|
||||
msg
|
||||
}
|
||||
|
||||
/// Create cursor position message
|
||||
pub fn encode_position(x: i32, y: i32) -> Message {
|
||||
let mut pos = CursorPosition::new();
|
||||
pos.x = x;
|
||||
@@ -284,7 +243,6 @@ mod tests {
|
||||
fn test_video_frame_encoding() {
|
||||
let mut adapter = VideoFrameAdapter::new(VideoCodec::H264);
|
||||
|
||||
// Encode a keyframe
|
||||
let data = vec![0x00, 0x00, 0x00, 0x01, 0x67]; // H264 SPS NAL
|
||||
let msg = adapter.encode_frame(&data, true, 0);
|
||||
|
||||
@@ -324,7 +282,6 @@ mod tests {
|
||||
fn test_audio_frame_encoding() {
|
||||
let adapter = AudioFrameAdapter::new(48000, 2);
|
||||
|
||||
// Encode an Opus frame
|
||||
let opus_data = vec![0xFC, 0x01, 0x02]; // Fake Opus data
|
||||
let msg = adapter.encode_opus_frame(&opus_data);
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
//! RustDesk HID Adapter
|
||||
//!
|
||||
//! Converts RustDesk HID events (KeyEvent, MouseEvent) to One-KVM HID events.
|
||||
|
||||
use super::protocol::hbb::message::key_event as ke_union;
|
||||
use super::protocol::{ControlKey, KeyEvent, MouseEvent};
|
||||
use crate::hid::{
|
||||
@@ -10,8 +6,6 @@ use crate::hid::{
|
||||
};
|
||||
use protobuf::Enum;
|
||||
|
||||
/// Mouse event types from RustDesk protocol
|
||||
/// mask = (button << 3) | event_type
|
||||
pub mod mouse_type {
|
||||
pub const MOVE: i32 = 0;
|
||||
pub const DOWN: i32 = 1;
|
||||
@@ -21,7 +15,6 @@ pub mod mouse_type {
|
||||
pub const MOVE_RELATIVE: i32 = 5;
|
||||
}
|
||||
|
||||
/// Mouse button IDs from RustDesk protocol (before left shift by 3)
|
||||
pub mod mouse_button {
|
||||
pub const LEFT: i32 = 0x01;
|
||||
pub const RIGHT: i32 = 0x02;
|
||||
@@ -30,9 +23,6 @@ pub mod mouse_button {
|
||||
pub const FORWARD: i32 = 0x10;
|
||||
}
|
||||
|
||||
/// Convert RustDesk MouseEvent to One-KVM MouseEvent(s)
|
||||
/// Returns a Vec because a single RustDesk event may need multiple One-KVM events
|
||||
/// (e.g., move + button + scroll)
|
||||
pub fn convert_mouse_event(
|
||||
event: &MouseEvent,
|
||||
screen_width: u32,
|
||||
@@ -41,23 +31,18 @@ pub fn convert_mouse_event(
|
||||
) -> Vec<OneKvmMouseEvent> {
|
||||
let mut events = Vec::new();
|
||||
|
||||
// Parse RustDesk mask format: (button << 3) | event_type
|
||||
let event_type = event.mask & 0x07;
|
||||
let button_id = event.mask >> 3;
|
||||
let include_abs_move = !relative_mode;
|
||||
|
||||
match event_type {
|
||||
mouse_type::MOVE => {
|
||||
// RustDesk uses absolute coordinates
|
||||
let x = event.x.max(0) as u32;
|
||||
let y = event.y.max(0) as u32;
|
||||
|
||||
// Normalize to 0-32767 range for absolute mouse (USB HID standard)
|
||||
let abs_x = ((x as u64 * 32767) / screen_width.max(1) as u64) as i32;
|
||||
let abs_y = ((y as u64 * 32767) / screen_height.max(1) as u64) as i32;
|
||||
|
||||
// Move event - may have button held down (button_id > 0 means dragging)
|
||||
// Just send move, button state is tracked separately by HID backend
|
||||
events.push(OneKvmMouseEvent {
|
||||
event_type: MouseEventType::MoveAbs,
|
||||
x: abs_x,
|
||||
@@ -67,7 +52,6 @@ pub fn convert_mouse_event(
|
||||
});
|
||||
}
|
||||
mouse_type::MOVE_RELATIVE => {
|
||||
// Relative movement uses delta values directly (dx, dy).
|
||||
events.push(OneKvmMouseEvent {
|
||||
event_type: MouseEventType::Move,
|
||||
x: event.x,
|
||||
@@ -78,7 +62,6 @@ pub fn convert_mouse_event(
|
||||
}
|
||||
mouse_type::DOWN => {
|
||||
if include_abs_move {
|
||||
// Button down - first move, then press
|
||||
let x = event.x.max(0) as u32;
|
||||
let y = event.y.max(0) as u32;
|
||||
let abs_x = ((x as u64 * 32767) / screen_width.max(1) as u64) as i32;
|
||||
@@ -104,7 +87,6 @@ pub fn convert_mouse_event(
|
||||
}
|
||||
mouse_type::UP => {
|
||||
if include_abs_move {
|
||||
// Button up - first move, then release
|
||||
let x = event.x.max(0) as u32;
|
||||
let y = event.y.max(0) as u32;
|
||||
let abs_x = ((x as u64 * 32767) / screen_width.max(1) as u64) as i32;
|
||||
@@ -130,7 +112,6 @@ pub fn convert_mouse_event(
|
||||
}
|
||||
mouse_type::WHEEL => {
|
||||
if include_abs_move {
|
||||
// Scroll event - move first, then scroll
|
||||
let x = event.x.max(0) as u32;
|
||||
let y = event.y.max(0) as u32;
|
||||
let abs_x = ((x as u64 * 32767) / screen_width.max(1) as u64) as i32;
|
||||
@@ -144,9 +125,6 @@ pub fn convert_mouse_event(
|
||||
});
|
||||
}
|
||||
|
||||
// RustDesk encodes scroll direction in the y coordinate
|
||||
// Positive y = scroll up, Negative y = scroll down
|
||||
// The button_id field is not used for direction
|
||||
let scroll = if event.y > 0 { 1i8 } else { -1i8 };
|
||||
events.push(OneKvmMouseEvent {
|
||||
event_type: MouseEventType::Scroll,
|
||||
@@ -158,7 +136,6 @@ pub fn convert_mouse_event(
|
||||
}
|
||||
_ => {
|
||||
if include_abs_move {
|
||||
// Unknown event type, just move
|
||||
let x = event.x.max(0) as u32;
|
||||
let y = event.y.max(0) as u32;
|
||||
let abs_x = ((x as u64 * 32767) / screen_width.max(1) as u64) as i32;
|
||||
@@ -177,7 +154,6 @@ pub fn convert_mouse_event(
|
||||
events
|
||||
}
|
||||
|
||||
/// Convert RustDesk button ID to One-KVM MouseButton
|
||||
fn button_id_to_button(button_id: i32) -> Option<MouseButton> {
|
||||
match button_id {
|
||||
mouse_button::LEFT => Some(MouseButton::Left),
|
||||
@@ -187,34 +163,19 @@ fn button_id_to_button(button_id: i32) -> Option<MouseButton> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert RustDesk KeyEvent to One-KVM KeyboardEvent
|
||||
///
|
||||
/// RustDesk KeyEvent has two modes:
|
||||
/// - down=true/false: Key state (pressed/released)
|
||||
/// - press=true: Complete key press (down + up), used for typing
|
||||
///
|
||||
/// For press=true events, we only send Down and let the caller handle
|
||||
/// the timing for Up event if needed. Most systems handle this correctly.
|
||||
pub fn convert_key_event(event: &KeyEvent) -> Option<KeyboardEvent> {
|
||||
// Determine if this is a key down or key up event
|
||||
// press=true means "key was pressed" (down event)
|
||||
// down=true means key is currently held down
|
||||
// down=false with press=false means key was released
|
||||
let event_type = if event.down || event.press {
|
||||
KeyEventType::Down
|
||||
} else {
|
||||
KeyEventType::Up
|
||||
};
|
||||
|
||||
// For modifier keys sent as ControlKey, don't include them in modifiers
|
||||
// to avoid double-pressing. The modifier will be tracked by HID state.
|
||||
let modifiers = if is_modifier_control_key(event) {
|
||||
KeyboardModifiers::default()
|
||||
} else {
|
||||
parse_modifiers(event)
|
||||
};
|
||||
|
||||
// Handle control keys
|
||||
if let Some(ke_union::Union::ControlKey(ck)) = &event.union {
|
||||
if let Some(key) = control_key_to_hid(ck.value()) {
|
||||
let key = CanonicalKey::from_hid_usage(key)?;
|
||||
@@ -226,9 +187,7 @@ pub fn convert_key_event(event: &KeyEvent) -> Option<KeyboardEvent> {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle character keys (chr field contains platform-specific keycode)
|
||||
if let Some(ke_union::Union::Chr(chr)) = &event.union {
|
||||
// chr contains USB HID scancode on Windows, X11 keycode on Linux
|
||||
if let Some(key) = keycode_to_hid(*chr) {
|
||||
let key = CanonicalKey::from_hid_usage(key)?;
|
||||
return Some(KeyboardEvent {
|
||||
@@ -239,13 +198,9 @@ pub fn convert_key_event(event: &KeyEvent) -> Option<KeyboardEvent> {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle unicode (for text input, we'd need to convert to scancodes)
|
||||
// Unicode input requires more complex handling, skip for now
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Check if the event is a modifier key sent as ControlKey
|
||||
fn is_modifier_control_key(event: &KeyEvent) -> bool {
|
||||
if let Some(ke_union::Union::ControlKey(ck)) = &event.union {
|
||||
let val = ck.value();
|
||||
@@ -260,7 +215,6 @@ fn is_modifier_control_key(event: &KeyEvent) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Parse modifier keys from RustDesk KeyEvent into KeyboardModifiers
|
||||
fn parse_modifiers(event: &KeyEvent) -> KeyboardModifiers {
|
||||
let mut modifiers = KeyboardModifiers::default();
|
||||
|
||||
@@ -281,7 +235,6 @@ fn parse_modifiers(event: &KeyEvent) -> KeyboardModifiers {
|
||||
modifiers
|
||||
}
|
||||
|
||||
/// Convert RustDesk ControlKey to USB HID usage code
|
||||
fn control_key_to_hid(key: i32) -> Option<u8> {
|
||||
match key {
|
||||
x if x == ControlKey::Alt as i32 => Some(0xE2), // Left Alt
|
||||
@@ -342,67 +295,47 @@ fn control_key_to_hid(key: i32) -> Option<u8> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert platform keycode to USB HID usage code
|
||||
/// Handles Windows Virtual Key Codes, X11 keycodes, and ASCII codes
|
||||
fn keycode_to_hid(keycode: u32) -> Option<u8> {
|
||||
// First try ASCII code mapping (RustDesk often sends ASCII codes)
|
||||
if let Some(hid) = ascii_to_hid(keycode) {
|
||||
return Some(hid);
|
||||
}
|
||||
// Then try Windows Virtual Key Code mapping
|
||||
if let Some(hid) = windows_vk_to_hid(keycode) {
|
||||
return Some(hid);
|
||||
}
|
||||
// Fall back to X11 keycode mapping for Linux clients
|
||||
x11_keycode_to_hid(keycode)
|
||||
}
|
||||
|
||||
/// Convert ASCII code to USB HID usage code
|
||||
fn ascii_to_hid(ascii: u32) -> Option<u8> {
|
||||
match ascii {
|
||||
// Lowercase letters a-z (ASCII 97-122)
|
||||
97..=122 => {
|
||||
// USB HID: a=0x04, b=0x05, ..., z=0x1D
|
||||
Some((ascii - 97 + 0x04) as u8)
|
||||
}
|
||||
// Uppercase letters A-Z (ASCII 65-90)
|
||||
65..=90 => {
|
||||
// USB HID: A=0x04, B=0x05, ..., Z=0x1D (same as lowercase)
|
||||
Some((ascii - 65 + 0x04) as u8)
|
||||
}
|
||||
// Numbers 0-9 (ASCII 48-57)
|
||||
97..=122 => Some((ascii - 97 + 0x04) as u8),
|
||||
65..=90 => Some((ascii - 65 + 0x04) as u8),
|
||||
48 => Some(0x27), // 0
|
||||
49..=57 => Some((ascii - 49 + 0x1E) as u8), // 1-9
|
||||
// Common punctuation
|
||||
32 => Some(0x2C), // Space
|
||||
13 => Some(0x28), // Enter (CR)
|
||||
10 => Some(0x28), // Enter (LF)
|
||||
9 => Some(0x2B), // Tab
|
||||
27 => Some(0x29), // Escape
|
||||
8 => Some(0x2A), // Backspace
|
||||
127 => Some(0x4C), // Delete
|
||||
// Symbols (US keyboard layout)
|
||||
45 => Some(0x2D), // -
|
||||
61 => Some(0x2E), // =
|
||||
91 => Some(0x2F), // [
|
||||
93 => Some(0x30), // ]
|
||||
92 => Some(0x31), // \
|
||||
59 => Some(0x33), // ;
|
||||
39 => Some(0x34), // '
|
||||
96 => Some(0x35), // `
|
||||
44 => Some(0x36), // ,
|
||||
46 => Some(0x37), // .
|
||||
47 => Some(0x38), // /
|
||||
32 => Some(0x2C), // Space
|
||||
13 => Some(0x28), // Enter (CR)
|
||||
10 => Some(0x28), // Enter (LF)
|
||||
9 => Some(0x2B), // Tab
|
||||
27 => Some(0x29), // Escape
|
||||
8 => Some(0x2A), // Backspace
|
||||
127 => Some(0x4C), // Delete
|
||||
45 => Some(0x2D), // -
|
||||
61 => Some(0x2E), // =
|
||||
91 => Some(0x2F), // [
|
||||
93 => Some(0x30), // ]
|
||||
92 => Some(0x31), // \
|
||||
59 => Some(0x33), // ;
|
||||
39 => Some(0x34), // '
|
||||
96 => Some(0x35), // `
|
||||
44 => Some(0x36), // ,
|
||||
46 => Some(0x37), // .
|
||||
47 => Some(0x38), // /
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert Windows Virtual Key Code to USB HID usage code
|
||||
fn windows_vk_to_hid(vk: u32) -> Option<u8> {
|
||||
match vk {
|
||||
// Letters A-Z (VK_A=0x41 to VK_Z=0x5A)
|
||||
0x41..=0x5A => {
|
||||
// USB HID: A=0x04, B=0x05, ..., Z=0x1D
|
||||
let letter = (vk - 0x41) as u8;
|
||||
Some(match letter {
|
||||
0 => 0x04, // A
|
||||
@@ -434,21 +367,16 @@ fn windows_vk_to_hid(vk: u32) -> Option<u8> {
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
// Numbers 0-9 (VK_0=0x30 to VK_9=0x39)
|
||||
0x30 => Some(0x27), // 0
|
||||
0x31..=0x39 => Some((vk - 0x31 + 0x1E) as u8), // 1-9
|
||||
// Numpad 0-9 (VK_NUMPAD0=0x60 to VK_NUMPAD9=0x69)
|
||||
0x60 => Some(0x62), // Numpad 0
|
||||
0x61..=0x69 => Some((vk - 0x61 + 0x59) as u8), // Numpad 1-9
|
||||
// Numpad operators
|
||||
0x6A => Some(0x55), // Numpad *
|
||||
0x6B => Some(0x57), // Numpad +
|
||||
0x6D => Some(0x56), // Numpad -
|
||||
0x6E => Some(0x63), // Numpad .
|
||||
0x6F => Some(0x54), // Numpad /
|
||||
// Function keys F1-F12 (VK_F1=0x70 to VK_F12=0x7B)
|
||||
0x6A => Some(0x55), // Numpad *
|
||||
0x6B => Some(0x57), // Numpad +
|
||||
0x6D => Some(0x56), // Numpad -
|
||||
0x6E => Some(0x63), // Numpad .
|
||||
0x6F => Some(0x54), // Numpad /
|
||||
0x70..=0x7B => Some((vk - 0x70 + 0x3A) as u8),
|
||||
// Special keys
|
||||
0x08 => Some(0x2A), // Backspace
|
||||
0x09 => Some(0x2B), // Tab
|
||||
0x0D => Some(0x28), // Enter
|
||||
@@ -464,7 +392,6 @@ fn windows_vk_to_hid(vk: u32) -> Option<u8> {
|
||||
0x28 => Some(0x51), // Down Arrow
|
||||
0x2D => Some(0x49), // Insert
|
||||
0x2E => Some(0x4C), // Delete
|
||||
// OEM keys (US keyboard layout)
|
||||
0xBA => Some(0x33), // ; :
|
||||
0xBB => Some(0x2E), // = +
|
||||
0xBC => Some(0x36), // , <
|
||||
@@ -476,66 +403,56 @@ fn windows_vk_to_hid(vk: u32) -> Option<u8> {
|
||||
0xDC => Some(0x31), // \ |
|
||||
0xDD => Some(0x30), // ] }
|
||||
0xDE => Some(0x34), // ' "
|
||||
// Lock keys
|
||||
0x14 => Some(0x39), // Caps Lock
|
||||
0x90 => Some(0x53), // Num Lock
|
||||
0x91 => Some(0x47), // Scroll Lock
|
||||
// Print Screen, Pause
|
||||
0x2C => Some(0x46), // Print Screen
|
||||
0x13 => Some(0x48), // Pause
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert X11 keycode to USB HID usage code (for Linux clients)
|
||||
fn x11_keycode_to_hid(keycode: u32) -> Option<u8> {
|
||||
match keycode {
|
||||
// Numbers: X11 keycode 10="1", 11="2", ..., 18="9", 19="0"
|
||||
10..=18 => Some((keycode - 10 + 0x1E) as u8), // 1-9
|
||||
19 => Some(0x27), // 0
|
||||
// Punctuation
|
||||
20 => Some(0x2D), // -
|
||||
21 => Some(0x2E), // =
|
||||
34 => Some(0x2F), // [
|
||||
35 => Some(0x30), // ]
|
||||
// Letters (X11 keycodes are row-based)
|
||||
// Row 1: q(24) w(25) e(26) r(27) t(28) y(29) u(30) i(31) o(32) p(33)
|
||||
24 => Some(0x14), // q
|
||||
25 => Some(0x1A), // w
|
||||
26 => Some(0x08), // e
|
||||
27 => Some(0x15), // r
|
||||
28 => Some(0x17), // t
|
||||
29 => Some(0x1C), // y
|
||||
30 => Some(0x18), // u
|
||||
31 => Some(0x0C), // i
|
||||
32 => Some(0x12), // o
|
||||
33 => Some(0x13), // p
|
||||
// Row 2: a(38) s(39) d(40) f(41) g(42) h(43) j(44) k(45) l(46)
|
||||
38 => Some(0x04), // a
|
||||
39 => Some(0x16), // s
|
||||
40 => Some(0x07), // d
|
||||
41 => Some(0x09), // f
|
||||
42 => Some(0x0A), // g
|
||||
43 => Some(0x0B), // h
|
||||
44 => Some(0x0D), // j
|
||||
45 => Some(0x0E), // k
|
||||
46 => Some(0x0F), // l
|
||||
47 => Some(0x33), // ;
|
||||
48 => Some(0x34), // '
|
||||
49 => Some(0x35), // `
|
||||
51 => Some(0x31), // \
|
||||
// Row 3: z(52) x(53) c(54) v(55) b(56) n(57) m(58)
|
||||
52 => Some(0x1D), // z
|
||||
53 => Some(0x1B), // x
|
||||
54 => Some(0x06), // c
|
||||
55 => Some(0x19), // v
|
||||
56 => Some(0x05), // b
|
||||
57 => Some(0x11), // n
|
||||
58 => Some(0x10), // m
|
||||
59 => Some(0x36), // ,
|
||||
60 => Some(0x37), // .
|
||||
61 => Some(0x38), // /
|
||||
// Space
|
||||
20 => Some(0x2D), // -
|
||||
21 => Some(0x2E), // =
|
||||
34 => Some(0x2F), // [
|
||||
35 => Some(0x30), // ]
|
||||
24 => Some(0x14), // q
|
||||
25 => Some(0x1A), // w
|
||||
26 => Some(0x08), // e
|
||||
27 => Some(0x15), // r
|
||||
28 => Some(0x17), // t
|
||||
29 => Some(0x1C), // y
|
||||
30 => Some(0x18), // u
|
||||
31 => Some(0x0C), // i
|
||||
32 => Some(0x12), // o
|
||||
33 => Some(0x13), // p
|
||||
38 => Some(0x04), // a
|
||||
39 => Some(0x16), // s
|
||||
40 => Some(0x07), // d
|
||||
41 => Some(0x09), // f
|
||||
42 => Some(0x0A), // g
|
||||
43 => Some(0x0B), // h
|
||||
44 => Some(0x0D), // j
|
||||
45 => Some(0x0E), // k
|
||||
46 => Some(0x0F), // l
|
||||
47 => Some(0x33), // ;
|
||||
48 => Some(0x34), // '
|
||||
49 => Some(0x35), // `
|
||||
51 => Some(0x31), // \
|
||||
52 => Some(0x1D), // z
|
||||
53 => Some(0x1B), // x
|
||||
54 => Some(0x06), // c
|
||||
55 => Some(0x19), // v
|
||||
56 => Some(0x05), // b
|
||||
57 => Some(0x11), // n
|
||||
58 => Some(0x10), // m
|
||||
59 => Some(0x36), // ,
|
||||
60 => Some(0x37), // .
|
||||
61 => Some(0x38), // /
|
||||
65 => Some(0x2C),
|
||||
_ => None,
|
||||
}
|
||||
@@ -573,7 +490,6 @@ mod tests {
|
||||
|
||||
let events = convert_mouse_event(&event, 1920, 1080, false);
|
||||
assert!(events.len() >= 2);
|
||||
// Should have a button down event
|
||||
assert!(events
|
||||
.iter()
|
||||
.any(|e| e.event_type == MouseEventType::Down && e.button == Some(MouseButton::Left)));
|
||||
|
||||
@@ -1,17 +1,4 @@
|
||||
//! RustDesk Protocol Integration Module
|
||||
//!
|
||||
//! This module implements the RustDesk client protocol, enabling One-KVM devices
|
||||
//! to be accessed via standard RustDesk clients through existing hbbs/hbbr servers.
|
||||
//!
|
||||
//! ## Architecture
|
||||
//!
|
||||
//! - `config`: Configuration types for RustDesk settings
|
||||
//! - `protocol`: Protobuf message wrappers and serialization
|
||||
//! - `crypto`: NaCl cryptography (key generation, encryption, signatures)
|
||||
//! - `rendezvous`: Communication with hbbs rendezvous server
|
||||
//! - `connection`: Client session handling
|
||||
//! - `frame_adapters`: Video/audio frame conversion to RustDesk format
|
||||
//! - `hid_adapter`: RustDesk HID events to One-KVM conversion
|
||||
//! RustDesk peer protocol (hbbs / hbbr).
|
||||
|
||||
pub mod bytes_codec;
|
||||
pub mod config;
|
||||
@@ -44,19 +31,13 @@ use self::connection::ConnectionManager;
|
||||
use self::protocol::{make_local_addr, make_relay_response, make_request_relay};
|
||||
use self::rendezvous::{AddrMangle, RendezvousMediator, RendezvousStatus};
|
||||
|
||||
/// Relay connection timeout
|
||||
const RELAY_CONNECT_TIMEOUT_MS: u64 = 10_000;
|
||||
|
||||
/// RustDesk service status
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ServiceStatus {
|
||||
/// Service is stopped
|
||||
Stopped,
|
||||
/// Service is starting
|
||||
Starting,
|
||||
/// Service is running and registered with rendezvous server
|
||||
Running,
|
||||
/// Service encountered an error
|
||||
Error(String),
|
||||
}
|
||||
|
||||
@@ -71,15 +52,8 @@ impl std::fmt::Display for ServiceStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/// Default port for direct TCP connections (same as RustDesk)
|
||||
const DIRECT_LISTEN_PORT: u16 = 21118;
|
||||
|
||||
/// RustDesk Service
|
||||
///
|
||||
/// Manages the RustDesk protocol integration, including:
|
||||
/// - Registration with hbbs rendezvous server
|
||||
/// - Accepting connections from RustDesk clients
|
||||
/// - Streaming video/audio and receiving HID input
|
||||
pub struct RustDeskService {
|
||||
config: Arc<RwLock<RustDeskConfig>>,
|
||||
status: Arc<RwLock<ServiceStatus>>,
|
||||
@@ -95,7 +69,6 @@ pub struct RustDeskService {
|
||||
}
|
||||
|
||||
impl RustDeskService {
|
||||
/// Create a new RustDesk service instance
|
||||
pub fn new(
|
||||
config: RustDeskConfig,
|
||||
video_manager: Arc<VideoStreamManager>,
|
||||
@@ -120,42 +93,34 @@ impl RustDeskService {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the port for direct TCP connections
|
||||
pub fn listen_port(&self) -> u16 {
|
||||
*self.listen_port.read()
|
||||
}
|
||||
|
||||
/// Get current service status
|
||||
pub fn status(&self) -> ServiceStatus {
|
||||
self.status.read().clone()
|
||||
}
|
||||
|
||||
/// Get current configuration
|
||||
pub fn config(&self) -> RustDeskConfig {
|
||||
self.config.read().clone()
|
||||
}
|
||||
|
||||
/// Update configuration
|
||||
pub fn update_config(&self, config: RustDeskConfig) {
|
||||
*self.config.write() = config;
|
||||
}
|
||||
|
||||
/// Get rendezvous status
|
||||
pub fn rendezvous_status(&self) -> Option<RendezvousStatus> {
|
||||
self.rendezvous.read().as_ref().map(|r| r.status())
|
||||
}
|
||||
|
||||
/// Get device ID
|
||||
pub fn device_id(&self) -> String {
|
||||
self.config.read().device_id.clone()
|
||||
}
|
||||
|
||||
/// Get connection count
|
||||
pub fn connection_count(&self) -> usize {
|
||||
self.connection_manager.connection_count()
|
||||
}
|
||||
|
||||
/// Start the RustDesk service
|
||||
pub async fn start(&self) -> anyhow::Result<()> {
|
||||
let config = self.config.read().clone();
|
||||
|
||||
@@ -181,74 +146,44 @@ impl RustDeskService {
|
||||
config.rendezvous_addr()
|
||||
);
|
||||
|
||||
// Initialize crypto
|
||||
if let Err(e) = crypto::init() {
|
||||
error!("Failed to initialize crypto: {}", e);
|
||||
*self.status.write() = ServiceStatus::Error(e.to_string());
|
||||
return Err(e.into());
|
||||
}
|
||||
|
||||
// Create and start rendezvous mediator with relay callback
|
||||
let mediator = Arc::new(RendezvousMediator::new(config.clone()));
|
||||
|
||||
// Set the keypair on connection manager (Curve25519 for encryption)
|
||||
let keypair = mediator.ensure_keypair();
|
||||
self.connection_manager.set_keypair(keypair);
|
||||
|
||||
// Set the signing keypair on connection manager (Ed25519 for SignedId)
|
||||
let signing_keypair = mediator.ensure_signing_keypair();
|
||||
self.connection_manager.set_signing_keypair(signing_keypair);
|
||||
|
||||
// Set the HID controller on connection manager
|
||||
self.connection_manager.set_hid(self.hid.clone());
|
||||
|
||||
// Set the audio controller on connection manager for audio streaming
|
||||
self.connection_manager.set_audio(self.audio.clone());
|
||||
|
||||
// Set the video manager on connection manager for video streaming
|
||||
self.connection_manager
|
||||
.set_video_manager(self.video_manager.clone());
|
||||
|
||||
*self.rendezvous.write() = Some(mediator.clone());
|
||||
|
||||
// Start TCP listener BEFORE the rendezvous mediator to ensure port is set correctly
|
||||
// This prevents race condition where mediator starts registration with wrong port
|
||||
let (tcp_handles, listen_port) = self.start_tcp_listener_with_port().await?;
|
||||
*self.tcp_listener_handle.write() = Some(tcp_handles);
|
||||
|
||||
// Set the listen port on mediator before starting the registration loop
|
||||
mediator.set_listen_port(listen_port);
|
||||
|
||||
// Create relay request handler
|
||||
let connection_manager = self.connection_manager.clone();
|
||||
let video_manager = self.video_manager.clone();
|
||||
let hid = self.hid.clone();
|
||||
let audio = self.audio.clone();
|
||||
let service_config = self.config.clone();
|
||||
|
||||
// Set the punch callback on the mediator (tries P2P first, then relay)
|
||||
let connection_manager_punch = self.connection_manager.clone();
|
||||
let video_manager_punch = self.video_manager.clone();
|
||||
let hid_punch = self.hid.clone();
|
||||
let audio_punch = self.audio.clone();
|
||||
let service_config_punch = self.config.clone();
|
||||
|
||||
mediator.set_punch_callback(Arc::new(
|
||||
mediator.set_punch_callback(Arc::new({
|
||||
let connection_manager = connection_manager.clone();
|
||||
let service_config = service_config.clone();
|
||||
move |peer_addr, rendezvous_addr, relay_server, uuid, socket_addr, device_id| {
|
||||
let conn_mgr = connection_manager_punch.clone();
|
||||
let video = video_manager_punch.clone();
|
||||
let hid = hid_punch.clone();
|
||||
let audio = audio_punch.clone();
|
||||
let config = service_config_punch.clone();
|
||||
|
||||
let conn_mgr = connection_manager.clone();
|
||||
let config = service_config.clone();
|
||||
tokio::spawn(async move {
|
||||
// Get relay_key from config (no public server fallback)
|
||||
let relay_key = {
|
||||
let cfg = config.read();
|
||||
cfg.relay_key.clone().unwrap_or_default()
|
||||
};
|
||||
|
||||
// Try P2P direct connection first
|
||||
if let Some(addr) = peer_addr {
|
||||
info!("Attempting P2P direct connection to {}", addr);
|
||||
match punch::try_direct_connection(addr).await {
|
||||
@@ -265,7 +200,7 @@ impl RustDeskService {
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to relay
|
||||
let relay_key = rustdesk_relay_key(&config);
|
||||
if let Err(e) = handle_relay_request(
|
||||
&rendezvous_addr,
|
||||
&relay_server,
|
||||
@@ -274,34 +209,23 @@ impl RustDeskService {
|
||||
&device_id,
|
||||
&relay_key,
|
||||
conn_mgr,
|
||||
video,
|
||||
hid,
|
||||
audio,
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("Failed to handle relay request: {}", e);
|
||||
}
|
||||
});
|
||||
},
|
||||
));
|
||||
}
|
||||
}));
|
||||
|
||||
// Set the relay callback on the mediator
|
||||
mediator.set_relay_callback(Arc::new(
|
||||
mediator.set_relay_callback(Arc::new({
|
||||
let connection_manager = connection_manager.clone();
|
||||
let service_config = service_config.clone();
|
||||
move |rendezvous_addr, relay_server, uuid, socket_addr, device_id| {
|
||||
let conn_mgr = connection_manager.clone();
|
||||
let video = video_manager.clone();
|
||||
let hid = hid.clone();
|
||||
let audio = audio.clone();
|
||||
let config = service_config.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
// Get relay_key from config (no public server fallback)
|
||||
let relay_key = {
|
||||
let cfg = config.read();
|
||||
cfg.relay_key.clone().unwrap_or_default()
|
||||
};
|
||||
|
||||
let relay_key = rustdesk_relay_key(&config);
|
||||
if let Err(e) = handle_relay_request(
|
||||
&rendezvous_addr,
|
||||
&relay_server,
|
||||
@@ -310,19 +234,15 @@ impl RustDeskService {
|
||||
&device_id,
|
||||
&relay_key,
|
||||
conn_mgr,
|
||||
video,
|
||||
hid,
|
||||
audio,
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("Failed to handle relay request: {}", e);
|
||||
}
|
||||
});
|
||||
},
|
||||
));
|
||||
}
|
||||
}));
|
||||
|
||||
// Set the intranet callback on the mediator for same-LAN connections
|
||||
let connection_manager2 = self.connection_manager.clone();
|
||||
mediator.set_intranet_callback(Arc::new(
|
||||
move |rendezvous_addr, peer_socket_addr, local_addr, relay_server, device_id| {
|
||||
@@ -345,7 +265,6 @@ impl RustDeskService {
|
||||
},
|
||||
));
|
||||
|
||||
// Spawn rendezvous task
|
||||
let status = self.status.clone();
|
||||
let handle = tokio::spawn(async move {
|
||||
loop {
|
||||
@@ -357,7 +276,6 @@ impl RustDeskService {
|
||||
Err(e) => {
|
||||
error!("Rendezvous mediator error: {}", e);
|
||||
*status.write() = ServiceStatus::Error(e.to_string());
|
||||
// Wait before retry
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
*status.write() = ServiceStatus::Starting;
|
||||
}
|
||||
@@ -372,10 +290,7 @@ impl RustDeskService {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start TCP listener for direct peer connections
|
||||
/// Returns the join handle and the port that was bound
|
||||
async fn start_tcp_listener_with_port(&self) -> anyhow::Result<(Vec<JoinHandle<()>>, u16)> {
|
||||
// Try to bind to the default port, or find an available port
|
||||
let (listeners, listen_port) = match self.bind_direct_listeners(DIRECT_LISTEN_PORT) {
|
||||
Ok(result) => result,
|
||||
Err(err) => {
|
||||
@@ -453,7 +368,6 @@ impl RustDeskService {
|
||||
Ok((listeners, listen_port))
|
||||
}
|
||||
|
||||
/// Stop the RustDesk service
|
||||
pub async fn stop(&self) -> anyhow::Result<()> {
|
||||
if self.status() == ServiceStatus::Stopped {
|
||||
return Ok(());
|
||||
@@ -461,23 +375,18 @@ impl RustDeskService {
|
||||
|
||||
info!("Stopping RustDesk service");
|
||||
|
||||
// Send shutdown signal (this will stop the TCP listener)
|
||||
let _ = self.shutdown_tx.send(());
|
||||
|
||||
// Close all connections
|
||||
self.connection_manager.close_all();
|
||||
|
||||
// Stop rendezvous mediator
|
||||
if let Some(mediator) = self.rendezvous.read().as_ref() {
|
||||
mediator.stop();
|
||||
}
|
||||
|
||||
// Wait for rendezvous task to finish
|
||||
if let Some(handle) = self.rendezvous_handle.write().take() {
|
||||
handle.abort();
|
||||
}
|
||||
|
||||
// Wait for TCP listener task to finish
|
||||
if let Some(handles) = self.tcp_listener_handle.write().take() {
|
||||
for handle in handles {
|
||||
handle.abort();
|
||||
@@ -490,15 +399,12 @@ impl RustDeskService {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Restart the service with new configuration
|
||||
pub async fn restart(&self, config: RustDeskConfig) -> anyhow::Result<()> {
|
||||
self.stop().await?;
|
||||
self.update_config(config);
|
||||
self.start().await
|
||||
}
|
||||
|
||||
/// Save keypair and UUID to config
|
||||
/// Returns the updated config if changes were made
|
||||
pub fn save_credentials(&self) -> Option<RustDeskConfig> {
|
||||
if let Some(mediator) = self.rendezvous.read().as_ref() {
|
||||
let kp = mediator.ensure_keypair();
|
||||
@@ -506,7 +412,6 @@ impl RustDeskService {
|
||||
let mut config = self.config.write();
|
||||
let mut changed = false;
|
||||
|
||||
// Save encryption keypair (Curve25519)
|
||||
let pk = kp.public_key_base64();
|
||||
let sk = kp.secret_key_base64();
|
||||
if config.public_key.as_ref() != Some(&pk) || config.private_key.as_ref() != Some(&sk) {
|
||||
@@ -515,7 +420,6 @@ impl RustDeskService {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Save signing keypair (Ed25519)
|
||||
let signing_pk = skp.public_key_base64();
|
||||
let signing_sk = skp.secret_key_base64();
|
||||
if config.signing_public_key.as_ref() != Some(&signing_pk)
|
||||
@@ -526,7 +430,6 @@ impl RustDeskService {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Save UUID if it was newly generated
|
||||
if mediator.uuid_needs_save() {
|
||||
let mediator_config = mediator.config();
|
||||
if let Some(uuid) = mediator_config.uuid {
|
||||
@@ -545,21 +448,16 @@ impl RustDeskService {
|
||||
None
|
||||
}
|
||||
|
||||
/// Save keypair to config (deprecated, use save_credentials instead)
|
||||
#[deprecated(note = "Use save_credentials instead")]
|
||||
pub fn save_keypair(&self) {
|
||||
let _ = self.save_credentials();
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle relay request from rendezvous server
|
||||
///
|
||||
/// Correct flow based on RustDesk protocol:
|
||||
/// 1. Connect to RENDEZVOUS server (not relay!)
|
||||
/// 2. Send RelayResponse with client's socket_addr
|
||||
/// 3. Connect to RELAY server
|
||||
/// 4. Accept connection without waiting for response
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn rustdesk_relay_key(config: &Arc<RwLock<RustDeskConfig>>) -> String {
|
||||
config.read().relay_key.clone().unwrap_or_default()
|
||||
}
|
||||
|
||||
async fn handle_relay_request(
|
||||
rendezvous_addr: &str,
|
||||
relay_server: &str,
|
||||
@@ -568,16 +466,12 @@ async fn handle_relay_request(
|
||||
device_id: &str,
|
||||
relay_key: &str,
|
||||
connection_manager: Arc<ConnectionManager>,
|
||||
_video_manager: Arc<VideoStreamManager>,
|
||||
_hid: Arc<HidController>,
|
||||
_audio: Arc<AudioController>,
|
||||
) -> anyhow::Result<()> {
|
||||
info!(
|
||||
"Handling relay request: rendezvous={}, relay={}, uuid={}",
|
||||
rendezvous_addr, relay_server, uuid
|
||||
);
|
||||
|
||||
// Step 1: Connect to RENDEZVOUS server and send RelayResponse
|
||||
let rendezvous_socket_addr: SocketAddr = tokio::net::lookup_host(rendezvous_addr)
|
||||
.await?
|
||||
.next()
|
||||
@@ -597,8 +491,7 @@ async fn handle_relay_request(
|
||||
rendezvous_socket_addr
|
||||
);
|
||||
|
||||
// Send RelayResponse to rendezvous server with client's socket_addr
|
||||
// IMPORTANT: Include our device ID so rendezvous server can look up and sign our public key
|
||||
// Rendezvous looks up our pk by device id (must set `id`, not raw pk on wire).
|
||||
let relay_response = make_relay_response(uuid, socket_addr, relay_server, device_id);
|
||||
let bytes = relay_response
|
||||
.write_to_bytes()
|
||||
@@ -606,10 +499,8 @@ async fn handle_relay_request(
|
||||
bytes_codec::write_frame(&mut rendezvous_stream, &bytes).await?;
|
||||
debug!("Sent RelayResponse to rendezvous server for uuid={}", uuid);
|
||||
|
||||
// Close rendezvous connection - we don't need to wait for response
|
||||
drop(rendezvous_stream);
|
||||
|
||||
// Step 2: Connect to RELAY server and send RequestRelay to identify ourselves
|
||||
let relay_addr: SocketAddr = tokio::net::lookup_host(relay_server)
|
||||
.await?
|
||||
.next()
|
||||
@@ -624,9 +515,7 @@ async fn handle_relay_request(
|
||||
|
||||
info!("Connected to relay server at {}", relay_addr);
|
||||
|
||||
// Send RequestRelay to relay server with our uuid, licence_key, and peer's socket_addr
|
||||
// The licence_key is required if the relay server is configured with -k option
|
||||
// The socket_addr is CRITICAL - the relay server uses it to match us with the peer
|
||||
// Relay pairs peers by uuid + mangled peer socket_addr (required when hbbr uses -k).
|
||||
let request_relay = make_request_relay(uuid, relay_key, socket_addr);
|
||||
let bytes = request_relay
|
||||
.write_to_bytes()
|
||||
@@ -634,10 +523,8 @@ async fn handle_relay_request(
|
||||
bytes_codec::write_frame(&mut stream, &bytes).await?;
|
||||
debug!("Sent RequestRelay to relay server for uuid={}", uuid);
|
||||
|
||||
// Decode peer address for logging
|
||||
let peer_addr = rendezvous::AddrMangle::decode(socket_addr).unwrap_or(relay_addr);
|
||||
|
||||
// Step 3: Accept connection - relay server bridges the connection
|
||||
connection_manager
|
||||
.accept_connection(stream, peer_addr)
|
||||
.await?;
|
||||
@@ -649,14 +536,6 @@ async fn handle_relay_request(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle intranet/same-LAN connection request
|
||||
///
|
||||
/// When the server determines that the client and peer are on the same intranet
|
||||
/// (same public IP or both on LAN), it sends FetchLocalAddr to the peer.
|
||||
/// The peer must:
|
||||
/// 1. Open a TCP connection to the rendezvous server
|
||||
/// 2. Send LocalAddr with our local address
|
||||
/// 3. Accept the peer connection over that same TCP stream
|
||||
async fn handle_intranet_request(
|
||||
rendezvous_addr: &str,
|
||||
peer_socket_addr: &[u8],
|
||||
@@ -670,11 +549,9 @@ async fn handle_intranet_request(
|
||||
rendezvous_addr, local_addr, device_id
|
||||
);
|
||||
|
||||
// Decode peer address for logging
|
||||
let peer_addr = AddrMangle::decode(peer_socket_addr);
|
||||
debug!("Peer address from FetchLocalAddr: {:?}", peer_addr);
|
||||
|
||||
// Connect to rendezvous server via TCP with timeout
|
||||
let mut stream =
|
||||
tokio::time::timeout(Duration::from_secs(5), TcpStream::connect(rendezvous_addr))
|
||||
.await
|
||||
@@ -685,7 +562,6 @@ async fn handle_intranet_request(
|
||||
rendezvous_addr
|
||||
);
|
||||
|
||||
// Build LocalAddr message with our local address (mangled)
|
||||
let local_addr_bytes = AddrMangle::encode(local_addr);
|
||||
let msg = make_local_addr(
|
||||
peer_socket_addr,
|
||||
@@ -698,24 +574,16 @@ async fn handle_intranet_request(
|
||||
.write_to_bytes()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to encode: {}", e))?;
|
||||
|
||||
// Send LocalAddr using RustDesk's variable-length framing
|
||||
bytes_codec::write_frame(&mut stream, &bytes).await?;
|
||||
|
||||
info!("Sent LocalAddr to rendezvous server, waiting for peer connection");
|
||||
|
||||
// Now the rendezvous server will forward this to the client,
|
||||
// and the client will connect to us through this same TCP stream.
|
||||
// The server proxies the connection between client and peer.
|
||||
|
||||
// Get peer address for logging/connection tracking
|
||||
let effective_peer_addr = peer_addr.unwrap_or_else(|| {
|
||||
// If we can't decode the peer address, use the rendezvous server address
|
||||
rendezvous_addr
|
||||
.parse()
|
||||
.unwrap_or_else(|_| "0.0.0.0:0".parse().unwrap())
|
||||
});
|
||||
|
||||
// Accept the connection - the stream is now a proxied connection to the client
|
||||
connection_manager
|
||||
.accept_connection(stream, effective_peer_addr)
|
||||
.await?;
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
//! RustDesk Protocol Messages
|
||||
//!
|
||||
//! This module provides the compiled protobuf messages for the RustDesk protocol.
|
||||
//! Messages are generated from rendezvous.proto and message.proto at build time.
|
||||
//! Uses protobuf-rust (same as RustDesk server) for full compatibility.
|
||||
//! Protobuf wrappers (`protos/` → `OUT_DIR`).
|
||||
|
||||
use protobuf::Message;
|
||||
|
||||
// Include the generated protobuf code
|
||||
#[path = ""]
|
||||
pub mod hbb {
|
||||
include!(concat!(env!("OUT_DIR"), "/protos/mod.rs"));
|
||||
}
|
||||
|
||||
// Re-export commonly used types
|
||||
pub use hbb::rendezvous::{
|
||||
punch_hole_response, relay_response, rendezvous_message, ConfigUpdate, ConnType,
|
||||
FetchLocalAddr, HealthCheck, KeyExchange, LocalAddr, NatType, OnlineRequest, OnlineResponse,
|
||||
@@ -21,7 +15,6 @@ pub use hbb::rendezvous::{
|
||||
RequestRelay, SoftwareUpdate, TestNatRequest, TestNatResponse,
|
||||
};
|
||||
|
||||
// Re-export message.proto types
|
||||
pub use hbb::message::{
|
||||
key_event, login_response, message, misc, AudioFormat, AudioFrame, Auth2FA, Clipboard,
|
||||
ControlKey, CursorData, CursorPosition, DisplayInfo, EncodedVideoFrame, EncodedVideoFrames,
|
||||
@@ -30,7 +23,6 @@ pub use hbb::message::{
|
||||
SupportedResolutions, TestDelay, VideoFrame, WindowsSessions,
|
||||
};
|
||||
|
||||
/// Helper to create a RendezvousMessage with RegisterPeer
|
||||
pub fn make_register_peer(id: &str, serial: i32) -> RendezvousMessage {
|
||||
let mut rp = RegisterPeer::new();
|
||||
rp.id = id.to_string();
|
||||
@@ -41,7 +33,6 @@ pub fn make_register_peer(id: &str, serial: i32) -> RendezvousMessage {
|
||||
msg
|
||||
}
|
||||
|
||||
/// Helper to create a RendezvousMessage with RegisterPk
|
||||
pub fn make_register_pk(id: &str, uuid: &[u8], pk: &[u8], old_id: &str) -> RendezvousMessage {
|
||||
let mut rpk = RegisterPk::new();
|
||||
rpk.id = id.to_string();
|
||||
@@ -54,7 +45,6 @@ pub fn make_register_pk(id: &str, uuid: &[u8], pk: &[u8], old_id: &str) -> Rende
|
||||
msg
|
||||
}
|
||||
|
||||
/// Helper to create a PunchHoleSent message
|
||||
pub fn make_punch_hole_sent(
|
||||
socket_addr: &[u8],
|
||||
id: &str,
|
||||
@@ -74,10 +64,7 @@ pub fn make_punch_hole_sent(
|
||||
msg
|
||||
}
|
||||
|
||||
/// Helper to create a RelayResponse message (sent to rendezvous server)
|
||||
/// IMPORTANT: The union field should be `Id` (our device ID), NOT `Pk`.
|
||||
/// The rendezvous server will look up our registered public key using this ID,
|
||||
/// sign it with the server's private key, and set the `pk` field before forwarding to client.
|
||||
/// Use `id` (device id), not raw `pk`; hbbs fills `pk` when forwarding.
|
||||
pub fn make_relay_response(
|
||||
uuid: &str,
|
||||
socket_addr: &[u8],
|
||||
@@ -96,13 +83,7 @@ pub fn make_relay_response(
|
||||
msg
|
||||
}
|
||||
|
||||
/// Helper to create a RequestRelay message (sent to relay server to identify ourselves)
|
||||
///
|
||||
/// The `licence_key` is required if the relay server is configured with a key.
|
||||
/// If the key doesn't match, the relay server will silently reject the connection.
|
||||
///
|
||||
/// IMPORTANT: `socket_addr` is the peer's encoded socket address (from FetchLocalAddr/RelayResponse).
|
||||
/// The relay server uses this to match the two peers connecting to the same relay session.
|
||||
/// `socket_addr` must be the peer's mangled addr; `licence_key` required if hbbr uses `-k`.
|
||||
pub fn make_request_relay(uuid: &str, licence_key: &str, socket_addr: &[u8]) -> RendezvousMessage {
|
||||
let mut rr = RequestRelay::new();
|
||||
rr.uuid = uuid.to_string();
|
||||
@@ -114,8 +95,6 @@ pub fn make_request_relay(uuid: &str, licence_key: &str, socket_addr: &[u8]) ->
|
||||
msg
|
||||
}
|
||||
|
||||
/// Helper to create a LocalAddr response message
|
||||
/// This is sent in response to FetchLocalAddr when a peer on the same LAN wants to connect
|
||||
pub fn make_local_addr(
|
||||
socket_addr: &[u8],
|
||||
local_addr: &[u8],
|
||||
@@ -135,12 +114,10 @@ pub fn make_local_addr(
|
||||
msg
|
||||
}
|
||||
|
||||
/// Decode a RendezvousMessage from bytes
|
||||
pub fn decode_rendezvous_message(buf: &[u8]) -> Result<RendezvousMessage, protobuf::Error> {
|
||||
RendezvousMessage::parse_from_bytes(buf)
|
||||
}
|
||||
|
||||
/// Decode a Message (session message) from bytes
|
||||
pub fn decode_message(buf: &[u8]) -> Result<hbb::message::Message, protobuf::Error> {
|
||||
hbb::message::Message::parse_from_bytes(buf)
|
||||
}
|
||||
|
||||
@@ -1,36 +1,19 @@
|
||||
//! P2P Punch Hole Implementation
|
||||
//!
|
||||
//! This module implements TCP direct connection attempts with relay fallback.
|
||||
//! When a PunchHole request is received, we try to connect directly to the peer.
|
||||
//! If the direct connection fails (timeout), we fall back to relay.
|
||||
//! Direct TCP attempt before relay fallback.
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use tokio::net::TcpStream;
|
||||
use tracing::{debug, info, warn};
|
||||
use tracing::{debug, info};
|
||||
|
||||
use super::connection::ConnectionManager;
|
||||
|
||||
/// Timeout for direct TCP connection attempt
|
||||
const DIRECT_CONNECT_TIMEOUT_MS: u64 = 3000;
|
||||
|
||||
/// Result of a punch hole attempt
|
||||
#[derive(Debug)]
|
||||
pub enum PunchResult {
|
||||
/// Direct connection succeeded
|
||||
DirectConnection(TcpStream),
|
||||
/// Direct connection failed, should use relay
|
||||
NeedRelay,
|
||||
}
|
||||
|
||||
/// Attempt direct TCP connection to peer
|
||||
///
|
||||
/// This is a simplified P2P approach:
|
||||
/// 1. Try to connect directly to the peer's address
|
||||
/// 2. If successful within timeout, use direct connection
|
||||
/// 3. If failed or timeout, fall back to relay
|
||||
pub async fn try_direct_connection(peer_addr: SocketAddr) -> PunchResult {
|
||||
info!("Attempting direct TCP connection to {}", peer_addr);
|
||||
|
||||
@@ -54,76 +37,3 @@ pub async fn try_direct_connection(peer_addr: SocketAddr) -> PunchResult {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Punch hole handler that tries direct connection first, then falls back to relay
|
||||
pub struct PunchHoleHandler {
|
||||
connection_manager: Arc<ConnectionManager>,
|
||||
}
|
||||
|
||||
impl PunchHoleHandler {
|
||||
pub fn new(connection_manager: Arc<ConnectionManager>) -> Self {
|
||||
Self { connection_manager }
|
||||
}
|
||||
|
||||
/// Handle punch hole request
|
||||
///
|
||||
/// Tries direct connection first, falls back to relay if needed.
|
||||
/// Returns true if direct connection succeeded, false if relay is needed.
|
||||
pub async fn handle_punch_hole(&self, peer_addr: Option<SocketAddr>) -> bool {
|
||||
let peer_addr = match peer_addr {
|
||||
Some(addr) => addr,
|
||||
None => {
|
||||
warn!("No peer address available for punch hole");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
match try_direct_connection(peer_addr).await {
|
||||
PunchResult::DirectConnection(stream) => {
|
||||
// Direct connection succeeded, accept it
|
||||
match self
|
||||
.connection_manager
|
||||
.accept_connection(stream, peer_addr)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
info!("P2P direct connection established with {}", peer_addr);
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to accept direct connection: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
PunchResult::NeedRelay => {
|
||||
debug!("Direct connection failed, need relay for {}", peer_addr);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a punch hole attempt with relay fallback
|
||||
///
|
||||
/// This function spawns an async task that:
|
||||
/// 1. Tries direct TCP connection to peer
|
||||
/// 2. If successful, accepts the connection
|
||||
/// 3. If failed, calls the relay callback
|
||||
pub fn spawn_punch_with_fallback<F>(
|
||||
connection_manager: Arc<ConnectionManager>,
|
||||
peer_addr: Option<SocketAddr>,
|
||||
relay_callback: F,
|
||||
) where
|
||||
F: FnOnce() + Send + 'static,
|
||||
{
|
||||
tokio::spawn(async move {
|
||||
let handler = PunchHoleHandler::new(connection_manager);
|
||||
|
||||
if !handler.handle_punch_hole(peer_addr).await {
|
||||
// Direct connection failed, use relay
|
||||
info!("Falling back to relay connection");
|
||||
relay_callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
//! RustDesk Rendezvous Mediator
|
||||
//!
|
||||
//! This module handles communication with the hbbs rendezvous server.
|
||||
//! It registers the device ID and public key, handles punch hole requests,
|
||||
//! and relay requests.
|
||||
//! HBBS UDP registration; punch / relay / intranet callbacks.
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
@@ -24,19 +20,14 @@ use super::protocol::{
|
||||
rendezvous_message, NatType, RendezvousMessage,
|
||||
};
|
||||
|
||||
/// Registration interval in milliseconds
|
||||
const REG_INTERVAL_MS: u64 = 12_000;
|
||||
|
||||
/// Minimum registration timeout
|
||||
const MIN_REG_TIMEOUT_MS: u64 = 3_000;
|
||||
|
||||
/// Maximum registration timeout
|
||||
const MAX_REG_TIMEOUT_MS: u64 = 30_000;
|
||||
|
||||
/// Timer interval for checking registration status
|
||||
const TIMER_INTERVAL_MS: u64 = 300;
|
||||
|
||||
/// Rendezvous mediator status
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum RendezvousStatus {
|
||||
Disconnected,
|
||||
@@ -58,44 +49,13 @@ impl std::fmt::Display for RendezvousStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/// Callback for handling incoming connection requests
|
||||
pub type ConnectionCallback = Arc<dyn Fn(ConnectionRequest) + Send + Sync>;
|
||||
|
||||
/// Incoming connection request from a RustDesk client
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConnectionRequest {
|
||||
/// Peer socket address (encoded)
|
||||
pub socket_addr: Vec<u8>,
|
||||
/// Relay server to use
|
||||
pub relay_server: String,
|
||||
/// NAT type
|
||||
pub nat_type: NatType,
|
||||
/// Connection UUID
|
||||
pub uuid: String,
|
||||
/// Whether to use secure connection
|
||||
pub secure: bool,
|
||||
}
|
||||
|
||||
/// Callback type for relay requests
|
||||
/// Parameters: rendezvous_addr, relay_server, uuid, socket_addr (client's mangled address), device_id
|
||||
pub type RelayCallback = Arc<dyn Fn(String, String, String, Vec<u8>, String) + Send + Sync>;
|
||||
|
||||
/// Callback type for P2P punch hole requests
|
||||
/// Parameters: peer_addr (decoded), relay_callback_params (rendezvous_addr, relay_server, uuid, socket_addr, device_id)
|
||||
/// Returns: should call relay callback if P2P fails
|
||||
pub type PunchCallback =
|
||||
Arc<dyn Fn(Option<SocketAddr>, String, String, String, Vec<u8>, String) + Send + Sync>;
|
||||
|
||||
/// Callback type for intranet/local address connections
|
||||
/// Parameters: rendezvous_addr, peer_socket_addr (mangled), local_addr, relay_server, device_id
|
||||
pub type IntranetCallback = Arc<dyn Fn(String, Vec<u8>, SocketAddr, String, String) + Send + Sync>;
|
||||
|
||||
/// Rendezvous Mediator
|
||||
///
|
||||
/// Handles communication with hbbs rendezvous server:
|
||||
/// - Registers device ID and public key
|
||||
/// - Maintains keep-alive with server
|
||||
/// - Handles punch hole and relay requests
|
||||
pub struct RendezvousMediator {
|
||||
config: Arc<RwLock<RustDeskConfig>>,
|
||||
keypair: Arc<RwLock<Option<KeyPair>>>,
|
||||
@@ -114,11 +74,9 @@ pub struct RendezvousMediator {
|
||||
}
|
||||
|
||||
impl RendezvousMediator {
|
||||
/// Create a new rendezvous mediator
|
||||
pub fn new(mut config: RustDeskConfig) -> Self {
|
||||
let (shutdown_tx, _) = broadcast::channel(1);
|
||||
|
||||
// Get or generate UUID from config (persisted)
|
||||
let (uuid, uuid_needs_save) = config.ensure_uuid();
|
||||
|
||||
Self {
|
||||
@@ -139,88 +97,71 @@ impl RendezvousMediator {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the TCP listen port for direct connections
|
||||
pub fn set_listen_port(&self, port: u16) {
|
||||
let old_port = *self.listen_port.read();
|
||||
if old_port != port {
|
||||
*self.listen_port.write() = port;
|
||||
// Port changed, increment serial to notify server
|
||||
self.increment_serial();
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the TCP listen port
|
||||
pub fn listen_port(&self) -> u16 {
|
||||
*self.listen_port.read()
|
||||
}
|
||||
|
||||
/// Increment the serial number to indicate local state change
|
||||
pub fn increment_serial(&self) {
|
||||
let mut serial = self.serial.write();
|
||||
*serial = serial.wrapping_add(1);
|
||||
debug!("Serial incremented to {}", *serial);
|
||||
}
|
||||
|
||||
/// Get current serial number
|
||||
pub fn serial(&self) -> i32 {
|
||||
*self.serial.read()
|
||||
}
|
||||
|
||||
/// Check if UUID needs to be saved to persistent storage
|
||||
pub fn uuid_needs_save(&self) -> bool {
|
||||
*self.uuid_needs_save.read()
|
||||
}
|
||||
|
||||
/// Get the current config (with UUID set)
|
||||
pub fn config(&self) -> RustDeskConfig {
|
||||
self.config.read().clone()
|
||||
}
|
||||
|
||||
/// Mark UUID as saved
|
||||
pub fn mark_uuid_saved(&self) {
|
||||
*self.uuid_needs_save.write() = false;
|
||||
}
|
||||
|
||||
/// Set the callback for relay requests
|
||||
pub fn set_relay_callback(&self, callback: RelayCallback) {
|
||||
*self.relay_callback.write() = Some(callback);
|
||||
}
|
||||
|
||||
/// Set the callback for P2P punch hole requests
|
||||
pub fn set_punch_callback(&self, callback: PunchCallback) {
|
||||
*self.punch_callback.write() = Some(callback);
|
||||
}
|
||||
|
||||
/// Set the callback for intranet/local address connections
|
||||
pub fn set_intranet_callback(&self, callback: IntranetCallback) {
|
||||
*self.intranet_callback.write() = Some(callback);
|
||||
}
|
||||
|
||||
/// Get current status
|
||||
pub fn status(&self) -> RendezvousStatus {
|
||||
self.status.read().clone()
|
||||
}
|
||||
|
||||
/// Update configuration
|
||||
pub fn update_config(&self, config: RustDeskConfig) {
|
||||
*self.config.write() = config;
|
||||
// Config changed, increment serial to notify server
|
||||
self.increment_serial();
|
||||
}
|
||||
|
||||
/// Initialize or get keypair (Curve25519 for encryption)
|
||||
pub fn ensure_keypair(&self) -> KeyPair {
|
||||
let mut keypair_guard = self.keypair.write();
|
||||
if keypair_guard.is_none() {
|
||||
let config = self.config.read();
|
||||
// Try to load from config first
|
||||
if let (Some(pk), Some(sk)) = (&config.public_key, &config.private_key) {
|
||||
if let Ok(kp) = KeyPair::from_base64(pk, sk) {
|
||||
*keypair_guard = Some(kp.clone());
|
||||
return kp;
|
||||
}
|
||||
}
|
||||
// Generate new keypair
|
||||
let kp = KeyPair::generate();
|
||||
*keypair_guard = Some(kp.clone());
|
||||
kp
|
||||
@@ -229,12 +170,10 @@ impl RendezvousMediator {
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize or get signing keypair (Ed25519 for SignedId)
|
||||
pub fn ensure_signing_keypair(&self) -> SigningKeyPair {
|
||||
let mut signing_guard = self.signing_keypair.write();
|
||||
if signing_guard.is_none() {
|
||||
let config = self.config.read();
|
||||
// Try to load from config first
|
||||
if let (Some(pk), Some(sk)) = (&config.signing_public_key, &config.signing_private_key)
|
||||
{
|
||||
if let Ok(skp) = SigningKeyPair::from_base64(pk, sk) {
|
||||
@@ -245,7 +184,6 @@ impl RendezvousMediator {
|
||||
warn!("Failed to decode signing keypair from config, generating new one");
|
||||
}
|
||||
}
|
||||
// Generate new signing keypair
|
||||
let skp = SigningKeyPair::generate();
|
||||
debug!("Generated new signing keypair");
|
||||
*signing_guard = Some(skp.clone());
|
||||
@@ -255,12 +193,10 @@ impl RendezvousMediator {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the device ID
|
||||
pub fn device_id(&self) -> String {
|
||||
self.config.read().device_id.clone()
|
||||
}
|
||||
|
||||
/// Start the rendezvous mediator
|
||||
pub async fn start(&self) -> anyhow::Result<()> {
|
||||
let config = self.config.read().clone();
|
||||
let effective_server = config.effective_rendezvous_server();
|
||||
@@ -284,13 +220,11 @@ impl RendezvousMediator {
|
||||
config.device_id, addr
|
||||
);
|
||||
|
||||
// Resolve server address
|
||||
let server_addr: SocketAddr = tokio::net::lookup_host(&addr)
|
||||
.await?
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to resolve {}", addr))?;
|
||||
|
||||
// Create UDP socket (match address family, enforce IPV6_V6ONLY)
|
||||
let bind_addr = match server_addr {
|
||||
SocketAddr::V4(_) => SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),
|
||||
SocketAddr::V6(_) => SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 0),
|
||||
@@ -302,11 +236,9 @@ impl RendezvousMediator {
|
||||
info!("Connected to rendezvous server at {}", server_addr);
|
||||
*self.status.write() = RendezvousStatus::Connected;
|
||||
|
||||
// Start registration loop
|
||||
self.registration_loop(socket).await
|
||||
}
|
||||
|
||||
/// Main registration loop
|
||||
async fn registration_loop(&self, socket: UdpSocket) -> anyhow::Result<()> {
|
||||
let mut timer = interval(Duration::from_millis(TIMER_INTERVAL_MS));
|
||||
let mut recv_buf = vec![0u8; 65535];
|
||||
@@ -318,7 +250,6 @@ impl RendezvousMediator {
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
// Handle incoming messages
|
||||
result = socket.recv(&mut recv_buf) => {
|
||||
match result {
|
||||
Ok(len) => {
|
||||
@@ -336,7 +267,6 @@ impl RendezvousMediator {
|
||||
}
|
||||
}
|
||||
|
||||
// Periodic registration
|
||||
_ = timer.tick() => {
|
||||
let now = Instant::now();
|
||||
let expired = last_register_resp
|
||||
@@ -360,7 +290,6 @@ impl RendezvousMediator {
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown signal
|
||||
_ = shutdown_rx.recv() => {
|
||||
info!("Rendezvous mediator shutting down");
|
||||
break;
|
||||
@@ -372,20 +301,16 @@ impl RendezvousMediator {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send registration message
|
||||
async fn send_register(&self, socket: &UdpSocket) -> anyhow::Result<()> {
|
||||
let key_confirmed = *self.key_confirmed.read();
|
||||
|
||||
if !key_confirmed {
|
||||
// Send RegisterPk with public key
|
||||
self.send_register_pk(socket).await
|
||||
} else {
|
||||
// Send RegisterPeer heartbeat
|
||||
self.send_register_peer(socket).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Send RegisterPeer message
|
||||
async fn send_register_peer(&self, socket: &UdpSocket) -> anyhow::Result<()> {
|
||||
let id = self.device_id();
|
||||
let serial = *self.serial.read();
|
||||
@@ -398,12 +323,8 @@ impl RendezvousMediator {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send RegisterPk message
|
||||
/// Uses the Ed25519 signing public key for registration
|
||||
async fn send_register_pk(&self, socket: &UdpSocket) -> anyhow::Result<()> {
|
||||
let id = self.device_id();
|
||||
// Use signing public key (Ed25519) for RegisterPk
|
||||
// This is what clients will use to verify our SignedId signature
|
||||
let signing_keypair = self.ensure_signing_keypair();
|
||||
let pk = signing_keypair.public_key_bytes();
|
||||
let uuid = *self.uuid.read();
|
||||
@@ -417,12 +338,6 @@ impl RendezvousMediator {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle FetchLocalAddr - send to callback for proper TCP handling
|
||||
///
|
||||
/// The intranet callback will:
|
||||
/// 1. Open a TCP connection to the rendezvous server
|
||||
/// 2. Send LocalAddr message
|
||||
/// 3. Accept the peer connection over that same TCP stream
|
||||
async fn send_local_addr(
|
||||
&self,
|
||||
_udp_socket: &UdpSocket,
|
||||
@@ -431,21 +346,17 @@ impl RendezvousMediator {
|
||||
) -> anyhow::Result<()> {
|
||||
let id = self.device_id();
|
||||
|
||||
// Get our actual local IP addresses for same-LAN connection
|
||||
let local_addrs = get_local_addresses();
|
||||
if local_addrs.is_empty() {
|
||||
debug!("No local addresses available for LocalAddr response");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Get the rendezvous server address for TCP connection
|
||||
let config = self.config.read().clone();
|
||||
let rendezvous_addr = config.rendezvous_addr();
|
||||
|
||||
// Use TCP listen port for direct connections
|
||||
let listen_port = self.listen_port();
|
||||
|
||||
// Use the first local IP
|
||||
let local_ip = local_addrs[0];
|
||||
let local_sock_addr = SocketAddr::new(local_ip, listen_port);
|
||||
|
||||
@@ -454,7 +365,6 @@ impl RendezvousMediator {
|
||||
local_sock_addr, rendezvous_addr
|
||||
);
|
||||
|
||||
// Call the intranet callback if set
|
||||
if let Some(callback) = self.intranet_callback.read().as_ref() {
|
||||
callback(
|
||||
rendezvous_addr,
|
||||
@@ -470,7 +380,6 @@ impl RendezvousMediator {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle response from rendezvous server
|
||||
async fn handle_response(
|
||||
&self,
|
||||
socket: &UdpSocket,
|
||||
@@ -486,7 +395,6 @@ impl RendezvousMediator {
|
||||
match msg.union {
|
||||
Some(rendezvous_message::Union::RegisterPeerResponse(rpr)) => {
|
||||
if rpr.request_pk {
|
||||
// Server wants us to register our public key
|
||||
info!("Server requested public key registration");
|
||||
*self.key_confirmed.write() = false;
|
||||
self.send_register_pk(socket).await?;
|
||||
@@ -497,30 +405,24 @@ impl RendezvousMediator {
|
||||
info!("Received RegisterPkResponse: result={:?}", rpr.result);
|
||||
match rpr.result.value() {
|
||||
0 => {
|
||||
// OK
|
||||
info!("✓ Public key registered successfully with server");
|
||||
*self.key_confirmed.write() = true;
|
||||
// Increment serial after successful registration
|
||||
self.increment_serial();
|
||||
*self.status.write() = RendezvousStatus::Registered;
|
||||
}
|
||||
2 => {
|
||||
// UUID_MISMATCH
|
||||
warn!("UUID mismatch, need to re-register");
|
||||
*self.key_confirmed.write() = false;
|
||||
}
|
||||
3 => {
|
||||
// ID_EXISTS
|
||||
error!("Device ID already exists on server");
|
||||
*self.status.write() =
|
||||
RendezvousStatus::Error("Device ID already exists".to_string());
|
||||
}
|
||||
4 => {
|
||||
// TOO_FREQUENT
|
||||
warn!("Registration too frequent");
|
||||
}
|
||||
5 => {
|
||||
// INVALID_ID_FORMAT
|
||||
error!("Invalid device ID format");
|
||||
*self.status.write() =
|
||||
RendezvousStatus::Error("Invalid ID format".to_string());
|
||||
@@ -540,7 +442,6 @@ impl RendezvousMediator {
|
||||
let effective_relay_server =
|
||||
select_relay_server(config.relay_server.as_deref(), &ph.relay_server);
|
||||
|
||||
// Decode the peer's socket address
|
||||
let peer_addr = if !ph.socket_addr.is_empty() {
|
||||
AddrMangle::decode(&ph.socket_addr)
|
||||
} else {
|
||||
@@ -556,9 +457,7 @@ impl RendezvousMediator {
|
||||
ph.nat_type
|
||||
);
|
||||
|
||||
// Send PunchHoleSent to acknowledge
|
||||
// IMPORTANT: socket_addr in PunchHoleSent should be the PEER's address (from PunchHole),
|
||||
// not our own address. This is how RustDesk protocol works.
|
||||
let id = self.device_id();
|
||||
|
||||
info!(
|
||||
@@ -586,16 +485,11 @@ impl RendezvousMediator {
|
||||
info!("Sent PunchHoleSent response successfully");
|
||||
}
|
||||
|
||||
// Try P2P direct connection first, fall back to relay if needed
|
||||
if let Some(relay_server) = effective_relay_server {
|
||||
// Generate a standard UUID v4 for relay pairing
|
||||
// This must match the format used by RustDesk client
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
let rendezvous_addr = config.rendezvous_addr();
|
||||
let device_id = config.device_id.clone();
|
||||
|
||||
// Use punch callback if set (tries P2P first, then relay)
|
||||
// Otherwise fall back to relay callback directly
|
||||
if let Some(callback) = self.punch_callback.read().as_ref() {
|
||||
callback(
|
||||
peer_addr,
|
||||
@@ -630,7 +524,6 @@ impl RendezvousMediator {
|
||||
rr.uuid,
|
||||
rr.secure
|
||||
);
|
||||
// Call the relay callback to handle the connection
|
||||
if let Some(callback) = self.relay_callback.read().as_ref() {
|
||||
if let Some(relay_server) = effective_relay_server {
|
||||
let rendezvous_addr = config.rendezvous_addr();
|
||||
@@ -653,7 +546,6 @@ impl RendezvousMediator {
|
||||
select_relay_server(config.relay_server.as_deref(), &fla.relay_server)
|
||||
.unwrap_or_default();
|
||||
|
||||
// Decode the peer address for logging
|
||||
let peer_addr = AddrMangle::decode(&fla.socket_addr);
|
||||
info!(
|
||||
"Received FetchLocalAddr request: peer_addr={:?}, socket_addr_len={}, relay_server={}, effective_relay_server={}",
|
||||
@@ -662,7 +554,6 @@ impl RendezvousMediator {
|
||||
fla.relay_server,
|
||||
effective_relay_server
|
||||
);
|
||||
// Respond with our local address for same-LAN direct connection
|
||||
self.send_local_addr(socket, &fla.socket_addr, &effective_relay_server)
|
||||
.await?;
|
||||
}
|
||||
@@ -671,7 +562,6 @@ impl RendezvousMediator {
|
||||
*self.serial.write() = cu.serial;
|
||||
}
|
||||
Some(other) => {
|
||||
// Log the actual message type for debugging
|
||||
let type_name = match other {
|
||||
rendezvous_message::Union::PunchHoleRequest(_) => "PunchHoleRequest",
|
||||
rendezvous_message::Union::PunchHoleResponse(_) => "PunchHoleResponse",
|
||||
@@ -696,23 +586,18 @@ impl RendezvousMediator {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stop the rendezvous mediator
|
||||
pub fn stop(&self) {
|
||||
info!("Stopping rendezvous mediator");
|
||||
let _ = self.shutdown_tx.send(());
|
||||
*self.status.write() = RendezvousStatus::Disconnected;
|
||||
}
|
||||
|
||||
/// Get a shutdown receiver
|
||||
pub fn shutdown_rx(&self) -> broadcast::Receiver<()> {
|
||||
self.shutdown_tx.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
/// AddrMangle - RustDesk's address encoding scheme
|
||||
///
|
||||
/// Certain routers and firewalls scan packets and modify IP addresses.
|
||||
/// This encoding mangles the address to avoid detection.
|
||||
/// RustDesk mangled socket encoding.
|
||||
pub struct AddrMangle;
|
||||
|
||||
fn normalize_relay_server(server: &str) -> Option<String> {
|
||||
@@ -735,9 +620,7 @@ fn select_relay_server(local_relay: Option<&str>, server_relay: &str) -> Option<
|
||||
}
|
||||
|
||||
impl AddrMangle {
|
||||
/// Encode a SocketAddr to bytes using RustDesk's mangle algorithm
|
||||
pub fn encode(addr: SocketAddr) -> Vec<u8> {
|
||||
// Try to convert IPv6-mapped IPv4 to plain IPv4
|
||||
let addr = try_into_v4(addr);
|
||||
|
||||
match addr {
|
||||
@@ -753,7 +636,6 @@ impl AddrMangle {
|
||||
let v = ((ip + tm) << 49) | (tm << 17) | (port + (tm & 0xFFFF));
|
||||
let bytes = v.to_le_bytes();
|
||||
|
||||
// Remove trailing zeros
|
||||
let mut n_padding = 0;
|
||||
for i in bytes.iter().rev() {
|
||||
if *i == 0u8 {
|
||||
@@ -774,13 +656,11 @@ impl AddrMangle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode bytes to SocketAddr using RustDesk's mangle algorithm
|
||||
pub fn decode(bytes: &[u8]) -> Option<SocketAddr> {
|
||||
use std::convert::TryInto;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4};
|
||||
|
||||
if bytes.len() > 16 {
|
||||
// IPv6 format: 16 bytes IP + 2 bytes port
|
||||
if bytes.len() != 18 {
|
||||
return None;
|
||||
}
|
||||
@@ -791,7 +671,6 @@ impl AddrMangle {
|
||||
return Some(SocketAddr::new(std::net::IpAddr::V6(ip), port));
|
||||
}
|
||||
|
||||
// IPv4 mangled format
|
||||
let mut padded = [0u8; 16];
|
||||
padded[..bytes.len()].copy_from_slice(bytes);
|
||||
let number = u128::from_le_bytes(padded);
|
||||
@@ -805,7 +684,6 @@ impl AddrMangle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to convert IPv6-mapped IPv4 address to plain IPv4
|
||||
fn try_into_v4(addr: SocketAddr) -> SocketAddr {
|
||||
match addr {
|
||||
SocketAddr::V6(v6) if !addr.ip().is_loopback() => {
|
||||
@@ -818,41 +696,30 @@ fn try_into_v4(addr: SocketAddr) -> SocketAddr {
|
||||
addr
|
||||
}
|
||||
|
||||
/// Check if an interface name belongs to Docker or other virtual networks
|
||||
fn is_virtual_interface(name: &str) -> bool {
|
||||
// Docker interfaces
|
||||
name.starts_with("docker")
|
||||
|| name.starts_with("br-")
|
||||
|| name.starts_with("veth")
|
||||
// Kubernetes/container interfaces
|
||||
|| name.starts_with("cni")
|
||||
|| name.starts_with("flannel")
|
||||
|| name.starts_with("calico")
|
||||
|| name.starts_with("weave")
|
||||
// Virtual bridge interfaces
|
||||
|| name.starts_with("virbr")
|
||||
|| name.starts_with("lxcbr")
|
||||
|| name.starts_with("lxdbr")
|
||||
// VPN interfaces (usually not useful for LAN discovery)
|
||||
|| name.starts_with("tun")
|
||||
|| name.starts_with("tap")
|
||||
}
|
||||
|
||||
/// Check if an IP address is in a Docker/container private range
|
||||
fn is_docker_ip(ip: &std::net::IpAddr) -> bool {
|
||||
if let std::net::IpAddr::V4(ipv4) = ip {
|
||||
let octets = ipv4.octets();
|
||||
// Docker default bridge: 172.17.0.0/16
|
||||
if octets[0] == 172 && octets[1] == 17 {
|
||||
return true;
|
||||
}
|
||||
// Docker user-defined networks: 172.18-31.0.0/16
|
||||
if octets[0] == 172 && (18..=31).contains(&octets[1]) {
|
||||
return true;
|
||||
}
|
||||
// Docker overlay networks: 10.0.0.0/8 (common range)
|
||||
// Note: 10.x.x.x is also used for corporate LANs, so we only filter
|
||||
// specific Docker-like patterns (10.0.x.x with small third octet)
|
||||
if octets[0] == 10 && octets[1] == 0 && octets[2] < 10 {
|
||||
return true;
|
||||
}
|
||||
@@ -860,22 +727,18 @@ fn is_docker_ip(ip: &std::net::IpAddr) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Get local IP addresses (non-loopback, non-Docker)
|
||||
fn get_local_addresses() -> Vec<std::net::IpAddr> {
|
||||
let mut addrs = Vec::new();
|
||||
|
||||
// Use pnet or network-interface crate if available, otherwise use simple method
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if let Ok(interfaces) = std::fs::read_dir("/sys/class/net") {
|
||||
for entry in interfaces.flatten() {
|
||||
let iface_name = entry.file_name().to_string_lossy().to_string();
|
||||
// Skip loopback and virtual interfaces
|
||||
if iface_name == "lo" || is_virtual_interface(&iface_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to get IP via command (simple approach)
|
||||
if let Ok(output) = std::process::Command::new("ip")
|
||||
.args(["-4", "addr", "show", &iface_name])
|
||||
.output()
|
||||
@@ -886,7 +749,6 @@ fn get_local_addresses() -> Vec<std::net::IpAddr> {
|
||||
let ip_part = &line[inet_pos + 5..];
|
||||
if let Some(slash_pos) = ip_part.find('/') {
|
||||
if let Ok(ip) = ip_part[..slash_pos].parse::<std::net::IpAddr>() {
|
||||
// Skip loopback and Docker IPs
|
||||
if !ip.is_loopback() && !is_docker_ip(&ip) {
|
||||
addrs.push(ip);
|
||||
}
|
||||
@@ -899,15 +761,11 @@ fn get_local_addresses() -> Vec<std::net::IpAddr> {
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try to get default route interface IP
|
||||
if addrs.is_empty() {
|
||||
// Try using DNS lookup to get local IP (connects to external server)
|
||||
if let Ok(socket) = std::net::UdpSocket::bind("0.0.0.0:0") {
|
||||
// Connect to a public DNS server (doesn't actually send data)
|
||||
if socket.connect("8.8.8.8:53").is_ok() {
|
||||
if let Ok(local_addr) = socket.local_addr() {
|
||||
let ip = local_addr.ip();
|
||||
// Skip loopback and Docker IPs
|
||||
if !ip.is_loopback() && !is_docker_ip(&ip) {
|
||||
addrs.push(ip);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user