Blob Blame History Raw
/******************************************************************************
* File: pit_server.c
*
* Description: Contains source code for an IPv6-capable 'PIT' server.
* This is a derivative of the tod6 (time-of-day) server that was written 
* by John Wenker.
* .......
* Author of tod6: John Wenker, Sr. Software Engineer,
*                  Performance Technologies, San Diego, USA
* .......
* The program tod6 was a time of day server. It has beeen modified
* to provide a microsecond timestamp on request.  Modified and adapted 
* for PIT purposes by Don Capps.  [ capps@iozone.org ]
* 
* This server sends the current value of gettimeofday() in 
* microseconds back to the client, as a numerical string.
*
* /etc/services should contain "PIT" with a specified port value.
*
******************************************************************************/
/*
** System header files.
*/
#include <errno.h>        /* errno declaration & error codes.            */
#include <netdb.h>        /* getaddrinfo(3) et al.                       */
#include <netinet/in.h>   /* sockaddr_in & sockaddr_in6 definition.      */
#include <stdio.h>        /* printf(3) et al.                            */
#include <stdlib.h>       /* exit(2).                                    */
#include <string.h>       /* String manipulation & memory functions.     */
#if defined(_SUA_)
#include <poll.h>         /* poll(2) and related definitions.            */
#else
#include <sys/poll.h>     /* poll(2) and related definitions.            */
#endif
#include <sys/socket.h>   /* Socket functions (socket(2), bind(2), etc). */
#include <time.h>         /* time(2) & ctime(3).                         */
#include <sys/time.h>     /* gettimeofday 				 */
#include <unistd.h>       /* getopt(3), read(2), etc.                    */
/* Include for Cygnus development environment for Windows */
#if defined (Windows)
#include <Windows.h>
int errno;
#endif

#if defined(_SUA_)
extern char *optarg, *opterr;
#endif

/*
** Constants.
**
** Please remember to add PIT service to the /etc/services file.
*/
#define DFLT_SERVICE "PIT"       /* Programmable Interdimensional Timer      */

#define INVALID_DESC -1          /* Invalid file descriptor.                 */
#define MAXCONNQLEN  3           /* Max nbr of connection requests to queue. */
#define MAXTCPSCKTS  2           /* One TCP socket for IPv4 & one for IPv6.  */
#define MAXUDPSCKTS  2           /* One UDP socket for IPv4 & one for IPv6.  */
#define VALIDOPTS    "vh:p:"     /* Valid command options.                   */
/*
** Simple boolean type definition.
*/
int false = 0;
int true = 1;
/*
** Prototypes for internal helper functions.
*/
static int  openSckt( const char *service,
                      const char *protocol,
                      int         desc[ ],
                      size_t     *descSize );
static void pit( int    tSckt[ ],
                 size_t tScktSize,
                 int    uSckt[ ],
                 size_t uScktSize );
/*
** Global data objects.
*/
static char        hostBfr[ NI_MAXHOST ];   /* For use w/getnameinfo(3).    */
static const char *pgmName;                 /* Program name w/o dir prefix. */
static char        servBfr[ NI_MAXSERV ];   /* For use w/getnameinfo(3).    */
static int     	   verbose = 0;             /* Verbose mode indication.     */
struct timeval tm;  /* Timeval structure, used with gettimeofday() */
char   timeStr[40]; /* String for time in microseconds */
char service_name[20];
int need;
/*
** Usage macro for command syntax violations.
*/
#define USAGE                                       \
        {                                           \
           fprintf( stderr,                         \
                    "Usage: %s [-v] -p service \n",   \
                    pgmName );                      \
           exit( 127 );                             \
        }  /* End USAGE macro. */
/*
** Macro to terminate the program if a system call error occurs.  The system
** call must be one of the usual type that returns -1 on error.  
*/
#define CHK(expr)                                                   \
        do                                                          \
        {                                                           \
           if ( (expr) == -1 )                                      \
           {                                                        \
              fprintf( stderr,                                      \
                       "%s (line %d): System call ERROR - %s.\n",   \
                       pgmName,                                     \
                       __LINE__,                                    \
                       strerror( errno ) );                         \
              exit( 1 );                                            \
           }   /* End IF system call failed. */                     \
        } while ( false )
/******************************************************************************
* Function: main
*
* Description:
*    Set up a PIT server and handle network requests.  This server
*    handles both TCP and UDP requests.
*
* Parameters:
*    The usual argc and argv parameters to a main() function.
*
* Return Value:
*    This is a daemon program and never returns.  However, in the degenerate
*    case where no sockets are created, the function returns zero.
******************************************************************************/
int main( int   argc,
          char *argv[ ] )
{
   int         opt;
   int         tSckt[ MAXTCPSCKTS ];     /* Array of TCP socket descriptors. */
   size_t      tScktSize = MAXTCPSCKTS;  /* Size of uSckt (# of elements).   */
   int         uSckt[ MAXUDPSCKTS ];     /* Array of UDP socket descriptors. */
   size_t      uScktSize = MAXUDPSCKTS;  /* Size of uSckt (# of elements).   */

   strcpy(service_name,DFLT_SERVICE);
   /*
   ** Set the program name (w/o directory prefix).
   */
   pgmName = strrchr( argv[ 0 ], '/' );
   pgmName = pgmName == NULL  ?  argv[ 0 ]  :  pgmName + 1;
   /*
   ** Process command options.
   */
   opterr = 0;   /* Turns off "invalid option" error messages. */
   while ( ( opt = getopt( argc, argv, VALIDOPTS ) ) >= 0 )
   {
      switch ( opt )
      {
         case 'v':   /* Verbose mode. */
         {
            verbose = true;
            break;
         }
         case 'p':   /* Get the port number */
         {
            strcpy(service_name,optarg);
	    need++;
            break;
         }
         default:
         {
            USAGE;
         }
      }  /* End SWITCH on command option. */
   }  /* End WHILE processing options. */

   if(need < 1)
   {
	USAGE;
	exit;
   }
   /*
   ** Open both a TCP and UDP socket, for both IPv4 & IPv6, on which to receive
   ** service requests.
   */
   if ( ( openSckt( service_name, "tcp", tSckt, &tScktSize ) < 0 ) ||
        ( openSckt( service_name, "udp", uSckt, &uScktSize ) < 0 ) )
   {
      exit( 1 );
   }
   /*
   ** Run the Programmable Interdimensional Timer server.
   */
   if ( ( tScktSize > 0 ) || ( uScktSize > 0 ) )
   {
      pit( tSckt,         /* pit() never returns. */
           tScktSize,
           uSckt,
           uScktSize );
   }
   /*
   ** Since pit() never returns, execution only gets here if no sockets were
   ** created.
   */
   if ( verbose )
   {
      fprintf( stderr,
               "%s: No sockets opened... terminating.\n",
               pgmName );
   }
   return 0;
}  /* End main() */
/******************************************************************************
* Function: openSckt
*
* Description:
*    Open passive (server) sockets for the indicated inet service & protocol.
*    Notice in the last sentence that "sockets" is plural.  During the interim
*    transition period while everyone is switching over to IPv6, the server
*    application has to open two sockets on which to listen for connections...
*    one for IPv4 traffic and one for IPv6 traffic.
*
* Parameters:
*    service  - Pointer to a character string representing the well-known port
*               on which to listen (can be a service name or a decimal number).
*    protocol - Pointer to a character string representing the transport layer
*               protocol (only "tcp" or "udp" are valid).
*    desc     - Pointer to an array into which the socket descriptors are
*               placed when opened.
*    descSize - This is a value-result parameter.  On input, it contains the
*               max number of descriptors that can be put into 'desc' (i.e. the
*               number of elements in the array).  Upon return, it will contain
*               the number of descriptors actually opened.  Any unused slots in
*               'desc' are set to INVALID_DESC.
*
* Return Value:
*    0 on success, -1 on error.
******************************************************************************/
static int openSckt( const char *service,
                     const char *protocol,
                     int         desc[ ],
                     size_t     *descSize )
{
   struct addrinfo *ai;
   int              aiErr;
   struct addrinfo *aiHead;
   struct addrinfo  hints    = { .ai_flags  = AI_PASSIVE,    /* Server mode.  */
                                 .ai_family = PF_UNSPEC };   /* IPv4 or IPv6. */
   size_t           maxDescs = *descSize;
   /*
   ** Initialize output parameters.  When the loop completes, *descSize is 0.
   */
   while ( *descSize > 0 )
   {
      desc[ --( *descSize ) ] = INVALID_DESC;
   }
   /*
   ** Check which protocol is selected (only TCP and UDP are valid).
   */
   if ( strcmp( protocol, "tcp" ) == 0 )        /* TCP protocol.     */
   {
      hints.ai_socktype = SOCK_STREAM;
      hints.ai_protocol = IPPROTO_TCP;
   }
   else if ( strcmp( protocol, "udp" ) == 0 )   /* UDP protocol.     */
   {
      hints.ai_socktype = SOCK_DGRAM;
      hints.ai_protocol = IPPROTO_UDP;
   }
   else                                         /* Invalid protocol. */
   {
      fprintf( stderr,
               "%s (line %d): ERROR - Unknown transport "
               "layer protocol \"%s\".\n",
               pgmName,
               __LINE__,
               protocol );
      return -1;
   }
   /*
   ** Look up the service's "well-known" port number.  Notice that NULL is being
   ** passed for the 'node' parameter, and that the AI_PASSIVE flag is set in
   ** 'hints'.  Thus, the program is requesting passive address information.
   ** The network address is initialized to :: (all zeros) for IPv6 records, or
   ** 0.0.0.0 for IPv4 records.
   */
   if ( ( aiErr = getaddrinfo( NULL,
                               service,
                               &hints,
                               &aiHead ) ) != 0 )
   {
      fprintf( stderr,
               "%s (line %d): ERROR - %s.\n",
               pgmName,
               __LINE__,
               gai_strerror( aiErr ) );
      return -1;
   }
   /*
   ** For each of the address records returned, attempt to set up a passive
   ** socket.
   */
   for ( ai = aiHead;
         ( ai != NULL ) && ( *descSize < maxDescs );
         ai = ai->ai_next )
   {
      if ( verbose )
      {
         /*
         ** Display the current address info.   Start with the protocol-
         ** independent fields first.
         */
         fprintf( stderr,
                  "Setting up a passive socket based on the "
                  "following address info:\n"
                  "   ai_flags     = %#02X\n"
                  "   ai_family    = %d (PF_INET = %d, PF_INET6 = %d)\n"
                  "   ai_socktype  = %d (SOCK_STREAM = %d, SOCK_DGRAM = %d)\n"
                  "   ai_protocol  = %d (IPPROTO_TCP = %d, IPPROTO_UDP = %d)\n"
                  "   ai_addrlen   = %d (sockaddr_in = %lu, "
                  "sockaddr_in6 = %lu)\n",
                  ai->ai_flags,
                  ai->ai_family,
                  PF_INET,
                  PF_INET6,
                  ai->ai_socktype,
                  SOCK_STREAM,
                  SOCK_DGRAM,
                  ai->ai_protocol,
                  IPPROTO_TCP,
                  IPPROTO_UDP,
                  ai->ai_addrlen,
                  sizeof( struct sockaddr_in ),
                  sizeof( struct sockaddr_in6 ) );
         /*
         ** Now display the protocol-specific formatted socket address.  Note
         ** that the program is requesting that getnameinfo(3) convert the
         ** host & service into numeric strings.
         */
         getnameinfo( ai->ai_addr,
                      ai->ai_addrlen,
                      hostBfr,
                      sizeof( hostBfr ),
                      servBfr,
                      sizeof( servBfr ),
                      NI_NUMERICHOST | NI_NUMERICSERV );
         switch ( ai->ai_family )
         {
            case PF_INET:   /* IPv4 address record. */
            {
               struct sockaddr_in *p = (struct sockaddr_in*) ai->ai_addr;
               fprintf( stderr,
                        "   ai_addr      = sin_family:   %d (AF_INET = %d, "
                        "AF_INET6 = %d)\n"
                        "                  sin_addr:     %s\n"
                        "                  sin_port:     %s\n",
                        p->sin_family,
                        AF_INET,
                        AF_INET6,
                        hostBfr,
                        servBfr );
               break;
            }  /* End CASE of IPv4. */
            case PF_INET6:   /* IPv6 address record. */
            {
               struct sockaddr_in6 *p = (struct sockaddr_in6*) ai->ai_addr;
               fprintf( stderr,
                        "   ai_addr      = sin6_family:   %d (AF_INET = %d, "
                        "AF_INET6 = %d)\n"
                        "                  sin6_addr:     %s\n"
                        "                  sin6_port:     %s\n"
                        "                  sin6_flowinfo: %d\n"
                        "                  sin6_scope_id: %d\n",
                        p->sin6_family,
                        AF_INET,
                        AF_INET6,
                        hostBfr,
                        servBfr,
                        p->sin6_flowinfo,
                        p->sin6_scope_id );
               break;
            }  /* End CASE of IPv6. */
            default:   /* Can never get here, but just for completeness. */
            {
               fprintf( stderr,
                        "%s (line %d): ERROR - Unknown protocol family (%d).\n",
                        pgmName,
                        __LINE__,
                        ai->ai_family );
               freeaddrinfo( aiHead );
               return -1;
            }  /* End DEFAULT case (unknown protocol family). */
         }  /* End SWITCH on protocol family. */
      }  /* End IF verbose mode. */
      /*
      ** Create a socket using the info in the addrinfo structure.
      */
      CHK( desc[ *descSize ] = socket( ai->ai_family,
                                       ai->ai_socktype,
                                       ai->ai_protocol ) );
      /*
      ** Here is the code that prevents "IPv4 mapped addresses", as discussed
      ** in Section 22.1.3.1.  If an IPv6 socket was just created, then set the
      ** IPV6_V6ONLY socket option.
      */
      if ( ai->ai_family == PF_INET6 )
      {
#if defined( IPV6_V6ONLY )
         /*
         ** Disable IPv4 mapped addresses.
         */
         int v6Only = 1;
         CHK( setsockopt( desc[ *descSize ],
                          IPPROTO_IPV6,
                          IPV6_V6ONLY,
                          &v6Only,
                          sizeof( v6Only ) ) );
#else
         /*
         ** IPV6_V6ONLY is not defined, so the socket option can't be set and
         ** thus IPv4 mapped addresses can't be disabled.  Print a warning
         ** message and close the socket.  Design note: If the
         ** #if...#else...#endif construct were removed, then this program
         ** would not compile (because IPV6_V6ONLY isn't defined).  That's an
         ** acceptable approach; IPv4 mapped addresses are certainly disabled
         ** if the program can't build!  However, since this program is also
         ** designed to work for IPv4 sockets as well as IPv6, I decided to
         ** allow the program to compile when IPV6_V6ONLY is not defined, and
         ** turn it into a run-time warning rather than a compile-time error.
         ** IPv4 mapped addresses are still disabled because _all_ IPv6 traffic
         ** is disabled (all IPv6 sockets are closed here), but at least this
         ** way the server can still service IPv4 network traffic.
         */
         fprintf( stderr,
                  "%s (line %d): WARNING - Cannot set IPV6_V6ONLY socket "
                  "option.  Closing IPv6 %s socket.\n",
                  pgmName,
                  __LINE__,
                  ai->ai_protocol == IPPROTO_TCP  ?  "TCP"  :  "UDP" );
         CHK( close( desc[ *descSize ] ) );
         continue;   /* Go to top of FOR loop w/o updating *descSize! */
#endif /* IPV6_V6ONLY */
      }  /* End IF this is an IPv6 socket. */
      /*
      ** Bind the socket.  Again, the info from the addrinfo structure is used.
      */
      CHK( bind( desc[ *descSize ],
                 ai->ai_addr,
                 ai->ai_addrlen ) );
      /*
      ** If this is a TCP socket, put the socket into passive listening mode
      ** (listen is only valid on connection-oriented sockets).
      */
      if ( ai->ai_socktype == SOCK_STREAM )
      {
         CHK( listen( desc[ *descSize ],
                      MAXCONNQLEN ) );
      }
      /*
      ** Socket set up okay.  Bump index to next descriptor array element.
      */
      *descSize += 1;
   }  /* End FOR each address info structure returned. */
   /*
   ** Dummy check for unused address records.
   */
   if ( verbose && ( ai != NULL ) )
   {
      fprintf( stderr,
               "%s (line %d): WARNING - Some address records were "
               "not processed due to insufficient array space.\n",
               pgmName,
               __LINE__ );
   }  /* End IF verbose and some address records remain unprocessed. */
   /*
   ** Clean up.
   */
   freeaddrinfo( aiHead );
   return 0;
}  /* End openSckt() */
/******************************************************************************
* Function: pit
*
* Description:
*    Listen on a set of sockets and send the current microsecond counter
*    that was produced by gettimeofday(), to any clients.  This function 
*    never returns.
*
* Parameters:
*    tSckt     - Array of TCP socket descriptors on which to listen.
*    tScktSize - Size of the tSckt array (nbr of elements).
*    uSckt     - Array of UDP socket descriptors on which to listen.
*    uScktSize - Size of the uSckt array (nbr of elements).
*
* Return Value: None.
******************************************************************************/
static void pit( int    tSckt[ ],
                 size_t tScktSize,
                 int    uSckt[ ],
                 size_t uScktSize )
{
   char                     bfr[ 256 ];
   ssize_t                  count;
   struct pollfd           *desc;
   size_t                   descSize = tScktSize + uScktSize;
   int                      idx;
   int                      newSckt;
   struct sockaddr         *sadr;
   socklen_t                sadrLen;
   struct sockaddr_storage  sockStor;
   int                      status;
   size_t                   timeLen;
   time_t                   timeVal;
   ssize_t                  wBytes;
   unsigned long long	    secs;
   int 	    		    ret;
   /*
   ** Allocate memory for the poll(2) array.
   */
   desc = malloc( descSize * sizeof( struct pollfd ) );
   if ( desc == NULL )
   {
      fprintf( stderr,
               "%s (line %d): ERROR - %s.\n",
               pgmName,
               __LINE__,
               strerror( ENOMEM ) );
      exit( 1 );
   }
   /*
   ** Initialize the poll(2) array.
   */
   for ( idx = 0;     idx < descSize;     idx++ )
   {
      desc[ idx ].fd      = idx < tScktSize  ?  tSckt[ idx ]
                                             :  uSckt[ idx - tScktSize ];
      desc[ idx ].events  = POLLIN;
      desc[ idx ].revents = 0;
   }
   /*
   ** Main PIT server loop.  Handles both TCP & UDP requests.  This is
   ** an interative server, and all requests are handled directly within the
   ** main loop.
   */
   while ( true )   /* Do forever. */
   {
      /*
      ** Wait for activity on one of the sockets.  The DO..WHILE construct is
      ** used to restart the system call in the event the process is
      ** interrupted by a signal.
      */
      do
      {
         status = poll( desc,
                        descSize,
                        -1 /* Wait indefinitely for input. */ );
      } while ( ( status < 0 ) && ( errno == EINTR ) );
      CHK( status );   /* Check for a bona fide system call error. */
      /*
      ** Get the current time.
      */
#if defined(Windows)
   LARGE_INTEGER freq,counter;
   double wintime,bigcounter;
   /* For Windows the time_of_day() is useless. It increments in 55 milli 
    * second increments. By using the Win32api one can get access to the 
    * high performance measurement interfaces. With this one can get back 
    * into the 8 to 9 microsecond resolution.
    */
      QueryPerformanceFrequency(&freq);
      QueryPerformanceCounter(&counter);
      bigcounter=(double)counter.HighPart *(double)0xffffffff +
              (double)counter.LowPart;
      wintime = (double)(bigcounter/(double)freq.LowPart);
      secs = (long long)(wintime * 1000000);
#else
      ret = gettimeofday( &tm,0 );
      secs = ((unsigned long long)tm.tv_sec * 1000000) 
		+ (unsigned long long)tm.tv_usec;
#endif

      ret = sprintf(timeStr,"%llu",secs);
      timeLen = strlen( timeStr );
      /*
      ** Process sockets with input available.
      */
      for ( idx = 0;     idx < descSize;     idx++ )
      {
         switch ( desc[ idx ].revents )
         {
            case 0:        /* No activity on this socket; try the next. */
               continue;
            case POLLIN:   /* Network activity.  Go process it.         */
               break;
            default:       /* Invalid poll events.                      */
            {
               fprintf( stderr,
                        "%s (line %d): ERROR - Invalid poll event (%#02X).\n",
                        pgmName,
                        __LINE__,
                        desc[ idx ].revents );
               exit( 1 );
            }
         }  /* End SWITCH on returned poll events. */
         /*
         ** Determine if this is a TCP request or UDP request.
         */
         if ( idx < tScktSize )
         {
            /*
            ** TCP connection requested.  Accept it.  Notice the use of
            ** the sockaddr_storage data type.
            */
            sadrLen = sizeof( sockStor );
            sadr    = (struct sockaddr*) &sockStor;
            CHK( newSckt = accept( desc[ idx ].fd,
                                   sadr,
                                   &sadrLen ) );
            CHK( shutdown( newSckt,       /* Server never recv's anything. */
                           SHUT_RD ) );
            if ( verbose )
            {
               /*
               ** Display the socket address of the remote client.  Begin with
               ** the address-independent fields.
               */
               fprintf( stderr,
                        "Sockaddr info for new TCP client:\n"
                        "   sa_family = %d (AF_INET = %d, AF_INET6 = %d)\n"
                        "   addr len  = %d (sockaddr_in = %lu, "
                        "sockaddr_in6 = %lu)\n",
                        sadr->sa_family,
                        AF_INET,
                        AF_INET6,
                        sadrLen,
                        sizeof( struct sockaddr_in ),
                        sizeof( struct sockaddr_in6 ) );
               /*
               ** Display the address-specific fields.
               */
               getnameinfo( sadr,
                            sadrLen,
                            hostBfr,
                            sizeof( hostBfr ),
                            servBfr,
                            sizeof( servBfr ),
                            NI_NUMERICHOST | NI_NUMERICSERV );
               /*
               ** Notice that we're switching on an address family now, not a
               ** protocol family.
               */
               switch ( sadr->sa_family )
               {
                  case AF_INET:   /* IPv4 address. */
                  {
                     struct sockaddr_in *p = (struct sockaddr_in*) sadr;
                     fprintf( stderr,
                              "   sin_addr  = sin_family: %d\n"
                              "               sin_addr:   %s\n"
                              "               sin_port:   %s\n",
                              p->sin_family,
                              hostBfr,
                              servBfr );
                     break;
                  }  /* End CASE of IPv4. */
                  case AF_INET6:   /* IPv6 address. */
                  {
                     struct sockaddr_in6 *p = (struct sockaddr_in6*) sadr;
                     fprintf( stderr,
                              "   sin6_addr = sin6_family:   %d\n"
                              "               sin6_addr:     %s\n"
                              "               sin6_port:     %s\n"
                              "               sin6_flowinfo: %d\n"
                              "               sin6_scope_id: %d\n",
                              p->sin6_family,
                              hostBfr,
                              servBfr,
                              p->sin6_flowinfo,
                              p->sin6_scope_id );
                     break;
                  }  /* End CASE of IPv6. */
                  default:   /* Can never get here, but for completeness. */
                  {
                     fprintf( stderr,
                              "%s (line %d): ERROR - Unknown address "
                              "family (%d).\n",
                              pgmName,
                              __LINE__,
                              sadr->sa_family );
                     break;
                  }  /* End DEFAULT case (unknown address family). */
               }  /* End SWITCH on address family. */
            }  /* End IF verbose mode. */
            /*
            ** Send the PIT to the client.
            */
            wBytes = timeLen;
            while ( wBytes > 0 )
            {
               do
               {
                  count = write( newSckt,
                                 timeStr,
                                 wBytes );
               } while ( ( count < 0 ) && ( errno == EINTR ) );
               CHK( count );   /* Check for an error. */
               wBytes -= count;
            }  /* End WHILE there is data to send. */
            CHK( close( newSckt ) );
         }  /* End IF this was a TCP connection request. */
         else
         {
            /*
            ** This is a UDP socket, and a datagram is available.  The funny
            ** thing about UDP requests is that this server doesn't require any
            ** client input; but it can't send the PIT unless it knows a client
            ** wants the data, and the only way that can occur with UDP is if
            ** the server receives a datagram from the client.  Thus, the
            ** server must receive _something_, but the content of the datagram
            ** is irrelevant.  Read in the datagram.  Again note the use of
            ** sockaddr_storage to receive the address.
            */
            sadrLen = sizeof( sockStor );
            sadr    = (struct sockaddr*) &sockStor;
            CHK( count = recvfrom( desc[ idx ].fd,
                                   bfr,
                                   sizeof( bfr ),
                                   0,
                                   sadr,
                                   &sadrLen ) );
            /*
            ** Display whatever was received on stdout.
            */
            if ( verbose )
            {
               ssize_t rBytes = count;
               fprintf( stderr,
                        "%s: UDP datagram received (%ld bytes).\n",
                        pgmName,
                        count );
               while ( count > 0 )
               {
                  fputc( bfr[ rBytes - count-- ],
                         stdout );
               }
               if ( bfr[ rBytes-1 ] != '\n' )
                  fputc( '\n', stdout );   /* Newline also flushes stdout. */
               /*
               ** Display the socket address of the remote client.  Address-
               ** independent fields first.
               */
               fprintf( stderr,
                        "Remote client's sockaddr info:\n"
                        "   sa_family = %d (AF_INET = %d, AF_INET6 = %d)\n"
                        "   addr len  = %d (sockaddr_in = %lu, "
                        "sockaddr_in6 = %lu)\n",
                        sadr->sa_family,
                        AF_INET,
                        AF_INET6,
                        sadrLen,
                        sizeof( struct sockaddr_in ),
                        sizeof( struct sockaddr_in6 ) );
               /*
               ** Display the address-specific information.
               */
               getnameinfo( sadr,
                            sadrLen,
                            hostBfr,
                            sizeof( hostBfr ),
                            servBfr,
                            sizeof( servBfr ),
                            NI_NUMERICHOST | NI_NUMERICSERV );
               switch ( sadr->sa_family )
               {
                  case AF_INET:   /* IPv4 address. */
                  {
                     struct sockaddr_in *p = (struct sockaddr_in*) sadr;
                     fprintf( stderr,
                              "   sin_addr  = sin_family: %d\n"
                              "               sin_addr:   %s\n"
                              "               sin_port:   %s\n",
                              p->sin_family,
                              hostBfr,
                              servBfr );
                     break;
                  }  /* End CASE of IPv4 address. */
                  case AF_INET6:   /* IPv6 address. */
                  {
                     struct sockaddr_in6 *p = (struct sockaddr_in6*) sadr;
                     fprintf( stderr,
                              "   sin6_addr = sin6_family:   %d\n"
                              "               sin6_addr:     %s\n"
                              "               sin6_port:     %s\n"
                              "               sin6_flowinfo: %d\n"
                              "               sin6_scope_id: %d\n",
                              p->sin6_family,
                              hostBfr,
                              servBfr,
                              p->sin6_flowinfo,
                              p->sin6_scope_id );
                     break;
                  }  /* End CASE of IPv6 address. */
                  default:   /* Can never get here, but for completeness. */
                  {
                     fprintf( stderr,
                              "%s (line %d): ERROR - Unknown address "
                              "family (%d).\n",
                              pgmName,
                              __LINE__,
                              sadr->sa_family );
                     break;
                  }  /* End DEFAULT case (unknown address family). */
               }  /* End SWITCH on address family. */
            }  /* End IF verbose mode. */
            /*
            ** Send the PIT to the client.
            */
            wBytes = timeLen;
            while ( wBytes > 0 )
            {
               do
               {
                  count = sendto( desc[ idx ].fd,
                                  timeStr,
                                  wBytes,
                                  0,
                                  sadr,        /* Address & address length   */
                                  sadrLen );   /*    received in recvfrom(). */
               } while ( ( count < 0 ) && ( errno == EINTR ) );
               CHK( count );   /* Check for a bona fide error. */
               wBytes -= count;
            }  /* End WHILE there is data to send. */
         }  /* End ELSE a UDP datagram is available. */
         desc[ idx ].revents = 0;   /* Clear the returned poll events. */
      }  /* End FOR each socket descriptor. */
   }  /* End WHILE forever. */
}  /* End pit() */