@cryptotaxi247 / netdata-1 / commits / edc82776a

add unique machine id to status file (#19778)

* add unique machine id to status file * clear errno before listening for sockets * remove the pipe when exiting

Costa Tsaousis committed Mar 5, 2025 at 15:09 UTC edc82776ae5fcca30feb9365b5b0c16c67fad65c
8 files changed +235 -7
CMakeLists.txt
+2
@@ -1068,6 +1068,8 @@ set(LIBNETDATA_FILES
1068 src/libnetdata/os/mmap_limit.h
1069 src/libnetdata/signals/signals.c
1070 src/libnetdata/signals/signals.h
1071 + src/libnetdata/os/machine_id.c
1072 + src/libnetdata/os/machine_id.h
1073 )
1074
1075 list(APPEND LIBNETDATA_FILES ${INICFG_FILES})
src/daemon/daemon-shutdown.c
+8 -5
@@ -300,13 +300,16 @@ void netdata_cleanup_and_exit(EXIT_REASON reason, const char *action, const char
300 sqlite_close_databases();
301 watcher_step_complete(WATCHER_STEP_ID_CLOSE_SQL_DATABASES);
302 sqlite_library_shutdown();
303 +
304 + // unlink the pid
305 + if(pidfile && *pidfile && unlink(pidfile) != 0)
306 + netdata_log_error("EXIT: cannot unlink pidfile '%s'.", pidfile);
307
308 + // unlink the pipe
309 + const char *pipe = daemon_pipename();
310 + if(pipe && *pipe && unlink(pipe) != 0)
311 + netdata_log_error("EXIT: cannot unlink netdatacli socket file '%s'.", pipe);
312
305 - // unlink the pid
306 - if(pidfile && *pidfile) {
307 - if(unlink(pidfile) != 0)
308 - netdata_log_error("EXIT: cannot unlink pidfile '%s'.", pidfile);
309 - }
313 watcher_step_complete(WATCHER_STEP_ID_REMOVE_PID_FILE);
314
315 netdata_ssl_cleanup();
src/daemon/daemon-status-file.c
+8 -2
@@ -9,7 +9,7 @@
9 #include <openssl/pem.h>
10 #include <openssl/err.h>
11
12 -#define STATUS_FILE_VERSION 9
12 +#define STATUS_FILE_VERSION 10
13
14 #define STATUS_FILENAME "status-netdata.json"
15
@@ -129,6 +129,7 @@ static void daemon_status_file_to_json(BUFFER *wb, DAEMON_STATUS_FILE *ds) {
129
130 buffer_json_member_add_object(wb, "host"); // ECS
131 {
132 + buffer_json_member_add_uuid_compact(wb, "id", ds->machine_id.uuid);
133 buffer_json_member_add_string_or_empty(wb, "architecture", ds->architecture); // ECS
134 buffer_json_member_add_string_or_empty(wb, "virtualization", ds->virtualization); // custom
135 buffer_json_member_add_string_or_empty(wb, "container", ds->container); // custom
@@ -136,7 +137,7 @@ static void daemon_status_file_to_json(BUFFER *wb, DAEMON_STATUS_FILE *ds) {
137
138 buffer_json_member_add_object(wb, "boot"); // ECS
139 {
139 - buffer_json_member_add_uuid(wb, "id", ds->boot_id.uuid); // ECS
140 + buffer_json_member_add_uuid_compact(wb, "id", ds->boot_id.uuid); // ECS
141 }
142 buffer_json_object_close(wb);
143
@@ -224,6 +225,7 @@ static bool daemon_status_file_from_json(json_object *jobj, void *data, BUFFER *
225 bool required_v3 = version >= 3 ? strict : false;
226 bool required_v4 = version >= 4 ? strict : false;
227 bool required_v5 = version >= 5 ? strict : false;
228 + bool required_v10 = version >= 10 ? strict : false;
229
230 // Parse timestamp
231 JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "@timestamp", datetime, error, required_v1);
@@ -254,6 +256,7 @@ static bool daemon_status_file_from_json(json_object *jobj, void *data, BUFFER *
256
257 // Parse host object
258 JSONC_PARSE_SUBOBJECT(jobj, path, "host", error, required_v1, {
259 + JSONC_PARSE_TXT2UUID_OR_ERROR_AND_RETURN(jobj, path, "id", ds->machine_id.uuid, error, required_v10);
260 JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "architecture", ds->architecture, error, required_v1);
261 JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "virtualization", ds->virtualization, error, required_v1);
262 JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "container", ds->container, error, required_v1);
@@ -399,6 +402,9 @@ static void daemon_status_file_refresh(DAEMON_STATUS status) {
402 session_status.host_id = UUID_ZERO;
403 }
404
405 + if(UUIDiszero(session_status.machine_id))
406 + session_status.machine_id = os_machine_id();
407 +
408 // copy items from the old status if they are not set
409 if(UUIDiszero(session_status.claim_id))
410 session_status.claim_id = last_session_status.claim_id;
src/daemon/daemon-status-file.h
+1
@@ -44,6 +44,7 @@ typedef struct daemon_status_file {
44 ND_UUID host_id; // the machine guid of the agent
45 ND_UUID node_id; // the Netdata Cloud node id of the agent
46 ND_UUID claim_id; // the Netdata Cloud claim id of the agent
47 + ND_UUID machine_id; // the unique machine id of the system
48
49 struct {
50 usec_t init_started_ut;
src/daemon/main.c
+1
@@ -902,6 +902,7 @@ int netdata_main(int argc, char **argv) {
902
903 delta_startup_time("web server sockets");
904 if(web_server_mode != WEB_SERVER_MODE_NONE) {
905 + errno_clear();
906 if (!api_listen_sockets_setup()) {
907 exit_initiated_add(EXIT_REASON_ALREADY_RUNNING);
908 daemon_status_file_update_status(DAEMON_STATUS_NONE);
src/libnetdata/os/machine_id.c new
+186
@@ -0,0 +1,186 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "machine_id.h"
4 +#include "libnetdata/libnetdata.h"
5 +
6 +static ND_UUID cached_machine_id = { 0 };
7 +static SPINLOCK spinlock = SPINLOCK_INITIALIZER;
8 +
9 +#if defined(OS_LINUX)
10 +
11 +static ND_UUID get_machine_id(void) {
12 + ND_UUID machine_id = { 0 };
13 +
14 + // Try systemd machine-id locations first
15 + const char *locations[] = {
16 + "/etc/machine-id", // systemd standard location
17 + "/var/lib/dbus/machine-id", // fallback for older systems or different distros
18 + "/sys/class/dmi/id/product_uuid" // hardware-based UUID from DMI
19 + };
20 +
21 + for (size_t i = 0; i < _countof(locations); i++) {
22 + char filename[FILENAME_MAX + 1];
23 + snprintfz(filename, sizeof(filename), "%s%s",
24 + netdata_configured_host_prefix ? netdata_configured_host_prefix : "", locations[i]);
25 +
26 + char buf[128];
27 + if (read_txt_file(filename, buf, sizeof(buf)) == 0) {
28 + if (uuid_parse(trim(buf), machine_id.uuid) == 0)
29 + return machine_id;
30 + }
31 + }
32 +
33 + // If no reliable machine ID could be found, return NO_MACHINE_ID
34 + return NO_MACHINE_ID;
35 +}
36 +
37 +#elif defined(OS_FREEBSD)
38 +
39 +static ND_UUID get_machine_id(void) {
40 + ND_UUID machine_id = { 0 };
41 + char buf[128];
42 +
43 + // Try FreeBSD host ID first
44 + char filename[FILENAME_MAX + 1];
45 + snprintfz(filename, sizeof(filename), "%s/etc/hostid",
46 + netdata_configured_host_prefix ? netdata_configured_host_prefix : "");
47 +
48 + if (read_txt_file(filename, buf, sizeof(buf)) == 0) {
49 + if (uuid_parse(trim(buf), machine_id.uuid) == 0)
50 + return machine_id;
51 + }
52 +
53 + // Fallback: Read system kern.hostuuid sysctl
54 + size_t len = sizeof(buf);
55 + if (sysctlbyname("kern.hostuuid", buf, &len, NULL, 0) == 0) {
56 + if (uuid_parse(trim(buf), machine_id.uuid) == 0)
57 + return machine_id;
58 + }
59 +
60 + // If no reliable machine ID could be found, return NO_MACHINE_ID
61 + return NO_MACHINE_ID;
62 +}
63 +
64 +#elif defined(OS_MACOS)
65 +
66 +#include <IOKit/IOKitLib.h>
67 +
68 +static ND_UUID get_machine_id(void) {
69 + ND_UUID machine_id = { 0 };
70 +
71 + // First try to get the platform UUID
72 + io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/");
73 + if (ioRegistryRoot) {
74 + CFStringRef uuidCf = (CFStringRef) IORegistryEntryCreateCFProperty(
75 + ioRegistryRoot,
76 + CFSTR(kIOPlatformUUIDKey),
77 + kCFAllocatorDefault,
78 + 0);
79 +
80 + if (uuidCf) {
81 + char uuid_str[UUID_STR_LEN];
82 + if (CFStringGetCString(uuidCf, uuid_str, sizeof(uuid_str), kCFStringEncodingUTF8)) {
83 + if (uuid_parse(uuid_str, machine_id.uuid) == 0) {
84 + CFRelease(uuidCf);
85 + IOObjectRelease(ioRegistryRoot);
86 + return machine_id;
87 + }
88 + }
89 + CFRelease(uuidCf);
90 + }
91 + IOObjectRelease(ioRegistryRoot);
92 + }
93 +
94 + // Fallback to IOPlatformExpertDevice's IOPlatformSerialNumber
95 + io_service_t platformExpert = IOServiceGetMatchingService(
96 + kIOMasterPortDefault,
97 + IOServiceMatching("IOPlatformExpertDevice"));
98 +
99 + if (platformExpert) {
100 + CFStringRef serialNumberCf = (CFStringRef) IORegistryEntryCreateCFProperty(
101 + platformExpert,
102 + CFSTR(kIOPlatformSerialNumberKey),
103 + kCFAllocatorDefault,
104 + 0);
105 +
106 + if (serialNumberCf) {
107 + char serial_str[128];
108 + if (CFStringGetCString(serialNumberCf, serial_str, sizeof(serial_str), kCFStringEncodingUTF8)) {
109 + // Hardware serial numbers are considered reliable and stable
110 + if (strlen(serial_str) > 0) {
111 + // Generate a UUID from the serial number
112 + char uuid_input[150];
113 + snprintfz(uuid_input, sizeof(uuid_input), "mac-serial:%s", serial_str);
114 + machine_id = UUID_generate_from_hash(uuid_input, strlen(uuid_input));
115 +
116 + CFRelease(serialNumberCf);
117 + IOObjectRelease(platformExpert);
118 + return machine_id;
119 + }
120 + }
121 + CFRelease(serialNumberCf);
122 + }
123 + IOObjectRelease(platformExpert);
124 + }
125 +
126 + // If no reliable machine ID could be found, return NO_MACHINE_ID
127 + return NO_MACHINE_ID;
128 +}
129 +
130 +#elif defined(OS_WINDOWS)
131 +#include <windows.h>
132 +
133 +static ND_UUID get_machine_id(void) {
134 + ND_UUID machine_id = { 0 };
135 + HKEY hKey;
136 +
137 + // Try to get MachineGuid from registry - this is the most reliable machine ID on Windows
138 + if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Cryptography", 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
139 + WCHAR guidW[64];
140 + DWORD guidSize = sizeof(guidW);
141 + DWORD type = REG_SZ;
142 +
143 + if (RegQueryValueExW(hKey, L"MachineGuid", NULL, &type, (LPBYTE)guidW, &guidSize) == ERROR_SUCCESS) {
144 + char guid_str[UUID_STR_LEN];
145 +
146 + // Convert GUID to UTF-8
147 + if (WideCharToMultiByte(CP_UTF8, 0, guidW, -1, guid_str, sizeof(guid_str), NULL, NULL) > 0) {
148 + if (uuid_parse(guid_str, machine_id.uuid) == 0) {
149 + RegCloseKey(hKey);
150 + return machine_id;
151 + }
152 + }
153 + }
154 + RegCloseKey(hKey);
155 + }
156 +
157 + // If no reliable machine ID could be found, return NO_MACHINE_ID
158 + return NO_MACHINE_ID;
159 +}
160 +
161 +#endif // OS_WINDOWS
162 +
163 +ND_UUID os_machine_id(void) {
164 + // Fast path - return cached value if available
165 + if(!UUIDiszero(cached_machine_id))
166 + return cached_machine_id;
167 +
168 + spinlock_lock(&spinlock);
169 +
170 + // Check again under lock in case another thread set it
171 + if(UUIDiszero(cached_machine_id)) {
172 + cached_machine_id = get_machine_id();
173 +
174 + // Log the result if debugging is enabled
175 + if(UUIDeq(cached_machine_id, NO_MACHINE_ID))
176 + nd_log(NDLS_DAEMON, NDLP_WARNING, "OS_MACHINE_ID: Could not detect a reliable machine ID");
177 + else {
178 + char buf[UUID_STR_LEN];
179 + uuid_unparse_lower(cached_machine_id.uuid, buf);
180 + nd_log(NDLS_DAEMON, NDLP_WARNING, "OS_MACHINE_ID: machine ID found '%s'", buf);
181 + }
182 + }
183 +
184 + spinlock_unlock(&spinlock);
185 + return cached_machine_id;
186 +}
src/libnetdata/os/machine_id.h new
+28
@@ -0,0 +1,28 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_MACHINE_ID_H
4 +#define NETDATA_MACHINE_ID_H
5 +
6 +#include "libnetdata/libnetdata.h"
7 +#include "libnetdata/uuid/uuid.h"
8 +
9 +/**
10 + * Special value returned when a reliable machine ID cannot be determined
11 + */
12 +#define NO_MACHINE_ID (ND_UUID){ .parts = { .hig64 = 1, .low64 = 1 } }
13 +
14 +/**
15 + * Get the machine ID
16 + *
17 + * Returns a UUID that uniquely identifies the machine.
18 + * On Linux, this is the systemd machine-id.
19 + * On other systems, it uses platform-specific values.
20 + *
21 + * The value is cached after first call.
22 + * Returns NO_MACHINE_ID when a reliable machine ID cannot be detected.
23 + *
24 + * @return ND_UUID The machine ID
25 + */
26 +ND_UUID os_machine_id(void);
27 +
28 +#endif // NETDATA_MACHINE_ID_H
src/libnetdata/os/os.h
+1
@@ -40,6 +40,7 @@
40 #include "run_dir.h"
41 #include "file_lock.h"
42 #include "mmap_limit.h"
43 +#include "machine_id.h"
44
45 // this includes windows.h to the whole of netdata
46 // so various conflicts arise