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