bcm-diag-proxy.c (19598B)
1 /* 2 * 3 * This license is set out in https://raw.githubusercontent.com/Broadcom-Network-Switching-Software/OpenBCM/master/Legal/LICENSE file. 4 * 5 * Copyright 2007-2019 Broadcom Inc. All rights reserved. 6 */ 7 8 #include <stdlib.h> 9 #include <stdio.h> 10 #include <string.h> 11 #include <unistd.h> 12 #include <assert.h> 13 #include <signal.h> 14 15 #include <sal/core/thread.h> 16 #include <sal/appl/config.h> 17 #include <linux-uk-proxy.h> 18 19 #include "proxy-service.h" 20 #include "socintf.h" 21 22 /* 23 * This is the proxy service the kernel diag shell uses. 24 */ 25 static char _input_proxy_service[64] = "BCM DIAG SHELL"; 26 static char _output_proxy_service[64] = "BCM DIAG SHELL"; 27 28 #define OUTPUT_LINE(_ctrl, _buf) \ 29 do { \ 30 if (_ctrl->ctrl.output_cb) { \ 31 unsigned int _len = strlen(_buf)+1; \ 32 _ctrl->ctrl.output_cb((proxy_ctrl_t*)_ctrl, (unsigned char*)_buf, &_len); \ 33 } \ 34 } while (0) 35 36 #define COPY_AND_OUTPUT_CMD(_ctrl, _buf, _cmd) \ 37 do { \ 38 strcpy(_buf, _cmd); \ 39 strcat(_buf, "\n"); \ 40 OUTPUT_LINE(_ctrl, _buf); \ 41 } while (0) 42 43 #define LINE_BUF_SIZE 256 44 45 #ifdef INCLUDE_EDITLINE 46 extern char *readline(const char *prompt); 47 extern void add_history(char *p); 48 #endif 49 static int use_editline = 0; 50 51 static char *progname; 52 53 /* 54 * Proxy control structure passed to all of our callbacks. 55 */ 56 typedef struct proxy_ctrl_t { 57 proxy_ctrl_t ctrl; 58 59 const char *service; /* Service pointer for kernel proxy callbacks */ 60 61 int output_fd; /* File descriptor for the output callback */ 62 int input_fd; /* File descriptor for the input callback */ 63 64 proxy_ctrl_t *sibling; /* Other proxies which should stop when this one does */ 65 } our_proxy_ctrl_t; 66 67 68 /* 69 * Function: skip_whitespace 70 * 71 * Purpose: 72 * Skip over leading whitespace in string 73 * Parameters: 74 * s - string to parse 75 * Returns: 76 * pointer to first non-whitespace character in input string 77 */ 78 static char * 79 skip_whitespace(char *s) 80 { 81 /* Skip leading whitespace */ 82 while (*s && *s <= ' ') { 83 s++; 84 } 85 return s; 86 } 87 88 /* 89 * Function: load_config 90 * 91 * Purpose: 92 * Load BCM configuration file 93 * Parameters: 94 * ctrl - proxy service control structure 95 * fname - name of configuration file 96 * Returns: 97 * 0 - configuration file found and loaded 98 * -1 - configuration file not found 99 */ 100 static int 101 load_config(our_proxy_ctrl_t *ctrl, 102 char *fname) 103 { 104 char cmd[64]; 105 char line[LINE_BUF_SIZE]; 106 FILE *fp; 107 int offs;; 108 109 fp = fopen(fname, "r"); 110 if (!fp) { 111 printf("cannot open '%s'\n", fname); 112 return -1; 113 } 114 COPY_AND_OUTPUT_CMD(ctrl, cmd, "local proxy_cmd_ enter_batchmode"); 115 line[LINE_BUF_SIZE-1] = 0; 116 strcpy(line, "config add "); 117 offs = strlen(line); 118 while (fgets(&line[offs], LINE_BUF_SIZE-1-offs, fp)) { 119 if (line[offs] != '#' && strchr(&line[offs], '=') != NULL) { 120 OUTPUT_LINE(ctrl, line); 121 } 122 } 123 COPY_AND_OUTPUT_CMD(ctrl, cmd, "local proxy_cmd_ exit_batchmode"); 124 fclose(fp); 125 return 0; 126 } 127 128 /* 129 * Function: run_script 130 * 131 * Purpose: 132 * Run soc script by passing file content through proxy 133 * Parameters: 134 * ctrl - proxy service control structure 135 * script - name of script file to run 136 * Returns: 137 * 0 - script found and executed 138 * -1 - script not found 139 */ 140 static int 141 run_script(our_proxy_ctrl_t *ctrl, 142 char *script, 143 char *init_script, 144 int recursion_level) 145 { 146 char line[LINE_BUF_SIZE]; 147 char cmd[64]; 148 char *s; 149 FILE *fp; 150 int cont = 0; 151 int reinit = 0; 152 int offset = 0; 153 154 if (++recursion_level > 4) { 155 printf("recursion too deep to run script '%s'\n", script); 156 return -1; 157 } 158 fp = fopen(script, "r"); 159 if (!fp) { 160 if(strchr(script, '$')) { 161 sprintf(line, "rcload %s", script); 162 COPY_AND_OUTPUT_CMD(ctrl, cmd, line); 163 sal_usleep(500000); 164 return 0; 165 } 166 else { 167 printf("cannot open script '%s'\n", script); 168 return -1; 169 } 170 } 171 if (recursion_level == 1) { 172 COPY_AND_OUTPUT_CMD(ctrl, cmd, "local proxy_cmd_ enter_batchmode"); 173 } 174 COPY_AND_OUTPUT_CMD(ctrl, cmd, "local proxy_cmd_ push_scope"); 175 line[LINE_BUF_SIZE-1] = 0; 176 while (fgets(line, LINE_BUF_SIZE-1, fp)) { 177 s = skip_whitespace(line); 178 if (!strncmp(line, "rcload ", 7)) { 179 if ((s = strchr(line, '\n')) != NULL) { 180 *s = 0; 181 } 182 s = skip_whitespace(line+7); 183 run_script(ctrl, s, init_script, recursion_level); 184 } 185 if (init_script && 186 (!strncmp(s, "tr ", 3) || !strncmp(s, "testrun ", 8))) { 187 /* 188 * Call init script after each testrun. Ideally we would 189 * run this script *before* the testrun command, but we may 190 * be in the middle of a multi-line command. 191 */ 192 reinit = 1; 193 /* 194 * Exceptions to speed up sanity tests 195 * Skip rc.soc for a few tests that don't have TRC flag set 196 * to prevent linkscan task from getting enabled. 197 */ 198 offset = 0; 199 if (!strncmp(s, "tr ", 3)) { 200 offset = 3; 201 } else if (!strncmp(s, "testrun ", 8)) { 202 offset = 8; 203 } 204 if (((s[offset] >= '1') && (s[offset] <= '8') && 205 ((s[offset + 1] < '0') || (s[offset + 1] > '9'))) || 206 !strncmp(&s[offset], "15", 2) || 207 !strncmp(&s[offset], "16", 2) || 208 !strncmp(&s[offset], "21", 2) || 209 !strncmp(&s[offset], "30", 2) || 210 !strncmp(&s[offset], "31", 2) || 211 !strncmp(&s[offset], "32", 2) || 212 !strncmp(&s[offset], "33", 2) || 213 !strncmp(&s[offset], "34", 2) || 214 !strncmp(&s[offset], "35", 2) || 215 !strncmp(&s[offset], "36", 2) || 216 !strncmp(&s[offset], "37", 2) || 217 !strncmp(&s[offset], "38", 2) || 218 !strncmp(&s[offset], "50", 2) || 219 !strncmp(&s[offset], "51", 2) || 220 !strncmp(&s[offset], "52", 2) || 221 !strncmp(&s[offset], "71", 2) || 222 !strncmp(&s[offset], "85", 2) || 223 !strncmp(&s[offset], "86", 2) || 224 !strncmp(&s[offset], "87", 2) || 225 !strncmp(&s[offset], "88", 2) 226 ) { 227 reinit = 0; 228 } 229 } 230 if (*s != '#' && strncmp(s, "rccache", 7) != 0) { 231 /* Send the line directly to output callback */ 232 OUTPUT_LINE(ctrl, line); 233 } 234 /* Check for line continuation */ 235 cont = (strstr(line, "\\\n")) ? 1 : 0; 236 if (reinit && !cont) { 237 /* 238 * Run init script as soon as possible after completing 239 * the testrun command. Since command line concatenation 240 * is handled remotely, we need to wait until we have sent 241 * a complete command line. 242 */ 243 COPY_AND_OUTPUT_CMD(ctrl, cmd, "local proxy_cmd_ push_scope"); 244 COPY_AND_OUTPUT_CMD(ctrl, cmd, "set rctest=0"); 245 run_script(ctrl, init_script, NULL, recursion_level); 246 COPY_AND_OUTPUT_CMD(ctrl, cmd, "local proxy_cmd_ pop_scope"); 247 reinit = 0; 248 } 249 } 250 COPY_AND_OUTPUT_CMD(ctrl, cmd, "local proxy_cmd_ pop_scope"); 251 if (recursion_level == 1) { 252 COPY_AND_OUTPUT_CMD(ctrl, cmd, "local proxy_cmd_ exit_batchmode"); 253 } 254 fclose(fp); 255 return 0; 256 } 257 258 /* 259 * Function: _fd_input_cb 260 * 261 * Purpose: 262 * Proxy input function from a file descriptor 263 * Parameters: 264 * ctrl - Proxy service control structure 265 * data - (OUT) buffer to receive the data 266 * len - (OUT) receives the data length 267 * Returns: 268 * 0 - data is ready to send. 269 * -1 - do not send data any data. 270 * Notes: 271 * It reads commands from the descriptor. 272 * The following commands are interpreted locally: 273 * 274 * quit - Quit the proxy program. 275 * Any data AFTER the quit is sent to the remote. Thus, 276 * "quit exit" will quit the local, and send "exit" to 277 * the remote. "quit" by itself will quit the local, and 278 * send nothing to the remote. This functionality prevents 279 * the user from accidentally exiting the diag shell at 280 * the remote end. 281 * 282 * exit - Quit the proxy program. 283 * Same functionality as the quit command. 284 * 285 * rcload <file> 286 * Lines in file <file> will be sent to the remote end. 287 * This provides a rudimentary approximation of the real 288 * diag shell 'rcload' function. 289 */ 290 291 292 /* 293 * Sometimes an input line (an instruction from the kernel shell) 294 * needs to be injected into the input read buffer. 295 */ 296 static char _proxy_input_line[64]; 297 298 static int 299 _fd_input_cb(our_proxy_ctrl_t *ctrl, 300 unsigned char *udata, 301 unsigned int *len) 302 { 303 char *config_file; 304 char *init_script; 305 char *s = NULL; 306 char *data = (char *)udata; 307 FILE *fp; 308 int input_fd; 309 310 /* Check for locally specified input first */ 311 if(_proxy_input_line[0]) { 312 strcpy(data, _proxy_input_line); 313 _proxy_input_line[0] = 0; 314 } else { 315 /* Retrieve input from the file descriptor */ 316 #ifdef INCLUDE_EDITLINE 317 if (use_editline) { 318 s = readline(""); 319 if (s) { 320 add_history(s); 321 strncpy(data, s, *len-1); 322 data[*len-1] = 0; 323 strcat(data, "\n"); 324 } else { 325 strcpy(data, "quit\n"); 326 } 327 } 328 #endif 329 if (!use_editline) { 330 /* 331 * Duplicate file descriptor when using fdopen() to 332 * prevent underlying socket from being closed by 333 * fclose(). 334 */ 335 input_fd = dup(ctrl->input_fd); 336 if (input_fd < 0) { 337 printf("failed to dup input_fd %d\n", ctrl->input_fd); 338 return -1; 339 } 340 341 /* Get a line from the descriptor */ 342 fp = fdopen(input_fd, "r"); 343 assert(fp); 344 if (fgets(data, *len, fp) == NULL) { 345 /* Broken pipe. Lets quit */ 346 strcpy(data, "quit\n"); 347 } 348 /* Strip CR characters (e.g. from telnet) */ 349 if ((s = strchr(data, '\r')) != NULL) { 350 *s++ = '\n'; 351 *s = 0; 352 } 353 /* Support backspace */ 354 while ((s = strchr(data, 0x08)) != NULL) { 355 if (s == (char *)data) { 356 memcpy(s, &s[1], strlen(s)); 357 } else { 358 memcpy(&s[-1], &s[1], strlen(s)); 359 } 360 } 361 fclose(fp); 362 } 363 } 364 365 /* Special commands? */ 366 if (!strncmp(data, "quit", 4) || !strncmp(data, "exit", 4)) { 367 /* Tell our service thread to exit */ 368 /* This will happen AFTER the data result from this function is sent. */ 369 ctrl->ctrl.exit = 1; 370 return -1; 371 } 372 373 if (!strncmp(data, "rcload ", 7)) { 374 /* Read lines from the given file */ 375 if ((s = strchr(data, '\n')) != NULL) { 376 *s = 0; 377 } 378 s = skip_whitespace(data+7); 379 init_script = "rc.soc"; 380 if (!strncmp(s, "-i", 2)) { 381 /* 382 * For backwards compatibility we need to silently 383 * ignore the -i switch, which used to be required 384 * for rc.soc to be executed in between testruns. 385 */ 386 s = skip_whitespace(s+2); 387 } else if (!strncmp(s, "-i-", 3)) { 388 /* 389 * Turn off init script feature (normally not used). 390 */ 391 init_script = NULL; 392 s = skip_whitespace(s+3); 393 } 394 run_script(ctrl, s, init_script, 0); 395 return -1; 396 } 397 398 if (!strncmp(data, "config refresh", 14)) { 399 /* Read config.bcm */ 400 if ((config_file = getenv("BCM_CONFIG_FILE")) == NULL) { 401 config_file = SAL_CONFIG_FILE; 402 } 403 load_config(ctrl, config_file); 404 return -1; 405 } 406 407 *len = strlen(data) + 1; 408 409 /* Send this buffer */ 410 return 0; 411 } 412 413 /* 414 * Function: _fd_output_cb 415 * 416 * Purpose: 417 * Proxy output function to a file descriptor 418 * Parameters: 419 * ctrl - proxy service control structure 420 * data - data which was read from the proxy 421 * len - length of the data. 422 * Returns: 423 * Always 0 424 * Notes: 425 * This is an output callback for the proxy service. 426 */ 427 static int 428 _fd_output_cb(our_proxy_ctrl_t *ctrl, 429 unsigned char *udata, 430 unsigned int *len) 431 { 432 char *data = (char *)udata; 433 434 /* Do we have an instruction from the shell to execute? */ 435 if(!strncmp("proxyecho", data, 9)) { 436 strcpy(_proxy_input_line, data+10); 437 } 438 else { 439 write(ctrl->output_fd, data, *len); 440 fsync(ctrl->output_fd); 441 } 442 return 0; 443 } 444 445 446 /* 447 * Function: _kernel_proxy_output_cb 448 * 449 * Purpose: 450 * Proxy output function. 451 * Parameters: 452 * ctrl - proxy control structure 453 * data - output data 454 * len - length of data 455 * Returns: 456 * Always 0 457 */ 458 static int 459 _kernel_proxy_output_cb(our_proxy_ctrl_t *ctrl, 460 unsigned char *data, 461 unsigned int *len) 462 { 463 /* This data should be sent to the given proxy service */ 464 return linux_uk_proxy_send(ctrl->service, data, *len); 465 } 466 467 /* 468 * Function: _kernel_proxy_input_cb 469 * 470 * Purpose: 471 * Proxy input function 472 * Parameters: 473 * ctrl - proxy control structure 474 * data - (OUT) proxy data 475 * len - (OUT) length of the data 476 * Returns: 477 * 0 - data available 478 * -1 - no data 479 */ 480 static int 481 _kernel_proxy_input_cb(our_proxy_ctrl_t *ctrl, 482 unsigned char *data, 483 unsigned int *len) 484 { 485 return linux_uk_proxy_recv(ctrl->service, data, len); 486 } 487 488 489 /* 490 * Function: _kernel_proxy_input_exit_cb 491 * 492 * Purpose: 493 * To be called when input proxy exits 494 * Parameters: 495 * ctrl - proxy control structure 496 * data - unused 497 * len - unused 498 * Returns: 499 * 0 - data available 500 * -1 - no data 501 */ 502 static int 503 _kernel_proxy_input_exit_cb(our_proxy_ctrl_t *ctrl, 504 unsigned char *data, 505 unsigned int *len) 506 { 507 /* Suspend service to prevent kernel threads from blocking */ 508 linux_uk_proxy_suspend(_input_proxy_service); 509 return 0; 510 } 511 512 static void 513 _trigger_read_proxy_thread_exit(proxy_ctrl_t* ctrl) 514 { 515 unsigned char data[] = {'\n', '\0'}; 516 unsigned int len = 1; 517 518 if (ctrl->output_cb) { 519 ctrl->output_cb(ctrl, data, &len); 520 } 521 } 522 523 /* 524 * Function: _kernel_fd_attach 525 * 526 * Purpose: 527 * Attach I/O file descriptors to proxy service 528 * Parameters: 529 * input_fd - file descriptor for input callback 530 * output_fd - file descriptor for output callback 531 * Returns: 532 * 0 533 */ 534 static int 535 _kernel_fd_attach(int input_fd, int output_fd) 536 { 537 our_proxy_ctrl_t reader; 538 our_proxy_ctrl_t writer; 539 540 memset(&reader, 0, sizeof(reader)); 541 memset(&writer, 0, sizeof(writer)); 542 543 if (getenv("INPUT_PROXY")) { 544 strcpy(_input_proxy_service, getenv("INPUT_PROXY")); 545 } 546 if (getenv("OUTPUT_PROXY")) { 547 strcpy(_output_proxy_service, getenv("OUTPUT_PROXY")); 548 } 549 550 linux_uk_proxy_open(); 551 552 /* Create the service, if it is not yet available. */ 553 /* We configure a queue size of 1 -- we do not want the io buffered */ 554 linux_uk_proxy_service_create(_input_proxy_service, 1, 0); 555 556 reader.ctrl.input_cb = (proxy_data_cb_t)_kernel_proxy_input_cb; 557 reader.ctrl.output_cb = (proxy_data_cb_t)_fd_output_cb; 558 reader.ctrl.exit_cb = (proxy_data_cb_t)_kernel_proxy_input_exit_cb; 559 reader.ctrl.max_data_size = LUK_MAX_DATA_SIZE; 560 reader.ctrl.exit = 0; 561 562 reader.service = _input_proxy_service; 563 reader.output_fd = output_fd; 564 reader.sibling = NULL; 565 566 /* Fork off the reader loop */ 567 proxy_service_start(&reader.ctrl, 1); 568 569 /* Resume in case service was suspended */ 570 linux_uk_proxy_resume(_input_proxy_service); 571 572 /* Create the service, if it is not yet available. */ 573 /* We configure a queue size of 1 -- we do not want the io buffered */ 574 linux_uk_proxy_service_create(_output_proxy_service, 1, 0); 575 576 writer.ctrl.input_cb = (proxy_data_cb_t)_fd_input_cb; 577 writer.ctrl.output_cb = (proxy_data_cb_t)_kernel_proxy_output_cb; 578 writer.ctrl.exit_cb = NULL; 579 writer.ctrl.max_data_size = LUK_MAX_DATA_SIZE; 580 writer.ctrl.exit = 0; 581 582 writer.service = _output_proxy_service; 583 writer.input_fd = input_fd; 584 writer.sibling = &reader.ctrl; 585 586 /* Start the writer loop (don't fork) */ 587 proxy_service_start(&writer.ctrl, 0); 588 /* Tell our sibling thread to exit as well */ 589 writer.sibling->exit = 2; 590 591 /* Wait for sibling to exit */ 592 while (writer.sibling->exit) { 593 sal_usleep(150000); 594 if (writer.sibling->exit) { 595 _trigger_read_proxy_thread_exit(&writer.ctrl); 596 } 597 } 598 599 return 0; 600 } 601 602 static int 603 _fd_stdio_attach(int fd) 604 { 605 our_proxy_ctrl_t reader; 606 our_proxy_ctrl_t writer; 607 608 memset(&reader, 0, sizeof(reader)); 609 memset(&writer, 0, sizeof(writer)); 610 611 reader.ctrl.input_cb = (proxy_data_cb_t)_fd_input_cb; 612 reader.input_fd = fd; 613 reader.ctrl.output_cb = (proxy_data_cb_t)_fd_output_cb; 614 reader.output_fd = 0; 615 reader.ctrl.max_data_size = LUK_MAX_DATA_SIZE; 616 reader.ctrl.exit = 0; 617 reader.sibling = NULL; 618 reader.ctrl.exit_cb = NULL; 619 620 /* Fork off the reader loop */ 621 proxy_service_start(&reader.ctrl, 1); 622 623 writer.ctrl.input_cb = (proxy_data_cb_t)_fd_input_cb; 624 writer.input_fd = 1; 625 writer.ctrl.output_cb = (proxy_data_cb_t)_fd_output_cb; 626 writer.output_fd = fd; 627 writer.ctrl.max_data_size = LUK_MAX_DATA_SIZE; 628 writer.ctrl.exit = 0; 629 writer.sibling = &reader.ctrl; 630 writer.ctrl.exit_cb = NULL; 631 632 /* Start the writer loop (don't fork) */ 633 proxy_service_start(&writer.ctrl, 0); 634 writer.sibling->exit = 2; 635 return 0; 636 } 637 638 static int 639 _daemon(int port) 640 { 641 int server_s; 642 int client_s; 643 644 server_s = setup_socket(port); 645 646 for (;;) { 647 printf("waiting for connections...\n"); 648 client_s = wait_for_cnxn(server_s); 649 printf("client connected.\n"); 650 _kernel_fd_attach(client_s, client_s); 651 close(client_s); 652 } 653 return 0; 654 } 655 656 static void 657 usage(void) 658 { 659 printf("Usage: %s [-d port]\n", progname); 660 printf(" %s [-s hostname port]\n", progname); 661 #ifdef INCLUDE_EDITLINE 662 printf(" %s [-n]\n", progname); 663 #endif 664 } 665 666 static void 667 ctrl_c_handler(int sig) 668 { 669 /* Suspend service to prevent kernel threads from blocking */ 670 linux_uk_proxy_suspend(_input_proxy_service); 671 672 exit(0); 673 } 674 675 int 676 main(int argc, char *argv[]) 677 { 678 if ((progname = strrchr(argv[0], '/')) == NULL) { 679 progname = argv[0]; 680 } else { 681 progname++; 682 } 683 684 /* Suspend proxy on exit */ 685 signal(SIGINT, ctrl_c_handler); 686 687 /* Broken pipes are not a problem */ 688 signal(SIGPIPE, SIG_IGN); 689 690 if (argc == 1) { 691 /* Attach on stdio */ 692 #ifdef INCLUDE_EDITLINE 693 use_editline = 1; 694 #else 695 use_editline = 0; 696 #endif 697 _kernel_fd_attach(0, 1); 698 #ifdef INCLUDE_EDITLINE 699 } else if (!strcmp(argv[1], "-n") && argc == 2) { 700 /* Attach on stdio */ 701 _kernel_fd_attach(0, 1); 702 #endif 703 } else if (!strcmp(argv[1], "-d") && argc == 3) { 704 /* Port to listen on */ 705 int port = atoi(argv[2]); 706 _daemon(port); 707 } else if (!strcmp(argv[1], "-s") && argc == 4) { 708 /* Connect to server */ 709 char *s = argv[2]; 710 int p = atoi(argv[3]); 711 int sockfd = conn(s, p); 712 _fd_stdio_attach(sockfd); 713 } else { 714 usage(); 715 } 716 return 0; 717 }