1/* @(#) udpxy server: main module 2 * 3 * Copyright 2008-2011 Pavel V. Cherenkov (pcherenkov@gmail.com) 4 * 5 * This file is part of udpxy. 6 * 7 * udpxy is free software: you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation, either version 3 of the License, or 10 * (at your option) any later version. 11 * 12 * udpxy is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with udpxy. If not, see <http://www.gnu.org/licenses/>. 19 */ 20 21#include "osdef.h" /* os-specific definitions */ 22 23#include <sys/types.h> 24#include <sys/socket.h> 25#include <arpa/inet.h> 26#include <net/if.h> 27#include <sys/wait.h> 28#include <sys/stat.h> 29#include <sys/select.h> 30 31#include <netinet/in.h> 32#include <netinet/tcp.h> 33#include <sys/time.h> 34 35#include <unistd.h> 36#include <stdio.h> 37#include <string.h> 38#include <stdlib.h> 39#include <signal.h> 40#include <errno.h> 41#include <assert.h> 42#include <fcntl.h> 43#include <syslog.h> 44#include <time.h> 45#include <sys/uio.h> 46 47#include "mtrace.h" 48#include "rparse.h" 49#include "util.h" 50#include "prbuf.h" 51#include "ifaddr.h" 52 53#include "udpxy.h" 54#include "ctx.h" 55#include "mkpg.h" 56#include "rtp.h" 57#include "uopt.h" 58#include "dpkt.h" 59#include "netop.h" 60 61/* external globals */ 62 63extern const char CMD_UDP[]; 64extern const char CMD_STATUS[]; 65extern const char CMD_RESTART[]; 66extern const char CMD_RTP[]; 67 68extern const size_t CMD_UDP_LEN; 69extern const size_t CMD_STATUS_LEN; 70extern const size_t CMD_RESTART_LEN; 71extern const size_t CMD_RTP_LEN; 72 73extern const char IPv4_ALL[]; 74 75extern const char UDPXY_COPYRIGHT_NOTICE[]; 76extern const char UDPXY_CONTACT[]; 77 78extern FILE* g_flog; 79extern volatile sig_atomic_t g_quit; 80 81extern const char g_udpxy_app[]; 82 83/* globals 84 */ 85 86struct udpxy_opt g_uopt; 87 88static char g_app_info[ 80 ] = {0}; 89 90/* misc 91 */ 92 93static volatile sig_atomic_t g_childexit = 0; 94 95static const int PID_RESET = 1; 96 97/*********************************************************/ 98 99/* process client requests - implemented in sloop.c */ 100extern int srv_loop (const char* ipaddr, int port, 101 const char* mcast_addr); 102 103/* handler for signals to perform a graceful exit 104 */ 105static void 106handle_quitsigs(int signo) 107{ 108 g_quit = (sig_atomic_t)1; 109 (void) &signo; 110 111 TRACE( (void)tmfprintf( g_flog, "*** Caught SIGNAL %d ***\n", signo ) ); 112 return; 113} 114 115 116/* return 1 if the application must gracefully quit 117 */ 118sig_atomic_t must_quit() { return g_quit; } 119 120 121/* handle SIGCHLD 122 */ 123static void 124handle_sigchld(int signo) 125{ 126 (void) &signo; 127 g_childexit = (sig_atomic_t)1; 128 129 TRACE( (void)tmfprintf( g_flog, "*** Caught SIGCHLD (%d) ***\n", signo ) ); 130 return; 131} 132 133/* 134static int get_childexit() { return g_childexit; } 135*/ 136 137/* clear SIGCHLD flag and adjust context if needed 138 */ 139static void 140wait_children( struct server_ctx* ctx, int options ) 141{ 142 int status, n = 0; 143 pid_t pid; 144 145 assert( ctx ); 146 if (0 == g_childexit) { 147 TRACE( (void)tmfputs ("No children exited since last check\n", 148 g_flog) ); 149 return; 150 } 151 152 g_childexit = 0; 153 154 TRACE( (void)tmfputs ("Waiting on exited children\n", g_flog) ); 155 while( 0 < (pid = waitpid( -1, &status, options )) ) { 156 TRACE( (void)tmfprintf( g_flog, "Client [%d] has exited.\n", pid) ); 157 delete_client( ctx, pid ); 158 ++n; 159 } 160 161 if( (-1 == pid) && ( ECHILD != errno ) ) { 162 mperror(g_flog, errno, "%s: waitpid", __func__); 163 } 164 165 if (n > 0) { 166 TRACE( (void)tmfprintf (g_flog, "Cleaned up %d children, " 167 "%ld still running\n", n, (long)(ctx->clmax - ctx->clfree)) ); 168 } 169 170 return; 171} 172 173 174/* wait for all children to quit 175 */ 176void 177wait_all( struct server_ctx* ctx ) { wait_children( ctx, 0 ); } 178 179 180/* wait for the children who already terminated 181 */ 182void 183wait_terminated( struct server_ctx* ctx ) { wait_children( ctx, WNOHANG ); } 184 185 186/* print out array of accepted socket fd's */ 187static void 188print_fds (FILE* fp, const char* msg, tmfd_t* asock, size_t len) 189{ 190 size_t i; 191 192 (void) fprintf (fp, "%s [%ld]: ", msg, (long)len); 193 for (i = 0; i < len; ++i) { 194 (void) fprintf (fp, "%s%d", (i ? "," : ""), asock[i].fd); 195 } 196 fputc ('\n', fp); 197 198 return; 199} 200 201 202/* read HTTP request from sockfd, parse it into command 203 * and its parameters (for instance, command='udp' and 204 * parameters being '192.168.0.1:5002') 205 */ 206static int 207read_command( int sockfd, struct server_ctx *srv) 208{ 209#define DBUF_SZ 2048 /* max size for raw data with HTTP request */ 210#define RBUF_SZ 512 /* max size for url-derived request */ 211 char httpbuf[ DBUF_SZ ] = "\0", request[ RBUF_SZ ] = "\0"; 212 ssize_t hlen; 213 size_t rlen; 214 int rc = 0; 215 216 assert( (sockfd > 0) && srv ); 217 218 TRACE( (void)tmfprintf( g_flog, "Reading command from socket [%d]\n", 219 sockfd ) ); 220 hlen = recv( sockfd, httpbuf, sizeof(httpbuf), 0 ); 221 if( 0>hlen ) { 222 rc = errno; 223 if( !no_fault(rc) ) 224 mperror(g_flog, rc, "%s - recv (%d)", __func__, rc); 225 else { 226 TRACE( mperror(g_flog, rc, "%s - recv (%d)", __func__, rc) ); 227 } 228 return rc; 229 } 230 if (0 == hlen) { 231 (void) tmfprintf (g_flog, "%s: client closed socket [%d]\n", 232 __func__, sockfd); 233 return 1; 234 } 235 236 /* DEBUG - re-enable if needed */ 237 TRACE( (void)tmfprintf( g_flog, "HTTP buffer [%ld bytes] received\n%s", (long)hlen, httpbuf ) ); 238 /* TRACE( (void) save_buffer( httpbuf, hlen, "/tmp/httpbuf.dat" ) ); */ 239 240 rlen = sizeof(request); 241 rc = get_request( httpbuf, (size_t)hlen, request, &rlen ); 242 if (rc) return rc; 243 244 TRACE( (void)tmfprintf( g_flog, "Request=[%s], length=[%lu]\n", 245 request, (u_long)rlen ) ); 246 247 (void) memset( &srv->rq, 0, sizeof(srv->rq) ); 248 rc = parse_param( request, rlen, srv->rq.cmd, sizeof(srv->rq.cmd), 249 srv->rq.param, sizeof(srv->rq.param), 250 srv->rq.tail, sizeof(srv->rq.tail) ); 251 if( 0 == rc ) { 252 TRACE( (void)tmfprintf( g_flog, "Command [%s] with params [%s], tail [%s]" 253 " read from socket=[%d]\n", srv->rq.cmd, srv->rq.param, 254 srv->rq.tail, sockfd) ); 255 } 256 257 return rc; 258} 259 260 261/* terminate the client process 262 */ 263static int 264terminate( pid_t pid ) 265{ 266 TRACE( (void)tmfprintf( g_flog, "Forcing client process [%d] to QUIT\n", 267 pid) ); 268 269 if( pid <= 0 ) return 0; 270 271 if( 0 != kill( pid, SIGQUIT ) ) { 272 if( ESRCH != errno ) { 273 mperror(g_flog, errno, "%s - kill", __func__); 274 return ERR_INTERNAL; 275 } 276 /* ESRCH could mean client has quit already; 277 * if so we should wait for it */ 278 279 TRACE( (void)tmfprintf( g_flog, "Process [%d] is not running.\n", 280 pid ) ); 281 } 282 283 return 0; 284} 285 286 287/* terminate all clients 288 */ 289void 290terminate_all_clients( struct server_ctx* ctx ) 291{ 292 size_t i; 293 pid_t pid; 294 295 for( i = 0; i < ctx->clmax; ++i ) { 296 pid = ctx->cl[i].pid; 297 if( pid > 0 ) (void) terminate( pid ); 298 } 299 300 return; 301} 302 303 304/* send HTTP response to socket 305 */ 306static int 307send_http_response( int sockfd, int code, const char* reason) 308{ 309 static char msg[ 3072 ]; 310 ssize_t nsent; 311 a_socklen_t msglen; 312 int err = 0; 313 314 assert( (sockfd > 0) && code && reason ); 315 316 msg[0] = '\0'; 317 318 if ((200 == code) && g_uopt.h200_ftr[0]) { 319 msglen = snprintf( msg, sizeof(msg) - 1, "HTTP/1.1 %d %s\r\nServer: %s\r\n%s\r\n%s\r\n\r\n", 320 code, reason, g_app_info, g_uopt.cnt_type, g_uopt.h200_ftr); 321 } 322 else { 323 msglen = snprintf( msg, sizeof(msg) - 1, "HTTP/1.1 %d %s\r\nServer: %s\r\n%s\r\n\r\n", 324 code, reason, g_app_info, g_uopt.cnt_type ); 325 } 326 if( msglen <= 0 ) return ERR_INTERNAL; 327 328 nsent = send( sockfd, msg, msglen, 0 ); 329 if( -1 == nsent ) { 330 err = errno; 331 if( !no_fault(err) ) 332 mperror(g_flog, err, "%s - send", __func__); 333 else { 334 TRACE( mperror(g_flog, err, "%s - send", __func__) ); 335 } 336 return ERR_INTERNAL; 337 } 338 339 TRACE( (void)tmfprintf( g_flog, "Sent HTTP response code=[%d], " 340 "reason=[%s] to socket=[%d]\n%s\n", 341 code, reason, sockfd, msg) ); 342 return 0; 343} 344 345 346/* renew multicast subscription if g_uopt.mcast_refresh seconds 347 * have passed since the last renewal 348 */ 349static void 350check_mcast_refresh( int msockfd, time_t* last_tm, 351 const struct in_addr* mifaddr ) 352{ 353 time_t now = 0; 354 355 if( NULL != g_uopt.srcfile ) /* reading from file */ 356 return; 357 358 assert( (msockfd > 0) && last_tm && mifaddr ); 359 now = time(NULL); 360 361 if( now - *last_tm >= g_uopt.mcast_refresh ) { 362 (void) renew_multicast( msockfd, mifaddr ); 363 *last_tm = now; 364 } 365 366 return; 367} 368 369 370/* analyze return value of I/O routines (read_data/write_data) 371 * and the pause-time marker to determine if we are in a 372 * PAUSE state 373 */ 374static int 375pause_detect( int ntrans, ssize_t max_pause_msec, time_t* p_pause ) 376{ 377 time_t now = 0; 378 const double MAX_PAUSE_SEC = (double)max_pause_msec / 1000.0; 379 380 assert( p_pause && MAX_PAUSE_SEC > 0.0 ); 381 382 /* timeshift: detect PAUSE by would-block error */ 383 if (IO_BLK == ntrans) { 384 now = time(NULL); 385 386 if (*p_pause) { 387 if( difftime(now, *p_pause) > MAX_PAUSE_SEC ) { 388 TRACE( (void)tmfprintf( g_flog, 389 "PAUSE timed out after [%.0f] seconds\n", 390 MAX_PAUSE_SEC ) ); 391 return -1; 392 } 393 } 394 else { 395 *p_pause = now; 396 TRACE( (void)tmfprintf( g_flog, "PAUSE started\n" ) ); 397 } 398 } 399 else { 400 if (*p_pause) { 401 *p_pause = 0; 402 TRACE( (void)tmfprintf( g_flog, "PAUSE ended\n" ) ); 403 } 404 } 405 406 return 0; 407} 408 409 410/* calculate values for: 411 * 1. number of messages to fit into data buffer 412 * 2. recommended (minimal) size of socket buffer 413 * (to read into the data buffer) 414 */ 415static int 416calc_buf_settings( ssize_t* bufmsgs, size_t* sock_buflen ) 417{ 418 ssize_t nmsgs = -1, max_buf_used = -1, env_snd_buflen = -1; 419 size_t buflen = 0; 420 421 /* how many messages should we process? */ 422 nmsgs = (g_uopt.rbuf_msgs > 0) ? g_uopt.rbuf_msgs : 423 (int)g_uopt.rbuf_len / ETHERNET_MTU; 424 425 /* how many bytes could be written at once 426 * to the send socket */ 427 max_buf_used = (g_uopt.rbuf_msgs > 0) 428 ? (ssize_t)(nmsgs * ETHERNET_MTU) : g_uopt.rbuf_len; 429 if (max_buf_used > g_uopt.rbuf_len) { 430 max_buf_used = g_uopt.rbuf_len; 431 } 432 433 assert( max_buf_used >= 0 ); 434 435 env_snd_buflen = get_sizeval( "UDPXY_SOCKBUF_LEN", 0); 436 buflen = (env_snd_buflen > 0) ? (size_t)env_snd_buflen : (size_t)max_buf_used; 437 438 if (buflen < (size_t) MIN_SOCKBUF_LEN) { 439 buflen = (size_t) MIN_SOCKBUF_LEN; 440 } 441 442 /* cannot go below the size of effective usage */ 443 if( buflen < (size_t)max_buf_used ) { 444 buflen = (size_t)max_buf_used; 445 } 446 447 if (bufmsgs) *bufmsgs = nmsgs; 448 if (sock_buflen) *sock_buflen = buflen; 449 450 TRACE( (void)tmfprintf( g_flog, 451 "min socket buffer = [%ld], " 452 "max space to use = [%ld], " 453 "Rmsgs = [%ld]\n", 454 (long)buflen, (long)max_buf_used, (long)nmsgs ) ); 455 456 return 0; 457} 458 459 460/* make send-socket (dsockfd) buffer size no less 461 * than that of read-socket (ssockfd) 462 */ 463static int 464sync_dsockbuf_len( int ssockfd, int dsockfd ) 465{ 466 size_t curr_sendbuf_len = 0, curr_rcvbuf_len = 0; 467 int rc = 0; 468 469 if ( 0 != g_uopt.nosync_dbuf ) { 470 TRACE( (void)tmfprintf( g_flog, 471 "Must not adjust buffer size " 472 "for send socket [%d]\n", dsockfd) ); 473 return 0; 474 } 475 476 assert( ssockfd && dsockfd ); 477 478 rc = get_sendbuf( dsockfd, &curr_sendbuf_len ); 479 if (0 != rc) return rc; 480 481 rc = get_rcvbuf( ssockfd, &curr_rcvbuf_len ); 482 if (0 != rc) return rc; 483 484 if ( curr_rcvbuf_len > curr_sendbuf_len ) { 485 rc = set_sendbuf( dsockfd, curr_rcvbuf_len ); 486 if (0 != rc) return rc; 487 } 488 489 return rc; 490} 491 492 493/* relay traffic from source to destination socket 494 * 495 */ 496static int 497relay_traffic( int ssockfd, int dsockfd, struct server_ctx* ctx, 498 int dfilefd, const struct in_addr* mifaddr ) 499{ 500 volatile sig_atomic_t quit = 0; 501 502 int rc = 0; 503 ssize_t nmsgs = -1; 504 ssize_t nrcv = 0, nsent = 0, nwr = 0, 505 lrcv = 0, lsent = 0; 506 char* data = NULL; 507 size_t data_len = g_uopt.rbuf_len; 508 struct rdata_opt ropt; 509 time_t pause_time = 0, rfr_tm = time(NULL); 510 sigset_t ubset; 511 512 const int ALLOW_PAUSES = get_flagval( "UDPXY_ALLOW_PAUSES", 0 ); 513 const ssize_t MAX_PAUSE_MSEC = 514 get_sizeval( "UDPXY_PAUSE_MSEC", 1000); 515 516 /* permissible variation in data-packet size */ 517 static const ssize_t t_delta = 0x20; 518 519 struct dstream_ctx ds; 520 521 static const int SET_PID = 1; 522 struct tps_data tps; 523 524 assert( ctx && mifaddr && MAX_PAUSE_MSEC > 0 ); 525 526 (void) sigemptyset (&ubset); 527 sigaddset (&ubset, SIGINT); 528 sigaddset (&ubset, SIGQUIT); 529 sigaddset (&ubset, SIGTERM); 530 531 /* restore the ability to receive *quit* signals */ 532 rc = sigprocmask (SIG_UNBLOCK, &ubset, NULL); 533 if (0 != rc) { 534 mperror (g_flog, errno, "%s: sigprocmask", __func__); 535 return -1; 536 } 537 538 /* NOPs to eliminate warnings in lean version */ 539 (void)&lrcv; (void)&lsent; (void)&t_delta; 540 541 check_fragments( NULL, 0, 0, 0, 0, g_flog ); 542 543 /* INIT 544 */ 545 546 rc = calc_buf_settings( &nmsgs, NULL ); 547 if (0 != rc) return -1; 548 549 TRACE( (void)tmfprintf( g_flog, "Data buffer will hold up to " 550 "[%d] messages\n", nmsgs ) ); 551 552 rc = init_dstream_ctx( &ds, ctx->rq.cmd, g_uopt.srcfile, nmsgs ); 553 if( 0 != rc ) return -1; 554 555 (void) set_nice( g_uopt.nice_incr, g_flog ); 556 557 do { 558 if( NULL == g_uopt.srcfile ) { 559 rc = set_timeouts( ssockfd, dsockfd, 560 ctx->rcv_tmout, 0, 561 ctx->snd_tmout, 0 ); 562 if( 0 != rc ) break; 563 } 564 565 if( dsockfd > 0 ) { 566 rc = sync_dsockbuf_len( ssockfd, dsockfd ); 567 if( 0 != rc ) break; 568 569 rc = send_http_response( dsockfd, 200, "OK" ); 570 if( 0 != rc ) break; 571 572 /* timeshift: to detect PAUSE make destination 573 * socket non-blocking, otherwise make it blocking 574 * (since it might have been set unblocking earlier) 575 */ 576 rc = set_nblock( dsockfd, (ALLOW_PAUSES ? 1 : 0) ); 577 if( 0 != rc ) break; 578 } 579 580 data = malloc(data_len); 581 if( NULL == data ) { 582 mperror( g_flog, errno, "%s: malloc", __func__ ); 583 break; 584 } 585 586 if( g_uopt.cl_tpstat ) 587 tpstat_init( &tps, SET_PID ); 588 } while(0); 589 590 TRACE( (void)tmfprintf( g_flog, "Relaying traffic from socket[%d] " 591 "to socket[%d], buffer size=[%d], Rmsgs=[%d], pauses=[%d]\n", 592 ssockfd, dsockfd, data_len, g_uopt.rbuf_msgs, ALLOW_PAUSES) ); 593 594 /* RELAY LOOP 595 */ 596 ropt.max_frgs = g_uopt.rbuf_msgs; 597 ropt.buf_tmout = g_uopt.dhold_tmout; 598 599 pause_time = 0; 600 601 while( (0 == rc) && !(quit = must_quit()) ) { 602 if( g_uopt.mcast_refresh > 0 ) { 603 check_mcast_refresh( ssockfd, &rfr_tm, mifaddr ); 604 } 605 606 nrcv = read_data( &ds, ssockfd, data, data_len, &ropt ); 607 if( -1 == nrcv ) break; 608 609 TRACE( check_fragments( "received new", data_len, 610 lrcv, nrcv, t_delta, g_flog ) ); 611 lrcv = nrcv; 612 613 if( dsockfd && (nrcv > 0) ) { 614 nsent = write_data( &ds, data, nrcv, dsockfd ); 615 if( -1 == nsent ) break; 616 617 if ( nsent < 0 ) { 618 if ( !ALLOW_PAUSES ) break; 619 if ( 0 != pause_detect( nsent, MAX_PAUSE_MSEC, &pause_time ) ) 620 break; 621 } 622 623 TRACE( check_fragments("sent", nrcv, 624 lsent, nsent, t_delta, g_flog) ); 625 lsent = nsent; 626 } 627 628 if( (dfilefd > 0) && (nrcv > 0) ) { 629 nwr = write_data( &ds, data, nrcv, dfilefd ); 630 if( -1 == nwr ) 631 break; 632 TRACE( check_fragments( "wrote to file", 633 nrcv, lsent, nwr, t_delta, g_flog ) ); 634 lsent = nwr; 635 } 636 637 if( ds.flags & F_SCATTERED ) reset_pkt_registry( &ds ); 638 639 if( uf_TRUE == g_uopt.cl_tpstat ) 640 tpstat_update( ctx, &tps, nsent ); 641 642 } /* end of RELAY LOOP */ 643 644 /* CLEANUP 645 */ 646 TRACE( (void)tmfprintf( g_flog, "Exited relay loop: received=[%ld], " 647 "sent=[%ld], quit=[%ld]\n", (long)nrcv, (long)nsent, (long)quit ) ); 648 649 free_dstream_ctx( &ds ); 650 if( NULL != data ) free( data ); 651 652 if( 0 != (quit = must_quit()) ) { 653 TRACE( (void)tmfprintf( g_flog, "Child process=[%d] must quit\n", 654 getpid()) ); 655 } 656 657 return rc; 658} 659 660 661/* process command to relay udp traffic 662 * 663 */ 664static int 665udp_relay( int sockfd, struct server_ctx* ctx ) 666{ 667 char mcast_addr[ IPADDR_STR_SIZE ]; 668 struct sockaddr_in addr; 669 670 uint16_t port; 671 pid_t new_pid; 672 int rc = 0, flags; 673 int msockfd = -1, sfilefd = -1, 674 dfilefd = -1, srcfd = -1; 675 char dfile_name[ MAXPATHLEN ]; 676 size_t rcvbuf_len = 0; 677 678 const struct in_addr *mifaddr = &(ctx->mcast_inaddr); 679 680 assert( (sockfd > 0) && ctx ); 681 682 TRACE( (void)tmfprintf( g_flog, "udp_relay : new_socket=[%d] param=[%s]\n", 683 sockfd, ctx->rq.param) ); 684 do { 685 rc = parse_udprelay( ctx->rq.param, sizeof(ctx->rq.param), 686 mcast_addr, IPADDR_STR_SIZE, &port ); 687 if( 0 != rc ) { 688 (void) tmfprintf( g_flog, "Error [%d] parsing parameters [%s]\n", 689 rc, ctx->rq.param ); 690 break; 691 } 692 693 if( 1 != inet_aton(mcast_addr, &addr.sin_addr) ) { 694 (void) tmfprintf( g_flog, "Invalid address: [%s]\n", mcast_addr ); 695 rc = ERR_INTERNAL; 696 break; 697 } 698 699 addr.sin_family = AF_INET; 700 addr.sin_port = htons( (short)port ); 701 702 } while(0); 703 704 if( 0 != rc ) { 705 (void) send_http_response( sockfd, 400, "Invalid address" ); 706 return rc; 707 } 708 709 /* start the (new) process to relay traffic */ 710 711 if( 0 != (new_pid = fork()) ) { 712 rc = add_client( ctx, new_pid, mcast_addr, port, sockfd ); 713 return rc; /* parent returns */ 714 } 715 716 /* child process: 717 */ 718 TRACE( (void)tmfprintf( g_flog, "Client process=[%d] started " 719 "for socket=[%d]\n", getpid(), sockfd) ); 720 721 (void) get_pidstr( PID_RESET, "c" ); 722 723 (void)close( ctx->lsockfd ); 724 725 /* close the reading end of the comm. pipe */ 726 (void)close( ctx->cpipe[0] ); 727 ctx->cpipe[0] = -1; 728 729 do { 730 /* make write end of pipe non-blocking (we don't want to 731 * block on pipe write while relaying traffic) 732 */ 733 if( -1 == (flags = fcntl( ctx->cpipe[1], F_GETFL )) || 734 -1 == fcntl( ctx->cpipe[1], F_SETFL, flags | O_NONBLOCK ) ) { 735 mperror( g_flog, errno, "%s: fcntl", __func__ ); 736 rc = -1; 737 break; 738 } 739 740 if( NULL != g_uopt.dstfile ) { 741 (void) snprintf( dfile_name, MAXPATHLEN - 1, 742 "%s.%d", g_uopt.dstfile, getpid() ); 743 dfilefd = creat( dfile_name, S_IRUSR | S_IWUSR | S_IRGRP ); 744 if( -1 == dfilefd ) { 745 mperror( g_flog, errno, "%s: g_uopt.dstfile open", __func__ ); 746 rc = -1; 747 break; 748 } 749 750 TRACE( (void)tmfprintf( g_flog, 751 "Dest file [%s] opened as fd=[%d]\n", 752 dfile_name, dfilefd ) ); 753 } 754 else dfilefd = -1; 755 756 if( NULL != g_uopt.srcfile ) { 757 sfilefd = open( g_uopt.srcfile, O_RDONLY | O_NOCTTY ); 758 if( -1 == sfilefd ) { 759 mperror( g_flog, errno, "%s: g_uopt.srcfile open", __func__ ); 760 rc = -1; 761 } 762 else { 763 TRACE( (void) tmfprintf( g_flog, "Source file [%s] opened\n", 764 g_uopt.srcfile ) ); 765 srcfd = sfilefd; 766 } 767 } 768 else { 769 rc = calc_buf_settings( NULL, &rcvbuf_len ); 770 if (0 == rc ) { 771 rc = setup_mcast_listener( &addr, mifaddr, &msockfd, 772 (g_uopt.nosync_sbuf ? 0 : rcvbuf_len) ); 773 srcfd = msockfd; 774 } 775 } 776 if( 0 != rc ) break; 777 778 rc = relay_traffic( srcfd, sockfd, ctx, dfilefd, mifaddr ); 779 if( 0 != rc ) break; 780 781 } while(0); 782 783 if( msockfd > 0 ) { 784 close_mcast_listener( msockfd, mifaddr ); 785 } 786 if( sfilefd > 0 ) { 787 (void) close( sfilefd ); 788 TRACE( (void) tmfprintf( g_flog, "Source file [%s] closed\n", 789 g_uopt.srcfile ) ); 790 } 791 if( dfilefd > 0 ) { 792 (void) close( dfilefd ); 793 TRACE( (void) tmfprintf( g_flog, "Dest file [%s] closed\n", 794 dfile_name ) ); 795 } 796 797 if( 0 != rc ) { 798 (void) send_http_response( sockfd, 500, "Service error" ); 799 } 800 801 (void) close( sockfd ); 802 free_server_ctx( ctx ); 803 804 closelog(); 805 806 TRACE( (void)tmfprintf( g_flog, "Child process=[%d] exits with rc=[%d]\n", 807 getpid(), rc) ); 808 809 if( g_flog && (stderr != g_flog) ) { 810 (void) fclose(g_flog); 811 } 812 813 free_uopt( &g_uopt ); 814 815 rc = ( 0 != rc ) ? ERR_INTERNAL : rc; 816 exit(rc); /* child exits */ 817 818 return rc; 819} 820 821 822/* send server status as HTTP response to the given socket 823 */ 824static int 825report_status( int sockfd, const struct server_ctx* ctx, int options ) 826{ 827 char *buf = NULL; 828 int rc = 0; 829 ssize_t n = -1; 830 size_t nlen = 0, bufsz, i; 831 struct client_ctx *clc = NULL; 832 833 enum {BLOCKING = 0, NON_BLOCKING = 1}; 834 enum {BYTES_HDR = 4096, BYTES_PER_CLI = 512}; 835 836 assert( (sockfd > 0) && ctx ); 837 838 bufsz = BYTES_HDR; 839 for (i = 0, clc=ctx->cl; i < ctx->clmax; ++i, ++clc) { 840 if( ctx->cl[i].pid > 0 ) 841 bufsz += BYTES_PER_CLI + strlen(clc->tail); 842 } 843 844 buf = malloc(bufsz); 845 if( !buf ) { 846 mperror(g_flog, ENOMEM, "malloc for %ld bytes for HTTP buffer " 847 "failed in %s", (long)bufsz, __func__ ); 848 return ERR_INTERNAL; 849 } 850 851 (void) memset( buf, 0, sizeof(bufsz) ); 852 853 nlen = bufsz; 854 rc = mk_status_page( ctx, buf, &nlen, options | MSO_HTTP_HEADER ); 855 856 (void) set_nblock(sockfd, BLOCKING); 857 n = send( sockfd, buf, (int)nlen, 0 ); 858 if( (-1 == n) && (EINTR != errno) ) { 859 mperror(g_flog, errno, "%s: send", __func__); 860 rc = ERR_INTERNAL; 861 } 862 (void) set_nblock(sockfd, NON_BLOCKING); 863 864 if( 0 != rc ) { 865 TRACE( (void)tmfprintf( g_flog, "Error generating status report\n" ) ); 866 } 867 else { 868 /* DEBUG only 869 TRACE( (void)tmfprintf( g_flog, "Saved status buffer to file\n" ) ); 870 TRACE( (void)save_buffer(buf, nlen, "/tmp/status-udpxy.html") ); 871 */ 872 } 873 874 free(buf); 875 return rc; 876} 877 878 879/* process command within a request 880 */ 881static int 882process_command( int new_sockfd, struct server_ctx* ctx ) 883{ 884 int rc = 0; 885 const int STAT_OPTIONS = 0; 886 const int RESTART_OPTIONS = MSO_SKIP_CLIENTS | MSO_RESTART; 887 888 assert( (new_sockfd > 0) && ctx ); 889 890 if( 0 == strncmp( ctx->rq.cmd, CMD_UDP, sizeof(ctx->rq.cmd) ) || 891 0 == strncmp( ctx->rq.cmd, CMD_RTP, sizeof(ctx->rq.cmd) ) ) { 892 if( ctx->clfree ) { 893 rc = udp_relay( new_sockfd, ctx ); 894 } 895 else { 896 send_http_response( new_sockfd, 503, "Client limit reached" ); 897 (void)tmfprintf( g_flog, "Client limit [%d] has been reached.\n", 898 ctx->clmax); 899 } 900 } 901 else if( 0 == strncmp( ctx->rq.cmd, CMD_STATUS, sizeof(ctx->rq.cmd) ) ) { 902 rc = report_status( new_sockfd, ctx, STAT_OPTIONS ); 903 } 904 else if( 0 == strncmp( ctx->rq.cmd, CMD_RESTART, sizeof(ctx->rq.cmd) ) ) { 905 (void) report_status( new_sockfd, ctx, RESTART_OPTIONS ); 906 907 terminate_all_clients( ctx ); 908 wait_all( ctx ); 909 } 910 else { 911 TRACE( (void)tmfprintf( g_flog, "Unrecognized command [%s]" 912 " - ignoring.\n", ctx->rq.cmd) ); 913 send_http_response( new_sockfd, 400, "Unrecognized request" ); 914 } 915 916 return rc; 917} 918 919 920void 921accept_requests (int sockfd, tmfd_t* asock, size_t* alen) 922{ 923 int new_sockfd = -1, err = 0, peer_port = -1, 924 wmark = g_uopt.rcv_lwmark; 925 size_t nmax = *alen, naccepted = 0; 926 struct sockaddr_in cliaddr; 927 a_socklen_t addrlen = sizeof (cliaddr); 928 char peer_addr [128] = "#undef#"; 929 static const int YES = 1; 930 931 while (naccepted < nmax) { 932 TRACE( (void)tmfputs ("Accepting new connection\n", g_flog) ); 933 934 new_sockfd = accept (sockfd, (struct sockaddr*)&cliaddr, &addrlen ); 935 if (-1 == new_sockfd) { 936 err = errno; 937 if ((EWOULDBLOCK == err) || (EAGAIN == err)) { 938 TRACE((void)tmfputs ("Nothing more to accept\n", g_flog)); 939 break; 940 } 941 if ((ECONNABORTED == err) || (ECONNRESET == err) || (EPROTO == err)) { 942 TRACE( (void)tmfprintf (g_flog, "Connection aborted/reset " 943 "at accept point, errno=%d\n", err) ); 944 continue; 945 } 946 947 mperror(g_flog, err, "%s: accept", __func__); 948 break; 949 } 950 951 if (0 != set_nblock (new_sockfd, 1)) { 952 (void) close (new_sockfd); /* TODO: error-aware close */ 953 continue; 954 } 955 /* 956 if (0 != set_timeouts(new_sockfd, new_sockfd, g_uopt.sr_tmout, 0, 957 g_uopt.sw_tmout, 0)) { 958 (void) close (new_sockfd); 959 continue; 960 } 961 */ 962 if (wmark > 0) { 963 if (0 != setsockopt (new_sockfd, SOL_SOCKET, SO_RCVLOWAT, 964 (char*)&wmark, sizeof(wmark))) { 965 mperror (g_flog, errno, "%s: setsockopt SO_RCVLOWAT [%d]", 966 __func__, wmark); 967 (void) close (new_sockfd); /* TODO: error-aware close */ 968 continue; 969 } else { 970 TRACE( (void)tmfprintf (g_flog, "Receive LOW WATERMARK [%d] applied " 971 "to newly-accepted socket [%d]\n", wmark, new_sockfd) ); 972 } 973 } 974 975 if (g_uopt.tcp_nodelay) { 976 if (0 != setsockopt(new_sockfd, IPPROTO_TCP, 977 TCP_NODELAY, &YES, sizeof(YES))) { 978 mperror(g_flog, errno, "%s setsockopt TCP_NODELAY", 979 __func__); 980 } 981 } 982 983 asock [naccepted].fd = new_sockfd; 984 asock [naccepted].atime = time (NULL); 985 ++naccepted; 986 987 (void) get_peerinfo (new_sockfd, peer_addr, 988 sizeof(peer_addr)-1, &peer_port); 989 990 TRACE( (void)tmfprintf( g_flog, "Accepted socket=[%d] from %s:%d " 991 "n=%ld/nmax=%ld\n", new_sockfd, peer_addr, peer_port, 992 (long)naccepted, (long)nmax) ); 993 } /* while */ 994 995 if (naccepted >= nmax) { 996 (void)tmfprintf (g_flog, "Accept limit max=[%d] reached, " 997 "%ld already accepted", (long)nmax, (long)naccepted); 998 } 999 1000 *alen = naccepted; 1001 TRACE( (void)tmfprintf (g_flog, "%s: Sockets accepted: [%ld]\n", 1002 __func__, (long)naccepted)); 1003 return; 1004} 1005 1006 1007static void 1008shrink_asock (tmfd_t* asock, size_t* alen, size_t nserved) 1009{ 1010 size_t nmax = *alen; 1011 long j = 0, v = 0; 1012 1013 (void) &print_fds; 1014 1015 /* uncomment to DEBUG 1016 TRACE( print_fds (g_flog, "unshrunk accepted sockets", asock, *alen) ); 1017 */ 1018 1019 (void) nserved; 1020 1021 for (; j < (long)nmax; ++j) { 1022 if (-1 == asock[j].fd) { 1023 ++v; 1024 continue; 1025 } 1026 1027 if (v > 0) { 1028 asock[j - v].fd = asock[j].fd; 1029 asock[j - v].atime = asock[j].atime; 1030 asock[j].fd = -1; 1031 } 1032 } 1033 1034 assert ((long)nserved == v); 1035 *alen = nmax - (size_t)v; 1036 1037 TRACE ( (void)tmfprintf (g_flog, "%s: %ld shrunk, was %ld now %ld\n", 1038 __func__, (long)v, (long)nmax, (long)*alen ) ); 1039 1040 /* uncomment to DEBUG 1041 TRACE( print_fds (g_flog, "remaining accepted sockets", asock, *alen) ); 1042 */ 1043} 1044 1045 1046void 1047tmout_requests (tmfd_t* asock, size_t *alen) 1048{ 1049 size_t nmax = *alen, i = 0, nout = 0; 1050 time_t now = time (NULL); 1051 1052 TRACE( (void)tmfprintf (g_flog, "%s: BEGIN with %ld sockets\n", 1053 __func__, (long)*alen) ); 1054 1055 for (; i < nmax; ++i) { 1056 assert ((asock[i].fd >= 0) && asock[i].atime); 1057 if ((asock[i].atime + g_uopt.ssel_tmout) < now) { 1058 TRACE( (void)tmfprintf (g_flog, "%s: timed out socket #%d [%d], " 1059 "atime/now/tmout=%ld/%ld/%ld\n", __func__, (i+1), asock[i].fd, 1060 (long)asock[i].atime, (long)now, g_uopt.ssel_tmout) ); 1061 1062 (void) close (asock[i].fd); 1063 asock[i].fd = -1; 1064 ++nout; 1065 } 1066 } 1067 shrink_asock (asock, alen, nout); /* will adjust alen */ 1068 1069 TRACE( (void)tmfprintf (g_flog, "%s: END with %ld sockets\n", 1070 __func__, (long)*alen) ); 1071 return; 1072} 1073 1074void 1075process_requests (tmfd_t* asock, size_t *alen, fd_set* rset, struct server_ctx* srv) 1076{ 1077 size_t nmax = *alen, i = 0, nserved = 0; 1078 int rc = 0, served = 0; 1079 time_t now = time (NULL); 1080 1081 /* uncomment to DEBUG */ 1082 TRACE( print_fds (g_flog, "pre-process sockets", asock, nmax) ); 1083 1084 for (; i < nmax; ++i, served = 0) { 1085 assert (asock[i].fd >= 0); 1086 assert (asock[i].atime > 0); 1087 1088 do { 1089 /* not selected - yet try to time it out */ 1090 if (!FD_ISSET(asock[i].fd, rset)) { 1091 if ((asock[i].atime + g_uopt.ssel_tmout) < now) { 1092 TRACE( (void)tmfprintf (g_flog, 1093 "%s: accepted socket [%ld] timed out\n", 1094 __func__, (long)asock[i].fd) ); 1095 ++served; /* timed out - must close */ 1096 } 1097 break; 1098 } 1099 1100 /* selected */ 1101 TRACE( (void)tmfprintf (g_flog, "acting on accepted socket " 1102 "[%d] (%d/%d)\n", asock[i].fd, i+1, nmax) ); 1103 1104 ++served; /* selected - must close regardless */ 1105 rc = read_command(asock[i].fd, srv); 1106 if( 0 != rc ) break; 1107 1108 rc = process_command(asock[i].fd, srv); 1109 } while (0); 1110 1111 if (0 != rc) { 1112 TRACE( (void)tmfprintf (g_flog, "error [%d] processing " 1113 "client socket [%d]\n", rc, asock[i])); 1114 } 1115 1116 TRACE( (void)tmfprintf (g_flog, "%s: %s accepted " 1117 "socket [%d]\n", __func__, (served ? "closing" : "skipping"), 1118 asock[i].fd) ); 1119 1120 if (served) { 1121 (void) close (asock[i].fd); 1122 asock[i].fd = -1; 1123 ++nserved; 1124 } 1125 } /* for */ 1126 1127 TRACE( (void)tmfprintf (g_flog, "Processed [%ld/%ld] accepted sockets\n", 1128 (long)nserved, (long)nmax) ); 1129 TRACE( print_fds (g_flog, "newly-accepted sockets", asock, nmax) ); 1130 1131 if (nserved >= nmax) { 1132 *alen = 0; 1133 TRACE( (void)tmfputs ("All accepted sockets processed\n", g_flog) ); 1134 } 1135 else { 1136 shrink_asock (asock, alen, nserved); 1137 } 1138 1139 return; 1140} 1141 1142 1143static void 1144usage( const char* app, FILE* fp ) 1145{ 1146 (void) fprintf (fp, "%s\n", g_app_info); 1147 (void) fprintf (fp, "usage: %s [-vTS] [-a listenaddr] -p port " 1148 "[-m mcast_ifc_addr] [-c clients] [-l logfile] " 1149 "[-B sizeK] [-n nice_incr]\n", app ); 1150 (void) fprintf(fp, 1151 "\t-v : enable verbose output [default = disabled]\n" 1152 "\t-S : enable client statistics [default = disabled]\n" 1153 "\t-T : do NOT run as a daemon [default = daemon if root]\n" 1154 "\t-a : (IPv4) address/interface to listen on [default = %s]\n" 1155 "\t-p : port to listen on\n" 1156 "\t-m : (IPv4) address/interface of (multicast) " 1157 "source [default = %s]\n" 1158 "\t-c : max clients to serve [default = %d, max = %d]\n", 1159 IPv4_ALL, IPv4_ALL, DEFAULT_CLIENT_COUNT, MAX_CLIENT_COUNT); 1160 (void)fprintf(fp, 1161 "\t-l : log output to file [default = stderr]\n" 1162 "\t-B : buffer size (65536, 32Kb, 1Mb) for inbound (multicast) data [default = %ld bytes]\n" 1163 "\t-R : maximum messages to store in buffer (-1 = all) " 1164 "[default = %d]\n" 1165 "\t-H : maximum time (sec) to hold data in buffer " 1166 "(-1 = unlimited) [default = %d]\n" 1167 "\t-n : nice value increment [default = %d]\n" 1168 "\t-M : periodically renew multicast subscription (skip if 0 sec) [default = %d sec]\n", 1169 (long)DEFAULT_CACHE_LEN, g_uopt.rbuf_msgs, DHOLD_TIMEOUT, g_uopt.nice_incr, 1170 (int)g_uopt.mcast_refresh ); 1171 (void) fprintf( fp, "Examples:\n" 1172 " %s -p 4022 \n" 1173 "\tlisten for HTTP requests on port 4022, all network interfaces\n" 1174 " %s -a lan0 -p 4022 -m lan1\n" 1175 "\tlisten for HTTP requests on interface lan0, port 4022;\n" 1176 "\tsubscribe to multicast groups on interface lan1\n", 1177 app, app); 1178 (void) fprintf( fp, "\n %s\n", UDPXY_COPYRIGHT_NOTICE ); 1179 (void) fprintf( fp, " %s\n\n", UDPXY_CONTACT ); 1180 return; 1181} 1182 1183 1184extern int udpxy_main( int argc, char* const argv[] ); 1185 1186int 1187udpxy_main( int argc, char* const argv[] ) 1188{ 1189 int rc = 0, ch = 0, port = -1, 1190 custom_log = 0, no_daemon = 0; 1191 1192 char ipaddr[IPADDR_STR_SIZE] = "\0", 1193 mcast_addr[IPADDR_STR_SIZE] = "\0"; 1194 1195 char pidfile[ MAXPATHLEN ] = "\0"; 1196 u_short MIN_MCAST_REFRESH = 0, MAX_MCAST_REFRESH = 0; 1197 1198/* support for -r -w (file read/write) option is disabled by default; 1199 * those features are experimental and for dev debugging ONLY 1200 * */ 1201#ifdef UDPXY_FILEIO 1202 static const char UDPXY_OPTMASK[] = "TvSa:l:p:m:c:B:n:R:r:w:H:M:"; 1203#else 1204 static const char UDPXY_OPTMASK[] = "TvSa:l:p:m:c:B:n:R:H:M:"; 1205#endif 1206 1207 struct sigaction qact, iact, cact, oldact; 1208 1209 mk_app_info(g_udpxy_app, g_app_info, sizeof(g_app_info) - 1); 1210 (void) get_pidstr( PID_RESET, "S" ); 1211 1212 rc = init_uopt( &g_uopt ); 1213 while( (0 == rc) && (-1 != (ch = getopt(argc, argv, UDPXY_OPTMASK))) ) { 1214 switch( ch ) { 1215 case 'v': set_verbose( &g_uopt.is_verbose ); 1216 break; 1217 case 'T': no_daemon = 1; 1218 break; 1219 case 'S': g_uopt.cl_tpstat = uf_TRUE; 1220 break; 1221 case 'a': 1222 rc = get_ipv4_address( optarg, ipaddr, sizeof(ipaddr) ); 1223 if( 0 != rc ) { 1224 (void) fprintf( stderr, "Invalid address: [%s]\n", 1225 optarg ); 1226 rc = ERR_PARAM; 1227 } 1228 break; 1229 1230 case 'p': 1231 port = atoi( optarg ); 1232 if( port <= 0 ) { 1233 (void) fprintf( stderr, "Invalid port number: [%d]\n", 1234 port ); 1235 rc = ERR_PARAM; 1236 } 1237 break; 1238 1239 case 'm': 1240 rc = get_ipv4_address( optarg, mcast_addr, 1241 sizeof(mcast_addr) ); 1242 if( 0 != rc ) { 1243 (void) fprintf( stderr, "Invalid multicast address: " 1244 "[%s]\n", optarg ); 1245 rc = ERR_PARAM; 1246 } 1247 break; 1248 1249 case 'c': 1250 g_uopt.max_clients = atoi( optarg ); 1251 if( (g_uopt.max_clients < MIN_CLIENT_COUNT) || 1252 (g_uopt.max_clients > MAX_CLIENT_COUNT) ) { 1253 (void) fprintf( stderr, 1254 "Client count should be between %d and %d\n", 1255 MIN_CLIENT_COUNT, MAX_CLIENT_COUNT ); 1256 rc = ERR_PARAM; 1257 } 1258 break; 1259 1260 case 'l': 1261 g_flog = fopen( optarg, "a" ); 1262 if( NULL == g_flog ) { 1263 rc = errno; 1264 (void) fprintf( stderr, "Error opening logfile " 1265 "[%s]: %s\n", 1266 optarg, strerror(rc) ); 1267 rc = ERR_PARAM; 1268 break; 1269 } 1270 1271 Setlinebuf( g_flog ); 1272 custom_log = 1; 1273 break; 1274 1275 case 'B': 1276 rc = a2size(optarg, &g_uopt.rbuf_len); 1277 if( 0 != rc ) { 1278 (void) fprintf( stderr, "Invalid buffer size: [%s]\n", 1279 optarg ); 1280 exit( ERR_PARAM ); 1281 } 1282 else if( (g_uopt.rbuf_len < MIN_MCACHE_LEN) || 1283 (g_uopt.rbuf_len > MAX_MCACHE_LEN) ) { 1284 fprintf(stderr, "Buffer size " 1285 "must be within [%ld-%ld] bytes\n", 1286 (long)MIN_MCACHE_LEN, (long)MAX_MCACHE_LEN ); 1287 rc = ERR_PARAM; 1288 } 1289 break; 1290 1291 case 'n': 1292 g_uopt.nice_incr = atoi( optarg ); 1293 if( 0 == g_uopt.nice_incr ) { 1294 (void) fprintf( stderr, 1295 "Invalid nice-value increment: [%s]\n", optarg ); 1296 rc = ERR_PARAM; 1297 break; 1298 } 1299 break; 1300 1301 case 'R': 1302 g_uopt.rbuf_msgs = atoi( optarg ); 1303 if( (g_uopt.rbuf_msgs <= 0) && (-1 != g_uopt.rbuf_msgs) ) { 1304 (void) fprintf( stderr, 1305 "Invalid Rmsgs size: [%s]\n", optarg ); 1306 rc = ERR_PARAM; 1307 break; 1308 } 1309 break; 1310 1311 case 'H': 1312 g_uopt.dhold_tmout = (time_t)atoi( optarg ); 1313 if( (0 == g_uopt.dhold_tmout) || 1314 ((g_uopt.dhold_tmout) < 0 && (-1 != g_uopt.dhold_tmout)) ) { 1315 (void) fprintf( stderr, "Invalid value for max time " 1316 "to hold buffered data: [%s]\n", optarg ); 1317 rc = ERR_PARAM; 1318 break; 1319 } 1320 break; 1321 1322 #ifdef UDPXY_FILEIO 1323 case 'r': 1324 if( 0 != access(optarg, R_OK) ) { 1325 perror("source file - access"); 1326 rc = ERR_PARAM; 1327 break; 1328 } 1329 1330 g_uopt.srcfile = strdup( optarg ); 1331 break; 1332 1333 case 'w': 1334 g_uopt.dstfile = strdup( optarg ); 1335 break; 1336 #endif /* UDPXY_FILEIO */ 1337 case 'M': 1338 g_uopt.mcast_refresh = (u_short)atoi( optarg ); 1339 1340 MIN_MCAST_REFRESH = 30; 1341 MAX_MCAST_REFRESH = 64000; 1342 if( g_uopt.mcast_refresh && 1343 (g_uopt.mcast_refresh < MIN_MCAST_REFRESH || 1344 g_uopt.mcast_refresh > MAX_MCAST_REFRESH )) { 1345 (void) fprintf( stderr, 1346 "Invalid multicast refresh period [%d] seconds, " 1347 "min=[%d] sec, max=[%d] sec\n", 1348 (int)g_uopt.mcast_refresh, 1349 (int)MIN_MCAST_REFRESH, (int)MAX_MCAST_REFRESH ); 1350 rc = ERR_PARAM; 1351 break; 1352 } 1353 break; 1354 1355 case ':': 1356 (void) fprintf( stderr, 1357 "Option [-%c] requires an argument\n", 1358 optopt ); 1359 rc = ERR_PARAM; 1360 break; 1361 case '?': 1362 (void) fprintf( stderr, 1363 "Unrecognized option: [-%c]\n", optopt ); 1364 rc = ERR_PARAM; 1365 break; 1366 1367 default: 1368 usage( argv[0], stderr ); 1369 rc = ERR_PARAM; 1370 break; 1371 } 1372 } /* while getopt */ 1373 1374 if (rc) { 1375 free_uopt( &g_uopt ); 1376 return rc; 1377 } 1378 1379 openlog( g_udpxy_app, LOG_CONS | LOG_PID, LOG_LOCAL0 ); 1380 1381 do { 1382 if( (argc < 2) || (port <= 0) || (rc != 0) ) { 1383 usage( argv[0], stderr ); 1384 rc = ERR_PARAM; break; 1385 } 1386 1387 if( '\0' == mcast_addr[0] ) { 1388 (void) strncpy( mcast_addr, IPv4_ALL, sizeof(mcast_addr) - 1 ); 1389 } 1390 1391 if( !custom_log ) { 1392 /* in debug mode output goes to stderr, otherwise to /dev/null */ 1393 g_flog = ((uf_TRUE == g_uopt.is_verbose) 1394 ? stderr 1395 : fopen( "/dev/null", "a" )); 1396 if( NULL == g_flog ) { 1397 perror("fopen"); 1398 rc = ERR_INTERNAL; break; 1399 } 1400 } 1401 1402 if( 0 == geteuid() ) { 1403 if( !no_daemon ) { 1404 if( stderr == g_flog ) { 1405 (void) fprintf( stderr, 1406 "Logfile must be specified to run " 1407 "in verbose mode in background\n" ); 1408 rc = ERR_PARAM; break; 1409 } 1410 1411 if( 0 != (rc = daemonize(0, g_flog)) ) { 1412 rc = ERR_INTERNAL; break; 1413 } 1414 } 1415 1416 rc = set_pidfile( g_udpxy_app, port, pidfile, sizeof(pidfile) ); 1417 if( 0 != rc ) { 1418 mperror( g_flog, errno, "set_pidfile" ); 1419 rc = ERR_INTERNAL; break; 1420 } 1421 1422 if( 0 != (rc = make_pidfile( pidfile, getpid(), g_flog )) ) 1423 break; 1424 } 1425 1426 qact.sa_handler = handle_quitsigs; 1427 sigemptyset(&qact.sa_mask); 1428 qact.sa_flags = 0; 1429 1430 if( (sigaction(SIGTERM, &qact, &oldact) < 0) || 1431 (sigaction(SIGQUIT, &qact, &oldact) < 0) || 1432 (sigaction(SIGINT, &qact, &oldact) < 0)) { 1433 perror("sigaction-quit"); 1434 rc = ERR_INTERNAL; break; 1435 } 1436 1437 iact.sa_handler = SIG_IGN; 1438 sigemptyset(&iact.sa_mask); 1439 iact.sa_flags = 0; 1440 1441 if( (sigaction(SIGPIPE, &iact, &oldact) < 0) ) { 1442 perror("sigaction-ignore"); 1443 rc = ERR_INTERNAL; break; 1444 } 1445 1446 cact.sa_handler = handle_sigchld; 1447 sigemptyset(&cact.sa_mask); 1448 cact.sa_flags = 0; 1449 1450 if( sigaction(SIGCHLD, &cact, &oldact) < 0 ) { 1451 perror("sigaction-sigchld"); 1452 rc = ERR_INTERNAL; break; 1453 } 1454 1455 syslog( LOG_NOTICE, "%s is starting\n", g_app_info ); 1456 TRACE( printcmdln( g_flog, g_app_info, argc, argv ) ); 1457 1458 rc = srv_loop( ipaddr, port, mcast_addr ); 1459 1460 syslog( LOG_NOTICE, "%s is exiting with rc=[%d]\n", 1461 g_app_info, rc); 1462 TRACE( tmfprintf( g_flog, "%s is exiting with rc=[%d]\n", 1463 g_udpxy_app, rc ) ); 1464 TRACE( printcmdln( g_flog, g_app_info, argc, argv ) ); 1465 } while(0); 1466 1467 if( '\0' != pidfile[0] ) { 1468 if( -1 == unlink(pidfile) ) { 1469 mperror( g_flog, errno, "unlink [%s]", pidfile ); 1470 } 1471 } 1472 1473 if( g_flog && (stderr != g_flog) ) { 1474 (void) fclose(g_flog); 1475 } 1476 1477 closelog(); 1478 free_uopt( &g_uopt ); 1479 1480 return rc; 1481} 1482 1483/* __EOF__ */ 1484 1485