From e61dcbe4c31c76a4b1c9bc5e1b9e577883963973 Mon Sep 17 00:00:00 2001 From: Julius Michaelis Date: Thu, 20 Oct 2016 21:14:08 +0200 Subject: [PATCH] Extend the netpuncher: clients can ask hosts for a connection initiation to circumvent port restricted NATs --- CMakeLists.txt | 5 ++ src/config/C4Config.cpp | 2 +- src/netpuncher/C4PuncherHash.h | 71 ++++++++++++++++++++++++++ src/netpuncher/C4PuncherPacket.cpp | 74 +++++++++++++++++++++++++++ src/netpuncher/C4PuncherPacket.h | 77 +++++++++++++++++++++++++++++ src/netpuncher/main.cpp | 52 ++++++++++++++++--- src/network/C4NetIO.h | 2 +- src/network/C4Network2.cpp | 58 ++++++++++++++++++++-- src/network/C4Network2.h | 12 +++++ src/network/C4Network2Address.cpp | 74 +++++++++++++++++++++++++++ src/network/C4Network2Address.h | 63 +++++++++++++++++++++++ src/network/C4Network2Client.cpp | 50 +------------------ src/network/C4Network2Client.h | 43 +--------------- src/network/C4Network2IO.cpp | 68 +++++++++++++++++-------- src/network/C4Network2IO.h | 9 ++-- src/network/C4Network2Reference.cpp | 6 ++- src/network/C4Network2Reference.h | 4 ++ src/network/C4Packet2.cpp | 1 - src/network/C4PacketBase.h | 5 +- 19 files changed, 543 insertions(+), 133 deletions(-) create mode 100644 src/netpuncher/C4PuncherHash.h create mode 100644 src/netpuncher/C4PuncherPacket.cpp create mode 100644 src/netpuncher/C4PuncherPacket.h create mode 100644 src/network/C4Network2Address.cpp create mode 100644 src/network/C4Network2Address.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d200fe151..3881f203a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -991,8 +991,12 @@ src/lib/StdBuf.h src/lib/StdCompiler.cpp src/lib/StdCompiler.h src/lib/StdResStr2.cpp +src/netpuncher/C4PuncherPacket.cpp +src/netpuncher/C4PuncherPacket.h src/network/C4NetIO.cpp src/network/C4NetIO.h +src/network/C4Network2Address.cpp +src/network/C4Network2Address.h src/platform/StdFile.cpp src/platform/StdFile.h src/platform/StdRegistry.cpp @@ -1235,6 +1239,7 @@ target_link_libraries(c4group ) add_executable(netpuncher EXCLUDE_FROM_ALL + src/netpuncher/C4PuncherHash.h src/netpuncher/main.cpp ) diff --git a/src/config/C4Config.cpp b/src/config/C4Config.cpp index 2529c475a..742106da4 100644 --- a/src/config/C4Config.cpp +++ b/src/config/C4Config.cpp @@ -184,7 +184,7 @@ void C4ConfigNetwork::CompileFunc(StdCompiler *pComp) pComp->Value(mkNamingAdapt(PacketLogging, "PacketLogging", 0 )); - pComp->Value(mkNamingAdapt(s(PuncherAddress), "PuncherAddress", "netpuncher.openclonk.org:11115")); // maybe store default for this one? + pComp->Value(mkNamingAdapt(s(PuncherAddress), "PuncherAddress", "netpuncher.openclonk.org:11115")); pComp->Value(mkNamingAdapt(mkParAdapt(LastLeagueServer, StdCompiler::RCT_All), "LastLeagueServer", "" )); pComp->Value(mkNamingAdapt(mkParAdapt(LastLeaguePlayerName, StdCompiler::RCT_All), "LastLeaguePlayerName", "" )); pComp->Value(mkNamingAdapt(mkParAdapt(LastLeagueAccount, StdCompiler::RCT_All), "LastLeagueAccount", "" )); diff --git a/src/netpuncher/C4PuncherHash.h b/src/netpuncher/C4PuncherHash.h new file mode 100644 index 000000000..fcf435b84 --- /dev/null +++ b/src/netpuncher/C4PuncherHash.h @@ -0,0 +1,71 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2016, The OpenClonk Team and contributors + * + * Distributed under the terms of the ISC license; see accompanying file + * "COPYING" for details. + * + * "Clonk" is a registered trademark of Matthes Bender, used with permission. + * See accompanying file "TRADEMARK" for details. + * + * To redistribute this file separately, substitute the full license texts + * for the above references. + */ + +#ifndef C4PuncherHash_H +#define C4PuncherHash_H + +#include +#include "network/C4NetIO.h" + +namespace { + + size_t hash_combiner(size_t left, size_t right) //replacable + { return left*2793419347^right; } + + template + struct hash_impl { + size_t operator()(size_t a, const std::tuple& t) const { + typedef typename std::tuple_element>::type nexttype; + hash_impl next; + size_t b = std::hash()(std::get(t)); + return next(hash_combiner(a, b), t); + } + }; + template + struct hash_impl<1, types...> { + size_t operator()(size_t a, const std::tuple& t) const { + typedef typename std::tuple_element<0, std::tuple>::type nexttype; + size_t b = std::hash()(std::get<0>(t)); + return hash_combiner(a, b); + } + }; + template + struct hash_impl<0, types...> { + size_t operator()(size_t a, const std::tuple& t) const { + return 0; + } + }; +} + +namespace std { + // Remove this when C++ gets proper tuple hashes. For now, http://stackoverflow.com/questions/7110301/generic-hash-for-tuples-in-unordered-map-unordered-set will do + template + struct hash> { + size_t operator()(const std::tuple& t) const { + const size_t begin = std::tuple_size>::value; + return hash_impl()(19739, t); + } + }; + + template<> + struct hash<::sockaddr_in> { + size_t operator()(const ::sockaddr_in& addr) const { + auto unpack = make_tuple(addr.sin_family, addr.sin_addr.s_addr, addr.sin_port); + return hash()(unpack); + } + }; +} + +#endif diff --git a/src/netpuncher/C4PuncherPacket.cpp b/src/netpuncher/C4PuncherPacket.cpp new file mode 100644 index 000000000..12929d8cb --- /dev/null +++ b/src/netpuncher/C4PuncherPacket.cpp @@ -0,0 +1,74 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2016, The OpenClonk Team and contributors + * + * Distributed under the terms of the ISC license; see accompanying file + * "COPYING" for details. + * + * "Clonk" is a registered trademark of Matthes Bender, used with permission. + * See accompanying file "TRADEMARK" for details. + * + * To redistribute this file separately, substitute the full license texts + * for the above references. + */ + +#include "C4Include.h" +#include "netpuncher/C4PuncherPacket.h" +#include "network/C4Network2Address.h" +#include + +std::unique_ptr C4NetpuncherPacket::Construct(const C4NetIOPacket& rpack) { + if (!rpack.getPData()) return nullptr; + try { + switch (rpack.getStatus()) + { + case PID_Puncher_AssID: return uptr(new C4NetpuncherPacketAssID(rpack)); + case PID_Puncher_SReq: return uptr(new C4NetpuncherPacketSReq(rpack)); + case PID_Puncher_CReq: return uptr(new C4NetpuncherPacketCReq(rpack)); + default: return nullptr; + } + } + catch (StdCompiler::Exception *e) { delete e; return nullptr; } + catch (...) { return nullptr; } +} +C4NetIOPacket C4NetpuncherPacket::PackTo(const C4NetIO::addr_t& addr) const { + C4NetIOPacket pkt; + pkt.SetAddr(addr); + StdBuf content(PackInto()); + char type = GetType(); + pkt.New(sizeof(type) + content.getSize()); + pkt.Write(&type, sizeof(type)); + pkt.Write(content, /*offset*/sizeof(type)); + return pkt; +} + +C4NetpuncherPacketCReq::C4NetpuncherPacketCReq(const C4NetIOPacket& rpack) { + C4Network2Address parse_addr; + CompileFromBuf(parse_addr, rpack.getPBuf()); + if (parse_addr.getProtocol() != P_UDP) throw P_UDP; + addr.sin_addr.s_addr = parse_addr.getAddr().sin_addr.s_addr, + addr.sin_family = parse_addr.getAddr().sin_family, + addr.sin_port = parse_addr.getAddr().sin_port; +} + +StdBuf C4NetpuncherPacketCReq::PackInto() const { + C4Network2Address ser_addr; + ser_addr.SetAddr(addr); + ser_addr.SetProtocol(P_UDP); + return DecompileToBuf(ser_addr); +} + +template +C4NetpuncherPacketID::C4NetpuncherPacketID(const C4NetIOPacket& rpack) { + std::istringstream iss(std::string(rpack.getPData(), rpack.getPSize())); + iss >> id; +} + +template +StdBuf C4NetpuncherPacketID::PackInto() const { + std::ostringstream oss; + oss << GetID(); + std::string s = oss.str(); + return StdCopyBuf(s.c_str(), s.length()); +} diff --git a/src/netpuncher/C4PuncherPacket.h b/src/netpuncher/C4PuncherPacket.h new file mode 100644 index 000000000..e1e2be45a --- /dev/null +++ b/src/netpuncher/C4PuncherPacket.h @@ -0,0 +1,77 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2016, The OpenClonk Team and contributors + * + * Distributed under the terms of the ISC license; see accompanying file + * "COPYING" for details. + * + * "Clonk" is a registered trademark of Matthes Bender, used with permission. + * See accompanying file "TRADEMARK" for details. + * + * To redistribute this file separately, substitute the full license texts + * for the above references. + */ + +#ifndef C4PuncherPacket_H +#define C4PuncherPacket_H + +/* I'm cooking up a seperate solution for the netpuncher because using C4Packet2.cpp would require introducing half a ton of stubs, and it seems like overkill */ + +#include "network/C4NetIO.h" + +enum C4NetpuncherPacketType { + PID_Puncher_AssID = 0x51, // Puncher announcing ID to client + PID_Puncher_SReq = 0x52, // Client requesting to be served with punching (for an ID) + PID_Puncher_CReq = 0x53, // Puncher requesting clients to punch (towards an address) + // extend this with exchanging ICE parameters, some day? +}; + +typedef uint32_t C4NetpuncherID_t; + +class C4NetpuncherPacket { +public: + typedef std::unique_ptr uptr; + static std::unique_ptr Construct(const C4NetIOPacket& rpack); + virtual ~C4NetpuncherPacket() {}; + virtual C4NetpuncherPacketType GetType() const = 0; + C4NetIOPacket PackTo(const C4NetIO::addr_t&) const; +protected: + virtual StdBuf PackInto() const = 0; + typedef C4NetpuncherID_t CID; +}; + +template +class C4NetpuncherPacketID : public C4NetpuncherPacket { +private: + CID id; + StdBuf PackInto() const override; +protected: + C4NetpuncherPacketID(const C4NetIOPacket& rpack); + C4NetpuncherPacketID(CID id) : id(id) {}; +public: + C4NetpuncherPacketType GetType() const final { return TYPE; } + CID GetID() const { return id; } + virtual ~C4NetpuncherPacketID() {}; +}; + +#define PIDC(n) \ + class C4NetpuncherPacket##n : public C4NetpuncherPacketID { \ + public: explicit C4NetpuncherPacket##n(const C4NetIOPacket& p) : C4NetpuncherPacketID(p) {}; \ + public: explicit C4NetpuncherPacket##n(CID id) : C4NetpuncherPacketID(id) {}; \ + } +PIDC(AssID); PIDC(SReq); +#undef PIDC + +class C4NetpuncherPacketCReq : public C4NetpuncherPacket { +private: + C4NetIO::addr_t addr; + StdBuf PackInto() const override; +public: + C4NetpuncherPacketType GetType() const final { return PID_Puncher_CReq; } + explicit C4NetpuncherPacketCReq(const C4NetIOPacket& rpack); + explicit C4NetpuncherPacketCReq(const C4NetIO::addr_t& addr) : addr(addr) {}; + const C4NetIO::addr_t& GetAddr() { return addr; } +}; + +#endif diff --git a/src/netpuncher/main.cpp b/src/netpuncher/main.cpp index f9c7b6d67..405bfecb6 100644 --- a/src/netpuncher/main.cpp +++ b/src/netpuncher/main.cpp @@ -2,7 +2,7 @@ * OpenClonk, http://www.openclonk.org * * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ - * Copyright (c) 2010-2013, The OpenClonk Team and contributors + * Copyright (c) 2010-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. @@ -15,25 +15,61 @@ */ #include "C4Include.h" +#include "netpuncher/C4PuncherHash.h" #include "network/C4Network2.h" +#include "netpuncher/C4PuncherPacket.h" #include +#include +#include +#include +#include + class C4PuncherServer : public C4NetIOUDP, private C4NetIO::CBClass { public: - C4PuncherServer() { C4NetIOUDP::SetCallback(this); } + typedef C4NetpuncherID_t CID; + C4PuncherServer() { + C4NetIOUDP::SetCallback(this); + rng = std::bind(std::uniform_int_distribution(1/*, max*/), std::ref(random_device)); + } private: + std::random_device random_device; + std::function rng; + std::unordered_map peer_ids; + std::unordered_map peer_addrs; // Event handlers - virtual bool OnConn(const addr_t &AddrPeer, const addr_t &AddrConnect, const addr_t *OwnAddr, C4NetIO *pNetIO) - { - printf("Punched back at %s:%d...\n", inet_ntoa(AddrPeer.sin_addr), htons(AddrPeer.sin_port)); + virtual bool OnConn(const addr_t &AddrPeer, const addr_t &AddrConnect, const addr_t *OwnAddr, C4NetIO *pNetIO) { + CID nid; + do { + nid = rng(); + } while(peer_addrs.count(nid) && !nid); + peer_ids.emplace(AddrPeer, nid); + peer_addrs.emplace(nid, AddrPeer); + Send(C4NetpuncherPacketAssID(nid).PackTo(AddrPeer)); + printf("Punched %s:%d... #%u\n", inet_ntoa(AddrPeer.sin_addr), htons(AddrPeer.sin_port), nid); return true; } - virtual void OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO) - { - // Unused + virtual void OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO) { + auto& addr = rPacket.getAddr(); + auto unpack = C4NetpuncherPacket::Construct(rPacket); + if (!unpack || unpack->GetType() != PID_Puncher_SReq) { Close(addr); return; } + auto other_it = peer_addrs.find(dynamic_cast(unpack.get())->GetID()); + if (other_it == peer_addrs.end()) return; // Might be nice to return some kind of error, for purposes of debugging. + Send(C4NetpuncherPacketCReq(other_it->second).PackTo(addr)); + Send(C4NetpuncherPacketCReq(addr).PackTo(other_it->second)); } + virtual void OnDisconn(const addr_t &AddrPeer, C4NetIO *pNetIO, const char *szReason) { + auto it = peer_ids.find(AddrPeer); + if (it == peer_ids.end()) { + printf("ERROR: closing connection for %s:%d: (%s) but no connection is known\n", inet_ntoa(AddrPeer.sin_addr), htons(AddrPeer.sin_port), szReason); + return; + } + peer_addrs.erase(it->second); + peer_ids.erase(it); + printf("Stopped punching %s:%d: %s...\n", inet_ntoa(AddrPeer.sin_addr), htons(AddrPeer.sin_port), szReason); + }; } Puncher; int main(int argc, char * argv[]) diff --git a/src/network/C4NetIO.h b/src/network/C4NetIO.h index fe6f6b435..936e77536 100644 --- a/src/network/C4NetIO.h +++ b/src/network/C4NetIO.h @@ -451,6 +451,7 @@ public: virtual bool Close(const addr_t &addr); virtual bool Send(const C4NetIOPacket &rPacket); + bool SendDirect(C4NetIOPacket &&rPacket); // (mt-safe) virtual bool Broadcast(const C4NetIOPacket &rPacket); virtual bool SetBroadcast(const addr_t &addr, bool fSet = true); @@ -739,7 +740,6 @@ protected: // sending bool BroadcastDirect(const Packet &rPacket, unsigned int iNr = ~0u); // (mt-safe) - bool SendDirect(C4NetIOPacket &&rPacket); // (mt-safe) // multicast related bool DoLoopbackTest(); diff --git a/src/network/C4Network2.cpp b/src/network/C4Network2.cpp index d8b3661b4..6851f1d95 100644 --- a/src/network/C4Network2.cpp +++ b/src/network/C4Network2.cpp @@ -147,7 +147,8 @@ C4Network2::C4Network2() pVoteDialog(NULL), fPausedForVote(false), iLastOwnVoting(0), - fStreaming(false) + fStreaming(false), + NetpuncherGameID(0) { } @@ -168,6 +169,8 @@ bool C4Network2::InitHost(bool fLobby) fChasing = false; fAllowJoin = false; iNextClientID = C4ClientIDStart; + NetpuncherGameID = 0; + NetpuncherAddr = ::Config.Network.PuncherAddress; // initialize client list Clients.Init(&Game.Clients, true); // initialize resource list @@ -228,6 +231,8 @@ C4Network2::InitResult C4Network2::InitClient(const C4Network2Reference &Ref, bo } // repeat if wrong password fWrongPassword = Ref.isPasswordNeeded(); + NetpuncherGameID = Ref.getNetpuncherGameID(); + NetpuncherAddr = Ref.getNetpuncherAddr(); StdStrBuf Password; for (;;) { @@ -295,6 +300,8 @@ C4Network2::InitResult C4Network2::InitClient(const class C4Network2Address *pAd pControl = &::Control.Network; // set exclusive connection mode NetIO.SetExclusiveConnMode(true); + // warm up netpuncher + InitPuncher(); // try to connect host StdStrBuf strAddresses; int iSuccesses = 0; for (int i = 0; i < iAddrCount; i++) @@ -663,6 +670,7 @@ void C4Network2::Clear() delete pVoteDialog; pVoteDialog = NULL; fPausedForVote = false; iLastOwnVoting = 0; + NetpuncherGameID = 0; Votes.Clear(); // don't clear fPasswordNeeded here, it's needed by InitClient } @@ -880,6 +888,49 @@ void C4Network2::HandleLobbyPacket(char cStatus, const C4PacketBase *pBasePkt, C if (pLobby) pLobby->HandlePacket(cStatus, pBasePkt, pClient); } +bool C4Network2::HandlePuncherPacket(C4NetpuncherPacket::uptr pkt) +{ + // TODO: is this all thread-safe? + assert(pkt); +#define GETPKT(c) dynamic_cast(pkt.get()) + switch (pkt->GetType()) + { + case PID_Puncher_CReq: + if (isHost()) + { + NetIO.Punch(GETPKT(CReq)->GetAddr()); + return true; + } + else + { + // The IP/Port should be already in the masterserver list, so just keep trying. + return Status.getState() == GS_Init; + } + case PID_Puncher_AssID: + if (isHost()) + { + NetpuncherGameID = GETPKT(AssID)->GetID(); + InvalidateReference(); + } + else + { + // While we don't need the ID as a client, this nicely serves as the signal that we can start using the netpuncher + if (Status.getState() == GS_Init && getNetpuncherGameID()) + NetIO.SendPuncherPacket(C4NetpuncherPacketSReq(getNetpuncherGameID())); + } + return true; + default: return false; + } +} + +void C4Network2::InitPuncher() +{ + // We have an internet connection, so let's punch the puncher server here in order to open an udp port + C4NetIO::addr_t PuncherAddr; + if (ResolveAddress(getNetpuncherAddr().getData(), &PuncherAddr, C4NetStdPortPuncher)) + NetIO.InitPuncher(PuncherAddr); +} + void C4Network2::OnGameSynchronized() { // savegame needed? @@ -1978,10 +2029,7 @@ bool C4Network2::LeagueStart(bool *pCancel) return false; } - // We have an internet connection, so let's punch the master server here in order to open an udp port - C4NetIO::addr_t PuncherAddr; - if (ResolveAddress(Config.Network.PuncherAddress, &PuncherAddr, C4NetStdPortPuncher)) - NetIO.Punch(PuncherAddr); + InitPuncher(); // Let's wait for response StdStrBuf Message = FormatString(LoadResStr("IDS_NET_LEAGUE_REGGAME"), pLeagueClient->getServerName()); diff --git a/src/network/C4Network2.h b/src/network/C4Network2.h index e5880d93a..629335470 100644 --- a/src/network/C4Network2.h +++ b/src/network/C4Network2.h @@ -193,6 +193,10 @@ protected: class C4Network2HTTPClient *pStreamer; unsigned int iCurrentStreamAmount, iCurrentStreamPosition; + // puncher + C4NetpuncherID_t NetpuncherGameID; + StdCopyStrBuf NetpuncherAddr; + public: // data access @@ -253,6 +257,7 @@ public: void OnDisconn(C4Network2IOConnection *pConn); void HandlePacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn); void HandleLobbyPacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn); + bool HandlePuncherPacket(C4NetpuncherPacket::uptr); // runtime join stuff void OnGameSynchronized(); @@ -297,6 +302,10 @@ public: bool FinishStreaming(); bool StopStreaming(); + // netpuncher + C4NetpuncherID_t getNetpuncherGameID() const { return NetpuncherGameID; } + StdStrBuf getNetpuncherAddr() const { return NetpuncherAddr; } + protected: using C4ApplicationSec1Timer::Execute; // to avoid "virtual ... is hidden" warning @@ -350,6 +359,9 @@ protected: // streaming bool StreamIn(bool fFinish); bool StreamOut(); + + // puncher + void InitPuncher(); }; extern C4Network2 Network; diff --git a/src/network/C4Network2Address.cpp b/src/network/C4Network2Address.cpp new file mode 100644 index 000000000..4945ed134 --- /dev/null +++ b/src/network/C4Network2Address.cpp @@ -0,0 +1,74 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ + * Copyright (c) 2010-2016, The OpenClonk Team and contributors + * + * Distributed under the terms of the ISC license; see accompanying file + * "COPYING" for details. + * + * "Clonk" is a registered trademark of Matthes Bender, used with permission. + * See accompanying file "TRADEMARK" for details. + * + * To redistribute this file separately, substitute the full license texts + * for the above references. + */ + +#include "C4Include.h" +#ifndef _WIN32 +#include +#include +#include +#include +#endif + +#include "network/C4Network2Address.h" + +// *** C4Network2Address + +void C4Network2Address::CompileFunc(StdCompiler *pComp) +{ + // Clear + if (pComp->isCompiler()) + { + ZeroMem(&addr, sizeof(addr)); + addr.sin_family = AF_INET; + } + + // Write protocol + StdEnumEntry Protocols[] = + { + { "UDP", P_UDP }, + { "TCP", P_TCP }, + + { NULL, P_NONE }, + }; + pComp->Value(mkEnumAdaptT(eProtocol, Protocols)); + pComp->Separator(StdCompiler::SEP_PART2); // ':' + + // Write IP (no IP = 0.0.0.0) + in_addr zero; zero.s_addr = INADDR_ANY; + pComp->Value(mkDefaultAdapt(addr.sin_addr, zero)); + pComp->Separator(StdCompiler::SEP_PART2); // ':' + + // Write port + uint16_t iPort = htons(addr.sin_port); + pComp->Value(iPort); + addr.sin_port = htons(iPort); +} + +StdStrBuf C4Network2Address::toString() const +{ + switch (eProtocol) + { + case P_UDP: return FormatString("UDP:%s:%d", inet_ntoa(addr.sin_addr), htons(addr.sin_port)); + case P_TCP: return FormatString("TCP:%s:%d", inet_ntoa(addr.sin_addr), htons(addr.sin_port)); + default: return StdStrBuf("INVALID"); + } +} + +bool C4Network2Address::operator == (const C4Network2Address &addr2) const +{ + return eProtocol == addr2.getProtocol() && AddrEqual(addr, addr2.getAddr()); +} + diff --git a/src/network/C4Network2Address.h b/src/network/C4Network2Address.h new file mode 100644 index 000000000..554d35201 --- /dev/null +++ b/src/network/C4Network2Address.h @@ -0,0 +1,63 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ + * Copyright (c) 2013-2016, The OpenClonk Team and contributors + * + * Distributed under the terms of the ISC license; see accompanying file + * "COPYING" for details. + * + * "Clonk" is a registered trademark of Matthes Bender, used with permission. + * See accompanying file "TRADEMARK" for details. + * + * To redistribute this file separately, substitute the full license texts + * for the above references. + */ + +#ifndef INC_C4Network2Address +#define INC_C4Network2Address + +#include "network/C4Network2IO.h" + +class C4Network2Address +{ +public: + C4Network2Address() + : eProtocol(P_NONE) + { ZeroMem(&addr, sizeof(addr)); } + + C4Network2Address(C4NetIO::addr_t addr, C4Network2IOProtocol eProtocol) + : addr(addr), eProtocol(eProtocol) + { } + + C4Network2Address(const C4Network2Address &addr) + : addr(addr.getAddr()), eProtocol(addr.getProtocol()) + { } + + void operator = (const C4Network2Address &addr) + { SetAddr(addr.getAddr()); SetProtocol(addr.getProtocol()); } + + bool operator == (const C4Network2Address &addr) const; + +protected: + C4NetIO::addr_t addr; + C4Network2IOProtocol eProtocol; + +public: + const C4NetIO::addr_t &getAddr() const { return addr; } + in_addr getIPAddr() const { return addr.sin_addr; } + bool isIPNull() const { return !addr.sin_addr.s_addr; } + uint16_t getPort() const { return htons(addr.sin_port); } + C4Network2IOProtocol getProtocol() const { return eProtocol; } + + StdStrBuf toString() const; + + void SetAddr(C4NetIO::addr_t naddr) { addr = naddr; } + void SetIP(in_addr ip) { addr.sin_addr = ip; } + void SetPort(uint16_t iPort) { addr.sin_port = htons(iPort); } + void SetProtocol(C4Network2IOProtocol enProtocol) { eProtocol = enProtocol; } + + void CompileFunc(StdCompiler *pComp); +}; + +#endif diff --git a/src/network/C4Network2Client.cpp b/src/network/C4Network2Client.cpp index ec8b6ea08..81f31917a 100644 --- a/src/network/C4Network2Client.cpp +++ b/src/network/C4Network2Client.cpp @@ -32,55 +32,6 @@ #include #endif -// *** C4Network2Address - -void C4Network2Address::CompileFunc(StdCompiler *pComp) -{ - // Clear - if (pComp->isCompiler()) - { - ZeroMem(&addr, sizeof(addr)); - addr.sin_family = AF_INET; - } - - // Write protocol - StdEnumEntry Protocols[] = - { - { "UDP", P_UDP }, - { "TCP", P_TCP }, - - { NULL, P_NONE }, - }; - pComp->Value(mkEnumAdaptT(eProtocol, Protocols)); - pComp->Separator(StdCompiler::SEP_PART2); // ':' - - // Write IP (no IP = 0.0.0.0) - in_addr zero; zero.s_addr = INADDR_ANY; - pComp->Value(mkDefaultAdapt(addr.sin_addr, zero)); - pComp->Separator(StdCompiler::SEP_PART2); // ':' - - // Write port - uint16_t iPort = htons(addr.sin_port); - pComp->Value(iPort); - addr.sin_port = htons(iPort); -} - -StdStrBuf C4Network2Address::toString() const -{ - switch (eProtocol) - { - case P_UDP: return FormatString("UDP:%s:%d", inet_ntoa(addr.sin_addr), htons(addr.sin_port)); - case P_TCP: return FormatString("TCP:%s:%d", inet_ntoa(addr.sin_addr), htons(addr.sin_port)); - default: return StdStrBuf("INVALID"); - } -} - -bool C4Network2Address::operator == (const C4Network2Address &addr2) const -{ - return eProtocol == addr2.getProtocol() && AddrEqual(addr, addr2.getAddr()); -} - - // *** C4Network2Client C4Network2Client::C4Network2Client(C4Client *pClient) @@ -201,6 +152,7 @@ bool C4Network2Client::DoConnectAttempt(C4Network2IO *pIO) bool C4Network2Client::hasAddr(const C4Network2Address &addr) const { + // Note that the host only knows its own address as 0.0.0.0, so if the real address is being added, that can't be sorted out. for (int32_t i = 0; i < iAddrCnt; i++) if (Addr[i] == addr) return true; diff --git a/src/network/C4Network2Client.h b/src/network/C4Network2Client.h index 34941c4d1..59bca3881 100644 --- a/src/network/C4Network2Client.h +++ b/src/network/C4Network2Client.h @@ -20,6 +20,7 @@ #include "network/C4Network2IO.h" #include "network/C4PacketBase.h" #include "network/C4Client.h" +#include "network/C4Network2Address.h" class C4Network2; class C4Network2IOConnection; @@ -40,47 +41,6 @@ enum C4Network2ClientStatus NCS_Remove // client is to be removed }; -class C4Network2Address -{ -public: - C4Network2Address() - : eProtocol(P_NONE) - { ZeroMem(&addr, sizeof(addr)); } - - C4Network2Address(C4NetIO::addr_t addr, C4Network2IOProtocol eProtocol) - : addr(addr), eProtocol(eProtocol) - { } - - C4Network2Address(const C4Network2Address &addr) - : addr(addr.getAddr()), eProtocol(addr.getProtocol()) - { } - - void operator = (const C4Network2Address &addr) - { SetAddr(addr.getAddr()); SetProtocol(addr.getProtocol()); } - - bool operator == (const C4Network2Address &addr) const; - -protected: - C4NetIO::addr_t addr; - C4Network2IOProtocol eProtocol; - -public: - const C4NetIO::addr_t &getAddr() const { return addr; } - in_addr getIPAddr() const { return addr.sin_addr; } - bool isIPNull() const { return !addr.sin_addr.s_addr; } - uint16_t getPort() const { return htons(addr.sin_port); } - C4Network2IOProtocol getProtocol() const { return eProtocol; } - - StdStrBuf toString() const; - - void SetAddr(C4NetIO::addr_t naddr) { addr = naddr; } - void SetIP(in_addr ip) { addr.sin_addr = ip; } - void SetPort(uint16_t iPort) { addr.sin_port = htons(iPort); } - void SetProtocol(C4Network2IOProtocol enProtocol) { eProtocol = enProtocol; } - - void CompileFunc(StdCompiler *pComp); -}; - class C4Network2Client { friend class C4Network2ClientList; @@ -244,4 +204,3 @@ public: }; #endif - diff --git a/src/network/C4Network2IO.cpp b/src/network/C4Network2IO.cpp index 1588ded46..08531cf4c 100644 --- a/src/network/C4Network2IO.cpp +++ b/src/network/C4Network2IO.cpp @@ -39,7 +39,7 @@ struct C4Network2IO::NetEvPacketData }; // compile options -#define C4NET2IO_DUMP_LEVEL 1 +#define C4NET2IO_DUMP_LEVEL 3 // *** C4Network2IO @@ -455,7 +455,7 @@ bool C4Network2IO::BroadcastMsg(const C4NetIOPacket &rPkt) // by both return fSuccess; } -bool C4Network2IO::Punch(C4NetIO::addr_t nPuncherAddr) +bool C4Network2IO::InitPuncher(C4NetIO::addr_t nPuncherAddr) { // UDP must be initialized if (!pNetIO_UDP) @@ -466,19 +466,30 @@ bool C4Network2IO::Punch(C4NetIO::addr_t nPuncherAddr) return pNetIO_UDP->Connect(PuncherAddr); } +void C4Network2IO::Punch(const C4NetIO::addr_t &punchee_addr) { + if (!pNetIO_UDP) + return; + C4PacketPing PktPeng; + dynamic_cast(pNetIO_UDP)->SendDirect(MkC4NetIOPacket(PID_Pong, PktPeng, punchee_addr)); +} + +void C4Network2IO::SendPuncherPacket(const C4NetpuncherPacket& p) { + if (!pNetIO_UDP || !PuncherAddr.sin_addr.s_addr) return; + pNetIO_UDP->Send(p.PackTo(PuncherAddr)); +} + // C4NetIO interface bool C4Network2IO::OnConn(const C4NetIO::addr_t &PeerAddr, const C4NetIO::addr_t &ConnectAddr, const C4NetIO::addr_t *pOwnAddr, C4NetIO *pNetIO) { - // puncher answer? We just make sure here a connection /can/ be established, so close it instantly. - if (pNetIO == pNetIO_UDP) - if (PuncherAddr.sin_addr.s_addr && AddrEqual(PuncherAddr, ConnectAddr)) - { - // got an address? - if (pOwnAddr) - OnPunch(*pOwnAddr); - // this is only a test connection - close it instantly - return false; - } + // puncher answer? + if (pNetIO == pNetIO_UDP && PuncherAddr.sin_addr.s_addr && AddrEqual(PuncherAddr, ConnectAddr)) + { + // got an address? + if (pOwnAddr) + OnPuncherConnect(*pOwnAddr); + return true; + } + #if(C4NET2IO_DUMP_LEVEL > 1) Application.InteractiveThread.ThreadLogS("OnConn: %s %s", C4TimeMilliseconds::Now().AsString().getData(), @@ -527,12 +538,11 @@ bool C4Network2IO::OnConn(const C4NetIO::addr_t &PeerAddr, const C4NetIO::addr_t void C4Network2IO::OnDisconn(const C4NetIO::addr_t &addr, C4NetIO *pNetIO, const char *szReason) { // punch? - if (pNetIO == pNetIO_UDP) - if (PuncherAddr.sin_addr.s_addr && AddrEqual(PuncherAddr, addr)) - { - ZeroMem(&PuncherAddr, sizeof(PuncherAddr)); - return; - } + if (pNetIO == pNetIO_UDP && PuncherAddr.sin_addr.s_addr && AddrEqual(PuncherAddr, addr)) + { + ZeroMem(&PuncherAddr, sizeof(PuncherAddr)); + return; + } #if(C4NET2IO_DUMP_LEVEL > 1) Application.InteractiveThread.ThreadLogS("OnDisconn: %s %s", C4TimeMilliseconds::Now().AsString().getData(), @@ -570,6 +580,11 @@ void C4Network2IO::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO) C4TimeMilliseconds::Now().AsString().getData(), rPacket.getStatus(), getNetIOName(pNetIO)); #endif + if (pNetIO == pNetIO_UDP && PuncherAddr.sin_addr.s_addr && AddrEqual(PuncherAddr, rPacket.getAddr())) + { + HandlePuncherPacket(rPacket); + return; + } if (!rPacket.getSize()) return; // find connection C4Network2IOConnection *pConn = GetConnection(rPacket.getAddr(), pNetIO); @@ -1120,6 +1135,17 @@ void C4Network2IO::HandleFwdReq(const C4PacketFwd &rFwd, C4Network2IOConnection } } +void C4Network2IO::HandlePuncherPacket(const C4NetIOPacket& rPacket) +{ + auto pkt = C4NetpuncherPacket::Construct(rPacket); + if (pkt && ::Network.HandlePuncherPacket(move(pkt))); + else + { + assert(pNetIO_UDP); + pNetIO_UDP->Close(rPacket.getAddr()); + } +} + bool C4Network2IO::Ping() { bool fSuccess = true; @@ -1237,7 +1263,7 @@ void C4Network2IO::SendConnPackets() } -void C4Network2IO::OnPunch(C4NetIO::addr_t addr) +void C4Network2IO::OnPuncherConnect(C4NetIO::addr_t addr) { // Sanity check assert (addr.sin_family == AF_INET); @@ -1250,8 +1276,8 @@ void C4Network2IO::OnPunch(C4NetIO::addr_t addr) // Add for local client C4Network2Client *pLocal = ::Network.Clients.GetLocal(); if (pLocal) - if (pLocal->AddAddr(C4Network2Address(addr, P_UDP), true)) - ::Network.InvalidateReference(); + pLocal->AddAddr(C4Network2Address(addr, P_UDP), true); + // Do not ::Network.InvalidateReference(); yet, we're expecting an ID from the netpuncher } // *** C4Network2IOConnection diff --git a/src/network/C4Network2IO.h b/src/network/C4Network2IO.h index 184febdcd..5cdc59bfb 100644 --- a/src/network/C4Network2IO.h +++ b/src/network/C4Network2IO.h @@ -19,6 +19,7 @@ #include "network/C4NetIO.h" #include "network/C4Client.h" #include "network/C4InteractiveThread.h" +#include "netpuncher/C4PuncherPacket.h" class C4Network2IOConnection; @@ -135,7 +136,9 @@ public: bool BroadcastMsg(const C4NetIOPacket &rPkt); // by both // punch - bool Punch(C4NetIO::addr_t PuncherAddr); // by main thread + bool InitPuncher(C4NetIO::addr_t PuncherAddr); // by main thread + void SendPuncherPacket(const C4NetpuncherPacket&); + void Punch(const C4NetIO::addr_t&); // sends a ping packet // stuff C4NetIO *getNetIO(C4Network2IOProtocol eProt); // by both @@ -185,6 +188,7 @@ protected: // packet handling (some are really handled here) void HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn); void HandleFwdReq(const class C4PacketFwd &rFwd, C4Network2IOConnection *pBy); + void HandlePuncherPacket(const C4NetIOPacket &rPacket); // misc bool Ping(); @@ -193,8 +197,7 @@ protected: void SendConnPackets(); // puncher - void OnPunch(C4NetIO::addr_t addr); - + void OnPuncherConnect(C4NetIO::addr_t addr); }; enum C4Network2IOConnStatus diff --git a/src/network/C4Network2Reference.cpp b/src/network/C4Network2Reference.cpp index 2bdfe58bb..0c3d30d01 100644 --- a/src/network/C4Network2Reference.cpp +++ b/src/network/C4Network2Reference.cpp @@ -30,7 +30,7 @@ C4Network2Reference::C4Network2Reference() : Icon(0), GameMode(), Time(0), Frame(0), StartTime(0), LeaguePerformance(0), JoinAllowed(true), ObservingAllowed(true), PasswordNeeded(false), OfficialServer(false), - IsEditor(false), iAddrCnt(0) + IsEditor(false), iAddrCnt(0), NetpuncherGameID(0) { } @@ -78,6 +78,8 @@ void C4Network2Reference::InitLocal() ObservingAllowed = ::Network.isObservingAllowed(); PasswordNeeded = ::Network.isPassworded(); IsEditor = !!::Application.isEditor; + NetpuncherGameID = ::Network.getNetpuncherGameID(); + NetpuncherAddr = ::Network.getNetpuncherAddr(); Game.Set(); // Addresses @@ -125,6 +127,8 @@ void C4Network2Reference::CompileFunc(StdCompiler *pComp) pComp->Value(mkNamingAdapt(Game.sEngineName, "Game", "None")); pComp->Value(mkNamingAdapt(mkArrayAdaptDM(Game.iVer,0),"Version" )); pComp->Value(mkNamingAdapt(OfficialServer, "OfficialServer", false)); + pComp->Value(mkNamingAdapt(NetpuncherGameID, "NetpuncherID", 0, false, false)); + pComp->Value(mkNamingAdapt(NetpuncherAddr, "NetpuncherAddr", "", false, false)); pComp->Value(Parameters); } diff --git a/src/network/C4Network2Reference.h b/src/network/C4Network2Reference.h index 78c96840d..f78abfea7 100644 --- a/src/network/C4Network2Reference.h +++ b/src/network/C4Network2Reference.h @@ -51,6 +51,8 @@ private: bool PasswordNeeded; bool OfficialServer; bool IsEditor; + C4NetpuncherID_t NetpuncherGameID; + StdCopyStrBuf NetpuncherAddr; // Engine information C4GameVersion Game; @@ -75,6 +77,8 @@ public: int32_t getStartTime() const { return StartTime; } StdStrBuf getGameGoalString() const; bool isEditor() const { return IsEditor; } + C4NetpuncherID_t getNetpuncherGameID() const { return NetpuncherGameID; } + StdStrBuf getNetpuncherAddr() const { return NetpuncherAddr; } void SetSourceIP(in_addr ip); diff --git a/src/network/C4Packet2.cpp b/src/network/C4Packet2.cpp index c799bf82f..b5134faff 100644 --- a/src/network/C4Packet2.cpp +++ b/src/network/C4Packet2.cpp @@ -101,7 +101,6 @@ const C4PktHandlingData PktHandlingData[] = { PID_ControlPkt, PC_Network, "Control Paket", false, false, PH_C4GameControlNetwork, PKT_UNPACK(C4PacketControlPkt) }, { PID_ExecSyncCtrl, PC_Network, "Execute Sync Control", false, false, PH_C4GameControlNetwork, PKT_UNPACK(C4PacketExecSyncCtrl)}, - // Control (Isn't send over network, handled only as part of a control list) { CID_ClientJoin, PC_Control, "Client Join", false, true, 0, PKT_UNPACK(C4ControlClientJoin) }, { CID_ClientUpdate, PC_Control, "Client Update", false, true, 0, PKT_UNPACK(C4ControlClientUpdate)}, diff --git a/src/network/C4PacketBase.h b/src/network/C4PacketBase.h index 52c540c0a..c421bee3e 100644 --- a/src/network/C4PacketBase.h +++ b/src/network/C4PacketBase.h @@ -169,6 +169,9 @@ enum C4PacketType CID_DebugRec = CID_First | 0x40, CID_MenuCommand = CID_First | 0x41, + + // Note: There are some more packet types in src/netpuncher/C4PuncherPacket.h + // They have been picked to be distinct from these for safety, not for necessary. }; // packet classes @@ -187,7 +190,7 @@ enum C4PacketHandlerID PH_C4Network2ClientList = 1 << 3, // client list class PH_C4Network2Players = 1 << 4, // player list class PH_C4Network2ResList = 1 << 5, // resource list class - PH_C4GameControlNetwork = 1 << 6 // network control class + PH_C4GameControlNetwork = 1 << 6, // network control class };