/* wait till the given time, update wait status * every update_sec seconds * */ static int wait_till( time_t endtime, int update_sec ) { u_int sec2wait = 0; u_int unslept = 0; time_t now = time(NULL); sig_atomic_t quit = 0; TRACE( (void)tmfprintf( g_flog, "%s: waiting till time=[%ld], now=[%ld]\n", __func__, (long)endtime, (long)now ) ); if( now >= endtime ) return 0; (void) tmfprintf( g_flog, "[%ld] seconds before recording begins\n", (long)(endtime - now) ); if( (update_sec <= 0) || ((now + update_sec) > endtime) ) { unslept = sleep( endtime - now ); } else { while( !(quit = must_quit()) && (now < endtime) ) { sec2wait = (now + update_sec) <= endtime ? update_sec : (endtime - now); update_waitstat( g_flog, endtime ); TRACE( (void)tmfprintf( g_flog, "Waiting for [%u] more seconds.\n", sec2wait ) ); unslept = sleep( sec2wait ); now = time(NULL); } } TRACE( (void)tmfprintf( g_flog, "[%u] seconds unslept, quit=[%d]\n", unslept, (long)quit ) ); return (unslept ? ERR_INTERNAL : 0); }
/* record network stream as per spec in opt */ static int record() { int rsock = -1, destfd = -1, rc = 0, wtime_sec = 0; struct in_addr raddr; struct timeval rtv; struct dstream_ctx ds; ssize_t nmsgs = 0; ssize_t nrcv = -1, lrcv = -1, t_delta = 0; int64_t n_total = 0; ssize_t nwr = -1, lwr = -1; sig_atomic_t quit = 0; struct rdata_opt ropt; int oflags = 0; char* data = NULL; static const u_short RSOCK_TIMEOUT = 5; extern const char CMD_UDP[]; /* NOPs to eliminate warnings in lean version */ (void)&t_delta; (void)&lrcv; t_delta = lrcv = lwr = 0; quit=0; check_fragments( NULL, 0, 0, 0, 0, g_flog ); /* init */ do { data = malloc( g_recopt.bufsize ); if( NULL == data ) { mperror(g_flog, errno, "%s: cannot allocate [%ld] bytes", __func__, (long)g_recopt.bufsize ); rc = ERR_INTERNAL; break; } rc = subscribe( &rsock, &raddr ); if( 0 != rc ) break; rtv.tv_sec = RSOCK_TIMEOUT; rtv.tv_usec = 0; rc = setsockopt( rsock, SOL_SOCKET, SO_RCVTIMEO, &rtv, sizeof(rtv) ); if( -1 == rc ) { mperror(g_flog, errno, "%s: setsockopt - SO_RCVTIMEO", __func__); rc = ERR_INTERNAL; break; } oflags = O_CREAT | O_TRUNC | O_WRONLY | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; # if defined(O_LARGEFILE) /* O_LARGEFILE is not defined under FreeBSD ??-7.1 */ oflags |= O_LARGEFILE; # endif destfd = open( g_recopt.dstfile, oflags, (mode_t)(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)); if( -1 == destfd ) { mperror( g_flog, errno, "%s: cannot create destination file [%s]", __func__, g_recopt.dstfile ); rc = ERR_INTERNAL; break; } rc = calc_buf_settings( &nmsgs, NULL ); if (0 != rc) return -1; if( nmsgs < (ssize_t)1 ) { (void) tmfprintf( g_flog, "Buffer for inbound data is too small [%ld] bytes; " "the minimum size is [%ld] bytes\n", (long)g_recopt.bufsize, (long)ETHERNET_MTU ); rc = ERR_PARAM; break; } TRACE( (void)tmfprintf( g_flog, "Inbound buffer set to " "[%d] messages\n", nmsgs ) ); rc = init_dstream_ctx( &ds, CMD_UDP, NULL, nmsgs ); if( 0 != rc ) return -1; (void) set_nice( g_recopt.nice_incr, g_flog ); /* set up alarm to break main loop */ if( 0 != g_recopt.end_time ) { wtime_sec = (int)difftime( g_recopt.end_time, time(NULL) ); assert( wtime_sec >= 0 ); (void) alarm( wtime_sec ); (void)tmfprintf( g_flog, "Recording will end in [%d] seconds\n", wtime_sec ); } } while(0); /* record loop */ ropt.max_frgs = g_recopt.rbuf_msgs; ropt.buf_tmout = -1; for( n_total = 0; (0 == rc) && !(quit = must_quit()); ) { nrcv = read_data( &ds, rsock, data, g_recopt.bufsize, &ropt ); if( -1 == nrcv ) { rc = ERR_INTERNAL; break; } if( 0 == n_total ) { (void) tmfprintf( g_flog, "Recording to file=[%s] started.\n", g_recopt.dstfile ); } TRACE( check_fragments( "received new", g_recopt.bufsize, lrcv, nrcv, t_delta, g_flog ) ); lrcv = nrcv; if( nrcv > 0 ) { if( g_recopt.max_fsize && ((n_total + nrcv) >= g_recopt.max_fsize) ) { break; } nwr = write_data( &ds, data, nrcv, destfd ); if( -1 == nwr ) { rc = ERR_INTERNAL; break; } n_total += (size_t)nwr; /* TRACE( tmfprintf( g_flog, "Wrote [%ld] to file, total=[%ld]\n", (long)nwr, (long)n_total ) ); */ TRACE( check_fragments( "wrote to file", nrcv, lwr, nwr, t_delta, g_flog ) ); lwr = nwr; } if( ds.flags & F_SCATTERED ) reset_pkt_registry( &ds ); } /* record loop */ (void) tmfprintf( g_flog, "Recording to file=[%s] stopped at filesize=[%lu] bytes\n", g_recopt.dstfile, (u_long)n_total ); /* CLEANUP */ (void) alarm(0); TRACE( (void)tmfprintf( g_flog, "Exited record loop: wrote [%lu] bytes to file [%s], " "rc=[%d], alarm=[%ld], quit=[%ld]\n", (u_long)n_total, g_recopt.dstfile, rc, g_alarm, (long)quit ) ); free_dstream_ctx( &ds ); if( data ) free( data ); close_mcast_listener( rsock, &raddr ); if( destfd >= 0 ) (void) close( destfd ); if( quit ) TRACE( (void)tmfprintf( g_flog, "%s process must quit\n", g_udpxrec_app ) ); return rc; }
/* relay traffic from source to destination socket * */ static int relay_traffic( int ssockfd, int dsockfd, struct server_ctx* ctx, int dfilefd, const struct in_addr* mifaddr ) { volatile sig_atomic_t quit = 0; int rc = 0; ssize_t nmsgs = -1; ssize_t nrcv = 0, nsent = 0, nwr = 0, lrcv = 0, lsent = 0; char* data = NULL; size_t data_len = g_uopt.rbuf_len; struct rdata_opt ropt; time_t pause_time = 0, rfr_tm = time(NULL); sigset_t ubset; const int ALLOW_PAUSES = get_flagval( "UDPXY_ALLOW_PAUSES", 0 ); const ssize_t MAX_PAUSE_MSEC = get_sizeval( "UDPXY_PAUSE_MSEC", 1000); /* permissible variation in data-packet size */ static const ssize_t t_delta = 0x20; struct dstream_ctx ds; static const int SET_PID = 1; struct tps_data tps; assert( ctx && mifaddr && MAX_PAUSE_MSEC > 0 ); (void) sigemptyset (&ubset); sigaddset (&ubset, SIGINT); sigaddset (&ubset, SIGQUIT); sigaddset (&ubset, SIGTERM); /* restore the ability to receive *quit* signals */ rc = sigprocmask (SIG_UNBLOCK, &ubset, NULL); if (0 != rc) { mperror (g_flog, errno, "%s: sigprocmask", __func__); return -1; } /* NOPs to eliminate warnings in lean version */ (void)&lrcv; (void)&lsent; (void)&t_delta; check_fragments( NULL, 0, 0, 0, 0, g_flog ); /* INIT */ rc = calc_buf_settings( &nmsgs, NULL ); if (0 != rc) return -1; TRACE( (void)tmfprintf( g_flog, "Data buffer will hold up to " "[%d] messages\n", nmsgs ) ); rc = init_dstream_ctx( &ds, ctx->cmd, g_uopt.srcfile, nmsgs ); if( 0 != rc ) return -1; (void) set_nice( g_uopt.nice_incr, g_flog ); do { if( NULL == g_uopt.srcfile ) { rc = set_timeouts( ssockfd, dsockfd, ctx->rcv_tmout, 0, ctx->snd_tmout, 0 ); if( 0 != rc ) break; } if( dsockfd > 0 ) { rc = sync_dsockbuf_len( ssockfd, dsockfd ); if( 0 != rc ) break; rc = send_http_response( dsockfd, 200, "OK" ); if( 0 != rc ) break; /* timeshift: to detect PAUSE make destination * socket non-blocking, otherwise make it blocking * (since it might have been set unblocking earlier) */ rc = set_nblock( dsockfd, (ALLOW_PAUSES ? 1 : 0) ); if( 0 != rc ) break; } data = malloc(data_len); if( NULL == data ) { mperror( g_flog, errno, "%s: malloc", __func__ ); break; } if( g_uopt.cl_tpstat ) tpstat_init( &tps, SET_PID ); } while(0); TRACE( (void)tmfprintf( g_flog, "Relaying traffic from socket[%d] " "to socket[%d], buffer size=[%d], Rmsgs=[%d], pauses=[%d]\n", ssockfd, dsockfd, data_len, g_uopt.rbuf_msgs, ALLOW_PAUSES) ); /* RELAY LOOP */ ropt.max_frgs = g_uopt.rbuf_msgs; ropt.buf_tmout = g_uopt.dhold_tmout; pause_time = 0; while( (0 == rc) && !(quit = must_quit()) ) { if( g_uopt.mcast_refresh > 0 ) { check_mcast_refresh( ssockfd, &rfr_tm, mifaddr ); } nrcv = read_data( &ds, ssockfd, data, data_len, &ropt ); if( -1 == nrcv ) break; TRACE( check_fragments( "received new", data_len, lrcv, nrcv, t_delta, g_flog ) ); lrcv = nrcv; if( dsockfd && (nrcv > 0) ) { nsent = write_data( &ds, data, nrcv, dsockfd ); if( -1 == nsent ) break; if ( nsent < 0 ) { if ( !ALLOW_PAUSES ) break; if ( 0 != pause_detect( nsent, MAX_PAUSE_MSEC, &pause_time ) ) break; } TRACE( check_fragments("sent", nrcv, lsent, nsent, t_delta, g_flog) ); lsent = nsent; } if( (dfilefd > 0) && (nrcv > 0) ) { nwr = write_data( &ds, data, nrcv, dfilefd ); if( -1 == nwr ) break; TRACE( check_fragments( "wrote to file", nrcv, lsent, nwr, t_delta, g_flog ) ); lsent = nwr; } if( ds.flags & F_SCATTERED ) reset_pkt_registry( &ds ); if( uf_TRUE == g_uopt.cl_tpstat ) tpstat_update( ctx, &tps, nsent ); } /* end of RELAY LOOP */ /* CLEANUP */ TRACE( (void)tmfprintf( g_flog, "Exited relay loop: received=[%ld], " "sent=[%ld], quit=[%ld]\n", (long)nrcv, (long)nsent, (long)quit ) ); free_dstream_ctx( &ds ); if( NULL != data ) free( data ); if( 0 != (quit = must_quit()) ) { TRACE( (void)tmfprintf( g_flog, "Child process=[%d] must quit\n", getpid()) ); } return rc; }
int main( int argc, char* const argv[] ) { const char app[] = "mcprobe"; int msockfd = 0, rc = 0, tries = -1; const char *mifaddr, *addrport; struct sigaction qact, oldact; if( argc < 3 ) { (void) fprintf( stderr, "Usage: %s mcast_ifc_addr channel_ip:port\n" "Example:\n" "\t mcprobe 192.168.1.1 224.0.2.26:1234\n\n" "(C) 2008-2013 by Pavel V. Cherenkov, licensed under GNU GPLv3\n\n", app ); return 1; } g_flog = stdout; mifaddr = argv[1]; addrport = argv[2]; 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) || (sigaction(SIGPIPE, &qact, &oldact) < 0)) { perror("sigaction-quit"); return 2; } #define LEN_64K (64 * 1024) msockfd = setup_mcast_socket( mifaddr, addrport, LEN_64K ); if( msockfd < 1 ) return msockfd; #define TMOUT_SEC 2 rc = set_rtmout( msockfd, TMOUT_SEC, 0 ); #define RENEW_TRIES 3 for( tries = RENEW_TRIES; (0 == rc) && (tries >= 0); ) { rc = read_msock( msockfd ); if( 0 != rc ) { if( RC_TIMEOUT == rc && (tries > 0) ) { rc = renew_mcast_subscr( msockfd, mifaddr ); if( 0 != rc ) break; --tries; } } /* 0 != rc */ if( must_quit() ) break; } close_mcast_socket( msockfd, mifaddr ); return rc; }
static int read_msock( int msockfd ) { char buf[ ETHERNET_MTU ]; struct timeval tvb, tva, dtv, min_dtv, max_dtv; time_t ltm, tmnow; char str_tmnow[ 32 ]; int rc = 0; ssize_t nread = -1, sum_read = 0, bytesec; size_t packets = (size_t)0, len; assert( msockfd > 0 ); ltm = tmnow = time(NULL); TRACE( (void)fprintf( g_flog, "%s started\n", __func__ ) ); timerclear( &min_dtv ); timerclear( &max_dtv ); for( packets = (size_t)0; !must_quit(); ++packets ) { (void) gettimeofday( &tva, NULL ); nread = read( msockfd, buf, sizeof(buf) ); (void) gettimeofday( &tvb, NULL ); if( tvb.tv_usec >= tva.tv_usec ) { dtv.tv_usec = tvb.tv_usec - tva.tv_usec; dtv.tv_sec = tvb.tv_sec - tva.tv_sec; } else { dtv.tv_usec = tvb.tv_usec - tva.tv_usec + 1000000; dtv.tv_sec = tvb.tv_sec - tva.tv_sec - 1; } if( !timerisset( &max_dtv ) ) { min_dtv = max_dtv = dtv; /* TRACE( (void)fprintf( g_flog, "Reading ...\n" ) ); */ } if( tvalcmp( &dtv, &min_dtv ) < 0 ) min_dtv = dtv; if( tvalcmp( &dtv, &max_dtv ) > 0 ) max_dtv = dtv; if( nread >= 0 ) sum_read += nread; #define REPORT_PERIOD_SEC 2 tmnow = time(NULL); if( difftime( tmnow, ltm ) > (double)REPORT_PERIOD_SEC ) { strncpy( str_tmnow, ctime(&tmnow), sizeof(str_tmnow) - 1 ); str_tmnow[ sizeof(str_tmnow) - 1 ] = 0; len = strlen( str_tmnow ); if( '\n' == str_tmnow[ len - 1 ] ) str_tmnow[ len - 1 ] = 0; bytesec = sum_read / REPORT_PERIOD_SEC; (void) fprintf( g_flog, "%s - packets: %ld, bytes: %ld speed: %.2f K/sec " "min: %lds.%ldms max: %lds.%ldms last: %lds.%ldms\n", str_tmnow, (long)packets, (long)sum_read, ((double)bytesec / 1024), (long)min_dtv.tv_sec, (long)(min_dtv.tv_usec / 1000), (long)max_dtv.tv_sec, (long)(max_dtv.tv_usec / 1000), (long)dtv.tv_sec, (long)(dtv.tv_usec / 1000) ); ltm = tmnow; packets = sum_read = (size_t)0; timerclear( &max_dtv ); timerclear( &min_dtv ); } if( nread <= 0 ) { if( EINTR == errno ) { (void) fprintf( g_flog, "\nINTERRUPT!\n" ); break; } else if( EAGAIN == errno ) { (void) fprintf( g_flog, "\nTIME-OUT!\n" ); rc = RC_TIMEOUT; break; } rc = -1; break; } } /* for */ TRACE( (void)fprintf( g_flog, "%s finished\n", __func__ ) ); return rc; }
/* process client requests */ int srv_loop( const char* ipaddr, int port, const char* mcast_addr ) { int rc, maxfd, err, nrdy, i; fd_set rset; struct timeval tmout, idle_tmout, *ptmout = NULL; tmfd_t *asock = NULL; size_t n = 0, nasock = 0, max_nasock = LQ_BACKLOG; sigset_t oset, bset; static const long IDLE_TMOUT_SEC = 30; assert( (port > 0) && mcast_addr && ipaddr ); (void)tmfprintf( g_flog, "Server is starting up, max clients = [%u]\n", g_uopt.max_clients ); asock = calloc (max_nasock, sizeof(*asock)); if (!asock) { mperror (g_flog, ENOMEM, "%s: calloc", __func__); return ERR_INTERNAL; } init_server_ctx( &g_srv, g_uopt.max_clients, (ipaddr[0] ? ipaddr : "0.0.0.0") , (uint16_t)port, mcast_addr ); g_srv.rcv_tmout = (u_short)g_uopt.rcv_tmout; g_srv.snd_tmout = RLY_SOCK_TIMEOUT; /* NB: server socket is non-blocking! */ if( 0 != (rc = setup_listener( ipaddr, port, &g_srv.lsockfd, g_uopt.lq_backlog )) ) { return rc; } sigemptyset (&bset); sigaddset (&bset, SIGINT); sigaddset (&bset, SIGQUIT); sigaddset (&bset, SIGCHLD); sigaddset (&bset, SIGTERM); (void) sigprocmask (SIG_BLOCK, &bset, &oset); TRACE( (void)tmfprintf( g_flog, "Entering server loop [%s]\n", SLOOP_TAG) ); while (1) { FD_ZERO( &rset ); FD_SET( g_srv.lsockfd, &rset ); FD_SET( g_srv.cpipe[0], &rset ); maxfd = (g_srv.lsockfd > g_srv.cpipe[0] ) ? g_srv.lsockfd : g_srv.cpipe[0]; for (i = 0; (size_t)i < nasock; ++i) { assert (asock[i].fd >= 0); FD_SET (asock[i].fd, &rset); if (asock[i].fd > maxfd) maxfd = asock[i].fd; } /* if there are accepted sockets - apply specified time-out */ tmout.tv_sec = g_uopt.ssel_tmout; tmout.tv_usec = 0; idle_tmout.tv_sec = IDLE_TMOUT_SEC; idle_tmout.tv_usec = 0; /* enforce *idle* select(2) timeout to alleviate signal contention */ ptmout = ((nasock > 0) && (g_uopt.ssel_tmout > 0)) ? &tmout : &idle_tmout; TRACE( (void)tmfprintf( g_flog, "Waiting for input from [%ld] fd's, " "%s timeout\n", (long)(2 + nasock), (ptmout ? "with" : "NO"))); if (ptmout && ptmout->tv_sec) { TRACE( (void)tmfprintf (g_flog, "select() timeout set to " "[%ld] seconds\n", ptmout->tv_sec) ); } (void) sigprocmask (SIG_UNBLOCK, &bset, NULL); if( must_quit() ) { TRACE( (void)tmfputs( "Must quit now\n", g_flog ) ); rc = 0; break; } nrdy = select (maxfd + 1, &rset, NULL, NULL, ptmout); err = errno; (void) sigprocmask (SIG_BLOCK, &bset, NULL); if( must_quit() ) { TRACE( (void)tmfputs( "Must quit now\n", g_flog ) ); rc = 0; break; } wait_terminated( &g_srv ); if( nrdy < 0 ) { if (EINTR == err) { TRACE( (void)tmfputs ("INTERRUPTED, yet " "will continue.\n", g_flog) ); rc = 0; continue; } mperror( g_flog, err, "%s: select", __func__ ); break; } TRACE( (void)tmfprintf (g_flog, "Got %ld requests\n", (long)nrdy) ); if (0 == nrdy) { /* time-out */ tmout_requests (asock, &nasock); rc = 0; continue; } if( FD_ISSET(g_srv.cpipe[0], &rset) ) { (void) tpstat_read( &g_srv ); if (--nrdy <= 0) continue; } if ((0 < nasock) && (0 < (nrdy - (FD_ISSET(g_srv.lsockfd, &rset) ? 1 : 0)))) { process_requests (asock, &nasock, &rset, &g_srv); /* n now contains # (yet) unprocessed accepted sockets */ } if (FD_ISSET(g_srv.lsockfd, &rset)) { if (nasock >= max_nasock) { (void) tmfprintf (g_flog, "Cannot accept sockets beyond " "the limit [%ld/%ld], skipping\n", (long)nasock, (long)max_nasock); } else { n = max_nasock - nasock; /* append asock */ accept_requests (g_srv.lsockfd, &(asock[nasock]), &n); nasock += n; } } } /* server loop */ TRACE( (void)tmfprintf( g_flog, "Exited server loop [%s]\n", SLOOP_TAG) ); for (i = 0; (size_t)i < nasock; ++i) { if (asock[i].fd > 0) (void) close (asock[i].fd); } free (asock); /* receive additional (blocked signals) */ (void) sigprocmask (SIG_SETMASK, &oset, NULL); wait_terminated( &g_srv ); terminate_all_clients( &g_srv ); wait_all( &g_srv ); if (0 != close( g_srv.lsockfd )) { mperror (g_flog, errno, "server socket close"); } free_server_ctx( &g_srv ); (void)tmfprintf( g_flog, "Server exits with rc=[%d]\n", rc ); return rc; }