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 a TCP CLI tool.
32 */
33
34 #include "openthread-core-config.h"
35
36 #include "cli_config.h"
37
38 #if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE
39
40 #include "cli_tcp.hpp"
41
42 #include <openthread/tcp.h>
43
44 #include "cli/cli.hpp"
45 #include "common/encoding.hpp"
46 #include "common/timer.hpp"
47
48 namespace ot {
49 namespace Cli {
50
51 constexpr TcpExample::Command TcpExample::sCommands[];
52
TcpExample(Output & aOutput)53 TcpExample::TcpExample(Output &aOutput)
54 : OutputWrapper(aOutput)
55 , mInitialized(false)
56 , mEndpointConnected(false)
57 , mSendBusy(false)
58 , mBenchmarkBytesTotal(0)
59 , mBenchmarkLinksLeft(0)
60 {
61 }
62
ProcessHelp(Arg aArgs[])63 otError TcpExample::ProcessHelp(Arg aArgs[])
64 {
65 OT_UNUSED_VARIABLE(aArgs);
66
67 for (const Command &command : sCommands)
68 {
69 OutputLine(command.mName);
70 }
71
72 return OT_ERROR_NONE;
73 }
74
ProcessInit(Arg aArgs[])75 otError TcpExample::ProcessInit(Arg aArgs[])
76 {
77 otError error = OT_ERROR_NONE;
78 size_t receiveBufferSize;
79
80 VerifyOrExit(!mInitialized, error = OT_ERROR_ALREADY);
81
82 if (aArgs[0].IsEmpty())
83 {
84 receiveBufferSize = sizeof(mReceiveBuffer);
85 }
86 else
87 {
88 uint32_t windowSize;
89
90 SuccessOrExit(error = aArgs[0].ParseAsUint32(windowSize));
91 VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
92
93 receiveBufferSize = windowSize + ((windowSize + 7) >> 3);
94 VerifyOrExit(receiveBufferSize <= sizeof(mReceiveBuffer) && receiveBufferSize != 0,
95 error = OT_ERROR_INVALID_ARGS);
96 }
97
98 {
99 otTcpEndpointInitializeArgs endpointArgs;
100
101 memset(&endpointArgs, 0x00, sizeof(endpointArgs));
102 endpointArgs.mEstablishedCallback = HandleTcpEstablishedCallback;
103 endpointArgs.mSendDoneCallback = HandleTcpSendDoneCallback;
104 endpointArgs.mReceiveAvailableCallback = HandleTcpReceiveAvailableCallback;
105 endpointArgs.mDisconnectedCallback = HandleTcpDisconnectedCallback;
106 endpointArgs.mContext = this;
107 endpointArgs.mReceiveBuffer = mReceiveBuffer;
108 endpointArgs.mReceiveBufferSize = receiveBufferSize;
109
110 SuccessOrExit(error = otTcpEndpointInitialize(GetInstancePtr(), &mEndpoint, &endpointArgs));
111 }
112
113 {
114 otTcpListenerInitializeArgs listenerArgs;
115
116 memset(&listenerArgs, 0x00, sizeof(listenerArgs));
117 listenerArgs.mAcceptReadyCallback = HandleTcpAcceptReadyCallback;
118 listenerArgs.mAcceptDoneCallback = HandleTcpAcceptDoneCallback;
119 listenerArgs.mContext = this;
120
121 error = otTcpListenerInitialize(GetInstancePtr(), &mListener, &listenerArgs);
122 if (error != OT_ERROR_NONE)
123 {
124 IgnoreReturnValue(otTcpEndpointDeinitialize(&mEndpoint));
125 ExitNow();
126 }
127 }
128
129 mInitialized = true;
130
131 exit:
132 return error;
133 }
134
ProcessDeinit(Arg aArgs[])135 otError TcpExample::ProcessDeinit(Arg aArgs[])
136 {
137 otError error = OT_ERROR_NONE;
138 otError endpointError;
139 otError listenerError;
140
141 VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
142 VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE);
143
144 endpointError = otTcpEndpointDeinitialize(&mEndpoint);
145 mSendBusy = false;
146
147 listenerError = otTcpListenerDeinitialize(&mListener);
148 mInitialized = false;
149
150 SuccessOrExit(error = endpointError);
151 SuccessOrExit(error = listenerError);
152
153 exit:
154 return error;
155 }
156
ProcessBind(Arg aArgs[])157 otError TcpExample::ProcessBind(Arg aArgs[])
158 {
159 otError error;
160 otSockAddr sockaddr;
161
162 VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE);
163
164 SuccessOrExit(error = aArgs[0].ParseAsIp6Address(sockaddr.mAddress));
165 SuccessOrExit(error = aArgs[1].ParseAsUint16(sockaddr.mPort));
166 VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
167
168 error = otTcpBind(&mEndpoint, &sockaddr);
169
170 exit:
171 return error;
172 }
173
ProcessConnect(Arg aArgs[])174 otError TcpExample::ProcessConnect(Arg aArgs[])
175 {
176 otError error;
177 otSockAddr sockaddr;
178
179 VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE);
180
181 SuccessOrExit(error = aArgs[0].ParseAsIp6Address(sockaddr.mAddress));
182 SuccessOrExit(error = aArgs[1].ParseAsUint16(sockaddr.mPort));
183 VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
184
185 SuccessOrExit(error = otTcpConnect(&mEndpoint, &sockaddr, OT_TCP_CONNECT_NO_FAST_OPEN));
186 mEndpointConnected = false;
187
188 exit:
189 return error;
190 }
191
ProcessSend(Arg aArgs[])192 otError TcpExample::ProcessSend(Arg aArgs[])
193 {
194 otError error;
195
196 VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE);
197 VerifyOrExit(!mSendBusy, error = OT_ERROR_BUSY);
198 VerifyOrExit(mBenchmarkBytesTotal == 0, error = OT_ERROR_BUSY);
199
200 mSendLink.mNext = nullptr;
201 mSendLink.mData = mSendBuffer;
202 VerifyOrExit(!aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
203 mSendLink.mLength = OT_MIN(aArgs[0].GetLength(), sizeof(mSendBuffer));
204 memcpy(mSendBuffer, aArgs[0].GetCString(), mSendLink.mLength);
205 VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
206
207 SuccessOrExit(error = otTcpSendByReference(&mEndpoint, &mSendLink, 0));
208 mSendBusy = true;
209
210 exit:
211 return error;
212 }
213
ProcessBenchmark(Arg aArgs[])214 otError TcpExample::ProcessBenchmark(Arg aArgs[])
215 {
216 otError error = OT_ERROR_NONE;
217 uint32_t toSendOut;
218
219 VerifyOrExit(!mSendBusy, error = OT_ERROR_BUSY);
220 VerifyOrExit(mBenchmarkBytesTotal == 0, error = OT_ERROR_BUSY);
221
222 if (aArgs[0].IsEmpty())
223 {
224 mBenchmarkBytesTotal = OPENTHREAD_CONFIG_CLI_TCP_DEFAULT_BENCHMARK_SIZE;
225 }
226 else
227 {
228 SuccessOrExit(error = aArgs[0].ParseAsUint32(mBenchmarkBytesTotal));
229 VerifyOrExit(mBenchmarkBytesTotal != 0, error = OT_ERROR_INVALID_ARGS);
230 }
231 VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
232
233 memset(mSendBuffer, 'a', sizeof(mSendBuffer));
234
235 mBenchmarkLinksLeft = (mBenchmarkBytesTotal + sizeof(mSendBuffer) - 1) / sizeof(mSendBuffer);
236 toSendOut = OT_MIN(OT_ARRAY_LENGTH(mBenchmarkLinks), mBenchmarkLinksLeft);
237 mBenchmarkStart = TimerMilli::GetNow();
238 for (uint32_t i = 0; i != toSendOut; i++)
239 {
240 mBenchmarkLinks[i].mNext = nullptr;
241 mBenchmarkLinks[i].mData = mSendBuffer;
242 mBenchmarkLinks[i].mLength = sizeof(mSendBuffer);
243 if (i == 0 && mBenchmarkBytesTotal % sizeof(mSendBuffer) != 0)
244 {
245 mBenchmarkLinks[i].mLength = mBenchmarkBytesTotal % sizeof(mSendBuffer);
246 }
247 SuccessOrExit(error = otTcpSendByReference(&mEndpoint, &mBenchmarkLinks[i],
248 i == toSendOut - 1 ? 0 : OT_TCP_SEND_MORE_TO_COME));
249 }
250
251 exit:
252 if (error != OT_ERROR_NONE)
253 {
254 mBenchmarkBytesTotal = 0;
255 mBenchmarkLinksLeft = 0;
256 }
257 return error;
258 }
259
ProcessSendEnd(Arg aArgs[])260 otError TcpExample::ProcessSendEnd(Arg aArgs[])
261 {
262 otError error;
263
264 VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE);
265 VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
266
267 error = otTcpSendEndOfStream(&mEndpoint);
268
269 exit:
270 return error;
271 }
272
ProcessAbort(Arg aArgs[])273 otError TcpExample::ProcessAbort(Arg aArgs[])
274 {
275 otError error;
276
277 VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
278 VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE);
279
280 SuccessOrExit(error = otTcpAbort(&mEndpoint));
281 mEndpointConnected = false;
282
283 exit:
284 return error;
285 }
286
ProcessListen(Arg aArgs[])287 otError TcpExample::ProcessListen(Arg aArgs[])
288 {
289 otError error;
290 otSockAddr sockaddr;
291
292 VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE);
293
294 SuccessOrExit(error = aArgs[0].ParseAsIp6Address(sockaddr.mAddress));
295 SuccessOrExit(error = aArgs[1].ParseAsUint16(sockaddr.mPort));
296 VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
297
298 SuccessOrExit(error = otTcpStopListening(&mListener));
299 error = otTcpListen(&mListener, &sockaddr);
300
301 exit:
302 return error;
303 }
304
ProcessStopListening(Arg aArgs[])305 otError TcpExample::ProcessStopListening(Arg aArgs[])
306 {
307 otError error;
308
309 VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
310 VerifyOrExit(mInitialized, error = OT_ERROR_INVALID_STATE);
311
312 error = otTcpStopListening(&mListener);
313
314 exit:
315 return error;
316 }
317
Process(Arg aArgs[])318 otError TcpExample::Process(Arg aArgs[])
319 {
320 otError error = OT_ERROR_INVALID_ARGS;
321 const Command *command;
322
323 VerifyOrExit(!aArgs[0].IsEmpty(), IgnoreError(ProcessHelp(nullptr)));
324
325 command = BinarySearch::Find(aArgs[0].GetCString(), sCommands);
326 VerifyOrExit(command != nullptr, error = OT_ERROR_INVALID_COMMAND);
327
328 error = (this->*command->mHandler)(aArgs + 1);
329
330 exit:
331 return error;
332 }
333
HandleTcpEstablishedCallback(otTcpEndpoint * aEndpoint)334 void TcpExample::HandleTcpEstablishedCallback(otTcpEndpoint *aEndpoint)
335 {
336 static_cast<TcpExample *>(otTcpEndpointGetContext(aEndpoint))->HandleTcpEstablished(aEndpoint);
337 }
338
HandleTcpSendDoneCallback(otTcpEndpoint * aEndpoint,otLinkedBuffer * aData)339 void TcpExample::HandleTcpSendDoneCallback(otTcpEndpoint *aEndpoint, otLinkedBuffer *aData)
340 {
341 static_cast<TcpExample *>(otTcpEndpointGetContext(aEndpoint))->HandleTcpSendDone(aEndpoint, aData);
342 }
343
HandleTcpReceiveAvailableCallback(otTcpEndpoint * aEndpoint,size_t aBytesAvailable,bool aEndOfStream,size_t aBytesRemaining)344 void TcpExample::HandleTcpReceiveAvailableCallback(otTcpEndpoint *aEndpoint,
345 size_t aBytesAvailable,
346 bool aEndOfStream,
347 size_t aBytesRemaining)
348 {
349 static_cast<TcpExample *>(otTcpEndpointGetContext(aEndpoint))
350 ->HandleTcpReceiveAvailable(aEndpoint, aBytesAvailable, aEndOfStream, aBytesRemaining);
351 }
352
HandleTcpDisconnectedCallback(otTcpEndpoint * aEndpoint,otTcpDisconnectedReason aReason)353 void TcpExample::HandleTcpDisconnectedCallback(otTcpEndpoint *aEndpoint, otTcpDisconnectedReason aReason)
354 {
355 static_cast<TcpExample *>(otTcpEndpointGetContext(aEndpoint))->HandleTcpDisconnected(aEndpoint, aReason);
356 }
357
HandleTcpAcceptReadyCallback(otTcpListener * aListener,const otSockAddr * aPeer,otTcpEndpoint ** aAcceptInto)358 otTcpIncomingConnectionAction TcpExample::HandleTcpAcceptReadyCallback(otTcpListener * aListener,
359 const otSockAddr *aPeer,
360 otTcpEndpoint ** aAcceptInto)
361 {
362 return static_cast<TcpExample *>(otTcpListenerGetContext(aListener))
363 ->HandleTcpAcceptReady(aListener, aPeer, aAcceptInto);
364 }
365
HandleTcpAcceptDoneCallback(otTcpListener * aListener,otTcpEndpoint * aEndpoint,const otSockAddr * aPeer)366 void TcpExample::HandleTcpAcceptDoneCallback(otTcpListener * aListener,
367 otTcpEndpoint * aEndpoint,
368 const otSockAddr *aPeer)
369 {
370 static_cast<TcpExample *>(otTcpListenerGetContext(aListener))->HandleTcpAcceptDone(aListener, aEndpoint, aPeer);
371 }
372
HandleTcpEstablished(otTcpEndpoint * aEndpoint)373 void TcpExample::HandleTcpEstablished(otTcpEndpoint *aEndpoint)
374 {
375 OT_UNUSED_VARIABLE(aEndpoint);
376 OutputLine("TCP: Connection established");
377 }
378
HandleTcpSendDone(otTcpEndpoint * aEndpoint,otLinkedBuffer * aData)379 void TcpExample::HandleTcpSendDone(otTcpEndpoint *aEndpoint, otLinkedBuffer *aData)
380 {
381 OT_UNUSED_VARIABLE(aEndpoint);
382
383 if (mBenchmarkBytesTotal == 0)
384 {
385 // If the benchmark encountered an error, we might end up here. So,
386 // tolerate some benchmark links finishing in this case.
387 if (aData == &mSendLink)
388 {
389 OT_ASSERT(mSendBusy);
390 mSendBusy = false;
391 }
392 }
393 else
394 {
395 OT_ASSERT(aData != &mSendLink);
396 mBenchmarkLinksLeft--;
397 if (mBenchmarkLinksLeft >= OT_ARRAY_LENGTH(mBenchmarkLinks))
398 {
399 aData->mLength = sizeof(mSendBuffer);
400 if (otTcpSendByReference(&mEndpoint, aData, 0) != OT_ERROR_NONE)
401 {
402 OutputLine("TCP Benchmark Failed");
403 mBenchmarkBytesTotal = 0;
404 }
405 }
406 else if (mBenchmarkLinksLeft == 0)
407 {
408 uint32_t milliseconds = TimerMilli::GetNow() - mBenchmarkStart;
409 uint32_t thousandTimesGoodput = (1000 * (mBenchmarkBytesTotal << 3) + (milliseconds >> 1)) / milliseconds;
410
411 OutputLine("TCP Benchmark Complete: Transferred %u bytes in %u milliseconds",
412 static_cast<unsigned int>(mBenchmarkBytesTotal), static_cast<unsigned int>(milliseconds));
413 OutputLine("TCP Goodput: %u.%03u kb/s", thousandTimesGoodput / 1000, thousandTimesGoodput % 1000);
414 mBenchmarkBytesTotal = 0;
415 }
416 }
417 }
418
HandleTcpReceiveAvailable(otTcpEndpoint * aEndpoint,size_t aBytesAvailable,bool aEndOfStream,size_t aBytesRemaining)419 void TcpExample::HandleTcpReceiveAvailable(otTcpEndpoint *aEndpoint,
420 size_t aBytesAvailable,
421 bool aEndOfStream,
422 size_t aBytesRemaining)
423 {
424 OT_UNUSED_VARIABLE(aBytesRemaining);
425 OT_ASSERT(aEndpoint == &mEndpoint);
426
427 if (aBytesAvailable > 0)
428 {
429 const otLinkedBuffer *data;
430 size_t totalReceived = 0;
431
432 IgnoreError(otTcpReceiveByReference(aEndpoint, &data));
433 for (; data != nullptr; data = data->mNext)
434 {
435 OutputLine("TCP: Received %u bytes: %.*s", static_cast<unsigned int>(data->mLength), data->mLength,
436 reinterpret_cast<const char *>(data->mData));
437 totalReceived += data->mLength;
438 }
439 OT_ASSERT(aBytesAvailable == totalReceived);
440 IgnoreReturnValue(otTcpCommitReceive(aEndpoint, totalReceived, 0));
441 }
442
443 if (aEndOfStream)
444 {
445 OutputLine("TCP: Reached end of stream");
446 }
447 }
448
HandleTcpDisconnected(otTcpEndpoint * aEndpoint,otTcpDisconnectedReason aReason)449 void TcpExample::HandleTcpDisconnected(otTcpEndpoint *aEndpoint, otTcpDisconnectedReason aReason)
450 {
451 static const char *const kReasonStrings[] = {
452 "Disconnected", // (0) OT_TCP_DISCONNECTED_REASON_NORMAL
453 "Connection refused", // (1) OT_TCP_DISCONNECTED_REASON_REFUSED
454 "Connection reset", // (2) OT_TCP_DISCONNECTED_REASON_RESET
455 "Entered TIME-WAIT state", // (3) OT_TCP_DISCONNECTED_REASON_TIME_WAIT
456 "Connection timed out", // (4) OT_TCP_DISCONNECTED_REASON_TIMED_OUT
457 };
458
459 OT_UNUSED_VARIABLE(aEndpoint);
460
461 static_assert(0 == OT_TCP_DISCONNECTED_REASON_NORMAL, "OT_TCP_DISCONNECTED_REASON_NORMAL value is incorrect");
462 static_assert(1 == OT_TCP_DISCONNECTED_REASON_REFUSED, "OT_TCP_DISCONNECTED_REASON_REFUSED value is incorrect");
463 static_assert(2 == OT_TCP_DISCONNECTED_REASON_RESET, "OT_TCP_DISCONNECTED_REASON_RESET value is incorrect");
464 static_assert(3 == OT_TCP_DISCONNECTED_REASON_TIME_WAIT, "OT_TCP_DISCONNECTED_REASON_TIME_WAIT value is incorrect");
465 static_assert(4 == OT_TCP_DISCONNECTED_REASON_TIMED_OUT, "OT_TCP_DISCONNECTED_REASON_TIMED_OUT value is incorrect");
466
467 OutputLine("TCP: %s", Stringify(aReason, kReasonStrings));
468
469 // We set this to false even for the TIME-WAIT state, so that we can reuse
470 // the active socket if an incoming connection comes in instead of waiting
471 // for the 2MSL timeout.
472 mEndpointConnected = false;
473 mSendBusy = false;
474
475 // Mark the benchmark as inactive if the connection was disconnected.
476 if (mBenchmarkBytesTotal != 0)
477 {
478 mBenchmarkBytesTotal = 0;
479 mBenchmarkLinksLeft = 0;
480 }
481 }
482
HandleTcpAcceptReady(otTcpListener * aListener,const otSockAddr * aPeer,otTcpEndpoint ** aAcceptInto)483 otTcpIncomingConnectionAction TcpExample::HandleTcpAcceptReady(otTcpListener * aListener,
484 const otSockAddr *aPeer,
485 otTcpEndpoint ** aAcceptInto)
486 {
487 OT_UNUSED_VARIABLE(aListener);
488
489 if (mEndpointConnected)
490 {
491 OutputFormat("TCP: Ignoring incoming connection request from ");
492 OutputSockAddr(*aPeer);
493 OutputLine(" (active socket is busy)");
494
495 return OT_TCP_INCOMING_CONNECTION_ACTION_DEFER;
496 }
497
498 *aAcceptInto = &mEndpoint;
499 return OT_TCP_INCOMING_CONNECTION_ACTION_ACCEPT;
500 }
501
HandleTcpAcceptDone(otTcpListener * aListener,otTcpEndpoint * aEndpoint,const otSockAddr * aPeer)502 void TcpExample::HandleTcpAcceptDone(otTcpListener *aListener, otTcpEndpoint *aEndpoint, const otSockAddr *aPeer)
503 {
504 OT_UNUSED_VARIABLE(aListener);
505 OT_UNUSED_VARIABLE(aEndpoint);
506
507 OutputFormat("Accepted connection from ");
508 OutputSockAddrLine(*aPeer);
509 }
510
511 } // namespace Cli
512 } // namespace ot
513
514 #endif // OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE
515