/** * @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; }