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:
 | 
					3. Allow the current user to manage VMs:
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
sudo adduser $USER libvirt
 | 
					sudo adduser $USER libvirt
 | 
				
			||||||
 | 
					sudo adduser $USER kvm 
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> Note: You will need to login again for this change to take effect.
 | 
					> 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 TOKENS_CLEAN_INTERVAL: Duration = Duration::from_secs(60);
 | 
				
			||||||
const VNC_TOKEN_LEN: usize = 15;
 | 
					const VNC_TOKEN_LEN: usize = 15;
 | 
				
			||||||
const VNC_TOKEN_LIFETIME: u64 = 120;
 | 
					pub const VNC_TOKEN_LIFETIME: u64 = 30;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(thiserror::Error, Debug)]
 | 
					#[derive(thiserror::Error, Debug)]
 | 
				
			||||||
enum VNCTokenError {
 | 
					enum VNCTokenError {
 | 
				
			||||||
    #[error("Could not consume token, because it does not exist!")]
 | 
					    #[error("Could not use token, because it does not exist!")]
 | 
				
			||||||
    ConsumeErrorTokenNotFound,
 | 
					    UseErrorTokenNotFound,
 | 
				
			||||||
    #[error("Could not consume token, because it has expired!")]
 | 
					    #[error("Could not use token, because it has expired!")]
 | 
				
			||||||
    ConsumeErrorTokenExpired,
 | 
					    UseErrorTokenExpired,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone)]
 | 
					#[derive(Debug, Clone)]
 | 
				
			||||||
@@ -64,24 +64,22 @@ impl Handler<IssueTokenReq> for VNCTokensActor {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[derive(Message)]
 | 
					#[derive(Message)]
 | 
				
			||||||
#[rtype(result = "anyhow::Result<XMLUuid>")]
 | 
					#[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>;
 | 
					    type Result = anyhow::Result<XMLUuid>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn handle(&mut self, msg: ConsumeTokenReq, _ctx: &mut Self::Context) -> Self::Result {
 | 
					    fn handle(&mut self, msg: UseTokenReq, _ctx: &mut Self::Context) -> Self::Result {
 | 
				
			||||||
        log::debug!("Attempt to consume a token {:?}", msg.0);
 | 
					        log::debug!("Attempt to use a token {:?}", msg.0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let token_index = self
 | 
					        let token = self
 | 
				
			||||||
            .0
 | 
					            .0
 | 
				
			||||||
            .iter()
 | 
					            .iter()
 | 
				
			||||||
            .position(|i| i.token == msg.0)
 | 
					            .find(|i| i.token == msg.0)
 | 
				
			||||||
            .ok_or(VNCTokenError::ConsumeErrorTokenNotFound)?;
 | 
					            .ok_or(VNCTokenError::UseErrorTokenNotFound)?;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let token = self.0.remove(token_index);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if token.is_expired() {
 | 
					        if token.is_expired() {
 | 
				
			||||||
            return Err(VNCTokenError::ConsumeErrorTokenExpired.into());
 | 
					            return Err(VNCTokenError::UseErrorTokenExpired.into());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(token.vm)
 | 
					        Ok(token.vm)
 | 
				
			||||||
@@ -101,8 +99,8 @@ impl VNCTokensManager {
 | 
				
			|||||||
        self.0.send(IssueTokenReq(id)).await?
 | 
					        self.0.send(IssueTokenReq(id)).await?
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Consume a VNC access token
 | 
					    /// Use a VNC access token
 | 
				
			||||||
    pub async fn consume_token(&self, token: String) -> anyhow::Result<XMLUuid> {
 | 
					    pub async fn use_token(&self, token: String) -> anyhow::Result<XMLUuid> {
 | 
				
			||||||
        self.0.send(ConsumeTokenReq(token)).await?
 | 
					        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::app_config::AppConfig;
 | 
				
			||||||
use crate::constants;
 | 
					use crate::constants;
 | 
				
			||||||
use crate::constants::{DISK_NAME_MAX_LEN, DISK_NAME_MIN_LEN, DISK_SIZE_MAX, DISK_SIZE_MIN};
 | 
					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)]
 | 
					#[derive(serde::Serialize)]
 | 
				
			||||||
struct ServerConstraints {
 | 
					struct ServerConstraints {
 | 
				
			||||||
    iso_max_size: usize,
 | 
					    iso_max_size: usize,
 | 
				
			||||||
 | 
					    vnc_token_duration: u64,
 | 
				
			||||||
    vm_name_size: LenConstraints,
 | 
					    vm_name_size: LenConstraints,
 | 
				
			||||||
    vm_title_size: LenConstraints,
 | 
					    vm_title_size: LenConstraints,
 | 
				
			||||||
    memory_size: LenConstraints,
 | 
					    memory_size: LenConstraints,
 | 
				
			||||||
@@ -47,6 +49,8 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
 | 
				
			|||||||
        constraints: ServerConstraints {
 | 
					        constraints: ServerConstraints {
 | 
				
			||||||
            iso_max_size: constants::ISO_MAX_SIZE,
 | 
					            iso_max_size: constants::ISO_MAX_SIZE,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            vnc_token_duration: VNC_TOKEN_LIFETIME,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            vm_name_size: LenConstraints { min: 2, max: 50 },
 | 
					            vm_name_size: LenConstraints { min: 2, max: 50 },
 | 
				
			||||||
            vm_title_size: LenConstraints { min: 0, max: 50 },
 | 
					            vm_title_size: LenConstraints { min: 0, max: 50 },
 | 
				
			||||||
            memory_size: LenConstraints {
 | 
					            memory_size: LenConstraints {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -269,7 +269,7 @@ pub async fn vnc(
 | 
				
			|||||||
    req: HttpRequest,
 | 
					    req: HttpRequest,
 | 
				
			||||||
    stream: web::Payload,
 | 
					    stream: web::Payload,
 | 
				
			||||||
) -> HttpResult {
 | 
					) -> 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 domain = client.get_single_domain(domain_id).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let socket_path = match domain.devices.graphics {
 | 
					    let socket_path = match domain.devices.graphics {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ export interface ServerConfig {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export interface ServerConstraints {
 | 
					export interface ServerConstraints {
 | 
				
			||||||
  iso_max_size: number;
 | 
					  iso_max_size: number;
 | 
				
			||||||
 | 
					  vnc_token_duration: number;
 | 
				
			||||||
  vm_name_size: LenConstraint;
 | 
					  vm_name_size: LenConstraint;
 | 
				
			||||||
  vm_title_size: LenConstraint;
 | 
					  vm_title_size: LenConstraint;
 | 
				
			||||||
  memory_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 { useParams } from "react-router-dom";
 | 
				
			||||||
import { VncScreen } from "react-vnc";
 | 
					import { VncScreen } from "react-vnc";
 | 
				
			||||||
 | 
					import { ServerApi } from "../api/ServerApi";
 | 
				
			||||||
import { VMApi, VMInfo } from "../api/VMApi";
 | 
					import { VMApi, VMInfo } from "../api/VMApi";
 | 
				
			||||||
import { useSnackbar } from "../hooks/providers/SnackbarProvider";
 | 
					import { useSnackbar } from "../hooks/providers/SnackbarProvider";
 | 
				
			||||||
 | 
					import { time } from "../utils/DateUtils";
 | 
				
			||||||
import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
					import { AsyncWidget } from "../widgets/AsyncWidget";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface VNCTokenInfo {
 | 
				
			||||||
 | 
					  url: string;
 | 
				
			||||||
 | 
					  expire: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function VNCRoute(): React.ReactElement {
 | 
					export function VNCRoute(): React.ReactElement {
 | 
				
			||||||
  const { uuid } = useParams();
 | 
					  const { uuid } = useParams();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -27,52 +34,58 @@ export function VNCRoute(): React.ReactElement {
 | 
				
			|||||||
function VNCInner(p: { vm: VMInfo }): React.ReactElement {
 | 
					function VNCInner(p: { vm: VMInfo }): React.ReactElement {
 | 
				
			||||||
  const snackbar = useSnackbar();
 | 
					  const snackbar = useSnackbar();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const counter = React.useRef(false);
 | 
					  const [token, setToken] = React.useState<VNCTokenInfo | undefined>();
 | 
				
			||||||
  const [url, setURL] = React.useState<string | undefined>();
 | 
					  const [counter, setCounter] = React.useState(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const load = async () => {
 | 
					  const connect = async (force: boolean) => {
 | 
				
			||||||
    try {
 | 
					    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);
 | 
					      const u = await VMApi.OneShotVNCURL(p.vm);
 | 
				
			||||||
      console.info(u);
 | 
					
 | 
				
			||||||
      if (counter.current === false) {
 | 
					      if (!token)
 | 
				
			||||||
        counter.current = true;
 | 
					        setToken({
 | 
				
			||||||
        setURL(u);
 | 
					          expire: time() + ServerApi.Config.constraints.vnc_token_duration,
 | 
				
			||||||
      }
 | 
					          url: u,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      console.error(e);
 | 
					      console.error(e);
 | 
				
			||||||
      snackbar("Failed to initialize VNC connection!");
 | 
					      snackbar("Failed to initialize VNC connection!");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const reconnect = () => {
 | 
					  const disconnected = () => {
 | 
				
			||||||
    counter.current = false;
 | 
					    connect(true);
 | 
				
			||||||
    setURL(undefined);
 | 
					 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    connect(false);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (token === undefined)
 | 
				
			||||||
 | 
					    return <p>Please wait, connecting to the machine...</p>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <AsyncWidget
 | 
					    <div
 | 
				
			||||||
      loadKey={counter.current}
 | 
					      style={{
 | 
				
			||||||
      load={load}
 | 
					        display: "flex",
 | 
				
			||||||
      ready={url !== undefined && counter.current}
 | 
					        alignItems: "center",
 | 
				
			||||||
      errMsg="Failed to get a token to initialize VNC connection!"
 | 
					        justifyContent: "center",
 | 
				
			||||||
      build={() => (
 | 
					        height: "100%",
 | 
				
			||||||
        <div
 | 
					      }}
 | 
				
			||||||
          style={{
 | 
					    >
 | 
				
			||||||
            display: "flex",
 | 
					      <VncScreen
 | 
				
			||||||
            alignItems: "center",
 | 
					        url={token!.url}
 | 
				
			||||||
            justifyContent: "center",
 | 
					        onDisconnect={() => {
 | 
				
			||||||
            height: "100%",
 | 
					          console.info("VNC disconnected " + token?.url);
 | 
				
			||||||
          }}
 | 
					          disconnected();
 | 
				
			||||||
        >
 | 
					        }}
 | 
				
			||||||
          <VncScreen
 | 
					      />
 | 
				
			||||||
            url={url!}
 | 
					    </div>
 | 
				
			||||||
            onDisconnect={() => {
 | 
					 | 
				
			||||||
              console.info("VNC disconnected " + url);
 | 
					 | 
				
			||||||
              reconnect();
 | 
					 | 
				
			||||||
            }}
 | 
					 | 
				
			||||||
          />
 | 
					 | 
				
			||||||
        </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