OpenClonk
C4Network2UPnPWin32.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2012-2016, The OpenClonk Team and contributors
5  *
6  * Distributed under the terms of the ISC license; see accompanying file
7  * "COPYING" for details.
8  *
9  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
10  * See accompanying file "TRADEMARK" for details.
11  *
12  * To redistribute this file separately, substitute the full license texts
13  * for the above references.
14  */
15 /* Win32 implementation of a UPnP port mapper */
16 
17 #include "C4Include.h"
19 #include "network/C4Network2UPnP.h"
20 #include "C4Version.h"
21 
22 #ifdef __GNUC__
23 #pragma GCC diagnostic push
24 #pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
25 #pragma GCC diagnostic ignored "-Wredundant-decls"
26 #pragma GCC diagnostic ignored "-Wunknown-pragmas"
27 #endif
28 #include <natupnp.h>
29 #ifdef __GNUC__
30 #pragma GCC diagnostic pop
31 #endif
32 
33 #if defined(__MINGW32__) || defined(__MINGW64__)
34 // MinGW doesn't usually have these
35 extern "C" const CLSID CLSID_UPnPNAT = { 0xAE1E00AA, 0x3FD5, 0x403C, { 0x8A, 0x27, 0x2B, 0xBD, 0xC3, 0x0C, 0xD0, 0xE1 } };
36 extern "C" const IID IID_IUPnPNAT = { 0xB171C812, 0xCC76, 0x485A, { 0x94, 0xD8, 0xB6, 0xB3, 0xA2, 0x79, 0x4E, 0x99 } };
37 #endif
38 
39 namespace
40 {
41  static BSTR PROTO_UDP = ::SysAllocString(L"UDP");
42  static BSTR PROTO_TCP = ::SysAllocString(L"TCP");
43 
44  template<class T> inline void SafeRelease(T* &t)
45  {
46  if (t) t->Release();
47  t = nullptr;
48  }
49 }
50 
51 class C4Network2UPnPP
52 {
53 public:
54  bool MustReleaseCOM{false};
55 
56  // NAT
57  IStaticPortMappingCollection *mappings{nullptr};
58  std::set<IStaticPortMapping*> added_mappings;
59 
60  C4Network2UPnPP() = default;
61 
62  void AddMapping(C4Network2IOProtocol protocol, uint16_t intport, uint16_t extport);
63  void RemoveMapping(C4Network2IOProtocol protocol, uint16_t extport);
64  void ClearNatMappings();
65 };
66 
68  : p(new C4Network2UPnPP)
69 {
70  Log("UPnP init...");
71  // Make sure COM is available
72  if (FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)))
73  {
74  // Didn't work, don't do UPnP then
75  Log("UPnP fail (no COM).");
76  return;
77  }
78  p->MustReleaseCOM = true;
79 
80  // Get the NAT service
81  IUPnPNAT *nat = nullptr;
82  if (FAILED(CoCreateInstance(CLSID_UPnPNAT, nullptr, CLSCTX_INPROC_SERVER, IID_IUPnPNAT, reinterpret_cast<void**>(&nat))))
83  {
84  Log("UPnP fail (no service).");
85  return;
86  }
87 
88  // Fetch NAT mappings
89  for (int ctr = 0; ctr < 10; ++ctr)
90  {
91  // Usually it doesn't work on the first try, give Windows some time to query the IGD
92  if (SUCCEEDED(nat->get_StaticPortMappingCollection(&p->mappings)) && p->mappings)
93  {
94  LogF("UPnP: Got NAT port mapping table after %d tries", ctr+1);
95  break;
96  }
97  if (ctr == 2) Log(LoadResStr("IDS_MSG_UPNPHINT"));
98  Sleep(1000);
99  }
100 
101  SafeRelease(nat);
102 
103  if (!p->mappings) Log("UPnP fail (no mapping).");
104 }
105 
107 {
108  p->ClearNatMappings();
109  if (p->MustReleaseCOM)
110  {
111  // Decrement COM reference count
112  CoUninitialize();
113  }
114  delete p; p = nullptr;
115 }
116 
117 void C4Network2UPnP::AddMapping(C4Network2IOProtocol protocol, uint16_t intport, uint16_t extport)
118 {
119  p->AddMapping(protocol, intport, extport);
120 }
121 
123 {
124  p->ClearNatMappings();
125 }
126 
128 {
129  if (!mappings)
130  return;
131  for(IStaticPortMapping *mapping: added_mappings)
132  {
133  BSTR proto, client;
134  long intport, extport;
135  mapping->get_ExternalPort(&extport);
136  mapping->get_InternalPort(&intport);
137  mapping->get_InternalClient(&client);
138  mapping->get_Protocol(&proto);
139  if (SUCCEEDED(mappings->Remove(extport, proto)))
140  LogF("UPnP: Closed port %d->%s:%d (%s)", (int)extport, StdStrBuf(client).getData(), (int)intport, StdStrBuf(proto).getData());
141  ::SysFreeString(proto);
142  ::SysFreeString(client);
143  SafeRelease(mapping);
144  }
145  SafeRelease(mappings);
146 }
147 
148 void C4Network2UPnPP::AddMapping(C4Network2IOProtocol protocol, uint16_t intport, uint16_t extport)
149 {
150  if (mappings)
151  {
152  // Get (one of the) local host address(es)
153  char hostname[MAX_PATH];
154  hostent *host;
155  if (gethostname(hostname, MAX_PATH) == 0 && (host = gethostbyname(hostname)) != nullptr)
156  {
157  in_addr addr;
158  addr.s_addr = *(ULONG*)host->h_addr_list[0];
159 
160  BSTR description = ::SysAllocString(ADDL(C4ENGINECAPTION));
161  BSTR client = ::SysAllocString(GetWideChar(inet_ntoa(addr)));
162  IStaticPortMapping *mapping = nullptr;
163  if (SUCCEEDED(mappings->Add(extport, protocol == P_TCP ? PROTO_TCP : PROTO_UDP, intport, client, VARIANT_TRUE, description, &mapping)))
164  {
165  LogF("UPnP: Successfully opened port %d->%s:%d (%s)", extport, StdStrBuf(client).getData(), intport, protocol == P_TCP ? "TCP" : "UDP");
166  added_mappings.insert(mapping);
167  }
168  ::SysFreeString(description);
169  ::SysFreeString(client);
170  }
171  }
172 }
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
bool Log(const char *szMessage)
Definition: C4Log.cpp:204
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:262
C4Network2IOProtocol
Definition: C4Network2IO.h:30
@ P_TCP
Definition: C4Network2IO.h:31
#define ADDL(s)
StdStrBuf::wchar_t_holder GetWideChar(const char *utf8, bool double_null_terminate=false)
void AddMapping(enum C4Network2IOProtocol protocol, uint16_t intport, uint16_t extport)
void AddMapping(C4Network2IOProtocol protocol, uint16_t intport, uint16_t extport)
IStaticPortMappingCollection * mappings
std::set< IStaticPortMapping * > added_mappings
C4Network2UPnPP()=default
void RemoveMapping(C4Network2IOProtocol protocol, uint16_t extport)