diff --git a/rust/doc/faq/progress-calculation.md b/rust/doc/faq/progress-calculation.md index c6f196f440..4f8a9b3b3b 100644 --- a/rust/doc/faq/progress-calculation.md +++ b/rust/doc/faq/progress-calculation.md @@ -14,10 +14,16 @@ You get a json object which looks like as following: "alive": 1, "queued": 0, "finished": 1, - "scanning": [ - "127.0.0.1": 12, - "127.0.0.3": 75, - ] + "scanning": { + "127.0.0.1": { + finished_tests: 10, + total_tests: 20 + }, + "127.0.0.3": { + finished_tests: 12, + total_tests: 20 + } + } } } ``` @@ -25,13 +31,16 @@ You get a json object which looks like as following: Then, with this information you can calculate the scan progress with the following suggested formula: ``` -scan_progress = (sum_of_scanning_hosts_values + 100 * finished) +for host in scanning { + sum_of_scanning_hosts_progress += 100 * finished_tests / total_tests + } +scan_progress = (sum_of_scanning_hosts_progress + 100 * finished) / (all - dead) ``` For the example given above, the progress is: ``` -scan_progress = (12 + 75 + 100 * 1) / (12 - 2) = 18.7 % +scan_progress = (50 + 60 + 100 * 1) / (12 - 2) = 18.7 % ``` ## Special case for resume task @@ -54,8 +63,8 @@ At the beginning of the resumed scan you have: "alive": 1, "queued": 0, "finished": 1, - "scanning": [ - ] + "scanning": { + } } } diff --git a/rust/doc/openapi.yml b/rust/doc/openapi.yml index 03138146de..a1bc1a1f9e 100644 --- a/rust/doc/openapi.yml +++ b/rust/doc/openapi.yml @@ -895,10 +895,7 @@ components: type: "integer" format: "int32" scanning: - description: "The IP Addresses of the currently scanned hosts." - type: "array" - items: - type: "string" + $ref: "#/components/schemas/Scanning" required: - all @@ -908,6 +905,30 @@ components: - queued - finished + SingleHostProgress: { + type: "object", + description: "Additional information about the scanned host", + properties: { + finished_tests: { + description: "The number of vulnerability tests already run for the host", + type: "integer", + format: "int32" + }, + total_tests: { + description: "The total amount of vulnerability tests to be run for the host", + type: "integer", + format: "int32" + } + } + } + + Scanning: + description: "The IP Addresses of the currently scanned hosts." + type: "object" + additionalProperties: { + $ref: "#/components/schemas/SingleHostProgress" + } + ScanAction: description: "An action to perform on a scan" type: "object" @@ -1228,16 +1249,24 @@ components: { "start_time": 1679649183, "status": "running", - "host_info": - { - "all": 14, - "excluded": 0, - "dead": 4, - "alive": 6, - "queued": 1, - "finished": 1, - "scanning": ["127.0.0.1", "10.0.5.1", "10.0.5.2", "10.0.5.3"], + "host_info": { + "all": 14, + "excluded": 0, + "dead": 4, + "alive": 6, + "queued": 1, + "finished": 1, + "scanning": { + "192.168.0.1": { + "finished_tests": 456, + "total_tests": 1000 + }, + "192.168.0.2": { + "finished_tests": 456, + "total_tests": 1000 + } }, + }, } scan_status_success: diff --git a/rust/src/models/host_info.rs b/rust/src/models/host_info.rs index 3910046a20..22a28eb3e5 100644 --- a/rust/src/models/host_info.rs +++ b/rust/src/models/host_info.rs @@ -14,7 +14,7 @@ pub struct HostInfoBuilder { pub alive: u64, pub queued: u64, pub finished: u64, - pub scanning: Option>, + pub scanning: Option>, } impl HostInfoBuilder { @@ -32,6 +32,58 @@ impl HostInfoBuilder { } } +#[derive(Default, Debug, Clone, Eq, PartialEq)] +#[cfg_attr( + feature = "serde_support", + derive(serde::Serialize, serde::Deserialize) +)] +pub struct SingleHostScanInfo { + finished_tests: AmountOfTests, + total_tests: AmountOfTests, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +#[cfg_attr( + feature = "serde_support", + derive(serde::Serialize, serde::Deserialize) +)] +enum AmountOfTests { + AmountOfTests(i32), + DeadHost, +} + +impl Default for AmountOfTests { + fn default() -> Self { + AmountOfTests::AmountOfTests(0) + } +} + +impl SingleHostScanInfo { + pub fn new(finished_tests: i32, total_tests: i32) -> Self { + Self { + finished_tests: AmountOfTests::AmountOfTests(finished_tests), + total_tests: if total_tests == -1 { + AmountOfTests::DeadHost + } else { + AmountOfTests::AmountOfTests(total_tests) + }, + } + } + + pub fn is_finished(&self) -> bool { + if let AmountOfTests::AmountOfTests(f) = self.finished_tests { + if let AmountOfTests::AmountOfTests(t) = self.total_tests { + return f == t; + } + } + false + } + + pub fn is_dead(&self) -> bool { + matches!(self.total_tests, AmountOfTests::DeadHost) + } +} + /// Information about hosts of a running scan #[derive(Debug, Clone, Default, PartialEq, Eq)] #[cfg_attr( @@ -51,9 +103,8 @@ pub struct HostInfo { feature = "serde_support", serde(skip_serializing_if = "Option::is_none") )] - scanning: Option>, - // Hosts that are currently being scanned. The second entry is the number of - // remaining VTs for this host. + scanning: Option>, + #[cfg_attr(feature = "serde_support", serde(skip))] remaining_vts_per_host: HashMap, } @@ -112,10 +163,10 @@ impl HostInfo { // and never completely replaced. let mut hs = other.scanning.clone().unwrap_or_default(); for (host, progress) in self.scanning.clone().unwrap_or_default().iter() { - if *progress == 100 || *progress == -1 { + if progress.is_finished() || progress.is_dead() { hs.remove(host); } else { - hs.insert(host.to_string(), *progress); + hs.insert(host.to_string(), progress.clone()); } } self.scanning = Some(hs); diff --git a/rust/src/openvas/result_collector.rs b/rust/src/openvas/result_collector.rs index d1daf04a8e..a4b044e38f 100644 --- a/rust/src/openvas/result_collector.rs +++ b/rust/src/openvas/result_collector.rs @@ -10,9 +10,12 @@ use std::{ sync::{Arc, Mutex}, }; -use crate::openvas::openvas_redis::{KbAccess, VtHelper}; use crate::osp::{OspResultType, OspScanResult}; use crate::storage::redis::RedisStorageResult; +use crate::{ + models::SingleHostScanInfo, + openvas::openvas_redis::{KbAccess, VtHelper}, +}; /// Structure to hold the results retrieve from redis main kb #[derive(Default, Debug, Clone)] @@ -30,7 +33,7 @@ pub struct Results { /// during the scan pub count_dead: i64, /// Current hosts status - pub host_status: HashMap, + pub host_status: HashMap, /// The scan status pub scan_status: String, } @@ -135,11 +138,12 @@ where } else if result_type == "ALARM" { push_result(OspResultType::Alarm); } else if result_type == "DEADHOST" { - new_dead += i64::from_str(&value).expect("Valid amount of dead hosts"); + new_dead += i64::from_str(&value).expect("Expected a valid amount of dead hosts"); } else if host_count { - count_total = i64::from_str(&value).expect("Valid amount of dead hosts"); + count_total = i64::from_str(&value).expect("Expected a valid amount of dead hosts"); } else if excluded_hosts { - count_excluded = i64::from_str(&value).expect("Valid amount of excluded hosts"); + count_excluded = + i64::from_str(&value).expect("Expected a valid amount of excluded hosts"); } } if let Ok(mut results) = Arc::as_ref(&self.results).lock() { @@ -160,37 +164,40 @@ where } fn process_status(&self, redis_status: Vec) -> RedisStorageResult<()> { - enum ScanProgress { - DeadHost = -1, - } let mut new_dead = 0; let mut new_alive = 0; - let mut all_hosts: HashMap = HashMap::new(); + let mut all_hosts: HashMap = HashMap::new(); for res in redis_status { let mut fields = res.splitn(3, '/'); - let current_host = fields.next().expect("Valid status value"); - let launched = fields.next().expect("Valid status value"); - let total = fields.next().expect("Valid status value"); + let current_host = fields.next().expect("Expected a valid status value"); + let launched = fields.next().expect("Expected a valid status value"); + let total = fields.next().expect("Expected a valid status value"); - let host_progress: i32 = match i32::from_str(total) { + let total = match i32::from_str(total) { // No plugins Ok(0) => { continue; } // Host Dead - Ok(-1) => ScanProgress::DeadHost as i32, - Ok(n) => ((f32::from_str(launched).expect("Integer") / n as f32) * 100.0) as i32, + Ok(-1) => -1, + Ok(n) => n, _ => { continue; } }; - if host_progress == -1 { + let launched = i32::from_str(launched).expect("Expected a valid integer value"); + let host_progress = ((launched as f32 / total as f32) * 100.0) as i32; + if total == -1 { new_dead += 1; } else if host_progress == 100 { new_alive += 1; } - all_hosts.insert(current_host.to_string(), host_progress); + + all_hosts.insert( + current_host.to_string(), + SingleHostScanInfo::new(launched, total), + ); tracing::debug!("Host {} has progress: {}", current_host, host_progress); } @@ -225,6 +232,7 @@ mod tests { use crate::models::{self, Protocol, Result, ResultType}; use crate::openvas::openvas_redis::FakeRedis; + use crate::openvas::result_collector::SingleHostScanInfo; use std::collections::HashMap; use super::ResultHelper; @@ -346,11 +354,11 @@ mod tests { resh.process_status(status).unwrap(); let mut r = HashMap::new(); - r.insert("127.0.0.1".to_string(), 12); - r.insert("127.0.0.3".to_string(), 75); - r.insert("127.0.0.4".to_string(), 100); - r.insert("127.0.0.2".to_string(), -1); - r.insert("127.0.0.5".to_string(), -1); + r.insert("127.0.0.1".to_string(), SingleHostScanInfo::new(128, 1000)); + r.insert("127.0.0.3".to_string(), SingleHostScanInfo::new(750, 1000)); + r.insert("127.0.0.4".to_string(), SingleHostScanInfo::new(1000, 1000)); + r.insert("127.0.0.2".to_string(), SingleHostScanInfo::new(0, -1)); + r.insert("127.0.0.5".to_string(), SingleHostScanInfo::new(0, -1)); assert_eq!(resh.results.as_ref().lock().unwrap().host_status, r); assert_eq!(resh.results.as_ref().lock().unwrap().count_alive, 1); diff --git a/rust/src/osp/response.rs b/rust/src/osp/response.rs index 17ccab1216..0e91484acb 100644 --- a/rust/src/osp/response.rs +++ b/rust/src/osp/response.rs @@ -7,6 +7,8 @@ use std::{collections::HashMap, fmt}; use serde::{de::Visitor, Deserialize}; +use crate::models::SingleHostScanInfo; + use super::commands::Error; /// StringU32 is a wrapper around u32 to allow deserialization of strings @@ -602,10 +604,10 @@ impl From for crate::models::Status { ScanStatus::Interrupted => crate::models::Phase::Failed, }; - let mut scanning: HashMap = HashMap::new(); + let mut scanning: HashMap = HashMap::new(); if let Some(i) = &value.host_info { for host in &i.host { - scanning.insert(host.name.clone(), 0); + scanning.insert(host.name.clone(), SingleHostScanInfo::new(0, 0)); } } crate::models::Status {