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