blob: 5aa3f7852111a769c0c216cf01352a12e3b82f2b [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éraud9d350962023-07-13 15:36:32 -040027#if __has_include(<fmt/std.h>)
Adrien Béraud25c30c42023-07-05 13:46:54 -040028#include <fmt/std.h>
Adrien Béraud9d350962023-07-13 15:36:32 -040029#else
30#include <fmt/ostream.h>
31#endif
Adrien Béraud612b55b2023-05-29 10:42:04 -040032
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040033namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040034namespace upnp {
35
36constexpr static auto MAP_UPDATE_INTERVAL = std::chrono::seconds(30);
37constexpr static int MAX_REQUEST_RETRIES = 20;
38constexpr static int MAX_REQUEST_REMOVE_COUNT = 5;
39
40constexpr static uint16_t UPNP_TCP_PORT_MIN {10000};
41constexpr static uint16_t UPNP_TCP_PORT_MAX {UPNP_TCP_PORT_MIN + 5000};
42constexpr static uint16_t UPNP_UDP_PORT_MIN {20000};
43constexpr static uint16_t UPNP_UDP_PORT_MAX {UPNP_UDP_PORT_MIN + 5000};
44
Adrien Béraud25c30c42023-07-05 13:46:54 -040045UPnPContext::UPnPContext(std::shared_ptr<asio::io_context> ctx, std::shared_ptr<dht::log::Logger> logger)
46 : mappingListUpdateTimer_(*ioContext)
Adrien Béraud612b55b2023-05-29 10:42:04 -040047{
Morteza Namvar5f639522023-07-04 17:08:58 -040048 // JAMI_DBG("Creating UPnPContext instance [%p]", this);
Adrien Béraud612b55b2023-05-29 10:42:04 -040049
50 // Set port ranges
51 portRange_.emplace(PortType::TCP, std::make_pair(UPNP_TCP_PORT_MIN, UPNP_TCP_PORT_MAX));
52 portRange_.emplace(PortType::UDP, std::make_pair(UPNP_UDP_PORT_MIN, UPNP_UDP_PORT_MAX));
53
Adrien Béraud25c30c42023-07-05 13:46:54 -040054 ioContext->post([this] { init(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -040055}
56
Adrien Béraud25c30c42023-07-05 13:46:54 -040057/*std::shared_ptr<UPnPContext>
Adrien Béraud612b55b2023-05-29 10:42:04 -040058UPnPContext::getUPnPContext()
59{
60 // This is the unique shared instance (singleton) of UPnPContext class.
61 static auto context = std::make_shared<UPnPContext>();
62 return context;
Adrien Béraud25c30c42023-07-05 13:46:54 -040063}*/
Adrien Béraud612b55b2023-05-29 10:42:04 -040064
65void
66UPnPContext::shutdown(std::condition_variable& cv)
67{
Morteza Namvar5f639522023-07-04 17:08:58 -040068 // JAMI_DBG("Shutdown UPnPContext instance [%p]", this);
Adrien Béraud612b55b2023-05-29 10:42:04 -040069
70 stopUpnp(true);
71
72 for (auto const& [_, proto] : protocolList_) {
73 proto->terminate();
74 }
75
76 {
77 std::lock_guard<std::mutex> lock(mappingMutex_);
78 mappingList_->clear();
Adrien Béraud25c30c42023-07-05 13:46:54 -040079 //if (mappingListUpdateTimer_)
80 // mappingListUpdateTimer_->cancel();
81 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -040082 controllerList_.clear();
83 protocolList_.clear();
84 shutdownComplete_ = true;
85 cv.notify_one();
86 }
87}
88
89void
90UPnPContext::shutdown()
91{
92 std::unique_lock<std::mutex> lk(mappingMutex_);
93 std::condition_variable cv;
94
95 runOnUpnpContextQueue([&, this] { shutdown(cv); });
96
Morteza Namvar5f639522023-07-04 17:08:58 -040097 // JAMI_DBG("Waiting for shutdown ...");
Adrien Béraud612b55b2023-05-29 10:42:04 -040098
99 if (cv.wait_for(lk, std::chrono::seconds(30), [this] { return shutdownComplete_; })) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400100 // JAMI_DBG("Shutdown completed");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400101 } else {
Morteza Namvar5f639522023-07-04 17:08:58 -0400102 // JAMI_ERR("Shutdown timed-out");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400103 }
104}
105
106UPnPContext::~UPnPContext()
107{
Morteza Namvar5f639522023-07-04 17:08:58 -0400108 // JAMI_DBG("UPnPContext instance [%p] destroyed", this);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400109}
110
111void
112UPnPContext::init()
113{
114 threadId_ = getCurrentThread();
115 CHECK_VALID_THREAD();
116
117#if HAVE_LIBNATPMP
118 auto natPmp = std::make_shared<NatPmp>();
119 natPmp->setObserver(this);
120 protocolList_.emplace(NatProtocolType::NAT_PMP, std::move(natPmp));
121#endif
122
123#if HAVE_LIBUPNP
124 auto pupnp = std::make_shared<PUPnP>();
125 pupnp->setObserver(this);
126 protocolList_.emplace(NatProtocolType::PUPNP, std::move(pupnp));
127#endif
128}
129
130void
131UPnPContext::startUpnp()
132{
133 assert(not controllerList_.empty());
134
135 CHECK_VALID_THREAD();
136
Morteza Namvar5f639522023-07-04 17:08:58 -0400137 // JAMI_DBG("Starting UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400138
139 // Request a new IGD search.
140 for (auto const& [_, protocol] : protocolList_) {
141 protocol->searchForIgd();
142 }
143
144 started_ = true;
145}
146
147void
148UPnPContext::stopUpnp(bool forceRelease)
149{
150 if (not isValidThread()) {
151 runOnUpnpContextQueue([this, forceRelease] { stopUpnp(forceRelease); });
152 return;
153 }
154
Morteza Namvar5f639522023-07-04 17:08:58 -0400155 // JAMI_DBG("Stopping UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400156
157 // Clear all current mappings if any.
158
159 // Use a temporary list to avoid processing the mapping
160 // list while holding the lock.
161 std::list<Mapping::sharedPtr_t> toRemoveList;
162 {
163 std::lock_guard<std::mutex> lock(mappingMutex_);
164
165 PortType types[2] {PortType::TCP, PortType::UDP};
166 for (auto& type : types) {
167 auto& mappingList = getMappingList(type);
168 for (auto const& [_, map] : mappingList) {
169 toRemoveList.emplace_back(map);
170 }
171 }
172 // Invalidate the current IGDs.
173 preferredIgd_.reset();
174 validIgdList_.clear();
175 }
176 for (auto const& map : toRemoveList) {
177 requestRemoveMapping(map);
178
179 // Notify is not needed in updateMappingState when
180 // shutting down (hence set it to false). NotifyCallback
181 // would trigger a new SIP registration and create a
182 // false registered state upon program close.
183 // It's handled by upper layers.
184
185 updateMappingState(map, MappingState::FAILED, false);
186 // We dont remove mappings with auto-update enabled,
187 // unless forceRelease is true.
188 if (not map->getAutoUpdate() or forceRelease) {
189 map->enableAutoUpdate(false);
190 unregisterMapping(map);
191 }
192 }
193
194 // Clear all current IGDs.
195 for (auto const& [_, protocol] : protocolList_) {
196 protocol->clearIgds();
197 }
198
199 started_ = false;
200}
201
202uint16_t
203UPnPContext::generateRandomPort(PortType type, bool mustBeEven)
204{
205 auto minPort = type == PortType::TCP ? UPNP_TCP_PORT_MIN : UPNP_UDP_PORT_MIN;
206 auto maxPort = type == PortType::TCP ? UPNP_TCP_PORT_MAX : UPNP_UDP_PORT_MAX;
207
208 if (minPort >= maxPort) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400209 // JAMI_ERR("Max port number (%i) must be greater than min port number (%i)", maxPort, minPort);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400210 // Must be called with valid range.
211 assert(false);
212 }
213
214 int fact = mustBeEven ? 2 : 1;
215 if (mustBeEven) {
216 minPort /= fact;
217 maxPort /= fact;
218 }
219
220 // Seed the generator.
221 static std::mt19937 gen(dht::crypto::getSeededRandomEngine());
222 // Define the range.
223 std::uniform_int_distribution<uint16_t> dist(minPort, maxPort);
224 return dist(gen) * fact;
225}
226
227void
228UPnPContext::connectivityChanged()
229{
230 if (not isValidThread()) {
231 runOnUpnpContextQueue([this] { connectivityChanged(); });
232 return;
233 }
234
235 auto hostAddr = ip_utils::getLocalAddr(AF_INET);
236
Morteza Namvar5f639522023-07-04 17:08:58 -0400237 // JAMI_DBG("Connectivity change check: host address %s", hostAddr.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400238
239 auto restartUpnp = false;
240
241 // On reception of "connectivity change" notification, the UPNP search
242 // will be restarted if either there is no valid IGD, or the IGD address
243 // changed.
244
245 if (not isReady()) {
246 restartUpnp = true;
247 } else {
248 // Check if the host address changed.
249 for (auto const& [_, protocol] : protocolList_) {
250 if (protocol->isReady() and hostAddr != protocol->getHostAddress()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400251 // JAMI_WARN("Host address changed from %s to %s",
252 // protocol->getHostAddress().toString().c_str(),
253 // hostAddr.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400254 protocol->clearIgds();
255 restartUpnp = true;
256 break;
257 }
258 }
259 }
260
261 // We have at least one valid IGD and the host address did
262 // not change, so no need to restart.
263 if (not restartUpnp) {
264 return;
265 }
266
267 // No registered controller. A new search will be performed when
268 // a controller is registered.
269 if (controllerList_.empty())
270 return;
271
Morteza Namvar5f639522023-07-04 17:08:58 -0400272 // JAMI_DBG("Connectivity changed. Clear the IGDs and restart");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400273
274 stopUpnp();
275 startUpnp();
276
277 // Mapping with auto update enabled must be processed first.
278 processMappingWithAutoUpdate();
279}
280
281void
282UPnPContext::setPublicAddress(const IpAddr& addr)
283{
284 if (not addr)
285 return;
286
287 std::lock_guard<std::mutex> lock(mappingMutex_);
288 if (knownPublicAddress_ != addr) {
289 knownPublicAddress_ = std::move(addr);
Morteza Namvar5f639522023-07-04 17:08:58 -0400290 // JAMI_DBG("Setting the known public address to %s", addr.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400291 }
292}
293
294bool
295UPnPContext::isReady() const
296{
297 std::lock_guard<std::mutex> lock(mappingMutex_);
298 return not validIgdList_.empty();
299}
300
301IpAddr
302UPnPContext::getExternalIP() const
303{
304 std::lock_guard<std::mutex> lock(mappingMutex_);
305 // Return the first IGD Ip available.
306 if (not validIgdList_.empty()) {
307 return (*validIgdList_.begin())->getPublicIp();
308 }
309 return {};
310}
311
312Mapping::sharedPtr_t
313UPnPContext::reserveMapping(Mapping& requestedMap)
314{
315 auto desiredPort = requestedMap.getExternalPort();
316
317 if (desiredPort == 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400318 // JAMI_DBG("Desired port is not set, will provide the first available port for [%s]",
Adrien Béraud25c30c42023-07-05 13:46:54 -0400319 // requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400320 } else {
Morteza Namvar5f639522023-07-04 17:08:58 -0400321 // JAMI_DBG("Try to find mapping for port %i [%s]", desiredPort, requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400322 }
323
324 Mapping::sharedPtr_t mapRes;
325
326 {
327 std::lock_guard<std::mutex> lock(mappingMutex_);
328 auto& mappingList = getMappingList(requestedMap.getType());
329
330 // We try to provide a mapping in "OPEN" state. If not found,
331 // we provide any available mapping. In this case, it's up to
332 // the caller to use it or not.
333 for (auto const& [_, map] : mappingList) {
334 // If the desired port is null, we pick the first available port.
335 if (map->isValid() and (desiredPort == 0 or map->getExternalPort() == desiredPort)
336 and map->isAvailable()) {
337 // Considere the first available mapping regardless of its
338 // state. A mapping with OPEN state will be used if found.
339 if (not mapRes)
340 mapRes = map;
341
342 if (map->getState() == MappingState::OPEN) {
343 // Found an "OPEN" mapping. We are done.
344 mapRes = map;
345 break;
346 }
347 }
348 }
349 }
350
351 // Create a mapping if none was available.
352 if (not mapRes) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400353 // JAMI_WARN("Did not find any available mapping. Will request one now");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400354 mapRes = registerMapping(requestedMap);
355 }
356
357 if (mapRes) {
358 // Make the mapping unavailable
359 mapRes->setAvailable(false);
360 // Copy attributes.
361 mapRes->setNotifyCallback(requestedMap.getNotifyCallback());
362 mapRes->enableAutoUpdate(requestedMap.getAutoUpdate());
363 // Notify the listener.
364 if (auto cb = mapRes->getNotifyCallback())
365 cb(mapRes);
366 }
367
368 updateMappingList(true);
369
370 return mapRes;
371}
372
373void
374UPnPContext::releaseMapping(const Mapping& map)
375{
376 if (not isValidThread()) {
377 runOnUpnpContextQueue([this, map] { releaseMapping(map); });
378 return;
379 }
380
381 auto mapPtr = getMappingWithKey(map.getMapKey());
382
383 if (not mapPtr) {
384 // Might happen if the mapping failed or was never granted.
Morteza Namvar5f639522023-07-04 17:08:58 -0400385 // JAMI_DBG("Mapping %s does not exist or was already removed", map.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400386 return;
387 }
388
389 if (mapPtr->isAvailable()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400390 // JAMI_WARN("Trying to release an unused mapping %s", mapPtr->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400391 return;
392 }
393
394 // Remove it.
395 requestRemoveMapping(mapPtr);
396 unregisterMapping(mapPtr);
397}
398
399void
400UPnPContext::registerController(void* controller)
401{
402 {
403 std::lock_guard<std::mutex> lock(mappingMutex_);
404 if (shutdownComplete_) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400405 // JAMI_WARN("UPnPContext already shut down");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400406 return;
407 }
408 }
409
410 if (not isValidThread()) {
411 runOnUpnpContextQueue([this, controller] { registerController(controller); });
412 return;
413 }
414
415 auto ret = controllerList_.emplace(controller);
416 if (not ret.second) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400417 // JAMI_WARN("Controller %p is already registered", controller);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400418 return;
419 }
420
Morteza Namvar5f639522023-07-04 17:08:58 -0400421 // JAMI_DBG("Successfully registered controller %p", controller);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400422 if (not started_)
423 startUpnp();
424}
425
426void
427UPnPContext::unregisterController(void* controller)
428{
429 if (not isValidThread()) {
430 runOnUpnpContextQueue([this, controller] { unregisterController(controller); });
431 return;
432 }
433
434 if (controllerList_.erase(controller) == 1) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400435 // JAMI_DBG("Successfully unregistered controller %p", controller);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400436 } else {
Morteza Namvar5f639522023-07-04 17:08:58 -0400437 // JAMI_DBG("Controller %p was already removed", controller);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400438 }
439
440 if (controllerList_.empty()) {
441 stopUpnp();
442 }
443}
444
445uint16_t
446UPnPContext::getAvailablePortNumber(PortType type)
447{
448 // Only return an availalable random port. No actual
449 // reservation is made here.
450
451 std::lock_guard<std::mutex> lock(mappingMutex_);
452 auto& mappingList = getMappingList(type);
453 int tryCount = 0;
454 while (tryCount++ < MAX_REQUEST_RETRIES) {
455 uint16_t port = generateRandomPort(type);
456 Mapping map(type, port, port);
457 if (mappingList.find(map.getMapKey()) == mappingList.end())
458 return port;
459 }
460
461 // Very unlikely to get here.
Morteza Namvar5f639522023-07-04 17:08:58 -0400462 // JAMI_ERR("Could not find an available port after %i trials", MAX_REQUEST_RETRIES);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400463 return 0;
464}
465
466void
467UPnPContext::requestMapping(const Mapping::sharedPtr_t& map)
468{
469 assert(map);
470
471 if (not isValidThread()) {
472 runOnUpnpContextQueue([this, map] { requestMapping(map); });
473 return;
474 }
475
476 auto const& igd = getPreferredIgd();
477 // We must have at least a valid IGD pointer if we get here.
478 // Not this method is called only if there were a valid IGD, however,
479 // because the processing is asynchronous, it's possible that the IGD
480 // was invalidated when the this code executed.
481 if (not igd) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400482 // JAMI_DBG("No valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400483 return;
484 }
485
486 map->setIgd(igd);
487
Morteza Namvar5f639522023-07-04 17:08:58 -0400488 // JAMI_DBG("Request mapping %s using protocol [%s] IGD [%s]",
489 // map->toString().c_str(),
490 // igd->getProtocolName(),
491 // igd->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400492
493 if (map->getState() != MappingState::IN_PROGRESS)
494 updateMappingState(map, MappingState::IN_PROGRESS);
495
496 auto const& protocol = protocolList_.at(igd->getProtocol());
497 protocol->requestMappingAdd(*map);
498}
499
500bool
501UPnPContext::provisionNewMappings(PortType type, int portCount)
502{
Morteza Namvar5f639522023-07-04 17:08:58 -0400503 // JAMI_DBG("Provision %i new mappings of type [%s]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400504
505 assert(portCount > 0);
506
507 while (portCount > 0) {
508 auto port = getAvailablePortNumber(type);
509 if (port > 0) {
510 // Found an available port number
511 portCount--;
512 Mapping map(type, port, port, true);
513 registerMapping(map);
514 } else {
515 // Very unlikely to get here!
Morteza Namvar5f639522023-07-04 17:08:58 -0400516 // JAMI_ERR("Can not find any available port to provision!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400517 return false;
518 }
519 }
520
521 return true;
522}
523
524bool
525UPnPContext::deleteUnneededMappings(PortType type, int portCount)
526{
Morteza Namvar5f639522023-07-04 17:08:58 -0400527 // JAMI_DBG("Remove %i unneeded mapping of type [%s]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400528
529 assert(portCount > 0);
530
531 CHECK_VALID_THREAD();
532
533 std::lock_guard<std::mutex> lock(mappingMutex_);
534 auto& mappingList = getMappingList(type);
535
536 for (auto it = mappingList.begin(); it != mappingList.end();) {
537 auto map = it->second;
538 assert(map);
539
540 if (not map->isAvailable()) {
541 it++;
542 continue;
543 }
544
545 if (map->getState() == MappingState::OPEN and portCount > 0) {
546 // Close portCount mappings in "OPEN" state.
547 requestRemoveMapping(map);
548 it = unregisterMapping(it);
549 portCount--;
550 } else if (map->getState() != MappingState::OPEN) {
551 // If this methods is called, it means there are more open
552 // mappings than required. So, all mappings in a state other
553 // than "OPEN" state (typically in in-progress state) will
554 // be deleted as well.
555 it = unregisterMapping(it);
556 } else {
557 it++;
558 }
559 }
560
561 return true;
562}
563
564void
565UPnPContext::updatePreferredIgd()
566{
567 CHECK_VALID_THREAD();
568
569 if (preferredIgd_ and preferredIgd_->isValid())
570 return;
571
572 // Reset and search for the best IGD.
573 preferredIgd_.reset();
574
575 for (auto const& [_, protocol] : protocolList_) {
576 if (protocol->isReady()) {
577 auto igdList = protocol->getIgdList();
578 assert(not igdList.empty());
579 auto const& igd = igdList.front();
580 if (not igd->isValid())
581 continue;
582
583 // Prefer NAT-PMP over PUPNP.
584 if (preferredIgd_ and igd->getProtocol() != NatProtocolType::NAT_PMP)
585 continue;
586
587 // Update.
588 preferredIgd_ = igd;
589 }
590 }
591
592 if (preferredIgd_ and preferredIgd_->isValid()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400593 // JAMI_DBG("Preferred IGD updated to [%s] IGD [%s %s] ",
594 // preferredIgd_->getProtocolName(),
595 // preferredIgd_->getUID().c_str(),
596 // preferredIgd_->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400597 }
598}
599
600std::shared_ptr<IGD>
601UPnPContext::getPreferredIgd() const
602{
603 CHECK_VALID_THREAD();
604
605 return preferredIgd_;
606}
607
608void
609UPnPContext::updateMappingList(bool async)
610{
611 // Run async if requested.
612 if (async) {
613 runOnUpnpContextQueue([this] { updateMappingList(false); });
614 return;
615 }
616
617 CHECK_VALID_THREAD();
618
619 // Update the preferred IGD.
620 updatePreferredIgd();
621
Adrien Béraud25c30c42023-07-05 13:46:54 -0400622 /*if (mappingListUpdateTimer_) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400623 mappingListUpdateTimer_->cancel();
624 mappingListUpdateTimer_ = {};
Adrien Béraud25c30c42023-07-05 13:46:54 -0400625 }*/
626 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400627
628 // Skip if no controller registered.
629 if (controllerList_.empty())
630 return;
631
632 // Cancel the current timer (if any) and re-schedule.
633 std::shared_ptr<IGD> prefIgd = getPreferredIgd();
634 if (not prefIgd) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400635 // JAMI_DBG("UPNP/NAT-PMP enabled, but no valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400636 // No valid IGD. Nothing to do.
637 return;
638 }
639
Adrien Béraud25c30c42023-07-05 13:46:54 -0400640 /*mappingListUpdateTimer_ = getScheduler()->scheduleIn([this] { updateMappingList(false); },
641 MAP_UPDATE_INTERVAL);*/
642 mappingListUpdateTimer_.expires_from_now(MAP_UPDATE_INTERVAL);
643 mappingListUpdateTimer_.async_wait([this](asio::error_code const& ec) {
644 if (ec != asio::error::operation_aborted)
645 updateMappingList(false);
646 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400647
648 // Process pending requests if any.
649 processPendingRequests(prefIgd);
650
651 // Make new requests for mappings that failed and have
652 // the auto-update option enabled.
653 processMappingWithAutoUpdate();
654
655 PortType typeArray[2] = {PortType::TCP, PortType::UDP};
656
657 for (auto idx : {0, 1}) {
658 auto type = typeArray[idx];
659
660 MappingStatus status;
661 getMappingStatus(type, status);
662
Morteza Namvar5f639522023-07-04 17:08:58 -0400663 // JAMI_DBG("Mapping status [%s] - overall %i: %i open (%i ready + %i in use), %i pending, %i "
664 // "in-progress, %i failed",
665 // Mapping::getTypeStr(type),
666 // status.sum(),
667 // status.openCount_,
668 // status.readyCount_,
669 // status.openCount_ - status.readyCount_,
670 // status.pendingCount_,
671 // status.inProgressCount_,
672 // status.failedCount_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400673
674 if (status.failedCount_ > 0) {
675 std::lock_guard<std::mutex> lock(mappingMutex_);
676 auto const& mappingList = getMappingList(type);
677 for (auto const& [_, map] : mappingList) {
678 if (map->getState() == MappingState::FAILED) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400679 // JAMI_DBG("Mapping status [%s] - Available [%s]",
680 // map->toString(true).c_str(),
681 // map->isAvailable() ? "YES" : "NO");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400682 }
683 }
684 }
685
686 int toRequestCount = (int) minOpenPortLimit_[idx]
687 - (int) (status.readyCount_ + status.inProgressCount_
688 + status.pendingCount_);
689
690 // Provision/release mappings accordingly.
691 if (toRequestCount > 0) {
692 // Take into account the request in-progress when making
693 // requests for new mappings.
694 provisionNewMappings(type, toRequestCount);
695 } else if (status.readyCount_ > maxOpenPortLimit_[idx]) {
696 deleteUnneededMappings(type, status.readyCount_ - maxOpenPortLimit_[idx]);
697 }
698 }
699
700 // Prune the mapping list if needed
701 if (protocolList_.at(NatProtocolType::PUPNP)->isReady()) {
702#if HAVE_LIBNATPMP
703 // Dont perform if NAT-PMP is valid.
704 if (not protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
705#endif
706 {
707 pruneMappingList();
708 }
709 }
710
711#if HAVE_LIBNATPMP
712 // Renew nat-pmp allocations
713 if (protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
714 renewAllocations();
715#endif
716}
717
718void
719UPnPContext::pruneMappingList()
720{
721 CHECK_VALID_THREAD();
722
723 MappingStatus status;
724 getMappingStatus(status);
725
726 // Do not prune the list if there are pending/in-progress requests.
727 if (status.inProgressCount_ != 0 or status.pendingCount_ != 0) {
728 return;
729 }
730
731 auto const& igd = getPreferredIgd();
732 if (not igd or igd->getProtocol() != NatProtocolType::PUPNP) {
733 return;
734 }
735 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
736
737 auto remoteMapList = protocol->getMappingsListByDescr(igd,
738 Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
739 if (remoteMapList.empty()) {
740 std::lock_guard<std::mutex> lock(mappingMutex_);
741 if (not getMappingList(PortType::TCP).empty() or getMappingList(PortType::TCP).empty()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400742 // JAMI_WARN("We have provisionned mappings but the PUPNP IGD returned an empty list!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400743 }
744 }
745
746 pruneUnMatchedMappings(igd, remoteMapList);
747 pruneUnTrackedMappings(igd, remoteMapList);
748}
749
750void
751UPnPContext::pruneUnMatchedMappings(const std::shared_ptr<IGD>& igd,
752 const std::map<Mapping::key_t, Mapping>& remoteMapList)
753{
754 // Check/synchronize local mapping list with the list
755 // returned by the IGD.
756
757 PortType types[2] {PortType::TCP, PortType::UDP};
758
759 for (auto& type : types) {
760 // Use a temporary list to avoid processing mappings while holding the lock.
761 std::list<Mapping::sharedPtr_t> toRemoveList;
762 {
763 std::lock_guard<std::mutex> lock(mappingMutex_);
764 auto& mappingList = getMappingList(type);
765 for (auto const& [_, map] : mappingList) {
766 // Only check mappings allocated by UPNP protocol.
767 if (map->getProtocol() != NatProtocolType::PUPNP) {
768 continue;
769 }
770 // Set mapping as failed if not found in the list
771 // returned by the IGD.
772 if (map->getState() == MappingState::OPEN
773 and remoteMapList.find(map->getMapKey()) == remoteMapList.end()) {
774 toRemoveList.emplace_back(map);
775
Morteza Namvar5f639522023-07-04 17:08:58 -0400776 // JAMI_WARN("Mapping %s (IGD %s) marked as \"OPEN\" but not found in the "
777 // "remote list. Mark as failed!",
778 // map->toString().c_str(),
779 // igd->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400780 }
781 }
782 }
783
784 for (auto const& map : toRemoveList) {
785 updateMappingState(map, MappingState::FAILED);
786 unregisterMapping(map);
787 }
788 }
789}
790
791void
792UPnPContext::pruneUnTrackedMappings(const std::shared_ptr<IGD>& igd,
793 const std::map<Mapping::key_t, Mapping>& remoteMapList)
794{
795 // Use a temporary list to avoid processing mappings while holding the lock.
796 std::list<Mapping> toRemoveList;
797 {
798 std::lock_guard<std::mutex> lock(mappingMutex_);
799
800 for (auto const& [_, map] : remoteMapList) {
801 // Must has valid IGD pointer and use UPNP protocol.
802 assert(map.getIgd());
803 assert(map.getIgd()->getProtocol() == NatProtocolType::PUPNP);
804 auto& mappingList = getMappingList(map.getType());
805 auto it = mappingList.find(map.getMapKey());
806 if (it == mappingList.end()) {
807 // Not present, request mapping remove.
808 toRemoveList.emplace_back(std::move(map));
809 // Make only few remove requests at once.
810 if (toRemoveList.size() >= MAX_REQUEST_REMOVE_COUNT)
811 break;
812 }
813 }
814 }
815
816 // Remove un-tracked mappings.
817 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
818 for (auto const& map : toRemoveList) {
819 protocol->requestMappingRemove(map);
820 }
821}
822
823void
824UPnPContext::pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd)
825{
826 CHECK_VALID_THREAD();
827
828 // Use temporary list to avoid holding the lock while
829 // processing the mapping list.
830 std::list<Mapping::sharedPtr_t> toRemoveList;
831 {
832 std::lock_guard<std::mutex> lock(mappingMutex_);
833
834 PortType types[2] {PortType::TCP, PortType::UDP};
835 for (auto& type : types) {
836 auto& mappingList = getMappingList(type);
837 for (auto const& [_, map] : mappingList) {
838 if (map->getIgd() == igd)
839 toRemoveList.emplace_back(map);
840 }
841 }
842 }
843
844 for (auto const& map : toRemoveList) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400845 // JAMI_DBG("Remove mapping %s (has an invalid IGD %s [%s])",
846 // map->toString().c_str(),
847 // igd->toString().c_str(),
848 // igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400849 updateMappingState(map, MappingState::FAILED);
850 unregisterMapping(map);
851 }
852}
853
854void
855UPnPContext::processPendingRequests(const std::shared_ptr<IGD>& igd)
856{
857 // This list holds the mappings to be requested. This is
858 // needed to avoid performing the requests while holding
859 // the lock.
860 std::list<Mapping::sharedPtr_t> requestsList;
861
862 // Populate the list of requests to perform.
863 {
864 std::lock_guard<std::mutex> lock(mappingMutex_);
865 PortType typeArray[2] {PortType::TCP, PortType::UDP};
866
867 for (auto type : typeArray) {
868 auto& mappingList = getMappingList(type);
869 for (auto& [_, map] : mappingList) {
870 if (map->getState() == MappingState::PENDING) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400871 // JAMI_DBG("Send pending request for mapping %s to IGD %s",
872 // map->toString().c_str(),
873 // igd->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400874 requestsList.emplace_back(map);
875 }
876 }
877 }
878 }
879
880 // Process the pending requests.
881 for (auto const& map : requestsList) {
882 requestMapping(map);
883 }
884}
885
886void
887UPnPContext::processMappingWithAutoUpdate()
888{
889 // This list holds the mappings to be requested. This is
890 // needed to avoid performing the requests while holding
891 // the lock.
892 std::list<Mapping::sharedPtr_t> requestsList;
893
894 // Populate the list of requests for mappings with auto-update enabled.
895 {
896 std::lock_guard<std::mutex> lock(mappingMutex_);
897 PortType typeArray[2] {PortType::TCP, PortType::UDP};
898
899 for (auto type : typeArray) {
900 auto& mappingList = getMappingList(type);
901 for (auto const& [_, map] : mappingList) {
902 if (map->getState() == MappingState::FAILED and map->getAutoUpdate()) {
903 requestsList.emplace_back(map);
904 }
905 }
906 }
907 }
908
909 for (auto const& oldMap : requestsList) {
910 // Request a new mapping if auto-update is enabled.
Morteza Namvar5f639522023-07-04 17:08:58 -0400911 // JAMI_DBG("Mapping %s has auto-update enabled, a new mapping will be requested",
912 // oldMap->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400913
914 // Reserve a new mapping.
915 Mapping newMapping(oldMap->getType());
916 newMapping.enableAutoUpdate(true);
917 newMapping.setNotifyCallback(oldMap->getNotifyCallback());
918
919 auto const& mapPtr = reserveMapping(newMapping);
920 assert(mapPtr);
921
922 // Release the old one.
923 oldMap->setAvailable(true);
924 oldMap->enableAutoUpdate(false);
925 oldMap->setNotifyCallback(nullptr);
926 unregisterMapping(oldMap);
927 }
928}
929
930void
931UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event)
932{
933 assert(igd);
934
935 if (not isValidThread()) {
936 runOnUpnpContextQueue([this, igd, event] { onIgdUpdated(igd, event); });
937 return;
938 }
939
940 // Reset to start search for a new best IGD.
941 preferredIgd_.reset();
942
943 char const* IgdState = event == UpnpIgdEvent::ADDED ? "ADDED"
944 : event == UpnpIgdEvent::REMOVED ? "REMOVED"
945 : "INVALID";
946
947 auto const& igdLocalAddr = igd->getLocalIp();
948 auto protocolName = igd->getProtocolName();
949
Morteza Namvar5f639522023-07-04 17:08:58 -0400950 // JAMI_DBG("New event for IGD [%s %s] [%s]: [%s]",
951 // igd->getUID().c_str(),
952 // igd->toString().c_str(),
953 // protocolName,
954 // IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400955
956 // Check if the IGD has valid addresses.
957 if (not igdLocalAddr) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400958 // JAMI_WARN("[%s] IGD has an invalid local address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400959 return;
960 }
961
962 if (not igd->getPublicIp()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400963 // JAMI_WARN("[%s] IGD has an invalid public address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400964 return;
965 }
966
967 if (knownPublicAddress_ and igd->getPublicIp() != knownPublicAddress_) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400968 // JAMI_WARN("[%s] IGD external address [%s] does not match known public address [%s]."
969 // " The mapped addresses might not be reachable",
970 // protocolName,
971 // igd->getPublicIp().toString().c_str(),
972 // knownPublicAddress_.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400973 }
974
975 // The IGD was removed or is invalid.
976 if (event == UpnpIgdEvent::REMOVED or event == UpnpIgdEvent::INVALID_STATE) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400977 // JAMI_WARN("State of IGD [%s %s] [%s] changed to [%s]. Pruning the mapping list",
978 // igd->getUID().c_str(),
979 // igd->toString().c_str(),
980 // protocolName,
981 // IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400982
983 pruneMappingsWithInvalidIgds(igd);
984
985 std::lock_guard<std::mutex> lock(mappingMutex_);
986 validIgdList_.erase(igd);
987 return;
988 }
989
990 // Update the IGD list.
991 {
992 std::lock_guard<std::mutex> lock(mappingMutex_);
993 auto ret = validIgdList_.emplace(igd);
994 if (ret.second) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400995 // JAMI_DBG("IGD [%s] on address %s was added. Will process any pending requests",
996 // protocolName,
997 // igdLocalAddr.toString(true, true).c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400998 } else {
999 // Already in the list.
Morteza Namvar5f639522023-07-04 17:08:58 -04001000 // JAMI_ERR("IGD [%s] on address %s already in the list",
1001 // protocolName,
1002 // igdLocalAddr.toString(true, true).c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001003 return;
1004 }
1005 }
1006
1007 // Update the provisionned mappings.
1008 updateMappingList(false);
1009}
1010
1011void
1012UPnPContext::onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1013{
1014 CHECK_VALID_THREAD();
1015
1016 // Check if we have a pending request for this response.
1017 auto map = getMappingWithKey(mapRes.getMapKey());
1018 if (not map) {
1019 // We may receive a response for a canceled request. Just ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001020 // JAMI_DBG("Response for mapping %s [IGD %s] [%s] does not have a local match",
1021 // mapRes.toString().c_str(),
1022 // igd->toString().c_str(),
1023 // mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001024 return;
1025 }
1026
1027 // The mapping request is new and successful. Update.
1028 map->setIgd(igd);
1029 map->setInternalAddress(mapRes.getInternalAddress());
1030 map->setExternalPort(mapRes.getExternalPort());
1031
1032 // Update the state and report to the owner.
1033 updateMappingState(map, MappingState::OPEN);
1034
Morteza Namvar5f639522023-07-04 17:08:58 -04001035 // JAMI_DBG("Mapping %s (on IGD %s [%s]) successfully performed",
1036 // map->toString().c_str(),
1037 // igd->toString().c_str(),
1038 // map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001039
1040 // Call setValid() to reset the errors counter. We need
1041 // to reset the counter on each successful response.
1042 igd->setValid(true);
1043}
1044
1045#if HAVE_LIBNATPMP
1046void
1047UPnPContext::onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map)
1048{
1049 auto mapPtr = getMappingWithKey(map.getMapKey());
1050
1051 if (not mapPtr) {
1052 // We may receive a notification for a canceled request. Ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001053 // JAMI_WARN("Renewed mapping %s from IGD %s [%s] does not have a match in local list",
1054 // map.toString().c_str(),
1055 // igd->toString().c_str(),
1056 // map.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001057 return;
1058 }
1059 if (mapPtr->getProtocol() != NatProtocolType::NAT_PMP or not mapPtr->isValid()
1060 or mapPtr->getState() != MappingState::OPEN) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001061 // JAMI_WARN("Renewed mapping %s from IGD %s [%s] is in unexpected state",
1062 // mapPtr->toString().c_str(),
1063 // igd->toString().c_str(),
1064 // mapPtr->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001065 return;
1066 }
1067
1068 mapPtr->setRenewalTime(map.getRenewalTime());
1069}
1070#endif
1071
1072void
1073UPnPContext::requestRemoveMapping(const Mapping::sharedPtr_t& map)
1074{
1075 CHECK_VALID_THREAD();
1076
1077 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001078 // JAMI_ERR("Mapping shared pointer is null!");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001079 return;
1080 }
1081
1082 if (not map->isValid()) {
1083 // Silently ignore if the mapping is invalid
1084 return;
1085 }
1086
1087 auto protocol = protocolList_.at(map->getIgd()->getProtocol());
1088 protocol->requestMappingRemove(*map);
1089}
1090
1091void
1092UPnPContext::deleteAllMappings(PortType type)
1093{
1094 if (not isValidThread()) {
1095 runOnUpnpContextQueue([this, type] { deleteAllMappings(type); });
1096 return;
1097 }
1098
1099 std::lock_guard<std::mutex> lock(mappingMutex_);
1100 auto& mappingList = getMappingList(type);
1101
1102 for (auto const& [_, map] : mappingList) {
1103 requestRemoveMapping(map);
1104 }
1105}
1106
1107void
1108UPnPContext::onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1109{
1110 if (not mapRes.isValid())
1111 return;
1112
1113 if (not isValidThread()) {
1114 runOnUpnpContextQueue([this, igd, mapRes] { onMappingRemoved(igd, mapRes); });
1115 return;
1116 }
1117
1118 auto map = getMappingWithKey(mapRes.getMapKey());
1119 // Notify the listener.
1120 if (map and map->getNotifyCallback())
1121 map->getNotifyCallback()(map);
1122}
1123
1124Mapping::sharedPtr_t
1125UPnPContext::registerMapping(Mapping& map)
1126{
1127 if (map.getExternalPort() == 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001128 // JAMI_DBG("Port number not set. Will set a random port number");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001129 auto port = getAvailablePortNumber(map.getType());
1130 map.setExternalPort(port);
1131 map.setInternalPort(port);
1132 }
1133
1134 // Newly added mapping must be in pending state by default.
1135 map.setState(MappingState::PENDING);
1136
1137 Mapping::sharedPtr_t mapPtr;
1138
1139 {
1140 std::lock_guard<std::mutex> lock(mappingMutex_);
1141 auto& mappingList = getMappingList(map.getType());
1142
1143 auto ret = mappingList.emplace(map.getMapKey(), std::make_shared<Mapping>(map));
1144 if (not ret.second) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001145 // JAMI_WARN("Mapping request for %s already added!", map.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001146 return {};
1147 }
1148 mapPtr = ret.first->second;
1149 assert(mapPtr);
1150 }
1151
1152 // No available IGD. The pending mapping requests will be processed
1153 // when a IGD becomes available (in onIgdAdded() method).
1154 if (not isReady()) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001155 // JAMI_WARN("No IGD available. Mapping will be requested when an IGD becomes available");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001156 } else {
1157 requestMapping(mapPtr);
1158 }
1159
1160 return mapPtr;
1161}
1162
1163std::map<Mapping::key_t, Mapping::sharedPtr_t>::iterator
1164UPnPContext::unregisterMapping(std::map<Mapping::key_t, Mapping::sharedPtr_t>::iterator it)
1165{
1166 assert(it->second);
1167
1168 CHECK_VALID_THREAD();
1169 auto descr = it->second->toString();
1170 auto& mappingList = getMappingList(it->second->getType());
1171 auto ret = mappingList.erase(it);
1172
1173 return ret;
1174}
1175
1176void
1177UPnPContext::unregisterMapping(const Mapping::sharedPtr_t& map)
1178{
1179 CHECK_VALID_THREAD();
1180
1181 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001182 // JAMI_ERR("Mapping pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001183 return;
1184 }
1185
1186 if (map->getAutoUpdate()) {
1187 // Dont unregister mappings with auto-update enabled.
1188 return;
1189 }
1190 auto& mappingList = getMappingList(map->getType());
1191
1192 if (mappingList.erase(map->getMapKey()) == 1) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001193 // JAMI_DBG("Unregistered mapping %s", map->toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001194 } else {
1195 // The mapping may already be un-registered. Just ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001196 // JAMI_DBG("Mapping %s [%s] does not have a local match",
1197 // map->toString().c_str(),
1198 // map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001199 }
1200}
1201
1202std::map<Mapping::key_t, Mapping::sharedPtr_t>&
1203UPnPContext::getMappingList(PortType type)
1204{
1205 unsigned typeIdx = type == PortType::TCP ? 0 : 1;
1206 return mappingList_[typeIdx];
1207}
1208
1209Mapping::sharedPtr_t
1210UPnPContext::getMappingWithKey(Mapping::key_t key)
1211{
1212 std::lock_guard<std::mutex> lock(mappingMutex_);
1213 auto const& mappingList = getMappingList(Mapping::getTypeFromMapKey(key));
1214 auto it = mappingList.find(key);
1215 if (it == mappingList.end())
1216 return nullptr;
1217 return it->second;
1218}
1219
1220void
1221UPnPContext::getMappingStatus(PortType type, MappingStatus& status)
1222{
1223 std::lock_guard<std::mutex> lock(mappingMutex_);
1224 auto& mappingList = getMappingList(type);
1225
1226 for (auto const& [_, map] : mappingList) {
1227 switch (map->getState()) {
1228 case MappingState::PENDING: {
1229 status.pendingCount_++;
1230 break;
1231 }
1232 case MappingState::IN_PROGRESS: {
1233 status.inProgressCount_++;
1234 break;
1235 }
1236 case MappingState::FAILED: {
1237 status.failedCount_++;
1238 break;
1239 }
1240 case MappingState::OPEN: {
1241 status.openCount_++;
1242 if (map->isAvailable())
1243 status.readyCount_++;
1244 break;
1245 }
1246
1247 default:
1248 // Must not get here.
1249 assert(false);
1250 break;
1251 }
1252 }
1253}
1254
1255void
1256UPnPContext::getMappingStatus(MappingStatus& status)
1257{
1258 getMappingStatus(PortType::TCP, status);
1259 getMappingStatus(PortType::UDP, status);
1260}
1261
1262void
1263UPnPContext::onMappingRequestFailed(const Mapping& mapRes)
1264{
1265 CHECK_VALID_THREAD();
1266
1267 auto const& map = getMappingWithKey(mapRes.getMapKey());
1268 if (not map) {
1269 // We may receive a response for a removed request. Just ignore it.
Morteza Namvar5f639522023-07-04 17:08:58 -04001270 // JAMI_DBG("Mapping %s [IGD %s] does not have a local match",
1271 // mapRes.toString().c_str(),
1272 // mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001273 return;
1274 }
1275
1276 auto igd = map->getIgd();
1277 if (not igd) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001278 // JAMI_ERR("IGD pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001279 return;
1280 }
1281
1282 updateMappingState(map, MappingState::FAILED);
1283 unregisterMapping(map);
1284
Morteza Namvar5f639522023-07-04 17:08:58 -04001285 // JAMI_WARN("Mapping request for %s failed on IGD %s [%s]",
1286 // map->toString().c_str(),
1287 // igd->toString().c_str(),
1288 // igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001289}
1290
1291void
1292UPnPContext::updateMappingState(const Mapping::sharedPtr_t& map, MappingState newState, bool notify)
1293{
1294 CHECK_VALID_THREAD();
1295
1296 assert(map);
1297
1298 // Ignore if the state did not change.
1299 if (newState == map->getState()) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001300 // JAMI_DBG("Mapping %s already in state %s", map->toString().c_str(), map->getStateStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001301 return;
1302 }
1303
1304 // Update the state.
1305 map->setState(newState);
1306
1307 // Notify the listener if set.
1308 if (notify and map->getNotifyCallback())
1309 map->getNotifyCallback()(map);
1310}
1311
1312#if HAVE_LIBNATPMP
1313void
1314UPnPContext::renewAllocations()
1315{
1316 CHECK_VALID_THREAD();
1317
1318 // Check if the we have valid PMP IGD.
1319 auto pmpProto = protocolList_.at(NatProtocolType::NAT_PMP);
1320
1321 auto now = sys_clock::now();
1322 std::vector<Mapping::sharedPtr_t> toRenew;
1323
1324 for (auto type : {PortType::TCP, PortType::UDP}) {
1325 std::lock_guard<std::mutex> lock(mappingMutex_);
1326 auto mappingList = getMappingList(type);
1327 for (auto const& [_, map] : mappingList) {
1328 if (not map->isValid())
1329 continue;
1330 if (map->getProtocol() != NatProtocolType::NAT_PMP)
1331 continue;
1332 if (map->getState() != MappingState::OPEN)
1333 continue;
1334 if (now < map->getRenewalTime())
1335 continue;
1336
1337 toRenew.emplace_back(map);
1338 }
1339 }
1340
1341 // Quit if there are no mapping to renew
1342 if (toRenew.empty())
1343 return;
1344
1345 for (auto const& map : toRenew) {
1346 pmpProto->requestMappingRenew(*map);
1347 }
1348}
1349#endif
1350
1351} // namespace upnp
1352} // namespace jami