1 /*
2 * Copyright (c) 2024, 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 CLI interpreter for Ping Sender function.
32 */
33
34 #include "cli_ping.hpp"
35
36 #include <openthread/ping_sender.h>
37
38 #include "cli/cli.hpp"
39 #include "cli/cli_output.hpp"
40 #include "common/code_utils.hpp"
41
42 #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
43
44 namespace ot {
45 namespace Cli {
46
PingSender(otInstance * aInstance,OutputImplementer & aOutputImplementer)47 PingSender::PingSender(otInstance *aInstance, OutputImplementer &aOutputImplementer)
48 : Output(aInstance, aOutputImplementer)
49 , mPingIsAsync(false)
50 {
51 }
52
Process(Arg aArgs[])53 otError PingSender::Process(Arg aArgs[])
54 {
55 otError error = OT_ERROR_NONE;
56 otPingSenderConfig config;
57 bool async = false;
58 bool nat64SynthesizedAddress;
59
60 /**
61 * @cli ping stop
62 * @code
63 * ping stop
64 * Done
65 * @endcode
66 * @par
67 * Stop sending ICMPv6 Echo Requests.
68 * @sa otPingSenderStop
69 */
70 if (aArgs[0] == "stop")
71 {
72 otPingSenderStop(GetInstancePtr());
73 ExitNow();
74 }
75 else if (aArgs[0] == "async")
76 {
77 async = true;
78 aArgs++;
79 }
80
81 memset(&config, 0, sizeof(config));
82
83 if (aArgs[0] == "-I")
84 {
85 SuccessOrExit(error = aArgs[1].ParseAsIp6Address(config.mSource));
86
87 #if !OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
88 VerifyOrExit(otIp6HasUnicastAddress(GetInstancePtr(), &config.mSource), error = OT_ERROR_INVALID_ARGS);
89 #endif
90 aArgs += 2;
91 }
92
93 if (aArgs[0] == "-m")
94 {
95 config.mMulticastLoop = true;
96 aArgs++;
97 }
98
99 SuccessOrExit(error = Interpreter::GetInterpreter().ParseToIp6Address(
100 GetInstancePtr(), aArgs[0], config.mDestination, nat64SynthesizedAddress));
101 if (nat64SynthesizedAddress)
102 {
103 OutputFormat("Pinging synthesized IPv6 address: ");
104 OutputIp6AddressLine(config.mDestination);
105 }
106
107 if (!aArgs[1].IsEmpty())
108 {
109 SuccessOrExit(error = aArgs[1].ParseAsUint16(config.mSize));
110 }
111
112 if (!aArgs[2].IsEmpty())
113 {
114 SuccessOrExit(error = aArgs[2].ParseAsUint16(config.mCount));
115 }
116
117 if (!aArgs[3].IsEmpty())
118 {
119 SuccessOrExit(error = ParsePingInterval(aArgs[3], config.mInterval));
120 }
121
122 if (!aArgs[4].IsEmpty())
123 {
124 SuccessOrExit(error = aArgs[4].ParseAsUint8(config.mHopLimit));
125 config.mAllowZeroHopLimit = (config.mHopLimit == 0);
126 }
127
128 if (!aArgs[5].IsEmpty())
129 {
130 uint32_t timeout;
131
132 SuccessOrExit(error = ParsePingInterval(aArgs[5], timeout));
133 VerifyOrExit(timeout <= NumericLimits<uint16_t>::kMax, error = OT_ERROR_INVALID_ARGS);
134 config.mTimeout = static_cast<uint16_t>(timeout);
135 }
136
137 VerifyOrExit(aArgs[6].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
138
139 config.mReplyCallback = PingSender::HandlePingReply;
140 config.mStatisticsCallback = PingSender::HandlePingStatistics;
141 config.mCallbackContext = this;
142
143 SuccessOrExit(error = otPingSenderPing(GetInstancePtr(), &config));
144
145 mPingIsAsync = async;
146
147 if (!async)
148 {
149 error = OT_ERROR_PENDING;
150 }
151
152 exit:
153 return error;
154 }
155
ParsePingInterval(const Arg & aArg,uint32_t & aInterval)156 otError PingSender::ParsePingInterval(const Arg &aArg, uint32_t &aInterval)
157 {
158 otError error = OT_ERROR_NONE;
159 const char *string = aArg.GetCString();
160 const uint32_t msFactor = 1000;
161 uint32_t factor = msFactor;
162
163 aInterval = 0;
164
165 while (*string)
166 {
167 if ('0' <= *string && *string <= '9')
168 {
169 // In the case of seconds, change the base of already calculated value.
170 if (factor == msFactor)
171 {
172 aInterval *= 10;
173 }
174
175 aInterval += static_cast<uint32_t>(*string - '0') * factor;
176
177 // In the case of milliseconds, change the multiplier factor.
178 if (factor != msFactor)
179 {
180 factor /= 10;
181 }
182 }
183 else if (*string == '.')
184 {
185 // Accept only one dot character.
186 VerifyOrExit(factor == msFactor, error = OT_ERROR_INVALID_ARGS);
187
188 // Start analyzing hundreds of milliseconds.
189 factor /= 10;
190 }
191 else
192 {
193 ExitNow(error = OT_ERROR_INVALID_ARGS);
194 }
195
196 string++;
197 }
198
199 exit:
200 return error;
201 }
202
HandlePingReply(const otPingSenderReply * aReply,void * aContext)203 void PingSender::HandlePingReply(const otPingSenderReply *aReply, void *aContext)
204 {
205 static_cast<PingSender *>(aContext)->HandlePingReply(aReply);
206 }
207
HandlePingReply(const otPingSenderReply * aReply)208 void PingSender::HandlePingReply(const otPingSenderReply *aReply)
209 {
210 OutputFormat("%u bytes from ", static_cast<uint16_t>(aReply->mSize + sizeof(otIcmp6Header)));
211 OutputIp6Address(aReply->mSenderAddress);
212 OutputLine(": icmp_seq=%u hlim=%u time=%ums", aReply->mSequenceNumber, aReply->mHopLimit, aReply->mRoundTripTime);
213 }
214
HandlePingStatistics(const otPingSenderStatistics * aStatistics,void * aContext)215 void PingSender::HandlePingStatistics(const otPingSenderStatistics *aStatistics, void *aContext)
216 {
217 static_cast<PingSender *>(aContext)->HandlePingStatistics(aStatistics);
218 }
219
HandlePingStatistics(const otPingSenderStatistics * aStatistics)220 void PingSender::HandlePingStatistics(const otPingSenderStatistics *aStatistics)
221 {
222 OutputFormat("%u packets transmitted, %u packets received.", aStatistics->mSentCount, aStatistics->mReceivedCount);
223
224 if ((aStatistics->mSentCount != 0) && !aStatistics->mIsMulticast &&
225 aStatistics->mReceivedCount <= aStatistics->mSentCount)
226 {
227 uint32_t packetLossRate =
228 1000 * (aStatistics->mSentCount - aStatistics->mReceivedCount) / aStatistics->mSentCount;
229
230 OutputFormat(" Packet loss = %lu.%u%%.", ToUlong(packetLossRate / 10),
231 static_cast<uint16_t>(packetLossRate % 10));
232 }
233
234 if (aStatistics->mReceivedCount != 0)
235 {
236 uint32_t avgRoundTripTime = 1000 * aStatistics->mTotalRoundTripTime / aStatistics->mReceivedCount;
237
238 OutputFormat(" Round-trip min/avg/max = %u/%u.%u/%u ms.", aStatistics->mMinRoundTripTime,
239 static_cast<uint16_t>(avgRoundTripTime / 1000), static_cast<uint16_t>(avgRoundTripTime % 1000),
240 aStatistics->mMaxRoundTripTime);
241 }
242
243 OutputNewLine();
244
245 if (!mPingIsAsync)
246 {
247 OutputResult(OT_ERROR_NONE);
248 }
249 }
250
OutputResult(otError aError)251 void PingSender::OutputResult(otError aError) { Interpreter::GetInterpreter().OutputResult(aError); }
252
253 } // namespace Cli
254 } // namespace ot
255
256 #endif // OPENTHREAD_CONFIG_PING_SENDER_ENABLE
257