From f6af2cb6bdb4c76cc060a44542adabdc04ad3d73 Mon Sep 17 00:00:00 2001 From: Srikrishna Veturi Date: Mon, 30 Mar 2026 17:08:21 -0500 Subject: [PATCH] Report eBPF service status instead of installation --- proxy_agent_extension/src/service_main.rs | 71 ++++++++++++++--- proxy_agent_shared/src/service.rs | 76 +++++++++++++++++++ .../src/service/windows_service.rs | 6 +- 3 files changed, 141 insertions(+), 12 deletions(-) diff --git a/proxy_agent_extension/src/service_main.rs b/proxy_agent_extension/src/service_main.rs index b0140d7b..89c94059 100644 --- a/proxy_agent_extension/src/service_main.rs +++ b/proxy_agent_extension/src/service_main.rs @@ -342,27 +342,42 @@ fn write_state_event( #[cfg(windows)] fn report_ebpf_status(status_obj: &mut StatusObj) { - match service::check_service_installed(constants::EBPF_CORE) { - (true, message) => { + use proxy_agent_shared::service::ServiceState; + + match service::check_service_status(constants::EBPF_CORE) { + (true, message, core_state, core_summary) => { logger::write(message.to_string()); - match service::check_service_installed(constants::EBPF_EXT) { - (true, message) => { + match service::check_service_status(constants::EBPF_EXT) { + (true, message, ext_state, ext_summary) => { logger::write(message.to_string()); + let both_running = core_state == Some(ServiceState::Running) + && ext_state == Some(ServiceState::Running); status_obj.substatus = { let mut substatus = status_obj.substatus.clone(); substatus.push(SubStatus { name: constants::EBPF_SUBSTATUS_NAME.to_string(), - status: constants::SUCCESS_STATUS.to_string(), - code: constants::STATUS_CODE_OK, + status: if both_running { + constants::SUCCESS_STATUS.to_string() + } else { + constants::ERROR_STATUS.to_string() + }, + code: if both_running { + constants::STATUS_CODE_OK + } else { + constants::STATUS_CODE_NOT_OK + }, formattedMessage: FormattedMessage { lang: constants::LANG_EN_US.to_string(), - message: "Ebpf Drivers successfully queried.".to_string(), + message: format!( + "EbpfCore: {}, NetEbpfExt: {}", + core_summary, ext_summary + ), }, }); substatus }; } - (false, message) => { + (false, message, _, _) => { logger::write(message.to_string()); status_obj.substatus = { let mut substatus = status_obj.substatus.clone(); @@ -383,7 +398,7 @@ fn report_ebpf_status(status_obj: &mut StatusObj) { } } } - (false, message) => { + (false, message, _, _) => { logger::write(message.to_string()); status_obj.substatus = { let mut substatus = status_obj.substatus.clone(); @@ -1184,6 +1199,44 @@ mod tests { status.substatus[3].name, constants::EBPF_SUBSTATUS_NAME.to_string() ); + + // Verify the eBPF substatus message includes service status info + let ebpf_substatus = &status.substatus[3]; + let ebpf_message = &ebpf_substatus.formattedMessage.message; + if ebpf_message.contains("unsuccessfully queried") { + // At least one service not installed — status should be Error + assert_eq!( + ebpf_substatus.status, + constants::ERROR_STATUS, + "Expected Error status when a service is not installed" + ); + } else { + // Both services found — message should contain status details for each driver + assert!( + ebpf_message.contains("EbpfCore:"), + "Expected message to contain 'EbpfCore:', got: {ebpf_message}" + ); + assert!( + ebpf_message.contains("NetEbpfExt:"), + "Expected message to contain 'NetEbpfExt:', got: {ebpf_message}" + ); + // Status depends on whether both services are running + if ebpf_message.contains("Running") && !ebpf_message.contains("Stopped") { + assert_eq!( + ebpf_substatus.status, + constants::SUCCESS_STATUS, + "Expected Success when both services are running" + ); + assert_eq!(ebpf_substatus.code, constants::STATUS_CODE_OK); + } else { + assert_eq!( + ebpf_substatus.status, + constants::ERROR_STATUS, + "Expected Error when at least one service is not running" + ); + assert_eq!(ebpf_substatus.code, constants::STATUS_CODE_NOT_OK); + } + } } #[tokio::test] diff --git a/proxy_agent_shared/src/service.rs b/proxy_agent_shared/src/service.rs index 53c2cd6f..aa5fd3ad 100644 --- a/proxy_agent_shared/src/service.rs +++ b/proxy_agent_shared/src/service.rs @@ -145,8 +145,44 @@ pub fn check_service_installed(service_name: &str) -> (bool, String) { } } +/// Checks whether a Windows service is installed and queries its runtime state and start type. +/// Returns (is_installed, log_message, current_state, status_summary). +/// `current_state` is `Some(ServiceState)` when the service exists (e.g., `Some(ServiceState::Running)`), +/// or `None` when the service is not installed. +/// `status_summary` is a human-readable string combining the runtime state and start type +/// (e.g., "Running, AutoStart") or "NotInstalled" if the service does not exist. +#[cfg(windows)] +pub fn check_service_status( + service_name: &str, +) -> (bool, String, Option, String) { + let state = match windows_service::query_service_status(service_name) { + Ok(status) => status.current_state, + Err(_) => { + return ( + false, + format!( + "check_service_status: service: {service_name} status query failed, service may not be installed" + ), + None, + "NotInstalled".to_string(), + ); + } + }; + + let start_type_str = match windows_service::query_service_config(service_name) { + Ok(config) => format!("{:?}", config.start_type), + Err(_) => "Unknown".to_string(), + }; + + let summary = format!("{:?}, {start_type_str}", state); + let message = format!("check_service_status: service: {service_name} status: {summary}"); + (true, message, Some(state), summary) +} + #[cfg(windows)] pub use windows_service::set_default_failure_actions; +#[cfg(windows)] +pub use windows_service::ServiceState; #[cfg(test)] mod tests { @@ -191,4 +227,44 @@ mod tests { _ = super::stop_and_delete_service(service_name).await.unwrap(); } } + + #[tokio::test] + async fn test_check_service_status() { + #[cfg(windows)] + { + let service_name = "test_check_service_status"; + // try delete the service if it exists + _ = super::stop_and_delete_service(service_name).await; + + // Verify non-existent service returns not installed + let (is_installed, message, state, summary) = super::check_service_status(service_name); + assert!(!is_installed); + assert!(message.contains("query failed")); + assert_eq!(state, None, "Expected None for non-existent service"); + assert_eq!(summary, "NotInstalled"); + + // Install a test service and verify status is reported + let exe_path = std::env::current_exe().unwrap(); + let result = super::install_service(service_name, service_name, vec![], exe_path); + assert!(result.is_ok()); + + let (is_installed, message, state, summary) = super::check_service_status(service_name); + assert!(is_installed); + assert!(message.contains("status:")); + // Service should be stopped (test exe can't actually run as a service) + assert_eq!( + state, + Some(super::ServiceState::Stopped), + "Expected Some(ServiceState::Stopped), got: {state:?}" + ); + // Summary should also contain start type info + assert!( + summary.contains("AutoStart"), + "Expected summary to contain 'AutoStart', got: {summary}" + ); + + // clean up + _ = super::stop_and_delete_service(service_name).await.unwrap(); + } + } } diff --git a/proxy_agent_shared/src/service/windows_service.rs b/proxy_agent_shared/src/service/windows_service.rs index c2a86de2..d480df9f 100644 --- a/proxy_agent_shared/src/service/windows_service.rs +++ b/proxy_agent_shared/src/service/windows_service.rs @@ -7,10 +7,10 @@ use std::ffi::OsString; use std::path::PathBuf; use std::str; use std::time::Duration; +pub use windows_service::service::ServiceState; use windows_service::service::{ ServiceAccess, ServiceAction, ServiceActionType, ServiceConfig, ServiceErrorControl, - ServiceFailureResetPeriod, ServiceInfo, ServiceStartType, ServiceState, ServiceStatus, - ServiceType, + ServiceFailureResetPeriod, ServiceInfo, ServiceStartType, ServiceStatus, ServiceType, }; use windows_service::service::{ServiceDependency, ServiceFailureActions}; use windows_service::service_manager::{ServiceManager, ServiceManagerAccess}; @@ -167,7 +167,7 @@ pub fn install_or_update_service( } } -fn query_service_status(service_name: &str) -> Result { +pub fn query_service_status(service_name: &str) -> Result { let service_manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT) .map_err(|e| Error::WindowsService(e, std::io::Error::last_os_error()))?;