• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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