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, 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));
66 return UPNP_GetValidIGD(devices, &urls, &data, local_address,
static_cast<int>(local_address_size));
73 if (private_port == 0 || public_port == 0)
75 const std::string port = std::to_string(public_port);
76#if defined(ASTEROIDS_NET_HAS_MINIUPNPC)
77 if (method == Method::Upnp) {
79 UPNPDev *devices = upnpDiscover(1000,
nullptr,
nullptr, 0, 0, 2, &error);
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);
88 if (devices !=
nullptr)
89 freeUPNPDevlist(devices);
93#if defined(ASTEROIDS_NET_HAS_NATPMP)
94 if (method == Method::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);
107 if (!parse_port(port_text, private_port)) {
109 status_message =
"Invalid port; automatic router mapping skipped.";
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";
117#if defined(ASTEROIDS_NET_HAS_MINIUPNPC)
119 UPNPDev *devices = upnpDiscover(1500,
nullptr,
nullptr, 0, 0, 2, &error);
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...";
136 freeUPNPDevlist(devices);
139 upnp_failure = std::string(
"UPnP mapping failed: ") + strupnperror(result);
141 }
else if (devices ==
nullptr) {
142 upnp_failure =
"no UPnP devices responded";
144 upnp_failure =
"no valid UPnP internet gateway";
146 if (devices !=
nullptr)
147 freeUPNPDevlist(devices);
150#if defined(ASTEROIDS_NET_HAS_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);
164 natpmp_failure = natpmp_error(response_result);
165 closenatpmp(&natpmp);
167 natpmp_failure = natpmp_error(init_result);
173 status_message = upnp_failure +
"; " + natpmp_failure +
". Forward UDP " + port +
" manually.";