sntp_client_misc.c
Go to the documentation of this file.
1 /**
2  * @file sntp_client.c
3  * @brief Helper functions for SNTP client
4  *
5  * @section License
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  *
9  * Copyright (C) 2010-2019 Oryx Embedded SARL. All rights reserved.
10  *
11  * This file is part of CycloneTCP Open.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26  *
27  * @section Description
28  *
29  * The Simple Network Time Protocol is used to synchronize computer clocks
30  * in the Internet. Refer to RFC 4330 for more details
31  *
32  * @author Oryx Embedded SARL (www.oryx-embedded.com)
33  * @version 1.9.2
34  **/
35 
36 //Switch to the appropriate trace level
37 #define TRACE_LEVEL SNTP_TRACE_LEVEL
38 
39 //Dependencies
40 #include <ctype.h>
41 #include "core/net.h"
42 #include "sntp/sntp_client.h"
43 #include "sntp/sntp_client_misc.h"
44 #include "debug.h"
45 
46 //Check TCP/IP stack configuration
47 #if (SNTP_CLIENT_SUPPORT == ENABLED)
48 
49 
50 /**
51  * @brief Open UDP connection
52  * @param[in] context Pointer to the SNTP client context
53  * @return Error code
54  **/
55 
57 {
58  error_t error;
59 
60  //Open a UDP socket
62 
63  //Valid socket?
64  if(context->socket != NULL)
65  {
66  //Associate the socket with the relevant interface
67  error = socketBindToInterface(context->socket, context->interface);
68  }
69  else
70  {
71  //Report an error
72  error = ERROR_OPEN_FAILED;
73  }
74 
75  //Return status code
76  return error;
77 }
78 
79 
80 /**
81  * @brief Close UDP connection
82  * @param[in] context Pointer to the SNTP client context
83  * @return Error code
84  **/
85 
87 {
88  //Valid socket?
89  if(context->socket != NULL)
90  {
91  //Close UDP socket
92  socketClose(context->socket);
93  context->socket = NULL;
94  }
95 }
96 
97 
98 /**
99  * @brief Send request to the NTP server
100  * @param[in] context Pointer to the SNTP client context
101  * @return Error code
102  **/
103 
105 {
106  error_t error;
107  NtpHeader *header;
108 
109  //Point to the buffer where to format the NTP message
110  header = (NtpHeader *) context->message;
111 
112  //The client initializes the NTP message header. For this purpose, all
113  //the NTP header fields are set to 0, except the Mode, VN, and optional
114  //Transmit Timestamp fields
115  memset(header, 0, sizeof(NtpHeader));
116 
117  //Format NTP request
118  header->vn = NTP_VERSION_3;
119  header->mode = NTP_MODE_CLIENT;
120 
121  //Time at which the NTP request was sent
123 
124  //The Transmit Timestamp allows a simple calculation to determine the
125  //propagation delay between the server and client and to align the system
126  //clock generally within a few tens of milliseconds relative to the server
127  header->transmitTimestamp.seconds = 0;
128  header->transmitTimestamp.fraction = htonl(context->retransmitStartTime);
129 
130  //Length of the NTP request
131  context->messageLen = sizeof(NtpHeader);
132 
133  //Debug message
134  TRACE_INFO("Sending NTP request message (%" PRIuSIZE " bytes)...\r\n",
135  context->messageLen);
136 
137  //Dump the contents of the NTP message for debugging purpose
138  sntpClientDumpMessage(context->message, context->messageLen);
139 
140  //Send the request to the designated NTP server
141  error = socketSendTo(context->socket, &context->serverIpAddr,
142  context->serverPort, context->message, context->messageLen,
143  NULL, 0);
144 
145  //Check status code
146  if(!error)
147  {
148  //Wait for server's response
150  }
151 
152  //Return status code
153  return error;
154 }
155 
156 
157 /**
158  * @brief Wait for NTP server's response
159  * @param[in] context Pointer to the SNTP client context
160  * @return Error code
161  **/
162 
164 {
165  error_t error;
166  systime_t t1;
167  systime_t t2;
168  systime_t time;
169  IpAddr ipAddr;
170  uint16_t port;
171 
172  //Get current time
173  time = osGetSystemTime();
174 
175  //Compute request timeout
176  if(timeCompare(context->startTime + context->timeout, time) > 0)
177  t1 = context->startTime + context->timeout - time;
178  else
179  t1 = 0;
180 
181  //Compute retransmission timeout
182  if(timeCompare(context->retransmitStartTime + context->retransmitTimeout, time) > 0)
183  t2 = context->retransmitStartTime + context->retransmitTimeout - time;
184  else
185  t2 = 0;
186 
187  //Adjust receive timeout
188  error = socketSetTimeout(context->socket, MIN(t1, t2));
189 
190  //Check status code
191  if(!error)
192  {
193  //Wait for server's response
194  error = socketReceiveFrom(context->socket, &ipAddr, &port,
195  context->message, NTP_MAX_MSG_SIZE, &context->messageLen, 0);
196  }
197 
198  //Any datagram received?
199  if(error == NO_ERROR)
200  {
201  //Check NTP response
202  error = sntpClientCheckResponse(context, &ipAddr, port,
203  context->message, context->messageLen);
204 
205  //Check status code
206  if(!error)
207  {
208  //A valid NTP response has been received
210  }
211  else
212  {
213  //Silently discard invalid NTP packets
214  error = sntpClientCheckTimeout(context);
215  }
216  }
217  else if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
218  {
219  //Check whether the timeout has elapsed
220  error = sntpClientCheckTimeout(context);
221  }
222  else
223  {
224  //A communication error has occured
225  }
226 
227  //Return status code
228  return error;
229 }
230 
231 
232 /**
233  * @brief Check whether the NTP response is valid
234  * @param[in] context Pointer to the SNTP client context
235  * @param[in] ipAddr Remote IP address
236  * @param[in] port Remote port number
237  * @param[in] message Pointer to the NTP message
238  * @param[in] length Length of the NTP message, in bytes
239  * @return Error code
240  **/
241 
243  const IpAddr *ipAddr, uint16_t port, const uint8_t *message,
244  size_t length)
245 {
246  NtpHeader *header;
247 
248  //Ensure the NTP packet is valid
249  if(length < sizeof(NtpHeader))
250  return ERROR_INVALID_MESSAGE;
251 
252  //Point to the NTP response
253  header = (NtpHeader *) context->message;
254 
255  //Debug message
256  TRACE_INFO("NTP response message received (%" PRIuSIZE " bytes)...\r\n",
257  length);
258 
259  //Dump NTP message
261 
262  //The server reply should be discarded if the VN field is 0
263  if(header->vn == 0)
264  return ERROR_INVALID_MESSAGE;
265 
266  //The server reply should be discarded if the Transmit Timestamp fields is 0
267  if(header->transmitTimestamp.seconds == 0 &&
268  header->transmitTimestamp.fraction == 0)
269  {
270  return ERROR_INVALID_MESSAGE;
271  }
272 
273  //The server reply should be discarded if the Mode field is not 4 (unicast)
274  //or 5 (broadcast)
275  if(header->mode != NTP_MODE_SERVER && header->mode != NTP_MODE_BROADCAST)
276  return ERROR_INVALID_MESSAGE;
277 
278  //The Originate Timestamp in the server reply should match the Transmit
279  //Timestamp used in the client request
280  if(header->originateTimestamp.seconds != 0)
281  return ERROR_INVALID_MESSAGE;
282  if(header->originateTimestamp.fraction != htonl(context->retransmitStartTime))
283  return ERROR_INVALID_MESSAGE;
284 
285  //The NTP response message is acceptable
286  return NO_ERROR;
287 }
288 
289 
290 /**
291  * @brief Parse NTP server's response
292  * @param[in] context Pointer to the SNTP client context
293  * @param[out] timestamp Pointer to the NTP timestamp
294  * @return Error code
295  **/
296 
298  NtpTimestamp *timestamp)
299 {
300  NtpHeader *header;
301 
302  //Ensure the NTP packet is valid
303  if(context->messageLen < sizeof(NtpHeader))
304  return ERROR_INVALID_LENGTH;
305 
306  //Point to the NTP response
307  header = (NtpHeader *) context->message;
308 
309  //Clear kiss code
310  context->kissCode = 0;
311 
312  //Kiss-of-Death packet received?
313  if(header->stratum == 0)
314  {
315  //The kiss code is encoded in four-character ASCII strings left
316  //justified and zero filled
317  context->kissCode = htonl(header->referenceId);
318 
319  //An SNTP client should stop sending to a particular server if that
320  //server returns a reply with a Stratum field of 0
321  return ERROR_REQUEST_REJECTED;
322  }
323 
324  //Extract NTP timestamp from server's response
325  timestamp->seconds = ntohl(header->transmitTimestamp.seconds);
326  timestamp->fraction = ntohl(header->transmitTimestamp.fraction);
327 
328  //Successful processing
329  return NO_ERROR;
330 }
331 
332 
333 /**
334  * @brief Determine whether a timeout error has occurred
335  * @param[in] context Pointer to the SNTP client context
336  * @return Error code
337  **/
338 
340 {
341  error_t error;
342  systime_t time;
343 
344  //Get current time
345  time = osGetSystemTime();
346 
347  //Check whether the timeout has elapsed
348  if(timeCompare(time, context->startTime + context->timeout) >= 0)
349  {
350  //Report a timeout error
351  error = ERROR_TIMEOUT;
352  }
353  else if(timeCompare(time, context->retransmitStartTime + context->retransmitTimeout) >= 0)
354  {
355  //The timeout value is doubled for each subsequent retransmission
356  context->retransmitTimeout = MIN(context->retransmitTimeout * 2,
358 
359  //Retransmit NTP request
360  context->state = SNTP_CLIENT_STATE_SENDING;
361 
362  //Continue processing
363  error = NO_ERROR;
364  }
365  else
366  {
367 #if (NET_RTOS_SUPPORT == ENABLED)
368  //Report a timeout error
369  error = ERROR_TIMEOUT;
370 #else
371  //The operation would block
372  error = ERROR_WOULD_BLOCK;
373 #endif
374  }
375 
376  //Return status code
377  return error;
378 }
379 
380 
381 /**
382  * @brief Dump NTP message for debugging purpose
383  * @param[in] message Pointer to the NTP message
384  * @param[in] length Length of the NTP message
385  **/
386 
387 void sntpClientDumpMessage(const uint8_t *message, size_t length)
388 {
389 #if (SNTP_TRACE_LEVEL >= TRACE_LEVEL_DEBUG)
390  uint32_t kissCode;
391  const NtpHeader *header;
392  const NtpAuthenticator *auth;
393 
394  //Valid NTP packet?
395  if(length >= sizeof(NtpHeader))
396  {
397  //Point to the NTP packet header
398  header = (NtpHeader *) message;
399 
400  //Dump NTP message
401  TRACE_DEBUG(" Mode = %" PRIu8 "\r\n", header->mode);
402  TRACE_DEBUG(" Version = %" PRIu8 "\r\n", header->vn);
403  TRACE_DEBUG(" Leap indicator = %" PRIu8 "\r\n", header->li);
404  TRACE_DEBUG(" Stratum = %" PRIu8 "\r\n", header->stratum);
405  TRACE_DEBUG(" Poll = %" PRIu8 "\r\n", header->poll);
406  TRACE_DEBUG(" Precision = %" PRId8 "\r\n", header->precision);
407  TRACE_DEBUG(" Root Delay = %" PRIu32 "\r\n", ntohl(header->rootDelay));
408  TRACE_DEBUG(" Root Dispersion = %" PRIu32 "\r\n", ntohl(header->rootDispersion));
409 
410  //Retrieve kiss code
411  kissCode = htonl(header->referenceId);
412 
413  //Valid kiss code?
414  if(isalnum((kissCode >> 24) & 0xFF) &&
415  isalnum((kissCode >> 16) & 0xFF) &&
416  isalnum((kissCode >> 8) & 0xFF) &&
417  isalnum(kissCode & 0xFF))
418  {
419  //Dump kiss code
420  TRACE_DEBUG(" Kiss Code = '%c%c%c%c'\r\n", (kissCode >> 24) & 0xFF,
421  (kissCode >> 16) & 0xFF, (kissCode >> 8) & 0xFF, kissCode & 0xFF);
422  }
423  else
424  {
425  //Dump reference identifier
426  TRACE_DEBUG(" Reference Identifier = %" PRIu32 "\r\n",
427  header->referenceId);
428  }
429 
430  //Dump reference timestamp
431  TRACE_DEBUG(" ReferenceTimestamp\r\n");
432  sntpClientDumpTimestamp(&header->referenceTimestamp);
433 
434  //Dump originate timestamp
435  TRACE_DEBUG(" Originate Timestamp\r\n");
436  sntpClientDumpTimestamp(&header->originateTimestamp);
437 
438  //Dump receive timestamp
439  TRACE_DEBUG(" Receive Timestamp\r\n");
440  sntpClientDumpTimestamp(&header->receiveTimestamp);
441 
442  //Dump transmit timestamp
443  TRACE_DEBUG(" Transmit Timestamp\r\n");
444  sntpClientDumpTimestamp(&header->transmitTimestamp);
445  }
446 
447  //When the NTP authentication scheme is implemented, the Key Identifier
448  //and Message Digest fields contain the message authentication code (MAC)
449  //information
450  if(length >= (sizeof(NtpHeader) + sizeof(NtpAuthenticator)))
451  {
452  //The Authenticator field is optional
453  auth = (NtpAuthenticator *) (message + sizeof(NtpHeader));
454 
455  //Dump key identifier
456  TRACE_DEBUG(" Key Identifier = %" PRIu32 "\r\n", ntohl(auth->keyId));
457 
458  //Dump message digest
459  TRACE_DEBUG(" Message Digest\r\n");
460  TRACE_DEBUG_ARRAY(" ", auth->messageDigest, 16);
461  }
462 #endif
463 }
464 
465 
466 /**
467  * @brief Dump NTP timestamp
468  * @param[in] timestamp Pointer to the NTP timestamp
469  **/
470 
471 void sntpClientDumpTimestamp(const NtpTimestamp *timestamp)
472 {
473  //Dump seconds
474  TRACE_DEBUG(" Seconds = %" PRIu32 "\r\n", ntohl(timestamp->seconds));
475  //Dump fraction field
476  TRACE_DEBUG(" Fraction = %" PRIu32 "\r\n", ntohl(timestamp->fraction));
477 }
478 
479 #endif
IpAddr serverIpAddr
NTP server address.
Definition: sntp_client.h:93
uint32_t systime_t
Definition: compiler_port.h:46
SNTP client context.
Definition: sntp_client.h:89
NetInterface * interface
Underlying network interface.
Definition: sntp_client.h:92
#define timeCompare(t1, t2)
Definition: os_port.h:42
error_t sntpClientCheckResponse(SntpClientContext *context, const IpAddr *ipAddr, uint16_t port, const uint8_t *message, size_t length)
Check whether the NTP response is valid.
systime_t osGetSystemTime(void)
Retrieve system time.
uint32_t time
TCP/IP stack core.
Debugging facilities.
void sntpClientDumpTimestamp(const NtpTimestamp *timestamp)
Dump NTP timestamp.
SNTP client (Simple Network Time Protocol)
uint8_t message[]
Definition: chap.h:152
systime_t retransmitTimeout
Retransmission timeout.
Definition: sntp_client.h:99
IP network address.
Definition: ip.h:59
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:799
error_t sntpClientParseResponse(SntpClientContext *context, NtpTimestamp *timestamp)
Parse NTP server&#39;s response.
uint8_t message[NTP_MAX_MSG_SIZE]
Buffer that holds the NTP request/response.
Definition: sntp_client.h:100
#define TRACE_DEBUG_ARRAY(p, a, n)
Definition: debug.h:107
error_t sntpClientCheckTimeout(SntpClientContext *context)
Determine whether a timeout error has occurred.
uint16_t serverPort
NTP server port.
Definition: sntp_client.h:94
error_t socketReceiveFrom(Socket *socket, IpAddr *srcIpAddr, uint16_t *srcPort, void *data, size_t size, size_t *received, uint_t flags)
Receive a datagram from a connectionless socket.
Definition: socket.c:606
SntpClientState state
SNTP client state.
Definition: sntp_client.h:91
systime_t timeout
Timeout value.
Definition: sntp_client.h:95
uint8_t ipAddr[4]
Definition: mib_common.h:187
uint32_t kissCode
Kiss code.
Definition: sntp_client.h:102
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:218
#define ntohl(value)
Definition: cpu_endian.h:399
error_t sntpClientSendRequest(SntpClientContext *context)
Send request to the NTP server.
#define htonl(value)
Definition: cpu_endian.h:393
error_t sntpClientOpenConnection(SntpClientContext *context)
Open UDP connection.
void sntpClientCloseConnection(SntpClientContext *context)
Close UDP connection.
#define NTP_MAX_MSG_SIZE
Definition: ntp_common.h:40
#define MIN(a, b)
Definition: os_port.h:62
#define SNTP_CLIENT_MAX_RETRANSMIT_TIMEOUT
Definition: sntp_client.h:61
#define TRACE_INFO(...)
Definition: debug.h:94
size_t messageLen
Length of the NTP message, in bytes.
Definition: sntp_client.h:101
Success.
Definition: error.h:44
error_t
Error codes.
Definition: error.h:42
Socket * socketOpen(uint_t type, uint_t protocol)
Create a socket (UDP or TCP)
Definition: socket.c:94
#define PRIuSIZE
Definition: compiler_port.h:78
Socket * socket
Underlying socket.
Definition: sntp_client.h:96
uint32_t t2
uint16_t port
Definition: dns_common.h:223
__start_packed struct @298 NtpAuthenticator
NTP authenticator.
uint32_t t1
__start_packed struct @297 NtpHeader
NTP packet header.
systime_t retransmitStartTime
Time at which the last request was sent.
Definition: sntp_client.h:98
uint8_t length
Definition: dtls_misc.h:142
error_t sntpClientReceiveResponse(SntpClientContext *context)
Wait for NTP server&#39;s response.
error_t socketSendTo(Socket *socket, const IpAddr *destIpAddr, uint16_t destPort, const void *data, size_t length, size_t *written, uint_t flags)
Send a datagram to a specific destination.
Definition: socket.c:513
__start_packed struct @296 NtpTimestamp
NTP timestamp representation.
void sntpClientDumpMessage(const uint8_t *message, size_t length)
Dump NTP message for debugging purpose.
systime_t startTime
Request start time.
Definition: sntp_client.h:97
error_t socketBindToInterface(Socket *socket, NetInterface *interface)
Bind a socket to a particular network interface.
Definition: socket.c:311
#define TRACE_DEBUG(...)
Definition: debug.h:106