Sometime ago somebody asked how to get date/time from NTP. Here's a simple code. I hope it helps somebody:
Code:
/* ntp3.c */
#define _GNU_SOURCE
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
// NTP uses Jan 1st 1900 as timestamp base.
// UNIX uses Jan 1st 1970.
// Seconds since 1 Jan 1900 to 1 Jan 1970.
#define NTP_TIMESTAMP_DELTA 2208988800U
#define NTP2UNIX_TIMESTAMP( x ) \
( time_t ) ( ( x ) - NTP_TIMESTAMP_DELTA )
// NTP v3 protocol is binary and uses this structure
// RFC-1305: https://tools.ietf.org/html/rfc1305
struct ntp_packet_s
{
// +-+-+-+-+-+-+-+-+
// |L|L|V|V|V|M|M|M|
// +-+-+-+-+-+-+-+-+
// --- ----- -----
// | | |
// | | +------ Mode (3 for client)
// | +------------ Version (3)
// +----------------- Leap "Second" (last minute) Indicator (0)
uint8_t li_vn_mode;
uint8_t stratum; // Stratum level of the local clock.
uint8_t poll; // Maximum interval between successive messages.
uint8_t precision; // Precision of the local clock.
uint32_t rootDelay; // Total round trip delay time.
uint32_t rootDispersion; // Max error aloud from primary clock source.
uint32_t refId; // Reference clock identifier.
uint32_t refTm_s; // Reference time-stamp seconds.
uint32_t refTm_f; // Reference time-stamp fraction of a second.
uint32_t origTm_s; // Originate time-stamp seconds.
uint32_t origTm_f; // Originate time-stamp fraction of a second.
uint32_t rxTm_s; // Received time-stamp seconds.
uint32_t rxTm_f; // Received time-stamp fraction of a second.
// These are ntp timestamps!
uint32_t txTm_s; // Transmit time-stamp seconds.
uint32_t txTm_f; // Transmit time-stamp fraction of a second.
};
// read/write timeout in seconds.
#define TIMEOUT 3
// Global var because signal handler will use it.
static int sigop; // 0 = writing, 1 = readning
static void timeout_handler ( int );
int main ( int argc, char *argv[] )
{
struct addrinfo ai_hint = { .ai_family = AF_INET }; // restrict to ipv4.
struct ntp_packet_s ntppkt = { .li_vn_mode = 033 };
struct sigaction sa = { 0 };
struct addrinfo *resai;
struct servent *se;
time_t t;
int fd;
// if second argument is not provided. Error.
if ( ! *++argv )
{
fprintf ( stderr, "Usage: %s <\"addr\">\n", basename( *(argv - 1) ) );
return EXIT_FAILURE;
}
// Resolve address...
if ( getaddrinfo ( *argv, NULL, &ai_hint, &resai ) )
{
perror ( "getaddrinfo" );
return EXIT_FAILURE;
}
if ( ! resai )
{
fprintf ( stderr, "ERROR: Cannot resolve '%s'.\n", *argv );
return EXIT_SUCCESS;
}
// Create UDP socket.
if ( ( fd = socket ( AF_INET, SOCK_DGRAM, IPPROTO_UDP ) ) == -1 )
{
perror ( "socket" );
freeaddrinfo ( resai );
return EXIT_FAILURE;
}
// Prefeer to get service port number from /etc/services.
if ( se = getservbyname ( "ntp", NULL ) )
( ( struct sockaddr_in * )resai->ai_addr )->sin_port = se->s_port;
else
// default NTP port is not on /etc/services.
( ( struct sockaddr_in * )resai->ai_addr )->sin_port = htons ( 123 );
// Nothing wrong connecting to an endpoint using UDP!
// We need this to use write/read instead of sendto/recvfrom...
// I did this only to make the code a little bit simplier.
if ( connect ( fd, resai->ai_addr, resai->ai_addrlen ) == -1 )
{
perror ( "connect" );
freeaddrinfo ( resai );
goto fail;
}
freeaddrinfo ( resai );
// Using SIGALRM to implement timeout...
sigfillset ( &sa.sa_mask ); // mask all other signals.
sa.sa_handler = timeout_handler;
sigaction ( SIGALRM, &sa, NULL );
// Send request.
sigop = 0; // sending...
alarm ( TIMEOUT );
if ( write ( fd, ( void * )&ntppkt, sizeof ntppkt ) == -1 )
{
perror ( "write" );
goto fail;
}
alarm ( 0 );
// Get response.
sigop = 1; // receiving...
alarm ( TIMEOUT );
if ( read ( fd, ( void * )&ntppkt, sizeof ntppkt ) == -1 )
{
perror ( "read" );
goto fail;
}
alarm ( 0 );
// Convert timestamp and print.
t = NTP2UNIX_TIMESTAMP( ntohl ( ntppkt.txTm_s ) );
printf ( "%s\n", ctime ( &t ) );
close ( fd );
return EXIT_SUCCESS;
fail:
// No need to reset alarm, we'll exit anyway!
close ( fd );
return EXIT_FAILURE;
}
// FIX: Why did I use write() instead of fputs?
// Because fputs, printf, ... are not Asynchronous Call Safe (signals).
void timeout_handler ( int signum )
{
char **p;
static const char * const msg[] =
{ "TIMEOUT: ", "sending request\n", "receiving respospnse\n" };
p = ( char ** )msg;
// Using write() 'cause printf() isn't AS-Safe.
write ( STDERR_FILENO, *p, strlen ( *p ) );
p += 1 + sigop;
write ( STDERR_FILENO, *p, strlen ( *p ) );
// POSIX tells us to return 128+signal.
exit ( 128 + signum );
}