blob: 7ea3d836795162a22b174a1bde69c0bb22b7b03d [file] [log] [blame]
Adrien Béraud612b55b2023-05-29 10:42:04 -04001/*
2 * Copyright (C) 2004-2023 Savoir-faire Linux Inc.
3 *
Adrien Béraudcb753622023-07-17 22:32:49 -04004 * This program is free software: you can redistribute it and/or modify
Adrien Béraud612b55b2023-05-29 10:42:04 -04005 * it under the terms of the GNU General Public License as published by
Adrien Béraudcb753622023-07-17 22:32:49 -04006 * the Free Software Foundation, either version 3 of the License, or
Adrien Béraud612b55b2023-05-29 10:42:04 -04007 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Adrien Béraudcb753622023-07-17 22:32:49 -040011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Adrien Béraud612b55b2023-05-29 10:42:04 -040012 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
Adrien Béraudcb753622023-07-17 22:32:49 -040015 * along with this program. If not, see <https://www.gnu.org/licenses/>.
Adrien Béraud612b55b2023-05-29 10:42:04 -040016 */
Morteza Namvar5f639522023-07-04 17:08:58 -040017#include "upnp/upnp_context.h"
Adrien Béraud25c30c42023-07-05 13:46:54 -040018#include "protocol/upnp_protocol.h"
19
Adrien Béraud370257c2023-08-15 20:53:09 -040020#if HAVE_LIBNATPMP
21#include "protocol/natpmp/nat_pmp.h"
22#endif
23#if HAVE_LIBUPNP
24#include "protocol/pupnp/pupnp.h"
25#endif
Amna7cd813c2023-10-02 18:15:47 -040026#include <asio.hpp>
Morteza Namvar5f639522023-07-04 17:08:58 -040027#include <asio/steady_timer.hpp>
Adrien Béraud9d350962023-07-13 15:36:32 -040028#if __has_include(<fmt/std.h>)
Adrien Béraud25c30c42023-07-05 13:46:54 -040029#include <fmt/std.h>
Adrien Béraud9d350962023-07-13 15:36:32 -040030#else
31#include <fmt/ostream.h>
32#endif
Adrien Béraud612b55b2023-05-29 10:42:04 -040033
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040034namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040035namespace upnp {
36
37constexpr static auto MAP_UPDATE_INTERVAL = std::chrono::seconds(30);
38constexpr static int MAX_REQUEST_RETRIES = 20;
39constexpr static int MAX_REQUEST_REMOVE_COUNT = 5;
40
41constexpr static uint16_t UPNP_TCP_PORT_MIN {10000};
42constexpr static uint16_t UPNP_TCP_PORT_MAX {UPNP_TCP_PORT_MIN + 5000};
43constexpr static uint16_t UPNP_UDP_PORT_MIN {20000};
44constexpr static uint16_t UPNP_UDP_PORT_MAX {UPNP_UDP_PORT_MIN + 5000};
45
Sébastien Blin55abf072023-07-19 10:21:21 -040046UPnPContext::UPnPContext(const std::shared_ptr<asio::io_context>& ioContext, const std::shared_ptr<dht::log::Logger>& logger)
Adrien Béraudc36965c2023-08-17 21:50:27 -040047 : ctx(createIoContext(ioContext, logger))
Adrien Béraud91fd4b62023-08-29 20:50:01 -040048 , logger_(logger)
Adrien Béraud95219ef2023-08-17 21:55:37 -040049 , mappingListUpdateTimer_(*ctx)
50 , connectivityChangedTimer_(*ctx)
Adrien Béraud612b55b2023-05-29 10:42:04 -040051{
Adrien Beraud3bd61c92023-08-17 16:57:37 -040052 if (logger_) logger_->debug("Creating UPnPContext instance [{}]", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -040053
54 // Set port ranges
55 portRange_.emplace(PortType::TCP, std::make_pair(UPNP_TCP_PORT_MIN, UPNP_TCP_PORT_MAX));
56 portRange_.emplace(PortType::UDP, std::make_pair(UPNP_UDP_PORT_MIN, UPNP_UDP_PORT_MAX));
57
Adrien Béraud370257c2023-08-15 20:53:09 -040058 ctx->post([this] { init(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -040059}
60
Adrien Béraudb04fbd72023-08-17 19:56:11 -040061std::shared_ptr<asio::io_context>
62UPnPContext::createIoContext(const std::shared_ptr<asio::io_context>& ctx, const std::shared_ptr<dht::log::Logger>& logger) {
63 if (ctx) {
64 return ctx;
65 } else {
66 if (logger) logger->debug("UPnPContext: starting dedicated io_context thread");
67 auto ioCtx = std::make_shared<asio::io_context>();
68 ioContextRunner_ = std::make_unique<std::thread>([ioCtx, l=logger]() {
69 try {
70 auto work = asio::make_work_guard(*ioCtx);
71 ioCtx->run();
72 } catch (const std::exception& ex) {
73 if (l) l->error("Unexpected io_context thread exception: {}", ex.what());
74 }
75 });
76 return ioCtx;
77 }
78}
79
Adrien Béraud612b55b2023-05-29 10:42:04 -040080void
81UPnPContext::shutdown(std::condition_variable& cv)
82{
Adrien Beraud3bd61c92023-08-17 16:57:37 -040083 if (logger_) logger_->debug("Shutdown UPnPContext instance [{}]", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -040084
85 stopUpnp(true);
86
87 for (auto const& [_, proto] : protocolList_) {
88 proto->terminate();
89 }
90
Adrien Béraud024c46f2024-03-02 23:53:18 -050091 std::lock_guard lock(mappingMutex_);
Adrien Béraud91fd4b62023-08-29 20:50:01 -040092 mappingList_->clear();
93 mappingListUpdateTimer_.cancel();
94 controllerList_.clear();
95 protocolList_.clear();
96 shutdownComplete_ = true;
François-Simon Fauteux-Chapleau808db4f2024-04-19 11:39:47 -040097 if (shutdownTimedOut_) {
98 // If we timed out in shutdown(), then calling notify_one is not necessary,
99 // and doing so anyway can cause bugs, see:
100 // https://git.jami.net/savoirfairelinux/dhtnet/-/issues/28
101 return;
102 }
Adrien Béraud91fd4b62023-08-29 20:50:01 -0400103 cv.notify_one();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400104}
105
106void
107UPnPContext::shutdown()
108{
Adrien Béraud024c46f2024-03-02 23:53:18 -0500109 std::unique_lock lk(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400110 std::condition_variable cv;
111
Adrien Béraud370257c2023-08-15 20:53:09 -0400112 ctx->post([&, this] { shutdown(cv); });
Sébastien Blin91cda3c2024-01-10 16:21:18 -0500113
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400114 if (logger_) logger_->debug("Waiting for shutdown ...");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400115
116 if (cv.wait_for(lk, std::chrono::seconds(30), [this] { return shutdownComplete_; })) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400117 if (logger_) logger_->debug("Shutdown completed");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400118 } else {
François-Simon Fauteux-Chapleau808db4f2024-04-19 11:39:47 -0400119 if (logger_) logger_->error("Shutdown timed out");
120 shutdownTimedOut_ = true;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400121 }
François-Simon Fauteux-Chapleau648907c2024-02-06 15:16:48 -0500122 // NOTE: It's important to unlock mappingMutex_ here, otherwise we get a
123 // deadlock when the call to cv.wait_for() above times out before we return
124 // from proto->terminate() in shutdown(cv).
125 lk.unlock();
Adrien Béraud91fd4b62023-08-29 20:50:01 -0400126
127 if (ioContextRunner_) {
Sébastien Blin91cda3c2024-01-10 16:21:18 -0500128 if (logger_) logger_->debug("UPnPContext: stopping io_context thread {}", fmt::ptr(this));
Adrien Béraud91fd4b62023-08-29 20:50:01 -0400129 ctx->stop();
130 ioContextRunner_->join();
131 ioContextRunner_.reset();
Sébastien Blin91cda3c2024-01-10 16:21:18 -0500132 if (logger_) logger_->debug("UPnPContext: stopping io_context thread - finished {}", fmt::ptr(this));
Adrien Béraud91fd4b62023-08-29 20:50:01 -0400133 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400134}
135
136UPnPContext::~UPnPContext()
137{
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400138 if (logger_) logger_->debug("UPnPContext instance [{}] destroyed", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400139}
140
141void
142UPnPContext::init()
143{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400144#if HAVE_LIBNATPMP
Adrien Béraud370257c2023-08-15 20:53:09 -0400145 auto natPmp = std::make_shared<NatPmp>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400146 natPmp->setObserver(this);
147 protocolList_.emplace(NatProtocolType::NAT_PMP, std::move(natPmp));
148#endif
149
150#if HAVE_LIBUPNP
Adrien Béraud370257c2023-08-15 20:53:09 -0400151 auto pupnp = std::make_shared<PUPnP>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400152 pupnp->setObserver(this);
153 protocolList_.emplace(NatProtocolType::PUPNP, std::move(pupnp));
154#endif
155}
156
157void
158UPnPContext::startUpnp()
159{
160 assert(not controllerList_.empty());
161
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400162 if (logger_) logger_->debug("Starting UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400163
164 // Request a new IGD search.
165 for (auto const& [_, protocol] : protocolList_) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400166 ctx->dispatch([p=protocol] { p->searchForIgd(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400167 }
168
169 started_ = true;
170}
171
172void
173UPnPContext::stopUpnp(bool forceRelease)
174{
François-Simon Fauteux-Chapleau808db4f2024-04-19 11:39:47 -0400175 if (logger_) logger_->debug("Stopping UPnP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400176
177 // Clear all current mappings if any.
178
179 // Use a temporary list to avoid processing the mapping
180 // list while holding the lock.
181 std::list<Mapping::sharedPtr_t> toRemoveList;
182 {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500183 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400184
185 PortType types[2] {PortType::TCP, PortType::UDP};
186 for (auto& type : types) {
187 auto& mappingList = getMappingList(type);
188 for (auto const& [_, map] : mappingList) {
189 toRemoveList.emplace_back(map);
190 }
191 }
192 // Invalidate the current IGDs.
193 preferredIgd_.reset();
194 validIgdList_.clear();
195 }
196 for (auto const& map : toRemoveList) {
197 requestRemoveMapping(map);
198
Adrien Béraud370257c2023-08-15 20:53:09 -0400199 // Notify is not needed in updateState when
Adrien Béraud612b55b2023-05-29 10:42:04 -0400200 // shutting down (hence set it to false). NotifyCallback
201 // would trigger a new SIP registration and create a
202 // false registered state upon program close.
203 // It's handled by upper layers.
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400204 updateMappingState(map, MappingState::FAILED, false);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400205 // We dont remove mappings with auto-update enabled,
206 // unless forceRelease is true.
207 if (not map->getAutoUpdate() or forceRelease) {
208 map->enableAutoUpdate(false);
209 unregisterMapping(map);
210 }
211 }
212
213 // Clear all current IGDs.
214 for (auto const& [_, protocol] : protocolList_) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400215 ctx->dispatch([p=protocol]{ p->clearIgds(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400216 }
217
218 started_ = false;
219}
220
221uint16_t
222UPnPContext::generateRandomPort(PortType type, bool mustBeEven)
223{
224 auto minPort = type == PortType::TCP ? UPNP_TCP_PORT_MIN : UPNP_UDP_PORT_MIN;
225 auto maxPort = type == PortType::TCP ? UPNP_TCP_PORT_MAX : UPNP_UDP_PORT_MAX;
226
227 if (minPort >= maxPort) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400228 // if (logger_) logger_->error("Max port number ({}) must be greater than min port number ({})", maxPort, minPort);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400229 // Must be called with valid range.
230 assert(false);
231 }
232
233 int fact = mustBeEven ? 2 : 1;
234 if (mustBeEven) {
235 minPort /= fact;
236 maxPort /= fact;
237 }
238
239 // Seed the generator.
240 static std::mt19937 gen(dht::crypto::getSeededRandomEngine());
241 // Define the range.
242 std::uniform_int_distribution<uint16_t> dist(minPort, maxPort);
243 return dist(gen) * fact;
244}
245
246void
247UPnPContext::connectivityChanged()
248{
Adrien Béraudc36965c2023-08-17 21:50:27 -0400249 // Debounce the connectivity change notification.
250 connectivityChangedTimer_.expires_after(std::chrono::milliseconds(50));
251 connectivityChangedTimer_.async_wait(std::bind(&UPnPContext::_connectivityChanged, this, std::placeholders::_1));
252}
253
254void
255UPnPContext::_connectivityChanged(const asio::error_code& ec)
256{
257 if (ec == asio::error::operation_aborted)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400258 return;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400259
260 auto hostAddr = ip_utils::getLocalAddr(AF_INET);
261
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400262 if (logger_) logger_->debug("Connectivity change check: host address {}", hostAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400263
264 auto restartUpnp = false;
265
266 // On reception of "connectivity change" notification, the UPNP search
267 // will be restarted if either there is no valid IGD, or the IGD address
268 // changed.
269
270 if (not isReady()) {
271 restartUpnp = true;
272 } else {
273 // Check if the host address changed.
274 for (auto const& [_, protocol] : protocolList_) {
275 if (protocol->isReady() and hostAddr != protocol->getHostAddress()) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400276 if (logger_) logger_->warn("Host address changed from {} to {}",
277 protocol->getHostAddress().toString(),
278 hostAddr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400279 protocol->clearIgds();
280 restartUpnp = true;
281 break;
282 }
283 }
284 }
285
286 // We have at least one valid IGD and the host address did
287 // not change, so no need to restart.
288 if (not restartUpnp) {
289 return;
290 }
291
292 // No registered controller. A new search will be performed when
293 // a controller is registered.
294 if (controllerList_.empty())
295 return;
296
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400297 if (logger_) logger_->debug("Connectivity changed. Clear the IGDs and restart");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400298
299 stopUpnp();
300 startUpnp();
301
302 // Mapping with auto update enabled must be processed first.
303 processMappingWithAutoUpdate();
304}
305
306void
307UPnPContext::setPublicAddress(const IpAddr& addr)
308{
309 if (not addr)
310 return;
311
Adrien Béraud024c46f2024-03-02 23:53:18 -0500312 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400313 if (knownPublicAddress_ != addr) {
314 knownPublicAddress_ = std::move(addr);
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400315 if (logger_) logger_->debug("Setting the known public address to {}", addr.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400316 }
317}
318
319bool
320UPnPContext::isReady() const
321{
Adrien Béraud024c46f2024-03-02 23:53:18 -0500322 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400323 return not validIgdList_.empty();
324}
325
326IpAddr
327UPnPContext::getExternalIP() const
328{
Adrien Béraud024c46f2024-03-02 23:53:18 -0500329 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400330 // Return the first IGD Ip available.
331 if (not validIgdList_.empty()) {
332 return (*validIgdList_.begin())->getPublicIp();
333 }
334 return {};
335}
336
337Mapping::sharedPtr_t
338UPnPContext::reserveMapping(Mapping& requestedMap)
339{
340 auto desiredPort = requestedMap.getExternalPort();
341
342 if (desiredPort == 0) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400343 if (logger_) logger_->debug("Desired port is not set, will provide the first available port for [{}]",
344 requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400345 } else {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400346 if (logger_) logger_->debug("Try to find mapping for port {:d} [{}]", desiredPort, requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400347 }
348
349 Mapping::sharedPtr_t mapRes;
350
351 {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500352 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400353 auto& mappingList = getMappingList(requestedMap.getType());
354
355 // We try to provide a mapping in "OPEN" state. If not found,
356 // we provide any available mapping. In this case, it's up to
357 // the caller to use it or not.
358 for (auto const& [_, map] : mappingList) {
359 // If the desired port is null, we pick the first available port.
360 if (map->isValid() and (desiredPort == 0 or map->getExternalPort() == desiredPort)
361 and map->isAvailable()) {
362 // Considere the first available mapping regardless of its
363 // state. A mapping with OPEN state will be used if found.
364 if (not mapRes)
365 mapRes = map;
366
367 if (map->getState() == MappingState::OPEN) {
368 // Found an "OPEN" mapping. We are done.
369 mapRes = map;
370 break;
371 }
372 }
373 }
374 }
375
376 // Create a mapping if none was available.
377 if (not mapRes) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400378 // JAMI_WARN("Did not find any available mapping. Will request one now");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400379 mapRes = registerMapping(requestedMap);
380 }
381
382 if (mapRes) {
383 // Make the mapping unavailable
384 mapRes->setAvailable(false);
385 // Copy attributes.
386 mapRes->setNotifyCallback(requestedMap.getNotifyCallback());
387 mapRes->enableAutoUpdate(requestedMap.getAutoUpdate());
388 // Notify the listener.
389 if (auto cb = mapRes->getNotifyCallback())
390 cb(mapRes);
391 }
392
393 updateMappingList(true);
394
395 return mapRes;
396}
397
398void
399UPnPContext::releaseMapping(const Mapping& map)
400{
Adrien Béraudc36965c2023-08-17 21:50:27 -0400401 ctx->dispatch([this, map] {
Sébastien Blin91cda3c2024-01-10 16:21:18 -0500402 if (shutdownComplete_)
403 return;
Adrien Béraudc36965c2023-08-17 21:50:27 -0400404 auto mapPtr = getMappingWithKey(map.getMapKey());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400405
Adrien Béraudc36965c2023-08-17 21:50:27 -0400406 if (not mapPtr) {
407 // Might happen if the mapping failed or was never granted.
408 if (logger_) logger_->debug("Mapping {} does not exist or was already removed", map.toString());
409 return;
410 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400411
Adrien Béraudc36965c2023-08-17 21:50:27 -0400412 if (mapPtr->isAvailable()) {
413 if (logger_) logger_->warn("Trying to release an unused mapping {}", mapPtr->toString());
414 return;
415 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400416
Adrien Béraudc36965c2023-08-17 21:50:27 -0400417 // Remove it.
418 requestRemoveMapping(mapPtr);
419 unregisterMapping(mapPtr);
420 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400421}
422
423void
424UPnPContext::registerController(void* controller)
425{
426 {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500427 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400428 if (shutdownComplete_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400429 if (logger_) logger_->warn("UPnPContext already shut down");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400430 return;
431 }
Adrien Béraudc36965c2023-08-17 21:50:27 -0400432 auto ret = controllerList_.emplace(controller);
433 if (not ret.second) {
434 if (logger_) logger_->warn("Controller {} is already registered", fmt::ptr(controller));
435 return;
436 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400437 }
438
Adrien Berauda8731ac2023-08-17 12:19:39 -0400439 if (logger_) logger_->debug("Successfully registered controller {}", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400440 if (not started_)
441 startUpnp();
442}
443
444void
445UPnPContext::unregisterController(void* controller)
446{
Sébastien Blin91cda3c2024-01-10 16:21:18 -0500447 if (shutdownComplete_)
448 return;
Adrien Béraud024c46f2024-03-02 23:53:18 -0500449 std::unique_lock lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400450 if (controllerList_.erase(controller) == 1) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400451 if (logger_) logger_->debug("Successfully unregistered controller {}", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400452 } else {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400453 if (logger_) logger_->debug("Controller {} was already removed", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400454 }
455
456 if (controllerList_.empty()) {
Adrien Béraudc36965c2023-08-17 21:50:27 -0400457 lock.unlock();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400458 stopUpnp();
459 }
460}
461
François-Simon Fauteux-Chapleau826f0ba2024-05-29 15:22:21 -0400462std::vector<IGDInfo>
463UPnPContext::getIgdsInfo() const
464{
465 std::vector<IGDInfo> igdInfoList;
466
467 std::lock_guard lk(mappingMutex_);
468 for (auto& igd : validIgdList_) {
469 auto protocol = protocolList_.at(igd->getProtocol());
470
471 IGDInfo info;
472 info.uid = igd->getUID();
473 info.localIp = igd->getLocalIp();
474 info.publicIp = igd->getPublicIp();
475 info.mappingInfoList = protocol->getMappingsInfo(igd);
476
477 igdInfoList.push_back(std::move(info));
478 }
479
480 return igdInfoList;
481}
482
Adrien Béraud612b55b2023-05-29 10:42:04 -0400483uint16_t
484UPnPContext::getAvailablePortNumber(PortType type)
485{
486 // Only return an availalable random port. No actual
487 // reservation is made here.
488
Adrien Béraud024c46f2024-03-02 23:53:18 -0500489 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400490 auto& mappingList = getMappingList(type);
491 int tryCount = 0;
492 while (tryCount++ < MAX_REQUEST_RETRIES) {
493 uint16_t port = generateRandomPort(type);
494 Mapping map(type, port, port);
495 if (mappingList.find(map.getMapKey()) == mappingList.end())
496 return port;
497 }
498
499 // Very unlikely to get here.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400500 if (logger_) logger_->error("Could not find an available port after %i trials", MAX_REQUEST_RETRIES);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400501 return 0;
502}
503
504void
505UPnPContext::requestMapping(const Mapping::sharedPtr_t& map)
506{
507 assert(map);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400508 auto const& igd = getPreferredIgd();
509 // We must have at least a valid IGD pointer if we get here.
510 // Not this method is called only if there were a valid IGD, however,
511 // because the processing is asynchronous, it's possible that the IGD
512 // was invalidated when the this code executed.
513 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400514 if (logger_) logger_->debug("No valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400515 return;
516 }
517
518 map->setIgd(igd);
519
Adrien Berauda8731ac2023-08-17 12:19:39 -0400520 if (logger_) logger_->debug("Request mapping {} using protocol [{}] IGD [{}]",
521 map->toString(),
522 igd->getProtocolName(),
523 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400524
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400525 updateMappingState(map, MappingState::IN_PROGRESS);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400526
527 auto const& protocol = protocolList_.at(igd->getProtocol());
528 protocol->requestMappingAdd(*map);
529}
530
531bool
532UPnPContext::provisionNewMappings(PortType type, int portCount)
533{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400534 if (logger_) logger_->debug("Provision {:d} new mappings of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400535
536 assert(portCount > 0);
537
538 while (portCount > 0) {
539 auto port = getAvailablePortNumber(type);
540 if (port > 0) {
541 // Found an available port number
542 portCount--;
543 Mapping map(type, port, port, true);
544 registerMapping(map);
545 } else {
546 // Very unlikely to get here!
Adrien Berauda8731ac2023-08-17 12:19:39 -0400547 if (logger_) logger_->error("Can not find any available port to provision!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400548 return false;
549 }
550 }
551
552 return true;
553}
554
555bool
556UPnPContext::deleteUnneededMappings(PortType type, int portCount)
557{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400558 if (logger_) logger_->debug("Remove {:d} unneeded mapping of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400559
560 assert(portCount > 0);
561
Adrien Béraud370257c2023-08-15 20:53:09 -0400562 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400563
Adrien Béraud024c46f2024-03-02 23:53:18 -0500564 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400565 auto& mappingList = getMappingList(type);
566
567 for (auto it = mappingList.begin(); it != mappingList.end();) {
568 auto map = it->second;
569 assert(map);
570
571 if (not map->isAvailable()) {
572 it++;
573 continue;
574 }
575
576 if (map->getState() == MappingState::OPEN and portCount > 0) {
577 // Close portCount mappings in "OPEN" state.
578 requestRemoveMapping(map);
Adrien Béraud370257c2023-08-15 20:53:09 -0400579 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400580 portCount--;
581 } else if (map->getState() != MappingState::OPEN) {
582 // If this methods is called, it means there are more open
583 // mappings than required. So, all mappings in a state other
584 // than "OPEN" state (typically in in-progress state) will
585 // be deleted as well.
Adrien Béraud370257c2023-08-15 20:53:09 -0400586 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400587 } else {
588 it++;
589 }
590 }
591
592 return true;
593}
594
595void
596UPnPContext::updatePreferredIgd()
597{
Adrien Béraud370257c2023-08-15 20:53:09 -0400598 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400599
600 if (preferredIgd_ and preferredIgd_->isValid())
601 return;
602
603 // Reset and search for the best IGD.
604 preferredIgd_.reset();
605
606 for (auto const& [_, protocol] : protocolList_) {
607 if (protocol->isReady()) {
608 auto igdList = protocol->getIgdList();
609 assert(not igdList.empty());
610 auto const& igd = igdList.front();
611 if (not igd->isValid())
612 continue;
613
614 // Prefer NAT-PMP over PUPNP.
615 if (preferredIgd_ and igd->getProtocol() != NatProtocolType::NAT_PMP)
616 continue;
617
618 // Update.
619 preferredIgd_ = igd;
620 }
621 }
622
623 if (preferredIgd_ and preferredIgd_->isValid()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400624 if (logger_) logger_->debug("Preferred IGD updated to [{}] IGD [{} {}] ",
625 preferredIgd_->getProtocolName(),
626 preferredIgd_->getUID(),
627 preferredIgd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400628 }
629}
630
631std::shared_ptr<IGD>
632UPnPContext::getPreferredIgd() const
633{
Adrien Béraud370257c2023-08-15 20:53:09 -0400634 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400635
636 return preferredIgd_;
637}
638
639void
640UPnPContext::updateMappingList(bool async)
641{
642 // Run async if requested.
643 if (async) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400644 ctx->post([this] { updateMappingList(false); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400645 return;
646 }
647
Adrien Béraud370257c2023-08-15 20:53:09 -0400648 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400649
650 // Update the preferred IGD.
651 updatePreferredIgd();
652
Adrien Béraud25c30c42023-07-05 13:46:54 -0400653 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400654
655 // Skip if no controller registered.
656 if (controllerList_.empty())
657 return;
658
659 // Cancel the current timer (if any) and re-schedule.
660 std::shared_ptr<IGD> prefIgd = getPreferredIgd();
661 if (not prefIgd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400662 if (logger_) logger_->debug("UPNP/NAT-PMP enabled, but no valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400663 // No valid IGD. Nothing to do.
664 return;
665 }
666
Adrien Berauda8731ac2023-08-17 12:19:39 -0400667 mappingListUpdateTimer_.expires_after(MAP_UPDATE_INTERVAL);
Adrien Béraud25c30c42023-07-05 13:46:54 -0400668 mappingListUpdateTimer_.async_wait([this](asio::error_code const& ec) {
669 if (ec != asio::error::operation_aborted)
670 updateMappingList(false);
671 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400672
673 // Process pending requests if any.
674 processPendingRequests(prefIgd);
675
676 // Make new requests for mappings that failed and have
677 // the auto-update option enabled.
678 processMappingWithAutoUpdate();
679
680 PortType typeArray[2] = {PortType::TCP, PortType::UDP};
681
682 for (auto idx : {0, 1}) {
683 auto type = typeArray[idx];
684
685 MappingStatus status;
686 getMappingStatus(type, status);
687
Adrien Berauda8731ac2023-08-17 12:19:39 -0400688 if (logger_) logger_->debug("Mapping status [{}] - overall {:d}: {:d} open ({:d} ready + {:d} in use), {:d} pending, {:d} "
689 "in-progress, {:d} failed",
690 Mapping::getTypeStr(type),
691 status.sum(),
692 status.openCount_,
693 status.readyCount_,
694 status.openCount_ - status.readyCount_,
695 status.pendingCount_,
696 status.inProgressCount_,
697 status.failedCount_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400698
699 if (status.failedCount_ > 0) {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500700 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400701 auto const& mappingList = getMappingList(type);
702 for (auto const& [_, map] : mappingList) {
703 if (map->getState() == MappingState::FAILED) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400704 if (logger_) logger_->debug("Mapping status [{}] - Available [{}]",
705 map->toString(true),
706 map->isAvailable() ? "YES" : "NO");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400707 }
708 }
709 }
710
711 int toRequestCount = (int) minOpenPortLimit_[idx]
712 - (int) (status.readyCount_ + status.inProgressCount_
713 + status.pendingCount_);
714
715 // Provision/release mappings accordingly.
716 if (toRequestCount > 0) {
717 // Take into account the request in-progress when making
718 // requests for new mappings.
719 provisionNewMappings(type, toRequestCount);
720 } else if (status.readyCount_ > maxOpenPortLimit_[idx]) {
721 deleteUnneededMappings(type, status.readyCount_ - maxOpenPortLimit_[idx]);
722 }
723 }
724
725 // Prune the mapping list if needed
726 if (protocolList_.at(NatProtocolType::PUPNP)->isReady()) {
727#if HAVE_LIBNATPMP
728 // Dont perform if NAT-PMP is valid.
729 if (not protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
730#endif
731 {
732 pruneMappingList();
733 }
734 }
735
736#if HAVE_LIBNATPMP
737 // Renew nat-pmp allocations
738 if (protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
739 renewAllocations();
740#endif
741}
742
743void
744UPnPContext::pruneMappingList()
745{
Adrien Béraud370257c2023-08-15 20:53:09 -0400746 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400747
748 MappingStatus status;
749 getMappingStatus(status);
750
751 // Do not prune the list if there are pending/in-progress requests.
752 if (status.inProgressCount_ != 0 or status.pendingCount_ != 0) {
753 return;
754 }
755
756 auto const& igd = getPreferredIgd();
757 if (not igd or igd->getProtocol() != NatProtocolType::PUPNP) {
758 return;
759 }
760 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
761
762 auto remoteMapList = protocol->getMappingsListByDescr(igd,
763 Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
Adrien Berauda0683d12023-08-22 18:09:02 -0400764 /*if (remoteMapList.empty()) {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500765 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400766 if (not getMappingList(PortType::TCP).empty() or getMappingList(PortType::TCP).empty()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400767 // JAMI_WARN("We have provisionned mappings but the PUPNP IGD returned an empty list!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400768 }
Adrien Berauda0683d12023-08-22 18:09:02 -0400769 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400770
771 pruneUnMatchedMappings(igd, remoteMapList);
772 pruneUnTrackedMappings(igd, remoteMapList);
773}
774
775void
776UPnPContext::pruneUnMatchedMappings(const std::shared_ptr<IGD>& igd,
777 const std::map<Mapping::key_t, Mapping>& remoteMapList)
778{
779 // Check/synchronize local mapping list with the list
780 // returned by the IGD.
781
Adrien Berauda0683d12023-08-22 18:09:02 -0400782 for (auto type: {PortType::TCP, PortType::UDP}) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400783 // Use a temporary list to avoid processing mappings while holding the lock.
784 std::list<Mapping::sharedPtr_t> toRemoveList;
785 {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500786 std::lock_guard lock(mappingMutex_);
Adrien Berauda0683d12023-08-22 18:09:02 -0400787 for (auto const& [_, map] : getMappingList(type)) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400788 // Only check mappings allocated by UPNP protocol.
789 if (map->getProtocol() != NatProtocolType::PUPNP) {
790 continue;
791 }
792 // Set mapping as failed if not found in the list
793 // returned by the IGD.
794 if (map->getState() == MappingState::OPEN
795 and remoteMapList.find(map->getMapKey()) == remoteMapList.end()) {
796 toRemoveList.emplace_back(map);
797
Adrien Berauda8731ac2023-08-17 12:19:39 -0400798 if (logger_) logger_->warn("Mapping {} (IGD {}) marked as \"OPEN\" but not found in the "
799 "remote list. Mark as failed!",
800 map->toString(),
801 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400802 }
803 }
804 }
805
806 for (auto const& map : toRemoveList) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400807 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400808 unregisterMapping(map);
809 }
810 }
811}
812
813void
814UPnPContext::pruneUnTrackedMappings(const std::shared_ptr<IGD>& igd,
815 const std::map<Mapping::key_t, Mapping>& remoteMapList)
816{
817 // Use a temporary list to avoid processing mappings while holding the lock.
818 std::list<Mapping> toRemoveList;
819 {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500820 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400821
822 for (auto const& [_, map] : remoteMapList) {
823 // Must has valid IGD pointer and use UPNP protocol.
824 assert(map.getIgd());
825 assert(map.getIgd()->getProtocol() == NatProtocolType::PUPNP);
826 auto& mappingList = getMappingList(map.getType());
827 auto it = mappingList.find(map.getMapKey());
828 if (it == mappingList.end()) {
829 // Not present, request mapping remove.
830 toRemoveList.emplace_back(std::move(map));
831 // Make only few remove requests at once.
832 if (toRemoveList.size() >= MAX_REQUEST_REMOVE_COUNT)
833 break;
834 }
835 }
836 }
837
838 // Remove un-tracked mappings.
839 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
840 for (auto const& map : toRemoveList) {
841 protocol->requestMappingRemove(map);
842 }
843}
844
845void
846UPnPContext::pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd)
847{
Adrien Béraud370257c2023-08-15 20:53:09 -0400848 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400849
850 // Use temporary list to avoid holding the lock while
851 // processing the mapping list.
852 std::list<Mapping::sharedPtr_t> toRemoveList;
853 {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500854 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400855
856 PortType types[2] {PortType::TCP, PortType::UDP};
857 for (auto& type : types) {
858 auto& mappingList = getMappingList(type);
859 for (auto const& [_, map] : mappingList) {
860 if (map->getIgd() == igd)
861 toRemoveList.emplace_back(map);
862 }
863 }
864 }
865
866 for (auto const& map : toRemoveList) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400867 if (logger_) logger_->debug("Remove mapping {} (has an invalid IGD {} [{}])",
868 map->toString(),
869 igd->toString(),
870 igd->getProtocolName());
Adrien Béraud56740312023-08-23 08:38:28 -0400871 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400872 unregisterMapping(map);
873 }
874}
875
876void
877UPnPContext::processPendingRequests(const std::shared_ptr<IGD>& igd)
878{
879 // This list holds the mappings to be requested. This is
880 // needed to avoid performing the requests while holding
881 // the lock.
882 std::list<Mapping::sharedPtr_t> requestsList;
883
884 // Populate the list of requests to perform.
885 {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500886 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400887 PortType typeArray[2] {PortType::TCP, PortType::UDP};
888
889 for (auto type : typeArray) {
890 auto& mappingList = getMappingList(type);
891 for (auto& [_, map] : mappingList) {
892 if (map->getState() == MappingState::PENDING) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400893 if (logger_) logger_->debug("Send pending request for mapping {} to IGD {}",
894 map->toString(),
895 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400896 requestsList.emplace_back(map);
897 }
898 }
899 }
900 }
901
902 // Process the pending requests.
903 for (auto const& map : requestsList) {
904 requestMapping(map);
905 }
906}
907
908void
909UPnPContext::processMappingWithAutoUpdate()
910{
911 // This list holds the mappings to be requested. This is
912 // needed to avoid performing the requests while holding
913 // the lock.
914 std::list<Mapping::sharedPtr_t> requestsList;
915
916 // Populate the list of requests for mappings with auto-update enabled.
917 {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500918 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400919 PortType typeArray[2] {PortType::TCP, PortType::UDP};
920
921 for (auto type : typeArray) {
922 auto& mappingList = getMappingList(type);
923 for (auto const& [_, map] : mappingList) {
924 if (map->getState() == MappingState::FAILED and map->getAutoUpdate()) {
925 requestsList.emplace_back(map);
926 }
927 }
928 }
929 }
930
931 for (auto const& oldMap : requestsList) {
932 // Request a new mapping if auto-update is enabled.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400933 if (logger_) logger_->debug("Mapping {} has auto-update enabled, a new mapping will be requested",
934 oldMap->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400935
936 // Reserve a new mapping.
937 Mapping newMapping(oldMap->getType());
938 newMapping.enableAutoUpdate(true);
939 newMapping.setNotifyCallback(oldMap->getNotifyCallback());
940
941 auto const& mapPtr = reserveMapping(newMapping);
942 assert(mapPtr);
943
944 // Release the old one.
945 oldMap->setAvailable(true);
946 oldMap->enableAutoUpdate(false);
947 oldMap->setNotifyCallback(nullptr);
948 unregisterMapping(oldMap);
949 }
950}
951
952void
953UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event)
954{
955 assert(igd);
956
Adrien Béraud612b55b2023-05-29 10:42:04 -0400957 // Reset to start search for a new best IGD.
958 preferredIgd_.reset();
959
960 char const* IgdState = event == UpnpIgdEvent::ADDED ? "ADDED"
961 : event == UpnpIgdEvent::REMOVED ? "REMOVED"
962 : "INVALID";
963
964 auto const& igdLocalAddr = igd->getLocalIp();
965 auto protocolName = igd->getProtocolName();
966
Adrien Berauda8731ac2023-08-17 12:19:39 -0400967 if (logger_) logger_->debug("New event for IGD [{} {}] [{}]: [{}]",
968 igd->getUID(),
969 igd->toString(),
970 protocolName,
971 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400972
973 // Check if the IGD has valid addresses.
974 if (not igdLocalAddr) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400975 if (logger_) logger_->warn("[{}] IGD has an invalid local address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400976 return;
977 }
978
979 if (not igd->getPublicIp()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400980 if (logger_) logger_->warn("[{}] IGD has an invalid public address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400981 return;
982 }
983
984 if (knownPublicAddress_ and igd->getPublicIp() != knownPublicAddress_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400985 if (logger_) logger_->warn("[{}] IGD external address [{}] does not match known public address [{}]."
986 " The mapped addresses might not be reachable",
987 protocolName,
988 igd->getPublicIp().toString(),
989 knownPublicAddress_.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400990 }
991
992 // The IGD was removed or is invalid.
993 if (event == UpnpIgdEvent::REMOVED or event == UpnpIgdEvent::INVALID_STATE) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400994 if (logger_) logger_->warn("State of IGD [{} {}] [{}] changed to [{}]. Pruning the mapping list",
995 igd->getUID(),
996 igd->toString(),
997 protocolName,
998 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400999
1000 pruneMappingsWithInvalidIgds(igd);
1001
Adrien Béraud024c46f2024-03-02 23:53:18 -05001002 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001003 validIgdList_.erase(igd);
1004 return;
1005 }
1006
1007 // Update the IGD list.
1008 {
Adrien Béraud024c46f2024-03-02 23:53:18 -05001009 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001010 auto ret = validIgdList_.emplace(igd);
1011 if (ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001012 if (logger_) logger_->debug("IGD [{}] on address {} was added. Will process any pending requests",
1013 protocolName,
1014 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001015 } else {
1016 // Already in the list.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001017 if (logger_) logger_->error("IGD [{}] on address {} already in the list",
1018 protocolName,
1019 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001020 return;
1021 }
1022 }
1023
1024 // Update the provisionned mappings.
1025 updateMappingList(false);
1026}
1027
1028void
1029UPnPContext::onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1030{
Adrien Béraud370257c2023-08-15 20:53:09 -04001031 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001032
1033 // Check if we have a pending request for this response.
1034 auto map = getMappingWithKey(mapRes.getMapKey());
1035 if (not map) {
1036 // We may receive a response for a canceled request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001037 if (logger_) logger_->debug("Response for mapping {} [IGD {}] [{}] does not have a local match",
1038 mapRes.toString(),
1039 igd->toString(),
1040 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001041 return;
1042 }
1043
1044 // The mapping request is new and successful. Update.
1045 map->setIgd(igd);
1046 map->setInternalAddress(mapRes.getInternalAddress());
1047 map->setExternalPort(mapRes.getExternalPort());
1048
1049 // Update the state and report to the owner.
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001050 updateMappingState(map, MappingState::OPEN);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001051
Adrien Berauda8731ac2023-08-17 12:19:39 -04001052 if (logger_) logger_->debug("Mapping {} (on IGD {} [{}]) successfully performed",
1053 map->toString(),
1054 igd->toString(),
1055 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001056
1057 // Call setValid() to reset the errors counter. We need
1058 // to reset the counter on each successful response.
1059 igd->setValid(true);
1060}
1061
1062#if HAVE_LIBNATPMP
1063void
1064UPnPContext::onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map)
1065{
1066 auto mapPtr = getMappingWithKey(map.getMapKey());
1067
1068 if (not mapPtr) {
1069 // We may receive a notification for a canceled request. Ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001070 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] does not have a match in local list",
1071 map.toString(),
1072 igd->toString(),
1073 map.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001074 return;
1075 }
1076 if (mapPtr->getProtocol() != NatProtocolType::NAT_PMP or not mapPtr->isValid()
1077 or mapPtr->getState() != MappingState::OPEN) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001078 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] is in unexpected state",
1079 mapPtr->toString(),
1080 igd->toString(),
1081 mapPtr->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001082 return;
1083 }
1084
1085 mapPtr->setRenewalTime(map.getRenewalTime());
1086}
1087#endif
1088
1089void
1090UPnPContext::requestRemoveMapping(const Mapping::sharedPtr_t& map)
1091{
Adrien Béraud370257c2023-08-15 20:53:09 -04001092 if (not map or not map->isValid()) {
Adrien Béraud612b55b2023-05-29 10:42:04 -04001093 // Silently ignore if the mapping is invalid
1094 return;
1095 }
Adrien Béraud612b55b2023-05-29 10:42:04 -04001096 auto protocol = protocolList_.at(map->getIgd()->getProtocol());
1097 protocol->requestMappingRemove(*map);
1098}
1099
1100void
1101UPnPContext::deleteAllMappings(PortType type)
1102{
Adrien Béraud024c46f2024-03-02 23:53:18 -05001103 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001104 auto& mappingList = getMappingList(type);
1105
1106 for (auto const& [_, map] : mappingList) {
1107 requestRemoveMapping(map);
1108 }
1109}
1110
1111void
1112UPnPContext::onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1113{
1114 if (not mapRes.isValid())
1115 return;
1116
Adrien Béraud612b55b2023-05-29 10:42:04 -04001117 auto map = getMappingWithKey(mapRes.getMapKey());
1118 // Notify the listener.
1119 if (map and map->getNotifyCallback())
1120 map->getNotifyCallback()(map);
1121}
1122
1123Mapping::sharedPtr_t
1124UPnPContext::registerMapping(Mapping& map)
1125{
1126 if (map.getExternalPort() == 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001127 // JAMI_DBG("Port number not set. Will set a random port number");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001128 auto port = getAvailablePortNumber(map.getType());
1129 map.setExternalPort(port);
1130 map.setInternalPort(port);
1131 }
1132
1133 // Newly added mapping must be in pending state by default.
1134 map.setState(MappingState::PENDING);
1135
1136 Mapping::sharedPtr_t mapPtr;
1137
1138 {
Adrien Béraud024c46f2024-03-02 23:53:18 -05001139 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001140 auto& mappingList = getMappingList(map.getType());
1141
1142 auto ret = mappingList.emplace(map.getMapKey(), std::make_shared<Mapping>(map));
1143 if (not ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001144 if (logger_) logger_->warn("Mapping request for {} already added!", map.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001145 return {};
1146 }
1147 mapPtr = ret.first->second;
1148 assert(mapPtr);
1149 }
1150
1151 // No available IGD. The pending mapping requests will be processed
1152 // when a IGD becomes available (in onIgdAdded() method).
1153 if (not isReady()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001154 if (logger_) logger_->warn("No IGD available. Mapping will be requested when an IGD becomes available");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001155 } else {
1156 requestMapping(mapPtr);
1157 }
1158
1159 return mapPtr;
1160}
1161
Adrien Béraud612b55b2023-05-29 10:42:04 -04001162void
1163UPnPContext::unregisterMapping(const Mapping::sharedPtr_t& map)
1164{
Adrien Béraud370257c2023-08-15 20:53:09 -04001165 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001166
1167 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001168 // JAMI_ERR("Mapping pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001169 return;
1170 }
1171
1172 if (map->getAutoUpdate()) {
1173 // Dont unregister mappings with auto-update enabled.
1174 return;
1175 }
1176 auto& mappingList = getMappingList(map->getType());
1177
1178 if (mappingList.erase(map->getMapKey()) == 1) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001179 if (logger_) logger_->debug("Unregistered mapping {}", map->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001180 } else {
1181 // The mapping may already be un-registered. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001182 if (logger_) logger_->debug("Mapping {} [{}] does not have a local match",
1183 map->toString(),
1184 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001185 }
1186}
1187
1188std::map<Mapping::key_t, Mapping::sharedPtr_t>&
1189UPnPContext::getMappingList(PortType type)
1190{
1191 unsigned typeIdx = type == PortType::TCP ? 0 : 1;
1192 return mappingList_[typeIdx];
1193}
1194
1195Mapping::sharedPtr_t
1196UPnPContext::getMappingWithKey(Mapping::key_t key)
1197{
Adrien Béraud024c46f2024-03-02 23:53:18 -05001198 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001199 auto const& mappingList = getMappingList(Mapping::getTypeFromMapKey(key));
1200 auto it = mappingList.find(key);
1201 if (it == mappingList.end())
1202 return nullptr;
1203 return it->second;
1204}
1205
1206void
1207UPnPContext::getMappingStatus(PortType type, MappingStatus& status)
1208{
Adrien Béraud024c46f2024-03-02 23:53:18 -05001209 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001210 auto& mappingList = getMappingList(type);
1211
1212 for (auto const& [_, map] : mappingList) {
1213 switch (map->getState()) {
1214 case MappingState::PENDING: {
1215 status.pendingCount_++;
1216 break;
1217 }
1218 case MappingState::IN_PROGRESS: {
1219 status.inProgressCount_++;
1220 break;
1221 }
1222 case MappingState::FAILED: {
1223 status.failedCount_++;
1224 break;
1225 }
1226 case MappingState::OPEN: {
1227 status.openCount_++;
1228 if (map->isAvailable())
1229 status.readyCount_++;
1230 break;
1231 }
1232
1233 default:
1234 // Must not get here.
1235 assert(false);
1236 break;
1237 }
1238 }
1239}
1240
1241void
1242UPnPContext::getMappingStatus(MappingStatus& status)
1243{
1244 getMappingStatus(PortType::TCP, status);
1245 getMappingStatus(PortType::UDP, status);
1246}
1247
1248void
1249UPnPContext::onMappingRequestFailed(const Mapping& mapRes)
1250{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001251 auto const& map = getMappingWithKey(mapRes.getMapKey());
1252 if (not map) {
1253 // We may receive a response for a removed request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001254 if (logger_) logger_->debug("Mapping {} [IGD {}] does not have a local match",
1255 mapRes.toString(),
1256 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001257 return;
1258 }
1259
1260 auto igd = map->getIgd();
1261 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001262 if (logger_) logger_->error("IGD pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001263 return;
1264 }
1265
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001266 updateMappingState(map, MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001267 unregisterMapping(map);
1268
Adrien Berauda8731ac2023-08-17 12:19:39 -04001269 if (logger_) logger_->warn("Mapping request for {} failed on IGD {} [{}]",
1270 map->toString(),
1271 igd->toString(),
1272 igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001273}
1274
Adrien Beraud3bd61c92023-08-17 16:57:37 -04001275void
1276UPnPContext::updateMappingState(const Mapping::sharedPtr_t& map, MappingState newState, bool notify)
1277{
1278 // CHECK_VALID_THREAD();
1279
1280 assert(map);
1281
1282 // Ignore if the state did not change.
1283 if (newState == map->getState()) {
1284 // JAMI_DBG("Mapping %s already in state %s", map->toString().c_str(), map->getStateStr());
1285 return;
1286 }
1287
1288 // Update the state.
1289 map->setState(newState);
1290
1291 // Notify the listener if set.
1292 if (notify and map->getNotifyCallback())
1293 map->getNotifyCallback()(map);
1294}
1295
Adrien Béraud612b55b2023-05-29 10:42:04 -04001296#if HAVE_LIBNATPMP
1297void
1298UPnPContext::renewAllocations()
1299{
Adrien Béraud370257c2023-08-15 20:53:09 -04001300 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001301
1302 // Check if the we have valid PMP IGD.
1303 auto pmpProto = protocolList_.at(NatProtocolType::NAT_PMP);
1304
1305 auto now = sys_clock::now();
1306 std::vector<Mapping::sharedPtr_t> toRenew;
1307
1308 for (auto type : {PortType::TCP, PortType::UDP}) {
Adrien Béraud024c46f2024-03-02 23:53:18 -05001309 std::lock_guard lock(mappingMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001310 auto mappingList = getMappingList(type);
1311 for (auto const& [_, map] : mappingList) {
1312 if (not map->isValid())
1313 continue;
1314 if (map->getProtocol() != NatProtocolType::NAT_PMP)
1315 continue;
1316 if (map->getState() != MappingState::OPEN)
1317 continue;
1318 if (now < map->getRenewalTime())
1319 continue;
1320
1321 toRenew.emplace_back(map);
1322 }
1323 }
1324
1325 // Quit if there are no mapping to renew
1326 if (toRenew.empty())
1327 return;
1328
1329 for (auto const& map : toRenew) {
1330 pmpProto->requestMappingRenew(*map);
1331 }
1332}
1333#endif
1334
1335} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -04001336} // namespace dhtnet