From 929c4c5c3854cb70342a8f7376e3d046a3477f56 Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Fri, 22 Nov 2024 10:27:24 +0100 Subject: [PATCH 01/24] Port to pure-rust xenstore Signed-off-by: Teddy Astie --- Cargo.toml | 6 ++---- src/publisher_xenstore.rs | 15 ++++++++------- src/xenstore_schema_rfc.rs | 36 +++++++++++++++++------------------- src/xenstore_schema_std.rs | 12 ++++++------ 4 files changed, 33 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a707160..7a05e51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,10 +24,9 @@ env_logger = ">=0.10.0" clap = { version = "4.4.8", features = ["derive"] } [dependencies.xenstore-rs] +git = "https://github.com/TSnake41/xenstore-rs.git" +branch = "pure-rust" optional = true -version = "0.7.0" -#git = "https://github.com/Wenzel/xenstore.git" -default-features = false [target.'cfg(unix)'.dependencies] uname = "0.1.1" @@ -39,7 +38,6 @@ sysctl = "0.5.0" [features] default = ["xenstore", "net_netlink"] xenstore = ["dep:xenstore-rs"] -static = ["xenstore-rs?/static"] net_netlink = ["dep:netlink-proto", "dep:netlink-packet-core", "dep:netlink-packet-route", "dep:rtnetlink"] net_pnet = ["dep:pnet_datalink", "dep:pnet_base", "dep:ipnetwork"] diff --git a/src/publisher_xenstore.rs b/src/publisher_xenstore.rs index d4e2ffa..06b460a 100644 --- a/src/publisher_xenstore.rs +++ b/src/publisher_xenstore.rs @@ -2,7 +2,8 @@ use crate::datastructs::{KernelInfo, NetEvent}; use std::env; use std::error::Error; use std::io; -use xenstore_rs::{Xs, XsOpenFlags}; +use xenstore_rs::unix::XsUnix; +use xenstore_rs::Xs; pub trait XenstoreSchema { fn publish_static(&mut self, os_info: &os_info::Info, kernel_info: &Option, @@ -19,7 +20,7 @@ pub struct Publisher { impl Publisher { pub fn new() -> Result> { - let xs = Xs::new(XsOpenFlags::ReadOnly)?; + let xs = XsUnix::new()?; let schema_name = env::var("XENSTORE_SCHEMA").unwrap_or("std".to_string()); let schema_ctor = schema_from_name(&schema_name)?; let schema = schema_ctor(xs); @@ -43,7 +44,7 @@ impl Publisher { } } -fn schema_from_name(name: &str) -> io::Result<&'static dyn Fn(Xs) -> Box> { +fn schema_from_name(name: &str) -> io::Result<&'static dyn Fn(XS) -> Box> { match name { "std" => Ok(&crate::xenstore_schema_std::Schema::new), "rfc" => Ok(&crate::xenstore_schema_rfc::Schema::new), @@ -52,12 +53,12 @@ fn schema_from_name(name: &str) -> io::Result<&'static dyn Fn(Xs) -> Box io::Result<()> { +pub fn xs_publish(xs: &impl Xs, key: &str, value: &str) -> io::Result<()> { log::trace!("+ {}={:?}", key, value); - xs.write(None, key, value) + xs.write(key, value) } -pub fn xs_unpublish(xs: &Xs, key: &str) -> io::Result<()> { +pub fn xs_unpublish(xs: &impl Xs, key: &str) -> io::Result<()> { log::trace!("- {}", key); - xs.rm(None, key) + xs.rm(key) } diff --git a/src/xenstore_schema_rfc.rs b/src/xenstore_schema_rfc.rs index a250949..a61f76c 100644 --- a/src/xenstore_schema_rfc.rs +++ b/src/xenstore_schema_rfc.rs @@ -4,31 +4,29 @@ use std::io; use std::net::IpAddr; use xenstore_rs::Xs; -pub struct Schema { - xs: Xs, -} +pub struct Schema(XS); const PROTOCOL_VERSION: &str = env!("CARGO_PKG_VERSION"); // FIXME: this should be a runtime config of xenstore-std.rs -impl Schema { - pub fn new(xs: Xs) -> Box { - Box::new(Schema { xs }) +impl Schema { + pub fn new(xs: XS) -> Box { + Box::new(Schema(xs)) } } -impl XenstoreSchema for Schema { +impl XenstoreSchema for Schema { fn publish_static(&mut self, os_info: &os_info::Info, kernel_info: &Option, _mem_total_kb: Option, ) -> io::Result<()> { - xs_publish(&self.xs, "data/xen-guest-agent", PROTOCOL_VERSION)?; - xs_publish(&self.xs, "data/os/name", + xs_publish(&self.0, "data/xen-guest-agent", PROTOCOL_VERSION)?; + xs_publish(&self.0, "data/os/name", &format!("{} {}", os_info.os_type(), os_info.version()))?; - xs_publish(&self.xs, "data/os/version", &os_info.version().to_string())?; - xs_publish(&self.xs, "data/os/class", "unix")?; + xs_publish(&self.0, "data/os/version", &os_info.version().to_string())?; + xs_publish(&self.0, "data/os/class", "unix")?; if let Some(kernel_info) = kernel_info { - xs_publish(&self.xs, "data/os/unix/kernel-version", &kernel_info.release)?; + xs_publish(&self.0, "data/os/unix/kernel-version", &kernel_info.release)?; } Ok(()) @@ -36,7 +34,7 @@ impl XenstoreSchema for Schema { fn cleanup_ifaces(&mut self) -> io::Result<()> { // Currently only vif interfaces are cleaned - xs_unpublish(&self.xs, "data/net") + xs_unpublish(&self.0, "data/net") } fn publish_memfree(&self, _mem_free_kb: usize) -> io::Result<()> { @@ -50,24 +48,24 @@ impl XenstoreSchema for Schema { let xs_iface_prefix = format!("data/net/{iface_id}"); match &event.op { NetEventOp::AddIface => { - xs_publish(&self.xs, &format!("{xs_iface_prefix}"), &event.iface.borrow().name)?; + xs_publish(&self.0, &format!("{xs_iface_prefix}"), &event.iface.borrow().name)?; }, NetEventOp::RmIface => { - xs_unpublish(&self.xs, &format!("{xs_iface_prefix}"))?; + xs_unpublish(&self.0, &format!("{xs_iface_prefix}"))?; }, NetEventOp::AddIp(address) => { let key_suffix = munged_address(address); - xs_publish(&self.xs, &format!("{xs_iface_prefix}/{key_suffix}"), "")?; + xs_publish(&self.0, &format!("{xs_iface_prefix}/{key_suffix}"), "")?; }, NetEventOp::RmIp(address) => { let key_suffix = munged_address(address); - xs_unpublish(&self.xs, &format!("{xs_iface_prefix}/{key_suffix}"))?; + xs_unpublish(&self.0, &format!("{xs_iface_prefix}/{key_suffix}"))?; }, NetEventOp::AddMac(mac_address) => { - xs_publish(&self.xs, &format!("{xs_iface_prefix}"), mac_address)?; + xs_publish(&self.0, &format!("{xs_iface_prefix}"), mac_address)?; }, NetEventOp::RmMac(_) => { - xs_unpublish(&self.xs, &format!("{xs_iface_prefix}"))?; + xs_unpublish(&self.0, &format!("{xs_iface_prefix}"))?; }, } Ok(()) diff --git a/src/xenstore_schema_std.rs b/src/xenstore_schema_std.rs index 9598d3b..0d291ab 100644 --- a/src/xenstore_schema_std.rs +++ b/src/xenstore_schema_std.rs @@ -5,8 +5,8 @@ use std::io; use std::net::IpAddr; use xenstore_rs::Xs; -pub struct Schema { - xs: Xs, +pub struct Schema { + xs: XS, // use of integer indices for IP addresses requires to keep a mapping ip_addresses: IpList, @@ -31,15 +31,15 @@ const AGENT_VERSION_MAJOR: &str = "1"; // XO does not show version at all if 0 const AGENT_VERSION_MINOR: &str = "0"; const AGENT_VERSION_MICRO: &str = "0"; // XAPI exposes "-1" if missing -impl Schema { - pub fn new(xs: Xs) -> Box { +impl Schema { + pub fn new(xs: XS) -> Box { let ip_addresses = IpList::new(); Box::new(Schema { xs, ip_addresses, forbidden_control_feature_balloon: false}) } } -impl XenstoreSchema for Schema { +impl XenstoreSchema for Schema { fn publish_static(&mut self, os_info: &os_info::Info, kernel_info: &Option, mem_total_kb: Option, ) -> io::Result<()> { @@ -144,7 +144,7 @@ impl XenstoreSchema for Schema { } } -impl Schema { +impl Schema { fn munged_address(&mut self, addr: &IpAddr, iface: &NetInterface) -> io::Result { let ip_entry = self.ip_addresses .entry(iface.index) From fcb90771db42d2918ecf6cfc7c5186821c5bc0d1 Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Tue, 26 Nov 2024 17:54:56 +0100 Subject: [PATCH 02/24] Reorganize the program structure Try to make things more organized Signed-off-by: Teddy Astie --- Cargo.lock | 291 ++++++------------ Cargo.toml | 1 + src/collector/memory/bsd.rs | 109 +++++++ src/collector/memory/linux.rs | 46 +++ src/collector/memory/mod.rs | 41 +++ src/collector/mod.rs | 2 + src/collector/net/mod.rs | 33 ++ .../net/netlink.rs} | 144 ++++++--- .../net/pnet.rs} | 0 src/collector_memory.rs | 16 - src/collector_memory_bsd.rs | 88 ------ src/collector_memory_linux.rs | 39 --- src/collector_net.rs | 20 -- src/datastructs.rs | 21 +- src/error.rs | 24 -- src/hypervisor.rs | 7 - src/hypervisor_linux.rs | 16 - src/main.rs | 86 +++--- src/publisher.rs | 46 --- src/publisher/mod.rs | 81 +++++ src/publisher/xenstore/mod.rs | 16 + .../xenstore/rfc.rs} | 54 ++-- .../xenstore/std.rs} | 95 ++++-- src/publisher_xenstore.rs | 64 ---- src/vif_detect.rs | 5 - src/vif_detect/freebsd.rs | 22 ++ src/vif_detect/linux.rs | 41 +++ src/vif_detect/mod.rs | 23 ++ src/vif_detect_freebsd.rs | 18 -- src/vif_detect_linux.rs | 44 --- 30 files changed, 745 insertions(+), 748 deletions(-) create mode 100644 src/collector/memory/bsd.rs create mode 100644 src/collector/memory/linux.rs create mode 100644 src/collector/memory/mod.rs create mode 100644 src/collector/mod.rs create mode 100644 src/collector/net/mod.rs rename src/{collector_net_netlink.rs => collector/net/netlink.rs} (62%) rename src/{collector_net_pnet.rs => collector/net/pnet.rs} (100%) delete mode 100644 src/collector_memory.rs delete mode 100644 src/collector_memory_bsd.rs delete mode 100644 src/collector_memory_linux.rs delete mode 100644 src/collector_net.rs delete mode 100644 src/error.rs delete mode 100644 src/hypervisor.rs delete mode 100644 src/hypervisor_linux.rs delete mode 100644 src/publisher.rs create mode 100644 src/publisher/mod.rs create mode 100644 src/publisher/xenstore/mod.rs rename src/{xenstore_schema_rfc.rs => publisher/xenstore/rfc.rs} (65%) rename src/{xenstore_schema_std.rs => publisher/xenstore/std.rs} (76%) delete mode 100644 src/publisher_xenstore.rs delete mode 100644 src/vif_detect.rs create mode 100644 src/vif_detect/freebsd.rs create mode 100644 src/vif_detect/linux.rs create mode 100644 src/vif_detect/mod.rs delete mode 100644 src/vif_detect_freebsd.rs delete mode 100644 src/vif_detect_linux.rs diff --git a/Cargo.lock b/Cargo.lock index 09ef812..eebbdd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -43,43 +43,43 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "async-stream" @@ -124,26 +124,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "bindgen" -version = "0.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" -dependencies = [ - "bitflags", - "cexpr", - "clang-sys", - "itertools", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn", -] - [[package]] name = "bitflags" version = "2.6.0" @@ -158,18 +138,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cfg-if" @@ -177,22 +148,11 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "clang-sys" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] - [[package]] name = "clap" -version = "4.5.19" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -200,9 +160,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.19" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -224,15 +184,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "deranged" @@ -243,12 +203,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - [[package]] name = "enum-as-inner" version = "0.6.1" @@ -261,6 +215,18 @@ dependencies = [ "syn", ] +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "env_filter" version = "0.1.2" @@ -295,9 +261,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -310,9 +276,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -320,15 +286,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -337,15 +303,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -354,21 +320,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -388,12 +354,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - [[package]] name = "heck" version = "0.5.0" @@ -438,36 +398,17 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" [[package]] name = "libc" -version = "0.2.159" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" - -[[package]] -name = "libloading" -version = "0.8.5" +version = "0.2.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" -dependencies = [ - "cfg-if", - "windows-targets", -] +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "lock_api" @@ -497,12 +438,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -606,16 +541,6 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "num-conv" version = "0.1.0" @@ -640,6 +565,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + [[package]] name = "os_info" version = "3.8.2" @@ -681,9 +612,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -691,12 +622,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" - [[package]] name = "pnet_base" version = "0.35.0" @@ -735,21 +660,11 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "prettyplease" -version = "0.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" -dependencies = [ - "proc-macro2", - "syn", -] - [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -774,9 +689,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -786,9 +701,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -825,12 +740,6 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "same-file" version = "1.0.6" @@ -848,30 +757,24 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -914,9 +817,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.79" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -952,18 +855,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -1005,9 +908,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -1043,9 +946,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "utf8parse" @@ -1194,6 +1097,7 @@ version = "0.5.0-dev" dependencies = [ "async-stream", "clap", + "enum_dispatch", "env_logger", "futures", "ipnetwork", @@ -1215,22 +1119,5 @@ dependencies = [ [[package]] name = "xenstore-rs" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb033ee238b7347ab2f1f3e550094efdb19b7175e6e4664978c72ff768c43f0a" -dependencies = [ - "libc", - "libloading", - "log", - "xenstore-sys", -] - -[[package]] -name = "xenstore-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864a4c2dfde8487a06452d052f0b2116829c3e9b48f8ac71a3f1421e555b709" -dependencies = [ - "bindgen", - "pkg-config", -] +version = "1.0.0-dev" +source = "git+https://github.com/TSnake41/xenstore-rs.git?branch=pure-rust#f02ef37f82b7401e7f13df22e319078b341a03be" diff --git a/Cargo.toml b/Cargo.toml index 7a05e51..51d16cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ ipnetwork = { version = "*", optional = true } log = "0.4.0" env_logger = ">=0.10.0" clap = { version = "4.4.8", features = ["derive"] } +enum_dispatch = "0.3.13" [dependencies.xenstore-rs] git = "https://github.com/TSnake41/xenstore-rs.git" diff --git a/src/collector/memory/bsd.rs b/src/collector/memory/bsd.rs new file mode 100644 index 0000000..986fe30 --- /dev/null +++ b/src/collector/memory/bsd.rs @@ -0,0 +1,109 @@ +use std::io; +use sysctl::Sysctl; + +use super::MemorySource; + +pub struct BsdMemorySource { + memtotal_ctl: sysctl::Ctl, + pagesize: usize, + meminactive_ctl: sysctl::Ctl, + memcache_ctl: sysctl::Ctl, + memfree_ctl: sysctl::Ctl, +} + +impl MemorySource for BsdMemorySource { + fn new() -> io::Result { + let pagesize_ctl = new_sysctl("hw.pagesize")?; + let pagesize_value = pagesize_ctl.value().map_err(sysctrlerror_to_ioerror)?; + let pagesize = *pagesize_value.as_int().ok_or(io::Error::new( + io::ErrorKind::InvalidData, + "cannot parse hw.pagesize as int", + ))? as usize; + Ok(Self { + memtotal_ctl: new_sysctl("hw.physmem")?, + pagesize, + meminactive_ctl: new_sysctl("vm.stats.vm.v_inactive_count")?, + memcache_ctl: new_sysctl("vm.stats.vm.v_cache_count")?, + memfree_ctl: new_sysctl("vm.stats.vm.v_free_count")?, + }) + } + fn get_total_kb(&mut self) -> io::Result { + Ok(get_field_ulong(&self.memtotal_ctl)? as usize / 1024) + } + fn get_available_kb(&mut self) -> io::Result { + let available = (get_field_u32(&self.meminactive_ctl)? as usize + + get_field_uint(&self.memcache_ctl)? as usize + + get_field_u32(&self.memfree_ctl)? as usize) + * self.pagesize; + Ok(available / 1024) + } +} + +// helper to create a sysctl with errors mapped to io::Error +fn new_sysctl(name: &str) -> io::Result { + match sysctl::Ctl::new(name) { + Err(sysctl::SysctlError::NotFound(_)) => Err(io::Error::new( + io::ErrorKind::NotFound, + format!("sysctl {} not found", name), + )), + Err(e) => Err(io::Error::new( + io::ErrorKind::Other, + format!("Unexpected error type creating sysctl::Ctl: {:?}", e), + )), + Ok(ctl) => Ok(ctl), + } +} + +fn sysctrlerror_to_ioerror(error: sysctl::SysctlError) -> io::Error { + match error { + e => io::Error::new(io::ErrorKind::Other, format!("sysctl error: {:?}", e)), + } +} + +fn get_field_ulong(ctl: &sysctl::Ctl) -> io::Result { + let v = ctl.value().map_err(sysctrlerror_to_ioerror)?; + if let Some(value) = v.as_ulong() { + Ok(*value) + } else { + Err(io::Error::new( + io::ErrorKind::Other, + format!( + "cannot interpret {} as ulong: {:?}", + ctl.name().map_err(sysctrlerror_to_ioerror)?, + v + ), + )) + } +} + +fn get_field_u32(ctl: &sysctl::Ctl) -> io::Result { + let v = ctl.value().map_err(sysctrlerror_to_ioerror)?; + if let Some(value) = v.as_u32() { + Ok(*value) + } else { + Err(io::Error::new( + io::ErrorKind::Other, + format!( + "cannot interpret {} as u32: {:?}", + ctl.name().map_err(sysctrlerror_to_ioerror)?, + v + ), + )) + } +} + +fn get_field_uint(ctl: &sysctl::Ctl) -> io::Result { + let v = ctl.value().map_err(sysctrlerror_to_ioerror)?; + if let Some(value) = v.as_uint() { + Ok(*value) + } else { + Err(io::Error::new( + io::ErrorKind::Other, + format!( + "cannot interpret {} as uint: {:?}", + ctl.name().map_err(sysctrlerror_to_ioerror)?, + v + ), + )) + } +} diff --git a/src/collector/memory/linux.rs b/src/collector/memory/linux.rs new file mode 100644 index 0000000..1c39e1e --- /dev/null +++ b/src/collector/memory/linux.rs @@ -0,0 +1,46 @@ +use std::fs::File; +use std::io::{self, Read, Seek}; + +use super::MemorySource; + +pub struct LinuxMemorySource { + meminfo: File, +} + +impl LinuxMemorySource { + fn get_num_field(&mut self, tag: &str) -> io::Result { + self.meminfo.rewind()?; + let mut rawdata = String::new(); + self.meminfo.read_to_string(&mut rawdata)?; + let tagindex = rawdata.find(tag).ok_or(io::Error::new( + io::ErrorKind::InvalidData, + format!("could not find {tag}"), + ))?; + let numindex = rawdata[tagindex + tag.len()..] + .find(|c: char| c.is_ascii_digit()) + .ok_or(io::Error::new( + io::ErrorKind::InvalidData, + format!("no number after {tag}"), + ))?; + let num_start = tagindex + tag.len() + numindex; + let num_end = num_start + + (rawdata[num_start..] + .find(|c: char| !c.is_ascii_digit()) + .unwrap()); + Ok(rawdata[num_start..num_end].parse().unwrap()) + } +} + +impl MemorySource for LinuxMemorySource { + fn new() -> io::Result { + let meminfo = File::open("/proc/meminfo")?; + Ok(Self { meminfo }) + } + + fn get_total_kb(&mut self) -> io::Result { + self.get_num_field("MemTotal:") + } + fn get_available_kb(&mut self) -> io::Result { + self.get_num_field("MemAvailable:") + } +} diff --git a/src/collector/memory/mod.rs b/src/collector/memory/mod.rs new file mode 100644 index 0000000..bec166d --- /dev/null +++ b/src/collector/memory/mod.rs @@ -0,0 +1,41 @@ +use std::io; + +#[cfg(target_os = "freebsd")] +pub mod bsd; + +#[cfg(target_os = "linux")] +pub mod linux; + +pub trait MemorySource: Sized { + fn new() -> io::Result; + fn get_total_kb(&mut self) -> io::Result; + fn get_available_kb(&mut self) -> io::Result; +} + +#[derive(Default)] +pub struct DummyMemorySource; + +impl MemorySource for DummyMemorySource { + fn new() -> io::Result { + Ok(Self) + } + + fn get_total_kb(&mut self) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Unsupported, + "no implementation for mem_total", + )) + } + fn get_available_kb(&mut self) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Unsupported, + "no implementation for mem_avail", + )) + } +} + +#[cfg(target_os = "linux")] +pub type PlatformMemorySource = linux::LinuxMemorySource; + +#[cfg(target_os = "freebsd")] +pub type PlatformMemorySource = bsd::BsdMemorySource; diff --git a/src/collector/mod.rs b/src/collector/mod.rs new file mode 100644 index 0000000..85183f7 --- /dev/null +++ b/src/collector/mod.rs @@ -0,0 +1,2 @@ +pub mod memory; +pub mod net; \ No newline at end of file diff --git a/src/collector/net/mod.rs b/src/collector/net/mod.rs new file mode 100644 index 0000000..300bb04 --- /dev/null +++ b/src/collector/net/mod.rs @@ -0,0 +1,33 @@ +pub mod netlink; + +#[cfg(feature = "pnet")] +pub mod pnet; + +use crate::datastructs::NetEvent; +use futures::stream::Stream; +use std::error::Error; +use std::io; + +pub trait NetworkSource: Sized { + fn new() -> io::Result; + async fn collect_current(&mut self) -> Result, Box>; + fn stream(&mut self) -> impl Stream> + '_; +} + +pub struct DummyNetworkSource; + +impl NetworkSource for DummyNetworkSource { + fn new() -> io::Result { + Ok(Self) + } + + async fn collect_current(&mut self) -> Result, Box> { + Ok(vec![]) + } + + fn stream(&mut self) -> impl Stream> + '_ { + futures::stream::empty::>() + } +} + +pub type PlatformNetworkSource = netlink::NetlinkNetworkSource; diff --git a/src/collector_net_netlink.rs b/src/collector/net/netlink.rs similarity index 62% rename from src/collector_net_netlink.rs rename to src/collector/net/netlink.rs index 9659391..206a446 100644 --- a/src/collector_net_netlink.rs +++ b/src/collector/net/netlink.rs @@ -21,14 +21,16 @@ use std::net::IpAddr; use std::rc::Rc; use std::vec::Vec; -pub struct NetworkSource { +use super::NetworkSource; + +pub struct NetlinkNetworkSource { handle: netlink_proto::ConnectionHandle, messages: UnboundedReceiver<(NetlinkMessage, SocketAddr)>, - iface_cache: &'static mut NetInterfaceCache, + iface_cache: NetInterfaceCache, } -impl NetworkSource { - pub fn new(iface_cache: &'static mut NetInterfaceCache) -> io::Result { +impl NetworkSource for NetlinkNetworkSource { + fn new() -> io::Result { let (mut connection, handle, messages) = new_connection(NETLINK_ROUTE)?; // What kinds of broadcast messages we want to listen for. let nl_mgroup_flags = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; @@ -39,10 +41,14 @@ impl NetworkSource { .bind(&nl_addr) .expect("failed to bind"); tokio::spawn(connection); - Ok(NetworkSource { handle, messages, iface_cache }) + Ok(NetlinkNetworkSource { + handle, + messages, + iface_cache: Default::default(), + }) } - pub async fn collect_current(&mut self) -> Result, Box> { + async fn collect_current(&mut self) -> Result, Box> { let mut events = Vec::::new(); // Create the netlink message that requests the links to be dumped @@ -56,7 +62,11 @@ impl NetworkSource { let mut nl_response = self.handle.request(nl_msg, SocketAddr::new(0, 0))?; // Handle response while let Some(packet) = nl_response.next().await { - if let NetlinkMessage{payload: NetlinkPayload::InnerMessage(msg), ..} = packet { + if let NetlinkMessage { + payload: NetlinkPayload::InnerMessage(msg), + .. + } = packet + { events.extend(self.netevent_from_rtnetlink(&msg)?); } } @@ -72,7 +82,11 @@ impl NetworkSource { let mut nl_response = self.handle.request(nl_msg, SocketAddr::new(0, 0))?; // Handle response while let Some(packet) = nl_response.next().await { - if let NetlinkMessage{payload: NetlinkPayload::InnerMessage(msg), ..} = packet { + if let NetlinkMessage { + payload: NetlinkPayload::InnerMessage(msg), + .. + } = packet + { events.extend(self.netevent_from_rtnetlink(&msg)?); } } @@ -80,7 +94,7 @@ impl NetworkSource { Ok(events) } - pub fn stream(&mut self) -> impl Stream> + '_ { + fn stream(&mut self) -> impl Stream> + '_ { try_stream! { while let Some((message, _)) = self.messages.next().await { if let NetlinkMessage{payload: NetlinkPayload::InnerMessage(msg), ..} = message { @@ -91,53 +105,80 @@ impl NetworkSource { }; } } +} - fn netevent_from_rtnetlink(&mut self, nl_msg: &RouteNetlinkMessage) - -> io::Result> { +impl NetlinkNetworkSource { + fn netevent_from_rtnetlink( + &mut self, + nl_msg: &RouteNetlinkMessage, + ) -> io::Result> { let mut events = Vec::::new(); match nl_msg { RouteNetlinkMessage::NewLink(link_msg) => { let (iface, mac_address) = self.nl_linkmessage_decode(link_msg)?; log::debug!("NewLink({iface:?} {mac_address:?})"); - events.push(NetEvent{iface: iface.clone(), op: NetEventOp::AddIface}); + events.push(NetEvent { + iface: iface.clone(), + op: NetEventOp::AddIface, + }); if let Some(mac_address) = mac_address { - events.push(NetEvent{iface, op: NetEventOp::AddMac(mac_address)}); + events.push(NetEvent { + iface, + op: NetEventOp::AddMac(mac_address), + }); } - }, + } RouteNetlinkMessage::DelLink(link_msg) => { let (iface, mac_address) = self.nl_linkmessage_decode(link_msg)?; log::debug!("DelLink({iface:?} {mac_address:?})"); if let Some(mac_address) = mac_address { - events.push(NetEvent{iface: iface.clone(), - op: NetEventOp::RmMac(mac_address)}); // redundant + events.push(NetEvent { + iface: iface.clone(), + op: NetEventOp::RmMac(mac_address), + }); // redundant } - events.push(NetEvent{iface, op: NetEventOp::RmIface}); - }, + events.push(NetEvent { + iface, + op: NetEventOp::RmIface, + }); + } RouteNetlinkMessage::NewAddress(address_msg) => { // FIXME does not distinguish when IP is on DOWN iface let (iface, address) = self.nl_addressmessage_decode(address_msg)?; log::debug!("NewAddress({iface:?} {address})"); - events.push(NetEvent{iface, op: NetEventOp::AddIp(address)}); - }, + events.push(NetEvent { + iface, + op: NetEventOp::AddIp(address), + }); + } RouteNetlinkMessage::DelAddress(address_msg) => { let (iface, address) = self.nl_addressmessage_decode(address_msg)?; log::debug!("DelAddress({iface:?} {address})"); - events.push(NetEvent{iface, op: NetEventOp::RmIp(address)}); - }, + events.push(NetEvent { + iface, + op: NetEventOp::RmIp(address), + }); + } _ => { - return Err(io::Error::new(io::ErrorKind::InvalidData, - format!("unhandled RouteNetlinkMessage: {nl_msg:?}"))); - }, + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("unhandled RouteNetlinkMessage: {nl_msg:?}"), + )); + } }; Ok(events) } fn nl_linkmessage_decode( - &mut self, msg: &LinkMessage - ) -> io::Result<(Rc>, // ref to the (possibly new) impacted interface - Option, // MAC address + &mut self, + msg: &LinkMessage, + ) -> io::Result<( + Rc>, // ref to the (possibly new) impacted interface + Option, // MAC address )> { - let LinkMessage{header, attributes, ..} = msg; + let LinkMessage { + header, attributes, .. + } = msg; // extract fields of interest let mut iface_name: Option = None; @@ -151,15 +192,20 @@ impl NetworkSource { } } // make sure message contains an address - let mac_address = address_bytes.map(|address_bytes| address_bytes.iter() - .map(|b| format!("{b:02x}")) - .collect::>().join(":")); + let mac_address = address_bytes.map(|address_bytes| { + address_bytes + .iter() + .map(|b| format!("{b:02x}")) + .collect::>() + .join(":") + }); - let iface = self.iface_cache + let iface = self + .iface_cache .entry(header.index) - .or_insert_with_key(|index| - RefCell::new(NetInterface::new(*index, iface_name.clone())) - .into()); + .or_insert_with_key(|index| { + RefCell::new(NetInterface::new(*index, iface_name.clone())).into() + }); // handle renaming if let Some(iface_name) = iface_name { @@ -173,9 +219,13 @@ impl NetworkSource { Ok((iface.clone(), mac_address)) } - fn nl_addressmessage_decode(&mut self, msg: &AddressMessage) - -> io::Result<(Rc>, IpAddr)> { - let AddressMessage{header, attributes, ..} = msg; + fn nl_addressmessage_decode( + &mut self, + msg: &AddressMessage, + ) -> io::Result<(Rc>, IpAddr)> { + let AddressMessage { + header, attributes, .. + } = msg; // extract fields of interest let mut address: Option<&IpAddr> = None; @@ -187,17 +237,21 @@ impl NetworkSource { } let iface = match self.iface_cache.entry(header.index) { - hash_map::Entry::Occupied(entry) => { entry.get().clone() }, + hash_map::Entry::Occupied(entry) => entry.get().clone(), hash_map::Entry::Vacant(_) => { - return Err(io::Error::new(io::ErrorKind::InvalidData, - format!("unknown interface for index {}", header.index))); - }, + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("unknown interface for index {}", header.index), + )); + } }; match address { Some(address) => Ok((iface.clone(), *address)), - None => Err(io::Error::new(io::ErrorKind::InvalidData, "unknown address")), + None => Err(io::Error::new( + io::ErrorKind::InvalidData, + "unknown address", + )), } } } - diff --git a/src/collector_net_pnet.rs b/src/collector/net/pnet.rs similarity index 100% rename from src/collector_net_pnet.rs rename to src/collector/net/pnet.rs diff --git a/src/collector_memory.rs b/src/collector_memory.rs deleted file mode 100644 index 3800dde..0000000 --- a/src/collector_memory.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::io; - -pub struct MemorySource {} - -impl MemorySource { - pub fn new() -> io::Result { - Ok(MemorySource {}) - } - - pub fn get_total_kb(&mut self) -> io::Result { - Err(io::Error::new(io::ErrorKind::Unsupported, "no implementation for mem_total")) - } - pub fn get_available_kb(&mut self) -> io::Result { - Err(io::Error::new(io::ErrorKind::Unsupported, "no implementation for mem_avail")) - } -} diff --git a/src/collector_memory_bsd.rs b/src/collector_memory_bsd.rs deleted file mode 100644 index 26e8b0d..0000000 --- a/src/collector_memory_bsd.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::io; -use sysctl::Sysctl; - -pub struct MemorySource { - memtotal_ctl: sysctl::Ctl, - pagesize: usize, - meminactive_ctl: sysctl::Ctl, - memcache_ctl: sysctl::Ctl, - memfree_ctl: sysctl::Ctl, -} - -impl MemorySource { - pub fn new() -> io::Result { - let pagesize_ctl = new_sysctl("hw.pagesize")?; - let pagesize_value = pagesize_ctl.value().map_err(sysctrlerror_to_ioerror)?; - let pagesize = *pagesize_value - .as_int().ok_or(io::Error::new(io::ErrorKind::InvalidData, - "cannot parse hw.pagesize as int"))? - as usize; - Ok(MemorySource { memtotal_ctl: new_sysctl("hw.physmem")?, - pagesize, - meminactive_ctl: new_sysctl("vm.stats.vm.v_inactive_count")?, - memcache_ctl: new_sysctl("vm.stats.vm.v_cache_count")?, - memfree_ctl: new_sysctl("vm.stats.vm.v_free_count")?, - }) - } - - pub fn get_total_kb(&mut self) -> io::Result { - Ok(get_field_ulong(&self.memtotal_ctl)? as usize / 1024) - } - pub fn get_available_kb(&mut self) -> io::Result { - let available = (get_field_u32(&self.meminactive_ctl)? as usize + - get_field_uint(&self.memcache_ctl)? as usize + - get_field_u32(&self.memfree_ctl)? as usize) * self.pagesize; - Ok(available / 1024) - } -} - -// helper to create a sysctl with errors mapped to io::Error -fn new_sysctl(name: &str) -> io::Result { - match sysctl::Ctl::new(name) { - Err(sysctl::SysctlError::NotFound(_)) => - Err(io::Error::new(io::ErrorKind::NotFound, format!("sysctl {} not found", name))), - Err(e) => - Err(io::Error::new(io::ErrorKind::Other, - format!("Unexpected error type creating sysctl::Ctl: {:?}", e))), - Ok(ctl) => Ok(ctl), - } -} - -fn sysctrlerror_to_ioerror(error: sysctl::SysctlError) -> io::Error { - match error { - e => io::Error::new(io::ErrorKind::Other, format!("sysctl error: {:?}", e)), - } -} - -fn get_field_ulong(ctl: &sysctl::Ctl) -> io::Result { - let v = ctl.value().map_err(sysctrlerror_to_ioerror)?; - if let Some(value) = v.as_ulong() { - Ok(*value) - } else { - Err(io::Error::new(io::ErrorKind::Other, - format!("cannot interpret {} as ulong: {:?}", - ctl.name().map_err(sysctrlerror_to_ioerror)?, v))) - } -} - -fn get_field_u32(ctl: &sysctl::Ctl) -> io::Result { - let v = ctl.value().map_err(sysctrlerror_to_ioerror)?; - if let Some(value) = v.as_u32() { - Ok(*value) - } else { - Err(io::Error::new(io::ErrorKind::Other, - format!("cannot interpret {} as u32: {:?}", - ctl.name().map_err(sysctrlerror_to_ioerror)?, v))) - } -} - -fn get_field_uint(ctl: &sysctl::Ctl) -> io::Result { - let v = ctl.value().map_err(sysctrlerror_to_ioerror)?; - if let Some(value) = v.as_uint() { - Ok(*value) - } else { - Err(io::Error::new(io::ErrorKind::Other, - format!("cannot interpret {} as uint: {:?}", - ctl.name().map_err(sysctrlerror_to_ioerror)?, v))) - } -} diff --git a/src/collector_memory_linux.rs b/src/collector_memory_linux.rs deleted file mode 100644 index 0a850be..0000000 --- a/src/collector_memory_linux.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::fs::File; -use std::io::{self, Read, Seek}; - -pub struct MemorySource { - meminfo: File, -} - -impl MemorySource { - pub fn new() -> io::Result { - let meminfo = File::open("/proc/meminfo")?; - Ok(MemorySource { meminfo }) - } - - pub fn get_total_kb(&mut self) -> io::Result { - self.get_num_field("MemTotal:") - } - pub fn get_available_kb(&mut self) -> io::Result { - self.get_num_field("MemAvailable:") - } - - fn get_num_field(&mut self, tag: &str) -> io::Result { - self.meminfo.rewind()?; - let mut rawdata = String::new(); - self.meminfo.read_to_string(&mut rawdata)?; - let tagindex = rawdata - .find(tag) - .ok_or(io::Error::new(io::ErrorKind::InvalidData, - format!("could not find {tag}")))?; - let numindex = rawdata[tagindex + tag.len()..] - .find(|c: char| c.is_ascii_digit()) - .ok_or(io::Error::new(io::ErrorKind::InvalidData, - format!("no number after {tag}")))?; - let num_start = tagindex + tag.len() + numindex; - let num_end = num_start + (rawdata[num_start..] - .find(|c: char| !c.is_ascii_digit()) - .unwrap()); - Ok(rawdata[num_start..num_end].parse().unwrap()) - } -} diff --git a/src/collector_net.rs b/src/collector_net.rs deleted file mode 100644 index cd35e18..0000000 --- a/src/collector_net.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::datastructs::{NetEvent, NetInterfaceCache}; -use futures::stream::Stream; -use std::error::Error; -use std::io; - -pub struct NetworkSource {} - -impl NetworkSource { - pub fn new(_cache: &'static mut NetInterfaceCache) -> io::Result { - Ok(NetworkSource {}) - } - - pub async fn collect_current(&mut self) -> Result, Box> { - Ok(vec![]) - } - - pub fn stream(&mut self) -> impl Stream> + '_ { - futures::stream::empty::>() - } -} diff --git a/src/datastructs.rs b/src/datastructs.rs index 67e759f..bab909e 100644 --- a/src/datastructs.rs +++ b/src/datastructs.rs @@ -3,6 +3,8 @@ use std::collections::HashMap; use std::net::IpAddr; use std::rc::Rc; +use crate::vif_detect::{PlatformVifDetector, VifDetector}; + pub struct KernelInfo { pub release: String, } @@ -10,22 +12,12 @@ pub struct KernelInfo { #[non_exhaustive] #[derive(Clone, Debug)] pub enum ToolstackNetInterface { - None, Vif(u32), // SRIOV, // PciPassthrough, // UsbPassthrough, } -impl ToolstackNetInterface { - pub fn is_none(&self) -> bool { - if let ToolstackNetInterface::None = self { - return true; - } - false - } -} - #[derive(Clone, Debug)] pub struct NetInterface { pub index: u32, @@ -40,11 +32,12 @@ impl NetInterface { None => { log::error!("new interface with index {index} has no name"); String::from("") // this is not valid, but user will now be aware - }, + } }; - NetInterface { index, - name: name.clone(), - toolstack_iface: crate::vif_detect::get_toolstack_interface(&name), + NetInterface { + index, + name: name.clone(), + toolstack_iface: PlatformVifDetector::get_toolstack_interface(&name).unwrap(), } } } diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index 12ba900..0000000 --- a/src/error.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::error::Error; -use std::fmt::{Debug, Formatter, Result, Display}; - -pub enum XenError { - NotInGuest, - HypervisorNotXen, -} - -impl Error for XenError {} - -impl Debug for XenError { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - write!(f, "{}", self) - } -} - -impl Display for XenError { - fn fmt(&self, f: &mut Formatter) -> Result { - match *self { - XenError::NotInGuest => write!(f, "Cannot identify hypervisor"), - XenError::HypervisorNotXen => write!(f, "Hypervisor is not Xen"), - } - } -} diff --git a/src/hypervisor.rs b/src/hypervisor.rs deleted file mode 100644 index 29a8ce6..0000000 --- a/src/hypervisor.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::error::XenError; - -pub fn check_is_in_xen_guest() -> Result<(), XenError> { - // NOTE: 'Ok' here implies that we do not know how to check for the hypervisor, - // so we assume the users are aware of their actions. - Ok(()) -} diff --git a/src/hypervisor_linux.rs b/src/hypervisor_linux.rs deleted file mode 100644 index 1222916..0000000 --- a/src/hypervisor_linux.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::fs; -use crate::error::XenError; - -pub fn check_is_in_xen_guest() -> Result<(), XenError> { - match fs::read_to_string("/sys/hypervisor/type") { - Ok(hypervisor_type) => { - let hypervisor_type = hypervisor_type.trim(); - log::debug!("hypervisor_type {hypervisor_type}"); - if hypervisor_type.eq("xen") { Ok(()) } else { Err(XenError::HypervisorNotXen) } - }, - Err(err) => { - log::error!("could not identify hypervisor type, {err}"); - Err(XenError::NotInGuest) - } - } -} diff --git a/src/main.rs b/src/main.rs index 3a56fbc..e521401 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,38 +1,17 @@ mod datastructs; -#[cfg_attr(feature = "xenstore", path = "publisher_xenstore.rs")] +mod collector; mod publisher; -#[cfg(feature = "xenstore")] -mod xenstore_schema_rfc; -#[cfg(feature = "xenstore")] -mod xenstore_schema_std; - -#[cfg_attr(feature = "net_netlink", path = "collector_net_netlink.rs")] -#[cfg_attr(feature = "net_pnet", path = "collector_net_pnet.rs")] -mod collector_net; - -#[cfg_attr(target_os = "linux", path = "collector_memory_linux.rs")] -#[cfg_attr(target_os = "freebsd", path = "collector_memory_bsd.rs")] -mod collector_memory; - -#[cfg_attr(target_os = "linux", path = "vif_detect_linux.rs")] -#[cfg_attr(target_os = "freebsd", path = "vif_detect_freebsd.rs")] mod vif_detect; -#[cfg_attr(target_os = "linux", path = "hypervisor_linux.rs")] -mod hypervisor; - -mod error; - use clap::Parser; - -use crate::collector_memory::MemorySource; -use crate::collector_net::NetworkSource; -use crate::datastructs::KernelInfo; -use crate::hypervisor::check_is_in_xen_guest; -use crate::publisher::Publisher; +use collector::memory::{MemorySource, PlatformMemorySource}; +use collector::net::{NetworkSource, PlatformNetworkSource}; +use publisher::{AgentPublisher, Publisher}; +use datastructs::KernelInfo; use futures::{pin_mut, select, FutureExt, TryStreamExt}; +use std::cell::LazyCell; use std::error::Error; use std::io; use std::str::FromStr; @@ -42,6 +21,26 @@ const REPORT_INTERNAL_NICS: bool = false; // FIXME make this a CLI flag const MEM_PERIOD_SECONDS: u64 = 60; const DEFAULT_LOGLEVEL: &str = "info"; +//TODO: Shouldn't be like that +struct LazyXs(LazyCell); + +impl xenstore_rs::Xs for LazyXs { + fn directory(&self, path: &str) -> io::Result>> { + self.0.directory(path) + } + + fn read(&self, path: &str) -> io::Result> { + self.0.read(path) + } + + fn write(&self, path: &str, data: &str) -> io::Result<()> { + self.0.write(path, data) + } + + fn rm(&self, path: &str) -> io::Result<()> { + self.0.rm(path) + } +} #[tokio::main] async fn main() -> Result<(), Box> { @@ -49,14 +48,11 @@ async fn main() -> Result<(), Box> { setup_logger(cli.stderr, &cli.loglevel)?; - if let Err(err) = check_is_in_xen_guest() { - log::error!("not starting xen-guest-agent, {err}"); - return Err(err.into()) - } - - let mut publisher = Publisher::new()?; + let mut publisher = AgentPublisher::new(LazyXs(LazyCell::new(|| { + xenstore_rs::unix::XsUnix::new().expect("Xenstore not available") + })))?; - let mut collector_memory = MemorySource::new()?; + let mut collector_memory = PlatformMemorySource::new()?; // Remove old entries from previous agent to avoid having unknown // interfaces. We will repopulate existing ones immediatly. @@ -65,10 +61,10 @@ async fn main() -> Result<(), Box> { let kernel_info = collect_kernel()?; let mem_total_kb = match collector_memory.get_total_kb() { Ok(mem_total_kb) => Some(mem_total_kb), - Err(error) if error.kind() == io::ErrorKind::Unsupported - => { log::warn!("Memory stats not supported"); - None - }, + Err(error) if error.kind() == io::ErrorKind::Unsupported => { + log::warn!("Memory stats not supported"); + None + } // propagate errors other than io::ErrorKind::Unsupported Err(error) => Err(error)?, }; @@ -78,10 +74,9 @@ async fn main() -> Result<(), Box> { let mut timer_stream = tokio::time::interval(Duration::from_secs(MEM_PERIOD_SECONDS)); // network events - let network_cache = Box::leak(Box::default()); - let mut collector_net = NetworkSource::new(network_cache)?; + let mut collector_net = PlatformNetworkSource::new()?; for event in collector_net.collect_current().await? { - if REPORT_INTERNAL_NICS || ! event.iface.borrow().toolstack_iface.is_none() { + if REPORT_INTERNAL_NICS { publisher.publish_netevent(&event)?; } } @@ -94,7 +89,7 @@ async fn main() -> Result<(), Box> { event = netevent_stream.try_next().fuse() => { match event? { Some(event) => { - if REPORT_INTERNAL_NICS || ! event.iface.borrow().toolstack_iface.is_none() { + if REPORT_INTERNAL_NICS { publisher.publish_netevent(&event)?; } else { log::debug!("no toolstack iface in {event:?}"); @@ -129,7 +124,7 @@ struct Cli { loglevel: String, } -fn setup_logger(use_stderr:bool, loglevel_string: &str) -> Result<(), Box> { +fn setup_logger(use_stderr: bool, loglevel_string: &str) -> Result<(), Box> { if use_stderr { setup_env_logger(loglevel_string)?; } else { @@ -161,7 +156,10 @@ fn setup_system_logger(loglevel_string: &str) -> Result<(), Box> { }; let logger = match syslog::unix(formatter) { - Err(e) => { eprintln!("impossible to connect to syslog: {:?}", e); return Ok(()); }, + Err(e) => { + eprintln!("impossible to connect to syslog: {:?}", e); + return Ok(()); + } Ok(logger) => logger, }; log::set_boxed_logger(Box::new(syslog::BasicLogger::new(logger)))?; diff --git a/src/publisher.rs b/src/publisher.rs deleted file mode 100644 index 2ebc2d9..0000000 --- a/src/publisher.rs +++ /dev/null @@ -1,46 +0,0 @@ -// default no-op Publisher implementation -use crate::datastructs::{KernelInfo, NetEvent, NetEventOp}; -use os_info; -use std::error::Error; -use std::io; - -pub struct Publisher {} - -impl Publisher { - pub fn new() -> Result> { - Ok(Publisher {}) - } - - pub fn publish_static(&self, os_info: &os_info::Info, kernel_info: &Option, - mem_total_kb: Option, - ) -> io::Result<()> { - println!("OS: {} - Version: {}", os_info.os_type(), os_info.version()); - if let Some(mem_total_kb) = mem_total_kb { - println!("Total memory: {mem_total_kb} KB"); - } - if let Some(KernelInfo { release }) = kernel_info { - println!("Kernel version: {}", release); - } - Ok(()) - } - pub fn publish_memfree(&mut self, mem_free_kb: usize) -> io::Result<()> { - println!("Free memory: {mem_free_kb} KB"); - Ok(()) - } - pub fn publish_netevent(&self, event: &NetEvent) -> io::Result<()> { - let iface_id = &event.iface.borrow().name; - match &event.op { - NetEventOp::AddIface => println!("{iface_id} +IFACE"), - NetEventOp::RmIface => println!("{iface_id} -IFACE"), - NetEventOp::AddIp(address) => println!("{iface_id} +IP {address}"), - NetEventOp::RmIp(address) => println!("{iface_id} -IP {address}"), - NetEventOp::AddMac(mac_address) => println!("{iface_id} +MAC {mac_address}"), - NetEventOp::RmMac(mac_address) => println!("{iface_id} -MAC {mac_address}"), - } - Ok(()) - } - - pub fn cleanup_ifaces(&mut self) -> io::Result<()> { - Ok(()) - } -} diff --git a/src/publisher/mod.rs b/src/publisher/mod.rs new file mode 100644 index 0000000..211fc5e --- /dev/null +++ b/src/publisher/mod.rs @@ -0,0 +1,81 @@ +// default no-op Publisher implementation +pub mod xenstore; + +use crate::datastructs::{KernelInfo, NetEvent, NetEventOp}; +use enum_dispatch::enum_dispatch; +use os_info; +use std::{env, io}; +use xenstore::{rfc::XenstoreRfc, std::XenstoreStd}; +use xenstore_rs::Xs; + +#[enum_dispatch] +pub trait Publisher: Sized { + fn publish_static( + &mut self, + os_info: &os_info::Info, + kernel_info: &Option, + mem_total_kb: Option, + ) -> io::Result<()>; + fn publish_memfree(&mut self, mem_free_kb: usize) -> io::Result<()>; + fn publish_netevent(&mut self, event: &NetEvent) -> io::Result<()>; + + fn cleanup_ifaces(&mut self) -> io::Result<()>; +} + +#[derive(Default)] +pub struct ConsolePublisher; + +impl Publisher for ConsolePublisher { + fn publish_static( + &mut self, + os_info: &os_info::Info, + kernel_info: &Option, + mem_total_kb: Option, + ) -> io::Result<()> { + println!("OS: {} - Version: {}", os_info.os_type(), os_info.version()); + if let Some(mem_total_kb) = mem_total_kb { + println!("Total memory: {mem_total_kb} KB"); + } + if let Some(KernelInfo { release }) = kernel_info { + println!("Kernel version: {}", release); + } + Ok(()) + } + fn publish_memfree(&mut self, mem_free_kb: usize) -> io::Result<()> { + println!("Free memory: {mem_free_kb} KB"); + Ok(()) + } + fn publish_netevent(&mut self, event: &NetEvent) -> io::Result<()> { + let iface_id = &event.iface.borrow().name; + match &event.op { + NetEventOp::AddIface => println!("{iface_id} +IFACE"), + NetEventOp::RmIface => println!("{iface_id} -IFACE"), + NetEventOp::AddIp(address) => println!("{iface_id} +IP {address}"), + NetEventOp::RmIp(address) => println!("{iface_id} -IP {address}"), + NetEventOp::AddMac(mac_address) => println!("{iface_id} +MAC {mac_address}"), + NetEventOp::RmMac(mac_address) => println!("{iface_id} -MAC {mac_address}"), + } + Ok(()) + } + + fn cleanup_ifaces(&mut self) -> io::Result<()> { + Ok(()) + } +} + +#[enum_dispatch(Publisher)] +pub enum AgentPublisher { + Console(ConsolePublisher), + XenstoreRfc(XenstoreRfc), + XenstoreStd(XenstoreStd), +} + +impl AgentPublisher { + pub fn new(xs: XS) -> io::Result { + match env::var("XENSTORE_PUBLISHER").unwrap_or_default().as_str() { + "console" => Ok(Self::Console(ConsolePublisher::default())), + "rfc" => Ok(Self::XenstoreRfc(XenstoreRfc::new(xs))), + "std" | _ => Ok(Self::XenstoreStd(XenstoreStd::new(xs))), + } + } +} diff --git a/src/publisher/xenstore/mod.rs b/src/publisher/xenstore/mod.rs new file mode 100644 index 0000000..d276368 --- /dev/null +++ b/src/publisher/xenstore/mod.rs @@ -0,0 +1,16 @@ +pub mod rfc; +pub mod std; + +use ::std::io; + +use xenstore_rs::Xs; + +pub fn xs_publish(xs: &impl Xs, key: &str, value: &str) -> io::Result<()> { + log::trace!("+ {}={:?}", key, value); + xs.write(key, value) +} + +pub fn xs_unpublish(xs: &impl Xs, key: &str) -> io::Result<()> { + log::trace!("- {}", key); + xs.rm(key) +} diff --git a/src/xenstore_schema_rfc.rs b/src/publisher/xenstore/rfc.rs similarity index 65% rename from src/xenstore_schema_rfc.rs rename to src/publisher/xenstore/rfc.rs index a61f76c..db2e61c 100644 --- a/src/xenstore_schema_rfc.rs +++ b/src/publisher/xenstore/rfc.rs @@ -1,28 +1,36 @@ use crate::datastructs::{KernelInfo, NetEvent, NetEventOp}; -use crate::publisher::{xs_publish, xs_unpublish, XenstoreSchema}; +use crate::publisher::Publisher; use std::io; use std::net::IpAddr; use xenstore_rs::Xs; -pub struct Schema(XS); +use super::{xs_publish, xs_unpublish}; + +pub struct XenstoreRfc(XS); const PROTOCOL_VERSION: &str = env!("CARGO_PKG_VERSION"); // FIXME: this should be a runtime config of xenstore-std.rs -impl Schema { - pub fn new(xs: XS) -> Box { - Box::new(Schema(xs)) +impl XenstoreRfc { + pub fn new(xs: XS) -> Self { + XenstoreRfc(xs) } } -impl XenstoreSchema for Schema { - fn publish_static(&mut self, os_info: &os_info::Info, kernel_info: &Option, - _mem_total_kb: Option, +impl Publisher for XenstoreRfc { + fn publish_static( + &mut self, + os_info: &os_info::Info, + kernel_info: &Option, + _mem_total_kb: Option, ) -> io::Result<()> { xs_publish(&self.0, "data/xen-guest-agent", PROTOCOL_VERSION)?; - xs_publish(&self.0, "data/os/name", - &format!("{} {}", os_info.os_type(), os_info.version()))?; + xs_publish( + &self.0, + "data/os/name", + &format!("{} {}", os_info.os_type(), os_info.version()), + )?; xs_publish(&self.0, "data/os/version", &os_info.version().to_string())?; xs_publish(&self.0, "data/os/class", "unix")?; if let Some(kernel_info) = kernel_info { @@ -37,7 +45,7 @@ impl XenstoreSchema for Schema { xs_unpublish(&self.0, "data/net") } - fn publish_memfree(&self, _mem_free_kb: usize) -> io::Result<()> { + fn publish_memfree(&mut self, _mem_free_kb: usize) -> io::Result<()> { //xs_publish(&self.xs, "data/meminfo_free", &mem_free_kb.to_string())?; Ok(()) } @@ -48,25 +56,29 @@ impl XenstoreSchema for Schema { let xs_iface_prefix = format!("data/net/{iface_id}"); match &event.op { NetEventOp::AddIface => { - xs_publish(&self.0, &format!("{xs_iface_prefix}"), &event.iface.borrow().name)?; - }, + xs_publish( + &self.0, + &format!("{xs_iface_prefix}"), + &event.iface.borrow().name, + )?; + } NetEventOp::RmIface => { xs_unpublish(&self.0, &format!("{xs_iface_prefix}"))?; - }, + } NetEventOp::AddIp(address) => { let key_suffix = munged_address(address); xs_publish(&self.0, &format!("{xs_iface_prefix}/{key_suffix}"), "")?; - }, + } NetEventOp::RmIp(address) => { let key_suffix = munged_address(address); xs_unpublish(&self.0, &format!("{xs_iface_prefix}/{key_suffix}"))?; - }, + } NetEventOp::AddMac(mac_address) => { xs_publish(&self.0, &format!("{xs_iface_prefix}"), mac_address)?; - }, + } NetEventOp::RmMac(_) => { xs_unpublish(&self.0, &format!("{xs_iface_prefix}"))?; - }, + } } Ok(()) } @@ -74,9 +86,7 @@ impl XenstoreSchema for Schema { fn munged_address(addr: &IpAddr) -> String { match addr { - IpAddr::V4(addr) => - "ipv4/".to_string() + &addr.to_string().replace('.', "_"), - IpAddr::V6(addr) => - "ipv6/".to_string() + &addr.to_string().replace(':', "_"), + IpAddr::V4(addr) => "ipv4/".to_string() + &addr.to_string().replace('.', "_"), + IpAddr::V6(addr) => "ipv6/".to_string() + &addr.to_string().replace(':', "_"), } } diff --git a/src/xenstore_schema_std.rs b/src/publisher/xenstore/std.rs similarity index 76% rename from src/xenstore_schema_std.rs rename to src/publisher/xenstore/std.rs index 0d291ab..4b6e55b 100644 --- a/src/xenstore_schema_std.rs +++ b/src/publisher/xenstore/std.rs @@ -1,11 +1,13 @@ use crate::datastructs::{KernelInfo, NetEvent, NetEventOp, NetInterface, ToolstackNetInterface}; -use crate::publisher::{xs_publish, xs_unpublish, XenstoreSchema}; +use crate::publisher::Publisher; use std::collections::HashMap; use std::io; use std::net::IpAddr; use xenstore_rs::Xs; -pub struct Schema { +use super::{xs_publish, xs_unpublish}; + +pub struct XenstoreStd { xs: XS, // use of integer indices for IP addresses requires to keep a mapping ip_addresses: IpList, @@ -31,17 +33,23 @@ const AGENT_VERSION_MAJOR: &str = "1"; // XO does not show version at all if 0 const AGENT_VERSION_MINOR: &str = "0"; const AGENT_VERSION_MICRO: &str = "0"; // XAPI exposes "-1" if missing -impl Schema { - pub fn new(xs: XS) -> Box { +impl XenstoreStd { + pub fn new(xs: XS) -> Self { let ip_addresses = IpList::new(); - Box::new(Schema { xs, ip_addresses, - forbidden_control_feature_balloon: false}) + XenstoreStd { + xs, + ip_addresses, + forbidden_control_feature_balloon: false, + } } } -impl XenstoreSchema for Schema { - fn publish_static(&mut self, os_info: &os_info::Info, kernel_info: &Option, - mem_total_kb: Option, +impl Publisher for XenstoreStd { + fn publish_static( + &mut self, + os_info: &os_info::Info, + kernel_info: &Option, + mem_total_kb: Option, ) -> io::Result<()> { // FIXME this is not anywhere standard, just minimal XS compatibility xs_publish(&self.xs, "attr/PVAddons/MajorVersion", AGENT_VERSION_MAJOR)?; @@ -51,8 +59,11 @@ impl XenstoreSchema for Schema { xs_publish(&self.xs, "attr/PVAddons/BuildVersion", &agent_version_build)?; xs_publish(&self.xs, "data/os_distro", &os_info.os_type().to_string())?; - xs_publish(&self.xs, "data/os_name", - &format!("{} {}", os_info.os_type(), os_info.version()))?; + xs_publish( + &self.xs, + "data/os_name", + &format!("{} {}", os_info.os_type(), os_info.version()), + )?; // FIXME .version only has "major" component right now; not a // big deal for a proto, os_minorver is known to be unreliable // in xe-guest-utilities at least for Debian @@ -61,7 +72,7 @@ impl XenstoreSchema for Schema { os_info::Version::Semantic(major, minor, _patch) => { xs_publish(&self.xs, "data/os_majorver", &major.to_string())?; xs_publish(&self.xs, "data/os_minorver", &minor.to_string())?; - }, + } _ => { // FIXME what to do with strings? // the lack of `os_*ver` is anyway not a big deal @@ -87,7 +98,7 @@ impl XenstoreSchema for Schema { Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { log::warn!("cannot write control/feature-balloon (impacts XAPI's squeezed)"); self.forbidden_control_feature_balloon = true; - }, + } Ok(_) => (), e => return e, } @@ -96,7 +107,7 @@ impl XenstoreSchema for Schema { Ok(()) } - fn publish_memfree(&self, mem_free_kb: usize) -> io::Result<()> { + fn publish_memfree(&mut self, mem_free_kb: usize) -> io::Result<()> { xs_publish(&self.xs, "data/meminfo_free", &mem_free_kb.to_string())?; Ok(()) } @@ -105,35 +116,35 @@ impl XenstoreSchema for Schema { fn publish_netevent(&mut self, event: &NetEvent) -> io::Result<()> { let iface_id = match event.iface.borrow().toolstack_iface { ToolstackNetInterface::Vif(id) => id, - ToolstackNetInterface::None => { - panic!("publish_netevent called with no toolstack iface for {:?}", event); - }, }; let xs_iface_prefix = format!("attr/vif/{iface_id}"); match &event.op { NetEventOp::AddIface => { xs_publish(&self.xs, &xs_iface_prefix, "")?; - }, + } NetEventOp::RmIface => { xs_unpublish(&self.xs, &xs_iface_prefix)?; - }, + } NetEventOp::AddIp(address) => { let key_suffix = self.munged_address(address, &event.iface.borrow())?; - xs_publish(&self.xs, &format!("{xs_iface_prefix}/{key_suffix}"), - &address.to_string())?; - }, + xs_publish( + &self.xs, + &format!("{xs_iface_prefix}/{key_suffix}"), + &address.to_string(), + )?; + } NetEventOp::RmIp(address) => { let key_suffix = self.munged_address(address, &event.iface.borrow())?; xs_unpublish(&self.xs, &format!("{xs_iface_prefix}/{key_suffix}"))?; - }, + } // FIXME extend IfaceIpStruct for this NetEventOp::AddMac(_mac_address) => { log::debug!("AddMac not applied"); - }, + } NetEventOp::RmMac(_mac_address) => { log::debug!("RmMac not applied"); - }, + } } Ok(()) } @@ -144,13 +155,19 @@ impl XenstoreSchema for Schema { } } -impl Schema { +impl XenstoreStd { fn munged_address(&mut self, addr: &IpAddr, iface: &NetInterface) -> io::Result { - let ip_entry = self.ip_addresses + let ip_entry = self + .ip_addresses .entry(iface.index) - .or_insert(IfaceIpStruct{v4: [None; NUM_IFACE_IPS], v6: [None; NUM_IFACE_IPS]}); - let ip_list = match addr { IpAddr::V4(_) => &mut ip_entry.v4, - IpAddr::V6(_) => &mut ip_entry.v6 }; + .or_insert(IfaceIpStruct { + v4: [None; NUM_IFACE_IPS], + v6: [None; NUM_IFACE_IPS], + }); + let ip_list = match addr { + IpAddr::V4(_) => &mut ip_entry.v4, + IpAddr::V6(_) => &mut ip_entry.v6, + }; let ip_slot = get_ip_slot(addr, ip_list)?; match addr { IpAddr::V4(_) => Ok(format!("ipv4/{ip_slot}")), @@ -163,8 +180,16 @@ fn get_ip_slot(ip: &IpAddr, list: &mut IfaceIpList) -> io::Result { let mut empty_idx: Option = None; for (idx, item) in list.iter().enumerate() { match item { - Some(item) => if item == ip { return Ok(idx) }, // found - None => if empty_idx.is_none() { empty_idx = Some(idx) } + Some(item) => { + if item == ip { + return Ok(idx); + } + } // found + None => { + if empty_idx.is_none() { + empty_idx = Some(idx) + } + } } } // not found, insert in empty space if possible @@ -172,6 +197,8 @@ fn get_ip_slot(ip: &IpAddr, list: &mut IfaceIpList) -> io::Result { list[idx] = Some(*ip); return Ok(idx); } - Err(io::Error::new(io::ErrorKind::OutOfMemory /*StorageFull?*/, - "no free slot for a new IP address")) + Err(io::Error::new( + io::ErrorKind::OutOfMemory, /*StorageFull?*/ + "no free slot for a new IP address", + )) } diff --git a/src/publisher_xenstore.rs b/src/publisher_xenstore.rs deleted file mode 100644 index 06b460a..0000000 --- a/src/publisher_xenstore.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::datastructs::{KernelInfo, NetEvent}; -use std::env; -use std::error::Error; -use std::io; -use xenstore_rs::unix::XsUnix; -use xenstore_rs::Xs; - -pub trait XenstoreSchema { - fn publish_static(&mut self, os_info: &os_info::Info, kernel_info: &Option, - mem_total_kb: Option, - ) -> io::Result<()>; - fn publish_memfree(&self, mem_free_kb: usize) -> io::Result<()>; - fn publish_netevent(&mut self, event: &NetEvent) -> io::Result<()>; - fn cleanup_ifaces(&mut self) -> io::Result<()>; -} - -pub struct Publisher { - schema: Box, -} - -impl Publisher { - pub fn new() -> Result> { - let xs = XsUnix::new()?; - let schema_name = env::var("XENSTORE_SCHEMA").unwrap_or("std".to_string()); - let schema_ctor = schema_from_name(&schema_name)?; - let schema = schema_ctor(xs); - Ok(Publisher { schema }) - } - - pub fn publish_static(&mut self, os_info: &os_info::Info, kernel_info: &Option, - mem_total_kb: Option, - ) -> io::Result<()> { - self.schema.publish_static(os_info, kernel_info, mem_total_kb) - } - pub fn publish_memfree(&mut self, mem_free_kb: usize) -> io::Result<()> { - self.schema.publish_memfree(mem_free_kb) - } - pub fn publish_netevent(&mut self, event: &NetEvent) -> io::Result<()> { - self.schema.publish_netevent(event) - } - - pub fn cleanup_ifaces(&mut self) -> io::Result<()> { - self.schema.cleanup_ifaces() - } -} - -fn schema_from_name(name: &str) -> io::Result<&'static dyn Fn(XS) -> Box> { - match name { - "std" => Ok(&crate::xenstore_schema_std::Schema::new), - "rfc" => Ok(&crate::xenstore_schema_rfc::Schema::new), - _ => Err(io::Error::new(io::ErrorKind::InvalidData, - format!("unknown schema '{name}'"))), - } -} - -pub fn xs_publish(xs: &impl Xs, key: &str, value: &str) -> io::Result<()> { - log::trace!("+ {}={:?}", key, value); - xs.write(key, value) -} - -pub fn xs_unpublish(xs: &impl Xs, key: &str) -> io::Result<()> { - log::trace!("- {}", key); - xs.rm(key) -} diff --git a/src/vif_detect.rs b/src/vif_detect.rs deleted file mode 100644 index 1590719..0000000 --- a/src/vif_detect.rs +++ /dev/null @@ -1,5 +0,0 @@ -use crate::datastructs::{NetEvent, ToolstackNetInterface}; - -pub fn get_toolstack_interface(iface_name: &str) -> ToolstackNetInterface { - return ToolstackNetInterface::None; -} diff --git a/src/vif_detect/freebsd.rs b/src/vif_detect/freebsd.rs new file mode 100644 index 0000000..b201bd5 --- /dev/null +++ b/src/vif_detect/freebsd.rs @@ -0,0 +1,22 @@ +use crate::datastructs::ToolstackNetInterface; + +#[derive(Default)] +struct FreebsdVifDetector; + +impl super::VifDetector for FreebsdVifDetector { + // identifies a VIF as named "xn%ID" + fn get_toolstack_interface(iface_name: &str) -> Option { + const PREFIX: &str = "xn"; + if !iface_name.starts_with(PREFIX) { + log::debug!("ignoring interface {iface_name} as not starting with '{PREFIX}'"); + return None; + } + + let index = iface_name[PREFIX.len()..] + .parse() + .inspect_err(|e| log::error!("cannot parse a VIF number adter {PREFIX}: {e}")) + .ok()?; + + Some(ToolstackNetInterface::Vif(index)) + } +} diff --git a/src/vif_detect/linux.rs b/src/vif_detect/linux.rs new file mode 100644 index 0000000..9afc06a --- /dev/null +++ b/src/vif_detect/linux.rs @@ -0,0 +1,41 @@ +use crate::datastructs::ToolstackNetInterface; +use std::fs; + +use super::VifDetector; + +// identifies a VIF from sysfs as devtype="vif", and take the VIF id +// from nodename="device/vif/$ID" + +// FIXME does not attempt to detect sr-iov VIFs + +#[derive(Default)] +pub struct LinuxVifDetector; + +impl VifDetector for LinuxVifDetector { + fn get_toolstack_interface(iface_name: &str) -> Option { + // FIXME: using ETHTOOL ioctl could be better + let device_path = format!("/sys/class/net/{iface_name}/device"); + let devtype = fs::read_to_string(format!("{device_path}/devtype")) + .inspect_err(|e| log::debug!("reading {device_path}/devtype: {e}")) + .ok()?; + + let "vif" = devtype.trim() else { + log::debug!("ignoring device {device_path}, devtype {devtype:?} not 'vif'"); + return None; + }; + + let nodename = fs::read_to_string(format!("{device_path}/nodename")) + .inspect_err(|e| log::error!("reading {device_path}/nodename: {e}")) + .ok()?; + let nodename = nodename.trim(); + + const PREFIX: &str = "device/vif/"; + if !nodename.starts_with(PREFIX) { + log::debug!("ignoring interface {nodename} as not under {PREFIX}"); + return None; + } + let vif_id = nodename[PREFIX.len()..].parse().unwrap(); + + Some(ToolstackNetInterface::Vif(vif_id)) + } +} diff --git a/src/vif_detect/mod.rs b/src/vif_detect/mod.rs new file mode 100644 index 0000000..4f14bda --- /dev/null +++ b/src/vif_detect/mod.rs @@ -0,0 +1,23 @@ +use crate::datastructs::ToolstackNetInterface; + +pub mod freebsd; +pub mod linux; + +pub trait VifDetector: Default { + fn get_toolstack_interface(iface_name: &str) -> Option; +} + +#[derive(Default)] +struct DummyVifDetector; + +impl VifDetector for DummyVifDetector { + fn get_toolstack_interface(_iface_name: &str) -> Option { + None + } +} + +#[cfg(target_os = "linux")] +pub type PlatformVifDetector = linux::LinuxVifDetector; + +#[cfg(target_os = "freebsd")] +pub type PlatformVifDetector = freebsd::FreebsdVifDetector; \ No newline at end of file diff --git a/src/vif_detect_freebsd.rs b/src/vif_detect_freebsd.rs deleted file mode 100644 index d3eb09e..0000000 --- a/src/vif_detect_freebsd.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::datastructs::ToolstackNetInterface; - -// identifies a VIF as named "xn%ID" - -pub fn get_toolstack_interface(iface_name: &str) -> ToolstackNetInterface { - const PREFIX: &str = "xn"; - if !iface_name.starts_with(PREFIX) { - log::debug!("ignoring interface {iface_name} as not starting with '{PREFIX}'"); - return ToolstackNetInterface::None; - } - match iface_name[PREFIX.len()..].parse() { - Ok(index) => { return ToolstackNetInterface::Vif(index); }, - Err(e) => { - log::error!("cannot parse a VIF number adter {PREFIX}: {e}"); - return ToolstackNetInterface::None; - }, - } -} diff --git a/src/vif_detect_linux.rs b/src/vif_detect_linux.rs deleted file mode 100644 index e1b9d71..0000000 --- a/src/vif_detect_linux.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::datastructs::ToolstackNetInterface; -use std::fs; - -// identifies a VIF from sysfs as devtype="vif", and take the VIF id -// from nodename="device/vif/$ID" - -// FIXME does not attempt to detect sr-iov VIFs - -pub fn get_toolstack_interface(iface_name: &str) -> ToolstackNetInterface { - // FIXME: using ETHTOOL ioctl could be better - let device_path = format!("/sys/class/net/{}/device", iface_name); - match fs::read_to_string(format!("{device_path}/devtype")) { - Ok(devtype) => { - let devtype = devtype.trim(); - if devtype != "vif" { - log::debug!("ignoring device {device_path}, devtype {devtype:?} not 'vif'"); - return ToolstackNetInterface::None; - } - match fs::read_to_string(format!("{device_path}/nodename")) { - Ok(nodename) => { - let nodename = nodename.trim(); - const PREFIX: &str = "device/vif/"; - if !nodename.starts_with(PREFIX) { - log::debug!("ignoring interface {nodename} as not under {PREFIX}"); - return ToolstackNetInterface::None; - } - let vif_id = nodename[PREFIX.len()..].parse().unwrap(); - - ToolstackNetInterface::Vif(vif_id) - }, - Err(e) => { - log::error!("reading {device_path}/nodename: {e}"); - - ToolstackNetInterface::None - }, - } - }, - Err(e) => { - log::debug!("reading {device_path}/devtype: {e}"); - - ToolstackNetInterface::None - }, - } -} From 979cd43fd4892215f914ede1dead23326de3784e Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Wed, 27 Nov 2024 12:00:56 +0100 Subject: [PATCH 03/24] Continue refactoring test --- Cargo.lock | 1 + Cargo.toml | 5 +- src/collector/mod.rs | 2 - src/logic.rs | 80 ++++ src/main.rs | 160 ++----- src/metrics/mod.rs | 466 ++++++++++++++++++++ src/provider/kernel.rs | 18 + src/{collector => provider}/memory/bsd.rs | 0 src/{collector => provider}/memory/linux.rs | 0 src/{collector => provider}/memory/mod.rs | 0 src/provider/mod.rs | 3 + src/{collector => provider}/net/mod.rs | 5 +- src/{collector => provider}/net/netlink.rs | 3 +- src/{collector => provider}/net/pnet.rs | 73 +-- src/publisher/mod.rs | 61 +-- src/publisher/xenstore/mod.rs | 13 + src/publisher/xenstore/rfc.rs | 24 +- src/publisher/xenstore/std.rs | 40 +- src/vif_detect/mod.rs | 2 +- 19 files changed, 734 insertions(+), 222 deletions(-) delete mode 100644 src/collector/mod.rs create mode 100644 src/logic.rs create mode 100644 src/metrics/mod.rs create mode 100644 src/provider/kernel.rs rename src/{collector => provider}/memory/bsd.rs (100%) rename src/{collector => provider}/memory/linux.rs (100%) rename src/{collector => provider}/memory/mod.rs (100%) create mode 100644 src/provider/mod.rs rename src/{collector => provider}/net/mod.rs (77%) rename src/{collector => provider}/net/netlink.rs (98%) rename src/{collector => provider}/net/pnet.rs (70%) diff --git a/Cargo.lock b/Cargo.lock index eebbdd3..669a4db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1095,6 +1095,7 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" name = "xen-guest-agent" version = "0.5.0-dev" dependencies = [ + "anyhow", "async-stream", "clap", "enum_dispatch", diff --git a/Cargo.toml b/Cargo.toml index 51d16cc..4ac5a31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "xen-guest-agent" version = "0.5.0-dev" authors = ["Yann Dirson "] edition = "2018" -rust-version = "1.71" +rust-version = "1.76" license = "AGPL-3.0-only" [dependencies] @@ -22,7 +22,8 @@ ipnetwork = { version = "*", optional = true } log = "0.4.0" env_logger = ">=0.10.0" clap = { version = "4.4.8", features = ["derive"] } -enum_dispatch = "0.3.13" +enum_dispatch = "0.3" +anyhow = "1.0" [dependencies.xenstore-rs] git = "https://github.com/TSnake41/xenstore-rs.git" diff --git a/src/collector/mod.rs b/src/collector/mod.rs deleted file mode 100644 index 85183f7..0000000 --- a/src/collector/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod memory; -pub mod net; \ No newline at end of file diff --git a/src/logic.rs b/src/logic.rs new file mode 100644 index 0000000..b1bf8bc --- /dev/null +++ b/src/logic.rs @@ -0,0 +1,80 @@ +use std::{io, time::Duration}; + +use futures::{pin_mut, select, FutureExt, TryStreamExt}; + +use crate::{ + provider::{kernel::collect_kernel, memory::MemorySource, net::NetworkSource}, + publisher::{MemoryInfo, OsInfo, Publisher}, + GuestAgentConfig, +}; + +pub async fn run( + config: GuestAgentConfig, + mut publisher: impl Publisher, + mut collector_memory: impl MemorySource, + mut collector_net: impl NetworkSource, +) -> anyhow::Result<()> { + // Remove old entries from previous agent to avoid having unknown + // interfaces. We will repopulate existing ones immediatly. + publisher.cleanup_ifaces()?; + + let kernel_info = collect_kernel()?; + let mem_total = match collector_memory.get_total_kb() { + Ok(mem_total_kb) => Some(mem_total_kb), + Err(error) if error.kind() == io::ErrorKind::Unsupported => { + log::warn!("Memory stats not supported"); + None + } + // propagate errors other than io::ErrorKind::Unsupported + Err(error) => Err(error)?, + }; + publisher.publish_osinfo(&OsInfo { + os_info: os_info::get(), + kernel_info, + })?; + + // periodic memory stat + let mut timer_stream = tokio::time::interval(Duration::from_secs_f64(config.period)); + + // network events + for event in collector_net.collect_current().await? { + if config.report_nics { + publisher.publish_netevent(&event)?; + } + } + let netevent_stream = collector_net.stream(); + pin_mut!(netevent_stream); // needed for iteration + + // main loop + loop { + select! { + event = netevent_stream.try_next().fuse() => { + match event? { + Some(event) => { + if config.report_nics { + publisher.publish_netevent(&event)?; + } else { + log::debug!("no toolstack iface in {event:?}"); + } + }, + // FIXME can't we handle those in `select!` directly? + None => { /* closed? */ }, + }; + }, + _ = timer_stream.tick().fuse() => { + let mem_total = mem_total.unwrap_or_default(); + match collector_memory.get_available_kb() { + Ok(mem_avail_kb) => publisher.publish_memory(&MemoryInfo { + mem_total, + mem_free: mem_total - mem_avail_kb + })?, + Err(e) if e.kind() == io::ErrorKind::Unsupported => (), + Err(e) => Err(e)?, + } + }, + complete => break, + } + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index e521401..722e3d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,153 +1,74 @@ mod datastructs; -mod collector; +mod logic; +mod provider; mod publisher; mod vif_detect; +//pub mod metrics; use clap::Parser; -use collector::memory::{MemorySource, PlatformMemorySource}; -use collector::net::{NetworkSource, PlatformNetworkSource}; -use publisher::{AgentPublisher, Publisher}; -use datastructs::KernelInfo; - -use futures::{pin_mut, select, FutureExt, TryStreamExt}; -use std::cell::LazyCell; -use std::error::Error; -use std::io; -use std::str::FromStr; -use std::time::Duration; - -const REPORT_INTERNAL_NICS: bool = false; // FIXME make this a CLI flag -const MEM_PERIOD_SECONDS: u64 = 60; -const DEFAULT_LOGLEVEL: &str = "info"; - -//TODO: Shouldn't be like that -struct LazyXs(LazyCell); - -impl xenstore_rs::Xs for LazyXs { - fn directory(&self, path: &str) -> io::Result>> { - self.0.directory(path) - } - - fn read(&self, path: &str) -> io::Result> { - self.0.read(path) - } +use log::LevelFilter; +use provider::memory::{MemorySource, PlatformMemorySource}; +use provider::net::{NetworkSource, PlatformNetworkSource}; +use publisher::xenstore::PlatformXs; +use publisher::AgentPublisher; - fn write(&self, path: &str, data: &str) -> io::Result<()> { - self.0.write(path, data) - } - - fn rm(&self, path: &str) -> io::Result<()> { - self.0.rm(path) - } -} +const MEM_PERIOD_SECONDS: f64 = 5.0; #[tokio::main] -async fn main() -> Result<(), Box> { - let cli = Cli::parse(); - - setup_logger(cli.stderr, &cli.loglevel)?; - - let mut publisher = AgentPublisher::new(LazyXs(LazyCell::new(|| { - xenstore_rs::unix::XsUnix::new().expect("Xenstore not available") - })))?; - - let mut collector_memory = PlatformMemorySource::new()?; - - // Remove old entries from previous agent to avoid having unknown - // interfaces. We will repopulate existing ones immediatly. - publisher.cleanup_ifaces()?; - - let kernel_info = collect_kernel()?; - let mem_total_kb = match collector_memory.get_total_kb() { - Ok(mem_total_kb) => Some(mem_total_kb), - Err(error) if error.kind() == io::ErrorKind::Unsupported => { - log::warn!("Memory stats not supported"); - None - } - // propagate errors other than io::ErrorKind::Unsupported - Err(error) => Err(error)?, - }; - publisher.publish_static(&os_info::get(), &kernel_info, mem_total_kb)?; +async fn main() -> anyhow::Result<()> { + let config = GuestAgentConfig::parse(); - // periodic memory stat - let mut timer_stream = tokio::time::interval(Duration::from_secs(MEM_PERIOD_SECONDS)); + setup_logger(config.stderr, config.log_level)?; - // network events - let mut collector_net = PlatformNetworkSource::new()?; - for event in collector_net.collect_current().await? { - if REPORT_INTERNAL_NICS { - publisher.publish_netevent(&event)?; - } - } - let netevent_stream = collector_net.stream(); - pin_mut!(netevent_stream); // needed for iteration - - // main loop - loop { - select! { - event = netevent_stream.try_next().fuse() => { - match event? { - Some(event) => { - if REPORT_INTERNAL_NICS { - publisher.publish_netevent(&event)?; - } else { - log::debug!("no toolstack iface in {event:?}"); - } - }, - // FIXME can't we handle those in `select!` directly? - None => { /* closed? */ }, - }; - }, - _ = timer_stream.tick().fuse() => { - match collector_memory.get_available_kb() { - Ok(mem_avail_kb) => publisher.publish_memfree(mem_avail_kb)?, - Err(ref e) if e.kind() == io::ErrorKind::Unsupported => (), - Err(e) => Err(e)?, - } - }, - complete => break, - } - } + let publisher = AgentPublisher::::new()?; + let collector_memory = PlatformMemorySource::new()?; + let collector_net = PlatformNetworkSource::new()?; - Ok(()) + logic::run(config, publisher, collector_memory, collector_net).await } #[derive(clap::Parser)] -struct Cli { +struct GuestAgentConfig { /// Print logs to stderr instead of system logs #[arg(short, long)] stderr: bool, /// Highest level of detail to log - #[arg(short, long, default_value_t = String::from(DEFAULT_LOGLEVEL))] - loglevel: String, + #[arg(short, long, default_value_t = LevelFilter::Info)] + log_level: LevelFilter, + + #[arg(short, long, default_value_t = true)] + report_nics: bool, + + #[arg(short, long, default_value_t = MEM_PERIOD_SECONDS)] + period: f64, } -fn setup_logger(use_stderr: bool, loglevel_string: &str) -> Result<(), Box> { +fn setup_logger(use_stderr: bool, level: LevelFilter) -> anyhow::Result<()> { if use_stderr { - setup_env_logger(loglevel_string)?; + setup_env_logger(level)?; } else { #[cfg(not(unix))] panic!("no system logger supported"); #[cfg(unix)] - setup_system_logger(loglevel_string)?; + setup_system_logger(level)?; } Ok(()) } // stdout logger for platforms with no specific implementation -fn setup_env_logger(loglevel_string: &str) -> Result<(), Box> { +fn setup_env_logger(level: LevelFilter) -> anyhow::Result<()> { // set default threshold to "info" not "error" - let env = env_logger::Env::default().default_filter_or(loglevel_string); + let env = env_logger::Env::default().default_filter_or(level.as_str()); env_logger::Builder::from_env(env).init(); Ok(()) } #[cfg(unix)] // syslog logger -fn setup_system_logger(loglevel_string: &str) -> Result<(), Box> { +fn setup_system_logger(level: LevelFilter) -> anyhow::Result<()> { let formatter = syslog::Formatter3164 { facility: syslog::Facility::LOG_USER, hostname: None, @@ -163,23 +84,6 @@ fn setup_system_logger(loglevel_string: &str) -> Result<(), Box> { Ok(logger) => logger, }; log::set_boxed_logger(Box::new(syslog::BasicLogger::new(logger)))?; - log::set_max_level(log::LevelFilter::from_str(loglevel_string)?); + log::set_max_level(level); Ok(()) } - -// UNIX uname() implementation -#[cfg(unix)] -fn collect_kernel() -> io::Result> { - let uname_info = uname::uname()?; - let info = KernelInfo { - release: uname_info.release, - }; - - Ok(Some(info)) -} - -// default implementation -#[cfg(not(unix))] -fn collect_kernel() -> io::Result> { - Ok(None) -} diff --git a/src/metrics/mod.rs b/src/metrics/mod.rs new file mode 100644 index 0000000..7396dfd --- /dev/null +++ b/src/metrics/mod.rs @@ -0,0 +1,466 @@ +use std::{io, rc::Rc}; + +use serde::{ser, Serialize}; +use xenstore_rs::Xs; + +pub struct Serializer { + xs: Rc, + path: String, +} + +impl Serializer { + pub fn new(xs: XS) -> Self { + Self { + xs: Rc::new(xs), + path: String::new(), + } + } +} + +type Error = io::Error; +type Result = io::Result; + +impl<'a, XS: Xs> ser::Serializer for &'a mut Serializer { + // The output type produced by this `Serializer` during successful + // serialization. Most serializers that produce text or binary output should + // set `Ok = ()` and serialize into an `io::Write` or buffer contained + // within the `Serializer` instance, as happens here. Serializers that build + // in-memory data structures may be simplified by using `Ok` to propagate + // the data structure around. + type Ok = (); + + // The error type when some error occurs during serialization. + type Error = Error; + + // Associated types for keeping track of additional state while serializing + // compound data structures like sequences and maps. In this case no + // additional state is required beyond what is already stored in the + // Serializer struct. + type SerializeSeq = Self; + type SerializeTuple = Self; + type SerializeTupleStruct = Self; + type SerializeTupleVariant = Self; + type SerializeMap = Self; + type SerializeStruct = Self; + type SerializeStructVariant = Self; + + // Here we go with the simple methods. The following 12 methods receive one + // of the primitive types of the data model and map it to JSON by appending + // into the output string. + fn serialize_bool(self, v: bool) -> Result<()> { + self.serialize_str(if v { "true" } else { "false" }) + } + + // JSON does not distinguish between different sizes of integers, so all + // signed integers will be serialized the same and all unsigned integers + // will be serialized the same. Other formats, especially compact binary + // formats, may need independent logic for the different sizes. + fn serialize_i8(self, v: i8) -> Result<()> { + self.serialize_i64(i64::from(v)) + } + + fn serialize_i16(self, v: i16) -> Result<()> { + self.serialize_i64(i64::from(v)) + } + + fn serialize_i32(self, v: i32) -> Result<()> { + self.serialize_i64(i64::from(v)) + } + + // Not particularly efficient but this is example code anyway. A more + // performant approach would be to use the `itoa` crate. + fn serialize_i64(self, v: i64) -> Result<()> { + self.xs.write(&self.path, &v.to_string()) + } + + fn serialize_u8(self, v: u8) -> Result<()> { + self.serialize_u64(u64::from(v)) + } + + fn serialize_u16(self, v: u16) -> Result<()> { + self.serialize_u64(u64::from(v)) + } + + fn serialize_u32(self, v: u32) -> Result<()> { + self.serialize_u64(u64::from(v)) + } + + fn serialize_u64(self, v: u64) -> Result<()> { + self.xs.write(&self.path, &v.to_string()) + } + + fn serialize_f32(self, v: f32) -> Result<()> { + self.serialize_f64(f64::from(v)) + } + + fn serialize_f64(self, v: f64) -> Result<()> { + self.xs.write(&self.path, &v.to_string()) + } + + // Serialize a char as a single-character string. Other formats may + // represent this differently. + fn serialize_char(self, v: char) -> Result<()> { + self.serialize_str(&v.to_string()) + } + + // This only works for strings that don't require escape sequences but you + // get the idea. For example it would emit invalid JSON if the input string + // contains a '"' character. + fn serialize_str(self, v: &str) -> Result<()> { + self.xs.write(&self.path, v) + } + + // Serialize a byte array as an array of bytes. Could also use a base64 + // string here. Binary formats will typically represent byte arrays more + // compactly. + fn serialize_bytes(self, v: &[u8]) -> Result<()> { + let data: String = v.iter().map(|b| format!("{b:X}")).collect(); + self.xs.write(&self.path, &data) + } + + // An absent optional is represented as the JSON `null`. + fn serialize_none(self) -> Result<()> { + self.serialize_unit() + } + + // A present optional is represented as just the contained value. Note that + // this is a lossy representation. For example the values `Some(())` and + // `None` both serialize as just `null`. Unfortunately this is typically + // what people expect when working with JSON. Other formats are encouraged + // to behave more intelligently if possible. + fn serialize_some(self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + // In Serde, unit means an anonymous value containing no data. Map this to + // JSON as `null`. + fn serialize_unit(self) -> Result<()> { + self.xs.write(&self.path, "") + } + + // Unit struct means a named value containing no data. Again, since there is + // no data, map this to JSON as `null`. There is no need to serialize the + // name in most formats. + fn serialize_unit_struct(self, _name: &'static str) -> Result<()> { + self.serialize_unit() + } + + // When serializing a unit variant (or any other kind of variant), formats + // can choose whether to keep track of it by index or by name. Binary + // formats typically use the index of the variant and human-readable formats + // typically use the name. + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result<()> { + self.serialize_str(variant) + } + + // As is done here, serializers are encouraged to treat newtype structs as + // insignificant wrappers around the data they contain. + fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + value.serialize(self) + } + + // Note that newtype variant (and all of the other variant serialization + // methods) refer exclusively to the "externally tagged" enum + // representation. + // + // Serialize this to JSON in externally tagged form as `{ NAME: VALUE }`. + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result<()> + where + T: ?Sized + Serialize, + { + self.output += "{"; + variant.serialize(&mut *self)?; + self.output += ":"; + value.serialize(&mut *self)?; + self.output += "}"; + Ok(()) + } + + // Now we get to the serialization of compound types. + // + // The start of the sequence, each value, and the end are three separate + // method calls. This one is responsible only for serializing the start, + // which in JSON is `[`. + // + // The length of the sequence may or may not be known ahead of time. This + // doesn't make a difference in JSON because the length is not represented + // explicitly in the serialized form. Some serializers may only be able to + // support sequences for which the length is known up front. + fn serialize_seq(self, _len: Option) -> Result { + self.output += "["; + Ok(self) + } + + // Tuples look just like sequences in JSON. Some formats may be able to + // represent tuples more efficiently by omitting the length, since tuple + // means that the corresponding `Deserialize implementation will know the + // length without needing to look at the serialized data. + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + // Tuple structs look just like sequences in JSON. + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_seq(Some(len)) + } + + // Tuple variants are represented in JSON as `{ NAME: [DATA...] }`. Again + // this method is only responsible for the externally tagged representation. + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + self.output += "{"; + variant.serialize(&mut *self)?; + self.output += ":["; + Ok(self) + } + + // Maps are represented in JSON as `{ K: V, K: V, ... }`. + fn serialize_map(self, _len: Option) -> Result { + self.output += "{"; + Ok(self) + } + + // Structs look just like maps in JSON. In particular, JSON requires that we + // serialize the field names of the struct. Other formats may be able to + // omit the field names when serializing structs because the corresponding + // Deserialize implementation is required to know what the keys are without + // looking at the serialized data. + fn serialize_struct(self, _name: &'static str, len: usize) -> Result { + self.serialize_map(Some(len)) + } + + // Struct variants are represented in JSON as `{ NAME: { K: V, ... } }`. + // This is the externally tagged representation. + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + self.output += "{"; + variant.serialize(&mut *self)?; + self.output += ":{"; + Ok(self) + } +} + +// The following 7 impls deal with the serialization of compound types like +// sequences and maps. Serialization of such types is begun by a Serializer +// method and followed by zero or more calls to serialize individual elements of +// the compound type and one call to end the compound type. +// +// This impl is SerializeSeq so these methods are called after `serialize_seq` +// is called on the Serializer. +impl<'a> ser::SerializeSeq for &'a mut Serializer { + // Must match the `Ok` type of the serializer. + type Ok = (); + // Must match the `Error` type of the serializer. + type Error = Error; + + // Serialize a single element of the sequence. + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + if !self.output.ends_with('[') { + self.output += ","; + } + value.serialize(&mut **self) + } + + // Close the sequence. + fn end(self) -> Result<()> { + self.output += "]"; + Ok(()) + } +} + +// Same thing but for tuples. +impl<'a> ser::SerializeTuple for &'a mut Serializer { + type Ok = (); + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + if !self.output.ends_with('[') { + self.output += ","; + } + value.serialize(&mut **self) + } + + fn end(self) -> Result<()> { + self.output += "]"; + Ok(()) + } +} + +// Same thing but for tuple structs. +impl<'a> ser::SerializeTupleStruct for &'a mut Serializer { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + if !self.output.ends_with('[') { + self.output += ","; + } + value.serialize(&mut **self) + } + + fn end(self) -> Result<()> { + self.output += "]"; + Ok(()) + } +} + +// Tuple variants are a little different. Refer back to the +// `serialize_tuple_variant` method above: +// +// self.output += "{"; +// variant.serialize(&mut *self)?; +// self.output += ":["; +// +// So the `end` method in this impl is responsible for closing both the `]` and +// the `}`. +impl<'a> ser::SerializeTupleVariant for &'a mut Serializer { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + if !self.output.ends_with('[') { + self.output += ","; + } + value.serialize(&mut **self) + } + + fn end(self) -> Result<()> { + self.output += "]}"; + Ok(()) + } +} + +// Some `Serialize` types are not able to hold a key and value in memory at the +// same time so `SerializeMap` implementations are required to support +// `serialize_key` and `serialize_value` individually. +// +// There is a third optional method on the `SerializeMap` trait. The +// `serialize_entry` method allows serializers to optimize for the case where +// key and value are both available simultaneously. In JSON it doesn't make a +// difference so the default behavior for `serialize_entry` is fine. +impl<'a> ser::SerializeMap for &'a mut Serializer { + type Ok = (); + type Error = Error; + + // The Serde data model allows map keys to be any serializable type. JSON + // only allows string keys so the implementation below will produce invalid + // JSON if the key serializes as something other than a string. + // + // A real JSON serializer would need to validate that map keys are strings. + // This can be done by using a different Serializer to serialize the key + // (instead of `&mut **self`) and having that other serializer only + // implement `serialize_str` and return an error on any other data type. + fn serialize_key(&mut self, key: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + if !self.output.ends_with('{') { + self.output += ","; + } + key.serialize(&mut **self) + } + + // It doesn't make a difference whether the colon is printed at the end of + // `serialize_key` or at the beginning of `serialize_value`. In this case + // the code is a bit simpler having it here. + fn serialize_value(&mut self, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + self.output += ":"; + value.serialize(&mut **self) + } + + fn end(self) -> Result<()> { + self.output += "}"; + Ok(()) + } +} + +// Structs are like maps in which the keys are constrained to be compile-time +// constant strings. +impl<'a> ser::SerializeStruct for &'a mut Serializer { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + if !self.output.ends_with('{') { + self.output += ","; + } + key.serialize(&mut **self)?; + self.output += ":"; + value.serialize(&mut **self) + } + + fn end(self) -> Result<()> {} +} + +// Similar to `SerializeTupleVariant`, here the `end` method is responsible for +// closing both of the curly braces opened by `serialize_struct_variant`. +impl<'a> ser::SerializeStructVariant for &'a mut Serializer { + type Ok = (); + type Error = Error; + + fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> + where + T: ?Sized + Serialize, + { + if !self.output.ends_with('{') { + self.output += ","; + } + key.serialize(&mut **self)?; + self.output += ":"; + value.serialize(&mut **self) + } + + fn end(self) -> Result<()> { + self.output += "}}"; + Ok(()) + } +} diff --git a/src/provider/kernel.rs b/src/provider/kernel.rs new file mode 100644 index 0000000..6d951e9 --- /dev/null +++ b/src/provider/kernel.rs @@ -0,0 +1,18 @@ +use std::io; + +use crate::datastructs::KernelInfo; + +// UNIX uname() implementation +#[cfg(unix)] +pub fn collect_kernel() -> io::Result> { + let uname_info = uname::uname()?; + Ok(Some(KernelInfo { + release: uname_info.release, + })) +} + +// default implementation +#[cfg(not(unix))] +pub fn collect_kernel() -> io::Result> { + Ok(None) +} diff --git a/src/collector/memory/bsd.rs b/src/provider/memory/bsd.rs similarity index 100% rename from src/collector/memory/bsd.rs rename to src/provider/memory/bsd.rs diff --git a/src/collector/memory/linux.rs b/src/provider/memory/linux.rs similarity index 100% rename from src/collector/memory/linux.rs rename to src/provider/memory/linux.rs diff --git a/src/collector/memory/mod.rs b/src/provider/memory/mod.rs similarity index 100% rename from src/collector/memory/mod.rs rename to src/provider/memory/mod.rs diff --git a/src/provider/mod.rs b/src/provider/mod.rs new file mode 100644 index 0000000..3811e09 --- /dev/null +++ b/src/provider/mod.rs @@ -0,0 +1,3 @@ +pub mod kernel; +pub mod memory; +pub mod net; diff --git a/src/collector/net/mod.rs b/src/provider/net/mod.rs similarity index 77% rename from src/collector/net/mod.rs rename to src/provider/net/mod.rs index 300bb04..1f02e56 100644 --- a/src/collector/net/mod.rs +++ b/src/provider/net/mod.rs @@ -5,12 +5,11 @@ pub mod pnet; use crate::datastructs::NetEvent; use futures::stream::Stream; -use std::error::Error; use std::io; pub trait NetworkSource: Sized { fn new() -> io::Result; - async fn collect_current(&mut self) -> Result, Box>; + async fn collect_current(&mut self) -> anyhow::Result>; fn stream(&mut self) -> impl Stream> + '_; } @@ -21,7 +20,7 @@ impl NetworkSource for DummyNetworkSource { Ok(Self) } - async fn collect_current(&mut self) -> Result, Box> { + async fn collect_current(&mut self) -> anyhow::Result> { Ok(vec![]) } diff --git a/src/collector/net/netlink.rs b/src/provider/net/netlink.rs similarity index 98% rename from src/collector/net/netlink.rs rename to src/provider/net/netlink.rs index 206a446..e69566c 100644 --- a/src/collector/net/netlink.rs +++ b/src/provider/net/netlink.rs @@ -15,7 +15,6 @@ use netlink_proto::{ use rtnetlink::constants::{RTMGRP_IPV4_IFADDR, RTMGRP_IPV6_IFADDR, RTMGRP_LINK}; use std::cell::RefCell; use std::collections::hash_map; -use std::error::Error; use std::io; use std::net::IpAddr; use std::rc::Rc; @@ -48,7 +47,7 @@ impl NetworkSource for NetlinkNetworkSource { }) } - async fn collect_current(&mut self) -> Result, Box> { + async fn collect_current(&mut self) -> anyhow::Result> { let mut events = Vec::::new(); // Create the netlink message that requests the links to be dumped diff --git a/src/collector/net/pnet.rs b/src/provider/net/pnet.rs similarity index 70% rename from src/collector/net/pnet.rs rename to src/provider/net/pnet.rs index a503cab..05d41b7 100644 --- a/src/collector/net/pnet.rs +++ b/src/provider/net/pnet.rs @@ -26,7 +26,10 @@ struct InterfaceInfo { impl InterfaceInfo { pub fn new(name: &str) -> InterfaceInfo { - InterfaceInfo { name: name.to_string(), addresses: HashSet::new() } + InterfaceInfo { + name: name.to_string(), + addresses: HashSet::new(), + } } } @@ -38,10 +41,13 @@ pub struct NetworkSource { impl NetworkSource { pub fn new(iface_cache: &'static mut NetInterfaceCache) -> io::Result { - Ok(NetworkSource {addresses_cache: AddressesState::new(), iface_cache}) + Ok(NetworkSource { + addresses_cache: AddressesState::new(), + iface_cache, + }) } - pub async fn collect_current(&mut self) -> Result, Box> { + pub async fn collect_current(&mut self) -> anyhow::Result> { Ok(self.get_ifconfig_data()?) } @@ -87,13 +93,16 @@ impl NetworkSource { for (cached_iface_index, cached_info) in self.addresses_cache.iter() { // iface object from iface_cache let iface = match self.iface_cache.entry(*cached_iface_index) { - hash_map::Entry::Occupied(entry) => { entry.get().clone() }, + hash_map::Entry::Occupied(entry) => entry.get().clone(), hash_map::Entry::Vacant(_) => { return Err(io::Error::new( io::ErrorKind::InvalidData, - format!("disappearing interface with index {} not in iface_cache", - cached_iface_index))); - }, + format!( + "disappearing interface with index {} not in iface_cache", + cached_iface_index + ), + )); + } }; // notify addresses or full iface removal match current_addresses.get(cached_iface_index) { @@ -106,41 +115,51 @@ impl NetworkSource { op: match disappearing { Address::IP(ip) => NetEventOp::RmIp(ip.ip()), Address::MAC(mac) => NetEventOp::RmMac((*mac).to_string()), - }}); + }, + }); } - }, + } None => { - events.push(NetEvent{iface: iface.clone(), op: NetEventOp::RmIface}); - }, + events.push(NetEvent { + iface: iface.clone(), + op: NetEventOp::RmIface, + }); + } }; } // appearing addresses for (iface_index, iface_info) in current_addresses.iter() { - let iface = self.iface_cache + let iface = self + .iface_cache .entry(*iface_index) .or_insert_with_key(|index| { - let iface = Rc::new(RefCell::new( - NetInterface::new(*index, Some(iface_info.name.clone())))); - events.push(NetEvent{iface: iface.clone(), op: NetEventOp::AddIface}); + let iface = Rc::new(RefCell::new(NetInterface::new( + *index, + Some(iface_info.name.clone()), + ))); + events.push(NetEvent { + iface: iface.clone(), + op: NetEventOp::AddIface, + }); iface }) .clone(); - let cache_adresses = - if let Some(cache_info) = self.addresses_cache.get(iface_index) { - &cache_info.addresses - } else { - &empty_address_set - }; + let cache_adresses = if let Some(cache_info) = self.addresses_cache.get(iface_index) { + &cache_info.addresses + } else { + &empty_address_set + }; for appearing in iface_info.addresses.difference(cache_adresses) { log::trace!("appearing {}: {:?}", iface.borrow().name, appearing); - events.push(NetEvent{iface: iface.clone(), - op: match appearing { - Address::IP(ip) => NetEventOp::AddIp(ip.ip()), - Address::MAC(mac) => NetEventOp::AddMac((*mac).to_string()), - }}); + events.push(NetEvent { + iface: iface.clone(), + op: match appearing { + Address::IP(ip) => NetEventOp::AddIp(ip.ip()), + Address::MAC(mac) => NetEventOp::AddMac((*mac).to_string()), + }, + }); } - } // replace cache with view diff --git a/src/publisher/mod.rs b/src/publisher/mod.rs index 211fc5e..11658d6 100644 --- a/src/publisher/mod.rs +++ b/src/publisher/mod.rs @@ -3,20 +3,24 @@ pub mod xenstore; use crate::datastructs::{KernelInfo, NetEvent, NetEventOp}; use enum_dispatch::enum_dispatch; -use os_info; use std::{env, io}; -use xenstore::{rfc::XenstoreRfc, std::XenstoreStd}; +use xenstore::{rfc::XenstoreRfc, std::XenstoreStd, XsBuild}; use xenstore_rs::Xs; +pub struct OsInfo { + pub os_info: os_info::Info, + pub kernel_info: Option, +} + +pub struct MemoryInfo { + pub mem_free: usize, + pub mem_total: usize, +} + #[enum_dispatch] pub trait Publisher: Sized { - fn publish_static( - &mut self, - os_info: &os_info::Info, - kernel_info: &Option, - mem_total_kb: Option, - ) -> io::Result<()>; - fn publish_memfree(&mut self, mem_free_kb: usize) -> io::Result<()>; + fn publish_osinfo(&mut self, os_info: &OsInfo) -> io::Result<()>; + fn publish_memory(&mut self, mem_info: &MemoryInfo) -> io::Result<()>; fn publish_netevent(&mut self, event: &NetEvent) -> io::Result<()>; fn cleanup_ifaces(&mut self) -> io::Result<()>; @@ -26,23 +30,23 @@ pub trait Publisher: Sized { pub struct ConsolePublisher; impl Publisher for ConsolePublisher { - fn publish_static( - &mut self, - os_info: &os_info::Info, - kernel_info: &Option, - mem_total_kb: Option, - ) -> io::Result<()> { - println!("OS: {} - Version: {}", os_info.os_type(), os_info.version()); - if let Some(mem_total_kb) = mem_total_kb { - println!("Total memory: {mem_total_kb} KB"); - } - if let Some(KernelInfo { release }) = kernel_info { - println!("Kernel version: {}", release); + fn publish_osinfo(&mut self, os_info: &OsInfo) -> io::Result<()> { + println!( + "OS: {} - Version: {}", + os_info.os_info.os_type(), + os_info.os_info.version() + ); + if let Some(KernelInfo { release }) = &os_info.kernel_info { + println!("Kernel version: {release}"); } Ok(()) } - fn publish_memfree(&mut self, mem_free_kb: usize) -> io::Result<()> { - println!("Free memory: {mem_free_kb} KB"); + fn publish_memory(&mut self, mem_info: &MemoryInfo) -> io::Result<()> { + println!( + "Memory: {}/{} KB", + mem_info.mem_free / 1024, + mem_info.mem_total / 1024 + ); Ok(()) } fn publish_netevent(&mut self, event: &NetEvent) -> io::Result<()> { @@ -70,12 +74,13 @@ pub enum AgentPublisher { XenstoreStd(XenstoreStd), } -impl AgentPublisher { - pub fn new(xs: XS) -> io::Result { +impl AgentPublisher { + #[allow(clippy::wildcard_in_or_patterns)] + pub fn new() -> io::Result { match env::var("XENSTORE_PUBLISHER").unwrap_or_default().as_str() { - "console" => Ok(Self::Console(ConsolePublisher::default())), - "rfc" => Ok(Self::XenstoreRfc(XenstoreRfc::new(xs))), - "std" | _ => Ok(Self::XenstoreStd(XenstoreStd::new(xs))), + "console" => Ok(Self::Console(ConsolePublisher)), + "rfc" => Ok(Self::XenstoreRfc(XenstoreRfc::new(XS::new()?))), + "std" | _ => Ok(Self::XenstoreStd(XenstoreStd::new(XS::new()?))), } } } diff --git a/src/publisher/xenstore/mod.rs b/src/publisher/xenstore/mod.rs index d276368..1f11c21 100644 --- a/src/publisher/xenstore/mod.rs +++ b/src/publisher/xenstore/mod.rs @@ -14,3 +14,16 @@ pub fn xs_unpublish(xs: &impl Xs, key: &str) -> io::Result<()> { log::trace!("- {}", key); xs.rm(key) } + +pub trait XsBuild: Sized + Xs { + fn new() -> io::Result; +} + +#[cfg(target_family = "unix")] +impl XsBuild for xenstore_rs::unix::XsUnix { + fn new() -> io::Result { + Self::new() + } +} + +pub type PlatformXs = xenstore_rs::unix::XsUnix; diff --git a/src/publisher/xenstore/rfc.rs b/src/publisher/xenstore/rfc.rs index db2e61c..d43d88c 100644 --- a/src/publisher/xenstore/rfc.rs +++ b/src/publisher/xenstore/rfc.rs @@ -1,11 +1,12 @@ -use crate::datastructs::{KernelInfo, NetEvent, NetEventOp}; -use crate::publisher::Publisher; +use crate::datastructs::{NetEvent, NetEventOp}; +use crate::publisher::{MemoryInfo, OsInfo, Publisher}; use std::io; use std::net::IpAddr; use xenstore_rs::Xs; use super::{xs_publish, xs_unpublish}; +#[derive(Clone)] pub struct XenstoreRfc(XS); const PROTOCOL_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -19,21 +20,20 @@ impl XenstoreRfc { } impl Publisher for XenstoreRfc { - fn publish_static( - &mut self, - os_info: &os_info::Info, - kernel_info: &Option, - _mem_total_kb: Option, - ) -> io::Result<()> { + fn publish_osinfo(&mut self, info: &OsInfo) -> io::Result<()> { xs_publish(&self.0, "data/xen-guest-agent", PROTOCOL_VERSION)?; xs_publish( &self.0, "data/os/name", - &format!("{} {}", os_info.os_type(), os_info.version()), + &format!("{} {}", info.os_info.os_type(), info.os_info.version()), + )?; + xs_publish( + &self.0, + "data/os/version", + &info.os_info.version().to_string(), )?; - xs_publish(&self.0, "data/os/version", &os_info.version().to_string())?; xs_publish(&self.0, "data/os/class", "unix")?; - if let Some(kernel_info) = kernel_info { + if let Some(kernel_info) = &info.kernel_info { xs_publish(&self.0, "data/os/unix/kernel-version", &kernel_info.release)?; } @@ -45,7 +45,7 @@ impl Publisher for XenstoreRfc { xs_unpublish(&self.0, "data/net") } - fn publish_memfree(&mut self, _mem_free_kb: usize) -> io::Result<()> { + fn publish_memory(&mut self, _mem_info: &MemoryInfo) -> io::Result<()> { //xs_publish(&self.xs, "data/meminfo_free", &mem_free_kb.to_string())?; Ok(()) } diff --git a/src/publisher/xenstore/std.rs b/src/publisher/xenstore/std.rs index 4b6e55b..6afc292 100644 --- a/src/publisher/xenstore/std.rs +++ b/src/publisher/xenstore/std.rs @@ -1,5 +1,6 @@ -use crate::datastructs::{KernelInfo, NetEvent, NetEventOp, NetInterface, ToolstackNetInterface}; +use crate::datastructs::{NetEvent, NetEventOp, NetInterface, ToolstackNetInterface}; use crate::publisher::Publisher; +use crate::publisher::{MemoryInfo, OsInfo}; use std::collections::HashMap; use std::io; use std::net::IpAddr; @@ -45,12 +46,7 @@ impl XenstoreStd { } impl Publisher for XenstoreStd { - fn publish_static( - &mut self, - os_info: &os_info::Info, - kernel_info: &Option, - mem_total_kb: Option, - ) -> io::Result<()> { + fn publish_osinfo(&mut self, info: &OsInfo) -> io::Result<()> { // FIXME this is not anywhere standard, just minimal XS compatibility xs_publish(&self.xs, "attr/PVAddons/MajorVersion", AGENT_VERSION_MAJOR)?; xs_publish(&self.xs, "attr/PVAddons/MinorVersion", AGENT_VERSION_MINOR)?; @@ -58,16 +54,20 @@ impl Publisher for XenstoreStd { let agent_version_build = format!("proto-{}", &env!("CARGO_PKG_VERSION")); xs_publish(&self.xs, "attr/PVAddons/BuildVersion", &agent_version_build)?; - xs_publish(&self.xs, "data/os_distro", &os_info.os_type().to_string())?; + xs_publish( + &self.xs, + "data/os_distro", + &info.os_info.os_type().to_string(), + )?; xs_publish( &self.xs, "data/os_name", - &format!("{} {}", os_info.os_type(), os_info.version()), + &format!("{} {}", info.os_info.os_type(), info.os_info.version()), )?; // FIXME .version only has "major" component right now; not a // big deal for a proto, os_minorver is known to be unreliable // in xe-guest-utilities at least for Debian - let os_version = os_info.version(); + let os_version = info.os_info.version(); match os_version { os_info::Version::Semantic(major, minor, _patch) => { xs_publish(&self.xs, "data/os_majorver", &major.to_string())?; @@ -79,14 +79,10 @@ impl Publisher for XenstoreStd { log::info!("cannot parse yet os version {:?}", os_version); } } - if let Some(kernel_info) = kernel_info { + if let Some(kernel_info) = &info.kernel_info { xs_publish(&self.xs, "data/os_uname", &kernel_info.release)?; } - if let Some(mem_total_kb) = mem_total_kb { - xs_publish(&self.xs, "data/meminfo_total", &mem_total_kb.to_string())?; - } - if !self.forbidden_control_feature_balloon { // we may want to be more clever some day, e.g. by // checking if the guest indeed has ballooning, and if the @@ -107,8 +103,18 @@ impl Publisher for XenstoreStd { Ok(()) } - fn publish_memfree(&mut self, mem_free_kb: usize) -> io::Result<()> { - xs_publish(&self.xs, "data/meminfo_free", &mem_free_kb.to_string())?; + fn publish_memory(&mut self, mem_info: &MemoryInfo) -> io::Result<()> { + xs_publish( + &self.xs, + "data/meminfo_free", + &(mem_info.mem_free / 1024).to_string(), + )?; + xs_publish( + &self.xs, + "data/meminfo_total", + &(mem_info.mem_total / 1024).to_string(), + )?; + Ok(()) } diff --git a/src/vif_detect/mod.rs b/src/vif_detect/mod.rs index 4f14bda..e57181e 100644 --- a/src/vif_detect/mod.rs +++ b/src/vif_detect/mod.rs @@ -20,4 +20,4 @@ impl VifDetector for DummyVifDetector { pub type PlatformVifDetector = linux::LinuxVifDetector; #[cfg(target_os = "freebsd")] -pub type PlatformVifDetector = freebsd::FreebsdVifDetector; \ No newline at end of file +pub type PlatformVifDetector = freebsd::FreebsdVifDetector; From ca5d8ee092dd516cc17106413dda2934dcc0711f Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Wed, 27 Nov 2024 18:34:07 +0100 Subject: [PATCH 04/24] Some other changes --- Cargo.lock | 23 -- Cargo.toml | 1 - src/datastructs.rs | 17 +- src/logic.rs | 93 +++---- src/main.rs | 41 +-- src/metrics/mod.rs | 466 ---------------------------------- src/provider/net/mod.rs | 89 ++++++- src/provider/net/netlink.rs | 87 ++++--- src/provider/net/pnet.rs | 71 ++++-- src/publisher/mod.rs | 51 +++- src/publisher/xenstore/mod.rs | 2 +- src/publisher/xenstore/rfc.rs | 4 +- src/publisher/xenstore/std.rs | 7 +- src/vif_detect/freebsd.rs | 2 +- src/vif_detect/mod.rs | 4 +- 15 files changed, 313 insertions(+), 645 deletions(-) delete mode 100644 src/metrics/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 669a4db..5299c91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,28 +81,6 @@ version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "autocfg" version = "1.4.0" @@ -1096,7 +1074,6 @@ name = "xen-guest-agent" version = "0.5.0-dev" dependencies = [ "anyhow", - "async-stream", "clap", "enum_dispatch", "env_logger", diff --git a/Cargo.toml b/Cargo.toml index 4ac5a31..af58cdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ netlink-packet-core = { version = "0.7.0", optional = true } netlink-packet-route = { version = ">=0.18.0, <0.20", optional = true } netlink-proto = { version = "0.11.2", optional = true } rtnetlink = { version = "0.14.0", optional = true } -async-stream = "0.3.4" os_info = { version = "3", default-features = false } pnet_datalink = { version = "*", optional = true } pnet_base = { version = "*", optional = true } diff --git a/src/datastructs.rs b/src/datastructs.rs index bab909e..033822b 100644 --- a/src/datastructs.rs +++ b/src/datastructs.rs @@ -1,7 +1,7 @@ -use std::cell::RefCell; use std::collections::HashMap; use std::net::IpAddr; -use std::rc::Rc; +use std::sync::Arc; +use std::sync::Mutex; use crate::vif_detect::{PlatformVifDetector, VifDetector}; @@ -10,8 +10,10 @@ pub struct KernelInfo { } #[non_exhaustive] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub enum ToolstackNetInterface { + #[default] + Unknown, Vif(u32), // SRIOV, // PciPassthrough, @@ -37,11 +39,14 @@ impl NetInterface { NetInterface { index, name: name.clone(), - toolstack_iface: PlatformVifDetector::get_toolstack_interface(&name).unwrap(), + toolstack_iface: PlatformVifDetector::get_toolstack_interface(&name) + .unwrap_or_default(), } } } +// TODO: Teddy: We should find a better solution than abusing Arc> + // The cache of currently-known network interfaces. We have to use // reference counting on the cached items, as we want on one hand to // use references to those items from NetEvent, and OTOH we want to @@ -49,7 +54,7 @@ impl NetInterface { // use `&'static NetInterface` because we can do the latter, which is // good in the end. // The interface may change name after creation (hence `RefCell`). -pub type NetInterfaceCache = HashMap>>; +pub type NetInterfaceCache = HashMap>>; #[derive(Debug)] pub enum NetEventOp { @@ -63,6 +68,6 @@ pub enum NetEventOp { #[derive(Debug)] pub struct NetEvent { - pub iface: Rc>, + pub iface: Arc>, pub op: NetEventOp, } diff --git a/src/logic.rs b/src/logic.rs index b1bf8bc..533edf9 100644 --- a/src/logic.rs +++ b/src/logic.rs @@ -1,22 +1,23 @@ use std::{io, time::Duration}; -use futures::{pin_mut, select, FutureExt, TryStreamExt}; +use futures::StreamExt; +use tokio::sync::mpsc; use crate::{ provider::{kernel::collect_kernel, memory::MemorySource, net::NetworkSource}, - publisher::{MemoryInfo, OsInfo, Publisher}, + publisher::{GuestMetric, MemoryInfo, OsInfo}, GuestAgentConfig, }; pub async fn run( config: GuestAgentConfig, - mut publisher: impl Publisher, - mut collector_memory: impl MemorySource, - mut collector_net: impl NetworkSource, + publisher_channel: mpsc::Sender, + mut collector_memory: impl MemorySource + Send + 'static, + mut collector_net: impl NetworkSource + Send + Unpin + 'static, ) -> anyhow::Result<()> { // Remove old entries from previous agent to avoid having unknown // interfaces. We will repopulate existing ones immediatly. - publisher.cleanup_ifaces()?; + publisher_channel.send(GuestMetric::CleanupIfaces).await?; let kernel_info = collect_kernel()?; let mem_total = match collector_memory.get_total_kb() { @@ -28,53 +29,59 @@ pub async fn run( // propagate errors other than io::ErrorKind::Unsupported Err(error) => Err(error)?, }; - publisher.publish_osinfo(&OsInfo { - os_info: os_info::get(), - kernel_info, - })?; - - // periodic memory stat - let mut timer_stream = tokio::time::interval(Duration::from_secs_f64(config.period)); + publisher_channel + .send(GuestMetric::OsInfo(OsInfo { + os_info: os_info::get(), + kernel_info, + })) + .await?; // network events for event in collector_net.collect_current().await? { if config.report_nics { - publisher.publish_netevent(&event)?; + publisher_channel.send(GuestMetric::Network(event)).await?; } } - let netevent_stream = collector_net.stream(); - pin_mut!(netevent_stream); // needed for iteration // main loop - loop { - select! { - event = netevent_stream.try_next().fuse() => { - match event? { - Some(event) => { - if config.report_nics { - publisher.publish_netevent(&event)?; - } else { - log::debug!("no toolstack iface in {event:?}"); - } - }, - // FIXME can't we handle those in `select!` directly? - None => { /* closed? */ }, - }; - }, - _ = timer_stream.tick().fuse() => { - let mem_total = mem_total.unwrap_or_default(); - match collector_memory.get_available_kb() { - Ok(mem_avail_kb) => publisher.publish_memory(&MemoryInfo { - mem_total, - mem_free: mem_total - mem_avail_kb - })?, - Err(e) if e.kind() == io::ErrorKind::Unsupported => (), - Err(e) => Err(e)?, + let network_task = tokio::spawn({ + let publisher_channel = publisher_channel.clone(); + async move { + loop { + while let Some(events) = collector_net.next().await { + for event in events { + publisher_channel + .send(GuestMetric::Network(event)) + .await + .unwrap(); + } } - }, - complete => break, + } } - } + }); + + let memory_task = tokio::spawn({ + let publisher_channel = publisher_channel.clone(); + let mut timer = tokio::time::interval(Duration::from_secs_f64(config.period)); + + async move { + loop { + timer.tick().await; + let mem_total = mem_total.unwrap_or_default(); + let mem_free = collector_memory.get_available_kb().unwrap(); + + publisher_channel + .send(GuestMetric::MemoryInfo(MemoryInfo { + mem_free, + mem_total, + })) + .await + .unwrap(); + } + } + }); + + let _ = futures::join!(network_task, memory_task); Ok(()) } diff --git a/src/main.rs b/src/main.rs index 722e3d3..0cc1172 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,25 +9,12 @@ mod vif_detect; use clap::Parser; use log::LevelFilter; use provider::memory::{MemorySource, PlatformMemorySource}; -use provider::net::{NetworkSource, PlatformNetworkSource}; +use provider::net::{AgentNetworkSource, NetworkSourceKind}; use publisher::xenstore::PlatformXs; -use publisher::AgentPublisher; +use publisher::PublisherKind; const MEM_PERIOD_SECONDS: f64 = 5.0; -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let config = GuestAgentConfig::parse(); - - setup_logger(config.stderr, config.log_level)?; - - let publisher = AgentPublisher::::new()?; - let collector_memory = PlatformMemorySource::new()?; - let collector_net = PlatformNetworkSource::new()?; - - logic::run(config, publisher, collector_memory, collector_net).await -} - #[derive(clap::Parser)] struct GuestAgentConfig { /// Print logs to stderr instead of system logs @@ -38,11 +25,35 @@ struct GuestAgentConfig { #[arg(short, long, default_value_t = LevelFilter::Info)] log_level: LevelFilter, + /// Whether we report NICs. #[arg(short, long, default_value_t = true)] report_nics: bool, + /// Update period. #[arg(short, long, default_value_t = MEM_PERIOD_SECONDS)] period: f64, + + #[arg(long, value_enum, default_value_t = Default::default())] + publisher: PublisherKind, + + #[arg(long, value_enum, default_value_t = Default::default())] + network: NetworkSourceKind, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let config = GuestAgentConfig::parse(); + + setup_logger(config.stderr, config.log_level)?; + + let publisher_channel: tokio::sync::mpsc::Sender = + publisher::spawn_publisher::(config.publisher) + .expect("Unable to initialize publisher"); + let collector_memory = PlatformMemorySource::new().expect("Unable to initialize memory source"); + let collector_net = + AgentNetworkSource::new(config.network).expect("Unable to initialize network source"); + + logic::run(config, publisher_channel, collector_memory, collector_net).await } fn setup_logger(use_stderr: bool, level: LevelFilter) -> anyhow::Result<()> { diff --git a/src/metrics/mod.rs b/src/metrics/mod.rs deleted file mode 100644 index 7396dfd..0000000 --- a/src/metrics/mod.rs +++ /dev/null @@ -1,466 +0,0 @@ -use std::{io, rc::Rc}; - -use serde::{ser, Serialize}; -use xenstore_rs::Xs; - -pub struct Serializer { - xs: Rc, - path: String, -} - -impl Serializer { - pub fn new(xs: XS) -> Self { - Self { - xs: Rc::new(xs), - path: String::new(), - } - } -} - -type Error = io::Error; -type Result = io::Result; - -impl<'a, XS: Xs> ser::Serializer for &'a mut Serializer { - // The output type produced by this `Serializer` during successful - // serialization. Most serializers that produce text or binary output should - // set `Ok = ()` and serialize into an `io::Write` or buffer contained - // within the `Serializer` instance, as happens here. Serializers that build - // in-memory data structures may be simplified by using `Ok` to propagate - // the data structure around. - type Ok = (); - - // The error type when some error occurs during serialization. - type Error = Error; - - // Associated types for keeping track of additional state while serializing - // compound data structures like sequences and maps. In this case no - // additional state is required beyond what is already stored in the - // Serializer struct. - type SerializeSeq = Self; - type SerializeTuple = Self; - type SerializeTupleStruct = Self; - type SerializeTupleVariant = Self; - type SerializeMap = Self; - type SerializeStruct = Self; - type SerializeStructVariant = Self; - - // Here we go with the simple methods. The following 12 methods receive one - // of the primitive types of the data model and map it to JSON by appending - // into the output string. - fn serialize_bool(self, v: bool) -> Result<()> { - self.serialize_str(if v { "true" } else { "false" }) - } - - // JSON does not distinguish between different sizes of integers, so all - // signed integers will be serialized the same and all unsigned integers - // will be serialized the same. Other formats, especially compact binary - // formats, may need independent logic for the different sizes. - fn serialize_i8(self, v: i8) -> Result<()> { - self.serialize_i64(i64::from(v)) - } - - fn serialize_i16(self, v: i16) -> Result<()> { - self.serialize_i64(i64::from(v)) - } - - fn serialize_i32(self, v: i32) -> Result<()> { - self.serialize_i64(i64::from(v)) - } - - // Not particularly efficient but this is example code anyway. A more - // performant approach would be to use the `itoa` crate. - fn serialize_i64(self, v: i64) -> Result<()> { - self.xs.write(&self.path, &v.to_string()) - } - - fn serialize_u8(self, v: u8) -> Result<()> { - self.serialize_u64(u64::from(v)) - } - - fn serialize_u16(self, v: u16) -> Result<()> { - self.serialize_u64(u64::from(v)) - } - - fn serialize_u32(self, v: u32) -> Result<()> { - self.serialize_u64(u64::from(v)) - } - - fn serialize_u64(self, v: u64) -> Result<()> { - self.xs.write(&self.path, &v.to_string()) - } - - fn serialize_f32(self, v: f32) -> Result<()> { - self.serialize_f64(f64::from(v)) - } - - fn serialize_f64(self, v: f64) -> Result<()> { - self.xs.write(&self.path, &v.to_string()) - } - - // Serialize a char as a single-character string. Other formats may - // represent this differently. - fn serialize_char(self, v: char) -> Result<()> { - self.serialize_str(&v.to_string()) - } - - // This only works for strings that don't require escape sequences but you - // get the idea. For example it would emit invalid JSON if the input string - // contains a '"' character. - fn serialize_str(self, v: &str) -> Result<()> { - self.xs.write(&self.path, v) - } - - // Serialize a byte array as an array of bytes. Could also use a base64 - // string here. Binary formats will typically represent byte arrays more - // compactly. - fn serialize_bytes(self, v: &[u8]) -> Result<()> { - let data: String = v.iter().map(|b| format!("{b:X}")).collect(); - self.xs.write(&self.path, &data) - } - - // An absent optional is represented as the JSON `null`. - fn serialize_none(self) -> Result<()> { - self.serialize_unit() - } - - // A present optional is represented as just the contained value. Note that - // this is a lossy representation. For example the values `Some(())` and - // `None` both serialize as just `null`. Unfortunately this is typically - // what people expect when working with JSON. Other formats are encouraged - // to behave more intelligently if possible. - fn serialize_some(self, value: &T) -> Result<()> - where - T: ?Sized + Serialize, - { - value.serialize(self) - } - - // In Serde, unit means an anonymous value containing no data. Map this to - // JSON as `null`. - fn serialize_unit(self) -> Result<()> { - self.xs.write(&self.path, "") - } - - // Unit struct means a named value containing no data. Again, since there is - // no data, map this to JSON as `null`. There is no need to serialize the - // name in most formats. - fn serialize_unit_struct(self, _name: &'static str) -> Result<()> { - self.serialize_unit() - } - - // When serializing a unit variant (or any other kind of variant), formats - // can choose whether to keep track of it by index or by name. Binary - // formats typically use the index of the variant and human-readable formats - // typically use the name. - fn serialize_unit_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - ) -> Result<()> { - self.serialize_str(variant) - } - - // As is done here, serializers are encouraged to treat newtype structs as - // insignificant wrappers around the data they contain. - fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result<()> - where - T: ?Sized + Serialize, - { - value.serialize(self) - } - - // Note that newtype variant (and all of the other variant serialization - // methods) refer exclusively to the "externally tagged" enum - // representation. - // - // Serialize this to JSON in externally tagged form as `{ NAME: VALUE }`. - fn serialize_newtype_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - value: &T, - ) -> Result<()> - where - T: ?Sized + Serialize, - { - self.output += "{"; - variant.serialize(&mut *self)?; - self.output += ":"; - value.serialize(&mut *self)?; - self.output += "}"; - Ok(()) - } - - // Now we get to the serialization of compound types. - // - // The start of the sequence, each value, and the end are three separate - // method calls. This one is responsible only for serializing the start, - // which in JSON is `[`. - // - // The length of the sequence may or may not be known ahead of time. This - // doesn't make a difference in JSON because the length is not represented - // explicitly in the serialized form. Some serializers may only be able to - // support sequences for which the length is known up front. - fn serialize_seq(self, _len: Option) -> Result { - self.output += "["; - Ok(self) - } - - // Tuples look just like sequences in JSON. Some formats may be able to - // represent tuples more efficiently by omitting the length, since tuple - // means that the corresponding `Deserialize implementation will know the - // length without needing to look at the serialized data. - fn serialize_tuple(self, len: usize) -> Result { - self.serialize_seq(Some(len)) - } - - // Tuple structs look just like sequences in JSON. - fn serialize_tuple_struct( - self, - _name: &'static str, - len: usize, - ) -> Result { - self.serialize_seq(Some(len)) - } - - // Tuple variants are represented in JSON as `{ NAME: [DATA...] }`. Again - // this method is only responsible for the externally tagged representation. - fn serialize_tuple_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - _len: usize, - ) -> Result { - self.output += "{"; - variant.serialize(&mut *self)?; - self.output += ":["; - Ok(self) - } - - // Maps are represented in JSON as `{ K: V, K: V, ... }`. - fn serialize_map(self, _len: Option) -> Result { - self.output += "{"; - Ok(self) - } - - // Structs look just like maps in JSON. In particular, JSON requires that we - // serialize the field names of the struct. Other formats may be able to - // omit the field names when serializing structs because the corresponding - // Deserialize implementation is required to know what the keys are without - // looking at the serialized data. - fn serialize_struct(self, _name: &'static str, len: usize) -> Result { - self.serialize_map(Some(len)) - } - - // Struct variants are represented in JSON as `{ NAME: { K: V, ... } }`. - // This is the externally tagged representation. - fn serialize_struct_variant( - self, - _name: &'static str, - _variant_index: u32, - variant: &'static str, - _len: usize, - ) -> Result { - self.output += "{"; - variant.serialize(&mut *self)?; - self.output += ":{"; - Ok(self) - } -} - -// The following 7 impls deal with the serialization of compound types like -// sequences and maps. Serialization of such types is begun by a Serializer -// method and followed by zero or more calls to serialize individual elements of -// the compound type and one call to end the compound type. -// -// This impl is SerializeSeq so these methods are called after `serialize_seq` -// is called on the Serializer. -impl<'a> ser::SerializeSeq for &'a mut Serializer { - // Must match the `Ok` type of the serializer. - type Ok = (); - // Must match the `Error` type of the serializer. - type Error = Error; - - // Serialize a single element of the sequence. - fn serialize_element(&mut self, value: &T) -> Result<()> - where - T: ?Sized + Serialize, - { - if !self.output.ends_with('[') { - self.output += ","; - } - value.serialize(&mut **self) - } - - // Close the sequence. - fn end(self) -> Result<()> { - self.output += "]"; - Ok(()) - } -} - -// Same thing but for tuples. -impl<'a> ser::SerializeTuple for &'a mut Serializer { - type Ok = (); - type Error = Error; - - fn serialize_element(&mut self, value: &T) -> Result<()> - where - T: ?Sized + Serialize, - { - if !self.output.ends_with('[') { - self.output += ","; - } - value.serialize(&mut **self) - } - - fn end(self) -> Result<()> { - self.output += "]"; - Ok(()) - } -} - -// Same thing but for tuple structs. -impl<'a> ser::SerializeTupleStruct for &'a mut Serializer { - type Ok = (); - type Error = Error; - - fn serialize_field(&mut self, value: &T) -> Result<()> - where - T: ?Sized + Serialize, - { - if !self.output.ends_with('[') { - self.output += ","; - } - value.serialize(&mut **self) - } - - fn end(self) -> Result<()> { - self.output += "]"; - Ok(()) - } -} - -// Tuple variants are a little different. Refer back to the -// `serialize_tuple_variant` method above: -// -// self.output += "{"; -// variant.serialize(&mut *self)?; -// self.output += ":["; -// -// So the `end` method in this impl is responsible for closing both the `]` and -// the `}`. -impl<'a> ser::SerializeTupleVariant for &'a mut Serializer { - type Ok = (); - type Error = Error; - - fn serialize_field(&mut self, value: &T) -> Result<()> - where - T: ?Sized + Serialize, - { - if !self.output.ends_with('[') { - self.output += ","; - } - value.serialize(&mut **self) - } - - fn end(self) -> Result<()> { - self.output += "]}"; - Ok(()) - } -} - -// Some `Serialize` types are not able to hold a key and value in memory at the -// same time so `SerializeMap` implementations are required to support -// `serialize_key` and `serialize_value` individually. -// -// There is a third optional method on the `SerializeMap` trait. The -// `serialize_entry` method allows serializers to optimize for the case where -// key and value are both available simultaneously. In JSON it doesn't make a -// difference so the default behavior for `serialize_entry` is fine. -impl<'a> ser::SerializeMap for &'a mut Serializer { - type Ok = (); - type Error = Error; - - // The Serde data model allows map keys to be any serializable type. JSON - // only allows string keys so the implementation below will produce invalid - // JSON if the key serializes as something other than a string. - // - // A real JSON serializer would need to validate that map keys are strings. - // This can be done by using a different Serializer to serialize the key - // (instead of `&mut **self`) and having that other serializer only - // implement `serialize_str` and return an error on any other data type. - fn serialize_key(&mut self, key: &T) -> Result<()> - where - T: ?Sized + Serialize, - { - if !self.output.ends_with('{') { - self.output += ","; - } - key.serialize(&mut **self) - } - - // It doesn't make a difference whether the colon is printed at the end of - // `serialize_key` or at the beginning of `serialize_value`. In this case - // the code is a bit simpler having it here. - fn serialize_value(&mut self, value: &T) -> Result<()> - where - T: ?Sized + Serialize, - { - self.output += ":"; - value.serialize(&mut **self) - } - - fn end(self) -> Result<()> { - self.output += "}"; - Ok(()) - } -} - -// Structs are like maps in which the keys are constrained to be compile-time -// constant strings. -impl<'a> ser::SerializeStruct for &'a mut Serializer { - type Ok = (); - type Error = Error; - - fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> - where - T: ?Sized + Serialize, - { - if !self.output.ends_with('{') { - self.output += ","; - } - key.serialize(&mut **self)?; - self.output += ":"; - value.serialize(&mut **self) - } - - fn end(self) -> Result<()> {} -} - -// Similar to `SerializeTupleVariant`, here the `end` method is responsible for -// closing both of the curly braces opened by `serialize_struct_variant`. -impl<'a> ser::SerializeStructVariant for &'a mut Serializer { - type Ok = (); - type Error = Error; - - fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> - where - T: ?Sized + Serialize, - { - if !self.output.ends_with('{') { - self.output += ","; - } - key.serialize(&mut **self)?; - self.output += ":"; - value.serialize(&mut **self) - } - - fn end(self) -> Result<()> { - self.output += "}}"; - Ok(()) - } -} diff --git a/src/provider/net/mod.rs b/src/provider/net/mod.rs index 1f02e56..472c552 100644 --- a/src/provider/net/mod.rs +++ b/src/provider/net/mod.rs @@ -1,32 +1,95 @@ +#[cfg(feature = "net_netlink")] pub mod netlink; -#[cfg(feature = "pnet")] +#[cfg(feature = "net_pnet")] pub mod pnet; +use std::{ + io, + pin::Pin, + task::{Context, Poll}, +}; + +use enum_dispatch::enum_dispatch; +use futures::{stream::Stream, StreamExt}; + use crate::datastructs::NetEvent; -use futures::stream::Stream; -use std::io; -pub trait NetworkSource: Sized { - fn new() -> io::Result; +#[enum_dispatch] +pub trait NetworkSource: Sized + Stream> { async fn collect_current(&mut self) -> anyhow::Result>; - fn stream(&mut self) -> impl Stream> + '_; } pub struct DummyNetworkSource; impl NetworkSource for DummyNetworkSource { - fn new() -> io::Result { - Ok(Self) - } - async fn collect_current(&mut self) -> anyhow::Result> { Ok(vec![]) } +} + +impl Stream for DummyNetworkSource { + type Item = Vec; + + fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(None) + } +} + +#[enum_dispatch(NetworkSource)] +pub enum AgentNetworkSource { + Dummy(DummyNetworkSource), + + #[cfg(feature = "net_netlink")] + Netlink(netlink::NetlinkNetworkSource), + #[cfg(feature = "net_pnet")] + Pnet(pnet::PnetNetworkSource), +} + +// enum_dispatch doesn't support supertraits, we need to do that manually instead +impl Stream for AgentNetworkSource { + type Item = Vec; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.get_mut() { + AgentNetworkSource::Dummy(s) => s.poll_next_unpin(cx), + #[cfg(feature = "net_netlink")] + AgentNetworkSource::Netlink(s) => s.poll_next_unpin(cx), + #[cfg(feature = "net_pnet")] + AgentNetworkSource::Pnet(s) => s.poll_next_unpin(cx), + } + } +} + +#[derive(Clone, Copy, clap::ValueEnum)] +pub enum NetworkSourceKind { + Dummy, + #[cfg(feature = "net_netlink")] + Netlink, + #[cfg(feature = "net_pnet")] + Pnet, +} - fn stream(&mut self) -> impl Stream> + '_ { - futures::stream::empty::>() +impl Default for NetworkSourceKind { + fn default() -> Self { + [ + #[cfg(feature = "net_netlink")] + Self::Netlink, + #[cfg(feature = "net_pnet")] + Self::Pnet, + Self::Dummy, + ][0] } } -pub type PlatformNetworkSource = netlink::NetlinkNetworkSource; +impl AgentNetworkSource { + pub fn new(kind: NetworkSourceKind) -> io::Result { + match kind { + NetworkSourceKind::Dummy => Ok(Self::Dummy(DummyNetworkSource)), + #[cfg(feature = "net_netlink")] + NetworkSourceKind::Netlink => Ok(Self::Netlink(netlink::NetlinkNetworkSource::new()?)), + #[cfg(feature = "net_pnet")] + NetworkSourceKind::Pnet => Ok(Self::Pnet(pnet::PnetNetworkSource::new()?)), + } + } +} diff --git a/src/provider/net/netlink.rs b/src/provider/net/netlink.rs index e69566c..5771eec 100644 --- a/src/provider/net/netlink.rs +++ b/src/provider/net/netlink.rs @@ -1,6 +1,6 @@ use crate::datastructs::{NetEvent, NetEventOp, NetInterface, NetInterfaceCache}; -use async_stream::try_stream; use futures::channel::mpsc::UnboundedReceiver; +use futures::ready; use futures::stream::{Stream, StreamExt}; use netlink_packet_core::{ NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST, @@ -13,11 +13,12 @@ use netlink_proto::{ sys::{protocols::NETLINK_ROUTE, AsyncSocket, SocketAddr}, }; use rtnetlink::constants::{RTMGRP_IPV4_IFADDR, RTMGRP_IPV6_IFADDR, RTMGRP_LINK}; -use std::cell::RefCell; use std::collections::hash_map; use std::io; use std::net::IpAddr; -use std::rc::Rc; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; use std::vec::Vec; use super::NetworkSource; @@ -29,24 +30,6 @@ pub struct NetlinkNetworkSource { } impl NetworkSource for NetlinkNetworkSource { - fn new() -> io::Result { - let (mut connection, handle, messages) = new_connection(NETLINK_ROUTE)?; - // What kinds of broadcast messages we want to listen for. - let nl_mgroup_flags = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; - let nl_addr = SocketAddr::new(0, nl_mgroup_flags); - connection - .socket_mut() - .socket_mut() - .bind(&nl_addr) - .expect("failed to bind"); - tokio::spawn(connection); - Ok(NetlinkNetworkSource { - handle, - messages, - iface_cache: Default::default(), - }) - } - async fn collect_current(&mut self) -> anyhow::Result> { let mut events = Vec::::new(); @@ -92,21 +75,55 @@ impl NetworkSource for NetlinkNetworkSource { Ok(events) } +} - fn stream(&mut self) -> impl Stream> + '_ { - try_stream! { - while let Some((message, _)) = self.messages.next().await { - if let NetlinkMessage{payload: NetlinkPayload::InnerMessage(msg), ..} = message { - for event in self.netevent_from_rtnetlink(&msg)? { - yield event; - } - } +impl Stream for NetlinkNetworkSource { + type Item = Vec; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + loop { + let Some((message, _)) = ready!(self.messages.poll_next_unpin(cx)) else { + log::info!("No more netlink message"); + return Poll::Ready(None); }; + + if let NetlinkMessage { + payload: NetlinkPayload::InnerMessage(msg), + .. + } = message + { + let Ok(events) = self + .netevent_from_rtnetlink(&msg) + .inspect_err(|e| log::error!("Unable to fetch netlink messages ({e})")) + else { + return Poll::Ready(None); + }; + + return Poll::Ready(Some(events)); + } } } } impl NetlinkNetworkSource { + pub fn new() -> io::Result { + let (mut connection, handle, messages) = new_connection(NETLINK_ROUTE)?; + // What kinds of broadcast messages we want to listen for. + let nl_mgroup_flags = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; + let nl_addr = SocketAddr::new(0, nl_mgroup_flags); + connection + .socket_mut() + .socket_mut() + .bind(&nl_addr) + .expect("failed to bind"); + tokio::spawn(connection); + Ok(NetlinkNetworkSource { + handle, + messages, + iface_cache: Default::default(), + }) + } + fn netevent_from_rtnetlink( &mut self, nl_msg: &RouteNetlinkMessage, @@ -172,8 +189,8 @@ impl NetlinkNetworkSource { &mut self, msg: &LinkMessage, ) -> io::Result<( - Rc>, // ref to the (possibly new) impacted interface - Option, // MAC address + Arc>, // ref to the (possibly new) impacted interface + Option, // MAC address )> { let LinkMessage { header, attributes, .. @@ -203,15 +220,15 @@ impl NetlinkNetworkSource { .iface_cache .entry(header.index) .or_insert_with_key(|index| { - RefCell::new(NetInterface::new(*index, iface_name.clone())).into() + Mutex::new(NetInterface::new(*index, iface_name.clone())).into() }); // handle renaming if let Some(iface_name) = iface_name { - let iface_renamed = iface.borrow().name != iface_name; + let iface_renamed = iface.lock().unwrap().name != iface_name; if iface_renamed { log::trace!("name change: {iface:?} now named '{iface_name}'"); - iface.borrow_mut().name = iface_name; + iface.lock().unwrap().name = iface_name; } }; @@ -221,7 +238,7 @@ impl NetlinkNetworkSource { fn nl_addressmessage_decode( &mut self, msg: &AddressMessage, - ) -> io::Result<(Rc>, IpAddr)> { + ) -> io::Result<(Arc>, IpAddr)> { let AddressMessage { header, attributes, .. } = msg; diff --git a/src/provider/net/pnet.rs b/src/provider/net/pnet.rs index 05d41b7..a17237e 100644 --- a/src/provider/net/pnet.rs +++ b/src/provider/net/pnet.rs @@ -1,16 +1,19 @@ use crate::datastructs::{NetEvent, NetEventOp, NetInterface, NetInterfaceCache}; -use async_stream::try_stream; +use futures::ready; use futures::stream::Stream; use ipnetwork::IpNetwork; use pnet_base::MacAddr; -use std::cell::RefCell; use std::collections::{hash_map, HashMap, HashSet}; -use std::error::Error; use std::io; -use std::rc::Rc; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; use std::time::Duration; +use tokio::time::Interval; -const IFACE_PERIOD_SECONDS: u64 = 60; +use super::NetworkSource; + +const IFACE_PERIOD_SECONDS: f32 = 60.0; #[derive(Debug, Eq, Hash, PartialEq)] enum Address { @@ -34,33 +37,41 @@ impl InterfaceInfo { } type AddressesState = HashMap; -pub struct NetworkSource { +pub struct PnetNetworkSource { addresses_cache: AddressesState, - iface_cache: &'static mut NetInterfaceCache, + iface_cache: NetInterfaceCache, + timer: Interval, } -impl NetworkSource { - pub fn new(iface_cache: &'static mut NetInterfaceCache) -> io::Result { - Ok(NetworkSource { - addresses_cache: AddressesState::new(), - iface_cache, - }) - } +impl PnetNetworkSource {} - pub async fn collect_current(&mut self) -> anyhow::Result> { +impl NetworkSource for PnetNetworkSource { + async fn collect_current(&mut self) -> anyhow::Result> { Ok(self.get_ifconfig_data()?) } +} - pub fn stream(&mut self) -> impl Stream> + '_ { - try_stream! { - let mut interval = tokio::time::interval(Duration::from_secs(IFACE_PERIOD_SECONDS)); - loop { - interval.tick().await; - for net_event in self.get_ifconfig_data()? { - yield net_event; - } - } - } +impl Stream for PnetNetworkSource { + type Item = Vec; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + ready!(self.timer.poll_tick(cx)); + + Poll::Ready( + self.get_ifconfig_data() + .inspect_err(|e| log::error!("Unable to fetch network data {e}")) + .ok(), + ) + } +} + +impl PnetNetworkSource { + pub fn new() -> io::Result { + Ok(Self { + addresses_cache: AddressesState::new(), + iface_cache: HashMap::new(), + timer: tokio::time::interval(Duration::from_secs_f32(IFACE_PERIOD_SECONDS)), + }) } fn get_ifconfig_data(&mut self) -> io::Result> { @@ -109,7 +120,11 @@ impl NetworkSource { Some(iface_info) => { let iface_adresses = &iface_info.addresses; for disappearing in cached_info.addresses.difference(iface_adresses) { - log::trace!("disappearing {}: {:?}", iface.borrow().name, disappearing); + log::trace!( + "disappearing {}: {:?}", + iface.lock().unwrap().name, + disappearing + ); events.push(NetEvent { iface: iface.clone(), op: match disappearing { @@ -134,7 +149,7 @@ impl NetworkSource { .iface_cache .entry(*iface_index) .or_insert_with_key(|index| { - let iface = Rc::new(RefCell::new(NetInterface::new( + let iface = Arc::new(Mutex::new(NetInterface::new( *index, Some(iface_info.name.clone()), ))); @@ -151,7 +166,7 @@ impl NetworkSource { &empty_address_set }; for appearing in iface_info.addresses.difference(cache_adresses) { - log::trace!("appearing {}: {:?}", iface.borrow().name, appearing); + log::trace!("appearing {}: {:?}", iface.lock().unwrap().name, appearing); events.push(NetEvent { iface: iface.clone(), op: match appearing { diff --git a/src/publisher/mod.rs b/src/publisher/mod.rs index 11658d6..7d95bd2 100644 --- a/src/publisher/mod.rs +++ b/src/publisher/mod.rs @@ -3,7 +3,8 @@ pub mod xenstore; use crate::datastructs::{KernelInfo, NetEvent, NetEventOp}; use enum_dispatch::enum_dispatch; -use std::{env, io}; +use std::io; +use tokio::sync::mpsc; use xenstore::{rfc::XenstoreRfc, std::XenstoreStd, XsBuild}; use xenstore_rs::Xs; @@ -17,6 +18,13 @@ pub struct MemoryInfo { pub mem_total: usize, } +pub enum GuestMetric { + OsInfo(OsInfo), + MemoryInfo(MemoryInfo), + Network(NetEvent), + CleanupIfaces, +} + #[enum_dispatch] pub trait Publisher: Sized { fn publish_osinfo(&mut self, os_info: &OsInfo) -> io::Result<()>; @@ -50,7 +58,7 @@ impl Publisher for ConsolePublisher { Ok(()) } fn publish_netevent(&mut self, event: &NetEvent) -> io::Result<()> { - let iface_id = &event.iface.borrow().name; + let iface_id = &event.iface.lock().unwrap().name; match &event.op { NetEventOp::AddIface => println!("{iface_id} +IFACE"), NetEventOp::RmIface => println!("{iface_id} -IFACE"), @@ -67,6 +75,14 @@ impl Publisher for ConsolePublisher { } } +#[derive(Clone, Copy, Default, Debug, clap::ValueEnum)] +pub enum PublisherKind { + Console, + #[default] + Xenstore, + XenstoreRfc, +} + #[enum_dispatch(Publisher)] pub enum AgentPublisher { Console(ConsolePublisher), @@ -76,11 +92,32 @@ pub enum AgentPublisher { impl AgentPublisher { #[allow(clippy::wildcard_in_or_patterns)] - pub fn new() -> io::Result { - match env::var("XENSTORE_PUBLISHER").unwrap_or_default().as_str() { - "console" => Ok(Self::Console(ConsolePublisher)), - "rfc" => Ok(Self::XenstoreRfc(XenstoreRfc::new(XS::new()?))), - "std" | _ => Ok(Self::XenstoreStd(XenstoreStd::new(XS::new()?))), + pub fn new(kind: PublisherKind) -> io::Result { + match kind { + PublisherKind::Console => Ok(Self::Console(ConsolePublisher)), + PublisherKind::Xenstore => Ok(Self::XenstoreStd(XenstoreStd::new(XS::new()?))), + PublisherKind::XenstoreRfc => Ok(Self::XenstoreRfc(XenstoreRfc::new(XS::new()?))), } } } + +pub fn spawn_publisher( + kind: PublisherKind, +) -> io::Result> { + let (tx, mut rx) = mpsc::channel(4); + let mut publisher = AgentPublisher::::new(kind)?; + + tokio::spawn(async move { + while let Some(metric) = rx.recv().await { + match &metric { + GuestMetric::OsInfo(os_info) => publisher.publish_osinfo(os_info), + GuestMetric::MemoryInfo(memory_info) => publisher.publish_memory(memory_info), + GuestMetric::Network(net_event) => publisher.publish_netevent(net_event), + GuestMetric::CleanupIfaces => publisher.cleanup_ifaces(), + } + .unwrap() + } + }); + + Ok(tx) +} diff --git a/src/publisher/xenstore/mod.rs b/src/publisher/xenstore/mod.rs index 1f11c21..57eaf6c 100644 --- a/src/publisher/xenstore/mod.rs +++ b/src/publisher/xenstore/mod.rs @@ -15,7 +15,7 @@ pub fn xs_unpublish(xs: &impl Xs, key: &str) -> io::Result<()> { xs.rm(key) } -pub trait XsBuild: Sized + Xs { +pub trait XsBuild: Sized + Xs + Send { fn new() -> io::Result; } diff --git a/src/publisher/xenstore/rfc.rs b/src/publisher/xenstore/rfc.rs index d43d88c..645819e 100644 --- a/src/publisher/xenstore/rfc.rs +++ b/src/publisher/xenstore/rfc.rs @@ -52,14 +52,14 @@ impl Publisher for XenstoreRfc { #[allow(clippy::useless_format)] fn publish_netevent(&mut self, event: &NetEvent) -> io::Result<()> { - let iface_id = &event.iface.borrow().index; + let iface_id = &event.iface.lock().unwrap().index; let xs_iface_prefix = format!("data/net/{iface_id}"); match &event.op { NetEventOp::AddIface => { xs_publish( &self.0, &format!("{xs_iface_prefix}"), - &event.iface.borrow().name, + &event.iface.lock().unwrap().name, )?; } NetEventOp::RmIface => { diff --git a/src/publisher/xenstore/std.rs b/src/publisher/xenstore/std.rs index 6afc292..400e1fa 100644 --- a/src/publisher/xenstore/std.rs +++ b/src/publisher/xenstore/std.rs @@ -120,8 +120,9 @@ impl Publisher for XenstoreStd { // see https://xenbits.xen.org/docs/unstable/misc/xenstore-paths.html#domain-controlled-paths fn publish_netevent(&mut self, event: &NetEvent) -> io::Result<()> { - let iface_id = match event.iface.borrow().toolstack_iface { + let iface_id = match event.iface.lock().unwrap().toolstack_iface { ToolstackNetInterface::Vif(id) => id, + ToolstackNetInterface::Unknown => u32::MAX, }; let xs_iface_prefix = format!("attr/vif/{iface_id}"); match &event.op { @@ -132,7 +133,7 @@ impl Publisher for XenstoreStd { xs_unpublish(&self.xs, &xs_iface_prefix)?; } NetEventOp::AddIp(address) => { - let key_suffix = self.munged_address(address, &event.iface.borrow())?; + let key_suffix = self.munged_address(address, &event.iface.lock().unwrap())?; xs_publish( &self.xs, &format!("{xs_iface_prefix}/{key_suffix}"), @@ -140,7 +141,7 @@ impl Publisher for XenstoreStd { )?; } NetEventOp::RmIp(address) => { - let key_suffix = self.munged_address(address, &event.iface.borrow())?; + let key_suffix = self.munged_address(address, &event.iface.lock().unwrap())?; xs_unpublish(&self.xs, &format!("{xs_iface_prefix}/{key_suffix}"))?; } diff --git a/src/vif_detect/freebsd.rs b/src/vif_detect/freebsd.rs index b201bd5..aaa08ea 100644 --- a/src/vif_detect/freebsd.rs +++ b/src/vif_detect/freebsd.rs @@ -1,7 +1,7 @@ use crate::datastructs::ToolstackNetInterface; #[derive(Default)] -struct FreebsdVifDetector; +pub struct FreebsdVifDetector; impl super::VifDetector for FreebsdVifDetector { // identifies a VIF as named "xn%ID" diff --git a/src/vif_detect/mod.rs b/src/vif_detect/mod.rs index e57181e..03ac7f5 100644 --- a/src/vif_detect/mod.rs +++ b/src/vif_detect/mod.rs @@ -1,6 +1,8 @@ use crate::datastructs::ToolstackNetInterface; +#[cfg(target_os = "freebsd")] pub mod freebsd; +#[cfg(target_os = "linux")] pub mod linux; pub trait VifDetector: Default { @@ -8,7 +10,7 @@ pub trait VifDetector: Default { } #[derive(Default)] -struct DummyVifDetector; +pub struct DummyVifDetector; impl VifDetector for DummyVifDetector { fn get_toolstack_interface(_iface_name: &str) -> Option { From 33a34f954dd472df92dad7c0f5667df1f01a28d8 Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Fri, 29 Nov 2024 10:36:14 +0100 Subject: [PATCH 05/24] Reorganize in crates --- Cargo.lock | 432 ++++++++---------- Cargo.toml | 58 +-- guest-metrics/Cargo.toml | 11 + guest-metrics/src/lib.rs | 66 +++ guest-metrics/src/plugin.rs | 13 + providers/provider-memory/Cargo.toml | 9 + .../provider-memory/src}/bsd.rs | 0 providers/provider-memory/src/lib.rs | 58 +++ .../provider-memory/src}/linux.rs | 2 + providers/provider-memory/src/windows.rs | 21 + providers/provider-netlink/Cargo.toml | 16 + .../provider-netlink/src/lib.rs | 9 +- providers/provider-netlink/src/rewrite.rs | 89 ++++ providers/provider-os/Cargo.toml | 13 + providers/provider-os/src/lib.rs | 39 ++ providers/provider-simple/Cargo.toml | 13 + providers/provider-simple/src/lib.rs | 176 +++++++ .../provider-simple/src/original.rs | 71 ++- providers/vif-detect/Cargo.toml | 8 + .../vif-detect/src}/freebsd.rs | 0 .../mod.rs => providers/vif-detect/src/lib.rs | 11 +- .../vif-detect/src}/linux.rs | 3 +- publishers/publisher-console/Cargo.toml | 11 + publishers/publisher-console/src/lib.rs | 77 ++++ publishers/publisher-xenstore/Cargo.toml | 19 + publishers/publisher-xenstore/src/lib.rs | 62 +++ publishers/publisher-xenstore/src/rfc.rs | 134 ++++++ .../publisher-xenstore/src}/std.rs | 84 +++- src/datastructs.rs | 73 --- src/logic.rs | 87 ---- src/provider/kernel.rs | 18 - src/provider/memory/mod.rs | 41 -- src/provider/mod.rs | 3 - src/provider/net/mod.rs | 95 ---- src/publisher/mod.rs | 123 ----- src/publisher/xenstore/mod.rs | 29 -- src/publisher/xenstore/rfc.rs | 92 ---- xen-guest-agent/Cargo.toml | 33 ++ xen-guest-agent/src/logic.rs | 26 ++ {src => xen-guest-agent/src}/main.rs | 21 +- xen-guest-agent/src/plugins.rs | 34 ++ xen-guest-agent/src/publisher.rs | 39 ++ 42 files changed, 1266 insertions(+), 953 deletions(-) create mode 100644 guest-metrics/Cargo.toml create mode 100644 guest-metrics/src/lib.rs create mode 100644 guest-metrics/src/plugin.rs create mode 100644 providers/provider-memory/Cargo.toml rename {src/provider/memory => providers/provider-memory/src}/bsd.rs (100%) create mode 100644 providers/provider-memory/src/lib.rs rename {src/provider/memory => providers/provider-memory/src}/linux.rs (92%) create mode 100644 providers/provider-memory/src/windows.rs create mode 100644 providers/provider-netlink/Cargo.toml rename src/provider/net/netlink.rs => providers/provider-netlink/src/lib.rs (97%) create mode 100644 providers/provider-netlink/src/rewrite.rs create mode 100644 providers/provider-os/Cargo.toml create mode 100644 providers/provider-os/src/lib.rs create mode 100644 providers/provider-simple/Cargo.toml create mode 100644 providers/provider-simple/src/lib.rs rename src/provider/net/pnet.rs => providers/provider-simple/src/original.rs (77%) create mode 100644 providers/vif-detect/Cargo.toml rename {src/vif_detect => providers/vif-detect/src}/freebsd.rs (100%) rename src/vif_detect/mod.rs => providers/vif-detect/src/lib.rs (61%) rename {src/vif_detect => providers/vif-detect/src}/linux.rs (96%) create mode 100644 publishers/publisher-console/Cargo.toml create mode 100644 publishers/publisher-console/src/lib.rs create mode 100644 publishers/publisher-xenstore/Cargo.toml create mode 100644 publishers/publisher-xenstore/src/lib.rs create mode 100644 publishers/publisher-xenstore/src/rfc.rs rename {src/publisher/xenstore => publishers/publisher-xenstore/src}/std.rs (71%) delete mode 100644 src/datastructs.rs delete mode 100644 src/logic.rs delete mode 100644 src/provider/kernel.rs delete mode 100644 src/provider/memory/mod.rs delete mode 100644 src/provider/mod.rs delete mode 100644 src/provider/net/mod.rs delete mode 100644 src/publisher/mod.rs delete mode 100644 src/publisher/xenstore/mod.rs delete mode 100644 src/publisher/xenstore/rfc.rs create mode 100644 xen-guest-agent/Cargo.toml create mode 100644 xen-guest-agent/src/logic.rs rename {src => xen-guest-agent/src}/main.rs (74%) create mode 100644 xen-guest-agent/src/plugins.rs create mode 100644 xen-guest-agent/src/publisher.rs diff --git a/Cargo.lock b/Cargo.lock index 5299c91..8094ca5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,10 +115,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] -name = "bytes" -version = "1.8.0" +name = "cc" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -326,24 +329,38 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "guest-metrics" +version = "0.6.0-dev" +dependencies = [ + "futures", + "os_info", + "uuid", +] + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hostname" version = "0.3.1" @@ -361,15 +378,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" -[[package]] -name = "ipnetwork" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" -dependencies = [ - "serde", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -378,25 +386,15 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "libc" -version = "0.2.164" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" - -[[package]] -name = "lock_api" -version = "0.4.12" +version = "0.2.166" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] +checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" [[package]] name = "log" @@ -426,99 +424,17 @@ dependencies = [ ] [[package]] -name = "mio" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" -dependencies = [ - "hermit-abi", - "libc", - "wasi", - "windows-sys 0.52.0", -] - -[[package]] -name = "netlink-packet-core" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" -dependencies = [ - "anyhow", - "byteorder", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-route" -version = "0.19.0" +name = "network-interface" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74c171cd77b4ee8c7708da746ce392440cb7bcf618d122ec9ecc607b12938bf4" +checksum = "433419f898328beca4f2c6c73a1b52540658d92b0a99f0269330457e0fd998d5" dependencies = [ - "anyhow", - "byteorder", + "cc", "libc", - "log", - "netlink-packet-core", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-utils" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" -dependencies = [ - "anyhow", - "byteorder", - "paste", - "thiserror", -] - -[[package]] -name = "netlink-proto" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b33524dc0968bfad349684447bfce6db937a9ac3332a1fe60c0c5a5ce63f21" -dependencies = [ - "bytes", - "futures", - "log", - "netlink-packet-core", - "netlink-sys", "thiserror", - "tokio", -] - -[[package]] -name = "netlink-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416060d346fbaf1f23f9512963e3e878f1a78e707cb699ba9215761754244307" -dependencies = [ - "bytes", - "futures", - "libc", - "log", - "tokio", -] - -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags", - "cfg-if", - "libc", + "winapi", ] -[[package]] -name = "no-std-net" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" - [[package]] name = "num-conv" version = "0.1.0" @@ -556,38 +472,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" dependencies = [ "log", + "serde", "windows-sys 0.52.0", ] -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pin-project-lite" version = "0.2.15" @@ -601,68 +489,78 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "pnet_base" -version = "0.35.0" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ - "no-std-net", + "unicode-ident", ] [[package]] -name = "pnet_datalink" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79e70ec0be163102a332e1d2d5586d362ad76b01cec86f830241f2b6452a7b7" +name = "provider-memory" +version = "0.1.0" dependencies = [ - "ipnetwork", - "libc", - "pnet_base", - "pnet_sys", - "winapi", + "futures", + "guest-metrics", + "tokio", ] [[package]] -name = "pnet_sys" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d4643d3d4db6b08741050c2f3afa9a892c4244c085a72fcda93c9c2c9a00f4b" +name = "provider-os" +version = "0.6.0-dev" dependencies = [ - "libc", - "winapi", + "futures", + "guest-metrics", + "uname", ] [[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +name = "provider-simple" +version = "0.1.0" +dependencies = [ + "futures", + "guest-metrics", + "network-interface", + "tokio", + "uuid", + "vif-detect", +] [[package]] -name = "proc-macro2" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +name = "publisher-console" +version = "0.6.0-dev" dependencies = [ - "unicode-ident", + "futures", + "guest-metrics", + "uuid", ] [[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +name = "publisher-xenstore" +version = "0.1.0" dependencies = [ - "proc-macro2", + "futures", + "guest-metrics", + "log", + "uuid", + "xenstore-rs", + "xenstore-win", ] [[package]] -name = "redox_syscall" -version = "0.5.7" +name = "quote" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "bitflags", + "proc-macro2", ] [[package]] @@ -694,24 +592,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "rtnetlink" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b684475344d8df1859ddb2d395dd3dac4f8f3422a1aa0725993cb375fc5caba5" -dependencies = [ - "futures", - "log", - "netlink-packet-core", - "netlink-packet-route", - "netlink-packet-utils", - "netlink-proto", - "netlink-sys", - "nix", - "thiserror", - "tokio", -] - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -727,12 +607,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "serde" version = "1.0.215" @@ -754,13 +628,10 @@ dependencies = [ ] [[package]] -name = "signal-hook-registry" -version = "1.4.2" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" @@ -771,22 +642,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "strsim" version = "0.11.1" @@ -891,15 +746,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", - "bytes", - "libc", - "mio", - "parking_lot", "pin-project-lite", - "signal-hook-registry", - "socket2", "tokio-macros", - "windows-sys 0.52.0", ] [[package]] @@ -934,12 +782,29 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", +] + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vif-detect" +version = "0.1.0" +dependencies = [ + "guest-metrics", + "log", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -987,6 +852,70 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -1078,24 +1007,29 @@ dependencies = [ "enum_dispatch", "env_logger", "futures", - "ipnetwork", - "libc", + "guest-metrics", "log", - "netlink-packet-core", - "netlink-packet-route", - "netlink-proto", - "os_info", - "pnet_base", - "pnet_datalink", - "rtnetlink", + "provider-memory", + "provider-os", + "provider-simple", + "publisher-console", + "publisher-xenstore", "sysctl", "syslog", "tokio", - "uname", - "xenstore-rs", ] [[package]] name = "xenstore-rs" version = "1.0.0-dev" source = "git+https://github.com/TSnake41/xenstore-rs.git?branch=pure-rust#f02ef37f82b7401e7f13df22e319078b341a03be" + +[[package]] +name = "xenstore-win" +version = "0.1.0" +source = "git+https://github.com/TSnake41/xenstore-win.git#a38e4284807891a4bf18046bc69ff5e5a9c7862a" +dependencies = [ + "log", + "windows", + "xenstore-rs", +] diff --git a/Cargo.toml b/Cargo.toml index af58cdc..75caa98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,47 +1,19 @@ -[package] -name = "xen-guest-agent" -version = "0.5.0-dev" -authors = ["Yann Dirson "] -edition = "2018" -rust-version = "1.76" -license = "AGPL-3.0-only" +[workspace] +package.version = "0.6.0-dev" +package.repository = "https://gitlab.com/xen-project/xen-guest-agent" +package.categories = ["virtualization"] +package.edition = "2021" -[dependencies] -futures = "0.3.26" -libc = "0.2.139" -tokio = { version = "1.25.0", features = ["full"] } -netlink-packet-core = { version = "0.7.0", optional = true } -netlink-packet-route = { version = ">=0.18.0, <0.20", optional = true } -netlink-proto = { version = "0.11.2", optional = true } -rtnetlink = { version = "0.14.0", optional = true } -os_info = { version = "3", default-features = false } -pnet_datalink = { version = "*", optional = true } -pnet_base = { version = "*", optional = true } -ipnetwork = { version = "*", optional = true } -log = "0.4.0" -env_logger = ">=0.10.0" -clap = { version = "4.4.8", features = ["derive"] } -enum_dispatch = "0.3" -anyhow = "1.0" - -[dependencies.xenstore-rs] -git = "https://github.com/TSnake41/xenstore-rs.git" -branch = "pure-rust" -optional = true - -[target.'cfg(unix)'.dependencies] -uname = "0.1.1" -syslog = "6.0" - -[target.'cfg(target_os = "freebsd")'.dependencies] -sysctl = "0.5.0" - -[features] -default = ["xenstore", "net_netlink"] -xenstore = ["dep:xenstore-rs"] -net_netlink = ["dep:netlink-proto", "dep:netlink-packet-core", "dep:netlink-packet-route", - "dep:rtnetlink"] -net_pnet = ["dep:pnet_datalink", "dep:pnet_base", "dep:ipnetwork"] +members = [ + "xen-guest-agent", + "guest-metrics", + "providers/provider-os", + "providers/provider-memory", + #"providers/provider-netlink", + "providers/provider-simple", + "publishers/publisher-xenstore", + "publishers/publisher-console", +] [profile.release] lto = true diff --git a/guest-metrics/Cargo.toml b/guest-metrics/Cargo.toml new file mode 100644 index 0000000..6dc6dc5 --- /dev/null +++ b/guest-metrics/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "guest-metrics" +edition.workspace = true +version.workspace = true +repository.workspace = true +categories.workspace = true + +[dependencies] +futures = "0.3" +uuid = "1.11" +os_info = "3" \ No newline at end of file diff --git a/guest-metrics/src/lib.rs b/guest-metrics/src/lib.rs new file mode 100644 index 0000000..04df3a3 --- /dev/null +++ b/guest-metrics/src/lib.rs @@ -0,0 +1,66 @@ +pub mod plugin; + +use std::net::IpAddr; + +use uuid::Uuid; + +#[derive(Debug)] +pub struct KernelInfo { + pub release: String, +} + +#[non_exhaustive] +#[derive(Clone, Debug, Default)] +pub enum ToolstackNetInterface { + #[default] + Unknown, + Vif(u32), + // SRIOV, + // PciPassthrough, + // UsbPassthrough, +} + +#[derive(Clone, Debug)] +pub struct NetInterface { + pub uuid: Uuid, + pub index: u32, + pub name: String, + pub toolstack_iface: ToolstackNetInterface, +} + +#[derive(Debug)] +pub enum NetEventOp { + AddMac(String), + RmMac(String), + AddIp(IpAddr), + RmIp(IpAddr), +} + +#[derive(Debug)] +pub struct NetEvent { + pub iface_id: Uuid, + pub op: NetEventOp, +} + +#[derive(Debug)] +pub struct OsInfo { + pub os_info: os_info::Info, + pub kernel_info: Option, +} + +#[derive(Debug)] +pub struct MemoryInfo { + pub mem_free: usize, + pub mem_total: usize, +} + +pub enum GuestMetric { + OperatingSystem(OsInfo), + AddIface(NetInterface), + RmIface(Uuid), + Memory(MemoryInfo), + Network(NetEvent), + CleanupIfaces, +} + +pub use os_info; \ No newline at end of file diff --git a/guest-metrics/src/plugin.rs b/guest-metrics/src/plugin.rs new file mode 100644 index 0000000..6c94625 --- /dev/null +++ b/guest-metrics/src/plugin.rs @@ -0,0 +1,13 @@ +use std::future::Future; + +use futures::channel::mpsc; + +use crate::GuestMetric; + +pub trait GuestAgentPlugin { + fn run(self, channel: mpsc::Sender) -> impl Future + Send; +} + +pub trait GuestAgentPublisher { + fn run(self, channel: mpsc::Receiver) -> impl Future + Send; +} diff --git a/providers/provider-memory/Cargo.toml b/providers/provider-memory/Cargo.toml new file mode 100644 index 0000000..3e957b4 --- /dev/null +++ b/providers/provider-memory/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "provider-memory" +version = "0.1.0" +edition = "2021" + +[dependencies] +guest-metrics = { path = "../../guest-metrics" } +futures = "0.3" +tokio = { version = "1.25.0", features = ["time"] } diff --git a/src/provider/memory/bsd.rs b/providers/provider-memory/src/bsd.rs similarity index 100% rename from src/provider/memory/bsd.rs rename to providers/provider-memory/src/bsd.rs diff --git a/providers/provider-memory/src/lib.rs b/providers/provider-memory/src/lib.rs new file mode 100644 index 0000000..5984558 --- /dev/null +++ b/providers/provider-memory/src/lib.rs @@ -0,0 +1,58 @@ +use std::{io, time::Duration}; + +use futures::{channel::mpsc, SinkExt}; +use guest_metrics::{plugin::GuestAgentPlugin, MemoryInfo}; + +#[cfg(target_os = "freebsd")] +pub mod bsd; + +#[cfg(target_os = "linux")] +pub mod linux; + +#[cfg(target_os = "windows")] +pub mod windows; + +pub trait MemorySource: Sized { + fn new() -> io::Result; + fn get_total_kb(&mut self) -> io::Result; + fn get_available_kb(&mut self) -> io::Result; +} + +#[cfg(target_os = "linux")] +pub type PlatformMemorySource = linux::LinuxMemorySource; + +#[cfg(target_os = "freebsd")] +pub type PlatformMemorySource = bsd::BsdMemorySource; + +#[cfg(target_os = "windows")] +pub type PlatformMemorySource = windows::WindowsMemorySource; + +pub struct MemoryPlugin; + +impl GuestAgentPlugin for MemoryPlugin { + fn run( + self, + mut channel: mpsc::Sender, + ) -> impl std::future::Future + Send { + async move { + let mut timer = tokio::time::interval(Duration::from_secs_f32(5.0)); + let mut memory_source = + PlatformMemorySource::new().expect("Unable to get memory information"); + + loop { + timer.tick().await; + + if channel + .send(guest_metrics::GuestMetric::Memory(MemoryInfo { + mem_free: memory_source.get_available_kb().unwrap(), + mem_total: memory_source.get_total_kb().unwrap(), + })) + .await + .is_err() + { + break; + } + } + } + } +} diff --git a/src/provider/memory/linux.rs b/providers/provider-memory/src/linux.rs similarity index 92% rename from src/provider/memory/linux.rs rename to providers/provider-memory/src/linux.rs index 1c39e1e..9f30485 100644 --- a/src/provider/memory/linux.rs +++ b/providers/provider-memory/src/linux.rs @@ -39,8 +39,10 @@ impl MemorySource for LinuxMemorySource { fn get_total_kb(&mut self) -> io::Result { self.get_num_field("MemTotal:") + .map(|m| /* Convert from KB */ m * 1024) } fn get_available_kb(&mut self) -> io::Result { self.get_num_field("MemAvailable:") + .map(|m| /* Convert from KB */ m * 1024) } } diff --git a/providers/provider-memory/src/windows.rs b/providers/provider-memory/src/windows.rs new file mode 100644 index 0000000..daa953f --- /dev/null +++ b/providers/provider-memory/src/windows.rs @@ -0,0 +1,21 @@ +#[derive(Default)] +pub struct WindowsMemorySource; + +impl MemorySource for WindowsMemorySource { + fn new() -> io::Result { + Ok(Self) + } + + fn get_total_kb(&mut self) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Unsupported, + "no implementation for mem_total", + )) + } + fn get_available_kb(&mut self) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Unsupported, + "no implementation for mem_avail", + )) + } +} diff --git a/providers/provider-netlink/Cargo.toml b/providers/provider-netlink/Cargo.toml new file mode 100644 index 0000000..f558030 --- /dev/null +++ b/providers/provider-netlink/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "provider-netlink" +version = "0.1.0" +edition = "2021" + +[dependencies] +futures = "0.3" +guest-metrics = { path = "../../guest-metrics" } +tokio = { version = "1", features = ["rt"] } +uuid = "1.11" +anyhow = "*" + +netlink-packet-core = { version = "0.7.0" } +netlink-packet-route = { version = ">=0.18.0, <0.20" } +netlink-proto = { version = "0.11.2" } +rtnetlink = { version = "0.14.0" } diff --git a/src/provider/net/netlink.rs b/providers/provider-netlink/src/lib.rs similarity index 97% rename from src/provider/net/netlink.rs rename to providers/provider-netlink/src/lib.rs index 5771eec..c24454a 100644 --- a/src/provider/net/netlink.rs +++ b/providers/provider-netlink/src/lib.rs @@ -1,4 +1,3 @@ -use crate::datastructs::{NetEvent, NetEventOp, NetInterface, NetInterfaceCache}; use futures::channel::mpsc::UnboundedReceiver; use futures::ready; use futures::stream::{Stream, StreamExt}; @@ -21,15 +20,13 @@ use std::sync::{Arc, Mutex}; use std::task::{Context, Poll}; use std::vec::Vec; -use super::NetworkSource; - pub struct NetlinkNetworkSource { handle: netlink_proto::ConnectionHandle, messages: UnboundedReceiver<(NetlinkMessage, SocketAddr)>, - iface_cache: NetInterfaceCache, + iface_cache: HashMap<>, } -impl NetworkSource for NetlinkNetworkSource { +impl NetlinkNetworkSource { async fn collect_current(&mut self) -> anyhow::Result> { let mut events = Vec::::new(); @@ -270,4 +267,4 @@ impl NetlinkNetworkSource { )), } } -} +} \ No newline at end of file diff --git a/providers/provider-netlink/src/rewrite.rs b/providers/provider-netlink/src/rewrite.rs new file mode 100644 index 0000000..047c06d --- /dev/null +++ b/providers/provider-netlink/src/rewrite.rs @@ -0,0 +1,89 @@ +use std::{collections::HashMap, io}; + +use futures::{channel::mpsc::UnboundedReceiver, StreamExt}; +use guest_metrics::plugin::GuestAgentPlugin; +use netlink_packet_core::{NetlinkMessage, NetlinkPayload}; +use netlink_packet_route::{link::LinkAttribute, RouteNetlinkMessage}; +use netlink_proto::{ + new_connection, + sys::{protocols::NETLINK_ROUTE, AsyncSocket, SocketAddr}, +}; +use rtnetlink::constants::{RTMGRP_IPV4_IFADDR, RTMGRP_IPV6_IFADDR, RTMGRP_LINK}; +use uuid::Uuid; + +pub struct NetlinkConnection { + handle: netlink_proto::ConnectionHandle, + messages: UnboundedReceiver<(NetlinkMessage, SocketAddr)>, + + interfaces: HashMap, +} + +impl NetlinkConnection { + pub fn new() -> io::Result { + let (mut connection, handle, messages) = new_connection(NETLINK_ROUTE)?; + // What kinds of broadcast messages we want to listen for. + let nl_mgroup_flags = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; + let nl_addr = SocketAddr::new(0, nl_mgroup_flags); + connection + .socket_mut() + .socket_mut() + .bind(&nl_addr) + .expect("failed to bind"); + tokio::spawn(connection); + Ok(Self { handle, messages }) + } +} + +pub struct NetlinkPlugin; + +impl GuestAgentPlugin for NetlinkPlugin { + fn run( + self, + mut channel: futures::channel::mpsc::Sender, + ) -> impl std::future::Future + Send { + async move { + let mut connection = NetlinkConnection::new().unwrap(); + + if let Some((msg, _)) = connection.messages.next().await { + if let NetlinkPayload::InnerMessage(inner_msg) = msg.payload { + process_message(inner_msg); + } + } + } + } +} + +fn process_message(inner_msg: RouteNetlinkMessage) { + match inner_msg { + RouteNetlinkMessage::NewLink(link_message) | RouteNetlinkMessage::GetLink(link_message) => { + let Some(ifname) = + link_message + .attributes + .iter() + .find_map(|attribute| match attribute { + LinkAttribute::IfName(n) => Some(n), + _ => None, + }) + else { + return; + }; + + let Some(mac) = link_message.attributes.iter().find_map(|attribute| match attribute { + LinkAttribute::Address() + }) + } + RouteNetlinkMessage::DelLink(link_message) => { + todo!() + } + RouteNetlinkMessage::NewAddress(address_message) => { + todo!() + } + RouteNetlinkMessage::DelAddress(address_message) => { + todo!() + } + RouteNetlinkMessage::GetAddress(address_message) => { + todo!() + } + _ => todo!(), + } +} diff --git a/providers/provider-os/Cargo.toml b/providers/provider-os/Cargo.toml new file mode 100644 index 0000000..14e82e4 --- /dev/null +++ b/providers/provider-os/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "provider-os" +edition = "2021" +version.workspace = true +repository.workspace = true +categories.workspace = true + +[dependencies] +guest-metrics = { path = "../../guest-metrics" } +futures = "0.3" + +[target.'cfg(unix)'.dependencies] +uname = "0.1.1" \ No newline at end of file diff --git a/providers/provider-os/src/lib.rs b/providers/provider-os/src/lib.rs new file mode 100644 index 0000000..5c9bc0b --- /dev/null +++ b/providers/provider-os/src/lib.rs @@ -0,0 +1,39 @@ +use futures::{channel::mpsc, SinkExt}; +use guest_metrics::{os_info, plugin::GuestAgentPlugin, GuestMetric, KernelInfo, OsInfo}; +use std::io; + +// UNIX uname() implementation +#[cfg(unix)] +pub fn collect_kernel() -> io::Result> { + let uname_info = uname::uname()?; + Ok(Some(KernelInfo { + release: uname_info.release, + })) +} + +// default implementation +#[cfg(not(unix))] +pub fn collect_kernel() -> io::Result> { + Ok(None) +} + +pub struct OsInfoPlugin; + +impl GuestAgentPlugin for OsInfoPlugin { + fn run( + self, + mut channel: mpsc::Sender, + ) -> impl std::future::Future + Send { + async move { + let kernel_info = collect_kernel().expect("Unable to fetch kernel information"); + + channel + .send(GuestMetric::OperatingSystem(OsInfo { + os_info: os_info::get(), + kernel_info, + })) + .await + .ok(); + } + } +} diff --git a/providers/provider-simple/Cargo.toml b/providers/provider-simple/Cargo.toml new file mode 100644 index 0000000..a675b05 --- /dev/null +++ b/providers/provider-simple/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "provider-simple" +version = "0.1.0" +edition = "2021" + +[dependencies] +futures = "0.3" +guest-metrics = { path = "../../guest-metrics" } +uuid = { version = "1.11", features = ["v4"] } +tokio = { version = "1", features = ["time"] } + +vif-detect = { path = "../vif-detect" } +network-interface = "2.0" diff --git a/providers/provider-simple/src/lib.rs b/providers/provider-simple/src/lib.rs new file mode 100644 index 0000000..b55a9be --- /dev/null +++ b/providers/provider-simple/src/lib.rs @@ -0,0 +1,176 @@ +use std::{collections::HashMap, time::Duration}; + +use futures::SinkExt; +use guest_metrics::{plugin::GuestAgentPlugin, GuestMetric, NetEvent, NetEventOp, NetInterface}; +use network_interface::{NetworkInterface, NetworkInterfaceConfig}; +use uuid::Uuid; +use vif_detect::{PlatformVifDetector, VifDetector}; + +#[derive(Default)] +pub struct SimpleNetworkPlugin { + interfaces: HashMap, + uuid_map: HashMap, +} + +impl GuestAgentPlugin for SimpleNetworkPlugin { + fn run( + mut self, + mut channel: futures::channel::mpsc::Sender, + ) -> impl std::future::Future + Send { + async move { + let mut timer = tokio::time::interval(Duration::from_secs_f32(5.0)); + + loop { + self.track_interfaces(&mut channel).await; + + timer.tick().await; + } + } + } +} + +impl SimpleNetworkPlugin { + async fn track_interfaces( + &mut self, + channel: &mut futures::channel::mpsc::Sender, + ) { + let interfaces = network_interface::NetworkInterface::show().unwrap(); + + // Check for new interfaces (ones not in uuid_map) + let new_interfaces = interfaces + .iter() + .filter(|interface| !self.uuid_map.contains_key(&interface.name)) + .collect::>(); + + let removed_interfaces = self + .uuid_map + .keys() + .filter(|&name| !interfaces.iter().any(|interface| interface.name == *name)) + .cloned() + .collect::>(); + + let changed_interfaces = interfaces + .iter() + .filter(|&interface| { + if let Some(current) = self.interfaces.get(&interface.name) { + interface != current + } else { + false + } + }) + .collect::>(); + + for interface in new_interfaces { + let uuid = Uuid::new_v4(); + self.interfaces + .insert(interface.name.clone(), interface.clone()); + self.uuid_map.insert(interface.name.clone(), uuid); + + channel + .send(GuestMetric::AddIface(NetInterface { + uuid, + index: interface.index, + name: interface.name.clone(), + toolstack_iface: PlatformVifDetector::get_toolstack_interface(&interface.name) + .unwrap_or_default(), + })) + .await + .unwrap(); + + for addr in &interface.addr { + channel + .send(GuestMetric::Network(NetEvent { + iface_id: uuid, + op: NetEventOp::AddIp(addr.ip()), + })) + .await + .unwrap(); + } + + if let Some(mac) = interface.mac_addr.clone() { + channel + .send(GuestMetric::Network(NetEvent { + iface_id: uuid, + op: NetEventOp::AddMac(mac), + })) + .await + .unwrap(); + } + } + + for interface in removed_interfaces { + let uuid = self.uuid_map[&interface]; + + channel.send(GuestMetric::RmIface(uuid)).await.unwrap(); + self.interfaces.remove(&interface); + self.uuid_map.remove(&interface); + } + + for interface in changed_interfaces { + let current_interface = &self.interfaces[&interface.name]; + let uuid = self.uuid_map[&interface.name]; + + // Check what is added and what is removed + + // Added addresses + for addr in interface.addr.iter().filter(|&addr| { + current_interface + .addr + .iter() + .all(|current_addr| addr != current_addr) + }) { + channel + .send(GuestMetric::Network(NetEvent { + iface_id: uuid, + op: NetEventOp::AddIp(addr.ip()), + })) + .await + .unwrap(); + } + + // Removed addresses + for addr in current_interface.addr.iter().filter(|&addr| { + interface + .addr + .iter() + .all(|current_addr| addr != current_addr) + }) { + channel + .send(GuestMetric::Network(NetEvent { + iface_id: uuid, + op: NetEventOp::RmIp(addr.ip()), + })) + .await + .unwrap(); + } + + // Changed MAC + if interface.mac_addr != current_interface.mac_addr { + if let Some(mac) = current_interface.mac_addr.clone() { + // Remove MAC + channel + .send(GuestMetric::Network(NetEvent { + iface_id: uuid, + op: NetEventOp::RmMac(mac), + })) + .await + .unwrap() + } + + if let Some(mac) = interface.mac_addr.clone() { + // Remove MAC + channel + .send(GuestMetric::Network(NetEvent { + iface_id: uuid, + op: NetEventOp::AddMac(mac), + })) + .await + .unwrap() + } + } + + self.interfaces + .insert(interface.name.clone(), interface.clone()); + } + } +} diff --git a/src/provider/net/pnet.rs b/providers/provider-simple/src/original.rs similarity index 77% rename from src/provider/net/pnet.rs rename to providers/provider-simple/src/original.rs index a17237e..211bf1b 100644 --- a/src/provider/net/pnet.rs +++ b/providers/provider-simple/src/original.rs @@ -1,19 +1,14 @@ -use crate::datastructs::{NetEvent, NetEventOp, NetInterface, NetInterfaceCache}; -use futures::ready; use futures::stream::Stream; use ipnetwork::IpNetwork; use pnet_base::MacAddr; +use std::cell::RefCell; use std::collections::{hash_map, HashMap, HashSet}; +use std::error::Error; use std::io; -use std::pin::Pin; -use std::sync::{Arc, Mutex}; -use std::task::{Context, Poll}; +use std::rc::Rc; use std::time::Duration; -use tokio::time::Interval; -use super::NetworkSource; - -const IFACE_PERIOD_SECONDS: f32 = 60.0; +const IFACE_PERIOD_SECONDS: u64 = 60; #[derive(Debug, Eq, Hash, PartialEq)] enum Address { @@ -37,41 +32,33 @@ impl InterfaceInfo { } type AddressesState = HashMap; -pub struct PnetNetworkSource { +pub struct NetworkSource { addresses_cache: AddressesState, - iface_cache: NetInterfaceCache, - timer: Interval, + iface_cache: &'static mut NetInterfaceCache, } -impl PnetNetworkSource {} - -impl NetworkSource for PnetNetworkSource { - async fn collect_current(&mut self) -> anyhow::Result> { - Ok(self.get_ifconfig_data()?) +impl NetworkSource { + pub fn new(iface_cache: &'static mut NetInterfaceCache) -> io::Result { + Ok(NetworkSource { + addresses_cache: AddressesState::new(), + iface_cache, + }) } -} - -impl Stream for PnetNetworkSource { - type Item = Vec; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - ready!(self.timer.poll_tick(cx)); - Poll::Ready( - self.get_ifconfig_data() - .inspect_err(|e| log::error!("Unable to fetch network data {e}")) - .ok(), - ) + pub async fn collect_current(&mut self) -> Result, Box> { + Ok(self.get_ifconfig_data()?) } -} -impl PnetNetworkSource { - pub fn new() -> io::Result { - Ok(Self { - addresses_cache: AddressesState::new(), - iface_cache: HashMap::new(), - timer: tokio::time::interval(Duration::from_secs_f32(IFACE_PERIOD_SECONDS)), - }) + pub fn stream(&mut self) -> impl Stream> + '_ { + try_stream! { + let mut interval = tokio::time::interval(Duration::from_secs(IFACE_PERIOD_SECONDS)); + loop { + interval.tick().await; + for net_event in self.get_ifconfig_data()? { + yield net_event; + } + } + } } fn get_ifconfig_data(&mut self) -> io::Result> { @@ -120,11 +107,7 @@ impl PnetNetworkSource { Some(iface_info) => { let iface_adresses = &iface_info.addresses; for disappearing in cached_info.addresses.difference(iface_adresses) { - log::trace!( - "disappearing {}: {:?}", - iface.lock().unwrap().name, - disappearing - ); + log::trace!("disappearing {}: {:?}", iface.borrow().name, disappearing); events.push(NetEvent { iface: iface.clone(), op: match disappearing { @@ -149,7 +132,7 @@ impl PnetNetworkSource { .iface_cache .entry(*iface_index) .or_insert_with_key(|index| { - let iface = Arc::new(Mutex::new(NetInterface::new( + let iface = Rc::new(RefCell::new(NetInterface::new( *index, Some(iface_info.name.clone()), ))); @@ -166,7 +149,7 @@ impl PnetNetworkSource { &empty_address_set }; for appearing in iface_info.addresses.difference(cache_adresses) { - log::trace!("appearing {}: {:?}", iface.lock().unwrap().name, appearing); + log::trace!("appearing {}: {:?}", iface.borrow().name, appearing); events.push(NetEvent { iface: iface.clone(), op: match appearing { diff --git a/providers/vif-detect/Cargo.toml b/providers/vif-detect/Cargo.toml new file mode 100644 index 0000000..df93cff --- /dev/null +++ b/providers/vif-detect/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "vif-detect" +version = "0.1.0" +edition = "2021" + +[dependencies] +log = "*" +guest-metrics = { path = "../../guest-metrics" } diff --git a/src/vif_detect/freebsd.rs b/providers/vif-detect/src/freebsd.rs similarity index 100% rename from src/vif_detect/freebsd.rs rename to providers/vif-detect/src/freebsd.rs diff --git a/src/vif_detect/mod.rs b/providers/vif-detect/src/lib.rs similarity index 61% rename from src/vif_detect/mod.rs rename to providers/vif-detect/src/lib.rs index 03ac7f5..da67c76 100644 --- a/src/vif_detect/mod.rs +++ b/providers/vif-detect/src/lib.rs @@ -1,4 +1,4 @@ -use crate::datastructs::ToolstackNetInterface; +use guest_metrics::ToolstackNetInterface; #[cfg(target_os = "freebsd")] pub mod freebsd; @@ -9,15 +9,6 @@ pub trait VifDetector: Default { fn get_toolstack_interface(iface_name: &str) -> Option; } -#[derive(Default)] -pub struct DummyVifDetector; - -impl VifDetector for DummyVifDetector { - fn get_toolstack_interface(_iface_name: &str) -> Option { - None - } -} - #[cfg(target_os = "linux")] pub type PlatformVifDetector = linux::LinuxVifDetector; diff --git a/src/vif_detect/linux.rs b/providers/vif-detect/src/linux.rs similarity index 96% rename from src/vif_detect/linux.rs rename to providers/vif-detect/src/linux.rs index 9afc06a..c5a5a52 100644 --- a/src/vif_detect/linux.rs +++ b/providers/vif-detect/src/linux.rs @@ -1,6 +1,7 @@ -use crate::datastructs::ToolstackNetInterface; use std::fs; +use guest_metrics::ToolstackNetInterface; + use super::VifDetector; // identifies a VIF from sysfs as devtype="vif", and take the VIF id diff --git a/publishers/publisher-console/Cargo.toml b/publishers/publisher-console/Cargo.toml new file mode 100644 index 0000000..1f82406 --- /dev/null +++ b/publishers/publisher-console/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "publisher-console" +edition = "2021" +version.workspace = true +repository.workspace = true +categories.workspace = true + +[dependencies] +guest-metrics = { path = "../../guest-metrics" } +uuid = "1.11" +futures = "0.3" diff --git a/publishers/publisher-console/src/lib.rs b/publishers/publisher-console/src/lib.rs new file mode 100644 index 0000000..d5e06b3 --- /dev/null +++ b/publishers/publisher-console/src/lib.rs @@ -0,0 +1,77 @@ +use std::collections::HashMap; + +use futures::{channel::mpsc, StreamExt}; +use guest_metrics::{ + plugin::GuestAgentPublisher, GuestMetric, KernelInfo, NetEventOp, NetInterface, +}; +use uuid::Uuid; + +#[derive(Default)] +pub struct ConsolePublisher { + ifaces: HashMap, +} + +impl ConsolePublisher { + fn process_message(&mut self, metric: GuestMetric) { + match metric { + GuestMetric::OperatingSystem(os_info) => { + println!( + "OS: {} - Version: {}", + os_info.os_info.os_type(), + os_info.os_info.version() + ); + if let Some(KernelInfo { release }) = &os_info.kernel_info { + println!("Kernel version: {release}"); + } + } + GuestMetric::Memory(memory_info) => { + println!( + "Memory: {}/{} KB", + memory_info.mem_free / 1024, + memory_info.mem_total / 1024 + ); + } + GuestMetric::AddIface(iface) => { + println!("{} +IFACE", iface.index); + self.ifaces.insert(iface.uuid, iface); + } + GuestMetric::RmIface(iface_id) => { + let Some((_, iface)) = self.ifaces.remove_entry(&iface_id) else { + return; + }; + + println!("{} -IFACE", iface.index); + } + GuestMetric::Network(net_event) => { + let Some(iface) = self.ifaces.get(&net_event.iface_id) else { + return; + }; + + match &net_event.op { + NetEventOp::AddIp(address) => println!("{} +IP {address}", iface.index), + NetEventOp::RmIp(address) => println!("{} -IP {address}", iface.index), + NetEventOp::AddMac(mac_address) => { + println!("{} +MAC {mac_address}", iface.index) + } + NetEventOp::RmMac(mac_address) => { + println!("{} -MAC {mac_address}", iface.index) + } + } + } + GuestMetric::CleanupIfaces => {} + } + } +} + +impl GuestAgentPublisher for ConsolePublisher { + fn run( + mut self, + mut channel: mpsc::Receiver, + ) -> impl std::future::Future + Send { + async move { + while let Some(msg) = channel.next().await { + self.process_message(msg) + } + } + } +} diff --git a/publishers/publisher-xenstore/Cargo.toml b/publishers/publisher-xenstore/Cargo.toml new file mode 100644 index 0000000..7322f36 --- /dev/null +++ b/publishers/publisher-xenstore/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "publisher-xenstore" +version = "0.1.0" +edition = "2021" + +[dependencies] +guest-metrics = { path = "../../guest-metrics" } +uuid = "1.11" +futures = "0.3" +log = "*" + +# Use "unix" feature on non-Windows. +[target.'cfg(not(target_os = "windows"))'.dependencies] +xenstore-rs = { git = "https://github.com/TSnake41/xenstore-rs.git", branch = "pure-rust" } + +# Use xenstore-win on Windows +[target.'cfg(target_os = "windows")'.dependencies] +xenstore-rs = { git = "https://github.com/TSnake41/xenstore-rs.git", branch = "pure-rust", default-features = false } +xenstore-win = { git = "https://github.com/TSnake41/xenstore-win.git" } \ No newline at end of file diff --git a/publishers/publisher-xenstore/src/lib.rs b/publishers/publisher-xenstore/src/lib.rs new file mode 100644 index 0000000..aca3ffc --- /dev/null +++ b/publishers/publisher-xenstore/src/lib.rs @@ -0,0 +1,62 @@ +mod rfc; +mod std; + +use ::std::io; + +use futures::channel::mpsc; +use guest_metrics::plugin::GuestAgentPublisher; +use xenstore_rs::Xs; + +pub fn xs_publish(xs: &impl Xs, key: &str, value: &str) -> io::Result<()> { + log::trace!("+ {}={:?}", key, value); + xs.write(key, value) +} + +pub fn xs_unpublish(xs: &impl Xs, key: &str) -> io::Result<()> { + log::trace!("- {}", key); + xs.rm(key) +} + +pub struct XenstoreRfcPublisher; + +impl GuestAgentPublisher for XenstoreRfcPublisher { + fn run( + self, + channel: mpsc::Receiver, + ) -> impl ::std::future::Future + Send { + async move { + #[cfg(not(target_os = "windows"))] + let xs = xenstore_rs::unix::XsUnix::new().expect("Unable to initialize xenstore"); + + #[cfg(target_os = "windows")] + let xs = xenstore_win::XsWindows::new().expect("Unable to initialize xenstore"); + + rfc::XenstoreRfc::new(xs) + .run(channel) + .await + .expect("Xenstore failure") + } + } +} + +pub struct XenstoreStdPublisher; + +impl GuestAgentPublisher for XenstoreStdPublisher { + fn run( + self, + channel: mpsc::Receiver, + ) -> impl ::std::future::Future + Send { + async move { + #[cfg(not(target_os = "windows"))] + let xs = xenstore_rs::unix::XsUnix::new().expect("Unable to initialize xenstore"); + + #[cfg(target_os = "windows")] + let xs = xenstore_win::XsWindows::new().expect("Unable to initialize xenstore"); + + std::XenstoreStd::new(xs) + .run(channel) + .await + .expect("Xenstore failure") + } + } +} diff --git a/publishers/publisher-xenstore/src/rfc.rs b/publishers/publisher-xenstore/src/rfc.rs new file mode 100644 index 0000000..2444e90 --- /dev/null +++ b/publishers/publisher-xenstore/src/rfc.rs @@ -0,0 +1,134 @@ +use futures::{channel::mpsc, StreamExt}; +use guest_metrics::{ + GuestMetric, MemoryInfo, NetEvent, NetEventOp, NetInterface, OsInfo, ToolstackNetInterface, +}; +use std::collections::HashMap; +use std::io; +use std::net::IpAddr; +use uuid::Uuid; +use xenstore_rs::Xs; + +use super::{xs_publish, xs_unpublish}; + +#[derive(Clone)] +pub struct XenstoreRfc { + xs: XS, + + ifaces: HashMap, +} + +const PROTOCOL_VERSION: &str = env!("CARGO_PKG_VERSION"); + +fn iface_prefix(iface_id: u32) -> String { + format!("data/net/{iface_id}") +} + +// FIXME: this should be a runtime config of xenstore-std.rs + +impl XenstoreRfc { + pub fn new(xs: XS) -> Self { + XenstoreRfc { + xs, + ifaces: HashMap::new(), + } + } + + fn publish_osinfo(&mut self, info: &OsInfo) -> io::Result<()> { + xs_publish(&self.xs, "data/xen-guest-agent", PROTOCOL_VERSION)?; + xs_publish( + &self.xs, + "data/os/name", + &format!("{} {}", info.os_info.os_type(), info.os_info.version()), + )?; + xs_publish( + &self.xs, + "data/os/version", + &info.os_info.version().to_string(), + )?; + xs_publish(&self.xs, "data/os/class", "unix")?; + if let Some(kernel_info) = &info.kernel_info { + xs_publish( + &self.xs, + "data/os/unix/kernel-version", + &kernel_info.release, + )?; + } + + Ok(()) + } + + fn cleanup_ifaces(&mut self) -> io::Result<()> { + // Currently only vif interfaces are cleaned + xs_unpublish(&self.xs, "data/net") + } + + fn publish_memory(&mut self, _mem_info: &MemoryInfo) -> io::Result<()> { + //xs_publish(&self.xs, "data/meminfo_free", &mem_free_kb.to_string())?; + Ok(()) + } + + #[allow(clippy::useless_format)] + fn publish_netevent(&mut self, event: &NetEvent) -> io::Result<()> { + let Some(iface) = self.ifaces.get(&event.iface_id) else { + return Err(io::Error::new( + io::ErrorKind::NotFound, + format!("Got event from unknown interface ({})", event.iface_id), + )); + }; + + let ToolstackNetInterface::Vif(iface_id) = iface.toolstack_iface else { + log::warn!("Got event from unsupported interface {:?}", iface); + return Ok(()); + }; + + let xs_iface_prefix = iface_prefix(iface_id); + match &event.op { + NetEventOp::AddIp(address) => { + let key_suffix = munged_address(address); + xs_publish(&self.xs, &format!("{xs_iface_prefix}/{key_suffix}"), "")?; + } + NetEventOp::RmIp(address) => { + let key_suffix = munged_address(address); + xs_unpublish(&self.xs, &format!("{xs_iface_prefix}/{key_suffix}"))?; + } + NetEventOp::AddMac(mac_address) => { + xs_publish(&self.xs, &format!("{xs_iface_prefix}"), mac_address)? + } + NetEventOp::RmMac(_) => xs_unpublish(&self.xs, &format!("{xs_iface_prefix}"))?, + } + Ok(()) + } + + pub async fn run(mut self, mut channel: mpsc::Receiver) -> io::Result<()> { + while let Some(metric) = channel.next().await { + match metric { + GuestMetric::OperatingSystem(os_info) => self.publish_osinfo(&os_info)?, + GuestMetric::Memory(memory_info) => self.publish_memory(&memory_info)?, + GuestMetric::Network(net_event) => self.publish_netevent(&net_event)?, + GuestMetric::CleanupIfaces => self.cleanup_ifaces()?, + GuestMetric::AddIface(net_interface) => { + if let ToolstackNetInterface::Vif(iface_id) = net_interface.toolstack_iface { + xs_publish(&self.xs, &iface_prefix(iface_id), "")?; + } + self.ifaces.insert(net_interface.uuid, net_interface); + } + GuestMetric::RmIface(uuid) => { + if let Some(interface) = self.ifaces.remove(&uuid) { + if let ToolstackNetInterface::Vif(iface_id) = interface.toolstack_iface { + xs_unpublish(&self.xs, &iface_prefix(iface_id))?; + } + } + } + } + } + + Ok(()) + } +} + +fn munged_address(addr: &IpAddr) -> String { + match addr { + IpAddr::V4(addr) => "ipv4/".to_string() + &addr.to_string().replace('.', "_"), + IpAddr::V6(addr) => "ipv6/".to_string() + &addr.to_string().replace(':', "_"), + } +} diff --git a/src/publisher/xenstore/std.rs b/publishers/publisher-xenstore/src/std.rs similarity index 71% rename from src/publisher/xenstore/std.rs rename to publishers/publisher-xenstore/src/std.rs index 400e1fa..8ef5c67 100644 --- a/src/publisher/xenstore/std.rs +++ b/publishers/publisher-xenstore/src/std.rs @@ -1,9 +1,13 @@ -use crate::datastructs::{NetEvent, NetEventOp, NetInterface, ToolstackNetInterface}; -use crate::publisher::Publisher; -use crate::publisher::{MemoryInfo, OsInfo}; +use futures::channel::mpsc; +use futures::{self, StreamExt}; +use guest_metrics::{ + os_info, GuestMetric, MemoryInfo, NetEvent, NetEventOp, NetInterface, OsInfo, + ToolstackNetInterface, +}; use std::collections::HashMap; use std::io; use std::net::IpAddr; +use uuid::Uuid; use xenstore_rs::Xs; use super::{xs_publish, xs_unpublish}; @@ -18,6 +22,8 @@ pub struct XenstoreStd { // with domain ownership. OTOH libxl creates it readonly, so we // catch the case where it is so to avoid uselessly retrying. forbidden_control_feature_balloon: bool, + + ifaces: HashMap, } const NUM_IFACE_IPS: usize = 10; @@ -41,11 +47,16 @@ impl XenstoreStd { xs, ip_addresses, forbidden_control_feature_balloon: false, + ifaces: HashMap::new(), } } } -impl Publisher for XenstoreStd { +fn iface_prefix(iface_id: u32) -> String { + format!("attr/vif/{iface_id}") +} + +impl XenstoreStd { fn publish_osinfo(&mut self, info: &OsInfo) -> io::Result<()> { // FIXME this is not anywhere standard, just minimal XS compatibility xs_publish(&self.xs, "attr/PVAddons/MajorVersion", AGENT_VERSION_MAJOR)?; @@ -91,7 +102,7 @@ impl Publisher for XenstoreStd { // balloon driver to do the job of signaling this // condition) match xs_publish(&self.xs, "control/feature-balloon", "1") { - Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { + Err(e) if e.kind() == io::ErrorKind::PermissionDenied => { log::warn!("cannot write control/feature-balloon (impacts XAPI's squeezed)"); self.forbidden_control_feature_balloon = true; } @@ -120,20 +131,22 @@ impl Publisher for XenstoreStd { // see https://xenbits.xen.org/docs/unstable/misc/xenstore-paths.html#domain-controlled-paths fn publish_netevent(&mut self, event: &NetEvent) -> io::Result<()> { - let iface_id = match event.iface.lock().unwrap().toolstack_iface { - ToolstackNetInterface::Vif(id) => id, - ToolstackNetInterface::Unknown => u32::MAX, + let Some(iface) = self.ifaces.get(&event.iface_id) else { + return Err(io::Error::new( + io::ErrorKind::NotFound, + format!("Got event from unknown interface ({})", event.iface_id), + )); + }; + + let ToolstackNetInterface::Vif(iface_id) = iface.toolstack_iface else { + log::warn!("Got event from unsupported interface {:?}", iface); + return Ok(()); }; - let xs_iface_prefix = format!("attr/vif/{iface_id}"); + + let xs_iface_prefix = iface_prefix(iface_id); match &event.op { - NetEventOp::AddIface => { - xs_publish(&self.xs, &xs_iface_prefix, "")?; - } - NetEventOp::RmIface => { - xs_unpublish(&self.xs, &xs_iface_prefix)?; - } NetEventOp::AddIp(address) => { - let key_suffix = self.munged_address(address, &event.iface.lock().unwrap())?; + let key_suffix = self.munged_address(address, iface_id)?; xs_publish( &self.xs, &format!("{xs_iface_prefix}/{key_suffix}"), @@ -141,16 +154,16 @@ impl Publisher for XenstoreStd { )?; } NetEventOp::RmIp(address) => { - let key_suffix = self.munged_address(address, &event.iface.lock().unwrap())?; + let key_suffix = self.munged_address(address, iface_id)?; xs_unpublish(&self.xs, &format!("{xs_iface_prefix}/{key_suffix}"))?; } // FIXME extend IfaceIpStruct for this NetEventOp::AddMac(_mac_address) => { - log::debug!("AddMac not applied"); + log::debug!("AddMac not applied") } NetEventOp::RmMac(_mac_address) => { - log::debug!("RmMac not applied"); + log::debug!("RmMac not applied") } } Ok(()) @@ -160,13 +173,11 @@ impl Publisher for XenstoreStd { // Currently only vif interfaces are cleaned xs_unpublish(&self.xs, "attr/vif") } -} -impl XenstoreStd { - fn munged_address(&mut self, addr: &IpAddr, iface: &NetInterface) -> io::Result { + fn munged_address(&mut self, addr: &IpAddr, iface_index: u32) -> io::Result { let ip_entry = self .ip_addresses - .entry(iface.index) + .entry(iface_index) .or_insert(IfaceIpStruct { v4: [None; NUM_IFACE_IPS], v6: [None; NUM_IFACE_IPS], @@ -181,6 +192,33 @@ impl XenstoreStd { IpAddr::V6(_) => Ok(format!("ipv6/{ip_slot}")), } } + + pub async fn run(mut self, mut channel: mpsc::Receiver) -> io::Result<()> { + while let Some(metric) = channel.next().await { + match metric { + GuestMetric::OperatingSystem(os_info) => self.publish_osinfo(&os_info)?, + GuestMetric::Memory(memory_info) => self.publish_memory(&memory_info)?, + GuestMetric::Network(net_event) => self.publish_netevent(&net_event)?, + GuestMetric::CleanupIfaces => self.cleanup_ifaces()?, + GuestMetric::AddIface(net_interface) => { + if let ToolstackNetInterface::Vif(iface_id) = net_interface.toolstack_iface { + xs_publish(&self.xs, &iface_prefix(iface_id), "")?; + } + + self.ifaces.insert(net_interface.uuid, net_interface); + } + GuestMetric::RmIface(uuid) => { + if let Some(interface) = self.ifaces.remove(&uuid) { + if let ToolstackNetInterface::Vif(iface_id) = interface.toolstack_iface { + xs_unpublish(&self.xs, &iface_prefix(iface_id))?; + } + } + } + } + } + + Ok(()) + } } fn get_ip_slot(ip: &IpAddr, list: &mut IfaceIpList) -> io::Result { diff --git a/src/datastructs.rs b/src/datastructs.rs deleted file mode 100644 index 033822b..0000000 --- a/src/datastructs.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::collections::HashMap; -use std::net::IpAddr; -use std::sync::Arc; -use std::sync::Mutex; - -use crate::vif_detect::{PlatformVifDetector, VifDetector}; - -pub struct KernelInfo { - pub release: String, -} - -#[non_exhaustive] -#[derive(Clone, Debug, Default)] -pub enum ToolstackNetInterface { - #[default] - Unknown, - Vif(u32), - // SRIOV, - // PciPassthrough, - // UsbPassthrough, -} - -#[derive(Clone, Debug)] -pub struct NetInterface { - pub index: u32, - pub name: String, - pub toolstack_iface: ToolstackNetInterface, -} - -impl NetInterface { - pub fn new(index: u32, name: Option) -> NetInterface { - let name = match name { - Some(string) => string, - None => { - log::error!("new interface with index {index} has no name"); - String::from("") // this is not valid, but user will now be aware - } - }; - NetInterface { - index, - name: name.clone(), - toolstack_iface: PlatformVifDetector::get_toolstack_interface(&name) - .unwrap_or_default(), - } - } -} - -// TODO: Teddy: We should find a better solution than abusing Arc> - -// The cache of currently-known network interfaces. We have to use -// reference counting on the cached items, as we want on one hand to -// use references to those items from NetEvent, and OTOH we want to -// remove interfaces from here once unplugged. And Rust won't let us -// use `&'static NetInterface` because we can do the latter, which is -// good in the end. -// The interface may change name after creation (hence `RefCell`). -pub type NetInterfaceCache = HashMap>>; - -#[derive(Debug)] -pub enum NetEventOp { - AddIface, - RmIface, - AddMac(String), - RmMac(String), - AddIp(IpAddr), - RmIp(IpAddr), -} - -#[derive(Debug)] -pub struct NetEvent { - pub iface: Arc>, - pub op: NetEventOp, -} diff --git a/src/logic.rs b/src/logic.rs deleted file mode 100644 index 533edf9..0000000 --- a/src/logic.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::{io, time::Duration}; - -use futures::StreamExt; -use tokio::sync::mpsc; - -use crate::{ - provider::{kernel::collect_kernel, memory::MemorySource, net::NetworkSource}, - publisher::{GuestMetric, MemoryInfo, OsInfo}, - GuestAgentConfig, -}; - -pub async fn run( - config: GuestAgentConfig, - publisher_channel: mpsc::Sender, - mut collector_memory: impl MemorySource + Send + 'static, - mut collector_net: impl NetworkSource + Send + Unpin + 'static, -) -> anyhow::Result<()> { - // Remove old entries from previous agent to avoid having unknown - // interfaces. We will repopulate existing ones immediatly. - publisher_channel.send(GuestMetric::CleanupIfaces).await?; - - let kernel_info = collect_kernel()?; - let mem_total = match collector_memory.get_total_kb() { - Ok(mem_total_kb) => Some(mem_total_kb), - Err(error) if error.kind() == io::ErrorKind::Unsupported => { - log::warn!("Memory stats not supported"); - None - } - // propagate errors other than io::ErrorKind::Unsupported - Err(error) => Err(error)?, - }; - publisher_channel - .send(GuestMetric::OsInfo(OsInfo { - os_info: os_info::get(), - kernel_info, - })) - .await?; - - // network events - for event in collector_net.collect_current().await? { - if config.report_nics { - publisher_channel.send(GuestMetric::Network(event)).await?; - } - } - - // main loop - let network_task = tokio::spawn({ - let publisher_channel = publisher_channel.clone(); - async move { - loop { - while let Some(events) = collector_net.next().await { - for event in events { - publisher_channel - .send(GuestMetric::Network(event)) - .await - .unwrap(); - } - } - } - } - }); - - let memory_task = tokio::spawn({ - let publisher_channel = publisher_channel.clone(); - let mut timer = tokio::time::interval(Duration::from_secs_f64(config.period)); - - async move { - loop { - timer.tick().await; - let mem_total = mem_total.unwrap_or_default(); - let mem_free = collector_memory.get_available_kb().unwrap(); - - publisher_channel - .send(GuestMetric::MemoryInfo(MemoryInfo { - mem_free, - mem_total, - })) - .await - .unwrap(); - } - } - }); - - let _ = futures::join!(network_task, memory_task); - - Ok(()) -} diff --git a/src/provider/kernel.rs b/src/provider/kernel.rs deleted file mode 100644 index 6d951e9..0000000 --- a/src/provider/kernel.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::io; - -use crate::datastructs::KernelInfo; - -// UNIX uname() implementation -#[cfg(unix)] -pub fn collect_kernel() -> io::Result> { - let uname_info = uname::uname()?; - Ok(Some(KernelInfo { - release: uname_info.release, - })) -} - -// default implementation -#[cfg(not(unix))] -pub fn collect_kernel() -> io::Result> { - Ok(None) -} diff --git a/src/provider/memory/mod.rs b/src/provider/memory/mod.rs deleted file mode 100644 index bec166d..0000000 --- a/src/provider/memory/mod.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::io; - -#[cfg(target_os = "freebsd")] -pub mod bsd; - -#[cfg(target_os = "linux")] -pub mod linux; - -pub trait MemorySource: Sized { - fn new() -> io::Result; - fn get_total_kb(&mut self) -> io::Result; - fn get_available_kb(&mut self) -> io::Result; -} - -#[derive(Default)] -pub struct DummyMemorySource; - -impl MemorySource for DummyMemorySource { - fn new() -> io::Result { - Ok(Self) - } - - fn get_total_kb(&mut self) -> io::Result { - Err(io::Error::new( - io::ErrorKind::Unsupported, - "no implementation for mem_total", - )) - } - fn get_available_kb(&mut self) -> io::Result { - Err(io::Error::new( - io::ErrorKind::Unsupported, - "no implementation for mem_avail", - )) - } -} - -#[cfg(target_os = "linux")] -pub type PlatformMemorySource = linux::LinuxMemorySource; - -#[cfg(target_os = "freebsd")] -pub type PlatformMemorySource = bsd::BsdMemorySource; diff --git a/src/provider/mod.rs b/src/provider/mod.rs deleted file mode 100644 index 3811e09..0000000 --- a/src/provider/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod kernel; -pub mod memory; -pub mod net; diff --git a/src/provider/net/mod.rs b/src/provider/net/mod.rs deleted file mode 100644 index 472c552..0000000 --- a/src/provider/net/mod.rs +++ /dev/null @@ -1,95 +0,0 @@ -#[cfg(feature = "net_netlink")] -pub mod netlink; - -#[cfg(feature = "net_pnet")] -pub mod pnet; - -use std::{ - io, - pin::Pin, - task::{Context, Poll}, -}; - -use enum_dispatch::enum_dispatch; -use futures::{stream::Stream, StreamExt}; - -use crate::datastructs::NetEvent; - -#[enum_dispatch] -pub trait NetworkSource: Sized + Stream> { - async fn collect_current(&mut self) -> anyhow::Result>; -} - -pub struct DummyNetworkSource; - -impl NetworkSource for DummyNetworkSource { - async fn collect_current(&mut self) -> anyhow::Result> { - Ok(vec![]) - } -} - -impl Stream for DummyNetworkSource { - type Item = Vec; - - fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(None) - } -} - -#[enum_dispatch(NetworkSource)] -pub enum AgentNetworkSource { - Dummy(DummyNetworkSource), - - #[cfg(feature = "net_netlink")] - Netlink(netlink::NetlinkNetworkSource), - #[cfg(feature = "net_pnet")] - Pnet(pnet::PnetNetworkSource), -} - -// enum_dispatch doesn't support supertraits, we need to do that manually instead -impl Stream for AgentNetworkSource { - type Item = Vec; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.get_mut() { - AgentNetworkSource::Dummy(s) => s.poll_next_unpin(cx), - #[cfg(feature = "net_netlink")] - AgentNetworkSource::Netlink(s) => s.poll_next_unpin(cx), - #[cfg(feature = "net_pnet")] - AgentNetworkSource::Pnet(s) => s.poll_next_unpin(cx), - } - } -} - -#[derive(Clone, Copy, clap::ValueEnum)] -pub enum NetworkSourceKind { - Dummy, - #[cfg(feature = "net_netlink")] - Netlink, - #[cfg(feature = "net_pnet")] - Pnet, -} - -impl Default for NetworkSourceKind { - fn default() -> Self { - [ - #[cfg(feature = "net_netlink")] - Self::Netlink, - #[cfg(feature = "net_pnet")] - Self::Pnet, - Self::Dummy, - ][0] - } -} - -impl AgentNetworkSource { - pub fn new(kind: NetworkSourceKind) -> io::Result { - match kind { - NetworkSourceKind::Dummy => Ok(Self::Dummy(DummyNetworkSource)), - #[cfg(feature = "net_netlink")] - NetworkSourceKind::Netlink => Ok(Self::Netlink(netlink::NetlinkNetworkSource::new()?)), - #[cfg(feature = "net_pnet")] - NetworkSourceKind::Pnet => Ok(Self::Pnet(pnet::PnetNetworkSource::new()?)), - } - } -} diff --git a/src/publisher/mod.rs b/src/publisher/mod.rs deleted file mode 100644 index 7d95bd2..0000000 --- a/src/publisher/mod.rs +++ /dev/null @@ -1,123 +0,0 @@ -// default no-op Publisher implementation -pub mod xenstore; - -use crate::datastructs::{KernelInfo, NetEvent, NetEventOp}; -use enum_dispatch::enum_dispatch; -use std::io; -use tokio::sync::mpsc; -use xenstore::{rfc::XenstoreRfc, std::XenstoreStd, XsBuild}; -use xenstore_rs::Xs; - -pub struct OsInfo { - pub os_info: os_info::Info, - pub kernel_info: Option, -} - -pub struct MemoryInfo { - pub mem_free: usize, - pub mem_total: usize, -} - -pub enum GuestMetric { - OsInfo(OsInfo), - MemoryInfo(MemoryInfo), - Network(NetEvent), - CleanupIfaces, -} - -#[enum_dispatch] -pub trait Publisher: Sized { - fn publish_osinfo(&mut self, os_info: &OsInfo) -> io::Result<()>; - fn publish_memory(&mut self, mem_info: &MemoryInfo) -> io::Result<()>; - fn publish_netevent(&mut self, event: &NetEvent) -> io::Result<()>; - - fn cleanup_ifaces(&mut self) -> io::Result<()>; -} - -#[derive(Default)] -pub struct ConsolePublisher; - -impl Publisher for ConsolePublisher { - fn publish_osinfo(&mut self, os_info: &OsInfo) -> io::Result<()> { - println!( - "OS: {} - Version: {}", - os_info.os_info.os_type(), - os_info.os_info.version() - ); - if let Some(KernelInfo { release }) = &os_info.kernel_info { - println!("Kernel version: {release}"); - } - Ok(()) - } - fn publish_memory(&mut self, mem_info: &MemoryInfo) -> io::Result<()> { - println!( - "Memory: {}/{} KB", - mem_info.mem_free / 1024, - mem_info.mem_total / 1024 - ); - Ok(()) - } - fn publish_netevent(&mut self, event: &NetEvent) -> io::Result<()> { - let iface_id = &event.iface.lock().unwrap().name; - match &event.op { - NetEventOp::AddIface => println!("{iface_id} +IFACE"), - NetEventOp::RmIface => println!("{iface_id} -IFACE"), - NetEventOp::AddIp(address) => println!("{iface_id} +IP {address}"), - NetEventOp::RmIp(address) => println!("{iface_id} -IP {address}"), - NetEventOp::AddMac(mac_address) => println!("{iface_id} +MAC {mac_address}"), - NetEventOp::RmMac(mac_address) => println!("{iface_id} -MAC {mac_address}"), - } - Ok(()) - } - - fn cleanup_ifaces(&mut self) -> io::Result<()> { - Ok(()) - } -} - -#[derive(Clone, Copy, Default, Debug, clap::ValueEnum)] -pub enum PublisherKind { - Console, - #[default] - Xenstore, - XenstoreRfc, -} - -#[enum_dispatch(Publisher)] -pub enum AgentPublisher { - Console(ConsolePublisher), - XenstoreRfc(XenstoreRfc), - XenstoreStd(XenstoreStd), -} - -impl AgentPublisher { - #[allow(clippy::wildcard_in_or_patterns)] - pub fn new(kind: PublisherKind) -> io::Result { - match kind { - PublisherKind::Console => Ok(Self::Console(ConsolePublisher)), - PublisherKind::Xenstore => Ok(Self::XenstoreStd(XenstoreStd::new(XS::new()?))), - PublisherKind::XenstoreRfc => Ok(Self::XenstoreRfc(XenstoreRfc::new(XS::new()?))), - } - } -} - -pub fn spawn_publisher( - kind: PublisherKind, -) -> io::Result> { - let (tx, mut rx) = mpsc::channel(4); - let mut publisher = AgentPublisher::::new(kind)?; - - tokio::spawn(async move { - while let Some(metric) = rx.recv().await { - match &metric { - GuestMetric::OsInfo(os_info) => publisher.publish_osinfo(os_info), - GuestMetric::MemoryInfo(memory_info) => publisher.publish_memory(memory_info), - GuestMetric::Network(net_event) => publisher.publish_netevent(net_event), - GuestMetric::CleanupIfaces => publisher.cleanup_ifaces(), - } - .unwrap() - } - }); - - Ok(tx) -} diff --git a/src/publisher/xenstore/mod.rs b/src/publisher/xenstore/mod.rs deleted file mode 100644 index 57eaf6c..0000000 --- a/src/publisher/xenstore/mod.rs +++ /dev/null @@ -1,29 +0,0 @@ -pub mod rfc; -pub mod std; - -use ::std::io; - -use xenstore_rs::Xs; - -pub fn xs_publish(xs: &impl Xs, key: &str, value: &str) -> io::Result<()> { - log::trace!("+ {}={:?}", key, value); - xs.write(key, value) -} - -pub fn xs_unpublish(xs: &impl Xs, key: &str) -> io::Result<()> { - log::trace!("- {}", key); - xs.rm(key) -} - -pub trait XsBuild: Sized + Xs + Send { - fn new() -> io::Result; -} - -#[cfg(target_family = "unix")] -impl XsBuild for xenstore_rs::unix::XsUnix { - fn new() -> io::Result { - Self::new() - } -} - -pub type PlatformXs = xenstore_rs::unix::XsUnix; diff --git a/src/publisher/xenstore/rfc.rs b/src/publisher/xenstore/rfc.rs deleted file mode 100644 index 645819e..0000000 --- a/src/publisher/xenstore/rfc.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::datastructs::{NetEvent, NetEventOp}; -use crate::publisher::{MemoryInfo, OsInfo, Publisher}; -use std::io; -use std::net::IpAddr; -use xenstore_rs::Xs; - -use super::{xs_publish, xs_unpublish}; - -#[derive(Clone)] -pub struct XenstoreRfc(XS); - -const PROTOCOL_VERSION: &str = env!("CARGO_PKG_VERSION"); - -// FIXME: this should be a runtime config of xenstore-std.rs - -impl XenstoreRfc { - pub fn new(xs: XS) -> Self { - XenstoreRfc(xs) - } -} - -impl Publisher for XenstoreRfc { - fn publish_osinfo(&mut self, info: &OsInfo) -> io::Result<()> { - xs_publish(&self.0, "data/xen-guest-agent", PROTOCOL_VERSION)?; - xs_publish( - &self.0, - "data/os/name", - &format!("{} {}", info.os_info.os_type(), info.os_info.version()), - )?; - xs_publish( - &self.0, - "data/os/version", - &info.os_info.version().to_string(), - )?; - xs_publish(&self.0, "data/os/class", "unix")?; - if let Some(kernel_info) = &info.kernel_info { - xs_publish(&self.0, "data/os/unix/kernel-version", &kernel_info.release)?; - } - - Ok(()) - } - - fn cleanup_ifaces(&mut self) -> io::Result<()> { - // Currently only vif interfaces are cleaned - xs_unpublish(&self.0, "data/net") - } - - fn publish_memory(&mut self, _mem_info: &MemoryInfo) -> io::Result<()> { - //xs_publish(&self.xs, "data/meminfo_free", &mem_free_kb.to_string())?; - Ok(()) - } - - #[allow(clippy::useless_format)] - fn publish_netevent(&mut self, event: &NetEvent) -> io::Result<()> { - let iface_id = &event.iface.lock().unwrap().index; - let xs_iface_prefix = format!("data/net/{iface_id}"); - match &event.op { - NetEventOp::AddIface => { - xs_publish( - &self.0, - &format!("{xs_iface_prefix}"), - &event.iface.lock().unwrap().name, - )?; - } - NetEventOp::RmIface => { - xs_unpublish(&self.0, &format!("{xs_iface_prefix}"))?; - } - NetEventOp::AddIp(address) => { - let key_suffix = munged_address(address); - xs_publish(&self.0, &format!("{xs_iface_prefix}/{key_suffix}"), "")?; - } - NetEventOp::RmIp(address) => { - let key_suffix = munged_address(address); - xs_unpublish(&self.0, &format!("{xs_iface_prefix}/{key_suffix}"))?; - } - NetEventOp::AddMac(mac_address) => { - xs_publish(&self.0, &format!("{xs_iface_prefix}"), mac_address)?; - } - NetEventOp::RmMac(_) => { - xs_unpublish(&self.0, &format!("{xs_iface_prefix}"))?; - } - } - Ok(()) - } -} - -fn munged_address(addr: &IpAddr) -> String { - match addr { - IpAddr::V4(addr) => "ipv4/".to_string() + &addr.to_string().replace('.', "_"), - IpAddr::V6(addr) => "ipv6/".to_string() + &addr.to_string().replace(':', "_"), - } -} diff --git a/xen-guest-agent/Cargo.toml b/xen-guest-agent/Cargo.toml new file mode 100644 index 0000000..3f7d980 --- /dev/null +++ b/xen-guest-agent/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "xen-guest-agent" +version = "0.5.0-dev" +authors = ["Yann Dirson "] +edition = "2018" +rust-version = "1.76" +license = "AGPL-3.0-only" + +[dependencies] +futures = "0.3.26" +tokio = { version = "1.25.0", features = ["rt", "macros", "rt-multi-thread"] } +log = "0.4.0" +env_logger = ">=0.10.0" +clap = { version = "4.4.8", features = ["derive"] } +enum_dispatch = "0.3" +anyhow = "1.0" + +guest-metrics = { path = "../guest-metrics" } + +publisher-console = { path = "../publishers/publisher-console" } +publisher-xenstore = { path = "../publishers/publisher-xenstore" } + +provider-memory = { path = "../providers/provider-memory" } +provider-os = { path = "../providers/provider-os" } +provider-simple = { path = "../providers/provider-simple" } + +[target.'cfg(target_os = "freebsd")'.dependencies] +sysctl = "0.5.0" + +[target.'cfg(target_family = "unix")'.dependencies] +syslog = "6.0" + +[features] diff --git a/xen-guest-agent/src/logic.rs b/xen-guest-agent/src/logic.rs new file mode 100644 index 0000000..a71e518 --- /dev/null +++ b/xen-guest-agent/src/logic.rs @@ -0,0 +1,26 @@ +use futures::{channel::mpsc, SinkExt}; +use tokio::task::JoinSet; + +use guest_metrics::{plugin::GuestAgentPlugin, GuestMetric}; + +use crate::{plugins::NetworkPlugin, publisher::AgentPublisher, GuestAgentConfig}; + +pub async fn run(config: GuestAgentConfig) -> anyhow::Result<()> { + let mut set: JoinSet<()> = JoinSet::new(); + + let (mut tx, rx) = mpsc::channel(4); + let publisher = AgentPublisher::new(config.publisher)?; + + set.spawn(publisher.run(rx)); + + // Remove old entries from previous agent to avoid having unknown + // interfaces. We will repopulate existing ones immediatly. + tx.send(GuestMetric::CleanupIfaces).await?; + + set.spawn(provider_os::OsInfoPlugin.run(tx.clone())); + set.spawn(provider_memory::MemoryPlugin.run(tx.clone())); + set.spawn(NetworkPlugin::new(config.network)?.run(tx.clone())); + + println!("{:?}", set.join_all().await); + Ok(()) +} diff --git a/src/main.rs b/xen-guest-agent/src/main.rs similarity index 74% rename from src/main.rs rename to xen-guest-agent/src/main.rs index 0cc1172..bbcc33f 100644 --- a/src/main.rs +++ b/xen-guest-agent/src/main.rs @@ -1,16 +1,12 @@ -mod datastructs; - mod logic; -mod provider; +//mod provider; +mod plugins; mod publisher; -mod vif_detect; //pub mod metrics; use clap::Parser; use log::LevelFilter; -use provider::memory::{MemorySource, PlatformMemorySource}; -use provider::net::{AgentNetworkSource, NetworkSourceKind}; -use publisher::xenstore::PlatformXs; +use plugins::NetworkPluginKind; use publisher::PublisherKind; const MEM_PERIOD_SECONDS: f64 = 5.0; @@ -37,7 +33,7 @@ struct GuestAgentConfig { publisher: PublisherKind, #[arg(long, value_enum, default_value_t = Default::default())] - network: NetworkSourceKind, + network: NetworkPluginKind, } #[tokio::main] @@ -46,14 +42,7 @@ async fn main() -> anyhow::Result<()> { setup_logger(config.stderr, config.log_level)?; - let publisher_channel: tokio::sync::mpsc::Sender = - publisher::spawn_publisher::(config.publisher) - .expect("Unable to initialize publisher"); - let collector_memory = PlatformMemorySource::new().expect("Unable to initialize memory source"); - let collector_net = - AgentNetworkSource::new(config.network).expect("Unable to initialize network source"); - - logic::run(config, publisher_channel, collector_memory, collector_net).await + logic::run(config).await } fn setup_logger(use_stderr: bool, level: LevelFilter) -> anyhow::Result<()> { diff --git a/xen-guest-agent/src/plugins.rs b/xen-guest-agent/src/plugins.rs new file mode 100644 index 0000000..fb0a1b1 --- /dev/null +++ b/xen-guest-agent/src/plugins.rs @@ -0,0 +1,34 @@ +use std::io; + +use futures::channel::mpsc; +use guest_metrics::{plugin::GuestAgentPlugin, GuestMetric}; +use provider_simple::SimpleNetworkPlugin; + +#[derive(Clone, Copy, Debug, clap::ValueEnum)] +pub enum NetworkPluginKind { + Simple, +} + +impl Default for NetworkPluginKind { + fn default() -> Self { + Self::Simple + } +} + +pub enum NetworkPlugin { + Simple(SimpleNetworkPlugin), +} + +impl NetworkPlugin { + pub fn new(kind: NetworkPluginKind) -> io::Result { + match kind { + NetworkPluginKind::Simple => Ok(Self::Simple(SimpleNetworkPlugin::default())), + } + } + + pub async fn run(self, channel: mpsc::Sender) { + match self { + NetworkPlugin::Simple(plugin) => plugin.run(channel).await, + } + } +} diff --git a/xen-guest-agent/src/publisher.rs b/xen-guest-agent/src/publisher.rs new file mode 100644 index 0000000..7887eb2 --- /dev/null +++ b/xen-guest-agent/src/publisher.rs @@ -0,0 +1,39 @@ +use std::io; + +use futures::channel::mpsc; +use guest_metrics::{plugin::GuestAgentPublisher, GuestMetric}; + +use publisher_console::ConsolePublisher; +use publisher_xenstore::{XenstoreRfcPublisher, XenstoreStdPublisher}; + +#[derive(Clone, Copy, Default, Debug, clap::ValueEnum)] +pub enum PublisherKind { + Console, + #[default] + Xenstore, + XenstoreRfc, +} + +pub enum AgentPublisher { + Console(ConsolePublisher), + XenstoreRfc(XenstoreRfcPublisher), + XenstoreStd(XenstoreStdPublisher), +} + +impl AgentPublisher { + pub fn new(kind: PublisherKind) -> io::Result { + match kind { + PublisherKind::Console => Ok(Self::Console(ConsolePublisher::default())), + PublisherKind::Xenstore => Ok(Self::XenstoreStd(XenstoreStdPublisher)), + PublisherKind::XenstoreRfc => Ok(Self::XenstoreRfc(XenstoreRfcPublisher)), + } + } + + pub async fn run(self, channel: mpsc::Receiver) { + match self { + AgentPublisher::Console(publisher) => publisher.run(channel).await, + AgentPublisher::XenstoreRfc(publisher) => publisher.run(channel).await, + AgentPublisher::XenstoreStd(publisher) => publisher.run(channel).await, + } + } +} From 9d4dc81413491a7872f9360478f6afd68d1c2761 Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Tue, 18 Feb 2025 18:24:51 +0100 Subject: [PATCH 06/24] Collapse logic.rs into main.rs. Signed-off-by: Teddy Astie --- xen-guest-agent/src/logic.rs | 26 -------------------------- xen-guest-agent/src/main.rs | 29 +++++++++++++++++++++++------ xen-guest-agent/src/plugins.rs | 1 + xen-guest-agent/src/publisher.rs | 2 +- 4 files changed, 25 insertions(+), 33 deletions(-) delete mode 100644 xen-guest-agent/src/logic.rs diff --git a/xen-guest-agent/src/logic.rs b/xen-guest-agent/src/logic.rs deleted file mode 100644 index a71e518..0000000 --- a/xen-guest-agent/src/logic.rs +++ /dev/null @@ -1,26 +0,0 @@ -use futures::{channel::mpsc, SinkExt}; -use tokio::task::JoinSet; - -use guest_metrics::{plugin::GuestAgentPlugin, GuestMetric}; - -use crate::{plugins::NetworkPlugin, publisher::AgentPublisher, GuestAgentConfig}; - -pub async fn run(config: GuestAgentConfig) -> anyhow::Result<()> { - let mut set: JoinSet<()> = JoinSet::new(); - - let (mut tx, rx) = mpsc::channel(4); - let publisher = AgentPublisher::new(config.publisher)?; - - set.spawn(publisher.run(rx)); - - // Remove old entries from previous agent to avoid having unknown - // interfaces. We will repopulate existing ones immediatly. - tx.send(GuestMetric::CleanupIfaces).await?; - - set.spawn(provider_os::OsInfoPlugin.run(tx.clone())); - set.spawn(provider_memory::MemoryPlugin.run(tx.clone())); - set.spawn(NetworkPlugin::new(config.network)?.run(tx.clone())); - - println!("{:?}", set.join_all().await); - Ok(()) -} diff --git a/xen-guest-agent/src/main.rs b/xen-guest-agent/src/main.rs index bbcc33f..e899020 100644 --- a/xen-guest-agent/src/main.rs +++ b/xen-guest-agent/src/main.rs @@ -1,13 +1,14 @@ -mod logic; -//mod provider; mod plugins; mod publisher; -//pub mod metrics; use clap::Parser; +use futures::{channel::mpsc, SinkExt}; use log::LevelFilter; -use plugins::NetworkPluginKind; -use publisher::PublisherKind; +use tokio::task::JoinSet; + +use guest_metrics::{plugin::GuestAgentPlugin, GuestMetric}; +use plugins::{NetworkPlugin, NetworkPluginKind}; +use publisher::{AgentPublisher, PublisherKind}; const MEM_PERIOD_SECONDS: f64 = 5.0; @@ -42,7 +43,23 @@ async fn main() -> anyhow::Result<()> { setup_logger(config.stderr, config.log_level)?; - logic::run(config).await + let mut set: JoinSet<()> = JoinSet::new(); + + let (mut tx, rx) = mpsc::channel(4); + let publisher = AgentPublisher::new(config.publisher)?; + + set.spawn(publisher.run(rx)); + + // Remove old entries from previous agent to avoid having unknown + // interfaces. We will repopulate existing ones immediatly. + tx.send(GuestMetric::CleanupIfaces).await?; + + set.spawn(provider_os::OsInfoPlugin.run(tx.clone())); + set.spawn(provider_memory::MemoryPlugin.run(tx.clone())); + set.spawn(NetworkPlugin::new(config.network)?.run(tx.clone())); + + println!("{:?}", set.join_all().await); + Ok(()) } fn setup_logger(use_stderr: bool, level: LevelFilter) -> anyhow::Result<()> { diff --git a/xen-guest-agent/src/plugins.rs b/xen-guest-agent/src/plugins.rs index fb0a1b1..e04760b 100644 --- a/xen-guest-agent/src/plugins.rs +++ b/xen-guest-agent/src/plugins.rs @@ -1,6 +1,7 @@ use std::io; use futures::channel::mpsc; + use guest_metrics::{plugin::GuestAgentPlugin, GuestMetric}; use provider_simple::SimpleNetworkPlugin; diff --git a/xen-guest-agent/src/publisher.rs b/xen-guest-agent/src/publisher.rs index 7887eb2..666fbe5 100644 --- a/xen-guest-agent/src/publisher.rs +++ b/xen-guest-agent/src/publisher.rs @@ -1,8 +1,8 @@ use std::io; use futures::channel::mpsc; -use guest_metrics::{plugin::GuestAgentPublisher, GuestMetric}; +use guest_metrics::{plugin::GuestAgentPublisher, GuestMetric}; use publisher_console::ConsolePublisher; use publisher_xenstore::{XenstoreRfcPublisher, XenstoreStdPublisher}; From a9d58dc74aab348d95b5338a742e6f8f4fa788cb Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Tue, 18 Feb 2025 18:25:44 +0100 Subject: [PATCH 07/24] Use mac address for vif-detect. Signed-off-by: Teddy Astie --- providers/provider-simple/src/lib.rs | 7 +++++-- providers/vif-detect/src/freebsd.rs | 4 ++-- providers/vif-detect/src/lib.rs | 2 +- providers/vif-detect/src/linux.rs | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/providers/provider-simple/src/lib.rs b/providers/provider-simple/src/lib.rs index b55a9be..b9cbad6 100644 --- a/providers/provider-simple/src/lib.rs +++ b/providers/provider-simple/src/lib.rs @@ -19,9 +19,10 @@ impl GuestAgentPlugin for SimpleNetworkPlugin { ) -> impl std::future::Future + Send { async move { let mut timer = tokio::time::interval(Duration::from_secs_f32(5.0)); + let vif_detector = PlatformVifDetector::default(); loop { - self.track_interfaces(&mut channel).await; + self.track_interfaces(&vif_detector, &mut channel).await; timer.tick().await; } @@ -32,6 +33,7 @@ impl GuestAgentPlugin for SimpleNetworkPlugin { impl SimpleNetworkPlugin { async fn track_interfaces( &mut self, + vif_detector: &impl VifDetector, channel: &mut futures::channel::mpsc::Sender, ) { let interfaces = network_interface::NetworkInterface::show().unwrap(); @@ -71,7 +73,8 @@ impl SimpleNetworkPlugin { uuid, index: interface.index, name: interface.name.clone(), - toolstack_iface: PlatformVifDetector::get_toolstack_interface(&interface.name) + toolstack_iface: vif_detector + .get_toolstack_interface(&interface.name, interface.mac_addr.as_deref()) .unwrap_or_default(), })) .await diff --git a/providers/vif-detect/src/freebsd.rs b/providers/vif-detect/src/freebsd.rs index aaa08ea..1a0f67f 100644 --- a/providers/vif-detect/src/freebsd.rs +++ b/providers/vif-detect/src/freebsd.rs @@ -1,11 +1,11 @@ -use crate::datastructs::ToolstackNetInterface; +use guest_metrics::ToolstackNetInterface; #[derive(Default)] pub struct FreebsdVifDetector; impl super::VifDetector for FreebsdVifDetector { // identifies a VIF as named "xn%ID" - fn get_toolstack_interface(iface_name: &str) -> Option { + fn get_toolstack_interface(&self, iface_name: &str, _mac_addr: Option<&str>) -> Option { const PREFIX: &str = "xn"; if !iface_name.starts_with(PREFIX) { log::debug!("ignoring interface {iface_name} as not starting with '{PREFIX}'"); diff --git a/providers/vif-detect/src/lib.rs b/providers/vif-detect/src/lib.rs index da67c76..2b50a09 100644 --- a/providers/vif-detect/src/lib.rs +++ b/providers/vif-detect/src/lib.rs @@ -6,7 +6,7 @@ pub mod freebsd; pub mod linux; pub trait VifDetector: Default { - fn get_toolstack_interface(iface_name: &str) -> Option; + fn get_toolstack_interface(&self, iface_name: &str, mac_addr: Option<&str>) -> Option; } #[cfg(target_os = "linux")] diff --git a/providers/vif-detect/src/linux.rs b/providers/vif-detect/src/linux.rs index c5a5a52..e734914 100644 --- a/providers/vif-detect/src/linux.rs +++ b/providers/vif-detect/src/linux.rs @@ -13,7 +13,7 @@ use super::VifDetector; pub struct LinuxVifDetector; impl VifDetector for LinuxVifDetector { - fn get_toolstack_interface(iface_name: &str) -> Option { + fn get_toolstack_interface(&self, iface_name: &str, _mac_addr: Option<&str>) -> Option { // FIXME: using ETHTOOL ioctl could be better let device_path = format!("/sys/class/net/{iface_name}/device"); let devtype = fs::read_to_string(format!("{device_path}/devtype")) From 7f0bab45873fdada398df11e3bf5a499a89dc303 Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Tue, 18 Feb 2025 18:26:02 +0100 Subject: [PATCH 08/24] Add Windows support for vif-detect Signed-off-by: Teddy Astie --- providers/vif-detect/Cargo.toml | 4 +++ providers/vif-detect/src/lib.rs | 5 +++ providers/vif-detect/src/windows.rs | 47 +++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 providers/vif-detect/src/windows.rs diff --git a/providers/vif-detect/Cargo.toml b/providers/vif-detect/Cargo.toml index df93cff..0b0c42b 100644 --- a/providers/vif-detect/Cargo.toml +++ b/providers/vif-detect/Cargo.toml @@ -6,3 +6,7 @@ edition = "2021" [dependencies] log = "*" guest-metrics = { path = "../../guest-metrics" } + +[target.'cfg(target_os = "windows")'.dependencies] +xenstore-rs = "0.8" +xenstore-win = { git = "https://github.com/TSnake41/xenstore-win.git" } \ No newline at end of file diff --git a/providers/vif-detect/src/lib.rs b/providers/vif-detect/src/lib.rs index 2b50a09..17023ac 100644 --- a/providers/vif-detect/src/lib.rs +++ b/providers/vif-detect/src/lib.rs @@ -4,6 +4,8 @@ use guest_metrics::ToolstackNetInterface; pub mod freebsd; #[cfg(target_os = "linux")] pub mod linux; +#[cfg(target_os = "windows")] +pub mod windows; pub trait VifDetector: Default { fn get_toolstack_interface(&self, iface_name: &str, mac_addr: Option<&str>) -> Option; @@ -14,3 +16,6 @@ pub type PlatformVifDetector = linux::LinuxVifDetector; #[cfg(target_os = "freebsd")] pub type PlatformVifDetector = freebsd::FreebsdVifDetector; + +#[cfg(target_os = "windows")] +pub type PlatformVifDetector = windows::WindowsVifDetector; \ No newline at end of file diff --git a/providers/vif-detect/src/windows.rs b/providers/vif-detect/src/windows.rs new file mode 100644 index 0000000..4bd25c7 --- /dev/null +++ b/providers/vif-detect/src/windows.rs @@ -0,0 +1,47 @@ +use std::sync::Mutex; + +use guest_metrics::ToolstackNetInterface; +use xenstore_rs::Xs; +use xenstore_win::XsWindows; + +pub struct WindowsVifDetector(Option>); + +impl Default for WindowsVifDetector { + fn default() -> Self { + Self( + XsWindows::new() + .inspect_err(|e| log::warn!("Unable to load xenstore: {e}")) + .ok() + .map(Mutex::new), + ) + } +} + +impl super::VifDetector for WindowsVifDetector { + fn get_toolstack_interface( + &self, + iface_name: &str, + mac_addr: Option<&str>, + ) -> Option { + let xs = self.0.as_ref()?.lock().unwrap(); + let mac_addr = mac_addr?; + + log::info!("Probing {iface_name} (MAC: {mac_addr})"); + + for vif_id in xs.directory("device/vif").ok()? { + let Some(vif_mac) = xs.read(&format!("device/vif/{vif_id}/mac")).ok() else { + log::warn!("vif/{vif_id} has no MAC address"); + continue + }; + + if mac_addr.trim().eq_ignore_ascii_case(vif_mac.trim()) { + log::info!("{iface_name} is vif/{vif_id}"); + return Some(ToolstackNetInterface::Vif( + vif_id.parse().expect("Unable to parse vif id"), + )); + } + } + + Some(ToolstackNetInterface::Unknown) + } +} From 7c3a75493c30ab79520ae3237d199181bde1d261 Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Tue, 18 Feb 2025 18:26:18 +0100 Subject: [PATCH 09/24] Add Windows support for provider-memory Signed-off-by: Teddy Astie --- providers/provider-memory/Cargo.toml | 3 +++ providers/provider-memory/src/windows.rs | 25 ++++++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/providers/provider-memory/Cargo.toml b/providers/provider-memory/Cargo.toml index 3e957b4..377d9c4 100644 --- a/providers/provider-memory/Cargo.toml +++ b/providers/provider-memory/Cargo.toml @@ -7,3 +7,6 @@ edition = "2021" guest-metrics = { path = "../../guest-metrics" } futures = "0.3" tokio = { version = "1.25.0", features = ["time"] } + +[target.'cfg(target_os = "windows")'.dependencies] +windows = { version = "0.58", features = ["Win32_System_SystemInformation"] } diff --git a/providers/provider-memory/src/windows.rs b/providers/provider-memory/src/windows.rs index daa953f..b571930 100644 --- a/providers/provider-memory/src/windows.rs +++ b/providers/provider-memory/src/windows.rs @@ -1,21 +1,30 @@ +use std::io; + +use windows::Win32::System::SystemInformation::{GlobalMemoryStatusEx, MEMORYSTATUSEX}; + +use crate::MemorySource; + #[derive(Default)] pub struct WindowsMemorySource; +fn read_memstatus_ex() -> io::Result { + let mut mem_status = MEMORYSTATUSEX::default(); + mem_status.dwLength = size_of_val(&mem_status) as u32; + + unsafe { GlobalMemoryStatusEx(&mut mem_status).map_err(io::Error::other)? }; + Ok(mem_status) +} + impl MemorySource for WindowsMemorySource { fn new() -> io::Result { Ok(Self) } fn get_total_kb(&mut self) -> io::Result { - Err(io::Error::new( - io::ErrorKind::Unsupported, - "no implementation for mem_total", - )) + Ok(read_memstatus_ex()?.ullTotalPhys as usize) } + fn get_available_kb(&mut self) -> io::Result { - Err(io::Error::new( - io::ErrorKind::Unsupported, - "no implementation for mem_avail", - )) + Ok(read_memstatus_ex()?.ullAvailPhys as usize) } } From d25bb50d826c7e75e86ea62050fa98d46e931f5c Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Tue, 18 Feb 2025 18:26:49 +0100 Subject: [PATCH 10/24] Use upstream xenstore-rs in publisher-xenstore Signed-off-by: Teddy Astie --- publishers/publisher-xenstore/Cargo.toml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/publishers/publisher-xenstore/Cargo.toml b/publishers/publisher-xenstore/Cargo.toml index 7322f36..b341f41 100644 --- a/publishers/publisher-xenstore/Cargo.toml +++ b/publishers/publisher-xenstore/Cargo.toml @@ -8,12 +8,8 @@ guest-metrics = { path = "../../guest-metrics" } uuid = "1.11" futures = "0.3" log = "*" +xenstore-rs = "0.8" -# Use "unix" feature on non-Windows. -[target.'cfg(not(target_os = "windows"))'.dependencies] -xenstore-rs = { git = "https://github.com/TSnake41/xenstore-rs.git", branch = "pure-rust" } - -# Use xenstore-win on Windows +# Also use xenstore-win on Windows [target.'cfg(target_os = "windows")'.dependencies] -xenstore-rs = { git = "https://github.com/TSnake41/xenstore-rs.git", branch = "pure-rust", default-features = false } xenstore-win = { git = "https://github.com/TSnake41/xenstore-win.git" } \ No newline at end of file From e93b72bd82cf3edc40423957f5dd814be5024f19 Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Tue, 18 Feb 2025 18:26:55 +0100 Subject: [PATCH 11/24] Update Cargo.lock Signed-off-by: Teddy Astie --- Cargo.lock | 144 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 81 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8094ca5..500d4a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,19 +67,20 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "autocfg" @@ -104,9 +105,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "byteorder" @@ -116,9 +117,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.2" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" dependencies = [ "shlex", ] @@ -131,9 +132,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.21" +version = "4.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d" dependencies = [ "clap_builder", "clap_derive", @@ -141,9 +142,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c" dependencies = [ "anstream", "anstyle", @@ -153,9 +154,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" dependencies = [ "heck", "proc-macro2", @@ -165,9 +166,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" @@ -210,9 +211,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", @@ -220,9 +221,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ "anstream", "anstyle", @@ -331,13 +332,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ "cfg-if", "libc", "wasi", + "windows-targets", ] [[package]] @@ -392,15 +394,15 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "libc" -version = "0.2.166" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "match_cfg" @@ -416,9 +418,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" dependencies = [ "adler2", ] @@ -452,24 +454,24 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "os_info" -version = "3.8.2" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +checksum = "2a604e53c24761286860eba4e2c8b23a0161526476b1de520139d69cdb85a6b5" dependencies = [ "log", "serde", @@ -478,9 +480,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -496,9 +498,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -510,6 +512,7 @@ dependencies = [ "futures", "guest-metrics", "tokio", + "windows", ] [[package]] @@ -556,9 +559,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -609,18 +612,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -650,9 +653,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.89" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -708,9 +711,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -731,9 +734,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -741,9 +744,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.41.1" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "pin-project-lite", @@ -752,9 +755,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", @@ -772,9 +775,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "utf8parse" @@ -784,9 +787,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.11.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6" dependencies = [ "getrandom", ] @@ -803,6 +806,8 @@ version = "0.1.0" dependencies = [ "guest-metrics", "log", + "xenstore-rs", + "xenstore-win", ] [[package]] @@ -817,9 +822,12 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] [[package]] name = "winapi" @@ -998,6 +1006,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] + [[package]] name = "xen-guest-agent" version = "0.5.0-dev" @@ -1021,13 +1038,14 @@ dependencies = [ [[package]] name = "xenstore-rs" -version = "1.0.0-dev" -source = "git+https://github.com/TSnake41/xenstore-rs.git?branch=pure-rust#f02ef37f82b7401e7f13df22e319078b341a03be" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df0b81a6d7038b1540a5368e43febc3b48c05d9426c96f815aba9a33b0e8d45" [[package]] name = "xenstore-win" version = "0.1.0" -source = "git+https://github.com/TSnake41/xenstore-win.git#a38e4284807891a4bf18046bc69ff5e5a9c7862a" +source = "git+https://github.com/TSnake41/xenstore-win.git#d79e6dfa10748839403c2a6134a98660d814c0d9" dependencies = [ "log", "windows", From bf470ed9f3d26e7b7cfc06403238fc1ebb532dbe Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Wed, 19 Feb 2025 15:11:43 +0100 Subject: [PATCH 12/24] Reintroduce netlink support Signed-off-by: Teddy Astie --- Cargo.lock | 182 +++++++++- providers/provider-netlink/Cargo.toml | 3 + providers/provider-netlink/src/lib.rs | 420 ++++++++++------------ providers/provider-netlink/src/rewrite.rs | 89 ----- xen-guest-agent/Cargo.toml | 2 + xen-guest-agent/src/plugins.rs | 15 + 6 files changed, 389 insertions(+), 322 deletions(-) delete mode 100644 providers/provider-netlink/src/rewrite.rs diff --git a/Cargo.lock b/Cargo.lock index 500d4a7..254fff4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,6 +115,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" + [[package]] name = "cc" version = "1.2.14" @@ -338,7 +344,7 @@ checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.13.3+wasi-0.2.2", "windows-targets", ] @@ -425,6 +431,81 @@ dependencies = [ "adler2", ] +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "netlink-packet-core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +dependencies = [ + "anyhow", + "byteorder", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c171cd77b4ee8c7708da746ce392440cb7bcf618d122ec9ecc607b12938bf4" +dependencies = [ + "anyhow", + "byteorder", + "libc", + "log", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror 1.0.69", +] + +[[package]] +name = "netlink-proto" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72452e012c2f8d612410d89eea01e2d9b56205274abb35d53f60200b2ec41d60" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror 2.0.11", +] + +[[package]] +name = "netlink-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23" +dependencies = [ + "bytes", + "futures", + "libc", + "log", + "tokio", +] + [[package]] name = "network-interface" version = "2.0.0" @@ -433,10 +514,21 @@ checksum = "433419f898328beca4f2c6c73a1b52540658d92b0a99f0269330457e0fd998d5" dependencies = [ "cc", "libc", - "thiserror", + "thiserror 1.0.69", "winapi", ] +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -478,6 +570,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -515,6 +613,23 @@ dependencies = [ "windows", ] +[[package]] +name = "provider-netlink" +version = "0.1.0" +dependencies = [ + "anyhow", + "futures", + "guest-metrics", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "rtnetlink", + "tokio", + "uuid", + "vif-detect", +] + [[package]] name = "provider-os" version = "0.6.0-dev" @@ -595,6 +710,24 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rtnetlink" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b684475344d8df1859ddb2d395dd3dac4f8f3422a1aa0725993cb375fc5caba5" +dependencies = [ + "futures", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-packet-utils", + "netlink-proto", + "netlink-sys", + "nix", + "thiserror 1.0.69", + "tokio", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -645,6 +778,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "strsim" version = "0.11.1" @@ -672,7 +815,7 @@ dependencies = [ "byteorder", "enum-as-inner", "libc", - "thiserror", + "thiserror 1.0.69", "walkdir", ] @@ -695,7 +838,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", ] [[package]] @@ -709,6 +861,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.3.37" @@ -749,8 +912,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", + "libc", + "mio", "pin-project-lite", + "socket2", "tokio-macros", + "windows-sys 0.52.0", ] [[package]] @@ -820,6 +987,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasi" version = "0.13.3+wasi-0.2.2" @@ -1027,6 +1200,7 @@ dependencies = [ "guest-metrics", "log", "provider-memory", + "provider-netlink", "provider-os", "provider-simple", "publisher-console", diff --git a/providers/provider-netlink/Cargo.toml b/providers/provider-netlink/Cargo.toml index f558030..2e67ee8 100644 --- a/providers/provider-netlink/Cargo.toml +++ b/providers/provider-netlink/Cargo.toml @@ -9,8 +9,11 @@ guest-metrics = { path = "../../guest-metrics" } tokio = { version = "1", features = ["rt"] } uuid = "1.11" anyhow = "*" +log = "*" netlink-packet-core = { version = "0.7.0" } netlink-packet-route = { version = ">=0.18.0, <0.20" } netlink-proto = { version = "0.11.2" } rtnetlink = { version = "0.14.0" } + +vif-detect = { path = "../vif-detect" } diff --git a/providers/provider-netlink/src/lib.rs b/providers/provider-netlink/src/lib.rs index c24454a..1b2492a 100644 --- a/providers/provider-netlink/src/lib.rs +++ b/providers/provider-netlink/src/lib.rs @@ -1,109 +1,35 @@ -use futures::channel::mpsc::UnboundedReceiver; -use futures::ready; -use futures::stream::{Stream, StreamExt}; +use std::{collections::HashMap, io}; + +use guest_metrics::{plugin::GuestAgentPlugin, GuestMetric, NetEvent, NetEventOp, NetInterface}; +use vif_detect::VifDetector; + +use futures::{ + channel::mpsc::{self, UnboundedReceiver}, + SinkExt, StreamExt, +}; +use uuid::Uuid; + use netlink_packet_core::{ NetlinkHeader, NetlinkMessage, NetlinkPayload, NLM_F_DUMP, NLM_F_REQUEST, }; use netlink_packet_route::{ - address, address::AddressMessage, link, link::LinkMessage, RouteNetlinkMessage, + address::{AddressAttribute, AddressMessage}, + link::{LinkAttribute, LinkMessage}, + RouteNetlinkMessage, }; use netlink_proto::{ - self, new_connection, + new_connection, sys::{protocols::NETLINK_ROUTE, AsyncSocket, SocketAddr}, }; use rtnetlink::constants::{RTMGRP_IPV4_IFADDR, RTMGRP_IPV6_IFADDR, RTMGRP_LINK}; -use std::collections::hash_map; -use std::io; -use std::net::IpAddr; -use std::pin::Pin; -use std::sync::{Arc, Mutex}; -use std::task::{Context, Poll}; -use std::vec::Vec; -pub struct NetlinkNetworkSource { +struct NetlinkConnection { handle: netlink_proto::ConnectionHandle, messages: UnboundedReceiver<(NetlinkMessage, SocketAddr)>, - iface_cache: HashMap<>, -} - -impl NetlinkNetworkSource { - async fn collect_current(&mut self) -> anyhow::Result> { - let mut events = Vec::::new(); - - // Create the netlink message that requests the links to be dumped - let mut nl_hdr = NetlinkHeader::default(); - nl_hdr.flags = NLM_F_DUMP | NLM_F_REQUEST; - let nl_msg = NetlinkMessage::new( - nl_hdr, - RouteNetlinkMessage::GetLink(LinkMessage::default()).into(), - ); - // Send the request - let mut nl_response = self.handle.request(nl_msg, SocketAddr::new(0, 0))?; - // Handle response - while let Some(packet) = nl_response.next().await { - if let NetlinkMessage { - payload: NetlinkPayload::InnerMessage(msg), - .. - } = packet - { - events.extend(self.netevent_from_rtnetlink(&msg)?); - } - } - - // Create the netlink message that requests the addresses to be dumped - let mut nl_hdr = NetlinkHeader::default(); - nl_hdr.flags = NLM_F_DUMP | NLM_F_REQUEST; - let nl_msg = NetlinkMessage::new( - nl_hdr, - RouteNetlinkMessage::GetAddress(AddressMessage::default()).into(), - ); - // Send the request - let mut nl_response = self.handle.request(nl_msg, SocketAddr::new(0, 0))?; - // Handle response - while let Some(packet) = nl_response.next().await { - if let NetlinkMessage { - payload: NetlinkPayload::InnerMessage(msg), - .. - } = packet - { - events.extend(self.netevent_from_rtnetlink(&msg)?); - } - } - - Ok(events) - } } -impl Stream for NetlinkNetworkSource { - type Item = Vec; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - loop { - let Some((message, _)) = ready!(self.messages.poll_next_unpin(cx)) else { - log::info!("No more netlink message"); - return Poll::Ready(None); - }; - - if let NetlinkMessage { - payload: NetlinkPayload::InnerMessage(msg), - .. - } = message - { - let Ok(events) = self - .netevent_from_rtnetlink(&msg) - .inspect_err(|e| log::error!("Unable to fetch netlink messages ({e})")) - else { - return Poll::Ready(None); - }; - - return Poll::Ready(Some(events)); - } - } - } -} - -impl NetlinkNetworkSource { - pub fn new() -> io::Result { +impl NetlinkConnection { + fn new() -> io::Result { let (mut connection, handle, messages) = new_connection(NETLINK_ROUTE)?; // What kinds of broadcast messages we want to listen for. let nl_mgroup_flags = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; @@ -112,159 +38,195 @@ impl NetlinkNetworkSource { .socket_mut() .socket_mut() .bind(&nl_addr) - .expect("failed to bind"); + .expect("failed to bind to netlink"); + tokio::spawn(connection); - Ok(NetlinkNetworkSource { - handle, - messages, - iface_cache: Default::default(), - }) + Ok(Self { handle, messages }) } +} - fn netevent_from_rtnetlink( - &mut self, - nl_msg: &RouteNetlinkMessage, - ) -> io::Result> { - let mut events = Vec::::new(); - match nl_msg { - RouteNetlinkMessage::NewLink(link_msg) => { - let (iface, mac_address) = self.nl_linkmessage_decode(link_msg)?; - log::debug!("NewLink({iface:?} {mac_address:?})"); - events.push(NetEvent { - iface: iface.clone(), - op: NetEventOp::AddIface, - }); - if let Some(mac_address) = mac_address { - events.push(NetEvent { - iface, - op: NetEventOp::AddMac(mac_address), - }); +#[derive(Default)] +pub struct NetlinkPlugin; + +impl GuestAgentPlugin for NetlinkPlugin { + fn run( + self, + mut channel: mpsc::Sender, + ) -> impl std::future::Future + Send { + async move { + let connection = NetlinkConnection::new().unwrap(); + let vif_identify = vif_detect::PlatformVifDetector::default(); + let mut interfaces = HashMap::new(); + + // Create the netlink message that requests the links to be dumped + let mut nl_hdr = NetlinkHeader::default(); + nl_hdr.flags = NLM_F_DUMP | NLM_F_REQUEST; + // Send the request + let link_stream = connection + .handle + .request( + NetlinkMessage::new( + nl_hdr, + RouteNetlinkMessage::GetLink(LinkMessage::default()).into(), + ), + SocketAddr::new(0, 0), + ) + .unwrap(); + + // Create the netlink message that requests the addresses to be dumped + let mut nl_hdr = NetlinkHeader::default(); + nl_hdr.flags = NLM_F_DUMP | NLM_F_REQUEST; + // Send the request + let address_stream = connection + .handle + .request( + NetlinkMessage::new( + nl_hdr, + RouteNetlinkMessage::GetAddress(AddressMessage::default()).into(), + ), + SocketAddr::new(0, 0), + ) + .unwrap(); + + let mut stream = link_stream + .chain(address_stream) + .chain(connection.messages.map(|(msg, _)| msg)); + + while let Some(msg) = stream.next().await { + if let NetlinkPayload::InnerMessage(inner_msg) = msg.payload { + if let Err(e) = + process_message(inner_msg, &mut channel, &vif_identify, &mut interfaces) + .await + { + log::error!("Unable to process netlink message: {e}"); + } } } - RouteNetlinkMessage::DelLink(link_msg) => { - let (iface, mac_address) = self.nl_linkmessage_decode(link_msg)?; - log::debug!("DelLink({iface:?} {mac_address:?})"); - if let Some(mac_address) = mac_address { - events.push(NetEvent { - iface: iface.clone(), - op: NetEventOp::RmMac(mac_address), - }); // redundant - } - events.push(NetEvent { - iface, - op: NetEventOp::RmIface, - }); - } - RouteNetlinkMessage::NewAddress(address_msg) => { - // FIXME does not distinguish when IP is on DOWN iface - let (iface, address) = self.nl_addressmessage_decode(address_msg)?; - log::debug!("NewAddress({iface:?} {address})"); - events.push(NetEvent { - iface, - op: NetEventOp::AddIp(address), - }); - } - RouteNetlinkMessage::DelAddress(address_msg) => { - let (iface, address) = self.nl_addressmessage_decode(address_msg)?; - log::debug!("DelAddress({iface:?} {address})"); - events.push(NetEvent { - iface, - op: NetEventOp::RmIp(address), - }); - } - _ => { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - format!("unhandled RouteNetlinkMessage: {nl_msg:?}"), - )); - } - }; - Ok(events) + } } +} - fn nl_linkmessage_decode( - &mut self, - msg: &LinkMessage, - ) -> io::Result<( - Arc>, // ref to the (possibly new) impacted interface - Option, // MAC address - )> { - let LinkMessage { - header, attributes, .. - } = msg; +async fn process_message( + inner_msg: RouteNetlinkMessage, + channel: &mut mpsc::Sender, + vif_identify: &impl VifDetector, + interfaces: &mut HashMap, +) -> anyhow::Result<()> { + match inner_msg { + RouteNetlinkMessage::NewLink(link_message) | RouteNetlinkMessage::GetLink(link_message) => { + let Some(ifname) = + link_message + .attributes + .iter() + .find_map(|attribute| match attribute { + LinkAttribute::IfName(n) => Some(n), + _ => None, + }) + else { + log::warn!("Ignoring NewLink/GetLink message without ifname"); + return Ok(()); + }; - // extract fields of interest - let mut iface_name: Option = None; - let mut address_bytes: Option<&Vec> = None; - for nla in attributes { - if let link::LinkAttribute::IfName(name) = nla { - iface_name = Some(name.to_string()); - } - if let link::LinkAttribute::Address(addr) = nla { - address_bytes = Some(addr); - } - } - // make sure message contains an address - let mac_address = address_bytes.map(|address_bytes| { - address_bytes + let mac = link_message + .attributes .iter() - .map(|b| format!("{b:02x}")) - .collect::>() - .join(":") - }); + .find_map(|attribute| match attribute { + LinkAttribute::Address(addr) => Some( + addr.iter() + .map(|b| format!("{b:02x}")) + .collect::>() + .join(":"), + ), + _ => None, + }); - let iface = self - .iface_cache - .entry(header.index) - .or_insert_with_key(|index| { - Mutex::new(NetInterface::new(*index, iface_name.clone())).into() - }); + let Some(toolstack_iface) = + vif_identify.get_toolstack_interface(&ifname, mac.as_deref()) + else { + log::debug!("Unknown interface {ifname} (mac: {mac:?})"); + return Ok(()); + }; - // handle renaming - if let Some(iface_name) = iface_name { - let iface_renamed = iface.lock().unwrap().name != iface_name; - if iface_renamed { - log::trace!("name change: {iface:?} now named '{iface_name}'"); - iface.lock().unwrap().name = iface_name; - } - }; + let uuid = Uuid::new_v4(); + + interfaces.insert(link_message.header.index, uuid); + channel + .send(GuestMetric::AddIface(NetInterface { + uuid, + index: link_message.header.index, + name: ifname.clone(), + toolstack_iface, + })) + .await?; + } + RouteNetlinkMessage::DelLink(link_message) => { + let Some(&uuid) = interfaces.get(&link_message.header.index) else { + return Ok(()); + }; - Ok((iface.clone(), mac_address)) - } + channel.send(GuestMetric::RmIface(uuid)).await.ok(); + } + RouteNetlinkMessage::NewAddress(address_message) + | RouteNetlinkMessage::GetAddress(address_message) => { + let Some(&iface_id) = interfaces.get(&address_message.header.index) else { + log::warn!( + "Ignoring NewAddress/GetAddress on unknown interface with index={}", + address_message.header.index + ); + return Ok(()); + }; - fn nl_addressmessage_decode( - &mut self, - msg: &AddressMessage, - ) -> io::Result<(Arc>, IpAddr)> { - let AddressMessage { - header, attributes, .. - } = msg; + let Some(&addr) = + address_message + .attributes + .iter() + .find_map(|attribute| match attribute { + AddressAttribute::Address(addr) => Some(addr), + _ => None, + }) + else { + log::debug!("Got NewAddress/GetAddress without IP."); + return Ok(()); + }; - // extract fields of interest - let mut address: Option<&IpAddr> = None; - for nla in attributes { - if let address::AddressAttribute::Address(addr) = nla { - address = Some(addr); - break; - } + channel + .send(GuestMetric::Network(NetEvent { + iface_id, + op: NetEventOp::AddIp(addr), + })) + .await?; } + RouteNetlinkMessage::DelAddress(address_message) => { + let Some(&iface_id) = interfaces.get(&address_message.header.index) else { + log::warn!( + "Ignoring DelAddress on unknown interface with index={}", + address_message.header.index + ); + return Ok(()); + }; - let iface = match self.iface_cache.entry(header.index) { - hash_map::Entry::Occupied(entry) => entry.get().clone(), - hash_map::Entry::Vacant(_) => { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - format!("unknown interface for index {}", header.index), - )); - } - }; + let Some(&addr) = + address_message + .attributes + .iter() + .find_map(|attribute| match attribute { + AddressAttribute::Address(addr) => Some(addr), + _ => None, + }) + else { + log::debug!("Got DelAddress without IP."); + return Ok(()); + }; - match address { - Some(address) => Ok((iface.clone(), *address)), - None => Err(io::Error::new( - io::ErrorKind::InvalidData, - "unknown address", - )), + channel + .send(GuestMetric::Network(NetEvent { + iface_id, + op: NetEventOp::RmIp(addr), + })) + .await?; } + _ => {} } -} \ No newline at end of file + + Ok(()) +} diff --git a/providers/provider-netlink/src/rewrite.rs b/providers/provider-netlink/src/rewrite.rs deleted file mode 100644 index 047c06d..0000000 --- a/providers/provider-netlink/src/rewrite.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::{collections::HashMap, io}; - -use futures::{channel::mpsc::UnboundedReceiver, StreamExt}; -use guest_metrics::plugin::GuestAgentPlugin; -use netlink_packet_core::{NetlinkMessage, NetlinkPayload}; -use netlink_packet_route::{link::LinkAttribute, RouteNetlinkMessage}; -use netlink_proto::{ - new_connection, - sys::{protocols::NETLINK_ROUTE, AsyncSocket, SocketAddr}, -}; -use rtnetlink::constants::{RTMGRP_IPV4_IFADDR, RTMGRP_IPV6_IFADDR, RTMGRP_LINK}; -use uuid::Uuid; - -pub struct NetlinkConnection { - handle: netlink_proto::ConnectionHandle, - messages: UnboundedReceiver<(NetlinkMessage, SocketAddr)>, - - interfaces: HashMap, -} - -impl NetlinkConnection { - pub fn new() -> io::Result { - let (mut connection, handle, messages) = new_connection(NETLINK_ROUTE)?; - // What kinds of broadcast messages we want to listen for. - let nl_mgroup_flags = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; - let nl_addr = SocketAddr::new(0, nl_mgroup_flags); - connection - .socket_mut() - .socket_mut() - .bind(&nl_addr) - .expect("failed to bind"); - tokio::spawn(connection); - Ok(Self { handle, messages }) - } -} - -pub struct NetlinkPlugin; - -impl GuestAgentPlugin for NetlinkPlugin { - fn run( - self, - mut channel: futures::channel::mpsc::Sender, - ) -> impl std::future::Future + Send { - async move { - let mut connection = NetlinkConnection::new().unwrap(); - - if let Some((msg, _)) = connection.messages.next().await { - if let NetlinkPayload::InnerMessage(inner_msg) = msg.payload { - process_message(inner_msg); - } - } - } - } -} - -fn process_message(inner_msg: RouteNetlinkMessage) { - match inner_msg { - RouteNetlinkMessage::NewLink(link_message) | RouteNetlinkMessage::GetLink(link_message) => { - let Some(ifname) = - link_message - .attributes - .iter() - .find_map(|attribute| match attribute { - LinkAttribute::IfName(n) => Some(n), - _ => None, - }) - else { - return; - }; - - let Some(mac) = link_message.attributes.iter().find_map(|attribute| match attribute { - LinkAttribute::Address() - }) - } - RouteNetlinkMessage::DelLink(link_message) => { - todo!() - } - RouteNetlinkMessage::NewAddress(address_message) => { - todo!() - } - RouteNetlinkMessage::DelAddress(address_message) => { - todo!() - } - RouteNetlinkMessage::GetAddress(address_message) => { - todo!() - } - _ => todo!(), - } -} diff --git a/xen-guest-agent/Cargo.toml b/xen-guest-agent/Cargo.toml index 3f7d980..c0bd282 100644 --- a/xen-guest-agent/Cargo.toml +++ b/xen-guest-agent/Cargo.toml @@ -23,6 +23,7 @@ publisher-xenstore = { path = "../publishers/publisher-xenstore" } provider-memory = { path = "../providers/provider-memory" } provider-os = { path = "../providers/provider-os" } provider-simple = { path = "../providers/provider-simple" } +provider-netlink = { path = "../providers/provider-netlink", optional = true } [target.'cfg(target_os = "freebsd")'.dependencies] sysctl = "0.5.0" @@ -31,3 +32,4 @@ sysctl = "0.5.0" syslog = "6.0" [features] +netlink = ["dep:provider-netlink"] \ No newline at end of file diff --git a/xen-guest-agent/src/plugins.rs b/xen-guest-agent/src/plugins.rs index e04760b..7d15572 100644 --- a/xen-guest-agent/src/plugins.rs +++ b/xen-guest-agent/src/plugins.rs @@ -5,31 +5,46 @@ use futures::channel::mpsc; use guest_metrics::{plugin::GuestAgentPlugin, GuestMetric}; use provider_simple::SimpleNetworkPlugin; +#[cfg(feature = "netlink")] +use provider_netlink::NetlinkPlugin; + #[derive(Clone, Copy, Debug, clap::ValueEnum)] pub enum NetworkPluginKind { Simple, + #[cfg(feature = "netlink")] + Netlink, } impl Default for NetworkPluginKind { fn default() -> Self { + #[cfg(feature = "netlink")] + return Self::Netlink; + + #[cfg(not(feature = "netlink"))] Self::Simple } } pub enum NetworkPlugin { Simple(SimpleNetworkPlugin), + #[cfg(feature = "netlink")] + Netlink(NetlinkPlugin), } impl NetworkPlugin { pub fn new(kind: NetworkPluginKind) -> io::Result { match kind { NetworkPluginKind::Simple => Ok(Self::Simple(SimpleNetworkPlugin::default())), + #[cfg(feature = "netlink")] + NetworkPluginKind::Netlink => Ok(Self::Netlink(NetlinkPlugin::default())), } } pub async fn run(self, channel: mpsc::Sender) { match self { NetworkPlugin::Simple(plugin) => plugin.run(channel).await, + #[cfg(feature = "netlink")] + NetworkPlugin::Netlink(plugin) => plugin.run(channel).await, } } } From 67d28a649a39b1e8ebbea041bf004b97ae76ae0c Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Wed, 19 Feb 2025 15:12:14 +0100 Subject: [PATCH 13/24] Expose interface kind on console publisher Signed-off-by: Teddy Astie --- publishers/publisher-console/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/publishers/publisher-console/src/lib.rs b/publishers/publisher-console/src/lib.rs index d5e06b3..8e76556 100644 --- a/publishers/publisher-console/src/lib.rs +++ b/publishers/publisher-console/src/lib.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use futures::{channel::mpsc, StreamExt}; use guest_metrics::{ plugin::GuestAgentPublisher, GuestMetric, KernelInfo, NetEventOp, NetInterface, + ToolstackNetInterface, }; use uuid::Uuid; @@ -32,7 +33,12 @@ impl ConsolePublisher { ); } GuestMetric::AddIface(iface) => { - println!("{} +IFACE", iface.index); + let ifkind = match iface.toolstack_iface { + ToolstackNetInterface::Unknown => String::from("unknown"), + ToolstackNetInterface::Vif(id) => format!("vif/{id}"), + _ => todo!(), + }; + println!("{} +IFACE ({})", iface.index, ifkind); self.ifaces.insert(iface.uuid, iface); } GuestMetric::RmIface(iface_id) => { From 402d8015372ac8127de0391288db0f8830c9002b Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Wed, 19 Feb 2025 15:12:45 +0100 Subject: [PATCH 14/24] Expose unknown Linux interfaces as Unknown instead of None Signed-off-by: Teddy Astie --- providers/vif-detect/src/linux.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/providers/vif-detect/src/linux.rs b/providers/vif-detect/src/linux.rs index e734914..4f5a6ce 100644 --- a/providers/vif-detect/src/linux.rs +++ b/providers/vif-detect/src/linux.rs @@ -13,16 +13,22 @@ use super::VifDetector; pub struct LinuxVifDetector; impl VifDetector for LinuxVifDetector { - fn get_toolstack_interface(&self, iface_name: &str, _mac_addr: Option<&str>) -> Option { + fn get_toolstack_interface( + &self, + iface_name: &str, + _mac_addr: Option<&str>, + ) -> Option { // FIXME: using ETHTOOL ioctl could be better let device_path = format!("/sys/class/net/{iface_name}/device"); - let devtype = fs::read_to_string(format!("{device_path}/devtype")) + let Some(devtype) = fs::read_to_string(format!("{device_path}/devtype")) .inspect_err(|e| log::debug!("reading {device_path}/devtype: {e}")) - .ok()?; + .ok() + else { + return Some(ToolstackNetInterface::Unknown); + }; let "vif" = devtype.trim() else { - log::debug!("ignoring device {device_path}, devtype {devtype:?} not 'vif'"); - return None; + return Some(ToolstackNetInterface::Unknown); }; let nodename = fs::read_to_string(format!("{device_path}/nodename")) From 6655f7e960614dfeca8223502a1569b5759d4cd7 Mon Sep 17 00:00:00 2001 From: Tu Dinh Date: Wed, 26 Feb 2025 00:51:49 +0100 Subject: [PATCH 15/24] Run as a service on Windows Signed-off-by: Tu Dinh --- Cargo.lock | 19 ++++ xen-guest-agent/Cargo.toml | 10 ++ xen-guest-agent/src/main.rs | 56 +++++++++-- xen-guest-agent/src/windows_debug_logger.rs | 23 +++++ xen-guest-agent/src/windows_service_main.rs | 102 ++++++++++++++++++++ 5 files changed, 201 insertions(+), 9 deletions(-) create mode 100644 xen-guest-agent/src/windows_debug_logger.rs create mode 100644 xen-guest-agent/src/windows_service_main.rs diff --git a/Cargo.lock b/Cargo.lock index 254fff4..5c67d50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1002,6 +1002,12 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + [[package]] name = "winapi" version = "0.3.9" @@ -1087,6 +1093,17 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-service" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193cae8e647981c35bc947fdd57ba7928b1fa0d4a79305f6dd2dc55221ac35ac" +dependencies = [ + "bitflags", + "widestring", + "windows-sys 0.59.0", +] + [[package]] name = "windows-strings" version = "0.1.0" @@ -1208,6 +1225,8 @@ dependencies = [ "sysctl", "syslog", "tokio", + "windows", + "windows-service", ] [[package]] diff --git a/xen-guest-agent/Cargo.toml b/xen-guest-agent/Cargo.toml index c0bd282..925c4ef 100644 --- a/xen-guest-agent/Cargo.toml +++ b/xen-guest-agent/Cargo.toml @@ -31,5 +31,15 @@ sysctl = "0.5.0" [target.'cfg(target_family = "unix")'.dependencies] syslog = "6.0" +[dependencies.windows] +version = "0.58" +features = [ + "Win32_Foundation", + "Win32_System_Diagnostics_Debug", +] + +[target."cfg(windows)".dependencies] +windows-service = "0.8" + [features] netlink = ["dep:provider-netlink"] \ No newline at end of file diff --git a/xen-guest-agent/src/main.rs b/xen-guest-agent/src/main.rs index e899020..2f99f62 100644 --- a/xen-guest-agent/src/main.rs +++ b/xen-guest-agent/src/main.rs @@ -1,5 +1,9 @@ mod plugins; mod publisher; +#[cfg(windows)] +mod windows_debug_logger; +#[cfg(windows)] +mod windows_service_main; use clap::Parser; use futures::{channel::mpsc, SinkExt}; @@ -10,6 +14,9 @@ use guest_metrics::{plugin::GuestAgentPlugin, GuestMetric}; use plugins::{NetworkPlugin, NetworkPluginKind}; use publisher::{AgentPublisher, PublisherKind}; +#[cfg(windows)] +use windows_debug_logger::WindowsDebugLogger; + const MEM_PERIOD_SECONDS: f64 = 5.0; #[derive(clap::Parser)] @@ -35,12 +42,14 @@ struct GuestAgentConfig { #[arg(long, value_enum, default_value_t = Default::default())] network: NetworkPluginKind, -} -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let config = GuestAgentConfig::parse(); + /// Run as a Windows service. + #[cfg(windows)] + #[arg(long)] + service: bool, +} +pub(crate) async fn run_async(config: &GuestAgentConfig) -> anyhow::Result> { setup_logger(config.stderr, config.log_level)?; let mut set: JoinSet<()> = JoinSet::new(); @@ -58,18 +67,40 @@ async fn main() -> anyhow::Result<()> { set.spawn(provider_memory::MemoryPlugin.run(tx.clone())); set.spawn(NetworkPlugin::new(config.network)?.run(tx.clone())); - println!("{:?}", set.join_all().await); + Ok(set) +} + +#[cfg(unix)] +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let config = GuestAgentConfig::parse(); + let set = run_async(&config).await?; + set.join_all().await; Ok(()) } +#[cfg(windows)] +fn main() -> anyhow::Result<()> { + let config = GuestAgentConfig::parse(); + if config.service { + windows_service_main::dispatch_main() + } else { + let builder = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_all() + .build()?; + builder.block_on(async { + let set = run_async(&config).await?; + set.join_all().await; + anyhow::Result::<()>::Ok(()) + }) + } +} + fn setup_logger(use_stderr: bool, level: LevelFilter) -> anyhow::Result<()> { if use_stderr { setup_env_logger(level)?; } else { - #[cfg(not(unix))] - panic!("no system logger supported"); - - #[cfg(unix)] setup_system_logger(level)?; } Ok(()) @@ -104,3 +135,10 @@ fn setup_system_logger(level: LevelFilter) -> anyhow::Result<()> { log::set_max_level(level); Ok(()) } + +#[cfg(windows)] +fn setup_system_logger(level: LevelFilter) -> anyhow::Result<()> { + log::set_boxed_logger(Box::new(WindowsDebugLogger {}))?; + log::set_max_level(level); + Ok(()) +} diff --git a/xen-guest-agent/src/windows_debug_logger.rs b/xen-guest-agent/src/windows_debug_logger.rs new file mode 100644 index 0000000..9613230 --- /dev/null +++ b/xen-guest-agent/src/windows_debug_logger.rs @@ -0,0 +1,23 @@ +use log::Log; +use windows::{core::HSTRING, Win32::System::Diagnostics::Debug::OutputDebugStringW}; + +pub(crate) struct WindowsDebugLogger {} + +impl Log for WindowsDebugLogger { + fn enabled(&self, _metadata: &log::Metadata) -> bool { + true + } + + fn log(&self, record: &log::Record) { + let message = format!( + "[xen-guest-agent] {}: {}\r\n", + record.level().as_str(), + record.args() + ); + unsafe { + OutputDebugStringW(&HSTRING::from(message)); + } + } + + fn flush(&self) {} +} diff --git a/xen-guest-agent/src/windows_service_main.rs b/xen-guest-agent/src/windows_service_main.rs new file mode 100644 index 0000000..e557689 --- /dev/null +++ b/xen-guest-agent/src/windows_service_main.rs @@ -0,0 +1,102 @@ +extern crate windows; +extern crate windows_service; + +use std::sync::mpsc; +use std::time::Duration; + +use clap::Parser; +use windows::Win32::Foundation::{ERROR_INVALID_PARAMETER, ERROR_SUCCESS}; + +use windows_service::service::{ + ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus, ServiceType, +}; +use windows_service::service_control_handler::{self, ServiceControlHandlerResult}; +use windows_service::service_dispatcher; + +use crate::{run_async, GuestAgentConfig}; + +const SERVICE_NAME: &str = "xenguestagent-rs"; + +fn service_main() -> anyhow::Result<()> { + let (stop_tx, stop_rx) = mpsc::channel::<()>(); + + let event_handler = move |control_event| -> ServiceControlHandlerResult { + match control_event { + ServiceControl::Stop => { + log::info!("Sending service stop message"); + stop_tx.send(()).unwrap(); + ServiceControlHandlerResult::NoError + } + ServiceControl::Interrogate => ServiceControlHandlerResult::NoError, + _ => ServiceControlHandlerResult::NotImplemented, + } + }; + + let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?; + + { + let status = ServiceStatus { + service_type: ServiceType::OWN_PROCESS, + current_state: ServiceState::Running, + controls_accepted: ServiceControlAccept::STOP, + exit_code: ServiceExitCode::Win32(ERROR_SUCCESS.0), + checkpoint: 0, + wait_hint: Duration::default(), + process_id: None, + }; + status_handle.set_service_status(status)?; + } + log::info!("Service starting"); + + let builder = tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_all() + .build()?; + let service_result = builder.block_on(async { + let config = GuestAgentConfig::parse(); + let mut set = run_async(&config).await?; + log::info!("Service started"); + stop_rx.recv()?; + log::info!("Service stopping"); + set.shutdown().await; + anyhow::Result::<()>::Ok(()) + }); + match service_result { + Ok(_) => log::info!("Service returned successfully"), + Err(ref e) => log::error!("Service returned error {e}"), + } + + { + let status = ServiceStatus { + service_type: ServiceType::OWN_PROCESS, + current_state: ServiceState::Stopped, + controls_accepted: ServiceControlAccept::empty(), + exit_code: if service_result.is_ok() { + ServiceExitCode::Win32(ERROR_SUCCESS.0) + } else { + ServiceExitCode::Win32(ERROR_INVALID_PARAMETER.0) + }, + checkpoint: 0, + wait_hint: Duration::default(), + process_id: None, + }; + status_handle.set_service_status(status)?; + } + + Ok(()) +} + +extern "system" fn ffi_service_main( + _num_service_arguments: u32, + _service_arguments: *mut *mut u16, +) { + match service_main() { + Ok(_) => (), + Err(ref e) => log::error!("Service start encountered an error {e}"), + } +} + +pub(crate) fn dispatch_main() -> anyhow::Result<()> { + service_dispatcher::start(SERVICE_NAME, ffi_service_main)?; + Ok(()) +} From 66807cb068fa241dd2a35156517d0e381a0cdd52 Mon Sep 17 00:00:00 2001 From: Tu Dinh Date: Wed, 26 Feb 2025 14:53:35 +0100 Subject: [PATCH 16/24] Windows MSVC: Build in Hybrid CRT mode This avoids having to install VC++ runtime along with the service. Signed-off-by: Tu Dinh --- .cargo/config.toml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..4a73da2 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.x86_64-pc-windows-msvc] +rustflags = [ + "-C", "target-feature=+crt-static", + "-C", "link-args=/nodefaultlib:libucrt.lib /defaultlib:ucrt.lib", +] From 7185aadc2b0c05723e1917385b34f8100580cea0 Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Wed, 26 Feb 2025 17:24:41 +0100 Subject: [PATCH 17/24] Tidy windows_service_main code formatting. Signed-off-by: Teddy Astie --- xen-guest-agent/src/windows_service_main.rs | 61 +++++++++------------ 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/xen-guest-agent/src/windows_service_main.rs b/xen-guest-agent/src/windows_service_main.rs index e557689..38a6ed9 100644 --- a/xen-guest-agent/src/windows_service_main.rs +++ b/xen-guest-agent/src/windows_service_main.rs @@ -1,6 +1,3 @@ -extern crate windows; -extern crate windows_service; - use std::sync::mpsc; use std::time::Duration; @@ -18,7 +15,7 @@ use crate::{run_async, GuestAgentConfig}; const SERVICE_NAME: &str = "xenguestagent-rs"; fn service_main() -> anyhow::Result<()> { - let (stop_tx, stop_rx) = mpsc::channel::<()>(); + let (stop_tx, stop_rx) = mpsc::channel(); let event_handler = move |control_event| -> ServiceControlHandlerResult { match control_event { @@ -34,18 +31,16 @@ fn service_main() -> anyhow::Result<()> { let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?; - { - let status = ServiceStatus { - service_type: ServiceType::OWN_PROCESS, - current_state: ServiceState::Running, - controls_accepted: ServiceControlAccept::STOP, - exit_code: ServiceExitCode::Win32(ERROR_SUCCESS.0), - checkpoint: 0, - wait_hint: Duration::default(), - process_id: None, - }; - status_handle.set_service_status(status)?; - } + status_handle.set_service_status(ServiceStatus { + service_type: ServiceType::OWN_PROCESS, + current_state: ServiceState::Running, + controls_accepted: ServiceControlAccept::STOP, + exit_code: ServiceExitCode::Win32(ERROR_SUCCESS.0), + checkpoint: 0, + wait_hint: Duration::default(), + process_id: None, + })?; + log::info!("Service starting"); let builder = tokio::runtime::Builder::new_multi_thread() @@ -66,22 +61,19 @@ fn service_main() -> anyhow::Result<()> { Err(ref e) => log::error!("Service returned error {e}"), } - { - let status = ServiceStatus { - service_type: ServiceType::OWN_PROCESS, - current_state: ServiceState::Stopped, - controls_accepted: ServiceControlAccept::empty(), - exit_code: if service_result.is_ok() { - ServiceExitCode::Win32(ERROR_SUCCESS.0) - } else { - ServiceExitCode::Win32(ERROR_INVALID_PARAMETER.0) - }, - checkpoint: 0, - wait_hint: Duration::default(), - process_id: None, - }; - status_handle.set_service_status(status)?; - } + status_handle.set_service_status(ServiceStatus { + service_type: ServiceType::OWN_PROCESS, + current_state: ServiceState::Stopped, + controls_accepted: ServiceControlAccept::empty(), + exit_code: if service_result.is_ok() { + ServiceExitCode::Win32(ERROR_SUCCESS.0) + } else { + ServiceExitCode::Win32(ERROR_INVALID_PARAMETER.0) + }, + checkpoint: 0, + wait_hint: Duration::default(), + process_id: None, + })?; Ok(()) } @@ -90,9 +82,8 @@ extern "system" fn ffi_service_main( _num_service_arguments: u32, _service_arguments: *mut *mut u16, ) { - match service_main() { - Ok(_) => (), - Err(ref e) => log::error!("Service start encountered an error {e}"), + if let Err(e) = service_main() { + log::error!("Service start encountered an error {e}"); } } From 4e8b1d1f3816951341ed78c95cbc7801dc160bc4 Mon Sep 17 00:00:00 2001 From: Tu Dinh Date: Thu, 27 Feb 2025 22:09:49 +0100 Subject: [PATCH 18/24] Include debug info in release builds Signed-off-by: Tu Dinh --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 75caa98..4fbd44f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ members = [ [profile.release] lto = true +debug = "full" From b60ee2266898d560cd107bee31f2ab0bd0443d4e Mon Sep 17 00:00:00 2001 From: Tu Dinh Date: Thu, 27 Feb 2025 22:35:13 +0100 Subject: [PATCH 19/24] Disable env_logger default features We don't need it, and it significantly bloats up binary size and compilation time. Signed-off-by: Tu Dinh --- Cargo.lock | 48 -------------------------------------- xen-guest-agent/Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c67d50..dd1ba86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,15 +17,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "anstream" version = "0.6.18" @@ -222,7 +213,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", - "regex", ] [[package]] @@ -231,10 +221,7 @@ version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ - "anstream", - "anstyle", "env_filter", - "humantime", "log", ] @@ -380,12 +367,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -681,35 +662,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - [[package]] name = "rtnetlink" version = "0.14.1" diff --git a/xen-guest-agent/Cargo.toml b/xen-guest-agent/Cargo.toml index 925c4ef..89883fa 100644 --- a/xen-guest-agent/Cargo.toml +++ b/xen-guest-agent/Cargo.toml @@ -10,7 +10,7 @@ license = "AGPL-3.0-only" futures = "0.3.26" tokio = { version = "1.25.0", features = ["rt", "macros", "rt-multi-thread"] } log = "0.4.0" -env_logger = ">=0.10.0" +env_logger = { version = ">=0.10.0", default-features = false } clap = { version = "4.4.8", features = ["derive"] } enum_dispatch = "0.3" anyhow = "1.0" From 956ceecbbf9300af68626c8910ff007f7880beec Mon Sep 17 00:00:00 2001 From: Tu Dinh Date: Fri, 28 Feb 2025 10:35:49 +0100 Subject: [PATCH 20/24] Upgrade to Rust 2021 and resolver v2 Signed-off-by: Tu Dinh --- Cargo.toml | 1 + xen-guest-agent/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4fbd44f..71d5f0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ package.version = "0.6.0-dev" package.repository = "https://gitlab.com/xen-project/xen-guest-agent" package.categories = ["virtualization"] package.edition = "2021" +resolver = "2" members = [ "xen-guest-agent", diff --git a/xen-guest-agent/Cargo.toml b/xen-guest-agent/Cargo.toml index 89883fa..6d83092 100644 --- a/xen-guest-agent/Cargo.toml +++ b/xen-guest-agent/Cargo.toml @@ -2,7 +2,7 @@ name = "xen-guest-agent" version = "0.5.0-dev" authors = ["Yann Dirson "] -edition = "2018" +edition = "2021" rust-version = "1.76" license = "AGPL-3.0-only" From 22f50dc4d5d33ba6f9372b5f34cb7e4dd68bbd36 Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Wed, 26 Feb 2025 17:26:10 +0100 Subject: [PATCH 21/24] Clippy/formatting related changes * Use async syntax for GuestAgentPublisher::run() Signed-off-by: Teddy Astie --- providers/provider-memory/src/lib.rs | 39 ++++------ providers/provider-netlink/src/lib.rs | 98 +++++++++++------------- providers/provider-os/src/lib.rs | 23 +++--- providers/provider-simple/src/lib.rs | 16 ++-- publishers/publisher-console/src/lib.rs | 11 +-- publishers/publisher-xenstore/src/lib.rs | 54 ++++++------- 6 files changed, 104 insertions(+), 137 deletions(-) diff --git a/providers/provider-memory/src/lib.rs b/providers/provider-memory/src/lib.rs index 5984558..278e7ac 100644 --- a/providers/provider-memory/src/lib.rs +++ b/providers/provider-memory/src/lib.rs @@ -30,28 +30,23 @@ pub type PlatformMemorySource = windows::WindowsMemorySource; pub struct MemoryPlugin; impl GuestAgentPlugin for MemoryPlugin { - fn run( - self, - mut channel: mpsc::Sender, - ) -> impl std::future::Future + Send { - async move { - let mut timer = tokio::time::interval(Duration::from_secs_f32(5.0)); - let mut memory_source = - PlatformMemorySource::new().expect("Unable to get memory information"); - - loop { - timer.tick().await; - - if channel - .send(guest_metrics::GuestMetric::Memory(MemoryInfo { - mem_free: memory_source.get_available_kb().unwrap(), - mem_total: memory_source.get_total_kb().unwrap(), - })) - .await - .is_err() - { - break; - } + async fn run(self, mut channel: mpsc::Sender) { + let mut timer = tokio::time::interval(Duration::from_secs_f32(5.0)); + let mut memory_source = + PlatformMemorySource::new().expect("Unable to get memory information"); + + loop { + timer.tick().await; + + if channel + .send(guest_metrics::GuestMetric::Memory(MemoryInfo { + mem_free: memory_source.get_available_kb().unwrap(), + mem_total: memory_source.get_total_kb().unwrap(), + })) + .await + .is_err() + { + break; } } } diff --git a/providers/provider-netlink/src/lib.rs b/providers/provider-netlink/src/lib.rs index 1b2492a..8bab703 100644 --- a/providers/provider-netlink/src/lib.rs +++ b/providers/provider-netlink/src/lib.rs @@ -49,57 +49,51 @@ impl NetlinkConnection { pub struct NetlinkPlugin; impl GuestAgentPlugin for NetlinkPlugin { - fn run( - self, - mut channel: mpsc::Sender, - ) -> impl std::future::Future + Send { - async move { - let connection = NetlinkConnection::new().unwrap(); - let vif_identify = vif_detect::PlatformVifDetector::default(); - let mut interfaces = HashMap::new(); - - // Create the netlink message that requests the links to be dumped - let mut nl_hdr = NetlinkHeader::default(); - nl_hdr.flags = NLM_F_DUMP | NLM_F_REQUEST; - // Send the request - let link_stream = connection - .handle - .request( - NetlinkMessage::new( - nl_hdr, - RouteNetlinkMessage::GetLink(LinkMessage::default()).into(), - ), - SocketAddr::new(0, 0), - ) - .unwrap(); - - // Create the netlink message that requests the addresses to be dumped - let mut nl_hdr = NetlinkHeader::default(); - nl_hdr.flags = NLM_F_DUMP | NLM_F_REQUEST; - // Send the request - let address_stream = connection - .handle - .request( - NetlinkMessage::new( - nl_hdr, - RouteNetlinkMessage::GetAddress(AddressMessage::default()).into(), - ), - SocketAddr::new(0, 0), - ) - .unwrap(); - - let mut stream = link_stream - .chain(address_stream) - .chain(connection.messages.map(|(msg, _)| msg)); - - while let Some(msg) = stream.next().await { - if let NetlinkPayload::InnerMessage(inner_msg) = msg.payload { - if let Err(e) = - process_message(inner_msg, &mut channel, &vif_identify, &mut interfaces) - .await - { - log::error!("Unable to process netlink message: {e}"); - } + async fn run(self, mut channel: mpsc::Sender) { + let connection = NetlinkConnection::new().unwrap(); + let vif_identify = vif_detect::PlatformVifDetector::default(); + let mut interfaces = HashMap::new(); + + // Create the netlink message that requests the links to be dumped + let mut nl_hdr = NetlinkHeader::default(); + nl_hdr.flags = NLM_F_DUMP | NLM_F_REQUEST; + // Send the request + let link_stream = connection + .handle + .request( + NetlinkMessage::new( + nl_hdr, + RouteNetlinkMessage::GetLink(LinkMessage::default()).into(), + ), + SocketAddr::new(0, 0), + ) + .unwrap(); + + // Create the netlink message that requests the addresses to be dumped + let mut nl_hdr = NetlinkHeader::default(); + nl_hdr.flags = NLM_F_DUMP | NLM_F_REQUEST; + // Send the request + let address_stream = connection + .handle + .request( + NetlinkMessage::new( + nl_hdr, + RouteNetlinkMessage::GetAddress(AddressMessage::default()).into(), + ), + SocketAddr::new(0, 0), + ) + .unwrap(); + + let mut stream = link_stream + .chain(address_stream) + .chain(connection.messages.map(|(msg, _)| msg)); + + while let Some(msg) = stream.next().await { + if let NetlinkPayload::InnerMessage(inner_msg) = msg.payload { + if let Err(e) = + process_message(inner_msg, &mut channel, &vif_identify, &mut interfaces).await + { + log::error!("Unable to process netlink message: {e}"); } } } @@ -141,7 +135,7 @@ async fn process_message( }); let Some(toolstack_iface) = - vif_identify.get_toolstack_interface(&ifname, mac.as_deref()) + vif_identify.get_toolstack_interface(ifname, mac.as_deref()) else { log::debug!("Unknown interface {ifname} (mac: {mac:?})"); return Ok(()); diff --git a/providers/provider-os/src/lib.rs b/providers/provider-os/src/lib.rs index 5c9bc0b..14525a3 100644 --- a/providers/provider-os/src/lib.rs +++ b/providers/provider-os/src/lib.rs @@ -20,20 +20,15 @@ pub fn collect_kernel() -> io::Result> { pub struct OsInfoPlugin; impl GuestAgentPlugin for OsInfoPlugin { - fn run( - self, - mut channel: mpsc::Sender, - ) -> impl std::future::Future + Send { - async move { - let kernel_info = collect_kernel().expect("Unable to fetch kernel information"); + async fn run(self, mut channel: mpsc::Sender) { + let kernel_info = collect_kernel().expect("Unable to fetch kernel information"); - channel - .send(GuestMetric::OperatingSystem(OsInfo { - os_info: os_info::get(), - kernel_info, - })) - .await - .ok(); - } + channel + .send(GuestMetric::OperatingSystem(OsInfo { + os_info: os_info::get(), + kernel_info, + })) + .await + .ok(); } } diff --git a/providers/provider-simple/src/lib.rs b/providers/provider-simple/src/lib.rs index b9cbad6..2008c78 100644 --- a/providers/provider-simple/src/lib.rs +++ b/providers/provider-simple/src/lib.rs @@ -13,19 +13,17 @@ pub struct SimpleNetworkPlugin { } impl GuestAgentPlugin for SimpleNetworkPlugin { - fn run( + async fn run( mut self, mut channel: futures::channel::mpsc::Sender, - ) -> impl std::future::Future + Send { - async move { - let mut timer = tokio::time::interval(Duration::from_secs_f32(5.0)); - let vif_detector = PlatformVifDetector::default(); + ) { + let mut timer = tokio::time::interval(Duration::from_secs_f32(5.0)); + let vif_detector = PlatformVifDetector::default(); - loop { - self.track_interfaces(&vif_detector, &mut channel).await; + loop { + self.track_interfaces(&vif_detector, &mut channel).await; - timer.tick().await; - } + timer.tick().await; } } } diff --git a/publishers/publisher-console/src/lib.rs b/publishers/publisher-console/src/lib.rs index 8e76556..d11debd 100644 --- a/publishers/publisher-console/src/lib.rs +++ b/publishers/publisher-console/src/lib.rs @@ -70,14 +70,9 @@ impl ConsolePublisher { } impl GuestAgentPublisher for ConsolePublisher { - fn run( - mut self, - mut channel: mpsc::Receiver, - ) -> impl std::future::Future + Send { - async move { - while let Some(msg) = channel.next().await { - self.process_message(msg) - } + async fn run(mut self, mut channel: mpsc::Receiver) { + while let Some(msg) = channel.next().await { + self.process_message(msg) } } } diff --git a/publishers/publisher-xenstore/src/lib.rs b/publishers/publisher-xenstore/src/lib.rs index aca3ffc..3f51aa5 100644 --- a/publishers/publisher-xenstore/src/lib.rs +++ b/publishers/publisher-xenstore/src/lib.rs @@ -20,43 +20,33 @@ pub fn xs_unpublish(xs: &impl Xs, key: &str) -> io::Result<()> { pub struct XenstoreRfcPublisher; impl GuestAgentPublisher for XenstoreRfcPublisher { - fn run( - self, - channel: mpsc::Receiver, - ) -> impl ::std::future::Future + Send { - async move { - #[cfg(not(target_os = "windows"))] - let xs = xenstore_rs::unix::XsUnix::new().expect("Unable to initialize xenstore"); - - #[cfg(target_os = "windows")] - let xs = xenstore_win::XsWindows::new().expect("Unable to initialize xenstore"); - - rfc::XenstoreRfc::new(xs) - .run(channel) - .await - .expect("Xenstore failure") - } + async fn run(self, channel: mpsc::Receiver) { + #[cfg(not(target_os = "windows"))] + let xs = xenstore_rs::unix::XsUnix::new().expect("Unable to initialize xenstore"); + + #[cfg(target_os = "windows")] + let xs = xenstore_win::XsWindows::new().expect("Unable to initialize xenstore"); + + rfc::XenstoreRfc::new(xs) + .run(channel) + .await + .expect("Xenstore failure") } } pub struct XenstoreStdPublisher; impl GuestAgentPublisher for XenstoreStdPublisher { - fn run( - self, - channel: mpsc::Receiver, - ) -> impl ::std::future::Future + Send { - async move { - #[cfg(not(target_os = "windows"))] - let xs = xenstore_rs::unix::XsUnix::new().expect("Unable to initialize xenstore"); - - #[cfg(target_os = "windows")] - let xs = xenstore_win::XsWindows::new().expect("Unable to initialize xenstore"); - - std::XenstoreStd::new(xs) - .run(channel) - .await - .expect("Xenstore failure") - } + async fn run(self, channel: mpsc::Receiver) { + #[cfg(not(target_os = "windows"))] + let xs = xenstore_rs::unix::XsUnix::new().expect("Unable to initialize xenstore"); + + #[cfg(target_os = "windows")] + let xs = xenstore_win::XsWindows::new().expect("Unable to initialize xenstore"); + + std::XenstoreStd::new(xs) + .run(channel) + .await + .expect("Xenstore failure") } } From 6f28b67c12027656c00a314bc71e975846782ff3 Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Wed, 26 Feb 2025 17:27:21 +0100 Subject: [PATCH 22/24] Only build windows dependencies for Windows target Signed-off-by: Teddy Astie --- xen-guest-agent/Cargo.toml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/xen-guest-agent/Cargo.toml b/xen-guest-agent/Cargo.toml index 6d83092..f43e053 100644 --- a/xen-guest-agent/Cargo.toml +++ b/xen-guest-agent/Cargo.toml @@ -31,15 +31,12 @@ sysctl = "0.5.0" [target.'cfg(target_family = "unix")'.dependencies] syslog = "6.0" -[dependencies.windows] -version = "0.58" -features = [ +[target."cfg(windows)".dependencies] +windows = { version = "0.58", features = [ "Win32_Foundation", "Win32_System_Diagnostics_Debug", -] - -[target."cfg(windows)".dependencies] +] } windows-service = "0.8" [features] -netlink = ["dep:provider-netlink"] \ No newline at end of file +netlink = ["dep:provider-netlink"] From 776edcf844d935d727b22e901aedad1236d8d215 Mon Sep 17 00:00:00 2001 From: Teddy Astie Date: Wed, 26 Feb 2025 17:28:28 +0100 Subject: [PATCH 23/24] Only start network plugin if report_nics is enabled Signed-off-by: Teddy Astie --- xen-guest-agent/src/main.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/xen-guest-agent/src/main.rs b/xen-guest-agent/src/main.rs index 2f99f62..8f74474 100644 --- a/xen-guest-agent/src/main.rs +++ b/xen-guest-agent/src/main.rs @@ -59,13 +59,15 @@ pub(crate) async fn run_async(config: &GuestAgentConfig) -> anyhow::Result Date: Fri, 28 Feb 2025 15:53:58 +0100 Subject: [PATCH 24/24] Port to smol async runtime * Use smol instead of tokio * Use flume instead of futures::mpsc Signed-off-by: Teddy Astie --- Cargo.lock | 459 +++++++++++++++++++- guest-metrics/Cargo.toml | 2 +- guest-metrics/src/plugin.rs | 6 +- providers/provider-memory/Cargo.toml | 3 +- providers/provider-memory/src/lib.rs | 10 +- providers/provider-netlink/Cargo.toml | 2 + providers/provider-netlink/src/lib.rs | 19 +- providers/provider-os/Cargo.toml | 3 +- providers/provider-os/src/lib.rs | 5 +- providers/provider-simple/Cargo.toml | 3 +- providers/provider-simple/src/lib.rs | 34 +- providers/vif-detect/src/freebsd.rs | 6 +- providers/vif-detect/src/lib.rs | 8 +- providers/vif-detect/src/windows.rs | 2 +- publishers/publisher-console/Cargo.toml | 2 +- publishers/publisher-console/src/lib.rs | 5 +- publishers/publisher-xenstore/Cargo.toml | 2 +- publishers/publisher-xenstore/src/lib.rs | 5 +- publishers/publisher-xenstore/src/rfc.rs | 5 +- publishers/publisher-xenstore/src/std.rs | 6 +- xen-guest-agent/Cargo.toml | 3 +- xen-guest-agent/src/main.rs | 62 +-- xen-guest-agent/src/plugins.rs | 10 +- xen-guest-agent/src/publisher.rs | 4 +- xen-guest-agent/src/windows_service_main.rs | 17 +- 25 files changed, 552 insertions(+), 131 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd1ba86..f69bfd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,132 @@ version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", + "tracing", +] + +[[package]] +name = "async-signal" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.59.0", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.4.0" @@ -100,6 +226,25 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + [[package]] name = "byteorder" version = "1.5.0" @@ -173,6 +318,21 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "deranged" version = "0.3.11" @@ -225,6 +385,16 @@ dependencies = [ "log", ] +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "error-chain" version = "0.12.4" @@ -234,6 +404,45 @@ dependencies = [ "version_check", ] +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin", +] + [[package]] name = "futures" version = "0.3.31" @@ -282,6 +491,19 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.31" @@ -323,6 +545,19 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + [[package]] name = "getrandom" version = "0.3.1" @@ -345,7 +580,7 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" name = "guest-metrics" version = "0.6.0-dev" dependencies = [ - "futures", + "flume", "os_info", "uuid", ] @@ -356,6 +591,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hostname" version = "0.3.1" @@ -379,12 +620,38 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.25" @@ -423,6 +690,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.15", +] + [[package]] name = "netlink-packet-core" version = "0.7.0" @@ -551,6 +827,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "paste" version = "1.0.15" @@ -569,6 +851,32 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "polling" +version = "3.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -588,9 +896,10 @@ dependencies = [ name = "provider-memory" version = "0.1.0" dependencies = [ + "flume", "futures", "guest-metrics", - "tokio", + "smol", "windows", ] @@ -599,6 +908,7 @@ name = "provider-netlink" version = "0.1.0" dependencies = [ "anyhow", + "flume", "futures", "guest-metrics", "log", @@ -606,6 +916,7 @@ dependencies = [ "netlink-packet-route", "netlink-proto", "rtnetlink", + "smol", "tokio", "uuid", "vif-detect", @@ -615,8 +926,9 @@ dependencies = [ name = "provider-os" version = "0.6.0-dev" dependencies = [ - "futures", + "flume", "guest-metrics", + "smol", "uname", ] @@ -624,10 +936,11 @@ dependencies = [ name = "provider-simple" version = "0.1.0" dependencies = [ + "flume", "futures", "guest-metrics", "network-interface", - "tokio", + "smol", "uuid", "vif-detect", ] @@ -636,7 +949,7 @@ dependencies = [ name = "publisher-console" version = "0.6.0-dev" dependencies = [ - "futures", + "flume", "guest-metrics", "uuid", ] @@ -645,7 +958,7 @@ dependencies = [ name = "publisher-xenstore" version = "0.1.0" dependencies = [ - "futures", + "flume", "guest-metrics", "log", "uuid", @@ -686,6 +999,19 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + [[package]] name = "same-file" version = "1.0.6" @@ -695,6 +1021,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.217" @@ -721,6 +1053,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "slab" version = "0.4.9" @@ -730,6 +1071,23 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smol" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" +dependencies = [ + "async-channel", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite", +] + [[package]] name = "socket2" version = "0.5.8" @@ -740,6 +1098,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "strsim" version = "0.11.1" @@ -868,21 +1235,25 @@ dependencies = [ "mio", "pin-project-lite", "socket2", - "tokio-macros", "windows-sys 0.52.0", ] [[package]] -name = "tokio-macros" -version = "2.5.0" +name = "tracing" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "proc-macro2", - "quote", - "syn", + "pin-project-lite", + "tracing-core", ] +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" + [[package]] name = "uname" version = "0.1.1" @@ -910,7 +1281,7 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6" dependencies = [ - "getrandom", + "getrandom 0.3.1", ] [[package]] @@ -954,6 +1325,63 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + [[package]] name = "widestring" version = "1.1.0" @@ -1165,6 +1593,7 @@ dependencies = [ "clap", "enum_dispatch", "env_logger", + "flume", "futures", "guest-metrics", "log", @@ -1174,9 +1603,9 @@ dependencies = [ "provider-simple", "publisher-console", "publisher-xenstore", + "smol", "sysctl", "syslog", - "tokio", "windows", "windows-service", ] diff --git a/guest-metrics/Cargo.toml b/guest-metrics/Cargo.toml index 6dc6dc5..85c078c 100644 --- a/guest-metrics/Cargo.toml +++ b/guest-metrics/Cargo.toml @@ -6,6 +6,6 @@ repository.workspace = true categories.workspace = true [dependencies] -futures = "0.3" +flume = "0.11.1" uuid = "1.11" os_info = "3" \ No newline at end of file diff --git a/guest-metrics/src/plugin.rs b/guest-metrics/src/plugin.rs index 6c94625..b04d85d 100644 --- a/guest-metrics/src/plugin.rs +++ b/guest-metrics/src/plugin.rs @@ -1,13 +1,11 @@ use std::future::Future; -use futures::channel::mpsc; - use crate::GuestMetric; pub trait GuestAgentPlugin { - fn run(self, channel: mpsc::Sender) -> impl Future + Send; + fn run(self, channel: flume::Sender) -> impl Future + Send; } pub trait GuestAgentPublisher { - fn run(self, channel: mpsc::Receiver) -> impl Future + Send; + fn run(self, channel: flume::Receiver) -> impl Future + Send; } diff --git a/providers/provider-memory/Cargo.toml b/providers/provider-memory/Cargo.toml index 377d9c4..bf9f623 100644 --- a/providers/provider-memory/Cargo.toml +++ b/providers/provider-memory/Cargo.toml @@ -6,7 +6,8 @@ edition = "2021" [dependencies] guest-metrics = { path = "../../guest-metrics" } futures = "0.3" -tokio = { version = "1.25.0", features = ["time"] } +flume = "0.11.1" +smol = "2.0.2" [target.'cfg(target_os = "windows")'.dependencies] windows = { version = "0.58", features = ["Win32_System_SystemInformation"] } diff --git a/providers/provider-memory/src/lib.rs b/providers/provider-memory/src/lib.rs index 278e7ac..7d96395 100644 --- a/providers/provider-memory/src/lib.rs +++ b/providers/provider-memory/src/lib.rs @@ -1,6 +1,6 @@ use std::{io, time::Duration}; -use futures::{channel::mpsc, SinkExt}; +use futures::StreamExt; use guest_metrics::{plugin::GuestAgentPlugin, MemoryInfo}; #[cfg(target_os = "freebsd")] @@ -30,16 +30,16 @@ pub type PlatformMemorySource = windows::WindowsMemorySource; pub struct MemoryPlugin; impl GuestAgentPlugin for MemoryPlugin { - async fn run(self, mut channel: mpsc::Sender) { - let mut timer = tokio::time::interval(Duration::from_secs_f32(5.0)); + async fn run(self, channel: flume::Sender) { + let mut timer = smol::Timer::interval(Duration::from_secs_f32(5.0)); let mut memory_source = PlatformMemorySource::new().expect("Unable to get memory information"); loop { - timer.tick().await; + timer.next().await; if channel - .send(guest_metrics::GuestMetric::Memory(MemoryInfo { + .send_async(guest_metrics::GuestMetric::Memory(MemoryInfo { mem_free: memory_source.get_available_kb().unwrap(), mem_total: memory_source.get_total_kb().unwrap(), })) diff --git a/providers/provider-netlink/Cargo.toml b/providers/provider-netlink/Cargo.toml index 2e67ee8..4758a82 100644 --- a/providers/provider-netlink/Cargo.toml +++ b/providers/provider-netlink/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" futures = "0.3" guest-metrics = { path = "../../guest-metrics" } tokio = { version = "1", features = ["rt"] } +smol = "2.0.2" +flume = "0.11.1" uuid = "1.11" anyhow = "*" log = "*" diff --git a/providers/provider-netlink/src/lib.rs b/providers/provider-netlink/src/lib.rs index 8bab703..013ba43 100644 --- a/providers/provider-netlink/src/lib.rs +++ b/providers/provider-netlink/src/lib.rs @@ -3,10 +3,7 @@ use std::{collections::HashMap, io}; use guest_metrics::{plugin::GuestAgentPlugin, GuestMetric, NetEvent, NetEventOp, NetInterface}; use vif_detect::VifDetector; -use futures::{ - channel::mpsc::{self, UnboundedReceiver}, - SinkExt, StreamExt, -}; +use futures::{channel::mpsc::UnboundedReceiver, StreamExt}; use uuid::Uuid; use netlink_packet_core::{ @@ -49,7 +46,7 @@ impl NetlinkConnection { pub struct NetlinkPlugin; impl GuestAgentPlugin for NetlinkPlugin { - async fn run(self, mut channel: mpsc::Sender) { + async fn run(self, channel: flume::Sender) { let connection = NetlinkConnection::new().unwrap(); let vif_identify = vif_detect::PlatformVifDetector::default(); let mut interfaces = HashMap::new(); @@ -91,7 +88,7 @@ impl GuestAgentPlugin for NetlinkPlugin { while let Some(msg) = stream.next().await { if let NetlinkPayload::InnerMessage(inner_msg) = msg.payload { if let Err(e) = - process_message(inner_msg, &mut channel, &vif_identify, &mut interfaces).await + process_message(inner_msg, &channel, &vif_identify, &mut interfaces).await { log::error!("Unable to process netlink message: {e}"); } @@ -102,7 +99,7 @@ impl GuestAgentPlugin for NetlinkPlugin { async fn process_message( inner_msg: RouteNetlinkMessage, - channel: &mut mpsc::Sender, + channel: &flume::Sender, vif_identify: &impl VifDetector, interfaces: &mut HashMap, ) -> anyhow::Result<()> { @@ -145,7 +142,7 @@ async fn process_message( interfaces.insert(link_message.header.index, uuid); channel - .send(GuestMetric::AddIface(NetInterface { + .send_async(GuestMetric::AddIface(NetInterface { uuid, index: link_message.header.index, name: ifname.clone(), @@ -158,7 +155,7 @@ async fn process_message( return Ok(()); }; - channel.send(GuestMetric::RmIface(uuid)).await.ok(); + channel.send_async(GuestMetric::RmIface(uuid)).await.ok(); } RouteNetlinkMessage::NewAddress(address_message) | RouteNetlinkMessage::GetAddress(address_message) => { @@ -184,7 +181,7 @@ async fn process_message( }; channel - .send(GuestMetric::Network(NetEvent { + .send_async(GuestMetric::Network(NetEvent { iface_id, op: NetEventOp::AddIp(addr), })) @@ -213,7 +210,7 @@ async fn process_message( }; channel - .send(GuestMetric::Network(NetEvent { + .send_async(GuestMetric::Network(NetEvent { iface_id, op: NetEventOp::RmIp(addr), })) diff --git a/providers/provider-os/Cargo.toml b/providers/provider-os/Cargo.toml index 14e82e4..f999cab 100644 --- a/providers/provider-os/Cargo.toml +++ b/providers/provider-os/Cargo.toml @@ -7,7 +7,8 @@ categories.workspace = true [dependencies] guest-metrics = { path = "../../guest-metrics" } -futures = "0.3" +smol = "2.0.2" +flume = "0.11.1" [target.'cfg(unix)'.dependencies] uname = "0.1.1" \ No newline at end of file diff --git a/providers/provider-os/src/lib.rs b/providers/provider-os/src/lib.rs index 14525a3..c79aab7 100644 --- a/providers/provider-os/src/lib.rs +++ b/providers/provider-os/src/lib.rs @@ -1,4 +1,3 @@ -use futures::{channel::mpsc, SinkExt}; use guest_metrics::{os_info, plugin::GuestAgentPlugin, GuestMetric, KernelInfo, OsInfo}; use std::io; @@ -20,11 +19,11 @@ pub fn collect_kernel() -> io::Result> { pub struct OsInfoPlugin; impl GuestAgentPlugin for OsInfoPlugin { - async fn run(self, mut channel: mpsc::Sender) { + async fn run(self, channel: flume::Sender) { let kernel_info = collect_kernel().expect("Unable to fetch kernel information"); channel - .send(GuestMetric::OperatingSystem(OsInfo { + .send_async(GuestMetric::OperatingSystem(OsInfo { os_info: os_info::get(), kernel_info, })) diff --git a/providers/provider-simple/Cargo.toml b/providers/provider-simple/Cargo.toml index a675b05..8675e49 100644 --- a/providers/provider-simple/Cargo.toml +++ b/providers/provider-simple/Cargo.toml @@ -7,7 +7,8 @@ edition = "2021" futures = "0.3" guest-metrics = { path = "../../guest-metrics" } uuid = { version = "1.11", features = ["v4"] } -tokio = { version = "1", features = ["time"] } +smol = "2.0.2" +flume = "0.11.1" vif-detect = { path = "../vif-detect" } network-interface = "2.0" diff --git a/providers/provider-simple/src/lib.rs b/providers/provider-simple/src/lib.rs index 2008c78..52dd1c9 100644 --- a/providers/provider-simple/src/lib.rs +++ b/providers/provider-simple/src/lib.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, time::Duration}; -use futures::SinkExt; +use futures::StreamExt; use guest_metrics::{plugin::GuestAgentPlugin, GuestMetric, NetEvent, NetEventOp, NetInterface}; use network_interface::{NetworkInterface, NetworkInterfaceConfig}; use uuid::Uuid; @@ -13,17 +13,14 @@ pub struct SimpleNetworkPlugin { } impl GuestAgentPlugin for SimpleNetworkPlugin { - async fn run( - mut self, - mut channel: futures::channel::mpsc::Sender, - ) { - let mut timer = tokio::time::interval(Duration::from_secs_f32(5.0)); + async fn run(mut self, channel: flume::Sender) { + let mut timer = smol::Timer::interval(Duration::from_secs_f32(5.0)); let vif_detector = PlatformVifDetector::default(); loop { - self.track_interfaces(&vif_detector, &mut channel).await; + self.track_interfaces(&vif_detector, &channel).await; - timer.tick().await; + timer.next().await; } } } @@ -32,7 +29,7 @@ impl SimpleNetworkPlugin { async fn track_interfaces( &mut self, vif_detector: &impl VifDetector, - channel: &mut futures::channel::mpsc::Sender, + channel: &flume::Sender, ) { let interfaces = network_interface::NetworkInterface::show().unwrap(); @@ -67,7 +64,7 @@ impl SimpleNetworkPlugin { self.uuid_map.insert(interface.name.clone(), uuid); channel - .send(GuestMetric::AddIface(NetInterface { + .send_async(GuestMetric::AddIface(NetInterface { uuid, index: interface.index, name: interface.name.clone(), @@ -80,7 +77,7 @@ impl SimpleNetworkPlugin { for addr in &interface.addr { channel - .send(GuestMetric::Network(NetEvent { + .send_async(GuestMetric::Network(NetEvent { iface_id: uuid, op: NetEventOp::AddIp(addr.ip()), })) @@ -90,7 +87,7 @@ impl SimpleNetworkPlugin { if let Some(mac) = interface.mac_addr.clone() { channel - .send(GuestMetric::Network(NetEvent { + .send_async(GuestMetric::Network(NetEvent { iface_id: uuid, op: NetEventOp::AddMac(mac), })) @@ -102,7 +99,10 @@ impl SimpleNetworkPlugin { for interface in removed_interfaces { let uuid = self.uuid_map[&interface]; - channel.send(GuestMetric::RmIface(uuid)).await.unwrap(); + channel + .send_async(GuestMetric::RmIface(uuid)) + .await + .unwrap(); self.interfaces.remove(&interface); self.uuid_map.remove(&interface); } @@ -121,7 +121,7 @@ impl SimpleNetworkPlugin { .all(|current_addr| addr != current_addr) }) { channel - .send(GuestMetric::Network(NetEvent { + .send_async(GuestMetric::Network(NetEvent { iface_id: uuid, op: NetEventOp::AddIp(addr.ip()), })) @@ -137,7 +137,7 @@ impl SimpleNetworkPlugin { .all(|current_addr| addr != current_addr) }) { channel - .send(GuestMetric::Network(NetEvent { + .send_async(GuestMetric::Network(NetEvent { iface_id: uuid, op: NetEventOp::RmIp(addr.ip()), })) @@ -150,7 +150,7 @@ impl SimpleNetworkPlugin { if let Some(mac) = current_interface.mac_addr.clone() { // Remove MAC channel - .send(GuestMetric::Network(NetEvent { + .send_async(GuestMetric::Network(NetEvent { iface_id: uuid, op: NetEventOp::RmMac(mac), })) @@ -161,7 +161,7 @@ impl SimpleNetworkPlugin { if let Some(mac) = interface.mac_addr.clone() { // Remove MAC channel - .send(GuestMetric::Network(NetEvent { + .send_async(GuestMetric::Network(NetEvent { iface_id: uuid, op: NetEventOp::AddMac(mac), })) diff --git a/providers/vif-detect/src/freebsd.rs b/providers/vif-detect/src/freebsd.rs index 1a0f67f..5559d17 100644 --- a/providers/vif-detect/src/freebsd.rs +++ b/providers/vif-detect/src/freebsd.rs @@ -5,7 +5,11 @@ pub struct FreebsdVifDetector; impl super::VifDetector for FreebsdVifDetector { // identifies a VIF as named "xn%ID" - fn get_toolstack_interface(&self, iface_name: &str, _mac_addr: Option<&str>) -> Option { + fn get_toolstack_interface( + &self, + iface_name: &str, + _mac_addr: Option<&str>, + ) -> Option { const PREFIX: &str = "xn"; if !iface_name.starts_with(PREFIX) { log::debug!("ignoring interface {iface_name} as not starting with '{PREFIX}'"); diff --git a/providers/vif-detect/src/lib.rs b/providers/vif-detect/src/lib.rs index 17023ac..087a7d9 100644 --- a/providers/vif-detect/src/lib.rs +++ b/providers/vif-detect/src/lib.rs @@ -8,7 +8,11 @@ pub mod linux; pub mod windows; pub trait VifDetector: Default { - fn get_toolstack_interface(&self, iface_name: &str, mac_addr: Option<&str>) -> Option; + fn get_toolstack_interface( + &self, + iface_name: &str, + mac_addr: Option<&str>, + ) -> Option; } #[cfg(target_os = "linux")] @@ -18,4 +22,4 @@ pub type PlatformVifDetector = linux::LinuxVifDetector; pub type PlatformVifDetector = freebsd::FreebsdVifDetector; #[cfg(target_os = "windows")] -pub type PlatformVifDetector = windows::WindowsVifDetector; \ No newline at end of file +pub type PlatformVifDetector = windows::WindowsVifDetector; diff --git a/providers/vif-detect/src/windows.rs b/providers/vif-detect/src/windows.rs index 4bd25c7..1e38e3a 100644 --- a/providers/vif-detect/src/windows.rs +++ b/providers/vif-detect/src/windows.rs @@ -31,7 +31,7 @@ impl super::VifDetector for WindowsVifDetector { for vif_id in xs.directory("device/vif").ok()? { let Some(vif_mac) = xs.read(&format!("device/vif/{vif_id}/mac")).ok() else { log::warn!("vif/{vif_id} has no MAC address"); - continue + continue; }; if mac_addr.trim().eq_ignore_ascii_case(vif_mac.trim()) { diff --git a/publishers/publisher-console/Cargo.toml b/publishers/publisher-console/Cargo.toml index 1f82406..69049f1 100644 --- a/publishers/publisher-console/Cargo.toml +++ b/publishers/publisher-console/Cargo.toml @@ -8,4 +8,4 @@ categories.workspace = true [dependencies] guest-metrics = { path = "../../guest-metrics" } uuid = "1.11" -futures = "0.3" +flume = "0.11.1" \ No newline at end of file diff --git a/publishers/publisher-console/src/lib.rs b/publishers/publisher-console/src/lib.rs index d11debd..76030fc 100644 --- a/publishers/publisher-console/src/lib.rs +++ b/publishers/publisher-console/src/lib.rs @@ -1,6 +1,5 @@ use std::collections::HashMap; -use futures::{channel::mpsc, StreamExt}; use guest_metrics::{ plugin::GuestAgentPublisher, GuestMetric, KernelInfo, NetEventOp, NetInterface, ToolstackNetInterface, @@ -70,8 +69,8 @@ impl ConsolePublisher { } impl GuestAgentPublisher for ConsolePublisher { - async fn run(mut self, mut channel: mpsc::Receiver) { - while let Some(msg) = channel.next().await { + async fn run(mut self, channel: flume::Receiver) { + while let Ok(msg) = channel.recv_async().await { self.process_message(msg) } } diff --git a/publishers/publisher-xenstore/Cargo.toml b/publishers/publisher-xenstore/Cargo.toml index b341f41..34bdfd2 100644 --- a/publishers/publisher-xenstore/Cargo.toml +++ b/publishers/publisher-xenstore/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] guest-metrics = { path = "../../guest-metrics" } uuid = "1.11" -futures = "0.3" +flume = "0.11.1" log = "*" xenstore-rs = "0.8" diff --git a/publishers/publisher-xenstore/src/lib.rs b/publishers/publisher-xenstore/src/lib.rs index 3f51aa5..40e2eef 100644 --- a/publishers/publisher-xenstore/src/lib.rs +++ b/publishers/publisher-xenstore/src/lib.rs @@ -3,7 +3,6 @@ mod std; use ::std::io; -use futures::channel::mpsc; use guest_metrics::plugin::GuestAgentPublisher; use xenstore_rs::Xs; @@ -20,7 +19,7 @@ pub fn xs_unpublish(xs: &impl Xs, key: &str) -> io::Result<()> { pub struct XenstoreRfcPublisher; impl GuestAgentPublisher for XenstoreRfcPublisher { - async fn run(self, channel: mpsc::Receiver) { + async fn run(self, channel: flume::Receiver) { #[cfg(not(target_os = "windows"))] let xs = xenstore_rs::unix::XsUnix::new().expect("Unable to initialize xenstore"); @@ -37,7 +36,7 @@ impl GuestAgentPublisher for XenstoreRfcPublisher { pub struct XenstoreStdPublisher; impl GuestAgentPublisher for XenstoreStdPublisher { - async fn run(self, channel: mpsc::Receiver) { + async fn run(self, channel: flume::Receiver) { #[cfg(not(target_os = "windows"))] let xs = xenstore_rs::unix::XsUnix::new().expect("Unable to initialize xenstore"); diff --git a/publishers/publisher-xenstore/src/rfc.rs b/publishers/publisher-xenstore/src/rfc.rs index 2444e90..c4efc31 100644 --- a/publishers/publisher-xenstore/src/rfc.rs +++ b/publishers/publisher-xenstore/src/rfc.rs @@ -1,4 +1,3 @@ -use futures::{channel::mpsc, StreamExt}; use guest_metrics::{ GuestMetric, MemoryInfo, NetEvent, NetEventOp, NetInterface, OsInfo, ToolstackNetInterface, }; @@ -99,8 +98,8 @@ impl XenstoreRfc { Ok(()) } - pub async fn run(mut self, mut channel: mpsc::Receiver) -> io::Result<()> { - while let Some(metric) = channel.next().await { + pub async fn run(mut self, channel: flume::Receiver) -> io::Result<()> { + while let Ok(metric) = channel.recv_async().await { match metric { GuestMetric::OperatingSystem(os_info) => self.publish_osinfo(&os_info)?, GuestMetric::Memory(memory_info) => self.publish_memory(&memory_info)?, diff --git a/publishers/publisher-xenstore/src/std.rs b/publishers/publisher-xenstore/src/std.rs index 8ef5c67..2a422e2 100644 --- a/publishers/publisher-xenstore/src/std.rs +++ b/publishers/publisher-xenstore/src/std.rs @@ -1,5 +1,3 @@ -use futures::channel::mpsc; -use futures::{self, StreamExt}; use guest_metrics::{ os_info, GuestMetric, MemoryInfo, NetEvent, NetEventOp, NetInterface, OsInfo, ToolstackNetInterface, @@ -193,8 +191,8 @@ impl XenstoreStd { } } - pub async fn run(mut self, mut channel: mpsc::Receiver) -> io::Result<()> { - while let Some(metric) = channel.next().await { + pub async fn run(mut self, channel: flume::Receiver) -> io::Result<()> { + while let Ok(metric) = channel.recv_async().await { match metric { GuestMetric::OperatingSystem(os_info) => self.publish_osinfo(&os_info)?, GuestMetric::Memory(memory_info) => self.publish_memory(&memory_info)?, diff --git a/xen-guest-agent/Cargo.toml b/xen-guest-agent/Cargo.toml index f43e053..afa5598 100644 --- a/xen-guest-agent/Cargo.toml +++ b/xen-guest-agent/Cargo.toml @@ -8,12 +8,13 @@ license = "AGPL-3.0-only" [dependencies] futures = "0.3.26" -tokio = { version = "1.25.0", features = ["rt", "macros", "rt-multi-thread"] } +smol = "2.0.2" log = "0.4.0" env_logger = { version = ">=0.10.0", default-features = false } clap = { version = "4.4.8", features = ["derive"] } enum_dispatch = "0.3" anyhow = "1.0" +flume = "0.11.1" guest-metrics = { path = "../guest-metrics" } diff --git a/xen-guest-agent/src/main.rs b/xen-guest-agent/src/main.rs index 8f74474..b3bd54e 100644 --- a/xen-guest-agent/src/main.rs +++ b/xen-guest-agent/src/main.rs @@ -6,9 +6,10 @@ mod windows_debug_logger; mod windows_service_main; use clap::Parser; -use futures::{channel::mpsc, SinkExt}; +use flume::Receiver; +use futures::future::{join_all, select}; use log::LevelFilter; -use tokio::task::JoinSet; +use smol::Executor; use guest_metrics::{plugin::GuestAgentPlugin, GuestMetric}; use plugins::{NetworkPlugin, NetworkPluginKind}; @@ -49,53 +50,58 @@ struct GuestAgentConfig { service: bool, } -pub(crate) async fn run_async(config: &GuestAgentConfig) -> anyhow::Result> { - setup_logger(config.stderr, config.log_level)?; - - let mut set: JoinSet<()> = JoinSet::new(); - - let (mut tx, rx) = mpsc::channel(4); +pub(crate) async fn run_async( + config: &GuestAgentConfig, + stop_rx: Receiver<()>, +) -> anyhow::Result<()> { + let (tx, rx) = flume::bounded(4); let publisher = AgentPublisher::new(config.publisher)?; + let mut tasks = vec![]; + let executor = Executor::new(); - set.spawn(publisher.run(rx)); + tasks.push(executor.spawn(publisher.run(rx))); if config.report_nics { // Remove old entries from previous agent to avoid having unknown // interfaces. We will repopulate existing ones immediatly. - tx.send(GuestMetric::CleanupIfaces).await?; - set.spawn(NetworkPlugin::new(config.network)?.run(tx.clone())); + tx.send_async(GuestMetric::CleanupIfaces).await?; + tasks.push(executor.spawn(NetworkPlugin::new(config.network)?.run(tx.clone()))); } - set.spawn(provider_os::OsInfoPlugin.run(tx.clone())); - set.spawn(provider_memory::MemoryPlugin.run(tx.clone())); + tasks.push(executor.spawn(provider_os::OsInfoPlugin.run(tx.clone()))); + tasks.push(executor.spawn(provider_memory::MemoryPlugin.run(tx.clone()))); - Ok(set) + executor + .run(async { + log::info!("Waiting for exit command"); + select(join_all(tasks), stop_rx.recv_async()).await; + log::info!("Got exit command"); + anyhow::Ok(()) + }) + .await?; + + Ok(()) } #[cfg(unix)] -#[tokio::main] -async fn main() -> anyhow::Result<()> { +fn main() -> anyhow::Result<()> { let config = GuestAgentConfig::parse(); - let set = run_async(&config).await?; - set.join_all().await; - Ok(()) + setup_logger(config.stderr, config.log_level)?; + + let (_stop_tx, stop_rx) = flume::bounded(0); + smol::block_on(run_async(&config, stop_rx)) } #[cfg(windows)] fn main() -> anyhow::Result<()> { let config = GuestAgentConfig::parse(); + setup_logger(config.stderr, config.log_level)?; + if config.service { windows_service_main::dispatch_main() } else { - let builder = tokio::runtime::Builder::new_multi_thread() - .worker_threads(2) - .enable_all() - .build()?; - builder.block_on(async { - let set = run_async(&config).await?; - set.join_all().await; - anyhow::Result::<()>::Ok(()) - }) + let (_stop_tx, stop_rx) = flume::bounded(0); + smol::block_on(run_async(&config, stop_rx)) } } diff --git a/xen-guest-agent/src/plugins.rs b/xen-guest-agent/src/plugins.rs index 7d15572..e066321 100644 --- a/xen-guest-agent/src/plugins.rs +++ b/xen-guest-agent/src/plugins.rs @@ -1,7 +1,5 @@ use std::io; -use futures::channel::mpsc; - use guest_metrics::{plugin::GuestAgentPlugin, GuestMetric}; use provider_simple::SimpleNetworkPlugin; @@ -17,10 +15,6 @@ pub enum NetworkPluginKind { impl Default for NetworkPluginKind { fn default() -> Self { - #[cfg(feature = "netlink")] - return Self::Netlink; - - #[cfg(not(feature = "netlink"))] Self::Simple } } @@ -36,11 +30,11 @@ impl NetworkPlugin { match kind { NetworkPluginKind::Simple => Ok(Self::Simple(SimpleNetworkPlugin::default())), #[cfg(feature = "netlink")] - NetworkPluginKind::Netlink => Ok(Self::Netlink(NetlinkPlugin::default())), + NetworkPluginKind::Netlink => Ok(Self::Netlink(NetlinkPlugin)), } } - pub async fn run(self, channel: mpsc::Sender) { + pub async fn run(self, channel: flume::Sender) { match self { NetworkPlugin::Simple(plugin) => plugin.run(channel).await, #[cfg(feature = "netlink")] diff --git a/xen-guest-agent/src/publisher.rs b/xen-guest-agent/src/publisher.rs index 666fbe5..f1dd372 100644 --- a/xen-guest-agent/src/publisher.rs +++ b/xen-guest-agent/src/publisher.rs @@ -1,7 +1,5 @@ use std::io; -use futures::channel::mpsc; - use guest_metrics::{plugin::GuestAgentPublisher, GuestMetric}; use publisher_console::ConsolePublisher; use publisher_xenstore::{XenstoreRfcPublisher, XenstoreStdPublisher}; @@ -29,7 +27,7 @@ impl AgentPublisher { } } - pub async fn run(self, channel: mpsc::Receiver) { + pub async fn run(self, channel: flume::Receiver) { match self { AgentPublisher::Console(publisher) => publisher.run(channel).await, AgentPublisher::XenstoreRfc(publisher) => publisher.run(channel).await, diff --git a/xen-guest-agent/src/windows_service_main.rs b/xen-guest-agent/src/windows_service_main.rs index 38a6ed9..1501fb9 100644 --- a/xen-guest-agent/src/windows_service_main.rs +++ b/xen-guest-agent/src/windows_service_main.rs @@ -1,4 +1,3 @@ -use std::sync::mpsc; use std::time::Duration; use clap::Parser; @@ -15,7 +14,7 @@ use crate::{run_async, GuestAgentConfig}; const SERVICE_NAME: &str = "xenguestagent-rs"; fn service_main() -> anyhow::Result<()> { - let (stop_tx, stop_rx) = mpsc::channel(); + let (stop_tx, stop_rx) = flume::bounded(0); let event_handler = move |control_event| -> ServiceControlHandlerResult { match control_event { @@ -43,18 +42,10 @@ fn service_main() -> anyhow::Result<()> { log::info!("Service starting"); - let builder = tokio::runtime::Builder::new_multi_thread() - .worker_threads(2) - .enable_all() - .build()?; - let service_result = builder.block_on(async { + let service_result: anyhow::Result<()> = smol::block_on(async { let config = GuestAgentConfig::parse(); - let mut set = run_async(&config).await?; - log::info!("Service started"); - stop_rx.recv()?; - log::info!("Service stopping"); - set.shutdown().await; - anyhow::Result::<()>::Ok(()) + run_async(&config, stop_rx).await?; + Ok(()) }); match service_result { Ok(_) => log::info!("Service returned successfully"),