diff --git a/Source/Engine/Platform/Win32/Win32Network.cpp b/Source/Engine/Platform/Win32/Win32Network.cpp new file mode 100644 index 000000000..6013f92e2 --- /dev/null +++ b/Source/Engine/Platform/Win32/Win32Network.cpp @@ -0,0 +1,370 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "Win32Network.h" +#include "Engine/Core/Log.h" +#include "Engine/Core/Collections/Array.h" +#include +#include +#include + +static_assert(sizeof NetworkSocket::Data >= sizeof SOCKET, "NetworkSocket::Data is not big enough to contains SOCKET !"); +static_assert(sizeof NetworkEndPoint::Data >= sizeof sockaddr_in6, "NetworkEndPoint::Data is not big enough to contains sockaddr_in6 !"); + +static const IN6_ADDR v4MappedPrefix = {{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 }}; +static WSAData _wsaData; + +/* + * TODO: Create func to retrieve WSA error string + * Known issues : + * Sometimes getaddrinfo fails without reason, re-trying right after works + * Even if dualstacking is enabled it's not possible to bind an Ipv4mappedIPv6 endpoint. windows limitation + */ + +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;; +} + +//TODO: can be simplified by casting addr to the right struct and ntohl the port, so we can get rid of getnameinfo +// getnameinfo return a name ( like JimiPC ), not the ip ! +static bool CreateEndPointFromAddr(sockaddr* addr, NetworkEndPoint& endPoint) +{ + uint16 size = GetAddrSize(*addr); + char name[100]; + char service[20]; + if (getnameinfo(addr, size, name, sizeof name, service, sizeof service, 0) != 0) + { + LOG(Error, "Unable to extract info from sockaddr ! Error : {0}", WSAGetLastError()); + return true; + } + void* paddr; + if (addr->sa_family == AF_INET6) + paddr = &((sockaddr_in6*)addr)->sin6_addr; + else if (addr->sa_family == AF_INET) + paddr = &((sockaddr_in*)addr)->sin_addr; + + char ip[INET6_ADDRSTRLEN]; + if (inet_ntop(addr->sa_family, paddr, ip, INET6_ADDRSTRLEN) == nullptr) + { + LOG(Error, "Unable to extract address from sockaddr ! Error : {0}", WSAGetLastError()); + return true; + } + endPoint.Address = String(ip); + endPoint.Port = String(service); + endPoint.IPVersion = GetIPVersionFromAddr(*addr); + memcpy(endPoint.Data, addr, size); + return false; +} + +static void PrintAddrFromInfo(addrinfo& info) +{ + addrinfo* curr; + for (curr = &info; curr != nullptr; curr = curr->ai_next) + { + void* addr; + if (curr->ai_family == AF_INET) { + sockaddr_in *ipv4 = (struct sockaddr_in *)curr->ai_addr; + addr = &(ipv4->sin_addr); + } else { + sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)curr->ai_addr; + addr = &(ipv6->sin6_addr); + } + + char str[INET6_ADDRSTRLEN]; + inet_ntop(curr->ai_family, addr, str, INET6_ADDRSTRLEN); + LOG(Info, "ADDR INFO family : {0} socktype : {1}, proto : {2} address : {3}", curr->ai_family, curr->ai_socktype, curr->ai_protocol, StringAnsi(str).ToString().Get()); + } +} + +bool Win32Network::Init() +{ + if (WSAStartup(MAKEWORD(2,0), &_wsaData) != 0) + return true; + return false; +} + +void Win32Network::Exit() +{ + WSACleanup(); +} + +bool Win32Network::CreateSocket(NetworkSocket& netsock, NetworkSocketCreateSettings& settings) +{ + netsock.Protocol = settings.Protocol; + netsock.IPVersion = settings.IPVersion; + const uint8 family = settings.IPVersion == NetworkIPVersion::IPv6 ? AF_INET6 : AF_INET; + const uint8 stype = settings.Protocol == NetworkProtocolType::Tcp ? SOCK_STREAM : SOCK_DGRAM; + const uint8 proto = settings.Protocol == NetworkProtocolType::Tcp ? IPPROTO_TCP : IPPROTO_UDP; + SOCKET sock; + + if ((sock = socket(family, stype, proto)) == INVALID_SOCKET) + { + LOG(Error, "Can't create native socket ! Error : {0}", WSAGetLastError()); + return true; + } + memcpy(netsock.Data, &sock, sizeof sock); + DWORD dw = 0; + if (family == AF_INET6 && setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&dw, sizeof dw) == SOCKET_ERROR) + { + LOG(Warning, "System does not support dual stacking socket ! Error : {0}", WSAGetLastError()); + } + unsigned long value = 1; + if (settings.ReuseAddress && setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&value, sizeof value) == SOCKET_ERROR) + { + LOG(Warning, "Can't set socket option to SO_REUSEADDR ! Error : {0}", WSAGetLastError()); + } + + if (settings.Broadcast && settings.Protocol == NetworkProtocolType::Udp) + { + if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&value, sizeof value) == SOCKET_ERROR) + { + LOG(Warning, "Can't set socket option to SO_BROADCAST ! Error : {0}", WSAGetLastError()); + } + } + else if (settings.Broadcast) + LOG(Warning, "Can't set socket option to SO_BROADCAST ! The socket must use UDP protocol. Error : {0}", WSAGetLastError()); + + if (ioctlsocket(sock, FIONBIO, &value) == SOCKET_ERROR) + { + LOG(Error, "Can't set socket to NON-BLOCKING type ! Error : {0}", WSAGetLastError()); + return true; // Support using blocking socket , need to test it + } + //DEBUG + LOG(Info, "New socket created : {0}", sock); //TODO: DEBUG + return false; +} + +bool Win32Network::DestroySocket(NetworkSocket& socket) +{ + const SOCKET sock = *(SOCKET*)socket.Data; + if (sock != INVALID_SOCKET) + { + closesocket(sock); + //DEBUG + LOG(Info, "Deleting socket : {0}", sock); //TODO: DEBUG + return false; + } + LOG(Warning, "Unable to delete socket INVALID_SOCKET ! Socket : {0}", sock); + return true; +} + + +bool Win32Network::ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + const uint16 size = GetAddrSizeFromEP(endPoint); + if (connect(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) + { + LOG(Error, "Unable to connect socket to address ! Socket : {0} Address : {1} Port : {2}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get()); + return true; + } + return false; +} + +bool Win32Network::BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint) +{ + if (socket.IPVersion != endPoint.IPVersion) + { + LOG(Error, "Can't bind socket to end point, Socket.IPVersion != EndPoint.IPVersion ! Socket : {0}", *(SOCKET*)socket.Data); + return true; + } + + const uint16 size = endPoint.IPVersion == NetworkIPVersion::IPv6 ? sizeof sockaddr_in6 : sizeof sockaddr_in; + const sockaddr* addr = (sockaddr*)endPoint.Data; + LOG(Info, "BIND : EndPoint family : {0}", addr->sa_family); + if (bind(*(SOCKET*)socket.Data, (const sockaddr*)endPoint.Data, size) == SOCKET_ERROR) + { + LOG(Error, "Unable to bind socket ! Socket : {0} Address : {1} Port : {2} Error : {3}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get(), WSAGetLastError()); + return true; + } + //DEBUG + LOG(Info, "Binding socket ! Socket : {0} Address : {1} Port : {2}", *(SOCKET*)socket.Data, endPoint.Address.Get(), endPoint.Port.Get()); + return false; +} + +bool Win32Network::Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint) +{ + if (serverSock.Protocol != NetworkProtocolType::Tcp) + { + LOG(Warning, "Can't accept connection on UDP socket ! Socket : {0}", *(SOCKET*)serverSock.Data); + return true; + } + SOCKET sock; + sockaddr addr; + if ((sock = accept(*(SOCKET*)serverSock.Data, &addr, nullptr)) == INVALID_SOCKET) + { + LOG(Warning, "Unable to accept incoming connection ! Socket : {0} Error : {1}", *(SOCKET*)serverSock.Data, WSAGetLastError()); + return true; + } + memcpy(newSock.Data, &sock, sizeof sock); + memcpy(newEndPoint.Data, &addr, GetAddrSize(addr)); + + newSock.Protocol = serverSock.Protocol; + newSock.IPVersion = serverSock.IPVersion; + if (CreateEndPointFromAddr(&addr, newEndPoint)) + return true; + return false; +} + +bool Win32Network::IsReadable(NetworkSocket& socket, uint64* size) +{ + unsigned long value; + if (ioctlsocket(*(SOCKET*)socket.Data, FIONREAD, &value) != 0) + { + LOG(Error, "Unable to query socket for readability ! Socket : {0} Error : {1}", *(SOCKET*)socket.Data, WSAGetLastError()); + return true; + } + *size = value; + return false; +} + +uint32 Win32Network::WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint) +{ + if (socket.IPVersion != endPoint->IPVersion) + { + LOG(Error, "Unable to send data, Socket.IPVersion != EndPoint.IPVersion ! Socket : {0}", *(SOCKET*)socket.Data); + return 0; + } + uint32 size; + if (endPoint == nullptr && socket.Protocol == NetworkProtocolType::Tcp) + { + if ((size = send(*(SOCKET*)socket.Data, (const char*)data, length, 0)) == SOCKET_ERROR) + { + LOG(Error, "Unable to send data ! Socket : {0} Data Length : {1}", *(SOCKET*)socket.Data, length); + return -1; + } + } + else if (endPoint != nullptr && socket.Protocol == NetworkProtocolType::Udp) + { + if ((size = sendto(*(SOCKET*)socket.Data, (const char*)data, length, 0, (const sockaddr*)endPoint->Data, GetAddrSizeFromEP(*endPoint))) == SOCKET_ERROR) + { + LOG(Error, "Unable to send data ! Socket : {0} Address : {1} Port : {2} Data Length : {3}", *(SOCKET*)socket.Data, endPoint->Address, endPoint->Port, length); + return -1; + } + } + else + { + //TODO: better explanation + LOG(Error, "Unable to send data ! Socket : {0} Data Length : {1}", *(SOCKET*)socket.Data, length); + return -1; + } + return size; +} + +/* + * TODO: handle size == 0 when there is a shutdown + */ +uint32 Win32Network::ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint) +{ + uint32 size; + if (endPoint == nullptr) // TCP + { + if ((size = recv(*(SOCKET*)socket.Data, (char*) buffer, bufferSize, 0)) == SOCKET_ERROR) + { + LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1}", *(SOCKET*)socket.Data, bufferSize); + return -1; + } + } + else // UDP + { + int32 addrsize; + sockaddr_in6 addr; + if ((size = recvfrom(*(SOCKET*)socket.Data, (char*) buffer, bufferSize, 0, (sockaddr*)&addr, &addrsize)) == SOCKET_ERROR) + { + LOG(Error, "Unable to read data ! Socket : {0} Buffer Size : {1}", *(SOCKET*)socket.Data, bufferSize); + return -1; + } + if (CreateEndPointFromAddr((sockaddr*)&addr, *endPoint)) + return true; + } + return size; +} + +// if address is null, it's ADDR_ANY +bool Win32Network::CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint) +{ + int status; + addrinfo hints; + addrinfo *info; + //TODO: Refactor this crappy thing + char* paddr= address != nullptr ? address->ToStringAnsi().Get() : nullptr; + char* pport = port != nullptr ? port->ToStringAnsi().Get() : nullptr; + //DEBUG + LOG(Info, "Searching available adresses for {0} : {1}", address == nullptr ? String("nullptr").Get() : address->Get(), + port == nullptr ? String("nullptr").Get() : port->Get()); + 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 (paddr == nullptr) + { + hints.ai_flags = AI_PASSIVE; + LOG(Info, "PASSIVE MODE"); + } + + // 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 ) + // can *paddr works ? + // paddr = nullptr don't work with this func + if ((status = getaddrinfo(paddr == nullptr ? nullptr : paddr, pport, &hints, &info)) != 0) + { + LOG(Error, "Unable to query info for address : {0} Error : {1} !", address->Get(), gai_strerror(status)); //TODO: address can be NULL + return true; + } + + if (info == nullptr) + { + LOG(Error, "Unable to resolve address ! Address : {0}", address->Get());//TODO: address can be NULL + return true; + } + + //DEBUG + PrintAddrFromInfo(*info); + + // We are taking the first addr in the linked list + if (CreateEndPointFromAddr(info->ai_addr, endPoint)) + { + freeaddrinfo(info); + return true; + } + freeaddrinfo(info); + + //DEBUG + LOG(Info, "Address found : {0} : {1}", endPoint.Address, endPoint.Port); + return false; +} + +NetworkEndPoint Win32Network::RemapEndPointToIPv6(NetworkEndPoint endPoint) +{ + if (endPoint.IPVersion == NetworkIPVersion::IPv6) + { + LOG(Warning, "Unable to remap EndPoint, already in IPv6 format !"); + return endPoint; + } + + NetworkEndPoint pv6; + sockaddr_in* addr4 = (sockaddr_in*)endPoint.Data; + sockaddr_in6* addr6 = (sockaddr_in6*)pv6.Data; + const SCOPE_ID scope = SCOPEID_UNSPECIFIED_INIT; + + // Can be replaced by windows built-in macro IN6ADDR_SETV4MAPPED() + memset(addr6, 0, sizeof sockaddr_in6); + addr6->sin6_family = AF_INET6; + addr6->sin6_scope_struct = scope; + addr6->sin6_addr = v4MappedPrefix; + addr6->sin6_port = addr4->sin_port; + memcpy(&addr6->sin6_addr.u.Byte[12], &addr4->sin_addr, 4); // :::::FFFF:XXXX:XXXX X=IPv4 + pv6.IPVersion = NetworkIPVersion::IPv6; + pv6.Port = endPoint.Port; + + return pv6; +} diff --git a/Source/Engine/Platform/Win32/Win32Network.h b/Source/Engine/Platform/Win32/Win32Network.h new file mode 100644 index 000000000..708462320 --- /dev/null +++ b/Source/Engine/Platform/Win32/Win32Network.h @@ -0,0 +1,29 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#if PLATFORM_WIN32 + +#include "Engine/Platform/Base/NetworkBase.h" + +class FLAXENGINE_API Win32Network : public NetworkBase +{ +public: + // [NetworkBase] + static bool Init(); + static void Exit(); + static bool CreateSocket(NetworkSocket& socket, NetworkSocketCreateSettings& settings); + static bool DestroySocket(NetworkSocket& socket); + static bool ConnectSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool BindSocket(NetworkSocket& socket, NetworkEndPoint& endPoint); + static bool Accept(NetworkSocket& serverSock, NetworkSocket& newSock, NetworkEndPoint& newEndPoint); + static bool IsReadable(NetworkSocket& socket, uint64* size); + static uint32 WriteSocket(NetworkSocket socket, byte* data, uint32 length, NetworkEndPoint* endPoint = nullptr); + static uint32 ReadSocket(NetworkSocket socket, byte* buffer, uint32 bufferSize, NetworkEndPoint* endPoint = nullptr); + static bool CreateEndPoint(String* address, String* port, NetworkIPVersion ipv, NetworkEndPoint& endPoint); + static NetworkEndPoint RemapEndPointToIPv6(NetworkEndPoint endPoint); + friend NetworkEndPoint; + friend NetworkSocket; +}; + +#endif diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index 129ff4249..db6151a17 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -11,6 +11,7 @@ #include "Engine/Core/Math/Math.h" #include "IncludeWindowsHeaders.h" #include "Engine/Core/Collections/HashFunctions.h" +#include "Engine/Platform/Network.h" #include #include @@ -212,9 +213,17 @@ bool Win32Platform::Init() DeviceId.D = (uint32)cpuInfo.ClockSpeed * cpuInfo.LogicalProcessorCount * cpuInfo.ProcessorCoreCount * cpuInfo.CacheLineSize; } + //TODO: log error if true + Win32Network::Init(); + return false; } +void Win32Platform::Exit() +{ + Network::Exit(); +} + void Win32Platform::MemoryBarrier() { // NOTE: _ReadWriteBarrier and friends only prevent the diff --git a/Source/Engine/Platform/Win32/Win32Platform.h b/Source/Engine/Platform/Win32/Win32Platform.h index b6a4cdd62..cde506278 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.h +++ b/Source/Engine/Platform/Win32/Win32Platform.h @@ -16,6 +16,7 @@ public: // [PlatformBase] static bool Init(); + static void Exit(); static void MemoryBarrier(); static int64 InterlockedExchange(int64 volatile* dst, int64 exchange); static int32 InterlockedCompareExchange(int32 volatile* dst, int32 exchange, int32 comperand); diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 0b1f70989..13c52d9a8 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -624,6 +624,8 @@ void WindowsPlatform::Exit() // Unregister app class UnregisterClassW(ApplicationWindowClass, nullptr); + + Win32Platform::Exit(); } #if !BUILD_RELEASE