Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 62 additions & 9 deletions proxy_agent_extension/src/service_main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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]
Expand Down
76 changes: 76 additions & 0 deletions proxy_agent_shared/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<windows_service::ServiceState>, 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 {
Expand Down Expand Up @@ -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();
}
}
}
6 changes: 3 additions & 3 deletions proxy_agent_shared/src/service/windows_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -167,7 +167,7 @@ pub fn install_or_update_service(
}
}

fn query_service_status(service_name: &str) -> Result<ServiceStatus> {
pub fn query_service_status(service_name: &str) -> Result<ServiceStatus> {
let service_manager =
ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)
.map_err(|e| Error::WindowsService(e, std::io::Error::last_os_error()))?;
Expand Down
Loading