1 /*
2 * Copyright (c) 2021, The OpenThread Authors.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * 3. Neither the name of the copyright holder nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 /**
30 * @file
31 * This file implements the ping sender module.
32 */
33
34 #include "ping_sender.hpp"
35
36 #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
37
38 #include "common/encoding.hpp"
39 #include "common/locator_getters.hpp"
40 #include "common/random.hpp"
41
42 namespace ot {
43 namespace Utils {
44
45 using Encoding::BigEndian::HostSwap32;
46
SetUnspecifiedToDefault(void)47 void PingSender::Config::SetUnspecifiedToDefault(void)
48 {
49 if (mSize == 0)
50 {
51 mSize = kDefaultSize;
52 }
53
54 if (mCount == 0)
55 {
56 mCount = kDefaultCount;
57 }
58
59 if (mInterval == 0)
60 {
61 mInterval = kDefaultInterval;
62 }
63
64 if (mTimeout == 0)
65 {
66 mTimeout = kDefaultTimeout;
67 }
68 }
69
InvokeReplyCallback(const Reply & aReply) const70 void PingSender::Config::InvokeReplyCallback(const Reply &aReply) const
71 {
72 VerifyOrExit(mReplyCallback != nullptr);
73 mReplyCallback(&aReply, mCallbackContext);
74
75 exit:
76 return;
77 }
78
InvokeStatisticsCallback(const Statistics & aStatistics) const79 void PingSender::Config::InvokeStatisticsCallback(const Statistics &aStatistics) const
80 {
81 VerifyOrExit(mStatisticsCallback != nullptr);
82 mStatisticsCallback(&aStatistics, mCallbackContext);
83
84 exit:
85 return;
86 }
87
PingSender(Instance & aInstance)88 PingSender::PingSender(Instance &aInstance)
89 : InstanceLocator(aInstance)
90 , mIdentifier(0)
91 , mTargetEchoSequence(0)
92 , mTimer(aInstance, PingSender::HandleTimer)
93 , mIcmpHandler(PingSender::HandleIcmpReceive, this)
94 {
95 IgnoreError(Get<Ip6::Icmp>().RegisterHandler(mIcmpHandler));
96 }
97
Ping(const Config & aConfig)98 Error PingSender::Ping(const Config &aConfig)
99 {
100 Error error = kErrorPending;
101
102 VerifyOrExit(!mTimer.IsRunning(), error = kErrorBusy);
103
104 mConfig = aConfig;
105 mConfig.SetUnspecifiedToDefault();
106
107 VerifyOrExit(mConfig.mInterval <= Timer::kMaxDelay, error = kErrorInvalidArgs);
108
109 mStatistics.Clear();
110 mStatistics.mIsMulticast = static_cast<Ip6::Address *>(&mConfig.mDestination)->IsMulticast();
111
112 mIdentifier++;
113 SendPing();
114
115 exit:
116 return error;
117 }
118
Stop(void)119 void PingSender::Stop(void)
120 {
121 mTimer.Stop();
122 mIdentifier++;
123 }
124
SendPing(void)125 void PingSender::SendPing(void)
126 {
127 TimeMilli now = TimerMilli::GetNow();
128 Message * message = nullptr;
129 Ip6::MessageInfo messageInfo;
130
131 messageInfo.SetSockAddr(mConfig.GetSource());
132 messageInfo.SetPeerAddr(mConfig.GetDestination());
133 messageInfo.mHopLimit = mConfig.mHopLimit;
134 messageInfo.mAllowZeroHopLimit = mConfig.mAllowZeroHopLimit;
135
136 message = Get<Ip6::Icmp>().NewMessage(0);
137 VerifyOrExit(message != nullptr);
138
139 SuccessOrExit(message->Append(HostSwap32(now.GetValue())));
140
141 if (mConfig.mSize > message->GetLength())
142 {
143 SuccessOrExit(message->SetLength(mConfig.mSize));
144 }
145
146 mTargetEchoSequence = Get<Ip6::Icmp>().GetEchoSequence();
147 SuccessOrExit(Get<Ip6::Icmp>().SendEchoRequest(*message, messageInfo, mIdentifier));
148 mStatistics.mSentCount++;
149
150 #if OPENTHREAD_CONFIG_OTNS_ENABLE
151 Get<Utils::Otns>().EmitPingRequest(mConfig.GetDestination(), mConfig.mSize, now.GetValue(), mConfig.mHopLimit);
152 #endif
153
154 message = nullptr;
155
156 exit:
157 FreeMessage(message);
158 mConfig.mCount--;
159
160 if (mConfig.mCount > 0)
161 {
162 mTimer.Start(mConfig.mInterval);
163 }
164 else
165 {
166 mTimer.Start(mConfig.mTimeout);
167 }
168 }
169
HandleTimer(Timer & aTimer)170 void PingSender::HandleTimer(Timer &aTimer)
171 {
172 aTimer.Get<PingSender>().HandleTimer();
173 }
174
HandleTimer(void)175 void PingSender::HandleTimer(void)
176 {
177 if (mConfig.mCount > 0)
178 {
179 SendPing();
180 }
181 else // The last reply times out, triggering the callback to print statistics in CLI.
182 {
183 mConfig.InvokeStatisticsCallback(mStatistics);
184 }
185 }
186
HandleIcmpReceive(void * aContext,otMessage * aMessage,const otMessageInfo * aMessageInfo,const otIcmp6Header * aIcmpHeader)187 void PingSender::HandleIcmpReceive(void * aContext,
188 otMessage * aMessage,
189 const otMessageInfo *aMessageInfo,
190 const otIcmp6Header *aIcmpHeader)
191 {
192 reinterpret_cast<PingSender *>(aContext)->HandleIcmpReceive(*static_cast<Message *>(aMessage),
193 *static_cast<const Ip6::MessageInfo *>(aMessageInfo),
194 *static_cast<const Ip6::Icmp::Header *>(aIcmpHeader));
195 }
196
HandleIcmpReceive(const Message & aMessage,const Ip6::MessageInfo & aMessageInfo,const Ip6::Icmp::Header & aIcmpHeader)197 void PingSender::HandleIcmpReceive(const Message & aMessage,
198 const Ip6::MessageInfo & aMessageInfo,
199 const Ip6::Icmp::Header &aIcmpHeader)
200 {
201 Reply reply;
202 uint32_t timestamp;
203
204 VerifyOrExit(mTimer.IsRunning());
205 VerifyOrExit(aIcmpHeader.GetType() == Ip6::Icmp::Header::kTypeEchoReply);
206 VerifyOrExit(aIcmpHeader.GetId() == mIdentifier);
207
208 SuccessOrExit(aMessage.Read(aMessage.GetOffset(), timestamp));
209 timestamp = HostSwap32(timestamp);
210
211 reply.mSenderAddress = aMessageInfo.GetPeerAddr();
212 reply.mRoundTripTime =
213 static_cast<uint16_t>(OT_MIN(TimerMilli::GetNow() - TimeMilli(timestamp), NumericLimits<uint16_t>::kMax));
214 reply.mSize = aMessage.GetLength() - aMessage.GetOffset();
215 reply.mSequenceNumber = aIcmpHeader.GetSequence();
216 reply.mHopLimit = aMessageInfo.GetHopLimit();
217
218 mStatistics.mReceivedCount++;
219 mStatistics.mTotalRoundTripTime += reply.mRoundTripTime;
220 mStatistics.mMaxRoundTripTime = OT_MAX(mStatistics.mMaxRoundTripTime, reply.mRoundTripTime);
221 mStatistics.mMinRoundTripTime = OT_MIN(mStatistics.mMinRoundTripTime, reply.mRoundTripTime);
222
223 #if OPENTHREAD_CONFIG_OTNS_ENABLE
224 Get<Utils::Otns>().EmitPingReply(aMessageInfo.GetPeerAddr(), reply.mSize, timestamp, reply.mHopLimit);
225 #endif
226 // Received all ping replies, no need to wait longer.
227 if (!mStatistics.mIsMulticast && mConfig.mCount == 0 && aIcmpHeader.GetSequence() == mTargetEchoSequence)
228 {
229 mTimer.Stop();
230 }
231 mConfig.InvokeReplyCallback(reply);
232 // Received all ping replies, no need to wait longer.
233 if (!mStatistics.mIsMulticast && mConfig.mCount == 0 && aIcmpHeader.GetSequence() == mTargetEchoSequence)
234 {
235 mConfig.InvokeStatisticsCallback(mStatistics);
236 }
237
238 exit:
239 return;
240 }
241
242 } // namespace Utils
243 } // namespace ot
244
245 #endif // #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
246