/**
* @file jade-netdelay.c
* @date October 20, 2010
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sys/timex.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
/** The port number bound to the UDP server. */
#define JADE_PORT 10013
/* For readability... */
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
typedef struct timespec timespec;
/**
* The main entry point for the client mode that executes periodically. This
* function sends a timespec to the specified host and then receives a response.
* The response is interpreted as a timespec duration and printed to standard
* output.
*
* @param host The host to communicate with.
*
* @return EXIT_SUCCESS or EXIT_FAILURE.
*/
static int main_client(const char * host);
/**
* The main entry point for the server mode that executes from a thread. This
* function receives a timespec from a client, records its own timespec, and
* then sends the difference back to the client.
*
* @param arg The thread input argument (not used).
*
* @return EXIT_SUCCESS or EXIT_FAILURE.
*/
static void * main_server(void * arg);
/**
* Normalizes a timespec so the 'tv_nsec' field ranges from 0 (inclusive) to
* 1e9 (exclusive).
*
* @param t The timespec to normalize.
*
* @return The 't' parameter.
*/
static timespec * timespec_normalize(timespec * t);
/**
* Prints a timespec to standard output.
*
* @param t The timespec to print.
*/
static void timespec_print(const timespec * t);
/**
* Receives a timespec from a UDP socket.
*
* @param sockfd The socket file descriptor.
* @param src_addr The address of the UDP client.
* @param t The timespec received.
*
* @return EXIT_SUCCESS or EXIT_FAILURE.
*/
static int timespec_recv(int sockfd, sockaddr_in * src_addr, timespec * t);
/**
* Sends a timespec to a remote machine using UDP. The functions returns
* EXIT_SUCCESS or EXIT_FAILURE.
*
* @note The data is so small, messages are never fragmented.
*
* @param sockfd The file descriptor for the remote socket.
* @param dest_addr The destination socket address.
* @param timespec The timespec to send to the client.
*
* @return EXIT_SUCCESS if successfull; otherwise, EXIT_FAILURE.
*/
static int timespec_send(
int sockfd,
const sockaddr_in * dest_addr,
const timespec * t);
/**
* Subtracts two timespec values and stores the result into a third data
* structure. The output may be one of the specified parameters. The output is
* normalized.
*
* @param t1 The timespec to subtract from.
* @param t2 The timespec to subtract.
* @param difference The result of the operation.
*
* @return The 'difference' parameter.
*/
static timespec * timespec_subtract(
const timespec * t1,
const timespec * t2,
timespec * difference);
/**
* Initializes a UDP socket for the server thread. The socket is bound to
* JADE_PORT.
*
* @param sockfd If successful, the initialized socket file descriptor.
* @return EXIT_SUCCESS or EXIT_FAILURE.
*/
static int udp_socket_init(int * sockfd);
/**
* The main entry point of the program. This function checks the command-line
* arguments, starts the server thread, and possibly starts a loop to check the
* network delay.
*
* @param argc The number of command-line arguments.
* @param argv The command-line values.
*
* @return EXIT_SUCCESS or EXIT_FAILURE.
*/
int main(int argc, const char * argv[])
{
/* Check for usage help. */
if (2 == argc && 0 == strcmp("--help", argv[1])) {
printf("Usage: jade-netdelay [remote-ip]\n");
printf("\n");
printf(" remote-ip Optionally, the remote IP address\n");
printf("\n");
printf("This program acts as either a server or a client. When running as a server,\n"
"this program listens for UDP data on port %hu. When data arrives, it is\n"
"interpreted as a 'struct timespec'. The current time on the server is then\n"
"determined, subtracted from this received timespec, and the difference is\n"
"sent back to the source of the incoming data.\n",
JADE_PORT);
printf("\n");
printf("When running as a client, this program sends timespec messages every second to\n"
"a server that is listening on port %hu. It then waits for the server to\n"
"return the difference in time it computes, and the difference is written to\n"
"standard output.\n",
JADE_PORT);
printf("\n");
printf("If a remote IP address is specified, this program runs as a client. Otherwise\n"
"this program runs as a client.\n");
printf("\n");
return EXIT_SUCCESS;
}
/* Check for a syntax error. */
if (argc > 2) {
fputs("The syntax of the command is incorrect.\n", stderr);
return EXIT_FAILURE;
}
/* Check for server/passive mode. */
if (argc == 1) {
pthread_t id;
printf(
"Entering passive mode. Listening for UDP timespecs on port %hu.\n",
JADE_PORT);
/* Start the server thread that listens on JADE_PORT. */
if (0 != pthread_create(&id, NULL, &main_server, NULL)) {
perror("pthread_create");
return EXIT_FAILURE;
}
/* Wait for the thread to terminate (only if there is an error). */
if (0 != pthread_join(id, NULL)) {
perror("pthread_join");
return EXIT_FAILURE;
}
/* Otherwise, this is running in client/active mode. */
} else {
/* Loop until the user quits the program. */
for (;;) {
/* Sleep for approximately one second. */
if (0 != sleep(1)) {
perror("sleep");
return EXIT_FAILURE;
}
/* Send a request for a time difference to the server. */
if (EXIT_SUCCESS != main_client(argv[1]))
return EXIT_FAILURE;
}
}
/* The program should not reach this point under normal circumstances. */
return EXIT_SUCCESS;
}
/* ------------------------------------------------------------------------- */
static int main_client(const char * host)
{
timespec difference;
timespec now;
sockaddr_in sin;
int sockfd;
sockaddr_in src_addr;
/* Create the socket for UDP/IP communications. */
if (-1 == (sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP))) {
perror("socket");
return EXIT_FAILURE;
}
/* Prepare the socket address for the host. */
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(JADE_PORT);
if (0 >= inet_pton(AF_INET, host, &sin.sin_addr)) {
fprintf(stderr, "Invalid host IP address '%s'.\n", host);
close(sockfd);
return EXIT_FAILURE;
}
/* Get the current time, which will be sent to the host. */
if (0 != clock_gettime(CLOCK_REALTIME, &now)) {
perror("clock_gettime");
close(sockfd);
return EXIT_FAILURE;
}
/* Send the timespec to the host. */
if (EXIT_SUCCESS != timespec_send(sockfd, &sin, &now)) {
close(sockfd);
return EXIT_FAILURE;
}
/* Receive the difference from the host. */
if (EXIT_SUCCESS != timespec_recv(sockfd, &src_addr, &difference)) {
close(sockfd);
return EXIT_FAILURE;
}
/* Print the difference returned from the host. */
timespec_print(&difference);
printf("\n");
/* Return successfully. */
return EXIT_SUCCESS;
}
/* ------------------------------------------------------------------------- */
static void * main_server(void * arg)
{
int sockfd;
/* Create the socket for UDP/IP communications. */
if (EXIT_SUCCESS != udp_socket_init(&sockfd))
return (void *)EXIT_FAILURE;
/* Loop indefinitely. */
for (;;) {
timespec difference;
timespec now;
timespec other;
sockaddr_in src_addr;
/* Receive a timespec from the client. */
if (EXIT_SUCCESS != timespec_recv(sockfd, &src_addr, &other)) {
close(sockfd);
return (void *)EXIT_FAILURE;
}
/* Get the current time. */
if (0 != clock_gettime(CLOCK_REALTIME, &now)) {
perror("clock_gettime");
close(sockfd);
return (void *)EXIT_FAILURE;
}
/* Calculate the difference in time. */
timespec_subtract(&now, &other, &difference);
/* Send the difference in time back to the client. */
if (EXIT_SUCCESS != timespec_send(sockfd, &src_addr, &difference)) {
close(sockfd);
return (void *)EXIT_FAILURE;
};
}
/* Close the socket before returning successfully. Note this should not
* occur under normal circumstances because there is no way out of the loop
* above. */
close(sockfd);
return (void *)EXIT_SUCCESS;
}
/* ------------------------------------------------------------------------- */
static timespec * timespec_normalize(timespec * t)
{
/* Eliminate overflows. */
while (t->tv_nsec > 1e9L) {
t->tv_nsec -= 1e9L;
t->tv_sec++;
}
/* Eliminate underflows. */
while (t->tv_nsec < 0L) {
t->tv_nsec += 1e9L;
t->tv_sec--;
}
/* Return the normalized parameter. */
return t;
}
/* ------------------------------------------------------------------------- */
static void timespec_print(const timespec * t)
{
/* Print the timespec. */
printf(
"%lu.%09lu",
(unsigned long)t->tv_sec,
(unsigned long)t->tv_nsec);
}
/* ------------------------------------------------------------------------- */
static int timespec_recv(
int sockfd,
sockaddr_in * src_addr,
timespec * t)
{
socklen_t addrlen;
ssize_t recsize;
/* The addrlen is used as both input and output for recvfrom. */
addrlen = sizeof(*src_addr);
/* Receive the timespec from the client. */
recsize = recvfrom(
sockfd,
t,
sizeof(*t),
0,
(sockaddr *)src_addr,
&addrlen);
/* Check for network errors. */
if (-1 == recsize) {
perror("recvfrom");
return EXIT_FAILURE;
}
/* Check the size of the data makes sense. */
if (sizeof(*t) != (size_t)recsize) {
fprintf(
stderr,
"Invalid number of bytes received (%d).",
(int)recsize);
return EXIT_FAILURE;
}
/* Return successfully. */
return EXIT_SUCCESS;
}
/* ------------------------------------------------------------------------- */
static int timespec_send(
int sockfd,
const sockaddr_in * dest_addr,
const timespec * t)
{
size_t len;
const char * ptr;
/* Get a byte pointer to the start of the timespec and the length of the
* data in bytes. */
ptr = (const char *)t;
len = sizeof(*t);
/* Loop while there are bytes remaining to send. This should occur only
* once for small amouts of data. */
while (len > 0) {
ssize_t result;
/* Send as many bytes as possible, and check for errors. */
result = sendto(
sockfd,
ptr,
len,
0,
(sockaddr *)dest_addr,
(socklen_t)sizeof(*dest_addr));
/* Check for network failures. */
if (result <= 0 || result > len) {
perror("sendto");
return EXIT_FAILURE;
}
/* Adjust the remaining bytes to send, which should be zero. */
len -= (size_t)result;
ptr += (size_t)result;
}
/* Return successfully. */
return EXIT_SUCCESS;
}
/* ------------------------------------------------------------------------- */
static timespec * timespec_subtract(
const timespec * t1,
const timespec * t2,
timespec * difference)
{
/* Add and two values. */
difference->tv_sec = t1->tv_sec - t2->tv_sec;
difference->tv_nsec = t1->tv_nsec - t2->tv_nsec;
/* Return the normalized difference. */
return timespec_normalize(difference);
}
/* ------------------------------------------------------------------------- */
static int udp_socket_init(int * sockfd)
{
int opt;
sockaddr_in sin;
/* Create the socket for UDP/IP communications. */
if (-1 == (*sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP))) {
perror("socket");
return EXIT_FAILURE;
}
/* Set the socket option to allow Control-C to end the program without
* causing the program to hold onto the binding after the program has
* terminated. */
opt = 1;
if (0 > setsockopt(*sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int))) {
perror("setsockopt");
close(*sockfd);
*sockfd = -1;
return EXIT_FAILURE;
}
/* Prepare the socket address to bind. */
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(JADE_PORT);
/* Bind to the port. */
if (-1 == bind(*sockfd, (sockaddr *)&sin, sizeof(sin))) {
perror("bind");
close(*sockfd);
*sockfd = -1;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}