1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright(c) 2020 Intel Corporation 3 */ 4 5 #include <ctype.h> 6 #include <errno.h> 7 #include <stdlib.h> 8 #ifndef RTE_EXEC_ENV_WINDOWS 9 #include <unistd.h> 10 #include <pthread.h> 11 #include <sys/socket.h> 12 #include <sys/un.h> 13 #include <sys/stat.h> 14 #endif /* !RTE_EXEC_ENV_WINDOWS */ 15 16 /* we won't link against libbsd, so just always use DPDKs-specific strlcpy */ 17 #undef RTE_USE_LIBBSD 18 #include <rte_string_fns.h> 19 #include <rte_common.h> 20 #include <rte_spinlock.h> 21 #include <rte_log.h> 22 23 #include "rte_telemetry.h" 24 #include "telemetry_json.h" 25 #include "telemetry_data.h" 26 #include "telemetry_internal.h" 27 28 #define MAX_CMD_LEN 56 29 #define MAX_OUTPUT_LEN (1024 * 16) 30 #define MAX_CONNECTIONS 10 31 32 #ifndef RTE_EXEC_ENV_WINDOWS 33 static void * 34 client_handler(void *socket); 35 #endif /* !RTE_EXEC_ENV_WINDOWS */ 36 37 struct cmd_callback { 38 char cmd[MAX_CMD_LEN]; 39 telemetry_cb fn; 40 char help[RTE_TEL_MAX_STRING_LEN]; 41 }; 42 43 #ifndef RTE_EXEC_ENV_WINDOWS 44 struct socket { 45 int sock; 46 char path[sizeof(((struct sockaddr_un *)0)->sun_path)]; 47 handler fn; 48 RTE_ATOMIC(uint16_t) *num_clients; 49 }; 50 static struct socket v2_socket; /* socket for v2 telemetry */ 51 static struct socket v1_socket; /* socket for v1 telemetry */ 52 #endif /* !RTE_EXEC_ENV_WINDOWS */ 53 54 static const char *telemetry_version; /* save rte_version */ 55 static const char *socket_dir; /* runtime directory */ 56 static rte_cpuset_t *thread_cpuset; 57 58 RTE_LOG_REGISTER_DEFAULT(logtype, WARNING); 59 #define TMTY_LOG(l, ...) rte_log(RTE_LOG_ ## l, logtype, "TELEMETRY: " __VA_ARGS__) 60 61 /* list of command callbacks, with one command registered by default */ 62 static struct cmd_callback *callbacks; 63 static int num_callbacks; /* How many commands are registered */ 64 /* Used when accessing or modifying list of command callbacks */ 65 static rte_spinlock_t callback_sl = RTE_SPINLOCK_INITIALIZER; 66 #ifndef RTE_EXEC_ENV_WINDOWS 67 static RTE_ATOMIC(uint16_t) v2_clients; 68 #endif /* !RTE_EXEC_ENV_WINDOWS */ 69 70 int 71 rte_telemetry_register_cmd(const char *cmd, telemetry_cb fn, const char *help) 72 { 73 struct cmd_callback *new_callbacks; 74 const char *cmdp = cmd; 75 int i = 0; 76 77 if (strlen(cmd) >= MAX_CMD_LEN || fn == NULL || cmd[0] != '/' 78 || strlen(help) >= RTE_TEL_MAX_STRING_LEN) 79 return -EINVAL; 80 81 while (*cmdp != '\0') { 82 if (!isalnum(*cmdp) && *cmdp != '_' && *cmdp != '/') 83 return -EINVAL; 84 cmdp++; 85 } 86 87 rte_spinlock_lock(&callback_sl); 88 new_callbacks = realloc(callbacks, sizeof(callbacks[0]) * (num_callbacks + 1)); 89 if (new_callbacks == NULL) { 90 rte_spinlock_unlock(&callback_sl); 91 return -ENOMEM; 92 } 93 callbacks = new_callbacks; 94 95 while (i < num_callbacks && strcmp(cmd, callbacks[i].cmd) > 0) 96 i++; 97 if (i != num_callbacks) 98 /* Move elements to keep the list alphabetical */ 99 memmove(callbacks + i + 1, callbacks + i, 100 sizeof(struct cmd_callback) * (num_callbacks - i)); 101 102 strlcpy(callbacks[i].cmd, cmd, MAX_CMD_LEN); 103 callbacks[i].fn = fn; 104 strlcpy(callbacks[i].help, help, RTE_TEL_MAX_STRING_LEN); 105 num_callbacks++; 106 rte_spinlock_unlock(&callback_sl); 107 108 return 0; 109 } 110 111 #ifndef RTE_EXEC_ENV_WINDOWS 112 113 static int 114 list_commands(const char *cmd __rte_unused, const char *params __rte_unused, 115 struct rte_tel_data *d) 116 { 117 int i; 118 119 rte_tel_data_start_array(d, RTE_TEL_STRING_VAL); 120 rte_spinlock_lock(&callback_sl); 121 for (i = 0; i < num_callbacks; i++) 122 rte_tel_data_add_array_string(d, callbacks[i].cmd); 123 rte_spinlock_unlock(&callback_sl); 124 return 0; 125 } 126 127 static int 128 json_info(const char *cmd __rte_unused, const char *params __rte_unused, 129 struct rte_tel_data *d) 130 { 131 rte_tel_data_start_dict(d); 132 rte_tel_data_add_dict_string(d, "version", telemetry_version); 133 rte_tel_data_add_dict_int(d, "pid", getpid()); 134 rte_tel_data_add_dict_int(d, "max_output_len", MAX_OUTPUT_LEN); 135 return 0; 136 } 137 138 static int 139 command_help(const char *cmd __rte_unused, const char *params, 140 struct rte_tel_data *d) 141 { 142 int i; 143 /* if no parameters return our own help text */ 144 const char *to_lookup = (params == NULL ? cmd : params); 145 146 rte_tel_data_start_dict(d); 147 rte_spinlock_lock(&callback_sl); 148 for (i = 0; i < num_callbacks; i++) 149 if (strcmp(to_lookup, callbacks[i].cmd) == 0) { 150 if (params == NULL) 151 rte_tel_data_string(d, callbacks[i].help); 152 else 153 rte_tel_data_add_dict_string(d, params, callbacks[i].help); 154 break; 155 } 156 rte_spinlock_unlock(&callback_sl); 157 if (i == num_callbacks) 158 return -1; 159 return 0; 160 } 161 162 static int 163 container_to_json(const struct rte_tel_data *d, char *out_buf, size_t buf_len) 164 { 165 size_t used = 0; 166 unsigned int i; 167 168 if (d->type != TEL_DICT && d->type != TEL_ARRAY_UINT && 169 d->type != TEL_ARRAY_INT && d->type != TEL_ARRAY_STRING) 170 return snprintf(out_buf, buf_len, "null"); 171 172 used = rte_tel_json_empty_array(out_buf, buf_len, 0); 173 if (d->type == TEL_ARRAY_UINT) 174 for (i = 0; i < d->data_len; i++) 175 used = rte_tel_json_add_array_uint(out_buf, 176 buf_len, used, 177 d->data.array[i].uval); 178 if (d->type == TEL_ARRAY_INT) 179 for (i = 0; i < d->data_len; i++) 180 used = rte_tel_json_add_array_int(out_buf, 181 buf_len, used, 182 d->data.array[i].ival); 183 if (d->type == TEL_ARRAY_STRING) 184 for (i = 0; i < d->data_len; i++) 185 used = rte_tel_json_add_array_string(out_buf, 186 buf_len, used, 187 d->data.array[i].sval); 188 if (d->type == TEL_DICT) 189 for (i = 0; i < d->data_len; i++) { 190 const struct tel_dict_entry *v = &d->data.dict[i]; 191 switch (v->type) { 192 case RTE_TEL_STRING_VAL: 193 used = rte_tel_json_add_obj_str(out_buf, 194 buf_len, used, 195 v->name, v->value.sval); 196 break; 197 case RTE_TEL_INT_VAL: 198 used = rte_tel_json_add_obj_int(out_buf, 199 buf_len, used, 200 v->name, v->value.ival); 201 break; 202 case RTE_TEL_UINT_VAL: 203 used = rte_tel_json_add_obj_uint(out_buf, 204 buf_len, used, 205 v->name, v->value.uval); 206 break; 207 case RTE_TEL_CONTAINER: 208 { 209 char *temp = malloc(buf_len); 210 if (temp == NULL) 211 break; 212 *temp = '\0'; /* ensure valid string */ 213 214 const struct container *cont = 215 &v->value.container; 216 if (container_to_json(cont->data, 217 temp, buf_len) != 0) 218 used = rte_tel_json_add_obj_json( 219 out_buf, 220 buf_len, used, 221 v->name, temp); 222 if (!cont->keep) 223 rte_tel_data_free(cont->data); 224 free(temp); 225 break; 226 } 227 } 228 } 229 230 return used; 231 } 232 233 static void 234 output_json(const char *cmd, const struct rte_tel_data *d, int s) 235 { 236 char out_buf[MAX_OUTPUT_LEN]; 237 238 char *cb_data_buf; 239 size_t buf_len, prefix_used, used = 0; 240 unsigned int i; 241 242 RTE_BUILD_BUG_ON(sizeof(out_buf) < MAX_CMD_LEN + 243 RTE_TEL_MAX_SINGLE_STRING_LEN + 10); 244 245 prefix_used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":", 246 MAX_CMD_LEN, cmd); 247 cb_data_buf = &out_buf[prefix_used]; 248 buf_len = sizeof(out_buf) - prefix_used - 1; /* space for '}' */ 249 250 switch (d->type) { 251 case TEL_NULL: 252 used = strlcpy(cb_data_buf, "null", buf_len); 253 break; 254 255 case TEL_STRING: 256 used = rte_tel_json_str(cb_data_buf, buf_len, 0, d->data.str); 257 break; 258 259 case TEL_DICT: 260 used = rte_tel_json_empty_obj(cb_data_buf, buf_len, 0); 261 for (i = 0; i < d->data_len; i++) { 262 const struct tel_dict_entry *v = &d->data.dict[i]; 263 switch (v->type) { 264 case RTE_TEL_STRING_VAL: 265 used = rte_tel_json_add_obj_str(cb_data_buf, 266 buf_len, used, 267 v->name, v->value.sval); 268 break; 269 case RTE_TEL_INT_VAL: 270 used = rte_tel_json_add_obj_int(cb_data_buf, 271 buf_len, used, 272 v->name, v->value.ival); 273 break; 274 case RTE_TEL_UINT_VAL: 275 used = rte_tel_json_add_obj_uint(cb_data_buf, 276 buf_len, used, 277 v->name, v->value.uval); 278 break; 279 case RTE_TEL_CONTAINER: 280 { 281 char *temp = malloc(buf_len); 282 if (temp == NULL) 283 break; 284 *temp = '\0'; /* ensure valid string */ 285 286 const struct container *cont = 287 &v->value.container; 288 if (container_to_json(cont->data, 289 temp, buf_len) != 0) 290 used = rte_tel_json_add_obj_json( 291 cb_data_buf, 292 buf_len, used, 293 v->name, temp); 294 if (!cont->keep) 295 rte_tel_data_free(cont->data); 296 free(temp); 297 } 298 } 299 } 300 break; 301 302 case TEL_ARRAY_STRING: 303 case TEL_ARRAY_INT: 304 case TEL_ARRAY_UINT: 305 case TEL_ARRAY_CONTAINER: 306 used = rte_tel_json_empty_array(cb_data_buf, buf_len, 0); 307 for (i = 0; i < d->data_len; i++) 308 if (d->type == TEL_ARRAY_STRING) 309 used = rte_tel_json_add_array_string( 310 cb_data_buf, 311 buf_len, used, 312 d->data.array[i].sval); 313 else if (d->type == TEL_ARRAY_INT) 314 used = rte_tel_json_add_array_int(cb_data_buf, 315 buf_len, used, 316 d->data.array[i].ival); 317 else if (d->type == TEL_ARRAY_UINT) 318 used = rte_tel_json_add_array_uint(cb_data_buf, 319 buf_len, used, 320 d->data.array[i].uval); 321 else if (d->type == TEL_ARRAY_CONTAINER) { 322 char *temp = malloc(buf_len); 323 if (temp == NULL) 324 break; 325 *temp = '\0'; /* ensure valid string */ 326 327 const struct container *rec_data = 328 &d->data.array[i].container; 329 if (container_to_json(rec_data->data, 330 temp, buf_len) != 0) 331 used = rte_tel_json_add_array_json( 332 cb_data_buf, 333 buf_len, used, temp); 334 if (!rec_data->keep) 335 rte_tel_data_free(rec_data->data); 336 free(temp); 337 } 338 break; 339 } 340 used += prefix_used; 341 used += strlcat(out_buf + used, "}", sizeof(out_buf) - used); 342 if (write(s, out_buf, used) < 0) 343 perror("Error writing to socket"); 344 } 345 346 static void 347 perform_command(telemetry_cb fn, const char *cmd, const char *param, int s) 348 { 349 struct rte_tel_data data = {0}; 350 351 int ret = fn(cmd, param, &data); 352 if (ret < 0) { 353 char out_buf[MAX_CMD_LEN + 10]; 354 int used = snprintf(out_buf, sizeof(out_buf), "{\"%.*s\":null}", 355 MAX_CMD_LEN, cmd ? cmd : "none"); 356 if (write(s, out_buf, used) < 0) 357 perror("Error writing to socket"); 358 return; 359 } 360 output_json(cmd, &data, s); 361 } 362 363 static int 364 unknown_command(const char *cmd __rte_unused, const char *params __rte_unused, 365 struct rte_tel_data *d) 366 { 367 return d->type = TEL_NULL; 368 } 369 370 static void * 371 client_handler(void *sock_id) 372 { 373 int s = (int)(uintptr_t)sock_id; 374 char buffer[1024]; 375 char info_str[1024]; 376 snprintf(info_str, sizeof(info_str), 377 "{\"version\":\"%s\",\"pid\":%d,\"max_output_len\":%d}", 378 telemetry_version, getpid(), MAX_OUTPUT_LEN); 379 if (write(s, info_str, strlen(info_str)) < 0) { 380 close(s); 381 return NULL; 382 } 383 384 /* receive data is not null terminated */ 385 int bytes = read(s, buffer, sizeof(buffer) - 1); 386 while (bytes > 0) { 387 buffer[bytes] = 0; 388 const char *cmd = strtok(buffer, ","); 389 const char *param = strtok(NULL, "\0"); 390 telemetry_cb fn = unknown_command; 391 int i; 392 393 if (cmd && strlen(cmd) < MAX_CMD_LEN) { 394 rte_spinlock_lock(&callback_sl); 395 for (i = 0; i < num_callbacks; i++) 396 if (strcmp(cmd, callbacks[i].cmd) == 0) { 397 fn = callbacks[i].fn; 398 break; 399 } 400 rte_spinlock_unlock(&callback_sl); 401 } 402 perform_command(fn, cmd, param, s); 403 404 bytes = read(s, buffer, sizeof(buffer) - 1); 405 } 406 close(s); 407 rte_atomic_fetch_sub_explicit(&v2_clients, 1, rte_memory_order_relaxed); 408 return NULL; 409 } 410 411 static void * 412 socket_listener(void *socket) 413 { 414 while (1) { 415 pthread_t th; 416 int rc; 417 struct socket *s = (struct socket *)socket; 418 int s_accepted = accept(s->sock, NULL, NULL); 419 if (s_accepted < 0) { 420 TMTY_LOG(ERR, "Error with accept, telemetry thread quitting\n"); 421 return NULL; 422 } 423 if (s->num_clients != NULL) { 424 uint16_t conns = rte_atomic_load_explicit(s->num_clients, 425 rte_memory_order_relaxed); 426 if (conns >= MAX_CONNECTIONS) { 427 close(s_accepted); 428 continue; 429 } 430 rte_atomic_fetch_add_explicit(s->num_clients, 1, 431 rte_memory_order_relaxed); 432 } 433 rc = pthread_create(&th, NULL, s->fn, 434 (void *)(uintptr_t)s_accepted); 435 if (rc != 0) { 436 TMTY_LOG(ERR, "Error with create client thread: %s\n", 437 strerror(rc)); 438 close(s_accepted); 439 if (s->num_clients != NULL) 440 rte_atomic_fetch_sub_explicit(s->num_clients, 1, 441 rte_memory_order_relaxed); 442 continue; 443 } 444 pthread_detach(th); 445 } 446 return NULL; 447 } 448 449 static inline char * 450 get_socket_path(const char *runtime_dir, const int version) 451 { 452 static char path[PATH_MAX]; 453 snprintf(path, sizeof(path), "%s/dpdk_telemetry.v%d", 454 strlen(runtime_dir) ? runtime_dir : "/tmp", version); 455 return path; 456 } 457 458 static void 459 unlink_sockets(void) 460 { 461 if (v2_socket.path[0]) 462 unlink(v2_socket.path); 463 if (v1_socket.path[0]) 464 unlink(v1_socket.path); 465 } 466 467 static int 468 create_socket(char *path) 469 { 470 int sock = socket(AF_UNIX, SOCK_SEQPACKET, 0); 471 if (sock < 0) { 472 TMTY_LOG(ERR, "Error with socket creation, %s\n", strerror(errno)); 473 return -1; 474 } 475 476 struct sockaddr_un sun = {.sun_family = AF_UNIX}; 477 strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); 478 TMTY_LOG(DEBUG, "Attempting socket bind to path '%s'\n", path); 479 480 if (bind(sock, (void *) &sun, sizeof(sun)) < 0) { 481 struct stat st; 482 483 TMTY_LOG(DEBUG, "Initial bind to socket '%s' failed.\n", path); 484 485 /* first check if we have a runtime dir */ 486 if (stat(socket_dir, &st) < 0 || !S_ISDIR(st.st_mode)) { 487 TMTY_LOG(ERR, "Cannot access DPDK runtime directory: %s\n", socket_dir); 488 close(sock); 489 return -ENOENT; 490 } 491 492 /* check if current socket is active */ 493 if (connect(sock, (void *)&sun, sizeof(sun)) == 0) { 494 close(sock); 495 return -EADDRINUSE; 496 } 497 498 /* socket is not active, delete and attempt rebind */ 499 TMTY_LOG(DEBUG, "Attempting unlink and retrying bind\n"); 500 unlink(sun.sun_path); 501 if (bind(sock, (void *) &sun, sizeof(sun)) < 0) { 502 TMTY_LOG(ERR, "Error binding socket: %s\n", strerror(errno)); 503 close(sock); 504 return -errno; /* if unlink failed, this will be -EADDRINUSE as above */ 505 } 506 } 507 508 if (listen(sock, 1) < 0) { 509 TMTY_LOG(ERR, "Error calling listen for socket: %s\n", strerror(errno)); 510 unlink(sun.sun_path); 511 close(sock); 512 return -errno; 513 } 514 TMTY_LOG(DEBUG, "Socket creation and binding ok\n"); 515 516 return sock; 517 } 518 519 static void 520 set_thread_name(pthread_t id __rte_unused, const char *name __rte_unused) 521 { 522 #if defined RTE_EXEC_ENV_LINUX && defined __GLIBC__ && defined __GLIBC_PREREQ 523 #if __GLIBC_PREREQ(2, 12) 524 pthread_setname_np(id, name); 525 #endif 526 #elif defined RTE_EXEC_ENV_FREEBSD 527 pthread_set_name_np(id, name); 528 #endif 529 } 530 531 static int 532 telemetry_legacy_init(void) 533 { 534 pthread_t t_old; 535 int rc; 536 537 if (num_legacy_callbacks == 1) { 538 TMTY_LOG(WARNING, "No legacy callbacks, legacy socket not created\n"); 539 return -1; 540 } 541 542 v1_socket.fn = legacy_client_handler; 543 if ((size_t) snprintf(v1_socket.path, sizeof(v1_socket.path), 544 "%s/telemetry", socket_dir) >= sizeof(v1_socket.path)) { 545 TMTY_LOG(ERR, "Error with socket binding, path too long\n"); 546 return -1; 547 } 548 v1_socket.sock = create_socket(v1_socket.path); 549 if (v1_socket.sock < 0) { 550 v1_socket.path[0] = '\0'; 551 return -1; 552 } 553 rc = pthread_create(&t_old, NULL, socket_listener, &v1_socket); 554 if (rc != 0) { 555 TMTY_LOG(ERR, "Error with create legacy socket thread: %s\n", 556 strerror(rc)); 557 close(v1_socket.sock); 558 v1_socket.sock = -1; 559 unlink(v1_socket.path); 560 v1_socket.path[0] = '\0'; 561 return -1; 562 } 563 pthread_setaffinity_np(t_old, sizeof(*thread_cpuset), thread_cpuset); 564 set_thread_name(t_old, "dpdk-telemet-v1"); 565 TMTY_LOG(DEBUG, "Legacy telemetry socket initialized ok\n"); 566 pthread_detach(t_old); 567 return 0; 568 } 569 570 static int 571 telemetry_v2_init(void) 572 { 573 char spath[sizeof(v2_socket.path)]; 574 pthread_t t_new; 575 short suffix = 0; 576 int rc; 577 578 v2_socket.num_clients = &v2_clients; 579 rte_telemetry_register_cmd("/", list_commands, 580 "Returns list of available commands, Takes no parameters"); 581 rte_telemetry_register_cmd("/info", json_info, 582 "Returns DPDK Telemetry information. Takes no parameters"); 583 rte_telemetry_register_cmd("/help", command_help, 584 "Returns help text for a command. Parameters: string command"); 585 v2_socket.fn = client_handler; 586 if (strlcpy(spath, get_socket_path(socket_dir, 2), sizeof(spath)) >= sizeof(spath)) { 587 TMTY_LOG(ERR, "Error with socket binding, path too long\n"); 588 return -1; 589 } 590 memcpy(v2_socket.path, spath, sizeof(v2_socket.path)); 591 592 v2_socket.sock = create_socket(v2_socket.path); 593 while (v2_socket.sock < 0) { 594 /* bail out on unexpected error, or suffix wrap-around */ 595 if (v2_socket.sock != -EADDRINUSE || suffix < 0) { 596 v2_socket.path[0] = '\0'; /* clear socket path */ 597 return -1; 598 } 599 /* add a suffix to the path if the basic version fails */ 600 if (snprintf(v2_socket.path, sizeof(v2_socket.path), "%s:%d", 601 spath, ++suffix) >= (int)sizeof(v2_socket.path)) { 602 TMTY_LOG(ERR, "Error with socket binding, path too long\n"); 603 return -1; 604 } 605 v2_socket.sock = create_socket(v2_socket.path); 606 } 607 rc = pthread_create(&t_new, NULL, socket_listener, &v2_socket); 608 if (rc != 0) { 609 TMTY_LOG(ERR, "Error with create socket thread: %s\n", 610 strerror(rc)); 611 close(v2_socket.sock); 612 v2_socket.sock = -1; 613 unlink(v2_socket.path); 614 v2_socket.path[0] = '\0'; 615 return -1; 616 } 617 pthread_setaffinity_np(t_new, sizeof(*thread_cpuset), thread_cpuset); 618 set_thread_name(t_new, "dpdk-telemet-v2"); 619 pthread_detach(t_new); 620 atexit(unlink_sockets); 621 622 return 0; 623 } 624 625 #endif /* !RTE_EXEC_ENV_WINDOWS */ 626 627 int32_t 628 rte_telemetry_init(const char *runtime_dir, const char *rte_version, rte_cpuset_t *cpuset) 629 { 630 telemetry_version = rte_version; 631 socket_dir = runtime_dir; 632 thread_cpuset = cpuset; 633 634 #ifndef RTE_EXEC_ENV_WINDOWS 635 if (telemetry_v2_init() != 0) 636 return -1; 637 TMTY_LOG(DEBUG, "Telemetry initialized ok\n"); 638 telemetry_legacy_init(); 639 #endif /* RTE_EXEC_ENV_WINDOWS */ 640 641 return 0; 642 } 643