OpenClonk
C4Network2UPnPLinux.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 /* Linux implementation of a UPnP port mapper (using miniupnpc) */
16 
17 #include "C4Include.h"
18 #include "network/C4Network2UPnP.h"
19 
20 #include "C4Version.h"
21 #include "game/C4Application.h"
22 
23 #include <miniupnpc.h>
24 #include <upnpcommands.h>
25 #include <upnperrors.h>
26 
27 #include <future>
28 
29 static const char *description = "OpenClonk";
30 
32 {
33 public:
35  virtual ~C4Network2UPnPP();
36 
37  void AddMapping(C4Network2IOProtocol protocol, uint16_t intport, uint16_t extport);
38  void ClearMappings();
39 
40 private:
41  void Init();
42 
43  struct PortMapping {
44  uint16_t external_port;
45  uint16_t internal_port;
46  std::string protocol;
47  };
48 
49  void AddPortMapping(const PortMapping& mapping);
50  void RemovePortMapping(const PortMapping& mapping);
51 
52  std::vector<PortMapping> added_mappings;
53 
54  // Synchronization using futures.
55  std::future<void> action;
56 
57  bool initialized = false;
58  char lanaddr[64];
59  UPNPDev *devlist = nullptr;
60  UPNPUrls upnp_urls;
61  IGDdatas igd_data;
62 };
63 
65 {
66  action = std::async(&C4Network2UPnPP::Init, this);
67 }
68 
69 void C4Network2UPnPP::Init()
70 {
71  int error, status;
72 
73 #if MINIUPNPC_API_VERSION == 10
74  // Distributed with Debian jessie.
75  if ((devlist = upnpDiscover(2000, nullptr, nullptr, 0, 0, &error)))
76 #else
77  if ((devlist = upnpDiscover(2000, nullptr, nullptr, UPNP_LOCAL_PORT_ANY, 0, 2, &error)))
78 #endif
79  {
80  if ((status = UPNP_GetValidIGD(devlist, &upnp_urls, &igd_data, lanaddr, sizeof(lanaddr))))
81  {
82  ThreadLogS("UPnP: Found IGD %s (status %d)", upnp_urls.controlURL, status);
83  initialized = true;
84  }
85  else
86  {
87  ThreadLog("UPnP: No IGD found.");
88  freeUPNPDevlist(devlist);
89  }
90  }
91  else
92  {
93  ThreadLog("UPnP: No UPnP device found on the network.");
94  }
95 
96 }
97 
99 {
100  ClearMappings();
101  action.wait();
102  ProcessEvents(); // necessary for logging
103  if (initialized)
104  {
105  FreeUPNPUrls(&upnp_urls);
106  freeUPNPDevlist(devlist);
107  }
108 }
109 
110 void C4Network2UPnPP::AddMapping(C4Network2IOProtocol protocol, uint16_t intport, uint16_t extport)
111 {
112  PortMapping mapping;
113  mapping.external_port = extport;
114  mapping.internal_port = intport;
115  mapping.protocol = (protocol == P_TCP ? "TCP" : "UDP");
116 
117  added_mappings.push_back(mapping);
118 
119  action = std::async([this, action = std::move(action), mapping]() {
120  action.wait();
121  AddPortMapping(mapping);
122  });
123 }
124 
126 {
127  action = std::async([this, action = std::move(action)]() {
128  action.wait();
129 
130  for (auto mapping : added_mappings)
131  RemovePortMapping(mapping);
132 
133  added_mappings.clear();
134  });
135 }
136 
137 void C4Network2UPnPP::AddPortMapping(const PortMapping& mapping)
138 {
139  if (!initialized) return; // Catches the case that UPnP initialization failed
140 
141  auto eport = std::to_string(mapping.external_port);
142  auto iport = std::to_string(mapping.internal_port);
143 
144  int r = UPNP_AddPortMapping(upnp_urls.controlURL, igd_data.first.servicetype,
145  eport.c_str(), iport.c_str(), lanaddr, description,
146  mapping.protocol.c_str(), 0, 0);
147  if (r == UPNPCOMMAND_SUCCESS)
148  ThreadLogS("UPnP: Added mapping %s %s -> %s:%s", mapping.protocol.c_str(), eport.c_str(), lanaddr, iport.c_str());
149  else
150  ThreadLog("UPnP: AddPortMapping failed with code %d (%s)", r, strupnperror(r));
151 }
152 
153 void C4Network2UPnPP::RemovePortMapping(const PortMapping& mapping)
154 {
155  if(!initialized) return; // Catches the case that UPnP initialization failed
156 
157  auto eport = std::to_string(mapping.external_port);
158 
159  int r = UPNP_DeletePortMapping(upnp_urls.controlURL, igd_data.first.servicetype,
160  eport.c_str(), mapping.protocol.c_str(), 0);
161  if (r == UPNPCOMMAND_SUCCESS)
162  ThreadLogS("UPnP: Removed mapping %s %s", mapping.protocol.c_str(), eport.c_str());
163  else
164  ThreadLog("UPnP: DeletePortMapping failed with code %d (%s)", r, strupnperror(r));
165 }
166 
168  p(new C4Network2UPnPP)
169 {
170 }
171 
173 {
174  delete p;
175 }
176 
177 void C4Network2UPnP::AddMapping(C4Network2IOProtocol protocol, uint16_t intport, uint16_t extport)
178 {
179  p->AddMapping(protocol, intport, extport);
180 }
181 
183 {
184  p->ClearMappings();
185 }
186 
C4Network2IOProtocol
Definition: C4Network2IO.h:30
@ P_TCP
Definition: C4Network2IO.h:31
bool ThreadLog(const char *szMessage,...) GNUC_FORMAT_ATTRIBUTE_O
bool ThreadLogS(const char *szMessage,...) GNUC_FORMAT_ATTRIBUTE_O
void AddMapping(enum C4Network2IOProtocol protocol, uint16_t intport, uint16_t extport)
void AddMapping(C4Network2IOProtocol protocol, uint16_t intport, uint16_t extport)