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