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