MXVK Vulkan Framework 0.35.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, std::size_t local_address_size, [[maybe_unused]] char *external_address, [[maybe_unused]] std::size_t external_address_size) {
63#if MINIUPNPC_API_VERSION >= 21
64 return UPNP_GetValidIGD(devices, &urls, &data, local_address, static_cast<int>(local_address_size), external_address, static_cast<int>(external_address_size));
65#else
66 return UPNP_GetValidIGD(devices, &urls, &data, local_address, static_cast<int>(local_address_size));
67#endif
68 }
69#endif
70 } // namespace
71
73 if (private_port == 0 || public_port == 0)
74 return;
75 const std::string port = std::to_string(public_port);
76#if defined(ASTEROIDS_NET_HAS_MINIUPNPC)
77 if (method == Method::Upnp) {
78 int error = 0;
79 UPNPDev *devices = upnpDiscover(1000, nullptr, nullptr, 0, 0, 2, &error);
80 UPNPUrls urls{};
81 IGDdatas data{};
82 char local_address[64]{};
83 char external_address[64]{};
84 if (devices != nullptr && get_valid_igd(devices, urls, data, local_address, sizeof(local_address), external_address, sizeof(external_address)) > 0) {
85 UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype, port.c_str(), "UDP", nullptr);
86 FreeUPNPUrls(&urls);
87 }
88 if (devices != nullptr)
89 freeUPNPDevlist(devices);
90 return;
91 }
92#endif
93#if defined(ASTEROIDS_NET_HAS_NATPMP)
94 if (method == Method::NatPmp) {
95 natpmp_t natpmp{};
96 if (initnatpmp(&natpmp, 0, 0) >= 0) {
97 sendnewportmappingrequest(&natpmp, NATPMP_PROTOCOL_UDP, private_port, public_port, 0);
98 natpmpresp_t response{};
99 wait_for_natpmp(natpmp, response);
100 closenatpmp(&natpmp);
101 }
102 }
103#endif
104 }
105
106 void PortMapping::open(const std::string &port_text) {
107 if (!parse_port(port_text, private_port)) {
108 private_port = 0;
109 status_message = "Invalid port; automatic router mapping skipped.";
110 return;
111 }
112 public_port = private_port;
113 const std::string port = std::to_string(private_port);
114 std::string upnp_failure = "UPnP support was not built";
115 std::string natpmp_failure = "NAT-PMP support was not built";
116
117#if defined(ASTEROIDS_NET_HAS_MINIUPNPC)
118 int error = 0;
119 UPNPDev *devices = upnpDiscover(1500, nullptr, nullptr, 0, 0, 2, &error);
120 UPNPUrls urls{};
121 IGDdatas data{};
122 char local_address[64]{};
123 char external_address[64]{};
124 const int igd_result = devices == nullptr ? 0 : get_valid_igd(devices, urls, data, local_address, sizeof(local_address), external_address, sizeof(external_address));
125 if (igd_result > 0) {
126 const int result = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, port.c_str(), port.c_str(), local_address, "asteroids-net", "UDP", nullptr, std::to_string(MAPPING_LIFETIME_SECONDS).c_str());
127 if (result == UPNPCOMMAND_SUCCESS) {
128 if (external_address[0] == '\0')
129 UPNP_GetExternalIPAddress(urls.controlURL, data.first.servicetype, external_address);
130 method = Method::Upnp;
131 status_message = std::string("UPnP mapped UDP ") + port;
132 if (external_address[0] != '\0')
133 status_message += std::string(" at ") + external_address + ":" + port;
134 status_message += "; waiting for players...";
135 FreeUPNPUrls(&urls);
136 freeUPNPDevlist(devices);
137 return;
138 }
139 upnp_failure = std::string("UPnP mapping failed: ") + strupnperror(result);
140 FreeUPNPUrls(&urls);
141 } else if (devices == nullptr) {
142 upnp_failure = "no UPnP devices responded";
143 } else {
144 upnp_failure = "no valid UPnP internet gateway";
145 }
146 if (devices != nullptr)
147 freeUPNPDevlist(devices);
148#endif
149
150#if defined(ASTEROIDS_NET_HAS_NATPMP)
151 natpmp_t natpmp{};
152 const int init_result = initnatpmp(&natpmp, 0, 0);
153 if (init_result >= 0) {
154 const int sent = sendnewportmappingrequest(&natpmp, NATPMP_PROTOCOL_UDP, private_port, public_port, MAPPING_LIFETIME_SECONDS);
155 natpmpresp_t response{};
156 const int response_result = sent < 0 ? sent : wait_for_natpmp(natpmp, response);
157 if (response_result >= 0) {
158 public_port = response.pnu.newportmapping.mappedpublicport;
159 method = Method::NatPmp;
160 status_message = "NAT-PMP mapped UDP " + std::to_string(public_port) + "; waiting for players...";
161 closenatpmp(&natpmp);
162 return;
163 }
164 natpmp_failure = natpmp_error(response_result);
165 closenatpmp(&natpmp);
166 } else {
167 natpmp_failure = natpmp_error(init_result);
168 }
169#endif
170
171 private_port = 0;
172 public_port = 0;
173 status_message = upnp_failure + "; " + natpmp_failure + ". Forward UDP " + port + " manually.";
174 }
175
176 const std::string &PortMapping::status() const { return status_message; }
177
178} // 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.