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