| /* |
| * |
| * Copyright (c) 2021 Project CHIP Authors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <errno.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| #include <lib/core/CHIPCore.h> |
| #include <lib/shell/Engine.h> |
| #include <lib/support/CodeUtils.h> |
| #include <lib/support/ErrorStr.h> |
| #include <messaging/ExchangeMgr.h> |
| #include <platform/CHIPDeviceLayer.h> |
| #include <protocols/echo/Echo.h> |
| #include <protocols/secure_channel/MessageCounterManager.h> |
| #include <protocols/secure_channel/PASESession.h> |
| #include <system/SystemPacketBuffer.h> |
| #include <transport/SessionManager.h> |
| #include <transport/raw/TCP.h> |
| #include <transport/raw/UDP.h> |
| |
| #include <ChipShellCollection.h> |
| #include <Globals.h> |
| |
| using namespace chip; |
| using namespace Shell; |
| using namespace Logging; |
| using chip::Inet::IPAddress; |
| |
| namespace { |
| |
| class PingArguments |
| { |
| public: |
| void Reset() |
| { |
| mMaxEchoCount = 3; |
| mEchoInterval = 1000; |
| mLastEchoTime = System::Clock::kZero; |
| mEchoCount = 0; |
| mEchoRespCount = 0; |
| mPayloadSize = 32; |
| mWaitingForEchoResp = false; |
| #if INET_CONFIG_ENABLE_TCP_ENDPOINT |
| mUsingTCP = false; |
| #endif |
| mUsingMRP = true; |
| mEchoPort = CHIP_PORT; |
| } |
| |
| System::Clock::Timestamp GetLastEchoTime() const { return mLastEchoTime; } |
| void SetLastEchoTime(System::Clock::Timestamp value) { mLastEchoTime = value; } |
| |
| uint64_t GetEchoCount() const { return mEchoCount; } |
| void SetEchoCount(uint64_t value) { mEchoCount = value; } |
| void IncrementEchoCount() { mEchoCount++; } |
| |
| uint64_t GetEchoRespCount() const { return mEchoRespCount; } |
| void SetEchoRespCount(uint64_t value) { mEchoRespCount = value; } |
| void IncrementEchoRespCount() { mEchoRespCount++; } |
| |
| uint32_t GetMaxEchoCount() const { return mMaxEchoCount; } |
| void SetMaxEchoCount(uint32_t id) { mMaxEchoCount = id; } |
| |
| uint32_t GetEchoInterval() const { return mEchoInterval; } |
| void SetEchoInterval(uint32_t value) { mEchoInterval = value; } |
| |
| uint32_t GetPayloadSize() const { return mPayloadSize; } |
| void SetPayloadSize(uint32_t value) { mPayloadSize = value; } |
| |
| uint16_t GetEchoPort() const { return mEchoPort; } |
| void SetEchoPort(uint16_t value) { mEchoPort = value; } |
| |
| bool IsWaitingForEchoResp() const { return mWaitingForEchoResp; } |
| void SetWaitingForEchoResp(bool value) { mWaitingForEchoResp = value; } |
| |
| #if INET_CONFIG_ENABLE_TCP_ENDPOINT |
| bool IsUsingTCP() const { return mUsingTCP; } |
| void SetUsingTCP(bool value) { mUsingTCP = value; } |
| #endif |
| |
| bool IsUsingMRP() const { return mUsingMRP; } |
| void SetUsingMRP(bool value) { mUsingMRP = value; } |
| |
| private: |
| // The last time a echo request was attempted to be sent. |
| System::Clock::Timestamp mLastEchoTime; |
| |
| // Count of the number of echo requests sent. |
| uint64_t mEchoCount; |
| |
| // Count of the number of echo responses received. |
| uint64_t mEchoRespCount; |
| |
| // The CHIP Echo request payload size in bytes. |
| uint32_t mPayloadSize; |
| |
| // Max value for the number of echo requests sent. |
| uint32_t mMaxEchoCount; |
| |
| // The CHIP Echo interval time in milliseconds. |
| uint32_t mEchoInterval; |
| |
| uint16_t mEchoPort; |
| |
| // True, if the echo client is waiting for an echo response |
| // after sending an echo request, false otherwise. |
| bool mWaitingForEchoResp; |
| |
| #if INET_CONFIG_ENABLE_TCP_ENDPOINT |
| bool mUsingTCP; |
| #endif |
| |
| bool mUsingMRP; |
| } gPingArguments; |
| |
| Protocols::Echo::EchoClient gEchoClient; |
| |
| CHIP_ERROR SendEchoRequest(streamer_t * stream); |
| void EchoTimerHandler(chip::System::Layer * systemLayer, void * appState); |
| |
| Transport::PeerAddress GetEchoPeerAddress() |
| { |
| #if INET_CONFIG_ENABLE_TCP_ENDPOINT |
| if (gPingArguments.IsUsingTCP()) |
| { |
| return Transport::PeerAddress::TCP(gDestAddr, gPingArguments.GetEchoPort()); |
| } |
| else |
| #endif |
| { |
| |
| return Transport::PeerAddress::UDP(gDestAddr, gPingArguments.GetEchoPort(), ::chip::Inet::InterfaceId::Null()); |
| } |
| } |
| |
| void Shutdown() |
| { |
| chip::DeviceLayer::SystemLayer().CancelTimer(EchoTimerHandler, NULL); |
| #if INET_CONFIG_ENABLE_TCP_ENDPOINT |
| if (gPingArguments.IsUsingTCP()) |
| { |
| gTCPManager.Disconnect(GetEchoPeerAddress()); |
| } |
| gTCPManager.Close(); |
| #endif |
| gUDPManager.Close(); |
| |
| gEchoClient.Shutdown(); |
| gExchangeManager.Shutdown(); |
| gSessionManager.Shutdown(); |
| } |
| |
| void EchoTimerHandler(chip::System::Layer * systemLayer, void * appState) |
| { |
| if (gPingArguments.GetEchoRespCount() != gPingArguments.GetEchoCount()) |
| { |
| streamer_printf(streamer_get(), "No response received\n"); |
| gPingArguments.SetEchoRespCount(gPingArguments.GetEchoCount()); |
| } |
| if (gPingArguments.GetEchoCount() < gPingArguments.GetMaxEchoCount()) |
| { |
| CHIP_ERROR err = SendEchoRequest(streamer_get()); |
| if (err != CHIP_NO_ERROR) |
| { |
| streamer_printf(streamer_get(), "Send request failed: %s\n", ErrorStr(err)); |
| Shutdown(); |
| } |
| } |
| else |
| { |
| Shutdown(); |
| } |
| } |
| |
| CHIP_ERROR SendEchoRequest(streamer_t * stream) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| Messaging::SendFlags sendFlags; |
| System::PacketBufferHandle payloadBuf; |
| uint32_t payloadSize = gPingArguments.GetPayloadSize(); |
| |
| payloadBuf = MessagePacketBuffer::New(payloadSize); |
| VerifyOrExit(!payloadBuf.IsNull(), err = CHIP_ERROR_NO_MEMORY); |
| |
| memset(payloadBuf->Start(), 0, payloadSize); |
| payloadBuf->SetDataLength(payloadSize); |
| |
| if (gPingArguments.IsUsingMRP()) |
| { |
| sendFlags.Set(Messaging::SendMessageFlags::kNone); |
| } |
| else |
| { |
| sendFlags.Set(Messaging::SendMessageFlags::kNoAutoRequestAck); |
| } |
| |
| gPingArguments.SetLastEchoTime(System::SystemClock().GetMonotonicTimestamp()); |
| SuccessOrExit(chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Milliseconds32(gPingArguments.GetEchoInterval()), |
| EchoTimerHandler, NULL)); |
| |
| streamer_printf(stream, "\nSend echo request message with payload size: %d bytes to Node: %" PRIu64 "\n", payloadSize, |
| kTestDeviceNodeId); |
| |
| err = gEchoClient.SendEchoRequest(std::move(payloadBuf), sendFlags); |
| |
| if (err == CHIP_NO_ERROR) |
| { |
| gPingArguments.SetWaitingForEchoResp(true); |
| gPingArguments.IncrementEchoCount(); |
| } |
| else |
| { |
| chip::DeviceLayer::SystemLayer().CancelTimer(EchoTimerHandler, NULL); |
| } |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| streamer_printf(stream, "Send echo request failed, err: %s\n", ErrorStr(err)); |
| } |
| |
| return err; |
| } |
| |
| CHIP_ERROR EstablishSecureSession(streamer_t * stream, const Transport::PeerAddress & peerAddress) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| Optional<Transport::PeerAddress> peerAddr; |
| SecurePairingUsingTestSecret * testSecurePairingSecret = chip::Platform::New<SecurePairingUsingTestSecret>(); |
| VerifyOrExit(testSecurePairingSecret != nullptr, err = CHIP_ERROR_NO_MEMORY); |
| |
| peerAddr = Optional<Transport::PeerAddress>::Value(peerAddress); |
| |
| // Attempt to connect to the peer. |
| err = gSessionManager.NewPairing(gSession, peerAddr, kTestDeviceNodeId, testSecurePairingSecret, |
| CryptoContext::SessionRole::kInitiator, gFabricIndex); |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| streamer_printf(stream, "Establish secure session failed, err: %s\n", ErrorStr(err)); |
| gPingArguments.SetLastEchoTime(System::SystemClock().GetMonotonicTimestamp()); |
| } |
| else |
| { |
| streamer_printf(stream, "Establish secure session succeeded\n"); |
| } |
| |
| return err; |
| } |
| |
| void HandleEchoResponseReceived(Messaging::ExchangeContext * ec, System::PacketBufferHandle && payload) |
| { |
| System::Clock::Timestamp respTime = System::SystemClock().GetMonotonicTimestamp(); |
| System::Clock::Milliseconds64 transitTime = respTime - gPingArguments.GetLastEchoTime(); |
| streamer_t * sout = streamer_get(); |
| |
| gPingArguments.SetWaitingForEchoResp(false); |
| gPingArguments.IncrementEchoRespCount(); |
| |
| streamer_printf(sout, "Echo Response: %" PRIu64 "/%" PRIu64 "(%.2f%%) len=%u time=%.3fs\n", gPingArguments.GetEchoRespCount(), |
| gPingArguments.GetEchoCount(), |
| static_cast<double>(gPingArguments.GetEchoRespCount()) * 100 / gPingArguments.GetEchoCount(), |
| payload->DataLength(), static_cast<double>(transitTime.count()) / 1000); |
| } |
| |
| void StartPinging(streamer_t * stream, char * destination) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| if (!IPAddress::FromString(destination, gDestAddr)) |
| { |
| streamer_printf(stream, "Invalid Echo Server IP address: %s\n", destination); |
| ExitNow(err = CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| |
| err = gFabricTable.Init(&gStorage); |
| SuccessOrExit(err); |
| |
| #if INET_CONFIG_ENABLE_TCP_ENDPOINT |
| err = gTCPManager.Init(Transport::TcpListenParameters(DeviceLayer::TCPEndPointManager()) |
| .SetAddressType(gDestAddr.Type()) |
| .SetListenPort(gPingArguments.GetEchoPort() + 1)); |
| VerifyOrExit(err == CHIP_NO_ERROR, streamer_printf(stream, "Failed to init TCP manager error: %s\n", ErrorStr(err))); |
| #endif |
| |
| err = gUDPManager.Init(Transport::UdpListenParameters(DeviceLayer::UDPEndPointManager()) |
| .SetAddressType(gDestAddr.Type()) |
| .SetListenPort(gPingArguments.GetEchoPort() + 1)); |
| VerifyOrExit(err == CHIP_NO_ERROR, streamer_printf(stream, "Failed to init UDP manager error: %s\n", ErrorStr(err))); |
| |
| #if INET_CONFIG_ENABLE_TCP_ENDPOINT |
| if (gPingArguments.IsUsingTCP()) |
| { |
| err = gSessionManager.Init(&DeviceLayer::SystemLayer(), &gTCPManager, &gMessageCounterManager, &gStorage, &gFabricTable); |
| SuccessOrExit(err); |
| |
| err = gExchangeManager.Init(&gSessionManager); |
| SuccessOrExit(err); |
| } |
| else |
| #endif |
| { |
| err = gSessionManager.Init(&DeviceLayer::SystemLayer(), &gUDPManager, &gMessageCounterManager, &gStorage, &gFabricTable); |
| SuccessOrExit(err); |
| |
| err = gExchangeManager.Init(&gSessionManager); |
| SuccessOrExit(err); |
| } |
| |
| err = gMessageCounterManager.Init(&gExchangeManager); |
| SuccessOrExit(err); |
| |
| // Start the CHIP connection to the CHIP echo responder. |
| err = EstablishSecureSession(stream, GetEchoPeerAddress()); |
| SuccessOrExit(err); |
| |
| err = gEchoClient.Init(&gExchangeManager, gSession.Get()); |
| SuccessOrExit(err); |
| |
| // Arrange to get a callback whenever an Echo Response is received. |
| gEchoClient.SetEchoResponseReceived(HandleEchoResponseReceived); |
| |
| err = SendEchoRequest(stream); |
| if (err != CHIP_NO_ERROR) |
| { |
| streamer_printf(stream, "Send request failed: %s\n", ErrorStr(err)); |
| } |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| streamer_printf(stream, "Ping failed with error: %s\n", ErrorStr(err)); |
| Shutdown(); |
| } |
| } |
| |
| void PrintUsage(streamer_t * stream) |
| { |
| streamer_printf(stream, "Usage: ping [options] <destination>\n\nOptions:\n"); |
| |
| // Need to split the help info to prevent overflowing the streamer_printf |
| // buffer (CONSOLE_DEFAULT_MAX_LINE 256) |
| streamer_printf(stream, " -h print help information\n"); |
| #if INET_CONFIG_ENABLE_TCP_ENDPOINT |
| streamer_printf(stream, " -u use UDP (default)\n"); |
| streamer_printf(stream, " -t use TCP\n"); |
| #endif |
| streamer_printf(stream, " -p <port> echo server port\n"); |
| streamer_printf(stream, " -i <interval> ping interval time in seconds\n"); |
| streamer_printf(stream, " -c <count> stop after <count> replies\n"); |
| streamer_printf(stream, " -r <1|0> enable or disable MRP\n"); |
| streamer_printf(stream, " -s <size> application payload size in bytes\n"); |
| } |
| |
| CHIP_ERROR cmd_ping(int argc, char ** argv) |
| { |
| streamer_t * sout = streamer_get(); |
| int optIndex = 0; |
| |
| gPingArguments.Reset(); |
| |
| while (optIndex < argc && argv[optIndex][0] == '-') |
| { |
| switch (argv[optIndex][1]) |
| { |
| case 'h': |
| PrintUsage(sout); |
| return CHIP_NO_ERROR; |
| #if INET_CONFIG_ENABLE_TCP_ENDPOINT |
| case 'u': |
| gPingArguments.SetUsingTCP(false); |
| break; |
| case 't': |
| gPingArguments.SetUsingTCP(true); |
| break; |
| #endif |
| case 'i': |
| if (++optIndex >= argc || argv[optIndex][0] == '-') |
| { |
| streamer_printf(sout, "Invalid argument specified for -i\n"); |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| else |
| { |
| gPingArguments.SetEchoInterval(atol(argv[optIndex]) * 1000); |
| } |
| break; |
| case 'c': |
| if (++optIndex >= argc || argv[optIndex][0] == '-') |
| { |
| streamer_printf(sout, "Invalid argument specified for -c\n"); |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| else |
| { |
| gPingArguments.SetMaxEchoCount(atol(argv[optIndex])); |
| } |
| break; |
| case 'p': |
| if (++optIndex >= argc || argv[optIndex][0] == '-') |
| { |
| streamer_printf(sout, "Invalid argument specified for -p\n"); |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| else |
| { |
| gPingArguments.SetEchoPort(atol(argv[optIndex])); |
| } |
| break; |
| case 's': |
| if (++optIndex >= argc || argv[optIndex][0] == '-') |
| { |
| streamer_printf(sout, "Invalid argument specified for -s\n"); |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| else |
| { |
| gPingArguments.SetPayloadSize(atol(argv[optIndex])); |
| } |
| break; |
| case 'r': |
| if (++optIndex >= argc || argv[optIndex][0] == '-') |
| { |
| streamer_printf(sout, "Invalid argument specified for -r\n"); |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| else |
| { |
| int arg = atoi(argv[optIndex]); |
| |
| if (arg == 0) |
| { |
| gPingArguments.SetUsingMRP(false); |
| } |
| else if (arg == 1) |
| { |
| gPingArguments.SetUsingMRP(true); |
| } |
| else |
| { |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| } |
| break; |
| default: |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| |
| optIndex++; |
| } |
| |
| if (optIndex >= argc) |
| { |
| streamer_printf(sout, "Missing IP address\n"); |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| |
| streamer_printf(sout, "IP address: %s\n", argv[optIndex]); |
| StartPinging(sout, argv[optIndex]); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| } // namespace |
| |
| static shell_command_t cmds_ping[] = { |
| { &cmd_ping, "ping", "Using Echo Protocol to measure packet loss across network paths" }, |
| }; |
| |
| void cmd_ping_init() |
| { |
| Engine::Root().RegisterCommands(cmds_ping, ArraySize(cmds_ping)); |
| } |