blob: 37f26c0fe9e5b1a01dba779b3aa2a5c2e8f7fc7b [file] [log] [blame]
/******************************************************************************
* 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 = 0x%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 (0x%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() */