jade-netdelay.c

/**
 * @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;
}
Valid HTML 4.01 Valid CSS