Files
One-KVM/src/vnc/rfb.rs
2026-06-15 22:25:18 +08:00

530 lines
17 KiB
Rust

use std::net::SocketAddr;
use bytes::Bytes;
use des::cipher::{BlockEncrypt, KeyInit};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::sync::broadcast;
use crate::config::{VncConfig, VncEncoding};
use crate::error::{AppError, Result};
use crate::hid::{
CanonicalKey, KeyEventType, KeyboardEvent, KeyboardModifiers, MouseButton, MouseEvent,
MouseEventType,
};
const ENCODING_TIGHT: i32 = 7;
const ENCODING_H264: i32 = 50;
const ENCODING_DESKTOP_SIZE: i32 = -223;
pub enum RfbFrame {
Jpeg {
data: Bytes,
width: u16,
height: u16,
},
H264 {
data: Bytes,
width: u16,
height: u16,
key: bool,
},
}
pub enum RfbInputEvent {
Key(RfbKeyEvent),
Pointer(RfbPointerEvent),
Clipboard(String),
Ignored,
Disconnected,
}
pub struct RfbKeyEvent {
pub down: bool,
pub keysym: u32,
}
pub struct RfbPointerEvent {
pub x: u16,
pub y: u16,
pub button_mask: u8,
pub previous_button_mask: u8,
}
#[derive(Default)]
struct ClientEncodings {
has_tight: bool,
tight_jpeg_quality: u8,
has_h264: bool,
has_resize: bool,
}
pub struct RfbClient {
stream: TcpStream,
peer: SocketAddr,
config: VncConfig,
encodings: ClientEncodings,
width: u16,
height: u16,
last_buttons: u8,
h264_waiting_keyframe: bool,
shutdown_tx: broadcast::Sender<()>,
}
impl RfbClient {
pub fn new(stream: TcpStream, peer: SocketAddr, config: VncConfig) -> Self {
let (shutdown_tx, _) = broadcast::channel(1);
Self {
stream,
peer,
config,
encodings: ClientEncodings::default(),
width: 800,
height: 600,
last_buttons: 0,
h264_waiting_keyframe: true,
shutdown_tx,
}
}
pub fn set_size(&mut self, width: u16, height: u16) {
self.width = width.max(1);
self.height = height.max(1);
}
pub fn shutdown_receiver(&self) -> broadcast::Receiver<()> {
self.shutdown_tx.subscribe()
}
pub async fn handshake(&mut self) -> Result<()> {
self.stream.write_all(b"RFB 003.008\n").await?;
let mut version = [0u8; 12];
self.stream.read_exact(&mut version).await?;
if !version.starts_with(b"RFB 003.00") {
return Err(AppError::BadRequest("Invalid RFB version".to_string()));
}
self.stream.write_all(&[1, 2]).await?;
let sec_type = read_u8(&mut self.stream).await?;
if sec_type != 2 {
return Err(AppError::BadRequest("VNCAuth is required".to_string()));
}
self.handle_vnc_auth().await?;
let _shared = read_u8(&mut self.stream).await?;
self.write_server_init().await?;
self.read_until_set_encodings().await?;
self.validate_encoding_policy()?;
tracing::info!(
"VNC client {} negotiated encoding {:?}",
self.peer,
self.config.encoding
);
Ok(())
}
async fn handle_vnc_auth(&mut self) -> Result<()> {
let challenge: [u8; 16] = rand::random();
self.stream.write_all(&challenge).await?;
let mut response = [0u8; 16];
self.stream.read_exact(&mut response).await?;
let password = self.config.password.as_deref().unwrap_or("");
let expected = encrypt_vnc_challenge(&challenge, password)?;
let ok = response == expected;
self.stream
.write_all(&(if ok { 0u32 } else { 1u32 }).to_be_bytes())
.await?;
if !ok {
return Err(AppError::BadRequest("Invalid VNC password".to_string()));
}
Ok(())
}
async fn write_server_init(&mut self) -> Result<()> {
self.stream.write_all(&self.width.to_be_bytes()).await?;
self.stream.write_all(&self.height.to_be_bytes()).await?;
self.stream
.write_all(&[32, 24, 0, 1, 0, 255, 0, 255, 0, 255, 16, 8, 0, 0, 0, 0])
.await?;
let name = b"One-KVM VNC";
self.stream
.write_all(&(name.len() as u32).to_be_bytes())
.await?;
self.stream.write_all(name).await?;
self.stream.flush().await?;
Ok(())
}
async fn read_until_set_encodings(&mut self) -> Result<()> {
loop {
let msg_type = read_u8(&mut self.stream).await?;
match msg_type {
0 => {
let mut buf = [0u8; 19];
self.stream.read_exact(&mut buf).await?;
}
2 => {
let _pad = read_u8(&mut self.stream).await?;
let count = read_u16(&mut self.stream).await?;
if count == 0 || count > 1024 {
return Err(AppError::BadRequest(
"Invalid VNC encoding list".to_string(),
));
}
let mut encodings = ClientEncodings::default();
for _ in 0..count {
let enc = read_i32(&mut self.stream).await?;
match enc {
ENCODING_TIGHT => encodings.has_tight = true,
ENCODING_H264 => encodings.has_h264 = true,
ENCODING_DESKTOP_SIZE => encodings.has_resize = true,
-32..=-23 => {
let q = ((enc + 33) * 10).clamp(10, 100) as u8;
encodings.tight_jpeg_quality = encodings.tight_jpeg_quality.max(q);
}
_ => {}
}
}
self.encodings = encodings;
return Ok(());
}
3 => {
let mut buf = [0u8; 9];
self.stream.read_exact(&mut buf).await?;
}
4 => {
let mut buf = [0u8; 7];
self.stream.read_exact(&mut buf).await?;
}
5 => {
let mut buf = [0u8; 5];
self.stream.read_exact(&mut buf).await?;
}
6 => {
let mut hdr = [0u8; 7];
self.stream.read_exact(&mut hdr).await?;
let len = u32::from_be_bytes([hdr[3], hdr[4], hdr[5], hdr[6]]) as usize;
let mut data = vec![0u8; len.min(1024 * 1024)];
self.stream.read_exact(&mut data).await?;
}
_ => {
return Err(AppError::BadRequest(format!(
"Unsupported RFB message {}",
msg_type
)))
}
}
}
}
fn validate_encoding_policy(&self) -> Result<()> {
match self.config.encoding {
VncEncoding::TightJpeg => {
if !self.encodings.has_tight || self.encodings.tight_jpeg_quality == 0 {
return Err(AppError::BadRequest(
"VNC client must support Tight JPEG encoding".to_string(),
));
}
}
VncEncoding::H264 => {
if !self.encodings.has_h264 {
return Err(AppError::BadRequest(
"VNC client must support Open H.264 encoding".to_string(),
));
}
}
}
Ok(())
}
pub async fn read_input_event(&mut self) -> Result<RfbInputEvent> {
let msg_type = match read_u8(&mut self.stream).await {
Ok(v) => v,
Err(AppError::Io(err)) if err.kind() == std::io::ErrorKind::UnexpectedEof => {
return Ok(RfbInputEvent::Disconnected);
}
Err(err) => return Err(err),
};
match msg_type {
0 => {
let mut buf = [0u8; 19];
self.stream.read_exact(&mut buf).await?;
Ok(RfbInputEvent::Ignored)
}
2 => {
let _pad = read_u8(&mut self.stream).await?;
let count = read_u16(&mut self.stream).await?;
for _ in 0..count {
let _ = read_i32(&mut self.stream).await?;
}
Ok(RfbInputEvent::Ignored)
}
3 => {
let mut buf = [0u8; 9];
self.stream.read_exact(&mut buf).await?;
Ok(RfbInputEvent::Ignored)
}
4 => {
let down = read_u8(&mut self.stream).await? != 0;
let mut pad = [0u8; 2];
self.stream.read_exact(&mut pad).await?;
let keysym = read_u32(&mut self.stream).await?;
Ok(RfbInputEvent::Key(RfbKeyEvent { down, keysym }))
}
5 => {
let button_mask = read_u8(&mut self.stream).await?;
let x = read_u16(&mut self.stream).await?;
let y = read_u16(&mut self.stream).await?;
let previous_button_mask = self.last_buttons;
self.last_buttons = button_mask;
Ok(RfbInputEvent::Pointer(RfbPointerEvent {
x,
y,
button_mask,
previous_button_mask,
}))
}
6 => {
let mut hdr = [0u8; 7];
self.stream.read_exact(&mut hdr).await?;
let len = u32::from_be_bytes([hdr[3], hdr[4], hdr[5], hdr[6]]) as usize;
let mut data = vec![0u8; len.min(1024 * 1024)];
self.stream.read_exact(&mut data).await?;
Ok(RfbInputEvent::Clipboard(
String::from_utf8_lossy(&data).to_string(),
))
}
_ => Err(AppError::BadRequest(format!(
"Unsupported RFB message {}",
msg_type
))),
}
}
pub async fn send_frame(&mut self, frame: RfbFrame) -> Result<()> {
match frame {
RfbFrame::Jpeg {
data,
width,
height,
} => {
self.maybe_resize(width, height).await?;
self.write_frame_header(width, height, ENCODING_TIGHT)
.await?;
write_tight_jpeg_payload(&mut self.stream, &data).await?;
}
RfbFrame::H264 {
data,
width,
height,
key,
} => {
self.maybe_resize(width, height).await?;
if self.h264_waiting_keyframe && !key {
return Ok(());
}
self.write_frame_header(width, height, ENCODING_H264)
.await?;
self.stream
.write_all(&(data.len() as u32).to_be_bytes())
.await?;
self.stream
.write_all(&(self.h264_waiting_keyframe as u32).to_be_bytes())
.await?;
self.stream.write_all(&data).await?;
self.h264_waiting_keyframe = false;
}
}
self.stream.flush().await?;
Ok(())
}
async fn maybe_resize(&mut self, width: u16, height: u16) -> Result<()> {
if width == self.width && height == self.height {
return Ok(());
}
if !self.encodings.has_resize {
return Err(AppError::BadRequest(
"VNC client does not support DesktopSize resize; reconnect required".to_string(),
));
}
self.write_frame_header(width, height, ENCODING_DESKTOP_SIZE)
.await?;
self.width = width;
self.height = height;
self.h264_waiting_keyframe = true;
Ok(())
}
async fn write_frame_header(&mut self, width: u16, height: u16, encoding: i32) -> Result<()> {
self.stream.write_all(&[0, 0]).await?;
self.stream.write_all(&1u16.to_be_bytes()).await?;
self.stream.write_all(&0u16.to_be_bytes()).await?;
self.stream.write_all(&0u16.to_be_bytes()).await?;
self.stream.write_all(&width.to_be_bytes()).await?;
self.stream.write_all(&height.to_be_bytes()).await?;
self.stream.write_all(&encoding.to_be_bytes()).await?;
Ok(())
}
}
async fn write_tight_jpeg_payload(stream: &mut TcpStream, data: &[u8]) -> Result<()> {
if data.len() > 0x3f_ffff {
return Err(AppError::BadRequest(
"JPEG frame too large for Tight encoding".to_string(),
));
}
stream.write_all(&[0b1001_1111]).await?;
write_compact_len(stream, data.len()).await?;
stream.write_all(data).await?;
Ok(())
}
async fn write_compact_len(stream: &mut TcpStream, len: usize) -> Result<()> {
if len <= 127 {
stream.write_all(&[(len & 0x7f) as u8]).await?;
} else if len <= 16_383 {
stream
.write_all(&[((len & 0x7f) as u8) | 0x80, ((len >> 7) & 0x7f) as u8])
.await?;
} else {
stream
.write_all(&[
((len & 0x7f) as u8) | 0x80,
(((len >> 7) & 0x7f) as u8) | 0x80,
((len >> 14) & 0xff) as u8,
])
.await?;
}
Ok(())
}
fn encrypt_vnc_challenge(challenge: &[u8; 16], password: &str) -> Result<[u8; 16]> {
let mut key = [0u8; 8];
for (dst, src) in key.iter_mut().zip(password.as_bytes().iter().take(8)) {
*dst = reverse_bits(*src);
}
let cipher = des::Des::new_from_slice(&key)
.map_err(|_| AppError::BadRequest("Invalid VNC DES key".to_string()))?;
let mut out = *challenge;
for chunk in out.chunks_exact_mut(8) {
cipher.encrypt_block(chunk.into());
}
Ok(out)
}
fn reverse_bits(byte: u8) -> u8 {
byte.reverse_bits()
}
async fn read_u8(stream: &mut TcpStream) -> Result<u8> {
let mut buf = [0u8; 1];
stream.read_exact(&mut buf).await?;
Ok(buf[0])
}
async fn read_u16(stream: &mut TcpStream) -> Result<u16> {
let mut buf = [0u8; 2];
stream.read_exact(&mut buf).await?;
Ok(u16::from_be_bytes(buf))
}
async fn read_u32(stream: &mut TcpStream) -> Result<u32> {
let mut buf = [0u8; 4];
stream.read_exact(&mut buf).await?;
Ok(u32::from_be_bytes(buf))
}
async fn read_i32(stream: &mut TcpStream) -> Result<i32> {
let mut buf = [0u8; 4];
stream.read_exact(&mut buf).await?;
Ok(i32::from_be_bytes(buf))
}
pub fn key_event_to_hid(event: RfbKeyEvent) -> Option<KeyboardEvent> {
let key = keysym_to_key(event.keysym)?;
Some(KeyboardEvent {
event_type: if event.down {
KeyEventType::Down
} else {
KeyEventType::Up
},
key,
modifiers: KeyboardModifiers::default(),
})
}
fn keysym_to_key(keysym: u32) -> Option<CanonicalKey> {
match keysym {
0xff08 => Some(CanonicalKey::Backspace),
0xff09 => Some(CanonicalKey::Tab),
0xff0d => Some(CanonicalKey::Enter),
0xff1b => Some(CanonicalKey::Escape),
0xffff => Some(CanonicalKey::Delete),
0xff50 => Some(CanonicalKey::Home),
0xff51 => Some(CanonicalKey::ArrowLeft),
0xff52 => Some(CanonicalKey::ArrowUp),
0xff53 => Some(CanonicalKey::ArrowRight),
0xff54 => Some(CanonicalKey::ArrowDown),
0xff55 => Some(CanonicalKey::PageUp),
0xff56 => Some(CanonicalKey::PageDown),
0xff57 => Some(CanonicalKey::End),
0xff63 => Some(CanonicalKey::Insert),
0xffbe..=0xffc9 => CanonicalKey::from_hid_usage((keysym - 0xffbe + 0x3a) as u8),
0x20 => Some(CanonicalKey::Space),
0x61..=0x7a => CanonicalKey::from_hid_usage((keysym - 0x61 + 0x04) as u8),
0x41..=0x5a => CanonicalKey::from_hid_usage((keysym - 0x41 + 0x04) as u8),
0x31..=0x39 => CanonicalKey::from_hid_usage((keysym - 0x31 + 0x1e) as u8),
0x30 => Some(CanonicalKey::Digit0),
0x2d => Some(CanonicalKey::Minus),
0x3d => Some(CanonicalKey::Equal),
0x5b => Some(CanonicalKey::BracketLeft),
0x5d => Some(CanonicalKey::BracketRight),
0x5c => Some(CanonicalKey::Backslash),
0x3b => Some(CanonicalKey::Semicolon),
0x27 => Some(CanonicalKey::Quote),
0x60 => Some(CanonicalKey::Backquote),
0x2c => Some(CanonicalKey::Comma),
0x2e => Some(CanonicalKey::Period),
0x2f => Some(CanonicalKey::Slash),
_ => None,
}
}
pub fn pointer_event_to_hid(event: RfbPointerEvent, width: u16, height: u16) -> Vec<MouseEvent> {
let mut out = Vec::new();
let abs_x = ((event.x as u64 * 32767) / width.max(1) as u64) as i32;
let abs_y = ((event.y as u64 * 32767) / height.max(1) as u64) as i32;
out.push(MouseEvent {
event_type: MouseEventType::MoveAbs,
x: abs_x,
y: abs_y,
button: None,
scroll: 0,
});
if event.button_mask & 0x08 != 0 {
out.push(MouseEvent::scroll(1));
}
if event.button_mask & 0x10 != 0 {
out.push(MouseEvent::scroll(-1));
}
for (bit, button) in [
(0x01, MouseButton::Left),
(0x02, MouseButton::Middle),
(0x04, MouseButton::Right),
] {
if (event.button_mask ^ event.previous_button_mask) & bit == 0 {
continue;
}
if event.button_mask & bit != 0 {
out.push(MouseEvent::button_down(button));
} else {
out.push(MouseEvent::button_up(button));
}
}
out
}