Search code examples
clinuxepochntp

How to convert NTP time to Unix Epoch time in C language (Linux)


I have been trying for several months to create a simple SNTP single Client/Server based on RFC5905. Finally I manage to make it work at least I think it works correctly, but when I tried to test my code against a real NTP server (e.g. 0.se.pool.ntp.org:123) the timestamps that I am receiving need to be recalculated. I have tried several different approaches but no matter for 3 days now but no matter what I tried nothing yet.

Does anybody know how to convert the NTP timestamp to Unix epoch timestamp?

Syntax to execute the Server e.g. ./server 127.0.0.1:5000 and Client e.g. ./client 127.0.0.1:5000

Syntax to execute the Client against a real NTP server e.g. ./client 0.se.pool.ntp.org:123

Sample of working code Client:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <math.h>
#include <sys/timeb.h>
#include <inttypes.h>
#include <limits.h>
#include <assert.h>

#define UNIX_EPOCH 2208988800UL /* 1970 - 1900 in seconds */

typedef struct client_packet client_packet;
struct client_packet {
  uint8_t client_li_vn_mode;
  uint8_t client_stratum;
  uint8_t client_poll;
  uint8_t client_precision;
  uint32_t client_root_delay;
  uint32_t client_root_dispersion;
  uint32_t client_reference_identifier;
  uint32_t client_reference_timestamp_sec;
  uint32_t client_reference_timestamp_microsec;
  uint32_t client_originate_timestamp_sec;
  uint32_t client_originate_timestamp_microsec;
  uint32_t client_receive_timestamp_sec;
  uint32_t client_receive_timestamp_microsec;
  uint32_t client_transmit_timestamp_sec;
  uint32_t client_transmit_timestamp_microsec;
}__attribute__((packed));

typedef struct server_send server_send;
struct server_send {
  uint8_t server_li_vn_mode;
  uint8_t server_stratum;
  uint8_t server_poll;
  uint8_t server_precision;
  uint32_t server_root_delay;
  uint32_t server_root_dispersion;
  char server_reference_identifier[4];
  uint32_t server_reference_timestamp_sec;
  uint32_t server_reference_timestamp_microsec;
  uint32_t server_originate_timestamp_sec;
  uint32_t server_originate_timestamp_microsec;
  uint32_t server_receive_timestamp_sec;
  uint32_t server_receive_timestamp_microsec;
  uint32_t server_transmit_timestamp_sec;
  uint32_t server_transmit_timestamp_microsec;
}__attribute__((packed));

/* Linux man page bind() */
#define handle_error(msg)               \
  do {perror(msg); exit(EXIT_FAILURE);} while (0)

uint32_t ClockGetTime() {
  struct timespec ts;
  clock_gettime(CLOCK_REALTIME, &ts);
  return (uint32_t)ts.tv_sec * 1000000LL + (uint32_t)ts.tv_nsec / 1000LL;
}

int main(int argc, char *argv[]) {

  int sockfd , numbytes;
  struct addrinfo hints, *servinfo, *p;
  int rv;

  client_packet memsend;
  server_send memrcv;
  
  memset( &memsend , 0 , sizeof memsend );
  memset( &memrcv , 0 , sizeof memrcv );

    char IP[16]; /* IP = 15 digits 1 extra for \0 null terminating character string */

    char PORT_STR[6]; /* Port = 5 digits MAX 1 extra for \0 null terminating character string */

    memset(IP , '\0' , sizeof(IP));
    memset(PORT_STR , '\0' , sizeof(PORT_STR));

    strcpy(IP, strtok(argv[1], ":"));
    strcpy(PORT_STR, strtok(NULL, ":"));
    
    memset( &hints , 0 , sizeof hints );
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;

    if ( ( rv = getaddrinfo( IP , PORT_STR , &hints , &servinfo ) ) != 0 ) {
      fprintf( stderr , "getaddrinfo: %s\n" , gai_strerror(rv) );
      return 1;
    }

    // loop through all the results and make a socket
    for( p = servinfo; p != NULL; p = p->ai_next ) {
      if ( ( sockfd = socket( p->ai_family , p->ai_socktype , p->ai_protocol ) ) == -1 ) {
    handle_error( "socket" );
    continue;
      }
      break;
    }

    if (p == NULL) {
      fprintf(stderr, "Error while binding socket\n");
      return 2;
    }

    memsend.client_li_vn_mode = 0b00100011;
    memsend.client_stratum = 0;
    memsend.client_poll = 0;
    memsend.client_precision = 0;
    memsend.client_root_delay = 0;
    memsend.client_root_dispersion = 0;
    memsend.client_reference_identifier = 0;
    memsend.client_reference_timestamp_sec = 0;
    memsend.client_reference_timestamp_microsec = 0;

    memsend.client_receive_timestamp_sec = 0;
    memsend.client_receive_timestamp_microsec = 0;

    time_t time_originate_sec = time(NULL);
    memsend.client_originate_timestamp_sec = time_originate_sec;
    memsend.client_originate_timestamp_microsec = ClockGetTime();

    memsend.client_transmit_timestamp_sec = memsend.client_originate_timestamp_sec;
    memsend.client_transmit_timestamp_microsec = memsend.client_originate_timestamp_microsec;
    
    if ( ( numbytes = sendto( sockfd, &memsend , sizeof memsend , 0 ,
                  p->ai_addr , p->ai_addrlen ) ) == -1 ) {
      handle_error("sendto");
      exit(1);
    }
  
    if ( ( numbytes = recvfrom( sockfd , &memrcv , sizeof memrcv , 0 ,
                (struct sockaddr *) &p->ai_addr, &p->ai_addrlen ) ) == -1 ) {
      handle_error( "recvfrom" );
      exit(1);
    }

    time_t time_rcv_sec = time(NULL);
    uint32_t client_rcv_timestamp_sec = time_rcv_sec;
    uint32_t client_rcv_timestamp_microsec = ClockGetTime();

    freeaddrinfo(servinfo);

    char Identifier[5];
    memset(Identifier , '\0' , sizeof Identifier);
    memcpy(Identifier , memrcv.server_reference_identifier , sizeof memrcv.server_reference_identifier);

    printf("\t Reference Identifier \t %"PRIu32" \t\t\t %s\n",memsend.client_reference_identifier,Identifier);
    printf("\t Reference Timestamp \t %"PRIu32".%"PRIu32" \t\t\t %"PRIu32".%"PRIu32"\n",memsend.client_reference_timestamp_sec,memsend.client_reference_timestamp_microsec,memrcv.server_reference_timestamp_sec,memrcv.server_reference_timestamp_microsec);
    printf("\t Originate Timestamp \t %"PRIu32".%"PRIu32" \t %"PRIu32".%"PRIu32"\n",memsend.client_originate_timestamp_sec,memsend.client_originate_timestamp_microsec,memrcv.server_originate_timestamp_sec,memrcv.server_originate_timestamp_microsec);
    printf("\t Receive Timestamp \t %"PRIu32".%"PRIu32" \t %"PRIu32".%"PRIu32"\n",client_rcv_timestamp_sec,client_rcv_timestamp_microsec,memrcv.server_receive_timestamp_sec,memrcv.server_receive_timestamp_microsec);
    printf("\t Transmit Timestamp \t %"PRIu32".%"PRIu32" \t %"PRIu32".%"PRIu32"\n\n",memsend.client_transmit_timestamp_sec,memsend.client_transmit_timestamp_microsec,memrcv.server_transmit_timestamp_sec,memrcv.server_transmit_timestamp_microsec);
  
    close(sockfd);

    return 0;
}

Sample of Server code:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <math.h>
#include <sys/timeb.h>
#include <inttypes.h>
#include <limits.h>

#define TRUE 1

typedef struct client_send client_send;
struct client_send {
  uint8_t client_li_vn_mode;
  uint8_t client_startum;
  uint8_t client_poll;
  uint8_t client_precision;
  uint32_t client_root_delay;
  uint32_t client_root_dispersion;
  uint32_t client_reference_identifier;
  uint32_t client_reference_timestamp_sec;
  uint32_t client_reference_timestamp_microsec;
  uint32_t client_originate_timestamp_sec;
  uint32_t client_originate_timestamp_microsec;
  uint32_t client_receive_timestamp_sec;
  uint32_t client_receive_timestamp_microsec;
  uint32_t client_transmit_timestamp_sec;
  uint32_t client_transmit_timestamp_microsec;
}__attribute__((packed));

typedef struct server_packet server_packet;
struct server_packet {
  uint8_t server_li_vn_mode;
  uint8_t server_startum;
  uint8_t server_poll;
  uint8_t server_precision;
  uint32_t server_root_delay;
  uint32_t server_root_dispersion;
  char server_reference_identifier[4];
  uint32_t server_reference_timestamp_sec;
  uint32_t server_reference_timestamp_microsec;
  uint32_t server_originate_timestamp_sec;
  uint32_t server_originate_timestamp_microsec;
  uint32_t server_receive_timestamp_sec;
  uint32_t server_receive_timestamp_microsec;
  uint32_t server_transmit_timestamp_sec;
  uint32_t server_transmit_timestamp_microsec;
}__attribute__((packed));

/* Linux man page bind() */
#define handle_error(msg)               \
  do {perror(msg); exit(EXIT_FAILURE);} while (0)

uint32_t ClockGetTime() {
  struct timespec ts;
  clock_gettime(CLOCK_REALTIME, &ts);
  return (uint32_t)ts.tv_sec * 1000000LL + (uint32_t)ts.tv_nsec / 1000LL;
}

unsigned long int precision() {

  struct timespec res;
  
  if ( clock_getres( CLOCK_REALTIME, &res) == -1 ) {
    perror( "clock get resolution" );
    return EXIT_FAILURE;
  }

  return res.tv_nsec / 1000;

}

void *get_in_addr(struct sockaddr *sa) {

  if (sa->sa_family == AF_INET) {
    return &(((struct sockaddr_in*)sa)->sin_addr);
  }

  return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int argc, char *argv[]) {

  server_packet send_mem;
  client_send rcv_mem;

  /* Empty structs */
  memset( &send_mem , 0 , sizeof send_mem );
  memset( &rcv_mem , 0 , sizeof rcv_mem );

  char s[INET_ADDRSTRLEN];
  struct addrinfo hints, *servinfo, *p;
  struct sockaddr_storage their_addr;
  socklen_t addr_len;
  int get, numbytes;
  int sockfd;

    char IP[16];

    char PORT_STR[6];

    memset(IP , '\0' , sizeof(IP));
    memset(PORT_STR , '\0' , sizeof(PORT_STR));

    strcpy(IP, strtok(argv[1], ":"));
    strcpy(PORT_STR, strtok(NULL, ":"));

    memset( &hints , 0 , sizeof hints );
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_flags = AI_PASSIVE;
    hints.ai_protocol = IPPROTO_UDP;

    if ( ( get = getaddrinfo( NULL , PORT_STR , &hints , &servinfo ) ) != 0) {
      fprintf( stderr , "getaddrinfo: %s\n" , gai_strerror(get) );
      return 1;
    }

    for( p = servinfo; p != NULL; p = p->ai_next ) {
      if ( ( sockfd = socket( p->ai_family , p->ai_socktype ,
                  p->ai_protocol ) ) == -1 ) {
    handle_error("socket");
    continue;
      }

      if ( bind( sockfd , p->ai_addr , p->ai_addrlen ) == -1 ) {
    close(sockfd);
    handle_error("bind");
    continue;
      }

      break;
    }

    if (p == NULL) {
      fprintf(stderr, "Not able to bind socket\n");
      return 2;
    }

    freeaddrinfo(servinfo);

    printf("\nServer is up and running: waiting to recv msg at port: %s...\n",
       PORT_STR);

    while(TRUE) {

      time_t t_ref_sec = time(NULL);
      unsigned long int Ref_epoc_sec = t_ref_sec;
      send_mem.server_reference_timestamp_sec = Ref_epoc_sec;

      unsigned long int t_ref_nanosec = ClockGetTime();
      send_mem.server_reference_timestamp_microsec = t_ref_nanosec;

      addr_len = sizeof(their_addr);

      if ((numbytes = recvfrom(sockfd, &rcv_mem , sizeof rcv_mem , 0,
                   (struct sockaddr *)&their_addr, &addr_len)) == -1) {
    handle_error("recvfrom");
    exit(1);
      }

      time_t t_rcv_sec = time(NULL);
      send_mem.server_receive_timestamp_sec = t_rcv_sec;
      send_mem.server_receive_timestamp_microsec = ClockGetTime();

      printf("Peer address: %s\n",
         inet_ntop(their_addr.ss_family,
               get_in_addr((struct sockaddr *)&their_addr),
               s, sizeof(s)));

      printf("Peer port: %i\n",p->ai_socktype);

      send_mem.server_li_vn_mode = 0b00100100;
      send_mem.server_startum = 0b00000001;
      send_mem.server_poll = 0b00000110;
      send_mem.server_precision = precision();
      send_mem.server_root_delay = 0;
      send_mem.server_root_dispersion = 0;
      memcpy( send_mem.server_reference_identifier , "LOCL" , 
          sizeof send_mem.server_reference_identifier );
      send_mem.server_originate_timestamp_sec = rcv_mem.client_originate_timestamp_sec;
      send_mem.server_originate_timestamp_microsec = rcv_mem.client_originate_timestamp_microsec;
      time_t t_send_sec = time(NULL);
      send_mem.server_transmit_timestamp_sec = t_send_sec;
      send_mem.server_transmit_timestamp_microsec = ClockGetTime();

      if ( sendto( sockfd, &send_mem , sizeof send_mem , 0 ,
           (struct sockaddr *) &their_addr , addr_len ) == -1 ) {
    handle_error("sendto");
    exit(1);
      } 

    }

    close(sockfd);

    return 0;
}

Sample of printed output when I use Server and Client.

Reference Identifier     0                       LOCL
Reference Timestamp      0.0                     1426637081.3564398733
Originate Timestamp      1426637087.3570333925   1426637087.3570333925
Receive Timestamp        1426637087.3570334078   1426637087.3570334003
Transmit Timestamp       1426637087.3570333925   1426637087.3570334046

Sample of printed output when I am probing a real NTP server (e.g. 0.se.pool.ntp.org:123).

Reference Identifier     0                       �$�
Reference Timestamp      0.0                     3879449560.3503094062
Originate Timestamp      1426637090.3573978972   1426637090.3573978972
Receive Timestamp        1426637090.3573992772   2722083800.781009125
Transmit Timestamp       1426637090.3573978972   2722083800.937312997

The expected output would be something similar to print out as I posted before.

Thank you in advance for everyones time and effort to assist me.

Update Relevant question but not close to the answer that I am looking for How to write a NTP client? [closed].


Solution

  • converting NTP timestamps to Unix timestamps (struct timeval) involves two problems to be solved.

    One is the offset between the two epochs. Unix uses an epoch located at 1/1/1970-00:00h (UTC) and NTP uses 1/1/1900-00:00h. This leads to an offset equivalent to 70 years in seconds (there are 17 leap years between the two dates so the offset is

    (70*365 + 17)*86400 = 2208988800
    

    to be substracted from NTP time to get Unix struct timeval.

    The second is that struct timeval uses 1/1000000 sec as unit of subsecond fractions and NTP uses 1/2^32 sec as its unit of fractional time. To convert from NTP to struct timeval one might divide the fractional part by 2^32 (this is easy, it's a right shift) and then multiply by 1000000. To cope with this, we have to use 64 bit arithmetic, as numbers range between 0 and 2^32 so, the best is:

    • to convert from NTP to struct timeval copy the fractional part field (the right 32 bits of a NTP timestamp) to a uint64_t variable and multiply it by 1000000, then right shift it by 32 bit positions to get the proper value. You must take into account that NTP timestamps are in network byte order, so perhaps you'll have to make some adjustments to be able to operate with the numbers.

    • To convert from struct timeval copy the tv_usec field of the unix time to a uint64_t and left shift it 32 bit positions, then divide it by 1000000 and convert to network byte order (most significative byte first)

    The following code sample illustrates this.

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <stdint.h>
    #include <getopt.h>
    
    #define OFFSET 2208988800ULL
    
    void ntp2tv(uint8_t ntp[8], struct timeval *tv)
    {
        uint64_t aux = 0;
        uint8_t *p = ntp;
        int i;
    
        /* we get the ntp in network byte order, so we must
         * convert it to host byte order. */
        for (i = 0; i < sizeof ntp / 2; i++) {
            aux <<= 8;
            aux |= *p++;
        } /* for */
    
        /* now we have in aux the NTP seconds offset */
        aux -= OFFSET;
        tv->tv_sec = aux;
    
        /* let's go with the fraction of second */
        aux = 0;
        for (; i < sizeof ntp; i++) {
            aux <<= 8;
            aux |= *p++;
        } /* for */
    
        /* now we have in aux the NTP fraction (0..2^32-1) */
        aux *= 1000000; /* multiply by 1e6 */
        aux >>= 32;     /* and divide by 2^32 */
        tv->tv_usec = aux;
    } /* ntp2tv */
    
    void tv2ntp(struct timeval *tv, uint8_t ntp[8])
    {
        uint64_t aux = 0;
        uint8_t *p = ntp + sizeof ntp;
        int i;
    
        aux = tv->tv_usec;
        aux <<= 32;
        aux /= 1000000;
    
        /* we set the ntp in network byte order */
        for (i = 0; i < sizeof ntp/2; i++) {
            *--p = aux & 0xff;
            aux >>= 8;
        } /* for */
    
        aux = tv->tv_sec;
        aux += OFFSET;
    
        /* let's go with the fraction of second */
        for (; i < sizeof ntp; i++) {
            *--p = aux & 0xff;
            aux >>= 8;
        } /* for */
    
    } /* ntp2tv */
    
    size_t print_tv(struct timeval *t)
    {
        return printf("%ld.%06ld\n", t->tv_sec, t->tv_usec);
    }
    
    size_t print_ntp(uint8_t ntp[8])
    {
        int i;
        int res = 0;
        for (i = 0; i < sizeof ntp; i++) {
            if (i == sizeof ntp / 2)
                res += printf(".");
            res += printf("%02x", ntp[i]);
        } /* for */
        res += printf("\n");
        return res;
    } /* print_ntp */
    
    
    int main(int argc, char *argv[])
    {
        struct timeval t;
        uint8_t ntp[8];
    
        gettimeofday(&t, NULL);
    
        printf("tv2ntp\n");
        tv2ntp(&t, ntp);
        printf("tv : "); print_tv(&t);
        printf("ntp: "); print_ntp(ntp);
    
        printf("ntp2tv\n");
        ntp2tv(ntp, &t);
        printf("tv : "); print_tv(&t);
        printf("ntp: "); print_ntp(ntp);
    }