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/num_utils.hpp"
42 #include "common/random.hpp"
43
44 namespace ot {
45 namespace Utils {
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)
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 = kErrorNone;
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 = AsCoreType(&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 messageInfo.mMulticastLoop = mConfig.mMulticastLoop;
136
137 message = Get<Ip6::Icmp>().NewMessage();
138 VerifyOrExit(message != nullptr);
139
140 SuccessOrExit(message->Append(BigEndian::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(void)171 void PingSender::HandleTimer(void)
172 {
173 if (mConfig.mCount > 0)
174 {
175 SendPing();
176 }
177 else // The last reply times out, triggering the callback to print statistics in CLI.
178 {
179 mConfig.InvokeStatisticsCallback(mStatistics);
180 }
181 }
182
HandleIcmpReceive(void * aContext,otMessage * aMessage,const otMessageInfo * aMessageInfo,const otIcmp6Header * aIcmpHeader)183 void PingSender::HandleIcmpReceive(void *aContext,
184 otMessage *aMessage,
185 const otMessageInfo *aMessageInfo,
186 const otIcmp6Header *aIcmpHeader)
187 {
188 reinterpret_cast<PingSender *>(aContext)->HandleIcmpReceive(AsCoreType(aMessage), AsCoreType(aMessageInfo),
189 AsCoreType(aIcmpHeader));
190 }
191
HandleIcmpReceive(const Message & aMessage,const Ip6::MessageInfo & aMessageInfo,const Ip6::Icmp::Header & aIcmpHeader)192 void PingSender::HandleIcmpReceive(const Message &aMessage,
193 const Ip6::MessageInfo &aMessageInfo,
194 const Ip6::Icmp::Header &aIcmpHeader)
195 {
196 Reply reply;
197 uint32_t timestamp;
198
199 VerifyOrExit(mTimer.IsRunning());
200 VerifyOrExit(aIcmpHeader.GetType() == Ip6::Icmp::Header::kTypeEchoReply);
201 VerifyOrExit(aIcmpHeader.GetId() == mIdentifier);
202
203 SuccessOrExit(aMessage.Read(aMessage.GetOffset(), timestamp));
204 timestamp = BigEndian::HostSwap32(timestamp);
205
206 reply.mSenderAddress = aMessageInfo.GetPeerAddr();
207 reply.mRoundTripTime = ClampToUint16(TimerMilli::GetNow() - TimeMilli(timestamp));
208 reply.mSize = aMessage.GetLength() - aMessage.GetOffset();
209 reply.mSequenceNumber = aIcmpHeader.GetSequence();
210 reply.mHopLimit = aMessageInfo.GetHopLimit();
211
212 mStatistics.mReceivedCount++;
213 mStatistics.mTotalRoundTripTime += reply.mRoundTripTime;
214 mStatistics.mMaxRoundTripTime = Max(mStatistics.mMaxRoundTripTime, reply.mRoundTripTime);
215 mStatistics.mMinRoundTripTime = Min(mStatistics.mMinRoundTripTime, reply.mRoundTripTime);
216
217 #if OPENTHREAD_CONFIG_OTNS_ENABLE
218 Get<Utils::Otns>().EmitPingReply(aMessageInfo.GetPeerAddr(), reply.mSize, timestamp, reply.mHopLimit);
219 #endif
220 // Received all ping replies, no need to wait longer.
221 if (!mStatistics.mIsMulticast && mConfig.mCount == 0 && aIcmpHeader.GetSequence() == mTargetEchoSequence)
222 {
223 mTimer.Stop();
224 }
225 mConfig.InvokeReplyCallback(reply);
226 // Received all ping replies, no need to wait longer.
227 if (!mStatistics.mIsMulticast && mConfig.mCount == 0 && aIcmpHeader.GetSequence() == mTargetEchoSequence)
228 {
229 mConfig.InvokeStatisticsCallback(mStatistics);
230 }
231
232 exit:
233 return;
234 }
235
236 } // namespace Utils
237 } // namespace ot
238
239 #endif // #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
240