Thread: Reading date/time from NTP

  1. #1
    Registered User
    Join Date
    Feb 2019
    Posts
    646

    Reading date/time from NTP

    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 );
    }

  2. #2
    Registered User
    Join Date
    Feb 2019
    Posts
    646
    Here's a simple compile/usage example (It doesn't need any additional libraries and doesn't need ntp/timedated.service configured).
    I have a ntp daemon configured in 10.4.9.1 (but you can use any known ntp site name you want):
    Code:
    $ cc -O2 -o ntp3 ntp3.c
    $ ./ntp3 10.4.9.1
    Wed Jan 29 10:38:08 2020

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. C++ date time
    By rahulsk1947 in forum C++ Programming
    Replies: 2
    Last Post: 05-22-2007, 02:06 PM
  2. Date & Time
    By g4j31a5 in forum C++ Programming
    Replies: 10
    Last Post: 08-15-2006, 09:25 AM
  3. Date and Time
    By Calavera in forum C Programming
    Replies: 3
    Last Post: 11-26-2004, 03:56 PM
  4. Getting time and date
    By winsonlee in forum C++ Programming
    Replies: 3
    Last Post: 08-18-2004, 11:31 PM
  5. Date and Time
    By fkheng in forum C Programming
    Replies: 19
    Last Post: 06-10-2003, 02:51 AM

Tags for this Thread