master
c 508 lines 16.9 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "aclk_rx_msgs.h"
4
5 #include "aclk_query_queue.h"
6 #include "aclk.h"
7 #include "aclk_capas.h"
8 #include "aclk_query.h"
9 #include "mqtt_websockets/aclk_mqtt_workers.h"
10
11 #include "schema-wrappers/proto_2_json.h"
12
13 #define ACLK_V2_PAYLOAD_SEPARATOR "\x0D\x0A\x0D\x0A"
14
15 #define ACLK_V_COMPRESSION 2
16
17 struct aclk_request {
18 bool has_type;
19 bool is_http;
20 // Heap-allocated string fields below are owned by the local instance in
21 // aclk_handle_cloud_cmd_message. On the v2 success path, msg_id and
22 // callback_topic are transferred into the query and released by
23 // aclk_query_free; payload is not consumed by v2 (the HTTP body is
24 // re-derived from the raw frame) and must be freed by the caller on both
25 // success and error paths. Any new owned field added here must extend
26 // both cleanup paths to preserve this invariant.
27 char *msg_id;
28 char *callback_topic;
29 char *payload;
30 int version;
31 int timeout;
32 int min_version;
33 int max_version;
34 };
35
36 static int cloud_to_agent_parse(JSON_ENTRY *e)
37 {
38 struct aclk_request *data = e->callback_data;
39
40 switch (e->type) {
41 case JSON_OBJECT:
42 case JSON_ARRAY:
43 break;
44 case JSON_STRING:
45 if (!strcmp(e->name, "msg-id")) {
46 data->msg_id = e->data.string ? strdupz(e->data.string) : NULL;
47 break;
48 }
49 if (!strcmp(e->name, "type")) {
50 data->has_type = true;
51 data->is_http = (e->data.string && (strcmp(e->data.string, "http") == 0));
52 break;
53 }
54 if (!strcmp(e->name, "callback-topic")) {
55 data->callback_topic = e->data.string ? strdupz(e->data.string) : NULL;
56 break;
57 }
58 if (!strcmp(e->name, "payload")) {
59 if (likely(e->data.string)) {
60 size_t len = strlen(e->data.string);
61 data->payload = mallocz(len+1);
62 if (!url_decode_r(data->payload, e->data.string, len + 1))
63 strcpy(data->payload, e->data.string);
64 }
65 break;
66 }
67 break;
68 case JSON_NUMBER:
69 if (!strcmp(e->name, "version")) {
70 data->version = (int)e->data.number;
71 break;
72 }
73 if (!strcmp(e->name, "timeout")) {
74 data->timeout = (int)e->data.number;
75 break;
76 }
77 if (!strcmp(e->name, "min-version")) {
78 data->min_version = (int)e->data.number;
79 break;
80 }
81 if (!strcmp(e->name, "max-version")) {
82 data->max_version = (int)e->data.number;
83 break;
84 }
85
86 break;
87
88 case JSON_BOOLEAN:
89 break;
90
91 case JSON_NULL:
92 break;
93 }
94 return 0;
95 }
96
97 static inline int aclk_extract_v2_data(char *payload, char **data)
98 {
99 char* ptr = strstr(payload, ACLK_V2_PAYLOAD_SEPARATOR);
100 if(!ptr)
101 return 1;
102 ptr += strlen(ACLK_V2_PAYLOAD_SEPARATOR);
103 *data = strdupz(ptr);
104 return 0;
105 }
106
107 static inline int aclk_v2_payload_get_query(const char *payload, char **query_url)
108 {
109 const char *start, *end;
110
111 if(strncmp(payload, "GET /", 5) == 0 || strncmp(payload, "PUT /", 5) == 0)
112 start = payload + 4;
113 else if(strncmp(payload, "POST /", 6) == 0)
114 start = payload + 5;
115 else if(strncmp(payload, "DELETE /", 8) == 0)
116 start = payload + 7;
117 else {
118 errno_clear();
119 netdata_log_error("Only accepting requests that start with GET, POST, PUT, DELETE from CLOUD.");
120 return 1;
121 }
122
123 if(!(end = strstr(payload, HTTP_1_1 HTTP_ENDL))) {
124 errno_clear();
125 netdata_log_error("Doesn't look like HTTP GET request.");
126 return 1;
127 }
128
129 *query_url = mallocz((end - start) + 1);
130 strncpyz(*query_url, start, end - start);
131
132 return 0;
133 }
134
135 static int aclk_handle_cloud_http_request_v2(struct aclk_request *cloud_to_agent, char *raw_payload)
136 {
137 errno_clear();
138 if (cloud_to_agent->version < ACLK_V_COMPRESSION) {
139 netdata_log_error(
140 "This handler cannot reply to request with version older than %d, received %d.",
141 ACLK_V_COMPRESSION,
142 cloud_to_agent->version);
143 return 1;
144 }
145
146 aclk_query_t *query = aclk_query_new(HTTP_API_V2);
147
148 if (unlikely(aclk_extract_v2_data(raw_payload, &query->data.http_api_v2.payload))) {
149 netdata_log_error("Error extracting payload expected after the JSON dictionary.");
150 goto error;
151 }
152
153 if (unlikely(aclk_v2_payload_get_query(query->data.http_api_v2.payload, &query->dedup_id))) {
154 netdata_log_error("Could not extract payload from query");
155 goto error;
156 }
157
158 if (unlikely(!cloud_to_agent->callback_topic)) {
159 netdata_log_error("Missing callback_topic");
160 goto error;
161 }
162
163 if (unlikely(!cloud_to_agent->msg_id)) {
164 netdata_log_error("Missing msg_id");
165 goto error;
166 }
167
168 // aclk_queue_query takes ownership of data pointer
169 query->callback_topic = cloud_to_agent->callback_topic;
170 query->timeout = cloud_to_agent->timeout;
171 // for clarity and code readability as when we process the request
172 // it would be strange to get URL from `dedup_id`
173 query->data.http_api_v2.query = query->dedup_id;
174 query->msg_id = cloud_to_agent->msg_id;
175 aclk_execute_query(query);
176 return 0;
177
178 error:
179 aclk_query_free(query);
180 return 1;
181 }
182
183 int aclk_handle_cloud_cmd_message(char *payload)
184 {
185 struct aclk_request cloud_to_agent;
186 memset(&cloud_to_agent, 0, sizeof(struct aclk_request));
187
188 if (unlikely(!payload)) {
189 error_report("ACLK incoming 'cmd' message is empty");
190 return 1;
191 }
192
193 netdata_log_debug(D_ACLK, "ACLK incoming 'cmd' message (%s)", payload);
194
195 int rc = json_parse(payload, &cloud_to_agent, cloud_to_agent_parse);
196
197 if (unlikely(rc != JSON_OK)) {
198 error_report("Malformed json request (%s)", payload);
199 goto err_cleanup;
200 }
201
202 if (!cloud_to_agent.has_type) {
203 error_report("Cloud message is missing compulsory key \"type\"");
204 goto err_cleanup;
205 }
206
207 // Originally we were expecting to have multiple types of 'cmd' message,
208 // but after the new protocol was designed we will ever only have 'http'
209 if (!cloud_to_agent.is_http) {
210 error_report("Only 'http' cmd message is supported");
211 goto err_cleanup;
212 }
213
214 if (likely(!aclk_handle_cloud_http_request_v2(&cloud_to_agent, payload))) {
215 // aclk_handle_cloud_http_request_v2 takes ownership of msg_id and
216 // callback_topic on success. The JSON-parsed payload field is not
217 // consumed by v2 (the HTTP body comes from the raw frame), so free
218 // it here to avoid leaking when the cmd JSON included a "payload" key.
219 freez(cloud_to_agent.payload);
220 return 0;
221 }
222
223 err_cleanup:
224 if (cloud_to_agent.payload)
225 freez(cloud_to_agent.payload);
226 if (cloud_to_agent.msg_id)
227 freez(cloud_to_agent.msg_id);
228 if (cloud_to_agent.callback_topic)
229 freez(cloud_to_agent.callback_topic);
230
231 return 1;
232 }
233
234 typedef uint32_t simple_hash_t;
235 typedef int(*rx_msg_handler)(const char *msg, size_t msg_len);
236
237 int handle_old_proto_cmd(const char *msg, size_t msg_len)
238 {
239 // msg is binary payload in all other cases
240 // however in this message from old legacy cloud
241 // we have to convert it to C string
242 char *str = mallocz(msg_len+1);
243 memcpy(str, msg, msg_len);
244 str[msg_len] = 0;
245 int rc = aclk_handle_cloud_cmd_message(str);
246 freez(str);
247 return rc;
248 }
249
250 int create_node_instance_result(const char *msg, size_t msg_len)
251 {
252 node_instance_creation_result_t res = parse_create_node_instance_result(msg, msg_len);
253 if (!res.machine_guid || !res.node_id) {
254 error_report("Error parsing CreateNodeInstanceResult");
255 freez(res.machine_guid);
256 freez(res.node_id);
257 return 1;
258 }
259
260 netdata_log_debug(D_ACLK, "CreateNodeInstanceResult: guid:%s nodeid:%s", res.machine_guid, res.node_id);
261
262 aclk_query_t *query = aclk_query_new(CREATE_NODE_INSTANCE);
263
264 query->data.node_id = res.node_id; // Will be freed on query free
265 query->machine_guid = res.machine_guid; // Will be freed on query free
266 aclk_add_job(query);
267 return 0;
268 }
269
270 int send_node_instances(const char *msg, size_t msg_len)
271 {
272 UNUSED(msg);
273 UNUSED(msg_len);
274 aclk_query_t *query = aclk_query_new(SEND_NODE_INSTANCES);
275 aclk_add_job(query);
276 return 0;
277 }
278
279 int stream_charts_and_dimensions(const char *msg, size_t msg_len)
280 {
281 UNUSED(msg);
282 UNUSED(msg_len);
283 error_report("Received obsolete StreamChartsAndDimensions msg");
284 return 0;
285 }
286
287 int charts_and_dimensions_ack(const char *msg, size_t msg_len)
288 {
289 UNUSED(msg);
290 UNUSED(msg_len);
291 error_report("Received obsolete StreamChartsAndDimensionsAck msg");
292 return 0;
293 }
294
295 int update_chart_configs(const char *msg, size_t msg_len)
296 {
297 UNUSED(msg);
298 UNUSED(msg_len);
299 error_report("Received obsolete UpdateChartConfigs msg");
300 return 0;
301 }
302
303 int start_alarm_streaming(const char *msg, size_t msg_len)
304 {
305 struct start_alarm_streaming res = parse_start_alarm_streaming(msg, msg_len);
306 if (!res.node_id) {
307 netdata_log_error("Error parsing StartAlarmStreaming");
308 return 1;
309 }
310 aclk_query_t *query = aclk_query_new(ALERT_START_STREAMING);
311 query->data.node_id = res.node_id; // Will be freed on query free
312 query->version = res.version;
313 aclk_add_job(query);
314 return 0;
315 }
316
317 int send_alarm_checkpoint(const char *msg, size_t msg_len)
318 {
319 struct send_alarm_checkpoint sac = parse_send_alarm_checkpoint(msg, msg_len);
320 if (!sac.node_id || !sac.claim_id) {
321 netdata_log_error("Error parsing SendAlarmCheckpoint");
322 freez(sac.node_id);
323 freez(sac.claim_id);
324 return 1;
325 }
326 aclk_query_t *query = aclk_query_new(ALERT_CHECKPOINT);
327 query->data.node_id = sac.node_id; // Will be freed on query free
328 query->claim_id = sac.claim_id;
329 query->version = sac.version;
330 aclk_add_job(query);
331 return 0;
332 }
333
334 int send_alarm_configuration(const char *msg, size_t msg_len)
335 {
336 char *config_hash = parse_send_alarm_configuration(msg, msg_len);
337 if (!config_hash || !*config_hash) {
338 netdata_log_error("Error parsing SendAlarmConfiguration");
339 freez(config_hash);
340 return 1;
341 }
342 aclk_send_alert_configuration(config_hash);
343 freez(config_hash);
344 return 0;
345 }
346
347 int send_alarm_snapshot(const char *msg, size_t msg_len)
348 {
349 struct send_alarm_snapshot *sas = parse_send_alarm_snapshot(msg, msg_len);
350 if (!sas->node_id || !sas->claim_id || !sas->snapshot_uuid) {
351 netdata_log_error("Error parsing SendAlarmSnapshot");
352 destroy_send_alarm_snapshot(sas);
353 return 1;
354 }
355 aclk_query_t *query = aclk_query_new(ALERT_CHECKPOINT);
356 query->data.node_id = sas->node_id; // Will be freed on query free
357 query->claim_id = sas->claim_id; // Will be freed on query free
358 query->version = 0; // force snapshot
359 aclk_add_job(query);
360 destroy_send_alarm_snapshot(sas);
361 return 0;
362 }
363
364 int handle_disconnect_req(const char *msg, size_t msg_len)
365 {
366 struct disconnect_cmd *cmd = parse_disconnect_cmd(msg, msg_len);
367 if (!cmd)
368 return 1;
369 if (cmd->permaban) {
370 netdata_log_error("Cloud Banned This Agent!");
371 aclk_disable_runtime = 1;
372 }
373 netdata_log_info("Cloud requested disconnect (EC=%u, \"%s\")", (unsigned int)cmd->error_code, cmd->error_description);
374 if (cmd->reconnect_after_s > 0) {
375 aclk_block_until = now_monotonic_sec() + cmd->reconnect_after_s;
376 netdata_log_info(
377 "Cloud asks not to reconnect for %u seconds. We shall honor that request",
378 (unsigned int)cmd->reconnect_after_s);
379 }
380 disconnect_req = ACLK_CLOUD_DISCONNECT;
381 freez(cmd->error_description);
382 freez(cmd);
383 return 0;
384 }
385
386 int contexts_checkpoint(const char *msg, size_t msg_len)
387 {
388 aclk_ctx_based = 1;
389
390 struct ctxs_checkpoint *cmd = parse_ctxs_checkpoint(msg, msg_len);
391 if (!cmd)
392 return 1;
393
394 aclk_query_t *query = aclk_query_new(CTX_CHECKPOINT);
395 query->data.payload = cmd;
396 aclk_add_job(query);
397 return 0;
398 }
399
400 int stop_streaming_contexts(const char *msg, size_t msg_len)
401 {
402 if (!aclk_ctx_based) {
403 error_report("Received StopStreamingContexts message but context based communication was not enabled (Cloud violated the protocol). Ignoring message");
404 return 1;
405 }
406
407 struct stop_streaming_ctxs *cmd = parse_stop_streaming_ctxs(msg, msg_len);
408 if (!cmd)
409 return 1;
410
411 aclk_query_t *query = aclk_query_new(CTX_STOP_STREAMING);
412 query->data.payload = cmd;
413 aclk_add_job(query);
414 return 0;
415 }
416
417 int cancel_pending_req(const char *msg, size_t msg_len)
418 {
419 struct aclk_cancel_pending_req cmd = {.request_id = NULL, .trace_id = NULL};
420 if(parse_cancel_pending_req(msg, msg_len, &cmd)) {
421 error_report("Error parsing CancelPendingReq");
422 return 1;
423 }
424
425 nd_log(NDLS_ACCESS, NDLP_NOTICE, "ACLK CancelPendingRequest REQ: %s, cloud trace-id: %s", cmd.request_id, cmd.trace_id);
426
427 if (mark_pending_req_cancelled(cmd.request_id))
428 error_report("CancelPending Request for %s failed. No such pending request.", cmd.request_id);
429
430 free_cancel_pending_req(&cmd);
431 return 0;
432 }
433
434 typedef struct {
435 const char *name;
436 simple_hash_t name_hash;
437 rx_msg_handler fnc;
438 } new_cloud_rx_msg_t;
439
440 new_cloud_rx_msg_t rx_msgs[] = {
441 { .name = "cmd", .name_hash = 0, .fnc = handle_old_proto_cmd },
442 { .name = "CreateNodeInstanceResult", .name_hash = 0, .fnc = create_node_instance_result }, // async
443 { .name = "SendNodeInstances", .name_hash = 0, .fnc = send_node_instances }, // async
444 { .name = "StreamChartsAndDimensions", .name_hash = 0, .fnc = stream_charts_and_dimensions }, // unused
445 { .name = "ChartsAndDimensionsAck", .name_hash = 0, .fnc = charts_and_dimensions_ack }, // unused
446 { .name = "UpdateChartConfigs", .name_hash = 0, .fnc = update_chart_configs }, // unused
447 { .name = "StartAlarmStreaming", .name_hash = 0, .fnc = start_alarm_streaming }, // async
448 { .name = "SendAlarmCheckpoint", .name_hash = 0, .fnc = send_alarm_checkpoint }, // async
449 { .name = "SendAlarmConfiguration", .name_hash = 0, .fnc = send_alarm_configuration }, // async
450 { .name = "SendAlarmSnapshot", .name_hash = 0, .fnc = send_alarm_snapshot }, // shouldn't be used
451 { .name = "DisconnectReq", .name_hash = 0, .fnc = handle_disconnect_req },
452 { .name = "ContextsCheckpoint", .name_hash = 0, .fnc = contexts_checkpoint }, // async
453 { .name = "StopStreamingContexts", .name_hash = 0, .fnc = stop_streaming_contexts }, // async
454 { .name = "CancelPendingRequest", .name_hash = 0, .fnc = cancel_pending_req },
455 { .name = NULL, .name_hash = 0, .fnc = NULL },
456 };
457
458 new_cloud_rx_msg_t *find_rx_handler_by_hash(simple_hash_t hash)
459 {
460 // we can afford to not compare strings after hash match
461 // because we check for collisions at initialization in
462 // aclk_init_rx_msg_handlers()
463 for (int i = 0; rx_msgs[i].fnc; i++) {
464 if (rx_msgs[i].name_hash == hash)
465 return &rx_msgs[i];
466 }
467 return NULL;
468 }
469
470 void aclk_init_rx_msg_handlers(void)
471 {
472 int i;
473 for (i = 0; rx_msgs[i].fnc; i++) {
474 simple_hash_t hash = simple_hash(rx_msgs[i].name);
475 new_cloud_rx_msg_t *hdl = find_rx_handler_by_hash(hash);
476 if (unlikely(hdl)) {
477 // the list of message names changes only by changing
478 // the source code, therefore fatal is appropriate
479 fatal("Hash collision. Choose better hash. Added '%s' clashes with existing '%s'", rx_msgs[i].name, hdl->name);
480 }
481 rx_msgs[i].name_hash = hash;
482 }
483 }
484
485 void aclk_handle_new_cloud_msg(const char *message_type, const char *msg, size_t msg_len, const char *topic __maybe_unused)
486 {
487 new_cloud_rx_msg_t *msg_descriptor = find_rx_handler_by_hash(simple_hash(message_type));
488 netdata_log_debug(D_ACLK, "Got message named '%s' from cloud", message_type);
489 if (unlikely(!msg_descriptor)) {
490 netdata_log_error("Do not know how to handle message of type '%s'. Ignoring", message_type);
491 return;
492 }
493
494 if (aclklog_enabled) {
495 if (!strncmp(message_type, "cmd", strlen("cmd"))) {
496 log_aclk_message_bin(msg, msg_len, 0, topic, msg_descriptor->name);
497 } else {
498 char *json = protomsg_to_json(msg, msg_len, msg_descriptor->name);
499 log_aclk_message_bin(json, strlen(json), 0, topic, msg_descriptor->name);
500 freez(json);
501 }
502 }
503
504 if (msg_descriptor->fnc(msg, msg_len)) {
505 netdata_log_error("Error processing message of type '%s'", message_type);
506 return;
507 }
508 }