sntp_client.c
Go to the documentation of this file.
1 /**
2  * @file sntp_client.c
3  * @brief SNTP client (Simple Network Time Protocol)
4  *
5  * @section License
6  *
7  * Copyright (C) 2010-2018 Oryx Embedded SARL. All rights reserved.
8  *
9  * This file is part of CycloneTCP Open.
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24  *
25  * @section Description
26  *
27  * The Simple Network Time Protocol is used to synchronize computer clocks
28  * in the Internet. Refer to RFC 4330 for more details
29  *
30  * @author Oryx Embedded SARL (www.oryx-embedded.com)
31  * @version 1.9.0
32  **/
33 
34 //Switch to the appropriate trace level
35 #define TRACE_LEVEL SNTP_TRACE_LEVEL
36 
37 //Dependencies
38 #include "core/net.h"
39 #include "sntp/sntp_client.h"
40 #include "error.h"
41 #include "debug.h"
42 
43 //Check TCP/IP stack configuration
44 #if (SNTP_CLIENT_SUPPORT == ENABLED)
45 
46 
47 /**
48  * @brief Retrieve current time from NTP server using SNTP protocol
49  * @param[in] interface Underlying network interface (optional parameter)
50  * @param[in] serverIpAddr IP address of the NTP server
51  * @param[out] timestamp Current time
52  * @return Error code
53  **/
54 
56  const IpAddr *serverIpAddr, NtpTimestamp *timestamp)
57 {
58  error_t error;
59  uint_t i;
60  systime_t timeout;
61  SntpClientContext context;
62 
63  //Check parameters
64  if(serverIpAddr == NULL || timestamp == NULL)
66 
67  //Use default network interface?
68  if(interface == NULL)
69  interface = netGetDefaultInterface();
70 
71  //Open a UDP socket
73  //Failed to open socket?
74  if(!context.socket)
75  return ERROR_OPEN_FAILED;
76 
77  //Associate the socket with the relevant interface
78  error = socketBindToInterface(context.socket, interface);
79  //Any error to report?
80  if(error)
81  {
82  //Close socket
83  socketClose(context.socket);
84  //Return status code
85  return error;
86  }
87 
88  //Only accept datagrams from the specified NTP server
89  error = socketConnect(context.socket, serverIpAddr, NTP_PORT);
90  //Any error to report?
91  if(error)
92  {
93  //Close socket
94  socketClose(context.socket);
95  //Return status code
96  return error;
97  }
98 
99  //Initial timeout value
100  timeout = SNTP_CLIENT_INIT_TIMEOUT;
101 
102  //Retransmission loop
103  for(i = 0; i < SNTP_CLIENT_MAX_RETRIES; i++)
104  {
105  //Send NTP request message
106  error = sntpSendRequest(&context);
107  //Failed to send message ?
108  if(error)
109  break;
110 
111  //Wait for a valid NTP response message
112  error = sntpWaitForResponse(&context, timeout);
113  //Valid NTP response received?
114  if(!error)
115  break;
116 
117  //The timeout value is doubled for each subsequent retransmission
118  timeout = MIN(timeout * 2, SNTP_CLIENT_MAX_TIMEOUT);
119  }
120 
121  //Successful processing?
122  if(!error)
123  {
124  //Save server timestamp
125  timestamp->seconds = ntohl(context.message.transmitTimestamp.seconds);
126  timestamp->fraction = ntohl(context.message.transmitTimestamp.fraction);
127  }
128 
129  //Close socket
130  socketClose(context.socket);
131  //Return status code
132  return error;
133 }
134 
135 
136 /**
137  * @brief Send NTP request using SNTP protocol
138  * @param[in] context SNTP client context
139  * @return Error code
140  **/
141 
143 {
144  size_t length;
146 
147  //Point to the buffer where to format the NTP message
148  message = &context->message;
149 
150  //Clear NTP message
151  memset(message, 0, sizeof(NtpHeader));
152 
153  //Format NTP request
154  message->vn = NTP_VERSION_3;
155  message->mode = NTP_MODE_CLIENT;
156 
157  //Time at which the NTP request was sent
158  context->t1 = osGetSystemTime();
159 
160  //The Transmit Timestamp allows a simple calculation to determine the
161  //propagation delay between the server and client and to align the system
162  //clock generally within a few tens of milliseconds relative to the server
163  message->transmitTimestamp.seconds = 0;
164  message->transmitTimestamp.fraction = htonl(context->t1);
165 
166  //Length of the NTP request
167  length = sizeof(NtpHeader);
168 
169  //Debug message
170  TRACE_INFO("Sending NTP request message (%" PRIuSIZE " bytes)...\r\n", sizeof(NtpHeader));
171  //Dump NTP message
173 
174  //Send NTP request
175  return socketSend(context->socket, message, length, NULL, 0);
176 }
177 
178 
179 /**
180  * @brief Wait for a valid response from the NTP server
181  * @param[in] context Pointer to the SNTP client context
182  * @param[in] timeout Maximum time period to wait
183  * @return Error code
184  **/
185 
187 {
188  error_t error;
189  size_t length;
190  systime_t elapsedTime;
191 
192  //Time elapsed since the NTP request was sent
193  elapsedTime = 0;
194 
195  //Keep listening as long as the retransmission timeout has not been reached
196  while(elapsedTime < timeout)
197  {
198  //Adjust receive timeout
199  error = socketSetTimeout(context->socket, timeout - elapsedTime);
200  //Any error to report?
201  if(error)
202  break;
203 
204  //Wait for a response from the NTP server
205  error = socketReceive(context->socket, &context->message,
206  sizeof(NtpHeader), &length, 0);
207 
208  //Any datagram received?
209  if(!error)
210  {
211  //Time at which the response was received
212  context->t4 = osGetSystemTime();
213 
214  //Parse incoming datagram
215  error = sntpParseResponse(context, &context->message, length);
216  //Valid NTP response message?
217  if(!error)
218  return NO_ERROR;
219  }
220 
221  //Compute the time elapsed since the NTP request was sent
222  elapsedTime = osGetSystemTime() - context->t1;
223  }
224 
225  //The timeout period elapsed
226  return ERROR_TIMEOUT;
227 }
228 
229 
230 /**
231  * @brief Parse NTP server response
232  * @param[in] context Pointer to the SNTP client context
233  * @param[in] message NTP response message to parse
234  * @param[in] length Length of the incoming NTP message
235  * @return Error code
236  **/
237 
239  const NtpHeader *message, size_t length)
240 {
241  //Ensure the NTP message is valid
242  if(length < sizeof(NtpHeader))
243  return ERROR_INVALID_MESSAGE;
244 
245  //Debug message
246  TRACE_INFO("NTP response message received (%" PRIuSIZE " bytes)...\r\n", length);
247  //Dump NTP message
249 
250  //The server reply should be discarded if any of the VN, Stratum,
251  //or Transmit Timestamp fields is 0
252  if(!message->vn || !message->stratum)
253  return ERROR_INVALID_MESSAGE;
254  if(!message->transmitTimestamp.seconds || !message->transmitTimestamp.fraction)
255  return ERROR_INVALID_MESSAGE;
256 
257  //The server reply should be discarded if the Mode field is
258  //not 4 (unicast) or 5 (broadcast)
259  if(message->mode != NTP_MODE_SERVER && message->mode != NTP_MODE_BROADCAST)
260  return ERROR_INVALID_MESSAGE;
261 
262  //The Originate Timestamp in the server reply should match the
263  //Transmit Timestamp used in the client request
264  if(message->originateTimestamp.seconds != 0)
266  if(message->originateTimestamp.fraction != htonl(context->t1))
268 
269  //The NTP response message is acceptable
270  return NO_ERROR;
271 }
272 
273 
274 /**
275  * @brief Dump NTP message for debugging purpose
276  * @param[in] message Pointer to the NTP message
277  * @param[in] length Length of the NTP message
278  **/
279 
281 {
282 #if (SNTP_TRACE_LEVEL >= TRACE_LEVEL_DEBUG)
283  uint8_t *p;
285 
286  //Point to the beginning of the message
287  p = (uint8_t *) message;
288 
289  //Check message length
290  if(length >= sizeof(NtpHeader))
291  {
292  //Dump NTP message
293  TRACE_DEBUG(" Mode = %" PRIu8 "\r\n", message->mode);
294  TRACE_DEBUG(" Version = %" PRIu8 "\r\n", message->vn);
295  TRACE_DEBUG(" Leap indicator = %" PRIu8 "\r\n", message->li);
296  TRACE_DEBUG(" Stratum = %" PRIu8 "\r\n", message->stratum);
297  TRACE_DEBUG(" Poll = %" PRIu8 "\r\n", message->poll);
298  TRACE_DEBUG(" Precision = %" PRId8 "\r\n", message->precision);
299  TRACE_DEBUG(" Root Delay = %" PRIu32 "\r\n", ntohl(message->rootDelay));
300  TRACE_DEBUG(" Root Dispersion = %" PRIu32 "\r\n", ntohl(message->rootDispersion));
301  TRACE_DEBUG(" Reference Identifier = %" PRIu32 "\r\n", ntohl(message->referenceIdentifier));
302 
303  //Dump reference timestamp
304  TRACE_DEBUG(" ReferenceTimestamp\r\n");
305  sntpDumpTimestamp(&message->referenceTimestamp);
306 
307  //Dump originate timestamp
308  TRACE_DEBUG(" Originate Timestamp\r\n");
309  sntpDumpTimestamp(&message->originateTimestamp);
310 
311  //Dump receive timestamp
312  TRACE_DEBUG(" Receive Timestamp\r\n");
313  sntpDumpTimestamp(&message->receiveTimestamp);
314 
315  //Dump transmit timestamp
316  TRACE_DEBUG(" Transmit Timestamp\r\n");
317  sntpDumpTimestamp(&message->transmitTimestamp);
318 
319  //Advance data pointer
320  p += sizeof(NtpHeader);
321  length -= sizeof(NtpHeader);
322 
323  //Any authentication data?
324  if(length >= sizeof(NtpAuthData))
325  {
326  //Point to the beginning of the authentication data
327  authData = (NtpAuthData *) p;
328 
329  //Dump transmit timestamp
330  TRACE_DEBUG(" Key Identifier = %" PRIu32 "\r\n", ntohl(authData->keyIdentifier));
331  //Dump message digest
332  TRACE_DEBUG(" Message Digest\r\n");
333  TRACE_DEBUG_ARRAY(" ", authData->messageDigest, 16);
334  }
335  }
336 #endif
337 }
338 
339 
340 /**
341  * @brief Dump NTP timestamp
342  * @param[in] timestamp Pointer to the NTP timestamp
343  **/
344 
345 void sntpDumpTimestamp(const NtpTimestamp *timestamp)
346 {
347  //Dump seconds
348  TRACE_DEBUG(" Seconds = %" PRIu32 "\r\n", ntohl(timestamp->seconds));
349  //Dump fraction field
350  TRACE_DEBUG(" Fraction = %" PRIu32 "\r\n", ntohl(timestamp->fraction));
351 }
352 
353 #endif
uint32_t systime_t
Definition: compiler_port.h:44
error_t socketReceive(Socket *socket, void *data, size_t size, size_t *received, uint_t flags)
Receive data from a connected socket.
Definition: socket.c:584
SNTP client context.
Definition: sntp_client.h:181
systime_t t1
Time at which the NTP request was sent by the client.
Definition: sntp_client.h:185
#define SNTP_CLIENT_INIT_TIMEOUT
Definition: sntp_client.h:52
systime_t osGetSystemTime(void)
Retrieve system time.
TCP/IP stack core.
#define NTP_PORT
Definition: sntp_client.h:65
Debugging facilities.
uint8_t p
Definition: ndp.h:295
SNTP client (Simple Network Time Protocol)
uint8_t message[]
Definition: chap.h:150
Invalid parameter.
Definition: error.h:45
__start_packed struct @295 NtpTimestamp
Time representation.
IP network address.
Definition: ip.h:57
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:797
#define TRACE_DEBUG_ARRAY(p, a, n)
Definition: debug.h:99
#define SNTP_CLIENT_MAX_TIMEOUT
Definition: sntp_client.h:59
void sntpDumpTimestamp(const NtpTimestamp *timestamp)
Dump NTP timestamp.
Definition: sntp_client.c:345
Error codes description.
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:216
#define ntohl(value)
Definition: cpu_endian.h:397
error_t sntpSendRequest(SntpClientContext *context)
Send NTP request using SNTP protocol.
Definition: sntp_client.c:142
#define htonl(value)
Definition: cpu_endian.h:391
systime_t t4
Time at which the NTP reply was received by the client.
Definition: sntp_client.h:186
NetInterface * netGetDefaultInterface(void)
Get default network interface.
Definition: net.c:1495
error_t sntpWaitForResponse(SntpClientContext *context, systime_t timeout)
Wait for a valid response from the NTP server.
Definition: sntp_client.c:186
NtpHeader message
Buffer where to format NTP messages.
Definition: sntp_client.h:184
#define MIN(a, b)
Definition: os_port.h:60
void sntpDumpMessage(const NtpHeader *message, size_t length)
Dump NTP message for debugging purpose.
Definition: sntp_client.c:280
#define TRACE_INFO(...)
Definition: debug.h:86
Success.
Definition: error.h:42
error_t socketConnect(Socket *socket, const IpAddr *remoteIpAddr, uint16_t remotePort)
Establish a connection to a specified socket.
Definition: socket.c:357
error_t
Error codes.
Definition: error.h:40
unsigned int uint_t
Definition: compiler_port.h:43
Socket * socketOpen(uint_t type, uint_t protocol)
Create a socket (UDP or TCP)
Definition: socket.c:92
__start_packed struct @296 NtpHeader
NTP packet header.
#define PRIuSIZE
Definition: compiler_port.h:72
Socket * socket
Underlying socket.
Definition: sntp_client.h:183
#define NetInterface
Definition: net.h:34
__start_packed struct @297 NtpAuthData
Authentication data.
error_t sntpClientGetTimestamp(NetInterface *interface, const IpAddr *serverIpAddr, NtpTimestamp *timestamp)
Retrieve current time from NTP server using SNTP protocol.
Definition: sntp_client.c:55
#define SNTP_CLIENT_MAX_RETRIES
Definition: sntp_client.h:45
error_t sntpParseResponse(SntpClientContext *context, const NtpHeader *message, size_t length)
Parse NTP server response.
Definition: sntp_client.c:238
uint8_t authData[]
Definition: ipv6.h:345
uint8_t length
Definition: dtls_misc.h:140
error_t socketSend(Socket *socket, const void *data, size_t length, size_t *written, uint_t flags)
Send data to a connected socket.
Definition: socket.c:490
error_t socketBindToInterface(Socket *socket, NetInterface *interface)
Bind a socket to a particular network interface.
Definition: socket.c:309
#define TRACE_DEBUG(...)
Definition: debug.h:98