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)
26 port =
static_cast<std::uint16_t
>(value);
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);
36 if (result != NATPMP_TRYAGAIN)
38 std::this_thread::sleep_for(std::chrono::milliseconds(100));
40 return NATPMP_TRYAGAIN;
43 std::string natpmp_error(
const int 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";
54 return "NAT-PMP gateway did not respond";
56 return "NAT-PMP request failed (" + std::to_string(error) +
")";
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));
69 return UPNP_GetValidIGD(devices, &urls, &data, local_address,
static_cast<int>(local_address_size));
76 if (private_port == 0 || public_port == 0)
78 const std::string port = std::to_string(public_port);
79#if defined(ASTEROIDS_NET_HAS_MINIUPNPC)
80 if (method == Method::Upnp) {
82 UPNPDev *devices = upnpDiscover(1000,
nullptr,
nullptr, 0, 0, 2, &error);
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);
92 if (devices !=
nullptr)
93 freeUPNPDevlist(devices);
97#if defined(ASTEROIDS_NET_HAS_NATPMP)
98 if (method == Method::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);
111 if (!parse_port(port_text, private_port)) {
113 status_message =
"Invalid port; automatic router mapping skipped.";
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";
121#if defined(ASTEROIDS_NET_HAS_MINIUPNPC)
123 UPNPDev *devices = upnpDiscover(1500,
nullptr,
nullptr, 0, 0, 2, &error);
126 char local_address[64]{};
127 char external_address[64]{};
128 const int igd_result = devices ==
nullptr
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...";
145 freeUPNPDevlist(devices);
148 upnp_failure = std::string(
"UPnP mapping failed: ") + strupnperror(result);
150 }
else if (devices ==
nullptr) {
151 upnp_failure =
"no UPnP devices responded";
153 upnp_failure =
"no valid UPnP internet gateway";
155 if (devices !=
nullptr)
156 freeUPNPDevlist(devices);
159#if defined(ASTEROIDS_NET_HAS_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);
173 natpmp_failure = natpmp_error(response_result);
174 closenatpmp(&natpmp);
176 natpmp_failure = natpmp_error(init_result);
182 status_message = upnp_failure +
"; " + natpmp_failure +
". Forward UDP " + port +
" manually.";