/* clear SIGCHLD flag and adjust context if needed */ static void wait_children( struct server_ctx* ctx, int options ) { int status, n = 0; pid_t pid; assert( ctx ); if (0 == g_childexit) { TRACE( (void)tmfputs ("No children exited since last check\n", g_flog) ); return; } g_childexit = 0; TRACE( (void)tmfputs ("Waiting on exited children\n", g_flog) ); while( 0 < (pid = waitpid( -1, &status, options )) ) { TRACE( (void)tmfprintf( g_flog, "Client [%d] has exited.\n", pid) ); delete_client( ctx, pid ); ++n; } if( (-1 == pid) && ( ECHILD != errno ) ) { mperror(g_flog, errno, "%s: waitpid", __func__); } if (n > 0) { TRACE( (void)tmfprintf (g_flog, "Cleaned up %d children, " "%ld still running\n", n, (long)(ctx->clmax - ctx->clfree)) ); } return; }
/* initialize incoming-stream context: * set data type (if possible) and flags */ int init_dstream_ctx( struct dstream_ctx* ds, const char* cmd, const char* fname, ssize_t nmsgs ) { extern const char CMD_UDP[]; extern const size_t CMD_UDP_LEN; extern const char CMD_RTP[]; extern const size_t CMD_RTP_LEN; assert( ds && cmd && (nmsgs > 0) ); ds->flags = 0; ds->pkt = NULL; ds->max_pkt = ds->pkt_count = 0; ds->mtu = ETHERNET_MTU; if( NULL != fname ) { ds->stype = UPXDT_UNKNOWN; ds->flags |= (F_CHECK_FMT | F_FILE_INPUT); TRACE( (void)tmfputs( "File stream, RTP check enabled\n", g_flog ) ); } else if( 0 == strncmp( cmd, CMD_UDP, CMD_UDP_LEN ) ) { ds->stype = UPXDT_UNKNOWN; ds->flags |= F_CHECK_FMT; TRACE( (void)tmfputs( "UDP stream, RTP check enabled\n", g_flog ) ); } else if( 0 == strncmp( cmd, CMD_RTP, CMD_RTP_LEN ) ) { ds->stype = UPXDT_RTP_TS; ds->flags |= F_SCATTERED; TRACE( (void)tmfputs( "RTP (over UDP) stream assumed," " no checks\n", g_flog ) ); } else { TRACE( (void)tmfprintf( g_flog, "%s: " "Irrelevant command [%s]\n", __func__, cmd) ); return -1; } ds->pkt = calloc( nmsgs, sizeof(ds->pkt[0]) ); if( NULL == ds->pkt ) { mperror( g_flog, errno, "%s: calloc", __func__ ); return -1; } ds->max_pkt = nmsgs; return 0; }
/* read data from source, determine underlying protocol * (if not already known); and process the packet * if needed (for RTP - register packet) * * return the number of octets read from the source * into the buffer */ static ssize_t read_packet( struct dstream_ctx* spc, int fd, char* buf, size_t len ) { ssize_t n = -1; size_t chunk_len = len; assert( spc && buf && len ); assert( fd > 0 ); /* if *RAW* data specified - read AS IS * and exit */ if( UPXDT_RAW == spc->stype ) { return read_buf( fd, buf, len, g_flog ); } /* if it is (or *could* be) RTP, read only MTU bytes */ if( (spc->stype == UPXDT_RTP_TS) || (spc->flags & F_CHECK_FMT) ) chunk_len = (len > spc->mtu) ? spc->mtu : len; #ifdef UDPXY_FILEIO if( spc->flags & F_FILE_INPUT ) { assert( !buf_overrun( buf, len, 0, chunk_len, g_flog ) ); n = read_frecord( fd, buf, chunk_len, &(spc->stype), g_flog ); if( n <= 0 ) return n; } else #endif /* UDPXY_FILEIO */ { assert( !buf_overrun(buf, len, 0, chunk_len, g_flog) ); n = read_buf( fd, buf, chunk_len, g_flog ); if( n <= 0 ) return n; } if( spc->flags & F_CHECK_FMT ) { spc->stype = get_mstream_type( buf, n, g_flog ); switch (spc->stype) { case UPXDT_RTP_TS: /* scattered: exclude RTP headers */ spc->flags |= F_SCATTERED; break; case UPXDT_TS: spc->flags &= ~F_SCATTERED; break; default: spc->stype = UPXDT_RAW; TRACE( (void)tmfputs( "Unrecognized stream type\n", g_flog ) ); break; } /* switch */ TRACE( (void)tmfprintf( g_flog, "Established stream as [%s]\n", fmt2str( spc->stype ) ) ); spc->flags &= ~F_CHECK_FMT; } if( spc->flags & F_SCATTERED ) if( -1 == register_packet( spc, buf, n ) ) return -1; return n; }
/* register received packet into registry (for scattered output) */ static int register_packet( struct dstream_ctx* spc, char* buf, size_t len ) { struct iovec* new_pkt = NULL; static const int DO_VERIFY = 1; void* new_buf = buf; size_t new_len = len; assert( spc->max_pkt > 0 ); /* enlarge packet registry if needed */ if( spc->pkt_count >= spc->max_pkt ) { spc->max_pkt <<= 1; spc->pkt = realloc( spc->pkt, spc->max_pkt * sizeof(spc->pkt[0]) ); if( NULL == spc->pkt ) { mperror( g_flog, errno, "%s: realloc", __func__ ); return -1; } TRACE( (void)tmfprintf( g_flog, "RTP packet registry " "expanded to [%lu] records\n", (u_long)spc->max_pkt ) ); } /* put packet info into registry */ new_pkt = &(spc->pkt[ spc->pkt_count ]); /* TRACE( (void)tmfprintf( stderr, "IN: packet [%lu]: buf=[%p], len=[%lu]\n", (u_long)spc->pkt_count, (void*)buf, (u_long)len ) ); */ if( 0 != RTP_process( &new_buf, &new_len, DO_VERIFY, g_flog ) ) { TRACE( (void)tmfputs("register packet: dropping\n", g_flog) ); spc->flags |= F_DROP_PACKET; return 0; } new_pkt->iov_base = new_buf; new_pkt->iov_len = new_len; /* TRACE( (void)tmfprintf( stderr, "OUT: packet [%lu]: buf=[%p], len=[%lu]\n", (u_long)spc->pkt_count, new_pkt->iov_base, (u_long)new_pkt->iov_len ) ); */ spc->pkt_count++; return 0; }
/* print out command-line */ void printcmdln( FILE* stream, const char* msg, int argc, char* const argv[] ) { int i = 0; assert( stream ); if( msg ) (void)tmfprintf( stream, "%s: ", msg ); else (void)tmfputs( "", stream ); for( i = 0; i < argc; ++i ) (void)fprintf( stream, "%s ", argv[i] ); (void)fputc( '\n', stream ); }
void accept_requests (int sockfd, tmfd_t* asock, size_t* alen) { int new_sockfd = -1, err = 0, peer_port = -1, wmark = g_uopt.rcv_lwmark; size_t nmax = *alen, naccepted = 0; struct sockaddr_in cliaddr; a_socklen_t addrlen = sizeof (cliaddr); char peer_addr [128] = "#undef#"; static const int YES = 1; while (naccepted < nmax) { TRACE( (void)tmfputs ("Accepting new connection\n", g_flog) ); new_sockfd = accept (sockfd, (struct sockaddr*)&cliaddr, &addrlen ); if (-1 == new_sockfd) { err = errno; if ((EWOULDBLOCK == err) || (EAGAIN == err)) { TRACE((void)tmfputs ("Nothing more to accept\n", g_flog)); break; } if ((ECONNABORTED == err) || (ECONNRESET == err) || (EPROTO == err)) { TRACE( (void)tmfprintf (g_flog, "Connection aborted/reset " "at accept point, errno=%d\n", err) ); continue; } mperror(g_flog, err, "%s: accept", __func__); break; } if (0 != set_nblock (new_sockfd, 1)) { (void) close (new_sockfd); /* TODO: error-aware close */ continue; } /* if (0 != set_timeouts(new_sockfd, new_sockfd, g_uopt.sr_tmout, 0, g_uopt.sw_tmout, 0)) { (void) close (new_sockfd); continue; } */ if (wmark > 0) { if (0 != setsockopt (new_sockfd, SOL_SOCKET, SO_RCVLOWAT, (char*)&wmark, sizeof(wmark))) { mperror (g_flog, errno, "%s: setsockopt SO_RCVLOWAT [%d]", __func__, wmark); (void) close (new_sockfd); /* TODO: error-aware close */ continue; } else { TRACE( (void)tmfprintf (g_flog, "Receive LOW WATERMARK [%d] applied " "to newly-accepted socket [%d]\n", wmark, new_sockfd) ); } } if (g_uopt.tcp_nodelay) { if (0 != setsockopt(new_sockfd, IPPROTO_TCP, TCP_NODELAY, &YES, sizeof(YES))) { mperror(g_flog, errno, "%s setsockopt TCP_NODELAY", __func__); } } asock [naccepted].fd = new_sockfd; asock [naccepted].atime = time (NULL); ++naccepted; (void) get_peerinfo (new_sockfd, peer_addr, sizeof(peer_addr)-1, &peer_port); TRACE( (void)tmfprintf( g_flog, "Accepted socket=[%d] from %s:%d " "n=%ld/nmax=%ld\n", new_sockfd, peer_addr, peer_port, (long)naccepted, (long)nmax) ); } /* while */ if (naccepted >= nmax) { (void)tmfprintf (g_flog, "Accept limit max=[%d] reached, " "%ld already accepted", (long)nmax, (long)naccepted); } *alen = naccepted; TRACE( (void)tmfprintf (g_flog, "%s: Sockets accepted: [%ld]\n", __func__, (long)naccepted)); return; }
void process_requests (tmfd_t* asock, size_t *alen, fd_set* rset, struct server_ctx* srv) { size_t nmax = *alen, i = 0, nserved = 0; int rc = 0, served = 0; char param[ 128 ] = "\0"; time_t now = time (NULL); /* uncomment to DEBUG */ TRACE( print_fds (g_flog, "pre-process sockets", asock, nmax) ); for (; i < nmax; ++i, served = 0) { assert (asock[i].fd >= 0); assert (asock[i].atime > 0); do { /* not selected - yet try to time it out */ if (!FD_ISSET(asock[i].fd, rset)) { if ((asock[i].atime + g_uopt.ssel_tmout) < now) { TRACE( (void)tmfprintf (g_flog, "%s: accepted socket [%ld] timed out\n", __func__, (long)asock[i].fd) ); ++served; /* timed out - must close */ } break; } /* selected */ TRACE( (void)tmfprintf (g_flog, "acting on accepted socket " "[%d] (%d/%d)\n", asock[i].fd, i+1, nmax) ); ++served; /* selected - must close regardless */ rc = read_command( asock[i].fd, srv->cmd, sizeof(srv->cmd), param, sizeof(param) ); if( 0 != rc ) break; rc = process_command (asock[i].fd, srv, param, sizeof(param) ); } while (0); if (0 != rc) { TRACE( (void)tmfprintf (g_flog, "error [%d] processing " "client socket [%d]\n", rc, asock[i])); } TRACE( (void)tmfprintf (g_flog, "%s: %s accepted " "socket [%d]\n", __func__, (served ? "closing" : "skipping"), asock[i].fd) ); if (served) { (void) close (asock[i].fd); asock[i].fd = -1; ++nserved; } } /* for */ TRACE( (void)tmfprintf (g_flog, "Processed [%ld/%ld] accepted sockets\n", (long)nserved, (long)nmax) ); TRACE( print_fds (g_flog, "newly-accepted sockets", asock, nmax) ); if (nserved >= nmax) { *alen = 0; TRACE( (void)tmfputs ("All accepted sockets processed\n", g_flog) ); } else { shrink_asock (asock, alen, nserved); } return; }
/* 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; }