diff --git a/Source/Engine/Platform/Network.h b/Source/Engine/Platform/Network.h index b8c198770..af9851a0a 100644 --- a/Source/Engine/Platform/Network.h +++ b/Source/Engine/Platform/Network.h @@ -7,7 +7,7 @@ #elif PLATFORM_UWP #include "Win32/Win32Network.h" #elif PLATFORM_LINUX -#include "Base/NetworkBase.h" +#include "Unix/UnixNetwork.h" #elif PLATFORM_PS4 #include "Base/NetworkBase.h" #elif PLATFORM_XBOX_SCARLETT diff --git a/Source/Engine/Platform/Types.h b/Source/Engine/Platform/Types.h index 3f6511241..3cec8ab1a 100644 --- a/Source/Engine/Platform/Types.h +++ b/Source/Engine/Platform/Types.h @@ -68,8 +68,8 @@ class LinuxThread; typedef LinuxThread Thread; class LinuxWindow; typedef LinuxWindow Window; -class NetworkBase; -typedef NetworkBase Network; +class UnixNetwork; +typedef UnixNetwork Network; #elif PLATFORM_PS4 diff --git a/Source/Engine/Platform/Unix/UnixNetwork.cpp b/Source/Engine/Platform/Unix/UnixNetwork.cpp new file mode 100644 index 000000000..ca99df44c --- /dev/null +++ b/Source/Engine/Platform/Unix/UnixNetwork.cpp @@ -0,0 +1,404 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#if PLATFORM_UNIX + +#include "UnixNetwork.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Collections/Array.h" +#include "Engine/Utilities/StringConverter.h" +#include +#include +#include +#include +#include +#include +#include + +struct UnixSocketData +{ + int sockfd; +}; +static_assert(sizeof(NetworkSocket::Data) >= sizeof(UnixSocketData), "NetworkSocket::Data is not big enough to contains UnixSocketData !"); +static_assert(sizeof(NetworkEndPoint::Data) >= sizeof(sockaddr_in6), "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); + +static int GetAddrSize(const sockaddr& addr) +{ + return addr.sa_family == AF_INET6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in); +} + +static int GetAddrSizeFromEP(NetworkEndPoint& endPoint) +{ + return endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof(sockaddr_in6) : sizeof(sockaddr_in); +} + +static NetworkIPVersion GetIPVersionFromAddr(const sockaddr& addr) +{ + return addr.sa_family == AF_INET6 ? NetworkIPVersion::IPv6 : NetworkIPVersion::IPv4;; +} + +static void TranslateSockOptToNative(NetworkSocketOption option, int32* level, int32* name) +{ + switch (option) + { +#define SOCKOPT(OPTENUM, OPTLEVEL, OPTNAME) case OPTENUM: *level = OPTLEVEL; *name = OPTNAME; break; + SOCKOPT(NetworkSocketOption::Debug, SOL_SOCKET, SO_DEBUG) + SOCKOPT(NetworkSocketOption::ReuseAddr, SOL_SOCKET, SO_REUSEADDR) + SOCKOPT(NetworkSocketOption::KeepAlive, SOL_SOCKET, SO_KEEPALIVE) + SOCKOPT(NetworkSocketOption::DontRoute, SOL_SOCKET, SO_DONTROUTE) + SOCKOPT(NetworkSocketOption::Broadcast, SOL_SOCKET, SO_BROADCAST) +#ifdef SO_USELOOPBACK + SOCKOPT(NetworkSocketOption::UseLoopback, SOL_SOCKET, SO_USELOOPBACK) +#endif + SOCKOPT(NetworkSocketOption::Linger, SOL_SOCKET, SO_LINGER) + SOCKOPT(NetworkSocketOption::OOBInline, SOL_SOCKET, SO_OOBINLINE) + SOCKOPT(NetworkSocketOption::SendBuffer, SOL_SOCKET, SO_SNDBUF) + SOCKOPT(NetworkSocketOption::RecvBuffer, SOL_SOCKET, SO_RCVBUF) + SOCKOPT(NetworkSocketOption::SendTimeout, SOL_SOCKET, SO_SNDTIMEO) + SOCKOPT(NetworkSocketOption::RecvTimeout, SOL_SOCKET, SO_RCVTIMEO) + SOCKOPT(NetworkSocketOption::Error, SOL_SOCKET, SO_ERROR) +#ifdef TCP_NODELAY + SOCKOPT(NetworkSocketOption::NoDelay, IPPROTO_TCP, TCP_NODELAY) +#endif + SOCKOPT(NetworkSocketOption::IPv6Only, IPPROTO_IPV6, IPV6_V6ONLY) + SOCKOPT(NetworkSocketOption::Mtu, IPPROTO_IP, IP_MTU) + SOCKOPT(NetworkSocketOption::Type, SOL_SOCKET, SO_TYPE) +#undef SOCKOPT + default: + *level = 0; + *name = 0; + break; + } +} +static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) +{ + uint32 size = GetAddrSize(*addr); + uint16 port; + void* paddr; + if (addr->sa_family == AF_INET6) + { + paddr = &((sockaddr_in6*)addr)->sin6_addr; + port = ntohs(((sockaddr_in6*)addr)->sin6_port); + } + else if (addr->sa_family == AF_INET) + { + paddr = &((sockaddr_in*)addr)->sin_addr; + port = ntohs(((sockaddr_in*)addr)->sin_port); + } + else + { + LOG(Error, "Unable to create endpoint, sockaddr must be INET or INET6! Family : {0}", addr->sa_family); + return true; + } + + char ip[INET6_ADDRSTRLEN]; + if (inet_ntop(addr->sa_family, paddr, ip, INET6_ADDRSTRLEN) == nullptr) + { + LOG(Error, "Unable to extract address from sockaddr!"); + LOG_UNIX_LAST_ERROR; + return true; + } + char strPort[6]; + sprintf(strPort, "%d", port); + endPoint.IPVersion = GetIPVersionFromAddr(*addr); + memcpy(endPoint.Data, addr, size); + return false; +} + +bool UnixNetwork::CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv) +{ + socket.Protocol = proto; + socket.IPVersion = ipv; + const int domain = socket.IPVersion == NetworkIPVersion::IPv6 ? AF_INET6 : AF_INET; + const int type = socket.Protocol == NetworkProtocol::Tcp ? SOCK_STREAM : SOCK_DGRAM; + const int protocol = socket.Protocol == NetworkProtocol::Tcp ? IPPROTO_TCP : IPPROTO_UDP; + auto& sock = *(UnixSocketData*)&socket.Data; + sock.sockfd = ::socket(domain, type, protocol); + if (sock.sockfd < 0) + { + LOG(Error, "Can't create native socket"); + LOG_UNIX_LAST_ERROR; + return true; + } + return false; +} + +bool UnixNetwork::DestroySocket(NetworkSocket& socket) +{ + auto& sock = *(UnixSocketData*)&socket.Data; + ::close(sock.sockfd); + return false; +} + +bool UnixNetwork::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value) +{ + const int32 v = value; + return SetSocketOption(socket, option, v); +} + +bool UnixNetwork::SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value) +{ + int32 optlvl = 0; + int32 optnme = 0; + TranslateSockOptToNative(option, &optlvl, &optnme); + auto& sock = *(UnixSocketData*)&socket.Data; + if (setsockopt(sock.sockfd, optlvl, optnme, (char*)&value, sizeof(value)) == -1) + { + LOG(Warning, "Unable to set socket option ! Socket : {0}", sock.sockfd); + LOG_UNIX_LAST_ERROR; + return true; + } + return false; +} + +bool UnixNetwork::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value) +{ + int32 v; + const bool status = GetSocketOption(socket, option, &v); + *value = v == 1 ? true : false; + return status; +} + +bool UnixNetwork::GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value) +{ + int32 optlvl = 0; + int32 optnme = 0; + TranslateSockOptToNative(option, &optlvl, &optnme); + socklen_t size; + auto& sock = *(UnixSocketData*)&socket.Data; + if (getsockopt(sock.sockfd, optlvl, optnme, (char*)value, &size) == -1) + { + LOG(Warning, "Unable to get socket option ! Socket : {0}", sock.sockfd); + LOG_UNIX_LAST_ERROR; + return true; + } + return false; +} + +bool UnixNetwork::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + const uint16 size = GetAddrSizeFromEP(endPoint); + auto& sock = *(UnixSocketData*)&socket.Data; + if (connect(sock.sockfd, (const sockaddr*)endPoint.Data, size) == -1) + { + LOG(Error, "Unable to connect socket to address! Socket : {0}", sock.sockfd); + LOG_UNIX_LAST_ERROR; + return true; + } + return false; +} + +bool UnixNetwork::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + auto& sock = *(UnixSocketData*)&socket.Data; + if (socket.IPVersion != endPoint.IPVersion) + { + LOG(Error, "Can't bind socket to end point, Socket.IPVersion != EndPoint.IPVersion! Socket : {0}", sock.sockfd); + return true; + } + const uint16 size = GetAddrSizeFromEP(endPoint); + if (bind(sock.sockfd, (const sockaddr*)endPoint.Data, size) == -1) + { + LOG(Error, "Unable to bind socket! Socket : {0}", sock.sockfd); + LOG_UNIX_LAST_ERROR; + return true; + } + return false; +} + +bool UnixNetwork::Listen(NetworkSocket& socket, uint16 queueSize) +{ + auto& sock = *(UnixSocketData*)&socket.Data; + if (listen(sock.sockfd, (int32)queueSize) == -1) + { + LOG(Error, "Unable to listen ! Socket : {0}", sock.sockfd); + return true; + } + return false; +} + +bool UnixNetwork::Accept(NetworkSocket& serverSocket, NetworkSocket& newSocket, NetworkEndPoint& newEndPoint) +{ + auto& serverSock = *(UnixSocketData*)&serverSocket.Data; + if (serverSocket.Protocol != NetworkProtocol::Tcp) + { + LOG(Warning, "Can't accept connection on UDP socket! Socket : {0}", serverSock.sockfd); + return true; + } + sockaddr_in6 addr; + socklen_t size = sizeof(sockaddr_in6); + int sock = accept(serverSock.sockfd, (sockaddr*)&addr, &size); + if (sock < 0) + { + LOG(Warning, "Unable to accept incoming connection! Socket : {0}", serverSock.sockfd); + LOG_UNIX_LAST_ERROR; + return true; + } + auto& newSock = *(UnixSocketData*)&newSocket.Data; + newSock.sockfd = sock; + memcpy(newEndPoint.Data, &addr, size); + newSocket.Protocol = serverSocket.Protocol; + newSocket.IPVersion = serverSocket.IPVersion; + if (CreateEndPointFromAddr((sockaddr*)&addr, newEndPoint)) + return true; + return false; +} + +bool UnixNetwork::IsReadable(NetworkSocket& socket) +{ + return NetworkBase::IsReadable(socket); // TODO: impl this +} + +bool UnixNetwork::IsWriteable(NetworkSocket& socket) +{ + return NetworkBase::IsWriteable(socket); // TODO: impl this +} + +bool UnixNetwork::CreateSocketGroup(uint32 capacity, NetworkSocketGroup& group) +{ + return NetworkBase::CreateSocketGroup(capacity, group); // TODO: impl this +} + +bool UnixNetwork::DestroySocketGroup(NetworkSocketGroup& group) +{ + return NetworkBase::DestroySocketGroup(group); // TODO: impl this +} + +int32 UnixNetwork::Poll(NetworkSocketGroup& group) +{ + return NetworkBase::Poll(group); // TODO: impl this +} + +bool UnixNetwork::GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state) +{ + return NetworkBase::GetSocketState(group, index, state); // TODO: impl this +} + +int32 UnixNetwork::AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket) +{ + return NetworkBase::AddSocketToGroup(group, socket); // TODO: impl this +} + +bool UnixNetwork::GetSocketFromGroup(NetworkSocketGroup& group, uint32 index, NetworkSocket* socket) +{ + return NetworkBase::GetSocketFromGroup(group, index, socket); // TODO: impl this +} + +void UnixNetwork::RemoveSocketFromGroup(NetworkSocketGroup& group, uint32 index) +{ + NetworkBase::RemoveSocketFromGroup(group, index); // TODO: impl this +} + +bool UnixNetwork::RemoveSocketFromGroup(NetworkSocketGroup& group, NetworkSocket& socket) +{ + return NetworkBase::RemoveSocketFromGroup(group, socket); // TODO: impl this +} + +void UnixNetwork::ClearGroup(NetworkSocketGroup& group) +{ + NetworkBase::ClearGroup(group); // TODO: impl this +} + +int32 UnixNetwork::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) +{ + auto& sock = *(UnixSocketData*)&socket.Data; + if (endPoint != nullptr && socket.IPVersion != endPoint->IPVersion) + { + LOG(Error, "Unable to send data, Socket.IPVersion != EndPoint.IPVersion! Socket : {0}", sock.sockfd); + return -1; + } + uint32 size; + if (endPoint == nullptr && socket.Protocol == NetworkProtocol::Tcp) + { + if ((size = send(sock.sockfd, (const char*)data, length, 0)) == -1) + { + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1}", sock.sockfd, length); + return -1; + } + } + else if (endPoint != nullptr && socket.Protocol == NetworkProtocol::Udp) + { + if ((size = sendto(sock.sockfd, (const char*)data, length, 0, (const sockaddr*)endPoint->Data, GetAddrSizeFromEP(*endPoint))) == -1) + { + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1}", sock.sockfd, length); + return -1; + } + } + else + { + // TODO: better explanation + LOG(Error, "Unable to send data! Socket : {0} Data Length : {1}", sock.sockfd, length); + return -1; + } + return size; +} + +int32 UnixNetwork::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) +{ + auto& sock = *(UnixSocketData*)&socket.Data; + uint32 size; + if (endPoint == nullptr) + { + if ((size = recv(sock.sockfd, (char*)buffer, bufferSize, 0)) == -1) + { + LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1}", sock.sockfd, bufferSize); + LOG_UNIX_LAST_ERROR; + return -1; + } + } + else + { + socklen_t addrsize = sizeof(sockaddr_in6); + sockaddr_in6 addr; + if ((size = recvfrom(sock.sockfd, (void*)buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == -1) + { + LOG(Error, "Unable to read data! Socket : {0} Buffer Size : {1}", sock.sockfd, bufferSize); + return -1; + } + if (CreateEndPointFromAddr((sockaddr*)&addr, *endPoint)) + return true; + } + return size; +} + +bool UnixNetwork::CreateEndPoint(NetworkAddress& address, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable) +{ + int status; + addrinfo hints; + addrinfo* info; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = ipv == NetworkIPVersion::IPv6 ? AF_INET6 : ipv == NetworkIPVersion::IPv4 ? AF_INET : AF_UNSPEC; + hints.ai_flags |= AI_ADDRCONFIG; + hints.ai_flags |= AI_V4MAPPED; + if (bindable) + hints.ai_flags = AI_PASSIVE; + + // consider using NUMERICHOST/NUMERICSERV if address is a valid Ipv4 or IPv6 so we can skip some look up ( potentially slow when resolving host names ) + const StringAsANSI<60> addressAnsi(*address.Address, address.Address.Length()); + const StringAsANSI<10> portAnsi(*address.Port, address.Port.Length()); + if ((status = getaddrinfo(address.Address == String::Empty ? nullptr : addressAnsi.Get(), address.Port == String::Empty ? nullptr : portAnsi.Get(), &hints, &info)) != 0) + { + LOG(Error, "Unable to query info for address : {0} Error : {1}", address.Address != String::Empty ? *address.Address : TEXT("ANY"), String(gai_strerror(status))); + return true; + } + + if (info == nullptr) + { + LOG(Error, "Unable to resolve address! Address : {0}", address.Address != String::Empty ? *address.Address : TEXT("ANY")); + return true; + } + + if (CreateEndPointFromAddr(info->ai_addr, endPoint)) + { + freeaddrinfo(info); + return true; + } + freeaddrinfo(info); + return false; +} + +NetworkEndPoint UnixNetwork::RemapEndPointToIPv6(NetworkEndPoint endPoint) +{ + return NetworkBase::RemapEndPointToIPv6(endPoint); // TODO: impl this +} + +#endif diff --git a/Source/Engine/Platform/Unix/UnixNetwork.h b/Source/Engine/Platform/Unix/UnixNetwork.h new file mode 100644 index 000000000..28c81ff2d --- /dev/null +++ b/Source/Engine/Platform/Unix/UnixNetwork.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_UNIX + +#include "Engine/Platform/Base/NetworkBase.h" + +class FLAXENGINE_API UnixNetwork : public NetworkBase +{ +public: + + // [NetworkBase] + static bool CreateSocket(NetworkSocket& socket, NetworkProtocol proto, NetworkIPVersion ipv); + static bool DestroySocket(NetworkSocket& socket); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool value); + static bool SetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32 value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, bool* value); + static bool GetSocketOption(NetworkSocket& socket, NetworkSocketOption option, int32* value); + static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool Listen(NetworkSocket& socket, uint16 queueSize); + static bool Accept(NetworkSocket& serverSocket, NetworkSocket& newSocket, NetworkEndPoint& newEndPoint); + static bool IsReadable(NetworkSocket& socket); + static bool IsWriteable(NetworkSocket& socket); + static bool CreateSocketGroup(uint32 capacity, NetworkSocketGroup& group); + static bool DestroySocketGroup(NetworkSocketGroup& group); + static int32 Poll(NetworkSocketGroup& group); + static bool GetSocketState(NetworkSocketGroup& group, uint32 index, NetworkSocketState& state); + static int32 AddSocketToGroup(NetworkSocketGroup& group, NetworkSocket& socket); + static bool GetSocketFromGroup(NetworkSocketGroup& group, uint32 index, NetworkSocket* socket); + static void RemoveSocketFromGroup(NetworkSocketGroup& group, uint32 index); + static bool RemoveSocketFromGroup(NetworkSocketGroup& group, NetworkSocket& socket); + static void ClearGroup(NetworkSocketGroup& group); + static int32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); + static int32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); + static bool CreateEndPoint(NetworkAddress& address, NetworkIPVersion ipv, NetworkEndPoint& endPoint, bool bindable = true); + static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint endPoint); +}; + +#endif