From ea0b9935d0e0e642858a61e3fa67db4e9d2753e8 Mon Sep 17 00:00:00 2001 From: paullegranddc Date: Thu, 25 Jun 2026 19:49:27 +0200 Subject: [PATCH 1/4] feat(machine id): Add helpers in ddcommon to fetch the machine UUID like the agent # Motivation We are replicating the agent RC client. One of the thing the agent uses is a stable identity for the agent host, decorrelated from the hostname (as it is not available in som platforms). It fetches the UUID using a library (psutils), for which there exists no strict equivalent in rust. This PR adds code that should be feature equal to the agent # Changes * Add new machine_id module in libdd-common * Fetch machine id on different platform --- libdd-common/Cargo.toml | 1 + libdd-common/src/lib.rs | 1 + libdd-common/src/machine_id/linux.rs | 121 ++++++++++++++++++++++ libdd-common/src/machine_id/macos.rs | 39 +++++++ libdd-common/src/machine_id/mod.rs | 135 +++++++++++++++++++++++++ libdd-common/src/machine_id/windows.rs | 91 +++++++++++++++++ 6 files changed, 388 insertions(+) create mode 100644 libdd-common/src/machine_id/linux.rs create mode 100644 libdd-common/src/machine_id/macos.rs create mode 100644 libdd-common/src/machine_id/mod.rs create mode 100644 libdd-common/src/machine_id/windows.rs diff --git a/libdd-common/Cargo.toml b/libdd-common/Cargo.toml index 456f8c175d..f5a5994329 100644 --- a/libdd-common/Cargo.toml +++ b/libdd-common/Cargo.toml @@ -69,6 +69,7 @@ features = [ "Win32_Foundation", "Win32_System_Diagnostics_ToolHelp", "Win32_System_Performance", + "Win32_System_Registry", "Win32_System_Threading", ] diff --git a/libdd-common/src/lib.rs b/libdd-common/src/lib.rs index b10df80d75..1a3b665245 100644 --- a/libdd-common/src/lib.rs +++ b/libdd-common/src/lib.rs @@ -21,6 +21,7 @@ pub mod connector; #[cfg(feature = "reqwest")] pub mod dump_server; pub mod entity_id; +pub mod machine_id; pub mod regex_engine; #[macro_use] pub mod cstr; diff --git a/libdd-common/src/machine_id/linux.rs b/libdd-common/src/machine_id/linux.rs new file mode 100644 index 0000000000..0888ab854f --- /dev/null +++ b/libdd-common/src/machine_id/linux.rs @@ -0,0 +1,121 @@ +// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +use std::path::Path; + +fn read_trimmed(path: &Path) -> Option { + let s = std::fs::read_to_string(path).ok()?; + let s = s.trim().to_owned(); + if s.is_empty() { + None + } else { + Some(s) + } +} + +pub fn get_machine_id_impl_paths(dmi_path: &Path, etc_path: &Path, boot_path: &Path) -> String { + if let Some(id) = read_trimmed(dmi_path) { + return id; + } + // agent compatibility: + // gopsutil only accepts /etc/machine-id when it's exactly 32 chars (bare hex) + if let Some(id) = read_trimmed(etc_path) { + if id.len() == 32 { + return id; + } + } + read_trimmed(boot_path).unwrap_or_default() +} + +pub fn get_machine_id_impl() -> String { + get_machine_id_impl_paths( + Path::new("/sys/class/dmi/id/product_uuid"), + Path::new("/etc/machine-id"), + Path::new("/proc/sys/kernel/random/boot_id"), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn write(path: &Path, content: &[u8]) { + std::fs::write(path, content).unwrap(); + } + + fn tmp_paths( + dir: &tempfile::TempDir, + ) -> (std::path::PathBuf, std::path::PathBuf, std::path::PathBuf) { + ( + dir.path().join("product_uuid"), + dir.path().join("machine_id"), + dir.path().join("boot_id"), + ) + } + + #[test] + fn level1_dmi_wins_when_present() { + let dir = tempfile::tempdir().unwrap(); + let (dmi, etc, boot) = tmp_paths(&dir); + write(&dmi, b"B08FA8A2-B01A-4D2B-BD95-FEC7E30C5AEC\n"); + write(&etc, b"aabbccddaabbccddaabbccddaabbccdd\n"); + write(&boot, b"cccccccccccccccccccccccccccccccc\n"); + assert_eq!( + get_machine_id_impl_paths(&dmi, &etc, &boot), + "B08FA8A2-B01A-4D2B-BD95-FEC7E30C5AEC" + ); + } + + #[test] + fn level2_etc_used_when_dmi_absent() { + let dir = tempfile::tempdir().unwrap(); + let (dmi, etc, boot) = tmp_paths(&dir); + write(&etc, b"aabbccddaabbccddaabbccddaabbccdd\n"); + write(&boot, b"cccccccccccccccccccccccccccccccc\n"); + assert_eq!( + get_machine_id_impl_paths(&dmi, &etc, &boot), + "aabbccddaabbccddaabbccddaabbccdd" + ); + } + + #[test] + fn level2_skipped_when_etc_not_32_chars() { + let dir = tempfile::tempdir().unwrap(); + let (dmi, etc, boot) = tmp_paths(&dir); + write(&etc, b"aabbccdd-aabb-ccdd-aabb-ccddaabbccdd\n"); + write(&boot, b"dddddddddddddddddddddddddddddddd\n"); + assert_eq!( + get_machine_id_impl_paths(&dmi, &etc, &boot), + "dddddddddddddddddddddddddddddddd" + ); + } + + #[test] + fn level3_boot_id_as_last_resort() { + let dir = tempfile::tempdir().unwrap(); + let (dmi, etc, boot) = tmp_paths(&dir); + write(&boot, b"cccccccccccccccccccccccccccccccc\n"); + assert_eq!( + get_machine_id_impl_paths(&dmi, &etc, &boot), + "cccccccccccccccccccccccccccccccc" + ); + } + + #[test] + fn all_absent_returns_empty() { + let dir = tempfile::tempdir().unwrap(); + let (dmi, etc, boot) = tmp_paths(&dir); + assert_eq!(get_machine_id_impl_paths(&dmi, &etc, &boot), ""); + } + + #[test] + fn trims_whitespace() { + let dir = tempfile::tempdir().unwrap(); + let (dmi, etc, boot) = tmp_paths(&dir); + write(&etc, b" aabbccddaabbccddaabbccddaabbccdd \n"); + assert_eq!( + get_machine_id_impl_paths(&dmi, &etc, &boot), + "aabbccddaabbccddaabbccddaabbccdd" + ); + } +} diff --git a/libdd-common/src/machine_id/macos.rs b/libdd-common/src/machine_id/macos.rs new file mode 100644 index 0000000000..3fd1e7a58e --- /dev/null +++ b/libdd-common/src/machine_id/macos.rs @@ -0,0 +1,39 @@ +// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +/// Returns `IOPlatformUUID` via `gethostuuid(3)`, which avoids a fork+exec of `ioreg`. +pub fn get_machine_id_impl() -> String { + let mut uuid = [0u8; 16]; + let wait = libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }; + let rc = unsafe { libc::gethostuuid(uuid.as_mut_ptr(), &wait) }; + if rc != 0 { + return String::new(); + } + format!( + "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", + uuid[0], uuid[1], uuid[2], uuid[3], + uuid[4], uuid[5], + uuid[6], uuid[7], + uuid[8], uuid[9], + uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15], + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn returns_nonempty_uuid() { + let id = get_machine_id_impl(); + assert!(!id.is_empty()); + assert_eq!(id.len(), 36); + assert_eq!(&id[8..9], "-"); + assert_eq!(&id[13..14], "-"); + assert_eq!(&id[18..19], "-"); + assert_eq!(&id[23..24], "-"); + } +} diff --git a/libdd-common/src/machine_id/mod.rs b/libdd-common/src/machine_id/mod.rs new file mode 100644 index 0000000000..e6de79d292 --- /dev/null +++ b/libdd-common/src/machine_id/mod.rs @@ -0,0 +1,135 @@ +// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +//! Host machine identifier, mirroring `pkg/util/uuid.GetUUID()` in the Go agent. +//! +//! | Platform | Source | +//! |----------|--------| +//! | Linux | `/sys/class/dmi/id/product_uuid` then `/etc/machine-id` → `/proc/sys/kernel/random/boot_id` | +//! | macOS | `gethostuuid(3)` | +//! | Windows | `HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid` | +//! | Other | `""` | +//! +//! All values are normalised to lowercase `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`. +//! Returns `""` on failure rather than a random UUID — the backend can detect +//! a missing value but not a wrong one. + +use std::sync::LazyLock; + +#[cfg(target_os = "linux")] +mod linux; + +#[cfg(target_os = "macos")] +mod macos; + +#[cfg(windows)] +mod windows; + +/// Normalise a raw OS machine-id to a lowercase hyphenated UUID string. +/// Strips hyphens, filters to hex digits, lowercases, then re-inserts hyphens. +/// Returns `""` if the result is not exactly 32 hex digits. +pub(crate) fn normalize_uuid(raw: &str) -> String { + let hex: String = raw + .chars() + .filter(|c| c.is_ascii_hexdigit()) + .flat_map(char::to_lowercase) + .collect(); + + if hex.len() != 32 { + return String::new(); + } + + format!( + "{}-{}-{}-{}-{}", + &hex[0..8], + &hex[8..12], + &hex[12..16], + &hex[16..20], + &hex[20..32], + ) +} + +static MACHINE_ID: LazyLock = LazyLock::new(|| { + let raw = { + #[cfg(target_os = "linux")] + { + linux::get_machine_id_impl() + } + #[cfg(target_os = "macos")] + { + macos::get_machine_id_impl() + } + #[cfg(windows)] + { + windows::get_machine_id_impl() + } + #[cfg(not(any(target_os = "linux", target_os = "macos", windows)))] + { + String::new() + } + }; + normalize_uuid(&raw) +}); + +/// Returns the host machine ID as a lowercase hyphenated UUID, cached for the process lifetime. +/// Returns `""` on failure or unsupported platforms. +pub fn get_machine_id() -> &'static str { + MACHINE_ID.as_str() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cached_value_is_stable() { + assert_eq!(get_machine_id(), get_machine_id()); + } + + #[test] + fn value_has_uuid_shape_if_nonempty() { + let id = get_machine_id(); + if id.is_empty() { + return; + } + assert_eq!(id.len(), 36); + for (i, c) in id.chars().enumerate() { + if [8, 13, 18, 23].contains(&i) { + assert_eq!(c, '-'); + } else { + assert!(c.is_ascii_hexdigit() && !c.is_ascii_uppercase()); + } + } + } + + #[test] + fn normalize_bare_hex_inserts_hyphens() { + assert_eq!( + normalize_uuid("b08fa8a2b01a4d2bbd95fec7e30c5aec"), + "b08fa8a2-b01a-4d2b-bd95-fec7e30c5aec" + ); + } + + #[test] + fn normalize_uppercase_uuid_lowercased() { + assert_eq!( + normalize_uuid("B08FA8A2-B01A-4D2B-BD95-FEC7E30C5AEC"), + "b08fa8a2-b01a-4d2b-bd95-fec7e30c5aec" + ); + } + + #[test] + fn normalize_lowercase_uuid_unchanged() { + assert_eq!( + normalize_uuid("b08fa8a2-b01a-4d2b-bd95-fec7e30c5aec"), + "b08fa8a2-b01a-4d2b-bd95-fec7e30c5aec" + ); + } + + #[test] + fn normalize_invalid_returns_empty() { + assert_eq!(normalize_uuid(""), ""); + assert_eq!(normalize_uuid("b08fa8a2"), ""); + assert_eq!(normalize_uuid("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"), ""); + } +} diff --git a/libdd-common/src/machine_id/windows.rs b/libdd-common/src/machine_id/windows.rs new file mode 100644 index 0000000000..d8abab29f8 --- /dev/null +++ b/libdd-common/src/machine_id/windows.rs @@ -0,0 +1,91 @@ +// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ +// SPDX-License-Identifier: Apache-2.0 + +use windows_sys::Win32::Foundation::{ERROR_SUCCESS, HKEY}; +use windows_sys::Win32::System::Registry::{ + RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_64KEY, + REG_SZ, +}; +use windows_sys::Win32::System::Threading::{GetCurrentProcess, IsWow64Process}; + +fn to_wide_null(s: &str) -> Vec { + s.encode_utf16().chain(std::iter::once(0u16)).collect() +} + +fn is_wow64() -> bool { + let mut result: i32 = 0; + let ok = unsafe { IsWow64Process(GetCurrentProcess(), &mut result) }; + ok != 0 && result != 0 +} + +pub fn get_machine_id_impl() -> String { + let access = if cfg!(target_pointer_width = "32") && is_wow64() { + KEY_READ | KEY_WOW64_64KEY + } else { + KEY_READ + }; + + let mut hkey: HKEY = 0; + // SAFETY: all pointers are valid. + let status = unsafe { + RegOpenKeyExW( + HKEY_LOCAL_MACHINE, + to_wide_null("SOFTWARE\\Microsoft\\Cryptography").as_ptr(), + 0, + access, + &mut hkey, + ) + }; + if status != ERROR_SUCCESS as i32 { + return String::new(); + } + + let value_wide = to_wide_null("MachineGuid"); + + let mut data_type: u32 = 0; + let mut data_len: u32 = 0; + // SAFETY: null data pointer is valid for a size-query call. + let status = unsafe { + RegQueryValueExW( + hkey, + value_wide.as_ptr(), + std::ptr::null_mut(), + &mut data_type, + std::ptr::null_mut(), + &mut data_len, + ) + }; + if status != ERROR_SUCCESS as i32 || data_type != REG_SZ { + // SAFETY: hkey is a valid open handle. + unsafe { RegCloseKey(hkey) }; + return String::new(); + } + + let mut buf: Vec = vec![0u16; (data_len as usize).div_ceil(2)]; + let mut actual_len = data_len; + // SAFETY: buf has the capacity returned by the size-query call above. + let status = unsafe { + RegQueryValueExW( + hkey, + value_wide.as_ptr(), + std::ptr::null_mut(), + &mut data_type, + buf.as_mut_ptr().cast(), + &mut actual_len, + ) + }; + // SAFETY: hkey is a valid open handle. + unsafe { RegCloseKey(hkey) }; + + if status != ERROR_SUCCESS as i32 { + return String::new(); + } + + while buf.last() == Some(&0u16) { + buf.pop(); + } + String::from_utf16(&buf) + .unwrap_or_default() + .trim() + .to_owned() +} From e17eaed88899444158550edefb043f8dab888755 Mon Sep 17 00:00:00 2001 From: paullegranddc Date: Thu, 25 Jun 2026 19:56:48 +0200 Subject: [PATCH 2/4] fix: bind vec passed by ptr early --- libdd-common/src/machine_id/windows.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libdd-common/src/machine_id/windows.rs b/libdd-common/src/machine_id/windows.rs index d8abab29f8..48db868fda 100644 --- a/libdd-common/src/machine_id/windows.rs +++ b/libdd-common/src/machine_id/windows.rs @@ -27,10 +27,11 @@ pub fn get_machine_id_impl() -> String { let mut hkey: HKEY = 0; // SAFETY: all pointers are valid. + let subkey = to_wide_null("SOFTWARE\\Microsoft\\Cryptography"); let status = unsafe { RegOpenKeyExW( HKEY_LOCAL_MACHINE, - to_wide_null("SOFTWARE\\Microsoft\\Cryptography").as_ptr(), + subkey.as_ptr(), 0, access, &mut hkey, From b480793203b50d202392ed6874e46c1633458c2c Mon Sep 17 00:00:00 2001 From: paullegranddc Date: Thu, 25 Jun 2026 20:12:34 +0200 Subject: [PATCH 3/4] fix: fmt + clippy --- libdd-common/src/machine_id/windows.rs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/libdd-common/src/machine_id/windows.rs b/libdd-common/src/machine_id/windows.rs index 48db868fda..33409eacf2 100644 --- a/libdd-common/src/machine_id/windows.rs +++ b/libdd-common/src/machine_id/windows.rs @@ -1,10 +1,10 @@ // Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/ // SPDX-License-Identifier: Apache-2.0 -use windows_sys::Win32::Foundation::{ERROR_SUCCESS, HKEY}; +use windows_sys::Win32::Foundation::ERROR_SUCCESS; use windows_sys::Win32::System::Registry::{ - RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_64KEY, - REG_SZ, + RegCloseKey, RegOpenKeyExW, RegQueryValueExW, HKEY, HKEY_LOCAL_MACHINE, KEY_READ, + KEY_WOW64_64KEY, REG_SZ, }; use windows_sys::Win32::System::Threading::{GetCurrentProcess, IsWow64Process}; @@ -28,16 +28,9 @@ pub fn get_machine_id_impl() -> String { let mut hkey: HKEY = 0; // SAFETY: all pointers are valid. let subkey = to_wide_null("SOFTWARE\\Microsoft\\Cryptography"); - let status = unsafe { - RegOpenKeyExW( - HKEY_LOCAL_MACHINE, - subkey.as_ptr(), - 0, - access, - &mut hkey, - ) - }; - if status != ERROR_SUCCESS as i32 { + let status = + unsafe { RegOpenKeyExW(HKEY_LOCAL_MACHINE, subkey.as_ptr(), 0, access, &mut hkey) }; + if status != ERROR_SUCCESS { return String::new(); } @@ -56,7 +49,7 @@ pub fn get_machine_id_impl() -> String { &mut data_len, ) }; - if status != ERROR_SUCCESS as i32 || data_type != REG_SZ { + if status != ERROR_SUCCESS || data_type != REG_SZ { // SAFETY: hkey is a valid open handle. unsafe { RegCloseKey(hkey) }; return String::new(); From d1ddfc269613012571cbd9fe288f31906ac5930e Mon Sep 17 00:00:00 2001 From: Edmund Kump Date: Thu, 25 Jun 2026 16:56:39 -0400 Subject: [PATCH 4/4] fix windows compile error for ERROR_SUCCESS type --- libdd-common/src/machine_id/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdd-common/src/machine_id/windows.rs b/libdd-common/src/machine_id/windows.rs index 33409eacf2..4a3546f49e 100644 --- a/libdd-common/src/machine_id/windows.rs +++ b/libdd-common/src/machine_id/windows.rs @@ -71,7 +71,7 @@ pub fn get_machine_id_impl() -> String { // SAFETY: hkey is a valid open handle. unsafe { RegCloseKey(hkey) }; - if status != ERROR_SUCCESS as i32 { + if status != ERROR_SUCCESS { return String::new(); }