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