| /* |
| * 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 |