blob: 9d306bf14cdab890ce1e094d84694e6ed21def67 [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;
Sébastien Blin77331092024-05-17 10:58:23 -0400353 err = getnatpmprequesttimeout(&handle, &timeout);
354 int millis = (timeout.tv_sec * 1000) + (timeout.tv_usec / 1000);
355 // Note, getnatpmprequesttimeout can be negative if previous deadline is passed.
356 if (err != 0 || millis < 0)
357 millis = 50;
Andreas Traczyk8f7e1bd2024-04-04 16:26:58 -0400358
Adrien Béraud612b55b2023-05-29 10:42:04 -0400359 // Wait for data.
Andreas Traczyk8f7e1bd2024-04-04 16:26:58 -0400360 if (_poll(&fds, 1, millis) == -1) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400361 err = NATPMP_ERR_SOCKETERROR;
362 break;
363 }
364
365 // Read the data.
366 err = readnatpmpresponseorretry(&handle, &response);
367
368 if (err == NATPMP_TRYAGAIN) {
369 std::this_thread::sleep_for(std::chrono::milliseconds(TIMEOUT_BEFORE_READ_RETRY));
370 } else {
371 break;
372 }
373 }
374
375 return err;
376}
377
378int
379NatPmp::sendMappingRequest(const Mapping& mapping, uint32_t& lifetime)
380{
Adrien Béraud370257c2023-08-15 20:53:09 -0400381 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400382
383 int err = sendnewportmappingrequest(&natpmpHdl_,
384 mapping.getType() == PortType::UDP ? NATPMP_PROTOCOL_UDP
385 : NATPMP_PROTOCOL_TCP,
386 mapping.getInternalPort(),
387 mapping.getExternalPort(),
388 lifetime);
389
390 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400391 if (logger_) logger_->error("NAT-PMP: Send mapping request failed with error {} {:d}",
392 getNatPmpErrorStr(err),
393 errno);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400394 return err;
395 }
396
397 unsigned readRetriesCounter = 0;
398
399 while (readRetriesCounter++ < MAX_READ_RETRIES) {
400 // Read the response
401 natpmpresp_t response;
402 err = readResponse(natpmpHdl_, response);
403
404 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400405 if (logger_) logger_->warn("NAT-PMP: Read response on IGD {} failed with error {}",
406 igd_->toString(),
407 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400408 } else if (response.type != NATPMP_RESPTYPE_TCPPORTMAPPING
409 and response.type != NATPMP_RESPTYPE_UDPPORTMAPPING) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400410 if (logger_) logger_->error("NAT-PMP: Unexpected response type ({:d}) for mapping {} from IGD {}.",
411 response.type,
412 mapping.toString(),
413 igd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400414 // Try to read again.
415 continue;
416 }
417
418 lifetime = response.pnu.newportmapping.lifetime;
419 // Done.
420 break;
421 }
422
423 return err;
424}
425
426int
427NatPmp::addPortMapping(Mapping& mapping)
428{
429 auto const& igdIn = mapping.getIgd();
430 assert(igdIn);
431 assert(igdIn->getProtocol() == NatProtocolType::NAT_PMP);
432
433 if (not igdIn->isValid() or not validIgdInstance(igdIn)) {
434 mapping.setState(MappingState::FAILED);
435 return NATPMP_ERR_INVALIDARGS;
436 }
437
438 mapping.setInternalAddress(getHostAddress().toString());
439
440 uint32_t lifetime = MAPPING_ALLOCATION_LIFETIME;
441 int err = sendMappingRequest(mapping, lifetime);
442
443 if (err < 0) {
444 mapping.setState(MappingState::FAILED);
445 return err;
446 }
447
448 // Set the renewal time and update.
449 mapping.setRenewalTime(sys_clock::now() + std::chrono::seconds(lifetime * 4 / 5));
450 mapping.setState(MappingState::OPEN);
451
452 return 0;
453}
454
455void
456NatPmp::requestMappingRemove(const Mapping& mapping)
457{
Adrien Béraud370257c2023-08-15 20:53:09 -0400458 ioContext->dispatch([w = weak(), mapping] {
459 if (auto pmpThis = w.lock()) {
460 Mapping map {mapping};
461 pmpThis->removePortMapping(map);
462 }
463 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400464}
465
466void
467NatPmp::removePortMapping(Mapping& mapping)
468{
469 auto igdIn = mapping.getIgd();
470 assert(igdIn);
471 if (not igdIn->isValid()) {
472 return;
473 }
474
475 if (not validIgdInstance(igdIn)) {
476 return;
477 }
478
479 Mapping mapToRemove(mapping);
480
481 uint32_t lifetime = 0;
482 int err = sendMappingRequest(mapping, lifetime);
483
484 if (err < 0) {
485 // Nothing to do if the request fails, just log the error.
Adrien Béraud17765bd2023-08-22 21:02:38 -0400486 if (logger_) logger_->warn("NAT-PMP: Send remove request failed with error {}. Ignoring",
487 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400488 }
489
490 // Update and notify the listener.
491 mapToRemove.setState(MappingState::FAILED);
492 processMappingRemoved(std::move(mapToRemove));
493}
494
495void
496NatPmp::getIgdPublicAddress()
497{
Adrien Béraud370257c2023-08-15 20:53:09 -0400498 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400499
500 // Set the public address for this IGD if it does not
501 // have one already.
502 if (igd_->getPublicIp()) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400503 if (logger_) logger_->warn("NAT-PMP: IGD {} already have a public address ({})",
504 igd_->toString(),
505 igd_->getPublicIp().toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400506 return;
507 }
508 assert(igd_->getProtocol() == NatProtocolType::NAT_PMP);
509
510 int err = sendpublicaddressrequest(&natpmpHdl_);
511
512 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400513 if (logger_) logger_->error("NAT-PMP: send public address request on IGD {} failed with error: {}",
514 igd_->toString(),
515 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400516
517 if (isErrorFatal(err)) {
518 // Fatal error, increment the counter.
519 incrementErrorsCounter(igd_);
520 }
521 return;
522 }
523
524 natpmpresp_t response;
525 err = readResponse(natpmpHdl_, response);
526
527 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400528 if (logger_) logger_->warn("NAT-PMP: Read response on IGD {} failed - {}",
529 igd_->toString(),
530 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400531 return;
532 }
533
534 if (response.type != NATPMP_RESPTYPE_PUBLICADDRESS) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400535 if (logger_) logger_->error("NAT-PMP: Unexpected response type ({:d}) for public address request from IGD {}.",
536 response.type,
537 igd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400538 return;
539 }
540
541 IpAddr publicAddr(response.pnu.publicaddress.addr);
542
543 if (not publicAddr) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400544 if (logger_) logger_->error("NAT-PMP: IGD {} returned an invalid public address {}",
545 igd_->toString(),
546 publicAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400547 }
548
549 // Update.
550 igd_->setPublicIp(publicAddr);
551 igd_->setValid(true);
552
Adrien Béraud17765bd2023-08-22 21:02:38 -0400553 if (logger_) logger_->debug("NAT-PMP: Setting IGD {} public address to {}",
554 igd_->toString(),
555 igd_->getPublicIp().toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400556}
557
558void
559NatPmp::removeAllMappings()
560{
Adrien Béraud370257c2023-08-15 20:53:09 -0400561 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400562
Adrien Béraud17765bd2023-08-22 21:02:38 -0400563 if (logger_) logger_->warn("NAT-PMP: Send request to close all existing mappings to IGD {}",
564 igd_->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400565
566 int err = sendnewportmappingrequest(&natpmpHdl_, NATPMP_PROTOCOL_TCP, 0, 0, 0);
567 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400568 if (logger_) logger_->warn("NAT-PMP: Send close all TCP mappings request failed with error {}",
569 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400570 }
571 err = sendnewportmappingrequest(&natpmpHdl_, NATPMP_PROTOCOL_UDP, 0, 0, 0);
572 if (err < 0) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400573 if (logger_) logger_->warn("NAT-PMP: Send close all UDP mappings request failed with error {}",
574 getNatPmpErrorStr(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400575 }
576}
577
578const char*
579NatPmp::getNatPmpErrorStr(int errorCode) const
580{
581#ifdef ENABLE_STRNATPMPERR
582 return strnatpmperr(errorCode);
583#else
584 switch (errorCode) {
585 case NATPMP_ERR_INVALIDARGS:
586 return "INVALIDARGS";
587 break;
588 case NATPMP_ERR_SOCKETERROR:
589 return "SOCKETERROR";
590 break;
591 case NATPMP_ERR_CANNOTGETGATEWAY:
592 return "CANNOTGETGATEWAY";
593 break;
594 case NATPMP_ERR_CLOSEERR:
595 return "CLOSEERR";
596 break;
597 case NATPMP_ERR_RECVFROM:
598 return "RECVFROM";
599 break;
600 case NATPMP_ERR_NOPENDINGREQ:
601 return "NOPENDINGREQ";
602 break;
603 case NATPMP_ERR_NOGATEWAYSUPPORT:
604 return "NOGATEWAYSUPPORT";
605 break;
606 case NATPMP_ERR_CONNECTERR:
607 return "CONNECTERR";
608 break;
609 case NATPMP_ERR_WRONGPACKETSOURCE:
610 return "WRONGPACKETSOURCE";
611 break;
612 case NATPMP_ERR_SENDERR:
613 return "SENDERR";
614 break;
615 case NATPMP_ERR_FCNTLERROR:
616 return "FCNTLERROR";
617 break;
618 case NATPMP_ERR_GETTIMEOFDAYERR:
619 return "GETTIMEOFDAYERR";
620 break;
621 case NATPMP_ERR_UNSUPPORTEDVERSION:
622 return "UNSUPPORTEDVERSION";
623 break;
624 case NATPMP_ERR_UNSUPPORTEDOPCODE:
625 return "UNSUPPORTEDOPCODE";
626 break;
627 case NATPMP_ERR_UNDEFINEDERROR:
628 return "UNDEFINEDERROR";
629 break;
630 case NATPMP_ERR_NOTAUTHORIZED:
631 return "NOTAUTHORIZED";
632 break;
633 case NATPMP_ERR_NETWORKFAILURE:
634 return "NETWORKFAILURE";
635 break;
636 case NATPMP_ERR_OUTOFRESOURCES:
637 return "OUTOFRESOURCES";
638 break;
639 case NATPMP_TRYAGAIN:
640 return "TRYAGAIN";
641 break;
642 default:
643 return "UNKNOWNERR";
644 break;
645 }
646#endif
647}
648
649bool
650NatPmp::isErrorFatal(int error)
651{
652 switch (error) {
653 case NATPMP_ERR_INVALIDARGS:
654 case NATPMP_ERR_SOCKETERROR:
655 case NATPMP_ERR_CANNOTGETGATEWAY:
656 case NATPMP_ERR_CLOSEERR:
657 case NATPMP_ERR_RECVFROM:
658 case NATPMP_ERR_NOGATEWAYSUPPORT:
659 case NATPMP_ERR_CONNECTERR:
660 case NATPMP_ERR_SENDERR:
661 case NATPMP_ERR_UNDEFINEDERROR:
662 case NATPMP_ERR_UNSUPPORTEDVERSION:
663 case NATPMP_ERR_UNSUPPORTEDOPCODE:
664 case NATPMP_ERR_NOTAUTHORIZED:
665 case NATPMP_ERR_NETWORKFAILURE:
666 case NATPMP_ERR_OUTOFRESOURCES:
667 return true;
668 default:
669 return false;
670 }
671}
672
673bool
674NatPmp::validIgdInstance(const std::shared_ptr<IGD>& igdIn)
675{
676 if (igd_.get() != igdIn.get()) {
Adrien Béraud17765bd2023-08-22 21:02:38 -0400677 if (logger_) logger_->error("NAT-PMP: IGD ({}) does not match local instance ({})",
678 igdIn->toString(),
679 igd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400680 return false;
681 }
682
683 return true;
684}
685
686void
687NatPmp::processIgdUpdate(UpnpIgdEvent event)
688{
689 if (igd_->isValid()) {
690 // Remove all current mappings if any.
691 removeAllMappings();
692 }
693
694 if (observer_ == nullptr)
695 return;
696 // Process the response on the context thread.
Sébastien Blinf6baf4b2024-01-03 15:51:36 -0500697 ioContext->post([w = weak(), event] {
698 if (auto shared = w.lock()) {
699 if (!shared->shutdownComplete_) {
700 shared->observer_->onIgdUpdated(shared->igd_, event);
701 }
702 }
703 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400704}
705
706void
707NatPmp::processMappingAdded(const Mapping& map)
708{
709 if (observer_ == nullptr)
710 return;
711
712 // Process the response on the context thread.
Sébastien Blinf6baf4b2024-01-03 15:51:36 -0500713 ioContext->post([w=weak(), map] {
714 if (auto shared = w.lock()) {
715 if (!shared->shutdownComplete_) {
716 shared->observer_->onMappingAdded(shared->igd_, map);
717 }
718 }
719 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400720}
721
722void
723NatPmp::processMappingRequestFailed(const Mapping& map)
724{
725 if (observer_ == nullptr)
726 return;
727
728 // Process the response on the context thread.
Sébastien Blinf6baf4b2024-01-03 15:51:36 -0500729 ioContext->post([w=weak(), map] {
730 if (auto shared = w.lock()) {
731 if (!shared->shutdownComplete_) {
732 shared->observer_->onMappingRequestFailed(map);
733 }
734 }
735 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400736}
737
738void
739NatPmp::processMappingRenewed(const Mapping& map)
740{
741 if (observer_ == nullptr)
742 return;
743
744 // Process the response on the context thread.
Sébastien Blinf6baf4b2024-01-03 15:51:36 -0500745 ioContext->post([w=weak(), map] {
746 if (auto shared = w.lock()) {
747 if (!shared->shutdownComplete_) {
748 shared->observer_->onMappingRenewed(shared->igd_, map);
749 }
750 }
751 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400752}
753
754void
755NatPmp::processMappingRemoved(const Mapping& map)
756{
757 if (observer_ == nullptr)
758 return;
759
760 // Process the response on the context thread.
Sébastien Blinf6baf4b2024-01-03 15:51:36 -0500761 ioContext->post([w=weak(), map] {
762 if (auto shared = w.lock()) {
763 if (!shared->shutdownComplete_) {
764 shared->observer_->onMappingRemoved(shared->igd_, map);
765 }
766 }
767 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400768}
769
770} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -0400771} // namespace dhtnet
Adrien Béraud612b55b2023-05-29 10:42:04 -0400772
773#endif //-- #if HAVE_LIBNATPMP