A simple NTP server and client implementation
一个简易的NTP服务端与客户端的实现
2010-11-22

Introduction

Recently I have read the RFCs of NTP protocols. Just to test my understanding of the protocol, a simple NTP server was implemented.

It has passed the test using the system clock synchronization of Windows XP. For testing and debugging, make sure to turn off the clock synchronization with the host system, when the client operating system is running in a virtual machine.

Code

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <stdint.h>
  5 #include <unistd.h>
  6 #include <signal.h>
  7 #include <sys/time.h> /* gettimeofday() */
  8 #include <sys/types.h>
  9 #include <sys/wait.h>
 10 #include <sys/socket.h>
 11 #include <netdb.h>
 12 #include <netinet/in.h>
 13 #include <arpa/inet.h>
 14 
 15 #include <time.h> /* for time() and ctime() */
 16 
 17 #define UTC_NTP 2208988800U /* 1970 - 1900 */
 18 
 19 /* get Timestamp for NTP in LOCAL ENDIAN */
 20 void gettime64(uint32_t ts[])
 21 {
 22     struct timeval tv;
 23     gettimeofday(&tv, NULL);
 24 
 25     ts[0] = tv.tv_sec + UTC_NTP;
 26     ts[1] = (4294*(tv.tv_usec)) + ((1981*(tv.tv_usec))>>11);
 27 }
 28 
 29 
 30 int die(const char *msg)
 31 {
 32     if (msg) {
 33         fputs(msg, stderr);
 34     }
 35     exit(-1);
 36 }
 37 
 38 
 39 void log_request_arrive(uint32_t *ntp_time)
 40 {
 41     time_t t;
 42 
 43     if (ntp_time) {
 44         t = *ntp_time - UTC_NTP;
 45     } else {
 46         t = time(NULL);
 47     }
 48     printf("A request comes at: %s", ctime(&t));
 49 }
 50 
 51 
 52 void log_ntp_event(char *msg)
 53 {
 54     puts(msg);
 55 }
 56 
 57 
 58 int ntp_reply(
 59         int socket_fd,
 60         struct sockaddr *saddr_p,
 61         socklen_t saddrlen,
 62         unsigned char recv_buf[],
 63         uint32_t recv_time[])
 64 {
 65     /* Assume that recv_time is in local endian ! */
 66     unsigned char send_buf[48];
 67     uint32_t *u32p;
 68 
 69     /* do not use 0xC7 because the LI can be `unsynchronized` */
 70     if ((recv_buf[0] & 0x07/*0xC7*/) != 0x3) {
 71         /* LI VN Mode stimmt nicht */
 72         log_ntp_event("Invalid request: found error at the first byte");
 73         return 1;
 74     }
 75 
 76     /* füllt LI VN Mode aus
 77        LI   = 0
 78        VN   = Version Nummer aus dem Client
 79        Mode = 4
 80        */
 81     send_buf[0] = (recv_buf[0] & 0x38) + 4;
 82 
 83     /* Stratum = 1 (primary reference)
 84        Reference ID = 'LOCL"
 85        (falscher) Bezug auf der lokalen Uhr.
 86        */
 87     /* Stratum */
 88     send_buf[1] = 0x01;
 89     /* Reference ID = "LOCL" */
 90     *(uint32_t*)&send_buf[12] = htonl(0x4C4F434C);
 91 
 92     /* Copy Poll */
 93     send_buf[2] = recv_buf[2];
 94 
 95     /* Precision in Microsecond ( from API gettimeofday() ) */
 96     send_buf[3] = (signed char)(-6);  /* 2^(-6) sec */
 97 
 98     /* danach sind alle Werte DWORD aligned  */
 99     u32p = (uint32_t *)&send_buf[4];
100     /* zur Vereinfachung , Root Delay = 0, Root Dispersion = 0 */
101     *u32p++ = 0;
102     *u32p++ = 0;
103 
104     /* Reference ID ist vorher eingetragen */
105     u32p++;
106 
107     /* falscher Reference TimeStamp,
108      * wird immer vor eine minute synchronisiert,
109      * damit die Überprüfung in Client zu belügen */
110     gettime64(u32p);
111     *u32p = htonl(*u32p - 60);   /* -1 Min.*/
112     u32p++;
113     *u32p = htonl(*u32p);   /* -1 Min.*/
114     u32p++;
115 
116     /* Originate Time = Transmit Time @ Client */
117     *u32p++ = *(uint32_t *)&recv_buf[40];
118     *u32p++ = *(uint32_t *)&recv_buf[44];
119 
120     /* Receive Time @ Server */
121     *u32p++ = htonl(recv_time[0]);
122     *u32p++ = htonl(recv_time[1]);
123 
124     /* zum Schluss: Transmit Time*/
125     gettime64(u32p);
126     *u32p = htonl(*u32p);   /* -1 Min.*/
127     u32p++;
128     *u32p = htonl(*u32p);   /* -1 Min.*/
129 
130     if (
131             sendto(
132                 socket_fd,
133                 send_buf,
134                 sizeof(send_buf), 0,
135                 saddr_p, saddrlen
136             ) < 48
137        ) {
138         perror("sendto error");
139         return 1;
140     }
141 
142     return 0;
143 }
144 
145 
146 void request_process_loop(int fd)
147 {
148     struct sockaddr src_addr;
149     socklen_t src_addrlen = sizeof(src_addr);
150     unsigned char buf[48];
151     uint32_t recv_time[2];
152     pid_t pid;
153 
154     while (1) {
155         while (recvfrom(fd, buf,
156                     48, 0,
157                     &src_addr,
158                     &src_addrlen)
159                 < 48 );  /* invalid request */
160 
161         gettime64(recv_time);
162         /* recv_time in local endian */
163         log_request_arrive(recv_time);
164 
165         pid = fork();
166         if (pid == 0) {
167             /* Child */
168             ntp_reply(fd, &src_addr , src_addrlen, buf, recv_time);
169             exit(0);
170         } else if (pid == -1) {
171             perror("fork() error");
172             die(NULL);
173         }
174         /* return to parent */
175     }
176 }
177 
178 
179 void ntp_server()
180 {
181     int s;
182     struct sockaddr_in sinaddr;
183 
184     s = socket(AF_INET, SOCK_DGRAM, 0);
185     if (s == -1) {
186         perror("Can not create socket.");
187         die(NULL);
188     }
189 
190     memset(&sinaddr, 0, sizeof(sinaddr));
191     sinaddr.sin_family = AF_INET;
192     sinaddr.sin_port = htons(123);
193     sinaddr.sin_addr.s_addr = INADDR_ANY;
194 
195     if (0 != bind(s, (struct sockaddr *)&sinaddr, sizeof(sinaddr))) {
196         perror("Bind error");
197         die(NULL);
198     }
199 
200     log_ntp_event(
201             "\n"
202             "========================================\n"
203             "= Server started, waiting for requests =\n"
204             "========================================\n"
205             );
206 
207     request_process_loop(s);
208     close(s);
209 }
210 
211 
212 void wait_wrapper()
213 {
214     int s;
215     wait(&s);
216 }
217 
218 
219 int main(int argc, char *argv[], char **env)
220 {
221     signal(SIGCHLD,wait_wrapper);
222     ntp_server();
223     /* nicht erreichbar: */
224     return 0;
225 }

A simple client for debug purpose is also available: client.c.gz.