Fix VNC connection issue
This commit is contained in:
		@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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<IssueTokenReq> for VNCTokensActor {
 | 
			
		||||
 | 
			
		||||
#[derive(Message)]
 | 
			
		||||
#[rtype(result = "anyhow::Result<XMLUuid>")]
 | 
			
		||||
pub struct ConsumeTokenReq(String);
 | 
			
		||||
pub struct UseTokenReq(String);
 | 
			
		||||
 | 
			
		||||
impl Handler<ConsumeTokenReq> for VNCTokensActor {
 | 
			
		||||
impl Handler<UseTokenReq> for VNCTokensActor {
 | 
			
		||||
    type Result = anyhow::Result<XMLUuid>;
 | 
			
		||||
 | 
			
		||||
    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<XMLUuid> {
 | 
			
		||||
        self.0.send(ConsumeTokenReq(token)).await?
 | 
			
		||||
    /// Use a VNC access token
 | 
			
		||||
    pub async fn use_token(&self, token: String) -> anyhow::Result<XMLUuid> {
 | 
			
		||||
        self.0.send(UseTokenReq(token)).await?
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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<string | undefined>();
 | 
			
		||||
  const [token, setToken] = React.useState<VNCTokenInfo | undefined>();
 | 
			
		||||
  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 <p>Please wait, connecting to the machine...</p>;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <AsyncWidget
 | 
			
		||||
      loadKey={counter.current}
 | 
			
		||||
      load={load}
 | 
			
		||||
      ready={url !== undefined && counter.current}
 | 
			
		||||
      errMsg="Failed to get a token to initialize VNC connection!"
 | 
			
		||||
      build={() => (
 | 
			
		||||
        <div
 | 
			
		||||
          style={{
 | 
			
		||||
            display: "flex",
 | 
			
		||||
            alignItems: "center",
 | 
			
		||||
            justifyContent: "center",
 | 
			
		||||
            height: "100%",
 | 
			
		||||
          }}
 | 
			
		||||
        >
 | 
			
		||||
          <VncScreen
 | 
			
		||||
            url={url!}
 | 
			
		||||
            onDisconnect={() => {
 | 
			
		||||
              console.info("VNC disconnected " + url);
 | 
			
		||||
              reconnect();
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      )}
 | 
			
		||||
    />
 | 
			
		||||
    <div
 | 
			
		||||
      style={{
 | 
			
		||||
        display: "flex",
 | 
			
		||||
        alignItems: "center",
 | 
			
		||||
        justifyContent: "center",
 | 
			
		||||
        height: "100%",
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <VncScreen
 | 
			
		||||
        url={token!.url}
 | 
			
		||||
        onDisconnect={() => {
 | 
			
		||||
          console.info("VNC disconnected " + token?.url);
 | 
			
		||||
          disconnected();
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								virtweb_frontend/src/utils/DateUtils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								virtweb_frontend/src/utils/DateUtils.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Get current UNIX time, in seconds
 | 
			
		||||
 */
 | 
			
		||||
export function time(): number {
 | 
			
		||||
  return Math.floor(new Date().getTime() / 1000);
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user