blob: 77a3f4ee3168dd30b183d760cef65dada9d428c8 [file] [log] [blame]
Adrien Béraud612b55b2023-05-29 10:42:04 -04001/*
2 * Copyright (C) 2004-2023 Savoir-faire Linux Inc.
3 *
Adrien Béraudcb753622023-07-17 22:32:49 -04004 * This program is free software: you can redistribute it and/or modify
Adrien Béraud612b55b2023-05-29 10:42:04 -04005 * it under the terms of the GNU General Public License as published by
Adrien Béraudcb753622023-07-17 22:32:49 -04006 * the Free Software Foundation, either version 3 of the License, or
Adrien Béraud612b55b2023-05-29 10:42:04 -04007 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Adrien Béraudcb753622023-07-17 22:32:49 -040011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Adrien Béraud612b55b2023-05-29 10:42:04 -040012 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
Adrien Béraudcb753622023-07-17 22:32:49 -040015 * along with this program. If not, see <https://www.gnu.org/licenses/>.
Adrien Béraud612b55b2023-05-29 10:42:04 -040016 */
Adrien Béraud612b55b2023-05-29 10:42:04 -040017#include "nat_pmp.h"
18
19#if HAVE_LIBNATPMP
Andreas Traczyk8f7e1bd2024-04-04 16:26:58 -040020#ifdef _WIN32
21// On Windows we assume WSAStartup is called during DHT initialization
22#include <winsock2.h>
23#include <ws2tcpip.h>
24#else
Sébastien Blinbaa52d92024-03-25 13:28:30 -040025#include <poll.h>
Andreas Traczyk8f7e1bd2024-04-04 16:26:58 -040026#endif
27
28#ifdef _WIN32
29#define _poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout)
30#else
31#define _poll(fds, nfds, timeout) poll(fds, nfds, timeout)
32#endif
Adrien Béraud612b55b2023-05-29 10:42:04 -040033
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040034namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040035namespace upnp {
36
Adrien Béraud370257c2023-08-15 20:53:09 -040037NatPmp::NatPmp(const std::shared_ptr<asio::io_context>& ctx, const std::shared_ptr<dht::log::Logger>& logger)
38 : UPnPProtocol(logger), ioContext(ctx), searchForIgdTimer_(*ctx)
Adrien Béraud612b55b2023-05-29 10:42:04 -040039{
Morteza Namvar5f639522023-07-04 17:08:58 -040040 // JAMI_DBG("NAT-PMP: Instance [%p] created", this);
Adrien Béraud370257c2023-08-15 20:53:09 -040041 ioContext->dispatch([this] {
Adrien Béraud612b55b2023-05-29 10:42:04 -040042 igd_ = std::make_shared<PMPIGD>();
43 });
44}
45
46NatPmp::~NatPmp()
47{
Morteza Namvar5f639522023-07-04 17:08:58 -040048 // JAMI_DBG("NAT-PMP: Instance [%p] destroyed", this);
Adrien Béraud612b55b2023-05-29 10:42:04 -040049}
50
51void
52NatPmp::initNatPmp()
53{
Adrien Béraud612b55b2023-05-29 10:42:04 -040054 initialized_ = false;
55
56 {
Adrien Béraud024c46f2024-03-02 23:53:18 -050057 std::lock_guard lock(natpmpMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -040058 hostAddress_ = ip_utils::getLocalAddr(AF_INET);
59 }
60
61 // Local address must be valid.
62 if (not getHostAddress() or getHostAddress().isLoopback()) {
Adrien Béraud17765bd2023-08-22 21:02:38 -040063 if (logger_) logger_->warn("NAT-PMP: Does not have a valid local address!");
Adrien Béraud612b55b2023-05-29 10:42:04 -040064 return;
65 }
66
67 assert(igd_);
68 if (igd_->isValid()) {
69 igd_->setValid(false);
70 processIgdUpdate(UpnpIgdEvent::REMOVED);
71 }
72
73 igd_->setLocalIp(IpAddr());
74 igd_->setPublicIp(IpAddr());
75 igd_->setUID("");
76
Adrien Béraud17765bd2023-08-22 21:02:38 -040077 if (logger_) logger_->debug("NAT-PMP: Trying to initialize IGD");
Adrien Béraud612b55b2023-05-29 10:42:04 -040078
79 int err = initnatpmp(&natpmpHdl_, 0, 0);
80
81 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -040082 if (logger_) logger_->warn("NAT-PMP: Initializing IGD using default gateway failed!");
Adrien Béraud612b55b2023-05-29 10:42:04 -040083 const auto& localGw = ip_utils::getLocalGateway();
84 if (not localGw) {
Adrien Béraud17765bd2023-08-22 21:02:38 -040085 if (logger_) logger_->warn("NAT-PMP: Couldn't find valid gateway on local host");
Adrien Béraud612b55b2023-05-29 10:42:04 -040086 err = NATPMP_ERR_CANNOTGETGATEWAY;
87 } else {
Adrien Béraud17765bd2023-08-22 21:02:38 -040088 if (logger_) logger_->warn("NAT-PMP: Trying to initialize using detected gateway {}",
89 localGw.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -040090 struct in_addr inaddr;
91 inet_pton(AF_INET, localGw.toString().c_str(), &inaddr);
92 err = initnatpmp(&natpmpHdl_, 1, inaddr.s_addr);
93 }
94 }
95
96 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -040097 if (logger_) logger_->error("NAT-PMP: Can't initialize libnatpmp -> {}", getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -040098 return;
99 }
100
101 char addrbuf[INET_ADDRSTRLEN];
102 inet_ntop(AF_INET, &natpmpHdl_.gateway, addrbuf, sizeof(addrbuf));
103 IpAddr igdAddr(addrbuf);
Adrien Béraud17765bd2023-08-22 21:02:38 -0400104 if (logger_) logger_->debug("NAT-PMP: Initialized on gateway {}", igdAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400105
106 // Set the local (gateway) address.
107 igd_->setLocalIp(igdAddr);
108 // NAT-PMP protocol does not have UID, but we will set generic
109 // one debugging purposes.
110 igd_->setUID("NAT-PMP Gateway");
111
112 // Search and set the public address.
113 getIgdPublicAddress();
114
115 // Update and notify.
116 if (igd_->isValid()) {
117 initialized_ = true;
118 processIgdUpdate(UpnpIgdEvent::ADDED);
119 };
120}
121
122void
123NatPmp::setObserver(UpnpMappingObserver* obs)
124{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400125 observer_ = obs;
126}
127
128void
129NatPmp::terminate(std::condition_variable& cv)
130{
Adrien Béraud7a82bee2023-08-30 10:26:45 -0400131 if (logger_) logger_->debug("NAT-PMP: Terminate instance {}", fmt::ptr(this));
132
Adrien Béraud612b55b2023-05-29 10:42:04 -0400133 initialized_ = false;
134 observer_ = nullptr;
135
Adrien Béraud024c46f2024-03-02 23:53:18 -0500136 std::lock_guard lock(natpmpMutex_);
Adrien Béraud7a82bee2023-08-30 10:26:45 -0400137 shutdownComplete_ = true;
138 cv.notify_one();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400139}
140
141void
142NatPmp::terminate()
143{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400144 std::condition_variable cv {};
145
Adrien Béraud370257c2023-08-15 20:53:09 -0400146 ioContext->dispatch([&] {
147 terminate(cv);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400148 });
149
Adrien Béraud024c46f2024-03-02 23:53:18 -0500150 std::unique_lock lk(natpmpMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400151 if (cv.wait_for(lk, std::chrono::seconds(10), [this] { return shutdownComplete_; })) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400152 if (logger_) logger_->debug("NAT-PMP: Shutdown completed");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400153 } else {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400154 if (logger_) logger_->error("NAT-PMP: Shutdown timed-out");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400155 }
156}
157
158const IpAddr
159NatPmp::getHostAddress() const
160{
Adrien Béraud024c46f2024-03-02 23:53:18 -0500161 std::lock_guard lock(natpmpMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400162 return hostAddress_;
163}
164
165void
166NatPmp::clearIgds()
167{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400168 bool do_close = false;
169
170 if (igd_) {
171 if (igd_->isValid()) {
172 do_close = true;
173 }
174 igd_->setValid(false);
175 }
176
177 initialized_ = false;
Adrien Béraud370257c2023-08-15 20:53:09 -0400178 searchForIgdTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400179
180 igdSearchCounter_ = 0;
181
182 if (do_close) {
183 closenatpmp(&natpmpHdl_);
184 memset(&natpmpHdl_, 0, sizeof(natpmpHdl_));
185 }
186}
187
188void
189NatPmp::searchForIgd()
190{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400191 if (not initialized_) {
192 initNatPmp();
193 }
194
195 // Schedule a retry in case init failed.
196 if (not initialized_) {
197 if (igdSearchCounter_++ < MAX_RESTART_SEARCH_RETRIES) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400198 if (logger_) logger_->debug("NAT-PMP: Start search for IGDs. Attempt {}", igdSearchCounter_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400199
200 // Cancel the current timer (if any) and re-schedule.
Adrien Béraud370257c2023-08-15 20:53:09 -0400201 searchForIgdTimer_.expires_after(NATPMP_SEARCH_RETRY_UNIT * igdSearchCounter_);
Sébastien Blind1e018c2023-09-01 09:15:12 -0400202 searchForIgdTimer_.async_wait([w=weak()](const asio::error_code& ec) {
203 if (!ec) {
204 if (auto shared = w.lock())
205 shared->searchForIgd();
206 }
Adrien Béraud370257c2023-08-15 20:53:09 -0400207 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400208 } else {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400209 if (logger_) logger_->warn("NAT-PMP: Setup failed after {} trials. NAT-PMP will be disabled!",
210 MAX_RESTART_SEARCH_RETRIES);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400211 }
212 }
213}
214
215std::list<std::shared_ptr<IGD>>
216NatPmp::getIgdList() const
217{
Adrien Béraud024c46f2024-03-02 23:53:18 -0500218 std::lock_guard lock(natpmpMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400219 std::list<std::shared_ptr<IGD>> igdList;
220 if (igd_->isValid())
221 igdList.emplace_back(igd_);
222 return igdList;
223}
224
225bool
226NatPmp::isReady() const
227{
228 if (observer_ == nullptr) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400229 if (logger_) logger_->error("NAT-PMP: the observer is not set!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400230 return false;
231 }
232
233 // Must at least have a valid local address.
234 if (not getHostAddress() or getHostAddress().isLoopback())
235 return false;
236
237 return igd_ and igd_->isValid();
238}
239
240void
241NatPmp::incrementErrorsCounter(const std::shared_ptr<IGD>& igdIn)
242{
243 if (not validIgdInstance(igdIn)) {
244 return;
245 }
246
247 if (not igd_->isValid()) {
248 // Already invalid. Nothing to do.
249 return;
250 }
251
252 if (not igd_->incrementErrorsCounter()) {
253 // Disable this IGD.
254 igd_->setValid(false);
255 // Notify the listener.
Adrien Béraud17765bd2023-08-22 21:02:38 -0400256 if (logger_) logger_->warn("NAT-PMP: No more valid IGD!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400257
258 processIgdUpdate(UpnpIgdEvent::INVALID_STATE);
259 }
260}
261
262void
263NatPmp::requestMappingAdd(const Mapping& mapping)
264{
265 // Process on nat-pmp thread.
Adrien Béraud370257c2023-08-15 20:53:09 -0400266 /*if (not isValidThread()) {
267 ioContext->post([w = weak(), mapping] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400268 if (auto pmpThis = w.lock()) {
269 pmpThis->requestMappingAdd(mapping);
270 }
271 });
272 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400273 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400274
275 Mapping map(mapping);
276 assert(map.getIgd());
277 auto err = addPortMapping(map);
278 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400279 if (logger_) logger_->warn("NAT-PMP: Request for mapping {} on {} failed with error {:d}: {}",
280 map.toString(),
281 igd_->toString(),
282 err,
283 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400284
285 if (isErrorFatal(err)) {
286 // Fatal error, increment the counter.
287 incrementErrorsCounter(igd_);
288 }
289 // Notify the listener.
290 processMappingRequestFailed(std::move(map));
291 } else {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400292 if (logger_) logger_->debug("NAT-PMP: Request for mapping {:s} on {:s} succeeded",
293 map.toString(),
294 igd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400295 // Notify the listener.
296 processMappingAdded(std::move(map));
297 }
298}
299
300void
301NatPmp::requestMappingRenew(const Mapping& mapping)
302{
303 // Process on nat-pmp thread.
Adrien Béraud370257c2023-08-15 20:53:09 -0400304 /*if (not isValidThread()) {
305 ioContext->post([w = weak(), mapping] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400306 if (auto pmpThis = w.lock()) {
307 pmpThis->requestMappingRenew(mapping);
308 }
309 });
310 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400311 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400312
313 Mapping map(mapping);
314 auto err = addPortMapping(map);
315 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400316 if (logger_) logger_->warn("NAT-PMP: Renewal request for mapping {} on {} failed with error {:d}: {}",
317 map.toString().c_str(),
318 igd_->toString().c_str(),
319 err,
320 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400321 // Notify the listener.
322 processMappingRequestFailed(std::move(map));
323
324 if (isErrorFatal(err)) {
325 // Fatal error, increment the counter.
326 incrementErrorsCounter(igd_);
327 }
328 } else {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400329 if (logger_) logger_->debug("NAT-PMP: Renewal request for mapping {} on {} succeeded",
330 map.toString().c_str(),
331 igd_->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400332 // Notify the listener.
333 processMappingRenewed(map);
334 }
335}
336
337int
338NatPmp::readResponse(natpmp_t& handle, natpmpresp_t& response)
339{
340 int err = 0;
341 unsigned readRetriesCounter = 0;
342
343 while (true) {
344 if (readRetriesCounter++ > MAX_READ_RETRIES) {
345 err = NATPMP_ERR_SOCKETERROR;
346 break;
347 }
348
Sébastien Blin4cf587e2024-03-08 16:00:18 -0500349 struct pollfd fds;
350 fds.fd = handle.s;
351 fds.events = POLLIN;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400352 struct timeval timeout;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400353 getnatpmprequesttimeout(&handle, &timeout);
Sébastien Blin4cf587e2024-03-08 16:00:18 -0500354 uint64_t millis = (timeout.tv_sec * (uint64_t)1000) + (timeout.tv_usec / 1000);
Andreas Traczyk8f7e1bd2024-04-04 16:26:58 -0400355
Adrien Béraud612b55b2023-05-29 10:42:04 -0400356 // Wait for data.
Andreas Traczyk8f7e1bd2024-04-04 16:26:58 -0400357 if (_poll(&fds, 1, millis) == -1) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400358 err = NATPMP_ERR_SOCKETERROR;
359 break;
360 }
361
362 // Read the data.
363 err = readnatpmpresponseorretry(&handle, &response);
364
365 if (err == NATPMP_TRYAGAIN) {
366 std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT_BEFORE_READ_RETRY));
367 } else {
368 break;
369 }
370 }
371
372 return err;
373}
374
375int
376NatPmp::sendMappingRequest(const Mapping& mapping, uint32_t& lifetime)
377{
Adrien Béraud370257c2023-08-15 20:53:09 -0400378 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400379
380 int err = sendnewportmappingrequest(&natpmpHdl_,
381 mapping.getType() == PortType::UDP ? NATPMP_PROTOCOL_UDP
382 : NATPMP_PROTOCOL_TCP,
383 mapping.getInternalPort(),
384 mapping.getExternalPort(),
385 lifetime);
386
387 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400388 if (logger_) logger_->error("NAT-PMP: Send mapping request failed with error {} {:d}",
389 getNatPmpErrorStr(err),
390 errno);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400391 return err;
392 }
393
394 unsigned readRetriesCounter = 0;
395
396 while (readRetriesCounter++ < MAX_READ_RETRIES) {
397 // Read the response
398 natpmpresp_t response;
399 err = readResponse(natpmpHdl_, response);
400
401 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400402 if (logger_) logger_->warn("NAT-PMP: Read response on IGD {} failed with error {}",
403 igd_->toString(),
404 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400405 } else if (response.type != NATPMP_RESPTYPE_TCPPORTMAPPING
406 and response.type != NATPMP_RESPTYPE_UDPPORTMAPPING) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400407 if (logger_) logger_->error("NAT-PMP: Unexpected response type ({:d}) for mapping {} from IGD {}.",
408 response.type,
409 mapping.toString(),
410 igd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400411 // Try to read again.
412 continue;
413 }
414
415 lifetime = response.pnu.newportmapping.lifetime;
416 // Done.
417 break;
418 }
419
420 return err;
421}
422
423int
424NatPmp::addPortMapping(Mapping& mapping)
425{
426 auto const& igdIn = mapping.getIgd();
427 assert(igdIn);
428 assert(igdIn->getProtocol() == NatProtocolType::NAT_PMP);
429
430 if (not igdIn->isValid() or not validIgdInstance(igdIn)) {
431 mapping.setState(MappingState::FAILED);
432 return NATPMP_ERR_INVALIDARGS;
433 }
434
435 mapping.setInternalAddress(getHostAddress().toString());
436
437 uint32_t lifetime = MAPPING_ALLOCATION_LIFETIME;
438 int err = sendMappingRequest(mapping, lifetime);
439
440 if (err < 0) {
441 mapping.setState(MappingState::FAILED);
442 return err;
443 }
444
445 // Set the renewal time and update.
446 mapping.setRenewalTime(sys_clock::now() + std::chrono::seconds(lifetime * 4 / 5));
447 mapping.setState(MappingState::OPEN);
448
449 return 0;
450}
451
452void
453NatPmp::requestMappingRemove(const Mapping& mapping)
454{
Adrien Béraud370257c2023-08-15 20:53:09 -0400455 ioContext->dispatch([w = weak(), mapping] {
456 if (auto pmpThis = w.lock()) {
457 Mapping map {mapping};
458 pmpThis->removePortMapping(map);
459 }
460 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400461}
462
463void
464NatPmp::removePortMapping(Mapping& mapping)
465{
466 auto igdIn = mapping.getIgd();
467 assert(igdIn);
468 if (not igdIn->isValid()) {
469 return;
470 }
471
472 if (not validIgdInstance(igdIn)) {
473 return;
474 }
475
476 Mapping mapToRemove(mapping);
477
478 uint32_t lifetime = 0;
479 int err = sendMappingRequest(mapping, lifetime);
480
481 if (err < 0) {
482 // Nothing to do if the request fails, just log the error.
Adrien Béraud17765bd2023-08-22 21:02:38 -0400483 if (logger_) logger_->warn("NAT-PMP: Send remove request failed with error {}. Ignoring",
484 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400485 }
486
487 // Update and notify the listener.
488 mapToRemove.setState(MappingState::FAILED);
489 processMappingRemoved(std::move(mapToRemove));
490}
491
492void
493NatPmp::getIgdPublicAddress()
494{
Adrien Béraud370257c2023-08-15 20:53:09 -0400495 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400496
497 // Set the public address for this IGD if it does not
498 // have one already.
499 if (igd_->getPublicIp()) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400500 if (logger_) logger_->warn("NAT-PMP: IGD {} already have a public address ({})",
501 igd_->toString(),
502 igd_->getPublicIp().toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400503 return;
504 }
505 assert(igd_->getProtocol() == NatProtocolType::NAT_PMP);
506
507 int err = sendpublicaddressrequest(&natpmpHdl_);
508
509 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400510 if (logger_) logger_->error("NAT-PMP: send public address request on IGD {} failed with error: {}",
511 igd_->toString(),
512 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400513
514 if (isErrorFatal(err)) {
515 // Fatal error, increment the counter.
516 incrementErrorsCounter(igd_);
517 }
518 return;
519 }
520
521 natpmpresp_t response;
522 err = readResponse(natpmpHdl_, response);
523
524 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400525 if (logger_) logger_->warn("NAT-PMP: Read response on IGD {} failed - {}",
526 igd_->toString(),
527 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400528 return;
529 }
530
531 if (response.type != NATPMP_RESPTYPE_PUBLICADDRESS) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400532 if (logger_) logger_->error("NAT-PMP: Unexpected response type ({:d}) for public address request from IGD {}.",
533 response.type,
534 igd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400535 return;
536 }
537
538 IpAddr publicAddr(response.pnu.publicaddress.addr);
539
540 if (not publicAddr) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400541 if (logger_) logger_->error("NAT-PMP: IGD {} returned an invalid public address {}",
542 igd_->toString(),
543 publicAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400544 }
545
546 // Update.
547 igd_->setPublicIp(publicAddr);
548 igd_->setValid(true);
549
Adrien Béraud17765bd2023-08-22 21:02:38 -0400550 if (logger_) logger_->debug("NAT-PMP: Setting IGD {} public address to {}",
551 igd_->toString(),
552 igd_->getPublicIp().toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400553}
554
555void
556NatPmp::removeAllMappings()
557{
Adrien Béraud370257c2023-08-15 20:53:09 -0400558 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400559
Adrien Béraud17765bd2023-08-22 21:02:38 -0400560 if (logger_) logger_->warn("NAT-PMP: Send request to close all existing mappings to IGD {}",
561 igd_->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400562
563 int err = sendnewportmappingrequest(&natpmpHdl_, NATPMP_PROTOCOL_TCP, 0, 0, 0);
564 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400565 if (logger_) logger_->warn("NAT-PMP: Send close all TCP mappings request failed with error {}",
566 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400567 }
568 err = sendnewportmappingrequest(&natpmpHdl_, NATPMP_PROTOCOL_UDP, 0, 0, 0);
569 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400570 if (logger_) logger_->warn("NAT-PMP: Send close all UDP mappings request failed with error {}",
571 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400572 }
573}
574
575const char*
576NatPmp::getNatPmpErrorStr(int errorCode) const
577{
578#ifdef ENABLE_STRNATPMPERR
579 return strnatpmperr(errorCode);
580#else
581 switch (errorCode) {
582 case NATPMP_ERR_INVALIDARGS:
583 return "INVALIDARGS";
584 break;
585 case NATPMP_ERR_SOCKETERROR:
586 return "SOCKETERROR";
587 break;
588 case NATPMP_ERR_CANNOTGETGATEWAY:
589 return "CANNOTGETGATEWAY";
590 break;
591 case NATPMP_ERR_CLOSEERR:
592 return "CLOSEERR";
593 break;
594 case NATPMP_ERR_RECVFROM:
595 return "RECVFROM";
596 break;
597 case NATPMP_ERR_NOPENDINGREQ:
598 return "NOPENDINGREQ";
599 break;
600 case NATPMP_ERR_NOGATEWAYSUPPORT:
601 return "NOGATEWAYSUPPORT";
602 break;
603 case NATPMP_ERR_CONNECTERR:
604 return "CONNECTERR";
605 break;
606 case NATPMP_ERR_WRONGPACKETSOURCE:
607 return "WRONGPACKETSOURCE";
608 break;
609 case NATPMP_ERR_SENDERR:
610 return "SENDERR";
611 break;
612 case NATPMP_ERR_FCNTLERROR:
613 return "FCNTLERROR";
614 break;
615 case NATPMP_ERR_GETTIMEOFDAYERR:
616 return "GETTIMEOFDAYERR";
617 break;
618 case NATPMP_ERR_UNSUPPORTEDVERSION:
619 return "UNSUPPORTEDVERSION";
620 break;
621 case NATPMP_ERR_UNSUPPORTEDOPCODE:
622 return "UNSUPPORTEDOPCODE";
623 break;
624 case NATPMP_ERR_UNDEFINEDERROR:
625 return "UNDEFINEDERROR";
626 break;
627 case NATPMP_ERR_NOTAUTHORIZED:
628 return "NOTAUTHORIZED";
629 break;
630 case NATPMP_ERR_NETWORKFAILURE:
631 return "NETWORKFAILURE";
632 break;
633 case NATPMP_ERR_OUTOFRESOURCES:
634 return "OUTOFRESOURCES";
635 break;
636 case NATPMP_TRYAGAIN:
637 return "TRYAGAIN";
638 break;
639 default:
640 return "UNKNOWNERR";
641 break;
642 }
643#endif
644}
645
646bool
647NatPmp::isErrorFatal(int error)
648{
649 switch (error) {
650 case NATPMP_ERR_INVALIDARGS:
651 case NATPMP_ERR_SOCKETERROR:
652 case NATPMP_ERR_CANNOTGETGATEWAY:
653 case NATPMP_ERR_CLOSEERR:
654 case NATPMP_ERR_RECVFROM:
655 case NATPMP_ERR_NOGATEWAYSUPPORT:
656 case NATPMP_ERR_CONNECTERR:
657 case NATPMP_ERR_SENDERR:
658 case NATPMP_ERR_UNDEFINEDERROR:
659 case NATPMP_ERR_UNSUPPORTEDVERSION:
660 case NATPMP_ERR_UNSUPPORTEDOPCODE:
661 case NATPMP_ERR_NOTAUTHORIZED:
662 case NATPMP_ERR_NETWORKFAILURE:
663 case NATPMP_ERR_OUTOFRESOURCES:
664 return true;
665 default:
666 return false;
667 }
668}
669
670bool
671NatPmp::validIgdInstance(const std::shared_ptr<IGD>& igdIn)
672{
673 if (igd_.get() != igdIn.get()) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400674 if (logger_) logger_->error("NAT-PMP: IGD ({}) does not match local instance ({})",
675 igdIn->toString(),
676 igd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400677 return false;
678 }
679
680 return true;
681}
682
683void
684NatPmp::processIgdUpdate(UpnpIgdEvent event)
685{
686 if (igd_->isValid()) {
687 // Remove all current mappings if any.
688 removeAllMappings();
689 }
690
691 if (observer_ == nullptr)
692 return;
693 // Process the response on the context thread.
Sébastien Blinf6baf4b2024-01-03 15:51:36 -0500694 ioContext->post([w = weak(), event] {
695 if (auto shared = w.lock()) {
696 if (!shared->shutdownComplete_) {
697 shared->observer_->onIgdUpdated(shared->igd_, event);
698 }
699 }
700 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400701}
702
703void
704NatPmp::processMappingAdded(const Mapping& map)
705{
706 if (observer_ == nullptr)
707 return;
708
709 // Process the response on the context thread.
Sébastien Blinf6baf4b2024-01-03 15:51:36 -0500710 ioContext->post([w=weak(), map] {
711 if (auto shared = w.lock()) {
712 if (!shared->shutdownComplete_) {
713 shared->observer_->onMappingAdded(shared->igd_, map);
714 }
715 }
716 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400717}
718
719void
720NatPmp::processMappingRequestFailed(const Mapping& map)
721{
722 if (observer_ == nullptr)
723 return;
724
725 // Process the response on the context thread.
Sébastien Blinf6baf4b2024-01-03 15:51:36 -0500726 ioContext->post([w=weak(), map] {
727 if (auto shared = w.lock()) {
728 if (!shared->shutdownComplete_) {
729 shared->observer_->onMappingRequestFailed(map);
730 }
731 }
732 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400733}
734
735void
736NatPmp::processMappingRenewed(const Mapping& map)
737{
738 if (observer_ == nullptr)
739 return;
740
741 // Process the response on the context thread.
Sébastien Blinf6baf4b2024-01-03 15:51:36 -0500742 ioContext->post([w=weak(), map] {
743 if (auto shared = w.lock()) {
744 if (!shared->shutdownComplete_) {
745 shared->observer_->onMappingRenewed(shared->igd_, map);
746 }
747 }
748 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400749}
750
751void
752NatPmp::processMappingRemoved(const Mapping& map)
753{
754 if (observer_ == nullptr)
755 return;
756
757 // Process the response on the context thread.
Sébastien Blinf6baf4b2024-01-03 15:51:36 -0500758 ioContext->post([w=weak(), map] {
759 if (auto shared = w.lock()) {
760 if (!shared->shutdownComplete_) {
761 shared->observer_->onMappingRemoved(shared->igd_, map);
762 }
763 }
764 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400765}
766
767} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -0400768} // namespace dhtnet
Adrien Béraud612b55b2023-05-29 10:42:04 -0400769
770#endif //-- #if HAVE_LIBNATPMP