diff --git a/CMakeLists.txt b/CMakeLists.txt index 87166bf33..20383d93f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ # OpenClonk, http://www.openclonk.org # # Copyright (c) 2009-2011 Günther Brammer -# Copyright (c) 2009-2011 Nicolas Hake +# Copyright (c) 2009-2012 Nicolas Hake # Copyright (c) 2009 David Dormagen # Copyright (c) 2009-2011 Armin Burgmeier # Copyright (c) 2009-2010 Sven Eberhardt @@ -465,6 +465,7 @@ set(OC_CLONK_SOURCES src/network/C4Network2Res.h src/network/C4Network2Stats.cpp src/network/C4Network2Stats.h + src/network/C4Network2UPnP.h src/network/C4Packet2.cpp src/network/C4PacketBase.h src/platform/Bitmap256.cpp @@ -712,7 +713,7 @@ if(USE_CONSOLE) CHECK_INCLUDE_FILE_CXX(readline.h HAVE_READLINE_H) CHECK_INCLUDE_FILE_CXX(readline/readline.h HAVE_READLINE_READLINE_H) endif() - +CHECK_INCLUDE_FILE_CXX(natupnp.h HAVE_NATUPNP_H) # ck 09-09-20: The following headers require Xlib.h for things such as # 'Bool' and 'Window' to be defined. Unfortunately, this doesn't exist @@ -741,6 +742,16 @@ if(HAVE_ICONV) endif() endif() +if(HAVE_NATUPNP_H) + list(APPEND OC_SYSTEM_SOURCES + src/network/C4Network2UPnPWin32.cpp + ) +else() + list(APPEND OC_SYSTEM_SOURCES + src/network/C4Network2UPnPDummy.cpp + ) +endif() + ############################################################################ # Locate necessary libraries ############################################################################ diff --git a/src/network/C4Network2IO.cpp b/src/network/C4Network2IO.cpp index 194e80870..1367ac71d 100644 --- a/src/network/C4Network2IO.cpp +++ b/src/network/C4Network2IO.cpp @@ -30,6 +30,8 @@ #include #include +#include "network/C4Network2Upnp.h" + #ifndef HAVE_WINSOCK #include #include @@ -51,6 +53,7 @@ struct C4Network2IO::NetEvPacketData C4Network2IO::C4Network2IO() : pNetIO_TCP(NULL), pNetIO_UDP(NULL), pNetIODiscover(NULL), pRefServer(NULL), + UPnPMgr(NULL), pConnList(NULL), iNextConnID(0), fAllowConnect(false), @@ -84,6 +87,13 @@ bool C4Network2IO::Init(int16_t iPortTCP, int16_t iPortUDP, int16_t iPortDiscove Thread.SetCallback(Ev_Net_Disconn, this); Thread.SetCallback(Ev_Net_Packet, this); + // initialize UPnP manager + if (iPortTCP > 0 || iPortUDP > 0) + { + assert(!UPnPMgr); + UPnPMgr = new C4Network2UPnP; + } + // initialize net i/o classes: TCP first if (iPortTCP > 0) { @@ -103,6 +113,7 @@ bool C4Network2IO::Init(int16_t iPortTCP, int16_t iPortUDP, int16_t iPortDiscove { Thread.AddProc(pNetIO_TCP); pNetIO_TCP->SetCallback(this); + UPnPMgr->AddMapping(P_TCP, iPortTCP, iPortTCP); } } @@ -138,8 +149,8 @@ bool C4Network2IO::Init(int16_t iPortTCP, int16_t iPortUDP, int16_t iPortDiscove { Thread.AddProc(pNetIO_UDP); pNetIO_UDP->SetCallback(this); + UPnPMgr->AddMapping(P_UDP, iPortUDP, iPortUDP); } - } // no protocols? @@ -224,6 +235,7 @@ void C4Network2IO::Clear() // by main thread if (pNetIO_TCP) { Thread.RemoveProc(pNetIO_TCP); delete pNetIO_TCP; pNetIO_TCP = NULL; } if (pNetIO_UDP) { Thread.RemoveProc(pNetIO_UDP); delete pNetIO_UDP; pNetIO_UDP = NULL; } if (pRefServer) { Thread.RemoveProc(pRefServer); delete pRefServer; pRefServer = NULL; } + delete UPnPMgr; UPnPMgr = NULL; // remove auto-accepts ClearAutoAccept(); // reset flags diff --git a/src/network/C4Network2IO.h b/src/network/C4Network2IO.h index 817e06aa1..5b308db9d 100644 --- a/src/network/C4Network2IO.h +++ b/src/network/C4Network2IO.h @@ -59,6 +59,9 @@ protected: // reference server class C4Network2RefServer *pRefServer; + // UPnP port mapping manager + class C4Network2UPnP *UPnPMgr; + // local client core C4ClientCore LCCore; CStdCSec LCCoreCSec; diff --git a/src/network/C4Network2UPnP.h b/src/network/C4Network2UPnP.h new file mode 100644 index 000000000..6877d2c75 --- /dev/null +++ b/src/network/C4Network2UPnP.h @@ -0,0 +1,36 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2012 Nicolas Hake + * + * Portions might be copyrighted by other authors who have contributed + * to OpenClonk. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * See isc_license.txt for full license and disclaimer. + * + * "Clonk" is a registered trademark of Matthes Bender. + * See clonk_trademark_license.txt for full license. + */ +/* Interface to a UPnP port mapper */ + +#ifndef INC_C4Network2Upnp +#define INC_C4Network2Upnp + +#include "platform/StdScheduler.h" +#include + +class C4Network2UPnP : boost::noncopyable +{ + struct C4Network2UPnPP *p; +public: + C4Network2UPnP(); + ~C4Network2UPnP(); + + void AddMapping(enum C4Network2IOProtocol protocol, uint16_t intport, uint16_t extport); + void ClearMappings(); +}; + +#endif diff --git a/src/network/C4Network2UPnPDummy.cpp b/src/network/C4Network2UPnPDummy.cpp new file mode 100644 index 000000000..acf70a77c --- /dev/null +++ b/src/network/C4Network2UPnPDummy.cpp @@ -0,0 +1,25 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2012 Nicolas Hake + * + * Portions might be copyrighted by other authors who have contributed + * to OpenClonk. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * See isc_license.txt for full license and disclaimer. + * + * "Clonk" is a registered trademark of Matthes Bender. + * See clonk_trademark_license.txt for full license. + */ +/* Dummy implementation of a UPnP port mapper; does nothing */ + +#include "C4Include.h" +#include "network/C4Network2UPnP.h" + +C4Network2UPnP::C4Network2UPnP() {} +C4Network2UPnP::~C4Network2UPnP() {} +void C4Network2UPnP::AddMapping(C4Network2IOProtocol, uint16_t, uint16_t) {} +void C4Network2UPnP::ClearMappings() {} diff --git a/src/network/C4Network2UPnPWin32.cpp b/src/network/C4Network2UPnPWin32.cpp new file mode 100644 index 000000000..bab89d282 --- /dev/null +++ b/src/network/C4Network2UPnPWin32.cpp @@ -0,0 +1,156 @@ +/* + * OpenClonk, http://www.openclonk.org + * + * Copyright (c) 2012 Nicolas Hake + * + * Portions might be copyrighted by other authors who have contributed + * to OpenClonk. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * See isc_license.txt for full license and disclaimer. + * + * "Clonk" is a registered trademark of Matthes Bender. + * See clonk_trademark_license.txt for full license. + */ +/* Win32 implementation of a UPnP port mapper */ + +#include "C4Include.h" +#include "platform/C4windowswrapper.h" +#include "network/C4Network2IO.h" +#include "network/C4Network2UPnP.h" +#include "C4Version.h" + +#include +#include +#include + +namespace +{ + static BSTR PROTO_UDP = ::SysAllocString(L"UDP"); + static BSTR PROTO_TCP = ::SysAllocString(L"TCP"); + + template inline void SafeRelease(T* &t) + { + if (t) t->Release(); + t = NULL; + } +} + +struct C4Network2UPnPP +{ + bool MustReleaseCOM; + + // NAT + IStaticPortMappingCollection *mappings; + std::set added_mappings; + + C4Network2UPnPP() + : MustReleaseCOM(false), + mappings(NULL) + {} + + void AddMapping(C4Network2IOProtocol protocol, uint16_t intport, uint16_t extport); + void RemoveMapping(C4Network2IOProtocol protocol, uint16_t extport); + void ClearNatMappings(); +}; + +C4Network2UPnP::C4Network2UPnP() + : p(new C4Network2UPnPP) +{ + // Make sure COM is available + if (FAILED(CoInitializeEx(0, COINIT_APARTMENTTHREADED))) + { + // Didn't work, don't do UPnP then + return; + } + p->MustReleaseCOM = true; + + // Get the NAT service + IUPnPNAT *nat = NULL; + if (FAILED(CoCreateInstance(CLSID_UPnPNAT, NULL, CLSCTX_INPROC_SERVER, IID_IUPnPNAT, reinterpret_cast(&nat)))) + return; + + // Fetch NAT mappings + for (int ctr = 0; ctr < 10; ++ctr) + { + // Usually it doesn't work on the first try, give Windows some time to query the IGD + if (SUCCEEDED(nat->get_StaticPortMappingCollection(&p->mappings)) && p->mappings) + { + LogF("UPnP: Got NAT port mapping table after %d tries", ctr+1); + break; + } + Sleep(1000); + } + + SafeRelease(nat); +} + +C4Network2UPnP::~C4Network2UPnP() +{ + p->ClearNatMappings(); + if (p->MustReleaseCOM) + { + // Decrement COM reference count + CoUninitialize(); + } + delete p; p = NULL; +} + +void C4Network2UPnP::AddMapping(C4Network2IOProtocol protocol, uint16_t intport, uint16_t extport) +{ + p->AddMapping(protocol, intport, extport); +} + +void C4Network2UPnP::ClearMappings() +{ + p->ClearNatMappings(); +} + +void C4Network2UPnPP::ClearNatMappings() +{ + if (!mappings) + return; + BOOST_FOREACH(IStaticPortMapping *mapping, added_mappings) + { + BSTR proto, client; + long intport, extport; + mapping->get_ExternalPort(&extport); + mapping->get_InternalPort(&intport); + mapping->get_InternalClient(&client); + mapping->get_Protocol(&proto); + if (SUCCEEDED(mappings->Remove(extport, proto))) + LogF("UPnP: Closed port %d->%s:%d (%s)", extport, StdStrBuf(client).getData(), intport, StdStrBuf(proto).getData()); + ::SysFreeString(proto); + ::SysFreeString(client); + SafeRelease(mapping); + } + SafeRelease(mappings); +} + +void C4Network2UPnPP::AddMapping(C4Network2IOProtocol protocol, uint16_t intport, uint16_t extport) +{ + if (mappings) + { + // Get (one of the) local host address(es) + char hostname[MAX_PATH]; + hostent *host; + if (gethostname(hostname, MAX_PATH) == 0 && (host = gethostbyname(hostname)) != NULL) + { + in_addr addr; + addr.s_addr = *(ULONG*)host->h_addr_list[0]; + + BSTR description = ::SysAllocString(ADDL(C4ENGINECAPTION)); + BSTR client = ::SysAllocString(GetWideChar(inet_ntoa(addr))); + IStaticPortMapping *mapping = NULL; + if (SUCCEEDED(mappings->Add(extport, protocol == P_TCP ? PROTO_TCP : PROTO_UDP, intport, client, VARIANT_TRUE, description, &mapping))) + { + LogF("UPnP: Successfully opened port %d->%s:%d (%s)", extport, StdStrBuf(client).getData(), intport, protocol == P_TCP ? "TCP" : "UDP"); + added_mappings.insert(mapping); + } + ::SysFreeString(description); + ::SysFreeString(client); + } + } +} diff --git a/src/platform/C4windowswrapper.h b/src/platform/C4windowswrapper.h index 963609375..92e179db9 100644 --- a/src/platform/C4windowswrapper.h +++ b/src/platform/C4windowswrapper.h @@ -19,6 +19,8 @@ #ifndef INC_C4windowswrapper #define INC_C4windowswrapper +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX #include #undef RGB #undef GetRValue