blob: d399a24f23bfc6be18d22011a0d6103378720a88 [file] [log] [blame]
/*
*
* 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/shell_core.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/PASESession.h>
#include <system/SystemPacketBuffer.h>
#include <transport/SecureSessionMgr.h>
#include <transport/raw/TCP.h>
#include <transport/raw/UDP.h>
#include <ChipShellCollection.h>
using namespace chip;
using namespace Shell;
using namespace Logging;
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
constexpr size_t kMaxTcpActiveConnectionCount = 4;
constexpr size_t kMaxTcpPendingPackets = 4;
#endif
constexpr size_t kMaxPayloadSize = 1280;
namespace {
class PingArguments
{
public:
void Reset()
{
mMaxEchoCount = 3;
mEchoInterval = 1000;
mLastEchoTime = 0;
mEchoCount = 0;
mEchoRespCount = 0;
mEchoReqSize = 32;
mWaitingForEchoResp = false;
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
mUsingTCP = false;
#endif
mUsingCRMP = true;
mEchoPort = CHIP_PORT;
}
uint64_t GetLastEchoTime() const { return mLastEchoTime; }
void SetLastEchoTime(uint64_t 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 GetEchoReqSize() const { return mEchoReqSize; }
void SetEchoReqSize(uint32_t value) { mEchoReqSize = 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 IsUsingCRMP() const { return mUsingCRMP; }
void SetUsingCRMP(bool value) { mUsingCRMP = value; }
private:
// The last time a echo request was attempted to be sent.
uint64_t 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 mEchoReqSize;
// 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 mUsingCRMP;
} gPingArguments;
constexpr Transport::AdminId gAdminId = 0;
Protocols::Echo::EchoClient gEchoClient;
TransportMgr<Transport::UDP> gUDPManager;
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
TransportMgr<Transport::TCP<kMaxTcpActiveConnectionCount, kMaxTcpPendingPackets>> gTCPManager;
#endif
Messaging::ExchangeManager gExchangeManager;
SecureSessionMgr gSessionManager;
Inet::IPAddress gDestAddr;
bool EchoIntervalExpired(void)
{
uint64_t now = System::Timer::GetCurrentEpoch();
return (now >= gPingArguments.GetLastEchoTime() + gPingArguments.GetEchoInterval());
}
CHIP_ERROR SendEchoRequest(streamer_t * stream)
{
CHIP_ERROR err = CHIP_NO_ERROR;
Messaging::SendFlags sendFlags;
System::PacketBufferHandle payloadBuf;
char * requestData = nullptr;
uint32_t size = gPingArguments.GetEchoReqSize();
VerifyOrExit(size <= kMaxPayloadSize, err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
requestData = static_cast<char *>(chip::Platform::MemoryAlloc(size));
VerifyOrExit(requestData != nullptr, err = CHIP_ERROR_NO_MEMORY);
snprintf(requestData, size, "Echo Message %" PRIu64 "\n", gPingArguments.GetEchoCount());
payloadBuf = MessagePacketBuffer::NewWithData(requestData, size);
VerifyOrExit(!payloadBuf.IsNull(), err = CHIP_ERROR_NO_MEMORY);
if (gPingArguments.IsUsingCRMP())
{
sendFlags.Set(Messaging::SendMessageFlags::kNone);
}
else
{
sendFlags.Set(Messaging::SendMessageFlags::kNoAutoRequestAck);
}
gPingArguments.SetLastEchoTime(System::Timer::GetCurrentEpoch());
streamer_printf(stream, "\nSend echo request message with payload size: %d bytes to Node: %" PRIu64 "\n", size,
kTestDeviceNodeId);
err = gEchoClient.SendEchoRequest(std::move(payloadBuf), sendFlags);
if (err == CHIP_NO_ERROR)
{
gPingArguments.SetWaitingForEchoResp(true);
gPingArguments.IncrementEchoCount();
}
exit:
if (requestData != nullptr)
{
chip::Platform::MemoryFree(requestData);
}
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, 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(peerAddr, kTestDeviceNodeId, testSecurePairingSecret,
SecureSessionMgr::PairingDirection::kInitiator, gAdminId);
exit:
if (err != CHIP_NO_ERROR)
{
streamer_printf(stream, "Establish secure session failed, err: %s\n", ErrorStr(err));
gPingArguments.SetLastEchoTime(System::Timer::GetCurrentEpoch());
}
else
{
streamer_printf(stream, "Establish secure session succeeded\n");
}
return err;
}
void HandleEchoResponseReceived(Messaging::ExchangeContext * ec, System::PacketBufferHandle payload)
{
uint32_t respTime = System::Timer::GetCurrentEpoch();
uint32_t 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=%.3fms\n", gPingArguments.GetEchoRespCount(),
gPingArguments.GetEchoCount(),
static_cast<double>(gPingArguments.GetEchoRespCount()) * 100 / gPingArguments.GetEchoCount(),
payload->DataLength(), static_cast<double>(transitTime) / 1000);
}
void StartPinging(streamer_t * stream, char * destination)
{
CHIP_ERROR err = CHIP_NO_ERROR;
Transport::AdminPairingTable admins;
Transport::PeerAddress peerAddress;
Transport::AdminPairingInfo * adminInfo = nullptr;
uint32_t maxEchoCount = 0;
if (!Inet::IPAddress::FromString(destination, gDestAddr))
{
streamer_printf(stream, "Invalid Echo Server IP address: %s\n", destination);
ExitNow(err = CHIP_ERROR_INVALID_ARGUMENT);
}
adminInfo = admins.AssignAdminId(gAdminId, kTestControllerNodeId);
VerifyOrExit(adminInfo != nullptr, err = CHIP_ERROR_NO_MEMORY);
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
err = gTCPManager.Init(Transport::TcpListenParameters(&DeviceLayer::InetLayer)
.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::InetLayer)
.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())
{
peerAddress = Transport::PeerAddress::TCP(gDestAddr, gPingArguments.GetEchoPort());
err = gSessionManager.Init(kTestControllerNodeId, &DeviceLayer::SystemLayer, &gTCPManager, &admins);
SuccessOrExit(err);
err = gExchangeManager.Init(&gSessionManager);
SuccessOrExit(err);
}
else
#endif
{
peerAddress = Transport::PeerAddress::UDP(gDestAddr, gPingArguments.GetEchoPort(), INET_NULL_INTERFACEID);
err = gSessionManager.Init(kTestControllerNodeId, &DeviceLayer::SystemLayer, &gUDPManager, &admins);
SuccessOrExit(err);
err = gExchangeManager.Init(&gSessionManager);
SuccessOrExit(err);
}
// Start the CHIP connection to the CHIP echo responder.
err = EstablishSecureSession(stream, peerAddress);
SuccessOrExit(err);
// TODO: temprary create a SecureSessionHandle from node id to unblock end-to-end test. Complete solution is tracked in PR:4451
err = gEchoClient.Init(&gExchangeManager, { kTestDeviceNodeId, 0, gAdminId });
SuccessOrExit(err);
// Arrange to get a callback whenever an Echo Response is received.
gEchoClient.SetEchoResponseReceived(HandleEchoResponseReceived);
maxEchoCount = gPingArguments.GetMaxEchoCount();
// Connection has been established. Now send the EchoRequests.
for (unsigned int i = 0; i < maxEchoCount; i++)
{
err = SendEchoRequest(stream);
if (err != CHIP_NO_ERROR)
{
streamer_printf(stream, "Send request failed: %s\n", ErrorStr(err));
break;
}
// Wait for response until the Echo interval.
while (!EchoIntervalExpired())
{
// TODO:#5496: Use condition_varible to suspend the current thread and wake it up when response arrive.
sleep(1);
}
// Check if expected response was received.
if (gPingArguments.IsWaitingForEchoResp())
{
streamer_printf(stream, "No response received\n");
gPingArguments.SetWaitingForEchoResp(false);
}
}
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
gTCPManager.Disconnect(peerAddress);
gTCPManager.Close();
#endif
gUDPManager.Close();
gEchoClient.Shutdown();
gExchangeManager.Shutdown();
gSessionManager.Shutdown();
exit:
if ((err != CHIP_NO_ERROR))
{
streamer_printf(stream, "Ping failed with error: %s\n", ErrorStr(err));
}
}
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 CRMP\n");
streamer_printf(stream, " -s <size> payload size in bytes\n");
}
int cmd_ping(int argc, char ** argv)
{
streamer_t * sout = streamer_get();
int ret = 0;
int optIndex = 0;
gPingArguments.Reset();
while (optIndex < argc && argv[optIndex][0] == '-')
{
switch (argv[optIndex][1])
{
case 'h':
PrintUsage(sout);
return 0;
#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 -1;
}
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 -1;
}
else
{
gPingArguments.SetMaxEchoCount(atol(argv[optIndex]));
}
break;
case 'p':
if (++optIndex >= argc || argv[optIndex][0] == '-')
{
streamer_printf(sout, "Invalid argument specified for -c\n");
return -1;
}
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 -1;
}
else
{
gPingArguments.SetEchoReqSize(atol(argv[optIndex]));
}
break;
case 'r':
if (++optIndex >= argc || argv[optIndex][0] == '-')
{
streamer_printf(sout, "Invalid argument specified for -r\n");
return -1;
}
else
{
int arg = atoi(argv[optIndex]);
if (arg == 0)
{
gPingArguments.SetUsingCRMP(false);
}
else if (arg == 1)
{
gPingArguments.SetUsingCRMP(true);
}
else
{
ret = -1;
}
}
break;
default:
ret = -1;
}
optIndex++;
}
if (optIndex >= argc)
{
streamer_printf(sout, "Missing IP address\n");
ret = -1;
}
if (ret == 0)
{
streamer_printf(sout, "IP address: %s\n", argv[optIndex]);
StartPinging(sout, argv[optIndex]);
}
return ret;
}
} // namespace
static shell_command_t cmds_ping[] = {
{ &cmd_ping, "ping", "Using Echo Protocol to measure packet loss across network paths" },
};
void cmd_ping_init()
{
shell_register(cmds_ping, ArraySize(cmds_ping));
}