blob: c4344cf7a7761796030405f84cda5e865835116d [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 */
Morteza Namvar5f639522023-07-04 17:08:58 -040017#include "upnp/upnp_context.h"
Adrien Béraud25c30c42023-07-05 13:46:54 -040018#include "protocol/upnp_protocol.h"
19
Morteza Namvar5f639522023-07-04 17:08:58 -040020#include <asio/steady_timer.hpp>
Adrien Béraud9d350962023-07-13 15:36:32 -040021#if __has_include(<fmt/std.h>)
Adrien Béraud25c30c42023-07-05 13:46:54 -040022#include <fmt/std.h>
Adrien Béraud9d350962023-07-13 15:36:32 -040023#else
24#include <fmt/ostream.h>
25#endif
Adrien Béraud612b55b2023-05-29 10:42:04 -040026
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040027namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040028namespace upnp {
29
30constexpr static auto MAP_UPDATE_INTERVAL = std::chrono::seconds(30);
31constexpr static int MAX_REQUEST_RETRIES = 20;
32constexpr static int MAX_REQUEST_REMOVE_COUNT = 5;
33
34constexpr static uint16_t UPNP_TCP_PORT_MIN {10000};
35constexpr static uint16_t UPNP_TCP_PORT_MAX {UPNP_TCP_PORT_MIN + 5000};
36constexpr static uint16_t UPNP_UDP_PORT_MIN {20000};
37constexpr static uint16_t UPNP_UDP_PORT_MAX {UPNP_UDP_PORT_MIN + 5000};
38
Adrien Béraud25c30c42023-07-05 13:46:54 -040039UPnPContext::UPnPContext(std::shared_ptr<asio::io_context> ctx, std::shared_ptr<dht::log::Logger> logger)
40 : mappingListUpdateTimer_(*ioContext)
Adrien Béraud612b55b2023-05-29 10:42:04 -040041{
Morteza Namvar5f639522023-07-04 17:08:58 -040042 // JAMI_DBG("Creating UPnPContext instance [%p]", this);
Adrien Béraud612b55b2023-05-29 10:42:04 -040043
44 // Set port ranges
45 portRange_.emplace(PortType::TCP, std::make_pair(UPNP_TCP_PORT_MIN, UPNP_TCP_PORT_MAX));
46 portRange_.emplace(PortType::UDP, std::make_pair(UPNP_UDP_PORT_MIN, UPNP_UDP_PORT_MAX));
47
Adrien Béraud25c30c42023-07-05 13:46:54 -040048 ioContext->post([this] { init(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -040049}
50
Adrien Béraud25c30c42023-07-05 13:46:54 -040051/*std::shared_ptr<UPnPContext>
Adrien Béraud612b55b2023-05-29 10:42:04 -040052UPnPContext::getUPnPContext()
53{
54 // This is the unique shared instance (singleton) of UPnPContext class.
55 static auto context = std::make_shared<UPnPContext>();
56 return context;
Adrien Béraud25c30c42023-07-05 13:46:54 -040057}*/
Adrien Béraud612b55b2023-05-29 10:42:04 -040058
59void
60UPnPContext::shutdown(std::condition_variable& cv)
61{
Morteza Namvar5f639522023-07-04 17:08:58 -040062 // JAMI_DBG("Shutdown UPnPContext instance [%p]", this);
Adrien Béraud612b55b2023-05-29 10:42:04 -040063
64 stopUpnp(true);
65
66 for (auto const& [_, proto] : protocolList_) {
67 proto->terminate();
68 }
69
70 {
71 std::lock_guard<std::mutex> lock(mappingMutex_);
72 mappingList_->clear();
Adrien Béraud25c30c42023-07-05 13:46:54 -040073 //if (mappingListUpdateTimer_)
74 // mappingListUpdateTimer_->cancel();
75 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -040076 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éraud25c30c42023-07-05 13:46:54 -0400313 // requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400314 } 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
Adrien Béraud25c30c42023-07-05 13:46:54 -0400616 /*if (mappingListUpdateTimer_) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400617 mappingListUpdateTimer_->cancel();
618 mappingListUpdateTimer_ = {};
Adrien Béraud25c30c42023-07-05 13:46:54 -0400619 }*/
620 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400621
622 // Skip if no controller registered.
623 if (controllerList_.empty())
624 return;
625
626 // Cancel the current timer (if any) and re-schedule.
627 std::shared_ptr<IGD> prefIgd = getPreferredIgd();
628 if (not prefIgd) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400629 // JAMI_DBG("UPNP/NAT-PMP enabled, but no valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400630 // No valid IGD. Nothing to do.
631 return;
632 }
633
Adrien Béraud25c30c42023-07-05 13:46:54 -0400634 /*mappingListUpdateTimer_ = getScheduler()->scheduleIn([this] { updateMappingList(false); },
635 MAP_UPDATE_INTERVAL);*/
636 mappingListUpdateTimer_.expires_from_now(MAP_UPDATE_INTERVAL);
637 mappingListUpdateTimer_.async_wait([this](asio::error_code const& ec) {
638 if (ec != asio::error::operation_aborted)
639 updateMappingList(false);
640 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400641
642 // Process pending requests if any.
643 processPendingRequests(prefIgd);
644
645 // Make new requests for mappings that failed and have
646 // the auto-update option enabled.
647 processMappingWithAutoUpdate();
648
649 PortType typeArray[2] = {PortType::TCP, PortType::UDP};
650
651 for (auto idx : {0, 1}) {
652 auto type = typeArray[idx];
653
654 MappingStatus status;
655 getMappingStatus(type, status);
656
Morteza Namvar5f639522023-07-04 17:08:58 -0400657 // JAMI_DBG("Mapping status [%s] - overall %i: %i open (%i ready + %i in use), %i pending, %i "
658 // "in-progress, %i failed",
659 // Mapping::getTypeStr(type),
660 // status.sum(),
661 // status.openCount_,
662 // status.readyCount_,
663 // status.openCount_ - status.readyCount_,
664 // status.pendingCount_,
665 // status.inProgressCount_,
666 // status.failedCount_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400667
668 if (status.failedCount_ > 0) {
669 std::lock_guard<std::mutex> lock(mappingMutex_);
670 auto const& mappingList = getMappingList(type);
671 for (auto const& [_, map] : mappingList) {
672 if (map->getState() == MappingState::FAILED) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400673 // JAMI_DBG("Mapping status [%s] - Available [%s]",
674 // map->toString(true).c_str(),
675 // map->isAvailable() ? "YES" : "NO");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400676 }
677 }
678 }
679
680 int toRequestCount = (int) minOpenPortLimit_[idx]
681 - (int) (status.readyCount_ + status.inProgressCount_
682 + status.pendingCount_);
683
684 // Provision/release mappings accordingly.
685 if (toRequestCount > 0) {
686 // Take into account the request in-progress when making
687 // requests for new mappings.
688 provisionNewMappings(type, toRequestCount);
689 } else if (status.readyCount_ > maxOpenPortLimit_[idx]) {
690 deleteUnneededMappings(type, status.readyCount_ - maxOpenPortLimit_[idx]);
691 }
692 }
693
694 // Prune the mapping list if needed
695 if (protocolList_.at(NatProtocolType::PUPNP)->isReady()) {
696#if HAVE_LIBNATPMP
697 // Dont perform if NAT-PMP is valid.
698 if (not protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
699#endif
700 {
701 pruneMappingList();
702 }
703 }
704
705#if HAVE_LIBNATPMP
706 // Renew nat-pmp allocations
707 if (protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
708 renewAllocations();
709#endif
710}
711
712void
713UPnPContext::pruneMappingList()
714{
715 CHECK_VALID_THREAD();
716
717 MappingStatus status;
718 getMappingStatus(status);
719
720 // Do not prune the list if there are pending/in-progress requests.
721 if (status.inProgressCount_ != 0 or status.pendingCount_ != 0) {
722 return;
723 }
724
725 auto const& igd = getPreferredIgd();
726 if (not igd or igd->getProtocol() != NatProtocolType::PUPNP) {
727 return;
728 }
729 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
730
731 auto remoteMapList = protocol->getMappingsListByDescr(igd,
732 Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
733 if (remoteMapList.empty()) {
734 std::lock_guard<std::mutex> lock(mappingMutex_);
735 if (not getMappingList(PortType::TCP).empty() or getMappingList(PortType::TCP).empty()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400736 // JAMI_WARN("We have provisionned mappings but the PUPNP IGD returned an empty list!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400737 }
738 }
739
740 pruneUnMatchedMappings(igd, remoteMapList);
741 pruneUnTrackedMappings(igd, remoteMapList);
742}
743
744void
745UPnPContext::pruneUnMatchedMappings(const std::shared_ptr<IGD>& igd,
746 const std::map<Mapping::key_t, Mapping>& remoteMapList)
747{
748 // Check/synchronize local mapping list with the list
749 // returned by the IGD.
750
751 PortType types[2] {PortType::TCP, PortType::UDP};
752
753 for (auto& type : types) {
754 // Use a temporary list to avoid processing mappings while holding the lock.
755 std::list<Mapping::sharedPtr_t> toRemoveList;
756 {
757 std::lock_guard<std::mutex> lock(mappingMutex_);
758 auto& mappingList = getMappingList(type);
759 for (auto const& [_, map] : mappingList) {
760 // Only check mappings allocated by UPNP protocol.
761 if (map->getProtocol() != NatProtocolType::PUPNP) {
762 continue;
763 }
764 // Set mapping as failed if not found in the list
765 // returned by the IGD.
766 if (map->getState() == MappingState::OPEN
767 and remoteMapList.find(map->getMapKey()) == remoteMapList.end()) {
768 toRemoveList.emplace_back(map);
769
Morteza Namvar5f639522023-07-04 17:08:58 -0400770 // JAMI_WARN("Mapping %s (IGD %s) marked as \"OPEN\" but not found in the "
771 // "remote list. Mark as failed!",
772 // map->toString().c_str(),
773 // igd->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400774 }
775 }
776 }
777
778 for (auto const& map : toRemoveList) {
779 updateMappingState(map, MappingState::FAILED);
780 unregisterMapping(map);
781 }
782 }
783}
784
785void
786UPnPContext::pruneUnTrackedMappings(const std::shared_ptr<IGD>& igd,
787 const std::map<Mapping::key_t, Mapping>& remoteMapList)
788{
789 // Use a temporary list to avoid processing mappings while holding the lock.
790 std::list<Mapping> toRemoveList;
791 {
792 std::lock_guard<std::mutex> lock(mappingMutex_);
793
794 for (auto const& [_, map] : remoteMapList) {
795 // Must has valid IGD pointer and use UPNP protocol.
796 assert(map.getIgd());
797 assert(map.getIgd()->getProtocol() == NatProtocolType::PUPNP);
798 auto& mappingList = getMappingList(map.getType());
799 auto it = mappingList.find(map.getMapKey());
800 if (it == mappingList.end()) {
801 // Not present, request mapping remove.
802 toRemoveList.emplace_back(std::move(map));
803 // Make only few remove requests at once.
804 if (toRemoveList.size() >= MAX_REQUEST_REMOVE_COUNT)
805 break;
806 }
807 }
808 }
809
810 // Remove un-tracked mappings.
811 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
812 for (auto const& map : toRemoveList) {
813 protocol->requestMappingRemove(map);
814 }
815}
816
817void
818UPnPContext::pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd)
819{
820 CHECK_VALID_THREAD();
821
822 // Use temporary list to avoid holding the lock while
823 // processing the mapping list.
824 std::list<Mapping::sharedPtr_t> toRemoveList;
825 {
826 std::lock_guard<std::mutex> lock(mappingMutex_);
827
828 PortType types[2] {PortType::TCP, PortType::UDP};
829 for (auto& type : types) {
830 auto& mappingList = getMappingList(type);
831 for (auto const& [_, map] : mappingList) {
832 if (map->getIgd() == igd)
833 toRemoveList.emplace_back(map);
834 }
835 }
836 }
837
838 for (auto const& map : toRemoveList) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400839 // JAMI_DBG("Remove mapping %s (has an invalid IGD %s [%s])",
840 // map->toString().c_str(),
841 // igd->toString().c_str(),
842 // igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400843 updateMappingState(map, MappingState::FAILED);
844 unregisterMapping(map);
845 }
846}
847
848void
849UPnPContext::processPendingRequests(const std::shared_ptr<IGD>& igd)
850{
851 // This list holds the mappings to be requested. This is
852 // needed to avoid performing the requests while holding
853 // the lock.
854 std::list<Mapping::sharedPtr_t> requestsList;
855
856 // Populate the list of requests to perform.
857 {
858 std::lock_guard<std::mutex> lock(mappingMutex_);
859 PortType typeArray[2] {PortType::TCP, PortType::UDP};
860
861 for (auto type : typeArray) {
862 auto& mappingList = getMappingList(type);
863 for (auto& [_, map] : mappingList) {
864 if (map->getState() == MappingState::PENDING) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400865 // JAMI_DBG("Send pending request for mapping %s to IGD %s",
866 // map->toString().c_str(),
867 // igd->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400868 requestsList.emplace_back(map);
869 }
870 }
871 }
872 }
873
874 // Process the pending requests.
875 for (auto const& map : requestsList) {
876 requestMapping(map);
877 }
878}
879
880void
881UPnPContext::processMappingWithAutoUpdate()
882{
883 // This list holds the mappings to be requested. This is
884 // needed to avoid performing the requests while holding
885 // the lock.
886 std::list<Mapping::sharedPtr_t> requestsList;
887
888 // Populate the list of requests for mappings with auto-update enabled.
889 {
890 std::lock_guard<std::mutex> lock(mappingMutex_);
891 PortType typeArray[2] {PortType::TCP, PortType::UDP};
892
893 for (auto type : typeArray) {
894 auto& mappingList = getMappingList(type);
895 for (auto const& [_, map] : mappingList) {
896 if (map->getState() == MappingState::FAILED and map->getAutoUpdate()) {
897 requestsList.emplace_back(map);
898 }
899 }
900 }
901 }
902
903 for (auto const& oldMap : requestsList) {
904 // Request a new mapping if auto-update is enabled.
Morteza Namvar5f639522023-07-04 17:08:58 -0400905 // JAMI_DBG("Mapping %s has auto-update enabled, a new mapping will be requested",
906 // oldMap->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400907
908 // Reserve a new mapping.
909 Mapping newMapping(oldMap->getType());
910 newMapping.enableAutoUpdate(true);
911 newMapping.setNotifyCallback(oldMap->getNotifyCallback());
912
913 auto const& mapPtr = reserveMapping(newMapping);
914 assert(mapPtr);
915
916 // Release the old one.
917 oldMap->setAvailable(true);
918 oldMap->enableAutoUpdate(false);
919 oldMap->setNotifyCallback(nullptr);
920 unregisterMapping(oldMap);
921 }
922}
923
924void
925UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event)
926{
927 assert(igd);
928
929 if (not isValidThread()) {
930 runOnUpnpContextQueue([this, igd, event] { onIgdUpdated(igd, event); });
931 return;
932 }
933
934 // Reset to start search for a new best IGD.
935 preferredIgd_.reset();
936
937 char const* IgdState = event == UpnpIgdEvent::ADDED ? "ADDED"
938 : event == UpnpIgdEvent::REMOVED ? "REMOVED"
939 : "INVALID";
940
941 auto const& igdLocalAddr = igd->getLocalIp();
942 auto protocolName = igd->getProtocolName();
943
Morteza Namvar5f639522023-07-04 17:08:58 -0400944 // JAMI_DBG("New event for IGD [%s %s] [%s]: [%s]",
945 // igd->getUID().c_str(),
946 // igd->toString().c_str(),
947 // protocolName,
948 // IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400949
950 // Check if the IGD has valid addresses.
951 if (not igdLocalAddr) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400952 // JAMI_WARN("[%s] IGD has an invalid local address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400953 return;
954 }
955
956 if (not igd->getPublicIp()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400957 // JAMI_WARN("[%s] IGD has an invalid public address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400958 return;
959 }
960
961 if (knownPublicAddress_ and igd->getPublicIp() != knownPublicAddress_) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400962 // JAMI_WARN("[%s] IGD external address [%s] does not match known public address [%s]."
963 // " The mapped addresses might not be reachable",
964 // protocolName,
965 // igd->getPublicIp().toString().c_str(),
966 // knownPublicAddress_.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400967 }
968
969 // The IGD was removed or is invalid.
970 if (event == UpnpIgdEvent::REMOVED or event == UpnpIgdEvent::INVALID_STATE) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400971 // JAMI_WARN("State of IGD [%s %s] [%s] changed to [%s]. Pruning the mapping list",
972 // igd->getUID().c_str(),
973 // igd->toString().c_str(),
974 // protocolName,
975 // IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400976
977 pruneMappingsWithInvalidIgds(igd);
978
979 std::lock_guard<std::mutex> lock(mappingMutex_);
980 validIgdList_.erase(igd);
981 return;
982 }
983
984 // Update the IGD list.
985 {
986 std::lock_guard<std::mutex> lock(mappingMutex_);
987 auto ret = validIgdList_.emplace(igd);
988 if (ret.second) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400989 // JAMI_DBG("IGD [%s] on address %s was added. Will process any pending requests",
990 // protocolName,
991 // igdLocalAddr.toString(true, true).c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400992 } else {
993 // Already in the list.
Morteza Namvar5f639522023-07-04 17:08:58 -0400994 // JAMI_ERR("IGD [%s] on address %s already in the list",
995 // protocolName,
996 // igdLocalAddr.toString(true, true).c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400997 return;
998 }
999 }
1000
1001 // Update the provisionned mappings.
1002 updateMappingList(false);
1003}
1004
1005void
1006UPnPContext::onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1007{
1008 CHECK_VALID_THREAD();
1009
1010 // Check if we have a pending request for this response.
1011 auto map = getMappingWithKey(mapRes.getMapKey());
1012 if (not map) {
1013 // We may receive a response for a canceled request. Just ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001014 // JAMI_DBG("Response for mapping %s [IGD %s] [%s] does not have a local match",
1015 // mapRes.toString().c_str(),
1016 // igd->toString().c_str(),
1017 // mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001018 return;
1019 }
1020
1021 // The mapping request is new and successful. Update.
1022 map->setIgd(igd);
1023 map->setInternalAddress(mapRes.getInternalAddress());
1024 map->setExternalPort(mapRes.getExternalPort());
1025
1026 // Update the state and report to the owner.
1027 updateMappingState(map, MappingState::OPEN);
1028
Morteza Namvar5f639522023-07-04 17:08:58 -04001029 // JAMI_DBG("Mapping %s (on IGD %s [%s]) successfully performed",
1030 // map->toString().c_str(),
1031 // igd->toString().c_str(),
1032 // map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001033
1034 // Call setValid() to reset the errors counter. We need
1035 // to reset the counter on each successful response.
1036 igd->setValid(true);
1037}
1038
1039#if HAVE_LIBNATPMP
1040void
1041UPnPContext::onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map)
1042{
1043 auto mapPtr = getMappingWithKey(map.getMapKey());
1044
1045 if (not mapPtr) {
1046 // We may receive a notification for a canceled request. Ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001047 // JAMI_WARN("Renewed mapping %s from IGD %s [%s] does not have a match in local list",
1048 // map.toString().c_str(),
1049 // igd->toString().c_str(),
1050 // map.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001051 return;
1052 }
1053 if (mapPtr->getProtocol() != NatProtocolType::NAT_PMP or not mapPtr->isValid()
1054 or mapPtr->getState() != MappingState::OPEN) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001055 // JAMI_WARN("Renewed mapping %s from IGD %s [%s] is in unexpected state",
1056 // mapPtr->toString().c_str(),
1057 // igd->toString().c_str(),
1058 // mapPtr->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001059 return;
1060 }
1061
1062 mapPtr->setRenewalTime(map.getRenewalTime());
1063}
1064#endif
1065
1066void
1067UPnPContext::requestRemoveMapping(const Mapping::sharedPtr_t& map)
1068{
1069 CHECK_VALID_THREAD();
1070
1071 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001072 // JAMI_ERR("Mapping shared pointer is null!");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001073 return;
1074 }
1075
1076 if (not map->isValid()) {
1077 // Silently ignore if the mapping is invalid
1078 return;
1079 }
1080
1081 auto protocol = protocolList_.at(map->getIgd()->getProtocol());
1082 protocol->requestMappingRemove(*map);
1083}
1084
1085void
1086UPnPContext::deleteAllMappings(PortType type)
1087{
1088 if (not isValidThread()) {
1089 runOnUpnpContextQueue([this, type] { deleteAllMappings(type); });
1090 return;
1091 }
1092
1093 std::lock_guard<std::mutex> lock(mappingMutex_);
1094 auto& mappingList = getMappingList(type);
1095
1096 for (auto const& [_, map] : mappingList) {
1097 requestRemoveMapping(map);
1098 }
1099}
1100
1101void
1102UPnPContext::onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1103{
1104 if (not mapRes.isValid())
1105 return;
1106
1107 if (not isValidThread()) {
1108 runOnUpnpContextQueue([this, igd, mapRes] { onMappingRemoved(igd, mapRes); });
1109 return;
1110 }
1111
1112 auto map = getMappingWithKey(mapRes.getMapKey());
1113 // Notify the listener.
1114 if (map and map->getNotifyCallback())
1115 map->getNotifyCallback()(map);
1116}
1117
1118Mapping::sharedPtr_t
1119UPnPContext::registerMapping(Mapping& map)
1120{
1121 if (map.getExternalPort() == 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001122 // JAMI_DBG("Port number not set. Will set a random port number");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001123 auto port = getAvailablePortNumber(map.getType());
1124 map.setExternalPort(port);
1125 map.setInternalPort(port);
1126 }
1127
1128 // Newly added mapping must be in pending state by default.
1129 map.setState(MappingState::PENDING);
1130
1131 Mapping::sharedPtr_t mapPtr;
1132
1133 {
1134 std::lock_guard<std::mutex> lock(mappingMutex_);
1135 auto& mappingList = getMappingList(map.getType());
1136
1137 auto ret = mappingList.emplace(map.getMapKey(), std::make_shared<Mapping>(map));
1138 if (not ret.second) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001139 // JAMI_WARN("Mapping request for %s already added!", map.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001140 return {};
1141 }
1142 mapPtr = ret.first->second;
1143 assert(mapPtr);
1144 }
1145
1146 // No available IGD. The pending mapping requests will be processed
1147 // when a IGD becomes available (in onIgdAdded() method).
1148 if (not isReady()) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001149 // JAMI_WARN("No IGD available. Mapping will be requested when an IGD becomes available");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001150 } else {
1151 requestMapping(mapPtr);
1152 }
1153
1154 return mapPtr;
1155}
1156
1157std::map<Mapping::key_t, Mapping::sharedPtr_t>::iterator
1158UPnPContext::unregisterMapping(std::map<Mapping::key_t, Mapping::sharedPtr_t>::iterator it)
1159{
1160 assert(it->second);
1161
1162 CHECK_VALID_THREAD();
1163 auto descr = it->second->toString();
1164 auto& mappingList = getMappingList(it->second->getType());
1165 auto ret = mappingList.erase(it);
1166
1167 return ret;
1168}
1169
1170void
1171UPnPContext::unregisterMapping(const Mapping::sharedPtr_t& map)
1172{
1173 CHECK_VALID_THREAD();
1174
1175 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001176 // JAMI_ERR("Mapping pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001177 return;
1178 }
1179
1180 if (map->getAutoUpdate()) {
1181 // Dont unregister mappings with auto-update enabled.
1182 return;
1183 }
1184 auto& mappingList = getMappingList(map->getType());
1185
1186 if (mappingList.erase(map->getMapKey()) == 1) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001187 // JAMI_DBG("Unregistered mapping %s", map->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001188 } else {
1189 // The mapping may already be un-registered. Just ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001190 // JAMI_DBG("Mapping %s [%s] does not have a local match",
1191 // map->toString().c_str(),
1192 // map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001193 }
1194}
1195
1196std::map<Mapping::key_t, Mapping::sharedPtr_t>&
1197UPnPContext::getMappingList(PortType type)
1198{
1199 unsigned typeIdx = type == PortType::TCP ? 0 : 1;
1200 return mappingList_[typeIdx];
1201}
1202
1203Mapping::sharedPtr_t
1204UPnPContext::getMappingWithKey(Mapping::key_t key)
1205{
1206 std::lock_guard<std::mutex> lock(mappingMutex_);
1207 auto const& mappingList = getMappingList(Mapping::getTypeFromMapKey(key));
1208 auto it = mappingList.find(key);
1209 if (it == mappingList.end())
1210 return nullptr;
1211 return it->second;
1212}
1213
1214void
1215UPnPContext::getMappingStatus(PortType type, MappingStatus& status)
1216{
1217 std::lock_guard<std::mutex> lock(mappingMutex_);
1218 auto& mappingList = getMappingList(type);
1219
1220 for (auto const& [_, map] : mappingList) {
1221 switch (map->getState()) {
1222 case MappingState::PENDING: {
1223 status.pendingCount_++;
1224 break;
1225 }
1226 case MappingState::IN_PROGRESS: {
1227 status.inProgressCount_++;
1228 break;
1229 }
1230 case MappingState::FAILED: {
1231 status.failedCount_++;
1232 break;
1233 }
1234 case MappingState::OPEN: {
1235 status.openCount_++;
1236 if (map->isAvailable())
1237 status.readyCount_++;
1238 break;
1239 }
1240
1241 default:
1242 // Must not get here.
1243 assert(false);
1244 break;
1245 }
1246 }
1247}
1248
1249void
1250UPnPContext::getMappingStatus(MappingStatus& status)
1251{
1252 getMappingStatus(PortType::TCP, status);
1253 getMappingStatus(PortType::UDP, status);
1254}
1255
1256void
1257UPnPContext::onMappingRequestFailed(const Mapping& mapRes)
1258{
1259 CHECK_VALID_THREAD();
1260
1261 auto const& map = getMappingWithKey(mapRes.getMapKey());
1262 if (not map) {
1263 // We may receive a response for a removed request. Just ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001264 // JAMI_DBG("Mapping %s [IGD %s] does not have a local match",
1265 // mapRes.toString().c_str(),
1266 // mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001267 return;
1268 }
1269
1270 auto igd = map->getIgd();
1271 if (not igd) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001272 // JAMI_ERR("IGD pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001273 return;
1274 }
1275
1276 updateMappingState(map, MappingState::FAILED);
1277 unregisterMapping(map);
1278
Morteza Namvar5f639522023-07-04 17:08:58 -04001279 // JAMI_WARN("Mapping request for %s failed on IGD %s [%s]",
1280 // map->toString().c_str(),
1281 // igd->toString().c_str(),
1282 // igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001283}
1284
1285void
1286UPnPContext::updateMappingState(const Mapping::sharedPtr_t& map, MappingState newState, bool notify)
1287{
1288 CHECK_VALID_THREAD();
1289
1290 assert(map);
1291
1292 // Ignore if the state did not change.
1293 if (newState == map->getState()) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001294 // JAMI_DBG("Mapping %s already in state %s", map->toString().c_str(), map->getStateStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001295 return;
1296 }
1297
1298 // Update the state.
1299 map->setState(newState);
1300
1301 // Notify the listener if set.
1302 if (notify and map->getNotifyCallback())
1303 map->getNotifyCallback()(map);
1304}
1305
1306#if HAVE_LIBNATPMP
1307void
1308UPnPContext::renewAllocations()
1309{
1310 CHECK_VALID_THREAD();
1311
1312 // Check if the we have valid PMP IGD.
1313 auto pmpProto = protocolList_.at(NatProtocolType::NAT_PMP);
1314
1315 auto now = sys_clock::now();
1316 std::vector<Mapping::sharedPtr_t> toRenew;
1317
1318 for (auto type : {PortType::TCP, PortType::UDP}) {
1319 std::lock_guard<std::mutex> lock(mappingMutex_);
1320 auto mappingList = getMappingList(type);
1321 for (auto const& [_, map] : mappingList) {
1322 if (not map->isValid())
1323 continue;
1324 if (map->getProtocol() != NatProtocolType::NAT_PMP)
1325 continue;
1326 if (map->getState() != MappingState::OPEN)
1327 continue;
1328 if (now < map->getRenewalTime())
1329 continue;
1330
1331 toRenew.emplace_back(map);
1332 }
1333 }
1334
1335 // Quit if there are no mapping to renew
1336 if (toRenew.empty())
1337 return;
1338
1339 for (auto const& map : toRenew) {
1340 pmpProto->requestMappingRenew(*map);
1341 }
1342}
1343#endif
1344
1345} // namespace upnp
1346} // namespace jami