master
c 1,365 lines 46.9 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "aclk.h"
4
5 #include "mqtt_websockets/mqtt_wss_client.h"
6 #include "mqtt_websockets/aclk_mqtt_workers.h"
7 #include "aclk_otp.h"
8 #include "aclk_tx_msgs.h"
9 #include "aclk_query.h"
10 #include "aclk_query_queue.h"
11 #include "aclk_util.h"
12 #include "aclk_rx_msgs.h"
13 #include "https_client.h"
14 #include "schema-wrappers/schema_wrappers.h"
15 #include "aclk_capas.h"
16 #include "aclk_proxy.h"
17
18 #ifdef ACLK_LOG_CONVERSATION_DIR
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <fcntl.h>
22 #endif
23
24 #define MQTT_DEFAULT_MAX_BUF_SIZE (25 * 1024 * 1024)
25
26 int aclk_pubacks_per_conn = 0; // How many PubAcks we got since MQTT conn est.
27 int aclk_rcvd_cloud_msgs = 0;
28 int aclk_connection_counter = 0;
29
30 mqtt_wss_client mqttwss_client;
31
32 static bool aclk_connected = false;
33 static inline void aclk_set_connected(void) {
34 __atomic_store_n(&aclk_connected, true, __ATOMIC_RELAXED);
35
36 daemon_status_file_update_status(DAEMON_STATUS_NONE);
37 }
38 static inline void aclk_set_disconnected(void) {
39 __atomic_store_n(&aclk_connected, false, __ATOMIC_RELAXED);
40
41 if(mqttwss_client)
42 mqtt_wss_reset_stats(mqttwss_client);
43
44 daemon_status_file_update_status(DAEMON_STATUS_NONE);
45 }
46
47 inline bool aclk_online(void) {
48 return __atomic_load_n(&aclk_connected, __ATOMIC_RELAXED);
49 }
50
51 bool aclk_online_for_contexts(void) {
52 return aclk_online() && aclk_query_scope_has(HTTP_ACL_METRICS);
53 }
54
55 bool aclk_online_for_alerts(void) {
56 return aclk_online() && aclk_query_scope_has(HTTP_ACL_ALERTS);
57 }
58
59 bool aclk_online_for_nodes(void) {
60 return aclk_online() && aclk_query_scope_has(HTTP_ACL_NODES);
61 }
62
63 int aclk_ctx_based = 0;
64 int aclk_disable_runtime = 0;
65
66 ACLK_DISCONNECT_ACTION disconnect_req = ACLK_NO_DISCONNECT;
67
68 usec_t aclk_session_us = 0;
69 time_t aclk_session_sec = 0;
70
71 time_t last_conn_time_mqtt = 0;
72 time_t last_conn_time_appl = 0;
73 time_t last_disconnect_time = 0;
74 time_t next_connection_attempt = 0;
75 float last_backoff_value = 0;
76
77 time_t aclk_block_until = 0;
78
79 struct mqtt_wss_stats aclk_statistics(void) {
80 if(mqttwss_client)
81 return mqtt_wss_get_stats(mqttwss_client);
82 else
83 return (struct mqtt_wss_stats) { 0 };
84 }
85
86 struct aclk_shared_state aclk_shared_state = {
87 .mqtt_shutdown_msg_id = -1,
88 .mqtt_shutdown_msg_rcvd = 0
89 };
90
91 #ifdef MQTT_WSS_DEBUG
92 #include <openssl/ssl.h>
93 #define DEFAULT_SSKEYLOGFILE_NAME "SSLKEYLOGFILE"
94 const char *ssl_log_filename = NULL;
95 FILE *ssl_log_file = NULL;
96 static void aclk_ssl_keylog_cb(const SSL *ssl, const char *line)
97 {
98 (void)ssl;
99 if (!ssl_log_file)
100 ssl_log_file = fopen(ssl_log_filename, "a");
101 if (!ssl_log_file) {
102 netdata_log_error("ACLK: Couldn't open ssl_log file (%s) for append.", ssl_log_filename);
103 return;
104 }
105 fputs(line, ssl_log_file);
106 putc('\n', ssl_log_file);
107 fflush(ssl_log_file);
108 }
109 #endif
110
111 #if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_300
112 OSSL_DECODER_CTX *aclk_dctx = NULL;
113 EVP_PKEY *aclk_private_key = NULL;
114 #else
115 static RSA *aclk_private_key = NULL;
116 #endif
117 static int load_private_key()
118 {
119 if (aclk_private_key != NULL) {
120 #if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_300
121 EVP_PKEY_free(aclk_private_key);
122 if (aclk_dctx)
123 OSSL_DECODER_CTX_free(aclk_dctx);
124
125 aclk_dctx = NULL;
126 #else
127 RSA_free(aclk_private_key);
128 #endif
129 }
130 aclk_private_key = NULL;
131 char filename[FILENAME_MAX + 1];
132 snprintfz(filename, FILENAME_MAX, "%s/cloud.d/private.pem", netdata_configured_varlib_dir);
133
134 long bytes_read;
135 char *private_key = read_by_filename(filename, &bytes_read);
136 if (!private_key) {
137 netdata_log_error("ACLK: Claimed agent cannot establish ACLK - unable to load private key '%s' failed.", filename);
138 return 1;
139 }
140 netdata_log_debug(D_ACLK, "Claimed agent loaded private key len=%ld bytes", bytes_read);
141
142 BIO *key_bio = BIO_new_mem_buf(private_key, -1);
143 if (key_bio==NULL) {
144 netdata_log_error("ACLK: Claimed agent cannot establish ACLK - failed to create BIO for key");
145 goto biofailed;
146 }
147
148 #if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_300
149 aclk_dctx = OSSL_DECODER_CTX_new_for_pkey(&aclk_private_key, "PEM", NULL,
150 "RSA",
151 OSSL_KEYMGMT_SELECT_PRIVATE_KEY,
152 NULL, NULL);
153
154 if (!aclk_dctx) {
155 netdata_log_error("ACLK: Loading private key (from claiming) failed - no OpenSSL Decoders found");
156 goto biofailed;
157 }
158
159 // this is necesseary to avoid RSA key with wrong size
160 if (!OSSL_DECODER_from_bio(aclk_dctx, key_bio)) {
161 netdata_log_error("ACLK: Decoding private key (from claiming) failed - invalid format.");
162 goto biofailed;
163 }
164 #else
165 aclk_private_key = PEM_read_bio_RSAPrivateKey(key_bio, NULL, NULL, NULL);
166 #endif
167 BIO_free(key_bio);
168 if (aclk_private_key!=NULL)
169 {
170 freez(private_key);
171 return 0;
172 }
173 char err[512];
174 ERR_error_string_n(ERR_get_error(), err, sizeof(err));
175 netdata_log_error("ACLK: Claimed agent cannot establish ACLK - cannot create private key: %s", err);
176
177 biofailed:
178 freez(private_key);
179 return 1;
180 }
181
182 /**
183 * Will block until agent is claimed. Returns only if agent claimed
184 * or if agent needs to shutdown.
185 *
186 * @return `0` if agent has been claimed,
187 * `1` if interrupted due to agent shutting down
188 */
189 static int wait_till_agent_claimed(void)
190 {
191 ND_UUID uuid = claim_id_get_uuid();
192 while (likely(UUIDiszero(uuid))) {
193 sleep_usec(USEC_PER_SEC * 1);
194 if (!service_running(SERVICE_ACLK))
195 return 1;
196 uuid = claim_id_get_uuid();
197 }
198 return 0;
199 }
200
201 /**
202 * Checks everything is ready for connection
203 * agent claimed, cloud url set and private key available
204 *
205 * @param aclk_hostname points to location where string pointer to hostname will be set
206 * @param aclk_port port to int where port will be saved
207 *
208 * @return If non 0 returned irrecoverable error happened (or exit_initiated) and ACLK should be terminated
209 */
210 static int wait_till_agent_claim_ready()
211 {
212 url_t url;
213 while (service_running(SERVICE_ACLK)) {
214 if (wait_till_agent_claimed())
215 return 1;
216
217 // The NULL return means the value was never initialised, but this value has been initialized in post_conf_load.
218 // We trap the impossible NULL here to keep the linter happy without using a fatal() in the code.
219 const char *cloud_base_url = cloud_config_url_get();
220 if (cloud_base_url == NULL) {
221 netdata_log_error("ACLK: Do not move the \"url\" out of netdata_conf_section_global_run_as_user!!");
222 return 1;
223 }
224
225 // We just check configuration is valid here
226 // TODO make it without malloc/free
227 memset(&url, 0, sizeof(url_t));
228 if (url_parse(cloud_base_url, &url)) {
229 netdata_log_error("ACLK: Agent is claimed but the URL in configuration key \"url\" is invalid, please fix");
230 url_t_destroy(&url);
231 sleep(5);
232 continue;
233 }
234 url_t_destroy(&url);
235
236 if (!load_private_key())
237 return 0;
238
239 sleep(5);
240 }
241
242 return 1;
243 }
244
245 static void msg_callback(const char *topic, const void *msg, size_t msglen, int qos)
246 {
247 UNUSED(qos);
248 aclk_rcvd_cloud_msgs++;
249
250 netdata_log_debug(D_ACLK, "Got Message From Broker Topic \"%s\" QOS %d", topic, qos);
251
252 if (aclk_shared_state.mqtt_shutdown_msg_id > 0) {
253 netdata_log_error("ACLK: Link is shutting down. Ignoring incoming message.");
254 return;
255 }
256
257 const char *msgtype = strrchr(topic, '/');
258 if (unlikely(!msgtype)) {
259 error_report("Cannot get message type from topic. Ignoring message from topic \"%s\"", topic);
260 return;
261 }
262 msgtype++;
263 if (unlikely(!*msgtype)) {
264 error_report("Message type empty. Ignoring message from topic \"%s\"", topic);
265 return;
266 }
267
268 #ifdef ACLK_LOG_CONVERSATION_DIR
269 #define FN_MAX_LEN 512
270 char filename[FN_MAX_LEN];
271 int logfd;
272 snprintf(filename, FN_MAX_LEN, ACLK_LOG_CONVERSATION_DIR "/%010d-rx-%s.bin", ACLK_GET_CONV_LOG_NEXT(), msgtype);
273 logfd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR );
274 if(logfd < 0)
275 netdata_log_error("ACLK: Error opening ACLK Conversation logfile \"%s\" for RX message.", filename);
276 write(logfd, msg, msglen);
277 close(logfd);
278 #endif
279
280 aclk_handle_new_cloud_msg(msgtype, msg, msglen, topic);
281 }
282
283 static void puback_callback(uint16_t packet_id)
284 {
285 if (++aclk_pubacks_per_conn == ACLK_PUBACKS_CONN_STABLE) {
286 last_conn_time_appl = now_realtime_sec();
287 aclk_tbeb_reset();
288 }
289
290 //#ifdef NETDATA_INTERNAL_CHECKS
291 // aclk_stats_msg_puback(packet_id);
292 //#endif
293
294 if (aclk_shared_state.mqtt_shutdown_msg_id == (int)packet_id) {
295 nd_log(NDLS_DAEMON, NDLP_DEBUG,
296 "Shutdown message has been acknowledged by the cloud. Exiting gracefully");
297
298 aclk_shared_state.mqtt_shutdown_msg_rcvd = 1;
299 }
300 }
301
302 void aclk_graceful_disconnect(mqtt_wss_client client);
303
304 bool schedule_node_update = false;
305 /* Keeps connection alive and handles all network communications.
306 * Returns on error or when netdata is shutting down.
307 * @param client instance of mqtt_wss_client
308 * @returns 0 - Netdata Exits
309 * >0 - Error happened. Reconnect and start over.
310 */
311 static int handle_connection(mqtt_wss_client client)
312 {
313 while (service_running(SERVICE_ACLK)) {
314 // timeout 1000 to check at least once a second
315 // for exit_initiated
316 int rc = mqtt_wss_service(client, 1000);
317 if (rc < 0){
318 worker_is_busy(WORKER_ACLK_DISCONNECTED);
319 error_report("Connection Error or Dropped");
320
321 if(rc == MQTT_WSS_ERR_REMOTE_CLOSED)
322 aclk_status_set(ACLK_STATUS_OFFLINE_CLOSED_BY_REMOTE);
323 else if(rc == MQTT_WSS_ERR_PROTO_MQTT)
324 aclk_status_set(ACLK_STATUS_OFFLINE_MQTT_PROTOCOL_ERROR);
325 else if(rc == MQTT_WSS_ERR_PROTO_WS)
326 aclk_status_set(ACLK_STATUS_OFFLINE_WS_PROTOCOL_ERROR);
327 else if(rc == MQTT_WSS_ERR_MSG_TOO_BIG)
328 aclk_status_set(ACLK_STATUS_OFFLINE_MESSAGE_TOO_BIG);
329 else if(rc == MQTT_WSS_ERR_POLL_FAILED)
330 aclk_status_set(ACLK_STATUS_OFFLINE_POLL_ERROR);
331 else /* if(rc == MQTT_WSS_ERR_CONN_DROP) */
332 aclk_status_set(ACLK_STATUS_OFFLINE_SOCKET_ERROR);
333
334 return 1;
335 }
336
337 if (disconnect_req != ACLK_NO_DISCONNECT) {
338 const char *reason;
339 switch (disconnect_req) {
340 case ACLK_CLOUD_DISCONNECT:
341 worker_is_busy(WORKER_ACLK_CMD_DISCONNECT);
342 reason = "cloud request";
343 aclk_status_set(ACLK_STATUS_OFFLINE_CLOUD_REQUESTED_DISCONNECT);
344 break;
345 case ACLK_PING_TIMEOUT:
346 worker_is_busy(WORKER_ACLK_CMD_TIMEOUT);
347 reason = "ping timeout";
348 schedule_node_update = true;
349 aclk_status_set(ACLK_STATUS_OFFLINE_PING_TIMEOUT);
350 break;
351 case ACLK_RELOAD_CONF:
352 worker_is_busy(WORKER_ACLK_CMD_RELOAD_CONF);
353 reason = "reclaim";
354 aclk_status_set(ACLK_STATUS_OFFLINE_RELOADING_CONFIG);
355 break;
356 default:
357 worker_is_busy(WORKER_ACLK_CMD_UNKNOWN);
358 reason = "unknown";
359 aclk_status_set(ACLK_STATUS_OFFLINE);
360 break;
361 }
362
363 nd_log(NDLS_DAEMON, NDLP_NOTICE, "Going to restart connection due to \"%s\"", reason);
364
365 disconnect_req = ACLK_NO_DISCONNECT;
366 aclk_graceful_disconnect(client);
367 aclk_shared_state.mqtt_shutdown_msg_id = -1;
368 aclk_shared_state.mqtt_shutdown_msg_rcvd = 0;
369 return 1;
370 }
371 }
372 return 0;
373 }
374
375 static inline void mqtt_connected_actions(mqtt_wss_client client)
376 {
377 char *topic = (char*)aclk_get_topic(ACLK_TOPICID_COMMAND);
378
379 if (!topic)
380 netdata_log_error("ACLK: Unable to fetch topic for COMMAND (to subscribe)");
381 else
382 mqtt_wss_subscribe(client, topic, 1);
383
384 topic = (char*)aclk_get_topic(ACLK_TOPICID_CMD_NG_V1);
385 if (!topic)
386 netdata_log_error("ACLK: Unable to fetch topic for protobuf COMMAND (to subscribe)");
387 else
388 mqtt_wss_subscribe(client, topic, 1);
389
390 aclk_set_connected();
391 aclk_pubacks_per_conn = 0;
392 aclk_rcvd_cloud_msgs = 0;
393 aclk_connection_counter++;
394
395 size_t iter = 0;
396 while ((topic = (char*)aclk_topic_cache_iterate(&iter)) != NULL)
397 mqtt_wss_set_topic_alias(client, topic);
398
399 aclk_send_agent_connection_update(client, 1);
400 }
401
402 void aclk_graceful_disconnect(mqtt_wss_client client)
403 {
404 nd_log(NDLS_DAEMON, NDLP_DEBUG,
405 "Preparing to gracefully shutdown ACLK connection");
406
407 aclk_shared_state.mqtt_shutdown_msg_id = aclk_send_agent_connection_update(client, 0);
408
409 time_t t = now_monotonic_sec();
410 while (!mqtt_wss_service(client, 100)) {
411 if (now_monotonic_sec() - t >= 2) {
412 netdata_log_error("ACLK: Wasn't able to gracefully shutdown ACLK in time!");
413 break;
414 }
415 if (aclk_shared_state.mqtt_shutdown_msg_rcvd) {
416 nd_log(NDLS_DAEMON, NDLP_DEBUG,
417 "MQTT App Layer `disconnect` message sent successfully");
418 break;
419 }
420 }
421
422 nd_log(NDLS_DAEMON, NDLP_WARNING, "ACLK link is down");
423 nd_log(NDLS_ACCESS, NDLP_WARNING, "ACLK DISCONNECTED");
424
425 last_disconnect_time = now_realtime_sec();
426 aclk_set_disconnected();
427
428 nd_log(NDLS_DAEMON, NDLP_DEBUG,
429 "Attempting to gracefully shutdown the MQTT/WSS connection");
430
431 mqtt_wss_disconnect(client, 1000);
432 }
433
434 static unsigned long aclk_reconnect_delay() {
435 unsigned long recon_delay;
436 time_t now;
437
438 if (aclk_disable_runtime) {
439 aclk_tbeb_reset();
440 return 60 * MSEC_PER_SEC;
441 }
442
443 now = now_monotonic_sec();
444 if (aclk_block_until) {
445 if (now < aclk_block_until) {
446 recon_delay = aclk_block_until - now;
447 recon_delay *= MSEC_PER_SEC;
448 aclk_block_until = 0;
449 aclk_tbeb_reset();
450 return recon_delay;
451 }
452 aclk_block_until = 0;
453 }
454
455 if (!aclk_env || !aclk_env->backoff.base)
456 return aclk_tbeb_delay(0, 2, 0, 1024);
457
458 return aclk_tbeb_delay(0, aclk_env->backoff.base, aclk_env->backoff.min_s, aclk_env->backoff.max_s);
459 }
460
461 /* Block till aclk_reconnect_delay is satisfied or exit_initiated is signalled
462 * @return 0 - Go ahead and connect (delay expired)
463 * 1 - exit_initiated
464 */
465 #define NETDATA_EXIT_POLL_MS (MSEC_PER_SEC/4)
466 static int aclk_block_till_recon_allowed() {
467 unsigned long recon_delay = aclk_reconnect_delay();
468
469 next_connection_attempt = now_realtime_sec() + (recon_delay / MSEC_PER_SEC);
470 last_backoff_value = (float)recon_delay / MSEC_PER_SEC;
471
472 nd_log(NDLS_DAEMON, NDLP_DEBUG,
473 "Wait before attempting to reconnect in %.3f seconds", recon_delay / (float)MSEC_PER_SEC);
474
475 // we want to wake up from time to time to check exit_initiated
476 worker_is_busy(WORKER_ACLK_WAITING_TO_CONNECT);
477 while (recon_delay)
478 {
479 if (!service_running(SERVICE_ACLK))
480 return 1;
481 if (recon_delay > NETDATA_EXIT_POLL_MS) {
482 sleep_usec(NETDATA_EXIT_POLL_MS * USEC_PER_MS);
483 recon_delay -= NETDATA_EXIT_POLL_MS;
484 continue;
485 }
486 sleep_usec(recon_delay * USEC_PER_MS);
487 recon_delay = 0;
488 }
489
490 worker_is_busy(WORKER_ACLK_CONNECT);
491 return !service_running(SERVICE_ACLK);
492 }
493
494 #ifndef ACLK_DISABLE_CHALLENGE
495 /* Cloud returns transport list ordered with highest
496 * priority first. This function selects highest prio
497 * transport that we can actually use (support)
498 */
499 static int aclk_get_transport_idx(aclk_env_t *env) {
500 for (size_t i = 0; i < env->transport_count; i++) {
501 // currently we support only MQTT 5
502 // therefore select first transport that matches
503 if (env->transports[i]->type == ACLK_TRP_MQTT_5) {
504 return i;
505 }
506 }
507 return -1;
508 }
509 #endif
510
511 ACLK_STATUS aclk_status = ACLK_STATUS_OFFLINE;
512
513 const char *aclk_status_to_string(void) {
514 if(aclk_status == ACLK_STATUS_CONNECTED)
515 return "connected";
516
517 if((int)aclk_status < (int)ND_SOCK_ERR_MAX)
518 return ND_SOCK_ERROR_2str((ND_SOCK_ERROR)aclk_status);
519
520 if((int)aclk_status < (int)HTTPS_CLIENT_RESP_MAX)
521 return https_client_resp_t_2str((https_client_resp_t)aclk_status);
522
523 switch(aclk_status) {
524 case ACLK_STATUS_CONNECTED:
525 return "connected";
526
527 case ACLK_STATUS_OFFLINE:
528 return "offline";
529
530 case ACLK_STATUS_DISABLED:
531 return "disabled";
532
533 case ACLK_STATUS_CANT_CONNECT_NO_CLOUD_URL:
534 return "configuration error, no cloud url";
535
536 case ACLK_STATUS_CANT_CONNECT_INVALID_CLOUD_URL:
537 return "configuration error, invalid cloud url";
538
539 case ACLK_STATUS_BLOCKED:
540 return "agent is blocked";
541
542 case ACLK_STATUS_NO_OLD_PROTOCOL:
543 return "can't connect, old protocol not supported";
544
545 case ACLK_STATUS_NO_PROTOCOL_CAPABILITY:
546 return "can't connect, protocol capability not supported";
547
548 case ACLK_STATUS_INVALID_ENV_AUTH_URL:
549 return "can't connect, invalid /env auth url";
550
551 case ACLK_STATUS_INVALID_ENV_TRANSPORT_IDX:
552 return "can't connect, invalid /env transport idx";
553
554 case ACLK_STATUS_INVALID_ENV_TRANSPORT_URL:
555 return "can't connect, invalid /env transport URL";
556
557 case ACLK_STATUS_NO_LWT_TOPIC:
558 return "can't connect, no LWT topic";
559
560 case ACLK_STATUS_OFFLINE_CLOUD_REQUESTED_DISCONNECT:
561 return "disconnected, due to remote request";
562
563 case ACLK_STATUS_OFFLINE_PING_TIMEOUT:
564 return "disconnected, ping timed out";
565
566 case ACLK_STATUS_OFFLINE_RELOADING_CONFIG:
567 return "disconnected, to reload config";
568
569 case ACLK_STATUS_OFFLINE_POLL_ERROR:
570 return "disconnected, poll() failed";
571
572 case ACLK_STATUS_OFFLINE_CLOSED_BY_REMOTE:
573 return "disconnected, closed by remote end";
574
575 case ACLK_STATUS_OFFLINE_SOCKET_ERROR:
576 return "disconnected, socket error";
577
578 case ACLK_STATUS_OFFLINE_MQTT_PROTOCOL_ERROR:
579 return "disconnected, MQTT protocol error";
580
581 case ACLK_STATUS_OFFLINE_WS_PROTOCOL_ERROR:
582 return "disconnected, WebSockets protocol error";
583
584 case ACLK_STATUS_OFFLINE_MESSAGE_TOO_BIG:
585 return "disconnected, message too big";
586
587 default:
588 return "unknown status";
589 }
590 }
591
592 void aclk_status_set(ACLK_STATUS status) {
593 aclk_status = status;
594
595 ND_LOG_STACK lgs[] = {
596 ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &aclk_connection_msgid),
597 ND_LOG_FIELD_END(),
598 };
599 ND_LOG_STACK_PUSH(lgs);
600
601 nd_log(NDLS_DAEMON, status == ACLK_STATUS_CONNECTED ? NDLP_INFO : NDLP_ERR,
602 "Netdata Cloud, ACLK connection status: %s", aclk_status_to_string());
603 }
604
605 const char *aclk_cloud_base_url = NULL;
606
607 /* Attempts to make a connection to MQTT broker over WSS
608 * @param client instance of mqtt_wss_client
609 * @return 0 - Successful Connection,
610 * <0 - Irrecoverable Error -> Kill ACLK,
611 * >0 - exit_initiated
612 */
613 #define CLOUD_BASE_URL_READ_RETRY 30
614 static int aclk_attempt_to_connect(mqtt_wss_client client)
615 {
616 https_client_resp_t rc;
617
618 url_t base_url;
619
620 #ifndef ACLK_DISABLE_CHALLENGE
621 url_t auth_url;
622 url_t mqtt_url;
623 #endif
624
625 bool fallback_ipv4 = false;
626 while (service_running(SERVICE_ACLK)) {
627 aclk_cloud_base_url = cloud_config_url_get();
628 if (aclk_cloud_base_url == NULL) {
629 error_report("ACLK: cloud base URL is empty.");
630 aclk_status_set(ACLK_STATUS_CANT_CONNECT_NO_CLOUD_URL);
631 return -1;
632 }
633
634 if (aclk_block_till_recon_allowed()) {
635 aclk_status_set(ACLK_STATUS_BLOCKED);
636 return 1;
637 }
638
639 nd_log(NDLS_DAEMON, NDLP_DEBUG, "ACLK: attempting to connect now");
640
641 memset(&base_url, 0, sizeof(url_t));
642 if (url_parse(aclk_cloud_base_url, &base_url)) {
643 aclk_status_set(ACLK_STATUS_CANT_CONNECT_INVALID_CLOUD_URL);
644 error_report("ACLK: base URL '%s' cannot be parsed.", aclk_cloud_base_url);
645 sleep(CLOUD_BASE_URL_READ_RETRY);
646 url_t_destroy(&base_url);
647 continue;
648 }
649
650 struct mqtt_connect_params mqtt_conn_params = {
651 .clientid = "anon",
652 .username = "anon",
653 .password = "anon",
654 .will_topic = "lwt",
655 .will_msg = NULL,
656 .will_flags = MQTT_WSS_PUB_QOS2,
657 .keep_alive = 60,
658 .drop_on_publish_fail = 1
659 };
660
661 #ifndef ACLK_DISABLE_CHALLENGE
662 if (aclk_env) {
663 aclk_env_t_destroy(aclk_env);
664 freez(aclk_env);
665 }
666 aclk_env = callocz(1, sizeof(aclk_env_t));
667
668 rc = aclk_get_env(aclk_env, base_url.host, base_url.port, &fallback_ipv4);
669 url_t_destroy(&base_url);
670 if (rc != HTTPS_CLIENT_RESP_OK) {
671 aclk_status_set((ACLK_STATUS)rc);
672 aclk_env_t_destroy(aclk_env);
673 freez(aclk_env);
674 aclk_env = NULL;
675 continue;
676 }
677
678 if (!service_running(SERVICE_ACLK)) {
679 aclk_status_set(ACLK_STATUS_DISABLED);
680 return 1;
681 }
682
683 if (aclk_env->encoding != ACLK_ENC_PROTO) {
684 aclk_status_set(ACLK_STATUS_NO_OLD_PROTOCOL);
685 error_report("ACLK: this agent can only use the new cloud protocol but cloud requested old one.");
686 continue;
687 }
688
689 if (!aclk_env_has_capa("proto")) {
690 aclk_status_set(ACLK_STATUS_NO_PROTOCOL_CAPABILITY);
691 error_report("ACLK: can't use encoding=proto without at least \"proto\" capability.");
692 continue;
693 }
694
695 nd_log(NDLS_DAEMON, NDLP_DEBUG,
696 "ACLK: new ACLK protobuf protocol negotiated successfully (/env response).");
697
698 memset(&auth_url, 0, sizeof(url_t));
699 if (url_parse(aclk_env->auth_endpoint, &auth_url)) {
700 aclk_status_set(ACLK_STATUS_INVALID_ENV_AUTH_URL);
701 error_report("ACLK: parsing URL returned by env endpoint for authentication failed. \"%s\"", aclk_env->auth_endpoint);
702 url_t_destroy(&auth_url);
703 continue;
704 }
705
706 rc = aclk_get_mqtt_otp(aclk_private_key, (char **)&mqtt_conn_params.clientid, (char **)&mqtt_conn_params.username, (char **)&mqtt_conn_params.password, &auth_url, &fallback_ipv4);
707 url_t_destroy(&auth_url);
708 if (rc != HTTPS_CLIENT_RESP_OK) {
709 aclk_status_set((ACLK_STATUS)rc);
710 error_report("ACLK: error passing Challenge/Response to get OTP");
711 continue;
712 }
713
714 // aclk_get_topic moved here as during OTP we
715 // generate the topic cache
716 mqtt_conn_params.will_topic = aclk_get_topic(ACLK_TOPICID_AGENT_CONN);
717
718 if (!mqtt_conn_params.will_topic) {
719 aclk_status_set(ACLK_STATUS_NO_LWT_TOPIC);
720 error_report("ACLK: couldn't get LWT topic. Will not send LWT.");
721 continue;
722 }
723
724 // Do the MQTT connection
725 int trp = aclk_get_transport_idx(aclk_env);
726 if (trp < 0) {
727 aclk_status_set(ACLK_STATUS_INVALID_ENV_TRANSPORT_IDX);
728 error_report("ACLK: cloud /env endpoint didn't return any transport usable by this agent.");
729 continue;
730 }
731
732 memset(&mqtt_url, 0, sizeof(url_t));
733 if (url_parse(aclk_env->transports[trp]->endpoint, &mqtt_url)){
734 aclk_status_set(ACLK_STATUS_INVALID_ENV_TRANSPORT_URL);
735 error_report("ACLK: failed to parse target URL for /env trp idx %d \"%s\"", trp, aclk_env->transports[trp]->endpoint);
736 url_t_destroy(&mqtt_url);
737 continue;
738 }
739 #endif
740
741 aclk_session_newarch = now_realtime_usec();
742 aclk_session_sec = aclk_session_newarch / USEC_PER_SEC;
743 aclk_session_us = aclk_session_newarch % USEC_PER_SEC;
744
745 mqtt_conn_params.will_msg = aclk_generate_lwt(&mqtt_conn_params.will_msg_len);
746
747 int ssl_flags = cloud_config_insecure_get() ? MQTT_WSS_SSL_ALLOW_SELF_SIGNED : MQTT_WSS_SSL_CERT_CHECK_FULL;
748
749 struct mqtt_wss_proxy proxy_conf = { .host = NULL, .port = 0, .username = NULL, .password = NULL, .proxy_destination = NULL, .type = MQTT_WSS_DIRECT };
750 aclk_set_proxy(
751 (char **)&proxy_conf.host,
752 &proxy_conf.port,
753 (char **)&proxy_conf.username,
754 (char **)&proxy_conf.password,
755 (char **)&proxy_conf.proxy_destination,
756 &proxy_conf.type);
757
758 #ifdef ACLK_DISABLE_CHALLENGE
759 int mqtt_rc = mqtt_wss_connect(client, base_url.host, base_url.port, &mqtt_conn_params, ssl_flags, &proxy_conf);
760 url_t_destroy(&base_url);
761 #else
762 int mqtt_rc = mqtt_wss_connect(client, mqtt_url.host, mqtt_url.port, &mqtt_conn_params, ssl_flags, &proxy_conf, &fallback_ipv4);
763 url_t_destroy(&mqtt_url);
764
765 freez((char*)mqtt_conn_params.clientid);
766 freez((char*)mqtt_conn_params.password);
767 freez((char*)mqtt_conn_params.username);
768 #endif
769
770 freez((char*)mqtt_conn_params.will_msg);
771 freez((char*)proxy_conf.host);
772 freez((char*)proxy_conf.username);
773 char *proxy_password = (char *)proxy_conf.password;
774 aclk_sensitive_free(&proxy_password);
775
776 if (!mqtt_rc) {
777 last_conn_time_mqtt = now_realtime_sec();
778 nd_log(NDLS_DAEMON, NDLP_INFO, "ACLK: connection successfully established");
779 aclk_status_set(ACLK_STATUS_CONNECTED);
780 nd_log(NDLS_ACCESS, NDLP_INFO, "ACLK CONNECTED");
781 mqtt_connected_actions(client);
782 fallback_ipv4 = false;
783 return 0;
784 }
785
786 error_report("ACLK: connection failed");
787 }
788
789 aclk_status_set(ACLK_STATUS_DISABLED);
790 return 1;
791 }
792
793 /**
794 * Main agent cloud link thread
795 *
796 * This thread will simply call the main event loop that handles
797 * pending requests - both inbound and outbound
798 *
799 * @param ptr is a pointer to the netdata_static_thread structure.
800 *
801 * @return It always returns NULL
802 */
803 void aclk_main(void *ptr)
804 {
805 struct netdata_static_thread *static_thread = ptr;
806
807 worker_register("ACLK");
808 worker_register_job_name(WORKER_ACLK_WAIT_CLAIMING, "wait claim");
809 worker_register_job_name(WORKER_ACLK_CONNECT, "connect");
810 worker_register_job_name(WORKER_ACLK_NODE_UPDATE, "node update");
811 worker_register_job_name(WORKER_ACLK_HANDLE_CONNECTION, "handle connection");
812 worker_register_job_name(WORKER_ACLK_DISCONNECTED, "disconnected");
813 worker_register_job_name(WORKER_ACLK_CMD_DISCONNECT, "cmd disconnect");
814 worker_register_job_name(WORKER_ACLK_CMD_TIMEOUT, "cmd timeout");
815 worker_register_job_name(WORKER_ACLK_CMD_RELOAD_CONF, "cmd reload");
816 worker_register_job_name(WORKER_ACLK_CMD_UNKNOWN, "cmd unknown");
817 worker_register_job_name(WORKER_ACLK_SENT_PING, "sent ping");
818 worker_register_job_name(WORKER_ACLK_POLL_ERROR, "poll error");
819 worker_register_job_name(WORKER_ACLK_POLL_OK, "poll ok");
820 worker_register_job_name(WORKER_ACLK_RX, "rx");
821 worker_register_job_name(WORKER_ACLK_RX_ERROR, "rx error");
822 worker_register_job_name(WORKER_ACLK_PROCESS_RAW, "p-raw");
823 worker_register_job_name(WORKER_ACLK_PROCESS_HANDSHAKE, "p-handshake");
824 worker_register_job_name(WORKER_ACLK_PROCESS_ESTABLISHED, "p-established");
825 worker_register_job_name(WORKER_ACLK_PROCESS_ERROR, "p-error");
826 worker_register_job_name(WORKER_ACLK_PROCESS_CLOSED_GRACEFULLY, "p-closed");
827 worker_register_job_name(WORKER_ACLK_PROCESS_UNKNOWN, "p-unknown");
828 worker_register_job_name(WORKER_ACLK_HANDLE_MQTT_INTERNAL, "mqtt internal");
829 worker_register_job_name(WORKER_ACLK_TX, "tx");
830 worker_register_job_name(WORKER_ACLK_TX_ERROR, "tx error");
831 worker_register_job_name(WORKER_ACLK_TRY_SEND_ALL, "try send all");
832 worker_register_job_name(WORKER_ACLK_HANDLE_INCOMING, "handle incoming");
833 worker_register_job_name(WORKER_ACLK_CPT_CONNACK, "cpt connack");
834 worker_register_job_name(WORKER_ACLK_CPT_PUBACK, "cpt puback");
835 worker_register_job_name(WORKER_ACLK_CPT_PINGRESP, "cpt pingresp");
836 worker_register_job_name(WORKER_ACLK_CPT_SUBACK, "cpt suback");
837 worker_register_job_name(WORKER_ACLK_CPT_PUBLISH, "cpt publish");
838 worker_register_job_name(WORKER_ACLK_CPT_DISCONNECT, "cpt disconnect");
839 worker_register_job_name(WORKER_ACLK_CPT_UNKNOWN, "cpt unknown");
840 worker_register_job_name(WORKER_ACLK_SEND_FRAGMENT, "send fragment");
841 worker_register_job_name(WORKER_ACLK_MSG_CALLBACK, "msg callback");
842 worker_register_job_name(WORKER_ACLK_WAITING_TO_CONNECT, "conn wait");
843 worker_register_job_name(WORKER_ACLK_RECLAIM_MEMORY, "reclaim");
844 worker_register_job_name(WORKER_ACLK_BUFFER_COMPACT, "compact");
845
846 aclk_init_rx_msg_handlers();
847
848 worker_is_busy(WORKER_ACLK_WAIT_CLAIMING);
849 if (wait_till_agent_claim_ready())
850 goto exit;
851
852 if (!((mqttwss_client = mqtt_wss_new(msg_callback, puback_callback)))) {
853 netdata_log_error("ACLK: Couldn't initialize MQTT_WSS network library");
854 goto exit;
855 }
856
857 #ifdef MQTT_WSS_DEBUG
858 size_t default_ssl_log_filename_size = strlen(netdata_configured_log_dir) + strlen(DEFAULT_SSKEYLOGFILE_NAME) + 2;
859 char *default_ssl_log_filename = mallocz(default_ssl_log_filename_size);
860 snprintfz(default_ssl_log_filename, default_ssl_log_filename_size, "%s/%s", netdata_configured_log_dir, DEFAULT_SSKEYLOGFILE_NAME);
861 ssl_log_filename = inicfg_get(&netdata_config, CONFIG_SECTION_CLOUD, "aclk ssl keylog file", default_ssl_log_filename);
862 freez(default_ssl_log_filename);
863 if (ssl_log_filename) {
864 error_report("SSLKEYLOGFILE active (path:\"%s\")!", ssl_log_filename);
865 mqtt_wss_set_SSL_CTX_keylog_cb(mqttwss_client, aclk_ssl_keylog_cb);
866 }
867 #endif
868
869 // Enable MQTT buffer growth if necessary
870 size_t max_buf_size = MQTT_DEFAULT_MAX_BUF_SIZE;
871 mqtt_wss_set_max_buf_size(mqttwss_client, max_buf_size);
872
873 // Keep reconnecting and talking until our time has come
874 // and the Grim Reaper (exit_initiated) calls
875 netdata_log_info("ACLK: Starting ACLK query event loop");
876 aclk_mqtt_client_set(mqttwss_client);
877 bool client_to_reset = true;
878 do {
879 worker_is_busy(WORKER_ACLK_CONNECT);
880 if (aclk_attempt_to_connect(mqttwss_client))
881 goto exit_full;
882
883 if (schedule_node_update) {
884 worker_is_busy(WORKER_ACLK_NODE_UPDATE);
885 schedule_node_state_update(localhost, 10);
886 schedule_node_update = false;
887 }
888
889 worker_is_busy(WORKER_ACLK_HANDLE_CONNECTION);
890 if (handle_connection(mqttwss_client)) {
891 worker_is_busy(WORKER_ACLK_DISCONNECTED);
892 last_disconnect_time = now_realtime_sec();
893 aclk_set_disconnected();
894 nd_log(NDLS_ACCESS, NDLP_WARNING, "ACLK DISCONNECTED");
895 }
896 } while (service_running(SERVICE_ACLK));
897 aclk_mqtt_client_reset();
898 // No need to reset the client again when exiting
899 client_to_reset = false;
900 worker_is_busy(WORKER_ACLK_DISCONNECTED);
901 aclk_graceful_disconnect(mqttwss_client);
902
903 #ifdef MQTT_WSS_DEBUG
904 if (ssl_log_file)
905 fclose(ssl_log_file);
906 #endif
907
908 exit_full:
909 free_topic_cache();
910 if (client_to_reset)
911 aclk_mqtt_client_reset();
912 mqtt_wss_destroy(mqttwss_client);
913 exit:
914 if (aclk_env) {
915 aclk_env_t_destroy(aclk_env);
916 freez(aclk_env);
917 }
918 static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
919 }
920
921 bool aclk_host_state_update_auto(RRDHOST *host) {
922 int live;
923 switch(rrdhost_ingestion_status(host)) {
924 default:
925 case RRDHOST_INGEST_STATUS_ARCHIVED:
926 case RRDHOST_INGEST_STATUS_INITIALIZING:
927 case RRDHOST_INGEST_STATUS_OFFLINE:
928 live = 0;
929 break;
930
931 case RRDHOST_INGEST_STATUS_REPLICATING:
932 // receiving replication
933 // no need to send this to NC
934 return false;
935
936 case RRDHOST_INGEST_STATUS_ONLINE:
937 // currently collecting data
938 live = 1;
939 break;
940 }
941 aclk_host_state_update(host, live, 1, NULL);
942 return true;
943 }
944
945 void aclk_create_node_instance_job(RRDHOST *host)
946 {
947 if (unlikely(!host))
948 return;
949
950 CLAIM_ID claim_id = claim_id_get();
951 if (!claim_id_is_set(claim_id))
952 return;
953
954 aclk_query_t *query = aclk_query_new(REGISTER_NODE);
955 int32_t hops = rrdhost_ingestion_hops(host);
956 node_instance_creation_t node_instance_creation = {
957 .hops = hops,
958 .hostname = rrdhost_hostname(host),
959 .machine_guid = host->machine_guid,
960 .claim_id = claim_id.str
961 };
962
963 query->data.bin_payload.topic = ACLK_TOPICID_CREATE_NODE;
964 query->data.bin_payload.msg_name = "CreateNodeInstance";
965 query->data.bin_payload.payload = generate_node_instance_creation(&query->data.bin_payload.size, &node_instance_creation);
966
967 nd_log_daemon(NDLP_DEBUG, "Queuing registration for host=%s, hops=%d", host->machine_guid, hops);
968
969 aclk_add_job(query);
970 }
971
972 void aclk_update_node_instance_job(RRDHOST *host, int live, int queryable, struct aclk_sync_completion *sync_completion)
973 {
974 if (unlikely(!host)) {
975 if (sync_completion)
976 aclk_sync_completion_signal(sync_completion);
977 return;
978 }
979
980 CLAIM_ID claim_id = claim_id_get();
981 if (!claim_id_is_set(claim_id)) {
982 if (sync_completion)
983 aclk_sync_completion_signal(sync_completion);
984 return;
985 }
986
987 aclk_query_t *query = aclk_query_new(NODE_STATE_UPDATE);
988 query->sync_completion = sync_completion;
989
990 int32_t hops = rrdhost_ingestion_hops(host);
991 node_instance_connection_t node_state_update = {
992 .claim_id = claim_id.str,
993 .hops = hops,
994 .live = live,
995 .queryable = queryable,
996 .session_id = aclk_session_newarch};
997
998 char node_id[UUID_STR_LEN];
999 uuid_unparse_lower(host->node_id.uuid, node_id);
1000
1001 node_state_update.node_id = node_id;
1002 node_state_update.capabilities = aclk_get_node_instance_capas(host);
1003
1004 query->data.bin_payload.topic = ACLK_TOPICID_NODE_CONN;
1005 query->data.bin_payload.msg_name = "UpdateNodeInstanceConnection";
1006 query->data.bin_payload.payload = generate_node_instance_connection(&query->data.bin_payload.size, &node_state_update);
1007
1008 nd_log_daemon(
1009 NDLP_DEBUG,
1010 "Queuing status update for node=%s, live=%d, hops=%d, queryable=%d",
1011 (char *)node_state_update.node_id,
1012 live,
1013 hops,
1014 queryable);
1015
1016 freez((void *)node_state_update.capabilities);
1017 aclk_add_job(query);
1018 }
1019
1020 void aclk_host_state_update(RRDHOST *host, int live, int queryable, struct aclk_sync_completion *sync_completion)
1021 {
1022 if (!aclk_online()) {
1023 if (sync_completion)
1024 aclk_sync_completion_signal(sync_completion);
1025 return;
1026 }
1027
1028 if (uuid_is_null(host->node_id.uuid)) {
1029 aclk_create_node_instance_job(host);
1030 if (sync_completion)
1031 aclk_sync_completion_signal(sync_completion);
1032 }
1033 else
1034 aclk_update_node_instance_job(host, live, queryable, sync_completion);
1035 }
1036
1037 void aclk_send_node_instances()
1038 {
1039 RRDHOST *host;
1040 dfe_start_reentrant(rrdhost_root_index, host)
1041 {
1042 int live = rrdhost_ingestion_status(host) == RRDHOST_INGEST_STATUS_ONLINE ? 1 : 0;
1043 aclk_host_state_update(host, live, 1, NULL);
1044 }
1045 dfe_done(host);
1046 }
1047
1048 void aclk_send_bin_msg(char *msg, size_t msg_len, enum aclk_topics subtopic, const char *msgname)
1049 {
1050 aclk_send_bin_message_subtopic_pid(mqttwss_client, msg, msg_len, subtopic, msgname);
1051 }
1052
1053 static void fill_alert_status_for_host(BUFFER *wb, RRDHOST *host)
1054 {
1055 struct aclk_sync_cfg_t *aclk_host_config = __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE);
1056 if (!aclk_host_config)
1057 return;
1058
1059 buffer_sprintf(
1060 wb,
1061 "\n\t\tUpdates: %d"
1062 "\n\t\tCheckpoints: %d"
1063 "\n\t\tAlert count: %d"
1064 "\n\t\tAlert snapshot count: %d",
1065 aclk_host_config->stream_alerts,
1066 aclk_host_config->checkpoint_count,
1067 aclk_host_config->alert_count,
1068 aclk_host_config->snapshot_count);
1069 }
1070
1071
1072 extern usec_t publish_latency;
1073
1074 char *aclk_state(void)
1075 {
1076 BUFFER *wb = buffer_create(1024, &netdata_buffers_statistics.buffers_aclk);
1077 struct tm *tmptr, tmbuf;
1078 char *ret;
1079
1080 buffer_strcat(wb,
1081 "ACLK Available: Yes\n"
1082 "ACLK Version: 2\n"
1083 "Protocols Supported: Protobuf\n"
1084 );
1085 buffer_sprintf(wb, "Protocol Used: Protobuf\nMQTT Version: %d\nClaimed: ", 5);
1086
1087 CLAIM_ID claim_id = claim_id_get();
1088 if (!claim_id_is_set(claim_id))
1089 buffer_strcat(wb, "No\n");
1090 else {
1091 const char *cloud_base_url = cloud_config_url_get();
1092
1093 char proxy_display[512];
1094 aclk_proxy_get_full_display(proxy_display, sizeof(proxy_display));
1095
1096 usec_t latency = __atomic_load_n(&publish_latency, __ATOMIC_RELAXED);
1097 char latency_str[64];
1098 duration_snprintf(latency_str, sizeof(latency_str), (int64_t) latency, "us", true);
1099 buffer_sprintf(wb, "Yes\nClaimed Id: %s\nCloud URL: %s\nACLK Proxy: %s\nPublish Latency: %s\n", claim_id.str, cloud_base_url ? cloud_base_url : "null", proxy_display, latency_str);
1100 }
1101
1102 bool aclk_is_online = aclk_online();
1103
1104 struct mqtt_wss_stats aclk_stats;
1105 if (aclk_is_online)
1106 aclk_stats = aclk_statistics();
1107
1108 buffer_sprintf(wb, "Online: %s\nReconnect count: %d\nBanned By Cloud: %s\n", aclk_is_online ? "Yes" : "No", aclk_connection_counter > 0 ? (aclk_connection_counter - 1) : 0, aclk_disable_runtime ? "Yes" : "No");
1109 if (last_conn_time_mqtt && ((tmptr = localtime_r(&last_conn_time_mqtt, &tmbuf))) ) {
1110 char timebuf[26];
1111 strftime(timebuf, 26, "%Y-%m-%d %H:%M:%S", tmptr);
1112 buffer_sprintf(wb, "Last Connection Time: %s\n", timebuf);
1113 }
1114 if (last_conn_time_appl && ((tmptr = localtime_r(&last_conn_time_appl, &tmbuf))) ) {
1115 char timebuf[26];
1116 strftime(timebuf, 26, "%Y-%m-%d %H:%M:%S", tmptr);
1117 buffer_sprintf(wb, "Last Connection Time + %d PUBACKs received: %s\n", ACLK_PUBACKS_CONN_STABLE, timebuf);
1118 }
1119 if (last_disconnect_time && ((tmptr = localtime_r(&last_disconnect_time, &tmbuf))) ) {
1120 char timebuf[26];
1121 strftime(timebuf, 26, "%Y-%m-%d %H:%M:%S", tmptr);
1122 buffer_sprintf(wb, "Last Disconnect Time: %s\n", timebuf);
1123 }
1124 if (!aclk_connected && next_connection_attempt && ((tmptr = localtime_r(&next_connection_attempt, &tmbuf))) ) {
1125 char timebuf[26];
1126 strftime(timebuf, 26, "%Y-%m-%d %H:%M:%S", tmptr);
1127 buffer_sprintf(wb, "Next Connection Attempt At: %s\nLast Backoff: %.3f", timebuf, last_backoff_value);
1128 }
1129
1130 if (aclk_is_online) {
1131 buffer_sprintf(wb, "Received Cloud MQTT Messages: %d\nMQTT Messages Confirmed by Remote Broker (PUBACKs): %d\nPending PUBACKS: %d\n",
1132 aclk_rcvd_cloud_msgs, aclk_pubacks_per_conn, aclk_stats.mqtt.packets_waiting_puback);
1133
1134 RRDHOST *host;
1135 rrd_rdlock();
1136 rrdhost_foreach_read(host) {
1137 buffer_sprintf(wb, "\n\n> Node Instance for mGUID: \"%s\" hostname \"%s\"\n", host->machine_guid, rrdhost_hostname(host));
1138
1139 buffer_strcat(wb, "\tClaimed ID: ");
1140 claim_id = rrdhost_claim_id_get(host);
1141 if(claim_id_is_set(claim_id))
1142 buffer_strcat(wb, claim_id.str);
1143 else
1144 buffer_strcat(wb, "null");
1145
1146 if (UUIDiszero(host->node_id))
1147 buffer_strcat(wb, "\n\tNode ID: null\n");
1148 else {
1149 char node_id_str[UUID_STR_LEN];
1150 uuid_unparse_lower(host->node_id.uuid, node_id_str);
1151 buffer_sprintf(wb, "\n\tNode ID: %s\n", node_id_str);
1152 }
1153
1154 buffer_sprintf(wb, "\tStreaming Hops: %d\n\tRelationship: %s",
1155 rrdhost_ingestion_hops(host),
1156 host == localhost ? "self" : "child");
1157
1158 if (host != localhost)
1159 buffer_sprintf(wb, "\n\tStreaming Connection Live: %s", host->receiver ? "true" : "false");
1160
1161 buffer_strcat(wb, "\n\tAlert Streaming Status:");
1162 fill_alert_status_for_host(wb, host);
1163 }
1164 rrd_rdunlock();
1165 }
1166
1167 ret = strdupz(buffer_tostring(wb));
1168 buffer_free(wb);
1169 return ret;
1170 }
1171
1172 static void fill_alert_status_for_host_json(json_object *obj, RRDHOST *host)
1173 {
1174 struct aclk_sync_cfg_t *aclk_host_config = __atomic_load_n(&host->aclk_host_config, __ATOMIC_ACQUIRE);
1175 if (!aclk_host_config)
1176 return;
1177
1178 json_object *tmp = json_object_new_int(aclk_host_config->stream_alerts);
1179 json_object_object_add(obj, "updates", tmp);
1180
1181 tmp = json_object_new_int(aclk_host_config->checkpoint_count);
1182 json_object_object_add(obj, "checkpoint-count", tmp);
1183
1184 tmp = json_object_new_int(aclk_host_config->alert_count);
1185 json_object_object_add(obj, "alert-count", tmp);
1186
1187 tmp = json_object_new_int(aclk_host_config->snapshot_count);
1188 json_object_object_add(obj, "alert-snapshot-count", tmp);
1189 tmp = json_object_new_int64(calculate_node_alert_version(aclk_host_config->host));
1190 json_object_object_add(obj, "alert-version", tmp);
1191 }
1192
1193 static json_object *timestamp_to_json(const time_t *t)
1194 {
1195 struct tm *tmptr, tmbuf;
1196 if (*t && ((tmptr = gmtime_r(t, &tmbuf))) ) {
1197 char timebuf[26];
1198 strftime(timebuf, 26, "%Y-%m-%d %H:%M:%S", tmptr);
1199 return json_object_new_string(timebuf);
1200 }
1201 return NULL;
1202 }
1203
1204 char *aclk_state_json(void)
1205 {
1206
1207 bool aclk_is_online = aclk_online();
1208
1209 struct mqtt_wss_stats aclk_stats;
1210
1211 if (aclk_is_online)
1212 aclk_stats = aclk_statistics();
1213 else
1214 memset(&aclk_stats, 0, sizeof(aclk_stats));
1215
1216 json_object *tmp, *grp, *msg = json_object_new_object();
1217
1218 tmp = json_object_new_boolean(1);
1219 json_object_object_add(msg, "aclk-available", tmp);
1220
1221 tmp = json_object_new_int(2);
1222 json_object_object_add(msg, "aclk-version", tmp);
1223
1224 grp = json_object_new_array();
1225 tmp = json_object_new_string("Protobuf");
1226 json_object_array_add(grp, tmp);
1227 json_object_object_add(msg, "protocols-supported", grp);
1228
1229 CLAIM_ID claim_id = claim_id_get();
1230 tmp = json_object_new_boolean(claim_id_is_set(claim_id));
1231 json_object_object_add(msg, "agent-claimed", tmp);
1232
1233 if (claim_id_is_set(claim_id))
1234 tmp = json_object_new_string(claim_id.str);
1235 else
1236 tmp = NULL;
1237 json_object_object_add(msg, "claimed-id", tmp);
1238
1239 const char *cloud_base_url = cloud_config_url_get();
1240 tmp = cloud_base_url ? json_object_new_string(cloud_base_url) : NULL;
1241 json_object_object_add(msg, "cloud-url", tmp);
1242
1243 {
1244 char proxy_display[512];
1245 aclk_proxy_get_full_display(proxy_display, sizeof(proxy_display));
1246 tmp = json_object_new_string(proxy_display);
1247 json_object_object_add(msg, "aclk_proxy", tmp);
1248 }
1249
1250 usec_t latency = __atomic_load_n(&publish_latency, __ATOMIC_RELAXED);
1251 tmp =json_object_new_int64((int64_t) latency);
1252 json_object_object_add(msg, "publish_latency_us", tmp);
1253
1254 tmp = json_object_new_boolean(aclk_is_online);
1255 json_object_object_add(msg, "online", tmp);
1256
1257 tmp = json_object_new_string("Protobuf");
1258 json_object_object_add(msg, "used-cloud-protocol", tmp);
1259
1260 tmp = json_object_new_int(5);
1261 json_object_object_add(msg, "mqtt-version", tmp);
1262
1263 tmp = json_object_new_int(aclk_rcvd_cloud_msgs);
1264 json_object_object_add(msg, "received-app-layer-msgs", tmp);
1265
1266 tmp = json_object_new_int(aclk_pubacks_per_conn);
1267 json_object_object_add(msg, "received-mqtt-pubacks", tmp);
1268
1269 tmp = json_object_new_int((int32_t) aclk_stats.mqtt.packets_waiting_puback);
1270 json_object_object_add(msg, "pending-mqtt-pubacks", tmp);
1271
1272 tmp = json_object_new_int(aclk_connection_counter > 0 ? (aclk_connection_counter - 1) : 0);
1273 json_object_object_add(msg, "reconnect-count", tmp);
1274
1275 json_object_object_add(msg, "last-connect-time-utc", timestamp_to_json(&last_conn_time_mqtt));
1276 json_object_object_add(msg, "last-connect-time-puback-utc", timestamp_to_json(&last_conn_time_appl));
1277 json_object_object_add(msg, "last-disconnect-time-utc", timestamp_to_json(&last_disconnect_time));
1278 json_object_object_add(msg, "next-connection-attempt-utc", !aclk_is_online ? timestamp_to_json(&next_connection_attempt) : NULL);
1279 tmp = NULL;
1280 if (!aclk_online() && last_backoff_value)
1281 tmp = json_object_new_double(last_backoff_value);
1282 json_object_object_add(msg, "last-backoff-value", tmp);
1283
1284 tmp = json_object_new_boolean(aclk_disable_runtime);
1285 json_object_object_add(msg, "banned-by-cloud", tmp);
1286
1287 grp = json_object_new_array();
1288
1289 RRDHOST *host;
1290 rrd_rdlock();
1291 rrdhost_foreach_read(host) {
1292 json_object *nodeinstance = json_object_new_object();
1293
1294 tmp = json_object_new_string(rrdhost_hostname(host));
1295 json_object_object_add(nodeinstance, "hostname", tmp);
1296
1297 tmp = json_object_new_string(host->machine_guid);
1298 json_object_object_add(nodeinstance, "mguid", tmp);
1299
1300 claim_id = rrdhost_claim_id_get(host);
1301 if(claim_id_is_set(claim_id)) {
1302 tmp = json_object_new_string(claim_id.str);
1303 json_object_object_add(nodeinstance, "claimed_id", tmp);
1304 } else
1305 json_object_object_add(nodeinstance, "claimed_id", NULL);
1306
1307 if (UUIDiszero(host->node_id)) {
1308 json_object_object_add(nodeinstance, "node-id", NULL);
1309 } else {
1310 char node_id_str[UUID_STR_LEN];
1311 uuid_unparse_lower(host->node_id.uuid, node_id_str);
1312 tmp = json_object_new_string(node_id_str);
1313 json_object_object_add(nodeinstance, "node-id", tmp);
1314 }
1315
1316 tmp = json_object_new_int(rrdhost_ingestion_hops(host));
1317 json_object_object_add(nodeinstance, "streaming-hops", tmp);
1318
1319 tmp = json_object_new_string(host == localhost ? "self" : "child");
1320 json_object_object_add(nodeinstance, "relationship", tmp);
1321
1322 tmp = json_object_new_boolean((host->receiver || host == localhost));
1323 json_object_object_add(nodeinstance, "streaming-online", tmp);
1324
1325 tmp = json_object_new_object();
1326 fill_alert_status_for_host_json(tmp, host);
1327 json_object_object_add(nodeinstance, "alert-sync-status", tmp);
1328
1329 json_object_array_add(grp, nodeinstance);
1330 }
1331 rrd_rdunlock();
1332 json_object_object_add(msg, "node-instances", grp);
1333
1334 char *str = strdupz(json_object_to_json_string_ext(msg, JSON_C_TO_STRING_PLAIN));
1335 json_object_put(msg);
1336 return str;
1337 }
1338
1339 void add_aclk_host_labels(void) {
1340 RRDLABELS *labels = localhost->rrdlabels;
1341
1342 rrdlabels_add(labels, "_aclk_available", "true", RRDLABEL_SRC_AUTO|RRDLABEL_SRC_ACLK);
1343 ACLK_PROXY_TYPE aclk_proxy;
1344 char *proxy_str;
1345 aclk_get_proxy(&aclk_proxy, false);
1346
1347 switch(aclk_proxy) {
1348 case PROXY_TYPE_SOCKS5:
1349 proxy_str = "SOCKS5";
1350 break;
1351 case PROXY_TYPE_SOCKS5H:
1352 proxy_str = "SOCKS5H";
1353 break;
1354 case PROXY_TYPE_HTTP:
1355 proxy_str = "HTTP";
1356 break;
1357 default:
1358 proxy_str = "none";
1359 break;
1360 }
1361
1362 rrdlabels_add(labels, "_mqtt_version", "5", RRDLABEL_SRC_AUTO);
1363 rrdlabels_add(labels, "_aclk_proxy", proxy_str, RRDLABEL_SRC_AUTO);
1364 rrdlabels_add(labels, "_aclk_ng_new_cloud_protocol", "true", RRDLABEL_SRC_AUTO|RRDLABEL_SRC_ACLK);
1365 }