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/as_core_type.hpp"
39 #include "common/encoding.hpp"
40 #include "common/locator_getters.hpp"
41 #include "common/random.hpp"
42
43 namespace ot {
44 namespace Utils {
45
46 using Encoding::BigEndian::HostSwap32;
47
SetUnspecifiedToDefault(void)48 void PingSender::Config::SetUnspecifiedToDefault(void)
49 {
50 if (mSize == 0)
51 {
52 mSize = kDefaultSize;
53 }
54
55 if (mCount == 0)
56 {
57 mCount = kDefaultCount;
58 }
59
60 if (mInterval == 0)
61 {
62 mInterval = kDefaultInterval;
63 }
64
65 if (mTimeout == 0)
66 {
67 mTimeout = kDefaultTimeout;
68 }
69 }
70
InvokeReplyCallback(const Reply & aReply) const71 void PingSender::Config::InvokeReplyCallback(const Reply &aReply) const
72 {
73 VerifyOrExit(mReplyCallback != nullptr);
74 mReplyCallback(&aReply, mCallbackContext);
75
76 exit:
77 return;
78 }
79
InvokeStatisticsCallback(const Statistics & aStatistics) const80 void PingSender::Config::InvokeStatisticsCallback(const Statistics &aStatistics) const
81 {
82 VerifyOrExit(mStatisticsCallback != nullptr);
83 mStatisticsCallback(&aStatistics, mCallbackContext);
84
85 exit:
86 return;
87 }
88
PingSender(Instance & aInstance)89 PingSender::PingSender(Instance &aInstance)
90 : InstanceLocator(aInstance)
91 , mIdentifier(0)
92 , mTargetEchoSequence(0)
93 , mTimer(aInstance, PingSender::HandleTimer)
94 , mIcmpHandler(PingSender::HandleIcmpReceive, this)
95 {
96 IgnoreError(Get<Ip6::Icmp>().RegisterHandler(mIcmpHandler));
97 }
98
Ping(const Config & aConfig)99 Error PingSender::Ping(const Config &aConfig)
100 {
101 Error error = kErrorNone;
102
103 VerifyOrExit(!mTimer.IsRunning(), error = kErrorBusy);
104
105 mConfig = aConfig;
106 mConfig.SetUnspecifiedToDefault();
107
108 VerifyOrExit(mConfig.mInterval <= Timer::kMaxDelay, error = kErrorInvalidArgs);
109
110 mStatistics.Clear();
111 mStatistics.mIsMulticast = AsCoreType(&mConfig.mDestination).IsMulticast();
112
113 mIdentifier++;
114 SendPing();
115
116 exit:
117 return error;
118 }
119
Stop(void)120 void PingSender::Stop(void)
121 {
122 mTimer.Stop();
123 mIdentifier++;
124 }
125
SendPing(void)126 void PingSender::SendPing(void)
127 {
128 TimeMilli now = TimerMilli::GetNow();
129 Message * message = nullptr;
130 Ip6::MessageInfo messageInfo;
131
132 messageInfo.SetSockAddr(mConfig.GetSource());
133 messageInfo.SetPeerAddr(mConfig.GetDestination());
134 messageInfo.mHopLimit = mConfig.mHopLimit;
135 messageInfo.mAllowZeroHopLimit = mConfig.mAllowZeroHopLimit;
136
137 message = Get<Ip6::Icmp>().NewMessage(0);
138 VerifyOrExit(message != nullptr);
139
140 SuccessOrExit(message->Append(HostSwap32(now.GetValue())));
141
142 if (mConfig.mSize > message->GetLength())
143 {
144 SuccessOrExit(message->SetLength(mConfig.mSize));
145 }
146
147 mTargetEchoSequence = Get<Ip6::Icmp>().GetEchoSequence();
148 SuccessOrExit(Get<Ip6::Icmp>().SendEchoRequest(*message, messageInfo, mIdentifier));
149 mStatistics.mSentCount++;
150
151 #if OPENTHREAD_CONFIG_OTNS_ENABLE
152 Get<Utils::Otns>().EmitPingRequest(mConfig.GetDestination(), mConfig.mSize, now.GetValue(), mConfig.mHopLimit);
153 #endif
154
155 message = nullptr;
156
157 exit:
158 FreeMessage(message);
159 mConfig.mCount--;
160
161 if (mConfig.mCount > 0)
162 {
163 mTimer.Start(mConfig.mInterval);
164 }
165 else
166 {
167 mTimer.Start(mConfig.mTimeout);
168 }
169 }
170
HandleTimer(Timer & aTimer)171 void PingSender::HandleTimer(Timer &aTimer)
172 {
173 aTimer.Get<PingSender>().HandleTimer();
174 }
175
HandleTimer(void)176 void PingSender::HandleTimer(void)
177 {
178 if (mConfig.mCount > 0)
179 {
180 SendPing();
181 }
182 else // The last reply times out, triggering the callback to print statistics in CLI.
183 {
184 mConfig.InvokeStatisticsCallback(mStatistics);
185 }
186 }
187
HandleIcmpReceive(void * aContext,otMessage * aMessage,const otMessageInfo * aMessageInfo,const otIcmp6Header * aIcmpHeader)188 void PingSender::HandleIcmpReceive(void * aContext,
189 otMessage * aMessage,
190 const otMessageInfo *aMessageInfo,
191 const otIcmp6Header *aIcmpHeader)
192 {
193 reinterpret_cast<PingSender *>(aContext)->HandleIcmpReceive(AsCoreType(aMessage), AsCoreType(aMessageInfo),
194 AsCoreType(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