blob: 35fdb50b26873a6ca8889d9c8e56809a4d39518a [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"
Adrien Béraud25c30c42023-07-05 13:46:54 -040024#include "protocol/upnp_protocol.h"
25
Morteza Namvar5f639522023-07-04 17:08:58 -040026#include <asio/steady_timer.hpp>
Adrien Béraud25c30c42023-07-05 13:46:54 -040027#include <fmt/std.h>
Adrien Béraud612b55b2023-05-29 10:42:04 -040028
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040029namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040030namespace upnp {
31
32constexpr static auto MAP_UPDATE_INTERVAL = std::chrono::seconds(30);
33constexpr static int MAX_REQUEST_RETRIES = 20;
34constexpr static int MAX_REQUEST_REMOVE_COUNT = 5;
35
36constexpr static uint16_t UPNP_TCP_PORT_MIN {10000};
37constexpr static uint16_t UPNP_TCP_PORT_MAX {UPNP_TCP_PORT_MIN + 5000};
38constexpr static uint16_t UPNP_UDP_PORT_MIN {20000};
39constexpr static uint16_t UPNP_UDP_PORT_MAX {UPNP_UDP_PORT_MIN + 5000};
40
Adrien Béraud25c30c42023-07-05 13:46:54 -040041UPnPContext::UPnPContext(std::shared_ptr<asio::io_context> ctx, std::shared_ptr<dht::log::Logger> logger)
42 : mappingListUpdateTimer_(*ioContext)
Adrien Béraud612b55b2023-05-29 10:42:04 -040043{
Morteza Namvar5f639522023-07-04 17:08:58 -040044 // JAMI_DBG("Creating UPnPContext instance [%p]", this);
Adrien Béraud612b55b2023-05-29 10:42:04 -040045
46 // Set port ranges
47 portRange_.emplace(PortType::TCP, std::make_pair(UPNP_TCP_PORT_MIN, UPNP_TCP_PORT_MAX));
48 portRange_.emplace(PortType::UDP, std::make_pair(UPNP_UDP_PORT_MIN, UPNP_UDP_PORT_MAX));
49
Adrien Béraud25c30c42023-07-05 13:46:54 -040050 ioContext->post([this] { init(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -040051}
52
Adrien Béraud25c30c42023-07-05 13:46:54 -040053/*std::shared_ptr<UPnPContext>
Adrien Béraud612b55b2023-05-29 10:42:04 -040054UPnPContext::getUPnPContext()
55{
56 // This is the unique shared instance (singleton) of UPnPContext class.
57 static auto context = std::make_shared<UPnPContext>();
58 return context;
Adrien Béraud25c30c42023-07-05 13:46:54 -040059}*/
Adrien Béraud612b55b2023-05-29 10:42:04 -040060
61void
62UPnPContext::shutdown(std::condition_variable& cv)
63{
Morteza Namvar5f639522023-07-04 17:08:58 -040064 // JAMI_DBG("Shutdown UPnPContext instance [%p]", this);
Adrien Béraud612b55b2023-05-29 10:42:04 -040065
66 stopUpnp(true);
67
68 for (auto const& [_, proto] : protocolList_) {
69 proto->terminate();
70 }
71
72 {
73 std::lock_guard<std::mutex> lock(mappingMutex_);
74 mappingList_->clear();
Adrien Béraud25c30c42023-07-05 13:46:54 -040075 //if (mappingListUpdateTimer_)
76 // mappingListUpdateTimer_->cancel();
77 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -040078 controllerList_.clear();
79 protocolList_.clear();
80 shutdownComplete_ = true;
81 cv.notify_one();
82 }
83}
84
85void
86UPnPContext::shutdown()
87{
88 std::unique_lock<std::mutex> lk(mappingMutex_);
89 std::condition_variable cv;
90
91 runOnUpnpContextQueue([&, this] { shutdown(cv); });
92
Morteza Namvar5f639522023-07-04 17:08:58 -040093 // JAMI_DBG("Waiting for shutdown ...");
Adrien Béraud612b55b2023-05-29 10:42:04 -040094
95 if (cv.wait_for(lk, std::chrono::seconds(30), [this] { return shutdownComplete_; })) {
Morteza Namvar5f639522023-07-04 17:08:58 -040096 // JAMI_DBG("Shutdown completed");
Adrien Béraud612b55b2023-05-29 10:42:04 -040097 } else {
Morteza Namvar5f639522023-07-04 17:08:58 -040098 // JAMI_ERR("Shutdown timed-out");
Adrien Béraud612b55b2023-05-29 10:42:04 -040099 }
100}
101
102UPnPContext::~UPnPContext()
103{
Morteza Namvar5f639522023-07-04 17:08:58 -0400104 // JAMI_DBG("UPnPContext instance [%p] destroyed", this);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400105}
106
107void
108UPnPContext::init()
109{
110 threadId_ = getCurrentThread();
111 CHECK_VALID_THREAD();
112
113#if HAVE_LIBNATPMP
114 auto natPmp = std::make_shared<NatPmp>();
115 natPmp->setObserver(this);
116 protocolList_.emplace(NatProtocolType::NAT_PMP, std::move(natPmp));
117#endif
118
119#if HAVE_LIBUPNP
120 auto pupnp = std::make_shared<PUPnP>();
121 pupnp->setObserver(this);
122 protocolList_.emplace(NatProtocolType::PUPNP, std::move(pupnp));
123#endif
124}
125
126void
127UPnPContext::startUpnp()
128{
129 assert(not controllerList_.empty());
130
131 CHECK_VALID_THREAD();
132
Morteza Namvar5f639522023-07-04 17:08:58 -0400133 // JAMI_DBG("Starting UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400134
135 // Request a new IGD search.
136 for (auto const& [_, protocol] : protocolList_) {
137 protocol->searchForIgd();
138 }
139
140 started_ = true;
141}
142
143void
144UPnPContext::stopUpnp(bool forceRelease)
145{
146 if (not isValidThread()) {
147 runOnUpnpContextQueue([this, forceRelease] { stopUpnp(forceRelease); });
148 return;
149 }
150
Morteza Namvar5f639522023-07-04 17:08:58 -0400151 // JAMI_DBG("Stopping UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400152
153 // Clear all current mappings if any.
154
155 // Use a temporary list to avoid processing the mapping
156 // list while holding the lock.
157 std::list<Mapping::sharedPtr_t> toRemoveList;
158 {
159 std::lock_guard<std::mutex> lock(mappingMutex_);
160
161 PortType types[2] {PortType::TCP, PortType::UDP};
162 for (auto& type : types) {
163 auto& mappingList = getMappingList(type);
164 for (auto const& [_, map] : mappingList) {
165 toRemoveList.emplace_back(map);
166 }
167 }
168 // Invalidate the current IGDs.
169 preferredIgd_.reset();
170 validIgdList_.clear();
171 }
172 for (auto const& map : toRemoveList) {
173 requestRemoveMapping(map);
174
175 // Notify is not needed in updateMappingState when
176 // shutting down (hence set it to false). NotifyCallback
177 // would trigger a new SIP registration and create a
178 // false registered state upon program close.
179 // It's handled by upper layers.
180
181 updateMappingState(map, MappingState::FAILED, false);
182 // We dont remove mappings with auto-update enabled,
183 // unless forceRelease is true.
184 if (not map->getAutoUpdate() or forceRelease) {
185 map->enableAutoUpdate(false);
186 unregisterMapping(map);
187 }
188 }
189
190 // Clear all current IGDs.
191 for (auto const& [_, protocol] : protocolList_) {
192 protocol->clearIgds();
193 }
194
195 started_ = false;
196}
197
198uint16_t
199UPnPContext::generateRandomPort(PortType type, bool mustBeEven)
200{
201 auto minPort = type == PortType::TCP ? UPNP_TCP_PORT_MIN : UPNP_UDP_PORT_MIN;
202 auto maxPort = type == PortType::TCP ? UPNP_TCP_PORT_MAX : UPNP_UDP_PORT_MAX;
203
204 if (minPort >= maxPort) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400205 // JAMI_ERR("Max port number (%i) must be greater than min port number (%i)", maxPort, minPort);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400206 // Must be called with valid range.
207 assert(false);
208 }
209
210 int fact = mustBeEven ? 2 : 1;
211 if (mustBeEven) {
212 minPort /= fact;
213 maxPort /= fact;
214 }
215
216 // Seed the generator.
217 static std::mt19937 gen(dht::crypto::getSeededRandomEngine());
218 // Define the range.
219 std::uniform_int_distribution<uint16_t> dist(minPort, maxPort);
220 return dist(gen) * fact;
221}
222
223void
224UPnPContext::connectivityChanged()
225{
226 if (not isValidThread()) {
227 runOnUpnpContextQueue([this] { connectivityChanged(); });
228 return;
229 }
230
231 auto hostAddr = ip_utils::getLocalAddr(AF_INET);
232
Morteza Namvar5f639522023-07-04 17:08:58 -0400233 // JAMI_DBG("Connectivity change check: host address %s", hostAddr.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400234
235 auto restartUpnp = false;
236
237 // On reception of "connectivity change" notification, the UPNP search
238 // will be restarted if either there is no valid IGD, or the IGD address
239 // changed.
240
241 if (not isReady()) {
242 restartUpnp = true;
243 } else {
244 // Check if the host address changed.
245 for (auto const& [_, protocol] : protocolList_) {
246 if (protocol->isReady() and hostAddr != protocol->getHostAddress()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400247 // JAMI_WARN("Host address changed from %s to %s",
248 // protocol->getHostAddress().toString().c_str(),
249 // hostAddr.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400250 protocol->clearIgds();
251 restartUpnp = true;
252 break;
253 }
254 }
255 }
256
257 // We have at least one valid IGD and the host address did
258 // not change, so no need to restart.
259 if (not restartUpnp) {
260 return;
261 }
262
263 // No registered controller. A new search will be performed when
264 // a controller is registered.
265 if (controllerList_.empty())
266 return;
267
Morteza Namvar5f639522023-07-04 17:08:58 -0400268 // JAMI_DBG("Connectivity changed. Clear the IGDs and restart");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400269
270 stopUpnp();
271 startUpnp();
272
273 // Mapping with auto update enabled must be processed first.
274 processMappingWithAutoUpdate();
275}
276
277void
278UPnPContext::setPublicAddress(const IpAddr& addr)
279{
280 if (not addr)
281 return;
282
283 std::lock_guard<std::mutex> lock(mappingMutex_);
284 if (knownPublicAddress_ != addr) {
285 knownPublicAddress_ = std::move(addr);
Morteza Namvar5f639522023-07-04 17:08:58 -0400286 // JAMI_DBG("Setting the known public address to %s", addr.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400287 }
288}
289
290bool
291UPnPContext::isReady() const
292{
293 std::lock_guard<std::mutex> lock(mappingMutex_);
294 return not validIgdList_.empty();
295}
296
297IpAddr
298UPnPContext::getExternalIP() const
299{
300 std::lock_guard<std::mutex> lock(mappingMutex_);
301 // Return the first IGD Ip available.
302 if (not validIgdList_.empty()) {
303 return (*validIgdList_.begin())->getPublicIp();
304 }
305 return {};
306}
307
308Mapping::sharedPtr_t
309UPnPContext::reserveMapping(Mapping& requestedMap)
310{
311 auto desiredPort = requestedMap.getExternalPort();
312
313 if (desiredPort == 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400314 // JAMI_DBG("Desired port is not set, will provide the first available port for [%s]",
Adrien Béraud25c30c42023-07-05 13:46:54 -0400315 // requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400316 } else {
Morteza Namvar5f639522023-07-04 17:08:58 -0400317 // JAMI_DBG("Try to find mapping for port %i [%s]", desiredPort, requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400318 }
319
320 Mapping::sharedPtr_t mapRes;
321
322 {
323 std::lock_guard<std::mutex> lock(mappingMutex_);
324 auto& mappingList = getMappingList(requestedMap.getType());
325
326 // We try to provide a mapping in "OPEN" state. If not found,
327 // we provide any available mapping. In this case, it's up to
328 // the caller to use it or not.
329 for (auto const& [_, map] : mappingList) {
330 // If the desired port is null, we pick the first available port.
331 if (map->isValid() and (desiredPort == 0 or map->getExternalPort() == desiredPort)
332 and map->isAvailable()) {
333 // Considere the first available mapping regardless of its
334 // state. A mapping with OPEN state will be used if found.
335 if (not mapRes)
336 mapRes = map;
337
338 if (map->getState() == MappingState::OPEN) {
339 // Found an "OPEN" mapping. We are done.
340 mapRes = map;
341 break;
342 }
343 }
344 }
345 }
346
347 // Create a mapping if none was available.
348 if (not mapRes) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400349 // JAMI_WARN("Did not find any available mapping. Will request one now");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400350 mapRes = registerMapping(requestedMap);
351 }
352
353 if (mapRes) {
354 // Make the mapping unavailable
355 mapRes->setAvailable(false);
356 // Copy attributes.
357 mapRes->setNotifyCallback(requestedMap.getNotifyCallback());
358 mapRes->enableAutoUpdate(requestedMap.getAutoUpdate());
359 // Notify the listener.
360 if (auto cb = mapRes->getNotifyCallback())
361 cb(mapRes);
362 }
363
364 updateMappingList(true);
365
366 return mapRes;
367}
368
369void
370UPnPContext::releaseMapping(const Mapping& map)
371{
372 if (not isValidThread()) {
373 runOnUpnpContextQueue([this, map] { releaseMapping(map); });
374 return;
375 }
376
377 auto mapPtr = getMappingWithKey(map.getMapKey());
378
379 if (not mapPtr) {
380 // Might happen if the mapping failed or was never granted.
Morteza Namvar5f639522023-07-04 17:08:58 -0400381 // JAMI_DBG("Mapping %s does not exist or was already removed", map.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400382 return;
383 }
384
385 if (mapPtr->isAvailable()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400386 // JAMI_WARN("Trying to release an unused mapping %s", mapPtr->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400387 return;
388 }
389
390 // Remove it.
391 requestRemoveMapping(mapPtr);
392 unregisterMapping(mapPtr);
393}
394
395void
396UPnPContext::registerController(void* controller)
397{
398 {
399 std::lock_guard<std::mutex> lock(mappingMutex_);
400 if (shutdownComplete_) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400401 // JAMI_WARN("UPnPContext already shut down");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400402 return;
403 }
404 }
405
406 if (not isValidThread()) {
407 runOnUpnpContextQueue([this, controller] { registerController(controller); });
408 return;
409 }
410
411 auto ret = controllerList_.emplace(controller);
412 if (not ret.second) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400413 // JAMI_WARN("Controller %p is already registered", controller);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400414 return;
415 }
416
Morteza Namvar5f639522023-07-04 17:08:58 -0400417 // JAMI_DBG("Successfully registered controller %p", controller);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400418 if (not started_)
419 startUpnp();
420}
421
422void
423UPnPContext::unregisterController(void* controller)
424{
425 if (not isValidThread()) {
426 runOnUpnpContextQueue([this, controller] { unregisterController(controller); });
427 return;
428 }
429
430 if (controllerList_.erase(controller) == 1) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400431 // JAMI_DBG("Successfully unregistered controller %p", controller);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400432 } else {
Morteza Namvar5f639522023-07-04 17:08:58 -0400433 // JAMI_DBG("Controller %p was already removed", controller);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400434 }
435
436 if (controllerList_.empty()) {
437 stopUpnp();
438 }
439}
440
441uint16_t
442UPnPContext::getAvailablePortNumber(PortType type)
443{
444 // Only return an availalable random port. No actual
445 // reservation is made here.
446
447 std::lock_guard<std::mutex> lock(mappingMutex_);
448 auto& mappingList = getMappingList(type);
449 int tryCount = 0;
450 while (tryCount++ < MAX_REQUEST_RETRIES) {
451 uint16_t port = generateRandomPort(type);
452 Mapping map(type, port, port);
453 if (mappingList.find(map.getMapKey()) == mappingList.end())
454 return port;
455 }
456
457 // Very unlikely to get here.
Morteza Namvar5f639522023-07-04 17:08:58 -0400458 // JAMI_ERR("Could not find an available port after %i trials", MAX_REQUEST_RETRIES);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400459 return 0;
460}
461
462void
463UPnPContext::requestMapping(const Mapping::sharedPtr_t& map)
464{
465 assert(map);
466
467 if (not isValidThread()) {
468 runOnUpnpContextQueue([this, map] { requestMapping(map); });
469 return;
470 }
471
472 auto const& igd = getPreferredIgd();
473 // We must have at least a valid IGD pointer if we get here.
474 // Not this method is called only if there were a valid IGD, however,
475 // because the processing is asynchronous, it's possible that the IGD
476 // was invalidated when the this code executed.
477 if (not igd) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400478 // JAMI_DBG("No valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400479 return;
480 }
481
482 map->setIgd(igd);
483
Morteza Namvar5f639522023-07-04 17:08:58 -0400484 // JAMI_DBG("Request mapping %s using protocol [%s] IGD [%s]",
485 // map->toString().c_str(),
486 // igd->getProtocolName(),
487 // igd->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400488
489 if (map->getState() != MappingState::IN_PROGRESS)
490 updateMappingState(map, MappingState::IN_PROGRESS);
491
492 auto const& protocol = protocolList_.at(igd->getProtocol());
493 protocol->requestMappingAdd(*map);
494}
495
496bool
497UPnPContext::provisionNewMappings(PortType type, int portCount)
498{
Morteza Namvar5f639522023-07-04 17:08:58 -0400499 // JAMI_DBG("Provision %i new mappings of type [%s]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400500
501 assert(portCount > 0);
502
503 while (portCount > 0) {
504 auto port = getAvailablePortNumber(type);
505 if (port > 0) {
506 // Found an available port number
507 portCount--;
508 Mapping map(type, port, port, true);
509 registerMapping(map);
510 } else {
511 // Very unlikely to get here!
Morteza Namvar5f639522023-07-04 17:08:58 -0400512 // JAMI_ERR("Can not find any available port to provision!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400513 return false;
514 }
515 }
516
517 return true;
518}
519
520bool
521UPnPContext::deleteUnneededMappings(PortType type, int portCount)
522{
Morteza Namvar5f639522023-07-04 17:08:58 -0400523 // JAMI_DBG("Remove %i unneeded mapping of type [%s]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400524
525 assert(portCount > 0);
526
527 CHECK_VALID_THREAD();
528
529 std::lock_guard<std::mutex> lock(mappingMutex_);
530 auto& mappingList = getMappingList(type);
531
532 for (auto it = mappingList.begin(); it != mappingList.end();) {
533 auto map = it->second;
534 assert(map);
535
536 if (not map->isAvailable()) {
537 it++;
538 continue;
539 }
540
541 if (map->getState() == MappingState::OPEN and portCount > 0) {
542 // Close portCount mappings in "OPEN" state.
543 requestRemoveMapping(map);
544 it = unregisterMapping(it);
545 portCount--;
546 } else if (map->getState() != MappingState::OPEN) {
547 // If this methods is called, it means there are more open
548 // mappings than required. So, all mappings in a state other
549 // than "OPEN" state (typically in in-progress state) will
550 // be deleted as well.
551 it = unregisterMapping(it);
552 } else {
553 it++;
554 }
555 }
556
557 return true;
558}
559
560void
561UPnPContext::updatePreferredIgd()
562{
563 CHECK_VALID_THREAD();
564
565 if (preferredIgd_ and preferredIgd_->isValid())
566 return;
567
568 // Reset and search for the best IGD.
569 preferredIgd_.reset();
570
571 for (auto const& [_, protocol] : protocolList_) {
572 if (protocol->isReady()) {
573 auto igdList = protocol->getIgdList();
574 assert(not igdList.empty());
575 auto const& igd = igdList.front();
576 if (not igd->isValid())
577 continue;
578
579 // Prefer NAT-PMP over PUPNP.
580 if (preferredIgd_ and igd->getProtocol() != NatProtocolType::NAT_PMP)
581 continue;
582
583 // Update.
584 preferredIgd_ = igd;
585 }
586 }
587
588 if (preferredIgd_ and preferredIgd_->isValid()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400589 // JAMI_DBG("Preferred IGD updated to [%s] IGD [%s %s] ",
590 // preferredIgd_->getProtocolName(),
591 // preferredIgd_->getUID().c_str(),
592 // preferredIgd_->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400593 }
594}
595
596std::shared_ptr<IGD>
597UPnPContext::getPreferredIgd() const
598{
599 CHECK_VALID_THREAD();
600
601 return preferredIgd_;
602}
603
604void
605UPnPContext::updateMappingList(bool async)
606{
607 // Run async if requested.
608 if (async) {
609 runOnUpnpContextQueue([this] { updateMappingList(false); });
610 return;
611 }
612
613 CHECK_VALID_THREAD();
614
615 // Update the preferred IGD.
616 updatePreferredIgd();
617
Adrien Béraud25c30c42023-07-05 13:46:54 -0400618 /*if (mappingListUpdateTimer_) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400619 mappingListUpdateTimer_->cancel();
620 mappingListUpdateTimer_ = {};
Adrien Béraud25c30c42023-07-05 13:46:54 -0400621 }*/
622 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400623
624 // Skip if no controller registered.
625 if (controllerList_.empty())
626 return;
627
628 // Cancel the current timer (if any) and re-schedule.
629 std::shared_ptr<IGD> prefIgd = getPreferredIgd();
630 if (not prefIgd) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400631 // JAMI_DBG("UPNP/NAT-PMP enabled, but no valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400632 // No valid IGD. Nothing to do.
633 return;
634 }
635
Adrien Béraud25c30c42023-07-05 13:46:54 -0400636 /*mappingListUpdateTimer_ = getScheduler()->scheduleIn([this] { updateMappingList(false); },
637 MAP_UPDATE_INTERVAL);*/
638 mappingListUpdateTimer_.expires_from_now(MAP_UPDATE_INTERVAL);
639 mappingListUpdateTimer_.async_wait([this](asio::error_code const& ec) {
640 if (ec != asio::error::operation_aborted)
641 updateMappingList(false);
642 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400643
644 // Process pending requests if any.
645 processPendingRequests(prefIgd);
646
647 // Make new requests for mappings that failed and have
648 // the auto-update option enabled.
649 processMappingWithAutoUpdate();
650
651 PortType typeArray[2] = {PortType::TCP, PortType::UDP};
652
653 for (auto idx : {0, 1}) {
654 auto type = typeArray[idx];
655
656 MappingStatus status;
657 getMappingStatus(type, status);
658
Morteza Namvar5f639522023-07-04 17:08:58 -0400659 // JAMI_DBG("Mapping status [%s] - overall %i: %i open (%i ready + %i in use), %i pending, %i "
660 // "in-progress, %i failed",
661 // Mapping::getTypeStr(type),
662 // status.sum(),
663 // status.openCount_,
664 // status.readyCount_,
665 // status.openCount_ - status.readyCount_,
666 // status.pendingCount_,
667 // status.inProgressCount_,
668 // status.failedCount_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400669
670 if (status.failedCount_ > 0) {
671 std::lock_guard<std::mutex> lock(mappingMutex_);
672 auto const& mappingList = getMappingList(type);
673 for (auto const& [_, map] : mappingList) {
674 if (map->getState() == MappingState::FAILED) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400675 // JAMI_DBG("Mapping status [%s] - Available [%s]",
676 // map->toString(true).c_str(),
677 // map->isAvailable() ? "YES" : "NO");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400678 }
679 }
680 }
681
682 int toRequestCount = (int) minOpenPortLimit_[idx]
683 - (int) (status.readyCount_ + status.inProgressCount_
684 + status.pendingCount_);
685
686 // Provision/release mappings accordingly.
687 if (toRequestCount > 0) {
688 // Take into account the request in-progress when making
689 // requests for new mappings.
690 provisionNewMappings(type, toRequestCount);
691 } else if (status.readyCount_ > maxOpenPortLimit_[idx]) {
692 deleteUnneededMappings(type, status.readyCount_ - maxOpenPortLimit_[idx]);
693 }
694 }
695
696 // Prune the mapping list if needed
697 if (protocolList_.at(NatProtocolType::PUPNP)->isReady()) {
698#if HAVE_LIBNATPMP
699 // Dont perform if NAT-PMP is valid.
700 if (not protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
701#endif
702 {
703 pruneMappingList();
704 }
705 }
706
707#if HAVE_LIBNATPMP
708 // Renew nat-pmp allocations
709 if (protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
710 renewAllocations();
711#endif
712}
713
714void
715UPnPContext::pruneMappingList()
716{
717 CHECK_VALID_THREAD();
718
719 MappingStatus status;
720 getMappingStatus(status);
721
722 // Do not prune the list if there are pending/in-progress requests.
723 if (status.inProgressCount_ != 0 or status.pendingCount_ != 0) {
724 return;
725 }
726
727 auto const& igd = getPreferredIgd();
728 if (not igd or igd->getProtocol() != NatProtocolType::PUPNP) {
729 return;
730 }
731 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
732
733 auto remoteMapList = protocol->getMappingsListByDescr(igd,
734 Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
735 if (remoteMapList.empty()) {
736 std::lock_guard<std::mutex> lock(mappingMutex_);
737 if (not getMappingList(PortType::TCP).empty() or getMappingList(PortType::TCP).empty()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400738 // JAMI_WARN("We have provisionned mappings but the PUPNP IGD returned an empty list!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400739 }
740 }
741
742 pruneUnMatchedMappings(igd, remoteMapList);
743 pruneUnTrackedMappings(igd, remoteMapList);
744}
745
746void
747UPnPContext::pruneUnMatchedMappings(const std::shared_ptr<IGD>& igd,
748 const std::map<Mapping::key_t, Mapping>& remoteMapList)
749{
750 // Check/synchronize local mapping list with the list
751 // returned by the IGD.
752
753 PortType types[2] {PortType::TCP, PortType::UDP};
754
755 for (auto& type : types) {
756 // Use a temporary list to avoid processing mappings while holding the lock.
757 std::list<Mapping::sharedPtr_t> toRemoveList;
758 {
759 std::lock_guard<std::mutex> lock(mappingMutex_);
760 auto& mappingList = getMappingList(type);
761 for (auto const& [_, map] : mappingList) {
762 // Only check mappings allocated by UPNP protocol.
763 if (map->getProtocol() != NatProtocolType::PUPNP) {
764 continue;
765 }
766 // Set mapping as failed if not found in the list
767 // returned by the IGD.
768 if (map->getState() == MappingState::OPEN
769 and remoteMapList.find(map->getMapKey()) == remoteMapList.end()) {
770 toRemoveList.emplace_back(map);
771
Morteza Namvar5f639522023-07-04 17:08:58 -0400772 // JAMI_WARN("Mapping %s (IGD %s) marked as \"OPEN\" but not found in the "
773 // "remote list. Mark as failed!",
774 // map->toString().c_str(),
775 // igd->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400776 }
777 }
778 }
779
780 for (auto const& map : toRemoveList) {
781 updateMappingState(map, MappingState::FAILED);
782 unregisterMapping(map);
783 }
784 }
785}
786
787void
788UPnPContext::pruneUnTrackedMappings(const std::shared_ptr<IGD>& igd,
789 const std::map<Mapping::key_t, Mapping>& remoteMapList)
790{
791 // Use a temporary list to avoid processing mappings while holding the lock.
792 std::list<Mapping> toRemoveList;
793 {
794 std::lock_guard<std::mutex> lock(mappingMutex_);
795
796 for (auto const& [_, map] : remoteMapList) {
797 // Must has valid IGD pointer and use UPNP protocol.
798 assert(map.getIgd());
799 assert(map.getIgd()->getProtocol() == NatProtocolType::PUPNP);
800 auto& mappingList = getMappingList(map.getType());
801 auto it = mappingList.find(map.getMapKey());
802 if (it == mappingList.end()) {
803 // Not present, request mapping remove.
804 toRemoveList.emplace_back(std::move(map));
805 // Make only few remove requests at once.
806 if (toRemoveList.size() >= MAX_REQUEST_REMOVE_COUNT)
807 break;
808 }
809 }
810 }
811
812 // Remove un-tracked mappings.
813 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
814 for (auto const& map : toRemoveList) {
815 protocol->requestMappingRemove(map);
816 }
817}
818
819void
820UPnPContext::pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd)
821{
822 CHECK_VALID_THREAD();
823
824 // Use temporary list to avoid holding the lock while
825 // processing the mapping list.
826 std::list<Mapping::sharedPtr_t> toRemoveList;
827 {
828 std::lock_guard<std::mutex> lock(mappingMutex_);
829
830 PortType types[2] {PortType::TCP, PortType::UDP};
831 for (auto& type : types) {
832 auto& mappingList = getMappingList(type);
833 for (auto const& [_, map] : mappingList) {
834 if (map->getIgd() == igd)
835 toRemoveList.emplace_back(map);
836 }
837 }
838 }
839
840 for (auto const& map : toRemoveList) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400841 // JAMI_DBG("Remove mapping %s (has an invalid IGD %s [%s])",
842 // map->toString().c_str(),
843 // igd->toString().c_str(),
844 // igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400845 updateMappingState(map, MappingState::FAILED);
846 unregisterMapping(map);
847 }
848}
849
850void
851UPnPContext::processPendingRequests(const std::shared_ptr<IGD>& igd)
852{
853 // This list holds the mappings to be requested. This is
854 // needed to avoid performing the requests while holding
855 // the lock.
856 std::list<Mapping::sharedPtr_t> requestsList;
857
858 // Populate the list of requests to perform.
859 {
860 std::lock_guard<std::mutex> lock(mappingMutex_);
861 PortType typeArray[2] {PortType::TCP, PortType::UDP};
862
863 for (auto type : typeArray) {
864 auto& mappingList = getMappingList(type);
865 for (auto& [_, map] : mappingList) {
866 if (map->getState() == MappingState::PENDING) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400867 // JAMI_DBG("Send pending request for mapping %s to IGD %s",
868 // map->toString().c_str(),
869 // igd->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400870 requestsList.emplace_back(map);
871 }
872 }
873 }
874 }
875
876 // Process the pending requests.
877 for (auto const& map : requestsList) {
878 requestMapping(map);
879 }
880}
881
882void
883UPnPContext::processMappingWithAutoUpdate()
884{
885 // This list holds the mappings to be requested. This is
886 // needed to avoid performing the requests while holding
887 // the lock.
888 std::list<Mapping::sharedPtr_t> requestsList;
889
890 // Populate the list of requests for mappings with auto-update enabled.
891 {
892 std::lock_guard<std::mutex> lock(mappingMutex_);
893 PortType typeArray[2] {PortType::TCP, PortType::UDP};
894
895 for (auto type : typeArray) {
896 auto& mappingList = getMappingList(type);
897 for (auto const& [_, map] : mappingList) {
898 if (map->getState() == MappingState::FAILED and map->getAutoUpdate()) {
899 requestsList.emplace_back(map);
900 }
901 }
902 }
903 }
904
905 for (auto const& oldMap : requestsList) {
906 // Request a new mapping if auto-update is enabled.
Morteza Namvar5f639522023-07-04 17:08:58 -0400907 // JAMI_DBG("Mapping %s has auto-update enabled, a new mapping will be requested",
908 // oldMap->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400909
910 // Reserve a new mapping.
911 Mapping newMapping(oldMap->getType());
912 newMapping.enableAutoUpdate(true);
913 newMapping.setNotifyCallback(oldMap->getNotifyCallback());
914
915 auto const& mapPtr = reserveMapping(newMapping);
916 assert(mapPtr);
917
918 // Release the old one.
919 oldMap->setAvailable(true);
920 oldMap->enableAutoUpdate(false);
921 oldMap->setNotifyCallback(nullptr);
922 unregisterMapping(oldMap);
923 }
924}
925
926void
927UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event)
928{
929 assert(igd);
930
931 if (not isValidThread()) {
932 runOnUpnpContextQueue([this, igd, event] { onIgdUpdated(igd, event); });
933 return;
934 }
935
936 // Reset to start search for a new best IGD.
937 preferredIgd_.reset();
938
939 char const* IgdState = event == UpnpIgdEvent::ADDED ? "ADDED"
940 : event == UpnpIgdEvent::REMOVED ? "REMOVED"
941 : "INVALID";
942
943 auto const& igdLocalAddr = igd->getLocalIp();
944 auto protocolName = igd->getProtocolName();
945
Morteza Namvar5f639522023-07-04 17:08:58 -0400946 // JAMI_DBG("New event for IGD [%s %s] [%s]: [%s]",
947 // igd->getUID().c_str(),
948 // igd->toString().c_str(),
949 // protocolName,
950 // IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400951
952 // Check if the IGD has valid addresses.
953 if (not igdLocalAddr) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400954 // JAMI_WARN("[%s] IGD has an invalid local address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400955 return;
956 }
957
958 if (not igd->getPublicIp()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400959 // JAMI_WARN("[%s] IGD has an invalid public address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400960 return;
961 }
962
963 if (knownPublicAddress_ and igd->getPublicIp() != knownPublicAddress_) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400964 // JAMI_WARN("[%s] IGD external address [%s] does not match known public address [%s]."
965 // " The mapped addresses might not be reachable",
966 // protocolName,
967 // igd->getPublicIp().toString().c_str(),
968 // knownPublicAddress_.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400969 }
970
971 // The IGD was removed or is invalid.
972 if (event == UpnpIgdEvent::REMOVED or event == UpnpIgdEvent::INVALID_STATE) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400973 // JAMI_WARN("State of IGD [%s %s] [%s] changed to [%s]. Pruning the mapping list",
974 // igd->getUID().c_str(),
975 // igd->toString().c_str(),
976 // protocolName,
977 // IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400978
979 pruneMappingsWithInvalidIgds(igd);
980
981 std::lock_guard<std::mutex> lock(mappingMutex_);
982 validIgdList_.erase(igd);
983 return;
984 }
985
986 // Update the IGD list.
987 {
988 std::lock_guard<std::mutex> lock(mappingMutex_);
989 auto ret = validIgdList_.emplace(igd);
990 if (ret.second) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400991 // JAMI_DBG("IGD [%s] on address %s was added. Will process any pending requests",
992 // protocolName,
993 // igdLocalAddr.toString(true, true).c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400994 } else {
995 // Already in the list.
Morteza Namvar5f639522023-07-04 17:08:58 -0400996 // JAMI_ERR("IGD [%s] on address %s already in the list",
997 // protocolName,
998 // igdLocalAddr.toString(true, true).c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400999 return;
1000 }
1001 }
1002
1003 // Update the provisionned mappings.
1004 updateMappingList(false);
1005}
1006
1007void
1008UPnPContext::onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1009{
1010 CHECK_VALID_THREAD();
1011
1012 // Check if we have a pending request for this response.
1013 auto map = getMappingWithKey(mapRes.getMapKey());
1014 if (not map) {
1015 // We may receive a response for a canceled request. Just ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001016 // JAMI_DBG("Response for mapping %s [IGD %s] [%s] does not have a local match",
1017 // mapRes.toString().c_str(),
1018 // igd->toString().c_str(),
1019 // mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001020 return;
1021 }
1022
1023 // The mapping request is new and successful. Update.
1024 map->setIgd(igd);
1025 map->setInternalAddress(mapRes.getInternalAddress());
1026 map->setExternalPort(mapRes.getExternalPort());
1027
1028 // Update the state and report to the owner.
1029 updateMappingState(map, MappingState::OPEN);
1030
Morteza Namvar5f639522023-07-04 17:08:58 -04001031 // JAMI_DBG("Mapping %s (on IGD %s [%s]) successfully performed",
1032 // map->toString().c_str(),
1033 // igd->toString().c_str(),
1034 // map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001035
1036 // Call setValid() to reset the errors counter. We need
1037 // to reset the counter on each successful response.
1038 igd->setValid(true);
1039}
1040
1041#if HAVE_LIBNATPMP
1042void
1043UPnPContext::onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map)
1044{
1045 auto mapPtr = getMappingWithKey(map.getMapKey());
1046
1047 if (not mapPtr) {
1048 // We may receive a notification for a canceled request. Ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001049 // JAMI_WARN("Renewed mapping %s from IGD %s [%s] does not have a match in local list",
1050 // map.toString().c_str(),
1051 // igd->toString().c_str(),
1052 // map.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001053 return;
1054 }
1055 if (mapPtr->getProtocol() != NatProtocolType::NAT_PMP or not mapPtr->isValid()
1056 or mapPtr->getState() != MappingState::OPEN) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001057 // JAMI_WARN("Renewed mapping %s from IGD %s [%s] is in unexpected state",
1058 // mapPtr->toString().c_str(),
1059 // igd->toString().c_str(),
1060 // mapPtr->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001061 return;
1062 }
1063
1064 mapPtr->setRenewalTime(map.getRenewalTime());
1065}
1066#endif
1067
1068void
1069UPnPContext::requestRemoveMapping(const Mapping::sharedPtr_t& map)
1070{
1071 CHECK_VALID_THREAD();
1072
1073 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001074 // JAMI_ERR("Mapping shared pointer is null!");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001075 return;
1076 }
1077
1078 if (not map->isValid()) {
1079 // Silently ignore if the mapping is invalid
1080 return;
1081 }
1082
1083 auto protocol = protocolList_.at(map->getIgd()->getProtocol());
1084 protocol->requestMappingRemove(*map);
1085}
1086
1087void
1088UPnPContext::deleteAllMappings(PortType type)
1089{
1090 if (not isValidThread()) {
1091 runOnUpnpContextQueue([this, type] { deleteAllMappings(type); });
1092 return;
1093 }
1094
1095 std::lock_guard<std::mutex> lock(mappingMutex_);
1096 auto& mappingList = getMappingList(type);
1097
1098 for (auto const& [_, map] : mappingList) {
1099 requestRemoveMapping(map);
1100 }
1101}
1102
1103void
1104UPnPContext::onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1105{
1106 if (not mapRes.isValid())
1107 return;
1108
1109 if (not isValidThread()) {
1110 runOnUpnpContextQueue([this, igd, mapRes] { onMappingRemoved(igd, mapRes); });
1111 return;
1112 }
1113
1114 auto map = getMappingWithKey(mapRes.getMapKey());
1115 // Notify the listener.
1116 if (map and map->getNotifyCallback())
1117 map->getNotifyCallback()(map);
1118}
1119
1120Mapping::sharedPtr_t
1121UPnPContext::registerMapping(Mapping& map)
1122{
1123 if (map.getExternalPort() == 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001124 // JAMI_DBG("Port number not set. Will set a random port number");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001125 auto port = getAvailablePortNumber(map.getType());
1126 map.setExternalPort(port);
1127 map.setInternalPort(port);
1128 }
1129
1130 // Newly added mapping must be in pending state by default.
1131 map.setState(MappingState::PENDING);
1132
1133 Mapping::sharedPtr_t mapPtr;
1134
1135 {
1136 std::lock_guard<std::mutex> lock(mappingMutex_);
1137 auto& mappingList = getMappingList(map.getType());
1138
1139 auto ret = mappingList.emplace(map.getMapKey(), std::make_shared<Mapping>(map));
1140 if (not ret.second) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001141 // JAMI_WARN("Mapping request for %s already added!", map.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001142 return {};
1143 }
1144 mapPtr = ret.first->second;
1145 assert(mapPtr);
1146 }
1147
1148 // No available IGD. The pending mapping requests will be processed
1149 // when a IGD becomes available (in onIgdAdded() method).
1150 if (not isReady()) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001151 // JAMI_WARN("No IGD available. Mapping will be requested when an IGD becomes available");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001152 } else {
1153 requestMapping(mapPtr);
1154 }
1155
1156 return mapPtr;
1157}
1158
1159std::map<Mapping::key_t, Mapping::sharedPtr_t>::iterator
1160UPnPContext::unregisterMapping(std::map<Mapping::key_t, Mapping::sharedPtr_t>::iterator it)
1161{
1162 assert(it->second);
1163
1164 CHECK_VALID_THREAD();
1165 auto descr = it->second->toString();
1166 auto& mappingList = getMappingList(it->second->getType());
1167 auto ret = mappingList.erase(it);
1168
1169 return ret;
1170}
1171
1172void
1173UPnPContext::unregisterMapping(const Mapping::sharedPtr_t& map)
1174{
1175 CHECK_VALID_THREAD();
1176
1177 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001178 // JAMI_ERR("Mapping pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001179 return;
1180 }
1181
1182 if (map->getAutoUpdate()) {
1183 // Dont unregister mappings with auto-update enabled.
1184 return;
1185 }
1186 auto& mappingList = getMappingList(map->getType());
1187
1188 if (mappingList.erase(map->getMapKey()) == 1) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001189 // JAMI_DBG("Unregistered mapping %s", map->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001190 } else {
1191 // The mapping may already be un-registered. Just ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001192 // JAMI_DBG("Mapping %s [%s] does not have a local match",
1193 // map->toString().c_str(),
1194 // map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001195 }
1196}
1197
1198std::map<Mapping::key_t, Mapping::sharedPtr_t>&
1199UPnPContext::getMappingList(PortType type)
1200{
1201 unsigned typeIdx = type == PortType::TCP ? 0 : 1;
1202 return mappingList_[typeIdx];
1203}
1204
1205Mapping::sharedPtr_t
1206UPnPContext::getMappingWithKey(Mapping::key_t key)
1207{
1208 std::lock_guard<std::mutex> lock(mappingMutex_);
1209 auto const& mappingList = getMappingList(Mapping::getTypeFromMapKey(key));
1210 auto it = mappingList.find(key);
1211 if (it == mappingList.end())
1212 return nullptr;
1213 return it->second;
1214}
1215
1216void
1217UPnPContext::getMappingStatus(PortType type, MappingStatus& status)
1218{
1219 std::lock_guard<std::mutex> lock(mappingMutex_);
1220 auto& mappingList = getMappingList(type);
1221
1222 for (auto const& [_, map] : mappingList) {
1223 switch (map->getState()) {
1224 case MappingState::PENDING: {
1225 status.pendingCount_++;
1226 break;
1227 }
1228 case MappingState::IN_PROGRESS: {
1229 status.inProgressCount_++;
1230 break;
1231 }
1232 case MappingState::FAILED: {
1233 status.failedCount_++;
1234 break;
1235 }
1236 case MappingState::OPEN: {
1237 status.openCount_++;
1238 if (map->isAvailable())
1239 status.readyCount_++;
1240 break;
1241 }
1242
1243 default:
1244 // Must not get here.
1245 assert(false);
1246 break;
1247 }
1248 }
1249}
1250
1251void
1252UPnPContext::getMappingStatus(MappingStatus& status)
1253{
1254 getMappingStatus(PortType::TCP, status);
1255 getMappingStatus(PortType::UDP, status);
1256}
1257
1258void
1259UPnPContext::onMappingRequestFailed(const Mapping& mapRes)
1260{
1261 CHECK_VALID_THREAD();
1262
1263 auto const& map = getMappingWithKey(mapRes.getMapKey());
1264 if (not map) {
1265 // We may receive a response for a removed request. Just ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001266 // JAMI_DBG("Mapping %s [IGD %s] does not have a local match",
1267 // mapRes.toString().c_str(),
1268 // mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001269 return;
1270 }
1271
1272 auto igd = map->getIgd();
1273 if (not igd) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001274 // JAMI_ERR("IGD pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001275 return;
1276 }
1277
1278 updateMappingState(map, MappingState::FAILED);
1279 unregisterMapping(map);
1280
Morteza Namvar5f639522023-07-04 17:08:58 -04001281 // JAMI_WARN("Mapping request for %s failed on IGD %s [%s]",
1282 // map->toString().c_str(),
1283 // igd->toString().c_str(),
1284 // igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001285}
1286
1287void
1288UPnPContext::updateMappingState(const Mapping::sharedPtr_t& map, MappingState newState, bool notify)
1289{
1290 CHECK_VALID_THREAD();
1291
1292 assert(map);
1293
1294 // Ignore if the state did not change.
1295 if (newState == map->getState()) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001296 // JAMI_DBG("Mapping %s already in state %s", map->toString().c_str(), map->getStateStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001297 return;
1298 }
1299
1300 // Update the state.
1301 map->setState(newState);
1302
1303 // Notify the listener if set.
1304 if (notify and map->getNotifyCallback())
1305 map->getNotifyCallback()(map);
1306}
1307
1308#if HAVE_LIBNATPMP
1309void
1310UPnPContext::renewAllocations()
1311{
1312 CHECK_VALID_THREAD();
1313
1314 // Check if the we have valid PMP IGD.
1315 auto pmpProto = protocolList_.at(NatProtocolType::NAT_PMP);
1316
1317 auto now = sys_clock::now();
1318 std::vector<Mapping::sharedPtr_t> toRenew;
1319
1320 for (auto type : {PortType::TCP, PortType::UDP}) {
1321 std::lock_guard<std::mutex> lock(mappingMutex_);
1322 auto mappingList = getMappingList(type);
1323 for (auto const& [_, map] : mappingList) {
1324 if (not map->isValid())
1325 continue;
1326 if (map->getProtocol() != NatProtocolType::NAT_PMP)
1327 continue;
1328 if (map->getState() != MappingState::OPEN)
1329 continue;
1330 if (now < map->getRenewalTime())
1331 continue;
1332
1333 toRenew.emplace_back(map);
1334 }
1335 }
1336
1337 // Quit if there are no mapping to renew
1338 if (toRenew.empty())
1339 return;
1340
1341 for (auto const& map : toRenew) {
1342 pmpProto->requestMappingRenew(*map);
1343 }
1344}
1345#endif
1346
1347} // namespace upnp
1348} // namespace jami