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/as_core_type.hpp"
35 #include "common/code_utils.hpp"
36 #include "common/debug.hpp"
37 #include "common/instance.hpp"
38 #include "common/locator_getters.hpp"
39 #include "common/log.hpp"
40 #include "net/udp6.hpp"
41 #include "thread/thread_netif.hpp"
42
43 /**
44 * @file
45 * This file implements the SNTP client.
46 */
47
48 namespace ot {
49 namespace Sntp {
50
51 RegisterLogModule("SntpClnt");
52
Header(void)53 Header::Header(void)
54 : mFlags(kNtpVersion << kVersionOffset | kModeClient << kModeOffset)
55 , mStratum(0)
56 , mPoll(0)
57 , mPrecision(0)
58 , mRootDelay(0)
59 , mRootDispersion(0)
60 , mReferenceId(0)
61 , mReferenceTimestampSeconds(0)
62 , mReferenceTimestampFraction(0)
63 , mOriginateTimestampSeconds(0)
64 , mOriginateTimestampFraction(0)
65 , mReceiveTimestampSeconds(0)
66 , mReceiveTimestampFraction(0)
67 , mTransmitTimestampSeconds(0)
68 , mTransmitTimestampFraction(0)
69 {
70 }
71
QueryMetadata(void)72 QueryMetadata::QueryMetadata(void)
73 : mTransmitTimestamp(0)
74 , mResponseHandler(nullptr)
75 , mResponseContext(nullptr)
76 , mTransmissionTime(0)
77 , mDestinationPort(0)
78 , mRetransmissionCount(0)
79 {
80 mSourceAddress.Clear();
81 mDestinationAddress.Clear();
82 }
83
QueryMetadata(otSntpResponseHandler aHandler,void * aContext)84 QueryMetadata::QueryMetadata(otSntpResponseHandler aHandler, void *aContext)
85 : mTransmitTimestamp(0)
86 , mResponseHandler(aHandler)
87 , mResponseContext(aContext)
88 , mTransmissionTime(0)
89 , mDestinationPort(0)
90 , mRetransmissionCount(0)
91 {
92 mSourceAddress.Clear();
93 mDestinationAddress.Clear();
94 }
95
Client(Instance & aInstance)96 Client::Client(Instance &aInstance)
97 : mSocket(aInstance)
98 , mRetransmissionTimer(aInstance, Client::HandleRetransmissionTimer)
99 , mUnixEra(0)
100 {
101 }
102
Start(void)103 Error Client::Start(void)
104 {
105 Error error;
106
107 SuccessOrExit(error = mSocket.Open(&Client::HandleUdpReceive, this));
108 SuccessOrExit(error = mSocket.Bind(0, OT_NETIF_UNSPECIFIED));
109
110 exit:
111 return error;
112 }
113
Stop(void)114 Error Client::Stop(void)
115 {
116 for (Message &message : mPendingQueries)
117 {
118 QueryMetadata queryMetadata;
119
120 queryMetadata.ReadFrom(message);
121 FinalizeSntpTransaction(message, queryMetadata, 0, kErrorAbort);
122 }
123
124 return mSocket.Close();
125 }
126
Query(const otSntpQuery * aQuery,otSntpResponseHandler aHandler,void * aContext)127 Error Client::Query(const otSntpQuery *aQuery, otSntpResponseHandler aHandler, void *aContext)
128 {
129 Error error;
130 QueryMetadata queryMetadata(aHandler, aContext);
131 Message * message = nullptr;
132 Message * messageCopy = nullptr;
133 Header header;
134 const Ip6::MessageInfo *messageInfo;
135
136 VerifyOrExit(aQuery->mMessageInfo != nullptr, error = kErrorInvalidArgs);
137
138 // Originate timestamp is used only as a unique token.
139 header.SetTransmitTimestampSeconds(TimerMilli::GetNow().GetValue() / 1000 + kTimeAt1970);
140
141 VerifyOrExit((message = NewMessage(header)) != nullptr, error = kErrorNoBufs);
142
143 messageInfo = AsCoreTypePtr(aQuery->mMessageInfo);
144
145 queryMetadata.mTransmitTimestamp = header.GetTransmitTimestampSeconds();
146 queryMetadata.mTransmissionTime = TimerMilli::GetNow() + kResponseTimeout;
147 queryMetadata.mSourceAddress = messageInfo->GetSockAddr();
148 queryMetadata.mDestinationPort = messageInfo->GetPeerPort();
149 queryMetadata.mDestinationAddress = messageInfo->GetPeerAddr();
150 queryMetadata.mRetransmissionCount = 0;
151
152 VerifyOrExit((messageCopy = CopyAndEnqueueMessage(*message, queryMetadata)) != nullptr, error = kErrorNoBufs);
153 SuccessOrExit(error = SendMessage(*message, *messageInfo));
154
155 exit:
156
157 if (error != kErrorNone)
158 {
159 if (message)
160 {
161 message->Free();
162 }
163
164 if (messageCopy)
165 {
166 DequeueMessage(*messageCopy);
167 }
168 }
169
170 return error;
171 }
172
NewMessage(const Header & aHeader)173 Message *Client::NewMessage(const Header &aHeader)
174 {
175 Message *message = nullptr;
176
177 VerifyOrExit((message = mSocket.NewMessage(sizeof(aHeader))) != nullptr);
178 IgnoreError(message->Prepend(aHeader));
179 message->SetOffset(0);
180
181 exit:
182 return message;
183 }
184
CopyAndEnqueueMessage(const Message & aMessage,const QueryMetadata & aQueryMetadata)185 Message *Client::CopyAndEnqueueMessage(const Message &aMessage, const QueryMetadata &aQueryMetadata)
186 {
187 Error error = kErrorNone;
188 Message *messageCopy = nullptr;
189
190 // Create a message copy for further retransmissions.
191 VerifyOrExit((messageCopy = aMessage.Clone()) != nullptr, error = kErrorNoBufs);
192
193 // Append the copy with retransmission data and add it to the queue.
194 SuccessOrExit(error = aQueryMetadata.AppendTo(*messageCopy));
195 mPendingQueries.Enqueue(*messageCopy);
196
197 mRetransmissionTimer.FireAtIfEarlier(aQueryMetadata.mTransmissionTime);
198
199 exit:
200 FreeAndNullMessageOnError(messageCopy, error);
201 return messageCopy;
202 }
203
DequeueMessage(Message & aMessage)204 void Client::DequeueMessage(Message &aMessage)
205 {
206 if (mRetransmissionTimer.IsRunning() && (mPendingQueries.GetHead() == nullptr))
207 {
208 // No more requests pending, stop the timer.
209 mRetransmissionTimer.Stop();
210 }
211
212 mPendingQueries.DequeueAndFree(aMessage);
213 }
214
SendMessage(Message & aMessage,const Ip6::MessageInfo & aMessageInfo)215 Error Client::SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
216 {
217 return mSocket.SendTo(aMessage, aMessageInfo);
218 }
219
SendCopy(const Message & aMessage,const Ip6::MessageInfo & aMessageInfo)220 void Client::SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
221 {
222 Error error;
223 Message *messageCopy = nullptr;
224
225 // Create a message copy for lower layers.
226 VerifyOrExit((messageCopy = aMessage.Clone(aMessage.GetLength() - sizeof(QueryMetadata))) != nullptr,
227 error = kErrorNoBufs);
228
229 // Send the copy.
230 SuccessOrExit(error = SendMessage(*messageCopy, aMessageInfo));
231
232 exit:
233 if (error != kErrorNone)
234 {
235 FreeMessage(messageCopy);
236 LogWarn("Failed to send SNTP request: %s", ErrorToString(error));
237 }
238 }
239
FindRelatedQuery(const Header & aResponseHeader,QueryMetadata & aQueryMetadata)240 Message *Client::FindRelatedQuery(const Header &aResponseHeader, QueryMetadata &aQueryMetadata)
241 {
242 Message *matchedMessage = nullptr;
243
244 for (Message &message : mPendingQueries)
245 {
246 // Read originate timestamp.
247 aQueryMetadata.ReadFrom(message);
248
249 if (aQueryMetadata.mTransmitTimestamp == aResponseHeader.GetOriginateTimestampSeconds())
250 {
251 matchedMessage = &message;
252 break;
253 }
254 }
255
256 return matchedMessage;
257 }
258
FinalizeSntpTransaction(Message & aQuery,const QueryMetadata & aQueryMetadata,uint64_t aTime,Error aResult)259 void Client::FinalizeSntpTransaction(Message & aQuery,
260 const QueryMetadata &aQueryMetadata,
261 uint64_t aTime,
262 Error aResult)
263 {
264 DequeueMessage(aQuery);
265
266 if (aQueryMetadata.mResponseHandler != nullptr)
267 {
268 aQueryMetadata.mResponseHandler(aQueryMetadata.mResponseContext, aTime, aResult);
269 }
270 }
271
HandleRetransmissionTimer(Timer & aTimer)272 void Client::HandleRetransmissionTimer(Timer &aTimer)
273 {
274 aTimer.Get<Client>().HandleRetransmissionTimer();
275 }
276
HandleRetransmissionTimer(void)277 void Client::HandleRetransmissionTimer(void)
278 {
279 TimeMilli now = TimerMilli::GetNow();
280 TimeMilli nextTime = now.GetDistantFuture();
281 QueryMetadata queryMetadata;
282 Ip6::MessageInfo messageInfo;
283
284 for (Message &message : mPendingQueries)
285 {
286 queryMetadata.ReadFrom(message);
287
288 if (now >= queryMetadata.mTransmissionTime)
289 {
290 if (queryMetadata.mRetransmissionCount >= kMaxRetransmit)
291 {
292 // No expected response.
293 FinalizeSntpTransaction(message, queryMetadata, 0, kErrorResponseTimeout);
294 continue;
295 }
296
297 // Increment retransmission counter and timer.
298 queryMetadata.mRetransmissionCount++;
299 queryMetadata.mTransmissionTime = now + kResponseTimeout;
300 queryMetadata.UpdateIn(message);
301
302 // Retransmit
303 messageInfo.SetPeerAddr(queryMetadata.mDestinationAddress);
304 messageInfo.SetPeerPort(queryMetadata.mDestinationPort);
305 messageInfo.SetSockAddr(queryMetadata.mSourceAddress);
306
307 SendCopy(message, messageInfo);
308 }
309
310 if (nextTime > queryMetadata.mTransmissionTime)
311 {
312 nextTime = queryMetadata.mTransmissionTime;
313 }
314 }
315
316 if (nextTime < now.GetDistantFuture())
317 {
318 mRetransmissionTimer.FireAt(nextTime);
319 }
320 }
321
HandleUdpReceive(void * aContext,otMessage * aMessage,const otMessageInfo * aMessageInfo)322 void Client::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
323 {
324 static_cast<Client *>(aContext)->HandleUdpReceive(AsCoreType(aMessage), AsCoreType(aMessageInfo));
325 }
326
HandleUdpReceive(Message & aMessage,const Ip6::MessageInfo & aMessageInfo)327 void Client::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
328 {
329 OT_UNUSED_VARIABLE(aMessageInfo);
330
331 Error error = kErrorNone;
332 Header responseHeader;
333 QueryMetadata queryMetadata;
334 Message * message = nullptr;
335 uint64_t unixTime = 0;
336
337 SuccessOrExit(aMessage.Read(aMessage.GetOffset(), responseHeader));
338
339 VerifyOrExit((message = FindRelatedQuery(responseHeader, queryMetadata)) != nullptr);
340
341 // Check if response came from the server.
342 VerifyOrExit(responseHeader.GetMode() == Header::kModeServer, error = kErrorFailed);
343
344 // Check the Kiss-o'-death packet.
345 if (!responseHeader.GetStratum())
346 {
347 char kissCode[Header::kKissCodeLength + 1];
348
349 memcpy(kissCode, responseHeader.GetKissCode(), Header::kKissCodeLength);
350 kissCode[Header::kKissCodeLength] = 0;
351
352 LogInfo("SNTP response contains the Kiss-o'-death packet with %s code", kissCode);
353 ExitNow(error = kErrorBusy);
354 }
355
356 // Check if timestamp has been set.
357 VerifyOrExit(responseHeader.GetTransmitTimestampSeconds() != 0 &&
358 responseHeader.GetTransmitTimestampFraction() != 0,
359 error = kErrorFailed);
360
361 // The NTP time starts at 1900 while the unix epoch starts at 1970.
362 // Due to NTP protocol limitation, this module stops working correctly after around year 2106, if
363 // unix era is not updated. This seems to be a reasonable limitation for now. Era number cannot be
364 // obtained using NTP protocol, and client of this module is responsible to set it properly.
365 unixTime = GetUnixEra() * (1ULL << 32);
366
367 if (responseHeader.GetTransmitTimestampSeconds() > kTimeAt1970)
368 {
369 unixTime += static_cast<uint64_t>(responseHeader.GetTransmitTimestampSeconds()) - kTimeAt1970;
370 }
371 else
372 {
373 unixTime += static_cast<uint64_t>(responseHeader.GetTransmitTimestampSeconds()) + (1ULL << 32) - kTimeAt1970;
374 }
375
376 // Return the time since 1970.
377 FinalizeSntpTransaction(*message, queryMetadata, unixTime, kErrorNone);
378
379 exit:
380
381 if (message != nullptr && error != kErrorNone)
382 {
383 FinalizeSntpTransaction(*message, queryMetadata, 0, error);
384 }
385 }
386
387 } // namespace Sntp
388 } // namespace ot
389
390 #endif // OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
391