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-2024 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 2.4.0
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  **/
84 
86 {
87  //Valid socket?
88  if(context->socket != NULL)
89  {
90  //Close UDP socket
91  socketClose(context->socket);
92  context->socket = NULL;
93  }
94 }
95 
96 
97 /**
98  * @brief Send request to the NTP server
99  * @param[in] context Pointer to the SNTP client context
100  * @return Error code
101  **/
102 
104 {
105  error_t error;
106  NtpHeader *header;
107 
108  //Point to the buffer where to format the NTP message
109  header = (NtpHeader *) context->message;
110 
111  //The client initializes the NTP message header. For this purpose, all
112  //the NTP header fields are set to 0, except the Mode, VN, and optional
113  //Transmit Timestamp fields
114  osMemset(header, 0, sizeof(NtpHeader));
115 
116  //Format NTP request
117  header->vn = NTP_VERSION_3;
118  header->mode = NTP_MODE_CLIENT;
119 
120  //Time at which the NTP request was sent
122 
123  //The Transmit Timestamp allows a simple calculation to determine the
124  //propagation delay between the server and client and to align the system
125  //clock generally within a few tens of milliseconds relative to the server
126  header->transmitTimestamp.seconds = 0;
127  header->transmitTimestamp.fraction = htonl(context->retransmitStartTime);
128 
129  //Length of the NTP request
130  context->messageLen = sizeof(NtpHeader);
131 
132  //Debug message
133  TRACE_INFO("Sending NTP request message (%" PRIuSIZE " bytes)...\r\n",
134  context->messageLen);
135 
136  //Dump the contents of the NTP message for debugging purpose
137  sntpClientDumpMessage(context->message, context->messageLen);
138 
139  //Send the request to the designated NTP server
140  error = socketSendTo(context->socket, &context->serverIpAddr,
141  context->serverPort, context->message, context->messageLen,
142  NULL, 0);
143 
144  //Check status code
145  if(!error)
146  {
147  //Wait for server's response
149  }
150 
151  //Return status code
152  return error;
153 }
154 
155 
156 /**
157  * @brief Wait for NTP server's response
158  * @param[in] context Pointer to the SNTP client context
159  * @return Error code
160  **/
161 
163 {
164  error_t error;
165  systime_t t1;
166  systime_t t2;
167  systime_t time;
168  IpAddr ipAddr;
169  uint16_t port;
170 
171  //Get current time
172  time = osGetSystemTime();
173 
174  //Compute request timeout
175  if(timeCompare(context->startTime + context->timeout, time) > 0)
176  {
177  t1 = context->startTime + context->timeout - time;
178  }
179  else
180  {
181  t1 = 0;
182  }
183 
184  //Compute retransmission timeout
185  if(timeCompare(context->retransmitStartTime + context->retransmitTimeout, time) > 0)
186  {
187  t2 = context->retransmitStartTime + context->retransmitTimeout - time;
188  }
189  else
190  {
191  t2 = 0;
192  }
193 
194  //Adjust receive timeout
195  error = socketSetTimeout(context->socket, MIN(t1, t2));
196 
197  //Check status code
198  if(!error)
199  {
200  //Wait for server's response
201  error = socketReceiveFrom(context->socket, &ipAddr, &port,
202  context->message, NTP_MAX_MSG_SIZE, &context->messageLen, 0);
203  }
204 
205  //Any datagram received?
206  if(error == NO_ERROR)
207  {
208  //Check NTP response
209  error = sntpClientCheckResponse(context, &ipAddr, port,
210  context->message, context->messageLen);
211 
212  //Check status code
213  if(!error)
214  {
215  //A valid NTP response has been received
217  }
218  else
219  {
220  //Silently discard invalid NTP packets
221  error = sntpClientCheckTimeout(context);
222  }
223  }
224  else if(error == ERROR_WOULD_BLOCK || error == ERROR_TIMEOUT)
225  {
226  //Check whether the timeout has elapsed
227  error = sntpClientCheckTimeout(context);
228  }
229  else
230  {
231  //A communication error has occurred
232  }
233 
234  //Return status code
235  return error;
236 }
237 
238 
239 /**
240  * @brief Check whether the NTP response is valid
241  * @param[in] context Pointer to the SNTP client context
242  * @param[in] ipAddr Remote IP address
243  * @param[in] port Remote port number
244  * @param[in] message Pointer to the NTP message
245  * @param[in] length Length of the NTP message, in bytes
246  * @return Error code
247  **/
248 
250  const IpAddr *ipAddr, uint16_t port, const uint8_t *message,
251  size_t length)
252 {
253  NtpHeader *header;
254 
255  //Ensure the NTP packet is valid
256  if(length < sizeof(NtpHeader))
257  return ERROR_INVALID_MESSAGE;
258 
259  //Point to the NTP response
260  header = (NtpHeader *) context->message;
261 
262  //Debug message
263  TRACE_INFO("NTP response message received (%" PRIuSIZE " bytes)...\r\n",
264  length);
265 
266  //Dump NTP message
268 
269  //The server reply should be discarded if the VN field is 0
270  if(header->vn == 0)
271  return ERROR_INVALID_MESSAGE;
272 
273  //The server reply should be discarded if the Transmit Timestamp fields is 0
274  if(header->transmitTimestamp.seconds == 0 &&
275  header->transmitTimestamp.fraction == 0)
276  {
277  return ERROR_INVALID_MESSAGE;
278  }
279 
280  //The server reply should be discarded if the Mode field is not 4 (unicast)
281  //or 5 (broadcast)
282  if(header->mode != NTP_MODE_SERVER && header->mode != NTP_MODE_BROADCAST)
283  return ERROR_INVALID_MESSAGE;
284 
285  //The Originate Timestamp in the server reply should match the Transmit
286  //Timestamp used in the client request
287  if(header->originateTimestamp.seconds != 0)
288  return ERROR_INVALID_MESSAGE;
289  if(header->originateTimestamp.fraction != htonl(context->retransmitStartTime))
290  return ERROR_INVALID_MESSAGE;
291 
292  //The NTP response message is acceptable
293  return NO_ERROR;
294 }
295 
296 
297 /**
298  * @brief Parse NTP server's response
299  * @param[in] context Pointer to the SNTP client context
300  * @param[out] timestamp Pointer to the NTP timestamp
301  * @return Error code
302  **/
303 
305  NtpTimestamp *timestamp)
306 {
307  NtpHeader *header;
308 
309  //Ensure the NTP packet is valid
310  if(context->messageLen < sizeof(NtpHeader))
311  return ERROR_INVALID_LENGTH;
312 
313  //Point to the NTP response
314  header = (NtpHeader *) context->message;
315 
316  //Clear kiss code
317  context->kissCode = 0;
318 
319  //Kiss-of-Death packet received?
320  if(header->stratum == 0)
321  {
322  //The kiss code is encoded in four-character ASCII strings left
323  //justified and zero filled
324  context->kissCode = htonl(header->referenceId);
325 
326  //An SNTP client should stop sending to a particular server if that
327  //server returns a reply with a Stratum field of 0
328  return ERROR_REQUEST_REJECTED;
329  }
330 
331  //Extract NTP timestamp from server's response
332  timestamp->seconds = ntohl(header->transmitTimestamp.seconds);
333  timestamp->fraction = ntohl(header->transmitTimestamp.fraction);
334 
335  //Successful processing
336  return NO_ERROR;
337 }
338 
339 
340 /**
341  * @brief Determine whether a timeout error has occurred
342  * @param[in] context Pointer to the SNTP client context
343  * @return Error code
344  **/
345 
347 {
348  error_t error;
349  systime_t time;
350 
351  //Get current time
352  time = osGetSystemTime();
353 
354  //Check whether the timeout has elapsed
355  if(timeCompare(time, context->startTime + context->timeout) >= 0)
356  {
357  //Report a timeout error
358  error = ERROR_TIMEOUT;
359  }
360  else if(timeCompare(time, context->retransmitStartTime + context->retransmitTimeout) >= 0)
361  {
362  //The timeout value is doubled for each subsequent retransmission
363  context->retransmitTimeout = MIN(context->retransmitTimeout * 2,
365 
366  //Retransmit NTP request
367  context->state = SNTP_CLIENT_STATE_SENDING;
368 
369  //Continue processing
370  error = NO_ERROR;
371  }
372  else
373  {
374 #if (NET_RTOS_SUPPORT == ENABLED)
375  //Report a timeout error
376  error = ERROR_TIMEOUT;
377 #else
378  //The operation would block
379  error = ERROR_WOULD_BLOCK;
380 #endif
381  }
382 
383  //Return status code
384  return error;
385 }
386 
387 
388 /**
389  * @brief Dump NTP message for debugging purpose
390  * @param[in] message Pointer to the NTP message
391  * @param[in] length Length of the NTP message
392  **/
393 
394 void sntpClientDumpMessage(const uint8_t *message, size_t length)
395 {
396 #if (SNTP_TRACE_LEVEL >= TRACE_LEVEL_DEBUG)
397  uint32_t kissCode;
398  const NtpHeader *header;
399  const NtpAuthenticator *auth;
400 
401  //Valid NTP packet?
402  if(length >= sizeof(NtpHeader))
403  {
404  //Point to the NTP packet header
405  header = (NtpHeader *) message;
406 
407  //Dump NTP message
408  TRACE_DEBUG(" Mode = %" PRIu8 "\r\n", header->mode);
409  TRACE_DEBUG(" Version = %" PRIu8 "\r\n", header->vn);
410  TRACE_DEBUG(" Leap indicator = %" PRIu8 "\r\n", header->li);
411  TRACE_DEBUG(" Stratum = %" PRIu8 "\r\n", header->stratum);
412  TRACE_DEBUG(" Poll = %" PRIu8 "\r\n", header->poll);
413  TRACE_DEBUG(" Precision = %" PRId8 "\r\n", header->precision);
414  TRACE_DEBUG(" Root Delay = %" PRIu32 "\r\n", ntohl(header->rootDelay));
415  TRACE_DEBUG(" Root Dispersion = %" PRIu32 "\r\n", ntohl(header->rootDispersion));
416 
417  //Retrieve kiss code
418  kissCode = htonl(header->referenceId);
419 
420  //Valid kiss code?
421  if(isalnum((kissCode >> 24) & 0xFF) &&
422  isalnum((kissCode >> 16) & 0xFF) &&
423  isalnum((kissCode >> 8) & 0xFF) &&
424  isalnum(kissCode & 0xFF))
425  {
426  //Dump kiss code
427  TRACE_DEBUG(" Kiss Code = '%c%c%c%c'\r\n", (kissCode >> 24) & 0xFF,
428  (kissCode >> 16) & 0xFF, (kissCode >> 8) & 0xFF, kissCode & 0xFF);
429  }
430  else
431  {
432  //Dump reference identifier
433  TRACE_DEBUG(" Reference Identifier = %" PRIu32 "\r\n",
434  header->referenceId);
435  }
436 
437  //Dump reference timestamp
438  TRACE_DEBUG(" ReferenceTimestamp\r\n");
439  sntpClientDumpTimestamp(&header->referenceTimestamp);
440 
441  //Dump originate timestamp
442  TRACE_DEBUG(" Originate Timestamp\r\n");
443  sntpClientDumpTimestamp(&header->originateTimestamp);
444 
445  //Dump receive timestamp
446  TRACE_DEBUG(" Receive Timestamp\r\n");
447  sntpClientDumpTimestamp(&header->receiveTimestamp);
448 
449  //Dump transmit timestamp
450  TRACE_DEBUG(" Transmit Timestamp\r\n");
451  sntpClientDumpTimestamp(&header->transmitTimestamp);
452  }
453 
454  //When the NTP authentication scheme is implemented, the Key Identifier
455  //and Message Digest fields contain the message authentication code (MAC)
456  //information
457  if(length >= (sizeof(NtpHeader) + sizeof(NtpAuthenticator)))
458  {
459  //The Authenticator field is optional
460  auth = (NtpAuthenticator *) (message + sizeof(NtpHeader));
461 
462  //Dump key identifier
463  TRACE_DEBUG(" Key Identifier = %" PRIu32 "\r\n", ntohl(auth->keyId));
464 
465  //Dump message digest
466  TRACE_DEBUG(" Message Digest\r\n");
467  TRACE_DEBUG_ARRAY(" ", auth->messageDigest, 16);
468  }
469 #endif
470 }
471 
472 
473 /**
474  * @brief Dump NTP timestamp
475  * @param[in] timestamp Pointer to the NTP timestamp
476  **/
477 
478 void sntpClientDumpTimestamp(const NtpTimestamp *timestamp)
479 {
480  //Dump seconds
481  TRACE_DEBUG(" Seconds = %" PRIu32 "\r\n", ntohl(timestamp->seconds));
482  //Dump fraction field
483  TRACE_DEBUG(" Fraction = %" PRIu32 "\r\n", ntohl(timestamp->fraction));
484 }
485 
486 #endif
uint8_t message[]
Definition: chap.h:154
#define PRIuSIZE
#define ntohl(value)
Definition: cpu_endian.h:422
#define htonl(value)
Definition: cpu_endian.h:414
Debugging facilities.
#define TRACE_DEBUG_ARRAY(p, a, n)
Definition: debug.h:108
#define TRACE_DEBUG(...)
Definition: debug.h:107
#define TRACE_INFO(...)
Definition: debug.h:95
uint32_t t1
uint32_t t2
uint32_t time
uint16_t port
Definition: dns_common.h:267
error_t
Error codes.
Definition: error.h:43
@ ERROR_WOULD_BLOCK
Definition: error.h:96
@ ERROR_TIMEOUT
Definition: error.h:95
@ ERROR_INVALID_MESSAGE
Definition: error.h:105
@ ERROR_OPEN_FAILED
Definition: error.h:75
@ NO_ERROR
Success.
Definition: error.h:44
@ ERROR_REQUEST_REJECTED
Definition: error.h:271
@ ERROR_INVALID_LENGTH
Definition: error.h:111
Ipv4Addr ipAddr
Definition: ipcp.h:105
TCP/IP stack core.
#define socketBindToInterface
Definition: net_legacy.h:193
NtpAuthenticator
Definition: ntp_common.h:176
@ NTP_VERSION_3
Definition: ntp_common.h:74
@ NTP_MODE_CLIENT
Definition: ntp_common.h:87
@ NTP_MODE_SERVER
Definition: ntp_common.h:88
@ NTP_MODE_BROADCAST
Definition: ntp_common.h:89
#define NTP_MAX_MSG_SIZE
Definition: ntp_common.h:40
NtpTimestamp
Definition: ntp_common.h:137
NtpHeader
Definition: ntp_common.h:165
#define osMemset(p, value, length)
Definition: os_port.h:135
#define timeCompare(t1, t2)
Definition: os_port.h:40
#define MIN(a, b)
Definition: os_port.h:63
systime_t osGetSystemTime(void)
Retrieve system time.
uint32_t systime_t
System time.
SNTP client (Simple Network Time Protocol)
#define SNTP_CLIENT_MAX_RETRANSMIT_TIMEOUT
Definition: sntp_client.h:61
@ SNTP_CLIENT_STATE_COMPLETE
Definition: sntp_client.h:86
@ SNTP_CLIENT_STATE_SENDING
Definition: sntp_client.h:84
@ SNTP_CLIENT_STATE_RECEIVING
Definition: sntp_client.h:85
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.
error_t sntpClientReceiveResponse(SntpClientContext *context)
Wait for NTP server's response.
error_t sntpClientSendRequest(SntpClientContext *context)
Send request to the NTP server.
error_t sntpClientCheckTimeout(SntpClientContext *context)
Determine whether a timeout error has occurred.
error_t sntpClientOpenConnection(SntpClientContext *context)
Open UDP connection.
void sntpClientDumpTimestamp(const NtpTimestamp *timestamp)
Dump NTP timestamp.
error_t sntpClientParseResponse(SntpClientContext *context, NtpTimestamp *timestamp)
Parse NTP server's response.
void sntpClientDumpMessage(const uint8_t *message, size_t length)
Dump NTP message for debugging purpose.
void sntpClientCloseConnection(SntpClientContext *context)
Close UDP connection.
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:1174
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:967
Socket * socketOpen(uint_t type, uint_t protocol)
Create a socket (UDP or TCP)
Definition: socket.c:125
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:148
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:1517
@ SOCKET_IP_PROTO_UDP
Definition: socket.h:101
@ SOCKET_TYPE_DGRAM
Definition: socket.h:86
IP network address.
Definition: ip.h:79
SNTP client context.
Definition: sntp_client.h:95
uint8_t message[NTP_MAX_MSG_SIZE]
Buffer that holds the NTP request/response.
Definition: sntp_client.h:105
uint32_t kissCode
Kiss code.
Definition: sntp_client.h:107
systime_t retransmitTimeout
Retransmission timeout.
Definition: sntp_client.h:104
size_t messageLen
Length of the NTP message, in bytes.
Definition: sntp_client.h:106
SntpClientState state
SNTP client state.
Definition: sntp_client.h:96
IpAddr serverIpAddr
NTP server address.
Definition: sntp_client.h:98
uint16_t serverPort
NTP server port.
Definition: sntp_client.h:99
systime_t timeout
Timeout value.
Definition: sntp_client.h:100
systime_t retransmitStartTime
Time at which the last request was sent.
Definition: sntp_client.h:103
Socket * socket
Underlying socket.
Definition: sntp_client.h:101
systime_t startTime
Request start time.
Definition: sntp_client.h:102
NetInterface * interface
Underlying network interface.
Definition: sntp_client.h:97
uint8_t length
Definition: tcp.h:368