blob: 3c17e09e07a22f0c45cd8b536a88c13a9fa92de0 [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{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400265 Mapping map(mapping);
266 assert(map.getIgd());
267 auto err = addPortMapping(map);
268 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400269 if (logger_) logger_->warn("NAT-PMP: Request for mapping {} on {} failed with error {:d}: {}",
270 map.toString(),
271 igd_->toString(),
272 err,
273 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400274
275 if (isErrorFatal(err)) {
276 // Fatal error, increment the counter.
277 incrementErrorsCounter(igd_);
278 }
279 // Notify the listener.
280 processMappingRequestFailed(std::move(map));
281 } else {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400282 if (logger_) logger_->debug("NAT-PMP: Request for mapping {:s} on {:s} succeeded",
283 map.toString(),
284 igd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400285 // Notify the listener.
286 processMappingAdded(std::move(map));
287 }
288}
289
290void
291NatPmp::requestMappingRenew(const Mapping& mapping)
292{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400293 Mapping map(mapping);
294 auto err = addPortMapping(map);
295 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400296 if (logger_) logger_->warn("NAT-PMP: Renewal request for mapping {} on {} failed with error {:d}: {}",
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400297 map.toString(),
298 igd_->toString(),
Adrien Béraud17765bd2023-08-22 21:02:38 -0400299 err,
300 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400301 // Notify the listener.
302 processMappingRequestFailed(std::move(map));
303
304 if (isErrorFatal(err)) {
305 // Fatal error, increment the counter.
306 incrementErrorsCounter(igd_);
307 }
308 } else {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400309 if (logger_) logger_->debug("NAT-PMP: Renewal request for mapping {} on {} succeeded",
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400310 map.toString(),
311 igd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400312 // Notify the listener.
313 processMappingRenewed(map);
314 }
315}
316
317int
318NatPmp::readResponse(natpmp_t& handle, natpmpresp_t& response)
319{
320 int err = 0;
321 unsigned readRetriesCounter = 0;
322
323 while (true) {
324 if (readRetriesCounter++ > MAX_READ_RETRIES) {
325 err = NATPMP_ERR_SOCKETERROR;
326 break;
327 }
328
Sébastien Blin4cf587e2024-03-08 16:00:18 -0500329 struct pollfd fds;
330 fds.fd = handle.s;
331 fds.events = POLLIN;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400332 struct timeval timeout;
Sébastien Blin77331092024-05-17 10:58:23 -0400333 err = getnatpmprequesttimeout(&handle, &timeout);
334 int millis = (timeout.tv_sec * 1000) + (timeout.tv_usec / 1000);
335 // Note, getnatpmprequesttimeout can be negative if previous deadline is passed.
336 if (err != 0 || millis < 0)
337 millis = 50;
Andreas Traczyk8f7e1bd2024-04-04 16:26:58 -0400338
Adrien Béraud612b55b2023-05-29 10:42:04 -0400339 // Wait for data.
Andreas Traczyk8f7e1bd2024-04-04 16:26:58 -0400340 if (_poll(&fds, 1, millis) == -1) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400341 err = NATPMP_ERR_SOCKETERROR;
342 break;
343 }
344
345 // Read the data.
346 err = readnatpmpresponseorretry(&handle, &response);
347
348 if (err == NATPMP_TRYAGAIN) {
349 std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT_BEFORE_READ_RETRY));
350 } else {
351 break;
352 }
353 }
354
355 return err;
356}
357
358int
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400359NatPmp::sendMappingRequest(Mapping& mapping, uint32_t& lifetime)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400360{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400361 int err = sendnewportmappingrequest(&natpmpHdl_,
362 mapping.getType() == PortType::UDP ? NATPMP_PROTOCOL_UDP
363 : NATPMP_PROTOCOL_TCP,
364 mapping.getInternalPort(),
365 mapping.getExternalPort(),
366 lifetime);
367
368 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400369 if (logger_) logger_->error("NAT-PMP: Send mapping request failed with error {} {:d}",
370 getNatPmpErrorStr(err),
371 errno);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400372 return err;
373 }
374
375 unsigned readRetriesCounter = 0;
376
377 while (readRetriesCounter++ < MAX_READ_RETRIES) {
378 // Read the response
379 natpmpresp_t response;
380 err = readResponse(natpmpHdl_, response);
381
382 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400383 if (logger_) logger_->warn("NAT-PMP: Read response on IGD {} failed with error {}",
384 igd_->toString(),
385 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400386 } else if (response.type != NATPMP_RESPTYPE_TCPPORTMAPPING
387 and response.type != NATPMP_RESPTYPE_UDPPORTMAPPING) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400388 if (logger_) logger_->error("NAT-PMP: Unexpected response type ({:d}) for mapping {} from IGD {}.",
389 response.type,
390 mapping.toString(),
391 igd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400392 // Try to read again.
393 continue;
394 }
395
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400396 uint16_t newExternalPort = response.pnu.newportmapping.mappedpublicport;
397 uint32_t newLifetime = response.pnu.newportmapping.lifetime;
398 if (lifetime > 0) {
399 // We requested the creation/renewal of a mapping and didn't get an error, so at this point
400 // newExternalPort and newLifetime should both be nonzero. However, that's not always the case
401 // in practice (presumably because some routers don't implement NAT-PMP correctly).
402 if (newExternalPort == 0 || newLifetime == 0) {
403 if (logger_) logger_->warn("NAT-PMP: mapping request returned without error but the response"
404 " contains invalid data (external port: {}, lifetime: {})",
405 newExternalPort,
406 newLifetime);
407 err = NATPMP_ERR_INVALIDARGS;
408 } else {
409 lifetime = newLifetime;
410 mapping.setExternalPort(newExternalPort);
411 }
412 }
413 break;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400414 }
415
416 return err;
417}
418
419int
420NatPmp::addPortMapping(Mapping& mapping)
421{
422 auto const& igdIn = mapping.getIgd();
423 assert(igdIn);
424 assert(igdIn->getProtocol() == NatProtocolType::NAT_PMP);
425
426 if (not igdIn->isValid() or not validIgdInstance(igdIn)) {
427 mapping.setState(MappingState::FAILED);
428 return NATPMP_ERR_INVALIDARGS;
429 }
430
431 mapping.setInternalAddress(getHostAddress().toString());
432
433 uint32_t lifetime = MAPPING_ALLOCATION_LIFETIME;
434 int err = sendMappingRequest(mapping, lifetime);
435
436 if (err < 0) {
437 mapping.setState(MappingState::FAILED);
438 return err;
439 }
440
441 // Set the renewal time and update.
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400442 mapping.setRenewalTime(sys_clock::now() + std::chrono::seconds(lifetime / 2));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400443 mapping.setState(MappingState::OPEN);
444
445 return 0;
446}
447
448void
449NatPmp::requestMappingRemove(const Mapping& mapping)
450{
Adrien Béraud370257c2023-08-15 20:53:09 -0400451 ioContext->dispatch([w = weak(), mapping] {
452 if (auto pmpThis = w.lock()) {
453 Mapping map {mapping};
454 pmpThis->removePortMapping(map);
455 }
456 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400457}
458
459void
460NatPmp::removePortMapping(Mapping& mapping)
461{
462 auto igdIn = mapping.getIgd();
463 assert(igdIn);
464 if (not igdIn->isValid()) {
465 return;
466 }
467
468 if (not validIgdInstance(igdIn)) {
469 return;
470 }
471
472 Mapping mapToRemove(mapping);
473
474 uint32_t lifetime = 0;
475 int err = sendMappingRequest(mapping, lifetime);
476
477 if (err < 0) {
478 // Nothing to do if the request fails, just log the error.
Adrien Béraud17765bd2023-08-22 21:02:38 -0400479 if (logger_) logger_->warn("NAT-PMP: Send remove request failed with error {}. Ignoring",
480 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400481 }
482
483 // Update and notify the listener.
484 mapToRemove.setState(MappingState::FAILED);
485 processMappingRemoved(std::move(mapToRemove));
486}
487
488void
489NatPmp::getIgdPublicAddress()
490{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400491 // Set the public address for this IGD if it does not
492 // have one already.
493 if (igd_->getPublicIp()) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400494 if (logger_) logger_->warn("NAT-PMP: IGD {} already have a public address ({})",
495 igd_->toString(),
496 igd_->getPublicIp().toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400497 return;
498 }
499 assert(igd_->getProtocol() == NatProtocolType::NAT_PMP);
500
501 int err = sendpublicaddressrequest(&natpmpHdl_);
502
503 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400504 if (logger_) logger_->error("NAT-PMP: send public address request on IGD {} failed with error: {}",
505 igd_->toString(),
506 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400507
508 if (isErrorFatal(err)) {
509 // Fatal error, increment the counter.
510 incrementErrorsCounter(igd_);
511 }
512 return;
513 }
514
515 natpmpresp_t response;
516 err = readResponse(natpmpHdl_, response);
517
518 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400519 if (logger_) logger_->warn("NAT-PMP: Read response on IGD {} failed - {}",
520 igd_->toString(),
521 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400522 return;
523 }
524
525 if (response.type != NATPMP_RESPTYPE_PUBLICADDRESS) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400526 if (logger_) logger_->error("NAT-PMP: Unexpected response type ({:d}) for public address request from IGD {}.",
527 response.type,
528 igd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400529 return;
530 }
531
532 IpAddr publicAddr(response.pnu.publicaddress.addr);
533
534 if (not publicAddr) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400535 if (logger_) logger_->error("NAT-PMP: IGD {} returned an invalid public address {}",
536 igd_->toString(),
537 publicAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400538 }
539
540 // Update.
541 igd_->setPublicIp(publicAddr);
542 igd_->setValid(true);
543
Adrien Béraud17765bd2023-08-22 21:02:38 -0400544 if (logger_) logger_->debug("NAT-PMP: Setting IGD {} public address to {}",
545 igd_->toString(),
546 igd_->getPublicIp().toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400547}
548
549void
550NatPmp::removeAllMappings()
551{
Adrien Béraud17765bd2023-08-22 21:02:38 -0400552 if (logger_) logger_->warn("NAT-PMP: Send request to close all existing mappings to IGD {}",
553 igd_->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400554
555 int err = sendnewportmappingrequest(&natpmpHdl_, NATPMP_PROTOCOL_TCP, 0, 0, 0);
556 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400557 if (logger_) logger_->warn("NAT-PMP: Send close all TCP mappings request failed with error {}",
558 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400559 }
560 err = sendnewportmappingrequest(&natpmpHdl_, NATPMP_PROTOCOL_UDP, 0, 0, 0);
561 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400562 if (logger_) logger_->warn("NAT-PMP: Send close all UDP mappings request failed with error {}",
563 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400564 }
565}
566
567const char*
568NatPmp::getNatPmpErrorStr(int errorCode) const
569{
570#ifdef ENABLE_STRNATPMPERR
571 return strnatpmperr(errorCode);
572#else
573 switch (errorCode) {
574 case NATPMP_ERR_INVALIDARGS:
575 return "INVALIDARGS";
576 break;
577 case NATPMP_ERR_SOCKETERROR:
578 return "SOCKETERROR";
579 break;
580 case NATPMP_ERR_CANNOTGETGATEWAY:
581 return "CANNOTGETGATEWAY";
582 break;
583 case NATPMP_ERR_CLOSEERR:
584 return "CLOSEERR";
585 break;
586 case NATPMP_ERR_RECVFROM:
587 return "RECVFROM";
588 break;
589 case NATPMP_ERR_NOPENDINGREQ:
590 return "NOPENDINGREQ";
591 break;
592 case NATPMP_ERR_NOGATEWAYSUPPORT:
593 return "NOGATEWAYSUPPORT";
594 break;
595 case NATPMP_ERR_CONNECTERR:
596 return "CONNECTERR";
597 break;
598 case NATPMP_ERR_WRONGPACKETSOURCE:
599 return "WRONGPACKETSOURCE";
600 break;
601 case NATPMP_ERR_SENDERR:
602 return "SENDERR";
603 break;
604 case NATPMP_ERR_FCNTLERROR:
605 return "FCNTLERROR";
606 break;
607 case NATPMP_ERR_GETTIMEOFDAYERR:
608 return "GETTIMEOFDAYERR";
609 break;
610 case NATPMP_ERR_UNSUPPORTEDVERSION:
611 return "UNSUPPORTEDVERSION";
612 break;
613 case NATPMP_ERR_UNSUPPORTEDOPCODE:
614 return "UNSUPPORTEDOPCODE";
615 break;
616 case NATPMP_ERR_UNDEFINEDERROR:
617 return "UNDEFINEDERROR";
618 break;
619 case NATPMP_ERR_NOTAUTHORIZED:
620 return "NOTAUTHORIZED";
621 break;
622 case NATPMP_ERR_NETWORKFAILURE:
623 return "NETWORKFAILURE";
624 break;
625 case NATPMP_ERR_OUTOFRESOURCES:
626 return "OUTOFRESOURCES";
627 break;
628 case NATPMP_TRYAGAIN:
629 return "TRYAGAIN";
630 break;
631 default:
632 return "UNKNOWNERR";
633 break;
634 }
635#endif
636}
637
638bool
639NatPmp::isErrorFatal(int error)
640{
641 switch (error) {
642 case NATPMP_ERR_INVALIDARGS:
643 case NATPMP_ERR_SOCKETERROR:
644 case NATPMP_ERR_CANNOTGETGATEWAY:
645 case NATPMP_ERR_CLOSEERR:
646 case NATPMP_ERR_RECVFROM:
647 case NATPMP_ERR_NOGATEWAYSUPPORT:
648 case NATPMP_ERR_CONNECTERR:
649 case NATPMP_ERR_SENDERR:
650 case NATPMP_ERR_UNDEFINEDERROR:
651 case NATPMP_ERR_UNSUPPORTEDVERSION:
652 case NATPMP_ERR_UNSUPPORTEDOPCODE:
653 case NATPMP_ERR_NOTAUTHORIZED:
654 case NATPMP_ERR_NETWORKFAILURE:
655 case NATPMP_ERR_OUTOFRESOURCES:
656 return true;
657 default:
658 return false;
659 }
660}
661
662bool
663NatPmp::validIgdInstance(const std::shared_ptr<IGD>& igdIn)
664{
665 if (igd_.get() != igdIn.get()) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400666 if (logger_) logger_->error("NAT-PMP: IGD ({}) does not match local instance ({})",
667 igdIn->toString(),
668 igd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400669 return false;
670 }
671
672 return true;
673}
674
675void
676NatPmp::processIgdUpdate(UpnpIgdEvent event)
677{
678 if (igd_->isValid()) {
679 // Remove all current mappings if any.
680 removeAllMappings();
681 }
682
683 if (observer_ == nullptr)
684 return;
685 // Process the response on the context thread.
Sébastien Blinf6baf4b2024-01-03 15:51:36 -0500686 ioContext->post([w = weak(), event] {
687 if (auto shared = w.lock()) {
688 if (!shared->shutdownComplete_) {
689 shared->observer_->onIgdUpdated(shared->igd_, event);
690 }
691 }
692 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400693}
694
695void
696NatPmp::processMappingAdded(const Mapping& map)
697{
698 if (observer_ == nullptr)
699 return;
700
701 // Process the response on the context thread.
Sébastien Blinf6baf4b2024-01-03 15:51:36 -0500702 ioContext->post([w=weak(), map] {
703 if (auto shared = w.lock()) {
704 if (!shared->shutdownComplete_) {
705 shared->observer_->onMappingAdded(shared->igd_, map);
706 }
707 }
708 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400709}
710
711void
712NatPmp::processMappingRequestFailed(const Mapping& map)
713{
714 if (observer_ == nullptr)
715 return;
716
717 // Process the response on the context thread.
Sébastien Blinf6baf4b2024-01-03 15:51:36 -0500718 ioContext->post([w=weak(), map] {
719 if (auto shared = w.lock()) {
720 if (!shared->shutdownComplete_) {
721 shared->observer_->onMappingRequestFailed(map);
722 }
723 }
724 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400725}
726
727void
728NatPmp::processMappingRenewed(const Mapping& map)
729{
730 if (observer_ == nullptr)
731 return;
732
733 // Process the response on the context thread.
Sébastien Blinf6baf4b2024-01-03 15:51:36 -0500734 ioContext->post([w=weak(), map] {
735 if (auto shared = w.lock()) {
736 if (!shared->shutdownComplete_) {
737 shared->observer_->onMappingRenewed(shared->igd_, map);
738 }
739 }
740 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400741}
742
743void
744NatPmp::processMappingRemoved(const Mapping& map)
745{
746 if (observer_ == nullptr)
747 return;
748
749 // Process the response on the context thread.
Sébastien Blinf6baf4b2024-01-03 15:51:36 -0500750 ioContext->post([w=weak(), map] {
751 if (auto shared = w.lock()) {
752 if (!shared->shutdownComplete_) {
753 shared->observer_->onMappingRemoved(shared->igd_, map);
754 }
755 }
756 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400757}
758
759} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -0400760} // namespace dhtnet
Adrien Béraud612b55b2023-05-29 10:42:04 -0400761
762#endif //-- #if HAVE_LIBNATPMP