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