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:

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