From 298feab441118bc279deaf5ebcff627c3ee32bbc Mon Sep 17 00:00:00 2001 From: Lukas Werling Date: Thu, 14 Dec 2017 14:34:38 +0100 Subject: [PATCH] Change netpuncher protocol to be more extensible - Each packet has a version field. - Clients connecting to the netpuncher always send a request packet. This allows the netpuncher to react differently depending on the client's version. - Encode packets as binary instead of ASCII. This allows adding fields while maintaining compatibility. --- src/netpuncher/C4PuncherPacket.cpp | 52 +++++++++++++++++++----------- src/netpuncher/C4PuncherPacket.h | 12 ++++++- src/netpuncher/main.cpp | 26 +++++++++++---- src/network/C4Network2.cpp | 11 +++++-- 4 files changed, 73 insertions(+), 28 deletions(-) diff --git a/src/netpuncher/C4PuncherPacket.cpp b/src/netpuncher/C4PuncherPacket.cpp index 562683976..28ec42166 100644 --- a/src/netpuncher/C4PuncherPacket.cpp +++ b/src/netpuncher/C4PuncherPacket.cpp @@ -18,19 +18,24 @@ #include "network/C4Network2Address.h" +static const char C4NetpuncherProtocolVersion = 1; +// Netpuncher packet header: (1 byte type "Status"), 1 byte version +static const size_t HeaderSize = 2, HeaderPSize = 1; + void C4NetpuncherID::CompileFunc(StdCompiler *pComp) { pComp->Value(mkNamingAdapt(v4, "IPv4", 0u)); pComp->Value(mkNamingAdapt(v6, "IPv6", 0u)); } std::unique_ptr C4NetpuncherPacket::Construct(const C4NetIOPacket& rpack) { - if (!rpack.getPData()) return nullptr; + if (!rpack.getPData() || *rpack.getPData() != C4NetpuncherProtocolVersion) 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)); + case PID_Puncher_IDReq: return uptr(new C4NetpuncherPacketIDReq(rpack)); default: return nullptr; } } @@ -42,36 +47,47 @@ C4NetIOPacket C4NetpuncherPacket::PackTo(const C4NetIO::addr_t& addr) const { 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)); + pkt.New(sizeof(type) + sizeof(C4NetpuncherProtocolVersion) + content.getSize()); + size_t offset = 0; + pkt.Write(&type, sizeof(type), offset); + offset += sizeof(type); + pkt.Write(&C4NetpuncherProtocolVersion, sizeof(C4NetpuncherProtocolVersion), offset); + offset += sizeof(C4NetpuncherProtocolVersion); + pkt.Write(content, offset); 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 = parse_addr.getAddr(); + if (rpack.getPSize() < HeaderPSize + 2 + 16) throw "invalid size"; + uint16_t port = *getBufPtr(rpack, HeaderSize); + addr.SetAddress(C4NetIO::addr_t::Any, port); + memcpy(&static_cast(&addr)->sin6_addr, getBufPtr(rpack, HeaderSize + sizeof(port)), 16); } StdBuf C4NetpuncherPacketCReq::PackInto() const { - C4Network2Address ser_addr; - ser_addr.SetAddr(addr); - ser_addr.SetProtocol(P_UDP); - return DecompileToBuf(ser_addr); + StdBuf buf; + auto sin6 = static_cast(addr.AsIPv6()); + auto port = addr.GetPort(); + buf.New(sizeof(port) + sizeof(sin6.sin6_addr)); + size_t offset = 0; + buf.Write(&port, sizeof(port), offset); + offset += sizeof(port); + buf.Write(&sin6.sin6_addr, sizeof(sin6.sin6_addr), offset); + static_assert(sizeof(sin6.sin6_addr) == 16, "expected sin6_addr to be 16 bytes"); + return buf; } template C4NetpuncherPacketID::C4NetpuncherPacketID(const C4NetIOPacket& rpack) { - std::istringstream iss(std::string(rpack.getPData(), rpack.getPSize())); - iss >> id; + if (rpack.getPSize() < HeaderPSize + sizeof(id)) throw "invalid size"; + id = *getBufPtr(rpack, HeaderSize); } template StdBuf C4NetpuncherPacketID::PackInto() const { - std::ostringstream oss; - oss << GetID(); - std::string s = oss.str(); - return StdCopyBuf(s.c_str(), s.length()); + StdBuf buf; + auto id = GetID(); + buf.New(sizeof(id)); + buf.Write(&id, sizeof(id)); + return buf; } diff --git a/src/netpuncher/C4PuncherPacket.h b/src/netpuncher/C4PuncherPacket.h index 265ceede7..85ea09643 100644 --- a/src/netpuncher/C4PuncherPacket.h +++ b/src/netpuncher/C4PuncherPacket.h @@ -21,9 +21,10 @@ #include "network/C4NetIO.h" enum C4NetpuncherPacketType { - PID_Puncher_AssID = 0x51, // Puncher announcing ID to client + PID_Puncher_AssID = 0x51, // Puncher announcing ID to host 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) + PID_Puncher_IDReq = 0x54, // Host requesting an ID // extend this with exchanging ICE parameters, some day? }; @@ -48,6 +49,15 @@ protected: typedef C4NetpuncherID::value CID; }; +class C4NetpuncherPacketIDReq : public C4NetpuncherPacket { +private: + StdBuf PackInto() const override { return StdBuf(); } +public: + C4NetpuncherPacketIDReq() = default; + C4NetpuncherPacketIDReq(const C4NetIOPacket& rpack) { } + C4NetpuncherPacketType GetType() const final { return PID_Puncher_IDReq; } +}; + template class C4NetpuncherPacketID : public C4NetpuncherPacket { private: diff --git a/src/netpuncher/main.cpp b/src/netpuncher/main.cpp index cd3871226..fdad86e7d 100644 --- a/src/netpuncher/main.cpp +++ b/src/netpuncher/main.cpp @@ -43,18 +43,32 @@ private: } while(peer_addrs.count(nid) && !nid); peer_ids.emplace(AddrPeer, nid); peer_addrs.emplace(nid, AddrPeer); - Send(C4NetpuncherPacketAssID(nid).PackTo(AddrPeer)); printf("Punched %s... #%u\n", AddrPeer.ToString().getData(), nid); return true; } void OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO) override { 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)); + if (!unpack) { Close(addr); return; } + switch (unpack->GetType()) { + case PID_Puncher_IDReq: { + auto it = peer_ids.find(addr); + if (it != peer_ids.end()) { + Send(C4NetpuncherPacketAssID(it->second).PackTo(addr)); + printf("Host: #%u\n", it->second); + } + break; + } + case PID_Puncher_SReq: { + 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)); + break; + } + default: + Close(addr); + } } void OnDisconn(const addr_t &AddrPeer, C4NetIO *pNetIO, const char *szReason) override { auto it = peer_ids.find(AddrPeer); diff --git a/src/network/C4Network2.cpp b/src/network/C4Network2.cpp index 762c4aa34..d7ca9c826 100644 --- a/src/network/C4Network2.cpp +++ b/src/network/C4Network2.cpp @@ -1032,10 +1032,15 @@ void C4Network2::OnPuncherConnect(C4NetIO::addr_t addr) } // Do not ::Network.InvalidateReference(); yet, we're expecting an ID from the netpuncher } - // Client connection: request packet from host. - if (!isHost()) + auto family = maybe_v4.GetFamily(); + if (isHost()) { - auto family = maybe_v4.GetFamily(); + // Host connection: request ID from netpuncher + NetIO.SendPuncherPacket(C4NetpuncherPacketIDReq(), family); + } + else + { + // Client connection: request packet from host. if (Status.getState() == GS_Init && getNetpuncherGameID(family)) NetIO.SendPuncherPacket(C4NetpuncherPacketSReq(getNetpuncherGameID(family)), family); }