Integrating latest 47acbe8

This commit is contained in:
alexpete
2021-03-25 13:57:57 -07:00
parent 448c549698
commit 75dc720198
10312 changed files with 2711566 additions and 671451 deletions
@@ -1,7 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<PacketGroup Name="CorePackets" PacketStart="0">
<Packet Name="InitiateConnectionPacket" Desc="This packet is used to initiate a new connection" />
<Packet Name="InitiateConnectionPacket" Desc="This packet is used to initiate a new connection">
<Member Type="AzNetworking::UdpPacketEncodingBuffer" Name="handshakeBuffer" />
</Packet>
<Packet Name="ConnectionHandshakePacket" Desc="This packet is used to negotiate the handshake of a new connection">
<Member Type="AzNetworking::UdpPacketEncodingBuffer" Name="handshakeBuffer" />
</Packet>
<Packet Name="TerminateConnectionPacket" Desc="This packet is used to gracefully terminate an existing connection">
<Member Type="AzNetworking::DisconnectReason" Name="disconnectReason" Init="AzNetworking::DisconnectReason::None" />
@@ -12,6 +12,7 @@
#pragma once
#include <AzCore/Name/Name.h>
#include <AzCore/PlatformDef.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
@@ -92,7 +93,14 @@ namespace AzNetworking
{
public:
virtual ~ICompressorFactory() = default;
//! Instantiate a new compressor
//! @return A unique_ptr to a new Compressor
virtual AZStd::unique_ptr<ICompressor> Create() = 0;
//! Gets the AZ Name of this compressor factory
//! @return the AZ Name of this compressor factory
virtual AZ::Name GetFactoryName() const = 0;
};
}
@@ -12,6 +12,7 @@
#pragma once
#include <AzNetworking/Framework/ICompressor.h>
#include <AzNetworking/Framework/INetworkInterface.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/ConnectionLayer/IConnectionListener.h>
@@ -45,5 +46,19 @@ namespace AzNetworking
//! @param name the name of the network interface to destroy
//! @return boolean true on success or false on failure
virtual bool DestroyNetworkInterface(AZ::Name name) = 0;
//! Registers a Compressor Factory that can be used to create compressors for INetworkInterfaces
//! @param factory The ICompressorFactory to register
virtual void RegisterCompressorFactory(ICompressorFactory* factory) = 0;
//! Creates a compressor using a registered factory looked up by name
//! @param name The name of the Compressor Factory to use, must match result of factory->GetFactoryName()
//! @return A unique_ptr to the new compressor
virtual AZStd::unique_ptr<ICompressor> CreateCompressor(AZ::Name name) = 0;
//! Unregisters the compressor factory
//! @param name The name of the Compressor factory to unregister, must match result of factory->GetFactoryName()
//! @return Whether the factory was found and unregistered
virtual bool UnregisterCompressorFactory(AZ::Name name) = 0;
};
}
@@ -26,6 +26,8 @@ namespace AzNetworking
AZ::TimeMs m_sendTimeMs = AZ::TimeMs{ 0 };
//! Returns the total number of packets sent on this socket.
uint64_t m_sendPackets = 0;
//! Returns the total number of encrypted packets sent on this socket.
uint64_t m_sendPacketsEncrypted = 0;
//! Returns the total number of bytes sent on this socket after compression.
uint64_t m_sendBytes = 0;
//! Returns the total number of bytes sent on this socket before compression.
@@ -34,6 +36,8 @@ namespace AzNetworking
uint64_t m_sendCompressedPacketsNoGain = 0;
//! Returns the delta gain of bytes saved (+) or lost (-) due to compression.
int64_t m_sendBytesCompressedDelta = 0;
//! Returns the numbers of bytes added by encryption.
uint64_t m_sendBytesEncryptionInflation = 0;
//! Returns the total number of packets that had to be resent on this network interface due to packet loss.
uint64_t m_resentPackets = 0;
//! Returns the total number of milliseconds spent processing received data on this network interface.
@@ -42,7 +42,7 @@ namespace AzNetworking
NetworkingSystemComponent::NetworkingSystemComponent()
{
SocketLayerInit();
//EncryptionLayerInit();
EncryptionLayerInit();
AZ::Interface<INetworking>::Register(this);
m_listenThread = AZStd::make_unique<TcpListenThread>();
@@ -53,11 +53,14 @@ namespace AzNetworking
{
// Delete all our network interfaces first so they can unregister from the reader and listen threads
m_networkInterfaces.clear();
m_compressorFactories.clear();
m_readerThread = nullptr;
m_listenThread = nullptr;
AZ::Interface<INetworking>::Unregister(this);
//EncryptionLayerShutdown();
EncryptionLayerShutdown();
SocketLayerShutdown();
}
@@ -123,6 +126,29 @@ namespace AzNetworking
return m_networkInterfaces.erase(name) > 0;
}
void NetworkingSystemComponent::RegisterCompressorFactory(ICompressorFactory* factory)
{
AZ_Assert(m_compressorFactories.find(factory->GetFactoryName()) == m_compressorFactories.end(), "A compressor factory with this name already exists");
m_compressorFactories.emplace(factory->GetFactoryName(), factory);
}
AZStd::unique_ptr<ICompressor> NetworkingSystemComponent::CreateCompressor(AZ::Name name)
{
auto compressorFactory = m_compressorFactories.find(name);
if(compressorFactory != m_compressorFactories.end())
{
return compressorFactory->second->Create();
}
return nullptr;
}
bool NetworkingSystemComponent::UnregisterCompressorFactory(AZ::Name name)
{
return m_compressorFactories.erase(name) > 0;
}
void NetworkingSystemComponent::DumpStats([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
{
AZLOG_INFO("Total sockets monitored by TcpListenThread: %u", m_listenThread->GetSocketCount());
@@ -13,6 +13,7 @@
#pragma once
#include <AzCore/Name/Name.h>
#include <AzNetworking/Framework/ICompressor.h>
#include <AzNetworking/Framework/INetworking.h>
#include <AzNetworking/Framework/INetworkInterface.h>
#include <AzNetworking/TcpTransport/TcpListenThread.h>
@@ -59,6 +60,9 @@ namespace AzNetworking
INetworkInterface* CreateNetworkInterface(AZ::Name name, ProtocolType protocolType, TrustZone trustZone, IConnectionListener& listener) override;
INetworkInterface* RetrieveNetworkInterface(AZ::Name name) override;
bool DestroyNetworkInterface(AZ::Name name) override;
void RegisterCompressorFactory(ICompressorFactory* factory) override;
AZStd::unique_ptr<ICompressor> CreateCompressor(AZ::Name name) override;
bool UnregisterCompressorFactory(AZ::Name name) override;
//! @}
//! Console commands.
@@ -74,5 +78,8 @@ namespace AzNetworking
NetworkInterfaces m_networkInterfaces;
AZStd::unique_ptr<TcpListenThread> m_listenThread;
AZStd::unique_ptr<UdpReaderThread> m_readerThread;
using CompressionFactories = AZStd::unordered_map<AZ::Name, AZStd::unique_ptr<ICompressorFactory>>;
CompressionFactories m_compressorFactories;
};
}
@@ -10,6 +10,7 @@
*
*/
#include <AzNetworking/Framework/INetworking.h>
#include <AzNetworking/TcpTransport/TcpConnection.h>
#include <AzNetworking/TcpTransport/TcpPacketHeader.h>
#include <AzNetworking/TcpTransport/TcpNetworkInterface.h>
@@ -17,7 +18,6 @@
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
#include <AzNetworking/ConnectionLayer/IConnectionSet.h>
#include <AzNetworking/Framework/ICompressor.h>
#include <AzNetworking/Utilities/CompressionCommon.h>
#include <AzNetworking/AutoGen/CorePackets.AutoPackets.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
@@ -61,8 +61,8 @@ namespace AzNetworking
, m_registeredSocketFd(InvalidSocketFd)
{
const AZ::CVarFixedString compressor = static_cast<AZ::CVarFixedString>(net_TcpCompressor);
const char* compressorName = compressor.c_str();
m_compressor = CreateCompressor(compressorName);
const AZ::Name compressorName = AZ::Name(compressor);
m_compressor = AZ::Interface<INetworking>::Get()->CreateCompressor(compressorName);
if (useEncryption)
{
@@ -115,6 +115,12 @@ namespace AzNetworking
m_sendRingbuffer.AdvanceReadBuffer(sentBytes);
m_networkInterface.GetMetrics().m_sendBytes += numSendBytes;
m_networkInterface.GetMetrics().m_sendBytesUncompressed += numSendBytes;
if (m_socket->IsEncrypted() && sentBytes > 0)
{
m_networkInterface.GetMetrics().m_sendBytesEncryptionInflation += (aznumeric_cast<uint32_t>(sentBytes) - numSendBytes);
m_networkInterface.GetMetrics().m_sendPacketsEncrypted++;
}
}
bool TcpConnection::UpdateRecv()
@@ -34,7 +34,7 @@ namespace AzNetworking
return true;
}
void TcpSocketManager::ProcessEvents(AZ::TimeMs maxBlockMs, const SocketEventCallback& readCallback, const SocketEventCallback& writeCallback)
void TcpSocketManager::ProcessEvents([[maybe_unused]]AZ::TimeMs maxBlockMs, const SocketEventCallback& readCallback, const SocketEventCallback& writeCallback)
{
// No edge triggering, just brute force iterate all socketFds and invoke the callbacks
for (auto socketFd : m_socketFds)
@@ -176,7 +176,7 @@ namespace AzNetworking
TcpSocket::Close();
}
int32_t TlsSocket::SendInternal(const uint8_t* data, uint32_t size) const
int32_t TlsSocket::SendInternal([[maybe_unused]] const uint8_t* data, [[maybe_unused]] uint32_t size) const
{
if (m_sslSocket == nullptr)
{
@@ -201,7 +201,7 @@ namespace AzNetworking
#endif
}
int32_t TlsSocket::ReceiveInternal(uint8_t* outData, uint32_t size) const
int32_t TlsSocket::ReceiveInternal([[maybe_unused]] uint8_t* outData, [[maybe_unused]] uint32_t size) const
{
if (m_sslSocket == nullptr)
{
@@ -12,6 +12,7 @@
#include <AzNetworking/UdpTransport/DtlsEndpoint.h>
#include <AzNetworking/UdpTransport/DtlsSocket.h>
#include <AzNetworking/UdpTransport/UdpConnection.h>
#include <AzNetworking/Utilities/EncryptionCommon.h>
#include <AzNetworking/Utilities/NetworkIncludes.h>
#include <AzCore/Console/IConsole.h>
@@ -57,14 +58,8 @@ namespace AzNetworking
return result;
}
DtlsEndpoint::ConnectResult DtlsEndpoint::Accept(const DtlsSocket& socket, const IpAddress& address, const UdpPacketEncodingBuffer& dtlsData)
DtlsEndpoint::ConnectResult DtlsEndpoint::Accept(const DtlsSocket& socket, const IpAddress& address)
{
if (dtlsData.GetSize() <= 0)
{
AZLOG_WARN("Encryption is enabled on accepting endpoint, but connector provided an empty DTLS handshake blob. Check that encryption is properly disabled on *BOTH* endpoints");
return DtlsEndpoint::ConnectResult::Failed;
}
const ConnectResult result = ConstructEndpointInternal(socket, address);
#if AZ_TRAIT_USE_OPENSSL
if (result != ConnectResult::Failed)
@@ -72,10 +67,7 @@ namespace AzNetworking
// This SSL should be configured to accept connections
SSL_set_accept_state(m_sslSocket);
m_state = HandshakeState::Accepting;
const uint8_t* encryptedData = dtlsData.GetBuffer();
const uint32_t encryptedSize = dtlsData.GetSize();
BIO_write(m_readBio, encryptedData, encryptedSize);
return CompleteHandshake(socket);
return ConnectResult::Pending;
}
#endif
return result;
@@ -88,49 +80,53 @@ namespace AzNetworking
|| (m_state == HandshakeState::Failed)); // In all cases caller should call CompleteHandshake() next and check the return value
}
DtlsEndpoint::ConnectResult DtlsEndpoint::CompleteHandshake(const UdpSocket& socket)
DtlsEndpoint::ConnectResult DtlsEndpoint::ProcessHandshakeData([[maybe_unused]] UdpConnection& connection, [[maybe_unused]] const UdpPacketEncodingBuffer& dtlsData)
{
UdpPacketEncodingBuffer responseData;
const ConnectResult result = PerformHandshakeInternal(responseData);
if ((result != ConnectResult::Failed) && (responseData.GetSize() > 0))
ConnectResult result = ConnectResult::Failed;
#if AZ_TRAIT_USE_OPENSSL
UdpPacketEncodingBuffer outDtlsData;
if (dtlsData.GetSize() > 0)
{
struct sockaddr_in dest;
memset(&dest, 0, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = m_address.GetAddress(ByteOrder::Network);
dest.sin_port = m_address.GetPort(ByteOrder::Network);
sendto(static_cast<int32_t>(socket.GetSocketFd()), reinterpret_cast<char*>(responseData.GetBuffer()), responseData.GetSize(), 0, (sockaddr*)&dest, sizeof(dest));
AZLOG(NET_DebugDtls, "Replying to DTLS handshake datagram, %u bytes", static_cast<int32_t>(responseData.GetSize()));
const uint8_t* encryptedData = dtlsData.GetBuffer();
const uint32_t encryptedSize = dtlsData.GetSize();
BIO_write(m_readBio, encryptedData, encryptedSize);
}
DtlsEndpoint::HandshakeState prevState = m_state;
result = PerformHandshakeInternal(outDtlsData);
// Pass along any remaining handshake data
// If we're the connecting endpoint and the handshake is complete, both sides are encrypted and this isn't necessary so skip it
bool continueHandshake = prevState != DtlsEndpoint::HandshakeState::Connecting || m_state != DtlsEndpoint::HandshakeState::Complete;
if (outDtlsData.GetSize() > 0 && continueHandshake)
{
CorePackets::ConnectionHandshakePacket handshakePacket = CorePackets::ConnectionHandshakePacket();
handshakePacket.SetHandshakeBuffer(outDtlsData);
// SSL prefers we handle resend by reobtaining data through SSL so we use unreliable and resend on timeout
connection.SendUnreliablePacket(handshakePacket);
}
#endif
return result;
}
const uint8_t* DtlsEndpoint::DecodePacket
(
[[maybe_unused]] const UdpSocket& socket,
[[maybe_unused]] const uint8_t* encryptedData,
[[maybe_unused]] int32_t encryptedSize,
[[maybe_unused]] uint8_t* outDecodedData,
[[maybe_unused]] int32_t& outDecodedSize
)
const uint8_t* DtlsEndpoint::DecodePacket([[maybe_unused]] UdpConnection& connection, const uint8_t* encryptedData, int32_t encryptedSize, uint8_t* outDecodedData, int32_t& outDecodedSize)
{
if (m_sslSocket == nullptr)
if (m_sslSocket == nullptr || IsConnecting())
{
// If the ssl socket is nullptr, it means encryption is not enabled, just passthrough the received data
// If the socket is connecting/handshaking, it means we can't yet encrypt, just passthrough the received data
outDecodedSize = encryptedSize;
return encryptedData;
}
#if AZ_TRAIT_USE_OPENSSL
BIO_write(m_readBio, encryptedData, encryptedSize);
if (IsConnecting())
{
CompleteHandshake(socket);
outDecodedSize = 0;
}
// CompleteHandshake() above may have failed and destroyed the SSL context, check here that state is valid so we don't crash on SSL_read
int32_t bioWriteSize = BIO_write(m_readBio, encryptedData, encryptedSize);
if (m_state != HandshakeState::Failed)
{
if (bioWriteSize != encryptedSize)
{
AZLOG_ERROR("BIO did not write as many bytes as provided");
}
outDecodedSize = SSL_read(m_sslSocket, outDecodedData, encryptedSize);
}
#endif
@@ -175,13 +171,6 @@ namespace AzNetworking
ConnectResult connectResult = ConnectResult::Pending;
#if AZ_TRAIT_USE_OPENSSL
if (SSL_is_init_finished(m_sslSocket))
{
const char* stateString = GetEnumString(m_state);
AZLOG(NET_DebugDtls, "dtls handshake is completed, unblocking connection for game traffic, prior state: %s", stateString);
m_state = HandshakeState::Complete;
connectResult = ConnectResult::Complete;
}
ERR_clear_error();
const int32_t result = SSL_do_handshake(m_sslSocket);
if (result <= 0)
@@ -200,6 +189,14 @@ namespace AzNetworking
}
}
if (SSL_is_init_finished(m_sslSocket))
{
const char* stateString = GetEnumString(m_state);
AZLOG(NET_DebugDtls, "dtls handshake is completed, unblocking connection for game traffic, prior state: %s", stateString);
m_state = HandshakeState::Complete;
connectResult = ConnectResult::Complete;
}
// Need to do this... connection negotiation may have left data in the write bio that we need to send out
if (BIO_ctrl_pending(m_writeBio) > 0)
{
@@ -22,6 +22,7 @@ typedef struct bio_st BIO;
namespace AzNetworking
{
class UdpConnection;
class UdpSocket;
class DtlsSocket;
@@ -61,30 +62,30 @@ namespace AzNetworking
ConnectResult Connect(const DtlsSocket& socket, const IpAddress& address, UdpPacketEncodingBuffer& outDtlsData);
//! Accepts a connection from the remote encrypted endpoint.
//! @param socket the dtls socket being used for data transmission
//! @param address the address of the remote endpoint connecting to us
//! @param dtlsData data buffer containing the initial dtls handshake packet
//! @param socket the dtls socket being used for data transmission
//! @param address the address of the remote endpoint connecting to us
//! @return a connect result specifying whether the connection is still pending, failed, or complete
ConnectResult Accept(const DtlsSocket& socket, const IpAddress& address, const UdpPacketEncodingBuffer& dtlsData);
ConnectResult Accept(const DtlsSocket& socket, const IpAddress& address);
//! Returns whether or not the endpoint is still negotiating the dtls handshake.
//! @return true if the endpoint is still in a connecting state
bool IsConnecting() const;
//! Attempts to complete the dtls handshake and establish an encrypted connection.
//! @param socket the dtls socket being used for data transmission
//! @param connection the UDP connection being used for data transmission
//! @param dtlsData data buffer containing dtls handshake packet
//! @return a connect result specifying whether the connection is still pending, failed, or complete
ConnectResult CompleteHandshake(const UdpSocket& socket);
ConnectResult ProcessHandshakeData(UdpConnection& connection, const UdpPacketEncodingBuffer& dtlsData);
//! If the endpoint has encryption enabled, this will decrypt the transmitted data and return the result.
//! @note sizes have to be signed since OpenSSL often returns negative values to represent error results
//! @param socket the DTLS socket being used for data transmission
//! @param connection the UDP connection being used for data transmission
//! @param encryptedData the potentially encrypted data received from the socket
//! @param encryptedSize the size of the received raw data
//! @param outDecodedData an appropriately sized output buffer to store decrypted data
//! @param outDecodedSize the size of the output buffer
//! @return pointer to the decoded data
const uint8_t* DecodePacket(const UdpSocket& socket, const uint8_t* encryptedData, int32_t encryptedSize, uint8_t* outDecodedData, int32_t& outDecodedSize);
const uint8_t* DecodePacket(UdpConnection& connection, const uint8_t* encryptedData, int32_t encryptedSize, uint8_t* outDecodedData, int32_t& outDecodedSize);
private:
@@ -10,6 +10,7 @@
*
*/
#include <AzNetworking/Framework/INetworkInterface.h>
#include <AzNetworking/UdpTransport/DtlsSocket.h>
#include <AzCore/Console/ILogger.h>
@@ -35,9 +36,9 @@ namespace AzNetworking
return dtlsEndpoint.Connect(*this, address, outDtlsData);
}
DtlsEndpoint::ConnectResult DtlsSocket::AcceptDtlsEndpoint(DtlsEndpoint& dtlsEndpoint, const IpAddress& address, const UdpPacketEncodingBuffer& dtlsData) const
DtlsEndpoint::ConnectResult DtlsSocket::AcceptDtlsEndpoint(DtlsEndpoint& dtlsEndpoint, const IpAddress& address) const
{
return dtlsEndpoint.Accept(*this, address, dtlsData);
return dtlsEndpoint.Accept(*this, address);
}
bool DtlsSocket::Open(uint16_t port, CanAcceptConnections canAccept, TrustZone trustZone)
@@ -91,6 +92,11 @@ namespace AzNetworking
// Write out the packet we were requested to send
const int32_t sentBytesRaw = SSL_write(dtlsEndpoint.m_sslSocket, data, size);
const int32_t sentBytesEnc = BIO_read(dtlsEndpoint.m_writeBio, encrpytedSendBuffer, sizeof(encrpytedSendBuffer));
// Track encryption metrics
m_sentBytesEncryptionInflation += aznumeric_cast<uint32_t>(sentBytesEnc - aznumeric_cast<int32_t>(size));
m_sentPacketsEncrypted++;
return UdpSocket::SendInternal(address, encrpytedSendBuffer, sentBytesEnc, encrypt, dtlsEndpoint);
#else
return 0;
@@ -46,9 +46,8 @@ namespace AzNetworking
//! Accepts an encryption socket wrapper.
//! @param dtlsEndpoint the encryption wrapper instance to create a connection over
//! @param address the IP address of the endpoint to connect to
//! @param dtlsData data buffer containing the dtls handshake packet
//! @return a connect result specifying whether the connection is still pending, failed, or complete
DtlsEndpoint::ConnectResult AcceptDtlsEndpoint(DtlsEndpoint& dtlsEndpoint, const IpAddress& address, const UdpPacketEncodingBuffer& dtlsData) const override;
DtlsEndpoint::ConnectResult AcceptDtlsEndpoint(DtlsEndpoint& dtlsEndpoint, const IpAddress& address) const override;
//! Opens the UDP socket on the given port.
//! @param port the port number to open the UDP socket on, 0 will bind to any available port
@@ -66,9 +66,9 @@ namespace AzNetworking
}
}
DtlsEndpoint::ConnectResult UdpConnection::CompleteHandshake()
DtlsEndpoint::ConnectResult UdpConnection::ProcessHandshakeData(const UdpPacketEncodingBuffer& dtlsData)
{
const DtlsEndpoint::ConnectResult result = m_dtlsEndpoint.CompleteHandshake(*(m_networkInterface.m_socket));
const DtlsEndpoint::ConnectResult result = m_dtlsEndpoint.ProcessHandshakeData(*this, dtlsData);
if (result == DtlsEndpoint::ConnectResult::Failed)
{
Disconnect(DisconnectReason::NetworkError, TerminationEndpoint::Local);
@@ -249,6 +249,27 @@ namespace AzNetworking
}
break;
case CorePackets::PacketType::ConnectionHandshakePacket:
{
AZLOG(NET_CorePackets, "Received core packet %s", "ConnectionHandshakePacket");
CorePackets::ConnectionHandshakePacket packet;
if (!serializer.Serialize(packet, "Packet"))
{
return false;
}
if (m_state != ConnectionState::Connected)
{
if (ProcessHandshakeData(packet.GetHandshakeBuffer()) == DtlsEndpoint::ConnectResult::Complete)
{
m_state = ConnectionState::Connected;
}
}
return true;
}
break;
case CorePackets::PacketType::TerminateConnectionPacket:
{
AZLOG(NET_CorePackets, "Received core packet %s", "TerminateConnection");
@@ -38,6 +38,7 @@ namespace AzNetworking
class UdpConnection
: public IConnection
{
friend class UdpFragmentQueue;
friend class UdpNetworkInterface;
public:
@@ -50,9 +51,10 @@ namespace AzNetworking
UdpConnection(ConnectionId connectionId, const IpAddress& remoteAddress, UdpNetworkInterface& networkInterface, ConnectionRole connectionRole);
~UdpConnection() override;
//! Helper to complete dtls handshake logic on a newly established connection
//! Helper to exchange dtls handshake data during connection handshake
//! @param dtlsData data buffer containing dtls handshake packet
//! @return the current result code for the dtls handshake operation, failed, pending, or complete
DtlsEndpoint::ConnectResult CompleteHandshake();
DtlsEndpoint::ConnectResult ProcessHandshakeData(const UdpPacketEncodingBuffer& dtlsData);
//! Updates the connection heartbeat if active.
//! @param currentTimeMs current wall clock time in milliseconds
@@ -155,7 +155,17 @@ namespace AzNetworking
}
}
connection->GetPacketTracker().ProcessReceived(connection, header);
return connectionListener.OnPacketReceived(connection, header, networkSerializer);
bool handledPacket = false;
if (header.GetPacketType() < aznumeric_cast<PacketType>(CorePackets::PacketType::MAX))
{
handledPacket = connection->HandleCorePacket(connectionListener, header, networkSerializer);
}
else
{
handledPacket = connectionListener.OnPacketReceived(connection, header, networkSerializer);
}
return handledPacket;
}
TimeoutResult UdpFragmentQueue::HandleTimeout(TimeoutQueue::TimeoutItem& item)
@@ -31,6 +31,7 @@ namespace AzNetworking
class UdpFragmentQueue
: public ITimeoutHandler
{
public:
//! Updates the UdpFragmentQueue timeout queue.
@@ -10,6 +10,7 @@
*
*/
#include <AzNetworking/Framework/INetworking.h>
#include <AzNetworking/UdpTransport/UdpNetworkInterface.h>
#include <AzNetworking/UdpTransport/UdpConnection.h>
#include <AzNetworking/UdpTransport/DtlsSocket.h>
@@ -17,7 +18,6 @@
#include <AzNetworking/Serialization/NetworkInputSerializer.h>
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
#include <AzNetworking/Framework/ICompressor.h>
#include <AzNetworking/Utilities/CompressionCommon.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
@@ -26,8 +26,11 @@ namespace AzNetworking
{
#if AZ_TRAIT_USE_OPENSSL
AZ_CVAR(bool, net_UdpUseEncryption, false, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Enable encryption on Udp based connections");
AZ_CVAR(uint32_t, net_SslInflationOverhead, 32, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "A SSL fudge overhead value to take out of fragmented packet payloads");
#else
static const bool net_UdpUseEncryption = false;
static const uint32_t net_SslInflationOverhead = 0;
#endif
AZ_CVAR(bool, net_UdpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Udp connections");
@@ -64,8 +67,8 @@ namespace AzNetworking
, m_readerThread(readerThread)
{
const AZ::CVarFixedString compressor = static_cast<AZ::CVarFixedString>(net_UdpCompressor);
const char* compressorName = compressor.c_str();
m_compressor = CreateCompressor(compressorName);
const AZ::Name compressorName = AZ::Name(compressor);
m_compressor = AZ::Interface<INetworking>::Get()->CreateCompressor(compressorName);
}
UdpNetworkInterface::~UdpNetworkInterface()
@@ -122,7 +125,7 @@ namespace AzNetworking
{
if (!m_socket->IsOpen())
{
m_socket->Open(m_port, UdpSocket::CanAcceptConnections::True, m_trustZone);
m_socket->Open(m_port, UdpSocket::CanAcceptConnections::False, m_trustZone);
m_readerThread.RegisterSocket(m_socket.get());
}
@@ -137,7 +140,12 @@ namespace AzNetworking
connection->m_state = ConnectionState::Connecting;
connection->SetConnectionMtu(MaxUdpTransmissionUnit);
connection->SetTimeoutId(timeoutId);
connection->SendReliablePacket(CorePackets::InitiateConnectionPacket());
// Signal the connection attempt
CorePackets::InitiateConnectionPacket connectPacket = CorePackets::InitiateConnectionPacket();
connectPacket.SetHandshakeBuffer(dtlsData);
connection->SendReliablePacket(connectPacket);
m_connectionListener.OnConnect(connection.get());
m_connectionSet.AddConnection(AZStd::move(connection));
return connectionId;
@@ -150,10 +158,6 @@ namespace AzNetworking
return;
}
#ifdef ENABLE_LATENCY_DEBUG
m_socket->ProcessDeferredPackets();
#endif
const AZ::TimeMs startTimeMs = AZ::GetElapsedTimeMs();
const UdpReaderThread::ReceivedPackets* packets = m_readerThread.GetReceivedPackets(m_socket.get());
if (packets == nullptr)
@@ -191,7 +195,7 @@ namespace AzNetworking
int32_t decodedPacketSize = 0;
m_decryptBuffer.Resize(m_decryptBuffer.GetCapacity());
const uint8_t* decodedPacketData = connection->GetDtlsEndpoint().DecodePacket(*m_socket, packet.m_buffer, packet.m_receivedBytes, m_decryptBuffer.GetBuffer(), decodedPacketSize);
const uint8_t* decodedPacketData = connection->GetDtlsEndpoint().DecodePacket(*connection, packet.m_buffer, packet.m_receivedBytes, m_decryptBuffer.GetBuffer(), decodedPacketSize);
m_decryptBuffer.Resize(decodedPacketSize);
if (decodedPacketSize == 0)
@@ -201,8 +205,7 @@ namespace AzNetworking
}
else if (decodedPacketSize < 0)
{
// Something bad happened on the SSL read and we're now invalid, we should disconnect
connection->Disconnect(DisconnectReason::SslFailure, TerminationEndpoint::Local);
// Late unencrypted handshake packets or just random garbage can show up, discard and continue
continue;
}
@@ -233,8 +236,8 @@ namespace AzNetworking
}
decodedPacketData = m_decompressBuffer.GetBuffer();
decodedPacketSize = m_decompressBuffer.GetSize();
GetMetrics().m_recvBytesUncompressed += decodedPacketSize;
}
GetMetrics().m_recvBytesUncompressed += decodedPacketSize;
TimeoutQueue::TimeoutItem* timeoutItem = m_connectionTimeoutQueue.RetrieveItem(connection->GetTimeoutId());
if (timeoutItem == nullptr)
@@ -273,11 +276,19 @@ namespace AzNetworking
if (handledPacket)
{
connection->UpdateHeartbeat(currentTimeMs);
if (connection->GetConnectionState() == ConnectionState::Connecting)
if (connection->GetConnectionState() == ConnectionState::Connecting && !connection->GetDtlsEndpoint().IsConnecting())
{
// Connection is realized once a packet is received and socket handshake is verified complete
connection->m_state = ConnectionState::Connected;
}
}
else if (m_socket->IsEncrypted() && connection->GetDtlsEndpoint().IsConnecting() &&
!IsHandshakePacket(connection->GetDtlsEndpoint(), header.GetPacketType()))
{
// It's possible for one side to finish its half of the handshake and start sending encrypted data
// If it's not an expected unencrypted type then skip it for now
continue;
}
else if (connection->GetConnectionState() != ConnectionState::Disconnecting)
{
connection->Disconnect(DisconnectReason::StreamError, TerminationEndpoint::Local);
@@ -309,6 +320,8 @@ namespace AzNetworking
// Update metrics
GetMetrics().m_sendPackets = m_socket->GetSentPackets();
GetMetrics().m_sendBytes = m_socket->GetSentBytes();
GetMetrics().m_sendPacketsEncrypted = m_socket->GetSentPacketsEncrypted();
GetMetrics().m_sendBytesEncryptionInflation = m_socket->GetSentBytesEncryptionInflation();
GetMetrics().m_recvTimeMs += receiveTimeMs;
GetMetrics().m_recvPackets = m_socket->GetRecvPackets();
GetMetrics().m_recvBytes = m_socket->GetRecvBytes();
@@ -410,8 +423,7 @@ namespace AzNetworking
// The ordering inside this function is incredibly important and fragile
const IpAddress& address = connection.GetRemoteAddress();
// We don't want to compress the initial InitiateConnectionPacket
// We can use this to transmit compression and encryption details in the future
// We don't want to compress the initial InitiateConnectionPacket, ConnectionHandshakePackets or FragmentedPackets of those two
const bool shouldCompress = packet.GetPacketType() != aznumeric_cast<PacketType>(CorePackets::PacketType::InitiateConnectionPacket);
if (address.GetAddress(ByteOrder::Host) == 0)
@@ -427,6 +439,29 @@ namespace AzNetworking
UdpPacketHeader header(connection.GetPacketTracker(), packet.GetPacketType(), reliableSequence);
const PacketId localPacketId = header.GetPacketId();
// If it's a reliable packet, make sure our reliable queue knows about it now because we might need to drop it if our connection is
// not set up
if (reliabilityType == ReliabilityType::Reliable)
{
if (!connection.PrepareReliablePacketForSend(localPacketId, reliableSequence, packet))
{
connection.Disconnect(DisconnectReason::ReliableQueueFull, TerminationEndpoint::Local);
}
}
// If we're still connecting, only transmit packets related to establishing connection and queue the rest for later
// This implicitly enforces that the only FragmentedPackets sent here are of ConnectionHandshakePacket
// Other large packets are simply queued before they are fragmented
if (connection.GetDtlsEndpoint().IsConnecting() && !IsHandshakePacket(connection.GetDtlsEndpoint(), packet.GetPacketType()))
{
// IMPORTANT that we register with the timeout queue here, otherwise we don't have the timer to pop for reliable packets
RegisterWithTimeoutQueue(connection.GetConnectionId(), localPacketId, reliabilityType, connection.GetMetrics());
AZLOG(
NET_DebugDtls, "Connection is still in handshake negotiation, blocking packet send for packet type %d",
(int)packet.GetPacketType());
return localPacketId;
}
UdpPacketEncodingBuffer buffer;
{
buffer.Resize(buffer.GetCapacity());
@@ -457,11 +492,12 @@ namespace AzNetworking
uint32_t packetSize = buffer.GetSize();
uint8_t* packetData = buffer.GetBuffer();
// If the packet doesn't fit within our MTU, break it up
if (packetSize > connection.GetConnectionMtu())
// If the packet doesn't fit within our MTU (minus potential SSL encryption overhead), break it up
if (packetSize > connection.GetConnectionMtu() - net_SslInflationOverhead)
{
// Each fragmented packet we send adds an extra fragmented packet header, need to deduct that from our chunk size, otherwise we infinitely loop
const uint32_t chunkSize = connection.GetConnectionMtu() - net_FragmentedHeaderOverhead;
// SSL encryption can also inflate our payload so we pre-emptively deduct an estimated tax
const uint32_t chunkSize = connection.GetConnectionMtu() - net_FragmentedHeaderOverhead - net_SslInflationOverhead;
const uint32_t numChunks = (packetSize + chunkSize - 1) / chunkSize; // We want to round up on the remainder
const uint8_t* chunkStart = packetData;
const SequenceId fragmentedSequence = connection.m_fragmentQueue.GetNextFragmentedSequenceId();
@@ -529,30 +565,9 @@ namespace AzNetworking
aznumeric_cast<uint32_t>(header.GetSequenceWindow())
);
// If it's a reliable packet, make sure our reliable queue knows about it now because we might need to drop it if our connection is not set up
if (reliabilityType == ReliabilityType::Reliable)
{
if (!connection.PrepareReliablePacketForSend(localPacketId, reliableSequence, packet))
{
connection.Disconnect(DisconnectReason::ReliableQueueFull, TerminationEndpoint::Local);
}
}
// Okay, if we're still connecting, and the packet we're trying to send is not a retransmitted initiate connection packet, return an error, don't send yet
if (connection.GetDtlsEndpoint().IsConnecting() && (packet.GetPacketType() != aznumeric_cast<PacketType>(CorePackets::PacketType::InitiateConnectionPacket)))
{
const DtlsEndpoint::ConnectResult result = connection.CompleteHandshake();
if (result != DtlsEndpoint::ConnectResult::Complete) // DTLS handshake still in progress
{
// IMPORTANT that we register with the timeout queue here, otherwise we don't have the timer to pop for reliable packets
RegisterWithTimeoutQueue(connection.GetConnectionId(), localPacketId, reliabilityType, connection.GetMetrics());
AZLOG(NET_DebugDtls, "Connection is still in handshake negotiation, blocking packet send for packet type %d", (int)packet.GetPacketType());
return localPacketId;
}
}
AZLOG(NET_DebugDtls, "Connection is sending packet type %d", aznumeric_cast<int32_t>(packet.GetPacketType()));
const bool shouldEncrypt = packet.GetPacketType() == aznumeric_cast<PacketType>(CorePackets::PacketType::InitiateConnectionPacket);
// If we're not connected then we're still handshaking and require packets to be unencrypted
const bool shouldEncrypt = !IsHandshakePacket(connection.GetDtlsEndpoint(), packet.GetPacketType());
if (m_socket->Send(address, packetData, packetSize, shouldEncrypt, connection.GetDtlsEndpoint(), connection.GetConnectionQuality()))
{
RegisterWithTimeoutQueue(connection.GetConnectionId(), localPacketId, reliabilityType, connection.GetMetrics());
@@ -626,11 +641,10 @@ namespace AzNetworking
AZLOG(Debug_UdpConnect, "Accepted new Udp Connection");
AZStd::unique_ptr<UdpConnection> connection = AZStd::make_unique<UdpConnection>(connectionId, connectPacket.m_address, *this, ConnectionRole::Acceptor);
UdpPacketEncodingBuffer dtlsData;
m_socket->AcceptDtlsEndpoint(connection->GetDtlsEndpoint(), connectPacket.m_address, dtlsData);
DtlsEndpoint::ConnectResult result = m_socket->AcceptDtlsEndpoint(connection->GetDtlsEndpoint(), connectPacket.m_address);
// We're accepting this connection, so we can immediately transition to a connected state
connection->m_state = ConnectionState::Connected;
// Transition state based on our how our socket resolved
connection->m_state = result == DtlsEndpoint::ConnectResult::Complete ? ConnectionState::Connected : ConnectionState::Connecting;
connection->SetTimeoutId(timeoutId);
m_connectionListener.OnConnect(connection.get());
m_connectionSet.AddConnection(AZStd::move(connection));
@@ -646,6 +660,14 @@ namespace AzNetworking
m_removedConnections.emplace_back(RemovedConnection{ connection, reason, endpoint });
}
bool UdpNetworkInterface::IsHandshakePacket(const DtlsEndpoint& endpoint, PacketType packetType) const
{
// Packets involved in handshake are InitiateConnection, ConnectionHandshake and FragmentedPackets of ConnectionHandshake
return packetType == aznumeric_cast<PacketType>(CorePackets::PacketType::InitiateConnectionPacket) ||
packetType == aznumeric_cast<PacketType>(CorePackets::PacketType::ConnectionHandshakePacket) ||
(packetType == aznumeric_cast<PacketType>(CorePackets::PacketType::FragmentedPacket) && endpoint.IsConnecting());
}
UdpNetworkInterface::ConnectionTimeoutFunctor::ConnectionTimeoutFunctor(UdpNetworkInterface& networkInterface)
: m_networkInterface(networkInterface)
@@ -668,7 +690,9 @@ namespace AzNetworking
{
if (udpConnection->GetDtlsEndpoint().IsConnecting())
{
udpConnection->CompleteHandshake();
// DTLS prefers we resend data lost over the wire with fresh SSL IDs so account for that here
UdpPacketEncodingBuffer dtlsData;
udpConnection->ProcessHandshakeData(dtlsData);
return TimeoutResult::Refresh;
}
}
@@ -104,6 +104,12 @@ namespace AzNetworking
//! @param endpoint whether the disconnection was initiated locally or remotely
void RequestDisconnect(UdpConnection* connection, DisconnectReason reason, TerminationEndpoint endpoint);
//! Internal helper to check if a packet's type is for connection handshake
//! @param endpoint DTLS endpoint participating in the handshake
//! @param packetType type of the packet
//! @return if the packet is for handshake
bool IsHandshakePacket(const DtlsEndpoint& endpoint, AzNetworking::PacketType packetType) const;
AZ_DISABLE_COPY_MOVE(UdpNetworkInterface);
struct ConnectionTimeoutFunctor final
@@ -18,6 +18,9 @@
#include <AzCore/Console/IConsole.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/EBus/IEventScheduler.h>
#include <AzCore/EBus/ScheduledEvent.h>
#include <AzCore/Interface/Interface.h>
namespace AzNetworking
{
@@ -41,13 +44,8 @@ namespace AzNetworking
return DtlsEndpoint::ConnectResult::Complete;
}
DtlsEndpoint::ConnectResult UdpSocket::AcceptDtlsEndpoint(DtlsEndpoint&, const IpAddress&, const UdpPacketEncodingBuffer& dtlsData) const
DtlsEndpoint::ConnectResult UdpSocket::AcceptDtlsEndpoint(DtlsEndpoint&, const IpAddress&) const
{
if (dtlsData.GetSize() > 0)
{
AZLOG_WARN("Encryption is disabled on accepting endpoint, but connector provided a DTLS handshake blob. Check that encryption is properly disabled on *BOTH* endpoints");
return DtlsEndpoint::ConnectResult::Failed;
}
// No-op, no encryption wrapper required
return DtlsEndpoint::ConnectResult::Complete;
}
@@ -108,28 +106,6 @@ namespace AzNetworking
m_socketFd = InvalidSocketFd;
}
#ifdef ENABLE_LATENCY_DEBUG
//! Checks packets deferred by latency debug and sends any that have passed their latency threshold
void UdpSocket::ProcessDeferredPackets()
{
const AZ::TimeMs currTimeMs = AZ::GetElapsedTimeMs();
while (m_sendBuffer.size() > 0)
{
const DeferredData& sendData = m_sendBuffer.front();
if (sendData.m_timeDeferredMs >= currTimeMs)
{
SendInternal(sendData.m_address, sendData.m_dataBuffer.GetBuffer(), sendData.m_dataBuffer.GetSize(), sendData.m_encrypt, *sendData.m_dtlsEndpoint);
m_sendBuffer.pop_front();
}
else
{
// Send buffer is sorted on append so all subsequent elements should also have time remaining
break;
}
}
}
#endif
int32_t UdpSocket::Send
(
const IpAddress& address,
@@ -146,17 +122,6 @@ namespace AzNetworking
AZ_Assert(address.GetAddress(ByteOrder::Host) != 0, "Invalid address");
AZ_Assert(address.GetPort(ByteOrder::Host) != 0, "Invalid address");
#ifdef ENABLE_LATENCY_DEBUG
if ((connectionQuality.m_latencyMs > AZ::TimeMs{ 0 }) || (connectionQuality.m_varianceMs > AZ::TimeMs{ 0 }))
{
const AZ::TimeMs jitterMs = aznumeric_cast<AZ::TimeMs>(m_random.GetRandom()) % (connectionQuality.m_varianceMs / aznumeric_cast<AZ::TimeMs>(2));
const AZ::TimeMs currTimeMs = AZ::GetElapsedTimeMs();
const AZ::TimeMs deferTimeMs = (connectionQuality.m_latencyMs / aznumeric_cast<AZ::TimeMs>(2)) + jitterMs;
m_sendBuffer.push_back(DeferredData(currTimeMs + deferTimeMs, address, data, size, encrypt, dtlsEndpoint));
std::sort(m_sendBuffer.begin(), m_sendBuffer.end());
}
#endif
if (!IsOpen())
{
return 0;
@@ -193,6 +158,18 @@ namespace AzNetworking
AZLOG_ERROR("Failed to write to socket (%d:%s)", error, GetNetworkErrorDesc(error));
}
}
#ifdef ENABLE_LATENCY_DEBUG
else if ((connectionQuality.m_latencyMs > AZ::TimeMs{ 0 }) || (connectionQuality.m_varianceMs > AZ::TimeMs{ 0 }))
{
const AZ::TimeMs jitterMs = aznumeric_cast<AZ::TimeMs>(m_random.GetRandom()) % (connectionQuality.m_varianceMs / aznumeric_cast<AZ::TimeMs>(2));
const AZ::TimeMs currTimeMs = AZ::GetElapsedTimeMs();
const AZ::TimeMs deferTimeMs = (connectionQuality.m_latencyMs / aznumeric_cast<AZ::TimeMs>(2)) + jitterMs;
DeferredData deferred = DeferredData(address, data, size, encrypt, dtlsEndpoint);
AZ::Interface<AZ::IEventScheduler>::Get()->AddCallback([&, deferredData = deferred]
{ SendInternalDeferred(deferredData); }, AZ::Name("Deferred packet"), deferTimeMs);
}
#endif
m_sentPackets++;
m_sentBytes += sentBytes;
@@ -253,7 +230,7 @@ namespace AzNetworking
}
int32_t UdpSocket::SendInternal(const IpAddress& address, const uint8_t* data, uint32_t size,
[[maybe_unused]] bool dontEncypt, [[maybe_unused]] DtlsEndpoint& dtlsEndpoint) const
[[maybe_unused]] bool encrypt, [[maybe_unused]] DtlsEndpoint& dtlsEndpoint) const
{
sockaddr_in destAddr;
memset(&destAddr, 0, sizeof(destAddr));
@@ -262,4 +239,11 @@ namespace AzNetworking
destAddr.sin_port = address.GetPort(ByteOrder::Network);
return sendto(static_cast<int32_t>(m_socketFd), reinterpret_cast<const char*>(data), size, 0, (sockaddr*)&destAddr, sizeof(destAddr));
}
#ifdef ENABLE_LATENCY_DEBUG
int32_t UdpSocket::SendInternalDeferred(const DeferredData& data) const
{
return SendInternal(data.m_address, data.m_dataBuffer.GetBuffer(), data.m_dataBuffer.GetSize(), data.m_encrypt, *data.m_dtlsEndpoint);
}
#endif
}
@@ -23,6 +23,12 @@
# define ENABLE_LATENCY_DEBUG 1
#endif
namespace AZ
{
// Forwards
class ScheduledEvent;
}
namespace AzNetworking
{
// Forwards
@@ -57,9 +63,8 @@ namespace AzNetworking
//! Accepts an encryption socket wrapper.
//! @param dtlsEndpoint the encryption wrapper instance to create a connection over
//! @param address the IP address of the endpoint to connect to
//! @param dtlsData data buffer containing the dtls handshake packet
//! @return a connect result specifying whether the connection is still pending, failed, or complete
virtual DtlsEndpoint::ConnectResult AcceptDtlsEndpoint(DtlsEndpoint& dtlsEndpoint, const IpAddress& address, const UdpPacketEncodingBuffer& dtlsData) const;
virtual DtlsEndpoint::ConnectResult AcceptDtlsEndpoint(DtlsEndpoint& dtlsEndpoint, const IpAddress& address) const;
//! Opens the UDP socket on the given port.
//! @param port the port number to open the UDP socket on, 0 will bind to any available port
@@ -71,11 +76,6 @@ namespace AzNetworking
//! Closes an open socket.
virtual void Close();
#ifdef ENABLE_LATENCY_DEBUG
//! Checks packets deferred by latency debug and sends any that have passed their latency threshold
virtual void ProcessDeferredPackets();
#endif
//! Returns true if the UDP socket is currently in an open state.
//! @return boolean true if the socket is in a connected state
bool IsOpen() const;
@@ -105,12 +105,20 @@ namespace AzNetworking
//! @return the total number of packets sent on this socket
uint32_t GetSentPackets() const;
//! Returns the total number of bytes received on this socket.
//! @return the total number of bytes received on this socket
//! Returns the total number of bytes sent on this socket.
//! @return the total number of bytes sent on this socket
uint32_t GetSentBytes() const;
//! Returns the total number of packets sent on this socket.
//! @return the total number of packets sent on this socket
//! Returns the total number of encrypted packets sent on this socket.
//! @return the total number of encrypted packets sent on this socket
uint32_t GetSentPacketsEncrypted() const;
//! Returns the total number of additional bytes sent on this socket due to SSL encryption.
//! @return the total number of additional bytes sent on this socket due to SSL encryption
uint32_t GetSentBytesEncryptionInflation() const;
//! Returns the total number of packets received on this socket.
//! @return the total number of packets received on this socket
uint32_t GetRecvPackets() const;
//! Returns the total number of bytes received on this socket.
@@ -119,6 +127,9 @@ namespace AzNetworking
protected:
mutable uint32_t m_sentPacketsEncrypted = 0;
mutable uint32_t m_sentBytesEncryptionInflation = 0;
virtual int32_t SendInternal(const IpAddress& address, const uint8_t* data, uint32_t size, bool encrypt, DtlsEndpoint& dtlsEndpoint) const;
private:
@@ -132,29 +143,24 @@ namespace AzNetworking
#ifdef ENABLE_LATENCY_DEBUG
struct DeferredData
{
DeferredData(AZ::TimeMs timeDeferredMs, const IpAddress& address, const uint8_t* data, uint32_t size, bool encrypt, DtlsEndpoint& dtlsEndpoint)
: m_timeDeferredMs(timeDeferredMs)
, m_address(address)
DeferredData(const IpAddress& address, const uint8_t* data, uint32_t size, bool encrypt, DtlsEndpoint& dtlsEndpoint)
: m_address(address)
, m_encrypt(encrypt)
, m_dtlsEndpoint(&dtlsEndpoint)
{
m_dataBuffer.CopyValues(data, size);
}
bool operator <(const DeferredData& rhs) const
{
return m_timeDeferredMs < rhs.m_timeDeferredMs;
}
AZ::TimeMs m_timeDeferredMs;
IpAddress m_address;
bool m_encrypt;
DtlsEndpoint* m_dtlsEndpoint;
UdpPacketEncodingBuffer m_dataBuffer;
DtlsEndpoint* m_dtlsEndpoint = nullptr;
AZ::ScheduledEvent* m_owningEvent = nullptr;
IpAddress m_address;
// Deferred UDP packets should have gone through UDP Fragmentation already so ChunkBuffer is sufficient size
ChunkBuffer m_dataBuffer;
};
mutable AZStd::deque<DeferredData> m_sendBuffer;
mutable AZStd::deque<DeferredData> m_recvBuffer;
int32_t SendInternalDeferred(const DeferredData& data) const;
mutable AZ::SimpleLcgRandom m_random;
#endif
};
@@ -34,6 +34,16 @@ namespace AzNetworking
return m_sentBytes;
}
inline uint32_t UdpSocket::GetSentPacketsEncrypted() const
{
return m_sentPacketsEncrypted;
}
inline uint32_t UdpSocket::GetSentBytesEncryptionInflation() const
{
return m_sentBytesEncryptionInflation;
}
inline uint32_t UdpSocket::GetRecvPackets() const
{
return m_recvPackets;
@@ -1,44 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/Console/IConsole.h>
#include <AzNetworking/Framework/ICompressor.h>
#include <AzNetworking/Utilities/CompressionCommon.h>
#ifdef ENABLE_MULTIPLAYER_COMPRESSION
// Requires the MultiplayerCompression Gem be enabled and set as a dependency of the Multiplayer Gem
#include <MultiplayerCompression/MultiplayerCompressionBus.h>
#endif
namespace AzNetworking
{
AZStd::unique_ptr<ICompressor> CreateCompressor(AZStd::string_view compressorName)
{
CompressorType compressorType = aznumeric_cast<AzNetworking::CompressorType>(static_cast<AZ::u32>(AZ::Crc32(compressorName)));
#ifdef ENABLE_MULTIPLAYER_COMPRESSION
// Requires the MultiplayerCompression Gem be enabled and set as a dependency of the Multiplayer Gem
CompressorType lz4Type;
MultiplayerCompression::MultiplayerCompressionRequestBus::BroadcastResult(lz4Type, &MultiplayerCompression::MultiplayerCompressionRequests::GetType);
if (lz4Type == compressorType)
{
AZStd::shared_ptr<ICompressorFactory> compressorFactory;
MultiplayerCompression::MultiplayerCompressionRequestBus::BroadcastResult(compressorFactory, &MultiplayerCompression::MultiplayerCompressionRequests::GetCompressionFactory);
return compressorFactory->Create();
}
#endif
AZ_Warning("CompressionCommon", false, "No compressor was found matching %.*s, check that related Gems are enabled.",
aznumeric_cast<int>(compressorName.size()), compressorName.data());
return nullptr;
}
}
@@ -1,25 +0,0 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
// forward declare
class ICompressor;
// Common helper methods needed for both TLS and DTLS transport implementations
namespace AzNetworking
{
//! Helper function to create a compressor, uses enabled Gems that supply compressors
//! @param compressorName The string name of the compressor type to create, this is Crc32'd to select by CompressorType
//! @return A unique_ptr to a Compressor implementation or nullptr on failure to match
AZStd::unique_ptr<ICompressor> CreateCompressor(AZStd::string_view compressorName);
}
@@ -1,14 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzNetworking/Utilities/EncryptionCommon.h>
#include <AzNetworking/Utilities/NetworkCommon.h>
@@ -35,7 +35,7 @@
namespace AzNetworking
{
AZ_CVAR(AZ::CVarFixedString, net_SslExternalCertificateFile, "testing.pem", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The filename of the EXTERNAL (server to client) certificate chain in PEM format (default is for debugging purposes)");
AZ_CVAR(AZ::CVarFixedString, net_SslExternalCertificateFile, "testcert.pem", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The filename of the EXTERNAL (server to client) certificate chain in PEM format (default is for debugging purposes)");
AZ_CVAR(AZ::CVarFixedString, net_SslExternalPrivateKeyFile, "testkey.pem", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The filename of the EXTERNAL (server to client) private key file in PEM format (default is for debugging purposes)");
AZ_CVAR(AZ::CVarFixedString, net_SslExternalContextPassword, "12345", nullptr, AZ::ConsoleFunctorFlags::DontReplicate | AZ::ConsoleFunctorFlags::IsInvisible, "The password required for the EXTERNAL (server to client) private certificate (default is for debugging purposes)");
@@ -54,52 +54,53 @@ namespace AzNetworking
void PrintSslErrorStack()
{
#if AZ_TRAIT_USE_OPENSSL
const int32_t errorCode = ERR_get_error();
const int32_t systemError = GetLastNetworkError();
switch (errorCode)
{
case SSL_ERROR_NONE:
AZLOG_ERROR("%X - SSL_ERROR_NONE: last system error is (%d:%s)", errorCode, systemError, GetNetworkErrorDesc(systemError));
break;
case SSL_ERROR_ZERO_RETURN:
AZLOG_ERROR("%X - SSL_ERROR_ZERO_RETURN: connection has been closed", errorCode);
break;
case SSL_ERROR_WANT_READ:
AZLOG_ERROR("%X - SSL_ERROR_WANT_READ: socket is non-blocking, read buffer is empty", errorCode);
break;
case SSL_ERROR_WANT_WRITE:
AZLOG_ERROR("%X - SSL_ERROR_WANT_WRITE: socket is non-blocking, write buffer is full", errorCode);
break;
case SSL_ERROR_WANT_CONNECT:
AZLOG_ERROR("%X - SSL_ERROR_WANT_CONNECT: socket is non-blocking, connect failed and should be retried", errorCode);
break;
case SSL_ERROR_WANT_ACCEPT:
AZLOG_ERROR("%X - SSL_ERROR_WANT_ACCEPT: socket is non-blocking, accept failed and should be retried", errorCode);
break;
case SSL_ERROR_WANT_X509_LOOKUP:
const int32_t errorCode = ERR_get_error();
const int32_t systemError = GetLastNetworkError();
switch (errorCode)
{
case SSL_ERROR_NONE:
AZLOG_ERROR("%X - SSL_ERROR_NONE: last system error is (%d:%s)", errorCode, systemError, GetNetworkErrorDesc(systemError));
break;
case SSL_ERROR_ZERO_RETURN:
AZLOG_ERROR("%X - SSL_ERROR_ZERO_RETURN: connection has been closed", errorCode);
break;
case SSL_ERROR_WANT_READ:
AZLOG_ERROR("%X - SSL_ERROR_WANT_READ: socket is non-blocking, read buffer is empty", errorCode);
break;
case SSL_ERROR_WANT_WRITE:
AZLOG_ERROR("%X - SSL_ERROR_WANT_WRITE: socket is non-blocking, write buffer is full", errorCode);
break;
case SSL_ERROR_WANT_CONNECT:
AZLOG_ERROR("%X - SSL_ERROR_WANT_CONNECT: socket is non-blocking, connect failed and should be retried", errorCode);
break;
case SSL_ERROR_WANT_ACCEPT:
AZLOG_ERROR("%X - SSL_ERROR_WANT_ACCEPT: socket is non-blocking, accept failed and should be retried", errorCode);
break;
case SSL_ERROR_WANT_X509_LOOKUP:
AZLOG_ERROR("%X - SSL_ERROR_WANT_X509_LOOKUP: operation did not complete, SSL_CTX_set_client_cert_cb() has asked to be called again, operation should be retried", errorCode);
break;
case SSL_ERROR_SYSCALL:
break;
case SSL_ERROR_SYSCALL:
AZLOG_ERROR("%X - SSL_ERROR_SYSCALL: system error, check errno (%d:%s)", errorCode, systemError, GetNetworkErrorDesc(systemError));
break;
case SSL_ERROR_SSL:
break;
case SSL_ERROR_SSL:
AZLOG_ERROR("%X - SSL_ERROR_SSL: lib %s, func %s, reason %s", errorCode, ERR_lib_error_string(errorCode), ERR_func_error_string(errorCode), ERR_reason_error_string(errorCode));
break;
default:
break;
default:
AZLOG_ERROR("%X - Unknown error code: lib %s, func %s, reason %s", errorCode, ERR_lib_error_string(errorCode), ERR_func_error_string(errorCode), ERR_reason_error_string(errorCode));
break;
break;
}
#endif
}
#if AZ_TRAIT_USE_OPENSSL
static const uint32_t MaxCookieHistory = 8;
static bool g_encryptionInitialized = false;
static int32_t g_azNetworkingTrustDataIndex = 0;
static AZ::TimeMs g_lastCookieTimestamp = AZ::TimeMs{ 0 };
static uint64_t g_validCookieArray[MaxCookieHistory];
static uint32_t g_cookieReplaceIndex = 0;
static bool g_encryptionInitialized = false;
static int32_t g_azNetworkingTrustDataIndex = 0;
static AZ::TimeMs g_lastCookieTimestamp = AZ::TimeMs{0};
static uint64_t g_validCookieArray[MaxCookieHistory];
static uint32_t g_cookieReplaceIndex = 0;
static void GetCertificatePaths(TrustZone trustZone, AZStd::string& certificatePath, AZStd::string& privateKeyPath)
{
@@ -150,7 +151,7 @@ namespace AzNetworking
AZStd::string unusedPrivateKeyPath;
GetCertificatePaths(trustZone, certificatePath, unusedPrivateKeyPath);
AZ::IO::FileIOStream stream(certificatePath.c_str(), AZ::IO::OpenMode::ModeWrite);
AZ::IO::FileIOStream stream(certificatePath.c_str(), AZ::IO::OpenMode::ModeRead);
const AZ::IO::SizeType publicCertLength = stream.GetLength();
if (publicCertLength <= 0)
{
@@ -245,7 +246,8 @@ namespace AzNetworking
// Catch a too long certificate chain. The depth limit set using SSL_CTX_set_verify_depth() is by purpose set to "limit+1" so that
// whenever the "depth>verify_depth" condition is met, we have violated the limit and want to log this error condition.
// We must do it here, because the CHAIN_TOO_LONG error would not be found explicitly; only errors introduced by cutting off the additional certificates would be logged.
// We must do it here, because the CHAIN_TOO_LONG error would not be found explicitly; only errors introduced by cutting off the
// additional certificates would be logged.
const int32_t depth = X509_STORE_CTX_get_error_depth(context);
if (depth > net_SslMaxCertDepth)
{
@@ -256,7 +258,7 @@ namespace AzNetworking
// Validate certificate before and after times
if (net_SslValidateExpiry)
{
const ASN1_TIME *notBeforeTime = X509_get_notBefore(err_cert);
const ASN1_TIME* notBeforeTime = X509_get_notBefore(err_cert);
const int32_t beforeTimeResult = X509_cmp_current_time(notBeforeTime);
if (beforeTimeResult >= 0)
@@ -266,7 +268,7 @@ namespace AzNetworking
result = OpenSslResultFailure;
}
const ASN1_TIME *notAfterTime = X509_get_notAfter(err_cert);
const ASN1_TIME* notAfterTime = X509_get_notAfter(err_cert);
const int32_t afterTimeResult = X509_cmp_current_time(notAfterTime);
if (afterTimeResult <= 0)
@@ -280,7 +282,7 @@ namespace AzNetworking
if (net_SslEnablePinning)
{
SSL* sslSocket = reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(context, SSL_get_ex_data_X509_STORE_CTX_idx()));
SSL_CTX* sslContext = SSL_get_SSL_CTX(sslSocket);;
SSL_CTX* sslContext = SSL_get_SSL_CTX(sslSocket);
TrustZone trustZone = static_cast<TrustZone>(reinterpret_cast<uintptr_t>(SSL_CTX_get_ex_data(sslContext, g_azNetworkingTrustDataIndex)));
if (!ValidatePinnedCertificate(err_cert, trustZone))
{
@@ -369,7 +371,6 @@ namespace AzNetworking
{
ERR_free_strings();
EVP_cleanup();
sk_SSL_COMP_free(SSL_COMP_get_compression_methods());
CRYPTO_cleanup_all_ex_data();
g_encryptionInitialized = false;
@@ -486,10 +487,16 @@ namespace AzNetworking
}
}
// Validate the clients certificate
// Validate the clients certificate only on handshake
SSL_CTX_set_verify(context, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, ValidateCertificateCallback);
// Set up ciphers using DH key exchange
// Only support a single cipher suite in OpenSSL that supports:
//
// ECDHE Primary key exchange using ephemeral elliptic curve diffie-hellman.
// RSA Authentication (public and private key) used to sign ECDHE parameters and can be checked against a CA.
// AES256 AES cipher for symmetric key encryption using a 256-bit key.
// GCM Mode of operation for symmetric key encryption.
// SHA384 SHA-2 hashing algorithm.
const AZ::CVarFixedString ciphers = net_SslCertCiphers;
if (SSL_CTX_set_cipher_list(context, ciphers.c_str()) != OpenSslResultSuccess)
{
@@ -501,6 +508,7 @@ namespace AzNetworking
SSL_CTX_set_cookie_generate_cb(context, GenerateCookieCallback);
SSL_CTX_set_cookie_verify_cb(context, VerifyCookieCallback);
// Automatically generate parameters for elliptic-curve diffie-hellman (i.e. curve type and coefficients).
SSL_CTX_set_ecdh_auto(context, 1);
scopedFree.ReleaseSslContextWithoutFree();
@@ -585,9 +593,9 @@ namespace AzNetworking
// SSL_shutdown can do very bad things if the SSL context is in a bad state
// Further, the documentation around safely using SSL_shutdown is extremely confusing and doesn't provide a functional example
// We never terminate encryption on a connection to send further data in plain-text, we explicitly close TCP sockets and UDP virtual connections are deleted
// Therefore just removing the call to SSL_shutdown for now
//SSL_shutdown(sslSocket);
// We never terminate encryption on a connection to send further data in plain-text, we explicitly close TCP sockets and UDP virtual
// connections are deleted Therefore just removing the call to SSL_shutdown for now
// SSL_shutdown(sslSocket);
SSL_free(sslSocket);
sslSocket = nullptr;
#endif
@@ -620,4 +628,4 @@ namespace AzNetworking
return 0;
#endif
}
}
} // namespace AzNetworking
@@ -124,8 +124,6 @@ set(FILES
UdpTransport/UdpSocket.inl
Utilities/CidrAddress.cpp
Utilities/CidrAddress.h
Utilities/CompressionCommon.cpp
Utilities/CompressionCommon.h
Utilities/EncryptionCommon.cpp
Utilities/EncryptionCommon.h
Utilities/Endian.h
@@ -40,16 +40,6 @@ ly_add_target(
*.AutoPackets.xml,AutoPackets_Source.jinja,$path/$fileprefix.AutoPackets.cpp
)
# Add ENABLE_MULTIPLAYER_COMPRESSION define to the following source files when the LY_ENABLE_MULTIPLAYER_COMPRESSION cache variable has been set
if($CACHE{LY_ENABLE_MULTIPLAYER_COMPRESSION})
set_property(SOURCE
CompressionCommon.cpp
#...
APPEND PROPERTY
COMPILE_DEFINITIONS ENABLE_MULTIPLAYER_COMPRESSION
)
endif()
################################################################################
# Tests
################################################################################