blob: a0fe9c6e1ec9205c288e7774a963a88a516c2ed0 [file] [log] [blame]
/*
* Copyright (c) 2021, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements the ping sender module.
*/
#include "ping_sender.hpp"
#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
#include "common/as_core_type.hpp"
#include "common/encoding.hpp"
#include "common/locator_getters.hpp"
#include "common/random.hpp"
namespace ot {
namespace Utils {
using Encoding::BigEndian::HostSwap32;
void PingSender::Config::SetUnspecifiedToDefault(void)
{
if (mSize == 0)
{
mSize = kDefaultSize;
}
if (mCount == 0)
{
mCount = kDefaultCount;
}
if (mInterval == 0)
{
mInterval = kDefaultInterval;
}
if (mTimeout == 0)
{
mTimeout = kDefaultTimeout;
}
}
void PingSender::Config::InvokeReplyCallback(const Reply &aReply) const
{
VerifyOrExit(mReplyCallback != nullptr);
mReplyCallback(&aReply, mCallbackContext);
exit:
return;
}
void PingSender::Config::InvokeStatisticsCallback(const Statistics &aStatistics) const
{
VerifyOrExit(mStatisticsCallback != nullptr);
mStatisticsCallback(&aStatistics, mCallbackContext);
exit:
return;
}
PingSender::PingSender(Instance &aInstance)
: InstanceLocator(aInstance)
, mIdentifier(0)
, mTargetEchoSequence(0)
, mTimer(aInstance, PingSender::HandleTimer)
, mIcmpHandler(PingSender::HandleIcmpReceive, this)
{
IgnoreError(Get<Ip6::Icmp>().RegisterHandler(mIcmpHandler));
}
Error PingSender::Ping(const Config &aConfig)
{
Error error = kErrorNone;
VerifyOrExit(!mTimer.IsRunning(), error = kErrorBusy);
mConfig = aConfig;
mConfig.SetUnspecifiedToDefault();
VerifyOrExit(mConfig.mInterval <= Timer::kMaxDelay, error = kErrorInvalidArgs);
mStatistics.Clear();
mStatistics.mIsMulticast = AsCoreType(&mConfig.mDestination).IsMulticast();
mIdentifier++;
SendPing();
exit:
return error;
}
void PingSender::Stop(void)
{
mTimer.Stop();
mIdentifier++;
}
void PingSender::SendPing(void)
{
TimeMilli now = TimerMilli::GetNow();
Message * message = nullptr;
Ip6::MessageInfo messageInfo;
messageInfo.SetSockAddr(mConfig.GetSource());
messageInfo.SetPeerAddr(mConfig.GetDestination());
messageInfo.mHopLimit = mConfig.mHopLimit;
messageInfo.mAllowZeroHopLimit = mConfig.mAllowZeroHopLimit;
message = Get<Ip6::Icmp>().NewMessage(0);
VerifyOrExit(message != nullptr);
SuccessOrExit(message->Append(HostSwap32(now.GetValue())));
if (mConfig.mSize > message->GetLength())
{
SuccessOrExit(message->SetLength(mConfig.mSize));
}
mTargetEchoSequence = Get<Ip6::Icmp>().GetEchoSequence();
SuccessOrExit(Get<Ip6::Icmp>().SendEchoRequest(*message, messageInfo, mIdentifier));
mStatistics.mSentCount++;
#if OPENTHREAD_CONFIG_OTNS_ENABLE
Get<Utils::Otns>().EmitPingRequest(mConfig.GetDestination(), mConfig.mSize, now.GetValue(), mConfig.mHopLimit);
#endif
message = nullptr;
exit:
FreeMessage(message);
mConfig.mCount--;
if (mConfig.mCount > 0)
{
mTimer.Start(mConfig.mInterval);
}
else
{
mTimer.Start(mConfig.mTimeout);
}
}
void PingSender::HandleTimer(Timer &aTimer)
{
aTimer.Get<PingSender>().HandleTimer();
}
void PingSender::HandleTimer(void)
{
if (mConfig.mCount > 0)
{
SendPing();
}
else // The last reply times out, triggering the callback to print statistics in CLI.
{
mConfig.InvokeStatisticsCallback(mStatistics);
}
}
void PingSender::HandleIcmpReceive(void * aContext,
otMessage * aMessage,
const otMessageInfo *aMessageInfo,
const otIcmp6Header *aIcmpHeader)
{
reinterpret_cast<PingSender *>(aContext)->HandleIcmpReceive(AsCoreType(aMessage), AsCoreType(aMessageInfo),
AsCoreType(aIcmpHeader));
}
void PingSender::HandleIcmpReceive(const Message & aMessage,
const Ip6::MessageInfo & aMessageInfo,
const Ip6::Icmp::Header &aIcmpHeader)
{
Reply reply;
uint32_t timestamp;
VerifyOrExit(mTimer.IsRunning());
VerifyOrExit(aIcmpHeader.GetType() == Ip6::Icmp::Header::kTypeEchoReply);
VerifyOrExit(aIcmpHeader.GetId() == mIdentifier);
SuccessOrExit(aMessage.Read(aMessage.GetOffset(), timestamp));
timestamp = HostSwap32(timestamp);
reply.mSenderAddress = aMessageInfo.GetPeerAddr();
reply.mRoundTripTime =
static_cast<uint16_t>(OT_MIN(TimerMilli::GetNow() - TimeMilli(timestamp), NumericLimits<uint16_t>::kMax));
reply.mSize = aMessage.GetLength() - aMessage.GetOffset();
reply.mSequenceNumber = aIcmpHeader.GetSequence();
reply.mHopLimit = aMessageInfo.GetHopLimit();
mStatistics.mReceivedCount++;
mStatistics.mTotalRoundTripTime += reply.mRoundTripTime;
mStatistics.mMaxRoundTripTime = OT_MAX(mStatistics.mMaxRoundTripTime, reply.mRoundTripTime);
mStatistics.mMinRoundTripTime = OT_MIN(mStatistics.mMinRoundTripTime, reply.mRoundTripTime);
#if OPENTHREAD_CONFIG_OTNS_ENABLE
Get<Utils::Otns>().EmitPingReply(aMessageInfo.GetPeerAddr(), reply.mSize, timestamp, reply.mHopLimit);
#endif
// Received all ping replies, no need to wait longer.
if (!mStatistics.mIsMulticast && mConfig.mCount == 0 && aIcmpHeader.GetSequence() == mTargetEchoSequence)
{
mTimer.Stop();
}
mConfig.InvokeReplyCallback(reply);
// Received all ping replies, no need to wait longer.
if (!mStatistics.mIsMulticast && mConfig.mCount == 0 && aIcmpHeader.GetSequence() == mTargetEchoSequence)
{
mConfig.InvokeStatisticsCallback(mStatistics);
}
exit:
return;
}
} // namespace Utils
} // namespace ot
#endif // #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE