Travaux Pratiques - NTP Network Time Protocol
Le Network Time Protocol (« protocole de temps réseau ») ou NTP est un protocole qui permet de synchroniser, à partir d'un réseau informatique, l'horloge locale d'ordinateurs sur une référence d'heure.
NTP est l'un des plus anciens protocoles d'Internet encore en service. Il fut conçu pour offrir une précision inférieure à la seconde dans la synchronisation des horloges.
Ce protocole est basé sur User Datagram Protocol ou UDP et utilise le port 123.
L'heure de référence fournie par NTP est UTC, et il ignore le changement de l'heure dû au fuseau horaire et le passage à l'heure d'été et d'hiver.
Il est malheureusement sujet au bug de l'an 2036. La fonction C time() et le type time_t en long au bug de l'an 2038.

Petit programme
Pour illustrer le fonctionnement d'un client NTP, on va comparer la date du système avec la date du serveur pool.ntp.org
Il devrait compiler avec le SASC 6.58 ou gcc et le netinclude de l'AmigaOS4.0

/* ntp.c */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/bsdsocket.h>
#include <proto/locale.h>

#define NTP_TIMESTAMP_DELTA 2208988800UL

typedef unsigned char uint8_t;
typedef unsigned long uint32_t;


/* Structure that defines the 48 byte NTP packet protocol. */
typedef struct
{

  uint8_t li_vn_mode;      /* Eight bits. li, vn, and mode.*/
                           /* li.   Two bits.   Leap indicator.*/
                           /* vn.   Three bits. Version number of the protocol.*/
                           /* mode. Three bits. Client will pick mode 3 for client.*/

  uint8_t stratum;         /* Eight bits. Stratum level of the local clock.*/
  uint8_t poll;            /* Eight bits. Maximum interval between successive messages.*/
  uint8_t precision;       /* Eight bits. Precision of the local clock.*/

  uint32_t rootDelay;      /* 32 bits. Total round trip delay time.*/
  uint32_t rootDispersion; /* 32 bits. Max error aloud from primary clock source.*/
  uint32_t refId;          /* 32 bits. Reference clock identifier.*/

  uint32_t refTm_s;        /* 32 bits. Reference time-stamp seconds.*/
  uint32_t refTm_f;        /* 32 bits. Reference time-stamp fraction of a second.*/

  uint32_t origTm_s;       /* 32 bits. Originate time-stamp seconds.*/
  uint32_t origTm_f;       /* 32 bits. Originate time-stamp fraction of a second.*/

  uint32_t rxTm_s;         /* 32 bits. Received time-stamp seconds.*/
  uint32_t rxTm_f;         /* 32 bits. Received time-stamp fraction of a second.*/

  uint32_t txTm_s;         /* 32 bits and the most important field the client cares about. Transmit time-stamp seconds.*/
  uint32_t txTm_f;         /* 32 bits. Transmit time-stamp fraction of a second.*/

} ntp_packet;              /* Total: 384 bits or 48 bytes. */


struct Library *SocketBase = NULL ;
struct LocaleBase *LocaleBase = NULL ;
struct Locale *locale = NULL ;

void error( char* msg )
{
  printf(msg) ;

  if (SocketBase != NULL)
  {
    CloseLibrary(SocketBase) ;
  }

  exit(0) ; // Quit the process.
}

int skread(int sock_id, char *buff, int len)
{
  int msglen     = 0 ;
  int msgtodolen = len ;
  int msgcur     = 0 ;

  do
  {
    msglen = recv(sock_id, buff+msgcur, msgtodolen, 0) ;

    if (msglen <= 0)
    {
    }
    else
    {
      msgcur     += msglen ;
      msgtodolen -= msglen ;
    }
  } while ((msglen > 0) && (msgtodolen > 0)) ;

  return msgcur ;
}

int skwrite(int sock_id, char *buff, int len)
{
  int msglen     = 0 ;
  int msgtodolen = len ;
  int msgcur     = 0 ;

  do
  {
    msglen = send(sock_id, buff+msgcur, msgtodolen, 0) ;
    if (msglen > 0)
    {
      msgcur     += msglen ;
      msgtodolen -= msglen ;
    }
  } while ((msglen > 0) && (msgtodolen > 0)) ;

  return msgcur ;
}

int main(int argc, char* argv[])
{
  int portno = 123 ; /* NTP UDP port number. */
  char* host_name = "pool.ntp.org" ; /* NTP server host-name. */
  struct sockaddr_in serv_addr ; /* Server address data structure. */
  struct hostent *server ;      /* Server data structure. */
  int sockfd, n ;
  time_t txTm, t1, t2 ;
  struct tm *loc_time ;
  ntp_packet packet ;
  char *mytz = NULL ;
  long tz = 0 ;    /* timezone shift, workaround for SAS/C time() */
  long tzl = 0 ;   /* shift GMT, set by the Locale */
  int i ;
  int bSet = 0 ;

#if defined(__SASC)
  tz = timezone ;
#endif

  for (i = 0 ; i < argc ; i++ )
  {
    if (stricmp(argv[i], "SET")==0)
    {
      bSet = 1 ;
    }
  }

  LocaleBase = (struct LocaleBase *)OpenLibrary("locale.library", 0) ;
  if (LocaleBase != NULL)
  {
    locale = OpenLocale(NULL) ;
    if (locale != NULL)
    {
      tzl = locale->loc_GMTOffset*60 ;
      CloseLocale(locale) ;
    }
    CloseLibrary((struct Library*)LocaleBase) ;
  }

  SocketBase = OpenLibrary("bsdsocket.library", 0) ;
  if (SocketBase == NULL)
  {
    printf("no TCP/IP stack, no BSDsocket.library\n") ;
    exit(0) ;
  }
  else
  {
    printf("%s %ld.%ld\n", SocketBase->lib_Node.ln_Name, SocketBase->lib_Version, SocketBase->lib_Revision ) ;
  }

  memset( &packet, 0, sizeof( ntp_packet ) ) ;

  /* Set the first byte's bits to 00,011,011 for li = 0, vn = 3, and mode = 3. The rest will */
  /* be left set to zero. */
  *((char *) &packet + 0 ) = 0x1b ; /* Represents 27 in base 10 or 00011011 in base 2. */

  /* Create a UDP socket, convert the host-name to an IP address, set the port number, */
  /* connect to the server, send the packet, and then read in the return packet. */
  sockfd = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); /* Create a UDP socket. */

  if ( sockfd < 0 )
    error( "ERROR opening socket" );

  server = gethostbyname( host_name ); /* Convert URL to IP. */
  if ( server == NULL )
  {
    printf("ERROR, no such host %s\n", host_name) ;
    error("SORRY") ;
  }

  /* Zero out the server address structure. */
  memset(&serv_addr, 0, sizeof(serv_addr)) ;

  serv_addr.sin_family = AF_INET ;

  /* Copy the server's IP address to the server address structure. */
  bcopy( ( char* )server->h_addr, ( char* ) &serv_addr.sin_addr.s_addr, server->h_length );

  /* Convert the port number integer to network big-endian style and save it to the server  */
  /* address structure. */

  serv_addr.sin_port = htons( portno );

  /* Call up the server using its IP address and port number. */

  if ( connect( sockfd, ( struct sockaddr * ) &serv_addr, sizeof( serv_addr) ) < 0 )
    error( "ERROR connecting" );

  printf("Send ntp packet to %s\n", host_name) ;
  /* Send it the NTP packet it wants. If n == -1, it failed. */

  n = skwrite( sockfd, ( char* ) &packet, sizeof( ntp_packet ) );

  if ( n < 0 )
    error( "ERROR writing to socket" ) ;

  printf("Wait packet back\n\n") ;
  /* Wait and receive the packet back from the server. If n == -1, it failed. */

  n = skread( sockfd, ( char* ) &packet, sizeof( ntp_packet ) );

  if ( n < 0 )
    error( "ERROR reading from socket" );

  /* These two fields contain the time-stamp seconds as the packet left the NTP server. */
  /* The number of seconds correspond to the seconds passed since 1900. */
  /* ntohl() converts the bit/byte order from the network's to host's "endianness". */

  packet.txTm_s = ntohl( packet.txTm_s ); /* Time-stamp seconds. */
  packet.txTm_f = ntohl( packet.txTm_f ); /* Time-stamp fraction of a second. */

  /* Extract the 32 bits that represent the time-stamp seconds (since NTP epoch) from when the packet */
  /* left the server.                                                                                 */
  /* Subtract 70 years worth of seconds from the seconds since 1900.                                  */
  /* This leaves the seconds since the UNIX epoch of 1970.                                            */
  /* (1900)------------------(1970)**************************************(Time Packet Left the Server)*/

  txTm = ( time_t ) ( packet.txTm_s - NTP_TIMESTAMP_DELTA );
  txTm += tz ;
  t1 = txTm ;
  /* Print the time we got from the server, accounting for local timezone and conversion from UTC time. */
  loc_time = localtime(&txTm) ;
  printf( "Server Time: %s", asctime(loc_time)) ;

  time(&txTm) ;
  t2 = txTm ;
  loc_time = localtime(&txTm) ;
  printf( "Actual Time: %s", asctime(loc_time)) ;
  printf ("Diff %ld s\n", t2-t1+tzl) ;

  if (bSet)
  {
    char buff[40] ;
    time_t t ;
    t = t1 - tzl ;
    loc_time = localtime(&t) ;

    sprintf(buff, "date %02ld-%02ld-%04ld %02ld:%02ld:%02ld", loc_time->tm_mday, 1+loc_time->tm_mon, 1900+loc_time->tm_year , loc_time->tm_hour, loc_time->tm_min, loc_time->tm_sec) ;
    printf("%s\n", buff) ;

    Execute(buff, (BPTR)NULL, Output()) ;
  }

  CloseLibrary(SocketBase) ;

  return 0;
}

Makefile SAS/C
Dans la user-startup ou la startup-sequence il devrait y avoir la définition des includes pour le SAS C à l'aide de la commande assign.
assign INCLUDE: dev:sc/include
ajouter
assign NETINCLUDE: dev:sdks/sdk_53.30/netinclude
Ce fameux netinclude se trouve dans SDK_Install\base.lha\Include\ du sdk_53.30.lha

Après avoir recopié le fichier source ntp.c dans un répertoire (nommé ntp aussi, il faut savoir ranger ses programmes :) ), créé les répertoires obj et bin puis édité un fichier makefile comme suit:

SRC=
OBJ=obj
BIN=bin

target: $(BIN)/ntp

$(OBJ)/ntp.o: $(SRC)/ntp.c
  sc INCLUDEDIR=NETINCLUDE: OBJNAME $(OBJ)/ntp.o $(SRC)/ntp.c

$(BIN)/ntp: $(OBJ)/ntp.o
  slink LIB:c.o,$(OBJ)/ntp.o TO $(BIN)/ntp LIB LIB:sc.lib NOICONS

De cette manière, le pré-compilateur ira chercher dans NETINCLUDE: puis dans INCLUDE: s'il ne trouve pas son include.
Et puis on ne perturbe pas ses includes préférés, puisqu'ils sont dans des répertoires distincts.
La première ligne signifie que l'on veut construire le programme ntp.
La commande sc compile le code source ntp.c avec les options qui vont bien.
La commande slink lie le petit segment de démarrage c.o avec les fonctions C standard de sc.lib pour générer le programme ntp.

Makefile gcc

SRC=..
OBJ=obj
BIN=bin

target: $(BIN)/ntp

$(OBJ)/ntp.o: $(SRC)/ntp.c
	gcc -noixemul -c -INETINCLUDE: -o $(OBJ)/ntp.o $(SRC)/ntp.c

$(BIN)/ntp: $(OBJ)/ntp.o
	gcc -noixemul -o $(BIN)/ntp $(OBJ)/ntp.o
	strip $(BIN)/ntp
Même principe que pour le makefile SAS/C mais avec gcc.

Essai
Voilà ce que donne le programme ntp.
On envoie une demande au serveur pool.ntp.org par le port 113, le serveur répond, on ajuste le temps et on compare avec la date de l'ordinateur.
Il a 4 secondes de retard.

bsdsocket.library 4.1
Send ntp packet to pool.ntp.org
Wait packet back

Server Time: Sat Jan 29 21:15:47 2022
Actual Time: Sat Jan 29 22:15:43 2022
Diff -4 s
option set, pour mettre à l'heure l'Amiga, avec l'heure réseau.
ntp set
bsdsocket.library 4.1
Send ntp packet to pool.ntp.org
Wait packet back

Server Time: Sat Jan 29 21:17:07 2022
Actual Time: Sat Jan 29 22:17:03 2022
Diff -4 s
date 29-01-2022 22:17:07

Conclusion
Voilà un petit utilitaire très utile pour des ordinateurs qui tournent dans des machines virtuelles, car l'horloge interne a tendance à dériver après plusieurs heures d'activité.