blob: 11112b7e671abdfd4c9d92566f22c61bf660312b [file] [log] [blame]
Adrien Béraud612b55b2023-05-29 10:42:04 -04001/*
2 * Copyright (C) 2004-2023 Savoir-faire Linux Inc.
3 *
4 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
5 * Author: Eden Abitbol <eden.abitbol@savoirfairelinux.com>
6 * Author: Mohamed Chibani <mohamed.chibani@savoirfairelinux.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22
Morteza Namvar5f639522023-07-04 17:08:58 -040023#include "upnp/upnp_context.h"
24#include <asio/steady_timer.hpp>
Adrien Béraud612b55b2023-05-29 10:42:04 -040025
26namespace jami {
27namespace upnp {
28
29constexpr static auto MAP_UPDATE_INTERVAL = std::chrono::seconds(30);
30constexpr static int MAX_REQUEST_RETRIES = 20;
31constexpr static int MAX_REQUEST_REMOVE_COUNT = 5;
32
33constexpr static uint16_t UPNP_TCP_PORT_MIN {10000};
34constexpr static uint16_t UPNP_TCP_PORT_MAX {UPNP_TCP_PORT_MIN + 5000};
35constexpr static uint16_t UPNP_UDP_PORT_MIN {20000};
36constexpr static uint16_t UPNP_UDP_PORT_MAX {UPNP_UDP_PORT_MIN + 5000};
37
38UPnPContext::UPnPContext()
39{
Morteza Namvar5f639522023-07-04 17:08:58 -040040 // JAMI_DBG("Creating UPnPContext instance [%p]", this);
Adrien Béraud612b55b2023-05-29 10:42:04 -040041
42 // Set port ranges
43 portRange_.emplace(PortType::TCP, std::make_pair(UPNP_TCP_PORT_MIN, UPNP_TCP_PORT_MAX));
44 portRange_.emplace(PortType::UDP, std::make_pair(UPNP_UDP_PORT_MIN, UPNP_UDP_PORT_MAX));
45
46 if (not isValidThread()) {
47 runOnUpnpContextQueue([this] { init(); });
48 return;
49 }
50}
51
52std::shared_ptr<UPnPContext>
53UPnPContext::getUPnPContext()
54{
55 // This is the unique shared instance (singleton) of UPnPContext class.
56 static auto context = std::make_shared<UPnPContext>();
57 return context;
58}
59
60void
61UPnPContext::shutdown(std::condition_variable& cv)
62{
Morteza Namvar5f639522023-07-04 17:08:58 -040063 // JAMI_DBG("Shutdown UPnPContext instance [%p]", this);
Adrien Béraud612b55b2023-05-29 10:42:04 -040064
65 stopUpnp(true);
66
67 for (auto const& [_, proto] : protocolList_) {
68 proto->terminate();
69 }
70
71 {
72 std::lock_guard<std::mutex> lock(mappingMutex_);
73 mappingList_->clear();
74 if (mappingListUpdateTimer_)
75 mappingListUpdateTimer_->cancel();
76 controllerList_.clear();
77 protocolList_.clear();
78 shutdownComplete_ = true;
79 cv.notify_one();
80 }
81}
82
83void
84UPnPContext::shutdown()
85{
86 std::unique_lock<std::mutex> lk(mappingMutex_);
87 std::condition_variable cv;
88
89 runOnUpnpContextQueue([&, this] { shutdown(cv); });
90
Morteza Namvar5f639522023-07-04 17:08:58 -040091 // JAMI_DBG("Waiting for shutdown ...");
Adrien Béraud612b55b2023-05-29 10:42:04 -040092
93 if (cv.wait_for(lk, std::chrono::seconds(30), [this] { return shutdownComplete_; })) {
Morteza Namvar5f639522023-07-04 17:08:58 -040094 // JAMI_DBG("Shutdown completed");
Adrien Béraud612b55b2023-05-29 10:42:04 -040095 } else {
Morteza Namvar5f639522023-07-04 17:08:58 -040096 // JAMI_ERR("Shutdown timed-out");
Adrien Béraud612b55b2023-05-29 10:42:04 -040097 }
98}
99
100UPnPContext::~UPnPContext()
101{
Morteza Namvar5f639522023-07-04 17:08:58 -0400102 // JAMI_DBG("UPnPContext instance [%p] destroyed", this);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400103}
104
105void
106UPnPContext::init()
107{
108 threadId_ = getCurrentThread();
109 CHECK_VALID_THREAD();
110
111#if HAVE_LIBNATPMP
112 auto natPmp = std::make_shared<NatPmp>();
113 natPmp->setObserver(this);
114 protocolList_.emplace(NatProtocolType::NAT_PMP, std::move(natPmp));
115#endif
116
117#if HAVE_LIBUPNP
118 auto pupnp = std::make_shared<PUPnP>();
119 pupnp->setObserver(this);
120 protocolList_.emplace(NatProtocolType::PUPNP, std::move(pupnp));
121#endif
122}
123
124void
125UPnPContext::startUpnp()
126{
127 assert(not controllerList_.empty());
128
129 CHECK_VALID_THREAD();
130
Morteza Namvar5f639522023-07-04 17:08:58 -0400131 // JAMI_DBG("Starting UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400132
133 // Request a new IGD search.
134 for (auto const& [_, protocol] : protocolList_) {
135 protocol->searchForIgd();
136 }
137
138 started_ = true;
139}
140
141void
142UPnPContext::stopUpnp(bool forceRelease)
143{
144 if (not isValidThread()) {
145 runOnUpnpContextQueue([this, forceRelease] { stopUpnp(forceRelease); });
146 return;
147 }
148
Morteza Namvar5f639522023-07-04 17:08:58 -0400149 // JAMI_DBG("Stopping UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400150
151 // Clear all current mappings if any.
152
153 // Use a temporary list to avoid processing the mapping
154 // list while holding the lock.
155 std::list<Mapping::sharedPtr_t> toRemoveList;
156 {
157 std::lock_guard<std::mutex> lock(mappingMutex_);
158
159 PortType types[2] {PortType::TCP, PortType::UDP};
160 for (auto& type : types) {
161 auto& mappingList = getMappingList(type);
162 for (auto const& [_, map] : mappingList) {
163 toRemoveList.emplace_back(map);
164 }
165 }
166 // Invalidate the current IGDs.
167 preferredIgd_.reset();
168 validIgdList_.clear();
169 }
170 for (auto const& map : toRemoveList) {
171 requestRemoveMapping(map);
172
173 // Notify is not needed in updateMappingState when
174 // shutting down (hence set it to false). NotifyCallback
175 // would trigger a new SIP registration and create a
176 // false registered state upon program close.
177 // It's handled by upper layers.
178
179 updateMappingState(map, MappingState::FAILED, false);
180 // We dont remove mappings with auto-update enabled,
181 // unless forceRelease is true.
182 if (not map->getAutoUpdate() or forceRelease) {
183 map->enableAutoUpdate(false);
184 unregisterMapping(map);
185 }
186 }
187
188 // Clear all current IGDs.
189 for (auto const& [_, protocol] : protocolList_) {
190 protocol->clearIgds();
191 }
192
193 started_ = false;
194}
195
196uint16_t
197UPnPContext::generateRandomPort(PortType type, bool mustBeEven)
198{
199 auto minPort = type == PortType::TCP ? UPNP_TCP_PORT_MIN : UPNP_UDP_PORT_MIN;
200 auto maxPort = type == PortType::TCP ? UPNP_TCP_PORT_MAX : UPNP_UDP_PORT_MAX;
201
202 if (minPort >= maxPort) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400203 // JAMI_ERR("Max port number (%i) must be greater than min port number (%i)", maxPort, minPort);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400204 // Must be called with valid range.
205 assert(false);
206 }
207
208 int fact = mustBeEven ? 2 : 1;
209 if (mustBeEven) {
210 minPort /= fact;
211 maxPort /= fact;
212 }
213
214 // Seed the generator.
215 static std::mt19937 gen(dht::crypto::getSeededRandomEngine());
216 // Define the range.
217 std::uniform_int_distribution<uint16_t> dist(minPort, maxPort);
218 return dist(gen) * fact;
219}
220
221void
222UPnPContext::connectivityChanged()
223{
224 if (not isValidThread()) {
225 runOnUpnpContextQueue([this] { connectivityChanged(); });
226 return;
227 }
228
229 auto hostAddr = ip_utils::getLocalAddr(AF_INET);
230
Morteza Namvar5f639522023-07-04 17:08:58 -0400231 // JAMI_DBG("Connectivity change check: host address %s", hostAddr.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400232
233 auto restartUpnp = false;
234
235 // On reception of "connectivity change" notification, the UPNP search
236 // will be restarted if either there is no valid IGD, or the IGD address
237 // changed.
238
239 if (not isReady()) {
240 restartUpnp = true;
241 } else {
242 // Check if the host address changed.
243 for (auto const& [_, protocol] : protocolList_) {
244 if (protocol->isReady() and hostAddr != protocol->getHostAddress()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400245 // JAMI_WARN("Host address changed from %s to %s",
246 // protocol->getHostAddress().toString().c_str(),
247 // hostAddr.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400248 protocol->clearIgds();
249 restartUpnp = true;
250 break;
251 }
252 }
253 }
254
255 // We have at least one valid IGD and the host address did
256 // not change, so no need to restart.
257 if (not restartUpnp) {
258 return;
259 }
260
261 // No registered controller. A new search will be performed when
262 // a controller is registered.
263 if (controllerList_.empty())
264 return;
265
Morteza Namvar5f639522023-07-04 17:08:58 -0400266 // JAMI_DBG("Connectivity changed. Clear the IGDs and restart");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400267
268 stopUpnp();
269 startUpnp();
270
271 // Mapping with auto update enabled must be processed first.
272 processMappingWithAutoUpdate();
273}
274
275void
276UPnPContext::setPublicAddress(const IpAddr& addr)
277{
278 if (not addr)
279 return;
280
281 std::lock_guard<std::mutex> lock(mappingMutex_);
282 if (knownPublicAddress_ != addr) {
283 knownPublicAddress_ = std::move(addr);
Morteza Namvar5f639522023-07-04 17:08:58 -0400284 // JAMI_DBG("Setting the known public address to %s", addr.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400285 }
286}
287
288bool
289UPnPContext::isReady() const
290{
291 std::lock_guard<std::mutex> lock(mappingMutex_);
292 return not validIgdList_.empty();
293}
294
295IpAddr
296UPnPContext::getExternalIP() const
297{
298 std::lock_guard<std::mutex> lock(mappingMutex_);
299 // Return the first IGD Ip available.
300 if (not validIgdList_.empty()) {
301 return (*validIgdList_.begin())->getPublicIp();
302 }
303 return {};
304}
305
306Mapping::sharedPtr_t
307UPnPContext::reserveMapping(Mapping& requestedMap)
308{
309 auto desiredPort = requestedMap.getExternalPort();
310
311 if (desiredPort == 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400312 // JAMI_DBG("Desired port is not set, will provide the first available port for [%s]",
Adrien Béraud612b55b2023-05-29 10:42:04 -0400313 requestedMap.getTypeStr());
314 } else {
Morteza Namvar5f639522023-07-04 17:08:58 -0400315 // JAMI_DBG("Try to find mapping for port %i [%s]", desiredPort, requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400316 }
317
318 Mapping::sharedPtr_t mapRes;
319
320 {
321 std::lock_guard<std::mutex> lock(mappingMutex_);
322 auto& mappingList = getMappingList(requestedMap.getType());
323
324 // We try to provide a mapping in "OPEN" state. If not found,
325 // we provide any available mapping. In this case, it's up to
326 // the caller to use it or not.
327 for (auto const& [_, map] : mappingList) {
328 // If the desired port is null, we pick the first available port.
329 if (map->isValid() and (desiredPort == 0 or map->getExternalPort() == desiredPort)
330 and map->isAvailable()) {
331 // Considere the first available mapping regardless of its
332 // state. A mapping with OPEN state will be used if found.
333 if (not mapRes)
334 mapRes = map;
335
336 if (map->getState() == MappingState::OPEN) {
337 // Found an "OPEN" mapping. We are done.
338 mapRes = map;
339 break;
340 }
341 }
342 }
343 }
344
345 // Create a mapping if none was available.
346 if (not mapRes) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400347 // JAMI_WARN("Did not find any available mapping. Will request one now");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400348 mapRes = registerMapping(requestedMap);
349 }
350
351 if (mapRes) {
352 // Make the mapping unavailable
353 mapRes->setAvailable(false);
354 // Copy attributes.
355 mapRes->setNotifyCallback(requestedMap.getNotifyCallback());
356 mapRes->enableAutoUpdate(requestedMap.getAutoUpdate());
357 // Notify the listener.
358 if (auto cb = mapRes->getNotifyCallback())
359 cb(mapRes);
360 }
361
362 updateMappingList(true);
363
364 return mapRes;
365}
366
367void
368UPnPContext::releaseMapping(const Mapping& map)
369{
370 if (not isValidThread()) {
371 runOnUpnpContextQueue([this, map] { releaseMapping(map); });
372 return;
373 }
374
375 auto mapPtr = getMappingWithKey(map.getMapKey());
376
377 if (not mapPtr) {
378 // Might happen if the mapping failed or was never granted.
Morteza Namvar5f639522023-07-04 17:08:58 -0400379 // JAMI_DBG("Mapping %s does not exist or was already removed", map.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400380 return;
381 }
382
383 if (mapPtr->isAvailable()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400384 // JAMI_WARN("Trying to release an unused mapping %s", mapPtr->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400385 return;
386 }
387
388 // Remove it.
389 requestRemoveMapping(mapPtr);
390 unregisterMapping(mapPtr);
391}
392
393void
394UPnPContext::registerController(void* controller)
395{
396 {
397 std::lock_guard<std::mutex> lock(mappingMutex_);
398 if (shutdownComplete_) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400399 // JAMI_WARN("UPnPContext already shut down");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400400 return;
401 }
402 }
403
404 if (not isValidThread()) {
405 runOnUpnpContextQueue([this, controller] { registerController(controller); });
406 return;
407 }
408
409 auto ret = controllerList_.emplace(controller);
410 if (not ret.second) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400411 // JAMI_WARN("Controller %p is already registered", controller);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400412 return;
413 }
414
Morteza Namvar5f639522023-07-04 17:08:58 -0400415 // JAMI_DBG("Successfully registered controller %p", controller);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400416 if (not started_)
417 startUpnp();
418}
419
420void
421UPnPContext::unregisterController(void* controller)
422{
423 if (not isValidThread()) {
424 runOnUpnpContextQueue([this, controller] { unregisterController(controller); });
425 return;
426 }
427
428 if (controllerList_.erase(controller) == 1) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400429 // JAMI_DBG("Successfully unregistered controller %p", controller);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400430 } else {
Morteza Namvar5f639522023-07-04 17:08:58 -0400431 // JAMI_DBG("Controller %p was already removed", controller);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400432 }
433
434 if (controllerList_.empty()) {
435 stopUpnp();
436 }
437}
438
439uint16_t
440UPnPContext::getAvailablePortNumber(PortType type)
441{
442 // Only return an availalable random port. No actual
443 // reservation is made here.
444
445 std::lock_guard<std::mutex> lock(mappingMutex_);
446 auto& mappingList = getMappingList(type);
447 int tryCount = 0;
448 while (tryCount++ < MAX_REQUEST_RETRIES) {
449 uint16_t port = generateRandomPort(type);
450 Mapping map(type, port, port);
451 if (mappingList.find(map.getMapKey()) == mappingList.end())
452 return port;
453 }
454
455 // Very unlikely to get here.
Morteza Namvar5f639522023-07-04 17:08:58 -0400456 // JAMI_ERR("Could not find an available port after %i trials", MAX_REQUEST_RETRIES);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400457 return 0;
458}
459
460void
461UPnPContext::requestMapping(const Mapping::sharedPtr_t& map)
462{
463 assert(map);
464
465 if (not isValidThread()) {
466 runOnUpnpContextQueue([this, map] { requestMapping(map); });
467 return;
468 }
469
470 auto const& igd = getPreferredIgd();
471 // We must have at least a valid IGD pointer if we get here.
472 // Not this method is called only if there were a valid IGD, however,
473 // because the processing is asynchronous, it's possible that the IGD
474 // was invalidated when the this code executed.
475 if (not igd) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400476 // JAMI_DBG("No valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400477 return;
478 }
479
480 map->setIgd(igd);
481
Morteza Namvar5f639522023-07-04 17:08:58 -0400482 // JAMI_DBG("Request mapping %s using protocol [%s] IGD [%s]",
483 // map->toString().c_str(),
484 // igd->getProtocolName(),
485 // igd->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400486
487 if (map->getState() != MappingState::IN_PROGRESS)
488 updateMappingState(map, MappingState::IN_PROGRESS);
489
490 auto const& protocol = protocolList_.at(igd->getProtocol());
491 protocol->requestMappingAdd(*map);
492}
493
494bool
495UPnPContext::provisionNewMappings(PortType type, int portCount)
496{
Morteza Namvar5f639522023-07-04 17:08:58 -0400497 // JAMI_DBG("Provision %i new mappings of type [%s]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400498
499 assert(portCount > 0);
500
501 while (portCount > 0) {
502 auto port = getAvailablePortNumber(type);
503 if (port > 0) {
504 // Found an available port number
505 portCount--;
506 Mapping map(type, port, port, true);
507 registerMapping(map);
508 } else {
509 // Very unlikely to get here!
Morteza Namvar5f639522023-07-04 17:08:58 -0400510 // JAMI_ERR("Can not find any available port to provision!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400511 return false;
512 }
513 }
514
515 return true;
516}
517
518bool
519UPnPContext::deleteUnneededMappings(PortType type, int portCount)
520{
Morteza Namvar5f639522023-07-04 17:08:58 -0400521 // JAMI_DBG("Remove %i unneeded mapping of type [%s]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400522
523 assert(portCount > 0);
524
525 CHECK_VALID_THREAD();
526
527 std::lock_guard<std::mutex> lock(mappingMutex_);
528 auto& mappingList = getMappingList(type);
529
530 for (auto it = mappingList.begin(); it != mappingList.end();) {
531 auto map = it->second;
532 assert(map);
533
534 if (not map->isAvailable()) {
535 it++;
536 continue;
537 }
538
539 if (map->getState() == MappingState::OPEN and portCount > 0) {
540 // Close portCount mappings in "OPEN" state.
541 requestRemoveMapping(map);
542 it = unregisterMapping(it);
543 portCount--;
544 } else if (map->getState() != MappingState::OPEN) {
545 // If this methods is called, it means there are more open
546 // mappings than required. So, all mappings in a state other
547 // than "OPEN" state (typically in in-progress state) will
548 // be deleted as well.
549 it = unregisterMapping(it);
550 } else {
551 it++;
552 }
553 }
554
555 return true;
556}
557
558void
559UPnPContext::updatePreferredIgd()
560{
561 CHECK_VALID_THREAD();
562
563 if (preferredIgd_ and preferredIgd_->isValid())
564 return;
565
566 // Reset and search for the best IGD.
567 preferredIgd_.reset();
568
569 for (auto const& [_, protocol] : protocolList_) {
570 if (protocol->isReady()) {
571 auto igdList = protocol->getIgdList();
572 assert(not igdList.empty());
573 auto const& igd = igdList.front();
574 if (not igd->isValid())
575 continue;
576
577 // Prefer NAT-PMP over PUPNP.
578 if (preferredIgd_ and igd->getProtocol() != NatProtocolType::NAT_PMP)
579 continue;
580
581 // Update.
582 preferredIgd_ = igd;
583 }
584 }
585
586 if (preferredIgd_ and preferredIgd_->isValid()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400587 // JAMI_DBG("Preferred IGD updated to [%s] IGD [%s %s] ",
588 // preferredIgd_->getProtocolName(),
589 // preferredIgd_->getUID().c_str(),
590 // preferredIgd_->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400591 }
592}
593
594std::shared_ptr<IGD>
595UPnPContext::getPreferredIgd() const
596{
597 CHECK_VALID_THREAD();
598
599 return preferredIgd_;
600}
601
602void
603UPnPContext::updateMappingList(bool async)
604{
605 // Run async if requested.
606 if (async) {
607 runOnUpnpContextQueue([this] { updateMappingList(false); });
608 return;
609 }
610
611 CHECK_VALID_THREAD();
612
613 // Update the preferred IGD.
614 updatePreferredIgd();
615
616 if (mappingListUpdateTimer_) {
617 mappingListUpdateTimer_->cancel();
618 mappingListUpdateTimer_ = {};
619 }
620
621 // Skip if no controller registered.
622 if (controllerList_.empty())
623 return;
624
625 // Cancel the current timer (if any) and re-schedule.
626 std::shared_ptr<IGD> prefIgd = getPreferredIgd();
627 if (not prefIgd) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400628 // JAMI_DBG("UPNP/NAT-PMP enabled, but no valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400629 // No valid IGD. Nothing to do.
630 return;
631 }
632
633 mappingListUpdateTimer_ = getScheduler()->scheduleIn([this] { updateMappingList(false); },
634 MAP_UPDATE_INTERVAL);
635
636 // Process pending requests if any.
637 processPendingRequests(prefIgd);
638
639 // Make new requests for mappings that failed and have
640 // the auto-update option enabled.
641 processMappingWithAutoUpdate();
642
643 PortType typeArray[2] = {PortType::TCP, PortType::UDP};
644
645 for (auto idx : {0, 1}) {
646 auto type = typeArray[idx];
647
648 MappingStatus status;
649 getMappingStatus(type, status);
650
Morteza Namvar5f639522023-07-04 17:08:58 -0400651 // JAMI_DBG("Mapping status [%s] - overall %i: %i open (%i ready + %i in use), %i pending, %i "
652 // "in-progress, %i failed",
653 // Mapping::getTypeStr(type),
654 // status.sum(),
655 // status.openCount_,
656 // status.readyCount_,
657 // status.openCount_ - status.readyCount_,
658 // status.pendingCount_,
659 // status.inProgressCount_,
660 // status.failedCount_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400661
662 if (status.failedCount_ > 0) {
663 std::lock_guard<std::mutex> lock(mappingMutex_);
664 auto const& mappingList = getMappingList(type);
665 for (auto const& [_, map] : mappingList) {
666 if (map->getState() == MappingState::FAILED) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400667 // JAMI_DBG("Mapping status [%s] - Available [%s]",
668 // map->toString(true).c_str(),
669 // map->isAvailable() ? "YES" : "NO");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400670 }
671 }
672 }
673
674 int toRequestCount = (int) minOpenPortLimit_[idx]
675 - (int) (status.readyCount_ + status.inProgressCount_
676 + status.pendingCount_);
677
678 // Provision/release mappings accordingly.
679 if (toRequestCount > 0) {
680 // Take into account the request in-progress when making
681 // requests for new mappings.
682 provisionNewMappings(type, toRequestCount);
683 } else if (status.readyCount_ > maxOpenPortLimit_[idx]) {
684 deleteUnneededMappings(type, status.readyCount_ - maxOpenPortLimit_[idx]);
685 }
686 }
687
688 // Prune the mapping list if needed
689 if (protocolList_.at(NatProtocolType::PUPNP)->isReady()) {
690#if HAVE_LIBNATPMP
691 // Dont perform if NAT-PMP is valid.
692 if (not protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
693#endif
694 {
695 pruneMappingList();
696 }
697 }
698
699#if HAVE_LIBNATPMP
700 // Renew nat-pmp allocations
701 if (protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
702 renewAllocations();
703#endif
704}
705
706void
707UPnPContext::pruneMappingList()
708{
709 CHECK_VALID_THREAD();
710
711 MappingStatus status;
712 getMappingStatus(status);
713
714 // Do not prune the list if there are pending/in-progress requests.
715 if (status.inProgressCount_ != 0 or status.pendingCount_ != 0) {
716 return;
717 }
718
719 auto const& igd = getPreferredIgd();
720 if (not igd or igd->getProtocol() != NatProtocolType::PUPNP) {
721 return;
722 }
723 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
724
725 auto remoteMapList = protocol->getMappingsListByDescr(igd,
726 Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
727 if (remoteMapList.empty()) {
728 std::lock_guard<std::mutex> lock(mappingMutex_);
729 if (not getMappingList(PortType::TCP).empty() or getMappingList(PortType::TCP).empty()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400730 // JAMI_WARN("We have provisionned mappings but the PUPNP IGD returned an empty list!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400731 }
732 }
733
734 pruneUnMatchedMappings(igd, remoteMapList);
735 pruneUnTrackedMappings(igd, remoteMapList);
736}
737
738void
739UPnPContext::pruneUnMatchedMappings(const std::shared_ptr<IGD>& igd,
740 const std::map<Mapping::key_t, Mapping>& remoteMapList)
741{
742 // Check/synchronize local mapping list with the list
743 // returned by the IGD.
744
745 PortType types[2] {PortType::TCP, PortType::UDP};
746
747 for (auto& type : types) {
748 // Use a temporary list to avoid processing mappings while holding the lock.
749 std::list<Mapping::sharedPtr_t> toRemoveList;
750 {
751 std::lock_guard<std::mutex> lock(mappingMutex_);
752 auto& mappingList = getMappingList(type);
753 for (auto const& [_, map] : mappingList) {
754 // Only check mappings allocated by UPNP protocol.
755 if (map->getProtocol() != NatProtocolType::PUPNP) {
756 continue;
757 }
758 // Set mapping as failed if not found in the list
759 // returned by the IGD.
760 if (map->getState() == MappingState::OPEN
761 and remoteMapList.find(map->getMapKey()) == remoteMapList.end()) {
762 toRemoveList.emplace_back(map);
763
Morteza Namvar5f639522023-07-04 17:08:58 -0400764 // JAMI_WARN("Mapping %s (IGD %s) marked as \"OPEN\" but not found in the "
765 // "remote list. Mark as failed!",
766 // map->toString().c_str(),
767 // igd->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400768 }
769 }
770 }
771
772 for (auto const& map : toRemoveList) {
773 updateMappingState(map, MappingState::FAILED);
774 unregisterMapping(map);
775 }
776 }
777}
778
779void
780UPnPContext::pruneUnTrackedMappings(const std::shared_ptr<IGD>& igd,
781 const std::map<Mapping::key_t, Mapping>& remoteMapList)
782{
783 // Use a temporary list to avoid processing mappings while holding the lock.
784 std::list<Mapping> toRemoveList;
785 {
786 std::lock_guard<std::mutex> lock(mappingMutex_);
787
788 for (auto const& [_, map] : remoteMapList) {
789 // Must has valid IGD pointer and use UPNP protocol.
790 assert(map.getIgd());
791 assert(map.getIgd()->getProtocol() == NatProtocolType::PUPNP);
792 auto& mappingList = getMappingList(map.getType());
793 auto it = mappingList.find(map.getMapKey());
794 if (it == mappingList.end()) {
795 // Not present, request mapping remove.
796 toRemoveList.emplace_back(std::move(map));
797 // Make only few remove requests at once.
798 if (toRemoveList.size() >= MAX_REQUEST_REMOVE_COUNT)
799 break;
800 }
801 }
802 }
803
804 // Remove un-tracked mappings.
805 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
806 for (auto const& map : toRemoveList) {
807 protocol->requestMappingRemove(map);
808 }
809}
810
811void
812UPnPContext::pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd)
813{
814 CHECK_VALID_THREAD();
815
816 // Use temporary list to avoid holding the lock while
817 // processing the mapping list.
818 std::list<Mapping::sharedPtr_t> toRemoveList;
819 {
820 std::lock_guard<std::mutex> lock(mappingMutex_);
821
822 PortType types[2] {PortType::TCP, PortType::UDP};
823 for (auto& type : types) {
824 auto& mappingList = getMappingList(type);
825 for (auto const& [_, map] : mappingList) {
826 if (map->getIgd() == igd)
827 toRemoveList.emplace_back(map);
828 }
829 }
830 }
831
832 for (auto const& map : toRemoveList) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400833 // JAMI_DBG("Remove mapping %s (has an invalid IGD %s [%s])",
834 // map->toString().c_str(),
835 // igd->toString().c_str(),
836 // igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400837 updateMappingState(map, MappingState::FAILED);
838 unregisterMapping(map);
839 }
840}
841
842void
843UPnPContext::processPendingRequests(const std::shared_ptr<IGD>& igd)
844{
845 // This list holds the mappings to be requested. This is
846 // needed to avoid performing the requests while holding
847 // the lock.
848 std::list<Mapping::sharedPtr_t> requestsList;
849
850 // Populate the list of requests to perform.
851 {
852 std::lock_guard<std::mutex> lock(mappingMutex_);
853 PortType typeArray[2] {PortType::TCP, PortType::UDP};
854
855 for (auto type : typeArray) {
856 auto& mappingList = getMappingList(type);
857 for (auto& [_, map] : mappingList) {
858 if (map->getState() == MappingState::PENDING) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400859 // JAMI_DBG("Send pending request for mapping %s to IGD %s",
860 // map->toString().c_str(),
861 // igd->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400862 requestsList.emplace_back(map);
863 }
864 }
865 }
866 }
867
868 // Process the pending requests.
869 for (auto const& map : requestsList) {
870 requestMapping(map);
871 }
872}
873
874void
875UPnPContext::processMappingWithAutoUpdate()
876{
877 // This list holds the mappings to be requested. This is
878 // needed to avoid performing the requests while holding
879 // the lock.
880 std::list<Mapping::sharedPtr_t> requestsList;
881
882 // Populate the list of requests for mappings with auto-update enabled.
883 {
884 std::lock_guard<std::mutex> lock(mappingMutex_);
885 PortType typeArray[2] {PortType::TCP, PortType::UDP};
886
887 for (auto type : typeArray) {
888 auto& mappingList = getMappingList(type);
889 for (auto const& [_, map] : mappingList) {
890 if (map->getState() == MappingState::FAILED and map->getAutoUpdate()) {
891 requestsList.emplace_back(map);
892 }
893 }
894 }
895 }
896
897 for (auto const& oldMap : requestsList) {
898 // Request a new mapping if auto-update is enabled.
Morteza Namvar5f639522023-07-04 17:08:58 -0400899 // JAMI_DBG("Mapping %s has auto-update enabled, a new mapping will be requested",
900 // oldMap->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400901
902 // Reserve a new mapping.
903 Mapping newMapping(oldMap->getType());
904 newMapping.enableAutoUpdate(true);
905 newMapping.setNotifyCallback(oldMap->getNotifyCallback());
906
907 auto const& mapPtr = reserveMapping(newMapping);
908 assert(mapPtr);
909
910 // Release the old one.
911 oldMap->setAvailable(true);
912 oldMap->enableAutoUpdate(false);
913 oldMap->setNotifyCallback(nullptr);
914 unregisterMapping(oldMap);
915 }
916}
917
918void
919UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event)
920{
921 assert(igd);
922
923 if (not isValidThread()) {
924 runOnUpnpContextQueue([this, igd, event] { onIgdUpdated(igd, event); });
925 return;
926 }
927
928 // Reset to start search for a new best IGD.
929 preferredIgd_.reset();
930
931 char const* IgdState = event == UpnpIgdEvent::ADDED ? "ADDED"
932 : event == UpnpIgdEvent::REMOVED ? "REMOVED"
933 : "INVALID";
934
935 auto const& igdLocalAddr = igd->getLocalIp();
936 auto protocolName = igd->getProtocolName();
937
Morteza Namvar5f639522023-07-04 17:08:58 -0400938 // JAMI_DBG("New event for IGD [%s %s] [%s]: [%s]",
939 // igd->getUID().c_str(),
940 // igd->toString().c_str(),
941 // protocolName,
942 // IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400943
944 // Check if the IGD has valid addresses.
945 if (not igdLocalAddr) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400946 // JAMI_WARN("[%s] IGD has an invalid local address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400947 return;
948 }
949
950 if (not igd->getPublicIp()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400951 // JAMI_WARN("[%s] IGD has an invalid public address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400952 return;
953 }
954
955 if (knownPublicAddress_ and igd->getPublicIp() != knownPublicAddress_) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400956 // JAMI_WARN("[%s] IGD external address [%s] does not match known public address [%s]."
957 // " The mapped addresses might not be reachable",
958 // protocolName,
959 // igd->getPublicIp().toString().c_str(),
960 // knownPublicAddress_.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400961 }
962
963 // The IGD was removed or is invalid.
964 if (event == UpnpIgdEvent::REMOVED or event == UpnpIgdEvent::INVALID_STATE) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400965 // JAMI_WARN("State of IGD [%s %s] [%s] changed to [%s]. Pruning the mapping list",
966 // igd->getUID().c_str(),
967 // igd->toString().c_str(),
968 // protocolName,
969 // IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400970
971 pruneMappingsWithInvalidIgds(igd);
972
973 std::lock_guard<std::mutex> lock(mappingMutex_);
974 validIgdList_.erase(igd);
975 return;
976 }
977
978 // Update the IGD list.
979 {
980 std::lock_guard<std::mutex> lock(mappingMutex_);
981 auto ret = validIgdList_.emplace(igd);
982 if (ret.second) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400983 // JAMI_DBG("IGD [%s] on address %s was added. Will process any pending requests",
984 // protocolName,
985 // igdLocalAddr.toString(true, true).c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400986 } else {
987 // Already in the list.
Morteza Namvar5f639522023-07-04 17:08:58 -0400988 // JAMI_ERR("IGD [%s] on address %s already in the list",
989 // protocolName,
990 // igdLocalAddr.toString(true, true).c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400991 return;
992 }
993 }
994
995 // Update the provisionned mappings.
996 updateMappingList(false);
997}
998
999void
1000UPnPContext::onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1001{
1002 CHECK_VALID_THREAD();
1003
1004 // Check if we have a pending request for this response.
1005 auto map = getMappingWithKey(mapRes.getMapKey());
1006 if (not map) {
1007 // We may receive a response for a canceled request. Just ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001008 // JAMI_DBG("Response for mapping %s [IGD %s] [%s] does not have a local match",
1009 // mapRes.toString().c_str(),
1010 // igd->toString().c_str(),
1011 // mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001012 return;
1013 }
1014
1015 // The mapping request is new and successful. Update.
1016 map->setIgd(igd);
1017 map->setInternalAddress(mapRes.getInternalAddress());
1018 map->setExternalPort(mapRes.getExternalPort());
1019
1020 // Update the state and report to the owner.
1021 updateMappingState(map, MappingState::OPEN);
1022
Morteza Namvar5f639522023-07-04 17:08:58 -04001023 // JAMI_DBG("Mapping %s (on IGD %s [%s]) successfully performed",
1024 // map->toString().c_str(),
1025 // igd->toString().c_str(),
1026 // map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001027
1028 // Call setValid() to reset the errors counter. We need
1029 // to reset the counter on each successful response.
1030 igd->setValid(true);
1031}
1032
1033#if HAVE_LIBNATPMP
1034void
1035UPnPContext::onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map)
1036{
1037 auto mapPtr = getMappingWithKey(map.getMapKey());
1038
1039 if (not mapPtr) {
1040 // We may receive a notification for a canceled request. Ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001041 // JAMI_WARN("Renewed mapping %s from IGD %s [%s] does not have a match in local list",
1042 // map.toString().c_str(),
1043 // igd->toString().c_str(),
1044 // map.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001045 return;
1046 }
1047 if (mapPtr->getProtocol() != NatProtocolType::NAT_PMP or not mapPtr->isValid()
1048 or mapPtr->getState() != MappingState::OPEN) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001049 // JAMI_WARN("Renewed mapping %s from IGD %s [%s] is in unexpected state",
1050 // mapPtr->toString().c_str(),
1051 // igd->toString().c_str(),
1052 // mapPtr->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001053 return;
1054 }
1055
1056 mapPtr->setRenewalTime(map.getRenewalTime());
1057}
1058#endif
1059
1060void
1061UPnPContext::requestRemoveMapping(const Mapping::sharedPtr_t& map)
1062{
1063 CHECK_VALID_THREAD();
1064
1065 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001066 // JAMI_ERR("Mapping shared pointer is null!");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001067 return;
1068 }
1069
1070 if (not map->isValid()) {
1071 // Silently ignore if the mapping is invalid
1072 return;
1073 }
1074
1075 auto protocol = protocolList_.at(map->getIgd()->getProtocol());
1076 protocol->requestMappingRemove(*map);
1077}
1078
1079void
1080UPnPContext::deleteAllMappings(PortType type)
1081{
1082 if (not isValidThread()) {
1083 runOnUpnpContextQueue([this, type] { deleteAllMappings(type); });
1084 return;
1085 }
1086
1087 std::lock_guard<std::mutex> lock(mappingMutex_);
1088 auto& mappingList = getMappingList(type);
1089
1090 for (auto const& [_, map] : mappingList) {
1091 requestRemoveMapping(map);
1092 }
1093}
1094
1095void
1096UPnPContext::onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1097{
1098 if (not mapRes.isValid())
1099 return;
1100
1101 if (not isValidThread()) {
1102 runOnUpnpContextQueue([this, igd, mapRes] { onMappingRemoved(igd, mapRes); });
1103 return;
1104 }
1105
1106 auto map = getMappingWithKey(mapRes.getMapKey());
1107 // Notify the listener.
1108 if (map and map->getNotifyCallback())
1109 map->getNotifyCallback()(map);
1110}
1111
1112Mapping::sharedPtr_t
1113UPnPContext::registerMapping(Mapping& map)
1114{
1115 if (map.getExternalPort() == 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001116 // JAMI_DBG("Port number not set. Will set a random port number");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001117 auto port = getAvailablePortNumber(map.getType());
1118 map.setExternalPort(port);
1119 map.setInternalPort(port);
1120 }
1121
1122 // Newly added mapping must be in pending state by default.
1123 map.setState(MappingState::PENDING);
1124
1125 Mapping::sharedPtr_t mapPtr;
1126
1127 {
1128 std::lock_guard<std::mutex> lock(mappingMutex_);
1129 auto& mappingList = getMappingList(map.getType());
1130
1131 auto ret = mappingList.emplace(map.getMapKey(), std::make_shared<Mapping>(map));
1132 if (not ret.second) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001133 // JAMI_WARN("Mapping request for %s already added!", map.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001134 return {};
1135 }
1136 mapPtr = ret.first->second;
1137 assert(mapPtr);
1138 }
1139
1140 // No available IGD. The pending mapping requests will be processed
1141 // when a IGD becomes available (in onIgdAdded() method).
1142 if (not isReady()) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001143 // JAMI_WARN("No IGD available. Mapping will be requested when an IGD becomes available");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001144 } else {
1145 requestMapping(mapPtr);
1146 }
1147
1148 return mapPtr;
1149}
1150
1151std::map<Mapping::key_t, Mapping::sharedPtr_t>::iterator
1152UPnPContext::unregisterMapping(std::map<Mapping::key_t, Mapping::sharedPtr_t>::iterator it)
1153{
1154 assert(it->second);
1155
1156 CHECK_VALID_THREAD();
1157 auto descr = it->second->toString();
1158 auto& mappingList = getMappingList(it->second->getType());
1159 auto ret = mappingList.erase(it);
1160
1161 return ret;
1162}
1163
1164void
1165UPnPContext::unregisterMapping(const Mapping::sharedPtr_t& map)
1166{
1167 CHECK_VALID_THREAD();
1168
1169 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001170 // JAMI_ERR("Mapping pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001171 return;
1172 }
1173
1174 if (map->getAutoUpdate()) {
1175 // Dont unregister mappings with auto-update enabled.
1176 return;
1177 }
1178 auto& mappingList = getMappingList(map->getType());
1179
1180 if (mappingList.erase(map->getMapKey()) == 1) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001181 // JAMI_DBG("Unregistered mapping %s", map->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001182 } else {
1183 // The mapping may already be un-registered. Just ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001184 // JAMI_DBG("Mapping %s [%s] does not have a local match",
1185 // map->toString().c_str(),
1186 // map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001187 }
1188}
1189
1190std::map<Mapping::key_t, Mapping::sharedPtr_t>&
1191UPnPContext::getMappingList(PortType type)
1192{
1193 unsigned typeIdx = type == PortType::TCP ? 0 : 1;
1194 return mappingList_[typeIdx];
1195}
1196
1197Mapping::sharedPtr_t
1198UPnPContext::getMappingWithKey(Mapping::key_t key)
1199{
1200 std::lock_guard<std::mutex> lock(mappingMutex_);
1201 auto const& mappingList = getMappingList(Mapping::getTypeFromMapKey(key));
1202 auto it = mappingList.find(key);
1203 if (it == mappingList.end())
1204 return nullptr;
1205 return it->second;
1206}
1207
1208void
1209UPnPContext::getMappingStatus(PortType type, MappingStatus& status)
1210{
1211 std::lock_guard<std::mutex> lock(mappingMutex_);
1212 auto& mappingList = getMappingList(type);
1213
1214 for (auto const& [_, map] : mappingList) {
1215 switch (map->getState()) {
1216 case MappingState::PENDING: {
1217 status.pendingCount_++;
1218 break;
1219 }
1220 case MappingState::IN_PROGRESS: {
1221 status.inProgressCount_++;
1222 break;
1223 }
1224 case MappingState::FAILED: {
1225 status.failedCount_++;
1226 break;
1227 }
1228 case MappingState::OPEN: {
1229 status.openCount_++;
1230 if (map->isAvailable())
1231 status.readyCount_++;
1232 break;
1233 }
1234
1235 default:
1236 // Must not get here.
1237 assert(false);
1238 break;
1239 }
1240 }
1241}
1242
1243void
1244UPnPContext::getMappingStatus(MappingStatus& status)
1245{
1246 getMappingStatus(PortType::TCP, status);
1247 getMappingStatus(PortType::UDP, status);
1248}
1249
1250void
1251UPnPContext::onMappingRequestFailed(const Mapping& mapRes)
1252{
1253 CHECK_VALID_THREAD();
1254
1255 auto const& map = getMappingWithKey(mapRes.getMapKey());
1256 if (not map) {
1257 // We may receive a response for a removed request. Just ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001258 // JAMI_DBG("Mapping %s [IGD %s] does not have a local match",
1259 // mapRes.toString().c_str(),
1260 // mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001261 return;
1262 }
1263
1264 auto igd = map->getIgd();
1265 if (not igd) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001266 // JAMI_ERR("IGD pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001267 return;
1268 }
1269
1270 updateMappingState(map, MappingState::FAILED);
1271 unregisterMapping(map);
1272
Morteza Namvar5f639522023-07-04 17:08:58 -04001273 // JAMI_WARN("Mapping request for %s failed on IGD %s [%s]",
1274 // map->toString().c_str(),
1275 // igd->toString().c_str(),
1276 // igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001277}
1278
1279void
1280UPnPContext::updateMappingState(const Mapping::sharedPtr_t& map, MappingState newState, bool notify)
1281{
1282 CHECK_VALID_THREAD();
1283
1284 assert(map);
1285
1286 // Ignore if the state did not change.
1287 if (newState == map->getState()) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001288 // JAMI_DBG("Mapping %s already in state %s", map->toString().c_str(), map->getStateStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001289 return;
1290 }
1291
1292 // Update the state.
1293 map->setState(newState);
1294
1295 // Notify the listener if set.
1296 if (notify and map->getNotifyCallback())
1297 map->getNotifyCallback()(map);
1298}
1299
1300#if HAVE_LIBNATPMP
1301void
1302UPnPContext::renewAllocations()
1303{
1304 CHECK_VALID_THREAD();
1305
1306 // Check if the we have valid PMP IGD.
1307 auto pmpProto = protocolList_.at(NatProtocolType::NAT_PMP);
1308
1309 auto now = sys_clock::now();
1310 std::vector<Mapping::sharedPtr_t> toRenew;
1311
1312 for (auto type : {PortType::TCP, PortType::UDP}) {
1313 std::lock_guard<std::mutex> lock(mappingMutex_);
1314 auto mappingList = getMappingList(type);
1315 for (auto const& [_, map] : mappingList) {
1316 if (not map->isValid())
1317 continue;
1318 if (map->getProtocol() != NatProtocolType::NAT_PMP)
1319 continue;
1320 if (map->getState() != MappingState::OPEN)
1321 continue;
1322 if (now < map->getRenewalTime())
1323 continue;
1324
1325 toRenew.emplace_back(map);
1326 }
1327 }
1328
1329 // Quit if there are no mapping to renew
1330 if (toRenew.empty())
1331 return;
1332
1333 for (auto const& map : toRenew) {
1334 pmpProto->requestMappingRenew(*map);
1335 }
1336}
1337#endif
1338
1339} // namespace upnp
1340} // namespace jami