1
2 /*
3 * Copyright (c) 2018, The OpenThread Authors.
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of the copyright holder nor the
14 * names of its contributors may be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "sntp_client.hpp"
31
32 #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
33
34 #include "common/code_utils.hpp"
35 #include "common/debug.hpp"
36 #include "common/instance.hpp"
37 #include "common/locator_getters.hpp"
38 #include "common/logging.hpp"
39 #include "net/udp6.hpp"
40 #include "thread/thread_netif.hpp"
41
42 /**
43 * @file
44 * This file implements the SNTP client.
45 */
46
47 namespace ot {
48 namespace Sntp {
49
Header(void)50 Header::Header(void)
51 : mFlags(kNtpVersion << kVersionOffset | kModeClient << kModeOffset)
52 , mStratum(0)
53 , mPoll(0)
54 , mPrecision(0)
55 , mRootDelay(0)
56 , mRootDispersion(0)
57 , mReferenceId(0)
58 , mReferenceTimestampSeconds(0)
59 , mReferenceTimestampFraction(0)
60 , mOriginateTimestampSeconds(0)
61 , mOriginateTimestampFraction(0)
62 , mReceiveTimestampSeconds(0)
63 , mReceiveTimestampFraction(0)
64 , mTransmitTimestampSeconds(0)
65 , mTransmitTimestampFraction(0)
66 {
67 }
68
QueryMetadata(void)69 QueryMetadata::QueryMetadata(void)
70 : mTransmitTimestamp(0)
71 , mResponseHandler(nullptr)
72 , mResponseContext(nullptr)
73 , mTransmissionTime(0)
74 , mDestinationPort(0)
75 , mRetransmissionCount(0)
76 {
77 mSourceAddress.Clear();
78 mDestinationAddress.Clear();
79 }
80
QueryMetadata(otSntpResponseHandler aHandler,void * aContext)81 QueryMetadata::QueryMetadata(otSntpResponseHandler aHandler, void *aContext)
82 : mTransmitTimestamp(0)
83 , mResponseHandler(aHandler)
84 , mResponseContext(aContext)
85 , mTransmissionTime(0)
86 , mDestinationPort(0)
87 , mRetransmissionCount(0)
88 {
89 mSourceAddress.Clear();
90 mDestinationAddress.Clear();
91 }
92
Client(Instance & aInstance)93 Client::Client(Instance &aInstance)
94 : mSocket(aInstance)
95 , mRetransmissionTimer(aInstance, Client::HandleRetransmissionTimer)
96 , mUnixEra(0)
97 {
98 }
99
Start(void)100 Error Client::Start(void)
101 {
102 Error error;
103
104 SuccessOrExit(error = mSocket.Open(&Client::HandleUdpReceive, this));
105 SuccessOrExit(error = mSocket.Bind(0, OT_NETIF_UNSPECIFIED));
106
107 exit:
108 return error;
109 }
110
Stop(void)111 Error Client::Stop(void)
112 {
113 Message * message = mPendingQueries.GetHead();
114 Message * messageToRemove;
115 QueryMetadata queryMetadata;
116
117 // Remove all pending queries.
118 while (message != nullptr)
119 {
120 messageToRemove = message;
121 message = message->GetNext();
122
123 queryMetadata.ReadFrom(*messageToRemove);
124 FinalizeSntpTransaction(*messageToRemove, queryMetadata, 0, kErrorAbort);
125 }
126
127 return mSocket.Close();
128 }
129
Query(const otSntpQuery * aQuery,otSntpResponseHandler aHandler,void * aContext)130 Error Client::Query(const otSntpQuery *aQuery, otSntpResponseHandler aHandler, void *aContext)
131 {
132 Error error;
133 QueryMetadata queryMetadata(aHandler, aContext);
134 Message * message = nullptr;
135 Message * messageCopy = nullptr;
136 Header header;
137 const Ip6::MessageInfo *messageInfo;
138
139 VerifyOrExit(aQuery->mMessageInfo != nullptr, error = kErrorInvalidArgs);
140
141 // Originate timestamp is used only as a unique token.
142 header.SetTransmitTimestampSeconds(TimerMilli::GetNow().GetValue() / 1000 + kTimeAt1970);
143
144 VerifyOrExit((message = NewMessage(header)) != nullptr, error = kErrorNoBufs);
145
146 messageInfo = static_cast<const Ip6::MessageInfo *>(aQuery->mMessageInfo);
147
148 queryMetadata.mTransmitTimestamp = header.GetTransmitTimestampSeconds();
149 queryMetadata.mTransmissionTime = TimerMilli::GetNow() + kResponseTimeout;
150 queryMetadata.mSourceAddress = messageInfo->GetSockAddr();
151 queryMetadata.mDestinationPort = messageInfo->GetPeerPort();
152 queryMetadata.mDestinationAddress = messageInfo->GetPeerAddr();
153 queryMetadata.mRetransmissionCount = 0;
154
155 VerifyOrExit((messageCopy = CopyAndEnqueueMessage(*message, queryMetadata)) != nullptr, error = kErrorNoBufs);
156 SuccessOrExit(error = SendMessage(*message, *messageInfo));
157
158 exit:
159
160 if (error != kErrorNone)
161 {
162 if (message)
163 {
164 message->Free();
165 }
166
167 if (messageCopy)
168 {
169 DequeueMessage(*messageCopy);
170 }
171 }
172
173 return error;
174 }
175
NewMessage(const Header & aHeader)176 Message *Client::NewMessage(const Header &aHeader)
177 {
178 Message *message = nullptr;
179
180 VerifyOrExit((message = mSocket.NewMessage(sizeof(aHeader))) != nullptr);
181 IgnoreError(message->Prepend(aHeader));
182 message->SetOffset(0);
183
184 exit:
185 return message;
186 }
187
CopyAndEnqueueMessage(const Message & aMessage,const QueryMetadata & aQueryMetadata)188 Message *Client::CopyAndEnqueueMessage(const Message &aMessage, const QueryMetadata &aQueryMetadata)
189 {
190 Error error = kErrorNone;
191 Message *messageCopy = nullptr;
192
193 // Create a message copy for further retransmissions.
194 VerifyOrExit((messageCopy = aMessage.Clone()) != nullptr, error = kErrorNoBufs);
195
196 // Append the copy with retransmission data and add it to the queue.
197 SuccessOrExit(error = aQueryMetadata.AppendTo(*messageCopy));
198 mPendingQueries.Enqueue(*messageCopy);
199
200 mRetransmissionTimer.FireAtIfEarlier(aQueryMetadata.mTransmissionTime);
201
202 exit:
203 FreeAndNullMessageOnError(messageCopy, error);
204 return messageCopy;
205 }
206
DequeueMessage(Message & aMessage)207 void Client::DequeueMessage(Message &aMessage)
208 {
209 if (mRetransmissionTimer.IsRunning() && (mPendingQueries.GetHead() == nullptr))
210 {
211 // No more requests pending, stop the timer.
212 mRetransmissionTimer.Stop();
213 }
214
215 mPendingQueries.DequeueAndFree(aMessage);
216 }
217
SendMessage(Message & aMessage,const Ip6::MessageInfo & aMessageInfo)218 Error Client::SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
219 {
220 return mSocket.SendTo(aMessage, aMessageInfo);
221 }
222
SendCopy(const Message & aMessage,const Ip6::MessageInfo & aMessageInfo)223 void Client::SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
224 {
225 Error error;
226 Message *messageCopy = nullptr;
227
228 // Create a message copy for lower layers.
229 VerifyOrExit((messageCopy = aMessage.Clone(aMessage.GetLength() - sizeof(QueryMetadata))) != nullptr,
230 error = kErrorNoBufs);
231
232 // Send the copy.
233 SuccessOrExit(error = SendMessage(*messageCopy, aMessageInfo));
234
235 exit:
236 if (error != kErrorNone)
237 {
238 FreeMessage(messageCopy);
239 otLogWarnIp6("Failed to send SNTP request: %s", ErrorToString(error));
240 }
241 }
242
FindRelatedQuery(const Header & aResponseHeader,QueryMetadata & aQueryMetadata)243 Message *Client::FindRelatedQuery(const Header &aResponseHeader, QueryMetadata &aQueryMetadata)
244 {
245 Header header;
246 Message *message = mPendingQueries.GetHead();
247
248 while (message != nullptr)
249 {
250 // Read originate timestamp.
251 aQueryMetadata.ReadFrom(*message);
252
253 if (aQueryMetadata.mTransmitTimestamp == aResponseHeader.GetOriginateTimestampSeconds())
254 {
255 ExitNow();
256 }
257
258 message = message->GetNext();
259 }
260
261 exit:
262 return message;
263 }
264
FinalizeSntpTransaction(Message & aQuery,const QueryMetadata & aQueryMetadata,uint64_t aTime,Error aResult)265 void Client::FinalizeSntpTransaction(Message & aQuery,
266 const QueryMetadata &aQueryMetadata,
267 uint64_t aTime,
268 Error aResult)
269 {
270 DequeueMessage(aQuery);
271
272 if (aQueryMetadata.mResponseHandler != nullptr)
273 {
274 aQueryMetadata.mResponseHandler(aQueryMetadata.mResponseContext, aTime, aResult);
275 }
276 }
277
HandleRetransmissionTimer(Timer & aTimer)278 void Client::HandleRetransmissionTimer(Timer &aTimer)
279 {
280 aTimer.Get<Client>().HandleRetransmissionTimer();
281 }
282
HandleRetransmissionTimer(void)283 void Client::HandleRetransmissionTimer(void)
284 {
285 TimeMilli now = TimerMilli::GetNow();
286 TimeMilli nextTime = now.GetDistantFuture();
287 QueryMetadata queryMetadata;
288 Message * message;
289 Message * nextMessage;
290 Ip6::MessageInfo messageInfo;
291
292 for (message = mPendingQueries.GetHead(); message != nullptr; message = nextMessage)
293 {
294 nextMessage = message->GetNext();
295
296 queryMetadata.ReadFrom(*message);
297
298 if (now >= queryMetadata.mTransmissionTime)
299 {
300 if (queryMetadata.mRetransmissionCount >= kMaxRetransmit)
301 {
302 // No expected response.
303 FinalizeSntpTransaction(*message, queryMetadata, 0, kErrorResponseTimeout);
304 continue;
305 }
306
307 // Increment retransmission counter and timer.
308 queryMetadata.mRetransmissionCount++;
309 queryMetadata.mTransmissionTime = now + kResponseTimeout;
310 queryMetadata.UpdateIn(*message);
311
312 // Retransmit
313 messageInfo.SetPeerAddr(queryMetadata.mDestinationAddress);
314 messageInfo.SetPeerPort(queryMetadata.mDestinationPort);
315 messageInfo.SetSockAddr(queryMetadata.mSourceAddress);
316
317 SendCopy(*message, messageInfo);
318 }
319
320 if (nextTime > queryMetadata.mTransmissionTime)
321 {
322 nextTime = queryMetadata.mTransmissionTime;
323 }
324 }
325
326 if (nextTime < now.GetDistantFuture())
327 {
328 mRetransmissionTimer.FireAt(nextTime);
329 }
330 }
331
HandleUdpReceive(void * aContext,otMessage * aMessage,const otMessageInfo * aMessageInfo)332 void Client::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
333 {
334 static_cast<Client *>(aContext)->HandleUdpReceive(*static_cast<Message *>(aMessage),
335 *static_cast<const Ip6::MessageInfo *>(aMessageInfo));
336 }
337
HandleUdpReceive(Message & aMessage,const Ip6::MessageInfo & aMessageInfo)338 void Client::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
339 {
340 OT_UNUSED_VARIABLE(aMessageInfo);
341
342 Error error = kErrorNone;
343 Header responseHeader;
344 QueryMetadata queryMetadata;
345 Message * message = nullptr;
346 uint64_t unixTime = 0;
347
348 SuccessOrExit(aMessage.Read(aMessage.GetOffset(), responseHeader));
349
350 VerifyOrExit((message = FindRelatedQuery(responseHeader, queryMetadata)) != nullptr);
351
352 // Check if response came from the server.
353 VerifyOrExit(responseHeader.GetMode() == Header::kModeServer, error = kErrorFailed);
354
355 // Check the Kiss-o'-death packet.
356 if (!responseHeader.GetStratum())
357 {
358 char kissCode[Header::kKissCodeLength + 1];
359
360 memcpy(kissCode, responseHeader.GetKissCode(), Header::kKissCodeLength);
361 kissCode[Header::kKissCodeLength] = 0;
362
363 otLogInfoIp6("SNTP response contains the Kiss-o'-death packet with %s code", kissCode);
364 ExitNow(error = kErrorBusy);
365 }
366
367 // Check if timestamp has been set.
368 VerifyOrExit(responseHeader.GetTransmitTimestampSeconds() != 0 &&
369 responseHeader.GetTransmitTimestampFraction() != 0,
370 error = kErrorFailed);
371
372 // The NTP time starts at 1900 while the unix epoch starts at 1970.
373 // Due to NTP protocol limitation, this module stops working correctly after around year 2106, if
374 // unix era is not updated. This seems to be a reasonable limitation for now. Era number cannot be
375 // obtained using NTP protocol, and client of this module is responsible to set it properly.
376 unixTime = GetUnixEra() * (1ULL << 32);
377
378 if (responseHeader.GetTransmitTimestampSeconds() > kTimeAt1970)
379 {
380 unixTime += static_cast<uint64_t>(responseHeader.GetTransmitTimestampSeconds()) - kTimeAt1970;
381 }
382 else
383 {
384 unixTime += static_cast<uint64_t>(responseHeader.GetTransmitTimestampSeconds()) + (1ULL << 32) - kTimeAt1970;
385 }
386
387 // Return the time since 1970.
388 FinalizeSntpTransaction(*message, queryMetadata, unixTime, kErrorNone);
389
390 exit:
391
392 if (message != nullptr && error != kErrorNone)
393 {
394 FinalizeSntpTransaction(*message, queryMetadata, 0, error);
395 }
396 }
397
398 } // namespace Sntp
399 } // namespace ot
400
401 #endif // OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
402