win32: Add UPnP port mapping support

Nicolas Hake 2012-01-15 21:27:16 +01:00
parent 687c1f4920
commit 683800c33c
7 changed files with 248 additions and 3 deletions

View File

@ -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
############################################################################

View File

@ -30,6 +30,8 @@
#include <C4Game.h>
#include <C4GameControl.h>
#include "network/C4Network2Upnp.h"
#ifndef HAVE_WINSOCK
#include <sys/socket.h>
#include <netinet/in.h>
@ -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

View File

@ -59,6 +59,9 @@ protected:
// reference server
class C4Network2RefServer *pRefServer;
// UPnP port mapping manager
class C4Network2UPnP *UPnPMgr;
// local client core
C4ClientCore LCCore;
CStdCSec LCCoreCSec;

View File

@ -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 <boost/noncopyable.hpp>
class C4Network2UPnP : boost::noncopyable
{
struct C4Network2UPnPP *p;
public:
C4Network2UPnP();
~C4Network2UPnP();
void AddMapping(enum C4Network2IOProtocol protocol, uint16_t intport, uint16_t extport);
void ClearMappings();
};
#endif

View File

@ -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() {}

View File

@ -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 <boost/foreach.hpp>
#include <natupnp.h>
#include <upnp.h>
namespace
{
static BSTR PROTO_UDP = ::SysAllocString(L"UDP");
static BSTR PROTO_TCP = ::SysAllocString(L"TCP");
template<class T> inline void SafeRelease(T* &t)
{
if (t) t->Release();
t = NULL;
}
}
struct C4Network2UPnPP
{
bool MustReleaseCOM;
// NAT
IStaticPortMappingCollection *mappings;
std::set<IStaticPortMapping*> 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<void**>(&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);
}
}
}

View File

@ -19,6 +19,8 @@
#ifndef INC_C4windowswrapper
#define INC_C4windowswrapper
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#undef RGB
#undef GetRValue