diff options
Diffstat (limited to 'Source/Core/Common')
| -rw-r--r-- | Source/Core/Common/Common.vcxproj | 5 | ||||
| -rw-r--r-- | Source/Core/Common/Common.vcxproj.filters | 7 | ||||
| -rw-r--r-- | Source/Core/Common/TraversalClient.cpp | 370 | ||||
| -rw-r--r-- | Source/Core/Common/TraversalClient.h | 88 | ||||
| -rw-r--r-- | Source/Core/Common/TraversalProto.h | 96 |
5 files changed, 564 insertions, 2 deletions
diff --git a/Source/Core/Common/Common.vcxproj b/Source/Core/Common/Common.vcxproj index dfe91db48b..fdd3183587 100644 --- a/Source/Core/Common/Common.vcxproj +++ b/Source/Core/Common/Common.vcxproj @@ -80,6 +80,8 @@ <ClInclude Include="SysConf.h" /> <ClInclude Include="Thread.h" /> <ClInclude Include="Timer.h" /> + <ClInclude Include="TraversalClient.h" /> + <ClInclude Include="TraversalProto.h" /> <ClInclude Include="x64ABI.h" /> <ClInclude Include="x64Analyzer.h" /> <ClInclude Include="x64Emitter.h" /> @@ -116,6 +118,7 @@ <ClCompile Include="SysConf.cpp" /> <ClCompile Include="Thread.cpp" /> <ClCompile Include="Timer.cpp" /> + <ClCompile Include="TraversalClient.cpp" /> <ClCompile Include="Version.cpp" /> <ClCompile Include="x64ABI.cpp" /> <ClCompile Include="x64Analyzer.cpp" /> @@ -142,4 +145,4 @@ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Label="ExtensionTargets"> </ImportGroup> -</Project> +</Project>
\ No newline at end of file diff --git a/Source/Core/Common/Common.vcxproj.filters b/Source/Core/Common/Common.vcxproj.filters index 2f121ca882..da11bd379b 100644 --- a/Source/Core/Common/Common.vcxproj.filters +++ b/Source/Core/Common/Common.vcxproj.filters @@ -71,6 +71,9 @@ </ClInclude> <ClInclude Include="GekkoDisassembler.h" /> <ClInclude Include="Event.h" /> + <ClInclude Include="JitRegister.h" /> + <ClInclude Include="TraversalClient.h" /> + <ClInclude Include="TraversalProto.h" /> </ItemGroup> <ItemGroup> <ClCompile Include="BreakPoints.cpp" /> @@ -117,8 +120,10 @@ </ClCompile> <ClCompile Include="XSaveWorkaround.cpp" /> <ClCompile Include="GekkoDisassembler.cpp" /> + <ClCompile Include="JitRegister.cpp" /> + <ClCompile Include="TraversalClient.cpp" /> </ItemGroup> <ItemGroup> <Text Include="CMakeLists.txt" /> </ItemGroup> -</Project> +</Project>
\ No newline at end of file diff --git a/Source/Core/Common/TraversalClient.cpp b/Source/Core/Common/TraversalClient.cpp new file mode 100644 index 0000000000..9ac5eca238 --- /dev/null +++ b/Source/Core/Common/TraversalClient.cpp @@ -0,0 +1,370 @@ +// This file is public domain, in case it's useful to anyone. -comex + +#include "Common/TraversalClient.h" +#include "enet/enet.h" +#include "Timer.h" + +static void GetRandomishBytes(u8* buf, size_t size) +{ + // We don't need high quality random numbers (which might not be available), + // just non-repeating numbers! + srand(enet_time_get()); + for (size_t i = 0; i < size; i++) + buf[i] = rand() & 0xff; +} + +TraversalClient::TraversalClient(ENetHost* netHost, const std::string& server) + : m_NetHost(netHost) + , m_Server(server) + , m_Client(nullptr) + , m_FailureReason(0) + , m_ConnectRequestId(0) + , m_PendingConnect(false) + , m_PingTime(0) +{ + netHost->intercept = TraversalClient::InterceptCallback; + + Reset(); + + ReconnectToServer(); +} + +TraversalClient::~TraversalClient() +{ +} + +void TraversalClient::ReconnectToServer() +{ + m_Server = "vps.qoid.us"; // XXX + if (enet_address_set_host(&m_ServerAddress, m_Server.c_str())) + { + OnFailure(BadHost); + return; + } + m_ServerAddress.port = 6262; + + m_State = Connecting; + + TraversalPacket hello = {}; + hello.type = TraversalPacketHelloFromClient; + hello.helloFromClient.protoVersion = TraversalProtoVersion; + SendTraversalPacket(hello); + if (m_Client) + m_Client->OnTraversalStateChanged(); +} + +static ENetAddress MakeENetAddress(TraversalInetAddress* address) +{ + ENetAddress eaddr; + if (address->isIPV6) + { + eaddr.port = 0; // no support yet :( + } + else + { + eaddr.host = address->address[0]; + eaddr.port = ntohs(address->port); + } + return eaddr; +} + +void TraversalClient::ConnectToClient(const std::string& host) +{ + if (host.size() > sizeof(TraversalHostId)) + { + PanicAlert("host too long"); + return; + } + TraversalPacket packet = {}; + packet.type = TraversalPacketConnectPlease; + memcpy(packet.connectPlease.hostId.data(), host.c_str(), host.size()); + m_ConnectRequestId = SendTraversalPacket(packet); + m_PendingConnect = true; +} + +bool TraversalClient::TestPacket(u8* data, size_t size, ENetAddress* from) +{ + if (from->host == m_ServerAddress.host && + from->port == m_ServerAddress.port) + { + if (size < sizeof(TraversalPacket)) + { + ERROR_LOG(NETPLAY, "Received too-short traversal packet."); + } + else + { + HandleServerPacket((TraversalPacket*) data); + return true; + } + } + return false; +} + +//--Temporary until more of the old netplay branch is moved over +void TraversalClient::Update() +{ + ENetEvent netEvent; + if (enet_host_service(m_NetHost, &netEvent, 4) > 0) + { + switch (netEvent.type) + { + case ENET_EVENT_TYPE_RECEIVE: + TestPacket(netEvent.packet->data, netEvent.packet->dataLength, &netEvent.peer->address); + + enet_packet_destroy(netEvent.packet); + break; + } + } + HandleResends(); +} + +void TraversalClient::HandleServerPacket(TraversalPacket* packet) +{ + u8 ok = 1; + switch (packet->type) + { + case TraversalPacketAck: + if (!packet->ack.ok) + { + OnFailure(ServerForgotAboutUs); + break; + } + for (auto it = m_OutgoingTraversalPackets.begin(); it != m_OutgoingTraversalPackets.end(); ++it) + { + if (it->packet.requestId == packet->requestId) + { + m_OutgoingTraversalPackets.erase(it); + break; + } + } + break; + case TraversalPacketHelloFromServer: + if (m_State != Connecting) + break; + if (!packet->helloFromServer.ok) + { + OnFailure(VersionTooOld); + break; + } + m_HostId = packet->helloFromServer.yourHostId; + m_State = Connected; + if (m_Client) + m_Client->OnTraversalStateChanged(); + break; + case TraversalPacketPleaseSendPacket: + { + // security is overrated. + ENetAddress addr = MakeENetAddress(&packet->pleaseSendPacket.address); + if (addr.port != 0) + { + char message[] = "Hello from Dolphin Netplay..."; + ENetBuffer buf; + buf.data = message; + buf.dataLength = sizeof(message) - 1; + enet_socket_send(m_NetHost->socket, &addr, &buf, 1); + } + else + { + // invalid IPV6 + ok = 0; + } + break; + } + case TraversalPacketConnectReady: + case TraversalPacketConnectFailed: + { + if (!m_PendingConnect || packet->connectReady.requestId != m_ConnectRequestId) + break; + + m_PendingConnect = false; + + if (!m_Client) + break; + + if (packet->type == TraversalPacketConnectReady) + m_Client->OnConnectReady(MakeENetAddress(&packet->connectReady.address)); + else + m_Client->OnConnectFailed(packet->connectFailed.reason); + break; + } + default: + WARN_LOG(NETPLAY, "Received unknown packet with type %d", packet->type); + break; + } + if (packet->type != TraversalPacketAck) + { + TraversalPacket ack = {}; + ack.type = TraversalPacketAck; + ack.requestId = packet->requestId; + ack.ack.ok = ok; + + ENetBuffer buf; + buf.data = &ack; + buf.dataLength = sizeof(ack); + if (enet_socket_send(m_NetHost->socket, &m_ServerAddress, &buf, 1) == -1) + OnFailure(SocketSendError); + } +} + +void TraversalClient::OnFailure(int reason) +{ + m_State = Failure; + m_FailureReason = reason; + + switch (reason) + { + case TraversalClient::BadHost: + { + auto server = "dolphin-emu.org"; + PanicAlertT("Couldn't look up central server %s", server); + break; + } + case TraversalClient::VersionTooOld: + PanicAlertT("Dolphin too old for traversal server"); + break; + case TraversalClient::ServerForgotAboutUs: + PanicAlertT("Disconnected from traversal server"); + break; + case TraversalClient::SocketSendError: + PanicAlertT("Socket error sending to traversal server"); + break; + case TraversalClient::ResendTimeout: + PanicAlertT("Timeout connecting to traversal server"); + break; + default: + PanicAlertT("Unknown error %x", reason); + break; + } + + if (m_Client) + m_Client->OnTraversalStateChanged(); +} + +void TraversalClient::ResendPacket(OutgoingTraversalPacketInfo* info) +{ + info->sendTime = enet_time_get(); + info->tries++; + ENetBuffer buf; + buf.data = &info->packet; + buf.dataLength = sizeof(info->packet); + if (enet_socket_send(m_NetHost->socket, &m_ServerAddress, &buf, 1) == -1) + OnFailure(SocketSendError); +} + +void TraversalClient::HandleResends() +{ + enet_uint32 now = enet_time_get(); + for (auto& tpi : m_OutgoingTraversalPackets) + { + if (now - tpi.sendTime >= (u32) (300 * tpi.tries)) + { + if (tpi.tries >= 5) + { + OnFailure(ResendTimeout); + m_OutgoingTraversalPackets.clear(); + break; + } + else + { + ResendPacket(&tpi); + } + } + } + HandlePing(); +} + +void TraversalClient::HandlePing() +{ + enet_uint32 now = enet_time_get(); + if (m_State == Connected && now - m_PingTime >= 500) + { + TraversalPacket ping = {0}; + ping.type = TraversalPacketPing; + ping.ping.hostId = m_HostId; + SendTraversalPacket(ping); + m_PingTime = now; + } +} + +TraversalRequestId TraversalClient::SendTraversalPacket(const TraversalPacket& packet) +{ + OutgoingTraversalPacketInfo info; + info.packet = packet; + GetRandomishBytes((u8*) &info.packet.requestId, sizeof(info.packet.requestId)); + info.tries = 0; + m_OutgoingTraversalPackets.push_back(info); + ResendPacket(&m_OutgoingTraversalPackets.back()); + return info.packet.requestId; +} + +void TraversalClient::Reset() +{ + m_PendingConnect = false; + m_Client = nullptr; +} + +int ENET_CALLBACK TraversalClient::InterceptCallback(ENetHost* host, ENetEvent* event) +{ + auto traversalClient = g_TraversalClient.get(); + if (traversalClient->TestPacket(host->receivedData, host->receivedDataLength, &host->receivedAddress)) + { + event->type = (ENetEventType)42; + return 1; + } + return 0; +} + +std::unique_ptr<TraversalClient> g_TraversalClient; +std::unique_ptr<ENetHost> g_MainNetHost; + +// The settings at the previous TraversalClient reset - notably, we +// need to know not just what port it's on, but whether it was +// explicitly requested. +static std::string g_OldServer; +static u16 g_OldPort; + +bool EnsureTraversalClient(const std::string& server, u16 port) +{ + if (!g_MainNetHost || !g_TraversalClient || server != g_OldServer || port != g_OldPort) + { + g_OldServer = server; + g_OldPort = port; + + ENetAddress addr = { ENET_HOST_ANY, port }; + ENetHost* host = enet_host_create( + &addr, // address + 50, // peerCount + 1, // channelLimit + 0, // incomingBandwidth + 0); // outgoingBandwidth + if (!host) + { + g_MainNetHost.reset(); + return false; + } + g_MainNetHost.reset(host); + + g_TraversalClient.reset(new TraversalClient(g_MainNetHost.get(), server)); + + } + return true; +} + +void ReleaseTraversalClient() +{ + if (!g_TraversalClient) + return; + + if (g_OldPort != 0) + { + // If we were listening at a specific port, kill the + // TraversalClient to avoid hanging on to the port. + g_TraversalClient.reset(); + g_MainNetHost.reset(); + } + else + { + // Reset any pending connection attempts. + g_TraversalClient->Reset(); + } +} diff --git a/Source/Core/Common/TraversalClient.h b/Source/Core/Common/TraversalClient.h new file mode 100644 index 0000000000..838ea6448a --- /dev/null +++ b/Source/Core/Common/TraversalClient.h @@ -0,0 +1,88 @@ +// This file is public domain, in case it's useful to anyone. -comex + +#pragma once +#include <functional> +#include <list> +#include <memory> +#include "Common/Common.h" +#include "Common/Thread.h" +#include "Common/TraversalProto.h" + +#include "enet/enet.h" + +class TraversalClientClient +{ +public: + virtual ~TraversalClientClient(){}; + virtual void OnTraversalStateChanged()=0; + virtual void OnConnectReady(ENetAddress addr)=0; + virtual void OnConnectFailed(u8 reason)=0; +}; + +class TraversalClient +{ +public: + enum State + { + Connecting, + Connected, + Failure + }; + + enum FailureReason + { + BadHost = 0x300, + VersionTooOld, + ServerForgotAboutUs, + SocketSendError, + ResendTimeout, + ConnectFailedError = 0x400, + }; + + TraversalClient(ENetHost* netHost, const std::string& server); + ~TraversalClient(); + void Reset(); + void ConnectToClient(const std::string& host); + void ReconnectToServer(); + void Update(); + + // called from NetHost + bool TestPacket(u8* data, size_t size, ENetAddress* from); + void HandleResends(); + + ENetHost* m_NetHost; + TraversalClientClient* m_Client; + TraversalHostId m_HostId; + State m_State; + int m_FailureReason; + +private: + struct OutgoingTraversalPacketInfo + { + TraversalPacket packet; + int tries; + enet_uint32 sendTime; + }; + + void HandleServerPacket(TraversalPacket* packet); + void ResendPacket(OutgoingTraversalPacketInfo* info); + TraversalRequestId SendTraversalPacket(const TraversalPacket& packet); + void OnFailure(int reason); + void HandlePing(); + static int ENET_CALLBACK InterceptCallback(ENetHost* host, ENetEvent* event); + + TraversalRequestId m_ConnectRequestId; + bool m_PendingConnect; + std::list<OutgoingTraversalPacketInfo> m_OutgoingTraversalPackets; + ENetAddress m_ServerAddress; + std::string m_Server; + enet_uint32 m_PingTime; +}; + +extern std::unique_ptr<TraversalClient> g_TraversalClient; +// the NetHost connected to the TraversalClient. +extern std::unique_ptr<ENetHost> g_MainNetHost; + +// Create g_TraversalClient and g_MainNetHost if necessary. +bool EnsureTraversalClient(const std::string& server, u16 port); +void ReleaseTraversalClient(); diff --git a/Source/Core/Common/TraversalProto.h b/Source/Core/Common/TraversalProto.h new file mode 100644 index 0000000000..32891beac6 --- /dev/null +++ b/Source/Core/Common/TraversalProto.h @@ -0,0 +1,96 @@ +// This file is public domain, in case it's useful to anyone. -comex + +#pragma once +#include <array> +#include "Common/CommonTypes.h" + + +typedef std::array<char, 8> TraversalHostId; +typedef u64 TraversalRequestId; + +enum TraversalPacketType +{ + // [*->*] + TraversalPacketAck = 0, + // [c->s] + TraversalPacketPing = 1, + // [c->s] + TraversalPacketHelloFromClient = 2, + // [s->c] + TraversalPacketHelloFromServer = 3, + // [c->s] When connecting, first the client asks the central server... + TraversalPacketConnectPlease = 4, + // [s->c] ...who asks the game host to send a UDP packet to the + // client... (an ack implies success) + TraversalPacketPleaseSendPacket = 5, + // [s->c] ...which the central server relays back to the client. + TraversalPacketConnectReady = 6, + // [s->c] Alternately, the server might not have heard of this host. + TraversalPacketConnectFailed = 7 +}; + +enum +{ + TraversalProtoVersion = 0 +}; + +enum TraversalConnectFailedReason +{ + TraversalConnectFailedClientDidntRespond = 0, + TraversalConnectFailedClientFailure, + TraversalConnectFailedNoSuchClient +}; + +#pragma pack(push, 1) +struct TraversalInetAddress +{ + u8 isIPV6; + u32 address[4]; + u16 port; +}; +struct TraversalPacket +{ + u8 type; + TraversalRequestId requestId; + union + { + struct + { + u8 ok; + } ack; + struct + { + TraversalHostId hostId; + } ping; + struct + { + u8 protoVersion; + } helloFromClient; + struct + { + u8 ok; + TraversalHostId yourHostId; + TraversalInetAddress yourAddress; // currently unused + } helloFromServer; + struct + { + TraversalHostId hostId; + } connectPlease; + struct + { + TraversalInetAddress address; + } pleaseSendPacket; + struct + { + TraversalRequestId requestId; + TraversalInetAddress address; + } connectReady; + struct + { + TraversalRequestId requestId; + u8 reason; + } connectFailed; + }; +}; +#pragma pack(pop) + |
