MXVK Vulkan Framework 0.24.0
C++20 Vulkan rendering framework for practical 2D and 3D application development with SDL3.
Loading...
Searching...
No Matches
port_mapping.cpp
Go to the documentation of this file.
1#include "port_mapping.hpp"
2
3#include <charconv>
4#include <chrono>
5#include <thread>
6
7#if defined(ASTEROIDS_NET_HAS_MINIUPNPC)
8#include <miniupnpc/miniupnpc.h>
9#include <miniupnpc/upnpcommands.h>
10#include <miniupnpc/upnperrors.h>
11#endif
12
13#if defined(ASTEROIDS_NET_HAS_NATPMP)
14#include <natpmp.h>
15#endif
16
17namespace space {
18 namespace {
19 constexpr int MAPPING_LIFETIME_SECONDS = 7200;
20
21 bool parse_port(const std::string &text, std::uint16_t &port) {
22 unsigned int value = 0;
23 const auto result = std::from_chars(text.data(), text.data() + text.size(), value);
24 if (result.ec != std::errc{} || result.ptr != text.data() + text.size() || value == 0 || value > 65535)
25 return false;
26 port = static_cast<std::uint16_t>(value);
27 return true;
28 }
29
30#if defined(ASTEROIDS_NET_HAS_NATPMP)
31 int wait_for_natpmp(natpmp_t &natpmp, natpmpresp_t &response) {
32 for (int attempt = 0; attempt < 12; ++attempt) {
33 const int result = readnatpmpresponseorretry(&natpmp, &response);
34 if (result >= 0)
35 return result;
36 if (result != NATPMP_TRYAGAIN)
37 return result;
38 std::this_thread::sleep_for(std::chrono::milliseconds(100));
39 }
40 return NATPMP_TRYAGAIN;
41 }
42
43 std::string natpmp_error(const int error) {
44 switch (error) {
45 case NATPMP_ERR_NOGATEWAYSUPPORT:
46 return "gateway does not support NAT-PMP";
47 case NATPMP_ERR_NOTAUTHORIZED:
48 return "NAT-PMP request was not authorized";
49 case NATPMP_ERR_NETWORKFAILURE:
50 return "NAT-PMP gateway reported a network failure";
51 case NATPMP_ERR_OUTOFRESOURCES:
52 return "NAT-PMP gateway has no mapping resources";
53 case NATPMP_TRYAGAIN:
54 return "NAT-PMP gateway did not respond";
55 default:
56 return "NAT-PMP request failed (" + std::to_string(error) + ")";
57 }
58 }
59#endif
60
61#if defined(ASTEROIDS_NET_HAS_MINIUPNPC)
62 int get_valid_igd(UPNPDev *devices, UPNPUrls &urls, IGDdatas &data, char *local_address,
63 std::size_t local_address_size, [[maybe_unused]] char *external_address,
64 [[maybe_unused]] std::size_t external_address_size) {
65#if MINIUPNPC_API_VERSION >= 21
66 return UPNP_GetValidIGD(devices, &urls, &data, local_address, static_cast<int>(local_address_size), external_address,
67 static_cast<int>(external_address_size));
68#else
69 return UPNP_GetValidIGD(devices, &urls, &data, local_address, static_cast<int>(local_address_size));
70#endif
71 }
72#endif
73 } // namespace
74
76 if (private_port == 0 || public_port == 0)
77 return;
78 const std::string port = std::to_string(public_port);
79#if defined(ASTEROIDS_NET_HAS_MINIUPNPC)
80 if (method == Method::Upnp) {
81 int error = 0;
82 UPNPDev *devices = upnpDiscover(1000, nullptr, nullptr, 0, 0, 2, &error);
83 UPNPUrls urls{};
84 IGDdatas data{};
85 char local_address[64]{};
86 char external_address[64]{};
87 if (devices != nullptr &&
88 get_valid_igd(devices, urls, data, local_address, sizeof(local_address), external_address, sizeof(external_address)) > 0) {
89 UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "UDP", nullptr);
90 FreeUPNPUrls(&urls);
91 }
92 if (devices != nullptr)
93 freeUPNPDevlist(devices);
94 return;
95 }
96#endif
97#if defined(ASTEROIDS_NET_HAS_NATPMP)
98 if (method == Method::NatPmp) {
99 natpmp_t natpmp{};
100 if (initnatpmp(&natpmp, 0, 0) >= 0) {
101 sendnewportmappingrequest(&natpmp, NATPMP_PROTOCOL_UDP, private_port, public_port, 0);
102 natpmpresp_t response{};
103 wait_for_natpmp(natpmp, response);
104 closenatpmp(&natpmp);
105 }
106 }
107#endif
108 }
109
110 void PortMapping::open(const std::string &port_text) {
111 if (!parse_port(port_text, private_port)) {
112 private_port = 0;
113 status_message = "Invalid port; automatic router mapping skipped.";
114 return;
115 }
116 public_port = private_port;
117 const std::string port = std::to_string(private_port);
118 std::string upnp_failure = "UPnP support was not built";
119 std::string natpmp_failure = "NAT-PMP support was not built";
120
121#if defined(ASTEROIDS_NET_HAS_MINIUPNPC)
122 int error = 0;
123 UPNPDev *devices = upnpDiscover(1500, nullptr, nullptr, 0, 0, 2, &error);
124 UPNPUrls urls{};
125 IGDdatas data{};
126 char local_address[64]{};
127 char external_address[64]{};
128 const int igd_result = devices == nullptr
129 ? 0
130 : get_valid_igd(devices, urls, data, local_address, sizeof(local_address), external_address,
131 sizeof(external_address));
132 if (igd_result > 0) {
133 const int result = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(),
134 local_address, "asteroids-net", "UDP", nullptr,
135 std::to_string(MAPPING_LIFETIME_SECONDS).c_str());
136 if (result == UPNPCOMMAND_SUCCESS) {
137 if (external_address[0] == '\0')
138 UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, external_address);
139 method = Method::Upnp;
140 status_message = std::string("UPnP mapped UDP ") + port;
141 if (external_address[0] != '\0')
142 status_message += std::string(" at ") + external_address + ":" + port;
143 status_message += "; waiting for players...";
144 FreeUPNPUrls(&urls);
145 freeUPNPDevlist(devices);
146 return;
147 }
148 upnp_failure = std::string("UPnP mapping failed: ") + strupnperror(result);
149 FreeUPNPUrls(&urls);
150 } else if (devices == nullptr) {
151 upnp_failure = "no UPnP devices responded";
152 } else {
153 upnp_failure = "no valid UPnP internet gateway";
154 }
155 if (devices != nullptr)
156 freeUPNPDevlist(devices);
157#endif
158
159#if defined(ASTEROIDS_NET_HAS_NATPMP)
160 natpmp_t natpmp{};
161 const int init_result = initnatpmp(&natpmp, 0, 0);
162 if (init_result >= 0) {
163 const int sent = sendnewportmappingrequest(&natpmp, NATPMP_PROTOCOL_UDP, private_port, public_port, MAPPING_LIFETIME_SECONDS);
164 natpmpresp_t response{};
165 const int response_result = sent < 0 ? sent : wait_for_natpmp(natpmp, response);
166 if (response_result >= 0) {
167 public_port = response.pnu.newportmapping.mappedpublicport;
168 method = Method::NatPmp;
169 status_message = "NAT-PMP mapped UDP " + std::to_string(public_port) + "; waiting for players...";
170 closenatpmp(&natpmp);
171 return;
172 }
173 natpmp_failure = natpmp_error(response_result);
174 closenatpmp(&natpmp);
175 } else {
176 natpmp_failure = natpmp_error(init_result);
177 }
178#endif
179
180 private_port = 0;
181 public_port = 0;
182 status_message = upnp_failure + "; " + natpmp_failure + ". Forward UDP " + port + " manually.";
183 }
184
185 const std::string &PortMapping::status() const { return status_message; }
186
187} // namespace space
~PortMapping()
Removes the active mapping, if one was created.
void open(const std::string &port)
Attempts to expose a local UDP port through the router.
const std::string & status() const
Returns a human-readable description of the mapping result.
bool parse_port(const std::string &text, std::uint16_t &port)
Automatic UDP port mapping for hosted multiplayer sessions.