From d1a9b6c3bb089871e12a78c56b7db16497f7a534 Mon Sep 17 00:00:00 2001 From: Pierre Hubert Date: Thu, 7 Dec 2023 00:23:19 +0100 Subject: [PATCH] Fix VNC connection issue --- README.md | 1 + .../src/actors/vnc_tokens_actor.rs | 34 ++++---- .../src/controllers/server_controller.rs | 4 + .../src/controllers/vm_controller.rs | 2 +- virtweb_frontend/src/api/ServerApi.ts | 1 + virtweb_frontend/src/routes/VNCRoute.tsx | 85 +++++++++++-------- virtweb_frontend/src/utils/DateUtils.ts | 6 ++ 7 files changed, 78 insertions(+), 55 deletions(-) create mode 100644 virtweb_frontend/src/utils/DateUtils.ts diff --git a/README.md b/README.md index a09ddf0..edd344d 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ sudo apt install qemu-kvm libvirt-daemon-system 3. Allow the current user to manage VMs: ``` sudo adduser $USER libvirt +sudo adduser $USER kvm ``` > Note: You will need to login again for this change to take effect. diff --git a/virtweb_backend/src/actors/vnc_tokens_actor.rs b/virtweb_backend/src/actors/vnc_tokens_actor.rs index 2d043b8..387a4cb 100644 --- a/virtweb_backend/src/actors/vnc_tokens_actor.rs +++ b/virtweb_backend/src/actors/vnc_tokens_actor.rs @@ -6,14 +6,14 @@ use std::time::Duration; const TOKENS_CLEAN_INTERVAL: Duration = Duration::from_secs(60); const VNC_TOKEN_LEN: usize = 15; -const VNC_TOKEN_LIFETIME: u64 = 120; +pub const VNC_TOKEN_LIFETIME: u64 = 30; #[derive(thiserror::Error, Debug)] enum VNCTokenError { - #[error("Could not consume token, because it does not exist!")] - ConsumeErrorTokenNotFound, - #[error("Could not consume token, because it has expired!")] - ConsumeErrorTokenExpired, + #[error("Could not use token, because it does not exist!")] + UseErrorTokenNotFound, + #[error("Could not use token, because it has expired!")] + UseErrorTokenExpired, } #[derive(Debug, Clone)] @@ -64,24 +64,22 @@ impl Handler for VNCTokensActor { #[derive(Message)] #[rtype(result = "anyhow::Result")] -pub struct ConsumeTokenReq(String); +pub struct UseTokenReq(String); -impl Handler for VNCTokensActor { +impl Handler for VNCTokensActor { type Result = anyhow::Result; - fn handle(&mut self, msg: ConsumeTokenReq, _ctx: &mut Self::Context) -> Self::Result { - log::debug!("Attempt to consume a token {:?}", msg.0); + fn handle(&mut self, msg: UseTokenReq, _ctx: &mut Self::Context) -> Self::Result { + log::debug!("Attempt to use a token {:?}", msg.0); - let token_index = self + let token = self .0 .iter() - .position(|i| i.token == msg.0) - .ok_or(VNCTokenError::ConsumeErrorTokenNotFound)?; - - let token = self.0.remove(token_index); + .find(|i| i.token == msg.0) + .ok_or(VNCTokenError::UseErrorTokenNotFound)?; if token.is_expired() { - return Err(VNCTokenError::ConsumeErrorTokenExpired.into()); + return Err(VNCTokenError::UseErrorTokenExpired.into()); } Ok(token.vm) @@ -101,8 +99,8 @@ impl VNCTokensManager { self.0.send(IssueTokenReq(id)).await? } - /// Consume a VNC access token - pub async fn consume_token(&self, token: String) -> anyhow::Result { - self.0.send(ConsumeTokenReq(token)).await? + /// Use a VNC access token + pub async fn use_token(&self, token: String) -> anyhow::Result { + self.0.send(UseTokenReq(token)).await? } } diff --git a/virtweb_backend/src/controllers/server_controller.rs b/virtweb_backend/src/controllers/server_controller.rs index 84cdb53..d53440b 100644 --- a/virtweb_backend/src/controllers/server_controller.rs +++ b/virtweb_backend/src/controllers/server_controller.rs @@ -1,3 +1,4 @@ +use crate::actors::vnc_tokens_actor::VNC_TOKEN_LIFETIME; use crate::app_config::AppConfig; use crate::constants; use crate::constants::{DISK_NAME_MAX_LEN, DISK_NAME_MIN_LEN, DISK_SIZE_MAX, DISK_SIZE_MIN}; @@ -29,6 +30,7 @@ struct LenConstraints { #[derive(serde::Serialize)] struct ServerConstraints { iso_max_size: usize, + vnc_token_duration: u64, vm_name_size: LenConstraints, vm_title_size: LenConstraints, memory_size: LenConstraints, @@ -47,6 +49,8 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder { constraints: ServerConstraints { iso_max_size: constants::ISO_MAX_SIZE, + vnc_token_duration: VNC_TOKEN_LIFETIME, + vm_name_size: LenConstraints { min: 2, max: 50 }, vm_title_size: LenConstraints { min: 0, max: 50 }, memory_size: LenConstraints { diff --git a/virtweb_backend/src/controllers/vm_controller.rs b/virtweb_backend/src/controllers/vm_controller.rs index 1538936..78be424 100644 --- a/virtweb_backend/src/controllers/vm_controller.rs +++ b/virtweb_backend/src/controllers/vm_controller.rs @@ -269,7 +269,7 @@ pub async fn vnc( req: HttpRequest, stream: web::Payload, ) -> HttpResult { - let domain_id = manager.consume_token(token.0.token).await?; + let domain_id = manager.use_token(token.0.token).await?; let domain = client.get_single_domain(domain_id).await?; let socket_path = match domain.devices.graphics { diff --git a/virtweb_frontend/src/api/ServerApi.ts b/virtweb_frontend/src/api/ServerApi.ts index afc541d..6b84f03 100644 --- a/virtweb_frontend/src/api/ServerApi.ts +++ b/virtweb_frontend/src/api/ServerApi.ts @@ -10,6 +10,7 @@ export interface ServerConfig { export interface ServerConstraints { iso_max_size: number; + vnc_token_duration: number; vm_name_size: LenConstraint; vm_title_size: LenConstraint; memory_size: LenConstraint; diff --git a/virtweb_frontend/src/routes/VNCRoute.tsx b/virtweb_frontend/src/routes/VNCRoute.tsx index ef76976..c345a41 100644 --- a/virtweb_frontend/src/routes/VNCRoute.tsx +++ b/virtweb_frontend/src/routes/VNCRoute.tsx @@ -1,10 +1,17 @@ -import React from "react"; +import React, { useEffect } from "react"; import { useParams } from "react-router-dom"; import { VncScreen } from "react-vnc"; +import { ServerApi } from "../api/ServerApi"; import { VMApi, VMInfo } from "../api/VMApi"; import { useSnackbar } from "../hooks/providers/SnackbarProvider"; +import { time } from "../utils/DateUtils"; import { AsyncWidget } from "../widgets/AsyncWidget"; +interface VNCTokenInfo { + url: string; + expire: number; +} + export function VNCRoute(): React.ReactElement { const { uuid } = useParams(); @@ -27,52 +34,58 @@ export function VNCRoute(): React.ReactElement { function VNCInner(p: { vm: VMInfo }): React.ReactElement { const snackbar = useSnackbar(); - const counter = React.useRef(false); - const [url, setURL] = React.useState(); + const [token, setToken] = React.useState(); + const [counter, setCounter] = React.useState(1); - const load = async () => { + const connect = async (force: boolean) => { try { + if (force) setCounter(counter + 1); + + // Check if getting new time is useless + if ((token?.expire ?? 0) > time()) return; + + setToken(undefined); + const u = await VMApi.OneShotVNCURL(p.vm); - console.info(u); - if (counter.current === false) { - counter.current = true; - setURL(u); - } + + if (!token) + setToken({ + expire: time() + ServerApi.Config.constraints.vnc_token_duration, + url: u, + }); } catch (e) { console.error(e); snackbar("Failed to initialize VNC connection!"); } }; - const reconnect = () => { - counter.current = false; - setURL(undefined); + const disconnected = () => { + connect(true); }; + useEffect(() => { + connect(false); + }); + + if (token === undefined) + return

Please wait, connecting to the machine...

; + return ( - ( -
- { - console.info("VNC disconnected " + url); - reconnect(); - }} - /> -
- )} - /> +
+ { + console.info("VNC disconnected " + token?.url); + disconnected(); + }} + /> +
); } diff --git a/virtweb_frontend/src/utils/DateUtils.ts b/virtweb_frontend/src/utils/DateUtils.ts new file mode 100644 index 0000000..1b74dea --- /dev/null +++ b/virtweb_frontend/src/utils/DateUtils.ts @@ -0,0 +1,6 @@ +/** + * Get current UNIX time, in seconds + */ +export function time(): number { + return Math.floor(new Date().getTime() / 1000); +}