/* write timestamp-prepended message to file */ int tmfputs( const char* s, FILE* stream ) { int n = -1, rc = 0, NO_RESET = 0; char tstamp[ 80 ] = {'\0'}; size_t ts_len = sizeof(tstamp) - 1; struct timeval tv_now; const char* pidstr = get_pidstr( NO_RESET, NULL ); (void)gettimeofday( &tv_now, NULL ); errno = 0; do { rc = mk_tvstamp( &tv_now, tstamp, &ts_len, 0 ); if( 0 != rc ) break; if( (n = fputs( tstamp, stream )) < 0 || (n = fputs( "\t", stream )) < 0 || (n = fputs( pidstr, stream )) < 0 || (n = fputs( "\t", stream )) < 0 ) break; if( (n = fputs( s, stream )) < 0 ) break; } while(0); if( n < 0 && errno ) { perror( "fputs" ); } return (0 != rc) ? -1 : n; }
/* write timestamp-prepended formatted message to file */ int tmfprintf( FILE* stream, const char* format, ... ) { va_list ap; int n = -1, total = 0, rc = 0, NO_RESET = 0; char tstamp[ 80 ] = {'\0'}; size_t ts_len = sizeof(tstamp) - 1; struct timeval tv_now; const char* pidstr = get_pidstr( NO_RESET, NULL ); (void)gettimeofday( &tv_now, NULL ); errno = 0; do { rc = mk_tvstamp( &tv_now, tstamp, &ts_len, 0 ); if( 0 != rc ) break; n = fprintf( stream, "%s\t%s\t", tstamp, pidstr ); if( n <= 0 ) break; total += n; va_start( ap, format ); n = vfprintf( stream, format, ap ); va_end( ap ); if( n <= 0 ) break; total += n; } while(0); if( n <= 0 ) { perror( "fprintf/vfprintf" ); return -1; } return (0 != rc) ? -1 : total; }
/* process command to relay udp traffic * */ static int udp_relay( int sockfd, const char* param, size_t plen, const struct in_addr* mifaddr, struct server_ctx* ctx ) { char mcast_addr[ IPADDR_STR_SIZE ]; struct sockaddr_in addr; uint16_t port; pid_t new_pid; int rc = 0, flags; int msockfd = -1, sfilefd = -1, dfilefd = -1, srcfd = -1; char dfile_name[ MAXPATHLEN ]; size_t rcvbuf_len = 0; assert( (sockfd > 0) && param && plen && ctx ); TRACE( (void)tmfprintf( g_flog, "udp_relay : new_socket=[%d] param=[%s]\n", sockfd, param) ); do { rc = parse_udprelay( param, plen, mcast_addr, IPADDR_STR_SIZE, &port ); if( 0 != rc ) { (void) tmfprintf( g_flog, "Error [%d] parsing parameters [%s]\n", rc, param ); break; } if( 1 != inet_aton(mcast_addr, &addr.sin_addr) ) { (void) tmfprintf( g_flog, "Invalid address: [%s]\n", mcast_addr ); rc = ERR_INTERNAL; break; } addr.sin_family = AF_INET; addr.sin_port = htons( (short)port ); } while(0); if( 0 != rc ) { (void) send_http_response( sockfd, 500, "Service error" ); return rc; } /* start the (new) process to relay traffic */ if( 0 != (new_pid = fork()) ) { rc = add_client( ctx, new_pid, mcast_addr, port, sockfd ); return rc; /* parent returns */ } /* child process: */ TRACE( (void)tmfprintf( g_flog, "Client process=[%d] started " "for socket=[%d]\n", getpid(), sockfd) ); (void) get_pidstr( PID_RESET, "c" ); (void)close( ctx->lsockfd ); /* close the reading end of the comm. pipe */ (void)close( ctx->cpipe[0] ); ctx->cpipe[0] = -1; do { /* make write end of pipe non-blocking (we don't want to * block on pipe write while relaying traffic) */ if( -1 == (flags = fcntl( ctx->cpipe[1], F_GETFL )) || -1 == fcntl( ctx->cpipe[1], F_SETFL, flags | O_NONBLOCK ) ) { mperror( g_flog, errno, "%s: fcntl", __func__ ); rc = -1; break; } if( NULL != g_uopt.dstfile ) { (void) snprintf( dfile_name, MAXPATHLEN - 1, "%s.%d", g_uopt.dstfile, getpid() ); dfilefd = creat( dfile_name, S_IRUSR | S_IWUSR | S_IRGRP ); if( -1 == dfilefd ) { mperror( g_flog, errno, "%s: g_uopt.dstfile open", __func__ ); rc = -1; break; } TRACE( (void)tmfprintf( g_flog, "Dest file [%s] opened as fd=[%d]\n", dfile_name, dfilefd ) ); } else dfilefd = -1; if( NULL != g_uopt.srcfile ) { sfilefd = open( g_uopt.srcfile, O_RDONLY | O_NOCTTY ); if( -1 == sfilefd ) { mperror( g_flog, errno, "%s: g_uopt.srcfile open", __func__ ); rc = -1; } else { TRACE( (void) tmfprintf( g_flog, "Source file [%s] opened\n", g_uopt.srcfile ) ); srcfd = sfilefd; } } else { rc = calc_buf_settings( NULL, &rcvbuf_len ); if (0 == rc ) { rc = setup_mcast_listener( &addr, mifaddr, &msockfd, (g_uopt.nosync_sbuf ? 0 : rcvbuf_len) ); srcfd = msockfd; } } if( 0 != rc ) break; rc = relay_traffic( srcfd, sockfd, ctx, dfilefd, mifaddr ); if( 0 != rc ) break; } while(0); if( msockfd > 0 ) { close_mcast_listener( msockfd, mifaddr ); } if( sfilefd > 0 ) { (void) close( sfilefd ); TRACE( (void) tmfprintf( g_flog, "Source file [%s] closed\n", g_uopt.srcfile ) ); } if( dfilefd > 0 ) { (void) close( dfilefd ); TRACE( (void) tmfprintf( g_flog, "Dest file [%s] closed\n", dfile_name ) ); } if( 0 != rc ) { (void) send_http_response( sockfd, 500, "Service error" ); } (void) close( sockfd ); free_server_ctx( ctx ); closelog(); TRACE( (void)tmfprintf( g_flog, "Child process=[%d] exits with rc=[%d]\n", getpid(), rc) ); if( g_flog && (stderr != g_flog) ) { (void) fclose(g_flog); } free_uopt( &g_uopt ); rc = ( 0 != rc ) ? ERR_INTERNAL : rc; exit(rc); /* child exits */ return rc; }
int udpxy_main( int argc, char* const argv[] ) { int rc = 0, ch = 0, port = -1, custom_log = 0, no_daemon = 0; char ipaddr[IPADDR_STR_SIZE] = "\0", mcast_addr[IPADDR_STR_SIZE] = "\0"; char pidfile[ MAXPATHLEN ] = "\0"; u_short MIN_MCAST_REFRESH = 0, MAX_MCAST_REFRESH = 0; /* support for -r -w (file read/write) option is disabled by default; * those features are experimental and for dev debugging ONLY * */ #ifdef UDPXY_FILEIO static const char UDPXY_OPTMASK[] = "TvSa:l:p:m:c:B:n:R:r:w:H:M:"; #else static const char UDPXY_OPTMASK[] = "TvSa:l:p:m:c:B:n:R:H:M:"; #endif struct sigaction qact, iact, cact, oldact; init_app_info(); (void) get_pidstr( PID_RESET, "S" ); rc = init_uopt( &g_uopt ); while( (0 == rc) && (-1 != (ch = getopt(argc, argv, UDPXY_OPTMASK))) ) { switch( ch ) { case 'v': set_verbose( &g_uopt.is_verbose ); break; case 'T': no_daemon = 1; break; case 'S': g_uopt.cl_tpstat = uf_TRUE; break; case 'a': rc = get_ipv4_address( optarg, ipaddr, sizeof(ipaddr) ); if( 0 != rc ) { (void) fprintf( stderr, "Invalid address: [%s]\n", optarg ); rc = ERR_PARAM; } break; case 'p': port = atoi( optarg ); if( port <= 0 ) { (void) fprintf( stderr, "Invalid port number: [%d]\n", port ); rc = ERR_PARAM; } break; case 'm': rc = get_ipv4_address( optarg, mcast_addr, sizeof(mcast_addr) ); if( 0 != rc ) { (void) fprintf( stderr, "Invalid multicast address: " "[%s]\n", optarg ); rc = ERR_PARAM; } break; case 'c': g_uopt.max_clients = atoi( optarg ); if( (g_uopt.max_clients < MIN_CLIENT_COUNT) || (g_uopt.max_clients > MAX_CLIENT_COUNT) ) { (void) fprintf( stderr, "Client count should be between %d and %d\n", MIN_CLIENT_COUNT, MAX_CLIENT_COUNT ); rc = ERR_PARAM; } break; case 'l': g_flog = fopen( optarg, "a" ); if( NULL == g_flog ) { rc = errno; (void) fprintf( stderr, "Error opening logfile " "[%s]: %s\n", optarg, strerror(rc) ); rc = ERR_PARAM; break; } Setlinebuf( g_flog ); custom_log = 1; break; case 'B': rc = a2size(optarg, &g_uopt.rbuf_len); if( 0 != rc ) { (void) fprintf( stderr, "Invalid buffer size: [%s]\n", optarg ); exit( ERR_PARAM ); } else if( (g_uopt.rbuf_len < MIN_MCACHE_LEN) || (g_uopt.rbuf_len > MAX_MCACHE_LEN) ) { fprintf(stderr, "Buffer size " "must be within [%ld-%ld] bytes\n", (long)MIN_MCACHE_LEN, (long)MAX_MCACHE_LEN ); rc = ERR_PARAM; } break; case 'n': g_uopt.nice_incr = atoi( optarg ); if( 0 == g_uopt.nice_incr ) { (void) fprintf( stderr, "Invalid nice-value increment: [%s]\n", optarg ); rc = ERR_PARAM; break; } break; case 'R': g_uopt.rbuf_msgs = atoi( optarg ); if( (g_uopt.rbuf_msgs <= 0) && (-1 != g_uopt.rbuf_msgs) ) { (void) fprintf( stderr, "Invalid Rmsgs size: [%s]\n", optarg ); rc = ERR_PARAM; break; } break; case 'H': g_uopt.dhold_tmout = (time_t)atoi( optarg ); if( (0 == g_uopt.dhold_tmout) || ((g_uopt.dhold_tmout) < 0 && (-1 != g_uopt.dhold_tmout)) ) { (void) fprintf( stderr, "Invalid value for max time " "to hold buffered data: [%s]\n", optarg ); rc = ERR_PARAM; break; } break; #ifdef UDPXY_FILEIO case 'r': if( 0 != access(optarg, R_OK) ) { perror("source file - access"); rc = ERR_PARAM; break; } g_uopt.srcfile = strdup( optarg ); break; case 'w': g_uopt.dstfile = strdup( optarg ); break; #endif /* UDPXY_FILEIO */ case 'M': g_uopt.mcast_refresh = (u_short)atoi( optarg ); MIN_MCAST_REFRESH = 30; MAX_MCAST_REFRESH = 64000; if( g_uopt.mcast_refresh && (g_uopt.mcast_refresh < MIN_MCAST_REFRESH || g_uopt.mcast_refresh > MAX_MCAST_REFRESH )) { (void) fprintf( stderr, "Invalid multicast refresh period [%d] seconds, " "min=[%d] sec, max=[%d] sec\n", (int)g_uopt.mcast_refresh, (int)MIN_MCAST_REFRESH, (int)MAX_MCAST_REFRESH ); rc = ERR_PARAM; break; } break; case ':': (void) fprintf( stderr, "Option [-%c] requires an argument\n", optopt ); rc = ERR_PARAM; break; case '?': (void) fprintf( stderr, "Unrecognized option: [-%c]\n", optopt ); rc = ERR_PARAM; break; default: usage( argv[0], stderr ); rc = ERR_PARAM; break; } } /* while getopt */ if (rc) { free_uopt( &g_uopt ); return rc; } openlog( g_udpxy_app, LOG_CONS | LOG_PID, LOG_LOCAL0 ); do { if( (argc < 2) || (port <= 0) || (rc != 0) ) { usage( argv[0], stderr ); rc = ERR_PARAM; break; } if( '\0' == mcast_addr[0] ) { (void) strncpy( mcast_addr, IPv4_ALL, sizeof(mcast_addr) - 1 ); } if( !custom_log ) { /* in debug mode output goes to stderr, otherwise to /dev/null */ g_flog = ((uf_TRUE == g_uopt.is_verbose) ? stderr : fopen( "/dev/null", "a" )); if( NULL == g_flog ) { perror("fopen"); rc = ERR_INTERNAL; break; } } if( 0 == geteuid() ) { if( !no_daemon ) { if( stderr == g_flog ) { (void) fprintf( stderr, "Logfile must be specified to run " "in verbose mode in background\n" ); rc = ERR_PARAM; break; } if( 0 != (rc = daemonize(0, g_flog)) ) { rc = ERR_INTERNAL; break; } } rc = set_pidfile( g_udpxy_app, port, pidfile, sizeof(pidfile) ); if( 0 != rc ) { mperror( g_flog, errno, "set_pidfile" ); rc = ERR_INTERNAL; break; } if( 0 != (rc = make_pidfile( pidfile, getpid(), g_flog )) ) break; } qact.sa_handler = handle_quitsigs; sigemptyset(&qact.sa_mask); qact.sa_flags = 0; if( (sigaction(SIGTERM, &qact, &oldact) < 0) || (sigaction(SIGQUIT, &qact, &oldact) < 0) || (sigaction(SIGINT, &qact, &oldact) < 0)) { perror("sigaction-quit"); rc = ERR_INTERNAL; break; } iact.sa_handler = SIG_IGN; sigemptyset(&iact.sa_mask); iact.sa_flags = 0; if( (sigaction(SIGPIPE, &iact, &oldact) < 0) ) { perror("sigaction-ignore"); rc = ERR_INTERNAL; break; } cact.sa_handler = handle_sigchld; sigemptyset(&cact.sa_mask); cact.sa_flags = 0; if( sigaction(SIGCHLD, &cact, &oldact) < 0 ) { perror("sigaction-sigchld"); rc = ERR_INTERNAL; break; } syslog( LOG_NOTICE, "%s is starting\n", g_udpxy_finfo ); TRACE( printcmdln( g_flog, g_udpxy_finfo, argc, argv ) ); rc = srv_loop( ipaddr, port, mcast_addr ); syslog( LOG_NOTICE, "%s is exiting with rc=[%d]\n", g_udpxy_finfo, rc); TRACE( tmfprintf( g_flog, "%s is exiting with rc=[%d]\n", g_udpxy_app, rc ) ); TRACE( printcmdln( g_flog, g_udpxy_finfo, argc, argv ) ); } while(0); if( '\0' != pidfile[0] ) { if( -1 == unlink(pidfile) ) { mperror( g_flog, errno, "unlink [%s]", pidfile ); } } if( g_flog && (stderr != g_flog) ) { (void) fclose(g_flog); } closelog(); free_uopt( &g_uopt ); return rc; }