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