Files
UnrealEngine/Engine/Source/Runtime/Online/ICMP/Private/UDPPing.cpp
2025-05-18 13:04:45 +08:00

1567 lines
43 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CoreMinimal.h"
#include "Misc/ConfigCacheIni.h"
#include "HAL/ThreadSafeBool.h"
#include "Containers/Ticker.h"
#include "Async/Future.h"
#include "Async/Async.h"
#include "IcmpPrivate.h"
#include "Icmp.h"
#include "SocketSubsystem.h"
#include "IPAddress.h"
#include "Sockets.h"
DEFINE_LOG_CATEGORY_STATIC(LogPing, Log, All)
extern uint16 NtoHS(uint16 val);
extern uint16 HtoNS(uint16 val);
extern uint32 NtoHL(uint32 val);
extern uint32 HtoNL(uint32 val);
// ----------------------------------------------------------------------------
// UDPEchoMany private interface
// ----------------------------------------------------------------------------
namespace UDPPing
{
struct FUdpPingHeader
{
uint16 Id;
uint16 Sequence;
uint16 Checksum;
void HostToNetwork();
void NetworkToHost();
};
struct FUdpPingBody
{
uint64 TimeCode;
uint32 Data[2];
void HostToNetwork();
void NetworkToHost();
};
struct FUdpPingPacket
{
FUdpPingHeader Header;
FUdpPingBody Body;
void HostToNetwork();
void NetworkToHost();
bool Validate(uint16 ExpectedId, uint16 ExpectedSequenceNum) const;
static FUdpPingPacket Create(uint16 Id, uint16 SequenceNum);
};
static const SIZE_T HeaderSize = sizeof(FUdpPingHeader);
static const SIZE_T BodySize = sizeof(FUdpPingBody);
static const SIZE_T SizePacked = HeaderSize + BodySize;
bool Pack(uint8* OutBuf, const SIZE_T BufSize, const FUdpPingPacket& Packet);
bool Unpack(FUdpPingPacket& OutPacket, uint8* const Buf, const SIZE_T BufSize);
bool UpdatePacketChecksum(uint8* const PingPacketBuf, const SIZE_T BufSize, const bool ToNetworkByteOrder);
uint16 CalculatePacketChecksum(uint8* const PingPacketBuf, const SIZE_T BufSize);
} // namespace UDPPing
// ----------------------------------------------------------------------------
namespace
{
typedef TFunction<void(FIcmpEchoManyResult)> FGotResultCallback;
DECLARE_DELEGATE_OneParam(FGotResultDelegate, FIcmpEchoManyResult);
typedef TFunction<void(EIcmpEchoManyStatus)> FStatusCallback;
DECLARE_DELEGATE_OneParam(FStatusDelegate, EIcmpEchoManyStatus);
static constexpr uint32 PingDataHigh = 0xaaaaaaaa;
static constexpr uint32 PingDataLow = 0xbbbbbbbb;
FSocket* CreateDatagramSocket(ISocketSubsystem& SocketSub, const FName& ProtocolType, bool blocking);
uint64 NtoHLL(uint64 Val);
uint64 HtoNLL(uint64 Val);
int32 CalcStackSize();
} // namespace
// ----------------------------------------------------------------------------
class FUdpPingWorker
: public FRunnable
{
struct FProgress
{
/* Unresolved target address */
FString Address;
/* Target port */
int32 Port;
/* Time at which ping was sent to address (used by timeout check) */
double StartTime;
/* Duration after sending that the ping times out */
double TimeoutDuration;
/* Identifying number for the ping, to distinguish between multiple pings to same host (or over entire target list) */
uint16 EchoId;
/* Sequence number, i.e. which send attempt sent the ping associated with this progress data */
uint16 SequenceNum;
/* Ping result */
FIcmpEchoResult Result;
/* The shared socket used to send/receive the ping */
FSocket* SocketPtr;
/* The socket IP address for sending to the target host */
TSharedPtr<FInternetAddr> ToAddr;
public:
FProgress();
}; // struct FProgress
struct FProgressKey
{
/* Resolved IP address if resolvable, for faster lookup on receiving replies */
FString Address;
/* Identifier used when sending to same address multiple times */
int UniqueId;
FProgressKey(const FString& InAddress, int InUniqueId);
bool operator ==(const FProgressKey& Rhs) const;
uint32 CalcHash() const;
friend uint32 GetTypeHash(const FProgressKey& Key) { return Key.CalcHash(); }
}; // struct FProgressKey
enum class ESendStatus
{
None,
Ok,
Busy,
NoTarget,
BadTarget,
NoSocket,
BadBuffer,
SocketSendFail,
BadSendSize
};
public:
FUdpPingWorker(ISocketSubsystem *const InSocketSub, const TArray<FIcmpTarget>& InTargets, float Timeout,
const FGotResultDelegate& InGotResultDelegate, const FStatusDelegate& InCompletionDelegate);
virtual ~FUdpPingWorker();
virtual bool Init() override;
virtual uint32 Run() override;
virtual void Stop() override;
virtual void Exit() override;
bool IsCanceled() const;
private:
bool InitProgressTable(const TArray<FIcmpTarget>& PingTargets);
FSocket* GetOrCreateSocket(const FName& ProtocolType);
bool DestroySockets();
bool SendPings(ISocketSubsystem& SocketSub);
int CheckAwaitReplies();
ESendStatus SendPing(ISocketSubsystem& SocketSub, uint8* SendBuf, const uint32 BufSize, FProgress& Progress);
static ESendStatus SendPing(FSocket& Socket, uint8* SendBuf, const uint32 BufSize,
const FInternetAddr& ToAddr, UDPPing::FUdpPingPacket& Packet, double& OutSendTimeSecs);
static bool HasReplyData(FSocket& Socket, const uint32 MinSize);
static bool RecvReplyFrom(FSocket& Socket, uint8* RecvBuf, const uint32 RecvSize, FInternetAddr& OutRecvFrom, uint64& OutRecvTimeCode);
static FProgress* ProcessReply(const FInternetAddr& FromAddr, uint8* const RecvBuf, const uint32 RecvSize,
const uint64 RecvTimeCode, TMap<FProgressKey, FProgress>& ProgressTable);
static bool CalculateTripTime(const uint64 ReplyTimeCode, const uint64 CurrentTimeCode, const double TimeoutSecs,
float& OutTime, EIcmpResponseStatus& OutStatus);
static void ResetSequenceNum();
static int NextSequenceNum();
private:
float Timeout;
int NumAwaitingReplies;
int NumValidRepliesReceived;
bool bCancelOperation;
double MinPingSendWaitTimeMs;
ISocketSubsystem* SocketSubsystem;
TArray<FIcmpTarget> Targets;
TMap<FProgressKey, FProgress> ProgressTable;
TMap<FName, FSocket *> SocketTable;
FGotResultDelegate GotResultDelegate;
FStatusDelegate CompletionDelegate;
static int SequenceNum;
static const SIZE_T SendDataSize;
static const SIZE_T RecvDataSize;
static const FTimespan RecvWaitTime;
static const FTimespan SendWaitTime;
static const double MaxTripTimeSecs;
static const double MaxInactivityWaitTimeSecs;
}; // class FUdpPingWorker
// ----------------------------------------------------------------------------
class FUdpPingManyAsync
: public FTSTickerObjectBase
{
public:
FUdpPingManyAsync(ISocketSubsystem* const InSocketSub, const FIcmpEchoManyCompleteDelegate& InCompletionDelegate);
virtual ~FUdpPingManyAsync();
void Start(const TArray<FIcmpTarget>& Targets, float Timeout, uint32 StackSize);
protected:
virtual bool Tick(float DeltaTime) override;
private:
static FIcmpEchoManyCompleteResult RunWorker(ISocketSubsystem* const SocketSub, const TArray<FIcmpTarget>& Targets, float Timeout);
private:
ISocketSubsystem* SocketSub;
FIcmpEchoManyCompleteDelegate CompletionDelegate;
FThreadSafeBool bThreadCompleted;
TFuture<FIcmpEchoManyCompleteResult> FutureResult;
}; // class FUdpPingManyAsync
// ----------------------------------------------------------------------------
// UDPEchoMany implementation
// ----------------------------------------------------------------------------
const SIZE_T FUdpPingWorker::SendDataSize = UDPPing::SizePacked;
const SIZE_T FUdpPingWorker::RecvDataSize = UDPPing::SizePacked;
const FTimespan FUdpPingWorker::RecvWaitTime = FTimespan::FromMilliseconds(0.25);
const FTimespan FUdpPingWorker::SendWaitTime = FTimespan::FromMilliseconds(0.25);
const double FUdpPingWorker::MaxTripTimeSecs = 60.0 * 1000.0; // 1000 minutes
const double FUdpPingWorker::MaxInactivityWaitTimeSecs = 60.0 * 10.0; // 10 minutes
int FUdpPingWorker::SequenceNum = 0;
FUdpPingWorker::FUdpPingWorker(ISocketSubsystem *const InSocketSub, const TArray<FIcmpTarget>& InTargets, float InTimeout,
const FGotResultDelegate& InGotResultDelegate, const FStatusDelegate& InCompletionDelegate)
: FRunnable()
, Timeout(InTimeout)
, NumAwaitingReplies(0)
, NumValidRepliesReceived(0)
, bCancelOperation(false)
, MinPingSendWaitTimeMs(0)
, SocketSubsystem(InSocketSub)
, Targets(InTargets)
, GotResultDelegate(InGotResultDelegate)
, CompletionDelegate(InCompletionDelegate)
{
}
FUdpPingWorker::~FUdpPingWorker()
{
DestroySockets();
SocketTable.Empty();
}
bool FUdpPingWorker::Init()
{
ProgressTable.Empty();
DestroySockets();
SocketTable.Empty();
NumAwaitingReplies = 0;
NumValidRepliesReceived = 0;
bCancelOperation = false;
MinPingSendWaitTimeMs = FUdpPingWorker::SendWaitTime.GetTotalMilliseconds();
GConfig->GetDouble(TEXT("Ping"), TEXT("MinPingSendWaitTimeMs"), MinPingSendWaitTimeMs, GEngineIni);
return true;
}
uint32 FUdpPingWorker::Run()
{
bool bOk = true;
// Setup
if (bOk && !SocketSubsystem)
{
UE_LOG(LogPing, Warning, TEXT("Run: no socket subsystem"));
bOk = false;
}
if (bOk && (Targets.Num() == 0))
{
UE_LOG(LogPing, Warning, TEXT("Run; no ping targets"));
bOk = false;
}
if (bOk && !InitProgressTable(Targets))
{
UE_LOG(LogPing, Warning, TEXT("Run; cannot init progress table"));
bOk = false;
}
// Loop
bOk = bOk && !IsCanceled() && SendPings(*SocketSubsystem);
// Completion
EIcmpEchoManyStatus CompletionStatus = EIcmpEchoManyStatus::Invalid;
if (IsCanceled())
{
CompletionStatus = EIcmpEchoManyStatus::Canceled;
}
else if (bOk)
{
CompletionStatus = EIcmpEchoManyStatus::Success;
}
else
{
CompletionStatus = EIcmpEchoManyStatus::Failure;
}
CompletionDelegate.ExecuteIfBound(CompletionStatus);
return 0;
}
void FUdpPingWorker::Stop()
{
bCancelOperation = true;
}
void FUdpPingWorker::Exit()
{
}
bool FUdpPingWorker::IsCanceled() const
{
return bCancelOperation;
}
bool FUdpPingWorker::InitProgressTable(const TArray<FIcmpTarget>& PingTargets)
{
if (!SocketSubsystem)
{
return false;
}
ProgressTable.Empty();
int Index = 0;
for (const FIcmpTarget& Target : PingTargets)
{
const int EchoId = ++Index;
FProgress Progress;
Progress.Address = Target.Address;
Progress.Port = Target.Port;
Progress.TimeoutDuration = Timeout;
Progress.EchoId = static_cast<uint16>(EchoId);
FString ResolvedAddress;
const bool bResolveOk = ResolveIp(SocketSubsystem, Target.Address, ResolvedAddress);
if (bResolveOk)
{
Progress.Result.ResolvedAddress = ResolvedAddress;
// Add an entry to table, ready to hold reply result.
// Use resolved address in the table key - needed for constant-time progress lookup when receiving replies.
const FProgressKey Key(ResolvedAddress, EchoId);
ProgressTable.Add(Key, Progress);
}
else
{
Progress.Result.Status = EIcmpResponseStatus::Unresolvable;
// Add an entry to table, pre-filled with unresolvable status.
// Use unresolved address in the table key.
const FProgressKey Key(Target.Address, EchoId);
ProgressTable.Add(Key, Progress);
}
}
return true;
}
FSocket* FUdpPingWorker::GetOrCreateSocket(const FName& ProtocolType)
{
if (!SocketSubsystem)
{
return nullptr;
}
FSocket** FoundSocket = SocketTable.Find(ProtocolType);
if (FoundSocket)
{
return *FoundSocket;
}
FSocket* Socket = CreateDatagramSocket(*SocketSubsystem, ProtocolType, false);
if (!Socket)
{
return nullptr;
}
return SocketTable.Add(ProtocolType, Socket);
}
bool FUdpPingWorker::DestroySockets()
{
if (!SocketSubsystem)
{
return false;
}
for (auto& Elem : SocketTable)
{
FSocket* & Socket = Elem.Value;
if (Socket)
{
SocketSubsystem->DestroySocket(Socket);
Socket = nullptr;
}
}
return true;
}
bool FUdpPingWorker::SendPings(ISocketSubsystem& SocketSub)
{
if (ProgressTable.Num() == 0)
{
UE_LOG(LogPing, Warning, TEXT("SendPings; empty progress table"));
return false;
}
typedef TMap<FProgressKey, FProgress>::TIterator TProgressIterator;
TProgressIterator ItSend = ProgressTable.CreateIterator();
uint8 SendBuf[SendDataSize]{ 0 };
uint64 RecvTimeCode = 0;
uint8 RecvBuf[RecvDataSize]{ 0 };
TSharedRef<FInternetAddr> RecvFromAddr = SocketSub.CreateInternetAddr();
NumAwaitingReplies = 0;
NumValidRepliesReceived = 0;
bool bDone = false;
uint64 LastSendActivityTimeCycles = FPlatformTime::Cycles64();
bool bSentLastPing = false;
int NumSkippedPings = -1;
while (!bDone && !IsCanceled())
{
// Receiving Stage
// Iterate over each socket created (sockets are shared based on protocol type)
for (TPair<FName, FSocket*>& Elem : SocketTable)
{
if (IsCanceled())
{
break;
}
FSocket* SocketPtr = Elem.Value;
if (!SocketPtr)
{
// Skip invalid Socket pointers.
continue;
}
if (SocketPtr->Wait(ESocketWaitConditions::WaitForRead, RecvWaitTime) && !IsCanceled())
{
// Deal with any pending incoming data on the socket.
// Inner loop deals with possibility that there are multiple incoming replies on socket.
while (HasReplyData(*SocketPtr, RecvDataSize))
{
if (!RecvReplyFrom(*SocketPtr, RecvBuf, RecvDataSize, *RecvFromAddr, RecvTimeCode))
{
// Failed to get a complete reply packet from socket this time,
// so exit this inner loop to allow more pings to be sent out
// and replies on other sockets to be checked.
UE_LOG(LogPing, Verbose, TEXT("SendPings; recv failed"));
break;
}
UE_LOG(LogPing, VeryVerbose, TEXT("SendPings; recv reply from %s"), *RecvFromAddr->ToString(false));
FProgress* const Progress = ProcessReply(*RecvFromAddr, RecvBuf, RecvDataSize, RecvTimeCode, ProgressTable);
if (Progress)
{
Progress->SocketPtr = nullptr;
// Notify result listeners (e.g. so results can be aggregated).
const FIcmpTarget OriginalSendInfo(Progress->Address, Progress->Port);
GotResultDelegate.ExecuteIfBound(FIcmpEchoManyResult(Progress->Result, OriginalSendInfo));
++NumValidRepliesReceived;
}
} // while (socket has reply data)
const ESocketErrors LastRecvError = SocketSub.GetLastErrorCode();
if (LastRecvError != ESocketErrors::SE_NO_ERROR)
{
UE_LOG(LogPing, Verbose, TEXT("SendPings; recv error: %u"), LastRecvError);
}
}
} // for (socket table)
// Sending Stage
if (!IsCanceled())
{
const bool bMoreToSend = (bool)ItSend;
// Check if we are waiting on any more replies, and timeout any that have expired.
NumAwaitingReplies = CheckAwaitReplies();
bDone = !bMoreToSend && (NumAwaitingReplies == 0);
if (bMoreToSend)
{
// More pings to send out.
// Check for inactivity.
const double DeltaSeconds = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - LastSendActivityTimeCycles);
if (DeltaSeconds > MaxInactivityWaitTimeSecs)
{
// Waiting too long for availability to send any more pings, so abort the whole send/recv loop.
// Allows task thread to complete with "canceled" status.
UE_LOG(LogPing, Warning, TEXT("SendPings; aborting due to inactivity after %.4f seconds"),
DeltaSeconds);
bCancelOperation = true;
break;
}
const double DeltaMs = FPlatformTime::ToMilliseconds64(FPlatformTime::Cycles64() - LastSendActivityTimeCycles);
if (bSentLastPing && (DeltaMs < MinPingSendWaitTimeMs && NumSkippedPings >= 0))
{
// Skip sending pings for a bit (but continue receiving) to not spam traffic.
++NumSkippedPings;
continue;
}
if (NumSkippedPings > 0)
{
UE_LOG(LogPing, VeryVerbose, TEXT("SendPings: paused sending pings for %d iterations (for %.4f ms, configured wait time is %.4f ms); resuming sends"), NumSkippedPings, DeltaMs, MinPingSendWaitTimeMs);
}
// Send ping for current target in progress table.
// Only send a maximum of one ping request per iteration; checking for replies takes priority.
FProgress& Progress = ItSend->Value;
const ESendStatus Status = SendPing(SocketSub, SendBuf, SendDataSize, Progress);
NumSkippedPings = 0;
bSentLastPing = false;
if ((ESendStatus::Ok == Status) || (ESendStatus::NoTarget == Status))
{
// Don't wait/block here; poll for is-reply-data-available in loop until received or timeout.
if (ESendStatus::Ok == Status)
{
// actually kick in the wait timer if we did anything on the network this time
bSentLastPing = true;
LastSendActivityTimeCycles = FPlatformTime::Cycles64();
}
// Advance to next send target address.
++ItSend;
}
else if (ESendStatus::Busy == Status)
{
// Socket wasn't ready to write, so don't advance through the send targets list;
// attempt to send again on next iteration.
// Double-check that socket didn't generate a fatal send error, as retrying would be pointless.
const ESocketErrors LastSendError = SocketSub.GetLastErrorCode();
const bool bInternalError = (LastSendError != ESocketErrors::SE_NO_ERROR)
&& (LastSendError != ESocketErrors::SE_EWOULDBLOCK)
&& (LastSendError != ESocketErrors::SE_EINPROGRESS);
if (bInternalError)
{
UE_LOG(LogPing, Verbose, TEXT("SendPings; (busy) internal socket error: %u"), LastSendError);
// Send failed, mark the failure in the results and advance to next target address.
Progress.Result.Status = EIcmpResponseStatus::Unresolvable;
++ItSend;
}
}
else
{
// Status is one of: BadTarget, NoSocket, BadBuffer, SocketSendFail, BadSendSize
UE_LOG(LogPing, Verbose, TEXT("SendPings; send error, status: %u"), int(Status));
// Send failed, mark the failure in the results and advance to next target address.
Progress.Result.Status = EIcmpResponseStatus::Unresolvable;
++ItSend;
} // send status
} // send
} // not canceled (send)
} // while (send/recv loop)
// Cleanup.
DestroySockets();
SocketTable.Empty();
// if we have any unresolvable results in the progress table, notify everyone of their failure now that we're all done
for (TPair<FProgressKey, FProgress>& Row : ProgressTable)
{
FProgress& Progress = Row.Value;
FIcmpEchoResult& Result = Progress.Result;
if (Result.Status != EIcmpResponseStatus::Success && Result.Status != EIcmpResponseStatus::Timeout)
{
UE_LOG(LogPing, Verbose, TEXT("SendPings(Done): %s:%d (id: %d, seq: %d) had non-successful status '%s' after send loop"), *Progress.Address, Progress.Port, Progress.EchoId, Progress.SequenceNum, LexToString(Result.Status));
Result.Time = Progress.TimeoutDuration;
Result.ReplyFrom.Empty();
const FIcmpTarget SendInfo(Progress.Address, Progress.Port);
GotResultDelegate.ExecuteIfBound(FIcmpEchoManyResult(Progress.Result, SendInfo));
}
}
return true;
}
int FUdpPingWorker::CheckAwaitReplies()
{
int NumAwaitingReply = 0;
for (TPair<FProgressKey, FProgress>& Row : ProgressTable)
{
FProgress& Progress = Row.Value;
FIcmpEchoResult& Result = Progress.Result;
bool bShouldNotifyFailure = false;
// InternalError status means no result yet.
if ((EIcmpResponseStatus::InternalError == Result.Status) && (Progress.StartTime > 0))
{
const double ReplyWaitDuration = FPlatformTime::Seconds() - Progress.StartTime;
if (ReplyWaitDuration >= Progress.TimeoutDuration)
{
UE_LOG(LogPing, Log, TEXT("CheckAwaitReplies: %s:%d (id: %d, seq: %d) timed out after %.4f seconds"), *Progress.Address, Progress.Port, Progress.EchoId, Progress.SequenceNum, Progress.TimeoutDuration);
bShouldNotifyFailure = true;
Result.Time = Progress.TimeoutDuration;
Result.Status = EIcmpResponseStatus::Timeout;
}
else
{
++NumAwaitingReply;
}
}
if (bShouldNotifyFailure)
{
Result.ReplyFrom.Empty();
Progress.SocketPtr = nullptr;
// Notify result listeners since we failed
const FIcmpTarget SendInfo(Progress.Address, Progress.Port);
GotResultDelegate.ExecuteIfBound(FIcmpEchoManyResult(Progress.Result, SendInfo));
}
}
return NumAwaitingReply;
}
FUdpPingWorker::ESendStatus FUdpPingWorker::SendPing(ISocketSubsystem& SocketSub,
uint8* SendBuf, const uint32 BufSize, FProgress& Progress)
{
if (EIcmpResponseStatus::Unresolvable == Progress.Result.Status)
{
return ESendStatus::NoTarget;
}
FIcmpEchoResult& Result = Progress.Result;
if (!Progress.ToAddr.IsValid())
{
TSharedRef<FInternetAddr> IpAddr = SocketSub.CreateInternetAddr();
bool bIsValidAddress = false;
IpAddr->SetIp(*Result.ResolvedAddress, bIsValidAddress);
if (!bIsValidAddress)
{
UE_LOG(LogPing, Verbose, TEXT("SendPing; unresolvable IP address"));
Result.Status = EIcmpResponseStatus::Unresolvable;
return ESendStatus::BadTarget;
}
IpAddr->SetPort(Progress.Port);
Progress.ToAddr = TSharedPtr<FInternetAddr>(IpAddr);
}
check(Progress.ToAddr.IsValid());
TSharedPtr<FInternetAddr> ToAddr = Progress.ToAddr;
if (!Progress.SocketPtr)
{
// Get/create a socket appropriate for the remote IP address.
FSocket* NewSocketPtr = GetOrCreateSocket(ToAddr->GetProtocolType());
if (!NewSocketPtr)
{
UE_LOG(LogPing, Verbose, TEXT("SendPing; failed to get socket for IP address: %s"), *Result.ResolvedAddress);
return ESendStatus::NoSocket;
}
Progress.SocketPtr = NewSocketPtr;
}
FSocket *const SocketPtr = Progress.SocketPtr;
check(SocketPtr);
if (!SocketPtr->Wait(ESocketWaitConditions::WaitForWrite, SendWaitTime))
{
// Socket not writable at this time, try again later.
return ESendStatus::Busy;
}
// Create the packet (in host byte order).
const uint16 SeqNum = static_cast<uint16>(NextSequenceNum());
Progress.SequenceNum = SeqNum;
UDPPing::FUdpPingPacket PingPacket = UDPPing::FUdpPingPacket::Create(Progress.EchoId, SeqNum);
// Pack and send out the packet.
return SendPing(*SocketPtr, SendBuf, BufSize, *ToAddr, PingPacket, Progress.StartTime);
}
FUdpPingWorker::ESendStatus FUdpPingWorker::SendPing(FSocket& Socket, uint8* SendBuf, const uint32 BufSize,
const FInternetAddr& ToAddr, UDPPing::FUdpPingPacket& Packet, double& OutSendTimeSecs)
{
UE_LOG(LogPing, VeryVerbose, TEXT("SendPing; %s id=%u (%#06x)"),
*ToAddr.ToString(true), Packet.Header.Id, Packet.Header.Id);
check(BufSize >= SendDataSize);
if (BufSize < SendDataSize)
{
return ESendStatus::BadBuffer;
}
Packet.HostToNetwork();
// Pack ping packet into send buffer and compute checksum.
FMemory::Memset(SendBuf, 0, SendDataSize);
UDPPing::Pack(SendBuf, SendDataSize, Packet);
UDPPing::UpdatePacketChecksum(SendBuf, SendDataSize, true);
// Send ping packet
int32 BytesSent = 0;
OutSendTimeSecs = FPlatformTime::Seconds();
if (!Socket.SendTo(SendBuf, SendDataSize, BytesSent, ToAddr))
{
UE_LOG(LogPing, Verbose, TEXT("SendPing: failed to send datagram"));
return ESendStatus::SocketSendFail;
}
if (BytesSent != SendDataSize)
{
UE_LOG(LogPing, Verbose, TEXT("SendPing: failed, sent %d/%" SIZE_T_FMT " bytes"), BytesSent, SendDataSize);
return ESendStatus::BadSendSize;
}
UE_LOG(LogPing, VeryVerbose, TEXT("SendPing; %s id=%u (%#06x) seq=%#06x chk=%#06x sendtime=%.4f ok (%d/%" SIZE_T_FMT " bytes)"),
*ToAddr.ToString(true), NtoHS(Packet.Header.Id), NtoHS(Packet.Header.Id), NtoHS(Packet.Header.Sequence),
NtoHS(reinterpret_cast<UDPPing::FUdpPingHeader* const>(SendBuf)->Checksum),
OutSendTimeSecs, BytesSent, SendDataSize);
return ESendStatus::Ok;
}
bool FUdpPingWorker::HasReplyData(FSocket& Socket, const uint32 MinSize)
{
uint32 DataSize(0u);
const bool bHasData = Socket.HasPendingData(DataSize)
&& (DataSize >= MinSize);
return bHasData;
}
bool FUdpPingWorker::RecvReplyFrom(FSocket& Socket, uint8* RecvBuf, const uint32 RecvSize,
FInternetAddr& OutRecvFrom, uint64& OutRecvTimeCode)
{
FMemory::Memset(RecvBuf, 0, RecvSize);
int32 BytesRead = 0;
const bool bRecvOk = Socket.RecvFrom(RecvBuf, RecvSize, BytesRead, OutRecvFrom);
if (bRecvOk)
{
OutRecvTimeCode = FPlatformTime::Cycles64();
}
UE_LOG(LogPing, VeryVerbose, TEXT("RecvReplyFrom: recv %d/%d bytes"), BytesRead, RecvSize);
return bRecvOk && (BytesRead == RecvSize);
}
FUdpPingWorker::FProgress* FUdpPingWorker::ProcessReply(const FInternetAddr& FromAddr, uint8* const RecvBuf,
const uint32 RecvSize, const uint64 RecvTimeCode, TMap<FProgressKey, FProgress>& ProgressTable)
{
// Unpack reply data from buffer into packet.
UDPPing::FUdpPingPacket RecvPacket;
FMemory::Memset(&RecvPacket, 0, sizeof(UDPPing::FUdpPingPacket));
UDPPing::Unpack(RecvPacket, RecvBuf, RecvSize);
// Convert to host byte-ordering, except for checksum which is manipulated below.
RecvPacket.NetworkToHost();
// Get checksum from packet, converted into host byte order.
const uint16 RecvChecksum = NtoHS(RecvPacket.Header.Checksum);
// Calculate checksum for packet data (excluding the checksum data within the packet)
const uint16 LocalChecksum = UDPPing::CalculatePacketChecksum(RecvBuf, RecvSize);
if (RecvChecksum != LocalChecksum)
{
UE_LOG(LogPing, Verbose, TEXT("ProcessReply; checksum mismatch: recv{%#06x} != local{%#06x}"),
RecvChecksum, LocalChecksum);
return nullptr;
}
// Find corresponding send progress information for this reply.
const FProgressKey LookupKey(FromAddr.ToString(false), RecvPacket.Header.Id);
FProgress* const FoundProgress = ProgressTable.Find(LookupKey);
if (!FoundProgress)
{
UE_LOG(LogPing, Verbose, TEXT("ProcessReply; progress info not found for %s"), *LookupKey.Address);
return nullptr;
}
FProgress& Progress = *FoundProgress;
FIcmpEchoResult& Result = Progress.Result;
check(LookupKey.Address == Result.ResolvedAddress);
if (LookupKey.Address != Result.ResolvedAddress)
{
// If we get here, the initial progress table setup is wrong, as the resolved address
// of the remote target is also part of the table element key.
UE_LOG(LogPing, Verbose, TEXT("ProcessReply; address mismatch %s != %s"),
*LookupKey.Address, *Result.ResolvedAddress);
return nullptr;
}
if (EIcmpResponseStatus::InternalError != Result.Status)
{
// Already have a status for this ping-reply pair (e.g. timeout).
return nullptr;
}
// Check that reply data makes sense, and also corresponds to the ID and sequence
// number of the ping originally sent out.
if (!RecvPacket.Validate(Progress.EchoId, Progress.SequenceNum))
{
UE_LOG(LogPing, Verbose, TEXT("ProcessReply; data mismatch: recv{id=%u seq=%u} != expect{id=%u seq=%u}"),
Progress.EchoId, Progress.SequenceNum, RecvPacket.Header.Id, RecvPacket.Header.Sequence);
return nullptr;
}
// Reply checks out, so update the progress result with the remote address,
// round-trip time, and success/fail/etc. status.
const double TimeoutSecs = Progress.TimeoutDuration;
Result.ReplyFrom = LookupKey.Address;
CalculateTripTime(RecvPacket.Body.TimeCode, RecvTimeCode, TimeoutSecs, Result.Time, Result.Status);
UE_LOG(LogPing, VeryVerbose, TEXT("ProcessReply; ok: %s:%u (%s) id=%u (%#06x) seq=%#06x ping=%.4f s status=%d"),
*Progress.Address, Progress.Port, *Result.ResolvedAddress, Progress.EchoId, Progress.EchoId,
Progress.SequenceNum, Result.Time, int(Result.Status));
return FoundProgress;
}
bool FUdpPingWorker::CalculateTripTime(const uint64 ReplyTimeCode, const uint64 CurrentTimeCode, const double TimeoutSecs,
float& OutTime, EIcmpResponseStatus& OutStatus)
{
const double DurationSecs = FPlatformTime::ToSeconds64(CurrentTimeCode - ReplyTimeCode);
const bool bIsValid = (DurationSecs > 0.0) && (DurationSecs < MaxTripTimeSecs);
if (bIsValid)
{
OutTime = static_cast<float>(DurationSecs);
OutStatus = EIcmpResponseStatus::Success;
}
else
{
OutTime = -1;
OutStatus = EIcmpResponseStatus::InternalError;
}
UE_LOG(LogPing, VeryVerbose, TEXT("CalculateTripTime; time=%.4f s status=%d"), DurationSecs, int(OutStatus));
return bIsValid;
}
void FUdpPingWorker::ResetSequenceNum()
{
SequenceNum = 0;
}
int FUdpPingWorker::NextSequenceNum()
{
const int Seq = SequenceNum;
++SequenceNum;
return Seq;
}
// ----------------------------------------------------------------------------
FUdpPingWorker::FProgress::FProgress()
: Port(0)
, StartTime(0.0)
, TimeoutDuration(0.0)
, EchoId(0u)
, SequenceNum(0u)
, SocketPtr(nullptr)
{
Result.Status = EIcmpResponseStatus::InternalError;
}
// ----------------------------------------------------------------------------
FUdpPingWorker::FProgressKey::FProgressKey(const FString& InAddress, int InUniqueId)
: Address(InAddress)
, UniqueId(InUniqueId)
{}
bool FUdpPingWorker::FProgressKey::operator ==(const FProgressKey& Rhs) const
{
return (Address == Rhs.Address) && (UniqueId == Rhs.UniqueId);
}
uint32 FUdpPingWorker::FProgressKey::CalcHash() const
{
return HashCombine(GetTypeHash(Address), GetTypeHash(UniqueId));
}
// ----------------------------------------------------------------------------
FUdpPingManyAsync::FUdpPingManyAsync(ISocketSubsystem* const InSocketSub, const FIcmpEchoManyCompleteDelegate& InCompletionDelegate)
: FTSTickerObjectBase(0)
, SocketSub(InSocketSub)
, CompletionDelegate(InCompletionDelegate)
, bThreadCompleted(false)
{}
FUdpPingManyAsync::~FUdpPingManyAsync()
{
check(IsInGameThread());
if (FutureResult.IsValid())
{
FutureResult.Wait();
}
}
void FUdpPingManyAsync::Start(const TArray<FIcmpTarget>& Targets, float Timeout, uint32 StackSize)
{
if (!SocketSub)
{
bThreadCompleted = true;
return;
}
bThreadCompleted = false;
TFunction<FIcmpEchoManyCompleteResult()> WorkerTask = [this, Targets, Timeout]()
{
auto Result = FUdpPingManyAsync::RunWorker(SocketSub, Targets, Timeout);
bThreadCompleted = true;
return Result;
};
FutureResult = AsyncThread(WorkerTask, StackSize);
}
bool FUdpPingManyAsync::Tick(float DeltaTime)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_UDPPing_Tick);
if (bThreadCompleted)
{
FIcmpEchoManyCompleteResult Result;
if (FutureResult.IsValid())
{
Result = FutureResult.Get();
}
CompletionDelegate.ExecuteIfBound(Result);
delete this;
return false;
}
return true;
}
FIcmpEchoManyCompleteResult FUdpPingManyAsync::RunWorker(ISocketSubsystem* const SocketSub, const TArray<FIcmpTarget>& Targets, float Timeout)
{
FIcmpEchoManyCompleteResult EndResult;
FGotResultDelegate OnGotResult;
OnGotResult.BindLambda([&EndResult](FIcmpEchoManyResult Result)
{
// Push result into total.
EndResult.AllResults.Add(Result);
});
FStatusDelegate OnCompleted;
OnCompleted.BindLambda([&EndResult](EIcmpEchoManyStatus EndStatus)
{
// Done, so just need the status of the completed work.
EndResult.Status = EndStatus;
});
FUdpPingWorker PingWorker(SocketSub, Targets, Timeout, OnGotResult, OnCompleted);
PingWorker.Init();
PingWorker.Run();
return EndResult;
}
// ----------------------------------------------------------------------------
namespace UDPPing
{
void FUdpPingHeader::HostToNetwork()
{
Id = HtoNS(Id);
Sequence = HtoNS(Sequence);
}
void FUdpPingHeader::NetworkToHost()
{
Id = NtoHS(Id);
Sequence = NtoHS(Sequence);
}
void FUdpPingBody::HostToNetwork()
{
TimeCode = HtoNLL(TimeCode);
Data[0] = HtoNL(Data[0]);
Data[1] = HtoNL(Data[1]);
}
void FUdpPingBody::NetworkToHost()
{
TimeCode = NtoHLL(TimeCode);
Data[0] = NtoHL(Data[0]);
Data[1] = NtoHL(Data[1]);
}
void FUdpPingPacket::HostToNetwork()
{
Header.HostToNetwork();
Body.HostToNetwork();
}
void FUdpPingPacket::NetworkToHost()
{
Header.NetworkToHost();
Body.NetworkToHost();
}
bool FUdpPingPacket::Validate(uint16 ExpectedId, uint16 ExpectedSequenceNum) const
{
// Assumes that the packet is in host byte order.
return (Header.Id == ExpectedId) && (Header.Sequence == ExpectedSequenceNum) &&
(Body.Data[0] == PingDataHigh) && (Body.Data[1] == PingDataLow) && (Body.TimeCode != 0u);
}
FUdpPingPacket FUdpPingPacket::Create(uint16 Id, uint16 SequenceNum)
{
// Create the packet (in host byte order).
FUdpPingPacket Packet;
FMemory::Memset(&Packet, 0, sizeof(FUdpPingPacket));
FUdpPingHeader& Header = Packet.Header;
Header.Id = Id;
Header.Sequence = SequenceNum;
Packet.Body.Data[0] = PingDataHigh;
Packet.Body.Data[1] = PingDataLow;
Packet.Body.TimeCode = FPlatformTime::Cycles64();
return Packet;
}
bool Pack(uint8* OutBuf, const SIZE_T BufSize, const FUdpPingPacket& InPacket)
{
if (!OutBuf || (BufSize < SizePacked))
{
return false;
}
FUdpPingHeader& OutHeader = *reinterpret_cast<FUdpPingHeader*>(OutBuf);
OutHeader.Id = InPacket.Header.Id;
OutHeader.Sequence = InPacket.Header.Sequence;
OutHeader.Checksum = InPacket.Header.Checksum;
FUdpPingBody& OutBody = *reinterpret_cast<FUdpPingBody*>(OutBuf + HeaderSize);
OutBody.TimeCode = InPacket.Body.TimeCode;
OutBody.Data[0] = InPacket.Body.Data[0];
OutBody.Data[1] = InPacket.Body.Data[1];
return true;
}
bool Unpack(FUdpPingPacket& OutPacket, uint8* const InBuf, const SIZE_T BufSize)
{
if (!InBuf || (BufSize < SizePacked))
{
return false;
}
FUdpPingHeader& InHeader = *reinterpret_cast<FUdpPingHeader*>(InBuf);
OutPacket.Header.Id = InHeader.Id;
OutPacket.Header.Sequence = InHeader.Sequence;
OutPacket.Header.Checksum = InHeader.Checksum;
FUdpPingBody& InBody = *reinterpret_cast<FUdpPingBody*>(InBuf + HeaderSize);
OutPacket.Body.TimeCode = InBody.TimeCode;
OutPacket.Body.Data[0] = InBody.Data[0];
OutPacket.Body.Data[1] = InBody.Data[1];
return true;
}
bool UpdatePacketChecksum(uint8* const PingPacketBuf, const SIZE_T BufSize, const bool ToNetworkByteOrder)
{
if (BufSize < SizePacked)
{
return false;
}
FUdpPingHeader& Header = *reinterpret_cast<FUdpPingHeader*>(PingPacketBuf);
Header.Checksum = 0;
const uint16 Checksum = static_cast<uint16>(CalculateChecksum(PingPacketBuf, SizePacked));
Header.Checksum = ToNetworkByteOrder ? HtoNS(Checksum) : Checksum;
return true;
}
uint16 CalculatePacketChecksum(uint8* const PingPacketBuf, const SIZE_T BufSize)
{
check(PingPacketBuf);
check(BufSize > 0);
FUdpPingHeader& Header = *reinterpret_cast<FUdpPingHeader*>(PingPacketBuf);
const uint16 OldValue = Header.Checksum;
Header.Checksum = 0;
const uint16 ChecksumResult = static_cast<uint16>(CalculateChecksum(PingPacketBuf, SizePacked));
Header.Checksum = OldValue;
return ChecksumResult;
}
} // namespace UDPPing
// ----------------------------------------------------------------------------
namespace
{
FSocket* CreateDatagramSocket(ISocketSubsystem& SocketSub, const FName& ProtocolType, bool blocking)
{
FSocket* const Socket = SocketSub.CreateSocket(NAME_DGram, TEXT("UDPPing"), ProtocolType);
if (!Socket)
{
return nullptr;
}
if (!blocking && !Socket->SetNonBlocking(true))
{
SocketSub.DestroySocket(Socket);
return nullptr;
}
return Socket;
}
uint64 NtoHLL(uint64 Val)
{
static const bool bNoConvert = (NtoHL(1) == 1);
return bNoConvert ? Val : ((static_cast<uint64>(NtoHL(Val)) << 32) + NtoHL(Val >> 32));
}
uint64 HtoNLL(uint64 Val)
{
static const bool bNoConvert = (HtoNL(1) == 1);
return bNoConvert ? Val : ((static_cast<uint64>(HtoNL(Val)) << 32) + HtoNL(Val >> 32));
}
int32 CalcStackSize()
{
int32 StackSize = 0;
#if PING_ALLOWS_CUSTOM_THREAD_SIZE
static const int32 MinSize = 32 * 1024;
static const int32 MaxSize = 2 * 1024 * 1024;
GConfig->GetInt(TEXT("Ping"), TEXT("StackSize"), StackSize, GEngineIni);
// Sanity clamp
if (StackSize != 0)
{
StackSize = FMath::Clamp(StackSize, MinSize, MaxSize);
}
#endif // PING_ALLOWS_CUSTOM_THREAD_SIZE
return StackSize;
}
} // namespace (anonymous)
// ----------------------------------------------------------------------------
void FUDPPing::UDPEchoMany(const TArray<FIcmpTarget>& Targets, float Timeout,
FIcmpEchoManyCompleteCallback CompletionCallback)
{
FIcmpEchoManyCompleteDelegate CompletionDelegate;
CompletionDelegate.BindLambda(CompletionCallback);
UDPEchoMany(Targets, Timeout, CompletionDelegate);
}
void FUDPPing::UDPEchoMany(const TArray<FIcmpTarget>& Targets, float Timeout,
FIcmpEchoManyCompleteDelegate CompletionDelegate)
{
const int32 StackSize = CalcStackSize();
ISocketSubsystem *const SocketSub = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
FUdpPingManyAsync *const PingMany = new FUdpPingManyAsync(SocketSub, CompletionDelegate);
check(PingMany);
if (PingMany)
{
PingMany->Start(Targets, Timeout, StackSize);
}
}
// ----------------------------------------------------------------------------
// UDPEcho (single)
// ----------------------------------------------------------------------------
namespace UDPPing
{
// 2 uint32 magic numbers + 64bit timecode
const SIZE_T PayloadSize = 4 * sizeof(uint32);
}
FIcmpEchoResult UDPEchoImpl(ISocketSubsystem* SocketSub, const FString& TargetAddress, float Timeout)
{
struct FUDPPingHeader
{
uint16 Id;
uint16 Sequence;
uint16 Checksum;
};
// Size of the udp header sent/received
static const SIZE_T UDPPingHeaderSize = sizeof(FUDPPingHeader);
// The packet we send is just the header plus our payload
static const SIZE_T PacketSize = UDPPingHeaderSize + UDPPing::PayloadSize;
// The result read back is just the header plus our payload;
static const SIZE_T ResultPacketSize = PacketSize;
// Location of the timecode in the buffer
static const SIZE_T TimeCodeOffset = UDPPingHeaderSize;
// Location of the payload in the buffer
static const SIZE_T MagicNumberOffset = TimeCodeOffset + sizeof(uint64);
static int PingSequence = 0;
FIcmpEchoResult Result;
Result.Status = EIcmpResponseStatus::InternalError;
FString PortStr;
TArray<FString> IpParts;
int32 NumTokens = TargetAddress.ParseIntoArray(IpParts, TEXT(":"));
FString Address = TargetAddress;
if (NumTokens == 2)
{
Address = IpParts[0];
PortStr = IpParts[1];
}
FString ResolvedAddress;
if (!ResolveIp(SocketSub, Address, ResolvedAddress))
{
Result.Status = EIcmpResponseStatus::Unresolvable;
return Result;
}
int32 Port = 0;
LexFromString(Port, *PortStr);
//ISocketSubsystem* SocketSub = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
if (SocketSub)
{
TSharedRef<FInternetAddr> ToAddr = SocketSub->CreateInternetAddr();
bool bIsValid = false;
ToAddr->SetIp(*ResolvedAddress, bIsValid);
ToAddr->SetPort(Port);
if (bIsValid)
{
uint8 SendBuffer[PacketSize];
// Clear packet buffer
FMemory::Memset(SendBuffer, 0, PacketSize);
Result.ResolvedAddress = ResolvedAddress;
FSocket* Socket = SocketSub->CreateSocket(NAME_DGram, TEXT("UDPPing"), ToAddr->GetProtocolType());
if (Socket)
{
uint16 SentId = (uint16)FPlatformProcess::GetCurrentProcessId();
uint16 SentSeq = PingSequence++;
FUDPPingHeader* PacketHeader = reinterpret_cast<FUDPPingHeader*>(SendBuffer);
PacketHeader->Id = HtoNS(SentId);
PacketHeader->Sequence = HtoNS(SentSeq);
PacketHeader->Checksum = 0;
// Put some data into the packet payload
uint32* PayloadStart = (uint32*)(SendBuffer + MagicNumberOffset);
PayloadStart[0] = HtoNL(PingDataHigh);
PayloadStart[1] = HtoNL(PingDataLow);
// Calculate the time packet is to be sent
uint64* TimeCodeStart = (uint64*)(SendBuffer + TimeCodeOffset);
uint64 TimeCode = FPlatformTime::Cycles64();
TimeCodeStart[0] = TimeCode;
// Calculate the packet checksum
PacketHeader->Checksum = CalculateChecksum(SendBuffer, PacketSize);
uint8 ResultBuffer[ResultPacketSize];
double TimeLeft = Timeout;
double StartTime = FPlatformTime::Seconds();
int32 BytesSent = 0;
if (Socket->SendTo(SendBuffer, PacketSize, BytesSent, *ToAddr))
{
bool bDone = false;
while (!bDone)
{
if (Socket->Wait(ESocketWaitConditions::WaitForRead, FTimespan::FromSeconds(TimeLeft)))
{
double EndTime = FPlatformTime::Seconds();
TimeLeft = FPlatformMath::Max(0.0, (double)Timeout - (EndTime - StartTime));
int32 BytesRead = 0;
TSharedRef<FInternetAddr> RecvAddr = SocketSub->CreateInternetAddr();
if (Socket->RecvFrom(ResultBuffer, ResultPacketSize, BytesRead, *RecvAddr))
{
if (BytesRead == ResultPacketSize)
{
uint64 NowTime = FPlatformTime::Cycles64();
Result.ReplyFrom = RecvAddr->ToString(false);
FUDPPingHeader* RecvHeader = reinterpret_cast<FUDPPingHeader*>(ResultBuffer);
// Validate the packet checksum
const uint16 RecvChecksum = RecvHeader->Checksum;
RecvHeader->Checksum = 0;
const uint16 LocalChecksum = (uint16)CalculateChecksum((uint8*)RecvHeader, PacketSize);
if (RecvChecksum == LocalChecksum)
{
// Convert values back from network byte order
RecvHeader->Id = NtoHS(RecvHeader->Id);
RecvHeader->Sequence = NtoHS(RecvHeader->Sequence);
uint32* MagicNumberPtr = (uint32*)(ResultBuffer + MagicNumberOffset);
if (MagicNumberPtr[0] == PingDataHigh && MagicNumberPtr[1] == PingDataLow)
{
// Estimate elapsed time
uint64* TimeCodePtr = (uint64*)(ResultBuffer + TimeCodeOffset);
uint64 PrevTime = *TimeCodePtr;
double DeltaTime = (NowTime - PrevTime) * FPlatformTime::GetSecondsPerCycle64();
if (Result.ReplyFrom == Result.ResolvedAddress &&
RecvHeader->Id == SentId && RecvHeader->Sequence == SentSeq &&
DeltaTime >= 0.0 && DeltaTime < (60.0 * 1000.0))
{
Result.Time = DeltaTime;
Result.Status = EIcmpResponseStatus::Success;
}
}
}
bDone = true;
}
}
else
{
// error reading from socket
bDone = true;
}
}
else
{
// timeout
Result.Status = EIcmpResponseStatus::Timeout;
Result.ReplyFrom.Empty();
Result.Time = Timeout;
bDone = true;
}
}
}
}
SocketSub->DestroySocket(Socket);
}
}
return Result;
}
class FUDPPingAsyncResult
: public FTSTickerObjectBase
{
public:
FUDPPingAsyncResult(ISocketSubsystem* InSocketSub, const FString& TargetAddress, float Timeout, uint32 StackSize, FIcmpEchoResultCallback InCallback)
: FTSTickerObjectBase(0)
, SocketSub(InSocketSub)
, Callback(InCallback)
, bThreadCompleted(false)
{
if (SocketSub)
{
bThreadCompleted = false;
TFunction<FIcmpEchoResult()> Task = [this, TargetAddress, Timeout]()
{
auto Result = UDPEchoImpl(SocketSub, TargetAddress, Timeout);
bThreadCompleted = true;
return Result;
};
// if we don't need a special stack size, use the task graph
if (StackSize == 0)
{
FutureResult = Async(EAsyncExecution::ThreadPool, Task);
}
else
{
FutureResult = AsyncThread(Task, StackSize);
}
}
else
{
bThreadCompleted = true;
}
}
virtual ~FUDPPingAsyncResult()
{
check(IsInGameThread());
if (FutureResult.IsValid())
{
FutureResult.Wait();
}
}
private:
virtual bool Tick(float DeltaTime) override
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_UDPPing_Tick);
if (bThreadCompleted)
{
FIcmpEchoResult Result;
if (FutureResult.IsValid())
{
Result = FutureResult.Get();
}
Callback(Result);
delete this;
return false;
}
return true;
}
/** Reference to the socket subsystem */
ISocketSubsystem* SocketSub;
/** Callback when the ping result returns */
FIcmpEchoResultCallback Callback;
/** Thread task complete */
FThreadSafeBool bThreadCompleted;
/** Async result future */
TFuture<FIcmpEchoResult> FutureResult;
};
void FUDPPing::UDPEcho(const FString& TargetAddress, float Timeout, FIcmpEchoResultCallback HandleResult)
{
int32 StackSize = 0;
#if PING_ALLOWS_CUSTOM_THREAD_SIZE
GConfig->GetInt(TEXT("Ping"), TEXT("StackSize"), StackSize, GEngineIni);
// Sanity clamp
if (StackSize != 0)
{
StackSize = FMath::Max<int32>(FMath::Min<int32>(StackSize, 2 * 1024 * 1024), 32 * 1024);
}
#endif
ISocketSubsystem* SocketSub = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM);
new FUDPPingAsyncResult(SocketSub, TargetAddress, Timeout, StackSize, HandleResult);
}