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 "instance/instance.hpp"
39
40 namespace ot {
41 namespace Utils {
42
SetUnspecifiedToDefault(void)43 void PingSender::Config::SetUnspecifiedToDefault(void)
44 {
45 if (mSize == 0)
46 {
47 mSize = kDefaultSize;
48 }
49
50 if (mCount == 0)
51 {
52 mCount = kDefaultCount;
53 }
54
55 if (mInterval == 0)
56 {
57 mInterval = kDefaultInterval;
58 }
59
60 if (mTimeout == 0)
61 {
62 mTimeout = kDefaultTimeout;
63 }
64 }
65
InvokeReplyCallback(const Reply & aReply) const66 void PingSender::Config::InvokeReplyCallback(const Reply &aReply) const
67 {
68 VerifyOrExit(mReplyCallback != nullptr);
69 mReplyCallback(&aReply, mCallbackContext);
70
71 exit:
72 return;
73 }
74
InvokeStatisticsCallback(const Statistics & aStatistics) const75 void PingSender::Config::InvokeStatisticsCallback(const Statistics &aStatistics) const
76 {
77 VerifyOrExit(mStatisticsCallback != nullptr);
78 mStatisticsCallback(&aStatistics, mCallbackContext);
79
80 exit:
81 return;
82 }
83
PingSender(Instance & aInstance)84 PingSender::PingSender(Instance &aInstance)
85 : InstanceLocator(aInstance)
86 , mIdentifier(0)
87 , mTargetEchoSequence(0)
88 , mTimer(aInstance)
89 , mIcmpHandler(PingSender::HandleIcmpReceive, this)
90 {
91 IgnoreError(Get<Ip6::Icmp>().RegisterHandler(mIcmpHandler));
92 }
93
Ping(const Config & aConfig)94 Error PingSender::Ping(const Config &aConfig)
95 {
96 Error error = kErrorNone;
97
98 VerifyOrExit(!mTimer.IsRunning(), error = kErrorBusy);
99
100 mConfig = aConfig;
101 mConfig.SetUnspecifiedToDefault();
102
103 VerifyOrExit(mConfig.mInterval <= Timer::kMaxDelay, error = kErrorInvalidArgs);
104
105 mStatistics.Clear();
106 mStatistics.mIsMulticast = AsCoreType(&mConfig.mDestination).IsMulticast();
107
108 mIdentifier++;
109 SendPing();
110
111 exit:
112 return error;
113 }
114
Stop(void)115 void PingSender::Stop(void)
116 {
117 mTimer.Stop();
118 mIdentifier++;
119 }
120
SendPing(void)121 void PingSender::SendPing(void)
122 {
123 TimeMilli now = TimerMilli::GetNow();
124 Message *message = nullptr;
125 Ip6::MessageInfo messageInfo;
126
127 messageInfo.SetSockAddr(mConfig.GetSource());
128 messageInfo.SetPeerAddr(mConfig.GetDestination());
129 messageInfo.mHopLimit = mConfig.mHopLimit;
130 messageInfo.mAllowZeroHopLimit = mConfig.mAllowZeroHopLimit;
131 messageInfo.mMulticastLoop = mConfig.mMulticastLoop;
132
133 message = Get<Ip6::Icmp>().NewMessage();
134 VerifyOrExit(message != nullptr);
135
136 SuccessOrExit(message->Append(BigEndian::HostSwap32(now.GetValue())));
137
138 if (mConfig.mSize > message->GetLength())
139 {
140 SuccessOrExit(message->SetLength(mConfig.mSize));
141 }
142
143 mTargetEchoSequence = Get<Ip6::Icmp>().GetEchoSequence();
144 SuccessOrExit(Get<Ip6::Icmp>().SendEchoRequest(*message, messageInfo, mIdentifier));
145 mStatistics.mSentCount++;
146
147 #if OPENTHREAD_CONFIG_OTNS_ENABLE
148 Get<Utils::Otns>().EmitPingRequest(mConfig.GetDestination(), mConfig.mSize, now.GetValue(), mConfig.mHopLimit);
149 #endif
150
151 message = nullptr;
152
153 exit:
154 FreeMessage(message);
155 mConfig.mCount--;
156
157 if (mConfig.mCount > 0)
158 {
159 mTimer.Start(mConfig.mInterval);
160 }
161 else
162 {
163 mTimer.Start(mConfig.mTimeout);
164 }
165 }
166
HandleTimer(void)167 void PingSender::HandleTimer(void)
168 {
169 if (mConfig.mCount > 0)
170 {
171 SendPing();
172 }
173 else // The last reply times out, triggering the callback to print statistics in CLI.
174 {
175 mConfig.InvokeStatisticsCallback(mStatistics);
176 }
177 }
178
HandleIcmpReceive(void * aContext,otMessage * aMessage,const otMessageInfo * aMessageInfo,const otIcmp6Header * aIcmpHeader)179 void PingSender::HandleIcmpReceive(void *aContext,
180 otMessage *aMessage,
181 const otMessageInfo *aMessageInfo,
182 const otIcmp6Header *aIcmpHeader)
183 {
184 reinterpret_cast<PingSender *>(aContext)->HandleIcmpReceive(AsCoreType(aMessage), AsCoreType(aMessageInfo),
185 AsCoreType(aIcmpHeader));
186 }
187
HandleIcmpReceive(const Message & aMessage,const Ip6::MessageInfo & aMessageInfo,const Ip6::Icmp::Header & aIcmpHeader)188 void PingSender::HandleIcmpReceive(const Message &aMessage,
189 const Ip6::MessageInfo &aMessageInfo,
190 const Ip6::Icmp::Header &aIcmpHeader)
191 {
192 Reply reply;
193 uint32_t timestamp;
194
195 VerifyOrExit(mTimer.IsRunning());
196 VerifyOrExit(aIcmpHeader.GetType() == Ip6::Icmp::Header::kTypeEchoReply);
197 VerifyOrExit(aIcmpHeader.GetId() == mIdentifier);
198
199 SuccessOrExit(aMessage.Read(aMessage.GetOffset(), timestamp));
200 timestamp = BigEndian::HostSwap32(timestamp);
201
202 reply.mSenderAddress = aMessageInfo.GetPeerAddr();
203 reply.mRoundTripTime = ClampToUint16(TimerMilli::GetNow() - TimeMilli(timestamp));
204 reply.mSize = aMessage.GetLength() - aMessage.GetOffset();
205 reply.mSequenceNumber = aIcmpHeader.GetSequence();
206 reply.mHopLimit = aMessageInfo.GetHopLimit();
207
208 mStatistics.mReceivedCount++;
209 mStatistics.mTotalRoundTripTime += reply.mRoundTripTime;
210 mStatistics.mMaxRoundTripTime = Max(mStatistics.mMaxRoundTripTime, reply.mRoundTripTime);
211 mStatistics.mMinRoundTripTime = Min(mStatistics.mMinRoundTripTime, reply.mRoundTripTime);
212
213 #if OPENTHREAD_CONFIG_OTNS_ENABLE
214 Get<Utils::Otns>().EmitPingReply(aMessageInfo.GetPeerAddr(), reply.mSize, timestamp, reply.mHopLimit);
215 #endif
216 // Received all ping replies, no need to wait longer.
217 if (!mStatistics.mIsMulticast && mConfig.mCount == 0 && aIcmpHeader.GetSequence() == mTargetEchoSequence)
218 {
219 mTimer.Stop();
220 }
221 mConfig.InvokeReplyCallback(reply);
222 // Received all ping replies, no need to wait longer.
223 if (!mStatistics.mIsMulticast && mConfig.mCount == 0 && aIcmpHeader.GetSequence() == mTargetEchoSequence)
224 {
225 mConfig.InvokeStatisticsCallback(mStatistics);
226 }
227
228 exit:
229 return;
230 }
231
232 } // namespace Utils
233 } // namespace ot
234
235 #endif // #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
236